From 8264ee7f23e4a4baa809521e3292144ccd0a5109 Mon Sep 17 00:00:00 2001 From: Packit Service Date: Dec 09 2020 08:46:54 +0000 Subject: bluez-5.52 base --- diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..e897eea --- /dev/null +++ b/AUTHORS @@ -0,0 +1,103 @@ +Maxim Krasnyansky +Marcel Holtmann +Stephen Crane +Jean Tourrilhes +Jan Beutel +Ilguiz Latypov +Thomas Moser +Nils Faerber +Martin Leopold +Wolfgang Heidrich +Fabrizio Gennari +Brad Midgley +Henryk Ploetz +Philip Blundell +Johan Hedberg +Claudio Takahasi +Eduardo Rocha +Denis Kenzior +Frederic Dalleau +Frederic Danis +Luiz Augusto von Dentz +Fabien Chevalier +Ohad Ben-Cohen +Daniel Gollub +Tom Patzig +Kai Vehmanen +Vinicius Gomes +Alok Barsode +Bastien Nocera +Albert Huang +Glenn Durfee +David Woodhouse +Christian Hoene +Pekka Pessi +Siarhei Siamashka +Nick Pelly +Lennart Poettering +Gustavo Padovan +Marc-Andre Lureau +Bea Lam +Zygo Blaxell +Forrest Zhao +Scott Talbot +Ilya Rubtsov +Mario Limonciello +Filippo Giunchedi +Jaikumar Ganesh +Elvis Pfutzenreuter +Santiago Carot-Nemesio +José Antonio Santos Cadenas +Francisco Alecrim +Daniel Orstadius +Anderson Briglia +Anderson Lizardo +Bruna Moreira +Brian Gix +Andre Guedes +Sheldon Demario +Lucas De Marchi +Szymon Janc +Syam Sidhardhan +Paulo Alcantara +Jefferson Delfes +Andrzej Kaczmarek +Eder Ruiz Maria +Mikel Astiz +Chan-yeol Park +João Paulo Rechi Vita +Larry Junior +Raymond Liu +Radoslaw Jablonski +Rafal Michalski +Dmitriy Paliy +Bartosz Szatkowski +Lukasz Pawlik +Slawomir Bochenski +Wayne Lee +Ricky Yuen +Takashi Sasai +Andre Dieb Martins +Cristian Rodríguez +Alex Deymo +Petri Gynther +Scott James Remnant +Jakub Tyszkowski +Grzegorz Kołodziejczyk +Marcin Krąglak +Łukasz Rymanowski +Jerzy Kasenberg +Arman Uguray +Artem Rakhov +Mike Ryan +David Herrmann +Jacob Siverskog +Sebastian Chłąd +Alex Gal +Loic Poulain +Gowtham Anandha Babu +Bharat Panda +Marie Janssen +Jaganath Kanakkassery +Michał Narajowski +Inga Stotlnad 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. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 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. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..8758e0e --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2283 @@ +ver 5.52: + Fix issue with AVDTP session disconnect timeout handling. + Mark media endpoint APIs as stable interfaces. + +ver 5.51: + Fix issue with first agent not being registered as default. + Fix issue with loading devices without Service Changed CCC. + Fix issue with GATT client and extended property reading. + Fix issue with handling GATT client invalid read behavior. + Fix issue with handling GATT disconnect handler removal. + Fix issue with missing GATT/GAP service records for SDP. + Fix issue with checking SDP continuation state length. + Fix issue with HID device removal on HoG disconnect. + Fix issue with AVDTP and session destroy handling. + Fix issue with AVCTP and output MTU accounting. + Fix issue with AVRCP and creating media items. + Add support for GATT database caching feature. + Add experimental support for Bluetooth Mesh Profile. + +ver 5.50: + Fix issue with GATT and reading long values. + Fix issue with GATT and reading multiple includes. + Fix issue with GATT and service changes when offline. + Fix issue with handling secondary service discovery. + Fix issue with handling persistency of CCC values. + Fix issue with handling Mesh session on disconnection. + Fix issue with handling Mesh proxy PDU SAR message length. + Fix issue with handling Mesh default heartbeat TTL value. + Add support for Mesh node-reset operation handling. + Add support for GATT authorization request handling. + Add support for GATT minimum key size requirements. + Add support for GATT server and included services. + Add support for handling additional advertising data. + Add support for handling separate discoverable state. + Add support for enabling HFP version 1.7 features. + Add support for dedicated Bluetooth logging daemon. + +ver 5.49: + Fix issue with configuring discoverable advertising flag. + Fix issue with bearer selection and single mode controllers. + Fix issue with Connect and ConnectProfile returning in progress. + Fix issue with missing Paired property change when not bonded. + Fix issue with storage for controllers without public address. + Fix issue with handling AVCTP disconnecting the channel queue. + Fix issue with not clearing connectable setting on power off. + Fix issue with creating multiple mgmt socket instances. + Fix issue with GATT server and BR/EDR only devices. + Fix issue with InterfaceAdded event ordering. + Add support for generic ConnectDevice method call. + Add support for Mesh heartbeat client functionality. + +ver 5.48: + Fix issue with subscriptions for unpaired devices. + Fix issue with handling A2DP and no available SEP. + Fix issue with handling AVCTP change path support. + Fix issue with handling AVCTP browsing channel. + Fix issue with handling AVCTP passthrough PDUs. + Fix issue with handling detaching of controller. + Fix issue with handling start discovery results. + Fix issue with handling non-connectable devices. + Fix issue with handling unused parameter in WriteValue. + Add support for service side AcquireWrite and AcquireNotify. + Add support for providing address type information. + Add support for cable based authentication and pairing. + Add support for Bluetooth Low-Energy battery service. + Add support for BTP client for qualification testing. + Add support for additional Mesh control functionality. + Mark advertising manager APIs as stable interfaces. + +ver 5.47: + Fix issue with handling AcquireNotify registration. + Fix issue with handling support for reconnection interval. + Fix issue with handling A2DP transport and accepting streams. + Fix issue with fallback from BR/EDR to LE bearer handling. + Add support for appearance and local name advertising data. + Add support for retrieving the supported discovery filters. + Add support for decoding Bluetooth 5.0 commands and events. + Add support for decoding Bluetooth Mesh advertising bearer. + Add support for Bluetooth Mesh control application. + +ver 5.46: + Fix issue with handling ATT over BR/EDR connections. + Fix issue with SDP browsing cleanup after connection. + Fix issue with pointer dereference and OPP Put request. + Fix issue with identity address updates during pairing. + Fix issue with not removing services that had disappeared. + Add support for improved discovery of included services. + Add support for simplified characteristics discovery. + Add support for GATT caching configuration option. + Add experimental support for AcquireWrite and AcquireNotify. + +ver 5.45: + Fix issue with agent support in Bluetooth client tool. + Fix issue with handling re-connection policy. + Fix issue with handling unknown ATT commands. + Fix issue with handling GATT Service Includes property. + Fix issue with handling PullAll for OBEX transfers. + Fix issue with handling delay in AVDTP Suspend responses. + Fix issue with handling decoding of management frames. + Add support for frame counters in Bluetooth monitor tool. + +ver 5.44: + Fix issue with GAP and GATT service registration. + Fix issue with wrong address type for ATT sockets. + Fix issue with dictionary entries for advertising. + Fix issue with device information and HID over GATT. + Fix issue with handling secondary service discovery. + Fix issue with handling Attribute Read Long procedure. + Fix issue with handling Attribute Write Long procedure. + Fix issue with handling abort of AVDTP SetConfiguration. + Add support for single-mode static address configuration. + Add support for MIDI over Bluetooth Low Energy. + +ver 5.43: + Fix issue with HID over GATT support. + Fix issue with ATT Find By Type response handling. + Fix issue with handling insufficient authentication. + Fix issue with bonding while pairing is in progress. + Fix issue with BR/EDR pairing for dual-mode devices. + Fix issue with handling profile policy resets. + Fix issue with connecting state of services. + Fix issue with handling PAN GN Master role. + Add support for enabling LE Privacy feature. + +ver 5.42: + Fix issue with PBAP call logs from different folders. + Fix issue with OBEX over L2CAP and PowerPC architecture. + Fix issue with BR/EDR over LE selection during discovery. + Fix issue with selection of bearer after bonding. + Fix issue with handling socket recv() return values. + Fix issue with setting connecting service state. + Fix issue with setting correct ATT default MTU value. + Fix issue with not setting AVRCP player identifier. + Fix issue with handling AVRCP browsable player. + Fix issue with addressing AVRCP player changes. + Add support for new management tracing capability. + Mark GATT D-Bus APIs as stable interfaces. + +ver 5.41: + Fix issue with service state changes handling. + Fix issue with AVRCP and no available player. + Fix issue with handling discovery filters. + Fix issue with handling temporary addresses. + Fix issue with GATT MTU size and BR/EDR links. + Fix issue with OBEX and creating directories. + +ver 5.40: + Fix issue with not storing GATT attributes. + Fix issue with optional GATT notifications. + Fix issue with reading GATT extended properties. + Fix issue with GATT device name properties. + Fix issue with previously paired devices. + Fix issue with handling device removal. + Fix issue with profile connection handling. + Add support for TTY monitor protocol. + +ver 5.39: + Fix issue with missing uHID kernel support. + Fix issue with GATT reliable write handling. + Fix issue with GATT service changed handling. + Fix issue with GATT execute write handling. + Fix issue with AVRCP player event handling. + Fix issue with AVRCP controller handling. + Fix issue with AVDTP connection handling. + Fix issue with AVDTP error handling. + +ver 5.38: + Fix issue with stack overflow and UUID handling. + Fix issue with ObjectManager interface and GATT. + Fix issue with GATT database and error handling. + Fix issue with GATT client notifications. + Fix issue with GATT object ordering. + Fix issue with GATT default MTU exchange. + Fix issue with device attribute clearing. + Fix issue with AVRCP capabilities request. + +ver 5.37: + Fix issue with registering external profiles. + Fix issue with connecting external profiles. + Fix issue with GATT service changed handling. + Fix issue with not emitting GattServices update. + Convert to unified HID over GATT profile support. + Convert to KeyboardDisplay as default IO capability. + Install btattach utility by default. + +ver 5.36: + Fix issue with PBAP headers for size query. + Fix issue with AVRCP current player handling. + Fix issue with device information handling. + Fix issue with device disconnect handling. + Fix issue with duplicate connect handling. + Fix issue with attribute claiming for drivers. + +ver 5.35: + Fix issue with connected devices after discovery. + Fix issue with profile support and LTK loading. + Fix issue with AVRCP events for volume control. + Fix issue with OBEX session owner handling. + Fix issue with HID over GATT setup failures. + Fix issue with GATT notification registration. + Fix issue with GATT cache validation feature. + Add support for persistent GATT database. + Add support for controller enabling option. + +ver 5.34: + Fix issue with GATT profiles and auto-connect. + Fix issue with missing GoepL2CapPsm SDP data. + Fix issue with suspending AVDTP endpoints. + Fix issue with audio service state on disconnect. + Add support for AVRCP Set Addressed Player feature. + Add support for AVRCP Get Folder Items feature. + Add support for Android 5.1 HFP WBS callbacks. + +ver 5.33: + Fix issue with memory leak in GATT database. + Fix issue with AVDTP set configuration handling. + Fix issue with AVDTP discover procedure. + Fix issue with not emitting Paired property. + +ver 5.32: + Fix issue with OPP GET request path handling. + Fix issue with ATT information request errors. + Fix issue with advertising instance numbers. + Fix issue with overwriting SDP record cache. + Fix issue with new connections during disconnect. + Add support for GATT security auto-elevation. + +ver 5.31: + Fix issue with crash in networking interface. + Fix issue with crash when creating endless GATT loops. + Fix issue with memory leak when connecting services. + Fix issue with memory leak creating new D-Bus proxy. + Fix issue with profile connections from remote devices. + Fix issue with GATT over BR/EDR and MTU notification. + Fix issue with HID and dual mode remote devices. + Fix issue with handling A2DP vendor codec setup. + Fix issue with AVRCP and syncing player state. + Fix issue with GATT secondary discovery handling. + Fix issue with wrong characteristic allocation. + Add support for handling BNEP setup response. + Add support for setting GATT database security flags. + Add support for setting discovery filters interface. + Add support for user controlled advertising interface. + Update Android qualification documentation to PTS 6.1 release. + +ver 5.30: + Fix compilation error in C++ due to inline function. + Fix issue with missing storage of device information. + Fix issue with GATT client and gaps in service handles. + Fix issue with AVDTP discovery callback crashing. + Fix issue with AVCTP channel handling in case of conflicts. + Fix issue with AVRCP target and get capabilities command. + Add experimental support for LE advertising manager API. + Add support for Android 5.1 GATT MTU exchange API. + +ver 5.29: + Fix issue with AVCTP initial key repeat timeout. + Fix issue with Android application disconnect handling. + Fix issue with Android support and service notifications. + Fix issue with Android support and Exchange MTU Request. + Fix issue with Android HFP support and AT+CMER handling. + Fix issue with Android HFP support and SLC setup. + Fix issue with Android HFP support and call hold status. + Fix issue with Android HFP support and indicator handling. + Fix issue with Android HFP support and SCO/eSCO disconnection. + Fix issue with Android HID over GATT support and battery service. + Fix issue with GATT sending Exchange MTU Request for BR/EDR. + Fix issue with GATT notification support without CCC. + Fix issue with GATT object life-time after disconnects. + Fix issue with GATT notification handling API. + Add experimental support for GATT client D-Bus API. + Add experimental support for GATT server D-Bus API. + Add support for Multi Profile Specification. + Update Android qualification documentation to PTS 6.0 release. + +ver 5.28: + Fix issue with GATT device discovery and probing. + Fix issue with bearer selection for dual-mode devices. + Fix issue with device removal while connected. + Fix issue with device name setting from inquiry response. + Fix issue with missing termination of name characteristic. + Fix issue with UTF-8 length handling for device name. + Fix issue with AVCTP key auto release handling. + Fix issue with AVCTP key press repetition handling. + Fix issue with payload sizes and GATT notifications. + Fix issue with memory corruption and GATT notifications. + Add support for HID proxy switching and CSR 8510 A10 devices. + Add support for Broadcom hex2hcd conversion utility. + +ver 5.27: + Fix issue with endian handling and management interface. + Fix issue with pending GATT operations when disconnecting. + Fix issue with 128-bit UUID conversions for HID over GATT. + Add support for Android 5.0 SELinux policies. + +ver 5.26: + Fix issue with handling A2DP XCASE connection state. + Fix issue with crash and A2DP configuration failures. + Fix issue with crash during OBEX session shutdown. + Add support for version 1.2 of Phonebook Access Profile. + Add support for HID over GATT get and set report handling. + Add support for Low Energy Secure Connections feature. + Add support for Bluetooth 4.2 commands and events. + Add support for Android 5.0 Bluetooth features. + +ver 5.25: + Fix issue with SCO connection after codec negotiation. + Fix issue with GATT and secondary service discovery. + Fix issue with GATT write descriptor callback. + Fix issue with MAP supported features bits. + Add support for MAP local time and timezone offset. + Add support for PBAP speed-dial and favorites folders. + Add support for PBAP speed-dial and identifier filters. + Add support for controller mode configuration option. + Add initial support for Android Lollipop features. + +ver 5.24: + Fix issue with storing of connection parameters. + Add support for Phonebook Access Profile 1.2 features. + Add support for Message Access Profile 1.2 event reports. + Add support for Android Bluetooth configuration options. + +ver 5.23: + Fix issue with concurrent authorization requests. + Fix issue with HID report identifier mismatch. + Fix issue with crash when receiving uHID events. + Fix issue with crash and OBEX disconnect handling. + Fix issue with OBEX client transfers and suspend. + Fix issue with parsing of MAP application parameters. + Fix issue with devices rejecting AVRCP GetCapabilities. + Add support for kernel whitelist and Android Bluetooth. + +ver 5.22: + Fix issue with UHID_OUTPUT events mapping. + Fix issue with UHID_FEATURE events handling. + Fix issue with UINT32_MAX overflow and AVRCP. + Fix issue when dirent type DT_UNKNOWN is returned. + Add support for kernel whitelist filtering feature. + Add support for Android Bluetooth GATT over BR/EDR. + +ver 5.21: + Fix issue with SDP requests and wrong PDU size. + Fix issue with handling passive scanning triggers. + Add support for storing and loading connection parameters. + Add support for kernel background auto-connection feature. + Add support for Android Bluetooth Scan Parameters feature. + Add support for Android Bluetooth Device Information feature. + Add support for Android Bluetooth Health Device interface. + +ver 5.20: + Fix issue with LED handling of PS3 controllers. + Add support for Android Bluetooth GATT server interface. + Add support for Android Bluetooth HID over GATT feature. + Add support for Android Bluetooth multi-profile feature. + Add support for Android Bluetooth aptX audio integration. + + Note: aptX codec not included + +ver 5.19: + Fix issue with OBEX Put-Delete and Create-Empty methods. + Fix issue with AVRCP browsable/searchable player properties. + Fix issue with handling multiple default agents. + Fix issue with handling unpair event per bearer. + Fix issue with HID over GATT report ID presence. + Add support for HID protocol handling in userspace. + Add support for Bluetooth reconnection policy framework. + Add support for Android Bluetooth SCO over HCI transport. + Add support for Android Bluetooth audio quality control. + Add support for Android Bluetooth Low Energy only mode. + +ver 5.18: + Fix issue with identifying LE single mode devices. + Fix issue with L2CAP and RFCOMM peer address lookup. + Add support for handling OBEX authentication procedure. + Add support for Android Bluetooth GATT client interface. + +ver 5.17: + Fix issue with not resetting OBEX SRM setup. + Fix issue with BR/EDR devices and auto-connect list. + Fix issue with bonding complete detection as peripheral. + Fix issue with not updating bearer timestamp of connections. + Fix issue with paired property for multiple bearers. + Add support for Android Bluetooth Handsfree interface. + Add support for Android Bluetooth Wideband speech. + +ver 5.16: + Fix issue with HID over GATT physical location. + Fix issue with HID over GATT unique identifier. + Fix issue with missing paired property notification. + Fix issue with endianess of long term key storage. + Add support for storing signature resolving keys. + Add support for Android Bluetooth AVRCP interface. + +ver 5.15: + Fix issue with LE enabling and background scanning. + Fix issue with HID over GATT input device name. + Fix issue with storage of slave long term keys. + Add support for handling identity resolving keys. + Add support for Android Bluetooth A2DP interface. + Add support for Android Bluetooth audio interface. + +ver 5.14: + Fix issue with marking PS3 controllers as trusted. + Fix issue with authorization of PS3 controllers. + Add support for DualShock 4 controller detection. + Add support for legacy pairing emulation. + Add support for secure simple pairing emulation. + Add support for automated pairing testing. + Add support for RFCOMM protocol testing. + Add support for HCI controller testing. + +ver 5.13: + Fix issue with PS3 controller detection. + Add support for data transfers to L2CAP testing tool. + Add support for delay reporting to AVDTP testing tool. + Add support for Android Bluetooth Core interface. + Add support for Android Bluetooth Socket interface. + Add support for Android Bluetooth HID Host interface. + Add support for Android Bluetooth PAN interface. + +ver 5.12: + Fix issue with missing reply to DisconnectProfile. + Fix issue with icon property and class of device changes. + Fix issue with HID devices when SDP record is not available. + Fix issue with handling auto-pairing of printers. + Fix issue with agent authorization handling. + Add support for PS3 controller setup and pairing. + Add support for LE L2CAP CoC test capabilities. + Add support for AVDTP qualification test cases. + Add support for SMP cryptographic test cases. + +ver 5.11: + Fix issue with connection attempt when not powered. + Fix issue with assigning player to AVRCP target role. + Fix issue with OBEX default cache directory. + Fix issue with SDP search error handling. + Fix issue with processing of SDP records. + Fix issue with HID to HCI switching utility. + Fix issue with mgmt end-to-end testing tool. + Fix issue with L2CAP end-to-end testing tool. + Add support for SMP end-to-end testing tool. + Add support for more Wii controllers. + +ver 5.10: + Fix issue with discoverable timeout handling. + Fix issue with MAP messages and record version. + Fix issue with MAP messages and status events. + Fix issue with MAP messages and relative folders. + Fix issue with MAP messages and type property signals. + Fix issue with transfer size for OBEX GET operations. + Fix issue with AVRCP service class identifier. + Fix issue with AVRCP tracking seeked signal. + Add support for OBEX command line client. + +ver 5.9: + Fix issue with network service and adapter removal. + Fix issue with misleading OBEX error messages. + Fix issue with OBEX transport reference handling. + Fix issue with memory leak with MAP event handler. + Fix issue with missing MAP property changed signal. + Fix issue with message type property values. + Fix issue with empty UUID list for devices. + Fix issue with profile agent cancel method. + Remove dependency on USB library. + +ver 5.8: + Fix issue with missing OBEX session properties. + Fix issue with missing SDP service refresh. + Fix issue with SDP attribute range check. + Fix issue with priority for SDP transactions. + Fix issue with service discovery after pairing. + Fix issue with race condition in service list. + Fix issue with input service state transition. + Fix issue with default authorization for profiles. + Fix issue with AVRCP browsing channel connections. + Add support for AVRCP role agnostic sessions. + +ver 5.7: + Fix issue with missing UUID discovery during pairing. + Fix issue with broken patch for SDP range check handling. + Fix issue with AVRCP usage of UID=0 for paused/stopped. + Add support MAP notification dispatching. + +ver 5.6: + Fix issue with incoming connections without SDP record. + Fix issue with canceling ongoing device connections. + Fix issue with handling failed connection attempts. + Fix issue with pending resume during A2DP open failures. + Fix issue with registering AVRCP unsupported notification. + Fix issue with listing available AVRCP target settings. + Fix issue with missing error for OBEX SetPath commands. + Fix issue with missing OBEX session command queue. + Fix issue with retrieving multiple MAP event reports. + Add support for command line player utility. + +ver 5.5: + Fix issue with race condition between SDP and properties. + Fix issue with handling storage of private device addresses. + Fix issue with NFC out-of-band pairing and power states. + Fix issue with short name during device update handling. + Fix issue with handling AVRCP without A2DP being present. + Add support for handling AVRCP pass-through operations. + Add support for automatically reconnecting HID devices. + Add support for automatically pairing of devices. + +ver 5.4: + Fix issue with invalid memory access and SDP service search. + Add support for available player changed event for controller. + Add support for UIDs changed event for AVRCP controller. + Add support for mandatory AVRCP pass-through operations. + Add support for Message Notification Service (MNS) server. + Add support for agent methods within command line client. + +ver 5.3: + Fix issue with registering invalid profiles. + Fix issue with inconsistent A2DP transport state. + Fix issue with A2DP resume while in configured state. + Fix issue with buffer overflow when processing SDP response. + Fix issue with missing range check for SDP attribute response. + Fix issue with missing validation of SDP data elements. + Fix issue with missing fallback to static hostname. + Fix issue with default adapter assignment. + +ver 5.2: + Fix issue with connection handling for Low Energy. + Fix issue with broken device discovery handling. + Fix issue with invalid memory access within A2DP. + Fix issue with handling empty path name of SetPath. + Fix issue with handling Message Access Profile filters. + Fix issue with handling network service unregistration. + Fix issue with not handling bogus device pairing results. + Fix issue with initial service discovery and profile manager. + Add support for AVRCP volume notifications. + Add support for AVRCP browsing commands. + +ver 5.1: + Fix issue with crash when removing OBEX session. + Fix issue with HID device disconnected from kernel. + Fix issue with buffer overflow when parsing HID SDP record. + Fix issue with SDP_TEXT_STR16 and SDP_URL_STR16 parsing. + Add support for integration with systemd's hostname daemon. + Add support for separate adapter alias property. + Add support for adapter and device modalias properties. + Add support for official BlueZ device information. + Add support for asynchronous management interface handling. + Add tool for testing management interface compliance. + Add tool for testing SDP qualification requirements. + Add tool for testing various EIR and AD data records. + +ver 5.0: + Introduce D-Bus Properties and ObjectManager interfaces. + Add support for generic profile interface. + Add support for global agent interface. + Add support for integrated OBEX daemon. + Add support for integrated hcidump utility. + Add support for Bluetooth tracing and monitor utility. + Add support for Bluetooth command line client utility. + Remove support for Handsfree gateway handling. + Remove support for GStreamer A2DP and SBC elements. + Disable default installation of Bluetooth library. + +ver 4.101: + Fix issue with missing BlueZ service file. + Fix issue with aborting A2DP setup during AVDTP start. + Fix issue with handling of multiple A2DP indication. + Fix issue with handling AVDTP abort with invalid SEID. + Fix issue with rejecting AVDTP abort commands. + Add support for handling AVDTP command collision. + +ver 4.100: + Fix issue with crashing when SCO connection fails. + Fix issue with HFP gateway failing on first GSM connection. + Fix issue with AVRCP and handling of vendor commands. + Fix issue with handling AVRCP subunit info command. + Fix issue with missing capability for AVRCP track reached end. + Fix issue with AVDTP signaling and GStreamer SBC NULL check. + Fix issue with AVDTP Reconfigure Reject message. + Fix issue with incorrect EIR length parsing. + Fix issue with SDP disconnect for HIDSDPDisable. + Fix issue with SDP interoperability with Mac OS X Lion. + Fix issue with reverse SDP discovery with some devices. + Fix issue with discovering state during power off operation. + Add support for AVRCP Volume Changed notifications. + Add support for AVRCP Set Absolute Volume handling. + Add support for display legacy PIN code agent method. + Add support for multiple media transports per endpoint. + Add support for discovering device information characteristics. + Add support for vendor source for Device ID setting. + Add support for immediate alert server. + Add support for link loss server. + + Notes: + This version requires D-Bus 1.4 or later. + This version requires GLib 2.28 or later. + +ver 4.99: + Fix issue with missing retries for BNEP connection setup. + Fix issue with not showing name if first EIR has no details. + Fix issue with running SDP discovery for LE devices. + Add support for GATT using 128-bit Bluetooth UUIDs. + Add support for retrieving key size information. + Add support for storing Long Term Keys. + Add support for Proximity Reporter API. + Add support for KeyboardDisplay IO capability. + Add support for version 1.0 of management API. + Add support for monitoring interface. + +ver 4.98: + Fix issue with adapter list upon initialization failure. + Fix issue with missing legacy property for Low Energy. + Fix issue with missing EIR information handling. + Fix issue with device address type tracking. + Fix issue with alert level characteristic. + Fix issue with headset shutdown handling. + Fix issue with Wiimote address handling. + Add support for advanced l2test options. + Add support for attribute protocol and multiple adapters. + +ver 4.97: + Update support for proximity profile. + Fix issue with SBC audio decoding quality. + Fix multiple issues with HFP support. + Fix multiple issues with A2DP support. + Fix multiple issues with AVDTP support. + Fix multiple issues with AVRCP support. + Add support for AVRCP meta-data transfer. + Add support for Bluetooth based thermometers. + +ver 4.96: + Fix issue with race condition in AVDTP stream start. + Fix issue with global adapter offline switching. + Fix issue with pairing and No Bonding devices. + Add support for Nintendo Wii Remote pairing. + +ver 4.95: + Fix issue with AVCTP replies with invalid PID. + Fix issue with AVRCP and unknown packet types. + Fix issue with AVRCP not using NOT_IMPLEMENTED correctly. + Fix issue with AVDTP discovery if all endpoints are in use. + Fix issue with invalid memory writes and media support. + Fix issue with not removing device alias and unbonding. + Fix issue with device disconnects and offline mode handling. + Add support for setting adapter name based on machine-info. + Add support for systemd service configuration. + +ver 4.94: + Fix issue with invalid read of memory in various modules. + Fix issue with buffer overflow when sending AVDTP commands. + Fix issue with response to vendor dependent AVRCP commands. + Fix issue with headset when not able to reply with ERROR. + Fix issue with crash when creating a device from storage. + Fix issue with handling non UTF-8 devices names. + Add support for improved discovery procedure. + +ver 4.93: + Fix issue with property type and Health Main channel. + Fix issue with crash when removing devices. + Add support for hid2hci and udev integration. + +ver 4.92: + Fix issue with handling of A2DP suspend response. + Fix issue with crashing when acquiring A2DP stream. + Fix issue with missing check for valid SCO before shutdown. + Fix issue with waiting for POLLERR when disconnecting SCO. + Fix issue with disconnect after primary service discovery. + Fix issue with attribute interface registration. + Add support for primary services over BR/EDR. + Add support for flushable packets of A2DP media. + +ver 4.91: + Fix issue with LMP version string and hciconfig. + Fix issue with missing discovery signal when scanning. + Fix issue with wrong state and canceling name resolving. + Fix issue with missing check during adapter initialization. + Fix issue with missing protocol not supported error and A2DP. + Fix issue with crash during driver unregistering and A2DP. + Fix issue with crash when receiving AVDTP close command. + Fix issue with remote SEP handling when A2DP codec changes. + Fix issue with SCO hangup handling and state changes. + Fix issue with security level and MCAP instances. + Fix issue with memory leak and HDP data channels. + Add support for discover characteristics by UUID to gatttool. + Add initial support for Out-of-Band association model. + Add initial support for SIM Access Profile. + +ver 4.90: + Fix issue with setting of global mode property. + Fix issue with handling of RequestSession responses. + Fix issue with TP_BNEP_CTRL_BV_01_C qualification test. + Fix issue with too short AVDTP request timeout. + Add support for SIM Access Profile manager. + Add support for new UUID utility functions. + Add support for attribute server notifications. + Add support for client characteristic configuration. + Update support for interactive GATT utility. + +ver 4.89: + Fix issue with name resolving when discovery is suspended. + Fix issue with parsing flags of advertising report. + Fix issue with SEP handling if interface is disabled. + Fix issue with device object creation on disconnect event. + Fix issue with indicators whenever the driver is initialized. + Fix issue with call indicator when parsing call info reply. + Fix issue with crash and allowed GATT MTU was too large. + Add support for SDP record of Primary GATT services. + Add support for interactive mode for GATT utility. + +ver 4.88: + Fix issue with HID channel reference count handling. + Fix issue with daemon exit on badly formatted AT+VTS. + Fix issue with crash while parsing of endpoint properties. + Fix issue with possible crash on AVDTP Suspend request timeout. + Fix issue with stopping inquiry before adapter is initialized. + Fix issue with creating device object when connection fails. + Fix issue with sending HCIDEVUP when adapter is already up. + Fix issue with handling bonding IO channel closing. + Fix agent cancellation in security mode 3 situations. + Update pairing code to support management interface. + +ver 4.87: + Fix issue with initialization when adapter is already up. + Fix issue with attribute server MTU and incoming connections. + Fix issue with duplicate characteristics after discovery. + +ver 4.86: + Revert wrong fix for SDP PDU size error response. + Fix various memory leaks in A2DP and AVDTP support. + Add Routing property to MediaTransport interface + Add proper tracking mechanism to NREC status. + Add READ_BLOB_REQUEST support to attribute server. + +ver 4.85: + Fix issue with event mask setting for older adapters. + Fix issue with device creation and pairing failures. + Add support for telephony support via oFono. + Add support for characteristic security level. + Update support for service registration. + +ver 4.84: + Fix issue with wrong parameters and device found signals. + Fix issue with leaking EIR data if RSSI does not change. + Fix issue with adapter initialization state. + Fix issue with closing of SDP server sockets. + +ver 4.83: + Fix issue with already connected HFP/HSP endpoints. + Fix missing reply when create device is canceled. + Fix memory leak within the attribute server. + Fix memory leak with unused extended inquiry name. + Fix setting paired state when device->authr is false. + Fix clearing authentication request for renewed keys. + Add support for storing link keys in runtime memory. + Update support for primary service discovery. + +ver 4.82: + Fix crash with mmap of files with multiples of page size. + Fix HFP response and hold (AT+BTRH) command response. + Fix device creation error response when powered off. + Fix device removal when connecting/browsing fails. + Add initial attribute permission implementation. + Add AVDTP SRC stream send buffer size verification. + Add support for setting link policy based on features. + +ver 4.81: + Fix issue with telephony driver initialization. + Fix issue with adapter services list initialization. + Fix crash after simultaneous authentication requests. + Add support for primary service search on device creation. + +ver 4.80: + Fix legacy link key storing for some buggy adapters. + Fix invalid memory access when EIR field length is zero. + Fix adapter initialization to wait for kernel HCI commands. + Fix initialization of adapters which are already up. + Fix possible race condition when initializing adapters. + Fix possible crashes when attempting to connect AVDTP. + Fix not aborting sink stream configuration on disconnect. + Fix not indicating disconnected state when connecting to AVDTP. + Fix not dropping AVDTP session when canceling stream setup. + Fix AVDTP abort not being send when the state is idle. + Fix regression with Low Energy and interleave discovery. + Add a new configuration option to disable Low Energy support. + Add iwmmxt optimization for SBC for ARM PXA series CPUs. + Update support for GATT Primary Service Discovery. + Update MCAP and HDP support. + +ver 4.79: + Fix issue with adapter initialization race condition. + Update new Bluetooth Management interface support. + +ver 4.78: + Fix various issues with AVDTP timer handling. + Fix various issues with handling of mode changes. + Fix issue with audio disconnect watch in connecting state. + Fix issue with handling call waiting indicators in telephony. + Fix issue with handling UUID parameter and RegisterEndpoint. + Add initial support for Bluetooth Management interface. + Add support for Application property to HealthChannel. + +ver 4.77: + Fix issue with device name and accessing already freed memory. + Fix issue with handling CHLD=0 command for handsfree. + Fix issue with manager properties and no adapters. + Fix issue with properties and broken service records. + Fix issue with A2DP playback and sample rate changes. + Update MCAP and HDP support. + +ver 4.76: + Fix issue in telephony driver with hanging up held call. + Fix issue in telephony driver with notifications when on hold. + Fix issue with blocking on setconf confirmation callback. + Fix issue with not always signaling new streams as sinks. + Fix issue with errors in case of endpoint request timeout. + Fix issue with HFP/HSP microphone and speaker gain values. + Add source if the device attempt to configure local sink stream. + Add PSM option for GATT/ATT over BR/EDR on gatttool. + Add support for GATT/ATT Attribute Write Request. + Update MCAP and HDP support. + +ver 4.75: + Fix use of uninitialized variable on legacy pairing. + Fix mismatch of attribute protocol opcode. + +ver 4.74: + Fix regression for Legacy Pairing. + Fix wrong PSM value for attribute protocol. + Fix issue with RSSI field in advertising reports. + Add support for Add BR/EDR and LE interleaved discovery. + Add support for GATT write characteristic value option. + Add support for specifying download address for AR300x. + +ver 4.73: + Fix problem with EIR data when setting the name. + Fix reading local name from command complete event. + Fix registering local endpoints with disabled socket interface. + Add support for more HCI operations using ops infrastructure. + Add support for GATT characteristic hierarchy. + Add support for GATT indications. + +ver 4.72: + Fix memory leak while connecting BTIO channels. + Fix crash with GStreamer plugin if SBC is not supported. + Fix issue with GATT server stop sending notifications. + Fix issue with GATT and dealing with the minimum MTU size. + Fix issue with file descriptor leak in GATT client. + Add support for UUID 128-bit handling in attribute client. + Add support for encoders/decoders for MTU Exchange. + Add support for the MTU Exchange procedure to the server. + Add support for a per channel MTU to the ATT server. + Add support for Characteristic interface. + Add support for new Media API and framework. + Add initial support for HDP plugin. + +ver 4.71: + Fix compilation when SBC support in not enabled. + Fix crash with RequestSession and application disconnects. + Fix memory leak and possible crash when removing audio device. + Fix issue with closing stream of locked sep when reconfiguring. + Fix issue where discovery could interfere with bonding. + Fix issue with Connected status when PS3 BD remote connects. + Fix issue with lifetime of fake input devices. + Add support for compile time option of oui.txt path. + Add support for printing IEEE1284 device ID for CUPS. + Add plugin for setting adapter class via DMI. + Add more features for attribute protocol and profile. + Add initial support for MCAP. + +ver 4.70: + Fix incoming call indication handling when in WAITING state. + Fix various SDP related qualification test case issues. + Fix logic to write EIR when SDP records are changed. + Fix UTF-8 validity check for remote names in EIR. + Add support for UUID-128 extended inquiry response. + Add service UUIDs from EIR to the DeviceFound signal. + Add fast connectable feature for Handsfree profile. + Add HCI command and event definitions for AMP support. + Add firmware download support for Qualcommh devices. + Add host level support for Atheros AR300x device. + Add initial support of ATT and GATT for basic rate. + +ver 4.69: + Fix issue with calling g_option_context_free() twice. + Fix inconsistencies with initial LE commands and events. + Add support for telephony ClearLastNumber method. + Add support for network server interface. + +ver 4.68: + Fix initialization of adapters in RAW mode. + Fix signal strength for HFP in Maemo's telephony support. + Add support for following the radio state via Maemo's MCE. + Add initial set of LE commands and events definitions. + Add mode option for L2CAP sockets to the BtIO API. + +ver 4.67: + Fix issue with authentication reply when bonding already completed. + Fix issue with not canceling authentication when bonding fails. + Fix issue with changed combination keys and temporary storage. + Fix issue with sdp_get_supp_feat library function. + Fix issue with missing unblock on device removal. + Fix issue with not waiting for mode change completion. + Add ARMv6 optimized version of analysis filter for SBC encoder. + +ver 4.66: + Fix regression with full debug enabling via SIGUSR2. + Fix redundant speaker/microphone gains being sent. + Fix not emitting PropertyChanged for SpeakerGain/MicrophoneGain. + Fix issue with storage usage when a record is not found in memory. + Fix issue with DiscoverServices not retrieving any records. + Fix audio profile disconnection order to match whitepaper. + Fix auto-accept confirmation when local agent has NoInputNoOutput. + Fix remote just-works SSP when MITM protection is required. + Fix performing dedicated bonding without MITM requirement. + Add support for storing debug link keys in runtime memory. + +ver 4.65: + Fix issues with general bonding being default setting now. + Fix driver removal upon device removal. + Add new "Blocked" property to device objects. + Add hciconfig support for blacklisting. + Add support for dynamic debug feature. + +ver 4.64: + Fix invalid memory access in headset_get_nrec function. + Fix issue with disconnect event on higher protocol layers. + Fix issue with list parsing in sdp_set_supp_features function. + Fix device object reference counting for SDP browse requests. + Add missing memory checks whenever memory is allocated for SDP. + Add support for exporting local services via D-Bus. + Add more L2CAP Enhanced Retransmission test options. + +ver 4.63: + Fix avdtp_abort not canceling pending requests. + Fix stale connection when abort gets rejected. + +ver 4.62: + Fix accidental symbol breakage with inquiry transmit power. + Fix using invalid data from previous headset connection. + Fix double free on AVDTP Abort response. + Fix possible crash while verifying AVDTP version. + Fix missing inuse flag when AVDTP stream is configured. + Add support for Bluetooth controller types. + +ver 4.61: + Fix issues with Read Inquiry Response Transmit Power Level. + Fix possible invalid read when removing a temporary device. + Fix mode restoration when remember_powered is false. + Fix conference call releasing in telephony-maemo. + Fix segmentation fault with authorization during headset disconnects. + Add support for handling unanswered AVDTP request on disconnect. + Add support for handling Inquiry Response Transmit Power Level. + Add support for caching of remote host features. + Add preliminary voice dialing support for HSP. + +ver 4.60: + Fix voice mailbox number reading from SIM. + Fix some races with D-Bus mainloop integration. + Add helpers for D-Bus signal watches. + +ver 4.59: + Add values for Bluetooth 4.0 specification. + Add SDP functions for HDP support. + Add test scripts for input and audio. + Fix missing close on BtIO create_io function. + Fix sending incorrect AVDTP commands after timeout occurs. + Fix timer removal when device disconnects unexpectedly. + Fix Extended Inquiry Response record for Device ID. + +ver 4.58: + Fix crash when adapter agent exists during authentication. + Fix CK-20W quirks for play and pause events. + +ver 4.57: + Fix unloading of drivers for uninitialized adapters. + Fix debug message to use requested and not opened SEID. + Fix codec selection for GStreamer plugin. + Fix deleting of SDP records during service updates. + Fix deleting of SDP records when a device is removed. + Fix handling when the SDP record is modified on remote device. + Fix potential buffer overflow by using snprintf instead of sprintf. + Fix const declarations for some storage function parameters. + +ver 4.56: + Add missing values from Bluetooth 3.0 specification. + Add proper tracking of device paired status. + Fix tracking of devices without permanently stored link key. + Fix issue with link key removal after connection failures. + Fix legacy pairing information based on remote host features. + Fix off-by-one issue with AVDTP capability parsing. + Fix AVRCP, AVCTP, AVDTP, A2DP and HFP version numbers. + Fix agent canceling before calling agent_destroy. + Fix service record parsing with an empty UUID list. + Fix various SDP related memory leaks. + +ver 4.55: + Add support for POSIX capabilities dropping. + Add special quirk for the Nokia CK-20W car kit. + Fix error code handling for AVDTP SetConfiguration response. + Fix updating out of range list when RSSI hasn't changed. + Fix various memory leaks and unnecessary error checks. + +ver 4.54: + Add introspection interface to output of introspection calls. + Fix stream handling when media transport disconnects prematurely. + Fix command timeout handling when there's no stream. + Fix headset_suspend_stream behavior for invalid states + Fix issue with AVDTP ABORTING state transition. + Fix issue with AVDTP suspend while closing. + +ver 4.53: + Fix issue with telephony connection state notifications. + Fix AVDTP stream leak for invalid media transport config. + Fix audio connection authorization handling with timeouts. + Fix race condition in authorizing audio connections. + Fix device authorized setting for AVRCP-only connections. + Fix duplicate attempts from device to connect signal channel. + +ver 4.52: + Add AVCTP support to test utility. + Fix AVDTP Abort when transport closes before response. + Fix authorization when the audio profiles are slow to connect. + Fix potential AVDTP reference leaks. + +ver 4.51: + Add utility for basic AVDTP testing. + Add support for configuring L2CAP FCS option. + Fix discovery mode for CUPS 1.4.x and later. + Fix global state tracking of audio service. + Fix last issues with the new build system. + +ver 4.50: + Fix issue with missing manual pages in distribution. + Fix issue with the configuration and state directories. + Fix issue with creating include directory. + Fix dependencies of include file generation. + +ver 4.49: + Add simple test program for basic GAP testing. + Add support for confirmation requests to agent example. + Add support for full non-recursive build. + Add five millisecond delay for Simple Pairing auto-accept. + Fix Class of Device setting when InitiallyPowered=false. + +ver 4.48: + Add library function for comparing UUID values. + Add support for creating all plugins as builtins. + Add support for async handling of service class changes. + Add support for source interface to audio IPC. + Fix device name settings when device is off or down. + Fix issue with enabled SCO server when not necessary. + Fix missing D-Bus access policy for CUPS backend. + Fix discovery results of CUPS backend. + Fix initialization handling of Maemo telephony. + +ver 4.47: + Add support for RFKILL unblock handling. + Add support for serial proxy configurations. + Add support for caching service class updates. + Fix issues with updating SDP service records. + Fix usage of limited discoverable mode. + Remove deprecated methods and signals for AudioSource. + +ver 4.46: + Add support for A2DP sink role. + Fix clearing svc_cache before the adapter is up. + Fix various pointer after free usages. + Fix various memory leaks. + +ver 4.45: + Fix UDEV_DATADIR fallback if pkg-config fails. + Fix adapter cleanup and setup prototypes. + Fix double-free with out-of-range devices. + Fix inband ring setting to be per-headset. + Fix handling of Maemo CSD startup. + +ver 4.44: + Add some missing manual pages. + Fix missing number prefix when installing udev rules. + Fix program prefix used in Bluetooth udev rules. + Fix three-way calling indicator order. + Fix downgrade/upgrade of callheld indicator. + Fix +CIEV sending when indicator value changes. + Fix signal handling for Maemo telephony driver. + Fix parsing issues with messages from Maemo CSD. + Fix issue with duplicate active calls. + +ver 4.43: + Add support for udev based on-demand startup. + Fix verbose error reporting of CUPS backend. + Fix various string length issues. + Fix issues with Maemo telephony driver. + Fix another device setup and temporary flag issue. + Fix and update example agent implementation. + +ver 4.42: + Add TI WL1271 to Texas Instruments chip list. + Add special udev mode to bluetoothd. + Fix regression when there is no agent registered. + Fix error return when bonding socket hang up. + Fix SCO server socket for HFP handsfree role. + Fix shutdown on SCO socket before closing. + Fix shutdown on A2DP audio stream channel before closing. + Fix issue with asserting on AVDTP reference count bugs. + Fix authorization denied issue with certain headsets. + Fix AVRCP UNITINFO and SUBUNIT INFO responses. + Fix discovery cancel issues in case SDP discovery fails. + +ver 4.41: + Fix pairing even if the ACL gets dropped before successful SDP. + Fix regression which caused device to be removed after pairing. + Fix HSP record fetching when remote device doesn't support it. + Fix SDP discovery canceling when clearing hs->pending. + Fix headset never connecting on the first attempt. + Fix headset state tracking if bt_search_service() fails. + Fix maximum headset connection count check. + Fix AVDTP Discover timeout handling. + Fix also UI_SET_KEYBIT for the new pause and play key codes. + +ver 4.40: + Add telephony driver for oFono telephony stack. + Add support for Dell specific HID proxy switching. + Add support for running hid2hci from udev. + Add mapping for AVRCP Play and Pause to dedicated key codes. + Fix AVRCP keycodes to better match existing X keymap support. + Fix various quoting issues within telephony support. + Fix memory allocation issue when generating PDUs for SDP. + Fix race condition on device removal. + Fix non-cancelable issue with CreateDevice method. + Fix non-working CancelDiscovery method call. + +ver 4.39: + Add workaround for dealing with unknown inquiry complete. + Fix discovering when using software scheduler. + Fix wrong NoInputNoOutput IO capability string. + Fix race condition with agent during pairing. + Fix agent cancellation for security mode 3 acceptor failure. + Fix temporary flag removal when device creation fails. + Fix hciattach to use ppoll instead of poll. + Fix service class update when adapter is down. + Fix service classes race condition during startup. + Fix release of audio client before freeing the device. + +ver 4.38: + Add support for builtin plugins. + Add framework for adapter operations. + Add constants for Enhanced Retransmission modes. + Fix HCI socket leak in device_remove_bonding. + Fix various format string issues. + Fix crashes with various free functions. + Fix issues with Headset and A2DP drivers to load again. + Fix sending AVRCP button released passthrough messages + Fix bug which prevent input devices to work after restart. + Fix issue with interpretation of UUID-128 as channel. + +ver 4.37: + Add version value for Bluetooth 3.0 devices. + Add additional L2CAP extended feature mask bits. + Add support for loading plugins in priority order. + Add support for more detailed usage of disconnect watches. + Add support for AVRCP volume control. + Add saturated clipping of SBC decoder output to 16-bit. + Fix potentially infinite recursion of adapter_up. + Fix SCO handling in the case of an incoming call. + Fix input service to use confirm callback. + Fix cleanup of temporary device entries from storage. + +ver 4.36: + Add proper tracking of AVCTP connect attempts. + Add support to channel pattern in Serial interface. + Fix A2DP sink crash if removing device while connecting. + Fix error handling if HFP indicators aren't initialized. + Fix segfault while handling an incoming SCO connection. + Fix Serial.Disconnect to abort connection attempt. + +ver 4.35: + Add support for Handsfree profile headset role. + Add additional checks for open SEIDs from clients. + Fix device removal while audio IPC client is connected. + Fix device removal when an authorization request is pending. + Fix incoming AVDTP connect while authorization in progress. + Fix disconnection timers for audio support. + Fix various potential NULL pointer deferences. + Fix callheld indicator value for multiple calls. + Fix voice number type usage. + Fix GDBus watch handling. + +ver 4.34: + Add support for version checks of plugins. + Add support for class property on adapter interface. + Add support for second SDP attempt after connection reset. + Add support for more detailed audio states. + Add support for HFP+A2DP auto connection feature. + Add support for new and improved audio IPC. + Add program for testing audio IPC interface. + Fix various AVDTP qualification related issues. + Fix broken SDP AttributeIdList parsing. + Fix invalid memory access of SDP URL handling. + Fix local class of device race conditions. + Fix issue with periodic inquiry on startup. + Fix missing temporary devices in some situations. + Fix SBC alignment issue for encoding with four subbands. + +ver 4.33: + Add Paired property to the DeviceFound signals. + Add support for Headset profile 1.2 version. + Fix broken network configuration when IPv6 is disabled. + Fix network regression that caused disconnection. + Fix SDP truncation of strings with NULL values. + Fix service discovery handling of CUPS helper. + +ver 4.32: + Fix broken SDP record handling. + Fix SDP data buffer parsing. + Fix more SDP memory leaks. + Fix read scan enable calls. + Fix A2DP stream handling. + +ver 4.31: + Add support for new BtIO helper library. + Fix AVDTP session close issue. + Fix SDP memory leaks. + Fix various uninitialized memory issues. + Fix duplicate signal emissions. + Fix property changes request handling. + Fix class of device storage handling. + +ver 4.30: + Add CID field to L2CAP socket address structure. + Fix reset of authentication requirements after bonding. + Fix storing of link keys when using dedicated bonding. + Fix storing of pre-Bluetooth 2.1 link keys. + Fix resetting trust settings on every reboot. + Fix handling of local name changes. + Fix memory leaks in hciconfig and hcitool + +ver 4.29: + Use AVRCP version 1.0 for now. + Decrease AVDTP idle timeout to one second. + Delay AVRCP connection when remote device connects A2DP. + Add workaround for AVDTP stream setup with broken headsets. + Add missing three-way calling feature bit for Handsfree. + Fix handsfree callheld indicator updating. + Fix parsing of all AT commands within the buffer. + Fix authentication replies when disconnected. + Fix handling of debug combination keys. + Fix handling of changed combination keys. + Fix handling of link keys when using no bonding. + Fix handling of invalid/unknown authentication requirements. + Fix closing of L2CAP raw socket used for dedicated bonding. + +ver 4.28: + Add AVDTP signal fragmentation support. + Add more SBC performance optimizations. + Add more SBC audio quality improvements. + Use native byte order for audio plugins. + Set the adapter alias only after checking the EIR data. + Fix auto-disconnect issue with explicit A2DP connections. + Fix invalid memory access of ALSA plugin. + Fix compilation with -Wsign-compare. + +ver 4.27: + Add more SBC optimization (MMX and ARM NEON). + Add BT_SECURITY and BT_DEFER_SETUP definitions. + Add support for deferred connection setup. + Add support for fragmentation of data packets. + Add option to trigger dedicated bonding. + Follow MITM requirements from remote device. + Require MITM for dedicated bonding if capabilities allow it. + Fix IO capabilities for non-pairing and pairing cases. + Fix no-bonding connections in non-bondable mode. + Fix new pairing detection with SSP. + Fix bonding with pre-2.1 devices and newer kernels. + Fix LIAC setting while toggling Pairable property. + Fix device creation for incoming security mode 3 connects. + Fix crash within A2DP with bogus pointer. + Fix issue with sdp_copy_record() function. + Fix crash with extract_des() if sdp_uuid_extract() fails. + +ver 4.26: + Use of constant shift in SBC quantization code. + Add possibility to analyze 4 blocks at once in encoder. + Fix correct handling of frame sizes in the encoder. + Fix for big endian problems in SBC codec. + Fix audio client socket to always be non-blocking. + Update telephony support for Maemo. + +ver 4.25: + Fix receiving data over the audio control socket. + Fix subbands selection for joint-stereo in SBC encoder. + Add new SBC analysis filter function. + +ver 4.24: + Fix signal emissions when removing adapters. + Fix missing adapter signals on exit. + Add support for bringing adapters down on exit. + Add support for RememberPowered option. + Add support for verbose compiler warnings. + Add more options to SBC encoder. + +ver 4.23: + Update audio IPC for better codec handling. + Fix bitstream optimization for SBC encoder. + Fix length header values of IPC messages. + Fix multiple coding style violations. + Fix FindDevice to handle temporary devices. + Add configuration option for DeviceID. + Add support for InitiallyPowered option. + Add missing signals for manager properties. + Add telephony support for Maemo. + +ver 4.22: + Add deny statements to D-Bus access policy. + Add support for LegacyPairing property. + Add support for global properties. + Add more commands to telephony testing script. + Add sender checks for serial and network interfaces. + Remove deprecated methods and signals from input interface. + Remove deprecated methods and signals from network interface. + Remove OffMode option and always use device down. + +ver 4.21: + Fix adapter initialization logic. + Fix adapter setup and start security manager early. + Fix usage issue with first_init variable. + +ver 4.20: + Cleanup session handling. + Cleanup mode setting handling. + Fix issue with concurrent audio clients. + Fix issue with HFP/HSP suspending. + Fix AT result code syntax handling. + Add Handsfree support for AT+NREC. + Add PairableTimeout adapter property. + +ver 4.19: + Fix installation of manual pages for old daemons. + Fix D-Bus signal emmissions for CreateDevice. + Fix issues with UUID probing. + Fix +BSRF syntax issue. + Add Pairable adapter property. + Add sdp_copy_record() library function. + +ver 4.18: + Fix release before close issue with RFCOMM TTYs. + Fix Connected property on input interface. + Fix DeviceFound signals during initial name resolving. + Fix service discovery handling. + Fix duplicate UUID detection. + Fix SBC gain mismatch and decoding handling. + Add more options to SBC encoder and decoder. + Add special any adapter object for service interface. + Add variable prefix to adapter and device object paths. + +ver 4.17: + Fix SBC encoder not writing last frame. + Fix missing timer for A2DP suspend. + Add more supported devices to hid2hci utility. + Add additional functionality to Handsfree support. + +ver 4.16: + Fix wrong parameter usage of watch callbacks. + Fix parameters for callback upon path removal. + Fix unloading of adapter drivers. + +ver 4.15: + Fix various A2DP state machine issues. + Fix some issues with the Handsfree error reporting. + Fix format string warnings with recent GCC versions. + Remove dependency on GModule. + +ver 4.14: + Fix types of property arrays. + Fix potential crash with input devices. + Fix PS3 BD remote input event generation. + Allow dynamic adapter driver registration. + Update udev rules. + +ver 4.13: + Fix service discovery and UUID handling. + Fix bonding issues with Simple Pairing. + Fix file descriptor misuse of SCO connections. + Fix various memory leaks in the device handling. + Fix AVCTP disconnect handling. + Fix GStreamer modes for MP3 encoding. + Add operator selection to Handsfree support. + +ver 4.12: + Fix crash with missing icon value. + Fix error checks of HAL plugin. + Fix SCO server socket cleanup on exit. + Fix memory leaks from DBusPendingCall. + Fix handling of pending authorization requests. + Fix missing protocol UUIDs in record pattern. + +ver 4.11: + Change SCO server socket into a generic one. + Add test script for dummy telephony plugin. + Fix uninitialized reply of multiple GetProperties methods. + +ver 4.10: + Fix memory leaks with HAL messages. + Add more advanced handsfree features. + Add properties to audio, input and network interfaces. + Stop device discovery timer on device removal. + +ver 4.9: + Fix signals for Powered and Discoverable properties. + Fix handling of Alias and Icon properties. + Fix duplicate entries for service UUIDs. + +ver 4.8: + Fix retrieving of formfactor value. + Fix retrieving of local and remote extended features. + Fix potential NULL pointer dereference during pairing. + Fix crash with browsing due to a remotely initated pairing. + +ver 4.7: + Fix pairing and service discovery logic. + Fix crashes during suspend and resume. + Fix race condition within devdown mode. + Add RequestSession and ReleaseSession methods. + Add Powered and Discoverable properties. + Add Devices property and deprecate ListDevices. + Add workaround for a broken carkit from Nokia. + +ver 4.6: + Fix Device ID record handling. + Fix service browsing and storage. + Fix authentication and encryption for input devices. + Fix adapter name initialization. + +ver 4.5: + Fix initialization issue with new adapters. + Send HID authentication request without blocking. + Hide the verbose SDP debug behind SDP_DEBUG. + Add extra UUIDs for service discovery. + Add SCO server socket listener. + Add authorization support to service plugin. + +ver 4.4: + Add temporary fix for the CUPS compile issue. + Add service-api.txt to distribution. + Mention the variable prefix of an object path + +ver 4.3: + Add dummy driver for telephony support. + Add support for discovery sessions. + Add service plugin for external services. + Various cleanups. + +ver 4.2: + Avoid memory copies in A2DP write routine. + Fix broken logic with Simple Pairing check and old kernels. + Allow non-bondable and outgoing SDP without agent. + Only remove the bonding for non-temporary devices. + Cleanup various unnecessary includes. + Make more unexported functions static. + Add basic infrastructure for gtk-doc support. + +ver 4.1: + Add 30 seconds timeout to BNEP connection setup phase. + Avoid memory copies in A2DP write routine for ALSA. + Make sure to include compat/sdp.h in the distribution. + +ver 4.0: + Initial public release. + +ver 3.36: + Add init routines for TI BRF chips. + Add extra attributes to the serial port record. + Add example record for headset audio gateway record. + Use Handsfree version 0x0105 for the gateway role. + Fix SDP record registration with specific record handles. + Fix BCSP sent/receive handling. + Fix various includes for cross-compilation. + Allow link mode settings for outgoing connections. + Allow bonding during periodic inquiry. + +ver 3.35: + Add two additional company identifiers. + Add UUID-128 support for service discovery. + Fix usage of friendly names for service discovery. + Fix authorization when experiemental is disabled. + Fix uninitialized variable in passkey request handling. + Enable output of timestamps for l2test and rctest. + +ver 3.34: + Replace various SDP functions with safe versions. + Add additional length validation for incoming SDP packets. + Use safe function versions for SDP client handling. + Fix issue with RemoveDevice during discovery procedure. + Fix collect for non-persistent service records. + +ver 3.33: + Add functions for reading and writing the link policy settings. + Add definition for authentication requirements. + Add support for handling Simple Pairing. + Add Simple Pairing support to Agent interface. + Add ReleaseMode method to Adapter interface. + Add DiscoverServices method to Device interface. + Remove obsolete code and cleanup the repository. + Move over to use the libgdbus API. + Enable PIE by default if supported. + +ver 3.32: + Add OCF constants for synchronous flow control enabling. + Add support for switching HID proxy devices from Dell. + Add more Bluetooth client/server helper functions. + Add support for input service idle timeout option. + Fix BNEP reconnection handling. + Fix return value for snd_pcm_hw_params() calls. + Use upper-case addresses for object paths. + Remove HAL support helpers. + Remove inotify support. + Remove service daemon activation handling. + Remove uneeded D-Bus API extension. + +ver 3.31: + Create device object for all pairing cases. + Convert authorization to internal function calls. + Add initial support for Headset Audio Gateway role. + Add generic Bluetooth helper functions for GLib. + Fix endiannes handling of connection handles. + Don't optimize when debug is enabled. + +ver 3.30: + Convert audio service into a plugin. + Convert input service into a plugin. + Convert serial service into a plugin. + Convert network service into a plugin. + Emit old device signals when a property is changed. + Fix missing DiscoverDevices and CancelDiscovery methods. + Add another company identifier. + Add basic support for Bluetooth sessions. + Add avinfo utility for AVDTP/A2DP classification. + Remove build option for deprecated sdpd binary. + +ver 3.29: + Introduce new D-Bus based API. + Add more SBC optimizations. + Add support for PS3 remote devices. + Fix alignment trap in SDP server. + Fix memory leak in sdp_get_uuidseq_attr function. + +ver 3.28: + Add support for MCAP UUIDs. + Add support for role switch for audio service. + Add disconnect timer for audio service. + Add disconnect detection to ALSA plugin. + Add more SBC optimizations. + Fix alignment issue of SDP server. + Remove support for SDP parsing via expat. + +ver 3.27: + Update uinput.h with extra key definitions. + Add support for input connect/disconnect callbacks. + Add ifdefs around some baud rate definitions. + Add another company identifier. + Add proper HFP service level connection handling. + Add basic headset automatic disconnect support. + Add support for new SBC API. + Fix SBC decoder noise at high bitpools. + Use 32-bit multipliers for further SBC optimization. + Check for RFCOMM connection state in SCO connect callback. + Make use of parameters selected in ALSA plugin. + +ver 3.26: + Fix compilation issues with UCHAR_MAX, USHRT_MAX and UINT_MAX. + Improve handling of different audio transports. + Enable services by default and keep old daemons disabled. + +ver 3.25: + Add limited support for Handsfree profile. + Add limited support for MPEG12/MP3 codec. + Add basic support for UNITINFO and SUBUNITINFO. + Add more SBC optimizations. + Fix external service (un)registration. + Allow GetInfo and GetAddress to fail. + +ver 3.24: + Add definitions for MDP. + Add TCP connection support for serial proxy. + Add fix for Logitech HID proxy switching. + Add missing macros, MIN, MAX, ABS and CLAMP. + Add more SBC encoder optimizations. + Add initial mechanism to handle headset commands. + Fix connecting to handsfree profile headsets. + Use proper function for checking signal name. + +ver 3.23: + Fix remote name request handling bug. + Fix key search function to honor the mmap area size. + Fix Avahi integration of network service. + Add new plugin communication for audio service. + Enable basic AVRCP support by default. + More optimizations to the SBC library. + Create common error definitions. + +ver 3.22: + Add missing include file from audio service. + Add SBC conformance test utility. + Add basic uinput support for AVRCP. + Fix L2CAP socket leak in audio service. + Fix buffer usage in GStreamer plugin. + Fix remote name request event handling. + +ver 3.21: + Add constant for Bluetooth socket options level. + Add initial AVRCP support. + Add A2DP sink support to GStreamer plugin. + Fix interoperability with A2DP suspend. + Fix sign error in 8-subband encoder. + Fix handling of service classes length size. + Store Extended Inquiry Response data information. + Publish device id information through EIR. + Support higher baud rates for Ericcson based chips. + +ver 3.20: + Fix GStreamer plugin file type detection. + Fix potential infinite loop in inotify support. + Fix D-Bus signatures for dict handling. + Fix issues with service activation. + Fix SDP failure handling of audio service. + Fix various memory leaks in input service. + Add secure device creation method to input service. + Add service information methods to serial service. + Add config file support to network service. + Add scripting capability to network service. + Add special on-mode handling. + Add optimization for SBC encoder. + Add tweaks for D-Bus 1.1.x libraries. + Add support for inquiry transmit power level. + +ver 3.19: + Limit range of bitpool announced while in ACP side. + Use poll instead of usleep to wait for worker thread. + Use default event mask from the specification. + Add L2CAP mode constants. + Add HID proxy support for Logitech diNovo Edge dongle. + Add refresh option to re-request device names. + Show correct connection link type. + +ver 3.18: + Don't allocate memory for the Bluetooth base UUID. + Implement proper locking for headsets. + Fix various A2DP SEP locking issues. + Fix and cleanup audio stream handling. + Fix stream starting if suspend request is pending. + Fix A2DP and AVDTP endianess problems. + Add network timeout and retransmission support. + Add more detailed decoding of EIR elements. + +ver 3.17: + Fix supported commands bit calculation. + Fix crashes in audio and network services. + Check PAN source and destination roles. + Only export the needed symbols for the plugins. + +ver 3.16: + Update company identifier list. + Add support for headsets with SCO audio over HCI. + Add support for auto-create through ALSA plugin. + Add support for ALSA plugin parameters. + Add GStreamer plugin with SBC decoder and encoder. + Fix network service NAP, GN and PANU servers. + Set EIR information from SDP database. + +ver 3.15: + Add A2DP support to the audio service. + Add proxy support to the serial service. + Extract main service class for later use. + Set service classes value from SDP database. + +ver 3.14: + Add missing signals for the adapter interface. + Add definitions and functions for Simple Pairing. + Add basic commands for Simple Pairing. + Add correct Simple Pairing and EIR interaction. + Add missing properties for remote information. + Add EPoX endian quirk to the input service. + Fix HID descriptor import and storage functions. + Fix handling of adapters in raw mode. + Fix remote device listing methods. + +ver 3.13: + Fix some issues with the headset support. + Fix concurrent pending connection attempts. + Fix usage of devname instead of netdev. + Add identifier for Nokia SyncML records. + Add command for reading the CSR chip revision. + Add generic CSR radio test support. + Update HCI command table. + +ver 3.12: + Add missing HCI command text descriptions + Add missing HCI commands structures. + Add missing HCI event structures. + Add common bachk() function. + Add support for limited discovery mode. + Add support for setting of event mask. + Add GetRemoteServiceIdentifiers method. + Add skeleton for local D-Bus server. + Add headset gain control methods. + Fix various headset implementation issues. + Fix various serial port service issues. + Fix various input service issues. + Let CUPS plugin discover printers in range. + Improve the BCM2035 UART init routine. + Ignore connection events for non-ACL links. + +ver 3.11: + Update API documentation. + Minimize SDP root records and browse groups. + Use same decoder for text and URL strings. + Fix URL data size handling. + Fix SDP pattern extraction for XML. + Fix network connection persistent state. + Add network connection helper methods. + Add initial version of serial port support. + Add class of device tracking. + +ver 3.10.1: + Add option to disable installation of manual pages. + Fix input service encryption setup. + Fix serial service methods. + Fix network service connection handling. + Provide a simple init script. + +ver 3.10: + Add initial version of network service. + Add initial version of serial service. + Add initial version of input service. + Add initial version of audio service. + Add authorization framework. + Add integer based SBC library. + Add version code for Bluetooth 2.1 specification. + Add ESCO_LINK connection type constant. + Export sdp_uuid32_to_uuid128() function. + +ver 3.9: + Add RemoteDeviceDisconnectRequested signal. + Add updated service framework. + Add embedded GLib library. + Add support for using system GLib library. + Create internal SDP server library. + +ver 3.8: + Sort discovered devices list based on their RSSI. + Send DiscoverableTimeoutChanged signal. + Fix local and remote name validity checking. + Add ListRemoteDevices and ListRecentRemoteDevices methods. + Add basic integration of confirmation concept. + Add support for service record description via XML. + Add support for external commands to the RFCOMM utility. + Add experimental service and authorization API. + Add functions for registering binary records. + +ver 3.7: + Fix class of device handling. + Fix error replies with pairing and security mode 3. + Fix disconnect method for RFCOMM connections. + Add match pattern for service searches. + Add support for prioritized watches. + Add additional PDU length checks. + Fix CSRC value for partial responses. + +ver 3.6.1: + Fix IO channel race conditions. + Fix pairing issues on big endian systems. + Fix pairing issues with page timeout errors. + Fix pairing state for security mode 3 requests. + Switch to user as default security manager mode. + +ver 3.6: + Update D-Bus based RFCOMM interface support. + Use L2CAP raw sockets for HCI connection creation. + Add periodic discovery support to the D-Bus interface. + Add initial support for device names via EIR. + Add proper UTF-8 validation of device names. + Add support for the J-Three keyboard. + Fix issues with the asynchronous API for SDP. + +ver 3.5: + Fix and cleanup watch functionality. + Add support for periodic inquiry mode. + Add support for asynchronous SDP requests. + Add more request owner tracking. + Add asynchronous API for SDP. + Document pageto and discovto options. + +ver 3.4: + Improve error reporting for failed HCI commands. + Improve handling of CancelBonding. + Fixed bonding reply message when disconnected. + Fix UUID128 string lookup handling. + Fix malloc() versus bt_malloc() usage. + +ver 3.3: + Don't change inquiry mode for Bluetooth 1.1 adapters. + Add udev rules for Bluetooth serial PCMCIA cards. + Add Cancel and Release methods for passkey agents. + Add GetRemoteClass method. + Convert to using ppoll() and pselect(). + Initialize allocated memory to zero. + Remove bcm203x firmware loader. + Remove kernel specific timeouts. + Add additional private data field for SDP sessions. + Add host controller to host flow control defines. + Add host number of completed packets defines. + Initialize various memory to zero before usage. + +ver 3.2: + Only check for the low-level D-Bus library. + Update possible device minor classes. + Fix timeout for pending reply. + Add more Inquiry with RSSI quirks. + Sleep only 100 msecs for device detection. + Don't send BondingCreated on link key renewal. + Allow storing of all UTF-8 remote device names. + Create storage filenames with a generic function. + Fix handling of SDP strings. + Add adapter type for SDIO cards. + Add features bit for link supervision timeout. + +ver 3.1: + Add missing placeholders for feature bits. + Fix handling of raw mode devices. + Fix busy loop in UUID extraction routine. + Remove inquiry mode setting. + Remove auth and encrypt settings. + +ver 3.0: + Implement the new BlueZ D-Bus API. + Fix broken behavior with EVT_CMD_STATUS. + Add features bit for pause encryption. + Add additional EIR error code. + Add more company identifiers. + Add another Phonebook Access identifier. + Update sniff subrating data structures. + +ver 2.25: + Use %jx instead of %llx for uint64_t and int64_t. + Allow null-terminated text strings. + Add UUID for N-Gage games. + Add UUID for Apple Macintosh Attributes. + Add Apple attributes and iSync records. + Add definitions for Apple Agent. + Add support for the Handsfree Audio Gateway service. + Add support for choosing a specific record handle. + Add support for dialup/telephone connections. + Add definitions for Apple Agent. + Add support for record handle on service registration. + +ver 2.24: + Fix display of SDP text and data strings. + Add support for device scan property. + Add support for additional access protocols. + Update the D-Bus policy configuration file. + +ver 2.23: + Update the new D-Bus interface. + Make dfutool ready for big endian architectures. + Add support for AVRCP specific service records. + Add support for writing complex BCCMD commands. + Add the new BCCMD interface utility. + Add MicroBCSP implementation from CSR. + Add constants and definitions for sniff subrating. + Add support for allocation of binary text elements. + Add HCI emulation tool. + Add fake HID support for old EPoX presenters. + Reject connections from unknown HID devices. + Fix service discovery deadlocks with Samsung D600 phones. + +ver 2.22: + Remove D-Bus 0.23 support. + Add initial version of the new D-Bus interface. + Add support for extended inquiry response commands. + Add support for the Logitech diNovo Media Desktop Laser. + Add compile time buffer checks (FORTIFY SOURCE). + Decode reserved LMP feature bits. + Fix errno overwrite problems. + Fix profile descriptor problem with Samsung phones. + +ver 2.21: + Move create_dirs() and create_file() into the textfile library. + Let textfile_put() also replace the last key value pair. + Fix memory leaks with textfile_get() usage. + Fix infinite loops and false positive matches. + Don't retrieve stored link keys for RAW devices. + Document the putkey and delkey commands. + Show supported commands also in clear text. + Support volatile changes of the BD_ADDR for CSR chips. + Add support for identification of supported commands. + Add missing OCF declarations for the security filter. + Add two new company identifiers. + +ver 2.20: + Add UUIDs for video distribution profile. + Add UUIDs for phonebook access profile. + Add attribute identifier for supported repositories. + Add definitions for extended inquiry response. + Add functions for extended inquiry response. + Add support for extended inquiry response. + Add support for HotSync service record. + Add support for ActiveSync service record. + Add ActiveSync networking support. + Fix D-Bus crashes with new API versions. + +ver 2.19: + Fix the GCC 4.0 warnings. + Fix the routing for dealing with raw devices. + Fix off by one memory allocation error. + Fix security problem with escape characters in device name. + Add per device service record functions. + Send D-Bus signals for inquiry results and remote name resolves. + Add support for device specific SDP records. + +ver 2.18: + Support D-Bus 0.23 and 0.33 API versions. + Support reading of complex BCCMD values. + Support minimum and maximum encryption key length. + Add support for reading and writing the inquiry scan type. + Add definitions for connection accept timeout and scan enable. + Add support for inquiry scan type. + Add tool for the CSR BCCMD interface. + Add first draft of the Audio/Video control utility. + Add disconnect timer support for the A2DP ALSA plugin. + Make SBC parameters configurable. + Replace non-printable characters in device names. + Remove hci_vhci.h header file. + Remove hci_uart.h header file. + +ver 2.17: + Set the storage directory through ${localstatedir}. + Add the textfile library for ASCII based file access. + Add support for return link keys event. + Add support for voice setting configuration. + Add support for page scan timeout configuration. + Add support for storing and deleting of stored link keys. + Add support for searching for services with UUID-128. + Add support for retrieving all possible service records. + Add support for a raw mode view of service records. + Add support for HID information caching in hidd. + Add support for authentication in pand and dund. + Add support for changing BD_ADDR of CSR chips. + Add pskey utility for changing CSR persistent storage values. + Add the firmware upgrade utility. + Add connection caching for the A2DP ALSA plugin. + Add functions for stored link keys. + Add definitions for PIN type and unit key. + Add SDP_WAIT_ON_CLOSE flag for sdp_connect(). + Include stdio.h in bluetooth.h header file. + Include sys/socket.h in the header files. + +ver 2.16: + Store link keys in ASCII based file format. + Support device name caching. + Support zero length data sizes in l2test. + Change default l2ping data size to 44 bytes. + Hide the server record and the public browse group root. + Read BD_ADDR if not set and if it is a raw device. + Add SDP language attributes. + Add support for browsing the L2CAP group. + Add support for stored pin codes for outgoing connections. + Add support for local commands and extended features. + Add support for reading CSR panic and fault codes. + Add config option for setting the inquiry mode. + Add OUI decoding support. + Use unlimited inquiry responses as default. + Use cached device names for PIN request. + Use the clock offset when getting the remote names. + Add function for reading local supported commands. + Add function for reading local extended features. + Add function for reading remote extended features. + Add function for getting the remote name with a clock offset. + Add function for extracting the OUI from a BD_ADDR. + Add inquiry info structure with RSSI and page scan mode. + Fix buffer allocation for features to string conversion. + Support inquiry with unlimited number of responses. + +ver 2.15: + Enable the RFCOMM service level security. + Add deprecated functions for reading the name. + Add command for reading the clock offset. + Add command for reading the clock. + Add function for reading the clock. + Add function for reading the local Bluetooth address. + Add function for reading the local supported features. + Don't configure raw devices. + Don't set inquiry scan or page scan on raw devices. + Don't show extended information for raw devices. + Support L2CAP signal sizes bigger than 2048 bytes. + Cleanup of the socket handling code of the test programs. + Use better way for unaligned access. + Remove sdp_internal.h and its usage. + +ver 2.14: + Make use of additional connection information. + Use library function for reading the RSSI. + Use library function for reading the link quality. + Use library function for reading the transmit power level. + Use library functions for the link supervision timeout. + Add tool for changing the device address. + Add function for reading the RSSI. + Add function for reading the link quality. + Add function for reading the transmit power level. + Add functions for the link supervision timeout. + Remove deprecated functions. + Update AM_PATH_BLUEZ macro. + +ver 2.13: + Use file permission 0600 for the link key file. + Add support for HID attribute descriptions. + Add support for Device ID attributes. + Add Device ID and HID attribute definitions. + Update the UUID constants and its translations. + Update L2CAP socket option definitions. + Update connection information definitions. + Various whitespace cleanups. + +ver 2.12: + Inherit the device specific options from the default. + Use --device for selecting the source device. + Add --nosdp option for devices with resource limitation. + Add support and parameter option for secure mode. + Add a lot of build ids and hardware revisions. + Add service classes and profile ids for WAP. + Add simple AM_PATH_BLUEZ macro. + Update UUID translation tables. + Correct kernel interface for CMTP and HIDP support. + +ver 2.11: + Initial support for the kernel security manager. + Various cleanups to avoid inclusion of kernel headers. + Fix output when the CUPS backend is called without arguments. + Fix problems with a 64 bit userland. + Use Bluetooth library functions if available. + Use standard numbering scheme of SDP record handles. + Use bit zero for vendor packets in the filter type bitmask. + Add SIM Access types for service discovery. + Add more audio/video profile translations. + Add another company identifier. + Add the missing HCI error codes. + Add RFCOMM socket options. + Add definition for the SECURE link mode. + Add functions for reading and writing the inquiry mode. + Add functions for AFH related settings and information. + Add version identifier for the Bluetooth 2.0 specification. + Add a master option to the hidd. + Add support for changing the link key of a connection. + Add support for requesting encryption on keyboards. + Add support for revision information of Digianswer devices. + Add support for the Zoom, IBM and TDK PCMCIA cards. + Add checks for the OpenOBEX and the ALSA libraries. + Add experimental mRouter support. + +ver 2.10: + Use a define for the configuration directory. + Fix string initialization for flags translation. + Fix and extend the unaligned access macros. + Make compiling with debug information optional. + Don't override CFLAGS from configure. + Check for usb_get_busses() and usb_interrupt_read(). + Add optional support for compiling with PIE. + Make installation of the init scripts optional. + Make compiling with debug information optional. + Don't override CFLAGS from configure. + +ver 2.9: + Retry SDP connect if busy in the CUPS backend. + Use packet type and allow role switch in hcitool. + Use the functions from the USB library for hid2hci. + Add Broadcom firmware loader. + Add EPoX endian quirk for buggy keyboards. + Add L2CAP info type and info result definitions. + Add value for L2CAP_CONF_RFC_MODE. + Change RSSI value to signed instead of unsigned. + Allow UUID32 values as protocol identifiers. + Update the autoconf/automake scripts. + +ver 2.8: + Use LIBS and LDADD instead of LDFLAGS. + Use HIDP subclass field for HID boot protocol. + Set olen before calling getsockopt() in pand. + Restore signals for dev-up script. + Add PID file support for pand. + Add size parameter to expand_name() in hcid. + Add support for audio source and audio sink SDP records. + Add support for HID virtual cable unplug. + Add support for AmbiCom BT2000C card. + Add defines and UUID's for audio/video profiles. + Add AVDTP protocol identifier. + Add HIDP subclass field. + Add PKGConfig support. + Fix the event code of inquiry with RSSI. + Remove dummy SDP library. + +ver 2.7: + Fix display of decoded LMP features. + Update company identifiers. + Add AFH related types. + Add first bits from EDR prototyping specification. + Add support for inquiry with RSSI. + Add HCRP related SDP functions. + Add HIDP header file. + Add support for getting the AFH channel map. + Add support for AFH mode. + Add support for inquiry mode. + Add Bluetooth backend for CUPS. + Add the hid2hci utility. + Add the hidd utility. + Add the pand utility. + Add the dund utility. + More endian bug fixes. + Give udev some time to create the RFCOMM device nodes. + Release the TTY if no device node is found. + New startup script for the Bluetooth subsystem. + Update to the autoconf stuff. + +ver 2.6: + Change default prefix to /usr. + Add manpages for hcid and hcid.conf. + Add the sdpd server daemon. + Add the sdptool utility. + Add the ciptool utility. + Add new company identifiers. + Add BNEP and CMTP header files. + Add the SDP library. + Use R2 for default value of pscan_rep_mode. + +ver 2.5: + Add decoding of Bluetooth 1.2 features. + Add link manager version parameter for Bluetooth 1.2. + Add new company identifiers. + Add D-Bus support for PIN request. + Support for transmit power level. + Support for park, sniff and hold mode. + Support for role switch. + Support for reading the clock offset. + Support for requesting authentication. + Support for setting connection encryption. + Show revision information for Broadcom devices. + Replace unprintable characters in device name. + Use R1 for default value of pscan_rep_mode. + Fix some 64-bit problems. + Fix some endian problems. + Report an error on PIN helper failure. + Update bluepin script for GTK2. + +ver 2.4: + Increase number of inquiry responses. + Support for transmit power level. + Display all 8 bytes of the features. + Add support for reading and writing of IAC. + Correct decoding class of device. + Use Ericsson revision command for ST Microelectronics devices. + Display AVM firmware version with 'revision' command. + New code for CSR specific revision information. + Support for ST Microelectronics specific initialization. + Support for 3Com card version 3.0. + Support for TDK, IBM and Socket cards. + Support for initial baud rate. + Update man pages. + Fixes for some memory leaks. + +ver 2.3: + Added const qualifiers to appropriate function arguments. + Minor fixes. + CSR firmware version is now displayed by 'revision' command. + Voice command is working properly on big endian machines. + Added support for Texas Bluetooth modules. + Added support for high UART baud rates on Ericsson modules. + BCSP initialization fixes. + Support for role switch command (hcitool). + RFCOMM config file parser fixes. + Update man pages. + Removed GLib dependency. + +ver 2.2: + Updated RFCOMM header file. + Additional HCI command and event defines. + Support for voice settings (hciconfig). + Minor hcitool fixes. + Improved configure script. + Added Headset testing tool. + Updated man pages. + RPM package. + +ver 2.1.1: + Resurrect hci_remote_name. + +ver 2.1: + Added hci_{read, write}_class_of_dev(). + Added hci_{read, write}_current_iac_lap(). + Added hci_write_local_name(). + Added RFCOMM header file. + Minor fixes. + Improved BCSP initialization (hciattach). + Support for displaying link quality (hcitool). + Support for changing link supervision timeout (hcitool). + New RFCOMM TTY configuration tool (rfcomm). + Minor fixes and updates. + +ver 2.0: + Additional company IDs. + BCSP initialization (hciattach). + Minor hciconfig fixes. + +ver 2.0-pr13: + Support for multiple pairing modes. + Link key database handling fixes. + +ver 2.0-pre12: + Removed max link key limit. Keys never expire. + Link key database is always updated. Reread PIN on SIGHUP (hcid). + Bluetooth script starts SDPd, if installed. + Other minor fixes. + +ver 2.0-pre11: + Improved link key management and more verbose logging (hcid). + Fixed scan command (hcitool). + +ver 2.0-pre10: + Fix hci_inquiry function to return errors and accept user buffers. + New functions hci_devba, hci_devid, hci_for_each_dev and hci_get_route. + Additional company IDs. + Makefile and other minor fixes. + Support for reading RSSI, remote name and changing + connection type (hcitool). + Device initialization fixes (hcid). + Other minor fixes and improvements. + Build environment cleanup and fixes. + +ver 2.0-pre9: + Improved bluepin. Working X authentication. + Improved hcitool. New flexible cmd syntax, additional commands. + Human readable display of the device features. + LMP features to string translation support. + Additional HCI command and event defines. + Extended hci_filter API. + +ver 2.0-pre8: + Additional HCI ioctls and defines. + All strings and buffers are allocated dynamically. + ba2str, str2ba automatically swap bdaddress. + Additional hciconfig commands. Support for ACL and SCO MTU ioctls. + Support for Inventel and COM1 UART based devices. + Minor hcitool fixes. + Improved l2test. New L2CAP test modes. + Minor fixes and cleanup. + +ver 2.0-pre7: + Bluetooth libraries and header files is now a separate package. + New build environment uses automake and libtool. + Massive header files cleanup. + Bluetooth utilities is now a separate package. + New build environment uses automake. + Moved all config files and security data to /etc/bluetooth. + Various cleanups. + +ver 2.0-pre6: + API cleanup and additions. + Improved hcitool. + l2test minor output fixes. + hciattach opt to display list of supported devices. + +ver 2.0-pre4: + HCI filter enhancements. + +ver 2.0-pre3: + Cleanup. + +ver 2.0-pre2: + Additional HCI library functions. + Improved CSR baud rate initialization. + PCMCIA scripts fixes and enhancements. + Documentation update. + +ver 2.0-pre1: + New UART initialization utility. + Hot plugging support for UART based PCMCIA devices. + SCO testing utility. + New authentication utility (bluepin). + Minor fixes and improvements. 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..84c9712 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,641 @@ + +AM_MAKEFLAGS = --no-print-directory +AM_CPPFLAGS = + +lib_LTLIBRARIES = + +noinst_LIBRARIES = + +noinst_LTLIBRARIES = + +bin_PROGRAMS = + +noinst_PROGRAMS = + +dist_man_MANS = + +dist_noinst_MANS = + +CLEANFILES = + +EXTRA_DIST = + +pkglibexecdir = $(libexecdir)/bluetooth + +pkglibexec_PROGRAMS = + +pkgincludedir = $(includedir)/bluetooth + +pkginclude_HEADERS = + +AM_CFLAGS = $(WARNING_CFLAGS) $(MISC_CFLAGS) $(UDEV_CFLAGS) $(ell_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) $(ell_built_sources) src/builtin.h + +if LIBRARY +pkginclude_HEADERS += $(lib_headers) + +lib_LTLIBRARIES += lib/libbluetooth.la + +lib_libbluetooth_la_SOURCES = $(lib_headers) $(lib_sources) +lib_libbluetooth_la_LDFLAGS = $(AM_LDFLAGS) -version-info 22:0:19 +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 + +if EXTERNAL_ELL +ell_cflags = @ELL_CFLAGS@ +ell_ldadd = @ELL_LIBS@ +ell_dependencies = +ell_built_sources = +else +ell_cflags = +ell_ldadd = ell/libell-internal.la +ell_dependencies = $(ell_ldadd) +ell_built_sources = ell/internal ell/ell.h + +noinst_LTLIBRARIES += ell/libell-internal.la + +ell_headers = ell/util.h \ + ell/log.h \ + ell/queue.h \ + ell/hashmap.h \ + ell/random.h \ + ell/signal.h \ + ell/timeout.h \ + ell/cipher.h \ + ell/checksum.h \ + ell/io.h \ + ell/idle.h \ + ell/main.h \ + ell/strv.h \ + ell/string.h \ + ell/utf8.h \ + ell/dbus.h \ + ell/dbus-service.h \ + ell/dbus-client.h + +ell_sources = ell/private.h ell/missing.h \ + ell/util.c \ + ell/log.c \ + ell/queue.c \ + ell/hashmap.c \ + ell/random.c \ + ell/signal.c \ + ell/timeout.c \ + ell/io.c \ + ell/idle.c \ + ell/main.c \ + ell/strv.c \ + ell/string.c \ + ell/cipher.c \ + ell/checksum.c \ + ell/utf8.c \ + ell/dbus-private.h \ + ell/dbus.c \ + ell/dbus-message.c \ + ell/dbus-util.c \ + ell/dbus-service.c \ + ell/dbus-client.c \ + ell/dbus-name-cache.c \ + ell/dbus-filter.c \ + ell/gvariant-private.h \ + ell/gvariant-util.c \ + ell/siphash-private.h \ + ell/siphash.c + +ell_libell_internal_la_SOURCES = $(ell_headers) $(ell_sources) +endif + +CLEANFILES += $(ell_built_sources) + +noinst_LTLIBRARIES += src/libshared-glib.la src/libshared-mainloop.la + +if LIBSHARED_ELL +noinst_LTLIBRARIES += src/libshared-ell.la +endif + +shared_sources = src/shared/io.h src/shared/timeout.h \ + src/shared/queue.h src/shared/queue.c \ + src/shared/util.h src/shared/util.c \ + src/shared/mgmt.h src/shared/mgmt.c \ + src/shared/crypto.h src/shared/crypto.c \ + src/shared/ecc.h src/shared/ecc.c \ + src/shared/ringbuf.h src/shared/ringbuf.c \ + src/shared/tester.h src/shared/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/log.h src/shared/log.c \ + src/shared/tty.h + +if READLINE +shared_sources += src/shared/shell.c src/shared/shell.h +endif + +src_libshared_glib_la_SOURCES = $(shared_sources) \ + src/shared/io-glib.c \ + src/shared/timeout-glib.c \ + src/shared/mainloop-glib.c \ + src/shared/mainloop-notify.h \ + src/shared/mainloop-notify.c + +src_libshared_mainloop_la_SOURCES = $(shared_sources) \ + src/shared/io-mainloop.c \ + src/shared/timeout-mainloop.c \ + src/shared/mainloop.h src/shared/mainloop.c \ + src/shared/mainloop-notify.h \ + src/shared/mainloop-notify.c + +if LIBSHARED_ELL +src_libshared_ell_la_SOURCES = $(shared_sources) \ + src/shared/io-ell.c \ + src/shared/timeout-ell.c \ + src/shared/mainloop.h \ + src/shared/mainloop-ell.c +endif + +attrib_sources = attrib/att.h attrib/att-database.h attrib/att.c \ + attrib/gatt.h attrib/gatt.c \ + attrib/gattrib.h attrib/gattrib.c \ + 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_cppflags = +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 + +pkglibexec_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/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_CPPFLAGS = $(AM_CPPFLAGS) -DBLUETOOTH_PLUGIN_BUILTIN \ + -DPLUGINDIR=\""$(build_plugindir)"\" \ + $(BACKTRACE_CFLAGS) $(builtin_cppflags) +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 +include Makefile.mesh + +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_CPPFLAGS += $(DBUS_CFLAGS) $(GLIB_CFLAGS) -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_CPPFLAGS = $(AM_CPPFLAGS) $(ALSA_CFLAGS) -DMIDI_TEST +unit_test_midi_SOURCES = unit/test-midi.c \ + profiles/midi/libmidi.h \ + profiles/midi/libmidi.c +unit_test_midi_LDADD = src/libshared-glib.la \ + $(GLIB_LIBS) $(ALSA_LIBS) +endif + +if MESH +unit_tests += unit/test-mesh-crypto +unit_test_mesh_crypto_CPPFLAGS = $(ell_cflags) +unit_test_mesh_crypto_SOURCES = unit/test-mesh-crypto.c \ + mesh/crypto.h ell/internal ell/ell.h \ + $(ell_sources) +unit_test_mesh_crypto_LDADD = src/libshared-ell.la \ + $(ell_ldadd) +endif + +if MAINTAINER_MODE +noinst_PROGRAMS += $(unit_tests) +endif + +TESTS = $(unit_tests) +AM_TESTS_ENVIRONMENT = MALLOC_CHECK_=3 MALLOC_PERTURB_=69 + +if DBUS_RUN_SESSION +AM_TESTS_ENVIRONMENT += dbus-run-session -- +endif + +if VALGRIND +LOG_COMPILER = valgrind --error-exitcode=1 --num-callers=30 +LOG_FLAGS = --trace-children=yes --leak-check=full --show-reachable=no \ + --suppressions=$(srcdir)/tools/valgrind.supp --quiet +endif + +pkgconfigdir = $(libdir)/pkgconfig + +if LIBRARY +pkgconfig_DATA = lib/bluez.pc +endif + +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-midi \ + --enable-manpages \ + --enable-android \ + --enable-mesh \ + --enable-btpclient \ + --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,@pkglibexecdir\@,$(pkglibexecdir),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_at)$(MKDIR_P) tools + $(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 $<) $@ + +ell/internal: Makefile + $(AM_V_at)$(MKDIR_P) ell + $(AM_V_GEN)for f in $(ell_headers) $(ell_sources) ; do \ + if [ ! -f $$f ] ; then \ + $(LN_S) -t ell -f $(abs_srcdir)/../ell/$$f ; \ + fi \ + done > $@ + +ell/ell.h: Makefile + $(AM_V_at)echo -n > $@ + $(AM_V_GEN)for f in $(ell_headers) ; do \ + echo "#include <$$f>" >> $@ ; \ + done + +maintainer-clean-local: + -rm -rf ell + +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.in b/Makefile.in new file mode 100644 index 0000000..31f3ea4 --- /dev/null +++ b/Makefile.in @@ -0,0 +1,11986 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + + + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = $(am__EXEEXT_1) $(am__EXEEXT_2) $(am__EXEEXT_3) \ + $(am__EXEEXT_4) $(am__EXEEXT_5) +noinst_PROGRAMS = $(am__EXEEXT_6) $(am__EXEEXT_7) $(am__EXEEXT_8) \ + $(am__EXEEXT_9) $(am__EXEEXT_10) $(am__EXEEXT_11) \ + $(am__EXEEXT_16) +pkglibexec_PROGRAMS = src/bluetoothd$(EXEEXT) $(am__EXEEXT_17) \ + $(am__EXEEXT_18) $(am__EXEEXT_19) +@LIBRARY_TRUE@am__append_1 = $(lib_headers) +@LIBRARY_TRUE@am__append_2 = lib/libbluetooth.la +@EXTERNAL_ELL_FALSE@am__append_3 = ell/libell-internal.la +@LIBSHARED_ELL_TRUE@am__append_4 = src/libshared-ell.la +@READLINE_TRUE@am__append_5 = src/shared/shell.c src/shared/shell.h +@NFC_TRUE@am__append_6 = neard +@NFC_TRUE@am__append_7 = plugins/neard.c +@SAP_TRUE@am__append_8 = sap +@SAP_TRUE@am__append_9 = profiles/sap/main.c profiles/sap/manager.h \ +@SAP_TRUE@ profiles/sap/manager.c profiles/sap/server.h \ +@SAP_TRUE@ profiles/sap/server.c profiles/sap/sap.h \ +@SAP_TRUE@ profiles/sap/sap-dummy.c + +@A2DP_TRUE@am__append_10 = a2dp +@A2DP_TRUE@am__append_11 = profiles/audio/source.h profiles/audio/source.c \ +@A2DP_TRUE@ profiles/audio/sink.h profiles/audio/sink.c \ +@A2DP_TRUE@ profiles/audio/a2dp.h profiles/audio/a2dp.c \ +@A2DP_TRUE@ profiles/audio/avdtp.h profiles/audio/avdtp.c \ +@A2DP_TRUE@ profiles/audio/media.h profiles/audio/media.c \ +@A2DP_TRUE@ profiles/audio/transport.h profiles/audio/transport.c \ +@A2DP_TRUE@ profiles/audio/a2dp-codecs.h + +@AVRCP_TRUE@am__append_12 = avrcp +@AVRCP_TRUE@am__append_13 = profiles/audio/control.h profiles/audio/control.c \ +@AVRCP_TRUE@ profiles/audio/avctp.h profiles/audio/avctp.c \ +@AVRCP_TRUE@ profiles/audio/avrcp.h profiles/audio/avrcp.c \ +@AVRCP_TRUE@ profiles/audio/player.h profiles/audio/player.c + +@NETWORK_TRUE@am__append_14 = network +@NETWORK_TRUE@am__append_15 = profiles/network/manager.c \ +@NETWORK_TRUE@ profiles/network/bnep.h profiles/network/bnep.c \ +@NETWORK_TRUE@ profiles/network/server.h profiles/network/server.c \ +@NETWORK_TRUE@ profiles/network/connection.h \ +@NETWORK_TRUE@ profiles/network/connection.c + +@HID_TRUE@am__append_16 = input +@HID_TRUE@am__append_17 = profiles/input/manager.c \ +@HID_TRUE@ profiles/input/server.h profiles/input/server.c \ +@HID_TRUE@ profiles/input/device.h profiles/input/device.c \ +@HID_TRUE@ profiles/input/hidp_defs.h profiles/input/sixaxis.h + +@HOG_TRUE@am__append_18 = hog +@HOG_TRUE@am__append_19 = profiles/input/hog.c profiles/input/uhid_copy.h \ +@HOG_TRUE@ profiles/input/hog-lib.c profiles/input/hog-lib.h \ +@HOG_TRUE@ profiles/deviceinfo/dis.c profiles/deviceinfo/dis.h \ +@HOG_TRUE@ profiles/battery/bas.c profiles/battery/bas.h \ +@HOG_TRUE@ profiles/scanparam/scpp.c profiles/scanparam/scpp.h \ +@HOG_TRUE@ profiles/input/suspend.h profiles/input/suspend-none.c + +@HOG_TRUE@am__append_20 = profiles/input/suspend-dummy.c +@HEALTH_TRUE@am__append_21 = health +@HEALTH_TRUE@am__append_22 = profiles/health/mcap.h profiles/health/mcap.c \ +@HEALTH_TRUE@ profiles/health/hdp_main.c profiles/health/hdp_types.h \ +@HEALTH_TRUE@ profiles/health/hdp_manager.h \ +@HEALTH_TRUE@ profiles/health/hdp_manager.c \ +@HEALTH_TRUE@ profiles/health/hdp.h profiles/health/hdp.c \ +@HEALTH_TRUE@ profiles/health/hdp_util.h profiles/health/hdp_util.c + +@MIDI_TRUE@am__append_23 = midi +@MIDI_TRUE@am__append_24 = profiles/midi/midi.c \ +@MIDI_TRUE@ profiles/midi/libmidi.h \ +@MIDI_TRUE@ profiles/midi/libmidi.c + +@MIDI_TRUE@am__append_25 = $(ALSA_CFLAGS) +@MIDI_TRUE@am__append_26 = $(ALSA_LIBS) +@SIXAXIS_TRUE@am__append_27 = plugins/sixaxis.la +@MAINTAINER_MODE_TRUE@am__append_28 = plugins/external-dummy.la +@CLIENT_TRUE@am__append_29 = client/bluetoothctl +@MONITOR_TRUE@am__append_30 = monitor/btmon +@LOGGER_TRUE@am__append_31 = tools/btmon-logger +@LOGGER_TRUE@@SYSTEMD_TRUE@am__append_32 = tools/bluetooth-logger.service +@TESTING_TRUE@am__append_33 = emulator/btvirt emulator/b1ee emulator/hfp \ +@TESTING_TRUE@ peripheral/btsensor tools/3dsp \ +@TESTING_TRUE@ tools/mgmt-tester tools/gap-tester \ +@TESTING_TRUE@ tools/l2cap-tester tools/sco-tester \ +@TESTING_TRUE@ tools/smp-tester tools/hci-tester \ +@TESTING_TRUE@ tools/rfcomm-tester tools/bnep-tester \ +@TESTING_TRUE@ tools/userchan-tester + +@TOOLS_TRUE@am__append_34 = tools/rctest tools/l2test tools/l2ping tools/bccmd \ +@TOOLS_TRUE@ tools/bluemoon tools/hex2hcd tools/mpris-proxy \ +@TOOLS_TRUE@ tools/btattach + +@TOOLS_TRUE@am__append_35 = tools/bdaddr tools/avinfo tools/avtest \ +@TOOLS_TRUE@ tools/scotest tools/amptest tools/hwdb \ +@TOOLS_TRUE@ tools/hcieventmask tools/hcisecfilter \ +@TOOLS_TRUE@ tools/btinfo tools/btconfig \ +@TOOLS_TRUE@ tools/btsnoop tools/btproxy \ +@TOOLS_TRUE@ tools/btiotest tools/bneptest tools/mcaptest \ +@TOOLS_TRUE@ tools/cltest tools/oobtest tools/advtest \ +@TOOLS_TRUE@ tools/seq2bseq tools/nokfw tools/rtlfw \ +@TOOLS_TRUE@ tools/bcmfw tools/create-image \ +@TOOLS_TRUE@ tools/eddystone tools/ibeacon \ +@TOOLS_TRUE@ tools/btgatt-client tools/btgatt-server \ +@TOOLS_TRUE@ tools/test-runner tools/check-selftest \ +@TOOLS_TRUE@ tools/gatt-service profiles/iap/iapd + +@TOOLS_TRUE@am__append_36 = tools/rctest.1 tools/l2ping.1 tools/bccmd.1 tools/btattach.1 +@TOOLS_TRUE@am__append_37 = tools/bdaddr.1 tools/mesh/local_node.json \ +@TOOLS_TRUE@ tools/mesh/prov_db.json +@MESH_TRUE@@TOOLS_TRUE@am__append_38 = tools/meshctl +@DEPRECATED_TRUE@@TOOLS_TRUE@am__append_39 = tools/hciattach tools/hciconfig tools/hcitool tools/hcidump \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/rfcomm tools/sdptool tools/ciptool + +@DEPRECATED_TRUE@@TOOLS_TRUE@am__append_40 = tools/hciattach.1 tools/hciconfig.1 \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hcitool.1 tools/hcidump.1 \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/rfcomm.1 tools/sdptool.1 tools/ciptool.1 + +@DEPRECATED_FALSE@@TOOLS_TRUE@am__append_41 = tools/hciattach.1 tools/hciconfig.1 \ +@DEPRECATED_FALSE@@TOOLS_TRUE@ tools/hcitool.1 tools/hcidump.1 \ +@DEPRECATED_FALSE@@TOOLS_TRUE@ tools/rfcomm.1 tools/sdptool.1 tools/ciptool.1 + +@TOOLS_FALSE@am__append_42 = tools/rctest.1 tools/l2ping.1 tools/bccmd.1 tools/btattach.1 +@HID2HCI_TRUE@udev_PROGRAMS = tools/hid2hci$(EXEEXT) +@HID2HCI_TRUE@am__append_43 = tools/hid2hci.1 +@HID2HCI_FALSE@am__append_44 = tools/hid2hci.1 +@READLINE_TRUE@am__append_45 = tools/btmgmt tools/obex-client-tool tools/obex-server-tool \ +@READLINE_TRUE@ tools/bluetooth-player tools/obexctl + +@DEPRECATED_TRUE@@READLINE_TRUE@am__append_46 = attrib/gatttool +@CUPS_TRUE@cups_PROGRAMS = profiles/cups/bluetooth$(EXEEXT) +@BTPCLIENT_TRUE@am__append_47 = tools/btpclient +@EXPERIMENTAL_TRUE@@OBEX_TRUE@am__append_48 = pcsuite +@EXPERIMENTAL_TRUE@@OBEX_TRUE@am__append_49 = obexd/plugins/pcsuite.c +@OBEX_TRUE@am__append_50 = obexd/src/obexd +@ANDROID_TRUE@am__append_51 = -DANDROID_VERSION=0x050100 +@ANDROID_TRUE@am__append_52 = android/system-emulator \ +@ANDROID_TRUE@ android/bluetoothd-snoop android/bluetoothd \ +@ANDROID_TRUE@ android/avdtptest android/haltest \ +@ANDROID_TRUE@ android/android-tester android/ipc-tester +@ANDROID_TRUE@am__append_53 = android/bluetooth.default.la \ +@ANDROID_TRUE@ android/audio.a2dp.default.la \ +@ANDROID_TRUE@ android/audio.sco.default.la +@ANDROID_TRUE@am__append_54 = android/test-ipc +@DATAFILES_TRUE@@MESH_TRUE@am__append_55 = mesh/bluetooth-mesh.conf +@MESH_TRUE@@SYSTEMD_TRUE@am__append_56 = mesh/bluetooth-mesh.service +@MESH_TRUE@@SYSTEMD_TRUE@am__append_57 = mesh/org.bluez.mesh.service +@MESH_TRUE@am__append_58 = mesh/bluetooth-meshd +@MESH_TRUE@am__append_59 = mesh/bluetooth-mesh.service +@HID2HCI_TRUE@am__append_60 = $(rules_DATA) +@MIDI_TRUE@am__append_61 = unit/test-midi +@MESH_TRUE@am__append_62 = unit/test-mesh-crypto +@MAINTAINER_MODE_TRUE@am__append_63 = $(unit_tests) +TESTS = $(am__EXEEXT_15) +@DBUS_RUN_SESSION_TRUE@am__append_64 = dbus-run-session -- +@MANPAGES_TRUE@am__append_65 = $(manual_pages) +subdir = . +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(top_srcdir)/configure \ + $(am__configure_deps) $(am__dist_zshcompletion_DATA_DIST) \ + $(am__pkginclude_HEADERS_DIST) $(am__DIST_COMMON) +am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ + configure.lineno config.status.lineno +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = config.h +CONFIG_CLEAN_FILES = src/bluetoothd.8 lib/bluez.pc +CONFIG_CLEAN_VPATH_FILES = +@CLIENT_TRUE@am__EXEEXT_1 = client/bluetoothctl$(EXEEXT) +@MONITOR_TRUE@am__EXEEXT_2 = monitor/btmon$(EXEEXT) +@TOOLS_TRUE@am__EXEEXT_3 = tools/rctest$(EXEEXT) tools/l2test$(EXEEXT) \ +@TOOLS_TRUE@ tools/l2ping$(EXEEXT) tools/bccmd$(EXEEXT) \ +@TOOLS_TRUE@ tools/bluemoon$(EXEEXT) tools/hex2hcd$(EXEEXT) \ +@TOOLS_TRUE@ tools/mpris-proxy$(EXEEXT) tools/btattach$(EXEEXT) +@MESH_TRUE@@TOOLS_TRUE@am__EXEEXT_4 = tools/meshctl$(EXEEXT) +@DEPRECATED_TRUE@@TOOLS_TRUE@am__EXEEXT_5 = tools/hciattach$(EXEEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciconfig$(EXEEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hcitool$(EXEEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hcidump$(EXEEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/rfcomm$(EXEEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/sdptool$(EXEEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/ciptool$(EXEEXT) +am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(cupsdir)" \ + "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(udevdir)" \ + "$(DESTDIR)$(libdir)" "$(DESTDIR)$(plugindir)" \ + "$(DESTDIR)$(testdir)" "$(DESTDIR)$(man1dir)" \ + "$(DESTDIR)$(man8dir)" "$(DESTDIR)$(confdir)" \ + "$(DESTDIR)$(dbusdir)" "$(DESTDIR)$(dbussessionbusdir)" \ + "$(DESTDIR)$(dbussystembusdir)" \ + "$(DESTDIR)$(zshcompletiondir)" "$(DESTDIR)$(pkgconfigdir)" \ + "$(DESTDIR)$(rulesdir)" "$(DESTDIR)$(statedir)" \ + "$(DESTDIR)$(systemdsystemunitdir)" \ + "$(DESTDIR)$(systemduserunitdir)" "$(DESTDIR)$(pkgincludedir)" +@TESTING_TRUE@am__EXEEXT_6 = emulator/btvirt$(EXEEXT) \ +@TESTING_TRUE@ emulator/b1ee$(EXEEXT) emulator/hfp$(EXEEXT) \ +@TESTING_TRUE@ peripheral/btsensor$(EXEEXT) tools/3dsp$(EXEEXT) \ +@TESTING_TRUE@ tools/mgmt-tester$(EXEEXT) \ +@TESTING_TRUE@ tools/gap-tester$(EXEEXT) \ +@TESTING_TRUE@ tools/l2cap-tester$(EXEEXT) \ +@TESTING_TRUE@ tools/sco-tester$(EXEEXT) \ +@TESTING_TRUE@ tools/smp-tester$(EXEEXT) \ +@TESTING_TRUE@ tools/hci-tester$(EXEEXT) \ +@TESTING_TRUE@ tools/rfcomm-tester$(EXEEXT) \ +@TESTING_TRUE@ tools/bnep-tester$(EXEEXT) \ +@TESTING_TRUE@ tools/userchan-tester$(EXEEXT) +@TOOLS_TRUE@am__EXEEXT_7 = tools/bdaddr$(EXEEXT) tools/avinfo$(EXEEXT) \ +@TOOLS_TRUE@ tools/avtest$(EXEEXT) tools/scotest$(EXEEXT) \ +@TOOLS_TRUE@ tools/amptest$(EXEEXT) tools/hwdb$(EXEEXT) \ +@TOOLS_TRUE@ tools/hcieventmask$(EXEEXT) \ +@TOOLS_TRUE@ tools/hcisecfilter$(EXEEXT) tools/btinfo$(EXEEXT) \ +@TOOLS_TRUE@ tools/btconfig$(EXEEXT) tools/btsnoop$(EXEEXT) \ +@TOOLS_TRUE@ tools/btproxy$(EXEEXT) tools/btiotest$(EXEEXT) \ +@TOOLS_TRUE@ tools/bneptest$(EXEEXT) tools/mcaptest$(EXEEXT) \ +@TOOLS_TRUE@ tools/cltest$(EXEEXT) tools/oobtest$(EXEEXT) \ +@TOOLS_TRUE@ tools/advtest$(EXEEXT) tools/seq2bseq$(EXEEXT) \ +@TOOLS_TRUE@ tools/nokfw$(EXEEXT) tools/rtlfw$(EXEEXT) \ +@TOOLS_TRUE@ tools/bcmfw$(EXEEXT) tools/create-image$(EXEEXT) \ +@TOOLS_TRUE@ tools/eddystone$(EXEEXT) tools/ibeacon$(EXEEXT) \ +@TOOLS_TRUE@ tools/btgatt-client$(EXEEXT) \ +@TOOLS_TRUE@ tools/btgatt-server$(EXEEXT) \ +@TOOLS_TRUE@ tools/test-runner$(EXEEXT) \ +@TOOLS_TRUE@ tools/check-selftest$(EXEEXT) \ +@TOOLS_TRUE@ tools/gatt-service$(EXEEXT) \ +@TOOLS_TRUE@ profiles/iap/iapd$(EXEEXT) +@READLINE_TRUE@am__EXEEXT_8 = tools/btmgmt$(EXEEXT) \ +@READLINE_TRUE@ tools/obex-client-tool$(EXEEXT) \ +@READLINE_TRUE@ tools/obex-server-tool$(EXEEXT) \ +@READLINE_TRUE@ tools/bluetooth-player$(EXEEXT) \ +@READLINE_TRUE@ tools/obexctl$(EXEEXT) +@DEPRECATED_TRUE@@READLINE_TRUE@am__EXEEXT_9 = \ +@DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gatttool$(EXEEXT) +@BTPCLIENT_TRUE@am__EXEEXT_10 = tools/btpclient$(EXEEXT) +@ANDROID_TRUE@am__EXEEXT_11 = android/system-emulator$(EXEEXT) \ +@ANDROID_TRUE@ android/bluetoothd-snoop$(EXEEXT) \ +@ANDROID_TRUE@ android/bluetoothd$(EXEEXT) \ +@ANDROID_TRUE@ android/avdtptest$(EXEEXT) \ +@ANDROID_TRUE@ android/haltest$(EXEEXT) \ +@ANDROID_TRUE@ android/android-tester$(EXEEXT) \ +@ANDROID_TRUE@ android/ipc-tester$(EXEEXT) +@ANDROID_TRUE@am__EXEEXT_12 = android/test-ipc$(EXEEXT) +@MIDI_TRUE@am__EXEEXT_13 = unit/test-midi$(EXEEXT) +@MESH_TRUE@am__EXEEXT_14 = unit/test-mesh-crypto$(EXEEXT) +am__EXEEXT_15 = $(am__EXEEXT_12) unit/test-eir$(EXEEXT) \ + unit/test-uuid$(EXEEXT) unit/test-textfile$(EXEEXT) \ + unit/test-crc$(EXEEXT) unit/test-crypto$(EXEEXT) \ + unit/test-ecc$(EXEEXT) unit/test-ringbuf$(EXEEXT) \ + unit/test-queue$(EXEEXT) unit/test-mgmt$(EXEEXT) \ + unit/test-uhid$(EXEEXT) unit/test-sdp$(EXEEXT) \ + unit/test-avdtp$(EXEEXT) unit/test-avctp$(EXEEXT) \ + unit/test-avrcp$(EXEEXT) unit/test-hfp$(EXEEXT) \ + unit/test-gdbus-client$(EXEEXT) \ + unit/test-gobex-header$(EXEEXT) \ + unit/test-gobex-packet$(EXEEXT) unit/test-gobex$(EXEEXT) \ + unit/test-gobex-transfer$(EXEEXT) \ + unit/test-gobex-apparam$(EXEEXT) unit/test-lib$(EXEEXT) \ + unit/test-gatt$(EXEEXT) unit/test-hog$(EXEEXT) \ + unit/test-gattrib$(EXEEXT) $(am__EXEEXT_13) $(am__EXEEXT_14) +@MAINTAINER_MODE_TRUE@am__EXEEXT_16 = $(am__EXEEXT_15) +@LOGGER_TRUE@am__EXEEXT_17 = tools/btmon-logger$(EXEEXT) +@OBEX_TRUE@am__EXEEXT_18 = obexd/src/obexd$(EXEEXT) +@MESH_TRUE@am__EXEEXT_19 = mesh/bluetooth-meshd$(EXEEXT) +PROGRAMS = $(bin_PROGRAMS) $(cups_PROGRAMS) $(noinst_PROGRAMS) \ + $(pkglibexec_PROGRAMS) $(udev_PROGRAMS) +LIBRARIES = $(noinst_LIBRARIES) +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +LTLIBRARIES = $(lib_LTLIBRARIES) $(noinst_LTLIBRARIES) \ + $(plugin_LTLIBRARIES) +am__DEPENDENCIES_1 = +@ANDROID_TRUE@android_audio_a2dp_default_la_DEPENDENCIES = \ +@ANDROID_TRUE@ $(am__DEPENDENCIES_1) +am__android_audio_a2dp_default_la_SOURCES_DIST = 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 +am__dirstamp = $(am__leading_dot)dirstamp +@ANDROID_TRUE@am_android_audio_a2dp_default_la_OBJECTS = \ +@ANDROID_TRUE@ android/audio_a2dp_default_la-hal-audio.lo \ +@ANDROID_TRUE@ android/audio_a2dp_default_la-hal-audio-sbc.lo \ +@ANDROID_TRUE@ android/audio_a2dp_default_la-hal-audio-aptx.lo +android_audio_a2dp_default_la_OBJECTS = \ + $(am_android_audio_a2dp_default_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +android_audio_a2dp_default_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) \ + $(android_audio_a2dp_default_la_LDFLAGS) $(LDFLAGS) -o $@ +@ANDROID_TRUE@am_android_audio_a2dp_default_la_rpath = -rpath \ +@ANDROID_TRUE@ $(plugindir) +@ANDROID_TRUE@android_audio_sco_default_la_DEPENDENCIES = \ +@ANDROID_TRUE@ $(am__DEPENDENCIES_1) +am__android_audio_sco_default_la_SOURCES_DIST = 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_TRUE@am_android_audio_sco_default_la_OBJECTS = \ +@ANDROID_TRUE@ android/audio_sco_default_la-hal-sco.lo \ +@ANDROID_TRUE@ android/audio_utils/audio_sco_default_la-resampler.lo +android_audio_sco_default_la_OBJECTS = \ + $(am_android_audio_sco_default_la_OBJECTS) +android_audio_sco_default_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(android_audio_sco_default_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +@ANDROID_TRUE@am_android_audio_sco_default_la_rpath = -rpath \ +@ANDROID_TRUE@ $(plugindir) +android_bluetooth_default_la_LIBADD = +am__android_bluetooth_default_la_SOURCES_DIST = 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_TRUE@am_android_bluetooth_default_la_OBJECTS = \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-bluetooth.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-socket.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-hidhost.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-health.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-pan.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-a2dp.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-a2dp-sink.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-avrcp.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-avrcp-ctrl.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-handsfree.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-handsfree-client.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-gatt.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-map-client.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-ipc.lo \ +@ANDROID_TRUE@ android/bluetooth_default_la-hal-utils.lo +android_bluetooth_default_la_OBJECTS = \ + $(am_android_bluetooth_default_la_OBJECTS) +android_bluetooth_default_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(android_bluetooth_default_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +@ANDROID_TRUE@am_android_bluetooth_default_la_rpath = -rpath \ +@ANDROID_TRUE@ $(plugindir) +ell_libell_internal_la_LIBADD = +am__ell_libell_internal_la_SOURCES_DIST = ell/util.h ell/log.h \ + ell/queue.h ell/hashmap.h ell/random.h ell/signal.h \ + ell/timeout.h ell/cipher.h ell/checksum.h ell/io.h ell/idle.h \ + ell/main.h ell/strv.h ell/string.h ell/utf8.h ell/dbus.h \ + ell/dbus-service.h ell/dbus-client.h ell/private.h \ + ell/missing.h ell/util.c ell/log.c ell/queue.c ell/hashmap.c \ + ell/random.c ell/signal.c ell/timeout.c ell/io.c ell/idle.c \ + ell/main.c ell/strv.c ell/string.c ell/cipher.c ell/checksum.c \ + ell/utf8.c ell/dbus-private.h ell/dbus.c ell/dbus-message.c \ + ell/dbus-util.c ell/dbus-service.c ell/dbus-client.c \ + ell/dbus-name-cache.c ell/dbus-filter.c ell/gvariant-private.h \ + ell/gvariant-util.c ell/siphash-private.h ell/siphash.c +am__objects_1 = +@EXTERNAL_ELL_FALSE@am__objects_2 = ell/util.lo ell/log.lo \ +@EXTERNAL_ELL_FALSE@ ell/queue.lo ell/hashmap.lo ell/random.lo \ +@EXTERNAL_ELL_FALSE@ ell/signal.lo ell/timeout.lo ell/io.lo \ +@EXTERNAL_ELL_FALSE@ ell/idle.lo ell/main.lo ell/strv.lo \ +@EXTERNAL_ELL_FALSE@ ell/string.lo ell/cipher.lo \ +@EXTERNAL_ELL_FALSE@ ell/checksum.lo ell/utf8.lo ell/dbus.lo \ +@EXTERNAL_ELL_FALSE@ ell/dbus-message.lo ell/dbus-util.lo \ +@EXTERNAL_ELL_FALSE@ ell/dbus-service.lo ell/dbus-client.lo \ +@EXTERNAL_ELL_FALSE@ ell/dbus-name-cache.lo ell/dbus-filter.lo \ +@EXTERNAL_ELL_FALSE@ ell/gvariant-util.lo ell/siphash.lo +@EXTERNAL_ELL_FALSE@am_ell_libell_internal_la_OBJECTS = \ +@EXTERNAL_ELL_FALSE@ $(am__objects_1) $(am__objects_2) +ell_libell_internal_la_OBJECTS = $(am_ell_libell_internal_la_OBJECTS) +@EXTERNAL_ELL_FALSE@am_ell_libell_internal_la_rpath = +gdbus_libgdbus_internal_la_LIBADD = +am_gdbus_libgdbus_internal_la_OBJECTS = gdbus/mainloop.lo \ + gdbus/watch.lo gdbus/object.lo gdbus/client.lo gdbus/polkit.lo +gdbus_libgdbus_internal_la_OBJECTS = \ + $(am_gdbus_libgdbus_internal_la_OBJECTS) +lib_libbluetooth_internal_la_LIBADD = +am__objects_3 = lib/bluetooth.lo lib/hci.lo lib/sdp.lo +am__objects_4 = lib/uuid.lo +am_lib_libbluetooth_internal_la_OBJECTS = $(am__objects_1) \ + $(am__objects_3) $(am__objects_1) $(am__objects_4) +lib_libbluetooth_internal_la_OBJECTS = \ + $(am_lib_libbluetooth_internal_la_OBJECTS) +lib_libbluetooth_la_LIBADD = +am__lib_libbluetooth_la_SOURCES_DIST = lib/bluetooth.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 lib/bluetooth.c \ + lib/hci.c lib/sdp.c +@LIBRARY_TRUE@am_lib_libbluetooth_la_OBJECTS = $(am__objects_1) \ +@LIBRARY_TRUE@ $(am__objects_3) +lib_libbluetooth_la_OBJECTS = $(am_lib_libbluetooth_la_OBJECTS) +lib_libbluetooth_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(lib_libbluetooth_la_LDFLAGS) \ + $(LDFLAGS) -o $@ +@LIBRARY_TRUE@am_lib_libbluetooth_la_rpath = -rpath $(libdir) +plugins_external_dummy_la_LIBADD = +am__plugins_external_dummy_la_SOURCES_DIST = plugins/external-dummy.c +@MAINTAINER_MODE_TRUE@am_plugins_external_dummy_la_OBJECTS = plugins/external_dummy_la-external-dummy.lo +plugins_external_dummy_la_OBJECTS = \ + $(am_plugins_external_dummy_la_OBJECTS) +plugins_external_dummy_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(plugins_external_dummy_la_CFLAGS) $(CFLAGS) \ + $(plugins_external_dummy_la_LDFLAGS) $(LDFLAGS) -o $@ +@MAINTAINER_MODE_TRUE@am_plugins_external_dummy_la_rpath = -rpath \ +@MAINTAINER_MODE_TRUE@ $(plugindir) +@SIXAXIS_TRUE@plugins_sixaxis_la_DEPENDENCIES = $(am__DEPENDENCIES_1) +am__plugins_sixaxis_la_SOURCES_DIST = plugins/sixaxis.c +@SIXAXIS_TRUE@am_plugins_sixaxis_la_OBJECTS = \ +@SIXAXIS_TRUE@ plugins/sixaxis_la-sixaxis.lo +plugins_sixaxis_la_OBJECTS = $(am_plugins_sixaxis_la_OBJECTS) +plugins_sixaxis_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(plugins_sixaxis_la_CFLAGS) $(CFLAGS) \ + $(plugins_sixaxis_la_LDFLAGS) $(LDFLAGS) -o $@ +@SIXAXIS_TRUE@am_plugins_sixaxis_la_rpath = -rpath $(plugindir) +src_libshared_ell_la_LIBADD = +am__src_libshared_ell_la_SOURCES_DIST = src/shared/io.h \ + src/shared/timeout.h src/shared/queue.h src/shared/queue.c \ + src/shared/util.h src/shared/util.c src/shared/mgmt.h \ + src/shared/mgmt.c src/shared/crypto.h src/shared/crypto.c \ + src/shared/ecc.h src/shared/ecc.c src/shared/ringbuf.h \ + src/shared/ringbuf.c src/shared/tester.h src/shared/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/log.h src/shared/log.c src/shared/tty.h \ + src/shared/shell.c src/shared/shell.h src/shared/io-ell.c \ + src/shared/timeout-ell.c src/shared/mainloop.h \ + src/shared/mainloop-ell.c +@READLINE_TRUE@am__objects_5 = src/shared/shell.lo +am__objects_6 = src/shared/queue.lo src/shared/util.lo \ + src/shared/mgmt.lo src/shared/crypto.lo src/shared/ecc.lo \ + src/shared/ringbuf.lo src/shared/tester.lo src/shared/hci.lo \ + src/shared/hci-crypto.lo src/shared/hfp.lo src/shared/uhid.lo \ + src/shared/pcap.lo src/shared/btsnoop.lo src/shared/ad.lo \ + src/shared/att.lo src/shared/gatt-helpers.lo \ + src/shared/gatt-client.lo src/shared/gatt-server.lo \ + src/shared/gatt-db.lo src/shared/gap.lo src/shared/log.lo \ + $(am__objects_5) +@LIBSHARED_ELL_TRUE@am_src_libshared_ell_la_OBJECTS = \ +@LIBSHARED_ELL_TRUE@ $(am__objects_6) src/shared/io-ell.lo \ +@LIBSHARED_ELL_TRUE@ src/shared/timeout-ell.lo \ +@LIBSHARED_ELL_TRUE@ src/shared/mainloop-ell.lo +src_libshared_ell_la_OBJECTS = $(am_src_libshared_ell_la_OBJECTS) +@LIBSHARED_ELL_TRUE@am_src_libshared_ell_la_rpath = +src_libshared_glib_la_LIBADD = +am__src_libshared_glib_la_SOURCES_DIST = src/shared/io.h \ + src/shared/timeout.h src/shared/queue.h src/shared/queue.c \ + src/shared/util.h src/shared/util.c src/shared/mgmt.h \ + src/shared/mgmt.c src/shared/crypto.h src/shared/crypto.c \ + src/shared/ecc.h src/shared/ecc.c src/shared/ringbuf.h \ + src/shared/ringbuf.c src/shared/tester.h src/shared/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/log.h src/shared/log.c src/shared/tty.h \ + src/shared/shell.c src/shared/shell.h src/shared/io-glib.c \ + src/shared/timeout-glib.c src/shared/mainloop-glib.c \ + src/shared/mainloop-notify.h src/shared/mainloop-notify.c +am_src_libshared_glib_la_OBJECTS = $(am__objects_6) \ + src/shared/io-glib.lo src/shared/timeout-glib.lo \ + src/shared/mainloop-glib.lo src/shared/mainloop-notify.lo +src_libshared_glib_la_OBJECTS = $(am_src_libshared_glib_la_OBJECTS) +src_libshared_mainloop_la_LIBADD = +am__src_libshared_mainloop_la_SOURCES_DIST = src/shared/io.h \ + src/shared/timeout.h src/shared/queue.h src/shared/queue.c \ + src/shared/util.h src/shared/util.c src/shared/mgmt.h \ + src/shared/mgmt.c src/shared/crypto.h src/shared/crypto.c \ + src/shared/ecc.h src/shared/ecc.c src/shared/ringbuf.h \ + src/shared/ringbuf.c src/shared/tester.h src/shared/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/log.h src/shared/log.c src/shared/tty.h \ + src/shared/shell.c src/shared/shell.h src/shared/io-mainloop.c \ + src/shared/timeout-mainloop.c src/shared/mainloop.h \ + src/shared/mainloop.c src/shared/mainloop-notify.h \ + src/shared/mainloop-notify.c +am_src_libshared_mainloop_la_OBJECTS = $(am__objects_6) \ + src/shared/io-mainloop.lo src/shared/timeout-mainloop.lo \ + src/shared/mainloop.lo src/shared/mainloop-notify.lo +src_libshared_mainloop_la_OBJECTS = \ + $(am_src_libshared_mainloop_la_OBJECTS) +am__android_android_tester_SOURCES_DIST = 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_TRUE@am_android_android_tester_OBJECTS = emulator/android_android_tester-hciemu.$(OBJEXT) \ +@ANDROID_TRUE@ emulator/android_android_tester-btdev.$(OBJEXT) \ +@ANDROID_TRUE@ emulator/android_android_tester-bthost.$(OBJEXT) \ +@ANDROID_TRUE@ emulator/android_android_tester-smp.$(OBJEXT) \ +@ANDROID_TRUE@ android/hardware/android_tester-hardware.$(OBJEXT) \ +@ANDROID_TRUE@ android/android_tester-tester-bluetooth.$(OBJEXT) \ +@ANDROID_TRUE@ android/android_tester-tester-socket.$(OBJEXT) \ +@ANDROID_TRUE@ android/android_tester-tester-hidhost.$(OBJEXT) \ +@ANDROID_TRUE@ android/android_tester-tester-pan.$(OBJEXT) \ +@ANDROID_TRUE@ android/android_tester-tester-hdp.$(OBJEXT) \ +@ANDROID_TRUE@ android/android_tester-tester-a2dp.$(OBJEXT) \ +@ANDROID_TRUE@ android/android_tester-tester-avrcp.$(OBJEXT) \ +@ANDROID_TRUE@ android/android_tester-tester-gatt.$(OBJEXT) \ +@ANDROID_TRUE@ android/android_tester-tester-map-client.$(OBJEXT) \ +@ANDROID_TRUE@ android/android_tester-tester-main.$(OBJEXT) +android_android_tester_OBJECTS = $(am_android_android_tester_OBJECTS) +@ANDROID_TRUE@android_android_tester_DEPENDENCIES = \ +@ANDROID_TRUE@ lib/libbluetooth-internal.la \ +@ANDROID_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) +android_android_tester_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(android_android_tester_LDFLAGS) \ + $(LDFLAGS) -o $@ +am__android_avdtptest_SOURCES_DIST = 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 \ + src/shared/log.h src/shared/log.c android/avdtp.h \ + android/avdtp.c +@ANDROID_TRUE@am_android_avdtptest_OBJECTS = \ +@ANDROID_TRUE@ android/avdtptest-avdtptest.$(OBJEXT) \ +@ANDROID_TRUE@ src/android_avdtptest-log.$(OBJEXT) \ +@ANDROID_TRUE@ btio/android_avdtptest-btio.$(OBJEXT) \ +@ANDROID_TRUE@ src/shared/android_avdtptest-util.$(OBJEXT) \ +@ANDROID_TRUE@ src/shared/android_avdtptest-queue.$(OBJEXT) \ +@ANDROID_TRUE@ src/shared/android_avdtptest-log.$(OBJEXT) \ +@ANDROID_TRUE@ android/avdtptest-avdtp.$(OBJEXT) +android_avdtptest_OBJECTS = $(am_android_avdtptest_OBJECTS) +@ANDROID_TRUE@android_avdtptest_DEPENDENCIES = \ +@ANDROID_TRUE@ lib/libbluetooth-internal.la \ +@ANDROID_TRUE@ $(am__DEPENDENCIES_1) +android_avdtptest_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(android_avdtptest_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ + -o $@ +am__android_bluetoothd_SOURCES_DIST = 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_TRUE@am_android_bluetoothd_OBJECTS = android/main.$(OBJEXT) \ +@ANDROID_TRUE@ src/log.$(OBJEXT) src/sdpd-database.$(OBJEXT) \ +@ANDROID_TRUE@ src/sdpd-server.$(OBJEXT) \ +@ANDROID_TRUE@ src/sdpd-service.$(OBJEXT) \ +@ANDROID_TRUE@ src/sdpd-request.$(OBJEXT) \ +@ANDROID_TRUE@ src/uuid-helper.$(OBJEXT) src/eir.$(OBJEXT) \ +@ANDROID_TRUE@ android/bluetooth.$(OBJEXT) \ +@ANDROID_TRUE@ android/hidhost.$(OBJEXT) \ +@ANDROID_TRUE@ profiles/scanparam/scpp.$(OBJEXT) \ +@ANDROID_TRUE@ profiles/deviceinfo/dis.$(OBJEXT) \ +@ANDROID_TRUE@ profiles/battery/bas.$(OBJEXT) \ +@ANDROID_TRUE@ profiles/input/hog-lib.$(OBJEXT) \ +@ANDROID_TRUE@ android/ipc.$(OBJEXT) android/avdtp.$(OBJEXT) \ +@ANDROID_TRUE@ android/a2dp.$(OBJEXT) \ +@ANDROID_TRUE@ android/a2dp-sink.$(OBJEXT) \ +@ANDROID_TRUE@ android/avctp.$(OBJEXT) android/avrcp.$(OBJEXT) \ +@ANDROID_TRUE@ android/avrcp-lib.$(OBJEXT) \ +@ANDROID_TRUE@ android/socket.$(OBJEXT) android/sco.$(OBJEXT) \ +@ANDROID_TRUE@ android/pan.$(OBJEXT) \ +@ANDROID_TRUE@ android/handsfree.$(OBJEXT) \ +@ANDROID_TRUE@ android/handsfree-client.$(OBJEXT) \ +@ANDROID_TRUE@ android/gatt.$(OBJEXT) android/health.$(OBJEXT) \ +@ANDROID_TRUE@ profiles/health/mcap.$(OBJEXT) \ +@ANDROID_TRUE@ android/map-client.$(OBJEXT) \ +@ANDROID_TRUE@ attrib/att.$(OBJEXT) attrib/gatt.$(OBJEXT) \ +@ANDROID_TRUE@ attrib/gattrib.$(OBJEXT) btio/btio.$(OBJEXT) \ +@ANDROID_TRUE@ src/sdp-client.$(OBJEXT) \ +@ANDROID_TRUE@ profiles/network/bnep.$(OBJEXT) +android_bluetoothd_OBJECTS = $(am_android_bluetoothd_OBJECTS) +@ANDROID_TRUE@android_bluetoothd_DEPENDENCIES = \ +@ANDROID_TRUE@ lib/libbluetooth-internal.la \ +@ANDROID_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) +am__android_bluetoothd_snoop_SOURCES_DIST = \ + android/bluetoothd-snoop.c src/log.c +@ANDROID_TRUE@am_android_bluetoothd_snoop_OBJECTS = \ +@ANDROID_TRUE@ android/bluetoothd-snoop.$(OBJEXT) \ +@ANDROID_TRUE@ src/log.$(OBJEXT) +android_bluetoothd_snoop_OBJECTS = \ + $(am_android_bluetoothd_snoop_OBJECTS) +@ANDROID_TRUE@android_bluetoothd_snoop_DEPENDENCIES = \ +@ANDROID_TRUE@ src/libshared-mainloop.la $(am__DEPENDENCIES_1) +am__android_haltest_SOURCES_DIST = 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_TRUE@am_android_haltest_OBJECTS = \ +@ANDROID_TRUE@ android/client/haltest-haltest.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-pollhandler.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-terminal.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-history.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-tabcompletion.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-av.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-av-sink.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-rc.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-rc-ctrl.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-bt.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-gatt.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-hf.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-hf-client.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-hh.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-pan.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-hl.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-sock.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-audio.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-sco.$(OBJEXT) \ +@ANDROID_TRUE@ android/client/haltest-if-mce.$(OBJEXT) \ +@ANDROID_TRUE@ android/hardware/haltest-hardware.$(OBJEXT) \ +@ANDROID_TRUE@ android/haltest-hal-utils.$(OBJEXT) +android_haltest_OBJECTS = $(am_android_haltest_OBJECTS) +android_haltest_DEPENDENCIES = +android_haltest_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(android_haltest_LDFLAGS) $(LDFLAGS) \ + -o $@ +am__android_ipc_tester_SOURCES_DIST = 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_TRUE@am_android_ipc_tester_OBJECTS = \ +@ANDROID_TRUE@ emulator/android_ipc_tester-hciemu.$(OBJEXT) \ +@ANDROID_TRUE@ emulator/android_ipc_tester-btdev.$(OBJEXT) \ +@ANDROID_TRUE@ emulator/android_ipc_tester-bthost.$(OBJEXT) \ +@ANDROID_TRUE@ emulator/android_ipc_tester-smp.$(OBJEXT) \ +@ANDROID_TRUE@ android/ipc_tester-hal-utils.$(OBJEXT) \ +@ANDROID_TRUE@ android/ipc_tester-ipc-tester.$(OBJEXT) +android_ipc_tester_OBJECTS = $(am_android_ipc_tester_OBJECTS) +@ANDROID_TRUE@android_ipc_tester_DEPENDENCIES = \ +@ANDROID_TRUE@ lib/libbluetooth-internal.la \ +@ANDROID_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) +am__android_system_emulator_SOURCES_DIST = android/system-emulator.c +@ANDROID_TRUE@am_android_system_emulator_OBJECTS = \ +@ANDROID_TRUE@ android/system-emulator.$(OBJEXT) +android_system_emulator_OBJECTS = \ + $(am_android_system_emulator_OBJECTS) +@ANDROID_TRUE@android_system_emulator_DEPENDENCIES = \ +@ANDROID_TRUE@ src/libshared-mainloop.la +am__android_test_ipc_SOURCES_DIST = android/test-ipc.c src/log.h \ + src/log.c android/ipc-common.h android/ipc.c android/ipc.h +@ANDROID_TRUE@am_android_test_ipc_OBJECTS = \ +@ANDROID_TRUE@ android/test-ipc.$(OBJEXT) src/log.$(OBJEXT) \ +@ANDROID_TRUE@ android/ipc.$(OBJEXT) +android_test_ipc_OBJECTS = $(am_android_test_ipc_OBJECTS) +@ANDROID_TRUE@android_test_ipc_DEPENDENCIES = src/libshared-glib.la \ +@ANDROID_TRUE@ $(am__DEPENDENCIES_1) +am__attrib_gatttool_SOURCES_DIST = attrib/gatttool.c attrib/att.c \ + attrib/gatt.c attrib/gattrib.c btio/btio.c attrib/gatttool.h \ + attrib/interactive.c attrib/utils.c src/log.c client/display.c \ + client/display.h +@DEPRECATED_TRUE@@READLINE_TRUE@am_attrib_gatttool_OBJECTS = \ +@DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gatttool.$(OBJEXT) \ +@DEPRECATED_TRUE@@READLINE_TRUE@ attrib/att.$(OBJEXT) \ +@DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gatt.$(OBJEXT) \ +@DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gattrib.$(OBJEXT) \ +@DEPRECATED_TRUE@@READLINE_TRUE@ btio/btio.$(OBJEXT) \ +@DEPRECATED_TRUE@@READLINE_TRUE@ attrib/interactive.$(OBJEXT) \ +@DEPRECATED_TRUE@@READLINE_TRUE@ attrib/utils.$(OBJEXT) \ +@DEPRECATED_TRUE@@READLINE_TRUE@ src/log.$(OBJEXT) \ +@DEPRECATED_TRUE@@READLINE_TRUE@ client/display.$(OBJEXT) +attrib_gatttool_OBJECTS = $(am_attrib_gatttool_OBJECTS) +@DEPRECATED_TRUE@@READLINE_TRUE@attrib_gatttool_DEPENDENCIES = \ +@DEPRECATED_TRUE@@READLINE_TRUE@ lib/libbluetooth-internal.la \ +@DEPRECATED_TRUE@@READLINE_TRUE@ src/libshared-glib.la \ +@DEPRECATED_TRUE@@READLINE_TRUE@ $(am__DEPENDENCIES_1) +am__client_bluetoothctl_SOURCES_DIST = client/main.c client/display.h \ + client/display.c client/agent.h client/agent.c \ + client/advertising.h client/advertising.c client/gatt.h \ + client/gatt.c +@CLIENT_TRUE@am_client_bluetoothctl_OBJECTS = client/main.$(OBJEXT) \ +@CLIENT_TRUE@ client/display.$(OBJEXT) client/agent.$(OBJEXT) \ +@CLIENT_TRUE@ client/advertising.$(OBJEXT) \ +@CLIENT_TRUE@ client/gatt.$(OBJEXT) +client_bluetoothctl_OBJECTS = $(am_client_bluetoothctl_OBJECTS) +@CLIENT_TRUE@client_bluetoothctl_DEPENDENCIES = \ +@CLIENT_TRUE@ gdbus/libgdbus-internal.la src/libshared-glib.la \ +@CLIENT_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am__emulator_b1ee_SOURCES_DIST = emulator/b1ee.c +@TESTING_TRUE@am_emulator_b1ee_OBJECTS = emulator/b1ee.$(OBJEXT) +emulator_b1ee_OBJECTS = $(am_emulator_b1ee_OBJECTS) +@TESTING_TRUE@emulator_b1ee_DEPENDENCIES = src/libshared-mainloop.la +am__emulator_btvirt_SOURCES_DIST = emulator/main.c monitor/bt.h \ + emulator/serial.h emulator/serial.c emulator/server.h \ + emulator/server.c emulator/vhci.h emulator/vhci.c \ + emulator/btdev.h emulator/btdev.c emulator/bthost.h \ + emulator/bthost.c emulator/smp.c emulator/phy.h emulator/phy.c \ + emulator/amp.h emulator/amp.c emulator/le.h emulator/le.c +@TESTING_TRUE@am_emulator_btvirt_OBJECTS = emulator/main.$(OBJEXT) \ +@TESTING_TRUE@ emulator/serial.$(OBJEXT) \ +@TESTING_TRUE@ emulator/server.$(OBJEXT) \ +@TESTING_TRUE@ emulator/vhci.$(OBJEXT) emulator/btdev.$(OBJEXT) \ +@TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) \ +@TESTING_TRUE@ emulator/phy.$(OBJEXT) emulator/amp.$(OBJEXT) \ +@TESTING_TRUE@ emulator/le.$(OBJEXT) +emulator_btvirt_OBJECTS = $(am_emulator_btvirt_OBJECTS) +@TESTING_TRUE@emulator_btvirt_DEPENDENCIES = \ +@TESTING_TRUE@ lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-mainloop.la +am__emulator_hfp_SOURCES_DIST = emulator/hfp.c +@TESTING_TRUE@am_emulator_hfp_OBJECTS = emulator/hfp.$(OBJEXT) +emulator_hfp_OBJECTS = $(am_emulator_hfp_OBJECTS) +@TESTING_TRUE@emulator_hfp_DEPENDENCIES = src/libshared-mainloop.la +am__mesh_bluetooth_meshd_SOURCES_DIST = mesh/mesh.h mesh/mesh.c \ + mesh/net-keys.h mesh/net-keys.c mesh/mesh-io.h mesh/mesh-io.c \ + mesh/mesh-mgmt.c mesh/mesh-mgmt.h mesh/error.h \ + mesh/mesh-io-api.h mesh/mesh-io-generic.h \ + mesh/mesh-io-generic.c mesh/net.h mesh/net.c mesh/crypto.h \ + mesh/crypto.c mesh/friend.h mesh/friend.c mesh/appkey.h \ + mesh/appkey.c mesh/node.h mesh/node.c mesh/provision.h \ + mesh/prov.h mesh/model.h mesh/model.c mesh/cfgmod.h \ + mesh/cfgmod-server.c mesh/mesh-config.h \ + mesh/mesh-config-json.c mesh/util.h mesh/util.c mesh/dbus.h \ + mesh/dbus.c mesh/agent.h mesh/agent.c mesh/prov-acceptor.c \ + mesh/prov-initiator.c mesh/manager.h mesh/manager.c \ + mesh/pb-adv.h mesh/pb-adv.c mesh/keyring.h mesh/keyring.c \ + mesh/mesh-defs.h mesh/main.c +@MESH_TRUE@am__objects_7 = mesh/mesh.$(OBJEXT) mesh/net-keys.$(OBJEXT) \ +@MESH_TRUE@ mesh/mesh-io.$(OBJEXT) mesh/mesh-mgmt.$(OBJEXT) \ +@MESH_TRUE@ mesh/mesh-io-generic.$(OBJEXT) mesh/net.$(OBJEXT) \ +@MESH_TRUE@ mesh/crypto.$(OBJEXT) mesh/friend.$(OBJEXT) \ +@MESH_TRUE@ mesh/appkey.$(OBJEXT) mesh/node.$(OBJEXT) \ +@MESH_TRUE@ mesh/model.$(OBJEXT) mesh/cfgmod-server.$(OBJEXT) \ +@MESH_TRUE@ mesh/mesh-config-json.$(OBJEXT) mesh/util.$(OBJEXT) \ +@MESH_TRUE@ mesh/dbus.$(OBJEXT) mesh/agent.$(OBJEXT) \ +@MESH_TRUE@ mesh/prov-acceptor.$(OBJEXT) \ +@MESH_TRUE@ mesh/prov-initiator.$(OBJEXT) \ +@MESH_TRUE@ mesh/manager.$(OBJEXT) mesh/pb-adv.$(OBJEXT) \ +@MESH_TRUE@ mesh/keyring.$(OBJEXT) +@MESH_TRUE@am_mesh_bluetooth_meshd_OBJECTS = $(am__objects_7) \ +@MESH_TRUE@ mesh/main.$(OBJEXT) +mesh_bluetooth_meshd_OBJECTS = $(am_mesh_bluetooth_meshd_OBJECTS) +@EXTERNAL_ELL_FALSE@am__DEPENDENCIES_2 = ell/libell-internal.la +am__monitor_btmon_SOURCES_DIST = monitor/main.c monitor/bt.h \ + monitor/display.h monitor/display.c monitor/hcidump.h \ + monitor/hcidump.c monitor/ellisys.h monitor/ellisys.c \ + monitor/control.h monitor/control.c monitor/packet.h \ + monitor/packet.c monitor/vendor.h monitor/vendor.c \ + monitor/lmp.h monitor/lmp.c monitor/crc.h monitor/crc.c \ + monitor/ll.h monitor/ll.c monitor/l2cap.h monitor/l2cap.c \ + monitor/sdp.h monitor/sdp.c monitor/avctp.h monitor/avctp.c \ + monitor/avdtp.h monitor/avdtp.c monitor/a2dp.h monitor/a2dp.c \ + monitor/rfcomm.h monitor/rfcomm.c monitor/bnep.h \ + monitor/bnep.c monitor/hwdb.h monitor/hwdb.c monitor/keys.h \ + monitor/keys.c monitor/analyze.h monitor/analyze.c \ + monitor/intel.h monitor/intel.c monitor/broadcom.h \ + monitor/broadcom.c monitor/jlink.h monitor/jlink.c \ + monitor/tty.h +@MONITOR_TRUE@am_monitor_btmon_OBJECTS = monitor/main.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/display.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/hcidump.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/ellisys.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/control.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/packet.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/vendor.$(OBJEXT) monitor/lmp.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/crc.$(OBJEXT) monitor/ll.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/l2cap.$(OBJEXT) monitor/sdp.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/avctp.$(OBJEXT) monitor/avdtp.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/a2dp.$(OBJEXT) monitor/rfcomm.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/bnep.$(OBJEXT) monitor/hwdb.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/keys.$(OBJEXT) monitor/analyze.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/intel.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/broadcom.$(OBJEXT) \ +@MONITOR_TRUE@ monitor/jlink.$(OBJEXT) +monitor_btmon_OBJECTS = $(am_monitor_btmon_OBJECTS) +@MONITOR_TRUE@monitor_btmon_DEPENDENCIES = \ +@MONITOR_TRUE@ lib/libbluetooth-internal.la \ +@MONITOR_TRUE@ src/libshared-mainloop.la $(am__DEPENDENCIES_1) +am__obexd_src_obexd_SOURCES_DIST = btio/btio.h btio/btio.c \ + gobex/gobex.h gobex/gobex.c gobex/gobex-defs.h \ + gobex/gobex-defs.c gobex/gobex-packet.c gobex/gobex-packet.h \ + gobex/gobex-header.c gobex/gobex-header.h \ + gobex/gobex-transfer.c gobex/gobex-debug.h \ + gobex/gobex-apparam.c gobex/gobex-apparam.h \ + obexd/plugins/filesystem.c obexd/plugins/filesystem.h \ + obexd/plugins/bluetooth.c obexd/plugins/pcsuite.c \ + obexd/plugins/opp.c obexd/plugins/ftp.c obexd/plugins/ftp.h \ + obexd/plugins/irmc.c obexd/plugins/pbap.c \ + obexd/plugins/vcard.h obexd/plugins/vcard.c \ + obexd/plugins/phonebook.h obexd/plugins/phonebook-dummy.c \ + obexd/plugins/mas.c obexd/src/map_ap.h \ + obexd/plugins/messages.h obexd/plugins/messages-dummy.c \ + obexd/client/mns.c obexd/client/map-event.h obexd/src/main.c \ + obexd/src/obexd.h obexd/src/plugin.h obexd/src/plugin.c \ + obexd/src/log.h obexd/src/log.c obexd/src/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.c obexd/client/transfer.h \ + obexd/client/transfer.c obexd/client/transport.h \ + obexd/client/transport.c obexd/client/driver.h \ + obexd/client/driver.c +am__objects_8 = btio/obexd-btio.$(OBJEXT) +am__objects_9 = gobex/obexd-gobex.$(OBJEXT) \ + gobex/obexd-gobex-defs.$(OBJEXT) \ + gobex/obexd-gobex-packet.$(OBJEXT) \ + gobex/obexd-gobex-header.$(OBJEXT) \ + gobex/obexd-gobex-transfer.$(OBJEXT) \ + gobex/obexd-gobex-apparam.$(OBJEXT) +@EXPERIMENTAL_TRUE@@OBEX_TRUE@am__objects_10 = obexd/plugins/obexd-pcsuite.$(OBJEXT) +@OBEX_TRUE@am__objects_11 = obexd/plugins/obexd-filesystem.$(OBJEXT) \ +@OBEX_TRUE@ obexd/plugins/obexd-bluetooth.$(OBJEXT) \ +@OBEX_TRUE@ $(am__objects_10) obexd/plugins/obexd-opp.$(OBJEXT) \ +@OBEX_TRUE@ obexd/plugins/obexd-ftp.$(OBJEXT) \ +@OBEX_TRUE@ obexd/plugins/obexd-irmc.$(OBJEXT) \ +@OBEX_TRUE@ obexd/plugins/obexd-pbap.$(OBJEXT) \ +@OBEX_TRUE@ obexd/plugins/obexd-vcard.$(OBJEXT) \ +@OBEX_TRUE@ obexd/plugins/obexd-phonebook-dummy.$(OBJEXT) \ +@OBEX_TRUE@ obexd/plugins/obexd-mas.$(OBJEXT) \ +@OBEX_TRUE@ obexd/plugins/obexd-messages-dummy.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-mns.$(OBJEXT) +@OBEX_TRUE@am_obexd_src_obexd_OBJECTS = $(am__objects_8) \ +@OBEX_TRUE@ $(am__objects_9) $(am__objects_11) \ +@OBEX_TRUE@ obexd/src/obexd-main.$(OBJEXT) \ +@OBEX_TRUE@ obexd/src/obexd-plugin.$(OBJEXT) \ +@OBEX_TRUE@ obexd/src/obexd-log.$(OBJEXT) \ +@OBEX_TRUE@ obexd/src/obexd-manager.$(OBJEXT) \ +@OBEX_TRUE@ obexd/src/obexd-obex.$(OBJEXT) \ +@OBEX_TRUE@ obexd/src/obexd-mimetype.$(OBJEXT) \ +@OBEX_TRUE@ obexd/src/obexd-service.$(OBJEXT) \ +@OBEX_TRUE@ obexd/src/obexd-transport.$(OBJEXT) \ +@OBEX_TRUE@ obexd/src/obexd-server.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-manager.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-session.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-bluetooth.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-sync.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-pbap.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-ftp.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-opp.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-map.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-map-event.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-transfer.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-transport.$(OBJEXT) \ +@OBEX_TRUE@ obexd/client/obexd-driver.$(OBJEXT) +am__objects_12 = $(am__objects_1) +nodist_obexd_src_obexd_OBJECTS = $(am__objects_12) +obexd_src_obexd_OBJECTS = $(am_obexd_src_obexd_OBJECTS) \ + $(nodist_obexd_src_obexd_OBJECTS) +@OBEX_TRUE@obexd_src_obexd_DEPENDENCIES = \ +@OBEX_TRUE@ lib/libbluetooth-internal.la \ +@OBEX_TRUE@ gdbus/libgdbus-internal.la $(am__DEPENDENCIES_1) \ +@OBEX_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +obexd_src_obexd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(obexd_src_obexd_CFLAGS) $(CFLAGS) $(obexd_src_obexd_LDFLAGS) \ + $(LDFLAGS) -o $@ +am__peripheral_btsensor_SOURCES_DIST = peripheral/main.c \ + peripheral/efivars.h peripheral/efivars.c peripheral/attach.h \ + peripheral/attach.c peripheral/log.h peripheral/log.c \ + peripheral/gap.h peripheral/gap.c peripheral/gatt.h \ + peripheral/gatt.c +@TESTING_TRUE@am_peripheral_btsensor_OBJECTS = \ +@TESTING_TRUE@ peripheral/main.$(OBJEXT) \ +@TESTING_TRUE@ peripheral/efivars.$(OBJEXT) \ +@TESTING_TRUE@ peripheral/attach.$(OBJEXT) \ +@TESTING_TRUE@ peripheral/log.$(OBJEXT) \ +@TESTING_TRUE@ peripheral/gap.$(OBJEXT) \ +@TESTING_TRUE@ peripheral/gatt.$(OBJEXT) +peripheral_btsensor_OBJECTS = $(am_peripheral_btsensor_OBJECTS) +@TESTING_TRUE@peripheral_btsensor_DEPENDENCIES = \ +@TESTING_TRUE@ src/libshared-mainloop.la \ +@TESTING_TRUE@ lib/libbluetooth-internal.la +am__profiles_cups_bluetooth_SOURCES_DIST = profiles/cups/main.c \ + profiles/cups/cups.h profiles/cups/sdp.c profiles/cups/spp.c \ + profiles/cups/hcrp.c +@CUPS_TRUE@am_profiles_cups_bluetooth_OBJECTS = \ +@CUPS_TRUE@ profiles/cups/main.$(OBJEXT) \ +@CUPS_TRUE@ profiles/cups/sdp.$(OBJEXT) \ +@CUPS_TRUE@ profiles/cups/spp.$(OBJEXT) \ +@CUPS_TRUE@ profiles/cups/hcrp.$(OBJEXT) +profiles_cups_bluetooth_OBJECTS = \ + $(am_profiles_cups_bluetooth_OBJECTS) +@CUPS_TRUE@profiles_cups_bluetooth_DEPENDENCIES = \ +@CUPS_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ +@CUPS_TRUE@ lib/libbluetooth-internal.la \ +@CUPS_TRUE@ gdbus/libgdbus-internal.la +am__profiles_iap_iapd_SOURCES_DIST = profiles/iap/main.c +@TOOLS_TRUE@am_profiles_iap_iapd_OBJECTS = \ +@TOOLS_TRUE@ profiles/iap/main.$(OBJEXT) +profiles_iap_iapd_OBJECTS = $(am_profiles_iap_iapd_OBJECTS) +@TOOLS_TRUE@profiles_iap_iapd_DEPENDENCIES = \ +@TOOLS_TRUE@ gdbus/libgdbus-internal.la $(am__DEPENDENCIES_1) \ +@TOOLS_TRUE@ $(am__DEPENDENCIES_1) +am__src_bluetoothd_SOURCES_DIST = plugins/hostname.c plugins/wiimote.c \ + plugins/autopair.c plugins/policy.c plugins/neard.c \ + 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 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 \ + 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 \ + profiles/network/manager.c profiles/network/bnep.h \ + profiles/network/bnep.c profiles/network/server.h \ + profiles/network/server.c profiles/network/connection.h \ + profiles/network/connection.c profiles/input/manager.c \ + profiles/input/server.h profiles/input/server.c \ + profiles/input/device.h profiles/input/device.c \ + profiles/input/hidp_defs.h profiles/input/sixaxis.h \ + profiles/input/hog.c profiles/input/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 \ + 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 \ + profiles/gap/gas.c profiles/scanparam/scan.c \ + profiles/deviceinfo/deviceinfo.c profiles/midi/midi.c \ + profiles/midi/libmidi.h profiles/midi/libmidi.c \ + profiles/battery/battery.c 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/btio.h btio/btio.c src/bluetooth.ver src/main.c src/log.h \ + src/log.c src/backtrace.h src/backtrace.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 +@NFC_TRUE@am__objects_13 = plugins/bluetoothd-neard.$(OBJEXT) +@SAP_TRUE@am__objects_14 = profiles/sap/bluetoothd-main.$(OBJEXT) \ +@SAP_TRUE@ profiles/sap/bluetoothd-manager.$(OBJEXT) \ +@SAP_TRUE@ profiles/sap/bluetoothd-server.$(OBJEXT) \ +@SAP_TRUE@ profiles/sap/bluetoothd-sap-dummy.$(OBJEXT) +@A2DP_TRUE@am__objects_15 = \ +@A2DP_TRUE@ profiles/audio/bluetoothd-source.$(OBJEXT) \ +@A2DP_TRUE@ profiles/audio/bluetoothd-sink.$(OBJEXT) \ +@A2DP_TRUE@ profiles/audio/bluetoothd-a2dp.$(OBJEXT) \ +@A2DP_TRUE@ profiles/audio/bluetoothd-avdtp.$(OBJEXT) \ +@A2DP_TRUE@ profiles/audio/bluetoothd-media.$(OBJEXT) \ +@A2DP_TRUE@ profiles/audio/bluetoothd-transport.$(OBJEXT) +@AVRCP_TRUE@am__objects_16 = \ +@AVRCP_TRUE@ profiles/audio/bluetoothd-control.$(OBJEXT) \ +@AVRCP_TRUE@ profiles/audio/bluetoothd-avctp.$(OBJEXT) \ +@AVRCP_TRUE@ profiles/audio/bluetoothd-avrcp.$(OBJEXT) \ +@AVRCP_TRUE@ profiles/audio/bluetoothd-player.$(OBJEXT) +@NETWORK_TRUE@am__objects_17 = \ +@NETWORK_TRUE@ profiles/network/bluetoothd-manager.$(OBJEXT) \ +@NETWORK_TRUE@ profiles/network/bluetoothd-bnep.$(OBJEXT) \ +@NETWORK_TRUE@ profiles/network/bluetoothd-server.$(OBJEXT) \ +@NETWORK_TRUE@ profiles/network/bluetoothd-connection.$(OBJEXT) +@HID_TRUE@am__objects_18 = \ +@HID_TRUE@ profiles/input/bluetoothd-manager.$(OBJEXT) \ +@HID_TRUE@ profiles/input/bluetoothd-server.$(OBJEXT) \ +@HID_TRUE@ profiles/input/bluetoothd-device.$(OBJEXT) +@HOG_TRUE@am__objects_19 = profiles/input/bluetoothd-hog.$(OBJEXT) \ +@HOG_TRUE@ profiles/input/bluetoothd-hog-lib.$(OBJEXT) \ +@HOG_TRUE@ profiles/deviceinfo/bluetoothd-dis.$(OBJEXT) \ +@HOG_TRUE@ profiles/battery/bluetoothd-bas.$(OBJEXT) \ +@HOG_TRUE@ profiles/scanparam/bluetoothd-scpp.$(OBJEXT) \ +@HOG_TRUE@ profiles/input/bluetoothd-suspend-none.$(OBJEXT) +@HEALTH_TRUE@am__objects_20 = \ +@HEALTH_TRUE@ profiles/health/bluetoothd-mcap.$(OBJEXT) \ +@HEALTH_TRUE@ profiles/health/bluetoothd-hdp_main.$(OBJEXT) \ +@HEALTH_TRUE@ profiles/health/bluetoothd-hdp_manager.$(OBJEXT) \ +@HEALTH_TRUE@ profiles/health/bluetoothd-hdp.$(OBJEXT) \ +@HEALTH_TRUE@ profiles/health/bluetoothd-hdp_util.$(OBJEXT) +@MIDI_TRUE@am__objects_21 = profiles/midi/bluetoothd-midi.$(OBJEXT) \ +@MIDI_TRUE@ profiles/midi/bluetoothd-libmidi.$(OBJEXT) +am__objects_22 = plugins/bluetoothd-hostname.$(OBJEXT) \ + plugins/bluetoothd-wiimote.$(OBJEXT) \ + plugins/bluetoothd-autopair.$(OBJEXT) \ + plugins/bluetoothd-policy.$(OBJEXT) $(am__objects_13) \ + $(am__objects_14) $(am__objects_15) $(am__objects_16) \ + $(am__objects_17) $(am__objects_18) $(am__objects_19) \ + $(am__objects_20) profiles/gap/bluetoothd-gas.$(OBJEXT) \ + profiles/scanparam/bluetoothd-scan.$(OBJEXT) \ + profiles/deviceinfo/bluetoothd-deviceinfo.$(OBJEXT) \ + $(am__objects_21) \ + profiles/battery/bluetoothd-battery.$(OBJEXT) +am__objects_23 = attrib/bluetoothd-att.$(OBJEXT) \ + attrib/bluetoothd-gatt.$(OBJEXT) \ + attrib/bluetoothd-gattrib.$(OBJEXT) \ + attrib/bluetoothd-gatt-service.$(OBJEXT) +am__objects_24 = btio/bluetoothd-btio.$(OBJEXT) +am_src_bluetoothd_OBJECTS = $(am__objects_22) $(am__objects_23) \ + $(am__objects_24) src/bluetoothd-main.$(OBJEXT) \ + src/bluetoothd-log.$(OBJEXT) \ + src/bluetoothd-backtrace.$(OBJEXT) \ + src/bluetoothd-rfkill.$(OBJEXT) \ + src/bluetoothd-sdpd-server.$(OBJEXT) \ + src/bluetoothd-sdpd-request.$(OBJEXT) \ + src/bluetoothd-sdpd-service.$(OBJEXT) \ + src/bluetoothd-sdpd-database.$(OBJEXT) \ + src/bluetoothd-attrib-server.$(OBJEXT) \ + src/bluetoothd-gatt-database.$(OBJEXT) \ + src/bluetoothd-sdp-xml.$(OBJEXT) \ + src/bluetoothd-sdp-client.$(OBJEXT) \ + src/bluetoothd-textfile.$(OBJEXT) \ + src/bluetoothd-uuid-helper.$(OBJEXT) \ + src/bluetoothd-plugin.$(OBJEXT) \ + src/bluetoothd-storage.$(OBJEXT) \ + src/bluetoothd-advertising.$(OBJEXT) \ + src/bluetoothd-agent.$(OBJEXT) src/bluetoothd-error.$(OBJEXT) \ + src/bluetoothd-adapter.$(OBJEXT) \ + src/bluetoothd-profile.$(OBJEXT) \ + src/bluetoothd-service.$(OBJEXT) \ + src/bluetoothd-gatt-client.$(OBJEXT) \ + src/bluetoothd-device.$(OBJEXT) \ + src/bluetoothd-dbus-common.$(OBJEXT) \ + src/bluetoothd-eir.$(OBJEXT) +nodist_src_bluetoothd_OBJECTS = $(am__objects_12) +src_bluetoothd_OBJECTS = $(am_src_bluetoothd_OBJECTS) \ + $(nodist_src_bluetoothd_OBJECTS) +@MIDI_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) +am__DEPENDENCIES_4 = $(am__DEPENDENCIES_3) +src_bluetoothd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(AM_CFLAGS) $(CFLAGS) $(src_bluetoothd_LDFLAGS) $(LDFLAGS) -o \ + $@ +am__tools_3dsp_SOURCES_DIST = tools/3dsp.c monitor/bt.h +@TESTING_TRUE@am_tools_3dsp_OBJECTS = tools/3dsp.$(OBJEXT) +tools_3dsp_OBJECTS = $(am_tools_3dsp_OBJECTS) +@TESTING_TRUE@tools_3dsp_DEPENDENCIES = src/libshared-mainloop.la +am__tools_advtest_SOURCES_DIST = tools/advtest.c +@TOOLS_TRUE@am_tools_advtest_OBJECTS = tools/advtest.$(OBJEXT) +tools_advtest_OBJECTS = $(am_tools_advtest_OBJECTS) +@TOOLS_TRUE@tools_advtest_DEPENDENCIES = lib/libbluetooth-internal.la \ +@TOOLS_TRUE@ src/libshared-mainloop.la +tools_amptest_SOURCES = tools/amptest.c +tools_amptest_OBJECTS = tools/amptest.$(OBJEXT) +@TOOLS_TRUE@tools_amptest_DEPENDENCIES = lib/libbluetooth-internal.la +tools_avinfo_SOURCES = tools/avinfo.c +tools_avinfo_OBJECTS = tools/avinfo.$(OBJEXT) +@TOOLS_TRUE@tools_avinfo_DEPENDENCIES = lib/libbluetooth-internal.la +tools_avtest_SOURCES = tools/avtest.c +tools_avtest_OBJECTS = tools/avtest.$(OBJEXT) +@TOOLS_TRUE@tools_avtest_DEPENDENCIES = lib/libbluetooth-internal.la +am__tools_bccmd_SOURCES_DIST = 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_TRUE@am_tools_bccmd_OBJECTS = tools/bccmd.$(OBJEXT) \ +@TOOLS_TRUE@ tools/csr.$(OBJEXT) tools/csr_hci.$(OBJEXT) \ +@TOOLS_TRUE@ tools/csr_usb.$(OBJEXT) tools/csr_h4.$(OBJEXT) \ +@TOOLS_TRUE@ tools/csr_3wire.$(OBJEXT) tools/csr_bcsp.$(OBJEXT) \ +@TOOLS_TRUE@ tools/ubcsp.$(OBJEXT) +tools_bccmd_OBJECTS = $(am_tools_bccmd_OBJECTS) +@TOOLS_TRUE@tools_bccmd_DEPENDENCIES = lib/libbluetooth-internal.la +tools_bcmfw_SOURCES = tools/bcmfw.c +tools_bcmfw_OBJECTS = tools/bcmfw.$(OBJEXT) +tools_bcmfw_LDADD = $(LDADD) +am__tools_bdaddr_SOURCES_DIST = tools/bdaddr.c src/oui.h src/oui.c +@TOOLS_TRUE@am_tools_bdaddr_OBJECTS = tools/bdaddr.$(OBJEXT) \ +@TOOLS_TRUE@ src/oui.$(OBJEXT) +tools_bdaddr_OBJECTS = $(am_tools_bdaddr_OBJECTS) +@TOOLS_TRUE@tools_bdaddr_DEPENDENCIES = lib/libbluetooth-internal.la \ +@TOOLS_TRUE@ $(am__DEPENDENCIES_1) +am__tools_bluemoon_SOURCES_DIST = tools/bluemoon.c monitor/bt.h +@TOOLS_TRUE@am_tools_bluemoon_OBJECTS = tools/bluemoon.$(OBJEXT) +tools_bluemoon_OBJECTS = $(am_tools_bluemoon_OBJECTS) +@TOOLS_TRUE@tools_bluemoon_DEPENDENCIES = src/libshared-mainloop.la +am__tools_bluetooth_player_SOURCES_DIST = tools/bluetooth-player.c +@READLINE_TRUE@am_tools_bluetooth_player_OBJECTS = \ +@READLINE_TRUE@ tools/bluetooth-player.$(OBJEXT) +tools_bluetooth_player_OBJECTS = $(am_tools_bluetooth_player_OBJECTS) +@READLINE_TRUE@tools_bluetooth_player_DEPENDENCIES = \ +@READLINE_TRUE@ gdbus/libgdbus-internal.la \ +@READLINE_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) \ +@READLINE_TRUE@ $(am__DEPENDENCIES_1) +am__tools_bnep_tester_SOURCES_DIST = tools/bnep-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c emulator/btdev.h \ + emulator/btdev.c emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +@TESTING_TRUE@am_tools_bnep_tester_OBJECTS = \ +@TESTING_TRUE@ tools/bnep-tester.$(OBJEXT) \ +@TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ +@TESTING_TRUE@ emulator/btdev.$(OBJEXT) \ +@TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) +tools_bnep_tester_OBJECTS = $(am_tools_bnep_tester_OBJECTS) +@TESTING_TRUE@tools_bnep_tester_DEPENDENCIES = \ +@TESTING_TRUE@ lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) +am__tools_bneptest_SOURCES_DIST = tools/bneptest.c btio/btio.h \ + btio/btio.c src/log.h src/log.c profiles/network/bnep.h \ + profiles/network/bnep.c +@TOOLS_TRUE@am_tools_bneptest_OBJECTS = tools/bneptest.$(OBJEXT) \ +@TOOLS_TRUE@ btio/btio.$(OBJEXT) src/log.$(OBJEXT) \ +@TOOLS_TRUE@ profiles/network/bnep.$(OBJEXT) +tools_bneptest_OBJECTS = $(am_tools_bneptest_OBJECTS) +@TOOLS_TRUE@tools_bneptest_DEPENDENCIES = \ +@TOOLS_TRUE@ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) \ +@TOOLS_TRUE@ src/libshared-mainloop.la +am__tools_btattach_SOURCES_DIST = tools/btattach.c monitor/bt.h +@TOOLS_TRUE@am_tools_btattach_OBJECTS = tools/btattach.$(OBJEXT) +tools_btattach_OBJECTS = $(am_tools_btattach_OBJECTS) +@TOOLS_TRUE@tools_btattach_DEPENDENCIES = src/libshared-mainloop.la +am__tools_btconfig_SOURCES_DIST = tools/btconfig.c +@TOOLS_TRUE@am_tools_btconfig_OBJECTS = tools/btconfig.$(OBJEXT) +tools_btconfig_OBJECTS = $(am_tools_btconfig_OBJECTS) +@TOOLS_TRUE@tools_btconfig_DEPENDENCIES = src/libshared-mainloop.la +am__tools_btgatt_client_SOURCES_DIST = tools/btgatt-client.c \ + src/uuid-helper.c +@TOOLS_TRUE@am_tools_btgatt_client_OBJECTS = \ +@TOOLS_TRUE@ tools/btgatt-client.$(OBJEXT) \ +@TOOLS_TRUE@ src/uuid-helper.$(OBJEXT) +tools_btgatt_client_OBJECTS = $(am_tools_btgatt_client_OBJECTS) +@TOOLS_TRUE@tools_btgatt_client_DEPENDENCIES = \ +@TOOLS_TRUE@ src/libshared-mainloop.la \ +@TOOLS_TRUE@ lib/libbluetooth-internal.la +am__tools_btgatt_server_SOURCES_DIST = tools/btgatt-server.c \ + src/uuid-helper.c +@TOOLS_TRUE@am_tools_btgatt_server_OBJECTS = \ +@TOOLS_TRUE@ tools/btgatt-server.$(OBJEXT) \ +@TOOLS_TRUE@ src/uuid-helper.$(OBJEXT) +tools_btgatt_server_OBJECTS = $(am_tools_btgatt_server_OBJECTS) +@TOOLS_TRUE@tools_btgatt_server_DEPENDENCIES = \ +@TOOLS_TRUE@ src/libshared-mainloop.la \ +@TOOLS_TRUE@ lib/libbluetooth-internal.la +am__tools_btinfo_SOURCES_DIST = tools/btinfo.c monitor/bt.h +@TOOLS_TRUE@am_tools_btinfo_OBJECTS = tools/btinfo.$(OBJEXT) +tools_btinfo_OBJECTS = $(am_tools_btinfo_OBJECTS) +@TOOLS_TRUE@tools_btinfo_DEPENDENCIES = src/libshared-mainloop.la +am__tools_btiotest_SOURCES_DIST = tools/btiotest.c btio/btio.h \ + btio/btio.c +@TOOLS_TRUE@am_tools_btiotest_OBJECTS = tools/btiotest.$(OBJEXT) \ +@TOOLS_TRUE@ btio/btio.$(OBJEXT) +tools_btiotest_OBJECTS = $(am_tools_btiotest_OBJECTS) +@TOOLS_TRUE@tools_btiotest_DEPENDENCIES = \ +@TOOLS_TRUE@ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) +am__tools_btmgmt_SOURCES_DIST = tools/btmgmt.c src/uuid-helper.c \ + client/display.c +@READLINE_TRUE@am_tools_btmgmt_OBJECTS = tools/btmgmt.$(OBJEXT) \ +@READLINE_TRUE@ src/uuid-helper.$(OBJEXT) \ +@READLINE_TRUE@ client/display.$(OBJEXT) +tools_btmgmt_OBJECTS = $(am_tools_btmgmt_OBJECTS) +@READLINE_TRUE@tools_btmgmt_DEPENDENCIES = \ +@READLINE_TRUE@ lib/libbluetooth-internal.la \ +@READLINE_TRUE@ src/libshared-mainloop.la +am__tools_btmon_logger_SOURCES_DIST = tools/btmon-logger.c +@LOGGER_TRUE@am_tools_btmon_logger_OBJECTS = \ +@LOGGER_TRUE@ tools/btmon-logger.$(OBJEXT) +tools_btmon_logger_OBJECTS = $(am_tools_btmon_logger_OBJECTS) +am__tools_btpclient_SOURCES_DIST = tools/btpclient.c src/shared/btp.c \ + src/shared/btp.h +@BTPCLIENT_TRUE@am_tools_btpclient_OBJECTS = \ +@BTPCLIENT_TRUE@ tools/btpclient.$(OBJEXT) \ +@BTPCLIENT_TRUE@ src/shared/btp.$(OBJEXT) +tools_btpclient_OBJECTS = $(am_tools_btpclient_OBJECTS) +am__tools_btproxy_SOURCES_DIST = tools/btproxy.c monitor/bt.h +@TOOLS_TRUE@am_tools_btproxy_OBJECTS = tools/btproxy.$(OBJEXT) +tools_btproxy_OBJECTS = $(am_tools_btproxy_OBJECTS) +@TOOLS_TRUE@tools_btproxy_DEPENDENCIES = src/libshared-mainloop.la +am__tools_btsnoop_SOURCES_DIST = tools/btsnoop.c +@TOOLS_TRUE@am_tools_btsnoop_OBJECTS = tools/btsnoop.$(OBJEXT) +tools_btsnoop_OBJECTS = $(am_tools_btsnoop_OBJECTS) +@TOOLS_TRUE@tools_btsnoop_DEPENDENCIES = src/libshared-mainloop.la +tools_check_selftest_SOURCES = tools/check-selftest.c +tools_check_selftest_OBJECTS = tools/check-selftest.$(OBJEXT) +tools_check_selftest_LDADD = $(LDADD) +tools_ciptool_SOURCES = tools/ciptool.c +tools_ciptool_OBJECTS = tools/ciptool.$(OBJEXT) +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_ciptool_DEPENDENCIES = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la +am__tools_cltest_SOURCES_DIST = tools/cltest.c +@TOOLS_TRUE@am_tools_cltest_OBJECTS = tools/cltest.$(OBJEXT) +tools_cltest_OBJECTS = $(am_tools_cltest_OBJECTS) +@TOOLS_TRUE@tools_cltest_DEPENDENCIES = lib/libbluetooth-internal.la \ +@TOOLS_TRUE@ src/libshared-mainloop.la +am__tools_create_image_SOURCES_DIST = tools/create-image.c +@TOOLS_TRUE@am_tools_create_image_OBJECTS = \ +@TOOLS_TRUE@ tools/create-image.$(OBJEXT) +tools_create_image_OBJECTS = $(am_tools_create_image_OBJECTS) +tools_create_image_LDADD = $(LDADD) +am__tools_eddystone_SOURCES_DIST = tools/eddystone.c monitor/bt.h +@TOOLS_TRUE@am_tools_eddystone_OBJECTS = tools/eddystone.$(OBJEXT) +tools_eddystone_OBJECTS = $(am_tools_eddystone_OBJECTS) +@TOOLS_TRUE@tools_eddystone_DEPENDENCIES = src/libshared-mainloop.la +am__tools_gap_tester_SOURCES_DIST = tools/gap-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c emulator/btdev.h \ + emulator/btdev.c emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +@TESTING_TRUE@am_tools_gap_tester_OBJECTS = \ +@TESTING_TRUE@ tools/gap-tester.$(OBJEXT) \ +@TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ +@TESTING_TRUE@ emulator/btdev.$(OBJEXT) \ +@TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) +tools_gap_tester_OBJECTS = $(am_tools_gap_tester_OBJECTS) +@TESTING_TRUE@tools_gap_tester_DEPENDENCIES = \ +@TESTING_TRUE@ lib/libbluetooth-internal.la \ +@TESTING_TRUE@ gdbus/libgdbus-internal.la src/libshared-glib.la \ +@TESTING_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am__tools_gatt_service_SOURCES_DIST = tools/gatt-service.c +@TOOLS_TRUE@am_tools_gatt_service_OBJECTS = \ +@TOOLS_TRUE@ tools/gatt-service.$(OBJEXT) +tools_gatt_service_OBJECTS = $(am_tools_gatt_service_OBJECTS) +@TOOLS_TRUE@tools_gatt_service_DEPENDENCIES = $(am__DEPENDENCIES_1) \ +@TOOLS_TRUE@ $(am__DEPENDENCIES_1) gdbus/libgdbus-internal.la +am__tools_hci_tester_SOURCES_DIST = tools/hci-tester.c monitor/bt.h +@TESTING_TRUE@am_tools_hci_tester_OBJECTS = \ +@TESTING_TRUE@ tools/hci-tester.$(OBJEXT) +tools_hci_tester_OBJECTS = $(am_tools_hci_tester_OBJECTS) +@TESTING_TRUE@tools_hci_tester_DEPENDENCIES = src/libshared-glib.la \ +@TESTING_TRUE@ $(am__DEPENDENCIES_1) +am__tools_hciattach_SOURCES_DIST = tools/hciattach.c tools/hciattach.h \ + tools/hciattach_st.c tools/hciattach_ti.c \ + tools/hciattach_tialt.c tools/hciattach_ath3k.c \ + tools/hciattach_qualcomm.c tools/hciattach_intel.c \ + tools/hciattach_bcm43xx.c +@DEPRECATED_TRUE@@TOOLS_TRUE@am_tools_hciattach_OBJECTS = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_st.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_ti.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_tialt.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_ath3k.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_qualcomm.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_intel.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_bcm43xx.$(OBJEXT) +tools_hciattach_OBJECTS = $(am_tools_hciattach_OBJECTS) +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciattach_DEPENDENCIES = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la +am__tools_hciconfig_SOURCES_DIST = tools/hciconfig.c tools/csr.h \ + tools/csr.c +@DEPRECATED_TRUE@@TOOLS_TRUE@am_tools_hciconfig_OBJECTS = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciconfig.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/csr.$(OBJEXT) +tools_hciconfig_OBJECTS = $(am_tools_hciconfig_OBJECTS) +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciconfig_DEPENDENCIES = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la +am__tools_hcidump_SOURCES_DIST = 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 +@DEPRECATED_TRUE@@TOOLS_TRUE@am_tools_hcidump_OBJECTS = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hcidump.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/parser.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/lmp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/hci.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/l2cap.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/amp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/smp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/att.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/sdp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/rfcomm.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/bnep.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/cmtp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/hidp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/hcrp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/avdtp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/avctp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/avrcp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/sap.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/obex.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/capi.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/ppp.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/tcpip.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/ericsson.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/csr.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/bpa.$(OBJEXT) +tools_hcidump_OBJECTS = $(am_tools_hcidump_OBJECTS) +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hcidump_DEPENDENCIES = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la +tools_hcieventmask_SOURCES = tools/hcieventmask.c +tools_hcieventmask_OBJECTS = tools/hcieventmask.$(OBJEXT) +@TOOLS_TRUE@tools_hcieventmask_DEPENDENCIES = \ +@TOOLS_TRUE@ lib/libbluetooth-internal.la +tools_hcisecfilter_SOURCES = tools/hcisecfilter.c +tools_hcisecfilter_OBJECTS = tools/hcisecfilter.$(OBJEXT) +tools_hcisecfilter_LDADD = $(LDADD) +am__tools_hcitool_SOURCES_DIST = tools/hcitool.c src/oui.h src/oui.c +@DEPRECATED_TRUE@@TOOLS_TRUE@am_tools_hcitool_OBJECTS = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hcitool.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ src/oui.$(OBJEXT) +tools_hcitool_OBJECTS = $(am_tools_hcitool_OBJECTS) +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hcitool_DEPENDENCIES = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ $(am__DEPENDENCIES_1) +am__tools_hex2hcd_SOURCES_DIST = tools/hex2hcd.c +@TOOLS_TRUE@am_tools_hex2hcd_OBJECTS = tools/hex2hcd.$(OBJEXT) +tools_hex2hcd_OBJECTS = $(am_tools_hex2hcd_OBJECTS) +tools_hex2hcd_LDADD = $(LDADD) +tools_hid2hci_SOURCES = tools/hid2hci.c +tools_hid2hci_OBJECTS = tools/hid2hci.$(OBJEXT) +@HID2HCI_TRUE@tools_hid2hci_DEPENDENCIES = $(am__DEPENDENCIES_1) +tools_hwdb_SOURCES = tools/hwdb.c +tools_hwdb_OBJECTS = tools/hwdb.$(OBJEXT) +@TOOLS_TRUE@tools_hwdb_DEPENDENCIES = lib/libbluetooth-internal.la +am__tools_ibeacon_SOURCES_DIST = tools/ibeacon.c monitor/bt.h +@TOOLS_TRUE@am_tools_ibeacon_OBJECTS = tools/ibeacon.$(OBJEXT) +tools_ibeacon_OBJECTS = $(am_tools_ibeacon_OBJECTS) +@TOOLS_TRUE@tools_ibeacon_DEPENDENCIES = src/libshared-mainloop.la +am__tools_l2cap_tester_SOURCES_DIST = 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 +@TESTING_TRUE@am_tools_l2cap_tester_OBJECTS = \ +@TESTING_TRUE@ tools/l2cap-tester.$(OBJEXT) \ +@TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ +@TESTING_TRUE@ emulator/btdev.$(OBJEXT) \ +@TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) +tools_l2cap_tester_OBJECTS = $(am_tools_l2cap_tester_OBJECTS) +@TESTING_TRUE@tools_l2cap_tester_DEPENDENCIES = \ +@TESTING_TRUE@ lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) +tools_l2ping_SOURCES = tools/l2ping.c +tools_l2ping_OBJECTS = tools/l2ping.$(OBJEXT) +@TOOLS_TRUE@tools_l2ping_DEPENDENCIES = lib/libbluetooth-internal.la +tools_l2test_SOURCES = tools/l2test.c +tools_l2test_OBJECTS = tools/l2test.$(OBJEXT) +@TOOLS_TRUE@tools_l2test_DEPENDENCIES = lib/libbluetooth-internal.la +am__tools_mcaptest_SOURCES_DIST = tools/mcaptest.c btio/btio.h \ + btio/btio.c src/log.c src/log.h profiles/health/mcap.h \ + profiles/health/mcap.c +@TOOLS_TRUE@am_tools_mcaptest_OBJECTS = tools/mcaptest.$(OBJEXT) \ +@TOOLS_TRUE@ btio/btio.$(OBJEXT) src/log.$(OBJEXT) \ +@TOOLS_TRUE@ profiles/health/mcap.$(OBJEXT) +tools_mcaptest_OBJECTS = $(am_tools_mcaptest_OBJECTS) +@TOOLS_TRUE@tools_mcaptest_DEPENDENCIES = \ +@TOOLS_TRUE@ lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) \ +@TOOLS_TRUE@ src/libshared-mainloop.la +am__tools_meshctl_SOURCES_DIST = tools/meshctl.c tools/mesh/mesh-net.h \ + tools/mesh/node.h tools/mesh/node.c tools/mesh/gatt.h \ + tools/mesh/gatt.c tools/mesh/crypto.h tools/mesh/crypto.c \ + tools/mesh/keys.h tools/mesh/net.h tools/mesh/net.c \ + tools/mesh/prov.h tools/mesh/prov.c tools/mesh/util.h \ + tools/mesh/util.c tools/mesh/agent.h tools/mesh/agent.c \ + tools/mesh/prov-db.h tools/mesh/prov-db.c \ + tools/mesh/config-model.h tools/mesh/config-client.c \ + tools/mesh/config-server.c tools/mesh/onoff-model.h \ + tools/mesh/onoff-model.c +@MESH_TRUE@@TOOLS_TRUE@am_tools_meshctl_OBJECTS = \ +@MESH_TRUE@@TOOLS_TRUE@ tools/meshctl.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/node.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/gatt.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/crypto.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/net.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/prov.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/util.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/agent.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/prov-db.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/config-client.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/config-server.$(OBJEXT) \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/onoff-model.$(OBJEXT) +tools_meshctl_OBJECTS = $(am_tools_meshctl_OBJECTS) +@MESH_TRUE@@TOOLS_TRUE@tools_meshctl_DEPENDENCIES = \ +@MESH_TRUE@@TOOLS_TRUE@ gdbus/libgdbus-internal.la \ +@MESH_TRUE@@TOOLS_TRUE@ src/libshared-glib.la \ +@MESH_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la \ +@MESH_TRUE@@TOOLS_TRUE@ $(am__DEPENDENCIES_1) \ +@MESH_TRUE@@TOOLS_TRUE@ $(am__DEPENDENCIES_1) +am__tools_mgmt_tester_SOURCES_DIST = tools/mgmt-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c emulator/btdev.h \ + emulator/btdev.c emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +@TESTING_TRUE@am_tools_mgmt_tester_OBJECTS = \ +@TESTING_TRUE@ tools/mgmt-tester.$(OBJEXT) \ +@TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ +@TESTING_TRUE@ emulator/btdev.$(OBJEXT) \ +@TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) +tools_mgmt_tester_OBJECTS = $(am_tools_mgmt_tester_OBJECTS) +@TESTING_TRUE@tools_mgmt_tester_DEPENDENCIES = \ +@TESTING_TRUE@ lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) +am__tools_mpris_proxy_SOURCES_DIST = tools/mpris-proxy.c +@TOOLS_TRUE@am_tools_mpris_proxy_OBJECTS = \ +@TOOLS_TRUE@ tools/mpris-proxy.$(OBJEXT) +tools_mpris_proxy_OBJECTS = $(am_tools_mpris_proxy_OBJECTS) +@TOOLS_TRUE@tools_mpris_proxy_DEPENDENCIES = \ +@TOOLS_TRUE@ gdbus/libgdbus-internal.la $(am__DEPENDENCIES_1) \ +@TOOLS_TRUE@ $(am__DEPENDENCIES_1) +am__tools_nokfw_SOURCES_DIST = tools/nokfw.c +@TOOLS_TRUE@am_tools_nokfw_OBJECTS = tools/nokfw.$(OBJEXT) +tools_nokfw_OBJECTS = $(am_tools_nokfw_OBJECTS) +tools_nokfw_LDADD = $(LDADD) +am__tools_obex_client_tool_SOURCES_DIST = gobex/gobex.h gobex/gobex.c \ + gobex/gobex-defs.h gobex/gobex-defs.c gobex/gobex-packet.c \ + gobex/gobex-packet.h gobex/gobex-header.c gobex/gobex-header.h \ + gobex/gobex-transfer.c gobex/gobex-debug.h \ + gobex/gobex-apparam.c gobex/gobex-apparam.h btio/btio.h \ + btio/btio.c tools/obex-client-tool.c +am__objects_25 = gobex/gobex.$(OBJEXT) gobex/gobex-defs.$(OBJEXT) \ + gobex/gobex-packet.$(OBJEXT) gobex/gobex-header.$(OBJEXT) \ + gobex/gobex-transfer.$(OBJEXT) gobex/gobex-apparam.$(OBJEXT) +am__objects_26 = btio/btio.$(OBJEXT) +@READLINE_TRUE@am_tools_obex_client_tool_OBJECTS = $(am__objects_25) \ +@READLINE_TRUE@ $(am__objects_26) \ +@READLINE_TRUE@ tools/obex-client-tool.$(OBJEXT) +tools_obex_client_tool_OBJECTS = $(am_tools_obex_client_tool_OBJECTS) +@READLINE_TRUE@tools_obex_client_tool_DEPENDENCIES = \ +@READLINE_TRUE@ lib/libbluetooth-internal.la \ +@READLINE_TRUE@ $(am__DEPENDENCIES_1) +am__tools_obex_server_tool_SOURCES_DIST = gobex/gobex.h gobex/gobex.c \ + gobex/gobex-defs.h gobex/gobex-defs.c gobex/gobex-packet.c \ + gobex/gobex-packet.h gobex/gobex-header.c gobex/gobex-header.h \ + gobex/gobex-transfer.c gobex/gobex-debug.h \ + gobex/gobex-apparam.c gobex/gobex-apparam.h btio/btio.h \ + btio/btio.c tools/obex-server-tool.c +@READLINE_TRUE@am_tools_obex_server_tool_OBJECTS = $(am__objects_25) \ +@READLINE_TRUE@ $(am__objects_26) \ +@READLINE_TRUE@ tools/obex-server-tool.$(OBJEXT) +tools_obex_server_tool_OBJECTS = $(am_tools_obex_server_tool_OBJECTS) +@READLINE_TRUE@tools_obex_server_tool_DEPENDENCIES = \ +@READLINE_TRUE@ lib/libbluetooth-internal.la \ +@READLINE_TRUE@ $(am__DEPENDENCIES_1) +am__tools_obexctl_SOURCES_DIST = tools/obexctl.c +@READLINE_TRUE@am_tools_obexctl_OBJECTS = tools/obexctl.$(OBJEXT) +tools_obexctl_OBJECTS = $(am_tools_obexctl_OBJECTS) +@READLINE_TRUE@tools_obexctl_DEPENDENCIES = \ +@READLINE_TRUE@ gdbus/libgdbus-internal.la \ +@READLINE_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) \ +@READLINE_TRUE@ $(am__DEPENDENCIES_1) +am__tools_oobtest_SOURCES_DIST = tools/oobtest.c +@TOOLS_TRUE@am_tools_oobtest_OBJECTS = tools/oobtest.$(OBJEXT) +tools_oobtest_OBJECTS = $(am_tools_oobtest_OBJECTS) +@TOOLS_TRUE@tools_oobtest_DEPENDENCIES = lib/libbluetooth-internal.la \ +@TOOLS_TRUE@ src/libshared-mainloop.la +tools_rctest_SOURCES = tools/rctest.c +tools_rctest_OBJECTS = tools/rctest.$(OBJEXT) +@TOOLS_TRUE@tools_rctest_DEPENDENCIES = lib/libbluetooth-internal.la +tools_rfcomm_SOURCES = tools/rfcomm.c +tools_rfcomm_OBJECTS = tools/rfcomm.$(OBJEXT) +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_rfcomm_DEPENDENCIES = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la +am__tools_rfcomm_tester_SOURCES_DIST = tools/rfcomm-tester.c \ + monitor/bt.h emulator/hciemu.h emulator/hciemu.c \ + emulator/btdev.h emulator/btdev.c emulator/bthost.h \ + emulator/bthost.c emulator/smp.c +@TESTING_TRUE@am_tools_rfcomm_tester_OBJECTS = \ +@TESTING_TRUE@ tools/rfcomm-tester.$(OBJEXT) \ +@TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ +@TESTING_TRUE@ emulator/btdev.$(OBJEXT) \ +@TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) +tools_rfcomm_tester_OBJECTS = $(am_tools_rfcomm_tester_OBJECTS) +@TESTING_TRUE@tools_rfcomm_tester_DEPENDENCIES = \ +@TESTING_TRUE@ lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) +am__tools_rtlfw_SOURCES_DIST = tools/rtlfw.c +@TOOLS_TRUE@am_tools_rtlfw_OBJECTS = tools/rtlfw.$(OBJEXT) +tools_rtlfw_OBJECTS = $(am_tools_rtlfw_OBJECTS) +tools_rtlfw_LDADD = $(LDADD) +am__tools_sco_tester_SOURCES_DIST = tools/sco-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c emulator/btdev.h \ + emulator/btdev.c emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +@TESTING_TRUE@am_tools_sco_tester_OBJECTS = \ +@TESTING_TRUE@ tools/sco-tester.$(OBJEXT) \ +@TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ +@TESTING_TRUE@ emulator/btdev.$(OBJEXT) \ +@TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) +tools_sco_tester_OBJECTS = $(am_tools_sco_tester_OBJECTS) +@TESTING_TRUE@tools_sco_tester_DEPENDENCIES = \ +@TESTING_TRUE@ lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) +tools_scotest_SOURCES = tools/scotest.c +tools_scotest_OBJECTS = tools/scotest.$(OBJEXT) +@TOOLS_TRUE@tools_scotest_DEPENDENCIES = lib/libbluetooth-internal.la +am__tools_sdptool_SOURCES_DIST = tools/sdptool.c src/sdp-xml.h \ + src/sdp-xml.c +@DEPRECATED_TRUE@@TOOLS_TRUE@am_tools_sdptool_OBJECTS = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/sdptool.$(OBJEXT) \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ src/sdp-xml.$(OBJEXT) +tools_sdptool_OBJECTS = $(am_tools_sdptool_OBJECTS) +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_sdptool_DEPENDENCIES = \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ $(am__DEPENDENCIES_1) +am__tools_seq2bseq_SOURCES_DIST = tools/seq2bseq.c +@TOOLS_TRUE@am_tools_seq2bseq_OBJECTS = tools/seq2bseq.$(OBJEXT) +tools_seq2bseq_OBJECTS = $(am_tools_seq2bseq_OBJECTS) +tools_seq2bseq_LDADD = $(LDADD) +am__tools_smp_tester_SOURCES_DIST = tools/smp-tester.c monitor/bt.h \ + emulator/hciemu.h emulator/hciemu.c emulator/btdev.h \ + emulator/btdev.c emulator/bthost.h emulator/bthost.c \ + emulator/smp.c +@TESTING_TRUE@am_tools_smp_tester_OBJECTS = \ +@TESTING_TRUE@ tools/smp-tester.$(OBJEXT) \ +@TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ +@TESTING_TRUE@ emulator/btdev.$(OBJEXT) \ +@TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) +tools_smp_tester_OBJECTS = $(am_tools_smp_tester_OBJECTS) +@TESTING_TRUE@tools_smp_tester_DEPENDENCIES = \ +@TESTING_TRUE@ lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) +tools_test_runner_SOURCES = tools/test-runner.c +tools_test_runner_OBJECTS = tools/test-runner.$(OBJEXT) +tools_test_runner_LDADD = $(LDADD) +am__tools_userchan_tester_SOURCES_DIST = 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 +@TESTING_TRUE@am_tools_userchan_tester_OBJECTS = \ +@TESTING_TRUE@ tools/userchan-tester.$(OBJEXT) \ +@TESTING_TRUE@ emulator/hciemu.$(OBJEXT) \ +@TESTING_TRUE@ emulator/btdev.$(OBJEXT) \ +@TESTING_TRUE@ emulator/bthost.$(OBJEXT) emulator/smp.$(OBJEXT) +tools_userchan_tester_OBJECTS = $(am_tools_userchan_tester_OBJECTS) +@TESTING_TRUE@tools_userchan_tester_DEPENDENCIES = \ +@TESTING_TRUE@ lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(am__DEPENDENCIES_1) +am_unit_test_avctp_OBJECTS = unit/test-avctp.$(OBJEXT) \ + src/log.$(OBJEXT) android/avctp.$(OBJEXT) +unit_test_avctp_OBJECTS = $(am_unit_test_avctp_OBJECTS) +unit_test_avctp_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am_unit_test_avdtp_OBJECTS = unit/test-avdtp.$(OBJEXT) \ + src/log.$(OBJEXT) android/avdtp.$(OBJEXT) +unit_test_avdtp_OBJECTS = $(am_unit_test_avdtp_OBJECTS) +unit_test_avdtp_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am_unit_test_avrcp_OBJECTS = unit/test-avrcp.$(OBJEXT) \ + src/log.$(OBJEXT) android/avctp.$(OBJEXT) \ + android/avrcp-lib.$(OBJEXT) +unit_test_avrcp_OBJECTS = $(am_unit_test_avrcp_OBJECTS) +unit_test_avrcp_DEPENDENCIES = lib/libbluetooth-internal.la \ + src/libshared-glib.la $(am__DEPENDENCIES_1) +am_unit_test_crc_OBJECTS = unit/test-crc.$(OBJEXT) \ + monitor/crc.$(OBJEXT) +unit_test_crc_OBJECTS = $(am_unit_test_crc_OBJECTS) +unit_test_crc_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am_unit_test_crypto_OBJECTS = unit/test-crypto.$(OBJEXT) +unit_test_crypto_OBJECTS = $(am_unit_test_crypto_OBJECTS) +unit_test_crypto_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am_unit_test_ecc_OBJECTS = unit/test-ecc.$(OBJEXT) +unit_test_ecc_OBJECTS = $(am_unit_test_ecc_OBJECTS) +unit_test_ecc_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am_unit_test_eir_OBJECTS = unit/test-eir.$(OBJEXT) src/eir.$(OBJEXT) \ + src/uuid-helper.$(OBJEXT) +unit_test_eir_OBJECTS = $(am_unit_test_eir_OBJECTS) +unit_test_eir_DEPENDENCIES = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) +am_unit_test_gatt_OBJECTS = unit/test-gatt.$(OBJEXT) +unit_test_gatt_OBJECTS = $(am_unit_test_gatt_OBJECTS) +unit_test_gatt_DEPENDENCIES = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) +am_unit_test_gattrib_OBJECTS = unit/test-gattrib.$(OBJEXT) \ + attrib/gattrib.$(OBJEXT) $(am__objects_26) src/log.$(OBJEXT) +unit_test_gattrib_OBJECTS = $(am_unit_test_gattrib_OBJECTS) +unit_test_gattrib_DEPENDENCIES = lib/libbluetooth-internal.la \ + src/libshared-glib.la $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_unit_test_gdbus_client_OBJECTS = unit/test-gdbus-client.$(OBJEXT) +unit_test_gdbus_client_OBJECTS = $(am_unit_test_gdbus_client_OBJECTS) +unit_test_gdbus_client_DEPENDENCIES = gdbus/libgdbus-internal.la \ + src/libshared-glib.la $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_unit_test_gobex_OBJECTS = $(am__objects_25) unit/util.$(OBJEXT) \ + unit/test-gobex.$(OBJEXT) +unit_test_gobex_OBJECTS = $(am_unit_test_gobex_OBJECTS) +unit_test_gobex_DEPENDENCIES = $(am__DEPENDENCIES_1) +am_unit_test_gobex_apparam_OBJECTS = $(am__objects_25) \ + unit/util.$(OBJEXT) unit/test-gobex-apparam.$(OBJEXT) +unit_test_gobex_apparam_OBJECTS = \ + $(am_unit_test_gobex_apparam_OBJECTS) +unit_test_gobex_apparam_DEPENDENCIES = $(am__DEPENDENCIES_1) +am_unit_test_gobex_header_OBJECTS = $(am__objects_25) \ + unit/util.$(OBJEXT) unit/test-gobex-header.$(OBJEXT) +unit_test_gobex_header_OBJECTS = $(am_unit_test_gobex_header_OBJECTS) +unit_test_gobex_header_DEPENDENCIES = $(am__DEPENDENCIES_1) +am_unit_test_gobex_packet_OBJECTS = $(am__objects_25) \ + unit/util.$(OBJEXT) unit/test-gobex-packet.$(OBJEXT) +unit_test_gobex_packet_OBJECTS = $(am_unit_test_gobex_packet_OBJECTS) +unit_test_gobex_packet_DEPENDENCIES = $(am__DEPENDENCIES_1) +am_unit_test_gobex_transfer_OBJECTS = $(am__objects_25) \ + unit/util.$(OBJEXT) unit/test-gobex-transfer.$(OBJEXT) +unit_test_gobex_transfer_OBJECTS = \ + $(am_unit_test_gobex_transfer_OBJECTS) +unit_test_gobex_transfer_DEPENDENCIES = $(am__DEPENDENCIES_1) +am_unit_test_hfp_OBJECTS = unit/test-hfp.$(OBJEXT) +unit_test_hfp_OBJECTS = $(am_unit_test_hfp_OBJECTS) +unit_test_hfp_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am_unit_test_hog_OBJECTS = unit/test-hog.$(OBJEXT) $(am__objects_26) \ + profiles/input/hog-lib.$(OBJEXT) \ + profiles/scanparam/scpp.$(OBJEXT) \ + profiles/battery/bas.$(OBJEXT) \ + profiles/deviceinfo/dis.$(OBJEXT) src/log.$(OBJEXT) \ + attrib/att.$(OBJEXT) attrib/gatt.$(OBJEXT) \ + attrib/gattrib.$(OBJEXT) +unit_test_hog_OBJECTS = $(am_unit_test_hog_OBJECTS) +unit_test_hog_DEPENDENCIES = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) +am_unit_test_lib_OBJECTS = unit/test-lib.$(OBJEXT) +unit_test_lib_OBJECTS = $(am_unit_test_lib_OBJECTS) +unit_test_lib_DEPENDENCIES = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) +am__unit_test_mesh_crypto_SOURCES_DIST = unit/test-mesh-crypto.c \ + mesh/crypto.h ell/internal ell/ell.h ell/private.h \ + ell/missing.h ell/util.c ell/log.c ell/queue.c ell/hashmap.c \ + ell/random.c ell/signal.c ell/timeout.c ell/io.c ell/idle.c \ + ell/main.c ell/strv.c ell/string.c ell/cipher.c ell/checksum.c \ + ell/utf8.c ell/dbus-private.h ell/dbus.c ell/dbus-message.c \ + ell/dbus-util.c ell/dbus-service.c ell/dbus-client.c \ + ell/dbus-name-cache.c ell/dbus-filter.c ell/gvariant-private.h \ + ell/gvariant-util.c ell/siphash-private.h ell/siphash.c +@EXTERNAL_ELL_FALSE@am__objects_27 = \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-util.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-log.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-queue.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-hashmap.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-random.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-signal.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-timeout.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-io.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-idle.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-main.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-strv.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-string.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-cipher.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-checksum.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-utf8.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-dbus.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-dbus-message.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-dbus-util.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-dbus-service.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-dbus-client.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-dbus-name-cache.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-dbus-filter.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-gvariant-util.$(OBJEXT) \ +@EXTERNAL_ELL_FALSE@ ell/unit_test_mesh_crypto-siphash.$(OBJEXT) +@MESH_TRUE@am_unit_test_mesh_crypto_OBJECTS = \ +@MESH_TRUE@ unit/test_mesh_crypto-test-mesh-crypto.$(OBJEXT) \ +@MESH_TRUE@ $(am__objects_27) +unit_test_mesh_crypto_OBJECTS = $(am_unit_test_mesh_crypto_OBJECTS) +@MESH_TRUE@unit_test_mesh_crypto_DEPENDENCIES = src/libshared-ell.la \ +@MESH_TRUE@ $(am__DEPENDENCIES_2) +am_unit_test_mgmt_OBJECTS = unit/test-mgmt.$(OBJEXT) +unit_test_mgmt_OBJECTS = $(am_unit_test_mgmt_OBJECTS) +unit_test_mgmt_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am__unit_test_midi_SOURCES_DIST = unit/test-midi.c \ + profiles/midi/libmidi.h profiles/midi/libmidi.c +@MIDI_TRUE@am_unit_test_midi_OBJECTS = \ +@MIDI_TRUE@ unit/test_midi-test-midi.$(OBJEXT) \ +@MIDI_TRUE@ profiles/midi/unit_test_midi-libmidi.$(OBJEXT) +unit_test_midi_OBJECTS = $(am_unit_test_midi_OBJECTS) +@MIDI_TRUE@unit_test_midi_DEPENDENCIES = src/libshared-glib.la \ +@MIDI_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +am_unit_test_queue_OBJECTS = unit/test-queue.$(OBJEXT) +unit_test_queue_OBJECTS = $(am_unit_test_queue_OBJECTS) +unit_test_queue_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am_unit_test_ringbuf_OBJECTS = unit/test-ringbuf.$(OBJEXT) +unit_test_ringbuf_OBJECTS = $(am_unit_test_ringbuf_OBJECTS) +unit_test_ringbuf_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am_unit_test_sdp_OBJECTS = unit/test-sdp.$(OBJEXT) \ + src/sdpd-database.$(OBJEXT) src/log.$(OBJEXT) \ + src/sdpd-service.$(OBJEXT) src/sdpd-request.$(OBJEXT) +unit_test_sdp_OBJECTS = $(am_unit_test_sdp_OBJECTS) +unit_test_sdp_DEPENDENCIES = lib/libbluetooth-internal.la \ + src/libshared-glib.la $(am__DEPENDENCIES_1) +am_unit_test_textfile_OBJECTS = unit/test-textfile.$(OBJEXT) \ + src/textfile.$(OBJEXT) +unit_test_textfile_OBJECTS = $(am_unit_test_textfile_OBJECTS) +unit_test_textfile_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am_unit_test_uhid_OBJECTS = unit/test-uhid.$(OBJEXT) +unit_test_uhid_OBJECTS = $(am_unit_test_uhid_OBJECTS) +unit_test_uhid_DEPENDENCIES = src/libshared-glib.la \ + $(am__DEPENDENCIES_1) +am_unit_test_uuid_OBJECTS = unit/test-uuid.$(OBJEXT) +unit_test_uuid_OBJECTS = $(am_unit_test_uuid_OBJECTS) +unit_test_uuid_DEPENDENCIES = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(am__DEPENDENCIES_1) +SCRIPTS = $(test_SCRIPTS) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = android/$(DEPDIR)/a2dp-sink.Po \ + android/$(DEPDIR)/a2dp.Po \ + android/$(DEPDIR)/android_tester-tester-a2dp.Po \ + android/$(DEPDIR)/android_tester-tester-avrcp.Po \ + android/$(DEPDIR)/android_tester-tester-bluetooth.Po \ + android/$(DEPDIR)/android_tester-tester-gatt.Po \ + android/$(DEPDIR)/android_tester-tester-hdp.Po \ + android/$(DEPDIR)/android_tester-tester-hidhost.Po \ + android/$(DEPDIR)/android_tester-tester-main.Po \ + android/$(DEPDIR)/android_tester-tester-map-client.Po \ + android/$(DEPDIR)/android_tester-tester-pan.Po \ + android/$(DEPDIR)/android_tester-tester-socket.Po \ + android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-aptx.Plo \ + android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-sbc.Plo \ + android/$(DEPDIR)/audio_a2dp_default_la-hal-audio.Plo \ + android/$(DEPDIR)/audio_sco_default_la-hal-sco.Plo \ + android/$(DEPDIR)/avctp.Po android/$(DEPDIR)/avdtp.Po \ + android/$(DEPDIR)/avdtptest-avdtp.Po \ + android/$(DEPDIR)/avdtptest-avdtptest.Po \ + android/$(DEPDIR)/avrcp-lib.Po android/$(DEPDIR)/avrcp.Po \ + android/$(DEPDIR)/bluetooth.Po \ + android/$(DEPDIR)/bluetooth_default_la-hal-a2dp-sink.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-a2dp.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-avrcp-ctrl.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-avrcp.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-bluetooth.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-gatt.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-handsfree-client.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-handsfree.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-health.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-hidhost.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-ipc.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-map-client.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-pan.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-socket.Plo \ + android/$(DEPDIR)/bluetooth_default_la-hal-utils.Plo \ + android/$(DEPDIR)/bluetoothd-snoop.Po \ + android/$(DEPDIR)/gatt.Po \ + android/$(DEPDIR)/haltest-hal-utils.Po \ + android/$(DEPDIR)/handsfree-client.Po \ + android/$(DEPDIR)/handsfree.Po android/$(DEPDIR)/health.Po \ + android/$(DEPDIR)/hidhost.Po android/$(DEPDIR)/ipc.Po \ + android/$(DEPDIR)/ipc_tester-hal-utils.Po \ + android/$(DEPDIR)/ipc_tester-ipc-tester.Po \ + android/$(DEPDIR)/main.Po android/$(DEPDIR)/map-client.Po \ + android/$(DEPDIR)/pan.Po android/$(DEPDIR)/sco.Po \ + android/$(DEPDIR)/socket.Po \ + android/$(DEPDIR)/system-emulator.Po \ + android/$(DEPDIR)/test-ipc.Po \ + android/audio_utils/$(DEPDIR)/audio_sco_default_la-resampler.Plo \ + android/client/$(DEPDIR)/haltest-haltest.Po \ + android/client/$(DEPDIR)/haltest-history.Po \ + android/client/$(DEPDIR)/haltest-if-audio.Po \ + android/client/$(DEPDIR)/haltest-if-av-sink.Po \ + android/client/$(DEPDIR)/haltest-if-av.Po \ + android/client/$(DEPDIR)/haltest-if-bt.Po \ + android/client/$(DEPDIR)/haltest-if-gatt.Po \ + android/client/$(DEPDIR)/haltest-if-hf-client.Po \ + android/client/$(DEPDIR)/haltest-if-hf.Po \ + android/client/$(DEPDIR)/haltest-if-hh.Po \ + android/client/$(DEPDIR)/haltest-if-hl.Po \ + android/client/$(DEPDIR)/haltest-if-mce.Po \ + android/client/$(DEPDIR)/haltest-if-pan.Po \ + android/client/$(DEPDIR)/haltest-if-rc-ctrl.Po \ + android/client/$(DEPDIR)/haltest-if-rc.Po \ + android/client/$(DEPDIR)/haltest-if-sco.Po \ + android/client/$(DEPDIR)/haltest-if-sock.Po \ + android/client/$(DEPDIR)/haltest-pollhandler.Po \ + android/client/$(DEPDIR)/haltest-tabcompletion.Po \ + android/client/$(DEPDIR)/haltest-terminal.Po \ + android/hardware/$(DEPDIR)/android_tester-hardware.Po \ + android/hardware/$(DEPDIR)/haltest-hardware.Po \ + attrib/$(DEPDIR)/att.Po attrib/$(DEPDIR)/bluetoothd-att.Po \ + attrib/$(DEPDIR)/bluetoothd-gatt-service.Po \ + attrib/$(DEPDIR)/bluetoothd-gatt.Po \ + attrib/$(DEPDIR)/bluetoothd-gattrib.Po \ + attrib/$(DEPDIR)/gatt.Po attrib/$(DEPDIR)/gattrib.Po \ + attrib/$(DEPDIR)/gatttool.Po attrib/$(DEPDIR)/interactive.Po \ + attrib/$(DEPDIR)/utils.Po \ + btio/$(DEPDIR)/android_avdtptest-btio.Po \ + btio/$(DEPDIR)/bluetoothd-btio.Po btio/$(DEPDIR)/btio.Po \ + btio/$(DEPDIR)/obexd-btio.Po client/$(DEPDIR)/advertising.Po \ + client/$(DEPDIR)/agent.Po client/$(DEPDIR)/display.Po \ + client/$(DEPDIR)/gatt.Po client/$(DEPDIR)/main.Po \ + ell/$(DEPDIR)/checksum.Plo ell/$(DEPDIR)/cipher.Plo \ + ell/$(DEPDIR)/dbus-client.Plo ell/$(DEPDIR)/dbus-filter.Plo \ + ell/$(DEPDIR)/dbus-message.Plo \ + ell/$(DEPDIR)/dbus-name-cache.Plo \ + ell/$(DEPDIR)/dbus-service.Plo ell/$(DEPDIR)/dbus-util.Plo \ + ell/$(DEPDIR)/dbus.Plo ell/$(DEPDIR)/gvariant-util.Plo \ + ell/$(DEPDIR)/hashmap.Plo ell/$(DEPDIR)/idle.Plo \ + ell/$(DEPDIR)/io.Plo ell/$(DEPDIR)/log.Plo \ + ell/$(DEPDIR)/main.Plo ell/$(DEPDIR)/queue.Plo \ + ell/$(DEPDIR)/random.Plo ell/$(DEPDIR)/signal.Plo \ + ell/$(DEPDIR)/siphash.Plo ell/$(DEPDIR)/string.Plo \ + ell/$(DEPDIR)/strv.Plo ell/$(DEPDIR)/timeout.Plo \ + ell/$(DEPDIR)/unit_test_mesh_crypto-checksum.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-cipher.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-client.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-filter.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-message.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-name-cache.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-service.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-util.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-dbus.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-gvariant-util.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-hashmap.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-idle.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-io.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-log.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-main.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-queue.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-random.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-signal.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-siphash.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-string.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-strv.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-timeout.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-utf8.Po \ + ell/$(DEPDIR)/unit_test_mesh_crypto-util.Po \ + ell/$(DEPDIR)/utf8.Plo ell/$(DEPDIR)/util.Plo \ + emulator/$(DEPDIR)/amp.Po \ + emulator/$(DEPDIR)/android_android_tester-btdev.Po \ + emulator/$(DEPDIR)/android_android_tester-bthost.Po \ + emulator/$(DEPDIR)/android_android_tester-hciemu.Po \ + emulator/$(DEPDIR)/android_android_tester-smp.Po \ + emulator/$(DEPDIR)/android_ipc_tester-btdev.Po \ + emulator/$(DEPDIR)/android_ipc_tester-bthost.Po \ + emulator/$(DEPDIR)/android_ipc_tester-hciemu.Po \ + emulator/$(DEPDIR)/android_ipc_tester-smp.Po \ + emulator/$(DEPDIR)/b1ee.Po emulator/$(DEPDIR)/btdev.Po \ + emulator/$(DEPDIR)/bthost.Po emulator/$(DEPDIR)/hciemu.Po \ + emulator/$(DEPDIR)/hfp.Po emulator/$(DEPDIR)/le.Po \ + emulator/$(DEPDIR)/main.Po emulator/$(DEPDIR)/phy.Po \ + emulator/$(DEPDIR)/serial.Po emulator/$(DEPDIR)/server.Po \ + emulator/$(DEPDIR)/smp.Po emulator/$(DEPDIR)/vhci.Po \ + gdbus/$(DEPDIR)/client.Plo gdbus/$(DEPDIR)/mainloop.Plo \ + gdbus/$(DEPDIR)/object.Plo gdbus/$(DEPDIR)/polkit.Plo \ + gdbus/$(DEPDIR)/watch.Plo gobex/$(DEPDIR)/gobex-apparam.Po \ + gobex/$(DEPDIR)/gobex-defs.Po gobex/$(DEPDIR)/gobex-header.Po \ + gobex/$(DEPDIR)/gobex-packet.Po \ + gobex/$(DEPDIR)/gobex-transfer.Po gobex/$(DEPDIR)/gobex.Po \ + gobex/$(DEPDIR)/obexd-gobex-apparam.Po \ + gobex/$(DEPDIR)/obexd-gobex-defs.Po \ + gobex/$(DEPDIR)/obexd-gobex-header.Po \ + gobex/$(DEPDIR)/obexd-gobex-packet.Po \ + gobex/$(DEPDIR)/obexd-gobex-transfer.Po \ + gobex/$(DEPDIR)/obexd-gobex.Po lib/$(DEPDIR)/bluetooth.Plo \ + lib/$(DEPDIR)/hci.Plo lib/$(DEPDIR)/sdp.Plo \ + lib/$(DEPDIR)/uuid.Plo mesh/$(DEPDIR)/agent.Po \ + mesh/$(DEPDIR)/appkey.Po mesh/$(DEPDIR)/cfgmod-server.Po \ + mesh/$(DEPDIR)/crypto.Po mesh/$(DEPDIR)/dbus.Po \ + mesh/$(DEPDIR)/friend.Po mesh/$(DEPDIR)/keyring.Po \ + mesh/$(DEPDIR)/main.Po mesh/$(DEPDIR)/manager.Po \ + mesh/$(DEPDIR)/mesh-config-json.Po \ + mesh/$(DEPDIR)/mesh-io-generic.Po mesh/$(DEPDIR)/mesh-io.Po \ + mesh/$(DEPDIR)/mesh-mgmt.Po mesh/$(DEPDIR)/mesh.Po \ + mesh/$(DEPDIR)/model.Po mesh/$(DEPDIR)/net-keys.Po \ + mesh/$(DEPDIR)/net.Po mesh/$(DEPDIR)/node.Po \ + mesh/$(DEPDIR)/pb-adv.Po mesh/$(DEPDIR)/prov-acceptor.Po \ + mesh/$(DEPDIR)/prov-initiator.Po mesh/$(DEPDIR)/util.Po \ + monitor/$(DEPDIR)/a2dp.Po monitor/$(DEPDIR)/analyze.Po \ + monitor/$(DEPDIR)/avctp.Po monitor/$(DEPDIR)/avdtp.Po \ + monitor/$(DEPDIR)/bnep.Po monitor/$(DEPDIR)/broadcom.Po \ + monitor/$(DEPDIR)/control.Po monitor/$(DEPDIR)/crc.Po \ + monitor/$(DEPDIR)/display.Po monitor/$(DEPDIR)/ellisys.Po \ + monitor/$(DEPDIR)/hcidump.Po monitor/$(DEPDIR)/hwdb.Po \ + monitor/$(DEPDIR)/intel.Po monitor/$(DEPDIR)/jlink.Po \ + monitor/$(DEPDIR)/keys.Po monitor/$(DEPDIR)/l2cap.Po \ + monitor/$(DEPDIR)/ll.Po monitor/$(DEPDIR)/lmp.Po \ + monitor/$(DEPDIR)/main.Po monitor/$(DEPDIR)/packet.Po \ + monitor/$(DEPDIR)/rfcomm.Po monitor/$(DEPDIR)/sdp.Po \ + monitor/$(DEPDIR)/vendor.Po \ + obexd/client/$(DEPDIR)/obexd-bluetooth.Po \ + obexd/client/$(DEPDIR)/obexd-driver.Po \ + obexd/client/$(DEPDIR)/obexd-ftp.Po \ + obexd/client/$(DEPDIR)/obexd-manager.Po \ + obexd/client/$(DEPDIR)/obexd-map-event.Po \ + obexd/client/$(DEPDIR)/obexd-map.Po \ + obexd/client/$(DEPDIR)/obexd-mns.Po \ + obexd/client/$(DEPDIR)/obexd-opp.Po \ + obexd/client/$(DEPDIR)/obexd-pbap.Po \ + obexd/client/$(DEPDIR)/obexd-session.Po \ + obexd/client/$(DEPDIR)/obexd-sync.Po \ + obexd/client/$(DEPDIR)/obexd-transfer.Po \ + obexd/client/$(DEPDIR)/obexd-transport.Po \ + obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po \ + obexd/plugins/$(DEPDIR)/obexd-filesystem.Po \ + obexd/plugins/$(DEPDIR)/obexd-ftp.Po \ + obexd/plugins/$(DEPDIR)/obexd-irmc.Po \ + obexd/plugins/$(DEPDIR)/obexd-mas.Po \ + obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po \ + obexd/plugins/$(DEPDIR)/obexd-opp.Po \ + obexd/plugins/$(DEPDIR)/obexd-pbap.Po \ + obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po \ + obexd/plugins/$(DEPDIR)/obexd-phonebook-dummy.Po \ + obexd/plugins/$(DEPDIR)/obexd-vcard.Po \ + obexd/src/$(DEPDIR)/obexd-log.Po \ + obexd/src/$(DEPDIR)/obexd-main.Po \ + obexd/src/$(DEPDIR)/obexd-manager.Po \ + obexd/src/$(DEPDIR)/obexd-mimetype.Po \ + obexd/src/$(DEPDIR)/obexd-obex.Po \ + obexd/src/$(DEPDIR)/obexd-plugin.Po \ + obexd/src/$(DEPDIR)/obexd-server.Po \ + obexd/src/$(DEPDIR)/obexd-service.Po \ + obexd/src/$(DEPDIR)/obexd-transport.Po \ + peripheral/$(DEPDIR)/attach.Po peripheral/$(DEPDIR)/efivars.Po \ + peripheral/$(DEPDIR)/gap.Po peripheral/$(DEPDIR)/gatt.Po \ + peripheral/$(DEPDIR)/log.Po peripheral/$(DEPDIR)/main.Po \ + plugins/$(DEPDIR)/bluetoothd-autopair.Po \ + plugins/$(DEPDIR)/bluetoothd-hostname.Po \ + plugins/$(DEPDIR)/bluetoothd-neard.Po \ + plugins/$(DEPDIR)/bluetoothd-policy.Po \ + plugins/$(DEPDIR)/bluetoothd-wiimote.Po \ + plugins/$(DEPDIR)/external_dummy_la-external-dummy.Plo \ + plugins/$(DEPDIR)/sixaxis_la-sixaxis.Plo \ + profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-control.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-media.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-player.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-sink.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-source.Po \ + profiles/audio/$(DEPDIR)/bluetoothd-transport.Po \ + profiles/battery/$(DEPDIR)/bas.Po \ + profiles/battery/$(DEPDIR)/bluetoothd-bas.Po \ + profiles/battery/$(DEPDIR)/bluetoothd-battery.Po \ + profiles/cups/$(DEPDIR)/hcrp.Po \ + profiles/cups/$(DEPDIR)/main.Po profiles/cups/$(DEPDIR)/sdp.Po \ + profiles/cups/$(DEPDIR)/spp.Po \ + profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po \ + profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po \ + profiles/deviceinfo/$(DEPDIR)/dis.Po \ + profiles/gap/$(DEPDIR)/bluetoothd-gas.Po \ + profiles/health/$(DEPDIR)/bluetoothd-hdp.Po \ + profiles/health/$(DEPDIR)/bluetoothd-hdp_main.Po \ + profiles/health/$(DEPDIR)/bluetoothd-hdp_manager.Po \ + profiles/health/$(DEPDIR)/bluetoothd-hdp_util.Po \ + profiles/health/$(DEPDIR)/bluetoothd-mcap.Po \ + profiles/health/$(DEPDIR)/mcap.Po \ + profiles/iap/$(DEPDIR)/main.Po \ + profiles/input/$(DEPDIR)/bluetoothd-device.Po \ + profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po \ + profiles/input/$(DEPDIR)/bluetoothd-hog.Po \ + profiles/input/$(DEPDIR)/bluetoothd-manager.Po \ + profiles/input/$(DEPDIR)/bluetoothd-server.Po \ + profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po \ + profiles/input/$(DEPDIR)/hog-lib.Po \ + profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po \ + profiles/midi/$(DEPDIR)/bluetoothd-midi.Po \ + profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po \ + profiles/network/$(DEPDIR)/bluetoothd-bnep.Po \ + profiles/network/$(DEPDIR)/bluetoothd-connection.Po \ + profiles/network/$(DEPDIR)/bluetoothd-manager.Po \ + profiles/network/$(DEPDIR)/bluetoothd-server.Po \ + profiles/network/$(DEPDIR)/bnep.Po \ + profiles/sap/$(DEPDIR)/bluetoothd-main.Po \ + profiles/sap/$(DEPDIR)/bluetoothd-manager.Po \ + profiles/sap/$(DEPDIR)/bluetoothd-sap-dummy.Po \ + profiles/sap/$(DEPDIR)/bluetoothd-server.Po \ + profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po \ + profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po \ + profiles/scanparam/$(DEPDIR)/scpp.Po \ + src/$(DEPDIR)/android_avdtptest-log.Po \ + src/$(DEPDIR)/bluetoothd-adapter.Po \ + src/$(DEPDIR)/bluetoothd-advertising.Po \ + src/$(DEPDIR)/bluetoothd-agent.Po \ + src/$(DEPDIR)/bluetoothd-attrib-server.Po \ + src/$(DEPDIR)/bluetoothd-backtrace.Po \ + src/$(DEPDIR)/bluetoothd-dbus-common.Po \ + src/$(DEPDIR)/bluetoothd-device.Po \ + src/$(DEPDIR)/bluetoothd-eir.Po \ + src/$(DEPDIR)/bluetoothd-error.Po \ + src/$(DEPDIR)/bluetoothd-gatt-client.Po \ + src/$(DEPDIR)/bluetoothd-gatt-database.Po \ + src/$(DEPDIR)/bluetoothd-log.Po \ + src/$(DEPDIR)/bluetoothd-main.Po \ + src/$(DEPDIR)/bluetoothd-plugin.Po \ + src/$(DEPDIR)/bluetoothd-profile.Po \ + src/$(DEPDIR)/bluetoothd-rfkill.Po \ + src/$(DEPDIR)/bluetoothd-sdp-client.Po \ + src/$(DEPDIR)/bluetoothd-sdp-xml.Po \ + src/$(DEPDIR)/bluetoothd-sdpd-database.Po \ + src/$(DEPDIR)/bluetoothd-sdpd-request.Po \ + src/$(DEPDIR)/bluetoothd-sdpd-server.Po \ + src/$(DEPDIR)/bluetoothd-sdpd-service.Po \ + src/$(DEPDIR)/bluetoothd-service.Po \ + src/$(DEPDIR)/bluetoothd-storage.Po \ + src/$(DEPDIR)/bluetoothd-textfile.Po \ + src/$(DEPDIR)/bluetoothd-uuid-helper.Po src/$(DEPDIR)/eir.Po \ + src/$(DEPDIR)/log.Po src/$(DEPDIR)/oui.Po \ + src/$(DEPDIR)/sdp-client.Po src/$(DEPDIR)/sdp-xml.Po \ + src/$(DEPDIR)/sdpd-database.Po src/$(DEPDIR)/sdpd-request.Po \ + src/$(DEPDIR)/sdpd-server.Po src/$(DEPDIR)/sdpd-service.Po \ + src/$(DEPDIR)/textfile.Po src/$(DEPDIR)/uuid-helper.Po \ + src/shared/$(DEPDIR)/ad.Plo \ + src/shared/$(DEPDIR)/android_avdtptest-log.Po \ + src/shared/$(DEPDIR)/android_avdtptest-queue.Po \ + src/shared/$(DEPDIR)/android_avdtptest-util.Po \ + src/shared/$(DEPDIR)/att.Plo src/shared/$(DEPDIR)/btp.Po \ + src/shared/$(DEPDIR)/btsnoop.Plo \ + src/shared/$(DEPDIR)/crypto.Plo src/shared/$(DEPDIR)/ecc.Plo \ + src/shared/$(DEPDIR)/gap.Plo \ + src/shared/$(DEPDIR)/gatt-client.Plo \ + src/shared/$(DEPDIR)/gatt-db.Plo \ + src/shared/$(DEPDIR)/gatt-helpers.Plo \ + src/shared/$(DEPDIR)/gatt-server.Plo \ + src/shared/$(DEPDIR)/hci-crypto.Plo \ + src/shared/$(DEPDIR)/hci.Plo src/shared/$(DEPDIR)/hfp.Plo \ + src/shared/$(DEPDIR)/io-ell.Plo \ + src/shared/$(DEPDIR)/io-glib.Plo \ + src/shared/$(DEPDIR)/io-mainloop.Plo \ + src/shared/$(DEPDIR)/log.Plo \ + src/shared/$(DEPDIR)/mainloop-ell.Plo \ + src/shared/$(DEPDIR)/mainloop-glib.Plo \ + src/shared/$(DEPDIR)/mainloop-notify.Plo \ + src/shared/$(DEPDIR)/mainloop.Plo \ + src/shared/$(DEPDIR)/mgmt.Plo src/shared/$(DEPDIR)/pcap.Plo \ + src/shared/$(DEPDIR)/queue.Plo \ + src/shared/$(DEPDIR)/ringbuf.Plo \ + src/shared/$(DEPDIR)/shell.Plo src/shared/$(DEPDIR)/tester.Plo \ + src/shared/$(DEPDIR)/timeout-ell.Plo \ + src/shared/$(DEPDIR)/timeout-glib.Plo \ + src/shared/$(DEPDIR)/timeout-mainloop.Plo \ + src/shared/$(DEPDIR)/uhid.Plo src/shared/$(DEPDIR)/util.Plo \ + tools/$(DEPDIR)/3dsp.Po tools/$(DEPDIR)/advtest.Po \ + tools/$(DEPDIR)/amptest.Po tools/$(DEPDIR)/avinfo.Po \ + tools/$(DEPDIR)/avtest.Po tools/$(DEPDIR)/bccmd.Po \ + tools/$(DEPDIR)/bcmfw.Po tools/$(DEPDIR)/bdaddr.Po \ + tools/$(DEPDIR)/bluemoon.Po \ + tools/$(DEPDIR)/bluetooth-player.Po \ + tools/$(DEPDIR)/bnep-tester.Po tools/$(DEPDIR)/bneptest.Po \ + tools/$(DEPDIR)/btattach.Po tools/$(DEPDIR)/btconfig.Po \ + tools/$(DEPDIR)/btgatt-client.Po \ + tools/$(DEPDIR)/btgatt-server.Po tools/$(DEPDIR)/btinfo.Po \ + tools/$(DEPDIR)/btiotest.Po tools/$(DEPDIR)/btmgmt.Po \ + tools/$(DEPDIR)/btmon-logger.Po tools/$(DEPDIR)/btpclient.Po \ + tools/$(DEPDIR)/btproxy.Po tools/$(DEPDIR)/btsnoop.Po \ + tools/$(DEPDIR)/check-selftest.Po tools/$(DEPDIR)/ciptool.Po \ + tools/$(DEPDIR)/cltest.Po tools/$(DEPDIR)/create-image.Po \ + tools/$(DEPDIR)/csr.Po tools/$(DEPDIR)/csr_3wire.Po \ + tools/$(DEPDIR)/csr_bcsp.Po tools/$(DEPDIR)/csr_h4.Po \ + tools/$(DEPDIR)/csr_hci.Po tools/$(DEPDIR)/csr_usb.Po \ + tools/$(DEPDIR)/eddystone.Po tools/$(DEPDIR)/gap-tester.Po \ + tools/$(DEPDIR)/gatt-service.Po tools/$(DEPDIR)/hci-tester.Po \ + tools/$(DEPDIR)/hciattach.Po \ + tools/$(DEPDIR)/hciattach_ath3k.Po \ + tools/$(DEPDIR)/hciattach_bcm43xx.Po \ + tools/$(DEPDIR)/hciattach_intel.Po \ + tools/$(DEPDIR)/hciattach_qualcomm.Po \ + tools/$(DEPDIR)/hciattach_st.Po \ + tools/$(DEPDIR)/hciattach_ti.Po \ + tools/$(DEPDIR)/hciattach_tialt.Po \ + tools/$(DEPDIR)/hciconfig.Po tools/$(DEPDIR)/hcidump.Po \ + tools/$(DEPDIR)/hcieventmask.Po \ + tools/$(DEPDIR)/hcisecfilter.Po tools/$(DEPDIR)/hcitool.Po \ + tools/$(DEPDIR)/hex2hcd.Po tools/$(DEPDIR)/hid2hci.Po \ + tools/$(DEPDIR)/hwdb.Po tools/$(DEPDIR)/ibeacon.Po \ + tools/$(DEPDIR)/l2cap-tester.Po tools/$(DEPDIR)/l2ping.Po \ + tools/$(DEPDIR)/l2test.Po tools/$(DEPDIR)/mcaptest.Po \ + tools/$(DEPDIR)/meshctl.Po tools/$(DEPDIR)/mgmt-tester.Po \ + tools/$(DEPDIR)/mpris-proxy.Po tools/$(DEPDIR)/nokfw.Po \ + tools/$(DEPDIR)/obex-client-tool.Po \ + tools/$(DEPDIR)/obex-server-tool.Po tools/$(DEPDIR)/obexctl.Po \ + tools/$(DEPDIR)/oobtest.Po tools/$(DEPDIR)/rctest.Po \ + tools/$(DEPDIR)/rfcomm-tester.Po tools/$(DEPDIR)/rfcomm.Po \ + tools/$(DEPDIR)/rtlfw.Po tools/$(DEPDIR)/sco-tester.Po \ + tools/$(DEPDIR)/scotest.Po tools/$(DEPDIR)/sdptool.Po \ + tools/$(DEPDIR)/seq2bseq.Po tools/$(DEPDIR)/smp-tester.Po \ + tools/$(DEPDIR)/test-runner.Po tools/$(DEPDIR)/ubcsp.Po \ + tools/$(DEPDIR)/userchan-tester.Po \ + tools/mesh/$(DEPDIR)/agent.Po \ + tools/mesh/$(DEPDIR)/config-client.Po \ + tools/mesh/$(DEPDIR)/config-server.Po \ + tools/mesh/$(DEPDIR)/crypto.Po tools/mesh/$(DEPDIR)/gatt.Po \ + tools/mesh/$(DEPDIR)/net.Po tools/mesh/$(DEPDIR)/node.Po \ + tools/mesh/$(DEPDIR)/onoff-model.Po \ + tools/mesh/$(DEPDIR)/prov-db.Po tools/mesh/$(DEPDIR)/prov.Po \ + tools/mesh/$(DEPDIR)/util.Po tools/parser/$(DEPDIR)/amp.Po \ + tools/parser/$(DEPDIR)/att.Po tools/parser/$(DEPDIR)/avctp.Po \ + tools/parser/$(DEPDIR)/avdtp.Po \ + tools/parser/$(DEPDIR)/avrcp.Po tools/parser/$(DEPDIR)/bnep.Po \ + tools/parser/$(DEPDIR)/bpa.Po tools/parser/$(DEPDIR)/capi.Po \ + tools/parser/$(DEPDIR)/cmtp.Po tools/parser/$(DEPDIR)/csr.Po \ + tools/parser/$(DEPDIR)/ericsson.Po \ + tools/parser/$(DEPDIR)/hci.Po tools/parser/$(DEPDIR)/hcrp.Po \ + tools/parser/$(DEPDIR)/hidp.Po tools/parser/$(DEPDIR)/l2cap.Po \ + tools/parser/$(DEPDIR)/lmp.Po tools/parser/$(DEPDIR)/obex.Po \ + tools/parser/$(DEPDIR)/parser.Po tools/parser/$(DEPDIR)/ppp.Po \ + tools/parser/$(DEPDIR)/rfcomm.Po tools/parser/$(DEPDIR)/sap.Po \ + tools/parser/$(DEPDIR)/sdp.Po tools/parser/$(DEPDIR)/smp.Po \ + tools/parser/$(DEPDIR)/tcpip.Po unit/$(DEPDIR)/test-avctp.Po \ + unit/$(DEPDIR)/test-avdtp.Po unit/$(DEPDIR)/test-avrcp.Po \ + unit/$(DEPDIR)/test-crc.Po unit/$(DEPDIR)/test-crypto.Po \ + unit/$(DEPDIR)/test-ecc.Po unit/$(DEPDIR)/test-eir.Po \ + unit/$(DEPDIR)/test-gatt.Po unit/$(DEPDIR)/test-gattrib.Po \ + unit/$(DEPDIR)/test-gdbus-client.Po \ + unit/$(DEPDIR)/test-gobex-apparam.Po \ + unit/$(DEPDIR)/test-gobex-header.Po \ + unit/$(DEPDIR)/test-gobex-packet.Po \ + unit/$(DEPDIR)/test-gobex-transfer.Po \ + unit/$(DEPDIR)/test-gobex.Po unit/$(DEPDIR)/test-hfp.Po \ + unit/$(DEPDIR)/test-hog.Po unit/$(DEPDIR)/test-lib.Po \ + unit/$(DEPDIR)/test-mgmt.Po unit/$(DEPDIR)/test-queue.Po \ + unit/$(DEPDIR)/test-ringbuf.Po unit/$(DEPDIR)/test-sdp.Po \ + unit/$(DEPDIR)/test-textfile.Po unit/$(DEPDIR)/test-uhid.Po \ + unit/$(DEPDIR)/test-uuid.Po \ + unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po \ + unit/$(DEPDIR)/test_midi-test-midi.Po unit/$(DEPDIR)/util.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(android_audio_a2dp_default_la_SOURCES) \ + $(android_audio_sco_default_la_SOURCES) \ + $(android_bluetooth_default_la_SOURCES) \ + $(ell_libell_internal_la_SOURCES) \ + $(gdbus_libgdbus_internal_la_SOURCES) \ + $(lib_libbluetooth_internal_la_SOURCES) \ + $(lib_libbluetooth_la_SOURCES) \ + $(plugins_external_dummy_la_SOURCES) \ + $(plugins_sixaxis_la_SOURCES) $(src_libshared_ell_la_SOURCES) \ + $(src_libshared_glib_la_SOURCES) \ + $(src_libshared_mainloop_la_SOURCES) \ + $(android_android_tester_SOURCES) $(android_avdtptest_SOURCES) \ + $(android_bluetoothd_SOURCES) \ + $(android_bluetoothd_snoop_SOURCES) $(android_haltest_SOURCES) \ + $(android_ipc_tester_SOURCES) \ + $(android_system_emulator_SOURCES) $(android_test_ipc_SOURCES) \ + $(attrib_gatttool_SOURCES) $(client_bluetoothctl_SOURCES) \ + $(emulator_b1ee_SOURCES) $(emulator_btvirt_SOURCES) \ + $(emulator_hfp_SOURCES) $(mesh_bluetooth_meshd_SOURCES) \ + $(monitor_btmon_SOURCES) $(obexd_src_obexd_SOURCES) \ + $(nodist_obexd_src_obexd_SOURCES) \ + $(peripheral_btsensor_SOURCES) \ + $(profiles_cups_bluetooth_SOURCES) \ + $(profiles_iap_iapd_SOURCES) $(src_bluetoothd_SOURCES) \ + $(nodist_src_bluetoothd_SOURCES) $(tools_3dsp_SOURCES) \ + $(tools_advtest_SOURCES) tools/amptest.c tools/avinfo.c \ + tools/avtest.c $(tools_bccmd_SOURCES) tools/bcmfw.c \ + $(tools_bdaddr_SOURCES) $(tools_bluemoon_SOURCES) \ + $(tools_bluetooth_player_SOURCES) $(tools_bnep_tester_SOURCES) \ + $(tools_bneptest_SOURCES) $(tools_btattach_SOURCES) \ + $(tools_btconfig_SOURCES) $(tools_btgatt_client_SOURCES) \ + $(tools_btgatt_server_SOURCES) $(tools_btinfo_SOURCES) \ + $(tools_btiotest_SOURCES) $(tools_btmgmt_SOURCES) \ + $(tools_btmon_logger_SOURCES) $(tools_btpclient_SOURCES) \ + $(tools_btproxy_SOURCES) $(tools_btsnoop_SOURCES) \ + tools/check-selftest.c tools/ciptool.c $(tools_cltest_SOURCES) \ + $(tools_create_image_SOURCES) $(tools_eddystone_SOURCES) \ + $(tools_gap_tester_SOURCES) $(tools_gatt_service_SOURCES) \ + $(tools_hci_tester_SOURCES) $(tools_hciattach_SOURCES) \ + $(tools_hciconfig_SOURCES) $(tools_hcidump_SOURCES) \ + tools/hcieventmask.c tools/hcisecfilter.c \ + $(tools_hcitool_SOURCES) $(tools_hex2hcd_SOURCES) \ + tools/hid2hci.c tools/hwdb.c $(tools_ibeacon_SOURCES) \ + $(tools_l2cap_tester_SOURCES) tools/l2ping.c tools/l2test.c \ + $(tools_mcaptest_SOURCES) $(tools_meshctl_SOURCES) \ + $(tools_mgmt_tester_SOURCES) $(tools_mpris_proxy_SOURCES) \ + $(tools_nokfw_SOURCES) $(tools_obex_client_tool_SOURCES) \ + $(tools_obex_server_tool_SOURCES) $(tools_obexctl_SOURCES) \ + $(tools_oobtest_SOURCES) tools/rctest.c tools/rfcomm.c \ + $(tools_rfcomm_tester_SOURCES) $(tools_rtlfw_SOURCES) \ + $(tools_sco_tester_SOURCES) tools/scotest.c \ + $(tools_sdptool_SOURCES) $(tools_seq2bseq_SOURCES) \ + $(tools_smp_tester_SOURCES) tools/test-runner.c \ + $(tools_userchan_tester_SOURCES) $(unit_test_avctp_SOURCES) \ + $(unit_test_avdtp_SOURCES) $(unit_test_avrcp_SOURCES) \ + $(unit_test_crc_SOURCES) $(unit_test_crypto_SOURCES) \ + $(unit_test_ecc_SOURCES) $(unit_test_eir_SOURCES) \ + $(unit_test_gatt_SOURCES) $(unit_test_gattrib_SOURCES) \ + $(unit_test_gdbus_client_SOURCES) $(unit_test_gobex_SOURCES) \ + $(unit_test_gobex_apparam_SOURCES) \ + $(unit_test_gobex_header_SOURCES) \ + $(unit_test_gobex_packet_SOURCES) \ + $(unit_test_gobex_transfer_SOURCES) $(unit_test_hfp_SOURCES) \ + $(unit_test_hog_SOURCES) $(unit_test_lib_SOURCES) \ + $(unit_test_mesh_crypto_SOURCES) $(unit_test_mgmt_SOURCES) \ + $(unit_test_midi_SOURCES) $(unit_test_queue_SOURCES) \ + $(unit_test_ringbuf_SOURCES) $(unit_test_sdp_SOURCES) \ + $(unit_test_textfile_SOURCES) $(unit_test_uhid_SOURCES) \ + $(unit_test_uuid_SOURCES) +DIST_SOURCES = $(am__android_audio_a2dp_default_la_SOURCES_DIST) \ + $(am__android_audio_sco_default_la_SOURCES_DIST) \ + $(am__android_bluetooth_default_la_SOURCES_DIST) \ + $(am__ell_libell_internal_la_SOURCES_DIST) \ + $(gdbus_libgdbus_internal_la_SOURCES) \ + $(lib_libbluetooth_internal_la_SOURCES) \ + $(am__lib_libbluetooth_la_SOURCES_DIST) \ + $(am__plugins_external_dummy_la_SOURCES_DIST) \ + $(am__plugins_sixaxis_la_SOURCES_DIST) \ + $(am__src_libshared_ell_la_SOURCES_DIST) \ + $(am__src_libshared_glib_la_SOURCES_DIST) \ + $(am__src_libshared_mainloop_la_SOURCES_DIST) \ + $(am__android_android_tester_SOURCES_DIST) \ + $(am__android_avdtptest_SOURCES_DIST) \ + $(am__android_bluetoothd_SOURCES_DIST) \ + $(am__android_bluetoothd_snoop_SOURCES_DIST) \ + $(am__android_haltest_SOURCES_DIST) \ + $(am__android_ipc_tester_SOURCES_DIST) \ + $(am__android_system_emulator_SOURCES_DIST) \ + $(am__android_test_ipc_SOURCES_DIST) \ + $(am__attrib_gatttool_SOURCES_DIST) \ + $(am__client_bluetoothctl_SOURCES_DIST) \ + $(am__emulator_b1ee_SOURCES_DIST) \ + $(am__emulator_btvirt_SOURCES_DIST) \ + $(am__emulator_hfp_SOURCES_DIST) \ + $(am__mesh_bluetooth_meshd_SOURCES_DIST) \ + $(am__monitor_btmon_SOURCES_DIST) \ + $(am__obexd_src_obexd_SOURCES_DIST) \ + $(am__peripheral_btsensor_SOURCES_DIST) \ + $(am__profiles_cups_bluetooth_SOURCES_DIST) \ + $(am__profiles_iap_iapd_SOURCES_DIST) \ + $(am__src_bluetoothd_SOURCES_DIST) \ + $(am__tools_3dsp_SOURCES_DIST) \ + $(am__tools_advtest_SOURCES_DIST) tools/amptest.c \ + tools/avinfo.c tools/avtest.c $(am__tools_bccmd_SOURCES_DIST) \ + tools/bcmfw.c $(am__tools_bdaddr_SOURCES_DIST) \ + $(am__tools_bluemoon_SOURCES_DIST) \ + $(am__tools_bluetooth_player_SOURCES_DIST) \ + $(am__tools_bnep_tester_SOURCES_DIST) \ + $(am__tools_bneptest_SOURCES_DIST) \ + $(am__tools_btattach_SOURCES_DIST) \ + $(am__tools_btconfig_SOURCES_DIST) \ + $(am__tools_btgatt_client_SOURCES_DIST) \ + $(am__tools_btgatt_server_SOURCES_DIST) \ + $(am__tools_btinfo_SOURCES_DIST) \ + $(am__tools_btiotest_SOURCES_DIST) \ + $(am__tools_btmgmt_SOURCES_DIST) \ + $(am__tools_btmon_logger_SOURCES_DIST) \ + $(am__tools_btpclient_SOURCES_DIST) \ + $(am__tools_btproxy_SOURCES_DIST) \ + $(am__tools_btsnoop_SOURCES_DIST) tools/check-selftest.c \ + tools/ciptool.c $(am__tools_cltest_SOURCES_DIST) \ + $(am__tools_create_image_SOURCES_DIST) \ + $(am__tools_eddystone_SOURCES_DIST) \ + $(am__tools_gap_tester_SOURCES_DIST) \ + $(am__tools_gatt_service_SOURCES_DIST) \ + $(am__tools_hci_tester_SOURCES_DIST) \ + $(am__tools_hciattach_SOURCES_DIST) \ + $(am__tools_hciconfig_SOURCES_DIST) \ + $(am__tools_hcidump_SOURCES_DIST) tools/hcieventmask.c \ + tools/hcisecfilter.c $(am__tools_hcitool_SOURCES_DIST) \ + $(am__tools_hex2hcd_SOURCES_DIST) tools/hid2hci.c tools/hwdb.c \ + $(am__tools_ibeacon_SOURCES_DIST) \ + $(am__tools_l2cap_tester_SOURCES_DIST) tools/l2ping.c \ + tools/l2test.c $(am__tools_mcaptest_SOURCES_DIST) \ + $(am__tools_meshctl_SOURCES_DIST) \ + $(am__tools_mgmt_tester_SOURCES_DIST) \ + $(am__tools_mpris_proxy_SOURCES_DIST) \ + $(am__tools_nokfw_SOURCES_DIST) \ + $(am__tools_obex_client_tool_SOURCES_DIST) \ + $(am__tools_obex_server_tool_SOURCES_DIST) \ + $(am__tools_obexctl_SOURCES_DIST) \ + $(am__tools_oobtest_SOURCES_DIST) tools/rctest.c \ + tools/rfcomm.c $(am__tools_rfcomm_tester_SOURCES_DIST) \ + $(am__tools_rtlfw_SOURCES_DIST) \ + $(am__tools_sco_tester_SOURCES_DIST) tools/scotest.c \ + $(am__tools_sdptool_SOURCES_DIST) \ + $(am__tools_seq2bseq_SOURCES_DIST) \ + $(am__tools_smp_tester_SOURCES_DIST) tools/test-runner.c \ + $(am__tools_userchan_tester_SOURCES_DIST) \ + $(unit_test_avctp_SOURCES) $(unit_test_avdtp_SOURCES) \ + $(unit_test_avrcp_SOURCES) $(unit_test_crc_SOURCES) \ + $(unit_test_crypto_SOURCES) $(unit_test_ecc_SOURCES) \ + $(unit_test_eir_SOURCES) $(unit_test_gatt_SOURCES) \ + $(unit_test_gattrib_SOURCES) $(unit_test_gdbus_client_SOURCES) \ + $(unit_test_gobex_SOURCES) $(unit_test_gobex_apparam_SOURCES) \ + $(unit_test_gobex_header_SOURCES) \ + $(unit_test_gobex_packet_SOURCES) \ + $(unit_test_gobex_transfer_SOURCES) $(unit_test_hfp_SOURCES) \ + $(unit_test_hog_SOURCES) $(unit_test_lib_SOURCES) \ + $(am__unit_test_mesh_crypto_SOURCES_DIST) \ + $(unit_test_mgmt_SOURCES) $(am__unit_test_midi_SOURCES_DIST) \ + $(unit_test_queue_SOURCES) $(unit_test_ringbuf_SOURCES) \ + $(unit_test_sdp_SOURCES) $(unit_test_textfile_SOURCES) \ + $(unit_test_uhid_SOURCES) $(unit_test_uuid_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +man1dir = $(mandir)/man1 +man8dir = $(mandir)/man8 +NROFF = nroff +MANS = $(dist_man_MANS) $(man_MANS) +am__dist_zshcompletion_DATA_DIST = completion/zsh/_bluetoothctl +DATA = $(conf_DATA) $(dbus_DATA) $(dbussessionbus_DATA) \ + $(dbussystembus_DATA) $(dist_zshcompletion_DATA) \ + $(pkgconfig_DATA) $(rules_DATA) $(state_DATA) \ + $(systemdsystemunit_DATA) $(systemduserunit_DATA) +am__pkginclude_HEADERS_DIST = lib/bluetooth.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 +HEADERS = $(pkginclude_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) \ + $(LISP)config.h.in +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +CSCOPE = cscope +AM_RECURSIVE_TARGETS = cscope check recheck +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red=''; \ + grn=''; \ + lgn=''; \ + blu=''; \ + mgn=''; \ + brg=''; \ + std=''; \ + fi; \ +} +am__recheck_rx = ^[ ]*:recheck:[ ]* +am__global_test_result_rx = ^[ ]*:global-test-result:[ ]* +am__copy_in_global_log_rx = ^[ ]*:copy-in-global-log:[ ]* +# A command that, given a newline-separated list of test names on the +# standard input, print the name of the tests that are to be re-run +# upon "make recheck". +am__list_recheck_tests = $(AWK) '{ \ + recheck = 1; \ + while ((rc = (getline line < ($$0 ".trs"))) != 0) \ + { \ + if (rc < 0) \ + { \ + if ((getline line2 < ($$0 ".log")) < 0) \ + recheck = 0; \ + break; \ + } \ + else if (line ~ /$(am__recheck_rx)[nN][Oo]/) \ + { \ + recheck = 0; \ + break; \ + } \ + else if (line ~ /$(am__recheck_rx)[yY][eE][sS]/) \ + { \ + break; \ + } \ + }; \ + if (recheck) \ + print $$0; \ + close ($$0 ".trs"); \ + close ($$0 ".log"); \ +}' +# A command that, given a newline-separated list of test names on the +# standard input, create the global log from their .trs and .log files. +am__create_global_log = $(AWK) ' \ +function fatal(msg) \ +{ \ + print "fatal: making $@: " msg | "cat >&2"; \ + exit 1; \ +} \ +function rst_section(header) \ +{ \ + print header; \ + len = length(header); \ + for (i = 1; i <= len; i = i + 1) \ + printf "="; \ + printf "\n\n"; \ +} \ +{ \ + copy_in_global_log = 1; \ + global_test_result = "RUN"; \ + while ((rc = (getline line < ($$0 ".trs"))) != 0) \ + { \ + if (rc < 0) \ + fatal("failed to read from " $$0 ".trs"); \ + if (line ~ /$(am__global_test_result_rx)/) \ + { \ + sub("$(am__global_test_result_rx)", "", line); \ + sub("[ ]*$$", "", line); \ + global_test_result = line; \ + } \ + else if (line ~ /$(am__copy_in_global_log_rx)[nN][oO]/) \ + copy_in_global_log = 0; \ + }; \ + if (copy_in_global_log) \ + { \ + rst_section(global_test_result ": " $$0); \ + while ((rc = (getline line < ($$0 ".log"))) != 0) \ + { \ + if (rc < 0) \ + fatal("failed to read from " $$0 ".log"); \ + print line; \ + }; \ + printf "\n"; \ + }; \ + close ($$0 ".trs"); \ + close ($$0 ".log"); \ +}' +# Restructured Text title. +am__rst_title = { sed 's/.*/ & /;h;s/./=/g;p;x;s/ *$$//;p;g' && echo; } +# Solaris 10 'make', and several other traditional 'make' implementations, +# pass "-e" to $(SHELL), and POSIX 2008 even requires this. Work around it +# by disabling -e (using the XSI extension "set +e") if it's set. +am__sh_e_setup = case $$- in *e*) set +e;; esac +# Default flags passed to test drivers. +am__common_driver_flags = \ + --color-tests "$$am__color_tests" \ + --enable-hard-errors "$$am__enable_hard_errors" \ + --expect-failure "$$am__expect_failure" +# To be inserted before the command running the test. Creates the +# directory for the log if needed. Stores in $dir the directory +# containing $f, in $tst the test, in $log the log. Executes the +# developer- defined test setup AM_TESTS_ENVIRONMENT (if any), and +# passes TESTS_ENVIRONMENT. Set up options for the wrapper that +# will run the test scripts (or their associated LOG_COMPILER, if +# thy have one). +am__check_pre = \ +$(am__sh_e_setup); \ +$(am__vpath_adj_setup) $(am__vpath_adj) \ +$(am__tty_colors); \ +srcdir=$(srcdir); export srcdir; \ +case "$@" in \ + */*) am__odir=`echo "./$@" | sed 's|/[^/]*$$||'`;; \ + *) am__odir=.;; \ +esac; \ +test "x$$am__odir" = x"." || test -d "$$am__odir" \ + || $(MKDIR_P) "$$am__odir" || exit $$?; \ +if test -f "./$$f"; then dir=./; \ +elif test -f "$$f"; then dir=; \ +else dir="$(srcdir)/"; fi; \ +tst=$$dir$$f; log='$@'; \ +if test -n '$(DISABLE_HARD_ERRORS)'; then \ + am__enable_hard_errors=no; \ +else \ + am__enable_hard_errors=yes; \ +fi; \ +case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$f[\ \ ]* | *[\ \ ]$$dir$$f[\ \ ]*) \ + am__expect_failure=yes;; \ + *) \ + am__expect_failure=no;; \ +esac; \ +$(AM_TESTS_ENVIRONMENT) $(TESTS_ENVIRONMENT) +# A shell command to get the names of the tests scripts with any registered +# extension removed (i.e., equivalently, the names of the test logs, with +# the '.log' extension removed). The result is saved in the shell variable +# '$bases'. This honors runtime overriding of TESTS and TEST_LOGS. Sadly, +# we cannot use something simpler, involving e.g., "$(TEST_LOGS:.log=)", +# since that might cause problem with VPATH rewrites for suffix-less tests. +# See also 'test-harness-vpath-rewrite.sh' and 'test-trs-basic.sh'. +am__set_TESTS_bases = \ + bases='$(TEST_LOGS)'; \ + bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \ + bases=`echo $$bases` +RECHECK_LOGS = $(TEST_LOGS) +TEST_SUITE_LOG = test-suite.log +TEST_EXTENSIONS = @EXEEXT@ .test +LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver +LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS) +am__set_b = \ + case '$@' in \ + */*) \ + case '$*' in \ + */*) b='$*';; \ + *) b=`echo '$@' | sed 's/\.log$$//'`; \ + esac;; \ + *) \ + b='$*';; \ + esac +am__test_logs1 = $(TESTS:=.log) +am__test_logs2 = $(am__test_logs1:@EXEEXT@.log=.log) +TEST_LOGS = $(am__test_logs2:.test.log=.log) +TEST_LOG_DRIVER = $(SHELL) $(top_srcdir)/test-driver +TEST_LOG_COMPILE = $(TEST_LOG_COMPILER) $(AM_TEST_LOG_FLAGS) \ + $(TEST_LOG_FLAGS) +am__DIST_COMMON = $(dist_man_MANS) $(srcdir)/Makefile.in \ + $(srcdir)/Makefile.mesh $(srcdir)/Makefile.obexd \ + $(srcdir)/Makefile.plugins $(srcdir)/Makefile.tools \ + $(srcdir)/android/Makefile.am $(srcdir)/config.h.in \ + $(top_srcdir)/lib/bluez.pc.in \ + $(top_srcdir)/src/bluetoothd.8.in AUTHORS COPYING COPYING.LIB \ + ChangeLog INSTALL NEWS README TODO compile config.guess \ + config.sub depcomp install-sh ltmain.sh missing test-driver +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +distdir = $(PACKAGE)-$(VERSION) +top_distdir = $(distdir) +am__remove_distdir = \ + if test -d "$(distdir)"; then \ + find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -rf "$(distdir)" \ + || { sleep 5 && rm -rf "$(distdir)"; }; \ + else :; fi +am__post_remove_distdir = $(am__remove_distdir) +GZIP_ENV = --best +DIST_ARCHIVES = $(distdir).tar.xz +DIST_TARGETS = dist-xz +distuninstallcheck_listfiles = find . -type f -print +am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ + | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' +distcleancheck_listfiles = find . -type f -print +pkgincludedir = $(includedir)/bluetooth +pkglibexecdir = $(libexecdir)/bluetooth +ACLOCAL = @ACLOCAL@ +ALSA_CFLAGS = @ALSA_CFLAGS@ +ALSA_LIBS = @ALSA_LIBS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BACKTRACE_CFLAGS = @BACKTRACE_CFLAGS@ +BACKTRACE_LIBS = @BACKTRACE_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONFIGDIR = @CONFIGDIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DBUS_CFLAGS = @DBUS_CFLAGS@ +DBUS_CONFDIR = @DBUS_CONFDIR@ +DBUS_LIBS = @DBUS_LIBS@ +DBUS_SESSIONBUSDIR = @DBUS_SESSIONBUSDIR@ +DBUS_SYSTEMBUSDIR = @DBUS_SYSTEMBUSDIR@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ELL_CFLAGS = @ELL_CFLAGS@ +ELL_LIBS = @ELL_LIBS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_LIBS = @GLIB_LIBS@ +GREP = @GREP@ +GTHREAD_CFLAGS = @GTHREAD_CFLAGS@ +GTHREAD_LIBS = @GTHREAD_LIBS@ +ICAL_CFLAGS = @ICAL_CFLAGS@ +ICAL_LIBS = @ICAL_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +JSONC_CFLAGS = @JSONC_CFLAGS@ +JSONC_LIBS = @JSONC_LIBS@ +JSON_CFLAGS = @JSON_CFLAGS@ +JSON_LIBS = @JSON_LIBS@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MISC_CFLAGS = @MISC_CFLAGS@ +MISC_LDFLAGS = @MISC_LDFLAGS@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SBC_CFLAGS = @SBC_CFLAGS@ +SBC_LIBS = @SBC_LIBS@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPEEXDSP_CFLAGS = @SPEEXDSP_CFLAGS@ +SPEEXDSP_LIBS = @SPEEXDSP_LIBS@ +STRIP = @STRIP@ +SYSTEMD_SYSTEMUNITDIR = @SYSTEMD_SYSTEMUNITDIR@ +SYSTEMD_USERUNITDIR = @SYSTEMD_USERUNITDIR@ +UDEV_CFLAGS = @UDEV_CFLAGS@ +UDEV_DIR = @UDEV_DIR@ +UDEV_LIBS = @UDEV_LIBS@ +VERSION = @VERSION@ +WARNING_CFLAGS = @WARNING_CFLAGS@ +ZSH_COMPLETIONDIR = @ZSH_COMPLETIONDIR@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +enable_coverage = @enable_coverage@ +enable_dbus_run_session = @enable_dbus_run_session@ +enable_valgrind = @enable_valgrind@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_MAKEFLAGS = --no-print-directory +AM_CPPFLAGS = $(am__append_51) $(DBUS_CFLAGS) $(GLIB_CFLAGS) \ + -I$(builddir)/lib +lib_LTLIBRARIES = $(am__append_2) +noinst_LIBRARIES = +noinst_LTLIBRARIES = lib/libbluetooth-internal.la \ + gdbus/libgdbus-internal.la $(am__append_3) \ + src/libshared-glib.la src/libshared-mainloop.la \ + $(am__append_4) +dist_man_MANS = $(am__append_36) $(am__append_40) $(am__append_43) +dist_noinst_MANS = $(am__append_65) +CLEANFILES = $(ell_built_sources) $(builtin_files) \ + src/bluetooth.service tools/bluetooth-logger.service \ + obexd/src/builtin.h $(builtin_files) obexd/src/obex.service \ + $(am__append_59) $(am__append_60) +EXTRA_DIST = src/bluetooth.service.in src/org.bluez.service \ + $(am__append_20) src/genbuiltin src/bluetooth.conf \ + src/main.conf profiles/network/network.conf \ + profiles/input/input.conf tools/bluetooth-logger.service.in \ + $(am__append_37) $(am__append_41) $(am__append_42) \ + $(am__append_44) obexd/src/obex.service.in \ + obexd/src/org.bluez.obex.service obexd/src/genbuiltin \ + 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 \ + mesh/bluetooth-mesh.conf mesh/bluetooth-mesh.service.in \ + mesh/org.bluez.mesh.service tools/hid2hci.rules \ + $(test_scripts) doc/assigned-numbers.txt \ + doc/supported-features.txt doc/test-coverage.txt \ + doc/test-runner.txt doc/settings-storage.txt 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 \ + doc/gatt-api.txt doc/advertising-api.txt doc/obex-api.txt \ + doc/obex-agent-api.txt doc/pics-opp.txt doc/pixit-opp.txt \ + doc/pts-opp.txt doc/btsnoop.txt tools/magic.btsnoop \ + $(manual_pages:.1=.txt) +pkginclude_HEADERS = $(am__append_1) +AM_CFLAGS = $(WARNING_CFLAGS) $(MISC_CFLAGS) $(UDEV_CFLAGS) $(ell_cflags) +AM_LDFLAGS = $(MISC_LDFLAGS) +@DATAFILES_TRUE@dbusdir = $(DBUS_CONFDIR)/dbus-1/system.d +@DATAFILES_TRUE@dbus_DATA = src/bluetooth.conf $(am__append_55) +@DATAFILES_TRUE@confdir = $(sysconfdir)/bluetooth +@DATAFILES_TRUE@conf_DATA = +@DATAFILES_TRUE@statedir = $(localstatedir)/lib/bluetooth +@DATAFILES_TRUE@state_DATA = +@SYSTEMD_TRUE@systemdsystemunitdir = $(SYSTEMD_SYSTEMUNITDIR) +@SYSTEMD_TRUE@systemdsystemunit_DATA = src/bluetooth.service \ +@SYSTEMD_TRUE@ $(am__append_32) $(am__append_56) +@SYSTEMD_TRUE@dbussystembusdir = $(DBUS_SYSTEMBUSDIR) +@SYSTEMD_TRUE@dbussystembus_DATA = src/org.bluez.service \ +@SYSTEMD_TRUE@ $(am__append_57) +plugindir = $(libdir)/bluetooth/plugins +@MAINTAINER_MODE_FALSE@build_plugindir = $(plugindir) +@MAINTAINER_MODE_TRUE@build_plugindir = $(abs_top_srcdir)/plugins/.libs +plugin_LTLIBRARIES = $(am__append_27) $(am__append_28) \ + $(am__append_53) +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) $(ell_built_sources) src/builtin.h \ + obexd/src/builtin.h +@LIBRARY_TRUE@lib_libbluetooth_la_SOURCES = $(lib_headers) $(lib_sources) +@LIBRARY_TRUE@lib_libbluetooth_la_LDFLAGS = $(AM_LDFLAGS) -version-info 22:0:19 +@LIBRARY_TRUE@lib_libbluetooth_la_DEPENDENCIES = $(local_headers) +lib_libbluetooth_internal_la_SOURCES = $(lib_headers) $(lib_sources) \ + $(extra_headers) $(extra_sources) + +gdbus_libgdbus_internal_la_SOURCES = gdbus/gdbus.h \ + gdbus/mainloop.c gdbus/watch.c \ + gdbus/object.c gdbus/client.c gdbus/polkit.c + +@EXTERNAL_ELL_FALSE@ell_cflags = +@EXTERNAL_ELL_TRUE@ell_cflags = @ELL_CFLAGS@ +@EXTERNAL_ELL_FALSE@ell_ldadd = ell/libell-internal.la +@EXTERNAL_ELL_TRUE@ell_ldadd = @ELL_LIBS@ +@EXTERNAL_ELL_FALSE@ell_dependencies = $(ell_ldadd) +@EXTERNAL_ELL_TRUE@ell_dependencies = +@EXTERNAL_ELL_FALSE@ell_built_sources = ell/internal ell/ell.h +@EXTERNAL_ELL_TRUE@ell_built_sources = +@EXTERNAL_ELL_FALSE@ell_headers = ell/util.h \ +@EXTERNAL_ELL_FALSE@ ell/log.h \ +@EXTERNAL_ELL_FALSE@ ell/queue.h \ +@EXTERNAL_ELL_FALSE@ ell/hashmap.h \ +@EXTERNAL_ELL_FALSE@ ell/random.h \ +@EXTERNAL_ELL_FALSE@ ell/signal.h \ +@EXTERNAL_ELL_FALSE@ ell/timeout.h \ +@EXTERNAL_ELL_FALSE@ ell/cipher.h \ +@EXTERNAL_ELL_FALSE@ ell/checksum.h \ +@EXTERNAL_ELL_FALSE@ ell/io.h \ +@EXTERNAL_ELL_FALSE@ ell/idle.h \ +@EXTERNAL_ELL_FALSE@ ell/main.h \ +@EXTERNAL_ELL_FALSE@ ell/strv.h \ +@EXTERNAL_ELL_FALSE@ ell/string.h \ +@EXTERNAL_ELL_FALSE@ ell/utf8.h \ +@EXTERNAL_ELL_FALSE@ ell/dbus.h \ +@EXTERNAL_ELL_FALSE@ ell/dbus-service.h \ +@EXTERNAL_ELL_FALSE@ ell/dbus-client.h + +@EXTERNAL_ELL_FALSE@ell_sources = ell/private.h ell/missing.h \ +@EXTERNAL_ELL_FALSE@ ell/util.c \ +@EXTERNAL_ELL_FALSE@ ell/log.c \ +@EXTERNAL_ELL_FALSE@ ell/queue.c \ +@EXTERNAL_ELL_FALSE@ ell/hashmap.c \ +@EXTERNAL_ELL_FALSE@ ell/random.c \ +@EXTERNAL_ELL_FALSE@ ell/signal.c \ +@EXTERNAL_ELL_FALSE@ ell/timeout.c \ +@EXTERNAL_ELL_FALSE@ ell/io.c \ +@EXTERNAL_ELL_FALSE@ ell/idle.c \ +@EXTERNAL_ELL_FALSE@ ell/main.c \ +@EXTERNAL_ELL_FALSE@ ell/strv.c \ +@EXTERNAL_ELL_FALSE@ ell/string.c \ +@EXTERNAL_ELL_FALSE@ ell/cipher.c \ +@EXTERNAL_ELL_FALSE@ ell/checksum.c \ +@EXTERNAL_ELL_FALSE@ ell/utf8.c \ +@EXTERNAL_ELL_FALSE@ ell/dbus-private.h \ +@EXTERNAL_ELL_FALSE@ ell/dbus.c \ +@EXTERNAL_ELL_FALSE@ ell/dbus-message.c \ +@EXTERNAL_ELL_FALSE@ ell/dbus-util.c \ +@EXTERNAL_ELL_FALSE@ ell/dbus-service.c \ +@EXTERNAL_ELL_FALSE@ ell/dbus-client.c \ +@EXTERNAL_ELL_FALSE@ ell/dbus-name-cache.c \ +@EXTERNAL_ELL_FALSE@ ell/dbus-filter.c \ +@EXTERNAL_ELL_FALSE@ ell/gvariant-private.h \ +@EXTERNAL_ELL_FALSE@ ell/gvariant-util.c \ +@EXTERNAL_ELL_FALSE@ ell/siphash-private.h \ +@EXTERNAL_ELL_FALSE@ ell/siphash.c + +@EXTERNAL_ELL_FALSE@ell_libell_internal_la_SOURCES = $(ell_headers) $(ell_sources) +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/log.h src/shared/log.c src/shared/tty.h \ + $(am__append_5) +src_libshared_glib_la_SOURCES = $(shared_sources) \ + src/shared/io-glib.c \ + src/shared/timeout-glib.c \ + src/shared/mainloop-glib.c \ + src/shared/mainloop-notify.h \ + src/shared/mainloop-notify.c + +src_libshared_mainloop_la_SOURCES = $(shared_sources) \ + src/shared/io-mainloop.c \ + src/shared/timeout-mainloop.c \ + src/shared/mainloop.h src/shared/mainloop.c \ + src/shared/mainloop-notify.h \ + src/shared/mainloop-notify.c + +@LIBSHARED_ELL_TRUE@src_libshared_ell_la_SOURCES = $(shared_sources) \ +@LIBSHARED_ELL_TRUE@ src/shared/io-ell.c \ +@LIBSHARED_ELL_TRUE@ src/shared/timeout-ell.c \ +@LIBSHARED_ELL_TRUE@ src/shared/mainloop.h \ +@LIBSHARED_ELL_TRUE@ src/shared/mainloop-ell.c + +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 = hostname wiimote autopair policy $(am__append_6) \ + $(am__append_8) $(am__append_10) $(am__append_12) \ + $(am__append_14) $(am__append_16) $(am__append_18) \ + $(am__append_21) gap scanparam deviceinfo $(am__append_23) \ + battery +builtin_sources = plugins/hostname.c plugins/wiimote.c \ + plugins/autopair.c plugins/policy.c $(am__append_7) \ + $(am__append_9) $(am__append_11) $(am__append_13) \ + $(am__append_15) $(am__append_17) $(am__append_19) \ + $(am__append_22) profiles/gap/gas.c profiles/scanparam/scan.c \ + profiles/deviceinfo/deviceinfo.c $(am__append_24) \ + profiles/battery/battery.c +builtin_cppflags = $(am__append_25) +builtin_nodist = +builtin_ldadd = $(am__append_26) +@SIXAXIS_TRUE@plugins_sixaxis_la_SOURCES = plugins/sixaxis.c +@SIXAXIS_TRUE@plugins_sixaxis_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ +@SIXAXIS_TRUE@ -no-undefined + +@SIXAXIS_TRUE@plugins_sixaxis_la_LIBADD = $(UDEV_LIBS) +@SIXAXIS_TRUE@plugins_sixaxis_la_CFLAGS = $(AM_CFLAGS) -fvisibility=hidden +@MAINTAINER_MODE_TRUE@plugins_external_dummy_la_SOURCES = plugins/external-dummy.c +@MAINTAINER_MODE_TRUE@plugins_external_dummy_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ +@MAINTAINER_MODE_TRUE@ -no-undefined + +@MAINTAINER_MODE_TRUE@plugins_external_dummy_la_CFLAGS = $(AM_CFLAGS) -fvisibility=hidden +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/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_CPPFLAGS = $(AM_CPPFLAGS) -DBLUETOOTH_PLUGIN_BUILTIN \ + -DPLUGINDIR=\""$(build_plugindir)"\" \ + $(BACKTRACE_CFLAGS) $(builtin_cppflags) + +src_bluetoothd_SHORTNAME = bluetoothd +builtin_files = src/builtin.h $(builtin_nodist) +nodist_src_bluetoothd_SOURCES = $(builtin_files) +man_MANS = src/bluetoothd.8 +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 test/test-mesh test/agent.py +unit_tests = $(am__append_54) unit/test-eir unit/test-uuid \ + unit/test-textfile unit/test-crc unit/test-crypto \ + unit/test-ecc unit/test-ringbuf unit/test-queue unit/test-mgmt \ + unit/test-uhid unit/test-sdp unit/test-avdtp unit/test-avctp \ + unit/test-avrcp unit/test-hfp unit/test-gdbus-client \ + unit/test-gobex-header unit/test-gobex-packet unit/test-gobex \ + unit/test-gobex-transfer unit/test-gobex-apparam unit/test-lib \ + unit/test-gatt unit/test-hog unit/test-gattrib \ + $(am__append_61) $(am__append_62) +@CLIENT_TRUE@client_bluetoothctl_SOURCES = client/main.c \ +@CLIENT_TRUE@ client/display.h client/display.c \ +@CLIENT_TRUE@ client/agent.h client/agent.c \ +@CLIENT_TRUE@ client/advertising.h \ +@CLIENT_TRUE@ client/advertising.c \ +@CLIENT_TRUE@ client/gatt.h client/gatt.c + +@CLIENT_TRUE@client_bluetoothctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ +@CLIENT_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline + +@ZSH_COMPLETIONS_TRUE@zshcompletiondir = $(ZSH_COMPLETIONDIR) +@ZSH_COMPLETIONS_TRUE@dist_zshcompletion_DATA = completion/zsh/_bluetoothctl +@MONITOR_TRUE@monitor_btmon_SOURCES = monitor/main.c monitor/bt.h \ +@MONITOR_TRUE@ monitor/display.h monitor/display.c \ +@MONITOR_TRUE@ monitor/hcidump.h monitor/hcidump.c \ +@MONITOR_TRUE@ monitor/ellisys.h monitor/ellisys.c \ +@MONITOR_TRUE@ monitor/control.h monitor/control.c \ +@MONITOR_TRUE@ monitor/packet.h monitor/packet.c \ +@MONITOR_TRUE@ monitor/vendor.h monitor/vendor.c \ +@MONITOR_TRUE@ monitor/lmp.h monitor/lmp.c \ +@MONITOR_TRUE@ monitor/crc.h monitor/crc.c \ +@MONITOR_TRUE@ monitor/ll.h monitor/ll.c \ +@MONITOR_TRUE@ monitor/l2cap.h monitor/l2cap.c \ +@MONITOR_TRUE@ monitor/sdp.h monitor/sdp.c \ +@MONITOR_TRUE@ monitor/avctp.h monitor/avctp.c \ +@MONITOR_TRUE@ monitor/avdtp.h monitor/avdtp.c \ +@MONITOR_TRUE@ monitor/a2dp.h monitor/a2dp.c \ +@MONITOR_TRUE@ monitor/rfcomm.h monitor/rfcomm.c \ +@MONITOR_TRUE@ monitor/bnep.h monitor/bnep.c \ +@MONITOR_TRUE@ monitor/hwdb.h monitor/hwdb.c \ +@MONITOR_TRUE@ monitor/keys.h monitor/keys.c \ +@MONITOR_TRUE@ monitor/analyze.h monitor/analyze.c \ +@MONITOR_TRUE@ monitor/intel.h monitor/intel.c \ +@MONITOR_TRUE@ monitor/broadcom.h monitor/broadcom.c \ +@MONITOR_TRUE@ monitor/jlink.h monitor/jlink.c \ +@MONITOR_TRUE@ monitor/tty.h + +@MONITOR_TRUE@monitor_btmon_LDADD = lib/libbluetooth-internal.la \ +@MONITOR_TRUE@ src/libshared-mainloop.la $(UDEV_LIBS) -ldl + +@LOGGER_TRUE@tools_btmon_logger_SOURCES = tools/btmon-logger.c +@LOGGER_TRUE@tools_btmon_logger_LDADD = src/libshared-mainloop.la +@LOGGER_TRUE@tools_btmon_logger_DEPENDENCIES = src/libshared-mainloop.la \ +@LOGGER_TRUE@ tools/bluetooth-logger.service + +@TESTING_TRUE@emulator_btvirt_SOURCES = emulator/main.c monitor/bt.h \ +@TESTING_TRUE@ emulator/serial.h emulator/serial.c \ +@TESTING_TRUE@ emulator/server.h emulator/server.c \ +@TESTING_TRUE@ emulator/vhci.h emulator/vhci.c \ +@TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ +@TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ +@TESTING_TRUE@ emulator/smp.c \ +@TESTING_TRUE@ emulator/phy.h emulator/phy.c \ +@TESTING_TRUE@ emulator/amp.h emulator/amp.c \ +@TESTING_TRUE@ emulator/le.h emulator/le.c + +@TESTING_TRUE@emulator_btvirt_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la +@TESTING_TRUE@emulator_b1ee_SOURCES = emulator/b1ee.c +@TESTING_TRUE@emulator_b1ee_LDADD = src/libshared-mainloop.la +@TESTING_TRUE@emulator_hfp_SOURCES = emulator/hfp.c +@TESTING_TRUE@emulator_hfp_LDADD = src/libshared-mainloop.la +@TESTING_TRUE@peripheral_btsensor_SOURCES = peripheral/main.c \ +@TESTING_TRUE@ peripheral/efivars.h peripheral/efivars.c \ +@TESTING_TRUE@ peripheral/attach.h peripheral/attach.c \ +@TESTING_TRUE@ peripheral/log.h peripheral/log.c \ +@TESTING_TRUE@ peripheral/gap.h peripheral/gap.c \ +@TESTING_TRUE@ peripheral/gatt.h peripheral/gatt.c + +@TESTING_TRUE@peripheral_btsensor_LDADD = src/libshared-mainloop.la \ +@TESTING_TRUE@ lib/libbluetooth-internal.la + +@TESTING_TRUE@tools_3dsp_SOURCES = tools/3dsp.c monitor/bt.h +@TESTING_TRUE@tools_3dsp_LDADD = src/libshared-mainloop.la +@TESTING_TRUE@tools_mgmt_tester_SOURCES = tools/mgmt-tester.c monitor/bt.h \ +@TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ +@TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ +@TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ +@TESTING_TRUE@ emulator/smp.c + +@TESTING_TRUE@tools_mgmt_tester_LDADD = lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) + +@TESTING_TRUE@tools_l2cap_tester_SOURCES = tools/l2cap-tester.c monitor/bt.h \ +@TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ +@TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ +@TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ +@TESTING_TRUE@ emulator/smp.c + +@TESTING_TRUE@tools_l2cap_tester_LDADD = lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) + +@TESTING_TRUE@tools_rfcomm_tester_SOURCES = tools/rfcomm-tester.c monitor/bt.h \ +@TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ +@TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ +@TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ +@TESTING_TRUE@ emulator/smp.c + +@TESTING_TRUE@tools_rfcomm_tester_LDADD = lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) + +@TESTING_TRUE@tools_bnep_tester_SOURCES = tools/bnep-tester.c monitor/bt.h \ +@TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ +@TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ +@TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ +@TESTING_TRUE@ emulator/smp.c + +@TESTING_TRUE@tools_bnep_tester_LDADD = lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) + +@TESTING_TRUE@tools_smp_tester_SOURCES = tools/smp-tester.c monitor/bt.h \ +@TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ +@TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ +@TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ +@TESTING_TRUE@ emulator/smp.c + +@TESTING_TRUE@tools_smp_tester_LDADD = lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) + +@TESTING_TRUE@tools_gap_tester_SOURCES = tools/gap-tester.c monitor/bt.h \ +@TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ +@TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ +@TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ +@TESTING_TRUE@ emulator/smp.c + +@TESTING_TRUE@tools_gap_tester_LDADD = lib/libbluetooth-internal.la \ +@TESTING_TRUE@ gdbus/libgdbus-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la \ +@TESTING_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) + +@TESTING_TRUE@tools_sco_tester_SOURCES = tools/sco-tester.c monitor/bt.h \ +@TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ +@TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ +@TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ +@TESTING_TRUE@ emulator/smp.c + +@TESTING_TRUE@tools_sco_tester_LDADD = lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) + +@TESTING_TRUE@tools_hci_tester_SOURCES = tools/hci-tester.c monitor/bt.h +@TESTING_TRUE@tools_hci_tester_LDADD = src/libshared-glib.la $(GLIB_LIBS) +@TESTING_TRUE@tools_userchan_tester_SOURCES = tools/userchan-tester.c monitor/bt.h \ +@TESTING_TRUE@ emulator/hciemu.h emulator/hciemu.c \ +@TESTING_TRUE@ emulator/btdev.h emulator/btdev.c \ +@TESTING_TRUE@ emulator/bthost.h emulator/bthost.c \ +@TESTING_TRUE@ emulator/smp.c + +@TESTING_TRUE@tools_userchan_tester_LDADD = lib/libbluetooth-internal.la \ +@TESTING_TRUE@ src/libshared-glib.la $(GLIB_LIBS) + +@TOOLS_TRUE@tools_bdaddr_SOURCES = tools/bdaddr.c src/oui.h src/oui.c +@TOOLS_TRUE@tools_bdaddr_LDADD = lib/libbluetooth-internal.la $(UDEV_LIBS) +@TOOLS_TRUE@tools_avinfo_LDADD = lib/libbluetooth-internal.la +@TOOLS_TRUE@tools_avtest_LDADD = lib/libbluetooth-internal.la +@TOOLS_TRUE@tools_scotest_LDADD = lib/libbluetooth-internal.la +@TOOLS_TRUE@tools_amptest_LDADD = lib/libbluetooth-internal.la +@TOOLS_TRUE@tools_hwdb_LDADD = lib/libbluetooth-internal.la +@TOOLS_TRUE@tools_hcieventmask_LDADD = lib/libbluetooth-internal.la +@TOOLS_TRUE@tools_btinfo_SOURCES = tools/btinfo.c monitor/bt.h +@TOOLS_TRUE@tools_btinfo_LDADD = src/libshared-mainloop.la +@TOOLS_TRUE@tools_btattach_SOURCES = tools/btattach.c monitor/bt.h +@TOOLS_TRUE@tools_btattach_LDADD = src/libshared-mainloop.la +@TOOLS_TRUE@tools_btconfig_SOURCES = tools/btconfig.c +@TOOLS_TRUE@tools_btconfig_LDADD = src/libshared-mainloop.la +@TOOLS_TRUE@tools_btsnoop_SOURCES = tools/btsnoop.c +@TOOLS_TRUE@tools_btsnoop_LDADD = src/libshared-mainloop.la +@TOOLS_TRUE@tools_btproxy_SOURCES = tools/btproxy.c monitor/bt.h +@TOOLS_TRUE@tools_btproxy_LDADD = src/libshared-mainloop.la +@TOOLS_TRUE@tools_btiotest_SOURCES = tools/btiotest.c btio/btio.h btio/btio.c +@TOOLS_TRUE@tools_btiotest_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) +@TOOLS_TRUE@tools_mcaptest_SOURCES = tools/mcaptest.c \ +@TOOLS_TRUE@ btio/btio.h btio/btio.c \ +@TOOLS_TRUE@ src/log.c src/log.h \ +@TOOLS_TRUE@ profiles/health/mcap.h profiles/health/mcap.c + +@TOOLS_TRUE@tools_mcaptest_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) \ +@TOOLS_TRUE@ src/libshared-mainloop.la -lrt + +@TOOLS_TRUE@tools_bneptest_SOURCES = tools/bneptest.c \ +@TOOLS_TRUE@ btio/btio.h btio/btio.c \ +@TOOLS_TRUE@ src/log.h src/log.c \ +@TOOLS_TRUE@ profiles/network/bnep.h profiles/network/bnep.c + +@TOOLS_TRUE@tools_bneptest_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) \ +@TOOLS_TRUE@ src/libshared-mainloop.la + +@TOOLS_TRUE@tools_cltest_SOURCES = tools/cltest.c +@TOOLS_TRUE@tools_cltest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la +@TOOLS_TRUE@tools_oobtest_SOURCES = tools/oobtest.c +@TOOLS_TRUE@tools_oobtest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la +@TOOLS_TRUE@tools_advtest_SOURCES = tools/advtest.c +@TOOLS_TRUE@tools_advtest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la +@TOOLS_TRUE@tools_seq2bseq_SOURCES = tools/seq2bseq.c +@TOOLS_TRUE@tools_nokfw_SOURCES = tools/nokfw.c +@TOOLS_TRUE@tools_rtlfw_SOURCES = tools/rtlfw.c +@TOOLS_TRUE@tools_create_image_SOURCES = tools/create-image.c +@TOOLS_TRUE@tools_eddystone_SOURCES = tools/eddystone.c monitor/bt.h +@TOOLS_TRUE@tools_eddystone_LDADD = src/libshared-mainloop.la +@TOOLS_TRUE@tools_ibeacon_SOURCES = tools/ibeacon.c monitor/bt.h +@TOOLS_TRUE@tools_ibeacon_LDADD = src/libshared-mainloop.la +@TOOLS_TRUE@tools_btgatt_client_SOURCES = tools/btgatt-client.c src/uuid-helper.c +@TOOLS_TRUE@tools_btgatt_client_LDADD = src/libshared-mainloop.la \ +@TOOLS_TRUE@ lib/libbluetooth-internal.la + +@TOOLS_TRUE@tools_btgatt_server_SOURCES = tools/btgatt-server.c src/uuid-helper.c +@TOOLS_TRUE@tools_btgatt_server_LDADD = src/libshared-mainloop.la \ +@TOOLS_TRUE@ lib/libbluetooth-internal.la + +@TOOLS_TRUE@tools_rctest_LDADD = lib/libbluetooth-internal.la +@TOOLS_TRUE@tools_l2test_LDADD = lib/libbluetooth-internal.la +@TOOLS_TRUE@tools_l2ping_LDADD = lib/libbluetooth-internal.la +@TOOLS_TRUE@tools_bccmd_SOURCES = tools/bccmd.c tools/csr.h tools/csr.c \ +@TOOLS_TRUE@ tools/csr_hci.c tools/csr_usb.c \ +@TOOLS_TRUE@ tools/csr_h4.c tools/csr_3wire.c \ +@TOOLS_TRUE@ tools/csr_bcsp.c tools/ubcsp.h tools/ubcsp.c + +@TOOLS_TRUE@tools_bccmd_LDADD = lib/libbluetooth-internal.la +@TOOLS_TRUE@tools_bluemoon_SOURCES = tools/bluemoon.c monitor/bt.h +@TOOLS_TRUE@tools_bluemoon_LDADD = src/libshared-mainloop.la +@TOOLS_TRUE@tools_hex2hcd_SOURCES = tools/hex2hcd.c +@TOOLS_TRUE@tools_mpris_proxy_SOURCES = tools/mpris-proxy.c +@TOOLS_TRUE@tools_mpris_proxy_LDADD = gdbus/libgdbus-internal.la $(GLIB_LIBS) $(DBUS_LIBS) +@TOOLS_TRUE@tools_gatt_service_SOURCES = tools/gatt-service.c +@TOOLS_TRUE@tools_gatt_service_LDADD = $(GLIB_LIBS) $(DBUS_LIBS) gdbus/libgdbus-internal.la +@TOOLS_TRUE@profiles_iap_iapd_SOURCES = profiles/iap/main.c +@TOOLS_TRUE@profiles_iap_iapd_LDADD = gdbus/libgdbus-internal.la $(GLIB_LIBS) $(DBUS_LIBS) +@MESH_TRUE@@TOOLS_TRUE@tools_meshctl_SOURCES = tools/meshctl.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/mesh-net.h \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/node.h tools/mesh/node.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/gatt.h tools/mesh/gatt.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/crypto.h tools/mesh/crypto.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/keys.h \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/net.h tools/mesh/net.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/prov.h tools/mesh/prov.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/util.h tools/mesh/util.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/agent.h tools/mesh/agent.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/prov-db.h tools/mesh/prov-db.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/config-model.h tools/mesh/config-client.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/config-server.c \ +@MESH_TRUE@@TOOLS_TRUE@ tools/mesh/onoff-model.h tools/mesh/onoff-model.c + +@MESH_TRUE@@TOOLS_TRUE@tools_meshctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ +@MESH_TRUE@@TOOLS_TRUE@ lib/libbluetooth-internal.la \ +@MESH_TRUE@@TOOLS_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) -ljson-c -lreadline + +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciattach_SOURCES = tools/hciattach.c tools/hciattach.h \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_st.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_ti.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_tialt.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_ath3k.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_qualcomm.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_intel.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/hciattach_bcm43xx.c + +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciattach_LDADD = lib/libbluetooth-internal.la +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciconfig_SOURCES = tools/hciconfig.c tools/csr.h tools/csr.c +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hciconfig_LDADD = lib/libbluetooth-internal.la +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hcitool_SOURCES = tools/hcitool.c src/oui.h src/oui.c +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hcitool_LDADD = lib/libbluetooth-internal.la $(UDEV_LIBS) +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hcidump_SOURCES = tools/hcidump.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/parser.h tools/parser/parser.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/lmp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/hci.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/l2cap.h tools/parser/l2cap.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/amp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/smp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/att.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/sdp.h tools/parser/sdp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/rfcomm.h tools/parser/rfcomm.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/bnep.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/cmtp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/hidp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/hcrp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/avdtp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/avctp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/avrcp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/sap.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/obex.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/capi.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/ppp.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/tcpip.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/ericsson.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/csr.c \ +@DEPRECATED_TRUE@@TOOLS_TRUE@ tools/parser/bpa.c + +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_sdptool_SOURCES = tools/sdptool.c src/sdp-xml.h src/sdp-xml.c +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_sdptool_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_ciptool_LDADD = lib/libbluetooth-internal.la +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_hcidump_LDADD = lib/libbluetooth-internal.la +@DEPRECATED_TRUE@@TOOLS_TRUE@tools_rfcomm_LDADD = lib/libbluetooth-internal.la +@HID2HCI_TRUE@udevdir = $(UDEV_DIR) +@HID2HCI_TRUE@tools_hid2hci_LDADD = $(UDEV_LIBS) +@READLINE_TRUE@tools_obex_client_tool_SOURCES = $(gobex_sources) $(btio_sources) \ +@READLINE_TRUE@ tools/obex-client-tool.c + +@READLINE_TRUE@tools_obex_client_tool_LDADD = lib/libbluetooth-internal.la \ +@READLINE_TRUE@ $(GLIB_LIBS) -lreadline + +@READLINE_TRUE@tools_obex_server_tool_SOURCES = $(gobex_sources) $(btio_sources) \ +@READLINE_TRUE@ tools/obex-server-tool.c + +@READLINE_TRUE@tools_obex_server_tool_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) +@READLINE_TRUE@tools_bluetooth_player_SOURCES = tools/bluetooth-player.c +@READLINE_TRUE@tools_bluetooth_player_LDADD = gdbus/libgdbus-internal.la \ +@READLINE_TRUE@ src/libshared-glib.la \ +@READLINE_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline + +@READLINE_TRUE@tools_obexctl_SOURCES = tools/obexctl.c +@READLINE_TRUE@tools_obexctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ +@READLINE_TRUE@ $(GLIB_LIBS) $(DBUS_LIBS) -lreadline + +@READLINE_TRUE@tools_btmgmt_SOURCES = tools/btmgmt.c src/uuid-helper.c client/display.c +@READLINE_TRUE@tools_btmgmt_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la \ +@READLINE_TRUE@ -lreadline + +@DEPRECATED_TRUE@@READLINE_TRUE@attrib_gatttool_SOURCES = attrib/gatttool.c attrib/att.c attrib/gatt.c \ +@DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gattrib.c btio/btio.c \ +@DEPRECATED_TRUE@@READLINE_TRUE@ attrib/gatttool.h attrib/interactive.c \ +@DEPRECATED_TRUE@@READLINE_TRUE@ attrib/utils.c src/log.c client/display.c \ +@DEPRECATED_TRUE@@READLINE_TRUE@ client/display.h + +@DEPRECATED_TRUE@@READLINE_TRUE@attrib_gatttool_LDADD = lib/libbluetooth-internal.la \ +@DEPRECATED_TRUE@@READLINE_TRUE@ src/libshared-glib.la $(GLIB_LIBS) -lreadline + +@CUPS_TRUE@cupsdir = $(libdir)/cups/backend +@CUPS_TRUE@profiles_cups_bluetooth_SOURCES = profiles/cups/main.c \ +@CUPS_TRUE@ profiles/cups/cups.h \ +@CUPS_TRUE@ profiles/cups/sdp.c \ +@CUPS_TRUE@ profiles/cups/spp.c \ +@CUPS_TRUE@ profiles/cups/hcrp.c + +@CUPS_TRUE@profiles_cups_bluetooth_LDADD = $(GLIB_LIBS) $(DBUS_LIBS) \ +@CUPS_TRUE@ lib/libbluetooth-internal.la \ +@CUPS_TRUE@ gdbus/libgdbus-internal.la + +@BTPCLIENT_TRUE@tools_btpclient_SOURCES = tools/btpclient.c src/shared/btp.c src/shared/btp.h +@BTPCLIENT_TRUE@tools_btpclient_LDADD = lib/libbluetooth-internal.la \ +@BTPCLIENT_TRUE@ src/libshared-ell.la $(ell_ldadd) + +@BTPCLIENT_TRUE@tools_btpclient_DEPENDENCIES = lib/libbluetooth-internal.la $(ell_dependencies) +@SYSTEMD_TRUE@systemduserunitdir = $(SYSTEMD_USERUNITDIR) +@SYSTEMD_TRUE@systemduserunit_DATA = obexd/src/obex.service +@SYSTEMD_TRUE@dbussessionbusdir = $(DBUS_SESSIONBUSDIR) +@SYSTEMD_TRUE@dbussessionbus_DATA = obexd/src/org.bluez.obex.service +@OBEX_TRUE@obex_plugindir = $(libdir)/obex/plugins +@OBEX_TRUE@obexd_builtin_modules = filesystem bluetooth \ +@OBEX_TRUE@ $(am__append_48) opp ftp irmc pbap mas mns +@OBEX_TRUE@obexd_builtin_sources = obexd/plugins/filesystem.c \ +@OBEX_TRUE@ obexd/plugins/filesystem.h \ +@OBEX_TRUE@ obexd/plugins/bluetooth.c $(am__append_49) \ +@OBEX_TRUE@ obexd/plugins/opp.c obexd/plugins/ftp.c \ +@OBEX_TRUE@ obexd/plugins/ftp.h obexd/plugins/irmc.c \ +@OBEX_TRUE@ obexd/plugins/pbap.c obexd/plugins/vcard.h \ +@OBEX_TRUE@ obexd/plugins/vcard.c obexd/plugins/phonebook.h \ +@OBEX_TRUE@ obexd/plugins/phonebook-dummy.c obexd/plugins/mas.c \ +@OBEX_TRUE@ obexd/src/map_ap.h obexd/plugins/messages.h \ +@OBEX_TRUE@ obexd/plugins/messages-dummy.c obexd/client/mns.c \ +@OBEX_TRUE@ obexd/src/map_ap.h obexd/client/map-event.h +@OBEX_TRUE@obexd_builtin_nodist = +@OBEX_TRUE@obexd_src_obexd_SOURCES = $(btio_sources) $(gobex_sources) \ +@OBEX_TRUE@ $(obexd_builtin_sources) \ +@OBEX_TRUE@ obexd/src/main.c obexd/src/obexd.h \ +@OBEX_TRUE@ obexd/src/plugin.h obexd/src/plugin.c \ +@OBEX_TRUE@ obexd/src/log.h obexd/src/log.c \ +@OBEX_TRUE@ obexd/src/manager.h obexd/src/manager.c \ +@OBEX_TRUE@ obexd/src/obex.h obexd/src/obex.c obexd/src/obex-priv.h \ +@OBEX_TRUE@ obexd/src/mimetype.h obexd/src/mimetype.c \ +@OBEX_TRUE@ obexd/src/service.h obexd/src/service.c \ +@OBEX_TRUE@ obexd/src/transport.h obexd/src/transport.c \ +@OBEX_TRUE@ obexd/src/server.h obexd/src/server.c \ +@OBEX_TRUE@ obexd/client/manager.h obexd/client/manager.c \ +@OBEX_TRUE@ obexd/client/session.h obexd/client/session.c \ +@OBEX_TRUE@ obexd/client/bluetooth.h obexd/client/bluetooth.c \ +@OBEX_TRUE@ obexd/client/sync.h obexd/client/sync.c \ +@OBEX_TRUE@ obexd/client/pbap.h obexd/client/pbap.c \ +@OBEX_TRUE@ obexd/client/ftp.h obexd/client/ftp.c \ +@OBEX_TRUE@ obexd/client/opp.h obexd/client/opp.c \ +@OBEX_TRUE@ obexd/client/map.h obexd/client/map.c \ +@OBEX_TRUE@ obexd/client/map-event.h obexd/client/map-event.c \ +@OBEX_TRUE@ obexd/client/transfer.h obexd/client/transfer.c \ +@OBEX_TRUE@ obexd/client/transport.h obexd/client/transport.c \ +@OBEX_TRUE@ obexd/client/driver.h obexd/client/driver.c \ +@OBEX_TRUE@ obexd/src/map_ap.h + +@OBEX_TRUE@obexd_src_obexd_LDADD = lib/libbluetooth-internal.la \ +@OBEX_TRUE@ gdbus/libgdbus-internal.la \ +@OBEX_TRUE@ $(ICAL_LIBS) $(DBUS_LIBS) $(GLIB_LIBS) -ldl + +@OBEX_TRUE@obexd_src_obexd_LDFLAGS = $(AM_LDFLAGS) -Wl,--export-dynamic +@OBEX_TRUE@obexd_src_obexd_CPPFLAGS = $(AM_CPPFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) \ +@OBEX_TRUE@ $(ICAL_CFLAGS) -DOBEX_PLUGIN_BUILTIN \ +@OBEX_TRUE@ -DPLUGINDIR=\""$(obex_plugindir)"\" \ +@OBEX_TRUE@ -D_FILE_OFFSET_BITS=64 \ +@OBEX_TRUE@ -I$(builddir)/lib -I$(builddir)/obexd/src + +@OBEX_TRUE@obexd_src_obexd_CFLAGS = $(AM_CFLAGS) -fPIC +obexd_src_obexd_SHORTNAME = obexd +obexd_builtin_files = obexd/src/builtin.h $(obexd_builtin_nodist) +nodist_obexd_src_obexd_SOURCES = $(obexd_builtin_files) +@ANDROID_TRUE@android_plugindir = $(abs_top_srcdir)/android/.libs +@ANDROID_TRUE@android_system_emulator_SOURCES = android/system-emulator.c +@ANDROID_TRUE@android_system_emulator_LDADD = src/libshared-mainloop.la +@ANDROID_TRUE@android_bluetoothd_snoop_SOURCES = android/bluetoothd-snoop.c src/log.c +@ANDROID_TRUE@android_bluetoothd_snoop_LDADD = src/libshared-mainloop.la $(GLIB_LIBS) +@ANDROID_TRUE@android_bluetoothd_SOURCES = android/main.c \ +@ANDROID_TRUE@ src/log.c \ +@ANDROID_TRUE@ android/hal-msg.h \ +@ANDROID_TRUE@ android/audio-msg.h \ +@ANDROID_TRUE@ android/sco-msg.h \ +@ANDROID_TRUE@ android/utils.h \ +@ANDROID_TRUE@ src/sdpd-database.c src/sdpd-server.c \ +@ANDROID_TRUE@ src/sdpd-service.c src/sdpd-request.c \ +@ANDROID_TRUE@ src/uuid-helper.h src/uuid-helper.c \ +@ANDROID_TRUE@ src/eir.h src/eir.c \ +@ANDROID_TRUE@ android/bluetooth.h android/bluetooth.c \ +@ANDROID_TRUE@ android/hidhost.h android/hidhost.c \ +@ANDROID_TRUE@ profiles/scanparam/scpp.h \ +@ANDROID_TRUE@ profiles/scanparam/scpp.c \ +@ANDROID_TRUE@ profiles/deviceinfo/dis.h \ +@ANDROID_TRUE@ profiles/deviceinfo/dis.c \ +@ANDROID_TRUE@ profiles/battery/bas.h profiles/battery/bas.c \ +@ANDROID_TRUE@ profiles/input/hog-lib.h \ +@ANDROID_TRUE@ profiles/input/hog-lib.c \ +@ANDROID_TRUE@ android/ipc-common.h \ +@ANDROID_TRUE@ android/ipc.h android/ipc.c \ +@ANDROID_TRUE@ android/avdtp.h android/avdtp.c \ +@ANDROID_TRUE@ android/a2dp.h android/a2dp.c \ +@ANDROID_TRUE@ android/a2dp-sink.h android/a2dp-sink.c \ +@ANDROID_TRUE@ android/avctp.h android/avctp.c \ +@ANDROID_TRUE@ android/avrcp.h android/avrcp.c \ +@ANDROID_TRUE@ android/avrcp-lib.h android/avrcp-lib.c \ +@ANDROID_TRUE@ android/socket.h android/socket.c \ +@ANDROID_TRUE@ android/sco.h android/sco.c \ +@ANDROID_TRUE@ android/pan.h android/pan.c \ +@ANDROID_TRUE@ android/handsfree.h android/handsfree.c \ +@ANDROID_TRUE@ android/handsfree-client.c android/handsfree-client.h \ +@ANDROID_TRUE@ android/gatt.h android/gatt.c \ +@ANDROID_TRUE@ android/health.h android/health.c \ +@ANDROID_TRUE@ profiles/health/mcap.h profiles/health/mcap.c \ +@ANDROID_TRUE@ android/map-client.h android/map-client.c \ +@ANDROID_TRUE@ attrib/att.c attrib/att.h \ +@ANDROID_TRUE@ attrib/gatt.c attrib/gatt.h \ +@ANDROID_TRUE@ attrib/gattrib.c attrib/gattrib.h \ +@ANDROID_TRUE@ btio/btio.h btio/btio.c \ +@ANDROID_TRUE@ src/sdp-client.h src/sdp-client.c \ +@ANDROID_TRUE@ profiles/network/bnep.h profiles/network/bnep.c + +@ANDROID_TRUE@android_bluetoothd_LDADD = lib/libbluetooth-internal.la \ +@ANDROID_TRUE@ src/libshared-glib.la $(GLIB_LIBS) + +@ANDROID_TRUE@android_bluetooth_default_la_SOURCES = android/hal.h android/hal-bluetooth.c \ +@ANDROID_TRUE@ android/hal-socket.c \ +@ANDROID_TRUE@ android/hal-hidhost.c \ +@ANDROID_TRUE@ android/hal-health.c \ +@ANDROID_TRUE@ android/hal-pan.c \ +@ANDROID_TRUE@ android/hal-a2dp.c \ +@ANDROID_TRUE@ android/hal-a2dp-sink.c \ +@ANDROID_TRUE@ android/hal-avrcp.c \ +@ANDROID_TRUE@ android/hal-avrcp-ctrl.c \ +@ANDROID_TRUE@ android/hal-handsfree.c \ +@ANDROID_TRUE@ android/hal-handsfree-client.c \ +@ANDROID_TRUE@ android/hal-gatt.c \ +@ANDROID_TRUE@ android/hal-map-client.c \ +@ANDROID_TRUE@ android/hardware/bluetooth.h \ +@ANDROID_TRUE@ android/hardware/bt_av.h \ +@ANDROID_TRUE@ android/hardware/bt_gatt.h \ +@ANDROID_TRUE@ android/hardware/bt_gatt_client.h \ +@ANDROID_TRUE@ android/hardware/bt_gatt_server.h \ +@ANDROID_TRUE@ android/hardware/bt_gatt_types.h \ +@ANDROID_TRUE@ android/hardware/bt_hf.h \ +@ANDROID_TRUE@ android/hardware/bt_hh.h \ +@ANDROID_TRUE@ android/hardware/bt_hl.h \ +@ANDROID_TRUE@ android/hardware/bt_pan.h \ +@ANDROID_TRUE@ android/hardware/bt_rc.h \ +@ANDROID_TRUE@ android/hardware/bt_sock.h \ +@ANDROID_TRUE@ android/hardware/bt_hf_client.h \ +@ANDROID_TRUE@ android/hardware/bt_mce.h \ +@ANDROID_TRUE@ android/hardware/hardware.h \ +@ANDROID_TRUE@ android/cutils/properties.h \ +@ANDROID_TRUE@ android/ipc-common.h \ +@ANDROID_TRUE@ android/hal-log.h \ +@ANDROID_TRUE@ android/hal-ipc.h android/hal-ipc.c \ +@ANDROID_TRUE@ android/hal-utils.h android/hal-utils.c + +@ANDROID_TRUE@android_bluetooth_default_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/android +@ANDROID_TRUE@android_bluetooth_default_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ +@ANDROID_TRUE@ -no-undefined + +@ANDROID_TRUE@android_avdtptest_SOURCES = android/avdtptest.c \ +@ANDROID_TRUE@ src/log.h src/log.c \ +@ANDROID_TRUE@ btio/btio.h btio/btio.c \ +@ANDROID_TRUE@ src/shared/util.h src/shared/util.c \ +@ANDROID_TRUE@ src/shared/queue.h src/shared/queue.c \ +@ANDROID_TRUE@ src/shared/log.h src/shared/log.c \ +@ANDROID_TRUE@ android/avdtp.h android/avdtp.c + +@ANDROID_TRUE@android_avdtptest_CFLAGS = $(AM_CFLAGS) +@ANDROID_TRUE@android_avdtptest_LDADD = lib/libbluetooth-internal.la $(GLIB_LIBS) +@ANDROID_TRUE@android_haltest_SOURCES = android/client/haltest.c \ +@ANDROID_TRUE@ android/client/pollhandler.h \ +@ANDROID_TRUE@ android/client/pollhandler.c \ +@ANDROID_TRUE@ android/client/terminal.h \ +@ANDROID_TRUE@ android/client/terminal.c \ +@ANDROID_TRUE@ android/client/history.h \ +@ANDROID_TRUE@ android/client/history.c \ +@ANDROID_TRUE@ android/client/tabcompletion.c \ +@ANDROID_TRUE@ android/client/if-main.h \ +@ANDROID_TRUE@ android/client/if-av.c \ +@ANDROID_TRUE@ android/client/if-av-sink.c \ +@ANDROID_TRUE@ android/client/if-rc.c \ +@ANDROID_TRUE@ android/client/if-rc-ctrl.c \ +@ANDROID_TRUE@ android/client/if-bt.c \ +@ANDROID_TRUE@ android/client/if-gatt.c \ +@ANDROID_TRUE@ android/client/if-hf.c \ +@ANDROID_TRUE@ android/client/if-hf-client.c \ +@ANDROID_TRUE@ android/client/if-hh.c \ +@ANDROID_TRUE@ android/client/if-pan.c \ +@ANDROID_TRUE@ android/client/if-hl.c \ +@ANDROID_TRUE@ android/client/if-sock.c \ +@ANDROID_TRUE@ android/client/if-audio.c \ +@ANDROID_TRUE@ android/client/if-sco.c \ +@ANDROID_TRUE@ android/client/if-mce.c \ +@ANDROID_TRUE@ android/hardware/hardware.c \ +@ANDROID_TRUE@ android/hal-utils.h android/hal-utils.c + +@ANDROID_TRUE@android_haltest_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/android \ +@ANDROID_TRUE@ -DPLUGINDIR=\""$(android_plugindir)"\" + +@ANDROID_TRUE@android_haltest_LDFLAGS = $(AM_LDFLAGS) -pthread +@ANDROID_TRUE@android_haltest_LDADD = -ldl -lm +@ANDROID_TRUE@android_android_tester_SOURCES = emulator/hciemu.h emulator/hciemu.c \ +@ANDROID_TRUE@ emulator/btdev.h emulator/btdev.c \ +@ANDROID_TRUE@ emulator/bthost.h emulator/bthost.c \ +@ANDROID_TRUE@ emulator/smp.c \ +@ANDROID_TRUE@ monitor/rfcomm.h \ +@ANDROID_TRUE@ android/hardware/hardware.c \ +@ANDROID_TRUE@ android/tester-bluetooth.c \ +@ANDROID_TRUE@ android/tester-socket.c \ +@ANDROID_TRUE@ android/tester-hidhost.c \ +@ANDROID_TRUE@ android/tester-pan.c \ +@ANDROID_TRUE@ android/tester-hdp.c \ +@ANDROID_TRUE@ android/tester-a2dp.c \ +@ANDROID_TRUE@ android/tester-avrcp.c \ +@ANDROID_TRUE@ android/tester-gatt.c \ +@ANDROID_TRUE@ android/tester-map-client.c \ +@ANDROID_TRUE@ android/tester-main.h android/tester-main.c + +@ANDROID_TRUE@android_android_tester_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/android \ +@ANDROID_TRUE@ -DPLUGINDIR=\""$(android_plugindir)"\" + +@ANDROID_TRUE@android_android_tester_LDADD = lib/libbluetooth-internal.la \ +@ANDROID_TRUE@ src/libshared-glib.la $(GLIB_LIBS) -ldl + +@ANDROID_TRUE@android_android_tester_LDFLAGS = $(AM_LDFLAGS) -pthread +@ANDROID_TRUE@android_ipc_tester_SOURCES = emulator/hciemu.h emulator/hciemu.c \ +@ANDROID_TRUE@ emulator/btdev.h emulator/btdev.c \ +@ANDROID_TRUE@ emulator/bthost.h emulator/bthost.c \ +@ANDROID_TRUE@ emulator/smp.c \ +@ANDROID_TRUE@ android/hal-utils.h android/hal-utils.c \ +@ANDROID_TRUE@ android/ipc-common.h android/ipc-tester.c + +@ANDROID_TRUE@android_ipc_tester_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/android +@ANDROID_TRUE@android_ipc_tester_LDADD = lib/libbluetooth-internal.la \ +@ANDROID_TRUE@ src/libshared-glib.la $(GLIB_LIBS) + +@ANDROID_TRUE@android_audio_a2dp_default_la_SOURCES = android/audio-msg.h \ +@ANDROID_TRUE@ android/hal-msg.h \ +@ANDROID_TRUE@ android/hal-audio.h \ +@ANDROID_TRUE@ android/hal-audio.c \ +@ANDROID_TRUE@ android/hal-audio-sbc.c \ +@ANDROID_TRUE@ android/hal-audio-aptx.c \ +@ANDROID_TRUE@ android/hardware/audio.h \ +@ANDROID_TRUE@ android/hardware/audio_effect.h \ +@ANDROID_TRUE@ android/hardware/hardware.h \ +@ANDROID_TRUE@ android/system/audio.h + +@ANDROID_TRUE@android_audio_a2dp_default_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/android \ +@ANDROID_TRUE@ $(SBC_CFLAGS) + +@ANDROID_TRUE@android_audio_a2dp_default_la_LIBADD = $(SBC_LIBS) -lrt +@ANDROID_TRUE@android_audio_a2dp_default_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ +@ANDROID_TRUE@ -no-undefined -pthread + +@ANDROID_TRUE@android_audio_sco_default_la_SOURCES = android/hal-log.h \ +@ANDROID_TRUE@ android/sco-msg.h \ +@ANDROID_TRUE@ android/hal-sco.c \ +@ANDROID_TRUE@ android/hardware/audio.h \ +@ANDROID_TRUE@ android/hardware/audio_effect.h \ +@ANDROID_TRUE@ android/hardware/hardware.h \ +@ANDROID_TRUE@ android/audio_utils/resampler.c \ +@ANDROID_TRUE@ android/audio_utils/resampler.h \ +@ANDROID_TRUE@ android/system/audio.h + +@ANDROID_TRUE@android_audio_sco_default_la_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/android +@ANDROID_TRUE@android_audio_sco_default_la_LIBADD = $(SPEEXDSP_LIBS) -lrt +@ANDROID_TRUE@android_audio_sco_default_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ +@ANDROID_TRUE@ -no-undefined + +@ANDROID_TRUE@android_test_ipc_SOURCES = android/test-ipc.c \ +@ANDROID_TRUE@ src/log.h src/log.c \ +@ANDROID_TRUE@ android/ipc-common.h \ +@ANDROID_TRUE@ android/ipc.c android/ipc.h + +@ANDROID_TRUE@android_test_ipc_LDADD = src/libshared-glib.la $(GLIB_LIBS) +@MESH_TRUE@mesh_sources = mesh/mesh.h mesh/mesh.c \ +@MESH_TRUE@ mesh/net-keys.h mesh/net-keys.c \ +@MESH_TRUE@ mesh/mesh-io.h mesh/mesh-io.c \ +@MESH_TRUE@ mesh/mesh-mgmt.c mesh/mesh-mgmt.h \ +@MESH_TRUE@ mesh/error.h mesh/mesh-io-api.h \ +@MESH_TRUE@ mesh/mesh-io-generic.h \ +@MESH_TRUE@ mesh/mesh-io-generic.c \ +@MESH_TRUE@ mesh/net.h mesh/net.c \ +@MESH_TRUE@ mesh/crypto.h mesh/crypto.c \ +@MESH_TRUE@ mesh/friend.h mesh/friend.c \ +@MESH_TRUE@ mesh/appkey.h mesh/appkey.c \ +@MESH_TRUE@ mesh/node.h mesh/node.c \ +@MESH_TRUE@ mesh/provision.h mesh/prov.h \ +@MESH_TRUE@ mesh/model.h mesh/model.c \ +@MESH_TRUE@ mesh/cfgmod.h mesh/cfgmod-server.c \ +@MESH_TRUE@ mesh/mesh-config.h mesh/mesh-config-json.c \ +@MESH_TRUE@ mesh/util.h mesh/util.c \ +@MESH_TRUE@ mesh/dbus.h mesh/dbus.c \ +@MESH_TRUE@ mesh/agent.h mesh/agent.c \ +@MESH_TRUE@ mesh/prov-acceptor.c mesh/prov-initiator.c \ +@MESH_TRUE@ mesh/manager.h mesh/manager.c \ +@MESH_TRUE@ mesh/pb-adv.h mesh/pb-adv.c \ +@MESH_TRUE@ mesh/keyring.h mesh/keyring.c \ +@MESH_TRUE@ mesh/mesh-defs.h + +@MESH_TRUE@mesh_bluetooth_meshd_SOURCES = $(mesh_sources) mesh/main.c +@MESH_TRUE@mesh_bluetooth_meshd_LDADD = src/libshared-ell.la $(ell_ldadd) -ljson-c +@MESH_TRUE@mesh_bluetooth_meshd_DEPENDENCIES = $(ell_dependencies) src/libshared-ell.la \ +@MESH_TRUE@ mesh/bluetooth-mesh.service + +@HID2HCI_TRUE@rulesdir = $(UDEV_DIR)/rules.d +@HID2HCI_TRUE@rules_DATA = tools/97-hid2hci.rules +@TEST_TRUE@testdir = $(pkglibdir)/test +@TEST_TRUE@test_SCRIPTS = $(test_scripts) +unit_test_eir_SOURCES = unit/test-eir.c src/eir.c src/uuid-helper.c +unit_test_eir_LDADD = src/libshared-glib.la lib/libbluetooth-internal.la \ + $(GLIB_LIBS) + +unit_test_uuid_SOURCES = unit/test-uuid.c +unit_test_uuid_LDADD = src/libshared-glib.la lib/libbluetooth-internal.la \ + $(GLIB_LIBS) + +unit_test_textfile_SOURCES = unit/test-textfile.c src/textfile.h src/textfile.c +unit_test_textfile_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_crc_SOURCES = unit/test-crc.c monitor/crc.h monitor/crc.c +unit_test_crc_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_crypto_SOURCES = unit/test-crypto.c +unit_test_crypto_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_ecc_SOURCES = unit/test-ecc.c +unit_test_ecc_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_ringbuf_SOURCES = unit/test-ringbuf.c +unit_test_ringbuf_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_queue_SOURCES = unit/test-queue.c +unit_test_queue_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_mgmt_SOURCES = unit/test-mgmt.c +unit_test_mgmt_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_uhid_SOURCES = unit/test-uhid.c +unit_test_uhid_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_sdp_SOURCES = unit/test-sdp.c \ + src/sdpd.h src/sdpd-database.c \ + src/log.h src/log.c \ + src/sdpd-service.c src/sdpd-request.c + +unit_test_sdp_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la $(GLIB_LIBS) + +unit_test_avdtp_SOURCES = unit/test-avdtp.c \ + src/log.h src/log.c \ + android/avdtp.c android/avdtp.h + +unit_test_avdtp_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_avctp_SOURCES = unit/test-avctp.c \ + src/log.h src/log.c \ + android/avctp.c android/avctp.h + +unit_test_avctp_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_avrcp_SOURCES = unit/test-avrcp.c \ + src/log.h src/log.c \ + 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_test_hfp_SOURCES = unit/test-hfp.c +unit_test_hfp_LDADD = src/libshared-glib.la $(GLIB_LIBS) +unit_test_gdbus_client_SOURCES = unit/test-gdbus-client.c +unit_test_gdbus_client_LDADD = gdbus/libgdbus-internal.la \ + src/libshared-glib.la $(GLIB_LIBS) $(DBUS_LIBS) + +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_test_lib_SOURCES = unit/test-lib.c +unit_test_lib_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(GLIB_LIBS) + +unit_test_gatt_SOURCES = unit/test-gatt.c +unit_test_gatt_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(GLIB_LIBS) + +unit_test_hog_SOURCES = unit/test-hog.c \ + $(btio_sources) \ + profiles/input/hog-lib.h profiles/input/hog-lib.c \ + profiles/scanparam/scpp.h profiles/scanparam/scpp.c \ + profiles/battery/bas.h profiles/battery/bas.c \ + profiles/deviceinfo/dis.h profiles/deviceinfo/dis.c \ + src/log.h src/log.c \ + attrib/att.h attrib/att.c \ + attrib/gatt.h attrib/gatt.c \ + attrib/gattrib.h attrib/gattrib.c + +unit_test_hog_LDADD = src/libshared-glib.la \ + lib/libbluetooth-internal.la $(GLIB_LIBS) + +unit_test_gattrib_SOURCES = unit/test-gattrib.c attrib/gattrib.c \ + $(btio_sources) src/log.h src/log.c + +unit_test_gattrib_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la \ + $(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt + +@MIDI_TRUE@unit_test_midi_CPPFLAGS = $(AM_CPPFLAGS) $(ALSA_CFLAGS) -DMIDI_TEST +@MIDI_TRUE@unit_test_midi_SOURCES = unit/test-midi.c \ +@MIDI_TRUE@ profiles/midi/libmidi.h \ +@MIDI_TRUE@ profiles/midi/libmidi.c + +@MIDI_TRUE@unit_test_midi_LDADD = src/libshared-glib.la \ +@MIDI_TRUE@ $(GLIB_LIBS) $(ALSA_LIBS) + +@MESH_TRUE@unit_test_mesh_crypto_CPPFLAGS = $(ell_cflags) +@MESH_TRUE@unit_test_mesh_crypto_SOURCES = unit/test-mesh-crypto.c \ +@MESH_TRUE@ mesh/crypto.h ell/internal ell/ell.h \ +@MESH_TRUE@ $(ell_sources) + +@MESH_TRUE@unit_test_mesh_crypto_LDADD = src/libshared-ell.la \ +@MESH_TRUE@ $(ell_ldadd) + +AM_TESTS_ENVIRONMENT = MALLOC_CHECK_=3 MALLOC_PERTURB_=69 \ + $(am__append_64) +@VALGRIND_TRUE@LOG_COMPILER = valgrind --error-exitcode=1 --num-callers=30 +@VALGRIND_TRUE@LOG_FLAGS = --trace-children=yes --leak-check=full --show-reachable=no \ +@VALGRIND_TRUE@ --suppressions=$(srcdir)/tools/valgrind.supp --quiet + +pkgconfigdir = $(libdir)/pkgconfig +@LIBRARY_TRUE@pkgconfig_DATA = lib/bluez.pc +manual_pages = doc/btmon.1 +DISTCHECK_CONFIGURE_FLAGS = --disable-datafiles --enable-library \ + --enable-health \ + --enable-midi \ + --enable-manpages \ + --enable-android \ + --enable-mesh \ + --enable-btpclient \ + --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,@pkglibexecdir\@,$(pkglibexecdir),g' \ + < $< > $@ + +all: $(BUILT_SOURCES) config.h + $(MAKE) $(AM_MAKEFLAGS) all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .log .o .obj .test .test$(EXEEXT) .trs +am--refresh: Makefile + @: +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(srcdir)/Makefile.plugins $(srcdir)/Makefile.tools $(srcdir)/Makefile.obexd $(srcdir)/android/Makefile.am $(srcdir)/Makefile.mesh $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + echo ' cd $(srcdir) && $(AUTOMAKE) --foreign'; \ + $(am__cd) $(srcdir) && $(AUTOMAKE) --foreign \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + echo ' $(SHELL) ./config.status'; \ + $(SHELL) ./config.status;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles);; \ + esac; +$(srcdir)/Makefile.plugins $(srcdir)/Makefile.tools $(srcdir)/Makefile.obexd $(srcdir)/android/Makefile.am $(srcdir)/Makefile.mesh $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + $(SHELL) ./config.status --recheck + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + $(am__cd) $(srcdir) && $(AUTOCONF) +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + $(am__cd) $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS) +$(am__aclocal_m4_deps): + +config.h: stamp-h1 + @test -f $@ || rm -f stamp-h1 + @test -f $@ || $(MAKE) $(AM_MAKEFLAGS) stamp-h1 + +stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status + @rm -f stamp-h1 + cd $(top_builddir) && $(SHELL) ./config.status config.h +$(srcdir)/config.h.in: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + ($(am__cd) $(top_srcdir) && $(AUTOHEADER)) + rm -f stamp-h1 + touch $@ + +distclean-hdr: + -rm -f config.h stamp-h1 +src/bluetoothd.8: $(top_builddir)/config.status $(top_srcdir)/src/bluetoothd.8.in + cd $(top_builddir) && $(SHELL) ./config.status $@ +lib/bluez.pc: $(top_builddir)/config.status $(top_srcdir)/lib/bluez.pc.in + cd $(top_builddir) && $(SHELL) ./config.status $@ +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-cupsPROGRAMS: $(cups_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(cups_PROGRAMS)'; test -n "$(cupsdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(cupsdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(cupsdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(cupsdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(cupsdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-cupsPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(cups_PROGRAMS)'; test -n "$(cupsdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(cupsdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(cupsdir)" && rm -f $$files + +clean-cupsPROGRAMS: + @list='$(cups_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-pkglibexecPROGRAMS: $(pkglibexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkglibexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkglibexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(pkglibexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(pkglibexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-pkglibexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(pkglibexec_PROGRAMS)'; test -n "$(pkglibexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(pkglibexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(pkglibexecdir)" && rm -f $$files + +clean-pkglibexecPROGRAMS: + @list='$(pkglibexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list +install-udevPROGRAMS: $(udev_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(udev_PROGRAMS)'; test -n "$(udevdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(udevdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(udevdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(udevdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(udevdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-udevPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(udev_PROGRAMS)'; test -n "$(udevdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(udevdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(udevdir)" && rm -f $$files + +clean-udevPROGRAMS: + @list='$(udev_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +clean-noinstLIBRARIES: + -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES) + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +install-pluginLTLIBRARIES: $(plugin_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(plugindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(plugindir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(plugindir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(plugindir)"; \ + } + +uninstall-pluginLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(plugin_LTLIBRARIES)'; test -n "$(plugindir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(plugindir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(plugindir)/$$f"; \ + done + +clean-pluginLTLIBRARIES: + -test -z "$(plugin_LTLIBRARIES)" || rm -f $(plugin_LTLIBRARIES) + @list='$(plugin_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } +android/$(am__dirstamp): + @$(MKDIR_P) android + @: > android/$(am__dirstamp) +android/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) android/$(DEPDIR) + @: > android/$(DEPDIR)/$(am__dirstamp) +android/audio_a2dp_default_la-hal-audio.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/audio_a2dp_default_la-hal-audio-sbc.lo: \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/audio_a2dp_default_la-hal-audio-aptx.lo: \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) + +android/audio.a2dp.default.la: $(android_audio_a2dp_default_la_OBJECTS) $(android_audio_a2dp_default_la_DEPENDENCIES) $(EXTRA_android_audio_a2dp_default_la_DEPENDENCIES) android/$(am__dirstamp) + $(AM_V_CCLD)$(android_audio_a2dp_default_la_LINK) $(am_android_audio_a2dp_default_la_rpath) $(android_audio_a2dp_default_la_OBJECTS) $(android_audio_a2dp_default_la_LIBADD) $(LIBS) +android/audio_sco_default_la-hal-sco.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/audio_utils/$(am__dirstamp): + @$(MKDIR_P) android/audio_utils + @: > android/audio_utils/$(am__dirstamp) +android/audio_utils/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) android/audio_utils/$(DEPDIR) + @: > android/audio_utils/$(DEPDIR)/$(am__dirstamp) +android/audio_utils/audio_sco_default_la-resampler.lo: \ + android/audio_utils/$(am__dirstamp) \ + android/audio_utils/$(DEPDIR)/$(am__dirstamp) + +android/audio.sco.default.la: $(android_audio_sco_default_la_OBJECTS) $(android_audio_sco_default_la_DEPENDENCIES) $(EXTRA_android_audio_sco_default_la_DEPENDENCIES) android/$(am__dirstamp) + $(AM_V_CCLD)$(android_audio_sco_default_la_LINK) $(am_android_audio_sco_default_la_rpath) $(android_audio_sco_default_la_OBJECTS) $(android_audio_sco_default_la_LIBADD) $(LIBS) +android/bluetooth_default_la-hal-bluetooth.lo: \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-socket.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-hidhost.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-health.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-pan.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-a2dp.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-a2dp-sink.lo: \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-avrcp.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-avrcp-ctrl.lo: \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-handsfree.lo: \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-handsfree-client.lo: \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-gatt.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-map-client.lo: \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-ipc.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/bluetooth_default_la-hal-utils.lo: android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) + +android/bluetooth.default.la: $(android_bluetooth_default_la_OBJECTS) $(android_bluetooth_default_la_DEPENDENCIES) $(EXTRA_android_bluetooth_default_la_DEPENDENCIES) android/$(am__dirstamp) + $(AM_V_CCLD)$(android_bluetooth_default_la_LINK) $(am_android_bluetooth_default_la_rpath) $(android_bluetooth_default_la_OBJECTS) $(android_bluetooth_default_la_LIBADD) $(LIBS) +ell/$(am__dirstamp): + @$(MKDIR_P) ell + @: > ell/$(am__dirstamp) +ell/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) ell/$(DEPDIR) + @: > ell/$(DEPDIR)/$(am__dirstamp) +ell/util.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/log.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/queue.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/hashmap.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/random.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/signal.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/timeout.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/io.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/idle.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/main.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/strv.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/string.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/cipher.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/checksum.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/utf8.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/dbus.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/dbus-message.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/dbus-util.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/dbus-service.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/dbus-client.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/dbus-name-cache.lo: ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/dbus-filter.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/gvariant-util.lo: ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/siphash.lo: ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) + +ell/libell-internal.la: $(ell_libell_internal_la_OBJECTS) $(ell_libell_internal_la_DEPENDENCIES) $(EXTRA_ell_libell_internal_la_DEPENDENCIES) ell/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(am_ell_libell_internal_la_rpath) $(ell_libell_internal_la_OBJECTS) $(ell_libell_internal_la_LIBADD) $(LIBS) +gdbus/$(am__dirstamp): + @$(MKDIR_P) gdbus + @: > gdbus/$(am__dirstamp) +gdbus/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) gdbus/$(DEPDIR) + @: > gdbus/$(DEPDIR)/$(am__dirstamp) +gdbus/mainloop.lo: gdbus/$(am__dirstamp) \ + gdbus/$(DEPDIR)/$(am__dirstamp) +gdbus/watch.lo: gdbus/$(am__dirstamp) gdbus/$(DEPDIR)/$(am__dirstamp) +gdbus/object.lo: gdbus/$(am__dirstamp) gdbus/$(DEPDIR)/$(am__dirstamp) +gdbus/client.lo: gdbus/$(am__dirstamp) gdbus/$(DEPDIR)/$(am__dirstamp) +gdbus/polkit.lo: gdbus/$(am__dirstamp) gdbus/$(DEPDIR)/$(am__dirstamp) + +gdbus/libgdbus-internal.la: $(gdbus_libgdbus_internal_la_OBJECTS) $(gdbus_libgdbus_internal_la_DEPENDENCIES) $(EXTRA_gdbus_libgdbus_internal_la_DEPENDENCIES) gdbus/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(gdbus_libgdbus_internal_la_OBJECTS) $(gdbus_libgdbus_internal_la_LIBADD) $(LIBS) +lib/$(am__dirstamp): + @$(MKDIR_P) lib + @: > lib/$(am__dirstamp) +lib/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) lib/$(DEPDIR) + @: > lib/$(DEPDIR)/$(am__dirstamp) +lib/bluetooth.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) +lib/hci.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) +lib/sdp.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) +lib/uuid.lo: lib/$(am__dirstamp) lib/$(DEPDIR)/$(am__dirstamp) + +lib/libbluetooth-internal.la: $(lib_libbluetooth_internal_la_OBJECTS) $(lib_libbluetooth_internal_la_DEPENDENCIES) $(EXTRA_lib_libbluetooth_internal_la_DEPENDENCIES) lib/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(lib_libbluetooth_internal_la_OBJECTS) $(lib_libbluetooth_internal_la_LIBADD) $(LIBS) + +lib/libbluetooth.la: $(lib_libbluetooth_la_OBJECTS) $(lib_libbluetooth_la_DEPENDENCIES) $(EXTRA_lib_libbluetooth_la_DEPENDENCIES) lib/$(am__dirstamp) + $(AM_V_CCLD)$(lib_libbluetooth_la_LINK) $(am_lib_libbluetooth_la_rpath) $(lib_libbluetooth_la_OBJECTS) $(lib_libbluetooth_la_LIBADD) $(LIBS) +plugins/$(am__dirstamp): + @$(MKDIR_P) plugins + @: > plugins/$(am__dirstamp) +plugins/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) plugins/$(DEPDIR) + @: > plugins/$(DEPDIR)/$(am__dirstamp) +plugins/external_dummy_la-external-dummy.lo: plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) + +plugins/external-dummy.la: $(plugins_external_dummy_la_OBJECTS) $(plugins_external_dummy_la_DEPENDENCIES) $(EXTRA_plugins_external_dummy_la_DEPENDENCIES) plugins/$(am__dirstamp) + $(AM_V_CCLD)$(plugins_external_dummy_la_LINK) $(am_plugins_external_dummy_la_rpath) $(plugins_external_dummy_la_OBJECTS) $(plugins_external_dummy_la_LIBADD) $(LIBS) +plugins/sixaxis_la-sixaxis.lo: plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) + +plugins/sixaxis.la: $(plugins_sixaxis_la_OBJECTS) $(plugins_sixaxis_la_DEPENDENCIES) $(EXTRA_plugins_sixaxis_la_DEPENDENCIES) plugins/$(am__dirstamp) + $(AM_V_CCLD)$(plugins_sixaxis_la_LINK) $(am_plugins_sixaxis_la_rpath) $(plugins_sixaxis_la_OBJECTS) $(plugins_sixaxis_la_LIBADD) $(LIBS) +src/shared/$(am__dirstamp): + @$(MKDIR_P) src/shared + @: > src/shared/$(am__dirstamp) +src/shared/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/shared/$(DEPDIR) + @: > src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/queue.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/util.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/mgmt.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/crypto.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/ecc.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/ringbuf.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/tester.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/hci.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/hci-crypto.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/hfp.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/uhid.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/pcap.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/btsnoop.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/ad.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/att.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/gatt-helpers.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/gatt-client.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/gatt-server.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/gatt-db.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/gap.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/log.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/shell.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/io-ell.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/timeout-ell.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/mainloop-ell.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/$(am__dirstamp): + @$(MKDIR_P) src + @: > src/$(am__dirstamp) + +src/libshared-ell.la: $(src_libshared_ell_la_OBJECTS) $(src_libshared_ell_la_DEPENDENCIES) $(EXTRA_src_libshared_ell_la_DEPENDENCIES) src/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(am_src_libshared_ell_la_rpath) $(src_libshared_ell_la_OBJECTS) $(src_libshared_ell_la_LIBADD) $(LIBS) +src/shared/io-glib.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/timeout-glib.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/mainloop-glib.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/mainloop-notify.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) + +src/libshared-glib.la: $(src_libshared_glib_la_OBJECTS) $(src_libshared_glib_la_DEPENDENCIES) $(EXTRA_src_libshared_glib_la_DEPENDENCIES) src/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(src_libshared_glib_la_OBJECTS) $(src_libshared_glib_la_LIBADD) $(LIBS) +src/shared/io-mainloop.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/timeout-mainloop.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/mainloop.lo: src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) + +src/libshared-mainloop.la: $(src_libshared_mainloop_la_OBJECTS) $(src_libshared_mainloop_la_DEPENDENCIES) $(EXTRA_src_libshared_mainloop_la_DEPENDENCIES) src/$(am__dirstamp) + $(AM_V_CCLD)$(LINK) $(src_libshared_mainloop_la_OBJECTS) $(src_libshared_mainloop_la_LIBADD) $(LIBS) +emulator/$(am__dirstamp): + @$(MKDIR_P) emulator + @: > emulator/$(am__dirstamp) +emulator/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) emulator/$(DEPDIR) + @: > emulator/$(DEPDIR)/$(am__dirstamp) +emulator/android_android_tester-hciemu.$(OBJEXT): \ + emulator/$(am__dirstamp) emulator/$(DEPDIR)/$(am__dirstamp) +emulator/android_android_tester-btdev.$(OBJEXT): \ + emulator/$(am__dirstamp) emulator/$(DEPDIR)/$(am__dirstamp) +emulator/android_android_tester-bthost.$(OBJEXT): \ + emulator/$(am__dirstamp) emulator/$(DEPDIR)/$(am__dirstamp) +emulator/android_android_tester-smp.$(OBJEXT): \ + emulator/$(am__dirstamp) emulator/$(DEPDIR)/$(am__dirstamp) +android/hardware/$(am__dirstamp): + @$(MKDIR_P) android/hardware + @: > android/hardware/$(am__dirstamp) +android/hardware/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) android/hardware/$(DEPDIR) + @: > android/hardware/$(DEPDIR)/$(am__dirstamp) +android/hardware/android_tester-hardware.$(OBJEXT): \ + android/hardware/$(am__dirstamp) \ + android/hardware/$(DEPDIR)/$(am__dirstamp) +android/android_tester-tester-bluetooth.$(OBJEXT): \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/android_tester-tester-socket.$(OBJEXT): \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/android_tester-tester-hidhost.$(OBJEXT): \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/android_tester-tester-pan.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/android_tester-tester-hdp.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/android_tester-tester-a2dp.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/android_tester-tester-avrcp.$(OBJEXT): \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/android_tester-tester-gatt.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/android_tester-tester-map-client.$(OBJEXT): \ + android/$(am__dirstamp) android/$(DEPDIR)/$(am__dirstamp) +android/android_tester-tester-main.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) + +android/android-tester$(EXEEXT): $(android_android_tester_OBJECTS) $(android_android_tester_DEPENDENCIES) $(EXTRA_android_android_tester_DEPENDENCIES) android/$(am__dirstamp) + @rm -f android/android-tester$(EXEEXT) + $(AM_V_CCLD)$(android_android_tester_LINK) $(android_android_tester_OBJECTS) $(android_android_tester_LDADD) $(LIBS) +android/avdtptest-avdtptest.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +src/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) src/$(DEPDIR) + @: > src/$(DEPDIR)/$(am__dirstamp) +src/android_avdtptest-log.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +btio/$(am__dirstamp): + @$(MKDIR_P) btio + @: > btio/$(am__dirstamp) +btio/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) btio/$(DEPDIR) + @: > btio/$(DEPDIR)/$(am__dirstamp) +btio/android_avdtptest-btio.$(OBJEXT): btio/$(am__dirstamp) \ + btio/$(DEPDIR)/$(am__dirstamp) +src/shared/android_avdtptest-util.$(OBJEXT): \ + src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/android_avdtptest-queue.$(OBJEXT): \ + src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +src/shared/android_avdtptest-log.$(OBJEXT): \ + src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) +android/avdtptest-avdtp.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) + +android/avdtptest$(EXEEXT): $(android_avdtptest_OBJECTS) $(android_avdtptest_DEPENDENCIES) $(EXTRA_android_avdtptest_DEPENDENCIES) android/$(am__dirstamp) + @rm -f android/avdtptest$(EXEEXT) + $(AM_V_CCLD)$(android_avdtptest_LINK) $(android_avdtptest_OBJECTS) $(android_avdtptest_LDADD) $(LIBS) +android/main.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +src/log.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) +src/sdpd-database.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/sdpd-server.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/sdpd-service.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/sdpd-request.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/uuid-helper.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/eir.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) +android/bluetooth.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/hidhost.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +profiles/scanparam/$(am__dirstamp): + @$(MKDIR_P) profiles/scanparam + @: > profiles/scanparam/$(am__dirstamp) +profiles/scanparam/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/scanparam/$(DEPDIR) + @: > profiles/scanparam/$(DEPDIR)/$(am__dirstamp) +profiles/scanparam/scpp.$(OBJEXT): profiles/scanparam/$(am__dirstamp) \ + profiles/scanparam/$(DEPDIR)/$(am__dirstamp) +profiles/deviceinfo/$(am__dirstamp): + @$(MKDIR_P) profiles/deviceinfo + @: > profiles/deviceinfo/$(am__dirstamp) +profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/deviceinfo/$(DEPDIR) + @: > profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp) +profiles/deviceinfo/dis.$(OBJEXT): \ + profiles/deviceinfo/$(am__dirstamp) \ + profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp) +profiles/battery/$(am__dirstamp): + @$(MKDIR_P) profiles/battery + @: > profiles/battery/$(am__dirstamp) +profiles/battery/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/battery/$(DEPDIR) + @: > profiles/battery/$(DEPDIR)/$(am__dirstamp) +profiles/battery/bas.$(OBJEXT): profiles/battery/$(am__dirstamp) \ + profiles/battery/$(DEPDIR)/$(am__dirstamp) +profiles/input/$(am__dirstamp): + @$(MKDIR_P) profiles/input + @: > profiles/input/$(am__dirstamp) +profiles/input/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/input/$(DEPDIR) + @: > profiles/input/$(DEPDIR)/$(am__dirstamp) +profiles/input/hog-lib.$(OBJEXT): profiles/input/$(am__dirstamp) \ + profiles/input/$(DEPDIR)/$(am__dirstamp) +android/ipc.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/avdtp.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/a2dp.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/a2dp-sink.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/avctp.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/avrcp.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/avrcp-lib.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/socket.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/sco.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/pan.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/handsfree.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/handsfree-client.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/gatt.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/health.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +profiles/health/$(am__dirstamp): + @$(MKDIR_P) profiles/health + @: > profiles/health/$(am__dirstamp) +profiles/health/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/health/$(DEPDIR) + @: > profiles/health/$(DEPDIR)/$(am__dirstamp) +profiles/health/mcap.$(OBJEXT): profiles/health/$(am__dirstamp) \ + profiles/health/$(DEPDIR)/$(am__dirstamp) +android/map-client.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +attrib/$(am__dirstamp): + @$(MKDIR_P) attrib + @: > attrib/$(am__dirstamp) +attrib/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) attrib/$(DEPDIR) + @: > attrib/$(DEPDIR)/$(am__dirstamp) +attrib/att.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/gatt.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/gattrib.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +btio/btio.$(OBJEXT): btio/$(am__dirstamp) \ + btio/$(DEPDIR)/$(am__dirstamp) +src/sdp-client.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +profiles/network/$(am__dirstamp): + @$(MKDIR_P) profiles/network + @: > profiles/network/$(am__dirstamp) +profiles/network/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/network/$(DEPDIR) + @: > profiles/network/$(DEPDIR)/$(am__dirstamp) +profiles/network/bnep.$(OBJEXT): profiles/network/$(am__dirstamp) \ + profiles/network/$(DEPDIR)/$(am__dirstamp) + +android/bluetoothd$(EXEEXT): $(android_bluetoothd_OBJECTS) $(android_bluetoothd_DEPENDENCIES) $(EXTRA_android_bluetoothd_DEPENDENCIES) android/$(am__dirstamp) + @rm -f android/bluetoothd$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(android_bluetoothd_OBJECTS) $(android_bluetoothd_LDADD) $(LIBS) +android/bluetoothd-snoop.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) + +android/bluetoothd-snoop$(EXEEXT): $(android_bluetoothd_snoop_OBJECTS) $(android_bluetoothd_snoop_DEPENDENCIES) $(EXTRA_android_bluetoothd_snoop_DEPENDENCIES) android/$(am__dirstamp) + @rm -f android/bluetoothd-snoop$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(android_bluetoothd_snoop_OBJECTS) $(android_bluetoothd_snoop_LDADD) $(LIBS) +android/client/$(am__dirstamp): + @$(MKDIR_P) android/client + @: > android/client/$(am__dirstamp) +android/client/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) android/client/$(DEPDIR) + @: > android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-haltest.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-pollhandler.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-terminal.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-history.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-tabcompletion.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-av.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-av-sink.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-rc.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-rc-ctrl.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-bt.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-gatt.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-hf.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-hf-client.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-hh.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-pan.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-hl.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-sock.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-audio.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-sco.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/client/haltest-if-mce.$(OBJEXT): \ + android/client/$(am__dirstamp) \ + android/client/$(DEPDIR)/$(am__dirstamp) +android/hardware/haltest-hardware.$(OBJEXT): \ + android/hardware/$(am__dirstamp) \ + android/hardware/$(DEPDIR)/$(am__dirstamp) +android/haltest-hal-utils.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) + +android/haltest$(EXEEXT): $(android_haltest_OBJECTS) $(android_haltest_DEPENDENCIES) $(EXTRA_android_haltest_DEPENDENCIES) android/$(am__dirstamp) + @rm -f android/haltest$(EXEEXT) + $(AM_V_CCLD)$(android_haltest_LINK) $(android_haltest_OBJECTS) $(android_haltest_LDADD) $(LIBS) +emulator/android_ipc_tester-hciemu.$(OBJEXT): \ + emulator/$(am__dirstamp) emulator/$(DEPDIR)/$(am__dirstamp) +emulator/android_ipc_tester-btdev.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +emulator/android_ipc_tester-bthost.$(OBJEXT): \ + emulator/$(am__dirstamp) emulator/$(DEPDIR)/$(am__dirstamp) +emulator/android_ipc_tester-smp.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +android/ipc_tester-hal-utils.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) +android/ipc_tester-ipc-tester.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) + +android/ipc-tester$(EXEEXT): $(android_ipc_tester_OBJECTS) $(android_ipc_tester_DEPENDENCIES) $(EXTRA_android_ipc_tester_DEPENDENCIES) android/$(am__dirstamp) + @rm -f android/ipc-tester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(android_ipc_tester_OBJECTS) $(android_ipc_tester_LDADD) $(LIBS) +android/system-emulator.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) + +android/system-emulator$(EXEEXT): $(android_system_emulator_OBJECTS) $(android_system_emulator_DEPENDENCIES) $(EXTRA_android_system_emulator_DEPENDENCIES) android/$(am__dirstamp) + @rm -f android/system-emulator$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(android_system_emulator_OBJECTS) $(android_system_emulator_LDADD) $(LIBS) +android/test-ipc.$(OBJEXT): android/$(am__dirstamp) \ + android/$(DEPDIR)/$(am__dirstamp) + +android/test-ipc$(EXEEXT): $(android_test_ipc_OBJECTS) $(android_test_ipc_DEPENDENCIES) $(EXTRA_android_test_ipc_DEPENDENCIES) android/$(am__dirstamp) + @rm -f android/test-ipc$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(android_test_ipc_OBJECTS) $(android_test_ipc_LDADD) $(LIBS) +attrib/gatttool.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/interactive.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/utils.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +client/$(am__dirstamp): + @$(MKDIR_P) client + @: > client/$(am__dirstamp) +client/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) client/$(DEPDIR) + @: > client/$(DEPDIR)/$(am__dirstamp) +client/display.$(OBJEXT): client/$(am__dirstamp) \ + client/$(DEPDIR)/$(am__dirstamp) + +attrib/gatttool$(EXEEXT): $(attrib_gatttool_OBJECTS) $(attrib_gatttool_DEPENDENCIES) $(EXTRA_attrib_gatttool_DEPENDENCIES) attrib/$(am__dirstamp) + @rm -f attrib/gatttool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(attrib_gatttool_OBJECTS) $(attrib_gatttool_LDADD) $(LIBS) +client/main.$(OBJEXT): client/$(am__dirstamp) \ + client/$(DEPDIR)/$(am__dirstamp) +client/agent.$(OBJEXT): client/$(am__dirstamp) \ + client/$(DEPDIR)/$(am__dirstamp) +client/advertising.$(OBJEXT): client/$(am__dirstamp) \ + client/$(DEPDIR)/$(am__dirstamp) +client/gatt.$(OBJEXT): client/$(am__dirstamp) \ + client/$(DEPDIR)/$(am__dirstamp) + +client/bluetoothctl$(EXEEXT): $(client_bluetoothctl_OBJECTS) $(client_bluetoothctl_DEPENDENCIES) $(EXTRA_client_bluetoothctl_DEPENDENCIES) client/$(am__dirstamp) + @rm -f client/bluetoothctl$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(client_bluetoothctl_OBJECTS) $(client_bluetoothctl_LDADD) $(LIBS) +emulator/b1ee.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) + +emulator/b1ee$(EXEEXT): $(emulator_b1ee_OBJECTS) $(emulator_b1ee_DEPENDENCIES) $(EXTRA_emulator_b1ee_DEPENDENCIES) emulator/$(am__dirstamp) + @rm -f emulator/b1ee$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(emulator_b1ee_OBJECTS) $(emulator_b1ee_LDADD) $(LIBS) +emulator/main.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +emulator/serial.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +emulator/server.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +emulator/vhci.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +emulator/btdev.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +emulator/bthost.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +emulator/smp.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +emulator/phy.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +emulator/amp.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) +emulator/le.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) + +emulator/btvirt$(EXEEXT): $(emulator_btvirt_OBJECTS) $(emulator_btvirt_DEPENDENCIES) $(EXTRA_emulator_btvirt_DEPENDENCIES) emulator/$(am__dirstamp) + @rm -f emulator/btvirt$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(emulator_btvirt_OBJECTS) $(emulator_btvirt_LDADD) $(LIBS) +emulator/hfp.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) + +emulator/hfp$(EXEEXT): $(emulator_hfp_OBJECTS) $(emulator_hfp_DEPENDENCIES) $(EXTRA_emulator_hfp_DEPENDENCIES) emulator/$(am__dirstamp) + @rm -f emulator/hfp$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(emulator_hfp_OBJECTS) $(emulator_hfp_LDADD) $(LIBS) +mesh/$(am__dirstamp): + @$(MKDIR_P) mesh + @: > mesh/$(am__dirstamp) +mesh/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) mesh/$(DEPDIR) + @: > mesh/$(DEPDIR)/$(am__dirstamp) +mesh/mesh.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/net-keys.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/mesh-io.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/mesh-mgmt.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/mesh-io-generic.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/net.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/crypto.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/friend.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/appkey.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/node.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/model.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/cfgmod-server.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/mesh-config-json.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/util.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/dbus.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/agent.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/prov-acceptor.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/prov-initiator.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/manager.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/pb-adv.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/keyring.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) +mesh/main.$(OBJEXT): mesh/$(am__dirstamp) \ + mesh/$(DEPDIR)/$(am__dirstamp) + +mesh/bluetooth-meshd$(EXEEXT): $(mesh_bluetooth_meshd_OBJECTS) $(mesh_bluetooth_meshd_DEPENDENCIES) $(EXTRA_mesh_bluetooth_meshd_DEPENDENCIES) mesh/$(am__dirstamp) + @rm -f mesh/bluetooth-meshd$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(mesh_bluetooth_meshd_OBJECTS) $(mesh_bluetooth_meshd_LDADD) $(LIBS) +monitor/$(am__dirstamp): + @$(MKDIR_P) monitor + @: > monitor/$(am__dirstamp) +monitor/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) monitor/$(DEPDIR) + @: > monitor/$(DEPDIR)/$(am__dirstamp) +monitor/main.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/display.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/hcidump.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/ellisys.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/control.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/packet.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/vendor.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/lmp.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/crc.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/ll.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/l2cap.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/sdp.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/avctp.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/avdtp.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/a2dp.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/rfcomm.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/bnep.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/hwdb.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/keys.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/analyze.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/intel.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/broadcom.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) +monitor/jlink.$(OBJEXT): monitor/$(am__dirstamp) \ + monitor/$(DEPDIR)/$(am__dirstamp) + +monitor/btmon$(EXEEXT): $(monitor_btmon_OBJECTS) $(monitor_btmon_DEPENDENCIES) $(EXTRA_monitor_btmon_DEPENDENCIES) monitor/$(am__dirstamp) + @rm -f monitor/btmon$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(monitor_btmon_OBJECTS) $(monitor_btmon_LDADD) $(LIBS) +btio/obexd-btio.$(OBJEXT): btio/$(am__dirstamp) \ + btio/$(DEPDIR)/$(am__dirstamp) +gobex/$(am__dirstamp): + @$(MKDIR_P) gobex + @: > gobex/$(am__dirstamp) +gobex/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) gobex/$(DEPDIR) + @: > gobex/$(DEPDIR)/$(am__dirstamp) +gobex/obexd-gobex.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +gobex/obexd-gobex-defs.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +gobex/obexd-gobex-packet.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +gobex/obexd-gobex-header.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +gobex/obexd-gobex-transfer.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +gobex/obexd-gobex-apparam.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/$(am__dirstamp): + @$(MKDIR_P) obexd/plugins + @: > obexd/plugins/$(am__dirstamp) +obexd/plugins/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) obexd/plugins/$(DEPDIR) + @: > obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-filesystem.$(OBJEXT): \ + obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-bluetooth.$(OBJEXT): \ + obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-pcsuite.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-opp.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-ftp.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-irmc.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-pbap.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-vcard.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-phonebook-dummy.$(OBJEXT): \ + obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-mas.$(OBJEXT): obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/plugins/obexd-messages-dummy.$(OBJEXT): \ + obexd/plugins/$(am__dirstamp) \ + obexd/plugins/$(DEPDIR)/$(am__dirstamp) +obexd/client/$(am__dirstamp): + @$(MKDIR_P) obexd/client + @: > obexd/client/$(am__dirstamp) +obexd/client/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) obexd/client/$(DEPDIR) + @: > obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-mns.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/src/$(am__dirstamp): + @$(MKDIR_P) obexd/src + @: > obexd/src/$(am__dirstamp) +obexd/src/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) obexd/src/$(DEPDIR) + @: > obexd/src/$(DEPDIR)/$(am__dirstamp) +obexd/src/obexd-main.$(OBJEXT): obexd/src/$(am__dirstamp) \ + obexd/src/$(DEPDIR)/$(am__dirstamp) +obexd/src/obexd-plugin.$(OBJEXT): obexd/src/$(am__dirstamp) \ + obexd/src/$(DEPDIR)/$(am__dirstamp) +obexd/src/obexd-log.$(OBJEXT): obexd/src/$(am__dirstamp) \ + obexd/src/$(DEPDIR)/$(am__dirstamp) +obexd/src/obexd-manager.$(OBJEXT): obexd/src/$(am__dirstamp) \ + obexd/src/$(DEPDIR)/$(am__dirstamp) +obexd/src/obexd-obex.$(OBJEXT): obexd/src/$(am__dirstamp) \ + obexd/src/$(DEPDIR)/$(am__dirstamp) +obexd/src/obexd-mimetype.$(OBJEXT): obexd/src/$(am__dirstamp) \ + obexd/src/$(DEPDIR)/$(am__dirstamp) +obexd/src/obexd-service.$(OBJEXT): obexd/src/$(am__dirstamp) \ + obexd/src/$(DEPDIR)/$(am__dirstamp) +obexd/src/obexd-transport.$(OBJEXT): obexd/src/$(am__dirstamp) \ + obexd/src/$(DEPDIR)/$(am__dirstamp) +obexd/src/obexd-server.$(OBJEXT): obexd/src/$(am__dirstamp) \ + obexd/src/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-manager.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-session.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-bluetooth.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-sync.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-pbap.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-ftp.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-opp.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-map.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-map-event.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-transfer.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-transport.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) +obexd/client/obexd-driver.$(OBJEXT): obexd/client/$(am__dirstamp) \ + obexd/client/$(DEPDIR)/$(am__dirstamp) + +obexd/src/obexd$(EXEEXT): $(obexd_src_obexd_OBJECTS) $(obexd_src_obexd_DEPENDENCIES) $(EXTRA_obexd_src_obexd_DEPENDENCIES) obexd/src/$(am__dirstamp) + @rm -f obexd/src/obexd$(EXEEXT) + $(AM_V_CCLD)$(obexd_src_obexd_LINK) $(obexd_src_obexd_OBJECTS) $(obexd_src_obexd_LDADD) $(LIBS) +peripheral/$(am__dirstamp): + @$(MKDIR_P) peripheral + @: > peripheral/$(am__dirstamp) +peripheral/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) peripheral/$(DEPDIR) + @: > peripheral/$(DEPDIR)/$(am__dirstamp) +peripheral/main.$(OBJEXT): peripheral/$(am__dirstamp) \ + peripheral/$(DEPDIR)/$(am__dirstamp) +peripheral/efivars.$(OBJEXT): peripheral/$(am__dirstamp) \ + peripheral/$(DEPDIR)/$(am__dirstamp) +peripheral/attach.$(OBJEXT): peripheral/$(am__dirstamp) \ + peripheral/$(DEPDIR)/$(am__dirstamp) +peripheral/log.$(OBJEXT): peripheral/$(am__dirstamp) \ + peripheral/$(DEPDIR)/$(am__dirstamp) +peripheral/gap.$(OBJEXT): peripheral/$(am__dirstamp) \ + peripheral/$(DEPDIR)/$(am__dirstamp) +peripheral/gatt.$(OBJEXT): peripheral/$(am__dirstamp) \ + peripheral/$(DEPDIR)/$(am__dirstamp) + +peripheral/btsensor$(EXEEXT): $(peripheral_btsensor_OBJECTS) $(peripheral_btsensor_DEPENDENCIES) $(EXTRA_peripheral_btsensor_DEPENDENCIES) peripheral/$(am__dirstamp) + @rm -f peripheral/btsensor$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(peripheral_btsensor_OBJECTS) $(peripheral_btsensor_LDADD) $(LIBS) +profiles/cups/$(am__dirstamp): + @$(MKDIR_P) profiles/cups + @: > profiles/cups/$(am__dirstamp) +profiles/cups/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/cups/$(DEPDIR) + @: > profiles/cups/$(DEPDIR)/$(am__dirstamp) +profiles/cups/main.$(OBJEXT): profiles/cups/$(am__dirstamp) \ + profiles/cups/$(DEPDIR)/$(am__dirstamp) +profiles/cups/sdp.$(OBJEXT): profiles/cups/$(am__dirstamp) \ + profiles/cups/$(DEPDIR)/$(am__dirstamp) +profiles/cups/spp.$(OBJEXT): profiles/cups/$(am__dirstamp) \ + profiles/cups/$(DEPDIR)/$(am__dirstamp) +profiles/cups/hcrp.$(OBJEXT): profiles/cups/$(am__dirstamp) \ + profiles/cups/$(DEPDIR)/$(am__dirstamp) + +profiles/cups/bluetooth$(EXEEXT): $(profiles_cups_bluetooth_OBJECTS) $(profiles_cups_bluetooth_DEPENDENCIES) $(EXTRA_profiles_cups_bluetooth_DEPENDENCIES) profiles/cups/$(am__dirstamp) + @rm -f profiles/cups/bluetooth$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(profiles_cups_bluetooth_OBJECTS) $(profiles_cups_bluetooth_LDADD) $(LIBS) +profiles/iap/$(am__dirstamp): + @$(MKDIR_P) profiles/iap + @: > profiles/iap/$(am__dirstamp) +profiles/iap/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/iap/$(DEPDIR) + @: > profiles/iap/$(DEPDIR)/$(am__dirstamp) +profiles/iap/main.$(OBJEXT): profiles/iap/$(am__dirstamp) \ + profiles/iap/$(DEPDIR)/$(am__dirstamp) + +profiles/iap/iapd$(EXEEXT): $(profiles_iap_iapd_OBJECTS) $(profiles_iap_iapd_DEPENDENCIES) $(EXTRA_profiles_iap_iapd_DEPENDENCIES) profiles/iap/$(am__dirstamp) + @rm -f profiles/iap/iapd$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(profiles_iap_iapd_OBJECTS) $(profiles_iap_iapd_LDADD) $(LIBS) +plugins/bluetoothd-hostname.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/bluetoothd-wiimote.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/bluetoothd-autopair.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/bluetoothd-policy.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +plugins/bluetoothd-neard.$(OBJEXT): plugins/$(am__dirstamp) \ + plugins/$(DEPDIR)/$(am__dirstamp) +profiles/sap/$(am__dirstamp): + @$(MKDIR_P) profiles/sap + @: > profiles/sap/$(am__dirstamp) +profiles/sap/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/sap/$(DEPDIR) + @: > profiles/sap/$(DEPDIR)/$(am__dirstamp) +profiles/sap/bluetoothd-main.$(OBJEXT): profiles/sap/$(am__dirstamp) \ + profiles/sap/$(DEPDIR)/$(am__dirstamp) +profiles/sap/bluetoothd-manager.$(OBJEXT): \ + profiles/sap/$(am__dirstamp) \ + profiles/sap/$(DEPDIR)/$(am__dirstamp) +profiles/sap/bluetoothd-server.$(OBJEXT): \ + profiles/sap/$(am__dirstamp) \ + profiles/sap/$(DEPDIR)/$(am__dirstamp) +profiles/sap/bluetoothd-sap-dummy.$(OBJEXT): \ + profiles/sap/$(am__dirstamp) \ + profiles/sap/$(DEPDIR)/$(am__dirstamp) +profiles/audio/$(am__dirstamp): + @$(MKDIR_P) profiles/audio + @: > profiles/audio/$(am__dirstamp) +profiles/audio/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/audio/$(DEPDIR) + @: > profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-source.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-sink.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-a2dp.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-avdtp.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-media.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-transport.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-control.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-avctp.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-avrcp.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/audio/bluetoothd-player.$(OBJEXT): \ + profiles/audio/$(am__dirstamp) \ + profiles/audio/$(DEPDIR)/$(am__dirstamp) +profiles/network/bluetoothd-manager.$(OBJEXT): \ + profiles/network/$(am__dirstamp) \ + profiles/network/$(DEPDIR)/$(am__dirstamp) +profiles/network/bluetoothd-bnep.$(OBJEXT): \ + profiles/network/$(am__dirstamp) \ + profiles/network/$(DEPDIR)/$(am__dirstamp) +profiles/network/bluetoothd-server.$(OBJEXT): \ + profiles/network/$(am__dirstamp) \ + profiles/network/$(DEPDIR)/$(am__dirstamp) +profiles/network/bluetoothd-connection.$(OBJEXT): \ + profiles/network/$(am__dirstamp) \ + profiles/network/$(DEPDIR)/$(am__dirstamp) +profiles/input/bluetoothd-manager.$(OBJEXT): \ + profiles/input/$(am__dirstamp) \ + profiles/input/$(DEPDIR)/$(am__dirstamp) +profiles/input/bluetoothd-server.$(OBJEXT): \ + profiles/input/$(am__dirstamp) \ + profiles/input/$(DEPDIR)/$(am__dirstamp) +profiles/input/bluetoothd-device.$(OBJEXT): \ + profiles/input/$(am__dirstamp) \ + profiles/input/$(DEPDIR)/$(am__dirstamp) +profiles/input/bluetoothd-hog.$(OBJEXT): \ + profiles/input/$(am__dirstamp) \ + profiles/input/$(DEPDIR)/$(am__dirstamp) +profiles/input/bluetoothd-hog-lib.$(OBJEXT): \ + profiles/input/$(am__dirstamp) \ + profiles/input/$(DEPDIR)/$(am__dirstamp) +profiles/deviceinfo/bluetoothd-dis.$(OBJEXT): \ + profiles/deviceinfo/$(am__dirstamp) \ + profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp) +profiles/battery/bluetoothd-bas.$(OBJEXT): \ + profiles/battery/$(am__dirstamp) \ + profiles/battery/$(DEPDIR)/$(am__dirstamp) +profiles/scanparam/bluetoothd-scpp.$(OBJEXT): \ + profiles/scanparam/$(am__dirstamp) \ + profiles/scanparam/$(DEPDIR)/$(am__dirstamp) +profiles/input/bluetoothd-suspend-none.$(OBJEXT): \ + profiles/input/$(am__dirstamp) \ + profiles/input/$(DEPDIR)/$(am__dirstamp) +profiles/health/bluetoothd-mcap.$(OBJEXT): \ + profiles/health/$(am__dirstamp) \ + profiles/health/$(DEPDIR)/$(am__dirstamp) +profiles/health/bluetoothd-hdp_main.$(OBJEXT): \ + profiles/health/$(am__dirstamp) \ + profiles/health/$(DEPDIR)/$(am__dirstamp) +profiles/health/bluetoothd-hdp_manager.$(OBJEXT): \ + profiles/health/$(am__dirstamp) \ + profiles/health/$(DEPDIR)/$(am__dirstamp) +profiles/health/bluetoothd-hdp.$(OBJEXT): \ + profiles/health/$(am__dirstamp) \ + profiles/health/$(DEPDIR)/$(am__dirstamp) +profiles/health/bluetoothd-hdp_util.$(OBJEXT): \ + profiles/health/$(am__dirstamp) \ + profiles/health/$(DEPDIR)/$(am__dirstamp) +profiles/gap/$(am__dirstamp): + @$(MKDIR_P) profiles/gap + @: > profiles/gap/$(am__dirstamp) +profiles/gap/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/gap/$(DEPDIR) + @: > profiles/gap/$(DEPDIR)/$(am__dirstamp) +profiles/gap/bluetoothd-gas.$(OBJEXT): profiles/gap/$(am__dirstamp) \ + profiles/gap/$(DEPDIR)/$(am__dirstamp) +profiles/scanparam/bluetoothd-scan.$(OBJEXT): \ + profiles/scanparam/$(am__dirstamp) \ + profiles/scanparam/$(DEPDIR)/$(am__dirstamp) +profiles/deviceinfo/bluetoothd-deviceinfo.$(OBJEXT): \ + profiles/deviceinfo/$(am__dirstamp) \ + profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp) +profiles/midi/$(am__dirstamp): + @$(MKDIR_P) profiles/midi + @: > profiles/midi/$(am__dirstamp) +profiles/midi/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) profiles/midi/$(DEPDIR) + @: > profiles/midi/$(DEPDIR)/$(am__dirstamp) +profiles/midi/bluetoothd-midi.$(OBJEXT): \ + profiles/midi/$(am__dirstamp) \ + profiles/midi/$(DEPDIR)/$(am__dirstamp) +profiles/midi/bluetoothd-libmidi.$(OBJEXT): \ + profiles/midi/$(am__dirstamp) \ + profiles/midi/$(DEPDIR)/$(am__dirstamp) +profiles/battery/bluetoothd-battery.$(OBJEXT): \ + profiles/battery/$(am__dirstamp) \ + profiles/battery/$(DEPDIR)/$(am__dirstamp) +attrib/bluetoothd-att.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/bluetoothd-gatt.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/bluetoothd-gattrib.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +attrib/bluetoothd-gatt-service.$(OBJEXT): attrib/$(am__dirstamp) \ + attrib/$(DEPDIR)/$(am__dirstamp) +btio/bluetoothd-btio.$(OBJEXT): btio/$(am__dirstamp) \ + btio/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-main.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-log.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-backtrace.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-rfkill.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-sdpd-server.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-sdpd-request.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-sdpd-service.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-sdpd-database.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-attrib-server.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-gatt-database.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-sdp-xml.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-sdp-client.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-textfile.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-uuid-helper.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-plugin.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-storage.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-advertising.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-agent.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-error.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-adapter.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-profile.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-service.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-gatt-client.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-device.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-dbus-common.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) +src/bluetoothd-eir.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) + +src/bluetoothd$(EXEEXT): $(src_bluetoothd_OBJECTS) $(src_bluetoothd_DEPENDENCIES) $(EXTRA_src_bluetoothd_DEPENDENCIES) src/$(am__dirstamp) + @rm -f src/bluetoothd$(EXEEXT) + $(AM_V_CCLD)$(src_bluetoothd_LINK) $(src_bluetoothd_OBJECTS) $(src_bluetoothd_LDADD) $(LIBS) +tools/$(am__dirstamp): + @$(MKDIR_P) tools + @: > tools/$(am__dirstamp) +tools/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) tools/$(DEPDIR) + @: > tools/$(DEPDIR)/$(am__dirstamp) +tools/3dsp.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/3dsp$(EXEEXT): $(tools_3dsp_OBJECTS) $(tools_3dsp_DEPENDENCIES) $(EXTRA_tools_3dsp_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/3dsp$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_3dsp_OBJECTS) $(tools_3dsp_LDADD) $(LIBS) +tools/advtest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/advtest$(EXEEXT): $(tools_advtest_OBJECTS) $(tools_advtest_DEPENDENCIES) $(EXTRA_tools_advtest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/advtest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_advtest_OBJECTS) $(tools_advtest_LDADD) $(LIBS) +tools/amptest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/amptest$(EXEEXT): $(tools_amptest_OBJECTS) $(tools_amptest_DEPENDENCIES) $(EXTRA_tools_amptest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/amptest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_amptest_OBJECTS) $(tools_amptest_LDADD) $(LIBS) +tools/avinfo.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/avinfo$(EXEEXT): $(tools_avinfo_OBJECTS) $(tools_avinfo_DEPENDENCIES) $(EXTRA_tools_avinfo_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/avinfo$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_avinfo_OBJECTS) $(tools_avinfo_LDADD) $(LIBS) +tools/avtest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/avtest$(EXEEXT): $(tools_avtest_OBJECTS) $(tools_avtest_DEPENDENCIES) $(EXTRA_tools_avtest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/avtest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_avtest_OBJECTS) $(tools_avtest_LDADD) $(LIBS) +tools/bccmd.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr_hci.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr_usb.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr_h4.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr_3wire.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/csr_bcsp.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/ubcsp.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/bccmd$(EXEEXT): $(tools_bccmd_OBJECTS) $(tools_bccmd_DEPENDENCIES) $(EXTRA_tools_bccmd_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/bccmd$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_bccmd_OBJECTS) $(tools_bccmd_LDADD) $(LIBS) +tools/bcmfw.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/bcmfw$(EXEEXT): $(tools_bcmfw_OBJECTS) $(tools_bcmfw_DEPENDENCIES) $(EXTRA_tools_bcmfw_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/bcmfw$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_bcmfw_OBJECTS) $(tools_bcmfw_LDADD) $(LIBS) +tools/bdaddr.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +src/oui.$(OBJEXT): src/$(am__dirstamp) src/$(DEPDIR)/$(am__dirstamp) + +tools/bdaddr$(EXEEXT): $(tools_bdaddr_OBJECTS) $(tools_bdaddr_DEPENDENCIES) $(EXTRA_tools_bdaddr_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/bdaddr$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_bdaddr_OBJECTS) $(tools_bdaddr_LDADD) $(LIBS) +tools/bluemoon.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/bluemoon$(EXEEXT): $(tools_bluemoon_OBJECTS) $(tools_bluemoon_DEPENDENCIES) $(EXTRA_tools_bluemoon_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/bluemoon$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_bluemoon_OBJECTS) $(tools_bluemoon_LDADD) $(LIBS) +tools/bluetooth-player.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/bluetooth-player$(EXEEXT): $(tools_bluetooth_player_OBJECTS) $(tools_bluetooth_player_DEPENDENCIES) $(EXTRA_tools_bluetooth_player_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/bluetooth-player$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_bluetooth_player_OBJECTS) $(tools_bluetooth_player_LDADD) $(LIBS) +tools/bnep-tester.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +emulator/hciemu.$(OBJEXT): emulator/$(am__dirstamp) \ + emulator/$(DEPDIR)/$(am__dirstamp) + +tools/bnep-tester$(EXEEXT): $(tools_bnep_tester_OBJECTS) $(tools_bnep_tester_DEPENDENCIES) $(EXTRA_tools_bnep_tester_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/bnep-tester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_bnep_tester_OBJECTS) $(tools_bnep_tester_LDADD) $(LIBS) +tools/bneptest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/bneptest$(EXEEXT): $(tools_bneptest_OBJECTS) $(tools_bneptest_DEPENDENCIES) $(EXTRA_tools_bneptest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/bneptest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_bneptest_OBJECTS) $(tools_bneptest_LDADD) $(LIBS) +tools/btattach.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/btattach$(EXEEXT): $(tools_btattach_OBJECTS) $(tools_btattach_DEPENDENCIES) $(EXTRA_tools_btattach_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btattach$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btattach_OBJECTS) $(tools_btattach_LDADD) $(LIBS) +tools/btconfig.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/btconfig$(EXEEXT): $(tools_btconfig_OBJECTS) $(tools_btconfig_DEPENDENCIES) $(EXTRA_tools_btconfig_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btconfig$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btconfig_OBJECTS) $(tools_btconfig_LDADD) $(LIBS) +tools/btgatt-client.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/btgatt-client$(EXEEXT): $(tools_btgatt_client_OBJECTS) $(tools_btgatt_client_DEPENDENCIES) $(EXTRA_tools_btgatt_client_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btgatt-client$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btgatt_client_OBJECTS) $(tools_btgatt_client_LDADD) $(LIBS) +tools/btgatt-server.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/btgatt-server$(EXEEXT): $(tools_btgatt_server_OBJECTS) $(tools_btgatt_server_DEPENDENCIES) $(EXTRA_tools_btgatt_server_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btgatt-server$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btgatt_server_OBJECTS) $(tools_btgatt_server_LDADD) $(LIBS) +tools/btinfo.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/btinfo$(EXEEXT): $(tools_btinfo_OBJECTS) $(tools_btinfo_DEPENDENCIES) $(EXTRA_tools_btinfo_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btinfo$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btinfo_OBJECTS) $(tools_btinfo_LDADD) $(LIBS) +tools/btiotest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/btiotest$(EXEEXT): $(tools_btiotest_OBJECTS) $(tools_btiotest_DEPENDENCIES) $(EXTRA_tools_btiotest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btiotest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btiotest_OBJECTS) $(tools_btiotest_LDADD) $(LIBS) +tools/btmgmt.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/btmgmt$(EXEEXT): $(tools_btmgmt_OBJECTS) $(tools_btmgmt_DEPENDENCIES) $(EXTRA_tools_btmgmt_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btmgmt$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btmgmt_OBJECTS) $(tools_btmgmt_LDADD) $(LIBS) +tools/btmon-logger.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/btmon-logger$(EXEEXT): $(tools_btmon_logger_OBJECTS) $(tools_btmon_logger_DEPENDENCIES) $(EXTRA_tools_btmon_logger_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btmon-logger$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btmon_logger_OBJECTS) $(tools_btmon_logger_LDADD) $(LIBS) +tools/btpclient.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +src/shared/btp.$(OBJEXT): src/shared/$(am__dirstamp) \ + src/shared/$(DEPDIR)/$(am__dirstamp) + +tools/btpclient$(EXEEXT): $(tools_btpclient_OBJECTS) $(tools_btpclient_DEPENDENCIES) $(EXTRA_tools_btpclient_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btpclient$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btpclient_OBJECTS) $(tools_btpclient_LDADD) $(LIBS) +tools/btproxy.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/btproxy$(EXEEXT): $(tools_btproxy_OBJECTS) $(tools_btproxy_DEPENDENCIES) $(EXTRA_tools_btproxy_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btproxy$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btproxy_OBJECTS) $(tools_btproxy_LDADD) $(LIBS) +tools/btsnoop.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/btsnoop$(EXEEXT): $(tools_btsnoop_OBJECTS) $(tools_btsnoop_DEPENDENCIES) $(EXTRA_tools_btsnoop_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/btsnoop$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_btsnoop_OBJECTS) $(tools_btsnoop_LDADD) $(LIBS) +tools/check-selftest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/check-selftest$(EXEEXT): $(tools_check_selftest_OBJECTS) $(tools_check_selftest_DEPENDENCIES) $(EXTRA_tools_check_selftest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/check-selftest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_check_selftest_OBJECTS) $(tools_check_selftest_LDADD) $(LIBS) +tools/ciptool.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/ciptool$(EXEEXT): $(tools_ciptool_OBJECTS) $(tools_ciptool_DEPENDENCIES) $(EXTRA_tools_ciptool_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/ciptool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_ciptool_OBJECTS) $(tools_ciptool_LDADD) $(LIBS) +tools/cltest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/cltest$(EXEEXT): $(tools_cltest_OBJECTS) $(tools_cltest_DEPENDENCIES) $(EXTRA_tools_cltest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/cltest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_cltest_OBJECTS) $(tools_cltest_LDADD) $(LIBS) +tools/create-image.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/create-image$(EXEEXT): $(tools_create_image_OBJECTS) $(tools_create_image_DEPENDENCIES) $(EXTRA_tools_create_image_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/create-image$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_create_image_OBJECTS) $(tools_create_image_LDADD) $(LIBS) +tools/eddystone.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/eddystone$(EXEEXT): $(tools_eddystone_OBJECTS) $(tools_eddystone_DEPENDENCIES) $(EXTRA_tools_eddystone_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/eddystone$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_eddystone_OBJECTS) $(tools_eddystone_LDADD) $(LIBS) +tools/gap-tester.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/gap-tester$(EXEEXT): $(tools_gap_tester_OBJECTS) $(tools_gap_tester_DEPENDENCIES) $(EXTRA_tools_gap_tester_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/gap-tester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_gap_tester_OBJECTS) $(tools_gap_tester_LDADD) $(LIBS) +tools/gatt-service.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/gatt-service$(EXEEXT): $(tools_gatt_service_OBJECTS) $(tools_gatt_service_DEPENDENCIES) $(EXTRA_tools_gatt_service_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/gatt-service$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_gatt_service_OBJECTS) $(tools_gatt_service_LDADD) $(LIBS) +tools/hci-tester.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/hci-tester$(EXEEXT): $(tools_hci_tester_OBJECTS) $(tools_hci_tester_DEPENDENCIES) $(EXTRA_tools_hci_tester_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hci-tester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hci_tester_OBJECTS) $(tools_hci_tester_LDADD) $(LIBS) +tools/hciattach.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_st.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_ti.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_tialt.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_ath3k.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_qualcomm.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_intel.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/hciattach_bcm43xx.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/hciattach$(EXEEXT): $(tools_hciattach_OBJECTS) $(tools_hciattach_DEPENDENCIES) $(EXTRA_tools_hciattach_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hciattach$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hciattach_OBJECTS) $(tools_hciattach_LDADD) $(LIBS) +tools/hciconfig.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/hciconfig$(EXEEXT): $(tools_hciconfig_OBJECTS) $(tools_hciconfig_DEPENDENCIES) $(EXTRA_tools_hciconfig_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hciconfig$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hciconfig_OBJECTS) $(tools_hciconfig_LDADD) $(LIBS) +tools/hcidump.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/parser/$(am__dirstamp): + @$(MKDIR_P) tools/parser + @: > tools/parser/$(am__dirstamp) +tools/parser/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) tools/parser/$(DEPDIR) + @: > tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/parser.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/lmp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/hci.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/l2cap.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/amp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/smp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/att.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/sdp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/rfcomm.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/bnep.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/cmtp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/hidp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/hcrp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/avdtp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/avctp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/avrcp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/sap.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/obex.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/capi.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/ppp.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/tcpip.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/ericsson.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/csr.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) +tools/parser/bpa.$(OBJEXT): tools/parser/$(am__dirstamp) \ + tools/parser/$(DEPDIR)/$(am__dirstamp) + +tools/hcidump$(EXEEXT): $(tools_hcidump_OBJECTS) $(tools_hcidump_DEPENDENCIES) $(EXTRA_tools_hcidump_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hcidump$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hcidump_OBJECTS) $(tools_hcidump_LDADD) $(LIBS) +tools/hcieventmask.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/hcieventmask$(EXEEXT): $(tools_hcieventmask_OBJECTS) $(tools_hcieventmask_DEPENDENCIES) $(EXTRA_tools_hcieventmask_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hcieventmask$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hcieventmask_OBJECTS) $(tools_hcieventmask_LDADD) $(LIBS) +tools/hcisecfilter.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/hcisecfilter$(EXEEXT): $(tools_hcisecfilter_OBJECTS) $(tools_hcisecfilter_DEPENDENCIES) $(EXTRA_tools_hcisecfilter_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hcisecfilter$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hcisecfilter_OBJECTS) $(tools_hcisecfilter_LDADD) $(LIBS) +tools/hcitool.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/hcitool$(EXEEXT): $(tools_hcitool_OBJECTS) $(tools_hcitool_DEPENDENCIES) $(EXTRA_tools_hcitool_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hcitool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hcitool_OBJECTS) $(tools_hcitool_LDADD) $(LIBS) +tools/hex2hcd.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/hex2hcd$(EXEEXT): $(tools_hex2hcd_OBJECTS) $(tools_hex2hcd_DEPENDENCIES) $(EXTRA_tools_hex2hcd_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hex2hcd$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hex2hcd_OBJECTS) $(tools_hex2hcd_LDADD) $(LIBS) +tools/hid2hci.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/hid2hci$(EXEEXT): $(tools_hid2hci_OBJECTS) $(tools_hid2hci_DEPENDENCIES) $(EXTRA_tools_hid2hci_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hid2hci$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hid2hci_OBJECTS) $(tools_hid2hci_LDADD) $(LIBS) +tools/hwdb.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/hwdb$(EXEEXT): $(tools_hwdb_OBJECTS) $(tools_hwdb_DEPENDENCIES) $(EXTRA_tools_hwdb_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/hwdb$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_hwdb_OBJECTS) $(tools_hwdb_LDADD) $(LIBS) +tools/ibeacon.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/ibeacon$(EXEEXT): $(tools_ibeacon_OBJECTS) $(tools_ibeacon_DEPENDENCIES) $(EXTRA_tools_ibeacon_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/ibeacon$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_ibeacon_OBJECTS) $(tools_ibeacon_LDADD) $(LIBS) +tools/l2cap-tester.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/l2cap-tester$(EXEEXT): $(tools_l2cap_tester_OBJECTS) $(tools_l2cap_tester_DEPENDENCIES) $(EXTRA_tools_l2cap_tester_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/l2cap-tester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_l2cap_tester_OBJECTS) $(tools_l2cap_tester_LDADD) $(LIBS) +tools/l2ping.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/l2ping$(EXEEXT): $(tools_l2ping_OBJECTS) $(tools_l2ping_DEPENDENCIES) $(EXTRA_tools_l2ping_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/l2ping$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_l2ping_OBJECTS) $(tools_l2ping_LDADD) $(LIBS) +tools/l2test.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/l2test$(EXEEXT): $(tools_l2test_OBJECTS) $(tools_l2test_DEPENDENCIES) $(EXTRA_tools_l2test_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/l2test$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_l2test_OBJECTS) $(tools_l2test_LDADD) $(LIBS) +tools/mcaptest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/mcaptest$(EXEEXT): $(tools_mcaptest_OBJECTS) $(tools_mcaptest_DEPENDENCIES) $(EXTRA_tools_mcaptest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/mcaptest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_mcaptest_OBJECTS) $(tools_mcaptest_LDADD) $(LIBS) +tools/meshctl.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +tools/mesh/$(am__dirstamp): + @$(MKDIR_P) tools/mesh + @: > tools/mesh/$(am__dirstamp) +tools/mesh/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) tools/mesh/$(DEPDIR) + @: > tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/node.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/gatt.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/crypto.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/net.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/prov.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/util.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/agent.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/prov-db.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/config-client.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/config-server.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) +tools/mesh/onoff-model.$(OBJEXT): tools/mesh/$(am__dirstamp) \ + tools/mesh/$(DEPDIR)/$(am__dirstamp) + +tools/meshctl$(EXEEXT): $(tools_meshctl_OBJECTS) $(tools_meshctl_DEPENDENCIES) $(EXTRA_tools_meshctl_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/meshctl$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_meshctl_OBJECTS) $(tools_meshctl_LDADD) $(LIBS) +tools/mgmt-tester.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/mgmt-tester$(EXEEXT): $(tools_mgmt_tester_OBJECTS) $(tools_mgmt_tester_DEPENDENCIES) $(EXTRA_tools_mgmt_tester_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/mgmt-tester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_mgmt_tester_OBJECTS) $(tools_mgmt_tester_LDADD) $(LIBS) +tools/mpris-proxy.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/mpris-proxy$(EXEEXT): $(tools_mpris_proxy_OBJECTS) $(tools_mpris_proxy_DEPENDENCIES) $(EXTRA_tools_mpris_proxy_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/mpris-proxy$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_mpris_proxy_OBJECTS) $(tools_mpris_proxy_LDADD) $(LIBS) +tools/nokfw.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/nokfw$(EXEEXT): $(tools_nokfw_OBJECTS) $(tools_nokfw_DEPENDENCIES) $(EXTRA_tools_nokfw_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/nokfw$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_nokfw_OBJECTS) $(tools_nokfw_LDADD) $(LIBS) +gobex/gobex.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +gobex/gobex-defs.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +gobex/gobex-packet.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +gobex/gobex-header.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +gobex/gobex-transfer.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +gobex/gobex-apparam.$(OBJEXT): gobex/$(am__dirstamp) \ + gobex/$(DEPDIR)/$(am__dirstamp) +tools/obex-client-tool.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/obex-client-tool$(EXEEXT): $(tools_obex_client_tool_OBJECTS) $(tools_obex_client_tool_DEPENDENCIES) $(EXTRA_tools_obex_client_tool_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/obex-client-tool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_obex_client_tool_OBJECTS) $(tools_obex_client_tool_LDADD) $(LIBS) +tools/obex-server-tool.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/obex-server-tool$(EXEEXT): $(tools_obex_server_tool_OBJECTS) $(tools_obex_server_tool_DEPENDENCIES) $(EXTRA_tools_obex_server_tool_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/obex-server-tool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_obex_server_tool_OBJECTS) $(tools_obex_server_tool_LDADD) $(LIBS) +tools/obexctl.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/obexctl$(EXEEXT): $(tools_obexctl_OBJECTS) $(tools_obexctl_DEPENDENCIES) $(EXTRA_tools_obexctl_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/obexctl$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_obexctl_OBJECTS) $(tools_obexctl_LDADD) $(LIBS) +tools/oobtest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/oobtest$(EXEEXT): $(tools_oobtest_OBJECTS) $(tools_oobtest_DEPENDENCIES) $(EXTRA_tools_oobtest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/oobtest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_oobtest_OBJECTS) $(tools_oobtest_LDADD) $(LIBS) +tools/rctest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/rctest$(EXEEXT): $(tools_rctest_OBJECTS) $(tools_rctest_DEPENDENCIES) $(EXTRA_tools_rctest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/rctest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_rctest_OBJECTS) $(tools_rctest_LDADD) $(LIBS) +tools/rfcomm.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/rfcomm$(EXEEXT): $(tools_rfcomm_OBJECTS) $(tools_rfcomm_DEPENDENCIES) $(EXTRA_tools_rfcomm_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/rfcomm$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_rfcomm_OBJECTS) $(tools_rfcomm_LDADD) $(LIBS) +tools/rfcomm-tester.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/rfcomm-tester$(EXEEXT): $(tools_rfcomm_tester_OBJECTS) $(tools_rfcomm_tester_DEPENDENCIES) $(EXTRA_tools_rfcomm_tester_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/rfcomm-tester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_rfcomm_tester_OBJECTS) $(tools_rfcomm_tester_LDADD) $(LIBS) +tools/rtlfw.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/rtlfw$(EXEEXT): $(tools_rtlfw_OBJECTS) $(tools_rtlfw_DEPENDENCIES) $(EXTRA_tools_rtlfw_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/rtlfw$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_rtlfw_OBJECTS) $(tools_rtlfw_LDADD) $(LIBS) +tools/sco-tester.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/sco-tester$(EXEEXT): $(tools_sco_tester_OBJECTS) $(tools_sco_tester_DEPENDENCIES) $(EXTRA_tools_sco_tester_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/sco-tester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_sco_tester_OBJECTS) $(tools_sco_tester_LDADD) $(LIBS) +tools/scotest.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/scotest$(EXEEXT): $(tools_scotest_OBJECTS) $(tools_scotest_DEPENDENCIES) $(EXTRA_tools_scotest_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/scotest$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_scotest_OBJECTS) $(tools_scotest_LDADD) $(LIBS) +tools/sdptool.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) +src/sdp-xml.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) + +tools/sdptool$(EXEEXT): $(tools_sdptool_OBJECTS) $(tools_sdptool_DEPENDENCIES) $(EXTRA_tools_sdptool_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/sdptool$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_sdptool_OBJECTS) $(tools_sdptool_LDADD) $(LIBS) +tools/seq2bseq.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/seq2bseq$(EXEEXT): $(tools_seq2bseq_OBJECTS) $(tools_seq2bseq_DEPENDENCIES) $(EXTRA_tools_seq2bseq_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/seq2bseq$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_seq2bseq_OBJECTS) $(tools_seq2bseq_LDADD) $(LIBS) +tools/smp-tester.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/smp-tester$(EXEEXT): $(tools_smp_tester_OBJECTS) $(tools_smp_tester_DEPENDENCIES) $(EXTRA_tools_smp_tester_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/smp-tester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_smp_tester_OBJECTS) $(tools_smp_tester_LDADD) $(LIBS) +tools/test-runner.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/test-runner$(EXEEXT): $(tools_test_runner_OBJECTS) $(tools_test_runner_DEPENDENCIES) $(EXTRA_tools_test_runner_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/test-runner$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_test_runner_OBJECTS) $(tools_test_runner_LDADD) $(LIBS) +tools/userchan-tester.$(OBJEXT): tools/$(am__dirstamp) \ + tools/$(DEPDIR)/$(am__dirstamp) + +tools/userchan-tester$(EXEEXT): $(tools_userchan_tester_OBJECTS) $(tools_userchan_tester_DEPENDENCIES) $(EXTRA_tools_userchan_tester_DEPENDENCIES) tools/$(am__dirstamp) + @rm -f tools/userchan-tester$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(tools_userchan_tester_OBJECTS) $(tools_userchan_tester_LDADD) $(LIBS) +unit/$(am__dirstamp): + @$(MKDIR_P) unit + @: > unit/$(am__dirstamp) +unit/$(DEPDIR)/$(am__dirstamp): + @$(MKDIR_P) unit/$(DEPDIR) + @: > unit/$(DEPDIR)/$(am__dirstamp) +unit/test-avctp.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-avctp$(EXEEXT): $(unit_test_avctp_OBJECTS) $(unit_test_avctp_DEPENDENCIES) $(EXTRA_unit_test_avctp_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-avctp$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_avctp_OBJECTS) $(unit_test_avctp_LDADD) $(LIBS) +unit/test-avdtp.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-avdtp$(EXEEXT): $(unit_test_avdtp_OBJECTS) $(unit_test_avdtp_DEPENDENCIES) $(EXTRA_unit_test_avdtp_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-avdtp$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_avdtp_OBJECTS) $(unit_test_avdtp_LDADD) $(LIBS) +unit/test-avrcp.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-avrcp$(EXEEXT): $(unit_test_avrcp_OBJECTS) $(unit_test_avrcp_DEPENDENCIES) $(EXTRA_unit_test_avrcp_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-avrcp$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_avrcp_OBJECTS) $(unit_test_avrcp_LDADD) $(LIBS) +unit/test-crc.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-crc$(EXEEXT): $(unit_test_crc_OBJECTS) $(unit_test_crc_DEPENDENCIES) $(EXTRA_unit_test_crc_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-crc$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_crc_OBJECTS) $(unit_test_crc_LDADD) $(LIBS) +unit/test-crypto.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-crypto$(EXEEXT): $(unit_test_crypto_OBJECTS) $(unit_test_crypto_DEPENDENCIES) $(EXTRA_unit_test_crypto_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-crypto$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_crypto_OBJECTS) $(unit_test_crypto_LDADD) $(LIBS) +unit/test-ecc.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-ecc$(EXEEXT): $(unit_test_ecc_OBJECTS) $(unit_test_ecc_DEPENDENCIES) $(EXTRA_unit_test_ecc_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-ecc$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_ecc_OBJECTS) $(unit_test_ecc_LDADD) $(LIBS) +unit/test-eir.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-eir$(EXEEXT): $(unit_test_eir_OBJECTS) $(unit_test_eir_DEPENDENCIES) $(EXTRA_unit_test_eir_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-eir$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_eir_OBJECTS) $(unit_test_eir_LDADD) $(LIBS) +unit/test-gatt.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-gatt$(EXEEXT): $(unit_test_gatt_OBJECTS) $(unit_test_gatt_DEPENDENCIES) $(EXTRA_unit_test_gatt_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-gatt$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_gatt_OBJECTS) $(unit_test_gatt_LDADD) $(LIBS) +unit/test-gattrib.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-gattrib$(EXEEXT): $(unit_test_gattrib_OBJECTS) $(unit_test_gattrib_DEPENDENCIES) $(EXTRA_unit_test_gattrib_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-gattrib$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_gattrib_OBJECTS) $(unit_test_gattrib_LDADD) $(LIBS) +unit/test-gdbus-client.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-gdbus-client$(EXEEXT): $(unit_test_gdbus_client_OBJECTS) $(unit_test_gdbus_client_DEPENDENCIES) $(EXTRA_unit_test_gdbus_client_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-gdbus-client$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_gdbus_client_OBJECTS) $(unit_test_gdbus_client_LDADD) $(LIBS) +unit/util.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) +unit/test-gobex.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-gobex$(EXEEXT): $(unit_test_gobex_OBJECTS) $(unit_test_gobex_DEPENDENCIES) $(EXTRA_unit_test_gobex_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-gobex$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_gobex_OBJECTS) $(unit_test_gobex_LDADD) $(LIBS) +unit/test-gobex-apparam.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-gobex-apparam$(EXEEXT): $(unit_test_gobex_apparam_OBJECTS) $(unit_test_gobex_apparam_DEPENDENCIES) $(EXTRA_unit_test_gobex_apparam_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-gobex-apparam$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_gobex_apparam_OBJECTS) $(unit_test_gobex_apparam_LDADD) $(LIBS) +unit/test-gobex-header.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-gobex-header$(EXEEXT): $(unit_test_gobex_header_OBJECTS) $(unit_test_gobex_header_DEPENDENCIES) $(EXTRA_unit_test_gobex_header_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-gobex-header$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_gobex_header_OBJECTS) $(unit_test_gobex_header_LDADD) $(LIBS) +unit/test-gobex-packet.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-gobex-packet$(EXEEXT): $(unit_test_gobex_packet_OBJECTS) $(unit_test_gobex_packet_DEPENDENCIES) $(EXTRA_unit_test_gobex_packet_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-gobex-packet$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_gobex_packet_OBJECTS) $(unit_test_gobex_packet_LDADD) $(LIBS) +unit/test-gobex-transfer.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-gobex-transfer$(EXEEXT): $(unit_test_gobex_transfer_OBJECTS) $(unit_test_gobex_transfer_DEPENDENCIES) $(EXTRA_unit_test_gobex_transfer_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-gobex-transfer$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_gobex_transfer_OBJECTS) $(unit_test_gobex_transfer_LDADD) $(LIBS) +unit/test-hfp.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-hfp$(EXEEXT): $(unit_test_hfp_OBJECTS) $(unit_test_hfp_DEPENDENCIES) $(EXTRA_unit_test_hfp_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-hfp$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_hfp_OBJECTS) $(unit_test_hfp_LDADD) $(LIBS) +unit/test-hog.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-hog$(EXEEXT): $(unit_test_hog_OBJECTS) $(unit_test_hog_DEPENDENCIES) $(EXTRA_unit_test_hog_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-hog$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_hog_OBJECTS) $(unit_test_hog_LDADD) $(LIBS) +unit/test-lib.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-lib$(EXEEXT): $(unit_test_lib_OBJECTS) $(unit_test_lib_DEPENDENCIES) $(EXTRA_unit_test_lib_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-lib$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_lib_OBJECTS) $(unit_test_lib_LDADD) $(LIBS) +unit/test_mesh_crypto-test-mesh-crypto.$(OBJEXT): \ + unit/$(am__dirstamp) unit/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-util.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-log.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-queue.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-hashmap.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-random.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-signal.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-timeout.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-io.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-idle.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-main.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-strv.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-string.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-cipher.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-checksum.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-utf8.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-dbus.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-dbus-message.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-dbus-util.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-dbus-service.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-dbus-client.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-dbus-name-cache.$(OBJEXT): \ + ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-dbus-filter.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-gvariant-util.$(OBJEXT): \ + ell/$(am__dirstamp) ell/$(DEPDIR)/$(am__dirstamp) +ell/unit_test_mesh_crypto-siphash.$(OBJEXT): ell/$(am__dirstamp) \ + ell/$(DEPDIR)/$(am__dirstamp) + +unit/test-mesh-crypto$(EXEEXT): $(unit_test_mesh_crypto_OBJECTS) $(unit_test_mesh_crypto_DEPENDENCIES) $(EXTRA_unit_test_mesh_crypto_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-mesh-crypto$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_mesh_crypto_OBJECTS) $(unit_test_mesh_crypto_LDADD) $(LIBS) +unit/test-mgmt.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-mgmt$(EXEEXT): $(unit_test_mgmt_OBJECTS) $(unit_test_mgmt_DEPENDENCIES) $(EXTRA_unit_test_mgmt_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-mgmt$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_mgmt_OBJECTS) $(unit_test_mgmt_LDADD) $(LIBS) +unit/test_midi-test-midi.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) +profiles/midi/unit_test_midi-libmidi.$(OBJEXT): \ + profiles/midi/$(am__dirstamp) \ + profiles/midi/$(DEPDIR)/$(am__dirstamp) + +unit/test-midi$(EXEEXT): $(unit_test_midi_OBJECTS) $(unit_test_midi_DEPENDENCIES) $(EXTRA_unit_test_midi_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-midi$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_midi_OBJECTS) $(unit_test_midi_LDADD) $(LIBS) +unit/test-queue.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-queue$(EXEEXT): $(unit_test_queue_OBJECTS) $(unit_test_queue_DEPENDENCIES) $(EXTRA_unit_test_queue_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-queue$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_queue_OBJECTS) $(unit_test_queue_LDADD) $(LIBS) +unit/test-ringbuf.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-ringbuf$(EXEEXT): $(unit_test_ringbuf_OBJECTS) $(unit_test_ringbuf_DEPENDENCIES) $(EXTRA_unit_test_ringbuf_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-ringbuf$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_ringbuf_OBJECTS) $(unit_test_ringbuf_LDADD) $(LIBS) +unit/test-sdp.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-sdp$(EXEEXT): $(unit_test_sdp_OBJECTS) $(unit_test_sdp_DEPENDENCIES) $(EXTRA_unit_test_sdp_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-sdp$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_sdp_OBJECTS) $(unit_test_sdp_LDADD) $(LIBS) +unit/test-textfile.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) +src/textfile.$(OBJEXT): src/$(am__dirstamp) \ + src/$(DEPDIR)/$(am__dirstamp) + +unit/test-textfile$(EXEEXT): $(unit_test_textfile_OBJECTS) $(unit_test_textfile_DEPENDENCIES) $(EXTRA_unit_test_textfile_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-textfile$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_textfile_OBJECTS) $(unit_test_textfile_LDADD) $(LIBS) +unit/test-uhid.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-uhid$(EXEEXT): $(unit_test_uhid_OBJECTS) $(unit_test_uhid_DEPENDENCIES) $(EXTRA_unit_test_uhid_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-uhid$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_uhid_OBJECTS) $(unit_test_uhid_LDADD) $(LIBS) +unit/test-uuid.$(OBJEXT): unit/$(am__dirstamp) \ + unit/$(DEPDIR)/$(am__dirstamp) + +unit/test-uuid$(EXEEXT): $(unit_test_uuid_OBJECTS) $(unit_test_uuid_DEPENDENCIES) $(EXTRA_unit_test_uuid_DEPENDENCIES) unit/$(am__dirstamp) + @rm -f unit/test-uuid$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(unit_test_uuid_OBJECTS) $(unit_test_uuid_LDADD) $(LIBS) +install-testSCRIPTS: $(test_SCRIPTS) + @$(NORMAL_INSTALL) + @list='$(test_SCRIPTS)'; test -n "$(testdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(testdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(testdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n' \ + -e 'h;s|.*|.|' \ + -e 'p;x;s,.*/,,;$(transform)' | sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1; } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) { files[d] = files[d] " " $$1; \ + if (++n[d] == $(am__install_max)) { \ + print "f", d, files[d]; n[d] = 0; files[d] = "" } } \ + else { print "f", d "/" $$4, $$1 } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_SCRIPT) $$files '$(DESTDIR)$(testdir)$$dir'"; \ + $(INSTALL_SCRIPT) $$files "$(DESTDIR)$(testdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-testSCRIPTS: + @$(NORMAL_UNINSTALL) + @list='$(test_SCRIPTS)'; test -n "$(testdir)" || exit 0; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 's,.*/,,;$(transform)'`; \ + dir='$(DESTDIR)$(testdir)'; $(am__uninstall_files_from_dir) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + -rm -f android/*.$(OBJEXT) + -rm -f android/*.lo + -rm -f android/audio_utils/*.$(OBJEXT) + -rm -f android/audio_utils/*.lo + -rm -f android/client/*.$(OBJEXT) + -rm -f android/hardware/*.$(OBJEXT) + -rm -f attrib/*.$(OBJEXT) + -rm -f btio/*.$(OBJEXT) + -rm -f client/*.$(OBJEXT) + -rm -f ell/*.$(OBJEXT) + -rm -f ell/*.lo + -rm -f emulator/*.$(OBJEXT) + -rm -f gdbus/*.$(OBJEXT) + -rm -f gdbus/*.lo + -rm -f gobex/*.$(OBJEXT) + -rm -f lib/*.$(OBJEXT) + -rm -f lib/*.lo + -rm -f mesh/*.$(OBJEXT) + -rm -f monitor/*.$(OBJEXT) + -rm -f obexd/client/*.$(OBJEXT) + -rm -f obexd/plugins/*.$(OBJEXT) + -rm -f obexd/src/*.$(OBJEXT) + -rm -f peripheral/*.$(OBJEXT) + -rm -f plugins/*.$(OBJEXT) + -rm -f plugins/*.lo + -rm -f profiles/audio/*.$(OBJEXT) + -rm -f profiles/battery/*.$(OBJEXT) + -rm -f profiles/cups/*.$(OBJEXT) + -rm -f profiles/deviceinfo/*.$(OBJEXT) + -rm -f profiles/gap/*.$(OBJEXT) + -rm -f profiles/health/*.$(OBJEXT) + -rm -f profiles/iap/*.$(OBJEXT) + -rm -f profiles/input/*.$(OBJEXT) + -rm -f profiles/midi/*.$(OBJEXT) + -rm -f profiles/network/*.$(OBJEXT) + -rm -f profiles/sap/*.$(OBJEXT) + -rm -f profiles/scanparam/*.$(OBJEXT) + -rm -f src/*.$(OBJEXT) + -rm -f src/shared/*.$(OBJEXT) + -rm -f src/shared/*.lo + -rm -f tools/*.$(OBJEXT) + -rm -f tools/mesh/*.$(OBJEXT) + -rm -f tools/parser/*.$(OBJEXT) + -rm -f unit/*.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/a2dp-sink.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/a2dp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/android_tester-tester-a2dp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/android_tester-tester-avrcp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/android_tester-tester-bluetooth.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/android_tester-tester-gatt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/android_tester-tester-hdp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/android_tester-tester-hidhost.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/android_tester-tester-main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/android_tester-tester-map-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/android_tester-tester-pan.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/android_tester-tester-socket.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-aptx.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-sbc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/audio_a2dp_default_la-hal-audio.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/audio_sco_default_la-hal-sco.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/avctp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/avdtp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/avdtptest-avdtp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/avdtptest-avdtptest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/avrcp-lib.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/avrcp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-a2dp-sink.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-a2dp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-avrcp-ctrl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-avrcp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-bluetooth.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-gatt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-handsfree-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-handsfree.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-health.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-hidhost.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-ipc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-map-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-pan.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-socket.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetooth_default_la-hal-utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/bluetoothd-snoop.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/haltest-hal-utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/handsfree-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/handsfree.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/health.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/hidhost.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/ipc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/ipc_tester-hal-utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/ipc_tester-ipc-tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/map-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/pan.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/sco.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/socket.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/system-emulator.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/$(DEPDIR)/test-ipc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/audio_utils/$(DEPDIR)/audio_sco_default_la-resampler.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-haltest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-history.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-audio.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-av-sink.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-av.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-bt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-gatt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-hf-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-hf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-hh.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-hl.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-mce.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-pan.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-rc-ctrl.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-rc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-sco.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-if-sock.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-pollhandler.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-tabcompletion.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/client/$(DEPDIR)/haltest-terminal.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/hardware/$(DEPDIR)/android_tester-hardware.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@android/hardware/$(DEPDIR)/haltest-hardware.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/att.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/bluetoothd-att.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/bluetoothd-gatt-service.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/bluetoothd-gatt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/bluetoothd-gattrib.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/gattrib.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/gatttool.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/interactive.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@attrib/$(DEPDIR)/utils.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@btio/$(DEPDIR)/android_avdtptest-btio.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@btio/$(DEPDIR)/bluetoothd-btio.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@btio/$(DEPDIR)/btio.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@btio/$(DEPDIR)/obexd-btio.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/advertising.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/agent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/display.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@client/$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/checksum.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/cipher.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-filter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-message.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-name-cache.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-service.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus-util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/dbus.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/gvariant-util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/hashmap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/idle.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/io.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/log.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/main.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/queue.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/random.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/signal.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/siphash.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/string.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/strv.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/timeout.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-checksum.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-cipher.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-filter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-message.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-name-cache.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-service.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-dbus.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-gvariant-util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-hashmap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-idle.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-io.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-log.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-queue.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-random.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-signal.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-siphash.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-string.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-strv.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-timeout.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-utf8.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/unit_test_mesh_crypto-util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/utf8.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@ell/$(DEPDIR)/util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/amp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/android_android_tester-btdev.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/android_android_tester-bthost.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/android_android_tester-hciemu.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/android_android_tester-smp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/android_ipc_tester-btdev.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/android_ipc_tester-bthost.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/android_ipc_tester-hciemu.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/android_ipc_tester-smp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/b1ee.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/btdev.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/bthost.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/hciemu.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/hfp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/le.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/phy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/serial.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/smp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@emulator/$(DEPDIR)/vhci.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/mainloop.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/object.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/polkit.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gdbus/$(DEPDIR)/watch.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex-apparam.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex-defs.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex-header.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex-packet.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex-transfer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/gobex.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex-apparam.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex-defs.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex-header.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex-packet.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex-transfer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@gobex/$(DEPDIR)/obexd-gobex.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/bluetooth.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/hci.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/sdp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@lib/$(DEPDIR)/uuid.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/agent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/appkey.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/cfgmod-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/crypto.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/dbus.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/friend.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/keyring.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/manager.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh-config-json.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh-io-generic.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh-io.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh-mgmt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/mesh.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/model.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/net-keys.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/net.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/node.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/pb-adv.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/prov-acceptor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/prov-initiator.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@mesh/$(DEPDIR)/util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/a2dp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/analyze.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/avctp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/avdtp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/bnep.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/broadcom.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/control.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/crc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/display.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/ellisys.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/hcidump.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/hwdb.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/intel.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/jlink.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/keys.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/l2cap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/ll.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/lmp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/packet.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/rfcomm.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/sdp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@monitor/$(DEPDIR)/vendor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-bluetooth.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-driver.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-ftp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-manager.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-map-event.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-map.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-mns.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-opp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-pbap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-session.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-sync.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-transfer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/client/$(DEPDIR)/obexd-transport.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-filesystem.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-ftp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-irmc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-mas.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-opp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-pbap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-phonebook-dummy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/plugins/$(DEPDIR)/obexd-vcard.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-log.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-manager.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-mimetype.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-obex.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-plugin.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-service.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@obexd/src/$(DEPDIR)/obexd-transport.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/attach.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/efivars.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/gap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/log.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@peripheral/$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-autopair.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-hostname.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-neard.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-policy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/bluetoothd-wiimote.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/external_dummy_la-external-dummy.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@plugins/$(DEPDIR)/sixaxis_la-sixaxis.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-control.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-media.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-player.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-sink.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-source.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/audio/$(DEPDIR)/bluetoothd-transport.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/battery/$(DEPDIR)/bas.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/battery/$(DEPDIR)/bluetoothd-bas.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/battery/$(DEPDIR)/bluetoothd-battery.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/cups/$(DEPDIR)/hcrp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/cups/$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/cups/$(DEPDIR)/sdp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/cups/$(DEPDIR)/spp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/deviceinfo/$(DEPDIR)/dis.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/gap/$(DEPDIR)/bluetoothd-gas.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/health/$(DEPDIR)/bluetoothd-hdp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/health/$(DEPDIR)/bluetoothd-hdp_main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/health/$(DEPDIR)/bluetoothd-hdp_manager.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/health/$(DEPDIR)/bluetoothd-hdp_util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/health/$(DEPDIR)/bluetoothd-mcap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/health/$(DEPDIR)/mcap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/iap/$(DEPDIR)/main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-device.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-hog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-manager.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/input/$(DEPDIR)/hog-lib.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/midi/$(DEPDIR)/bluetoothd-midi.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/network/$(DEPDIR)/bluetoothd-bnep.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/network/$(DEPDIR)/bluetoothd-connection.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/network/$(DEPDIR)/bluetoothd-manager.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/network/$(DEPDIR)/bluetoothd-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/network/$(DEPDIR)/bnep.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/sap/$(DEPDIR)/bluetoothd-main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/sap/$(DEPDIR)/bluetoothd-manager.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/sap/$(DEPDIR)/bluetoothd-sap-dummy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/sap/$(DEPDIR)/bluetoothd-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@profiles/scanparam/$(DEPDIR)/scpp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/android_avdtptest-log.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-adapter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-advertising.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-agent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-attrib-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-backtrace.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-dbus-common.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-device.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-eir.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-error.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-gatt-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-gatt-database.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-log.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-main.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-plugin.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-profile.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-rfkill.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdp-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdp-xml.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdpd-database.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdpd-request.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdpd-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-sdpd-service.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-service.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-storage.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-textfile.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/bluetoothd-uuid-helper.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/eir.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/log.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/oui.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdp-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdp-xml.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-database.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-request.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/sdpd-service.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/textfile.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/$(DEPDIR)/uuid-helper.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/ad.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/android_avdtptest-log.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/android_avdtptest-queue.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/android_avdtptest-util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/att.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/btp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/btsnoop.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/crypto.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/ecc.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/gap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/gatt-client.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/gatt-db.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/gatt-helpers.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/gatt-server.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/hci-crypto.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/hci.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/hfp.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/io-ell.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/io-glib.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/io-mainloop.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/log.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/mainloop-ell.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/mainloop-glib.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/mainloop-notify.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/mainloop.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/mgmt.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/pcap.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/queue.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/ringbuf.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/shell.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/tester.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/timeout-ell.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/timeout-glib.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/timeout-mainloop.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/uhid.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@src/shared/$(DEPDIR)/util.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/3dsp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/advtest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/amptest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/avinfo.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/avtest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bccmd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bcmfw.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bdaddr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bluemoon.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bluetooth-player.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bnep-tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/bneptest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btattach.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btconfig.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btgatt-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btgatt-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btinfo.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btiotest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btmgmt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btmon-logger.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btpclient.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btproxy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/btsnoop.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/check-selftest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/ciptool.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/cltest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/create-image.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr_3wire.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr_bcsp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr_h4.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr_hci.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/csr_usb.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/eddystone.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/gap-tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/gatt-service.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hci-tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_ath3k.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_bcm43xx.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_intel.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_qualcomm.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_st.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_ti.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciattach_tialt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hciconfig.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hcidump.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hcieventmask.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hcisecfilter.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hcitool.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hex2hcd.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hid2hci.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/hwdb.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/ibeacon.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/l2cap-tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/l2ping.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/l2test.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/mcaptest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/meshctl.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/mgmt-tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/mpris-proxy.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/nokfw.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/obex-client-tool.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/obex-server-tool.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/obexctl.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/oobtest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/rctest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/rfcomm-tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/rfcomm.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/rtlfw.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/sco-tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/scotest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/sdptool.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/seq2bseq.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/smp-tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/test-runner.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/ubcsp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/$(DEPDIR)/userchan-tester.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/agent.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/config-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/config-server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/crypto.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/gatt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/net.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/node.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/onoff-model.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/prov-db.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/prov.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/mesh/$(DEPDIR)/util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/amp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/att.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/avctp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/avdtp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/avrcp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/bnep.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/bpa.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/capi.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/cmtp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/csr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/ericsson.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/hci.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/hcrp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/hidp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/l2cap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/lmp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/obex.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/parser.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/ppp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/rfcomm.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/sap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/sdp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/smp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@tools/parser/$(DEPDIR)/tcpip.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-avctp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-avdtp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-avrcp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-crc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-crypto.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-ecc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-eir.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gatt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gattrib.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gdbus-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gobex-apparam.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gobex-header.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gobex-packet.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gobex-transfer.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-gobex.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-hfp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-hog.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-lib.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-mgmt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-queue.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-ringbuf.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-sdp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-textfile.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-uhid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test-uuid.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/test_midi-test-midi.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@unit/$(DEPDIR)/util.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +android/audio_a2dp_default_la-hal-audio.lo: android/hal-audio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_audio_a2dp_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/audio_a2dp_default_la-hal-audio.lo -MD -MP -MF android/$(DEPDIR)/audio_a2dp_default_la-hal-audio.Tpo -c -o android/audio_a2dp_default_la-hal-audio.lo `test -f 'android/hal-audio.c' || echo '$(srcdir)/'`android/hal-audio.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/audio_a2dp_default_la-hal-audio.Tpo android/$(DEPDIR)/audio_a2dp_default_la-hal-audio.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-audio.c' object='android/audio_a2dp_default_la-hal-audio.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_audio_a2dp_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/audio_a2dp_default_la-hal-audio.lo `test -f 'android/hal-audio.c' || echo '$(srcdir)/'`android/hal-audio.c + +android/audio_a2dp_default_la-hal-audio-sbc.lo: android/hal-audio-sbc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_audio_a2dp_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/audio_a2dp_default_la-hal-audio-sbc.lo -MD -MP -MF android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-sbc.Tpo -c -o android/audio_a2dp_default_la-hal-audio-sbc.lo `test -f 'android/hal-audio-sbc.c' || echo '$(srcdir)/'`android/hal-audio-sbc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-sbc.Tpo android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-sbc.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-audio-sbc.c' object='android/audio_a2dp_default_la-hal-audio-sbc.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_audio_a2dp_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/audio_a2dp_default_la-hal-audio-sbc.lo `test -f 'android/hal-audio-sbc.c' || echo '$(srcdir)/'`android/hal-audio-sbc.c + +android/audio_a2dp_default_la-hal-audio-aptx.lo: android/hal-audio-aptx.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_audio_a2dp_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/audio_a2dp_default_la-hal-audio-aptx.lo -MD -MP -MF android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-aptx.Tpo -c -o android/audio_a2dp_default_la-hal-audio-aptx.lo `test -f 'android/hal-audio-aptx.c' || echo '$(srcdir)/'`android/hal-audio-aptx.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-aptx.Tpo android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-aptx.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-audio-aptx.c' object='android/audio_a2dp_default_la-hal-audio-aptx.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_audio_a2dp_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/audio_a2dp_default_la-hal-audio-aptx.lo `test -f 'android/hal-audio-aptx.c' || echo '$(srcdir)/'`android/hal-audio-aptx.c + +android/audio_sco_default_la-hal-sco.lo: android/hal-sco.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_audio_sco_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/audio_sco_default_la-hal-sco.lo -MD -MP -MF android/$(DEPDIR)/audio_sco_default_la-hal-sco.Tpo -c -o android/audio_sco_default_la-hal-sco.lo `test -f 'android/hal-sco.c' || echo '$(srcdir)/'`android/hal-sco.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/audio_sco_default_la-hal-sco.Tpo android/$(DEPDIR)/audio_sco_default_la-hal-sco.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-sco.c' object='android/audio_sco_default_la-hal-sco.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_audio_sco_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/audio_sco_default_la-hal-sco.lo `test -f 'android/hal-sco.c' || echo '$(srcdir)/'`android/hal-sco.c + +android/audio_utils/audio_sco_default_la-resampler.lo: android/audio_utils/resampler.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_audio_sco_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/audio_utils/audio_sco_default_la-resampler.lo -MD -MP -MF android/audio_utils/$(DEPDIR)/audio_sco_default_la-resampler.Tpo -c -o android/audio_utils/audio_sco_default_la-resampler.lo `test -f 'android/audio_utils/resampler.c' || echo '$(srcdir)/'`android/audio_utils/resampler.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/audio_utils/$(DEPDIR)/audio_sco_default_la-resampler.Tpo android/audio_utils/$(DEPDIR)/audio_sco_default_la-resampler.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/audio_utils/resampler.c' object='android/audio_utils/audio_sco_default_la-resampler.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_audio_sco_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/audio_utils/audio_sco_default_la-resampler.lo `test -f 'android/audio_utils/resampler.c' || echo '$(srcdir)/'`android/audio_utils/resampler.c + +android/bluetooth_default_la-hal-bluetooth.lo: android/hal-bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-bluetooth.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-bluetooth.Tpo -c -o android/bluetooth_default_la-hal-bluetooth.lo `test -f 'android/hal-bluetooth.c' || echo '$(srcdir)/'`android/hal-bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-bluetooth.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-bluetooth.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-bluetooth.c' object='android/bluetooth_default_la-hal-bluetooth.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-bluetooth.lo `test -f 'android/hal-bluetooth.c' || echo '$(srcdir)/'`android/hal-bluetooth.c + +android/bluetooth_default_la-hal-socket.lo: android/hal-socket.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-socket.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-socket.Tpo -c -o android/bluetooth_default_la-hal-socket.lo `test -f 'android/hal-socket.c' || echo '$(srcdir)/'`android/hal-socket.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-socket.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-socket.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-socket.c' object='android/bluetooth_default_la-hal-socket.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-socket.lo `test -f 'android/hal-socket.c' || echo '$(srcdir)/'`android/hal-socket.c + +android/bluetooth_default_la-hal-hidhost.lo: android/hal-hidhost.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-hidhost.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-hidhost.Tpo -c -o android/bluetooth_default_la-hal-hidhost.lo `test -f 'android/hal-hidhost.c' || echo '$(srcdir)/'`android/hal-hidhost.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-hidhost.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-hidhost.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-hidhost.c' object='android/bluetooth_default_la-hal-hidhost.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-hidhost.lo `test -f 'android/hal-hidhost.c' || echo '$(srcdir)/'`android/hal-hidhost.c + +android/bluetooth_default_la-hal-health.lo: android/hal-health.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-health.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-health.Tpo -c -o android/bluetooth_default_la-hal-health.lo `test -f 'android/hal-health.c' || echo '$(srcdir)/'`android/hal-health.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-health.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-health.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-health.c' object='android/bluetooth_default_la-hal-health.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-health.lo `test -f 'android/hal-health.c' || echo '$(srcdir)/'`android/hal-health.c + +android/bluetooth_default_la-hal-pan.lo: android/hal-pan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-pan.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-pan.Tpo -c -o android/bluetooth_default_la-hal-pan.lo `test -f 'android/hal-pan.c' || echo '$(srcdir)/'`android/hal-pan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-pan.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-pan.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-pan.c' object='android/bluetooth_default_la-hal-pan.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-pan.lo `test -f 'android/hal-pan.c' || echo '$(srcdir)/'`android/hal-pan.c + +android/bluetooth_default_la-hal-a2dp.lo: android/hal-a2dp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-a2dp.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-a2dp.Tpo -c -o android/bluetooth_default_la-hal-a2dp.lo `test -f 'android/hal-a2dp.c' || echo '$(srcdir)/'`android/hal-a2dp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-a2dp.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-a2dp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-a2dp.c' object='android/bluetooth_default_la-hal-a2dp.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-a2dp.lo `test -f 'android/hal-a2dp.c' || echo '$(srcdir)/'`android/hal-a2dp.c + +android/bluetooth_default_la-hal-a2dp-sink.lo: android/hal-a2dp-sink.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-a2dp-sink.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-a2dp-sink.Tpo -c -o android/bluetooth_default_la-hal-a2dp-sink.lo `test -f 'android/hal-a2dp-sink.c' || echo '$(srcdir)/'`android/hal-a2dp-sink.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-a2dp-sink.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-a2dp-sink.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-a2dp-sink.c' object='android/bluetooth_default_la-hal-a2dp-sink.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-a2dp-sink.lo `test -f 'android/hal-a2dp-sink.c' || echo '$(srcdir)/'`android/hal-a2dp-sink.c + +android/bluetooth_default_la-hal-avrcp.lo: android/hal-avrcp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-avrcp.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-avrcp.Tpo -c -o android/bluetooth_default_la-hal-avrcp.lo `test -f 'android/hal-avrcp.c' || echo '$(srcdir)/'`android/hal-avrcp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-avrcp.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-avrcp.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-avrcp.c' object='android/bluetooth_default_la-hal-avrcp.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-avrcp.lo `test -f 'android/hal-avrcp.c' || echo '$(srcdir)/'`android/hal-avrcp.c + +android/bluetooth_default_la-hal-avrcp-ctrl.lo: android/hal-avrcp-ctrl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-avrcp-ctrl.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-avrcp-ctrl.Tpo -c -o android/bluetooth_default_la-hal-avrcp-ctrl.lo `test -f 'android/hal-avrcp-ctrl.c' || echo '$(srcdir)/'`android/hal-avrcp-ctrl.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-avrcp-ctrl.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-avrcp-ctrl.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-avrcp-ctrl.c' object='android/bluetooth_default_la-hal-avrcp-ctrl.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-avrcp-ctrl.lo `test -f 'android/hal-avrcp-ctrl.c' || echo '$(srcdir)/'`android/hal-avrcp-ctrl.c + +android/bluetooth_default_la-hal-handsfree.lo: android/hal-handsfree.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-handsfree.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-handsfree.Tpo -c -o android/bluetooth_default_la-hal-handsfree.lo `test -f 'android/hal-handsfree.c' || echo '$(srcdir)/'`android/hal-handsfree.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-handsfree.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-handsfree.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-handsfree.c' object='android/bluetooth_default_la-hal-handsfree.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-handsfree.lo `test -f 'android/hal-handsfree.c' || echo '$(srcdir)/'`android/hal-handsfree.c + +android/bluetooth_default_la-hal-handsfree-client.lo: android/hal-handsfree-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-handsfree-client.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-handsfree-client.Tpo -c -o android/bluetooth_default_la-hal-handsfree-client.lo `test -f 'android/hal-handsfree-client.c' || echo '$(srcdir)/'`android/hal-handsfree-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-handsfree-client.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-handsfree-client.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-handsfree-client.c' object='android/bluetooth_default_la-hal-handsfree-client.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-handsfree-client.lo `test -f 'android/hal-handsfree-client.c' || echo '$(srcdir)/'`android/hal-handsfree-client.c + +android/bluetooth_default_la-hal-gatt.lo: android/hal-gatt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-gatt.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-gatt.Tpo -c -o android/bluetooth_default_la-hal-gatt.lo `test -f 'android/hal-gatt.c' || echo '$(srcdir)/'`android/hal-gatt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-gatt.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-gatt.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-gatt.c' object='android/bluetooth_default_la-hal-gatt.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-gatt.lo `test -f 'android/hal-gatt.c' || echo '$(srcdir)/'`android/hal-gatt.c + +android/bluetooth_default_la-hal-map-client.lo: android/hal-map-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-map-client.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-map-client.Tpo -c -o android/bluetooth_default_la-hal-map-client.lo `test -f 'android/hal-map-client.c' || echo '$(srcdir)/'`android/hal-map-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-map-client.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-map-client.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-map-client.c' object='android/bluetooth_default_la-hal-map-client.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-map-client.lo `test -f 'android/hal-map-client.c' || echo '$(srcdir)/'`android/hal-map-client.c + +android/bluetooth_default_la-hal-ipc.lo: android/hal-ipc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-ipc.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-ipc.Tpo -c -o android/bluetooth_default_la-hal-ipc.lo `test -f 'android/hal-ipc.c' || echo '$(srcdir)/'`android/hal-ipc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-ipc.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-ipc.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-ipc.c' object='android/bluetooth_default_la-hal-ipc.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-ipc.lo `test -f 'android/hal-ipc.c' || echo '$(srcdir)/'`android/hal-ipc.c + +android/bluetooth_default_la-hal-utils.lo: android/hal-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/bluetooth_default_la-hal-utils.lo -MD -MP -MF android/$(DEPDIR)/bluetooth_default_la-hal-utils.Tpo -c -o android/bluetooth_default_la-hal-utils.lo `test -f 'android/hal-utils.c' || echo '$(srcdir)/'`android/hal-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/bluetooth_default_la-hal-utils.Tpo android/$(DEPDIR)/bluetooth_default_la-hal-utils.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-utils.c' object='android/bluetooth_default_la-hal-utils.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_bluetooth_default_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/bluetooth_default_la-hal-utils.lo `test -f 'android/hal-utils.c' || echo '$(srcdir)/'`android/hal-utils.c + +plugins/external_dummy_la-external-dummy.lo: plugins/external-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(plugins_external_dummy_la_CFLAGS) $(CFLAGS) -MT plugins/external_dummy_la-external-dummy.lo -MD -MP -MF plugins/$(DEPDIR)/external_dummy_la-external-dummy.Tpo -c -o plugins/external_dummy_la-external-dummy.lo `test -f 'plugins/external-dummy.c' || echo '$(srcdir)/'`plugins/external-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/external_dummy_la-external-dummy.Tpo plugins/$(DEPDIR)/external_dummy_la-external-dummy.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/external-dummy.c' object='plugins/external_dummy_la-external-dummy.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(plugins_external_dummy_la_CFLAGS) $(CFLAGS) -c -o plugins/external_dummy_la-external-dummy.lo `test -f 'plugins/external-dummy.c' || echo '$(srcdir)/'`plugins/external-dummy.c + +plugins/sixaxis_la-sixaxis.lo: plugins/sixaxis.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(plugins_sixaxis_la_CFLAGS) $(CFLAGS) -MT plugins/sixaxis_la-sixaxis.lo -MD -MP -MF plugins/$(DEPDIR)/sixaxis_la-sixaxis.Tpo -c -o plugins/sixaxis_la-sixaxis.lo `test -f 'plugins/sixaxis.c' || echo '$(srcdir)/'`plugins/sixaxis.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/sixaxis_la-sixaxis.Tpo plugins/$(DEPDIR)/sixaxis_la-sixaxis.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/sixaxis.c' object='plugins/sixaxis_la-sixaxis.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(plugins_sixaxis_la_CFLAGS) $(CFLAGS) -c -o plugins/sixaxis_la-sixaxis.lo `test -f 'plugins/sixaxis.c' || echo '$(srcdir)/'`plugins/sixaxis.c + +emulator/android_android_tester-hciemu.o: emulator/hciemu.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_android_tester-hciemu.o -MD -MP -MF emulator/$(DEPDIR)/android_android_tester-hciemu.Tpo -c -o emulator/android_android_tester-hciemu.o `test -f 'emulator/hciemu.c' || echo '$(srcdir)/'`emulator/hciemu.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_android_tester-hciemu.Tpo emulator/$(DEPDIR)/android_android_tester-hciemu.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/hciemu.c' object='emulator/android_android_tester-hciemu.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_android_tester-hciemu.o `test -f 'emulator/hciemu.c' || echo '$(srcdir)/'`emulator/hciemu.c + +emulator/android_android_tester-hciemu.obj: emulator/hciemu.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_android_tester-hciemu.obj -MD -MP -MF emulator/$(DEPDIR)/android_android_tester-hciemu.Tpo -c -o emulator/android_android_tester-hciemu.obj `if test -f 'emulator/hciemu.c'; then $(CYGPATH_W) 'emulator/hciemu.c'; else $(CYGPATH_W) '$(srcdir)/emulator/hciemu.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_android_tester-hciemu.Tpo emulator/$(DEPDIR)/android_android_tester-hciemu.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/hciemu.c' object='emulator/android_android_tester-hciemu.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_android_tester-hciemu.obj `if test -f 'emulator/hciemu.c'; then $(CYGPATH_W) 'emulator/hciemu.c'; else $(CYGPATH_W) '$(srcdir)/emulator/hciemu.c'; fi` + +emulator/android_android_tester-btdev.o: emulator/btdev.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_android_tester-btdev.o -MD -MP -MF emulator/$(DEPDIR)/android_android_tester-btdev.Tpo -c -o emulator/android_android_tester-btdev.o `test -f 'emulator/btdev.c' || echo '$(srcdir)/'`emulator/btdev.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_android_tester-btdev.Tpo emulator/$(DEPDIR)/android_android_tester-btdev.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/btdev.c' object='emulator/android_android_tester-btdev.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_android_tester-btdev.o `test -f 'emulator/btdev.c' || echo '$(srcdir)/'`emulator/btdev.c + +emulator/android_android_tester-btdev.obj: emulator/btdev.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_android_tester-btdev.obj -MD -MP -MF emulator/$(DEPDIR)/android_android_tester-btdev.Tpo -c -o emulator/android_android_tester-btdev.obj `if test -f 'emulator/btdev.c'; then $(CYGPATH_W) 'emulator/btdev.c'; else $(CYGPATH_W) '$(srcdir)/emulator/btdev.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_android_tester-btdev.Tpo emulator/$(DEPDIR)/android_android_tester-btdev.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/btdev.c' object='emulator/android_android_tester-btdev.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_android_tester-btdev.obj `if test -f 'emulator/btdev.c'; then $(CYGPATH_W) 'emulator/btdev.c'; else $(CYGPATH_W) '$(srcdir)/emulator/btdev.c'; fi` + +emulator/android_android_tester-bthost.o: emulator/bthost.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_android_tester-bthost.o -MD -MP -MF emulator/$(DEPDIR)/android_android_tester-bthost.Tpo -c -o emulator/android_android_tester-bthost.o `test -f 'emulator/bthost.c' || echo '$(srcdir)/'`emulator/bthost.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_android_tester-bthost.Tpo emulator/$(DEPDIR)/android_android_tester-bthost.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/bthost.c' object='emulator/android_android_tester-bthost.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_android_tester-bthost.o `test -f 'emulator/bthost.c' || echo '$(srcdir)/'`emulator/bthost.c + +emulator/android_android_tester-bthost.obj: emulator/bthost.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_android_tester-bthost.obj -MD -MP -MF emulator/$(DEPDIR)/android_android_tester-bthost.Tpo -c -o emulator/android_android_tester-bthost.obj `if test -f 'emulator/bthost.c'; then $(CYGPATH_W) 'emulator/bthost.c'; else $(CYGPATH_W) '$(srcdir)/emulator/bthost.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_android_tester-bthost.Tpo emulator/$(DEPDIR)/android_android_tester-bthost.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/bthost.c' object='emulator/android_android_tester-bthost.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_android_tester-bthost.obj `if test -f 'emulator/bthost.c'; then $(CYGPATH_W) 'emulator/bthost.c'; else $(CYGPATH_W) '$(srcdir)/emulator/bthost.c'; fi` + +emulator/android_android_tester-smp.o: emulator/smp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_android_tester-smp.o -MD -MP -MF emulator/$(DEPDIR)/android_android_tester-smp.Tpo -c -o emulator/android_android_tester-smp.o `test -f 'emulator/smp.c' || echo '$(srcdir)/'`emulator/smp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_android_tester-smp.Tpo emulator/$(DEPDIR)/android_android_tester-smp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/smp.c' object='emulator/android_android_tester-smp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_android_tester-smp.o `test -f 'emulator/smp.c' || echo '$(srcdir)/'`emulator/smp.c + +emulator/android_android_tester-smp.obj: emulator/smp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_android_tester-smp.obj -MD -MP -MF emulator/$(DEPDIR)/android_android_tester-smp.Tpo -c -o emulator/android_android_tester-smp.obj `if test -f 'emulator/smp.c'; then $(CYGPATH_W) 'emulator/smp.c'; else $(CYGPATH_W) '$(srcdir)/emulator/smp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_android_tester-smp.Tpo emulator/$(DEPDIR)/android_android_tester-smp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/smp.c' object='emulator/android_android_tester-smp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_android_tester-smp.obj `if test -f 'emulator/smp.c'; then $(CYGPATH_W) 'emulator/smp.c'; else $(CYGPATH_W) '$(srcdir)/emulator/smp.c'; fi` + +android/hardware/android_tester-hardware.o: android/hardware/hardware.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/hardware/android_tester-hardware.o -MD -MP -MF android/hardware/$(DEPDIR)/android_tester-hardware.Tpo -c -o android/hardware/android_tester-hardware.o `test -f 'android/hardware/hardware.c' || echo '$(srcdir)/'`android/hardware/hardware.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/hardware/$(DEPDIR)/android_tester-hardware.Tpo android/hardware/$(DEPDIR)/android_tester-hardware.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hardware/hardware.c' object='android/hardware/android_tester-hardware.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/hardware/android_tester-hardware.o `test -f 'android/hardware/hardware.c' || echo '$(srcdir)/'`android/hardware/hardware.c + +android/hardware/android_tester-hardware.obj: android/hardware/hardware.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/hardware/android_tester-hardware.obj -MD -MP -MF android/hardware/$(DEPDIR)/android_tester-hardware.Tpo -c -o android/hardware/android_tester-hardware.obj `if test -f 'android/hardware/hardware.c'; then $(CYGPATH_W) 'android/hardware/hardware.c'; else $(CYGPATH_W) '$(srcdir)/android/hardware/hardware.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/hardware/$(DEPDIR)/android_tester-hardware.Tpo android/hardware/$(DEPDIR)/android_tester-hardware.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hardware/hardware.c' object='android/hardware/android_tester-hardware.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/hardware/android_tester-hardware.obj `if test -f 'android/hardware/hardware.c'; then $(CYGPATH_W) 'android/hardware/hardware.c'; else $(CYGPATH_W) '$(srcdir)/android/hardware/hardware.c'; fi` + +android/android_tester-tester-bluetooth.o: android/tester-bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-bluetooth.o -MD -MP -MF android/$(DEPDIR)/android_tester-tester-bluetooth.Tpo -c -o android/android_tester-tester-bluetooth.o `test -f 'android/tester-bluetooth.c' || echo '$(srcdir)/'`android/tester-bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-bluetooth.Tpo android/$(DEPDIR)/android_tester-tester-bluetooth.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-bluetooth.c' object='android/android_tester-tester-bluetooth.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-bluetooth.o `test -f 'android/tester-bluetooth.c' || echo '$(srcdir)/'`android/tester-bluetooth.c + +android/android_tester-tester-bluetooth.obj: android/tester-bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-bluetooth.obj -MD -MP -MF android/$(DEPDIR)/android_tester-tester-bluetooth.Tpo -c -o android/android_tester-tester-bluetooth.obj `if test -f 'android/tester-bluetooth.c'; then $(CYGPATH_W) 'android/tester-bluetooth.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-bluetooth.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-bluetooth.Tpo android/$(DEPDIR)/android_tester-tester-bluetooth.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-bluetooth.c' object='android/android_tester-tester-bluetooth.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-bluetooth.obj `if test -f 'android/tester-bluetooth.c'; then $(CYGPATH_W) 'android/tester-bluetooth.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-bluetooth.c'; fi` + +android/android_tester-tester-socket.o: android/tester-socket.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-socket.o -MD -MP -MF android/$(DEPDIR)/android_tester-tester-socket.Tpo -c -o android/android_tester-tester-socket.o `test -f 'android/tester-socket.c' || echo '$(srcdir)/'`android/tester-socket.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-socket.Tpo android/$(DEPDIR)/android_tester-tester-socket.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-socket.c' object='android/android_tester-tester-socket.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-socket.o `test -f 'android/tester-socket.c' || echo '$(srcdir)/'`android/tester-socket.c + +android/android_tester-tester-socket.obj: android/tester-socket.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-socket.obj -MD -MP -MF android/$(DEPDIR)/android_tester-tester-socket.Tpo -c -o android/android_tester-tester-socket.obj `if test -f 'android/tester-socket.c'; then $(CYGPATH_W) 'android/tester-socket.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-socket.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-socket.Tpo android/$(DEPDIR)/android_tester-tester-socket.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-socket.c' object='android/android_tester-tester-socket.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-socket.obj `if test -f 'android/tester-socket.c'; then $(CYGPATH_W) 'android/tester-socket.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-socket.c'; fi` + +android/android_tester-tester-hidhost.o: android/tester-hidhost.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-hidhost.o -MD -MP -MF android/$(DEPDIR)/android_tester-tester-hidhost.Tpo -c -o android/android_tester-tester-hidhost.o `test -f 'android/tester-hidhost.c' || echo '$(srcdir)/'`android/tester-hidhost.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-hidhost.Tpo android/$(DEPDIR)/android_tester-tester-hidhost.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-hidhost.c' object='android/android_tester-tester-hidhost.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-hidhost.o `test -f 'android/tester-hidhost.c' || echo '$(srcdir)/'`android/tester-hidhost.c + +android/android_tester-tester-hidhost.obj: android/tester-hidhost.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-hidhost.obj -MD -MP -MF android/$(DEPDIR)/android_tester-tester-hidhost.Tpo -c -o android/android_tester-tester-hidhost.obj `if test -f 'android/tester-hidhost.c'; then $(CYGPATH_W) 'android/tester-hidhost.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-hidhost.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-hidhost.Tpo android/$(DEPDIR)/android_tester-tester-hidhost.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-hidhost.c' object='android/android_tester-tester-hidhost.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-hidhost.obj `if test -f 'android/tester-hidhost.c'; then $(CYGPATH_W) 'android/tester-hidhost.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-hidhost.c'; fi` + +android/android_tester-tester-pan.o: android/tester-pan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-pan.o -MD -MP -MF android/$(DEPDIR)/android_tester-tester-pan.Tpo -c -o android/android_tester-tester-pan.o `test -f 'android/tester-pan.c' || echo '$(srcdir)/'`android/tester-pan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-pan.Tpo android/$(DEPDIR)/android_tester-tester-pan.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-pan.c' object='android/android_tester-tester-pan.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-pan.o `test -f 'android/tester-pan.c' || echo '$(srcdir)/'`android/tester-pan.c + +android/android_tester-tester-pan.obj: android/tester-pan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-pan.obj -MD -MP -MF android/$(DEPDIR)/android_tester-tester-pan.Tpo -c -o android/android_tester-tester-pan.obj `if test -f 'android/tester-pan.c'; then $(CYGPATH_W) 'android/tester-pan.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-pan.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-pan.Tpo android/$(DEPDIR)/android_tester-tester-pan.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-pan.c' object='android/android_tester-tester-pan.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-pan.obj `if test -f 'android/tester-pan.c'; then $(CYGPATH_W) 'android/tester-pan.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-pan.c'; fi` + +android/android_tester-tester-hdp.o: android/tester-hdp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-hdp.o -MD -MP -MF android/$(DEPDIR)/android_tester-tester-hdp.Tpo -c -o android/android_tester-tester-hdp.o `test -f 'android/tester-hdp.c' || echo '$(srcdir)/'`android/tester-hdp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-hdp.Tpo android/$(DEPDIR)/android_tester-tester-hdp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-hdp.c' object='android/android_tester-tester-hdp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-hdp.o `test -f 'android/tester-hdp.c' || echo '$(srcdir)/'`android/tester-hdp.c + +android/android_tester-tester-hdp.obj: android/tester-hdp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-hdp.obj -MD -MP -MF android/$(DEPDIR)/android_tester-tester-hdp.Tpo -c -o android/android_tester-tester-hdp.obj `if test -f 'android/tester-hdp.c'; then $(CYGPATH_W) 'android/tester-hdp.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-hdp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-hdp.Tpo android/$(DEPDIR)/android_tester-tester-hdp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-hdp.c' object='android/android_tester-tester-hdp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-hdp.obj `if test -f 'android/tester-hdp.c'; then $(CYGPATH_W) 'android/tester-hdp.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-hdp.c'; fi` + +android/android_tester-tester-a2dp.o: android/tester-a2dp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-a2dp.o -MD -MP -MF android/$(DEPDIR)/android_tester-tester-a2dp.Tpo -c -o android/android_tester-tester-a2dp.o `test -f 'android/tester-a2dp.c' || echo '$(srcdir)/'`android/tester-a2dp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-a2dp.Tpo android/$(DEPDIR)/android_tester-tester-a2dp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-a2dp.c' object='android/android_tester-tester-a2dp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-a2dp.o `test -f 'android/tester-a2dp.c' || echo '$(srcdir)/'`android/tester-a2dp.c + +android/android_tester-tester-a2dp.obj: android/tester-a2dp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-a2dp.obj -MD -MP -MF android/$(DEPDIR)/android_tester-tester-a2dp.Tpo -c -o android/android_tester-tester-a2dp.obj `if test -f 'android/tester-a2dp.c'; then $(CYGPATH_W) 'android/tester-a2dp.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-a2dp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-a2dp.Tpo android/$(DEPDIR)/android_tester-tester-a2dp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-a2dp.c' object='android/android_tester-tester-a2dp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-a2dp.obj `if test -f 'android/tester-a2dp.c'; then $(CYGPATH_W) 'android/tester-a2dp.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-a2dp.c'; fi` + +android/android_tester-tester-avrcp.o: android/tester-avrcp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-avrcp.o -MD -MP -MF android/$(DEPDIR)/android_tester-tester-avrcp.Tpo -c -o android/android_tester-tester-avrcp.o `test -f 'android/tester-avrcp.c' || echo '$(srcdir)/'`android/tester-avrcp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-avrcp.Tpo android/$(DEPDIR)/android_tester-tester-avrcp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-avrcp.c' object='android/android_tester-tester-avrcp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-avrcp.o `test -f 'android/tester-avrcp.c' || echo '$(srcdir)/'`android/tester-avrcp.c + +android/android_tester-tester-avrcp.obj: android/tester-avrcp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-avrcp.obj -MD -MP -MF android/$(DEPDIR)/android_tester-tester-avrcp.Tpo -c -o android/android_tester-tester-avrcp.obj `if test -f 'android/tester-avrcp.c'; then $(CYGPATH_W) 'android/tester-avrcp.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-avrcp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-avrcp.Tpo android/$(DEPDIR)/android_tester-tester-avrcp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-avrcp.c' object='android/android_tester-tester-avrcp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-avrcp.obj `if test -f 'android/tester-avrcp.c'; then $(CYGPATH_W) 'android/tester-avrcp.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-avrcp.c'; fi` + +android/android_tester-tester-gatt.o: android/tester-gatt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-gatt.o -MD -MP -MF android/$(DEPDIR)/android_tester-tester-gatt.Tpo -c -o android/android_tester-tester-gatt.o `test -f 'android/tester-gatt.c' || echo '$(srcdir)/'`android/tester-gatt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-gatt.Tpo android/$(DEPDIR)/android_tester-tester-gatt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-gatt.c' object='android/android_tester-tester-gatt.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-gatt.o `test -f 'android/tester-gatt.c' || echo '$(srcdir)/'`android/tester-gatt.c + +android/android_tester-tester-gatt.obj: android/tester-gatt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-gatt.obj -MD -MP -MF android/$(DEPDIR)/android_tester-tester-gatt.Tpo -c -o android/android_tester-tester-gatt.obj `if test -f 'android/tester-gatt.c'; then $(CYGPATH_W) 'android/tester-gatt.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-gatt.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-gatt.Tpo android/$(DEPDIR)/android_tester-tester-gatt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-gatt.c' object='android/android_tester-tester-gatt.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-gatt.obj `if test -f 'android/tester-gatt.c'; then $(CYGPATH_W) 'android/tester-gatt.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-gatt.c'; fi` + +android/android_tester-tester-map-client.o: android/tester-map-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-map-client.o -MD -MP -MF android/$(DEPDIR)/android_tester-tester-map-client.Tpo -c -o android/android_tester-tester-map-client.o `test -f 'android/tester-map-client.c' || echo '$(srcdir)/'`android/tester-map-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-map-client.Tpo android/$(DEPDIR)/android_tester-tester-map-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-map-client.c' object='android/android_tester-tester-map-client.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-map-client.o `test -f 'android/tester-map-client.c' || echo '$(srcdir)/'`android/tester-map-client.c + +android/android_tester-tester-map-client.obj: android/tester-map-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-map-client.obj -MD -MP -MF android/$(DEPDIR)/android_tester-tester-map-client.Tpo -c -o android/android_tester-tester-map-client.obj `if test -f 'android/tester-map-client.c'; then $(CYGPATH_W) 'android/tester-map-client.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-map-client.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-map-client.Tpo android/$(DEPDIR)/android_tester-tester-map-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-map-client.c' object='android/android_tester-tester-map-client.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-map-client.obj `if test -f 'android/tester-map-client.c'; then $(CYGPATH_W) 'android/tester-map-client.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-map-client.c'; fi` + +android/android_tester-tester-main.o: android/tester-main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-main.o -MD -MP -MF android/$(DEPDIR)/android_tester-tester-main.Tpo -c -o android/android_tester-tester-main.o `test -f 'android/tester-main.c' || echo '$(srcdir)/'`android/tester-main.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-main.Tpo android/$(DEPDIR)/android_tester-tester-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-main.c' object='android/android_tester-tester-main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-main.o `test -f 'android/tester-main.c' || echo '$(srcdir)/'`android/tester-main.c + +android/android_tester-tester-main.obj: android/tester-main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/android_tester-tester-main.obj -MD -MP -MF android/$(DEPDIR)/android_tester-tester-main.Tpo -c -o android/android_tester-tester-main.obj `if test -f 'android/tester-main.c'; then $(CYGPATH_W) 'android/tester-main.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-main.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/android_tester-tester-main.Tpo android/$(DEPDIR)/android_tester-tester-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/tester-main.c' object='android/android_tester-tester-main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_android_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/android_tester-tester-main.obj `if test -f 'android/tester-main.c'; then $(CYGPATH_W) 'android/tester-main.c'; else $(CYGPATH_W) '$(srcdir)/android/tester-main.c'; fi` + +android/avdtptest-avdtptest.o: android/avdtptest.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT android/avdtptest-avdtptest.o -MD -MP -MF android/$(DEPDIR)/avdtptest-avdtptest.Tpo -c -o android/avdtptest-avdtptest.o `test -f 'android/avdtptest.c' || echo '$(srcdir)/'`android/avdtptest.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/avdtptest-avdtptest.Tpo android/$(DEPDIR)/avdtptest-avdtptest.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/avdtptest.c' object='android/avdtptest-avdtptest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o android/avdtptest-avdtptest.o `test -f 'android/avdtptest.c' || echo '$(srcdir)/'`android/avdtptest.c + +android/avdtptest-avdtptest.obj: android/avdtptest.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT android/avdtptest-avdtptest.obj -MD -MP -MF android/$(DEPDIR)/avdtptest-avdtptest.Tpo -c -o android/avdtptest-avdtptest.obj `if test -f 'android/avdtptest.c'; then $(CYGPATH_W) 'android/avdtptest.c'; else $(CYGPATH_W) '$(srcdir)/android/avdtptest.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/avdtptest-avdtptest.Tpo android/$(DEPDIR)/avdtptest-avdtptest.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/avdtptest.c' object='android/avdtptest-avdtptest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o android/avdtptest-avdtptest.obj `if test -f 'android/avdtptest.c'; then $(CYGPATH_W) 'android/avdtptest.c'; else $(CYGPATH_W) '$(srcdir)/android/avdtptest.c'; fi` + +src/android_avdtptest-log.o: src/log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT src/android_avdtptest-log.o -MD -MP -MF src/$(DEPDIR)/android_avdtptest-log.Tpo -c -o src/android_avdtptest-log.o `test -f 'src/log.c' || echo '$(srcdir)/'`src/log.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/android_avdtptest-log.Tpo src/$(DEPDIR)/android_avdtptest-log.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/log.c' object='src/android_avdtptest-log.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o src/android_avdtptest-log.o `test -f 'src/log.c' || echo '$(srcdir)/'`src/log.c + +src/android_avdtptest-log.obj: src/log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT src/android_avdtptest-log.obj -MD -MP -MF src/$(DEPDIR)/android_avdtptest-log.Tpo -c -o src/android_avdtptest-log.obj `if test -f 'src/log.c'; then $(CYGPATH_W) 'src/log.c'; else $(CYGPATH_W) '$(srcdir)/src/log.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/android_avdtptest-log.Tpo src/$(DEPDIR)/android_avdtptest-log.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/log.c' object='src/android_avdtptest-log.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o src/android_avdtptest-log.obj `if test -f 'src/log.c'; then $(CYGPATH_W) 'src/log.c'; else $(CYGPATH_W) '$(srcdir)/src/log.c'; fi` + +btio/android_avdtptest-btio.o: btio/btio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT btio/android_avdtptest-btio.o -MD -MP -MF btio/$(DEPDIR)/android_avdtptest-btio.Tpo -c -o btio/android_avdtptest-btio.o `test -f 'btio/btio.c' || echo '$(srcdir)/'`btio/btio.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) btio/$(DEPDIR)/android_avdtptest-btio.Tpo btio/$(DEPDIR)/android_avdtptest-btio.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='btio/btio.c' object='btio/android_avdtptest-btio.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o btio/android_avdtptest-btio.o `test -f 'btio/btio.c' || echo '$(srcdir)/'`btio/btio.c + +btio/android_avdtptest-btio.obj: btio/btio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT btio/android_avdtptest-btio.obj -MD -MP -MF btio/$(DEPDIR)/android_avdtptest-btio.Tpo -c -o btio/android_avdtptest-btio.obj `if test -f 'btio/btio.c'; then $(CYGPATH_W) 'btio/btio.c'; else $(CYGPATH_W) '$(srcdir)/btio/btio.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) btio/$(DEPDIR)/android_avdtptest-btio.Tpo btio/$(DEPDIR)/android_avdtptest-btio.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='btio/btio.c' object='btio/android_avdtptest-btio.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o btio/android_avdtptest-btio.obj `if test -f 'btio/btio.c'; then $(CYGPATH_W) 'btio/btio.c'; else $(CYGPATH_W) '$(srcdir)/btio/btio.c'; fi` + +src/shared/android_avdtptest-util.o: src/shared/util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT src/shared/android_avdtptest-util.o -MD -MP -MF src/shared/$(DEPDIR)/android_avdtptest-util.Tpo -c -o src/shared/android_avdtptest-util.o `test -f 'src/shared/util.c' || echo '$(srcdir)/'`src/shared/util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/android_avdtptest-util.Tpo src/shared/$(DEPDIR)/android_avdtptest-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/util.c' object='src/shared/android_avdtptest-util.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o src/shared/android_avdtptest-util.o `test -f 'src/shared/util.c' || echo '$(srcdir)/'`src/shared/util.c + +src/shared/android_avdtptest-util.obj: src/shared/util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT src/shared/android_avdtptest-util.obj -MD -MP -MF src/shared/$(DEPDIR)/android_avdtptest-util.Tpo -c -o src/shared/android_avdtptest-util.obj `if test -f 'src/shared/util.c'; then $(CYGPATH_W) 'src/shared/util.c'; else $(CYGPATH_W) '$(srcdir)/src/shared/util.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/android_avdtptest-util.Tpo src/shared/$(DEPDIR)/android_avdtptest-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/util.c' object='src/shared/android_avdtptest-util.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o src/shared/android_avdtptest-util.obj `if test -f 'src/shared/util.c'; then $(CYGPATH_W) 'src/shared/util.c'; else $(CYGPATH_W) '$(srcdir)/src/shared/util.c'; fi` + +src/shared/android_avdtptest-queue.o: src/shared/queue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT src/shared/android_avdtptest-queue.o -MD -MP -MF src/shared/$(DEPDIR)/android_avdtptest-queue.Tpo -c -o src/shared/android_avdtptest-queue.o `test -f 'src/shared/queue.c' || echo '$(srcdir)/'`src/shared/queue.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/android_avdtptest-queue.Tpo src/shared/$(DEPDIR)/android_avdtptest-queue.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/queue.c' object='src/shared/android_avdtptest-queue.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o src/shared/android_avdtptest-queue.o `test -f 'src/shared/queue.c' || echo '$(srcdir)/'`src/shared/queue.c + +src/shared/android_avdtptest-queue.obj: src/shared/queue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT src/shared/android_avdtptest-queue.obj -MD -MP -MF src/shared/$(DEPDIR)/android_avdtptest-queue.Tpo -c -o src/shared/android_avdtptest-queue.obj `if test -f 'src/shared/queue.c'; then $(CYGPATH_W) 'src/shared/queue.c'; else $(CYGPATH_W) '$(srcdir)/src/shared/queue.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/android_avdtptest-queue.Tpo src/shared/$(DEPDIR)/android_avdtptest-queue.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/queue.c' object='src/shared/android_avdtptest-queue.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o src/shared/android_avdtptest-queue.obj `if test -f 'src/shared/queue.c'; then $(CYGPATH_W) 'src/shared/queue.c'; else $(CYGPATH_W) '$(srcdir)/src/shared/queue.c'; fi` + +src/shared/android_avdtptest-log.o: src/shared/log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT src/shared/android_avdtptest-log.o -MD -MP -MF src/shared/$(DEPDIR)/android_avdtptest-log.Tpo -c -o src/shared/android_avdtptest-log.o `test -f 'src/shared/log.c' || echo '$(srcdir)/'`src/shared/log.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/android_avdtptest-log.Tpo src/shared/$(DEPDIR)/android_avdtptest-log.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/log.c' object='src/shared/android_avdtptest-log.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o src/shared/android_avdtptest-log.o `test -f 'src/shared/log.c' || echo '$(srcdir)/'`src/shared/log.c + +src/shared/android_avdtptest-log.obj: src/shared/log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT src/shared/android_avdtptest-log.obj -MD -MP -MF src/shared/$(DEPDIR)/android_avdtptest-log.Tpo -c -o src/shared/android_avdtptest-log.obj `if test -f 'src/shared/log.c'; then $(CYGPATH_W) 'src/shared/log.c'; else $(CYGPATH_W) '$(srcdir)/src/shared/log.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/shared/$(DEPDIR)/android_avdtptest-log.Tpo src/shared/$(DEPDIR)/android_avdtptest-log.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/shared/log.c' object='src/shared/android_avdtptest-log.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o src/shared/android_avdtptest-log.obj `if test -f 'src/shared/log.c'; then $(CYGPATH_W) 'src/shared/log.c'; else $(CYGPATH_W) '$(srcdir)/src/shared/log.c'; fi` + +android/avdtptest-avdtp.o: android/avdtp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT android/avdtptest-avdtp.o -MD -MP -MF android/$(DEPDIR)/avdtptest-avdtp.Tpo -c -o android/avdtptest-avdtp.o `test -f 'android/avdtp.c' || echo '$(srcdir)/'`android/avdtp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/avdtptest-avdtp.Tpo android/$(DEPDIR)/avdtptest-avdtp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/avdtp.c' object='android/avdtptest-avdtp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o android/avdtptest-avdtp.o `test -f 'android/avdtp.c' || echo '$(srcdir)/'`android/avdtp.c + +android/avdtptest-avdtp.obj: android/avdtp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -MT android/avdtptest-avdtp.obj -MD -MP -MF android/$(DEPDIR)/avdtptest-avdtp.Tpo -c -o android/avdtptest-avdtp.obj `if test -f 'android/avdtp.c'; then $(CYGPATH_W) 'android/avdtp.c'; else $(CYGPATH_W) '$(srcdir)/android/avdtp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/avdtptest-avdtp.Tpo android/$(DEPDIR)/avdtptest-avdtp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/avdtp.c' object='android/avdtptest-avdtp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(android_avdtptest_CFLAGS) $(CFLAGS) -c -o android/avdtptest-avdtp.obj `if test -f 'android/avdtp.c'; then $(CYGPATH_W) 'android/avdtp.c'; else $(CYGPATH_W) '$(srcdir)/android/avdtp.c'; fi` + +android/client/haltest-haltest.o: android/client/haltest.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-haltest.o -MD -MP -MF android/client/$(DEPDIR)/haltest-haltest.Tpo -c -o android/client/haltest-haltest.o `test -f 'android/client/haltest.c' || echo '$(srcdir)/'`android/client/haltest.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-haltest.Tpo android/client/$(DEPDIR)/haltest-haltest.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/haltest.c' object='android/client/haltest-haltest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-haltest.o `test -f 'android/client/haltest.c' || echo '$(srcdir)/'`android/client/haltest.c + +android/client/haltest-haltest.obj: android/client/haltest.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-haltest.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-haltest.Tpo -c -o android/client/haltest-haltest.obj `if test -f 'android/client/haltest.c'; then $(CYGPATH_W) 'android/client/haltest.c'; else $(CYGPATH_W) '$(srcdir)/android/client/haltest.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-haltest.Tpo android/client/$(DEPDIR)/haltest-haltest.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/haltest.c' object='android/client/haltest-haltest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-haltest.obj `if test -f 'android/client/haltest.c'; then $(CYGPATH_W) 'android/client/haltest.c'; else $(CYGPATH_W) '$(srcdir)/android/client/haltest.c'; fi` + +android/client/haltest-pollhandler.o: android/client/pollhandler.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-pollhandler.o -MD -MP -MF android/client/$(DEPDIR)/haltest-pollhandler.Tpo -c -o android/client/haltest-pollhandler.o `test -f 'android/client/pollhandler.c' || echo '$(srcdir)/'`android/client/pollhandler.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-pollhandler.Tpo android/client/$(DEPDIR)/haltest-pollhandler.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/pollhandler.c' object='android/client/haltest-pollhandler.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-pollhandler.o `test -f 'android/client/pollhandler.c' || echo '$(srcdir)/'`android/client/pollhandler.c + +android/client/haltest-pollhandler.obj: android/client/pollhandler.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-pollhandler.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-pollhandler.Tpo -c -o android/client/haltest-pollhandler.obj `if test -f 'android/client/pollhandler.c'; then $(CYGPATH_W) 'android/client/pollhandler.c'; else $(CYGPATH_W) '$(srcdir)/android/client/pollhandler.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-pollhandler.Tpo android/client/$(DEPDIR)/haltest-pollhandler.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/pollhandler.c' object='android/client/haltest-pollhandler.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-pollhandler.obj `if test -f 'android/client/pollhandler.c'; then $(CYGPATH_W) 'android/client/pollhandler.c'; else $(CYGPATH_W) '$(srcdir)/android/client/pollhandler.c'; fi` + +android/client/haltest-terminal.o: android/client/terminal.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-terminal.o -MD -MP -MF android/client/$(DEPDIR)/haltest-terminal.Tpo -c -o android/client/haltest-terminal.o `test -f 'android/client/terminal.c' || echo '$(srcdir)/'`android/client/terminal.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-terminal.Tpo android/client/$(DEPDIR)/haltest-terminal.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/terminal.c' object='android/client/haltest-terminal.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-terminal.o `test -f 'android/client/terminal.c' || echo '$(srcdir)/'`android/client/terminal.c + +android/client/haltest-terminal.obj: android/client/terminal.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-terminal.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-terminal.Tpo -c -o android/client/haltest-terminal.obj `if test -f 'android/client/terminal.c'; then $(CYGPATH_W) 'android/client/terminal.c'; else $(CYGPATH_W) '$(srcdir)/android/client/terminal.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-terminal.Tpo android/client/$(DEPDIR)/haltest-terminal.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/terminal.c' object='android/client/haltest-terminal.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-terminal.obj `if test -f 'android/client/terminal.c'; then $(CYGPATH_W) 'android/client/terminal.c'; else $(CYGPATH_W) '$(srcdir)/android/client/terminal.c'; fi` + +android/client/haltest-history.o: android/client/history.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-history.o -MD -MP -MF android/client/$(DEPDIR)/haltest-history.Tpo -c -o android/client/haltest-history.o `test -f 'android/client/history.c' || echo '$(srcdir)/'`android/client/history.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-history.Tpo android/client/$(DEPDIR)/haltest-history.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/history.c' object='android/client/haltest-history.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-history.o `test -f 'android/client/history.c' || echo '$(srcdir)/'`android/client/history.c + +android/client/haltest-history.obj: android/client/history.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-history.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-history.Tpo -c -o android/client/haltest-history.obj `if test -f 'android/client/history.c'; then $(CYGPATH_W) 'android/client/history.c'; else $(CYGPATH_W) '$(srcdir)/android/client/history.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-history.Tpo android/client/$(DEPDIR)/haltest-history.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/history.c' object='android/client/haltest-history.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-history.obj `if test -f 'android/client/history.c'; then $(CYGPATH_W) 'android/client/history.c'; else $(CYGPATH_W) '$(srcdir)/android/client/history.c'; fi` + +android/client/haltest-tabcompletion.o: android/client/tabcompletion.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-tabcompletion.o -MD -MP -MF android/client/$(DEPDIR)/haltest-tabcompletion.Tpo -c -o android/client/haltest-tabcompletion.o `test -f 'android/client/tabcompletion.c' || echo '$(srcdir)/'`android/client/tabcompletion.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-tabcompletion.Tpo android/client/$(DEPDIR)/haltest-tabcompletion.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/tabcompletion.c' object='android/client/haltest-tabcompletion.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-tabcompletion.o `test -f 'android/client/tabcompletion.c' || echo '$(srcdir)/'`android/client/tabcompletion.c + +android/client/haltest-tabcompletion.obj: android/client/tabcompletion.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-tabcompletion.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-tabcompletion.Tpo -c -o android/client/haltest-tabcompletion.obj `if test -f 'android/client/tabcompletion.c'; then $(CYGPATH_W) 'android/client/tabcompletion.c'; else $(CYGPATH_W) '$(srcdir)/android/client/tabcompletion.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-tabcompletion.Tpo android/client/$(DEPDIR)/haltest-tabcompletion.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/tabcompletion.c' object='android/client/haltest-tabcompletion.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-tabcompletion.obj `if test -f 'android/client/tabcompletion.c'; then $(CYGPATH_W) 'android/client/tabcompletion.c'; else $(CYGPATH_W) '$(srcdir)/android/client/tabcompletion.c'; fi` + +android/client/haltest-if-av.o: android/client/if-av.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-av.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-av.Tpo -c -o android/client/haltest-if-av.o `test -f 'android/client/if-av.c' || echo '$(srcdir)/'`android/client/if-av.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-av.Tpo android/client/$(DEPDIR)/haltest-if-av.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-av.c' object='android/client/haltest-if-av.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-av.o `test -f 'android/client/if-av.c' || echo '$(srcdir)/'`android/client/if-av.c + +android/client/haltest-if-av.obj: android/client/if-av.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-av.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-av.Tpo -c -o android/client/haltest-if-av.obj `if test -f 'android/client/if-av.c'; then $(CYGPATH_W) 'android/client/if-av.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-av.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-av.Tpo android/client/$(DEPDIR)/haltest-if-av.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-av.c' object='android/client/haltest-if-av.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-av.obj `if test -f 'android/client/if-av.c'; then $(CYGPATH_W) 'android/client/if-av.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-av.c'; fi` + +android/client/haltest-if-av-sink.o: android/client/if-av-sink.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-av-sink.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-av-sink.Tpo -c -o android/client/haltest-if-av-sink.o `test -f 'android/client/if-av-sink.c' || echo '$(srcdir)/'`android/client/if-av-sink.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-av-sink.Tpo android/client/$(DEPDIR)/haltest-if-av-sink.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-av-sink.c' object='android/client/haltest-if-av-sink.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-av-sink.o `test -f 'android/client/if-av-sink.c' || echo '$(srcdir)/'`android/client/if-av-sink.c + +android/client/haltest-if-av-sink.obj: android/client/if-av-sink.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-av-sink.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-av-sink.Tpo -c -o android/client/haltest-if-av-sink.obj `if test -f 'android/client/if-av-sink.c'; then $(CYGPATH_W) 'android/client/if-av-sink.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-av-sink.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-av-sink.Tpo android/client/$(DEPDIR)/haltest-if-av-sink.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-av-sink.c' object='android/client/haltest-if-av-sink.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-av-sink.obj `if test -f 'android/client/if-av-sink.c'; then $(CYGPATH_W) 'android/client/if-av-sink.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-av-sink.c'; fi` + +android/client/haltest-if-rc.o: android/client/if-rc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-rc.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-rc.Tpo -c -o android/client/haltest-if-rc.o `test -f 'android/client/if-rc.c' || echo '$(srcdir)/'`android/client/if-rc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-rc.Tpo android/client/$(DEPDIR)/haltest-if-rc.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-rc.c' object='android/client/haltest-if-rc.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-rc.o `test -f 'android/client/if-rc.c' || echo '$(srcdir)/'`android/client/if-rc.c + +android/client/haltest-if-rc.obj: android/client/if-rc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-rc.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-rc.Tpo -c -o android/client/haltest-if-rc.obj `if test -f 'android/client/if-rc.c'; then $(CYGPATH_W) 'android/client/if-rc.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-rc.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-rc.Tpo android/client/$(DEPDIR)/haltest-if-rc.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-rc.c' object='android/client/haltest-if-rc.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-rc.obj `if test -f 'android/client/if-rc.c'; then $(CYGPATH_W) 'android/client/if-rc.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-rc.c'; fi` + +android/client/haltest-if-rc-ctrl.o: android/client/if-rc-ctrl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-rc-ctrl.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-rc-ctrl.Tpo -c -o android/client/haltest-if-rc-ctrl.o `test -f 'android/client/if-rc-ctrl.c' || echo '$(srcdir)/'`android/client/if-rc-ctrl.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-rc-ctrl.Tpo android/client/$(DEPDIR)/haltest-if-rc-ctrl.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-rc-ctrl.c' object='android/client/haltest-if-rc-ctrl.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-rc-ctrl.o `test -f 'android/client/if-rc-ctrl.c' || echo '$(srcdir)/'`android/client/if-rc-ctrl.c + +android/client/haltest-if-rc-ctrl.obj: android/client/if-rc-ctrl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-rc-ctrl.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-rc-ctrl.Tpo -c -o android/client/haltest-if-rc-ctrl.obj `if test -f 'android/client/if-rc-ctrl.c'; then $(CYGPATH_W) 'android/client/if-rc-ctrl.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-rc-ctrl.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-rc-ctrl.Tpo android/client/$(DEPDIR)/haltest-if-rc-ctrl.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-rc-ctrl.c' object='android/client/haltest-if-rc-ctrl.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-rc-ctrl.obj `if test -f 'android/client/if-rc-ctrl.c'; then $(CYGPATH_W) 'android/client/if-rc-ctrl.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-rc-ctrl.c'; fi` + +android/client/haltest-if-bt.o: android/client/if-bt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-bt.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-bt.Tpo -c -o android/client/haltest-if-bt.o `test -f 'android/client/if-bt.c' || echo '$(srcdir)/'`android/client/if-bt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-bt.Tpo android/client/$(DEPDIR)/haltest-if-bt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-bt.c' object='android/client/haltest-if-bt.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-bt.o `test -f 'android/client/if-bt.c' || echo '$(srcdir)/'`android/client/if-bt.c + +android/client/haltest-if-bt.obj: android/client/if-bt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-bt.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-bt.Tpo -c -o android/client/haltest-if-bt.obj `if test -f 'android/client/if-bt.c'; then $(CYGPATH_W) 'android/client/if-bt.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-bt.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-bt.Tpo android/client/$(DEPDIR)/haltest-if-bt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-bt.c' object='android/client/haltest-if-bt.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-bt.obj `if test -f 'android/client/if-bt.c'; then $(CYGPATH_W) 'android/client/if-bt.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-bt.c'; fi` + +android/client/haltest-if-gatt.o: android/client/if-gatt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-gatt.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-gatt.Tpo -c -o android/client/haltest-if-gatt.o `test -f 'android/client/if-gatt.c' || echo '$(srcdir)/'`android/client/if-gatt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-gatt.Tpo android/client/$(DEPDIR)/haltest-if-gatt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-gatt.c' object='android/client/haltest-if-gatt.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-gatt.o `test -f 'android/client/if-gatt.c' || echo '$(srcdir)/'`android/client/if-gatt.c + +android/client/haltest-if-gatt.obj: android/client/if-gatt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-gatt.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-gatt.Tpo -c -o android/client/haltest-if-gatt.obj `if test -f 'android/client/if-gatt.c'; then $(CYGPATH_W) 'android/client/if-gatt.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-gatt.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-gatt.Tpo android/client/$(DEPDIR)/haltest-if-gatt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-gatt.c' object='android/client/haltest-if-gatt.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-gatt.obj `if test -f 'android/client/if-gatt.c'; then $(CYGPATH_W) 'android/client/if-gatt.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-gatt.c'; fi` + +android/client/haltest-if-hf.o: android/client/if-hf.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-hf.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-hf.Tpo -c -o android/client/haltest-if-hf.o `test -f 'android/client/if-hf.c' || echo '$(srcdir)/'`android/client/if-hf.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-hf.Tpo android/client/$(DEPDIR)/haltest-if-hf.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-hf.c' object='android/client/haltest-if-hf.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-hf.o `test -f 'android/client/if-hf.c' || echo '$(srcdir)/'`android/client/if-hf.c + +android/client/haltest-if-hf.obj: android/client/if-hf.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-hf.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-hf.Tpo -c -o android/client/haltest-if-hf.obj `if test -f 'android/client/if-hf.c'; then $(CYGPATH_W) 'android/client/if-hf.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-hf.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-hf.Tpo android/client/$(DEPDIR)/haltest-if-hf.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-hf.c' object='android/client/haltest-if-hf.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-hf.obj `if test -f 'android/client/if-hf.c'; then $(CYGPATH_W) 'android/client/if-hf.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-hf.c'; fi` + +android/client/haltest-if-hf-client.o: android/client/if-hf-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-hf-client.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-hf-client.Tpo -c -o android/client/haltest-if-hf-client.o `test -f 'android/client/if-hf-client.c' || echo '$(srcdir)/'`android/client/if-hf-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-hf-client.Tpo android/client/$(DEPDIR)/haltest-if-hf-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-hf-client.c' object='android/client/haltest-if-hf-client.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-hf-client.o `test -f 'android/client/if-hf-client.c' || echo '$(srcdir)/'`android/client/if-hf-client.c + +android/client/haltest-if-hf-client.obj: android/client/if-hf-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-hf-client.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-hf-client.Tpo -c -o android/client/haltest-if-hf-client.obj `if test -f 'android/client/if-hf-client.c'; then $(CYGPATH_W) 'android/client/if-hf-client.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-hf-client.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-hf-client.Tpo android/client/$(DEPDIR)/haltest-if-hf-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-hf-client.c' object='android/client/haltest-if-hf-client.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-hf-client.obj `if test -f 'android/client/if-hf-client.c'; then $(CYGPATH_W) 'android/client/if-hf-client.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-hf-client.c'; fi` + +android/client/haltest-if-hh.o: android/client/if-hh.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-hh.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-hh.Tpo -c -o android/client/haltest-if-hh.o `test -f 'android/client/if-hh.c' || echo '$(srcdir)/'`android/client/if-hh.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-hh.Tpo android/client/$(DEPDIR)/haltest-if-hh.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-hh.c' object='android/client/haltest-if-hh.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-hh.o `test -f 'android/client/if-hh.c' || echo '$(srcdir)/'`android/client/if-hh.c + +android/client/haltest-if-hh.obj: android/client/if-hh.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-hh.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-hh.Tpo -c -o android/client/haltest-if-hh.obj `if test -f 'android/client/if-hh.c'; then $(CYGPATH_W) 'android/client/if-hh.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-hh.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-hh.Tpo android/client/$(DEPDIR)/haltest-if-hh.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-hh.c' object='android/client/haltest-if-hh.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-hh.obj `if test -f 'android/client/if-hh.c'; then $(CYGPATH_W) 'android/client/if-hh.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-hh.c'; fi` + +android/client/haltest-if-pan.o: android/client/if-pan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-pan.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-pan.Tpo -c -o android/client/haltest-if-pan.o `test -f 'android/client/if-pan.c' || echo '$(srcdir)/'`android/client/if-pan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-pan.Tpo android/client/$(DEPDIR)/haltest-if-pan.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-pan.c' object='android/client/haltest-if-pan.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-pan.o `test -f 'android/client/if-pan.c' || echo '$(srcdir)/'`android/client/if-pan.c + +android/client/haltest-if-pan.obj: android/client/if-pan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-pan.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-pan.Tpo -c -o android/client/haltest-if-pan.obj `if test -f 'android/client/if-pan.c'; then $(CYGPATH_W) 'android/client/if-pan.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-pan.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-pan.Tpo android/client/$(DEPDIR)/haltest-if-pan.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-pan.c' object='android/client/haltest-if-pan.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-pan.obj `if test -f 'android/client/if-pan.c'; then $(CYGPATH_W) 'android/client/if-pan.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-pan.c'; fi` + +android/client/haltest-if-hl.o: android/client/if-hl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-hl.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-hl.Tpo -c -o android/client/haltest-if-hl.o `test -f 'android/client/if-hl.c' || echo '$(srcdir)/'`android/client/if-hl.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-hl.Tpo android/client/$(DEPDIR)/haltest-if-hl.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-hl.c' object='android/client/haltest-if-hl.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-hl.o `test -f 'android/client/if-hl.c' || echo '$(srcdir)/'`android/client/if-hl.c + +android/client/haltest-if-hl.obj: android/client/if-hl.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-hl.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-hl.Tpo -c -o android/client/haltest-if-hl.obj `if test -f 'android/client/if-hl.c'; then $(CYGPATH_W) 'android/client/if-hl.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-hl.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-hl.Tpo android/client/$(DEPDIR)/haltest-if-hl.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-hl.c' object='android/client/haltest-if-hl.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-hl.obj `if test -f 'android/client/if-hl.c'; then $(CYGPATH_W) 'android/client/if-hl.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-hl.c'; fi` + +android/client/haltest-if-sock.o: android/client/if-sock.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-sock.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-sock.Tpo -c -o android/client/haltest-if-sock.o `test -f 'android/client/if-sock.c' || echo '$(srcdir)/'`android/client/if-sock.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-sock.Tpo android/client/$(DEPDIR)/haltest-if-sock.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-sock.c' object='android/client/haltest-if-sock.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-sock.o `test -f 'android/client/if-sock.c' || echo '$(srcdir)/'`android/client/if-sock.c + +android/client/haltest-if-sock.obj: android/client/if-sock.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-sock.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-sock.Tpo -c -o android/client/haltest-if-sock.obj `if test -f 'android/client/if-sock.c'; then $(CYGPATH_W) 'android/client/if-sock.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-sock.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-sock.Tpo android/client/$(DEPDIR)/haltest-if-sock.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-sock.c' object='android/client/haltest-if-sock.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-sock.obj `if test -f 'android/client/if-sock.c'; then $(CYGPATH_W) 'android/client/if-sock.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-sock.c'; fi` + +android/client/haltest-if-audio.o: android/client/if-audio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-audio.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-audio.Tpo -c -o android/client/haltest-if-audio.o `test -f 'android/client/if-audio.c' || echo '$(srcdir)/'`android/client/if-audio.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-audio.Tpo android/client/$(DEPDIR)/haltest-if-audio.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-audio.c' object='android/client/haltest-if-audio.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-audio.o `test -f 'android/client/if-audio.c' || echo '$(srcdir)/'`android/client/if-audio.c + +android/client/haltest-if-audio.obj: android/client/if-audio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-audio.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-audio.Tpo -c -o android/client/haltest-if-audio.obj `if test -f 'android/client/if-audio.c'; then $(CYGPATH_W) 'android/client/if-audio.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-audio.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-audio.Tpo android/client/$(DEPDIR)/haltest-if-audio.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-audio.c' object='android/client/haltest-if-audio.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-audio.obj `if test -f 'android/client/if-audio.c'; then $(CYGPATH_W) 'android/client/if-audio.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-audio.c'; fi` + +android/client/haltest-if-sco.o: android/client/if-sco.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-sco.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-sco.Tpo -c -o android/client/haltest-if-sco.o `test -f 'android/client/if-sco.c' || echo '$(srcdir)/'`android/client/if-sco.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-sco.Tpo android/client/$(DEPDIR)/haltest-if-sco.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-sco.c' object='android/client/haltest-if-sco.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-sco.o `test -f 'android/client/if-sco.c' || echo '$(srcdir)/'`android/client/if-sco.c + +android/client/haltest-if-sco.obj: android/client/if-sco.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-sco.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-sco.Tpo -c -o android/client/haltest-if-sco.obj `if test -f 'android/client/if-sco.c'; then $(CYGPATH_W) 'android/client/if-sco.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-sco.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-sco.Tpo android/client/$(DEPDIR)/haltest-if-sco.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-sco.c' object='android/client/haltest-if-sco.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-sco.obj `if test -f 'android/client/if-sco.c'; then $(CYGPATH_W) 'android/client/if-sco.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-sco.c'; fi` + +android/client/haltest-if-mce.o: android/client/if-mce.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-mce.o -MD -MP -MF android/client/$(DEPDIR)/haltest-if-mce.Tpo -c -o android/client/haltest-if-mce.o `test -f 'android/client/if-mce.c' || echo '$(srcdir)/'`android/client/if-mce.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-mce.Tpo android/client/$(DEPDIR)/haltest-if-mce.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-mce.c' object='android/client/haltest-if-mce.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-mce.o `test -f 'android/client/if-mce.c' || echo '$(srcdir)/'`android/client/if-mce.c + +android/client/haltest-if-mce.obj: android/client/if-mce.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/client/haltest-if-mce.obj -MD -MP -MF android/client/$(DEPDIR)/haltest-if-mce.Tpo -c -o android/client/haltest-if-mce.obj `if test -f 'android/client/if-mce.c'; then $(CYGPATH_W) 'android/client/if-mce.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-mce.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/client/$(DEPDIR)/haltest-if-mce.Tpo android/client/$(DEPDIR)/haltest-if-mce.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/client/if-mce.c' object='android/client/haltest-if-mce.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/client/haltest-if-mce.obj `if test -f 'android/client/if-mce.c'; then $(CYGPATH_W) 'android/client/if-mce.c'; else $(CYGPATH_W) '$(srcdir)/android/client/if-mce.c'; fi` + +android/hardware/haltest-hardware.o: android/hardware/hardware.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/hardware/haltest-hardware.o -MD -MP -MF android/hardware/$(DEPDIR)/haltest-hardware.Tpo -c -o android/hardware/haltest-hardware.o `test -f 'android/hardware/hardware.c' || echo '$(srcdir)/'`android/hardware/hardware.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/hardware/$(DEPDIR)/haltest-hardware.Tpo android/hardware/$(DEPDIR)/haltest-hardware.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hardware/hardware.c' object='android/hardware/haltest-hardware.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/hardware/haltest-hardware.o `test -f 'android/hardware/hardware.c' || echo '$(srcdir)/'`android/hardware/hardware.c + +android/hardware/haltest-hardware.obj: android/hardware/hardware.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/hardware/haltest-hardware.obj -MD -MP -MF android/hardware/$(DEPDIR)/haltest-hardware.Tpo -c -o android/hardware/haltest-hardware.obj `if test -f 'android/hardware/hardware.c'; then $(CYGPATH_W) 'android/hardware/hardware.c'; else $(CYGPATH_W) '$(srcdir)/android/hardware/hardware.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/hardware/$(DEPDIR)/haltest-hardware.Tpo android/hardware/$(DEPDIR)/haltest-hardware.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hardware/hardware.c' object='android/hardware/haltest-hardware.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/hardware/haltest-hardware.obj `if test -f 'android/hardware/hardware.c'; then $(CYGPATH_W) 'android/hardware/hardware.c'; else $(CYGPATH_W) '$(srcdir)/android/hardware/hardware.c'; fi` + +android/haltest-hal-utils.o: android/hal-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/haltest-hal-utils.o -MD -MP -MF android/$(DEPDIR)/haltest-hal-utils.Tpo -c -o android/haltest-hal-utils.o `test -f 'android/hal-utils.c' || echo '$(srcdir)/'`android/hal-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/haltest-hal-utils.Tpo android/$(DEPDIR)/haltest-hal-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-utils.c' object='android/haltest-hal-utils.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/haltest-hal-utils.o `test -f 'android/hal-utils.c' || echo '$(srcdir)/'`android/hal-utils.c + +android/haltest-hal-utils.obj: android/hal-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/haltest-hal-utils.obj -MD -MP -MF android/$(DEPDIR)/haltest-hal-utils.Tpo -c -o android/haltest-hal-utils.obj `if test -f 'android/hal-utils.c'; then $(CYGPATH_W) 'android/hal-utils.c'; else $(CYGPATH_W) '$(srcdir)/android/hal-utils.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/haltest-hal-utils.Tpo android/$(DEPDIR)/haltest-hal-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-utils.c' object='android/haltest-hal-utils.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_haltest_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/haltest-hal-utils.obj `if test -f 'android/hal-utils.c'; then $(CYGPATH_W) 'android/hal-utils.c'; else $(CYGPATH_W) '$(srcdir)/android/hal-utils.c'; fi` + +emulator/android_ipc_tester-hciemu.o: emulator/hciemu.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_ipc_tester-hciemu.o -MD -MP -MF emulator/$(DEPDIR)/android_ipc_tester-hciemu.Tpo -c -o emulator/android_ipc_tester-hciemu.o `test -f 'emulator/hciemu.c' || echo '$(srcdir)/'`emulator/hciemu.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_ipc_tester-hciemu.Tpo emulator/$(DEPDIR)/android_ipc_tester-hciemu.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/hciemu.c' object='emulator/android_ipc_tester-hciemu.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_ipc_tester-hciemu.o `test -f 'emulator/hciemu.c' || echo '$(srcdir)/'`emulator/hciemu.c + +emulator/android_ipc_tester-hciemu.obj: emulator/hciemu.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_ipc_tester-hciemu.obj -MD -MP -MF emulator/$(DEPDIR)/android_ipc_tester-hciemu.Tpo -c -o emulator/android_ipc_tester-hciemu.obj `if test -f 'emulator/hciemu.c'; then $(CYGPATH_W) 'emulator/hciemu.c'; else $(CYGPATH_W) '$(srcdir)/emulator/hciemu.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_ipc_tester-hciemu.Tpo emulator/$(DEPDIR)/android_ipc_tester-hciemu.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/hciemu.c' object='emulator/android_ipc_tester-hciemu.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_ipc_tester-hciemu.obj `if test -f 'emulator/hciemu.c'; then $(CYGPATH_W) 'emulator/hciemu.c'; else $(CYGPATH_W) '$(srcdir)/emulator/hciemu.c'; fi` + +emulator/android_ipc_tester-btdev.o: emulator/btdev.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_ipc_tester-btdev.o -MD -MP -MF emulator/$(DEPDIR)/android_ipc_tester-btdev.Tpo -c -o emulator/android_ipc_tester-btdev.o `test -f 'emulator/btdev.c' || echo '$(srcdir)/'`emulator/btdev.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_ipc_tester-btdev.Tpo emulator/$(DEPDIR)/android_ipc_tester-btdev.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/btdev.c' object='emulator/android_ipc_tester-btdev.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_ipc_tester-btdev.o `test -f 'emulator/btdev.c' || echo '$(srcdir)/'`emulator/btdev.c + +emulator/android_ipc_tester-btdev.obj: emulator/btdev.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_ipc_tester-btdev.obj -MD -MP -MF emulator/$(DEPDIR)/android_ipc_tester-btdev.Tpo -c -o emulator/android_ipc_tester-btdev.obj `if test -f 'emulator/btdev.c'; then $(CYGPATH_W) 'emulator/btdev.c'; else $(CYGPATH_W) '$(srcdir)/emulator/btdev.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_ipc_tester-btdev.Tpo emulator/$(DEPDIR)/android_ipc_tester-btdev.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/btdev.c' object='emulator/android_ipc_tester-btdev.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_ipc_tester-btdev.obj `if test -f 'emulator/btdev.c'; then $(CYGPATH_W) 'emulator/btdev.c'; else $(CYGPATH_W) '$(srcdir)/emulator/btdev.c'; fi` + +emulator/android_ipc_tester-bthost.o: emulator/bthost.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_ipc_tester-bthost.o -MD -MP -MF emulator/$(DEPDIR)/android_ipc_tester-bthost.Tpo -c -o emulator/android_ipc_tester-bthost.o `test -f 'emulator/bthost.c' || echo '$(srcdir)/'`emulator/bthost.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_ipc_tester-bthost.Tpo emulator/$(DEPDIR)/android_ipc_tester-bthost.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/bthost.c' object='emulator/android_ipc_tester-bthost.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_ipc_tester-bthost.o `test -f 'emulator/bthost.c' || echo '$(srcdir)/'`emulator/bthost.c + +emulator/android_ipc_tester-bthost.obj: emulator/bthost.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_ipc_tester-bthost.obj -MD -MP -MF emulator/$(DEPDIR)/android_ipc_tester-bthost.Tpo -c -o emulator/android_ipc_tester-bthost.obj `if test -f 'emulator/bthost.c'; then $(CYGPATH_W) 'emulator/bthost.c'; else $(CYGPATH_W) '$(srcdir)/emulator/bthost.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_ipc_tester-bthost.Tpo emulator/$(DEPDIR)/android_ipc_tester-bthost.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/bthost.c' object='emulator/android_ipc_tester-bthost.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_ipc_tester-bthost.obj `if test -f 'emulator/bthost.c'; then $(CYGPATH_W) 'emulator/bthost.c'; else $(CYGPATH_W) '$(srcdir)/emulator/bthost.c'; fi` + +emulator/android_ipc_tester-smp.o: emulator/smp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_ipc_tester-smp.o -MD -MP -MF emulator/$(DEPDIR)/android_ipc_tester-smp.Tpo -c -o emulator/android_ipc_tester-smp.o `test -f 'emulator/smp.c' || echo '$(srcdir)/'`emulator/smp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_ipc_tester-smp.Tpo emulator/$(DEPDIR)/android_ipc_tester-smp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/smp.c' object='emulator/android_ipc_tester-smp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_ipc_tester-smp.o `test -f 'emulator/smp.c' || echo '$(srcdir)/'`emulator/smp.c + +emulator/android_ipc_tester-smp.obj: emulator/smp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT emulator/android_ipc_tester-smp.obj -MD -MP -MF emulator/$(DEPDIR)/android_ipc_tester-smp.Tpo -c -o emulator/android_ipc_tester-smp.obj `if test -f 'emulator/smp.c'; then $(CYGPATH_W) 'emulator/smp.c'; else $(CYGPATH_W) '$(srcdir)/emulator/smp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) emulator/$(DEPDIR)/android_ipc_tester-smp.Tpo emulator/$(DEPDIR)/android_ipc_tester-smp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='emulator/smp.c' object='emulator/android_ipc_tester-smp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o emulator/android_ipc_tester-smp.obj `if test -f 'emulator/smp.c'; then $(CYGPATH_W) 'emulator/smp.c'; else $(CYGPATH_W) '$(srcdir)/emulator/smp.c'; fi` + +android/ipc_tester-hal-utils.o: android/hal-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/ipc_tester-hal-utils.o -MD -MP -MF android/$(DEPDIR)/ipc_tester-hal-utils.Tpo -c -o android/ipc_tester-hal-utils.o `test -f 'android/hal-utils.c' || echo '$(srcdir)/'`android/hal-utils.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/ipc_tester-hal-utils.Tpo android/$(DEPDIR)/ipc_tester-hal-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-utils.c' object='android/ipc_tester-hal-utils.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/ipc_tester-hal-utils.o `test -f 'android/hal-utils.c' || echo '$(srcdir)/'`android/hal-utils.c + +android/ipc_tester-hal-utils.obj: android/hal-utils.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/ipc_tester-hal-utils.obj -MD -MP -MF android/$(DEPDIR)/ipc_tester-hal-utils.Tpo -c -o android/ipc_tester-hal-utils.obj `if test -f 'android/hal-utils.c'; then $(CYGPATH_W) 'android/hal-utils.c'; else $(CYGPATH_W) '$(srcdir)/android/hal-utils.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/ipc_tester-hal-utils.Tpo android/$(DEPDIR)/ipc_tester-hal-utils.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/hal-utils.c' object='android/ipc_tester-hal-utils.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/ipc_tester-hal-utils.obj `if test -f 'android/hal-utils.c'; then $(CYGPATH_W) 'android/hal-utils.c'; else $(CYGPATH_W) '$(srcdir)/android/hal-utils.c'; fi` + +android/ipc_tester-ipc-tester.o: android/ipc-tester.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/ipc_tester-ipc-tester.o -MD -MP -MF android/$(DEPDIR)/ipc_tester-ipc-tester.Tpo -c -o android/ipc_tester-ipc-tester.o `test -f 'android/ipc-tester.c' || echo '$(srcdir)/'`android/ipc-tester.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/ipc_tester-ipc-tester.Tpo android/$(DEPDIR)/ipc_tester-ipc-tester.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/ipc-tester.c' object='android/ipc_tester-ipc-tester.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/ipc_tester-ipc-tester.o `test -f 'android/ipc-tester.c' || echo '$(srcdir)/'`android/ipc-tester.c + +android/ipc_tester-ipc-tester.obj: android/ipc-tester.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT android/ipc_tester-ipc-tester.obj -MD -MP -MF android/$(DEPDIR)/ipc_tester-ipc-tester.Tpo -c -o android/ipc_tester-ipc-tester.obj `if test -f 'android/ipc-tester.c'; then $(CYGPATH_W) 'android/ipc-tester.c'; else $(CYGPATH_W) '$(srcdir)/android/ipc-tester.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) android/$(DEPDIR)/ipc_tester-ipc-tester.Tpo android/$(DEPDIR)/ipc_tester-ipc-tester.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='android/ipc-tester.c' object='android/ipc_tester-ipc-tester.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(android_ipc_tester_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o android/ipc_tester-ipc-tester.obj `if test -f 'android/ipc-tester.c'; then $(CYGPATH_W) 'android/ipc-tester.c'; else $(CYGPATH_W) '$(srcdir)/android/ipc-tester.c'; fi` + +btio/obexd-btio.o: btio/btio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT btio/obexd-btio.o -MD -MP -MF btio/$(DEPDIR)/obexd-btio.Tpo -c -o btio/obexd-btio.o `test -f 'btio/btio.c' || echo '$(srcdir)/'`btio/btio.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) btio/$(DEPDIR)/obexd-btio.Tpo btio/$(DEPDIR)/obexd-btio.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='btio/btio.c' object='btio/obexd-btio.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o btio/obexd-btio.o `test -f 'btio/btio.c' || echo '$(srcdir)/'`btio/btio.c + +btio/obexd-btio.obj: btio/btio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT btio/obexd-btio.obj -MD -MP -MF btio/$(DEPDIR)/obexd-btio.Tpo -c -o btio/obexd-btio.obj `if test -f 'btio/btio.c'; then $(CYGPATH_W) 'btio/btio.c'; else $(CYGPATH_W) '$(srcdir)/btio/btio.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) btio/$(DEPDIR)/obexd-btio.Tpo btio/$(DEPDIR)/obexd-btio.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='btio/btio.c' object='btio/obexd-btio.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o btio/obexd-btio.obj `if test -f 'btio/btio.c'; then $(CYGPATH_W) 'btio/btio.c'; else $(CYGPATH_W) '$(srcdir)/btio/btio.c'; fi` + +gobex/obexd-gobex.o: gobex/gobex.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex.Tpo -c -o gobex/obexd-gobex.o `test -f 'gobex/gobex.c' || echo '$(srcdir)/'`gobex/gobex.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex.Tpo gobex/$(DEPDIR)/obexd-gobex.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex.c' object='gobex/obexd-gobex.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex.o `test -f 'gobex/gobex.c' || echo '$(srcdir)/'`gobex/gobex.c + +gobex/obexd-gobex.obj: gobex/gobex.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex.Tpo -c -o gobex/obexd-gobex.obj `if test -f 'gobex/gobex.c'; then $(CYGPATH_W) 'gobex/gobex.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex.Tpo gobex/$(DEPDIR)/obexd-gobex.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex.c' object='gobex/obexd-gobex.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex.obj `if test -f 'gobex/gobex.c'; then $(CYGPATH_W) 'gobex/gobex.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex.c'; fi` + +gobex/obexd-gobex-defs.o: gobex/gobex-defs.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-defs.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-defs.Tpo -c -o gobex/obexd-gobex-defs.o `test -f 'gobex/gobex-defs.c' || echo '$(srcdir)/'`gobex/gobex-defs.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-defs.Tpo gobex/$(DEPDIR)/obexd-gobex-defs.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-defs.c' object='gobex/obexd-gobex-defs.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-defs.o `test -f 'gobex/gobex-defs.c' || echo '$(srcdir)/'`gobex/gobex-defs.c + +gobex/obexd-gobex-defs.obj: gobex/gobex-defs.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-defs.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-defs.Tpo -c -o gobex/obexd-gobex-defs.obj `if test -f 'gobex/gobex-defs.c'; then $(CYGPATH_W) 'gobex/gobex-defs.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-defs.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-defs.Tpo gobex/$(DEPDIR)/obexd-gobex-defs.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-defs.c' object='gobex/obexd-gobex-defs.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-defs.obj `if test -f 'gobex/gobex-defs.c'; then $(CYGPATH_W) 'gobex/gobex-defs.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-defs.c'; fi` + +gobex/obexd-gobex-packet.o: gobex/gobex-packet.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-packet.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-packet.Tpo -c -o gobex/obexd-gobex-packet.o `test -f 'gobex/gobex-packet.c' || echo '$(srcdir)/'`gobex/gobex-packet.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-packet.Tpo gobex/$(DEPDIR)/obexd-gobex-packet.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-packet.c' object='gobex/obexd-gobex-packet.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-packet.o `test -f 'gobex/gobex-packet.c' || echo '$(srcdir)/'`gobex/gobex-packet.c + +gobex/obexd-gobex-packet.obj: gobex/gobex-packet.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-packet.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-packet.Tpo -c -o gobex/obexd-gobex-packet.obj `if test -f 'gobex/gobex-packet.c'; then $(CYGPATH_W) 'gobex/gobex-packet.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-packet.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-packet.Tpo gobex/$(DEPDIR)/obexd-gobex-packet.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-packet.c' object='gobex/obexd-gobex-packet.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-packet.obj `if test -f 'gobex/gobex-packet.c'; then $(CYGPATH_W) 'gobex/gobex-packet.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-packet.c'; fi` + +gobex/obexd-gobex-header.o: gobex/gobex-header.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-header.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-header.Tpo -c -o gobex/obexd-gobex-header.o `test -f 'gobex/gobex-header.c' || echo '$(srcdir)/'`gobex/gobex-header.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-header.Tpo gobex/$(DEPDIR)/obexd-gobex-header.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-header.c' object='gobex/obexd-gobex-header.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-header.o `test -f 'gobex/gobex-header.c' || echo '$(srcdir)/'`gobex/gobex-header.c + +gobex/obexd-gobex-header.obj: gobex/gobex-header.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-header.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-header.Tpo -c -o gobex/obexd-gobex-header.obj `if test -f 'gobex/gobex-header.c'; then $(CYGPATH_W) 'gobex/gobex-header.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-header.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-header.Tpo gobex/$(DEPDIR)/obexd-gobex-header.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-header.c' object='gobex/obexd-gobex-header.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-header.obj `if test -f 'gobex/gobex-header.c'; then $(CYGPATH_W) 'gobex/gobex-header.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-header.c'; fi` + +gobex/obexd-gobex-transfer.o: gobex/gobex-transfer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-transfer.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-transfer.Tpo -c -o gobex/obexd-gobex-transfer.o `test -f 'gobex/gobex-transfer.c' || echo '$(srcdir)/'`gobex/gobex-transfer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-transfer.Tpo gobex/$(DEPDIR)/obexd-gobex-transfer.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-transfer.c' object='gobex/obexd-gobex-transfer.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-transfer.o `test -f 'gobex/gobex-transfer.c' || echo '$(srcdir)/'`gobex/gobex-transfer.c + +gobex/obexd-gobex-transfer.obj: gobex/gobex-transfer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-transfer.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-transfer.Tpo -c -o gobex/obexd-gobex-transfer.obj `if test -f 'gobex/gobex-transfer.c'; then $(CYGPATH_W) 'gobex/gobex-transfer.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-transfer.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-transfer.Tpo gobex/$(DEPDIR)/obexd-gobex-transfer.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-transfer.c' object='gobex/obexd-gobex-transfer.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-transfer.obj `if test -f 'gobex/gobex-transfer.c'; then $(CYGPATH_W) 'gobex/gobex-transfer.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-transfer.c'; fi` + +gobex/obexd-gobex-apparam.o: gobex/gobex-apparam.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-apparam.o -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-apparam.Tpo -c -o gobex/obexd-gobex-apparam.o `test -f 'gobex/gobex-apparam.c' || echo '$(srcdir)/'`gobex/gobex-apparam.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-apparam.Tpo gobex/$(DEPDIR)/obexd-gobex-apparam.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-apparam.c' object='gobex/obexd-gobex-apparam.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-apparam.o `test -f 'gobex/gobex-apparam.c' || echo '$(srcdir)/'`gobex/gobex-apparam.c + +gobex/obexd-gobex-apparam.obj: gobex/gobex-apparam.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT gobex/obexd-gobex-apparam.obj -MD -MP -MF gobex/$(DEPDIR)/obexd-gobex-apparam.Tpo -c -o gobex/obexd-gobex-apparam.obj `if test -f 'gobex/gobex-apparam.c'; then $(CYGPATH_W) 'gobex/gobex-apparam.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-apparam.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) gobex/$(DEPDIR)/obexd-gobex-apparam.Tpo gobex/$(DEPDIR)/obexd-gobex-apparam.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gobex/gobex-apparam.c' object='gobex/obexd-gobex-apparam.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o gobex/obexd-gobex-apparam.obj `if test -f 'gobex/gobex-apparam.c'; then $(CYGPATH_W) 'gobex/gobex-apparam.c'; else $(CYGPATH_W) '$(srcdir)/gobex/gobex-apparam.c'; fi` + +obexd/plugins/obexd-filesystem.o: obexd/plugins/filesystem.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-filesystem.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-filesystem.Tpo -c -o obexd/plugins/obexd-filesystem.o `test -f 'obexd/plugins/filesystem.c' || echo '$(srcdir)/'`obexd/plugins/filesystem.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-filesystem.Tpo obexd/plugins/$(DEPDIR)/obexd-filesystem.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/filesystem.c' object='obexd/plugins/obexd-filesystem.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-filesystem.o `test -f 'obexd/plugins/filesystem.c' || echo '$(srcdir)/'`obexd/plugins/filesystem.c + +obexd/plugins/obexd-filesystem.obj: obexd/plugins/filesystem.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-filesystem.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-filesystem.Tpo -c -o obexd/plugins/obexd-filesystem.obj `if test -f 'obexd/plugins/filesystem.c'; then $(CYGPATH_W) 'obexd/plugins/filesystem.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/filesystem.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-filesystem.Tpo obexd/plugins/$(DEPDIR)/obexd-filesystem.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/filesystem.c' object='obexd/plugins/obexd-filesystem.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-filesystem.obj `if test -f 'obexd/plugins/filesystem.c'; then $(CYGPATH_W) 'obexd/plugins/filesystem.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/filesystem.c'; fi` + +obexd/plugins/obexd-bluetooth.o: obexd/plugins/bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-bluetooth.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-bluetooth.Tpo -c -o obexd/plugins/obexd-bluetooth.o `test -f 'obexd/plugins/bluetooth.c' || echo '$(srcdir)/'`obexd/plugins/bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-bluetooth.Tpo obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/bluetooth.c' object='obexd/plugins/obexd-bluetooth.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-bluetooth.o `test -f 'obexd/plugins/bluetooth.c' || echo '$(srcdir)/'`obexd/plugins/bluetooth.c + +obexd/plugins/obexd-bluetooth.obj: obexd/plugins/bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-bluetooth.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-bluetooth.Tpo -c -o obexd/plugins/obexd-bluetooth.obj `if test -f 'obexd/plugins/bluetooth.c'; then $(CYGPATH_W) 'obexd/plugins/bluetooth.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/bluetooth.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-bluetooth.Tpo obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/bluetooth.c' object='obexd/plugins/obexd-bluetooth.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-bluetooth.obj `if test -f 'obexd/plugins/bluetooth.c'; then $(CYGPATH_W) 'obexd/plugins/bluetooth.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/bluetooth.c'; fi` + +obexd/plugins/obexd-pcsuite.o: obexd/plugins/pcsuite.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-pcsuite.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-pcsuite.Tpo -c -o obexd/plugins/obexd-pcsuite.o `test -f 'obexd/plugins/pcsuite.c' || echo '$(srcdir)/'`obexd/plugins/pcsuite.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-pcsuite.Tpo obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/pcsuite.c' object='obexd/plugins/obexd-pcsuite.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-pcsuite.o `test -f 'obexd/plugins/pcsuite.c' || echo '$(srcdir)/'`obexd/plugins/pcsuite.c + +obexd/plugins/obexd-pcsuite.obj: obexd/plugins/pcsuite.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-pcsuite.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-pcsuite.Tpo -c -o obexd/plugins/obexd-pcsuite.obj `if test -f 'obexd/plugins/pcsuite.c'; then $(CYGPATH_W) 'obexd/plugins/pcsuite.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/pcsuite.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-pcsuite.Tpo obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/pcsuite.c' object='obexd/plugins/obexd-pcsuite.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-pcsuite.obj `if test -f 'obexd/plugins/pcsuite.c'; then $(CYGPATH_W) 'obexd/plugins/pcsuite.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/pcsuite.c'; fi` + +obexd/plugins/obexd-opp.o: obexd/plugins/opp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-opp.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-opp.Tpo -c -o obexd/plugins/obexd-opp.o `test -f 'obexd/plugins/opp.c' || echo '$(srcdir)/'`obexd/plugins/opp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-opp.Tpo obexd/plugins/$(DEPDIR)/obexd-opp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/opp.c' object='obexd/plugins/obexd-opp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-opp.o `test -f 'obexd/plugins/opp.c' || echo '$(srcdir)/'`obexd/plugins/opp.c + +obexd/plugins/obexd-opp.obj: obexd/plugins/opp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-opp.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-opp.Tpo -c -o obexd/plugins/obexd-opp.obj `if test -f 'obexd/plugins/opp.c'; then $(CYGPATH_W) 'obexd/plugins/opp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/opp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-opp.Tpo obexd/plugins/$(DEPDIR)/obexd-opp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/opp.c' object='obexd/plugins/obexd-opp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-opp.obj `if test -f 'obexd/plugins/opp.c'; then $(CYGPATH_W) 'obexd/plugins/opp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/opp.c'; fi` + +obexd/plugins/obexd-ftp.o: obexd/plugins/ftp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-ftp.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-ftp.Tpo -c -o obexd/plugins/obexd-ftp.o `test -f 'obexd/plugins/ftp.c' || echo '$(srcdir)/'`obexd/plugins/ftp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-ftp.Tpo obexd/plugins/$(DEPDIR)/obexd-ftp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/ftp.c' object='obexd/plugins/obexd-ftp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-ftp.o `test -f 'obexd/plugins/ftp.c' || echo '$(srcdir)/'`obexd/plugins/ftp.c + +obexd/plugins/obexd-ftp.obj: obexd/plugins/ftp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-ftp.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-ftp.Tpo -c -o obexd/plugins/obexd-ftp.obj `if test -f 'obexd/plugins/ftp.c'; then $(CYGPATH_W) 'obexd/plugins/ftp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/ftp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-ftp.Tpo obexd/plugins/$(DEPDIR)/obexd-ftp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/ftp.c' object='obexd/plugins/obexd-ftp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-ftp.obj `if test -f 'obexd/plugins/ftp.c'; then $(CYGPATH_W) 'obexd/plugins/ftp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/ftp.c'; fi` + +obexd/plugins/obexd-irmc.o: obexd/plugins/irmc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-irmc.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-irmc.Tpo -c -o obexd/plugins/obexd-irmc.o `test -f 'obexd/plugins/irmc.c' || echo '$(srcdir)/'`obexd/plugins/irmc.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-irmc.Tpo obexd/plugins/$(DEPDIR)/obexd-irmc.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/irmc.c' object='obexd/plugins/obexd-irmc.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-irmc.o `test -f 'obexd/plugins/irmc.c' || echo '$(srcdir)/'`obexd/plugins/irmc.c + +obexd/plugins/obexd-irmc.obj: obexd/plugins/irmc.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-irmc.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-irmc.Tpo -c -o obexd/plugins/obexd-irmc.obj `if test -f 'obexd/plugins/irmc.c'; then $(CYGPATH_W) 'obexd/plugins/irmc.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/irmc.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-irmc.Tpo obexd/plugins/$(DEPDIR)/obexd-irmc.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/irmc.c' object='obexd/plugins/obexd-irmc.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-irmc.obj `if test -f 'obexd/plugins/irmc.c'; then $(CYGPATH_W) 'obexd/plugins/irmc.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/irmc.c'; fi` + +obexd/plugins/obexd-pbap.o: obexd/plugins/pbap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-pbap.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-pbap.Tpo -c -o obexd/plugins/obexd-pbap.o `test -f 'obexd/plugins/pbap.c' || echo '$(srcdir)/'`obexd/plugins/pbap.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-pbap.Tpo obexd/plugins/$(DEPDIR)/obexd-pbap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/pbap.c' object='obexd/plugins/obexd-pbap.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-pbap.o `test -f 'obexd/plugins/pbap.c' || echo '$(srcdir)/'`obexd/plugins/pbap.c + +obexd/plugins/obexd-pbap.obj: obexd/plugins/pbap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-pbap.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-pbap.Tpo -c -o obexd/plugins/obexd-pbap.obj `if test -f 'obexd/plugins/pbap.c'; then $(CYGPATH_W) 'obexd/plugins/pbap.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/pbap.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-pbap.Tpo obexd/plugins/$(DEPDIR)/obexd-pbap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/pbap.c' object='obexd/plugins/obexd-pbap.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-pbap.obj `if test -f 'obexd/plugins/pbap.c'; then $(CYGPATH_W) 'obexd/plugins/pbap.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/pbap.c'; fi` + +obexd/plugins/obexd-vcard.o: obexd/plugins/vcard.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-vcard.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-vcard.Tpo -c -o obexd/plugins/obexd-vcard.o `test -f 'obexd/plugins/vcard.c' || echo '$(srcdir)/'`obexd/plugins/vcard.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-vcard.Tpo obexd/plugins/$(DEPDIR)/obexd-vcard.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/vcard.c' object='obexd/plugins/obexd-vcard.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-vcard.o `test -f 'obexd/plugins/vcard.c' || echo '$(srcdir)/'`obexd/plugins/vcard.c + +obexd/plugins/obexd-vcard.obj: obexd/plugins/vcard.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-vcard.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-vcard.Tpo -c -o obexd/plugins/obexd-vcard.obj `if test -f 'obexd/plugins/vcard.c'; then $(CYGPATH_W) 'obexd/plugins/vcard.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/vcard.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-vcard.Tpo obexd/plugins/$(DEPDIR)/obexd-vcard.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/vcard.c' object='obexd/plugins/obexd-vcard.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-vcard.obj `if test -f 'obexd/plugins/vcard.c'; then $(CYGPATH_W) 'obexd/plugins/vcard.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/vcard.c'; fi` + +obexd/plugins/obexd-phonebook-dummy.o: obexd/plugins/phonebook-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-phonebook-dummy.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-phonebook-dummy.Tpo -c -o obexd/plugins/obexd-phonebook-dummy.o `test -f 'obexd/plugins/phonebook-dummy.c' || echo '$(srcdir)/'`obexd/plugins/phonebook-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-phonebook-dummy.Tpo obexd/plugins/$(DEPDIR)/obexd-phonebook-dummy.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/phonebook-dummy.c' object='obexd/plugins/obexd-phonebook-dummy.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-phonebook-dummy.o `test -f 'obexd/plugins/phonebook-dummy.c' || echo '$(srcdir)/'`obexd/plugins/phonebook-dummy.c + +obexd/plugins/obexd-phonebook-dummy.obj: obexd/plugins/phonebook-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-phonebook-dummy.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-phonebook-dummy.Tpo -c -o obexd/plugins/obexd-phonebook-dummy.obj `if test -f 'obexd/plugins/phonebook-dummy.c'; then $(CYGPATH_W) 'obexd/plugins/phonebook-dummy.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/phonebook-dummy.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-phonebook-dummy.Tpo obexd/plugins/$(DEPDIR)/obexd-phonebook-dummy.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/phonebook-dummy.c' object='obexd/plugins/obexd-phonebook-dummy.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-phonebook-dummy.obj `if test -f 'obexd/plugins/phonebook-dummy.c'; then $(CYGPATH_W) 'obexd/plugins/phonebook-dummy.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/phonebook-dummy.c'; fi` + +obexd/plugins/obexd-mas.o: obexd/plugins/mas.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-mas.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-mas.Tpo -c -o obexd/plugins/obexd-mas.o `test -f 'obexd/plugins/mas.c' || echo '$(srcdir)/'`obexd/plugins/mas.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-mas.Tpo obexd/plugins/$(DEPDIR)/obexd-mas.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/mas.c' object='obexd/plugins/obexd-mas.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-mas.o `test -f 'obexd/plugins/mas.c' || echo '$(srcdir)/'`obexd/plugins/mas.c + +obexd/plugins/obexd-mas.obj: obexd/plugins/mas.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-mas.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-mas.Tpo -c -o obexd/plugins/obexd-mas.obj `if test -f 'obexd/plugins/mas.c'; then $(CYGPATH_W) 'obexd/plugins/mas.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/mas.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-mas.Tpo obexd/plugins/$(DEPDIR)/obexd-mas.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/mas.c' object='obexd/plugins/obexd-mas.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-mas.obj `if test -f 'obexd/plugins/mas.c'; then $(CYGPATH_W) 'obexd/plugins/mas.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/mas.c'; fi` + +obexd/plugins/obexd-messages-dummy.o: obexd/plugins/messages-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-messages-dummy.o -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Tpo -c -o obexd/plugins/obexd-messages-dummy.o `test -f 'obexd/plugins/messages-dummy.c' || echo '$(srcdir)/'`obexd/plugins/messages-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Tpo obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/messages-dummy.c' object='obexd/plugins/obexd-messages-dummy.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-messages-dummy.o `test -f 'obexd/plugins/messages-dummy.c' || echo '$(srcdir)/'`obexd/plugins/messages-dummy.c + +obexd/plugins/obexd-messages-dummy.obj: obexd/plugins/messages-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/plugins/obexd-messages-dummy.obj -MD -MP -MF obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Tpo -c -o obexd/plugins/obexd-messages-dummy.obj `if test -f 'obexd/plugins/messages-dummy.c'; then $(CYGPATH_W) 'obexd/plugins/messages-dummy.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/messages-dummy.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Tpo obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/plugins/messages-dummy.c' object='obexd/plugins/obexd-messages-dummy.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/plugins/obexd-messages-dummy.obj `if test -f 'obexd/plugins/messages-dummy.c'; then $(CYGPATH_W) 'obexd/plugins/messages-dummy.c'; else $(CYGPATH_W) '$(srcdir)/obexd/plugins/messages-dummy.c'; fi` + +obexd/client/obexd-mns.o: obexd/client/mns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-mns.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-mns.Tpo -c -o obexd/client/obexd-mns.o `test -f 'obexd/client/mns.c' || echo '$(srcdir)/'`obexd/client/mns.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-mns.Tpo obexd/client/$(DEPDIR)/obexd-mns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/mns.c' object='obexd/client/obexd-mns.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-mns.o `test -f 'obexd/client/mns.c' || echo '$(srcdir)/'`obexd/client/mns.c + +obexd/client/obexd-mns.obj: obexd/client/mns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-mns.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-mns.Tpo -c -o obexd/client/obexd-mns.obj `if test -f 'obexd/client/mns.c'; then $(CYGPATH_W) 'obexd/client/mns.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/mns.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-mns.Tpo obexd/client/$(DEPDIR)/obexd-mns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/mns.c' object='obexd/client/obexd-mns.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-mns.obj `if test -f 'obexd/client/mns.c'; then $(CYGPATH_W) 'obexd/client/mns.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/mns.c'; fi` + +obexd/src/obexd-main.o: obexd/src/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-main.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-main.Tpo -c -o obexd/src/obexd-main.o `test -f 'obexd/src/main.c' || echo '$(srcdir)/'`obexd/src/main.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-main.Tpo obexd/src/$(DEPDIR)/obexd-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/main.c' object='obexd/src/obexd-main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-main.o `test -f 'obexd/src/main.c' || echo '$(srcdir)/'`obexd/src/main.c + +obexd/src/obexd-main.obj: obexd/src/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-main.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-main.Tpo -c -o obexd/src/obexd-main.obj `if test -f 'obexd/src/main.c'; then $(CYGPATH_W) 'obexd/src/main.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/main.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-main.Tpo obexd/src/$(DEPDIR)/obexd-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/main.c' object='obexd/src/obexd-main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-main.obj `if test -f 'obexd/src/main.c'; then $(CYGPATH_W) 'obexd/src/main.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/main.c'; fi` + +obexd/src/obexd-plugin.o: obexd/src/plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-plugin.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-plugin.Tpo -c -o obexd/src/obexd-plugin.o `test -f 'obexd/src/plugin.c' || echo '$(srcdir)/'`obexd/src/plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-plugin.Tpo obexd/src/$(DEPDIR)/obexd-plugin.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/plugin.c' object='obexd/src/obexd-plugin.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-plugin.o `test -f 'obexd/src/plugin.c' || echo '$(srcdir)/'`obexd/src/plugin.c + +obexd/src/obexd-plugin.obj: obexd/src/plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-plugin.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-plugin.Tpo -c -o obexd/src/obexd-plugin.obj `if test -f 'obexd/src/plugin.c'; then $(CYGPATH_W) 'obexd/src/plugin.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/plugin.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-plugin.Tpo obexd/src/$(DEPDIR)/obexd-plugin.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/plugin.c' object='obexd/src/obexd-plugin.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-plugin.obj `if test -f 'obexd/src/plugin.c'; then $(CYGPATH_W) 'obexd/src/plugin.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/plugin.c'; fi` + +obexd/src/obexd-log.o: obexd/src/log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-log.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-log.Tpo -c -o obexd/src/obexd-log.o `test -f 'obexd/src/log.c' || echo '$(srcdir)/'`obexd/src/log.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-log.Tpo obexd/src/$(DEPDIR)/obexd-log.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/log.c' object='obexd/src/obexd-log.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-log.o `test -f 'obexd/src/log.c' || echo '$(srcdir)/'`obexd/src/log.c + +obexd/src/obexd-log.obj: obexd/src/log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-log.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-log.Tpo -c -o obexd/src/obexd-log.obj `if test -f 'obexd/src/log.c'; then $(CYGPATH_W) 'obexd/src/log.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/log.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-log.Tpo obexd/src/$(DEPDIR)/obexd-log.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/log.c' object='obexd/src/obexd-log.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-log.obj `if test -f 'obexd/src/log.c'; then $(CYGPATH_W) 'obexd/src/log.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/log.c'; fi` + +obexd/src/obexd-manager.o: obexd/src/manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-manager.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-manager.Tpo -c -o obexd/src/obexd-manager.o `test -f 'obexd/src/manager.c' || echo '$(srcdir)/'`obexd/src/manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-manager.Tpo obexd/src/$(DEPDIR)/obexd-manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/manager.c' object='obexd/src/obexd-manager.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-manager.o `test -f 'obexd/src/manager.c' || echo '$(srcdir)/'`obexd/src/manager.c + +obexd/src/obexd-manager.obj: obexd/src/manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-manager.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-manager.Tpo -c -o obexd/src/obexd-manager.obj `if test -f 'obexd/src/manager.c'; then $(CYGPATH_W) 'obexd/src/manager.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/manager.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-manager.Tpo obexd/src/$(DEPDIR)/obexd-manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/manager.c' object='obexd/src/obexd-manager.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-manager.obj `if test -f 'obexd/src/manager.c'; then $(CYGPATH_W) 'obexd/src/manager.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/manager.c'; fi` + +obexd/src/obexd-obex.o: obexd/src/obex.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-obex.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-obex.Tpo -c -o obexd/src/obexd-obex.o `test -f 'obexd/src/obex.c' || echo '$(srcdir)/'`obexd/src/obex.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-obex.Tpo obexd/src/$(DEPDIR)/obexd-obex.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/obex.c' object='obexd/src/obexd-obex.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-obex.o `test -f 'obexd/src/obex.c' || echo '$(srcdir)/'`obexd/src/obex.c + +obexd/src/obexd-obex.obj: obexd/src/obex.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-obex.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-obex.Tpo -c -o obexd/src/obexd-obex.obj `if test -f 'obexd/src/obex.c'; then $(CYGPATH_W) 'obexd/src/obex.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/obex.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-obex.Tpo obexd/src/$(DEPDIR)/obexd-obex.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/obex.c' object='obexd/src/obexd-obex.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-obex.obj `if test -f 'obexd/src/obex.c'; then $(CYGPATH_W) 'obexd/src/obex.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/obex.c'; fi` + +obexd/src/obexd-mimetype.o: obexd/src/mimetype.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-mimetype.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-mimetype.Tpo -c -o obexd/src/obexd-mimetype.o `test -f 'obexd/src/mimetype.c' || echo '$(srcdir)/'`obexd/src/mimetype.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-mimetype.Tpo obexd/src/$(DEPDIR)/obexd-mimetype.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/mimetype.c' object='obexd/src/obexd-mimetype.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-mimetype.o `test -f 'obexd/src/mimetype.c' || echo '$(srcdir)/'`obexd/src/mimetype.c + +obexd/src/obexd-mimetype.obj: obexd/src/mimetype.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-mimetype.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-mimetype.Tpo -c -o obexd/src/obexd-mimetype.obj `if test -f 'obexd/src/mimetype.c'; then $(CYGPATH_W) 'obexd/src/mimetype.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/mimetype.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-mimetype.Tpo obexd/src/$(DEPDIR)/obexd-mimetype.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/mimetype.c' object='obexd/src/obexd-mimetype.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-mimetype.obj `if test -f 'obexd/src/mimetype.c'; then $(CYGPATH_W) 'obexd/src/mimetype.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/mimetype.c'; fi` + +obexd/src/obexd-service.o: obexd/src/service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-service.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-service.Tpo -c -o obexd/src/obexd-service.o `test -f 'obexd/src/service.c' || echo '$(srcdir)/'`obexd/src/service.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-service.Tpo obexd/src/$(DEPDIR)/obexd-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/service.c' object='obexd/src/obexd-service.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-service.o `test -f 'obexd/src/service.c' || echo '$(srcdir)/'`obexd/src/service.c + +obexd/src/obexd-service.obj: obexd/src/service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-service.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-service.Tpo -c -o obexd/src/obexd-service.obj `if test -f 'obexd/src/service.c'; then $(CYGPATH_W) 'obexd/src/service.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/service.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-service.Tpo obexd/src/$(DEPDIR)/obexd-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/service.c' object='obexd/src/obexd-service.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-service.obj `if test -f 'obexd/src/service.c'; then $(CYGPATH_W) 'obexd/src/service.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/service.c'; fi` + +obexd/src/obexd-transport.o: obexd/src/transport.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-transport.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-transport.Tpo -c -o obexd/src/obexd-transport.o `test -f 'obexd/src/transport.c' || echo '$(srcdir)/'`obexd/src/transport.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-transport.Tpo obexd/src/$(DEPDIR)/obexd-transport.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/transport.c' object='obexd/src/obexd-transport.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-transport.o `test -f 'obexd/src/transport.c' || echo '$(srcdir)/'`obexd/src/transport.c + +obexd/src/obexd-transport.obj: obexd/src/transport.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-transport.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-transport.Tpo -c -o obexd/src/obexd-transport.obj `if test -f 'obexd/src/transport.c'; then $(CYGPATH_W) 'obexd/src/transport.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/transport.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-transport.Tpo obexd/src/$(DEPDIR)/obexd-transport.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/transport.c' object='obexd/src/obexd-transport.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-transport.obj `if test -f 'obexd/src/transport.c'; then $(CYGPATH_W) 'obexd/src/transport.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/transport.c'; fi` + +obexd/src/obexd-server.o: obexd/src/server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-server.o -MD -MP -MF obexd/src/$(DEPDIR)/obexd-server.Tpo -c -o obexd/src/obexd-server.o `test -f 'obexd/src/server.c' || echo '$(srcdir)/'`obexd/src/server.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-server.Tpo obexd/src/$(DEPDIR)/obexd-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/server.c' object='obexd/src/obexd-server.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-server.o `test -f 'obexd/src/server.c' || echo '$(srcdir)/'`obexd/src/server.c + +obexd/src/obexd-server.obj: obexd/src/server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/src/obexd-server.obj -MD -MP -MF obexd/src/$(DEPDIR)/obexd-server.Tpo -c -o obexd/src/obexd-server.obj `if test -f 'obexd/src/server.c'; then $(CYGPATH_W) 'obexd/src/server.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/server.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/src/$(DEPDIR)/obexd-server.Tpo obexd/src/$(DEPDIR)/obexd-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/src/server.c' object='obexd/src/obexd-server.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/src/obexd-server.obj `if test -f 'obexd/src/server.c'; then $(CYGPATH_W) 'obexd/src/server.c'; else $(CYGPATH_W) '$(srcdir)/obexd/src/server.c'; fi` + +obexd/client/obexd-manager.o: obexd/client/manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-manager.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-manager.Tpo -c -o obexd/client/obexd-manager.o `test -f 'obexd/client/manager.c' || echo '$(srcdir)/'`obexd/client/manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-manager.Tpo obexd/client/$(DEPDIR)/obexd-manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/manager.c' object='obexd/client/obexd-manager.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-manager.o `test -f 'obexd/client/manager.c' || echo '$(srcdir)/'`obexd/client/manager.c + +obexd/client/obexd-manager.obj: obexd/client/manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-manager.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-manager.Tpo -c -o obexd/client/obexd-manager.obj `if test -f 'obexd/client/manager.c'; then $(CYGPATH_W) 'obexd/client/manager.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/manager.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-manager.Tpo obexd/client/$(DEPDIR)/obexd-manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/manager.c' object='obexd/client/obexd-manager.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-manager.obj `if test -f 'obexd/client/manager.c'; then $(CYGPATH_W) 'obexd/client/manager.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/manager.c'; fi` + +obexd/client/obexd-session.o: obexd/client/session.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-session.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-session.Tpo -c -o obexd/client/obexd-session.o `test -f 'obexd/client/session.c' || echo '$(srcdir)/'`obexd/client/session.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-session.Tpo obexd/client/$(DEPDIR)/obexd-session.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/session.c' object='obexd/client/obexd-session.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-session.o `test -f 'obexd/client/session.c' || echo '$(srcdir)/'`obexd/client/session.c + +obexd/client/obexd-session.obj: obexd/client/session.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-session.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-session.Tpo -c -o obexd/client/obexd-session.obj `if test -f 'obexd/client/session.c'; then $(CYGPATH_W) 'obexd/client/session.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/session.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-session.Tpo obexd/client/$(DEPDIR)/obexd-session.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/session.c' object='obexd/client/obexd-session.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-session.obj `if test -f 'obexd/client/session.c'; then $(CYGPATH_W) 'obexd/client/session.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/session.c'; fi` + +obexd/client/obexd-bluetooth.o: obexd/client/bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-bluetooth.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-bluetooth.Tpo -c -o obexd/client/obexd-bluetooth.o `test -f 'obexd/client/bluetooth.c' || echo '$(srcdir)/'`obexd/client/bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-bluetooth.Tpo obexd/client/$(DEPDIR)/obexd-bluetooth.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/bluetooth.c' object='obexd/client/obexd-bluetooth.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-bluetooth.o `test -f 'obexd/client/bluetooth.c' || echo '$(srcdir)/'`obexd/client/bluetooth.c + +obexd/client/obexd-bluetooth.obj: obexd/client/bluetooth.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-bluetooth.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-bluetooth.Tpo -c -o obexd/client/obexd-bluetooth.obj `if test -f 'obexd/client/bluetooth.c'; then $(CYGPATH_W) 'obexd/client/bluetooth.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/bluetooth.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-bluetooth.Tpo obexd/client/$(DEPDIR)/obexd-bluetooth.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/bluetooth.c' object='obexd/client/obexd-bluetooth.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-bluetooth.obj `if test -f 'obexd/client/bluetooth.c'; then $(CYGPATH_W) 'obexd/client/bluetooth.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/bluetooth.c'; fi` + +obexd/client/obexd-sync.o: obexd/client/sync.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-sync.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-sync.Tpo -c -o obexd/client/obexd-sync.o `test -f 'obexd/client/sync.c' || echo '$(srcdir)/'`obexd/client/sync.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-sync.Tpo obexd/client/$(DEPDIR)/obexd-sync.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/sync.c' object='obexd/client/obexd-sync.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-sync.o `test -f 'obexd/client/sync.c' || echo '$(srcdir)/'`obexd/client/sync.c + +obexd/client/obexd-sync.obj: obexd/client/sync.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-sync.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-sync.Tpo -c -o obexd/client/obexd-sync.obj `if test -f 'obexd/client/sync.c'; then $(CYGPATH_W) 'obexd/client/sync.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/sync.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-sync.Tpo obexd/client/$(DEPDIR)/obexd-sync.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/sync.c' object='obexd/client/obexd-sync.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-sync.obj `if test -f 'obexd/client/sync.c'; then $(CYGPATH_W) 'obexd/client/sync.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/sync.c'; fi` + +obexd/client/obexd-pbap.o: obexd/client/pbap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-pbap.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-pbap.Tpo -c -o obexd/client/obexd-pbap.o `test -f 'obexd/client/pbap.c' || echo '$(srcdir)/'`obexd/client/pbap.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-pbap.Tpo obexd/client/$(DEPDIR)/obexd-pbap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/pbap.c' object='obexd/client/obexd-pbap.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-pbap.o `test -f 'obexd/client/pbap.c' || echo '$(srcdir)/'`obexd/client/pbap.c + +obexd/client/obexd-pbap.obj: obexd/client/pbap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-pbap.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-pbap.Tpo -c -o obexd/client/obexd-pbap.obj `if test -f 'obexd/client/pbap.c'; then $(CYGPATH_W) 'obexd/client/pbap.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/pbap.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-pbap.Tpo obexd/client/$(DEPDIR)/obexd-pbap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/pbap.c' object='obexd/client/obexd-pbap.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-pbap.obj `if test -f 'obexd/client/pbap.c'; then $(CYGPATH_W) 'obexd/client/pbap.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/pbap.c'; fi` + +obexd/client/obexd-ftp.o: obexd/client/ftp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-ftp.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-ftp.Tpo -c -o obexd/client/obexd-ftp.o `test -f 'obexd/client/ftp.c' || echo '$(srcdir)/'`obexd/client/ftp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-ftp.Tpo obexd/client/$(DEPDIR)/obexd-ftp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/ftp.c' object='obexd/client/obexd-ftp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-ftp.o `test -f 'obexd/client/ftp.c' || echo '$(srcdir)/'`obexd/client/ftp.c + +obexd/client/obexd-ftp.obj: obexd/client/ftp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-ftp.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-ftp.Tpo -c -o obexd/client/obexd-ftp.obj `if test -f 'obexd/client/ftp.c'; then $(CYGPATH_W) 'obexd/client/ftp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/ftp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-ftp.Tpo obexd/client/$(DEPDIR)/obexd-ftp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/ftp.c' object='obexd/client/obexd-ftp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-ftp.obj `if test -f 'obexd/client/ftp.c'; then $(CYGPATH_W) 'obexd/client/ftp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/ftp.c'; fi` + +obexd/client/obexd-opp.o: obexd/client/opp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-opp.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-opp.Tpo -c -o obexd/client/obexd-opp.o `test -f 'obexd/client/opp.c' || echo '$(srcdir)/'`obexd/client/opp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-opp.Tpo obexd/client/$(DEPDIR)/obexd-opp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/opp.c' object='obexd/client/obexd-opp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-opp.o `test -f 'obexd/client/opp.c' || echo '$(srcdir)/'`obexd/client/opp.c + +obexd/client/obexd-opp.obj: obexd/client/opp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-opp.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-opp.Tpo -c -o obexd/client/obexd-opp.obj `if test -f 'obexd/client/opp.c'; then $(CYGPATH_W) 'obexd/client/opp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/opp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-opp.Tpo obexd/client/$(DEPDIR)/obexd-opp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/opp.c' object='obexd/client/obexd-opp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-opp.obj `if test -f 'obexd/client/opp.c'; then $(CYGPATH_W) 'obexd/client/opp.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/opp.c'; fi` + +obexd/client/obexd-map.o: obexd/client/map.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-map.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-map.Tpo -c -o obexd/client/obexd-map.o `test -f 'obexd/client/map.c' || echo '$(srcdir)/'`obexd/client/map.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-map.Tpo obexd/client/$(DEPDIR)/obexd-map.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/map.c' object='obexd/client/obexd-map.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-map.o `test -f 'obexd/client/map.c' || echo '$(srcdir)/'`obexd/client/map.c + +obexd/client/obexd-map.obj: obexd/client/map.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-map.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-map.Tpo -c -o obexd/client/obexd-map.obj `if test -f 'obexd/client/map.c'; then $(CYGPATH_W) 'obexd/client/map.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/map.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-map.Tpo obexd/client/$(DEPDIR)/obexd-map.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/map.c' object='obexd/client/obexd-map.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-map.obj `if test -f 'obexd/client/map.c'; then $(CYGPATH_W) 'obexd/client/map.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/map.c'; fi` + +obexd/client/obexd-map-event.o: obexd/client/map-event.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-map-event.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-map-event.Tpo -c -o obexd/client/obexd-map-event.o `test -f 'obexd/client/map-event.c' || echo '$(srcdir)/'`obexd/client/map-event.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-map-event.Tpo obexd/client/$(DEPDIR)/obexd-map-event.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/map-event.c' object='obexd/client/obexd-map-event.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-map-event.o `test -f 'obexd/client/map-event.c' || echo '$(srcdir)/'`obexd/client/map-event.c + +obexd/client/obexd-map-event.obj: obexd/client/map-event.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-map-event.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-map-event.Tpo -c -o obexd/client/obexd-map-event.obj `if test -f 'obexd/client/map-event.c'; then $(CYGPATH_W) 'obexd/client/map-event.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/map-event.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-map-event.Tpo obexd/client/$(DEPDIR)/obexd-map-event.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/map-event.c' object='obexd/client/obexd-map-event.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-map-event.obj `if test -f 'obexd/client/map-event.c'; then $(CYGPATH_W) 'obexd/client/map-event.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/map-event.c'; fi` + +obexd/client/obexd-transfer.o: obexd/client/transfer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-transfer.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-transfer.Tpo -c -o obexd/client/obexd-transfer.o `test -f 'obexd/client/transfer.c' || echo '$(srcdir)/'`obexd/client/transfer.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-transfer.Tpo obexd/client/$(DEPDIR)/obexd-transfer.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/transfer.c' object='obexd/client/obexd-transfer.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-transfer.o `test -f 'obexd/client/transfer.c' || echo '$(srcdir)/'`obexd/client/transfer.c + +obexd/client/obexd-transfer.obj: obexd/client/transfer.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-transfer.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-transfer.Tpo -c -o obexd/client/obexd-transfer.obj `if test -f 'obexd/client/transfer.c'; then $(CYGPATH_W) 'obexd/client/transfer.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/transfer.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-transfer.Tpo obexd/client/$(DEPDIR)/obexd-transfer.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/transfer.c' object='obexd/client/obexd-transfer.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-transfer.obj `if test -f 'obexd/client/transfer.c'; then $(CYGPATH_W) 'obexd/client/transfer.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/transfer.c'; fi` + +obexd/client/obexd-transport.o: obexd/client/transport.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-transport.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-transport.Tpo -c -o obexd/client/obexd-transport.o `test -f 'obexd/client/transport.c' || echo '$(srcdir)/'`obexd/client/transport.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-transport.Tpo obexd/client/$(DEPDIR)/obexd-transport.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/transport.c' object='obexd/client/obexd-transport.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-transport.o `test -f 'obexd/client/transport.c' || echo '$(srcdir)/'`obexd/client/transport.c + +obexd/client/obexd-transport.obj: obexd/client/transport.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-transport.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-transport.Tpo -c -o obexd/client/obexd-transport.obj `if test -f 'obexd/client/transport.c'; then $(CYGPATH_W) 'obexd/client/transport.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/transport.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-transport.Tpo obexd/client/$(DEPDIR)/obexd-transport.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/transport.c' object='obexd/client/obexd-transport.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-transport.obj `if test -f 'obexd/client/transport.c'; then $(CYGPATH_W) 'obexd/client/transport.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/transport.c'; fi` + +obexd/client/obexd-driver.o: obexd/client/driver.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-driver.o -MD -MP -MF obexd/client/$(DEPDIR)/obexd-driver.Tpo -c -o obexd/client/obexd-driver.o `test -f 'obexd/client/driver.c' || echo '$(srcdir)/'`obexd/client/driver.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-driver.Tpo obexd/client/$(DEPDIR)/obexd-driver.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/driver.c' object='obexd/client/obexd-driver.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-driver.o `test -f 'obexd/client/driver.c' || echo '$(srcdir)/'`obexd/client/driver.c + +obexd/client/obexd-driver.obj: obexd/client/driver.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -MT obexd/client/obexd-driver.obj -MD -MP -MF obexd/client/$(DEPDIR)/obexd-driver.Tpo -c -o obexd/client/obexd-driver.obj `if test -f 'obexd/client/driver.c'; then $(CYGPATH_W) 'obexd/client/driver.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/driver.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) obexd/client/$(DEPDIR)/obexd-driver.Tpo obexd/client/$(DEPDIR)/obexd-driver.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obexd/client/driver.c' object='obexd/client/obexd-driver.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(obexd_src_obexd_CPPFLAGS) $(CPPFLAGS) $(obexd_src_obexd_CFLAGS) $(CFLAGS) -c -o obexd/client/obexd-driver.obj `if test -f 'obexd/client/driver.c'; then $(CYGPATH_W) 'obexd/client/driver.c'; else $(CYGPATH_W) '$(srcdir)/obexd/client/driver.c'; fi` + +plugins/bluetoothd-hostname.o: plugins/hostname.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-hostname.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-hostname.Tpo -c -o plugins/bluetoothd-hostname.o `test -f 'plugins/hostname.c' || echo '$(srcdir)/'`plugins/hostname.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-hostname.Tpo plugins/$(DEPDIR)/bluetoothd-hostname.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/hostname.c' object='plugins/bluetoothd-hostname.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-hostname.o `test -f 'plugins/hostname.c' || echo '$(srcdir)/'`plugins/hostname.c + +plugins/bluetoothd-hostname.obj: plugins/hostname.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-hostname.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-hostname.Tpo -c -o plugins/bluetoothd-hostname.obj `if test -f 'plugins/hostname.c'; then $(CYGPATH_W) 'plugins/hostname.c'; else $(CYGPATH_W) '$(srcdir)/plugins/hostname.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-hostname.Tpo plugins/$(DEPDIR)/bluetoothd-hostname.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/hostname.c' object='plugins/bluetoothd-hostname.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-hostname.obj `if test -f 'plugins/hostname.c'; then $(CYGPATH_W) 'plugins/hostname.c'; else $(CYGPATH_W) '$(srcdir)/plugins/hostname.c'; fi` + +plugins/bluetoothd-wiimote.o: plugins/wiimote.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-wiimote.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-wiimote.Tpo -c -o plugins/bluetoothd-wiimote.o `test -f 'plugins/wiimote.c' || echo '$(srcdir)/'`plugins/wiimote.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-wiimote.Tpo plugins/$(DEPDIR)/bluetoothd-wiimote.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/wiimote.c' object='plugins/bluetoothd-wiimote.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-wiimote.o `test -f 'plugins/wiimote.c' || echo '$(srcdir)/'`plugins/wiimote.c + +plugins/bluetoothd-wiimote.obj: plugins/wiimote.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-wiimote.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-wiimote.Tpo -c -o plugins/bluetoothd-wiimote.obj `if test -f 'plugins/wiimote.c'; then $(CYGPATH_W) 'plugins/wiimote.c'; else $(CYGPATH_W) '$(srcdir)/plugins/wiimote.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-wiimote.Tpo plugins/$(DEPDIR)/bluetoothd-wiimote.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/wiimote.c' object='plugins/bluetoothd-wiimote.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-wiimote.obj `if test -f 'plugins/wiimote.c'; then $(CYGPATH_W) 'plugins/wiimote.c'; else $(CYGPATH_W) '$(srcdir)/plugins/wiimote.c'; fi` + +plugins/bluetoothd-autopair.o: plugins/autopair.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-autopair.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-autopair.Tpo -c -o plugins/bluetoothd-autopair.o `test -f 'plugins/autopair.c' || echo '$(srcdir)/'`plugins/autopair.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-autopair.Tpo plugins/$(DEPDIR)/bluetoothd-autopair.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/autopair.c' object='plugins/bluetoothd-autopair.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-autopair.o `test -f 'plugins/autopair.c' || echo '$(srcdir)/'`plugins/autopair.c + +plugins/bluetoothd-autopair.obj: plugins/autopair.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-autopair.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-autopair.Tpo -c -o plugins/bluetoothd-autopair.obj `if test -f 'plugins/autopair.c'; then $(CYGPATH_W) 'plugins/autopair.c'; else $(CYGPATH_W) '$(srcdir)/plugins/autopair.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-autopair.Tpo plugins/$(DEPDIR)/bluetoothd-autopair.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/autopair.c' object='plugins/bluetoothd-autopair.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-autopair.obj `if test -f 'plugins/autopair.c'; then $(CYGPATH_W) 'plugins/autopair.c'; else $(CYGPATH_W) '$(srcdir)/plugins/autopair.c'; fi` + +plugins/bluetoothd-policy.o: plugins/policy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-policy.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-policy.Tpo -c -o plugins/bluetoothd-policy.o `test -f 'plugins/policy.c' || echo '$(srcdir)/'`plugins/policy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-policy.Tpo plugins/$(DEPDIR)/bluetoothd-policy.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/policy.c' object='plugins/bluetoothd-policy.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-policy.o `test -f 'plugins/policy.c' || echo '$(srcdir)/'`plugins/policy.c + +plugins/bluetoothd-policy.obj: plugins/policy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-policy.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-policy.Tpo -c -o plugins/bluetoothd-policy.obj `if test -f 'plugins/policy.c'; then $(CYGPATH_W) 'plugins/policy.c'; else $(CYGPATH_W) '$(srcdir)/plugins/policy.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-policy.Tpo plugins/$(DEPDIR)/bluetoothd-policy.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/policy.c' object='plugins/bluetoothd-policy.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-policy.obj `if test -f 'plugins/policy.c'; then $(CYGPATH_W) 'plugins/policy.c'; else $(CYGPATH_W) '$(srcdir)/plugins/policy.c'; fi` + +plugins/bluetoothd-neard.o: plugins/neard.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-neard.o -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-neard.Tpo -c -o plugins/bluetoothd-neard.o `test -f 'plugins/neard.c' || echo '$(srcdir)/'`plugins/neard.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-neard.Tpo plugins/$(DEPDIR)/bluetoothd-neard.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/neard.c' object='plugins/bluetoothd-neard.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-neard.o `test -f 'plugins/neard.c' || echo '$(srcdir)/'`plugins/neard.c + +plugins/bluetoothd-neard.obj: plugins/neard.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT plugins/bluetoothd-neard.obj -MD -MP -MF plugins/$(DEPDIR)/bluetoothd-neard.Tpo -c -o plugins/bluetoothd-neard.obj `if test -f 'plugins/neard.c'; then $(CYGPATH_W) 'plugins/neard.c'; else $(CYGPATH_W) '$(srcdir)/plugins/neard.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) plugins/$(DEPDIR)/bluetoothd-neard.Tpo plugins/$(DEPDIR)/bluetoothd-neard.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='plugins/neard.c' object='plugins/bluetoothd-neard.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o plugins/bluetoothd-neard.obj `if test -f 'plugins/neard.c'; then $(CYGPATH_W) 'plugins/neard.c'; else $(CYGPATH_W) '$(srcdir)/plugins/neard.c'; fi` + +profiles/sap/bluetoothd-main.o: profiles/sap/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/sap/bluetoothd-main.o -MD -MP -MF profiles/sap/$(DEPDIR)/bluetoothd-main.Tpo -c -o profiles/sap/bluetoothd-main.o `test -f 'profiles/sap/main.c' || echo '$(srcdir)/'`profiles/sap/main.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/sap/$(DEPDIR)/bluetoothd-main.Tpo profiles/sap/$(DEPDIR)/bluetoothd-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/sap/main.c' object='profiles/sap/bluetoothd-main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/sap/bluetoothd-main.o `test -f 'profiles/sap/main.c' || echo '$(srcdir)/'`profiles/sap/main.c + +profiles/sap/bluetoothd-main.obj: profiles/sap/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/sap/bluetoothd-main.obj -MD -MP -MF profiles/sap/$(DEPDIR)/bluetoothd-main.Tpo -c -o profiles/sap/bluetoothd-main.obj `if test -f 'profiles/sap/main.c'; then $(CYGPATH_W) 'profiles/sap/main.c'; else $(CYGPATH_W) '$(srcdir)/profiles/sap/main.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/sap/$(DEPDIR)/bluetoothd-main.Tpo profiles/sap/$(DEPDIR)/bluetoothd-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/sap/main.c' object='profiles/sap/bluetoothd-main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/sap/bluetoothd-main.obj `if test -f 'profiles/sap/main.c'; then $(CYGPATH_W) 'profiles/sap/main.c'; else $(CYGPATH_W) '$(srcdir)/profiles/sap/main.c'; fi` + +profiles/sap/bluetoothd-manager.o: profiles/sap/manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/sap/bluetoothd-manager.o -MD -MP -MF profiles/sap/$(DEPDIR)/bluetoothd-manager.Tpo -c -o profiles/sap/bluetoothd-manager.o `test -f 'profiles/sap/manager.c' || echo '$(srcdir)/'`profiles/sap/manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/sap/$(DEPDIR)/bluetoothd-manager.Tpo profiles/sap/$(DEPDIR)/bluetoothd-manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/sap/manager.c' object='profiles/sap/bluetoothd-manager.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/sap/bluetoothd-manager.o `test -f 'profiles/sap/manager.c' || echo '$(srcdir)/'`profiles/sap/manager.c + +profiles/sap/bluetoothd-manager.obj: profiles/sap/manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/sap/bluetoothd-manager.obj -MD -MP -MF profiles/sap/$(DEPDIR)/bluetoothd-manager.Tpo -c -o profiles/sap/bluetoothd-manager.obj `if test -f 'profiles/sap/manager.c'; then $(CYGPATH_W) 'profiles/sap/manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/sap/manager.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/sap/$(DEPDIR)/bluetoothd-manager.Tpo profiles/sap/$(DEPDIR)/bluetoothd-manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/sap/manager.c' object='profiles/sap/bluetoothd-manager.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/sap/bluetoothd-manager.obj `if test -f 'profiles/sap/manager.c'; then $(CYGPATH_W) 'profiles/sap/manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/sap/manager.c'; fi` + +profiles/sap/bluetoothd-server.o: profiles/sap/server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/sap/bluetoothd-server.o -MD -MP -MF profiles/sap/$(DEPDIR)/bluetoothd-server.Tpo -c -o profiles/sap/bluetoothd-server.o `test -f 'profiles/sap/server.c' || echo '$(srcdir)/'`profiles/sap/server.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/sap/$(DEPDIR)/bluetoothd-server.Tpo profiles/sap/$(DEPDIR)/bluetoothd-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/sap/server.c' object='profiles/sap/bluetoothd-server.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/sap/bluetoothd-server.o `test -f 'profiles/sap/server.c' || echo '$(srcdir)/'`profiles/sap/server.c + +profiles/sap/bluetoothd-server.obj: profiles/sap/server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/sap/bluetoothd-server.obj -MD -MP -MF profiles/sap/$(DEPDIR)/bluetoothd-server.Tpo -c -o profiles/sap/bluetoothd-server.obj `if test -f 'profiles/sap/server.c'; then $(CYGPATH_W) 'profiles/sap/server.c'; else $(CYGPATH_W) '$(srcdir)/profiles/sap/server.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/sap/$(DEPDIR)/bluetoothd-server.Tpo profiles/sap/$(DEPDIR)/bluetoothd-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/sap/server.c' object='profiles/sap/bluetoothd-server.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/sap/bluetoothd-server.obj `if test -f 'profiles/sap/server.c'; then $(CYGPATH_W) 'profiles/sap/server.c'; else $(CYGPATH_W) '$(srcdir)/profiles/sap/server.c'; fi` + +profiles/sap/bluetoothd-sap-dummy.o: profiles/sap/sap-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/sap/bluetoothd-sap-dummy.o -MD -MP -MF profiles/sap/$(DEPDIR)/bluetoothd-sap-dummy.Tpo -c -o profiles/sap/bluetoothd-sap-dummy.o `test -f 'profiles/sap/sap-dummy.c' || echo '$(srcdir)/'`profiles/sap/sap-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/sap/$(DEPDIR)/bluetoothd-sap-dummy.Tpo profiles/sap/$(DEPDIR)/bluetoothd-sap-dummy.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/sap/sap-dummy.c' object='profiles/sap/bluetoothd-sap-dummy.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/sap/bluetoothd-sap-dummy.o `test -f 'profiles/sap/sap-dummy.c' || echo '$(srcdir)/'`profiles/sap/sap-dummy.c + +profiles/sap/bluetoothd-sap-dummy.obj: profiles/sap/sap-dummy.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/sap/bluetoothd-sap-dummy.obj -MD -MP -MF profiles/sap/$(DEPDIR)/bluetoothd-sap-dummy.Tpo -c -o profiles/sap/bluetoothd-sap-dummy.obj `if test -f 'profiles/sap/sap-dummy.c'; then $(CYGPATH_W) 'profiles/sap/sap-dummy.c'; else $(CYGPATH_W) '$(srcdir)/profiles/sap/sap-dummy.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/sap/$(DEPDIR)/bluetoothd-sap-dummy.Tpo profiles/sap/$(DEPDIR)/bluetoothd-sap-dummy.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/sap/sap-dummy.c' object='profiles/sap/bluetoothd-sap-dummy.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/sap/bluetoothd-sap-dummy.obj `if test -f 'profiles/sap/sap-dummy.c'; then $(CYGPATH_W) 'profiles/sap/sap-dummy.c'; else $(CYGPATH_W) '$(srcdir)/profiles/sap/sap-dummy.c'; fi` + +profiles/audio/bluetoothd-source.o: profiles/audio/source.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-source.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-source.Tpo -c -o profiles/audio/bluetoothd-source.o `test -f 'profiles/audio/source.c' || echo '$(srcdir)/'`profiles/audio/source.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-source.Tpo profiles/audio/$(DEPDIR)/bluetoothd-source.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/source.c' object='profiles/audio/bluetoothd-source.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-source.o `test -f 'profiles/audio/source.c' || echo '$(srcdir)/'`profiles/audio/source.c + +profiles/audio/bluetoothd-source.obj: profiles/audio/source.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-source.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-source.Tpo -c -o profiles/audio/bluetoothd-source.obj `if test -f 'profiles/audio/source.c'; then $(CYGPATH_W) 'profiles/audio/source.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/source.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-source.Tpo profiles/audio/$(DEPDIR)/bluetoothd-source.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/source.c' object='profiles/audio/bluetoothd-source.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-source.obj `if test -f 'profiles/audio/source.c'; then $(CYGPATH_W) 'profiles/audio/source.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/source.c'; fi` + +profiles/audio/bluetoothd-sink.o: profiles/audio/sink.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-sink.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-sink.Tpo -c -o profiles/audio/bluetoothd-sink.o `test -f 'profiles/audio/sink.c' || echo '$(srcdir)/'`profiles/audio/sink.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-sink.Tpo profiles/audio/$(DEPDIR)/bluetoothd-sink.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/sink.c' object='profiles/audio/bluetoothd-sink.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-sink.o `test -f 'profiles/audio/sink.c' || echo '$(srcdir)/'`profiles/audio/sink.c + +profiles/audio/bluetoothd-sink.obj: profiles/audio/sink.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-sink.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-sink.Tpo -c -o profiles/audio/bluetoothd-sink.obj `if test -f 'profiles/audio/sink.c'; then $(CYGPATH_W) 'profiles/audio/sink.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/sink.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-sink.Tpo profiles/audio/$(DEPDIR)/bluetoothd-sink.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/sink.c' object='profiles/audio/bluetoothd-sink.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-sink.obj `if test -f 'profiles/audio/sink.c'; then $(CYGPATH_W) 'profiles/audio/sink.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/sink.c'; fi` + +profiles/audio/bluetoothd-a2dp.o: profiles/audio/a2dp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-a2dp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Tpo -c -o profiles/audio/bluetoothd-a2dp.o `test -f 'profiles/audio/a2dp.c' || echo '$(srcdir)/'`profiles/audio/a2dp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/a2dp.c' object='profiles/audio/bluetoothd-a2dp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-a2dp.o `test -f 'profiles/audio/a2dp.c' || echo '$(srcdir)/'`profiles/audio/a2dp.c + +profiles/audio/bluetoothd-a2dp.obj: profiles/audio/a2dp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-a2dp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Tpo -c -o profiles/audio/bluetoothd-a2dp.obj `if test -f 'profiles/audio/a2dp.c'; then $(CYGPATH_W) 'profiles/audio/a2dp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/a2dp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/a2dp.c' object='profiles/audio/bluetoothd-a2dp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-a2dp.obj `if test -f 'profiles/audio/a2dp.c'; then $(CYGPATH_W) 'profiles/audio/a2dp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/a2dp.c'; fi` + +profiles/audio/bluetoothd-avdtp.o: profiles/audio/avdtp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avdtp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Tpo -c -o profiles/audio/bluetoothd-avdtp.o `test -f 'profiles/audio/avdtp.c' || echo '$(srcdir)/'`profiles/audio/avdtp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avdtp.c' object='profiles/audio/bluetoothd-avdtp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avdtp.o `test -f 'profiles/audio/avdtp.c' || echo '$(srcdir)/'`profiles/audio/avdtp.c + +profiles/audio/bluetoothd-avdtp.obj: profiles/audio/avdtp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avdtp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Tpo -c -o profiles/audio/bluetoothd-avdtp.obj `if test -f 'profiles/audio/avdtp.c'; then $(CYGPATH_W) 'profiles/audio/avdtp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avdtp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avdtp.c' object='profiles/audio/bluetoothd-avdtp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avdtp.obj `if test -f 'profiles/audio/avdtp.c'; then $(CYGPATH_W) 'profiles/audio/avdtp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avdtp.c'; fi` + +profiles/audio/bluetoothd-media.o: profiles/audio/media.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-media.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-media.Tpo -c -o profiles/audio/bluetoothd-media.o `test -f 'profiles/audio/media.c' || echo '$(srcdir)/'`profiles/audio/media.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-media.Tpo profiles/audio/$(DEPDIR)/bluetoothd-media.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/media.c' object='profiles/audio/bluetoothd-media.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-media.o `test -f 'profiles/audio/media.c' || echo '$(srcdir)/'`profiles/audio/media.c + +profiles/audio/bluetoothd-media.obj: profiles/audio/media.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-media.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-media.Tpo -c -o profiles/audio/bluetoothd-media.obj `if test -f 'profiles/audio/media.c'; then $(CYGPATH_W) 'profiles/audio/media.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/media.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-media.Tpo profiles/audio/$(DEPDIR)/bluetoothd-media.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/media.c' object='profiles/audio/bluetoothd-media.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-media.obj `if test -f 'profiles/audio/media.c'; then $(CYGPATH_W) 'profiles/audio/media.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/media.c'; fi` + +profiles/audio/bluetoothd-transport.o: profiles/audio/transport.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-transport.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-transport.Tpo -c -o profiles/audio/bluetoothd-transport.o `test -f 'profiles/audio/transport.c' || echo '$(srcdir)/'`profiles/audio/transport.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-transport.Tpo profiles/audio/$(DEPDIR)/bluetoothd-transport.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/transport.c' object='profiles/audio/bluetoothd-transport.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-transport.o `test -f 'profiles/audio/transport.c' || echo '$(srcdir)/'`profiles/audio/transport.c + +profiles/audio/bluetoothd-transport.obj: profiles/audio/transport.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-transport.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-transport.Tpo -c -o profiles/audio/bluetoothd-transport.obj `if test -f 'profiles/audio/transport.c'; then $(CYGPATH_W) 'profiles/audio/transport.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/transport.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-transport.Tpo profiles/audio/$(DEPDIR)/bluetoothd-transport.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/transport.c' object='profiles/audio/bluetoothd-transport.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-transport.obj `if test -f 'profiles/audio/transport.c'; then $(CYGPATH_W) 'profiles/audio/transport.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/transport.c'; fi` + +profiles/audio/bluetoothd-control.o: profiles/audio/control.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-control.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-control.Tpo -c -o profiles/audio/bluetoothd-control.o `test -f 'profiles/audio/control.c' || echo '$(srcdir)/'`profiles/audio/control.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-control.Tpo profiles/audio/$(DEPDIR)/bluetoothd-control.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/control.c' object='profiles/audio/bluetoothd-control.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-control.o `test -f 'profiles/audio/control.c' || echo '$(srcdir)/'`profiles/audio/control.c + +profiles/audio/bluetoothd-control.obj: profiles/audio/control.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-control.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-control.Tpo -c -o profiles/audio/bluetoothd-control.obj `if test -f 'profiles/audio/control.c'; then $(CYGPATH_W) 'profiles/audio/control.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/control.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-control.Tpo profiles/audio/$(DEPDIR)/bluetoothd-control.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/control.c' object='profiles/audio/bluetoothd-control.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-control.obj `if test -f 'profiles/audio/control.c'; then $(CYGPATH_W) 'profiles/audio/control.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/control.c'; fi` + +profiles/audio/bluetoothd-avctp.o: profiles/audio/avctp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avctp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avctp.Tpo -c -o profiles/audio/bluetoothd-avctp.o `test -f 'profiles/audio/avctp.c' || echo '$(srcdir)/'`profiles/audio/avctp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avctp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avctp.c' object='profiles/audio/bluetoothd-avctp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avctp.o `test -f 'profiles/audio/avctp.c' || echo '$(srcdir)/'`profiles/audio/avctp.c + +profiles/audio/bluetoothd-avctp.obj: profiles/audio/avctp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avctp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avctp.Tpo -c -o profiles/audio/bluetoothd-avctp.obj `if test -f 'profiles/audio/avctp.c'; then $(CYGPATH_W) 'profiles/audio/avctp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avctp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avctp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avctp.c' object='profiles/audio/bluetoothd-avctp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avctp.obj `if test -f 'profiles/audio/avctp.c'; then $(CYGPATH_W) 'profiles/audio/avctp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avctp.c'; fi` + +profiles/audio/bluetoothd-avrcp.o: profiles/audio/avrcp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avrcp.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Tpo -c -o profiles/audio/bluetoothd-avrcp.o `test -f 'profiles/audio/avrcp.c' || echo '$(srcdir)/'`profiles/audio/avrcp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avrcp.c' object='profiles/audio/bluetoothd-avrcp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avrcp.o `test -f 'profiles/audio/avrcp.c' || echo '$(srcdir)/'`profiles/audio/avrcp.c + +profiles/audio/bluetoothd-avrcp.obj: profiles/audio/avrcp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-avrcp.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Tpo -c -o profiles/audio/bluetoothd-avrcp.obj `if test -f 'profiles/audio/avrcp.c'; then $(CYGPATH_W) 'profiles/audio/avrcp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avrcp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Tpo profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/avrcp.c' object='profiles/audio/bluetoothd-avrcp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-avrcp.obj `if test -f 'profiles/audio/avrcp.c'; then $(CYGPATH_W) 'profiles/audio/avrcp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/avrcp.c'; fi` + +profiles/audio/bluetoothd-player.o: profiles/audio/player.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-player.o -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-player.Tpo -c -o profiles/audio/bluetoothd-player.o `test -f 'profiles/audio/player.c' || echo '$(srcdir)/'`profiles/audio/player.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-player.Tpo profiles/audio/$(DEPDIR)/bluetoothd-player.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/player.c' object='profiles/audio/bluetoothd-player.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-player.o `test -f 'profiles/audio/player.c' || echo '$(srcdir)/'`profiles/audio/player.c + +profiles/audio/bluetoothd-player.obj: profiles/audio/player.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/audio/bluetoothd-player.obj -MD -MP -MF profiles/audio/$(DEPDIR)/bluetoothd-player.Tpo -c -o profiles/audio/bluetoothd-player.obj `if test -f 'profiles/audio/player.c'; then $(CYGPATH_W) 'profiles/audio/player.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/player.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/audio/$(DEPDIR)/bluetoothd-player.Tpo profiles/audio/$(DEPDIR)/bluetoothd-player.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/audio/player.c' object='profiles/audio/bluetoothd-player.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/audio/bluetoothd-player.obj `if test -f 'profiles/audio/player.c'; then $(CYGPATH_W) 'profiles/audio/player.c'; else $(CYGPATH_W) '$(srcdir)/profiles/audio/player.c'; fi` + +profiles/network/bluetoothd-manager.o: profiles/network/manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-manager.o -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-manager.Tpo -c -o profiles/network/bluetoothd-manager.o `test -f 'profiles/network/manager.c' || echo '$(srcdir)/'`profiles/network/manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-manager.Tpo profiles/network/$(DEPDIR)/bluetoothd-manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/manager.c' object='profiles/network/bluetoothd-manager.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-manager.o `test -f 'profiles/network/manager.c' || echo '$(srcdir)/'`profiles/network/manager.c + +profiles/network/bluetoothd-manager.obj: profiles/network/manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-manager.obj -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-manager.Tpo -c -o profiles/network/bluetoothd-manager.obj `if test -f 'profiles/network/manager.c'; then $(CYGPATH_W) 'profiles/network/manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/manager.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-manager.Tpo profiles/network/$(DEPDIR)/bluetoothd-manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/manager.c' object='profiles/network/bluetoothd-manager.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-manager.obj `if test -f 'profiles/network/manager.c'; then $(CYGPATH_W) 'profiles/network/manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/manager.c'; fi` + +profiles/network/bluetoothd-bnep.o: profiles/network/bnep.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-bnep.o -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-bnep.Tpo -c -o profiles/network/bluetoothd-bnep.o `test -f 'profiles/network/bnep.c' || echo '$(srcdir)/'`profiles/network/bnep.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-bnep.Tpo profiles/network/$(DEPDIR)/bluetoothd-bnep.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/bnep.c' object='profiles/network/bluetoothd-bnep.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-bnep.o `test -f 'profiles/network/bnep.c' || echo '$(srcdir)/'`profiles/network/bnep.c + +profiles/network/bluetoothd-bnep.obj: profiles/network/bnep.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-bnep.obj -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-bnep.Tpo -c -o profiles/network/bluetoothd-bnep.obj `if test -f 'profiles/network/bnep.c'; then $(CYGPATH_W) 'profiles/network/bnep.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/bnep.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-bnep.Tpo profiles/network/$(DEPDIR)/bluetoothd-bnep.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/bnep.c' object='profiles/network/bluetoothd-bnep.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-bnep.obj `if test -f 'profiles/network/bnep.c'; then $(CYGPATH_W) 'profiles/network/bnep.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/bnep.c'; fi` + +profiles/network/bluetoothd-server.o: profiles/network/server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-server.o -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-server.Tpo -c -o profiles/network/bluetoothd-server.o `test -f 'profiles/network/server.c' || echo '$(srcdir)/'`profiles/network/server.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-server.Tpo profiles/network/$(DEPDIR)/bluetoothd-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/server.c' object='profiles/network/bluetoothd-server.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-server.o `test -f 'profiles/network/server.c' || echo '$(srcdir)/'`profiles/network/server.c + +profiles/network/bluetoothd-server.obj: profiles/network/server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-server.obj -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-server.Tpo -c -o profiles/network/bluetoothd-server.obj `if test -f 'profiles/network/server.c'; then $(CYGPATH_W) 'profiles/network/server.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/server.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-server.Tpo profiles/network/$(DEPDIR)/bluetoothd-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/server.c' object='profiles/network/bluetoothd-server.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-server.obj `if test -f 'profiles/network/server.c'; then $(CYGPATH_W) 'profiles/network/server.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/server.c'; fi` + +profiles/network/bluetoothd-connection.o: profiles/network/connection.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-connection.o -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-connection.Tpo -c -o profiles/network/bluetoothd-connection.o `test -f 'profiles/network/connection.c' || echo '$(srcdir)/'`profiles/network/connection.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-connection.Tpo profiles/network/$(DEPDIR)/bluetoothd-connection.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/connection.c' object='profiles/network/bluetoothd-connection.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-connection.o `test -f 'profiles/network/connection.c' || echo '$(srcdir)/'`profiles/network/connection.c + +profiles/network/bluetoothd-connection.obj: profiles/network/connection.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/network/bluetoothd-connection.obj -MD -MP -MF profiles/network/$(DEPDIR)/bluetoothd-connection.Tpo -c -o profiles/network/bluetoothd-connection.obj `if test -f 'profiles/network/connection.c'; then $(CYGPATH_W) 'profiles/network/connection.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/connection.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/network/$(DEPDIR)/bluetoothd-connection.Tpo profiles/network/$(DEPDIR)/bluetoothd-connection.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/network/connection.c' object='profiles/network/bluetoothd-connection.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/network/bluetoothd-connection.obj `if test -f 'profiles/network/connection.c'; then $(CYGPATH_W) 'profiles/network/connection.c'; else $(CYGPATH_W) '$(srcdir)/profiles/network/connection.c'; fi` + +profiles/input/bluetoothd-manager.o: profiles/input/manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-manager.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-manager.Tpo -c -o profiles/input/bluetoothd-manager.o `test -f 'profiles/input/manager.c' || echo '$(srcdir)/'`profiles/input/manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-manager.Tpo profiles/input/$(DEPDIR)/bluetoothd-manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/manager.c' object='profiles/input/bluetoothd-manager.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-manager.o `test -f 'profiles/input/manager.c' || echo '$(srcdir)/'`profiles/input/manager.c + +profiles/input/bluetoothd-manager.obj: profiles/input/manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-manager.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-manager.Tpo -c -o profiles/input/bluetoothd-manager.obj `if test -f 'profiles/input/manager.c'; then $(CYGPATH_W) 'profiles/input/manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/manager.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-manager.Tpo profiles/input/$(DEPDIR)/bluetoothd-manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/manager.c' object='profiles/input/bluetoothd-manager.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-manager.obj `if test -f 'profiles/input/manager.c'; then $(CYGPATH_W) 'profiles/input/manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/manager.c'; fi` + +profiles/input/bluetoothd-server.o: profiles/input/server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-server.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-server.Tpo -c -o profiles/input/bluetoothd-server.o `test -f 'profiles/input/server.c' || echo '$(srcdir)/'`profiles/input/server.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-server.Tpo profiles/input/$(DEPDIR)/bluetoothd-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/server.c' object='profiles/input/bluetoothd-server.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-server.o `test -f 'profiles/input/server.c' || echo '$(srcdir)/'`profiles/input/server.c + +profiles/input/bluetoothd-server.obj: profiles/input/server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-server.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-server.Tpo -c -o profiles/input/bluetoothd-server.obj `if test -f 'profiles/input/server.c'; then $(CYGPATH_W) 'profiles/input/server.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/server.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-server.Tpo profiles/input/$(DEPDIR)/bluetoothd-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/server.c' object='profiles/input/bluetoothd-server.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-server.obj `if test -f 'profiles/input/server.c'; then $(CYGPATH_W) 'profiles/input/server.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/server.c'; fi` + +profiles/input/bluetoothd-device.o: profiles/input/device.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-device.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-device.Tpo -c -o profiles/input/bluetoothd-device.o `test -f 'profiles/input/device.c' || echo '$(srcdir)/'`profiles/input/device.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-device.Tpo profiles/input/$(DEPDIR)/bluetoothd-device.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/device.c' object='profiles/input/bluetoothd-device.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-device.o `test -f 'profiles/input/device.c' || echo '$(srcdir)/'`profiles/input/device.c + +profiles/input/bluetoothd-device.obj: profiles/input/device.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-device.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-device.Tpo -c -o profiles/input/bluetoothd-device.obj `if test -f 'profiles/input/device.c'; then $(CYGPATH_W) 'profiles/input/device.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/device.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-device.Tpo profiles/input/$(DEPDIR)/bluetoothd-device.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/device.c' object='profiles/input/bluetoothd-device.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-device.obj `if test -f 'profiles/input/device.c'; then $(CYGPATH_W) 'profiles/input/device.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/device.c'; fi` + +profiles/input/bluetoothd-hog.o: profiles/input/hog.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-hog.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-hog.Tpo -c -o profiles/input/bluetoothd-hog.o `test -f 'profiles/input/hog.c' || echo '$(srcdir)/'`profiles/input/hog.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-hog.Tpo profiles/input/$(DEPDIR)/bluetoothd-hog.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/hog.c' object='profiles/input/bluetoothd-hog.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-hog.o `test -f 'profiles/input/hog.c' || echo '$(srcdir)/'`profiles/input/hog.c + +profiles/input/bluetoothd-hog.obj: profiles/input/hog.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-hog.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-hog.Tpo -c -o profiles/input/bluetoothd-hog.obj `if test -f 'profiles/input/hog.c'; then $(CYGPATH_W) 'profiles/input/hog.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/hog.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-hog.Tpo profiles/input/$(DEPDIR)/bluetoothd-hog.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/hog.c' object='profiles/input/bluetoothd-hog.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-hog.obj `if test -f 'profiles/input/hog.c'; then $(CYGPATH_W) 'profiles/input/hog.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/hog.c'; fi` + +profiles/input/bluetoothd-hog-lib.o: profiles/input/hog-lib.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-hog-lib.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Tpo -c -o profiles/input/bluetoothd-hog-lib.o `test -f 'profiles/input/hog-lib.c' || echo '$(srcdir)/'`profiles/input/hog-lib.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Tpo profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/hog-lib.c' object='profiles/input/bluetoothd-hog-lib.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-hog-lib.o `test -f 'profiles/input/hog-lib.c' || echo '$(srcdir)/'`profiles/input/hog-lib.c + +profiles/input/bluetoothd-hog-lib.obj: profiles/input/hog-lib.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-hog-lib.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Tpo -c -o profiles/input/bluetoothd-hog-lib.obj `if test -f 'profiles/input/hog-lib.c'; then $(CYGPATH_W) 'profiles/input/hog-lib.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/hog-lib.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Tpo profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/hog-lib.c' object='profiles/input/bluetoothd-hog-lib.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-hog-lib.obj `if test -f 'profiles/input/hog-lib.c'; then $(CYGPATH_W) 'profiles/input/hog-lib.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/hog-lib.c'; fi` + +profiles/deviceinfo/bluetoothd-dis.o: profiles/deviceinfo/dis.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/deviceinfo/bluetoothd-dis.o -MD -MP -MF profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Tpo -c -o profiles/deviceinfo/bluetoothd-dis.o `test -f 'profiles/deviceinfo/dis.c' || echo '$(srcdir)/'`profiles/deviceinfo/dis.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Tpo profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/deviceinfo/dis.c' object='profiles/deviceinfo/bluetoothd-dis.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/deviceinfo/bluetoothd-dis.o `test -f 'profiles/deviceinfo/dis.c' || echo '$(srcdir)/'`profiles/deviceinfo/dis.c + +profiles/deviceinfo/bluetoothd-dis.obj: profiles/deviceinfo/dis.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/deviceinfo/bluetoothd-dis.obj -MD -MP -MF profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Tpo -c -o profiles/deviceinfo/bluetoothd-dis.obj `if test -f 'profiles/deviceinfo/dis.c'; then $(CYGPATH_W) 'profiles/deviceinfo/dis.c'; else $(CYGPATH_W) '$(srcdir)/profiles/deviceinfo/dis.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Tpo profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/deviceinfo/dis.c' object='profiles/deviceinfo/bluetoothd-dis.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/deviceinfo/bluetoothd-dis.obj `if test -f 'profiles/deviceinfo/dis.c'; then $(CYGPATH_W) 'profiles/deviceinfo/dis.c'; else $(CYGPATH_W) '$(srcdir)/profiles/deviceinfo/dis.c'; fi` + +profiles/battery/bluetoothd-bas.o: profiles/battery/bas.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/battery/bluetoothd-bas.o -MD -MP -MF profiles/battery/$(DEPDIR)/bluetoothd-bas.Tpo -c -o profiles/battery/bluetoothd-bas.o `test -f 'profiles/battery/bas.c' || echo '$(srcdir)/'`profiles/battery/bas.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/battery/$(DEPDIR)/bluetoothd-bas.Tpo profiles/battery/$(DEPDIR)/bluetoothd-bas.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/battery/bas.c' object='profiles/battery/bluetoothd-bas.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/battery/bluetoothd-bas.o `test -f 'profiles/battery/bas.c' || echo '$(srcdir)/'`profiles/battery/bas.c + +profiles/battery/bluetoothd-bas.obj: profiles/battery/bas.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/battery/bluetoothd-bas.obj -MD -MP -MF profiles/battery/$(DEPDIR)/bluetoothd-bas.Tpo -c -o profiles/battery/bluetoothd-bas.obj `if test -f 'profiles/battery/bas.c'; then $(CYGPATH_W) 'profiles/battery/bas.c'; else $(CYGPATH_W) '$(srcdir)/profiles/battery/bas.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/battery/$(DEPDIR)/bluetoothd-bas.Tpo profiles/battery/$(DEPDIR)/bluetoothd-bas.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/battery/bas.c' object='profiles/battery/bluetoothd-bas.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/battery/bluetoothd-bas.obj `if test -f 'profiles/battery/bas.c'; then $(CYGPATH_W) 'profiles/battery/bas.c'; else $(CYGPATH_W) '$(srcdir)/profiles/battery/bas.c'; fi` + +profiles/scanparam/bluetoothd-scpp.o: profiles/scanparam/scpp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/scanparam/bluetoothd-scpp.o -MD -MP -MF profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Tpo -c -o profiles/scanparam/bluetoothd-scpp.o `test -f 'profiles/scanparam/scpp.c' || echo '$(srcdir)/'`profiles/scanparam/scpp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Tpo profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/scanparam/scpp.c' object='profiles/scanparam/bluetoothd-scpp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/scanparam/bluetoothd-scpp.o `test -f 'profiles/scanparam/scpp.c' || echo '$(srcdir)/'`profiles/scanparam/scpp.c + +profiles/scanparam/bluetoothd-scpp.obj: profiles/scanparam/scpp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/scanparam/bluetoothd-scpp.obj -MD -MP -MF profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Tpo -c -o profiles/scanparam/bluetoothd-scpp.obj `if test -f 'profiles/scanparam/scpp.c'; then $(CYGPATH_W) 'profiles/scanparam/scpp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/scanparam/scpp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Tpo profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/scanparam/scpp.c' object='profiles/scanparam/bluetoothd-scpp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/scanparam/bluetoothd-scpp.obj `if test -f 'profiles/scanparam/scpp.c'; then $(CYGPATH_W) 'profiles/scanparam/scpp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/scanparam/scpp.c'; fi` + +profiles/input/bluetoothd-suspend-none.o: profiles/input/suspend-none.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-suspend-none.o -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Tpo -c -o profiles/input/bluetoothd-suspend-none.o `test -f 'profiles/input/suspend-none.c' || echo '$(srcdir)/'`profiles/input/suspend-none.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Tpo profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/suspend-none.c' object='profiles/input/bluetoothd-suspend-none.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-suspend-none.o `test -f 'profiles/input/suspend-none.c' || echo '$(srcdir)/'`profiles/input/suspend-none.c + +profiles/input/bluetoothd-suspend-none.obj: profiles/input/suspend-none.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/input/bluetoothd-suspend-none.obj -MD -MP -MF profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Tpo -c -o profiles/input/bluetoothd-suspend-none.obj `if test -f 'profiles/input/suspend-none.c'; then $(CYGPATH_W) 'profiles/input/suspend-none.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/suspend-none.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Tpo profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/input/suspend-none.c' object='profiles/input/bluetoothd-suspend-none.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/input/bluetoothd-suspend-none.obj `if test -f 'profiles/input/suspend-none.c'; then $(CYGPATH_W) 'profiles/input/suspend-none.c'; else $(CYGPATH_W) '$(srcdir)/profiles/input/suspend-none.c'; fi` + +profiles/health/bluetoothd-mcap.o: profiles/health/mcap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/health/bluetoothd-mcap.o -MD -MP -MF profiles/health/$(DEPDIR)/bluetoothd-mcap.Tpo -c -o profiles/health/bluetoothd-mcap.o `test -f 'profiles/health/mcap.c' || echo '$(srcdir)/'`profiles/health/mcap.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/health/$(DEPDIR)/bluetoothd-mcap.Tpo profiles/health/$(DEPDIR)/bluetoothd-mcap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/health/mcap.c' object='profiles/health/bluetoothd-mcap.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/health/bluetoothd-mcap.o `test -f 'profiles/health/mcap.c' || echo '$(srcdir)/'`profiles/health/mcap.c + +profiles/health/bluetoothd-mcap.obj: profiles/health/mcap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/health/bluetoothd-mcap.obj -MD -MP -MF profiles/health/$(DEPDIR)/bluetoothd-mcap.Tpo -c -o profiles/health/bluetoothd-mcap.obj `if test -f 'profiles/health/mcap.c'; then $(CYGPATH_W) 'profiles/health/mcap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/health/mcap.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/health/$(DEPDIR)/bluetoothd-mcap.Tpo profiles/health/$(DEPDIR)/bluetoothd-mcap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/health/mcap.c' object='profiles/health/bluetoothd-mcap.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/health/bluetoothd-mcap.obj `if test -f 'profiles/health/mcap.c'; then $(CYGPATH_W) 'profiles/health/mcap.c'; else $(CYGPATH_W) '$(srcdir)/profiles/health/mcap.c'; fi` + +profiles/health/bluetoothd-hdp_main.o: profiles/health/hdp_main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/health/bluetoothd-hdp_main.o -MD -MP -MF profiles/health/$(DEPDIR)/bluetoothd-hdp_main.Tpo -c -o profiles/health/bluetoothd-hdp_main.o `test -f 'profiles/health/hdp_main.c' || echo '$(srcdir)/'`profiles/health/hdp_main.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/health/$(DEPDIR)/bluetoothd-hdp_main.Tpo profiles/health/$(DEPDIR)/bluetoothd-hdp_main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/health/hdp_main.c' object='profiles/health/bluetoothd-hdp_main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/health/bluetoothd-hdp_main.o `test -f 'profiles/health/hdp_main.c' || echo '$(srcdir)/'`profiles/health/hdp_main.c + +profiles/health/bluetoothd-hdp_main.obj: profiles/health/hdp_main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/health/bluetoothd-hdp_main.obj -MD -MP -MF profiles/health/$(DEPDIR)/bluetoothd-hdp_main.Tpo -c -o profiles/health/bluetoothd-hdp_main.obj `if test -f 'profiles/health/hdp_main.c'; then $(CYGPATH_W) 'profiles/health/hdp_main.c'; else $(CYGPATH_W) '$(srcdir)/profiles/health/hdp_main.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/health/$(DEPDIR)/bluetoothd-hdp_main.Tpo profiles/health/$(DEPDIR)/bluetoothd-hdp_main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/health/hdp_main.c' object='profiles/health/bluetoothd-hdp_main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/health/bluetoothd-hdp_main.obj `if test -f 'profiles/health/hdp_main.c'; then $(CYGPATH_W) 'profiles/health/hdp_main.c'; else $(CYGPATH_W) '$(srcdir)/profiles/health/hdp_main.c'; fi` + +profiles/health/bluetoothd-hdp_manager.o: profiles/health/hdp_manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/health/bluetoothd-hdp_manager.o -MD -MP -MF profiles/health/$(DEPDIR)/bluetoothd-hdp_manager.Tpo -c -o profiles/health/bluetoothd-hdp_manager.o `test -f 'profiles/health/hdp_manager.c' || echo '$(srcdir)/'`profiles/health/hdp_manager.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/health/$(DEPDIR)/bluetoothd-hdp_manager.Tpo profiles/health/$(DEPDIR)/bluetoothd-hdp_manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/health/hdp_manager.c' object='profiles/health/bluetoothd-hdp_manager.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/health/bluetoothd-hdp_manager.o `test -f 'profiles/health/hdp_manager.c' || echo '$(srcdir)/'`profiles/health/hdp_manager.c + +profiles/health/bluetoothd-hdp_manager.obj: profiles/health/hdp_manager.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/health/bluetoothd-hdp_manager.obj -MD -MP -MF profiles/health/$(DEPDIR)/bluetoothd-hdp_manager.Tpo -c -o profiles/health/bluetoothd-hdp_manager.obj `if test -f 'profiles/health/hdp_manager.c'; then $(CYGPATH_W) 'profiles/health/hdp_manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/health/hdp_manager.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/health/$(DEPDIR)/bluetoothd-hdp_manager.Tpo profiles/health/$(DEPDIR)/bluetoothd-hdp_manager.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/health/hdp_manager.c' object='profiles/health/bluetoothd-hdp_manager.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/health/bluetoothd-hdp_manager.obj `if test -f 'profiles/health/hdp_manager.c'; then $(CYGPATH_W) 'profiles/health/hdp_manager.c'; else $(CYGPATH_W) '$(srcdir)/profiles/health/hdp_manager.c'; fi` + +profiles/health/bluetoothd-hdp.o: profiles/health/hdp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/health/bluetoothd-hdp.o -MD -MP -MF profiles/health/$(DEPDIR)/bluetoothd-hdp.Tpo -c -o profiles/health/bluetoothd-hdp.o `test -f 'profiles/health/hdp.c' || echo '$(srcdir)/'`profiles/health/hdp.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/health/$(DEPDIR)/bluetoothd-hdp.Tpo profiles/health/$(DEPDIR)/bluetoothd-hdp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/health/hdp.c' object='profiles/health/bluetoothd-hdp.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/health/bluetoothd-hdp.o `test -f 'profiles/health/hdp.c' || echo '$(srcdir)/'`profiles/health/hdp.c + +profiles/health/bluetoothd-hdp.obj: profiles/health/hdp.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/health/bluetoothd-hdp.obj -MD -MP -MF profiles/health/$(DEPDIR)/bluetoothd-hdp.Tpo -c -o profiles/health/bluetoothd-hdp.obj `if test -f 'profiles/health/hdp.c'; then $(CYGPATH_W) 'profiles/health/hdp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/health/hdp.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/health/$(DEPDIR)/bluetoothd-hdp.Tpo profiles/health/$(DEPDIR)/bluetoothd-hdp.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/health/hdp.c' object='profiles/health/bluetoothd-hdp.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/health/bluetoothd-hdp.obj `if test -f 'profiles/health/hdp.c'; then $(CYGPATH_W) 'profiles/health/hdp.c'; else $(CYGPATH_W) '$(srcdir)/profiles/health/hdp.c'; fi` + +profiles/health/bluetoothd-hdp_util.o: profiles/health/hdp_util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/health/bluetoothd-hdp_util.o -MD -MP -MF profiles/health/$(DEPDIR)/bluetoothd-hdp_util.Tpo -c -o profiles/health/bluetoothd-hdp_util.o `test -f 'profiles/health/hdp_util.c' || echo '$(srcdir)/'`profiles/health/hdp_util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/health/$(DEPDIR)/bluetoothd-hdp_util.Tpo profiles/health/$(DEPDIR)/bluetoothd-hdp_util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/health/hdp_util.c' object='profiles/health/bluetoothd-hdp_util.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/health/bluetoothd-hdp_util.o `test -f 'profiles/health/hdp_util.c' || echo '$(srcdir)/'`profiles/health/hdp_util.c + +profiles/health/bluetoothd-hdp_util.obj: profiles/health/hdp_util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/health/bluetoothd-hdp_util.obj -MD -MP -MF profiles/health/$(DEPDIR)/bluetoothd-hdp_util.Tpo -c -o profiles/health/bluetoothd-hdp_util.obj `if test -f 'profiles/health/hdp_util.c'; then $(CYGPATH_W) 'profiles/health/hdp_util.c'; else $(CYGPATH_W) '$(srcdir)/profiles/health/hdp_util.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/health/$(DEPDIR)/bluetoothd-hdp_util.Tpo profiles/health/$(DEPDIR)/bluetoothd-hdp_util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/health/hdp_util.c' object='profiles/health/bluetoothd-hdp_util.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/health/bluetoothd-hdp_util.obj `if test -f 'profiles/health/hdp_util.c'; then $(CYGPATH_W) 'profiles/health/hdp_util.c'; else $(CYGPATH_W) '$(srcdir)/profiles/health/hdp_util.c'; fi` + +profiles/gap/bluetoothd-gas.o: profiles/gap/gas.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/gap/bluetoothd-gas.o -MD -MP -MF profiles/gap/$(DEPDIR)/bluetoothd-gas.Tpo -c -o profiles/gap/bluetoothd-gas.o `test -f 'profiles/gap/gas.c' || echo '$(srcdir)/'`profiles/gap/gas.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/gap/$(DEPDIR)/bluetoothd-gas.Tpo profiles/gap/$(DEPDIR)/bluetoothd-gas.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/gap/gas.c' object='profiles/gap/bluetoothd-gas.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/gap/bluetoothd-gas.o `test -f 'profiles/gap/gas.c' || echo '$(srcdir)/'`profiles/gap/gas.c + +profiles/gap/bluetoothd-gas.obj: profiles/gap/gas.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/gap/bluetoothd-gas.obj -MD -MP -MF profiles/gap/$(DEPDIR)/bluetoothd-gas.Tpo -c -o profiles/gap/bluetoothd-gas.obj `if test -f 'profiles/gap/gas.c'; then $(CYGPATH_W) 'profiles/gap/gas.c'; else $(CYGPATH_W) '$(srcdir)/profiles/gap/gas.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/gap/$(DEPDIR)/bluetoothd-gas.Tpo profiles/gap/$(DEPDIR)/bluetoothd-gas.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/gap/gas.c' object='profiles/gap/bluetoothd-gas.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/gap/bluetoothd-gas.obj `if test -f 'profiles/gap/gas.c'; then $(CYGPATH_W) 'profiles/gap/gas.c'; else $(CYGPATH_W) '$(srcdir)/profiles/gap/gas.c'; fi` + +profiles/scanparam/bluetoothd-scan.o: profiles/scanparam/scan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/scanparam/bluetoothd-scan.o -MD -MP -MF profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Tpo -c -o profiles/scanparam/bluetoothd-scan.o `test -f 'profiles/scanparam/scan.c' || echo '$(srcdir)/'`profiles/scanparam/scan.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Tpo profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/scanparam/scan.c' object='profiles/scanparam/bluetoothd-scan.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/scanparam/bluetoothd-scan.o `test -f 'profiles/scanparam/scan.c' || echo '$(srcdir)/'`profiles/scanparam/scan.c + +profiles/scanparam/bluetoothd-scan.obj: profiles/scanparam/scan.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/scanparam/bluetoothd-scan.obj -MD -MP -MF profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Tpo -c -o profiles/scanparam/bluetoothd-scan.obj `if test -f 'profiles/scanparam/scan.c'; then $(CYGPATH_W) 'profiles/scanparam/scan.c'; else $(CYGPATH_W) '$(srcdir)/profiles/scanparam/scan.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Tpo profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/scanparam/scan.c' object='profiles/scanparam/bluetoothd-scan.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/scanparam/bluetoothd-scan.obj `if test -f 'profiles/scanparam/scan.c'; then $(CYGPATH_W) 'profiles/scanparam/scan.c'; else $(CYGPATH_W) '$(srcdir)/profiles/scanparam/scan.c'; fi` + +profiles/deviceinfo/bluetoothd-deviceinfo.o: profiles/deviceinfo/deviceinfo.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/deviceinfo/bluetoothd-deviceinfo.o -MD -MP -MF profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Tpo -c -o profiles/deviceinfo/bluetoothd-deviceinfo.o `test -f 'profiles/deviceinfo/deviceinfo.c' || echo '$(srcdir)/'`profiles/deviceinfo/deviceinfo.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Tpo profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/deviceinfo/deviceinfo.c' object='profiles/deviceinfo/bluetoothd-deviceinfo.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/deviceinfo/bluetoothd-deviceinfo.o `test -f 'profiles/deviceinfo/deviceinfo.c' || echo '$(srcdir)/'`profiles/deviceinfo/deviceinfo.c + +profiles/deviceinfo/bluetoothd-deviceinfo.obj: profiles/deviceinfo/deviceinfo.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/deviceinfo/bluetoothd-deviceinfo.obj -MD -MP -MF profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Tpo -c -o profiles/deviceinfo/bluetoothd-deviceinfo.obj `if test -f 'profiles/deviceinfo/deviceinfo.c'; then $(CYGPATH_W) 'profiles/deviceinfo/deviceinfo.c'; else $(CYGPATH_W) '$(srcdir)/profiles/deviceinfo/deviceinfo.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Tpo profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/deviceinfo/deviceinfo.c' object='profiles/deviceinfo/bluetoothd-deviceinfo.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/deviceinfo/bluetoothd-deviceinfo.obj `if test -f 'profiles/deviceinfo/deviceinfo.c'; then $(CYGPATH_W) 'profiles/deviceinfo/deviceinfo.c'; else $(CYGPATH_W) '$(srcdir)/profiles/deviceinfo/deviceinfo.c'; fi` + +profiles/midi/bluetoothd-midi.o: profiles/midi/midi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/bluetoothd-midi.o -MD -MP -MF profiles/midi/$(DEPDIR)/bluetoothd-midi.Tpo -c -o profiles/midi/bluetoothd-midi.o `test -f 'profiles/midi/midi.c' || echo '$(srcdir)/'`profiles/midi/midi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/bluetoothd-midi.Tpo profiles/midi/$(DEPDIR)/bluetoothd-midi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/midi.c' object='profiles/midi/bluetoothd-midi.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/bluetoothd-midi.o `test -f 'profiles/midi/midi.c' || echo '$(srcdir)/'`profiles/midi/midi.c + +profiles/midi/bluetoothd-midi.obj: profiles/midi/midi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/bluetoothd-midi.obj -MD -MP -MF profiles/midi/$(DEPDIR)/bluetoothd-midi.Tpo -c -o profiles/midi/bluetoothd-midi.obj `if test -f 'profiles/midi/midi.c'; then $(CYGPATH_W) 'profiles/midi/midi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/midi.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/bluetoothd-midi.Tpo profiles/midi/$(DEPDIR)/bluetoothd-midi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/midi.c' object='profiles/midi/bluetoothd-midi.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/bluetoothd-midi.obj `if test -f 'profiles/midi/midi.c'; then $(CYGPATH_W) 'profiles/midi/midi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/midi.c'; fi` + +profiles/midi/bluetoothd-libmidi.o: profiles/midi/libmidi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/bluetoothd-libmidi.o -MD -MP -MF profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Tpo -c -o profiles/midi/bluetoothd-libmidi.o `test -f 'profiles/midi/libmidi.c' || echo '$(srcdir)/'`profiles/midi/libmidi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Tpo profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/libmidi.c' object='profiles/midi/bluetoothd-libmidi.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/bluetoothd-libmidi.o `test -f 'profiles/midi/libmidi.c' || echo '$(srcdir)/'`profiles/midi/libmidi.c + +profiles/midi/bluetoothd-libmidi.obj: profiles/midi/libmidi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/bluetoothd-libmidi.obj -MD -MP -MF profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Tpo -c -o profiles/midi/bluetoothd-libmidi.obj `if test -f 'profiles/midi/libmidi.c'; then $(CYGPATH_W) 'profiles/midi/libmidi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/libmidi.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Tpo profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/libmidi.c' object='profiles/midi/bluetoothd-libmidi.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/bluetoothd-libmidi.obj `if test -f 'profiles/midi/libmidi.c'; then $(CYGPATH_W) 'profiles/midi/libmidi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/libmidi.c'; fi` + +profiles/battery/bluetoothd-battery.o: profiles/battery/battery.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/battery/bluetoothd-battery.o -MD -MP -MF profiles/battery/$(DEPDIR)/bluetoothd-battery.Tpo -c -o profiles/battery/bluetoothd-battery.o `test -f 'profiles/battery/battery.c' || echo '$(srcdir)/'`profiles/battery/battery.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/battery/$(DEPDIR)/bluetoothd-battery.Tpo profiles/battery/$(DEPDIR)/bluetoothd-battery.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/battery/battery.c' object='profiles/battery/bluetoothd-battery.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/battery/bluetoothd-battery.o `test -f 'profiles/battery/battery.c' || echo '$(srcdir)/'`profiles/battery/battery.c + +profiles/battery/bluetoothd-battery.obj: profiles/battery/battery.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/battery/bluetoothd-battery.obj -MD -MP -MF profiles/battery/$(DEPDIR)/bluetoothd-battery.Tpo -c -o profiles/battery/bluetoothd-battery.obj `if test -f 'profiles/battery/battery.c'; then $(CYGPATH_W) 'profiles/battery/battery.c'; else $(CYGPATH_W) '$(srcdir)/profiles/battery/battery.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/battery/$(DEPDIR)/bluetoothd-battery.Tpo profiles/battery/$(DEPDIR)/bluetoothd-battery.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/battery/battery.c' object='profiles/battery/bluetoothd-battery.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/battery/bluetoothd-battery.obj `if test -f 'profiles/battery/battery.c'; then $(CYGPATH_W) 'profiles/battery/battery.c'; else $(CYGPATH_W) '$(srcdir)/profiles/battery/battery.c'; fi` + +attrib/bluetoothd-att.o: attrib/att.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-att.o -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-att.Tpo -c -o attrib/bluetoothd-att.o `test -f 'attrib/att.c' || echo '$(srcdir)/'`attrib/att.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-att.Tpo attrib/$(DEPDIR)/bluetoothd-att.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/att.c' object='attrib/bluetoothd-att.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-att.o `test -f 'attrib/att.c' || echo '$(srcdir)/'`attrib/att.c + +attrib/bluetoothd-att.obj: attrib/att.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-att.obj -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-att.Tpo -c -o attrib/bluetoothd-att.obj `if test -f 'attrib/att.c'; then $(CYGPATH_W) 'attrib/att.c'; else $(CYGPATH_W) '$(srcdir)/attrib/att.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-att.Tpo attrib/$(DEPDIR)/bluetoothd-att.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/att.c' object='attrib/bluetoothd-att.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-att.obj `if test -f 'attrib/att.c'; then $(CYGPATH_W) 'attrib/att.c'; else $(CYGPATH_W) '$(srcdir)/attrib/att.c'; fi` + +attrib/bluetoothd-gatt.o: attrib/gatt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-gatt.o -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-gatt.Tpo -c -o attrib/bluetoothd-gatt.o `test -f 'attrib/gatt.c' || echo '$(srcdir)/'`attrib/gatt.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-gatt.Tpo attrib/$(DEPDIR)/bluetoothd-gatt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/gatt.c' object='attrib/bluetoothd-gatt.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-gatt.o `test -f 'attrib/gatt.c' || echo '$(srcdir)/'`attrib/gatt.c + +attrib/bluetoothd-gatt.obj: attrib/gatt.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-gatt.obj -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-gatt.Tpo -c -o attrib/bluetoothd-gatt.obj `if test -f 'attrib/gatt.c'; then $(CYGPATH_W) 'attrib/gatt.c'; else $(CYGPATH_W) '$(srcdir)/attrib/gatt.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-gatt.Tpo attrib/$(DEPDIR)/bluetoothd-gatt.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/gatt.c' object='attrib/bluetoothd-gatt.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-gatt.obj `if test -f 'attrib/gatt.c'; then $(CYGPATH_W) 'attrib/gatt.c'; else $(CYGPATH_W) '$(srcdir)/attrib/gatt.c'; fi` + +attrib/bluetoothd-gattrib.o: attrib/gattrib.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-gattrib.o -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-gattrib.Tpo -c -o attrib/bluetoothd-gattrib.o `test -f 'attrib/gattrib.c' || echo '$(srcdir)/'`attrib/gattrib.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-gattrib.Tpo attrib/$(DEPDIR)/bluetoothd-gattrib.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/gattrib.c' object='attrib/bluetoothd-gattrib.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-gattrib.o `test -f 'attrib/gattrib.c' || echo '$(srcdir)/'`attrib/gattrib.c + +attrib/bluetoothd-gattrib.obj: attrib/gattrib.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-gattrib.obj -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-gattrib.Tpo -c -o attrib/bluetoothd-gattrib.obj `if test -f 'attrib/gattrib.c'; then $(CYGPATH_W) 'attrib/gattrib.c'; else $(CYGPATH_W) '$(srcdir)/attrib/gattrib.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-gattrib.Tpo attrib/$(DEPDIR)/bluetoothd-gattrib.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/gattrib.c' object='attrib/bluetoothd-gattrib.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-gattrib.obj `if test -f 'attrib/gattrib.c'; then $(CYGPATH_W) 'attrib/gattrib.c'; else $(CYGPATH_W) '$(srcdir)/attrib/gattrib.c'; fi` + +attrib/bluetoothd-gatt-service.o: attrib/gatt-service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-gatt-service.o -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-gatt-service.Tpo -c -o attrib/bluetoothd-gatt-service.o `test -f 'attrib/gatt-service.c' || echo '$(srcdir)/'`attrib/gatt-service.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-gatt-service.Tpo attrib/$(DEPDIR)/bluetoothd-gatt-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/gatt-service.c' object='attrib/bluetoothd-gatt-service.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-gatt-service.o `test -f 'attrib/gatt-service.c' || echo '$(srcdir)/'`attrib/gatt-service.c + +attrib/bluetoothd-gatt-service.obj: attrib/gatt-service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT attrib/bluetoothd-gatt-service.obj -MD -MP -MF attrib/$(DEPDIR)/bluetoothd-gatt-service.Tpo -c -o attrib/bluetoothd-gatt-service.obj `if test -f 'attrib/gatt-service.c'; then $(CYGPATH_W) 'attrib/gatt-service.c'; else $(CYGPATH_W) '$(srcdir)/attrib/gatt-service.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) attrib/$(DEPDIR)/bluetoothd-gatt-service.Tpo attrib/$(DEPDIR)/bluetoothd-gatt-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='attrib/gatt-service.c' object='attrib/bluetoothd-gatt-service.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o attrib/bluetoothd-gatt-service.obj `if test -f 'attrib/gatt-service.c'; then $(CYGPATH_W) 'attrib/gatt-service.c'; else $(CYGPATH_W) '$(srcdir)/attrib/gatt-service.c'; fi` + +btio/bluetoothd-btio.o: btio/btio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT btio/bluetoothd-btio.o -MD -MP -MF btio/$(DEPDIR)/bluetoothd-btio.Tpo -c -o btio/bluetoothd-btio.o `test -f 'btio/btio.c' || echo '$(srcdir)/'`btio/btio.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) btio/$(DEPDIR)/bluetoothd-btio.Tpo btio/$(DEPDIR)/bluetoothd-btio.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='btio/btio.c' object='btio/bluetoothd-btio.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o btio/bluetoothd-btio.o `test -f 'btio/btio.c' || echo '$(srcdir)/'`btio/btio.c + +btio/bluetoothd-btio.obj: btio/btio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT btio/bluetoothd-btio.obj -MD -MP -MF btio/$(DEPDIR)/bluetoothd-btio.Tpo -c -o btio/bluetoothd-btio.obj `if test -f 'btio/btio.c'; then $(CYGPATH_W) 'btio/btio.c'; else $(CYGPATH_W) '$(srcdir)/btio/btio.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) btio/$(DEPDIR)/bluetoothd-btio.Tpo btio/$(DEPDIR)/bluetoothd-btio.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='btio/btio.c' object='btio/bluetoothd-btio.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o btio/bluetoothd-btio.obj `if test -f 'btio/btio.c'; then $(CYGPATH_W) 'btio/btio.c'; else $(CYGPATH_W) '$(srcdir)/btio/btio.c'; fi` + +src/bluetoothd-main.o: src/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-main.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-main.Tpo -c -o src/bluetoothd-main.o `test -f 'src/main.c' || echo '$(srcdir)/'`src/main.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-main.Tpo src/$(DEPDIR)/bluetoothd-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/main.c' object='src/bluetoothd-main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-main.o `test -f 'src/main.c' || echo '$(srcdir)/'`src/main.c + +src/bluetoothd-main.obj: src/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-main.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-main.Tpo -c -o src/bluetoothd-main.obj `if test -f 'src/main.c'; then $(CYGPATH_W) 'src/main.c'; else $(CYGPATH_W) '$(srcdir)/src/main.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-main.Tpo src/$(DEPDIR)/bluetoothd-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/main.c' object='src/bluetoothd-main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-main.obj `if test -f 'src/main.c'; then $(CYGPATH_W) 'src/main.c'; else $(CYGPATH_W) '$(srcdir)/src/main.c'; fi` + +src/bluetoothd-log.o: src/log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-log.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-log.Tpo -c -o src/bluetoothd-log.o `test -f 'src/log.c' || echo '$(srcdir)/'`src/log.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-log.Tpo src/$(DEPDIR)/bluetoothd-log.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/log.c' object='src/bluetoothd-log.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-log.o `test -f 'src/log.c' || echo '$(srcdir)/'`src/log.c + +src/bluetoothd-log.obj: src/log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-log.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-log.Tpo -c -o src/bluetoothd-log.obj `if test -f 'src/log.c'; then $(CYGPATH_W) 'src/log.c'; else $(CYGPATH_W) '$(srcdir)/src/log.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-log.Tpo src/$(DEPDIR)/bluetoothd-log.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/log.c' object='src/bluetoothd-log.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-log.obj `if test -f 'src/log.c'; then $(CYGPATH_W) 'src/log.c'; else $(CYGPATH_W) '$(srcdir)/src/log.c'; fi` + +src/bluetoothd-backtrace.o: src/backtrace.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-backtrace.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-backtrace.Tpo -c -o src/bluetoothd-backtrace.o `test -f 'src/backtrace.c' || echo '$(srcdir)/'`src/backtrace.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-backtrace.Tpo src/$(DEPDIR)/bluetoothd-backtrace.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/backtrace.c' object='src/bluetoothd-backtrace.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-backtrace.o `test -f 'src/backtrace.c' || echo '$(srcdir)/'`src/backtrace.c + +src/bluetoothd-backtrace.obj: src/backtrace.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-backtrace.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-backtrace.Tpo -c -o src/bluetoothd-backtrace.obj `if test -f 'src/backtrace.c'; then $(CYGPATH_W) 'src/backtrace.c'; else $(CYGPATH_W) '$(srcdir)/src/backtrace.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-backtrace.Tpo src/$(DEPDIR)/bluetoothd-backtrace.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/backtrace.c' object='src/bluetoothd-backtrace.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-backtrace.obj `if test -f 'src/backtrace.c'; then $(CYGPATH_W) 'src/backtrace.c'; else $(CYGPATH_W) '$(srcdir)/src/backtrace.c'; fi` + +src/bluetoothd-rfkill.o: src/rfkill.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-rfkill.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-rfkill.Tpo -c -o src/bluetoothd-rfkill.o `test -f 'src/rfkill.c' || echo '$(srcdir)/'`src/rfkill.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-rfkill.Tpo src/$(DEPDIR)/bluetoothd-rfkill.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/rfkill.c' object='src/bluetoothd-rfkill.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-rfkill.o `test -f 'src/rfkill.c' || echo '$(srcdir)/'`src/rfkill.c + +src/bluetoothd-rfkill.obj: src/rfkill.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-rfkill.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-rfkill.Tpo -c -o src/bluetoothd-rfkill.obj `if test -f 'src/rfkill.c'; then $(CYGPATH_W) 'src/rfkill.c'; else $(CYGPATH_W) '$(srcdir)/src/rfkill.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-rfkill.Tpo src/$(DEPDIR)/bluetoothd-rfkill.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/rfkill.c' object='src/bluetoothd-rfkill.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-rfkill.obj `if test -f 'src/rfkill.c'; then $(CYGPATH_W) 'src/rfkill.c'; else $(CYGPATH_W) '$(srcdir)/src/rfkill.c'; fi` + +src/bluetoothd-sdpd-server.o: src/sdpd-server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-server.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-server.Tpo -c -o src/bluetoothd-sdpd-server.o `test -f 'src/sdpd-server.c' || echo '$(srcdir)/'`src/sdpd-server.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-server.Tpo src/$(DEPDIR)/bluetoothd-sdpd-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-server.c' object='src/bluetoothd-sdpd-server.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-server.o `test -f 'src/sdpd-server.c' || echo '$(srcdir)/'`src/sdpd-server.c + +src/bluetoothd-sdpd-server.obj: src/sdpd-server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-server.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-server.Tpo -c -o src/bluetoothd-sdpd-server.obj `if test -f 'src/sdpd-server.c'; then $(CYGPATH_W) 'src/sdpd-server.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-server.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-server.Tpo src/$(DEPDIR)/bluetoothd-sdpd-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-server.c' object='src/bluetoothd-sdpd-server.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-server.obj `if test -f 'src/sdpd-server.c'; then $(CYGPATH_W) 'src/sdpd-server.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-server.c'; fi` + +src/bluetoothd-sdpd-request.o: src/sdpd-request.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-request.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-request.Tpo -c -o src/bluetoothd-sdpd-request.o `test -f 'src/sdpd-request.c' || echo '$(srcdir)/'`src/sdpd-request.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-request.Tpo src/$(DEPDIR)/bluetoothd-sdpd-request.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-request.c' object='src/bluetoothd-sdpd-request.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-request.o `test -f 'src/sdpd-request.c' || echo '$(srcdir)/'`src/sdpd-request.c + +src/bluetoothd-sdpd-request.obj: src/sdpd-request.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-request.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-request.Tpo -c -o src/bluetoothd-sdpd-request.obj `if test -f 'src/sdpd-request.c'; then $(CYGPATH_W) 'src/sdpd-request.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-request.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-request.Tpo src/$(DEPDIR)/bluetoothd-sdpd-request.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-request.c' object='src/bluetoothd-sdpd-request.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-request.obj `if test -f 'src/sdpd-request.c'; then $(CYGPATH_W) 'src/sdpd-request.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-request.c'; fi` + +src/bluetoothd-sdpd-service.o: src/sdpd-service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-service.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-service.Tpo -c -o src/bluetoothd-sdpd-service.o `test -f 'src/sdpd-service.c' || echo '$(srcdir)/'`src/sdpd-service.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-service.Tpo src/$(DEPDIR)/bluetoothd-sdpd-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-service.c' object='src/bluetoothd-sdpd-service.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-service.o `test -f 'src/sdpd-service.c' || echo '$(srcdir)/'`src/sdpd-service.c + +src/bluetoothd-sdpd-service.obj: src/sdpd-service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-service.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-service.Tpo -c -o src/bluetoothd-sdpd-service.obj `if test -f 'src/sdpd-service.c'; then $(CYGPATH_W) 'src/sdpd-service.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-service.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-service.Tpo src/$(DEPDIR)/bluetoothd-sdpd-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-service.c' object='src/bluetoothd-sdpd-service.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-service.obj `if test -f 'src/sdpd-service.c'; then $(CYGPATH_W) 'src/sdpd-service.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-service.c'; fi` + +src/bluetoothd-sdpd-database.o: src/sdpd-database.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-database.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-database.Tpo -c -o src/bluetoothd-sdpd-database.o `test -f 'src/sdpd-database.c' || echo '$(srcdir)/'`src/sdpd-database.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-database.Tpo src/$(DEPDIR)/bluetoothd-sdpd-database.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-database.c' object='src/bluetoothd-sdpd-database.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-database.o `test -f 'src/sdpd-database.c' || echo '$(srcdir)/'`src/sdpd-database.c + +src/bluetoothd-sdpd-database.obj: src/sdpd-database.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdpd-database.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdpd-database.Tpo -c -o src/bluetoothd-sdpd-database.obj `if test -f 'src/sdpd-database.c'; then $(CYGPATH_W) 'src/sdpd-database.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-database.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdpd-database.Tpo src/$(DEPDIR)/bluetoothd-sdpd-database.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdpd-database.c' object='src/bluetoothd-sdpd-database.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdpd-database.obj `if test -f 'src/sdpd-database.c'; then $(CYGPATH_W) 'src/sdpd-database.c'; else $(CYGPATH_W) '$(srcdir)/src/sdpd-database.c'; fi` + +src/bluetoothd-attrib-server.o: src/attrib-server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-attrib-server.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-attrib-server.Tpo -c -o src/bluetoothd-attrib-server.o `test -f 'src/attrib-server.c' || echo '$(srcdir)/'`src/attrib-server.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-attrib-server.Tpo src/$(DEPDIR)/bluetoothd-attrib-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/attrib-server.c' object='src/bluetoothd-attrib-server.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-attrib-server.o `test -f 'src/attrib-server.c' || echo '$(srcdir)/'`src/attrib-server.c + +src/bluetoothd-attrib-server.obj: src/attrib-server.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-attrib-server.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-attrib-server.Tpo -c -o src/bluetoothd-attrib-server.obj `if test -f 'src/attrib-server.c'; then $(CYGPATH_W) 'src/attrib-server.c'; else $(CYGPATH_W) '$(srcdir)/src/attrib-server.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-attrib-server.Tpo src/$(DEPDIR)/bluetoothd-attrib-server.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/attrib-server.c' object='src/bluetoothd-attrib-server.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-attrib-server.obj `if test -f 'src/attrib-server.c'; then $(CYGPATH_W) 'src/attrib-server.c'; else $(CYGPATH_W) '$(srcdir)/src/attrib-server.c'; fi` + +src/bluetoothd-gatt-database.o: src/gatt-database.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-gatt-database.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-gatt-database.Tpo -c -o src/bluetoothd-gatt-database.o `test -f 'src/gatt-database.c' || echo '$(srcdir)/'`src/gatt-database.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-gatt-database.Tpo src/$(DEPDIR)/bluetoothd-gatt-database.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/gatt-database.c' object='src/bluetoothd-gatt-database.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-gatt-database.o `test -f 'src/gatt-database.c' || echo '$(srcdir)/'`src/gatt-database.c + +src/bluetoothd-gatt-database.obj: src/gatt-database.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-gatt-database.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-gatt-database.Tpo -c -o src/bluetoothd-gatt-database.obj `if test -f 'src/gatt-database.c'; then $(CYGPATH_W) 'src/gatt-database.c'; else $(CYGPATH_W) '$(srcdir)/src/gatt-database.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-gatt-database.Tpo src/$(DEPDIR)/bluetoothd-gatt-database.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/gatt-database.c' object='src/bluetoothd-gatt-database.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-gatt-database.obj `if test -f 'src/gatt-database.c'; then $(CYGPATH_W) 'src/gatt-database.c'; else $(CYGPATH_W) '$(srcdir)/src/gatt-database.c'; fi` + +src/bluetoothd-sdp-xml.o: src/sdp-xml.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdp-xml.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdp-xml.Tpo -c -o src/bluetoothd-sdp-xml.o `test -f 'src/sdp-xml.c' || echo '$(srcdir)/'`src/sdp-xml.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdp-xml.Tpo src/$(DEPDIR)/bluetoothd-sdp-xml.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdp-xml.c' object='src/bluetoothd-sdp-xml.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdp-xml.o `test -f 'src/sdp-xml.c' || echo '$(srcdir)/'`src/sdp-xml.c + +src/bluetoothd-sdp-xml.obj: src/sdp-xml.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdp-xml.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdp-xml.Tpo -c -o src/bluetoothd-sdp-xml.obj `if test -f 'src/sdp-xml.c'; then $(CYGPATH_W) 'src/sdp-xml.c'; else $(CYGPATH_W) '$(srcdir)/src/sdp-xml.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdp-xml.Tpo src/$(DEPDIR)/bluetoothd-sdp-xml.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdp-xml.c' object='src/bluetoothd-sdp-xml.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdp-xml.obj `if test -f 'src/sdp-xml.c'; then $(CYGPATH_W) 'src/sdp-xml.c'; else $(CYGPATH_W) '$(srcdir)/src/sdp-xml.c'; fi` + +src/bluetoothd-sdp-client.o: src/sdp-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdp-client.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdp-client.Tpo -c -o src/bluetoothd-sdp-client.o `test -f 'src/sdp-client.c' || echo '$(srcdir)/'`src/sdp-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdp-client.Tpo src/$(DEPDIR)/bluetoothd-sdp-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdp-client.c' object='src/bluetoothd-sdp-client.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdp-client.o `test -f 'src/sdp-client.c' || echo '$(srcdir)/'`src/sdp-client.c + +src/bluetoothd-sdp-client.obj: src/sdp-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-sdp-client.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-sdp-client.Tpo -c -o src/bluetoothd-sdp-client.obj `if test -f 'src/sdp-client.c'; then $(CYGPATH_W) 'src/sdp-client.c'; else $(CYGPATH_W) '$(srcdir)/src/sdp-client.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-sdp-client.Tpo src/$(DEPDIR)/bluetoothd-sdp-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/sdp-client.c' object='src/bluetoothd-sdp-client.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-sdp-client.obj `if test -f 'src/sdp-client.c'; then $(CYGPATH_W) 'src/sdp-client.c'; else $(CYGPATH_W) '$(srcdir)/src/sdp-client.c'; fi` + +src/bluetoothd-textfile.o: src/textfile.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-textfile.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-textfile.Tpo -c -o src/bluetoothd-textfile.o `test -f 'src/textfile.c' || echo '$(srcdir)/'`src/textfile.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-textfile.Tpo src/$(DEPDIR)/bluetoothd-textfile.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/textfile.c' object='src/bluetoothd-textfile.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-textfile.o `test -f 'src/textfile.c' || echo '$(srcdir)/'`src/textfile.c + +src/bluetoothd-textfile.obj: src/textfile.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-textfile.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-textfile.Tpo -c -o src/bluetoothd-textfile.obj `if test -f 'src/textfile.c'; then $(CYGPATH_W) 'src/textfile.c'; else $(CYGPATH_W) '$(srcdir)/src/textfile.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-textfile.Tpo src/$(DEPDIR)/bluetoothd-textfile.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/textfile.c' object='src/bluetoothd-textfile.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-textfile.obj `if test -f 'src/textfile.c'; then $(CYGPATH_W) 'src/textfile.c'; else $(CYGPATH_W) '$(srcdir)/src/textfile.c'; fi` + +src/bluetoothd-uuid-helper.o: src/uuid-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-uuid-helper.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-uuid-helper.Tpo -c -o src/bluetoothd-uuid-helper.o `test -f 'src/uuid-helper.c' || echo '$(srcdir)/'`src/uuid-helper.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-uuid-helper.Tpo src/$(DEPDIR)/bluetoothd-uuid-helper.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/uuid-helper.c' object='src/bluetoothd-uuid-helper.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-uuid-helper.o `test -f 'src/uuid-helper.c' || echo '$(srcdir)/'`src/uuid-helper.c + +src/bluetoothd-uuid-helper.obj: src/uuid-helper.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-uuid-helper.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-uuid-helper.Tpo -c -o src/bluetoothd-uuid-helper.obj `if test -f 'src/uuid-helper.c'; then $(CYGPATH_W) 'src/uuid-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/uuid-helper.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-uuid-helper.Tpo src/$(DEPDIR)/bluetoothd-uuid-helper.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/uuid-helper.c' object='src/bluetoothd-uuid-helper.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-uuid-helper.obj `if test -f 'src/uuid-helper.c'; then $(CYGPATH_W) 'src/uuid-helper.c'; else $(CYGPATH_W) '$(srcdir)/src/uuid-helper.c'; fi` + +src/bluetoothd-plugin.o: src/plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-plugin.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-plugin.Tpo -c -o src/bluetoothd-plugin.o `test -f 'src/plugin.c' || echo '$(srcdir)/'`src/plugin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-plugin.Tpo src/$(DEPDIR)/bluetoothd-plugin.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/plugin.c' object='src/bluetoothd-plugin.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-plugin.o `test -f 'src/plugin.c' || echo '$(srcdir)/'`src/plugin.c + +src/bluetoothd-plugin.obj: src/plugin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-plugin.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-plugin.Tpo -c -o src/bluetoothd-plugin.obj `if test -f 'src/plugin.c'; then $(CYGPATH_W) 'src/plugin.c'; else $(CYGPATH_W) '$(srcdir)/src/plugin.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-plugin.Tpo src/$(DEPDIR)/bluetoothd-plugin.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/plugin.c' object='src/bluetoothd-plugin.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-plugin.obj `if test -f 'src/plugin.c'; then $(CYGPATH_W) 'src/plugin.c'; else $(CYGPATH_W) '$(srcdir)/src/plugin.c'; fi` + +src/bluetoothd-storage.o: src/storage.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-storage.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-storage.Tpo -c -o src/bluetoothd-storage.o `test -f 'src/storage.c' || echo '$(srcdir)/'`src/storage.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-storage.Tpo src/$(DEPDIR)/bluetoothd-storage.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/storage.c' object='src/bluetoothd-storage.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-storage.o `test -f 'src/storage.c' || echo '$(srcdir)/'`src/storage.c + +src/bluetoothd-storage.obj: src/storage.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-storage.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-storage.Tpo -c -o src/bluetoothd-storage.obj `if test -f 'src/storage.c'; then $(CYGPATH_W) 'src/storage.c'; else $(CYGPATH_W) '$(srcdir)/src/storage.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-storage.Tpo src/$(DEPDIR)/bluetoothd-storage.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/storage.c' object='src/bluetoothd-storage.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-storage.obj `if test -f 'src/storage.c'; then $(CYGPATH_W) 'src/storage.c'; else $(CYGPATH_W) '$(srcdir)/src/storage.c'; fi` + +src/bluetoothd-advertising.o: src/advertising.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-advertising.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-advertising.Tpo -c -o src/bluetoothd-advertising.o `test -f 'src/advertising.c' || echo '$(srcdir)/'`src/advertising.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-advertising.Tpo src/$(DEPDIR)/bluetoothd-advertising.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/advertising.c' object='src/bluetoothd-advertising.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-advertising.o `test -f 'src/advertising.c' || echo '$(srcdir)/'`src/advertising.c + +src/bluetoothd-advertising.obj: src/advertising.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-advertising.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-advertising.Tpo -c -o src/bluetoothd-advertising.obj `if test -f 'src/advertising.c'; then $(CYGPATH_W) 'src/advertising.c'; else $(CYGPATH_W) '$(srcdir)/src/advertising.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-advertising.Tpo src/$(DEPDIR)/bluetoothd-advertising.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/advertising.c' object='src/bluetoothd-advertising.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-advertising.obj `if test -f 'src/advertising.c'; then $(CYGPATH_W) 'src/advertising.c'; else $(CYGPATH_W) '$(srcdir)/src/advertising.c'; fi` + +src/bluetoothd-agent.o: src/agent.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-agent.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-agent.Tpo -c -o src/bluetoothd-agent.o `test -f 'src/agent.c' || echo '$(srcdir)/'`src/agent.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-agent.Tpo src/$(DEPDIR)/bluetoothd-agent.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/agent.c' object='src/bluetoothd-agent.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-agent.o `test -f 'src/agent.c' || echo '$(srcdir)/'`src/agent.c + +src/bluetoothd-agent.obj: src/agent.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-agent.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-agent.Tpo -c -o src/bluetoothd-agent.obj `if test -f 'src/agent.c'; then $(CYGPATH_W) 'src/agent.c'; else $(CYGPATH_W) '$(srcdir)/src/agent.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-agent.Tpo src/$(DEPDIR)/bluetoothd-agent.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/agent.c' object='src/bluetoothd-agent.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-agent.obj `if test -f 'src/agent.c'; then $(CYGPATH_W) 'src/agent.c'; else $(CYGPATH_W) '$(srcdir)/src/agent.c'; fi` + +src/bluetoothd-error.o: src/error.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-error.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-error.Tpo -c -o src/bluetoothd-error.o `test -f 'src/error.c' || echo '$(srcdir)/'`src/error.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-error.Tpo src/$(DEPDIR)/bluetoothd-error.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/error.c' object='src/bluetoothd-error.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-error.o `test -f 'src/error.c' || echo '$(srcdir)/'`src/error.c + +src/bluetoothd-error.obj: src/error.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-error.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-error.Tpo -c -o src/bluetoothd-error.obj `if test -f 'src/error.c'; then $(CYGPATH_W) 'src/error.c'; else $(CYGPATH_W) '$(srcdir)/src/error.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-error.Tpo src/$(DEPDIR)/bluetoothd-error.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/error.c' object='src/bluetoothd-error.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-error.obj `if test -f 'src/error.c'; then $(CYGPATH_W) 'src/error.c'; else $(CYGPATH_W) '$(srcdir)/src/error.c'; fi` + +src/bluetoothd-adapter.o: src/adapter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-adapter.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-adapter.Tpo -c -o src/bluetoothd-adapter.o `test -f 'src/adapter.c' || echo '$(srcdir)/'`src/adapter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-adapter.Tpo src/$(DEPDIR)/bluetoothd-adapter.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/adapter.c' object='src/bluetoothd-adapter.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-adapter.o `test -f 'src/adapter.c' || echo '$(srcdir)/'`src/adapter.c + +src/bluetoothd-adapter.obj: src/adapter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-adapter.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-adapter.Tpo -c -o src/bluetoothd-adapter.obj `if test -f 'src/adapter.c'; then $(CYGPATH_W) 'src/adapter.c'; else $(CYGPATH_W) '$(srcdir)/src/adapter.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-adapter.Tpo src/$(DEPDIR)/bluetoothd-adapter.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/adapter.c' object='src/bluetoothd-adapter.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-adapter.obj `if test -f 'src/adapter.c'; then $(CYGPATH_W) 'src/adapter.c'; else $(CYGPATH_W) '$(srcdir)/src/adapter.c'; fi` + +src/bluetoothd-profile.o: src/profile.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-profile.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-profile.Tpo -c -o src/bluetoothd-profile.o `test -f 'src/profile.c' || echo '$(srcdir)/'`src/profile.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-profile.Tpo src/$(DEPDIR)/bluetoothd-profile.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/profile.c' object='src/bluetoothd-profile.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-profile.o `test -f 'src/profile.c' || echo '$(srcdir)/'`src/profile.c + +src/bluetoothd-profile.obj: src/profile.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-profile.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-profile.Tpo -c -o src/bluetoothd-profile.obj `if test -f 'src/profile.c'; then $(CYGPATH_W) 'src/profile.c'; else $(CYGPATH_W) '$(srcdir)/src/profile.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-profile.Tpo src/$(DEPDIR)/bluetoothd-profile.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/profile.c' object='src/bluetoothd-profile.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-profile.obj `if test -f 'src/profile.c'; then $(CYGPATH_W) 'src/profile.c'; else $(CYGPATH_W) '$(srcdir)/src/profile.c'; fi` + +src/bluetoothd-service.o: src/service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-service.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-service.Tpo -c -o src/bluetoothd-service.o `test -f 'src/service.c' || echo '$(srcdir)/'`src/service.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-service.Tpo src/$(DEPDIR)/bluetoothd-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/service.c' object='src/bluetoothd-service.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-service.o `test -f 'src/service.c' || echo '$(srcdir)/'`src/service.c + +src/bluetoothd-service.obj: src/service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-service.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-service.Tpo -c -o src/bluetoothd-service.obj `if test -f 'src/service.c'; then $(CYGPATH_W) 'src/service.c'; else $(CYGPATH_W) '$(srcdir)/src/service.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-service.Tpo src/$(DEPDIR)/bluetoothd-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/service.c' object='src/bluetoothd-service.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-service.obj `if test -f 'src/service.c'; then $(CYGPATH_W) 'src/service.c'; else $(CYGPATH_W) '$(srcdir)/src/service.c'; fi` + +src/bluetoothd-gatt-client.o: src/gatt-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-gatt-client.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-gatt-client.Tpo -c -o src/bluetoothd-gatt-client.o `test -f 'src/gatt-client.c' || echo '$(srcdir)/'`src/gatt-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-gatt-client.Tpo src/$(DEPDIR)/bluetoothd-gatt-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/gatt-client.c' object='src/bluetoothd-gatt-client.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-gatt-client.o `test -f 'src/gatt-client.c' || echo '$(srcdir)/'`src/gatt-client.c + +src/bluetoothd-gatt-client.obj: src/gatt-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-gatt-client.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-gatt-client.Tpo -c -o src/bluetoothd-gatt-client.obj `if test -f 'src/gatt-client.c'; then $(CYGPATH_W) 'src/gatt-client.c'; else $(CYGPATH_W) '$(srcdir)/src/gatt-client.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-gatt-client.Tpo src/$(DEPDIR)/bluetoothd-gatt-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/gatt-client.c' object='src/bluetoothd-gatt-client.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-gatt-client.obj `if test -f 'src/gatt-client.c'; then $(CYGPATH_W) 'src/gatt-client.c'; else $(CYGPATH_W) '$(srcdir)/src/gatt-client.c'; fi` + +src/bluetoothd-device.o: src/device.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-device.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-device.Tpo -c -o src/bluetoothd-device.o `test -f 'src/device.c' || echo '$(srcdir)/'`src/device.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-device.Tpo src/$(DEPDIR)/bluetoothd-device.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/device.c' object='src/bluetoothd-device.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-device.o `test -f 'src/device.c' || echo '$(srcdir)/'`src/device.c + +src/bluetoothd-device.obj: src/device.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-device.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-device.Tpo -c -o src/bluetoothd-device.obj `if test -f 'src/device.c'; then $(CYGPATH_W) 'src/device.c'; else $(CYGPATH_W) '$(srcdir)/src/device.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-device.Tpo src/$(DEPDIR)/bluetoothd-device.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/device.c' object='src/bluetoothd-device.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-device.obj `if test -f 'src/device.c'; then $(CYGPATH_W) 'src/device.c'; else $(CYGPATH_W) '$(srcdir)/src/device.c'; fi` + +src/bluetoothd-dbus-common.o: src/dbus-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-dbus-common.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-dbus-common.Tpo -c -o src/bluetoothd-dbus-common.o `test -f 'src/dbus-common.c' || echo '$(srcdir)/'`src/dbus-common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-dbus-common.Tpo src/$(DEPDIR)/bluetoothd-dbus-common.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dbus-common.c' object='src/bluetoothd-dbus-common.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-dbus-common.o `test -f 'src/dbus-common.c' || echo '$(srcdir)/'`src/dbus-common.c + +src/bluetoothd-dbus-common.obj: src/dbus-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-dbus-common.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-dbus-common.Tpo -c -o src/bluetoothd-dbus-common.obj `if test -f 'src/dbus-common.c'; then $(CYGPATH_W) 'src/dbus-common.c'; else $(CYGPATH_W) '$(srcdir)/src/dbus-common.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-dbus-common.Tpo src/$(DEPDIR)/bluetoothd-dbus-common.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/dbus-common.c' object='src/bluetoothd-dbus-common.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-dbus-common.obj `if test -f 'src/dbus-common.c'; then $(CYGPATH_W) 'src/dbus-common.c'; else $(CYGPATH_W) '$(srcdir)/src/dbus-common.c'; fi` + +src/bluetoothd-eir.o: src/eir.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-eir.o -MD -MP -MF src/$(DEPDIR)/bluetoothd-eir.Tpo -c -o src/bluetoothd-eir.o `test -f 'src/eir.c' || echo '$(srcdir)/'`src/eir.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-eir.Tpo src/$(DEPDIR)/bluetoothd-eir.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/eir.c' object='src/bluetoothd-eir.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-eir.o `test -f 'src/eir.c' || echo '$(srcdir)/'`src/eir.c + +src/bluetoothd-eir.obj: src/eir.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT src/bluetoothd-eir.obj -MD -MP -MF src/$(DEPDIR)/bluetoothd-eir.Tpo -c -o src/bluetoothd-eir.obj `if test -f 'src/eir.c'; then $(CYGPATH_W) 'src/eir.c'; else $(CYGPATH_W) '$(srcdir)/src/eir.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/$(DEPDIR)/bluetoothd-eir.Tpo src/$(DEPDIR)/bluetoothd-eir.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/eir.c' object='src/bluetoothd-eir.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(src_bluetoothd_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o src/bluetoothd-eir.obj `if test -f 'src/eir.c'; then $(CYGPATH_W) 'src/eir.c'; else $(CYGPATH_W) '$(srcdir)/src/eir.c'; fi` + +unit/test_mesh_crypto-test-mesh-crypto.o: unit/test-mesh-crypto.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unit/test_mesh_crypto-test-mesh-crypto.o -MD -MP -MF unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Tpo -c -o unit/test_mesh_crypto-test-mesh-crypto.o `test -f 'unit/test-mesh-crypto.c' || echo '$(srcdir)/'`unit/test-mesh-crypto.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Tpo unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='unit/test-mesh-crypto.c' object='unit/test_mesh_crypto-test-mesh-crypto.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unit/test_mesh_crypto-test-mesh-crypto.o `test -f 'unit/test-mesh-crypto.c' || echo '$(srcdir)/'`unit/test-mesh-crypto.c + +unit/test_mesh_crypto-test-mesh-crypto.obj: unit/test-mesh-crypto.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unit/test_mesh_crypto-test-mesh-crypto.obj -MD -MP -MF unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Tpo -c -o unit/test_mesh_crypto-test-mesh-crypto.obj `if test -f 'unit/test-mesh-crypto.c'; then $(CYGPATH_W) 'unit/test-mesh-crypto.c'; else $(CYGPATH_W) '$(srcdir)/unit/test-mesh-crypto.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Tpo unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='unit/test-mesh-crypto.c' object='unit/test_mesh_crypto-test-mesh-crypto.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unit/test_mesh_crypto-test-mesh-crypto.obj `if test -f 'unit/test-mesh-crypto.c'; then $(CYGPATH_W) 'unit/test-mesh-crypto.c'; else $(CYGPATH_W) '$(srcdir)/unit/test-mesh-crypto.c'; fi` + +ell/unit_test_mesh_crypto-util.o: ell/util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-util.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-util.Tpo -c -o ell/unit_test_mesh_crypto-util.o `test -f 'ell/util.c' || echo '$(srcdir)/'`ell/util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-util.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/util.c' object='ell/unit_test_mesh_crypto-util.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-util.o `test -f 'ell/util.c' || echo '$(srcdir)/'`ell/util.c + +ell/unit_test_mesh_crypto-util.obj: ell/util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-util.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-util.Tpo -c -o ell/unit_test_mesh_crypto-util.obj `if test -f 'ell/util.c'; then $(CYGPATH_W) 'ell/util.c'; else $(CYGPATH_W) '$(srcdir)/ell/util.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-util.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/util.c' object='ell/unit_test_mesh_crypto-util.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-util.obj `if test -f 'ell/util.c'; then $(CYGPATH_W) 'ell/util.c'; else $(CYGPATH_W) '$(srcdir)/ell/util.c'; fi` + +ell/unit_test_mesh_crypto-log.o: ell/log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-log.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-log.Tpo -c -o ell/unit_test_mesh_crypto-log.o `test -f 'ell/log.c' || echo '$(srcdir)/'`ell/log.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-log.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-log.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/log.c' object='ell/unit_test_mesh_crypto-log.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-log.o `test -f 'ell/log.c' || echo '$(srcdir)/'`ell/log.c + +ell/unit_test_mesh_crypto-log.obj: ell/log.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-log.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-log.Tpo -c -o ell/unit_test_mesh_crypto-log.obj `if test -f 'ell/log.c'; then $(CYGPATH_W) 'ell/log.c'; else $(CYGPATH_W) '$(srcdir)/ell/log.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-log.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-log.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/log.c' object='ell/unit_test_mesh_crypto-log.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-log.obj `if test -f 'ell/log.c'; then $(CYGPATH_W) 'ell/log.c'; else $(CYGPATH_W) '$(srcdir)/ell/log.c'; fi` + +ell/unit_test_mesh_crypto-queue.o: ell/queue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-queue.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-queue.Tpo -c -o ell/unit_test_mesh_crypto-queue.o `test -f 'ell/queue.c' || echo '$(srcdir)/'`ell/queue.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-queue.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-queue.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/queue.c' object='ell/unit_test_mesh_crypto-queue.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-queue.o `test -f 'ell/queue.c' || echo '$(srcdir)/'`ell/queue.c + +ell/unit_test_mesh_crypto-queue.obj: ell/queue.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-queue.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-queue.Tpo -c -o ell/unit_test_mesh_crypto-queue.obj `if test -f 'ell/queue.c'; then $(CYGPATH_W) 'ell/queue.c'; else $(CYGPATH_W) '$(srcdir)/ell/queue.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-queue.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-queue.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/queue.c' object='ell/unit_test_mesh_crypto-queue.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-queue.obj `if test -f 'ell/queue.c'; then $(CYGPATH_W) 'ell/queue.c'; else $(CYGPATH_W) '$(srcdir)/ell/queue.c'; fi` + +ell/unit_test_mesh_crypto-hashmap.o: ell/hashmap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-hashmap.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-hashmap.Tpo -c -o ell/unit_test_mesh_crypto-hashmap.o `test -f 'ell/hashmap.c' || echo '$(srcdir)/'`ell/hashmap.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-hashmap.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-hashmap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/hashmap.c' object='ell/unit_test_mesh_crypto-hashmap.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-hashmap.o `test -f 'ell/hashmap.c' || echo '$(srcdir)/'`ell/hashmap.c + +ell/unit_test_mesh_crypto-hashmap.obj: ell/hashmap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-hashmap.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-hashmap.Tpo -c -o ell/unit_test_mesh_crypto-hashmap.obj `if test -f 'ell/hashmap.c'; then $(CYGPATH_W) 'ell/hashmap.c'; else $(CYGPATH_W) '$(srcdir)/ell/hashmap.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-hashmap.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-hashmap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/hashmap.c' object='ell/unit_test_mesh_crypto-hashmap.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-hashmap.obj `if test -f 'ell/hashmap.c'; then $(CYGPATH_W) 'ell/hashmap.c'; else $(CYGPATH_W) '$(srcdir)/ell/hashmap.c'; fi` + +ell/unit_test_mesh_crypto-random.o: ell/random.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-random.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-random.Tpo -c -o ell/unit_test_mesh_crypto-random.o `test -f 'ell/random.c' || echo '$(srcdir)/'`ell/random.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-random.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-random.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/random.c' object='ell/unit_test_mesh_crypto-random.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-random.o `test -f 'ell/random.c' || echo '$(srcdir)/'`ell/random.c + +ell/unit_test_mesh_crypto-random.obj: ell/random.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-random.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-random.Tpo -c -o ell/unit_test_mesh_crypto-random.obj `if test -f 'ell/random.c'; then $(CYGPATH_W) 'ell/random.c'; else $(CYGPATH_W) '$(srcdir)/ell/random.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-random.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-random.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/random.c' object='ell/unit_test_mesh_crypto-random.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-random.obj `if test -f 'ell/random.c'; then $(CYGPATH_W) 'ell/random.c'; else $(CYGPATH_W) '$(srcdir)/ell/random.c'; fi` + +ell/unit_test_mesh_crypto-signal.o: ell/signal.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-signal.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-signal.Tpo -c -o ell/unit_test_mesh_crypto-signal.o `test -f 'ell/signal.c' || echo '$(srcdir)/'`ell/signal.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-signal.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-signal.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/signal.c' object='ell/unit_test_mesh_crypto-signal.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-signal.o `test -f 'ell/signal.c' || echo '$(srcdir)/'`ell/signal.c + +ell/unit_test_mesh_crypto-signal.obj: ell/signal.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-signal.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-signal.Tpo -c -o ell/unit_test_mesh_crypto-signal.obj `if test -f 'ell/signal.c'; then $(CYGPATH_W) 'ell/signal.c'; else $(CYGPATH_W) '$(srcdir)/ell/signal.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-signal.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-signal.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/signal.c' object='ell/unit_test_mesh_crypto-signal.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-signal.obj `if test -f 'ell/signal.c'; then $(CYGPATH_W) 'ell/signal.c'; else $(CYGPATH_W) '$(srcdir)/ell/signal.c'; fi` + +ell/unit_test_mesh_crypto-timeout.o: ell/timeout.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-timeout.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-timeout.Tpo -c -o ell/unit_test_mesh_crypto-timeout.o `test -f 'ell/timeout.c' || echo '$(srcdir)/'`ell/timeout.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-timeout.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-timeout.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/timeout.c' object='ell/unit_test_mesh_crypto-timeout.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-timeout.o `test -f 'ell/timeout.c' || echo '$(srcdir)/'`ell/timeout.c + +ell/unit_test_mesh_crypto-timeout.obj: ell/timeout.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-timeout.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-timeout.Tpo -c -o ell/unit_test_mesh_crypto-timeout.obj `if test -f 'ell/timeout.c'; then $(CYGPATH_W) 'ell/timeout.c'; else $(CYGPATH_W) '$(srcdir)/ell/timeout.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-timeout.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-timeout.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/timeout.c' object='ell/unit_test_mesh_crypto-timeout.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-timeout.obj `if test -f 'ell/timeout.c'; then $(CYGPATH_W) 'ell/timeout.c'; else $(CYGPATH_W) '$(srcdir)/ell/timeout.c'; fi` + +ell/unit_test_mesh_crypto-io.o: ell/io.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-io.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-io.Tpo -c -o ell/unit_test_mesh_crypto-io.o `test -f 'ell/io.c' || echo '$(srcdir)/'`ell/io.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-io.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-io.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/io.c' object='ell/unit_test_mesh_crypto-io.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-io.o `test -f 'ell/io.c' || echo '$(srcdir)/'`ell/io.c + +ell/unit_test_mesh_crypto-io.obj: ell/io.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-io.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-io.Tpo -c -o ell/unit_test_mesh_crypto-io.obj `if test -f 'ell/io.c'; then $(CYGPATH_W) 'ell/io.c'; else $(CYGPATH_W) '$(srcdir)/ell/io.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-io.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-io.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/io.c' object='ell/unit_test_mesh_crypto-io.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-io.obj `if test -f 'ell/io.c'; then $(CYGPATH_W) 'ell/io.c'; else $(CYGPATH_W) '$(srcdir)/ell/io.c'; fi` + +ell/unit_test_mesh_crypto-idle.o: ell/idle.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-idle.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-idle.Tpo -c -o ell/unit_test_mesh_crypto-idle.o `test -f 'ell/idle.c' || echo '$(srcdir)/'`ell/idle.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-idle.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-idle.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/idle.c' object='ell/unit_test_mesh_crypto-idle.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-idle.o `test -f 'ell/idle.c' || echo '$(srcdir)/'`ell/idle.c + +ell/unit_test_mesh_crypto-idle.obj: ell/idle.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-idle.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-idle.Tpo -c -o ell/unit_test_mesh_crypto-idle.obj `if test -f 'ell/idle.c'; then $(CYGPATH_W) 'ell/idle.c'; else $(CYGPATH_W) '$(srcdir)/ell/idle.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-idle.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-idle.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/idle.c' object='ell/unit_test_mesh_crypto-idle.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-idle.obj `if test -f 'ell/idle.c'; then $(CYGPATH_W) 'ell/idle.c'; else $(CYGPATH_W) '$(srcdir)/ell/idle.c'; fi` + +ell/unit_test_mesh_crypto-main.o: ell/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-main.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-main.Tpo -c -o ell/unit_test_mesh_crypto-main.o `test -f 'ell/main.c' || echo '$(srcdir)/'`ell/main.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-main.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/main.c' object='ell/unit_test_mesh_crypto-main.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-main.o `test -f 'ell/main.c' || echo '$(srcdir)/'`ell/main.c + +ell/unit_test_mesh_crypto-main.obj: ell/main.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-main.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-main.Tpo -c -o ell/unit_test_mesh_crypto-main.obj `if test -f 'ell/main.c'; then $(CYGPATH_W) 'ell/main.c'; else $(CYGPATH_W) '$(srcdir)/ell/main.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-main.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-main.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/main.c' object='ell/unit_test_mesh_crypto-main.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-main.obj `if test -f 'ell/main.c'; then $(CYGPATH_W) 'ell/main.c'; else $(CYGPATH_W) '$(srcdir)/ell/main.c'; fi` + +ell/unit_test_mesh_crypto-strv.o: ell/strv.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-strv.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-strv.Tpo -c -o ell/unit_test_mesh_crypto-strv.o `test -f 'ell/strv.c' || echo '$(srcdir)/'`ell/strv.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-strv.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-strv.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/strv.c' object='ell/unit_test_mesh_crypto-strv.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-strv.o `test -f 'ell/strv.c' || echo '$(srcdir)/'`ell/strv.c + +ell/unit_test_mesh_crypto-strv.obj: ell/strv.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-strv.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-strv.Tpo -c -o ell/unit_test_mesh_crypto-strv.obj `if test -f 'ell/strv.c'; then $(CYGPATH_W) 'ell/strv.c'; else $(CYGPATH_W) '$(srcdir)/ell/strv.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-strv.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-strv.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/strv.c' object='ell/unit_test_mesh_crypto-strv.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-strv.obj `if test -f 'ell/strv.c'; then $(CYGPATH_W) 'ell/strv.c'; else $(CYGPATH_W) '$(srcdir)/ell/strv.c'; fi` + +ell/unit_test_mesh_crypto-string.o: ell/string.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-string.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-string.Tpo -c -o ell/unit_test_mesh_crypto-string.o `test -f 'ell/string.c' || echo '$(srcdir)/'`ell/string.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-string.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-string.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/string.c' object='ell/unit_test_mesh_crypto-string.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-string.o `test -f 'ell/string.c' || echo '$(srcdir)/'`ell/string.c + +ell/unit_test_mesh_crypto-string.obj: ell/string.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-string.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-string.Tpo -c -o ell/unit_test_mesh_crypto-string.obj `if test -f 'ell/string.c'; then $(CYGPATH_W) 'ell/string.c'; else $(CYGPATH_W) '$(srcdir)/ell/string.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-string.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-string.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/string.c' object='ell/unit_test_mesh_crypto-string.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-string.obj `if test -f 'ell/string.c'; then $(CYGPATH_W) 'ell/string.c'; else $(CYGPATH_W) '$(srcdir)/ell/string.c'; fi` + +ell/unit_test_mesh_crypto-cipher.o: ell/cipher.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-cipher.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-cipher.Tpo -c -o ell/unit_test_mesh_crypto-cipher.o `test -f 'ell/cipher.c' || echo '$(srcdir)/'`ell/cipher.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-cipher.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-cipher.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/cipher.c' object='ell/unit_test_mesh_crypto-cipher.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-cipher.o `test -f 'ell/cipher.c' || echo '$(srcdir)/'`ell/cipher.c + +ell/unit_test_mesh_crypto-cipher.obj: ell/cipher.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-cipher.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-cipher.Tpo -c -o ell/unit_test_mesh_crypto-cipher.obj `if test -f 'ell/cipher.c'; then $(CYGPATH_W) 'ell/cipher.c'; else $(CYGPATH_W) '$(srcdir)/ell/cipher.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-cipher.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-cipher.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/cipher.c' object='ell/unit_test_mesh_crypto-cipher.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-cipher.obj `if test -f 'ell/cipher.c'; then $(CYGPATH_W) 'ell/cipher.c'; else $(CYGPATH_W) '$(srcdir)/ell/cipher.c'; fi` + +ell/unit_test_mesh_crypto-checksum.o: ell/checksum.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-checksum.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-checksum.Tpo -c -o ell/unit_test_mesh_crypto-checksum.o `test -f 'ell/checksum.c' || echo '$(srcdir)/'`ell/checksum.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-checksum.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-checksum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/checksum.c' object='ell/unit_test_mesh_crypto-checksum.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-checksum.o `test -f 'ell/checksum.c' || echo '$(srcdir)/'`ell/checksum.c + +ell/unit_test_mesh_crypto-checksum.obj: ell/checksum.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-checksum.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-checksum.Tpo -c -o ell/unit_test_mesh_crypto-checksum.obj `if test -f 'ell/checksum.c'; then $(CYGPATH_W) 'ell/checksum.c'; else $(CYGPATH_W) '$(srcdir)/ell/checksum.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-checksum.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-checksum.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/checksum.c' object='ell/unit_test_mesh_crypto-checksum.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-checksum.obj `if test -f 'ell/checksum.c'; then $(CYGPATH_W) 'ell/checksum.c'; else $(CYGPATH_W) '$(srcdir)/ell/checksum.c'; fi` + +ell/unit_test_mesh_crypto-utf8.o: ell/utf8.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-utf8.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-utf8.Tpo -c -o ell/unit_test_mesh_crypto-utf8.o `test -f 'ell/utf8.c' || echo '$(srcdir)/'`ell/utf8.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-utf8.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-utf8.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/utf8.c' object='ell/unit_test_mesh_crypto-utf8.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-utf8.o `test -f 'ell/utf8.c' || echo '$(srcdir)/'`ell/utf8.c + +ell/unit_test_mesh_crypto-utf8.obj: ell/utf8.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-utf8.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-utf8.Tpo -c -o ell/unit_test_mesh_crypto-utf8.obj `if test -f 'ell/utf8.c'; then $(CYGPATH_W) 'ell/utf8.c'; else $(CYGPATH_W) '$(srcdir)/ell/utf8.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-utf8.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-utf8.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/utf8.c' object='ell/unit_test_mesh_crypto-utf8.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-utf8.obj `if test -f 'ell/utf8.c'; then $(CYGPATH_W) 'ell/utf8.c'; else $(CYGPATH_W) '$(srcdir)/ell/utf8.c'; fi` + +ell/unit_test_mesh_crypto-dbus.o: ell/dbus.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus.Tpo -c -o ell/unit_test_mesh_crypto-dbus.o `test -f 'ell/dbus.c' || echo '$(srcdir)/'`ell/dbus.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus.c' object='ell/unit_test_mesh_crypto-dbus.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus.o `test -f 'ell/dbus.c' || echo '$(srcdir)/'`ell/dbus.c + +ell/unit_test_mesh_crypto-dbus.obj: ell/dbus.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus.Tpo -c -o ell/unit_test_mesh_crypto-dbus.obj `if test -f 'ell/dbus.c'; then $(CYGPATH_W) 'ell/dbus.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus.c' object='ell/unit_test_mesh_crypto-dbus.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus.obj `if test -f 'ell/dbus.c'; then $(CYGPATH_W) 'ell/dbus.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus.c'; fi` + +ell/unit_test_mesh_crypto-dbus-message.o: ell/dbus-message.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-message.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-message.Tpo -c -o ell/unit_test_mesh_crypto-dbus-message.o `test -f 'ell/dbus-message.c' || echo '$(srcdir)/'`ell/dbus-message.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-message.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-message.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-message.c' object='ell/unit_test_mesh_crypto-dbus-message.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-message.o `test -f 'ell/dbus-message.c' || echo '$(srcdir)/'`ell/dbus-message.c + +ell/unit_test_mesh_crypto-dbus-message.obj: ell/dbus-message.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-message.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-message.Tpo -c -o ell/unit_test_mesh_crypto-dbus-message.obj `if test -f 'ell/dbus-message.c'; then $(CYGPATH_W) 'ell/dbus-message.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-message.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-message.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-message.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-message.c' object='ell/unit_test_mesh_crypto-dbus-message.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-message.obj `if test -f 'ell/dbus-message.c'; then $(CYGPATH_W) 'ell/dbus-message.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-message.c'; fi` + +ell/unit_test_mesh_crypto-dbus-util.o: ell/dbus-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-util.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-util.Tpo -c -o ell/unit_test_mesh_crypto-dbus-util.o `test -f 'ell/dbus-util.c' || echo '$(srcdir)/'`ell/dbus-util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-util.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-util.c' object='ell/unit_test_mesh_crypto-dbus-util.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-util.o `test -f 'ell/dbus-util.c' || echo '$(srcdir)/'`ell/dbus-util.c + +ell/unit_test_mesh_crypto-dbus-util.obj: ell/dbus-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-util.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-util.Tpo -c -o ell/unit_test_mesh_crypto-dbus-util.obj `if test -f 'ell/dbus-util.c'; then $(CYGPATH_W) 'ell/dbus-util.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-util.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-util.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-util.c' object='ell/unit_test_mesh_crypto-dbus-util.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-util.obj `if test -f 'ell/dbus-util.c'; then $(CYGPATH_W) 'ell/dbus-util.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-util.c'; fi` + +ell/unit_test_mesh_crypto-dbus-service.o: ell/dbus-service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-service.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-service.Tpo -c -o ell/unit_test_mesh_crypto-dbus-service.o `test -f 'ell/dbus-service.c' || echo '$(srcdir)/'`ell/dbus-service.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-service.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-service.c' object='ell/unit_test_mesh_crypto-dbus-service.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-service.o `test -f 'ell/dbus-service.c' || echo '$(srcdir)/'`ell/dbus-service.c + +ell/unit_test_mesh_crypto-dbus-service.obj: ell/dbus-service.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-service.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-service.Tpo -c -o ell/unit_test_mesh_crypto-dbus-service.obj `if test -f 'ell/dbus-service.c'; then $(CYGPATH_W) 'ell/dbus-service.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-service.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-service.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-service.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-service.c' object='ell/unit_test_mesh_crypto-dbus-service.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-service.obj `if test -f 'ell/dbus-service.c'; then $(CYGPATH_W) 'ell/dbus-service.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-service.c'; fi` + +ell/unit_test_mesh_crypto-dbus-client.o: ell/dbus-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-client.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-client.Tpo -c -o ell/unit_test_mesh_crypto-dbus-client.o `test -f 'ell/dbus-client.c' || echo '$(srcdir)/'`ell/dbus-client.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-client.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-client.c' object='ell/unit_test_mesh_crypto-dbus-client.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-client.o `test -f 'ell/dbus-client.c' || echo '$(srcdir)/'`ell/dbus-client.c + +ell/unit_test_mesh_crypto-dbus-client.obj: ell/dbus-client.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-client.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-client.Tpo -c -o ell/unit_test_mesh_crypto-dbus-client.obj `if test -f 'ell/dbus-client.c'; then $(CYGPATH_W) 'ell/dbus-client.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-client.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-client.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-client.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-client.c' object='ell/unit_test_mesh_crypto-dbus-client.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-client.obj `if test -f 'ell/dbus-client.c'; then $(CYGPATH_W) 'ell/dbus-client.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-client.c'; fi` + +ell/unit_test_mesh_crypto-dbus-name-cache.o: ell/dbus-name-cache.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-name-cache.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-name-cache.Tpo -c -o ell/unit_test_mesh_crypto-dbus-name-cache.o `test -f 'ell/dbus-name-cache.c' || echo '$(srcdir)/'`ell/dbus-name-cache.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-name-cache.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-name-cache.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-name-cache.c' object='ell/unit_test_mesh_crypto-dbus-name-cache.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-name-cache.o `test -f 'ell/dbus-name-cache.c' || echo '$(srcdir)/'`ell/dbus-name-cache.c + +ell/unit_test_mesh_crypto-dbus-name-cache.obj: ell/dbus-name-cache.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-name-cache.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-name-cache.Tpo -c -o ell/unit_test_mesh_crypto-dbus-name-cache.obj `if test -f 'ell/dbus-name-cache.c'; then $(CYGPATH_W) 'ell/dbus-name-cache.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-name-cache.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-name-cache.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-name-cache.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-name-cache.c' object='ell/unit_test_mesh_crypto-dbus-name-cache.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-name-cache.obj `if test -f 'ell/dbus-name-cache.c'; then $(CYGPATH_W) 'ell/dbus-name-cache.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-name-cache.c'; fi` + +ell/unit_test_mesh_crypto-dbus-filter.o: ell/dbus-filter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-filter.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-filter.Tpo -c -o ell/unit_test_mesh_crypto-dbus-filter.o `test -f 'ell/dbus-filter.c' || echo '$(srcdir)/'`ell/dbus-filter.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-filter.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-filter.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-filter.c' object='ell/unit_test_mesh_crypto-dbus-filter.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-filter.o `test -f 'ell/dbus-filter.c' || echo '$(srcdir)/'`ell/dbus-filter.c + +ell/unit_test_mesh_crypto-dbus-filter.obj: ell/dbus-filter.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-dbus-filter.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-filter.Tpo -c -o ell/unit_test_mesh_crypto-dbus-filter.obj `if test -f 'ell/dbus-filter.c'; then $(CYGPATH_W) 'ell/dbus-filter.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-filter.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-filter.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-filter.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/dbus-filter.c' object='ell/unit_test_mesh_crypto-dbus-filter.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-dbus-filter.obj `if test -f 'ell/dbus-filter.c'; then $(CYGPATH_W) 'ell/dbus-filter.c'; else $(CYGPATH_W) '$(srcdir)/ell/dbus-filter.c'; fi` + +ell/unit_test_mesh_crypto-gvariant-util.o: ell/gvariant-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-gvariant-util.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-gvariant-util.Tpo -c -o ell/unit_test_mesh_crypto-gvariant-util.o `test -f 'ell/gvariant-util.c' || echo '$(srcdir)/'`ell/gvariant-util.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-gvariant-util.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-gvariant-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/gvariant-util.c' object='ell/unit_test_mesh_crypto-gvariant-util.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-gvariant-util.o `test -f 'ell/gvariant-util.c' || echo '$(srcdir)/'`ell/gvariant-util.c + +ell/unit_test_mesh_crypto-gvariant-util.obj: ell/gvariant-util.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-gvariant-util.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-gvariant-util.Tpo -c -o ell/unit_test_mesh_crypto-gvariant-util.obj `if test -f 'ell/gvariant-util.c'; then $(CYGPATH_W) 'ell/gvariant-util.c'; else $(CYGPATH_W) '$(srcdir)/ell/gvariant-util.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-gvariant-util.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-gvariant-util.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/gvariant-util.c' object='ell/unit_test_mesh_crypto-gvariant-util.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-gvariant-util.obj `if test -f 'ell/gvariant-util.c'; then $(CYGPATH_W) 'ell/gvariant-util.c'; else $(CYGPATH_W) '$(srcdir)/ell/gvariant-util.c'; fi` + +ell/unit_test_mesh_crypto-siphash.o: ell/siphash.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-siphash.o -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-siphash.Tpo -c -o ell/unit_test_mesh_crypto-siphash.o `test -f 'ell/siphash.c' || echo '$(srcdir)/'`ell/siphash.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-siphash.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-siphash.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/siphash.c' object='ell/unit_test_mesh_crypto-siphash.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-siphash.o `test -f 'ell/siphash.c' || echo '$(srcdir)/'`ell/siphash.c + +ell/unit_test_mesh_crypto-siphash.obj: ell/siphash.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ell/unit_test_mesh_crypto-siphash.obj -MD -MP -MF ell/$(DEPDIR)/unit_test_mesh_crypto-siphash.Tpo -c -o ell/unit_test_mesh_crypto-siphash.obj `if test -f 'ell/siphash.c'; then $(CYGPATH_W) 'ell/siphash.c'; else $(CYGPATH_W) '$(srcdir)/ell/siphash.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ell/$(DEPDIR)/unit_test_mesh_crypto-siphash.Tpo ell/$(DEPDIR)/unit_test_mesh_crypto-siphash.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ell/siphash.c' object='ell/unit_test_mesh_crypto-siphash.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_mesh_crypto_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ell/unit_test_mesh_crypto-siphash.obj `if test -f 'ell/siphash.c'; then $(CYGPATH_W) 'ell/siphash.c'; else $(CYGPATH_W) '$(srcdir)/ell/siphash.c'; fi` + +unit/test_midi-test-midi.o: unit/test-midi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unit/test_midi-test-midi.o -MD -MP -MF unit/$(DEPDIR)/test_midi-test-midi.Tpo -c -o unit/test_midi-test-midi.o `test -f 'unit/test-midi.c' || echo '$(srcdir)/'`unit/test-midi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) unit/$(DEPDIR)/test_midi-test-midi.Tpo unit/$(DEPDIR)/test_midi-test-midi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='unit/test-midi.c' object='unit/test_midi-test-midi.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unit/test_midi-test-midi.o `test -f 'unit/test-midi.c' || echo '$(srcdir)/'`unit/test-midi.c + +unit/test_midi-test-midi.obj: unit/test-midi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT unit/test_midi-test-midi.obj -MD -MP -MF unit/$(DEPDIR)/test_midi-test-midi.Tpo -c -o unit/test_midi-test-midi.obj `if test -f 'unit/test-midi.c'; then $(CYGPATH_W) 'unit/test-midi.c'; else $(CYGPATH_W) '$(srcdir)/unit/test-midi.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) unit/$(DEPDIR)/test_midi-test-midi.Tpo unit/$(DEPDIR)/test_midi-test-midi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='unit/test-midi.c' object='unit/test_midi-test-midi.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o unit/test_midi-test-midi.obj `if test -f 'unit/test-midi.c'; then $(CYGPATH_W) 'unit/test-midi.c'; else $(CYGPATH_W) '$(srcdir)/unit/test-midi.c'; fi` + +profiles/midi/unit_test_midi-libmidi.o: profiles/midi/libmidi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/unit_test_midi-libmidi.o -MD -MP -MF profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Tpo -c -o profiles/midi/unit_test_midi-libmidi.o `test -f 'profiles/midi/libmidi.c' || echo '$(srcdir)/'`profiles/midi/libmidi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Tpo profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/libmidi.c' object='profiles/midi/unit_test_midi-libmidi.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/unit_test_midi-libmidi.o `test -f 'profiles/midi/libmidi.c' || echo '$(srcdir)/'`profiles/midi/libmidi.c + +profiles/midi/unit_test_midi-libmidi.obj: profiles/midi/libmidi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT profiles/midi/unit_test_midi-libmidi.obj -MD -MP -MF profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Tpo -c -o profiles/midi/unit_test_midi-libmidi.obj `if test -f 'profiles/midi/libmidi.c'; then $(CYGPATH_W) 'profiles/midi/libmidi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/libmidi.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Tpo profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='profiles/midi/libmidi.c' object='profiles/midi/unit_test_midi-libmidi.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(unit_test_midi_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o profiles/midi/unit_test_midi-libmidi.obj `if test -f 'profiles/midi/libmidi.c'; then $(CYGPATH_W) 'profiles/midi/libmidi.c'; else $(CYGPATH_W) '$(srcdir)/profiles/midi/libmidi.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + -rm -rf android/.libs android/_libs + -rm -rf android/audio_utils/.libs android/audio_utils/_libs + -rm -rf attrib/.libs attrib/_libs + -rm -rf client/.libs client/_libs + -rm -rf ell/.libs ell/_libs + -rm -rf emulator/.libs emulator/_libs + -rm -rf gdbus/.libs gdbus/_libs + -rm -rf lib/.libs lib/_libs + -rm -rf mesh/.libs mesh/_libs + -rm -rf monitor/.libs monitor/_libs + -rm -rf obexd/src/.libs obexd/src/_libs + -rm -rf peripheral/.libs peripheral/_libs + -rm -rf plugins/.libs plugins/_libs + -rm -rf profiles/cups/.libs profiles/cups/_libs + -rm -rf profiles/iap/.libs profiles/iap/_libs + -rm -rf src/.libs src/_libs + -rm -rf src/shared/.libs src/shared/_libs + -rm -rf tools/.libs tools/_libs + -rm -rf unit/.libs unit/_libs + +distclean-libtool: + -rm -f libtool config.lt +install-man1: $(dist_man_MANS) $(man_MANS) + @$(NORMAL_INSTALL) + @list1=''; \ + list2='$(dist_man_MANS) $(man_MANS)'; \ + test -n "$(man1dir)" \ + && test -n "`echo $$list1$$list2`" \ + || exit 0; \ + echo " $(MKDIR_P) '$(DESTDIR)$(man1dir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(man1dir)" || exit 1; \ + { for i in $$list1; do echo "$$i"; done; \ + if test -n "$$list2"; then \ + for i in $$list2; do echo "$$i"; done \ + | sed -n '/\.1[a-z]*$$/p'; \ + fi; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man1dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man1dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man1dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man1dir)" || exit $$?; }; \ + done; } + +uninstall-man1: + @$(NORMAL_UNINSTALL) + @list=''; test -n "$(man1dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + l2='$(dist_man_MANS) $(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.1[a-z]*$$/p'; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^1][0-9a-z]*$$,1,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + dir='$(DESTDIR)$(man1dir)'; $(am__uninstall_files_from_dir) +install-man8: $(dist_man_MANS) $(man_MANS) + @$(NORMAL_INSTALL) + @list1=''; \ + list2='$(dist_man_MANS) $(man_MANS)'; \ + test -n "$(man8dir)" \ + && test -n "`echo $$list1$$list2`" \ + || exit 0; \ + echo " $(MKDIR_P) '$(DESTDIR)$(man8dir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(man8dir)" || exit 1; \ + { for i in $$list1; do echo "$$i"; done; \ + if test -n "$$list2"; then \ + for i in $$list2; do echo "$$i"; done \ + | sed -n '/\.8[a-z]*$$/p'; \ + fi; \ + } | while read p; do \ + if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; echo "$$p"; \ + done | \ + sed -e 'n;s,.*/,,;p;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,' | \ + sed 'N;N;s,\n, ,g' | { \ + list=; while read file base inst; do \ + if test "$$base" = "$$inst"; then list="$$list $$file"; else \ + echo " $(INSTALL_DATA) '$$file' '$(DESTDIR)$(man8dir)/$$inst'"; \ + $(INSTALL_DATA) "$$file" "$(DESTDIR)$(man8dir)/$$inst" || exit $$?; \ + fi; \ + done; \ + for i in $$list; do echo "$$i"; done | $(am__base_list) | \ + while read files; do \ + test -z "$$files" || { \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(man8dir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(man8dir)" || exit $$?; }; \ + done; } + +uninstall-man8: + @$(NORMAL_UNINSTALL) + @list=''; test -n "$(man8dir)" || exit 0; \ + files=`{ for i in $$list; do echo "$$i"; done; \ + l2='$(dist_man_MANS) $(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ + sed -n '/\.8[a-z]*$$/p'; \ + } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ + -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ + dir='$(DESTDIR)$(man8dir)'; $(am__uninstall_files_from_dir) +install-confDATA: $(conf_DATA) + @$(NORMAL_INSTALL) + @list='$(conf_DATA)'; test -n "$(confdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(confdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(confdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(confdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(confdir)" || exit $$?; \ + done + +uninstall-confDATA: + @$(NORMAL_UNINSTALL) + @list='$(conf_DATA)'; test -n "$(confdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(confdir)'; $(am__uninstall_files_from_dir) +install-dbusDATA: $(dbus_DATA) + @$(NORMAL_INSTALL) + @list='$(dbus_DATA)'; test -n "$(dbusdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(dbusdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(dbusdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(dbusdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(dbusdir)" || exit $$?; \ + done + +uninstall-dbusDATA: + @$(NORMAL_UNINSTALL) + @list='$(dbus_DATA)'; test -n "$(dbusdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(dbusdir)'; $(am__uninstall_files_from_dir) +install-dbussessionbusDATA: $(dbussessionbus_DATA) + @$(NORMAL_INSTALL) + @list='$(dbussessionbus_DATA)'; test -n "$(dbussessionbusdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(dbussessionbusdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(dbussessionbusdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(dbussessionbusdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(dbussessionbusdir)" || exit $$?; \ + done + +uninstall-dbussessionbusDATA: + @$(NORMAL_UNINSTALL) + @list='$(dbussessionbus_DATA)'; test -n "$(dbussessionbusdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(dbussessionbusdir)'; $(am__uninstall_files_from_dir) +install-dbussystembusDATA: $(dbussystembus_DATA) + @$(NORMAL_INSTALL) + @list='$(dbussystembus_DATA)'; test -n "$(dbussystembusdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(dbussystembusdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(dbussystembusdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(dbussystembusdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(dbussystembusdir)" || exit $$?; \ + done + +uninstall-dbussystembusDATA: + @$(NORMAL_UNINSTALL) + @list='$(dbussystembus_DATA)'; test -n "$(dbussystembusdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(dbussystembusdir)'; $(am__uninstall_files_from_dir) +install-dist_zshcompletionDATA: $(dist_zshcompletion_DATA) + @$(NORMAL_INSTALL) + @list='$(dist_zshcompletion_DATA)'; test -n "$(zshcompletiondir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(zshcompletiondir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(zshcompletiondir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(zshcompletiondir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(zshcompletiondir)" || exit $$?; \ + done + +uninstall-dist_zshcompletionDATA: + @$(NORMAL_UNINSTALL) + @list='$(dist_zshcompletion_DATA)'; test -n "$(zshcompletiondir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(zshcompletiondir)'; $(am__uninstall_files_from_dir) +install-pkgconfigDATA: $(pkgconfig_DATA) + @$(NORMAL_INSTALL) + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkgconfigdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkgconfigdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgconfigdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgconfigdir)" || exit $$?; \ + done + +uninstall-pkgconfigDATA: + @$(NORMAL_UNINSTALL) + @list='$(pkgconfig_DATA)'; test -n "$(pkgconfigdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkgconfigdir)'; $(am__uninstall_files_from_dir) +install-rulesDATA: $(rules_DATA) + @$(NORMAL_INSTALL) + @list='$(rules_DATA)'; test -n "$(rulesdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(rulesdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(rulesdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(rulesdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(rulesdir)" || exit $$?; \ + done + +uninstall-rulesDATA: + @$(NORMAL_UNINSTALL) + @list='$(rules_DATA)'; test -n "$(rulesdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(rulesdir)'; $(am__uninstall_files_from_dir) +install-stateDATA: $(state_DATA) + @$(NORMAL_INSTALL) + @list='$(state_DATA)'; test -n "$(statedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(statedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(statedir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(statedir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(statedir)" || exit $$?; \ + done + +uninstall-stateDATA: + @$(NORMAL_UNINSTALL) + @list='$(state_DATA)'; test -n "$(statedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(statedir)'; $(am__uninstall_files_from_dir) +install-systemdsystemunitDATA: $(systemdsystemunit_DATA) + @$(NORMAL_INSTALL) + @list='$(systemdsystemunit_DATA)'; test -n "$(systemdsystemunitdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(systemdsystemunitdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(systemdsystemunitdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(systemdsystemunitdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(systemdsystemunitdir)" || exit $$?; \ + done + +uninstall-systemdsystemunitDATA: + @$(NORMAL_UNINSTALL) + @list='$(systemdsystemunit_DATA)'; test -n "$(systemdsystemunitdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(systemdsystemunitdir)'; $(am__uninstall_files_from_dir) +install-systemduserunitDATA: $(systemduserunit_DATA) + @$(NORMAL_INSTALL) + @list='$(systemduserunit_DATA)'; test -n "$(systemduserunitdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(systemduserunitdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(systemduserunitdir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(systemduserunitdir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(systemduserunitdir)" || exit $$?; \ + done + +uninstall-systemduserunitDATA: + @$(NORMAL_UNINSTALL) + @list='$(systemduserunit_DATA)'; test -n "$(systemduserunitdir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(systemduserunitdir)'; $(am__uninstall_files_from_dir) +install-pkgincludeHEADERS: $(pkginclude_HEADERS) + @$(NORMAL_INSTALL) + @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkgincludedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkgincludedir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkgincludedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkgincludedir)" || exit $$?; \ + done + +uninstall-pkgincludeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(pkginclude_HEADERS)'; test -n "$(pkgincludedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkgincludedir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscope: cscope.files + test ! -s cscope.files \ + || $(CSCOPE) -b -q $(AM_CSCOPEFLAGS) $(CSCOPEFLAGS) -i cscope.files $(CSCOPE_ARGS) +clean-cscope: + -rm -f cscope.files +cscope.files: clean-cscope cscopelist +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + -rm -f cscope.out cscope.in.out cscope.po.out cscope.files + +# Recover from deleted '.trs' file; this should ensure that +# "rm -f foo.log; make foo.trs" re-run 'foo.test', and re-create +# both 'foo.log' and 'foo.trs'. Break the recipe in two subshells +# to avoid problems with "make -n". +.log.trs: + rm -f $< $@ + $(MAKE) $(AM_MAKEFLAGS) $< + +# Leading 'am--fnord' is there to ensure the list of targets does not +# expand to empty, as could happen e.g. with make check TESTS=''. +am--fnord $(TEST_LOGS) $(TEST_LOGS:.log=.trs): $(am__force_recheck) +am--force-recheck: + @: + +$(TEST_SUITE_LOG): $(TEST_LOGS) + @$(am__set_TESTS_bases); \ + am__f_ok () { test -f "$$1" && test -r "$$1"; }; \ + redo_bases=`for i in $$bases; do \ + am__f_ok $$i.trs && am__f_ok $$i.log || echo $$i; \ + done`; \ + if test -n "$$redo_bases"; then \ + redo_logs=`for i in $$redo_bases; do echo $$i.log; done`; \ + redo_results=`for i in $$redo_bases; do echo $$i.trs; done`; \ + if $(am__make_dryrun); then :; else \ + rm -f $$redo_logs && rm -f $$redo_results || exit 1; \ + fi; \ + fi; \ + if test -n "$$am__remaking_logs"; then \ + echo "fatal: making $(TEST_SUITE_LOG): possible infinite" \ + "recursion detected" >&2; \ + elif test -n "$$redo_logs"; then \ + am__remaking_logs=yes $(MAKE) $(AM_MAKEFLAGS) $$redo_logs; \ + fi; \ + if $(am__make_dryrun); then :; else \ + st=0; \ + errmsg="fatal: making $(TEST_SUITE_LOG): failed to create"; \ + for i in $$redo_bases; do \ + test -f $$i.trs && test -r $$i.trs \ + || { echo "$$errmsg $$i.trs" >&2; st=1; }; \ + test -f $$i.log && test -r $$i.log \ + || { echo "$$errmsg $$i.log" >&2; st=1; }; \ + done; \ + test $$st -eq 0 || exit 1; \ + fi + @$(am__sh_e_setup); $(am__tty_colors); $(am__set_TESTS_bases); \ + ws='[ ]'; \ + results=`for b in $$bases; do echo $$b.trs; done`; \ + test -n "$$results" || results=/dev/null; \ + all=` grep "^$$ws*:test-result:" $$results | wc -l`; \ + pass=` grep "^$$ws*:test-result:$$ws*PASS" $$results | wc -l`; \ + fail=` grep "^$$ws*:test-result:$$ws*FAIL" $$results | wc -l`; \ + skip=` grep "^$$ws*:test-result:$$ws*SKIP" $$results | wc -l`; \ + xfail=`grep "^$$ws*:test-result:$$ws*XFAIL" $$results | wc -l`; \ + xpass=`grep "^$$ws*:test-result:$$ws*XPASS" $$results | wc -l`; \ + error=`grep "^$$ws*:test-result:$$ws*ERROR" $$results | wc -l`; \ + if test `expr $$fail + $$xpass + $$error` -eq 0; then \ + success=true; \ + else \ + success=false; \ + fi; \ + br='==================='; br=$$br$$br$$br$$br; \ + result_count () \ + { \ + if test x"$$1" = x"--maybe-color"; then \ + maybe_colorize=yes; \ + elif test x"$$1" = x"--no-color"; then \ + maybe_colorize=no; \ + else \ + echo "$@: invalid 'result_count' usage" >&2; exit 4; \ + fi; \ + shift; \ + desc=$$1 count=$$2; \ + if test $$maybe_colorize = yes && test $$count -gt 0; then \ + color_start=$$3 color_end=$$std; \ + else \ + color_start= color_end=; \ + fi; \ + echo "$${color_start}# $$desc $$count$${color_end}"; \ + }; \ + create_testsuite_report () \ + { \ + result_count $$1 "TOTAL:" $$all "$$brg"; \ + result_count $$1 "PASS: " $$pass "$$grn"; \ + result_count $$1 "SKIP: " $$skip "$$blu"; \ + result_count $$1 "XFAIL:" $$xfail "$$lgn"; \ + result_count $$1 "FAIL: " $$fail "$$red"; \ + result_count $$1 "XPASS:" $$xpass "$$red"; \ + result_count $$1 "ERROR:" $$error "$$mgn"; \ + }; \ + { \ + echo "$(PACKAGE_STRING): $(subdir)/$(TEST_SUITE_LOG)" | \ + $(am__rst_title); \ + create_testsuite_report --no-color; \ + echo; \ + echo ".. contents:: :depth: 2"; \ + echo; \ + for b in $$bases; do echo $$b; done \ + | $(am__create_global_log); \ + } >$(TEST_SUITE_LOG).tmp || exit 1; \ + mv $(TEST_SUITE_LOG).tmp $(TEST_SUITE_LOG); \ + if $$success; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + test x"$$VERBOSE" = x || cat $(TEST_SUITE_LOG); \ + fi; \ + echo "$${col}$$br$${std}"; \ + echo "$${col}Testsuite summary for $(PACKAGE_STRING)$${std}"; \ + echo "$${col}$$br$${std}"; \ + create_testsuite_report --maybe-color; \ + echo "$$col$$br$$std"; \ + if $$success; then :; else \ + echo "$${col}See $(subdir)/$(TEST_SUITE_LOG)$${std}"; \ + if test -n "$(PACKAGE_BUGREPORT)"; then \ + echo "$${col}Please report to $(PACKAGE_BUGREPORT)$${std}"; \ + fi; \ + echo "$$col$$br$$std"; \ + fi; \ + $$success || exit 1 + +check-TESTS: + @list='$(RECHECK_LOGS)'; test -z "$$list" || rm -f $$list + @list='$(RECHECK_LOGS:.log=.trs)'; test -z "$$list" || rm -f $$list + @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + @set +e; $(am__set_TESTS_bases); \ + log_list=`for i in $$bases; do echo $$i.log; done`; \ + trs_list=`for i in $$bases; do echo $$i.trs; done`; \ + log_list=`echo $$log_list`; trs_list=`echo $$trs_list`; \ + $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) TEST_LOGS="$$log_list"; \ + exit $$?; +recheck: all + @test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + @set +e; $(am__set_TESTS_bases); \ + bases=`for i in $$bases; do echo $$i; done \ + | $(am__list_recheck_tests)` || exit 1; \ + log_list=`for i in $$bases; do echo $$i.log; done`; \ + log_list=`echo $$log_list`; \ + $(MAKE) $(AM_MAKEFLAGS) $(TEST_SUITE_LOG) \ + am__force_recheck=am--force-recheck \ + TEST_LOGS="$$log_list"; \ + exit $$? +android/test-ipc.log: android/test-ipc$(EXEEXT) + @p='android/test-ipc$(EXEEXT)'; \ + b='android/test-ipc'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-eir.log: unit/test-eir$(EXEEXT) + @p='unit/test-eir$(EXEEXT)'; \ + b='unit/test-eir'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-uuid.log: unit/test-uuid$(EXEEXT) + @p='unit/test-uuid$(EXEEXT)'; \ + b='unit/test-uuid'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-textfile.log: unit/test-textfile$(EXEEXT) + @p='unit/test-textfile$(EXEEXT)'; \ + b='unit/test-textfile'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-crc.log: unit/test-crc$(EXEEXT) + @p='unit/test-crc$(EXEEXT)'; \ + b='unit/test-crc'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-crypto.log: unit/test-crypto$(EXEEXT) + @p='unit/test-crypto$(EXEEXT)'; \ + b='unit/test-crypto'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-ecc.log: unit/test-ecc$(EXEEXT) + @p='unit/test-ecc$(EXEEXT)'; \ + b='unit/test-ecc'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-ringbuf.log: unit/test-ringbuf$(EXEEXT) + @p='unit/test-ringbuf$(EXEEXT)'; \ + b='unit/test-ringbuf'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-queue.log: unit/test-queue$(EXEEXT) + @p='unit/test-queue$(EXEEXT)'; \ + b='unit/test-queue'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-mgmt.log: unit/test-mgmt$(EXEEXT) + @p='unit/test-mgmt$(EXEEXT)'; \ + b='unit/test-mgmt'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-uhid.log: unit/test-uhid$(EXEEXT) + @p='unit/test-uhid$(EXEEXT)'; \ + b='unit/test-uhid'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-sdp.log: unit/test-sdp$(EXEEXT) + @p='unit/test-sdp$(EXEEXT)'; \ + b='unit/test-sdp'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-avdtp.log: unit/test-avdtp$(EXEEXT) + @p='unit/test-avdtp$(EXEEXT)'; \ + b='unit/test-avdtp'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-avctp.log: unit/test-avctp$(EXEEXT) + @p='unit/test-avctp$(EXEEXT)'; \ + b='unit/test-avctp'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-avrcp.log: unit/test-avrcp$(EXEEXT) + @p='unit/test-avrcp$(EXEEXT)'; \ + b='unit/test-avrcp'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-hfp.log: unit/test-hfp$(EXEEXT) + @p='unit/test-hfp$(EXEEXT)'; \ + b='unit/test-hfp'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-gdbus-client.log: unit/test-gdbus-client$(EXEEXT) + @p='unit/test-gdbus-client$(EXEEXT)'; \ + b='unit/test-gdbus-client'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-gobex-header.log: unit/test-gobex-header$(EXEEXT) + @p='unit/test-gobex-header$(EXEEXT)'; \ + b='unit/test-gobex-header'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-gobex-packet.log: unit/test-gobex-packet$(EXEEXT) + @p='unit/test-gobex-packet$(EXEEXT)'; \ + b='unit/test-gobex-packet'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-gobex.log: unit/test-gobex$(EXEEXT) + @p='unit/test-gobex$(EXEEXT)'; \ + b='unit/test-gobex'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-gobex-transfer.log: unit/test-gobex-transfer$(EXEEXT) + @p='unit/test-gobex-transfer$(EXEEXT)'; \ + b='unit/test-gobex-transfer'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-gobex-apparam.log: unit/test-gobex-apparam$(EXEEXT) + @p='unit/test-gobex-apparam$(EXEEXT)'; \ + b='unit/test-gobex-apparam'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-lib.log: unit/test-lib$(EXEEXT) + @p='unit/test-lib$(EXEEXT)'; \ + b='unit/test-lib'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-gatt.log: unit/test-gatt$(EXEEXT) + @p='unit/test-gatt$(EXEEXT)'; \ + b='unit/test-gatt'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-hog.log: unit/test-hog$(EXEEXT) + @p='unit/test-hog$(EXEEXT)'; \ + b='unit/test-hog'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-gattrib.log: unit/test-gattrib$(EXEEXT) + @p='unit/test-gattrib$(EXEEXT)'; \ + b='unit/test-gattrib'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-midi.log: unit/test-midi$(EXEEXT) + @p='unit/test-midi$(EXEEXT)'; \ + b='unit/test-midi'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +unit/test-mesh-crypto.log: unit/test-mesh-crypto$(EXEEXT) + @p='unit/test-mesh-crypto$(EXEEXT)'; \ + b='unit/test-mesh-crypto'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +.test.log: + @p='$<'; \ + $(am__set_b); \ + $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +@am__EXEEXT_TRUE@.test$(EXEEXT).log: +@am__EXEEXT_TRUE@ @p='$<'; \ +@am__EXEEXT_TRUE@ $(am__set_b); \ +@am__EXEEXT_TRUE@ $(am__check_pre) $(TEST_LOG_DRIVER) --test-name "$$f" \ +@am__EXEEXT_TRUE@ --log-file $$b.log --trs-file $$b.trs \ +@am__EXEEXT_TRUE@ $(am__common_driver_flags) $(AM_TEST_LOG_DRIVER_FLAGS) $(TEST_LOG_DRIVER_FLAGS) -- $(TEST_LOG_COMPILE) \ +@am__EXEEXT_TRUE@ "$$tst" $(AM_TESTS_FD_REDIRECT) + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + $(am__remove_distdir) + test -d "$(distdir)" || mkdir "$(distdir)" + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + -test -n "$(am__skip_mode_fix)" \ + || find "$(distdir)" -type d ! -perm -755 \ + -exec chmod u+rwx,go+rx {} \; -o \ + ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -400 -exec chmod a+r {} \; -o \ + ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \ + || chmod -R a+r "$(distdir)" +dist-gzip: distdir + tardir=$(distdir) && $(am__tar) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).tar.gz + $(am__post_remove_distdir) + +dist-bzip2: distdir + tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 + $(am__post_remove_distdir) + +dist-lzip: distdir + tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz + $(am__post_remove_distdir) +dist-xz: distdir + tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz + $(am__post_remove_distdir) + +dist-tarZ: distdir + @echo WARNING: "Support for distribution archives compressed with" \ + "legacy program 'compress' is deprecated." >&2 + @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 + tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z + $(am__post_remove_distdir) + +dist-shar: distdir + @echo WARNING: "Support for shar distribution archives is" \ + "deprecated." >&2 + @echo WARNING: "It will be removed altogether in Automake 2.0" >&2 + shar $(distdir) | eval GZIP= gzip $(GZIP_ENV) -c >$(distdir).shar.gz + $(am__post_remove_distdir) + +dist-zip: distdir + -rm -f $(distdir).zip + zip -rq $(distdir).zip $(distdir) + $(am__post_remove_distdir) + +dist dist-all: + $(MAKE) $(AM_MAKEFLAGS) $(DIST_TARGETS) am__post_remove_distdir='@:' + $(am__post_remove_distdir) + +# This target untars the dist file and tries a VPATH configuration. Then +# it guarantees that the distribution is self-contained by making another +# tarfile. +distcheck: dist + case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + esac + chmod -R a-w $(distdir) + chmod u+w $(distdir) + mkdir $(distdir)/_build $(distdir)/_build/sub $(distdir)/_inst + chmod a-w $(distdir) + test -d $(distdir)/_build || exit 0; \ + dc_install_base=`$(am__cd) $(distdir)/_inst && pwd | sed -e 's,^[^:\\/]:[\\/],/,'` \ + && dc_destdir="$${TMPDIR-/tmp}/am-dc-$$$$/" \ + && am__cwd=`pwd` \ + && $(am__cd) $(distdir)/_build/sub \ + && ../../configure \ + $(AM_DISTCHECK_CONFIGURE_FLAGS) \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + --srcdir=../.. --prefix="$$dc_install_base" \ + && $(MAKE) $(AM_MAKEFLAGS) \ + && $(MAKE) $(AM_MAKEFLAGS) dvi \ + && $(MAKE) $(AM_MAKEFLAGS) check \ + && $(MAKE) $(AM_MAKEFLAGS) install \ + && $(MAKE) $(AM_MAKEFLAGS) installcheck \ + && $(MAKE) $(AM_MAKEFLAGS) uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) distuninstallcheck_dir="$$dc_install_base" \ + distuninstallcheck \ + && chmod -R a-w "$$dc_install_base" \ + && ({ \ + (cd ../.. && umask 077 && mkdir "$$dc_destdir") \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" install \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" uninstall \ + && $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$dc_destdir" \ + distuninstallcheck_dir="$$dc_destdir" distuninstallcheck; \ + } || { rm -rf "$$dc_destdir"; exit 1; }) \ + && rm -rf "$$dc_destdir" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && rm -rf $(DIST_ARCHIVES) \ + && $(MAKE) $(AM_MAKEFLAGS) distcleancheck \ + && cd "$$am__cwd" \ + || exit 1 + $(am__post_remove_distdir) + @(echo "$(distdir) archives ready for distribution: "; \ + list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ + sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' +distuninstallcheck: + @test -n '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: trying to run $@ with an empty' \ + '$$(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + $(am__cd) '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left after uninstall:" ; \ + if test -n "$(DESTDIR)"; then \ + echo " (check DESTDIR support)"; \ + fi ; \ + $(distuninstallcheck_listfiles) ; \ + exit 1; } >&2 +distcleancheck: distclean + @if test '$(srcdir)' = . ; then \ + echo "ERROR: distcleancheck can only run from a VPATH build" ; \ + exit 1 ; \ + fi + @test `$(distcleancheck_listfiles) | wc -l` -eq 0 \ + || { echo "ERROR: files left in build directory after distclean:" ; \ + $(distcleancheck_listfiles) ; \ + exit 1; } >&2 +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-am +all-am: Makefile $(PROGRAMS) $(LIBRARIES) $(LTLIBRARIES) $(SCRIPTS) \ + $(MANS) $(DATA) $(HEADERS) config.h +install-binPROGRAMS: install-libLTLIBRARIES + +installdirs: + for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(cupsdir)" "$(DESTDIR)$(pkglibexecdir)" "$(DESTDIR)$(udevdir)" "$(DESTDIR)$(libdir)" "$(DESTDIR)$(plugindir)" "$(DESTDIR)$(testdir)" "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(man8dir)" "$(DESTDIR)$(confdir)" "$(DESTDIR)$(dbusdir)" "$(DESTDIR)$(dbussessionbusdir)" "$(DESTDIR)$(dbussystembusdir)" "$(DESTDIR)$(zshcompletiondir)" "$(DESTDIR)$(pkgconfigdir)" "$(DESTDIR)$(rulesdir)" "$(DESTDIR)$(statedir)" "$(DESTDIR)$(systemdsystemunitdir)" "$(DESTDIR)$(systemduserunitdir)" "$(DESTDIR)$(pkgincludedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + -test -z "$(TEST_LOGS)" || rm -f $(TEST_LOGS) + -test -z "$(TEST_LOGS:.log=.trs)" || rm -f $(TEST_LOGS:.log=.trs) + -test -z "$(TEST_SUITE_LOG)" || rm -f $(TEST_SUITE_LOG) + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + -rm -f android/$(DEPDIR)/$(am__dirstamp) + -rm -f android/$(am__dirstamp) + -rm -f android/audio_utils/$(DEPDIR)/$(am__dirstamp) + -rm -f android/audio_utils/$(am__dirstamp) + -rm -f android/client/$(DEPDIR)/$(am__dirstamp) + -rm -f android/client/$(am__dirstamp) + -rm -f android/hardware/$(DEPDIR)/$(am__dirstamp) + -rm -f android/hardware/$(am__dirstamp) + -rm -f attrib/$(DEPDIR)/$(am__dirstamp) + -rm -f attrib/$(am__dirstamp) + -rm -f btio/$(DEPDIR)/$(am__dirstamp) + -rm -f btio/$(am__dirstamp) + -rm -f client/$(DEPDIR)/$(am__dirstamp) + -rm -f client/$(am__dirstamp) + -rm -f ell/$(DEPDIR)/$(am__dirstamp) + -rm -f ell/$(am__dirstamp) + -rm -f emulator/$(DEPDIR)/$(am__dirstamp) + -rm -f emulator/$(am__dirstamp) + -rm -f gdbus/$(DEPDIR)/$(am__dirstamp) + -rm -f gdbus/$(am__dirstamp) + -rm -f gobex/$(DEPDIR)/$(am__dirstamp) + -rm -f gobex/$(am__dirstamp) + -rm -f lib/$(DEPDIR)/$(am__dirstamp) + -rm -f lib/$(am__dirstamp) + -rm -f mesh/$(DEPDIR)/$(am__dirstamp) + -rm -f mesh/$(am__dirstamp) + -rm -f monitor/$(DEPDIR)/$(am__dirstamp) + -rm -f monitor/$(am__dirstamp) + -rm -f obexd/client/$(DEPDIR)/$(am__dirstamp) + -rm -f obexd/client/$(am__dirstamp) + -rm -f obexd/plugins/$(DEPDIR)/$(am__dirstamp) + -rm -f obexd/plugins/$(am__dirstamp) + -rm -f obexd/src/$(DEPDIR)/$(am__dirstamp) + -rm -f obexd/src/$(am__dirstamp) + -rm -f peripheral/$(DEPDIR)/$(am__dirstamp) + -rm -f peripheral/$(am__dirstamp) + -rm -f plugins/$(DEPDIR)/$(am__dirstamp) + -rm -f plugins/$(am__dirstamp) + -rm -f profiles/audio/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/audio/$(am__dirstamp) + -rm -f profiles/battery/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/battery/$(am__dirstamp) + -rm -f profiles/cups/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/cups/$(am__dirstamp) + -rm -f profiles/deviceinfo/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/deviceinfo/$(am__dirstamp) + -rm -f profiles/gap/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/gap/$(am__dirstamp) + -rm -f profiles/health/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/health/$(am__dirstamp) + -rm -f profiles/iap/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/iap/$(am__dirstamp) + -rm -f profiles/input/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/input/$(am__dirstamp) + -rm -f profiles/midi/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/midi/$(am__dirstamp) + -rm -f profiles/network/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/network/$(am__dirstamp) + -rm -f profiles/sap/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/sap/$(am__dirstamp) + -rm -f profiles/scanparam/$(DEPDIR)/$(am__dirstamp) + -rm -f profiles/scanparam/$(am__dirstamp) + -rm -f src/$(DEPDIR)/$(am__dirstamp) + -rm -f src/$(am__dirstamp) + -rm -f src/shared/$(DEPDIR)/$(am__dirstamp) + -rm -f src/shared/$(am__dirstamp) + -rm -f tools/$(DEPDIR)/$(am__dirstamp) + -rm -f tools/$(am__dirstamp) + -rm -f tools/mesh/$(DEPDIR)/$(am__dirstamp) + -rm -f tools/mesh/$(am__dirstamp) + -rm -f tools/parser/$(DEPDIR)/$(am__dirstamp) + -rm -f tools/parser/$(am__dirstamp) + -rm -f unit/$(DEPDIR)/$(am__dirstamp) + -rm -f unit/$(am__dirstamp) + -test -z "$(DISTCLEANFILES)" || rm -f $(DISTCLEANFILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) + -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES) +clean: clean-am + +clean-am: clean-binPROGRAMS clean-cupsPROGRAMS clean-generic \ + clean-libLTLIBRARIES clean-libtool clean-local \ + clean-noinstLIBRARIES clean-noinstLTLIBRARIES \ + clean-noinstPROGRAMS clean-pkglibexecPROGRAMS \ + clean-pluginLTLIBRARIES clean-udevPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -f android/$(DEPDIR)/a2dp-sink.Po + -rm -f android/$(DEPDIR)/a2dp.Po + -rm -f android/$(DEPDIR)/android_tester-tester-a2dp.Po + -rm -f android/$(DEPDIR)/android_tester-tester-avrcp.Po + -rm -f android/$(DEPDIR)/android_tester-tester-bluetooth.Po + -rm -f android/$(DEPDIR)/android_tester-tester-gatt.Po + -rm -f android/$(DEPDIR)/android_tester-tester-hdp.Po + -rm -f android/$(DEPDIR)/android_tester-tester-hidhost.Po + -rm -f android/$(DEPDIR)/android_tester-tester-main.Po + -rm -f android/$(DEPDIR)/android_tester-tester-map-client.Po + -rm -f android/$(DEPDIR)/android_tester-tester-pan.Po + -rm -f android/$(DEPDIR)/android_tester-tester-socket.Po + -rm -f android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-aptx.Plo + -rm -f android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-sbc.Plo + -rm -f android/$(DEPDIR)/audio_a2dp_default_la-hal-audio.Plo + -rm -f android/$(DEPDIR)/audio_sco_default_la-hal-sco.Plo + -rm -f android/$(DEPDIR)/avctp.Po + -rm -f android/$(DEPDIR)/avdtp.Po + -rm -f android/$(DEPDIR)/avdtptest-avdtp.Po + -rm -f android/$(DEPDIR)/avdtptest-avdtptest.Po + -rm -f android/$(DEPDIR)/avrcp-lib.Po + -rm -f android/$(DEPDIR)/avrcp.Po + -rm -f android/$(DEPDIR)/bluetooth.Po + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-a2dp-sink.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-a2dp.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-avrcp-ctrl.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-avrcp.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-bluetooth.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-gatt.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-handsfree-client.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-handsfree.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-health.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-hidhost.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-ipc.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-map-client.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-pan.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-socket.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-utils.Plo + -rm -f android/$(DEPDIR)/bluetoothd-snoop.Po + -rm -f android/$(DEPDIR)/gatt.Po + -rm -f android/$(DEPDIR)/haltest-hal-utils.Po + -rm -f android/$(DEPDIR)/handsfree-client.Po + -rm -f android/$(DEPDIR)/handsfree.Po + -rm -f android/$(DEPDIR)/health.Po + -rm -f android/$(DEPDIR)/hidhost.Po + -rm -f android/$(DEPDIR)/ipc.Po + -rm -f android/$(DEPDIR)/ipc_tester-hal-utils.Po + -rm -f android/$(DEPDIR)/ipc_tester-ipc-tester.Po + -rm -f android/$(DEPDIR)/main.Po + -rm -f android/$(DEPDIR)/map-client.Po + -rm -f android/$(DEPDIR)/pan.Po + -rm -f android/$(DEPDIR)/sco.Po + -rm -f android/$(DEPDIR)/socket.Po + -rm -f android/$(DEPDIR)/system-emulator.Po + -rm -f android/$(DEPDIR)/test-ipc.Po + -rm -f android/audio_utils/$(DEPDIR)/audio_sco_default_la-resampler.Plo + -rm -f android/client/$(DEPDIR)/haltest-haltest.Po + -rm -f android/client/$(DEPDIR)/haltest-history.Po + -rm -f android/client/$(DEPDIR)/haltest-if-audio.Po + -rm -f android/client/$(DEPDIR)/haltest-if-av-sink.Po + -rm -f android/client/$(DEPDIR)/haltest-if-av.Po + -rm -f android/client/$(DEPDIR)/haltest-if-bt.Po + -rm -f android/client/$(DEPDIR)/haltest-if-gatt.Po + -rm -f android/client/$(DEPDIR)/haltest-if-hf-client.Po + -rm -f android/client/$(DEPDIR)/haltest-if-hf.Po + -rm -f android/client/$(DEPDIR)/haltest-if-hh.Po + -rm -f android/client/$(DEPDIR)/haltest-if-hl.Po + -rm -f android/client/$(DEPDIR)/haltest-if-mce.Po + -rm -f android/client/$(DEPDIR)/haltest-if-pan.Po + -rm -f android/client/$(DEPDIR)/haltest-if-rc-ctrl.Po + -rm -f android/client/$(DEPDIR)/haltest-if-rc.Po + -rm -f android/client/$(DEPDIR)/haltest-if-sco.Po + -rm -f android/client/$(DEPDIR)/haltest-if-sock.Po + -rm -f android/client/$(DEPDIR)/haltest-pollhandler.Po + -rm -f android/client/$(DEPDIR)/haltest-tabcompletion.Po + -rm -f android/client/$(DEPDIR)/haltest-terminal.Po + -rm -f android/hardware/$(DEPDIR)/android_tester-hardware.Po + -rm -f android/hardware/$(DEPDIR)/haltest-hardware.Po + -rm -f attrib/$(DEPDIR)/att.Po + -rm -f attrib/$(DEPDIR)/bluetoothd-att.Po + -rm -f attrib/$(DEPDIR)/bluetoothd-gatt-service.Po + -rm -f attrib/$(DEPDIR)/bluetoothd-gatt.Po + -rm -f attrib/$(DEPDIR)/bluetoothd-gattrib.Po + -rm -f attrib/$(DEPDIR)/gatt.Po + -rm -f attrib/$(DEPDIR)/gattrib.Po + -rm -f attrib/$(DEPDIR)/gatttool.Po + -rm -f attrib/$(DEPDIR)/interactive.Po + -rm -f attrib/$(DEPDIR)/utils.Po + -rm -f btio/$(DEPDIR)/android_avdtptest-btio.Po + -rm -f btio/$(DEPDIR)/bluetoothd-btio.Po + -rm -f btio/$(DEPDIR)/btio.Po + -rm -f btio/$(DEPDIR)/obexd-btio.Po + -rm -f client/$(DEPDIR)/advertising.Po + -rm -f client/$(DEPDIR)/agent.Po + -rm -f client/$(DEPDIR)/display.Po + -rm -f client/$(DEPDIR)/gatt.Po + -rm -f client/$(DEPDIR)/main.Po + -rm -f ell/$(DEPDIR)/checksum.Plo + -rm -f ell/$(DEPDIR)/cipher.Plo + -rm -f ell/$(DEPDIR)/dbus-client.Plo + -rm -f ell/$(DEPDIR)/dbus-filter.Plo + -rm -f ell/$(DEPDIR)/dbus-message.Plo + -rm -f ell/$(DEPDIR)/dbus-name-cache.Plo + -rm -f ell/$(DEPDIR)/dbus-service.Plo + -rm -f ell/$(DEPDIR)/dbus-util.Plo + -rm -f ell/$(DEPDIR)/dbus.Plo + -rm -f ell/$(DEPDIR)/gvariant-util.Plo + -rm -f ell/$(DEPDIR)/hashmap.Plo + -rm -f ell/$(DEPDIR)/idle.Plo + -rm -f ell/$(DEPDIR)/io.Plo + -rm -f ell/$(DEPDIR)/log.Plo + -rm -f ell/$(DEPDIR)/main.Plo + -rm -f ell/$(DEPDIR)/queue.Plo + -rm -f ell/$(DEPDIR)/random.Plo + -rm -f ell/$(DEPDIR)/signal.Plo + -rm -f ell/$(DEPDIR)/siphash.Plo + -rm -f ell/$(DEPDIR)/string.Plo + -rm -f ell/$(DEPDIR)/strv.Plo + -rm -f ell/$(DEPDIR)/timeout.Plo + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-checksum.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-cipher.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-client.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-filter.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-message.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-name-cache.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-service.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-util.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-gvariant-util.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-hashmap.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-idle.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-io.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-log.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-main.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-queue.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-random.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-signal.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-siphash.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-string.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-strv.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-timeout.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-utf8.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-util.Po + -rm -f ell/$(DEPDIR)/utf8.Plo + -rm -f ell/$(DEPDIR)/util.Plo + -rm -f emulator/$(DEPDIR)/amp.Po + -rm -f emulator/$(DEPDIR)/android_android_tester-btdev.Po + -rm -f emulator/$(DEPDIR)/android_android_tester-bthost.Po + -rm -f emulator/$(DEPDIR)/android_android_tester-hciemu.Po + -rm -f emulator/$(DEPDIR)/android_android_tester-smp.Po + -rm -f emulator/$(DEPDIR)/android_ipc_tester-btdev.Po + -rm -f emulator/$(DEPDIR)/android_ipc_tester-bthost.Po + -rm -f emulator/$(DEPDIR)/android_ipc_tester-hciemu.Po + -rm -f emulator/$(DEPDIR)/android_ipc_tester-smp.Po + -rm -f emulator/$(DEPDIR)/b1ee.Po + -rm -f emulator/$(DEPDIR)/btdev.Po + -rm -f emulator/$(DEPDIR)/bthost.Po + -rm -f emulator/$(DEPDIR)/hciemu.Po + -rm -f emulator/$(DEPDIR)/hfp.Po + -rm -f emulator/$(DEPDIR)/le.Po + -rm -f emulator/$(DEPDIR)/main.Po + -rm -f emulator/$(DEPDIR)/phy.Po + -rm -f emulator/$(DEPDIR)/serial.Po + -rm -f emulator/$(DEPDIR)/server.Po + -rm -f emulator/$(DEPDIR)/smp.Po + -rm -f emulator/$(DEPDIR)/vhci.Po + -rm -f gdbus/$(DEPDIR)/client.Plo + -rm -f gdbus/$(DEPDIR)/mainloop.Plo + -rm -f gdbus/$(DEPDIR)/object.Plo + -rm -f gdbus/$(DEPDIR)/polkit.Plo + -rm -f gdbus/$(DEPDIR)/watch.Plo + -rm -f gobex/$(DEPDIR)/gobex-apparam.Po + -rm -f gobex/$(DEPDIR)/gobex-defs.Po + -rm -f gobex/$(DEPDIR)/gobex-header.Po + -rm -f gobex/$(DEPDIR)/gobex-packet.Po + -rm -f gobex/$(DEPDIR)/gobex-transfer.Po + -rm -f gobex/$(DEPDIR)/gobex.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex-apparam.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex-defs.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex-header.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex-packet.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex-transfer.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex.Po + -rm -f lib/$(DEPDIR)/bluetooth.Plo + -rm -f lib/$(DEPDIR)/hci.Plo + -rm -f lib/$(DEPDIR)/sdp.Plo + -rm -f lib/$(DEPDIR)/uuid.Plo + -rm -f mesh/$(DEPDIR)/agent.Po + -rm -f mesh/$(DEPDIR)/appkey.Po + -rm -f mesh/$(DEPDIR)/cfgmod-server.Po + -rm -f mesh/$(DEPDIR)/crypto.Po + -rm -f mesh/$(DEPDIR)/dbus.Po + -rm -f mesh/$(DEPDIR)/friend.Po + -rm -f mesh/$(DEPDIR)/keyring.Po + -rm -f mesh/$(DEPDIR)/main.Po + -rm -f mesh/$(DEPDIR)/manager.Po + -rm -f mesh/$(DEPDIR)/mesh-config-json.Po + -rm -f mesh/$(DEPDIR)/mesh-io-generic.Po + -rm -f mesh/$(DEPDIR)/mesh-io.Po + -rm -f mesh/$(DEPDIR)/mesh-mgmt.Po + -rm -f mesh/$(DEPDIR)/mesh.Po + -rm -f mesh/$(DEPDIR)/model.Po + -rm -f mesh/$(DEPDIR)/net-keys.Po + -rm -f mesh/$(DEPDIR)/net.Po + -rm -f mesh/$(DEPDIR)/node.Po + -rm -f mesh/$(DEPDIR)/pb-adv.Po + -rm -f mesh/$(DEPDIR)/prov-acceptor.Po + -rm -f mesh/$(DEPDIR)/prov-initiator.Po + -rm -f mesh/$(DEPDIR)/util.Po + -rm -f monitor/$(DEPDIR)/a2dp.Po + -rm -f monitor/$(DEPDIR)/analyze.Po + -rm -f monitor/$(DEPDIR)/avctp.Po + -rm -f monitor/$(DEPDIR)/avdtp.Po + -rm -f monitor/$(DEPDIR)/bnep.Po + -rm -f monitor/$(DEPDIR)/broadcom.Po + -rm -f monitor/$(DEPDIR)/control.Po + -rm -f monitor/$(DEPDIR)/crc.Po + -rm -f monitor/$(DEPDIR)/display.Po + -rm -f monitor/$(DEPDIR)/ellisys.Po + -rm -f monitor/$(DEPDIR)/hcidump.Po + -rm -f monitor/$(DEPDIR)/hwdb.Po + -rm -f monitor/$(DEPDIR)/intel.Po + -rm -f monitor/$(DEPDIR)/jlink.Po + -rm -f monitor/$(DEPDIR)/keys.Po + -rm -f monitor/$(DEPDIR)/l2cap.Po + -rm -f monitor/$(DEPDIR)/ll.Po + -rm -f monitor/$(DEPDIR)/lmp.Po + -rm -f monitor/$(DEPDIR)/main.Po + -rm -f monitor/$(DEPDIR)/packet.Po + -rm -f monitor/$(DEPDIR)/rfcomm.Po + -rm -f monitor/$(DEPDIR)/sdp.Po + -rm -f monitor/$(DEPDIR)/vendor.Po + -rm -f obexd/client/$(DEPDIR)/obexd-bluetooth.Po + -rm -f obexd/client/$(DEPDIR)/obexd-driver.Po + -rm -f obexd/client/$(DEPDIR)/obexd-ftp.Po + -rm -f obexd/client/$(DEPDIR)/obexd-manager.Po + -rm -f obexd/client/$(DEPDIR)/obexd-map-event.Po + -rm -f obexd/client/$(DEPDIR)/obexd-map.Po + -rm -f obexd/client/$(DEPDIR)/obexd-mns.Po + -rm -f obexd/client/$(DEPDIR)/obexd-opp.Po + -rm -f obexd/client/$(DEPDIR)/obexd-pbap.Po + -rm -f obexd/client/$(DEPDIR)/obexd-session.Po + -rm -f obexd/client/$(DEPDIR)/obexd-sync.Po + -rm -f obexd/client/$(DEPDIR)/obexd-transfer.Po + -rm -f obexd/client/$(DEPDIR)/obexd-transport.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-filesystem.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-ftp.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-irmc.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-mas.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-opp.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-pbap.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-phonebook-dummy.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-vcard.Po + -rm -f obexd/src/$(DEPDIR)/obexd-log.Po + -rm -f obexd/src/$(DEPDIR)/obexd-main.Po + -rm -f obexd/src/$(DEPDIR)/obexd-manager.Po + -rm -f obexd/src/$(DEPDIR)/obexd-mimetype.Po + -rm -f obexd/src/$(DEPDIR)/obexd-obex.Po + -rm -f obexd/src/$(DEPDIR)/obexd-plugin.Po + -rm -f obexd/src/$(DEPDIR)/obexd-server.Po + -rm -f obexd/src/$(DEPDIR)/obexd-service.Po + -rm -f obexd/src/$(DEPDIR)/obexd-transport.Po + -rm -f peripheral/$(DEPDIR)/attach.Po + -rm -f peripheral/$(DEPDIR)/efivars.Po + -rm -f peripheral/$(DEPDIR)/gap.Po + -rm -f peripheral/$(DEPDIR)/gatt.Po + -rm -f peripheral/$(DEPDIR)/log.Po + -rm -f peripheral/$(DEPDIR)/main.Po + -rm -f plugins/$(DEPDIR)/bluetoothd-autopair.Po + -rm -f plugins/$(DEPDIR)/bluetoothd-hostname.Po + -rm -f plugins/$(DEPDIR)/bluetoothd-neard.Po + -rm -f plugins/$(DEPDIR)/bluetoothd-policy.Po + -rm -f plugins/$(DEPDIR)/bluetoothd-wiimote.Po + -rm -f plugins/$(DEPDIR)/external_dummy_la-external-dummy.Plo + -rm -f plugins/$(DEPDIR)/sixaxis_la-sixaxis.Plo + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-control.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-media.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-player.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-sink.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-source.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-transport.Po + -rm -f profiles/battery/$(DEPDIR)/bas.Po + -rm -f profiles/battery/$(DEPDIR)/bluetoothd-bas.Po + -rm -f profiles/battery/$(DEPDIR)/bluetoothd-battery.Po + -rm -f profiles/cups/$(DEPDIR)/hcrp.Po + -rm -f profiles/cups/$(DEPDIR)/main.Po + -rm -f profiles/cups/$(DEPDIR)/sdp.Po + -rm -f profiles/cups/$(DEPDIR)/spp.Po + -rm -f profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po + -rm -f profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po + -rm -f profiles/deviceinfo/$(DEPDIR)/dis.Po + -rm -f profiles/gap/$(DEPDIR)/bluetoothd-gas.Po + -rm -f profiles/health/$(DEPDIR)/bluetoothd-hdp.Po + -rm -f profiles/health/$(DEPDIR)/bluetoothd-hdp_main.Po + -rm -f profiles/health/$(DEPDIR)/bluetoothd-hdp_manager.Po + -rm -f profiles/health/$(DEPDIR)/bluetoothd-hdp_util.Po + -rm -f profiles/health/$(DEPDIR)/bluetoothd-mcap.Po + -rm -f profiles/health/$(DEPDIR)/mcap.Po + -rm -f profiles/iap/$(DEPDIR)/main.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-device.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-hog.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-manager.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-server.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po + -rm -f profiles/input/$(DEPDIR)/hog-lib.Po + -rm -f profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po + -rm -f profiles/midi/$(DEPDIR)/bluetoothd-midi.Po + -rm -f profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po + -rm -f profiles/network/$(DEPDIR)/bluetoothd-bnep.Po + -rm -f profiles/network/$(DEPDIR)/bluetoothd-connection.Po + -rm -f profiles/network/$(DEPDIR)/bluetoothd-manager.Po + -rm -f profiles/network/$(DEPDIR)/bluetoothd-server.Po + -rm -f profiles/network/$(DEPDIR)/bnep.Po + -rm -f profiles/sap/$(DEPDIR)/bluetoothd-main.Po + -rm -f profiles/sap/$(DEPDIR)/bluetoothd-manager.Po + -rm -f profiles/sap/$(DEPDIR)/bluetoothd-sap-dummy.Po + -rm -f profiles/sap/$(DEPDIR)/bluetoothd-server.Po + -rm -f profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po + -rm -f profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po + -rm -f profiles/scanparam/$(DEPDIR)/scpp.Po + -rm -f src/$(DEPDIR)/android_avdtptest-log.Po + -rm -f src/$(DEPDIR)/bluetoothd-adapter.Po + -rm -f src/$(DEPDIR)/bluetoothd-advertising.Po + -rm -f src/$(DEPDIR)/bluetoothd-agent.Po + -rm -f src/$(DEPDIR)/bluetoothd-attrib-server.Po + -rm -f src/$(DEPDIR)/bluetoothd-backtrace.Po + -rm -f src/$(DEPDIR)/bluetoothd-dbus-common.Po + -rm -f src/$(DEPDIR)/bluetoothd-device.Po + -rm -f src/$(DEPDIR)/bluetoothd-eir.Po + -rm -f src/$(DEPDIR)/bluetoothd-error.Po + -rm -f src/$(DEPDIR)/bluetoothd-gatt-client.Po + -rm -f src/$(DEPDIR)/bluetoothd-gatt-database.Po + -rm -f src/$(DEPDIR)/bluetoothd-log.Po + -rm -f src/$(DEPDIR)/bluetoothd-main.Po + -rm -f src/$(DEPDIR)/bluetoothd-plugin.Po + -rm -f src/$(DEPDIR)/bluetoothd-profile.Po + -rm -f src/$(DEPDIR)/bluetoothd-rfkill.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdp-client.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdp-xml.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdpd-database.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdpd-request.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdpd-server.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdpd-service.Po + -rm -f src/$(DEPDIR)/bluetoothd-service.Po + -rm -f src/$(DEPDIR)/bluetoothd-storage.Po + -rm -f src/$(DEPDIR)/bluetoothd-textfile.Po + -rm -f src/$(DEPDIR)/bluetoothd-uuid-helper.Po + -rm -f src/$(DEPDIR)/eir.Po + -rm -f src/$(DEPDIR)/log.Po + -rm -f src/$(DEPDIR)/oui.Po + -rm -f src/$(DEPDIR)/sdp-client.Po + -rm -f src/$(DEPDIR)/sdp-xml.Po + -rm -f src/$(DEPDIR)/sdpd-database.Po + -rm -f src/$(DEPDIR)/sdpd-request.Po + -rm -f src/$(DEPDIR)/sdpd-server.Po + -rm -f src/$(DEPDIR)/sdpd-service.Po + -rm -f src/$(DEPDIR)/textfile.Po + -rm -f src/$(DEPDIR)/uuid-helper.Po + -rm -f src/shared/$(DEPDIR)/ad.Plo + -rm -f src/shared/$(DEPDIR)/android_avdtptest-log.Po + -rm -f src/shared/$(DEPDIR)/android_avdtptest-queue.Po + -rm -f src/shared/$(DEPDIR)/android_avdtptest-util.Po + -rm -f src/shared/$(DEPDIR)/att.Plo + -rm -f src/shared/$(DEPDIR)/btp.Po + -rm -f src/shared/$(DEPDIR)/btsnoop.Plo + -rm -f src/shared/$(DEPDIR)/crypto.Plo + -rm -f src/shared/$(DEPDIR)/ecc.Plo + -rm -f src/shared/$(DEPDIR)/gap.Plo + -rm -f src/shared/$(DEPDIR)/gatt-client.Plo + -rm -f src/shared/$(DEPDIR)/gatt-db.Plo + -rm -f src/shared/$(DEPDIR)/gatt-helpers.Plo + -rm -f src/shared/$(DEPDIR)/gatt-server.Plo + -rm -f src/shared/$(DEPDIR)/hci-crypto.Plo + -rm -f src/shared/$(DEPDIR)/hci.Plo + -rm -f src/shared/$(DEPDIR)/hfp.Plo + -rm -f src/shared/$(DEPDIR)/io-ell.Plo + -rm -f src/shared/$(DEPDIR)/io-glib.Plo + -rm -f src/shared/$(DEPDIR)/io-mainloop.Plo + -rm -f src/shared/$(DEPDIR)/log.Plo + -rm -f src/shared/$(DEPDIR)/mainloop-ell.Plo + -rm -f src/shared/$(DEPDIR)/mainloop-glib.Plo + -rm -f src/shared/$(DEPDIR)/mainloop-notify.Plo + -rm -f src/shared/$(DEPDIR)/mainloop.Plo + -rm -f src/shared/$(DEPDIR)/mgmt.Plo + -rm -f src/shared/$(DEPDIR)/pcap.Plo + -rm -f src/shared/$(DEPDIR)/queue.Plo + -rm -f src/shared/$(DEPDIR)/ringbuf.Plo + -rm -f src/shared/$(DEPDIR)/shell.Plo + -rm -f src/shared/$(DEPDIR)/tester.Plo + -rm -f src/shared/$(DEPDIR)/timeout-ell.Plo + -rm -f src/shared/$(DEPDIR)/timeout-glib.Plo + -rm -f src/shared/$(DEPDIR)/timeout-mainloop.Plo + -rm -f src/shared/$(DEPDIR)/uhid.Plo + -rm -f src/shared/$(DEPDIR)/util.Plo + -rm -f tools/$(DEPDIR)/3dsp.Po + -rm -f tools/$(DEPDIR)/advtest.Po + -rm -f tools/$(DEPDIR)/amptest.Po + -rm -f tools/$(DEPDIR)/avinfo.Po + -rm -f tools/$(DEPDIR)/avtest.Po + -rm -f tools/$(DEPDIR)/bccmd.Po + -rm -f tools/$(DEPDIR)/bcmfw.Po + -rm -f tools/$(DEPDIR)/bdaddr.Po + -rm -f tools/$(DEPDIR)/bluemoon.Po + -rm -f tools/$(DEPDIR)/bluetooth-player.Po + -rm -f tools/$(DEPDIR)/bnep-tester.Po + -rm -f tools/$(DEPDIR)/bneptest.Po + -rm -f tools/$(DEPDIR)/btattach.Po + -rm -f tools/$(DEPDIR)/btconfig.Po + -rm -f tools/$(DEPDIR)/btgatt-client.Po + -rm -f tools/$(DEPDIR)/btgatt-server.Po + -rm -f tools/$(DEPDIR)/btinfo.Po + -rm -f tools/$(DEPDIR)/btiotest.Po + -rm -f tools/$(DEPDIR)/btmgmt.Po + -rm -f tools/$(DEPDIR)/btmon-logger.Po + -rm -f tools/$(DEPDIR)/btpclient.Po + -rm -f tools/$(DEPDIR)/btproxy.Po + -rm -f tools/$(DEPDIR)/btsnoop.Po + -rm -f tools/$(DEPDIR)/check-selftest.Po + -rm -f tools/$(DEPDIR)/ciptool.Po + -rm -f tools/$(DEPDIR)/cltest.Po + -rm -f tools/$(DEPDIR)/create-image.Po + -rm -f tools/$(DEPDIR)/csr.Po + -rm -f tools/$(DEPDIR)/csr_3wire.Po + -rm -f tools/$(DEPDIR)/csr_bcsp.Po + -rm -f tools/$(DEPDIR)/csr_h4.Po + -rm -f tools/$(DEPDIR)/csr_hci.Po + -rm -f tools/$(DEPDIR)/csr_usb.Po + -rm -f tools/$(DEPDIR)/eddystone.Po + -rm -f tools/$(DEPDIR)/gap-tester.Po + -rm -f tools/$(DEPDIR)/gatt-service.Po + -rm -f tools/$(DEPDIR)/hci-tester.Po + -rm -f tools/$(DEPDIR)/hciattach.Po + -rm -f tools/$(DEPDIR)/hciattach_ath3k.Po + -rm -f tools/$(DEPDIR)/hciattach_bcm43xx.Po + -rm -f tools/$(DEPDIR)/hciattach_intel.Po + -rm -f tools/$(DEPDIR)/hciattach_qualcomm.Po + -rm -f tools/$(DEPDIR)/hciattach_st.Po + -rm -f tools/$(DEPDIR)/hciattach_ti.Po + -rm -f tools/$(DEPDIR)/hciattach_tialt.Po + -rm -f tools/$(DEPDIR)/hciconfig.Po + -rm -f tools/$(DEPDIR)/hcidump.Po + -rm -f tools/$(DEPDIR)/hcieventmask.Po + -rm -f tools/$(DEPDIR)/hcisecfilter.Po + -rm -f tools/$(DEPDIR)/hcitool.Po + -rm -f tools/$(DEPDIR)/hex2hcd.Po + -rm -f tools/$(DEPDIR)/hid2hci.Po + -rm -f tools/$(DEPDIR)/hwdb.Po + -rm -f tools/$(DEPDIR)/ibeacon.Po + -rm -f tools/$(DEPDIR)/l2cap-tester.Po + -rm -f tools/$(DEPDIR)/l2ping.Po + -rm -f tools/$(DEPDIR)/l2test.Po + -rm -f tools/$(DEPDIR)/mcaptest.Po + -rm -f tools/$(DEPDIR)/meshctl.Po + -rm -f tools/$(DEPDIR)/mgmt-tester.Po + -rm -f tools/$(DEPDIR)/mpris-proxy.Po + -rm -f tools/$(DEPDIR)/nokfw.Po + -rm -f tools/$(DEPDIR)/obex-client-tool.Po + -rm -f tools/$(DEPDIR)/obex-server-tool.Po + -rm -f tools/$(DEPDIR)/obexctl.Po + -rm -f tools/$(DEPDIR)/oobtest.Po + -rm -f tools/$(DEPDIR)/rctest.Po + -rm -f tools/$(DEPDIR)/rfcomm-tester.Po + -rm -f tools/$(DEPDIR)/rfcomm.Po + -rm -f tools/$(DEPDIR)/rtlfw.Po + -rm -f tools/$(DEPDIR)/sco-tester.Po + -rm -f tools/$(DEPDIR)/scotest.Po + -rm -f tools/$(DEPDIR)/sdptool.Po + -rm -f tools/$(DEPDIR)/seq2bseq.Po + -rm -f tools/$(DEPDIR)/smp-tester.Po + -rm -f tools/$(DEPDIR)/test-runner.Po + -rm -f tools/$(DEPDIR)/ubcsp.Po + -rm -f tools/$(DEPDIR)/userchan-tester.Po + -rm -f tools/mesh/$(DEPDIR)/agent.Po + -rm -f tools/mesh/$(DEPDIR)/config-client.Po + -rm -f tools/mesh/$(DEPDIR)/config-server.Po + -rm -f tools/mesh/$(DEPDIR)/crypto.Po + -rm -f tools/mesh/$(DEPDIR)/gatt.Po + -rm -f tools/mesh/$(DEPDIR)/net.Po + -rm -f tools/mesh/$(DEPDIR)/node.Po + -rm -f tools/mesh/$(DEPDIR)/onoff-model.Po + -rm -f tools/mesh/$(DEPDIR)/prov-db.Po + -rm -f tools/mesh/$(DEPDIR)/prov.Po + -rm -f tools/mesh/$(DEPDIR)/util.Po + -rm -f tools/parser/$(DEPDIR)/amp.Po + -rm -f tools/parser/$(DEPDIR)/att.Po + -rm -f tools/parser/$(DEPDIR)/avctp.Po + -rm -f tools/parser/$(DEPDIR)/avdtp.Po + -rm -f tools/parser/$(DEPDIR)/avrcp.Po + -rm -f tools/parser/$(DEPDIR)/bnep.Po + -rm -f tools/parser/$(DEPDIR)/bpa.Po + -rm -f tools/parser/$(DEPDIR)/capi.Po + -rm -f tools/parser/$(DEPDIR)/cmtp.Po + -rm -f tools/parser/$(DEPDIR)/csr.Po + -rm -f tools/parser/$(DEPDIR)/ericsson.Po + -rm -f tools/parser/$(DEPDIR)/hci.Po + -rm -f tools/parser/$(DEPDIR)/hcrp.Po + -rm -f tools/parser/$(DEPDIR)/hidp.Po + -rm -f tools/parser/$(DEPDIR)/l2cap.Po + -rm -f tools/parser/$(DEPDIR)/lmp.Po + -rm -f tools/parser/$(DEPDIR)/obex.Po + -rm -f tools/parser/$(DEPDIR)/parser.Po + -rm -f tools/parser/$(DEPDIR)/ppp.Po + -rm -f tools/parser/$(DEPDIR)/rfcomm.Po + -rm -f tools/parser/$(DEPDIR)/sap.Po + -rm -f tools/parser/$(DEPDIR)/sdp.Po + -rm -f tools/parser/$(DEPDIR)/smp.Po + -rm -f tools/parser/$(DEPDIR)/tcpip.Po + -rm -f unit/$(DEPDIR)/test-avctp.Po + -rm -f unit/$(DEPDIR)/test-avdtp.Po + -rm -f unit/$(DEPDIR)/test-avrcp.Po + -rm -f unit/$(DEPDIR)/test-crc.Po + -rm -f unit/$(DEPDIR)/test-crypto.Po + -rm -f unit/$(DEPDIR)/test-ecc.Po + -rm -f unit/$(DEPDIR)/test-eir.Po + -rm -f unit/$(DEPDIR)/test-gatt.Po + -rm -f unit/$(DEPDIR)/test-gattrib.Po + -rm -f unit/$(DEPDIR)/test-gdbus-client.Po + -rm -f unit/$(DEPDIR)/test-gobex-apparam.Po + -rm -f unit/$(DEPDIR)/test-gobex-header.Po + -rm -f unit/$(DEPDIR)/test-gobex-packet.Po + -rm -f unit/$(DEPDIR)/test-gobex-transfer.Po + -rm -f unit/$(DEPDIR)/test-gobex.Po + -rm -f unit/$(DEPDIR)/test-hfp.Po + -rm -f unit/$(DEPDIR)/test-hog.Po + -rm -f unit/$(DEPDIR)/test-lib.Po + -rm -f unit/$(DEPDIR)/test-mgmt.Po + -rm -f unit/$(DEPDIR)/test-queue.Po + -rm -f unit/$(DEPDIR)/test-ringbuf.Po + -rm -f unit/$(DEPDIR)/test-sdp.Po + -rm -f unit/$(DEPDIR)/test-textfile.Po + -rm -f unit/$(DEPDIR)/test-uhid.Po + -rm -f unit/$(DEPDIR)/test-uuid.Po + -rm -f unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po + -rm -f unit/$(DEPDIR)/test_midi-test-midi.Po + -rm -f unit/$(DEPDIR)/util.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-hdr distclean-libtool distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-confDATA install-cupsPROGRAMS \ + install-dbusDATA install-dbussessionbusDATA \ + install-dbussystembusDATA install-dist_zshcompletionDATA \ + install-man install-pkgconfigDATA install-pkgincludeHEADERS \ + install-pluginLTLIBRARIES install-rulesDATA install-stateDATA \ + install-systemdsystemunitDATA install-systemduserunitDATA \ + install-testSCRIPTS install-udevPROGRAMS + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS install-libLTLIBRARIES \ + install-pkglibexecPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: install-man1 install-man8 + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f $(am__CONFIG_DISTCLEAN_FILES) + -rm -rf $(top_srcdir)/autom4te.cache + -rm -f android/$(DEPDIR)/a2dp-sink.Po + -rm -f android/$(DEPDIR)/a2dp.Po + -rm -f android/$(DEPDIR)/android_tester-tester-a2dp.Po + -rm -f android/$(DEPDIR)/android_tester-tester-avrcp.Po + -rm -f android/$(DEPDIR)/android_tester-tester-bluetooth.Po + -rm -f android/$(DEPDIR)/android_tester-tester-gatt.Po + -rm -f android/$(DEPDIR)/android_tester-tester-hdp.Po + -rm -f android/$(DEPDIR)/android_tester-tester-hidhost.Po + -rm -f android/$(DEPDIR)/android_tester-tester-main.Po + -rm -f android/$(DEPDIR)/android_tester-tester-map-client.Po + -rm -f android/$(DEPDIR)/android_tester-tester-pan.Po + -rm -f android/$(DEPDIR)/android_tester-tester-socket.Po + -rm -f android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-aptx.Plo + -rm -f android/$(DEPDIR)/audio_a2dp_default_la-hal-audio-sbc.Plo + -rm -f android/$(DEPDIR)/audio_a2dp_default_la-hal-audio.Plo + -rm -f android/$(DEPDIR)/audio_sco_default_la-hal-sco.Plo + -rm -f android/$(DEPDIR)/avctp.Po + -rm -f android/$(DEPDIR)/avdtp.Po + -rm -f android/$(DEPDIR)/avdtptest-avdtp.Po + -rm -f android/$(DEPDIR)/avdtptest-avdtptest.Po + -rm -f android/$(DEPDIR)/avrcp-lib.Po + -rm -f android/$(DEPDIR)/avrcp.Po + -rm -f android/$(DEPDIR)/bluetooth.Po + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-a2dp-sink.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-a2dp.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-avrcp-ctrl.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-avrcp.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-bluetooth.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-gatt.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-handsfree-client.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-handsfree.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-health.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-hidhost.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-ipc.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-map-client.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-pan.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-socket.Plo + -rm -f android/$(DEPDIR)/bluetooth_default_la-hal-utils.Plo + -rm -f android/$(DEPDIR)/bluetoothd-snoop.Po + -rm -f android/$(DEPDIR)/gatt.Po + -rm -f android/$(DEPDIR)/haltest-hal-utils.Po + -rm -f android/$(DEPDIR)/handsfree-client.Po + -rm -f android/$(DEPDIR)/handsfree.Po + -rm -f android/$(DEPDIR)/health.Po + -rm -f android/$(DEPDIR)/hidhost.Po + -rm -f android/$(DEPDIR)/ipc.Po + -rm -f android/$(DEPDIR)/ipc_tester-hal-utils.Po + -rm -f android/$(DEPDIR)/ipc_tester-ipc-tester.Po + -rm -f android/$(DEPDIR)/main.Po + -rm -f android/$(DEPDIR)/map-client.Po + -rm -f android/$(DEPDIR)/pan.Po + -rm -f android/$(DEPDIR)/sco.Po + -rm -f android/$(DEPDIR)/socket.Po + -rm -f android/$(DEPDIR)/system-emulator.Po + -rm -f android/$(DEPDIR)/test-ipc.Po + -rm -f android/audio_utils/$(DEPDIR)/audio_sco_default_la-resampler.Plo + -rm -f android/client/$(DEPDIR)/haltest-haltest.Po + -rm -f android/client/$(DEPDIR)/haltest-history.Po + -rm -f android/client/$(DEPDIR)/haltest-if-audio.Po + -rm -f android/client/$(DEPDIR)/haltest-if-av-sink.Po + -rm -f android/client/$(DEPDIR)/haltest-if-av.Po + -rm -f android/client/$(DEPDIR)/haltest-if-bt.Po + -rm -f android/client/$(DEPDIR)/haltest-if-gatt.Po + -rm -f android/client/$(DEPDIR)/haltest-if-hf-client.Po + -rm -f android/client/$(DEPDIR)/haltest-if-hf.Po + -rm -f android/client/$(DEPDIR)/haltest-if-hh.Po + -rm -f android/client/$(DEPDIR)/haltest-if-hl.Po + -rm -f android/client/$(DEPDIR)/haltest-if-mce.Po + -rm -f android/client/$(DEPDIR)/haltest-if-pan.Po + -rm -f android/client/$(DEPDIR)/haltest-if-rc-ctrl.Po + -rm -f android/client/$(DEPDIR)/haltest-if-rc.Po + -rm -f android/client/$(DEPDIR)/haltest-if-sco.Po + -rm -f android/client/$(DEPDIR)/haltest-if-sock.Po + -rm -f android/client/$(DEPDIR)/haltest-pollhandler.Po + -rm -f android/client/$(DEPDIR)/haltest-tabcompletion.Po + -rm -f android/client/$(DEPDIR)/haltest-terminal.Po + -rm -f android/hardware/$(DEPDIR)/android_tester-hardware.Po + -rm -f android/hardware/$(DEPDIR)/haltest-hardware.Po + -rm -f attrib/$(DEPDIR)/att.Po + -rm -f attrib/$(DEPDIR)/bluetoothd-att.Po + -rm -f attrib/$(DEPDIR)/bluetoothd-gatt-service.Po + -rm -f attrib/$(DEPDIR)/bluetoothd-gatt.Po + -rm -f attrib/$(DEPDIR)/bluetoothd-gattrib.Po + -rm -f attrib/$(DEPDIR)/gatt.Po + -rm -f attrib/$(DEPDIR)/gattrib.Po + -rm -f attrib/$(DEPDIR)/gatttool.Po + -rm -f attrib/$(DEPDIR)/interactive.Po + -rm -f attrib/$(DEPDIR)/utils.Po + -rm -f btio/$(DEPDIR)/android_avdtptest-btio.Po + -rm -f btio/$(DEPDIR)/bluetoothd-btio.Po + -rm -f btio/$(DEPDIR)/btio.Po + -rm -f btio/$(DEPDIR)/obexd-btio.Po + -rm -f client/$(DEPDIR)/advertising.Po + -rm -f client/$(DEPDIR)/agent.Po + -rm -f client/$(DEPDIR)/display.Po + -rm -f client/$(DEPDIR)/gatt.Po + -rm -f client/$(DEPDIR)/main.Po + -rm -f ell/$(DEPDIR)/checksum.Plo + -rm -f ell/$(DEPDIR)/cipher.Plo + -rm -f ell/$(DEPDIR)/dbus-client.Plo + -rm -f ell/$(DEPDIR)/dbus-filter.Plo + -rm -f ell/$(DEPDIR)/dbus-message.Plo + -rm -f ell/$(DEPDIR)/dbus-name-cache.Plo + -rm -f ell/$(DEPDIR)/dbus-service.Plo + -rm -f ell/$(DEPDIR)/dbus-util.Plo + -rm -f ell/$(DEPDIR)/dbus.Plo + -rm -f ell/$(DEPDIR)/gvariant-util.Plo + -rm -f ell/$(DEPDIR)/hashmap.Plo + -rm -f ell/$(DEPDIR)/idle.Plo + -rm -f ell/$(DEPDIR)/io.Plo + -rm -f ell/$(DEPDIR)/log.Plo + -rm -f ell/$(DEPDIR)/main.Plo + -rm -f ell/$(DEPDIR)/queue.Plo + -rm -f ell/$(DEPDIR)/random.Plo + -rm -f ell/$(DEPDIR)/signal.Plo + -rm -f ell/$(DEPDIR)/siphash.Plo + -rm -f ell/$(DEPDIR)/string.Plo + -rm -f ell/$(DEPDIR)/strv.Plo + -rm -f ell/$(DEPDIR)/timeout.Plo + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-checksum.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-cipher.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-client.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-filter.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-message.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-name-cache.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-service.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus-util.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-dbus.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-gvariant-util.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-hashmap.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-idle.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-io.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-log.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-main.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-queue.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-random.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-signal.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-siphash.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-string.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-strv.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-timeout.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-utf8.Po + -rm -f ell/$(DEPDIR)/unit_test_mesh_crypto-util.Po + -rm -f ell/$(DEPDIR)/utf8.Plo + -rm -f ell/$(DEPDIR)/util.Plo + -rm -f emulator/$(DEPDIR)/amp.Po + -rm -f emulator/$(DEPDIR)/android_android_tester-btdev.Po + -rm -f emulator/$(DEPDIR)/android_android_tester-bthost.Po + -rm -f emulator/$(DEPDIR)/android_android_tester-hciemu.Po + -rm -f emulator/$(DEPDIR)/android_android_tester-smp.Po + -rm -f emulator/$(DEPDIR)/android_ipc_tester-btdev.Po + -rm -f emulator/$(DEPDIR)/android_ipc_tester-bthost.Po + -rm -f emulator/$(DEPDIR)/android_ipc_tester-hciemu.Po + -rm -f emulator/$(DEPDIR)/android_ipc_tester-smp.Po + -rm -f emulator/$(DEPDIR)/b1ee.Po + -rm -f emulator/$(DEPDIR)/btdev.Po + -rm -f emulator/$(DEPDIR)/bthost.Po + -rm -f emulator/$(DEPDIR)/hciemu.Po + -rm -f emulator/$(DEPDIR)/hfp.Po + -rm -f emulator/$(DEPDIR)/le.Po + -rm -f emulator/$(DEPDIR)/main.Po + -rm -f emulator/$(DEPDIR)/phy.Po + -rm -f emulator/$(DEPDIR)/serial.Po + -rm -f emulator/$(DEPDIR)/server.Po + -rm -f emulator/$(DEPDIR)/smp.Po + -rm -f emulator/$(DEPDIR)/vhci.Po + -rm -f gdbus/$(DEPDIR)/client.Plo + -rm -f gdbus/$(DEPDIR)/mainloop.Plo + -rm -f gdbus/$(DEPDIR)/object.Plo + -rm -f gdbus/$(DEPDIR)/polkit.Plo + -rm -f gdbus/$(DEPDIR)/watch.Plo + -rm -f gobex/$(DEPDIR)/gobex-apparam.Po + -rm -f gobex/$(DEPDIR)/gobex-defs.Po + -rm -f gobex/$(DEPDIR)/gobex-header.Po + -rm -f gobex/$(DEPDIR)/gobex-packet.Po + -rm -f gobex/$(DEPDIR)/gobex-transfer.Po + -rm -f gobex/$(DEPDIR)/gobex.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex-apparam.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex-defs.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex-header.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex-packet.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex-transfer.Po + -rm -f gobex/$(DEPDIR)/obexd-gobex.Po + -rm -f lib/$(DEPDIR)/bluetooth.Plo + -rm -f lib/$(DEPDIR)/hci.Plo + -rm -f lib/$(DEPDIR)/sdp.Plo + -rm -f lib/$(DEPDIR)/uuid.Plo + -rm -f mesh/$(DEPDIR)/agent.Po + -rm -f mesh/$(DEPDIR)/appkey.Po + -rm -f mesh/$(DEPDIR)/cfgmod-server.Po + -rm -f mesh/$(DEPDIR)/crypto.Po + -rm -f mesh/$(DEPDIR)/dbus.Po + -rm -f mesh/$(DEPDIR)/friend.Po + -rm -f mesh/$(DEPDIR)/keyring.Po + -rm -f mesh/$(DEPDIR)/main.Po + -rm -f mesh/$(DEPDIR)/manager.Po + -rm -f mesh/$(DEPDIR)/mesh-config-json.Po + -rm -f mesh/$(DEPDIR)/mesh-io-generic.Po + -rm -f mesh/$(DEPDIR)/mesh-io.Po + -rm -f mesh/$(DEPDIR)/mesh-mgmt.Po + -rm -f mesh/$(DEPDIR)/mesh.Po + -rm -f mesh/$(DEPDIR)/model.Po + -rm -f mesh/$(DEPDIR)/net-keys.Po + -rm -f mesh/$(DEPDIR)/net.Po + -rm -f mesh/$(DEPDIR)/node.Po + -rm -f mesh/$(DEPDIR)/pb-adv.Po + -rm -f mesh/$(DEPDIR)/prov-acceptor.Po + -rm -f mesh/$(DEPDIR)/prov-initiator.Po + -rm -f mesh/$(DEPDIR)/util.Po + -rm -f monitor/$(DEPDIR)/a2dp.Po + -rm -f monitor/$(DEPDIR)/analyze.Po + -rm -f monitor/$(DEPDIR)/avctp.Po + -rm -f monitor/$(DEPDIR)/avdtp.Po + -rm -f monitor/$(DEPDIR)/bnep.Po + -rm -f monitor/$(DEPDIR)/broadcom.Po + -rm -f monitor/$(DEPDIR)/control.Po + -rm -f monitor/$(DEPDIR)/crc.Po + -rm -f monitor/$(DEPDIR)/display.Po + -rm -f monitor/$(DEPDIR)/ellisys.Po + -rm -f monitor/$(DEPDIR)/hcidump.Po + -rm -f monitor/$(DEPDIR)/hwdb.Po + -rm -f monitor/$(DEPDIR)/intel.Po + -rm -f monitor/$(DEPDIR)/jlink.Po + -rm -f monitor/$(DEPDIR)/keys.Po + -rm -f monitor/$(DEPDIR)/l2cap.Po + -rm -f monitor/$(DEPDIR)/ll.Po + -rm -f monitor/$(DEPDIR)/lmp.Po + -rm -f monitor/$(DEPDIR)/main.Po + -rm -f monitor/$(DEPDIR)/packet.Po + -rm -f monitor/$(DEPDIR)/rfcomm.Po + -rm -f monitor/$(DEPDIR)/sdp.Po + -rm -f monitor/$(DEPDIR)/vendor.Po + -rm -f obexd/client/$(DEPDIR)/obexd-bluetooth.Po + -rm -f obexd/client/$(DEPDIR)/obexd-driver.Po + -rm -f obexd/client/$(DEPDIR)/obexd-ftp.Po + -rm -f obexd/client/$(DEPDIR)/obexd-manager.Po + -rm -f obexd/client/$(DEPDIR)/obexd-map-event.Po + -rm -f obexd/client/$(DEPDIR)/obexd-map.Po + -rm -f obexd/client/$(DEPDIR)/obexd-mns.Po + -rm -f obexd/client/$(DEPDIR)/obexd-opp.Po + -rm -f obexd/client/$(DEPDIR)/obexd-pbap.Po + -rm -f obexd/client/$(DEPDIR)/obexd-session.Po + -rm -f obexd/client/$(DEPDIR)/obexd-sync.Po + -rm -f obexd/client/$(DEPDIR)/obexd-transfer.Po + -rm -f obexd/client/$(DEPDIR)/obexd-transport.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-bluetooth.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-filesystem.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-ftp.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-irmc.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-mas.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-messages-dummy.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-opp.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-pbap.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-pcsuite.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-phonebook-dummy.Po + -rm -f obexd/plugins/$(DEPDIR)/obexd-vcard.Po + -rm -f obexd/src/$(DEPDIR)/obexd-log.Po + -rm -f obexd/src/$(DEPDIR)/obexd-main.Po + -rm -f obexd/src/$(DEPDIR)/obexd-manager.Po + -rm -f obexd/src/$(DEPDIR)/obexd-mimetype.Po + -rm -f obexd/src/$(DEPDIR)/obexd-obex.Po + -rm -f obexd/src/$(DEPDIR)/obexd-plugin.Po + -rm -f obexd/src/$(DEPDIR)/obexd-server.Po + -rm -f obexd/src/$(DEPDIR)/obexd-service.Po + -rm -f obexd/src/$(DEPDIR)/obexd-transport.Po + -rm -f peripheral/$(DEPDIR)/attach.Po + -rm -f peripheral/$(DEPDIR)/efivars.Po + -rm -f peripheral/$(DEPDIR)/gap.Po + -rm -f peripheral/$(DEPDIR)/gatt.Po + -rm -f peripheral/$(DEPDIR)/log.Po + -rm -f peripheral/$(DEPDIR)/main.Po + -rm -f plugins/$(DEPDIR)/bluetoothd-autopair.Po + -rm -f plugins/$(DEPDIR)/bluetoothd-hostname.Po + -rm -f plugins/$(DEPDIR)/bluetoothd-neard.Po + -rm -f plugins/$(DEPDIR)/bluetoothd-policy.Po + -rm -f plugins/$(DEPDIR)/bluetoothd-wiimote.Po + -rm -f plugins/$(DEPDIR)/external_dummy_la-external-dummy.Plo + -rm -f plugins/$(DEPDIR)/sixaxis_la-sixaxis.Plo + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-a2dp.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avctp.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avdtp.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-avrcp.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-control.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-media.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-player.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-sink.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-source.Po + -rm -f profiles/audio/$(DEPDIR)/bluetoothd-transport.Po + -rm -f profiles/battery/$(DEPDIR)/bas.Po + -rm -f profiles/battery/$(DEPDIR)/bluetoothd-bas.Po + -rm -f profiles/battery/$(DEPDIR)/bluetoothd-battery.Po + -rm -f profiles/cups/$(DEPDIR)/hcrp.Po + -rm -f profiles/cups/$(DEPDIR)/main.Po + -rm -f profiles/cups/$(DEPDIR)/sdp.Po + -rm -f profiles/cups/$(DEPDIR)/spp.Po + -rm -f profiles/deviceinfo/$(DEPDIR)/bluetoothd-deviceinfo.Po + -rm -f profiles/deviceinfo/$(DEPDIR)/bluetoothd-dis.Po + -rm -f profiles/deviceinfo/$(DEPDIR)/dis.Po + -rm -f profiles/gap/$(DEPDIR)/bluetoothd-gas.Po + -rm -f profiles/health/$(DEPDIR)/bluetoothd-hdp.Po + -rm -f profiles/health/$(DEPDIR)/bluetoothd-hdp_main.Po + -rm -f profiles/health/$(DEPDIR)/bluetoothd-hdp_manager.Po + -rm -f profiles/health/$(DEPDIR)/bluetoothd-hdp_util.Po + -rm -f profiles/health/$(DEPDIR)/bluetoothd-mcap.Po + -rm -f profiles/health/$(DEPDIR)/mcap.Po + -rm -f profiles/iap/$(DEPDIR)/main.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-device.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-hog-lib.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-hog.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-manager.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-server.Po + -rm -f profiles/input/$(DEPDIR)/bluetoothd-suspend-none.Po + -rm -f profiles/input/$(DEPDIR)/hog-lib.Po + -rm -f profiles/midi/$(DEPDIR)/bluetoothd-libmidi.Po + -rm -f profiles/midi/$(DEPDIR)/bluetoothd-midi.Po + -rm -f profiles/midi/$(DEPDIR)/unit_test_midi-libmidi.Po + -rm -f profiles/network/$(DEPDIR)/bluetoothd-bnep.Po + -rm -f profiles/network/$(DEPDIR)/bluetoothd-connection.Po + -rm -f profiles/network/$(DEPDIR)/bluetoothd-manager.Po + -rm -f profiles/network/$(DEPDIR)/bluetoothd-server.Po + -rm -f profiles/network/$(DEPDIR)/bnep.Po + -rm -f profiles/sap/$(DEPDIR)/bluetoothd-main.Po + -rm -f profiles/sap/$(DEPDIR)/bluetoothd-manager.Po + -rm -f profiles/sap/$(DEPDIR)/bluetoothd-sap-dummy.Po + -rm -f profiles/sap/$(DEPDIR)/bluetoothd-server.Po + -rm -f profiles/scanparam/$(DEPDIR)/bluetoothd-scan.Po + -rm -f profiles/scanparam/$(DEPDIR)/bluetoothd-scpp.Po + -rm -f profiles/scanparam/$(DEPDIR)/scpp.Po + -rm -f src/$(DEPDIR)/android_avdtptest-log.Po + -rm -f src/$(DEPDIR)/bluetoothd-adapter.Po + -rm -f src/$(DEPDIR)/bluetoothd-advertising.Po + -rm -f src/$(DEPDIR)/bluetoothd-agent.Po + -rm -f src/$(DEPDIR)/bluetoothd-attrib-server.Po + -rm -f src/$(DEPDIR)/bluetoothd-backtrace.Po + -rm -f src/$(DEPDIR)/bluetoothd-dbus-common.Po + -rm -f src/$(DEPDIR)/bluetoothd-device.Po + -rm -f src/$(DEPDIR)/bluetoothd-eir.Po + -rm -f src/$(DEPDIR)/bluetoothd-error.Po + -rm -f src/$(DEPDIR)/bluetoothd-gatt-client.Po + -rm -f src/$(DEPDIR)/bluetoothd-gatt-database.Po + -rm -f src/$(DEPDIR)/bluetoothd-log.Po + -rm -f src/$(DEPDIR)/bluetoothd-main.Po + -rm -f src/$(DEPDIR)/bluetoothd-plugin.Po + -rm -f src/$(DEPDIR)/bluetoothd-profile.Po + -rm -f src/$(DEPDIR)/bluetoothd-rfkill.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdp-client.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdp-xml.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdpd-database.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdpd-request.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdpd-server.Po + -rm -f src/$(DEPDIR)/bluetoothd-sdpd-service.Po + -rm -f src/$(DEPDIR)/bluetoothd-service.Po + -rm -f src/$(DEPDIR)/bluetoothd-storage.Po + -rm -f src/$(DEPDIR)/bluetoothd-textfile.Po + -rm -f src/$(DEPDIR)/bluetoothd-uuid-helper.Po + -rm -f src/$(DEPDIR)/eir.Po + -rm -f src/$(DEPDIR)/log.Po + -rm -f src/$(DEPDIR)/oui.Po + -rm -f src/$(DEPDIR)/sdp-client.Po + -rm -f src/$(DEPDIR)/sdp-xml.Po + -rm -f src/$(DEPDIR)/sdpd-database.Po + -rm -f src/$(DEPDIR)/sdpd-request.Po + -rm -f src/$(DEPDIR)/sdpd-server.Po + -rm -f src/$(DEPDIR)/sdpd-service.Po + -rm -f src/$(DEPDIR)/textfile.Po + -rm -f src/$(DEPDIR)/uuid-helper.Po + -rm -f src/shared/$(DEPDIR)/ad.Plo + -rm -f src/shared/$(DEPDIR)/android_avdtptest-log.Po + -rm -f src/shared/$(DEPDIR)/android_avdtptest-queue.Po + -rm -f src/shared/$(DEPDIR)/android_avdtptest-util.Po + -rm -f src/shared/$(DEPDIR)/att.Plo + -rm -f src/shared/$(DEPDIR)/btp.Po + -rm -f src/shared/$(DEPDIR)/btsnoop.Plo + -rm -f src/shared/$(DEPDIR)/crypto.Plo + -rm -f src/shared/$(DEPDIR)/ecc.Plo + -rm -f src/shared/$(DEPDIR)/gap.Plo + -rm -f src/shared/$(DEPDIR)/gatt-client.Plo + -rm -f src/shared/$(DEPDIR)/gatt-db.Plo + -rm -f src/shared/$(DEPDIR)/gatt-helpers.Plo + -rm -f src/shared/$(DEPDIR)/gatt-server.Plo + -rm -f src/shared/$(DEPDIR)/hci-crypto.Plo + -rm -f src/shared/$(DEPDIR)/hci.Plo + -rm -f src/shared/$(DEPDIR)/hfp.Plo + -rm -f src/shared/$(DEPDIR)/io-ell.Plo + -rm -f src/shared/$(DEPDIR)/io-glib.Plo + -rm -f src/shared/$(DEPDIR)/io-mainloop.Plo + -rm -f src/shared/$(DEPDIR)/log.Plo + -rm -f src/shared/$(DEPDIR)/mainloop-ell.Plo + -rm -f src/shared/$(DEPDIR)/mainloop-glib.Plo + -rm -f src/shared/$(DEPDIR)/mainloop-notify.Plo + -rm -f src/shared/$(DEPDIR)/mainloop.Plo + -rm -f src/shared/$(DEPDIR)/mgmt.Plo + -rm -f src/shared/$(DEPDIR)/pcap.Plo + -rm -f src/shared/$(DEPDIR)/queue.Plo + -rm -f src/shared/$(DEPDIR)/ringbuf.Plo + -rm -f src/shared/$(DEPDIR)/shell.Plo + -rm -f src/shared/$(DEPDIR)/tester.Plo + -rm -f src/shared/$(DEPDIR)/timeout-ell.Plo + -rm -f src/shared/$(DEPDIR)/timeout-glib.Plo + -rm -f src/shared/$(DEPDIR)/timeout-mainloop.Plo + -rm -f src/shared/$(DEPDIR)/uhid.Plo + -rm -f src/shared/$(DEPDIR)/util.Plo + -rm -f tools/$(DEPDIR)/3dsp.Po + -rm -f tools/$(DEPDIR)/advtest.Po + -rm -f tools/$(DEPDIR)/amptest.Po + -rm -f tools/$(DEPDIR)/avinfo.Po + -rm -f tools/$(DEPDIR)/avtest.Po + -rm -f tools/$(DEPDIR)/bccmd.Po + -rm -f tools/$(DEPDIR)/bcmfw.Po + -rm -f tools/$(DEPDIR)/bdaddr.Po + -rm -f tools/$(DEPDIR)/bluemoon.Po + -rm -f tools/$(DEPDIR)/bluetooth-player.Po + -rm -f tools/$(DEPDIR)/bnep-tester.Po + -rm -f tools/$(DEPDIR)/bneptest.Po + -rm -f tools/$(DEPDIR)/btattach.Po + -rm -f tools/$(DEPDIR)/btconfig.Po + -rm -f tools/$(DEPDIR)/btgatt-client.Po + -rm -f tools/$(DEPDIR)/btgatt-server.Po + -rm -f tools/$(DEPDIR)/btinfo.Po + -rm -f tools/$(DEPDIR)/btiotest.Po + -rm -f tools/$(DEPDIR)/btmgmt.Po + -rm -f tools/$(DEPDIR)/btmon-logger.Po + -rm -f tools/$(DEPDIR)/btpclient.Po + -rm -f tools/$(DEPDIR)/btproxy.Po + -rm -f tools/$(DEPDIR)/btsnoop.Po + -rm -f tools/$(DEPDIR)/check-selftest.Po + -rm -f tools/$(DEPDIR)/ciptool.Po + -rm -f tools/$(DEPDIR)/cltest.Po + -rm -f tools/$(DEPDIR)/create-image.Po + -rm -f tools/$(DEPDIR)/csr.Po + -rm -f tools/$(DEPDIR)/csr_3wire.Po + -rm -f tools/$(DEPDIR)/csr_bcsp.Po + -rm -f tools/$(DEPDIR)/csr_h4.Po + -rm -f tools/$(DEPDIR)/csr_hci.Po + -rm -f tools/$(DEPDIR)/csr_usb.Po + -rm -f tools/$(DEPDIR)/eddystone.Po + -rm -f tools/$(DEPDIR)/gap-tester.Po + -rm -f tools/$(DEPDIR)/gatt-service.Po + -rm -f tools/$(DEPDIR)/hci-tester.Po + -rm -f tools/$(DEPDIR)/hciattach.Po + -rm -f tools/$(DEPDIR)/hciattach_ath3k.Po + -rm -f tools/$(DEPDIR)/hciattach_bcm43xx.Po + -rm -f tools/$(DEPDIR)/hciattach_intel.Po + -rm -f tools/$(DEPDIR)/hciattach_qualcomm.Po + -rm -f tools/$(DEPDIR)/hciattach_st.Po + -rm -f tools/$(DEPDIR)/hciattach_ti.Po + -rm -f tools/$(DEPDIR)/hciattach_tialt.Po + -rm -f tools/$(DEPDIR)/hciconfig.Po + -rm -f tools/$(DEPDIR)/hcidump.Po + -rm -f tools/$(DEPDIR)/hcieventmask.Po + -rm -f tools/$(DEPDIR)/hcisecfilter.Po + -rm -f tools/$(DEPDIR)/hcitool.Po + -rm -f tools/$(DEPDIR)/hex2hcd.Po + -rm -f tools/$(DEPDIR)/hid2hci.Po + -rm -f tools/$(DEPDIR)/hwdb.Po + -rm -f tools/$(DEPDIR)/ibeacon.Po + -rm -f tools/$(DEPDIR)/l2cap-tester.Po + -rm -f tools/$(DEPDIR)/l2ping.Po + -rm -f tools/$(DEPDIR)/l2test.Po + -rm -f tools/$(DEPDIR)/mcaptest.Po + -rm -f tools/$(DEPDIR)/meshctl.Po + -rm -f tools/$(DEPDIR)/mgmt-tester.Po + -rm -f tools/$(DEPDIR)/mpris-proxy.Po + -rm -f tools/$(DEPDIR)/nokfw.Po + -rm -f tools/$(DEPDIR)/obex-client-tool.Po + -rm -f tools/$(DEPDIR)/obex-server-tool.Po + -rm -f tools/$(DEPDIR)/obexctl.Po + -rm -f tools/$(DEPDIR)/oobtest.Po + -rm -f tools/$(DEPDIR)/rctest.Po + -rm -f tools/$(DEPDIR)/rfcomm-tester.Po + -rm -f tools/$(DEPDIR)/rfcomm.Po + -rm -f tools/$(DEPDIR)/rtlfw.Po + -rm -f tools/$(DEPDIR)/sco-tester.Po + -rm -f tools/$(DEPDIR)/scotest.Po + -rm -f tools/$(DEPDIR)/sdptool.Po + -rm -f tools/$(DEPDIR)/seq2bseq.Po + -rm -f tools/$(DEPDIR)/smp-tester.Po + -rm -f tools/$(DEPDIR)/test-runner.Po + -rm -f tools/$(DEPDIR)/ubcsp.Po + -rm -f tools/$(DEPDIR)/userchan-tester.Po + -rm -f tools/mesh/$(DEPDIR)/agent.Po + -rm -f tools/mesh/$(DEPDIR)/config-client.Po + -rm -f tools/mesh/$(DEPDIR)/config-server.Po + -rm -f tools/mesh/$(DEPDIR)/crypto.Po + -rm -f tools/mesh/$(DEPDIR)/gatt.Po + -rm -f tools/mesh/$(DEPDIR)/net.Po + -rm -f tools/mesh/$(DEPDIR)/node.Po + -rm -f tools/mesh/$(DEPDIR)/onoff-model.Po + -rm -f tools/mesh/$(DEPDIR)/prov-db.Po + -rm -f tools/mesh/$(DEPDIR)/prov.Po + -rm -f tools/mesh/$(DEPDIR)/util.Po + -rm -f tools/parser/$(DEPDIR)/amp.Po + -rm -f tools/parser/$(DEPDIR)/att.Po + -rm -f tools/parser/$(DEPDIR)/avctp.Po + -rm -f tools/parser/$(DEPDIR)/avdtp.Po + -rm -f tools/parser/$(DEPDIR)/avrcp.Po + -rm -f tools/parser/$(DEPDIR)/bnep.Po + -rm -f tools/parser/$(DEPDIR)/bpa.Po + -rm -f tools/parser/$(DEPDIR)/capi.Po + -rm -f tools/parser/$(DEPDIR)/cmtp.Po + -rm -f tools/parser/$(DEPDIR)/csr.Po + -rm -f tools/parser/$(DEPDIR)/ericsson.Po + -rm -f tools/parser/$(DEPDIR)/hci.Po + -rm -f tools/parser/$(DEPDIR)/hcrp.Po + -rm -f tools/parser/$(DEPDIR)/hidp.Po + -rm -f tools/parser/$(DEPDIR)/l2cap.Po + -rm -f tools/parser/$(DEPDIR)/lmp.Po + -rm -f tools/parser/$(DEPDIR)/obex.Po + -rm -f tools/parser/$(DEPDIR)/parser.Po + -rm -f tools/parser/$(DEPDIR)/ppp.Po + -rm -f tools/parser/$(DEPDIR)/rfcomm.Po + -rm -f tools/parser/$(DEPDIR)/sap.Po + -rm -f tools/parser/$(DEPDIR)/sdp.Po + -rm -f tools/parser/$(DEPDIR)/smp.Po + -rm -f tools/parser/$(DEPDIR)/tcpip.Po + -rm -f unit/$(DEPDIR)/test-avctp.Po + -rm -f unit/$(DEPDIR)/test-avdtp.Po + -rm -f unit/$(DEPDIR)/test-avrcp.Po + -rm -f unit/$(DEPDIR)/test-crc.Po + -rm -f unit/$(DEPDIR)/test-crypto.Po + -rm -f unit/$(DEPDIR)/test-ecc.Po + -rm -f unit/$(DEPDIR)/test-eir.Po + -rm -f unit/$(DEPDIR)/test-gatt.Po + -rm -f unit/$(DEPDIR)/test-gattrib.Po + -rm -f unit/$(DEPDIR)/test-gdbus-client.Po + -rm -f unit/$(DEPDIR)/test-gobex-apparam.Po + -rm -f unit/$(DEPDIR)/test-gobex-header.Po + -rm -f unit/$(DEPDIR)/test-gobex-packet.Po + -rm -f unit/$(DEPDIR)/test-gobex-transfer.Po + -rm -f unit/$(DEPDIR)/test-gobex.Po + -rm -f unit/$(DEPDIR)/test-hfp.Po + -rm -f unit/$(DEPDIR)/test-hog.Po + -rm -f unit/$(DEPDIR)/test-lib.Po + -rm -f unit/$(DEPDIR)/test-mgmt.Po + -rm -f unit/$(DEPDIR)/test-queue.Po + -rm -f unit/$(DEPDIR)/test-ringbuf.Po + -rm -f unit/$(DEPDIR)/test-sdp.Po + -rm -f unit/$(DEPDIR)/test-textfile.Po + -rm -f unit/$(DEPDIR)/test-uhid.Po + -rm -f unit/$(DEPDIR)/test-uuid.Po + -rm -f unit/$(DEPDIR)/test_mesh_crypto-test-mesh-crypto.Po + -rm -f unit/$(DEPDIR)/test_midi-test-midi.Po + -rm -f unit/$(DEPDIR)/util.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic \ + maintainer-clean-local + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-confDATA \ + uninstall-cupsPROGRAMS uninstall-dbusDATA \ + uninstall-dbussessionbusDATA uninstall-dbussystembusDATA \ + uninstall-dist_zshcompletionDATA uninstall-libLTLIBRARIES \ + uninstall-man uninstall-pkgconfigDATA \ + uninstall-pkgincludeHEADERS uninstall-pkglibexecPROGRAMS \ + uninstall-pluginLTLIBRARIES uninstall-rulesDATA \ + uninstall-stateDATA uninstall-systemdsystemunitDATA \ + uninstall-systemduserunitDATA uninstall-testSCRIPTS \ + uninstall-udevPROGRAMS + +uninstall-man: uninstall-man1 uninstall-man8 + +.MAKE: all check check-am install install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles am--refresh check \ + check-TESTS check-am clean clean-binPROGRAMS clean-cscope \ + clean-cupsPROGRAMS clean-generic clean-libLTLIBRARIES \ + clean-libtool clean-local clean-noinstLIBRARIES \ + clean-noinstLTLIBRARIES clean-noinstPROGRAMS \ + clean-pkglibexecPROGRAMS clean-pluginLTLIBRARIES \ + clean-udevPROGRAMS cscope cscopelist-am ctags ctags-am dist \ + dist-all dist-bzip2 dist-gzip dist-lzip dist-shar dist-tarZ \ + dist-xz dist-zip distcheck distclean distclean-compile \ + distclean-generic distclean-hdr distclean-libtool \ + distclean-tags distcleancheck distdir distuninstallcheck dvi \ + dvi-am html html-am info info-am install install-am \ + install-binPROGRAMS install-confDATA install-cupsPROGRAMS \ + install-data install-data-am install-dbusDATA \ + install-dbussessionbusDATA install-dbussystembusDATA \ + install-dist_zshcompletionDATA install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-libLTLIBRARIES \ + install-man install-man1 install-man8 install-pdf \ + install-pdf-am install-pkgconfigDATA install-pkgincludeHEADERS \ + install-pkglibexecPROGRAMS install-pluginLTLIBRARIES \ + install-ps install-ps-am install-rulesDATA install-stateDATA \ + install-strip install-systemdsystemunitDATA \ + install-systemduserunitDATA install-testSCRIPTS \ + install-udevPROGRAMS installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic \ + maintainer-clean-local mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + recheck tags tags-am uninstall uninstall-am \ + uninstall-binPROGRAMS uninstall-confDATA \ + uninstall-cupsPROGRAMS uninstall-dbusDATA \ + uninstall-dbussessionbusDATA uninstall-dbussystembusDATA \ + uninstall-dist_zshcompletionDATA uninstall-libLTLIBRARIES \ + uninstall-man uninstall-man1 uninstall-man8 \ + uninstall-pkgconfigDATA uninstall-pkgincludeHEADERS \ + uninstall-pkglibexecPROGRAMS uninstall-pluginLTLIBRARIES \ + uninstall-rulesDATA uninstall-stateDATA \ + uninstall-systemdsystemunitDATA uninstall-systemduserunitDATA \ + uninstall-testSCRIPTS uninstall-udevPROGRAMS + +.PRECIOUS: Makefile + +@BTPCLIENT_TRUE@tools/btpclient.$(OBJEXT): src/libshared-ell.la ell/internal + +obexd/src/plugin.$(OBJEXT): obexd/src/builtin.h + +obexd/src/builtin.h: obexd/src/genbuiltin $(obexd_builtin_sources) + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(srcdir)/obexd/src/genbuiltin $(obexd_builtin_modules) > $@ + +@MESH_TRUE@mesh/mesh.$(OBJEXT): ell/internal +@MESH_TRUE@mesh/main.$(OBJEXT): src/builtin.h lib/bluetooth/bluetooth.h + +%.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_at)$(MKDIR_P) tools + $(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 $<) $@ + +ell/internal: Makefile + $(AM_V_at)$(MKDIR_P) ell + $(AM_V_GEN)for f in $(ell_headers) $(ell_sources) ; do \ + if [ ! -f $$f ] ; then \ + $(LN_S) -t ell -f $(abs_srcdir)/../ell/$$f ; \ + fi \ + done > $@ + +ell/ell.h: Makefile + $(AM_V_at)echo -n > $@ + $(AM_V_GEN)for f in $(ell_headers) ; do \ + echo "#include <$$f>" >> $@ ; \ + done + +maintainer-clean-local: + -rm -rf ell + +@COVERAGE_TRUE@clean-coverage: +@COVERAGE_TRUE@ @lcov --directory $(top_builddir) --zerocounters +@COVERAGE_TRUE@ $(RM) -r coverage $(top_builddir)/coverage.info + +@COVERAGE_TRUE@coverage: check +@COVERAGE_TRUE@ @lcov --compat-libtool --directory $(top_builddir) --capture \ +@COVERAGE_TRUE@ --output-file $(top_builddir)/coverage.info +@COVERAGE_TRUE@ $(AM_V_at)$(MKDIR_P) coverage +@COVERAGE_TRUE@ @genhtml -o coverage/ $(top_builddir)/coverage.info + +@COVERAGE_TRUE@clean-local: clean-coverage +@COVERAGE_TRUE@ -find $(top_builddir) -name "*.gcno" -delete +@COVERAGE_TRUE@ -find $(top_builddir) -name "*.gcda" -delete +@COVERAGE_TRUE@ $(RM) -r lib/bluetooth + +@COVERAGE_FALSE@clean-local: +@COVERAGE_FALSE@ -find $(top_builddir) -name "*.gcno" -delete +@COVERAGE_FALSE@ -find $(top_builddir) -name "*.gcda" -delete +@COVERAGE_FALSE@ $(RM) -r lib/bluetooth + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/Makefile.mesh b/Makefile.mesh new file mode 100644 index 0000000..90979cb --- /dev/null +++ b/Makefile.mesh @@ -0,0 +1,51 @@ +if MESH + +if DATAFILES +dbus_DATA += mesh/bluetooth-mesh.conf +endif + +if SYSTEMD +systemdsystemunit_DATA += mesh/bluetooth-mesh.service +dbussystembus_DATA += mesh/org.bluez.mesh.service +endif + +mesh_sources = mesh/mesh.h mesh/mesh.c \ + mesh/net-keys.h mesh/net-keys.c \ + mesh/mesh-io.h mesh/mesh-io.c \ + mesh/mesh-mgmt.c mesh/mesh-mgmt.h \ + mesh/error.h mesh/mesh-io-api.h \ + mesh/mesh-io-generic.h \ + mesh/mesh-io-generic.c \ + mesh/net.h mesh/net.c \ + mesh/crypto.h mesh/crypto.c \ + mesh/friend.h mesh/friend.c \ + mesh/appkey.h mesh/appkey.c \ + mesh/node.h mesh/node.c \ + mesh/provision.h mesh/prov.h \ + mesh/model.h mesh/model.c \ + mesh/cfgmod.h mesh/cfgmod-server.c \ + mesh/mesh-config.h mesh/mesh-config-json.c \ + mesh/util.h mesh/util.c \ + mesh/dbus.h mesh/dbus.c \ + mesh/agent.h mesh/agent.c \ + mesh/prov-acceptor.c mesh/prov-initiator.c \ + mesh/manager.h mesh/manager.c \ + mesh/pb-adv.h mesh/pb-adv.c \ + mesh/keyring.h mesh/keyring.c \ + mesh/mesh-defs.h +pkglibexec_PROGRAMS += mesh/bluetooth-meshd + +mesh/mesh.$(OBJEXT): ell/internal +mesh/main.$(OBJEXT): src/builtin.h lib/bluetooth/bluetooth.h + +mesh_bluetooth_meshd_SOURCES = $(mesh_sources) mesh/main.c +mesh_bluetooth_meshd_LDADD = src/libshared-ell.la $(ell_ldadd) -ljson-c +mesh_bluetooth_meshd_DEPENDENCIES = $(ell_dependencies) src/libshared-ell.la \ + mesh/bluetooth-mesh.service + +CLEANFILES += mesh/bluetooth-mesh.service + +endif + +EXTRA_DIST += mesh/bluetooth-mesh.conf mesh/bluetooth-mesh.service.in \ + mesh/org.bluez.mesh.service diff --git a/Makefile.obexd b/Makefile.obexd new file mode 100644 index 0000000..de59d29 --- /dev/null +++ b/Makefile.obexd @@ -0,0 +1,112 @@ +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 + +if OBEX + +obex_plugindir = $(libdir)/obex/plugins + +obexd_builtin_modules = +obexd_builtin_sources = +obexd_builtin_nodist = + +obexd_builtin_modules += filesystem +obexd_builtin_sources += obexd/plugins/filesystem.c obexd/plugins/filesystem.h + +obexd_builtin_modules += bluetooth +obexd_builtin_sources += obexd/plugins/bluetooth.c + +if EXPERIMENTAL +obexd_builtin_modules += pcsuite +obexd_builtin_sources += obexd/plugins/pcsuite.c +endif + +obexd_builtin_modules += opp +obexd_builtin_sources += obexd/plugins/opp.c + +obexd_builtin_modules += ftp +obexd_builtin_sources += obexd/plugins/ftp.c obexd/plugins/ftp.h + +obexd_builtin_modules += irmc +obexd_builtin_sources += obexd/plugins/irmc.c + +obexd_builtin_modules += pbap +obexd_builtin_sources += obexd/plugins/pbap.c \ + obexd/plugins/vcard.h obexd/plugins/vcard.c \ + obexd/plugins/phonebook.h \ + obexd/plugins/phonebook-dummy.c + +obexd_builtin_modules += mas +obexd_builtin_sources += obexd/plugins/mas.c obexd/src/map_ap.h \ + obexd/plugins/messages.h \ + obexd/plugins/messages-dummy.c + +obexd_builtin_modules += mns +obexd_builtin_sources += obexd/client/mns.c obexd/src/map_ap.h \ + obexd/client/map-event.h + +pkglibexec_PROGRAMS += obexd/src/obexd + +obexd_src_obexd_SOURCES = $(btio_sources) $(gobex_sources) \ + $(obexd_builtin_sources) \ + obexd/src/main.c obexd/src/obexd.h \ + obexd/src/plugin.h obexd/src/plugin.c \ + obexd/src/log.h obexd/src/log.c \ + obexd/src/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/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 = $(AM_LDFLAGS) -Wl,--export-dynamic + +obexd_src_obexd_CPPFLAGS = $(AM_CPPFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) \ + $(ICAL_CFLAGS) -DOBEX_PLUGIN_BUILTIN \ + -DPLUGINDIR=\""$(obex_plugindir)"\" \ + -D_FILE_OFFSET_BITS=64 \ + -I$(builddir)/lib -I$(builddir)/obexd/src + +obexd_src_obexd_CFLAGS = $(AM_CFLAGS) -fPIC + +endif + +obexd_src_obexd_SHORTNAME = obexd + +obexd_builtin_files = obexd/src/builtin.h $(obexd_builtin_nodist) + +nodist_obexd_src_obexd_SOURCES = $(obexd_builtin_files) + +BUILT_SOURCES += obexd/src/builtin.h + +obexd/src/plugin.$(OBJEXT): obexd/src/builtin.h + +obexd/src/builtin.h: obexd/src/genbuiltin $(obexd_builtin_sources) + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(srcdir)/obexd/src/genbuiltin $(obexd_builtin_modules) > $@ + +CLEANFILES += obexd/src/builtin.h $(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..98eee9e --- /dev/null +++ b/Makefile.plugins @@ -0,0 +1,114 @@ + +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 profiles/input/sixaxis.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_cppflags += $(ALSA_CFLAGS) +builtin_ldadd += $(ALSA_LIBS) +endif + +builtin_modules += battery +builtin_sources += profiles/battery/battery.c + +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 +plugins_sixaxis_la_LIBADD = $(UDEV_LIBS) +plugins_sixaxis_la_CFLAGS = $(AM_CFLAGS) -fvisibility=hidden +endif diff --git a/Makefile.tools b/Makefile.tools new file mode 100644 index 0000000..7ce05b7 --- /dev/null +++ b/Makefile.tools @@ -0,0 +1,482 @@ + +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 +client_bluetoothctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ + $(GLIB_LIBS) $(DBUS_LIBS) -lreadline +endif + +if ZSH_COMPLETIONS +zshcompletiondir=$(ZSH_COMPLETIONDIR) +dist_zshcompletion_DATA = completion/zsh/_bluetoothctl +endif + +if MONITOR +bin_PROGRAMS += monitor/btmon + +monitor_btmon_SOURCES = monitor/main.c monitor/bt.h \ + monitor/display.h monitor/display.c \ + monitor/hcidump.h monitor/hcidump.c \ + monitor/ellisys.h monitor/ellisys.c \ + monitor/control.h monitor/control.c \ + monitor/packet.h monitor/packet.c \ + monitor/vendor.h monitor/vendor.c \ + monitor/lmp.h monitor/lmp.c \ + monitor/crc.h monitor/crc.c \ + monitor/ll.h monitor/ll.c \ + monitor/l2cap.h monitor/l2cap.c \ + monitor/sdp.h monitor/sdp.c \ + monitor/avctp.h monitor/avctp.c \ + monitor/avdtp.h monitor/avdtp.c \ + monitor/a2dp.h monitor/a2dp.c \ + monitor/rfcomm.h monitor/rfcomm.c \ + monitor/bnep.h monitor/bnep.c \ + monitor/hwdb.h monitor/hwdb.c \ + monitor/keys.h monitor/keys.c \ + monitor/analyze.h monitor/analyze.c \ + monitor/intel.h monitor/intel.c \ + monitor/broadcom.h monitor/broadcom.c \ + monitor/jlink.h monitor/jlink.c \ + monitor/tty.h +monitor_btmon_LDADD = lib/libbluetooth-internal.la \ + src/libshared-mainloop.la $(UDEV_LIBS) -ldl +endif + +if LOGGER +pkglibexec_PROGRAMS += tools/btmon-logger + +tools_btmon_logger_SOURCES = tools/btmon-logger.c +tools_btmon_logger_LDADD = src/libshared-mainloop.la +tools_btmon_logger_DEPENDENCIES = src/libshared-mainloop.la \ + tools/bluetooth-logger.service + +if SYSTEMD +systemdsystemunit_DATA += tools/bluetooth-logger.service +endif +endif + +CLEANFILES += tools/bluetooth-logger.service +EXTRA_DIST += tools/bluetooth-logger.service.in + +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/rtlfw \ + tools/bcmfw tools/create-image \ + tools/eddystone tools/ibeacon \ + tools/btgatt-client tools/btgatt-server \ + tools/test-runner tools/check-selftest \ + tools/gatt-service profiles/iap/iapd + +tools_bdaddr_SOURCES = tools/bdaddr.c src/oui.h src/oui.c +tools_bdaddr_LDADD = lib/libbluetooth-internal.la $(UDEV_LIBS) + +tools_avinfo_LDADD = lib/libbluetooth-internal.la + +tools_avtest_LDADD = lib/libbluetooth-internal.la + +tools_scotest_LDADD = lib/libbluetooth-internal.la + +tools_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) \ + src/libshared-mainloop.la -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) \ + src/libshared-mainloop.la + +tools_cltest_SOURCES = tools/cltest.c +tools_cltest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la + +tools_oobtest_SOURCES = tools/oobtest.c +tools_oobtest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la + +tools_advtest_SOURCES = tools/advtest.c +tools_advtest_LDADD = lib/libbluetooth-internal.la src/libshared-mainloop.la + +tools_seq2bseq_SOURCES = tools/seq2bseq.c + +tools_nokfw_SOURCES = tools/nokfw.c + +tools_rtlfw_SOURCES = tools/rtlfw.c + +tools_create_image_SOURCES = tools/create-image.c + +tools_eddystone_SOURCES = tools/eddystone.c monitor/bt.h +tools_eddystone_LDADD = src/libshared-mainloop.la + +tools_ibeacon_SOURCES = tools/ibeacon.c monitor/bt.h +tools_ibeacon_LDADD = src/libshared-mainloop.la + +tools_btgatt_client_SOURCES = tools/btgatt-client.c src/uuid-helper.c +tools_btgatt_client_LDADD = src/libshared-mainloop.la \ + lib/libbluetooth-internal.la + +tools_btgatt_server_SOURCES = tools/btgatt-server.c src/uuid-helper.c +tools_btgatt_server_LDADD = src/libshared-mainloop.la \ + lib/libbluetooth-internal.la + +tools_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 MESH +bin_PROGRAMS += tools/meshctl + +tools_meshctl_SOURCES = tools/meshctl.c \ + tools/mesh/mesh-net.h \ + tools/mesh/node.h tools/mesh/node.c \ + tools/mesh/gatt.h tools/mesh/gatt.c \ + tools/mesh/crypto.h tools/mesh/crypto.c \ + tools/mesh/keys.h \ + tools/mesh/net.h tools/mesh/net.c \ + tools/mesh/prov.h tools/mesh/prov.c \ + tools/mesh/util.h tools/mesh/util.c \ + tools/mesh/agent.h tools/mesh/agent.c \ + tools/mesh/prov-db.h tools/mesh/prov-db.c \ + tools/mesh/config-model.h tools/mesh/config-client.c \ + tools/mesh/config-server.c \ + tools/mesh/onoff-model.h tools/mesh/onoff-model.c +tools_meshctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ + lib/libbluetooth-internal.la \ + $(GLIB_LIBS) $(DBUS_LIBS) -ljson-c -lreadline +endif + +EXTRA_DIST += tools/mesh/local_node.json tools/mesh/prov_db.json + +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 +tools_bluetooth_player_LDADD = gdbus/libgdbus-internal.la \ + src/libshared-glib.la \ + $(GLIB_LIBS) $(DBUS_LIBS) -lreadline + +tools_obexctl_SOURCES = tools/obexctl.c +tools_obexctl_LDADD = gdbus/libgdbus-internal.la src/libshared-glib.la \ + $(GLIB_LIBS) $(DBUS_LIBS) -lreadline + +tools_btmgmt_SOURCES = tools/btmgmt.c src/uuid-helper.c client/display.c +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 test/test-mesh test/agent.py + +if BTPCLIENT +noinst_PROGRAMS += tools/btpclient + +tools_btpclient_SOURCES = tools/btpclient.c src/shared/btp.c src/shared/btp.h +tools_btpclient_LDADD = lib/libbluetooth-internal.la \ + src/libshared-ell.la $(ell_ldadd) +tools_btpclient_DEPENDENCIES = lib/libbluetooth-internal.la $(ell_dependencies) +tools/btpclient.$(OBJEXT): src/libshared-ell.la ell/internal +endif 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..1c5c140 --- /dev/null +++ b/README @@ -0,0 +1,271 @@ +BlueZ - Bluetooth protocol stack for Linux +****************************************** + +Copyright (C) 2000-2001 Qualcomm Incorporated +Copyright (C) 2002-2003 Maxim Krasnyansky +Copyright (C) 2002-2010 Marcel Holtmann + + +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 + + +Embedded Linux library +====================== + +In order to compile mesh support and test client utility the development +version of Embedded Linux library is required to be present. The development +repositories can be found here: + + git://git.kernel.org/pub/scm/libs/ell/ell.git + https://kernel.googlesource.com/pub/scm/libs/ell/ell.git + +The build systems requires that the Embedded Linux library source code +is available on the same top level directory as the source code: + + . + |--- ell + | |--- ell + | `--- unit + `--- bluez + |--- src + `--- tools + +It is not required to build or install Embedded Linux library. The build +will happen when building the binaries and it will then be linked internally. + +When using --enable-external-ell build option, it is not required that the +Embedded Linux library source code is available in the top level directory. + +When neither --enable-mesh nor --enable-btpclient is specified, then this +part is irrelevant and Embedded Linux library is not required. + + +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..5986102 --- /dev/null +++ b/TODO @@ -0,0 +1,239 @@ +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 +==================== + +Mesh +==== +- Read default configuration settings (e.g., provisioning timeout, supported + features, etc.)from mesh.conf file. + + Priority: Medium + Complexity: C1 + +- Add examples and unit tests + + Priority: High + Complexity: C1 + +- Implement unified feature management mechanism for mesh nodes that are hosted + on the same device + + Priority: Medium + Complexity: C2 + +- Implement mandatory Health Server model. Most likely, this will involve + additions to mesh D-Bus API + + Priority: Medium + Complexity: C2 + +- Design and implement Mesh Provisioner/ Configuration Client Model. + + Priority: Medium + Complexity: C4 + +- Add support for GATT proxy server + + Priority: Low + Complexity: C4 + +- Merge common functionality from tools/mesh. Ideally, source code from the + tools/mesh directory should completely dissapear. + + Priority: Low + Complexity: C2 + +- Support for Low Power Node (LPN) + + Priority: Low + Complexity: C4 + +- Support for all the provisioning OOB types + + Priority: Medium + Complexity: C2 diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..5298483 --- /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_32" + 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 -Wl,-z,now" + 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/aclocal.m4 b/aclocal.m4 new file mode 100644 index 0000000..fe1d81f --- /dev/null +++ b/aclocal.m4 @@ -0,0 +1,10502 @@ +# generated automatically by aclocal 1.16.1 -*- Autoconf -*- + +# Copyright (C) 1996-2018 Free Software Foundation, Inc. + +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], [])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])]) +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],, +[m4_warning([this file was generated for autoconf 2.69. +You have another version of autoconf. It may work, but is not guaranteed to. +If you have problems, you may need to regenerate the build system entirely. +To do so, use the procedure documented by the package, typically 'autoreconf'.])]) + +# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- +# +# Copyright (C) 1996-2001, 2003-2015 Free Software Foundation, Inc. +# Written by Gordon Matzigkeit, 1996 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +m4_define([_LT_COPYING], [dnl +# Copyright (C) 2014 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program or library that is built +# using GNU Libtool, you may include this file under the same +# distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +]) + +# serial 58 LT_INIT + + +# LT_PREREQ(VERSION) +# ------------------ +# Complain and exit if this libtool version is less that VERSION. +m4_defun([LT_PREREQ], +[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1, + [m4_default([$3], + [m4_fatal([Libtool version $1 or higher is required], + 63)])], + [$2])]) + + +# _LT_CHECK_BUILDDIR +# ------------------ +# Complain if the absolute build directory name contains unusual characters +m4_defun([_LT_CHECK_BUILDDIR], +[case `pwd` in + *\ * | *\ *) + AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;; +esac +]) + + +# LT_INIT([OPTIONS]) +# ------------------ +AC_DEFUN([LT_INIT], +[AC_PREREQ([2.62])dnl We use AC_PATH_PROGS_FEATURE_CHECK +AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +AC_BEFORE([$0], [LT_LANG])dnl +AC_BEFORE([$0], [LT_OUTPUT])dnl +AC_BEFORE([$0], [LTDL_INIT])dnl +m4_require([_LT_CHECK_BUILDDIR])dnl + +dnl Autoconf doesn't catch unexpanded LT_ macros by default: +m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl +m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl +dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4 +dnl unless we require an AC_DEFUNed macro: +AC_REQUIRE([LTOPTIONS_VERSION])dnl +AC_REQUIRE([LTSUGAR_VERSION])dnl +AC_REQUIRE([LTVERSION_VERSION])dnl +AC_REQUIRE([LTOBSOLETE_VERSION])dnl +m4_require([_LT_PROG_LTMAIN])dnl + +_LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}]) + +dnl Parse OPTIONS +_LT_SET_OPTIONS([$0], [$1]) + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS=$ltmain + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' +AC_SUBST(LIBTOOL)dnl + +_LT_SETUP + +# Only expand once: +m4_define([LT_INIT]) +])# LT_INIT + +# Old names: +AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT]) +AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PROG_LIBTOOL], []) +dnl AC_DEFUN([AM_PROG_LIBTOOL], []) + + +# _LT_PREPARE_CC_BASENAME +# ----------------------- +m4_defun([_LT_PREPARE_CC_BASENAME], [ +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in @S|@*""; do + case $cc_temp in + compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;; + distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} +])# _LT_PREPARE_CC_BASENAME + + +# _LT_CC_BASENAME(CC) +# ------------------- +# It would be clearer to call AC_REQUIREs from _LT_PREPARE_CC_BASENAME, +# but that macro is also expanded into generated libtool script, which +# arranges for $SED and $ECHO to be set by different means. +m4_defun([_LT_CC_BASENAME], +[m4_require([_LT_PREPARE_CC_BASENAME])dnl +AC_REQUIRE([_LT_DECL_SED])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl +func_cc_basename $1 +cc_basename=$func_cc_basename_result +]) + + +# _LT_FILEUTILS_DEFAULTS +# ---------------------- +# It is okay to use these file commands and assume they have been set +# sensibly after 'm4_require([_LT_FILEUTILS_DEFAULTS])'. +m4_defun([_LT_FILEUTILS_DEFAULTS], +[: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} +])# _LT_FILEUTILS_DEFAULTS + + +# _LT_SETUP +# --------- +m4_defun([_LT_SETUP], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl + +_LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl +dnl +_LT_DECL([], [host_alias], [0], [The host system])dnl +_LT_DECL([], [host], [0])dnl +_LT_DECL([], [host_os], [0])dnl +dnl +_LT_DECL([], [build_alias], [0], [The build system])dnl +_LT_DECL([], [build], [0])dnl +_LT_DECL([], [build_os], [0])dnl +dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +dnl +AC_REQUIRE([AC_PROG_LN_S])dnl +test -z "$LN_S" && LN_S="ln -s" +_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl +dnl +AC_REQUIRE([LT_CMD_MAX_LEN])dnl +_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl +_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl +dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl +m4_require([_LT_CMD_RELOAD])dnl +m4_require([_LT_CHECK_MAGIC_METHOD])dnl +m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl +m4_require([_LT_CMD_OLD_ARCHIVE])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_WITH_SYSROOT])dnl +m4_require([_LT_CMD_TRUNCATE])dnl + +_LT_CONFIG_LIBTOOL_INIT([ +# See if we are running on zsh, and set the options that allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi +]) +if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + +_LT_CHECK_OBJDIR + +m4_require([_LT_TAG_COMPILER])dnl + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld=$lt_cv_prog_gnu_ld + +old_CC=$CC +old_CFLAGS=$CFLAGS + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +_LT_CC_BASENAME([$compiler]) + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + _LT_PATH_MAGIC + fi + ;; +esac + +# Use C for the default configuration in the libtool script +LT_SUPPORTED_TAG([CC]) +_LT_LANG_C_CONFIG +_LT_LANG_DEFAULT_CONFIG +_LT_CONFIG_COMMANDS +])# _LT_SETUP + + +# _LT_PREPARE_SED_QUOTE_VARS +# -------------------------- +# Define a few sed substitution that help us do robust quoting. +m4_defun([_LT_PREPARE_SED_QUOTE_VARS], +[# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\([["`$\\]]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\([["`\\]]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' +]) + +# _LT_PROG_LTMAIN +# --------------- +# Note that this code is called both from 'configure', and 'config.status' +# now that we use AC_CONFIG_COMMANDS to generate libtool. Notably, +# 'config.status' has no value for ac_aux_dir unless we are using Automake, +# so we pass a copy along to make sure it has a sensible value anyway. +m4_defun([_LT_PROG_LTMAIN], +[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl +_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir']) +ltmain=$ac_aux_dir/ltmain.sh +])# _LT_PROG_LTMAIN + + + +# So that we can recreate a full libtool script including additional +# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS +# in macros and then make a single call at the end using the 'libtool' +# label. + + +# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS]) +# ---------------------------------------- +# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL_INIT], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_INIT], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_INIT]) + + +# _LT_CONFIG_LIBTOOL([COMMANDS]) +# ------------------------------ +# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS]) + + +# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS]) +# ----------------------------------------------------- +m4_defun([_LT_CONFIG_SAVE_COMMANDS], +[_LT_CONFIG_LIBTOOL([$1]) +_LT_CONFIG_LIBTOOL_INIT([$2]) +]) + + +# _LT_FORMAT_COMMENT([COMMENT]) +# ----------------------------- +# Add leading comment marks to the start of each line, and a trailing +# full-stop to the whole comment if one is not present already. +m4_define([_LT_FORMAT_COMMENT], +[m4_ifval([$1], [ +m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])], + [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.]) +)]) + + + + + +# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?]) +# ------------------------------------------------------------------- +# CONFIGNAME is the name given to the value in the libtool script. +# VARNAME is the (base) name used in the configure script. +# VALUE may be 0, 1 or 2 for a computed quote escaped value based on +# VARNAME. Any other value will be used directly. +m4_define([_LT_DECL], +[lt_if_append_uniq([lt_decl_varnames], [$2], [, ], + [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name], + [m4_ifval([$1], [$1], [$2])]) + lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3]) + m4_ifval([$4], + [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])]) + lt_dict_add_subkey([lt_decl_dict], [$2], + [tagged?], [m4_ifval([$5], [yes], [no])])]) +]) + + +# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION]) +# -------------------------------------------------------- +m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])]) + + +# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_tag_varnames], +[_lt_decl_filter([tagged?], [yes], $@)]) + + +# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..]) +# --------------------------------------------------------- +m4_define([_lt_decl_filter], +[m4_case([$#], + [0], [m4_fatal([$0: too few arguments: $#])], + [1], [m4_fatal([$0: too few arguments: $#: $1])], + [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)], + [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)], + [lt_dict_filter([lt_decl_dict], $@)])[]dnl +]) + + +# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...]) +# -------------------------------------------------- +m4_define([lt_decl_quote_varnames], +[_lt_decl_filter([value], [1], $@)]) + + +# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_dquote_varnames], +[_lt_decl_filter([value], [2], $@)]) + + +# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_varnames_tagged], +[m4_assert([$# <= 2])dnl +_$0(m4_quote(m4_default([$1], [[, ]])), + m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]), + m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))]) +m4_define([_lt_decl_varnames_tagged], +[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])]) + + +# lt_decl_all_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_all_varnames], +[_$0(m4_quote(m4_default([$1], [[, ]])), + m4_if([$2], [], + m4_quote(lt_decl_varnames), + m4_quote(m4_shift($@))))[]dnl +]) +m4_define([_lt_decl_all_varnames], +[lt_join($@, lt_decl_varnames_tagged([$1], + lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl +]) + + +# _LT_CONFIG_STATUS_DECLARE([VARNAME]) +# ------------------------------------ +# Quote a variable value, and forward it to 'config.status' so that its +# declaration there will have the same value as in 'configure'. VARNAME +# must have a single quote delimited value for this to work. +m4_define([_LT_CONFIG_STATUS_DECLARE], +[$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`']) + + +# _LT_CONFIG_STATUS_DECLARATIONS +# ------------------------------ +# We delimit libtool config variables with single quotes, so when +# we write them to config.status, we have to be sure to quote all +# embedded single quotes properly. In configure, this macro expands +# each variable declared with _LT_DECL (and _LT_TAGDECL) into: +# +# ='`$ECHO "$" | $SED "$delay_single_quote_subst"`' +m4_defun([_LT_CONFIG_STATUS_DECLARATIONS], +[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames), + [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAGS +# ---------------- +# Output comment and list of tags supported by the script +m4_defun([_LT_LIBTOOL_TAGS], +[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl +available_tags='_LT_TAGS'dnl +]) + + +# _LT_LIBTOOL_DECLARE(VARNAME, [TAG]) +# ----------------------------------- +# Extract the dictionary values for VARNAME (optionally with TAG) and +# expand to a commented shell variable setting: +# +# # Some comment about what VAR is for. +# visible_name=$lt_internal_name +m4_define([_LT_LIBTOOL_DECLARE], +[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], + [description])))[]dnl +m4_pushdef([_libtool_name], + m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl +m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])), + [0], [_libtool_name=[$]$1], + [1], [_libtool_name=$lt_[]$1], + [2], [_libtool_name=$lt_[]$1], + [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl +m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl +]) + + +# _LT_LIBTOOL_CONFIG_VARS +# ----------------------- +# Produce commented declarations of non-tagged libtool config variables +# suitable for insertion in the LIBTOOL CONFIG section of the 'libtool' +# script. Tagged libtool config variables (even for the LIBTOOL CONFIG +# section) are produced by _LT_LIBTOOL_TAG_VARS. +m4_defun([_LT_LIBTOOL_CONFIG_VARS], +[m4_foreach([_lt_var], + m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAG_VARS(TAG) +# ------------------------- +m4_define([_LT_LIBTOOL_TAG_VARS], +[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])]) + + +# _LT_TAGVAR(VARNAME, [TAGNAME]) +# ------------------------------ +m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])]) + + +# _LT_CONFIG_COMMANDS +# ------------------- +# Send accumulated output to $CONFIG_STATUS. Thanks to the lists of +# variables for single and double quote escaping we saved from calls +# to _LT_DECL, we can put quote escaped variables declarations +# into 'config.status', and then the shell code to quote escape them in +# for loops in 'config.status'. Finally, any additional code accumulated +# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded. +m4_defun([_LT_CONFIG_COMMANDS], +[AC_PROVIDE_IFELSE([LT_OUTPUT], + dnl If the libtool generation code has been placed in $CONFIG_LT, + dnl instead of duplicating it all over again into config.status, + dnl then we will have config.status run $CONFIG_LT later, so it + dnl needs to know what name is stored there: + [AC_CONFIG_COMMANDS([libtool], + [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])], + dnl If the libtool generation code is destined for config.status, + dnl expand the accumulated commands and init code now: + [AC_CONFIG_COMMANDS([libtool], + [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])]) +])#_LT_CONFIG_COMMANDS + + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT], +[ + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +_LT_CONFIG_STATUS_DECLARATIONS +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$[]1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_quote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_dquote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +_LT_OUTPUT_LIBTOOL_INIT +]) + +# _LT_GENERATED_FILE_INIT(FILE, [COMMENT]) +# ------------------------------------ +# Generate a child script FILE with all initialization necessary to +# reuse the environment learned by the parent script, and make the +# file executable. If COMMENT is supplied, it is inserted after the +# '#!' sequence but before initialization text begins. After this +# macro, additional text can be appended to FILE to form the body of +# the child script. The macro ends with non-zero status if the +# file could not be fully written (such as if the disk is full). +m4_ifdef([AS_INIT_GENERATED], +[m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])], +[m4_defun([_LT_GENERATED_FILE_INIT], +[m4_require([AS_PREPARE])]dnl +[m4_pushdef([AS_MESSAGE_LOG_FD])]dnl +[lt_write_fail=0 +cat >$1 <<_ASEOF || lt_write_fail=1 +#! $SHELL +# Generated by $as_me. +$2 +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$1 <<\_ASEOF || lt_write_fail=1 +AS_SHELL_SANITIZE +_AS_PREPARE +exec AS_MESSAGE_FD>&1 +_ASEOF +test 0 = "$lt_write_fail" && chmod +x $1[]dnl +m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT + +# LT_OUTPUT +# --------- +# This macro allows early generation of the libtool script (before +# AC_OUTPUT is called), incase it is used in configure for compilation +# tests. +AC_DEFUN([LT_OUTPUT], +[: ${CONFIG_LT=./config.lt} +AC_MSG_NOTICE([creating $CONFIG_LT]) +_LT_GENERATED_FILE_INIT(["$CONFIG_LT"], +[# Run this file to recreate a libtool stub with the current configuration.]) + +cat >>"$CONFIG_LT" <<\_LTEOF +lt_cl_silent=false +exec AS_MESSAGE_LOG_FD>>config.log +{ + echo + AS_BOX([Running $as_me.]) +} >&AS_MESSAGE_LOG_FD + +lt_cl_help="\ +'$as_me' creates a local libtool stub from the current configuration, +for use in further configure time tests before the real libtool is +generated. + +Usage: $[0] [[OPTIONS]] + + -h, --help print this help, then exit + -V, --version print version number, then exit + -q, --quiet do not print progress messages + -d, --debug don't remove temporary files + +Report bugs to ." + +lt_cl_version="\ +m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl +m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION]) +configured by $[0], generated by m4_PACKAGE_STRING. + +Copyright (C) 2011 Free Software Foundation, Inc. +This config.lt script is free software; the Free Software Foundation +gives unlimited permision to copy, distribute and modify it." + +while test 0 != $[#] +do + case $[1] in + --version | --v* | -V ) + echo "$lt_cl_version"; exit 0 ;; + --help | --h* | -h ) + echo "$lt_cl_help"; exit 0 ;; + --debug | --d* | -d ) + debug=: ;; + --quiet | --q* | --silent | --s* | -q ) + lt_cl_silent=: ;; + + -*) AC_MSG_ERROR([unrecognized option: $[1] +Try '$[0] --help' for more information.]) ;; + + *) AC_MSG_ERROR([unrecognized argument: $[1] +Try '$[0] --help' for more information.]) ;; + esac + shift +done + +if $lt_cl_silent; then + exec AS_MESSAGE_FD>/dev/null +fi +_LTEOF + +cat >>"$CONFIG_LT" <<_LTEOF +_LT_OUTPUT_LIBTOOL_COMMANDS_INIT +_LTEOF + +cat >>"$CONFIG_LT" <<\_LTEOF +AC_MSG_NOTICE([creating $ofile]) +_LT_OUTPUT_LIBTOOL_COMMANDS +AS_EXIT(0) +_LTEOF +chmod +x "$CONFIG_LT" + +# configure is writing to config.log, but config.lt does its own redirection, +# appending to config.log, which fails on DOS, as config.log is still kept +# open by configure. Here we exec the FD to /dev/null, effectively closing +# config.log, so it can be properly (re)opened and appended to by config.lt. +lt_cl_success=: +test yes = "$silent" && + lt_config_lt_args="$lt_config_lt_args --quiet" +exec AS_MESSAGE_LOG_FD>/dev/null +$SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false +exec AS_MESSAGE_LOG_FD>>config.log +$lt_cl_success || AS_EXIT(1) +])# LT_OUTPUT + + +# _LT_CONFIG(TAG) +# --------------- +# If TAG is the built-in tag, create an initial libtool script with a +# default configuration from the untagged config vars. Otherwise add code +# to config.status for appending the configuration named by TAG from the +# matching tagged config vars. +m4_defun([_LT_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_CONFIG_SAVE_COMMANDS([ + m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl + m4_if(_LT_TAG, [C], [ + # See if we are running on zsh, and set the options that allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST + fi + + cfgfile=${ofile}T + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL +# Generated automatically by $as_me ($PACKAGE) $VERSION +# NOTE: Changes made to this file will be lost: look at ltmain.sh. + +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit, 1996 + +_LT_COPYING +_LT_LIBTOOL_TAGS + +# Configured defaults for sys_lib_dlsearch_path munging. +: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} + +# ### BEGIN LIBTOOL CONFIG +_LT_LIBTOOL_CONFIG_VARS +_LT_LIBTOOL_TAG_VARS +# ### END LIBTOOL CONFIG + +_LT_EOF + + cat <<'_LT_EOF' >> "$cfgfile" + +# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE + +_LT_PREPARE_MUNGE_PATH_LIST +_LT_PREPARE_CC_BASENAME + +# ### END FUNCTIONS SHARED WITH CONFIGURE + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + _LT_PROG_LTMAIN + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" +], +[cat <<_LT_EOF >> "$ofile" + +dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded +dnl in a comment (ie after a #). +# ### BEGIN LIBTOOL TAG CONFIG: $1 +_LT_LIBTOOL_TAG_VARS(_LT_TAG) +# ### END LIBTOOL TAG CONFIG: $1 +_LT_EOF +])dnl /m4_if +], +[m4_if([$1], [], [ + PACKAGE='$PACKAGE' + VERSION='$VERSION' + RM='$RM' + ofile='$ofile'], []) +])dnl /_LT_CONFIG_SAVE_COMMANDS +])# _LT_CONFIG + + +# LT_SUPPORTED_TAG(TAG) +# --------------------- +# Trace this macro to discover what tags are supported by the libtool +# --tag option, using: +# autoconf --trace 'LT_SUPPORTED_TAG:$1' +AC_DEFUN([LT_SUPPORTED_TAG], []) + + +# C support is built-in for now +m4_define([_LT_LANG_C_enabled], []) +m4_define([_LT_TAGS], []) + + +# LT_LANG(LANG) +# ------------- +# Enable libtool support for the given language if not already enabled. +AC_DEFUN([LT_LANG], +[AC_BEFORE([$0], [LT_OUTPUT])dnl +m4_case([$1], + [C], [_LT_LANG(C)], + [C++], [_LT_LANG(CXX)], + [Go], [_LT_LANG(GO)], + [Java], [_LT_LANG(GCJ)], + [Fortran 77], [_LT_LANG(F77)], + [Fortran], [_LT_LANG(FC)], + [Windows Resource], [_LT_LANG(RC)], + [m4_ifdef([_LT_LANG_]$1[_CONFIG], + [_LT_LANG($1)], + [m4_fatal([$0: unsupported language: "$1"])])])dnl +])# LT_LANG + + +# _LT_LANG(LANGNAME) +# ------------------ +m4_defun([_LT_LANG], +[m4_ifdef([_LT_LANG_]$1[_enabled], [], + [LT_SUPPORTED_TAG([$1])dnl + m4_append([_LT_TAGS], [$1 ])dnl + m4_define([_LT_LANG_]$1[_enabled], [])dnl + _LT_LANG_$1_CONFIG($1)])dnl +])# _LT_LANG + + +m4_ifndef([AC_PROG_GO], [ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_GO. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +m4_defun([AC_PROG_GO], +[AC_LANG_PUSH(Go)dnl +AC_ARG_VAR([GOC], [Go compiler command])dnl +AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl +_AC_ARG_VAR_LDFLAGS()dnl +AC_CHECK_TOOL(GOC, gccgo) +if test -z "$GOC"; then + if test -n "$ac_tool_prefix"; then + AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo]) + fi +fi +if test -z "$GOC"; then + AC_CHECK_PROG(GOC, gccgo, gccgo, false) +fi +])#m4_defun +])#m4_ifndef + + +# _LT_LANG_DEFAULT_CONFIG +# ----------------------- +m4_defun([_LT_LANG_DEFAULT_CONFIG], +[AC_PROVIDE_IFELSE([AC_PROG_CXX], + [LT_LANG(CXX)], + [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])]) + +AC_PROVIDE_IFELSE([AC_PROG_F77], + [LT_LANG(F77)], + [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])]) + +AC_PROVIDE_IFELSE([AC_PROG_FC], + [LT_LANG(FC)], + [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])]) + +dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal +dnl pulling things in needlessly. +AC_PROVIDE_IFELSE([AC_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([LT_PROG_GCJ], + [LT_LANG(GCJ)], + [m4_ifdef([AC_PROG_GCJ], + [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([A][M_PROG_GCJ], + [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([LT_PROG_GCJ], + [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])]) + +AC_PROVIDE_IFELSE([AC_PROG_GO], + [LT_LANG(GO)], + [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])]) + +AC_PROVIDE_IFELSE([LT_PROG_RC], + [LT_LANG(RC)], + [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])]) +])# _LT_LANG_DEFAULT_CONFIG + +# Obsolete macros: +AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)]) +AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)]) +AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)]) +AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)]) +AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_CXX], []) +dnl AC_DEFUN([AC_LIBTOOL_F77], []) +dnl AC_DEFUN([AC_LIBTOOL_FC], []) +dnl AC_DEFUN([AC_LIBTOOL_GCJ], []) +dnl AC_DEFUN([AC_LIBTOOL_RC], []) + + +# _LT_TAG_COMPILER +# ---------------- +m4_defun([_LT_TAG_COMPILER], +[AC_REQUIRE([AC_PROG_CC])dnl + +_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl +_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl +_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl +_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC +])# _LT_TAG_COMPILER + + +# _LT_COMPILER_BOILERPLATE +# ------------------------ +# Check for compiler boilerplate output or warnings with +# the simple compiler test code. +m4_defun([_LT_COMPILER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* +])# _LT_COMPILER_BOILERPLATE + + +# _LT_LINKER_BOILERPLATE +# ---------------------- +# Check for linker boilerplate output or warnings with +# the simple link test code. +m4_defun([_LT_LINKER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* +])# _LT_LINKER_BOILERPLATE + +# _LT_REQUIRED_DARWIN_CHECKS +# ------------------------- +m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[ + case $host_os in + rhapsody* | darwin*) + AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:]) + AC_CHECK_TOOL([NMEDIT], [nmedit], [:]) + AC_CHECK_TOOL([LIPO], [lipo], [:]) + AC_CHECK_TOOL([OTOOL], [otool], [:]) + AC_CHECK_TOOL([OTOOL64], [otool64], [:]) + _LT_DECL([], [DSYMUTIL], [1], + [Tool to manipulate archived DWARF debug symbol files on Mac OS X]) + _LT_DECL([], [NMEDIT], [1], + [Tool to change global to local symbols on Mac OS X]) + _LT_DECL([], [LIPO], [1], + [Tool to manipulate fat objects and archives on Mac OS X]) + _LT_DECL([], [OTOOL], [1], + [ldd/readelf like tool for Mach-O binaries on Mac OS X]) + _LT_DECL([], [OTOOL64], [1], + [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4]) + + AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod], + [lt_cv_apple_cc_single_mod=no + if test -z "$LT_MULTI_MODULE"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test 0 = "$_lt_result"; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi]) + + AC_CACHE_CHECK([for -exported_symbols_list linker flag], + [lt_cv_ld_exported_symbols_list], + [lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [lt_cv_ld_exported_symbols_list=yes], + [lt_cv_ld_exported_symbols_list=no]) + LDFLAGS=$save_LDFLAGS + ]) + + AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load], + [lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD + echo "$AR cru libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD + $AR cru libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD + echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD + $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + ]) + case $host_os in + rhapsody* | darwin1.[[012]]) + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[[91]]*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + 10.[[012]][[,.]]*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test yes = "$lt_cv_apple_cc_single_mod"; then + _lt_dar_single_mod='$single_module' + fi + if test yes = "$lt_cv_ld_exported_symbols_list"; then + _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' + fi + if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac +]) + + +# _LT_DARWIN_LINKER_FEATURES([TAG]) +# --------------------------------- +# Checks for linker and compiler features on darwin +m4_defun([_LT_DARWIN_LINKER_FEATURES], +[ + m4_require([_LT_REQUIRED_DARWIN_CHECKS]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_automatic, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + if test yes = "$lt_cv_ld_force_load"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes], + [FC], [_LT_TAGVAR(compiler_needs_object, $1)=yes]) + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='' + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + m4_if([$1], [CXX], +[ if test yes != "$lt_cv_apple_cc_single_mod"; then + _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" + fi +],[]) + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi +]) + +# _LT_SYS_MODULE_PATH_AIX([TAGNAME]) +# ---------------------------------- +# Links a minimal program and checks the executable +# for the system default hardcoded library path. In most cases, +# this is /usr/lib:/lib, but when the MPI compilers are used +# the location of the communication and MPI libs are included too. +# If we don't find anything, use the default library path according +# to the aix ld manual. +# Store the results from the different compilers for each TAGNAME. +# Allow to override them for all tags through lt_cv_aix_libpath. +m4_defun([_LT_SYS_MODULE_PATH_AIX], +[m4_require([_LT_DECL_SED])dnl +if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])], + [AC_LINK_IFELSE([AC_LANG_PROGRAM],[ + lt_aix_libpath_sed='[ + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }]' + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi],[]) + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=/usr/lib:/lib + fi + ]) + aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1]) +fi +])# _LT_SYS_MODULE_PATH_AIX + + +# _LT_SHELL_INIT(ARG) +# ------------------- +m4_define([_LT_SHELL_INIT], +[m4_divert_text([M4SH-INIT], [$1 +])])# _LT_SHELL_INIT + + + +# _LT_PROG_ECHO_BACKSLASH +# ----------------------- +# Find how we can fake an echo command that does not interpret backslash. +# In particular, with Autoconf 2.60 or later we add some code to the start +# of the generated configure script that will find a shell with a builtin +# printf (that we can use as an echo command). +m4_defun([_LT_PROG_ECHO_BACKSLASH], +[ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +AC_MSG_CHECKING([how to print strings]) +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$[]1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "$*" +} + +case $ECHO in + printf*) AC_MSG_RESULT([printf]) ;; + print*) AC_MSG_RESULT([print -r]) ;; + *) AC_MSG_RESULT([cat]) ;; +esac + +m4_ifdef([_AS_DETECT_SUGGESTED], +[_AS_DETECT_SUGGESTED([ + test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test "X`printf %s $ECHO`" = "X$ECHO" \ + || test "X`print -r -- $ECHO`" = "X$ECHO" )])]) + +_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts]) +_LT_DECL([], [ECHO], [1], [An echo program that protects backslashes]) +])# _LT_PROG_ECHO_BACKSLASH + + +# _LT_WITH_SYSROOT +# ---------------- +AC_DEFUN([_LT_WITH_SYSROOT], +[AC_MSG_CHECKING([for sysroot]) +AC_ARG_WITH([sysroot], +[AS_HELP_STRING([--with-sysroot@<:@=DIR@:>@], + [Search for dependent libraries within DIR (or the compiler's sysroot + if not specified).])], +[], [with_sysroot=no]) + +dnl lt_sysroot will always be passed unquoted. We quote it here +dnl in case the user passed a directory name. +lt_sysroot= +case $with_sysroot in #( + yes) + if test yes = "$GCC"; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + AC_MSG_RESULT([$with_sysroot]) + AC_MSG_ERROR([The sysroot must be an absolute path.]) + ;; +esac + + AC_MSG_RESULT([${lt_sysroot:-no}]) +_LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl +[dependent libraries, and where our libraries should be installed.])]) + +# _LT_ENABLE_LOCK +# --------------- +m4_defun([_LT_ENABLE_LOCK], +[AC_ARG_ENABLE([libtool-lock], + [AS_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test no = "$enable_libtool_lock" || enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out what ABI is being produced by ac_compile, and set mode + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE=32 + ;; + *ELF-64*) + HPUX_IA64_MODE=64 + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + if test yes = "$lt_cv_prog_gnu_ld"; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +mips64*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + emul=elf + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + emul="${emul}32" + ;; + *64-bit*) + emul="${emul}64" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *MSB*) + emul="${emul}btsmip" + ;; + *LSB*) + emul="${emul}ltsmip" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *N32*) + emul="${emul}n32" + ;; + esac + LD="${LD-ld} -m $emul" + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. Note that the listed cases only cover the + # situations where additional linker options are needed (such as when + # doing 32-bit compilation for a host where ld defaults to 64-bit, or + # vice versa); the common cases where no linker options are needed do + # not appear in the list. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + case `/usr/bin/file conftest.o` in + *x86-64*) + LD="${LD-ld} -m elf32_x86_64" + ;; + *) + LD="${LD-ld} -m elf_i386" + ;; + esac + ;; + powerpc64le-*linux*) + LD="${LD-ld} -m elf32lppclinux" + ;; + powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + powerpcle-*linux*) + LD="${LD-ld} -m elf64lppc" + ;; + powerpc-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -belf" + AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, + [AC_LANG_PUSH(C) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) + AC_LANG_POP]) + if test yes != "$lt_cv_cc_needs_belf"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS=$SAVE_CFLAGS + fi + ;; +*-*solaris*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*|x86_64-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD=${LD-ld}_sol2 + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks=$enable_libtool_lock +])# _LT_ENABLE_LOCK + + +# _LT_PROG_AR +# ----------- +m4_defun([_LT_PROG_AR], +[AC_CHECK_TOOLS(AR, [ar], false) +: ${AR=ar} +: ${AR_FLAGS=cru} +_LT_DECL([], [AR], [1], [The archiver]) +_LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive]) + +AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file], + [lt_cv_ar_at_file=no + AC_COMPILE_IFELSE([AC_LANG_PROGRAM], + [echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD' + AC_TRY_EVAL([lt_ar_try]) + if test 0 -eq "$ac_status"; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + AC_TRY_EVAL([lt_ar_try]) + if test 0 -ne "$ac_status"; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + ]) + ]) + +if test no = "$lt_cv_ar_at_file"; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi +_LT_DECL([], [archiver_list_spec], [1], + [How to feed a file listing to the archiver]) +])# _LT_PROG_AR + + +# _LT_CMD_OLD_ARCHIVE +# ------------------- +m4_defun([_LT_CMD_OLD_ARCHIVE], +[_LT_PROG_AR + +AC_CHECK_TOOL(STRIP, strip, :) +test -z "$STRIP" && STRIP=: +_LT_DECL([], [STRIP], [1], [A symbol stripping program]) + +AC_CHECK_TOOL(RANLIB, ranlib, :) +test -z "$RANLIB" && RANLIB=: +_LT_DECL([], [RANLIB], [1], + [Commands used to install an old-style archive]) + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + bitrig* | openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac +_LT_DECL([], [old_postinstall_cmds], [2]) +_LT_DECL([], [old_postuninstall_cmds], [2]) +_LT_TAGDECL([], [old_archive_cmds], [2], + [Commands used to build an old-style archive]) +_LT_DECL([], [lock_old_archive_extraction], [0], + [Whether to use a lock for old archive extraction]) +])# _LT_CMD_OLD_ARCHIVE + + +# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------------------- +# Check whether the given compiler option works +AC_DEFUN([_LT_COMPILER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$3" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + fi + $RM conftest* +]) + +if test yes = "[$]$2"; then + m4_if([$5], , :, [$5]) +else + m4_if([$6], , :, [$6]) +fi +])# _LT_COMPILER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], []) + + +# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------- +# Check whether the given linker option works +AC_DEFUN([_LT_LINKER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $3" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&AS_MESSAGE_LOG_FD + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + else + $2=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS +]) + +if test yes = "[$]$2"; then + m4_if([$4], , :, [$4]) +else + m4_if([$5], , :, [$5]) +fi +])# _LT_LINKER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], []) + + +# LT_CMD_MAX_LEN +#--------------- +AC_DEFUN([LT_CMD_MAX_LEN], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +# find the maximum length of command line arguments +AC_MSG_CHECKING([the maximum length of command line arguments]) +AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl + i=0 + teststring=ABCD + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[ ]]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len" && \ + test undefined != "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test X`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test 17 != "$i" # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac +]) +if test -n "$lt_cv_sys_max_cmd_len"; then + AC_MSG_RESULT($lt_cv_sys_max_cmd_len) +else + AC_MSG_RESULT(none) +fi +max_cmd_len=$lt_cv_sys_max_cmd_len +_LT_DECL([], [max_cmd_len], [0], + [What is the maximum length of a command?]) +])# LT_CMD_MAX_LEN + +# Old name: +AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], []) + + +# _LT_HEADER_DLFCN +# ---------------- +m4_defun([_LT_HEADER_DLFCN], +[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl +])# _LT_HEADER_DLFCN + + +# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, +# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) +# ---------------------------------------------------------------- +m4_defun([_LT_TRY_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes = "$cross_compiling"; then : + [$4] +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +[#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +}] +_LT_EOF + if AC_TRY_EVAL(ac_link) && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) $1 ;; + x$lt_dlneed_uscore) $2 ;; + x$lt_dlunknown|x*) $3 ;; + esac + else : + # compilation failed + $3 + fi +fi +rm -fr conftest* +])# _LT_TRY_DLOPEN_SELF + + +# LT_SYS_DLOPEN_SELF +# ------------------ +AC_DEFUN([LT_SYS_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes != "$enable_dlopen"; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen=load_add_on + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen=LoadLibrary + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl],[ + lt_cv_dlopen=dyld + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ]) + ;; + + tpf*) + # Don't try to run any link tests for TPF. We know it's impossible + # because TPF is a cross-compiler, and we know how we open DSOs. + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + lt_cv_dlopen_self=no + ;; + + *) + AC_CHECK_FUNC([shl_load], + [lt_cv_dlopen=shl_load], + [AC_CHECK_LIB([dld], [shl_load], + [lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld], + [AC_CHECK_FUNC([dlopen], + [lt_cv_dlopen=dlopen], + [AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl], + [AC_CHECK_LIB([svld], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld], + [AC_CHECK_LIB([dld], [dld_link], + [lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld]) + ]) + ]) + ]) + ]) + ]) + ;; + esac + + if test no = "$lt_cv_dlopen"; then + enable_dlopen=no + else + enable_dlopen=yes + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS=$CPPFLAGS + test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS=$LDFLAGS + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS=$LIBS + LIBS="$lt_cv_dlopen_libs $LIBS" + + AC_CACHE_CHECK([whether a program can dlopen itself], + lt_cv_dlopen_self, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, + lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) + ]) + + if test yes = "$lt_cv_dlopen_self"; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + AC_CACHE_CHECK([whether a statically linked program can dlopen itself], + lt_cv_dlopen_self_static, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, + lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) + ]) + fi + + CPPFLAGS=$save_CPPFLAGS + LDFLAGS=$save_LDFLAGS + LIBS=$save_LIBS + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi +_LT_DECL([dlopen_support], [enable_dlopen], [0], + [Whether dlopen is supported]) +_LT_DECL([dlopen_self], [enable_dlopen_self], [0], + [Whether dlopen of programs is supported]) +_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0], + [Whether dlopen of statically linked programs is supported]) +])# LT_SYS_DLOPEN_SELF + +# Old name: +AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], []) + + +# _LT_COMPILER_C_O([TAGNAME]) +# --------------------------- +# Check to see if options -c and -o are simultaneously supported by compiler. +# This macro does not hard code the compiler like AC_PROG_CC_C_O. +m4_defun([_LT_COMPILER_C_O], +[m4_require([_LT_DECL_SED])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + fi + fi + chmod u+w . 2>&AS_MESSAGE_LOG_FD + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* +]) +_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1], + [Does compiler simultaneously support -c and -o options?]) +])# _LT_COMPILER_C_O + + +# _LT_COMPILER_FILE_LOCKS([TAGNAME]) +# ---------------------------------- +# Check to see if we can do hard links to lock some files if needed +m4_defun([_LT_COMPILER_FILE_LOCKS], +[m4_require([_LT_ENABLE_LOCK])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_COMPILER_C_O([$1]) + +hard_links=nottested +if test no = "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + AC_MSG_CHECKING([if we can lock with hard links]) + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + AC_MSG_RESULT([$hard_links]) + if test no = "$hard_links"; then + AC_MSG_WARN(['$CC' does not support '-c -o', so 'make -j' may be unsafe]) + need_locks=warn + fi +else + need_locks=no +fi +_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?]) +])# _LT_COMPILER_FILE_LOCKS + + +# _LT_CHECK_OBJDIR +# ---------------- +m4_defun([_LT_CHECK_OBJDIR], +[AC_CACHE_CHECK([for objdir], [lt_cv_objdir], +[rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null]) +objdir=$lt_cv_objdir +_LT_DECL([], [objdir], [0], + [The name of the directory that contains temporary libtool files])dnl +m4_pattern_allow([LT_OBJDIR])dnl +AC_DEFINE_UNQUOTED([LT_OBJDIR], "$lt_cv_objdir/", + [Define to the sub-directory where libtool stores uninstalled libraries.]) +])# _LT_CHECK_OBJDIR + + +# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME]) +# -------------------------------------- +# Check hardcoding attributes. +m4_defun([_LT_LINKER_HARDCODE_LIBPATH], +[AC_MSG_CHECKING([how to hardcode library paths into programs]) +_LT_TAGVAR(hardcode_action, $1)= +if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" || + test -n "$_LT_TAGVAR(runpath_var, $1)" || + test yes = "$_LT_TAGVAR(hardcode_automatic, $1)"; then + + # We can hardcode non-existent directories. + if test no != "$_LT_TAGVAR(hardcode_direct, $1)" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" && + test no != "$_LT_TAGVAR(hardcode_minus_L, $1)"; then + # Linking always hardcodes the temporary library directory. + _LT_TAGVAR(hardcode_action, $1)=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + _LT_TAGVAR(hardcode_action, $1)=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + _LT_TAGVAR(hardcode_action, $1)=unsupported +fi +AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)]) + +if test relink = "$_LT_TAGVAR(hardcode_action, $1)" || + test yes = "$_LT_TAGVAR(inherit_rpath, $1)"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi +_LT_TAGDECL([], [hardcode_action], [0], + [How to hardcode a shared library path into an executable]) +])# _LT_LINKER_HARDCODE_LIBPATH + + +# _LT_CMD_STRIPLIB +# ---------------- +m4_defun([_LT_CMD_STRIPLIB], +[m4_require([_LT_DECL_EGREP]) +striplib= +old_striplib= +AC_MSG_CHECKING([whether stripping libraries is possible]) +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + AC_MSG_RESULT([yes]) +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP"; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac +fi +_LT_DECL([], [old_striplib], [1], [Commands to strip libraries]) +_LT_DECL([], [striplib], [1]) +])# _LT_CMD_STRIPLIB + + +# _LT_PREPARE_MUNGE_PATH_LIST +# --------------------------- +# Make sure func_munge_path_list() is defined correctly. +m4_defun([_LT_PREPARE_MUNGE_PATH_LIST], +[[# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x@S|@2 in + x) + ;; + *:) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'` \@S|@@S|@1\" + ;; + x:*) + eval @S|@1=\"\@S|@@S|@1 `$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval @S|@1=\"\@S|@@S|@1\ `$ECHO @S|@2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval @S|@1=\"`$ECHO @S|@2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \@S|@@S|@1\" + ;; + *) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + esac +} +]])# _LT_PREPARE_PATH_LIST + + +# _LT_SYS_DYNAMIC_LINKER([TAG]) +# ----------------------------- +# PORTME Fill in your ld.so characteristics +m4_defun([_LT_SYS_DYNAMIC_LINKER], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_OBJDUMP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PREPARE_MUNGE_PATH_LIST])dnl +AC_MSG_CHECKING([dynamic linker characteristics]) +m4_if([$1], + [], [ +if test yes = "$GCC"; then + case $host_os in + darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; + *) lt_awk_arg='/^libraries:/' ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq='s|=\([[A-Za-z]]:\)|\1|g' ;; + *) lt_sed_strip_eq='s|=/|/|g' ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary... + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + # ...but if some path component already ends with the multilib dir we assume + # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). + case "$lt_multi_os_dir; $lt_search_path_spec " in + "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) + lt_multi_os_dir= + ;; + esac + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" + elif test -n "$lt_multi_os_dir"; then + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS = " "; FS = "/|\n";} { + lt_foo = ""; + lt_count = 0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo = "/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[[lt_foo]]++; } + if (lt_freq[[lt_foo]] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's|/\([[A-Za-z]]:\)|\1|g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi]) +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +AC_ARG_VAR([LT_SYS_LIBRARY_PATH], +[User-defined run-time library search path.]) + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[[4-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[[01]] | aix4.[[01]].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a[(]lib.so.V[)]' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)], lib.a[(]lib.so.V[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a[(]lib.so.V[)], lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[[45]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"]) + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"]) + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[[23]].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[[01]]* | freebsdelf3.[[01]]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \ + freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[[3-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath], + [lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \ + LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\"" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null], + [lt_cv_shlibpath_overrides_runpath=yes])]) + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + ]) + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsdelf*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='NetBSD ld.elf_so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +AC_MSG_RESULT([$dynamic_linker]) +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + +_LT_DECL([], [variables_saved_for_relink], [1], + [Variables whose values should be saved in libtool wrapper scripts and + restored at link time]) +_LT_DECL([], [need_lib_prefix], [0], + [Do we need the "lib" prefix for modules?]) +_LT_DECL([], [need_version], [0], [Do we need a version for libraries?]) +_LT_DECL([], [version_type], [0], [Library versioning type]) +_LT_DECL([], [runpath_var], [0], [Shared library runtime path variable]) +_LT_DECL([], [shlibpath_var], [0],[Shared library path variable]) +_LT_DECL([], [shlibpath_overrides_runpath], [0], + [Is shlibpath searched before the hard-coded library search path?]) +_LT_DECL([], [libname_spec], [1], [Format of library name prefix]) +_LT_DECL([], [library_names_spec], [1], + [[List of archive names. First name is the real one, the rest are links. + The last name is the one that the linker finds with -lNAME]]) +_LT_DECL([], [soname_spec], [1], + [[The coded name of the library, if different from the real name]]) +_LT_DECL([], [install_override_mode], [1], + [Permission mode override for installation of shared libraries]) +_LT_DECL([], [postinstall_cmds], [2], + [Command to use after installation of a shared archive]) +_LT_DECL([], [postuninstall_cmds], [2], + [Command to use after uninstallation of a shared archive]) +_LT_DECL([], [finish_cmds], [2], + [Commands used to finish a libtool library installation in a directory]) +_LT_DECL([], [finish_eval], [1], + [[As "finish_cmds", except a single script fragment to be evaled but + not shown]]) +_LT_DECL([], [hardcode_into_libs], [0], + [Whether we should hardcode library paths into libraries]) +_LT_DECL([], [sys_lib_search_path_spec], [2], + [Compile-time system search path for libraries]) +_LT_DECL([sys_lib_dlsearch_path_spec], [configure_time_dlsearch_path], [2], + [Detected run-time system search path for libraries]) +_LT_DECL([], [configure_time_lt_sys_library_path], [2], + [Explicit LT_SYS_LIBRARY_PATH set during ./configure time]) +])# _LT_SYS_DYNAMIC_LINKER + + +# _LT_PATH_TOOL_PREFIX(TOOL) +# -------------------------- +# find a file program that can recognize shared library +AC_DEFUN([_LT_PATH_TOOL_PREFIX], +[m4_require([_LT_DECL_EGREP])dnl +AC_MSG_CHECKING([for $1]) +AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, +[case $MAGIC_CMD in +[[\\/*] | ?:[\\/]*]) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR +dnl $ac_dummy forces splitting on constant user-supplied paths. +dnl POSIX.2 word splitting is done only on the output of word expansions, +dnl not every word. This closes a longstanding sh security hole. + ac_dummy="m4_if([$2], , $PATH, [$2])" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$1"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"$1" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac]) +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + AC_MSG_RESULT($MAGIC_CMD) +else + AC_MSG_RESULT(no) +fi +_LT_DECL([], [MAGIC_CMD], [0], + [Used to examine libraries when file_magic_cmd begins with "file"])dnl +])# _LT_PATH_TOOL_PREFIX + +# Old name: +AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], []) + + +# _LT_PATH_MAGIC +# -------------- +# find a file program that can recognize a shared library +m4_defun([_LT_PATH_MAGIC], +[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) + else + MAGIC_CMD=: + fi +fi +])# _LT_PATH_MAGIC + + +# LT_PATH_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([LT_PATH_LD], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PROG_ECHO_BACKSLASH])dnl + +AC_ARG_WITH([gnu-ld], + [AS_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test no = "$withval" || with_gnu_ld=yes], + [with_gnu_ld=no])dnl + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &1 conftest.i +cat conftest.i conftest.i >conftest2.i +: ${lt_DD:=$DD} +AC_PATH_PROGS_FEATURE_CHECK([lt_DD], [dd], +[if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: +fi]) +rm -f conftest.i conftest2.i conftest.out]) +])# _LT_PATH_DD + + +# _LT_CMD_TRUNCATE +# ---------------- +# find command to truncate a binary pipe +m4_defun([_LT_CMD_TRUNCATE], +[m4_require([_LT_PATH_DD]) +AC_CACHE_CHECK([how to truncate binary pipes], [lt_cv_truncate_bin], +[printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +lt_cv_truncate_bin= +if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" +fi +rm -f conftest.i conftest2.i conftest.out +test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q"]) +_LT_DECL([lt_truncate_bin], [lt_cv_truncate_bin], [1], + [Command to truncate a binary pipe]) +])# _LT_CMD_TRUNCATE + + +# _LT_CHECK_MAGIC_METHOD +# ---------------------- +# how to check for library dependencies +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_MAGIC_METHOD], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +AC_CACHE_CHECK([how to recognize dependent libraries], +lt_cv_deplibs_check_method, +[lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# 'unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# that responds to the $file_magic_cmd with a given extended regex. +# If you have 'file' or equivalent on your system and you're not sure +# whether 'pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[[4-9]]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[[45]]*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'] + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[[3-9]]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd* | bitrig*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +os2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac +]) + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + +_LT_DECL([], [deplibs_check_method], [1], + [Method to check whether dependent libraries are shared objects]) +_LT_DECL([], [file_magic_cmd], [1], + [Command to use when deplibs_check_method = "file_magic"]) +_LT_DECL([], [file_magic_glob], [1], + [How to find potential files when deplibs_check_method = "file_magic"]) +_LT_DECL([], [want_nocaseglob], [1], + [Find potential files using nocaseglob when deplibs_check_method = "file_magic"]) +])# _LT_CHECK_MAGIC_METHOD + + +# LT_PATH_NM +# ---------- +# find the pathname to a BSD- or MS-compatible name lister +AC_DEFUN([LT_PATH_NM], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM, +[if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM=$NM +else + lt_nm_to_check=${ac_tool_prefix}nm + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + tmp_nm=$ac_dir/$lt_tmp_nm + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the 'sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty + case $build_os in + mingw*) lt_bad_file=conftest.nm/nofile ;; + *) lt_bad_file=/dev/null ;; + esac + case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in + *$lt_bad_file* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break 2 + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break 2 + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS=$lt_save_ifs + done + : ${lt_cv_path_NM=no} +fi]) +if test no != "$lt_cv_path_NM"; then + NM=$lt_cv_path_NM +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :) + case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols -headers" + ;; + *) + DUMPBIN=: + ;; + esac + fi + AC_SUBST([DUMPBIN]) + if test : != "$DUMPBIN"; then + NM=$DUMPBIN + fi +fi +test -z "$NM" && NM=nm +AC_SUBST([NM]) +_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl + +AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface], + [lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD) + cat conftest.out >&AS_MESSAGE_LOG_FD + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest*]) +])# LT_PATH_NM + +# Old names: +AU_ALIAS([AM_PROG_NM], [LT_PATH_NM]) +AU_ALIAS([AC_PROG_NM], [LT_PATH_NM]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_PROG_NM], []) +dnl AC_DEFUN([AC_PROG_NM], []) + +# _LT_CHECK_SHAREDLIB_FROM_LINKLIB +# -------------------------------- +# how to determine the name of the shared library +# associated with a specific link library. +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +m4_require([_LT_DECL_DLLTOOL]) +AC_CACHE_CHECK([how to associate runtime and link libraries], +lt_cv_sharedlib_from_linklib_cmd, +[lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh; + # decide which one to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd=$ECHO + ;; +esac +]) +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + +_LT_DECL([], [sharedlib_from_linklib_cmd], [1], + [Command to associate shared and link libraries]) +])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB + + +# _LT_PATH_MANIFEST_TOOL +# ---------------------- +# locate the manifest tool +m4_defun([_LT_PATH_MANIFEST_TOOL], +[AC_CHECK_TOOL(MANIFEST_TOOL, mt, :) +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool], + [lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&AS_MESSAGE_LOG_FD + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest*]) +if test yes != "$lt_cv_path_mainfest_tool"; then + MANIFEST_TOOL=: +fi +_LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl +])# _LT_PATH_MANIFEST_TOOL + + +# _LT_DLL_DEF_P([FILE]) +# --------------------- +# True iff FILE is a Windows DLL '.def' file. +# Keep in sync with func_dll_def_p in the libtool script +AC_DEFUN([_LT_DLL_DEF_P], +[dnl + test DEF = "`$SED -n dnl + -e '\''s/^[[ ]]*//'\'' dnl Strip leading whitespace + -e '\''/^\(;.*\)*$/d'\'' dnl Delete empty lines and comments + -e '\''s/^\(EXPORTS\|LIBRARY\)\([[ ]].*\)*$/DEF/p'\'' dnl + -e q dnl Only consider the first "real" line + $1`" dnl +])# _LT_DLL_DEF_P + + +# LT_LIB_M +# -------- +# check for math library +AC_DEFUN([LT_LIB_M], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM=-lmw) + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM=-lm) + ;; +esac +AC_SUBST([LIBM]) +])# LT_LIB_M + +# Old name: +AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_CHECK_LIBM], []) + + +# _LT_COMPILER_NO_RTTI([TAGNAME]) +# ------------------------------- +m4_defun([_LT_COMPILER_NO_RTTI], +[m4_require([_LT_TAG_COMPILER])dnl + +_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + +if test yes = "$GCC"; then + case $cc_basename in + nvcc*) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;; + *) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;; + esac + + _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], + lt_cv_prog_compiler_rtti_exceptions, + [-fno-rtti -fno-exceptions], [], + [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) +fi +_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1], + [Compiler flag to turn off builtin functions]) +])# _LT_COMPILER_NO_RTTI + + +# _LT_CMD_GLOBAL_SYMBOLS +# ---------------------- +m4_defun([_LT_CMD_GLOBAL_SYMBOLS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([LT_PATH_NM])dnl +AC_REQUIRE([LT_PATH_LD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_TAG_COMPILER])dnl + +# Check for command to grab the raw symbol name followed by C symbol from nm. +AC_MSG_CHECKING([command to parse $NM output from $compiler object]) +AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], +[ +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[[BCDEGRST]]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[[BCDT]]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[[ABCDGISTW]]' + ;; +hpux*) + if test ia64 = "$host_cpu"; then + symcode='[[ABCDEGRST]]' + fi + ;; +irix* | nonstopux*) + symcode='[[BCDEGRST]]' + ;; +osf*) + symcode='[[BCDEGQRST]]' + ;; +solaris*) + symcode='[[BDRT]]' + ;; +sco3.2v5*) + symcode='[[DT]]' + ;; +sysv4.2uw2*) + symcode='[[DT]]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[[ABDT]]' + ;; +sysv4) + symcode='[[DFNSTU]]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[[ABCDGIRSTW]]' ;; +esac + +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Gets list of data symbols to import. + lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" + # Adjust the below global symbol transforms to fixup imported variables. + lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" + lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" + lt_c_name_lib_hook="\ + -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ + -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" +else + # Disable hooks by default. + lt_cv_sys_global_symbol_to_import= + lt_cdecl_hook= + lt_c_name_hook= + lt_c_name_lib_hook= +fi + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n"\ +$lt_cdecl_hook\ +" -e 's/^T .* \(.*\)$/extern int \1();/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ +$lt_c_name_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" + +# Transform an extracted symbol line into symbol name with lib prefix and +# symbol address. +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ +$lt_c_name_lib_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function, + # D for any global variable and I for any imported variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK ['"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ +" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ +" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ +" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ +" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx]" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if AC_TRY_EVAL(ac_compile); then + # Now try to grab the symbols. + nlist=conftest.nm + $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&AS_MESSAGE_LOG_FD + if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&AS_MESSAGE_LOG_FD && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT@&t@_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT@&t@_DLSYM_CONST +#else +# define LT@&t@_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT@&t@_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[[]] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS=conftstm.$ac_objext + CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" + if AC_TRY_EVAL(ac_link) && test -s conftest$ac_exeext; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD + fi + else + echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test yes = "$pipe_works"; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done +]) +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + AC_MSG_RESULT(failed) +else + AC_MSG_RESULT(ok) +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + +_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1], + [Take the output of nm and produce a listing of raw symbols and C names]) +_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1], + [Transform the output of nm in a proper C declaration]) +_LT_DECL([global_symbol_to_import], [lt_cv_sys_global_symbol_to_import], [1], + [Transform the output of nm into a list of symbols to manually relocate]) +_LT_DECL([global_symbol_to_c_name_address], + [lt_cv_sys_global_symbol_to_c_name_address], [1], + [Transform the output of nm in a C name address pair]) +_LT_DECL([global_symbol_to_c_name_address_lib_prefix], + [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1], + [Transform the output of nm in a C name address pair when lib prefix is needed]) +_LT_DECL([nm_interface], [lt_cv_nm_interface], [1], + [The name lister interface]) +_LT_DECL([], [nm_file_list_spec], [1], + [Specify filename containing input files for $NM]) +]) # _LT_CMD_GLOBAL_SYMBOLS + + +# _LT_COMPILER_PIC([TAGNAME]) +# --------------------------- +m4_defun([_LT_COMPILER_PIC], +[m4_require([_LT_TAG_COMPILER])dnl +_LT_TAGVAR(lt_prog_compiler_wl, $1)= +_LT_TAGVAR(lt_prog_compiler_pic, $1)= +_LT_TAGVAR(lt_prog_compiler_static, $1)= + +m4_if([$1], [CXX], [ + # C++ specific cases for pic, static, wl, etc. + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + case $host_os in + aix[[4-9]]*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + dgux*) + case $cc_basename in + ec++*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + fi + ;; + aCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # KAI C++ Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + ecpc* ) + # old Intel C++ for x86_64, which still supported -KPIC. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + icpc* ) + # Intel C++, used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*) + # IBM XL 8.0, 9.0 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd* | netbsdelf*-gnu) + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + cxx*) + # Digital/Compaq C++ + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + lcc*) + # Lucid + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + *) + ;; + esac + ;; + vxworks*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +], +[ + if test yes = "$GCC"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker ' + if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + case $cc_basename in + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + hpux9* | hpux10* | hpux11*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC (with -KPIC) is the default. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + # old Intel for x86_64, which still supported -KPIC. + ecc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # flang / f18. f95 an alias for gfortran or flang on Debian + flang* | f18* | f95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared' + _LT_TAGVAR(lt_prog_compiler_static, $1)='--static' + ;; + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + ccc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All Alpha code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='' + ;; + *Sun\ F* | *Sun*Fortran*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + ;; + *Intel*\ [[CF]]*Compiler*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + *Portland\ Group*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All OSF/1 code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + rdos*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + solaris*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';; + *) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';; + esac + ;; + + sunos4*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + unicos*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + + uts4*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +]) +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])" + ;; +esac + +AC_CACHE_CHECK([for $compiler option to produce PIC], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)]) +_LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1) + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works], + [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)], + [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [], + [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;; + esac], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) +fi +_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1], + [Additional compiler flags for building library objects]) + +_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1], + [How to pass a linker flag through the compiler]) +# +# Check to make sure the static flag actually works. +# +wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\" +_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works], + _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1), + $lt_tmp_static_flag, + [], + [_LT_TAGVAR(lt_prog_compiler_static, $1)=]) +_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1], + [Compiler flag to prevent dynamic linking]) +])# _LT_COMPILER_PIC + + +# _LT_LINKER_SHLIBS([TAGNAME]) +# ---------------------------- +# See if the linker supports building shared libraries. +m4_defun([_LT_LINKER_SHLIBS], +[AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +m4_if([$1], [CXX], [ + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + case $host_os in + aix[[4-9]]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + _LT_TAGVAR(export_symbols_cmds, $1)=$ltdll_cmds + ;; + cygwin* | mingw* | cegcc*) + case $cc_basename in + cl*) + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + ;; + esac + ;; + linux* | k*bsd*-gnu | gnu*) + _LT_TAGVAR(link_all_deplibs, $1)=no + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac +], [ + runpath_var= + _LT_TAGVAR(allow_undefined_flag, $1)= + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(archive_cmds, $1)= + _LT_TAGVAR(archive_expsym_cmds, $1)= + _LT_TAGVAR(compiler_needs_object, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(hardcode_automatic, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(hardcode_libdir_separator, $1)= + _LT_TAGVAR(hardcode_minus_L, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_TAGVAR(inherit_rpath, $1)=no + _LT_TAGVAR(link_all_deplibs, $1)=unknown + _LT_TAGVAR(module_cmds, $1)= + _LT_TAGVAR(module_expsym_cmds, $1)= + _LT_TAGVAR(old_archive_from_new_cmds, $1)= + _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)= + _LT_TAGVAR(thread_safe_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + _LT_TAGVAR(include_expsyms, $1)= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ' (' and ')$', so one must not match beginning or + # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', + # as well as any symbol that contains 'd'. + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. +dnl Note also adjust exclude_expsyms for C++ above. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test yes != "$GCC"; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd* | bitrig*) + with_gnu_ld=no + ;; + linux* | k*bsd*-gnu | gnu*) + _LT_TAGVAR(link_all_deplibs, $1)=no + ;; + esac + + _LT_TAGVAR(ld_shlibs, $1)=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test yes = "$with_gnu_ld"; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;; + *\ \(GNU\ Binutils\)\ [[3-9]]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test yes = "$lt_use_gnu_ld_interface"; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='$wl' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + supports_anon_versioning=no + case `$LD -v | $SED -e 's/([^)]\+)\s\+//' 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[[3-9]]*) + # On AIX/PPC, the GNU linker is very broken + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test linux-dietlibc = "$host_os"; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test no = "$tmp_diet" + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + _LT_TAGVAR(whole_archive_flag_spec, $1)= + tmp_sharedflag='--shared' ;; + nagfor*) # NAGFOR 5.3 + tmp_sharedflag='-Wl,-shared' ;; + xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + tcc*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-rdynamic' + ;; + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*) + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + sunos4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + + if test no = "$_LT_TAGVAR(ld_shlibs, $1)"; then + runpath_var= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + _LT_TAGVAR(hardcode_direct, $1)=unsupported + fi + ;; + + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then + aix_use_runtimelinking=yes + break + fi + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # traditional, no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GCC"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + ;; + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag="$shared_flag "'$wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared libraries. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + bsdi[[45]]*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + # FIXME: Should let the user specify the lib program. + _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + esac + ;; + + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + dgux*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + hpux9*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + + hpux10*) + if test yes,no = "$GCC,$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + fi + ;; + + hpux11*) + if test yes,no = "$GCC,$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + m4_if($1, [], [ + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + _LT_LINKER_OPTION([if $CC understands -b], + _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'], + [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags']) + ;; + esac + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol], + [lt_cv_irix_exported_symbol], + [save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" + AC_LINK_IFELSE( + [AC_LANG_SOURCE( + [AC_LANG_CASE([C], [[int foo (void) { return 0; }]], + [C++], [[int foo (void) { return 0; }]], + [Fortran 77], [[ + subroutine foo + end]], + [Fortran], [[ + subroutine foo + end]])])], + [lt_cv_irix_exported_symbol=yes], + [lt_cv_irix_exported_symbol=no]) + LDFLAGS=$save_LDFLAGS]) + if test yes = "$lt_cv_irix_exported_symbol"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' + fi + _LT_TAGVAR(link_all_deplibs, $1)=no + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + linux*) + case $cc_basename in + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(ld_shlibs, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + newsos6) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *nto* | *qnx*) + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + fi + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + osf3*) + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + solaris*) + _LT_TAGVAR(no_undefined_flag, $1)=' -z defs' + if test yes = "$GCC"; then + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + _LT_TAGVAR(archive_cmds, $1)='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. GCC discards it without '$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test yes = "$GCC"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + fi + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + sunos4*) + if test sequent = "$host_vendor"; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4) + case $host_vendor in + sni) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' + _LT_TAGVAR(hardcode_direct, $1)=no + ;; + motorola) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4.3*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + _LT_TAGVAR(ld_shlibs, $1)=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + if test sni = "$host_vendor"; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Blargedynsym' + ;; + esac + fi + fi +]) +AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) +test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + +_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld + +_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl +_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl +_LT_DECL([], [extract_expsyms_cmds], [2], + [The commands to extract the exported symbol list from a shared archive]) + +# +# Do we need to explicitly link libc? +# +case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in +x|xyes) + # Assume -lc should be added + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $_LT_TAGVAR(archive_cmds, $1) in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + AC_CACHE_CHECK([whether -lc should be explicitly linked in], + [lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1), + [$RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if AC_TRY_EVAL(ac_compile) 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) + pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1) + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1) + _LT_TAGVAR(allow_undefined_flag, $1)= + if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) + then + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no + else + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes + fi + _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + ]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1) + ;; + esac + fi + ;; +esac + +_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0], + [Whether or not to add -lc for building shared libraries]) +_LT_TAGDECL([allow_libtool_libs_with_static_runtimes], + [enable_shared_with_static_runtimes], [0], + [Whether or not to disallow shared libs when runtime libs are static]) +_LT_TAGDECL([], [export_dynamic_flag_spec], [1], + [Compiler flag to allow reflexive dlopens]) +_LT_TAGDECL([], [whole_archive_flag_spec], [1], + [Compiler flag to generate shared objects directly from archives]) +_LT_TAGDECL([], [compiler_needs_object], [1], + [Whether the compiler copes with passing no objects directly]) +_LT_TAGDECL([], [old_archive_from_new_cmds], [2], + [Create an old-style archive from a shared archive]) +_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2], + [Create a temporary old-style archive to link instead of a shared archive]) +_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive]) +_LT_TAGDECL([], [archive_expsym_cmds], [2]) +_LT_TAGDECL([], [module_cmds], [2], + [Commands used to build a loadable module if different from building + a shared archive.]) +_LT_TAGDECL([], [module_expsym_cmds], [2]) +_LT_TAGDECL([], [with_gnu_ld], [1], + [Whether we are building with GNU ld or not]) +_LT_TAGDECL([], [allow_undefined_flag], [1], + [Flag that allows shared libraries with undefined symbols to be built]) +_LT_TAGDECL([], [no_undefined_flag], [1], + [Flag that enforces no undefined symbols]) +_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1], + [Flag to hardcode $libdir into a binary during linking. + This must work even if $libdir does not exist]) +_LT_TAGDECL([], [hardcode_libdir_separator], [1], + [Whether we need a single "-rpath" flag with a separated argument]) +_LT_TAGDECL([], [hardcode_direct], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary]) +_LT_TAGDECL([], [hardcode_direct_absolute], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary and the resulting library dependency is + "absolute", i.e impossible to change by setting $shlibpath_var if the + library is relocated]) +_LT_TAGDECL([], [hardcode_minus_L], [0], + [Set to "yes" if using the -LDIR flag during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_shlibpath_var], [0], + [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_automatic], [0], + [Set to "yes" if building a shared library automatically hardcodes DIR + into the library and all subsequent libraries and executables linked + against it]) +_LT_TAGDECL([], [inherit_rpath], [0], + [Set to yes if linker adds runtime paths of dependent libraries + to runtime path list]) +_LT_TAGDECL([], [link_all_deplibs], [0], + [Whether libtool must link a program against all its dependency libraries]) +_LT_TAGDECL([], [always_export_symbols], [0], + [Set to "yes" if exported symbols are required]) +_LT_TAGDECL([], [export_symbols_cmds], [2], + [The commands to list exported symbols]) +_LT_TAGDECL([], [exclude_expsyms], [1], + [Symbols that should not be listed in the preloaded symbols]) +_LT_TAGDECL([], [include_expsyms], [1], + [Symbols that must always be exported]) +_LT_TAGDECL([], [prelink_cmds], [2], + [Commands necessary for linking programs (against libraries) with templates]) +_LT_TAGDECL([], [postlink_cmds], [2], + [Commands necessary for finishing linking programs]) +_LT_TAGDECL([], [file_list_spec], [1], + [Specify filename containing input files]) +dnl FIXME: Not yet implemented +dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1], +dnl [Compiler flag to generate thread safe objects]) +])# _LT_LINKER_SHLIBS + + +# _LT_LANG_C_CONFIG([TAG]) +# ------------------------ +# Ensure that the configuration variables for a C compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_C_CONFIG], +[m4_require([_LT_DECL_EGREP])dnl +lt_save_CC=$CC +AC_LANG_PUSH(C) + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + +_LT_TAG_COMPILER +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + LT_SYS_DLOPEN_SELF + _LT_CMD_STRIPLIB + + # Report what library types will actually be built + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_CONFIG($1) +fi +AC_LANG_POP +CC=$lt_save_CC +])# _LT_LANG_C_CONFIG + + +# _LT_LANG_CXX_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a C++ compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_CXX_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +if test -n "$CXX" && ( test no != "$CXX" && + ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || + (test g++ != "$CXX"))); then + AC_PROG_CXXCPP +else + _lt_caught_CXX_error=yes +fi + +AC_LANG_PUSH(C++) +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(compiler_needs_object, $1)=no +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the CXX compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_caught_CXX_error"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="int some_variable = 0;" + + # Code to be used in simple link tests + lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }' + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_CFLAGS=$CFLAGS + lt_save_LD=$LD + lt_save_GCC=$GCC + GCC=$GXX + lt_save_with_gnu_ld=$with_gnu_ld + lt_save_path_LD=$lt_cv_path_LD + if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx + else + $as_unset lt_cv_prog_gnu_ld + fi + if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX + else + $as_unset lt_cv_path_LD + fi + test -z "${LDCXX+set}" || LD=$LDCXX + CC=${CXX-"c++"} + CFLAGS=$CXXFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + # We don't want -fno-exception when compiling C++ code, so set the + # no_builtin_flag separately + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + else + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + fi + + if test yes = "$GXX"; then + # Set up default GNU C++ configuration + + LT_PATH_LD + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test yes = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='$wl' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | + $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + + else + GXX=no + with_gnu_ld=no + wlarc= + fi + + # PORTME: fill in a description of your system's C++ link characteristics + AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) + _LT_TAGVAR(ld_shlibs, $1)=yes + case $host_os in + aix3*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GXX"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag=$shared_flag' $wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to + # export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + # The "-G" linker flag allows undefined symbols. + _LT_TAGVAR(no_undefined_flag, $1)='-bernotok' + # Determine the default libpath from the value encoded in an empty + # executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared + # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + cygwin* | mingw* | pw32* | cegcc*) + case $GXX,$cc_basename in + ,cl* | no,cl*) + # Native MSVC + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + func_to_tool_file "$lt_outputfile"~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # g++ + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + freebsd2.*) + # C++ shared libraries reported to be fairly broken before + # switch to ELF + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + freebsd-elf*) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + hpux9*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + hpux10*|hpux11*) + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' + fi + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + esac + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc* | ecpc* ) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + case `$CC -V` in + *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*) + _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ + compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' + _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ + $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ + $RANLIB $oldlib' + _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 6 and above use weak symbols + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl--rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + ;; + cxx*) + # Compaq C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' + ;; + xl* | mpixl* | bgxl*) + # IBM XL 8.0 on PPC, with GNU ld + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + + lynxos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + m88k*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + + *nto* | *qnx*) + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + fi + output_verbose_link_cmd=func_echo_all + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + case $host in + osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; + *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;; + esac + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + cxx*) + case $host in + osf3*) + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + ;; + *) + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ + $RM $lib.exp' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + case $host in + osf3*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + psos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(archive_cmds_need_lc,$1)=yes + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-z ${wl}defs' + if $CC --version | $GREP -v '^2\.7' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + else + # g++ 2.7 appears to require '-G' NOT '-shared' on this + # platform. + _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + fi + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $wl$libdir' + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~ + '"$_LT_TAGVAR(old_archive_cmds, $1)" + _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~ + '"$_LT_TAGVAR(reload_cmds, $1)" + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + vxworks*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) + test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + + _LT_TAGVAR(GCC, $1)=$GXX + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS + LDCXX=$LD + LD=$lt_save_LD + GCC=$lt_save_GCC + with_gnu_ld=$lt_save_with_gnu_ld + lt_cv_path_LDCXX=$lt_cv_path_LD + lt_cv_path_LD=$lt_save_path_LD + lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld + lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +fi # test yes != "$_lt_caught_CXX_error" + +AC_LANG_POP +])# _LT_LANG_CXX_CONFIG + + +# _LT_FUNC_STRIPNAME_CNF +# ---------------------- +# func_stripname_cnf prefix suffix name +# strip PREFIX and SUFFIX off of NAME. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +# +# This function is identical to the (non-XSI) version of func_stripname, +# except this one can be used by m4 code that may be executed by configure, +# rather than the libtool script. +m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl +AC_REQUIRE([_LT_DECL_SED]) +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH]) +func_stripname_cnf () +{ + case @S|@2 in + .*) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%\\\\@S|@2\$%%"`;; + *) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%@S|@2\$%%"`;; + esac +} # func_stripname_cnf +])# _LT_FUNC_STRIPNAME_CNF + + +# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME]) +# --------------------------------- +# Figure out "hidden" library dependencies from verbose +# compiler output when linking a shared library. +# Parse the compiler output and extract the necessary +# objects, libraries and library flags. +m4_defun([_LT_SYS_HIDDEN_LIBDEPS], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl +# Dependencies to place before and after the object being linked: +_LT_TAGVAR(predep_objects, $1)= +_LT_TAGVAR(postdep_objects, $1)= +_LT_TAGVAR(predeps, $1)= +_LT_TAGVAR(postdeps, $1)= +_LT_TAGVAR(compiler_lib_search_path, $1)= + +dnl we can't use the lt_simple_compile_test_code here, +dnl because it contains code intended for an executable, +dnl not a library. It's possible we should let each +dnl tag define a new lt_????_link_test_code variable, +dnl but it's only used here... +m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF +int a; +void foo (void) { a = 0; } +_LT_EOF +], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF +class Foo +{ +public: + Foo (void) { a = 0; } +private: + int a; +}; +_LT_EOF +], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer*4 a + a=0 + return + end +_LT_EOF +], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer a + a=0 + return + end +_LT_EOF +], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF +public class foo { + private int a; + public void bar (void) { + a = 0; + } +}; +_LT_EOF +], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF +package foo +func foo() { +} +_LT_EOF +]) + +_lt_libdeps_save_CFLAGS=$CFLAGS +case "$CC $CFLAGS " in #( +*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; +*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; +*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; +esac + +dnl Parse the compiler output and extract the necessary +dnl objects, libraries and library flags. +if AC_TRY_EVAL(ac_compile); then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + for p in `eval "$output_verbose_link_cmd"`; do + case $prev$p in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test x-L = "$p" || + test x-R = "$p"; then + prev=$p + continue + fi + + # Expand the sysroot to ease extracting the directories later. + if test -z "$prev"; then + case $p in + -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; + -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; + -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; + esac + fi + case $p in + =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; + esac + if test no = "$pre_test_object_deps_done"; then + case $prev in + -L | -R) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then + _LT_TAGVAR(compiler_lib_search_path, $1)=$prev$p + else + _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} $prev$p" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$_LT_TAGVAR(postdeps, $1)"; then + _LT_TAGVAR(postdeps, $1)=$prev$p + else + _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} $prev$p" + fi + fi + prev= + ;; + + *.lto.$objext) ;; # Ignore GCC LTO objects + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test no = "$pre_test_object_deps_done"; then + if test -z "$_LT_TAGVAR(predep_objects, $1)"; then + _LT_TAGVAR(predep_objects, $1)=$p + else + _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p" + fi + else + if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then + _LT_TAGVAR(postdep_objects, $1)=$p + else + _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling $1 test program" +fi + +$RM -f confest.$objext +CFLAGS=$_lt_libdeps_save_CFLAGS + +# PORTME: override above test on systems where it is broken +m4_if([$1], [CXX], +[case $host_os in +interix[[3-9]]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + _LT_TAGVAR(predep_objects,$1)= + _LT_TAGVAR(postdep_objects,$1)= + _LT_TAGVAR(postdeps,$1)= + ;; +esac +]) + +case " $_LT_TAGVAR(postdeps, $1) " in +*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; +esac + _LT_TAGVAR(compiler_lib_search_dirs, $1)= +if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then + _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | $SED -e 's! -L! !g' -e 's!^ !!'` +fi +_LT_TAGDECL([], [compiler_lib_search_dirs], [1], + [The directories searched by this compiler when creating a shared library]) +_LT_TAGDECL([], [predep_objects], [1], + [Dependencies to place before and after the objects being linked to + create a shared library]) +_LT_TAGDECL([], [postdep_objects], [1]) +_LT_TAGDECL([], [predeps], [1]) +_LT_TAGDECL([], [postdeps], [1]) +_LT_TAGDECL([], [compiler_lib_search_path], [1], + [The library search path used internally by the compiler when linking + a shared library]) +])# _LT_SYS_HIDDEN_LIBDEPS + + +# _LT_LANG_F77_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a Fortran 77 compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_F77_CONFIG], +[AC_LANG_PUSH(Fortran 77) +if test -z "$F77" || test no = "$F77"; then + _lt_disable_F77=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for f77 test sources. +ac_ext=f + +# Object file extension for compiled f77 test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the F77 compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_F77"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${F77-"f77"} + CFLAGS=$FFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + GCC=$G77 + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$G77 + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_F77" + +AC_LANG_POP +])# _LT_LANG_F77_CONFIG + + +# _LT_LANG_FC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for a Fortran compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_FC_CONFIG], +[AC_LANG_PUSH(Fortran) + +if test -z "$FC" || test no = "$FC"; then + _lt_disable_FC=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for fc test sources. +ac_ext=${ac_fc_srcext-f} + +# Object file extension for compiled fc test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the FC compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_FC"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${FC-"f95"} + CFLAGS=$FCFLAGS + compiler=$CC + GCC=$ac_cv_fc_compiler_gnu + + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$ac_cv_fc_compiler_gnu + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_FC" + +AC_LANG_POP +])# _LT_LANG_FC_CONFIG + + +# _LT_LANG_GCJ_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Java Compiler compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GCJ_CONFIG], +[AC_REQUIRE([LT_PROG_GCJ])dnl +AC_LANG_SAVE + +# Source file extension for Java test sources. +ac_ext=java + +# Object file extension for compiled Java test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="class foo {}" + +# Code to be used in simple link tests +lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GCJ-"gcj"} +CFLAGS=$GCJFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# GCJ did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GCJ_CONFIG + + +# _LT_LANG_GO_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Go compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GO_CONFIG], +[AC_REQUIRE([LT_PROG_GO])dnl +AC_LANG_SAVE + +# Source file extension for Go test sources. +ac_ext=go + +# Object file extension for compiled Go test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="package main; func main() { }" + +# Code to be used in simple link tests +lt_simple_link_test_code='package main; func main() { }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GOC-"gccgo"} +CFLAGS=$GOFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# Go did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GO_CONFIG + + +# _LT_LANG_RC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for the Windows resource compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_RC_CONFIG], +[AC_REQUIRE([LT_PROG_RC])dnl +AC_LANG_SAVE + +# Source file extension for RC test sources. +ac_ext=rc + +# Object file extension for compiled RC test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' + +# Code to be used in simple link tests +lt_simple_link_test_code=$lt_simple_compile_test_code + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC= +CC=${RC-"windres"} +CFLAGS= +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) +_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + +if test -n "$compiler"; then + : + _LT_CONFIG($1) +fi + +GCC=$lt_save_GCC +AC_LANG_RESTORE +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_RC_CONFIG + + +# LT_PROG_GCJ +# ----------- +AC_DEFUN([LT_PROG_GCJ], +[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ], + [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ], + [AC_CHECK_TOOL(GCJ, gcj,) + test set = "${GCJFLAGS+set}" || GCJFLAGS="-g -O2" + AC_SUBST(GCJFLAGS)])])[]dnl +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_GCJ], []) + + +# LT_PROG_GO +# ---------- +AC_DEFUN([LT_PROG_GO], +[AC_CHECK_TOOL(GOC, gccgo,) +]) + + +# LT_PROG_RC +# ---------- +AC_DEFUN([LT_PROG_RC], +[AC_CHECK_TOOL(RC, windres,) +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_RC], []) + + +# _LT_DECL_EGREP +# -------------- +# If we don't have a new enough Autoconf to choose the best grep +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_EGREP], +[AC_REQUIRE([AC_PROG_EGREP])dnl +AC_REQUIRE([AC_PROG_FGREP])dnl +test -z "$GREP" && GREP=grep +_LT_DECL([], [GREP], [1], [A grep program that handles long lines]) +_LT_DECL([], [EGREP], [1], [An ERE matcher]) +_LT_DECL([], [FGREP], [1], [A literal string matcher]) +dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too +AC_SUBST([GREP]) +]) + + +# _LT_DECL_OBJDUMP +# -------------- +# If we don't have a new enough Autoconf to choose the best objdump +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_OBJDUMP], +[AC_CHECK_TOOL(OBJDUMP, objdump, false) +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper]) +AC_SUBST([OBJDUMP]) +]) + +# _LT_DECL_DLLTOOL +# ---------------- +# Ensure DLLTOOL variable is set. +m4_defun([_LT_DECL_DLLTOOL], +[AC_CHECK_TOOL(DLLTOOL, dlltool, false) +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program]) +AC_SUBST([DLLTOOL]) +]) + +# _LT_DECL_SED +# ------------ +# Check for a fully-functional sed program, that truncates +# as few characters as possible. Prefer GNU sed if found. +m4_defun([_LT_DECL_SED], +[AC_PROG_SED +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" +_LT_DECL([], [SED], [1], [A sed program that does not truncate output]) +_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"], + [Sed that helps us avoid accidentally triggering echo(1) options like -n]) +])# _LT_DECL_SED + +m4_ifndef([AC_PROG_SED], [ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_SED. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # + +m4_defun([AC_PROG_SED], +[AC_MSG_CHECKING([for a sed that does not truncate output]) +AC_CACHE_VAL(lt_cv_path_SED, +[# Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +IFS=$as_save_IFS +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f "$lt_ac_sed" && continue + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test 10 -lt "$lt_ac_count" && break + lt_ac_count=`expr $lt_ac_count + 1` + if test "$lt_ac_count" -gt "$lt_ac_max"; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done +]) +SED=$lt_cv_path_SED +AC_SUBST([SED]) +AC_MSG_RESULT([$SED]) +])#AC_PROG_SED +])#m4_ifndef + +# Old name: +AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_SED], []) + + +# _LT_CHECK_SHELL_FEATURES +# ------------------------ +# Find out whether the shell is Bourne or XSI compatible, +# or has some other useful features. +m4_defun([_LT_CHECK_SHELL_FEATURES], +[if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi +_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac +_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl +_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl +])# _LT_CHECK_SHELL_FEATURES + + +# _LT_PATH_CONVERSION_FUNCTIONS +# ----------------------------- +# Determine what file name conversion functions should be used by +# func_to_host_file (and, implicitly, by func_to_host_path). These are needed +# for certain cross-compile configurations and native mingw. +m4_defun([_LT_PATH_CONVERSION_FUNCTIONS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_MSG_CHECKING([how to convert $build file names to $host format]) +AC_CACHE_VAL(lt_cv_to_host_file_cmd, +[case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac +]) +to_host_file_cmd=$lt_cv_to_host_file_cmd +AC_MSG_RESULT([$lt_cv_to_host_file_cmd]) +_LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd], + [0], [convert $build file names to $host format])dnl + +AC_MSG_CHECKING([how to convert $build file names to toolchain format]) +AC_CACHE_VAL(lt_cv_to_tool_file_cmd, +[#assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac +]) +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +AC_MSG_RESULT([$lt_cv_to_tool_file_cmd]) +_LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd], + [0], [convert $build files to toolchain format])dnl +])# _LT_PATH_CONVERSION_FUNCTIONS + +# Helper functions for option handling. -*- Autoconf -*- +# +# Copyright (C) 2004-2005, 2007-2009, 2011-2015 Free Software +# Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 8 ltoptions.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])]) + + +# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME) +# ------------------------------------------ +m4_define([_LT_MANGLE_OPTION], +[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])]) + + +# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME) +# --------------------------------------- +# Set option OPTION-NAME for macro MACRO-NAME, and if there is a +# matching handler defined, dispatch to it. Other OPTION-NAMEs are +# saved as a flag. +m4_define([_LT_SET_OPTION], +[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl +m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]), + _LT_MANGLE_DEFUN([$1], [$2]), + [m4_warning([Unknown $1 option '$2'])])[]dnl +]) + + +# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET]) +# ------------------------------------------------------------ +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +m4_define([_LT_IF_OPTION], +[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])]) + + +# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET) +# ------------------------------------------------------- +# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME +# are set. +m4_define([_LT_UNLESS_OPTIONS], +[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option), + [m4_define([$0_found])])])[]dnl +m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3 +])[]dnl +]) + + +# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST) +# ---------------------------------------- +# OPTION-LIST is a space-separated list of Libtool options associated +# with MACRO-NAME. If any OPTION has a matching handler declared with +# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about +# the unknown option and exit. +m4_defun([_LT_SET_OPTIONS], +[# Set options +m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [_LT_SET_OPTION([$1], _LT_Option)]) + +m4_if([$1],[LT_INIT],[ + dnl + dnl Simply set some default values (i.e off) if boolean options were not + dnl specified: + _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no + ]) + _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no + ]) + dnl + dnl If no reference was made to various pairs of opposing options, then + dnl we run the default mode handler for the pair. For example, if neither + dnl 'shared' nor 'disable-shared' was passed, we enable building of shared + dnl archives by default: + _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED]) + _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install], + [_LT_ENABLE_FAST_INSTALL]) + _LT_UNLESS_OPTIONS([LT_INIT], [aix-soname=aix aix-soname=both aix-soname=svr4], + [_LT_WITH_AIX_SONAME([aix])]) + ]) +])# _LT_SET_OPTIONS + + + +# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME) +# ----------------------------------------- +m4_define([_LT_MANGLE_DEFUN], +[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])]) + + +# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE) +# ----------------------------------------------- +m4_define([LT_OPTION_DEFINE], +[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl +])# LT_OPTION_DEFINE + + +# dlopen +# ------ +LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes +]) + +AU_DEFUN([AC_LIBTOOL_DLOPEN], +[_LT_SET_OPTION([LT_INIT], [dlopen]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'dlopen' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], []) + + +# win32-dll +# --------- +# Declare package support for building win32 dll's. +LT_OPTION_DEFINE([LT_INIT], [win32-dll], +[enable_win32_dll=yes + +case $host in +*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*) + AC_CHECK_TOOL(AS, as, false) + AC_CHECK_TOOL(DLLTOOL, dlltool, false) + AC_CHECK_TOOL(OBJDUMP, objdump, false) + ;; +esac + +test -z "$AS" && AS=as +_LT_DECL([], [AS], [1], [Assembler program])dnl + +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl + +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl +])# win32-dll + +AU_DEFUN([AC_LIBTOOL_WIN32_DLL], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +_LT_SET_OPTION([LT_INIT], [win32-dll]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'win32-dll' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], []) + + +# _LT_ENABLE_SHARED([DEFAULT]) +# ---------------------------- +# implement the --enable-shared flag, and supports the 'shared' and +# 'disable-shared' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_SHARED], +[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([shared], + [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@], + [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_shared=]_LT_ENABLE_SHARED_DEFAULT) + + _LT_DECL([build_libtool_libs], [enable_shared], [0], + [Whether or not to build shared libraries]) +])# _LT_ENABLE_SHARED + +LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared]) +]) + +AC_DEFUN([AC_DISABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], [disable-shared]) +]) + +AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) +AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_SHARED], []) +dnl AC_DEFUN([AM_DISABLE_SHARED], []) + + + +# _LT_ENABLE_STATIC([DEFAULT]) +# ---------------------------- +# implement the --enable-static flag, and support the 'static' and +# 'disable-static' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_STATIC], +[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([static], + [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@], + [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_static=]_LT_ENABLE_STATIC_DEFAULT) + + _LT_DECL([build_old_libs], [enable_static], [0], + [Whether or not to build static libraries]) +])# _LT_ENABLE_STATIC + +LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static]) +]) + +AC_DEFUN([AC_DISABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], [disable-static]) +]) + +AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) +AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_STATIC], []) +dnl AC_DEFUN([AM_DISABLE_STATIC], []) + + + +# _LT_ENABLE_FAST_INSTALL([DEFAULT]) +# ---------------------------------- +# implement the --enable-fast-install flag, and support the 'fast-install' +# and 'disable-fast-install' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_FAST_INSTALL], +[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([fast-install], + [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], + [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT) + +_LT_DECL([fast_install], [enable_fast_install], [0], + [Whether or not to optimize for fast installation])dnl +])# _LT_ENABLE_FAST_INSTALL + +LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])]) + +# Old names: +AU_DEFUN([AC_ENABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the 'fast-install' option into LT_INIT's first parameter.]) +]) + +AU_DEFUN([AC_DISABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], [disable-fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the 'disable-fast-install' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], []) +dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], []) + + +# _LT_WITH_AIX_SONAME([DEFAULT]) +# ---------------------------------- +# implement the --with-aix-soname flag, and support the `aix-soname=aix' +# and `aix-soname=both' and `aix-soname=svr4' LT_INIT options. DEFAULT +# is either `aix', `both' or `svr4'. If omitted, it defaults to `aix'. +m4_define([_LT_WITH_AIX_SONAME], +[m4_define([_LT_WITH_AIX_SONAME_DEFAULT], [m4_if($1, svr4, svr4, m4_if($1, both, both, aix))])dnl +shared_archive_member_spec= +case $host,$enable_shared in +power*-*-aix[[5-9]]*,yes) + AC_MSG_CHECKING([which variant of shared library versioning to provide]) + AC_ARG_WITH([aix-soname], + [AS_HELP_STRING([--with-aix-soname=aix|svr4|both], + [shared library versioning (aka "SONAME") variant to provide on AIX, @<:@default=]_LT_WITH_AIX_SONAME_DEFAULT[@:>@.])], + [case $withval in + aix|svr4|both) + ;; + *) + AC_MSG_ERROR([Unknown argument to --with-aix-soname]) + ;; + esac + lt_cv_with_aix_soname=$with_aix_soname], + [AC_CACHE_VAL([lt_cv_with_aix_soname], + [lt_cv_with_aix_soname=]_LT_WITH_AIX_SONAME_DEFAULT) + with_aix_soname=$lt_cv_with_aix_soname]) + AC_MSG_RESULT([$with_aix_soname]) + if test aix != "$with_aix_soname"; then + # For the AIX way of multilib, we name the shared archive member + # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', + # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. + # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, + # the AIX toolchain works better with OBJECT_MODE set (default 32). + if test 64 = "${OBJECT_MODE-32}"; then + shared_archive_member_spec=shr_64 + else + shared_archive_member_spec=shr + fi + fi + ;; +*) + with_aix_soname=aix + ;; +esac + +_LT_DECL([], [shared_archive_member_spec], [0], + [Shared archive member basename, for filename based shared library versioning on AIX])dnl +])# _LT_WITH_AIX_SONAME + +LT_OPTION_DEFINE([LT_INIT], [aix-soname=aix], [_LT_WITH_AIX_SONAME([aix])]) +LT_OPTION_DEFINE([LT_INIT], [aix-soname=both], [_LT_WITH_AIX_SONAME([both])]) +LT_OPTION_DEFINE([LT_INIT], [aix-soname=svr4], [_LT_WITH_AIX_SONAME([svr4])]) + + +# _LT_WITH_PIC([MODE]) +# -------------------- +# implement the --with-pic flag, and support the 'pic-only' and 'no-pic' +# LT_INIT options. +# MODE is either 'yes' or 'no'. If omitted, it defaults to 'both'. +m4_define([_LT_WITH_PIC], +[AC_ARG_WITH([pic], + [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@], + [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], + [lt_p=${PACKAGE-default} + case $withval in + yes|no) pic_mode=$withval ;; + *) + pic_mode=default + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for lt_pkg in $withval; do + IFS=$lt_save_ifs + if test "X$lt_pkg" = "X$lt_p"; then + pic_mode=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [pic_mode=m4_default([$1], [default])]) + +_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl +])# _LT_WITH_PIC + +LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])]) + +# Old name: +AU_DEFUN([AC_LIBTOOL_PICMODE], +[_LT_SET_OPTION([LT_INIT], [pic-only]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'pic-only' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_PICMODE], []) + + +m4_define([_LTDL_MODE], []) +LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive], + [m4_define([_LTDL_MODE], [nonrecursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [recursive], + [m4_define([_LTDL_MODE], [recursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [subproject], + [m4_define([_LTDL_MODE], [subproject])]) + +m4_define([_LTDL_TYPE], []) +LT_OPTION_DEFINE([LTDL_INIT], [installable], + [m4_define([_LTDL_TYPE], [installable])]) +LT_OPTION_DEFINE([LTDL_INIT], [convenience], + [m4_define([_LTDL_TYPE], [convenience])]) + +# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*- +# +# Copyright (C) 2004-2005, 2007-2008, 2011-2015 Free Software +# Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 6 ltsugar.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])]) + + +# lt_join(SEP, ARG1, [ARG2...]) +# ----------------------------- +# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their +# associated separator. +# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier +# versions in m4sugar had bugs. +m4_define([lt_join], +[m4_if([$#], [1], [], + [$#], [2], [[$2]], + [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])]) +m4_define([_lt_join], +[m4_if([$#$2], [2], [], + [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])]) + + +# lt_car(LIST) +# lt_cdr(LIST) +# ------------ +# Manipulate m4 lists. +# These macros are necessary as long as will still need to support +# Autoconf-2.59, which quotes differently. +m4_define([lt_car], [[$1]]) +m4_define([lt_cdr], +[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])], + [$#], 1, [], + [m4_dquote(m4_shift($@))])]) +m4_define([lt_unquote], $1) + + +# lt_append(MACRO-NAME, STRING, [SEPARATOR]) +# ------------------------------------------ +# Redefine MACRO-NAME to hold its former content plus 'SEPARATOR''STRING'. +# Note that neither SEPARATOR nor STRING are expanded; they are appended +# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked). +# No SEPARATOR is output if MACRO-NAME was previously undefined (different +# than defined and empty). +# +# This macro is needed until we can rely on Autoconf 2.62, since earlier +# versions of m4sugar mistakenly expanded SEPARATOR but not STRING. +m4_define([lt_append], +[m4_define([$1], + m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])]) + + + +# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...]) +# ---------------------------------------------------------- +# Produce a SEP delimited list of all paired combinations of elements of +# PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list +# has the form PREFIXmINFIXSUFFIXn. +# Needed until we can rely on m4_combine added in Autoconf 2.62. +m4_define([lt_combine], +[m4_if(m4_eval([$# > 3]), [1], + [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl +[[m4_foreach([_Lt_prefix], [$2], + [m4_foreach([_Lt_suffix], + ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[, + [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])]) + + +# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ]) +# ----------------------------------------------------------------------- +# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited +# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ. +m4_define([lt_if_append_uniq], +[m4_ifdef([$1], + [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1], + [lt_append([$1], [$2], [$3])$4], + [$5])], + [lt_append([$1], [$2], [$3])$4])]) + + +# lt_dict_add(DICT, KEY, VALUE) +# ----------------------------- +m4_define([lt_dict_add], +[m4_define([$1($2)], [$3])]) + + +# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE) +# -------------------------------------------- +m4_define([lt_dict_add_subkey], +[m4_define([$1($2:$3)], [$4])]) + + +# lt_dict_fetch(DICT, KEY, [SUBKEY]) +# ---------------------------------- +m4_define([lt_dict_fetch], +[m4_ifval([$3], + m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]), + m4_ifdef([$1($2)], [m4_defn([$1($2)])]))]) + + +# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE]) +# ----------------------------------------------------------------- +m4_define([lt_if_dict_fetch], +[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4], + [$5], + [$6])]) + + +# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...]) +# -------------------------------------------------------------- +m4_define([lt_dict_filter], +[m4_if([$5], [], [], + [lt_join(m4_quote(m4_default([$4], [[, ]])), + lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]), + [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl +]) + +# ltversion.m4 -- version numbers -*- Autoconf -*- +# +# Copyright (C) 2004, 2011-2015 Free Software Foundation, Inc. +# Written by Scott James Remnant, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# @configure_input@ + +# serial 4179 ltversion.m4 +# This file is part of GNU Libtool + +m4_define([LT_PACKAGE_VERSION], [2.4.6]) +m4_define([LT_PACKAGE_REVISION], [2.4.6]) + +AC_DEFUN([LTVERSION_VERSION], +[macro_version='2.4.6' +macro_revision='2.4.6' +_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?]) +_LT_DECL(, macro_revision, 0) +]) + +# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*- +# +# Copyright (C) 2004-2005, 2007, 2009, 2011-2015 Free Software +# Foundation, Inc. +# Written by Scott James Remnant, 2004. +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 5 lt~obsolete.m4 + +# These exist entirely to fool aclocal when bootstrapping libtool. +# +# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN), +# which have later been changed to m4_define as they aren't part of the +# exported API, or moved to Autoconf or Automake where they belong. +# +# The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN +# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us +# using a macro with the same name in our local m4/libtool.m4 it'll +# pull the old libtool.m4 in (it doesn't see our shiny new m4_define +# and doesn't know about Autoconf macros at all.) +# +# So we provide this file, which has a silly filename so it's always +# included after everything else. This provides aclocal with the +# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything +# because those macros already exist, or will be overwritten later. +# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. +# +# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here. +# Yes, that means every name once taken will need to remain here until +# we give up compatibility with versions before 1.7, at which point +# we need to keep only those names which we still refer to. + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])]) + +m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])]) +m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])]) +m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])]) +m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])]) +m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])]) +m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])]) +m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])]) +m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])]) +m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])]) +m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])]) +m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])]) +m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])]) +m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])]) +m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])]) +m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])]) +m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])]) +m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])]) +m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])]) +m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) +m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])]) +m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])]) +m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])]) +m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])]) +m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])]) +m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])]) +m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])]) +m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])]) +m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])]) +m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])]) +m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])]) +m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])]) +m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])]) +m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])]) +m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])]) +m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])]) +m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])]) +m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])]) +m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])]) +m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])]) +m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])]) +m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])]) +m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])]) +m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])]) +m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])]) +m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])]) +m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])]) +m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])]) +m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])]) +m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])]) +m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])]) +m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])]) + +dnl pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +dnl serial 11 (pkg-config-0.29) +dnl +dnl Copyright © 2004 Scott James Remnant . +dnl Copyright © 2012-2015 Dan Nicholson +dnl +dnl This program is free software; you can redistribute it and/or modify +dnl it under the terms of the GNU General Public License as published by +dnl the Free Software Foundation; either version 2 of the License, or +dnl (at your option) any later version. +dnl +dnl This program is distributed in the hope that it will be useful, but +dnl WITHOUT ANY WARRANTY; without even the implied warranty of +dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +dnl General Public License for more details. +dnl +dnl You should have received a copy of the GNU General Public License +dnl along with this program; if not, write to the Free Software +dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +dnl 02111-1307, USA. +dnl +dnl As a special exception to the GNU General Public License, if you +dnl distribute this file as part of a program that contains a +dnl configuration script generated by Autoconf, you may include it under +dnl the same distribution terms that you use for the rest of that +dnl program. + +dnl PKG_PREREQ(MIN-VERSION) +dnl ----------------------- +dnl Since: 0.29 +dnl +dnl Verify that the version of the pkg-config macros are at least +dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's +dnl installed version of pkg-config, this checks the developer's version +dnl of pkg.m4 when generating configure. +dnl +dnl To ensure that this macro is defined, also add: +dnl m4_ifndef([PKG_PREREQ], +dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])]) +dnl +dnl See the "Since" comment for each macro you use to see what version +dnl of the macros you require. +m4_defun([PKG_PREREQ], +[m4_define([PKG_MACROS_VERSION], [0.29]) +m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1, + [m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])]) +])dnl PKG_PREREQ + +dnl PKG_PROG_PKG_CONFIG([MIN-VERSION]) +dnl ---------------------------------- +dnl Since: 0.16 +dnl +dnl Search for the pkg-config tool and set the PKG_CONFIG variable to +dnl first found in the path. Checks that the version of pkg-config found +dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is +dnl used since that's the first version where most current features of +dnl pkg-config existed. +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$]) +m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility]) +AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path]) +AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path]) + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi +fi[]dnl +])dnl PKG_PROG_PKG_CONFIG + +dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ------------------------------------------------------------------- +dnl Since: 0.18 +dnl +dnl Check to see whether a particular set of modules exists. Similar to +dnl PKG_CHECK_MODULES(), but does not set variables or print errors. +dnl +dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +dnl only at the first occurence in configure.ac, so if the first place +dnl it's called might be skipped (such as if it is within an "if", you +dnl have to call PKG_CHECK_EXISTS manually +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_default([$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + +dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +dnl --------------------------------------------- +dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting +dnl pkg_failed based on the result. +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes ], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])dnl _PKG_CONFIG + +dnl _PKG_SHORT_ERRORS_SUPPORTED +dnl --------------------------- +dnl Internal check to see if pkg-config supports short errors. +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])dnl _PKG_SHORT_ERRORS_SUPPORTED + + +dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +dnl [ACTION-IF-NOT-FOUND]) +dnl -------------------------------------------------------------- +dnl Since: 0.4.0 +dnl +dnl Note that if there is a possibility the first call to +dnl PKG_CHECK_MODULES might not happen, you should be sure to include an +dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + AC_MSG_RESULT([no]) + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + m4_default([$4], [AC_MSG_ERROR( +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT])[]dnl + ]) +elif test $pkg_failed = untried; then + AC_MSG_RESULT([no]) + m4_default([$4], [AC_MSG_FAILURE( +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])[]dnl + ]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + $3 +fi[]dnl +])dnl PKG_CHECK_MODULES + + +dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +dnl [ACTION-IF-NOT-FOUND]) +dnl --------------------------------------------------------------------- +dnl Since: 0.29 +dnl +dnl Checks for existence of MODULES and gathers its build flags with +dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags +dnl and VARIABLE-PREFIX_LIBS from --libs. +dnl +dnl Note that if there is a possibility the first call to +dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to +dnl include an explicit call to PKG_PROG_PKG_CONFIG in your +dnl configure.ac. +AC_DEFUN([PKG_CHECK_MODULES_STATIC], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +_save_PKG_CONFIG=$PKG_CONFIG +PKG_CONFIG="$PKG_CONFIG --static" +PKG_CHECK_MODULES($@) +PKG_CONFIG=$_save_PKG_CONFIG[]dnl +])dnl PKG_CHECK_MODULES_STATIC + + +dnl PKG_INSTALLDIR([DIRECTORY]) +dnl ------------------------- +dnl Since: 0.27 +dnl +dnl Substitutes the variable pkgconfigdir as the location where a module +dnl should install pkg-config .pc files. By default the directory is +dnl $libdir/pkgconfig, but the default can be changed by passing +dnl DIRECTORY. The user can override through the --with-pkgconfigdir +dnl parameter. +AC_DEFUN([PKG_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([pkgconfigdir], + [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],, + [with_pkgconfigdir=]pkg_default) +AC_SUBST([pkgconfigdir], [$with_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +])dnl PKG_INSTALLDIR + + +dnl PKG_NOARCH_INSTALLDIR([DIRECTORY]) +dnl -------------------------------- +dnl Since: 0.27 +dnl +dnl Substitutes the variable noarch_pkgconfigdir as the location where a +dnl module should install arch-independent pkg-config .pc files. By +dnl default the directory is $datadir/pkgconfig, but the default can be +dnl changed by passing DIRECTORY. The user can override through the +dnl --with-noarch-pkgconfigdir parameter. +AC_DEFUN([PKG_NOARCH_INSTALLDIR], +[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])]) +m4_pushdef([pkg_description], + [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@]) +AC_ARG_WITH([noarch-pkgconfigdir], + [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],, + [with_noarch_pkgconfigdir=]pkg_default) +AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir]) +m4_popdef([pkg_default]) +m4_popdef([pkg_description]) +])dnl PKG_NOARCH_INSTALLDIR + + +dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE, +dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ------------------------------------------- +dnl Since: 0.28 +dnl +dnl Retrieves the value of the pkg-config variable for the given module. +AC_DEFUN([PKG_CHECK_VAR], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl + +_PKG_CONFIG([$1], [variable="][$3]["], [$2]) +AS_VAR_COPY([$1], [pkg_cv_][$1]) + +AS_VAR_IF([$1], [""], [$5], [$4])dnl +])dnl PKG_CHECK_VAR + +# Copyright (C) 2002-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_AUTOMAKE_VERSION(VERSION) +# ---------------------------- +# Automake X.Y traces this macro to ensure aclocal.m4 has been +# generated from the m4 files accompanying Automake X.Y. +# (This private macro should not be called outside this file.) +AC_DEFUN([AM_AUTOMAKE_VERSION], +[am__api_version='1.16' +dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to +dnl require some minimum version. Point them to the right macro. +m4_if([$1], [1.16.1], [], + [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl +]) + +# _AM_AUTOCONF_VERSION(VERSION) +# ----------------------------- +# aclocal traces this macro to find the Autoconf version. +# This is a private macro too. Using m4_define simplifies +# the logic in aclocal, which can simply ignore this definition. +m4_define([_AM_AUTOCONF_VERSION], []) + +# AM_SET_CURRENT_AUTOMAKE_VERSION +# ------------------------------- +# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. +# This function is AC_REQUIREd by AM_INIT_AUTOMAKE. +AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], +[AM_AUTOMAKE_VERSION([1.16.1])dnl +m4_ifndef([AC_AUTOCONF_VERSION], + [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl +_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) + +# AM_AUX_DIR_EXPAND -*- Autoconf -*- + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets +# $ac_aux_dir to '$srcdir/foo'. In other projects, it is set to +# '$srcdir', '$srcdir/..', or '$srcdir/../..'. +# +# Of course, Automake must honor this variable whenever it calls a +# tool from the auxiliary directory. The problem is that $srcdir (and +# therefore $ac_aux_dir as well) can be either absolute or relative, +# depending on how configure is run. This is pretty annoying, since +# it makes $ac_aux_dir quite unusable in subdirectories: in the top +# source directory, any form will work fine, but in subdirectories a +# relative path needs to be adjusted first. +# +# $ac_aux_dir/missing +# fails when called from a subdirectory if $ac_aux_dir is relative +# $top_srcdir/$ac_aux_dir/missing +# fails if $ac_aux_dir is absolute, +# fails when called from a subdirectory in a VPATH build with +# a relative $ac_aux_dir +# +# The reason of the latter failure is that $top_srcdir and $ac_aux_dir +# are both prefixed by $srcdir. In an in-source build this is usually +# harmless because $srcdir is '.', but things will broke when you +# start a VPATH build or use an absolute $srcdir. +# +# So we could use something similar to $top_srcdir/$ac_aux_dir/missing, +# iff we strip the leading $srcdir from $ac_aux_dir. That would be: +# am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"` +# and then we would define $MISSING as +# MISSING="\${SHELL} $am_aux_dir/missing" +# This will work as long as MISSING is not called from configure, because +# unfortunately $(top_srcdir) has no meaning in configure. +# However there are other variables, like CC, which are often used in +# configure, and could therefore not use this "fixed" $ac_aux_dir. +# +# Another solution, used here, is to always expand $ac_aux_dir to an +# absolute PATH. The drawback is that using absolute paths prevent a +# configured tree to be moved without reconfiguration. + +AC_DEFUN([AM_AUX_DIR_EXPAND], +[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +# Expand $ac_aux_dir to an absolute path. +am_aux_dir=`cd "$ac_aux_dir" && pwd` +]) + +# AM_CONDITIONAL -*- Autoconf -*- + +# Copyright (C) 1997-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_CONDITIONAL(NAME, SHELL-CONDITION) +# ------------------------------------- +# Define a conditional. +AC_DEFUN([AM_CONDITIONAL], +[AC_PREREQ([2.52])dnl + m4_if([$1], [TRUE], [AC_FATAL([$0: invalid condition: $1])], + [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl +AC_SUBST([$1_TRUE])dnl +AC_SUBST([$1_FALSE])dnl +_AM_SUBST_NOTMAKE([$1_TRUE])dnl +_AM_SUBST_NOTMAKE([$1_FALSE])dnl +m4_define([_AM_COND_VALUE_$1], [$2])dnl +if $2; then + $1_TRUE= + $1_FALSE='#' +else + $1_TRUE='#' + $1_FALSE= +fi +AC_CONFIG_COMMANDS_PRE( +[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then + AC_MSG_ERROR([[conditional "$1" was never defined. +Usually this means the macro was only invoked conditionally.]]) +fi])]) + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + + +# There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be +# written in clear, in which case automake, when reading aclocal.m4, +# will think it sees a *use*, and therefore will trigger all it's +# C support machinery. Also note that it means that autoscan, seeing +# CC etc. in the Makefile, will ask for an AC_PROG_CC use... + + +# _AM_DEPENDENCIES(NAME) +# ---------------------- +# See how the compiler implements dependency checking. +# NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC". +# We try a few techniques and use that to set a single cache variable. +# +# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was +# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular +# dependency, and given that the user is not expected to run this macro, +# just rely on AC_PROG_CC. +AC_DEFUN([_AM_DEPENDENCIES], +[AC_REQUIRE([AM_SET_DEPDIR])dnl +AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl +AC_REQUIRE([AM_MAKE_INCLUDE])dnl +AC_REQUIRE([AM_DEP_TRACK])dnl + +m4_if([$1], [CC], [depcc="$CC" am_compiler_list=], + [$1], [CXX], [depcc="$CXX" am_compiler_list=], + [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'], + [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'], + [$1], [UPC], [depcc="$UPC" am_compiler_list=], + [$1], [GCJ], [depcc="$GCJ" am_compiler_list='gcc3 gcc'], + [depcc="$$1" am_compiler_list=]) + +AC_CACHE_CHECK([dependency style of $depcc], + [am_cv_$1_dependencies_compiler_type], +[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_$1_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp` + fi + am__universal=false + m4_case([$1], [CC], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac], + [CXX], + [case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac]) + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_$1_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_$1_dependencies_compiler_type=none +fi +]) +AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type]) +AM_CONDITIONAL([am__fastdep$1], [ + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_$1_dependencies_compiler_type" = gcc3]) +]) + + +# AM_SET_DEPDIR +# ------------- +# Choose a directory name for dependency files. +# This macro is AC_REQUIREd in _AM_DEPENDENCIES. +AC_DEFUN([AM_SET_DEPDIR], +[AC_REQUIRE([AM_SET_LEADING_DOT])dnl +AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl +]) + + +# AM_DEP_TRACK +# ------------ +AC_DEFUN([AM_DEP_TRACK], +[AC_ARG_ENABLE([dependency-tracking], [dnl +AS_HELP_STRING( + [--enable-dependency-tracking], + [do not reject slow dependency extractors]) +AS_HELP_STRING( + [--disable-dependency-tracking], + [speeds up one-time build])]) +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' + am__nodep='_no' +fi +AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) +AC_SUBST([AMDEPBACKSLASH])dnl +_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl +AC_SUBST([am__nodep])dnl +_AM_SUBST_NOTMAKE([am__nodep])dnl +]) + +# Generate code to set up dependency tracking. -*- Autoconf -*- + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_OUTPUT_DEPENDENCY_COMMANDS +# ------------------------------ +AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], +[{ + # Older Autoconf quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + # TODO: see whether this extra hack can be removed once we start + # requiring Autoconf 2.70 or later. + AS_CASE([$CONFIG_FILES], + [*\'*], [eval set x "$CONFIG_FILES"], + [*], [set x $CONFIG_FILES]) + shift + # Used to flag and report bootstrapping failures. + am_rc=0 + for am_mf + do + # Strip MF so we end up with the name of the file. + am_mf=`AS_ECHO(["$am_mf"]) | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile which includes + # dependency-tracking related rules and includes. + # Grep'ing the whole file directly is not great: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ + || continue + am_dirpart=`AS_DIRNAME(["$am_mf"])` + am_filepart=`AS_BASENAME(["$am_mf"])` + AM_RUN_LOG([cd "$am_dirpart" \ + && sed -e '/# am--include-marker/d' "$am_filepart" \ + | $MAKE -f - am--depfiles]) || am_rc=$? + done + if test $am_rc -ne 0; then + AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments + for automatic dependency tracking. Try re-running configure with the + '--disable-dependency-tracking' option to at least be able to build + the package (albeit without support for automatic dependency tracking).]) + fi + AS_UNSET([am_dirpart]) + AS_UNSET([am_filepart]) + AS_UNSET([am_mf]) + AS_UNSET([am_rc]) + rm -f conftest-deps.mk +} +])# _AM_OUTPUT_DEPENDENCY_COMMANDS + + +# AM_OUTPUT_DEPENDENCY_COMMANDS +# ----------------------------- +# This macro should only be invoked once -- use via AC_REQUIRE. +# +# This code is only required when automatic dependency tracking is enabled. +# This creates each '.Po' and '.Plo' makefile fragment that we'll need in +# order to bootstrap the dependency handling code. +AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], +[AC_CONFIG_COMMANDS([depfiles], + [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], + [AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"])]) + +# Do all the work for Automake. -*- Autoconf -*- + +# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This macro actually does too much. Some checks are only needed if +# your package does certain things. But this isn't really a big deal. + +dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O. +m4_define([AC_PROG_CC], +m4_defn([AC_PROG_CC]) +[_AM_PROG_CC_C_O +]) + +# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE]) +# AM_INIT_AUTOMAKE([OPTIONS]) +# ----------------------------------------------- +# The call with PACKAGE and VERSION arguments is the old style +# call (pre autoconf-2.50), which is being phased out. PACKAGE +# and VERSION should now be passed to AC_INIT and removed from +# the call to AM_INIT_AUTOMAKE. +# We support both call styles for the transition. After +# the next Automake release, Autoconf can make the AC_INIT +# arguments mandatory, and then we can depend on a new Autoconf +# release and drop the old call support. +AC_DEFUN([AM_INIT_AUTOMAKE], +[AC_PREREQ([2.65])dnl +dnl Autoconf wants to disallow AM_ names. We explicitly allow +dnl the ones we care about. +m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl +AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl +AC_REQUIRE([AC_PROG_INSTALL])dnl +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + AC_MSG_ERROR([source directory already configured; run "make distclean" there first]) + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi +AC_SUBST([CYGPATH_W]) + +# Define the identity of the package. +dnl Distinguish between old-style and new-style calls. +m4_ifval([$2], +[AC_DIAGNOSE([obsolete], + [$0: two- and three-arguments forms are deprecated.]) +m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl + AC_SUBST([PACKAGE], [$1])dnl + AC_SUBST([VERSION], [$2])], +[_AM_SET_OPTIONS([$1])dnl +dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT. +m4_if( + m4_ifdef([AC_PACKAGE_NAME], [ok]):m4_ifdef([AC_PACKAGE_VERSION], [ok]), + [ok:ok],, + [m4_fatal([AC_INIT should be called with package and version arguments])])dnl + AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl + AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl + +_AM_IF_OPTION([no-define],, +[AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package]) + AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl + +# Some tools Automake needs. +AC_REQUIRE([AM_SANITY_CHECK])dnl +AC_REQUIRE([AC_ARG_PROGRAM])dnl +AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}]) +AM_MISSING_PROG([AUTOCONF], [autoconf]) +AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}]) +AM_MISSING_PROG([AUTOHEADER], [autoheader]) +AM_MISSING_PROG([MAKEINFO], [makeinfo]) +AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl +AC_REQUIRE([AC_PROG_MKDIR_P])dnl +# For better backward compatibility. To be removed once Automake 1.9.x +# dies out for good. For more background, see: +# +# +AC_SUBST([mkdir_p], ['$(MKDIR_P)']) +# We need awk for the "check" target (and possibly the TAP driver). The +# system "awk" is bad on some platforms. +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([AC_PROG_MAKE_SET])dnl +AC_REQUIRE([AM_SET_LEADING_DOT])dnl +_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])], + [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])], + [_AM_PROG_TAR([v7])])]) +_AM_IF_OPTION([no-dependencies],, +[AC_PROVIDE_IFELSE([AC_PROG_CC], + [_AM_DEPENDENCIES([CC])], + [m4_define([AC_PROG_CC], + m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_CXX], + [_AM_DEPENDENCIES([CXX])], + [m4_define([AC_PROG_CXX], + m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJC], + [_AM_DEPENDENCIES([OBJC])], + [m4_define([AC_PROG_OBJC], + m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl +AC_PROVIDE_IFELSE([AC_PROG_OBJCXX], + [_AM_DEPENDENCIES([OBJCXX])], + [m4_define([AC_PROG_OBJCXX], + m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl +]) +AC_REQUIRE([AM_SILENT_RULES])dnl +dnl The testsuite driver may need to know about EXEEXT, so add the +dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen. This +dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below. +AC_CONFIG_COMMANDS_PRE(dnl +[m4_provide_if([_AM_COMPILER_EXEEXT], + [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl + +# POSIX will say in a future version that running "rm -f" with no argument +# is OK; and we want to be able to make that assumption in our Makefile +# recipes. So use an aggressive probe to check that the usage we want is +# actually supported "in the wild" to an acceptable degree. +# See automake bug#10828. +# To make any issue more visible, cause the running configure to be aborted +# by default if the 'rm' program in use doesn't match our expectations; the +# user can still override this though. +if rm -f && rm -fr && rm -rf; then : OK; else + cat >&2 <<'END' +Oops! + +Your 'rm' program seems unable to run without file operands specified +on the command line, even when the '-f' option is present. This is contrary +to the behaviour of most rm programs out there, and not conforming with +the upcoming POSIX standard: + +Please tell bug-automake@gnu.org about your system, including the value +of your $PATH and any error possibly output before this message. This +can help us improve future automake versions. + +END + if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then + echo 'Configuration will proceed anyway, since you have set the' >&2 + echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 + echo >&2 + else + cat >&2 <<'END' +Aborting the configuration process, to ensure you take notice of the issue. + +You can download and install GNU coreutils to get an 'rm' implementation +that behaves properly: . + +If you want to complete the configuration process using your problematic +'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM +to "yes", and re-run configure. + +END + AC_MSG_ERROR([Your 'rm' program is bad, sorry.]) + fi +fi +dnl The trailing newline in this macro's definition is deliberate, for +dnl backward compatibility and to allow trailing 'dnl'-style comments +dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841. +]) + +dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion. Do not +dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further +dnl mangled by Autoconf and run in a shell conditional statement. +m4_define([_AC_COMPILER_EXEEXT], +m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])]) + +# When config.status generates a header, we must update the stamp-h file. +# This file resides in the same directory as the config header +# that is generated. The stamp files are numbered to have different names. + +# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the +# loop where config.status creates the headers, so we can generate +# our stamp files there. +AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK], +[# Compute $1's index in $config_headers. +_am_arg=$1 +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_SH +# ------------------ +# Define $install_sh. +AC_DEFUN([AM_PROG_INSTALL_SH], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +if test x"${install_sh+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi +AC_SUBST([install_sh])]) + +# Copyright (C) 2003-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# Check whether the underlying file-system supports filenames +# with a leading dot. For instance MS-DOS doesn't. +AC_DEFUN([AM_SET_LEADING_DOT], +[rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null +AC_SUBST([am__leading_dot])]) + +# Add --enable-maintainer-mode option to configure. -*- Autoconf -*- +# From Jim Meyering + +# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_MAINTAINER_MODE([DEFAULT-MODE]) +# ---------------------------------- +# Control maintainer-specific portions of Makefiles. +# Default is to disable them, unless 'enable' is passed literally. +# For symmetry, 'disable' may be passed as well. Anyway, the user +# can override the default with the --enable/--disable switch. +AC_DEFUN([AM_MAINTAINER_MODE], +[m4_case(m4_default([$1], [disable]), + [enable], [m4_define([am_maintainer_other], [disable])], + [disable], [m4_define([am_maintainer_other], [enable])], + [m4_define([am_maintainer_other], [enable]) + m4_warn([syntax], [unexpected argument to AM@&t@_MAINTAINER_MODE: $1])]) +AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles]) + dnl maintainer-mode's default is 'disable' unless 'enable' is passed + AC_ARG_ENABLE([maintainer-mode], + [AS_HELP_STRING([--]am_maintainer_other[-maintainer-mode], + am_maintainer_other[ make rules and dependencies not useful + (and sometimes confusing) to the casual installer])], + [USE_MAINTAINER_MODE=$enableval], + [USE_MAINTAINER_MODE=]m4_if(am_maintainer_other, [enable], [no], [yes])) + AC_MSG_RESULT([$USE_MAINTAINER_MODE]) + AM_CONDITIONAL([MAINTAINER_MODE], [test $USE_MAINTAINER_MODE = yes]) + MAINT=$MAINTAINER_MODE_TRUE + AC_SUBST([MAINT])dnl +] +) + +# Check to see how 'make' treats includes. -*- Autoconf -*- + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_MAKE_INCLUDE() +# ----------------- +# Check whether make has an 'include' directive that can support all +# the idioms we need for our automatic dependency tracking code. +AC_DEFUN([AM_MAKE_INCLUDE], +[AC_MSG_CHECKING([whether ${MAKE-make} supports the include directive]) +cat > confinc.mk << 'END' +am__doit: + @echo this is the am__doit target >confinc.out +.PHONY: am__doit +END +am__include="#" +am__quote= +# BSD make does it like this. +echo '.include "confinc.mk" # ignored' > confmf.BSD +# Other make implementations (GNU, Solaris 10, AIX) do it like this. +echo 'include confinc.mk # ignored' > confmf.GNU +_am_result=no +for s in GNU BSD; do + AM_RUN_LOG([${MAKE-make} -f confmf.$s && cat confinc.out]) + AS_CASE([$?:`cat confinc.out 2>/dev/null`], + ['0:this is the am__doit target'], + [AS_CASE([$s], + [BSD], [am__include='.include' am__quote='"'], + [am__include='include' am__quote=''])]) + if test "$am__include" != "#"; then + _am_result="yes ($s style)" + break + fi +done +rm -f confinc.* confmf.* +AC_MSG_RESULT([${_am_result}]) +AC_SUBST([am__include])]) +AC_SUBST([am__quote])]) + +# Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- + +# Copyright (C) 1997-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_MISSING_PROG(NAME, PROGRAM) +# ------------------------------ +AC_DEFUN([AM_MISSING_PROG], +[AC_REQUIRE([AM_MISSING_HAS_RUN]) +$1=${$1-"${am_missing_run}$2"} +AC_SUBST($1)]) + +# AM_MISSING_HAS_RUN +# ------------------ +# Define MISSING if not defined so far and test if it is modern enough. +# If it is, set am_missing_run to use it, otherwise, to nothing. +AC_DEFUN([AM_MISSING_HAS_RUN], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([missing])dnl +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --is-lightweight"; then + am_missing_run="$MISSING " +else + am_missing_run= + AC_MSG_WARN(['missing' script is too old or missing]) +fi +]) + +# Helper functions for option handling. -*- Autoconf -*- + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_MANGLE_OPTION(NAME) +# ----------------------- +AC_DEFUN([_AM_MANGLE_OPTION], +[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) + +# _AM_SET_OPTION(NAME) +# -------------------- +# Set option NAME. Presently that only means defining a flag for this option. +AC_DEFUN([_AM_SET_OPTION], +[m4_define(_AM_MANGLE_OPTION([$1]), [1])]) + +# _AM_SET_OPTIONS(OPTIONS) +# ------------------------ +# OPTIONS is a space-separated list of Automake options. +AC_DEFUN([_AM_SET_OPTIONS], +[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) + +# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET]) +# ------------------------------------------- +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +AC_DEFUN([_AM_IF_OPTION], +[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_PROG_CC_C_O +# --------------- +# Like AC_PROG_CC_C_O, but changed for automake. We rewrite AC_PROG_CC +# to automatically call this. +AC_DEFUN([_AM_PROG_CC_C_O], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +AC_REQUIRE_AUX_FILE([compile])dnl +AC_LANG_PUSH([C])dnl +AC_CACHE_CHECK( + [whether $CC understands -c and -o together], + [am_cv_prog_cc_c_o], + [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])]) + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i]) +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +AC_LANG_POP([C])]) + +# For backward compatibility. +AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])]) + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_RUN_LOG(COMMAND) +# ------------------- +# Run COMMAND, save the exit status in ac_status, and log it. +# (This has been adapted from Autoconf's _AC_RUN_LOG macro.) +AC_DEFUN([AM_RUN_LOG], +[{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD + ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + (exit $ac_status); }]) + +# Check to make sure that the build environment is sane. -*- Autoconf -*- + +# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_SANITY_CHECK +# --------------- +AC_DEFUN([AM_SANITY_CHECK], +[AC_MSG_CHECKING([whether build environment is sane]) +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[[\\\"\#\$\&\'\`$am_lf]]*) + AC_MSG_ERROR([unsafe absolute working directory name]);; +esac +case $srcdir in + *[[\\\"\#\$\&\'\`$am_lf\ \ ]]*) + AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);; +esac + +# Do 'set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + am_has_slept=no + for am_try in 1 2; do + echo "timestamp, slept: $am_has_slept" > conftest.file + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$[*]" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + if test "$[*]" != "X $srcdir/configure conftest.file" \ + && test "$[*]" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + AC_MSG_ERROR([ls -t appears to fail. Make sure there is not a broken + alias in your environment]) + fi + if test "$[2]" = conftest.file || test $am_try -eq 2; then + break + fi + # Just in case. + sleep 1 + am_has_slept=yes + done + test "$[2]" = conftest.file + ) +then + # Ok. + : +else + AC_MSG_ERROR([newly created file is older than distributed files! +Check your system clock]) +fi +AC_MSG_RESULT([yes]) +# If we didn't sleep, we still need to ensure time stamps of config.status and +# generated files are strictly newer. +am_sleep_pid= +if grep 'slept: no' conftest.file >/dev/null 2>&1; then + ( sleep 1 ) & + am_sleep_pid=$! +fi +AC_CONFIG_COMMANDS_PRE( + [AC_MSG_CHECKING([that generated files are newer than configure]) + if test -n "$am_sleep_pid"; then + # Hide warnings about reused PIDs. + wait $am_sleep_pid 2>/dev/null + fi + AC_MSG_RESULT([done])]) +rm -f conftest.file +]) + +# Copyright (C) 2009-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_SILENT_RULES([DEFAULT]) +# -------------------------- +# Enable less verbose build rules; with the default set to DEFAULT +# ("yes" being less verbose, "no" or empty being verbose). +AC_DEFUN([AM_SILENT_RULES], +[AC_ARG_ENABLE([silent-rules], [dnl +AS_HELP_STRING( + [--enable-silent-rules], + [less verbose build output (undo: "make V=1")]) +AS_HELP_STRING( + [--disable-silent-rules], + [verbose build output (undo: "make V=0")])dnl +]) +case $enable_silent_rules in @%:@ ((( + yes) AM_DEFAULT_VERBOSITY=0;; + no) AM_DEFAULT_VERBOSITY=1;; + *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);; +esac +dnl +dnl A few 'make' implementations (e.g., NonStop OS and NextStep) +dnl do not support nested variable expansions. +dnl See automake bug#9928 and bug#10237. +am_make=${MAKE-make} +AC_CACHE_CHECK([whether $am_make supports nested variables], + [am_cv_make_support_nested_variables], + [if AS_ECHO([['TRUE=$(BAR$(V)) +BAR0=false +BAR1=true +V=1 +am__doit: + @$(TRUE) +.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then + am_cv_make_support_nested_variables=yes +else + am_cv_make_support_nested_variables=no +fi]) +if test $am_cv_make_support_nested_variables = yes; then + dnl Using '$V' instead of '$(V)' breaks IRIX make. + AM_V='$(V)' + AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' +else + AM_V=$AM_DEFAULT_VERBOSITY + AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY +fi +AC_SUBST([AM_V])dnl +AM_SUBST_NOTMAKE([AM_V])dnl +AC_SUBST([AM_DEFAULT_V])dnl +AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl +AC_SUBST([AM_DEFAULT_VERBOSITY])dnl +AM_BACKSLASH='\' +AC_SUBST([AM_BACKSLASH])dnl +_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl +]) + +# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# AM_PROG_INSTALL_STRIP +# --------------------- +# One issue with vendor 'install' (even GNU) is that you can't +# specify the program used to strip binaries. This is especially +# annoying in cross-compiling environments, where the build's strip +# is unlikely to handle the host's binaries. +# Fortunately install-sh will honor a STRIPPROG variable, so we +# always use install-sh in "make install-strip", and initialize +# STRIPPROG with the value of the STRIP variable (set by the user). +AC_DEFUN([AM_PROG_INSTALL_STRIP], +[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +# Installed binaries are usually stripped using 'strip' when the user +# run "make install-strip". However 'strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the 'STRIP' environment variable to overrule this program. +dnl Don't test for $cross_compiling = yes, because it might be 'maybe'. +if test "$cross_compiling" != no; then + AC_CHECK_TOOL([STRIP], [strip], :) +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" +AC_SUBST([INSTALL_STRIP_PROGRAM])]) + +# Copyright (C) 2006-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_SUBST_NOTMAKE(VARIABLE) +# --------------------------- +# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in. +# This macro is traced by Automake. +AC_DEFUN([_AM_SUBST_NOTMAKE]) + +# AM_SUBST_NOTMAKE(VARIABLE) +# -------------------------- +# Public sister of _AM_SUBST_NOTMAKE. +AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) + +# Check how to create a tarball. -*- Autoconf -*- + +# Copyright (C) 2004-2018 Free Software Foundation, Inc. +# +# This file is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# _AM_PROG_TAR(FORMAT) +# -------------------- +# Check how to create a tarball in format FORMAT. +# FORMAT should be one of 'v7', 'ustar', or 'pax'. +# +# Substitute a variable $(am__tar) that is a command +# writing to stdout a FORMAT-tarball containing the directory +# $tardir. +# tardir=directory && $(am__tar) > result.tar +# +# Substitute a variable $(am__untar) that extract such +# a tarball read from stdin. +# $(am__untar) < result.tar +# +AC_DEFUN([_AM_PROG_TAR], +[# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AC_SUBST([AMTAR], ['$${TAR-tar}']) + +# We'll loop over all known methods to create a tar archive until one works. +_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none' + +m4_if([$1], [v7], + [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], + + [m4_case([$1], + [ustar], + [# The POSIX 1988 'ustar' format is defined with fixed-size fields. + # There is notably a 21 bits limit for the UID and the GID. In fact, + # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343 + # and bug#13588). + am_max_uid=2097151 # 2^21 - 1 + am_max_gid=$am_max_uid + # The $UID and $GID variables are not portable, so we need to resort + # to the POSIX-mandated id(1) utility. Errors in the 'id' calls + # below are definitely unexpected, so allow the users to see them + # (that is, avoid stderr redirection). + am_uid=`id -u || echo unknown` + am_gid=`id -g || echo unknown` + AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format]) + if test $am_uid -le $am_max_uid; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + _am_tools=none + fi + AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format]) + if test $am_gid -le $am_max_gid; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + _am_tools=none + fi], + + [pax], + [], + + [m4_fatal([Unknown tar format])]) + + AC_MSG_CHECKING([how to create a $1 tar archive]) + + # Go ahead even if we have the value already cached. We do so because we + # need to set the values for the 'am__tar' and 'am__untar' variables. + _am_tools=${am_cv_prog_tar_$1-$_am_tools} + + for _am_tool in $_am_tools; do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; do + AM_RUN_LOG([$_am_tar --version]) && break + done + am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - "'"$tardir"' + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x $1 -w "$$tardir"' + am__tar_='pax -L -x $1 -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H $1 -L' + am__tar_='find "$tardir" -print | cpio -o -H $1 -L' + am__untar='cpio -i -H $1 -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_$1}" && break + + # tar/untar a dummy directory, and stop if the command works. + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar]) + rm -rf conftest.dir + if test -s conftest.tar; then + AM_RUN_LOG([$am__untar /dev/null 2>&1 && break + fi + done + rm -rf conftest.dir + + AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool]) + AC_MSG_RESULT([$am_cv_prog_tar_$1])]) + +AC_SUBST([am__tar]) +AC_SUBST([am__untar]) +]) # _AM_PROG_TAR + +m4_include([acinclude.m4]) diff --git a/android/Android.mk b/android/Android.mk new file mode 100644 index 0000000..8f842e7 --- /dev/null +++ b/android/Android.mk @@ -0,0 +1,857 @@ +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/shared/ad.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/shared/log.c \ + 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..a370598 --- /dev/null +++ b/android/Makefile.am @@ -0,0 +1,322 @@ +if ANDROID + +AM_CPPFLAGS += -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_CPPFLAGS = $(AM_CPPFLAGS) -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 \ + src/shared/log.h src/shared/log.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_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/android \ + -DPLUGINDIR=\""$(android_plugindir)"\" +android_haltest_LDFLAGS = $(AM_LDFLAGS) -pthread +android_haltest_LDADD = -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_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/android \ + -DPLUGINDIR=\""$(android_plugindir)"\" +android_android_tester_LDADD = lib/libbluetooth-internal.la \ + src/libshared-glib.la $(GLIB_LIBS) -ldl +android_android_tester_LDFLAGS = $(AM_LDFLAGS) -pthread + +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_CPPFLAGS = $(AM_CPPFLAGS) -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_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/android \ + $(SBC_CFLAGS) +android_audio_a2dp_default_la_LIBADD = $(SBC_LIBS) -lrt +android_audio_a2dp_default_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ + -no-undefined -pthread + +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_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/android +android_audio_sco_default_la_LIBADD = $(SPEEXDSP_LIBS) -lrt +android_audio_sco_default_la_LDFLAGS = $(AM_LDFLAGS) -module -avoid-version \ + -no-undefined +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_-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 + 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 + Don't enable Handsfree Profile (HFP) +vendor Set vendor name in DIS. If not set fallback to + "ro.product.manufacturer". +model Set model name used as default adapter name. + If not set fallback to "ro.product.model". +name Set model number in DIS. If not set fallback to + "ro.product.name". +serialno Set serial number in DIS. If not set fallback to + "ro.serialno". +systemid Set system ID in DIS. Hex string encoded uint64. +pnpid 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 Firmware revision in DIS. If not set fallback to + "ro.build.version.release". +hwrew 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 +#endif + +#include +#include + +#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..8bcdfd2 --- /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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#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 (A2DP_GET_VENDOR_ID(*vndcodec) == APTX_VENDOR_ID && + A2DP_GET_CODEC_ID(*vndcodec) == 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, + A2DP_GET_VENDOR_ID(*vndcodec), + A2DP_GET_CODEC_ID(*vndcodec)); + } + + 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: + + 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: + + Opcode 0x05 - Resume Stream command + + Command parameters: Endpoint ID (1 octet) + Response parameters: + + Opcode 0x06 - Suspend Stream command + + Command parameters: Endpoint ID (1 octet) + Response parameters: 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 +#include +#include +#include +#include +#include + +#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 +#include + +__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 + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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..7fb8cb7 --- /dev/null +++ b/android/avdtp.c @@ -0,0 +1,3487 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "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 ""; + } +} + +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 (A2DP_GET_VENDOR_ID(*vndcodec) != + lsep->vndcodec_vendor) + continue; + + if (A2DP_GET_CODEC_ID(*vndcodec) != + 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 + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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..98b9ef0 --- /dev/null +++ b/android/avdtptest.c @@ -0,0 +1,910 @@ +/* + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#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 SRC (source) or SINK (sink)\n" + "\t-i HCI adapter\n" + "\t-c connect\n" + "\t-l listen\n" + "\t-r reject commands\n" + "\t-f fragment\n" + "\t-p configure stream\n" + "\t-s send command\n" + "\t-v 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..21d0195 --- /dev/null +++ b/android/avrcp-lib.c @@ -0,0 +1,3617 @@ +/* + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#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(struct get_element_attributes_req *req, + 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(req->attrs[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, 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..f4b138b --- /dev/null +++ b/android/avrcp.c @@ -0,0 +1,1174 @@ +/* + * + * 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 +#endif + +#include +#include +#include +#include + +#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 queue_free(void *data, void *user_data) +{ + g_free(data); +} + +static void avrcp_device_free(void *data) +{ + struct avrcp_device *dev = data; + + if (dev->queue) { + g_queue_foreach(dev->queue, queue_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..fb027bf --- /dev/null +++ b/android/bluetooth.c @@ -0,0 +1,5518 @@ +/* + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#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/shared/ad.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); + + if (adapter.supported_settings & MGMT_SETTING_BREDR) + load_link_keys(keys, cb); + else + cb(0, &adapter.bdaddr); + + 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_adv_features_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_adv_features *rp = param; + bt_bluetooth_ready cb = user_data; + int err; + + if (status) { + error("Failed to read advertising features for index %u: %s (0x%02x)", + adapter.index, mgmt_errstr(status), status); + err = -EIO; + goto failed; + } + + if (length < sizeof(*rp)) { + error("Too small read advertising features response"); + err = -EIO; + goto failed; + } + + adapter.max_advert_instance = rp->max_instances; + info("Max LE advertising instances: %d", adapter.max_advert_instance); + + load_devices_info(cb); + + return; + +failed: + cb(err, NULL); +} + +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); + + if (adapter.supported_settings & MGMT_SETTING_LE) { + if (mgmt_send(mgmt_if, MGMT_OP_READ_ADV_FEATURES, adapter.index, + 0, NULL, + read_adv_features_complete, cb, NULL) == 0) { + error("Cannot get LE adv features"); + err = -EIO; + goto failed; + } + } else { + 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; +} + +struct addrm_adv_user_data { + bt_le_addrm_advertising_done cb; + void *user_data; +}; + +static void add_advertising_cb(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct addrm_adv_user_data *data = user_data; + + DBG(""); + + if (status) + error("Failed to add advertising %s (0x%02x))", + mgmt_errstr(status), status); + + data->cb(status, data->user_data); +} + +bool bt_le_add_advertising(struct adv_instance *adv, + bt_le_addrm_advertising_done cb, void *user_data) +{ + struct mgmt_cp_add_advertising *cp; + struct addrm_adv_user_data *cb_data; + size_t len; + size_t adv_data_len = 0; + size_t sr_data_len = 0; + uint8_t *dst, *adv_data, *sr_data; + bool ok = false; + + /* These accept NULL and return NULL */ + adv_data = bt_ad_generate(adv->ad, &adv_data_len); + sr_data = bt_ad_generate(adv->sr, &sr_data_len); + + len = sizeof(*cp) + adv_data_len + sr_data_len; + cp = malloc0(len); + if (!cp) + goto out; + + cp->instance = adv->instance; + cp->timeout = adv->timeout; + /* XXX: how should we set duration? (kernel defaults to 2s) */ + + switch (adv->type) { + case ANDROID_ADVERTISING_EVENT_TYPE_CONNECTABLE: + cp->flags |= MGMT_ADV_FLAG_CONNECTABLE; + break; + + case ANDROID_ADVERTISING_EVENT_TYPE_SCANNABLE: + case ANDROID_ADVERTISING_EVENT_TYPE_NON_CONNECTABLE: + default: + break; + } + + if (adv->include_tx_power) + cp->flags |= MGMT_ADV_FLAG_TX_POWER; + + dst = cp->data; + if (adv_data) { + cp->adv_data_len = adv_data_len; + memcpy(dst, adv_data, adv_data_len); + dst += adv_data_len; + } + + if (sr_data) { + cp->scan_rsp_len = sr_data_len; + memcpy(dst, sr_data, sr_data_len); + dst += sr_data_len; + } + + DBG("lens: adv=%u sr=%u total=%zu", + cp->adv_data_len, cp->scan_rsp_len, len); + + cb_data = new0(__typeof__(*cb_data), 1); + cb_data->cb = cb; + cb_data->user_data = user_data; + + ok = (mgmt_send(mgmt_if, MGMT_OP_ADD_ADVERTISING, adapter.index, + len, cp, add_advertising_cb, cb_data, free) > 0); + + if (!ok) + free(cb_data); + +out: + free(adv_data); + free(sr_data); + free(cp); + + return ok; +} + +static void remove_advertising_cb(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct addrm_adv_user_data *data = user_data; + + DBG(""); + + if (status) + error("Failed to remove advertising %s (0x%02x))", + mgmt_errstr(status), status); + + data->cb(status, data->user_data); +} + +bool bt_le_remove_advertising(struct adv_instance *adv, + bt_le_addrm_advertising_done cb, void *user_data) +{ + struct mgmt_cp_remove_advertising cp = { + .instance = adv->instance, + }; + struct addrm_adv_user_data *cb_data; + bool ok; + + cb_data = new0(__typeof__(*cb_data), 1); + cb_data->cb = cb; + cb_data->user_data = user_data; + + ok = (mgmt_send(mgmt_if, MGMT_OP_REMOVE_ADVERTISING, adapter.index, + sizeof(cp), &cp, + remove_advertising_cb, cb_data, free) > 0); + + if (!ok) + free(cb_data); + + return ok; +} + +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..b139cb1 --- /dev/null +++ b/android/bluetooth.h @@ -0,0 +1,115 @@ +/* + * + * 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); + +struct bt_ad; +struct adv_instance { + uint8_t instance; + int32_t timeout; + int32_t type; + struct bt_ad *ad; + struct bt_ad *sr; + unsigned include_tx_power:1; +}; + +/* Values below have no C API definition - only in Java (AdvertiseManager.java) + * and bluedroid + */ +enum android_adv_type { + ANDROID_ADVERTISING_EVENT_TYPE_CONNECTABLE = 0, + ANDROID_ADVERTISING_EVENT_TYPE_SCANNABLE = 2, + ANDROID_ADVERTISING_EVENT_TYPE_NON_CONNECTABLE = 3, +}; + +typedef void (*bt_le_addrm_advertising_done)(uint8_t status, void *user_data); +bool bt_le_add_advertising(struct adv_instance *adv, + bt_le_addrm_advertising_done cb, void *user_data); +bool bt_le_remove_advertising(struct adv_instance *adv, + bt_le_addrm_advertising_done cb, void *user_data); diff --git a/android/bluetoothd-snoop.c b/android/bluetoothd-snoop.c new file mode 100644 index 0000000..25cddee --- /dev/null +++ b/android/bluetoothd-snoop.c @@ -0,0 +1,255 @@ +/* + * + * 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 +#endif + +#include +#include +#include +#if defined(ANDROID) +#include +#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, 0, 0, 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; + + __btd_log_init(NULL, 0); + + DBG(""); + + set_capabilities(); + + if (argc > 1) + path = argv[1]; + else + path = DEFAULT_SNOOP_FILE; + + mainloop_init(); + + 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_with_signal(signal_callback, NULL); + + 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 +#include +#include +#include +#include + +#include + +#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..e9a92a2 --- /dev/null +++ b/android/client/haltest.c @@ -0,0 +1,478 @@ +/* + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 \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, "[]"), + STD_METHOD(quit), + METHOD("exit", quit_p, NULL, NULL), + STD_METHODH(source, ""), + 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 +#include +#include +#include + +#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..630b5e6 --- /dev/null +++ b/android/client/if-audio.c @@ -0,0 +1,536 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include + +#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, ""), + 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, ""), + STD_METHODH(set_parameters, ""), + STD_METHODH(set_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..a6c8679 --- /dev/null +++ b/android/client/if-av-sink.c @@ -0,0 +1,140 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#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, ""), + STD_METHODCH(disconnect, ""), + 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..798a47a --- /dev/null +++ b/android/client/if-av.c @@ -0,0 +1,144 @@ +/* + * 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 _GNU_SOURCE +#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, ""), + STD_METHODCH(disconnect, ""), + 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..75403ec --- /dev/null +++ b/android/client/if-bt.c @@ -0,0 +1,1024 @@ +/* + * 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 _GNU_SOURCE +#include +#include + +#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, ""), + STD_METHODCH(set_adapter_property, " "), + STD_METHODCH(get_remote_device_properties, ""), + STD_METHODCH(get_remote_device_property, " "), + STD_METHODCH(set_remote_device_property, + " "), + STD_METHODCH(get_remote_service_record, " "), + STD_METHODCH(get_remote_services, ""), + STD_METHOD(start_discovery), + STD_METHOD(cancel_discovery), +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + STD_METHODCH(create_bond, " []"), + STD_METHOD(read_energy_info), + STD_METHODCH(get_connection_state, ""), +#else + STD_METHODCH(create_bond, ""), +#endif + STD_METHODCH(remove_bond, ""), + STD_METHODCH(cancel_bond, ""), + STD_METHODCH(pin_reply, "
[]"), + STD_METHODCH(ssp_reply, "
1|0 []"), + STD_METHODCH(get_profile_interface, ""), + STD_METHODH(dut_mode_configure, ""), + STD_METHOD(dut_mode_send), + STD_METHOD(le_test_mode), + STD_METHODH(config_hci_snoop_log, ""), + 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..fbd9381 --- /dev/null +++ b/android/client/if-gatt.c @@ -0,0 +1,2677 @@ +/* + * 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 _GNU_SOURCE +#include +#include + +#include + +#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 = false; + bool include_name = false; + bool include_txpower = false; + 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 = false; + bool include_name = false; + bool include_txpower = false; + 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, "[]"), + STD_METHODCH(unregister_client, ""), +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + STD_METHODCH(scan, "[1|0]"), + STD_METHODCH(connect, " [] [ " + " " + " " + " " + " "), + STD_METHODCH(scan_filter_add_remove, " " + " " + " [] " + " [] []"), + STD_METHODCH(scan_filter_clear, " "), + STD_METHODCH(scan_filter_enable, " []"), + STD_METHODCH(set_adv_data, " [] " + " [] " + " [] []" + " []"), + STD_METHODCH(configure_mtu, " "), + STD_METHODCH(conn_parameter_update, " " + " "), + STD_METHODH(set_scan_parameters, " "), + STD_METHODCH(multi_adv_enable, " " + " " + " "), + STD_METHODCH(multi_adv_update, " " + " " + " "), + STD_METHODCH(multi_adv_set_inst_data, " []" + " [] " + " [] []" + " []"), + STD_METHODCH(multi_adv_disable, ""), + STD_METHODCH(batchscan_cfg_storage, " " + " " + " "), + STD_METHODCH(batchscan_enb_batch_scan, " " + " " + " "), + STD_METHODCH(batchscan_dis_batch_scan, ""), + STD_METHODCH(batchscan_read_reports, " "), +#else + STD_METHODCH(scan, " [1|0]"), + STD_METHODCH(connect, " []"), +#endif + STD_METHODCH(disconnect, " "), + STD_METHODCH(refresh, " "), + STD_METHODCH(search_service, " []"), + STD_METHODCH(get_included_service, " "), + STD_METHODCH(get_characteristic, " "), + STD_METHODCH(get_descriptor, " "), + STD_METHODCH(read_characteristic, + " []"), + STD_METHODCH(write_characteristic, + " []"), + STD_METHODCH(read_descriptor, + " []"), + STD_METHODCH(write_descriptor, + " []"), + STD_METHODCH(execute_write, " "), + STD_METHODCH(register_for_notification, + " "), + STD_METHODCH(deregister_for_notification, + " "), + STD_METHODCH(read_remote_rssi, " "), + STD_METHODCH(get_device_type, ""), + STD_METHODCH(test_command, + " [u1] [u2] [u3] [u4] [u5]"), + STD_METHODCH(listen, " [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, "[]"), + GATTS_METHODCH(unregister_server, ""), +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) + GATTS_METHODCH(connect, " [] []"), +#else + GATTS_METHODCH(connect, " []"), +#endif + GATTS_METHODCH(disconnect, " "), + GATTS_METHODCH(add_service, " "), + GATTS_METHODCH(add_included_service, + " "), + GATTS_METHODCH(add_characteristic, + " "), + GATTS_METHODCH(add_descriptor, + " "), + GATTS_METHODCH(start_service, + " "), + GATTS_METHODCH(stop_service, " "), + GATTS_METHODCH(delete_service, " "), + GATTS_METHODH(send_indication, + " []"), + GATTS_METHODH(send_response, + " []"), + 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..f701a81 --- /dev/null +++ b/android/client/if-hf-client.c @@ -0,0 +1,669 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#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, ""), + STD_METHODCH(disconnect, ""), + STD_METHODCH(connect_audio, ""), + STD_METHODCH(disconnect_audio, ""), + STD_METHOD(start_voice_recognition), + STD_METHOD(stop_voice_recognition), + STD_METHODCH(volume_control, " "), + STD_METHODH(dial, ""), + STD_METHODH(dial_memory, ""), + STD_METHODCH(handle_call_action, " "), + STD_METHOD(query_current_calls), + STD_METHOD(query_current_operator_name), + STD_METHOD(retrieve_subscriber_info), + STD_METHODH(send_dtmf, ""), + 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..68df00b --- /dev/null +++ b/android/client/if-hf.c @@ -0,0 +1,1063 @@ +/* + * 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 _GNU_SOURCE +#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, "[]"), + STD_METHODH(start_voice_recognition, ""), + STD_METHODH(stop_voice_recognition, ""), + STD_METHODCH(volume_control, " "), + STD_METHODH(cops_response, " "), + STD_METHODCH(cind_response, + " " + " "), + STD_METHODH(formatted_at_response, " "), + STD_METHODCH(at_response, " [ ]"), + STD_METHODCH(clcc_response, + " " + " "), + STD_METHODCH(configure_wbs, " "), +#else + STD_METHOD(init), + STD_METHOD(start_voice_recognition), + STD_METHOD(stop_voice_recognition), + STD_METHODCH(volume_control, " "), + STD_METHODH(cops_response, ""), + STD_METHODCH(cind_response, + " " + " "), + STD_METHODH(formatted_at_response, ""), + STD_METHODCH(at_response, " []"), + STD_METHODCH(clcc_response, + " " + ""), +#endif + STD_METHODCH(connect, ""), + STD_METHODCH(disconnect, ""), + STD_METHODCH(connect_audio, ""), + STD_METHODCH(disconnect_audio, ""), + STD_METHODCH(device_status_notification, + " "), + STD_METHODCH(phone_state_change, + " " + ""), + 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..04a54de --- /dev/null +++ b/android/client/if-hh.c @@ -0,0 +1,455 @@ +/* + * 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 _GNU_SOURCE +#include +#include +#include + +#include +#include + +#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, ""), + STD_METHODCH(disconnect, ""), + STD_METHODCH(virtual_unplug, ""), + STD_METHODCH(set_info, ""), + STD_METHODCH(get_protocol, " "), + STD_METHODCH(set_protocol, " "), + STD_METHODCH(get_report, " "), + STD_METHODCH(set_report, " "), + STD_METHODCH(send_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..1498fd5 --- /dev/null +++ b/android/client/if-hl.c @@ -0,0 +1,378 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include + +#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, + " \n" + "\n" + "[[] [] [] []]" + "..."), + STD_METHODH(unregister_application, ""), + STD_METHODH(connect_channel, " "), + STD_METHODH(destroy_channel, ""), + STD_METHODH(close_channel, " "), + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hal.h" + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +#include +#include +#endif + +#include +#include +#include +#include +#include + +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..ef19689 --- /dev/null +++ b/android/client/if-mce.c @@ -0,0 +1,88 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#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, ""), + 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..b3098ba --- /dev/null +++ b/android/client/if-pan.c @@ -0,0 +1,214 @@ +/* + * 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 _GNU_SOURCE +#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, " "), + STD_METHODCH(enable, ""), + STD_METHOD(get_local_role), + STD_METHODCH(disconnect, ""), + 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..39e4b90 --- /dev/null +++ b/android/client/if-rc-ctrl.c @@ -0,0 +1,115 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include +#include + +#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, " "), + 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..6a5bffb --- /dev/null +++ b/android/client/if-rc.c @@ -0,0 +1,401 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include +#include + +#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, + " "), + STD_METHODCH(get_element_attr_rsp, " "), + STD_METHODCH(set_player_app_value_rsp, ""), + STD_METHODCH(set_volume, ""), + STD_METHODCH(register_notification_rsp, + " \n" + "BTRC_EVT_PLAY_STATUS_CHANGED \n" + "BTRC_EVT_TRACK_CHANGE \n" + "BTRC_EVT_TRACK_REACHED_END \n" + "BTRC_EVT_TRACK_REACHED_START \n" + "BTRC_EVT_PLAY_POS_CHANGED \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..6b570d2 --- /dev/null +++ b/android/client/if-sco.c @@ -0,0 +1,816 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include + +#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, ""), + 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, ""), + STD_METHODH(set_parameters, ""), + STD_METHODH(set_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..a188cf6 --- /dev/null +++ b/android/client/if-sock.c @@ -0,0 +1,351 @@ +/* + * 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 _GNU_SOURCE +#include +#include +#include +#include + +#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, + " [] []"), + STD_METHODCH(connect, + " []"), + 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 +#include +#include + +#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 + +/* 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..dc6faa8 --- /dev/null +++ b/android/client/tabcompletion.c @@ -0,0 +1,375 @@ +/* + * 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 _GNU_SOURCE +#include +#include +#include +#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 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 +#include +#include +#include +#include +#include + +#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 + +/* 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 +#include +#include +#include +#include +#include + +#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..9351a66 --- /dev/null +++ b/android/gatt.c @@ -0,0 +1,7482 @@ +/* + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include + +#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 "src/shared/ad.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 adv_instance *adv; +}; + +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 uint32_t adv_inst_bits = 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 free_adv_instance(struct adv_instance *adv) +{ + if (!adv) + return; + + if (adv->instance) + adv_inst_bits &= ~(1 << (adv->instance - 1)); + + bt_ad_unref(adv->ad); + bt_ad_unref(adv->sr); + free(adv); +} + +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_adv_instance(app->adv); + + 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 struct adv_instance *find_adv_instance(uint32_t client_if) +{ + struct gatt_app *app; + struct adv_instance *adv; + uint8_t inst = 0; + unsigned int i; + + app = find_app_by_id(client_if); + if (!app) + return NULL; + + if (app->adv) + return app->adv; + + /* Assume that kernel supports <= 32 advertising instances (5 today) + * We have already indicated the number to the android framework layers + * via the LE features so we don't check again here. + * The kernel will detect the error if needed + */ + for (i = 0; i < sizeof(adv_inst_bits) * 8; i++) { + uint32_t mask = 1 << i; + + if (!(adv_inst_bits & mask)) { + inst = i + 1; + adv_inst_bits |= mask; + break; + } + } + if (!inst) + return NULL; + + adv = new0(__typeof__(*adv), 1); + adv->instance = inst; + app->adv = adv; + + DBG("Assigned advertising instance %d for client %d", inst, client_if); + + return adv; +}; + +/* Build advertising data object from a data buffer containing + * manufacturer_data, service_data, service uuids (in that order) + * The input data is raw with no TLV structure and the service uuids are 128 bit + */ +static struct bt_ad *build_adv_data(int32_t manufacturer_data_len, + int32_t service_data_len, + int32_t service_uuid_len, + const uint8_t *data_in) +{ + const int one_svc_uuid_len = 128 / 8; /* Android uses 128bit UUIDs */ + uint8_t *src = (uint8_t *)data_in; + struct bt_ad *ad; + unsigned num_svc_uuids, i; + + ad = bt_ad_new(); + + if (manufacturer_data_len >= 2) { /* Includes manufacturer id */ + uint16_t manufacturer_id; + + manufacturer_id = bt_get_le16(src); + src += 2; + + if (!bt_ad_add_manufacturer_data(ad, + manufacturer_id, + src, + manufacturer_data_len - 2)) + goto err; + + src += manufacturer_data_len - 2; + } + + if (service_data_len >= 2) { /* Includes service uuid (always 16 bit) */ + bt_uuid_t bt_uuid; + uint16_t uuid16; + + uuid16 = bt_get_le16(src); + src += 2; + bt_uuid16_create(&bt_uuid, uuid16); + + if (!bt_ad_add_service_data(ad, + &bt_uuid, + src, + service_data_len - 2)) + goto err; + + src += service_data_len - 2; + } + + if (service_uuid_len % one_svc_uuid_len) { + error("Service UUIDs not multiple of %d bytes (%d)", + one_svc_uuid_len, service_uuid_len); + num_svc_uuids = 0; + } else { + num_svc_uuids = service_uuid_len / one_svc_uuid_len; + } + + for (i = 0; i < num_svc_uuids; i++) { + bt_uuid_t bt_uuid; + + android2uuid(src, &bt_uuid); + src += one_svc_uuid_len; + + if (!bt_ad_add_service_uuid(ad, &bt_uuid)) + goto err; + } + + return ad; + +err: + bt_ad_unref(ad); + return NULL; +} + + +static void handle_client_setup_multi_adv(const void *buf, uint16_t len) +{ + const struct hal_cmd_gatt_client_setup_multi_adv *cmd = buf; + struct hal_ev_gatt_client_multi_adv_enable ev; + struct adv_instance *adv; + uint8_t status; + + DBG("client_if %d min_interval=%d max_interval=%d type=%d channel_map=0x%x tx_power=%d timeout=%d", + cmd->client_if, + cmd->min_interval, + cmd->max_interval, + cmd->type, + cmd->channel_map, + cmd->tx_power, + cmd->timeout); + + adv = find_adv_instance(cmd->client_if); + if (!adv) { + status = HAL_STATUS_FAILED; + goto out; + } + + status = HAL_STATUS_SUCCESS; + adv->timeout = cmd->timeout; + adv->type = cmd->type; + if (adv->type != ANDROID_ADVERTISING_EVENT_TYPE_SCANNABLE) { + bt_ad_unref(adv->sr); + adv->sr = NULL; + } + +out: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SETUP_MULTI_ADV, + status); + + ev.client_if = cmd->client_if; + ev.status = status; + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_MULTI_ADV_ENABLE, sizeof(ev), &ev); +} + +/* This is not currently called by Android 5.1 */ +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); +} + +struct addrm_adv_cb_data { + int32_t client_if; + struct adv_instance *adv; +}; + +static void add_advertising_cb(uint8_t status, void *user_data) +{ + struct addrm_adv_cb_data *cb_data = user_data; + struct hal_ev_gatt_client_multi_adv_data ev = { + .status = status, + .client_if = cb_data->client_if, + }; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_MULTI_ADV_DATA, + sizeof(ev), &ev); + + free(cb_data); +} + +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; + struct adv_instance *adv; + struct bt_ad *adv_data; + struct addrm_adv_cb_data *cb_data = NULL; + uint8_t status = HAL_STATUS_FAILED; + + DBG("client_if %d set_scan_rsp=%d include_name=%d include_tx_power=%d appearance=%d manuf_data_len=%d svc_data_len=%d svc_uuid_len=%d", + cmd->client_if, + cmd->set_scan_rsp, + cmd->include_name, + cmd->include_tx_power, + cmd->appearance, + cmd->manufacturer_data_len, + cmd->service_data_len, + cmd->service_uuid_len + ); + + adv = find_adv_instance(cmd->client_if); + if (!adv) + goto out; + + adv->include_tx_power = cmd->include_tx_power ? 1 : 0; + + adv_data = build_adv_data(cmd->manufacturer_data_len, + cmd->service_data_len, + cmd->service_uuid_len, + cmd->data_service_uuid); + if (!adv_data) + goto out; + + if (cmd->set_scan_rsp) { + bt_ad_unref(adv->sr); + adv->sr = adv_data; + } else { + bt_ad_unref(adv->ad); + adv->ad = adv_data; + } + + cb_data = new0(__typeof__(*cb_data), 1); + cb_data->client_if = cmd->client_if; + cb_data->adv = adv; + + if (!bt_le_add_advertising(adv, add_advertising_cb, cb_data)) { + error("gatt: Could not add advertising"); + free(cb_data); + goto out; + } + + status = HAL_STATUS_SUCCESS; + +out: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_SETUP_MULTI_ADV_INST, + status); + + if (status != HAL_STATUS_SUCCESS) { + struct hal_ev_gatt_client_multi_adv_data ev = { + .status = status, + .client_if = cmd->client_if, + }; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_MULTI_ADV_DATA, + sizeof(ev), &ev); + } +} + +static void remove_advertising_cb(uint8_t status, void *user_data) +{ + struct addrm_adv_cb_data *cb_data = user_data; + struct hal_ev_gatt_client_multi_adv_data ev = { + .status = status, + .client_if = cb_data->client_if, + }; + + free_adv_instance(cb_data->adv); + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_MULTI_ADV_DISABLE, + sizeof(ev), &ev); + + free(cb_data); +} + +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; + struct adv_instance *adv; + struct gatt_app *app; + struct addrm_adv_cb_data *cb_data = NULL; + uint8_t status = HAL_STATUS_FAILED; + + DBG("client_if %d", cmd->client_if); + + adv = find_adv_instance(cmd->client_if); + if (!adv) + goto out; + + cb_data = new0(__typeof__(*cb_data), 1); + cb_data->client_if = cmd->client_if; + cb_data->adv = adv; + + if (!bt_le_remove_advertising(adv, remove_advertising_cb, cb_data)) { + error("gatt: Could not remove advertising"); + free(cb_data); + goto out; + } + + app = find_app_by_id(cmd->client_if); + if (app) + app->adv = NULL; + + status = HAL_STATUS_SUCCESS; + +out: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_OP_GATT_CLIENT_DISABLE_MULTI_ADV_INST, + status); + + if (status != HAL_STATUS_SUCCESS) { + struct hal_ev_gatt_client_multi_adv_data ev = { + .status = status, + .client_if = cmd->client_if, + }; + + ipc_send_notif(hal_ipc, HAL_SERVICE_ID_GATT, + HAL_EV_GATT_CLIENT_MULTI_ADV_DISABLE, + sizeof(ev), &ev); + } +} + +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, true, + 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 +#include +#include + +#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 +#include +#include + +#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..4e364fc --- /dev/null +++ b/android/hal-audio-aptx.c @@ -0,0 +1,271 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#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 = + A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID), + .frequency = APTX_SAMPLING_FREQ_44100 | + APTX_SAMPLING_FREQ_48000, + .channel_mode = APTX_CHANNEL_MODE_STEREO, + }, + { + .info = + A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID), + .frequency = APTX_SAMPLING_FREQ_48000, + .channel_mode = APTX_CHANNEL_MODE_STEREO, + }, + { + .info = + A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_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..b06cafb --- /dev/null +++ b/android/hal-audio-sbc.c @@ -0,0 +1,429 @@ +/* + * 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 _GNU_SOURCE +#include +#include +#include +#include + +#include +#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 = SBC_MIN_BITPOOL, + .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_44100, + }, + { + .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 = SBC_MIN_BITPOOL, + .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_44100, + }, + { + .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 = SBC_MIN_BITPOOL, + .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_48000, + }, +}; + +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..2b25b6e --- /dev/null +++ b/android/hal-audio.c @@ -0,0 +1,1642 @@ +/* + * 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 _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 +#include + +#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..a6137a5 --- /dev/null +++ b/android/hal-avrcp-ctrl.c @@ -0,0 +1,146 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#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..b2127ad --- /dev/null +++ b/android/hal-avrcp.c @@ -0,0 +1,689 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#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..ee3a5e0 --- /dev/null +++ b/android/hal-bluetooth.c @@ -0,0 +1,1139 @@ +/* + * 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 _GNU_SOURCE +#include +#include +#include +#include +#include + +#include + +#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; + uint16_t len_ret; + size_t len; + + DBG("prop: %s", btproperty2str(property)); + + if (!interface_ready()) + return BT_STATUS_NOT_READY; + + adapter_prop_from_hal(property, &cmd->type, &len_ret, cmd->val); + + cmd->len = len_ret; + 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..2e706cf --- /dev/null +++ b/android/hal-gatt.c @@ -0,0 +1,2104 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include + +#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 +#include +#include +#include + +#include + +#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..af21b67 --- /dev/null +++ b/android/hal-handsfree.c @@ -0,0 +1,893 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#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 +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include + +#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: + + 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: + + 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: + + 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: + Response parameters: + + In case of an error, the error response will be returned. + + Opcode 0x02 - Disable command/response + + Command parameters: + Response parameters: + + In case of an error, the error response will be returned. + + Opcode 0x03 - Get Adapter Properties command/response + + Command parameters: + Response parameters: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + In case of an error, the error response will be returned. + + Opcode 0x0b - Start Discovery command/response + + Command parameters: + Response parameters: + + In case of an error, the error response will be returned. + + Opcode 0x0c - Cancel Discovery command/response + + Command parameters: + Response parameters: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + In case of an error, the error response will be returned. + + Opcode 0x02 - Disconnect command/response + + Command parameters: Remote address (6 octets) + Response parameters: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + 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: + + 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: + + 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: + + In case of an error, the error response will be returned. + + Opcode 0x02 - Disconnect command/response + + Command parameters: Remote address (6 octets) + Response parameters: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + In case of an error, the error response will be returned. + + Opcode 0x02 - Disconnect command/response + + Command parameters: Remote address (6 octets) + Response parameters: + + 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: + + 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: + + 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: + + 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: + + Opcode 0x83 - List Player Attributes notification + + Notification parameters: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + In case of an error, the error response will be returned. + + Opcode 0x17 - Server Register command/response + + Command parameters: UUID (16 octets) + Response parameters: + + In case of an error, the error response will be returned. + + Opcode 0x18 - Server Unregister command/response + + Command parameters: Server (4 octets) + Response parameters: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + 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: + + In case of an error, the error response will be returned. + + Opcode 0x02 - Disonnect command/response + + Command parameters: Remote address (6 octetcs) + Response parameters: + + 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: + + 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: + + In case of an error, the error response will be returned. + + Opcode 0x05 - Start Voice Recognition command/response + + Command parameters: + Response parameters: + + In case of an error, the error response will be returned. + + Opcode 0x06 - Stop Voice Recognition command/response + + Command parameters: + Response parameters: + + 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: + + 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: + + In case of an error, the error response will be returned. + + Opcode 0x09 - Dial Memory command/response + + Command parameters: Location (4 octet) + Response parameters: + + 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: + + 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: + Response parameters: + + In case of an error, the error response will be returned. + + Opcode 0x12 - Query Current Operator Name + + Command parameters: + Response parameters: + + In case of an error, the error response will be returned. + + Opcode 0x13 - Retrieve Subscriber Info command/response + + Command parameters: + Response parameters: + + 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: + + In case of an error, the error response will be returned. + + Opcode 0x15 - Request Last Voice Tag Number command/response + + Command parameters: + Response parameters: + + 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: + + +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: + + 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 +#else +#include +#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 +#include +#include + +#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..5e1afc8 --- /dev/null +++ b/android/hal-pan.c @@ -0,0 +1,211 @@ +/* + * 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 _GNU_SOURCE +#include +#include +#include + +#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..f4b4d31 --- /dev/null +++ b/android/hal-sco.c @@ -0,0 +1,1531 @@ +/* + * 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 _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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..b971074 --- /dev/null +++ b/android/hal-socket.c @@ -0,0 +1,97 @@ +/* + * 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 _GNU_SOURCE +#include +#include +#include +#include + +#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..74f2927 --- /dev/null +++ b/android/hal-utils.c @@ -0,0 +1,419 @@ +/* + * 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 _GNU_SOURCE +#include +#include +#include +#include + +#include + +#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 + +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PLATFORM_VER(a, b, c) ((a << 16) | ( b << 8) | (c)) + +#if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) +#include +#include +#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 +#endif + +#include +#include +#include +#include +#include + +#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..ebe0372 --- /dev/null +++ b/android/handsfree.c @@ -0,0 +1,3033 @@ +/* + * + * 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 +#endif + +#include +#include +#include +#include +#include + +#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; + uint16_t mtu; + int fd; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + dev = find_device(&bdaddr); + if (!dev || !bt_sco_get_fd_and_mtu(sco, &fd, &mtu)) + goto failed; + + rsp.mtu = mtu; + 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 +#include +#include +#include + +#include +#include +#include + +__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 +#include +#include +#include +#include + +#include + + +__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 () +// 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 () +// 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 () +// 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 +#include +#include +#include + +#include + +__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 +#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 +#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 + +#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 +#include + +__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 + +__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..42d03ba --- /dev/null +++ b/android/hardware/hardware.c @@ -0,0 +1,125 @@ +/* + * 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. + */ + +#define _GNU_SOURCE +#include + +#include +#include +#include +#include +#include +#include + +#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/2]; + + if (inst) + snprintf(name, sizeof(name), "%s.%s", class_id, inst); + else + snprintf(name, sizeof(name), "%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 +#include + +__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..so and + * audio.a2dp..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..90253ac --- /dev/null +++ b/android/health.c @@ -0,0 +1,2048 @@ +/* + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#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 +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#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..c1d0e8a --- /dev/null +++ b/android/ipc-tester.c @@ -0,0 +1,1513 @@ +/* + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 + +#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 + 11]; + 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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..b505c2f --- /dev/null +++ b/android/main.c @@ -0,0 +1,806 @@ +/* + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if defined(ANDROID) +#include +#include +#endif + +#include + +#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 +#endif + +#include +#include +#include +#include + +#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..1e6d876 --- /dev/null +++ b/android/pan.c @@ -0,0 +1,904 @@ +/* + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_GATT_4B_11 True Server: Characteristic Aggregate Format + Descriptor (M) +TSPC_GATT_4B_12 True Server: Characteristic Format: Boolean (M) +TSPC_GATT_4B_13 True Server: Characteristic Format: 2Bit (M) +TSPC_GATT_4B_14 True Server: Characteristic Format: nibble (M) +TSPC_GATT_4B_15 True Server: Characteristic Format: Uint8 (M) +TSPC_GATT_4B_16 True Server: Characteristic Format: Uint12 (M) +TSPC_GATT_4B_17 True Server: Characteristic Format: Uint16 (M) +TSPC_GATT_4B_18 True Server: Characteristic Format: Uint24 (M) +TSPC_GATT_4B_19 True Server: Characteristic Format: Uint32 (M) +TSPC_GATT_4B_20 True Server: Characteristic Format: Uint48 (M) +TSPC_GATT_4B_21 True Server: Characteristic Format: Uint64 (M) +TSPC_GATT_4B_22 True Server: Characteristic Format: Uint128 (M) +TSPC_GATT_4B_23 True Server: Characteristic Format: Sint8 (M) +TSPC_GATT_4B_24 True Server: Characteristic Format: Sint12 (M) +TSPC_GATT_4B_25 True Server: Characteristic Format: Sint16 (M) +TSPC_GATT_4B_26 True Server: Characteristic Format: Sint24 (M) +TSPC_GATT_4B_27 True Server: Characteristic Format: Sint32 (M) +TSPC_GATT_4B_28 True Server: Characteristic Format: Sint48 (M) +TSPC_GATT_4B_29 True Server: Characteristic Format: Sint64 (M) +TSPC_GATT_4B_30 True Server: Characteristic Format: Sint128 (M) +TSPC_GATT_4B_31 True Server: Characteristic Format: Float32 (M) +TSPC_GATT_4B_32 True Server: Characteristic Format: Float64 (M) +TSPC_GATT_4B_33 True Server: Characteristic Format: SFLOAT (M) +TSPC_GATT_4B_34 True Server: Characteristic Format: FLOAT (M) +TSPC_GATT_4B_35 True Server: Characteristic Format: Duint16 (M) +TSPC_GATT_4B_36 True Server: Characteristic Format: utf8s (M) +TSPC_GATT_4B_37 True Server: Characteristic Format: utf16s (M) +TSPC_GATT_4B_38 True Server: Characteristic Format: struct (M) +------------------------------------------------------------------------------- + + + Generic Attribute Profile Service +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GATT_6_2 True Discover GATT Services using Service Discovery + Profile (C.1) +TSPC_GATT_6_3 True Publish SDP record for GATT services support + via BR/EDR (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory IF TSPC_GATT_1_1 is supported, otherwise Excluded +C.2: Mandatory IF TSPC_GATT_1_2 is supported, otherwise Excluded +------------------------------------------------------------------------------- + + + Attribute Protocol Transport Security +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GATT_7_1 True Security Mode 4 (C.1) +TSPC_GATT_7_2 True LE Security Mode 1 (C.2) +TSPC_GATT_7_3 True LE Security Mode 2 (C.2) +TSPC_GATT_7_4 True LE Authentication Procedure (C.2) +TSPC_GATT_7_5 True LE connection data signing procedure (C.2) +TSPC_GATT_7_6 True LE Authenticate signed data procedure (C.2) +TSPC_GATT_7_7 True LE Authorization Procedure (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory IF TSPC_GATT_2_1 is supported, otherwise Excluded +C.2: Optional IF TSPC_GATT_2_2 is supported, otherwise Excluded +------------------------------------------------------------------------------- + + + Attribute Protocol Client Messages +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ATT_3_1 True Attribute Error Response (M) +TSPC_ATT_3_2 True Exchange MTU Request (O) +TSPC_ATT_3_4 True Find Information Request (O) +TSPC_ATT_3_6 True Find by Type Value Request (O) +TSPC_ATT_3_8 True Read by Type Request (O) +TSPC_ATT_3_10 True Read Request (O) +TSPC_ATT_3_12 True Read Blob Request (O) +TSPC_ATT_3_14 False (*) Read Multiple Request (O) +TSPC_ATT_3_16 True Read by Group Type Request (O) +TSPC_ATT_3_17 True Read by Group Type Response (C.6) +TSPC_ATT_3_18 True Write Request (O) +TSPC_ATT_3_20 True Write Command (O) +TSPC_ATT_3_21 True Signed Write Command (O) +TSPC_ATT_3_22 True Prepare Write Request (O) +TSPC_ATT_3_24 True Execute Write Request (C.8) +TSPC_ATT_3_26 True Handle Value Notification (M) +TSPC_ATT_3_28 True Handle Value Confirmation (M) +------------------------------------------------------------------------------- +C.6: Mandatory IF TSPC_ATT_3_16 is supported, otherwise Excluded +C.8: Mandatory IF TSPC_ATT_3_22 is supported, otherwise Excluded +------------------------------------------------------------------------------- + + Attribute Protocol Server Messages +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ATT_4_1 True Attribute Error Response (M) +TSPC_ATT_4_3 True Exchange MTU Response (M) +TSPC_ATT_4_5 True Find Information Response (M) +TSPC_ATT_4_7 True Find by Type Value Response (M) +TSPC_ATT_4_8 True Read by Type Request (M) +TSPC_ATT_4_9 True Read by Type Response (M) +TSPC_ATT_4_11 True Read Response (M) +TSPC_ATT_4_15 False (*) Read Multiple Response (C.2) +TSPC_ATT_4_17 True Read by Group Type Response (M) +TSPC_ATT_4_19 True Write Response (C.3) +TSPC_ATT_4_20 True Write Command (O) +TSPC_ATT_4_21 True Signed Write Command (O) +TSPC_ATT_4_23 True Prepare Write Response (C.4) +TSPC_ATT_4_25 True Execute Write Response (C.4) +TSPC_ATT_4_26 True Handle Value Notification (O) +TSPC_ATT_4_27 True Handle Value Indication (O) +------------------------------------------------------------------------------- +C.2: Mandatory IF TSPC_ATT_4_14 is supported, otherwise Excluded +C.3: Mandatory IF TSPC_ATT_4_18 is supported, otherwise Excluded +C.4: Mandatory IF TSPC_ATT_4_22 is supported, otherwise Excluded +C.5: Mandatory IF TSPC_ATT_4_27 is supported, otherwise Excluded +------------------------------------------------------------------------------- + + + Attribute Protocol Transport +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ATT_5_2 True LE Security Mode 1 (C.2) +TSPC_ATT_5_4 True LE Authentication Procedure (C.2) +TSPC_ATT_5_7 True LE Authorization Procedure (C.2) +------------------------------------------------------------------------------- +C.2: Optional to support if 2/2 (Attribute Protocol Supported over LE), + otherwise Excluded +------------------------------------------------------------------------------- + + + Device Configuration +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAP_0_2 True LE (C.2) +------------------------------------------------------------------------------- +C.2: Mandatory IF (SUM ICS 34/2 (LE GAP) AND NOT SUM ICS 32/3 (BR/EDR GAP)) + is supported, otherwise Excluded +------------------------------------------------------------------------------- diff --git a/android/pics-gavdp.txt b/android/pics-gavdp.txt new file mode 100644 index 0000000..8fa5ac1 --- /dev/null +++ b/android/pics-gavdp.txt @@ -0,0 +1,38 @@ +GAVDP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Role +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAVDP_1_1 True (*) Initiator (C.1) +TSPC_GAVDP_1_2 True (*) Initiator (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory if Acceptor/Initiator is not supported +------------------------------------------------------------------------------- + + GAVDP Procedures (Initiator) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAVDP_2_1 True Connection Establishment (M) +TSPC_GAVDP_2_2 True (*) Transfer Control -Suspend (O) +TSPC_GAVDP_2_3 False Transfer Control – Change Parameters (O) +------------------------------------------------------------------------------- + + + GAVDP Procedures (Acceptor) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_GAVDP_3_1 True Connection Establishment (M) +TSPC_GAVDP_3_2 True (*) Transfer Control -Suspend (O) +TSPC_GAVDP_3_3 False Transfer Control – Change Parameters (O) +------------------------------------------------------------------------------- diff --git a/android/pics-hdp.txt b/android/pics-hdp.txt new file mode 100644 index 0000000..a613c97 --- /dev/null +++ b/android/pics-hdp.txt @@ -0,0 +1,307 @@ +HDP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + + Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_0_1 False HDP 1.0 (C.1) +TSPC_HDP_0_2 True HDP 1.1 (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support only one Profile version. +------------------------------------------------------------------------------- + + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_1_1 True Supports Source Role (C.1, C.2) +TSPC_HDP_1_2 True (*) Supports Sink Role (C.1, C.3) +------------------------------------------------------------------------------- +C.1: At least one of the defined roles is Mandatory. +C.2: Mandatory if TSPC_MCAP_1_1 is supported, otherwise Excluded. +C.3: Mandatory if TSPC_MCAP_1_1 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + GAP Features - Source +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_2_1 True Supports General-discoverable Mode (M) +TSPC_HDP_2_2 True Supports bondable Mode (M) (C.1) +TSPC_HDP_2_3 True Supports Response to Authentication requests (M) +TSPC_HDP_2_4 True Supports Initiation of Authentication (M) (C.2) +TSPC_HDP_2_5 True Supports Acceptance of Encryption request (M) +TSPC_HDP_2_6 True Supports Initiation of Encryption (M) (C.3) +TSPC_HDP_2_7 True (*) Supports General Inquiry (C.5) (C.4) +TSPC_HDP_2_8 True Supports Acceptance of Bonding requests (M) +TSPC_HDP_2_9 True (*) Supports Initiation of Bonding (O) +TSPC_HDP_2_10 True (*) Supports Extended Inquiry Response (C.7) +TSPC_HDP_2_11 False Supports use of Health Class of Device (O) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_HDP_2_9 is supported, otherwise Optional. +C.2: Mandatory if Security Mode 2, 3, or 4 is supported, otherwise Optional. +C.3: Mandatory if Bluetooth version 2.1 + EDR is claimed, otherwise Optional. +C.4: Mandatory if TSPC_HDP_2_9 is supported, otherwise Optional. +C.5: Mandatory if TSPC_HDP_2_9 is supported, otherwise Optional. +C.6: Mandatory if Bluetooth Core Specification 2.1 + EDR or later + (Not SUM ICS 31/4) and Table 2/1 (Supports General-discoverable Mode) + is supported, otherwise Optional if Bluetooth Core Specification 2.1 + + EDR or later (Not SUM ICS 31/4) is supported, otherwise Excluded. +C.7: Mandatory if Bluetooth Core specification 2.1 + EDR or later is supported, + otherwise Excluded. +------------------------------------------------------------------------------- + + + L2CAP Features - Source +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_3_1 True Supports Reliable Control Channel (C.1) +TSPC_HDP_3_2 True Uses FCS for Control Channel (M) +TSPC_HDP_3_3 True Supports Reliable Data Channel (C.1) +TSPC_HDP_3_4 True Can send data using SAR in ERTM (C.2) +TSPC_HDP_3_5 True (*) Uses FCS for Reliable Data Channel (O) +TSPC_HDP_3_6 True (*) Supports FCS option of "No FCS" for Reliable + Data Channel (C.3) +TSPC_HDP_3_7 True Supports Streaming Data Channel (C.4) +TSPC_HDP_3_8 True (*) Can send data using SAR in SM (C.5) +TSPC_HDP_3_9 True (*) Uses FCS for Steaming Data Channel (C.6) +TSPC_HDP_3_10 True (*) Supports FCS option of "No FCS" for Streaming + (C.7) +TSPC_HDP_3_11 True Maximum number of simultaneous Data Channels + supported (DCmax) per MCL (C.8) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_L2CAP_2_12 is supported, otherwise Excluded. +C.2: Mandatory if TSPC_L2CAP_2_22 is supported, otherwise Excluded. +C.3: Optional if TSPC_L2CAP_2_14 is supported, otherwise Excluded. +C.4: Optional if TSPC_L2CAP_2_13 is supported, otherwise Excluded. +C.5: Mandatory if TSPC_HDP_3_7 and TSPC_L2CAP_2_23 are supported, otherwise + Excluded. +C.6: Optional if TSPC_HDP_3_7 is supported, otherwise Excluded. +C.7: Optional if TSPC_HDP_3_7 and TSPC_L2CAP_2_14 are supported, otherwise + Excluded. +C.8: >=2 if Table TSPC_HDP_3_7 is claimed, otherwise >=1. +------------------------------------------------------------------------------- + + + SDP Attributes - Source +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_4_1 True Supports advertisement of HDP Service Record + (C.1) (C.4) +TSPC_HDP_4_2 True Service Class ID List (C.2) +TSPC_HDP_4_3 True Protocol Descriptor List (C.2) +TSPC_HDP_4_4 True Bluetooth Profile Descriptor List (C.2) +TSPC_HDP_4_5 True (*) Additional Protocol Descriptor Lists (C.2) +TSPC_HDP_4_6 True (*) Service Name (O) +TSPC_HDP_4_7 True (*) Service Description (O) +TSPC_HDP_4_8 True (*) Provider Name (O) +TSPC_HDP_4_9 True HDP Supported Features (MDEP List) (C.3) +TSPC_HDP_4_10 True MCAP Data Exchange Specification (C.3) +TSPC_HDP_4_11 True MCAP Supported Procedures (C.3) +TSPC_HDP_4_12 True (*) Service Record State (O) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_HDP_6_3 is supported, otherwise Excluded. +C.2: Mandatory if TSPC_HDP_4_1 is supported, otherwise Optional. +C.3: Mandatory if TSPC_HDP_4_1 is supported, otherwise Excluded. +C.4: Mandatory to support SDP Server Role (SDP 1b/1) if this item is supported. +------------------------------------------------------------------------------- + + + Device Identification - Source +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_5_1 True Device Identification Profile v1.3 or later + (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_HDP_4_1 is supported, otherwise Optional. +------------------------------------------------------------------------------- + + + HDP Features - Source +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_6_1 True Supports Standard Op Codes (M) +TSPC_HDP_6_2 True (*) Supports Initiate creation of Control and Data + Channels (C.3) (C.7) (C.1 - MCAP Status) +TSPC_HDP_6_3 True Supports Accept creation of Control and Data + Channels (C.3) (C.8) (C.1 - MCAP Status) +TSPC_HDP_6_4 False Supports Initiate Reconnection of MDL (O) + (C.2 - MCAP Status) +TSPC_HDP_6_5 True Supports Accept Reconnection of MDL (C.4) +TSPC_HDP_6_6 False Supports Clock Synchronization Protocol (O) +TSPC_HDP_6_7 False (*) Supports Sync-Slave (C.5) +TSPC_HDP_6_8 False Supports Sync-Master (C.6) +------------------------------------------------------------------------------- +C.1: If TSPC_HDP_6_1 is supported, at least one is Mandatory, otherwise + Excluded. +C.2: Optional if TSPC_HDP_6_1 is supported, otherwise Excluded. +C.3: Mandatory to support at least one. +C.4: Mandatory if TSPC_HDP_6_3 is supported, otherwise Excluded. + +C.5: Mandatory if TSPC_HDP_6_6 is supported, otherwise Excluded. +C.6: Optional if TSPC_HDP_6_6 is supported, otherwise Excluded. +C.7: Mandatory to support SDP Client Role (SDP 1b/2) if this item is supported. +C.8: Mandatory to support SDP Server Role (SDP 1b/1) if this item is supported. +------------------------------------------------------------------------------- + + + Data Exchange Features - Source +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_7_1 False Supports Initiation of Echo Test (O) +TSPC_HDP_7_2 True Supports Acceptance of Echo Test (M) +TSPC_HDP_7_3 True Supports IEEE 11073-20601 (M) +TSPC_HDP_7_4 False (*) Supports IEEE 11073-20601 Agent Role (C.1) +TSPC_HDP_7_5 True (*) Supports IEEE 11073-20601 Manager Role (C.1) +TSPC_HDP_7_6 False Supports Initiation of Association Release (O) +------------------------------------------------------------------------------- +C.1: If TSPC_HDP_7_3 is supported, at least one is Mandatory, otherwise + Excluded. +------------------------------------------------------------------------------- + + + GAP Features - Sink +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_8_1 True Supports General-discoverable Mode (M) +TSPC_HDP_8_2 True Supports Bondable Mode (M) (C.1) +TSPC_HDP_8_3 True Supports Response to Authentitaction requests + (M) +TSPC_HDP_8_4 True Supports Initiation of Authentication (M) (C.2) +TSPC_HDP_8_5 True Supports Acceptance of Encryption request (M) +TSPC_HDP_8_6 True Supports Initiation of Encryption (M) (C.3) +TSPC_HDP_8_7 True Supports General Inquiry (M) C.4) +TSPC_HDP_8_8 True Supports Acceptance of Bonding requests (M) +TSPC_HDP_8_9 True (*) Supports Initiation of Bonding (O) +TSPC_HDP_8_10 True (*) Supports Extended Inquiry Response (C.5) (C.6) +TSPC_HDP_8_11 False Supports use of Health Class of Device (O) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_HDP_8_9 is supported, otherwise Optional. +C.2: Mandatory if Security Mode 2, 3, or 4 is supported, otherwise Optional. +C.3: Mandatory if Bluetooth version 2.1 + EDR is claimed (Not SUM ICS 31/4), + otherwise Optional. +C.4: Mandatory if TSPC_HDP_8_9 is supported, otherwise Optional. +C.5: Mandatory if Bluetooth Core Specification 2.1 + EDR or later + (Not SUM ICS 31/4) and TSPC_HDP_8_1 is supported, otherwise Optional + if Bluetooth Core Specification 2.1 + EDR or later is supported, + otherwise Excluded. +C.6: Mandatory if Bluetooth Core specification 2.1 + EDR or later + (Not SUM ICS 31/4) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + + L2CAP Features - Sink +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_9_1 True Supports Reliable Control Channel (C.1) +TSPC_HDP_9_2 True Uses FCS for Control Channel (M) +TSPC_HDP_9_3 True Supports Reliable Data Channel (C.1) +TSPC_HDP_9_4 True Can send data using SAR in ERTM (C.2) +TSPC_HDP_9_5 True (*) Uses FCS for Reliable Data Channel (O) +TSPC_HDP_9_6 True (*) Supports FCS option of "No FCS" for Reliable + Data Channel (C.3) +TSPC_HDP_9_7 True Supports Streaming Data Channel (C.4) +TSPC_HDP_9_8 True Can send data using SAR in SM (C.5) +TSPC_HDP_9_9 True (*) Uses FCS for Steaming Data Channel (O) +TSPC_HDP_9_10 True (*) Supports FCS option of "No FCS" for Streaming + Data Channel (C.3) +TSPC_HDP_9_11 True Maximum number of simultaneous Data Channels + supported (DCmax) per MCL (M) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_L2CAP_2_12 is supported, otherwise Excluded. +C.2: Mandatory if TSPC_L2CAP_2_22 is supported, otherwise Excluded. +C.3: Optional if TSPC_L2CAP_2_14 is supported, otherwise Excluded. +C.4: Mandatory if TSPC_L2CAP_2_13 is supported, otherwise Excluded. +C.5: Mandatory if TSPC_L2CAP_2_23 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + SDP Attributes - Sink +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_10_1 True Supports advertisement of HDP Service Record (C.1) +TSPC_HDP_10_2 True Service Class ID List (M) +TSPC_HDP_10_3 True Protocol Descriptor List (M) +TSPC_HDP_10_4 True Bluetooth Profile Descriptor List (M) +TSPC_HDP_10_5 True Additional Protocol Descriptor Lists (M) +TSPC_HDP_10_6 True (*) Service Name (O) +TSPC_HDP_10_7 True (*) Service Description (O) +TSPC_HDP_10_8 True (*) Provider Name (O) +TSPC_HDP_10_9 True HDP Supported Features (MDEP List) (M) +TSPC_HDP_10_10 True MCAP Data Exchange Specification (M) +TSPC_HDP_10_11 True MCAP Supported Procedures (M) +TSPC_HDP_10_12 True (*) Service Record State (O) +------------------------------------------------------------------------------- +C.1: Mandatory to support 10/1 and SDP Server Role (SDP 1b/1). +------------------------------------------------------------------------------- + + + Device Identification - Sink +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_11_1 True Device Identification Profile v1.3 or later + (M) (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory if 1/2 is supported. +------------------------------------------------------------------------------- + + + HDP Features - Sink +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_12_1 True Supports Standard Op Codes (M) +TSPC_HDP_12_2 True Supports Initiate creation of Control and Data + Channels (C.1) (C.5) +TSPC_HDP_12_3 True Supports Accept creation of Control and Data + Channels (C.1) (C.6) +TSPC_HDP_12_4 False Supports Initiate Reconnection of MDL (O) (C.2) +TSPC_HDP_12_5 True Supports Accept Reconnection of MDL (M) +TSPC_HDP_12_6 False Supports Clock Synchronization Protocol (O) +TSPC_HDP_12_7 False Supports Sync-Slave (C.3) +TSPC_HDP_12_8 False Supports Sync-Master (C.6) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_HDP_12_1 is supported, otherwise Excluded. +C.2: Optional if TSPC_HDP_12_1 is supported, otherwise Excluded. +C.3: Mandatory if TSPC_HDP_12_6 is supported, otherwise Excluded. +C.4: Optional if TSPC_HDP_12_6 is supported, otherwise Excluded. +C.5: Mandatory to support 12/2 and SDP Client Role (SDP 1b/2). +C.6: Mandatory to support 12/3 and SDP Server Role (SDP 1b/1). +------------------------------------------------------------------------------- + + + Data Exchange Features - Sink +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HDP_13_1 False Supports Initiation of Echo Test (O) +TSPC_HDP_13_2 True Supports Acceptance of Echo Test (M) +TSPC_HDP_13_3 True Supports IEEE 11073-20601 (M) +TSPC_HDP_13_4 True (*) Supports IEEE 11073-20601 Agent Role (C.1) +TSPC_HDP_13_5 False Supports IEEE 11073-20601 Manager Role (C.1) +TSPC_HDP_13_6 False Supports Initiation of Association Release (O) +------------------------------------------------------------------------------- +C.1: If TSPC_HDP_13_3 is supported, at least one is Mandatory, otherwise + Excluded. +------------------------------------------------------------------------------- diff --git a/android/pics-hfp.txt b/android/pics-hfp.txt new file mode 100644 index 0000000..c658a25 --- /dev/null +++ b/android/pics-hfp.txt @@ -0,0 +1,232 @@ +HFP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HFP_0_1 False Version: Hands-Free Profile v1.5 (O.1) +TSPC_HFP_0_2 True (*) Version: Hands-Free Profile v1.6 (O.1) +TSPC_HFP_0_3 False Version: Hands-Free Profile v1.7 (O.1) +------------------------------------------------------------------------------- +O.1: It is mandatory to support only one of the adopted versions. +------------------------------------------------------------------------------- + + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HFP_1_1 True (*) Role: Audio Gateway (AG) (O.1) +TSPC_HFP_1_2 False Role: Hands-Free (HF) (O.1) +------------------------------------------------------------------------------- +O.1: It is mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + Audio Gateway Role +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HFP_2_1 True Connection management (M) +TSPC_HFP_2_1a True (*) SLC initiation during active ongoing call (O) +TSPC_HFP_2_2 True Phone Status Information (M) +TSPC_HFP_2_3 True Audio connection handling (M) +TSPC_HFP_2_3a False Audio connection establishment independent of + a call processing (O) +TSPC_HFP_2_3b True (*) eSCO support in Audio Connection (C.10) +TSPC_HFP_2_3c True (*) Codec negotiation (C.7) +TSPC_HFP_2_4a False Accept an incoming voice call + (in-band ring) (C.1) +TSPC_HFP_2_4b True (*) Accept an incoming voice call + (no in-band ring) (C.1) +TSPC_HFP_2_4c False Capability to change the "in-band ring" + settings (O) +TSPC_HFP_2_5 True (*) Reject an incoming voice call (O) +TSPC_HFP_2_6 True Terminate a call (M) +TSPC_HFP_2_7 True Audio connection transfer during an ongoing + call (M) +TSPC_HFP_2_7a True (*) HF-initiated Audio transfer to AG during + ongoing call (O) +TSPC_HFP_2_8 True Place a call with a phone number supplied by + the HF (M) +TSPC_HFP_2_9 True Place a call using memory dialing (M) +TSPC_HFP_2_10 True Place a call to the last number dialed (M) +TSPC_HFP_2_11 True Call waiting notification (M) +TSPC_HFP_2_12 True (*) Three Way Calling (O) +TSPC_HFP_2_12a True (*) User Busy (AT+CHLD value 0) (C.3) +TSPC_HFP_2_12b True (*) Call Hold Handling (AT+CHLD value 1,2) (C.2) +TSPC_HFP_2_12c True (*) Three Way Call (AT+CHLD value 3) (C.3) +TSPC_HFP_2_12d False Explicit Call Transfer (AT+CHLD value 4) (C.3) +TSPC_HFP_2_13 True Calling Line Identification (CLI) (M) +TSPC_HFP_2_14 True (*) Echo canceling (EC) and Noise reduction (NR) (O) +TSPC_HFP_2_15 True (*) Voice recognition activation (O) +TSPC_HFP_2_15a True (*) Initiate voice recognition from AG (C.6) +TSPC_HFP_2_15b True (*) Autonomous voice deactivation (C.6) +TSPC_HFP_2_16 False Attach a phone number to a voice tag (O) +TSPC_HFP_2_17 True Ability to transmit DTMF codes (M) +TSPC_HFP_2_18a True (*) Remote audio volume control – speaker (O) +TSPC_HFP_2_18b False Remote audio volume control – microphone (O) +TSPC_HFP_2_18c True (*) Volume Level Synchronization – speaker and + microphone (C.5) +TSPC_HFP_2_19 False Response and hold (O) +TSPC_HFP_2_20 True Subscriber Number Information (M) +TSPC_HFP_2_21a True Enhanced Call Status (C.4) +TSPC_HFP_2_21b False Enhanced Call Control (C.3) +TSPC_HFP_2_21c True (*) Enhanced Call Status with limited network + notification (C.4) +TSPC_HFP_2_22 False Support for automatic link loss recovery (O) +TSPC_HFP_2_23 True Individual Indicator Activation (C.9) +TSPC_HFP_2_24 True (*) Wide Band Speech service (C.8) +TSPC_HFP_2_25 False Support roaming function (O) +TSPC_HFP_2_26 False HF Indicators (C.11) +TSPC_HFP_2_27 False Support CVSD eSCO s4 setting (C.12) +------------------------------------------------------------------------------- +C.1: The AG must support one of item TSPC_HFP_2_4a or TSPC_HFP_2_4b +C.2: Mandatory if TSPC_HFP_2_12 is TRUE; otherwise excluded +C.3: Optional if TSPC_HFP_2_12 is TRUE; otherwise excluded +C.4: The AG must support one of item TSPC_HFP_2_21a or TSPC_HFP_2_21c +C.5: Mandatory if TSPC_HFP_2_18a or TSPC_HFP_2_18b; otherwise optional +C.6: Optional if TSPC_HFP_2_15 is supported, otherwise excluded +C.7: Mandatory if TSPC_HFP_2_24 otherwise excluded +C.8: Excluded if TSPC_HFP_0_1 otherwise optional +C.9: Excluded if TSPC_HFP_0_1 otherwise mandatory +C.10: Mandatory if TSPC_HFP_2_27 or TSPC_HFP_2_24 otherwise optional +C.11: Optional IF HFP v1.5 (TSPC_HFP_0_1) OR HFP v1.6 (TSPC_HFP_0_2) is NOT + supported, otherwise Excluded. +C.12: Excluded IF HFP v1.5 (TSPC_HFP_0_1) OR HFP v1.6 (TSPC_HFP_0_2) is + supported, otherwise Mandatory. +------------------------------------------------------------------------------- + + + Hands-Free Role +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HFP_3_1 False (*) Connection Management (M) +TSPC_HFP_3_2a False (*) Phone Status Information ("service" and "call" + indicators) (M) +TSPC_HFP_3_2b False Phone Status Information ("callsetup" + indicators) (O) +TSPC_HFP_3_2c False Accept indicator of signal strength (O) +TSPC_HFP_3_2d False Accept indicator of roaming state ("roam:") (O) +TSPC_HFP_3_2e False Accept indicator of battery level ("battchg") (O) +TSPC_HFP_3_2f False Accept indicator of operator selection (O) +TSPC_HFP_3_3 False (*) Audio connection handling (M) +TSPC_HFP_3_3a False Audio connection establishment independent + of call processing (O) +TSPC_HFP_3_3b False eSCO support in Audio Connection (C.7) +TSPC_HFP_3_3c False Codec negotiation (C.5) +TSPC_HFP_3_4a False (*) Accept an incoming voice call (in-band ring) (M) +TSPC_HFP_3_4b False (*) Accept an incoming voice call (no in-band + ring) (M) +TSPC_HFP_3_4c False Accept an incoming voice call (in-band ring + muting) (O) +TSPC_HFP_3_5 False (*) Reject an incoming voice call (M) +TSPC_HFP_3_6 False (*) Terminate a call (M) +TSPC_HFP_3_7 False (*) Audio connection transfer during an ongoing + call (M) +TSPC_HFP_3_7a False HF-initiated Audio transfer to AG during + ongoing call (O) +TSPC_HFP_3_8 False Place a call with a phone number supplied by + the HF (O) +TSPC_HFP_3_9 False Place a call using memory dialing (O) +TSPC_HFP_3_10 False Place a call to the last number dialed (O) +TSPC_HFP_3_11 False Call waiting notification (O) +TSPC_HFP_3_12 False Three Way Calling (O) +TSPC_HFP_3_12a False Three way calling (AT+CHLD values 0) (C.2) +TSPC_HFP_3_12b False Three way calling (AT+CHLD values 1 and 2) (C.1) +TSPC_HFP_3_12c False Three way calling (AT+CHLD value 3) (C.2) +TSPC_HFP_3_12d False Three way calling (AT+CHLD value 4) (C.2) +TSPC_HFP_3_12e False Originate new call with established call in + progress (C.2) +TSPC_HFP_3_13 False Calling Line Identification (CLI) (O) +TSPC_HFP_3_14 False Echo cancelling (EC) and Noise reduction (NR) (O) +TSPC_HFP_3_15 False Voice recognition activation/deactivation (O) +TSPC_HFP_3_16 False Attach a phone number to a voice tag (O) +TSPC_HFP_3_17 False Ability to transmit DTMF codes (O) +TSPC_HFP_3_18a False Remote audio volume control – speaker (O) +TSPC_HFP_3_18b False Remote audio volume control – microphone (O) +TSPC_HFP_3_18c False Volume Level Synchronization – speaker (C.3) +TSPC_HFP_3_18d False Volume Level Synchronization – microphone (C.4) +TSPC_HFP_3_18e False HF informs AG about local changes of audio + volume (O) +TSPC_HFP_3_18f False HF informs AG about local changes of + microphone gain (O) +TSPC_HFP_3_19 False Response and hold (O) +TSPC_HFP_3_20 False Subscriber Number Information (O) +TSPC_HFP_3_21a False Enhanced Call Status (O) +TSPC_HFP_3_21b False Enhanced Call Control (C.2) +TSPC_HFP_3_22 False Support for automatic link loss recovery (O) +TSPC_HFP_3_23 False (*) Individual Indicator Activation (C.6) +TSPC_HFP_3_24 False Wide Band Speech service (C.6) +TSPC_HFP_3_25 False HF Indicators (C.8) +TSPC_HFP_3_26 False Support CVSD eSCO S4 setting (C.9) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_HFP_3_12; otherwise excluded +C.2: Optional if TSPC_HFP_3_12; otherwise excluded +C.3: Mandatory if TSPC_HFP_3_18a or TSPC_HFP_3_18b, otherwise optional +C.4: Mandatory if TSPC_HFP_3_18b, otherwise optional +C.5: Mandatory if TSPC_HFP_3_24 otherwise excluded +C.6: Excluded if TSPC_HFP_0_1 otherwise optional +C.7: Mandatory if TSPC_HFP_3_26 or TSPC_HFP_3_24 otherwise optional +C.8: Optional IF HFP v1.5 (TSPC_HFP_0_1) OR HFP v1.6 (TSPC_HFP_0_2) is NOT + supported, otherwise Excluded. +C.9: Excluded IF HFP v1.5 (TSPC_HFP_0_1) OR HFP v1.6 (TSPC_HFP_0_2) is + supported, otherwise Mandatory. +------------------------------------------------------------------------------- + + + Audio Coding Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HFP_4_1 True CVSD audio coding over SCO (M) +TSPC_HFP_4_2 True (*) mSBC audio coding over eSCO (C.1) +TSPC_HFP_4_3 True (*) CVSD audio coding over eSCO (Initiating) (C.2) +TSPC_HFP_4_2 True (*) CVSD audio coding over eSCO (Accepting) (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory if Wide band speech service is supported TSPC_HFP_2_24 or + TSPC_HFP_3_24, otherwise excluded +C.2: Mandatory IF TPSC_HFP_2_3b OR TSPC_HFP_3_3b; otherwise Excluded. +------------------------------------------------------------------------------- + + + Supplementary Interoperability Verification +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HFP_8_1 True (*) Multiple audio transfers during call – + AG and HF initiated (C.1) +TSPC_HFP_8_2 True (*) Audio transfer by SLC release during + an active call (C.1) +TSPC_HFP_8_3 True (*) Audio transfer by powering ON HF (O) +TSPC_HFP_8_4 True (*) SLC during SDP response (O) +TSPC_HFP_8_5 True (*) Handle dynamic server channel number for HFP + service (O) +TSPC_HFP_8_6 False HF disallows connections in non-discoverable + mode (C.2) +TSPC_HFP_8_7 True (*) HF connects to AG during incoming call (O) +TSPC_HFP_8_8 True (*) Link loss during incoming call (C.3) +TSPC_HFP_8_9 True (*) SLC release during incoming call (C.3) +TSPC_HFP_8_10 True (*) Voice Recognition Activation (C.4) +TSPC_HFP_8_11 True (*) Place outgoing call by dialing number on + the AG (O) +TSPC_HFP_8_12 True (*) Active call termination – NO CARRIER signal + (C.5) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_HFP_2_7a or TSPC_HFP_3_7a is supported, + otherwise excluded +C.2: Optional if TSPC_HFP_1_2 is supported, otherwise excluded +C.3: Optional if TSPC_HFP_1_1 is supported, otherwise excluded +C.4: Optional if TSPC_HFP_2_15 or TSPC_HFP_3_15 is supported, + otherwise excluded +C.5: Optional if TSPC_HFP_2_6 is supported, otherwise excluded +------------------------------------------------------------------------------- diff --git a/android/pics-hid.txt b/android/pics-hid.txt new file mode 100644 index 0000000..875f9b7 --- /dev/null +++ b/android/pics-hid.txt @@ -0,0 +1,291 @@ +HID PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_1_1 True (*) Role: Host, Report protocol (O.1) +TSPC_HID_1_2 False Role: HID Role (O.1) +TSPC_HID_1_3 False Role: Host, Boot protocol (O.1) +------------------------------------------------------------------------------- +O.1: It is Mandatory to support One of these roles. +------------------------------------------------------------------------------- + + + Application Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_2_1 True (*) Host: Establish HID connection (C.4) +TSPC_HID_2_2 True (*) Host: Accept HID connection (C.4) +TSPC_HID_2_3 True (*) Host: Terminate HID connection (C.4) +TSPC_HID_2_4 True (*) Host: Accept termination of HID connection (C.4) +TSPC_HID_2_5 True (*) Host: Support for virtual cables (C.4) +TSPC_HID_2_6 True (*) Host: HID initiated connection (C.4) +TSPC_HID_2_7 True (*) Host: Host initiated connection (C.4) +TSPC_HID_2_8 True (*) Host: Host data transfer to HID (C.1) +TSPC_HID_2_9 True (*) Host: HID data transfer to Host (C.1) +TSPC_HID_2_10 False Host: Boot mode data transfer to Host (C.2) +TSPC_HID_2_11 False Host : Boot mode data transfer to HID (C.2) +TSPC_HID_2_12 False Host : Support for Application to send + GET_Report (O) +TSPC_HID_2_13 False Host : Support for Application to send + SET_REPORT (O) +TSPC_HID_2_14 False Host : Support for sending HCI_CONTROL with + VIRTUAL_CABLE_UNPLUG (C.3) +TSPC_HID_2_15 False Host : Support for receiving HCI_CONTROL with + VIRTUAL_CABLE_UNPLUG (C.3) +------------------------------------------------------------------------------- +C.1: Optional for Boot Mode Only Hosts (TSPC_HID_1_3); Mandatory for Host Role + (TSPC_HID_1_1); OTHERWISE Excluded. +C.2: Mandatory for Boot Mode Only Hosts (TSPC_HID_1_3); otherwise Optional. +C.3: Optional IF (TSPC_HID_2_5) supported, otherwise excluded. +C.4: Mandatory IF TSPC_HID_1_1 (Host, Report protocol) is supported, otherwise + Optional. +------------------------------------------------------------------------------- + + + Device to Host Transfers +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_3_1 False Host : Data reports larger than host MTU on + Control channel (O) +TSPC_HID_3_2 True (*) Host : Data reports larger than host MTU on + Interrupt channel (C.1) +TSPC_HID_3_3 True (*) Host : Data reports to host (C.1) +TSPC_HID_3_4 False Host : Boot mode reports to host (C.2) +------------------------------------------------------------------------------- +C.1: Excluded for Boot Mode Only Hosts (TSPC_HID_1_3); Mandatory IF + TSPC_HID_2_12 is supported, otherwise Optional. +C.2: Mandatory IF TSPC_HID_1_3 is supported, otherwise Optional. +------------------------------------------------------------------------------- + + + Host to Device Transfers +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_4_1 False Host : Data reports larger than device MTU on + Control channel (C.1) +TSPC_HID_4_2 False Host : Data reports larger than device MTU on + Interrupt channel (C.1) +TSPC_HID_4_3 True (*) Host : Data reports to device (C.2) +TSPC_HID_4_4 False Host : Boot mode reports to device (O) +------------------------------------------------------------------------------- +C.1: Excluded for Boot Mode Only Hosts (TSPC_HID_1_3); otherwise Optional +C.2: Excluded for Boot Mode Only Hosts (TSPC_HID_1_3); otherwise Mandatory for + Host Role (TSPC_HID_1_1). +------------------------------------------------------------------------------- + + + HID Control Commands +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_5_1 False Host : Set_Protocol command (C.1, C.4) +TSPC_HID_5_2 False Host : Get_Protocol command (C.1, C.4) +TSPC_HID_5_3 False Host : Set_Idle command (O) +TSPC_HID_5_4 False Host : Get_Idle command (O) +TSPC_HID_5_5 False Host : Set_Report command (C.2) +TSPC_HID_5_6 False Host : Get_Report command (C.3) +------------------------------------------------------------------------------- +C.1: Mandatory for Boot Mode Only Hosts (TSPC_HID_1_3); otherwise Optional. +C.2: Mandatory IF (TSPC_HID_1_1) supported AND (TSPC_HID_2_13) supported. +C.3: Mandatory IF (TSPC_HID_1_1) Supported AND (TSPC_HID_2_12) Supported +C.4: Mandatory to support TSPC_HID_5_1 (Set_Protocol command) AND TSPC_HID_5_2 + (Get_Protocol command) IF one of TSPC_HID_5_1 (Set_Protocol command) + OR TSPC_HID_5_2 (Get_Protocol command) is supported, otherwise + Excluded. +------------------------------------------------------------------------------- + + + Host Link Manager Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_6_1 False Host : Initiate Authentication before + connection completed (C.1) +TSPC_HID_6_2 False Host : Initiate Authentication after connection + completed (C.1) +TSPC_HID_6_3 False Host : Initiate pairing before connection + completed (C.2) +TSPC_HID_6_4 False Host : Initiate pairing after connection + completed (C.2) +TSPC_HID_6_5 False Host : Encryption (O) +TSPC_HID_6_6 False Host : Initiate encryption (C.3) +TSPC_HID_6_7 False Host : Accept encryption requests (C.3) +TSPC_HID_6_8 True (*) Host : Role switch (Master/Slave) (C.4) +TSPC_HID_6_9 True (*) Host : Request Master Slave switch (C.4) +TSPC_HID_6_10 True (*) Host : Accept Master Slave switch requests (C.4) +TSPC_HID_6_11 False Host : Hold mode (O) +TSPC_HID_6_12 True (*) Host : Sniff mode (C.4) +TSPC_HID_6_13 False Host : Park mode (O) +------------------------------------------------------------------------------- +C.1: Mandatory to support TSPC_HID_6_1 AND TSPC_HID_6_2 IF GAP 2/3 + (Initiate LMP-Authentication) is supported, otherwise Excluded. +C.2: If Pairing supported both (TSPC_HID_6_3) AND (TSPC_HID_6_4) must + be supported. +C.3: Mandatory IF (TSPC_HID_6_5) encryption supported. +C.4: Mandatory IF (TSPC_HID_1_1) supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Host Link Control Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_7_1 True (*) Host : Supports inquiry, 79 channel (C.1) +TSPC_HID_7_2 False Host : Supports inquiry scan, 79 channel (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory to support IF (TSPC_HID_1_1) supported, otherwise Excluded. +C.2: Feature should not be used by a Host, but can be supported in LM. +------------------------------------------------------------------------------- + + + HID Device Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_8_1 False Hid : Pointing HID (O.1) +TSPC_HID_8_2 False Hid : Keyboard HID (O.1) +TSPC_HID_8_3 False Hid : Identification HID (O.1) +TSPC_HID_8_4 False Hid : Other HID (O.1) +------------------------------------------------------------------------------- +O.1: It is Mandatory to support One of these roles IF (TSPC_HID_1_2) + is selected +------------------------------------------------------------------------------- + + + HID Application Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_9_1 False Hid : Establish HID connection (O) +TSPC_HID_9_2 False (*) Hid : Accept HID connection (M) +TSPC_HID_9_3 False Hid : Terminate HID connection (O) +TSPC_HID_9_4 False (*) Hid : Accept Termination of HID connection (M) +TSPC_HID_9_5 False Hid : Support for virtual cables (O) +TSPC_HID_9_6 False Hid : HID initiated reconnection (C.1) +TSPC_HID_9_7 False Hid : Host initiated reconnection (C.1) +TSPC_HID_9_8 False Hid : Host data transfer to HID (C.2) +TSPC_HID_9_9 False Hid : HID data transfer to Host (C.2) +TSPC_HID_9_10 False Hid : HID Boot mode data transfer to Host (C.3) +TSPC_HID_9_11 False Hid : Host Boot mode data transfer to HID (C.4) +TSPC_HID_9_12 False Hid : Output reports declared (C.4) +TSPC_HID_9_13 False Hid : Input reports declared (C.3) +TSPC_HID_9_14 False Hid : Feature reports declared (O) +TSPC_HID_9_15 False Hid : Support for sending HCI_CONTROL with + VIRTUAL_CABLE_UNPLUG (C.5) +TSPC_HID_9_16 False Hid : Support for receiving HCI_CONTROL with + VIRTUAL_CABLE_UNPLUG (C.5) +------------------------------------------------------------------------------- +C.1: One of these is Mandatory IF (TSPC_HID_9_5) is supported + (SDP attribute 0x204=True) +C.2: One of these is Mandatory if TSPC_HID_1_2 (HID Role) is supported. +C.3: Mandatory IF (TSPC_HID_8_1) OR (TSPC_HID_8_2) is selected +C.4: Mandatory IF (TSPC_HID_8_2) is supported (for status indicators) +C.5: Optional IF (TSPC_HID_9_5) supported, otherwise excluded. +------------------------------------------------------------------------------- + + + Device to Host Transfers +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_10_1 False Hid : Data reports larger than host MTU on + Control channel (O) +TSPC_HID_10_2 False Hid : Data reports larger than host MTU on + Interrupt channel (O) +TSPC_HID_10_3 False Hid : Data reports to host (O) +TSPC_HID_10_4 False Hid : Boot mode reports to host (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory IF (TSPC_HID_8_1) OR (TSPC_HID_8_2) is supported. + Optional for other HIDs. +------------------------------------------------------------------------------- + + + Host to Device Transfers +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_11_1 False Hid : Data reports larger than device MTU on + Control channel (O) +TSPC_HID_11_2 False Hid : Data reports larger than device MTU on + Interrupt channel (O) +TSPC_HID_11_3 False Hid : Data reports to device (O) +TSPC_HID_11_4 False Hid : Boot mode reports to device (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory IF (TSPC_HID_8_2) is supported. Optional for other HIDs. +------------------------------------------------------------------------------- + + + HID Control Commands +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_12_1 False Hid : Set_Protocol command (C.1, C.5) +TSPC_HID_12_2 False Hid : Get_Protocol command (C.1, C.5) +TSPC_HID_12_3 False Hid : Set_Idle command (C.2) +TSPC_HID_12_4 False Hid : Get_Idle command (C.2) +TSPC_HID_12_5 False Hid : Set_Report command (C.3) +TSPC_HID_12_6 False Hid : Get_Report command (C.4) +------------------------------------------------------------------------------- +C.1: Mandatory IF (TSPC_HID_8_1) OR (TSPC_HID_8_2) is supported. + Optional for other HIDs. If either Set_Protocol or Get_Protocol + supported, both are Mandatory. +C.2: Mandatory IF (TSPC_HID_8_2) Keyboard is selected. Optional for other HIDs. +C.3: Mandatory IF (TSPC_HID_9_12) or (TSPC_HID_9_14) supported. +C.4: Mandatory IF (TSPC_HID_9_13) or (TSPC_HID_9_14) supported +C.5: If either TSPC_HID_12_1 (Set_Protocol command) OR TSPC_HID_12_2 + (Get_Protocol command) is supported, both TSPC_HID_12_1 + (Set_Protocol command) AND TSPC_HID_12_2 (Get_Protocol command) are + Mandatory to support +------------------------------------------------------------------------------- + + + HID Link Manager Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_13_1 False Hid : Host initiated Authentication before + connection completed (C.1) +TSPC_HID_13_2 False Hid : Host initiated Authentication after + connection completed (C.1) +TSPC_HID_13_3 False Hid : Item no longer used (N/A) +TSPC_HID_13_4 False Hid : Item no longer used (N/A) +TSPC_HID_13_5 False Hid : Encryption (C.1) +TSPC_HID_13_6 False Hid : Initiate encryption (O) +TSPC_HID_13_7 False Hid : Accept encryption requests (C.2) +TSPC_HID_13_8 False Hid : Role switch (Master/Slave) (C.3) +TSPC_HID_13_9 False Hid : Request Master Slave switch (O) +TSPC_HID_13_10 False Hid : Accept Master Slave switch requests (C.3) +TSPC_HID_13_11 False Hid : Hold mode (O) +TSPC_HID_13_12 False Hid : Sniff mode (O) +TSPC_HID_13_13 False Hid : Park mode (O) +------------------------------------------------------------------------------- +C.1: Mandatory IF (TSPC_HID_8_2) OR (TSPC_HID_8_3) is selected. Optional + for other HIDs. +C.2: Mandatory IF (TSPC_HID_13_5) supported. +C.3: Mandatory IF (TSPC_HID_9_6) is supported. +------------------------------------------------------------------------------- + + + HID Link Control Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HID_14_1 False Hid : Supports inquiry, 79 channel (O) +TSPC_HID_14_2 False Hid : Supports inquiry scan, 79 channel (M.1) +TSPC_ALL False Enables all test cases when set to true. +------------------------------------------------------------------------------- +M.1: Mandatory IF (TSPC_HID_1_2) is supported. +------------------------------------------------------------------------------- diff --git a/android/pics-hogp.txt b/android/pics-hogp.txt new file mode 100644 index 0000000..bd9c9f9 --- /dev/null +++ b/android/pics-hogp.txt @@ -0,0 +1,409 @@ +HOGP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Profile Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_1_1 False (*) HID Device (Server) (C.1) +TSPC_HOGP_1_2 True Report Host (Client) (C.1) +TSPC_HOGP_1_3 False (*) Boot Host (Client) (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of TSPC_HOGP_1_1 or TSPC_HOGP_1_2 + or TSPC_HOGP_1_3. +------------------------------------------------------------------------------- + + + Transport +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_2_1 False (*) Profile supported over BR/EDR (C.1) +TSPC_HOGP_2_2 True Profile supported over LE (M) +------------------------------------------------------------------------------- +C.1: Excluded for this profile. +------------------------------------------------------------------------------- + + + Services - HID Device +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_3_1 False (*) Implements HID Service (M.1) +TSPC_HOGP_3_2 False (*) Multiple Service instances - HID Service (O) +TSPC_HOGP_3_3 False (*) Implements Battery Service (M.1) +TSPC_HOGP_3_4 False (*) Implements Device Information Service (M.1) +TSPC_HOGP_3_5 False (*) Implements Scan Parameters Service (O) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_1 selected +------------------------------------------------------------------------------- + + + Features - HID Device +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_4_1 False (*) Include HID Service UUID in AD in GAP + Discoverable Mode (O) +TSPC_HOGP_4_2 False (*) Include Local Name in AD or Scan Response Data + (O) +TSPC_HOGP_4_3 False (*) Include Appearance in AD or Scan Response Data + (O) +TSPC_HOGP_4_4 False (*) Support Device Information Service + characteristic: PnP ID (M) +TSPC_HOGP_4_5 False (*) Report characteristic (C.1) +TSPC_HOGP_4_6 False (*) Non-HID Service characteristic described within + Report Map characteristic (C.1) +TSPC_HOGP_4_7 False (*) External Report Reference characteristic + descriptor for Report Map characteristic + (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of these features. +C.2: Mandatory if TSPC_HOGP_4_6 is supported, else excluded. +------------------------------------------------------------------------------- + + + GAP Requirements - HID Device +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_5_1 False (*) Peripheral (M.1) +TSPC_HOGP_5_2 False (*) Directed Connectable Mode (O) +TSPC_HOGP_5_3 False (*) Undirected Connectable Mode (M.1) +TSPC_HOGP_5_4 False (*) Bondable mode (peripheral) (M.1) +TSPC_HOGP_5_5 False (*) Bonding procedure (peripheral) (M.1) +TSPC_HOGP_5_6 False (*) LE Security Mode 1 (peripheral) (M.1) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_1 selected +------------------------------------------------------------------------------- + + + SM Requirements - HID Device +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_6_1 False (*) No security + (LE Security Level 1) (M.1) +TSPC_HOGP_6_2 False (*) Unauthenticated no MITM protection + (LE Security Level 2, Just Works) (M.1) +TSPC_HOGP_6_3 False (*) Authenticated MITM protection + (LE Security Level 3, Passkey) (O) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_1 selected +------------------------------------------------------------------------------- + + + Client Services Support - Report Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_7_1 True HID Service (M.1) +TSPC_HOGP_7_2 True Battery Service (M.1) +TSPC_HOGP_7_3 True Device Information Service (M.1) +TSPC_HOGP_7_4 True Scan Parameters Service (M.1) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_2 selected +------------------------------------------------------------------------------- + + + GATT based Profile Support - Report Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_7a_1 True Scan Parameters Profile (M.1) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_2 selected +------------------------------------------------------------------------------- + + + Client Service Support - Boot Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_8_1 False (*) HID Service (M.1) +TSPC_HOGP_8_2 False (*) Battery Service (O) +TSPC_HOGP_8_3 False (*) Device Information Service (O) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_3 selected +------------------------------------------------------------------------------- + + + Discover Services & Characteristics - Report Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_9_1 True Discover HID Service (M.1) +TSPC_HOGP_9_2 True Discover Battery Service (M.1) +TSPC_HOGP_9_3 True Discover Device Information Service (M.1) +TSPC_HOGP_9_4 True Discover Scan Parameters Service (M.1) +TSPC_HOGP_9_5 True Discover HID Service characteristic: Report Map + (M.1) +TSPC_HOGP_9_6 True Discover HID Service characteristic: Report Map + - External Report Reference + characteristic descriptor (M.1) +TSPC_HOGP_9_7 True Discover HID Service characteristic: Report + (M.1) +TSPC_HOGP_9_8 True Discover HID Service characteristic: Report + - Client Characteristic Configuration + characteristic descriptor (M.1) +TSPC_HOGP_9_9 True Discover HID Service characteristic: Report + - Report Reference characteristic + descriptor (M.1) +TSPC_HOGP_9_10 True Discover HID Service characteristic: HID + Information (M.1) +TSPC_HOGP_9_11 True Discover HID Service characteristic: HID + Control Point (M.1) +TSPC_HOGP_9_12 True Discover HID Service characteristic: Protocol + Mode (O) +TSPC_HOGP_9_13 True Discover Battery Service characteristic: Battery + Level (M.1) +TSPC_HOGP_9_14 True Discover Battery Service characteristic: Battery + Level - Client Characteristic + Configuration characteristic descriptor + (M.1) +TSPC_HOGP_9_15 True Discover Device Information Service + characteristic: PnP ID (M.1) +TSPC_HOGP_9_16 True Discover non-HID Service characteristic: Report + Reference characteristic descriptor + (M.1) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_2 selected +------------------------------------------------------------------------------- + + + Discover Services & Characteristics - Boot Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_10_1 False (*) Discover HID Service (M.1) +TSPC_HOGP_10_2 False (*) Discover Battery Service (O) +TSPC_HOGP_10_3 False (*) Discover Device Information Service (O) +TSPC_HOGP_10_4 False (*) Discover HID Service characteristic: Protocol + Mode (M.1) +TSPC_HOGP_10_5 False (*) Discover HID Service characteristic: Boot + Keyboard Input Report (C.1, C.2) +TSPC_HOGP_10_6 False (*) Discover HID Service characteristic: Boot + Keyboard Input Report - Client + Characteristic Configuration + characteristic descriptor (C.3) +TSPC_HOGP_10_7 False (*) Discover HID Service characteristic: Boot + Keyboard Output Report (C.1, C.2) +TSPC_HOGP_10_8 False (*) Discover HID Service characteristic: Boot + Mouse Input Report (C.1) +TSPC_HOGP_10_9 False (*) Discover HID Service characteristic: Boot + Mouse Input Report - Client + Characteristic Configuration + characteristic descriptor (C.4) +TSPC_HOGP_10_10 False (*) Discover Battery Service characteristic: + Battery Level (O) +TSPC_HOGP_10_11 False (*) Discover Battery Service characteristic: + Battery Level - Client Characteristic + Configuration characteristic descriptor + (O) +TSPC_HOGP_10_12 False (*) Discover Device Information Service + characteristic: PnP ID (O) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_3 selected +C.1: Mandatory to support at least one of TSPC_HOGP_10_5, TSPC_HOGP_10_7, or + TSPC_HOGP_10_8. +C.2: If one of TSPC_HOGP_10_5 or TSPC_HOGP_10_7 is supported, both shall be + supported. +C.3: Mandatory to support if TSPC_HOGP_10_5 is supported, else excluded. +C.4: Mandatory to support if TSPC_HOGP_10_8 is supported, else excluded. +------------------------------------------------------------------------------- + + + Features - Report Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_11_1 True Read Report Map characteristic (M.1) +TSPC_HOGP_11_2 True Read Report Map characteristic: External + Report Reference characteristic + descriptor (M.1) +TSPC_HOGP_11_3 True Read Report characteristic: Report Type: + Input Report (M.1) +TSPC_HOGP_11_4 True Write Report characteristic: Report Type: + Input Report (M.1) +TSPC_HOGP_11_5 True Read Report characteristic: Report Type: + Output Report (M.1) +TSPC_HOGP_11_6 True Write HID Report characteristic: Report Type: + Output Report (M.1) +TSPC_HOGP_11_7 True Read HID Report characteristic: Report Type: + Feature Report (M.1) +TSPC_HOGP_11_8 True Write HID Report characteristic: Report Type: + Feature Report (M.1) +TSPC_HOGP_11_9 True Read Report characteristic: Report Reference + characteristic descriptor (M.1) +TSPC_HOGP_11_10 True Read Report characteristic: Input Report: + Client Characteristic Configuration + characteristic descriptor (M.1) +TSPC_HOGP_11_11 True Report characteristic configuration with 0x0001 + (M.1) +TSPC_HOGP_11_11a True Report characteristic configuration with 0x0000 + (O) +TSPC_HOGP_11_12 True Read HID Information characteristic (M.1) +TSPC_HOGP_11_13 False (*) Suspend State (O) +TSPC_HOGP_11_14 False (*) Exit Suspend State (C.1) +TSPC_HOGP_11_15 False (*) Write HID Control Point characteristic: Suspend + command (C.1) +TSPC_HOGP_11_16 False (*) Write HID Control Point characteristic: Exit + Suspend command (C.1) +TSPC_HOGP_11_17 False (*) Read Protocol Mode characteristic: Get Protocol + command (O) +TSPC_HOGP_11_18 False (*) Write Protocol Mode characteristic: Set Report + Protocol Mode command (O) +TSPC_HOGP_11_19 True Read Battery Level characteristic (M.1) +TSPC_HOGP_11_20 True Read Battery Level characteristic: Client + Characteristic Configuration + characteristic descriptor (M.1) +TSPC_HOGP_11_21 True Battery Level characteristic configuration with + 0x0000 0r 0x0001 (M.1) +TSPC_HOGP_11_22 True Read non-HID Service characteristic: Report + Reference characteristic descriptor + (M.1) +TSPC_HOGP_11_23 True Read PnP ID characteristic (M.1) +TSPC_HOGP_11_24 True Notify Report characteristic (M.1) +TSPC_HOGP_11_25 True Notify Battery Level characteristic (M.1) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_2 selected +C.1: Mandatory to support if TSPC_HOGP_11_13 is supported, else excluded. +------------------------------------------------------------------------------- + + + Features - Boot Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_12_1 False (*) Read Protocol Mode characteristic: Get Protocol + Mode command (M.1) +TSPC_HOGP_12_2 False (*) Write Protocol Mode characteristic: Set Boot + Protocol Mode command (M.1) +TSPC_HOGP_12_3 False (*) Read HID Service characteristic: Boot Keyboard + Input Report (C.1) +TSPC_HOGP_12_4 False (*) Write HID Service characteristic: Boot Keyboard + Input Report (C.1) +TSPC_HOGP_12_5 False (*) Read Client Characteristic Configuration + characteristic descriptor for Boot + Keyboard Input Report (C.1) +TSPC_HOGP_12_6 False (*) Boot Keyboard Input Report characteristic: + configuration with 0x0000 or 0x0001 + (C.1) +TSPC_HOGP_12_7 False (*) Read HID Service characteristic: Boot Keyboard + Output Report (C.1) +TSPC_HOGP_12_8 False (*) Write HID Service characteristic: Boot Keyboard + Output Report (C.1) +TSPC_HOGP_12_9 False (*) Read HID Service characteristic: Boot Mouse + Input Report (C.2) +TSPC_HOGP_12_10 False (*) Write HID Service characteristic: Boot Mouse + Input Report (C.2) +TSPC_HOGP_12_11 False (*) Read Client Characteristic Configuration + characteristic descriptor for Boot + Mouse Input Report (C.2) +TSPC_HOGP_12_12 False (*) Boot Mouse Input Report characteristic: + configuration with 0x0000 or 0x0001 + (C.2) +TSPC_HOGP_12_13 False (*) Notify Boot Keyboard Input Report characteristic + (C.1) +TSPC_HOGP_12_14 False (*) Notify Boot Mouse Input Report characteristic + (C.2) +TSPC_HOGP_12_15 False (*) Read Battery Level characteristic (O) +TSPC_HOGP_12_16 False (*) Read Battery Level characteristic: Client + Characteristic Configuration + characteristic descriptor (O) +TSPC_HOGP_12_17 False (*) Battery Level characteristic: configuration with + 0x0000 or 0x0001 (O) +TSPC_HOGP_12_18 False (*) Notify Battery Level characteristic (O) +TSPC_HOGP_12_19 False (*) Read PnP ID characteristic (O) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_3 selected +C.1: Mandatory to support if TSPC_HOGP_10_5 or TSPC_HOGP_10_7 is supported, + else excluded. +C.2: Mandatory to support if TSPC_HOGP_10_8 is supported, else excluded. +------------------------------------------------------------------------------- + + + GATT Requirements - Report Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_13_1 True Attribute Protocol supported over LE Transport + (M.1) +TSPC_HOGP_13_2 True Generic Attribute Profile Client (M.1) +TSPC_HOGP_13_3 True Discover All Primary Services (C.1) +TSPC_HOGP_13_4 False (*) Discover Primary Services by Service UUID (C.1) +TSPC_HOGP_13_5 True Find Included Services (M.1) +TSPC_HOGP_13_6 True Discover All Characteristics of a Service (C.2) +TSPC_HOGP_13_7 False (*) Discover Characteristics by UUID (C.2) +TSPC_HOGP_13_8 True Discover All Characteristic Descriptors (M.1) +TSPC_HOGP_13_9 True Read Characteristic Value (M.1) +TSPC_HOGP_13_10 True Read using Characteristic UUID (O) +TSPC_HOGP_13_11 True Read Long Characteristic Value (M.1) +TSPC_HOGP_13_12 True Read Characteristic Descriptors (M.1) +TSPC_HOGP_13_13 True Write without Response (M.1) +TSPC_HOGP_13_14 True Write Characteristic Value (M.1) +TSPC_HOGP_13_15 True Write Characteristic Descriptors (M.1) +TSPC_HOGP_13_16 True Notifications (M.1) +TSPC_HOGP_13_17 True Exchange MTU (M.1) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_2 selected +C.1: Mandatory to support at least one of these features. +C.2: Mandatory to support at least one of these features. +------------------------------------------------------------------------------- + + + GATT Requirements - Boot Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_14_1 False (*) Attribute Protocol supported over LE Transport + (M.1) +TSPC_HOGP_14_2 False (*) Generic Attribute Profile Client (M.1) +TSPC_HOGP_14_3 False (*) Discover All Primary Services (C.1) +TSPC_HOGP_14_4 False (*) Discover Primary Services by Service UUID (C.1) +TSPC_HOGP_14_5 False (*) Discover All Characteristics of a Service (O) +TSPC_HOGP_14_6 False (*) Discover Characteristics by UUID (O) +TSPC_HOGP_14_7 False (*) Discover All Characteristic Descriptors (M.1) +TSPC_HOGP_14_8 False (*) Read Characteristic Value (M.1) +TSPC_HOGP_14_9 False (*) Read using Characteristic UUID (M.1) +TSPC_HOGP_14_10 False (*) Read Characteristic Descriptors (M.1) +TSPC_HOGP_14_11 False (*) Write without Response (M.1) +TSPC_HOGP_14_12 False (*) Write Characteristic Value (M.1) +TSPC_HOGP_14_13 False (*) Write Characteristic Descriptors (M.1) +TSPC_HOGP_14_14 False (*) Notifications (M.1) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_3 selected +------------------------------------------------------------------------------- + + + GAP Requirements - HID Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_15_1 True Central (M.1) +TSPC_HOGP_15_2 True LE Security Mode 1 (central) (M.1) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_2 or TSPC_HOGP_1_3 is selected +------------------------------------------------------------------------------- + + + SM Requirements - HID Host +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HOGP_16_1 True No Security Requirements (LE Security Level 1, + No Security) (M.1) +TSPC_HOGP_16_2 True Unauthenticated no MITM protection (LE Security + Level 2, Just Works) (M.1) +TSPC_HOGP_16_3 True Authenticated MITM protection (LE Security + Level 3, Passkey) (O) +------------------------------------------------------------------------------- +M.1: Mandatory if TSPC_HOGP_1_2 or TSPC_HOGP_1_3 is selected +------------------------------------------------------------------------------- diff --git a/android/pics-hsp.txt b/android/pics-hsp.txt new file mode 100644 index 0000000..e55b986 --- /dev/null +++ b/android/pics-hsp.txt @@ -0,0 +1,103 @@ +HSP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HSP_0_1 False Version: Headset Profile v1.1 (C.1) +TSPC_HSP_0_2 True (*) Version: Headset Profile v1.2 (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support one and only one of these versions. +------------------------------------------------------------------------------- + + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HSP_1_1 True (*) Role: Audio Gateway (AG) (C.1) +TSPC_HSP_1_2 False Role: Headset (HS) (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + Audio Gateway Role +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HSP_2_1 True Incoming audio connection establishment (M) +TSPC_HSP_2_2 True (*) Ring (AT command) (C.3) +TSPC_HSP_2_3 False Inband ring tone (O) +TSPC_HSP_2_4 True (*) Outgoing audio connection establishment (O) +TSPC_HSP_2_5 True (*) Audio connection release from HS (C.5) +TSPC_HSP_2_6 True Audio connection release from AG (M) +TSPC_HSP_2_7 True Audio connection transfer: AG to HS (M) +TSPC_HSP_2_8 True Audio connection transfer: HS to AG (M) +TSPC_HSP_2_9 True (*) Remote audio volume control (C.1) +TSPC_HSP_2_10 True (*) HS informs AG about local changes of audio + volume (O) +TSPC_HSP_2_11 True (*) Audio volume setting storage by HS (O) +TSPC_HSP_2_12 False Remote microphone gain control (C.2) +TSPC_HSP_2_13 False HS informs AG about local changes of microphone + gain (O) +TSPC_HSP_2_14 False Microphone gain setting storage by HS (O) +TSPC_HSP_2_15 True Connection handling with Detach/Page (M) +TSPC_HSP_2_16 False Connection handling with Park Mode (C.4) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_HSP_2_10 is supported, otherwise optional +C:2: Mandatory if TSPC_HSP_2_13 is supported, otherwise optional +C.3: Excluded if TSPC_HSP_2_3 and TSPC_HSP_4_1 ("Show that in-band + ringing and RING are mutually exclusive") are supported, + otherwise optional +C.4: Excluded if TSPC_HSP_0_2 is supported, otherwise optional +C.5: Mandatory if TSPC_HSP_0_1 is supported, otherwise optional +------------------------------------------------------------------------------- + + + Headset Application Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HSP_3_1 False (*) Incoming audio connection establishment (M) +TSPC_HSP_3_2 False (*) Ring (AT command) (M) +TSPC_HSP_3_3 False (*) Inband ring tone (M) +TSPC_HSP_3_4 False (*) Outgoing audio connection establishment (M) +TSPC_HSP_3_5 False (*) Audio connection release from HS (M) +TSPC_HSP_3_6 False (*) Audio connection release from AG (M) +TSPC_HSP_3_7 False (*) Audio connection transfer: AG to HS (M) +TSPC_HSP_3_8 False (*) Audio connection transfer: HS to AG (M) +TSPC_HSP_3_9 False Remote audio volume control (C.1) +TSPC_HSP_3_10 False HS informs AG about local changes of audio + volume (O) +TSPC_HSP_3_11 False Audio volume setting storage by HS (O) +TSPC_HSP_3_12 False Remote microphone gain control (C.2) +TSPC_HSP_3_13 False HS informs AG about local changes of microphone + gain (O) +TSPC_HSP_3_14 False Microphone gain setting storage by HS (O) +TSPC_HSP_3_15 False (*) Connection handling with Detach/Page (M) +TSPC_HSP_3_16 False Connection handling with Park Mode (C.3) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_HSP_3_10 is supported, otherwise optional +C.2: Mandatory if TSPC_HSP_2_13 is supported, otherwise optional +C.3: Excluded if TSPC_HSP_0_2 is supported, otherwise optional +------------------------------------------------------------------------------- + + + Errata Service Releases +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_HSP_4_1 False Show that in-band ringing and RING are + mutually exclusive (C.1) +------------------------------------------------------------------------------- +C.1: Excluded if TSPC_HSP_0_2 is supported, otherwise optional +------------------------------------------------------------------------------- diff --git a/android/pics-iopt.txt b/android/pics-iopt.txt new file mode 100644 index 0000000..7277c4c --- /dev/null +++ b/android/pics-iopt.txt @@ -0,0 +1,223 @@ +IOPT PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Profiles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_support_ Support for: Advanced +AdvancedAudioDistributionProfile_Sink False Audio Distribution + Profile. Role: Sink + +TSPC_support_ Support for: Advanced +AdvancedAudioDistributionProfile_Source True (*) Audio Distribution + Profile. Role: Source + +TSPC_support_AVRemoteControlProfile_CT True (*) Support for: Audio\Video + Remote Control Profile. + Role: Controller + +TSPC_support_AVRemoteControlProfile_TG True (*) Support for: Audio\Video + Remote Control Profile. + Role: Target + +TSPC_support_BasicImagingProfile_CLIENT False Support for: Basic + Imaging Profile. + Role: Client + +TSPC_support_BasicImagingProfile_ Support for: Basic +SERVER_ImagingAutomaticArchive False Imaging Profile. Role: + Server Functionality: + Imaging autoarchive + +TSPC_support_BasicImagingProfile_ False Support for: Basic +SERVER_ImagingReferencedObjects Imaging Profile. Role: + Server Functionality: + Imaging ref. objects + +TSPC_support_BasicImagingProfile_ False Support for: Basic +SERVER_ImagingResponder Imaging Profile. Role: + Server Functionality: + Imaging responder + +TSPC_support_ False Support for: Basic +BasicPrintingProfile_PRINTER Printing Profile. Role: + Printer + +TSPC_support_ Support for: Basic +BasicPriProfile_PRINTER_ReflectedUI False Printing Profile. Role: + Printer Functionality: + Reflected UI + +TSPC_support_BasicPrintingProfile_ Support for: Basic +SENDER_Referenced_objects_Service False Printing Profile. Role: + Sender Functionality: + Refe. objects service + +TSPC_support_DialUpNetworkingProfile_DT False Support for: Dial-Up + Networking Profile. + Role: Data Terminal + +TSPC_support_DialUpNetworkingProfile_GW False Support for: Dial-Up + Networking Profile. + Role: Gateway + +TSPC_support_ False Support for: Extended +ExtendedServiceDiscoveryProfile_IP_LAP SDP. Version: IP-LAP + +TSPC_support_ False Support for: Extended +ExtendedServiceDiscoveryProfile_IP_PAN SDP. Version: IP-PAN + +TSPC_support_ False Support for: Extended +ExtendedServiceDiscoveryProfile_L2CAP SDP. Version: L2CAP + +TSPC_support_FAXProfile_DT False Support for: FAX Profile + Role: Data Terminal + +TSPC_support_FAXProfile_GW False Support for: FAX Profile + Role: Gateway + +TSPC_support_FileTransferProfile_CLIENT False Support for: FTP + Role: Client + +TSPC_support_FileTransferProfile_SERVER False Support for: FTP + Role: Server + +TSPC_support_HealthDeviceProfile_Sink True (*) Support for: HDP + Role: Sink + +TSPC_support_HealthDeviceProfile_Source False Support for: HDP + Role: Source + +TSPC_support_NewHandsFreeProfile_AG True (*) Support for: HFP + Role: Audio gateway + +TSPC_support_NewHandsFreeProfile_HF False Support for: HFP + Role: Hands-Free unit + +TSPC_support_ False Support for: Hard Copy +HardCopyReplacementProfile_ cable Repl. Profile +CLIENT_CR_RegisterNotofication_support Role: Client + Functionality: CR + register notification + support + +TSPC_support_ False Support for: Hard Copy +HardCopyReplacementProfile_CLIENT_print cable Repl. Profile. + Role: Client + Functionality: Print + +TSPC_support_ False Support for: Hard Copy +HardCopyReplacementProfile_CLIENT_scan cable Repl. Profile. + Role: Client + Functionality: Scan + +TSPC_support_ False Support for: Hard Copy +HardCopyReplacementProfile_SERVER_print cable Repl. Profile. + Role: Server + Functionality: Print + +TSPC_support_ False Support for: Hard Copy +HardCopyReplacementProfile_SERVER_scan cable Repl. Profile. + Role: Server + Functionality: Scan + +TSPC_support_HeadsetProfile_AG True (*) Support for: HSP + Role: Audio Gateway + +TSPC_support_HeadsetProfile_HS False Support for: HSP + Role: Headset + +TSPC_support_ False Support for: HID +HumanInterfaceDeviceProfile Role: Device + +TSPC_support_HID_Host True (*) Support for: HID + Role: Host + +TSPC_support_LANAccessProfile_DT False Support for: LAN Access + Profile. Role: Data + Terminal + +TSPC_support_LANAccessProfile_LAP False Support for: LAN Access + Profile. Role: LAN + Access Point + +TSPC_support_MessaeAccessProfile_MCE False Support for: MAP + Role: MCE + +TSPC_support_MessageAccessProfile_MSE True (*) Support for: MAP + Role: MSE + +TSPC_support_ObjectPushProfile_CLIENT True (*) Support for: OPP + Role: Client + +TSPC_support_ObjectPushProfile_SERVER True (*) Support for: OPP + Role: Server + +TSPC_support_ False Support for: PAN +PersonalAreaNetworkProfile_GN Role: GN + +TSPC_support_ True (*) Support for: PAN +PersonalAreaNetworkProfile_NAP Role: NAP + +TSPC_support_ True (*) Support for: PAN +PersonalAreaNetworkProfile_PANU Role: PANU + +TSPC_support_PhonebookAccessProfile_PCE False Support for: PBAP + Role: PCE + +TSPC_support_PhonebookAccessProfile_PSE True (*) Support for: PBAP + Role: PSE + +TSPC_support_SerialPortProfile_Service False Support for: SPP + Role: Dev B + +TSPC_support_ False Support for: Service +ServiceDiscoveryApplicationProfile Discovery Application + Profile + +TSPC_support_SIMAccessProfile_CLIENT False Support for: SIM access + Profile. Role: Client + +TSPC_support_SIMAccessProfile_SERVER False Support for: SIM access + Profile. Role: Server + +TSPC_support_ False Support for: +SynchronizationProfile_CLIENT Synchronization Profile + Role: Client + +TSPC_support_ False Support for: +SynchronizationProfile_SERVER Synchronization Profile + Role: Server + +TSPC_support_UDIProfile_MT False Support for: UDI Profile + Role: MT + +TSPC_support_UDIProfile_TA False Support for: UDI Profile + Role: TA + +TSPC_support_ False Support for: Video +VideoDistributionProfile_Sink distribution Profile + Role: Sink + +TSPC_support_ False Support for: Video +VideoDistributionProfile_Source distribution Profile + Role: Source + +TSPC_support_WAPOverBluetooth_CLIENT False Support for: WAP over + Bluetooth Profile + Role: Client + +TSPC_support_WAPOverBluetooth_PROXY False Support for: WAP over + Bluetooth Profile + Role: PROXY + +TSPC_support_GNSS_SERVER False Support for: GNSS + Role: Server diff --git a/android/pics-l2cap.txt b/android/pics-l2cap.txt new file mode 100644 index 0000000..ce50c98 --- /dev/null +++ b/android/pics-l2cap.txt @@ -0,0 +1,178 @@ +L2CAP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_L2CAP_1_1 True Data Channel Initiator (C.3) +TSPC_L2CAP_1_2 True Data Channel Acceptor (C.1) +TSPC_L2CAP_1_3 True LE Master (C.2) +TSPC_L2CAP_1_4 True LE Slave (C.2) +TSPC_L2CAP_1_5 True LE Data Channel Initiator (C.4) +TSPC_L2CAP_1_6 True LE Data Channel Acceptor (C.5) +------------------------------------------------------------------------------- +C.1: Mandatory IF BR/EDR or BR/EDR/LE is supported, otherwise Excluded. +C.2: Mandatory to support (at least one of TSPC_L2CAP_1_3 or TSPC_L2CAP_1_4) + IF LE or BR/EDR/LE is supported, otherwise Excluded. +C.3: Optional IF BR/EDR or BR/EDR/LE is supported, otherwise Excluded. +C.4: Optional IF LE or BR/EDR/LE and Core Spec v4.1 or Core Spec v4.1+HS and + TSPC_L2CAP_2_46 is supported, otherwise Excluded. +C.5: Mandatory IF LE or BR/EDR/LE and Core Spec v4.1 or Core Spec v4.1+HS and + TSPC_L2CAP_2_46 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + General Operation +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_L2CAP_2_1 True Support of L2CAP signaling channel (C.20) +TSPC_L2CAP_2_2 True Support of configuration process (C.20) +TSPC_L2CAP_2_3 True Support of connection oriented data + channel (C.20) +TSPC_L2CAP_2_4 True Support of command echo request (C.21) +TSPC_L2CAP_2_5 True Support of command echo response (C.20) +TSPC_L2CAP_2_6 True Support of command information request (C.21) +TSPC_L2CAP_2_7 True Support of command information response (C.20) +TSPC_L2CAP_2_8 False (*) Support of a channel group (C.21) +TSPC_L2CAP_2_9 False (*) Support of packet for connectionless + channel (C.21) +TSPC_L2CAP_2_10 False (*) Support retransmission mode (C.21) +TSPC_L2CAP_2_11 False (*) Support flow control mode(C.21) +TSPC_L2CAP_2_12 True Enhanced Retransmission Mode (C.1, C.13) +TSPC_L2CAP_2_13 True Streaming Mode (C.1, C.14) +TSPC_L2CAP_2_14 True FCS Option (C.2) +TSPC_L2CAP_2_15 True Generate Local Busy Condition (C.3) +TSPC_L2CAP_2_16 False (*) Send Reject (C.3) +TSPC_L2CAP_2_17 True Send Selective Reject (C.3) +TSPC_L2CAP_2_18 True Mandatory use of ERTM (C.4) +TSPC_L2CAP_2_19 True Mandatory use of Streaming Mode (C.5) +TSPC_L2CAP_2_20 True Optional use of ERTM (C.4) +TSPC_L2CAP_2_21 True Optional use of Streaming Mode (C.5) +TSPC_L2CAP_2_22 True Send data using SAR in ERTM (C.6) +TSPC_L2CAP_2_23 True Send data using SAR in Streaming Mode (C.7) +TSPC_L2CAP_2_24 True Actively request Basic Mode for a PSM that + supports the use of ERTM or Streaming + Mode (C.8) +TSPC_L2CAP_2_25 True Supports performing L2CAP channel mode + configuration fallback from SM + to ERTM (C.9) +TSPC_L2CAP_2_26 True Supports sending more than one unacknowledged + I-Frame when operating in ERTM (C.10) +TSPC_L2CAP_2_27 True Supports sending more than three unacknowledged + I-Frame when operating in ERTM (C.10) +TSPC_L2CAP_2_28 True Supports configuring the peer TxWindow + greater than 1 (C.11) +TSPC_L2CAP_2_29 False (*) AMP Support (C.12) +TSPC_L2CAP_2_30 True Fixed Channel Support (C.12) +TSPC_L2CAP_2_31 False (*) AMP Manager Support (C.12) +TSPC_L2CAP_2_32 False (*) ERTM over AMP (C.12) +TSPC_L2CAP_2_33 False (*) Streaming Mode Source over AMP Support (C.15) +TSPC_L2CAP_2_34 False (*) Streaming Mode Sink over AMP Support (C.15) +TSPC_L2CAP_2_35 True Unicast Connectionless Data, Reception + (C.1, C.16) +TSPC_L2CAP_2_36 True Ability to transmit an unencrypted packet over + a Unicast connectionless L2CAP + channel (C.16) +TSPC_L2CAP_2_37 True Ability to transmit an encrypted packet over + a Unicast connectionless L2CAP + channel (C.16) +TSPC_L2CAP_2_38 False (*) Extended Flow Specification for BR/EDR (C.8) +TSPC_L2CAP_2_39 False (*) Extended Window Size (C.8) +TSPC_L2CAP_2_40 True Support of Low Energy signaling channel (C.17) +TSPC_L2CAP_2_41 True Support of command reject (C.17) +TSPC_L2CAP_2_42 True Send Connection Parameter Update Request (C.18) +TSPC_L2CAP_2_43 True Send Connection Parameter Update Response (C.19) +TSPC_L2CAP_2_44 False (*) Extended Flow Specification for AMP (C.22) +TSPC_L2CAP_2_45 True Send disconnect request command (C.21) +TSCP_L2CAP_2_46 True Support LE Credit Based Flow Control + Mode (C.23) +TSCP_L2CAP_2_47 True Support for LE Data Channel (C.24) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of TSPC_L2CAP_2_12 OR TSPC_L2CAP_2_13 OR + TSPC_L2CAP_2_35 IF BR/EDR OR BR/EDR/LE AND SUM_ICS 31/7 (CSA1) OR + Core Spec v3.0 or later is supported, ELSE Excluded +C.2: Optional IF TSPC_L2CAP_2_12 OR TSPC_L2CAP_2_13 is claimed, ELSE Excluded. +C.3: Optional IF TSPC_L2CAP_2_12 AND TSPC_L2CAP_2_28 is claimed, ELSE Excluded. +C.4: IF TSPC_L2CAP_2_12 is claimed THEN either TSPC_L2CAP_2_18 + OR TSPC_L2CAP_2_20 is Mandatory, ELSE Excluded. +C.5: IF TSPC_L2CAP_2_13 is claimed THEN either TSPC_L2CAP_2_19 + OR TSPC_L2CAP_2_21 are Mandatory, ELSE Excluded. +C.6: Optional IF TSPC_L2CAP_2_12 is claimed, ELSE Excluded. +C.7: Optional IF TSPC_L2CAP_2_13 is claimed, ELSE Excluded. +C.8: Optional IF TSPC_L2CAP_2_12 OR TSPC_L2CAP_2_13 is claimed, ELSE Excluded. +C.9: Mandatory IF TSPC_L2CAP_2_12 AND TSPC_L2CAP_2_13 AND TSPC_L2CAP_2_21 + is claimed, ELSE Excluded. +C.10: Optional IF TSPC_L2CAP_2_12 is claimed, ELSE Excluded. +C.11: Optional IF TSPC_L2CAP_2_12 is claimed, ELSE Excluded. +C.12: Mandatory IF Core Spec v3.0+HS OR Core Spec v4.0+HS OR + Core Spec v4.1+HS OR Core Spec v4.2+HS is claimed, ELSE Optional. +C.13: Mandatory IF Core Spec v3.0+HS OR Core Spec v4.0+HS OR + Core Spec v4.1+HS OR Core Spec v4.2+HS is claimed, ELSE Optional. +C.14: Optional IF Core Spec v3.0 OR or later is claimed, ELSE Excluded. +C.15: Optional IF TSPC_L2CAP_2_29 is claimed, ELSE Excluded. +C.16: Optional IF Core Spec v3.0 or later is claimed, ELSE Excluded. +C.17: Mandatory IF LE OR BR/EDR/LE is claimed, ELSE Excluded. +C.18: Optional IF Core Spec 4.0 OR TSPC_L2CAP_1_4 is claimed, ELSE Excluded. +C.19: Mandatory IF Core Spec 4.0 AND TSPC_L2CAP_1_3 is claimed, ELSE Excluded. +C.20: Mandatory IF BR/EDR OR BR/EDR/LE, is claimed, ELSE Excluded +C.21: Optional IF BR/EDR OR BR/EDR/LE, is claimed, ELSE Excluded. +C.22: Mandatory IF TSPC_L2CAP_2_29 is claimed, ELSE Excluded. +C.23: Optional LE OR BR/EDR/LE AND Core Spec v4.1 OR Core Spec v4.1+HS OR + Core Spec v4.2 OR Core Spec v4.2+HS is supported, otherwise Excluded. +C.24: Mandatory IF TSPC_L2CAP_2_46 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Configurable Parameters +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_L2CAP_3_1 True Support of RTX timer (M) +TSPC_L2CAP_3_2 True Support of ERTX timer (C.4) +TSPC_L2CAP_3_3 True Support minimum MTU size 48 octets (C.4) +TSPC_L2CAP_3_4 True Support MTU size larger than 48 octets (C.5) +TSPC_L2CAP_3_5 True Support of flush timeout value for reliable + channel (C.4) +TSPC_L2CAP_3_6 False (*) Support of flush timeout value for unreliable + channel (C.5) +TSPC_L2CAP_3_7 False (*) Support of bi-directional quality of service + (QoS) option field (C.1) +TSPC_L2CAP_3_8 False (*) Negotiate QoS service type (C.5) +TSPC_L2CAP_3_9 False (*) Negotiate and support service type ‘No + traffic’ (C.2) +TSPC_L2CAP_3_10 False (*) Negotiate and support service type ‘Best + effort’ (C.3) +TSPC_L2CAP_3_11 False (*) Negotiate and support service type + ‘Guaranteed’ (C.2) +TSPC_L2CAP_3_12 True Support minimum MTU size 23 octets (C.6) +TSPC_L2CAP_3_13 False (*) Negotiate and support service type ‘No traffic’ + for Extended Flow Specification (C.7) +TSPC_L2CAP_3_14 False (*) Negotiate and support service type ‘Best Effort' + for Extended Flow Specification (C.8) +TSPC_L2CAP_3_15 False (*) Negotiate and support service type ‘Guaranteed’ + for Extended Flow Specification (C.9) +TSPC_L2CAP_3_16 True Support Multiple Simultaneous LE Data + Channels (C.10) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_L2CAP_3_8 is supported, ELSE Optional. +C.2: Optional if TSPC_L2CAP_3_8 is supported, ELSE Excluded. +C.3: Mandatory if TSPC_L2CAP_3_8 is supported, ELSE Excluded. +C.4: Mandatory IF BR/EDR OR BR/EDR/LE is claimed, ELSE Excluded. +C.5: Optional IF BR/EDR OR BR/EDR/LE is claimed, ELSE Excluded. +C.6: Mandatory IF LE OR BR/EDR/LE is claimed, ELSE Excluded. +C.7: Optional if TSPC_L2CAP_2_44 OR TSPC_L2CAP_2_38 is supported, ELSE Excluded. +C.8: Mandatory if TSPC_L2CAP_2_44 OR TSPC_L2CAP_2_38 is supported, + ELSE Excluded. +C.9: Optional if TSPC_L2CAP_2_44 OR TSPC_L2CAP_2_38 is supported, ELSE Excluded. +C.10: Optional IF TSPC_L2CAP_2_47 AND Core Spec 4.1 is supported, + otherwise Excluded. +------------------------------------------------------------------------------- diff --git a/android/pics-map.txt b/android/pics-map.txt new file mode 100644 index 0000000..b1346b6 --- /dev/null +++ b/android/pics-map.txt @@ -0,0 +1,175 @@ +MAP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MAP_0_1 False Role: Map 1.0 (C1) +TSPC_MAP_0_2 True (*) Role: Map 1.1 (C1) +TSPC_MAP_0_3 False Role: Map 1.2 (C1) +------------------------------------------------------------------------------- +C.1: Mandatory to support only one Profile version. +------------------------------------------------------------------------------- + + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MAP_1_1 True (*) Role: Messaging Server Equipment (C1) +TSPC_MAP_1_2 False Role: Messaging Client Equipment (C1) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + Supported features MCE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MAP_2_1 False MCE: Message Notification (C1) +TSPC_MAP_2_1a False MCE: SendEvent (C4) +TSPC_MAP_2_2 False MCE: Message Browsing (C1) +TSPC_MAP_2_2a False MCE: SetFolder (C5) +TSPC_MAP_2_2b False MCE: GetFoldersListing (C5) +TSPC_MAP_2_2c False MCE: GetMessagesListing (C5) +TSPC_MAP_2_2d False MCE: GetMessage (O) +TSPC_MAP_2_2e False MCE: SetMessageStatus (O) +TSPC_MAP_2_2f False MCE: UpdateInbox (O) +TSPC_MAP_2_2g False MCE: Filtering (O) +TSPC_MAP_2_2h False MCE: Multiple simultaneous MAS instances (O) +TSPC_MAP_2_3 False MCE: Message Uploading (O) +TSPC_MAP_2_3a False MCE: SetFolder (C6) +TSPC_MAP_2_3b False MCE: GetFoldersListing (C6) +TSPC_MAP_2_3c False MCE: PushMessage (C6) +TSPC_MAP_2_4 False MCE: Message Delete (O) +TSPC_MAP_2_4a False MCE: SetMessageStatus (C7) +TSPC_MAP_2_5 False MCE: Notification Registration (C2) +TSPC_MAP_2_5a False MCE: SetNotificationRegistration off (O) +TSPC_MAP_2_5b False MCE: SetNotificationRegistration on (C8) +TSPC_MAP_2_6 False MCE: Supported Message Types +TSPC_MAP_2_6a False (*) MCE: EMAIL (C3) +TSPC_MAP_2_6b False (*) MCE: SMS_GSM (C3) +TSPC_MAP_2_6c False (*) MCE: SMS_CDMA (C3) +TSPC_MAP_2_6d False (*) MCE: MMS (C3) +TSPC_MAP_2_7 False MCE: Instance Information (Not Supported) +TSPC_MAP_2_7a False (*) MCE: GetMASInstanceInformation (Not Supported) +TSPC_MAP_2_8 False MCE: Extended MAP-Event-Report (Not Supported) +TSPC_MAP_2_8a False (*) MCE: MAP-Event-Report: Version 1.1 + (Not Supported) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of the defined features TSPC_MAP_2_1 or + TSPC_MAP_2_2. +C.2: Mandatory to support TSPC_MAP_2_5 if TSPC_MAP_2_1 is supported. +C.3: Mandatory to support at least one of the defined message types + TSPC_MAP_2_6a to TSPC_MAP_2_6d IF TSPC_MAP_2_2 or TSPC_MAP_2_3 is + supported. +C.4: Support of functionality TSPC_MAP_2_1a mandatory IF related feature + TSPC_MAP_2_1 supported. +C.5: Support of functionality mandatory IF TSPC_MAP_2_2 supported. +C.6: Support of functionality mandatory IF TSPC_MAP_2_3 supported. +C.7: Support of functionality mandatory IF TSPC_MAP_2_4 supported. +C.8: Mandatory to support IF TSPC_MAP_2_5 (Notification Registration) is + supported, otherwise excluded. +C.9: Optional to support IF TSPC_MAP_0_3 (MAP v1.2) is supported, otherwise + excluded. +C.10: Mandatory to support IF TSPC_MAP_0_3 (MAP v1.2) and TSPC_MAP_2_1 + (Message Notification) is supported, otherwise excluded. +------------------------------------------------------------------------------- + + + Supported features MSE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MAP_3_1 True MSE: Message Notification (M) +TSPC_MAP_3_1a True MSE: SendEvent (M) +TSPC_MAP_3_2 True MSE: Message Browsing (M) +TSPC_MAP_3_2a True MSE: SetFolder (M) +TSPC_MAP_3_2b True MSE: GetFoldersListing (M) +TSPC_MAP_3_2c True MSE: GetMessagesListing (M) +TSPC_MAP_3_2d True MSE: GetMessage (M) +TSPC_MAP_3_2e True MSE: SetMessageStatus (M) +TSPC_MAP_3_2f True MSE: UpdateInbox (M) +TSPC_MAP_3_2g False MSE: Multiple simultaneous MAS instances (O) +TSPC_MAP_3_3 True MSE: Message Uploading (M) +TSPC_MAP_3_3a True MSE: SetFolder (M) +TSPC_MAP_3_3b True MSE: GetFoldersListing (M) +TSPC_MAP_3_3c True MSE: PushMessage (M) +TSPC_MAP_3_4 True MSE: Message Delete (M) +TSPC_MAP_3_4a True MSE: SetMessageStatus (M) +TSPC_MAP_3_5 True MSE: Notification Registration (M) +TSPC_MAP_3_5a True MSE: SetNotificationRegistration (M) +TSPC_MAP_3_6 False MSE: Supported Message Types +TSPC_MAP_3_6a False MSE: EMAIL (C1) +TSPC_MAP_3_6b True MSE: SMS_GSM (C1) +TSPC_MAP_3_6c True (*) MSE: SMS_CDMA (C1) +TSPC_MAP_3_6d True MSE: MMS (C1) +TSPC_MAP_3_7 False MSE: Instance Information (Not Supported) +TSPC_MAP_3_7a False (*) MSE: GetMASInstanceInformation (Not Supported) +TSPC_MAP_3_8 False MSE: Extended MAP-Event-Report (Not Supported) +TSPC_MAP_3_8a False (*) MSE: MAP-Event-Report: Version 1.1 + (Not Supported) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of the defined message types + TSPC_MAP_3_6a to TSPC_MAP_3_6d IF TSPC_MAP_3_2 or TSPC_MAP_3_3 + is supported. +C.2: Mandatory to support IF TSPC_MAP_0_3 (MAP v1.2) is supported, + otherwise excluded. +------------------------------------------------------------------------------- + + + GOEP v2.0 or later Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MAP_7b_1 False GOEP v2.0 or later (C1) +TSPC_MAP_7b_2 False GOEP v2 Backwards Compatibility (C1) +TSPC_MAP_7b_3 False OBEX over L2CAP (C1) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_MAP_0_3 (MAP v1.2) is supported else excluded. +------------------------------------------------------------------------------- + + + MCE OBEX Header Support +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MAP_10_1 False (*) Name (M) +TSPC_MAP_10_2 False (*) Typr (M) +TSPC_MAP_10_3 False (*) Body (M) +TSPC_MAP_10_4 False (*) End of Body (M) +TSPC_MAP_10_5 False (*) Target (M) +TSPC_MAP_10_6 False (*) Who (M) +TSPC_MAP_10_7 False (*) Connection ID (M) +TSPC_MAP_10_8 False (*) Application Parameters (M) +TSPC_MAP_10_9 False SRM (C2) +TSPC_MAP_10_10 False Receive SRMP (C2) +TSPC_MAP_10_11 False Send SRMP (C2) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_MAP_0_3 (MAP v1.2) is supported else excluded. +C.2: Optional if TSPC_MAP_0_3 (MAP v1.2) is supported else excluded. +------------------------------------------------------------------------------- + + + GetMessagesListing Filtering Parameter Support +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MAP_20_1 False (*) MCE: FilterMessageType (O) +TSPC_MAP_20_2 False (*) MCE: FilterPeriodBegin (O) +TSPC_MAP_20_3 False (*) MCE: FilterPeriodEnd (O) +TSPC_MAP_20_4 False (*) MCE: FilterReadStatus (O) +TSPC_MAP_20_5 False (*) MCE: FilterRecipient (O) +TSPC_MAP_20_6 False (*) MCE: FilterOriginator (O) +TSPC_MAP_20_7 False (*) MCE: FilterPriority (O) +TSPC_ALL False (*) Turns on all the test cases +------------------------------------------------------------------------------- diff --git a/android/pics-mcap.txt b/android/pics-mcap.txt new file mode 100644 index 0000000..218ee80 --- /dev/null +++ b/android/pics-mcap.txt @@ -0,0 +1,141 @@ +MCAP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Protocols +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MCAP_1_A_1 True (*) Supports Standard Op Codes (C.1) +TSPC_MCAP_1_A_2 True (*) Supports Clock Synchronization Protocol (C.1) +------------------------------------------------------------------------------- +C.1: Support for at least one of the defined protocols is Mandatory. +------------------------------------------------------------------------------- + + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MCAP_1_1 True (*) Supports Source Role (C.1) +TSPC_MCAP_1_2 True (*) Supports Sink Role (C.1) +TSPC_MCAP_1_3 False Supports Sync-Slave Role (C.2) +TSPC_MCAP_1_4 False Supports Sync-Master Role (C.3) +------------------------------------------------------------------------------- +C.1: If support for TSPC_MCAP_1_A_1 is supported, at least one of the + defined roles is Mandatory otherwise Excluded. +C.2: Mandatory if TSPC_MCAP_1_A_2 is supported, otherwise Excluded. +C.3: Optional if TSPC_MCAP_1_A_2 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + L2CAP Features - Source +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MCAP_2_1 True (*) Supports L2CAP Control Channel (M) +TSPC_MCAP_2_2 True (*) Supports at least one L2CAP Data Channel (M) +TSPC_MCAP_2_3 True (*) Maximum number of simultaneous L2CAP Data + Channels supported (DCmax) per MCL (M) +TSPC_MCAP_2_4 False Can support multiple simultaneous MCLs with + Standard Op Codes (O) +------------------------------------------------------------------------------- + + + Connection Management - Source +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MCAP_3_1 This row intentionally left blank +TSPC_MCAP_3_2 True (*) Initiate creation of Control and Data Channels + (C.1) +TSPC_MCAP_3_3 True (*) Accept creation of Control and Data Channels + (C.1) +TSPC_MCAP_3_4 True (*) Initiate Disconnection of MCL (M) +TSPC_MCAP_3_5 True (*) Accept Disconnection of MCL (M) +TSPC_MCAP_3_6 True (*) Initiate Disconnection of MDL (M) +TSPC_MCAP_3_7 True (*) Accept Disconnection of MDL (M) +TSPC_MCAP_3_8 False Initiate Reconnection of MDL (O) +TSPC_MCAP_3_9 False Accept Reconnection of MDL (C.2) +TSPC_MCAP_3_10 False Initiate Deletion of MDL (O) +TSPC_MCAP_3_11 True (*) Accept Deletion of MDL (M) +TSPC_MCAP_3_12 False Initiate Delete of All MDLs using 0xFFFF (O) +TSPC_MCAP_3_13 True (*) Accept Delete of All MDLs using 0xFFFF (M) +TSPC_MCAP_3_14 False Send MDL Abort request (O) +TSPC_MCAP_3_15 True (*) Accept MDL Abort request (M) +------------------------------------------------------------------------------- +C.1: Support for at least one of TSPC_MCAP_3_2 or TSPC_MCAP_3_3 is Mandatory. +C.2: Mandatory if TSPC_MCAP_3_3 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + L2CAP Features - Sink +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MCAP_4_1 True (*) Supports L2CAP Control Channel (M) +TSPC_MCAP_4_2 True (*) Supports at least one L2CAP Data Channel (M) +TSPC_MCAP_4_3 True (*) Maximum number of simultaneous L2CAP Data + Channels supported (DCmax) per MCL (M) +TSPC_MCAP_4_4 False Can support multiple simultaneous MCLs with + Standard Op Codes (O) +------------------------------------------------------------------------------- + + + Connection Management - Sink +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MCAP_5_1 This row intentionally left blank +TSPC_MCAP_5_2 True (*) Initiate creation of Control and Data Channels + (M) +TSPC_MCAP_5_3 True (*) Accept creation of Control and Data Channels + (M) +TSPC_MCAP_5_4 True (*) Initiate Disconnection of MCL (M) +TSPC_MCAP_5_5 True (*) Accept Disconnection of MCL (M) +TSPC_MCAP_5_6 True (*) Initiate Disconnection of MDL (M) +TSPC_MCAP_5_7 True (*) Accept Disconnection of MDL (M) +TSPC_MCAP_5_8 False Initiate Reconnection of MDL (O) +TSPC_MCAP_5_9 True (*) Accept Reconnection of MDL (M) +TSPC_MCAP_5_10 False Initiate Deletion of MDL (O) +TSPC_MCAP_5_11 True (*) Accept Deletion of MDL (M) +TSPC_MCAP_5_12 False Initiate Delete of All MDLs using 0xFFFF (O) +TSPC_MCAP_5_13 True (*) Accept Delete of All MDLs using 0xFFFF (M) +TSPC_MCAP_5_14 False Send MDL Abort request (O) +TSPC_MCAP_5_15 True (*) Accept MDL Abort request (M) +------------------------------------------------------------------------------- + + + Clock Synchronization Features - Sync-Slave +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MCAP_6_1 False (*) Accept MD_SYNC_CAP_REQ and MD_SYNC_SET_REQ and + Initiate MD_SYNC_INFO_IND (C.1) +TSPC_MCAP_6_2 This row intentionally left blank +TSPC_MCAP_6_3 False Can support multiple simultaneous MCLs with CSP + (O) +TSPC_MCAP_6_4 False Can access Bluetooth Clock (O) +------------------------------------------------------------------------------- +C.1: Mandatory if MCAP TSPC_MCAP_1_A_2 is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Clock Synchronization Features - Sync-Master +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MCAP_7_1 This row intentionally left blank +TSPC_MCAP_7_2 False (*) Initiate MD_SYNC_CAP_REQ and MD_SYNC_SET_REQ + and Accept MD_SYNC_INFO_IND (C.1) +TSPC_MCAP_7_3 False Can support multiple simultaneous MCLs with CSP (O) +TSPC_MCAP_7_4 False (*) Can access Bluetooth Clock (O) +------------------------------------------------------------------------------- +C.1: Mandatory to support IF TSPC_MCAP_1_A_2 is supported. +------------------------------------------------------------------------------- diff --git a/android/pics-mps.txt b/android/pics-mps.txt new file mode 100644 index 0000000..5aad7c1 --- /dev/null +++ b/android/pics-mps.txt @@ -0,0 +1,337 @@ +MPS PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MPS_0_1 True MPS v1.0 (M) +------------------------------------------------------------------------------- + + + Profile Version Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MPS_1_1 True (*) A2DP 1.2 or later (O) +TSPC_MPS_1_2 True (*) AVRCP 1.3 or later (O) +TSPC_MPS_1_3 False DUN 1.1 or later (O) +TSPC_MPS_1_4 True (*) HFP 1.5 or later (O) +TSPC_MPS_1_5 True (*) PAN 1.0 or later (O) +TSPC_MPS_1_6 True (*) PBAP 1.1 or later (O) +------------------------------------------------------------------------------- + + + Profile Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MPS_2_1 True (*) A2DP Source (SRC) (C.1) +TSPC_MPS_2_2 False A2DP Sink (SNK) (C.1) +TSPC_MPS_2_3 True (*) AVRCP Controller (CT) (C.1) +TSPC_MPS_2_4 True (*) AVRCP Target (TG) (C.1) +TSPC_MPS_2_5 False DUN Gateway (GW) (C.1) +TSPC_MPS_2_6 False DUN Data Terminal (DT) (C.1) +TSPC_MPS_2_7 True (*) HFP Audio Gateway (AG) (C.1) +TSPC_MPS_2_8 False HFP Hands-Free (HF) (C.1) +TSPC_MPS_2_9 True (*) PAN Network Access Point (NAP) (C.1) +TSPC_MPS_2_10 False PAN Group Ad-hoc Network (GN) (C.1) +TSPC_MPS_2_11 True (*) PAN User (PANU) (C.1) +TSPC_MPS_2_12 False PBAP PCE (C.1) +TSPC_MPS_2_13 True (*) PBAP PSE (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to declare each role as supported within the represented Profile + otherwise Excluded. The roles declared shall match that of the roles + supported within the Profile. +------------------------------------------------------------------------------- + + + Profile Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MPS_3_1 True (*) Receiving PASS THROUGH command in Category 1 + (AVRCP - TG) (C.1) +TSPC_MPS_3_2 True (*) Receiving PASS THROUGH command in Category 1 + (AVRCP - TG) - PAUSE (C.1) +TSPC_MPS_3_3 False Sending PASS THROUGH command in Category 1 + (AVRCP - CT) - PLAY (C.2) +TSPC_MPS_3_4 False Sending PASS THROUGH command in Category 1 + (AVRCP - CT) - PAUSE (C.2) +TSPC_MPS_3_5 True (*) Transfer Control - Suspend (GAVDP - Initiator) + (C.3) +TSPC_MPS_3_6 True (*) Transfer Control - Suspend (GAVDP - Acceptor) + (C.4) +TSPC_MPS_3_7 False Accept an incoming voice call (in-band ring) + (C.5) +TSPC_MPS_3_8 True (*) Accept an incoming voice call (no in-band ring) + (C.5) +TSPC_MPS_3_9 False Place a call with a phone number supplied by + the HF (C.6) +TSPC_MPS_3_10 True (*) Register Notification: PLAYBACK_STATUS_CHANGED + (C.7) +TSPC_MPS_3_11 True (*) Ability to support parallel data and call + operation (O) +TSPC_MPS_3_12 True (*) PBAP Phone Book Download (C.8) +TSPC_MPS_3_13 True (*) Ability to support multiple concurrent device + connections (O) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_MPS_2_1 (A2DP Source role) and TSPC_MPS_2_4 (AVRCP + Target role) are supported, otherwise Excluded. +C.2: Mandatory if TSPC_MPS_2_2 (A2DP Sink role) and TSPC_MPS_2_3 (AVRCP + Controller role) are supported, otherwise Excluded. +C.3: Mandatory if TSPC_MPS_1_4 (HFP 1.5 or later) and TSPC_MPS_2_1 (A2DP Source + role) are supported; Optional if TSPC_MPS_1_4 (HFP 1.5 or later) and + TSPC_MPS_2_2 (A2DP Sink role) are supported, otherwise Excluded. +C.4: Mandatory if TSPC_MPS_1_4 (HFP 1.5 or later) and TSPC_MPS_2_1 (A2DP Source + role) or TSPC_MPS_2_2 (A2DP Sink role) are supported, otherwise + Excluded. +C.5: Mandatory to support at least one if TSPC_MPS_1_4 (HFP 1.5 or later) is + supported, otherwise Excluded. +C.6: Mandatory if TSPC_MPS_1_4 (HFP 1.5 or later) and HFP 3/8 (Place a call with + a phone number supplied by the HF) are supported, otherwise Excluded. +C.7: Mandatory if TSPC_MPS_2_3 (AVRCP Controller role) is supported, otherwise + Excluded. +C.8: Mandatory if TSPC_MPS_1_6 (PBAP 1.1 or later) and PBAP 2/1 (Phone Book + Download) are supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Device Capability Support +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MPS_4_1 True Multiple Profiles Single Device (MPSD) (M) +TSPC_MPS_4_2 True (*) Multiple Profiles Multiple Devices (MPMD) (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_MPS_3_13 (Ability to support multiple concurrent device + connections), otherwise Excluded. +------------------------------------------------------------------------------- + + + MPSD scenarios +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MPS_6_1 True (*) HFP-AG and A2DP-SRC Implementation Answer + Incoming Call during Audio Streaming (C.1) +TSPC_MPS_6_2 False HFP-HF and A2DP-SNK Implementation Answer + Incoming Call during Audio Streaming (C.2) +TSPC_MPS_6_3 True (*) HFP-AG and A2DP-SRC Implementation Outgoing + Call during Audio Streaming (C.1) +TSPC_MPS_6_4 False HFP-HF and A2DP-SNK Implementation Outgoing + Call during Audio Streaming (C.2) +TSPC_MPS_6_5 True (*) HFP-AG and A2DP-SRC Implementation Reject/Ignore + Incoming Call during Audio Streaming (C.1) +TSPC_MPS_6_6 False HFP-HF and A2DP-SNK Implementation Reject/Ignore + Incoming Call during Audio Streaming (C.2) +TSPC_MPS_6_7 True (*) HFP-AG and A2DP-SRC Implementation HFP Call + Termination during AVP Connection (C.1) +TSPC_MPS_6_8 False HFP-HF and A2DP-SNK Implementation HFP Call + Termination during AVP Connection (C.2) +TSPC_MPS_6_9 True (*) HFP-AG and A2DP-SRC Implementation Press Play + on Audio Player during Active Call (C.1) +TSPC_MPS_6_10 False HFP-HF and A2DP-SNK Implementation Press Play + on Audio Player during Active Call (C.2) +TSPC_MPS_6_11 True (*) HFP-AG and A2DP-SRC Implementation Start Audio + Streaming after AVRCP Play Command (C.1) +TSPC_MPS_6_12 False HFP-HF and A2DP-SNK Implementation Start Audio + Streaming after AVRCP Play Command (C.2) +TSPC_MPS_6_13 True (*) HFP-AG and A2DP-SRC Implementation Suspend Audio + Streaming after AVRCP Pause/Stop (C.1) +TSPC_MPS_6_14 False HFP-HF and A2DP-SNK Implementation Suspend Audio + Streaming after AVRCP Pause/Stop (C.2) +TSPC_MPS_6_15 False HFP-AG and DUN-GW Implementation Data + Communication under PSDM (DUN) during Active + Voice Call (C.3) +TSPC_MPS_6_16 False HFP-HF and DUN-DT Implementation Data + Communication under PSDM (DUN) during Active + Voice call (C.4) +TSPC_MPS_6_17 False HFP-AG and DUN-GW Implementation Outgoing Voice + Call during Data Communication under PSDM (DUN) + (C.3) +TSPC_MPS_6_18 False HFP-HF and DUN-DT Implementation Outgoing Voice + Call during Data Communication under PSDM (DUN) + (C.4) +TSPC_MPS_6_19 False HFP-AG and DUN-GW Implementation Incoming Voice + Call during Data Communication under PSDM (DUN) + (C.3) +TSPC_MPS_6_20 False HFP-HF and DUN-DT Implementation Incoming Voice + Call during Data Communication under PSDM (DUN) + (C.4) +TSPC_MPS_6_21 False A2DP-SRC and DUN-GW Implementation Start Audio + Streaming during Data Communication under PSDM + (DUN) (C.5) +TSPC_MPS_6_22 False A2DP-SNK and DUN-DT Implementation Start Audio + Streaming during Data Communication under PSDM + (DUN) (C.6) +TSPC_MPS_6_23 False A2DP-SRC and DUN-GW Implementation Data + Communication Establishment under PSDM (DUN) + during Audio Streaming (C.5) +TSPC_MPS_6_24 False A2DP-SNK and DUN-DT Implementation Data + Communication Establishment under PSDM (DUN) + during Audio Streaming (C.6) +TSPC_MPS_6_25 False HFP-AG and DUN-GW Implementation Terminate + Voice Call/Data Call during Data Communication + and Voice Call (C.5) +TSPC_MPS_6_26 False HFP-HF and DUN-DT Implementation Terminate + Voice Call/Data Call during Data Communication + and Voice Call (C.6) +TSPC_MPS_6_27 True (*) HFP-AG and PAN-NAP Implementation Data + Communication in Personal Area Network during + Active Voice Call (C.7) +TSPC_MPS_6_28 False HFP-HF and PAN-PANU Implementation Data + Communication in Personal Area Network during + Active Voice Call (C.8) +TSPC_MPS_6_29 True (*) HFP-AG and PAN-NAP Implementation Outgoing + Voice Call during Data Communication in Personal + Area Network (C.7) +TSPC_MPS_6_30 False HFP-HF and PAN-PANU Implementation Outgoing + Voice Call during Data Communication in Personal + Area Network (C.8) +TSPC_MPS_6_31 True (*) HFP-AG and PAN-NAP Implementation Incoming Voice + Call during Data Communication in Personal Area + Network (C.7) +TSPC_MPS_6_32 False HFP-HF and PAN-PANU Implementation Incoming + Voice Call during Data Communication in Personal + Area Network (C.8) +TSPC_MPS_6_33 True (*) A2DP-SRC and PAN-NAP Implementation Start Audio + Streaming during Data Communication in Personal + Area Network (C.9) +TSPC_MPS_6_34 False A2DP-SNK and PAN-PANU Implementation Start Audio + Streaming during Data Communication in Personal + Area Network (C.10) +TSPC_MPS_6_35 True (*) A2DP-SRC and PAN-NAP Implementation Data + Communication Establishment in Personal Area + Network during Audio Streaming (C.9) +TSPC_MPS_6_36 False A2DP-SNK and PAN_PANU Implementation Data + Communication Establishment in Personal Area + Network during Audio Streaming (C.10) +TSPC_MPS_6_37 True (*) A2DP-SRC_PBAP-Server Implementation Phonebook + Download during Audio Streaming (C.11) +TSPC_MPS_6_38 False A2DP-SNK and PBAP-Client Implementation + Phonebook Download during Audio Streaming (C.12) +TSPC_MPS_6_39 True (*) HFP-AG and PBAP-Server Implementation PBAP and + HFP Connection Behaviour (C.13) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_MPS_2_1, TSPC_MPS_2_4 and TSPC_MPS_2_7 are supported, + otherwise Excluded. +C.2: Mandatory if TSPC_MPS_2_2, TSPC_MPS_2_3 and TSPC_MPS_2_8 are supported, + otherwise Excluded. +C.3: Mandatory if TSPC_MPS_2_5 and TSPC_MPS_2_7 are supported and TSPC_MPS_3_9, + otherwise Excluded. +C.4: Mandatory if TSPC_MPS_2_6 and TSPC_MPS_2_8 are supported, otherwise + Excluded. +C.5: Mandatory if TSPC_MPS_2_1 and TSPC_MPS_2_5 are supported, otherwise + Excluded. +C.6: Mandatory if TSPC_MPS_2_2 and TSPC_MPS_2_6 are supported, otherwise + Excluded. +C.7: Mandatory if TSPC_MPS_2_7 and TSPC_MPS_2_9 and TSPC_MPS_3_11 are + supported, otherwise Excluded. +C.8: Mandatory if TSPC_MPS_2_8 and TSPC_MPS_2_11 are supported and + TSPC_MPS_3_11, otherwise Excluded. +C.9: Mandatory if TSPC_MPS_2_1 and TSPC_MPS_2_9 are supported, otherwise + Excluded. +C.10: Mandatory if TSPC_MPS_2_2 and TSPC_MPS_2_11 are supported, otherwise + Excluded. +C.11: Mandatory if TSPC_MPS_2_1 and TSPC_MPS_2_13 are supported, otherwise + Excluded. +C.12: Mandatory if TSPC_MPS_2_2 and TSPC_MPS_2_12 are supported, otherwise + Excluded. +C.13: Mandatory if TSPC_MPS_2_7 and TSPC_MPS_2_13 are supported, otherwise + Excluded. +------------------------------------------------------------------------------- + + + MPMD Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MPS_7_1 False HFP-HF and A2DP-SNK and AVRCP-CT Implementation + Answer Incoming Call during Audio Streaming + (C.1) +TSPC_MPS_7_2 True (*) A2DP-SRC and AVRCP-TG Implementation Answer + Incoming Call during Audio Streaming (C.2) +TSPC_MPS_7_3 False HFP-HF and A2DP-SNK and AVRCP-CT Implementation + Outgoing Call during Audio Streaming (C.1) +TSPC_MPS_7_4 True (*) A2DP-SRC and AVRCP-TG Implementation Outgoing + Call during Audio Streaming (C.2) +TSPC_MPS_7_5 False HFP-HF and A2DP-SNK and AVRCP-CT Implementation + Reject/Ignore Incoming Call during Audio + Streaming (C.1) +TSPC_MPS_7_6 True (*) A2DP-SRC and AVRCP-TG Implementation + Reject/Ignore Incoming Call during Audio + Streaming (C.2) +TSPC_MPS_7_7 False HFP-HF and A2DP-SNK and AVRCP-CT Implementation + HFP Call Termination during AVP Connection (C.1) +TSPC_MPS_7_8 True (*) A2DP-SRC and AVRCP-TG Implementation HFP Call + Termination during AVP Connection (C.2) +TSPC_MPS_7_9 False HFP-HF and A2DP-SNK and AVRCP-CT Implementation + Press Play on Audio Player during Active Call + (C.1) +TSPC_MPS_7_10 True (*) A2DP-SRC and AVRCP-TG Implementation Press Play + on Audio Player during Active Call (C.2) +TSPC_MPS_7_11 True (*) A2DP-SRC and AVRCP-TG Implementation Start Audio + Streaming during Data Communication under PSDM + (C.2) +TSPC_MPS_7_12 False A2DP-SNK and AVRCP-CT and DUN-DT Implementation + Start Audio Streaming during Data Communication + under PSDM (C.3) +TSPC_MPS_7_13 True (*) A2DP-SRC and AVRCP-TG Implementation Start + Packet Data Communication during Audio Streaming + (C.2) +TSPC_MPS_7_14 False A2DP-SNK and AVRCP-CT and DUN-DT Implementation + Start Packet Data Communication during Audio + Streaming (C.3) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_MPS_2_2, TSPC_MPS_2_3 and TSPC_MPS_2_8 are supported, + otherwise Excluded. +C.2: Mandatory if TSPC_MPS_2_1 and TSPC_MPS_2_4 are supported, otherwise + Excluded. +C.3: Mandatory if TSPC_MPS_2_2, TSPC_MPS_2_3 and 2/6TSPC_MPS_2_6 supported, + otherwise Excluded. +------------------------------------------------------------------------------- + + + MPS Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MPS_8_1 True (*) AVP Suspension (C.1) +TSPC_MPS_8_2 True (*) Profile (Dis-)Connection behaviour (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_MPS_1_1 and TSPC_MPS_1_2 are supported, otherwise + Excluded. +C.2: Mandatory if TSPC_MPS_1_1, TSPC_MPS_1_2 and TSPC_MPS_1_4 are supported, + otherwise Excluded. +------------------------------------------------------------------------------- + + + MPS Dependencies +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MPS_9_1 True Implements Bluetooth Core Specification v2.1 + + EDR or later (M) +------------------------------------------------------------------------------- + + + MPS Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_MPS_10_1 True SDP Record (M) +TSPC_MPS_10_2 True (*) Media Stream Suspension (C.1) +TSPC_MPS_10_3 True (*) Sniff Mode during Streaming (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_MPS_1_1 and TSPC_MPS_1_4 are supported, otherwise + Excluded. +C.2: Mandatory if TSPC_MPS_1_1 is supported, otherwise Excluded. +------------------------------------------------------------------------------- diff --git a/android/pics-opp.txt b/android/pics-opp.txt new file mode 100644 index 0000000..145198d --- /dev/null +++ b/android/pics-opp.txt @@ -0,0 +1,187 @@ +OPP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_1_1 True (*) Role: Object Push Client (C.1) +TSPC_OPP_1_2 True (*) Role: Object Push Server (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + Client Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_1b_1 True (*) Client supports OPP version 1.1. (C.1) +TSPC_OPP_1b_2 False Client supports OPP version 1.2. (C.1) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the profile versions. +------------------------------------------------------------------------------- + + + Client Application Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_2_1 True Client: Perform Service Discovery request (M) +TSPC_OPP_2_2 True Client: Authentication/PIN exchange supported. + (M) +TSPC_OPP_2_2a True (*) Client: Require Authentication/PIN by default. + (O) +TSPC_OPP_2_3 True Client: Object Push (M) +TSPC_OPP_2_4 True (*) Client: vCard 2.1 (C.3) +TSPC_OPP_2_5 False Client: vCalender 1.0 (O) +TSPC_OPP_2_6 False Client: vMsg as defined in IrMC 1.1 (O) +TSPC_OPP_2_7 False Client: vNote as defined in IrMC 1.1 (O) +TSPC_OPP_2_8 True (*) Client: Support content formats other than those + declared in TSPC_OPP_2_4 through + TSPC_OPP_2_7. (O) +TSPC_OPP_2_8a False Client: Support specific set of other content + formats. (C.4) +TSPC_OPP_2_8b True (*) Client: Support all content formats. (C.4) +TSPC_OPP_2_9 True (*) Client: Push multiple vCard objects. (O) +TSPC_OPP_2_9a False Client: Push multiple vCard objects using + different PUT operations. (C.5) +TSPC_OPP_2_9b True (*) Client: Push multiple vCard objects using the + same PUT operation. (C.5) +TSPC_OPP_2_10 False Client: Push multiple vCalender objects. (O) +TSPC_OPP_2_10a False Client: Push multiple vCalendar objects using + different PUT operations. (C.6) +TSPC_OPP_2_10b False Client: Push multiple vCalendar objects using + the same PUT operation. (C.6) +TSPC_OPP_2_11 False Client: Push multiple vMsg objects. (O) +TSPC_OPP_2_11a False Client: Push multiple vMsg objects using + different PUT operations. (C.7) +TSPC_OPP_2_11b False Client: Push multiple vMsg objects using the + same PUT operation. (C.7) +TSPC_OPP_2_12 False Client: Push multiple vNote objects. (O) +TSPC_OPP_2_12a False Client: Push multiple vNote objects using + different PUT operations. (C.8) +TSPC_OPP_2_12b False Client: Push multiple vNote objects using the + same PUT operation. (C.8) +TSPC_OPP_2_13 False Client: Pull business card (O) +TSPC_OPP_2_14 False Client: vCard 2.1 (C.1) +TSPC_OPP_2_15 False Client: Exchange business card (O) +TSPC_OPP_2_16 False Client: vCard 2.1 (C.2) +TSPC_OPP_2_17 False GOEP v2 (C.9) +TSPC_OPP_2_18 False GOEP v2 Backward Compability (C.9) +TSPC_OPP_2_19 False OBEX over L2CAP (C.9) +TSPC_OPP_2_20 False OBEX Reliable Session (C.10) +TSPC_OPP_2_21 False OBEX SRM (C.10) +TSPC_OPP_2_22 False Send OBEX SRMP header (C.10) +TSPC_OPP_2_23 False Receive OBEX SRMP header (C.11) +------------------------------------------------------------------------------- +C.1: Mandatory to Support IF (TSPC_OPP_2_13) Business Card Pull is supported. +C.2: Mandatory to Support IF (TSPC_OPP_2_15) Business Card Exchange is + supported. +C.3: vCard 2.1 support is required for devices containing phonebook + applications. vCard 2.1 support optional for other devices. +C.4: Mandatory to support one of TSPC_OPP_2_8a or TSPC_OPP_2_8b if TSPC_OPP_2_8 + is supported. Otherwise, both items are excluded. +C.5: Mandatory to support at least one of TSPC_OPP_2_9a and TSPC_OPP_2_9b if + TSPC_OPP_2_9 is supported. Otherwise, both items are excluded. +C.6: Mandatory to support at least one of TSPC_OPP_2_10a and TSPC_OPP_2_10b if + TSPC_OPP_2_10 is supported. Otherwise, both items are excluded. +C.7: Mandatory to support at least one of TSPC_OPP_2_11a and TSPC_OPP_2_11b if + TSPC_OPP_2_11 is supported. Otherwise, both items are excluded. +C.8: Mandatory to support at least one of TSPC_OPP_2_12a and TSPC_OPP_2_12b if + TSPC_OPP_2_12 is supported. Otherwise, both items are excluded. +C.9: Mandatory if TSPC_OPP_1b_2 supported. +C.10: Optional to support if TSPC_OPP_1b_2 supported else excluded. +C.11: Mandatory if TSPC_OPP_17 and TSPC_OPP_21 supported else excluded. +------------------------------------------------------------------------------- + + + Server Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_2b_1 True (*) Server supports OPP version 1.1. +TSPC_OPP_2b_2 False Server supports OPP version 1.2. +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the profile versions. +------------------------------------------------------------------------------- + + + Server Application Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_3_1 True Server: Provide information on supported + contents type on service discovery + request. (M) +TSPC_OPP_3_2 True Server: Authentication/PIN exchange supported. + (M) +TSPC_OPP_3_3 True Server: Object Push (M) +TSPC_OPP_3_3a True (*) Server: Receive multiple objects in the same + PUT operation. (O) +TSPC_OPP_3_4 True (*) Server: vCard 2.1 (C.3) +TSPC_OPP_3_5 False Server: vCalender 1.0 format (O) +TSPC_OPP_3_6 False Server: vMsg as defined in IrMC 1.1 (O) +TSPC_OPP_3_7 False Server: vNote as defined in IrMC 1.1 (O) +TSPC_OPP_3_8 True (*) Server: Support content formats other than those + declared in TSPC_OPP_3_4 through + TSPC_OPP_3_7. (O) +TSPC_OPP_3_8a False Server: Support specific set of other content + formats. (C.4) +TSPC_OPP_3_8b True (*) Server: Support all content formats. (C.4) +TSPC_OPP_3_9 True (*) Server: Object Push vCard reject. (O) +TSPC_OPP_3_10 False Server: Object Push vCal reject. (O) +TSPC_OPP_3_11 False Server: Object Push vMsg reject. (O) +TSPC_OPP_3_12 False Server: Object Push vNote reject. (O) +TSPC_OPP_3_13 False Server: Business card pull (O.1) +TSPC_OPP_3_14 False Server: vCard 2.1 (C.1) +TSPC_OPP_3_15 False Server: Business card pull reject. (O) +TSPC_OPP_3_16 False Server: Business card exchange (O.2) +TSPC_OPP_3_17 False Server: vCard 2.1 (C.2) +TSPC_OPP_3_18 False Server: Business card exchange reject. (O) +TSPC_OPP_3_19 False GOEP v2 (C.5) +TSPC_OPP_3_20 False GOEP v2 Backward Compability (C.5) +TSPC_OPP_3_21 False OBEX over L2CAP (C.5) +TSPC_OPP_3_22 False OBEX Reliable Session (C.16) +TSPC_OPP_3_23 False OBEX SRM (C.6) +TSPC_OPP_3_24 False Send OBEX SRMP header (C.6) +TSPC_OPP_3_25 False Receive OBEX SRMP header (C.7) +------------------------------------------------------------------------------- +O.1: IF NOT Supported, an error message must be sent on request for Business + Card Pull. +O.2: IF NOT Supported, an error message must be sent on request for Business + Card Exchange. +C.1: Mandatory to Support IF (TSPC_OPP_3_13) Business Card Pull is supported. +C.2: Mandatory to Support IF (TSPC_OPP_3_16) Business Card Exchange is + supported. +C.3: vCard 2.1 support is required for devices containing phonebook + applications. vCard 2.1 support optional for other devices. +C.4: Mandatory to support one of TSPC_OPP_3_8a or TSPC_OPP_3_8b if TSPC_OPP_3_8 + is supported. Otherwise, both items are excluded. +C.5: Mandatory if TSPC_OPP_2b_2 supported. +C.6: Optional to support if TSPC_OPP_2b_2 supported, else excluded. +C.7: Mandatory if TSPC_OPP_3_19 and TSPC_OPP_3_23 supported else excluded. +------------------------------------------------------------------------------- + + + Additional OPP Capabilities +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_4_1 False Abort-Push Operation (O) +TSPC_OPP_4_2 False Intentionally Left Blank (N/A) +TSPC_OPP_4_3 False Multiple vCards transferred as a single vObject + (C.1) +TSPC_OPP_4_4 False Multiple vCards transfer (C.1) +TSPC_OPP_4_5 False vCards with multiple Phone Number Fields (C.1) +TSPC_OPP_4_6 False Push vCal to Different Time Zone Server (C.1) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_OPP_1_2 is supported, otherwise excluded. +------------------------------------------------------------------------------- diff --git a/android/pics-pan.txt b/android/pics-pan.txt new file mode 100644 index 0000000..57863c0 --- /dev/null +++ b/android/pics-pan.txt @@ -0,0 +1,152 @@ +PAN PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PAN_1_1 True (*) Role: Network Access Point (O.1) +TSPC_PAN_1_2 False Role: Group Ad-hoc Network (O.1) +TSPC_PAN_1_3 True (*) Role: PAN User (O.1) +TSPC_PAN_1a_1 True BNEP: BNEP Connection Setup (M) +TSPC_PAN_1a_2 True BNEP: BNEP Data Packet Reception (M) +TSPC_PAN_1a_3 True BNEP: BNEP Data Packet Transmission (M) +TSPC_PAN_1a_3a True BNEP: BNEP Compressed Packet Transmission (O) +TSPC_PAN_1a_3b True BNEP: BNEP Compressed Packet Transmission + Source Only (O) +TSPC_PAN_1a_4 True BNEP: BNEP Control Message Processing (M) +TSPC_PAN_1a_5 True BNEP: BNEP Extension Header Processing (M) +TSPC_PAN_1a_6 False BNEP: Network Protocol Filter Message + Transmission (O) +TSPC_PAN_1a_7 False BNEP: Multicast Address Filter Message + Transmission (O) +------------------------------------------------------------------------------- +O.1: It is mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + Network Access Point Application Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PAN_2_1 True NAP: Support BNEP (M) +TSPC_PAN_2_2 True NAP: Support BNEP Forwarding (M) +TSPC_PAN_2_3 False NAP: Support Layer 2-Bridging between PAN and + External Network (C.1) +TSPC_PAN_2_4 True (*) NAP: Support IP forwarding between PAN and + External Network (C.1) +TSPC_PAN_2_5 False NAP: Support BNEP Packet Filtering (O) +TSPC_PAN_2_6 False NAP: Support IPv4 (C.2) +TSPC_PAN_2_6a False NAP: Supports operable routable IPv4 address (O) +TSPC_PAN_2_6b False NAP: Support link-local address configuration + for IPv4 (C.4) +TSPC_PAN_2_7 False NAP: Support ping client for IPv4 (O) +TSPC_PAN_2_8 False NAP: Support DHCP Client for IPv4 (O) +TSPC_PAN_2_9 False NAP: Support DNS/LLMNR Resolver for IPv4 (O) +TSPC_PAN_2_9a False (*) NAP: Support LLMNR Sender for IPv4 (C.5) +TSPC_PAN_2_9b False NAP: Support LLMNR Responder for IPv4 (O) +TSPC_PAN_2_10 False NAP: Support HTTP Client for IPv4 (O) +TSPC_PAN_2_11 False NAP: Support WAP Client for IPv4 (O) +TSPC_PAN_2_12 False NAP: Support IPv6 (C.3) +TSPC_PAN_2_13 False NAP: Support ping client for IPv6 (O) +TSPC_PAN_2_14 False NAP: Support DNS/LLMNR Resolver for IPv6 (O) +TSPC_PAN_2_14a False (*) NAP: Support LLMNR Sender for IPv6 (C.6) +TSPC_PAN_2_14b False NAP: Support LLMNR Responder for IPv6 (O) +TSPC_PAN_2_15 False NAP: Support HTTP Client for IPv6 (O) +TSPC_PAN_2_16 False NAP: Support WAP Client for IPv6 (O) +TSPC_PAN_2_17 True NAP: Supports Connectable Mode (M) +TSPC_PAN_2_18 True NAP: NAP Service Record (M) +TSPC_PAN_2_19 False NAP: Support at least three PANUs (O) +TSPC_PAN_2_20 False NAP: Support at least two PANUs (O) +------------------------------------------------------------------------------- +Note that support for IP-related features only applies to the PAN interface of + the NAP (i.e. If the IP stack is accessible by PANUs). +C.1: Network Access Point devices MUST support either (TSPC_PAN_2_3) + OR (TSPC_PAN_2_4). +C.2: Mandatory to support IF any IPv4-based transport protocol OR + (TSPC_PAN_2_7-11) is supported, ELSE Optional. +C.3: Mandatory to support IF any IPv6-based transport protocol OR + (TSPC_PAN_2_13-16) is supported, ELSE Optional. +C.4: Mandatory if TSPC_PAN_2_6 is supported and TSPC_PAN_2_6a is not supported, + otherwise optional. +C.5: Mandatory if item (TSPC_PAN_2_6) supported. +C.6: Mandatory if item (TSPC_PAN_2_12) supported +------------------------------------------------------------------------------- + + + Group Ad-hoc Network Application Features + (GN Application Features) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PAN_3_1 False (*) GN: Support BNEP (M) +TSPC_PAN_3_2 False (*) GN: Support BNEP Forwarding (M) +TSPC_PAN_3_3 False GN: Support BNEP Packet Filtering (O) +TSPC_PAN_3_4 False GN: Support IPv4 (C.1) +TSPC_PAN_3_5 False GN: Support ping client for IPv4 (O) +TSPC_PAN_3_6 False GN: Support DHCP Client for IPv4 (O) +TSPC_PAN_3_7 False GN: Support DNS/LLMNR Resolver for IPv4 (O) +TSPC_PAN_3_7a False (*) GN: Support LLMNR Sender for IPv4 (C.3) +TSPC_PAN_3_7b False GN: Support LLMNR Responder for IPv4 (O) +TSPC_PAN_3_8 False GN: Support HTTP Client for IPv4 (O) +TSPC_PAN_3_9 False GN: Support WAP Client for IPv4 (O) +TSPC_PAN_3_10 False GN: Support IPv6 (C.2) +TSPC_PAN_3_11 False GN: Support ping client for IPv6 (O) +TSPC_PAN_3_12 False GN: Support DNS/LLMNR Resolver for IPv6 (O) +TSPC_PAN_3_12a False (*) GN: Support LLMNR Sender for IPv6 (C.4) +TSPC_PAN_3_12b False GN: Support LLMNR Responder for IPv6 (O) +TSPC_PAN_3_13 False GN: Support HTTP Client for IPv6 (O) +TSPC_PAN_3_14 False GN: Support WAP Client for IPv6 (O) +TSPC_PAN_3_15 False (*) GN: Supports Connectable Mode (M) +TSPC_PAN_3_16 False (*) GN: GN Service Record (M) +TSPC_PAN_3_17 False GN: Support at least three PANUs (O) +TSPC_PAN_3_18 False GN: Support at least two PANUs (O) +------------------------------------------------------------------------------- +C.1: Mandatory to support IF any IPv4-based transport protocol OR + (TSPC_PAN_3_5-9) is supported, ELSE Optional. +C.2: Mandatory to support IF any IPv6-based transport protocol OR + (TSPC_PAN_3_11-14) is supported, ELSE Optional. +C.3: Mandatory to support IF (TSPC_PAN_3_4) is supported. +C.4: Mandatory to support if (TSPC_PAN_3_10) is supported. +------------------------------------------------------------------------------- + + + PAN User Application Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PAN_4_1 True PANU: Support BNEP (M) +TSPC_PAN_4_2 True (*) PANU: Support IPv4 (C.1) +TSPC_PAN_4_3 False PANU: Support ping client for IPv4 (O) +TSPC_PAN_4_4 False PANU: Support DHCP client for IPv4 (O) +TSPC_PAN_4_5 False PANU: Support DNS/LLMNR Resolver for IPv4 (O) +TSPC_PAN_4_5a False (*) PANU: Support LLMNR Sender for IPv4 (C.2) + Reference: SE #3558, TSE #4382, TCW #448 +TSPC_PAN_4_5b False PANU: Support LLMNR Responder for IPv4 (O) +TSPC_PAN_4_6 False PANU: Support HTTP Client for IPv4 (O) +TSPC_PAN_4_7 False PANU: Support WAP Client for IPv4 (O) +TSPC_PAN_4_8 False PANU: Support IPv6 (C.1) +TSPC_PAN_4_9 False PANU: Support ping client for IPv6 (O) +TSPC_PAN_4_10 False PANU: Support DNS/LLMNR Resolver for IPv6 (O) +TSPC_PAN_4_10a False (*) PANU: Support LLMNR Sender for IPv6 (C.3) +TSPC_PAN_4_10b False PANU: Support LLMNR Responder for IPv6 (O) +TSPC_PAN_4_11 False PANU: Support HTTP Client for IPv6 (O) +TSPC_PAN_4_12 False PANU: Support WAP Client for IPv6 (O) +TSPC_PAN_4_13 False PANU: Support connections to multi-user + NAPs/GNs (O) +TSPC_PAN_4_14 False PANU: Supports Connectable Mode (O) +TSPC_PAN_4_15 False PANU: PANU Service Record (O) +TSPC_ALL False Turns on all the test cases +------------------------------------------------------------------------------- +C.1: PAN User devices must support at least One of items (TSPC_PAN_4_2) or + (TSPC_PAN_4_8). +C.2: Mandatory to support if (TSPC_PAN_4_2) is supported. +C.3: Mandatory to support if (TSPC_PAN_4_8) is supported. +------------------------------------------------------------------------------- diff --git a/android/pics-pbap.txt b/android/pics-pbap.txt new file mode 100644 index 0000000..bc01d8a --- /dev/null +++ b/android/pics-pbap.txt @@ -0,0 +1,475 @@ +PBAP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_1_1 False Role: PCE (C.1) +TSPC_PBAP_1_2 True (*) Role: PSE (C.1) +------------------------------------------------------------------------------- +C1: It is mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + Client Major Profile Version (X.Y) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_2a_1 False PBAP 1.0 (C.1) +TSPC_PBAP_2a_2 False PBAP 1.1 (C.1) +TSPC_PBAP_2a_3 False PBAP 1.2 (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support one and only one major profile version. +------------------------------------------------------------------------------- + + + Client Minor Profile Version (X.Y) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_2b_1 False PBAP 1.1.1 (C.1) +------------------------------------------------------------------------------- +C.1: Optional if 2a/2 (PBAP 1.1) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Supported features (PCE) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_2_1 False (*) PCE: Phone Book Download (C.1) +TSPC_PBAP_2_2 False (*) PCE: Phone Book Browsing (C.1) +TSPC_PBAP_2_3 False (*) PCE: Session Management (M) +TSPC_PBAP_2_4 False PCE: Able to Request Size of the Phonebook (O) +TSPC_PBAP_2_5 False PCE: Database Identifier (C.2) +TSPC_PBAP_2_6 False PCE: Folder Version Counters (C.2) +TSPC_PBAP_2_7 False PCE: vCard Selecting (C.2) +TSPC_PBAP_2_7a False PCE: Able to send vCardSelector (C.2) +TSPC_PBAP_2_7b False PCE: Able to send vCardSelectorOperator (C.2) +TSPC_PBAP_2_8 False (*) PCE: Enhanced Missed Calls (C.4) +TSPC_PBAP_2_8a False (*) PCE: Able to reset the missed Calls (C.2) +TSPC_PBAP_2_9 False PCE: X-BT-UCI vCard Field (C.2) +TSPC_PBAP_2_9a False PCE: Able to request X-BT-UCI Field (C.2) +TSPC_PBAP_2_10 False PCE: X-BT-UID vCard Field (C.2) +TSPC_PBAP_2_10a False PCE: Referencing Contacts (C.2) +TSPC_PBAP_2_12 False PCE: Contact Image Default Format (C.2) +TSPC_PBAP_2_12a False PCE: Able to request Contact Images (C.2) +TSPC_PBAP_2_13 False PCE: Supported Phonebook Objects (C.3) +TSPC_PBAP_2_13a False (*) PCE: Telecom/pb (C.3) +TSPC_PBAP_2_13b False PCE: Telecom/ich (C.3) +TSPC_PBAP_2_13c False PCE: Telecom/och (C.3) +TSPC_PBAP_2_13d False (*) PCE: Telecom/mch (C.3) +TSPC_PBAP_2_13e False (*) PCE: Telecom/cch (C.3) +TSPC_PBAP_2_13f False PCE: Telecom/spd (C.3) +TSPC_PBAP_2_13g False PCE: Telecom/fav (C.3) +TSPC_PBAP_2_13h False PCE: SIM1/Telecom/pb (C.3) +TSPC_PBAP_2_13i False PCE: SIM1/Telecom/ich (C.3) +TSPC_PBAP_2_13j False PCE: SIM1/Telecom/och (C.3) +TSPC_PBAP_2_13k False PCE: SIM1/Telecom/mch (C.3) +TSPC_PBAP_2_13l False PCE: SIM1/Telecom/cch (C.3) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the defined features. +C.2: Optional if TSPC_PBAP_0_3 (PBAP 1.2) is supported, otherwise Excluded. +C.3: Mandatory to support at least one of the listed phonebook objects . +C.4: Optional if TSPC_PBAP_0_3 (PBAP 1.2) and any of the mch or cch folders + (13d,13e,13k,13l) are supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Supported Phone Book Download functions (PCE) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_3_1 False (*) PCE: Pull Phone Book (C.1) +------------------------------------------------------------------------------- +C1: Mandatory for PCE if Phone Book Download (TSPC_PBAP_2_1) is supported, + otherwise excluded. +------------------------------------------------------------------------------- + + + Supported Phone Book Browsing functions (PCE) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_4_1 False (*) PCE: Set Phone Book (C.1) +TSPC_PBAP_4_2 False (*) PCE: Pull vCard Listing (C.1) +TSPC_PBAP_4_3 False (*) PCE: Pull vCard Entry (C.1) +------------------------------------------------------------------------------- +C1: Mandatory for PCE if Phone Book Browsing TSPC_PBAP_2_2 is supported, + otherwise excluded. +------------------------------------------------------------------------------- + + + Used vCard formats (PCE) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_5_1 False (*) PCE: vCard 2.1 (C.1) +TSPC_PBAP_5_2 False (*) PCE: vCard 3.0 (C.1) +------------------------------------------------------------------------------- +C1: It is mandatory to support at least one of the defined versions if PCE + supported. +------------------------------------------------------------------------------- + + + OBEX Functions for PCE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_6_1 False (*) PCE: Connect (M) +TSPC_PBAP_6_2 False (*) PCE: Disconnect (M) +TSPC_PBAP_6_3 False (*) PCE: Get (M) +TSPC_PBAP_6_4 False PCE: Abort (O) +TSPC_PBAP_6_5 False (*) PCE: SetPath (C.1) +TSPC_PBAP_6_6 False PCE: Support for OBEX authentication initiation + (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_PBAP_2_2 (Phone Book Browsing) is supported, + otherwise Excluded. +C.2: Optional to support initiation if TSPC_PBAP_0_1 (PBAP 1.0) or + TSPC_PBAP_0_2 (PBAP 1.1) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + PCE OBEX Header Support +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_7_1 False (*) PCE: Name (M) +TSPC_PBAP_7_2 False (*) PCE: Type (M) +TSPC_PBAP_7_3 False (*) PCE: Body (M) +TSPC_PBAP_7_4 False (*) PCE: End of Body (M) +TSPC_PBAP_7_5 False (*) PCE: Target (M) +TSPC_PBAP_7_6 False (*) PCE: Who (M) +TSPC_PBAP_7_7 False (*) PCE: Connection ID (M) +TSPC_PBAP_7_8 False (*) PCE: Authentication Challenge (M) +TSPC_PBAP_7_9 False (*) PCE: Authentication Response (M) +TSPC_PBAP_7_10 False (*) PCE: Application Parameters (M) +TSPC_PBAP_7_11 False PCE: Single Response Mode (C.1) +TSPC_PBAP_7_12 False PCE: Single Response Mode Parameter + (ability to parse) (C.1) +TSPC_PBAP_7_13 False PCE: Single Response Mode Parameter + (ability to send) (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_PBAP_0_3 (PBAP 1.2) is supported, otherwise Excluded. +C.2: Optional if TSPC_PBAP_2a_3 (PBAP 1.2) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + OBEX Error Codes for PCE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_8_1 False (*) PCE: Bad Request (M) +TSPC_PBAP_8_2 False (*) PCE: Not Implemented (M) +TSPC_PBAP_8_3 False (*) PCE: Unauthorized (M) +TSPC_PBAP_8_4 False (*) PCE: Precondition Failed (M) +TSPC_PBAP_8_5 False (*) PCE: Not Found (M) +TSPC_PBAP_8_6 False (*) PCE: Not Acceptable (M) +TSPC_PBAP_8_7 False (*) PCE: Service Unavailable (M) +TSPC_PBAP_8_8 False (*) PCE: Forbidden (M) +------------------------------------------------------------------------------- + + + Server Major Profile Version (X.Y) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_9a_1 False PBAP 1.0 (C.1) +TSPC_PBAP_9a_2 True (*) PBAP 1.1 (C.1) +TSPC_PBAP_9a_3 False PBAP 1.2 (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support one and only one major profile version. +------------------------------------------------------------------------------- + + + Server Minor Profile Version (X.Y.Z) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_9b_1 False PBAP 1.1.1 (C.1) +------------------------------------------------------------------------------- +C.1: Optional if 9a/2 (PBAP 1.1) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Supported features (PSE) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_9_1 True PSE: Phone Book Download (M) +TSPC_PBAP_9_2 True PSE: Phone Book Browsing (M) +TSPC_PBAP_9_3 True PSE: Session Management (M) +TSPC_PBAP_9_4 True PSE: Able to request the size of the Phonebook + (M) +TSPC_PBAP_9_5 False PSE: Database Identifier (C.1) +TSPC_PBAP_9_5a False PSE: Able to keep a persistent Database + Identifier (C.2) +TSPC_PBAP_9_5b False PSE: Able to regenerate a Database Identifier + (C.2) +TSPC_PBAP_9_6 False PSE: Folder Version Counters (C.1) +TSPC_PBAP_9_6a False PSE: Able to Insert or Remove Entries (C.2) +TSPC_PBAP_9_6b False PSE: Able to Modify contact primary Fields (C.2) +TSPC_PBAP_9_6c False PSE: Able to Modify contact secondary Fields + (C.2) +TSPC_PBAP_9_7 False (*) PSE: vCard Selecting (C.1) +TSPC_PBAP_9_8 False (*) PSE: Enhanced Missed Calls (C.4) +TSPC_PBAP_9_9 False PSE: X-BT-UCI vCard Field (C.2) +TSPC_PBAP_9_10 False PSE: X-BT-UID vCard Field (C.2) +TSPC_PBAP_9_10a False PSE: Referencing Contacts (C.3) +TSPC_PBAP_9_12 False PSE: Contact Image Default Format (C.1) +TSPC_PBAP_9_12a False PSE: Able to request Contact Images (C.2) +TSPC_PBAP_9_13 False PSE: Supported Phonebook Objects +TSPC_PBAP_9_13a True PSE: Telecom/pb (M) +TSPC_PBAP_9_13b True (*) PSE: Telecom/ich (O) +TSPC_PBAP_9_13c True (*) PSE: Telecom/och (O) +TSPC_PBAP_9_13d True (*) PSE: Telecom/mch (O) +TSPC_PBAP_9_13e True PSE: Telecom/cch (O) +TSPC_PBAP_9_13f False PSE: Telecom/spd (C.2) +TSPC_PBAP_9_13g False PSE: Telecom/fav (C.2) +TSPC_PBAP_9_13h False (*) PSE: SIM1/Telecom/pb (O) +TSPC_PBAP_9_13i False PSE: SIM1/Telecom/ich (O) +TSPC_PBAP_9_13j False PSE: SIM1/Telecom/och (O) +TSPC_PBAP_9_13k False (*) PSE: SIM1/Telecom/mch (O) +TSPC_PBAP_9_13l False PSE: SIM1/Telecom/cch (O) +TSPC_PBAP_9_14 False PSE: Deleted Handles Behavior +TSPC_PBAP_9_14a True PSE: Error reporting (C.5) +TSPC_PBAP_9_14b False PSE: Change tracking (C.5) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_PBAP_0_3 (PBAP 1.2) is supported, otherwise Excluded. +C.2: Optional if TSPC_PBAP_0_3 (PBAP 1.2) is supported, otherwise Excluded. +C.3: Optional if TSPC_PBAP_9_10 (X-BT-UID vCard Property) is supported, + otherwise Excluded. +C.4: Optional if TSPC_PBAP_0_3 (PBAP 1.2) and any of the mch or cch folders + (13d,13e,13k,13l) are supported, otherwise Excluded. +C.5: It is mandatory to support at least one of the defined deleted handles + behaviors. +------------------------------------------------------------------------------- + + + Supported Phone Book Download functions ( PSE ) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_10_1 True PSE: Pull Phone Book (M) +TSPC_PBAP_10_2 False PSE: Call History Function (O) +------------------------------------------------------------------------------- + + + Supported Phone Book Browsing functions ( PSE ) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_11_1 True PSE: Set Phone Book (M) +TSPC_PBAP_11_2 True PSE: Pull vCard Listing (M) +TSPC_PBAP_11_3 True PSE: Pull vCard Entry (M) +------------------------------------------------------------------------------- + + + Used vCard formats (PSE) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_12_1 True PSE: vCard 2.1 (M) +TSPC_PBAP_12_2 True PSE: vCard 3.0 (M) +------------------------------------------------------------------------------- + + + OBEX Functions for PSE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_13_1 True PSE: Connect (M) +TSPC_PBAP_13_2 True PSE: Disconnect (M) +TSPC_PBAP_13_3 True PSE: Get (M) +TSPC_PBAP_13_4 True PSE: Abort (M) +TSPC_PBAP_13_5 True PSE: SetPath (M) +TSPC_PBAP_13_6 False PSE: Support for OBEX authentication initiation + (C.1) +------------------------------------------------------------------------------- +C.1: Optional to support initiation if TSPC_PBAP_0_1 (PBAP 1.0) or + TSPC_PBAP_0_2 (PBAP 1.1) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + PSE OBEX Header Support +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_14_1 True PSE: Name (M) +TSPC_PBAP_14_2 True PSE: Type (M) +TSPC_PBAP_14_3 True PSE: Body (M) +TSPC_PBAP_14_4 True PSE: End of Body (M) +TSPC_PBAP_14_5 True PSE: Target (M) +TSPC_PBAP_14_6 True PSE: Who (M) +TSPC_PBAP_14_7 True PSE: Connection ID (M) +TSPC_PBAP_14_8 True PSE: Authentication Challenge (M) +TSPC_PBAP_14_9 True PSE: Authentication Response (M) +TSPC_PBAP_14_10 True PSE: Application Parameters (M) +TSPC_PBAP_14_11 False PSE: Single Response Mode (C.1) +TSPC_PBAP_14_12 False PSE: Single Response Mode Parameter + (ability to parse) (C.1) +TSPC_PBAP_14_13 False PSE: Single Response Mode Parameter + (ability to send) (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_PBAP_0_3 (PBAP 1.2) is supported, otherwise Excluded. +C.2: Optional if TSPC_PBAP_9a_3 (PBAP 1.2) is supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + OBEX Error Codes for PSE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_15_1 True PSE: Bad Request (M) +TSPC_PBAP_15_2 True PSE: Not Implemented (M) +TSPC_PBAP_15_3 True (*) PSE: Unauthorized (O) +TSPC_PBAP_15_4 True (*) PSE: Precondition Failed (C.1) +TSPC_PBAP_15_5 True PSE: Not Found (M) +TSPC_PBAP_15_6 True (*) PSE: Not Acceptable (O) +TSPC_PBAP_15_7 True PSE: Service Unavailable (M) +TSPC_PBAP_15_8 True (*) PSE: Forbidden (O) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_PBAP_9_14a (Error reporting) is supported, otherwise + Optional. +------------------------------------------------------------------------------- + + + GAP Modes for PCE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_16_1 False (*) PCE: General discoverable mode (M) +TSPC_PBAP_16_2 False (*) PCE: Pairable mode (M) +------------------------------------------------------------------------------- + + + GAP Modes for PSE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_17_1 True PSE: General discoverable mode (M) +TSPC_PBAP_17_2 True PSE: Pairable mode (M) +------------------------------------------------------------------------------- + + + GAP Security Modes for PCE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_18_1 False (*) PCE: Authentication Procedure (M) +TSPC_PBAP_18_2 False (*) PCE: Initiate LMP-Authentication (M) +TSPC_PBAP_18_3 False PCE: Security mode 1 (C.1) +TSPC_PBAP_18_4 False PCE: Security mode 2 (C.1) +TSPC_PBAP_18_5 False PCE: Security mode 3 (C.1) +TSPC_PBAP_18_6 False PCE: Security mode 4 (C.1) +------------------------------------------------------------------------------- +C.1: At least one of TSPC_PBAP_18_4, TSPC_PBAP_18_5 or TSPC_PBAP_18_6 + (security mode 2, 3, or 4) shall be supported. +------------------------------------------------------------------------------- + + + GAP Security Modes for PSE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_19_1 True PSE: Authentication Procedure (M) +TSPC_PBAP_19_2 True PSE: Initiate LMP-Authentication (M) +TSPC_PBAP_19_3 False PSE: Security mode 1 (C.2) +TSPC_PBAP_19_4 False PSE: Security mode 2 (C.1) +TSPC_PBAP_19_5 False PSE: Security mode 3 (C.1) +TSPC_PBAP_19_6 True (*) PSE: Security mode 4 (C.1) +------------------------------------------------------------------------------- +C.1: At least one of TSPC_PBAP_19_3, TSPC_PBAP_19_4, TSPC_PBAP_19_5 or + TSPC_PBAP_19_6 (security mode 2, 3, or 4) shall be supported. +C.2: Excluded in PSE. +------------------------------------------------------------------------------- + + + GAP Idle Modes for PSE +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_21_1 True PSE: Initiation of General Inquiry (M) +TSPC_PBAP_21_2 False PSE: Initiation of Limited Inquiry (O) +------------------------------------------------------------------------------- + + + SDP Attributes (PCE) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_22_1 False (*) PCE: BluetoothProfileDescriptorList (M) +------------------------------------------------------------------------------- + + SDP Attributes (PSE) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_23_1 True PSE: ProtocolDescriptorList (M) +TSPC_PBAP_23_2 True PSE: BluetoothProfileDescriptorList (M) +------------------------------------------------------------------------------- + + + Additional PBAP Capabilities +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_24_1 False PCE: Retrieve Large Phone Book (C.1) +TSPC_PBAP_24_2 False PSE: Transfer Large Phone Book (C.2) +TSPC_PBAP_24_3 False PCE: Retrieve Empty Phone Book (C.1) +TSPC_PBAP_24_4 False PSE: Transfer Empty Phone Book (C.2) +TSPC_PBAP_24_5 False PSE: Return Phonebook – Limit number of entries + (C.2) +TSPC_PBAP_24_6 False PSE: Return vCard listing – Limit number of + entries (C.2) +TSPC_PBAP_24_7 False PSE: Phone Book Order (C.2) +TSPC_PBAP_24_8 False PSE: Call stack timestamps (C.3) +TSPC_PBAP_24_9 False PSE: No User Interaction (C.2) +TSPC_PBAP_24_10 False PSE: Special Character Handling (C.2) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_PBAP_2_1 is supported, otherwise excluded. +C.2: Optional if TSPC_PBAP_1_2 is supported, otherwise excluded. +C.3: Optional if TSPC_PBAP_10_2 is supported, otherwise excluded. +------------------------------------------------------------------------------- + + + GOEP 2.0 or later Features (PCE) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_25_1 False PCE: GOEP v2.0 or later (M) +TSPC_PBAP_25_2 False (*) PCE: GOEP v2 Backwards Compatibility (M) +TSPC_PBAP_25_3 False PCE: OBEX over L2CAP (M) +TSPC_PBAP_25_4 False PCE: OBEX SRM (M) +TSPC_PBAP_25_5 False (*) PCE: Send OBEX SRMP header (C.1) +TSPC_PBAP_25_6 False PCE: Receive OBEX SRMP header (M) +------------------------------------------------------------------------------- +C.1: Optional to support if TSPC_PBAP_25_4 (OBEX SRM) is supported, + otherwise Excluded. +------------------------------------------------------------------------------- + + + GOEP 2.0 or later Features (PSE) +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_PBAP_26_1 False PSE: GOEP v2.0 or later (M) +TSPC_PBAP_26_2 False (*) PSE: GOEP v2 Backwards Compatibility (M) +TSPC_PBAP_26_3 False PSE: OBEX over L2CAP (M) +TSPC_PBAP_26_4 False PSE: OBEX SRM (M) +TSPC_PBAP_26_5 False (*) PSE: Send OBEX SRMP header (C.1) +TSPC_PBAP_26_6 False PSE: Receive OBEX SRMP header (M) +TSPC_ALL False Turns on all the test cases +------------------------------------------------------------------------------- +C.1: Optional if TSPC_PBAP_26_4 (OBEX SRM) is supported, otherwise Excluded. +------------------------------------------------------------------------------- diff --git a/android/pics-rfcomm.txt b/android/pics-rfcomm.txt new file mode 100644 index 0000000..032ee73 --- /dev/null +++ b/android/pics-rfcomm.txt @@ -0,0 +1,65 @@ +RFCOMM PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Protocol Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_RFCOMM_0_1 False RFCOMM 1.1 with TS 07.10 (C.1) +TSPC_RFCOMM_0_2 True (*) RFCOMM 1.2 with TS 07.10 (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support one and only one of the protocol versions. +------------------------------------------------------------------------------- + + + Supported Procedures +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_RFCOMM_1_1 True (*) Initialize RFCOMM Session (C.2) +TSPC_RFCOMM_1_2 True (*) Respond to Initialization of an RFCOMM + Session (C.1) +TSPC_RFCOMM_1_3 True Shutdown RFCOMM Session (M) +TSPC_RFCOMM_1_4 True Respond to a Shutdown of an RFCOMM + Session (M) +TSPC_RFCOMM_1_5 True (*) Establish DLC (C.2) +TSPC_RFCOMM_1_6 True (*) Respond to Establishment of a DLC (C.1) +TSPC_RFCOMM_1_7 True Disconnect DLC (M) +TSPC_RFCOMM_1_8 True Respond to Disconnection of a DLC (M) +TSPC_RFCOMM_1_9 True Respond to and send MSC Command (M) +TSPC_RFCOMM_1_10 True Initiate Transfer Information (M) +TSPC_RFCOMM_1_11 True Respond to Test Command (M) +TSPC_RFCOMM_1_12 True (*) Send Test Command (O) +TSPC_RFCOMM_1_13 True React to Aggregate Flow Control (M) +TSPC_RFCOMM_1_14 True Respond to RLS Command (M) +TSPC_RFCOMM_1_15 False Send RLS Command (O) +TSPC_RFCOMM_1_16 True Respond to PN Command (M) +TSPC_RFCOMM_1_17 True (*) Send PN Command (C.3) +TSPC_RFCOMM_1_18 True (*) Send Non-Supported Command (NSC) + response (C.4) +TSPC_RFCOMM_1_19 True Respond to RPN Command (M) +TSPC_RFCOMM_1_20 True (*) Send RPN Command (O) +TSPC_RFCOMM_1_21 True (*) Closing Multiplexer by First Sending + a DISC Command (O) +TSPC_RFCOMM_1_22 True Support of Credit Based Flow Control (M) +------------------------------------------------------------------------------- +C.1: Mandatory if SPP 1/2 (Device B) is supported, otherwise Excluded. +C.2: Mandatory if SPP 1/1 (Device A) is supported, otherwise Excluded. +C.3: Mandatory if SPP 1/1 (Device A) is supported, otherwise Optional. +C.4: Mandatory to support if TSPC_RFCOMM_0_2 is supported, otherwise Optional. +------------------------------------------------------------------------------- + + +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_SPP_2_1 False Channel the tester should use when + connecting to the IUT (Default "") +------------------------------------------------------------------------------- diff --git a/android/pics-scpp.txt b/android/pics-scpp.txt new file mode 100644 index 0000000..601ac32 --- /dev/null +++ b/android/pics-scpp.txt @@ -0,0 +1,143 @@ +ScPP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults + +M - mandatory +O - optional + + Connection Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_1_1 False (*) Scan Server (C.1) +TSPC_ScPP_1_2 True Scan Client (C.1) +------------------------------------------------------------------------------- +Note: Mandatory to support at least one of TSPC_ScPP_1_1 or TSPC_ScPP_1_2. +------------------------------------------------------------------------------- + + + Transport Requirements +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_2_1 False (*) Profile supported over BR/EDR (C.1) +TSPC_ScPP_2_2 True Profile supported over LE (M) +------------------------------------------------------------------------------- +C.1: Excluded for this profile. +------------------------------------------------------------------------------- + + + Services - Scan Server +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_3_1 False (*) Implements Scan Parameters Service (M) +------------------------------------------------------------------------------- + + + GAP Requirements - Scan Server Role +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_4_1 False (*) Peripheral (M) +TSPC_ScPP_4_2 False (*) Directed Connectable Mode (O) +TSPC_ScPP_4_3 False (*) Undirected Connectable Mode (M) +TSPC_ScPP_4_4 False (*) Bondable mode (peripheral) (O) +TSPC_ScPP_4_5 False (*) Bonding procedure (peripheral) (O) +TSPC_ScPP_4_6 False (*) LE Security Mode 1 (peripheral) (M) +------------------------------------------------------------------------------- + + + Scan Server +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_5_1 False (*) SM 2.3.1- +TSPC_ScPP_5_2 False (*) Unauthenticated no MITM protection. (LE Security + Level 2, Just Works) (O) +TSPC_ScPP_5_3 False (*) Authenticated MITM protection (LE Security + Level 3, Passkey) (O) +------------------------------------------------------------------------------- + + + Client Services Support - Scan Client Role +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_6_1 True Scan Parameters Service (M) +------------------------------------------------------------------------------- + + + Discover Services and Characteristics - Scan Client Role +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_7_1 True Discover Scan Parameters Service (M) +TSPC_ScPP_7_2 True Discover Scan Parameters characteristic: + Scan interval Window (M) +TSPC_ScPP_7_3 True Discover Scan Parameters characteristic: + Scan Refresh (M) +TSPC_ScPP_7_4 True Discover Scan Parameters characteristic: + Scan Refresh – Client Characteristic + Configuration Descriptor (M) +------------------------------------------------------------------------------- + + + Features - Client +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_8_1 True Write Scan Interval Window characteristic (M) +TSPC_ScPP_8_2 True Configure Scan Refresh characteristic: + Client Characteristic Configuration + characteristic descriptor with 0x0001 (M) +TSPC_ScPP_8_3 True Notify Scan Refresh characteristic (M) +------------------------------------------------------------------------------- + + + GATT Requirements - Scan Client +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_9_1 True Attribute Protocol supported over LE Transport + (M) +TSPC_ScPP_9_2 True Generic Attribute Profile Client (M) +TSPC_ScPP_9_3 True Discover All Primary Services (C.1) +TSPC_ScPP_9_4 True Discover Primary Services by Service UUID (C.1) +TSPC_ScPP_9_5 True Discover All Characteristics of a Service (C.2) +TSPC_ScPP_9_6 True Discover Characteristics by UUID (C.2) +TSPC_ScPP_9_7 True Discover All Characteristic Descriptors (M) +TSPC_ScPP_9_8 True Write without Response (M) +TSPC_ScPP_9_9 True Write Characteristic Descriptors (M) +TSPC_ScPP_9_10 True Notifications (M) +------------------------------------------------------------------------------- +C.1: Mandatory to support one of these sub-procedures for service discovery +C.2: Mandatory to support one of these sub-procedures for characteristic + discovery +------------------------------------------------------------------------------- + + + GAP Requirements - Scan Client +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_10_1 True Central (M) +TSPC_ScPP_10_2 True Bondable mode (central) (O) +TSPC_ScPP_10_3 True Bonding procedure (central) (O) +TSPC_ScPP_10_4 True Central (M) +------------------------------------------------------------------------------- + + + SM Requirements - Scan Client +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_ScPP_11_1 True No Security Requirements (LE Security Level 1, + No Security) (M) +TSPC_ScPP_11_2 True Unauthenticated no MITM protection (LE Security + Level 2, Just Works) (O) +TSPC_ScPP_11_3 True Authenticated MITM protection (LE Security + Level 3, Passkey) (O) +------------------------------------------------------------------------------- diff --git a/android/pics-sdp.txt b/android/pics-sdp.txt new file mode 100644 index 0000000..508b80d --- /dev/null +++ b/android/pics-sdp.txt @@ -0,0 +1,140 @@ +SDP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + + Support Different Size Capabilities on UUID +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SDP_1_1 True Support for 128 bit UUID (M) +TSPC_SDP_1_2 True Support for 32 bit UUID (M) +TSPC_SDP_1_3 True Support for 16 bit UUID (M) +------------------------------------------------------------------------------- + + + Roles +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SDP_1b_1 True (*) Support for server role (C.1) +TSPC_SDP_1b_2 True (*) Support for client role (C.1) +------------------------------------------------------------------------------- +C.1 Mandatory to support at least one of the roles +------------------------------------------------------------------------------- + + + Valid Service Search Request +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SDP_2_1 True (*) Support for respond on search of single + Service, using ServiceSearchRequest (C.2) +TSPC_SDP_2_2 True (*) Support for respond on search of Service, + using continuation state (O) +TSPC_SDP_2_3 True (*) Search for services using the continuation + state (C.1) +------------------------------------------------------------------------------- +C.1 Mandatory to support if the client role is supported TSPC_SDP_1b_2 +C.2 Mandatory to support if the server role is supported TSPC_SDP_1b_1 +------------------------------------------------------------------------------- + + + Invalid Service Search Request +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SDP_3_1 True Support for error response on Service search + request (M) +------------------------------------------------------------------------------- + + + Valid Service Attribute Request +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SDP_4_1 True Support for respond on search of + Attribute(s) (M) +TSPC_SDP_4_2 True (*) Support for respond on search of + Attribute, using continuation state (O) +TSPC_SDP_4_3 True (*) Support for respond on search on + attribute AdditionalProtocolDescriptorList (O) +------------------------------------------------------------------------------- + + + Invalid Service Attribute Request +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SDP_5_1 True Support for error response on Attribute + search request (M) +------------------------------------------------------------------------------- + + + Valid Service Search Attribute Request +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SDP_6_1 True Support for respond on search for Service(s) + and Attribute(s) (M) +TSPC_SDP_6_2 True (*) Support for respond on search of Attribute, + using continuation state (O) +TSPC_SDP_6_3 True (*) Support for respond on search on attribute + AdditionalProtocolDescriptorList on existing + service (O) +------------------------------------------------------------------------------- + + + Invalid Service Search Attribute Request +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SDP_7_1 True Support for error response on Service and + Attribute request (M) +------------------------------------------------------------------------------- + + + Service Browsing +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SDP_8_1 True (*) Support for browsing, using + SDP_ServiceSearchRequest and + SDP_ServiceAttributeRequest (O) +TSPC_SDP_8_2 True (*) Support for browsing, using + SDP_ServiceSearchAttributeRequest (O) +------------------------------------------------------------------------------- + + + Attributes Present in IUT +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SDP_9_1 True (*) ServiceID (O) +TSPC_SDP_9_2 True (*) ProtocolDescriptorList (O) +TSPC_SDP_9_3 True (*) ServiceRecordState (O) +TSPC_SDP_9_4 True (*) ServiceInfoTimeToLive (O) +TSPC_SDP_9_5 True (*) BrowseGroupList (O) +TSPC_SDP_9_6 True (*) LanguageBaseAttributeIdList (O) +TSPC_SDP_9_7 True (*) ServiceAvailability (O) +TSPC_SDP_9_8 True (*) IconURL (O) +TSPC_SDP_9_9 True (*) ServiceName (O) +TSPC_SDP_9_10 True (*) ServiceDescription (O) +TSPC_SDP_9_11 True (*) ProviderName (O) +TSPC_SDP_9_12 True (*) VersionNumberList (O) +TSPC_SDP_9_13 True (*) ServiceDataBaseState (O) +TSPC_SDP_9_14 True (*) BluetoothProfileDescriptorList (O) +TSPC_SDP_9_15 True (*) DocumentationURL (O) +TSPC_SDP_9_16 True (*) ClientExecutableURL (O) +TSPC_SDP_9_17 True (*) AdditionalProtocolDescriptorList (C.1) +TSPC_SDP_9_18 True ServiceRecordHandle (M) +TSPC_SDP_9_19 True ServiceClassIDList (M) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_SDP_9_2 is supported, otherwise excluded +------------------------------------------------------------------------------- diff --git a/android/pics-sm.txt b/android/pics-sm.txt new file mode 100644 index 0000000..c31fe76 --- /dev/null +++ b/android/pics-sm.txt @@ -0,0 +1,96 @@ +SM PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +^ - field not available on PTS + +M - mandatory +O - optional + + Connection Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_SM_1_1 True Master Role (Initiator) (C.1) +TSPC_SM_1_2 True Slave Role (Responder) (C.2) +------------------------------------------------------------------------------- +C.1: Mandatory to support if TSPC_SM_1_2 is NOT supported, otherwise Optional +C.2: Optional IF ((4.0 OR 4.0+HS) AND TSPC_GAP_5_3) OR ((4.1 OR 4.1+HS OR 4.2 + OR 4.2+HS) AND (TSPC_GAP_5_3 OR TSPC_GAP_38_3))) +------------------------------------------------------------------------------- + + + Security Properties +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_SM_2_1 True Authenticated MITM protection (O) +TSPC_SM_2_2 True Unauthenticated no MITM protection (C.1) +TSPC_SM_2_3 True No security requirements (M) +TSPC_SM_2_4 False (*) OOB supported (O) +TSPC_SM_2_5 (^) LE Secure Connections (C.2) +------------------------------------------------------------------------------- +C.1: If TSPC_SM_2_1 is supported then Mandatory, else Optional +C.2: Optional IF Core 4.2 OR Core 4.2+HS are supported, otherwise Excluded +------------------------------------------------------------------------------- + + + Encryption Key Size +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_SM_3_1 True Encryption Key Size Negotiation (M) +------------------------------------------------------------------------------- + + + Pairing Method +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_SM_4_1 True Just Works (O) +TSPC_SM_4_2 True Passkey Entry (C.1) +TSPC_SM_4_3 False (*) Out of Band (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of the defined methods IF TSPC_SM_2_1 is + supported, otherwise Excluded. +------------------------------------------------------------------------------- + + + Security Initiation +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_SM_5_1 True Encryption Setup using STK (C.3) +TSPC_SM_5_2 True Encryption Setup using LTK (O) +TSPC_SM_5_3 True Slave Initiated Security (C.1) +TSPC_SM_5_4 True Slave Initiated Security – Master response(C.2) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_SM_1_2 is supported, otherwise Excluded +C.2: Mandatory if TSPC_SM_1_1 is supported, otherwise Excluded +C.3: Mandatory IF TSPC_SM_2_1 OR TSPC_SM_2_2 OR TSPC_SM_2_4 is supported, + otherwise Excluded +------------------------------------------------------------------------------- + + + Signing Algorithm +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_SM_6_1 True Signing Algorithm - Generation (O) +TSPC_SM_6_2 True Signing Algorithm - Resolving (O) +------------------------------------------------------------------------------- + + + Key Distribution +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_SM_7_1 True Encryption Key (C.1) +TSPC_SM_7_2 True Identity Key (C.2) +TSPC_SM_7_3 True Signing Key (C.3) +------------------------------------------------------------------------------- +C.1: Mandatory if TSPC_GAP_24_2 OR TSPC_GAP_42_6 is supported, ELSE Optional +C.2: Mandatory if TSPC_GAP_26_3 is supported, ELSE Optional +C.3: Mandatory if TSPC_GAP_25_6 OR TSPC_GAP_35_6 is supported, ELSE Optional +------------------------------------------------------------------------------- diff --git a/android/pics-spp.txt b/android/pics-spp.txt new file mode 100644 index 0000000..d6bf97a --- /dev/null +++ b/android/pics-spp.txt @@ -0,0 +1,99 @@ +SPP PICS for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + + Profile Version +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SPP_0_1 False SPP v1.1 (C.1) +TSPC_SPP_0_2 True (*) SPP v1.2 (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support only one Profile version. +------------------------------------------------------------------------------- + + + Device Role +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SPP_1_1 True (*) Device A (DevA) (C.1) +TSPC_SPP_1_2 True (*) Device B (DevB) (C.1) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + Support of SPP Service +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SPP_2_1 True (*) Support of Serial Profile Service (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory for devices that support Serial Profile for serial cable + emulation as a Bluetooth service. Irrelevant of devices that only + support Serial Profile for usage by another application profile + e.g. Fax Profile, Dun Profile, Hands free Profile, etc. +------------------------------------------------------------------------------- + + + Application Procedures +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SPP_3_1 True (*) Establish link and set up virtual serial + connection (C.1) +TSPC_SPP_3_2 True (*) Accept link and virtual serial connection + establishment (C.2) +TSPC_SPP_3_3 True (*) Register Service record for application in + local SDP database (C.2) +TSPC_SPP_3_4 True (*) No release in Sniff mode. Sniff mode enabled + in the Link Manager (O) +TSPC_SPP_3_5 True (*) No release in Hold mode. Hold mode enabled + in the Link Manager (O) +TSPC_SPP_3_6 True (*) No release in Park mode. Park mode enabled + in the Link Manager (O) +TSPC_SPP_3_7 True (*) No release after Master/Slave switch. M/S + switch enabled in the Link manager (O) +------------------------------------------------------------------------------- +C.1: Mandatory for Device A, Irrelevant for Device B. +C.2: Mandatory for Device B, Irrelevant for Device A. +------------------------------------------------------------------------------- + + + Service Port Profile Record Content (SerialPort UUID) +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SPP_4_1 True (*) SerialPort service class (UUID16: 0x1101) (C.1) +TSPC_SPP_4_2 True (*) Protocol0, L2CAP (C.1) +TSPC_SPP_4_3 True (*) Protocol1, RFCOMM (C.1) +TSPC_SPP_4_4 True (*) Server Channel number (C.1) +TSPC_SPP_4_5 True (*) Displayable text name (C.2) +TSPC_SPP_4_6 True (*) BluetoothProfileDescriptorList (C.3) +------------------------------------------------------------------------------- +C.1: Mandatory for role B, if capability 'Support of Serial Profile Service' + (TSPC_SPP_2_1) supported. Irrelevant for role A. +C.2: Mandatory to support if TSPC_SPP_2_1 AND TSPC_SPP_0_1 are supported, + otherwise Optional. +C.3: Mandatory to support if TSPC_SPP_2_1 AND TSPC_SPP_0_2 are supported, + otherwise Optional. +------------------------------------------------------------------------------- + + + Encryption +------------------------------------------------------------------------------- +Item Selected Description +------------------------------------------------------------------------------- +TSPC_SPP_5_1 True (*) Initiate encryption (O) +TSPC_SPP_5_2 True Accept encryption request (M) +TSPC_SPP_5_3 True Point to point encryption (M) +TSPC_SPP_5_4 True Stop encryption (M) +------------------------------------------------------------------------------- diff --git a/android/pixit-a2dp.txt b/android/pixit-a2dp.txt new file mode 100644 index 0000000..c7f9762 --- /dev/null +++ b/android/pixit-a2dp.txt @@ -0,0 +1,30 @@ +A2DP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +# - should be set to PTS's bin/audio folder + +Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_security_enabled TRUE (*) +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_SRC_class_of_device 080418 +TSPX_SNK_class_of_device 04041C +TSPX_pin_code 0000 +TSPX_delete_link_key FALSE +TSPX_time_guard 300000 +TSPX_use_implicit_send TRUE +TSPX_media_directory C:\Program Files\Bluetooth SIG\Bluetooth PTS\ + bin\audio (#) +TSPX_no_avrcp FALSE (*) +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_rfcomm_channel 8 +TSPX_l2cap_psm 1011 +TSPX_no_confirmations FALSE +TSPX_cover_art_uuid 3EEE +------------------------------------------------------------------------------- diff --git a/android/pixit-avctp.txt b/android/pixit-avctp.txt new file mode 100644 index 0000000..ef85510 --- /dev/null +++ b/android/pixit-avctp.txt @@ -0,0 +1,39 @@ +AVCTP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +# - should be set to PTS's bin/audio folder + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_avctp_psm 0017 +TSPX_avctp_profile_id 110E +TSPX_connect_avdtp TRUE +TSPX_avctp_tester_command_data +TSPX_avctp_tester_response_data +TSPX_avctp_iut_command_data +TSPX_avctp_iut_response_data +TSPX_bd_addr_iut 11223344556677 (*&) +TSPX_pin_code 0000 +TSPX_delete_link_key FALSE +TSPX_security_enabled FALSE +TSPX_class_of_device 20050C +TSPX_player_feature_bitmask 0000000000000007FFF00070000000000 +TSPX_avrcp_version +TSPX_establish_avdtp_stream TRUE +TSPX_tester_av_role SNK (*) +TSPX_time_guard 300000 +TSPX_avrcp_only FALSE +TSPX_use_implicit_send TRUE +TSPX_media_directory C:\Program Files\Bluetooth SIG\Bluetooth PTS\ + bin\audio (#) +TSPX_no_confirmations FALSE +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_rfcomm_channel 8 +TSPX_l2cap_psm 1011 +------------------------------------------------------------------------------- diff --git a/android/pixit-avdtp.txt b/android/pixit-avdtp.txt new file mode 100644 index 0000000..f73b676 --- /dev/null +++ b/android/pixit-avdtp.txt @@ -0,0 +1,31 @@ +AVDTP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +# - should be set to PTS's bin/audio folder + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_SNK_class_of_device 04041C +TSPX_SRC_class_of_device 080418 +TSPX_security_control_data +TSPX_content_protection_data +TSPX_content_protection_type +TSPX_no_avrcp TRUE +TSPX_media_directory +TSPX_bd_addr_iut 11223344556677 (*&) +TSPX_delete_link_key FALSE +TSPX_pin_code 1234 +TSPX_security_enabled TRUE (*) +TSPX_time_guard 300000 +TSPX_use_implicit_send TRUE +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_l2cap_psm 1003 +TSPX_rfcomm_channel 8 +TSPX_no_confirmations FALSE +------------------------------------------------------------------------------- diff --git a/android/pixit-avrcp.txt b/android/pixit-avrcp.txt new file mode 100644 index 0000000..9d5b87e --- /dev/null +++ b/android/pixit-avrcp.txt @@ -0,0 +1,36 @@ +AVRCP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +# - should be set to PTS's bin/audio folder + +Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_security_enabled FALSE +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_class_of_device 20050C +TSPX_player_feature_bitmask 0000000000000007FFF00070000000000 +TSPX_pin_code 0000 +TSPX_delete_link_key FALSE +TSPX_time_guard 300000 +TSPX_avrcp_only FALSE +TSPX_search_string 3 +TSPX_max_avc_fragments 10 +TSPX_establish_avdtp_stream TRUE +TSPX_use_implicit_send TRUE +TSPX_avrcp_version +TSPX_tester_av_role +TSPX_media_directory C:\Program Files\Bluetooth SIG\Bluetooth PTS\ + bin\audio (#) +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_rfcomm_channel 8 +TSPX_l2cap_psm 1011 +TSPX_no_confirmations FALSE +TSPX_no_cover_art_folder +TSPX_cover_art_folder +------------------------------------------------------------------------------- diff --git a/android/pixit-bnep.txt b/android/pixit-bnep.txt new file mode 100644 index 0000000..1366535 --- /dev/null +++ b/android/pixit-bnep.txt @@ -0,0 +1,30 @@ +BNEP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +# - should be set to PTS's bin/audio folder + +Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_class_of_device 04041C +TSPX_security_control_data +TSPX_content_protection_data +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_delete_link_key FALSE +TSPX_pin_code 1234 +TSPX_security_enabled FALSE +TSPX_time_guard 300000 +TSPX_use_implicit_send TRUE +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_l2cap_psm 000F +TSPX_rfcomm_channel 8 +TSPX_no_confirmations FALSE +TSPX_UUID_dest_address 1116 +TSPX_UUID_source_address 1115 +TSPX_MAC_dest_address 000000000000 (*&) +TSPX_MAC_source_address 000000000000 (*&) diff --git a/android/pixit-did.txt b/android/pixit-did.txt new file mode 100644 index 0000000..0a0f1cc --- /dev/null +++ b/android/pixit-did.txt @@ -0,0 +1,24 @@ +DID PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_security_enabled False +TSPX_ClientExecutableURL False (*) +TSPX_ServiceDescription False (*) +TSPX_DocumentationURL False (*) +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_class_of_device_pts 200404 +TSPX_device_search_time 30 +TSPX_delete_link_key False +TSPX_pin_code 0000 +TSPX_time_guard 200000 +TSPX_use_implicit_send True +TSPX_secure_simple_pairing_pass_key_confirmation False +------------------------------------------------------------------------------- diff --git a/android/pixit-dis.txt b/android/pixit-dis.txt new file mode 100644 index 0000000..e53532e --- /dev/null +++ b/android/pixit-dis.txt @@ -0,0 +1,26 @@ +DIS PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_time_guard 180000 +TSPX_use_implicit_send TRUE +TSPX_tester_database_file C:/Program Files/... +TSPX_mtu_size 23 +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_delete_link_key FALSE +TSPX_pin_code 0000 +TSPX_use_dynamic_pin FALSE +TSPX_delete_ltk FALSE +TSPX_security_enabled TRUE (*) +TSPX_iut_setup_att_over_br_edr FALSE +TSPX_tester_appearance 0000 +TSPX_iut_use_resolvable_random_address FALSE +------------------------------------------------------------------------------- diff --git a/android/pixit-gap.txt b/android/pixit-gap.txt new file mode 100644 index 0000000..92a8135 --- /dev/null +++ b/android/pixit-gap.txt @@ -0,0 +1,60 @@ +GAP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +# - should be set to IUT name + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_bd_addr_PTS C000DEADBEEF +TSPX_broadcaster_class_of_device 100104 +TSPX_observer_class_of_device 100104 +TSPX_peripheral_class_of_device 100104 +TSPX_central_class_of_device 100104 +TSPX_security_enabled True +TSPX_delete_link_key True +TSPX_pin_code 0000 +TSPX_time_guard 300000 +TSPX_use_implicit_send True +TSPX_use_dynamic_pin False +TSPX_secure_simple_pairing_pass_key_confirmation False +TSPX_using_public_device_address True +TSPX_using_random_device_address False +TSPX_lim_adv_timeout 30720 +TSPX_gen_disc_adv_min 30720 +TSPX_lim_disc_scan_min 10240 +TSPX_gen_disc_scan_min 10240 +TSPX_database_file Database-GAP.sig +TSPX_iut_rx_mtu 23 +TSPX_iut_private_address_interval 30000 (*) +TSPX_iut_privacy_enabled False +TSPX_psm 1001 +TSPX_iut_valid_connection_interval_min 00C8 +TSPX_iut_valid_conneciton_interval_max 0960 +TSPX_iut_valid_connection_latency 0006 +TSPX_iut_valid_timeout_multiplier 0962 +TSPX_iut_connection_parameter_timeout 30000 +TSPX_iut_invalid_connection_interval_min 0000 +TSPX_iut_invalid_conneciton_interval_max 0000 +TSPX_iut_invalid_connection_latency 0000 +TSPX_iut_invalid_timeout_multiplier 0000 +TSPX_LE_scan_interval 0010 +TSPX_LE_scan_window 0010 +TSPX_con_interval_min 0032 +TSPX_con_interval_max 0046 +TSPX_con_latency 0000 +TSPX_supervision_timeout 07D0 +TSPX_minimum_ce_length 0000 +TSPX_maximum_ce_length 0000 +TSPX_pairing_before_service_request False +TSPX_iut_mandates_mitm False +TSPX_encryption_before_service_request False +TSPX_tester_appearance 0000 +TSPX_iut_advertising_data_in_broadcasting_mode [set to default value] +TSPX_iut_device_name_in_adv_packet_for_random_address PTS-66DE (#) +------------------------------------------------------------------------------- diff --git a/android/pixit-gatt.txt b/android/pixit-gatt.txt new file mode 100644 index 0000000..c6cfaa7 --- /dev/null +++ b/android/pixit-gatt.txt @@ -0,0 +1,32 @@ +GATT PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_security_enabled FALSE +TSPX_delete_link_key TRUE +TSPX_time_guard 180000 +TSPX_selected_handle 0012 +TSPX_use_implicit_send TRUE +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_iut_use_dynamic_bd_addr FALSE +TSPX_iut_setup_att_over_br_edr FALSE +TSPX_tester_database_file [Path to GATT Test + Database] +TSPX_iut_is_client_periphral FALSE +TSPX_iut_is_server_central FALSE +TSPX_mtu_size 23 +TSPX_pin_code 0000 +TSPX_use_dynamic_pin FALSE +TSPX_delete_ltk FALSE +TSPX_characteristic_readable FALSE +TSPX_tester_appearance 0000 +TSPX_iut_use_resolvable_random_access FALSE +------------------------------------------------------------------------------- diff --git a/android/pixit-gavdp.txt b/android/pixit-gavdp.txt new file mode 100644 index 0000000..0ee9eca --- /dev/null +++ b/android/pixit-gavdp.txt @@ -0,0 +1,32 @@ +GAVDP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +^ - should be set accordingly +# - should be set to PTS's bin/audio folder + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_SNK_class_of_device 04041C +TSPX_SRC_class_of_device 080418 +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_delete_link_key FALSE +TSPX_media_directory C:\Program Files\Bluetooth SIG\Bluetooth PTS\ + bin\audio (#) +TSPX_no_avrcp FALSE +TSPX_pin_code 0000 +TSPX_security_enabled FALSE +TSPX_tester_av_role +TSPX_time_guard 300000 +TSPX_use_implicit_send TRUE +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_rfcomm_channel 8 +TSPX_l2cap_psm 1011 +TSPX_no_confirmations FALSE +TSPX_cover_art_uuid +------------------------------------------------------------------------------- diff --git a/android/pixit-hdp.txt b/android/pixit-hdp.txt new file mode 100644 index 0000000..ca9b8a8 --- /dev/null +++ b/android/pixit-hdp.txt @@ -0,0 +1,32 @@ +HDP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_security_enabled TRUE +TSPX_delete_link_key FALSE +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_sink_device_class_of_device 000900 +TSPX_source_device_class_of_device 000900 +TSPX_pin_code 0000 +TSPX_use_dynamic_pin FALSE +TSPX_use_implicit_send TRUE +TSPX_MCAP_l2cap_psm_control 1001 +TSPX_MCAP_l2cap_psm_data 1003 +TSPX_MCAP_sync_lead_time 0BB8 +TSPX_MCAP_timestamp_native_resolution 2233 +TSPX_MCAP_timestamp_native_accuracy 1400 +TSPX_MCAP_timestamp_required_accuracy 6400 +TSPX_DC_max 1 +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_time_guard 60000000 +TSPX_ieee_device_specialization 10417 +TSPX_ieee_standard_configuration TRUE +TSPX_MCAP_bluetooth_clock_access_resolution 55 +------------------------------------------------------------------------------- diff --git a/android/pixit-hfp.txt b/android/pixit-hfp.txt new file mode 100644 index 0000000..896744e --- /dev/null +++ b/android/pixit-hfp.txt @@ -0,0 +1,41 @@ +HFP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +# - should be set to the respective phone numbers +^ - should be set according to the reported phone number's type + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_security_enabled TRUE +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_hf_class_of_device 200408 +TSPX_ag_class_of_device 400204 +TSPX_packet_type_sco 00A0 +TSPX_phone_number 1234567 (#) +TSPX_second_phone_number 7654321 (#) +TSPX_phone_number_type 145 (*^) +TSPX_second_phone_number_type 145 (*^) +TSPX_phone_number_memory 1 +TSPX_phone_number_memory_invalid_index 9999 +TSPX_scan_all_memory_dial_locations FALSE +TSPX_pin_code 0000 +TSPX_time_guard 300000 +TSPX_use_implicit_send TRUE +TSPX_verbose_implicit_send FALSE +TSPX_delete_link_key FALSE +TSPX_server_channel_tester 01 +TSPX_server_channel_iut 00 +TSPX_verify_CLIP_information TRUE +TSPX_no_fail_verdict FALSE +TSPX_network_supports_correct_call_and_callstatus TRUE +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_AG_match_tester_BRSF_codec_negotiation_bit FALSE +TSPX_HFP_Unsupported_HF_Indicator_1 99 +TSPX_HFP_Supported_HF_Indiccator_1 1 +TSPX_Automation FALSE +------------------------------------------------------------------------------- diff --git a/android/pixit-hid.txt b/android/pixit-hid.txt new file mode 100644 index 0000000..511957b --- /dev/null +++ b/android/pixit-hid.txt @@ -0,0 +1,31 @@ +HID PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_security_enabled True +TSPX_delete_link_key True +TSPX_query_iut_sdp True +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_pointing_device_class_of_device 002580 +TSPX_keyboard_device_class_of_device 002540 +TSPX_host_class_of_device 100108 +TSPX_pts_device_role MOUSE +TSPX_pin_code 0000 +TSPX_use_dynamic_pin_code False +TSPX_time_guard 6000000 +TSPX_hid_data_interval 1000 +TSPX_use_implicit_send True +TSPX_verbose_implicit_send False +TSPX_no_fail_verdicts False +TSPX_time_reconnect 30000 +TSPX_secure_simple_pairing_pass_key_confirmation False +TSPX_hid_report_id 1 +TSPX_hid_report_data ff00 (*) +------------------------------------------------------------------------------- diff --git a/android/pixit-hogp.txt b/android/pixit-hogp.txt new file mode 100644 index 0000000..6a38d19 --- /dev/null +++ b/android/pixit-hogp.txt @@ -0,0 +1,29 @@ +HOGP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_time_guard 180000 +TSPX_use_implicit_send TRUE +TSPX_tester_database_file [Path to HOGP Test + Database] +TSPX_mtu_size 23 +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_delete_link_key FALSE +TSPX_pin_code 0000 +TSPX_use_dynamic_pin FALSE +TSPX_delete_ltk FALSE +TSPX_security_enabled TRUE +TSPX_input_report_data CDA6F8B3AA +TSPX_output_report_data 001234567890EF +TSPX_feature_report_data 872D3F45EA +TSPX_tester_appearance 03C0 +TSPX_iut_use_resolvable_random_address FALSE +------------------------------------------------------------------------------- diff --git a/android/pixit-hsp.txt b/android/pixit-hsp.txt new file mode 100644 index 0000000..9ba74d2 --- /dev/null +++ b/android/pixit-hsp.txt @@ -0,0 +1,30 @@ +HSP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_security_enabled TRUE +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_hs_class_of_device 200404 +TSPX_ag_class_of_device 400204 +TSPX_packet_type_sco 00A0 +TSPX_pin_code 0000 +TSPX_time_guard 20000000 +TSPX_use_implicit_send TRUE +TSPX_verbose_implicit_send FALSE +TSPX_delete_link_key FALSE +TSPX_server_channel_tester 01 +TSPX_server_channel_iut 00 +TSPX_no_fail_verdict FALSE +TSPX_remote_audio_volume_control TRUE +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_inband_ring_only FALSE +TSPX_no_ring_or_inband_ring_tone FALSE +TSPX_iut_establish_audio_before_RING FALSE +------------------------------------------------------------------------------- diff --git a/android/pixit-iopt.txt b/android/pixit-iopt.txt new file mode 100644 index 0000000..181a91d --- /dev/null +++ b/android/pixit-iopt.txt @@ -0,0 +1,23 @@ +IOPT PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_security_enabled FALSE +TSPX_delete_link_key FALSE +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_class_of_device_pts 200404 +TSPX_class_of_device_test_pts_initiator TRUE +TSPX_limited_inquiry_used FALSE +TSPX_pin_code 0000 +TSPX_time_guard 200000 +TSPX_device_search_time 20 +TSPX_use_implicit_send TRUE +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +------------------------------------------------------------------------------- diff --git a/android/pixit-l2cap.txt b/android/pixit-l2cap.txt new file mode 100644 index 0000000..23fad19 --- /dev/null +++ b/android/pixit-l2cap.txt @@ -0,0 +1,59 @@ +L2CAP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_client_class_of_device 100104 +TSPX_server_class_of_device 100104 +TSPX_security_enabled TRUE (*) +TSPX_delete_link_key FALSE +TSPX_pin_code 0000 +TSPX_flushto FFFF +TSPX_inmtu 02A0 +TSPX_no_fail_verditcs FALSE +TSPX_oumtu 02A0 +TSPX_tester_mps 0017 +TSPX_tester_mtu 02A0 +TSPX_iut_role_initiator TRUE (*) +TSPX_le_psm 0080 (*) +TSPX_psm 1011 (*) +TSPX_psm_unsupported 00F1 +TSPX_psm_authentication_required 00F2 +TSPX_psm_authorization_required 00F3 +TSPX_psm_encryption_key_size_required 00F4 +TSPX_time_guard 180000 +TSPX_timer_ertx 120000 +TSPX_timer_ertx_max 300000 +TSPX_timer_ertx_min 60000 +TSPX_timer_rtx 10000 +TSPX_timer_rtx_max 60000 +TSPX_timer_rtx_min 1000 +TSPX_rfc_mode_tx_window_size 08 +TSPX_rfc_mode_max_transmit 03 +TSPX_rfc_mode_retransmission_timeout 07D0 +TSPX_rfc_mode_monitor_timeout 2EE0 +TSPX_rfc_mode_maximum_pdu_size 02A0 +TSPX_extended_window_size 0012 +TSPX_use_implicit_send TRUE +TSPX_use_dynamic_pin FALSE +TSPX_iut_SDU_size_in_bytes 144 +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_iut_address_type_random FALSE +TSPX_tester_adv_interval_min 0030 +TSPX_tester_adv_interval_max 0050 +TSPX_tester_le_scan_interval 0C80 +TSPX_tester_le_scan_window 0C80 +TSPX_tester_conn_interval_min 0028 +TSPX_tester_conn_interval_max 0050 +TSPX_tester_conn_latency 0000 +TSPX_tester_supervision_timeout 0C80 +TSPX_tester_min_CE_length 0080 +TSPX_tester_max_CE_length 0C80 +------------------------------------------------------------------------------- diff --git a/android/pixit-map.txt b/android/pixit-map.txt new file mode 100644 index 0000000..90272cb --- /dev/null +++ b/android/pixit-map.txt @@ -0,0 +1,44 @@ +MAP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +# - should be set to tester's phone number +$ - should be set to IUT e-mail address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_client_class_of_device 100204 +TSPX_delete_link_key FALSE +TSPX_get_object_name put.gif +TSPX_initial_path +TSPX_l2cap_psm 1001 +TSPX_no_confirmations FALSE +TSPX_pin_code 0000 +TSPX_rfcomm_channel 8 +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_security_enabled TRUE +TSPX_server_class_of_device 100204 +TSPX_time_guard 300000 +TSPX_use_implicit_send TRUE +TSPX_Message_Access_rfcomm_channel 1 +TSPX_Message_Notification_rfcomm_channel 2 +TSPX_SPP_rfcomm_channel 03 +TSPX_filter_period_begin 20100101T000000 +TSPX_filter_period_end 20111231T125959 +TSPX_filter_recipient PTS +TSPX_filter_originator PTS +TSPX_default_message_upload_folder_in_msg draft +TSPX_default_test_folder_in_msg inbox +TSPX_message_notification_l2cap_psm 1003 +TSPX_message_notification_rfcomm_channel 9 +TSPX_upload_msg_phonenumber 123456789 (#) +TSPX_upload_msg_emailaddress IUT-email ($) +TSPX_Automation FALSE +------------------------------------------------------------------------------- diff --git a/android/pixit-mcap.txt b/android/pixit-mcap.txt new file mode 100644 index 0000000..a8bbe52 --- /dev/null +++ b/android/pixit-mcap.txt @@ -0,0 +1,37 @@ +MCAP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_delete_link_key FALSE +TSPX_MCAP_DC_max 1 (*) +TSPX_MCAP_l2cap_psm_control 1003 +TSPX_MCAP_l2cap_psm_control_B +TSPX_MCAP_l2cap_psm_data 1005 +TSPX_MCAP_l2cap_psm_data_B +TSPX_MCAP_bluetooth_clock_access_resolution 55 +TSPX_MCAP_create_mdl_configuration +TSPX_MCAP_data_channel_setup_mode +TSPX_MCAP_iut_initiate_connection FALSE +TSPX_MCAP_mdep_id 12 +TSPX_MCAP_profile_name +TSPX_MCAP_sync_lead_time 0BB8 +TSPX_tester_role +TSPX_MCAP_timestamp_native_accuracy 1400 +TSPX_MCAP_timestamp_native_resolution 2233 +TSPX_MCAP_timestamp_required_accuracy 6400 +TSPX_host_class_of_device 100108 +TSPX_pin_code 0000 +TSPX_security_enabled TRUE (*) +TSPX_time_guard 6000000 +TSPX_use_dynamic_pin FALSE +TSPX_use_implicit_send TRUE +TSPX_verbose_implicit_send TRUE +------------------------------------------------------------------------------- diff --git a/android/pixit-mps.txt b/android/pixit-mps.txt new file mode 100644 index 0000000..116a8c0 --- /dev/null +++ b/android/pixit-mps.txt @@ -0,0 +1,47 @@ +MPS PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +^ - should be set accordingly +# - should be set according to the reported phone number's type + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_avrcp_revision 1.5 (^) +TSPX_class_of_device 20050C +TSPX_establish_avdtp_stream TRUE +TSPX_iut_establishes_initial_condition FALSE +TSPX_tester_device A (*) +TSPX_media_directory +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_delete_link_key FALSE +TSPX_pin_code 0000 +TSPX_security_enabled FALSE +TSPX_time_guard 300000 +TSPX_use_implicit_send TRUE +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_l2cap_psm 1003 +TSPX_rfcomm_channel 8 +TSPX_no_confirmations FALSE +TSPX_AG_match_tester_BRSF_codec_negotiation_bit FALSE +TSPX_network_supports_correct_call_and_callstatus TRUE +TSPX_no_fail_verdicts FALSE +TSPX_packet_type_sco 00A0 +TSPX_phone_number 1234567 (^) +TSPX_phone_number_memory 1 +TSPX_phone_number_memory_invalid_index 9999 +TSPX_phone_number_type 145 (*#) +TSPX_scan_all_memory_dial_locations FALSE +TSPX_second_phone_number 1234567 (^) +TSPX_second_phone_type 129 +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_server_channel_iut 00 +TSPX_server_channel_tester 01 +TSPX_verbose_implicit_send FALSE +TSPX_verify_CLIP_information TRUE +------------------------------------------------------------------------------- diff --git a/android/pixit-opp.txt b/android/pixit-opp.txt new file mode 100644 index 0000000..f6651a1 --- /dev/null +++ b/android/pixit-opp.txt @@ -0,0 +1,27 @@ +OPP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_supported_extension jpg (*) +TSPX_unsupported_extension pts +TSPX_client_class_of_device 100104 +TSPX_server_class_of_device 100104 +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_l2cap_psm 1003 +TSPX_rfcomm_channel 8 +TSPX_no_confirmations FALSE +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_delete_link_key FALSE +TSPX_pin_code 0000 +TSPX_security_enabled FALSE +TSPX_time_guard 300000 +TSPX_use_implicit_send TRUE +------------------------------------------------------------------------------- diff --git a/android/pixit-pan.txt b/android/pixit-pan.txt new file mode 100644 index 0000000..713646e --- /dev/null +++ b/android/pixit-pan.txt @@ -0,0 +1,39 @@ +PAN PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT or PTS Bluetooth/MAC address respectively + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_GN_class_of_device 020104 +TSPX_NAP_class_of_device 020300 +TSPX_PANU_class_of_device 020104 +TSPX_time_guard 300000 +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_security_enabled False +TSPX_pin_code 0000 +TSPX_delete_link_key False +TSPX_use_implicit_send True +TSPX_iut_ip_address 192.168.1.100 (*&) +TSPX_iut_port_number 4242 +TSPX_PTS_ip_address 192.168.168.100 +TSPX_PTS_port_number 4242 +TSPX_bd_addr_PTS 112233445566 (*&) +TSPX_checksum E851 +TSPX_secure_simple_pairing_pass_key_confirmation False +TSPX_iut_friendly_bt_name gprs-pc +TSPX_PTS_role_when_iut_is_PANU default +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_l2cap_psm 000F +TSPX_rfcomm_channel 8 +TSPX_no_confirmations FALSE +TSPX_UUID_dest_address 0000 +TSPX_UUID_source_address 0000 +TSPX_MAC_dest_address 112233445566 (*&) +TSPX_MAC_source_address 112233445566 (*&) +------------------------------------------------------------------------------- diff --git a/android/pixit-pbap.txt b/android/pixit-pbap.txt new file mode 100644 index 0000000..9bf6c06 --- /dev/null +++ b/android/pixit-pbap.txt @@ -0,0 +1,37 @@ +PBAP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_security_enabled True +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_pin_code 0000 +TSPX_time_guard 100000 +TSPX_use_implicit_send True +TSPX_client_class_of_device 100204 +TSPX_server_class_of_device 100204 +TSPX_PSE_vCardSelector 0000000000000001 +TSPX_delete_link_key False +TSPX_PBAP_profile_version 1 +TSPX_PBAP_supported_repositories 1 +TSPX_PBAP_rfcomm_channel 1 +TSPX_telecom_folder_path telecom +TSPX_secure_simple_pairing_pass_key_confirmation False +TSPX_check_downloaded_contents_after_disconnect False +TSPX_SPP_rfcomm_channel 03 +TSPX_l2cap_psm 1005 +TSPX_rfcomm_channel 2 +TSPX_obex_auth_password 0000 +TSPX_no_confirmations False +TSPX_PSE_vCardSelector 0000000000000001 +TSPX_Automation False +TSPX_search_criteria PTS +------------------------------------------------------------------------------- diff --git a/android/pixit-rfcomm.txt b/android/pixit-rfcomm.txt new file mode 100644 index 0000000..88402a5 --- /dev/null +++ b/android/pixit-rfcomm.txt @@ -0,0 +1,28 @@ +RFCOMM PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +# - should be set to PTS's bin/audio folder + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_bd_addr_iut 11223344556677 (*&) +TSPX_delete_link_key FALSE +TSPX_pin_code 1234 +TSPX_security_enabled TRUE +TSPX_time_guard 300000 +TSPX_use_implicit_send TRUE +TSPX_service_name_tester COM5 +TSPX_class_of_device_tester 200408 +TSPX_server_channel_iut 01 (*) +TSPX_verification_pattern_length 4 +TSPX_T1_Acknowledgement_Timer 20000 +TSPX_T1_Acknowledgement_Timer_Dlc 300000 +TSPX_T2_Response_Timer 20000 +TSPX_max_frame_size_iut 127 +TSPX_RPN_parameters_iut 0302001113 +------------------------------------------------------------------------------- diff --git a/android/pixit-scpp.txt b/android/pixit-scpp.txt new file mode 100644 index 0000000..c8d9f37 --- /dev/null +++ b/android/pixit-scpp.txt @@ -0,0 +1,25 @@ +ScPP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_time_guard 180000 +TSPX_use_implicit_send TRUE +TSPX_tester_database_file C:/Program Files/... +TSPX_mtu_size 23 +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_delete_link_key FALSE +TSPX_pin_code 0000 +TSPX_use_dynamic_pin FALSE +TSPX_delete_ltk FALSE +TSPX_security_enabled FALSE +TSPX_tester_appearance 0000 +TSPX_iut_use_resolvable_random_address FALSE +------------------------------------------------------------------------------- diff --git a/android/pixit-sdp.txt b/android/pixit-sdp.txt new file mode 100644 index 0000000..b430f84 --- /dev/null +++ b/android/pixit-sdp.txt @@ -0,0 +1,45 @@ +SDP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address +^ - should be set accordingly +# - should be set according to the reported phone number's type + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_sdp_service_search_pattern 0100 +TSPX_sdp_service_search_pattern_no_results EEEE +TSPX_sdp_service_search_additional_protocol_descriptor_list +TSPX_sdp_service_search_bluetooth_profile_descriptor_list +TSPX_sdp_service_search_pattern_browse_group_list +TSPX_sdp_service_search_pattern_client_exe_url +TSPX_sdp_service_search_pattern_documentation_url +TSPX_sdp_service_search_pattern_icon_url +TSPX_sdp_service_search_pattern_language_base_attribute_id_list +TSPX_sdp_service_search_pattern_protocol_descriptor_list +TSPX_sdp_service_search_pattern_provider_name +TSPX_sdp_service_search_pattern_service_availability +TSPX_sdp_service_search_pattern_service_data_base_state 1000(*) +TSPX_sdp_service_search_pattern_service_description +TSPX_sdp_service_search_pattern_service_id +TSPX_sdp_service_search_pattern_service_info_time_to_live +TSPX_sdp_service_search_pattern_version_number_list 1000(*) +TSPX_sdp_service_search_pattern_service_name +TSPX_sdp_service_search_pattern_service_record_state +TSPX_sdp_unsupported_attribute_id EEEE +TSPX_security_enabled FALSE +TSPX_delete_link_key FALSE +TSPX_bd_addr_iut 112233445566(*&) +TSPX_class_of_device_pts 200404 +TSPX_class_of_device_test_pts_initiator TRUE +TSPX_limited_inquiry_used FALSE +TSPX_pin_code 0000 +TSPX_time_guard 200000 +TSPX_device_search_time 20 +TSPX_use_implicit_send TRUE +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +------------------------------------------------------------------------------- diff --git a/android/pixit-sm.txt b/android/pixit-sm.txt new file mode 100644 index 0000000..6facbb8 --- /dev/null +++ b/android/pixit-sm.txt @@ -0,0 +1,72 @@ +SM PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_SMP_pin_code 111111 +TSPX_OOB_Data 000000000000FE12036E5A + 889F4D +TSPX_peer_addr_type 00 +TSPX_own_addr_type 00 +TSPX_conn_interval_min 0190 +TSPX_conn_interval_max 0190 +TSPX_con_latency 0000 +TSPX_client_class_of_device 100104 +TSPX_server_class_of_device 100104 +TSPX_security_enabled TRUE +TSPX_delete_link_key TRUE +TSPX_pin_code 1234 +TSPX_ATTR_HANDLE 0000 +TSPX_ATTR_VALUE 000000000000000 +TSPX_delay_variation_in FFFFFFFF +TSPX_delay_variation_out FFFFFFFF +TSPX_flushto FFFF +TSPX_inmtu 02A0 +TSPX_inquiry_length 17 +TSPX_latency_in FFFFFFFF +TSPX_latency_out FFFFFFFF +TSPX_linkto 3000 +TSPX_max_nbr_retransmission 10 +TSPX_no_fail_verdicts FALSE +TSPX_outmtu 02A0 +TSPX_tester_role_optional L2CAP_ROLE_INITIATOR +TSPX_page_scan_mode 00 +TSPX_page_scan_repetition_mode 00 +TSPX_peak_bandwidth_in 00000000 +TSPX_peak_bandwidth_out 00000000 +TSPX_psm 0011 +TSPX_service_type_in 01 +TSPX_service_type_out 01 +TSPX_support_retransmissions TRUE +TSPX_time_guard 180000 +TSPX_timer_ertx 120000 +TSPX_timer_ertx_max 300000 +TSPX_timer_ertx_min 60000 +TSPX_timer_rtx 10000 +TSPX_timer_rtx_max 60000 +TSPX_timer_rtx_min 1000 +TSPX_token_bucket_size_in 00000000 +TSPX_token_bucket_size_out 00000000 +TSPX_token_rate_in 00000000 +TSPX_token_rate_out 00000000 +TSPX_rfc_mode_mode 03 +TSPX_rfc_mode_tx_window_size 08 +TSPX_rfc_mode_max_transmit 03 +TSPX_rfc_mode_retransmission_timeout 07D0 +TSPX_rfc_mode_monitor_timeout 2EE0 +TSPX_rfc_mode_maximum_pdu_size 02A0 +TSPX_extended_window_size 0012 +TSPX_use_implicit_send TRUE +TSPX_use_dynamic_pin FALSE +TSPX_iut_SDU_size_in_bytes 144 +TSPX_secure_simple_pairing_pass_key_confirmation FALSE +TSPX_Min_Encryption_Key_Length 07 +TSPX_Bonding_Flags 00 +------------------------------------------------------------------------------- diff --git a/android/pixit-spp.txt b/android/pixit-spp.txt new file mode 100644 index 0000000..4bd54d0 --- /dev/null +++ b/android/pixit-spp.txt @@ -0,0 +1,19 @@ +SPP PIXIT for the PTS tool. + +PTS version: 6.1 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_security_enabled TRUE +TSPX_pin_code 0000 +TSPX_time_guard 300000 +TSPX_delete_link_key FALSE +TSPX_stop_immediately_when_fail TRUE +TSPX_class_of_device_tester 200408 +------------------------------------------------------------------------------- diff --git a/android/pts-a2dp.txt b/android/pts-a2dp.txt new file mode 100644 index 0000000..7131a73 --- /dev/null +++ b/android/pts-a2dp.txt @@ -0,0 +1,70 @@ +PTS test results for A2DP + +PTS version: 6.1 +Tested 21-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + + Source (SRC) +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_SRC_CC_BV_09_I PASS Start streaming +TC_SRC_CC_BV_10_I PASS Start streaming +TC_SRC_REL_BV_01_I PASS When requested disconnect from IUT +TC_SRC_REL_BV_02_I PASS Start streaming +TC_SRC_SET_BV_01_I PASS +TC_SRC_SET_BV_02_I PASS +TC_SRC_SET_BV_03_I PASS Start streaming +TC_SRC_SET_BV_04_I PASS Start streaming +TC_SRC_SET_BV_05_I PASS Start streaming + IUT must be moved out of range + Initiate connection +TC_SRC_SET_BV_06_I PASS PTS issue#13469 + IUT must be moved out of range + To pass set TSPX_no_avrcp to TRUE +TC_SRC_SUS_BV_01_I PASS Start streaming + Stop streaming + Start streaming +TC_SRC_SUS_BV_02_I PASS Start streaming +TC_SRC_SDP_BV_01_I PASS +TC_SRC_AS_BV_01_I PASS Requires checking if the output on the IUT is + correct +TC_SRC_AS_BV_02_I N/A +TC_SRC_AS_BV_03_I N/A +TC_SRC_SYN_BV_02_I N/A +------------------------------------------------------------------------------- + + + Sink (SNK) +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_SNK_CC_BV_01_I N/A +TC_SNK_CC_BV_02_I N/A +TC_SNK_CC_BV_03_I N/A +TC_SNK_CC_BV_04_I N/A +TC_SNK_CC_BV_05_I N/A +TC_SNK_CC_BV_06_I N/A +TC_SNK_CC_BV_07_I N/A +TC_SNK_CC_BV_08_I N/A +TC_SNK_REL_BV_01_I N/A +TC_SNK_REL_BV_02_I N/A +TC_SNK_SET_BV_01_I N/A +TC_SNK_SET_BV_02_I N/A +TC_SNK_SET_BV_03_I N/A +TC_SNK_SET_BV_04_I N/A +TC_SNK_SET_BV_05_I N/A +TC_SNK_SET_BV_06_I N/A +TC_SNK_SUS_BV_01_I N/A +TC_SNK_SUS_BV_02_I N/A +TC_SNK_SDP_BV_02_I N/A +TC_SNK_AS_BV_01_I N/A +TC_SNK_AS_BV_02_I N/A +TC_SNK_SYN_BV_01_I N/A +------------------------------------------------------------------------------- diff --git a/android/pts-avctp.txt b/android/pts-avctp.txt new file mode 100644 index 0000000..ec48322 --- /dev/null +++ b/android/pts-avctp.txt @@ -0,0 +1,43 @@ +PTS test results for AVCTP + +PTS version: 6.1 +Tested: 21-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + + Controller (CT) +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_CT_CCM_BV_01_C N/A +TC_CT_CCM_BV_02_C N/A +TC_CT_CCM_BV_03_C N/A +TC_CT_CCM_BV_04_C N/A +TC_CT_CCM_BI_01_C N/A +TC_CT_NFR_BV_01_C N/A +TC_CT_NFR_BV_04_C PASS haltest: rc set_volume 5 + Note: IUT must be connectable and discoverable +TC_CT_FRA_BV_01_C N/A +TC_CT_FRA_BV_04_C N/A +------------------------------------------------------------------------------- + + + Target (TG) +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_TG_CCM_BV_01_C PASS +TC_TG_CCM_BV_02_C PASS +TC_TG_CCM_BV_03_C PASS +TC_TG_CCM_BV_04_C PASS +TC_TG_NFR_BV_02_C PASS +TC_TG_NFR_BV_03_C PASS +TC_TG_NFR_BI_01_C PASS +TC_TG_FRA_BV_02_C N/A Fragmentation not supported +TC_TG_FRA_BV_03_C N/A Fragmentation not supported +------------------------------------------------------------------------------- diff --git a/android/pts-avdtp.txt b/android/pts-avdtp.txt new file mode 100644 index 0000000..1a5699d --- /dev/null +++ b/android/pts-avdtp.txt @@ -0,0 +1,237 @@ +PTS test results for AVDTP + +PTS version: 6.1 +Tested: 22-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_ACP_SNK_L2C_EM_BV_02_C N/A +TC_ACP_SNK_SIG_FRA_BV_01_C N/A +TC_ACP_SNK_SIG_FRA_BV_02_C N/A +TC_ACP_SNK_SIG_SEC_BI_01_C N/A +TC_ACP_SNK_SIG_SEC_BV_02_C N/A +TC_ACP_SNK_SIG_SMG_BV_06_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BV_08_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BV_10_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BV_12_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BV_14_C N/A +TC_ACP_SNK_SIG_SMG_BV_16_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BV_18_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BV_20_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BV_22_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BV_24_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BV_26_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BV_27_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_ESR05_BV_14_C N/A +TC_ACP_SNK_SIG_SMG_BI_02_C N/A +TC_ACP_SNK_SIG_SMG_BI_03_C N/A +TC_ACP_SNK_SIG_SMG_BI_05_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BI_06_C N/A +TC_ACP_SNK_SIG_SMG_BI_08_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BI_09_C N/A +TC_ACP_SNK_SIG_SMG_BI_11_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BI_12_C N/A +TC_ACP_SNK_SIG_SMG_BI_14_C N/A +TC_ACP_SNK_SIG_SMG_BI_15_C N/A +TC_ACP_SNK_SIG_SMG_BI_17_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BI_18_C N/A +TC_ACP_SNK_SIG_SMG_BI_20_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BI_21_C N/A +TC_ACP_SNK_SIG_SMG_BI_23_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BI_24_C N/A +TC_ACP_SNK_SIG_SMG_BI_26_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BI_27_C N/A +TC_ACP_SNK_SIG_SMG_BI_28_C N/A +TC_ACP_SNK_SIG_SMG_BI_29_C N/A +TC_ACP_SNK_SIG_SMG_BI_33_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_BI_34_C N/A +TC_ACP_SNK_SIG_SMG_ESR04_BI_28_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SMG_ESR05_BI_15_C N/A +TC_ACP_SNK_SIG_SYN_BV_01_C PASS avdtptest -d SINK -l +TC_ACP_SNK_SIG_SYN_BV_03_C PASS avdtptest -d SINK -l +TC_ACP_SNK_TRA_BTR_BI_01_C PASS avdtptest -d SINK -l +TC_ACP_SNK_TRA_BTR_BV_02_C PASS avdtptest -d SINK -l +TC_ACP_SNK_TRA_MUX_BI_01_C N/A +TC_ACP_SNK_TRA_MUX_BV_05_C N/A +TC_ACP_SNK_TRA_MUX_BV_06_C N/A +TC_ACP_SNK_TRA_REC_BI_01_C N/A +TC_ACP_SNK_TRA_REC_BV_01_C N/A +TC_ACP_SNK_TRA_REC_BV_02_C N/A +TC_ACP_SNK_TRA_REP_BI_01_C N/A +TC_ACP_SNK_TRA_REP_BV_01_C N/A +TC_ACP_SNK_TRA_REP_BV_02_C N/A +TC_ACP_SNK_TRA_REP_ESR02_BI_01_C N/A +TC_ACP_SNK_TRA_RHC_BI_01_C N/A +TC_ACP_SNK_TRA_RHC_BV_01_C N/A +TC_ACP_SNK_TRA_RHC_BV_02_C N/A +TC_ACP_SRC_L2C_EM_BV_02_C N/A +TC_ACP_SRC_SIG_FRA_BV_01_C N/A +TC_ACP_SRC_SIG_FRA_BV_02_C N/A +TC_ACP_SRC_SIG_SEC_BI_01_C N/A +TC_ACP_SRC_SIG_SEC_BV_02_C N/A +TC_ACP_SRC_SIG_SMG_BV_06_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BV_08_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BV_10_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BV_12_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BV_14_C N/A +TC_ACP_SRC_SIG_SMG_BV_16_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BV_18_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BV_20_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BV_22_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BV_24_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BV_26_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BV_27_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_ESR05_BV_14_C N/A +TC_ACP_SRC_SIG_SMG_BI_02_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BI_03_C N/A +TC_ACP_SRC_SIG_SMG_BI_05_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BI_06_C N/A +TC_ACP_SRC_SIG_SMG_BI_08_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BI_09_C N/A +TC_ACP_SRC_SIG_SMG_BI_11_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BI_12_C N/A +TC_ACP_SRC_SIG_SMG_BI_14_C N/A +TC_ACP_SRC_SIG_SMG_BI_15_C N/A +TC_ACP_SRC_SIG_SMG_BI_17_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BI_18_C N/A +TC_ACP_SRC_SIG_SMG_BI_20_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BI_21_C N/A +TC_ACP_SRC_SIG_SMG_BI_23_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BI_24_C N/A +TC_ACP_SRC_SIG_SMG_BI_26_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BI_27_C N/A +TC_ACP_SRC_SIG_SMG_BI_28_C N/A +TC_ACP_SRC_SIG_SMG_BI_29_C N/A +TC_ACP_SRC_SIG_SMG_BI_33_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_BI_34_C N/A +TC_ACP_SRC_SIG_SMG_ESR04_BI_28_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SMG_ESR05_BI_15_C N/A +TC_ACP_SRC_SIG_SYN_BV_05_C PASS avdtptest -d SRC -l +TC_ACP_SRC_SIG_SYN_BV_06_C PASS avdtptest -d SRC -l +TC_ACP_SRC_TRA_BTR_BI_01_C PASS avdtptest -d SRC -l +TC_ACP_SRC_TRA_BTR_BV_01_C PASS avdtptest -d SRC -l -p -s start +TC_ACP_SRC_TRA_MUX_BI_01_C N/A +TC_ACP_SRC_TRA_MUX_BV_05_C N/A +TC_ACP_SRC_TRA_MUX_BV_06_C N/A +TC_ACP_SRC_TRA_REC_BI_01_C N/A +TC_ACP_SRC_TRA_REC_BV_01_C N/A +TC_ACP_SRC_TRA_REC_BV_02_C N/A +TC_ACP_SRC_TRA_REP_BI_01_C N/A +TC_ACP_SRC_TRA_REP_BV_01_C N/A +TC_ACP_SRC_TRA_REP_BV_02_C N/A +TC_ACP_SRC_TRA_REP_ESR02_BI_01_C N/A +TC_ACP_SRC_TRA_RHC_BI_01_C N/A +TC_ACP_SRC_TRA_RHC_BV_01_C N/A +TC_ACP_SRC_TRA_RHC_BV_02_C N/A +TC_INT_SNK_L2C_BM_BV_03_C N/A +TC_INT_SNK_L2C_BM_BV_06_C N/A +TC_INT_SNK_SIG_FRA_BV_01_C N/A +TC_INT_SNK_SIG_FRA_BV_02_C N/A +TC_INT_SNK_SIG_SEC_BV_01_C N/A +TC_INT_SNK_SIG_SMG_BV_05_C PASS avdtptest -d SINK -l -p +TC_INT_SNK_SIG_SMG_BV_07_C PASS avdtptest -d SINK -l -p + -v 0x0100 +TC_INT_SNK_SIG_SMG_BV_09_C PASS avdtptest -d SINK -l -p +TC_INT_SNK_SIG_SMG_BV_11_C PASS avdtptest -d SINK -l -s getconf +TC_INT_SNK_SIG_SMG_BV_13_C N/A +TC_INT_SNK_SIG_SMG_BV_15_C PASS avdtptest -d SINK -l -p +TC_INT_SNK_SIG_SMG_BV_19_C PASS avdtptest -d SINK -l -s close +TC_INT_SNK_SIG_SMG_BV_23_C PASS avdtptest -d SINK -l -p -s abort +TC_INT_SNK_SIG_SMG_BV_25_C PASS avdtptest -d SINK -l -p +TC_INT_SNK_SIG_SMG_BV_28_C PASS avdtptest -d SINK -l -p +TC_INT_SNK_SIG_SMG_BV_31_C PASS avdtptest -d SINK -l -p + -v 0x0100 +TC_INT_SNK_SIG_SMG_ESR05_BV_13_C N/A +TC_INT_SNK_SIG_SMG_BI_01_C N/A +TC_INT_SNK_SIG_SMG_BI_04_C N/A +TC_INT_SNK_SIG_SMG_BI_07_C N/A +TC_INT_SNK_SIG_SMG_BI_10_C N/A +TC_INT_SNK_SIG_SMG_BI_13_C N/A +TC_INT_SNK_SIG_SMG_BI_16_C N/A +TC_INT_SNK_SIG_SMG_BI_19_C N/A +TC_INT_SNK_SIG_SMG_BI_22_C N/A +TC_INT_SNK_SIG_SMG_BI_25_C N/A +TC_INT_SNK_SIG_SMG_BI_30_C PASS avdtptest -d SINK -l -p + -v 0x0100 +TC_INT_SNK_SIG_SMG_BI_32_C N/A +TC_INT_SNK_SIG_SMG_BI_35_C PASS avdtptest -d SINK -l -p +TC_INT_SNK_SIG_SMG_BI_36_C PASS avdtptest -d SINK -l -p +TC_INT_SNK_SIG_SMG_ESR05_BI_13_C N/A +TC_INT_SNK_SIG_SYN_BV_01_C PASS avdtptest -d SINK -l -p +TC_INT_SNK_SIG_SYN_BV_02_C PASS avdtptest -d SINK -l -p +TC_INT_SNK_SIG_SYN_BV_03_C PASS avdtptest -d SINK -l +TC_INT_SNK_SIG_SYN_BV_04_C PASS avdtptest -d SINK -l -p +TC_INT_SNK_TRA_BTR_BI_01_C PASS avdtptest -d SINK -l +TC_INT_SNK_TRA_BTR_BV_02_C PASS avdtptest -d SINK -l +TC_INT_SNK_TRA_MUX_BI_01_C N/A +TC_INT_SNK_TRA_MUX_BV_05_C N/A +TC_INT_SNK_TRA_MUX_BV_06_C N/A +TC_INT_SNK_TRA_REC_BI_01_C N/A +TC_INT_SNK_TRA_REC_BV_01_C N/A +TC_INT_SNK_TRA_REC_BV_02_C N/A +TC_INT_SNK_TRA_REP_BI_01_C N/A +TC_INT_SNK_TRA_REP_BV_01_C N/A +TC_INT_SNK_TRA_REP_BV_02_C N/A +TC_INT_SNK_TRA_REP_ESR02_BI_01_C N/A +TC_INT_SNK_TRA_RHC_BI_01_C N/A +TC_INT_SNK_TRA_RHC_BV_01_C N/A +TC_INT_SNK_TRA_RHC_BV_02_C N/A +TC_INT_SRC_L2C_BM_BV_03_C N/A +TC_INT_SRC_L2C_BM_BV_06_C N/A +TC_INT_SRC_SIG_FRA_BV_01_C N/A +TC_INT_SRC_SIG_FRA_BV_02_C N/A +TC_INT_SRC_SIG_SEC_BV_01_C N/A +TC_INT_SRC_SIG_SMG_BV_05_C PASS avdtptest -d SRC -l -p +TC_INT_SRC_SIG_SMG_BV_07_C PASS avdtptest -d SRC -l -p -v 0x0100 +TC_INT_SRC_SIG_SMG_BV_09_C PASS avdtptest -d SRC -l -p +TC_INT_SRC_SIG_SMG_BV_11_C PASS avdtptest -d SRC -l -s getconf +TC_INT_SRC_SIG_SMG_BV_13_C N/A +TC_INT_SRC_SIG_SMG_BV_15_C PASS avdtptest -d SRC -l -p +TC_INT_SRC_SIG_SMG_BV_17_C PASS avdtptest -d SRC -l -p -s start +TC_INT_SRC_SIG_SMG_BV_19_C PASS avdtptest -d SRC -l -s close +TC_INT_SRC_SIG_SMG_BV_21_C PASS avdtptest -d SRC -l -s suspend +TC_INT_SRC_SIG_SMG_BV_23_C PASS avdtptest -d SRC -l -p -s abort +TC_INT_SRC_SIG_SMG_BV_25_C PASS avdtptest -d SRC -l -p +TC_INT_SRC_SIG_SMG_BV_28_C PASS avdtptest -d SRC -l -p +TC_INT_SRC_SIG_SMG_BV_31_C PASS avdtptest -d SRC -l -p -v 0x0100 +TC_INT_SRC_SIG_SMG_ESR05_BV_13_C N/A +TC_INT_SRC_SIG_SMG_BI_01_C N/A +TC_INT_SRC_SIG_SMG_BI_04_C N/A +TC_INT_SRC_SIG_SMG_BI_07_C N/A +TC_INT_SRC_SIG_SMG_BI_10_C N/A +TC_INT_SRC_SIG_SMG_BI_13_C N/A +TC_INT_SRC_SIG_SMG_BI_16_C N/A +TC_INT_SRC_SIG_SMG_BI_19_C N/A +TC_INT_SRC_SIG_SMG_BI_22_C N/A +TC_INT_SRC_SIG_SMG_BI_25_C N/A +TC_INT_SRC_SIG_SMG_BI_30_C PASS avdtptest -d SRC -l -p -v 0x0100 +TC_INT_SRC_SIG_SMG_BI_32_C N/A +TC_INT_SRC_SIG_SMG_BI_35_C PASS avdtptest -d SRC -l -p +TC_INT_SRC_SIG_SMG_BI_36_C PASS avdtptest -d SRC -l -p +TC_INT_SRC_SIG_SMG_ESR05_BI_13_C N/A +TC_INT_SRC_SIG_SYN_BV_05_C PASS avdtptest -d SRC -l +TC_INT_SRC_SIG_SYN_BV_06_C PASS avdtptest -d SRC -l +TC_INT_SRC_TRA_BTR_BI_01_C PASS avdtptest -d SRC -l +TC_INT_SRC_TRA_BTR_BV_01_C PASS avdtptest -d SRC -l -p -s start +TC_INT_SRC_TRA_MUX_BI_01_C N/A +TC_INT_SRC_TRA_MUX_BV_05_C N/A +TC_INT_SRC_TRA_MUX_BV_06_C N/A +TC_INT_SRC_TRA_REC_BI_01_C N/A +TC_INT_SRC_TRA_REC_BV_01_C N/A +TC_INT_SRC_TRA_REC_BV_02_C N/A +TC_INT_SRC_TRA_REP_BI_01_C N/A +TC_INT_SRC_TRA_REP_BV_01_C N/A +TC_INT_SRC_TRA_REP_BV_02_C N/A +TC_INT_SRC_TRA_REP_ESR02_BI_01_C N/A +TC_INT_SRC_TRA_RHC_BI_01_C N/A +TC_INT_SRC_TRA_RHC_BV_01_C N/A +TC_INT_SRC_TRA_RHC_BV_02_C N/A +------------------------------------------------------------------------------- diff --git a/android/pts-avrcp.txt b/android/pts-avrcp.txt new file mode 100644 index 0000000..b7e1386 --- /dev/null +++ b/android/pts-avrcp.txt @@ -0,0 +1,242 @@ +PTS test results for AVRCP + +PTS version: 6.1 +Tested: 21-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + + Controller (CT) +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_CT_BGN_BV_01_I N/A +TC_CT_BGN_BV_02_I N/A +TC_CT_CA_BV_01_C N/A +TC_CT_CA_BV_03_C N/A +TC_CT_CA_BV_05_C N/A +TC_CT_CA_BV_07_C N/A +TC_CT_CA_BV_09_C N/A +TC_CT_CA_BV_11_C N/A +TC_CT_CA_BV_13_C N/A +TC_CT_CA_BV_15_C N/A +TC_CT_CA_BV_17_C N/A +TC_CT_CA_BV_18_C N/A +TC_CT_CA_BV_01_I N/A +TC_CT_CA_BV_02_I N/A +TC_CT_CA_BV_03_I N/A +TC_CT_CEC_BV_01_I N/A +TC_CT_CEC_BV_02_I N/A +TC_CT_CFG_BV_01_C N/A +TC_CT_CON_BV_01_C N/A +TC_CT_CON_BV_03_C N/A +TC_CT_CRC_BV_01_I N/A +TC_CT_CRC_BV_02_I N/A +TC_CT_ICC_BV_01_I N/A +TC_CT_ICC_BV_02_I N/A +TC_CT_MCN_CB_BV_01_C N/A +TC_CT_MCN_CB_BV_04_C N/A +TC_CT_MCN_CB_BV_07_C N/A +TC_CT_MCN_CB_BV_12_C N/A +TC_CT_MCN_CB_BV_01_I N/A +TC_CT_MCN_CB_BV_02_I N/A +TC_CT_MCN_CB_BV_03_I N/A +TC_CT_MCN_CB_BV_04_I N/A +TC_CT_MCN_CB_BV_05_I N/A +TC_CT_MCN_CB_BV_06_I N/A +TC_CT_MCN_NP_BV_01_C N/A +TC_CT_MCN_NP_BV_03_C N/A +TC_CT_MCN_NP_BV_05_C N/A +TC_CT_MCN_NP_BV_08_C N/A +TC_CT_MCN_NP_BV_10_C N/A +TC_CT_MCN_NP_BV_01_I N/A +TC_CT_MCN_NP_BV_02_I N/A +TC_CT_MCN_NP_BV_03_I N/A +TC_CT_MCN_NP_BV_04_I N/A +TC_CT_MCN_NP_BV_05_I N/A +TC_CT_MCN_NP_BV_06_I N/A +TC_CT_MCN_NP_BV_07_I N/A +TC_CT_MCN_SRC_BV_01_C N/A +TC_CT_MCN_SRC_BV_03_C N/A +TC_CT_MCN_SRC_BV_05_C N/A +TC_CT_MCN_SRC_BV_07_C N/A +TC_CT_MCN_SRC_BV_01_I N/A +TC_CT_MCN_SRC_BV_02_I N/A +TC_CT_MCN_SRC_BV_03_I N/A +TC_CT_MCN_SRC_BV_04_I N/A +TC_CT_MDI_BV_01_C N/A +TC_CT_MDI_BV_03_C N/A +TC_CT_MPS_BV_01_C N/A +TC_CT_MPS_BV_03_C N/A +TC_CT_MPS_BV_08_C N/A +TC_CT_MPS_BV_11_C N/A +TC_CT_MPS_BV_01_I N/A +TC_CT_MPS_BV_02_I N/A +TC_CT_MPS_BV_03_I N/A +TC_CT_NFY_BV_01_C N/A +TC_CT_PAS_BV_01_C N/A +TC_CT_PAS_BV_03_C N/A +TC_CT_PAS_BV_05_C N/A +TC_CT_PAS_BV_07_C N/A +TC_CT_PAS_BV_09_C N/A +TC_CT_PAS_BV_11_C N/A +TC_CT_PTH_BV_01_C N/A +TC_CT_PTH_BV_02_C N/A +TC_CT_PTT_BV_01_I N/A +TC_CT_PTT_BV_02_I N/A +TC_CT_PTT_BV_03_I N/A +TC_CT_PTT_BV_04_I N/A +TC_CT_PTT_BV_05_I N/A +TC_CT_RCR_BV_01_C N/A +TC_CT_RCR_BV_03_C N/A +TC_CT_VLH_BI_03_C PASS Send SetAbsolute Volume command by pressing + volume up or down buttons then adb logcat and + check VOLUME_CHANGED value +TC_CT_VLH_BI_04_C PASS adb logcat: check VOLUME_CHANGED value +TC_CT_VLH_BV_01_C PASS Send SetAbsolute Volume command by pressing + volume up or down buttons +TC_CT_VLH_BV_03_C PASS adb logcat: check VOLUME_CHANGED value +TC_CT_VLH_BV_01_I PASS Send SetAbsolute Volume command by pressing + volume up or down buttons +TC_CT_VLH_BV_02_I PASS Send SetAbsolute Volume command by pressing + volume up or down buttons +------------------------------------------------------------------------------- + + + Target (TG) +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_TG_BGN_BV_01_I N/A +TC_TG_BGN_BV_02_I N/A +TC_TG_CA_BI_01_C N/A +TC_TG_CA_BI_02_C N/A +TC_TG_CA_BI_03_C N/A +TC_TG_CA_BI_04_C N/A +TC_TG_CA_BI_05_C N/A +TC_TG_CA_BI_06_C N/A +TC_TG_CA_BI_07_C N/A +TC_TG_CA_BI_08_C N/A +TC_TG_CA_BI_09_C N/A +TC_TG_CA_BI_10_C N/A +TC_TG_CA_BV_02_C N/A +TC_TG_CA_BV_04_C N/A +TC_TG_CA_BV_06_C N/A +TC_TG_CA_BV_08_C N/A +TC_TG_CA_BV_10_C N/A +TC_TG_CA_BV_12_C N/A +TC_TG_CA_BV_14_C N/A +TC_TG_CA_BV_16_C N/A +TC_TG_CA_BV_01_I N/A +TC_TG_CA_BV_02_I N/A +TC_TG_CA_BV_03_I N/A +TC_TG_CEC_BV_01_I PASS +TC_TG_CEC_BV_02_I PASS +TC_TG_CFG_BI_01_C PASS +TC_TG_CFG_BV_02_C PASS +TC_TG_CON_BV_02_C N/A +TC_TG_CON_BV_04_C N/A +TC_TG_CON_BV_05_C N/A +TC_TG_CRC_BV_01_I PASS +TC_TG_CRC_BV_02_I PASS Disconnect from PTS +TC_TG_ICC_BV_01_I PASS +TC_TG_ICC_BV_02_I PASS +TC_TG_INV_BI_01_C PASS +TC_TG_INV_BI_02_C N/A +TC_TG_MCN_CB_BI_01_C N/A +TC_TG_MCN_CB_BI_02_C N/A +TC_TG_MCN_CB_BI_03_C N/A +TC_TG_MCN_CB_BI_04_C N/A +TC_TG_MCN_CB_BI_05_C N/A +TC_TG_MCN_CB_BV_02_C N/A +TC_TG_MCN_CB_BV_02_I N/A +TC_TG_MCN_CB_BV_03_C N/A +TC_TG_MCN_CB_BV_03_I N/A +TC_TG_MCN_CB_BV_04_I N/A +TC_TG_MCN_CB_BV_05_C N/A +TC_TG_MCN_CB_BV_05_I N/A +TC_TG_MCN_CB_BV_06_C N/A +TC_TG_MCN_CB_BV_06_I N/A +TC_TG_MCN_CB_BV_07_I N/A +TC_TG_MCN_CB_BV_08_C N/A +TC_TG_MCN_CB_BV_09_C N/A +TC_TG_MCN_CB_BV_10_C N/A +TC_TG_MCN_CB_BV_11_C N/A +TC_TG_MCN_CB_BV_13_C N/A +TC_TG_MCN_NP_BI_01_C N/A +TC_TG_MCN_NP_BI_02_C N/A +TC_TG_MCN_NP_BV_01_I N/A +TC_TG_MCN_NP_BV_02_C N/A +TC_TG_MCN_NP_BV_02_I N/A +TC_TG_MCN_NP_BV_03_I N/A +TC_TG_MCN_NP_BV_04_C N/A +TC_TG_MCN_NP_BV_04_I N/A +TC_TG_MCN_NP_BV_05_I N/A +TC_TG_MCN_NP_BV_06_C N/A +TC_TG_MCN_NP_BV_06_I N/A +TC_TG_MCN_NP_BV_07_C N/A +TC_TG_MCN_NP_BV_07_I N/A +TC_TG_MCN_NP_BV_09_C N/A +TC_TG_MCN_NP_BV_11_C N/A +TC_TG_MCN_SRC_BV_01_I N/A +TC_TG_MCN_SRC_BV_02_C N/A +TC_TG_MCN_SRC_BV_02_I N/A +TC_TG_MCN_SRC_BV_03_I N/A +TC_TG_MCN_SRC_BV_04_C N/A +TC_TG_MCN_SRC_BV_04_I N/A +TC_TG_MCN_SRC_BV_06_C N/A +TC_TG_MCN_SRC_BV_08_C N/A +TC_TG_MDI_BV_02_C PASS +TC_TG_MDI_BV_04_C PASS +TC_TG_MDI_BV_05_C PASS +TC_TG_MPS_BI_01_C N/A +TC_TG_MPS_BI_02_C N/A +TC_TG_MPS_BV_01_I N/A +TC_TG_MPS_BV_02_C N/A +TC_TG_MPS_BV_02_I N/A +TC_TG_MPS_BV_03_I N/A +TC_TG_MPS_BV_04_C N/A +TC_TG_MPS_BV_05_C N/A +TC_TG_MPS_BV_06_C N/A +TC_TG_MPS_BV_07_C N/A +TC_TG_MPS_BV_09_C N/A +TC_TG_MPS_BV_10_C N/A +TC_TG_MPS_BV_12_C N/A +TC_TG_NFY_BI_01_C PASS +TC_TG_NFY_BV_02_C PASS Change track when requested +TC_TG_NFY_BV_03_C N/A +TC_TG_NFY_BV_04_C PASS +TC_TG_NFY_BV_05_C PASS +TC_TG_NFY_BV_06_C N/A +TC_TG_NFY_BV_07_C N/A +TC_TG_NFY_BV_08_C PASS +TC_TG_PAS_BI_01_C N/A +TC_TG_PAS_BI_02_C N/A +TC_TG_PAS_BI_03_C N/A +TC_TG_PAS_BI_04_C N/A +TC_TG_PAS_BI_05_C N/A +TC_TG_PAS_BV_02_C N/A +TC_TG_PAS_BV_04_C N/A +TC_TG_PAS_BV_06_C N/A +TC_TG_PAS_BV_08_C N/A +TC_TG_PAS_BV_10_C N/A +TC_TG_PTT_BV_01_I PASS +TC_TG_PTT_BV_02_I PASS +TC_TG_PTT_BV_03_I N/A +TC_TG_PTT_BV_04_I N/A +TC_TG_PTT_BV_05_I N/A +TC_TG_RCR_BV_02_C PASS Use modified media metadata (artist, title, + album etc.) to be larger than 512 byte. +TC_TG_RCR_BV_04_C PASS Use modified media metadata (artist, title, + album etc.) to be larger than 512 byte. +TC_TG_VLH_BI_01_C N/A +TC_TG_VLH_BI_02_C N/A +TC_TG_VLH_BV_01_I N/A +TC_TG_VLH_BV_02_C N/A +TC_TG_VLH_BV_02_I N/A +TC_TG_VLH_BV_04_C N/A +------------------------------------------------------------------------------- diff --git a/android/pts-bnep.txt b/android/pts-bnep.txt new file mode 100644 index 0000000..8b6986a --- /dev/null +++ b/android/pts-bnep.txt @@ -0,0 +1,60 @@ +PTS test results for BNEP + +PTS version: 6.1 +Tested: 11-May-2015 +Android version: 5.1 +Kernel version: 4.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +-------------------------------------------------------------------------------- +Test Name Result Notes +-------------------------------------------------------------------------------- +TC_CTRL_BV_01_C PASS bneptest -s -b -n +TC_CTRL_BV_02_C PASS bneptest -c -b -n +TC_CTRL_BV_03_C PASS bneptest -s -b -n +TC_CTRL_BV_04_C PASS PTS issue #13169 + bneptest -s -b -n +TC_CTRL_BV_05_C PASS PTS issue #13169 + bneptest -s -b -n +TC_CTRL_BV_06_C PASS PTS issue #13169 + bneptest -s -b -n +TC_CTRL_BV_07_C PASS PTS issue #13169 + bneptest -c -b -n + -t 3 -d 0 -e 1500 -y 1 +TC_CTRL_BV_08_C PASS PTS issue #13169 + bneptest -s -b -n +TC_CTRL_BV_09_C PASS bneptest -c -b -n + -t 5 -g 00:00:00:00:00:00 + -j ff:ff:ff:ff:ff:ff -y 1 +TC_CTRL_BV_10_C PASS PTS issue #13169 + bneptest -s -b -n +TC_CTRL_BV_19_C PASS bneptest -s -b -n +TC_RX_TYPE_0_BV_11_C PASS PTS issue #13171 + bneptest -s -b -n +TC_RX_C_BV_12_C PASS PTS issue #13171 + bneptest -s -b -n +TC_RX_C_S_BV_13_C PASS PTS issue #13171 + bneptest -s -b -n +TC_RX_C_S_BV_14_C PASS PTS issue #13171 + bneptest -s -b -n +TC_RX_TYPE_0_BV_15_C PASS PTS issue #13169 + bneptest -s -b -n +TC_RX_TYPE_0_BV_16_C PASS PTS issue #13171 + bneptest -s -b -n +TC_RX_TYPE_0_BV_17_C PASS PTS issue #13169 + bneptest -s -b -n +TC_RX_TYPE_0_BV_18_C PASS PTS issue #13171 + bneptest -s -b -n +TC_TX_TYPE_0_BV_20_C PASS bneptest -c -b -n + -w 0 -k -f +TC_TX_C_BV_21_C PASS bneptest -c -b -n + -w 2 -k -f +TC_TX_C_S_BV_22_C PASS bneptest -c -b -n + -w 3 -k -f +TC_TX_C_D_BV_23_C PASS bneptest -c -b -n + -w 4 -k -f diff --git a/android/pts-did.txt b/android/pts-did.txt new file mode 100644 index 0000000..f44bf8f --- /dev/null +++ b/android/pts-did.txt @@ -0,0 +1,20 @@ +PTS test results for DID + +PTS version: 6.1 +Tested: 04-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_SDI_BV_1_I PASS IUT must be discoverable +TC_SDI_BV_2_I PASS IUT must be discoverable +TC_SDI_BV_3_I PASS IUT must be discoverable +TC_SDI_BV_4_I PASS IUT must be discoverable +------------------------------------------------------------------------------- diff --git a/android/pts-dis.txt b/android/pts-dis.txt new file mode 100644 index 0000000..c7900fd --- /dev/null +++ b/android/pts-dis.txt @@ -0,0 +1,40 @@ +PTS test results for DIS + +PTS version: 6.1 +Tested: 11-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup +NONE test result is none + +NOTE: DIS testing might require some extra properties to be set. Please refer +to the README file in android folder. Section: Customization. + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_SD_BV_01_C PASS +TC_DEC_BV_01_C PASS +TC_DEC_BV_02_C PASS +TC_DEC_BV_03_C PASS +TC_DEC_BV_04_C PASS +TC_DEC_BV_05_C PASS +TC_DEC_BV_06_C PASS +TC_DEC_BV_07_C PASS +TC_DEC_BV_08_C N/A +TC_DEC_BV_09_C PASS +TC_CR_BV_01_C PASS +TC_CR_BV_02_C PASS +TC_CR_BV_03_C PASS +TC_CR_BV_04_C PASS +TC_CR_BV_05_C PASS +TC_CR_BV_06_C PASS +TC_CR_BV_07_C PASS +TC_CR_BV_08_C N/A +TC_CR_BV_09_C PASS +TC_SDP_BV_01_C PASS +------------------------------------------------------------------------------- diff --git a/android/pts-gap.txt b/android/pts-gap.txt new file mode 100644 index 0000000..fe42d86 --- /dev/null +++ b/android/pts-gap.txt @@ -0,0 +1,432 @@ +PTS test results for GAP + +PTS version: 6.1 +Tested: 11-May-2015 +Android version: 5.1 +Kernel version: 4.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_MOD_NDIS_BV_01_C PASS IUT must be non-discoverable +TC_MOD_LDIS_BV_01_C PASS btmgmt discov limited 30 +TC_MOD_LDIS_BV_02_C PASS btmgmt discov limited 30 +TC_MOD_LDIS_BV_03_C PASS btmgmt discov limited 30 +TC_MOD_GDIS_BV_01_C PASS IUT must be discoverable +TC_MOD_GDIS_BV_02_C PASS IUT must be discoverable +TC_MOD_NCON_BV_01_C PASS btmgmt connectable off +TC_MOD_CON_BV_01_C PASS btmgmt connectable on +TC_BROB_BCST_BV_01_C N/A +TC_BROB_BCST_BV_02_C N/A +TC_BROB_BCST_BV_03_C N/A +TC_BROB_OBSV_BV_01_C N/A +TC_BROB_OBSV_BV_02_C N/A +TC_BROB_OBSV_BV_03_C N/A +TC_BROB_OBSV_BV_04_C N/A +TC_BROB_OBSV_BV_05_C N/A +TC_DISC_NONM_BV_01_C PASS btmgmt connectable off + btmgmt advertising on +TC_DISC_NONM_BV_02_C PASS btmgmt connectable on + btmgmt discov off + btmgmt advertising on +TC_DISC_LIMM_BV_01_C PASS btmgmt connectable on + btmgmt discov off + + btmgmt discov limited 30 +TC_DISC_LIMM_BV_02_C PASS btmgmt connectable on + btmgmt advertising on + btmgmt discov limited 30 +TC_DISC_LIMM_BV_03_C PASS btmgmt connectable on + btmgmt discov off + + btmgmt discov limited 30 + btmgmt advertising on +TC_DISC_LIMM_BV_04_C PASS btmgmt connectable on + btmgmt discov off + btmgmt power off + btmgmt bredr off + btmgmt power on + btmgmt discov limited 30 + btmgmt advertising on +TC_DISC_GENM_BV_01_C PASS btmgmt connectable on + btmgmt discov on + btmgmt advertising on + +TC_DISC_GENM_BV_02_C PASS btmgmt connectable on + btmgmt advertising on + btmgmt discov on +TC_DISC_GENM_BV_03_C PASS btmgmt connectable on + btmgmt discov on + btmgmt advertising on + +TC_DISC_GENM_BV_04_C PASS btmgmt connectable on + btmgmt power off + btmgmt le on + btmgmt bredr off + btmgmt power on + btmgmt discov on + btmgmt advertising on +TC_DISC_LIMP_BV_01_C PASS btmgmt find -l + PTS AD flags must have bit 1 unset and bit 0 set +TC_DISC_LIMP_BV_02_C PASS btmgmt find -l + PTS AD flags must have bit 1 set and bit 0 unset +TC_DISC_LIMP_BV_03_C PASS btmgmt find -l + PTS AD flags must have bit 1 and bit 0 unset +TC_DISC_LIMP_BV_04_C PASS btmgmt find -l + PTS AD flags must have bit 1 and bit 0 unset +TC_DISC_LIMP_BV_05_C PASS btmgmt find -l + PTS AD flags must have bit 1 and bit 0 unset +TC_DISC_GENP_BV_01_C PASS btmgmt find -l + PTS AD flags must have bit 1 set and bit 0 unset +TC_DISC_GENP_BV_02_C PASS btmgmt find -l + PTS AD flags must have bit 1 unset and bit 0 set +TC_DISC_GENP_BV_03_C PASS btmgmt find -l + PTS AD flags must have bit 1 and bit 0 unset +TC_DISC_GENP_BV_04_C PASS btmgmt find -l + PTS AD flags must have bit 1 and bit 0 unset +TC_DISC_GENP_BV_05_C PASS btmgmt find -l + PTS AD flags must have bit 1 and bit 0 unset +TC_IDLE_GIN_BV_01_C PASS Start discovery from IUT +TC_IDLE_LIN_BV_01_C PASS hcitool scan --iac=liac +TC_IDLE_NAMP_BV_01_C PASS haltest: gattc register_client + gattc listen 1 + gattc search_service 1 1800 + gattc get_characteristic 1 {1800,0,1} + gattc read_characteristic 1 {1800,0,1} {2a00,1} +TC_IDLE_NAMP_BV_02_C PASS btmgmt advertising on +TC_CONN_NCON_BV_01_C PASS btmgmt connectable off + btmgmt advertising on + +TC_CONN_NCON_BV_02_C PASS + Note: non-connectable and discoverable ? +TC_CONN_NCON_BV_03_C PASS + Note: non-connectable and discoverable ? +TC_CONN_DCON_BV_01_C PASS btmgmt connectable on + btmgmt advertising on +TC_CONN_DCON_BV_02_C N/A +TC_CONN_DCON_BV_03_C N/A +TC_CONN_UCON_BV_01_C PASS btmgmt connectable on + btmgmt advertising on +TC_CONN_UCON_BV_02_C PASS btmgmt connectable on + btmgmt discov on + btmgmt advertising on +TC_CONN_UCON_BV_03_C PASS btmgmt connectable on + btmgmt advertising on + btmgmt discov limited 30 +TC_CONN_UCON_BV_04_C N/A +TC_CONN_UCON_BV_05_C N/A +TC_CONN_ACEP_BV_01_C PASS 'gattc connect' prior to pressing OK on PTS +TC_CONN_ACEP_BV_02_C N/A +TC_CONN_GCEP_BV_01_C PASS 'gattc connect' prior to pressing OK on PTS +TC_CONN_GCEP_BV_02_C PASS 'gattc connect' prior to pressing OK on PTS +TC_CONN_GCEP_BV_03_C N/A +TC_CONN_GCEP_BV_04_C N/A +TC_CONN_SCEP_BV_01_C PASS 'gattc connect' prior to pressing OK on PTS +TC_CONN_SCEP_BV_02_C N/A +TC_CONN_DCEP_BV_01_C PASS 'gattc connect' prior to pressing OK on PTS +TC_CONN_DCEP_BV_02_C N/A +TC_CONN_DCEP_BV_03_C PASS gattc connect +TC_CONN_DCEP_BV_04_C N/A +TC_CONN_CPUP_BV_01_C PASS btmgmt advertising on +TC_CONN_CPUP_BV_02_C PASS btmgmt advertising on +TC_CONN_CPUP_BV_03_C PASS btmgmt advertising on +TC_CONN_CPUP_BV_04_C PASS gattc register_client + gattc connect + gattc disconnect +TC_CONN_CPUP_BV_05_C PASS gattc register_client + gattc connect + gattc disconnect +TC_CONN_CPUP_BV_06_C PASS gattc register_client + gattc connect 1 + hcitool lecup 0x00C8 0x0960 0x0007 + 0x0960 + gattc disconnect + +TC_CONN_TERM_BV_01_C PASS gattc register_client + gattc listen + gattc disconnect +TC_CONN_PRDA_BV_01_C PASS gattc register_client + gattc listen + gattc disconnect +TC_CONN_PRDA_BV_02_C PASS PTS issue #12950 + gattc register_client + gattc connect + bluetooth create_bond + gattc connect + gattc test_command 226 0 2 +TC_BOND_NBON_BV_01_C PASS haltest: + gattc register_client + gattc connect + gatt disconnect + gattc connect + gatt disconnect +TC_BOND_NBON_BV_02_C PASS haltest: gattc register_client + gattc connect
+ bluetooth create_bond
+ gattc connect
+ bluetooth create_bond
+TC_BOND_NBON_BV_03_C PASS haltest: gattc listen +TC_BOND_BON_BV_01_C PASS PTS issue #12503 + haltest: + bluetooth set_adapter_property + BT_PROPERTY_ADAPTER_SCAN_MODE + BT_SCAN_MODE_CONNECTABLE + gattc register_client + gattc listen 1 + bluetooth create_bond +TC_BOND_BON_BV_02_C PASS gattc regicter_client + gattc scan + gattc connect + bluetooth create_bond + gattc connect + gattc test_command 226 1 +TC_BOND_BON_BV_03_C PASS gattc register_client + gattc listen 1 +TC_BOND_BON_BV_04_C PASS haltest: gattc_register_client + gattc connect
+ gattc disconnect + gattc connect
+ gattc test_command 226 0 2 +TC_SEC_AUT_BV_11_C PASS haltest: gattc register_client + gatts register_server + gatts add_service 2 3 + gatts add_characteristic 2 1b 10 68 + gatts start_service 2 1b 1 + gattc listen 1 + PTS asks for handle with Insufficient auth + gatts send_response 1 1 0 1d 0 0x1234 +TC_SEC_AUT_BV_12_C PASS haltest: gatts register_server + gatts add_service 1 3 + gatts add_characteristic 1 1b 10 68 + gatts start_service 1 1b 1 + gatts connect 1 + PTS asks for handle with Insufficient auth + gatts send_response 1 1 0 1d 0 0x1234 +TC_SEC_AUT_BV_13_C PASS haltest: gatts register_server + gatts add_service 1 3 + gatts add_characteristic 1 1b 10 68 + gatts start_service 1 1b 1 + gatts connect 1 + PTS asks for handle with Insufficient auth + gatts send_response 1 1 0 1d 0 0x1234 +TC_SEC_AUT_BV_14_C PASS haltest: gattc register_client + gatts register_server + gatts add_service 2 3 + gatts add_characteristic 2 1b 10 68 + gatts start_service 2 1b 1 + gattc listen 1 + PTS asks for handle with Insufficient auth + gatts send_response 1 1 0 1d 0 0x1234 +TC_SEC_AUT_BV_15_C N/A +TC_SEC_AUT_BV_16_C N/A +TC_SEC_AUT_BV_17_C PASS haltest: gattc register_client + gattc connect + gattc search_service + gattc get_characteristic + gattc read_characteristic + bluetooth create_bond +TC_SEC_AUT_BV_18_C PASS haltest: gattc register_client + gattc listen + gattc search_service + gattc get_characteristic + gattc read_characteristic + bluetooth create_bond + gattc read_characteristic +TC_SEC_AUT_BV_19_C PASS +TC_SEC_AUT_BV_20_C PASS haltest: gattc register_client + gattc listen 1 1 + gattc search_service 2 + gattc get_characteristic 2 {1801,1,1} + gattc read_characteristic 2 {1801,1,1} {2a05,1} + gattc read_characteristic 2 {1801,1,1} {2a05,1} + 1 +TC_SEC_AUT_BV_21_C PASS haltest: gattc register_client + gattc connect + bluetooth create_bond + gattc connect + gattc test_command 226 0 1 +TC_SEC_AUT_BV_22_C PASS btmgmt io-cap 3 + haltest: gattc register_client + gattc listen + gattc test_command 226 1 +TC_SEC_AUT_BV_23_C PASS haltest: gattc register_client + gatts register_server + gatts add_service 2 3 + gatts add_characteristic 2 1b 10 34 + gatts start_service 2 1b 1 + gattc listen 1 + PTS asks for handle with insufficient encryption + gatts send_response 3 1 0 1d 0 0x1234 +TC_SEC_AUT_BV_24_C PASS haltest: gatts register_server + gatts add_service 1 3 + gatts add_characteristic 1 1d 10 34 + gatts start_service 1 1d 1 + gatts connect + gatts disconnect + gatts connect + PTS asks for handle with insufficient encryption + gatts send_response 2 1 0 1f 0 0x1234 +TC_SEC_CSIGN_BV_01_C PASS haltest: + gattc connect + bluetooth create_bond + gattc connect + gattc write_characteristic: 4 + gattc disconnect +TC_SEC_CSIGN_BV_02_C PASS haltest: gattc register_client + gatts register_server + gatts add_service 2 3 + gatts add_characteristic 2 1d 66 129 + gatts start_service 2 1d 1 + gattc listen 1 + gatts disconnect +TC_SEC_CSIGN_BI_01_C PASS gattc register_client + gatts register_server + gatts add_service 2 3 + gatts add_characteristic 2 1d 66 129 + gatts start_service 2 1d 1 + gattc listen 1 + gatts disconnect + gattc disconnect +TC_SEC_CSIGN_BI_02_C PASS gattc register_client + gatts register_server + gatts add_service 2 3 + gatts add_characteristic 2 1b 66 129 + gatts start_service 2 1b 1 + gattc listen 1 + gatts disconnect + gattc disconnect +TC_SEC_CSIGN_BI_03_C PASS gattc register_client + gatts register_server + gatts add_service 2 3 + gatts add_characteristic 2 1b 66 129 + gatts start_service 2 1b 1 + gattc listen 1 + gatts disconnect + gattc disconnect + bluetooth remove_bond +TC_SEC_CSIGN_BI_04_C PASS gattc register_client + gatts register_server + gatts add_service 2 3 + gatts add_characteristic 2 1b 64 256 + gatts start_service 2 1b 1 + gattc listen 1 + gatts disconnect + gattc disconnect +TC_PRIV_CONN_BV_01_C N/A +TC_PRIV_CONN_BV_02_C N/A +TC_PRIV_CONN_BV_03_C N/A +TC_PRIV_CONN_BV_04_C N/A +TC_PRIV_CONN_BV_05_C N/A +TC_PRIV_CONN_BV_06_C N/A +TC_PRIV_CONN_BV_07_C N/A +TC_PRIV_CONN_BV_08_C N/A +TC_PRIV_CONN_BV_09_C N/A +TC_PRIV_CONN_BV_10_C PASS PTS issue #12951 + Note: PIXITs required to be changed: + TSPX_using_public_device_address: FALSE + TSPX_using_random_device_address: TRUE + echo 30 > /sys/kernel/debug/bluetooth/hci0/ + rpa_timeout + btmgmt power off + btmgmt privacy on + btmgmt power on +TC_PRIV_CONN_BV_11_C INC PTS issue #12952 + JIRA #BA-186 +TC_ADV_BV_01_C N/A +TC_ADV_BV_02_C PASS gattc register_client + gattc listen 1 1 +TC_ADV_BV_03_C PASS gattc register_client + gattc listen 1 1 +TC_ADV_BV_04_C N/A +TC_ADV_BV_05_C PASS gattc register_client + gattc listen 1 1 +TC_ADV_BV_06_C N/A +TC_ADV_BV_07_C N/A +TC_ADV_BV_08_C N/A +TC_ADV_BV_09_C N/A +TC_ADV_BV_10_C N/A +TC_ADV_BV_11_C N/A +TC_ADV_BV_12_C N/A +TC_ADV_BV_13_C N/A +TC_ADV_BV_14_C N/A +TC_ADV_BV_15_C N/A +TC_ADV_BV_16_C N/A +TC_GAT_BV_01_C PASS haltest: + gattc register_client + gattc listen +TC_GAT_BV_02_C N/A +TC_GAT_BV_03_C N/A +TC_GAT_BV_04_C N/A +TC_GAT_BV_05_C N/A +TC_GAT_BV_06_C N/A +TC_GAT_BV_07_C N/A +TC_GAT_BV_08_C N/A +TC_DM_NCON_BV_01_C PASS bluetooth set_adapter_property + BT_PROPERTY_ADAPTER_SCAN_MODE + BT_SCAN_MODE_NONE + gattc register_client + gattc listen 1 +TC_DM_CON_BV_01_C PASS bluetooth set_adapter_property + BT_PROPERTY_ADAPTER_SCAN_MODE + BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE + gattc register_client + gattc listen 1 +TC_DM_NBON_BV_01_C PASS btmgmt pairable off + btmgmt pair -c 0x04 -t 0x01 +TC_DM_BON_BV_01_C PASS btmgmt pairable on + btmgmt pair -c 0x04 -t 0x01 +TC_DM_GIN_BV_01_C PASS +TC_DM_LIN_BV_01_C PASS +TC_DM_NAD_BV_01_C PASS btmgmt find +TC_DM_NAD_BV_02_C PASS +TC_DM_LEP_BV_01_C PASS bluetooth set_adapter_property + BT_PROPERTY_ADAPTER_SCAN_MODE + BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE + gattc register_client + gattc listen 1 1 +TC_DM_LEP_BV_02_C PASS Use basic rate PTS dongle + haltest: + bluetooth set_adapter_property +TC_DM_LEP_BV_04_C PASS haltest: + gattc connect +TC_DM_LEP_BV_05_C PASS Use basic rate PTS dongle + btmgmt find -b + l2test -n +TC_DM_LEP_BV_06_C PASS gattc connect +TC_DM_LEP_BV_07_C PASS bluetooth set_adapter_property + BT_PROPERTY_ADAPTER_SCAN_MODE + BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE + gattc register_client + gattc listen 1 1 +TC_DM_LEP_BV_08_C PASS bluetooth set_adapter_property + BT_PROPERTY_ADAPTER_SCAN_MODE + BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE + gattc register_client + gattc listen 1 1 +TC_DM_LEP_BV_09_C PASS haltest: + bluetooth enable + bluetooth set_adapter_property + BT_PROPERTY_ADAPTER_SCAN_MODE + BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE + gattc register_client + gattc scan 1 + gattc connect + l2test -n -P 31 + disconnect +TC_DM_LEP_BV_10_C PASS btmgmt find + l2test -n -P 31 +TC_DM_LEP_BV_11_C PASS haltest: + bluetooth enable + bluetooth set_adapter_property + BT_PROPERTY_ADAPTER_SCAN_MODE + BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE + gattc register_client + gattc connect + gattc disconnect +------------------------------------------------------------------------------- diff --git a/android/pts-gatt.txt b/android/pts-gatt.txt new file mode 100644 index 0000000..3531cca --- /dev/null +++ b/android/pts-gatt.txt @@ -0,0 +1,1422 @@ +PTS test results for GATT + +PTS version: 6.1 +Tested: 24-April-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_GAC_CL_BV_01_C PASS haltest: + gattc scan + gattc search_service + gattc get_characteristic + gattc write_characteristic: type 3 +TC_GAC_SR_BV_01_C PASS PTS issue #13073 + TSE #6271 + haltest: + gatts add_service + gatts add_chaaracteristic: + 10 17 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: + value greater than MTU + repeat with correct offset +TC_GAD_CL_BV_01_C PASS haltest: + NOTE: Repeat following steps if asked + gattc connect + gattc search_service + gattc disconnect + +TC_GAD_CL_BV_02_C PASS haltest: + NOTE: Repeat following steps if asked + gattc connect + gattc search_service + gattc disconnect + +TC_GAD_CL_BV_03_C PASS haltest: + NOTE: Repeat following steps if asked + gattc connect + gattc test_command 0xe0 0x2802 0x08 + 0x0001 0xffff + NOTE: Keep on mind MTU size + (some att rsp could not fit) + gattc_disconnect + +TC_GAD_CL_BV_04_C PASS haltest: + NOTE: Repeat following steps if asked + gattc connect + gattc search_service + gattc get_characteristic +TC_GAD_CL_BV_05_C PASS haltest: + NOTE: Repeat following steps if asked + gattc connect + gattc test_command 0xe0 0x2803 0x08 + + gattc disconnect + +TC_GAD_CL_BV_06_C PASS haltest: + NOTE: Repeat following steps if asked + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc disconnect + +TC_GAD_CL_BV_07_C PASS haltest: + NOTE: Repeat following step if asked + bluetooth get_remote_services +TC_GAD_CL_BV_08_C PASS haltest: + NOTE: Repear following step if asked + bluetooth get_remote_services +TC_GAD_SR_BV_01_C PASS haltest: + gattc register_client + gattc listen +TC_GAD_SR_BV_02_C PASS haltest: + gattc register_client + gattc listen +TC_GAD_SR_BV_03_C PASS haltest: + gattc register_client + gattc listen + gatts register_server + gatts add_service + gatts start_service + gatts add_service + gatts add_included_service + gatts start_service +TC_GAD_SR_BV_04_C PASS haltest: + gattc register_client + gattc listen +TC_GAD_SR_BV_05_C PASS haltest: + gattc register_client + gattc listen +TC_GAD_SR_BV_06_C PASS haltest: + gattc register_client + gattc listen +TC_GAD_SR_BV_07_C PASS haltest: + when requested: + bluetooth get_remote_services + NOTE: check if found requested service +TC_GAD_SR_BV_08_C PASS haltest: + when requested: + bluetooth get_remote_services + NOTE: check if found requested service +TC_GAR_CL_BV_01_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc read_characteristic + + gattc disconnect + +TC_GAR_CL_BI_01_C PASS haltest: + gattc connect + gattc test_command 0xe0 0x0000 + 0x0a + gattc disconnect + +TC_GAR_CL_BI_02_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc read_characteristic + + gattc disconnect + +TC_GAR_CL_BI_03_C PASS haltest: + gattc connect + gattc test_command 0xe0 0x0000 + 0x0a + gattc disconnect + +TC_GAR_CL_BI_04_C PASS haltest: + gattc connect + gattc test_command 0xe0 0x0000 + 0x0a + gattc disconnect + +TC_GAR_CL_BI_05_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc read_characteristic + + gattc disconnect + +TC_GAR_CL_BV_03_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 0x0001 0xffff + gattc disconnect + +TC_GAR_CL_BI_06_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GAR_CL_BI_07_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GAR_CL_BI_09_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GAR_CL_BI_10_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GAR_CL_BI_11_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GAR_CL_BV_04_C PASS haltest: + gattc connect + gattc search_service + NOTE: Repeat following steps if asked + gattc get_characteristic + gattc read_characteristic + + NOTE: After reading all characteristics + gattc disconnect + +TC_GAR_CL_BI_12_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc read_characteristic + + gattc disconnect + +TC_GAR_CL_BI_13_C PASS haltest: + gattc connect + gattc test_command 0xe0 0x0000 + 0x0c + gattc disconnect + +TC_GAR_CL_BI_14_C PASS haltest: + gattc connect + gattc test_command 0xe0 0x0000 + 0x0a + gattc disconnect + +TC_GAR_CL_BI_15_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc read_characteristic + + gattc disconnect + +TC_GAR_CL_BI_16_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc read_characteristic + + gattc disconnect + +TC_GAR_CL_BI_17_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc read_characteristic + + gattc disconnect + +TC_GAR_CL_BV_05_C N/A +TC_GAR_CL_BI_18_C N/A +TC_GAR_CL_BI_19_C N/A +TC_GAR_CL_BI_20_C N/A +TC_GAR_CL_BI_21_C N/A +TC_GAR_CL_BI_22_C N/A +TC_GAR_CL_BV_06_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc read_descriptor + + gattc disconnect + +TC_GAR_CL_BI_23_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc read_descriptor + + gattc disconnect + +TC_GAR_CL_BI_24_C PASS haltest: + gattc connect + gattc test_command 0xe0 0x0000 + 0x0a + gattc disconnect + +TC_GAR_CL_BI_25_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc read_descriptor + + gattc disconnect + +TC_GAR_CL_BI_26_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc read_descriptor + + gattc disconnect + +TC_GAR_CL_BI_27_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc read_descriptor + + gattc disconnect + +TC_GAR_CL_BV_07_C PASS haltest: + gattc connect + gattc search_service + NOTE: Repeat following step if asked + gattc get_characteristic + gattc get_descriptor + + gattc read_descriptor + + NOTE: After reading all characteristics + gattc disconnect + +TC_GAR_CL_BI_28_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc read_descriptor + + gattc disconnect + +TC_GAR_CL_BI_29_C PASS haltest: + gattc connect + gattc test_command 0xe0 0x0000 + 0x0c + gattc disconnect + +TC_GAR_CL_BI_30_C PASS haltest: + gattc connect + gattc test_command 0xe0 0x0000 + 0x0a + gattc disconnect + +TC_GAR_CL_BI_31_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc read_descriptor + + gattc disconnect + +TC_GAR_CL_BI_32_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc read_descriptor + + gattc disconnect + +TC_GAR_CL_BI_33_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc read_descriptor + + gattc disconnect + +TC_GAR_CL_BI_34_C PASS haltest: + gattc connect + gattc test_command 224 0 0x0a + gattc disconnect +TC_GAR_CL_BI_35_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc read_characteristic + + gattc disconnect + +TC_GAR_SR_BV_01_C PASS +TC_GAR_SR_BI_01_C PASS +TC_GAR_SR_BI_02_C PASS +TC_GAR_SR_BI_03_C PASS haltest: + gatts add_service + gatts add_characteristic: + 2 1 + gatts start_service + gatts send_response: 8 +TC_GAR_SR_BI_04_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 3 + gatts start_service + gatts send_response +TC_GAR_SR_BI_05_C PASS haltest: + gatts add_service + gatts add_characteristic: + 2 1 + gatts start_service + gatts send_response: 12 +TC_GAR_SR_BV_03_C PASS +TC_GAR_SR_BI_06_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 16 + gatts start_service +TC_GAR_SR_BI_07_C PASS +TC_GAR_SR_BI_08_C PASS +TC_GAR_SR_BI_09_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts start_service + gatts send_response: 8 +TC_GAR_SR_BI_10_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts start_service + gatts send_response: 5 +TC_GAR_SR_BI_11_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts start_service + gatts send_response: 12 +TC_GAR_SR_BV_04_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset +TC_GAR_SR_BI_12_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 8 16 + gatts start_service + gatts send_response +TC_GAR_SR_BI_13_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: 7 +TC_GAR_SR_BI_14_C PASS haltest: + gatts add_service + gatts add_characteristic: + 2 1 + gatts start_service + gatts send_response: 1 +TC_GAR_SR_BI_15_C PASS haltest: + gatts add_service + gatts add_characteristic: + 2 1 + gatts start_service + gatts send_response: 8 +TC_GAR_SR_BI_16_C PASS haltest: + gatts add_service + gatts add_characteristic: + 2 1 + gatts start_service + gatts send_response: 5 +TC_GAR_SR_BI_17_C PASS haltest: + gatts add_service + gatts add_characteristic: + 2 1 + gatts start_service + gatts send_response: 12 +TC_GAR_SR_BV_05_C N/A +TC_GAR_SR_BI_18_C N/A +TC_GAR_SR_BI_19_C N/A +TC_GAR_SR_BI_20_C N/A +TC_GAR_SR_BI_21_C N/A +TC_GAR_SR_BI_22_C N/A +TC_GAR_SR_BV_06_C PASS haltest: + gatts add_service + gatts add_characteristic: + 2 1 + gatts add_descriptor + gatts start_service + gatts send_response +TC_GAR_SR_BI_23_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 16 + gatts start_service +TC_GAR_SR_BI_24_C PASS haltest: + gatts add_service + gatts add_characteristic: + 2 1 + gatts add_descriptor + gatts start_service + gatts send_response: 1 +TC_GAR_SR_BI_25_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: 8 +TC_GAR_SR_BI_26_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: 5 +TC_GAR_SR_BI_27_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: 12 +TC_GAR_SR_BV_07_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset +TC_GAR_SR_BV_08_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset +TC_GAR_SR_BI_28_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 16 + gatts start_service +TC_GAR_SR_BI_29_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: 7 +TC_GAR_SR_BI_30_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: 1 +TC_GAR_SR_BI_31_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: 8 +TC_GAR_SR_BI_32_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: 5 +TC_GAR_SR_BI_33_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: 12 +TC_GAR_SR_BI_34_C PASS haltest: + gatts add_service + gatts add_characteristic + gatts start_service + gatts send_response 0x80-0x9F +TC_GAR_SR_BI_35_C PASS haltest: + gatts add_service + gatts add_characteristic + gatts start_service + gatts send_response 0x80-0x9F +TC_GAW_CL_BV_01_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 1 + gattc disconnect + +TC_GAW_CL_BV_02_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 4 + gattc disconnect + +TC_GAW_CL_BV_03_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 2 + gattc disconnect + +TC_GAW_CL_BI_02_C PASS haltest: + gattc connect + gattc test_command 0xe1 0x0000 0x12 + + gattc disconnect + +TC_GAW_CL_BI_03_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 2 + gattc disconnect + +TC_GAW_CL_BI_04_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 2 + gattc disconnect + +TC_GAW_CL_BI_05_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 2 + gattc disconnect + +TC_GAW_CL_BI_06_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 2 + gattc disconnect + +TC_GAW_CL_BV_05_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 3 + gattc execute_write 1 + gattc disconnect + +TC_GAW_CL_BI_07_C PASS haltest: + gattc connect + gattc test_command 0xe1 0x0000 0x12 + + gattc disconnect + +TC_GAW_CL_BI_08_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 3 + gattc execute_write 1 + gattc disconnect + +TC_GAW_CL_BI_09_C PASS haltest: + gattc connect + gattc test_command 0xe1 0x0000 0x16 + + gattc test_command 0xe1 0x0000 0x18 1 + gattc disconnect + +TC_GAW_CL_BI_11_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 3 + gattc disconnect + +TC_GAW_CL_BI_12_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 3 + gattc disconnect + +TC_GAW_CL_BI_13_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 3 + gattc disconnect + +TC_GAW_CL_BV_06_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 3 + gattc execute_write 1 + gattc disconnect + +TC_GAW_CL_BI_14_C PASS haltest: + gattc connect + gattc test_command 0xe1 0x0000 0x16 + + gattc disconnect + +TC_GAW_CL_BI_15_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 3 + gattc disconnect + +TC_GAW_CL_BI_17_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 3 + gattc disconnect + +TC_GAW_CL_BI_18_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 3 + gattc disconnect + +TC_GAW_CL_BI_19_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 3 + gattc disconnect + +TC_GAW_CL_BV_08_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 2 + gattc disconnect + +TC_GAW_CL_BI_20_C PASS haltest: + gattc connect + gattc test_command 0xe1 0x0000 0x12 + + gattc disconnect + +TC_GAW_CL_BI_21_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 2 + gattc disconnect + +TC_GAW_CL_BI_22_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 2 + gattc disconnect + +TC_GAW_CL_BI_23_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 2 + gattc disconnect + +TC_GAW_CL_BI_24_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 2 + gattc disconnect + +TC_GAW_CL_BV_09_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 3 + gattc execute_write 1 + gattc disconnect + +TC_GAW_CL_BI_25_C PASS haltest: + gattc connect + gattc test_command 0xe1 0x0000 0x16 + + gattc disconnect + +TC_GAW_CL_BI_26_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 3 + gattc disconnect + +TC_GAW_CL_BI_27_C PASS haltest: + gattc connect + gattc test_command 0xe1 0x0000 0x16 + + gattc test_command 0xe1 0x0000 0x18 1 + gattc disconnect + +TC_GAW_CL_BI_29_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 3 + gattc disconnect + +TC_GAW_CL_BI_30_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 3 + gattc disconnect + +TC_GAW_CL_BI_31_C PASS haltest: + gattc connect + gattc test_command 0xe1 0x0000 0x16 + 0x0000 + gattc disconnect + +TC_GAW_CL_BI_32_C PASS haltest: + gattc connect + gattc test_command 0xe1 0x0000 0x16 + + gattc test_command 0xe1 0x0000 0x18 0 + gattc disconnect + +TC_GAW_CL_BI_33_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 2 + gattc disconnect + +TC_GAW_CL_BI_34_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 2 + gattc disconnect + +TC_GAW_CL_BI_35_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 2 + gattc disconnect + +TC_GAW_CL_BI_36_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 2 + gattc disconnect + +TC_GAW_SR_BV_01_C PASS haltest: + gatts add_service + gatts add_characteristic: + 4 17 + gatts start_service +TC_GAW_SR_BV_02_C PASS haltest: + gatts add service + gatts add_characteristics: + 66 145 + gatts start_service + gattc listen + gatts send_response: (twice) + NOTE: gatts_request_write_cb shall be called + (verify it) +TC_GAW_SR_BI_01_C PASS haltest: + gatts add_service + gatts add_characteristic: + 68 + 129 + gatts start_service + gatts send_response: repeat with 1 +TC_GAW_SR_BV_03_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 +TC_GAW_SR_BI_02_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: 1 +TC_GAW_SR_BI_03_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 1 + gatts start_service +TC_GAW_SR_BI_04_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: 8 +TC_GAW_SR_BI_05_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: 5 +TC_GAW_SR_BI_06_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: 12 +TC_GAW_SR_BV_05_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: + repeat with correct value +TC_GAW_SR_BI_07_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response +TC_GAW_SR_BI_08_C PASS haltest: + gatts add_service + gatts add_characteristic: + 2 1 + gatts start_service +TC_GAW_SR_BI_09_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: 7 +TC_GAW_SR_BI_11_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: 8 +TC_GAW_SR_BI_12_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: 5 +TC_GAW_SR_BI_13_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: 12 +TC_GAW_SR_BV_06_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: + repeat with correct value +TC_GAW_SR_BV_10_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: + repeat with correct value +TC_GAW_SR_BI_14_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: 1 +TC_GAW_SR_BI_15_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: 3 +TC_GAW_SR_BI_17_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: 8 +TC_GAW_SR_BI_18_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: 5 +TC_GAW_SR_BI_19_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: 12 +TC_GAW_SR_BV_07_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: + repeat with correct value +TC_GAW_CL_BV_08_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response +TC_GAW_SR_BI_20_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response: 1 +TC_GAW_SR_BI_21_C PASS haltest: + gatts add_service + gatts add_characteristic: + 2 1 + gatts add_descriptor: 1 + gatts start_service +TC_GAW_SR_BI_22_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response: 8 + +TC_GAW_SR_BI_23_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response: 5 +TC_GAW_SR_BI_24_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response: 12 +TC_GAW_SR_BV_09_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: + repeat with correct value +TC_GAW_SR_BI_25_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response: 1 +TC_GAW_SR_BI_26_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 1 + gatts start_service +TC_GAW_SR_BI_27_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 1 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: 7 +TC_GAW_SR_BI_29_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response: 8 +TC_GAW_SR_BI_30_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response: 5 +TC_GAW_SR_BI_31_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response: 12 +TC_GAW_SR_BI_32_C PASS PTS issue #12823 + haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response + gatts send_response: 13 +TC_GAW_SR_BI_33_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: 13 +TC_GAW_SR_BI_34_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response + gatts send_response: 13 +TC_GAW_SR_BI_35_C PASS haltest: + gatts add_service + gatts add_characteristic: + 10 17 + gatts add_descriptor: 17 + gatts start_service + gatts send_response: + value greater than MTU + repeat with correct offset + gatts send_response: 13 +TC_GAN_CL_BV_01_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 2 0x0100 + gattc disconnect + +TC_GAN_SR_BV_01_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 26 17 + gatts add_descriptor: 2902 + 11 + gatts start_service + gatts send_response + gatts send_response + gatts send_indication: + char value handle + 0 +TC_GAI_CL_BV_01_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc get_descriptor + + gattc write_descriptor + 2 0x0200 + gattc disconnect + +TC_GAI_SR_BV_01_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 42 17 + gatts add_descriptor: 17 + gatts start_service + gatts add_service + gatts start_service +TC_GAS_CL_BV_01_C PASS haltest: + gattc connect + gattc disconnect + +TC_GAS_SR_BV_01_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 42 17 + gatts add_descriptor: 17 + gatts start_service + gatts add_service + gatts start_service +TC_GAT_CL_BV_01_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc read_characteristic + + wait for 30 sec timeout +TC_GAT_CL_BV_02_C PASS haltest: + gattc connect + gattc search_service + gattc get_characteristic + gattc write_characteristic + 2 + wait for 30 sec timeout +TC_GAT_SR_BV_01_C PASS haltest: + gatts add_service + gatts add_characteristic: + 42 17 + gatts add_descriptor: 17 + gatts start_service + gatts add_service + gatts start_service +TC_GPA_CL_BV_01_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GPA_CL_BV_02_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GPA_CL_BV_03_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GPA_CL_BV_04_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GPA_CL_BV_05_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GPA_CL_BV_06_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc connect + gattc search_service + gattc get_characteristic + gattc read_descriptor + + gattc disconnect + +TC_GPA_CL_BV_07_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GPA_CL_BV_08_C PASS haltest: + gattc connect + gattc test_command 0xe0 + 0x08 + gattc disconnect + +TC_GPA_CL_BV_11_C PASS haltest: + gattc connect + Repeat following steps 5 times: + 1.Find Characteristic Aggregate Format + gattc test_command 224 [u1] 8 + 2.Read aggregate descriptor + gattc test_command 224 [u1] 10 + 3.Read 3 handles from aggregate descriptor + value + gattc test_command 224 [u1] 10 + 4.Compare descriptors values + gattc disconnect + +TC_GPA_CL_BV_12_C PASS haltest: + gattc connect + Repeat following steps 5 times: + 1.Find Characteristic Presentation Format + gattc test_command 224 [u1] 8 + 2.Find characteristic in this range + gattc test_command 224 2803 [u1] 8 + 3.Read characteristic declaration + gattc test_command 224 [u1] 10 + 4.Read characteristic value + gattc test_command 224 [u1] 10 + 5.Compare characteristic value and + presentation format + gattc disconnect +TC_GPA_SR_BV_01_C PASS +TC_GPA_SR_BV_02_C PASS haltest: + gatts add_service + gatts start_service +TC_GPA_SR_BV_03_C PASS haltest: + gatts add_service + gatts add_service + add_included_service + gatts start_service + gatts start_service +TC_GPA_SR_BV_04_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 10 17 + gatts start_service +TC_GPA_SR_BV_05_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 138 17 + gatts add_descriptor 2900 + gatts start_service +TC_GPA_SR_BV_06_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 138 17 + gatts add_descriptor 2901 + gatts start_service +TC_GPA_SR_BV_07_C PASS +TC_GPA_SR_BV_08_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 138 17 + gatts add_descriptor 2903 + gatts start_service + gatts send_response +TC_GPA_SR_BV_11_C INC PTS issue #13392 + haltest: + gatts add_service + gatts add_chaaracteristic: + 138 17 + gatts add_descriptor 2905 + gatts start_service + gatts send_response: repeat with correct offset + and data +TC_GPA_SR_BV_12_C PASS haltest: + gatts add_service + gatts add_chaaracteristic: + 10 17 + gatts add_descriptor 2904 + gatts start_service + gatts send_response: repeat with correct data +------------------------------------------------------------------------------- diff --git a/android/pts-gavdp.txt b/android/pts-gavdp.txt new file mode 100644 index 0000000..30b59ea --- /dev/null +++ b/android/pts-gavdp.txt @@ -0,0 +1,23 @@ +PTS test results for GAVDP + +PTS version: 6.1 +Tested: 08-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup +NONE test result is none + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_ACP_APP_CON_BV_01_C PASS +TC_ACP_APP_TRC_BV_01_C N/A +TC_ACP_APP_TRC_BV_02_C PASS +TC_INT_APP_CON_BV_01_C PASS +TC_INT_APP_TRC_BV_01_C N/A +TC_INT_APP_TRC_BV_02_C PASS +------------------------------------------------------------------------------- diff --git a/android/pts-hdp.txt b/android/pts-hdp.txt new file mode 100644 index 0000000..cbe1b3a --- /dev/null +++ b/android/pts-hdp.txt @@ -0,0 +1,296 @@ +PTS test results for HDP + +PTS version: 6.1 +Tested: 08-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_SRC_CON_BV_01_I PASS haltest: + hl register_application + for instance: + hl register_application health intel heartrate + heartrate-monitor 1 BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE testing + + when prompted: + bluetooth ssp_reply + for instance: + bluetooth ssp_reply + BT_SSP_VARIANT_CONSENT 1 + Note: IUT must be discoverable, connectable +TC_SRC_CON_BV_02_I PASS Note: IUT must be in discoverable mode +TC_SRC_CON_BV_03_I PASS when prompted: bluetooth ssp_reply +TC_SRC_CON_BV_04_I PASS haltest: + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SRC_CON_BV_05_I PASS when prompted: bluetooth ssp_reply + Note: IUT must be in connectable mode +TC_SRC_CON_BV_06_I PASS haltest: + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SRC_CON_BV_07_I PASS bluetooth start_discovery + Note: PTS HDP device must be discovered +TC_SRC_CON_BV_08_I PASS bluetooth remove_bond + when prompted: bluetooth ssp_reply +TC_SRC_CON_BV_09_I PASS haltest: + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SRC_CON_BV_10_I N/A +TC_SRC_CC_BV_01_C PASS haltest: + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SRC_CC_BV_02_C PASS when prompted: bluetooth ssp_reply + Note: IUT must be discoverable, connectable +TC_SRC_CC_BV_03_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SRC_CC_BV_05_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + Note: IUT must be discoverable, connectable +TC_SRC_CC_BV_07_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 2 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_STREAMING pulse-oximeter + + hl connect_channel + + + when prompted: bluetooth ssp_reply + + when prompted: + hl connect_channel + +TC_SRC_CC_BV_09_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 2 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_STREAMING pulse-oximeter + + when prompted: bluetooth ssp_reply + Note: IUT must be discoverable, connectable +TC_SRC_CC_BI_12_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + Note: IUT must be discoverable, connectable +TC_SRC_HCT_BV_01_I PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SRC_HCT_BV_02_I PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + Note: IUT must be discoverable, connectable +TC_SRC_HCT_BV_03_I N/A +TC_SRC_HCT_BV_04_I PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + + when prompted: bluetooth ssp_reply + Note: IUT must be discoverable, connectable +TC_SRC_HCT_BV_05_C N/A +TC_SRC_HCT_BV_06_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + + when prompted: bluetooth ssp_reply + Note: IUT must be discoverable, connectable +TC_SRC_HCT_BV_07_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter +TC_SRC_DE_BV_01_I N/A +TC_SRC_DE_BV_02_I PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SOURCE 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + Note: IUT must be discoverable, connectable +TC_SRC_DEP_BV_01_I N/A +TC_SRC_DEP_BV_02_I N/A +TC_SNK_CON_BV_01_I PASS haltest: + hl register_application + for instance: + hl register_application health intel heartrate + heartrate-monitor 1 BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_RELIABLE testing + + when prompted: + bluetooth ssp_reply + for instance: + bluetooth ssp_reply + BT_SSP_VARIANT_CONSENT 1 + Note: IUT must be discoverable, connectable +TC_SNK_CON_BV_02_I PASS Note: IUT must be discoverable, connectable +TC_SNK_CON_BV_03_I PASS when prompted: bluetooth ssp_reply + Note: IUT must be discoverable, connectable +TC_SNK_CON_BV_04_I PASS haltest: + hl connect_channel + + + when prompted: bluetooth ssp_reply + Note: IUT must be discoverable, connectable +TC_SNK_CON_BV_05_I PASS when prompted: bluetooth ssp_reply +TC_SNK_CON_BV_06_I PASS haltest: + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SNK_CON_BV_07_I PASS bluetooth start_discovery +TC_SNK_CON_BV_08_I PASS bluetooth remove_bond + + when prompted: bluetooth ssp_reply + Note: IUT must be discoverable, connectable +TC_SNK_CON_BV_09_I PASS haltest: + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SNK_CON_BV_10_I N/A +TC_SNK_CC_BV_01_C PASS haltest: + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SNK_CC_BV_02_C PASS when prompted: bluetooth ssp_reply + Note: IUT must be discoverable, connectable +TC_SNK_CC_BV_04_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SNK_CC_BV_06_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 2 + BTHL_MDEP_ROLE_SINK 4100 BTHL_CHANNEL_TYPE_RELIABLE + pulse-oximeter + BTHL_MDEP_ROLE_SINK 4100 BTHL_CHANNEL_TYPE_STREAMING + pulse-oximeter +TC_SNK_CC_BV_08_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 2 + BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_STREAMING pulse-oximeter + + hl connect_channel + + + when prompted: bluetooth ssp_reply + + when prompted: + hl connect_channel + +TC_SNK_CC_BV_10_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 2 + BTHL_MDEP_ROLE_SINK 4100 BTHL_CHANNEL_TYPE_RELIABLE + pulse-oximeter + BTHL_MDEP_ROLE_SINK 4100 BTHL_CHANNEL_TYPE_STREAMING + pulse-oximeter + Note: IUT must be discoverable, connectable +TC_SNK_CC_BI_11_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + Note: IUT must be discoverable, connectable +TC_SNK_HCT_BV_01_I PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + + hl connect_channel + + + when prompted: bluetooth ssp_reply +TC_SNK_HCT_BV_02_I PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + Note: IUT must be discoverable, connectable +TC_SNK_HCT_BV_03_I N/A +TC_SNK_HCT_BV_04_I PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + + when prompted: bluetooth ssp_reply + Note: IUT must be discoverable, connectable +TC_SNK_HCT_BV_05_C N/A +TC_SNK_HCT_BV_06_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + Note: IUT must be discoverable, connectable +TC_SNK_HCT_BV_07_C PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + Note: IUT must be discoverable, connectable +TC_SNK_DE_BV_01_I N/A +TC_SNK_DE_BV_02_I PASS haltest: + hl register_application bluez-android Bluez + bluez-hdp health-device-profile 1 + BTHL_MDEP_ROLE_SINK 4100 + BTHL_CHANNEL_TYPE_RELIABLE pulse-oximeter + Note: IUT must be discoverable, connectable +TC_SNK_DEP_BV_03_I N/A +TC_SNK_DEP_BV_04_I N/A +------------------------------------------------------------------------------- diff --git a/android/pts-hfp.txt b/android/pts-hfp.txt new file mode 100644 index 0000000..05fccd8 --- /dev/null +++ b/android/pts-hfp.txt @@ -0,0 +1,250 @@ +PTS test results for HFP + +PTS version: 6.1 +Tested: 14-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_AG_OOR_BV_01_I PASS +TC_AG_OOR_BV_02_I PASS +TC_AG_TRS_BV_01_I PASS +TC_AG_PSI_BV_01_I PASS +TC_AG_PSI_BV_02_I N/A +TC_AG_PSI_BV_03_I PASS +TC_AG_PSI_BV_04_I PASS +TC_AG_PSI_BV_05_I PASS +TC_AG_ACS_BV_02_I N/A +TC_AG_ACS_BV_04_I PASS +TC_AG_ACS_BV_06_I N/A +TC_AG_ACS_BV_08_I PASS +TC_AG_ACS_BV_10_I N/A +TC_AG_ACS_BV_11_I PASS +TC_AG_ACS_BI_14_I PASS +TC_AG_ACS_BI_16_I N/A +TC_AG_ACR_BV_01_I PASS +TC_AG_ACR_BV_02_I PASS +TC_AG_CLI_BV_01_I PASS +TC_AG_ICA_BV_01_I N/A +TC_AG_ICA_BV_02_I N/A +TC_AG_ICA_BV_04_I PASS +TC_AG_ICA_BV_05_I N/A +TC_AG_ICA_BV_06_I PASS +TC_AG_ICR_BV_01_I PASS +TC_AG_ICR_BV_02_I PASS +TC_AG_TCA_BV_01_I PASS +TC_AG_TCA_BV_02_I PASS +TC_AG_TCA_BV_03_I PASS +TC_AG_TCA_BV_04_I PASS +TC_AG_TCA_BV_05_I PASS +TC_AG_ATH_BV_03_I PASS +TC_AG_ATH_BV_04_I PASS +TC_AG_ATH_BV_05_I PASS +TC_AG_ATH_BV_06_I PASS +TC_AG_ATA_BV_01_I PASS +TC_AG_ATA_BV_02_I PASS +TC_AG_OCN_BV_01_I PASS +TC_AG_OCM_BV_01_I PASS +TC_AG_OCM_BV_02_I PASS +TC_AG_OCL_BV_01_I PASS +TC_AG_OCL_BV_02_I PASS +TC_AG_TWC_BV_01_I PASS +TC_AG_TWC_BV_02_I PASS +TC_AG_TWC_BV_03_I PASS +TC_AG_TWC_BV_04_I PASS +TC_AG_TWC_BV_05_I PASS +TC_AG_TWC_BV_06_I N/A +TC_AG_CIT_BV_01_I PASS +TC_AG_ENO_BV_01_I PASS +TC_AG_ENO_BV_02_I N/A +TC_AG_VRA_BV_01_I PASS +TC_AG_VRA_BV_02_I PASS +TC_AG_VRA_BI_01_I PASS +TC_AG_VRD_BV_01_I N/A +TC_AG_VTG_BV_01_I N/A +TC_AG_TDC_BV_01_I PASS +TC_AG_RSV_BV_01_I PASS +TC_AG_RSV_BV_02_I PASS +TC_AG_RSV_BV_03_I PASS +TC_AG_RMV_BV_01_I N/A +TC_AG_RMV_BV_02_I N/A +TC_AG_RMV_BV_03_I N/A +TC_AG_ECS_BV_01_I PASS +TC_AG_ECS_BV_02_I PASS +TC_AG_ECS_BV_03_I PASS +TC_AG_ECC_BV_01_I N/A +TC_AG_ECC_BV_02_I N/A +TC_AG_ECC_BI_03_I PASS +TC_AG_ECC_BI_04_I PASS +TC_AG_RHH_BV_01_I N/A +TC_AG_RHH_BV_02_I N/A +TC_AG_RHH_BV_03_I N/A +TC_AG_RHH_BV_04_I N/A +TC_AG_RHH_BV_05_I N/A +TC_AG_RHH_BV_06_I N/A +TC_AG_RHH_BV_07_I N/A +TC_AG_RHH_BV_08_I N/A +TC_AG_NUM_BV_01_I PASS +TC_AG_SLC_BV_01_C PASS +TC_AG_SLC_BV_02_C PASS +TC_AG_SLC_BV_03_C PASS +TC_AG_SLC_BV_04_C PASS +TC_AG_SLC_BV_05_I PASS +TC_AG_SLC_BV_06_I PASS +TC_AG_SLC_BV_07_I PASS +TC_AG_SLC_BV_09_I N/A +TC_AG_SLC_BV_10_I N/A +TC_AG_ACC_BV_08_I PASS +TC_AG_ACC_BV_09_I PASS +TC_AG_ACC_BV_10_I PASS +TC_AG_ACC_BV_11_I PASS +TC_AG_ACC_BI_12_I PASS +TC_AG_ACC_BI_13_I PASS +TC_AG_ACC_BI_14_I PASS +TC_AG_ACC_BV_15_I PASS +TC_AG_WBS_BV_01_I PASS +TC_AG_DIS_BV_01_I PASS +TC_AG_SDP_BV_01_I PASS +TC_AG_IIA_BV_01_I PASS +TC_AG_IIA_BV_02_I PASS +TC_AG_IIA_BV_03_I N/A +TC_AG_IIA_BV_05_I PASS +TC_AG_IID_BV_01_I PASS +TC_AG_IID_BV_02_I N/A +TC_AG_IID_BV_03_I PASS +TC_AG_IID_BV_04_I PASS +TC_AG_IIC_BV_01_I PASS +TC_AG_IIC_BV_02_I PASS +TC_AG_IIC_BV_03_I PASS +TC_AG_HFI_BV_02_I N/A +TC_AG_HFI_BV_03_I N/A +TC_HF_OOR_BV_01_I N/A +TC_HF_OOR_BV_02_I N/A +TC_HF_TRS_BV_01_I N/A +TC_HF_PSI_BV_01_I N/A +TC_HF_PSI_BV_02_I N/A +TC_HF_PSI_BV_03_I N/A +TC_HF_PSI_BV_04_I N/A +TC_HF_ACS_BV_01_I N/A +TC_HF_ACS_BV_03_I N/A +TC_HF_ACS_BV_05_I N/A +TC_HF_ACS_BV_07_I N/A +TC_HF_ACS_BV_09_I N/A +TC_HF_ACS_BV_12_I N/A +TC_HF_ACS_BI_13_I N/A +TC_HF_ACR_BV_01_I N/A +TC_HF_ACR_BV_02_I N/A +TC_HF_CLI_BV_01_I N/A +TC_HF_ICA_BV_01_I N/A +TC_HF_ICA_BV_02_I N/A +TC_HF_ICA_BV_03_I N/A +TC_HF_ICA_BV_04_I N/A +TC_HF_ICA_BV_05_I N/A +TC_HF_ICA_BV_06_I N/A +TC_HF_ICA_BV_07_I N/A +TC_HF_ICR_BV_01_I N/A +TC_HF_ICR_BV_02_I N/A +TC_HF_TCA_BV_01_I N/A +TC_HF_TCA_BV_02_I N/A +TC_HF_TCA_BV_03_I N/A +TC_HF_TCA_BV_04_I N/A +TC_HF_ATH_BV_03_I N/A +TC_HF_ATH_BV_04_I N/A +TC_HF_ATH_BV_05_I N/A +TC_HF_ATH_BV_06_I N/A +TC_HF_ATH_BV_09_I N/A +TC_HF_ATA_BV_01_I N/A +TC_HF_ATA_BV_02_I N/A +TC_HF_ATA_BV_03_I N/A +TC_HF_OCN_BV_01_I N/A +TC_HF_OCM_BV_01_I N/A +TC_HF_OCM_BV_02_I N/A +TC_HF_OCL_BV_01_I N/A +TC_HF_OCL_BV_02_I N/A +TC_HF_TWC_BV_01_I N/A +TC_HF_TWC_BV_02_I N/A +TC_HF_TWC_BV_03_I N/A +TC_HF_TWC_BV_04_I N/A +TC_HF_TWC_BV_05_I N/A +TC_HF_TWC_BV_06_I N/A +TC_HF_CIT_BV_01_I N/A +TC_HF_ENO_BV_01_I N/A +TC_HF_VRA_BV_01_I N/A +TC_HF_VRA_BV_02_I N/A +TC_HF_VRA_BV_03_I N/A +TC_HF_VRD_BV_01_I N/A +TC_HF_VTG_BV_01_I N/A +TC_HF_TDC_BV_01_I N/A +TC_HF_RSV_BV_01_I N/A +TC_HF_RSV_BV_02_I N/A +TC_HF_RSV_BV_03_I N/A +TC_HF_RMV_BV_01_I N/A +TC_HF_RMV_BV_02_I N/A +TC_HF_RMV_BV_03_I N/A +TC_HF_ECS_BV_01_I N/A +TC_HF_ECS_BV_02_I N/A +TC_HF_ECS_BV_03_I N/A +TC_HF_ECC_BV_01_I N/A +TC_HF_ECC_BV_02_I N/A +TC_HF_RHH_BV_01_I N/A +TC_HF_RHH_BV_02_I N/A +TC_HF_RHH_BV_03_I N/A +TC_HF_RHH_BV_04_I N/A +TC_HF_RHH_BV_05_I N/A +TC_HF_RHH_BV_06_I N/A +TC_HF_RHH_BV_07_I N/A +TC_HF_RHH_BV_08_I N/A +TC_HF_NUM_BV_01_I N/A +TC_HF_NUM_BI_01_I N/A +TC_HF_SLC_BV_01_C N/A +TC_HF_SLC_BV_02_C N/A +TC_HF_SLC_BV_03_C N/A +TC_HF_SLC_BV_04_C N/A +TC_HF_SLC_BV_05_I N/A +TC_HF_SLC_BV_06_I N/A +TC_HF_SLC_BV_08_I N/A +TC_HF_ACC_BV_01_I N/A +TC_HF_ACC_BV_02_I N/A +TC_HF_ACC_BV_03_I N/A +TC_HF_ACC_BV_04_I N/A +TC_HF_ACC_BV_05_I N/A +TC_HF_ACC_BV_06_I N/A +TC_HF_ACC_BV_07_I N/A +TC_HF_WBS_BV_02_I N/A +TC_HF_WBS_BV_03_I N/A +TC_HF_DIS_BV_01_I N/A +TC_HF_DIS_BV_02_I N/A +TC_HF_SDP_BV_01_I N/A +TC_HF_SDP_BV_02_C N/A +TC_HF_SDP_BV_03_C N/A +TC_HF_ATAH_BV_01_I N/A +TC_HF_OCA_BV_01_I N/A +TC_HF_IIA_BV_04_I N/A +TC_AG_COD_BV_02_I PASS +TC_AG_ATAH_BV_01_I PASS +TC_AG_ATA_BV_03_I PASS +TC_AG_ATH_BV_09_I PASS +TC_AG_SDP_BV_02_C PASS +TC_AG_SDP_BV_03_C PASS +TC_AG_ICA_BV_07_I PASS +TC_AG_ICA_BV_08_I PASS +TC_AG_ICA_BV_09_I PASS +TC_AG_VRA_BV_03_I PASS +TC_AG_OCA_BV_01_I PASS +TC_AG_TCA_BV_06_I PASS +TC_HF_ATAH_BV_03_I N/A +TC_HF_ATA_BV_03_I N/A +TC_HF_ATH_BV_09_I N/A +TC_HF_SDP_BV_02_C N/A +TC_HF_SDP_BV_03_C N/A +TC_HF_DIS_BV_02_I N/A +TC_HF_ICA_BV_07_I N/A +TC_HF_VRA_BV_03_I N/A +TC_HF_OCA_BV_01_I N/A diff --git a/android/pts-hid.txt b/android/pts-hid.txt new file mode 100644 index 0000000..80f11e8 --- /dev/null +++ b/android/pts-hid.txt @@ -0,0 +1,74 @@ +PTS test results for HID + +PTS version: 6.1 +Tested: 19-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_HOS_HCE_BV_01_I PASS +TC_HOS_HCE_BV_03_I PASS +TC_HOS_HCE_BV_04_I PASS +TC_HOS_HCR_BV_01_I PASS +TC_HOS_HCR_BV_02_I PASS +TC_HOS_HCR_BV_03_I N/A +TC_HOS_HCR_BV_04_I N/A +TC_HOS_HDT_BV_01_I PASS +TC_HOS_HDT_BV_02_I PASS haltest: hidhost connect + hidhost send_data ff00 + NOTE: PTS displays wrong report data on popup + PTS issue #13021 +TC_HOS_HDT_BV_03_I N/A +TC_HOS_HDT_BV_04_I N/A +TC_HOS_HID_BV_01_C N/A +TC_HOS_HID_BV_02_C N/A +TC_HOS_HID_BV_03_C N/A +TC_HOS_HID_BV_04_C N/A +TC_HOS_HID_BV_05_C N/A +TC_HOS_HID_BV_06_C N/A +TC_HOS_HID_BV_08_C N/A +TC_HOS_HID_BV_09_C N/A +TC_HOS_HID_BV_10_C N/A +TC_HOS_DAT_BV_01_C PASS haltest: hidhost connect + hidhost send_data ff00 + NOTE: PTS displays wrong report data on popup + PTS issue #13021 +TC_HOS_DAT_BV_02_C N/A +TC_HOS_DAT_BI_01_C N/A +TC_HOS_DAT_BI_02_C N/A +TC_DEV_HCE_BV_01_I N/A +TC_DEV_HCE_BV_02_I N/A +TC_DEV_HCE_BV_03_I N/A +TC_DEV_HCE_BV_04_I N/A +TC_DEV_HCE_BV_05_I N/A +TC_DEV_HCR_BV_01_I N/A +TC_DEV_HCR_BV_02_I N/A +TC_DEV_HCR_BV_03_I N/A +TC_DEV_HCR_BV_04_I N/A +TC_DEV_HDT_BV_01_I N/A +TC_DEV_HDT_BV_02_I N/A +TC_DEV_HDT_BV_03_I N/A +TC_DEV_HDT_BV_04_I N/A +TC_DEV_HID_BV_01_C N/A +TC_DEV_HID_BV_03_C N/A +TC_DEV_HID_BV_04_C N/A +TC_DEV_HID_BV_05_C N/A +TC_DEV_HID_BV_06_C N/A +TC_DEV_HID_BV_08_C N/A +TC_DEV_HID_BV_09_C N/A +TC_DEV_HID_BV_10_C N/A +TC_DEV_HID_BI_01_C N/A +TC_DEV_HID_BI_02_C N/A +TC_DEV_DAT_BV_01_C N/A +TC_DEV_SDD_BV_01_C N/A +TC_DEV_SDD_BV_02_C N/A +TC_DEV_SDD_BV_03_C N/A +TC_DEV_SDD_BV_04_I N/A +------------------------------------------------------------------------------- diff --git a/android/pts-hogp.txt b/android/pts-hogp.txt new file mode 100644 index 0000000..a6b8dc1 --- /dev/null +++ b/android/pts-hogp.txt @@ -0,0 +1,102 @@ +PTS test results for HoG + +PTS version: 6.1 +Tested: 20-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_HGDS_HH_BV_01_I PASS +TC_HGDS_HH_BV_02_I PASS +TC_HGDS_HH_BV_03_I PASS +TC_HGDS_HD_BV_01_I N/A +TC_HGDS_HD_BV_02_I N/A +TC_HGDR_RH_BV_01_I PASS +TC_HGDC_RH_BV_01_I PASS +TC_HGDC_RH_BV_02_I PASS +TC_HGDC_RH_BV_03_I PASS +TC_HGDC_RH_BV_04_I PASS +TC_HGDC_RH_BV_05_I PASS +TC_HGDC_RH_BV_06_I PASS +TC_HGDC_RH_BV_07_I PASS +TC_HGDC_HH_BV_08_I PASS +TC_HGDC_HH_BV_14_I PASS +TC_HGDC_HH_BV_15_I PASS +TC_HGDC_HH_BV_16_I PASS +TC_HGDC_BH_BV_09_I N/A +TC_HGDC_BH_BV_10_I N/A +TC_HGDC_BH_BV_11_I N/A +TC_HGDC_BH_BV_12_I N/A +TC_HGDC_BH_BV_13_I N/A +TC_HGRF_RH_BV_01_I PASS +TC_HGRF_RH_BV_02_I PASS +TC_HGRF_RH_BV_03_I PASS +TC_HGRF_RH_BV_04_I PASS +TC_HGRF_RH_BV_05_I PASS +TC_HGRF_RH_BV_19_I PASS +TC_HGRF_RH_BV_06_I PASS +TC_HGRF_RH_BV_07_I PASS +TC_HGRF_RH_BV_08_I PASS +TC_HGRF_RH_BV_09_I PASS +TC_HGRF_HH_BV_10_I PASS +TC_HGRF_HH_BV_11_I PASS +TC_HGRF_HH_BV_12_I PASS +TC_HGRF_BH_BV_13_I N/A +TC_HGRF_BH_BV_14_I N/A +TC_HGRF_BH_BV_15_I N/A +TC_HGRF_BH_BV_16_I N/A +TC_HGRF_BH_BV_17_I N/A +TC_HGRF_HH_BV_18_I N/A +TC_HGWF_RH_BV_01_I PASS haltest: hidhost connect + hidhost set_report BTHH_INPUT_REPORT + AAB3F8A6CD + hidhost disconnect +TC_HGWF_RH_BV_02_I PASS haltest: hidhost connect + hidhost set_report BTHH_OUTPUT_REPORT + EF907856341200 + hidhost disconnect +TC_HGWF_RH_BV_03_I PASS haltest: hidhost connect + hidhost set_report BTHH_OUTPUT_REPORT + EF907856341200 + hidhost disconnect +TC_HGWF_RH_BV_04_I PASS haltest: hidhost connect + hidhost set_report BTHH_FEATURE_REPORT + EA453F2D87 + hidhost disconnect +TC_HGWF_RH_BV_05_I N/A +TC_HGWF_RH_BV_06_I N/A +TC_HGWF_RH_BV_07_I N/A +TC_HGWF_BH_BV_08_I N/A +TC_HGWF_BH_BV_09_I N/A +TC_HGWF_BH_BV_10_I N/A +TC_HGWF_BH_BV_11_I N/A +TC_HGCF_RH_BV_01_I PASS +TC_HGCF_RH_BV_02_I PASS haltest: hidhost connect + gattc search_service 1 + gattc get_characteristic 1 {1812,2,1} + gattc get_descriptor 1 {1812,2,1} {2a4d,5} + gattc write_descriptor 1 {1812,2,1} {2a4d,5} + {2902,1} 2 0x0000 0 + gattc get_characteristic 1 {1812,5,1} + gattc get_descriptor 1 {1812,5,1} {2a4d,4} + gattc write_descriptor 1 {1812,5,1} + {2a4d,4} {2902,1} 2 0x0000 0 + hidhost disconnect +TC_HGCF_BH_BV_03_I N/A +TC_HGCF_BH_BV_04_I N/A +TC_HGCF_BH_BV_05_I N/A +TC_HGCF_BH_BV_06_I N/A +TC_HGNF_RH_BV_01_I PASS +TC_HGNF_RH_BI_01_I PASS +TC_HGNF_RH_BI_01_I PASS +TC_HGNF_BH_BV_02_I N/A +TC_HGNF_BH_BV_03_I N/A +TC_HGNF_BH_BI_01_I N/A +------------------------------------------------------------------------------- diff --git a/android/pts-hsp.txt b/android/pts-hsp.txt new file mode 100644 index 0000000..8a48dd8 --- /dev/null +++ b/android/pts-hsp.txt @@ -0,0 +1,41 @@ +PTS test results for HSP + +PTS version: 6.1 +Tested: 13-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_AG_IAC_BV_01_I PASS +TC_AG_IAC_BV_02_I N/A +TC_AG_OAC_BV_01_I PASS +TC_AG_ACR_BV_01_I PASS +TC_AG_ACR_BV_02_I PASS +TC_AG_ACT_BV_01_I PASS +TC_AG_ACT_BV_02_I PASS +TC_AG_RAV_BV_01_I PASS +TC_AG_RAV_BV_02_I PASS +TC_AG_RAV_BV_03_I PASS +TC_AG_RAV_BV_04_I N/A +TC_AG_RAV_BV_05_I N/A +TC_AG_RAV_BV_06_I N/A +TC_HS_IAC_BV_01_I N/A +TC_HS_IAC_BV_02_I N/A +TC_HS_OAC_BV_01_I N/A +TC_HS_ACR_BV_01_I N/A +TC_HS_ACR_BV_02_I N/A +TC_HS_ACT_BV_01_I N/A +TC_HS_ACT_BV_02_I N/A +TC_HS_RAV_BV_01_I N/A +TC_HS_RAV_BV_02_I N/A +TC_HS_RAV_BV_03_I N/A +TC_HS_RAV_BV_04_I N/A +TC_HS_RAV_BV_05_I N/A +TC_HS_RAV_BV_06_I N/A diff --git a/android/pts-iopt.txt b/android/pts-iopt.txt new file mode 100644 index 0000000..cd7ad32 --- /dev/null +++ b/android/pts-iopt.txt @@ -0,0 +1,26 @@ +PTS test results for IOPT + +PTS version: 6.1 +Tested: 21-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_COD_BV_01_I PASS IUT must be discoverable +TC_COD_BV_02_I N/A PTS issue#13473 +TC_SDSS_BV_02_I PASS Note: HDP sink record should be registered before test + run, e.g. register health app via HDPSample.apk +TC_SDAS_BV_03_I PASS Note: HDP sink record should be registered before test + run, e.g. register health app via HDPSample.apk +TC_SDR_BV_04_I PASS For every asked to check PTS bt profile: + haltest: bluetooth get_remote_service_record + + Note: 0000xxxx - acceptable 16bit uuid format +------------------------------------------------------------------------------- diff --git a/android/pts-l2cap.txt b/android/pts-l2cap.txt new file mode 100644 index 0000000..81552dd --- /dev/null +++ b/android/pts-l2cap.txt @@ -0,0 +1,191 @@ +PTS test results for L2CAP + +PTS version: 6.1 +Tested: 28-May-2015 +Android version: 5.1 +Kernel version: 4.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- + For all tests daemon should be stopped then: + setprop ctl.start hciattach +TC_COS_CED_BV_01_C PASS l2test -n -P 4113 +TC_COS_CED_BV_03_C PASS l2test -y -N 1 -P 4113 +TC_COS_CED_BV_04_C PASS l2test -n -P 4113 +TC_COS_CED_BV_05_C PASS l2test -r -P 4113 +TC_COS_CED_BV_07_C PASS l2test -n -P 4113 +TC_COS_CED_BV_08_C PASS l2test -n -P 4113 +TC_COS_CED_BV_09_C PASS l2test -n -P 4113 +TC_COS_CED_BV_10_C N/A +TC_COS_CED_BV_11_C PASS l2test -u -P 4113 +TC_COS_CED_BI_01_C PASS +TC_COS_CFD_BV_01_C PASS l2test -r -P 4113 +TC_COS_CFD_BV_02_C PASS l2test -n -P 4113 +TC_COS_CFD_BV_03_C PASS l2test -n -P 4113 +TC_COS_CFD_BV_08_C PASS l2test -n -P 4113 +TC_COS_CFD_BV_09_C PASS l2test -n -P 4113 +TC_COS_CFD_BV_10_C N/A +TC_COS_CFD_BV_11_C PASS l2test -n -P 4113 +TC_COS_CFD_BV_12_C PASS l2test -n -P 4113 +TC_COS_CFD_BV_13_C N/A +TC_COS_IEX_BV_01_C PASS l2test -n -P 4113 +TC_COS_IEX_BV_02_C PASS +TC_COS_ECH_BV_01_C PASS +TC_COS_ECH_BV_02_C PASS l2ping -c 1 +TC_COS_CFC_BV_01_C PASS l2test -y -N 1 -b 40 -V le_public -P 37 +TC_COS_CFC_BV_02_C PASS l2test -y -N 1 -b 1 -V le_public -P 37 +TC_COS_CFC_BV_03_C PASS l2test -u -V le_public -P 37 +TC_COS_CFC_BV_04_C PASS l2test -u -V le_public -P 37 +TC_COS_CFC_BV_05_C PASS l2test -u -V le_public + l2test -u -V le_public +TC_CLS_CLR_BV_01_C N/A +TC_CLS_UCD_BV_01_C PASS +TC_CLS_UCD_BV_02_C PASS l2test -s -G -N 1 -P 4113 +TC_CLS_UCD_BV_03_C PASS l2test -s -E -G -N 1 -P 4113 +TC_EXF_BV_01_C PASS +TC_EXF_BV_02_C PASS +TC_EXF_BV_03_C PASS +TC_EXF_BV_04_C N/A +TC_EXF_BV_05_C PASS +TC_EXF_BV_06_C N/A +TC_CMC_BV_01_C PASS l2test -r -X ertm -P 4113 +TC_CMC_BV_02_C PASS l2test -r -X ertm -P 4113 +TC_CMC_BV_03_C PASS l2test -r -X ertm -P 4113 +TC_CMC_BV_04_C PASS l2test -r -X streaming -P 4113 +TC_CMC_BV_05_C PASS l2test -r -X streaming -P 4113 +TC_CMC_BV_06_C PASS l2test -r -X streaming -P 4113 +TC_CMC_BV_07_C PASS l2test -r -X ertm -P 4113 +TC_CMC_BV_08_C PASS l2test -r -X streaming -P 4113 +TC_CMC_BV_09_C PASS l2test -r -X basic -P 4113 +TC_CMC_BV_10_C PASS l2test -n -P 4113 +TC_CMC_BV_11_C PASS l2test -n -P 4113 +TC_CMC_BV_12_C PASS l2test -z -X ertm +TC_CMC_BV_13_C PASS l2test -z -X streaming +TC_CMC_BV_14_C PASS l2test -r -X streaming -P 4113 +TC_CMC_BV_15_C PASS l2test -r -X streaming -P 4113 +TC_CMC_BI_01_C PASS l2test -r -X ertm -P 4113 +TC_CMC_BI_02_C PASS l2test -r -X ertm -P 4113 +TC_CMC_BI_03_C PASS l2test -r -X streaming -P 4113 +TC_CMC_BI_04_C PASS l2test -r -X streaming -P 4113 +TC_CMC_BI_05_C PASS l2test -r -X basic -P 4113 +TC_CMC_BI_06_C PASS l2test -r -X basic -P 4113 +TC_FOC_BV_01_C PASS l2test -r -X ertm -P 4113 -F 0 +TC_FOC_BV_02_C PASS l2test -r -X ertm -P 4113 -F 0 +TC_FOC_BV_03_C PASS l2test -r -X ertm -P 4113 -F 0 +TC_OFS_BV_01_C PASS l2test -x -X ertm -P 4113 -F 0 -N 1 +TC_OFS_BV_02_C PASS l2test -r -X ertm -P 4113 -F 0 +TC_OFS_BV_03_C PASS l2test -x -X streaming -P 4113 -F 0 -N 1 +TC_OFS_BV_04_C PASS l2test -d -X streaming -P 4113 -F 0 +TC_OFS_BV_05_C PASS l2test -x -X ertm -P 4113 -N 1 +TC_OFS_BV_06_C PASS l2test -r -X ertm -P 4113 +TC_OFS_BV_07_C PASS l2test -x -X streaming -P 4113 -F 0 -N 1 +TC_OFS_BV_08_C PASS l2test -d -X streaming -P 4113 +TC_ERM_BV_01_C PASS l2test -x -X ertm -P 4113 -N 3 -Y 3 +TC_ERM_BV_02_C PASS l2test -r -X ertm -P 4113 +TC_ERM_BV_03_C PASS l2test -r -X ertm -P 4113 +TC_ERM_BV_05_C PASS l2test -x -X ertm -P 4113 -N 2 -Y 2 +TC_ERM_BV_06_C PASS l2test -x -X ertm -P 4113 -N 2 -Y 2 +TC_ERM_BV_07_C PASS l2test -r -H 1000 -K 10000 -X ertm -P 4113 +TC_ERM_BV_08_C PASS l2test -x -X ertm -P 4113 -N 1 +TC_ERM_BV_09_C PASS l2test -X ertm -P 4113 +TC_ERM_BV_10_C PASS l2test -x -X ertm -P 4113 -N 1 +TC_ERM_BV_11_C PASS l2test -x -X ertm -P 4113 -N 1 -Q 1 +TC_ERM_BV_12_C PASS l2test -x -X ertm -P 4113 -R -N 1 -Q 1 +TC_ERM_BV_13_C PASS l2test -x -X ertm -P 4113 -N 2 +TC_ERM_BV_14_C PASS l2test -x -X ertm -P 4113 -N 4 +TC_ERM_BV_15_C PASS l2test -x -X ertm -P 4113 -N 4 +TC_ERM_BV_16_C N/A +TC_ERM_BV_17_C PASS l2test -X ertm -P 4113 +TC_ERM_BV_18_C PASS l2test -x -X ertm -P 4113 -N 1 +TC_ERM_BV_19_C PASS l2test -x -X ertm -P 4113 -N 1 +TC_ERM_BV_20_C PASS l2test -x -X ertm -P 4113 -N 1 +TC_ERM_BV_21_C PASS l2test -x -X ertm -P 4113 -D 2000 -N 2 +TC_ERM_BV_22_C PASS l2test -r -H 1000 -K 10000 -X ertm -P 4113 +TC_ERM_BV_23_C PASS l2test -x -X ertm -P 4113 -N 2 +TC_ERM_BI_01_C N/A +TC_ERM_BI_02_C PASS l2test -X ertm -P 4113 +TC_ERM_BI_03_C PASS l2test -x -X ertm -P 4113 -N 2 +TC_ERM_BI_04_C PASS l2test -x -X ertm -P 4113 -N 2 +TC_ERM_BI_05_C PASS l2test -x -X ertm -P 4113 -N 2 +TC_STM_BV_01_C PASS l2test -x -X streaming -P 4113 -N 3 -Y 3 +TC_STM_BV_02_C PASS l2test -d -X streaming -P 4113 +TC_STM_BV_03_C PASS l2test -x -X streaming -P 4113 -N 2 +TC_STM_BV_11_C N/A +TC_STM_BV_12_C N/A +TC_STM_BV_13_C N/A +TC_FIX_BV_01_C PASS l2test -z -P 4113 +TC_FIX_BV_02_C N/A +TC_EWC_BV_01_C N/A +TC_EWC_BV_02_C N/A +TC_EWC_BV_03_C N/A +TC_LSC_BV_01_C N/A +TC_LSC_BV_02_C N/A +TC_LSC_BV_03_C N/A +TC_LSC_BI_04_C N/A +TC_LSC_BI_05_C N/A +TC_LSC_BV_06_C N/A +TC_LSC_BV_07_C N/A +TC_LSC_BV_08_C N/A +TC_LSC_BV_09_C N/A +TC_LSC_BI_10_C N/A +TC_LSC_BI_11_C N/A +TC_LSC_BV_12_C N/A +TC_CCH_BV_01_C N/A +TC_CCH_BV_02_C N/A +TC_CCH_BV_03_C N/A +TC_CCH_BV_04_C N/A +TC_ECF_BV_01_C N/A +TC_ECF_BV_02_C N/A +TC_ECF_BV_03_C N/A +TC_ECF_BV_04_C N/A +TC_ECF_BV_05_C N/A +TC_ECF_BV_06_C N/A +TC_ECF_BV_07_C N/A +TC_ECF_BV_08_C N/A +TC_LE_CPU_BV_01_C PASS btmgmt advertising on + l2test -r -V le_public -J 4 +TC_LE_CPU_BV_02_C PASS l2test -n -V le_public -J 4 +TC_LE_CPU_BI_01_C PASS l2test -n -V le_public -J 4 +TC_LE_CPU_BI_02_C PASS btmgmt advertising on + l2test -r -V le_public -J 4 +TC_LE_REJ_BI_01_C PASS l2test -n -V le_public -J 4 +TC_LE_REJ_BI_02_C PASS l2test -n -V le_public -J 4 +TC_LE_CFC_BV_01_C PASS l2test -n -V le_public -P 37 +TC_LE_CFC_BV_02_C PASS l2test -n -V le_public -P 37 +TC_LE_CFC_BV_03_C PASS l2test -x -N 1 -V le_public + hcitool lecc + hcitool ledc +TC_LE_CFC_BV_04_C PASS l2test -n -V le_public -P 241 +TC_LE_CFC_BV_05_C PASS l2test -r -V le_public -J 4 + hcitool lecc + hcitool ledc +TC_LE_CFC_BV_06_C PASS l2test -s -N 10 -V le_public +TC_LE_CFC_BV_07_C PASS l2test -u -V le_public +TC_LE_CFC_BI_01_C PASS l2test -u -V le_public +TC_LE_CFC_BV_08_C PASS l2test -n -V le_public -P 37 +TC_LE_CFC_BV_09_C PASS l2test -n -V le_public -P 37 +TC_LE_CFC_BV_16_C PASS l2test -n -V le_public -P 37 +TC_LE_CFC_BV_17_C N/A +TC_LE_CID_BV_01_C PASS PTS issue #12730 + l2test -r -J 2 + l2test -r -J 4 -V le_public + hcitool cc + hcitool lecc --static + l2test -s -N 1 -C 0 -e 5 -D 10000 + l2test -s -N 1 -C 0 -D 10000 -g 10000 + -V le_public +TC_LE_CID_BV_02_I PASS PTS issue #12730 + l2test -r -J 2 + l2test -r -J 4 -V le_public + l2test -w -N 1 -C 0 -D 5000 -g 10000 + l2test -w -N 1 -C 0 -D 5000 -e 5 -g 10000 + -V le_public + hcitool cc + hcitool lecc --static diff --git a/android/pts-map.txt b/android/pts-map.txt new file mode 100644 index 0000000..2be8db2 --- /dev/null +++ b/android/pts-map.txt @@ -0,0 +1,95 @@ +PTS test results for MAP + +PTS version: 6.1 +Tested: 29-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_MCE_MSM_BV_01_I N/A +TC_MCE_MSM_BV_02_I N/A +TC_MCE_MSM_BV_03_I N/A +TC_MCE_MSM_BV_04_I N/A +TC_MCE_MSM_BV_13_I N/A +TC_MCE_MSM_BV_14_I N/A +TC_MCE_MNR_BV_01_I N/A +TC_MCE_MNR_BV_02_I N/A +TC_MCE_MMB_BV_01_I N/A +TC_MCE_MMB_BV_02_I N/A +TC_MCE_MMB_BV_03_I N/A +TC_MCE_MMB_BV_19_I N/A +TC_MCE_MMB_BV_04_I N/A +TC_MCE_MMB_BV_17_I N/A +TC_MCE_MMB_BV_06_I N/A +TC_MCE_MMB_BV_07_I N/A +TC_MCE_MMB_BV_08_I N/A +TC_MCE_MMD_BV_01_I N/A +TC_MCE_MMU_BV_01_I N/A +TC_MCE_MMN_BV_01_I N/A +TC_MCE_MMN_BV_03_I N/A +TC_MCE_MMI_BV_01_I N/A +TC_MCE_MFB_BV_01_I N/A +TC_MCE_MFB_BV_03_I N/A +TC_MCE_MFB_BV_04_I N/A +TC_MCE_BC_BV_02_I N/A +TC_MCE_BC_BV_04_I N/A +TC_MCE_CON_BV_01_I N/A +TC_MCE_CON_BV_02_I N/A +TC_MCE_ROB_BV_01_I N/A +TC_MCE_SRM_BV_03_I N/A +TC_MCE_SRM_BV_07_I N/A +TC_MCE_SRMP_BI_01_I N/A +TC_MCE_SRMP_BV_01_I N/A +TC_MCE_SRMP_BV_04_I N/A +TC_MCE_SRMP_BV_05_I N/A +TC_MCE_SRMP_BV_06_I N/A +TC_MSE_MSM_BV_05_I PASS +TC_MSE_MSM_BV_06_I PASS +TC_MSE_MSM_BV_07_I PASS +TC_MSE_MSM_BV_08_I PASS +TC_MSE_MSM_BV_09_I N/A +TC_MSE_MSM_BV_10_I N/A +TC_MSE_MSM_BV_11_I N/A +TC_MSE_MSM_BV_12_I N/A +TC_MSE_MNR_BV_03_I PASS +TC_MSE_MNR_BV_04_I PASS +TC_MSE_MMB_BV_09_I PASS +TC_MSE_MMB_BV_10_I PASS +TC_MSE_MMB_BV_11_I PASS +TC_MSE_MMB_BV_20_I PASS +TC_MSE_MMB_BV_12_I N/A +TC_MSE_MMB_BV_18_I PASS +TC_MSE_MMB_BV_13_I PASS +TC_MSE_MMB_BV_14_I PASS +TC_MSE_MMB_BV_15_I PASS +TC_MSE_MMB_BV_16_I PASS +TC_MSE_MMD_BV_02_I PASS +TC_MSE_MMU_BV_02_I PASS +TC_MSE_MMU_BV_03_I PASS +TC_MSE_MMN_BV_02_I INC JIRA #BA-380 +TC_MSE_MMN_BV_04_I N/A +TC_MSE_MMI_BV_02_I N/A +TC_MSE_MFB_BV_02_I N/A +TC_MSE_MFB_BV_05_I N/A +TC_MSE_BC_BV_01_I N/A +TC_MSE_BC_BV_03_I N/A +TC_MSE_CON_BV_01_I N/A +TC_MSE_CON_BV_02_I N/A +TC_MSE_ROB_BV_01_I N/A +TC_MSE_ROB_BV_02_I N/A +TC_MSE_SRM_BI_02_I N/A +TC_MSE_SRM_BI_03_I N/A +TC_MSE_SRM_BI_05_I N/A +TC_MSE_SRM_BV_04_I N/A +TC_MSE_SRM_BV_08_I N/A +TC_MSE_SRMP_BI_02_I N/A +TC_MSE_SRMP_BV_02_I N/A +TC_MSE_SRMP_BV_03_I N/A +------------------------------------------------------------------------------- diff --git a/android/pts-mcap.txt b/android/pts-mcap.txt new file mode 100644 index 0000000..8e224a1 --- /dev/null +++ b/android/pts-mcap.txt @@ -0,0 +1,80 @@ +PTS test results for MCAP + +PTS version: 6.1 +Tested: 19-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +Note: Test were done with ssp enabled and in most of the cases requires pairing + confirmation. This can be done easily by using 'btmgmt monitor'. + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_MCAP_CE_BV_01_C PASS mcaptest -C 4099 -D 4101 -f 2 -dc +TC_MCAP_CE_BV_02_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_CE_BV_03_C PASS mcaptest -C 4099 -D 4101 -f 2 -c +TC_MCAP_CE_BV_04_C PASS mcaptest -C 4099 -D 4101 -f 2 -d +TC_MCAP_CM_ABT_BV_01_C N/A +TC_MCAP_CM_ABT_BV_02_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_CM_ABT_BV_03_C N/A +TC_MCAP_CM_DEL_BV_01_C N/A +TC_MCAP_CM_DEL_BV_02_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_CM_DEL_BV_03_C N/A +TC_MCAP_CM_DEL_BV_04_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_CM_DIS_BV_01_C PASS mcaptest -C 4099 -D 4101 -ab -e 2 -f 2 +TC_MCAP_CM_DIS_BV_02_C PASS mcaptest -C 4099 -D 4101 +TC_MCAP_CM_DIS_BV_03_C PASS mcaptest -C 4099 -D 4101 -n -f 2 +TC_MCAP_CM_DIS_BV_04_C PASS mcaptest -C 4099 -D 4101 -ab -e 2 -f 2 +TC_MCAP_CM_DIS_BV_05_C PASS mcaptest -C 4099 -D 4101 +TC_MCAP_CM_REC_BV_01_C N/A +TC_MCAP_CM_REC_BV_02_C PASS mcaptest -C 4099 -D 4101 -n -f 2 +TC_MCAP_CM_REC_BV_03_C N/A +TC_MCAP_CM_REC_BV_04_C PASS mcaptest -C 4099 -D 4101 -n -f 2 +TC_MCAP_CM_REC_BV_05_C N/A +TC_MCAP_CM_REC_BV_06_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_CS_ERR_BI_01_C N/A +TC_MCAP_CS_ERR_BI_02_C N/A +TC_MCAP_CS_ERR_BI_03_C N/A +TC_MCAP_CS_ERR_BI_04_C N/A +TC_MCAP_CS_I_BV_01_I N/A +TC_MCAP_CS_I_BV_02_I N/A +TC_MCAP_CS_I_BV_03_C N/A +TC_MCAP_CS_I_BV_04_C N/A +TC_MCAP_CS_R_BV_01_I N/A +TC_MCAP_CS_R_BV_02_I N/A +TC_MCAP_CS_R_BV_03_C N/A +TC_MCAP_CS_T_BV_04_C N/A +TC_MCAP_ERR_BI_01_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_ERR_BI_02_C PASS mcaptest -C 4099 -D 4101 -dn -f 2 +TC_MCAP_ERR_BI_03_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_ERR_BI_04_C PASS mcaptest -C 4099 -D 4101 -dn -f 2 +TC_MCAP_ERR_BI_05_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_ERR_BI_06_C PASS mcaptest -C 4099 -D 4101 -dn -f 2 +TC_MCAP_ERR_BI_07_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_ERR_BI_08_C PASS mcaptest -C 4099 -D 4101 -dn -f 2 +TC_MCAP_ERR_BI_09_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_ERR_BI_10_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_ERR_BI_11_C PASS mcaptest -C 4099 -D 4101 -dn -f 2 +TC_MCAP_ERR_BI_12_C PASS mcaptest -C 4099 -D 4101 -dn -f 2 +TC_MCAP_ERR_BI_13_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_ERR_BI_14_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_ERR_BI_15_C PASS mcaptest -C 4099 -D 4101 -dn -f 2 +TC_MCAP_ERR_BI_16_C PASS mcaptest -C 4099 -D 4101 -u -f 2 +TC_MCAP_ERR_BI_17_C PASS mcaptest -C 4099 -D 4101 -dn -f 2 +TC_MCAP_ERR_BI_18_C PASS mcaptest -C 4099 -D 4101 -dn -f 2 +TC_MCAP_ERR_BI_19_C N/A +TC_MCAP_ERR_BI_20_C PASS mcaptest -C 4099 -D 4101 -g -f 2 +TC_MCAP_INV_BI_01_C PASS mcaptest -C 4099 -D 4101 -dc +TC_MCAP_INV_BI_02_C PASS mcaptest -C 4099 -D 4101 -dn -f 2 +TC_MCAP_INV_BI_03_C PASS mcaptest -C 4099 -D 4101 -d -f 2 -c +TC_MCAP_INV_BI_04_C PASS mcaptest -C 4099 -D 4101 -f 2 -c +TC_MCAP_INV_BI_05_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_INV_BI_06_C PASS mcaptest -C 4099 -D 4101 -f 2 +TC_MCAP_INV_BI_07_C PASS mcaptest -C 4099 -D 4101 -f 2 +------------------------------------------------------------------------------- diff --git a/android/pts-mps.txt b/android/pts-mps.txt new file mode 100644 index 0000000..87a0e45 --- /dev/null +++ b/android/pts-mps.txt @@ -0,0 +1,60 @@ +PTS test results for MPS + +PTS version: 6.1 +Tested: 20-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup +NONE test result is none + +Note: Do not use AOSP Music player. Use e.g. MortPlayer or Poweramp. +For full tests conformance, "Touch sounds" on IUT should be disabled +(Settings > Sound¬ification > Other sounds) + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_AG_PSE_HFPB_CTH_SD_BV_01_I PASS +TC_AG_SRC_HFAV_ACT_SD_BV_01_I PASS +TC_AG_SRC_HFAV_ACT_SD_BV_02_I PASS +TC_AG_SRC_HFAV_ACT_SD_BV_03_I PASS +TC_AG_SRC_HFAV_CLH_SD_BV_01_I PASS +TC_AG_SRC_HFAV_CLH_SD_BV_02_I N/A +TC_AG_SRC_HFAV_CLH_SD_BV_03_I PASS +TC_AG_SRC_HFAV_CLH_SD_BV_04_I PASS +TC_AG_SRC_HFAV_CLH_SD_BV_05_I PASS +TC_AG_SRC_HFAV_CLH_SD_BV_06_I PASS PTS issue #13466 +TC_AVP_CTH_SD_BI_01_I PASS +TC_AVP_CTH_SD_BI_02_I PASS +TC_HF_SNK_HFAV_ACT_SD_BV_01_I N/A +TC_HF_SNK_HFAV_ACT_SD_BV_02_I N/A +TC_HF_SNK_HFAV_ACT_SD_BV_03_I N/A +TC_HF_SNK_HFAV_CLH_SD_BV_01_I N/A +TC_HF_SNK_HFAV_CLH_SD_BV_02_I N/A +TC_HF_SNK_HFAV_CLH_SD_BV_03_I N/A +TC_HF_SNK_HFAV_CLH_SD_BV_04_I N/A +TC_HF_SNK_HFAV_CLH_SD_BV_05_I N/A +TC_HF_SNK_HFAV_CLH_SD_BV_06_I N/A +TC_HF_SNK_CT_HFAV_ACT_MD_BV_01_I N/A +TC_HF_SNK_CT_HFAV_CLH_MD_BV_01_I N/A +TC_HF_SNK_CT_HFAV_CLH_MD_BV_02_I N/A +TC_HF_SNK_CT_HFAV_CLH_MD_BV_03_I N/A +TC_HF_SNK_CT_HFAV_CLH_MD_BV_04_I N/A +TC_HF_SNK_CT_HFAV_CLH_MD_BV_05_I N/A +TC_HF_SNK_CT_HFAV_CLH_MD_BV_06_I N/A +TC_SDP_CTH_SD_BV_01_I PASS +TC_SNK_PCE_AVO_ACT_SD_BV_01_I N/A +TC_SRC_PSE_AVO_ACT_SD_BV_01_I PASS +TC_SRC_TG_HFAV_ACT_MD_BV_01_I PASS +TC_SRC_TG_HFAV_CLH_MD_BV_01_I PASS +TC_SRC_TG_HFAV_CLH_MD_BV_02_I PASS +TC_SRC_TG_HFAV_CLH_MD_BV_03_I PASS +TC_SRC_TG_HFAV_CLH_MD_BV_04_I PASS +TC_SRC_TG_HFAV_CLH_MD_BV_05_I PASS +TC_SRC_TG_HFAV_CLH_MD_BV_06_I PASS +TC_PAIRING_HF_SNK_CT N/A Pairing helper for MD tests +------------------------------------------------------------------------------- diff --git a/android/pts-opp.txt b/android/pts-opp.txt new file mode 100644 index 0000000..8496700 --- /dev/null +++ b/android/pts-opp.txt @@ -0,0 +1,119 @@ +PTS test results for OPP + +PTS version: 6.1 +Tested: 20-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup +NONE test result is none + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_CLIENT_BC_BV_02_I N/A +TC_CLIENT_BC_BV_04_I N/A +TC_CLIENT_BCE_BV_01_I N/A +TC_CLIENT_BCE_BV_03_I N/A +TC_CLIENT_BCE_BV_04_I N/A +TC_CLIENT_BCE_BV_05_I N/A +TC_CLIENT_BCE_BV_06_I N/A +TC_CLIENT_BCE_BV_07_I N/A +TC_CLIENT_BCP_BV_01_I N/A +TC_CLIENT_BCP_BV_02_I N/A +TC_CLIENT_BCP_BV_03_I N/A +TC_CLIENT_BCP_BV_04_I N/A +TC_CLIENT_BCP_BV_05_I N/A +TC_CLIENT_CON_BV_01_C N/A +TC_CLIENT_OPH_BI_01_C PASS +TC_CLIENT_OPH_BV_01_I PASS +TC_CLIENT_OPH_BV_02_I N/A +TC_CLIENT_OPH_BV_03_I PASS +TC_CLIENT_OPH_BV_04_I N/A +TC_CLIENT_OPH_BV_05_I PASS +TC_CLIENT_OPH_BV_07_I N/A +TC_CLIENT_OPH_BV_08_I N/A +TC_CLIENT_OPH_BV_09_I N/A +TC_CLIENT_OPH_BV_10_I N/A +TC_CLIENT_OPH_BV_11_I N/A +TC_CLIENT_OPH_BV_12_I N/A +TC_CLIENT_OPH_BV_13_I N/A +TC_CLIENT_OPH_BV_14_I N/A +TC_CLIENT_OPH_BV_15_I N/A +TC_CLIENT_OPH_BV_16_I N/A +TC_CLIENT_OPH_BV_17_I N/A +TC_CLIENT_OPH_BV_18_I N/A +TC_CLIENT_OPH_BV_19_I PASS Send file other than vCard +TC_CLIENT_OPH_BV_20_I PASS +TC_CLIENT_OPH_BV_22_I PASS Send file greater than 2 MB +TC_CLIENT_OPH_BV_23_I PASS Send two vCards in a single operation +TC_CLIENT_OPH_BV_24_I N/A +TC_CLIENT_OPH_BV_25_I N/A +TC_CLIENT_OPH_BV_26_I N/A +TC_CLIENT_SRM_BV_01_C N/A +TC_CLIENT_SRM_BV_03_C N/A +TC_CLIENT_SRM_BV_05_C N/A +TC_CLIENT_SRM_BV_07_C N/A +TC_CLIENT_SRMP_BI_01_C N/A +TC_CLIENT_SRMP_BV_01_C N/A +TC_CLIENT_SRMP_BV_04_C N/A +TC_CLIENT_SRMP_BV_05_C N/A +TC_CLIENT_SRMP_BV_06_C N/A +TC_SERVER_BC_BV_01_I N/A +TC_SERVER_BC_BV_03_I N/A +TC_SERVER_BCE_BV_01_I N/A +TC_SERVER_BCE_BV_03_I N/A +TC_SERVER_BCE_BV_04_I N/A +TC_SERVER_BCE_BV_05_I N/A +TC_SERVER_BCE_BV_06_I N/A +TC_SERVER_BCE_BV_07_I N/A +TC_SERVER_BCP_BV_01_I N/A +TC_SERVER_BCP_BV_02_I PASS +TC_SERVER_BCP_BV_03_I N/A +TC_SERVER_BCP_BV_04_I N/A +TC_SERVER_BCP_BV_05_I N/A +TC_SERVER_CON_BV_02_C N/A +TC_SERVER_OPH_BV_01_I PASS +TC_SERVER_OPH_BV_02_I PASS +TC_SERVER_OPH_BV_03_I PASS +TC_SERVER_OPH_BV_04_I PASS +TC_SERVER_OPH_BV_05_I PASS +TC_SERVER_OPH_BV_07_I N/A +TC_SERVER_OPH_BV_08_I N/A +TC_SERVER_OPH_BV_09_I N/A +TC_SERVER_OPH_BV_10_I PASS +TC_SERVER_OPH_BV_11_I N/A +TC_SERVER_OPH_BV_12_I N/A +TC_SERVER_OPH_BV_13_I N/A +TC_SERVER_OPH_BV_14_I PASS +TC_SERVER_OPH_BV_15_I N/A +TC_SERVER_OPH_BV_16_I N/A +TC_SERVER_OPH_BV_17_I N/A +TC_SERVER_OPH_BV_18_I PASS +TC_SERVER_OPH_BV_19_I PASS +TC_SERVER_OPH_BV_21_I N/A +TC_SERVER_OPH_BV_22_I PASS +TC_SERVER_OPH_BV_23_I PASS +TC_SERVER_OPH_BV_24_I N/A +TC_SERVER_OPH_BV_25_I N/A +TC_SERVER_OPH_BV_26_I N/A +TC_SERVER_ROB_BV_01_I N/A +TC_SERVER_ROB_BV_02_I N/A +TC_SERVER_SRM_BI_03_I N/A +TC_SERVER_SRM_BI_05_I N/A +TC_SERVER_SRM_BV_04_I N/A +TC_SERVER_SRM_BV_08_I N/A +TC_SERVER_SRMP_BV_02_I N/A +TC_SERVER_SRMP_BV_03_I N/A +TC_CLIENT_OPH_BV_27_I N/A +TC_CLIENT_OPH_BV_34_I PASS +TC_SERVER_OPH_BV_27_I N/A +TC_SERVER_OPH_BV_30_I N/A +TC_SERVER_OPH_BV_31_I N/A +TC_SERVER_OPH_BV_32_I N/A +TC_SERVER_OPH_BV_33_I N/A +TC_SERVER_OPH_BV_34_I PASS +------------------------------------------------------------------------------- diff --git a/android/pts-pan.txt b/android/pts-pan.txt new file mode 100644 index 0000000..350b84e --- /dev/null +++ b/android/pts-pan.txt @@ -0,0 +1,71 @@ +PTS test results for PAN + +PTS version: 6.1 +Tested: 11-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +-------------------------------------------------------------------------------- +Test Name Result Notes +-------------------------------------------------------------------------------- +TC_BNEP_BROADCAST_0_BV_01_C N/A +TC_BNEP_BROADCAST_0_BV_02_C N/A +TC_BNEP_MULTICAST_0_BV_03_C N/A +TC_BNEP_MULTICAST_0_BV_04_C N/A +TC_BNEP_FORWARD_UNICAST_BV_05_C N/A +TC_BNEP_FORWARD_UNICAST_BV_06_C N/A +TC_BNEP_EXTENSION_0_BV_07_C_TESTER_1 N/A +TC_BNEP_EXTENSION_0_BV_07_C_TESTER_2 N/A +TC_BNEP_FORWARD_BV_08_C_TESTER_1 N/A +TC_BNEP_FORWARD_BV_08_C_TESTER_2 N/A +TC_BNEP_FORWARD_BROADCAST_BV_09_C_TESTER_1 N/A +TC_BNEP_FORWARD_BROADCAST_BV_09_C_TESTER_2 N/A +TC_BNEP_FORWARD_BROADCAST_BV_09_C_TESTER_3 N/A +TC_BNEP_FILTER_BV_10_C_TESTER_1 N/A +TC_BNEP_FILTER_BV_10_C_TESTER_2 N/A +TC_BNEP_FILTER_BV_11_C_TESTER_1 N/A +TC_BNEP_FILTER_BV_11_C_TESTER_2 N/A +TC_BNEP_FILTER_BV_12_C_TESTER_1 N/A +TC_BNEP_FILTER_BV_12_C_TESTER_2 N/A +TC_BNEP_FILTER_BV_13_C_TESTER_1 N/A +TC_BNEP_FILTER_BV_13_C_TESTER_2 N/A +TC_BNEP_FILTER_BV_14_C_TESTER_1 N/A +TC_BNEP_FILTER_BV_14_C_TESTER_2 N/A +TC_BNEP_FILTER_BV_15_C_TESTER_1 N/A +TC_BNEP_FILTER_BV_15_C_TESTER_2 N/A +TC_BRIDGE_TX_BV_01_I PASS +TC_BRIDGE_RX_BV_02_I PASS To initiate general + ethernet use for e.g. + ping. +TC_IPv4_AUTONET_BV_01_I PASS To initiate general + ethernet use for e.g. + ping. +TC_IPv6_AUTONET_BV_02_I N/A +TC_IP_DHCP_BV_03_I PASS +TC_IP_LLMNR_BV_01_I N/A +TC_IP_LLMNR_BV_02_I N/A +TC_IP_DNS_BV_01_I N/A +TC_IP_APP_BV_03_I N/A +TC_IP_APP_BV_05_I INC PTS issue #13436 + ip neighbour add + lladdr + dev bt-pan + Note: ARP record should + be add immediately after + establish of bt-pan, + because PTS treat other + than ICMP frames as + error. +TC_MISC_ROLE_BV_01_C N/A +TC_MISC_ROLE_BV_02_C N/A +TC_MISC_UUID_BV_01_C PASS +TC_MISC_UUID_BV_02_C PASS +TC_SDP_NAP_BV_01_C PASS +TC_SDP_GN_BV_01_C N/A +TC_SDP_PANU_BV_01_C N/A +-------------------------------------------------------------------------------- diff --git a/android/pts-pbap.txt b/android/pts-pbap.txt new file mode 100644 index 0000000..0c597eb --- /dev/null +++ b/android/pts-pbap.txt @@ -0,0 +1,145 @@ +PTS test results for PBAP + +PTS version: 6.1 +Tested: 19-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_PCE_SSM_BV_01_C N/A +TC_PCE_SSM_BV_02_C N/A +TC_PCE_SSM_BV_06_C N/A +TC_PCE_SSM_BV_08_C N/A +TC_PCE_SSM_BI_01_C N/A +TC_PCE_SSM_BV_09_C N/A +TC_PCE_SSM_BV_10_C N/A +TC_PCE_PBD_BV_01_C N/A +TC_PCE_PBD_BV_04_C N/A +TC_PCE_PBD_BV_38_C N/A +TC_PCE_PBD_BV_29_C N/A +TC_PCE_PBD_BV_40_C N/A +TC_PCE_PBD_BV_41_C N/A +TC_PCE_PBD_BV_42_C N/A +TC_PCE_PBD_BV_43_C N/A +TC_PCE_PBD_BV_44_C N/A +TC_PCE_PBD_BV_45_C N/A +TC_PCE_PBD_BV_46_C N/A +TC_PCE_PBD_BV_47_C N/A +TC_PCE_PBD_BV_48_C N/A +TC_PCE_PBB_BV_01_C N/A +TC_PCE_PBB_BV_02_C N/A +TC_PCE_PBB_BV_03_C N/A +TC_PCE_PBB_BV_05_C N/A +TC_PCE_PBB_BV_39_C N/A +TC_PCE_PBB_BV_40_C N/A +TC_PCE_PBB_BV_41_C N/A +TC_PCE_PBB_BV_42_C N/A +TC_PCE_PBB_BV_33_C N/A +TC_PCE_PBB_BV_34_C N/A +TC_PCE_PBB_BV_35_C N/A +TC_PCE_PBB_BV_36_C N/A +TC_PCE_PBB_BV_43_C N/A +TC_PCE_PBB_BV_37_C N/A +TC_PCE_PBB_BV_38_C N/A +TC_PCE_PBF_BV_01_I N/A +TC_PCE_PBF_BV_02_I N/A +TC_PCE_PBF_BV_03_I N/A +TC_PCE_PDF_BV_01_I N/A +TC_PCE_PDF_BV_06_I N/A +TC_PSE_SSM_BV_03_C PASS Tester must accept obex request +TC_PSE_SSM_BV_05_C PASS +TC_PSE_SSM_BV_07_C PASS Tester must accept obex request with + TSPX_auth_password set in PIXITs +TC_PSE_SSM_BI_02_C PASS +TC_PSE_SSM_BI_03_C N/A +TC_PSE_SSM_BV_08_I PASS Tester must compare passkey on IUT and PTS +TC_PSE_SSM_BV_11_C N/A + +TC_PSE_PBD_BV_02_C PASS Tester must compare phone book size with the + value given by PTS +TC_PSE_PBD_BV_03_C PASS Tester must compare phone book size with the + value given by PTS +TC_PSE_PBD_BV_05_C N/A +TC_PSE_PBD_BI_01_C PASS +TC_PSE_PBD_BV_06_C N/A +TC_PSE_PBD_BV_07_C N/A +TC_PSE_PBD_BV_08_C N/A +TC_PSE_PBD_BV_09_C N/A +TC_PSE_PBD_BV_10_C N/A +TC_PSE_PBD_BV_17_C PASS +TC_PSE_PBD_BV_18_C N/A +TC_PSE_PBD_BV_19_C N/A +TC_PSE_PBD_BV_20_C N/A +TC_PSE_PBD_BV_21_C N/A +TC_PSE_PBD_BV_22_C N/A +TC_PSE_PBD_BV_23_C N/A +TC_PSE_PBD_BV_24_C N/A +TC_PSE_PBD_BV_25_C N/A +TC_PSE_PBD_BV_26_C N/A +TC_PSE_PBD_BV_27_C N/A +TC_PSE_PBD_BV_28_C N/A +TC_PSE_PBD_BV_29_C N/A +TC_PSE_PBD_BV_30_C N/A +TC_PSE_PBD_BV_31_C N/A +TC_PSE_PBD_BV_32_C N/A +TC_PSE_PBD_BV_33_C N/A +TC_PSE_PBD_BV_34_C N/A +TC_PSE_PBD_BV_35_C N/A +TC_PSE_PBD_BV_36_C PASS +TC_PSE_PBD_BV_37_C N/A +TC_PSE_PBB_BV_06_C PASS +TC_PSE_PBB_BV_07_C PASS +TC_PSE_PBB_BV_08_C PASS Tester must compare phone book size with the + value given by PTS +TC_PSE_PBB_BV_09_C PASS +TC_PSE_PBB_BV_10_C PASS Tester must verify vcard content received by PTS +TC_PSE_PBB_BV_11_C PASS Tester must verify number of new missed calls + with value given by PTS +TC_PSE_PBB_BI_01_C PASS +TC_PSE_PBB_BI_07_C PASS +TC_PSE_PBB_BV_12_C PASS +TC_PSE_PBB_BV_13_C N/A +TC_PSE_PBB_BV_14_C N/A +TC_PSE_PBB_BV_15_C N/A +TC_PSE_PBB_BV_16_C N/A +TC_PSE_PBB_BV_17_C N/A +TC_PSE_PBB_BV_18_C N/A +TC_PSE_PBB_BV_19_C N/A +TC_PSE_PBB_BV_20_C N/A +TC_PSE_PBB_BV_21_C N/A +TC_PSE_PBB_BV_22_C N/A +TC_PSE_PBB_BV_23_C N/A +TC_PSE_PBB_BV_24_C N/A +TC_PSE_PBB_BV_25_C N/A +TC_PSE_PBB_BV_26_C N/A +TC_PSE_PBB_BV_27_C N/A +TC_PSE_PBB_BV_44_C N/A +TC_PSE_PBB_BV_45_C N/A +TC_PSE_PBB_BV_46_C N/A +TC_PSE_PBB_BV_28_C N/A +TC_PSE_PBB_BV_29_C N/A +TC_PSE_PBB_BV_30_C N/A +TC_PSE_PBB_BV_31_C PASS Requires entries in IUT's /och and /ich folders +TC_PSE_PBB_BV_32_C N/A +TC_PSE_PBF_BV_01_I PASS +TC_PSE_PBF_BV_02_I PASS Tester must verify vcard content received by PTS +TC_PSE_PBF_BV_03_I PASS +TC_PSE_PDF_BV_01_I PASS Tester must compare phone book size with the + value given by PTS +TC_PSE_PDF_BV_06_I PASS +TC_PSE_BC_BV_03_I N/A +TC_PSE_CON_BV_02_I N/A +TC_PSE_ROB_BV_01_I N/A +TC_PSE_SRM_BI_03_I N/A +TC_PSE_SRM_BI_05_I N/A +TC_PSE_SRM_BV_08_I N/A +TC_PSE_SRMP_BI_02_I N/A +TC_PSE_SRMP_BV_02_I N/A +------------------------------------------------------------------------------- diff --git a/android/pts-rfcomm.txt b/android/pts-rfcomm.txt new file mode 100644 index 0000000..b1fce28 --- /dev/null +++ b/android/pts-rfcomm.txt @@ -0,0 +1,38 @@ +PTS test results for RFCOMM + +PTS version: 6.1 +Tested: 12-May-2015 +Android version: 5.1 +Kernel version: 4.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup +NONE test result is none + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_RFC_BV_01_C PASS rctest -n -P 1 +TC_RFC_BV_02_C PASS rctest -r -P 1 +TC_RFC_BV_03_C PASS rctest -r -P 1 +TC_RFC_BV_04_C PASS rctest -r -P 1 +TC_RFC_BV_05_C PASS rctest -n -P 4 + Note: test requires IUT to connect on the given + channel. sdptool browse to check the + channel. +TC_RFC_BV_06_C PASS rctest -r -P 1 +TC_RFC_BV_07_C PASS rctest -r -P 1 +TC_RFC_BV_08_C PASS rctest -r -P 1 +TC_RFC_BV_11_C PASS rctest -r -P 1 +TC_RFC_BV_13_C PASS rctest -r -P 1 +TC_RFC_BV_14_C N/A +TC_RFC_BV_15_C PASS rctest -r -P 1 +TC_RFC_BV_17_C PASS rctest -d -P 1 +TC_RFC_BV_19_C PASS +TC_RFC_BV_21_C PASS rctest -w -N 10 -P 1 +TC_RFC_BV_22_C PASS rctest -w -N 10 -P 1 +TC_RFC_BV_25_C PASS rctest -r -P 1 +------------------------------------------------------------------------------- diff --git a/android/pts-scpp.txt b/android/pts-scpp.txt new file mode 100644 index 0000000..3fab984 --- /dev/null +++ b/android/pts-scpp.txt @@ -0,0 +1,24 @@ +PTS test results for ScPP + +PTS version: 6.1 +Tested: 12-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup +NONE test result is none + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_SPDS_SC_BV_01_I PASS +TC_SPDC_SC_BV_01_I PASS +TC_SPDC_SC_BV_02_I PASS +TC_SPDC_SC_BV_03_I PASS +TC_SPWF_SC_BV_01_I PASS +TC_SPCF_SC_BV_01_I PASS +TC_SPNF_SC_BV_01_I PASS +------------------------------------------------------------------------------- diff --git a/android/pts-sdp.txt b/android/pts-sdp.txt new file mode 100644 index 0000000..8a3e889 --- /dev/null +++ b/android/pts-sdp.txt @@ -0,0 +1,77 @@ +PTS test results for SDP + +PTS version: 6.1 +Tested: 13-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup +NONE test result is none + +Note: from haltest: +bluetooth set_adapter_property BT_PROPERTY_ADAPTER_SCAN_MODE + BT_SCAN_MODE_CONNECTABLE +bluetooth enable +socket listen BTSOCK_L2CAP BlueZ 0 + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_SERVER_BRW_BV_01_C PASS +TC_SERVER_BRW_BV_01_C PASS +TC_SERVER_SA_BI_01_C PASS +TC_SERVER_SA_BI_02_C PASS +TC_SERVER_SA_BI_03_C PASS +TC_SERVER_SA_BV_01_C PASS +TC_SERVER_SA_BV_03_C PASS +TC_SERVER_SA_BV_04_C PASS +TC_SERVER_SA_BV_05_C PASS +TC_SERVER_SA_BV_06_C PASS +TC_SERVER_SA_BV_07_C PASS +TC_SERVER_SA_BV_08_C PASS +TC_SERVER_SA_BV_09_C PASS +TC_SERVER_SA_BV_10_C PASS +TC_SERVER_SA_BV_11_C PASS +TC_SERVER_SA_BV_12_C PASS +TC_SERVER_SA_BV_13_C PASS +TC_SERVER_SA_BV_14_C PASS +TC_SERVER_SA_BV_15_C PASS +TC_SERVER_SA_BV_16_C PASS +TC_SERVER_SA_BV_17_C PASS +TC_SERVER_SA_BV_18_C PASS +TC_SERVER_SA_BV_19_C PASS +TC_SERVER_SA_BV_20_C PASS +TC_SERVER_SA_BV_21_C PASS +TC_SERVER_SS_BI_01_C PASS +TC_SERVER_SS_BI_02_C PASS +TC_SERVER_SS_BV_01_C PASS +TC_SERVER_SS_BV_03_C PASS +TC_SERVER_SS_BV_04_C PASS +TC_SERVER_SSA_BI_01_C PASS +TC_SERVER_SSA_BI_02_C PASS +TC_SERVER_SSA_BV_01_C PASS +TC_SERVER_SSA_BV_02_C PASS +TC_SERVER_SSA_BV_03_C PASS +TC_SERVER_SSA_BV_04_C PASS +TC_SERVER_SSA_BV_06_C PASS +TC_SERVER_SSA_BV_07_C PASS +TC_SERVER_SSA_BV_08_C PASS +TC_SERVER_SSA_BV_09_C PASS +TC_SERVER_SSA_BV_10_C PASS +TC_SERVER_SSA_BV_11_C PASS +TC_SERVER_SSA_BV_12_C PASS +TC_SERVER_SSA_BV_13_C PASS +TC_SERVER_SSA_BV_14_C PASS +TC_SERVER_SSA_BV_15_C PASS +TC_SERVER_SSA_BV_16_C PASS +TC_SERVER_SSA_BV_17_C PASS +TC_SERVER_SSA_BV_18_C PASS +TC_SERVER_SSA_BV_19_C PASS +TC_SERVER_SSA_BV_20_C PASS +TC_SERVER_SSA_BV_21_C PASS +TC_SERVER_SSA_BV_22_C PASS +TC_SERVER_SSA_BV_23_C PASS +------------------------------------------------------------------------------- diff --git a/android/pts-sm.txt b/android/pts-sm.txt new file mode 100644 index 0000000..1fad305 --- /dev/null +++ b/android/pts-sm.txt @@ -0,0 +1,102 @@ +PTS test results for SM + +PTS version: 6.1 +Tested: 04-May-2015 +Android version: 5.1 +kernel version: 4.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup +NONE test result is none + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_PROT_BV_01_C PASS btmgmt pair -c 0x03 -t 0x01 +TC_PROT_BV_02_C PASS btmgmt advertising on + btmgmt pair -c 0x03 -t 0x01 +TC_JW_BV_01_C PASS btmgmt pairable off + btmgmt pair -c 0x03 -t 0x01 +TC_JW_BV_02_C PASS btmgmt advertising on +TC_JW_BV_05_C PASS btmgmt pair -c 0x03 -t 0x01 +TC_JW_BI_01_C PASS btmgmt pair -c 0x03 -t 0x01 +TC_JW_BI_02_C PASS btmgmt pairable on +TC_JW_BI_03_C PASS btmgmt pairable on + btmgmt advertising on +TC_JW_BI_04_C PASS btmgmt pairable off + btmgmg pair -c 0x03 -t 0x01 +TC_PKE_BV_01_C PASS btmgmt pairable off + btmgmt pair -c 0x04 -t 0x01 + Note: provide passkey to PTS +TC_PKE_BV_02_C PASS btmgmt pairable off + btmgmt io-cap 0x04 + Note: provide passkey +TC_PKE_BV_04_C PASS btmgmt pair -c 0x04 -t 0x01 +TC_PKE_BV_05_C PASS btmgmt io-cap 0x04 + l2test -r -J4 -AES -V le_public +TC_PKE_BI_01_C PASS btmgmt pair -c 0x04 -t 0x01 + Note: Enter invalid passkey in PTS +TC_PKE_BI_02_C PASS btmgmt pair -c 0x04 -t 0x01 + Note: provide passkey +TC_PKE_BI_03_C PASS btmgmt io-cap 0x04 + btmgmt advertising on + Note: Enter invalid passkey in PTS +TC_OOB_BV_01_C N/A +TC_OOB_BV_02_C N/A +TC_OOB_BV_03_C N/A +TC_OOB_BV_04_C N/A +TC_OOB_BV_05_C PASS btmgmt pair -c 0x04 -t 0x01 + Note: Enter valid passkey in PTS +TC_OOB_BV_06_C PASS btmgmt advertising on + Note: Enter valid passkey in PTS +TC_OOB_BV_07_C PASS btmgmt pair -c 0x04 -t 0x01 +TC_OOB_BV_08_C PASS btmgmt advertising on + Note: Accept pairing in btmgmt +TC_OOB_BV_09_C N/A +TC_OOB_BV_10_C N/A +TC_OOB_BI_01_C N/A +TC_OOB_BI_02_C N/A +TC_EKS_BV_01_C PASS btmgmt pair -c 0x04 -t 0x01 + Note: Enter valid passkey in PTS +TC_EKS_BV_02_C PASS btmgmt advertising on + Note: Accept pairing in btmgmt +TC_EKS_BI_01_C PASS btmgmt pair -c 0x03 -t 0x01 +TC_EKS_BI_02_C PASS btmgmt advertising on +TC_SIGN_BV_01_C INC PTS issue #12305 +TC_SIGN_BV_03_C PASS haltest: + gattc register_client 1234 + gattc listen 1 1 + Note: IUT must be connectable and discoverable +TC_SIGN_BI_01_C PASS haltest: + gattc register client 1234 + gattc listen 1 1 + Note: IUT must be connectable and discoverable +TC_KDU_BV_01_C PASS btmgmt pairable on + btmgmt advertising on + btmgmt connectable on +TC_KDU_BV_02_C PASS PTS issue #12302 + Note: Can pass it with following instructions: + btmgmt privacy on + btmgmt advertising on + Check our random address (valid for 15 min) + Set PIXIT TSPX_bd_addr_iut to random address + Set PIXIT TSPX_peer_type to 01 +TC_KDU_BV_03_C PASS btmgmt pairable on + btmgmt advertising on +TC_KDU_BV_04_C PASS btmgmt pair -c 0x03 -t 0x01 +TC_KDU_BV_05_C PASS PTS issue #12302 + Note: Can pass it with following instructions: + btmgmt privacy on + Check our random address (valid for 15 min) + Set PIXIT TSPX_bd_addr_iut to random address + Set PIXIT TSPX_peer_type to 01 +TC_KDU_BV_06_C PASS btmgmt pair -c 0x03 -t 0x01 +TC_KDU_BV_07_C PASS btmgmt pairable on +TC_SIP_BV_01_C PASS btmgmt advertising on +TC_SIP_BV_02_C PASS btmgmt pair -c 0x03 -t 0x01 +TC_SIE_BV_01_C PASS btmgmt io-cap 0x03 + btmgmt advertising on +------------------------------------------------------------------------------- diff --git a/android/pts-spp.txt b/android/pts-spp.txt new file mode 100644 index 0000000..0845825 --- /dev/null +++ b/android/pts-spp.txt @@ -0,0 +1,22 @@ +PTS test results for SPP + +PTS version: 6.1 +Tested: 19-May-2015 +Android version: 5.1 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_DevA_APP_BV_01_C PASS haltest: socket connect + BTSOCK_RFCOMM 00001101 0 +TC_DevB_APP_BV_02_C PASS haltest: socket listen BTSOCK_RFCOMM SerialPort + 00001101 + Note: IUT must be in connectable, discoverable + mode. +------------------------------------------------------------------------------- diff --git a/android/sco-msg.h b/android/sco-msg.h new file mode 100644 index 0000000..d1b13d7 --- /dev/null +++ b/android/sco-msg.h @@ -0,0 +1,40 @@ +/* + * + * 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 + * + */ + +static const char BLUEZ_SCO_SK_PATH[] = "\0bluez_sco_socket"; + +#define SCO_SERVICE_ID 0 + +#define SCO_STATUS_SUCCESS IPC_STATUS_SUCCESS +#define SCO_STATUS_FAILED 0x01 + +#define SCO_OP_STATUS IPC_OP_STATUS + +#define SCO_OP_GET_FD 0x01 +struct sco_cmd_get_fd { + uint8_t bdaddr[6]; +} __attribute__((packed)); + +struct sco_rsp_get_fd { + uint16_t mtu; +} __attribute__((packed)); diff --git a/android/sco.c b/android/sco.c new file mode 100644 index 0000000..e8ac685 --- /dev/null +++ b/android/sco.c @@ -0,0 +1,351 @@ +/* + * + * 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 +#endif + +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "btio/btio.h" +#include "src/log.h" +#include "src/shared/util.h" + +#include "sco.h" + +struct bt_sco { + int ref_count; + + GIOChannel *server_io; + + GIOChannel *io; + guint watch; + + bdaddr_t local_addr; + bdaddr_t remote_addr; + + bt_sco_confirm_func_t confirm_cb; + bt_sco_conn_func_t connect_cb; + bt_sco_disconn_func_t disconnect_cb; +}; + +/* We support only one sco for the moment */ +static bool sco_in_use = false; + +static void clear_remote_address(struct bt_sco *sco) +{ + memset(&sco->remote_addr, 0, sizeof(bdaddr_t)); +} + +static gboolean disconnect_watch(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + struct bt_sco *sco = user_data; + + g_io_channel_shutdown(sco->io, TRUE, NULL); + g_io_channel_unref(sco->io); + sco->io = NULL; + + DBG(""); + + sco->watch = 0; + + if (sco->disconnect_cb) + sco->disconnect_cb(&sco->remote_addr); + + clear_remote_address(sco); + + return FALSE; +} + +static void connect_sco_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct bt_sco *sco = user_data; + + DBG(""); + + /* Lets unref connecting io */ + if (sco->io) { + g_io_channel_unref(sco->io); + sco->io = NULL; + } + + if (err) { + error("sco: Audio connect failed (%s)", err->message); + + /* + * Connect_sco_cb is called only when connect_cb is in place + * Therefore it is safe to call it + */ + sco->connect_cb(SCO_STATUS_ERROR, &sco->remote_addr); + + clear_remote_address(sco); + + return; + } + + g_io_channel_set_close_on_unref(chan, TRUE); + + sco->io = g_io_channel_ref(chan); + sco->watch = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + disconnect_watch, sco); + + /* It is safe to call it here */ + sco->connect_cb(SCO_STATUS_OK, &sco->remote_addr); +} + +static void confirm_sco_cb(GIOChannel *chan, gpointer user_data) +{ + char address[18]; + bdaddr_t bdaddr; + GError *err = NULL; + struct bt_sco *sco = user_data; + uint16_t voice_settings; + + DBG(""); + + bt_io_get(chan, &err, + BT_IO_OPT_DEST, address, + BT_IO_OPT_DEST_BDADDR, &bdaddr, + BT_IO_OPT_INVALID); + if (err) { + error("sco: audio confirm failed (%s)", err->message); + g_error_free(err); + goto drop; + } + + if (!sco->confirm_cb || !sco->connect_cb) { + error("sco: Connect and/or confirm callback not registered "); + goto drop; + } + + /* Check if there is SCO */ + if (sco->io) { + error("sco: SCO is in progress"); + goto drop; + } + + if (!sco->confirm_cb(&bdaddr, &voice_settings)) { + error("sco: Audio connection from %s rejected", address); + goto drop; + } + + bacpy(&sco->remote_addr, &bdaddr); + + DBG("Incoming SCO connection from %s, voice settings 0x%x", address, + voice_settings); + + err = NULL; + bt_io_set(chan, &err, BT_IO_OPT_VOICE, voice_settings, + BT_IO_OPT_INVALID); + if (err) { + error("sco: Could not set voice settings (%s)", err->message); + g_error_free(err); + goto drop; + } + + if (!bt_io_accept(chan, connect_sco_cb, sco, NULL, NULL)) { + error("sco: Failed to accept audio connection"); + goto drop; + } + + sco->io = g_io_channel_ref(chan); + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static bool sco_listen(struct bt_sco *sco) +{ + GError *err = NULL; + + if (!sco) + return false; + + sco->server_io = bt_io_listen(NULL, confirm_sco_cb, sco, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + &sco->local_addr, + BT_IO_OPT_INVALID); + if (!sco->server_io) { + error("sco: Failed to listen on SCO: %s", err->message); + g_error_free(err); + return false; + } + + return true; +} + +struct bt_sco *bt_sco_new(const bdaddr_t *local_bdaddr) +{ + struct bt_sco *sco; + + if (!local_bdaddr) + return NULL; + + /* For now we support only one SCO connection per time */ + if (sco_in_use) + return NULL; + + sco = new0(struct bt_sco, 1); + if (!sco) + return NULL; + + bacpy(&sco->local_addr, local_bdaddr); + + if (!sco_listen(sco)) { + free(sco); + return NULL; + } + + sco_in_use = true; + + return bt_sco_ref(sco); +} + +struct bt_sco *bt_sco_ref(struct bt_sco *sco) +{ + if (!sco) + return NULL; + + __sync_fetch_and_add(&sco->ref_count, 1); + + return sco; +} + +static void sco_free(struct bt_sco *sco) +{ + if (sco->server_io) { + g_io_channel_shutdown(sco->server_io, TRUE, NULL); + g_io_channel_unref(sco->server_io); + } + + if (sco->io) { + g_io_channel_shutdown(sco->io, TRUE, NULL); + g_io_channel_unref(sco->io); + } + + g_free(sco); + sco_in_use = false; +} + +void bt_sco_unref(struct bt_sco *sco) +{ + DBG(""); + + if (!sco) + return; + + if (__sync_sub_and_fetch(&sco->ref_count, 1)) + return; + + sco_free(sco); +} + +bool bt_sco_connect(struct bt_sco *sco, const bdaddr_t *addr, + uint16_t voice_settings) +{ + GIOChannel *io; + GError *gerr = NULL; + + DBG(""); + + if (!sco || !sco->connect_cb || !addr) { + error("sco: Incorrect parameters or missing connect_cb"); + return false; + } + + /* Check if we have connection in progress */ + if (sco->io) { + error("sco: Connection already in progress"); + return false; + } + + io = bt_io_connect(connect_sco_cb, sco, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &sco->local_addr, + BT_IO_OPT_DEST_BDADDR, addr, + BT_IO_OPT_VOICE, voice_settings, + BT_IO_OPT_INVALID); + + if (!io) { + error("sco: unable to connect audio: %s", gerr->message); + g_error_free(gerr); + return false; + } + + sco->io = io; + + bacpy(&sco->remote_addr, addr); + + return true; +} + +void bt_sco_disconnect(struct bt_sco *sco) +{ + if (!sco) + return; + + if (sco->io) + g_io_channel_shutdown(sco->io, TRUE, NULL); +} + +bool bt_sco_get_fd_and_mtu(struct bt_sco *sco, int *fd, uint16_t *mtu) +{ + GError *err; + + if (!sco->io || !fd || !mtu) + return false; + + err = NULL; + if (!bt_io_get(sco->io, &err, BT_IO_OPT_MTU, mtu, BT_IO_OPT_INVALID)) { + error("Unable to get MTU: %s\n", err->message); + g_clear_error(&err); + return false; + } + + *fd = g_io_channel_unix_get_fd(sco->io); + + return true; +} + +void bt_sco_set_confirm_cb(struct bt_sco *sco, + bt_sco_confirm_func_t func) +{ + sco->confirm_cb = func; +} + +void bt_sco_set_connect_cb(struct bt_sco *sco, bt_sco_conn_func_t func) +{ + sco->connect_cb = func; +} + +void bt_sco_set_disconnect_cb(struct bt_sco *sco, + bt_sco_disconn_func_t func) +{ + sco->disconnect_cb = func; +} diff --git a/android/sco.h b/android/sco.h new file mode 100644 index 0000000..4e1a2b3 --- /dev/null +++ b/android/sco.h @@ -0,0 +1,51 @@ +/* + * + * 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 + * + */ + +enum sco_status { + SCO_STATUS_OK, + SCO_STATUS_ERROR, +}; + +struct bt_sco; + +struct bt_sco *bt_sco_new(const bdaddr_t *local_bdaddr); + +struct bt_sco *bt_sco_ref(struct bt_sco *sco); +void bt_sco_unref(struct bt_sco *sco); + +bool bt_sco_connect(struct bt_sco *sco, const bdaddr_t *remote_addr, + uint16_t voice_settings); +void bt_sco_disconnect(struct bt_sco *sco); +bool bt_sco_get_fd_and_mtu(struct bt_sco *sco, int *fd, uint16_t *mtu); + +typedef bool (*bt_sco_confirm_func_t) (const bdaddr_t *remote_addr, + uint16_t *voice_settings); +typedef void (*bt_sco_conn_func_t) (enum sco_status status, + const bdaddr_t *addr); +typedef void (*bt_sco_disconn_func_t) (const bdaddr_t *addr); + +void bt_sco_set_confirm_cb(struct bt_sco *sco, + bt_sco_confirm_func_t func); +void bt_sco_set_connect_cb(struct bt_sco *sco, bt_sco_conn_func_t func); +void bt_sco_set_disconnect_cb(struct bt_sco *sco, + bt_sco_disconn_func_t func); diff --git a/android/socket.c b/android/socket.c new file mode 100644 index 0000000..15e1bfc --- /dev/null +++ b/android/socket.c @@ -0,0 +1,1322 @@ +/* + * + * 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 +#endif + +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "btio/btio.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" +#include "socket.h" + +#define RFCOMM_CHANNEL_MAX 30 + +#define OPP_DEFAULT_CHANNEL 9 +#define HSP_AG_DEFAULT_CHANNEL 12 +#define HFP_AG_DEFAULT_CHANNEL 13 +#define PBAP_DEFAULT_CHANNEL 15 +#define MAP_MAS_DEFAULT_CHANNEL 16 + +#define SVC_HINT_OBEX 0x10 + +/* Hardcoded MAP stuff needed for MAS SMS Instance.*/ +#define DEFAULT_MAS_INSTANCE 0x00 + +#define MAP_MSG_TYPE_SMS_GSM 0x02 +#define MAP_MSG_TYPE_SMS_CDMA 0x04 +#define DEFAULT_MAS_MSG_TYPE (MAP_MSG_TYPE_SMS_GSM | MAP_MSG_TYPE_SMS_CDMA) + +static struct ipc *hal_ipc = NULL; +struct rfcomm_sock { + int channel; /* RFCOMM channel */ + BtIOSecLevel sec_level; + + /* for socket to BT */ + int bt_sock; + guint bt_watch; + + /* for socket to HAL */ + int jv_sock; + guint jv_watch; + + bdaddr_t dst; + uint32_t service_handle; + + uint8_t *buf; + int buf_size; +}; + +struct rfcomm_channel { + bool reserved; + struct rfcomm_sock *rfsock; +}; + +static bdaddr_t adapter_addr; + +static uint8_t hal_mode = HAL_MODE_SOCKET_DEFAULT; + +static const uint8_t zero_uuid[16] = { 0 }; + +/* Simple list of RFCOMM connected sockets */ +static GList *connections = NULL; + +static struct rfcomm_channel servers[RFCOMM_CHANNEL_MAX + 1]; + +static uint32_t test_sdp_record_uuid16 = 0; +static uint32_t test_sdp_record_uuid32 = 0; +static uint32_t test_sdp_record_uuid128 = 0; + +static int rfsock_set_buffer(struct rfcomm_sock *rfsock) +{ + socklen_t len = sizeof(int); + int rcv, snd, size, err; + + err = getsockopt(rfsock->bt_sock, SOL_SOCKET, SO_RCVBUF, &rcv, &len); + if (err < 0) { + int err = -errno; + error("getsockopt(SO_RCVBUF): %s", strerror(-err)); + return err; + } + + err = getsockopt(rfsock->bt_sock, SOL_SOCKET, SO_SNDBUF, &snd, &len); + if (err < 0) { + int err = -errno; + error("getsockopt(SO_SNDBUF): %s", strerror(-err)); + return err; + } + + size = MAX(rcv, snd); + + DBG("Set buffer size %d", size); + + rfsock->buf = g_malloc(size); + rfsock->buf_size = size; + + return 0; +} + +static void cleanup_rfsock(gpointer data) +{ + struct rfcomm_sock *rfsock = data; + + DBG("rfsock %p bt_sock %d jv_sock %d", rfsock, rfsock->bt_sock, + rfsock->jv_sock); + + if (rfsock->jv_sock >= 0) + if (close(rfsock->jv_sock) < 0) + error("close() fd %d failed: %s", rfsock->jv_sock, + strerror(errno)); + + if (rfsock->bt_sock >= 0) + if (close(rfsock->bt_sock) < 0) + error("close() fd %d: failed: %s", rfsock->bt_sock, + strerror(errno)); + + if (rfsock->bt_watch > 0) + if (!g_source_remove(rfsock->bt_watch)) + error("bt_watch source was not found"); + + if (rfsock->jv_watch > 0) + if (!g_source_remove(rfsock->jv_watch)) + error("stack_watch source was not found"); + + if (rfsock->service_handle) + bt_adapter_remove_record(rfsock->service_handle); + + if (rfsock->buf) + g_free(rfsock->buf); + + g_free(rfsock); +} + +static struct rfcomm_sock *create_rfsock(int bt_sock, int *hal_sock) +{ + int fds[2] = {-1, -1}; + struct rfcomm_sock *rfsock; + + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) < 0) { + error("socketpair(): %s", strerror(errno)); + *hal_sock = -1; + return NULL; + } + + rfsock = g_new0(struct rfcomm_sock, 1); + rfsock->jv_sock = fds[0]; + *hal_sock = fds[1]; + rfsock->bt_sock = bt_sock; + + DBG("rfsock %p", rfsock); + + if (bt_sock < 0) + return rfsock; + + if (rfsock_set_buffer(rfsock) < 0) { + cleanup_rfsock(rfsock); + return NULL; + } + + return rfsock; +} + +static sdp_record_t *create_rfcomm_record(uint8_t chan, uuid_t *uuid, + const char *svc_name, + bool has_obex) +{ + sdp_list_t *svclass_id; + sdp_list_t *seq, *proto_seq, *pbg_seq; + sdp_list_t *proto[3]; + uuid_t l2cap_uuid, rfcomm_uuid, obex_uuid, pbg_uuid; + sdp_data_t *channel; + sdp_record_t *record; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + record->handle = sdp_next_handle(); + + svclass_id = sdp_list_append(NULL, uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap_uuid); + seq = 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, &chan); + proto[1] = sdp_list_append(proto[1], channel); + seq = sdp_list_append(seq, proto[1]); + + if (has_obex) { + sdp_uuid16_create(&obex_uuid, OBEX_UUID); + proto[2] = sdp_list_append(NULL, &obex_uuid); + seq = sdp_list_append(seq, proto[2]); + } + + proto_seq = sdp_list_append(NULL, seq); + sdp_set_access_protos(record, proto_seq); + + sdp_uuid16_create(&pbg_uuid, PUBLIC_BROWSE_GROUP); + pbg_seq = sdp_list_append(NULL, &pbg_uuid); + sdp_set_browse_groups(record, pbg_seq); + + if (svc_name) + sdp_set_info_attr(record, svc_name, NULL, NULL); + + sdp_data_free(channel); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + if (has_obex) + sdp_list_free(proto[2], NULL); + sdp_list_free(seq, NULL); + sdp_list_free(proto_seq, NULL); + sdp_list_free(pbg_seq, NULL); + sdp_list_free(svclass_id, NULL); + + return record; +} + +static sdp_record_t *create_opp_record(uint8_t chan, const char *svc_name) +{ + uint8_t formats[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0xff }; + uint8_t dtd = SDP_UINT8; + uuid_t uuid; + sdp_list_t *seq; + sdp_profile_desc_t profile[1]; + void *dtds[sizeof(formats)], *values[sizeof(formats)]; + sdp_data_t *formats_list; + sdp_record_t *record; + size_t i; + + sdp_uuid16_create(&uuid, OBEX_OBJPUSH_SVCLASS_ID); + + record = create_rfcomm_record(chan, &uuid, svc_name, true); + if (!record) + return NULL; + + sdp_uuid16_create(&profile[0].uuid, OBEX_OBJPUSH_PROFILE_ID); + profile[0].version = 0x0100; + seq = sdp_list_append(NULL, profile); + sdp_set_profile_descs(record, seq); + + for (i = 0; i < sizeof(formats); i++) { + dtds[i] = &dtd; + values[i] = &formats[i]; + } + formats_list = sdp_seq_alloc(dtds, values, sizeof(formats)); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FORMATS_LIST, formats_list); + + sdp_list_free(seq, NULL); + + return record; +} + +static sdp_record_t *create_pbap_record(uint8_t chan, const char *svc_name) +{ + sdp_list_t *seq; + sdp_profile_desc_t profile[1]; + uint8_t formats = 0x01; + sdp_record_t *record; + uuid_t uuid; + + sdp_uuid16_create(&uuid, PBAP_PSE_SVCLASS_ID); + + record = create_rfcomm_record(chan, &uuid, svc_name, true); + if (!record) + return NULL; + + sdp_uuid16_create(&profile[0].uuid, PBAP_PROFILE_ID); + profile[0].version = 0x0101; + seq = sdp_list_append(NULL, profile); + sdp_set_profile_descs(record, seq); + + sdp_attr_add_new(record, SDP_ATTR_SUPPORTED_REPOSITORIES, SDP_UINT8, + &formats); + + sdp_list_free(seq, NULL); + + return record; +} + +static sdp_record_t *create_mas_record(uint8_t chan, const char *svc_name) +{ + sdp_list_t *seq; + sdp_profile_desc_t profile[1]; + uint8_t minst, mtype; + sdp_record_t *record; + uuid_t uuid; + int cnt, ret; + + switch (hal_mode) { + case HAL_MODE_SOCKET_DYNAMIC_MAP: + /* + * Service name for MAP is passed as XXYYname + * XX - instance + * YY - message type + */ + ret = sscanf(svc_name, "%02hhx%02hhx%n", &minst, &mtype, &cnt); + if (ret != 2 || cnt != 4) + return NULL; + + svc_name += 4; + break; + case HAL_MODE_SOCKET_DEFAULT: + minst = DEFAULT_MAS_INSTANCE; + mtype = DEFAULT_MAS_MSG_TYPE; + break; + default: + return NULL; + } + + sdp_uuid16_create(&uuid, MAP_MSE_SVCLASS_ID); + + record = create_rfcomm_record(chan, &uuid, svc_name, true); + if (!record) + return NULL; + + sdp_uuid16_create(&profile[0].uuid, MAP_PROFILE_ID); + profile[0].version = 0x0101; + seq = sdp_list_append(NULL, profile); + sdp_set_profile_descs(record, seq); + + sdp_attr_add_new(record, SDP_ATTR_MAS_INSTANCE_ID, SDP_UINT8, &minst); + sdp_attr_add_new(record, SDP_ATTR_SUPPORTED_MESSAGE_TYPES, SDP_UINT8, + &mtype); + + sdp_list_free(seq, NULL); + + return record; +} + +static sdp_record_t *create_spp_record(uint8_t chan, const char *svc_name) +{ + sdp_record_t *record; + uuid_t uuid; + + sdp_uuid16_create(&uuid, SERIAL_PORT_SVCLASS_ID); + + record = create_rfcomm_record(chan, &uuid, svc_name, false); + if (!record) + return NULL; + + return record; +} + +static sdp_record_t *create_app_record(uint8_t chan, + const uint8_t *app_uuid, + const char *svc_name) +{ + sdp_record_t *record; + uuid_t uuid; + + sdp_uuid128_create(&uuid, app_uuid); + sdp_uuid128_to_uuid(&uuid); + + record = create_rfcomm_record(chan, &uuid, svc_name, false); + if (!record) + return NULL; + + return record; +} + +static const struct profile_info { + uint8_t uuid[16]; + uint8_t channel; + uint8_t svc_hint; + BtIOSecLevel sec_level; + sdp_record_t * (*create_record)(uint8_t chan, const char *svc_name); +} profiles[] = { + { + .uuid = { + 0x00, 0x00, 0x11, 0x08, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB + }, + .channel = HSP_AG_DEFAULT_CHANNEL, + .svc_hint = 0, + .sec_level = BT_IO_SEC_MEDIUM, + .create_record = NULL + }, { + .uuid = { + 0x00, 0x00, 0x11, 0x1F, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB + }, + .channel = HFP_AG_DEFAULT_CHANNEL, + .svc_hint = 0, + .sec_level = BT_IO_SEC_MEDIUM, + .create_record = NULL + }, { + .uuid = { + 0x00, 0x00, 0x11, 0x2F, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB + }, + .channel = PBAP_DEFAULT_CHANNEL, + .svc_hint = SVC_HINT_OBEX, + .sec_level = BT_IO_SEC_MEDIUM, + .create_record = create_pbap_record + }, { + .uuid = { + 0x00, 0x00, 0x11, 0x05, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB + }, + .channel = OPP_DEFAULT_CHANNEL, + .svc_hint = SVC_HINT_OBEX, + .sec_level = BT_IO_SEC_LOW, + .create_record = create_opp_record + }, { + .uuid = { + 0x00, 0x00, 0x11, 0x32, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB + }, + .channel = MAP_MAS_DEFAULT_CHANNEL, + .svc_hint = SVC_HINT_OBEX, + .sec_level = BT_IO_SEC_MEDIUM, + .create_record = create_mas_record + }, { + .uuid = { + 0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB + }, + .channel = 0, + .svc_hint = 0, + .sec_level = BT_IO_SEC_MEDIUM, + .create_record = create_spp_record + }, +}; + +static uint32_t sdp_service_register(uint8_t channel, const uint8_t *uuid, + const struct profile_info *profile, + const void *svc_name) +{ + sdp_record_t *record = NULL; + uint8_t svc_hint = 0; + + if (profile && profile->create_record) { + record = profile->create_record(channel, svc_name); + svc_hint = profile->svc_hint; + } else if (uuid) { + record = create_app_record(channel, uuid, svc_name); + } + + if (!record) + return 0; + + if (bt_adapter_add_record(record, svc_hint) < 0) { + error("Failed to register on SDP record"); + sdp_record_free(record); + return 0; + } + + return record->handle; +} + +static int bt_sock_send_fd(int sock_fd, const void *buf, int len, int send_fd) +{ + ssize_t ret; + struct msghdr msg; + struct cmsghdr *cmsg; + struct iovec iv; + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + + DBG("len %d sock_fd %d send_fd %d", len, sock_fd, send_fd); + + if (sock_fd == -1 || send_fd == -1) + return -1; + + memset(&msg, 0, sizeof(msg)); + memset(cmsgbuf, 0, sizeof(cmsgbuf)); + + 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(send_fd)); + + memcpy(CMSG_DATA(cmsg), &send_fd, sizeof(send_fd)); + + iv.iov_base = (unsigned char *) buf; + iv.iov_len = len; + + msg.msg_iov = &iv; + msg.msg_iovlen = 1; + + ret = sendmsg(sock_fd, &msg, MSG_NOSIGNAL); + if (ret < 0) { + error("sendmsg(): sock_fd %d send_fd %d: %s", + sock_fd, send_fd, strerror(errno)); + return ret; + } + + return ret; +} + +static const struct profile_info *get_profile_by_uuid(const uint8_t *uuid) +{ + unsigned int i; + + for (i = 0; i < G_N_ELEMENTS(profiles); i++) { + if (!memcmp(profiles[i].uuid, uuid, 16)) + return &profiles[i]; + } + + return NULL; +} + +static int try_write_all(int fd, unsigned char *buf, int len) +{ + int sent = 0; + + while (len > 0) { + int written; + + written = write(fd, buf, len); + if (written < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + + if (!written) + return 0; + + len -= written; buf += written; sent += written; + } + + return sent; +} + +static gboolean jv_sock_client_event_cb(GIOChannel *io, GIOCondition cond, + gpointer data) +{ + struct rfcomm_sock *rfsock = data; + int len, sent; + + if (cond & G_IO_HUP) { + DBG("Socket %d hang up", g_io_channel_unix_get_fd(io)); + goto fail; + } + + if (cond & (G_IO_ERR | G_IO_NVAL)) { + error("Socket %d error", g_io_channel_unix_get_fd(io)); + goto fail; + } + + len = read(rfsock->jv_sock, rfsock->buf, rfsock->buf_size); + if (len <= 0) { + error("read(): %s", strerror(errno)); + /* Read again */ + return TRUE; + } + + sent = try_write_all(rfsock->bt_sock, rfsock->buf, len); + if (sent < 0) { + error("write(): %s", strerror(errno)); + goto fail; + } + + return TRUE; +fail: + DBG("rfsock %p jv_sock %d cond %d", rfsock, rfsock->jv_sock, cond); + + connections = g_list_remove(connections, rfsock); + cleanup_rfsock(rfsock); + + return FALSE; +} + +static gboolean bt_sock_event_cb(GIOChannel *io, GIOCondition cond, + gpointer data) +{ + struct rfcomm_sock *rfsock = data; + int len, sent; + + if (cond & G_IO_HUP) { + DBG("Socket %d hang up", g_io_channel_unix_get_fd(io)); + goto fail; + } + + if (cond & (G_IO_ERR | G_IO_NVAL)) { + error("Socket %d error", g_io_channel_unix_get_fd(io)); + goto fail; + } + + len = read(rfsock->bt_sock, rfsock->buf, rfsock->buf_size); + if (len <= 0) { + error("read(): %s", strerror(errno)); + /* Read again */ + return TRUE; + } + + sent = try_write_all(rfsock->jv_sock, rfsock->buf, len); + if (sent < 0) { + error("write(): %s", strerror(errno)); + goto fail; + } + + return TRUE; +fail: + DBG("rfsock %p bt_sock %d cond %d", rfsock, rfsock->bt_sock, cond); + + connections = g_list_remove(connections, rfsock); + cleanup_rfsock(rfsock); + + return FALSE; +} + +static bool sock_send_accept(struct rfcomm_sock *rfsock, bdaddr_t *bdaddr, + int fd_accepted) +{ + struct hal_sock_connect_signal cmd; + int len; + + DBG(""); + + cmd.size = sizeof(cmd); + bdaddr2android(bdaddr, cmd.bdaddr); + cmd.channel = rfsock->channel; + cmd.status = 0; + + len = bt_sock_send_fd(rfsock->jv_sock, &cmd, sizeof(cmd), fd_accepted); + if (len != sizeof(cmd)) { + error("Error sending accept signal"); + return false; + } + + return true; +} + +static gboolean jv_sock_server_event_cb(GIOChannel *io, GIOCondition cond, + gpointer data) +{ + struct rfcomm_sock *rfsock = data; + + DBG("rfsock %p jv_sock %d cond %d", rfsock, rfsock->jv_sock, cond); + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_ERR | G_IO_HUP)) { + servers[rfsock->channel].rfsock = NULL; + cleanup_rfsock(rfsock); + } + + return FALSE; +} + +static void accept_cb(GIOChannel *io, GError *err, gpointer user_data) +{ + struct rfcomm_sock *rfsock = user_data; + struct rfcomm_sock *new_rfsock; + GIOChannel *jv_io; + GError *gerr = NULL; + bdaddr_t dst; + char address[18]; + int new_sock; + int hal_sock; + guint id; + GIOCondition cond; + + if (err) { + error("%s", err->message); + return; + } + + bt_io_get(io, &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(io, TRUE, NULL); + return; + } + + ba2str(&dst, address); + DBG("Incoming connection from %s on channel %d (rfsock %p)", address, + rfsock->channel, rfsock); + + new_sock = g_io_channel_unix_get_fd(io); + new_rfsock = create_rfsock(new_sock, &hal_sock); + if (!new_rfsock) { + g_io_channel_shutdown(io, TRUE, NULL); + return; + } + + DBG("new rfsock %p bt_sock %d jv_sock %d hal_sock %d", new_rfsock, + new_rfsock->bt_sock, new_rfsock->jv_sock, hal_sock); + + if (!sock_send_accept(rfsock, &dst, hal_sock)) { + cleanup_rfsock(new_rfsock); + return; + } + + connections = g_list_append(connections, new_rfsock); + + /* Handle events from Android */ + cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL; + jv_io = g_io_channel_unix_new(new_rfsock->jv_sock); + id = g_io_add_watch(jv_io, cond, jv_sock_client_event_cb, new_rfsock); + g_io_channel_unref(jv_io); + + new_rfsock->jv_watch = id; + + /* Handle rfcomm events */ + cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL; + id = g_io_add_watch(io, cond, bt_sock_event_cb, new_rfsock); + g_io_channel_set_close_on_unref(io, FALSE); + + new_rfsock->bt_watch = id; +} + +static int find_free_channel(void) +{ + int ch; + + /* channel 0 is reserver so we don't use it */ + for (ch = 1; ch <= RFCOMM_CHANNEL_MAX; ch++) { + struct rfcomm_channel *srv = &servers[ch]; + + if (!srv->reserved && srv->rfsock == NULL) + return ch; + } + + return 0; +} + +static BtIOSecLevel get_sec_level(uint8_t flags) +{ + /* + * HAL_SOCK_FLAG_AUTH should require MITM but in our case setting + * security to BT_IO_SEC_HIGH would also require 16-digits PIN code + * for pre-2.1 devices which is not what Android expects. For this + * reason we ignore this flag to not break apps which use "secure" + * sockets (have both auth and encrypt flags set, there is no public + * API in Android which should provide proper high security socket). + */ + return flags & HAL_SOCK_FLAG_ENCRYPT ? BT_IO_SEC_MEDIUM : + BT_IO_SEC_LOW; +} + +static uint8_t rfcomm_listen(int chan, const uint8_t *name, const uint8_t *uuid, + uint8_t flags, int *hal_sock) +{ + const struct profile_info *profile; + struct rfcomm_sock *rfsock = NULL; + BtIOSecLevel sec_level; + GIOChannel *io, *jv_io; + GIOCondition cond; + GError *err = NULL; + guint id; + uuid_t uu; + char uuid_str[32]; + + sdp_uuid128_create(&uu, uuid); + sdp_uuid2strn(&uu, uuid_str, sizeof(uuid_str)); + + DBG("chan %d flags 0x%02x uuid %s name %s", chan, flags, uuid_str, + name); + + if ((!memcmp(uuid, zero_uuid, sizeof(zero_uuid)) && chan <= 0) || + (chan > RFCOMM_CHANNEL_MAX)) { + error("Invalid rfcomm listen params"); + return HAL_STATUS_INVALID; + } + + profile = get_profile_by_uuid(uuid); + if (!profile) { + sec_level = get_sec_level(flags); + } else { + if (!profile->create_record) + return HAL_STATUS_INVALID; + + chan = profile->channel; + sec_level = profile->sec_level; + } + + if (chan <= 0) + chan = find_free_channel(); + + if (!chan) { + error("No free channels"); + return HAL_STATUS_BUSY; + } + + if (servers[chan].rfsock != NULL) { + error("Channel already registered (%d)", chan); + return HAL_STATUS_BUSY; + } + + DBG("chan %d sec_level %d", chan, sec_level); + + rfsock = create_rfsock(-1, hal_sock); + if (!rfsock) + return HAL_STATUS_FAILED; + + rfsock->channel = chan; + + io = bt_io_listen(accept_cb, NULL, rfsock, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, sec_level, + BT_IO_OPT_INVALID); + if (!io) { + error("Failed listen: %s", err->message); + g_error_free(err); + goto failed; + } + + rfsock->bt_sock = g_io_channel_unix_get_fd(io); + + g_io_channel_set_close_on_unref(io, FALSE); + g_io_channel_unref(io); + + /* Handle events from Android */ + cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; + jv_io = g_io_channel_unix_new(rfsock->jv_sock); + id = g_io_add_watch_full(jv_io, G_PRIORITY_HIGH, cond, + jv_sock_server_event_cb, rfsock, + NULL); + g_io_channel_unref(jv_io); + + rfsock->jv_watch = id; + + DBG("rfsock %p bt_sock %d jv_sock %d hal_sock %d", rfsock, + rfsock->bt_sock, + rfsock->jv_sock, + *hal_sock); + + if (write(rfsock->jv_sock, &chan, sizeof(chan)) != sizeof(chan)) { + error("Error sending RFCOMM channel"); + goto failed; + } + + rfsock->service_handle = sdp_service_register(chan, uuid, profile, + name); + + servers[chan].rfsock = rfsock; + + return HAL_STATUS_SUCCESS; + +failed: + + cleanup_rfsock(rfsock); + close(*hal_sock); + return HAL_STATUS_FAILED; +} + +static uint32_t add_test_record(uuid_t *uuid) +{ + sdp_record_t *record; + sdp_list_t *svclass_id; + sdp_list_t *seq, *pbg_seq, *proto_seq, *ap_seq; + sdp_list_t *proto, *proto1, *aproto; + uuid_t l2cap_uuid, pbg_uuid, ap_uuid; + + record = sdp_record_alloc(); + if (!record) + return 0; + + record->handle = sdp_next_handle(); + + svclass_id = sdp_list_append(NULL, uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto = sdp_list_append(NULL, &l2cap_uuid); + seq = sdp_list_append(NULL, proto); + + proto_seq = sdp_list_append(NULL, seq); + sdp_set_access_protos(record, proto_seq); + + sdp_uuid16_create(&pbg_uuid, PUBLIC_BROWSE_GROUP); + pbg_seq = sdp_list_append(NULL, &pbg_uuid); + sdp_set_browse_groups(record, pbg_seq); + + /* Additional Protocol Descriptor List */ + sdp_uuid16_create(&ap_uuid, L2CAP_UUID); + proto1 = sdp_list_append(NULL, &ap_uuid); + ap_seq = sdp_list_append(NULL, proto1); + aproto = sdp_list_append(NULL, ap_seq); + sdp_set_add_access_protos(record, aproto); + + sdp_set_service_id(record, *uuid); + sdp_set_record_state(record, 0); + sdp_set_service_ttl(record, 0); + sdp_set_service_avail(record, 0); + sdp_set_url_attr(record, "http://www.bluez.org", + "http://www.bluez.org", "http://www.bluez.org"); + + sdp_list_free(proto, NULL); + sdp_list_free(seq, NULL); + sdp_list_free(proto_seq, NULL); + sdp_list_free(pbg_seq, NULL); + sdp_list_free(svclass_id, NULL); + + if (bt_adapter_add_record(record, 0) < 0) { + sdp_record_free(record); + return 0; + } + + return record->handle; +} + +static void test_sdp_cleanup(void) +{ + if (test_sdp_record_uuid16) { + bt_adapter_remove_record(test_sdp_record_uuid16); + test_sdp_record_uuid16 = 0; + } + + if (test_sdp_record_uuid32) { + bt_adapter_remove_record(test_sdp_record_uuid32); + test_sdp_record_uuid32 = 0; + } + + if (test_sdp_record_uuid128) { + bt_adapter_remove_record(test_sdp_record_uuid128); + test_sdp_record_uuid128 = 0; + } +} + +static void test_sdp_init(void) +{ + char uuid128[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + uuid_t u; + + sdp_uuid16_create(&u, 0xffff); + test_sdp_record_uuid16 = add_test_record(&u); + + sdp_uuid32_create(&u, 0xffffffff); + test_sdp_record_uuid32 = add_test_record(&u); + + sdp_uuid128_create(&u, uuid128); + test_sdp_record_uuid128 = add_test_record(&u); +} + +static uint8_t l2cap_listen(int chan, const uint8_t *name, const uint8_t *uuid, + uint8_t flags, int *hal_sock) +{ + /* TODO be more strict here? */ + if (strcmp("BlueZ", (const char *) name)) { + error("socket: Only SDP test supported on L2CAP"); + return HAL_STATUS_UNSUPPORTED; + } + + test_sdp_cleanup(); + test_sdp_init(); + + *hal_sock = -1; + + return HAL_STATUS_SUCCESS; +} + +static void handle_listen(const void *buf, uint16_t len) +{ + const struct hal_cmd_socket_listen *cmd = buf; + uint8_t status; + int hal_sock; + + switch (cmd->type) { + case HAL_SOCK_RFCOMM: + status = rfcomm_listen(cmd->channel, cmd->name, cmd->uuid, + cmd->flags, &hal_sock); + break; + case HAL_SOCK_L2CAP: + status = l2cap_listen(cmd->channel, cmd->name, cmd->uuid, + cmd->flags, &hal_sock); + break; + case HAL_SOCK_SCO: + status = HAL_STATUS_UNSUPPORTED; + break; + default: + status = HAL_STATUS_INVALID; + break; + } + + if (status != HAL_STATUS_SUCCESS) + goto failed; + + ipc_send_rsp_full(hal_ipc, HAL_SERVICE_ID_SOCKET, HAL_OP_SOCKET_LISTEN, + 0, NULL, hal_sock); + close(hal_sock); + return; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_SOCKET, HAL_OP_SOCKET_LISTEN, + status); +} + +static bool sock_send_connect(struct rfcomm_sock *rfsock, bdaddr_t *bdaddr) +{ + struct hal_sock_connect_signal cmd; + int len; + + DBG(""); + + memset(&cmd, 0, sizeof(cmd)); + cmd.size = sizeof(cmd); + bdaddr2android(bdaddr, cmd.bdaddr); + cmd.channel = rfsock->channel; + cmd.status = 0; + + len = write(rfsock->jv_sock, &cmd, sizeof(cmd)); + if (len < 0) { + error("%s", strerror(errno)); + return false; + } + + if (len != sizeof(cmd)) { + error("Error sending connect signal"); + return false; + } + + return true; +} + +static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) +{ + struct rfcomm_sock *rfsock = user_data; + bdaddr_t *dst = &rfsock->dst; + GIOChannel *jv_io; + char address[18]; + guint id; + GIOCondition cond; + + if (err) { + error("%s", err->message); + goto fail; + } + + ba2str(dst, address); + DBG("Connected to %s on channel %d (rfsock %p)", address, + rfsock->channel, rfsock); + + if (!sock_send_connect(rfsock, dst)) + goto fail; + + /* Handle events from Android */ + cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL; + jv_io = g_io_channel_unix_new(rfsock->jv_sock); + id = g_io_add_watch(jv_io, cond, jv_sock_client_event_cb, rfsock); + g_io_channel_unref(jv_io); + + rfsock->jv_watch = id; + + /* Handle rfcomm events */ + cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + id = g_io_add_watch(io, cond, bt_sock_event_cb, rfsock); + g_io_channel_set_close_on_unref(io, FALSE); + + rfsock->bt_watch = id; + + return; +fail: + connections = g_list_remove(connections, rfsock); + cleanup_rfsock(rfsock); +} + +static bool do_rfcomm_connect(struct rfcomm_sock *rfsock, int chan) +{ + GIOChannel *io; + GError *gerr = NULL; + + DBG("rfsock %p sec_level %d chan %d", rfsock, rfsock->sec_level, chan); + + io = bt_io_connect(connect_cb, rfsock, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &adapter_addr, + BT_IO_OPT_DEST_BDADDR, &rfsock->dst, + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, rfsock->sec_level, + BT_IO_OPT_INVALID); + if (!io) { + error("Failed connect: %s", gerr->message); + g_error_free(gerr); + return false; + } + + g_io_channel_set_close_on_unref(io, FALSE); + g_io_channel_unref(io); + + if (write(rfsock->jv_sock, &chan, sizeof(chan)) != sizeof(chan)) { + error("Error sending RFCOMM channel"); + return false; + } + + rfsock->bt_sock = g_io_channel_unix_get_fd(io); + rfsock_set_buffer(rfsock); + rfsock->channel = chan; + connections = g_list_append(connections, rfsock); + + return true; +} + +static void sdp_search_cb(sdp_list_t *recs, int err, gpointer data) +{ + struct rfcomm_sock *rfsock = data; + sdp_list_t *list; + int chan; + + DBG(""); + + if (err < 0) { + error("Unable to get SDP record: %s", strerror(-err)); + goto fail; + } + + if (!recs || !recs->data) { + error("No SDP records found"); + goto fail; + } + + for (list = recs; list != NULL; list = list->next) { + sdp_record_t *rec = list->data; + sdp_list_t *protos; + + if (sdp_get_access_protos(rec, &protos) < 0) { + error("Unable to get proto list"); + goto fail; + } + + chan = 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 (chan) + break; + } + + if (chan <= 0) { + error("Could not get RFCOMM channel %d", chan); + goto fail; + } + + DBG("Got RFCOMM channel %d", chan); + + if (do_rfcomm_connect(rfsock, chan)) + return; +fail: + cleanup_rfsock(rfsock); +} + +static uint8_t connect_rfcomm(const bdaddr_t *addr, int chan, + const uint8_t *uuid, uint8_t flags, + int *hal_sock) +{ + struct rfcomm_sock *rfsock; + char address[18]; + uuid_t uu; + char uuid_str[32]; + + sdp_uuid128_create(&uu, uuid); + sdp_uuid2strn(&uu, uuid_str, sizeof(uuid_str)); + ba2str(addr, address); + + DBG("addr %s chan %d flags 0x%02x uuid %s", address, chan, flags, + uuid_str); + + if ((!memcmp(uuid, zero_uuid, sizeof(zero_uuid)) && chan <= 0) || + !bacmp(addr, BDADDR_ANY)) { + error("Invalid rfcomm connect params"); + return HAL_STATUS_INVALID; + } + + rfsock = create_rfsock(-1, hal_sock); + if (!rfsock) + return HAL_STATUS_FAILED; + + DBG("rfsock %p jv_sock %d hal_sock %d", rfsock, rfsock->jv_sock, + *hal_sock); + + rfsock->sec_level = get_sec_level(flags); + + bacpy(&rfsock->dst, addr); + + if (!memcmp(uuid, zero_uuid, sizeof(zero_uuid))) { + if (!do_rfcomm_connect(rfsock, chan)) + goto failed; + } else { + + if (bt_search_service(&adapter_addr, &rfsock->dst, &uu, + sdp_search_cb, rfsock, NULL, 0) < 0) { + error("Failed to search SDP records"); + goto failed; + } + } + + return HAL_STATUS_SUCCESS; + +failed: + cleanup_rfsock(rfsock); + close(*hal_sock); + return HAL_STATUS_FAILED; +} + +static void handle_connect(const void *buf, uint16_t len) +{ + const struct hal_cmd_socket_connect *cmd = buf; + bdaddr_t bdaddr; + uint8_t status; + int hal_sock; + + DBG(""); + + android2bdaddr(cmd->bdaddr, &bdaddr); + + switch (cmd->type) { + case HAL_SOCK_RFCOMM: + status = connect_rfcomm(&bdaddr, cmd->channel, cmd->uuid, + cmd->flags, &hal_sock); + break; + case HAL_SOCK_SCO: + case HAL_SOCK_L2CAP: + status = HAL_STATUS_UNSUPPORTED; + break; + default: + status = HAL_STATUS_INVALID; + break; + } + + if (status != HAL_STATUS_SUCCESS) + goto failed; + + ipc_send_rsp_full(hal_ipc, HAL_SERVICE_ID_SOCKET, HAL_OP_SOCKET_CONNECT, + 0, NULL, hal_sock); + close(hal_sock); + return; + +failed: + ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_SOCKET, HAL_OP_SOCKET_CONNECT, + status); + +} + +static const struct ipc_handler cmd_handlers[] = { + /* HAL_OP_SOCKET_LISTEN */ + { handle_listen, false, sizeof(struct hal_cmd_socket_listen) }, + /* HAL_OP_SOCKET_CONNECT */ + { handle_connect, false, sizeof(struct hal_cmd_socket_connect) }, +}; + +void bt_socket_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode) +{ + size_t i; + + DBG(""); + + hal_mode = mode; + + /* + * make sure channels assigned for profiles are reserved and not used + * for app services + */ + for (i = 0; i < G_N_ELEMENTS(profiles); i++) + if (profiles[i].channel) + servers[profiles[i].channel].reserved = true; + + bacpy(&adapter_addr, addr); + + hal_ipc = ipc; + ipc_register(hal_ipc, HAL_SERVICE_ID_SOCKET, cmd_handlers, + G_N_ELEMENTS(cmd_handlers)); +} + +void bt_socket_unregister(void) +{ + int ch; + + DBG(""); + + test_sdp_cleanup(); + + g_list_free_full(connections, cleanup_rfsock); + + for (ch = 0; ch <= RFCOMM_CHANNEL_MAX; ch++) + if (servers[ch].rfsock) + cleanup_rfsock(servers[ch].rfsock); + + memset(servers, 0, sizeof(servers)); + + ipc_unregister(hal_ipc, HAL_SERVICE_ID_SOCKET); + hal_ipc = NULL; +} diff --git a/android/socket.h b/android/socket.h new file mode 100644 index 0000000..b0e78c6 --- /dev/null +++ b/android/socket.h @@ -0,0 +1,32 @@ +/* + * + * 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 hal_sock_connect_signal { + short size; + uint8_t bdaddr[6]; + int channel; + int status; +} __attribute__((packed)); + +void bt_socket_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode); +void bt_socket_unregister(void); diff --git a/android/system-emulator.c b/android/system-emulator.c new file mode 100644 index 0000000..edf3e89 --- /dev/null +++ b/android/system-emulator.c @@ -0,0 +1,251 @@ +/* + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef WAIT_ANY +#define WAIT_ANY (-1) +#endif + +#include "src/shared/mainloop.h" + +static char exec_dir[PATH_MAX]; + +static pid_t daemon_pid = -1; +static pid_t snoop_pid = -1; + +static void run_valgrind(char *prg_name) +{ + char *prg_argv[6]; + char *prg_envp[3]; + + prg_argv[0] = "/usr/bin/valgrind"; + prg_argv[1] = "--leak-check=full"; + prg_argv[2] = "--track-origins=yes"; + prg_argv[3] = prg_name; + prg_argv[4] = "-d"; + prg_argv[5] = 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(char *prg_name) +{ + char *prg_argv[3]; + char *prg_envp[1]; + + prg_argv[0] = prg_name; + prg_argv[1] = "-d"; + prg_argv[2] = NULL; + + prg_envp[0] = NULL; + + execve(prg_argv[0], prg_argv, prg_envp); +} + +static void ctl_start(void) +{ + char prg_name[PATH_MAX + 11]; + pid_t pid; + + snprintf(prg_name, sizeof(prg_name), "%s/%s", exec_dir, "bluetoothd"); + + printf("Starting %s\n", prg_name); + + pid = fork(); + if (pid < 0) { + perror("Failed to fork new process"); + return; + } + + if (pid == 0) { + run_valgrind(prg_name); + + /* Fallback to no valgrind if running with valgind failed */ + run_bluetoothd(prg_name); + exit(0); + } + + printf("New process %d created\n", pid); + + daemon_pid = pid; +} + +static void snoop_start(void) +{ + char prg_name[PATH_MAX + 17]; + char *prg_argv[3]; + char *prg_envp[1]; + pid_t pid; + + snprintf(prg_name, sizeof(prg_name), "%s/%s", exec_dir, + "bluetoothd-snoop"); + + prg_argv[0] = prg_name; + prg_argv[1] = "/tmp/btsnoop_hci.log"; + prg_argv[2] = NULL; + + prg_envp[0] = NULL; + + printf("Starting %s\n", prg_name); + + pid = fork(); + if (pid < 0) { + perror("Failed to fork new process"); + return; + } + + if (pid == 0) { + execve(prg_argv[0], prg_argv, prg_envp); + exit(0); + } + + printf("New process %d created\n", pid); + + snoop_pid = pid; +} + +static void snoop_stop(void) +{ + printf("Stoping %s/%s\n", exec_dir, "bluetoothd-snoop"); + + kill(snoop_pid, SIGTERM); +} + +static void system_socket_callback(int fd, uint32_t events, void *user_data) +{ + char buf[4096]; + ssize_t len; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(fd); + return; + } + + len = read(fd, buf, sizeof(buf)); + if (len < 0) + return; + + printf("Received %s\n", buf); + + if (!strcmp(buf, "bluetooth.start=daemon")) { + if (daemon_pid > 0) + return; + + ctl_start(); + } else if (!strcmp(buf, "bluetooth.start=snoop")) { + if (snoop_pid > 0) + return; + + snoop_start(); + } else if (!strcmp(buf, "bluetooth.stop=snoop")) { + if (snoop_pid > 0) + snoop_stop(); + } +} + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + case SIGCHLD: + while (1) { + pid_t pid; + int status; + + pid = waitpid(WAIT_ANY, &status, WNOHANG); + if (pid < 0 || pid == 0) + break; + + printf("Process %d terminated with status=%d\n", + pid, status); + + if (pid == daemon_pid) + daemon_pid = -1; + else if (pid == snoop_pid) + snoop_pid = -1; + } + break; + } +} + +int main(int argc, char *argv[]) +{ + const char SYSTEM_SOCKET_PATH[] = "\0android_system"; + struct sockaddr_un addr; + int fd; + + mainloop_init(); + + printf("Android system emulator ver %s\n", VERSION); + + snprintf(exec_dir, sizeof(exec_dir), "%s", dirname(argv[0])); + + fd = socket(PF_LOCAL, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + perror("Failed to create system socket"); + return EXIT_FAILURE; + } + + 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"); + close(fd); + return EXIT_FAILURE; + } + + mainloop_add_fd(fd, EPOLLIN, system_socket_callback, NULL, NULL); + + /* Make sure bluetoothd creates files with proper permissions */ + umask(0177); + + return mainloop_run_with_signal(signal_callback, NULL); +} diff --git a/android/system/audio.h b/android/system/audio.h new file mode 100644 index 0000000..d2da76d --- /dev/null +++ b/android/system/audio.h @@ -0,0 +1,1418 @@ +/* + * 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_CORE_H +#define ANDROID_AUDIO_CORE_H + +#include +#include +#include +#include +#include + +__BEGIN_DECLS + +#define popcount __builtin_popcount + +/* The enums were moved here mostly from + * frameworks/base/include/media/AudioSystem.h + */ + +/* device address used to refer to the standard remote submix */ +#define AUDIO_REMOTE_SUBMIX_DEVICE_ADDRESS "0" + +/* AudioFlinger and AudioPolicy services use I/O handles to identify audio sources and sinks */ +typedef int audio_io_handle_t; +#define AUDIO_IO_HANDLE_NONE 0 + +/* Audio stream types */ +typedef enum { + /* These values must kept in sync with + * frameworks/base/media/java/android/media/AudioSystem.java + */ + AUDIO_STREAM_DEFAULT = -1, + AUDIO_STREAM_MIN = 0, + AUDIO_STREAM_VOICE_CALL = 0, + AUDIO_STREAM_SYSTEM = 1, + AUDIO_STREAM_RING = 2, + AUDIO_STREAM_MUSIC = 3, + AUDIO_STREAM_ALARM = 4, + AUDIO_STREAM_NOTIFICATION = 5, + AUDIO_STREAM_BLUETOOTH_SCO = 6, + AUDIO_STREAM_ENFORCED_AUDIBLE = 7, /* Sounds that cannot be muted by user + * and must be routed to speaker + */ + AUDIO_STREAM_DTMF = 8, + AUDIO_STREAM_TTS = 9, + + AUDIO_STREAM_CNT, + AUDIO_STREAM_MAX = AUDIO_STREAM_CNT - 1, +} audio_stream_type_t; + +/* Do not change these values without updating their counterparts + * in frameworks/base/media/java/android/media/AudioAttributes.java + */ +typedef enum { + AUDIO_CONTENT_TYPE_UNKNOWN = 0, + AUDIO_CONTENT_TYPE_SPEECH = 1, + AUDIO_CONTENT_TYPE_MUSIC = 2, + AUDIO_CONTENT_TYPE_MOVIE = 3, + AUDIO_CONTENT_TYPE_SONIFICATION = 4, + + AUDIO_CONTENT_TYPE_CNT, + AUDIO_CONTENT_TYPE_MAX = AUDIO_CONTENT_TYPE_CNT - 1, +} audio_content_type_t; + +/* Do not change these values without updating their counterparts + * in frameworks/base/media/java/android/media/AudioAttributes.java + */ +typedef enum { + AUDIO_USAGE_UNKNOWN = 0, + AUDIO_USAGE_MEDIA = 1, + AUDIO_USAGE_VOICE_COMMUNICATION = 2, + AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING = 3, + AUDIO_USAGE_ALARM = 4, + AUDIO_USAGE_NOTIFICATION = 5, + AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE = 6, + AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7, + AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8, + AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9, + AUDIO_USAGE_NOTIFICATION_EVENT = 10, + AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY = 11, + AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12, + AUDIO_USAGE_ASSISTANCE_SONIFICATION = 13, + AUDIO_USAGE_GAME = 14, + + AUDIO_USAGE_CNT, + AUDIO_USAGE_MAX = AUDIO_USAGE_CNT - 1, +} audio_usage_t; + +typedef uint32_t audio_flags_mask_t; + +/* Do not change these values without updating their counterparts + * in frameworks/base/media/java/android/media/AudioAttributes.java + */ +enum { + AUDIO_FLAG_AUDIBILITY_ENFORCED = 0x1, + AUDIO_FLAG_SECURE = 0x2, + AUDIO_FLAG_SCO = 0x4, + AUDIO_FLAG_BEACON = 0x8, + AUDIO_FLAG_HW_AV_SYNC = 0x10, + AUDIO_FLAG_HW_HOTWORD = 0x20, +}; + +/* Do not change these values without updating their counterparts + * in frameworks/base/media/java/android/media/MediaRecorder.java, + * frameworks/av/services/audiopolicy/AudioPolicyService.cpp, + * and system/media/audio_effects/include/audio_effects/audio_effects_conf.h! + */ +typedef enum { + AUDIO_SOURCE_DEFAULT = 0, + AUDIO_SOURCE_MIC = 1, + AUDIO_SOURCE_VOICE_UPLINK = 2, + AUDIO_SOURCE_VOICE_DOWNLINK = 3, + AUDIO_SOURCE_VOICE_CALL = 4, + AUDIO_SOURCE_CAMCORDER = 5, + AUDIO_SOURCE_VOICE_RECOGNITION = 6, + AUDIO_SOURCE_VOICE_COMMUNICATION = 7, + AUDIO_SOURCE_REMOTE_SUBMIX = 8, /* Source for the mix to be presented remotely. */ + /* An example of remote presentation is Wifi Display */ + /* where a dongle attached to a TV can be used to */ + /* play the mix captured by this audio source. */ + AUDIO_SOURCE_CNT, + AUDIO_SOURCE_MAX = AUDIO_SOURCE_CNT - 1, + AUDIO_SOURCE_HOTWORD = 1999, /* A low-priority, preemptible audio source for + for background software hotword detection. + Same tuning as AUDIO_SOURCE_VOICE_RECOGNITION. + Used only internally to the framework. Not exposed + at the audio HAL. */ +} audio_source_t; + +/* Audio attributes */ +#define AUDIO_ATTRIBUTES_TAGS_MAX_SIZE 256 +typedef struct { + audio_content_type_t content_type; + audio_usage_t usage; + audio_source_t source; + audio_flags_mask_t flags; + char tags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE]; /* UTF8 */ +} audio_attributes_t; + +/* special audio session values + * (XXX: should this be living in the audio effects land?) + */ +typedef enum { + /* session for effects attached to a particular output stream + * (value must be less than 0) + */ + AUDIO_SESSION_OUTPUT_STAGE = -1, + + /* session for effects applied to output mix. These effects can + * be moved by audio policy manager to another output stream + * (value must be 0) + */ + AUDIO_SESSION_OUTPUT_MIX = 0, + + /* application does not specify an explicit session ID to be used, + * and requests a new session ID to be allocated + * TODO use unique values for AUDIO_SESSION_OUTPUT_MIX and AUDIO_SESSION_ALLOCATE, + * after all uses have been updated from 0 to the appropriate symbol, and have been tested. + */ + AUDIO_SESSION_ALLOCATE = 0, +} audio_session_t; + +/* a unique ID allocated by AudioFlinger for use as a audio_io_handle_t or audio_session_t */ +typedef int audio_unique_id_t; + +#define AUDIO_UNIQUE_ID_ALLOCATE AUDIO_SESSION_ALLOCATE + +/* Audio sub formats (see enum audio_format). */ + +/* PCM sub formats */ +typedef enum { + /* All of these are in native byte order */ + AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1, /* DO NOT CHANGE - PCM signed 16 bits */ + AUDIO_FORMAT_PCM_SUB_8_BIT = 0x2, /* DO NOT CHANGE - PCM unsigned 8 bits */ + AUDIO_FORMAT_PCM_SUB_32_BIT = 0x3, /* PCM signed .31 fixed point */ + AUDIO_FORMAT_PCM_SUB_8_24_BIT = 0x4, /* PCM signed 7.24 fixed point */ + AUDIO_FORMAT_PCM_SUB_FLOAT = 0x5, /* PCM single-precision floating point */ + AUDIO_FORMAT_PCM_SUB_24_BIT_PACKED = 0x6, /* PCM signed .23 fixed point packed in 3 bytes */ +} audio_format_pcm_sub_fmt_t; + +/* The audio_format_*_sub_fmt_t declarations are not currently used */ + +/* MP3 sub format field definition : can use 11 LSBs in the same way as MP3 + * frame header to specify bit rate, stereo mode, version... + */ +typedef enum { + AUDIO_FORMAT_MP3_SUB_NONE = 0x0, +} audio_format_mp3_sub_fmt_t; + +/* AMR NB/WB sub format field definition: specify frame block interleaving, + * bandwidth efficient or octet aligned, encoding mode for recording... + */ +typedef enum { + AUDIO_FORMAT_AMR_SUB_NONE = 0x0, +} audio_format_amr_sub_fmt_t; + +/* AAC sub format field definition: specify profile or bitrate for recording... */ +typedef enum { + AUDIO_FORMAT_AAC_SUB_MAIN = 0x1, + AUDIO_FORMAT_AAC_SUB_LC = 0x2, + AUDIO_FORMAT_AAC_SUB_SSR = 0x4, + AUDIO_FORMAT_AAC_SUB_LTP = 0x8, + AUDIO_FORMAT_AAC_SUB_HE_V1 = 0x10, + AUDIO_FORMAT_AAC_SUB_SCALABLE = 0x20, + AUDIO_FORMAT_AAC_SUB_ERLC = 0x40, + AUDIO_FORMAT_AAC_SUB_LD = 0x80, + AUDIO_FORMAT_AAC_SUB_HE_V2 = 0x100, + AUDIO_FORMAT_AAC_SUB_ELD = 0x200, +} audio_format_aac_sub_fmt_t; + +/* VORBIS sub format field definition: specify quality for recording... */ +typedef enum { + AUDIO_FORMAT_VORBIS_SUB_NONE = 0x0, +} audio_format_vorbis_sub_fmt_t; + +/* Audio format consists of a main format field (upper 8 bits) and a sub format + * field (lower 24 bits). + * + * The main format indicates the main codec type. The sub format field + * indicates options and parameters for each format. The sub format is mainly + * used for record to indicate for instance the requested bitrate or profile. + * It can also be used for certain formats to give informations not present in + * the encoded audio stream (e.g. octet alignement for AMR). + */ +typedef enum { + AUDIO_FORMAT_INVALID = 0xFFFFFFFFUL, + AUDIO_FORMAT_DEFAULT = 0, + AUDIO_FORMAT_PCM = 0x00000000UL, /* DO NOT CHANGE */ + AUDIO_FORMAT_MP3 = 0x01000000UL, + AUDIO_FORMAT_AMR_NB = 0x02000000UL, + AUDIO_FORMAT_AMR_WB = 0x03000000UL, + AUDIO_FORMAT_AAC = 0x04000000UL, + AUDIO_FORMAT_HE_AAC_V1 = 0x05000000UL, /* Deprecated, Use AUDIO_FORMAT_AAC_HE_V1*/ + AUDIO_FORMAT_HE_AAC_V2 = 0x06000000UL, /* Deprecated, Use AUDIO_FORMAT_AAC_HE_V2*/ + AUDIO_FORMAT_VORBIS = 0x07000000UL, + AUDIO_FORMAT_OPUS = 0x08000000UL, + AUDIO_FORMAT_AC3 = 0x09000000UL, + AUDIO_FORMAT_E_AC3 = 0x0A000000UL, + AUDIO_FORMAT_MAIN_MASK = 0xFF000000UL, + AUDIO_FORMAT_SUB_MASK = 0x00FFFFFFUL, + + /* Aliases */ + /* note != AudioFormat.ENCODING_PCM_16BIT */ + AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_16_BIT), + /* note != AudioFormat.ENCODING_PCM_8BIT */ + AUDIO_FORMAT_PCM_8_BIT = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_8_BIT), + AUDIO_FORMAT_PCM_32_BIT = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_32_BIT), + AUDIO_FORMAT_PCM_8_24_BIT = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_8_24_BIT), + AUDIO_FORMAT_PCM_FLOAT = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_FLOAT), + AUDIO_FORMAT_PCM_24_BIT_PACKED = (AUDIO_FORMAT_PCM | + AUDIO_FORMAT_PCM_SUB_24_BIT_PACKED), + AUDIO_FORMAT_AAC_MAIN = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_MAIN), + AUDIO_FORMAT_AAC_LC = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_LC), + AUDIO_FORMAT_AAC_SSR = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_SSR), + AUDIO_FORMAT_AAC_LTP = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_LTP), + AUDIO_FORMAT_AAC_HE_V1 = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_HE_V1), + AUDIO_FORMAT_AAC_SCALABLE = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_SCALABLE), + AUDIO_FORMAT_AAC_ERLC = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_ERLC), + AUDIO_FORMAT_AAC_LD = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_LD), + AUDIO_FORMAT_AAC_HE_V2 = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_HE_V2), + AUDIO_FORMAT_AAC_ELD = (AUDIO_FORMAT_AAC | + AUDIO_FORMAT_AAC_SUB_ELD), +} audio_format_t; + +/* For the channel mask for position assignment representation */ +enum { + +/* These can be a complete audio_channel_mask_t. */ + + AUDIO_CHANNEL_NONE = 0x0, + AUDIO_CHANNEL_INVALID = 0xC0000000, + +/* These can be the bits portion of an audio_channel_mask_t + * with representation AUDIO_CHANNEL_REPRESENTATION_POSITION. + * Using these bits as a complete audio_channel_mask_t is deprecated. + */ + + /* output channels */ + AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1, + AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2, + AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4, + AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8, + AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10, + AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20, + AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40, + AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80, + AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100, + AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200, + AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400, + AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800, + AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000, + AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000, + AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000, + AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000, + AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000, + AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000, + +/* TODO: should these be considered complete channel masks, or only bits? */ + + AUDIO_CHANNEL_OUT_MONO = AUDIO_CHANNEL_OUT_FRONT_LEFT, + AUDIO_CHANNEL_OUT_STEREO = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT), + AUDIO_CHANNEL_OUT_QUAD = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_BACK_LEFT | + AUDIO_CHANNEL_OUT_BACK_RIGHT), + AUDIO_CHANNEL_OUT_QUAD_BACK = AUDIO_CHANNEL_OUT_QUAD, + /* like AUDIO_CHANNEL_OUT_QUAD_BACK with *_SIDE_* instead of *_BACK_* */ + AUDIO_CHANNEL_OUT_QUAD_SIDE = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_SIDE_LEFT | + AUDIO_CHANNEL_OUT_SIDE_RIGHT), + AUDIO_CHANNEL_OUT_5POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_FRONT_CENTER | + AUDIO_CHANNEL_OUT_LOW_FREQUENCY | + AUDIO_CHANNEL_OUT_BACK_LEFT | + AUDIO_CHANNEL_OUT_BACK_RIGHT), + AUDIO_CHANNEL_OUT_5POINT1_BACK = AUDIO_CHANNEL_OUT_5POINT1, + /* like AUDIO_CHANNEL_OUT_5POINT1_BACK with *_SIDE_* instead of *_BACK_* */ + AUDIO_CHANNEL_OUT_5POINT1_SIDE = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_FRONT_CENTER | + AUDIO_CHANNEL_OUT_LOW_FREQUENCY | + AUDIO_CHANNEL_OUT_SIDE_LEFT | + AUDIO_CHANNEL_OUT_SIDE_RIGHT), + // matches the correct AudioFormat.CHANNEL_OUT_7POINT1_SURROUND definition for 7.1 + AUDIO_CHANNEL_OUT_7POINT1 = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_FRONT_CENTER | + AUDIO_CHANNEL_OUT_LOW_FREQUENCY | + AUDIO_CHANNEL_OUT_BACK_LEFT | + AUDIO_CHANNEL_OUT_BACK_RIGHT | + AUDIO_CHANNEL_OUT_SIDE_LEFT | + AUDIO_CHANNEL_OUT_SIDE_RIGHT), + AUDIO_CHANNEL_OUT_ALL = (AUDIO_CHANNEL_OUT_FRONT_LEFT | + AUDIO_CHANNEL_OUT_FRONT_RIGHT | + AUDIO_CHANNEL_OUT_FRONT_CENTER | + AUDIO_CHANNEL_OUT_LOW_FREQUENCY | + AUDIO_CHANNEL_OUT_BACK_LEFT | + AUDIO_CHANNEL_OUT_BACK_RIGHT | + AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER | + AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER | + AUDIO_CHANNEL_OUT_BACK_CENTER| + AUDIO_CHANNEL_OUT_SIDE_LEFT| + AUDIO_CHANNEL_OUT_SIDE_RIGHT| + AUDIO_CHANNEL_OUT_TOP_CENTER| + AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT| + AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER| + AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT| + AUDIO_CHANNEL_OUT_TOP_BACK_LEFT| + AUDIO_CHANNEL_OUT_TOP_BACK_CENTER| + AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT), + +/* These are bits only, not complete values */ + + /* input channels */ + AUDIO_CHANNEL_IN_LEFT = 0x4, + AUDIO_CHANNEL_IN_RIGHT = 0x8, + AUDIO_CHANNEL_IN_FRONT = 0x10, + AUDIO_CHANNEL_IN_BACK = 0x20, + AUDIO_CHANNEL_IN_LEFT_PROCESSED = 0x40, + AUDIO_CHANNEL_IN_RIGHT_PROCESSED = 0x80, + AUDIO_CHANNEL_IN_FRONT_PROCESSED = 0x100, + AUDIO_CHANNEL_IN_BACK_PROCESSED = 0x200, + AUDIO_CHANNEL_IN_PRESSURE = 0x400, + AUDIO_CHANNEL_IN_X_AXIS = 0x800, + AUDIO_CHANNEL_IN_Y_AXIS = 0x1000, + AUDIO_CHANNEL_IN_Z_AXIS = 0x2000, + AUDIO_CHANNEL_IN_VOICE_UPLINK = 0x4000, + AUDIO_CHANNEL_IN_VOICE_DNLINK = 0x8000, + +/* TODO: should these be considered complete channel masks, or only bits, or deprecated? */ + + AUDIO_CHANNEL_IN_MONO = AUDIO_CHANNEL_IN_FRONT, + AUDIO_CHANNEL_IN_STEREO = (AUDIO_CHANNEL_IN_LEFT | AUDIO_CHANNEL_IN_RIGHT), + AUDIO_CHANNEL_IN_FRONT_BACK = (AUDIO_CHANNEL_IN_FRONT | AUDIO_CHANNEL_IN_BACK), + AUDIO_CHANNEL_IN_ALL = (AUDIO_CHANNEL_IN_LEFT | + AUDIO_CHANNEL_IN_RIGHT | + AUDIO_CHANNEL_IN_FRONT | + AUDIO_CHANNEL_IN_BACK| + AUDIO_CHANNEL_IN_LEFT_PROCESSED | + AUDIO_CHANNEL_IN_RIGHT_PROCESSED | + AUDIO_CHANNEL_IN_FRONT_PROCESSED | + AUDIO_CHANNEL_IN_BACK_PROCESSED| + AUDIO_CHANNEL_IN_PRESSURE | + AUDIO_CHANNEL_IN_X_AXIS | + AUDIO_CHANNEL_IN_Y_AXIS | + AUDIO_CHANNEL_IN_Z_AXIS | + AUDIO_CHANNEL_IN_VOICE_UPLINK | + AUDIO_CHANNEL_IN_VOICE_DNLINK), +}; + +/* A channel mask per se only defines the presence or absence of a channel, not the order. + * But see AUDIO_INTERLEAVE_* below for the platform convention of order. + * + * audio_channel_mask_t is an opaque type and its internal layout should not + * be assumed as it may change in the future. + * Instead, always use the functions declared in this header to examine. + * + * These are the current representations: + * + * AUDIO_CHANNEL_REPRESENTATION_POSITION + * is a channel mask representation for position assignment. + * Each low-order bit corresponds to the spatial position of a transducer (output), + * or interpretation of channel (input). + * The user of a channel mask needs to know the context of whether it is for output or input. + * The constants AUDIO_CHANNEL_OUT_* or AUDIO_CHANNEL_IN_* apply to the bits portion. + * It is not permitted for no bits to be set. + * + * AUDIO_CHANNEL_REPRESENTATION_INDEX + * is a channel mask representation for index assignment. + * Each low-order bit corresponds to a selected channel. + * There is no platform interpretation of the various bits. + * There is no concept of output or input. + * It is not permitted for no bits to be set. + * + * All other representations are reserved for future use. + * + * Warning: current representation distinguishes between input and output, but this will not the be + * case in future revisions of the platform. Wherever there is an ambiguity between input and output + * that is currently resolved by checking the channel mask, the implementer should look for ways to + * fix it with additional information outside of the mask. + */ +typedef uint32_t audio_channel_mask_t; + +/* Maximum number of channels for all representations */ +#define AUDIO_CHANNEL_COUNT_MAX 30 + +/* log(2) of maximum number of representations, not part of public API */ +#define AUDIO_CHANNEL_REPRESENTATION_LOG2 2 + +/* Representations */ +typedef enum { + AUDIO_CHANNEL_REPRESENTATION_POSITION = 0, // must be zero for compatibility + // 1 is reserved for future use + AUDIO_CHANNEL_REPRESENTATION_INDEX = 2, + // 3 is reserved for future use +} audio_channel_representation_t; + +/* The return value is undefined if the channel mask is invalid. */ +static inline uint32_t audio_channel_mask_get_bits(audio_channel_mask_t channel) +{ + return channel & ((1 << AUDIO_CHANNEL_COUNT_MAX) - 1); +} + +/* The return value is undefined if the channel mask is invalid. */ +static inline audio_channel_representation_t audio_channel_mask_get_representation( + audio_channel_mask_t channel) +{ + // The right shift should be sufficient, but also "and" for safety in case mask is not 32 bits + return (audio_channel_representation_t) + ((channel >> AUDIO_CHANNEL_COUNT_MAX) & ((1 << AUDIO_CHANNEL_REPRESENTATION_LOG2) - 1)); +} + +/* Returns true if the channel mask is valid, + * or returns false for AUDIO_CHANNEL_NONE, AUDIO_CHANNEL_INVALID, and other invalid values. + * This function is unable to determine whether a channel mask for position assignment + * is invalid because an output mask has an invalid output bit set, + * or because an input mask has an invalid input bit set. + * All other APIs that take a channel mask assume that it is valid. + */ +static inline bool audio_channel_mask_is_valid(audio_channel_mask_t channel) +{ + uint32_t bits = audio_channel_mask_get_bits(channel); + audio_channel_representation_t representation = audio_channel_mask_get_representation(channel); + switch (representation) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + break; + default: + bits = 0; + break; + } + return bits != 0; +} + +/* Not part of public API */ +static inline audio_channel_mask_t audio_channel_mask_from_representation_and_bits( + audio_channel_representation_t representation, uint32_t bits) +{ + return (audio_channel_mask_t) ((representation << AUDIO_CHANNEL_COUNT_MAX) | bits); +} + +/* Expresses the convention when stereo audio samples are stored interleaved + * in an array. This should improve readability by allowing code to use + * symbolic indices instead of hard-coded [0] and [1]. + * + * For multi-channel beyond stereo, the platform convention is that channels + * are interleaved in order from least significant channel mask bit + * to most significant channel mask bit, with unused bits skipped. + * Any exceptions to this convention will be noted at the appropriate API. + */ +enum { + AUDIO_INTERLEAVE_LEFT = 0, + AUDIO_INTERLEAVE_RIGHT = 1, +}; + +typedef enum { + AUDIO_MODE_INVALID = -2, + AUDIO_MODE_CURRENT = -1, + AUDIO_MODE_NORMAL = 0, + AUDIO_MODE_RINGTONE = 1, + AUDIO_MODE_IN_CALL = 2, + AUDIO_MODE_IN_COMMUNICATION = 3, + + AUDIO_MODE_CNT, + AUDIO_MODE_MAX = AUDIO_MODE_CNT - 1, +} audio_mode_t; + +/* This enum is deprecated */ +typedef enum { + AUDIO_IN_ACOUSTICS_NONE = 0, + AUDIO_IN_ACOUSTICS_AGC_ENABLE = 0x0001, + AUDIO_IN_ACOUSTICS_AGC_DISABLE = 0, + AUDIO_IN_ACOUSTICS_NS_ENABLE = 0x0002, + AUDIO_IN_ACOUSTICS_NS_DISABLE = 0, + AUDIO_IN_ACOUSTICS_TX_IIR_ENABLE = 0x0004, + AUDIO_IN_ACOUSTICS_TX_DISABLE = 0, +} audio_in_acoustics_t; + +enum { + AUDIO_DEVICE_NONE = 0x0, + /* reserved bits */ + AUDIO_DEVICE_BIT_IN = 0x80000000, + AUDIO_DEVICE_BIT_DEFAULT = 0x40000000, + /* output devices */ + AUDIO_DEVICE_OUT_EARPIECE = 0x1, + AUDIO_DEVICE_OUT_SPEAKER = 0x2, + AUDIO_DEVICE_OUT_WIRED_HEADSET = 0x4, + AUDIO_DEVICE_OUT_WIRED_HEADPHONE = 0x8, + AUDIO_DEVICE_OUT_BLUETOOTH_SCO = 0x10, + AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20, + AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT = 0x40, + AUDIO_DEVICE_OUT_BLUETOOTH_A2DP = 0x80, + AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100, + AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200, + AUDIO_DEVICE_OUT_AUX_DIGITAL = 0x400, + AUDIO_DEVICE_OUT_HDMI = AUDIO_DEVICE_OUT_AUX_DIGITAL, + /* uses an analog connection (multiplexed over the USB connector pins for instance) */ + AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET = 0x800, + AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000, + /* USB accessory mode: your Android device is a USB device and the dock is a USB host */ + AUDIO_DEVICE_OUT_USB_ACCESSORY = 0x2000, + /* USB host mode: your Android device is a USB host and the dock is a USB device */ + AUDIO_DEVICE_OUT_USB_DEVICE = 0x4000, + AUDIO_DEVICE_OUT_REMOTE_SUBMIX = 0x8000, + /* Telephony voice TX path */ + AUDIO_DEVICE_OUT_TELEPHONY_TX = 0x10000, + /* Analog jack with line impedance detected */ + AUDIO_DEVICE_OUT_LINE = 0x20000, + /* HDMI Audio Return Channel */ + AUDIO_DEVICE_OUT_HDMI_ARC = 0x40000, + /* S/PDIF out */ + AUDIO_DEVICE_OUT_SPDIF = 0x80000, + /* FM transmitter out */ + AUDIO_DEVICE_OUT_FM = 0x100000, + /* Line out for av devices */ + AUDIO_DEVICE_OUT_AUX_LINE = 0x200000, + /* limited-output speaker device for acoustic safety */ + AUDIO_DEVICE_OUT_SPEAKER_SAFE = 0x400000, + AUDIO_DEVICE_OUT_DEFAULT = AUDIO_DEVICE_BIT_DEFAULT, + AUDIO_DEVICE_OUT_ALL = (AUDIO_DEVICE_OUT_EARPIECE | + AUDIO_DEVICE_OUT_SPEAKER | + AUDIO_DEVICE_OUT_WIRED_HEADSET | + AUDIO_DEVICE_OUT_WIRED_HEADPHONE | + AUDIO_DEVICE_OUT_BLUETOOTH_SCO | + AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET | + AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT | + AUDIO_DEVICE_OUT_BLUETOOTH_A2DP | + AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | + AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER | + AUDIO_DEVICE_OUT_HDMI | + AUDIO_DEVICE_OUT_ANLG_DOCK_HEADSET | + AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET | + AUDIO_DEVICE_OUT_USB_ACCESSORY | + AUDIO_DEVICE_OUT_USB_DEVICE | + AUDIO_DEVICE_OUT_REMOTE_SUBMIX | + AUDIO_DEVICE_OUT_TELEPHONY_TX | + AUDIO_DEVICE_OUT_LINE | + AUDIO_DEVICE_OUT_HDMI_ARC | + AUDIO_DEVICE_OUT_SPDIF | + AUDIO_DEVICE_OUT_FM | + AUDIO_DEVICE_OUT_AUX_LINE | + AUDIO_DEVICE_OUT_SPEAKER_SAFE | + AUDIO_DEVICE_OUT_DEFAULT), + AUDIO_DEVICE_OUT_ALL_A2DP = (AUDIO_DEVICE_OUT_BLUETOOTH_A2DP | + AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | + AUDIO_DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER), + AUDIO_DEVICE_OUT_ALL_SCO = (AUDIO_DEVICE_OUT_BLUETOOTH_SCO | + AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET | + AUDIO_DEVICE_OUT_BLUETOOTH_SCO_CARKIT), + AUDIO_DEVICE_OUT_ALL_USB = (AUDIO_DEVICE_OUT_USB_ACCESSORY | + AUDIO_DEVICE_OUT_USB_DEVICE), + + /* input devices */ + AUDIO_DEVICE_IN_COMMUNICATION = AUDIO_DEVICE_BIT_IN | 0x1, + AUDIO_DEVICE_IN_AMBIENT = AUDIO_DEVICE_BIT_IN | 0x2, + AUDIO_DEVICE_IN_BUILTIN_MIC = AUDIO_DEVICE_BIT_IN | 0x4, + AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET = AUDIO_DEVICE_BIT_IN | 0x8, + AUDIO_DEVICE_IN_WIRED_HEADSET = AUDIO_DEVICE_BIT_IN | 0x10, + AUDIO_DEVICE_IN_AUX_DIGITAL = AUDIO_DEVICE_BIT_IN | 0x20, + AUDIO_DEVICE_IN_HDMI = AUDIO_DEVICE_IN_AUX_DIGITAL, + /* Telephony voice RX path */ + AUDIO_DEVICE_IN_VOICE_CALL = AUDIO_DEVICE_BIT_IN | 0x40, + AUDIO_DEVICE_IN_TELEPHONY_RX = AUDIO_DEVICE_IN_VOICE_CALL, + AUDIO_DEVICE_IN_BACK_MIC = AUDIO_DEVICE_BIT_IN | 0x80, + AUDIO_DEVICE_IN_REMOTE_SUBMIX = AUDIO_DEVICE_BIT_IN | 0x100, + AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET = AUDIO_DEVICE_BIT_IN | 0x200, + AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET = AUDIO_DEVICE_BIT_IN | 0x400, + AUDIO_DEVICE_IN_USB_ACCESSORY = AUDIO_DEVICE_BIT_IN | 0x800, + AUDIO_DEVICE_IN_USB_DEVICE = AUDIO_DEVICE_BIT_IN | 0x1000, + /* FM tuner input */ + AUDIO_DEVICE_IN_FM_TUNER = AUDIO_DEVICE_BIT_IN | 0x2000, + /* TV tuner input */ + AUDIO_DEVICE_IN_TV_TUNER = AUDIO_DEVICE_BIT_IN | 0x4000, + /* Analog jack with line impedance detected */ + AUDIO_DEVICE_IN_LINE = AUDIO_DEVICE_BIT_IN | 0x8000, + /* S/PDIF in */ + AUDIO_DEVICE_IN_SPDIF = AUDIO_DEVICE_BIT_IN | 0x10000, + AUDIO_DEVICE_IN_BLUETOOTH_A2DP = AUDIO_DEVICE_BIT_IN | 0x20000, + AUDIO_DEVICE_IN_LOOPBACK = AUDIO_DEVICE_BIT_IN | 0x40000, + AUDIO_DEVICE_IN_DEFAULT = AUDIO_DEVICE_BIT_IN | AUDIO_DEVICE_BIT_DEFAULT, + + AUDIO_DEVICE_IN_ALL = (AUDIO_DEVICE_IN_COMMUNICATION | + AUDIO_DEVICE_IN_AMBIENT | + AUDIO_DEVICE_IN_BUILTIN_MIC | + AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET | + AUDIO_DEVICE_IN_WIRED_HEADSET | + AUDIO_DEVICE_IN_HDMI | + AUDIO_DEVICE_IN_TELEPHONY_RX | + AUDIO_DEVICE_IN_BACK_MIC | + AUDIO_DEVICE_IN_REMOTE_SUBMIX | + AUDIO_DEVICE_IN_ANLG_DOCK_HEADSET | + AUDIO_DEVICE_IN_DGTL_DOCK_HEADSET | + AUDIO_DEVICE_IN_USB_ACCESSORY | + AUDIO_DEVICE_IN_USB_DEVICE | + AUDIO_DEVICE_IN_FM_TUNER | + AUDIO_DEVICE_IN_TV_TUNER | + AUDIO_DEVICE_IN_LINE | + AUDIO_DEVICE_IN_SPDIF | + AUDIO_DEVICE_IN_BLUETOOTH_A2DP | + AUDIO_DEVICE_IN_LOOPBACK | + AUDIO_DEVICE_IN_DEFAULT), + AUDIO_DEVICE_IN_ALL_SCO = AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET, + AUDIO_DEVICE_IN_ALL_USB = (AUDIO_DEVICE_IN_USB_ACCESSORY | + AUDIO_DEVICE_IN_USB_DEVICE), +}; + +typedef uint32_t audio_devices_t; + +/* the audio output flags serve two purposes: + * - when an AudioTrack is created they indicate a "wish" to be connected to an + * output stream with attributes corresponding to the specified flags + * - when present in an output profile descriptor listed for a particular audio + * hardware module, they indicate that an output stream can be opened that + * supports the attributes indicated by the flags. + * the audio policy manager will try to match the flags in the request + * (when getOuput() is called) to an available output stream. + */ +typedef enum { + AUDIO_OUTPUT_FLAG_NONE = 0x0, // no attributes + AUDIO_OUTPUT_FLAG_DIRECT = 0x1, // this output directly connects a track + // to one output stream: no software mixer + AUDIO_OUTPUT_FLAG_PRIMARY = 0x2, // this output is the primary output of + // the device. It is unique and must be + // present. It is opened by default and + // receives routing, audio mode and volume + // controls related to voice calls. + AUDIO_OUTPUT_FLAG_FAST = 0x4, // output supports "fast tracks", + // defined elsewhere + AUDIO_OUTPUT_FLAG_DEEP_BUFFER = 0x8, // use deep audio buffers + AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD = 0x10, // offload playback of compressed + // streams to hardware codec + AUDIO_OUTPUT_FLAG_NON_BLOCKING = 0x20, // use non-blocking write + AUDIO_OUTPUT_FLAG_HW_AV_SYNC = 0x40 // output uses a hardware A/V synchronization source +} audio_output_flags_t; + +/* The audio input flags are analogous to audio output flags. + * Currently they are used only when an AudioRecord is created, + * to indicate a preference to be connected to an input stream with + * attributes corresponding to the specified flags. + */ +typedef enum { + AUDIO_INPUT_FLAG_NONE = 0x0, // no attributes + AUDIO_INPUT_FLAG_FAST = 0x1, // prefer an input that supports "fast tracks" + AUDIO_INPUT_FLAG_HW_HOTWORD = 0x2, // prefer an input that captures from hw hotword source +} audio_input_flags_t; + +/* Additional information about compressed streams offloaded to + * hardware playback + * The version and size fields must be initialized by the caller by using + * one of the constants defined here. + */ +typedef struct { + uint16_t version; // version of the info structure + uint16_t size; // total size of the structure including version and size + uint32_t sample_rate; // sample rate in Hz + audio_channel_mask_t channel_mask; // channel mask + audio_format_t format; // audio format + audio_stream_type_t stream_type; // stream type + uint32_t bit_rate; // bit rate in bits per second + int64_t duration_us; // duration in microseconds, -1 if unknown + bool has_video; // true if stream is tied to a video stream + bool is_streaming; // true if streaming, false if local playback +} audio_offload_info_t; + +#define AUDIO_MAKE_OFFLOAD_INFO_VERSION(maj,min) \ + ((((maj) & 0xff) << 8) | ((min) & 0xff)) + +#define AUDIO_OFFLOAD_INFO_VERSION_0_1 AUDIO_MAKE_OFFLOAD_INFO_VERSION(0, 1) +#define AUDIO_OFFLOAD_INFO_VERSION_CURRENT AUDIO_OFFLOAD_INFO_VERSION_0_1 + +static const audio_offload_info_t AUDIO_INFO_INITIALIZER = { + version: AUDIO_OFFLOAD_INFO_VERSION_CURRENT, + size: sizeof(audio_offload_info_t), + sample_rate: 0, + channel_mask: 0, + format: AUDIO_FORMAT_DEFAULT, + stream_type: AUDIO_STREAM_VOICE_CALL, + bit_rate: 0, + duration_us: 0, + has_video: false, + is_streaming: false +}; + +/* common audio stream configuration parameters + * You should memset() the entire structure to zero before use to + * ensure forward compatibility + */ +struct audio_config { + uint32_t sample_rate; + audio_channel_mask_t channel_mask; + audio_format_t format; + audio_offload_info_t offload_info; + size_t frame_count; +}; +typedef struct audio_config audio_config_t; + +static const audio_config_t AUDIO_CONFIG_INITIALIZER = { + sample_rate: 0, + channel_mask: AUDIO_CHANNEL_NONE, + format: AUDIO_FORMAT_DEFAULT, + offload_info: { + version: AUDIO_OFFLOAD_INFO_VERSION_CURRENT, + size: sizeof(audio_offload_info_t), + sample_rate: 0, + channel_mask: 0, + format: AUDIO_FORMAT_DEFAULT, + stream_type: AUDIO_STREAM_VOICE_CALL, + bit_rate: 0, + duration_us: 0, + has_video: false, + is_streaming: false + }, + frame_count: 0, +}; + + +/* audio hw module handle functions or structures referencing a module */ +typedef int audio_module_handle_t; + +/****************************** + * Volume control + *****************************/ + +/* If the audio hardware supports gain control on some audio paths, + * the platform can expose them in the audio_policy.conf file. The audio HAL + * will then implement gain control functions that will use the following data + * structures. */ + +/* Type of gain control exposed by an audio port */ +#define AUDIO_GAIN_MODE_JOINT 0x1 /* supports joint channel gain control */ +#define AUDIO_GAIN_MODE_CHANNELS 0x2 /* supports separate channel gain control */ +#define AUDIO_GAIN_MODE_RAMP 0x4 /* supports gain ramps */ + +typedef uint32_t audio_gain_mode_t; + + +/* An audio_gain struct is a representation of a gain stage. + * A gain stage is always attached to an audio port. */ +struct audio_gain { + audio_gain_mode_t mode; /* e.g. AUDIO_GAIN_MODE_JOINT */ + audio_channel_mask_t channel_mask; /* channels which gain an be controlled. + N/A if AUDIO_GAIN_MODE_CHANNELS is not supported */ + int min_value; /* minimum gain value in millibels */ + int max_value; /* maximum gain value in millibels */ + int default_value; /* default gain value in millibels */ + unsigned int step_value; /* gain step in millibels */ + unsigned int min_ramp_ms; /* minimum ramp duration in ms */ + unsigned int max_ramp_ms; /* maximum ramp duration in ms */ +}; + +/* The gain configuration structure is used to get or set the gain values of a + * given port */ +struct audio_gain_config { + int index; /* index of the corresponding audio_gain in the + audio_port gains[] table */ + audio_gain_mode_t mode; /* mode requested for this command */ + audio_channel_mask_t channel_mask; /* channels which gain value follows. + N/A in joint mode */ + int values[sizeof(audio_channel_mask_t) * 8]; /* gain values in millibels + for each channel ordered from LSb to MSb in + channel mask. The number of values is 1 in joint + mode or popcount(channel_mask) */ + unsigned int ramp_duration_ms; /* ramp duration in ms */ +}; + +/****************************** + * Routing control + *****************************/ + +/* Types defined here are used to describe an audio source or sink at internal + * framework interfaces (audio policy, patch panel) or at the audio HAL. + * Sink and sources are grouped in a concept of “audio port” representing an + * audio end point at the edge of the system managed by the module exposing + * the interface. */ + +/* Audio port role: either source or sink */ +typedef enum { + AUDIO_PORT_ROLE_NONE, + AUDIO_PORT_ROLE_SOURCE, + AUDIO_PORT_ROLE_SINK, +} audio_port_role_t; + +/* Audio port type indicates if it is a session (e.g AudioTrack), + * a mix (e.g PlaybackThread output) or a physical device + * (e.g AUDIO_DEVICE_OUT_SPEAKER) */ +typedef enum { + AUDIO_PORT_TYPE_NONE, + AUDIO_PORT_TYPE_DEVICE, + AUDIO_PORT_TYPE_MIX, + AUDIO_PORT_TYPE_SESSION, +} audio_port_type_t; + +/* Each port has a unique ID or handle allocated by policy manager */ +typedef int audio_port_handle_t; +#define AUDIO_PORT_HANDLE_NONE 0 + + +/* maximum audio device address length */ +#define AUDIO_DEVICE_MAX_ADDRESS_LEN 32 + +/* extension for audio port configuration structure when the audio port is a + * hardware device */ +struct audio_port_config_device_ext { + audio_module_handle_t hw_module; /* module the device is attached to */ + audio_devices_t type; /* device type (e.g AUDIO_DEVICE_OUT_SPEAKER) */ + char address[AUDIO_DEVICE_MAX_ADDRESS_LEN]; /* device address. "" if N/A */ +}; + +/* extension for audio port configuration structure when the audio port is a + * sub mix */ +struct audio_port_config_mix_ext { + audio_module_handle_t hw_module; /* module the stream is attached to */ + audio_io_handle_t handle; /* I/O handle of the input/output stream */ + union { + //TODO: change use case for output streams: use strategy and mixer attributes + audio_stream_type_t stream; + audio_source_t source; + } usecase; +}; + +/* extension for audio port configuration structure when the audio port is an + * audio session */ +struct audio_port_config_session_ext { + audio_session_t session; /* audio session */ +}; + +/* Flags indicating which fields are to be considered in struct audio_port_config */ +#define AUDIO_PORT_CONFIG_SAMPLE_RATE 0x1 +#define AUDIO_PORT_CONFIG_CHANNEL_MASK 0x2 +#define AUDIO_PORT_CONFIG_FORMAT 0x4 +#define AUDIO_PORT_CONFIG_GAIN 0x8 +#define AUDIO_PORT_CONFIG_ALL (AUDIO_PORT_CONFIG_SAMPLE_RATE | \ + AUDIO_PORT_CONFIG_CHANNEL_MASK | \ + AUDIO_PORT_CONFIG_FORMAT | \ + AUDIO_PORT_CONFIG_GAIN) + +/* audio port configuration structure used to specify a particular configuration of + * an audio port */ +struct audio_port_config { + audio_port_handle_t id; /* port unique ID */ + audio_port_role_t role; /* sink or source */ + audio_port_type_t type; /* device, mix ... */ + unsigned int config_mask; /* e.g AUDIO_PORT_CONFIG_ALL */ + unsigned int sample_rate; /* sampling rate in Hz */ + audio_channel_mask_t channel_mask; /* channel mask if applicable */ + audio_format_t format; /* format if applicable */ + struct audio_gain_config gain; /* gain to apply if applicable */ + union { + struct audio_port_config_device_ext device; /* device specific info */ + struct audio_port_config_mix_ext mix; /* mix specific info */ + struct audio_port_config_session_ext session; /* session specific info */ + } ext; +}; + + +/* max number of sampling rates in audio port */ +#define AUDIO_PORT_MAX_SAMPLING_RATES 16 +/* max number of channel masks in audio port */ +#define AUDIO_PORT_MAX_CHANNEL_MASKS 16 +/* max number of audio formats in audio port */ +#define AUDIO_PORT_MAX_FORMATS 16 +/* max number of gain controls in audio port */ +#define AUDIO_PORT_MAX_GAINS 16 + +/* extension for audio port structure when the audio port is a hardware device */ +struct audio_port_device_ext { + audio_module_handle_t hw_module; /* module the device is attached to */ + audio_devices_t type; /* device type (e.g AUDIO_DEVICE_OUT_SPEAKER) */ + char address[AUDIO_DEVICE_MAX_ADDRESS_LEN]; +}; + +/* Latency class of the audio mix */ +typedef enum { + AUDIO_LATENCY_LOW, + AUDIO_LATENCY_NORMAL, +} audio_mix_latency_class_t; + +/* extension for audio port structure when the audio port is a sub mix */ +struct audio_port_mix_ext { + audio_module_handle_t hw_module; /* module the stream is attached to */ + audio_io_handle_t handle; /* I/O handle of the input.output stream */ + audio_mix_latency_class_t latency_class; /* latency class */ + // other attributes: routing strategies +}; + +/* extension for audio port structure when the audio port is an audio session */ +struct audio_port_session_ext { + audio_session_t session; /* audio session */ +}; + + +struct audio_port { + audio_port_handle_t id; /* port unique ID */ + audio_port_role_t role; /* sink or source */ + audio_port_type_t type; /* device, mix ... */ + unsigned int num_sample_rates; /* number of sampling rates in following array */ + unsigned int sample_rates[AUDIO_PORT_MAX_SAMPLING_RATES]; + unsigned int num_channel_masks; /* number of channel masks in following array */ + audio_channel_mask_t channel_masks[AUDIO_PORT_MAX_CHANNEL_MASKS]; + unsigned int num_formats; /* number of formats in following array */ + audio_format_t formats[AUDIO_PORT_MAX_FORMATS]; + unsigned int num_gains; /* number of gains in following array */ + struct audio_gain gains[AUDIO_PORT_MAX_GAINS]; + struct audio_port_config active_config; /* current audio port configuration */ + union { + struct audio_port_device_ext device; + struct audio_port_mix_ext mix; + struct audio_port_session_ext session; + } ext; +}; + +/* An audio patch represents a connection between one or more source ports and + * one or more sink ports. Patches are connected and disconnected by audio policy manager or by + * applications via framework APIs. + * Each patch is identified by a handle at the interface used to create that patch. For instance, + * when a patch is created by the audio HAL, the HAL allocates and returns a handle. + * This handle is unique to a given audio HAL hardware module. + * But the same patch receives another system wide unique handle allocated by the framework. + * This unique handle is used for all transactions inside the framework. + */ +typedef int audio_patch_handle_t; +#define AUDIO_PATCH_HANDLE_NONE 0 + +#define AUDIO_PATCH_PORTS_MAX 16 + +struct audio_patch { + audio_patch_handle_t id; /* patch unique ID */ + unsigned int num_sources; /* number of sources in following array */ + struct audio_port_config sources[AUDIO_PATCH_PORTS_MAX]; + unsigned int num_sinks; /* number of sinks in following array */ + struct audio_port_config sinks[AUDIO_PATCH_PORTS_MAX]; +}; + + + +/* a HW synchronization source returned by the audio HAL */ +typedef uint32_t audio_hw_sync_t; + +/* an invalid HW synchronization source indicating an error */ +#define AUDIO_HW_SYNC_INVALID 0 + +static inline bool audio_is_output_device(audio_devices_t device) +{ + if (((device & AUDIO_DEVICE_BIT_IN) == 0) && + (popcount(device) == 1) && ((device & ~AUDIO_DEVICE_OUT_ALL) == 0)) + return true; + else + return false; +} + +static inline bool audio_is_input_device(audio_devices_t device) +{ + if ((device & AUDIO_DEVICE_BIT_IN) != 0) { + device &= ~AUDIO_DEVICE_BIT_IN; + if ((popcount(device) == 1) && ((device & ~AUDIO_DEVICE_IN_ALL) == 0)) + return true; + } + return false; +} + +static inline bool audio_is_output_devices(audio_devices_t device) +{ + return (device & AUDIO_DEVICE_BIT_IN) == 0; +} + +static inline bool audio_is_a2dp_in_device(audio_devices_t device) +{ + if ((device & AUDIO_DEVICE_BIT_IN) != 0) { + device &= ~AUDIO_DEVICE_BIT_IN; + if ((popcount(device) == 1) && (device & AUDIO_DEVICE_IN_BLUETOOTH_A2DP)) + return true; + } + return false; +} + +static inline bool audio_is_a2dp_out_device(audio_devices_t device) +{ + if ((popcount(device) == 1) && (device & AUDIO_DEVICE_OUT_ALL_A2DP)) + return true; + else + return false; +} + +// Deprecated - use audio_is_a2dp_out_device() instead +static inline bool audio_is_a2dp_device(audio_devices_t device) +{ + return audio_is_a2dp_out_device(device); +} + +static inline bool audio_is_bluetooth_sco_device(audio_devices_t device) +{ + if ((device & AUDIO_DEVICE_BIT_IN) == 0) { + if ((popcount(device) == 1) && ((device & ~AUDIO_DEVICE_OUT_ALL_SCO) == 0)) + return true; + } else { + device &= ~AUDIO_DEVICE_BIT_IN; + if ((popcount(device) == 1) && ((device & ~AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET) == 0)) + return true; + } + + return false; +} + +static inline bool audio_is_usb_out_device(audio_devices_t device) +{ + return ((popcount(device) == 1) && (device & AUDIO_DEVICE_OUT_ALL_USB)); +} + +static inline bool audio_is_usb_in_device(audio_devices_t device) +{ + if ((device & AUDIO_DEVICE_BIT_IN) != 0) { + device &= ~AUDIO_DEVICE_BIT_IN; + if (popcount(device) == 1 && (device & AUDIO_DEVICE_IN_ALL_USB) != 0) + return true; + } + return false; +} + +/* OBSOLETE - use audio_is_usb_out_device() instead. */ +static inline bool audio_is_usb_device(audio_devices_t device) +{ + return audio_is_usb_out_device(device); +} + +static inline bool audio_is_remote_submix_device(audio_devices_t device) +{ + if ((device & AUDIO_DEVICE_OUT_REMOTE_SUBMIX) == AUDIO_DEVICE_OUT_REMOTE_SUBMIX + || (device & AUDIO_DEVICE_IN_REMOTE_SUBMIX) == AUDIO_DEVICE_IN_REMOTE_SUBMIX) + return true; + else + return false; +} + +/* Returns true if: + * representation is valid, and + * there is at least one channel bit set which _could_ correspond to an input channel, and + * there are no channel bits set which could _not_ correspond to an input channel. + * Otherwise returns false. + */ +static inline bool audio_is_input_channel(audio_channel_mask_t channel) +{ + uint32_t bits = audio_channel_mask_get_bits(channel); + switch (audio_channel_mask_get_representation(channel)) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + if (bits & ~AUDIO_CHANNEL_IN_ALL) { + bits = 0; + } + // fall through + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + return bits != 0; + default: + return false; + } +} + +/* Returns true if: + * representation is valid, and + * there is at least one channel bit set which _could_ correspond to an output channel, and + * there are no channel bits set which could _not_ correspond to an output channel. + * Otherwise returns false. + */ +static inline bool audio_is_output_channel(audio_channel_mask_t channel) +{ + uint32_t bits = audio_channel_mask_get_bits(channel); + switch (audio_channel_mask_get_representation(channel)) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + if (bits & ~AUDIO_CHANNEL_OUT_ALL) { + bits = 0; + } + // fall through + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + return bits != 0; + default: + return false; + } +} + +/* Returns the number of channels from an input channel mask, + * used in the context of audio input or recording. + * If a channel bit is set which could _not_ correspond to an input channel, + * it is excluded from the count. + * Returns zero if the representation is invalid. + */ +static inline uint32_t audio_channel_count_from_in_mask(audio_channel_mask_t channel) +{ + uint32_t bits = audio_channel_mask_get_bits(channel); + switch (audio_channel_mask_get_representation(channel)) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + // TODO: We can now merge with from_out_mask and remove anding + bits &= AUDIO_CHANNEL_IN_ALL; + // fall through + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + return popcount(bits); + default: + return 0; + } +} + +/* Returns the number of channels from an output channel mask, + * used in the context of audio output or playback. + * If a channel bit is set which could _not_ correspond to an output channel, + * it is excluded from the count. + * Returns zero if the representation is invalid. + */ +static inline uint32_t audio_channel_count_from_out_mask(audio_channel_mask_t channel) +{ + uint32_t bits = audio_channel_mask_get_bits(channel); + switch (audio_channel_mask_get_representation(channel)) { + case AUDIO_CHANNEL_REPRESENTATION_POSITION: + // TODO: We can now merge with from_in_mask and remove anding + bits &= AUDIO_CHANNEL_OUT_ALL; + // fall through + case AUDIO_CHANNEL_REPRESENTATION_INDEX: + return popcount(bits); + default: + return 0; + } +} + +/* Derive an output channel mask for position assignment from a channel count. + * This is to be used when the content channel mask is unknown. The 1, 2, 4, 5, 6, 7 and 8 channel + * cases are mapped to the standard game/home-theater layouts, but note that 4 is mapped to quad, + * and not stereo + FC + mono surround. A channel count of 3 is arbitrarily mapped to stereo + FC + * for continuity with stereo. + * Returns the matching channel mask, + * or AUDIO_CHANNEL_NONE if the channel count is zero, + * or AUDIO_CHANNEL_INVALID if the channel count exceeds that of the + * configurations for which a default output channel mask is defined. + */ +static inline audio_channel_mask_t audio_channel_out_mask_from_count(uint32_t channel_count) +{ + uint32_t bits; + switch (channel_count) { + case 0: + return AUDIO_CHANNEL_NONE; + case 1: + bits = AUDIO_CHANNEL_OUT_MONO; + break; + case 2: + bits = AUDIO_CHANNEL_OUT_STEREO; + break; + case 3: + bits = AUDIO_CHANNEL_OUT_STEREO | AUDIO_CHANNEL_OUT_FRONT_CENTER; + break; + case 4: // 4.0 + bits = AUDIO_CHANNEL_OUT_QUAD; + break; + case 5: // 5.0 + bits = AUDIO_CHANNEL_OUT_QUAD | AUDIO_CHANNEL_OUT_FRONT_CENTER; + break; + case 6: // 5.1 + bits = AUDIO_CHANNEL_OUT_5POINT1; + break; + case 7: // 6.1 + bits = AUDIO_CHANNEL_OUT_5POINT1 | AUDIO_CHANNEL_OUT_BACK_CENTER; + break; + case 8: + bits = AUDIO_CHANNEL_OUT_7POINT1; + break; + default: + return AUDIO_CHANNEL_INVALID; + } + return audio_channel_mask_from_representation_and_bits( + AUDIO_CHANNEL_REPRESENTATION_POSITION, bits); +} + +/* Derive an input channel mask for position assignment from a channel count. + * Currently handles only mono and stereo. + * Returns the matching channel mask, + * or AUDIO_CHANNEL_NONE if the channel count is zero, + * or AUDIO_CHANNEL_INVALID if the channel count exceeds that of the + * configurations for which a default input channel mask is defined. + */ +static inline audio_channel_mask_t audio_channel_in_mask_from_count(uint32_t channel_count) +{ + uint32_t bits; + switch (channel_count) { + case 0: + return AUDIO_CHANNEL_NONE; + case 1: + bits = AUDIO_CHANNEL_IN_MONO; + break; + case 2: + bits = AUDIO_CHANNEL_IN_STEREO; + break; + default: + return AUDIO_CHANNEL_INVALID; + } + return audio_channel_mask_from_representation_and_bits( + AUDIO_CHANNEL_REPRESENTATION_POSITION, bits); +} + +/* Derive a channel mask for index assignment from a channel count. + * Returns the matching channel mask, + * or AUDIO_CHANNEL_NONE if the channel count is zero, + * or AUDIO_CHANNEL_INVALID if the channel count exceeds AUDIO_CHANNEL_COUNT_MAX. + */ +static inline audio_channel_mask_t audio_channel_mask_for_index_assignment_from_count( + uint32_t channel_count) +{ + uint32_t bits; + + if (channel_count == 0) { + return AUDIO_CHANNEL_NONE; + } + if (channel_count > AUDIO_CHANNEL_COUNT_MAX) { + return AUDIO_CHANNEL_INVALID; + } + bits = (1 << channel_count) - 1; + return audio_channel_mask_from_representation_and_bits( + AUDIO_CHANNEL_REPRESENTATION_INDEX, bits); +} + +static inline bool audio_is_valid_format(audio_format_t format) +{ + switch (format & AUDIO_FORMAT_MAIN_MASK) { + case AUDIO_FORMAT_PCM: + switch (format) { + case AUDIO_FORMAT_PCM_16_BIT: + case AUDIO_FORMAT_PCM_8_BIT: + case AUDIO_FORMAT_PCM_32_BIT: + case AUDIO_FORMAT_PCM_8_24_BIT: + case AUDIO_FORMAT_PCM_FLOAT: + case AUDIO_FORMAT_PCM_24_BIT_PACKED: + return true; + case AUDIO_FORMAT_INVALID: + case AUDIO_FORMAT_DEFAULT: + case AUDIO_FORMAT_MP3: + case AUDIO_FORMAT_AMR_NB: + case AUDIO_FORMAT_AMR_WB: + case AUDIO_FORMAT_AAC: + case AUDIO_FORMAT_HE_AAC_V1: + case AUDIO_FORMAT_HE_AAC_V2: + case AUDIO_FORMAT_VORBIS: + case AUDIO_FORMAT_OPUS: + case AUDIO_FORMAT_AC3: + case AUDIO_FORMAT_E_AC3: + case AUDIO_FORMAT_MAIN_MASK: + case AUDIO_FORMAT_SUB_MASK: + case AUDIO_FORMAT_AAC_MAIN: + case AUDIO_FORMAT_AAC_LC: + case AUDIO_FORMAT_AAC_SSR: + case AUDIO_FORMAT_AAC_LTP: + case AUDIO_FORMAT_AAC_HE_V1: + case AUDIO_FORMAT_AAC_SCALABLE: + case AUDIO_FORMAT_AAC_ERLC: + case AUDIO_FORMAT_AAC_LD: + case AUDIO_FORMAT_AAC_HE_V2: + case AUDIO_FORMAT_AAC_ELD: + default: + return false; + } + /* not reached */ + case AUDIO_FORMAT_MP3: + case AUDIO_FORMAT_AMR_NB: + case AUDIO_FORMAT_AMR_WB: + case AUDIO_FORMAT_AAC: + case AUDIO_FORMAT_HE_AAC_V1: + case AUDIO_FORMAT_HE_AAC_V2: + case AUDIO_FORMAT_VORBIS: + case AUDIO_FORMAT_OPUS: + case AUDIO_FORMAT_AC3: + case AUDIO_FORMAT_E_AC3: + return true; + default: + return false; + } +} + +static inline bool audio_is_linear_pcm(audio_format_t format) +{ + return ((format & AUDIO_FORMAT_MAIN_MASK) == AUDIO_FORMAT_PCM); +} + +static inline size_t audio_bytes_per_sample(audio_format_t format) +{ + size_t size = 0; + + switch (format) { + case AUDIO_FORMAT_PCM_32_BIT: + case AUDIO_FORMAT_PCM_8_24_BIT: + size = sizeof(int32_t); + break; + case AUDIO_FORMAT_PCM_24_BIT_PACKED: + size = sizeof(uint8_t) * 3; + break; + case AUDIO_FORMAT_PCM_16_BIT: + size = sizeof(int16_t); + break; + case AUDIO_FORMAT_PCM_8_BIT: + size = sizeof(uint8_t); + break; + case AUDIO_FORMAT_PCM_FLOAT: + size = sizeof(float); + break; + case AUDIO_FORMAT_INVALID: + case AUDIO_FORMAT_DEFAULT: + case AUDIO_FORMAT_MP3: + case AUDIO_FORMAT_AMR_NB: + case AUDIO_FORMAT_AMR_WB: + case AUDIO_FORMAT_AAC: + case AUDIO_FORMAT_HE_AAC_V1: + case AUDIO_FORMAT_HE_AAC_V2: + case AUDIO_FORMAT_VORBIS: + case AUDIO_FORMAT_OPUS: + case AUDIO_FORMAT_AC3: + case AUDIO_FORMAT_E_AC3: + case AUDIO_FORMAT_MAIN_MASK: + case AUDIO_FORMAT_SUB_MASK: + case AUDIO_FORMAT_AAC_MAIN: + case AUDIO_FORMAT_AAC_LC: + case AUDIO_FORMAT_AAC_SSR: + case AUDIO_FORMAT_AAC_LTP: + case AUDIO_FORMAT_AAC_HE_V1: + case AUDIO_FORMAT_AAC_SCALABLE: + case AUDIO_FORMAT_AAC_ERLC: + case AUDIO_FORMAT_AAC_LD: + case AUDIO_FORMAT_AAC_HE_V2: + case AUDIO_FORMAT_AAC_ELD: + default: + break; + } + return size; +} + +/* converts device address to string sent to audio HAL via set_parameters */ +#if 0 /* never used error */ +static char *audio_device_address_to_parameter(audio_devices_t device, const char *address) +{ + const size_t kSize = AUDIO_DEVICE_MAX_ADDRESS_LEN + sizeof("a2dp_sink_address="); + char param[kSize]; + + if (device & AUDIO_DEVICE_OUT_ALL_A2DP) + snprintf(param, kSize, "%s=%s", "a2dp_sink_address", address); + else if (device & AUDIO_DEVICE_OUT_REMOTE_SUBMIX) + snprintf(param, kSize, "%s=%s", "mix", address); + else + snprintf(param, kSize, "%s", address); + + return strdup(param); +} +#endif + +__END_DECLS + +#endif // ANDROID_AUDIO_CORE_H diff --git a/android/test-ipc.c b/android/test-ipc.c new file mode 100644 index 0000000..bb7d15f --- /dev/null +++ b/android/test-ipc.c @@ -0,0 +1,577 @@ +/* + * + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "src/shared/util.h" +#include "src/log.h" +#include "android/ipc-common.h" +#include "android/ipc.h" + +static const char HAL_SK_PATH[] = "\0test_hal_socket"; + +#define SERVICE_ID_MAX 10 + +struct test_data { + bool disconnect; + const void *cmd; + uint16_t cmd_size; + uint8_t service; + const struct ipc_handler *handlers; + uint8_t handlers_size; +}; + +struct context { + GMainLoop *main_loop; + + int sk; + + guint source; + guint cmd_source; + guint notif_source; + + GIOChannel *cmd_io; + GIOChannel *notif_io; + + const struct test_data *data; +}; + + +static struct ipc *ipc = NULL; + +static void context_quit(struct context *context) +{ + g_main_loop_quit(context->main_loop); +} + +static gboolean cmd_watch(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct context *context = user_data; + const struct test_data *test_data = context->data; + const struct ipc_hdr *sent_msg = test_data->cmd; + uint8_t buf[128]; + int sk; + + struct ipc_hdr success_resp = { + .service_id = sent_msg->service_id, + .opcode = sent_msg->opcode, + .len = 0, + }; + + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { + g_assert(test_data->disconnect); + return FALSE; + } + + g_assert(!test_data->disconnect); + + sk = g_io_channel_unix_get_fd(io); + + g_assert(read(sk, buf, sizeof(buf)) == sizeof(struct ipc_hdr)); + g_assert(!memcmp(&success_resp, buf, sizeof(struct ipc_hdr))); + + context_quit(context); + + return TRUE; +} + +static gboolean notif_watch(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct context *context = user_data; + const struct test_data *test_data = context->data; + + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { + g_assert(test_data->disconnect); + return FALSE; + } + + g_assert(!test_data->disconnect); + + return TRUE; +} + +static gboolean connect_handler(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct context *context = user_data; + const struct test_data *test_data = context->data; + GIOChannel *new_io; + GIOCondition watch_cond; + int sk; + + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { + g_assert(FALSE); + return FALSE; + } + + g_assert(!context->cmd_source || !context->notif_source); + + sk = accept(context->sk, NULL, NULL); + g_assert(sk >= 0); + + new_io = g_io_channel_unix_new(sk); + + watch_cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL; + + if (context->cmd_source && !context->notif_source) { + context->notif_source = g_io_add_watch(new_io, watch_cond, + notif_watch, context); + g_assert(context->notif_source > 0); + context->notif_io = new_io; + } + + if (!context->cmd_source) { + context->cmd_source = g_io_add_watch(new_io, watch_cond, + cmd_watch, context); + context->cmd_io = new_io; + } + + if (context->cmd_source && context->notif_source && !test_data->cmd) + context_quit(context); + + return TRUE; +} + +static struct context *create_context(gconstpointer data) +{ + struct context *context = g_new0(struct context, 1); + struct sockaddr_un addr; + GIOChannel *io; + int ret, sk; + + context->main_loop = g_main_loop_new(NULL, FALSE); + g_assert(context->main_loop); + + sk = socket(AF_LOCAL, SOCK_SEQPACKET, 0); + g_assert(sk >= 0); + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + memcpy(addr.sun_path, HAL_SK_PATH, sizeof(HAL_SK_PATH)); + + ret = bind(sk, (struct sockaddr *) &addr, sizeof(addr)); + g_assert(ret == 0); + + ret = listen(sk, 5); + g_assert(ret == 0); + + io = g_io_channel_unix_new(sk); + + g_io_channel_set_close_on_unref(io, TRUE); + + context->source = g_io_add_watch(io, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + connect_handler, context); + g_assert(context->source > 0); + + g_io_channel_unref(io); + + context->sk = sk; + context->data = data; + + return context; +} + +static void execute_context(struct context *context) +{ + g_main_loop_run(context->main_loop); + + g_io_channel_shutdown(context->notif_io, TRUE, NULL); + g_io_channel_shutdown(context->cmd_io, TRUE, NULL); + g_io_channel_unref(context->cmd_io); + g_io_channel_unref(context->notif_io); + + g_source_remove(context->notif_source); + g_source_remove(context->cmd_source); + g_source_remove(context->source); + + g_main_loop_unref(context->main_loop); + + g_free(context); +} + +static void disconnected(void *data) +{ + struct context *context = data; + + g_assert(context->data->disconnect); + + context_quit(context); +} + +static void test_init(gconstpointer data) +{ + struct context *context = create_context(data); + + ipc = ipc_init(HAL_SK_PATH, sizeof(HAL_SK_PATH), SERVICE_ID_MAX, + true, NULL, NULL); + + g_assert(ipc); + + execute_context(context); + + ipc_cleanup(ipc); + ipc = NULL; +} + +static gboolean send_cmd(gpointer user_data) +{ + struct context *context = user_data; + const struct test_data *test_data = context->data; + int sk; + + sk = g_io_channel_unix_get_fd(context->cmd_io); + g_assert(sk >= 0); + + g_assert(write(sk, test_data->cmd, test_data->cmd_size) == + test_data->cmd_size); + + return FALSE; +} + +static gboolean register_service(gpointer user_data) +{ + struct context *context = user_data; + const struct test_data *test_data = context->data; + + ipc_register(ipc, test_data->service, test_data->handlers, + test_data->handlers_size); + + return FALSE; +} + +static gboolean unregister_service(gpointer user_data) +{ + struct context *context = user_data; + const struct test_data *test_data = context->data; + + ipc_unregister(ipc, test_data->service); + + return FALSE; +} + +static void test_cmd(gconstpointer data) +{ + struct context *context = create_context(data); + + ipc = ipc_init(HAL_SK_PATH, sizeof(HAL_SK_PATH), SERVICE_ID_MAX, + true, disconnected, context); + + g_assert(ipc); + + g_idle_add(send_cmd, context); + + execute_context(context); + + ipc_cleanup(ipc); + ipc = NULL; +} + +static void test_cmd_reg(gconstpointer data) +{ + struct context *context = create_context(data); + const struct test_data *test_data = context->data; + + ipc = ipc_init(HAL_SK_PATH, sizeof(HAL_SK_PATH), SERVICE_ID_MAX, + true, disconnected, context); + + g_assert(ipc); + + g_idle_add(register_service, context); + g_idle_add(send_cmd, context); + + execute_context(context); + + ipc_unregister(ipc, test_data->service); + + ipc_cleanup(ipc); + ipc = NULL; +} + +static void test_cmd_reg_1(gconstpointer data) +{ + struct context *context = create_context(data); + + ipc = ipc_init(HAL_SK_PATH, sizeof(HAL_SK_PATH), SERVICE_ID_MAX, + true, disconnected, context); + + g_assert(ipc); + + g_idle_add(register_service, context); + g_idle_add(unregister_service, context); + g_idle_add(send_cmd, context); + + execute_context(context); + + ipc_cleanup(ipc); + ipc = NULL; +} + +static void test_cmd_handler_1(const void *buf, uint16_t len) +{ + ipc_send_rsp(ipc, 0, 1, 0); +} + +static void test_cmd_handler_2(const void *buf, uint16_t len) +{ + ipc_send_rsp(ipc, 0, 2, 0); +} + +static void test_cmd_handler_invalid(const void *buf, uint16_t len) +{ + g_assert(false); +} + +static const struct test_data test_init_1 = {}; + +static const struct ipc_hdr test_cmd_1_hdr = { + .service_id = 0, + .opcode = 1, + .len = 0 +}; + +static const struct ipc_hdr test_cmd_2_hdr = { + .service_id = 0, + .opcode = 2, + .len = 0 +}; + +static const struct test_data test_cmd_service_invalid_1 = { + .cmd = &test_cmd_1_hdr, + .cmd_size = sizeof(test_cmd_1_hdr), + .disconnect = true, +}; + +static const struct ipc_handler cmd_handlers[] = { + { test_cmd_handler_1, false, 0 } +}; + +static const struct test_data test_cmd_service_valid_1 = { + .cmd = &test_cmd_1_hdr, + .cmd_size = sizeof(test_cmd_1_hdr), + .service = 0, + .handlers = cmd_handlers, + .handlers_size = 1 +}; + +static const struct test_data test_cmd_service_invalid_2 = { + .cmd = &test_cmd_1_hdr, + .cmd_size = sizeof(test_cmd_1_hdr), + .service = 0, + .handlers = cmd_handlers, + .handlers_size = 1, + .disconnect = true, +}; + +static const struct ipc_handler cmd_handlers_invalid_2[] = { + { test_cmd_handler_1, false, 0 }, + { test_cmd_handler_invalid, false, 0 } +}; + +static const struct ipc_handler cmd_handlers_invalid_1[] = { + { test_cmd_handler_invalid, false, 0 }, + { test_cmd_handler_2, false, 0 }, +}; + +static const struct test_data test_cmd_opcode_valid_1 = { + .cmd = &test_cmd_1_hdr, + .cmd_size = sizeof(test_cmd_1_hdr), + .service = 0, + .handlers = cmd_handlers_invalid_2, + .handlers_size = 2, +}; + +static const struct test_data test_cmd_opcode_valid_2 = { + .cmd = &test_cmd_2_hdr, + .cmd_size = sizeof(test_cmd_2_hdr), + .service = 0, + .handlers = cmd_handlers_invalid_1, + .handlers_size = 2, +}; + +static const struct test_data test_cmd_opcode_invalid_1 = { + .cmd = &test_cmd_2_hdr, + .cmd_size = sizeof(test_cmd_2_hdr), + .service = 0, + .handlers = cmd_handlers, + .handlers_size = 1, + .disconnect = true, +}; + +static const struct test_data test_cmd_hdr_invalid = { + .cmd = &test_cmd_1_hdr, + .cmd_size = sizeof(test_cmd_1_hdr) - 1, + .service = 0, + .handlers = cmd_handlers, + .handlers_size = 1, + .disconnect = true, +}; + +#define VARDATA_EX1 "some data example" + +struct vardata { + struct ipc_hdr hdr; + uint8_t data[IPC_MTU - sizeof(struct ipc_hdr)]; +} __attribute__((packed)); + +static const struct vardata test_cmd_vardata = { + .hdr.service_id = 0, + .hdr.opcode = 1, + .hdr.len = sizeof(VARDATA_EX1), + .data = VARDATA_EX1, +}; + +static const struct ipc_handler cmd_vardata_handlers[] = { + { test_cmd_handler_1, true, sizeof(VARDATA_EX1) } +}; + +static const struct test_data test_cmd_vardata_valid = { + .cmd = &test_cmd_vardata, + .cmd_size = sizeof(struct ipc_hdr) + sizeof(VARDATA_EX1), + .service = 0, + .handlers = cmd_vardata_handlers, + .handlers_size = 1, +}; + +static const struct ipc_handler cmd_vardata_handlers_valid2[] = { + { test_cmd_handler_1, true, sizeof(VARDATA_EX1) - 1 } +}; + +static const struct test_data test_cmd_vardata_valid_2 = { + .cmd = &test_cmd_vardata, + .cmd_size = sizeof(struct ipc_hdr) + sizeof(VARDATA_EX1), + .service = 0, + .handlers = cmd_vardata_handlers_valid2, + .handlers_size = 1, +}; + +static const struct test_data test_cmd_vardata_invalid_1 = { + .cmd = &test_cmd_vardata, + .cmd_size = sizeof(struct ipc_hdr) + sizeof(VARDATA_EX1) - 1, + .service = 0, + .handlers = cmd_vardata_handlers, + .handlers_size = 1, + .disconnect = true, +}; + +static const struct ipc_hdr test_cmd_service_offrange_hdr = { + .service_id = SERVICE_ID_MAX + 1, + .opcode = 1, + .len = 0 +}; + +static const struct test_data test_cmd_service_offrange = { + .cmd = &test_cmd_service_offrange_hdr, + .cmd_size = sizeof(struct ipc_hdr), + .service = 0, + .handlers = cmd_handlers, + .handlers_size = 1, + .disconnect = true, +}; + +static const struct vardata test_cmd_invalid_data_1 = { + .hdr.service_id = 0, + .hdr.opcode = 1, + .hdr.len = sizeof(VARDATA_EX1), + .data = VARDATA_EX1, +}; + +static const struct test_data test_cmd_msg_invalid_1 = { + .cmd = &test_cmd_invalid_data_1, + .cmd_size = sizeof(struct ipc_hdr) + sizeof(VARDATA_EX1) - 1, + .service = 0, + .handlers = cmd_handlers, + .handlers_size = 1, + .disconnect = true, +}; + +static const struct vardata test_cmd_invalid_data_2 = { + .hdr.service_id = 0, + .hdr.opcode = 1, + .hdr.len = sizeof(VARDATA_EX1) - 1, + .data = VARDATA_EX1, +}; + +static const struct test_data test_cmd_msg_invalid_2 = { + .cmd = &test_cmd_invalid_data_2, + .cmd_size = sizeof(struct ipc_hdr) + sizeof(VARDATA_EX1), + .service = 0, + .handlers = cmd_handlers, + .handlers_size = 1, + .disconnect = true, +}; + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + + if (g_test_verbose()) + __btd_log_init("*", 0); + + g_test_add_data_func("/android_ipc/init", &test_init_1, test_init); + g_test_add_data_func("/android_ipc/service_invalid_1", + &test_cmd_service_invalid_1, test_cmd); + g_test_add_data_func("/android_ipc/service_valid_1", + &test_cmd_service_valid_1, test_cmd_reg); + g_test_add_data_func("/android_ipc/service_invalid_2", + &test_cmd_service_invalid_2, test_cmd_reg_1); + g_test_add_data_func("/android_ipc/opcode_valid_1", + &test_cmd_opcode_valid_1, test_cmd_reg); + g_test_add_data_func("/android_ipc/opcode_valid_2", + &test_cmd_opcode_valid_2, test_cmd_reg); + g_test_add_data_func("/android_ipc/opcode_invalid_1", + &test_cmd_opcode_invalid_1, test_cmd_reg); + g_test_add_data_func("/android_ipc/vardata_valid", + &test_cmd_vardata_valid, test_cmd_reg); + g_test_add_data_func("/android_ipc/vardata_valid_2", + &test_cmd_vardata_valid_2, test_cmd_reg); + g_test_add_data_func("/android_ipc/vardata_invalid_1", + &test_cmd_vardata_invalid_1, test_cmd_reg); + g_test_add_data_func("/android_ipc/service_offrange", + &test_cmd_service_offrange, test_cmd_reg); + g_test_add_data_func("/android_ipc/hdr_invalid", + &test_cmd_hdr_invalid, test_cmd_reg); + g_test_add_data_func("/android_ipc/msg_invalid_1", + &test_cmd_msg_invalid_1, test_cmd_reg); + g_test_add_data_func("/android_ipc/msg_invalid_2", + &test_cmd_msg_invalid_2, test_cmd_reg); + + return g_test_run(); +} diff --git a/android/tester-a2dp.c b/android/tester-a2dp.c new file mode 100644 index 0000000..554a394 --- /dev/null +++ b/android/tester-a2dp.c @@ -0,0 +1,250 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include + +#include "emulator/bthost.h" +#include "src/shared/util.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "lib/bluetooth.h" +#include "android/utils.h" +#include "tester-main.h" + +static struct queue *list; + +#define req_dsc 0x00, 0x01 +#define rsp_dsc 0x02, 0x01, 0x04, 0x08 +#define req_get 0x10, 0x02, 0x04 +#define rsp_get 0x12, 0x02, 0x01, 0x00, 0x07, 0x06, 0x00, \ + 0x00, 0xff, 0xff, 0x02, 0x40 +#define req_cfg 0x20, 0x03, 0x04, 0x04, 0x01, 0x00, 0x07, \ + 0x06, 0x00, 0x00, 0x21, 0x15, 0x02, \ + 0x40 +#define rsp_cfg 0x22, 0x03 +#define req_open 0x30, 0x06, 0x04 +#define rsp_open 0x32, 0x06 +#define req_close 0x40, 0x08, 0x04 +#define rsp_close 0x42, 0x08 +#define req_start 0x40, 0x07, 0x04 +#define rsp_start 0x42, 0x07 +#define req_suspend 0x50, 0x09, 0x04 +#define rsp_suspend 0x52, 0x09 + +static const struct pdu_set pdus[] = { + { raw_pdu(req_dsc), raw_pdu(rsp_dsc) }, + { raw_pdu(req_get), raw_pdu(rsp_get) }, + { raw_pdu(req_cfg), raw_pdu(rsp_cfg) }, + { raw_pdu(req_open), raw_pdu(rsp_open) }, + { raw_pdu(req_close), raw_pdu(rsp_close) }, + { raw_pdu(req_start), raw_pdu(rsp_start) }, + { raw_pdu(req_suspend), raw_pdu(rsp_suspend) }, + { end_pdu, end_pdu }, +}; + +static struct emu_l2cap_cid_data cid_data = { + .pdu = pdus, +}; + +static void a2dp_connect_request_cb(uint16_t handle, uint16_t cid, + void *user_data) +{ + struct emu_l2cap_cid_data *cid_data = user_data; + + if (cid_data->handle) + return; + + cid_data->handle = handle; + cid_data->cid = cid; + + tester_handle_l2cap_data_exchange(cid_data); +} + +static struct emu_set_l2cap_data l2cap_setup_data = { + .psm = 25, + .func = a2dp_connect_request_cb, + .user_data = &cid_data, +}; + +static void a2dp_connect_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + cid_data.handle = 0; + cid_data.cid = 0; + + bdaddr2android((const bdaddr_t *) addr, &bdaddr); + + step->action_status = data->if_a2dp->connect(&bdaddr); + + schedule_action_verification(step); +} + +static void a2dp_disconnect_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) addr, &bdaddr); + + step->action_status = data->if_a2dp->disconnect(&bdaddr); + + schedule_action_verification(step); +} + +static void audio_resume_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + int err; + + err = data->audio->open_output_stream(data->audio, + 0, + AUDIO_DEVICE_OUT_ALL_A2DP, + AUDIO_OUTPUT_FLAG_NONE, + NULL, + &data->if_stream, NULL); + if (err < 0) { + step->action_status = BT_STATUS_FAIL; + goto done; + } + + /* Write something to force resume */ + data->if_stream->write(data->if_stream, &err, sizeof(err)); + +done: + schedule_action_verification(step); +} + +static void audio_suspend_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + data->if_stream->common.standby(&data->if_stream->common); + + schedule_action_verification(step); +} + +static struct test_case test_cases[] = { + TEST_CASE_BREDRLE("A2DP Init", + ACTION_SUCCESS(dummy_action, NULL), + ), + TEST_CASE_BREDRLE("A2DP Connect - Success", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &l2cap_setup_data), + ACTION_SUCCESS(a2dp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_DISCONNECTED), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("A2DP Disconnect - Success", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &l2cap_setup_data), + ACTION_SUCCESS(a2dp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(a2dp_disconnect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_DISCONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_DISCONNECTED), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("A2DP Resume - Success", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &l2cap_setup_data), + ACTION_SUCCESS(a2dp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(audio_resume_action, NULL), + CALLBACK_AV_AUDIO_STATE(CB_A2DP_AUDIO_STATE, + BTAV_AUDIO_STATE_STARTED), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_DISCONNECTED), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("A2DP Suspend - Success", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &l2cap_setup_data), + ACTION_SUCCESS(a2dp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(audio_resume_action, NULL), + CALLBACK_AV_AUDIO_STATE(CB_A2DP_AUDIO_STATE, + BTAV_AUDIO_STATE_STARTED), + ACTION_SUCCESS(audio_suspend_action, NULL), + CALLBACK_AV_AUDIO_STATE(CB_A2DP_AUDIO_STATE, + BTAV_AUDIO_STATE_STOPPED), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_DISCONNECTED), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), +}; + +struct queue *get_a2dp_tests(void) +{ + uint16_t i = 0; + + list = queue_new(); + + for (; i < sizeof(test_cases) / sizeof(test_cases[0]); ++i) + queue_push_tail(list, &test_cases[i]); + + return list; +} + +void remove_a2dp_tests(void) +{ + queue_destroy(list, NULL); +} diff --git a/android/tester-avrcp.c b/android/tester-avrcp.c new file mode 100644 index 0000000..e0e7b7f --- /dev/null +++ b/android/tester-avrcp.c @@ -0,0 +1,598 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include + +#include "emulator/bthost.h" +#include "src/shared/util.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "lib/bluetooth.h" +#include "android/utils.h" +#include "tester-main.h" + +static struct queue *list; + +#define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20 +#define AVRCP_GET_PLAY_STATUS 0x30 +#define AVRCP_REGISTER_NOTIFICATION 0x31 + +#define sdp_rsp_pdu 0x07, \ + 0x00, 0x00, \ + 0x00, 0x7f, \ + 0x00, 0x7c, \ + 0x36, 0x00, 0x79, 0x36, 0x00, 0x3b, 0x09, 0x00, 0x00, \ + 0x0a, 0x00, 0x01, 0x00, 0x04, 0x09, 0x00, 0x01, 0x35, \ + 0x06, 0x19, 0x11, 0x0e, 0x19, 0x11, 0x0f, 0x09, 0x00, \ + 0x04, 0x35, 0x10, 0x35, 0x06, 0x19, 0x01, 0x00, 0x09, \ + 0x00, 0x17, 0x35, 0x06, 0x19, 0x00, 0x17, 0x09, 0x01, \ + 0x03, 0x09, 0x00, 0x09, 0x35, 0x08, 0x35, 0x06, 0x19, \ + 0x11, 0x0e, 0x09, 0x01, 0x00, 0x09, 0x03, 0x11, 0x09, \ + 0x00, 0x01, 0x36, 0x00, 0x38, 0x09, 0x00, 0x00, 0x0a, \ + 0x00, 0x01, 0x00, 0x05, 0x09, 0x00, 0x01, 0x35, 0x03, \ + 0x19, 0x11, 0x0c, 0x09, 0x00, 0x04, 0x35, 0x10, 0x35, \ + 0x06, 0x19, 0x01, 0x00, 0x09, 0x00, 0x17, 0x35, 0x06, \ + 0x19, 0x00, 0x17, 0x09, 0x01, 0x03, 0x09, 0x00, 0x09, \ + 0x35, 0x08, 0x35, 0x06, 0x19, 0x11, 0x0e, 0x09, 0x01, \ + 0x04, 0x09, 0x03, 0x11, 0x09, 0x00, 0x02, \ + 0x00 + +static const struct pdu_set sdp_pdus[] = { + { end_pdu, raw_pdu(sdp_rsp_pdu) }, + { end_pdu, end_pdu }, +}; + +static struct emu_l2cap_cid_data sdp_data = { + .pdu = sdp_pdus, + .is_sdp = TRUE, +}; + +#define req_dsc 0x00, 0x01 +#define rsp_dsc 0x02, 0x01, 0x04, 0x08 +#define req_get 0x10, 0x02, 0x04 +#define rsp_get 0x12, 0x02, 0x01, 0x00, 0x07, 0x06, 0x00, \ + 0x00, 0xff, 0xff, 0x02, 0x40 +#define req_cfg 0x20, 0x03, 0x04, 0x04, 0x01, 0x00, 0x07, \ + 0x06, 0x00, 0x00, 0x21, 0x15, 0x02, \ + 0x40 +#define rsp_cfg 0x22, 0x03 +#define req_open 0x30, 0x06, 0x04 +#define rsp_open 0x32, 0x06 +#define req_close 0x40, 0x08, 0x04 +#define rsp_close 0x42, 0x08 +#define req_start 0x40, 0x07, 0x04 +#define rsp_start 0x42, 0x07 +#define req_suspend 0x50, 0x09, 0x04 +#define rsp_suspend 0x52, 0x09 + +#define req_play_status 0x00, 0x11, 0x0e, 0x01, 0x48, 0x00, 0x00, 0x19, 0x58, \ + 0x30, 0x00, 0x00, 0x00 +#define rsp_play_status 0x02, 0x11, 0x0e, 0x0c, 0x48, 0x00, 0x00, 0x19, 0x58, \ + 0x30, 0x00, 0x00, 0x09, 0xbb, 0xbb, 0xbb, 0xbb, 0xaa, \ + 0xaa, 0xaa, 0xaa, 0x00 + +#define req_track_notif 0x00, 0x11, 0x0e, 0x03, 0x48, 0x00, 0x00, 0x19, 0x58, \ + 0x31, 0x00, 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00 + +#define rsp_track_notif 0x00, 0x11, 0x0e, 0x0F, 0x48, 0x00, 0x00, 0x19, 0x58, \ + 0x31, 0x00, 0x00, 0x09, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, \ + 0xFF, 0xFF, 0xFF, 0xFF + +#define req_position_notif 0x00, 0x11, 0x0e, 0x03, 0x48, 0x00, 0x00, 0x19, \ + 0x58, 0x31, 0x00, 0x00, 0x05, 0x05, 0x00, \ + 0x00, 0x00, 0x00 + +#define rsp_position_notif 0x00, 0x11, 0x0e, 0x0F, 0x48, 0x00, 0x00, 0x19, \ + 0x58, 0x31, 0x00, 0x00, 0x04, 0x05, 0xFF, \ + 0xFF, 0xFF, 0xFF + +#define req_status_notif 0x00, 0x11, 0x0e, 0x03, 0x48, 0x00, 0x00, 0x19, \ + 0x58, 0x31, 0x00, 0x00, 0x05, 0x01, 0x00, \ + 0x00, 0x00, 0x00 + +#define rsp_status_notif 0x00, 0x11, 0x0e, 0x0D, 0x48, 0x00, 0x00, 0x19, \ + 0x58, 0x31, 0x00, 0x00, 0x01, 0x01, 0x00 + +#define req_ele_attr 0x00, 0x11, 0x0e, 0x01, 0x48, 0x00, 0x00, 0x19, 0x58, \ + 0x20, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, \ + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07 + +#define rsp_ele_attr 0x02, 0x11, 0x0e, 0x0c, 0x48, 0x00, 0x00, 0x19, 0x58, \ + 0x20, 0x00, 0x00, 0x2a, 0x02, 0x00, 0x00, 0x00, 0x01, \ + 0x00, 0x6a, 0x00, 0x13, 0x47, 0x69, 0x76, 0x65, 0x20, \ + 0x50, 0x65, 0x61, 0x63, 0x65, 0x20, 0x61, 0x20, 0x43, \ + 0x68, 0x61, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x07, \ + 0x00, 0x6a, 0x00, 0x06, 0x31, 0x30, 0x33, 0x30, 0x30, \ + 0x30 + +static const struct pdu_set pdus[] = { + { raw_pdu(req_dsc), raw_pdu(rsp_dsc) }, + { raw_pdu(req_get), raw_pdu(rsp_get) }, + { raw_pdu(req_cfg), raw_pdu(rsp_cfg) }, + { raw_pdu(req_open), raw_pdu(rsp_open) }, + { raw_pdu(req_close), raw_pdu(rsp_close) }, + { raw_pdu(req_start), raw_pdu(rsp_start) }, + { raw_pdu(req_suspend), raw_pdu(rsp_suspend) }, + { end_pdu, end_pdu }, +}; + +static struct emu_l2cap_cid_data a2dp_data = { + .pdu = pdus, +}; + +static struct emu_l2cap_cid_data avrcp_data; + +static btrc_element_attr_val_t ele_attrs[2] = { + { + .attr_id = BTRC_MEDIA_ATTR_TITLE, + .text = {0x47, 0x69, 0x76, 0x65, 0x20, 0x50, 0x65, 0x61, 0x63, 0x65, + 0x20, 0x61, 0x20, 0x43, 0x68, 0x61, 0x6e, 0x63, 0x65} + }, + { + .attr_id = BTRC_MEDIA_ATTR_PLAYING_TIME, + .text = {0x31, 0x30, 0x33, 0x30, 0x30, 0x30} + } +}; + +static btrc_element_attr_val_t exp_attrs[2]; + +static void print_avrcp(const char *str, void *user_data) +{ + tester_debug("avrcp: %s", str); +} + +static void avrcp_cid_hook_cb(const void *data, uint16_t len, void *user_data) +{ + struct step *step; + uint8_t pdu, event; + + util_hexdump('>', data, len, print_avrcp, NULL); + + pdu = ((uint8_t *) data)[9]; + switch (pdu) { + case AVRCP_GET_PLAY_STATUS: + step = g_new0(struct step, 1); + step->callback = CB_AVRCP_PLAY_STATUS_RSP; + step->callback_result.song_length = get_be32(data + 13); + step->callback_result.song_position = get_be32(data + 17); + step->callback_result.play_status = ((uint8_t *) data)[21]; + schedule_callback_verification(step); + break; + case AVRCP_REGISTER_NOTIFICATION: + event = ((uint8_t *) data)[13]; + switch (event) { + case 0x01: + step = g_new0(struct step, 1); + step->callback = CB_AVRCP_REG_NOTIF_RSP; + step->callback_result.play_status = + ((uint8_t *) data)[14]; + schedule_callback_verification(step); + break; + + case 0x02: + step = g_new0(struct step, 1); + step->callback = CB_AVRCP_REG_NOTIF_RSP; + step->callback_result.rc_index = get_be64(data + 14); + schedule_callback_verification(step); + break; + + case 0x05: + step = g_new0(struct step, 1); + step->callback = CB_AVRCP_REG_NOTIF_RSP; + step->callback_result.song_position = + get_be32(data + 14); + schedule_callback_verification(step); + break; + } + break; + case AVRCP_GET_ELEMENT_ATTRIBUTES: + step = g_new0(struct step, 1); + step->callback = CB_AVRCP_GET_ATTR_RSP; + step->callback_result.num_of_attrs = ((uint8_t *) data)[13]; + + memset(exp_attrs, 0, 2 * sizeof(btrc_element_attr_val_t)); + exp_attrs[0].attr_id = get_be16(data + 16); + memcpy(exp_attrs[0].text, data + 22, 19); + exp_attrs[1].attr_id = get_be16(data + 43); + memcpy(exp_attrs[1].text, data + 49, 6); + step->callback_result.attrs = exp_attrs; + schedule_callback_verification(step); + break; + } +} + +static void avrcp_connect_request_cb(uint16_t handle, uint16_t cid, + void *user_data) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + struct emu_l2cap_cid_data *cid_data = user_data; + + cid_data->handle = handle; + cid_data->cid = cid; + + bthost_add_cid_hook(bthost, handle, cid, avrcp_cid_hook_cb, cid_data); +} + +static struct emu_set_l2cap_data avrcp_setup_data = { + .psm = 23, + .func = avrcp_connect_request_cb, + .user_data = &avrcp_data, +}; + +static void a2dp_connect_request_cb(uint16_t handle, uint16_t cid, + void *user_data) +{ + struct emu_l2cap_cid_data *cid_data = user_data; + + if (cid_data->handle) + return; + + cid_data->handle = handle; + cid_data->cid = cid; + avrcp_data.handle = handle; + avrcp_data.cid = cid; + + tester_handle_l2cap_data_exchange(cid_data); +} + +static struct emu_set_l2cap_data a2dp_setup_data = { + .psm = 25, + .func = a2dp_connect_request_cb, + .user_data = &a2dp_data, +}; + +static struct emu_set_l2cap_data sdp_setup_data = { + .psm = 1, + .func = tester_generic_connect_cb, + .user_data = &sdp_data, +}; + +static void avrcp_connect_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + sdp_data.handle = 0; + sdp_data.cid = 0; + + a2dp_data.handle = 0; + a2dp_data.cid = 0; + + avrcp_data.handle = 0; + avrcp_data.cid = 0; + + bdaddr2android((const bdaddr_t *) addr, &bdaddr); + + step->action_status = data->if_a2dp->connect(&bdaddr); + + schedule_action_verification(step); +} + +static void avrcp_disconnect_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) addr, &bdaddr); + + step->action_status = data->if_a2dp->disconnect(&bdaddr); + + schedule_action_verification(step); +} + +static void avrcp_get_play_status_req(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + const struct iovec pdu = raw_pdu(req_play_status); + struct step *step = g_new0(struct step, 1); + + bthost_send_cid_v(bthost, avrcp_data.handle, avrcp_data.cid, &pdu, 1); + step->action_status = BT_STATUS_SUCCESS; + schedule_action_verification(step); +} + +static void avrcp_get_play_status_rsp(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_avrcp->get_play_status_rsp(0x00, + 0xbbbbbbbb, 0xaaaaaaaa); + schedule_action_verification(step); +} + +static void avrcp_reg_notif_track_changed_req(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + const struct iovec pdu = raw_pdu(req_track_notif); + struct step *step = g_new0(struct step, 1); + + bthost_send_cid_v(bthost, avrcp_data.handle, avrcp_data.cid, &pdu, 1); + step->action_status = BT_STATUS_SUCCESS; + schedule_action_verification(step); +} + +static void avrcp_reg_notif_track_changed_rsp(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + uint64_t track; + btrc_register_notification_t reg; + + track = 0xffffffffffffffff; + memcpy(reg.track, &track, sizeof(btrc_uid_t)); + step->action_status = data->if_avrcp->register_notification_rsp( + BTRC_EVT_TRACK_CHANGE, + BTRC_NOTIFICATION_TYPE_INTERIM, ®); + + schedule_action_verification(step); +} + +static void avrcp_reg_notif_play_position_changed_req(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + const struct iovec pdu = raw_pdu(req_position_notif); + struct step *step = g_new0(struct step, 1); + + bthost_send_cid_v(bthost, avrcp_data.handle, avrcp_data.cid, &pdu, 1); + step->action_status = BT_STATUS_SUCCESS; + schedule_action_verification(step); +} + +static void avrcp_reg_notif_play_position_changed_rsp(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + btrc_register_notification_t reg; + + reg.song_pos = 0xffffffff; + step->action_status = data->if_avrcp->register_notification_rsp( + BTRC_EVT_PLAY_POS_CHANGED, + BTRC_NOTIFICATION_TYPE_INTERIM, ®); + + schedule_action_verification(step); +} + +static void avrcp_reg_notif_play_status_changed_req(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + const struct iovec pdu = raw_pdu(req_status_notif); + struct step *step = g_new0(struct step, 1); + + bthost_send_cid_v(bthost, avrcp_data.handle, avrcp_data.cid, &pdu, 1); + step->action_status = BT_STATUS_SUCCESS; + schedule_action_verification(step); +} + +static void avrcp_reg_notif_play_status_changed_rsp(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + btrc_register_notification_t reg; + + reg.play_status = BTRC_PLAYSTATE_STOPPED; + step->action_status = data->if_avrcp->register_notification_rsp( + BTRC_EVT_PLAY_STATUS_CHANGED, + BTRC_NOTIFICATION_TYPE_CHANGED, ®); + + schedule_action_verification(step); +} + +static void avrcp_get_element_attributes_req(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + const struct iovec pdu = raw_pdu(req_ele_attr); + struct step *step = g_new0(struct step, 1); + + bthost_send_cid_v(bthost, avrcp_data.handle, avrcp_data.cid, &pdu, 1); + step->action_status = BT_STATUS_SUCCESS; + schedule_action_verification(step); +} + +static void avrcp_get_element_attributes_rsp(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_avrcp->get_element_attr_rsp(2, + ele_attrs); + + schedule_action_verification(step); +} + +static struct test_case test_cases[] = { + TEST_CASE_BREDRLE("AVRCP Init", + ACTION_SUCCESS(dummy_action, NULL), + ), + TEST_CASE_BREDRLE("AVRCP Connect - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &sdp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &a2dp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &avrcp_setup_data), + ACTION_SUCCESS(avrcp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("AVRCP Disconnect - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &sdp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &a2dp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &avrcp_setup_data), + ACTION_SUCCESS(avrcp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(avrcp_disconnect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_DISCONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_DISCONNECTED), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("AVRCP GetPlayStatus - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &sdp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &a2dp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &avrcp_setup_data), + ACTION_SUCCESS(avrcp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(avrcp_get_play_status_req, NULL), + CALLBACK(CB_AVRCP_PLAY_STATUS_REQ), + ACTION_SUCCESS(avrcp_get_play_status_rsp, NULL), + CALLBACK_RC_PLAY_STATUS(CB_AVRCP_PLAY_STATUS_RSP, 0xbbbbbbbb, + 0xaaaaaaaa, 0x00), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("AVRCP RegNotifTrackChanged - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &sdp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &a2dp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &avrcp_setup_data), + ACTION_SUCCESS(avrcp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(avrcp_reg_notif_track_changed_req, NULL), + CALLBACK(CB_AVRCP_REG_NOTIF_REQ), + ACTION_SUCCESS(avrcp_reg_notif_track_changed_rsp, NULL), + CALLBACK_RC_REG_NOTIF_TRACK_CHANGED(CB_AVRCP_REG_NOTIF_RSP, + 0xffffffffffffffff), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("AVRCP RegNotifPlayPositionChanged - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &sdp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &a2dp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &avrcp_setup_data), + ACTION_SUCCESS(avrcp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(avrcp_reg_notif_play_position_changed_req, NULL), + CALLBACK(CB_AVRCP_REG_NOTIF_REQ), + ACTION_SUCCESS(avrcp_reg_notif_play_position_changed_rsp, NULL), + CALLBACK_RC_REG_NOTIF_POSITION_CHANGED(CB_AVRCP_REG_NOTIF_RSP, + 0xffffffff), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("AVRCP RegNotifPlayStatusChanged - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &sdp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &a2dp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &avrcp_setup_data), + ACTION_SUCCESS(avrcp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(avrcp_reg_notif_play_status_changed_req, NULL), + CALLBACK(CB_AVRCP_REG_NOTIF_REQ), + ACTION_SUCCESS(avrcp_reg_notif_play_status_changed_rsp, NULL), + CALLBACK_RC_REG_NOTIF_STATUS_CHANGED(CB_AVRCP_REG_NOTIF_RSP, + 0x00), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("AVRCP GetElementAttributes - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &sdp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &a2dp_setup_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, &avrcp_setup_data), + ACTION_SUCCESS(avrcp_connect_action, NULL), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTING), + CALLBACK_AV_CONN_STATE(CB_A2DP_CONN_STATE, + BTAV_CONNECTION_STATE_CONNECTED), + ACTION_SUCCESS(avrcp_get_element_attributes_req, NULL), + CALLBACK(CB_AVRCP_GET_ATTR_REQ), + ACTION_SUCCESS(avrcp_get_element_attributes_rsp, NULL), + CALLBACK_RC_GET_ELEMENT_ATTRIBUTES(CB_AVRCP_GET_ATTR_RSP, 2, + ele_attrs), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), +}; + +struct queue *get_avrcp_tests(void) +{ + uint16_t i = 0; + + list = queue_new(); + + for (; i < sizeof(test_cases) / sizeof(test_cases[0]); ++i) + queue_push_tail(list, &test_cases[i]); + + return list; +} + +void remove_avrcp_tests(void) +{ + queue_destroy(list, NULL); +} diff --git a/android/tester-bluetooth.c b/android/tester-bluetooth.c new file mode 100644 index 0000000..6db337a --- /dev/null +++ b/android/tester-bluetooth.c @@ -0,0 +1,1269 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include + +#include "emulator/bthost.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "tester-main.h" + +static struct queue *list; /* List of bluetooth test cases */ + +static bt_bdaddr_t emu_bdaddr_val = { + .address = { 0x00, 0xaa, 0x01, 0x00, 0x00, 0x00 }, +}; +static bt_property_t prop_emu_bdaddr = { + .type = BT_PROPERTY_BDADDR, + .val = &emu_bdaddr_val, + .len = sizeof(emu_bdaddr_val), +}; + +static char emu_bdname_val[] = "BlueZ for Android"; +static bt_property_t prop_emu_bdname = { + .type = BT_PROPERTY_BDNAME, + .val = &emu_bdname_val, + .len = sizeof(emu_bdname_val) - 1, +}; + +static char emu_uuids_val[] = { + /* Multi profile UUID */ + 0x00, 0x00, 0x11, 0x3b, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, + 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB, + /* Device identification profile UUID */ + 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, + 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB, +}; +static bt_property_t prop_emu_uuids = { + .type = BT_PROPERTY_UUIDS, + .val = &emu_uuids_val, + .len = sizeof(emu_uuids_val), +}; + +static uint32_t emu_cod_val = 0x00020c; +static bt_property_t prop_emu_cod = { + .type = BT_PROPERTY_CLASS_OF_DEVICE, + .val = &emu_cod_val, + .len = sizeof(emu_cod_val), +}; + +static bt_device_type_t emu_tod_dual_val = BT_DEVICE_DEVTYPE_DUAL; +static bt_property_t prop_emu_dual_tod = { + .type = BT_PROPERTY_TYPE_OF_DEVICE, + .val = &emu_tod_dual_val, + .len = sizeof(emu_tod_dual_val), +}; + +static bt_scan_mode_t emu_scan_mode_val = BT_SCAN_MODE_NONE; +static bt_property_t prop_emu_scan_mode = { + .type = BT_PROPERTY_ADAPTER_SCAN_MODE, + .val = &emu_scan_mode_val, + .len = sizeof(emu_scan_mode_val), +}; + +static uint32_t emu_disc_timeout_val = 120; +static bt_property_t prop_emu_disc_timeout = { + .type = BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT, + .val = &emu_disc_timeout_val, + .len = sizeof(emu_disc_timeout_val), +}; + +static bt_property_t prop_emu_bonded_devs = { + .type = BT_PROPERTY_ADAPTER_BONDED_DEVICES, + .val = NULL, + .len = 0, +}; + +static bt_bdaddr_t emu_remote_bdaddr_val = { + .address = { 0x00, 0xaa, 0x01, 0x01, 0x00, 0x00 }, +}; +static bt_property_t prop_emu_remote_bdadr = { + .type = BT_PROPERTY_BDADDR, + .val = &emu_remote_bdaddr_val, + .len = sizeof(emu_remote_bdaddr_val), +}; +static struct bt_action_data prop_emu_remote_ble_bdaddr_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_BDADDR, +}; + +static uint32_t emu_remote_type_val = BT_DEVICE_DEVTYPE_BREDR; + +static uint32_t emu_remote_tod_ble_val = BT_DEVICE_DEVTYPE_BLE; +static bt_property_t prop_emu_remote_ble_tod_prop = { + .type = BT_PROPERTY_TYPE_OF_DEVICE, + .val = &emu_remote_tod_ble_val, + .len = sizeof(emu_remote_tod_ble_val), +}; +static struct bt_action_data prop_emu_remote_ble_tod_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_TYPE_OF_DEVICE, +}; + +static int32_t emu_remote_rssi_val = -60; + +static int32_t emu_remote_ble_rssi_val = 127; +static bt_property_t prop_emu_remote_ble_rssi_prop = { + .type = BT_PROPERTY_REMOTE_RSSI, + .val = &emu_remote_ble_rssi_val, + .len = sizeof(emu_remote_ble_rssi_val), +}; +static struct bt_action_data prop_emu_remote_ble_rssi_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_REMOTE_RSSI, +}; + +static char emu_remote_bdname_val[] = "00:AA:01:01:00:00"; +static bt_property_t prop_emu_remote_ble_bdname_prop = { + .type = BT_PROPERTY_BDNAME, + .val = &emu_remote_bdname_val, + .len = sizeof(emu_remote_bdname_val) - 1, +}; +static struct bt_action_data prop_emu_remote_ble_bdname_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_BDNAME, +}; + +static uint32_t emu_remote_cod_val = 0; +static bt_property_t prop_emu_remote_ble_cod_prop = { + .type = BT_PROPERTY_CLASS_OF_DEVICE, + .val = &emu_remote_cod_val, + .len = sizeof(emu_remote_cod_val), +}; +static struct bt_action_data prop_emu_remote_ble_cod_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_CLASS_OF_DEVICE, +}; + +static bt_property_t prop_emu_remote_ble_uuids_prop = { + .type = BT_PROPERTY_UUIDS, + .val = NULL, + .len = 0, +}; +static struct bt_action_data prop_emu_remote_ble_uuids_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_UUIDS, +}; + +static bt_property_t prop_emu_remote_ble_timestamp_prop = { + .type = BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP, + .val = NULL, + .len = 4, +}; +static struct bt_action_data prop_emu_remote_ble_timestamp_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP, +}; + +static struct bt_action_data prop_emu_remote_ble_scan_mode_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_ADAPTER_SCAN_MODE, +}; + +static struct bt_action_data prop_emu_remote_ble_bondeddev_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_ADAPTER_BONDED_DEVICES, +}; + +static struct bt_action_data prop_emu_remote_ble_disctimeout_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT, +}; + +static struct bt_action_data prop_emu_remote_ble_verinfo_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_REMOTE_VERSION_INFO, +}; + +static char prop_test_fname_val[] = "FriendlyTestName"; +static bt_property_t prop_emu_remote_ble_fname_prop = { + .type = BT_PROPERTY_REMOTE_FRIENDLY_NAME, + .val = &prop_test_fname_val, + .len = sizeof(prop_test_fname_val) - 1, +}; +static struct bt_action_data prop_emu_remote_ble_fname_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_REMOTE_FRIENDLY_NAME, + .prop = &prop_emu_remote_ble_fname_prop, +}; + +static bt_pin_code_t emu_pin_value = { + .pin = { 0x30, 0x30, 0x30, 0x30 }, +}; +static bt_pin_code_t emu_pin_invalid_value = { + .pin = { 0x30, 0x10, 0x30, 0x30 }, +}; +static struct bt_action_data emu_pin_set_req = { + .addr = &emu_remote_bdaddr_val, + .pin = &emu_pin_value, + .pin_len = 4, +}; +static struct bt_action_data emu_pin_set_invalid_req = { + .addr = &emu_remote_bdaddr_val, + .pin = &emu_pin_invalid_value, + .pin_len = 4, +}; + +static bt_property_t prop_emu_default_set[] = { + { BT_PROPERTY_BDADDR, sizeof(emu_bdaddr_val), NULL }, + { BT_PROPERTY_BDNAME, sizeof(emu_bdname_val) - 1, &emu_bdname_val }, + { BT_PROPERTY_CLASS_OF_DEVICE, sizeof(uint32_t), NULL }, + { BT_PROPERTY_TYPE_OF_DEVICE, sizeof(emu_tod_dual_val), + &emu_tod_dual_val }, + { BT_PROPERTY_ADAPTER_SCAN_MODE, sizeof(emu_scan_mode_val), + &emu_scan_mode_val }, + { BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT, sizeof(emu_disc_timeout_val), + &emu_disc_timeout_val}, + { BT_PROPERTY_ADAPTER_BONDED_DEVICES, 0, NULL }, + { BT_PROPERTY_UUIDS, sizeof(emu_uuids_val), &emu_uuids_val }, +}; + +static bt_property_t prop_emu_remote_ble_default_set[] = { + { BT_PROPERTY_BDADDR, sizeof(emu_remote_bdaddr_val), + &emu_remote_bdaddr_val }, + { BT_PROPERTY_TYPE_OF_DEVICE, sizeof(emu_remote_tod_ble_val), + &emu_remote_tod_ble_val }, + { BT_PROPERTY_REMOTE_RSSI, sizeof(emu_remote_ble_rssi_val), + &emu_remote_ble_rssi_val }, +}; + +static bt_property_t prop_emu_remote_bredr_default_set[] = { + { BT_PROPERTY_BDADDR, sizeof(emu_remote_bdaddr_val), + &emu_remote_bdaddr_val }, + { BT_PROPERTY_TYPE_OF_DEVICE, sizeof(emu_remote_type_val), + &emu_remote_type_val }, + { BT_PROPERTY_REMOTE_RSSI, sizeof(emu_remote_rssi_val), + &emu_remote_rssi_val }, +}; + +static bt_property_t prop_emu_remote_any_default_set[] = { + { BT_PROPERTY_BDADDR, sizeof(emu_remote_bdaddr_val), + &emu_remote_bdaddr_val }, +}; + +static bt_property_t prop_emu_remote_bles_query_set[] = { + { BT_PROPERTY_TYPE_OF_DEVICE, sizeof(emu_remote_tod_ble_val), + &emu_remote_tod_ble_val }, + { BT_PROPERTY_CLASS_OF_DEVICE, sizeof(emu_remote_cod_val), + &emu_remote_cod_val }, + { BT_PROPERTY_REMOTE_RSSI, sizeof(emu_remote_ble_rssi_val), + &emu_remote_ble_rssi_val }, + { BT_PROPERTY_BDNAME, sizeof(emu_remote_bdname_val) - 1, + &emu_remote_bdname_val }, + { BT_PROPERTY_UUIDS, 0, NULL }, + { BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP, 4, NULL }, +}; + +static bt_property_t prop_emu_remotes_pin_req_set[] = { + { BT_PROPERTY_BDADDR, sizeof(emu_remote_bdaddr_val), + &emu_remote_bdaddr_val }, + { BT_PROPERTY_CLASS_OF_DEVICE, sizeof(emu_remote_cod_val), + &emu_remote_cod_val }, + { BT_PROPERTY_BDNAME, sizeof(emu_remote_bdname_val) - 1, + &emu_remote_bdname_val }, +}; + +static char test_bdname[] = "test_bdname"; +static bt_property_t prop_test_bdname = { + .type = BT_PROPERTY_BDNAME, + .val = test_bdname, + .len = sizeof(test_bdname) - 1, +}; +static struct bt_action_data prop_test_remote_ble_bdname_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_BDNAME, + .prop = &prop_test_bdname, +}; + +static bt_scan_mode_t test_scan_mode_connectable_discoverable = + BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE; +static bt_property_t prop_test_scanmode_conn_discov = { + .type = BT_PROPERTY_ADAPTER_SCAN_MODE, + .val = &test_scan_mode_connectable_discoverable, + .len = sizeof(bt_scan_mode_t), +}; + +static uint32_t test_disctimeout_val = 600; +static bt_property_t prop_test_disctimeout = { + .type = BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT, + .val = &test_disctimeout_val, + .len = sizeof(test_disctimeout_val), +}; +static struct bt_action_data prop_test_remote_ble_disc_timeout_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT, + .prop = &prop_test_disctimeout, +}; + +static unsigned char test_uuids_val[] = { 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, + 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00 }; +static bt_property_t prop_test_uuid = { + .type = BT_PROPERTY_UUIDS, + .val = &test_uuids_val, + .len = sizeof(test_uuids_val), +}; +static struct bt_action_data prop_test_remote_ble_uuids_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_UUIDS, + .prop = &prop_test_uuid, +}; + +static uint32_t test_cod_val = 0; +static bt_property_t prop_test_cod = { + .type = BT_PROPERTY_CLASS_OF_DEVICE, + .val = &test_cod_val, + .len = sizeof(test_cod_val), +}; +static struct bt_action_data prop_test_remote_ble_cod_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_CLASS_OF_DEVICE, + .prop = &prop_test_cod, +}; + +static uint32_t test_tod_val = BT_DEVICE_DEVTYPE_BLE; +static bt_property_t prop_test_tod = { + .type = BT_PROPERTY_TYPE_OF_DEVICE, + .val = &test_tod_val, + .len = sizeof(test_tod_val), +}; +static struct bt_action_data prop_test_remote_ble_tod_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_TYPE_OF_DEVICE, + .prop = &prop_test_tod, +}; + +static int32_t test_remote_rssi_val = -9; +static bt_property_t prop_test_remote_rssi = { + .type = BT_PROPERTY_REMOTE_RSSI, + .val = &test_remote_rssi_val, + .len = sizeof(test_remote_rssi_val), +}; +static struct bt_action_data prop_test_remote_ble_rssi_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_REMOTE_RSSI, + .prop = &prop_test_remote_rssi, +}; + +static bt_service_record_t test_srvc_record_val = { + .uuid = { {0x00} }, + .channel = 12, + .name = "bt_name", +}; +static bt_property_t prop_test_srvc_record = { + .type = BT_PROPERTY_SERVICE_RECORD, + .val = &test_srvc_record_val, + .len = sizeof(test_srvc_record_val), +}; +static struct bt_action_data prop_test_remote_ble_srvc_record_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_SERVICE_RECORD, + .prop = &prop_test_srvc_record, +}; + +static bt_bdaddr_t test_bdaddr_val = { + .address = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, +}; +static bt_property_t prop_test_bdaddr = { + .type = BT_PROPERTY_BDADDR, + .val = &test_bdaddr_val, + .len = sizeof(test_bdaddr_val), +}; +static struct bt_action_data prop_test_remote_ble_bdaddr_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_BDADDR, + .prop = &prop_test_bdaddr, +}; +static struct bt_action_data prop_test_bdaddr_req = { + .addr = &test_bdaddr_val, + .prop_type = BT_PROPERTY_BDADDR, + .prop = &prop_test_bdaddr, +}; + +static bt_scan_mode_t setprop_scan_mode_conn_val = BT_SCAN_MODE_CONNECTABLE; + +static bt_property_t prop_test_scan_mode_conn = { + .type = BT_PROPERTY_ADAPTER_SCAN_MODE, + .val = &setprop_scan_mode_conn_val, + .len = sizeof(setprop_scan_mode_conn_val), +}; + +static bt_scan_mode_t test_scan_mode_none_val = BT_SCAN_MODE_NONE; +static bt_property_t prop_test_scan_mode_none = { + .type = BT_PROPERTY_ADAPTER_SCAN_MODE, + .val = &test_scan_mode_none_val, + .len = sizeof(test_scan_mode_none_val), +}; +static struct bt_action_data prop_test_remote_ble_scanmode_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_ADAPTER_SCAN_MODE, + .prop = &prop_test_scan_mode_none, +}; + +static bt_bdaddr_t test_bonded_dev_addr_val = { + .address = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 }, +}; +static bt_property_t prop_test_bonded_dev_addr = { + .type = BT_PROPERTY_ADAPTER_BONDED_DEVICES, + .val = &test_bonded_dev_addr_val, + .len = sizeof(test_bonded_dev_addr_val), +}; +static struct bt_action_data prop_test_ble_bonded_dev_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_ADAPTER_BONDED_DEVICES, + .prop = &prop_test_bonded_dev_addr, +}; + +static uint32_t test_remote_timestamp_val = 42; +static bt_property_t prop_test_remote_ble_timestamp_prop = { + .type = BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP, + .val = &test_remote_timestamp_val, + .len = sizeof(test_remote_timestamp_val), +}; +static struct bt_action_data prop_test_remote_ble_timestamp_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP, + .prop = &prop_test_remote_ble_timestamp_prop, +}; + +static struct bt_action_data ssp_confirm_accept_reply = { + .addr = &emu_remote_bdaddr_val, + .ssp_variant = BT_SSP_VARIANT_PASSKEY_CONFIRMATION, + .accept = TRUE, +}; + +static struct bt_action_data ssp_confirm_reject_reply = { + .addr = &emu_remote_bdaddr_val, + .ssp_variant = BT_SSP_VARIANT_PASSKEY_CONFIRMATION, + .accept = FALSE, +}; + +static struct bt_action_data no_input_no_output_io_cap = { + .io_cap = 0x03, +}; + +static struct bt_action_data display_yes_no_io_cap = { + .io_cap = 0x01, +}; + +static uint16_t test_conn_handle = 0; + +static void conn_cb(uint16_t handle, void *user_data) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + + tester_print("New connection with handle 0x%04x", handle); + + test_conn_handle = handle; + + bthost_request_auth(bthost, handle); +} + +static struct test_case test_cases[] = { + TEST_CASE_BREDRLE("Bluetooth Init", + ACTION_SUCCESS(dummy_action, NULL), + ), + TEST_CASE_BREDRLE("Bluetooth Enable - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_ADAPTER_PROPS(prop_emu_default_set, 8), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ), + TEST_CASE_BREDRLE("Bluetooth Enable - Success 2", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_ADAPTER_PROPS(prop_emu_default_set, 8), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + ), + TEST_CASE_BREDRLE("Bluetooth Disable - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Bluetooth Set BDNAME - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_set_property_action, &prop_test_bdname), + CALLBACK_ADAPTER_PROPS(&prop_test_bdname, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Set SCAN_MODE - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_set_property_action, + &prop_test_scanmode_conn_discov), + CALLBACK_ADAPTER_PROPS(&prop_test_scanmode_conn_discov, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Set DISCOVERY_TIMEOUT - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_set_property_action, &prop_test_disctimeout), + CALLBACK_ADAPTER_PROPS(&prop_test_disctimeout, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Get BDADDR - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_get_property_action, &prop_emu_bdaddr), + CALLBACK_ADAPTER_PROPS(&prop_emu_bdaddr, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Get BDNAME - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_get_property_action, &prop_emu_bdname), + CALLBACK_ADAPTER_PROPS(&prop_emu_bdname, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Set UUID - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_FAIL(bt_set_property_action, &prop_test_uuid), + ), + TEST_CASE_BREDRLE("Bluetooth Set CLASS_OF_DEVICE - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_FAIL(bt_set_property_action, &prop_test_cod), + ), + TEST_CASE_BREDRLE("Bluetooth Set TYPE_OF_DEVICE - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_FAIL(bt_set_property_action, &prop_test_tod), + ), + TEST_CASE_BREDRLE("Bluetooth Set REMOTE_RSSI - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_FAIL(bt_set_property_action, &prop_test_remote_rssi), + ), + TEST_CASE_BREDRLE("Bluetooth Set SERVICE_RECORD - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_FAIL(bt_set_property_action, &prop_test_srvc_record), + ), + TEST_CASE_BREDRLE("Bluetooth Set BDADDR - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_FAIL(bt_set_property_action, &prop_test_bdaddr), + ), + TEST_CASE_BREDRLE("Bluetooth Set SCAN_MODE_CONNECTABLE - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_set_property_action, + &prop_test_scan_mode_conn), + CALLBACK_ADAPTER_PROPS(&prop_test_scan_mode_conn, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Set BONDED_DEVICES - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_FAIL(bt_set_property_action, &prop_test_bonded_dev_addr), + ), + TEST_CASE_BREDRLE("Bluetooth Get CLASS_OF_DEVICE - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_get_property_action, &prop_emu_cod), + CALLBACK_ADAPTER_PROPS(&prop_emu_cod, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Get TYPE_OF_DEVICE - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_get_property_action, &prop_emu_dual_tod), + CALLBACK_ADAPTER_PROPS(&prop_emu_dual_tod, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Get SCAN_MODE - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_get_property_action, &prop_emu_scan_mode), + CALLBACK_ADAPTER_PROPS(&prop_emu_scan_mode, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Get DISCOVERY_TIMEOUT - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_get_property_action, &prop_emu_disc_timeout), + CALLBACK_ADAPTER_PROPS(&prop_emu_disc_timeout, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Get UUIDS - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_get_property_action, &prop_emu_uuids), + CALLBACK_ADAPTER_PROPS(&prop_emu_uuids, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Get BONDED_DEVICES - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_get_property_action, &prop_emu_bonded_devs), + CALLBACK_ADAPTER_PROPS(&prop_emu_bonded_devs, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Set SCAN_MODE - Success 2", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_set_property_action, + &prop_test_scan_mode_none), + CALLBACK_ADAPTER_PROPS(&prop_test_scan_mode_none, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Discovery Start - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ), + TEST_CASE_BREDRLE("Bluetooth Discovery Start - Done", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + ), + TEST_CASE_BREDRLE("Bluetooth Discovery Stop - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ), + TEST_CASE_BREDRLE("Bluetooth Discovery Stop - Done", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + ), + TEST_CASE_BREDRLE("Bluetooth Discovery Device Found", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_ble_default_set, 3), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get Props - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_get_device_props_action, + &emu_remote_bdaddr_val), + CALLBACK_DEVICE_PROPS(prop_emu_remote_bles_query_set, 6), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get BDNAME - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_get_device_prop_action, + &prop_emu_remote_ble_bdname_req), + CALLBACK_DEVICE_PROPS(&prop_emu_remote_ble_bdname_prop, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get UUIDS - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_get_device_prop_action, + &prop_emu_remote_ble_uuids_req), + CALLBACK_DEVICE_PROPS(&prop_emu_remote_ble_uuids_prop, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get COD - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_get_device_prop_action, + &prop_emu_remote_ble_cod_req), + CALLBACK_DEVICE_PROPS(&prop_emu_remote_ble_cod_prop, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get TOD - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_get_device_prop_action, + &prop_emu_remote_ble_tod_req), + CALLBACK_DEVICE_PROPS(&prop_emu_remote_ble_tod_prop, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get RSSI - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_get_device_prop_action, + &prop_emu_remote_ble_rssi_req), + CALLBACK_DEVICE_PROPS(&prop_emu_remote_ble_rssi_prop, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get TIMESTAMP - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_get_device_prop_action, + &prop_emu_remote_ble_timestamp_req), + CALLBACK_DEVICE_PROPS(&prop_emu_remote_ble_timestamp_prop, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get BDADDR - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_get_device_prop_action, + &prop_emu_remote_ble_bdaddr_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get SCAN_MODE - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_get_device_prop_action, + &prop_emu_remote_ble_scan_mode_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get BONDED_DEVICES - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_get_device_prop_action, + &prop_emu_remote_ble_bondeddev_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get DISCOVERY_TIMEOUT - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_get_device_prop_action, + &prop_emu_remote_ble_disctimeout_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get VERSION_INFO - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_get_device_prop_action, + &prop_emu_remote_ble_verinfo_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Get FRIENDLY_NAME - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_get_device_prop_action, + &prop_emu_remote_ble_fname_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set FRIENDLY_NAME - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_set_device_prop_action, + &prop_emu_remote_ble_fname_req), + ACTION_SUCCESS(bt_get_device_prop_action, + &prop_emu_remote_ble_fname_req), + CALLBACK_DEVICE_PROPS(&prop_emu_remote_ble_fname_prop, 1), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set BDNAME - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_remote_ble_bdname_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set UUIDS - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_remote_ble_uuids_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set COD - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_remote_ble_cod_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set TOD - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_remote_ble_tod_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set RSSI - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_remote_ble_rssi_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set TIMESTAMP - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_remote_ble_timestamp_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set BDADDR - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_remote_ble_bdaddr_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set SERVICE_RECORD - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_remote_ble_srvc_record_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set SCAN_MODE - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_remote_ble_scanmode_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set BONDED_DEVICES - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_ble_bonded_dev_req), + ), + TEST_CASE_BREDRLE("Bluetooth Device Set DISCOVERY_TIMEOUT - Fail", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_any_default_set, 1), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_FAIL(bt_set_device_prop_action, + &prop_test_remote_ble_disc_timeout_req), + ), + TEST_CASE_BREDR("Bluetooth Create Bond PIN - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_pin_code_action, &emu_pin_set_req), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_bredr_default_set, 3), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_create_bond_action, + &prop_test_remote_ble_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_PROPS(CB_BT_PIN_REQUEST, prop_emu_remotes_pin_req_set, + 2), + ACTION_SUCCESS(bt_pin_reply_accept_action, + &emu_pin_set_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDED, + &prop_emu_remote_bdadr, 1), + ), + TEST_CASE_BREDR("Bluetooth Create Bond PIN - Bad PIN", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_pin_code_action, &emu_pin_set_req), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_bredr_default_set, 3), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_create_bond_action, + &prop_test_remote_ble_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_PROPS(CB_BT_PIN_REQUEST, prop_emu_remotes_pin_req_set, + 2), + ACTION_SUCCESS(bt_pin_reply_accept_action, + &emu_pin_set_invalid_req), + CALLBACK_BOND_STATE_FAILED(BT_BOND_STATE_NONE, + &prop_emu_remote_bdadr, 1, + BT_STATUS_AUTH_FAILURE), + ), + TEST_CASE_BREDR("Bluetooth Create Bond SSP -Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_io_cap, &display_yes_no_io_cap), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_bredr_default_set, 3), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_create_bond_action, + &prop_test_remote_ble_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_SSP_REQ(BT_SSP_VARIANT_PASSKEY_CONFIRMATION, + prop_emu_remotes_pin_req_set, 2), + ACTION_SUCCESS(bt_ssp_reply_accept_action, + &ssp_confirm_accept_reply), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDED, + &prop_emu_remote_bdadr, 1), + ), + TEST_CASE_BREDR("Bluetooth Create Bond SSP - Negative reply", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_io_cap, &display_yes_no_io_cap), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_bredr_default_set, 3), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_create_bond_action, + &prop_test_remote_ble_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_SSP_REQ(BT_SSP_VARIANT_PASSKEY_CONFIRMATION, + prop_emu_remotes_pin_req_set, 2), + ACTION_SUCCESS(bt_ssp_reply_accept_action, + &ssp_confirm_reject_reply), + CALLBACK_BOND_STATE_FAILED(BT_BOND_STATE_NONE, + &prop_emu_remote_bdadr, 1, + BT_STATUS_AUTH_FAILURE), + ), + TEST_CASE_BREDR("Bluetooth Create Bond - No Discovery", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_io_cap, &display_yes_no_io_cap), + ACTION_SUCCESS(bt_create_bond_action, + &prop_test_remote_ble_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_SSP_REQ(BT_SSP_VARIANT_PASSKEY_CONFIRMATION, + prop_emu_remotes_pin_req_set, 2), + ACTION_SUCCESS(bt_ssp_reply_accept_action, + &ssp_confirm_accept_reply), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDED, + &prop_emu_remote_bdadr, 1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDR("Bluetooth Create Bond - Bad Address", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(bt_create_bond_action, &prop_test_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_test_bdaddr, 1), + CALLBACK_BOND_STATE_FAILED(BT_BOND_STATE_NONE, + &prop_test_bdaddr, 1, + BT_STATUS_FAIL), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDR("Bluetooth Cancel Bonding - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_io_cap, &display_yes_no_io_cap), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_bredr_default_set, 3), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_create_bond_action, + &prop_test_remote_ble_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_SSP_REQ(BT_SSP_VARIANT_PASSKEY_CONFIRMATION, + prop_emu_remotes_pin_req_set, 2), + ACTION_SUCCESS(bt_cancel_bond_action, &emu_remote_bdaddr_val), + CALLBACK_BOND_STATE_FAILED(BT_BOND_STATE_NONE, + &prop_emu_remote_bdadr, 1, + BT_STATUS_FAIL), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDR("Bluetooth Remove Bond - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_io_cap, &display_yes_no_io_cap), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remote_bredr_default_set, 3), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STOPPED), + ACTION_SUCCESS(bt_create_bond_action, + &prop_test_remote_ble_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_SSP_REQ(BT_SSP_VARIANT_PASSKEY_CONFIRMATION, + prop_emu_remotes_pin_req_set, 2), + ACTION_SUCCESS(bt_ssp_reply_accept_action, + &ssp_confirm_accept_reply), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDED, + &prop_emu_remote_bdadr, 1), + ACTION_SUCCESS(bt_remove_bond_action, &emu_remote_bdaddr_val), + CALLBACK_BOND_STATE(BT_BOND_STATE_NONE, + &prop_emu_remote_bdadr, 1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDR("Bluetooth Accept Bond - Just Works - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_set_property_action, + &prop_test_scanmode_conn_discov), + CALLBACK_ADAPTER_PROPS(&prop_test_scanmode_conn_discov, 1), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_io_cap, &no_input_no_output_io_cap), + ACTION_SUCCESS(emu_set_connect_cb_action, conn_cb), + ACTION_SUCCESS(emu_remote_connect_hci_action, NULL), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDED, + &prop_emu_remote_bdadr, 1), + ACTION_SUCCESS(bt_remove_bond_action, &emu_remote_bdaddr_val), + CALLBACK_BOND_STATE(BT_BOND_STATE_NONE, + &prop_emu_remote_bdadr, 1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDR("Bluetooth Accept Bond - No Bond - Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_set_property_action, + &prop_test_scanmode_conn_discov), + CALLBACK_ADAPTER_PROPS(&prop_test_scanmode_conn_discov, 1), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_io_cap, &no_input_no_output_io_cap), + ACTION_SUCCESS(emu_set_connect_cb_action, conn_cb), + ACTION_SUCCESS(emu_remote_connect_hci_action, NULL), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDED, + &prop_emu_remote_bdadr, 1), + ACTION_SUCCESS(emu_remote_disconnect_hci_action, + &test_conn_handle), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_BOND_STATE(BT_BOND_STATE_NONE, + &prop_emu_remote_bdadr, 1), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), +}; + +struct queue *get_bluetooth_tests(void) +{ + uint16_t i = 0; + + list = queue_new(); + + for (; i < sizeof(test_cases) / sizeof(test_cases[0]); ++i) + queue_push_tail(list, &test_cases[i]); + + return list; +} + +void remove_bluetooth_tests(void) +{ + queue_destroy(list, NULL); +} diff --git a/android/tester-gatt.c b/android/tester-gatt.c new file mode 100644 index 0000000..c106097 --- /dev/null +++ b/android/tester-gatt.c @@ -0,0 +1,3693 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include + +#include "emulator/bthost.h" +#include "lib/bluetooth.h" +#include "src/shared/util.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "tester-main.h" + +#define ATT_HANDLE_SIZE 2 + +#define L2CAP_ATT_ERROR 0x01 +#define L2CAP_ATT_EXCHANGE_MTU_REQ 0x02 +#define L2CAP_ATT_EXCHANGE_MTU_RSP 0x03 +#define L2CAP_ATT_FIND_BY_TYPE_REQ 0x06 +#define L2CAP_ATT_READ_REQ 0x0a +#define L2CAP_ATT_READ_RSP 0x0b +#define L2CAP_ATT_WRITE_REQ 0x12 +#define L2CAP_ATT_WRITE_RSP 0x13 +#define L2CAP_ATT_HANDLE_VALUE_NOTIFY 0x1b +#define L2CAP_ATT_HANDLE_VALUE_IND 0x1d + +#define GATT_STATUS_SUCCESS 0x00000000 +#define GATT_STATUS_FAILURE 0x00000101 +#define GATT_STATUS_INS_AUTH 0x08 + +#define GATT_ERR_INVAL_ATTR_VALUE_LEN 0x0D + +#define GATT_SERVER_DISCONNECTED 0 +#define GATT_SERVER_CONNECTED 1 + +#define APP1_ID 1 +#define APP2_ID 2 + +#define CONN1_ID 1 +#define CONN2_ID 2 + +#define TRANS1_ID 1 + +#define BT_TRANSPORT_UNKNOWN 0x00 + +#define GATT_SERVER_TRANSPORT_LE 0x01 +#define GATT_SERVER_TRANSPORT_BREDR 0x02 +#define GATT_SERVER_TRANSPORT_LE_BREDR (0x01 | 0x02) + +#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 CHAR_PROP_BROADCAST 0x01 +#define CHAR_PROP_READ 0x02 +#define CHAR_PROP_WRITE_WITHOUT_RESPONSE 0x04 +#define CHAR_PROP_WRITE 0x08 +#define CHAR_PROP_NOTIFY 0x10 +#define CHAR_PROP_INDICATE 0x20 +#define CHAR_PROP_AUTHENTICATED_SIGNED_WRITES 0x40 +#define CHAR_PROP_EXTENDED_PROPERTIES 0x80 + +#define CHAR_PERM_READ 0x0001 +#define CHAR_PERM_READ_ENCRYPTED 0x0002 +#define CHAR_PERM_READ_ENCRYPTED_MITM 0x0004 +#define CHAR_PERM_WRITE 0x0010 +#define CHAR_PERM_WRITE_ENCRYPTED 0x0020 +#define CHAR_PERM_WRITE_ENCRYPTED_MITM 0x0040 +#define CHAR_PERM_WRITE_SIGNED 0x0080 +#define CHAR_PERM_WRITE_SIGNED_MITM 0x0100 + +static struct queue *list; /* List of gatt test cases */ + +static uint16_t srvc1_handle; +static uint16_t inc_srvc1_handle; +static uint16_t char1_handle; + +static struct iovec char1_handle_v = { + .iov_base = &char1_handle, + .iov_len = sizeof(char1_handle), +}; + +struct set_att_data { + char *to; + char *from; + int len; +}; + +struct att_write_req_data { + uint16_t *attr_handle; + uint8_t *value; +}; + +static bt_uuid_t app1_uuid = { + .uu = { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, +}; + +static bt_uuid_t app2_uuid = { + .uu = { 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }, +}; + +static uint8_t value_1[] = {0x01}; + +static uint8_t att_write_req_value_1[] = {0x00, 0x01, 0x02, 0x03}; +static struct iovec att_write_req_value_1_v = { + .iov_base = att_write_req_value_1, + .iov_len = sizeof(att_write_req_value_1), +}; + +struct gatt_connect_data { + const int app_id; + const int conn_id; +}; + +struct gatt_search_service_data { + const int conn_id; + bt_uuid_t *filter_uuid; +}; + +struct get_char_data { + const int conn_id; + btgatt_srvc_id_t *service; +}; + +struct get_desc_data { + const int conn_id; + btgatt_srvc_id_t *service; + btgatt_gatt_id_t *characteristic; + btgatt_gatt_id_t *desc; +}; + +struct get_incl_data { + const int conn_id; + btgatt_srvc_id_t *service; + btgatt_srvc_id_t *start_service; +}; + +struct read_char_data { + const int conn_id; + btgatt_srvc_id_t *service; + btgatt_gatt_id_t *characteristic; + int auth_req; +}; + +struct read_desc_data { + const int conn_id; + btgatt_srvc_id_t *service; + btgatt_gatt_id_t *characteristic; + btgatt_gatt_id_t *descriptor; + int auth_req; +}; + +struct write_char_data { + int conn_id; + btgatt_srvc_id_t *service; + btgatt_gatt_id_t *characteristic; + int write_type; + int len; + int auth_req; + char *p_value; +}; + +struct write_desc_data { + int conn_id; + btgatt_srvc_id_t *service; + btgatt_gatt_id_t *characteristic; + btgatt_gatt_id_t *descriptor; + int write_type; + int len; + int auth_req; + char *p_value; +}; + +struct notif_data { + int conn_id; + const bt_bdaddr_t *bdaddr; + btgatt_srvc_id_t *service; + btgatt_gatt_id_t *charac; +}; + +struct add_service_data { + int app_id; + btgatt_srvc_id_t *service; + int num_handles; +}; + +struct add_included_service_data { + int app_id; + uint16_t *inc_srvc_handle; + uint16_t *srvc_handle; +}; +struct add_char_data { + int app_id; + uint16_t *srvc_handle; + bt_uuid_t *uuid; + int properties; + int permissions; +}; + +struct add_desc_data { + int app_id; + uint16_t *srvc_handle; + bt_uuid_t *uuid; + int permissions; +}; + +struct start_srvc_data { + int app_id; + uint16_t *srvc_handle; + int transport; +}; + +struct stop_srvc_data { + int app_id; + uint16_t *srvc_handle; +}; + +struct delete_srvc_data { + int app_id; + uint16_t *srvc_handle; +}; + +struct send_indication_data { + int app_id; + uint16_t *attr_handle; + int conn_id; + int len; + int confirm; + char *p_value; +}; + +struct send_resp_data { + int conn_id; + int trans_id; + int status; + btgatt_response_t *response; +}; + +static bt_bdaddr_t emu_remote_bdaddr_val = { + .address = { 0x00, 0xaa, 0x01, 0x01, 0x00, 0x00 }, +}; +static bt_device_type_t emu_remote_ble_device_type = BT_DEVICE_DEVTYPE_BLE; + +static bt_property_t prop_emu_remotes_default_set[] = { + { BT_PROPERTY_BDADDR, sizeof(emu_remote_bdaddr_val), + &emu_remote_bdaddr_val }, +}; +static bt_property_t prop_emu_remotes_default_le_set[] = { + { BT_PROPERTY_BDADDR, sizeof(emu_remote_bdaddr_val), + &emu_remote_bdaddr_val }, + { BT_PROPERTY_TYPE_OF_DEVICE, sizeof(bt_device_type_t), + &emu_remote_ble_device_type }, +}; + +static struct bt_action_data prop_test_remote_ble_bdaddr_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_BDADDR, + .prop = &prop_emu_remotes_default_set[0], +}; + +static bt_scan_mode_t setprop_scan_mode_conn_val = + BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE; + +static bt_property_t prop_test_scan_mode_conn = { + .type = BT_PROPERTY_ADAPTER_SCAN_MODE, + .val = &setprop_scan_mode_conn_val, + .len = sizeof(setprop_scan_mode_conn_val), +}; + +static struct emu_l2cap_cid_data cid_data; + +static struct gatt_connect_data app1_conn_req = { + .app_id = APP1_ID, + .conn_id = CONN1_ID, +}; + +static struct gatt_connect_data app1_conn2_req = { + .app_id = APP1_ID, + .conn_id = CONN2_ID, +}; + +static struct gatt_connect_data app2_conn_req = { + .app_id = APP2_ID, + .conn_id = CONN2_ID, +}; + +static struct gatt_search_service_data search_services_1 = { + .conn_id = CONN1_ID, + .filter_uuid = NULL, +}; + +static const struct iovec exchange_mtu_req_pdu = raw_pdu(0x02, 0xa0, 0x02); +static const struct iovec exchange_mtu_resp_pdu = raw_pdu(0x03, 0xa0, 0x02); + +static struct bt_action_data bearer_type = { + .bearer_type = BDADDR_LE_PUBLIC, +}; + +static btgatt_srvc_id_t service_1 = { + .is_primary = true, + .id = { + .inst_id = 0, + .uuid.uu = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00} + } +}; + +static btgatt_srvc_id_t service_2 = { + .is_primary = true, + .id = { + .inst_id = 1, + .uuid.uu = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x01, 0x18, 0x00, 0x00}, + } +}; + +static btgatt_srvc_id_t service_add_1 = { + .is_primary = true, + .id = { + .inst_id = 0, + .uuid.uu = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0xFF, 0xEF, 0x00, 0x00}, + } +}; + +static btgatt_srvc_id_t service_add_2 = { + .is_primary = true, + .id = { + .inst_id = 1, + .uuid.uu = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0xFF, 0xDF, 0x00, 0x00}, + } +}; + +static btgatt_srvc_id_t service_add_3 = { + .is_primary = true, + .id = { + .inst_id = 2, + .uuid.uu = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0xFF, 0xCF, 0x00, 0x00}, + } +}; + +static btgatt_srvc_id_t included_1 = { + .is_primary = false, + .id = { + .inst_id = 1, + .uuid.uu = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00}, + } +}; + +static btgatt_srvc_id_t included_2 = { + .is_primary = false, + .id = { + .inst_id = 1, + .uuid.uu = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}, + } +}; + +static btgatt_gatt_id_t characteristic_1 = { + .inst_id = 1, + .uuid.uu = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00} +}; + +static btgatt_gatt_id_t desc_1 = { + .inst_id = 1, + .uuid.uu = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00} +}; + +static btgatt_gatt_id_t desc_2 = { + .inst_id = 2, + .uuid.uu = {0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x10, 0x00, 0x00, 0x01, 0x29, 0x00, 0x00} +}; + +static btgatt_read_params_t read_params_1; +static btgatt_write_params_t write_params_1; +static btgatt_notify_params_t notify_params_1; + +static struct get_char_data get_char_data_1 = { + .conn_id = CONN1_ID, + .service = &service_1 +}; + +static struct get_char_data get_char_data_2 = { + .conn_id = CONN1_ID, + .service = &service_2 +}; + +static struct get_desc_data get_desc_data_1 = { + .conn_id = CONN1_ID, + .service = &service_1, + .characteristic = &characteristic_1, +}; + +static struct get_desc_data get_desc_data_2 = { + .conn_id = CONN1_ID, + .service = &service_1, + .characteristic = &characteristic_1, + .desc = &desc_1, +}; + +static struct read_char_data read_char_data_1 = { + .conn_id = CONN1_ID, + .service = &service_1, + .characteristic = &characteristic_1, +}; + +static struct read_char_data read_char_data_2 = { + .conn_id = CONN1_ID, + .service = &service_2, + .characteristic = &characteristic_1, +}; + +static struct read_desc_data read_desc_data_1 = { + .conn_id = CONN1_ID, + .service = &service_1, + .characteristic = &characteristic_1, + .descriptor = &desc_1, +}; + +static struct read_desc_data read_desc_data_2 = { + .conn_id = CONN1_ID, + .service = &service_1, + .characteristic = &characteristic_1, + .descriptor = &desc_2, +}; + +static struct get_incl_data get_incl_data_1 = { + .conn_id = CONN1_ID, + .service = &service_1 +}; + +static char value_2[] = {0x00, 0x01, 0x02, 0x03}; + +static struct write_char_data write_char_data_1 = { + .conn_id = CONN1_ID, + .service = &service_1, + .characteristic = &characteristic_1, + .write_type = GATT_WRITE_TYPE_NO_RESPONSE, + .len = sizeof(value_2), + .p_value = value_2, + .auth_req = 0 +}; + +static struct write_char_data write_char_data_2 = { + .conn_id = CONN1_ID, + .service = &service_1, + .characteristic = &characteristic_1, + .write_type = GATT_WRITE_TYPE_DEFAULT, + .len = sizeof(value_2), + .p_value = value_2, + .auth_req = 0 +}; + +static struct write_desc_data write_desc_data_1 = { + .conn_id = CONN1_ID, + .service = &service_1, + .characteristic = &characteristic_1, + .descriptor = &desc_1, + .write_type = 2, + .len = sizeof(value_2), + .auth_req = 0, + .p_value = value_2, +}; + +static struct write_desc_data write_desc_data_2 = { + .conn_id = CONN1_ID, + .service = &service_1, + .characteristic = &characteristic_1, + .descriptor = &desc_2, + .write_type = 2, + .len = sizeof(value_2), + .auth_req = 0, + .p_value = value_2, +}; + +static struct notif_data notif_data_1 = { + .conn_id = CONN1_ID, + .service = &service_1, + .charac = &characteristic_1, + .bdaddr = &emu_remote_bdaddr_val, +}; + +static struct add_service_data add_service_data_1 = { + .app_id = APP1_ID, + .service = &service_add_1, + .num_handles = 1 +}; + +static struct add_service_data add_service_data_2 = { + .app_id = APP1_ID, + .service = &service_add_2, + .num_handles = 1 +}; + +static struct add_service_data add_service_data_3 = { + .app_id = APP1_ID, + .service = &service_add_3, + .num_handles = 1 +}; + +static struct add_service_data add_service_data_4 = { + .app_id = APP1_ID, + .service = &service_add_1, + .num_handles = 2 +}; + +static struct add_service_data add_service_data_5 = { + .app_id = APP1_ID, + .service = &service_add_1, + .num_handles = 3 +}; + +static struct add_service_data add_service_data_6 = { + .app_id = APP1_ID, + .service = &service_add_1, + .num_handles = 4 +}; + +static struct add_service_data add_bad_service_data_1 = { + .app_id = APP1_ID, + .service = &service_add_1, + .num_handles = 0 +}; + +static struct add_service_data add_sec_service_data_1 = { + .app_id = APP1_ID, + .service = &included_1, + .num_handles = 1 +}; + +static uint16_t srvc_bad_handle = 0xffff; + +static struct add_included_service_data add_inc_service_data_1 = { + .app_id = APP1_ID, + .inc_srvc_handle = &inc_srvc1_handle, + .srvc_handle = &srvc1_handle +}; + +static struct add_included_service_data add_bad_inc_service_data_1 = { + .app_id = APP1_ID, + .inc_srvc_handle = &srvc_bad_handle, + .srvc_handle = &srvc1_handle +}; + +static struct add_char_data add_char_data_1 = { + .app_id = APP1_ID, + .srvc_handle = &srvc1_handle, + .uuid = &app1_uuid, + .properties = 0, + .permissions = 0 +}; + +static struct add_char_data add_char_data_2 = { + .app_id = APP1_ID, + .srvc_handle = &srvc1_handle, + .uuid = &app1_uuid, + .properties = CHAR_PROP_WRITE, + .permissions = CHAR_PERM_WRITE +}; + +static struct add_char_data add_bad_char_data_1 = { + .app_id = APP1_ID, + .srvc_handle = &srvc_bad_handle, + .uuid = &app1_uuid, + .properties = 0, + .permissions = 0 +}; + +static struct add_desc_data add_bad_desc_data_1 = { + .app_id = APP1_ID, + .srvc_handle = &srvc_bad_handle, + .uuid = &app2_uuid, + .permissions = 0 +}; + +static struct add_desc_data add_bad_desc_data_2 = { + .app_id = APP2_ID, + .srvc_handle = &srvc1_handle, + .uuid = &app2_uuid, + .permissions = 0 +}; + +static struct add_desc_data add_desc_data_1 = { + .app_id = APP1_ID, + .srvc_handle = &srvc1_handle, + .uuid = &app2_uuid, + .permissions = 0 +}; + +static struct start_srvc_data start_srvc_data_1 = { + .app_id = APP1_ID, + .srvc_handle = &srvc1_handle, + .transport = GATT_SERVER_TRANSPORT_LE_BREDR +}; + +static struct start_srvc_data start_srvc_data_2 = { + .app_id = APP1_ID, + .srvc_handle = &srvc1_handle, + .transport = GATT_SERVER_TRANSPORT_LE +}; + +static struct start_srvc_data start_bad_srvc_data_1 = { + .app_id = APP1_ID, + .srvc_handle = &srvc_bad_handle, + .transport = GATT_SERVER_TRANSPORT_LE +}; + +static struct start_srvc_data start_bad_srvc_data_2 = { + .app_id = APP1_ID, + .srvc_handle = &srvc1_handle, + .transport = 0 +}; + +static struct stop_srvc_data stop_srvc_data_1 = { + .app_id = APP1_ID, + .srvc_handle = &srvc1_handle +}; + +static struct stop_srvc_data stop_bad_srvc_data_1 = { + .app_id = APP1_ID, + .srvc_handle = &srvc_bad_handle +}; + +static struct delete_srvc_data delete_srvc_data_1 = { + .app_id = APP1_ID, + .srvc_handle = &srvc1_handle +}; + +static struct delete_srvc_data delete_bad_srvc_data_1 = { + .app_id = APP1_ID, + .srvc_handle = &srvc_bad_handle +}; + +static uint16_t srvc_indication_handle_1 = 0x01; + +static struct send_indication_data send_indication_data_1 = { + .app_id = APP1_ID, + .attr_handle = &srvc_indication_handle_1, + .conn_id = CONN1_ID, + .len = sizeof(value_2), + .p_value = value_2, + .confirm = 1 +}; + +static struct send_indication_data send_indication_data_2 = { + .app_id = APP1_ID, + .attr_handle = &srvc_indication_handle_1, + .conn_id = CONN1_ID, + .len = sizeof(value_2), + .p_value = value_2, + .confirm = 0 +}; + +static struct send_indication_data send_bad_indication_data_1 = { + .app_id = APP1_ID, + .attr_handle = &srvc_indication_handle_1, + .conn_id = CONN2_ID, + .len = sizeof(value_2), + .p_value = value_2, + .confirm = 0 +}; + +struct set_read_params { + btgatt_read_params_t *params; + btgatt_srvc_id_t *srvc_id; + btgatt_gatt_id_t *char_id; + btgatt_gatt_id_t *descr_id; + uint8_t *value; + uint16_t len; + uint16_t value_type; + uint8_t status; +}; + +struct set_write_params { + btgatt_write_params_t *params; + btgatt_srvc_id_t *srvc_id; + btgatt_gatt_id_t *char_id; + btgatt_gatt_id_t *descr_id; + uint8_t status; +}; + +struct set_notify_params { + btgatt_notify_params_t *params; + uint8_t *value; + uint16_t len; + uint8_t is_notify; + btgatt_srvc_id_t *srvc_id; + btgatt_gatt_id_t *char_id; + bt_bdaddr_t *bdaddr; +}; + +static struct set_read_params set_read_param_1 = { + .params = &read_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .value = value_1, + .len = sizeof(value_1), + .status = BT_STATUS_SUCCESS +}; + +static struct set_read_params set_read_param_2 = { + .params = &read_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .status = GATT_STATUS_INS_AUTH +}; + +static struct set_read_params set_read_param_3 = { + .params = &read_params_1, + .srvc_id = &service_2, + .char_id = &characteristic_1, + .status = BT_STATUS_FAIL +}; + +static struct set_read_params set_read_param_4 = { + .params = &read_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .descr_id = &desc_1, + .value = value_1, + .len = sizeof(value_1), + .status = BT_STATUS_SUCCESS +}; + +static struct set_read_params set_read_param_5 = { + .params = &read_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .descr_id = &desc_1, + .status = GATT_STATUS_INS_AUTH +}; + +static struct set_read_params set_read_param_6 = { + .params = &read_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .descr_id = &desc_2, + .status = BT_STATUS_FAIL +}; + +static struct set_write_params set_write_param_1 = { + .params = &write_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .status = BT_STATUS_SUCCESS +}; + +static struct set_write_params set_write_param_2 = { + .params = &write_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .status = GATT_STATUS_INS_AUTH +}; + +static struct set_write_params set_write_param_3 = { + .params = &write_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .status = BT_STATUS_FAIL +}; + +static struct set_write_params set_write_param_4 = { + .params = &write_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .descr_id = &desc_1, + .status = BT_STATUS_SUCCESS +}; + +static struct set_write_params set_write_param_5 = { + .params = &write_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .descr_id = &desc_2, + .status = BT_STATUS_FAIL +}; + +static struct set_write_params set_write_param_6 = { + .params = &write_params_1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .descr_id = &desc_1, + .status = GATT_STATUS_INS_AUTH +}; + +static struct set_notify_params set_notify_param_1 = { + .params = ¬ify_params_1, + .value = value_1, + .len = sizeof(value_1), + .is_notify = 0, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .bdaddr = &emu_remote_bdaddr_val +}; + +static struct set_notify_params set_notify_param_2 = { + .params = ¬ify_params_1, + .value = value_1, + .len = sizeof(value_1), + .is_notify = 1, + .srvc_id = &service_1, + .char_id = &characteristic_1, + .bdaddr = &emu_remote_bdaddr_val +}; + +static btgatt_response_t response_1 = { + .handle = 0x1c, + .attr_value.auth_req = 0, + .attr_value.handle = 0x1d, + .attr_value.len = 0, + .attr_value.offset = 0, +}; + +static btgatt_response_t response_2 = { + .handle = 0x1c, + .attr_value.auth_req = 0, + .attr_value.handle = 0x1d, + .attr_value.len = sizeof(att_write_req_value_1), + .attr_value.offset = 0, +}; + +static struct send_resp_data send_resp_data_1 = { + .conn_id = CONN1_ID, + .trans_id = TRANS1_ID, + .status = BT_STATUS_SUCCESS, + .response = &response_1, +}; + +static struct send_resp_data send_resp_data_2 = { + .conn_id = CONN1_ID, + .trans_id = TRANS1_ID, + .status = BT_STATUS_SUCCESS, + .response = &response_2, +}; + +static struct send_resp_data send_resp_data_2_error = { + .conn_id = CONN1_ID, + .trans_id = TRANS1_ID, + .status = GATT_ERR_INVAL_ATTR_VALUE_LEN, + .response = &response_2, +}; + +#define SEARCH_SERVICE_SINGLE_SUCCESS_PDUS \ + raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), \ + raw_pdu(0x11, 0x06, 0x01, 0x00, 0x10, 0x00, 0x00, 0x18), \ + raw_pdu(0x10, 0x11, 0x00, 0xff, 0xff, 0x00, 0x28), \ + raw_pdu(0x01, 0x10, 0x11, 0x00, 0x0a) + +#define READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS \ + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x03, 0x28), \ + raw_pdu(0x09, 0x07, 0x02, 0x00, 0x04, 0x00, 0x00, 0x19, 0x00), \ + raw_pdu(0x08, 0x03, 0x00, 0x10, 0x00, 0x03, 0x28), \ + raw_pdu(0x01, 0x08, 0x03, 0x00, 0x0a) + +static struct iovec search_service[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + end_pdu +}; + +static struct iovec search_service_2[] = { + raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), + raw_pdu(0x11, 0x06, 0x01, 0x00, 0x10, 0x00, 0x00, 0x18), + raw_pdu(0x10, 0x11, 0x00, 0xff, 0xff, 0x00, 0x28), + raw_pdu(0x11, 0x06, 0x11, 0x00, 0x20, 0x00, 0x01, 0x18), + raw_pdu(0x10, 0x21, 0x00, 0xff, 0xff, 0x00, 0x28), + raw_pdu(0x01, 0x10, 0x21, 0x00, 0x0a), + end_pdu +}; + +static struct iovec search_service_3[] = { + raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), + raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a), + end_pdu +}; + +static struct iovec search_service_4[] = { + raw_pdu(0x10, 0x01, 0x00, 0xff, 0xff, 0x00, 0x28), + raw_pdu(0x11, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x18), + end_pdu +}; + +static struct iovec get_characteristic_1[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + end_pdu +}; + +static struct iovec get_characteristic_2[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x09, 0x07, 0x00, 0x00, 0x04, 0x00, 0x00, 0x19, 0x00), + end_pdu +}; + +static struct iovec get_descriptor_0[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + raw_pdu(0x04, 0x01, 0x00, 0x10, 0x00), + raw_pdu(0x05, 0x01, 0x00, 0x00, 0x00, 0x29), + end_pdu +}; + +static struct iovec get_descriptor_1[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + raw_pdu(0x04, 0x01, 0x00, 0x10, 0x00), + raw_pdu(0x05, 0x01, 0x04, 0x00, 0x00, 0x29), + raw_pdu(0x04, 0x05, 0x00, 0x10, 0x00), + raw_pdu(0x01, 0x04, 0x05, 0x00, 0x0a), + end_pdu +}; + +static struct iovec get_descriptor_2[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + raw_pdu(0x04, 0x01, 0x00, 0x10, 0x00), + raw_pdu(0x05, 0x01, 0x04, 0x00, 0x00, 0x29, 0x05, 0x00, 0x01, 0x29), + raw_pdu(0x04, 0x06, 0x00, 0x10, 0x00), + raw_pdu(0x01, 0x04, 0x06, 0x00, 0x0a), + end_pdu +}; + +static struct iovec get_descriptor_3[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + raw_pdu(0x04, 0x01, 0x00, 0x10, 0x00), + raw_pdu(0x01, 0x04, 0x01, 0x00, 0x0a), + end_pdu +}; + +static struct iovec get_included_0[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x02, 0x28), + raw_pdu(0x09, 0x08, 0x00, 0x00, 0x15, 0x00, 0x19, 0x00, 0xff, 0xfe), + end_pdu +}; + +static struct iovec get_included_1[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x02, 0x28), + raw_pdu(0x09, 0x08, 0x02, 0x00, 0x15, 0x00, 0x19, 0x00, 0xff, 0xfe), + raw_pdu(0x08, 0x03, 0x00, 0x10, 0x00, 0x02, 0x28), + raw_pdu(0x01, 0x08, 0x03, 0x00, 0x0a), + end_pdu +}; + +static struct iovec get_included_2[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x02, 0x28), + raw_pdu(0x09, 0x06, 0x02, 0x00, 0x15, 0x00, 0x19, 0x00), + raw_pdu(0x0a, 0x15, 0x00), + raw_pdu(0x0b, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10), + raw_pdu(0x08, 0x03, 0x00, 0x10, 0x00, 0x02, 0x28), + raw_pdu(0x01, 0x08, 0x03, 0x00, 0x0a), + end_pdu +}; + +static struct iovec get_included_3[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x02, 0x28), + raw_pdu(0x01, 0x08, 0x01, 0x00, 0x0a), + end_pdu +}; + +static struct iovec read_characteristic_1[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x09, 0x07, 0x02, 0x00, 0x04, 0x03, 0x00, 0x19, 0x00), + raw_pdu(0x08, 0x03, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x01, 0x08, 0x03, 0x00, 0x0a), + raw_pdu(0x0a, 0x03, 0x00), + raw_pdu(0x0b, 0x01), + end_pdu +}; + +static struct iovec read_characteristic_2[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x09, 0x07, 0x02, 0x00, 0x04, 0x03, 0x00, 0x19, 0x00), + raw_pdu(0x08, 0x03, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x01, 0x08, 0x03, 0x00, 0x0a), + raw_pdu(0x0a, 0x03, 0x00), + raw_pdu(0x01, 0x0a, 0x03, 0x00, 0x08), + end_pdu +}; + +static struct iovec read_descriptor_1[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + raw_pdu(0x04, 0x01, 0x00, 0x10, 0x00), + raw_pdu(0x05, 0x01, 0x04, 0x00, 0x00, 0x29), + raw_pdu(0x04, 0x05, 0x00, 0x10, 0x00), + raw_pdu(0x01, 0x04, 0x05, 0x00, 0x0a), + raw_pdu(0x0a, 0x04, 0x00), + raw_pdu(0x0b, 0x01), + end_pdu +}; + +static struct iovec read_descriptor_2[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + raw_pdu(0x04, 0x01, 0x00, 0x10, 0x00), + raw_pdu(0x05, 0x01, 0x04, 0x00, 0x00, 0x29), + raw_pdu(0x04, 0x05, 0x00, 0x10, 0x00), + raw_pdu(0x01, 0x04, 0x05, 0x00, 0x0a), + raw_pdu(0x0a, 0x04, 0x00), + raw_pdu(0x01, 0x0a, 0x04, 0x00, 0x08), + end_pdu +}; + +static struct iovec write_characteristic_1[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x09, 0x07, 0x02, 0x00, 0x04, 0x03, 0x00, 0x19, 0x00), + raw_pdu(0x08, 0x03, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x01, 0x08, 0x03, 0x00, 0x0a), + raw_pdu(0x52, 0x03, 0x00, 0x00, 0x01, 0x02, 0x03), + end_pdu +}; + +static struct iovec write_characteristic_2[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x09, 0x07, 0x02, 0x00, 0x04, 0x03, 0x00, 0x19, 0x00), + raw_pdu(0x08, 0x03, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x01, 0x08, 0x03, 0x00, 0x0a), + raw_pdu(0x12, 0x03, 0x00, 0x00, 0x01, 0x02, 0x03), + raw_pdu(0x13), + end_pdu +}; + +static struct iovec write_characteristic_3[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + raw_pdu(0x08, 0x01, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x09, 0x07, 0x02, 0x00, 0x04, 0x03, 0x00, 0x19, 0x00), + raw_pdu(0x08, 0x03, 0x00, 0x10, 0x00, 0x03, 0x28), + raw_pdu(0x01, 0x08, 0x03, 0x00, 0x0a), + raw_pdu(0x12, 0x03, 0x00, 0x00, 0x01, 0x02, 0x03), + raw_pdu(0x01, 0x12, 0x03, 0x00, 0x08), + end_pdu +}; + +static struct iovec write_descriptor_1[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + raw_pdu(0x04, 0x01, 0x00, 0x10, 0x00), + raw_pdu(0x05, 0x01, 0x04, 0x00, 0x00, 0x29), + raw_pdu(0x04, 0x05, 0x00, 0x10, 0x00), + raw_pdu(0x01, 0x04, 0x05, 0x00, 0x0a), + raw_pdu(0x12, 0x04, 0x00, 0x00, 0x01, 0x02, 0x03), + raw_pdu(0x13), + end_pdu +}; + +static struct iovec write_descriptor_2[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + raw_pdu(0x04, 0x01, 0x00, 0x10, 0x00), + raw_pdu(0x05, 0x01, 0x04, 0x00, 0x00, 0x29), + raw_pdu(0x04, 0x05, 0x00, 0x10, 0x00), + raw_pdu(0x01, 0x04, 0x05, 0x00, 0x0a), + raw_pdu(0x12, 0x04, 0x00, 0x00, 0x01, 0x02, 0x03), + raw_pdu(0x01, 0x12, 0x04, 0x00, 0x08), + end_pdu +}; + +static struct iovec notification_1[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + end_pdu +}; + +static struct iovec notification_2[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + raw_pdu(0x1d, 0x03, 0x00, 0x01), + raw_pdu(0x1e), + end_pdu +}; + +static struct iovec notification_3[] = { + SEARCH_SERVICE_SINGLE_SUCCESS_PDUS, + READ_BY_TYPE_SINGLE_CHARACTERISTIC_PDUS, + raw_pdu(0x1b, 0x03, 0x00, 0x01), + end_pdu +}; + +static struct iovec send_indication_1[] = { + raw_pdu(0x1d, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03), + raw_pdu(0x1e), + end_pdu +}; + +static struct iovec send_notification_1[] = { + raw_pdu(0x1b, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03), + end_pdu +}; + +static struct iovec search_range_1[] = { + raw_pdu(0x01, 0xff, 0xff, 0xff), + end_pdu +}; + +static struct iovec primary_type = raw_pdu(0x00, 0x28); + +/* att commands define raw pdus */ +static struct iovec att_read_req_op_v = raw_pdu(L2CAP_ATT_READ_REQ); +static struct iovec att_write_req_op_v = raw_pdu(L2CAP_ATT_WRITE_REQ); +static struct iovec att_find_by_type_req_op_v = + raw_pdu(L2CAP_ATT_FIND_BY_TYPE_REQ); + +static struct iovec svc_change_ccc_handle_v = raw_pdu(0x1c, 0x00); +static struct iovec svc_change_ccc_value_v = raw_pdu(0x00, 0x01); + +static void gatt_client_register_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + bt_uuid_t *app_uuid = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + if (!app_uuid) { + tester_warn("No app uuid provided for register action."); + return; + } + + step->action_status = data->if_gatt->client->register_client(app_uuid); + + schedule_action_verification(step); +} + +static void gatt_client_unregister_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + int32_t cl_id = PTR_TO_INT(current_data_step->set_data); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->client->unregister_client(cl_id); + + schedule_action_verification(step); +} + +static void gatt_client_start_scan_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->client->scan(TRUE); + + schedule_action_verification(step); +} + +static void gatt_client_stop_scan_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->client->scan(FALSE); + + schedule_action_verification(step); +} + +static void gatt_client_connect_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct gatt_connect_data *conn_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->client->connect( + conn_data->app_id, + &emu_remote_bdaddr_val, 0, + BT_TRANSPORT_UNKNOWN); + + schedule_action_verification(step); +} + +static void gatt_client_disconnect_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct gatt_connect_data *conn_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->client->disconnect( + conn_data->app_id, + &emu_remote_bdaddr_val, + conn_data->conn_id); + + schedule_action_verification(step); +} + +static void gatt_client_do_listen_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct gatt_connect_data *conn_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->client->listen( + conn_data->app_id, + 1); + + schedule_action_verification(step); +} + +static void gatt_client_stop_listen_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct gatt_connect_data *conn_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->client->listen( + conn_data->app_id, + 0); + + schedule_action_verification(step); +} + +static void gatt_client_get_characteristic_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct get_char_data *get_char = current_data_step->set_data; + const btgatt_client_interface_t *client = data->if_gatt->client; + struct step *step = g_new0(struct step, 1); + int status; + + status = client->get_characteristic(get_char->conn_id, + get_char->service, NULL); + step->action_status = status; + + schedule_action_verification(step); +} + +static void gatt_client_get_descriptor_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct get_desc_data *get_desc = current_data_step->set_data; + const btgatt_client_interface_t *client = data->if_gatt->client; + struct step *step = g_new0(struct step, 1); + int status; + + status = client->get_descriptor(get_desc->conn_id, get_desc->service, + get_desc->characteristic, + get_desc->desc); + step->action_status = status; + + schedule_action_verification(step); +} + +static void gatt_client_get_included_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct get_incl_data *get_incl = current_data_step->set_data; + const btgatt_client_interface_t *client = data->if_gatt->client; + struct step *step = g_new0(struct step, 1); + int status; + + status = client->get_included_service(get_incl->conn_id, + get_incl->service, get_incl->start_service); + + step->action_status = status; + + schedule_action_verification(step); +} + +static void gatt_client_read_characteristic_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct read_char_data *read_char_data = current_data_step->set_data; + const btgatt_client_interface_t *client = data->if_gatt->client; + struct step *step = g_new0(struct step, 1); + int status; + + status = client->read_characteristic(read_char_data->conn_id, + read_char_data->service, read_char_data->characteristic, + read_char_data->auth_req); + + step->action_status = status; + + schedule_action_verification(step); +} + +static void gatt_client_read_descriptor_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct read_desc_data *read_desc_data = current_data_step->set_data; + const btgatt_client_interface_t *client = data->if_gatt->client; + struct step *step = g_new0(struct step, 1); + int status; + + status = client->read_descriptor(read_desc_data->conn_id, + read_desc_data->service, read_desc_data->characteristic, + read_desc_data->descriptor, + read_desc_data->auth_req); + + step->action_status = status; + + schedule_action_verification(step); +} + +static void gatt_client_write_characteristic_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct write_char_data *write_char_data = current_data_step->set_data; + const btgatt_client_interface_t *client = data->if_gatt->client; + struct step *step = g_new0(struct step, 1); + int status; + + status = client->write_characteristic(write_char_data->conn_id, + write_char_data->service, + write_char_data->characteristic, + write_char_data->write_type, + write_char_data->len, + write_char_data->auth_req, + write_char_data->p_value); + + step->action_status = status; + + schedule_action_verification(step); +} + +static void gatt_client_register_for_notification_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct notif_data *notif_data = current_data_step->set_data; + const btgatt_client_interface_t *client = data->if_gatt->client; + struct step *step = g_new0(struct step, 1); + int status; + + status = client->register_for_notification(notif_data->conn_id, + notif_data->bdaddr, + notif_data->service, + notif_data->charac); + step->action_status = status; + + schedule_action_verification(step); +} + +static void gatt_client_deregister_for_notification_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct notif_data *notif_data = current_data_step->set_data; + const btgatt_client_interface_t *client = data->if_gatt->client; + struct step *step = g_new0(struct step, 1); + int status; + + status = client->deregister_for_notification(notif_data->conn_id, + notif_data->bdaddr, + notif_data->service, + notif_data->charac); + step->action_status = status; + + schedule_action_verification(step); +} + +static void gatt_server_register_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + bt_uuid_t *app_uuid = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + if (!app_uuid) { + tester_warn("No app uuid provided for register action."); + return; + } + + step->action_status = data->if_gatt->server->register_server(app_uuid); + + schedule_action_verification(step); +} + +static void gatt_server_unregister_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + int32_t sr_id = PTR_TO_INT(current_data_step->set_data); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->unregister_server(sr_id); + + schedule_action_verification(step); +} + +static void gatt_server_connect_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct gatt_connect_data *conn_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->connect( + conn_data->app_id, + &emu_remote_bdaddr_val, 0, + BT_TRANSPORT_UNKNOWN); + + schedule_action_verification(step); +} + +static void gatt_server_disconnect_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct gatt_connect_data *conn_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->disconnect( + conn_data->app_id, + &emu_remote_bdaddr_val, + conn_data->conn_id); + + schedule_action_verification(step); +} + +static void gatt_server_add_service_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct add_service_data *add_srvc_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->add_service( + add_srvc_data->app_id, + add_srvc_data->service, + add_srvc_data->num_handles); + + schedule_action_verification(step); +} + +static void gatt_server_add_inc_service_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct add_included_service_data *add_inc_srvc_data = + current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->add_included_service( + add_inc_srvc_data->app_id, + *add_inc_srvc_data->srvc_handle, + *add_inc_srvc_data->inc_srvc_handle); + + schedule_action_verification(step); +} + +static void gatt_server_add_char_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct add_char_data *add_char_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->add_characteristic( + add_char_data->app_id, + *add_char_data->srvc_handle, + add_char_data->uuid, + add_char_data->properties, + add_char_data->permissions); + + schedule_action_verification(step); +} + +static void gatt_server_add_desc_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct add_desc_data *add_desc_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->add_descriptor( + add_desc_data->app_id, + *add_desc_data->srvc_handle, + add_desc_data->uuid, + add_desc_data->permissions); + + schedule_action_verification(step); +} + +static void gatt_client_write_descriptor_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct write_desc_data *write_desc_data = current_data_step->set_data; + const btgatt_client_interface_t *client = data->if_gatt->client; + struct step *step = g_new0(struct step, 1); + int status; + + status = client->write_descriptor(write_desc_data->conn_id, + write_desc_data->service, + write_desc_data->characteristic, + write_desc_data->descriptor, + write_desc_data->write_type, + write_desc_data->len, + write_desc_data->auth_req, + write_desc_data->p_value); + + step->action_status = status; + + schedule_action_verification(step); +} + +static void gatt_server_start_srvc_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct start_srvc_data *start_srvc_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->start_service( + start_srvc_data->app_id, + *start_srvc_data->srvc_handle, + start_srvc_data->transport); + + schedule_action_verification(step); +} + +static void gatt_server_stop_srvc_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct stop_srvc_data *stop_srvc_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->stop_service( + stop_srvc_data->app_id, + *stop_srvc_data->srvc_handle); + + schedule_action_verification(step); +} + +static void gatt_server_delete_srvc_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct delete_srvc_data *delete_srvc_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->delete_service( + delete_srvc_data->app_id, + *delete_srvc_data->srvc_handle); + + schedule_action_verification(step); +} + +static void gatt_server_send_indication_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct send_indication_data *send_indication_data = + current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->send_indication( + send_indication_data->app_id, + *send_indication_data->attr_handle, + send_indication_data->conn_id, + send_indication_data->len, + send_indication_data->confirm, + send_indication_data->p_value); + + schedule_action_verification(step); +} + +static void gatt_server_send_response_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct send_resp_data *send_resp_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_gatt->server->send_response( + send_resp_data->conn_id, + send_resp_data->trans_id, + send_resp_data->status, + send_resp_data->response); + + schedule_action_verification(step); +} + +static void gatt_cid_hook_cb(const void *data, uint16_t len, void *user_data) +{ + struct test_data *t_data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(t_data->hciemu); + struct emu_l2cap_cid_data *cid_data = user_data; + const uint8_t *pdu = data; + struct iovec *gatt_pdu = queue_peek_head(t_data->pdus); + struct step *step; + + tester_debug("Received att pdu with opcode 0x%02x", pdu[0]); + + switch (pdu[0]) { + case L2CAP_ATT_ERROR: + step = g_new0(struct step, 1); + + step->callback = CB_EMU_ATT_ERROR; + step->callback_result.error = pdu[4]; + + schedule_callback_verification(step); + break; + case L2CAP_ATT_EXCHANGE_MTU_REQ: + tester_print("Exchange MTU request received."); + + if (!memcmp(exchange_mtu_req_pdu.iov_base, pdu, len)) + bthost_send_cid_v(bthost, cid_data->handle, + cid_data->cid, + &exchange_mtu_resp_pdu, 1); + + break; + case L2CAP_ATT_EXCHANGE_MTU_RSP: + tester_print("Exchange MTU response received."); + + break; + case L2CAP_ATT_HANDLE_VALUE_IND: + step = g_new0(struct step, 1); + + step->callback = CB_EMU_VALUE_INDICATION; + + schedule_callback_verification(step); + goto respond; + case L2CAP_ATT_HANDLE_VALUE_NOTIFY: + step = g_new0(struct step, 1); + + step->callback = CB_EMU_VALUE_NOTIFICATION; + + schedule_callback_verification(step); + break; + case L2CAP_ATT_READ_RSP: + /* TODO - More complicated cases should also verify pdu data */ + step = g_new0(struct step, 1); + + step->callback = CB_EMU_READ_RESPONSE; + + schedule_callback_verification(step); + break; + case L2CAP_ATT_WRITE_RSP: + /* TODO - More complicated cases should also verify pdu data */ + step = g_new0(struct step, 1); + + step->callback = CB_EMU_WRITE_RESPONSE; + + schedule_callback_verification(step); + break; + default: + if (!gatt_pdu || !gatt_pdu->iov_base) { + tester_print("Unknown ATT packet."); + break; + } + + if (gatt_pdu->iov_len != len) { + tester_print("Size of incoming frame is not valid"); + tester_print("Expected size = %zd incoming size = %d", + gatt_pdu->iov_len, len); + break; + } + +respond: + if (memcmp(gatt_pdu->iov_base, data, len)) { + tester_print("Incoming data mismatch"); + break; + } + queue_pop_head(t_data->pdus); + gatt_pdu = queue_pop_head(t_data->pdus); + if (!gatt_pdu || !gatt_pdu->iov_base) + break; + + bthost_send_cid_v(bthost, cid_data->handle, cid_data->cid, + gatt_pdu, 1); + + break; + } +} + +static void gatt_remote_send_frame_action(void) +{ + struct test_data *t_data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(t_data->hciemu); + struct iovec *gatt_pdu = queue_pop_head(t_data->pdus); + struct step *step = g_new0(struct step, 1); + + if (!gatt_pdu) { + tester_print("No frame to send"); + step->action_status = BT_STATUS_FAIL; + } else { + bthost_send_cid_v(bthost, cid_data.handle, cid_data.cid, + gatt_pdu, 1); + step->action_status = BT_STATUS_SUCCESS; + } + + schedule_action_verification(step); +} + +static void gatt_remote_send_raw_pdu_action(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + struct step *current_data_step = queue_peek_head(data->steps); + struct iovec *pdu = current_data_step->set_data; + struct iovec *pdu2 = current_data_step->set_data_2; + struct iovec *pdu3 = current_data_step->set_data_3; + struct step *step = g_new0(struct step, 1); + + if (cid_data.handle && cid_data.cid) { + struct iovec rsp[3]; + size_t len = 0; + + if (!pdu) { + step->action_status = BT_STATUS_FAIL; + goto done; + } + + rsp[0].iov_base = pdu->iov_base; + rsp[0].iov_len = pdu->iov_len; + len++; + + if (pdu2) { + rsp[1].iov_base = pdu2->iov_base; + rsp[1].iov_len = pdu2->iov_len; + len++; + } + + if (pdu3) { + rsp[2].iov_base = pdu3->iov_base; + rsp[2].iov_len = pdu3->iov_len; + len++; + } + + bthost_send_cid_v(bthost, cid_data.handle, cid_data.cid, rsp, + len); + step->action_status = BT_STATUS_SUCCESS; + } else { + tester_debug("No connection set up"); + step->action_status = BT_STATUS_FAIL; + } + +done: + schedule_action_verification(step); +} + +static void gatt_conn_cb(uint16_t handle, void *user_data) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + + tester_print("New connection with handle 0x%04x", handle); + + if (data->hciemu_type == HCIEMU_TYPE_BREDR) { + tester_warn("Not handled device type."); + return; + } + + cid_data.cid = 0x0004; + cid_data.handle = handle; + + bthost_add_cid_hook(bthost, handle, cid_data.cid, gatt_cid_hook_cb, + &cid_data); +} + +static void gatt_client_search_services(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct step *step = g_new0(struct step, 1); + struct gatt_search_service_data *search_data; + int status; + + search_data = current_data_step->set_data; + + status = data->if_gatt->client->search_service(search_data->conn_id, + search_data->filter_uuid); + step->action_status = status; + + schedule_action_verification(step); +} + +static void init_pdus(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct step *step = g_new0(struct step, 1); + struct iovec *pdu = current_data_step->set_data; + + while (pdu->iov_base) { + queue_push_tail(data->pdus, pdu); + pdu++; + } + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +static void init_read_params_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct step *step = g_new0(struct step, 1); + struct set_read_params *set_param_data = current_data_step->set_data; + btgatt_read_params_t *param = set_param_data->params; + + memset(param, 0, sizeof(*param)); + + if (set_param_data->srvc_id) + memcpy(¶m->srvc_id, set_param_data->srvc_id, + sizeof(btgatt_srvc_id_t)); + + if (set_param_data->char_id) + memcpy(¶m->char_id, set_param_data->char_id, + sizeof(btgatt_gatt_id_t)); + + if (set_param_data->descr_id) + memcpy(¶m->descr_id, set_param_data->descr_id, + sizeof(btgatt_gatt_id_t)); + + param->value_type = set_param_data->value_type; + param->status = set_param_data->status; + param->value.len = set_param_data->len; + + if (param->value.len != 0) + memcpy(¶m->value.value, set_param_data->value, + param->value.len); + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +static void init_write_params_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct step *step = g_new0(struct step, 1); + struct set_write_params *set_param_data = current_data_step->set_data; + btgatt_write_params_t *param = set_param_data->params; + + memset(param, 0, sizeof(*param)); + + if (set_param_data->srvc_id) + memcpy(¶m->srvc_id, set_param_data->srvc_id, + sizeof(btgatt_srvc_id_t)); + + if (set_param_data->char_id) + memcpy(¶m->char_id, set_param_data->char_id, + sizeof(btgatt_gatt_id_t)); + + if (set_param_data->descr_id) + memcpy(¶m->descr_id, set_param_data->descr_id, + sizeof(btgatt_gatt_id_t)); + + param->status = set_param_data->status; + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +static void init_notify_params_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct step *step = g_new0(struct step, 1); + struct set_notify_params *set_param_data = current_data_step->set_data; + btgatt_notify_params_t *param = set_param_data->params; + + memset(param, 0, sizeof(*param)); + + if (set_param_data->srvc_id) + memcpy(¶m->srvc_id, set_param_data->srvc_id, + sizeof(btgatt_srvc_id_t)); + + if (set_param_data->char_id) + memcpy(¶m->char_id, set_param_data->char_id, + sizeof(btgatt_gatt_id_t)); + + param->len = set_param_data->len; + param->is_notify = set_param_data->is_notify; + + memcpy(¶m->bda, set_param_data->bdaddr, sizeof(bt_bdaddr_t)); + if (param->len != 0) + memcpy(¶m->value, set_param_data->value, param->len); + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +static void trigger_device_found(void *user_data) +{ + emu_setup_powered_remote_action(); +} + +static void delayemu_setup_powered_remote_action(void) +{ + /* Make sure discovery is enabled before enabling advertising. + * Unfortunately GATT HAL doesn't have discovering callback like + * Bluetooth HAL so we need to delay + */ + tester_wait(1, trigger_device_found, NULL); +} + +static struct test_case test_cases[] = { + TEST_CASE_BREDRLE("Gatt Init", + ACTION_SUCCESS(dummy_action, NULL), + ), + TEST_CASE_BREDRLE("Gatt Client - Register", + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ), + TEST_CASE_BREDRLE("Gatt Client - Unregister", + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_unregister_action, + INT_TO_PTR(APP1_ID)), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ), + TEST_CASE_BREDRLE("Gatt Client - Scan", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - LE Connect", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - LE Disconnect", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_disconnect_action, + &app1_conn_req), + CALLBACK_GATTC_DISCONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - LE Multiple Client Conn./Disc.", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_register_action, &app2_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_connect_action, &app2_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN2_ID, APP2_ID), + ACTION_SUCCESS(gatt_client_disconnect_action, + &app2_conn_req), + CALLBACK_GATTC_DISCONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN2_ID, APP2_ID), + ACTION_SUCCESS(gatt_client_disconnect_action, + &app1_conn_req), + CALLBACK_GATTC_DISCONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Listen and Disconnect", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(bt_set_property_action, + &prop_test_scan_mode_conn), + CALLBACK_ADAPTER_PROPS(&prop_test_scan_mode_conn, 1), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_do_listen_action, &app1_conn_req), + CALLBACK_STATUS(CB_GATTC_LISTEN, GATT_STATUS_SUCCESS), + ACTION_SUCCESS(emu_remote_connect_hci_action, &bearer_type), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_stop_listen_action, + &app1_conn_req), + CALLBACK_STATUS(CB_GATTC_LISTEN, GATT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_disconnect_action, + &app1_conn_req), + CALLBACK_GATTC_DISCONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Double Listen", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(bt_set_property_action, + &prop_test_scan_mode_conn), + CALLBACK_ADAPTER_PROPS(&prop_test_scan_mode_conn, 1), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_do_listen_action, &app1_conn_req), + CALLBACK_STATUS(CB_GATTC_LISTEN, GATT_STATUS_SUCCESS), + ACTION_SUCCESS(emu_remote_connect_hci_action, &bearer_type), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_stop_listen_action, + &app1_conn_req), + CALLBACK_STATUS(CB_GATTC_LISTEN, GATT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_disconnect_action, + &app1_conn_req), + CALLBACK_GATTC_DISCONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + /* Close ACL on emulated remotes side so it can reconnect */ + ACTION_SUCCESS(emu_remote_disconnect_hci_action, + &cid_data.handle), + CALLBACK_STATE(CB_BT_ACL_STATE_CHANGED, + BT_ACL_STATE_DISCONNECTED), + ACTION_SUCCESS(gatt_client_do_listen_action, &app1_conn_req), + CALLBACK_STATUS(CB_GATTC_LISTEN, GATT_STATUS_SUCCESS), + ACTION_SUCCESS(emu_remote_connect_hci_action, &bearer_type), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN2_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_disconnect_action, + &app1_conn2_req), + CALLBACK_GATTC_DISCONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN2_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_stop_listen_action, + &app1_conn_req), + CALLBACK_STATUS(CB_GATTC_LISTEN, GATT_STATUS_SUCCESS), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Search Service - Single", + ACTION_SUCCESS(init_pdus, search_service), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_RESULT(CONN1_ID, &service_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Search Service - Multiple", + ACTION_SUCCESS(init_pdus, search_service_2), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_RESULT(CONN1_ID, &service_1), + CALLBACK_GATTC_SEARCH_RESULT(CONN1_ID, &service_2), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Search Service - None", + ACTION_SUCCESS(init_pdus, search_service_3), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Search Service - Incorrect rsp", + ACTION_SUCCESS(init_pdus, search_service_4), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Characteristic - Single", + ACTION_SUCCESS(init_pdus, get_characteristic_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Characteristic - Incorrect rsp", + ACTION_SUCCESS(init_pdus, get_characteristic_2), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_FAILURE, + CONN1_ID, &service_1, NULL, 0), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Characteristic - None", + ACTION_SUCCESS(init_pdus, get_characteristic_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_FAIL(gatt_client_get_characteristic_action, + &get_char_data_2), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_FAILURE, + CONN1_ID, &service_2, + NULL, 0), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Descriptor - Incorrect rsp", + ACTION_SUCCESS(init_pdus, get_descriptor_0), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_1), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_FAILURE, CONN1_ID, + &service_1, &characteristic_1, NULL), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Descriptor - Single", + ACTION_SUCCESS(init_pdus, get_descriptor_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_1), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &characteristic_1, &desc_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Descriptor - Multiple", + ACTION_SUCCESS(init_pdus, get_descriptor_2), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, + &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_1), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &characteristic_1, + &desc_1), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_2), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &characteristic_1, + &desc_2), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Descriptor - None", + ACTION_SUCCESS(init_pdus, get_descriptor_3), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_1), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_FAILURE, CONN1_ID, + &service_1, &characteristic_1, NULL), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Included Services - Incorrect rsp", + ACTION_SUCCESS(init_pdus, get_included_0), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_included_action, + &get_incl_data_1), + CALLBACK_GATTC_GET_INCLUDED(GATT_STATUS_FAILURE, CONN1_ID, + &service_1, NULL), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Included Service - 16 UUID", + ACTION_SUCCESS(init_pdus, get_included_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_included_action, + &get_incl_data_1), + CALLBACK_GATTC_GET_INCLUDED(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &included_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Included Service - 128 UUID", + ACTION_SUCCESS(init_pdus, get_included_2), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_included_action, + &get_incl_data_1), + CALLBACK_GATTC_GET_INCLUDED(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &included_2), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Get Included Service - None", + ACTION_SUCCESS(init_pdus, get_included_3), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_included_action, + &get_incl_data_1), + CALLBACK_GATTC_GET_INCLUDED(GATT_STATUS_FAILURE, CONN1_ID, + &service_1, NULL), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Read Characteristic - Success", + ACTION_SUCCESS(init_pdus, read_characteristic_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(init_read_params_action, &set_read_param_1), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_read_characteristic_action, + &read_char_data_1), + CALLBACK_GATTC_READ_CHARACTERISTIC(GATT_STATUS_SUCCESS, + CONN1_ID, &read_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + + TEST_CASE_BREDRLE("Gatt Client - Read Characteristic - Insuf. Auth.", + ACTION_SUCCESS(init_pdus, read_characteristic_2), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(init_read_params_action, &set_read_param_2), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_read_characteristic_action, + &read_char_data_1), + CALLBACK_GATTC_READ_CHARACTERISTIC(GATT_STATUS_INS_AUTH, + CONN1_ID, &read_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Read Characteristic - Wrong params", + ACTION_SUCCESS(init_pdus, read_characteristic_2), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(init_read_params_action, &set_read_param_3), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_FAIL(gatt_client_read_characteristic_action, + &read_char_data_2), + CALLBACK_GATTC_READ_CHARACTERISTIC(GATT_STATUS_FAILURE, + CONN1_ID, &read_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Read Descriptor - Success", + ACTION_SUCCESS(init_pdus, read_descriptor_1), + ACTION_SUCCESS(init_read_params_action, &set_read_param_4), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_1), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &characteristic_1, &desc_1), + ACTION_SUCCESS(gatt_client_read_descriptor_action, + &read_desc_data_1), + CALLBACK_GATTC_READ_DESCRIPTOR(GATT_STATUS_SUCCESS, + CONN1_ID, &read_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Read Descriptor - Insuf. Auth.", + ACTION_SUCCESS(init_pdus, read_descriptor_2), + ACTION_SUCCESS(init_read_params_action, &set_read_param_5), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_1), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &characteristic_1, &desc_1), + ACTION_SUCCESS(gatt_client_read_descriptor_action, + &read_desc_data_1), + CALLBACK_GATTC_READ_DESCRIPTOR(GATT_STATUS_INS_AUTH, + CONN1_ID, &read_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Read Descriptor - Wrong params", + ACTION_SUCCESS(init_pdus, read_descriptor_2), + ACTION_SUCCESS(init_read_params_action, &set_read_param_6), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_1), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &characteristic_1, &desc_1), + ACTION_FAIL(gatt_client_read_descriptor_action, + &read_desc_data_2), + CALLBACK_GATTC_READ_DESCRIPTOR(GATT_STATUS_FAILURE, + CONN1_ID, &read_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Write Characteristic Cmd - Success", + ACTION_SUCCESS(init_pdus, write_characteristic_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(init_write_params_action, &set_write_param_1), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_write_characteristic_action, + &write_char_data_1), + CALLBACK_GATTC_WRITE_CHARACTERISTIC(GATT_STATUS_SUCCESS, + CONN1_ID, &write_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Write Characteristic Req - Success", + ACTION_SUCCESS(init_pdus, write_characteristic_2), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(init_write_params_action, &set_write_param_1), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_write_characteristic_action, + &write_char_data_2), + CALLBACK_GATTC_WRITE_CHARACTERISTIC(GATT_STATUS_SUCCESS, + CONN1_ID, &write_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Write Characteristic - Insuf. Auth.", + ACTION_SUCCESS(init_pdus, write_characteristic_3), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(init_write_params_action, &set_write_param_2), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_write_characteristic_action, + &write_char_data_2), + CALLBACK_GATTC_WRITE_CHARACTERISTIC(GATT_STATUS_INS_AUTH, + CONN1_ID, &write_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Write Characteristic - Wrong Params", + ACTION_SUCCESS(init_pdus, write_characteristic_3), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(init_write_params_action, &set_write_param_3), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_FAIL(gatt_client_write_characteristic_action, + &write_char_data_2), + CALLBACK_GATTC_WRITE_CHARACTERISTIC(GATT_STATUS_FAILURE, + CONN1_ID, &write_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Register For Notification - Success", + ACTION_SUCCESS(init_pdus, notification_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_register_for_notification_action, + ¬if_data_1), + CALLBACK_GATTC_REGISTER_FOR_NOTIF(GATT_STATUS_SUCCESS, CONN1_ID, + &characteristic_1, + &service_1, 1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Deregister For Notification - Success", + ACTION_SUCCESS(init_pdus, notification_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_register_for_notification_action, + ¬if_data_1), + CALLBACK_GATTC_REGISTER_FOR_NOTIF(GATT_STATUS_SUCCESS, CONN1_ID, + &characteristic_1, + &service_1, 1), + ACTION_SUCCESS(gatt_client_deregister_for_notification_action, + ¬if_data_1), + CALLBACK_GATTC_REGISTER_FOR_NOTIF(GATT_STATUS_SUCCESS, CONN1_ID, + &characteristic_1, + &service_1, 0), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Register For Notification - Indicate", + ACTION_SUCCESS(init_pdus, notification_2), + ACTION_SUCCESS(init_notify_params_action, &set_notify_param_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_register_for_notification_action, + ¬if_data_1), + CALLBACK_GATTC_REGISTER_FOR_NOTIF(GATT_STATUS_SUCCESS, CONN1_ID, + &characteristic_1, + &service_1, 1), + ACTION_SUCCESS(gatt_remote_send_frame_action, NULL), + CALLBACK_GATTC_NOTIFY(CONN1_ID, ¬ify_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Register For Notification - Notify", + ACTION_SUCCESS(init_pdus, notification_3), + ACTION_SUCCESS(init_notify_params_action, &set_notify_param_2), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_register_for_notification_action, + ¬if_data_1), + CALLBACK_GATTC_REGISTER_FOR_NOTIF(GATT_STATUS_SUCCESS, CONN1_ID, + &characteristic_1, + &service_1, 1), + ACTION_SUCCESS(gatt_remote_send_frame_action, NULL), + CALLBACK_GATTC_NOTIFY(CONN1_ID, ¬ify_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Write Descriptor - Success", + ACTION_SUCCESS(init_pdus, write_descriptor_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(init_write_params_action, &set_write_param_4), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_1), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &characteristic_1, &desc_1), + ACTION_SUCCESS(gatt_client_write_descriptor_action, + &write_desc_data_1), + CALLBACK_GATTC_WRITE_DESCRIPTOR(GATT_STATUS_SUCCESS, + CONN1_ID, &write_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Write Descriptor - Insuf. Auth.", + ACTION_SUCCESS(init_pdus, write_descriptor_2), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(init_write_params_action, &set_write_param_6), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_1), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &characteristic_1, &desc_1), + ACTION_SUCCESS(gatt_client_write_descriptor_action, + &write_desc_data_1), + CALLBACK_GATTC_WRITE_DESCRIPTOR(GATT_STATUS_INS_AUTH, + CONN1_ID, &write_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Client - Write Descriptor - Wrong Param", + ACTION_SUCCESS(init_pdus, write_descriptor_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(init_write_params_action, &set_write_param_5), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_client_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTC_REGISTER_CLIENT, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_client_start_scan_action, NULL), + ACTION_SUCCESS(delayemu_setup_powered_remote_action, NULL), + CLLBACK_GATTC_SCAN_RES(prop_emu_remotes_default_set, 1, TRUE), + ACTION_SUCCESS(gatt_client_stop_scan_action, NULL), + ACTION_SUCCESS(gatt_client_connect_action, &app1_conn_req), + CALLBACK_GATTC_CONNECT(GATT_STATUS_SUCCESS, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_client_search_services, &search_services_1), + CALLBACK_GATTC_SEARCH_COMPLETE(GATT_STATUS_SUCCESS, CONN1_ID), + ACTION_SUCCESS(gatt_client_get_characteristic_action, + &get_char_data_1), + CALLBACK_GATTC_GET_CHARACTERISTIC_CB(GATT_STATUS_SUCCESS, + CONN1_ID, &service_1, &characteristic_1, 4), + ACTION_SUCCESS(gatt_client_get_descriptor_action, + &get_desc_data_1), + CALLBACK_GATTC_GET_DESCRIPTOR(GATT_STATUS_SUCCESS, CONN1_ID, + &service_1, &characteristic_1, &desc_1), + ACTION_FAIL(gatt_client_write_descriptor_action, + &write_desc_data_2), + CALLBACK_GATTC_WRITE_DESCRIPTOR(GATT_STATUS_FAILURE, + CONN1_ID, &write_params_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Server - Register", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ), + TEST_CASE_BREDRLE("Gatt Server - Unregister", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_unregister_action, + INT_TO_PTR(APP1_ID)), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ), + TEST_CASE_BREDRLE("Gatt Server - LE Connect", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Server - LE Disconnect", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_server_disconnect_action, + &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_DISCONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Server - LE Multiple Server Conn./Disc", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_register_action, &app2_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_server_connect_action, &app2_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN2_ID, APP2_ID), + ACTION_SUCCESS(gatt_server_disconnect_action, &app2_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_DISCONNECTED, + prop_emu_remotes_default_set, + CONN2_ID, APP2_ID), + ACTION_SUCCESS(gatt_server_disconnect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_DISCONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Single Service Successful", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Multiple Services Successful", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, NULL), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_2), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_2, NULL, NULL), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_3), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_3, NULL, NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Service with 0 handles", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_FAIL(gatt_server_add_service_action, + &add_bad_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_FAILURE, APP1_ID, + &service_add_1, NULL, NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Secondary Service", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_sec_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &included_1, NULL, NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Included Service Successful", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_4), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_4), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &inc_srvc1_handle), + ACTION_SUCCESS(gatt_server_add_inc_service_action, + &add_inc_service_data_1), + CALLBACK_GATTS_INC_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle, NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Inc. Service with wrong handle", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_4), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_4), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, NULL), + ACTION_FAIL(gatt_server_add_inc_service_action, + &add_bad_inc_service_data_1), + CALLBACK_GATTS_INC_SERVICE_ADDED(GATT_STATUS_FAILURE, APP1_ID, + &srvc1_handle, NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Single Characteristic Successful", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_5), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_add_char_action, &add_char_data_1), + CALLBACK_GATTS_CHARACTERISTIC_ADDED(GATT_STATUS_SUCCESS, + APP1_ID, &app1_uuid, + &srvc1_handle, NULL, + NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Char. wrong service handle", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_5), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_FAIL(gatt_server_add_char_action, &add_bad_char_data_1), + CALLBACK_GATTS_CHARACTERISTIC_ADDED(GATT_STATUS_FAILURE, + APP1_ID, &app1_uuid, + NULL, NULL, NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Single Descriptor Successful", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_6), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_add_char_action, &add_char_data_1), + CALLBACK_GATTS_CHARACTERISTIC_ADDED(GATT_STATUS_SUCCESS, + APP1_ID, &app1_uuid, + &srvc1_handle, NULL, + NULL), + ACTION_SUCCESS(gatt_server_add_desc_action, &add_desc_data_1), + CALLBACK_GATTS_DESCRIPTOR_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &app2_uuid, &srvc1_handle, + NULL, NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Desc. wrong service handle", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_6), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_add_char_action, &add_char_data_1), + CALLBACK_GATTS_CHARACTERISTIC_ADDED(GATT_STATUS_SUCCESS, + APP1_ID, &app1_uuid, + &srvc1_handle, NULL, + NULL), + ACTION_FAIL(gatt_server_add_desc_action, &add_bad_desc_data_1), + CALLBACK_GATTS_DESCRIPTOR_ADDED(GATT_STATUS_FAILURE, APP1_ID, + &app2_uuid, NULL, NULL, NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Add Desc. wrong app ID", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_6), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_add_char_action, &add_char_data_1), + CALLBACK_GATTS_CHARACTERISTIC_ADDED(GATT_STATUS_SUCCESS, + APP1_ID, &app1_uuid, + &srvc1_handle, NULL, + NULL), + ACTION_FAIL(gatt_server_add_desc_action, &add_bad_desc_data_2), + CALLBACK_GATTS_DESCRIPTOR_ADDED(GATT_STATUS_FAILURE, APP2_ID, + &app2_uuid, NULL, NULL, NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Start Service Successful BREDRLE", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_start_srvc_action, + &start_srvc_data_1), + CALLBACK_GATTS_SERVICE_STARTED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle), + ), + TEST_CASE_BREDRLE("Gatt Server - Start Service Successful LE", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_start_srvc_action, + &start_srvc_data_2), + CALLBACK_GATTS_SERVICE_STARTED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle), + ), + TEST_CASE_BREDRLE("Gatt Server - Start Service wrong service handle", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, NULL), + ACTION_FAIL(gatt_server_start_srvc_action, + &start_bad_srvc_data_1), + CALLBACK_GATTS_SERVICE_STARTED(GATT_STATUS_FAILURE, APP1_ID, + NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Start Service wrong server transport", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_FAIL(gatt_server_start_srvc_action, + &start_bad_srvc_data_2), + CALLBACK_GATTS_SERVICE_STARTED(GATT_STATUS_FAILURE, APP1_ID, + &srvc1_handle), + ), + TEST_CASE_BREDRLE("Gatt Server - Stop Service Successful", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_start_srvc_action, + &start_srvc_data_1), + CALLBACK_GATTS_SERVICE_STARTED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_stop_srvc_action, &stop_srvc_data_1), + CALLBACK_GATTS_SERVICE_STOPPED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle), + ), + TEST_CASE_BREDRLE("Gatt Server - Stop Service wrong service handle", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_start_srvc_action, + &start_srvc_data_1), + CALLBACK_GATTS_SERVICE_STARTED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle), + ACTION_FAIL(gatt_server_stop_srvc_action, + &stop_bad_srvc_data_1), + CALLBACK_GATTS_SERVICE_STOPPED(GATT_STATUS_FAILURE, APP1_ID, + NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Delete Service Successful", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_delete_srvc_action, + &delete_srvc_data_1), + CALLBACK_GATTS_SERVICE_DELETED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle), + ), + TEST_CASE_BREDRLE("Gatt Server - Delete Service wrong handle", + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_1), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_FAIL(gatt_server_delete_srvc_action, + &delete_bad_srvc_data_1), + CALLBACK_GATTS_SERVICE_DELETED(GATT_STATUS_FAILURE, APP1_ID, + NULL), + ), + TEST_CASE_BREDRLE("Gatt Server - Send Indication", + ACTION_SUCCESS(init_pdus, send_indication_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_server_send_indication_action, + &send_indication_data_1), + CALLBACK(CB_EMU_VALUE_INDICATION), + CALLBACK_GATTS_NOTIF_CONF(CONN1_ID, GATT_STATUS_SUCCESS), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Server - Send Notification", + ACTION_SUCCESS(init_pdus, send_notification_1), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_SUCCESS(gatt_server_send_indication_action, + &send_indication_data_2), + CALLBACK_GATTS_NOTIF_CONF(CONN1_ID, GATT_STATUS_SUCCESS), + CALLBACK(CB_EMU_VALUE_NOTIFICATION), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Server - Send Notification, wrong conn id", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + ACTION_FAIL(gatt_server_send_indication_action, + &send_bad_indication_data_1), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Server - Send response to read char request", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_5), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_add_char_action, &add_char_data_1), + CALLBACK_GATTS_CHARACTERISTIC_ADDED(GATT_STATUS_SUCCESS, + APP1_ID, &app1_uuid, + &srvc1_handle, NULL, + &char1_handle), + ACTION_SUCCESS(gatt_server_start_srvc_action, + &start_srvc_data_2), + CALLBACK_GATTS_SERVICE_STARTED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + PROCESS_DATA(GATT_STATUS_SUCCESS, + gatt_remote_send_raw_pdu_action, + &att_read_req_op_v, &char1_handle_v, NULL), + CALLBACK_GATTS_REQUEST_READ(CONN1_ID, TRANS1_ID, + prop_emu_remotes_default_set, + &char1_handle, 0, false), + ACTION_SUCCESS(gatt_server_send_response_action, + &send_resp_data_1), + CALLBACK(CB_EMU_READ_RESPONSE), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Server - Send response to write char request", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_5), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_add_char_action, &add_char_data_2), + CALLBACK_GATTS_CHARACTERISTIC_ADDED(GATT_STATUS_SUCCESS, + APP1_ID, &app1_uuid, + &srvc1_handle, NULL, + &char1_handle), + ACTION_SUCCESS(gatt_server_start_srvc_action, + &start_srvc_data_2), + CALLBACK_GATTS_SERVICE_STARTED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + PROCESS_DATA(GATT_STATUS_SUCCESS, + gatt_remote_send_raw_pdu_action, + &att_write_req_op_v, &char1_handle_v, + &att_write_req_value_1_v), + CALLBACK_GATTS_REQUEST_WRITE(CONN1_ID, TRANS1_ID, + prop_emu_remotes_default_set, + &char1_handle, 0, + sizeof(att_write_req_value_1), + true, false, + att_write_req_value_1), + ACTION_SUCCESS(gatt_server_send_response_action, + &send_resp_data_2), + CALLBACK(CB_EMU_WRITE_RESPONSE), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Server - Find By Type - Attribute not found", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_5), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_add_char_action, &add_char_data_2), + CALLBACK_GATTS_CHARACTERISTIC_ADDED(GATT_STATUS_SUCCESS, + APP1_ID, &app1_uuid, + &srvc1_handle, NULL, + &char1_handle), + ACTION_SUCCESS(gatt_server_start_srvc_action, + &start_srvc_data_2), + CALLBACK_GATTS_SERVICE_STARTED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + PROCESS_DATA(GATT_STATUS_SUCCESS, + gatt_remote_send_raw_pdu_action, + &att_find_by_type_req_op_v, + &search_range_1, + &primary_type), + CALLBACK_ERROR(CB_EMU_ATT_ERROR, 0x0a), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + /* This tests embeded ccc */ + TEST_CASE_BREDRLE("Gatt Server - Srvc change write req. success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + /* For CCC we need to be bonded */ + ACTION_SUCCESS(bt_create_bond_action, + &prop_test_remote_ble_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDED, + &prop_emu_remotes_default_set[0], 1), + /* Write and receive confirmation */ + PROCESS_DATA(GATT_STATUS_SUCCESS, + gatt_remote_send_raw_pdu_action, + &att_write_req_op_v, &svc_change_ccc_handle_v, + &svc_change_ccc_value_v), + CALLBACK(CB_EMU_WRITE_RESPONSE), + /* Shutdown */ + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Gatt Server - Send error resp to write char request", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_set_connect_cb_action, gatt_conn_cb), + ACTION_SUCCESS(gatt_server_register_action, &app1_uuid), + CALLBACK_STATUS(CB_GATTS_REGISTER_SERVER, BT_STATUS_SUCCESS), + ACTION_SUCCESS(gatt_server_add_service_action, + &add_service_data_5), + CALLBACK_GATTS_SERVICE_ADDED(GATT_STATUS_SUCCESS, APP1_ID, + &service_add_1, NULL, + &srvc1_handle), + ACTION_SUCCESS(gatt_server_add_char_action, &add_char_data_2), + CALLBACK_GATTS_CHARACTERISTIC_ADDED(GATT_STATUS_SUCCESS, + APP1_ID, &app1_uuid, + &srvc1_handle, NULL, + &char1_handle), + ACTION_SUCCESS(gatt_server_start_srvc_action, + &start_srvc_data_2), + CALLBACK_GATTS_SERVICE_STARTED(GATT_STATUS_SUCCESS, APP1_ID, + &srvc1_handle), + ACTION_SUCCESS(bt_start_discovery_action, NULL), + CALLBACK_STATE(CB_BT_DISCOVERY_STATE_CHANGED, + BT_DISCOVERY_STARTED), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_le_set, 2), + ACTION_SUCCESS(bt_cancel_discovery_action, NULL), + ACTION_SUCCESS(gatt_server_connect_action, &app1_conn_req), + CALLBACK_GATTS_CONNECTION(GATT_SERVER_CONNECTED, + prop_emu_remotes_default_set, + CONN1_ID, APP1_ID), + PROCESS_DATA(GATT_STATUS_SUCCESS, + gatt_remote_send_raw_pdu_action, + &att_write_req_op_v, &char1_handle_v, + &att_write_req_value_1_v), + CALLBACK_GATTS_REQUEST_WRITE(CONN1_ID, TRANS1_ID, + prop_emu_remotes_default_set, + &char1_handle, 0, + sizeof(att_write_req_value_1), + true, false, + att_write_req_value_1), + ACTION_SUCCESS(gatt_server_send_response_action, + &send_resp_data_2_error), + CALLBACK_ERROR(CB_EMU_ATT_ERROR, GATT_ERR_INVAL_ATTR_VALUE_LEN), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), +}; + +struct queue *get_gatt_tests(void) +{ + uint16_t i = 0; + + list = queue_new(); + + for (; i < sizeof(test_cases) / sizeof(test_cases[0]); ++i) + queue_push_tail(list, &test_cases[i]); + + return list; +} + +void remove_gatt_tests(void) +{ + queue_destroy(list, NULL); +} diff --git a/android/tester-hdp.c b/android/tester-hdp.c new file mode 100644 index 0000000..b4d14a3 --- /dev/null +++ b/android/tester-hdp.c @@ -0,0 +1,563 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include + +#include "emulator/bthost.h" +#include "lib/bluetooth.h" +#include "android/utils.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "tester-main.h" + +typedef enum { + HDP_APP_SINK_RELIABLE, + HDP_APP_SINK_STREAM, + HDP_APP_SOURCE_RELIABLE, + HDP_APP_SOURCE_STREAM, +} hdp_app_reg_type; + +#define hdp_rsp_pdu 0x07, \ + 0x00, 0x00, \ + 0x01, 0xc8, \ + 0x01, 0xc5, \ + 0x36, 0x01, 0xc2, 0x36, 0x01, 0xbf, 0x09, 0x00, 0x00, \ + 0x0a, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x01, 0x35, \ + 0x03, 0x19, 0x14, 0x01, 0x09, 0x00, 0x04, 0x35, 0x10, \ + 0x35, 0x06, 0x19, 0x01, 0x00, 0x09, 0x10, 0x01, 0x35, \ + 0x06, 0x19, 0x00, 0x1e, 0x09, 0x01, 0x00, 0x09, 0x00, \ + 0x09, 0x35, 0x08, 0x35, 0x06, 0x19, 0x14, 0x00, 0x09, \ + 0x01, 0x01, 0x09, 0x00, 0x0d, 0x35, 0x0f, 0x35, 0x0d, \ + 0x35, 0x06, 0x19, 0x01, 0x00, 0x09, 0x10, 0x03, 0x35, \ + 0x03, 0x19, 0x00, 0x1f, 0x09, 0x01, 0x00, 0x25, 0x03, \ + 0x48, 0x44, 0x50, 0x09, 0x01, 0x01, 0x25, 0x28, 0x43, \ + 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x2c, 0x20, 0x64, \ + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x2c, 0x20, 0x61, \ + 0x6e, 0x64, 0x20, 0x72, 0x65, 0x8b, 0x6c, 0x61, 0x79, \ + 0x20, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x20, 0x64, \ + 0x61, 0x74, 0x61, 0x09, 0x01, 0x02, 0x25, 0x0d, 0x42, \ + 0x4c, 0x55, 0x45, 0x54, 0x4f, 0x4f, 0x54, 0x48, 0x20, \ + 0x53, 0x49, 0x47, 0x09, 0x02, 0x00, 0x36, 0x01, 0x22, \ + 0x35, 0x18, 0x08, 0x01, 0x09, 0x10, 0x04, 0x08, 0x00, \ + 0x25, 0x0f, 0x50, 0x75, 0x6c, 0x73, 0x65, 0x20, 0x4f, \ + 0x78, 0x69, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x0d, 0x35, \ + 0x20, 0x08, 0x02, 0x09, 0x10, 0x07, 0x08, 0x00, 0x25, \ + 0x17, 0x42, 0x6c, 0x6f, 0x6f, 0x64, 0x20, 0x50, 0x72, \ + 0x65, 0x73, 0x73, 0x75, 0x72, 0x65, 0x20, 0x4d, 0x6f, \ + 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x0d, 0x35, 0x1a, 0x08, \ + 0x03, 0x09, 0x10, 0x08, 0x08, 0x00, 0x25, 0x11, 0x42, \ + 0x6f, 0x64, 0x79, 0x20, 0x54, 0x68, 0x65, 0x72, 0x6d, \ + 0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x0d, 0x35, 0x1e, \ + 0x08, 0x04, 0x09, 0x10, 0x0f, 0x08, 0x00, 0x25, 0x15, \ + 0x42, 0x6f, 0x64, 0x79, 0x20, 0x57, 0x65, 0x69, 0x67, \ + 0x68, 0x74, 0x20, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x09, \ + 0x09, 0x09, 0x0d, 0x35, 0x17, 0x08, 0x05, 0x09, 0x10, \ + 0x11, 0x08, 0x00, 0x25, 0x0e, 0x47, 0x6c, 0x75, 0x63, \ + 0x6f, 0x73, 0x65, 0x20, 0x4d, 0x65, 0x74, 0x65, 0x72, \ + 0x0d, 0x35, 0x18, 0x08, 0x06, 0x09, 0x10, 0x04, 0x08, \ + 0x01, 0x25, 0x0f, 0x50, 0x75, 0x6c, 0x73, 0x65, 0x20, \ + 0x4f, 0x78, 0x69, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x0d, \ + 0x35, 0x20, 0x08, 0x07, 0x09, 0x10, 0x07, 0x08, 0x01, \ + 0x25, 0x17, 0x42, 0x6c, 0x6f, 0x6f, 0x64, 0x20, 0x50, \ + 0x72, 0x65, 0x73, 0x73, 0x75, 0x72, 0x65, 0x20, 0x4d, \ + 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x0d, 0x35, 0x1a, \ + 0x08, 0x08, 0x09, 0x10, 0x08, 0x08, 0x01, 0x25, 0x11, \ + 0x42, 0x6f, 0x64, 0x79, 0x20, 0x54, 0x68, 0x65, 0x72, \ + 0x6d, 0x6f, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x0d, 0x35, \ + 0x1e, 0x08, 0x09, 0x09, 0x10, 0x0f, 0x08, 0x01, 0x25, \ + 0x15, 0x42, 0x6f, 0x64, 0x79, 0x20, 0x57, 0x65, 0x69, \ + 0x67, 0x68, 0x74, 0x20, 0x53, 0x63, 0x61, 0x6c, 0x65, \ + 0x09, 0x09, 0x09, 0x0d, 0x35, 0x17, 0x08, 0x0a, 0x09, \ + 0x10, 0x11, 0x08, 0x01, 0x25, 0x0e, 0x47, 0x6c, 0x75, \ + 0x63, 0x6f, 0x73, 0x65, 0x20, 0x4d, 0x65, 0x74, 0x65, \ + 0x72, 0x0d, 0x09, 0x03, 0x01, 0x08, 0x01, 0x09, 0x03, \ + 0x02, 0x08, 0x00, \ + 0x00 + +static const struct pdu_set sdp_pdus[] = { + { end_pdu, raw_pdu(hdp_rsp_pdu) }, + { end_pdu, end_pdu }, +}; + +static struct emu_l2cap_cid_data sdp_cid_data = { + .pdu = sdp_pdus, + .is_sdp = TRUE, +}; + +static struct emu_l2cap_cid_data ctrl_cid_data; +static struct emu_l2cap_cid_data data_cid_data; + +static struct queue *list; /* List of hdp test cases */ + +static bthl_reg_param_t *create_app(hdp_app_reg_type type) +{ + bthl_reg_param_t *reg; + bthl_mdep_cfg_t mdep1, mdep2; + + reg = malloc(sizeof(bthl_reg_param_t)); + reg->application_name = "bluez-android"; + reg->provider_name = "Bluez"; + reg->srv_name = "bluez-hdp"; + reg->srv_desp = "health-device-profile"; + + mdep1.data_type = 4100; + mdep1.mdep_description = "pulse-oximeter"; + + mdep2.data_type = 4100; + mdep2.mdep_description = "pulse-oximeter"; + + switch (type) { + case HDP_APP_SINK_RELIABLE: + reg->number_of_mdeps = 1; + mdep1.mdep_role = BTHL_MDEP_ROLE_SINK; + mdep1.channel_type = BTHL_CHANNEL_TYPE_RELIABLE; + reg->mdep_cfg = malloc(reg->number_of_mdeps * + sizeof(bthl_mdep_cfg_t)); + reg->mdep_cfg[0] = mdep1; + break; + + case HDP_APP_SINK_STREAM: + reg->number_of_mdeps = 2; + + mdep1.mdep_role = BTHL_MDEP_ROLE_SINK; + mdep1.channel_type = BTHL_CHANNEL_TYPE_RELIABLE; + + mdep2.mdep_role = BTHL_MDEP_ROLE_SINK; + mdep2.channel_type = BTHL_CHANNEL_TYPE_STREAMING; + + reg->mdep_cfg = malloc(reg->number_of_mdeps * + sizeof(bthl_mdep_cfg_t)); + reg->mdep_cfg[0] = mdep1; + reg->mdep_cfg[1] = mdep2; + break; + + case HDP_APP_SOURCE_RELIABLE: + reg->number_of_mdeps = 1; + + mdep1.mdep_role = BTHL_MDEP_ROLE_SOURCE; + mdep1.channel_type = BTHL_CHANNEL_TYPE_RELIABLE; + + reg->mdep_cfg = malloc(reg->number_of_mdeps * + sizeof(bthl_mdep_cfg_t)); + reg->mdep_cfg[0] = mdep1; + break; + + case HDP_APP_SOURCE_STREAM: + reg->number_of_mdeps = 2; + + mdep1.mdep_role = BTHL_MDEP_ROLE_SOURCE; + mdep1.channel_type = BTHL_CHANNEL_TYPE_RELIABLE; + + mdep2.mdep_role = BTHL_MDEP_ROLE_SOURCE; + mdep2.channel_type = BTHL_CHANNEL_TYPE_STREAMING; + + reg->mdep_cfg = malloc(reg->number_of_mdeps * + sizeof(bthl_mdep_cfg_t)); + reg->mdep_cfg[0] = mdep1; + reg->mdep_cfg[1] = mdep2; + break; + } + + + return reg; +} + +static void hdp_register_sink_reliable_app_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + int app_id = 0; + bthl_reg_param_t *reg; + + reg = create_app(HDP_APP_SINK_RELIABLE); + step->action_status = data->if_hdp->register_application(reg, &app_id); + + schedule_action_verification(step); + free(reg->mdep_cfg); + free(reg); +} + +static void hdp_register_sink_stream_app_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + int app_id = 0; + bthl_reg_param_t *reg; + + reg = create_app(HDP_APP_SINK_STREAM); + step->action_status = data->if_hdp->register_application(reg, &app_id); + + schedule_action_verification(step); + free(reg->mdep_cfg); + free(reg); +} + +static void hdp_register_source_reliable_app_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + int app_id = 0; + bthl_reg_param_t *reg; + + reg = create_app(HDP_APP_SOURCE_RELIABLE); + step->action_status = data->if_hdp->register_application(reg, &app_id); + + schedule_action_verification(step); + free(reg->mdep_cfg); + free(reg); +} + +static void hdp_register_source_stream_app_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + int app_id = 0; + bthl_reg_param_t *reg; + + reg = create_app(HDP_APP_SOURCE_STREAM); + step->action_status = data->if_hdp->register_application(reg, &app_id); + + schedule_action_verification(step); + free(reg->mdep_cfg); + free(reg); +} + +static void hdp_unregister_app_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_hdp->unregister_application(1); + + schedule_action_verification(step); +} + +static void mcap_ctrl_cid_hook_cb(const void *data, uint16_t len, + void *user_data) +{ + struct test_data *t_data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(t_data->hciemu); + struct emu_l2cap_cid_data *cid_data = user_data; + uint8_t crt_rsp[5], del_rsp[4], config; + uint8_t opcode = ((uint8_t *) data)[0]; + static bool reliable = false; + + switch (opcode) { + case 0x01: /* MD_CREATE_MDL_REQ */ + crt_rsp[0] = 0x02; /* MD_CREATE_MDL_RSP */ + crt_rsp[1] = 0x00; /* Response code - Success */ + crt_rsp[2] = ((uint8_t *) data)[1]; /* mdlid */ + crt_rsp[3] = ((uint8_t *) data)[2]; + config = ((uint8_t *) data)[4]; + + if (config == 0x00) { + if (!reliable) { + crt_rsp[4] = 0x01; + reliable = true; + } else { + crt_rsp[4] = 0x02; + reliable = false; + } + } else { + crt_rsp[4] = config; + } + + bthost_send_cid(bthost, cid_data->handle, + cid_data->cid, + crt_rsp, sizeof(crt_rsp)); + break; + case 0x03: /* MD_RECONNECT_MDL_REQ */ + case 0x05: /* MD_ABORT_MDL_REQ */ + break; + case 0x07: /* MD_DELETE_MDL_REQ */ + del_rsp[0] = 0x08; /* MD_DELETE_MDL_RSP */ + del_rsp[1] = 0x00; /* Response code - Success */ + del_rsp[2] = ((uint8_t *) data)[1]; /* mdlid */ + del_rsp[3] = ((uint8_t *) data)[2]; + bthost_send_cid(bthost, cid_data->handle, + cid_data->cid, + del_rsp, sizeof(del_rsp)); + break; + } +} + +static void mcap_ctrl_connect_cb(uint16_t handle, uint16_t cid, void *user_data) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + struct emu_l2cap_cid_data *cid_data = user_data; + + cid_data->handle = handle; + cid_data->cid = cid; + + bthost_add_cid_hook(bthost, handle, cid, mcap_ctrl_cid_hook_cb, + cid_data); +} + +/* Emulate SDP (PSM = 1) */ +static struct emu_set_l2cap_data l2cap_setup_sdp_data = { + .psm = 1, + .func = tester_generic_connect_cb, + .user_data = &sdp_cid_data, +}; + +/* Emulate Control Channel (PSM = 0x1001) */ +static struct emu_set_l2cap_data l2cap_setup_cc_data = { + .psm = 0x1001, + .func = mcap_ctrl_connect_cb, + .user_data = &ctrl_cid_data, +}; + +/* Emulate Data Channel (PSM = 0x1003) */ +static struct emu_set_l2cap_data l2cap_setup_dc_data = { + .psm = 0x1003, + .func = tester_generic_connect_cb, + .user_data = &data_cid_data, +}; + +static void hdp_connect_source_reliable_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + int app_id, channel_id, mdep_cfg_index; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + app_id = 1; + mdep_cfg_index = 0; + channel_id = 0; + step->action_status = data->if_hdp->connect_channel(app_id, &bdaddr, + mdep_cfg_index, &channel_id); + + schedule_action_verification(step); +} + +static void hdp_destroy_source_reliable_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_hdp->destroy_channel(1); + schedule_action_verification(step); +} + +static void hdp_connect_sink_reliable_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + int app_id, channel_id, mdep_cfg_index; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + app_id = 1; + mdep_cfg_index = 0; + channel_id = 0; + step->action_status = data->if_hdp->connect_channel(app_id, &bdaddr, + mdep_cfg_index, &channel_id); + + schedule_action_verification(step); +} + +static void hdp_connect_sink_stream_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + int app_id, channel_id, mdep_cfg_index; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + app_id = 1; + mdep_cfg_index = 1; + channel_id = 0; + step->action_status = data->if_hdp->connect_channel(app_id, &bdaddr, + mdep_cfg_index, &channel_id); + + schedule_action_verification(step); +} + +static void hdp_destroy_sink_reliable_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_hdp->destroy_channel(1); + schedule_action_verification(step); +} + +static void hdp_destroy_sink_stream_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_hdp->destroy_channel(2); + schedule_action_verification(step); +} + +static struct test_case test_cases[] = { + TEST_CASE_BREDRLE("HDP Init", + ACTION_SUCCESS(dummy_action, NULL), + ), + TEST_CASE_BREDRLE("HDP Register Sink Reliable Application", + ACTION_SUCCESS(hdp_register_sink_reliable_app_action, NULL), + CALLBACK_HDP_APP_REG_STATE(CB_HDP_APP_REG_STATE, 1, + BTHL_APP_REG_STATE_REG_SUCCESS), + ), + TEST_CASE_BREDRLE("HDP Register Sink Stream Application", + ACTION_SUCCESS(hdp_register_sink_stream_app_action, NULL), + CALLBACK_HDP_APP_REG_STATE(CB_HDP_APP_REG_STATE, 1, + BTHL_APP_REG_STATE_REG_SUCCESS), + ), + TEST_CASE_BREDRLE("HDP Register Source Reliable Application", + ACTION_SUCCESS(hdp_register_source_reliable_app_action, NULL), + CALLBACK_HDP_APP_REG_STATE(CB_HDP_APP_REG_STATE, 1, + BTHL_APP_REG_STATE_REG_SUCCESS), + ), + TEST_CASE_BREDRLE("HDP Register Source Stream Application", + ACTION_SUCCESS(hdp_register_source_stream_app_action, NULL), + CALLBACK_HDP_APP_REG_STATE(CB_HDP_APP_REG_STATE, 1, + BTHL_APP_REG_STATE_REG_SUCCESS), + ), + TEST_CASE_BREDRLE("HDP Unegister Application", + ACTION_SUCCESS(hdp_register_source_stream_app_action, NULL), + CALLBACK_HDP_APP_REG_STATE(CB_HDP_APP_REG_STATE, 1, + BTHL_APP_REG_STATE_REG_SUCCESS), + ACTION_SUCCESS(hdp_unregister_app_action, NULL), + CALLBACK_HDP_APP_REG_STATE(CB_HDP_APP_REG_STATE, 1, + BTHL_APP_REG_STATE_DEREG_SUCCESS), + ), + TEST_CASE_BREDRLE("HDP Connect Source Reliable Channel", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_dc_data), + ACTION_SUCCESS(hdp_register_source_reliable_app_action, NULL), + CALLBACK_HDP_APP_REG_STATE(CB_HDP_APP_REG_STATE, 1, + BTHL_APP_REG_STATE_REG_SUCCESS), + ACTION_SUCCESS(hdp_connect_source_reliable_action, NULL), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 1, 0, + BTHL_CONN_STATE_CONNECTING), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 1, 0, + BTHL_CONN_STATE_CONNECTED), + ), + TEST_CASE_BREDRLE("HDP Destroy Source Reliable Channel", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_dc_data), + ACTION_SUCCESS(hdp_register_source_reliable_app_action, NULL), + CALLBACK_HDP_APP_REG_STATE(CB_HDP_APP_REG_STATE, 1, + BTHL_APP_REG_STATE_REG_SUCCESS), + ACTION_SUCCESS(hdp_connect_source_reliable_action, NULL), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 1, 0, + BTHL_CONN_STATE_CONNECTING), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 1, 0, + BTHL_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hdp_destroy_source_reliable_action, NULL), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 1, 0, + BTHL_CONN_STATE_DESTROYED), + ), + TEST_CASE_BREDRLE("HDP Connect Sink Streaming Channel", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_dc_data), + ACTION_SUCCESS(hdp_register_sink_stream_app_action, NULL), + CALLBACK_HDP_APP_REG_STATE(CB_HDP_APP_REG_STATE, 1, + BTHL_APP_REG_STATE_REG_SUCCESS), + ACTION_SUCCESS(hdp_connect_sink_reliable_action, NULL), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 1, 0, + BTHL_CONN_STATE_CONNECTING), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 1, 0, + BTHL_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hdp_connect_sink_stream_action, NULL), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 2, 1, + BTHL_CONN_STATE_CONNECTED), + ), + TEST_CASE_BREDRLE("HDP Destroy Sink Streaming Channel", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_dc_data), + ACTION_SUCCESS(hdp_register_sink_stream_app_action, NULL), + CALLBACK_HDP_APP_REG_STATE(CB_HDP_APP_REG_STATE, 1, + BTHL_APP_REG_STATE_REG_SUCCESS), + ACTION_SUCCESS(hdp_connect_sink_reliable_action, NULL), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 1, 0, + BTHL_CONN_STATE_CONNECTING), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 1, 0, + BTHL_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hdp_connect_sink_stream_action, NULL), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 2, 1, + BTHL_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hdp_destroy_sink_reliable_action, NULL), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 1, 0, + BTHL_CONN_STATE_DESTROYED), + ACTION_SUCCESS(hdp_destroy_sink_stream_action, NULL), + CALLBACK_HDP_CHANNEL_STATE(CB_HDP_CHANNEL_STATE, 1, 2, 1, + BTHL_CONN_STATE_DESTROYED), + ), +}; + +struct queue *get_hdp_tests(void) +{ + uint16_t i = 0; + + list = queue_new(); + + for (; i < sizeof(test_cases) / sizeof(test_cases[0]); ++i) + queue_push_tail(list, &test_cases[i]); + + return list; +} + +void remove_hdp_tests(void) +{ + queue_destroy(list, NULL); +} diff --git a/android/tester-hidhost.c b/android/tester-hidhost.c new file mode 100644 index 0000000..73092cd --- /dev/null +++ b/android/tester-hidhost.c @@ -0,0 +1,733 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include + +#include "emulator/bthost.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "lib/bluetooth.h" +#include "android/utils.h" +#include "tester-main.h" + +#define HID_GET_REPORT_PROTOCOL 0x60 +#define HID_GET_BOOT_PROTOCOL 0x61 +#define HID_SET_REPORT_PROTOCOL 0x70 +#define HID_SET_BOOT_PROTOCOL 0x71 + +#define HID_SET_INPUT_REPORT 0x51 +#define HID_SET_OUTPUT_REPORT 0x52 +#define HID_SET_FEATURE_REPORT 0x53 + +#define HID_SEND_DATA 0xa2 + +#define HID_GET_INPUT_REPORT 0x49 +#define HID_GET_OUTPUT_REPORT 0x4a +#define HID_GET_FEATURE_REPORT 0x4b + +#define HID_MODE_DEFAULT 0x00 +#define HID_MODE_BREDR 0x01 +#define HID_MODE_LE 0x02 + +#define HID_EXPECTED_REPORT_SIZE 0x02 + +#define HID_VIRTUAL_CABLE_UNPLUG 0x15 + +static struct queue *list; /* List of hidhost test cases */ + +#define did_req_pdu 0x06, \ + 0x00, 0x00, \ + 0x00, 0x0f, \ + 0x35, 0x03, \ + 0x19, 0x12, 0x00, 0xff, 0xff, 0x35, 0x05, 0x0a, 0x00, \ + 0x00, 0xff, 0xff, 0x00 + +#define did_rsp_pdu 0x07, \ + 0x00, 0x00, \ + 0x00, 0x4f, \ + 0x00, 0x4c, \ + 0x35, 0x4a, 0x35, 0x48, 0x09, 0x00, 0x00, 0x0a, 0x00, \ + 0x01, 0x00, 0x00, 0x09, 0x00, 0x01, 0x35, 0x03, 0x19, \ + 0x12, 0x00, 0x09, 0x00, 0x05, 0x35, 0x03, 0x19, 0x10, \ + 0x02, 0x09, 0x00, 0x09, 0x35, 0x08, 0x35, 0x06, 0x19, \ + 0x12, 0x00, 0x09, 0x01, 0x03, 0x09, 0x02, 0x00, 0x09, \ + 0x01, 0x03, 0x09, 0x02, 0x01, 0x09, 0x1d, 0x6b, 0x09, \ + 0x02, 0x02, 0x09, 0x02, 0x46, 0x09, 0x02, 0x03, 0x09, \ + 0x05, 0x0e, 0x09, 0x02, 0x04, 0x28, 0x01, 0x09, 0x02, \ + 0x05, 0x09, 0x00, 0x02, \ + 0x00 + +#define hid_req_pdu 0x06, \ + 0x00, 0x01, \ + 0x00, 0x0f, \ + 0x35, 0x03, \ + 0x19, 0x11, 0x24, 0xff, 0xff, 0x35, 0x05, 0x0a, 0x00, \ + 0x00, 0xff, 0xff, 0x00 + +#define hid_rsp_pdu 0x07, \ + 0x00, 0x01, \ + 0x01, 0x71, \ + 0x01, 0x6E, \ + 0x36, 0x01, 0x6b, 0x36, 0x01, 0x68, 0x09, 0x00, 0x00, \ + 0x0a, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x01, 0x35, \ + 0x03, 0x19, 0x11, 0x24, 0x09, 0x00, 0x04, 0x35, 0x0d, \ + 0x35, 0x06, 0x19, 0x01, 0x00, 0x09, 0x00, 0x11, 0x35, \ + 0x03, 0x19, 0x00, 0x11, 0x09, 0x00, 0x05, 0x35, 0x03, \ + 0x19, 0x10, 0x02, 0x09, 0x00, 0x06, 0x35, 0x09, 0x09, \ + 0x65, 0x6e, 0x09, 0x00, 0x6a, 0x09, 0x01, 0x00, 0x09, \ + 0x00, 0x09, 0x35, 0x08, 0x35, 0x06, 0x19, 0x11, 0x24, \ + 0x09, 0x01, 0x00, 0x09, 0x00, 0x0d, 0x35, 0x0f, 0x35, \ + 0x0d, 0x35, 0x06, 0x19, 0x01, 0x00, 0x09, 0x00, 0x13, \ + 0x35, 0x03, 0x19, 0x00, 0x11, 0x09, 0x01, 0x00, 0x25, \ + 0x1e, 0x4c, 0x6f, 0x67, 0x69, 0x74, 0x65, 0x63, 0x68, \ + 0x20, 0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, \ + 0x68, 0x20, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x20, 0x4d, \ + 0x35, 0x35, 0x35, 0x62, 0x09, 0x01, 0x01, 0x25, 0x0f, \ + 0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68, \ + 0x20, 0x4d, 0x6f, 0x75, 0x73, 0x65, 0x09, 0x01, 0x02, \ + 0x25, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x74, 0x65, 0x63, \ + 0x68, 0x09, 0x02, 0x00, 0x09, 0x01, 0x00, 0x09, 0x02, \ + 0x01, 0x09, 0x01, 0x11, 0x09, 0x02, 0x02, 0x08, 0x80, \ + 0x09, 0x02, 0x03, 0x08, 0x21, 0x09, 0x02, 0x04, 0x28, \ + 0x01, 0x09, 0x02, 0x05, 0x28, 0x01, 0x09, 0x02, 0x06, \ + 0x35, 0x74, 0x35, 0x72, 0x08, 0x22, 0x25, 0x6e, 0x05, \ + 0x01, 0x09, 0x02, 0xa1, 0x01, 0x85, 0x02, 0x09, 0x01, \ + 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x08, 0x15, \ + 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, \ + 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x16, 0x01, 0xf8, \ + 0x26, 0xff, 0x07, 0x75, 0x0c, 0x95, 0x02, 0x81, 0x06, \ + 0x09, 0x38, 0x15, 0x81, 0x25, 0x7f, 0x75, 0x08, 0x95, \ + 0x01, 0x81, 0x06, 0x05, 0x0c, 0x0a, 0x38, 0x02, 0x81, \ + 0x06, 0x05, 0x09, 0x19, 0x09, 0x29, 0x10, 0x15, 0x00, \ + 0x25, 0x01, 0x95, 0x08, 0x75, 0x01, 0x81, 0x02, 0xc0, \ + 0xc0, 0x06, 0x00, 0xff, 0x09, 0x01, 0xa1, 0x01, 0x85, \ + 0x10, 0x75, 0x08, 0x95, 0x06, 0x15, 0x00, 0x26, 0xff, \ + 0x00, 0x09, 0x01, 0x81, 0x00, 0x09, 0x01, 0x91, 0x00, \ + 0xc0, 0x09, 0x02, 0x07, 0x35, 0x08, 0x35, 0x06, 0x09, \ + 0x04, 0x09, 0x09, 0x01, 0x00, 0x09, 0x02, 0x08, 0x28, \ + 0x00, 0x09, 0x02, 0x09, 0x28, 0x01, 0x09, 0x02, 0x0a, \ + 0x28, 0x01, 0x09, 0x02, 0x0b, 0x09, 0x01, 0x00, 0x09, \ + 0x02, 0x0c, 0x09, 0x0c, 0x80, 0x09, 0x02, 0x0d, 0x28, \ + 0x00, 0x09, 0x02, 0x0e, 0x28, 0x01, \ + 0x00 + +static const struct pdu_set sdp_pdus[] = { + { raw_pdu(did_req_pdu), raw_pdu(did_rsp_pdu) }, + { raw_pdu(hid_req_pdu), raw_pdu(hid_rsp_pdu) }, + { end_pdu, end_pdu }, +}; + +static struct emu_l2cap_cid_data sdp_cid_data = { + .pdu = sdp_pdus, + .is_sdp = TRUE, +}; + +#define hid_keyboard_rsp_pdu 0x07, \ + 0x00, 0x01, \ + 0x02, 0x04, \ + 0x02, 0x01, \ + 0x36, 0x01, 0xfe, 0x36, 0x01, 0x93, 0x09, 0x00, 0x00, \ + 0x0a, 0x00, 0x01, 0x00, 0x00, 0x09, 0x00, 0x01, 0x35, \ + 0x03, 0x19, 0x11, 0x24, 0x09, 0x00, 0x04, 0x35, 0x0d, \ + 0x35, 0x06, 0x19, 0x01, 0x00, 0x09, 0x00, 0x11, 0x35, \ + 0x03, 0x19, 0x00, 0x11, 0x09, 0x00, 0x06, 0x35, 0x09, \ + 0x09, 0x65, 0x6e, 0x09, 0x00, 0x6a, 0x09, 0x01, 0x00, \ + 0x09, 0x00, 0x09, 0x35, 0x08, 0x35, 0x06, 0x19, 0x11, \ + 0x24, 0x09, 0x01, 0x00, 0x09, 0x00, 0x0d, 0x35, 0x0f, \ + 0x35, 0x0d, 0x35, 0x06, 0x19, 0x01, 0x00, 0x09, 0x00, \ + 0x13, 0x35, 0x03, 0x19, 0x00, 0x11, 0x09, 0x01, 0x00, \ + 0x25, 0x10, 0x53, 0x41, 0x4d, 0x53, 0x55, 0x4e, 0x47, \ + 0x20, 0x4b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, \ + 0x09, 0x01, 0x01, 0x25, 0x08, 0x4b, 0x65, 0x79, 0x62, \ + 0x6f, 0x61, 0x72, 0x64, 0x09, 0x01, 0x02, 0x25, 0x0d, \ + 0x43, 0x53, 0x52, 0x20, 0x48, 0x49, 0x44, 0x45, 0x6e, \ + 0x67, 0x69, 0x6e, 0x65, 0x09, 0x02, 0x00, 0x09, 0x01, \ + 0x00, 0x09, 0x02, 0x01, 0x09, 0x01, 0x11, 0x09, 0x02, \ + 0x02, 0x08, 0x40, 0x09, 0x02, 0x03, 0x08, 0x23, 0x09, \ + 0x02, 0x04, 0x28, 0x01, 0x09, 0x02, 0x05, 0x28, 0x01, \ + 0x09, 0x02, 0x06, 0x35, 0xb7, 0x35, 0xb5, 0x08, 0x22, \ + 0x25, 0xb1, 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, \ + 0x07, 0x85, 0x01, 0x19, 0xe0, 0x29, 0xe7, 0x15, 0x00, \ + 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, \ + 0x01, 0x75, 0x08, 0x81, 0x01, 0x95, 0x05, 0x75, 0x01, \ + 0x05, 0x08, 0x85, 0x01, 0x19, 0x01, 0x29, 0x05, 0x91, \ + 0x02, 0x95, 0x01, 0x75, 0x03, 0x91, 0x03, 0x95, 0x06, \ + 0x75, 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, \ + 0x00, 0x29, 0x6f, 0x81, 0x00, 0xc0, 0x05, 0x0c, 0x09, \ + 0x01, 0xa1, 0x01, 0x85, 0x02, 0x05, 0x0c, 0x15, 0x00, \ + 0x25, 0x01, 0x75, 0x01, 0x95, 0x18, 0x09, 0xe2, 0x09, \ + 0xea, 0x09, 0xe9, 0x09, 0xb7, 0x09, 0xcd, 0x0a, 0x23, \ + 0x02, 0x0a, 0x8a, 0x01, 0x0a, 0x21, 0x02, 0x75, 0x01, \ + 0x95, 0x03, 0x81, 0x02, 0x75, 0x01, 0x95, 0x05, 0x81, \ + 0x01, 0x05, 0x08, 0x85, 0xff, 0x95, 0x01, 0x75, 0x02, \ + 0x09, 0x24, 0x09, 0x26, 0x81, 0x02, 0x75, 0x06, 0x81, \ + 0x01, 0xc0, 0x06, 0x7f, 0xff, 0x09, 0x01, 0xa1, 0x01, \ + 0x85, 0x03, 0x15, 0x00, 0x25, 0x01, 0x09, 0xb9, 0x09, \ + 0xb5, 0x09, 0xba, 0x09, 0xbb, 0x09, 0xbc, 0x09, 0xbd, \ + 0x09, 0xb6, 0x09, 0xb7, 0x75, 0x01, 0x95, 0x06, 0x81, \ + 0x02, 0x75, 0x01, 0x95, 0x02, 0x81, 0x01, 0xc0, 0x09, \ + 0x02, 0x07, 0x35, 0x08, 0x35, 0x06, 0x09, 0x04, 0x09, \ + 0x09, 0x01, 0x00, 0x09, 0x02, 0x08, 0x28, 0x00, 0x09, \ + 0x02, 0x09, 0x28, 0x01, 0x09, 0x02, 0x0a, 0x28, 0x01, \ + 0x09, 0x02, 0x0b, 0x09, 0x01, 0x00, 0x09, 0x02, 0x0c, \ + 0x09, 0x1f, 0x40, 0x09, 0x02, 0x0d, 0x28, 0x00, 0x09, \ + 0x02, 0x0e, 0x28, 0x01, 0x36, 0x00, 0x65, 0x09, 0x00, \ + 0x00, 0x0a, 0x00, 0x01, 0x00, 0x01, 0x09, 0x00, 0x01, \ + 0x35, 0x03, 0x19, 0x12, 0x00, 0x09, 0x00, 0x04, 0x35, \ + 0x0d, 0x35, 0x06, 0x19, 0x01, 0x00, 0x09, 0x00, 0x01, \ + 0x35, 0x03, 0x19, 0x00, 0x01, 0x09, 0x00, 0x06, 0x35, \ + 0x09, 0x09, 0x65, 0x6e, 0x09, 0x00, 0x6a, 0x09, 0x01, \ + 0x00, 0x09, 0x00, 0x09, 0x35, 0x08, 0x35, 0x06, 0x19, \ + 0x12, 0x00, 0x09, 0x01, 0x00, 0x09, 0x01, 0x01, 0x25, \ + 0x00, 0x09, 0x02, 0x00, 0x09, 0x01, 0x00, 0x09, 0x02, \ + 0x01, 0x09, 0x23, 0x3d, 0x09, 0x02, 0x02, 0x09, 0x01, \ + 0x3d, 0x09, 0x02, 0x03, 0x09, 0x00, 0x00, 0x09, 0x02, \ + 0x04, 0x28, 0x01, 0x09, 0x02, 0x05, 0x09, 0x00, 0x02, \ + 0x00 + +static const struct pdu_set sdp_kb_pdus[] = { + { raw_pdu(did_req_pdu), raw_pdu(did_rsp_pdu) }, + { raw_pdu(hid_req_pdu), raw_pdu(hid_keyboard_rsp_pdu) }, + { end_pdu, end_pdu }, +}; + +static struct emu_l2cap_cid_data sdp_kb_cid_data = { + .pdu = sdp_kb_pdus, + .is_sdp = TRUE, +}; + +static struct emu_l2cap_cid_data ctrl_cid_data; +static struct emu_l2cap_cid_data intr_cid_data; + +static void hid_prepare_reply_protocol_mode(struct emu_l2cap_cid_data *cid_data) +{ + struct test_data *t_data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(t_data->hciemu); + const struct iovec pdu = raw_pdu(0xa0, 0x00); + + bthost_send_cid_v(bthost, cid_data->handle, cid_data->cid, &pdu, 1); +} + +static void hid_prepare_reply_report(struct emu_l2cap_cid_data *cid_data) +{ + struct test_data *t_data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(t_data->hciemu); + const struct iovec pdu = raw_pdu(0xa2, 0x01, 0x00); + + bthost_send_cid_v(bthost, cid_data->handle, cid_data->cid, &pdu, 1); +} + +static void hid_ctrl_cid_hook_cb(const void *data, uint16_t len, + void *user_data) +{ + struct emu_l2cap_cid_data *cid_data = user_data; + uint8_t header = ((uint8_t *) data)[0]; + struct step *step; + + switch (header) { + case HID_GET_REPORT_PROTOCOL: + case HID_GET_BOOT_PROTOCOL: + case HID_SET_REPORT_PROTOCOL: + case HID_SET_BOOT_PROTOCOL: + hid_prepare_reply_protocol_mode(cid_data); + break; + case HID_GET_INPUT_REPORT: + case HID_GET_OUTPUT_REPORT: + case HID_GET_FEATURE_REPORT: + hid_prepare_reply_report(cid_data); + break; + /* + * HID device doesnot reply for this commads, so reaching pdu's + * to hid device means assuming test passed + */ + case HID_SET_INPUT_REPORT: + case HID_SET_OUTPUT_REPORT: + case HID_SET_FEATURE_REPORT: + case HID_SEND_DATA: + /* Successfully verify sending data step */ + step = g_new0(struct step, 1); + + step->callback = CB_EMU_CONFIRM_SEND_DATA; + + schedule_callback_verification(step); + break; + case HID_VIRTUAL_CABLE_UNPLUG: + step = g_new0(struct step, 1); + + step->callback = CB_EMU_CONNECTION_REJECTED; + + schedule_callback_verification(step); + break; + } +} +static void hid_ctrl_connect_cb(uint16_t handle, uint16_t cid, void *user_data) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + struct emu_l2cap_cid_data *cid_data = user_data; + + cid_data->handle = handle; + cid_data->cid = cid; + + bthost_add_cid_hook(bthost, handle, cid, hid_ctrl_cid_hook_cb, + cid_data); +} + +static void hid_intr_cid_hook_cb(const void *data, uint16_t len, + void *user_data) +{ + uint8_t header = ((uint8_t *) data)[0]; + struct step *step; + + switch (header) { + case HID_SEND_DATA: + /* Successfully verify sending data step */ + step = g_new0(struct step, 1); + + step->callback = CB_EMU_CONFIRM_SEND_DATA; + + schedule_callback_verification(step); + break; + } +} +static void hid_intr_connect_cb(uint16_t handle, uint16_t cid, void *user_data) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + struct emu_l2cap_cid_data *cid_data = user_data; + + cid_data->handle = handle; + cid_data->cid = cid; + + bthost_add_cid_hook(bthost, handle, cid, hid_intr_cid_hook_cb, + cid_data); +} + +static bt_scan_mode_t setprop_scan_mode_conn_val = BT_SCAN_MODE_CONNECTABLE; + +static bt_property_t prop_test_scan_mode_conn = { + .type = BT_PROPERTY_ADAPTER_SCAN_MODE, + .val = &setprop_scan_mode_conn_val, + .len = sizeof(setprop_scan_mode_conn_val), +}; + +/* Emulate SDP (PSM = 1) */ +static struct emu_set_l2cap_data l2cap_setup_sdp_data = { + .psm = 1, + .func = tester_generic_connect_cb, + .user_data = &sdp_cid_data, +}; + +static struct emu_set_l2cap_data l2cap_setup_kb_sdp_data = { + .psm = 1, + .func = tester_generic_connect_cb, + .user_data = &sdp_kb_cid_data, +}; + +/* Emulate Control Channel (PSM = 17) */ +static struct emu_set_l2cap_data l2cap_setup_cc_data = { + .psm = 17, + .func = hid_ctrl_connect_cb, + .user_data = &ctrl_cid_data, +}; + +/* Emulate Interrupt Channel (PSM = 19) */ +static struct emu_set_l2cap_data l2cap_setup_ic_data = { + .psm = 19, + .func = hid_intr_connect_cb, + .user_data = &intr_cid_data, +}; + +static void hidhost_connect_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + + step->action_status = data->if_hid->connect(&bdaddr); + + schedule_action_verification(step); +} + +static void hidhost_disconnect_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + + step->action_status = data->if_hid->disconnect(&bdaddr); + + schedule_action_verification(step); +} + +static void hidhost_virtual_unplug_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + + step->action_status = data->if_hid->virtual_unplug(&bdaddr); + + schedule_action_verification(step); +} + +static void hidhost_get_protocol_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + + step->action_status = data->if_hid->get_protocol(&bdaddr, + BTHH_REPORT_MODE); + + schedule_action_verification(step); +} + +static void hidhost_set_protocol_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + + step->action_status = data->if_hid->set_protocol(&bdaddr, + BTHH_REPORT_MODE); + + schedule_action_verification(step); +} + +static void hidhost_get_report_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + + step->action_status = data->if_hid->get_report(&bdaddr, + BTHH_INPUT_REPORT, 1, + 20); + + schedule_action_verification(step); +} + +static void hidhost_set_report_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + char *buf = "fe0201"; + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + + step->action_status = data->if_hid->send_data(&bdaddr, buf); + schedule_action_verification(step); +} + +static void hidhost_send_data_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *hid_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + char *buf = "010101"; + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) hid_addr, &bdaddr); + + step->action_status = data->if_hid->set_report(&bdaddr, + BTHH_INPUT_REPORT, buf); + schedule_action_verification(step); +} + +static void client_l2cap_rsp(uint8_t code, const void *data, uint16_t len, + void *user_data) +{ + struct test_data *t_data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(t_data->hciemu); + struct emu_l2cap_cid_data *cid_data = user_data; + const uint16_t *psm = data; + const struct iovec con_req = raw_pdu(0x13, 0x00, /* PSM */ + 0x41, 0x00); /* Source CID */ + + if (len < sizeof(*psm)) { + tester_warn("Invalid l2cap response."); + return; + } + + switch (*psm) { + case 0x40: + bthost_add_cid_hook(bthost, cid_data->handle, 0x40, + hid_ctrl_cid_hook_cb, cid_data); + + bthost_l2cap_req(bthost, cid_data->handle, 0x02, + con_req.iov_base, con_req.iov_len, + client_l2cap_rsp, cid_data); + break; + case 0x41: + bthost_add_cid_hook(bthost, cid_data->handle, 0x41, + hid_intr_cid_hook_cb, cid_data); + break; + default: + break; + } +} + +static void hidhost_conn_cb(uint16_t handle, void *user_data) +{ + const struct iovec con_req = raw_pdu(0x11, 0x00, /* PSM */ + 0x40, 0x00); /* Source CID */ + + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + + if (data->hciemu_type == HCIEMU_TYPE_BREDR) { + tester_warn("Not handled device type."); + return; + } + + ctrl_cid_data.cid = 0x40; + ctrl_cid_data.handle = handle; + + tester_print("Sending L2CAP Request from remote"); + + bthost_l2cap_req(bthost, handle, 0x02, con_req.iov_base, + con_req.iov_len, client_l2cap_rsp, + &ctrl_cid_data); +} + +static struct test_case test_cases[] = { + TEST_CASE_BREDRLE("HidHost Init", + ACTION_SUCCESS(dummy_action, NULL), + ), + TEST_CASE_BREDRLE("HidHost Connect Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_ic_data), + ACTION_SUCCESS(hidhost_connect_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_CONNECTED), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_DISCONNECTED), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("HidHost Disconnect Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_ic_data), + ACTION_SUCCESS(hidhost_connect_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hidhost_disconnect_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_DISCONNECTED), + ), + TEST_CASE_BREDRLE("HidHost VirtualUnplug Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_ic_data), + ACTION_SUCCESS(hidhost_connect_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hidhost_virtual_unplug_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_DISCONNECTED), + ), + TEST_CASE_BREDRLE("HidHost GetProtocol Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_ic_data), + ACTION_SUCCESS(hidhost_connect_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hidhost_get_protocol_action, NULL), + CALLBACK_HH_MODE(CB_HH_PROTOCOL_MODE, BTHH_OK, HID_MODE_BREDR), + ), + TEST_CASE_BREDRLE("HidHost SetProtocol Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_ic_data), + ACTION_SUCCESS(hidhost_connect_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hidhost_set_protocol_action, NULL), + CALLBACK_HH_MODE(CB_HH_PROTOCOL_MODE, BTHH_OK, HID_MODE_BREDR), + ), + TEST_CASE_BREDRLE("HidHost GetReport Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_ic_data), + ACTION_SUCCESS(hidhost_connect_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hidhost_get_report_action, NULL), + CALLBACK_HHREPORT(CB_HH_GET_REPORT, BTHH_OK, + HID_EXPECTED_REPORT_SIZE), + ), + TEST_CASE_BREDRLE("HidHost SetReport Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_ic_data), + ACTION_SUCCESS(hidhost_connect_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_CONNECTING), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hidhost_set_report_action, NULL), + CALLBACK(CB_EMU_CONFIRM_SEND_DATA), + ), + TEST_CASE_BREDRLE("HidHost SendData Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_ic_data), + ACTION_SUCCESS(hidhost_connect_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hidhost_send_data_action, NULL), + CALLBACK(CB_EMU_CONFIRM_SEND_DATA), + ), + TEST_CASE_BREDRLE("HidHost Connect Encrypted Success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_kb_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_ic_data), + ACTION_SUCCESS(hidhost_connect_action, NULL), + CALLBACK(CB_EMU_ENCRYPTION_ENABLED), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_CONNECTED), + ACTION_SUCCESS(hidhost_send_data_action, NULL), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_HH_CONNECTION_STATE, + BTHH_CONN_STATE_DISCONNECTED), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("HidHost Reject Unknown Remote Connection", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(bt_set_property_action, + &prop_test_scan_mode_conn), + CALLBACK_ADAPTER_PROPS(&prop_test_scan_mode_conn, 1), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_kb_sdp_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_cc_data), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_setup_ic_data), + /* Trigger incoming connection */ + ACTION_SUCCESS(emu_set_connect_cb_action, hidhost_conn_cb), + ACTION_SUCCESS(emu_remote_connect_hci_action, NULL), + CALLBACK(CB_EMU_CONNECTION_REJECTED), + ), +}; + +struct queue *get_hidhost_tests(void) +{ + uint16_t i = 0; + + list = queue_new(); + + for (; i < sizeof(test_cases) / sizeof(test_cases[0]); ++i) + queue_push_tail(list, &test_cases[i]); + + return list; +} + +void remove_hidhost_tests(void) +{ + queue_destroy(list, NULL); +} diff --git a/android/tester-main.c b/android/tester-main.c new file mode 100644 index 0000000..3c5af29 --- /dev/null +++ b/android/tester-main.c @@ -0,0 +1,3378 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/mgmt.h" +#include "src/shared/util.h" +#include "src/shared/tester.h" +#include "src/shared/mgmt.h" +#include "src/shared/queue.h" +#include "emulator/bthost.h" +#include "monitor/bt.h" +#include "tester-main.h" + +static char exec_dir[PATH_MAX + 1]; + +static gint scheduled_cbacks_num; + +#define EMULATOR_SIGNAL_TIMEOUT 2 /* in seconds */ +#define EMULATOR_SIGNAL "emulator_started" + +#define BT_TRANSPORT_UNKNOWN 0x00 + +static struct { + uint16_t cb_num; + const char *str; +} cb_table[] = { + DBG_CB(CB_BT_NONE), + DBG_CB(CB_BT_ADAPTER_STATE_CHANGED), + DBG_CB(CB_BT_ADAPTER_PROPERTIES), + DBG_CB(CB_BT_REMOTE_DEVICE_PROPERTIES), + DBG_CB(CB_BT_DEVICE_FOUND), + DBG_CB(CB_BT_DISCOVERY_STATE_CHANGED), + DBG_CB(CB_BT_PIN_REQUEST), + DBG_CB(CB_BT_SSP_REQUEST), + DBG_CB(CB_BT_BOND_STATE_CHANGED), + DBG_CB(CB_BT_ACL_STATE_CHANGED), + DBG_CB(CB_BT_THREAD_EVT), + DBG_CB(CB_BT_DUT_MODE_RECV), + DBG_CB(CB_BT_LE_TEST_MODE), + + /* Hidhost cb */ + DBG_CB(CB_HH_CONNECTION_STATE), + DBG_CB(CB_HH_HID_INFO), + DBG_CB(CB_HH_PROTOCOL_MODE), + DBG_CB(CB_HH_IDLE_TIME), + DBG_CB(CB_HH_GET_REPORT), + DBG_CB(CB_HH_VIRTUAL_UNPLUG), + + /* PAN cb */ + DBG_CB(CB_PAN_CONTROL_STATE), + DBG_CB(CB_PAN_CONNECTION_STATE), + + /* HDP cb */ + DBG_CB(CB_HDP_APP_REG_STATE), + DBG_CB(CB_HDP_CHANNEL_STATE), + + /* A2DP cb */ + DBG_CB(CB_A2DP_CONN_STATE), + DBG_CB(CB_A2DP_AUDIO_STATE), + + /* AVRCP */ + DBG_CB(CB_AVRCP_PLAY_STATUS_REQ), + DBG_CB(CB_AVRCP_PLAY_STATUS_RSP), + DBG_CB(CB_AVRCP_REG_NOTIF_REQ), + DBG_CB(CB_AVRCP_REG_NOTIF_RSP), + DBG_CB(CB_AVRCP_GET_ATTR_REQ), + DBG_CB(CB_AVRCP_GET_ATTR_RSP), + + /* Gatt client */ + DBG_CB(CB_GATTC_REGISTER_CLIENT), + DBG_CB(CB_GATTC_SCAN_RESULT), + DBG_CB(CB_GATTC_OPEN), + DBG_CB(CB_GATTC_CLOSE), + DBG_CB(CB_GATTC_SEARCH_COMPLETE), + DBG_CB(CB_GATTC_SEARCH_RESULT), + DBG_CB(CB_GATTC_GET_CHARACTERISTIC), + DBG_CB(CB_GATTC_GET_DESCRIPTOR), + DBG_CB(CB_GATTC_GET_INCLUDED_SERVICE), + DBG_CB(CB_GATTC_REGISTER_FOR_NOTIFICATION), + DBG_CB(CB_GATTC_NOTIFY), + DBG_CB(CB_GATTC_READ_CHARACTERISTIC), + DBG_CB(CB_GATTC_WRITE_CHARACTERISTIC), + DBG_CB(CB_GATTC_READ_DESCRIPTOR), + DBG_CB(CB_GATTC_WRITE_DESCRIPTOR), + DBG_CB(CB_GATTC_EXECUTE_WRITE), + DBG_CB(CB_GATTC_READ_REMOTE_RSSI), + DBG_CB(CB_GATTC_LISTEN), + + /* Gatt server */ + DBG_CB(CB_GATTS_REGISTER_SERVER), + DBG_CB(CB_GATTS_CONNECTION), + DBG_CB(CB_GATTS_SERVICE_ADDED), + DBG_CB(CB_GATTS_INCLUDED_SERVICE_ADDED), + DBG_CB(CB_GATTS_CHARACTERISTIC_ADDED), + DBG_CB(CB_GATTS_DESCRIPTOR_ADDED), + DBG_CB(CB_GATTS_SERVICE_STARTED), + DBG_CB(CB_GATTS_SERVICE_STOPPED), + DBG_CB(CB_GATTS_SERVICE_DELETED), + DBG_CB(CB_GATTS_REQUEST_READ), + DBG_CB(CB_GATTS_REQUEST_WRITE), + DBG_CB(CB_GATTS_REQUEST_EXEC_WRITE), + DBG_CB(CB_GATTS_RESPONSE_CONFIRMATION), + DBG_CB(CB_GATTS_INDICATION_SEND), + + /* Map client */ + DBG_CB(CB_MAP_CLIENT_REMOTE_MAS_INSTANCES), + + /* Emulator callbacks */ + DBG_CB(CB_EMU_CONFIRM_SEND_DATA), + DBG_CB(CB_EMU_ENCRYPTION_ENABLED), + DBG_CB(CB_EMU_ENCRYPTION_DISABLED), + DBG_CB(CB_EMU_CONNECTION_REJECTED), + DBG_CB(CB_EMU_VALUE_INDICATION), + DBG_CB(CB_EMU_VALUE_NOTIFICATION), + DBG_CB(CB_EMU_READ_RESPONSE), + DBG_CB(CB_EMU_WRITE_RESPONSE), + DBG_CB(CB_EMU_ATT_ERROR), +}; + +static gboolean check_callbacks_called(gpointer user_data) +{ + /* + * Wait for all callbacks scheduled in current test context to execute + * in main loop. This will avoid late callback calls after test case has + * already failed or timed out. + */ + + if (g_atomic_int_get(&scheduled_cbacks_num) == 0) { + tester_teardown_complete(); + return FALSE; + } else if (scheduled_cbacks_num < 0) { + tester_warn("Unscheduled callback called!"); + return FALSE; + } + + return TRUE; +} + +static void check_daemon_term(void) +{ + int status; + pid_t pid; + struct test_data *data = tester_get_data(); + + if (!data) + return; + + pid = waitpid(data->bluetoothd_pid, &status, WNOHANG); + if (pid != data->bluetoothd_pid) + return; + + data->bluetoothd_pid = 0; + + if (WIFEXITED(status) && (WEXITSTATUS(status) == EXIT_SUCCESS)) { + g_idle_add(check_callbacks_called, NULL); + return; + } + + tester_warn("Unexpected Daemon shutdown with status %d", status); +} + +static gboolean signal_handler(GIOChannel *channel, GIOCondition cond, + gpointer user_data) +{ + 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 SIGCHLD: + check_daemon_term(); + break; + } + + return TRUE; +} + +static guint setup_signalfd(void) +{ + GIOChannel *channel; + guint source; + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) + return 0; + + fd = signalfd(-1, &mask, 0); + if (fd < 0) + 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 void test_post_teardown(const void *test_data) +{ + struct test_data *data = tester_get_data(); + + /* remove hook for encryption change */ + hciemu_del_hook(data->hciemu, HCIEMU_HOOK_POST_EVT, 0x08); + + hciemu_unref(data->hciemu); + data->hciemu = NULL; + + g_source_remove(data->signalfd); + data->signalfd = 0; +} + +static void bluetoothd_start(int hci_index) +{ + char prg_name[PATH_MAX + 1 + 11]; + char index[8]; + char *prg_argv[5]; + + 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] = "-d"; + prg_argv[4] = 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 = EMULATOR_SIGNAL_TIMEOUT; + 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, "bluetooth.start=daemon")) + goto failed; + + close(pipe); + close(fd); + return bluetoothd_start(hci_index); + +failed: + close(pipe); + + if (fd >= 0) + close(fd); +} + +static void mgmt_debug(const char *str, void *user_data) +{ + const char *prefix = user_data; + + tester_print("%s%s", prefix, str); +} + +static bool hciemu_post_encr_hook(const void *data, uint16_t len, + void *user_data) +{ + struct step *step; + + /* + * Expected data: status (1 octet) + conn. handle (2 octets) + + * encryption flag (1 octet) + */ + if (len < 4) + return true; + + step = g_new0(struct step, 1); + + step->callback = ((uint8_t *)data)[3] ? CB_EMU_ENCRYPTION_ENABLED : + CB_EMU_ENCRYPTION_DISABLED; + + schedule_callback_verification(step); + return true; +} + +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; + } + + /* set hook for encryption change */ + hciemu_add_hook(data->hciemu, HCIEMU_HOOK_POST_EVT, 0x08, + hciemu_post_encr_hook, NULL); + + 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 *test_data) +{ + struct test_data *data = tester_get_data(); + + data->signalfd = setup_signalfd(); + if (!data->signalfd) { + tester_warn("Failed to setup signalfd"); + tester_pre_setup_failed(); + return; + } + + data->mgmt = mgmt_new_default(); + if (!data->mgmt) { + tester_warn("Failed to setup management interface"); + tester_pre_setup_failed(); + return; + } + + if (!tester_use_debug()) + fclose(stderr); + else + mgmt_set_debug(data->mgmt, mgmt_debug, "mgmt: ", NULL); + + mgmt_send(data->mgmt, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0, + NULL, read_index_list_callback, NULL, NULL); +} + +static bool match_property(bt_property_t *exp_prop, bt_property_t *rec_prop, + int prop_num) +{ + if (exp_prop->type && (exp_prop->type != rec_prop->type)) + return 0; + + if (exp_prop->len && (exp_prop->len != rec_prop->len)) { + tester_debug("Property [%d] len don't match! received=%d, " + "expected=%d", prop_num, rec_prop->len, + exp_prop->len); + return 0; + } + + if (exp_prop->val && memcmp(exp_prop->val, rec_prop->val, + exp_prop->len)) { + tester_debug("Property [%d] value don't match!", prop_num); + return 0; + } + + return 1; +} + +static bool match_mas_inst(btmce_mas_instance_t *exp_inst, + btmce_mas_instance_t *rec_inst, int inst_num) +{ + if (exp_inst->id && (exp_inst->id != rec_inst->id)) { + tester_debug("MAS inst. [%d] id missmatch %d vs %d", inst_num, + rec_inst->id, exp_inst->id); + return 0; + } + + if (exp_inst->scn && (exp_inst->scn != rec_inst->scn)) { + tester_debug("MAS inst. [%d] scn missmatch %d vs %d", inst_num, + rec_inst->scn, exp_inst->scn); + return 0; + } + + if (exp_inst->msg_types && + (exp_inst->msg_types != rec_inst->msg_types)) { + tester_debug("Mas inst. [%d] mesg type missmatch %d vs %d", + inst_num, rec_inst->scn, exp_inst->scn); + return 0; + } + + if (exp_inst->p_name && memcmp(exp_inst->p_name, rec_inst->p_name, + strlen(exp_inst->p_name))) { + tester_debug("Mas inst. [%d] name don't match!", inst_num); + return 0; + } + + return 1; +} + +static int verify_property(bt_property_t *exp_props, int exp_num_props, + bt_property_t *rec_props, int rec_num_props) +{ + int i, j; + int exp_prop_to_find = exp_num_props; + + if (rec_num_props == 0) + return 1; + + if (exp_num_props == 0) { + tester_debug("Wrong number of expected properties given"); + tester_test_failed(); + return 1; + } + + /* Get first exp prop to match and search for it */ + for (i = 0; i < exp_num_props; i++) { + for (j = 0; j < rec_num_props; j++) { + if (match_property(&exp_props[i], &rec_props[j], i)) { + exp_prop_to_find--; + break; + } + } + } + + return exp_prop_to_find; +} + +static int verify_mas_inst(btmce_mas_instance_t *exp_inst, int exp_num_inst, + btmce_mas_instance_t *rec_inst, + int rec_num_inst) +{ + int i, j; + int exp_inst_to_find = exp_num_inst; + + if (rec_num_inst == 0) + return 1; + + if (exp_num_inst == 0) { + tester_debug("Wrong number of expected MAS instances given"); + tester_test_failed(); + return 1; + } + + for (i = 0; i < exp_num_inst; i++) { + for (j = 0; j < rec_num_inst; j++) { + if (match_mas_inst(&exp_inst[i], &rec_inst[i], i)) { + exp_inst_to_find--; + break; + } + } + } + + return exp_inst_to_find; +} + +/* + * Check each test case step if test case expected + * data is set and match it with expected result. + */ + +static bool verify_gatt_ids(btgatt_gatt_id_t *a, btgatt_gatt_id_t *b) +{ + + if (memcmp(&a->uuid, &b->uuid, sizeof(bt_uuid_t))) + return false; + + if (a->inst_id != b->inst_id) + return false; + + return true; +} + +static bool verify_services(btgatt_srvc_id_t *a, btgatt_srvc_id_t *b) +{ + if (a->is_primary != b->is_primary) + return false; + + return verify_gatt_ids(&a->id, &b->id); +} + +static bool match_data(struct step *step) +{ + struct test_data *data = tester_get_data(); + const struct step *exp; + + exp = queue_peek_head(data->steps); + + if (!exp) { + /* Can occure while test passed already */ + tester_debug("Cannot get step to match"); + return false; + } + + if (exp->action_status != step->action_status) { + tester_debug("Action status don't match"); + return false; + } + + if (!exp->callback && !step->callback) + return true; + + if (exp->callback != step->callback) { + tester_debug("Callback type mismatch: %s vs %s", + cb_table[step->callback].str, + cb_table[exp->callback].str); + return false; + } + + if (exp->callback_result.state != step->callback_result.state) { + tester_debug("Callback state mismatch: %d vs %d", + step->callback_result.state, + exp->callback_result.state); + return false; + } + + if (exp->callback_result.status != step->callback_result.status) { + tester_debug("Callback status mismatch: %d vs %d", + step->callback_result.status, + exp->callback_result.status); + return false; + } + + if (exp->callback_result.mode != step->callback_result.mode) { + tester_debug("Callback mode mismatch: %02x vs %02x", + step->callback_result.mode, + exp->callback_result.mode); + return false; + } + + if (exp->callback_result.report_size != + step->callback_result.report_size) { + tester_debug("Callback report size mismatch: %d vs %d", + step->callback_result.report_size, + exp->callback_result.report_size); + return false; + } + + if (exp->callback_result.ctrl_state != + step->callback_result.ctrl_state) { + tester_debug("Callback ctrl state mismatch: %d vs %d", + step->callback_result.ctrl_state, + exp->callback_result.ctrl_state); + return false; + } + + if (exp->callback_result.conn_state != + step->callback_result.conn_state) { + tester_debug("Callback connection state mismatch: %d vs %d", + step->callback_result.conn_state, + exp->callback_result.conn_state); + return false; + } + + if (exp->callback_result.local_role != + step->callback_result.local_role) { + tester_debug("Callback local_role mismatch: %d vs %d", + step->callback_result.local_role, + exp->callback_result.local_role); + return false; + } + + if (exp->callback_result.remote_role != + step->callback_result.remote_role) { + tester_debug("Callback remote_role mismatch: %d vs %d", + step->callback_result.remote_role, + exp->callback_result.remote_role); + return false; + } + + if (exp->callback_result.app_id != step->callback_result.app_id) { + tester_debug("Callback app_id mismatch: %d vs %d", + step->callback_result.app_id, + exp->callback_result.app_id); + return false; + } + + if (exp->callback_result.channel_id != + step->callback_result.channel_id) { + tester_debug("Callback channel_id mismatch: %d vs %d", + step->callback_result.channel_id, + exp->callback_result.channel_id); + return false; + } + + if (exp->callback_result.mdep_cfg_index != + step->callback_result.mdep_cfg_index) { + tester_debug("Callback mdep_cfg_index mismatch: %d vs %d", + step->callback_result.mdep_cfg_index, + exp->callback_result.mdep_cfg_index); + return false; + } + + if (exp->callback_result.app_state != step->callback_result.app_state) { + tester_debug("Callback app_state mismatch: %d vs %d", + step->callback_result.app_state, + exp->callback_result.app_state); + return false; + } + + if (exp->callback_result.channel_state != + step->callback_result.channel_state) { + tester_debug("Callback channel_state mismatch: %d vs %d", + step->callback_result.channel_state, + exp->callback_result.channel_state); + return false; + } + + if (exp->callback_result.av_conn_state != + step->callback_result.av_conn_state) { + tester_debug("Callback av conn state mismatch: 0x%x vs 0x%x", + step->callback_result.av_conn_state, + exp->callback_result.av_conn_state); + return false; + } + + if (exp->callback_result.av_audio_state != + step->callback_result.av_audio_state) { + tester_debug("Callback av audio state mismatch: 0x%x vs 0x%x", + step->callback_result.av_audio_state, + exp->callback_result.av_audio_state); + return false; + } + + if (exp->callback_result.song_length != + step->callback_result.song_length) { + tester_debug("Callback song_length mismatch: 0x%x vs 0x%x", + step->callback_result.song_length, + exp->callback_result.song_length); + return false; + } + + if (exp->callback_result.song_position != + step->callback_result.song_position) { + tester_debug("Callback song_position mismatch: 0x%x vs 0x%x", + step->callback_result.song_position, + exp->callback_result.song_position); + return false; + } + + if (exp->callback_result.play_status != + step->callback_result.play_status) { + tester_debug("Callback play_status mismatch: 0x%x vs 0x%x", + step->callback_result.play_status, + exp->callback_result.play_status); + return false; + } + + if (exp->callback_result.rc_index != + step->callback_result.rc_index) { + tester_debug("Callback rc_index mismatch"); + return false; + } + + if (exp->callback_result.num_of_attrs != + step->callback_result.num_of_attrs) { + tester_debug("Callback rc num of attrs mismatch"); + return false; + } + + if (exp->callback_result.attrs) { + if (memcmp(step->callback_result.attrs, + exp->callback_result.attrs, + exp->callback_result.num_of_attrs * + sizeof(btrc_element_attr_val_t))) { + tester_debug("Callback rc element attributes doesn't match"); + return false; + } + } + + if (exp->callback_result.pairing_variant != + step->callback_result.pairing_variant) { + tester_debug("Callback pairing result mismatch: %d vs %d", + step->callback_result.pairing_variant, + exp->callback_result.pairing_variant); + return false; + } + + if (exp->callback_result.adv_data != step->callback_result.adv_data) { + tester_debug("Callback adv. data status mismatch: %d vs %d", + step->callback_result.adv_data, + exp->callback_result.adv_data); + return false; + } + + if (exp->callback_result.conn_id != step->callback_result.conn_id) { + tester_debug("Callback conn_id mismatch: %d vs %d", + step->callback_result.conn_id, + exp->callback_result.conn_id); + return false; + } + + if (exp->callback_result.gatt_app_id != + step->callback_result.gatt_app_id) { + tester_debug("Callback gatt_app_id mismatch: %d vs %d", + step->callback_result.gatt_app_id, + exp->callback_result.gatt_app_id); + return false; + } + + if (exp->callback_result.properties && + verify_property(exp->callback_result.properties, + exp->callback_result.num_properties, + step->callback_result.properties, + step->callback_result.num_properties)) { + tester_debug("Gatt properties don't match"); + return false; + } + + if (exp->callback_result.service && + !verify_services(step->callback_result.service, + exp->callback_result.service)) { + tester_debug("Gatt service doesn't match"); + return false; + } + + if (exp->callback_result.characteristic) { + btgatt_gatt_id_t *a; + btgatt_gatt_id_t *b; + a = step->callback_result.characteristic; + b = exp->callback_result.characteristic; + + if (!verify_gatt_ids(a, b)) { + tester_debug("Gatt char doesn't match"); + return false; + } + } + + if (exp->callback_result.char_prop != step->callback_result.char_prop) { + tester_debug("Gatt char prop mismatch: %d vs %d", + step->callback_result.char_prop, + exp->callback_result.char_prop); + return false; + } + + if (exp->callback_result.descriptor) { + btgatt_gatt_id_t *a; + btgatt_gatt_id_t *b; + a = step->callback_result.descriptor; + b = exp->callback_result.descriptor; + + if (!verify_gatt_ids(a, b)) { + tester_debug("Gatt desc doesn't match"); + return false; + } + } + + if (exp->callback_result.included) { + if (!verify_services(step->callback_result.included, + exp->callback_result.included)) { + tester_debug("Gatt include srvc doesn't match"); + return false; + } + } + + if (exp->callback_result.read_params) { + if (memcmp(step->callback_result.read_params, + exp->callback_result.read_params, + sizeof(btgatt_read_params_t))) { + tester_debug("Gatt read_param doesn't match"); + return false; + } + } + + if (exp->callback_result.write_params) { + if (memcmp(step->callback_result.write_params, + exp->callback_result.write_params, + sizeof(btgatt_write_params_t))) { + tester_debug("Gatt write_param doesn't match"); + return false; + } + + if (exp->callback_result.notification_registered != + step->callback_result.notification_registered) { + tester_debug("Gatt registered flag mismatch"); + return false; + } + + if (exp->callback_result.notify_params) { + if (memcmp(step->callback_result.notify_params, + exp->callback_result.notify_params, + sizeof(btgatt_notify_params_t))) { + tester_debug("Gatt notify_param doesn't match"); + return false; + } + } + } + + if (exp->callback_result.connected != + step->callback_result.connected) { + tester_debug("Gatt server conn status mismatch: %d vs %d", + step->callback_result.connected, + exp->callback_result.connected); + return false; + } + + if (exp->callback_result.attr_handle && + step->callback_result.attr_handle) + if (*exp->callback_result.attr_handle != + *step->callback_result.attr_handle) { + tester_debug("Gatt attribute handle mismatch: %d vs %d", + *step->callback_result.attr_handle, + *exp->callback_result.attr_handle); + return false; + } + + if (exp->callback_result.srvc_handle && + step->callback_result.srvc_handle) + if (*exp->callback_result.srvc_handle != + *step->callback_result.srvc_handle) { + tester_debug("Gatt service handle mismatch: %d vs %d", + *step->callback_result.srvc_handle, + *exp->callback_result.srvc_handle); + return false; + } + + if (exp->callback_result.inc_srvc_handle && + step->callback_result.inc_srvc_handle) + if (*exp->callback_result.inc_srvc_handle != + *step->callback_result.inc_srvc_handle) { + tester_debug("Gatt inc. srvc handle mismatch: %d vs %d", + *step->callback_result.inc_srvc_handle, + *exp->callback_result.inc_srvc_handle); + return false; + } + + if (exp->callback_result.uuid && step->callback_result.uuid) + if (memcmp(exp->callback_result.uuid, + step->callback_result.uuid, + sizeof(*exp->callback_result.uuid))) { + tester_debug("Uuid mismatch"); + return false; + } + + if (exp->callback_result.trans_id != step->callback_result.trans_id) { + tester_debug("Gatt trans id mismatch: %d vs %d", + exp->callback_result.trans_id, + step->callback_result.trans_id); + return false; + } + + if (exp->callback_result.offset != step->callback_result.offset) { + tester_debug("Gatt offset mismatch: %d vs %d", + exp->callback_result.offset, + step->callback_result.offset); + return false; + } + + if (exp->callback_result.is_long != step->callback_result.is_long) { + tester_debug("Gatt is long attr value flag mismatch: %d vs %d", + exp->callback_result.is_long, + step->callback_result.is_long); + return false; + } + + if (exp->callback_result.length > 0) { + if (exp->callback_result.length != + step->callback_result.length) { + tester_debug("Gatt attr length mismatch: %d vs %d", + exp->callback_result.length, + step->callback_result.length); + return false; + } + if (!exp->callback_result.value || + !step->callback_result.value) { + tester_debug("Gatt attr values are wrong set"); + return false; + } + if (!memcmp(exp->callback_result.value, + step->callback_result.value, + exp->callback_result.length)) { + tester_debug("Gatt attr value mismatch"); + return false; + } + } + + if (exp->callback_result.need_rsp != step->callback_result.need_rsp) { + tester_debug("Gatt need response value flag mismatch: %d vs %d", + exp->callback_result.need_rsp, + step->callback_result.need_rsp); + return false; + } + + if (exp->callback_result.is_prep != step->callback_result.is_prep) { + tester_debug("Gatt is prepared value flag mismatch: %d vs %d", + exp->callback_result.is_prep, + step->callback_result.is_prep); + return false; + } + + if (exp->callback_result.num_mas_instances != + step->callback_result.num_mas_instances) { + tester_debug("Mas instance count mismatch: %d vs %d", + exp->callback_result.num_mas_instances, + step->callback_result.num_mas_instances); + return false; + } + + if (exp->callback_result.mas_instances && + verify_mas_inst(exp->callback_result.mas_instances, + exp->callback_result.num_mas_instances, + step->callback_result.mas_instances, + step->callback_result.num_mas_instances)) { + tester_debug("Mas instances don't match"); + return false; + } + + if (exp->callback_result.error != step->callback_result.error) { + tester_debug("Err mismatch: %d vs %d", + exp->callback_result.error, + step->callback_result.error); + return false; + } + + if (exp->store_srvc_handle) + memcpy(exp->store_srvc_handle, + step->callback_result.srvc_handle, + sizeof(*exp->store_srvc_handle)); + + if (exp->store_char_handle) + memcpy(exp->store_char_handle, + step->callback_result.char_handle, + sizeof(*exp->store_char_handle)); + + return true; +} + +static void init_test_steps(struct test_data *data) +{ + const struct test_case *test_steps = data->test_data; + int i = 0; + + for (i = 0; i < test_steps->step_num; i++) + queue_push_tail(data->steps, (void *) &(test_steps->step[i])); + + tester_print("tester: Number of test steps=%d", + queue_length(data->steps)); +} + +/* + * Each test case step should be verified, if match with + * expected result tester should go to next test step. + */ +static void verify_step(struct step *step, queue_destroy_func_t cleanup_cb) +{ + struct test_data *data = tester_get_data(); + const struct test_case *test_steps = data->test_data; + struct step *next_step; + + tester_debug("tester: STEP[%d] check", + test_steps->step_num-queue_length(data->steps) + 1); + + if (step && !match_data(step)) { + if (cleanup_cb) + cleanup_cb(step); + + return; + } + + queue_pop_head(data->steps); + + if (cleanup_cb) + cleanup_cb(step); + + tester_debug("tester: STEP[%d] pass", + test_steps->step_num-queue_length(data->steps)); + + if (queue_isempty(data->steps)) { + tester_print("tester: All steps done, passing"); + tester_test_passed(); + + return; + } + + /* goto next step action if declared in step */ + next_step = queue_peek_head(data->steps); + + if (next_step->action) + next_step->action(); +} + +/* + * NOTICE: + * Its mandatory for callback to set proper step.callback value so that + * step verification could pass and move to next test step + */ + +static void free_properties(struct step *step) +{ + bt_property_t *properties = step->callback_result.properties; + int num_properties = step->callback_result.num_properties; + int i; + + for (i = 0; i < num_properties; i++) + g_free(properties[i].val); + + g_free(properties); +} + +static void free_mas_instances(struct step *step) +{ + btmce_mas_instance_t *mas_instances; + int num_instances; + int i; + + mas_instances = step->callback_result.mas_instances; + num_instances = step->callback_result.num_mas_instances; + + for (i = 0; i < num_instances; i++) + g_free(mas_instances[i].p_name); + + g_free(mas_instances); +} + +static void destroy_callback_step(void *data) +{ + struct step *step = data; + + if (step->callback_result.properties) + free_properties(step); + + if (step->callback_result.service) + free(step->callback_result.service); + + if (step->callback_result.characteristic) + free(step->callback_result.characteristic); + + if (step->callback_result.descriptor) + free(step->callback_result.descriptor); + + if (step->callback_result.included) + free(step->callback_result.included); + + if (step->callback_result.read_params) + free(step->callback_result.read_params); + + if (step->callback_result.write_params) + free(step->callback_result.write_params); + + if (step->callback_result.notify_params) + free(step->callback_result.notify_params); + + if (step->callback_result.srvc_handle) + free(step->callback_result.srvc_handle); + + if (step->callback_result.inc_srvc_handle) + free(step->callback_result.inc_srvc_handle); + + if (step->callback_result.uuid) + free(step->callback_result.uuid); + + if (step->callback_result.char_handle) + free(step->callback_result.char_handle); + + if (step->callback_result.desc_handle) + free(step->callback_result.desc_handle); + + if (step->callback_result.attr_handle) + free(step->callback_result.attr_handle); + + if (step->callback_result.value) + free(step->callback_result.value); + + if (step->callback_result.mas_instances) + free_mas_instances(step); + + g_free(step); + g_atomic_int_dec_and_test(&scheduled_cbacks_num); +} + +static gboolean verify_action(gpointer user_data) +{ + struct step *step = user_data; + + verify_step(step, g_free); + + return FALSE; +} + +static gboolean verify_callback(gpointer user_data) +{ + struct test_data *data = tester_get_data(); + struct step *step = user_data; + + /* Return if callback came when all steps are already verified */ + if (queue_isempty(data->steps)) { + destroy_callback_step(step); + return FALSE; + } + + /* + * TODO: This may call action from next step before callback data + * from previous step was freed. + */ + verify_step(step, destroy_callback_step); + + return FALSE; +} + +void schedule_callback_verification(struct step *step) +{ + g_atomic_int_inc(&scheduled_cbacks_num); + g_idle_add(verify_callback, step); +} + +void schedule_action_verification(struct step *step) +{ + g_idle_add_full(G_PRIORITY_HIGH_IDLE, verify_action, step, NULL); +} + +static void adapter_state_changed_cb(bt_state_t state) +{ + struct step *step = g_new0(struct step, 1); + + step->callback_result.state = state; + step->callback = CB_BT_ADAPTER_STATE_CHANGED; + + schedule_callback_verification(step); +} + +static bt_property_t *copy_properties(int num_properties, + bt_property_t *properties) +{ + int i; + bt_property_t *props = g_new0(bt_property_t, num_properties); + + for (i = 0; i < num_properties; i++) { + props[i].type = properties[i].type; + props[i].len = properties[i].len; + props[i].val = g_memdup(properties[i].val, properties[i].len); + } + + return props; +} + +static bt_property_t *repack_properties(int num_properties, + bt_property_t **properties) +{ + int i; + bt_property_t *props = g_new0(bt_property_t, num_properties); + + for (i = 0; i < num_properties; i++) { + props[i].type = properties[i]->type; + props[i].len = properties[i]->len; + props[i].val = g_memdup(properties[i]->val, properties[i]->len); + } + + return props; +} + +static bt_property_t *create_property(bt_property_type_t type, void *val, + int len) +{ + bt_property_t *prop = g_new0(bt_property_t, 1); + + prop->type = type; + prop->len = len; + prop->val = g_memdup(val, len); + + return prop; +} + +static void adapter_properties_cb(bt_status_t status, int num_properties, + bt_property_t *properties) +{ + struct step *step = g_new0(struct step, 1); + + step->callback_result.status = status; + step->callback_result.num_properties = num_properties; + step->callback_result.properties = copy_properties(num_properties, + properties); + step->callback = CB_BT_ADAPTER_PROPERTIES; + + schedule_callback_verification(step); +} + +static void discovery_state_changed_cb(bt_discovery_state_t state) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_BT_DISCOVERY_STATE_CHANGED; + step->callback_result.state = state; + + schedule_callback_verification(step); +} + +static void device_found_cb(int num_properties, bt_property_t *properties) +{ + struct step *step = g_new0(struct step, 1); + + step->callback_result.num_properties = num_properties; + step->callback_result.properties = copy_properties(num_properties, + properties); + + step->callback = CB_BT_DEVICE_FOUND; + + schedule_callback_verification(step); +} + +static void remote_device_properties_cb(bt_status_t status, + bt_bdaddr_t *bd_addr, int num_properties, + bt_property_t *properties) +{ + struct step *step = g_new0(struct step, 1); + + step->callback_result.num_properties = num_properties; + step->callback_result.properties = copy_properties(num_properties, + properties); + + step->callback = CB_BT_REMOTE_DEVICE_PROPERTIES; + + schedule_callback_verification(step); +} + +static void bond_state_changed_cb(bt_status_t status, + bt_bdaddr_t *remote_bd_addr, + bt_bond_state_t state) +{ + struct step *step = g_new0(struct step, 1); + + step->callback_result.status = status; + step->callback_result.state = state; + + /* Utilize property verification mechanism for bdaddr */ + step->callback_result.num_properties = 1; + step->callback_result.properties = + create_property(BT_PROPERTY_BDADDR, remote_bd_addr, + sizeof(*remote_bd_addr)); + + step->callback = CB_BT_BOND_STATE_CHANGED; + + schedule_callback_verification(step); +} + +static void pin_request_cb(bt_bdaddr_t *remote_bd_addr, + bt_bdname_t *bd_name, uint32_t cod) +{ + struct step *step = g_new0(struct step, 1); + bt_property_t *props[3]; + + step->callback = CB_BT_PIN_REQUEST; + + /* Utilize property verification mechanism for those */ + props[0] = create_property(BT_PROPERTY_BDADDR, remote_bd_addr, + sizeof(*remote_bd_addr)); + props[1] = create_property(BT_PROPERTY_BDNAME, bd_name->name, + strlen((char *) bd_name->name)); + props[2] = create_property(BT_PROPERTY_CLASS_OF_DEVICE, &cod, + sizeof(cod)); + + step->callback_result.num_properties = 3; + step->callback_result.properties = repack_properties(3, props); + + g_free(props[0]->val); + g_free(props[0]); + g_free(props[1]->val); + g_free(props[1]); + g_free(props[2]->val); + g_free(props[2]); + + schedule_callback_verification(step); +} + +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) +{ + struct step *step = g_new0(struct step, 1); + bt_property_t *props[3]; + + step->callback = CB_BT_SSP_REQUEST; + + /* Utilize property verification mechanism for those */ + props[0] = create_property(BT_PROPERTY_BDADDR, remote_bd_addr, + sizeof(*remote_bd_addr)); + props[1] = create_property(BT_PROPERTY_BDNAME, bd_name->name, + strlen((char *) bd_name->name)); + props[2] = create_property(BT_PROPERTY_CLASS_OF_DEVICE, &cod, + sizeof(cod)); + + step->callback_result.num_properties = 3; + step->callback_result.properties = repack_properties(3, props); + + g_free(props[0]->val); + g_free(props[0]); + g_free(props[1]->val); + g_free(props[1]); + g_free(props[2]->val); + g_free(props[2]); + + schedule_callback_verification(step); +} + +static void acl_state_changed_cb(bt_status_t status, + bt_bdaddr_t *remote_bd_addr, + bt_acl_state_t state) { + struct step *step = g_new0(struct step, 1); + + step->callback = CB_BT_ACL_STATE_CHANGED; + + step->callback_result.status = status; + step->callback_result.state = state; + + schedule_callback_verification(step); +} + +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 = NULL, + .dut_mode_recv_cb = NULL, + .le_test_mode_cb = NULL, + .energy_info_cb = NULL, +}; + +static void hidhost_connection_state_cb(bt_bdaddr_t *bd_addr, + bthh_connection_state_t state) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_HH_CONNECTION_STATE; + step->callback_result.state = state; + + schedule_callback_verification(step); +} + +static void hidhost_virtual_unplug_cb(bt_bdaddr_t *bd_addr, bthh_status_t status) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_HH_VIRTUAL_UNPLUG; + step->callback_result.status = status; + + schedule_callback_verification(step); +} + +static void hidhost_protocol_mode_cb(bt_bdaddr_t *bd_addr, + bthh_status_t status, + bthh_protocol_mode_t mode) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_HH_PROTOCOL_MODE; + step->callback_result.status = status; + step->callback_result.mode = mode; + + /* TODO: add bdaddr to verify? */ + + schedule_callback_verification(step); +} + +static void hidhost_hid_info_cb(bt_bdaddr_t *bd_addr, bthh_hid_info_t hid) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_HH_HID_INFO; + + schedule_callback_verification(step); +} + +static void hidhost_get_report_cb(bt_bdaddr_t *bd_addr, bthh_status_t status, + uint8_t *report, int size) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_HH_GET_REPORT; + + step->callback_result.status = status; + step->callback_result.report_size = size; + + schedule_callback_verification(step); +} + +static bthh_callbacks_t bthh_callbacks = { + .size = sizeof(bthh_callbacks), + .connection_state_cb = hidhost_connection_state_cb, + .hid_info_cb = hidhost_hid_info_cb, + .protocol_mode_cb = hidhost_protocol_mode_cb, + .idle_time_cb = NULL, + .get_report_cb = hidhost_get_report_cb, + .virtual_unplug_cb = hidhost_virtual_unplug_cb, + .handshake_cb = NULL, +}; + +static void gattc_register_client_cb(int status, int client_if, + bt_uuid_t *app_uuid) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_REGISTER_CLIENT; + + step->callback_result.status = status; + + schedule_callback_verification(step); +} + +static void gattc_scan_result_cb(bt_bdaddr_t *bda, int rssi, uint8_t *adv_data) +{ + struct step *step = g_new0(struct step, 1); + bt_property_t *props[2]; + + step->callback = CB_GATTC_SCAN_RESULT; + step->callback_result.adv_data = adv_data ? TRUE : FALSE; + + /* Utilize property verification mechanism for those */ + props[0] = create_property(BT_PROPERTY_BDADDR, bda, sizeof(*bda)); + props[1] = create_property(BT_PROPERTY_REMOTE_RSSI, &rssi, + sizeof(rssi)); + + step->callback_result.num_properties = 2; + step->callback_result.properties = repack_properties(2, props); + + g_free(props[0]->val); + g_free(props[0]); + g_free(props[1]->val); + g_free(props[1]); + + schedule_callback_verification(step); +} + +static void gattc_connect_cb(int conn_id, int status, int client_if, + bt_bdaddr_t *bda) +{ + struct step *step = g_new0(struct step, 1); + bt_property_t *props[1]; + + step->callback = CB_GATTC_OPEN; + step->callback_result.status = status; + step->callback_result.conn_id = conn_id; + step->callback_result.gatt_app_id = client_if; + + /* Utilize property verification mechanism for bdaddr */ + props[0] = create_property(BT_PROPERTY_BDADDR, bda, sizeof(*bda)); + + step->callback_result.num_properties = 1; + step->callback_result.properties = repack_properties(1, props); + + g_free(props[0]->val); + g_free(props[0]); + + schedule_callback_verification(step); +} + +static void gattc_disconnect_cb(int conn_id, int status, int client_if, + bt_bdaddr_t *bda) +{ + struct step *step = g_new0(struct step, 1); + bt_property_t *props[1]; + + step->callback = CB_GATTC_CLOSE; + step->callback_result.status = status; + step->callback_result.conn_id = conn_id; + step->callback_result.gatt_app_id = client_if; + + /* Utilize property verification mechanism for bdaddr */ + props[0] = create_property(BT_PROPERTY_BDADDR, bda, sizeof(*bda)); + + step->callback_result.num_properties = 1; + step->callback_result.properties = repack_properties(1, props); + + g_free(props[0]->val); + g_free(props[0]); + + schedule_callback_verification(step); +} + +static void gattc_listen_cb(int status, int server_if) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_LISTEN; + step->callback_result.status = status; + + schedule_callback_verification(step); +} + +static void gattc_search_result_cb(int conn_id, btgatt_srvc_id_t *srvc_id) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_SEARCH_RESULT; + step->callback_result.conn_id = conn_id; + step->callback_result.service = g_memdup(srvc_id, sizeof(*srvc_id)); + + schedule_callback_verification(step); +} + +static void gattc_search_complete_cb(int conn_id, int status) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_SEARCH_COMPLETE; + step->callback_result.conn_id = conn_id; + + schedule_callback_verification(step); +} + +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) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_GET_CHARACTERISTIC; + step->callback_result.status = status; + step->callback_result.conn_id = conn_id; + step->callback_result.service = g_memdup(srvc_id, sizeof(*srvc_id)); + step->callback_result.characteristic = g_memdup(char_id, + sizeof(*char_id)); + step->callback_result.char_prop = char_prop; + + schedule_callback_verification(step); +} + +static void gattc_get_descriptor_cb(int conn_id, int status, + btgatt_srvc_id_t *srvc_id, btgatt_gatt_id_t *char_id, + btgatt_gatt_id_t *descr_id) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_GET_DESCRIPTOR; + step->callback_result.status = status; + step->callback_result.conn_id = conn_id; + step->callback_result.service = g_memdup(srvc_id, sizeof(*srvc_id)); + step->callback_result.characteristic = g_memdup(char_id, + sizeof(*char_id)); + step->callback_result.descriptor = g_memdup(descr_id, + sizeof(*descr_id)); + + schedule_callback_verification(step); +} + +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) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_GET_INCLUDED_SERVICE; + step->callback_result.status = status; + step->callback_result.conn_id = conn_id; + step->callback_result.service = g_memdup(srvc_id, sizeof(*srvc_id)); + step->callback_result.included = g_memdup(incl_srvc_id, + sizeof(*srvc_id)); + + schedule_callback_verification(step); +} + +static void gattc_read_characteristic_cb(int conn_id, int status, + btgatt_read_params_t *p_data) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_READ_CHARACTERISTIC; + step->callback_result.status = status; + step->callback_result.conn_id = conn_id; + step->callback_result.read_params = g_memdup(p_data, sizeof(*p_data)); + + schedule_callback_verification(step); +} + +static void gattc_read_descriptor_cb(int conn_id, int status, + btgatt_read_params_t *p_data) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_READ_DESCRIPTOR; + step->callback_result.status = status; + step->callback_result.conn_id = conn_id; + step->callback_result.read_params = g_memdup(p_data, sizeof(*p_data)); + + schedule_callback_verification(step); +} + +static void gattc_write_characteristic_cb(int conn_id, int status, + btgatt_write_params_t *p_data) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_WRITE_CHARACTERISTIC; + step->callback_result.status = status; + step->callback_result.conn_id = conn_id; + step->callback_result.write_params = g_memdup(p_data, sizeof(*p_data)); + + schedule_callback_verification(step); +} + +static void gattc_write_descriptor_cb(int conn_id, int status, + btgatt_write_params_t *p_data) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_WRITE_DESCRIPTOR; + step->callback_result.status = status; + step->callback_result.conn_id = conn_id; + step->callback_result.write_params = g_memdup(p_data, sizeof(*p_data)); + + schedule_callback_verification(step); +} + +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) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_REGISTER_FOR_NOTIFICATION; + step->callback_result.status = status; + step->callback_result.conn_id = conn_id; + step->callback_result.service = g_memdup(srvc_id, sizeof(*srvc_id)); + step->callback_result.characteristic = g_memdup(char_id, + sizeof(*char_id)); + step->callback_result.notification_registered = registered; + + schedule_callback_verification(step); +} + +static void gattc_notif_cb(int conn_id, btgatt_notify_params_t *p_data) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTC_NOTIFY; + step->callback_result.conn_id = conn_id; + step->callback_result.notify_params = g_memdup(p_data, sizeof(*p_data)); + + schedule_callback_verification(step); +} + +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_notif_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 = NULL, + .read_remote_rssi_cb = NULL, + .listen_cb = gattc_listen_cb +}; + +static void gatts_register_server_cb(int status, int server_if, + bt_uuid_t *app_uuid) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTS_REGISTER_SERVER; + + step->callback_result.status = status; + + schedule_callback_verification(step); +} + +static void gatts_connection_cb(int conn_id, int server_if, int connected, + bt_bdaddr_t *bda) +{ + struct step *step = g_new0(struct step, 1); + bt_property_t *props[1]; + + step->callback = CB_GATTS_CONNECTION; + step->callback_result.conn_id = conn_id; + step->callback_result.gatt_app_id = server_if; + step->callback_result.connected = connected; + + /* Utilize property verification mechanism for bdaddr */ + props[0] = create_property(BT_PROPERTY_BDADDR, bda, sizeof(*bda)); + + step->callback_result.num_properties = 1; + step->callback_result.properties = repack_properties(1, props); + + g_free(props[0]->val); + g_free(props[0]); + + schedule_callback_verification(step); +} + +static void gatts_service_added_cb(int status, int server_if, + btgatt_srvc_id_t *srvc_id, + int srvc_handle) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTS_SERVICE_ADDED; + + step->callback_result.status = status; + step->callback_result.gatt_app_id = server_if; + step->callback_result.service = g_memdup(srvc_id, sizeof(*srvc_id)); + step->callback_result.srvc_handle = g_memdup(&srvc_handle, + sizeof(srvc_handle)); + + schedule_callback_verification(step); +} + +static void gatts_included_service_added_cb(int status, int server_if, + int srvc_handle, + int inc_srvc_handle) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTS_INCLUDED_SERVICE_ADDED; + + step->callback_result.status = status; + step->callback_result.gatt_app_id = server_if; + step->callback_result.srvc_handle = g_memdup(&srvc_handle, + sizeof(srvc_handle)); + step->callback_result.inc_srvc_handle = g_memdup(&inc_srvc_handle, + sizeof(inc_srvc_handle)); + + schedule_callback_verification(step); +} + +static void gatts_characteristic_added_cb(int status, int server_if, + bt_uuid_t *uuid, + int srvc_handle, + int char_handle) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTS_CHARACTERISTIC_ADDED; + + step->callback_result.status = status; + step->callback_result.gatt_app_id = server_if; + step->callback_result.srvc_handle = g_memdup(&srvc_handle, + sizeof(srvc_handle)); + step->callback_result.uuid = g_memdup(uuid, sizeof(*uuid)); + step->callback_result.char_handle = g_memdup(&char_handle, + sizeof(char_handle)); + + schedule_callback_verification(step); +} + +static void gatts_descriptor_added_cb(int status, int server_if, + bt_uuid_t *uuid, + int srvc_handle, + int desc_handle) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTS_DESCRIPTOR_ADDED; + + step->callback_result.status = status; + step->callback_result.gatt_app_id = server_if; + step->callback_result.srvc_handle = g_memdup(&srvc_handle, + sizeof(srvc_handle)); + step->callback_result.uuid = g_memdup(uuid, sizeof(*uuid)); + step->callback_result.desc_handle = g_memdup(&desc_handle, + sizeof(desc_handle)); + + schedule_callback_verification(step); +} + +static void gatts_service_started_cb(int status, int server_if, int srvc_handle) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTS_SERVICE_STARTED; + + step->callback_result.status = status; + step->callback_result.gatt_app_id = server_if; + step->callback_result.srvc_handle = g_memdup(&srvc_handle, + sizeof(srvc_handle)); + + schedule_callback_verification(step); +} + +static void gatts_service_stopped_cb(int status, int server_if, int srvc_handle) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTS_SERVICE_STOPPED; + + step->callback_result.status = status; + step->callback_result.gatt_app_id = server_if; + step->callback_result.srvc_handle = g_memdup(&srvc_handle, + sizeof(srvc_handle)); + + schedule_callback_verification(step); +} + +static void gatts_service_deleted_cb(int status, int server_if, int srvc_handle) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTS_SERVICE_DELETED; + + step->callback_result.status = status; + step->callback_result.gatt_app_id = server_if; + step->callback_result.srvc_handle = g_memdup(&srvc_handle, + sizeof(srvc_handle)); + + schedule_callback_verification(step); +} + +static void gatts_request_read_cb(int conn_id, int trans_id, bt_bdaddr_t *bda, + int attr_handle, int offset, + bool is_long) +{ + struct step *step = g_new0(struct step, 1); + bt_property_t *props[1]; + + step->callback = CB_GATTS_REQUEST_READ; + + step->callback_result.conn_id = conn_id; + step->callback_result.trans_id = trans_id; + step->callback_result.attr_handle = g_memdup(&attr_handle, + sizeof(attr_handle)); + step->callback_result.offset = offset; + step->callback_result.is_long = is_long; + + /* Utilize property verification mechanism for bdaddr */ + props[0] = create_property(BT_PROPERTY_BDADDR, bda, sizeof(*bda)); + + step->callback_result.num_properties = 1; + step->callback_result.properties = repack_properties(1, props); + + g_free(props[0]->val); + g_free(props[0]); + + schedule_callback_verification(step); +} + +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) +{ + struct step *step = g_new0(struct step, 1); + bt_property_t *props[1]; + + step->callback = CB_GATTS_REQUEST_WRITE; + + step->callback_result.conn_id = conn_id; + step->callback_result.trans_id = trans_id; + step->callback_result.attr_handle = g_memdup(&attr_handle, + sizeof(attr_handle)); + step->callback_result.offset = offset; + step->callback_result.length = length; + step->callback_result.need_rsp = need_rsp; + step->callback_result.is_prep = is_prep; + step->callback_result.value = g_memdup(&value, length); + + /* Utilize property verification mechanism for bdaddr */ + props[0] = create_property(BT_PROPERTY_BDADDR, bda, sizeof(*bda)); + + step->callback_result.num_properties = 1; + step->callback_result.properties = repack_properties(1, props); + + g_free(props[0]->val); + g_free(props[0]); + + schedule_callback_verification(step); +} + +static void gatts_indication_send_cb(int conn_id, int status) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_GATTS_INDICATION_SEND; + + step->callback_result.conn_id = conn_id; + step->callback_result.status = status; + + schedule_callback_verification(step); +} + +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 = NULL, + .response_confirmation_cb = NULL, + .indication_sent_cb = gatts_indication_send_cb, + .congestion_cb = NULL, +}; + +static const btgatt_callbacks_t btgatt_callbacks = { + .size = sizeof(btgatt_callbacks), + .client = &btgatt_client_callbacks, + .server = &btgatt_server_callbacks +}; + +static void pan_control_state_cb(btpan_control_state_t state, int local_role, + bt_status_t error, const char *ifname) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_PAN_CONTROL_STATE; + step->callback_result.state = error; + step->callback_result.ctrl_state = state; + step->callback_result.local_role = local_role; + + schedule_callback_verification(step); +} + +static void pan_connection_state_cb(btpan_connection_state_t state, + bt_status_t error, + const bt_bdaddr_t *bd_addr, + int local_role, int remote_role) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_PAN_CONNECTION_STATE; + step->callback_result.state = error; + step->callback_result.conn_state = state; + step->callback_result.local_role = local_role; + step->callback_result.remote_role = remote_role; + + schedule_callback_verification(step); +} + +static btpan_callbacks_t btpan_callbacks = { + .size = sizeof(btpan_callbacks), + .control_state_cb = pan_control_state_cb, + .connection_state_cb = pan_connection_state_cb, +}; + +static void hdp_app_reg_state_cb(int app_id, bthl_app_reg_state_t state) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_HDP_APP_REG_STATE; + step->callback_result.app_id = app_id; + step->callback_result.app_state = state; + + schedule_callback_verification(step); +} + +static void hdp_channel_state_cb(int app_id, bt_bdaddr_t *bd_addr, + int mdep_cfg_index, int channel_id, + bthl_channel_state_t state, int fd) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_HDP_CHANNEL_STATE; + step->callback_result.app_id = app_id; + step->callback_result.channel_id = channel_id; + step->callback_result.mdep_cfg_index = mdep_cfg_index; + step->callback_result.channel_state = state; + + schedule_callback_verification(step); +} + +static bthl_callbacks_t bthl_callbacks = { + .size = sizeof(bthl_callbacks), + .app_reg_state_cb = hdp_app_reg_state_cb, + .channel_state_cb = hdp_channel_state_cb, +}; + +static void a2dp_connection_state_cb(btav_connection_state_t state, + bt_bdaddr_t *bd_addr) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_A2DP_CONN_STATE; + step->callback_result.av_conn_state = state; + + schedule_callback_verification(step); +} + +static void a2dp_audio_state_cb(btav_audio_state_t state, bt_bdaddr_t *bd_addr) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_A2DP_AUDIO_STATE; + step->callback_result.av_audio_state = state; + + schedule_callback_verification(step); +} + +static btav_callbacks_t bta2dp_callbacks = { + .size = sizeof(bta2dp_callbacks), + .connection_state_cb = a2dp_connection_state_cb, + .audio_state_cb = a2dp_audio_state_cb, +}; + +static void avrcp_get_play_status_cb(void) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_AVRCP_PLAY_STATUS_REQ; + schedule_callback_verification(step); +} + +static void avrcp_register_notification_cb(btrc_event_id_t event_id, + uint32_t param) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_AVRCP_REG_NOTIF_REQ; + schedule_callback_verification(step); +} + +static void avrcp_get_element_attr_cb(uint8_t num_attr, + btrc_media_attr_t *p_attrs) +{ + struct step *step = g_new0(struct step, 1); + + step->callback = CB_AVRCP_GET_ATTR_REQ; + schedule_callback_verification(step); +} + +static btrc_callbacks_t btavrcp_callbacks = { + .size = sizeof(btavrcp_callbacks), + .get_play_status_cb = avrcp_get_play_status_cb, + .register_notification_cb = avrcp_register_notification_cb, + .get_element_attr_cb = avrcp_get_element_attr_cb, +}; + +static btmce_mas_instance_t *copy_mas_instances(int num_instances, + btmce_mas_instance_t *instances) +{ + int i; + btmce_mas_instance_t *inst; + + inst = g_new0(btmce_mas_instance_t, num_instances); + + for (i = 0; i < num_instances; i++) { + inst[i].id = instances[i].id; + inst[i].scn = instances[i].scn; + inst[i].msg_types = instances[i].msg_types; + inst[i].p_name = g_memdup(instances[i].p_name, + strlen(instances[i].p_name)); + } + + return inst; +} + +static void map_client_get_remote_mas_instances_cb(bt_status_t status, + bt_bdaddr_t *bd_addr, + int num_instances, + btmce_mas_instance_t + *instances) +{ + struct step *step = g_new0(struct step, 1); + + step->callback_result.status = status; + step->callback_result.num_mas_instances = num_instances; + step->callback_result.mas_instances = copy_mas_instances(num_instances, + instances); + + step->callback = CB_MAP_CLIENT_REMOTE_MAS_INSTANCES; + + schedule_callback_verification(step); +} + +static btmce_callbacks_t btmap_client_callbacks = { + .size = sizeof(btmap_client_callbacks), + .remote_mas_instances_cb = map_client_get_remote_mas_instances_cb, +}; + +static bool setup_base(struct test_data *data) +{ + const hw_module_t *module; + hw_device_t *device; + int signal_fd[2]; + char buf[1024]; + pid_t pid; + int len; + int err; + + if (pipe(signal_fd)) + return false; + + pid = fork(); + + if (pid < 0) { + close(signal_fd[0]); + close(signal_fd[1]); + return false; + } + + if (pid == 0) { + if (!tester_use_debug()) + fclose(stderr); + + close(signal_fd[0]); + emulator(signal_fd[1], data->mgmt_index); + exit(0); + } + + close(signal_fd[1]); + data->bluetoothd_pid = pid; + + len = read(signal_fd[0], buf, sizeof(buf)); + if (len <= 0 || strcmp(buf, EMULATOR_SIGNAL)) { + close(signal_fd[0]); + return false; + } + + close(signal_fd[0]); + + err = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, + AUDIO_HARDWARE_MODULE_ID_A2DP, &module); + if (err) + return false; + + err = audio_hw_device_open(module, &data->audio); + if (err) + return false; + + err = hw_get_module(BT_HARDWARE_MODULE_ID, &module); + if (err) + return false; + + err = module->methods->open(module, BT_HARDWARE_MODULE_ID, &device); + if (err) + return false; + + data->device = device; + + data->if_bluetooth = ((bluetooth_device_t *) + device)->get_bluetooth_interface(); + if (!data->if_bluetooth) + return false; + + if (!(data->steps = queue_new())) + return false; + + data->pdus = queue_new(); + if (!data->pdus) { + queue_destroy(data->steps, NULL); + return false; + } + + return true; +} + +static void setup(const void *test_data) +{ + struct test_data *data = tester_get_data(); + bt_status_t status; + + if (!setup_base(data)) { + tester_setup_failed(); + return; + } + + status = data->if_bluetooth->init(&bt_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_bluetooth = NULL; + tester_setup_failed(); + return; + } + + tester_setup_complete(); +} + +static void setup_socket(const void *test_data) +{ + struct test_data *data = tester_get_data(); + bt_status_t status; + const void *sock; + + if (!setup_base(data)) { + tester_setup_failed(); + return; + } + + status = data->if_bluetooth->init(&bt_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_bluetooth = NULL; + tester_setup_failed(); + return; + } + + sock = data->if_bluetooth->get_profile_interface(BT_PROFILE_SOCKETS_ID); + if (!sock) { + tester_setup_failed(); + return; + } + + data->if_sock = sock; + + tester_setup_complete(); +} + +static void setup_hidhost(const void *test_data) +{ + struct test_data *data = tester_get_data(); + bt_status_t status; + const void *hid; + + if (!setup_base(data)) { + tester_setup_failed(); + return; + } + + status = data->if_bluetooth->init(&bt_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_bluetooth = NULL; + tester_setup_failed(); + return; + } + + hid = data->if_bluetooth->get_profile_interface(BT_PROFILE_HIDHOST_ID); + if (!hid) { + tester_setup_failed(); + return; + } + + data->if_hid = hid; + + status = data->if_hid->init(&bthh_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_hid = NULL; + tester_setup_failed(); + return; + } + + tester_setup_complete(); +} + +static void setup_pan(const void *test_data) +{ + struct test_data *data = tester_get_data(); + bt_status_t status; + const void *pan; + + if (!setup_base(data)) { + tester_setup_failed(); + return; + } + + status = data->if_bluetooth->init(&bt_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_bluetooth = NULL; + tester_setup_failed(); + return; + } + + pan = data->if_bluetooth->get_profile_interface(BT_PROFILE_PAN_ID); + if (!pan) { + tester_setup_failed(); + return; + } + + data->if_pan = pan; + + status = data->if_pan->init(&btpan_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_pan = NULL; + tester_setup_failed(); + return; + } + + tester_setup_complete(); +} + +static void setup_hdp(const void *test_data) +{ + struct test_data *data = tester_get_data(); + bt_status_t status; + const void *hdp; + + if (!setup_base(data)) { + tester_setup_failed(); + return; + } + + status = data->if_bluetooth->init(&bt_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_bluetooth = NULL; + tester_setup_failed(); + return; + } + + hdp = data->if_bluetooth->get_profile_interface(BT_PROFILE_HEALTH_ID); + if (!hdp) { + tester_setup_failed(); + return; + } + + data->if_hdp = hdp; + + status = data->if_hdp->init(&bthl_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_hdp = NULL; + tester_setup_failed(); + return; + } + + tester_setup_complete(); +} + +static void setup_a2dp(const void *test_data) +{ + struct test_data *data = tester_get_data(); + const bt_interface_t *if_bt; + bt_status_t status; + const void *a2dp; + + if (!setup_base(data)) { + tester_setup_failed(); + return; + } + + if_bt = data->if_bluetooth; + + status = if_bt->init(&bt_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_bluetooth = NULL; + tester_setup_failed(); + return; + } + + a2dp = if_bt->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID); + if (!a2dp) { + tester_setup_failed(); + return; + } + + data->if_a2dp = a2dp; + + status = data->if_a2dp->init(&bta2dp_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_a2dp = NULL; + tester_setup_failed(); + return; + } + + tester_setup_complete(); +} + +static void setup_avrcp(const void *test_data) +{ + struct test_data *data = tester_get_data(); + const bt_interface_t *if_bt; + bt_status_t status; + const void *a2dp, *avrcp; + + if (!setup_base(data)) { + tester_setup_failed(); + return; + } + + if_bt = data->if_bluetooth; + + status = if_bt->init(&bt_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_bluetooth = NULL; + tester_setup_failed(); + return; + } + + a2dp = if_bt->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID); + if (!a2dp) { + tester_setup_failed(); + return; + } + + data->if_a2dp = a2dp; + + status = data->if_a2dp->init(&bta2dp_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_a2dp = NULL; + tester_setup_failed(); + return; + } + + avrcp = if_bt->get_profile_interface(BT_PROFILE_AV_RC_ID); + if (!avrcp) { + tester_setup_failed(); + return; + } + + data->if_avrcp = avrcp; + + status = data->if_avrcp->init(&btavrcp_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_avrcp = NULL; + tester_setup_failed(); + return; + } + + tester_setup_complete(); +} + +static void setup_gatt(const void *test_data) +{ + struct test_data *data = tester_get_data(); + bt_status_t status; + const void *gatt; + + if (!setup_base(data)) { + tester_setup_failed(); + return; + } + + status = data->if_bluetooth->init(&bt_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_bluetooth = NULL; + tester_setup_failed(); + return; + } + + gatt = data->if_bluetooth->get_profile_interface(BT_PROFILE_GATT_ID); + if (!gatt) { + tester_setup_failed(); + return; + } + + data->if_gatt = gatt; + + status = data->if_gatt->init(&btgatt_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_gatt = NULL; + + tester_setup_failed(); + return; + } + + tester_setup_complete(); +} + +static void setup_map_client(const void *test_data) +{ + struct test_data *data = tester_get_data(); + bt_status_t status; + const void *map_client; + + if (!setup_base(data)) { + tester_setup_failed(); + return; + } + + status = data->if_bluetooth->init(&bt_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_bluetooth = NULL; + tester_setup_failed(); + return; + } + + map_client = data->if_bluetooth->get_profile_interface( + BT_PROFILE_MAP_CLIENT_ID); + if (!map_client) { + tester_setup_failed(); + return; + } + + data->if_map_client = map_client; + + status = data->if_map_client->init(&btmap_client_callbacks); + if (status != BT_STATUS_SUCCESS) { + data->if_map_client = NULL; + + tester_setup_failed(); + return; + } + + tester_setup_complete(); +} + +static void teardown(const void *test_data) +{ + struct test_data *data = tester_get_data(); + + queue_destroy(data->steps, NULL); + data->steps = NULL; + + queue_destroy(data->pdus, NULL); + data->pdus = NULL; + + /* no cleanup for map_client */ + data->if_map_client = NULL; + + if (data->if_gatt) { + data->if_gatt->cleanup(); + data->if_gatt = NULL; + } + + if (data->if_hid) { + data->if_hid->cleanup(); + data->if_hid = NULL; + } + + if (data->if_pan) { + data->if_pan->cleanup(); + data->if_pan = NULL; + } + + if (data->if_hdp) { + data->if_hdp->cleanup(); + data->if_hdp = NULL; + } + + if (data->if_stream) { + data->audio->close_output_stream(data->audio, data->if_stream); + data->if_stream = NULL; + } + + if (data->if_a2dp) { + data->if_a2dp->cleanup(); + data->if_a2dp = NULL; + } + + if (data->if_avrcp) { + data->if_avrcp->cleanup(); + data->if_avrcp = NULL; + } + + if (data->if_bluetooth) { + data->if_bluetooth->cleanup(); + data->if_bluetooth = NULL; + } + + data->device->close(data->device); + audio_hw_device_close(data->audio); + + /* + * Ssp_request_cb pointer can be set do default_ssp_req_cb. + * Set it back to ssp_request_cb + */ + bt_callbacks.ssp_request_cb = ssp_request_cb; + + if (!data->bluetoothd_pid) + tester_teardown_complete(); +} + +static void emu_connectable_complete(uint16_t opcode, uint8_t status, + const void *param, uint8_t len, + void *user_data) +{ + struct step *step; + struct test_data *data = user_data; + + switch (opcode) { + case BT_HCI_CMD_WRITE_SCAN_ENABLE: + break; + case BT_HCI_CMD_LE_SET_ADV_ENABLE: + /* + * For BREDRLE emulator we want to verify step after scan + * enable and not after le_set_adv_enable + */ + if (data->hciemu_type == HCIEMU_TYPE_BREDRLE) + return; + + break; + default: + return; + } + + step = g_new0(struct step, 1); + + if (status) { + tester_warn("Emulated remote setup failed."); + step->action_status = BT_STATUS_FAIL; + } else { + tester_debug("Emulated remote setup done."); + step->action_status = BT_STATUS_SUCCESS; + } + + schedule_action_verification(step); +} + +void emu_setup_powered_remote_action(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost; + + bthost = hciemu_client_get_host(data->hciemu); + bthost_set_cmd_complete_cb(bthost, emu_connectable_complete, data); + + if ((data->hciemu_type == HCIEMU_TYPE_LE) || + (data->hciemu_type == HCIEMU_TYPE_BREDRLE)) { + uint8_t adv[3]; + + adv[0] = 0x02; /* Field length */ + adv[1] = 0x01; /* Flags */ + adv[2] = 0x02; /* Flags value */ + + bthost_set_adv_data(bthost, adv, sizeof(adv)); + bthost_set_adv_enable(bthost, 0x01); + } + + if (data->hciemu_type != HCIEMU_TYPE_LE) + bthost_write_scan_enable(bthost, 0x03); +} + +void emu_set_pin_code_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct bthost *bthost; + struct step *step = g_new0(struct step, 1); + + bthost = hciemu_client_get_host(data->hciemu); + + bthost_set_pin_code(bthost, action_data->pin->pin, + action_data->pin_len); + + step->action_status = BT_STATUS_SUCCESS; + + tester_print("Setting emu pin done."); + + schedule_action_verification(step); +} + +void emu_set_ssp_mode_action(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost; + struct step *step = g_new0(struct step, 1); + + bthost = hciemu_client_get_host(data->hciemu); + + bthost_write_ssp_mode(bthost, 0x01); + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +void emu_set_connect_cb_action(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + struct step *current_data_step = queue_peek_head(data->steps); + void *cb = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + bthost_set_connect_cb(bthost, cb, data); + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +void emu_remote_connect_hci_action(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + const uint8_t *master_addr; + + master_addr = hciemu_get_master_bdaddr(data->hciemu); + + tester_print("Trying to connect hci"); + + if (action_data) + bthost_hci_connect(bthost, master_addr, + action_data->bearer_type); + else + bthost_hci_connect(bthost, master_addr, BDADDR_BREDR); + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +void emu_remote_disconnect_hci_action(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + struct step *current_data_step = queue_peek_head(data->steps); + uint16_t *handle = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + if (handle) { + bthost_hci_disconnect(bthost, *handle, 0x13); + step->action_status = BT_STATUS_SUCCESS; + } else { + step->action_status = BT_STATUS_FAIL; + } + + schedule_action_verification(step); +} + +void emu_set_io_cap(void) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost; + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + bthost = hciemu_client_get_host(data->hciemu); + + if (action_data) + bthost_set_io_capability(bthost, action_data->io_cap); + else + bthost_set_io_capability(bthost, 0x01); + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +void emu_add_l2cap_server_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct emu_set_l2cap_data *l2cap_data = current_data_step->set_data; + struct bthost *bthost; + struct step *step = g_new0(struct step, 1); + + if (!l2cap_data) { + tester_warn("Invalid l2cap_data params"); + step->action_status = BT_STATUS_FAIL; + goto done; + } + + bthost = hciemu_client_get_host(data->hciemu); + + bthost_add_l2cap_server(bthost, l2cap_data->psm, l2cap_data->func, + l2cap_data->user_data); + + step->action_status = BT_STATUS_SUCCESS; + +done: + schedule_action_verification(step); +} + +static void print_data(const char *str, void *user_data) +{ + tester_debug("tester: %s", str); +} + +static void emu_generic_cid_hook_cb(const void *data, uint16_t len, + void *user_data) +{ + struct test_data *t_data = tester_get_data(); + struct emu_l2cap_cid_data *cid_data = user_data; + const struct pdu_set *pdus = cid_data->pdu; + struct bthost *bthost = hciemu_client_get_host(t_data->hciemu); + int i; + + for (i = 0; pdus[i].rsp.iov_base; i++) { + if (pdus[i].req.iov_base) { + if (pdus[i].req.iov_len != len) + continue; + + if (memcmp(pdus[i].req.iov_base, data, len)) + continue; + } + + if (pdus[i].rsp.iov_base) { + util_hexdump('>', pdus[i].rsp.iov_base, + pdus[i].rsp.iov_len, print_data, NULL); + + /* if its sdp pdu use transaction ID from request */ + if (cid_data->is_sdp) { + struct iovec rsp[3]; + + rsp[0].iov_base = pdus[i].rsp.iov_base; + rsp[0].iov_len = 1; + + rsp[1].iov_base = ((uint8_t *) data) + 1; + rsp[1].iov_len = 2; + + rsp[2].iov_base = pdus[i].rsp.iov_base + 3; + rsp[2].iov_len = pdus[i].rsp.iov_len - 3; + + bthost_send_cid_v(bthost, cid_data->handle, + cid_data->cid, rsp, 3); + } else { + bthost_send_cid_v(bthost, cid_data->handle, + cid_data->cid, &pdus[i].rsp, 1); + } + + } + } +} + +void tester_handle_l2cap_data_exchange(struct emu_l2cap_cid_data *cid_data) +{ + struct test_data *t_data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(t_data->hciemu); + + bthost_add_cid_hook(bthost, cid_data->handle, cid_data->cid, + emu_generic_cid_hook_cb, cid_data); +} + +void tester_generic_connect_cb(uint16_t handle, uint16_t cid, void *user_data) +{ + struct emu_l2cap_cid_data *cid_data = user_data; + + cid_data->handle = handle; + cid_data->cid = cid; + + tester_handle_l2cap_data_exchange(cid_data); +} + +static void rfcomm_connect_cb(uint16_t handle, uint16_t cid, void *user_data, + bool status) +{ + struct step *step = g_new0(struct step, 1); + + tester_print("Connect handle %d, cid %d cb status: %d", handle, cid, + status); + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +void emu_add_rfcomm_server_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *rfcomm_data = current_data_step->set_data; + struct bthost *bthost; + struct step *step; + + if (!rfcomm_data) { + tester_warn("Invalid l2cap_data params"); + return; + } + + step = g_new0(struct step, 1); + + bthost = hciemu_client_get_host(data->hciemu); + + bthost_add_rfcomm_server(bthost, rfcomm_data->channel, + rfcomm_connect_cb, data); + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +void dummy_action(void) +{ + struct step *step = g_new0(struct step, 1); + + step->action = dummy_action; + + schedule_action_verification(step); +} + +void bluetooth_enable_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_bluetooth->enable(); + + schedule_action_verification(step); +} + +void bluetooth_disable_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_bluetooth->disable(); + + schedule_action_verification(step); +} + +void bt_set_property_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step; + struct step *current_data_step = queue_peek_head(data->steps); + bt_property_t *prop; + + if (!current_data_step->set_data) { + tester_debug("BT property not set for step"); + tester_test_failed(); + return; + } + + step = g_new0(struct step, 1); + + prop = (bt_property_t *)current_data_step->set_data; + + step->action_status = data->if_bluetooth->set_adapter_property(prop); + + schedule_action_verification(step); +} + +void bt_get_property_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step; + struct step *current_data_step = queue_peek_head(data->steps); + bt_property_t *prop; + + if (!current_data_step->set_data) { + tester_debug("BT property to get not defined"); + tester_test_failed(); + return; + } + + step = g_new0(struct step, 1); + + prop = (bt_property_t *)current_data_step->set_data; + + step->action_status = data->if_bluetooth->get_adapter_property( + prop->type); + + schedule_action_verification(step); +} + +void bt_start_discovery_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_bluetooth->start_discovery(); + + schedule_action_verification(step); +} + +void bt_cancel_discovery_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_bluetooth->cancel_discovery(); + + schedule_action_verification(step); +} + +void bt_get_device_props_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct step *step; + + if (!current_data_step->set_data) { + tester_debug("bdaddr not defined"); + tester_test_failed(); + return; + } + + step = g_new0(struct step, 1); + + step->action_status = + data->if_bluetooth->get_remote_device_properties( + current_data_step->set_data); + + schedule_action_verification(step); +} + +void bt_get_device_prop_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step; + + if (!action_data) { + tester_warn("No arguments for 'get remote device prop' req."); + tester_test_failed(); + return; + } + + step = g_new0(struct step, 1); + + step->action_status = data->if_bluetooth->get_remote_device_property( + action_data->addr, + action_data->prop_type); + + schedule_action_verification(step); +} + +void bt_set_device_prop_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step; + + if (!action_data) { + tester_warn("No arguments for 'set remote device prop' req."); + tester_test_failed(); + return; + } + + step = g_new0(struct step, 1); + + step->action_status = data->if_bluetooth->set_remote_device_property( + action_data->addr, + action_data->prop); + + schedule_action_verification(step); +} + +void bt_create_bond_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step; + + if (!action_data || !action_data->addr) { + tester_warn("Bad arguments for 'create bond' req."); + tester_test_failed(); + return; + } + + step = g_new0(struct step, 1); + + step->action_status = + data->if_bluetooth->create_bond(action_data->addr, + action_data->transport_type ? + action_data->transport_type : + BT_TRANSPORT_UNKNOWN); + + schedule_action_verification(step); +} + +void bt_pin_reply_accept_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step; + + if (!action_data || !action_data->addr || !action_data->pin) { + tester_warn("Bad arguments for 'pin reply' req."); + tester_test_failed(); + return; + } + + step = g_new0(struct step, 1); + + step->action_status = data->if_bluetooth->pin_reply(action_data->addr, + TRUE, + action_data->pin_len, + action_data->pin); + + schedule_action_verification(step); +} + +void bt_ssp_reply_accept_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_bluetooth->ssp_reply(action_data->addr, + action_data->ssp_variant, + action_data->accept, 0); + + schedule_action_verification(step); +} + +void bt_cancel_bond_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + bt_bdaddr_t *addr = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_bluetooth->cancel_bond(addr); + + schedule_action_verification(step); +} + +void bt_remove_bond_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + bt_bdaddr_t *addr = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_bluetooth->remove_bond(addr); + + schedule_action_verification(step); +} + +static void default_ssp_req_cb(bt_bdaddr_t *remote_bd_addr, bt_bdname_t *name, + uint32_t cod, bt_ssp_variant_t pairing_variant, + uint32_t pass_key) +{ + struct test_data *t_data = tester_get_data(); + + t_data->if_bluetooth->ssp_reply(remote_bd_addr, pairing_variant, true, + pass_key); +} + +void set_default_ssp_request_handler(void) +{ + struct step *step = g_new0(struct step, 1); + + bt_callbacks.ssp_request_cb = default_ssp_req_cb; + + step->action_status = BT_STATUS_SUCCESS; + + schedule_action_verification(step); +} + +static void generic_test_function(const void *test_data) +{ + struct test_data *data = tester_get_data(); + struct step *first_step; + + init_test_steps(data); + + /* first step action */ + first_step = queue_peek_head(data->steps); + if (!first_step->action) { + tester_print("tester: No initial action declared"); + tester_test_failed(); + return; + } + first_step->action(); +} + +#define test(data, test_setup, test, test_teardown) \ + do { \ + struct test_data *user; \ + user = g_malloc0(sizeof(struct test_data)); \ + if (!user) \ + break; \ + user->hciemu_type = data->emu_type; \ + user->test_data = data; \ + tester_add_full(data->title, data, test_pre_setup, \ + test_setup, test, test_teardown, \ + test_post_teardown, 3, user, g_free); \ + } while (0) + +static void tester_testcases_cleanup(void) +{ + remove_bluetooth_tests(); + remove_socket_tests(); + remove_hidhost_tests(); + remove_gatt_tests(); + remove_a2dp_tests(); + remove_avrcp_tests(); + remove_hdp_tests(); + remove_pan_tests(); +} + +static void add_bluetooth_tests(void *data, void *user_data) +{ + struct test_case *tc = data; + + test(tc, setup, generic_test_function, teardown); +} + +static void add_socket_tests(void *data, void *user_data) +{ + struct test_case *tc = data; + + test(tc, setup_socket, generic_test_function, teardown); +} + +static void add_hidhost_tests(void *data, void *user_data) +{ + struct test_case *tc = data; + + test(tc, setup_hidhost, generic_test_function, teardown); +} + +static void add_pan_tests(void *data, void *user_data) +{ + struct test_case *tc = data; + + test(tc, setup_pan, generic_test_function, teardown); +} + +static void add_hdp_tests(void *data, void *user_data) +{ + struct test_case *tc = data; + + test(tc, setup_hdp, generic_test_function, teardown); +} + +static void add_a2dp_tests(void *data, void *user_data) +{ + struct test_case *tc = data; + + test(tc, setup_a2dp, generic_test_function, teardown); +} + +static void add_avrcp_tests(void *data, void *user_data) +{ + struct test_case *tc = data; + + test(tc, setup_avrcp, generic_test_function, teardown); +} + +static void add_gatt_tests(void *data, void *user_data) +{ + struct test_case *tc = data; + + test(tc, setup_gatt, generic_test_function, teardown); +} + +static void add_map_client_tests(void *data, void *user_data) +{ + struct test_case *tc = data; + + test(tc, setup_map_client, generic_test_function, teardown); +} + +int main(int argc, char *argv[]) +{ + snprintf(exec_dir, sizeof(exec_dir), "%s", dirname(argv[0])); + + tester_init(&argc, &argv); + + queue_foreach(get_bluetooth_tests(), add_bluetooth_tests, NULL); + queue_foreach(get_socket_tests(), add_socket_tests, NULL); + queue_foreach(get_hidhost_tests(), add_hidhost_tests, NULL); + queue_foreach(get_pan_tests(), add_pan_tests, NULL); + queue_foreach(get_hdp_tests(), add_hdp_tests, NULL); + queue_foreach(get_a2dp_tests(), add_a2dp_tests, NULL); + queue_foreach(get_avrcp_tests(), add_avrcp_tests, NULL); + queue_foreach(get_gatt_tests(), add_gatt_tests, NULL); + queue_foreach(get_map_client_tests(), add_map_client_tests, NULL); + + if (tester_run()) + return 1; + + tester_testcases_cleanup(); + + return 0; +} diff --git a/android/tester-main.h b/android/tester-main.h new file mode 100644 index 0000000..8a7384c --- /dev/null +++ b/android/tester-main.h @@ -0,0 +1,801 @@ +/* + * + * 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 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "emulator/hciemu.h" +#include + +struct pdu_set { + struct iovec req; + struct iovec rsp; +}; + +#define raw_data(args...) ((unsigned char[]) { args }) + +#define raw_pdu(args...) \ + { \ + .iov_base = raw_data(args), \ + .iov_len = sizeof(raw_data(args)), \ + } + +#define end_pdu { .iov_base = NULL } + +#define TEST_CASE_BREDR(text, ...) { \ + HCIEMU_TYPE_BREDR, \ + text, \ + sizeof((struct step[]) {__VA_ARGS__}) / sizeof(struct step), \ + (struct step[]) {__VA_ARGS__}, \ + } + +#define TEST_CASE_BREDRLE(text, ...) { \ + HCIEMU_TYPE_BREDRLE, \ + text, \ + sizeof((struct step[]) {__VA_ARGS__}) / sizeof(struct step), \ + (struct step[]) {__VA_ARGS__}, \ + } + +#define MODIFY_DATA(status, modif_fun, from, to, len) { \ + .action_status = status, \ + .action = modif_fun, \ + .set_data = from, \ + .set_data_2 = to, \ + .set_data_len = len, \ + } + +#define PROCESS_DATA(status, proc_fun, data1, data2, data3) { \ + .action_status = status, \ + .action = proc_fun, \ + .set_data = data1, \ + .set_data_2 = data2, \ + .set_data_3 = data3, \ + } + +#define ACTION(status, act_fun, data_set) { \ + .action_status = status, \ + .action = act_fun, \ + .set_data = data_set, \ + } + +#define ACTION_FAIL(act_fun, data_set) \ + ACTION(BT_STATUS_FAIL, act_fun, data_set) + +#define ACTION_SUCCESS(act_fun, data_set) \ + ACTION(BT_STATUS_SUCCESS, act_fun, data_set) + +#define CALLBACK(cb) { \ + .callback = cb, \ + } + +#define CALLBACK_STATE(cb, cb_res) { \ + .callback = cb, \ + .callback_result.state = cb_res, \ + } + +#define CALLBACK_STATUS(cb, cb_res) { \ + .callback = cb, \ + .callback_result.status = cb_res, \ + } + +#define CALLBACK_ERROR(cb, cb_err) { \ + .callback = cb, \ + .callback_result.error = cb_err, \ + } + +#define CALLBACK_ADAPTER_PROPS(props, prop_cnt) { \ + .callback = CB_BT_ADAPTER_PROPERTIES, \ + .callback_result.properties = props, \ + .callback_result.num_properties = prop_cnt, \ + } + +#define CALLBACK_PROPS(cb, props, prop_cnt) { \ + .callback = cb, \ + .callback_result.properties = props, \ + .callback_result.num_properties = prop_cnt, \ + } + +#define CALLBACK_HH_MODE(cb, cb_res, cb_mode) { \ + .callback = cb, \ + .callback_result.status = cb_res, \ + .callback_result.mode = cb_mode, \ + } + +#define CALLBACK_HHREPORT(cb, cb_res, cb_rep_size) { \ + .callback = cb, \ + .callback_result.status = cb_res, \ + .callback_result.report_size = cb_rep_size, \ + } + +#define CLLBACK_GATTC_SCAN_RES(props, prop_cnt, cb_adv_data) {\ + .callback = CB_GATTC_SCAN_RESULT, \ + .callback_result.properties = props, \ + .callback_result.num_properties = prop_cnt, \ + .callback_result.adv_data = cb_adv_data, \ + } + +#define CALLBACK_GATTC_CONNECT(cb_res, cb_prop, cb_conn_id, cb_client_id) { \ + .callback = CB_GATTC_OPEN, \ + .callback_result.status = cb_res, \ + .callback_result.properties = cb_prop, \ + .callback_result.num_properties = 1, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.gatt_app_id = cb_client_id, \ + } + +#define CALLBACK_GATTC_SEARCH_RESULT(cb_conn_id, cb_service) { \ + .callback = CB_GATTC_SEARCH_RESULT, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.service = cb_service \ + } + +#define CALLBACK_GATTC_SEARCH_COMPLETE(cb_res, cb_conn_id) { \ + .callback = CB_GATTC_SEARCH_COMPLETE, \ + .callback_result.conn_id = cb_conn_id \ + } +#define CALLBACK_GATTC_GET_CHARACTERISTIC_CB(cb_res, cb_conn_id, cb_service, \ + cb_char, cb_char_prop) { \ + .callback = CB_GATTC_GET_CHARACTERISTIC, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.status = cb_res, \ + .callback_result.service = cb_service, \ + .callback_result.characteristic = cb_char, \ + .callback_result.char_prop = cb_char_prop \ + } + +#define CALLBACK_GATTC_GET_DESCRIPTOR(cb_res, cb_conn_id, cb_service, \ + cb_char, cb_desc) { \ + .callback = CB_GATTC_GET_DESCRIPTOR, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.status = cb_res, \ + .callback_result.service = cb_service, \ + .callback_result.characteristic = cb_char, \ + .callback_result.descriptor = cb_desc \ + } + +#define CALLBACK_GATTC_GET_INCLUDED(cb_res, cb_conn_id, cb_service, \ + cb_incl) { \ + .callback = CB_GATTC_GET_INCLUDED_SERVICE, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.status = cb_res, \ + .callback_result.service = cb_service, \ + .callback_result.included = cb_incl, \ + } + +#define CALLBACK_GATTC_READ_CHARACTERISTIC(cb_res, cb_conn_id, cb_read_data) { \ + .callback = CB_GATTC_READ_CHARACTERISTIC, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.status = cb_res, \ + .callback_result.read_params = cb_read_data, \ + } + +#define CALLBACK_GATTC_READ_DESCRIPTOR(cb_res, cb_conn_id, cb_read_data) { \ + .callback = CB_GATTC_READ_DESCRIPTOR, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.status = cb_res, \ + .callback_result.read_params = cb_read_data, \ + } + +#define CALLBACK_GATTC_WRITE_DESCRIPTOR(cb_res, cb_conn_id, cb_write_data) { \ + .callback = CB_GATTC_WRITE_DESCRIPTOR, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.status = cb_res, \ + .callback_result.write_params = cb_write_data, \ + } + +#define CALLBACK_GATTC_WRITE_CHARACTERISTIC(cb_res, cb_conn_id, \ + cb_write_data) { \ + .callback = CB_GATTC_WRITE_CHARACTERISTIC, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.status = cb_res, \ + .callback_result.write_params = cb_write_data, \ + } + +#define CALLBACK_GATTC_REGISTER_FOR_NOTIF(cb_res, cb_conn_id, cb_char,\ + cb_service, cb_registered) { \ + .callback = CB_GATTC_REGISTER_FOR_NOTIFICATION, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.status = cb_res, \ + .callback_result.service = cb_service, \ + .callback_result.characteristic = cb_char, \ + .callback_result.notification_registered = cb_registered \ + } + +#define CALLBACK_GATTC_NOTIFY(cb_conn_id, cb_notify) { \ + .callback = CB_GATTC_NOTIFY, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.notify_params = cb_notify \ + } + +#define CALLBACK_GATTC_DISCONNECT(cb_res, cb_prop, cb_conn_id, cb_client_id) { \ + .callback = CB_GATTC_CLOSE, \ + .callback_result.status = cb_res, \ + .callback_result.properties = cb_prop, \ + .callback_result.num_properties = 1, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.gatt_app_id = cb_client_id, \ + } + +#define CALLBACK_GATTS_CONNECTION(cb_res, cb_prop, cb_conn_id, cb_server_id) { \ + .callback = CB_GATTS_CONNECTION, \ + .callback_result.connected = cb_res, \ + .callback_result.properties = cb_prop, \ + .callback_result.num_properties = 1, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.gatt_app_id = cb_server_id, \ + } + +#define CALLBACK_GATTS_NOTIF_CONF(cb_conn_id, cb_status) { \ + .callback = CB_GATTS_INDICATION_SEND, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.status = cb_status, \ + } + +#define CALLBACK_GATTS_SERVICE_ADDED(cb_res, cb_server_id, cb_service, \ + cb_srvc_handle, \ + cb_store_srvc_handle) { \ + .callback = CB_GATTS_SERVICE_ADDED, \ + .callback_result.status = cb_res, \ + .callback_result.gatt_app_id = cb_server_id, \ + .callback_result.service = cb_service, \ + .callback_result.srvc_handle = cb_srvc_handle, \ + .store_srvc_handle = cb_store_srvc_handle, \ + } + +#define CALLBACK_GATTS_INC_SERVICE_ADDED(cb_res, cb_server_id, cb_srvc_handle, \ + cb_inc_srvc_handle) { \ + .callback = CB_GATTS_INCLUDED_SERVICE_ADDED, \ + .callback_result.status = cb_res, \ + .callback_result.gatt_app_id = cb_server_id, \ + .callback_result.srvc_handle = cb_srvc_handle, \ + .callback_result.inc_srvc_handle = cb_inc_srvc_handle, \ + } + +#define CALLBACK_GATTS_CHARACTERISTIC_ADDED(cb_res, cb_server_id, cb_uuid, \ + cb_srvc_handle, \ + cb_char_handle, \ + cb_store_char_handle) { \ + .callback = CB_GATTS_CHARACTERISTIC_ADDED, \ + .callback_result.status = cb_res, \ + .callback_result.gatt_app_id = cb_server_id, \ + .callback_result.uuid = cb_uuid, \ + .callback_result.srvc_handle = cb_srvc_handle, \ + .callback_result.char_handle = cb_char_handle, \ + .store_char_handle = cb_store_char_handle, \ + } + +#define CALLBACK_GATTS_DESCRIPTOR_ADDED(cb_res, cb_server_id, cb_uuid, \ + cb_srvc_handle, cb_desc_handle, \ + cb_store_desc_handle) { \ + .callback = CB_GATTS_DESCRIPTOR_ADDED, \ + .callback_result.status = cb_res, \ + .callback_result.gatt_app_id = cb_server_id, \ + .callback_result.uuid = cb_uuid, \ + .callback_result.srvc_handle = cb_srvc_handle, \ + .callback_result.desc_handle = cb_desc_handle, \ + .store_desc_handle = cb_store_desc_handle, \ + } + +#define CALLBACK_GATTS_SERVICE_STARTED(cb_res, cb_server_id, cb_srvc_handle) { \ + .callback = CB_GATTS_SERVICE_STARTED, \ + .callback_result.status = cb_res, \ + .callback_result.gatt_app_id = cb_server_id, \ + .callback_result.srvc_handle = cb_srvc_handle, \ + } + +#define CALLBACK_GATTS_SERVICE_STOPPED(cb_res, cb_server_id, cb_srvc_handle) { \ + .callback = CB_GATTS_SERVICE_STOPPED, \ + .callback_result.status = cb_res, \ + .callback_result.gatt_app_id = cb_server_id, \ + .callback_result.srvc_handle = cb_srvc_handle, \ + } + +#define CALLBACK_GATTS_SERVICE_DELETED(cb_res, cb_server_id, cb_srvc_handle) { \ + .callback = CB_GATTS_SERVICE_DELETED, \ + .callback_result.status = cb_res, \ + .callback_result.gatt_app_id = cb_server_id, \ + .callback_result.srvc_handle = cb_srvc_handle, \ + } + +#define CALLBACK_GATTS_REQUEST_READ(cb_conn_id, cb_trans_id, cb_prop, \ + cb_attr_handle, cb_offset, \ + cb_is_long) { \ + .callback = CB_GATTS_REQUEST_READ, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.trans_id = cb_trans_id, \ + .callback_result.properties = cb_prop, \ + .callback_result.num_properties = 1, \ + .callback_result.attr_handle = cb_attr_handle, \ + .callback_result.offset = cb_offset, \ + .callback_result.is_long = cb_is_long, \ + } + +#define CALLBACK_GATTS_REQUEST_WRITE(cb_conn_id, cb_trans_id, cb_prop, \ + cb_attr_handle, cb_offset, \ + cb_length, cb_need_rsp, \ + cb_is_prep, cb_value) { \ + .callback = CB_GATTS_REQUEST_WRITE, \ + .callback_result.conn_id = cb_conn_id, \ + .callback_result.trans_id = cb_trans_id, \ + .callback_result.properties = cb_prop, \ + .callback_result.num_properties = 1, \ + .callback_result.attr_handle = cb_attr_handle, \ + .callback_result.offset = cb_offset, \ + .callback_result.length = cb_length, \ + .callback_result.need_rsp = cb_need_rsp, \ + .callback_result.is_prep = cb_is_prep, \ + .callback_result.value = cb_value, \ + } + +#define CALLBACK_MAP_CLIENT_REMOTE_MAS_INSTANCE(cb_status, cb_prop, \ + cb_num_inst, cb_instances) { \ + .callback = CB_MAP_CLIENT_REMOTE_MAS_INSTANCES, \ + .callback_result.properties = cb_prop, \ + .callback_result.num_properties = 1, \ + .callback_result.status = cb_status, \ + .callback_result.num_mas_instances = cb_num_inst, \ + .callback_result.mas_instances = cb_instances, \ + } + +#define CALLBACK_PAN_CTRL_STATE(cb, cb_res, cb_state, cb_local_role) { \ + .callback = cb, \ + .callback_result.status = cb_res, \ + .callback_result.ctrl_state = cb_state, \ + .callback_result.local_role = cb_local_role, \ + } + +#define CALLBACK_PAN_CONN_STATE(cb, cb_res, cb_state, cb_local_role, \ + cb_remote_role) { \ + .callback = cb, \ + .callback_result.status = cb_res, \ + .callback_result.conn_state = cb_state, \ + .callback_result.local_role = cb_local_role, \ + .callback_result.remote_role = cb_remote_role, \ + } + +#define CALLBACK_HDP_APP_REG_STATE(cb, cb_app_id, cb_state) { \ + .callback = cb, \ + .callback_result.app_id = cb_app_id, \ + .callback_result.app_state = cb_state, \ + } + +#define CALLBACK_HDP_CHANNEL_STATE(cb, cb_app_id, cb_channel_id, \ + cb_mdep_cfg_index, cb_state) { \ + .callback = cb, \ + .callback_result.app_id = cb_app_id, \ + .callback_result.channel_id = cb_channel_id, \ + .callback_result.mdep_cfg_index = cb_mdep_cfg_index, \ + .callback_result.channel_state = cb_state, \ + } + +#define CALLBACK_AV_CONN_STATE(cb, cb_av_conn_state) { \ + .callback = cb, \ + .callback_result.av_conn_state = cb_av_conn_state, \ + } + +#define CALLBACK_AV_AUDIO_STATE(cb, cb_av_audio_state) { \ + .callback = cb, \ + .callback_result.av_audio_state = cb_av_audio_state, \ + } + +#define CALLBACK_RC_PLAY_STATUS(cb, cb_length, cb_position, cb_status) { \ + .callback = cb, \ + .callback_result.song_length = cb_length, \ + .callback_result.song_position = cb_position, \ + .callback_result.play_status = cb_status, \ + } + +#define CALLBACK_RC_REG_NOTIF_TRACK_CHANGED(cb, cb_index) { \ + .callback = cb, \ + .callback_result.rc_index = cb_index, \ + } + +#define CALLBACK_RC_REG_NOTIF_POSITION_CHANGED(cb, cb_position) { \ + .callback = cb, \ + .callback_result.song_position = cb_position, \ + } + +#define CALLBACK_RC_REG_NOTIF_STATUS_CHANGED(cb, cb_status) { \ + .callback = cb, \ + .callback_result.play_status = cb_status, \ + } + +#define CALLBACK_RC_GET_ELEMENT_ATTRIBUTES(cb, cb_num_of_attrs, cb_attrs) { \ + .callback = cb, \ + .callback_result.num_of_attrs = cb_num_of_attrs, \ + .callback_result.attrs = cb_attrs, \ + } + +#define CALLBACK_DEVICE_PROPS(props, prop_cnt) \ + CALLBACK_PROPS(CB_BT_REMOTE_DEVICE_PROPERTIES, props, prop_cnt) + +#define CALLBACK_DEVICE_FOUND(props, prop_cnt) \ + CALLBACK_PROPS(CB_BT_DEVICE_FOUND, props, prop_cnt) + +#define CALLBACK_BOND_STATE(cb_res, props, prop_cnt) { \ + .callback = CB_BT_BOND_STATE_CHANGED, \ + .callback_result.state = cb_res, \ + .callback_result.properties = props, \ + .callback_result.num_properties = prop_cnt, \ + } + +#define CALLBACK_BOND_STATE_FAILED(cb_res, props, prop_cnt, reason) { \ + .callback = CB_BT_BOND_STATE_CHANGED, \ + .callback_result.state = cb_res, \ + .callback_result.status = reason, \ + .callback_result.properties = props, \ + .callback_result.num_properties = prop_cnt, \ + } + +#define CALLBACK_SSP_REQ(pair_var, props, prop_cnt) { \ + .callback = CB_BT_SSP_REQUEST, \ + .callback_result.pairing_variant = pair_var, \ + .callback_result.properties = props, \ + .callback_result.num_properties = prop_cnt, \ + } + +#define DBG_CB(cb) { cb, #cb } + +/* + * NOTICE: + * Callback enum sections should be + * updated while adding new HAL to tester. + */ +typedef enum { + CB_BT_NONE, + CB_BT_ADAPTER_STATE_CHANGED, + CB_BT_ADAPTER_PROPERTIES, + CB_BT_REMOTE_DEVICE_PROPERTIES, + CB_BT_DEVICE_FOUND, + CB_BT_DISCOVERY_STATE_CHANGED, + CB_BT_PIN_REQUEST, + CB_BT_SSP_REQUEST, + CB_BT_BOND_STATE_CHANGED, + CB_BT_ACL_STATE_CHANGED, + CB_BT_THREAD_EVT, + CB_BT_DUT_MODE_RECV, + CB_BT_LE_TEST_MODE, + + /* Hidhost cb */ + CB_HH_CONNECTION_STATE, + CB_HH_HID_INFO, + CB_HH_PROTOCOL_MODE, + CB_HH_IDLE_TIME, + CB_HH_GET_REPORT, + CB_HH_VIRTUAL_UNPLUG, + + /* PAN cb */ + CB_PAN_CONTROL_STATE, + CB_PAN_CONNECTION_STATE, + + /* HDP cb */ + CB_HDP_APP_REG_STATE, + CB_HDP_CHANNEL_STATE, + + /* A2DP cb */ + CB_A2DP_CONN_STATE, + CB_A2DP_AUDIO_STATE, + + /* AVRCP */ + CB_AVRCP_PLAY_STATUS_REQ, + CB_AVRCP_PLAY_STATUS_RSP, + CB_AVRCP_REG_NOTIF_REQ, + CB_AVRCP_REG_NOTIF_RSP, + CB_AVRCP_GET_ATTR_REQ, + CB_AVRCP_GET_ATTR_RSP, + + /* Gatt client */ + CB_GATTC_REGISTER_CLIENT, + CB_GATTC_SCAN_RESULT, + CB_GATTC_OPEN, + CB_GATTC_CLOSE, + CB_GATTC_SEARCH_COMPLETE, + CB_GATTC_SEARCH_RESULT, + CB_GATTC_GET_CHARACTERISTIC, + CB_GATTC_GET_DESCRIPTOR, + CB_GATTC_GET_INCLUDED_SERVICE, + CB_GATTC_REGISTER_FOR_NOTIFICATION, + CB_GATTC_NOTIFY, + CB_GATTC_READ_CHARACTERISTIC, + CB_GATTC_WRITE_CHARACTERISTIC, + CB_GATTC_READ_DESCRIPTOR, + CB_GATTC_WRITE_DESCRIPTOR, + CB_GATTC_EXECUTE_WRITE, + CB_GATTC_READ_REMOTE_RSSI, + CB_GATTC_LISTEN, + + /* Gatt server */ + CB_GATTS_REGISTER_SERVER, + CB_GATTS_CONNECTION, + CB_GATTS_SERVICE_ADDED, + CB_GATTS_INCLUDED_SERVICE_ADDED, + CB_GATTS_CHARACTERISTIC_ADDED, + CB_GATTS_DESCRIPTOR_ADDED, + CB_GATTS_SERVICE_STARTED, + CB_GATTS_SERVICE_STOPPED, + CB_GATTS_SERVICE_DELETED, + CB_GATTS_REQUEST_READ, + CB_GATTS_REQUEST_WRITE, + CB_GATTS_REQUEST_EXEC_WRITE, + CB_GATTS_RESPONSE_CONFIRMATION, + CB_GATTS_INDICATION_SEND, + + /* Map client */ + CB_MAP_CLIENT_REMOTE_MAS_INSTANCES, + + /* Emulator callbacks */ + CB_EMU_CONFIRM_SEND_DATA, + CB_EMU_ENCRYPTION_ENABLED, + CB_EMU_ENCRYPTION_DISABLED, + CB_EMU_CONNECTION_REJECTED, + CB_EMU_VALUE_INDICATION, + CB_EMU_VALUE_NOTIFICATION, + CB_EMU_READ_RESPONSE, + CB_EMU_WRITE_RESPONSE, + CB_EMU_ATT_ERROR, +} expected_bt_callback_t; + +struct test_data { + struct mgmt *mgmt; + audio_hw_device_t *audio; + struct hw_device_t *device; + struct hciemu *hciemu; + enum hciemu_type hciemu_type; + + const bt_interface_t *if_bluetooth; + const btsock_interface_t *if_sock; + const bthh_interface_t *if_hid; + const btpan_interface_t *if_pan; + const bthl_interface_t *if_hdp; + const btav_interface_t *if_a2dp; + struct audio_stream_out *if_stream; + const btrc_interface_t *if_avrcp; + const btgatt_interface_t *if_gatt; + const btmce_interface_t *if_map_client; + + const void *test_data; + struct queue *steps; + + guint signalfd; + uint16_t mgmt_index; + pid_t bluetoothd_pid; + + struct queue *pdus; +}; + +/* + * Struct holding bluetooth HAL action parameters + */ +struct bt_action_data { + bt_bdaddr_t *addr; + + /* Remote props action arguments */ + const int prop_type; + const bt_property_t *prop; + + /* Bonding requests parameters */ + bt_pin_code_t *pin; + const uint8_t pin_len; + const uint8_t ssp_variant; + const bool accept; + const uint16_t io_cap; + + /* Socket HAL specific params */ + const btsock_type_t sock_type; + const int channel; + const uint8_t *service_uuid; + const char *service_name; + const int flags; + int *fd; + + /* HidHost params */ + const int report_size; + + /*Connection params*/ + const uint8_t bearer_type; + const uint8_t transport_type; +}; + +/* bthost's l2cap server setup parameters */ +struct emu_set_l2cap_data { + const uint16_t psm; + const bthost_l2cap_connect_cb func; + void *user_data; +}; + +struct emu_l2cap_cid_data { + const struct pdu_set *pdu; + + uint16_t handle; + uint16_t cid; + bool is_sdp; +}; + +struct map_inst_data { + int32_t id; + int32_t scn; + int32_t msg_types; + int32_t name_len; + uint8_t *name; +}; + +/* + * Callback data structure should be enhanced with data + * returned by callbacks. It's used for test case step + * matching with expected step data. + */ +struct bt_callback_data { + bt_state_t state; + bt_status_t status; + int num_properties; + bt_property_t *properties; + bt_uuid_t *uuid; + + bt_ssp_variant_t pairing_variant; + + bthh_protocol_mode_t mode; + int report_size; + + bool adv_data; + + int gatt_app_id; + int conn_id; + int trans_id; + int offset; + bool is_long; + int connected; + uint16_t *attr_handle; + uint16_t *srvc_handle; + uint16_t *inc_srvc_handle; + uint16_t *char_handle; + uint16_t *desc_handle; + btgatt_srvc_id_t *service; + btgatt_gatt_id_t *characteristic; + btgatt_gatt_id_t *descriptor; + btgatt_srvc_id_t *included; + btgatt_read_params_t *read_params; + btgatt_write_params_t *write_params; + btgatt_notify_params_t *notify_params; + int notification_registered; + int char_prop; + int length; + uint8_t *value; + bool need_rsp; + bool is_prep; + uint8_t error; + + btpan_control_state_t ctrl_state; + btpan_connection_state_t conn_state; + int local_role; + int remote_role; + + int app_id; + int channel_id; + int mdep_cfg_index; + bthl_app_reg_state_t app_state; + bthl_channel_state_t channel_state; + + btav_connection_state_t av_conn_state; + btav_audio_state_t av_audio_state; + uint32_t song_length; + uint32_t song_position; + btrc_play_status_t play_status; + uint64_t rc_index; + uint8_t num_of_attrs; + btrc_element_attr_val_t *attrs; + + int num_mas_instances; + btmce_mas_instance_t *mas_instances; +}; + +/* + * Step structure contains expected step data and step + * action, which should be performed before step check. + */ +struct step { + void (*action)(void); + int action_status; + + expected_bt_callback_t callback; + struct bt_callback_data callback_result; + + void *set_data; + void *set_data_2; + void *set_data_3; + int set_data_len; + + uint16_t *store_srvc_handle; + uint16_t *store_char_handle; + uint16_t *store_desc_handle; +}; + +struct test_case { + const uint8_t emu_type; + const char *title; + const uint16_t step_num; + const struct step *step; +}; + +void tester_handle_l2cap_data_exchange(struct emu_l2cap_cid_data *cid_data); +void tester_generic_connect_cb(uint16_t handle, uint16_t cid, void *user_data); + +/* Get, remove test cases API */ +struct queue *get_bluetooth_tests(void); +void remove_bluetooth_tests(void); +struct queue *get_socket_tests(void); +void remove_socket_tests(void); +struct queue *get_hidhost_tests(void); +void remove_hidhost_tests(void); +struct queue *get_pan_tests(void); +void remove_pan_tests(void); +struct queue *get_hdp_tests(void); +void remove_hdp_tests(void); +struct queue *get_a2dp_tests(void); +void remove_a2dp_tests(void); +struct queue *get_avrcp_tests(void); +void remove_avrcp_tests(void); +struct queue *get_gatt_tests(void); +void remove_gatt_tests(void); +struct queue *get_map_client_tests(void); +void remove_map_client_tests(void); + +/* Generic tester API */ +void schedule_action_verification(struct step *step); +void schedule_callback_verification(struct step *step); + +/* Emulator actions */ +void emu_setup_powered_remote_action(void); +void emu_set_pin_code_action(void); +void emu_set_ssp_mode_action(void); +void emu_set_connect_cb_action(void); +void emu_remote_connect_hci_action(void); +void emu_remote_disconnect_hci_action(void); +void emu_set_io_cap(void); +void emu_add_l2cap_server_action(void); +void emu_add_rfcomm_server_action(void); + +/* Actions */ +void dummy_action(void); +void bluetooth_enable_action(void); +void bluetooth_disable_action(void); +void bt_set_property_action(void); +void bt_get_property_action(void); +void bt_start_discovery_action(void); +void bt_cancel_discovery_action(void); +void bt_get_device_props_action(void); +void bt_get_device_prop_action(void); +void bt_set_device_prop_action(void); +void bt_create_bond_action(void); +void bt_pin_reply_accept_action(void); +void bt_ssp_reply_accept_action(void); +void bt_cancel_bond_action(void); +void bt_remove_bond_action(void); +void set_default_ssp_request_handler(void); diff --git a/android/tester-map-client.c b/android/tester-map-client.c new file mode 100644 index 0000000..ff3f272 --- /dev/null +++ b/android/tester-map-client.c @@ -0,0 +1,154 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include + +#include "emulator/bthost.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "tester-main.h" + +static struct queue *list = NULL; /* List of map client test cases */ + +#define INST0_ID 0 +#define INST1_ID 1 + +#define sdp_rsp_pdu 0x07, \ + 0x00, 0x00, \ + 0x00, 0xb5, \ + 0x00, 0xb2, \ + 0x35, 0xb0, 0x36, 0x00, 0x56, 0x09, 0x00, 0x00, 0x0a, \ + 0x00, 0x01, 0x00, 0x09, 0x09, 0x00, 0x01, 0x35, 0x03, \ + 0x19, 0x11, 0x32, 0x09, 0x00, 0x04, 0x35, 0x11, 0x35, \ + 0x03, 0x19, 0x01, 0x00, 0x35, 0x05, 0x19, 0x00, 0x03, \ + 0x08, 0x04, 0x35, 0x03, 0x19, 0x00, 0x08, 0x09, 0x00, \ + 0x05, 0x35, 0x03, 0x19, 0x10, 0x02, 0x09, 0x00, 0x09, \ + 0x35, 0x08, 0x35, 0x06, 0x19, 0x11, 0x34, 0x09, 0x01, \ + 0x01, 0x09, 0x01, 0x00, 0x25, 0x0c, 0x4d, 0x41, 0x50, \ + 0x20, 0x53, 0x4d, 0x53, 0x2f, 0x4d, 0x4d, 0x53, 0x00, \ + 0x09, 0x03, 0x15, 0x08, 0x00, 0x09, 0x03, 0x16, 0x08, \ + 0x0e, 0x36, 0x00, 0x54, 0x09, 0x00, 0x00, 0x0a, 0x00, \ + 0x01, 0x00, 0x0a, 0x09, 0x00, 0x01, 0x35, 0x03, 0x19, \ + 0x11, 0x32, 0x09, 0x00, 0x04, 0x35, 0x11, 0x35, 0x03, \ + 0x19, 0x01, 0x00, 0x35, 0x05, 0x19, 0x00, 0x03, 0x08, \ + 0x05, 0x35, 0x03, 0x19, 0x00, 0x08, 0x09, 0x00, 0x05, \ + 0x35, 0x03, 0x19, 0x10, 0x02, 0x09, 0x00, 0x09, 0x35, \ + 0x08, 0x35, 0x06, 0x19, 0x11, 0x34, 0x09, 0x01, 0x01, \ + 0x09, 0x01, 0x00, 0x25, 0x0a, 0x4d, 0x41, 0x50, 0x20, \ + 0x45, 0x4d, 0x41, 0x49, 0x4c, 0x00, 0x09, 0x03, 0x15, \ + 0x08, 0x01, 0x09, 0x03, 0x16, 0x08, 0x01, \ + 0x00 + +static const struct pdu_set pdus[] = { + { end_pdu, raw_pdu(sdp_rsp_pdu) }, + { end_pdu, end_pdu }, +}; + +static struct emu_l2cap_cid_data cid_data = { + .pdu = pdus, +}; + +static bt_bdaddr_t emu_remote_bdaddr_val = { + .address = { 0x00, 0xaa, 0x01, 0x01, 0x00, 0x00 }, +}; + +static struct emu_set_l2cap_data l2cap_sdp_setup_data = { + .psm = 1, + .func = tester_generic_connect_cb, + .user_data = &cid_data, +}; + +/* TODO define all parameters according to specification document */ +static btmce_mas_instance_t remote_map_inst_sms_mms_email_val[] = { + { INST0_ID, 4, 14, "MAP SMS/MMS" }, + { INST1_ID, 5, 1, "MAP EMAIL" }, +}; + +static void map_client_cid_hook_cb(const void *data, uint16_t len, + void *user_data) +{ + /* TODO extend if needed */ +} + +static void map_client_conn_cb(uint16_t handle, void *user_data) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost = hciemu_client_get_host(data->hciemu); + + tester_print("New connection with handle 0x%04x", handle); + + if (data->hciemu_type == HCIEMU_TYPE_BREDR) { + tester_warn("Not handled device type."); + return; + } + + cid_data.cid = 0x0040; + cid_data.handle = handle; + + bthost_add_cid_hook(bthost, handle, cid_data.cid, + map_client_cid_hook_cb, &cid_data); +} + +static void map_client_get_instances_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + bt_bdaddr_t *bd_addr = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + step->action_status = + data->if_map_client->get_remote_mas_instances(bd_addr); + + schedule_action_verification(step); +} + +static struct test_case test_cases[] = { + TEST_CASE_BREDRLE("MAP Client Init", ACTION_SUCCESS(dummy_action, NULL), + ), + TEST_CASE_BREDRLE("MAP Client - Get mas instances success", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, + &l2cap_sdp_setup_data), + ACTION_SUCCESS(emu_set_connect_cb_action, map_client_conn_cb), + ACTION_SUCCESS(map_client_get_instances_action, + &emu_remote_bdaddr_val), + CALLBACK_MAP_CLIENT_REMOTE_MAS_INSTANCE(BT_STATUS_SUCCESS, NULL, + 2, remote_map_inst_sms_mms_email_val), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), +}; + +struct queue *get_map_client_tests(void) +{ + uint16_t i = 0; + + list = queue_new(); + + for (; i < sizeof(test_cases) / sizeof(test_cases[0]); ++i) + queue_push_tail(list, &test_cases[i]); + + return list; +} + +void remove_map_client_tests(void) +{ + queue_destroy(list, NULL); +} diff --git a/android/tester-pan.c b/android/tester-pan.c new file mode 100644 index 0000000..74ad107 --- /dev/null +++ b/android/tester-pan.c @@ -0,0 +1,240 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include + +#include "emulator/bthost.h" +#include "lib/bluetooth.h" +#include "android/utils.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "tester-main.h" + +static struct queue *list; /* List of pan test cases */ + +#define pan_conn_req_pdu 0x01, 0x01, 0x02, 0x11, 0x16, 0x11, 0x15 +#define pan_conn_rsp_pdu 0x01, 0x02, 0x00, 0x00 + +static const struct pdu_set pdus[] = { + { raw_pdu(pan_conn_req_pdu), raw_pdu(pan_conn_rsp_pdu) }, + { end_pdu, end_pdu }, +}; + +static struct emu_l2cap_cid_data cid_data = { + .pdu = pdus, +}; + +static struct emu_set_l2cap_data l2cap_setup_data = { + .psm = 15, + .func = tester_generic_connect_cb, + .user_data = &cid_data, +}; + +static void pan_connect_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *pan_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) pan_addr, &bdaddr); + + step->action_status = data->if_pan->connect(&bdaddr, + BTPAN_ROLE_PANU, BTPAN_ROLE_PANNAP); + + schedule_action_verification(step); +} + +static void pan_disconnect_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *pan_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + + bdaddr2android((const bdaddr_t *) pan_addr, &bdaddr); + + step->action_status = data->if_pan->disconnect(&bdaddr); + + schedule_action_verification(step); +} + +static void pan_get_local_role_action(void) +{ + struct test_data *data = tester_get_data(); + const uint8_t *pan_addr = hciemu_get_client_bdaddr(data->hciemu); + struct step *step = g_new0(struct step, 1); + bt_bdaddr_t bdaddr; + int role; + + bdaddr2android((const bdaddr_t *) pan_addr, &bdaddr); + + role = data->if_pan->get_local_role(); + if (role == BTPAN_ROLE_PANU) + step->action_status = BT_STATUS_SUCCESS; + else + step->action_status = BT_STATUS_FAIL; + + schedule_action_verification(step); +} + +static void pan_enable_nap_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_pan->enable(BTPAN_ROLE_PANNAP); + + schedule_action_verification(step); +} + +static void pan_enable_panu_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_pan->enable(BTPAN_ROLE_PANU); + + schedule_action_verification(step); +} + +static void pan_enable_none_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *step = g_new0(struct step, 1); + + step->action_status = data->if_pan->enable(BTPAN_ROLE_NONE); + + schedule_action_verification(step); +} + +static struct test_case test_cases[] = { + TEST_CASE_BREDRLE("PAN Init", + ACTION_SUCCESS(dummy_action, NULL), + ), + TEST_CASE_BREDRLE("PAN Connect - Success", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &l2cap_setup_data), + ACTION_SUCCESS(pan_connect_action, NULL), + CALLBACK_PAN_CONN_STATE(CB_PAN_CONNECTION_STATE, + BT_STATUS_SUCCESS, + BTPAN_STATE_CONNECTING, + BTPAN_ROLE_PANU, BTPAN_ROLE_PANNAP), + CALLBACK_PAN_CTRL_STATE(CB_PAN_CONTROL_STATE, BT_STATUS_SUCCESS, + BTPAN_STATE_ENABLED, BTPAN_ROLE_PANU), + CALLBACK_PAN_CONN_STATE(CB_PAN_CONNECTION_STATE, + BT_STATUS_SUCCESS, + BTPAN_STATE_CONNECTED, + BTPAN_ROLE_PANU, BTPAN_ROLE_PANNAP), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_PAN_CONN_STATE(CB_PAN_CONNECTION_STATE, + BT_STATUS_SUCCESS, + BTPAN_STATE_DISCONNECTED, + BTPAN_ROLE_PANU, BTPAN_ROLE_PANNAP), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("PAN Disconnect - Success", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &l2cap_setup_data), + ACTION_SUCCESS(pan_connect_action, NULL), + CALLBACK_PAN_CONN_STATE(CB_PAN_CONNECTION_STATE, + BT_STATUS_SUCCESS, + BTPAN_STATE_CONNECTING, + BTPAN_ROLE_PANU, BTPAN_ROLE_PANNAP), + CALLBACK_PAN_CTRL_STATE(CB_PAN_CONTROL_STATE, BT_STATUS_SUCCESS, + BTPAN_STATE_ENABLED, BTPAN_ROLE_PANU), + CALLBACK_PAN_CONN_STATE(CB_PAN_CONNECTION_STATE, + BT_STATUS_SUCCESS, + BTPAN_STATE_CONNECTED, + BTPAN_ROLE_PANU, BTPAN_ROLE_PANNAP), + ACTION_SUCCESS(pan_disconnect_action, NULL), + CALLBACK_PAN_CONN_STATE(CB_PAN_CONNECTION_STATE, + BT_STATUS_SUCCESS, + BTPAN_STATE_DISCONNECTED, + BTPAN_ROLE_PANU, BTPAN_ROLE_PANNAP), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("PAN GetLocalRole - Success", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(emu_add_l2cap_server_action, &l2cap_setup_data), + ACTION_SUCCESS(pan_connect_action, NULL), + CALLBACK_PAN_CONN_STATE(CB_PAN_CONNECTION_STATE, + BT_STATUS_SUCCESS, + BTPAN_STATE_CONNECTING, + BTPAN_ROLE_PANU, BTPAN_ROLE_PANNAP), + CALLBACK_PAN_CTRL_STATE(CB_PAN_CONTROL_STATE, BT_STATUS_SUCCESS, + BTPAN_STATE_ENABLED, BTPAN_ROLE_PANU), + CALLBACK_PAN_CONN_STATE(CB_PAN_CONNECTION_STATE, + BT_STATUS_SUCCESS, + BTPAN_STATE_CONNECTED, + BTPAN_ROLE_PANU, BTPAN_ROLE_PANNAP), + ACTION_SUCCESS(pan_get_local_role_action, NULL), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_PAN_CONN_STATE(CB_PAN_CONNECTION_STATE, + BT_STATUS_SUCCESS, + BTPAN_STATE_DISCONNECTED, + BTPAN_ROLE_PANU, BTPAN_ROLE_PANNAP), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("PAN Enable NAP - Success", + ACTION_SUCCESS(pan_enable_nap_action, NULL), + CALLBACK_PAN_CTRL_STATE(CB_PAN_CONTROL_STATE, BT_STATUS_SUCCESS, + BTPAN_STATE_ENABLED, BTPAN_ROLE_PANNAP), + ), + TEST_CASE_BREDRLE("PAN Enable PANU - Success", + ACTION(BT_STATUS_UNSUPPORTED, pan_enable_panu_action, NULL), + ), + TEST_CASE_BREDRLE("PAN Enable NONE - Success", + ACTION_SUCCESS(pan_enable_nap_action, NULL), + CALLBACK_PAN_CTRL_STATE(CB_PAN_CONTROL_STATE, BT_STATUS_SUCCESS, + BTPAN_STATE_ENABLED, BTPAN_ROLE_PANNAP), + ACTION_SUCCESS(pan_enable_none_action, NULL), + CALLBACK_PAN_CTRL_STATE(CB_PAN_CONTROL_STATE, BT_STATUS_SUCCESS, + BTPAN_STATE_DISABLED, BTPAN_ROLE_NONE), + ), +}; + +struct queue *get_pan_tests(void) +{ + uint16_t i = 0; + + list = queue_new(); + + for (; i < sizeof(test_cases) / sizeof(test_cases[0]); ++i) + queue_push_tail(list, &test_cases[i]); + + return list; +} + +void remove_pan_tests(void) +{ + queue_destroy(list, NULL); +} diff --git a/android/tester-socket.c b/android/tester-socket.c new file mode 100644 index 0000000..ac77e5a --- /dev/null +++ b/android/tester-socket.c @@ -0,0 +1,461 @@ +/* + * 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. + * + */ + +#define _GNU_SOURCE +#include +#include +#include + +#include "emulator/bthost.h" +#include "src/shared/tester.h" +#include "src/shared/queue.h" +#include "tester-main.h" + +static struct queue *list; /* List of socket test cases */ + +static bt_bdaddr_t bdaddr_dummy = { + .address = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55} +}; + +static int got_fd_result = -1; + +static struct bt_action_data btsock_param_socktype_0 = { + .addr = &bdaddr_dummy, + .sock_type = 0, + .channel = 1, + .service_uuid = NULL, + .service_name = "Test service", + .flags = 0, + .fd = &got_fd_result, +}; + +static struct bt_action_data btsock_param_socktype_l2cap = { + .addr = &bdaddr_dummy, + .sock_type = BTSOCK_L2CAP, + .channel = 1, + .service_uuid = NULL, + .service_name = "Test service", + .flags = 0, + .fd = &got_fd_result, +}; + +static struct bt_action_data btsock_param_channel_0 = { + .addr = &bdaddr_dummy, + .sock_type = BTSOCK_RFCOMM, + .channel = 0, + .service_uuid = NULL, + .service_name = "Test service", + .flags = 0, + .fd = &got_fd_result, +}; + +static struct bt_action_data btsock_param = { + .addr = &bdaddr_dummy, + .sock_type = BTSOCK_RFCOMM, + .channel = 1, + .service_uuid = NULL, + .service_name = "Test service", + .flags = 0, + .fd = &got_fd_result, +}; + +static struct bt_action_data btsock_param_inv_bdaddr = { + .addr = NULL, + .sock_type = BTSOCK_RFCOMM, + .channel = 1, + .service_uuid = NULL, + .service_name = "Test service", + .flags = 0, + .fd = &got_fd_result, +}; + +static bt_bdaddr_t emu_remote_bdaddr_val = { + .address = { 0x00, 0xaa, 0x01, 0x01, 0x00, 0x00 }, +}; +static bt_property_t prop_emu_remote_bdadr = { + .type = BT_PROPERTY_BDADDR, + .val = &emu_remote_bdaddr_val, + .len = sizeof(emu_remote_bdaddr_val), +}; +static bt_property_t prop_emu_remotes_default_set[] = { + { BT_PROPERTY_BDADDR, sizeof(emu_remote_bdaddr_val), + &emu_remote_bdaddr_val }, +}; + +static struct bt_action_data btsock_param_emu_bdaddr = { + .addr = &emu_remote_bdaddr_val, + .sock_type = BTSOCK_RFCOMM, + .channel = 1, + .service_uuid = NULL, + .service_name = "Test service", + .flags = 0, + .fd = &got_fd_result, +}; + +static struct emu_set_l2cap_data l2cap_setup_data = { + .psm = 0x0003, + .func = NULL, + .user_data = NULL, +}; + +static struct bt_action_data prop_emu_remote_bdaddr_req = { + .addr = &emu_remote_bdaddr_val, + .prop_type = BT_PROPERTY_BDADDR, + .prop = &prop_emu_remote_bdadr, +}; + +static void socket_listen_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + *action_data->fd = -1; + + step->action_status = data->if_sock->listen(action_data->sock_type, + action_data->service_name, + action_data->service_uuid, + action_data->channel, + action_data->fd, + action_data->flags); + + schedule_action_verification(step); +} + +static void socket_connect_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step; + int status; + + *action_data->fd = -1; + + status = data->if_sock->connect(action_data->addr, + action_data->sock_type, + action_data->service_uuid, + action_data->channel, + action_data->fd, + action_data->flags); + + tester_print("status %d sock_fd %d", status, *action_data->fd); + + if (!status) + return; + + step = g_new0(struct step, 1); + step->action_status = status; + + schedule_action_verification(step); +} + +static gboolean socket_chan_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + int sock_fd = g_io_channel_unix_get_fd(io); + struct step *step = g_new0(struct step, 1); + int channel, len; + + tester_print("%s", __func__); + + if (cond & G_IO_HUP) { + tester_warn("Socket %d hang up", sock_fd); + + step->action_status = BT_STATUS_FAIL; + goto done; + } + + if (cond & (G_IO_ERR | G_IO_NVAL)) { + tester_warn("Socket error: sock %d cond %d", sock_fd, cond); + + step->action_status = BT_STATUS_FAIL; + goto done; + } + + len = read(sock_fd, &channel, sizeof(channel)); + if (len != sizeof(channel)) { + tester_warn("Socket read failed"); + + step->action_status = BT_STATUS_FAIL; + goto done; + } + + tester_print("read correct channel: %d", channel); + + step->action_status = BT_STATUS_SUCCESS; + +done: + schedule_action_verification(step); + return FALSE; +} + +static void socket_read_fd_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + GIOChannel *io; + + io = g_io_channel_unix_new(*action_data->fd); + g_io_channel_set_close_on_unref(io, TRUE); + + g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + socket_chan_cb, NULL); + + g_io_channel_unref(io); +} + +static void socket_verify_fd_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + if (!*action_data->fd) { + step->action_status = BT_STATUS_FAIL; + goto done; + } + + step->action_status = (fcntl(*action_data->fd, F_GETFD) < 0) ? + BT_STATUS_FAIL : BT_STATUS_SUCCESS; + +done: + schedule_action_verification(step); +} + +static void socket_verify_channel_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + int channel, len; + struct step *step = g_new0(struct step, 1); + + if (!*action_data->fd) { + tester_warn("Ups no action_data->fd"); + + step->action_status = BT_STATUS_FAIL; + goto done; + } + + len = read(*action_data->fd, &channel, sizeof(channel)); + if (len != sizeof(channel) || channel != action_data->channel) { + tester_warn("Ups bad channel"); + + step->action_status = BT_STATUS_FAIL; + goto done; + } + + step->action_status = BT_STATUS_SUCCESS; + +done: + schedule_action_verification(step); +} + +static void socket_close_channel_action(void) +{ + struct test_data *data = tester_get_data(); + struct step *current_data_step = queue_peek_head(data->steps); + struct bt_action_data *action_data = current_data_step->set_data; + struct step *step = g_new0(struct step, 1); + + if (!*action_data->fd) { + tester_warn("Ups no action_data->fd"); + + step->action_status = BT_STATUS_FAIL; + goto done; + } + + close(*action_data->fd); + *action_data->fd = -1; + + step->action_status = BT_STATUS_SUCCESS; + +done: + schedule_action_verification(step); +} + +static struct test_case test_cases[] = { + TEST_CASE_BREDRLE("Socket Init", + ACTION_SUCCESS(dummy_action, NULL), + ), + TEST_CASE_BREDRLE("Socket Listen - Invalid: sock_type 0", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION(BT_STATUS_PARM_INVALID, socket_listen_action, + &btsock_param_socktype_0), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Listen - Invalid: sock_type L2CAP", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION(BT_STATUS_UNSUPPORTED, socket_listen_action, + &btsock_param_socktype_l2cap), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Listen - Invalid: chan, uuid", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION(BT_STATUS_PARM_INVALID, socket_listen_action, + &btsock_param_channel_0), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Listen - Check returned fd valid", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(socket_listen_action, &btsock_param), + ACTION_SUCCESS(socket_verify_fd_action, &btsock_param), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Listen - Check returned channel", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(socket_listen_action, &btsock_param), + ACTION_SUCCESS(socket_verify_fd_action, &btsock_param), + ACTION_SUCCESS(socket_verify_channel_action, &btsock_param), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Listen - Close and Listen again", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(socket_listen_action, &btsock_param), + ACTION_SUCCESS(socket_verify_fd_action, &btsock_param), + ACTION_SUCCESS(socket_verify_channel_action, &btsock_param), + ACTION_SUCCESS(socket_close_channel_action, &btsock_param), + ACTION_SUCCESS(socket_listen_action, &btsock_param), + ACTION_SUCCESS(socket_verify_fd_action, &btsock_param), + ACTION_SUCCESS(socket_verify_channel_action, &btsock_param), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Listen - Invalid: double Listen", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(socket_listen_action, &btsock_param), + ACTION_SUCCESS(socket_verify_fd_action, &btsock_param), + ACTION_SUCCESS(socket_verify_channel_action, &btsock_param), + ACTION(BT_STATUS_BUSY, socket_listen_action, &btsock_param), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Connect - Invalid: sock_type 0", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION(BT_STATUS_PARM_INVALID, socket_connect_action, + &btsock_param_socktype_0), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Connect - Invalid: sock_type L2CAP", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION(BT_STATUS_UNSUPPORTED, socket_connect_action, + &btsock_param_socktype_l2cap), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Connect - Invalid: chan, uuid", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION(BT_STATUS_PARM_INVALID, socket_connect_action, + &btsock_param_channel_0), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Connect - Invalid: bdaddr", + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION(BT_STATUS_PARM_INVALID, socket_connect_action, + &btsock_param_inv_bdaddr), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Connect - Check returned fd valid", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(bt_create_bond_action, + &prop_emu_remote_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_set, 1), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDED, + &prop_emu_remote_bdadr, 1), + CALLBACK_DEVICE_PROPS(NULL, 0), + ACTION_SUCCESS(emu_add_l2cap_server_action, &l2cap_setup_data), + ACTION_SUCCESS(emu_add_rfcomm_server_action, + &btsock_param_emu_bdaddr), + ACTION_SUCCESS(socket_connect_action, &btsock_param_emu_bdaddr), + ACTION_SUCCESS(socket_verify_fd_action, + &btsock_param_emu_bdaddr), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), + TEST_CASE_BREDRLE("Socket Connect - Check returned chann", + ACTION_SUCCESS(set_default_ssp_request_handler, NULL), + ACTION_SUCCESS(bluetooth_enable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_ON), + ACTION_SUCCESS(emu_setup_powered_remote_action, NULL), + ACTION_SUCCESS(emu_set_ssp_mode_action, NULL), + ACTION_SUCCESS(bt_create_bond_action, + &prop_emu_remote_bdaddr_req), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDING, + &prop_emu_remote_bdadr, 1), + CALLBACK_DEVICE_FOUND(prop_emu_remotes_default_set, 1), + CALLBACK_BOND_STATE(BT_BOND_STATE_BONDED, + &prop_emu_remote_bdadr, 1), + CALLBACK_DEVICE_PROPS(NULL, 0), + ACTION_SUCCESS(emu_add_l2cap_server_action, &l2cap_setup_data), + ACTION_SUCCESS(emu_add_rfcomm_server_action, + &btsock_param_emu_bdaddr), + ACTION_SUCCESS(socket_connect_action, &btsock_param_emu_bdaddr), + ACTION_SUCCESS(socket_verify_fd_action, + &btsock_param_emu_bdaddr), + ACTION_SUCCESS(socket_verify_channel_action, + &btsock_param_emu_bdaddr), + ACTION_SUCCESS(socket_read_fd_action, &btsock_param_emu_bdaddr), + ACTION_SUCCESS(bluetooth_disable_action, NULL), + CALLBACK_STATE(CB_BT_ADAPTER_STATE_CHANGED, BT_STATE_OFF), + ), +}; + +struct queue *get_socket_tests(void) +{ + uint16_t i = 0; + + list = queue_new(); + + for (; i < sizeof(test_cases) / sizeof(test_cases[0]); ++i) + queue_push_tail(list, &test_cases[i]); + + return list; +} + +void remove_socket_tests(void) +{ + queue_destroy(list, NULL); +} diff --git a/android/utils.h b/android/utils.h new file mode 100644 index 0000000..7adc2da --- /dev/null +++ b/android/utils.h @@ -0,0 +1,44 @@ +/* + * + * 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 + * + */ + +static inline void android2bdaddr(const void *buf, bdaddr_t *dst) +{ + baswap(dst, buf); +} + +static inline void bdaddr2android(const bdaddr_t *src, void *buf) +{ + baswap(buf, src); +} + +const char *bt_config_get_vendor(void); +const char *bt_config_get_model(void); +const char *bt_config_get_name(void); +const char *bt_config_get_serial(void); +const char *bt_config_get_fw_rev(void); +const char *bt_config_get_hw_rev(void); +uint64_t bt_config_get_system_id(void); +uint16_t bt_config_get_pnp_source(void); +uint16_t bt_config_get_pnp_vendor(void); +uint16_t bt_config_get_pnp_product(void); +uint16_t bt_config_get_pnp_version(void); diff --git a/attrib/att-database.h b/attrib/att-database.h new file mode 100644 index 0000000..48c50e3 --- /dev/null +++ b/attrib/att-database.h @@ -0,0 +1,43 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Texas Instruments 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 + * + */ + +/* Requirements for read/write operations */ +enum { + ATT_NONE, /* No restrictions */ + ATT_AUTHENTICATION, /* Authentication required */ + ATT_AUTHORIZATION, /* Authorization required */ + ATT_NOT_PERMITTED, /* Operation not permitted */ +}; + +struct attribute { + uint16_t handle; + bt_uuid_t uuid; + int read_req; /* Read requirement */ + int write_req; /* Write requirement */ + uint8_t (*read_cb)(struct attribute *a, struct btd_device *device, + gpointer user_data); + uint8_t (*write_cb)(struct attribute *a, struct btd_device *device, + gpointer user_data); + gpointer cb_user_data; + size_t len; + uint8_t *data; +}; diff --git a/attrib/att.c b/attrib/att.c new file mode 100644 index 0000000..c0438a3 --- /dev/null +++ b/attrib/att.c @@ -0,0 +1,1251 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "att.h" + +static inline void put_uuid_le(const bt_uuid_t *src, void *dst) +{ + if (src->type == BT_UUID16) + put_le16(src->value.u16, dst); + else + /* Convert from 128-bit BE to LE */ + bswap_128(&src->value.u128, dst); +} + +const char *att_ecode2str(uint8_t status) +{ + switch (status) { + case ATT_ECODE_INVALID_HANDLE: + return "Invalid handle"; + case ATT_ECODE_READ_NOT_PERM: + return "Attribute can't be read"; + case ATT_ECODE_WRITE_NOT_PERM: + return "Attribute can't be written"; + case ATT_ECODE_INVALID_PDU: + return "Attribute PDU was invalid"; + case ATT_ECODE_AUTHENTICATION: + return "Attribute requires authentication before read/write"; + case ATT_ECODE_REQ_NOT_SUPP: + return "Server doesn't support the request received"; + case ATT_ECODE_INVALID_OFFSET: + return "Offset past the end of the attribute"; + case ATT_ECODE_AUTHORIZATION: + return "Attribute requires authorization before read/write"; + case ATT_ECODE_PREP_QUEUE_FULL: + return "Too many prepare writes have been queued"; + case ATT_ECODE_ATTR_NOT_FOUND: + return "No attribute found within the given range"; + case ATT_ECODE_ATTR_NOT_LONG: + return "Attribute can't be read/written using Read Blob Req"; + case ATT_ECODE_INSUFF_ENCR_KEY_SIZE: + return "Encryption Key Size is insufficient"; + case ATT_ECODE_INVAL_ATTR_VALUE_LEN: + return "Attribute value length is invalid"; + case ATT_ECODE_UNLIKELY: + return "Request attribute has encountered an unlikely error"; + case ATT_ECODE_INSUFF_ENC: + return "Encryption required before read/write"; + case ATT_ECODE_UNSUPP_GRP_TYPE: + return "Attribute type is not a supported grouping attribute"; + case ATT_ECODE_INSUFF_RESOURCES: + return "Insufficient Resources to complete the request"; + case ATT_ECODE_IO: + return "Internal application error: I/O"; + case ATT_ECODE_TIMEOUT: + return "A timeout occured"; + case ATT_ECODE_ABORTED: + return "The operation was aborted"; + default: + return "Unexpected error code"; + } +} + +void att_data_list_free(struct att_data_list *list) +{ + if (list == NULL) + return; + + if (list->data) { + int i; + for (i = 0; i < list->num; i++) + g_free(list->data[i]); + } + + g_free(list->data); + g_free(list); +} + +struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len) +{ + struct att_data_list *list; + int i; + + if (len > UINT8_MAX) + return NULL; + + list = g_new0(struct att_data_list, 1); + list->len = len; + list->num = num; + + list->data = g_malloc0(sizeof(uint8_t *) * num); + + for (i = 0; i < num; i++) + list->data[i] = g_malloc0(sizeof(uint8_t) * len); + + return list; +} + +static void get_uuid(uint8_t type, const void *val, bt_uuid_t *uuid) +{ + if (type == BT_UUID16) + bt_uuid16_create(uuid, get_le16(val)); + else { + uint128_t u128; + + /* Convert from 128-bit LE to BE */ + bswap_128(val, &u128); + bt_uuid128_create(uuid, u128); + } +} + +uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, size_t len) +{ + uint16_t uuid_len; + + if (!uuid) + return 0; + + if (uuid->type == BT_UUID16) + uuid_len = 2; + else if (uuid->type == BT_UUID128) + uuid_len = 16; + else + return 0; + + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_READ_BY_GROUP_REQ; + /* Starting Handle (2 octets) */ + put_le16(start, &pdu[1]); + /* Ending Handle (2 octets) */ + put_le16(end, &pdu[3]); + /* Attribute Group Type (2 or 16 octet UUID) */ + put_uuid_le(uuid, &pdu[5]); + + return 5 + uuid_len; +} + +uint16_t dec_read_by_grp_req(const uint8_t *pdu, size_t len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid) +{ + const size_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); + uint8_t type; + + if (pdu == NULL) + return 0; + + if (start == NULL || end == NULL || uuid == NULL) + return 0; + + if (pdu[0] != ATT_OP_READ_BY_GROUP_REQ) + return 0; + + if (len == (min_len + 2)) + type = BT_UUID16; + else if (len == (min_len + 16)) + type = BT_UUID128; + else + return 0; + + *start = get_le16(&pdu[1]); + *end = get_le16(&pdu[3]); + + get_uuid(type, &pdu[5], uuid); + + return len; +} + +uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu, + size_t len) +{ + int i; + uint16_t w; + uint8_t *ptr; + + if (list == NULL) + return 0; + + if (len < list->len + sizeof(uint8_t) * 2) + return 0; + + pdu[0] = ATT_OP_READ_BY_GROUP_RESP; + pdu[1] = list->len; + + ptr = &pdu[2]; + + for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) { + memcpy(ptr, list->data[i], list->len); + ptr += list->len; + w += list->len; + } + + return w; +} + +struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, size_t len) +{ + struct att_data_list *list; + const uint8_t *ptr; + uint16_t elen, num; + int i; + + if (pdu[0] != ATT_OP_READ_BY_GROUP_RESP) + return NULL; + + /* PDU must contain at least: + * - Attribute Opcode (1 octet) + * - Length (1 octet) + * - Attribute Data List (at least one entry): + * - Attribute Handle (2 octets) + * - End Group Handle (2 octets) + * - Attribute Value (at least 1 octet) */ + if (len < 7) + return NULL; + + elen = pdu[1]; + /* Minimum Attribute Data List size */ + if (elen < 5) + return NULL; + + /* Reject incomplete Attribute Data List */ + if ((len - 2) % elen) + return NULL; + + num = (len - 2) / elen; + list = att_data_list_alloc(num, elen); + if (list == NULL) + return NULL; + + ptr = &pdu[2]; + + for (i = 0; i < num; i++) { + memcpy(list->data[i], ptr, list->len); + ptr += list->len; + } + + return list; +} + +uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + const uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len) +{ + uint16_t min_len = sizeof(pdu[0]) + sizeof(start) + sizeof(end) + + sizeof(uint16_t); + + if (pdu == NULL) + return 0; + + if (!uuid) + return 0; + + if (uuid->type != BT_UUID16) + return 0; + + if (vlen > len - min_len) + vlen = len - min_len; + + pdu[0] = ATT_OP_FIND_BY_TYPE_REQ; + put_le16(start, &pdu[1]); + put_le16(end, &pdu[3]); + put_le16(uuid->value.u16, &pdu[5]); + + if (vlen > 0) { + memcpy(&pdu[7], value, vlen); + return min_len + vlen; + } + + return min_len; +} + +uint16_t dec_find_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid, + uint8_t *value, size_t *vlen) +{ + if (pdu == NULL) + return 0; + + if (len < 7) + return 0; + + /* Attribute Opcode (1 octet) */ + if (pdu[0] != ATT_OP_FIND_BY_TYPE_REQ) + return 0; + + /* First requested handle number (2 octets) */ + *start = get_le16(&pdu[1]); + /* Last requested handle number (2 octets) */ + *end = get_le16(&pdu[3]); + /* 16-bit UUID to find (2 octets) */ + bt_uuid16_create(uuid, get_le16(&pdu[5])); + + /* Attribute value to find */ + *vlen = len - 7; + if (*vlen > 0) + memcpy(value, pdu + 7, *vlen); + + return len; +} + +uint16_t enc_find_by_type_resp(GSList *matches, uint8_t *pdu, size_t len) +{ + GSList *l; + uint16_t offset; + + if (!pdu) + return 0; + + pdu[0] = ATT_OP_FIND_BY_TYPE_RESP; + + for (l = matches, offset = 1; + l && len >= (offset + sizeof(uint16_t) * 2); + l = l->next, offset += sizeof(uint16_t) * 2) { + struct att_range *range = l->data; + + put_le16(range->start, &pdu[offset]); + put_le16(range->end, &pdu[offset + 2]); + } + + return offset; +} + +GSList *dec_find_by_type_resp(const uint8_t *pdu, size_t len) +{ + struct att_range *range; + GSList *matches; + off_t offset; + + /* PDU should contain at least: + * - Attribute Opcode (1 octet) + * - Handles Information List (at least one entry): + * - Found Attribute Handle (2 octets) + * - Group End Handle (2 octets) */ + if (pdu == NULL || len < 5) + return NULL; + + if (pdu[0] != ATT_OP_FIND_BY_TYPE_RESP) + return NULL; + + /* Reject incomplete Handles Information List */ + if ((len - 1) % 4) + return NULL; + + for (offset = 1, matches = NULL; + len >= (offset + sizeof(uint16_t) * 2); + offset += sizeof(uint16_t) * 2) { + range = g_new0(struct att_range, 1); + range->start = get_le16(&pdu[offset]); + range->end = get_le16(&pdu[offset + 2]); + + matches = g_slist_append(matches, range); + } + + return matches; +} + +uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, size_t len) +{ + uint16_t uuid_len; + + if (!uuid) + return 0; + + if (uuid->type == BT_UUID16) + uuid_len = 2; + else if (uuid->type == BT_UUID128) + uuid_len = 16; + else + return 0; + + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_READ_BY_TYPE_REQ; + /* Starting Handle (2 octets) */ + put_le16(start, &pdu[1]); + /* Ending Handle (2 octets) */ + put_le16(end, &pdu[3]); + /* Attribute Type (2 or 16 octet UUID) */ + put_uuid_le(uuid, &pdu[5]); + + return 5 + uuid_len; +} + +uint16_t dec_read_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid) +{ + const size_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); + uint8_t type; + + if (pdu == NULL) + return 0; + + if (start == NULL || end == NULL || uuid == NULL) + return 0; + + if (len == (min_len + 2)) + type = BT_UUID16; + else if (len == (min_len + 16)) + type = BT_UUID128; + else + return 0; + + if (pdu[0] != ATT_OP_READ_BY_TYPE_REQ) + return 0; + + *start = get_le16(&pdu[1]); + *end = get_le16(&pdu[3]); + + get_uuid(type, &pdu[5], uuid); + + return len; +} + +uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu, + size_t len) +{ + uint8_t *ptr; + size_t i, w, l; + + if (list == NULL) + return 0; + + if (pdu == NULL) + return 0; + + l = MIN(len - 2, list->len); + + pdu[0] = ATT_OP_READ_BY_TYPE_RESP; + pdu[1] = l; + ptr = &pdu[2]; + + for (i = 0, w = 2; i < list->num && w + l <= len; i++) { + memcpy(ptr, list->data[i], l); + ptr += l; + w += l; + } + + return w; +} + +struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, size_t len) +{ + struct att_data_list *list; + const uint8_t *ptr; + uint16_t elen, num; + int i; + + if (pdu[0] != ATT_OP_READ_BY_TYPE_RESP) + return NULL; + + /* PDU must contain at least: + * - Attribute Opcode (1 octet) + * - Length (1 octet) + * - Attribute Data List (at least one entry): + * - Attribute Handle (2 octets) + * - Attribute Value (at least 1 octet) */ + if (len < 5) + return NULL; + + elen = pdu[1]; + /* Minimum Attribute Data List size */ + if (elen < 3) + return NULL; + + /* Reject incomplete Attribute Data List */ + if ((len - 2) % elen) + return NULL; + + num = (len - 2) / elen; + list = att_data_list_alloc(num, elen); + if (list == NULL) + return NULL; + + ptr = &pdu[2]; + + for (i = 0; i < num; i++) { + memcpy(list->data[i], ptr, list->len); + ptr += list->len; + } + + return list; +} + +uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle); + + if (pdu == NULL) + return 0; + + if (vlen > len - min_len) + vlen = len - min_len; + + pdu[0] = ATT_OP_WRITE_CMD; + put_le16(handle, &pdu[1]); + + if (vlen > 0) { + memcpy(&pdu[3], value, vlen); + return min_len + vlen; + } + + return min_len; +} + +uint16_t dec_write_cmd(const uint8_t *pdu, size_t len, uint16_t *handle, + uint8_t *value, size_t *vlen) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); + + if (pdu == NULL) + return 0; + + if (value == NULL || vlen == NULL || handle == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_WRITE_CMD) + return 0; + + *handle = get_le16(&pdu[1]); + memcpy(value, pdu + min_len, len - min_len); + *vlen = len - min_len; + + return len; +} + +uint16_t enc_signed_write_cmd(uint16_t handle, const uint8_t *value, + size_t vlen, struct bt_crypto *crypto, + const uint8_t csrk[16], + uint32_t sign_cnt, + uint8_t *pdu, size_t len) +{ + const uint16_t hdr_len = sizeof(pdu[0]) + sizeof(handle); + const uint16_t min_len = hdr_len + ATT_SIGNATURE_LEN; + + if (pdu == NULL) + return 0; + + if (vlen > len - min_len) + vlen = len - min_len; + + pdu[0] = ATT_OP_SIGNED_WRITE_CMD; + put_le16(handle, &pdu[1]); + + if (vlen > 0) + memcpy(&pdu[hdr_len], value, vlen); + + if (!bt_crypto_sign_att(crypto, csrk, pdu, hdr_len + vlen, sign_cnt, + &pdu[hdr_len + vlen])) + return 0; + + return min_len + vlen; +} + +uint16_t dec_signed_write_cmd(const uint8_t *pdu, size_t len, + uint16_t *handle, + uint8_t *value, size_t *vlen, + uint8_t signature[12]) +{ + const uint16_t hdr_len = sizeof(pdu[0]) + sizeof(*handle); + const uint16_t min_len = hdr_len + ATT_SIGNATURE_LEN; + + + if (pdu == NULL) + return 0; + + if (value == NULL || vlen == NULL || handle == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_SIGNED_WRITE_CMD) + return 0; + + *vlen = len - min_len; + *handle = get_le16(&pdu[1]); + memcpy(value, pdu + hdr_len, *vlen); + + memcpy(signature, pdu + hdr_len + *vlen, ATT_SIGNATURE_LEN); + + return len; +} + +uint16_t enc_write_req(uint16_t handle, const uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle); + + if (pdu == NULL) + return 0; + + if (vlen > len - min_len) + vlen = len - min_len; + + pdu[0] = ATT_OP_WRITE_REQ; + put_le16(handle, &pdu[1]); + + if (vlen > 0) { + memcpy(&pdu[3], value, vlen); + return min_len + vlen; + } + + return min_len; +} + +uint16_t dec_write_req(const uint8_t *pdu, size_t len, uint16_t *handle, + uint8_t *value, size_t *vlen) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); + + if (pdu == NULL) + return 0; + + if (value == NULL || vlen == NULL || handle == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_WRITE_REQ) + return 0; + + *handle = get_le16(&pdu[1]); + *vlen = len - min_len; + if (*vlen > 0) + memcpy(value, pdu + min_len, *vlen); + + return len; +} + +uint16_t enc_write_resp(uint8_t *pdu) +{ + if (pdu == NULL) + return 0; + + pdu[0] = ATT_OP_WRITE_RESP; + + return sizeof(pdu[0]); +} + +uint16_t dec_write_resp(const uint8_t *pdu, size_t len) +{ + if (pdu == NULL) + return 0; + + if (pdu[0] != ATT_OP_WRITE_RESP) + return 0; + + return len; +} + +uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, size_t len) +{ + if (pdu == NULL) + return 0; + + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_READ_REQ; + /* Attribute Handle (2 octets) */ + put_le16(handle, &pdu[1]); + + return 3; +} + +uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu, + size_t len) +{ + if (pdu == NULL) + return 0; + + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_READ_BLOB_REQ; + /* Attribute Handle (2 octets) */ + put_le16(handle, &pdu[1]); + /* Value Offset (2 octets) */ + put_le16(offset, &pdu[3]); + + return 5; +} + +uint16_t dec_read_req(const uint8_t *pdu, size_t len, uint16_t *handle) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle); + + if (pdu == NULL) + return 0; + + if (handle == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_READ_REQ) + return 0; + + *handle = get_le16(&pdu[1]); + + return min_len; +} + +uint16_t dec_read_blob_req(const uint8_t *pdu, size_t len, uint16_t *handle, + uint16_t *offset) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) + + sizeof(*offset); + + if (pdu == NULL) + return 0; + + if (handle == NULL) + return 0; + + if (offset == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_READ_BLOB_REQ) + return 0; + + *handle = get_le16(&pdu[1]); + *offset = get_le16(&pdu[3]); + + return min_len; +} + +uint16_t enc_read_resp(uint8_t *value, size_t vlen, uint8_t *pdu, size_t len) +{ + if (pdu == NULL) + return 0; + + /* If the attribute value length is longer than the allowed PDU size, + * send only the octets that fit on the PDU. The remaining octets can + * be requested using the Read Blob Request. */ + if (vlen > len - 1) + vlen = len - 1; + + pdu[0] = ATT_OP_READ_RESP; + + memcpy(pdu + 1, value, vlen); + + return vlen + 1; +} + +uint16_t enc_read_blob_resp(uint8_t *value, size_t vlen, uint16_t offset, + uint8_t *pdu, size_t len) +{ + if (pdu == NULL) + return 0; + + vlen -= offset; + if (vlen > len - 1) + vlen = len - 1; + + pdu[0] = ATT_OP_READ_BLOB_RESP; + + memcpy(pdu + 1, &value[offset], vlen); + + return vlen + 1; +} + +ssize_t dec_read_resp(const uint8_t *pdu, size_t len, uint8_t *value, + size_t vlen) +{ + if (pdu == NULL) + return -EINVAL; + + if (pdu[0] != ATT_OP_READ_RESP) + return -EINVAL; + + if (value == NULL) + return len - 1; + + if (vlen < (len - 1)) + return -ENOBUFS; + + memcpy(value, pdu + 1, len - 1); + + return len - 1; +} + +uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status, + uint8_t *pdu, size_t len) +{ + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_ERROR; + /* Request Opcode In Error (1 octet) */ + pdu[1] = opcode; + /* Attribute Handle In Error (2 octets) */ + put_le16(handle, &pdu[2]); + /* Error Code (1 octet) */ + pdu[4] = status; + + return 5; +} + +uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, + size_t len) +{ + if (pdu == NULL) + return 0; + + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_FIND_INFO_REQ; + /* Starting Handle (2 octets) */ + put_le16(start, &pdu[1]); + /* Ending Handle (2 octets) */ + put_le16(end, &pdu[3]); + + return 5; +} + +uint16_t dec_find_info_req(const uint8_t *pdu, size_t len, uint16_t *start, + uint16_t *end) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*start) + sizeof(*end); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + if (start == NULL || end == NULL) + return 0; + + if (pdu[0] != ATT_OP_FIND_INFO_REQ) + return 0; + + *start = get_le16(&pdu[1]); + *end = get_le16(&pdu[3]); + + return min_len; +} + +uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list, + uint8_t *pdu, size_t len) +{ + uint8_t *ptr; + size_t i, w; + + if (pdu == NULL) + return 0; + + if (list == NULL) + return 0; + + if (len < list->len + sizeof(uint8_t) * 2) + return 0; + + pdu[0] = ATT_OP_FIND_INFO_RESP; + pdu[1] = format; + ptr = (void *) &pdu[2]; + + for (i = 0, w = 2; i < list->num && w + list->len <= len; i++) { + memcpy(ptr, list->data[i], list->len); + ptr += list->len; + w += list->len; + } + + return w; +} + +struct att_data_list *dec_find_info_resp(const uint8_t *pdu, size_t len, + uint8_t *format) +{ + struct att_data_list *list; + uint8_t *ptr; + uint16_t elen, num; + int i; + + if (pdu == NULL) + return 0; + + if (format == NULL) + return 0; + + if (pdu[0] != ATT_OP_FIND_INFO_RESP) + return 0; + + *format = pdu[1]; + elen = sizeof(pdu[0]) + sizeof(*format); + if (*format == 0x01) + elen += 2; + else if (*format == 0x02) + elen += 16; + + num = (len - 2) / elen; + + ptr = (void *) &pdu[2]; + + list = att_data_list_alloc(num, elen); + if (list == NULL) + return NULL; + + for (i = 0; i < num; i++) { + memcpy(list->data[i], ptr, list->len); + ptr += list->len; + } + + return list; +} + +uint16_t enc_notification(uint16_t handle, uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); + + if (pdu == NULL) + return 0; + + if (len < (vlen + min_len)) + return 0; + + pdu[0] = ATT_OP_HANDLE_NOTIFY; + put_le16(handle, &pdu[1]); + memcpy(&pdu[3], value, vlen); + + return vlen + min_len; +} + +uint16_t enc_indication(uint16_t handle, uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); + + if (pdu == NULL) + return 0; + + if (len < (vlen + min_len)) + return 0; + + pdu[0] = ATT_OP_HANDLE_IND; + put_le16(handle, &pdu[1]); + memcpy(&pdu[3], value, vlen); + + return vlen + min_len; +} + +uint16_t dec_indication(const uint8_t *pdu, size_t len, uint16_t *handle, + uint8_t *value, size_t vlen) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(uint16_t); + uint16_t dlen; + + if (pdu == NULL) + return 0; + + if (pdu[0] != ATT_OP_HANDLE_IND) + return 0; + + if (len < min_len) + return 0; + + dlen = MIN(len - min_len, vlen); + + if (handle) + *handle = get_le16(&pdu[1]); + + memcpy(value, &pdu[3], dlen); + + return dlen; +} + +uint16_t enc_confirmation(uint8_t *pdu, size_t len) +{ + if (pdu == NULL) + return 0; + + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_HANDLE_CNF; + + return 1; +} + +uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, size_t len) +{ + if (pdu == NULL) + return 0; + + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_MTU_REQ; + /* Client Rx MTU (2 octets) */ + put_le16(mtu, &pdu[1]); + + return 3; +} + +uint16_t dec_mtu_req(const uint8_t *pdu, size_t len, uint16_t *mtu) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu); + + if (pdu == NULL) + return 0; + + if (mtu == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_MTU_REQ) + return 0; + + *mtu = get_le16(&pdu[1]); + + return min_len; +} + +uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, size_t len) +{ + if (pdu == NULL) + return 0; + + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_MTU_RESP; + /* Server Rx MTU (2 octets) */ + put_le16(mtu, &pdu[1]); + + return 3; +} + +uint16_t dec_mtu_resp(const uint8_t *pdu, size_t len, uint16_t *mtu) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*mtu); + + if (pdu == NULL) + return 0; + + if (mtu == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_MTU_RESP) + return 0; + + *mtu = get_le16(&pdu[1]); + + return min_len; +} + +uint16_t enc_prep_write_req(uint16_t handle, uint16_t offset, + const uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) + + sizeof(offset); + + if (pdu == NULL) + return 0; + + if (vlen > len - min_len) + vlen = len - min_len; + + pdu[0] = ATT_OP_PREP_WRITE_REQ; + put_le16(handle, &pdu[1]); + put_le16(offset, &pdu[3]); + + if (vlen > 0) { + memcpy(&pdu[5], value, vlen); + return min_len + vlen; + } + + return min_len; +} + +uint16_t dec_prep_write_req(const uint8_t *pdu, size_t len, uint16_t *handle, + uint16_t *offset, uint8_t *value, size_t *vlen) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) + + sizeof(*offset); + + if (pdu == NULL) + return 0; + + if (handle == NULL || offset == NULL || value == NULL || vlen == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_PREP_WRITE_REQ) + return 0; + + *handle = get_le16(&pdu[1]); + *offset = get_le16(&pdu[3]); + + *vlen = len - min_len; + if (*vlen > 0) + memcpy(value, pdu + min_len, *vlen); + + return len; +} + +uint16_t enc_prep_write_resp(uint16_t handle, uint16_t offset, + const uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(handle) + + sizeof(offset); + + if (pdu == NULL) + return 0; + + if (vlen > len - min_len) + vlen = len - min_len; + + pdu[0] = ATT_OP_PREP_WRITE_RESP; + put_le16(handle, &pdu[1]); + put_le16(offset, &pdu[3]); + + if (vlen > 0) { + memcpy(&pdu[5], value, vlen); + return min_len + vlen; + } + + return min_len; +} + +uint16_t dec_prep_write_resp(const uint8_t *pdu, size_t len, uint16_t *handle, + uint16_t *offset, uint8_t *value, size_t *vlen) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*handle) + + sizeof(*offset); + + if (pdu == NULL) + return 0; + + if (handle == NULL || offset == NULL || value == NULL || vlen == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_PREP_WRITE_REQ) + return 0; + + *handle = get_le16(&pdu[1]); + *offset = get_le16(&pdu[3]); + *vlen = len - min_len; + if (*vlen > 0) + memcpy(value, pdu + min_len, *vlen); + + return len; +} + +uint16_t enc_exec_write_req(uint8_t flags, uint8_t *pdu, size_t len) +{ + if (pdu == NULL) + return 0; + + if (flags > 1) + return 0; + + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_EXEC_WRITE_REQ; + /* Flags (1 octet) */ + pdu[1] = flags; + + return 2; +} + +uint16_t dec_exec_write_req(const uint8_t *pdu, size_t len, uint8_t *flags) +{ + const uint16_t min_len = sizeof(pdu[0]) + sizeof(*flags); + + if (pdu == NULL) + return 0; + + if (flags == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_EXEC_WRITE_REQ) + return 0; + + *flags = pdu[1]; + + return min_len; +} + +uint16_t enc_exec_write_resp(uint8_t *pdu) +{ + if (pdu == NULL) + return 0; + + /* Attribute Opcode (1 octet) */ + pdu[0] = ATT_OP_EXEC_WRITE_RESP; + + return 1; +} + +uint16_t dec_exec_write_resp(const uint8_t *pdu, size_t len) +{ + const uint16_t min_len = sizeof(pdu[0]); + + if (pdu == NULL) + return 0; + + if (len < min_len) + return 0; + + if (pdu[0] != ATT_OP_EXEC_WRITE_RESP) + return 0; + + return len; +} diff --git a/attrib/att.h b/attrib/att.h new file mode 100644 index 0000000..2311aaf --- /dev/null +++ b/attrib/att.h @@ -0,0 +1,202 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "src/shared/crypto.h" + +/* Len of signature in write signed packet */ +#define ATT_SIGNATURE_LEN 12 + +/* Attribute Protocol Opcodes */ +#define ATT_OP_ERROR 0x01 +#define ATT_OP_MTU_REQ 0x02 +#define ATT_OP_MTU_RESP 0x03 +#define ATT_OP_FIND_INFO_REQ 0x04 +#define ATT_OP_FIND_INFO_RESP 0x05 +#define ATT_OP_FIND_BY_TYPE_REQ 0x06 +#define ATT_OP_FIND_BY_TYPE_RESP 0x07 +#define ATT_OP_READ_BY_TYPE_REQ 0x08 +#define ATT_OP_READ_BY_TYPE_RESP 0x09 +#define ATT_OP_READ_REQ 0x0A +#define ATT_OP_READ_RESP 0x0B +#define ATT_OP_READ_BLOB_REQ 0x0C +#define ATT_OP_READ_BLOB_RESP 0x0D +#define ATT_OP_READ_MULTI_REQ 0x0E +#define ATT_OP_READ_MULTI_RESP 0x0F +#define ATT_OP_READ_BY_GROUP_REQ 0x10 +#define ATT_OP_READ_BY_GROUP_RESP 0x11 +#define ATT_OP_WRITE_REQ 0x12 +#define ATT_OP_WRITE_RESP 0x13 +#define ATT_OP_WRITE_CMD 0x52 +#define ATT_OP_PREP_WRITE_REQ 0x16 +#define ATT_OP_PREP_WRITE_RESP 0x17 +#define ATT_OP_EXEC_WRITE_REQ 0x18 +#define ATT_OP_EXEC_WRITE_RESP 0x19 +#define ATT_OP_HANDLE_NOTIFY 0x1B +#define ATT_OP_HANDLE_IND 0x1D +#define ATT_OP_HANDLE_CNF 0x1E +#define ATT_OP_SIGNED_WRITE_CMD 0xD2 + +/* Error codes for Error response PDU */ +#define ATT_ECODE_INVALID_HANDLE 0x01 +#define ATT_ECODE_READ_NOT_PERM 0x02 +#define ATT_ECODE_WRITE_NOT_PERM 0x03 +#define ATT_ECODE_INVALID_PDU 0x04 +#define ATT_ECODE_AUTHENTICATION 0x05 +#define ATT_ECODE_REQ_NOT_SUPP 0x06 +#define ATT_ECODE_INVALID_OFFSET 0x07 +#define ATT_ECODE_AUTHORIZATION 0x08 +#define ATT_ECODE_PREP_QUEUE_FULL 0x09 +#define ATT_ECODE_ATTR_NOT_FOUND 0x0A +#define ATT_ECODE_ATTR_NOT_LONG 0x0B +#define ATT_ECODE_INSUFF_ENCR_KEY_SIZE 0x0C +#define ATT_ECODE_INVAL_ATTR_VALUE_LEN 0x0D +#define ATT_ECODE_UNLIKELY 0x0E +#define ATT_ECODE_INSUFF_ENC 0x0F +#define ATT_ECODE_UNSUPP_GRP_TYPE 0x10 +#define ATT_ECODE_INSUFF_RESOURCES 0x11 +/* Application error */ +#define ATT_ECODE_IO 0x80 +#define ATT_ECODE_TIMEOUT 0x81 +#define ATT_ECODE_ABORTED 0x82 + +#define ATT_MAX_VALUE_LEN 512 +#define ATT_DEFAULT_L2CAP_MTU 48 +#define ATT_DEFAULT_LE_MTU 23 + +#define ATT_CID 4 +#define ATT_PSM 31 + +/* Flags for Execute Write Request Operation */ +#define ATT_CANCEL_ALL_PREP_WRITES 0x00 +#define ATT_WRITE_ALL_PREP_WRITES 0x01 + +/* Find Information Response Formats */ +#define ATT_FIND_INFO_RESP_FMT_16BIT 0x01 +#define ATT_FIND_INFO_RESP_FMT_128BIT 0x02 + +struct att_data_list { + uint16_t num; + uint16_t len; + uint8_t **data; +}; + +struct att_range { + uint16_t start; + uint16_t end; +}; + +struct att_data_list *att_data_list_alloc(uint16_t num, uint16_t len); +void att_data_list_free(struct att_data_list *list); + +const char *att_ecode2str(uint8_t status); +uint16_t enc_read_by_grp_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, size_t len); +uint16_t dec_read_by_grp_req(const uint8_t *pdu, size_t len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid); +uint16_t enc_read_by_grp_resp(struct att_data_list *list, uint8_t *pdu, + size_t len); +uint16_t enc_find_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + const uint8_t *value, size_t vlen, uint8_t *pdu, + size_t len); +uint16_t dec_find_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid, uint8_t *value, size_t *vlen); +uint16_t enc_find_by_type_resp(GSList *ranges, uint8_t *pdu, size_t len); +GSList *dec_find_by_type_resp(const uint8_t *pdu, size_t len); +struct att_data_list *dec_read_by_grp_resp(const uint8_t *pdu, size_t len); +uint16_t enc_read_by_type_req(uint16_t start, uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, size_t len); +uint16_t dec_read_by_type_req(const uint8_t *pdu, size_t len, uint16_t *start, + uint16_t *end, bt_uuid_t *uuid); +uint16_t enc_read_by_type_resp(struct att_data_list *list, uint8_t *pdu, + size_t len); +uint16_t enc_write_cmd(uint16_t handle, const uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len); +uint16_t dec_write_cmd(const uint8_t *pdu, size_t len, uint16_t *handle, + uint8_t *value, size_t *vlen); +uint16_t enc_signed_write_cmd(uint16_t handle, + const uint8_t *value, size_t vlen, + struct bt_crypto *crypto, + const uint8_t csrk[16], + uint32_t sign_cnt, + uint8_t *pdu, size_t len); +uint16_t dec_signed_write_cmd(const uint8_t *pdu, size_t len, + uint16_t *handle, + uint8_t *value, size_t *vlen, + uint8_t signature[12]); +struct att_data_list *dec_read_by_type_resp(const uint8_t *pdu, size_t len); +uint16_t enc_write_req(uint16_t handle, const uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len); +uint16_t dec_write_req(const uint8_t *pdu, size_t len, uint16_t *handle, + uint8_t *value, size_t *vlen); +uint16_t enc_write_resp(uint8_t *pdu); +uint16_t dec_write_resp(const uint8_t *pdu, size_t len); +uint16_t enc_read_req(uint16_t handle, uint8_t *pdu, size_t len); +uint16_t enc_read_blob_req(uint16_t handle, uint16_t offset, uint8_t *pdu, + size_t len); +uint16_t dec_read_req(const uint8_t *pdu, size_t len, uint16_t *handle); +uint16_t dec_read_blob_req(const uint8_t *pdu, size_t len, uint16_t *handle, + uint16_t *offset); +uint16_t enc_read_resp(uint8_t *value, size_t vlen, uint8_t *pdu, size_t len); +uint16_t enc_read_blob_resp(uint8_t *value, size_t vlen, uint16_t offset, + uint8_t *pdu, size_t len); +ssize_t dec_read_resp(const uint8_t *pdu, size_t len, uint8_t *value, + size_t vlen); +uint16_t enc_error_resp(uint8_t opcode, uint16_t handle, uint8_t status, + uint8_t *pdu, size_t len); +uint16_t enc_find_info_req(uint16_t start, uint16_t end, uint8_t *pdu, + size_t len); +uint16_t dec_find_info_req(const uint8_t *pdu, size_t len, uint16_t *start, + uint16_t *end); +uint16_t enc_find_info_resp(uint8_t format, struct att_data_list *list, + uint8_t *pdu, size_t len); +struct att_data_list *dec_find_info_resp(const uint8_t *pdu, size_t len, + uint8_t *format); +uint16_t enc_notification(uint16_t handle, uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len); +uint16_t enc_indication(uint16_t handle, uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len); +uint16_t dec_indication(const uint8_t *pdu, size_t len, uint16_t *handle, + uint8_t *value, size_t vlen); +uint16_t enc_confirmation(uint8_t *pdu, size_t len); + +uint16_t enc_mtu_req(uint16_t mtu, uint8_t *pdu, size_t len); +uint16_t dec_mtu_req(const uint8_t *pdu, size_t len, uint16_t *mtu); +uint16_t enc_mtu_resp(uint16_t mtu, uint8_t *pdu, size_t len); +uint16_t dec_mtu_resp(const uint8_t *pdu, size_t len, uint16_t *mtu); + +uint16_t enc_prep_write_req(uint16_t handle, uint16_t offset, + const uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len); +uint16_t dec_prep_write_req(const uint8_t *pdu, size_t len, uint16_t *handle, + uint16_t *offset, uint8_t *value, size_t *vlen); +uint16_t enc_prep_write_resp(uint16_t handle, uint16_t offset, + const uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len); +uint16_t dec_prep_write_resp(const uint8_t *pdu, size_t len, uint16_t *handle, + uint16_t *offset, uint8_t *value, + size_t *vlen); +uint16_t enc_exec_write_req(uint8_t flags, uint8_t *pdu, size_t len); +uint16_t dec_exec_write_req(const uint8_t *pdu, size_t len, uint8_t *flags); +uint16_t enc_exec_write_resp(uint8_t *pdu); +uint16_t dec_exec_write_resp(const uint8_t *pdu, size_t len); diff --git a/attrib/gatt-service.c b/attrib/gatt-service.c new file mode 100644 index 0000000..629d9cf --- /dev/null +++ b/attrib/gatt-service.c @@ -0,0 +1,375 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/adapter.h" +#include "src/shared/util.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "attrib/att-database.h" +#include "src/attrib-server.h" +#include "attrib/gatt-service.h" +#include "src/log.h" + +struct gatt_info { + bt_uuid_t uuid; + uint8_t props; + int authentication; + int authorization; + GSList *callbacks; + unsigned int num_attrs; + uint16_t *value_handle; + uint16_t *ccc_handle; +}; + +struct attrib_cb { + attrib_event_t event; + void *fn; + void *user_data; +}; + +static inline void put_uuid_le(const bt_uuid_t *src, void *dst) +{ + if (src->type == BT_UUID16) + put_le16(src->value.u16, dst); + else + /* Convert from 128-bit BE to LE */ + bswap_128(&src->value.u128, dst); +} + +static GSList *parse_opts(gatt_option opt1, va_list args) +{ + gatt_option opt = opt1; + struct gatt_info *info; + struct attrib_cb *cb; + GSList *l = NULL; + + info = g_new0(struct gatt_info, 1); + l = g_slist_append(l, info); + + while (opt != GATT_OPT_INVALID) { + switch (opt) { + case GATT_OPT_CHR_UUID16: + bt_uuid16_create(&info->uuid, va_arg(args, int)); + /* characteristic declaration and value */ + info->num_attrs += 2; + break; + case GATT_OPT_CHR_UUID: + memcpy(&info->uuid, va_arg(args, bt_uuid_t *), + sizeof(bt_uuid_t)); + /* characteristic declaration and value */ + info->num_attrs += 2; + break; + case GATT_OPT_CHR_PROPS: + info->props = va_arg(args, int); + + if (info->props & (GATT_CHR_PROP_NOTIFY | + GATT_CHR_PROP_INDICATE)) + /* client characteristic configuration */ + info->num_attrs += 1; + + /* TODO: "Extended Properties" property requires a + * descriptor, but it is not supported yet. */ + break; + case GATT_OPT_CHR_VALUE_CB: + cb = g_new0(struct attrib_cb, 1); + cb->event = va_arg(args, attrib_event_t); + cb->fn = va_arg(args, void *); + cb->user_data = va_arg(args, void *); + info->callbacks = g_slist_append(info->callbacks, cb); + break; + case GATT_OPT_CHR_VALUE_GET_HANDLE: + info->value_handle = va_arg(args, void *); + break; + case GATT_OPT_CCC_GET_HANDLE: + info->ccc_handle = va_arg(args, void *); + break; + case GATT_OPT_CHR_AUTHENTICATION: + info->authentication = va_arg(args, gatt_option); + break; + case GATT_OPT_CHR_AUTHORIZATION: + info->authorization = va_arg(args, gatt_option); + break; + case GATT_CHR_VALUE_READ: + case GATT_CHR_VALUE_WRITE: + case GATT_CHR_VALUE_BOTH: + case GATT_OPT_INVALID: + default: + error("Invalid option: %d", opt); + } + + opt = va_arg(args, gatt_option); + if (opt == GATT_OPT_CHR_UUID16 || opt == GATT_OPT_CHR_UUID) { + info = g_new0(struct gatt_info, 1); + l = g_slist_append(l, info); + } + } + + return l; +} + +static struct attribute *add_service_declaration(struct btd_adapter *adapter, + uint16_t handle, uint16_t svc, bt_uuid_t *uuid) +{ + bt_uuid_t bt_uuid; + uint8_t atval[16]; + int len; + + put_uuid_le(uuid, &atval[0]); + len = bt_uuid_len(uuid); + + bt_uuid16_create(&bt_uuid, svc); + + return attrib_db_add(adapter, handle, &bt_uuid, ATT_NONE, + ATT_NOT_PERMITTED, atval, len); +} + +static int att_read_req(int authorization, int authentication, uint8_t props) +{ + if (authorization == GATT_CHR_VALUE_READ || + authorization == GATT_CHR_VALUE_BOTH) + return ATT_AUTHORIZATION; + else if (authentication == GATT_CHR_VALUE_READ || + authentication == GATT_CHR_VALUE_BOTH) + return ATT_AUTHENTICATION; + else if (!(props & GATT_CHR_PROP_READ)) + return ATT_NOT_PERMITTED; + + return ATT_NONE; +} + +static int att_write_req(int authorization, int authentication, uint8_t props) +{ + if (authorization == GATT_CHR_VALUE_WRITE || + authorization == GATT_CHR_VALUE_BOTH) + return ATT_AUTHORIZATION; + else if (authentication == GATT_CHR_VALUE_WRITE || + authentication == GATT_CHR_VALUE_BOTH) + return ATT_AUTHENTICATION; + else if (!(props & (GATT_CHR_PROP_WRITE | + GATT_CHR_PROP_WRITE_WITHOUT_RESP))) + return ATT_NOT_PERMITTED; + + return ATT_NONE; +} + +static int find_callback(gconstpointer a, gconstpointer b) +{ + const struct attrib_cb *cb = a; + unsigned int event = GPOINTER_TO_UINT(b); + + return cb->event - event; +} + +static gboolean add_characteristic(struct btd_adapter *adapter, + uint16_t *handle, struct gatt_info *info) +{ + int read_req, write_req; + uint16_t h = *handle; + struct attribute *a; + bt_uuid_t bt_uuid; + uint8_t atval[ATT_MAX_VALUE_LEN]; + GSList *l; + + if ((info->uuid.type != BT_UUID16 && info->uuid.type != BT_UUID128) || + !info->props) { + error("Characteristic UUID or properties are missing"); + return FALSE; + } + + read_req = att_read_req(info->authorization, info->authentication, + info->props); + write_req = att_write_req(info->authorization, info->authentication, + info->props); + + /* TODO: static characteristic values are not supported, therefore a + * callback must be always provided if a read/write property is set */ + if (read_req != ATT_NOT_PERMITTED) { + gpointer reqs = GUINT_TO_POINTER(ATTRIB_READ); + + if (!g_slist_find_custom(info->callbacks, reqs, + find_callback)) { + error("Callback for read required"); + return FALSE; + } + } + + if (write_req != ATT_NOT_PERMITTED) { + gpointer reqs = GUINT_TO_POINTER(ATTRIB_WRITE); + + if (!g_slist_find_custom(info->callbacks, reqs, + find_callback)) { + error("Callback for write required"); + return FALSE; + } + } + + /* characteristic declaration */ + bt_uuid16_create(&bt_uuid, GATT_CHARAC_UUID); + atval[0] = info->props; + put_le16(h + 1, &atval[1]); + put_uuid_le(&info->uuid, &atval[3]); + if (attrib_db_add(adapter, h++, &bt_uuid, ATT_NONE, ATT_NOT_PERMITTED, + atval, 3 + info->uuid.type / 8) == NULL) + return FALSE; + + /* characteristic value */ + a = attrib_db_add(adapter, h++, &info->uuid, read_req, write_req, + NULL, 0); + if (a == NULL) + return FALSE; + + for (l = info->callbacks; l != NULL; l = l->next) { + struct attrib_cb *cb = l->data; + + switch (cb->event) { + case ATTRIB_READ: + a->read_cb = cb->fn; + break; + case ATTRIB_WRITE: + a->write_cb = cb->fn; + break; + } + + a->cb_user_data = cb->user_data; + } + + if (info->value_handle != NULL) + *info->value_handle = a->handle; + + /* client characteristic configuration descriptor */ + if (info->props & (GATT_CHR_PROP_NOTIFY | GATT_CHR_PROP_INDICATE)) { + uint8_t cfg_val[2]; + + bt_uuid16_create(&bt_uuid, GATT_CLIENT_CHARAC_CFG_UUID); + cfg_val[0] = 0x00; + cfg_val[1] = 0x00; + a = attrib_db_add(adapter, h++, &bt_uuid, ATT_NONE, + ATT_AUTHENTICATION, cfg_val, sizeof(cfg_val)); + if (a == NULL) + return FALSE; + + if (info->ccc_handle != NULL) + *info->ccc_handle = a->handle; + } + + *handle = h; + + return TRUE; +} + +static void free_gatt_info(void *data) +{ + struct gatt_info *info = data; + + g_slist_free_full(info->callbacks, g_free); + g_free(info); +} + +static void service_attr_del(struct btd_adapter *adapter, uint16_t start_handle, + uint16_t end_handle) +{ + uint16_t handle; + + /* For a 128-bit category primary service below handle should be checked + * for both non-zero as well as >= 0xffff. As on last iteration the + * handle will turn to 0 from 0xffff and loop will be infinite. + */ + for (handle = start_handle; (handle != 0 && handle <= end_handle); + handle++) { + if (attrib_db_del(adapter, handle) < 0) + error("Can't delete handle 0x%04x", handle); + } +} + +gboolean gatt_service_add(struct btd_adapter *adapter, uint16_t uuid, + bt_uuid_t *svc_uuid, gatt_option opt1, ...) +{ + char uuidstr[MAX_LEN_UUID_STR]; + uint16_t start_handle, h; + unsigned int size; + va_list args; + GSList *chrs, *l; + + bt_uuid_to_string(svc_uuid, uuidstr, MAX_LEN_UUID_STR); + + if (svc_uuid->type != BT_UUID16 && svc_uuid->type != BT_UUID128) { + error("Invalid service uuid: %s", uuidstr); + return FALSE; + } + + va_start(args, opt1); + chrs = parse_opts(opt1, args); + va_end(args); + + /* calculate how many attributes are necessary for this service */ + for (l = chrs, size = 1; l != NULL; l = l->next) { + struct gatt_info *info = l->data; + size += info->num_attrs; + } + + start_handle = attrib_db_find_avail(adapter, svc_uuid, size); + if (start_handle == 0) { + error("Not enough free handles to register service"); + goto fail; + } + + DBG("New service: handle 0x%04x, UUID %s, %d attributes", + start_handle, uuidstr, size); + + /* service declaration */ + h = start_handle; + if (add_service_declaration(adapter, h++, uuid, svc_uuid) == NULL) + goto fail; + + for (l = chrs; l != NULL; l = l->next) { + struct gatt_info *info = l->data; + + DBG("New characteristic: handle 0x%04x", h); + if (!add_characteristic(adapter, &h, info)) { + service_attr_del(adapter, start_handle, h - 1); + goto fail; + } + } + + g_assert(size < USHRT_MAX); + g_assert(h == 0 || (h - start_handle == (uint16_t) size)); + g_slist_free_full(chrs, free_gatt_info); + + return TRUE; + +fail: + g_slist_free_full(chrs, free_gatt_info); + return FALSE; +} diff --git a/attrib/gatt-service.h b/attrib/gatt-service.h new file mode 100644 index 0000000..728d3a8 --- /dev/null +++ b/attrib/gatt-service.h @@ -0,0 +1,57 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +typedef enum { + GATT_OPT_INVALID = 0, + + /* bt_uuid_t* value */ + GATT_OPT_CHR_UUID, + + /* a uint16 value */ + GATT_OPT_CHR_UUID16, + + GATT_OPT_CHR_PROPS, + GATT_OPT_CHR_VALUE_CB, + GATT_OPT_CHR_AUTHENTICATION, + GATT_OPT_CHR_AUTHORIZATION, + + /* Get attribute handle for characteristic value */ + GATT_OPT_CHR_VALUE_GET_HANDLE, + + /* Get handle for ccc attribute */ + GATT_OPT_CCC_GET_HANDLE, + + /* arguments for authentication/authorization */ + GATT_CHR_VALUE_READ, + GATT_CHR_VALUE_WRITE, + GATT_CHR_VALUE_BOTH, +} gatt_option; + +typedef enum { + ATTRIB_READ, + ATTRIB_WRITE, +} attrib_event_t; + +gboolean gatt_service_add(struct btd_adapter *adapter, uint16_t uuid, + bt_uuid_t *svc_uuid, gatt_option opt1, ...); diff --git a/attrib/gatt.c b/attrib/gatt.c new file mode 100644 index 0000000..480f874 --- /dev/null +++ b/attrib/gatt.c @@ -0,0 +1,1260 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "att.h" +#include "gattrib.h" +#include "gatt.h" + +struct discover_primary { + int ref; + GAttrib *attrib; + unsigned int id; + bt_uuid_t uuid; + uint16_t start; + GSList *primaries; + gatt_cb_t cb; + void *user_data; +}; + +/* Used for the Included Services Discovery (ISD) procedure */ +struct included_discovery { + GAttrib *attrib; + unsigned int id; + int refs; + int err; + uint16_t start_handle; + uint16_t end_handle; + GSList *includes; + gatt_cb_t cb; + void *user_data; +}; + +struct included_uuid_query { + struct included_discovery *isd; + struct gatt_included *included; +}; + +struct discover_char { + int ref; + GAttrib *attrib; + unsigned int id; + bt_uuid_t *uuid; + uint16_t end; + uint16_t start; + GSList *characteristics; + gatt_cb_t cb; + void *user_data; +}; + +struct discover_desc { + int ref; + GAttrib *attrib; + unsigned int id; + bt_uuid_t *uuid; + uint16_t start; + uint16_t end; + GSList *descriptors; + gatt_cb_t cb; + void *user_data; +}; + +static void discover_primary_unref(void *data) +{ + struct discover_primary *dp = data; + + dp->ref--; + + if (dp->ref > 0) + return; + + g_slist_free_full(dp->primaries, g_free); + g_attrib_unref(dp->attrib); + g_free(dp); +} + +static struct discover_primary *discover_primary_ref( + struct discover_primary *dp) +{ + dp->ref++; + + return dp; +} + +static struct included_discovery *isd_ref(struct included_discovery *isd) +{ + __sync_fetch_and_add(&isd->refs, 1); + + return isd; +} + +static void isd_unref(struct included_discovery *isd) +{ + if (__sync_sub_and_fetch(&isd->refs, 1) > 0) + return; + + if (isd->err) + isd->cb(isd->err, NULL, isd->user_data); + else + isd->cb(isd->err, isd->includes, isd->user_data); + + g_slist_free_full(isd->includes, g_free); + g_attrib_unref(isd->attrib); + g_free(isd); +} + +static void discover_char_unref(void *data) +{ + struct discover_char *dc = data; + + dc->ref--; + + if (dc->ref > 0) + return; + + g_slist_free_full(dc->characteristics, g_free); + g_attrib_unref(dc->attrib); + g_free(dc->uuid); + g_free(dc); +} + +static struct discover_char *discover_char_ref(struct discover_char *dc) +{ + dc->ref++; + + return dc; +} + +static void discover_desc_unref(void *data) +{ + struct discover_desc *dd = data; + + dd->ref--; + + if (dd->ref > 0) + return; + + g_slist_free_full(dd->descriptors, g_free); + g_attrib_unref(dd->attrib); + g_free(dd->uuid); + g_free(dd); +} + +static struct discover_desc *discover_desc_ref(struct discover_desc *dd) +{ + dd->ref++; + + return dd; +} + +static void put_uuid_le(const bt_uuid_t *uuid, void *dst) +{ + if (uuid->type == BT_UUID16) + put_le16(uuid->value.u16, dst); + else + /* Convert from 128-bit BE to LE */ + bswap_128(&uuid->value.u128, dst); +} + +static void get_uuid128(uint8_t type, const void *val, bt_uuid_t *uuid) +{ + if (type == BT_UUID16) { + bt_uuid_t uuid16; + + bt_uuid16_create(&uuid16, get_le16(val)); + bt_uuid_to_uuid128(&uuid16, uuid); + } else { + uint128_t u128; + + /* Convert from 128-bit LE to BE */ + bswap_128(val, &u128); + bt_uuid128_create(uuid, u128); + } +} + +static guint16 encode_discover_primary(uint16_t start, uint16_t end, + bt_uuid_t *uuid, uint8_t *pdu, size_t len) +{ + bt_uuid_t prim; + guint16 plen; + + bt_uuid16_create(&prim, GATT_PRIM_SVC_UUID); + + if (uuid == NULL) { + /* Discover all primary services */ + plen = enc_read_by_grp_req(start, end, &prim, pdu, len); + } else { + uint8_t value[16]; + size_t vlen; + + /* Discover primary service by service UUID */ + put_uuid_le(uuid, value); + vlen = bt_uuid_len(uuid); + + plen = enc_find_by_type_req(start, end, &prim, value, vlen, + pdu, len); + } + + return plen; +} + +static void primary_by_uuid_cb(guint8 status, const guint8 *ipdu, + guint16 iplen, gpointer user_data) + +{ + struct discover_primary *dp = user_data; + GSList *ranges, *last; + struct att_range *range; + uint8_t *buf; + guint16 oplen; + int err = 0; + size_t buflen; + + if (status) { + err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status; + goto done; + } + + ranges = dec_find_by_type_resp(ipdu, iplen); + if (ranges == NULL) + goto done; + + dp->primaries = g_slist_concat(dp->primaries, ranges); + + last = g_slist_last(ranges); + range = last->data; + + if (range->end == 0xffff) + goto done; + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (range->end < dp->start) { + err = ATT_ECODE_UNLIKELY; + goto done; + } + + dp->start = range->end + 1; + + buf = g_attrib_get_buffer(dp->attrib, &buflen); + oplen = encode_discover_primary(dp->start, 0xffff, &dp->uuid, + buf, buflen); + + if (oplen == 0) + goto done; + + g_attrib_send(dp->attrib, dp->id, buf, oplen, primary_by_uuid_cb, + discover_primary_ref(dp), discover_primary_unref); + return; + +done: + dp->cb(err, dp->primaries, dp->user_data); +} + +static void primary_all_cb(guint8 status, const guint8 *ipdu, guint16 iplen, + gpointer user_data) +{ + struct discover_primary *dp = user_data; + struct att_data_list *list; + unsigned int i, err; + uint16_t start, end; + uint8_t type; + + if (status) { + err = status == ATT_ECODE_ATTR_NOT_FOUND ? 0 : status; + goto done; + } + + list = dec_read_by_grp_resp(ipdu, iplen); + if (list == NULL) { + err = ATT_ECODE_IO; + goto done; + } + + if (list->len == 6) + type = BT_UUID16; + else if (list->len == 20) + type = BT_UUID128; + else { + att_data_list_free(list); + err = ATT_ECODE_INVALID_PDU; + goto done; + } + + for (i = 0, end = 0; i < list->num; i++) { + const uint8_t *data = list->data[i]; + struct gatt_primary *primary; + bt_uuid_t uuid128; + + start = get_le16(&data[0]); + end = get_le16(&data[2]); + + get_uuid128(type, &data[4], &uuid128); + + primary = g_try_new0(struct gatt_primary, 1); + if (!primary) { + att_data_list_free(list); + err = ATT_ECODE_INSUFF_RESOURCES; + goto done; + } + primary->range.start = start; + primary->range.end = end; + bt_uuid_to_string(&uuid128, primary->uuid, sizeof(primary->uuid)); + dp->primaries = g_slist_append(dp->primaries, primary); + } + + att_data_list_free(list); + err = 0; + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (end < dp->start) { + err = ATT_ECODE_UNLIKELY; + goto done; + } + + dp->start = end + 1; + + if (end != 0xffff) { + size_t buflen; + uint8_t *buf = g_attrib_get_buffer(dp->attrib, &buflen); + guint16 oplen = encode_discover_primary(dp->start, 0xffff, NULL, + buf, buflen); + + + g_attrib_send(dp->attrib, dp->id, buf, oplen, primary_all_cb, + discover_primary_ref(dp), + discover_primary_unref); + + return; + } + +done: + dp->cb(err, dp->primaries, dp->user_data); +} + +guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data) +{ + struct discover_primary *dp; + size_t buflen; + uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); + GAttribResultFunc cb; + guint16 plen; + + plen = encode_discover_primary(0x0001, 0xffff, uuid, buf, buflen); + if (plen == 0) + return 0; + + dp = g_try_new0(struct discover_primary, 1); + if (dp == NULL) + return 0; + + dp->attrib = g_attrib_ref(attrib); + dp->cb = func; + dp->user_data = user_data; + dp->start = 0x0001; + + if (uuid) { + dp->uuid = *uuid; + cb = primary_by_uuid_cb; + } else + cb = primary_all_cb; + + dp->id = g_attrib_send(attrib, 0, buf, plen, cb, + discover_primary_ref(dp), + discover_primary_unref); + + return dp->id; +} + +static void resolve_included_uuid_cb(uint8_t status, const uint8_t *pdu, + uint16_t len, gpointer user_data) +{ + struct included_uuid_query *query = user_data; + struct included_discovery *isd = query->isd; + struct gatt_included *incl = query->included; + unsigned int err = status; + bt_uuid_t uuid128; + size_t buflen; + uint8_t *buf; + + if (err) + goto done; + + buf = g_attrib_get_buffer(isd->attrib, &buflen); + if (dec_read_resp(pdu, len, buf, buflen) != 16) { + err = ATT_ECODE_IO; + goto done; + } + + get_uuid128(BT_UUID128, buf, &uuid128); + + bt_uuid_to_string(&uuid128, incl->uuid, sizeof(incl->uuid)); + isd->includes = g_slist_append(isd->includes, incl); + query->included = NULL; + +done: + if (isd->err == 0) + isd->err = err; +} + +static void inc_query_free(void *data) +{ + struct included_uuid_query *query = data; + + isd_unref(query->isd); + g_free(query->included); + g_free(query); +} + +static guint resolve_included_uuid(struct included_discovery *isd, + struct gatt_included *incl) +{ + struct included_uuid_query *query; + size_t buflen; + uint8_t *buf = g_attrib_get_buffer(isd->attrib, &buflen); + guint16 oplen = enc_read_req(incl->range.start, buf, buflen); + + query = g_new0(struct included_uuid_query, 1); + query->isd = isd_ref(isd); + query->included = incl; + + return g_attrib_send(isd->attrib, query->isd->id, buf, oplen, + resolve_included_uuid_cb, query, + inc_query_free); +} + +static struct gatt_included *included_from_buf(const uint8_t *buf, gsize len) +{ + struct gatt_included *incl = g_new0(struct gatt_included, 1); + + incl->handle = get_le16(&buf[0]); + incl->range.start = get_le16(&buf[2]); + incl->range.end = get_le16(&buf[4]); + + if (len == 8) { + bt_uuid_t uuid128; + + get_uuid128(BT_UUID16, &buf[6], &uuid128); + bt_uuid_to_string(&uuid128, incl->uuid, sizeof(incl->uuid)); + } + + return incl; +} + +static void find_included_cb(uint8_t status, const uint8_t *pdu, uint16_t len, + gpointer user_data); + +static guint find_included(struct included_discovery *isd, uint16_t start) +{ + bt_uuid_t uuid; + size_t buflen; + uint8_t *buf = g_attrib_get_buffer(isd->attrib, &buflen); + guint16 oplen; + + bt_uuid16_create(&uuid, GATT_INCLUDE_UUID); + oplen = enc_read_by_type_req(start, isd->end_handle, &uuid, + buf, buflen); + + /* If id != 0 it means we are in the middle of include search */ + if (isd->id) + return g_attrib_send(isd->attrib, isd->id, buf, oplen, + find_included_cb, isd_ref(isd), + (GDestroyNotify) isd_unref); + + /* This is first call from the gattrib user */ + isd->id = g_attrib_send(isd->attrib, 0, buf, oplen, find_included_cb, + isd_ref(isd), (GDestroyNotify) isd_unref); + + return isd->id; +} + +static void find_included_cb(uint8_t status, const uint8_t *pdu, uint16_t len, + gpointer user_data) +{ + struct included_discovery *isd = user_data; + uint16_t last_handle = isd->end_handle; + unsigned int err = status; + struct att_data_list *list; + int i; + + if (err == ATT_ECODE_ATTR_NOT_FOUND) + err = 0; + + if (status) + goto done; + + list = dec_read_by_type_resp(pdu, len); + if (list == NULL) { + err = ATT_ECODE_IO; + goto done; + } + + if (list->len != 6 && list->len != 8) { + err = ATT_ECODE_IO; + att_data_list_free(list); + goto done; + } + + for (i = 0; i < list->num; i++) { + struct gatt_included *incl; + + incl = included_from_buf(list->data[i], list->len); + last_handle = incl->handle; + + /* 128 bit UUID, needs resolving */ + if (list->len == 6) { + resolve_included_uuid(isd, incl); + continue; + } + + isd->includes = g_slist_append(isd->includes, incl); + } + + att_data_list_free(list); + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (last_handle < isd->start_handle) { + isd->err = ATT_ECODE_UNLIKELY; + goto done; + } + + isd->start_handle = last_handle + 1; + + if (last_handle < isd->end_handle) + find_included(isd, isd->start_handle); + +done: + if (isd->err == 0) + isd->err = err; +} + +unsigned int gatt_find_included(GAttrib *attrib, uint16_t start, uint16_t end, + gatt_cb_t func, gpointer user_data) +{ + struct included_discovery *isd; + + isd = g_new0(struct included_discovery, 1); + isd->attrib = g_attrib_ref(attrib); + isd->start_handle = start; + isd->end_handle = end; + isd->cb = func; + isd->user_data = user_data; + + return find_included(isd, start); +} + +static void char_discovered_cb(guint8 status, const guint8 *ipdu, guint16 iplen, + gpointer user_data) +{ + struct discover_char *dc = user_data; + struct att_data_list *list; + unsigned int i, err = 0; + uint16_t last = 0; + uint8_t type; + + /* We have all the characteristic now, lets send it up */ + if (status == ATT_ECODE_ATTR_NOT_FOUND) { + err = dc->characteristics ? 0 : status; + goto done; + } + + if (status) { + err = status; + goto done; + } + + list = dec_read_by_type_resp(ipdu, iplen); + if (list == NULL) { + err = ATT_ECODE_IO; + goto done; + } + + if (list->len == 7) + type = BT_UUID16; + else + type = BT_UUID128; + + for (i = 0; i < list->num; i++) { + uint8_t *value = list->data[i]; + struct gatt_char *chars; + bt_uuid_t uuid128; + + last = get_le16(value); + + get_uuid128(type, &value[5], &uuid128); + + if (dc->uuid && bt_uuid_cmp(dc->uuid, &uuid128)) + continue; + + chars = g_try_new0(struct gatt_char, 1); + if (!chars) { + att_data_list_free(list); + err = ATT_ECODE_INSUFF_RESOURCES; + goto done; + } + + chars->handle = last; + chars->properties = value[2]; + chars->value_handle = get_le16(&value[3]); + bt_uuid_to_string(&uuid128, chars->uuid, sizeof(chars->uuid)); + dc->characteristics = g_slist_append(dc->characteristics, + chars); + } + + att_data_list_free(list); + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (last < dc->start) { + err = ATT_ECODE_UNLIKELY; + goto done; + } + + dc->start = last + 1; + + if (last != 0 && (dc->start < dc->end)) { + bt_uuid_t uuid; + guint16 oplen; + size_t buflen; + uint8_t *buf; + + buf = g_attrib_get_buffer(dc->attrib, &buflen); + + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + + oplen = enc_read_by_type_req(dc->start, dc->end, &uuid, buf, + buflen); + + if (oplen == 0) + return; + + g_attrib_send(dc->attrib, dc->id, buf, oplen, + char_discovered_cb, discover_char_ref(dc), + discover_char_unref); + + return; + } + +done: + dc->cb(err, dc->characteristics, dc->user_data); +} + +guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data) +{ + size_t buflen; + uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); + struct discover_char *dc; + bt_uuid_t type_uuid; + guint16 plen; + + bt_uuid16_create(&type_uuid, GATT_CHARAC_UUID); + + plen = enc_read_by_type_req(start, end, &type_uuid, buf, buflen); + if (plen == 0) + return 0; + + dc = g_try_new0(struct discover_char, 1); + if (dc == NULL) + return 0; + + dc->attrib = g_attrib_ref(attrib); + dc->cb = func; + dc->user_data = user_data; + dc->end = end; + dc->start = start; + dc->uuid = g_memdup(uuid, sizeof(bt_uuid_t)); + + dc->id = g_attrib_send(attrib, 0, buf, plen, char_discovered_cb, + discover_char_ref(dc), discover_char_unref); + + return dc->id; +} + +guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end, + bt_uuid_t *uuid, GAttribResultFunc func, + gpointer user_data) +{ + size_t buflen; + uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); + guint16 plen; + + plen = enc_read_by_type_req(start, end, uuid, buf, buflen); + if (plen == 0) + return 0; + + return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL); +} + +struct read_long_data { + GAttrib *attrib; + GAttribResultFunc func; + gpointer user_data; + guint8 *buffer; + guint16 size; + guint16 handle; + guint id; + int ref; +}; + +static void read_long_destroy(gpointer user_data) +{ + struct read_long_data *long_read = user_data; + + if (__sync_sub_and_fetch(&long_read->ref, 1) > 0) + return; + + g_attrib_unref(long_read->attrib); + + if (long_read->buffer != NULL) + g_free(long_read->buffer); + + g_free(long_read); +} + +static void read_blob_helper(guint8 status, const guint8 *rpdu, guint16 rlen, + gpointer user_data) +{ + struct read_long_data *long_read = user_data; + uint8_t *buf; + size_t buflen; + guint8 *tmp; + guint16 plen; + guint id; + + if (status != 0 || rlen == 1) { + status = 0; + goto done; + } + + tmp = g_try_realloc(long_read->buffer, long_read->size + rlen - 1); + + if (tmp == NULL) { + status = ATT_ECODE_INSUFF_RESOURCES; + goto done; + } + + memcpy(&tmp[long_read->size], &rpdu[1], rlen - 1); + long_read->buffer = tmp; + long_read->size += rlen - 1; + + buf = g_attrib_get_buffer(long_read->attrib, &buflen); + if (rlen < buflen) + goto done; + + plen = enc_read_blob_req(long_read->handle, long_read->size - 1, + buf, buflen); + id = g_attrib_send(long_read->attrib, long_read->id, buf, plen, + read_blob_helper, long_read, read_long_destroy); + + if (id != 0) { + __sync_fetch_and_add(&long_read->ref, 1); + return; + } + + status = ATT_ECODE_IO; + +done: + long_read->func(status, long_read->buffer, long_read->size, + long_read->user_data); +} + +static void read_char_helper(guint8 status, const guint8 *rpdu, + guint16 rlen, gpointer user_data) +{ + struct read_long_data *long_read = user_data; + size_t buflen; + uint8_t *buf = g_attrib_get_buffer(long_read->attrib, &buflen); + guint16 plen; + guint id; + + if (status != 0 || rlen < buflen) + goto done; + + long_read->buffer = g_malloc(rlen); + if (long_read->buffer == NULL) { + status = ATT_ECODE_INSUFF_RESOURCES; + goto done; + } + + memcpy(long_read->buffer, rpdu, rlen); + long_read->size = rlen; + + plen = enc_read_blob_req(long_read->handle, rlen - 1, buf, buflen); + + id = g_attrib_send(long_read->attrib, long_read->id, buf, plen, + read_blob_helper, long_read, read_long_destroy); + if (id != 0) { + __sync_fetch_and_add(&long_read->ref, 1); + return; + } + + status = ATT_ECODE_IO; + +done: + long_read->func(status, rpdu, rlen, long_read->user_data); +} + +guint gatt_read_char(GAttrib *attrib, uint16_t handle, GAttribResultFunc func, + gpointer user_data) +{ + uint8_t *buf; + size_t buflen; + guint16 plen; + guint id; + struct read_long_data *long_read; + + long_read = g_try_new0(struct read_long_data, 1); + + if (long_read == NULL) + return 0; + + long_read->attrib = g_attrib_ref(attrib); + long_read->func = func; + long_read->user_data = user_data; + long_read->handle = handle; + + buf = g_attrib_get_buffer(attrib, &buflen); + plen = enc_read_req(handle, buf, buflen); + id = g_attrib_send(attrib, 0, buf, plen, read_char_helper, + long_read, read_long_destroy); + if (id == 0) { + g_attrib_unref(long_read->attrib); + g_free(long_read); + } else { + __sync_fetch_and_add(&long_read->ref, 1); + long_read->id = id; + } + + return id; +} + +struct write_long_data { + GAttrib *attrib; + GAttribResultFunc func; + gpointer user_data; + guint16 handle; + uint16_t offset; + uint8_t *value; + size_t vlen; +}; + +static guint execute_write(GAttrib *attrib, uint8_t flags, + GAttribResultFunc func, gpointer user_data) +{ + uint8_t *buf; + size_t buflen; + guint16 plen; + + buf = g_attrib_get_buffer(attrib, &buflen); + plen = enc_exec_write_req(flags, buf, buflen); + if (plen == 0) + return 0; + + return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL); +} + +static guint prepare_write(struct write_long_data *long_write); + +static void prepare_write_cb(guint8 status, const guint8 *rpdu, guint16 rlen, + gpointer user_data) +{ + struct write_long_data *long_write = user_data; + + if (status != 0) { + long_write->func(status, rpdu, rlen, long_write->user_data); + return; + } + + /* Skip Prepare Write Response PDU header (5 bytes) */ + long_write->offset += rlen - 5; + + if (long_write->offset == long_write->vlen) { + execute_write(long_write->attrib, ATT_WRITE_ALL_PREP_WRITES, + long_write->func, long_write->user_data); + g_free(long_write->value); + g_free(long_write); + + return; + } + + prepare_write(long_write); +} + +static guint prepare_write(struct write_long_data *long_write) +{ + GAttrib *attrib = long_write->attrib; + uint16_t handle = long_write->handle; + uint16_t offset = long_write->offset; + uint8_t *buf, *value = long_write->value + offset; + size_t buflen, vlen = long_write->vlen - offset; + guint16 plen; + + buf = g_attrib_get_buffer(attrib, &buflen); + + plen = enc_prep_write_req(handle, offset, value, vlen, buf, buflen); + if (plen == 0) + return 0; + + return g_attrib_send(attrib, 0, buf, plen, prepare_write_cb, long_write, + NULL); +} + +guint gatt_write_char(GAttrib *attrib, uint16_t handle, const uint8_t *value, + size_t vlen, GAttribResultFunc func, gpointer user_data) +{ + uint8_t *buf; + size_t buflen; + struct write_long_data *long_write; + + buf = g_attrib_get_buffer(attrib, &buflen); + + /* Use Write Request if payload fits on a single transfer, including 3 + * bytes for the header. */ + if (vlen <= buflen - 3) { + uint16_t plen; + + plen = enc_write_req(handle, value, vlen, buf, buflen); + if (plen == 0) + return 0; + + return g_attrib_send(attrib, 0, buf, plen, func, user_data, + NULL); + } + + /* Write Long Characteristic Values */ + long_write = g_try_new0(struct write_long_data, 1); + if (long_write == NULL) + return 0; + + long_write->attrib = attrib; + long_write->func = func; + long_write->user_data = user_data; + long_write->handle = handle; + long_write->value = g_memdup(value, vlen); + long_write->vlen = vlen; + + return prepare_write(long_write); +} + +guint gatt_execute_write(GAttrib *attrib, uint8_t flags, + GAttribResultFunc func, gpointer user_data) +{ + return execute_write(attrib, flags, func, user_data); +} + +guint gatt_reliable_write_char(GAttrib *attrib, uint16_t handle, + const uint8_t *value, size_t vlen, + GAttribResultFunc func, + gpointer user_data) +{ + uint8_t *buf; + guint16 plen; + size_t buflen; + + buf = g_attrib_get_buffer(attrib, &buflen); + + plen = enc_prep_write_req(handle, 0, value, vlen, buf, buflen); + if (!plen) + return 0; + + return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL); +} + +guint gatt_exchange_mtu(GAttrib *attrib, uint16_t mtu, GAttribResultFunc func, + gpointer user_data) +{ + uint8_t *buf; + size_t buflen; + guint16 plen; + + buf = g_attrib_get_buffer(attrib, &buflen); + plen = enc_mtu_req(mtu, buf, buflen); + return g_attrib_send(attrib, 0, buf, plen, func, user_data, NULL); +} + +static void desc_discovered_cb(guint8 status, const guint8 *ipdu, + guint16 iplen, gpointer user_data) +{ + struct discover_desc *dd = user_data; + struct att_data_list *list; + unsigned int i, err = 0; + guint8 format; + uint16_t last = 0xffff; + uint8_t type; + gboolean uuid_found = FALSE; + + if (status == ATT_ECODE_ATTR_NOT_FOUND) { + err = dd->descriptors ? 0 : status; + goto done; + } + + if (status) { + err = status; + goto done; + } + + list = dec_find_info_resp(ipdu, iplen, &format); + if (!list) { + err = ATT_ECODE_IO; + goto done; + } + + if (format == ATT_FIND_INFO_RESP_FMT_16BIT) + type = BT_UUID16; + else + type = BT_UUID128; + + for (i = 0; i < list->num; i++) { + uint8_t *value = list->data[i]; + struct gatt_desc *desc; + bt_uuid_t uuid128; + + last = get_le16(value); + + get_uuid128(type, &value[2], &uuid128); + + if (dd->uuid) { + if (bt_uuid_cmp(dd->uuid, &uuid128)) + continue; + else + uuid_found = TRUE; + } + + desc = g_try_new0(struct gatt_desc, 1); + if (!desc) { + att_data_list_free(list); + err = ATT_ECODE_INSUFF_RESOURCES; + goto done; + } + + bt_uuid_to_string(&uuid128, desc->uuid, sizeof(desc->uuid)); + desc->handle = last; + + if (type == BT_UUID16) + desc->uuid16 = get_le16(&value[2]); + + dd->descriptors = g_slist_append(dd->descriptors, desc); + + if (uuid_found) + break; + } + + att_data_list_free(list); + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (last < dd->start) { + err = ATT_ECODE_UNLIKELY; + goto done; + } + + dd->start = last + 1; + + if (last < dd->end && !uuid_found) { + guint16 oplen; + size_t buflen; + uint8_t *buf; + + buf = g_attrib_get_buffer(dd->attrib, &buflen); + + oplen = enc_find_info_req(dd->start, dd->end, buf, buflen); + if (oplen == 0) + return; + + g_attrib_send(dd->attrib, dd->id, buf, oplen, + desc_discovered_cb, discover_desc_ref(dd), + discover_desc_unref); + + return; + } + +done: + dd->cb(err, dd->descriptors, dd->user_data); +} + +guint gatt_discover_desc(GAttrib *attrib, uint16_t start, uint16_t end, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data) +{ + size_t buflen; + uint8_t *buf = g_attrib_get_buffer(attrib, &buflen); + struct discover_desc *dd; + guint16 plen; + + plen = enc_find_info_req(start, end, buf, buflen); + if (plen == 0) + return 0; + + dd = g_try_new0(struct discover_desc, 1); + if (dd == NULL) + return 0; + + dd->attrib = g_attrib_ref(attrib); + dd->cb = func; + dd->user_data = user_data; + dd->start = start; + dd->end = end; + dd->uuid = g_memdup(uuid, sizeof(bt_uuid_t)); + + dd->id = g_attrib_send(attrib, 0, buf, plen, desc_discovered_cb, + discover_desc_ref(dd), discover_desc_unref); + + return dd->id; +} + +guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, const uint8_t *value, + int vlen, GDestroyNotify notify, gpointer user_data) +{ + uint8_t *buf; + size_t buflen; + guint16 plen; + + buf = g_attrib_get_buffer(attrib, &buflen); + plen = enc_write_cmd(handle, value, vlen, buf, buflen); + return g_attrib_send(attrib, 0, buf, plen, NULL, user_data, notify); +} + +guint gatt_signed_write_cmd(GAttrib *attrib, uint16_t handle, + const uint8_t *value, int vlen, + struct bt_crypto *crypto, + const uint8_t csrk[16], + uint32_t sign_cnt, + GDestroyNotify notify, + gpointer user_data) +{ + uint8_t *buf; + size_t buflen; + guint16 plen; + + buf = g_attrib_get_buffer(attrib, &buflen); + plen = enc_signed_write_cmd(handle, value, vlen, crypto, csrk, sign_cnt, + buf, buflen); + if (plen == 0) + return 0; + + return g_attrib_send(attrib, 0, buf, plen, NULL, user_data, notify); +} + +static sdp_data_t *proto_seq_find(sdp_list_t *proto_list) +{ + sdp_list_t *list; + uuid_t proto; + + sdp_uuid16_create(&proto, ATT_UUID); + + for (list = proto_list; list; list = list->next) { + sdp_list_t *p; + for (p = list->data; p; p = p->next) { + sdp_data_t *seq = p->data; + if (seq && seq->dtd == SDP_UUID16 && + sdp_uuid16_cmp(&proto, &seq->val.uuid) == 0) + return seq->next; + } + } + + return NULL; +} + +static gboolean parse_proto_params(sdp_list_t *proto_list, uint16_t *psm, + uint16_t *start, uint16_t *end) +{ + sdp_data_t *seq1, *seq2; + + if (psm) + *psm = sdp_get_proto_port(proto_list, L2CAP_UUID); + + /* Getting start and end handle */ + seq1 = proto_seq_find(proto_list); + if (!seq1 || seq1->dtd != SDP_UINT16) + return FALSE; + + seq2 = seq1->next; + if (!seq2 || seq2->dtd != SDP_UINT16) + return FALSE; + + if (start) + *start = seq1->val.uint16; + + if (end) + *end = seq2->val.uint16; + + return TRUE; +} + +gboolean gatt_parse_record(const sdp_record_t *rec, + uuid_t *prim_uuid, uint16_t *psm, + uint16_t *start, uint16_t *end) +{ + sdp_list_t *list; + uuid_t uuid; + gboolean ret; + + if (sdp_get_service_classes(rec, &list) < 0) + return FALSE; + + memcpy(&uuid, list->data, sizeof(uuid)); + sdp_list_free(list, free); + + if (sdp_get_access_protos(rec, &list) < 0) + return FALSE; + + ret = parse_proto_params(list, psm, start, end); + + sdp_list_foreach(list, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(list, NULL); + + /* FIXME: replace by bt_uuid_t after uuid_t/sdp code cleanup */ + if (ret && prim_uuid) + memcpy(prim_uuid, &uuid, sizeof(uuid_t)); + + return ret; +} diff --git a/attrib/gatt.h b/attrib/gatt.h new file mode 100644 index 0000000..63b2940 --- /dev/null +++ b/attrib/gatt.h @@ -0,0 +1,122 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* + * GATT Characteristic Property bit field + * Reference: Core SPEC 4.1 page 2183 (Table 3.5: Characteristic Properties + * bit field) defines how the Characteristic Value can be used, or how the + * characteristic descriptors (see Section 3.3.3 - page 2184) can be accessed. + * In the core spec, regular properties are included in the characteristic + * declaration, and the extended properties are defined as descriptor. + */ + +#define GATT_CHR_PROP_BROADCAST 0x01 +#define GATT_CHR_PROP_READ 0x02 +#define GATT_CHR_PROP_WRITE_WITHOUT_RESP 0x04 +#define GATT_CHR_PROP_WRITE 0x08 +#define GATT_CHR_PROP_NOTIFY 0x10 +#define GATT_CHR_PROP_INDICATE 0x20 +#define GATT_CHR_PROP_AUTH 0x40 +#define GATT_CHR_PROP_EXT_PROP 0x80 + +/* Client Characteristic Configuration bit field */ +#define GATT_CLIENT_CHARAC_CFG_NOTIF_BIT 0x0001 +#define GATT_CLIENT_CHARAC_CFG_IND_BIT 0x0002 + +typedef void (*gatt_cb_t) (uint8_t status, GSList *l, void *user_data); + +struct gatt_primary { + char uuid[MAX_LEN_UUID_STR + 1]; + gboolean changed; + struct att_range range; +}; + +struct gatt_included { + char uuid[MAX_LEN_UUID_STR + 1]; + uint16_t handle; + struct att_range range; +}; + +struct gatt_char { + char uuid[MAX_LEN_UUID_STR + 1]; + uint16_t handle; + uint8_t properties; + uint16_t value_handle; +}; + +struct gatt_desc { + char uuid[MAX_LEN_UUID_STR + 1]; + uint16_t handle; + uint16_t uuid16; +}; + +guint gatt_discover_primary(GAttrib *attrib, bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data); + +unsigned int gatt_find_included(GAttrib *attrib, uint16_t start, uint16_t end, + gatt_cb_t func, gpointer user_data); + +guint gatt_discover_char(GAttrib *attrib, uint16_t start, uint16_t end, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data); + +guint gatt_read_char(GAttrib *attrib, uint16_t handle, GAttribResultFunc func, + gpointer user_data); + +guint gatt_write_char(GAttrib *attrib, uint16_t handle, const uint8_t *value, + size_t vlen, GAttribResultFunc func, + gpointer user_data); + +guint gatt_discover_desc(GAttrib *attrib, uint16_t start, uint16_t end, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data); + +guint gatt_reliable_write_char(GAttrib *attrib, uint16_t handle, + const uint8_t *value, size_t vlen, + GAttribResultFunc func, + gpointer user_data); + +guint gatt_execute_write(GAttrib *attrib, uint8_t flags, + GAttribResultFunc func, gpointer user_data); + +guint gatt_write_cmd(GAttrib *attrib, uint16_t handle, const uint8_t *value, + int vlen, GDestroyNotify notify, gpointer user_data); + +guint gatt_signed_write_cmd(GAttrib *attrib, uint16_t handle, + const uint8_t *value, int vlen, + struct bt_crypto *crypto, + const uint8_t csrk[16], + uint32_t sign_cnt, + GDestroyNotify notify, + gpointer user_data); +guint gatt_read_char_by_uuid(GAttrib *attrib, uint16_t start, uint16_t end, + bt_uuid_t *uuid, GAttribResultFunc func, + gpointer user_data); + +guint gatt_exchange_mtu(GAttrib *attrib, uint16_t mtu, GAttribResultFunc func, + gpointer user_data); + +gboolean gatt_parse_record(const sdp_record_t *rec, + uuid_t *prim_uuid, uint16_t *psm, + uint16_t *start, uint16_t *end); diff --git a/attrib/gattrib.c b/attrib/gattrib.c new file mode 100644 index 0000000..57ca015 --- /dev/null +++ b/attrib/gattrib.c @@ -0,0 +1,488 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" + +#include "btio/btio.h" +#include "src/log.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "attrib/gattrib.h" + +struct _GAttrib { + int ref_count; + struct bt_att *att; + GIOChannel *io; + GDestroyNotify destroy; + gpointer destroy_user_data; + struct queue *callbacks; + uint8_t *buf; + int buflen; + struct queue *track_ids; +}; + +struct id_pair { + unsigned int org_id; + unsigned int pend_id; +}; + +struct attrib_callbacks { + struct id_pair *id; + GAttribResultFunc result_func; + GAttribNotifyFunc notify_func; + GDestroyNotify destroy_func; + gpointer user_data; + GAttrib *parent; + uint16_t notify_handle; +}; + +static bool find_with_org_id(const void *data, const void *user_data) +{ + const struct id_pair *p = data; + unsigned int orig_id = PTR_TO_UINT(user_data); + + return (p->org_id == orig_id); +} + +static struct id_pair *store_id(GAttrib *attrib, unsigned int org_id, + unsigned int pend_id) +{ + struct id_pair *t; + + t = new0(struct id_pair, 1); + if (!t) + return NULL; + + t->org_id = org_id; + t->pend_id = pend_id; + + if (queue_push_tail(attrib->track_ids, t)) + return t; + + return NULL; +} + +GAttrib *g_attrib_new(GIOChannel *io, guint16 mtu, bool ext_signed) +{ + gint fd; + GAttrib *attr; + + if (!io) + return NULL; + + fd = g_io_channel_unix_get_fd(io); + attr = new0(GAttrib, 1); + if (!attr) + return NULL; + + g_io_channel_ref(io); + attr->io = io; + + attr->att = bt_att_new(fd, ext_signed); + if (!attr->att) + goto fail; + + bt_att_set_close_on_unref(attr->att, true); + g_io_channel_set_close_on_unref(io, FALSE); + + if (!bt_att_set_mtu(attr->att, mtu)) + goto fail; + + attr->buf = malloc0(mtu); + attr->buflen = mtu; + if (!attr->buf) + goto fail; + + attr->callbacks = queue_new(); + if (!attr->callbacks) + goto fail; + + attr->track_ids = queue_new(); + if (!attr->track_ids) + goto fail; + + return g_attrib_ref(attr); + +fail: + free(attr->buf); + bt_att_unref(attr->att); + g_io_channel_unref(io); + free(attr); + return NULL; +} + +GAttrib *g_attrib_ref(GAttrib *attrib) +{ + if (!attrib) + return NULL; + + __sync_fetch_and_add(&attrib->ref_count, 1); + + DBG("%p: g_attrib_ref=%d ", attrib, attrib->ref_count); + + return attrib; +} + +static void attrib_callbacks_destroy(void *data) +{ + struct attrib_callbacks *cb = data; + + if (cb->destroy_func) + cb->destroy_func(cb->user_data); + + if (queue_remove(cb->parent->track_ids, cb->id)) + free(cb->id); + + free(data); +} + +static void attrib_callbacks_remove(void *data) +{ + struct attrib_callbacks *cb = data; + + if (!data || !queue_remove(cb->parent->callbacks, data)) + return; + + attrib_callbacks_destroy(data); +} + +void g_attrib_unref(GAttrib *attrib) +{ + if (!attrib) + return; + + DBG("%p: g_attrib_unref=%d ", attrib, attrib->ref_count - 1); + + if (__sync_sub_and_fetch(&attrib->ref_count, 1)) + return; + + if (attrib->destroy) + attrib->destroy(attrib->destroy_user_data); + + bt_att_unref(attrib->att); + + queue_destroy(attrib->callbacks, attrib_callbacks_destroy); + queue_destroy(attrib->track_ids, free); + + free(attrib->buf); + + g_io_channel_unref(attrib->io); + + free(attrib); +} + +GIOChannel *g_attrib_get_channel(GAttrib *attrib) +{ + if (!attrib) + return NULL; + + return attrib->io; +} + +struct bt_att *g_attrib_get_att(GAttrib *attrib) +{ + if (!attrib) + return NULL; + + return attrib->att; +} + +gboolean g_attrib_set_destroy_function(GAttrib *attrib, GDestroyNotify destroy, + gpointer user_data) +{ + if (!attrib) + return FALSE; + + attrib->destroy = destroy; + attrib->destroy_user_data = user_data; + + return TRUE; +} + + +static uint8_t *construct_full_pdu(uint8_t opcode, const void *pdu, + uint16_t length) +{ + uint8_t *buf = malloc0(length + 1); + + if (!buf) + return NULL; + + buf[0] = opcode; + memcpy(buf + 1, pdu, length); + + return buf; +} + +static void attrib_callback_result(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + uint8_t *buf; + struct attrib_callbacks *cb = user_data; + guint8 status = 0; + + if (!cb) + return; + + buf = construct_full_pdu(opcode, pdu, length); + if (!buf) + return; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + /* Error code is the third byte of the PDU data */ + if (length < 4) + status = BT_ATT_ERROR_UNLIKELY; + else + status = ((guint8 *)pdu)[3]; + } + + if (cb->result_func) + cb->result_func(status, buf, length + 1, cb->user_data); + + free(buf); +} + +static void attrib_callback_notify(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + uint8_t *buf; + struct attrib_callbacks *cb = user_data; + + if (!cb || !cb->notify_func) + return; + + if (cb->notify_handle != GATTRIB_ALL_HANDLES && length < 2) + return; + + if (cb->notify_handle != GATTRIB_ALL_HANDLES && + cb->notify_handle != get_le16(pdu)) + return; + + buf = construct_full_pdu(opcode, pdu, length); + if (!buf) + return; + + cb->notify_func(buf, length + 1, cb->user_data); + + free(buf); +} + +guint g_attrib_send(GAttrib *attrib, guint id, const guint8 *pdu, guint16 len, + GAttribResultFunc func, gpointer user_data, + GDestroyNotify notify) +{ + struct attrib_callbacks *cb = NULL; + bt_att_response_func_t response_cb = NULL; + bt_att_destroy_func_t destroy_cb = NULL; + unsigned int pend_id; + + if (!attrib) + return 0; + + if (!pdu || !len) + return 0; + + if (func || notify) { + cb = new0(struct attrib_callbacks, 1); + if (!cb) + return 0; + cb->result_func = func; + cb->user_data = user_data; + cb->destroy_func = notify; + cb->parent = attrib; + queue_push_head(attrib->callbacks, cb); + response_cb = attrib_callback_result; + destroy_cb = attrib_callbacks_remove; + + } + + pend_id = bt_att_send(attrib->att, pdu[0], (void *) pdu + 1, len - 1, + response_cb, cb, destroy_cb); + + /* + * We store here pair as it is easier to handle it in response and in + * case where user request us to use specific id request - see below. + */ + if (id == 0) + id = pend_id; + + /* + * If user what us to use given id, lets keep track on that so we give + * user a possibility to cancel ongoing request. + */ + if (cb) + cb->id = store_id(attrib, id, pend_id); + + return id; +} + +gboolean g_attrib_cancel(GAttrib *attrib, guint id) +{ + struct id_pair *p; + + if (!attrib) + return FALSE; + + /* + * If request belongs to gattrib and is not yet done it has to be on + * the tracking id queue + * + * FIXME: It can happen that on the queue there is id_pair with + * given id which was provided by the user. In the same time it might + * happen that other attrib user got dynamic allocated req_id with same + * value as the one provided by the other user. + * In such case there are two clients having same request id and in + * this point of time we don't know which one calls cancel. For + * now we cancel request in which id was specified by the user. + */ + p = queue_remove_if(attrib->track_ids, find_with_org_id, + UINT_TO_PTR(id)); + if (!p) + return FALSE; + + id = p->pend_id; + free(p); + + return bt_att_cancel(attrib->att, id); +} + +static void cancel_request(void *data, void *user_data) +{ + struct id_pair *p = data; + GAttrib *attrib = user_data; + + bt_att_cancel(attrib->att, p->pend_id); +} + +gboolean g_attrib_cancel_all(GAttrib *attrib) +{ + if (!attrib) + return FALSE; + + /* Cancel only request which belongs to gattrib */ + queue_foreach(attrib->track_ids, cancel_request, attrib); + queue_remove_all(attrib->track_ids, NULL, NULL, free); + + return TRUE; +} + +guint g_attrib_register(GAttrib *attrib, guint8 opcode, guint16 handle, + GAttribNotifyFunc func, gpointer user_data, + GDestroyNotify notify) +{ + struct attrib_callbacks *cb = NULL; + + if (!attrib) + return 0; + + if (func || notify) { + cb = new0(struct attrib_callbacks, 1); + if (!cb) + return 0; + cb->notify_func = func; + cb->notify_handle = handle; + cb->user_data = user_data; + cb->destroy_func = notify; + cb->parent = attrib; + queue_push_head(attrib->callbacks, cb); + } + + if (opcode == GATTRIB_ALL_REQS) + opcode = BT_ATT_ALL_REQUESTS; + + return bt_att_register(attrib->att, opcode, attrib_callback_notify, + cb, attrib_callbacks_remove); +} + +uint8_t *g_attrib_get_buffer(GAttrib *attrib, size_t *len) +{ + uint16_t mtu; + + if (!attrib || !len) + return NULL; + + mtu = bt_att_get_mtu(attrib->att); + + /* + * Clients of this expect a buffer to use. + * + * Pdu encoding in shared/att verifies if whole buffer fits the mtu, + * thus we should set the buflen also when mtu is reduced. But we + * need to reallocate the buffer only if mtu is larger. + */ + if (mtu > attrib->buflen) + attrib->buf = g_realloc(attrib->buf, mtu); + + attrib->buflen = mtu; + *len = attrib->buflen; + return attrib->buf; +} + +gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu) +{ + if (!attrib) + return FALSE; + + /* + * Clients of this expect a buffer to use. + * + * Pdu encoding in sharred/att verifies if whole buffer fits the mtu, + * thus we should set the buflen also when mtu is reduced. But we + * need to reallocate the buffer only if mtu is larger. + */ + if (mtu > attrib->buflen) + attrib->buf = g_realloc(attrib->buf, mtu); + + attrib->buflen = mtu; + + return bt_att_set_mtu(attrib->att, mtu); +} + +gboolean g_attrib_unregister(GAttrib *attrib, guint id) +{ + if (!attrib) + return FALSE; + + return bt_att_unregister(attrib->att, id); +} + +gboolean g_attrib_unregister_all(GAttrib *attrib) +{ + if (!attrib) + return false; + + return bt_att_unregister_all(attrib->att); +} diff --git a/attrib/gattrib.h b/attrib/gattrib.h new file mode 100644 index 0000000..611f952 --- /dev/null +++ b/attrib/gattrib.h @@ -0,0 +1,76 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef __GATTRIB_H +#define __GATTRIB_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define GATTRIB_ALL_REQS 0xFE +#define GATTRIB_ALL_HANDLES 0x0000 + +struct bt_att; /* Forward declaration for compatibility */ +struct _GAttrib; +typedef struct _GAttrib GAttrib; + +typedef void (*GAttribResultFunc) (guint8 status, const guint8 *pdu, + guint16 len, gpointer user_data); +typedef void (*GAttribDisconnectFunc)(gpointer user_data); +typedef void (*GAttribDebugFunc)(const char *str, gpointer user_data); +typedef void (*GAttribNotifyFunc)(const guint8 *pdu, guint16 len, + gpointer user_data); + +GAttrib *g_attrib_new(GIOChannel *io, guint16 mtu, bool ext_signed); +GAttrib *g_attrib_ref(GAttrib *attrib); +void g_attrib_unref(GAttrib *attrib); + +GIOChannel *g_attrib_get_channel(GAttrib *attrib); + +struct bt_att *g_attrib_get_att(GAttrib *attrib); + +gboolean g_attrib_set_destroy_function(GAttrib *attrib, + GDestroyNotify destroy, gpointer user_data); + +guint g_attrib_send(GAttrib *attrib, guint id, const guint8 *pdu, guint16 len, + GAttribResultFunc func, gpointer user_data, + GDestroyNotify notify); + +gboolean g_attrib_cancel(GAttrib *attrib, guint id); +gboolean g_attrib_cancel_all(GAttrib *attrib); + +guint g_attrib_register(GAttrib *attrib, guint8 opcode, guint16 handle, + GAttribNotifyFunc func, gpointer user_data, + GDestroyNotify notify); + +uint8_t *g_attrib_get_buffer(GAttrib *attrib, size_t *len); +gboolean g_attrib_set_mtu(GAttrib *attrib, int mtu); + +gboolean g_attrib_unregister(GAttrib *attrib, guint id); +gboolean g_attrib_unregister_all(GAttrib *attrib); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/attrib/gatttool.c b/attrib/gatttool.c new file mode 100644 index 0000000..95bd20a --- /dev/null +++ b/attrib/gatttool.c @@ -0,0 +1,624 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "att.h" +#include "btio/btio.h" +#include "gattrib.h" +#include "gatt.h" +#include "gatttool.h" + +static char *opt_src = NULL; +static char *opt_dst = NULL; +static char *opt_dst_type = NULL; +static char *opt_value = NULL; +static char *opt_sec_level = NULL; +static bt_uuid_t *opt_uuid = NULL; +static int opt_start = 0x0001; +static int opt_end = 0xffff; +static int opt_handle = -1; +static int opt_mtu = 0; +static int opt_psm = 0; +static gboolean opt_primary = FALSE; +static gboolean opt_characteristics = FALSE; +static gboolean opt_char_read = FALSE; +static gboolean opt_listen = FALSE; +static gboolean opt_char_desc = FALSE; +static gboolean opt_char_write = FALSE; +static gboolean opt_char_write_req = FALSE; +static gboolean opt_interactive = FALSE; +static GMainLoop *event_loop; +static gboolean got_error = FALSE; +static GSourceFunc operation; + +struct characteristic_data { + GAttrib *attrib; + uint16_t start; + uint16_t end; +}; + +static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) +{ + GAttrib *attrib = user_data; + uint8_t *opdu; + uint16_t handle, i, olen = 0; + size_t plen; + + handle = get_le16(&pdu[1]); + + switch (pdu[0]) { + case ATT_OP_HANDLE_NOTIFY: + g_print("Notification handle = 0x%04x value: ", handle); + break; + case ATT_OP_HANDLE_IND: + g_print("Indication handle = 0x%04x value: ", handle); + break; + default: + g_print("Invalid opcode\n"); + return; + } + + for (i = 3; i < len; i++) + g_print("%02x ", pdu[i]); + + g_print("\n"); + + if (pdu[0] == ATT_OP_HANDLE_NOTIFY) + return; + + opdu = g_attrib_get_buffer(attrib, &plen); + olen = enc_confirmation(opdu, plen); + + if (olen > 0) + g_attrib_send(attrib, 0, opdu, olen, NULL, NULL, NULL); +} + +static gboolean listen_start(gpointer user_data) +{ + GAttrib *attrib = user_data; + + g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, GATTRIB_ALL_HANDLES, + events_handler, attrib, NULL); + g_attrib_register(attrib, ATT_OP_HANDLE_IND, GATTRIB_ALL_HANDLES, + events_handler, attrib, NULL); + + return FALSE; +} + +static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) +{ + GAttrib *attrib; + uint16_t mtu; + uint16_t cid; + GError *gerr = NULL; + + if (err) { + g_printerr("%s\n", err->message); + got_error = TRUE; + g_main_loop_quit(event_loop); + } + + bt_io_get(io, &gerr, BT_IO_OPT_IMTU, &mtu, + BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID); + + if (gerr) { + g_printerr("Can't detect MTU, using default: %s", + gerr->message); + g_error_free(gerr); + mtu = ATT_DEFAULT_LE_MTU; + } + + if (cid == ATT_CID) + mtu = ATT_DEFAULT_LE_MTU; + + attrib = g_attrib_new(io, mtu, false); + + if (opt_listen) + g_idle_add(listen_start, attrib); + + operation(attrib); +} + +static void primary_all_cb(uint8_t status, GSList *services, void *user_data) +{ + GSList *l; + + if (status) { + g_printerr("Discover all primary services failed: %s\n", + att_ecode2str(status)); + goto done; + } + + for (l = services; l; l = l->next) { + struct gatt_primary *prim = l->data; + g_print("attr handle = 0x%04x, end grp handle = 0x%04x " + "uuid: %s\n", prim->range.start, prim->range.end, prim->uuid); + } + +done: + g_main_loop_quit(event_loop); +} + +static void primary_by_uuid_cb(uint8_t status, GSList *ranges, void *user_data) +{ + GSList *l; + + if (status != 0) { + g_printerr("Discover primary services by UUID failed: %s\n", + att_ecode2str(status)); + goto done; + } + + for (l = ranges; l; l = l->next) { + struct att_range *range = l->data; + g_print("Starting handle: %04x Ending handle: %04x\n", + range->start, range->end); + } + +done: + g_main_loop_quit(event_loop); +} + +static gboolean primary(gpointer user_data) +{ + GAttrib *attrib = user_data; + + if (opt_uuid) + gatt_discover_primary(attrib, opt_uuid, primary_by_uuid_cb, + NULL); + else + gatt_discover_primary(attrib, NULL, primary_all_cb, NULL); + + return FALSE; +} + +static void char_discovered_cb(uint8_t status, GSList *characteristics, + void *user_data) +{ + GSList *l; + + if (status) { + g_printerr("Discover all characteristics failed: %s\n", + att_ecode2str(status)); + goto done; + } + + for (l = characteristics; l; l = l->next) { + struct gatt_char *chars = l->data; + + g_print("handle = 0x%04x, char properties = 0x%02x, char value " + "handle = 0x%04x, uuid = %s\n", chars->handle, + chars->properties, chars->value_handle, chars->uuid); + } + +done: + g_main_loop_quit(event_loop); +} + +static gboolean characteristics(gpointer user_data) +{ + GAttrib *attrib = user_data; + + gatt_discover_char(attrib, opt_start, opt_end, opt_uuid, + char_discovered_cb, NULL); + + return FALSE; +} + +static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + uint8_t value[plen]; + ssize_t vlen; + int i; + + if (status != 0) { + g_printerr("Characteristic value/descriptor read failed: %s\n", + att_ecode2str(status)); + goto done; + } + + vlen = dec_read_resp(pdu, plen, value, sizeof(value)); + if (vlen < 0) { + g_printerr("Protocol error\n"); + goto done; + } + g_print("Characteristic value/descriptor: "); + for (i = 0; i < vlen; i++) + g_print("%02x ", value[i]); + g_print("\n"); + +done: + if (!opt_listen) + g_main_loop_quit(event_loop); +} + +static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct att_data_list *list; + int i; + + if (status != 0) { + g_printerr("Read characteristics by UUID failed: %s\n", + att_ecode2str(status)); + goto done; + } + + list = dec_read_by_type_resp(pdu, plen); + if (list == NULL) + goto done; + + for (i = 0; i < list->num; i++) { + uint8_t *value = list->data[i]; + int j; + + g_print("handle: 0x%04x \t value: ", get_le16(value)); + value += 2; + for (j = 0; j < list->len - 2; j++, value++) + g_print("%02x ", *value); + g_print("\n"); + } + + att_data_list_free(list); + +done: + g_main_loop_quit(event_loop); +} + +static gboolean characteristics_read(gpointer user_data) +{ + GAttrib *attrib = user_data; + + if (opt_uuid != NULL) { + + gatt_read_char_by_uuid(attrib, opt_start, opt_end, opt_uuid, + char_read_by_uuid_cb, NULL); + + return FALSE; + } + + if (opt_handle <= 0) { + g_printerr("A valid handle is required\n"); + g_main_loop_quit(event_loop); + return FALSE; + } + + gatt_read_char(attrib, opt_handle, char_read_cb, attrib); + + return FALSE; +} + +static void mainloop_quit(gpointer user_data) +{ + uint8_t *value = user_data; + + g_free(value); + g_main_loop_quit(event_loop); +} + +static gboolean characteristics_write(gpointer user_data) +{ + GAttrib *attrib = user_data; + uint8_t *value; + size_t len; + + if (opt_handle <= 0) { + g_printerr("A valid handle is required\n"); + goto error; + } + + if (opt_value == NULL || opt_value[0] == '\0') { + g_printerr("A value is required\n"); + goto error; + } + + len = gatt_attr_data_from_string(opt_value, &value); + if (len == 0) { + g_printerr("Invalid value\n"); + goto error; + } + + gatt_write_cmd(attrib, opt_handle, value, len, mainloop_quit, value); + + g_free(value); + return FALSE; + +error: + g_main_loop_quit(event_loop); + return FALSE; +} + +static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + if (status != 0) { + g_printerr("Characteristic Write Request failed: " + "%s\n", att_ecode2str(status)); + goto done; + } + + if (!dec_write_resp(pdu, plen) && !dec_exec_write_resp(pdu, plen)) { + g_printerr("Protocol error\n"); + goto done; + } + + g_print("Characteristic value was written successfully\n"); + +done: + if (!opt_listen) + g_main_loop_quit(event_loop); +} + +static gboolean characteristics_write_req(gpointer user_data) +{ + GAttrib *attrib = user_data; + uint8_t *value; + size_t len; + + if (opt_handle <= 0) { + g_printerr("A valid handle is required\n"); + goto error; + } + + if (opt_value == NULL || opt_value[0] == '\0') { + g_printerr("A value is required\n"); + goto error; + } + + len = gatt_attr_data_from_string(opt_value, &value); + if (len == 0) { + g_printerr("Invalid value\n"); + goto error; + } + + gatt_write_char(attrib, opt_handle, value, len, char_write_req_cb, + NULL); + + g_free(value); + return FALSE; + +error: + g_main_loop_quit(event_loop); + return FALSE; +} + +static void char_desc_cb(uint8_t status, GSList *descriptors, void *user_data) +{ + GSList *l; + + if (status) { + g_printerr("Discover descriptors failed: %s\n", + att_ecode2str(status)); + return; + } + + for (l = descriptors; l; l = l->next) { + struct gatt_desc *desc = l->data; + + g_print("handle = 0x%04x, uuid = %s\n", desc->handle, + desc->uuid); + } + + if (!opt_listen) + g_main_loop_quit(event_loop); +} + +static gboolean characteristics_desc(gpointer user_data) +{ + GAttrib *attrib = user_data; + + gatt_discover_desc(attrib, opt_start, opt_end, NULL, char_desc_cb, + NULL); + + return FALSE; +} + +static gboolean parse_uuid(const char *key, const char *value, + gpointer user_data, GError **error) +{ + if (!value) + return FALSE; + + opt_uuid = g_try_malloc(sizeof(bt_uuid_t)); + if (opt_uuid == NULL) + return FALSE; + + if (bt_string_to_uuid(opt_uuid, value) < 0) + return FALSE; + + return TRUE; +} + +static GOptionEntry primary_char_options[] = { + { "start", 's' , 0, G_OPTION_ARG_INT, &opt_start, + "Starting handle(optional)", "0x0001" }, + { "end", 'e' , 0, G_OPTION_ARG_INT, &opt_end, + "Ending handle(optional)", "0xffff" }, + { "uuid", 'u', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, + parse_uuid, "UUID16 or UUID128(optional)", "0x1801"}, + { NULL }, +}; + +static GOptionEntry char_rw_options[] = { + { "handle", 'a' , 0, G_OPTION_ARG_INT, &opt_handle, + "Read/Write characteristic by handle(required)", "0x0001" }, + { "value", 'n' , 0, G_OPTION_ARG_STRING, &opt_value, + "Write characteristic value (required for write operation)", + "0x0001" }, + {NULL}, +}; + +static GOptionEntry gatt_options[] = { + { "primary", 0, 0, G_OPTION_ARG_NONE, &opt_primary, + "Primary Service Discovery", NULL }, + { "characteristics", 0, 0, G_OPTION_ARG_NONE, &opt_characteristics, + "Characteristics Discovery", NULL }, + { "char-read", 0, 0, G_OPTION_ARG_NONE, &opt_char_read, + "Characteristics Value/Descriptor Read", NULL }, + { "char-write", 0, 0, G_OPTION_ARG_NONE, &opt_char_write, + "Characteristics Value Write Without Response (Write Command)", + NULL }, + { "char-write-req", 0, 0, G_OPTION_ARG_NONE, &opt_char_write_req, + "Characteristics Value Write (Write Request)", NULL }, + { "char-desc", 0, 0, G_OPTION_ARG_NONE, &opt_char_desc, + "Characteristics Descriptor Discovery", NULL }, + { "listen", 0, 0, G_OPTION_ARG_NONE, &opt_listen, + "Listen for notifications and indications", NULL }, + { "interactive", 'I', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, + &opt_interactive, "Use interactive mode", NULL }, + { NULL }, +}; + +static GOptionEntry options[] = { + { "adapter", 'i', 0, G_OPTION_ARG_STRING, &opt_src, + "Specify local adapter interface", "hciX" }, + { "device", 'b', 0, G_OPTION_ARG_STRING, &opt_dst, + "Specify remote Bluetooth address", "MAC" }, + { "addr-type", 't', 0, G_OPTION_ARG_STRING, &opt_dst_type, + "Set LE address type. Default: public", "[public | random]"}, + { "mtu", 'm', 0, G_OPTION_ARG_INT, &opt_mtu, + "Specify the MTU size", "MTU" }, + { "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm, + "Specify the PSM for GATT/ATT over BR/EDR", "PSM" }, + { "sec-level", 'l', 0, G_OPTION_ARG_STRING, &opt_sec_level, + "Set security level. Default: low", "[low | medium | high]"}, + { NULL }, +}; + +int main(int argc, char *argv[]) +{ + GOptionContext *context; + GOptionGroup *gatt_group, *params_group, *char_rw_group; + GError *gerr = NULL; + GIOChannel *chan; + + opt_dst_type = g_strdup("public"); + opt_sec_level = g_strdup("low"); + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + /* GATT commands */ + gatt_group = g_option_group_new("gatt", "GATT commands", + "Show all GATT commands", NULL, NULL); + g_option_context_add_group(context, gatt_group); + g_option_group_add_entries(gatt_group, gatt_options); + + /* Primary Services and Characteristics arguments */ + params_group = g_option_group_new("params", + "Primary Services/Characteristics arguments", + "Show all Primary Services/Characteristics arguments", + NULL, NULL); + g_option_context_add_group(context, params_group); + g_option_group_add_entries(params_group, primary_char_options); + + /* Characteristics value/descriptor read/write arguments */ + char_rw_group = g_option_group_new("char-read-write", + "Characteristics Value/Descriptor Read/Write arguments", + "Show all Characteristics Value/Descriptor Read/Write " + "arguments", + NULL, NULL); + g_option_context_add_group(context, char_rw_group); + g_option_group_add_entries(char_rw_group, char_rw_options); + + if (!g_option_context_parse(context, &argc, &argv, &gerr)) { + g_printerr("%s\n", gerr->message); + g_clear_error(&gerr); + } + + if (opt_interactive) { + interactive(opt_src, opt_dst, opt_dst_type, opt_psm); + goto done; + } + + if (opt_primary) + operation = primary; + else if (opt_characteristics) + operation = characteristics; + else if (opt_char_read) + operation = characteristics_read; + else if (opt_char_write) + operation = characteristics_write; + else if (opt_char_write_req) + operation = characteristics_write_req; + else if (opt_char_desc) + operation = characteristics_desc; + else { + char *help = g_option_context_get_help(context, TRUE, NULL); + g_print("%s\n", help); + g_free(help); + got_error = TRUE; + goto done; + } + + if (opt_dst == NULL) { + g_print("Remote Bluetooth address required\n"); + got_error = TRUE; + goto done; + } + + chan = gatt_connect(opt_src, opt_dst, opt_dst_type, opt_sec_level, + opt_psm, opt_mtu, connect_cb, &gerr); + if (chan == NULL) { + g_printerr("%s\n", gerr->message); + g_clear_error(&gerr); + got_error = TRUE; + goto done; + } + + event_loop = g_main_loop_new(NULL, FALSE); + + g_main_loop_run(event_loop); + + g_main_loop_unref(event_loop); + +done: + g_option_context_free(context); + g_free(opt_src); + g_free(opt_dst); + g_free(opt_uuid); + g_free(opt_sec_level); + + if (got_error) + exit(EXIT_FAILURE); + else + exit(EXIT_SUCCESS); +} diff --git a/attrib/gatttool.h b/attrib/gatttool.h new file mode 100644 index 0000000..8f0913c --- /dev/null +++ b/attrib/gatttool.h @@ -0,0 +1,30 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int interactive(const char *src, const char *dst, const char *dst_type, + int psm); +GIOChannel *gatt_connect(const char *src, const char *dst, + const char *dst_type, const char *sec_level, + int psm, int mtu, BtIOConnect connect_cb, + GError **gerr); +size_t gatt_attr_data_from_string(const char *str, uint8_t **data); diff --git a/attrib/interactive.c b/attrib/interactive.c new file mode 100644 index 0000000..9a7976d --- /dev/null +++ b/attrib/interactive.c @@ -0,0 +1,1032 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "btio/btio.h" +#include "att.h" +#include "gattrib.h" +#include "gatt.h" +#include "gatttool.h" +#include "client/display.h" + +static GIOChannel *iochannel = NULL; +static GAttrib *attrib = NULL; +static GMainLoop *event_loop; +static GString *prompt; + +static char *opt_src = NULL; +static char *opt_dst = NULL; +static char *opt_dst_type = NULL; +static char *opt_sec_level = NULL; +static int opt_psm = 0; +static int opt_mtu = 0; +static int start; +static int end; + +static void cmd_help(int argcp, char **argvp); + +static enum state { + STATE_DISCONNECTED, + STATE_CONNECTING, + STATE_CONNECTED +} conn_state; + +#define error(fmt, arg...) \ + rl_printf(COLOR_RED "Error: " COLOR_OFF fmt, ## arg) + +#define failed(fmt, arg...) \ + rl_printf(COLOR_RED "Command Failed: " COLOR_OFF fmt, ## arg) + +static char *get_prompt(void) +{ + if (conn_state == STATE_CONNECTED) + g_string_assign(prompt, COLOR_BLUE); + else + g_string_assign(prompt, ""); + + if (opt_dst) + g_string_append_printf(prompt, "[%17s]", opt_dst); + else + g_string_append_printf(prompt, "[%17s]", ""); + + if (conn_state == STATE_CONNECTED) + g_string_append(prompt, COLOR_OFF); + + if (opt_psm) + g_string_append(prompt, "[BR]"); + else + g_string_append(prompt, "[LE]"); + + g_string_append(prompt, "> "); + + return prompt->str; +} + + +static void set_state(enum state st) +{ + conn_state = st; + rl_set_prompt(get_prompt()); +} + +static void events_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) +{ + uint8_t *opdu; + uint16_t handle, i, olen; + size_t plen; + GString *s; + + handle = get_le16(&pdu[1]); + + switch (pdu[0]) { + case ATT_OP_HANDLE_NOTIFY: + s = g_string_new(NULL); + g_string_printf(s, "Notification handle = 0x%04x value: ", + handle); + break; + case ATT_OP_HANDLE_IND: + s = g_string_new(NULL); + g_string_printf(s, "Indication handle = 0x%04x value: ", + handle); + break; + default: + error("Invalid opcode\n"); + return; + } + + for (i = 3; i < len; i++) + g_string_append_printf(s, "%02x ", pdu[i]); + + rl_printf("%s\n", s->str); + g_string_free(s, TRUE); + + if (pdu[0] == ATT_OP_HANDLE_NOTIFY) + return; + + opdu = g_attrib_get_buffer(attrib, &plen); + olen = enc_confirmation(opdu, plen); + + if (olen > 0) + g_attrib_send(attrib, 0, opdu, olen, NULL, NULL, NULL); +} + +static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) +{ + uint16_t mtu; + uint16_t cid; + + if (err) { + set_state(STATE_DISCONNECTED); + error("%s\n", err->message); + return; + } + + bt_io_get(io, &err, BT_IO_OPT_IMTU, &mtu, + BT_IO_OPT_CID, &cid, BT_IO_OPT_INVALID); + + if (err) { + g_printerr("Can't detect MTU, using default: %s", err->message); + g_error_free(err); + mtu = ATT_DEFAULT_LE_MTU; + } + + if (cid == ATT_CID) + mtu = ATT_DEFAULT_LE_MTU; + + attrib = g_attrib_new(iochannel, mtu, false); + g_attrib_register(attrib, ATT_OP_HANDLE_NOTIFY, GATTRIB_ALL_HANDLES, + events_handler, attrib, NULL); + g_attrib_register(attrib, ATT_OP_HANDLE_IND, GATTRIB_ALL_HANDLES, + events_handler, attrib, NULL); + set_state(STATE_CONNECTED); + rl_printf("Connection successful\n"); +} + +static void disconnect_io() +{ + if (conn_state == STATE_DISCONNECTED) + return; + + g_attrib_unref(attrib); + attrib = NULL; + opt_mtu = 0; + + g_io_channel_shutdown(iochannel, FALSE, NULL); + g_io_channel_unref(iochannel); + iochannel = NULL; + + set_state(STATE_DISCONNECTED); +} + +static void primary_all_cb(uint8_t status, GSList *services, void *user_data) +{ + GSList *l; + + if (status) { + error("Discover all primary services failed: %s\n", + att_ecode2str(status)); + return; + } + + if (services == NULL) { + error("No primary service found\n"); + return; + } + + for (l = services; l; l = l->next) { + struct gatt_primary *prim = l->data; + rl_printf("attr handle: 0x%04x, end grp handle: 0x%04x uuid: %s\n", + prim->range.start, prim->range.end, prim->uuid); + } +} + +static void primary_by_uuid_cb(uint8_t status, GSList *ranges, void *user_data) +{ + GSList *l; + + if (status) { + error("Discover primary services by UUID failed: %s\n", + att_ecode2str(status)); + return; + } + + if (ranges == NULL) { + error("No service UUID found\n"); + return; + } + + for (l = ranges; l; l = l->next) { + struct att_range *range = l->data; + rl_printf("Starting handle: 0x%04x Ending handle: 0x%04x\n", + range->start, range->end); + } +} + +static void included_cb(uint8_t status, GSList *includes, void *user_data) +{ + GSList *l; + + if (status) { + error("Find included services failed: %s\n", + att_ecode2str(status)); + return; + } + + if (includes == NULL) { + rl_printf("No included services found for this range\n"); + return; + } + + for (l = includes; l; l = l->next) { + struct gatt_included *incl = l->data; + rl_printf("handle: 0x%04x, start handle: 0x%04x, " + "end handle: 0x%04x uuid: %s\n", + incl->handle, incl->range.start, + incl->range.end, incl->uuid); + } +} + +static void char_cb(uint8_t status, GSList *characteristics, void *user_data) +{ + GSList *l; + + if (status) { + error("Discover all characteristics failed: %s\n", + att_ecode2str(status)); + return; + } + + for (l = characteristics; l; l = l->next) { + struct gatt_char *chars = l->data; + + rl_printf("handle: 0x%04x, char properties: 0x%02x, char value " + "handle: 0x%04x, uuid: %s\n", chars->handle, + chars->properties, chars->value_handle, + chars->uuid); + } +} + +static void char_desc_cb(uint8_t status, GSList *descriptors, void *user_data) +{ + GSList *l; + + if (status) { + error("Discover descriptors failed: %s\n", + att_ecode2str(status)); + return; + } + + for (l = descriptors; l; l = l->next) { + struct gatt_desc *desc = l->data; + + rl_printf("handle: 0x%04x, uuid: %s\n", desc->handle, + desc->uuid); + } +} + +static void char_read_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + uint8_t value[plen]; + ssize_t vlen; + int i; + GString *s; + + if (status != 0) { + error("Characteristic value/descriptor read failed: %s\n", + att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, plen, value, sizeof(value)); + if (vlen < 0) { + error("Protocol error\n"); + return; + } + + s = g_string_new("Characteristic value/descriptor: "); + for (i = 0; i < vlen; i++) + g_string_append_printf(s, "%02x ", value[i]); + + rl_printf("%s\n", s->str); + g_string_free(s, TRUE); +} + +static void char_read_by_uuid_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct att_data_list *list; + int i; + GString *s; + + if (status != 0) { + error("Read characteristics by UUID failed: %s\n", + att_ecode2str(status)); + return; + } + + list = dec_read_by_type_resp(pdu, plen); + if (list == NULL) + return; + + s = g_string_new(NULL); + for (i = 0; i < list->num; i++) { + uint8_t *value = list->data[i]; + int j; + + g_string_printf(s, "handle: 0x%04x \t value: ", + get_le16(value)); + value += 2; + for (j = 0; j < list->len - 2; j++, value++) + g_string_append_printf(s, "%02x ", *value); + + rl_printf("%s\n", s->str); + } + + att_data_list_free(list); + g_string_free(s, TRUE); +} + +static void cmd_exit(int argcp, char **argvp) +{ + rl_callback_handler_remove(); + g_main_loop_quit(event_loop); +} + +static gboolean channel_watcher(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + disconnect_io(); + + return FALSE; +} + +static void cmd_connect(int argcp, char **argvp) +{ + GError *gerr = NULL; + + if (conn_state != STATE_DISCONNECTED) + return; + + if (argcp > 1) { + g_free(opt_dst); + opt_dst = g_strdup(argvp[1]); + + g_free(opt_dst_type); + if (argcp > 2) + opt_dst_type = g_strdup(argvp[2]); + else + opt_dst_type = g_strdup("public"); + } + + if (opt_dst == NULL) { + error("Remote Bluetooth address required\n"); + return; + } + + rl_printf("Attempting to connect to %s\n", opt_dst); + set_state(STATE_CONNECTING); + iochannel = gatt_connect(opt_src, opt_dst, opt_dst_type, opt_sec_level, + opt_psm, opt_mtu, connect_cb, &gerr); + if (iochannel == NULL) { + set_state(STATE_DISCONNECTED); + error("%s\n", gerr->message); + g_error_free(gerr); + } else + g_io_add_watch(iochannel, G_IO_HUP, channel_watcher, NULL); +} + +static void cmd_disconnect(int argcp, char **argvp) +{ + disconnect_io(); +} + +static void cmd_primary(int argcp, char **argvp) +{ + bt_uuid_t uuid; + + if (conn_state != STATE_CONNECTED) { + failed("Disconnected\n"); + return; + } + + if (argcp == 1) { + gatt_discover_primary(attrib, NULL, primary_all_cb, NULL); + return; + } + + if (bt_string_to_uuid(&uuid, argvp[1]) < 0) { + error("Invalid UUID\n"); + return; + } + + gatt_discover_primary(attrib, &uuid, primary_by_uuid_cb, NULL); +} + +static int strtohandle(const char *src) +{ + char *e; + int dst; + + errno = 0; + dst = strtoll(src, &e, 16); + if (errno != 0 || *e != '\0') + return -EINVAL; + + return dst; +} + +static void cmd_included(int argcp, char **argvp) +{ + int start = 0x0001; + int end = 0xffff; + + if (conn_state != STATE_CONNECTED) { + failed("Disconnected\n"); + return; + } + + if (argcp > 1) { + start = strtohandle(argvp[1]); + if (start < 0) { + error("Invalid start handle: %s\n", argvp[1]); + return; + } + end = start; + } + + if (argcp > 2) { + end = strtohandle(argvp[2]); + if (end < 0) { + error("Invalid end handle: %s\n", argvp[2]); + return; + } + } + + gatt_find_included(attrib, start, end, included_cb, NULL); +} + +static void cmd_char(int argcp, char **argvp) +{ + int start = 0x0001; + int end = 0xffff; + + if (conn_state != STATE_CONNECTED) { + failed("Disconnected\n"); + return; + } + + if (argcp > 1) { + start = strtohandle(argvp[1]); + if (start < 0) { + error("Invalid start handle: %s\n", argvp[1]); + return; + } + } + + if (argcp > 2) { + end = strtohandle(argvp[2]); + if (end < 0) { + error("Invalid end handle: %s\n", argvp[2]); + return; + } + } + + if (argcp > 3) { + bt_uuid_t uuid; + + if (bt_string_to_uuid(&uuid, argvp[3]) < 0) { + error("Invalid UUID\n"); + return; + } + + gatt_discover_char(attrib, start, end, &uuid, char_cb, NULL); + return; + } + + gatt_discover_char(attrib, start, end, NULL, char_cb, NULL); +} + +static void cmd_char_desc(int argcp, char **argvp) +{ + if (conn_state != STATE_CONNECTED) { + failed("Disconnected\n"); + return; + } + + if (argcp > 1) { + start = strtohandle(argvp[1]); + if (start < 0) { + error("Invalid start handle: %s\n", argvp[1]); + return; + } + } else + start = 0x0001; + + if (argcp > 2) { + end = strtohandle(argvp[2]); + if (end < 0) { + error("Invalid end handle: %s\n", argvp[2]); + return; + } + } else + end = 0xffff; + + gatt_discover_desc(attrib, start, end, NULL, char_desc_cb, NULL); +} + +static void cmd_read_hnd(int argcp, char **argvp) +{ + int handle; + + if (conn_state != STATE_CONNECTED) { + failed("Disconnected\n"); + return; + } + + if (argcp < 2) { + error("Missing argument: handle\n"); + return; + } + + handle = strtohandle(argvp[1]); + if (handle < 0) { + error("Invalid handle: %s\n", argvp[1]); + return; + } + + gatt_read_char(attrib, handle, char_read_cb, attrib); +} + +static void cmd_read_uuid(int argcp, char **argvp) +{ + int start = 0x0001; + int end = 0xffff; + bt_uuid_t uuid; + + if (conn_state != STATE_CONNECTED) { + failed("Disconnected\n"); + return; + } + + if (argcp < 2) { + error("Missing argument: UUID\n"); + return; + } + + if (bt_string_to_uuid(&uuid, argvp[1]) < 0) { + error("Invalid UUID\n"); + return; + } + + if (argcp > 2) { + start = strtohandle(argvp[2]); + if (start < 0) { + error("Invalid start handle: %s\n", argvp[1]); + return; + } + } + + if (argcp > 3) { + end = strtohandle(argvp[3]); + if (end < 0) { + error("Invalid end handle: %s\n", argvp[2]); + return; + } + } + + gatt_read_char_by_uuid(attrib, start, end, &uuid, char_read_by_uuid_cb, + NULL); +} + +static void char_write_req_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + if (status != 0) { + error("Characteristic Write Request failed: " + "%s\n", att_ecode2str(status)); + return; + } + + if (!dec_write_resp(pdu, plen) && !dec_exec_write_resp(pdu, plen)) { + error("Protocol error\n"); + return; + } + + rl_printf("Characteristic value was written successfully\n"); +} + +static void cmd_char_write(int argcp, char **argvp) +{ + uint8_t *value; + size_t plen; + int handle; + + if (conn_state != STATE_CONNECTED) { + failed("Disconnected\n"); + return; + } + + if (argcp < 3) { + rl_printf("Usage: %s \n", argvp[0]); + return; + } + + handle = strtohandle(argvp[1]); + if (handle <= 0) { + error("A valid handle is required\n"); + return; + } + + plen = gatt_attr_data_from_string(argvp[2], &value); + if (plen == 0) { + error("Invalid value\n"); + return; + } + + if (g_strcmp0("char-write-req", argvp[0]) == 0) + gatt_write_char(attrib, handle, value, plen, + char_write_req_cb, NULL); + else + gatt_write_cmd(attrib, handle, value, plen, NULL, NULL); + + g_free(value); +} + +static void cmd_sec_level(int argcp, char **argvp) +{ + GError *gerr = NULL; + BtIOSecLevel sec_level; + + if (argcp < 2) { + rl_printf("sec-level: %s\n", opt_sec_level); + return; + } + + if (strcasecmp(argvp[1], "medium") == 0) + sec_level = BT_IO_SEC_MEDIUM; + else if (strcasecmp(argvp[1], "high") == 0) + sec_level = BT_IO_SEC_HIGH; + else if (strcasecmp(argvp[1], "low") == 0) + sec_level = BT_IO_SEC_LOW; + else { + rl_printf("Allowed values: low | medium | high\n"); + return; + } + + g_free(opt_sec_level); + opt_sec_level = g_strdup(argvp[1]); + + if (conn_state != STATE_CONNECTED) + return; + + if (opt_psm) { + rl_printf("Change will take effect on reconnection\n"); + return; + } + + bt_io_set(iochannel, &gerr, + BT_IO_OPT_SEC_LEVEL, sec_level, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s\n", gerr->message); + g_error_free(gerr); + } +} + +static void exchange_mtu_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + uint16_t mtu; + + if (status != 0) { + error("Exchange MTU Request failed: %s\n", + att_ecode2str(status)); + return; + } + + if (!dec_mtu_resp(pdu, plen, &mtu)) { + error("Protocol error\n"); + return; + } + + mtu = MIN(mtu, opt_mtu); + /* Set new value for MTU in client */ + if (g_attrib_set_mtu(attrib, mtu)) + rl_printf("MTU was exchanged successfully: %d\n", mtu); + else + error("Error exchanging MTU\n"); +} + +static void cmd_mtu(int argcp, char **argvp) +{ + if (conn_state != STATE_CONNECTED) { + failed("Disconnected\n"); + return; + } + + if (opt_psm) { + failed("Operation is only available for LE transport.\n"); + return; + } + + if (argcp < 2) { + rl_printf("Usage: mtu \n"); + return; + } + + if (opt_mtu) { + failed("MTU exchange can only occur once per connection.\n"); + return; + } + + errno = 0; + opt_mtu = strtoll(argvp[1], NULL, 0); + if (errno != 0 || opt_mtu < ATT_DEFAULT_LE_MTU) { + error("Invalid value. Minimum MTU size is %d\n", + ATT_DEFAULT_LE_MTU); + return; + } + + gatt_exchange_mtu(attrib, opt_mtu, exchange_mtu_cb, NULL); +} + +static struct { + const char *cmd; + void (*func)(int argcp, char **argvp); + const char *params; + const char *desc; +} commands[] = { + { "help", cmd_help, "", + "Show this help"}, + { "exit", cmd_exit, "", + "Exit interactive mode" }, + { "quit", cmd_exit, "", + "Exit interactive mode" }, + { "connect", cmd_connect, "[address [address type]]", + "Connect to a remote device" }, + { "disconnect", cmd_disconnect, "", + "Disconnect from a remote device" }, + { "primary", cmd_primary, "[UUID]", + "Primary Service Discovery" }, + { "included", cmd_included, "[start hnd [end hnd]]", + "Find Included Services" }, + { "characteristics", cmd_char, "[start hnd [end hnd [UUID]]]", + "Characteristics Discovery" }, + { "char-desc", cmd_char_desc, "[start hnd] [end hnd]", + "Characteristics Descriptor Discovery" }, + { "char-read-hnd", cmd_read_hnd, "", + "Characteristics Value/Descriptor Read by handle" }, + { "char-read-uuid", cmd_read_uuid, " [start hnd] [end hnd]", + "Characteristics Value/Descriptor Read by UUID" }, + { "char-write-req", cmd_char_write, " ", + "Characteristic Value Write (Write Request)" }, + { "char-write-cmd", cmd_char_write, " ", + "Characteristic Value Write (No response)" }, + { "sec-level", cmd_sec_level, "[low | medium | high]", + "Set security level. Default: low" }, + { "mtu", cmd_mtu, "", + "Exchange MTU for GATT/ATT" }, + { NULL, NULL, NULL} +}; + +static void cmd_help(int argcp, char **argvp) +{ + int i; + + for (i = 0; commands[i].cmd; i++) + rl_printf("%-15s %-30s %s\n", commands[i].cmd, + commands[i].params, commands[i].desc); +} + +static void parse_line(char *line_read) +{ + char **argvp; + int argcp; + int i; + + if (line_read == NULL) { + rl_printf("\n"); + cmd_exit(0, NULL); + return; + } + + line_read = g_strstrip(line_read); + + if (*line_read == '\0') + goto done; + + add_history(line_read); + + if (g_shell_parse_argv(line_read, &argcp, &argvp, NULL) == FALSE) + goto done; + + for (i = 0; commands[i].cmd; i++) + if (strcasecmp(commands[i].cmd, argvp[0]) == 0) + break; + + if (commands[i].cmd) + commands[i].func(argcp, argvp); + else + error("%s: command not found\n", argvp[0]); + + g_strfreev(argvp); + +done: + free(line_read); +} + +static gboolean prompt_read(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) { + g_io_channel_unref(chan); + return FALSE; + } + + rl_callback_read_char(); + + return TRUE; +} + +static char *completion_generator(const char *text, int state) +{ + static int index = 0, len = 0; + const char *cmd = NULL; + + if (state == 0) { + index = 0; + len = strlen(text); + } + + while ((cmd = commands[index].cmd) != NULL) { + index++; + if (strncmp(cmd, text, len) == 0) + return strdup(cmd); + } + + return NULL; +} + +static char **commands_completion(const char *text, int start, int end) +{ + if (start == 0) + return rl_completion_matches(text, &completion_generator); + else + return NULL; +} + +static guint setup_standard_input(void) +{ + GIOChannel *channel; + guint source; + + channel = g_io_channel_unix_new(fileno(stdin)); + + source = g_io_add_watch(channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + prompt_read, NULL); + + g_io_channel_unref(channel); + + return source; +} + +static gboolean signal_handler(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + static unsigned int __terminated = 0; + struct signalfd_siginfo si; + ssize_t result; + int fd; + + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + g_main_loop_quit(event_loop); + return FALSE; + } + + fd = g_io_channel_unix_get_fd(channel); + + result = read(fd, &si, sizeof(si)); + if (result != sizeof(si)) + return FALSE; + + switch (si.ssi_signo) { + case SIGINT: + rl_replace_line("", 0); + rl_crlf(); + rl_on_new_line(); + rl_redisplay(); + break; + case SIGTERM: + if (__terminated == 0) { + rl_replace_line("", 0); + rl_crlf(); + g_main_loop_quit(event_loop); + } + + __terminated = 1; + break; + } + + return TRUE; +} + +static guint setup_signalfd(void) +{ + GIOChannel *channel; + guint source; + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + perror("Failed to set signal mask"); + return 0; + } + + fd = signalfd(-1, &mask, 0); + if (fd < 0) { + perror("Failed to create signal descriptor"); + return 0; + } + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + source = g_io_add_watch(channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + signal_handler, NULL); + + g_io_channel_unref(channel); + + return source; +} + +int interactive(const char *src, const char *dst, + const char *dst_type, int psm) +{ + guint input; + guint signal; + + opt_sec_level = g_strdup("low"); + + opt_src = g_strdup(src); + opt_dst = g_strdup(dst); + opt_dst_type = g_strdup(dst_type); + opt_psm = psm; + + prompt = g_string_new(NULL); + + event_loop = g_main_loop_new(NULL, FALSE); + + input = setup_standard_input(); + signal = setup_signalfd(); + + rl_attempted_completion_function = commands_completion; + rl_erase_empty_line = 1; + rl_callback_handler_install(get_prompt(), parse_line); + + g_main_loop_run(event_loop); + + rl_callback_handler_remove(); + cmd_disconnect(0, NULL); + g_source_remove(input); + g_source_remove(signal); + g_main_loop_unref(event_loop); + g_string_free(prompt, TRUE); + + g_free(opt_src); + g_free(opt_dst); + g_free(opt_sec_level); + + return 0; +} diff --git a/attrib/utils.c b/attrib/utils.c new file mode 100644 index 0000000..a749029 --- /dev/null +++ b/attrib/utils.c @@ -0,0 +1,122 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "btio/btio.h" +#include "att.h" +#include "gattrib.h" +#include "gatt.h" +#include "gatttool.h" + +GIOChannel *gatt_connect(const char *src, const char *dst, + const char *dst_type, const char *sec_level, + int psm, int mtu, BtIOConnect connect_cb, + GError **gerr) +{ + GIOChannel *chan; + bdaddr_t sba, dba; + uint8_t dest_type; + GError *tmp_err = NULL; + BtIOSecLevel sec; + + str2ba(dst, &dba); + + /* Local adapter */ + if (src != NULL) { + if (!strncmp(src, "hci", 3)) + hci_devba(atoi(src + 3), &sba); + else + str2ba(src, &sba); + } else + bacpy(&sba, BDADDR_ANY); + + /* Not used for BR/EDR */ + if (strcmp(dst_type, "random") == 0) + dest_type = BDADDR_LE_RANDOM; + else + dest_type = BDADDR_LE_PUBLIC; + + if (strcmp(sec_level, "medium") == 0) + sec = BT_IO_SEC_MEDIUM; + else if (strcmp(sec_level, "high") == 0) + sec = BT_IO_SEC_HIGH; + else + sec = BT_IO_SEC_LOW; + + if (psm == 0) + chan = bt_io_connect(connect_cb, NULL, NULL, &tmp_err, + BT_IO_OPT_SOURCE_BDADDR, &sba, + BT_IO_OPT_SOURCE_TYPE, BDADDR_LE_PUBLIC, + BT_IO_OPT_DEST_BDADDR, &dba, + BT_IO_OPT_DEST_TYPE, dest_type, + BT_IO_OPT_CID, ATT_CID, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_INVALID); + else + chan = bt_io_connect(connect_cb, NULL, NULL, &tmp_err, + BT_IO_OPT_SOURCE_BDADDR, &sba, + BT_IO_OPT_DEST_BDADDR, &dba, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_IMTU, mtu, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_INVALID); + + if (tmp_err) { + g_propagate_error(gerr, tmp_err); + return NULL; + } + + return chan; +} + +size_t gatt_attr_data_from_string(const char *str, uint8_t **data) +{ + char tmp[3]; + size_t size, i; + + size = strlen(str) / 2; + *data = g_try_malloc0(size); + if (*data == NULL) + return 0; + + tmp[2] = '\0'; + for (i = 0; i < size; i++) { + memcpy(tmp, str + (i * 2), 2); + (*data)[i] = (uint8_t) strtol(tmp, NULL, 16); + } + + return size; +} diff --git a/btio/btio.c b/btio/btio.c new file mode 100644 index 0000000..af2276d --- /dev/null +++ b/btio/btio.c @@ -0,0 +1,1682 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009-2010 Marcel Holtmann + * Copyright (C) 2009-2010 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/l2cap.h" +#include "lib/rfcomm.h" +#include "lib/sco.h" + +#include "btio.h" + +#ifndef BT_FLUSHABLE +#define BT_FLUSHABLE 8 +#endif + +#define ERROR_FAILED(gerr, str, err) \ + g_set_error(gerr, BT_IO_ERROR, err, \ + str ": %s (%d)", strerror(err), err) + +#define DEFAULT_DEFER_TIMEOUT 30 + +typedef enum { + BT_IO_L2CAP, + BT_IO_RFCOMM, + BT_IO_SCO, + BT_IO_INVALID, +} BtIOType; + +struct set_opts { + bdaddr_t src; + bdaddr_t dst; + BtIOType type; + uint8_t src_type; + uint8_t dst_type; + int defer; + int sec_level; + uint8_t channel; + uint16_t psm; + uint16_t cid; + uint16_t mtu; + uint16_t imtu; + uint16_t omtu; + int master; + uint8_t mode; + int flushable; + uint32_t priority; + uint16_t voice; +}; + +struct connect { + BtIOConnect connect; + gpointer user_data; + GDestroyNotify destroy; +}; + +struct accept { + BtIOConnect connect; + gpointer user_data; + GDestroyNotify destroy; +}; + +struct server { + BtIOConnect connect; + BtIOConfirm confirm; + gpointer user_data; + GDestroyNotify destroy; +}; + +static BtIOType bt_io_get_type(GIOChannel *io, GError **gerr) +{ + int sk = g_io_channel_unix_get_fd(io); + int domain, proto, err; + socklen_t len; + + domain = 0; + len = sizeof(domain); + err = getsockopt(sk, SOL_SOCKET, SO_DOMAIN, &domain, &len); + if (err < 0) { + ERROR_FAILED(gerr, "getsockopt(SO_DOMAIN)", errno); + return BT_IO_INVALID; + } + + if (domain != AF_BLUETOOTH) { + g_set_error(gerr, BT_IO_ERROR, EINVAL, + "BtIO socket domain not AF_BLUETOOTH"); + return BT_IO_INVALID; + } + + proto = 0; + len = sizeof(proto); + err = getsockopt(sk, SOL_SOCKET, SO_PROTOCOL, &proto, &len); + if (err < 0) { + ERROR_FAILED(gerr, "getsockopt(SO_PROTOCOL)", errno); + return BT_IO_INVALID; + } + + switch (proto) { + case BTPROTO_RFCOMM: + return BT_IO_RFCOMM; + case BTPROTO_SCO: + return BT_IO_SCO; + case BTPROTO_L2CAP: + return BT_IO_L2CAP; + default: + g_set_error(gerr, BT_IO_ERROR, EINVAL, + "Unknown BtIO socket type"); + return BT_IO_INVALID; + } +} + +static void server_remove(struct server *server) +{ + if (server->destroy) + server->destroy(server->user_data); + g_free(server); +} + +static void connect_remove(struct connect *conn) +{ + if (conn->destroy) + conn->destroy(conn->user_data); + g_free(conn); +} + +static void accept_remove(struct accept *accept) +{ + if (accept->destroy) + accept->destroy(accept->user_data); + g_free(accept); +} + +static gboolean check_nval(GIOChannel *io) +{ + struct pollfd fds; + + memset(&fds, 0, sizeof(fds)); + fds.fd = g_io_channel_unix_get_fd(io); + fds.events = POLLNVAL; + + if (poll(&fds, 1, 0) > 0 && (fds.revents & POLLNVAL)) + return TRUE; + + return FALSE; +} + +static gboolean accept_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct accept *accept = user_data; + GError *gerr = NULL; + + /* If the user aborted this accept attempt */ + if ((cond & G_IO_NVAL) || check_nval(io)) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) { + int err, sk_err, sock = g_io_channel_unix_get_fd(io); + socklen_t len = sizeof(sk_err); + + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0) + err = -errno; + else + err = -sk_err; + + if (err < 0) + ERROR_FAILED(&gerr, "HUP or ERR on socket", -err); + } + + accept->connect(io, gerr, accept->user_data); + + g_clear_error(&gerr); + + return FALSE; +} + +static gboolean connect_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct connect *conn = user_data; + GError *gerr = NULL; + int err, sk_err, sock; + socklen_t len = sizeof(sk_err); + + /* If the user aborted this connect attempt */ + if ((cond & G_IO_NVAL) || check_nval(io)) + return FALSE; + + sock = g_io_channel_unix_get_fd(io); + + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0) + err = -errno; + else + err = -sk_err; + + if (err < 0) + ERROR_FAILED(&gerr, "connect error", -err); + + conn->connect(io, gerr, conn->user_data); + + g_clear_error(&gerr); + + return FALSE; +} + +static gboolean server_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct server *server = user_data; + int srv_sock, cli_sock; + GIOChannel *cli_io; + + /* If the user closed the server */ + if ((cond & G_IO_NVAL) || check_nval(io)) + return FALSE; + + srv_sock = g_io_channel_unix_get_fd(io); + + cli_sock = accept(srv_sock, NULL, NULL); + if (cli_sock < 0) + return TRUE; + + cli_io = g_io_channel_unix_new(cli_sock); + + g_io_channel_set_close_on_unref(cli_io, TRUE); + g_io_channel_set_flags(cli_io, G_IO_FLAG_NONBLOCK, NULL); + + if (server->confirm) + server->confirm(cli_io, server->user_data); + else + server->connect(cli_io, NULL, server->user_data); + + g_io_channel_unref(cli_io); + + return TRUE; +} + +static void server_add(GIOChannel *io, BtIOConnect connect, + BtIOConfirm confirm, gpointer user_data, + GDestroyNotify destroy) +{ + struct server *server; + GIOCondition cond; + + server = g_new0(struct server, 1); + server->connect = connect; + server->confirm = confirm; + server->user_data = user_data; + server->destroy = destroy; + + cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, server_cb, server, + (GDestroyNotify) server_remove); +} + +static void connect_add(GIOChannel *io, BtIOConnect connect, + gpointer user_data, GDestroyNotify destroy) +{ + struct connect *conn; + GIOCondition cond; + + conn = g_new0(struct connect, 1); + conn->connect = connect; + conn->user_data = user_data; + conn->destroy = destroy; + + cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, connect_cb, conn, + (GDestroyNotify) connect_remove); +} + +static void accept_add(GIOChannel *io, BtIOConnect connect, gpointer user_data, + GDestroyNotify destroy) +{ + struct accept *accept; + GIOCondition cond; + + accept = g_new0(struct accept, 1); + accept->connect = connect; + accept->user_data = user_data; + accept->destroy = destroy; + + cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, accept_cb, accept, + (GDestroyNotify) accept_remove); +} + +static int l2cap_bind(int sock, const bdaddr_t *src, uint8_t src_type, + uint16_t psm, uint16_t cid, GError **err) +{ + struct sockaddr_l2 addr; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + + if (cid) + addr.l2_cid = htobs(cid); + else + addr.l2_psm = htobs(psm); + + addr.l2_bdaddr_type = src_type; + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + int error = -errno; + ERROR_FAILED(err, "l2cap_bind", errno); + return error; + } + + return 0; +} + +static int l2cap_connect(int sock, const bdaddr_t *dst, uint8_t dst_type, + uint16_t psm, uint16_t cid) +{ + int err; + struct sockaddr_l2 addr; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + if (cid) + addr.l2_cid = htobs(cid); + else + addr.l2_psm = htobs(psm); + + addr.l2_bdaddr_type = dst_type; + + err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + return -errno; + + return 0; +} + +static int l2cap_set_master(int sock, int master) +{ + int flags; + socklen_t len; + + len = sizeof(flags); + if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len) < 0) + return -errno; + + if (master) { + if (flags & L2CAP_LM_MASTER) + return 0; + flags |= L2CAP_LM_MASTER; + } else { + if (!(flags & L2CAP_LM_MASTER)) + return 0; + flags &= ~L2CAP_LM_MASTER; + } + + if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, sizeof(flags)) < 0) + return -errno; + + return 0; +} + +static int rfcomm_set_master(int sock, int master) +{ + int flags; + socklen_t len; + + len = sizeof(flags); + if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, &len) < 0) + return -errno; + + if (master) { + if (flags & RFCOMM_LM_MASTER) + return 0; + flags |= RFCOMM_LM_MASTER; + } else { + if (!(flags & RFCOMM_LM_MASTER)) + return 0; + flags &= ~RFCOMM_LM_MASTER; + } + + if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, sizeof(flags)) < 0) + return -errno; + + return 0; +} + +static int l2cap_set_lm(int sock, int level) +{ + int lm_map[] = { + 0, + L2CAP_LM_AUTH, + L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT, + L2CAP_LM_AUTH | L2CAP_LM_ENCRYPT | L2CAP_LM_SECURE, + }, opt = lm_map[level]; + + if (setsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) + return -errno; + + return 0; +} + +static int rfcomm_set_lm(int sock, int level) +{ + int lm_map[] = { + 0, + RFCOMM_LM_AUTH, + RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT, + RFCOMM_LM_AUTH | RFCOMM_LM_ENCRYPT | RFCOMM_LM_SECURE, + }, opt = lm_map[level]; + + if (setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, sizeof(opt)) < 0) + return -errno; + + return 0; +} + +static gboolean set_sec_level(int sock, BtIOType type, int level, GError **err) +{ + struct bt_security sec; + int ret; + + if (level < BT_SECURITY_LOW || level > BT_SECURITY_HIGH) { + g_set_error(err, BT_IO_ERROR, EINVAL, + "Valid security level range is %d-%d", + BT_SECURITY_LOW, BT_SECURITY_HIGH); + return FALSE; + } + + memset(&sec, 0, sizeof(sec)); + sec.level = level; + + if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, + sizeof(sec)) == 0) + return TRUE; + + if (errno != ENOPROTOOPT) { + ERROR_FAILED(err, "setsockopt(BT_SECURITY)", errno); + return FALSE; + } + + if (type == BT_IO_L2CAP) + ret = l2cap_set_lm(sock, level); + else + ret = rfcomm_set_lm(sock, level); + + if (ret < 0) { + ERROR_FAILED(err, "setsockopt(LM)", -ret); + return FALSE; + } + + return TRUE; +} + +static int l2cap_get_lm(int sock, int *sec_level) +{ + int opt; + socklen_t len; + + len = sizeof(opt); + if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &opt, &len) < 0) + return -errno; + + *sec_level = 0; + + if (opt & L2CAP_LM_AUTH) + *sec_level = BT_SECURITY_LOW; + if (opt & L2CAP_LM_ENCRYPT) + *sec_level = BT_SECURITY_MEDIUM; + if (opt & L2CAP_LM_SECURE) + *sec_level = BT_SECURITY_HIGH; + + return 0; +} + +static int rfcomm_get_lm(int sock, int *sec_level) +{ + int opt; + socklen_t len; + + len = sizeof(opt); + if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &opt, &len) < 0) + return -errno; + + *sec_level = 0; + + if (opt & RFCOMM_LM_AUTH) + *sec_level = BT_SECURITY_LOW; + if (opt & RFCOMM_LM_ENCRYPT) + *sec_level = BT_SECURITY_MEDIUM; + if (opt & RFCOMM_LM_SECURE) + *sec_level = BT_SECURITY_HIGH; + + return 0; +} + +static gboolean get_sec_level(int sock, BtIOType type, int *level, + GError **err) +{ + struct bt_security sec; + socklen_t len; + int ret; + + memset(&sec, 0, sizeof(sec)); + len = sizeof(sec); + if (getsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) == 0) { + *level = sec.level; + return TRUE; + } + + if (errno != ENOPROTOOPT) { + ERROR_FAILED(err, "getsockopt(BT_SECURITY)", errno); + return FALSE; + } + + if (type == BT_IO_L2CAP) + ret = l2cap_get_lm(sock, level); + else + ret = rfcomm_get_lm(sock, level); + + if (ret < 0) { + ERROR_FAILED(err, "getsockopt(LM)", -ret); + return FALSE; + } + + return TRUE; +} + +static int l2cap_set_flushable(int sock, gboolean flushable) +{ + int f; + + f = flushable; + if (setsockopt(sock, SOL_BLUETOOTH, BT_FLUSHABLE, &f, sizeof(f)) < 0) + return -errno; + + return 0; +} + +static int set_priority(int sock, uint32_t prio) +{ + if (setsockopt(sock, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)) < 0) + return -errno; + + return 0; +} + +static gboolean get_key_size(int sock, int *size, GError **err) +{ + struct bt_security sec; + socklen_t len; + + memset(&sec, 0, sizeof(sec)); + len = sizeof(sec); + if (getsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) == 0) { + *size = sec.key_size; + return TRUE; + } + + return FALSE; +} + +static gboolean set_l2opts(int sock, uint16_t imtu, uint16_t omtu, + uint8_t mode, GError **err) +{ + struct l2cap_options l2o; + socklen_t len; + + memset(&l2o, 0, sizeof(l2o)); + len = sizeof(l2o); + if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) { + ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno); + return FALSE; + } + + if (imtu) + l2o.imtu = imtu; + if (omtu) + l2o.omtu = omtu; + if (mode) + l2o.mode = mode; + + if (setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o)) < 0) { + ERROR_FAILED(err, "setsockopt(L2CAP_OPTIONS)", errno); + return FALSE; + } + + return TRUE; +} + +static gboolean set_le_imtu(int sock, uint16_t imtu, GError **err) +{ + if (setsockopt(sock, SOL_BLUETOOTH, BT_RCVMTU, &imtu, + sizeof(imtu)) < 0) { + ERROR_FAILED(err, "setsockopt(BT_RCVMTU)", errno); + return FALSE; + } + + return TRUE; +} + +static gboolean l2cap_set(int sock, uint8_t src_type, int sec_level, + uint16_t imtu, uint16_t omtu, uint8_t mode, + int master, int flushable, uint32_t priority, + GError **err) +{ + if (imtu || omtu || mode) { + gboolean ret; + + if (src_type == BDADDR_BREDR) + ret = set_l2opts(sock, imtu, omtu, mode, err); + else + ret = set_le_imtu(sock, imtu, err); + + if (!ret) + return ret; + } + + if (master >= 0 && l2cap_set_master(sock, master) < 0) { + ERROR_FAILED(err, "l2cap_set_master", errno); + return FALSE; + } + + if (flushable >= 0 && l2cap_set_flushable(sock, flushable) < 0) { + ERROR_FAILED(err, "l2cap_set_flushable", errno); + return FALSE; + } + + if (priority > 0 && set_priority(sock, priority) < 0) { + ERROR_FAILED(err, "set_priority", errno); + return FALSE; + } + + if (sec_level && !set_sec_level(sock, BT_IO_L2CAP, sec_level, err)) + return FALSE; + + return TRUE; +} + +static int rfcomm_bind(int sock, + const bdaddr_t *src, uint8_t channel, GError **err) +{ + struct sockaddr_rc addr; + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, src); + addr.rc_channel = channel; + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + int error = -errno; + ERROR_FAILED(err, "rfcomm_bind", errno); + return error; + } + + return 0; +} + +static int rfcomm_connect(int sock, const bdaddr_t *dst, uint8_t channel) +{ + int err; + struct sockaddr_rc addr; + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, dst); + addr.rc_channel = channel; + + err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + return -errno; + + return 0; +} + +static gboolean rfcomm_set(int sock, int sec_level, int master, GError **err) +{ + if (sec_level && !set_sec_level(sock, BT_IO_RFCOMM, sec_level, err)) + return FALSE; + + if (master >= 0 && rfcomm_set_master(sock, master) < 0) { + ERROR_FAILED(err, "rfcomm_set_master", errno); + return FALSE; + } + + return TRUE; +} + +static int sco_bind(int sock, const bdaddr_t *src, GError **err) +{ + struct sockaddr_sco addr; + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, src); + + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + int error = -errno; + ERROR_FAILED(err, "sco_bind", errno); + return error; + } + + return 0; +} + +static int sco_connect(int sock, const bdaddr_t *dst) +{ + struct sockaddr_sco addr; + int err; + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, dst); + + err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + return -errno; + + return 0; +} + +static gboolean sco_set(int sock, uint16_t mtu, uint16_t voice, GError **err) +{ + struct sco_options sco_opt; + struct bt_voice bt_voice; + socklen_t len; + + if (!mtu) + goto voice; + + len = sizeof(sco_opt); + memset(&sco_opt, 0, len); + if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) { + ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno); + return FALSE; + } + + sco_opt.mtu = mtu; + if (setsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, + sizeof(sco_opt)) < 0) { + ERROR_FAILED(err, "setsockopt(SCO_OPTIONS)", errno); + return FALSE; + } + +voice: + if (!voice) + return TRUE; + + memset(&bt_voice, 0, sizeof(bt_voice)); + bt_voice.setting = voice; + if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &bt_voice, + sizeof(bt_voice)) < 0) { + ERROR_FAILED(err, "setsockopt(BT_VOICE)", errno); + return FALSE; + } + + return TRUE; +} + +static gboolean parse_set_opts(struct set_opts *opts, GError **err, + BtIOOption opt1, va_list args) +{ + BtIOOption opt = opt1; + const char *str; + + memset(opts, 0, sizeof(*opts)); + + /* Set defaults */ + opts->type = BT_IO_SCO; + opts->defer = DEFAULT_DEFER_TIMEOUT; + opts->master = -1; + opts->mode = L2CAP_MODE_BASIC; + opts->flushable = -1; + opts->priority = 0; + opts->src_type = BDADDR_BREDR; + opts->dst_type = BDADDR_BREDR; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + str = va_arg(args, const char *); + str2ba(str, &opts->src); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(&opts->src, va_arg(args, const bdaddr_t *)); + break; + case BT_IO_OPT_SOURCE_TYPE: + opts->src_type = va_arg(args, int); + break; + case BT_IO_OPT_DEST: + str2ba(va_arg(args, const char *), &opts->dst); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(&opts->dst, va_arg(args, const bdaddr_t *)); + break; + case BT_IO_OPT_DEST_TYPE: + opts->dst_type = va_arg(args, int); + break; + case BT_IO_OPT_DEFER_TIMEOUT: + opts->defer = va_arg(args, int); + break; + case BT_IO_OPT_SEC_LEVEL: + opts->sec_level = va_arg(args, int); + break; + case BT_IO_OPT_CHANNEL: + opts->type = BT_IO_RFCOMM; + opts->channel = va_arg(args, int); + break; + case BT_IO_OPT_PSM: + opts->type = BT_IO_L2CAP; + opts->psm = va_arg(args, int); + break; + case BT_IO_OPT_CID: + opts->type = BT_IO_L2CAP; + opts->cid = va_arg(args, int); + break; + case BT_IO_OPT_MTU: + opts->mtu = va_arg(args, int); + opts->imtu = opts->mtu; + opts->omtu = opts->mtu; + break; + case BT_IO_OPT_OMTU: + opts->omtu = va_arg(args, int); + if (!opts->mtu) + opts->mtu = opts->omtu; + break; + case BT_IO_OPT_IMTU: + opts->imtu = va_arg(args, int); + if (!opts->mtu) + opts->mtu = opts->imtu; + break; + case BT_IO_OPT_MASTER: + opts->master = va_arg(args, gboolean); + break; + case BT_IO_OPT_MODE: + opts->mode = va_arg(args, int); + break; + case BT_IO_OPT_FLUSHABLE: + opts->flushable = va_arg(args, gboolean); + break; + case BT_IO_OPT_PRIORITY: + opts->priority = va_arg(args, int); + break; + case BT_IO_OPT_VOICE: + opts->voice = va_arg(args, int); + break; + case BT_IO_OPT_INVALID: + case BT_IO_OPT_KEY_SIZE: + case BT_IO_OPT_SOURCE_CHANNEL: + case BT_IO_OPT_DEST_CHANNEL: + case BT_IO_OPT_HANDLE: + case BT_IO_OPT_CLASS: + default: + g_set_error(err, BT_IO_ERROR, EINVAL, + "Unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static gboolean get_src(int sock, void *src, socklen_t len, GError **err) +{ + socklen_t olen; + + memset(src, 0, len); + olen = len; + if (getsockname(sock, src, &olen) < 0) { + ERROR_FAILED(err, "getsockname", errno); + return FALSE; + } + + return TRUE; +} + +static gboolean get_dst(int sock, void *dst, socklen_t len, GError **err) +{ + socklen_t olen; + + memset(dst, 0, len); + olen = len; + if (getpeername(sock, dst, &olen) < 0) { + ERROR_FAILED(err, "getpeername", errno); + return FALSE; + } + + return TRUE; +} + +static int l2cap_get_info(int sock, uint16_t *handle, uint8_t *dev_class) +{ + struct l2cap_conninfo info; + socklen_t len; + + len = sizeof(info); + if (getsockopt(sock, SOL_L2CAP, L2CAP_CONNINFO, &info, &len) < 0) + return -errno; + + if (handle) + *handle = info.hci_handle; + + if (dev_class) + memcpy(dev_class, info.dev_class, 3); + + return 0; +} + +static int l2cap_get_flushable(int sock, gboolean *flushable) +{ + int f; + socklen_t len; + + f = 0; + len = sizeof(f); + if (getsockopt(sock, SOL_BLUETOOTH, BT_FLUSHABLE, &f, &len) < 0) + return -errno; + + if (f) + *flushable = TRUE; + else + *flushable = FALSE; + + return 0; +} + +static int get_priority(int sock, uint32_t *prio) +{ + socklen_t len; + + len = sizeof(*prio); + if (getsockopt(sock, SOL_SOCKET, SO_PRIORITY, prio, &len) < 0) + return -errno; + + return 0; +} + +static gboolean l2cap_get(int sock, GError **err, BtIOOption opt1, + va_list args) +{ + BtIOOption opt = opt1; + struct sockaddr_l2 src, dst; + struct l2cap_options l2o; + int flags; + uint8_t dev_class[3]; + uint16_t handle = 0; + socklen_t len; + gboolean flushable = FALSE, have_dst = FALSE; + uint32_t priority; + + if (!get_src(sock, &src, sizeof(src), err)) + return FALSE; + + memset(&l2o, 0, sizeof(l2o)); + + if (src.l2_bdaddr_type != BDADDR_BREDR) { + len = sizeof(l2o.imtu); + if (getsockopt(sock, SOL_BLUETOOTH, BT_RCVMTU, + &l2o.imtu, &len) == 0) + goto parse_opts; + + /* Non-LE CoC enabled kernels will return one of these + * in which case we need to fall back to L2CAP_OPTIONS. + */ + if (errno != EPROTONOSUPPORT && errno != ENOPROTOOPT) { + ERROR_FAILED(err, "getsockopt(BT_RCVMTU)", errno); + return FALSE; + } + } + + len = sizeof(l2o); + if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) { + ERROR_FAILED(err, "getsockopt(L2CAP_OPTIONS)", errno); + return FALSE; + } + +parse_opts: + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + ba2str(&src.l2_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &src.l2_bdaddr); + break; + case BT_IO_OPT_DEST: + if (!have_dst) + have_dst = get_dst(sock, &dst, sizeof(dst), + err); + if (!have_dst) + return FALSE; + ba2str(&dst.l2_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + if (!have_dst) + have_dst = get_dst(sock, &dst, sizeof(dst), + err); + if (!have_dst) + return FALSE; + bacpy(va_arg(args, bdaddr_t *), &dst.l2_bdaddr); + break; + case BT_IO_OPT_DEST_TYPE: + if (!have_dst) + have_dst = get_dst(sock, &dst, sizeof(dst), + err); + if (!have_dst) + return FALSE; + *(va_arg(args, uint8_t *)) = dst.l2_bdaddr_type; + break; + case BT_IO_OPT_DEFER_TIMEOUT: + len = sizeof(int); + if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, + va_arg(args, int *), &len) < 0) { + ERROR_FAILED(err, "getsockopt(DEFER_SETUP)", + errno); + return FALSE; + } + break; + case BT_IO_OPT_SEC_LEVEL: + if (!get_sec_level(sock, BT_IO_L2CAP, + va_arg(args, int *), err)) + return FALSE; + break; + case BT_IO_OPT_KEY_SIZE: + if (!get_key_size(sock, va_arg(args, int *), err)) + return FALSE; + break; + case BT_IO_OPT_PSM: + if (src.l2_psm) { + *(va_arg(args, uint16_t *)) = btohs(src.l2_psm); + break; + } + + if (!have_dst) + have_dst = get_dst(sock, &dst, sizeof(dst), + err); + if (!have_dst) + return FALSE; + + *(va_arg(args, uint16_t *)) = btohs(dst.l2_psm); + break; + case BT_IO_OPT_CID: + if (src.l2_cid) { + *(va_arg(args, uint16_t *)) = btohs(src.l2_cid); + break; + } + + if (!have_dst) + have_dst = get_dst(sock, &dst, sizeof(dst), + err); + if (!have_dst) + return FALSE; + + *(va_arg(args, uint16_t *)) = btohs(dst.l2_cid); + break; + case BT_IO_OPT_OMTU: + if (src.l2_bdaddr_type == BDADDR_BREDR) { + *(va_arg(args, uint16_t *)) = l2o.omtu; + break; + } + + len = sizeof(l2o.omtu); + if (getsockopt(sock, SOL_BLUETOOTH, BT_SNDMTU, + &l2o.omtu, &len) < 0) { + ERROR_FAILED(err, "getsockopt(BT_SNDMTU)", + errno); + return FALSE; + } + + *(va_arg(args, uint16_t *)) = l2o.omtu; + break; + case BT_IO_OPT_IMTU: + *(va_arg(args, uint16_t *)) = l2o.imtu; + break; + case BT_IO_OPT_MASTER: + len = sizeof(flags); + if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, + &len) < 0) { + ERROR_FAILED(err, "getsockopt(L2CAP_LM)", + errno); + return FALSE; + } + *(va_arg(args, gboolean *)) = + (flags & L2CAP_LM_MASTER) ? TRUE : FALSE; + break; + case BT_IO_OPT_HANDLE: + if (l2cap_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "L2CAP_CONNINFO", errno); + return FALSE; + } + *(va_arg(args, uint16_t *)) = handle; + break; + case BT_IO_OPT_CLASS: + if (l2cap_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "L2CAP_CONNINFO", errno); + return FALSE; + } + memcpy(va_arg(args, uint8_t *), dev_class, 3); + break; + case BT_IO_OPT_MODE: + *(va_arg(args, uint8_t *)) = l2o.mode; + break; + case BT_IO_OPT_FLUSHABLE: + if (l2cap_get_flushable(sock, &flushable) < 0) { + ERROR_FAILED(err, "get_flushable", errno); + return FALSE; + } + *(va_arg(args, gboolean *)) = flushable; + break; + case BT_IO_OPT_PRIORITY: + if (get_priority(sock, &priority) < 0) { + ERROR_FAILED(err, "get_priority", errno); + return FALSE; + } + *(va_arg(args, uint32_t *)) = priority; + break; + case BT_IO_OPT_INVALID: + case BT_IO_OPT_SOURCE_TYPE: + case BT_IO_OPT_CHANNEL: + case BT_IO_OPT_SOURCE_CHANNEL: + case BT_IO_OPT_DEST_CHANNEL: + case BT_IO_OPT_MTU: + case BT_IO_OPT_VOICE: + default: + g_set_error(err, BT_IO_ERROR, EINVAL, + "Unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static int rfcomm_get_info(int sock, uint16_t *handle, uint8_t *dev_class) +{ + struct rfcomm_conninfo info; + socklen_t len; + + len = sizeof(info); + if (getsockopt(sock, SOL_RFCOMM, RFCOMM_CONNINFO, &info, &len) < 0) + return -errno; + + if (handle) + *handle = info.hci_handle; + + if (dev_class) + memcpy(dev_class, info.dev_class, 3); + + return 0; +} + +static gboolean rfcomm_get(int sock, GError **err, BtIOOption opt1, + va_list args) +{ + BtIOOption opt = opt1; + struct sockaddr_rc src, dst; + gboolean have_dst = FALSE; + int flags; + socklen_t len; + uint8_t dev_class[3]; + uint16_t handle = 0; + + if (!get_src(sock, &src, sizeof(src), err)) + return FALSE; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + ba2str(&src.rc_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &src.rc_bdaddr); + break; + case BT_IO_OPT_DEST: + if (!have_dst) + have_dst = get_dst(sock, &dst, sizeof(dst), + err); + if (!have_dst) + return FALSE; + ba2str(&dst.rc_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + if (!have_dst) + have_dst = get_dst(sock, &dst, sizeof(dst), + err); + if (!have_dst) + return FALSE; + bacpy(va_arg(args, bdaddr_t *), &dst.rc_bdaddr); + break; + case BT_IO_OPT_DEFER_TIMEOUT: + len = sizeof(int); + if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, + va_arg(args, int *), &len) < 0) { + ERROR_FAILED(err, "getsockopt(DEFER_SETUP)", + errno); + return FALSE; + } + break; + case BT_IO_OPT_SEC_LEVEL: + if (!get_sec_level(sock, BT_IO_RFCOMM, + va_arg(args, int *), err)) + return FALSE; + break; + case BT_IO_OPT_CHANNEL: + if (src.rc_channel) { + *(va_arg(args, uint8_t *)) = src.rc_channel; + break; + } + + if (!have_dst) + have_dst = get_dst(sock, &dst, sizeof(dst), + err); + if (!have_dst) + return FALSE; + + *(va_arg(args, uint8_t *)) = dst.rc_channel; + break; + case BT_IO_OPT_SOURCE_CHANNEL: + *(va_arg(args, uint8_t *)) = src.rc_channel; + break; + case BT_IO_OPT_DEST_CHANNEL: + if (!have_dst) + have_dst = get_dst(sock, &dst, sizeof(dst), + err); + if (!have_dst) + return FALSE; + + *(va_arg(args, uint8_t *)) = dst.rc_channel; + break; + case BT_IO_OPT_MASTER: + len = sizeof(flags); + if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &flags, + &len) < 0) { + ERROR_FAILED(err, "getsockopt(RFCOMM_LM)", + errno); + return FALSE; + } + *(va_arg(args, gboolean *)) = + (flags & RFCOMM_LM_MASTER) ? TRUE : FALSE; + break; + case BT_IO_OPT_HANDLE: + if (rfcomm_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "RFCOMM_CONNINFO", errno); + return FALSE; + } + *(va_arg(args, uint16_t *)) = handle; + break; + case BT_IO_OPT_CLASS: + if (rfcomm_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "RFCOMM_CONNINFO", errno); + return FALSE; + } + memcpy(va_arg(args, uint8_t *), dev_class, 3); + break; + case BT_IO_OPT_SOURCE_TYPE: + case BT_IO_OPT_DEST_TYPE: + case BT_IO_OPT_KEY_SIZE: + case BT_IO_OPT_PSM: + case BT_IO_OPT_CID: + case BT_IO_OPT_MTU: + case BT_IO_OPT_OMTU: + case BT_IO_OPT_IMTU: + case BT_IO_OPT_MODE: + case BT_IO_OPT_FLUSHABLE: + case BT_IO_OPT_PRIORITY: + case BT_IO_OPT_VOICE: + case BT_IO_OPT_INVALID: + default: + g_set_error(err, BT_IO_ERROR, EINVAL, + "Unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static int sco_get_info(int sock, uint16_t *handle, uint8_t *dev_class) +{ + struct sco_conninfo info; + socklen_t len; + + len = sizeof(info); + if (getsockopt(sock, SOL_SCO, SCO_CONNINFO, &info, &len) < 0) + return -errno; + + if (handle) + *handle = info.hci_handle; + + if (dev_class) + memcpy(dev_class, info.dev_class, 3); + + return 0; +} + +static gboolean sco_get(int sock, GError **err, BtIOOption opt1, va_list args) +{ + BtIOOption opt = opt1; + struct sockaddr_sco src, dst; + struct sco_options sco_opt; + socklen_t len; + uint8_t dev_class[3]; + uint16_t handle = 0; + + len = sizeof(sco_opt); + memset(&sco_opt, 0, len); + if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) { + ERROR_FAILED(err, "getsockopt(SCO_OPTIONS)", errno); + return FALSE; + } + + if (!get_src(sock, &src, sizeof(src), err)) + return FALSE; + + if (!get_dst(sock, &dst, sizeof(dst), err)) + return FALSE; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + ba2str(&src.sco_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &src.sco_bdaddr); + break; + case BT_IO_OPT_DEST: + ba2str(&dst.sco_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &dst.sco_bdaddr); + break; + case BT_IO_OPT_MTU: + case BT_IO_OPT_IMTU: + case BT_IO_OPT_OMTU: + *(va_arg(args, uint16_t *)) = sco_opt.mtu; + break; + case BT_IO_OPT_HANDLE: + if (sco_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "SCO_CONNINFO", errno); + return FALSE; + } + *(va_arg(args, uint16_t *)) = handle; + break; + case BT_IO_OPT_CLASS: + if (sco_get_info(sock, &handle, dev_class) < 0) { + ERROR_FAILED(err, "SCO_CONNINFO", errno); + return FALSE; + } + memcpy(va_arg(args, uint8_t *), dev_class, 3); + break; + case BT_IO_OPT_SOURCE_TYPE: + case BT_IO_OPT_DEST_TYPE: + case BT_IO_OPT_DEFER_TIMEOUT: + case BT_IO_OPT_SEC_LEVEL: + case BT_IO_OPT_KEY_SIZE: + case BT_IO_OPT_CHANNEL: + case BT_IO_OPT_SOURCE_CHANNEL: + case BT_IO_OPT_DEST_CHANNEL: + case BT_IO_OPT_PSM: + case BT_IO_OPT_CID: + case BT_IO_OPT_MASTER: + case BT_IO_OPT_MODE: + case BT_IO_OPT_FLUSHABLE: + case BT_IO_OPT_PRIORITY: + case BT_IO_OPT_VOICE: + case BT_IO_OPT_INVALID: + default: + g_set_error(err, BT_IO_ERROR, EINVAL, + "Unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static gboolean get_valist(GIOChannel *io, BtIOType type, GError **err, + BtIOOption opt1, va_list args) +{ + int sock; + + sock = g_io_channel_unix_get_fd(io); + + switch (type) { + case BT_IO_L2CAP: + return l2cap_get(sock, err, opt1, args); + case BT_IO_RFCOMM: + return rfcomm_get(sock, err, opt1, args); + case BT_IO_SCO: + return sco_get(sock, err, opt1, args); + case BT_IO_INVALID: + default: + g_set_error(err, BT_IO_ERROR, EINVAL, + "Unknown BtIO type %d", type); + return FALSE; + } +} + +gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data, + GDestroyNotify destroy, GError **err) +{ + int sock; + char c; + struct pollfd pfd; + + sock = g_io_channel_unix_get_fd(io); + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = sock; + pfd.events = POLLOUT; + + if (poll(&pfd, 1, 0) < 0) { + ERROR_FAILED(err, "poll", errno); + return FALSE; + } + + if (!(pfd.revents & POLLOUT)) { + if (read(sock, &c, 1) < 0) { + ERROR_FAILED(err, "read", errno); + return FALSE; + } + } + + accept_add(io, connect, user_data, destroy); + + return TRUE; +} + +gboolean bt_io_set(GIOChannel *io, GError **err, BtIOOption opt1, ...) +{ + va_list args; + gboolean ret; + struct set_opts opts; + int sock; + BtIOType type; + + va_start(args, opt1); + ret = parse_set_opts(&opts, err, opt1, args); + va_end(args); + + if (!ret) + return ret; + + type = bt_io_get_type(io, err); + if (type == BT_IO_INVALID) + return FALSE; + + sock = g_io_channel_unix_get_fd(io); + + switch (type) { + case BT_IO_L2CAP: + return l2cap_set(sock, opts.src_type, opts.sec_level, opts.imtu, + opts.omtu, opts.mode, opts.master, + opts.flushable, opts.priority, err); + case BT_IO_RFCOMM: + return rfcomm_set(sock, opts.sec_level, opts.master, err); + case BT_IO_SCO: + return sco_set(sock, opts.mtu, opts.voice, err); + case BT_IO_INVALID: + default: + g_set_error(err, BT_IO_ERROR, EINVAL, + "Unknown BtIO type %d", type); + return FALSE; + } + +} + +gboolean bt_io_get(GIOChannel *io, GError **err, BtIOOption opt1, ...) +{ + va_list args; + gboolean ret; + BtIOType type; + + type = bt_io_get_type(io, err); + if (type == BT_IO_INVALID) + return FALSE; + + va_start(args, opt1); + ret = get_valist(io, type, err, opt1, args); + va_end(args); + + return ret; +} + +static GIOChannel *create_io(gboolean server, struct set_opts *opts, + GError **err) +{ + int sock; + GIOChannel *io; + + switch (opts->type) { + case BT_IO_L2CAP: + sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sock < 0) { + ERROR_FAILED(err, "socket(SEQPACKET, L2CAP)", errno); + return NULL; + } + if (l2cap_bind(sock, &opts->src, opts->src_type, + server ? opts->psm : 0, opts->cid, err) < 0) + goto failed; + if (!l2cap_set(sock, opts->src_type, opts->sec_level, + opts->imtu, opts->omtu, opts->mode, + opts->master, opts->flushable, opts->priority, + err)) + goto failed; + break; + case BT_IO_RFCOMM: + sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sock < 0) { + ERROR_FAILED(err, "socket(STREAM, RFCOMM)", errno); + return NULL; + } + if (rfcomm_bind(sock, &opts->src, + server ? opts->channel : 0, err) < 0) + goto failed; + if (!rfcomm_set(sock, opts->sec_level, opts->master, err)) + goto failed; + break; + case BT_IO_SCO: + sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + if (sock < 0) { + ERROR_FAILED(err, "socket(SEQPACKET, SCO)", errno); + return NULL; + } + if (sco_bind(sock, &opts->src, err) < 0) + goto failed; + if (!sco_set(sock, opts->mtu, opts->voice, err)) + goto failed; + break; + case BT_IO_INVALID: + default: + g_set_error(err, BT_IO_ERROR, EINVAL, + "Unknown BtIO type %d", opts->type); + return NULL; + } + + io = g_io_channel_unix_new(sock); + + g_io_channel_set_close_on_unref(io, TRUE); + g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); + + return io; + +failed: + close(sock); + + return NULL; +} + +GIOChannel *bt_io_connect(BtIOConnect connect, gpointer user_data, + GDestroyNotify destroy, GError **gerr, + BtIOOption opt1, ...) +{ + GIOChannel *io; + va_list args; + struct set_opts opts; + int err, sock; + gboolean ret; + + va_start(args, opt1); + ret = parse_set_opts(&opts, gerr, opt1, args); + va_end(args); + + if (ret == FALSE) + return NULL; + + io = create_io(FALSE, &opts, gerr); + if (io == NULL) + return NULL; + + sock = g_io_channel_unix_get_fd(io); + + switch (opts.type) { + case BT_IO_L2CAP: + err = l2cap_connect(sock, &opts.dst, opts.dst_type, + opts.psm, opts.cid); + break; + case BT_IO_RFCOMM: + err = rfcomm_connect(sock, &opts.dst, opts.channel); + break; + case BT_IO_SCO: + err = sco_connect(sock, &opts.dst); + break; + case BT_IO_INVALID: + default: + g_set_error(gerr, BT_IO_ERROR, EINVAL, + "Unknown BtIO type %d", opts.type); + return NULL; + } + + if (err < 0) { + ERROR_FAILED(gerr, "connect", -err); + g_io_channel_unref(io); + return NULL; + } + + connect_add(io, connect, user_data, destroy); + + return io; +} + +GIOChannel *bt_io_listen(BtIOConnect connect, BtIOConfirm confirm, + gpointer user_data, GDestroyNotify destroy, + GError **err, BtIOOption opt1, ...) +{ + GIOChannel *io; + va_list args; + struct set_opts opts; + int sock; + gboolean ret; + + va_start(args, opt1); + ret = parse_set_opts(&opts, err, opt1, args); + va_end(args); + + if (ret == FALSE) + return NULL; + + io = create_io(TRUE, &opts, err); + if (io == NULL) + return NULL; + + sock = g_io_channel_unix_get_fd(io); + + if (confirm) + setsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, &opts.defer, + sizeof(opts.defer)); + + if (listen(sock, 5) < 0) { + ERROR_FAILED(err, "listen", errno); + g_io_channel_unref(io); + return NULL; + } + + server_add(io, connect, confirm, user_data, destroy); + + return io; +} + +GQuark bt_io_error_quark(void) +{ + return g_quark_from_static_string("bt-io-error-quark"); +} diff --git a/btio/btio.h b/btio/btio.h new file mode 100644 index 0000000..2dce9f0 --- /dev/null +++ b/btio/btio.h @@ -0,0 +1,95 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009-2010 Marcel Holtmann + * Copyright (C) 2009-2010 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef BT_IO_H +#define BT_IO_H + +#include + +#define BT_IO_ERROR bt_io_error_quark() + +GQuark bt_io_error_quark(void); + +typedef enum { + BT_IO_OPT_INVALID = 0, + BT_IO_OPT_SOURCE, + BT_IO_OPT_SOURCE_BDADDR, + BT_IO_OPT_SOURCE_TYPE, + BT_IO_OPT_DEST, + BT_IO_OPT_DEST_BDADDR, + BT_IO_OPT_DEST_TYPE, + BT_IO_OPT_DEFER_TIMEOUT, + BT_IO_OPT_SEC_LEVEL, + BT_IO_OPT_KEY_SIZE, + BT_IO_OPT_CHANNEL, + BT_IO_OPT_SOURCE_CHANNEL, + BT_IO_OPT_DEST_CHANNEL, + BT_IO_OPT_PSM, + BT_IO_OPT_CID, + BT_IO_OPT_MTU, + BT_IO_OPT_OMTU, + BT_IO_OPT_IMTU, + BT_IO_OPT_MASTER, + BT_IO_OPT_HANDLE, + BT_IO_OPT_CLASS, + BT_IO_OPT_MODE, + BT_IO_OPT_FLUSHABLE, + BT_IO_OPT_PRIORITY, + BT_IO_OPT_VOICE, +} BtIOOption; + +typedef enum { + BT_IO_SEC_SDP = 0, + BT_IO_SEC_LOW, + BT_IO_SEC_MEDIUM, + BT_IO_SEC_HIGH, +} BtIOSecLevel; + +typedef enum { + BT_IO_MODE_BASIC = 0, + BT_IO_MODE_RETRANS, + BT_IO_MODE_FLOWCTL, + BT_IO_MODE_ERTM, + BT_IO_MODE_STREAMING +} BtIOMode; + +typedef void (*BtIOConfirm)(GIOChannel *io, gpointer user_data); + +typedef void (*BtIOConnect)(GIOChannel *io, GError *err, gpointer user_data); + +gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data, + GDestroyNotify destroy, GError **err); + +gboolean bt_io_set(GIOChannel *io, GError **err, BtIOOption opt1, ...); + +gboolean bt_io_get(GIOChannel *io, GError **err, BtIOOption opt1, ...); + +GIOChannel *bt_io_connect(BtIOConnect connect, gpointer user_data, + GDestroyNotify destroy, GError **gerr, + BtIOOption opt1, ...); + +GIOChannel *bt_io_listen(BtIOConnect connect, BtIOConfirm confirm, + gpointer user_data, GDestroyNotify destroy, + GError **err, BtIOOption opt1, ...); + +#endif diff --git a/client/advertising.c b/client/advertising.c new file mode 100644 index 0000000..afc8754 --- /dev/null +++ b/client/advertising.c @@ -0,0 +1,965 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2016 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "gdbus/gdbus.h" +#include "src/shared/util.h" +#include "src/shared/shell.h" +#include "advertising.h" + +#define AD_PATH "/org/bluez/advertising" +#define AD_IFACE "org.bluez.LEAdvertisement1" + +struct ad_data { + uint8_t data[25]; + uint8_t len; +}; + +struct service_data { + char *uuid; + struct ad_data data; +}; + +struct manufacturer_data { + uint16_t id; + struct ad_data data; +}; + +struct data { + uint8_t type; + struct ad_data data; +}; + +static struct ad { + bool registered; + char *type; + char *local_name; + char *secondary; + uint16_t local_appearance; + uint16_t duration; + uint16_t timeout; + uint16_t discoverable_to; + char **uuids; + size_t uuids_len; + struct service_data service; + struct manufacturer_data manufacturer; + struct data data; + bool discoverable; + bool tx_power; + bool name; + bool appearance; +} ad = { + .local_appearance = UINT16_MAX, +}; + +static void ad_release(DBusConnection *conn) +{ + ad.registered = false; + + g_dbus_unregister_interface(conn, AD_PATH, AD_IFACE); +} + +static DBusMessage *release_advertising(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + bt_shell_printf("Advertising released\n"); + + ad_release(conn); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable ad_methods[] = { + { GDBUS_METHOD("Release", NULL, NULL, release_advertising) }, + { } +}; + +static void register_setup(DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter dict; + const char *path = AD_PATH; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + dbus_message_iter_close_container(iter, &dict); +} + +static void print_uuid(const char *uuid) +{ + const char *text; + + text = bt_uuidstr_to_str(uuid); + if (text) { + char str[26]; + unsigned int n; + + str[sizeof(str) - 1] = '\0'; + + n = snprintf(str, sizeof(str), "%s", text); + if (n > sizeof(str) - 1) { + str[sizeof(str) - 2] = '.'; + str[sizeof(str) - 3] = '.'; + if (str[sizeof(str) - 4] == ' ') + str[sizeof(str) - 4] = '.'; + + n = sizeof(str) - 1; + } + + bt_shell_printf("UUID: %s(%s)\n", str, uuid); + } else + bt_shell_printf("UUID: (%s)\n", uuid ? uuid : ""); +} + +static void print_ad_uuids(void) +{ + char **uuid; + + for (uuid = ad.uuids; uuid && *uuid; uuid++) + print_uuid(*uuid); +} + +static void print_ad(void) +{ + print_ad_uuids(); + + if (ad.service.uuid) { + print_uuid(ad.service.uuid); + bt_shell_hexdump(ad.service.data.data, ad.service.data.len); + } + + if (ad.manufacturer.data.len) { + bt_shell_printf("Manufacturer: %u\n", ad.manufacturer.id); + bt_shell_hexdump(ad.manufacturer.data.data, + ad.manufacturer.data.len); + } + + if (ad.data.data.len) { + bt_shell_printf("Data Type: 0x%02x\n", ad.data.type); + bt_shell_hexdump(ad.data.data.data, ad.data.data.len); + } + + bt_shell_printf("Tx Power: %s\n", ad.tx_power ? "on" : "off"); + + if (ad.local_name) + bt_shell_printf("LocalName: %s\n", ad.local_name); + else + bt_shell_printf("Name: %s\n", ad.name ? "on" : "off"); + + if (ad.local_appearance != UINT16_MAX) + bt_shell_printf("Appearance: %s (0x%04x)\n", + bt_appear_to_str(ad.local_appearance), + ad.local_appearance); + else + bt_shell_printf("Apperance: %s\n", + ad.appearance ? "on" : "off"); + + bt_shell_printf("Discoverable: %s\n", ad.discoverable ? "on": "off"); + + if (ad.duration) + bt_shell_printf("Duration: %u sec\n", ad.duration); + + if (ad.timeout) + bt_shell_printf("Timeout: %u sec\n", ad.timeout); +} + +static void register_reply(DBusMessage *message, void *user_data) +{ + DBusConnection *conn = user_data; + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == FALSE) { + ad.registered = true; + bt_shell_printf("Advertising object registered\n"); + print_ad(); + /* Leave advertise running even on noninteractive mode */ + } else { + bt_shell_printf("Failed to register advertisement: %s\n", error.name); + dbus_error_free(&error); + + if (g_dbus_unregister_interface(conn, AD_PATH, + AD_IFACE) == FALSE) + bt_shell_printf("Failed to unregister advertising object\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static gboolean get_type(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + const char *type = "peripheral"; + + if (ad.type && strlen(ad.type) > 0) + type = ad.type; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &type); + + return TRUE; +} + +static gboolean uuids_exists(const GDBusPropertyTable *property, void *data) +{ + return ad.uuids_len != 0; +} + +static gboolean get_uuids(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter array; + size_t i; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "as", &array); + + for (i = 0; i < ad.uuids_len; i++) + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, + &ad.uuids[i]); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean service_data_exists(const GDBusPropertyTable *property, + void *data) +{ + return ad.service.uuid != NULL; +} + +static gboolean get_service_data(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter dict; + struct ad_data *data = &ad.service.data; + uint8_t *val = data->data; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + + g_dbus_dict_append_array(&dict, ad.service.uuid, DBUS_TYPE_BYTE, &val, + data->len); + + dbus_message_iter_close_container(iter, &dict); + + return TRUE; +} + +static gboolean manufacturer_data_exists(const GDBusPropertyTable *property, + void *data) +{ + return ad.manufacturer.id != 0; +} + +static gboolean get_manufacturer_data(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter dict; + struct ad_data *data = &ad.manufacturer.data; + uint8_t *val = data->data; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{qv}", &dict); + + g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_UINT16, + &ad.manufacturer.id, + DBUS_TYPE_BYTE, &val, data->len); + + dbus_message_iter_close_container(iter, &dict); + + return TRUE; +} + +static gboolean includes_exists(const GDBusPropertyTable *property, void *data) +{ + return ad.tx_power || ad.name || ad.appearance; +} + +static gboolean get_includes(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "as", &array); + + if (ad.tx_power) { + const char *str = "tx-power"; + + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &str); + } + + if (ad.name) { + const char *str = "local-name"; + + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &str); + } + + if (ad.appearance) { + const char *str = "appearance"; + + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &str); + } + + dbus_message_iter_close_container(iter, &array); + + + return TRUE; +} + +static gboolean local_name_exits(const GDBusPropertyTable *property, void *data) +{ + return ad.local_name ? TRUE : FALSE; +} + +static gboolean get_local_name(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ad.local_name); + + return TRUE; +} + +static gboolean appearance_exits(const GDBusPropertyTable *property, void *data) +{ + return ad.local_appearance != UINT16_MAX ? TRUE : FALSE; +} + +static gboolean get_appearance(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, + &ad.local_appearance); + + return TRUE; +} + +static gboolean duration_exits(const GDBusPropertyTable *property, void *data) +{ + return ad.duration; +} + +static gboolean get_duration(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &ad.duration); + + return TRUE; +} + +static gboolean timeout_exits(const GDBusPropertyTable *property, void *data) +{ + return ad.timeout; +} + +static gboolean get_timeout(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &ad.timeout); + + return TRUE; +} + +static gboolean data_exists(const GDBusPropertyTable *property, void *data) +{ + return ad.data.type != 0; +} + +static gboolean get_data(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter dict; + struct ad_data *data = &ad.data.data; + uint8_t *val = data->data; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{yv}", &dict); + + g_dbus_dict_append_basic_array(&dict, DBUS_TYPE_BYTE, &ad.data.type, + DBUS_TYPE_BYTE, &val, data->len); + + dbus_message_iter_close_container(iter, &dict); + + return TRUE; +} + +static gboolean discoverable_exists(const GDBusPropertyTable *property, + void *data) +{ + return ad.discoverable; +} + +static gboolean get_discoverable(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + dbus_bool_t value = ad.discoverable; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); + + return TRUE; +} + +static gboolean discoverable_timeout_exits(const GDBusPropertyTable *property, + void *data) +{ + return ad.discoverable_to; +} + +static gboolean get_discoverable_timeout(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, + &ad.discoverable_to); + + return TRUE; +} + +static gboolean secondary_exits(const GDBusPropertyTable *property, void *data) +{ + return ad.secondary ? TRUE : FALSE; +} + +static gboolean get_secondary(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &ad.secondary); + + return TRUE; +} + +static const GDBusPropertyTable ad_props[] = { + { "Type", "s", get_type }, + { "ServiceUUIDs", "as", get_uuids, NULL, uuids_exists }, + { "ServiceData", "a{sv}", get_service_data, NULL, service_data_exists }, + { "ManufacturerData", "a{qv}", get_manufacturer_data, NULL, + manufacturer_data_exists }, + { "Data", "a{yv}", get_data, NULL, data_exists }, + { "Discoverable", "b", get_discoverable, NULL, discoverable_exists }, + { "DiscoverableTimeout", "q", get_discoverable_timeout, NULL, + discoverable_timeout_exits }, + { "Includes", "as", get_includes, NULL, includes_exists }, + { "LocalName", "s", get_local_name, NULL, local_name_exits }, + { "Appearance", "q", get_appearance, NULL, appearance_exits }, + { "Duration", "q", get_duration, NULL, duration_exits }, + { "Timeout", "q", get_timeout, NULL, timeout_exits }, + { "SecondaryChannel", "s", get_secondary, NULL, secondary_exits }, + { } +}; + +void ad_register(DBusConnection *conn, GDBusProxy *manager, const char *type) +{ + if (ad.registered) { + bt_shell_printf("Advertisement is already registered\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + g_free(ad.type); + ad.type = g_strdup(type); + + if (g_dbus_register_interface(conn, AD_PATH, AD_IFACE, ad_methods, + NULL, ad_props, NULL, NULL) == FALSE) { + bt_shell_printf("Failed to register advertising object\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (g_dbus_proxy_method_call(manager, "RegisterAdvertisement", + register_setup, register_reply, + conn, NULL) == FALSE) { + bt_shell_printf("Failed to register advertising\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void unregister_setup(DBusMessageIter *iter, void *user_data) +{ + const char *path = AD_PATH; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); +} + +static void unregister_reply(DBusMessage *message, void *user_data) +{ + DBusConnection *conn = user_data; + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == FALSE) { + ad.registered = false; + bt_shell_printf("Advertising object unregistered\n"); + if (g_dbus_unregister_interface(conn, AD_PATH, + AD_IFACE) == FALSE) + bt_shell_printf("Failed to unregister advertising" + " object\n"); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } else { + bt_shell_printf("Failed to unregister advertisement: %s\n", + error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +void ad_unregister(DBusConnection *conn, GDBusProxy *manager) +{ + if (!manager) + ad_release(conn); + + if (!ad.registered) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + g_free(ad.type); + ad.type = NULL; + + if (g_dbus_proxy_method_call(manager, "UnregisterAdvertisement", + unregister_setup, unregister_reply, + conn, NULL) == FALSE) { + bt_shell_printf("Failed to unregister advertisement method\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void ad_clear_uuids(void) +{ + g_strfreev(ad.uuids); + ad.uuids = NULL; + ad.uuids_len = 0; +} + +void ad_advertise_uuids(DBusConnection *conn, int argc, char *argv[]) +{ + if (argc < 2 || !strlen(argv[1])) { + print_ad_uuids(); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + ad_clear_uuids(); + + ad.uuids = g_strdupv(&argv[1]); + if (!ad.uuids) { + bt_shell_printf("Failed to parse input\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ad.uuids_len = g_strv_length(ad.uuids); + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "ServiceUUIDs"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_disable_uuids(DBusConnection *conn) +{ + if (!ad.uuids) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad_clear_uuids(); + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "ServiceUUIDs"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void ad_clear_service(void) +{ + g_free(ad.service.uuid); + memset(&ad.service, 0, sizeof(ad.service)); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static bool ad_add_data(struct ad_data *data, int argc, char *argv[]) +{ + unsigned int i; + + memset(data, 0, sizeof(*data)); + + for (i = 0; i < (unsigned int) argc; i++) { + long int val; + char *endptr = NULL; + + if (i >= G_N_ELEMENTS(data->data)) { + bt_shell_printf("Too much data\n"); + return false; + } + + val = strtol(argv[i], &endptr, 0); + if (!endptr || *endptr != '\0' || val > UINT8_MAX) { + bt_shell_printf("Invalid value at index %d\n", i); + return false; + } + + data->data[data->len] = val; + data->len++; + } + + return true; +} + +void ad_advertise_service(DBusConnection *conn, int argc, char *argv[]) +{ + struct ad_data data; + + if (argc < 2 || !strlen(argv[1])) { + if (ad.service.uuid) { + print_uuid(ad.service.uuid); + bt_shell_hexdump(ad.service.data.data, + ad.service.data.len); + } + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (!ad_add_data(&data, argc - 2, argv + 2)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + ad_clear_service(); + + ad.service.uuid = g_strdup(argv[1]); + ad.service.data = data; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "ServiceData"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_disable_service(DBusConnection *conn) +{ + if (!ad.service.uuid) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad_clear_service(); + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "ServiceData"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void ad_clear_manufacturer(void) +{ + memset(&ad.manufacturer, 0, sizeof(ad.manufacturer)); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_manufacturer(DBusConnection *conn, int argc, char *argv[]) +{ + char *endptr = NULL; + long int val; + struct ad_data data; + + if (argc < 2 || !strlen(argv[1])) { + if (ad.manufacturer.data.len) { + bt_shell_printf("Manufacturer: %u\n", + ad.manufacturer.id); + bt_shell_hexdump(ad.manufacturer.data.data, + ad.manufacturer.data.len); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + val = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0' || val > UINT16_MAX) { + bt_shell_printf("Invalid manufacture id\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (!ad_add_data(&data, argc - 2, argv + 2)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + ad_clear_manufacturer(); + ad.manufacturer.id = val; + ad.manufacturer.data = data; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, + "ManufacturerData"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_disable_manufacturer(DBusConnection *conn) +{ + if (!ad.manufacturer.id && !ad.manufacturer.data.len) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad_clear_manufacturer(); + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, + "ManufacturerData"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void ad_clear_data(void) +{ + memset(&ad.manufacturer, 0, sizeof(ad.manufacturer)); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_data(DBusConnection *conn, int argc, char *argv[]) +{ + char *endptr = NULL; + long int val; + struct ad_data data; + + if (argc < 2 || !strlen(argv[1])) { + if (ad.manufacturer.data.len) { + bt_shell_printf("Type: 0x%02x\n", ad.data.type); + bt_shell_hexdump(ad.data.data.data, ad.data.data.len); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + val = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0' || val > UINT8_MAX) { + bt_shell_printf("Invalid type\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (!ad_add_data(&data, argc - 2, argv + 2)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + ad_clear_data(); + ad.data.type = val; + ad.data.data = data; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "Data"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_disable_data(DBusConnection *conn) +{ + if (!ad.data.type && !ad.data.data.len) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad_clear_data(); + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "Data"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_discoverable(DBusConnection *conn, dbus_bool_t *value) +{ + if (!value) { + bt_shell_printf("Discoverable: %s\n", + ad.discoverable ? "on" : "off"); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (ad.discoverable == *value) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad.discoverable = *value; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "Discoverable"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_discoverable_timeout(DBusConnection *conn, long int *value) +{ + if (!value) { + if (ad.discoverable_to) + bt_shell_printf("Timeout: %u sec\n", + ad.discoverable_to); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (ad.discoverable_to == *value) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad.discoverable_to = *value; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, + "DiscoverableTimeout"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_tx_power(DBusConnection *conn, dbus_bool_t *value) +{ + if (!value) { + bt_shell_printf("Tx Power: %s\n", ad.tx_power ? "on" : "off"); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (ad.tx_power == *value) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad.tx_power = *value; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "Includes"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_name(DBusConnection *conn, bool value) +{ + if (ad.name == value) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad.name = value; + + if (!value) { + free(ad.local_name); + ad.local_name = NULL; + } + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "Includes"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_local_name(DBusConnection *conn, const char *name) +{ + if (!name) { + if (ad.local_name) + bt_shell_printf("LocalName: %s\n", ad.local_name); + else + bt_shell_printf("Name: %s\n", ad.name ? "on" : "off"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (ad.local_name && !strcmp(name, ad.local_name)) + return; + + g_free(ad.local_name); + ad.local_name = strdup(name); + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "LocalName"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_appearance(DBusConnection *conn, bool value) +{ + if (ad.appearance == value) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad.appearance = value; + + if (!value) + ad.local_appearance = UINT16_MAX; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "Includes"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_local_appearance(DBusConnection *conn, long int *value) +{ + if (!value) { + if (ad.local_appearance != UINT16_MAX) + bt_shell_printf("Appearance: %s (0x%04x)\n", + bt_appear_to_str(ad.local_appearance), + ad.local_appearance); + else + bt_shell_printf("Apperance: %s\n", + ad.appearance ? "on" : "off"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (ad.local_appearance == *value) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad.local_appearance = *value; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "Appearance"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_duration(DBusConnection *conn, long int *value) +{ + if (!value) { + if (ad.duration) + bt_shell_printf("Duration: %u sec\n", ad.duration); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (ad.duration == *value) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad.duration = *value; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "Duration"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_timeout(DBusConnection *conn, long int *value) +{ + if (!value) { + if (ad.timeout) + bt_shell_printf("Timeout: %u sec\n", ad.timeout); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (ad.timeout == *value) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + ad.timeout = *value; + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, "Timeout"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void ad_advertise_secondary(DBusConnection *conn, const char *value) +{ + if (!value) { + if (ad.secondary) + bt_shell_printf("Secondary Channel: %s\n", + ad.secondary); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (ad.secondary && !strcmp(value, ad.secondary)) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + free(ad.secondary); + + if (value[0] == '\0') { + ad.secondary = NULL; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + ad.secondary = strdup(value); + + g_dbus_emit_property_changed(conn, AD_PATH, AD_IFACE, + "SecondaryChannel"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} diff --git a/client/advertising.h b/client/advertising.h new file mode 100644 index 0000000..9967e65 --- /dev/null +++ b/client/advertising.h @@ -0,0 +1,44 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2016 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 + * + */ + +void ad_register(DBusConnection *conn, GDBusProxy *manager, const char *type); +void ad_unregister(DBusConnection *conn, GDBusProxy *manager); + +void ad_advertise_uuids(DBusConnection *conn, int argc, char *argv[]); +void ad_disable_uuids(DBusConnection *conn); +void ad_advertise_service(DBusConnection *conn, int argc, char *argv[]); +void ad_disable_service(DBusConnection *conn); +void ad_advertise_manufacturer(DBusConnection *conn, int argc, char *argv[]); +void ad_disable_manufacturer(DBusConnection *conn); +void ad_advertise_tx_power(DBusConnection *conn, dbus_bool_t *value); +void ad_advertise_name(DBusConnection *conn, bool value); +void ad_advertise_appearance(DBusConnection *conn, bool value); +void ad_advertise_local_name(DBusConnection *conn, const char *name); +void ad_advertise_local_appearance(DBusConnection *conn, long int *value); +void ad_advertise_duration(DBusConnection *conn, long int *value); +void ad_advertise_timeout(DBusConnection *conn, long int *value); +void ad_advertise_data(DBusConnection *conn, int argc, char *argv[]); +void ad_disable_data(DBusConnection *conn); +void ad_advertise_discoverable(DBusConnection *conn, dbus_bool_t *value); +void ad_advertise_discoverable_timeout(DBusConnection *conn, long int *value); +void ad_advertise_secondary(DBusConnection *conn, const char *value); diff --git a/client/agent.c b/client/agent.c new file mode 100644 index 0000000..4def1b4 --- /dev/null +++ b/client/agent.c @@ -0,0 +1,444 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 +#endif + +#include +#include +#include +#include +#include + +#include + +#include "src/shared/shell.h" +#include "gdbus/gdbus.h" +#include "agent.h" + +#define AGENT_PATH "/org/bluez/agent" +#define AGENT_INTERFACE "org.bluez.Agent1" + +#define AGENT_PROMPT COLOR_RED "[agent]" COLOR_OFF " " + +static gboolean agent_registered = FALSE; +static const char *agent_capability = NULL; +static DBusMessage *pending_message = NULL; + +static void agent_release_prompt(void) +{ + if (!pending_message) + return; + + bt_shell_release_prompt(""); +} + +dbus_bool_t agent_completion(void) +{ + if (!pending_message) + return FALSE; + + return TRUE; +} + +static void pincode_response(const char *input, void *user_data) +{ + DBusConnection *conn = user_data; + + g_dbus_send_reply(conn, pending_message, DBUS_TYPE_STRING, &input, + DBUS_TYPE_INVALID); +} + +static void passkey_response(const char *input, void *user_data) +{ + DBusConnection *conn = user_data; + dbus_uint32_t passkey; + + if (sscanf(input, "%u", &passkey) == 1) + g_dbus_send_reply(conn, pending_message, DBUS_TYPE_UINT32, + &passkey, DBUS_TYPE_INVALID); + else if (!strcmp(input, "no")) + g_dbus_send_error(conn, pending_message, + "org.bluez.Error.Rejected", NULL); + else + g_dbus_send_error(conn, pending_message, + "org.bluez.Error.Canceled", NULL); +} + +static void confirm_response(const char *input, void *user_data) +{ + DBusConnection *conn = user_data; + + if (!strcmp(input, "yes")) + g_dbus_send_reply(conn, pending_message, DBUS_TYPE_INVALID); + else if (!strcmp(input, "no")) + g_dbus_send_error(conn, pending_message, + "org.bluez.Error.Rejected", NULL); + else + g_dbus_send_error(conn, pending_message, + "org.bluez.Error.Canceled", NULL); +} + +static void agent_release(DBusConnection *conn) +{ + agent_registered = FALSE; + agent_capability = NULL; + + if (pending_message) { + dbus_message_unref(pending_message); + pending_message = NULL; + } + + agent_release_prompt(); + + g_dbus_unregister_interface(conn, AGENT_PATH, AGENT_INTERFACE); +} + +static DBusMessage *release_agent(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + bt_shell_printf("Agent released\n"); + + agent_release(conn); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *request_pincode(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *device; + + bt_shell_printf("Request PIN code\n"); + + dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, + DBUS_TYPE_INVALID); + + bt_shell_prompt_input("agent", "Enter PIN code:", pincode_response, + conn); + + pending_message = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *display_pincode(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *device; + const char *pincode; + + dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, + DBUS_TYPE_STRING, &pincode, DBUS_TYPE_INVALID); + + bt_shell_printf(AGENT_PROMPT "PIN code: %s\n", pincode); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *request_passkey(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *device; + + bt_shell_printf("Request passkey\n"); + + dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, + DBUS_TYPE_INVALID); + + bt_shell_prompt_input("agent", "Enter passkey (number in 0-999999):", + passkey_response, conn); + + pending_message = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *display_passkey(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *device; + dbus_uint32_t passkey; + dbus_uint16_t entered; + char passkey_full[7]; + + dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, + DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_UINT16, &entered, + DBUS_TYPE_INVALID); + + snprintf(passkey_full, sizeof(passkey_full), "%.6u", passkey); + passkey_full[6] = '\0'; + + if (entered > strlen(passkey_full)) + entered = strlen(passkey_full); + + bt_shell_printf(AGENT_PROMPT "Passkey: " + COLOR_BOLDGRAY "%.*s" COLOR_BOLDWHITE "%s\n" COLOR_OFF, + entered, passkey_full, passkey_full + entered); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *request_confirmation(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *device; + dbus_uint32_t passkey; + char *str; + + bt_shell_printf("Request confirmation\n"); + + dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, + DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_INVALID); + + str = g_strdup_printf("Confirm passkey %06u (yes/no):", passkey); + bt_shell_prompt_input("agent", str, confirm_response, conn); + g_free(str); + + pending_message = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *request_authorization(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *device; + + bt_shell_printf("Request authorization\n"); + + dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, + DBUS_TYPE_INVALID); + + bt_shell_prompt_input("agent", "Accept pairing (yes/no):", + confirm_response, conn); + + pending_message = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *authorize_service(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *device, *uuid; + char *str; + + bt_shell_printf("Authorize service\n"); + + dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, + DBUS_TYPE_STRING, &uuid, DBUS_TYPE_INVALID); + + str = g_strdup_printf("Authorize service %s (yes/no):", uuid); + bt_shell_prompt_input("agent", str, confirm_response, conn); + g_free(str); + + pending_message = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *cancel_request(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + bt_shell_printf("Request canceled\n"); + + agent_release_prompt(); + dbus_message_unref(pending_message); + pending_message = NULL; + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable methods[] = { + { GDBUS_METHOD("Release", NULL, NULL, release_agent) }, + { GDBUS_ASYNC_METHOD("RequestPinCode", + GDBUS_ARGS({ "device", "o" }), + GDBUS_ARGS({ "pincode", "s" }), request_pincode) }, + { GDBUS_METHOD("DisplayPinCode", + GDBUS_ARGS({ "device", "o" }, { "pincode", "s" }), + NULL, display_pincode) }, + { GDBUS_ASYNC_METHOD("RequestPasskey", + GDBUS_ARGS({ "device", "o" }), + GDBUS_ARGS({ "passkey", "u" }), request_passkey) }, + { GDBUS_METHOD("DisplayPasskey", + GDBUS_ARGS({ "device", "o" }, { "passkey", "u" }, + { "entered", "q" }), + NULL, display_passkey) }, + { GDBUS_ASYNC_METHOD("RequestConfirmation", + GDBUS_ARGS({ "device", "o" }, { "passkey", "u" }), + NULL, request_confirmation) }, + { GDBUS_ASYNC_METHOD("RequestAuthorization", + GDBUS_ARGS({ "device", "o" }), + NULL, request_authorization) }, + { GDBUS_ASYNC_METHOD("AuthorizeService", + GDBUS_ARGS({ "device", "o" }, { "uuid", "s" }), + NULL, authorize_service) }, + { GDBUS_METHOD("Cancel", NULL, NULL, cancel_request) }, + { } +}; + +static void register_agent_setup(DBusMessageIter *iter, void *user_data) +{ + const char *path = AGENT_PATH; + const char *capability = agent_capability; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &capability); +} + +static void register_agent_reply(DBusMessage *message, void *user_data) +{ + DBusConnection *conn = user_data; + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == FALSE) { + agent_registered = TRUE; + bt_shell_printf("Agent registered\n"); + } else { + bt_shell_printf("Failed to register agent: %s\n", error.name); + dbus_error_free(&error); + + if (g_dbus_unregister_interface(conn, AGENT_PATH, + AGENT_INTERFACE) == FALSE) + bt_shell_printf("Failed to unregister agent object\n"); + } +} + +void agent_register(DBusConnection *conn, GDBusProxy *manager, + const char *capability) + +{ + if (agent_registered == TRUE) { + bt_shell_printf("Agent is already registered\n"); + return; + } + + agent_capability = capability; + + if (g_dbus_register_interface(conn, AGENT_PATH, + AGENT_INTERFACE, methods, + NULL, NULL, NULL, NULL) == FALSE) { + bt_shell_printf("Failed to register agent object\n"); + return; + } + + if (g_dbus_proxy_method_call(manager, "RegisterAgent", + register_agent_setup, + register_agent_reply, + conn, NULL) == FALSE) { + bt_shell_printf("Failed to call register agent method\n"); + return; + } + + agent_capability = NULL; +} + +static void unregister_agent_setup(DBusMessageIter *iter, void *user_data) +{ + const char *path = AGENT_PATH; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); +} + +static void unregister_agent_reply(DBusMessage *message, void *user_data) +{ + DBusConnection *conn = user_data; + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == FALSE) { + bt_shell_printf("Agent unregistered\n"); + agent_release(conn); + } else { + bt_shell_printf("Failed to unregister agent: %s\n", error.name); + dbus_error_free(&error); + } +} + +void agent_unregister(DBusConnection *conn, GDBusProxy *manager) +{ + if (agent_registered == FALSE) { + bt_shell_printf("No agent is registered\n"); + return; + } + + if (!manager) { + bt_shell_printf("Agent unregistered\n"); + agent_release(conn); + return; + } + + if (g_dbus_proxy_method_call(manager, "UnregisterAgent", + unregister_agent_setup, + unregister_agent_reply, + conn, NULL) == FALSE) { + bt_shell_printf("Failed to call unregister agent method\n"); + return; + } +} + +static void request_default_setup(DBusMessageIter *iter, void *user_data) +{ + const char *path = AGENT_PATH; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); +} + +static void request_default_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to request default agent: %s\n", + error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Default agent request successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void agent_default(DBusConnection *conn, GDBusProxy *manager) +{ + if (agent_registered == FALSE) { + bt_shell_printf("No agent is registered\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (g_dbus_proxy_method_call(manager, "RequestDefaultAgent", + request_default_setup, + request_default_reply, + NULL, NULL) == FALSE) { + bt_shell_printf("Failed to call RequestDefaultAgent method\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} diff --git a/client/agent.h b/client/agent.h new file mode 100644 index 0000000..30f302c --- /dev/null +++ b/client/agent.h @@ -0,0 +1,29 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 + * + */ + +void agent_register(DBusConnection *conn, GDBusProxy *manager, + const char *capability); +void agent_unregister(DBusConnection *conn, GDBusProxy *manager); +void agent_default(DBusConnection *conn, GDBusProxy *manager); + +dbus_bool_t agent_completion(void); diff --git a/client/display.c b/client/display.c new file mode 100644 index 0000000..0561386 --- /dev/null +++ b/client/display.c @@ -0,0 +1,168 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "display.h" + +static char *saved_prompt = NULL; +static int saved_point = 0; +static rl_prompt_input_func saved_func = NULL; +static void *saved_user_data = NULL; + +void rl_printf(const char *fmt, ...) +{ + va_list args; + bool save_input; + char *saved_line; + int saved_point; + + save_input = !RL_ISSTATE(RL_STATE_DONE); + + if (save_input) { + saved_point = rl_point; + saved_line = rl_copy_text(0, rl_end); + rl_save_prompt(); + rl_replace_line("", 0); + rl_redisplay(); + } + + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + + if (save_input) { + rl_restore_prompt(); + rl_replace_line(saved_line, 0); + rl_point = saved_point; + rl_forced_update_display(); + free(saved_line); + } +} + +void rl_hexdump(const unsigned char *buf, size_t len) +{ + static const char hexdigits[] = "0123456789abcdef"; + char str[68]; + size_t i; + + if (!len) + return; + + str[0] = ' '; + + for (i = 0; i < len; i++) { + str[((i % 16) * 3) + 1] = ' '; + str[((i % 16) * 3) + 2] = hexdigits[buf[i] >> 4]; + str[((i % 16) * 3) + 3] = hexdigits[buf[i] & 0xf]; + str[(i % 16) + 51] = isprint(buf[i]) ? buf[i] : '.'; + + if ((i + 1) % 16 == 0) { + str[49] = ' '; + str[50] = ' '; + str[67] = '\0'; + rl_printf("%s\n", str); + str[0] = ' '; + } + } + + if (i % 16 > 0) { + size_t j; + for (j = (i % 16); j < 16; j++) { + str[(j * 3) + 1] = ' '; + str[(j * 3) + 2] = ' '; + str[(j * 3) + 3] = ' '; + str[j + 51] = ' '; + } + str[49] = ' '; + str[50] = ' '; + str[67] = '\0'; + rl_printf("%s\n", str); + } +} + +void rl_prompt_input(const char *label, const char *msg, + rl_prompt_input_func func, void *user_data) +{ + char prompt[256]; + + /* Normal use should not prompt for user input to the value a second + * time before it releases the prompt, but we take a safe action. */ + if (saved_prompt) + return; + + saved_point = rl_point; + saved_prompt = strdup(rl_prompt); + saved_func = func; + saved_user_data = user_data; + + rl_set_prompt(""); + rl_redisplay(); + + memset(prompt, 0, sizeof(prompt)); + snprintf(prompt, sizeof(prompt), COLOR_RED "[%s]" COLOR_OFF " %s ", + label, msg); + rl_set_prompt(prompt); + + rl_replace_line("", 0); + rl_redisplay(); +} + +int rl_release_prompt(const char *input) +{ + rl_prompt_input_func func; + void *user_data; + + if (!saved_prompt) + return -1; + + /* This will cause rl_expand_prompt to re-run over the last prompt, but + * our prompt doesn't expand anyway. */ + rl_set_prompt(saved_prompt); + rl_replace_line("", 0); + rl_point = saved_point; + rl_redisplay(); + + free(saved_prompt); + saved_prompt = NULL; + + func = saved_func; + user_data = saved_user_data; + + saved_func = NULL; + saved_user_data = NULL; + + func(input, user_data); + + return 0; +} diff --git a/client/display.h b/client/display.h new file mode 100644 index 0000000..e991d19 --- /dev/null +++ b/client/display.h @@ -0,0 +1,38 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 + * + */ + +#define COLOR_OFF "\x1B[0m" +#define COLOR_RED "\x1B[0;91m" +#define COLOR_GREEN "\x1B[0;92m" +#define COLOR_YELLOW "\x1B[0;93m" +#define COLOR_BLUE "\x1B[0;94m" +#define COLOR_BOLDGRAY "\x1B[1;30m" +#define COLOR_BOLDWHITE "\x1B[1;37m" + +void rl_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2))); +void rl_hexdump(const unsigned char *buf, size_t len); + +typedef void (*rl_prompt_input_func) (const char *input, void *user_data); +void rl_prompt_input(const char *label, const char *msg, + rl_prompt_input_func func, void *user_data); +int rl_release_prompt(const char *input); diff --git a/client/gatt.c b/client/gatt.c new file mode 100644 index 0000000..416eda9 --- /dev/null +++ b/client/gatt.c @@ -0,0 +1,3170 @@ +/* + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/io.h" +#include "src/shared/shell.h" +#include "gdbus/gdbus.h" +#include "gatt.h" + +#define APP_PATH "/org/bluez/app" +#define DEVICE_INTERFACE "org.bluez.Device1" +#define PROFILE_INTERFACE "org.bluez.GattProfile1" +#define SERVICE_INTERFACE "org.bluez.GattService1" +#define CHRC_INTERFACE "org.bluez.GattCharacteristic1" +#define DESC_INTERFACE "org.bluez.GattDescriptor1" + +/* String display constants */ +#define COLORED_NEW COLOR_GREEN "NEW" COLOR_OFF +#define COLORED_CHG COLOR_YELLOW "CHG" COLOR_OFF +#define COLORED_DEL COLOR_RED "DEL" COLOR_OFF + +#define MAX_ATTR_VAL_LEN 512 + +struct desc { + struct chrc *chrc; + char *path; + uint16_t handle; + char *uuid; + char **flags; + size_t value_len; + unsigned int max_val_len; + uint8_t *value; +}; + +struct chrc { + struct service *service; + GDBusProxy *proxy; + char *path; + uint16_t handle; + char *uuid; + char **flags; + bool notifying; + GList *descs; + size_t value_len; + unsigned int max_val_len; + uint8_t *value; + uint16_t mtu; + struct io *write_io; + struct io *notify_io; + bool authorization_req; +}; + +struct service { + DBusConnection *conn; + GDBusProxy *proxy; + char *path; + uint16_t handle; + char *uuid; + bool primary; + GList *chrcs; + GList *inc; +}; + +static GList *local_services; +static GList *services; +static GList *characteristics; +static GList *descriptors; +static GList *managers; +static GList *uuids; +static DBusMessage *pending_message = NULL; + +struct sock_io { + GDBusProxy *proxy; + struct io *io; + uint16_t mtu; +}; + +static struct sock_io write_io; +static struct sock_io notify_io; + +static void print_service(struct service *service, const char *description) +{ + const char *text; + + text = bt_uuidstr_to_str(service->uuid); + if (!text) + bt_shell_printf("%s%s%s%s Service (Handle 0x%04x)\n\t%s\n\t" + "%s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + service->primary ? "Primary" : + "Secondary", + service->handle, service->path, + service->uuid); + else + bt_shell_printf("%s%s%s%s Service (Handle 0x%04x)\n\t%s\n\t%s" + "\n\t%s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + service->primary ? "Primary" : + "Secondary", + service->handle, service->path, + service->uuid, text); +} + +static void print_inc_service(struct service *service, const char *description) +{ + const char *text; + + text = bt_uuidstr_to_str(service->uuid); + if (!text) + bt_shell_printf("%s%s%s%s Included Service (Handle 0x%04x)\n\t" + "%s\n\t%s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + service->primary ? "Primary" : + "Secondary", + service->handle, service->path, + service->uuid); + else + bt_shell_printf("%s%s%s%s Included Service (Handle 0x%04x)\n\t" + "%s\n\t%s\n\t%s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + service->primary ? "Primary" : + "Secondary", + service->handle, service->path, + service->uuid, text); +} + +static void print_service_proxy(GDBusProxy *proxy, const char *description) +{ + struct service service; + DBusMessageIter iter; + const char *uuid; + dbus_bool_t primary; + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &uuid); + + if (g_dbus_proxy_get_property(proxy, "Primary", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &primary); + + service.path = (char *) g_dbus_proxy_get_path(proxy); + service.uuid = (char *) uuid; + service.primary = primary; + + print_service(&service, description); +} + +void gatt_add_service(GDBusProxy *proxy) +{ + services = g_list_append(services, proxy); + + print_service_proxy(proxy, COLORED_NEW); +} + +static struct service *remove_service_by_proxy(struct GDBusProxy *proxy) +{ + GList *l; + + for (l = local_services; l; l = g_list_next(l)) { + struct service *service = l->data; + + if (service->proxy == proxy) { + local_services = g_list_delete_link(local_services, l); + return service; + } + } + + return NULL; +} + +void gatt_remove_service(GDBusProxy *proxy) +{ + struct service *service; + GList *l; + + l = g_list_find(services, proxy); + if (!l) + return; + + services = g_list_delete_link(services, l); + + print_service_proxy(proxy, COLORED_DEL); + + service = remove_service_by_proxy(proxy); + if (service) + g_dbus_unregister_interface(service->conn, service->path, + SERVICE_INTERFACE); +} + +static void print_chrc(struct chrc *chrc, const char *description) +{ + const char *text; + + text = bt_uuidstr_to_str(chrc->uuid); + if (!text) + bt_shell_printf("%s%s%sCharacteristic (Handle 0x%04x)\n\t%s\n\t" + "%s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + chrc->handle, chrc->path, chrc->uuid); + else + bt_shell_printf("%s%s%sCharacteristic (Handle 0x%04x)\n\t%s\n\t" + "%s\n\t%s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + chrc->handle, chrc->path, chrc->uuid, + text); +} + +static void print_characteristic(GDBusProxy *proxy, const char *description) +{ + struct chrc chrc; + DBusMessageIter iter; + const char *uuid; + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &uuid); + + chrc.path = (char *) g_dbus_proxy_get_path(proxy); + chrc.uuid = (char *) uuid; + + print_chrc(&chrc, description); +} + +static gboolean chrc_is_child(GDBusProxy *characteristic) +{ + DBusMessageIter iter; + const char *service; + + if (!g_dbus_proxy_get_property(characteristic, "Service", &iter)) + return FALSE; + + dbus_message_iter_get_basic(&iter, &service); + + return g_dbus_proxy_lookup(services, NULL, service, + "org.bluez.GattService1") != NULL; +} + +void gatt_add_characteristic(GDBusProxy *proxy) +{ + if (!chrc_is_child(proxy)) + return; + + characteristics = g_list_append(characteristics, proxy); + + print_characteristic(proxy, COLORED_NEW); +} + +static void notify_io_destroy(void) +{ + io_destroy(notify_io.io); + memset(¬ify_io, 0, sizeof(notify_io)); +} + +static void write_io_destroy(void) +{ + io_destroy(write_io.io); + memset(&write_io, 0, sizeof(write_io)); +} + +void gatt_remove_characteristic(GDBusProxy *proxy) +{ + GList *l; + + l = g_list_find(characteristics, proxy); + if (!l) + return; + + characteristics = g_list_delete_link(characteristics, l); + + print_characteristic(proxy, COLORED_DEL); + + if (write_io.proxy == proxy) + write_io_destroy(); + else if (notify_io.proxy == proxy) + notify_io_destroy(); +} + +static void print_desc(struct desc *desc, const char *description) +{ + const char *text; + + text = bt_uuidstr_to_str(desc->uuid); + if (!text) + bt_shell_printf("%s%s%sDescriptor (Handle 0x%04x)\n\t%s\n\t" + "%s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + desc->handle, desc->path, desc->uuid); + else + bt_shell_printf("%s%s%sDescriptor (Handle 0x%04x)\n\t%s\n\t" + "%s\n\t%s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + desc->handle, desc->path, desc->uuid, + text); +} + +static void print_descriptor(GDBusProxy *proxy, const char *description) +{ + struct desc desc; + DBusMessageIter iter; + const char *uuid; + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &uuid); + + desc.path = (char *) g_dbus_proxy_get_path(proxy); + desc.uuid = (char *) uuid; + + print_desc(&desc, description); +} + +static gboolean descriptor_is_child(GDBusProxy *characteristic) +{ + GList *l; + DBusMessageIter iter; + const char *service, *path; + + if (!g_dbus_proxy_get_property(characteristic, "Characteristic", &iter)) + return FALSE; + + dbus_message_iter_get_basic(&iter, &service); + + for (l = characteristics; l; l = g_list_next(l)) { + GDBusProxy *proxy = l->data; + + path = g_dbus_proxy_get_path(proxy); + + if (!strcmp(path, service)) + return TRUE; + } + + return FALSE; +} + +void gatt_add_descriptor(GDBusProxy *proxy) +{ + if (!descriptor_is_child(proxy)) + return; + + descriptors = g_list_append(descriptors, proxy); + + print_descriptor(proxy, COLORED_NEW); +} + +void gatt_remove_descriptor(GDBusProxy *proxy) +{ + GList *l; + + l = g_list_find(descriptors, proxy); + if (!l) + return; + + descriptors = g_list_delete_link(descriptors, l); + + print_descriptor(proxy, COLORED_DEL); +} + +static void list_attributes(const char *path, GList *source) +{ + GList *l; + + for (l = source; l; l = g_list_next(l)) { + GDBusProxy *proxy = l->data; + const char *proxy_path; + + proxy_path = g_dbus_proxy_get_path(proxy); + + if (!g_str_has_prefix(proxy_path, path)) + continue; + + if (source == services) { + print_service_proxy(proxy, NULL); + list_attributes(proxy_path, characteristics); + } else if (source == characteristics) { + print_characteristic(proxy, NULL); + list_attributes(proxy_path, descriptors); + } else if (source == descriptors) + print_descriptor(proxy, NULL); + } +} + +static void list_descs(GList *descs) +{ + GList *l; + + for (l = descs; l; l = g_list_next(l)) { + struct desc *desc = l->data; + + print_desc(desc, NULL); + } +} + +static void list_chrcs(GList *chrcs) +{ + GList *l; + + for (l = chrcs; l; l = g_list_next(l)) { + struct chrc *chrc = l->data; + + print_chrc(chrc, NULL); + + list_descs(chrc->descs); + } +} + +static void list_services(void) +{ + GList *l; + + for (l = local_services; l; l = g_list_next(l)) { + struct service *service = l->data; + + print_service(service, NULL); + + list_chrcs(service->chrcs); + } +} + +void gatt_list_attributes(const char *path) +{ + if (path && !strcmp(path, "local")) { + list_services(); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + list_attributes(path, services); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static GDBusProxy *select_attribute(const char *path) +{ + GDBusProxy *proxy; + + proxy = g_dbus_proxy_lookup(services, NULL, path, + "org.bluez.GattService1"); + if (proxy) + return proxy; + + proxy = g_dbus_proxy_lookup(characteristics, NULL, path, + "org.bluez.GattCharacteristic1"); + if (proxy) + return proxy; + + return g_dbus_proxy_lookup(descriptors, NULL, path, + "org.bluez.GattDescriptor1"); +} + +static GDBusProxy *select_proxy_by_uuid(GDBusProxy *parent, const char *uuid, + GList *source) +{ + GList *l; + const char *value; + DBusMessageIter iter; + + for (l = source; l; l = g_list_next(l)) { + GDBusProxy *proxy = l->data; + + if (parent && !g_str_has_prefix(g_dbus_proxy_get_path(proxy), + g_dbus_proxy_get_path(parent))) + continue; + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + continue; + + dbus_message_iter_get_basic(&iter, &value); + + if (strcasecmp(uuid, value) == 0) + return proxy; + + if (strlen(uuid) == 4 && !strncasecmp(value + 4, uuid, 4)) + return proxy; + } + + return NULL; +} + +static GDBusProxy *select_attribute_by_uuid(GDBusProxy *parent, + const char *uuid) +{ + GDBusProxy *proxy; + + proxy = select_proxy_by_uuid(parent, uuid, services); + if (proxy) + return proxy; + + proxy = select_proxy_by_uuid(parent, uuid, characteristics); + if (proxy) + return proxy; + + return select_proxy_by_uuid(parent, uuid, descriptors); +} + +GDBusProxy *gatt_select_attribute(GDBusProxy *parent, const char *arg) +{ + if (arg[0] == '/') + return select_attribute(arg); + + if (parent) { + GDBusProxy *proxy = select_attribute_by_uuid(parent, arg); + if (proxy) + return proxy; + } + + return select_attribute_by_uuid(NULL, arg); +} + +static char *attribute_generator(const char *text, int state, GList *source) +{ + static int index; + + if (!state) { + index = 0; + } + + return g_dbus_proxy_path_lookup(source, &index, text); +} + +char *gatt_attribute_generator(const char *text, int state) +{ + static GList *list = NULL; + + if (!state) { + GList *list1; + + if (list) { + g_list_free(list); + list = NULL; + } + + list1 = g_list_copy(characteristics); + list1 = g_list_concat(list1, g_list_copy(descriptors)); + + list = g_list_copy(services); + list = g_list_concat(list, list1); + } + + return attribute_generator(text, state, list); +} + +static void read_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + DBusMessageIter iter, array; + uint8_t *value; + int len; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to read: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + dbus_message_iter_init(message, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + bt_shell_printf("Invalid response to read\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + dbus_message_iter_recurse(&iter, &array); + dbus_message_iter_get_fixed_array(&array, &value, &len); + + if (len < 0) { + bt_shell_printf("Unable to parse value\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_hexdump(value, len); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void read_setup(DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter dict; + uint16_t *offset = user_data; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + g_dbus_dict_append_entry(&dict, "offset", DBUS_TYPE_UINT16, offset); + + dbus_message_iter_close_container(iter, &dict); +} + +static void read_attribute(GDBusProxy *proxy, uint16_t offset) +{ + if (g_dbus_proxy_method_call(proxy, "ReadValue", read_setup, read_reply, + &offset, NULL) == FALSE) { + bt_shell_printf("Failed to read\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to read %s\n", g_dbus_proxy_get_path(proxy)); +} + +void gatt_read_attribute(GDBusProxy *proxy, int argc, char *argv[]) +{ + const char *iface; + uint16_t offset = 0; + + iface = g_dbus_proxy_get_interface(proxy); + if (!strcmp(iface, "org.bluez.GattCharacteristic1") || + !strcmp(iface, "org.bluez.GattDescriptor1")) { + + if (argc == 2) + offset = atoi(argv[1]); + + read_attribute(proxy, offset); + return; + } + + bt_shell_printf("Unable to read attribute %s\n", + g_dbus_proxy_get_path(proxy)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void write_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to write: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +struct write_attribute_data { + DBusMessage *msg; + struct iovec iov; + char *type; + uint16_t offset; +}; + +static void write_setup(DBusMessageIter *iter, void *user_data) +{ + struct write_attribute_data *wd = user_data; + DBusMessageIter array, dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array); + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &wd->iov.iov_base, + wd->iov.iov_len); + dbus_message_iter_close_container(iter, &array); + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + if (wd->type) + g_dbus_dict_append_entry(&dict, "type", DBUS_TYPE_STRING, + &wd->type); + + g_dbus_dict_append_entry(&dict, "offset", DBUS_TYPE_UINT16, + &wd->offset); + + dbus_message_iter_close_container(iter, &dict); +} + +static int sock_send(struct io *io, struct iovec *iov, size_t iovlen) +{ + struct msghdr msg; + int ret; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = iovlen; + + ret = sendmsg(io_get_fd(io), &msg, MSG_NOSIGNAL); + if (ret < 0) { + ret = -errno; + bt_shell_printf("sendmsg: %s", strerror(-ret)); + } + + return ret; +} + +static void write_attribute(GDBusProxy *proxy, + struct write_attribute_data *data) +{ + /* Write using the fd if it has been acquired and fit the MTU */ + if (proxy == write_io.proxy && + (write_io.io && write_io.mtu >= data->iov.iov_len)) { + bt_shell_printf("Attempting to write fd %d\n", + io_get_fd(write_io.io)); + if (sock_send(write_io.io, &data->iov, 1) < 0) { + bt_shell_printf("Failed to write: %s", strerror(errno)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + return; + } + + if (g_dbus_proxy_method_call(proxy, "WriteValue", write_setup, + write_reply, data, NULL) == FALSE) { + bt_shell_printf("Failed to write\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to write %s\n", + g_dbus_proxy_get_path(proxy)); +} + +static uint8_t *str2bytearray(char *arg, size_t *val_len) +{ + uint8_t value[MAX_ATTR_VAL_LEN]; + char *entry; + unsigned int i; + + for (i = 0; (entry = strsep(&arg, " \t")) != NULL; i++) { + long int val; + char *endptr = NULL; + + if (*entry == '\0') + continue; + + if (i >= G_N_ELEMENTS(value)) { + bt_shell_printf("Too much data\n"); + return NULL; + } + + val = strtol(entry, &endptr, 0); + if (!endptr || *endptr != '\0' || val > UINT8_MAX) { + bt_shell_printf("Invalid value at index %d\n", i); + return NULL; + } + + value[i] = val; + } + + *val_len = i; + + return g_memdup(value, i); +} + +void gatt_write_attribute(GDBusProxy *proxy, int argc, char *argv[]) +{ + const char *iface; + struct write_attribute_data data; + + memset(&data, 0, sizeof(data)); + + iface = g_dbus_proxy_get_interface(proxy); + if (!strcmp(iface, "org.bluez.GattCharacteristic1") || + !strcmp(iface, "org.bluez.GattDescriptor1")) { + data.iov.iov_base = str2bytearray(argv[1], &data.iov.iov_len); + + if (argc > 2) + data.offset = atoi(argv[2]); + + if (argc > 3) + data.type = argv[3]; + + write_attribute(proxy, &data); + return; + } + + bt_shell_printf("Unable to write attribute %s\n", + g_dbus_proxy_get_path(proxy)); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static bool sock_read(struct io *io, void *user_data) +{ + struct chrc *chrc = user_data; + struct msghdr msg; + struct iovec iov; + uint8_t buf[MAX_ATTR_VAL_LEN]; + int fd = io_get_fd(io); + ssize_t bytes_read; + + if (io != notify_io.io && !chrc) + return true; + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + bytes_read = recvmsg(fd, &msg, MSG_DONTWAIT); + if (bytes_read < 0) { + bt_shell_printf("recvmsg: %s", strerror(errno)); + return false; + } + + if (!bytes_read) + return false; + + if (chrc) + bt_shell_printf("[" COLORED_CHG "] Attribute %s (%s) " + "written:\n", chrc->path, + bt_uuidstr_to_str(chrc->uuid)); + else + bt_shell_printf("[" COLORED_CHG "] %s Notification:\n", + g_dbus_proxy_get_path(notify_io.proxy)); + + bt_shell_hexdump(buf, bytes_read); + + return true; +} + +static bool sock_hup(struct io *io, void *user_data) +{ + struct chrc *chrc = user_data; + + if (chrc) { + bt_shell_printf("Attribute %s %s sock closed\n", chrc->path, + io == chrc->write_io ? "Write" : "Notify"); + + if (io == chrc->write_io) { + io_destroy(chrc->write_io); + chrc->write_io = NULL; + } else { + io_destroy(chrc->notify_io); + chrc->notify_io = NULL; + } + + return false; + } + + bt_shell_printf("%s closed\n", io == notify_io.io ? "Notify" : "Write"); + + if (io == notify_io.io) + notify_io_destroy(); + else + write_io_destroy(); + + return false; +} + +static struct io *sock_io_new(int fd, void *user_data) +{ + struct io *io; + + io = io_new(fd); + + io_set_close_on_destroy(io, true); + + io_set_read_handler(io, sock_read, user_data, NULL); + + io_set_disconnect_handler(io, sock_hup, user_data, NULL); + + return io; +} + +static void acquire_write_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + int fd; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to acquire write: %s\n", error.name); + dbus_error_free(&error); + write_io.proxy = NULL; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (write_io.io) + write_io_destroy(); + + if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_UINT16, &write_io.mtu, + DBUS_TYPE_INVALID) == false)) { + bt_shell_printf("Invalid AcquireWrite response\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("AcquireWrite success: fd %d MTU %u\n", fd, + write_io.mtu); + + write_io.io = sock_io_new(fd, NULL); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void acquire_setup(DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + dbus_message_iter_close_container(iter, &dict); +} + +void gatt_acquire_write(GDBusProxy *proxy, const char *arg) +{ + const char *iface; + + iface = g_dbus_proxy_get_interface(proxy); + if (strcmp(iface, "org.bluez.GattCharacteristic1")) { + bt_shell_printf("Unable to acquire write: %s not a" + " characteristic\n", + g_dbus_proxy_get_path(proxy)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (g_dbus_proxy_method_call(proxy, "AcquireWrite", acquire_setup, + acquire_write_reply, NULL, NULL) == FALSE) { + bt_shell_printf("Failed to AcquireWrite\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + write_io.proxy = proxy; +} + +void gatt_release_write(GDBusProxy *proxy, const char *arg) +{ + if (proxy != write_io.proxy || !write_io.io) { + bt_shell_printf("Write not acquired\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + write_io_destroy(); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void acquire_notify_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + int fd; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to acquire notify: %s\n", error.name); + dbus_error_free(&error); + write_io.proxy = NULL; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (notify_io.io) { + io_destroy(notify_io.io); + notify_io.io = NULL; + } + + notify_io.mtu = 0; + + if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_UINT16, ¬ify_io.mtu, + DBUS_TYPE_INVALID) == false)) { + bt_shell_printf("Invalid AcquireNotify response\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("AcquireNotify success: fd %d MTU %u\n", fd, + notify_io.mtu); + + notify_io.io = sock_io_new(fd, NULL); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void gatt_acquire_notify(GDBusProxy *proxy, const char *arg) +{ + const char *iface; + + iface = g_dbus_proxy_get_interface(proxy); + if (strcmp(iface, "org.bluez.GattCharacteristic1")) { + bt_shell_printf("Unable to acquire notify: %s not a" + " characteristic\n", + g_dbus_proxy_get_path(proxy)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (g_dbus_proxy_method_call(proxy, "AcquireNotify", acquire_setup, + acquire_notify_reply, NULL, NULL) == FALSE) { + bt_shell_printf("Failed to AcquireNotify\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + notify_io.proxy = proxy; +} + +void gatt_release_notify(GDBusProxy *proxy, const char *arg) +{ + if (proxy != notify_io.proxy || !notify_io.io) { + bt_shell_printf("Notify not acquired\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + notify_io_destroy(); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void notify_reply(DBusMessage *message, void *user_data) +{ + bool enable = GPOINTER_TO_UINT(user_data); + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to %s notify: %s\n", + enable ? "start" : "stop", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Notify %s\n", enable == TRUE ? "started" : "stopped"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void notify_attribute(GDBusProxy *proxy, bool enable) +{ + const char *method; + + if (enable == TRUE) + method = "StartNotify"; + else + method = "StopNotify"; + + if (g_dbus_proxy_method_call(proxy, method, NULL, notify_reply, + GUINT_TO_POINTER(enable), NULL) == FALSE) { + bt_shell_printf("Failed to %s notify\n", + enable ? "start" : "stop"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void gatt_notify_attribute(GDBusProxy *proxy, bool enable) +{ + const char *iface; + + iface = g_dbus_proxy_get_interface(proxy); + if (!strcmp(iface, "org.bluez.GattCharacteristic1")) { + notify_attribute(proxy, enable); + return; + } + + bt_shell_printf("Unable to notify attribute %s\n", + g_dbus_proxy_get_path(proxy)); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void register_app_setup(DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter opt; + const char *path = "/"; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &opt); + dbus_message_iter_close_container(iter, &opt); + +} + +static void register_app_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to register application: %s\n", + error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Application registered\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void gatt_add_manager(GDBusProxy *proxy) +{ + managers = g_list_append(managers, proxy); +} + +void gatt_remove_manager(GDBusProxy *proxy) +{ + managers = g_list_remove(managers, proxy); +} + +static int match_proxy(const void *a, const void *b) +{ + GDBusProxy *proxy1 = (void *) a; + GDBusProxy *proxy2 = (void *) b; + + return strcmp(g_dbus_proxy_get_path(proxy1), + g_dbus_proxy_get_path(proxy2)); +} + +static DBusMessage *release_profile(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + g_dbus_unregister_interface(conn, APP_PATH, PROFILE_INTERFACE); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable methods[] = { + { GDBUS_METHOD("Release", NULL, NULL, release_profile) }, + { } +}; + +static gboolean get_uuids(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + DBusMessageIter entry; + GList *uuid; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + + for (uuid = uuids; uuid; uuid = g_list_next(uuid->next)) + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, + &uuid->data); + + dbus_message_iter_close_container(iter, &entry); + + return TRUE; +} + +static const GDBusPropertyTable properties[] = { + { "UUIDs", "as", get_uuids }, + { } +}; + +void gatt_register_app(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]) +{ + GList *l; + int i; + + l = g_list_find_custom(managers, proxy, match_proxy); + if (!l) { + bt_shell_printf("Unable to find GattManager proxy\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + for (i = 0; i < argc; i++) + uuids = g_list_append(uuids, g_strdup(argv[i])); + + if (uuids) { + if (g_dbus_register_interface(conn, APP_PATH, + PROFILE_INTERFACE, methods, + NULL, properties, NULL, + NULL) == FALSE) { + bt_shell_printf("Failed to register application" + " object\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + if (g_dbus_proxy_method_call(l->data, "RegisterApplication", + register_app_setup, + register_app_reply, NULL, + NULL) == FALSE) { + bt_shell_printf("Failed register application\n"); + g_dbus_unregister_interface(conn, APP_PATH, PROFILE_INTERFACE); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void unregister_app_reply(DBusMessage *message, void *user_data) +{ + DBusConnection *conn = user_data; + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to unregister application: %s\n", + error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Application unregistered\n"); + + if (!uuids) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + g_list_free_full(uuids, g_free); + uuids = NULL; + + g_dbus_unregister_interface(conn, APP_PATH, PROFILE_INTERFACE); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void unregister_app_setup(DBusMessageIter *iter, void *user_data) +{ + const char *path = "/"; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); +} + +void gatt_unregister_app(DBusConnection *conn, GDBusProxy *proxy) +{ + GList *l; + + l = g_list_find_custom(managers, proxy, match_proxy); + if (!l) { + bt_shell_printf("Unable to find GattManager proxy\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (g_dbus_proxy_method_call(l->data, "UnregisterApplication", + unregister_app_setup, + unregister_app_reply, conn, + NULL) == FALSE) { + bt_shell_printf("Failed unregister profile\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void desc_free(void *data) +{ + struct desc *desc = data; + + g_free(desc->path); + g_free(desc->uuid); + g_strfreev(desc->flags); + g_free(desc->value); + g_free(desc); +} + +static void desc_unregister(void *data) +{ + struct desc *desc = data; + + print_desc(desc, COLORED_DEL); + + g_dbus_unregister_interface(desc->chrc->service->conn, desc->path, + DESC_INTERFACE); +} + +static void chrc_free(void *data) +{ + struct chrc *chrc = data; + + g_list_free_full(chrc->descs, desc_unregister); + g_free(chrc->path); + g_free(chrc->uuid); + g_strfreev(chrc->flags); + g_free(chrc->value); + g_free(chrc); +} + +static void chrc_unregister(void *data) +{ + struct chrc *chrc = data; + + print_chrc(chrc, COLORED_DEL); + + g_dbus_unregister_interface(chrc->service->conn, chrc->path, + CHRC_INTERFACE); +} + +static void inc_unregister(void *data) +{ + char *path = data; + + g_free(path); +} + +static void service_free(void *data) +{ + struct service *service = data; + + g_list_free_full(service->chrcs, chrc_unregister); + g_list_free_full(service->inc, inc_unregister); + g_free(service->path); + g_free(service->uuid); + g_free(service); +} + +static gboolean service_get_handle(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct service *service = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, + &service->handle); + + return TRUE; +} + +static void service_set_handle(const GDBusPropertyTable *property, + DBusMessageIter *value, GDBusPendingPropertySet id, + void *data) +{ + struct service *service = data; + + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT16) { + g_dbus_pending_property_error(id, "org.bluez.InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(value, &service->handle); + + print_service(service, COLORED_CHG); + + g_dbus_pending_property_success(id); +} + +static gboolean service_get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct service *service = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &service->uuid); + + return TRUE; +} + +static gboolean service_get_primary(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct service *service = data; + dbus_bool_t primary; + + primary = service->primary ? TRUE : FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &primary); + + return TRUE; +} + + +static gboolean service_get_includes(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + DBusMessageIter array; + struct service *service = data; + char *inc = NULL; + GList *l; + + if (service->inc) { + for (l = service->inc ; l; l = g_list_next(l)) { + + inc = l->data; + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_OBJECT_PATH_AS_STRING, &array); + + dbus_message_iter_append_basic(&array, + DBUS_TYPE_OBJECT_PATH, &inc); + + } + + dbus_message_iter_close_container(iter, &array); + + return TRUE; + } + + return FALSE; + +} + +static gboolean service_exist_includes(const GDBusPropertyTable *property, + void *data) +{ + struct service *service = data; + + if (service->inc) + return TRUE; + else + return FALSE; + +} + + +static const GDBusPropertyTable service_properties[] = { + { "Handle", "q", service_get_handle, service_set_handle }, + { "UUID", "s", service_get_uuid }, + { "Primary", "b", service_get_primary }, + { "Includes", "ao", service_get_includes, + NULL, service_exist_includes }, + { } +}; + +static void service_set_primary(const char *input, void *user_data) +{ + struct service *service = user_data; + + if (!strcmp(input, "yes")) + service->primary = true; + else if (!strcmp(input, "no")) { + service->primary = false; + } else { + bt_shell_printf("Invalid option: %s\n", input); + local_services = g_list_remove(local_services, service); + print_service(service, COLORED_DEL); + g_dbus_unregister_interface(service->conn, service->path, + SERVICE_INTERFACE); + } +} + +void gatt_register_service(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]) +{ + struct service *service; + bool primary = true; + + service = g_new0(struct service, 1); + service->conn = conn; + service->uuid = g_strdup(argv[1]); + service->path = g_strdup_printf("%s/service%u", APP_PATH, + g_list_length(local_services)); + service->primary = primary; + + if (argc > 2) + service->handle = atoi(argv[2]); + + if (g_dbus_register_interface(conn, service->path, + SERVICE_INTERFACE, NULL, NULL, + service_properties, service, + service_free) == FALSE) { + bt_shell_printf("Failed to register service object\n"); + service_free(service); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print_service(service, COLORED_NEW); + + local_services = g_list_append(local_services, service); + + bt_shell_prompt_input(service->path, "Primary (yes/no):", + service_set_primary, service); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct service *service_find(const char *pattern) +{ + GList *l; + + for (l = local_services; l; l = g_list_next(l)) { + struct service *service = l->data; + + /* match object path */ + if (!strcmp(service->path, pattern)) + return service; + + /* match UUID */ + if (!strcmp(service->uuid, pattern)) + return service; + } + + return NULL; +} + +void gatt_unregister_service(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]) +{ + struct service *service; + + service = service_find(argv[1]); + if (!service) { + bt_shell_printf("Failed to unregister service object\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + local_services = g_list_remove(local_services, service); + + print_service(service, COLORED_DEL); + + g_dbus_unregister_interface(service->conn, service->path, + SERVICE_INTERFACE); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static char *inc_find(struct service *serv, char *path) +{ + GList *lc; + + for (lc = serv->inc; lc; lc = g_list_next(lc)) { + char *incp = lc->data; + /* match object path */ + if (!strcmp(incp, path)) + return incp; + } + + return NULL; +} + +void gatt_register_include(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]) +{ + struct service *service, *inc_service; + char *inc_path; + + if (!local_services) { + bt_shell_printf("No service registered\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + service = g_list_last(local_services)->data; + + + inc_service = service_find(argv[1]); + if (!inc_service) { + bt_shell_printf("Failed to find service object\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + inc_path = g_strdup(service->path); + + inc_service->inc = g_list_append(inc_service->inc, inc_path); + + print_service(inc_service, COLORED_NEW); + print_inc_service(service, COLORED_NEW); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +void gatt_unregister_include(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]) +{ + struct service *ser_inc, *service; + char *path = NULL; + + service = service_find(argv[1]); + if (!service) { + bt_shell_printf("Failed to unregister include service" + " object\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ser_inc = service_find(argv[2]); + if (!ser_inc) { + bt_shell_printf("Failed to find include service object\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + path = inc_find(service, ser_inc->path); + if (path) { + service->inc = g_list_remove(service->inc, path); + inc_unregister(path); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static gboolean chrc_get_handle(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct chrc *chrc = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &chrc->handle); + + return TRUE; +} + +static void chrc_set_handle(const GDBusPropertyTable *property, + DBusMessageIter *value, GDBusPendingPropertySet id, + void *data) +{ + struct chrc *chrc = data; + + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT16) { + g_dbus_pending_property_error(id, "org.bluez.InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(value, &chrc->handle); + + print_chrc(chrc, COLORED_CHG); + + g_dbus_pending_property_success(id); +} + +static gboolean chrc_get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct chrc *chrc = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &chrc->uuid); + + return TRUE; +} + +static gboolean chrc_get_service(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct chrc *chrc = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &chrc->service->path); + + return TRUE; +} + +static gboolean chrc_get_value(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct chrc *chrc = data; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array); + + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &chrc->value, chrc->value_len); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean chrc_get_notifying(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct chrc *chrc = data; + dbus_bool_t value; + + value = chrc->notifying ? TRUE : FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); + + return TRUE; +} + +static gboolean chrc_get_flags(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct chrc *chrc = data; + int i; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &array); + + for (i = 0; chrc->flags[i]; i++) + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, + &chrc->flags[i]); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean chrc_get_write_acquired(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct chrc *chrc = data; + dbus_bool_t value; + + value = chrc->write_io ? TRUE : FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); + + return TRUE; +} + +static gboolean chrc_write_acquired_exists(const GDBusPropertyTable *property, + void *data) +{ + struct chrc *chrc = data; + int i; + + if (chrc->proxy) + return FALSE; + + for (i = 0; chrc->flags[i]; i++) { + if (!strcmp("write-without-response", chrc->flags[i])) + return TRUE; + } + + return FALSE; +} + +static gboolean chrc_get_notify_acquired(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct chrc *chrc = data; + dbus_bool_t value; + + value = chrc->notify_io ? TRUE : FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); + + return TRUE; +} + +static gboolean chrc_notify_acquired_exists(const GDBusPropertyTable *property, + void *data) +{ + struct chrc *chrc = data; + int i; + + if (chrc->proxy) + return FALSE; + + for (i = 0; chrc->flags[i]; i++) { + if (!strcmp("notify", chrc->flags[i])) + return TRUE; + } + + return FALSE; +} + +static const GDBusPropertyTable chrc_properties[] = { + { "Handle", "q", chrc_get_handle, chrc_set_handle, NULL }, + { "UUID", "s", chrc_get_uuid, NULL, NULL }, + { "Service", "o", chrc_get_service, NULL, NULL }, + { "Value", "ay", chrc_get_value, NULL, NULL }, + { "Notifying", "b", chrc_get_notifying, NULL, NULL }, + { "Flags", "as", chrc_get_flags, NULL, NULL }, + { "WriteAcquired", "b", chrc_get_write_acquired, NULL, + chrc_write_acquired_exists }, + { "NotifyAcquired", "b", chrc_get_notify_acquired, NULL, + chrc_notify_acquired_exists }, + { } +}; + +static const char *path_to_address(const char *path) +{ + GDBusProxy *proxy; + DBusMessageIter iter; + const char *address = path; + + proxy = bt_shell_get_env(path); + + if (g_dbus_proxy_get_property(proxy, "Address", &iter)) + dbus_message_iter_get_basic(&iter, &address); + + return address; +} + +static int parse_options(DBusMessageIter *iter, uint16_t *offset, uint16_t *mtu, + char **device, char **link, + bool *prep_authorize) +{ + DBusMessageIter dict; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(&dict, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "offset") == 0) { + if (var != DBUS_TYPE_UINT16) + return -EINVAL; + if (offset) + dbus_message_iter_get_basic(&value, offset); + } else if (strcasecmp(key, "MTU") == 0) { + if (var != DBUS_TYPE_UINT16) + return -EINVAL; + if (mtu) + dbus_message_iter_get_basic(&value, mtu); + } else if (strcasecmp(key, "device") == 0) { + if (var != DBUS_TYPE_OBJECT_PATH) + return -EINVAL; + if (device) + dbus_message_iter_get_basic(&value, device); + } else if (strcasecmp(key, "link") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + if (link) + dbus_message_iter_get_basic(&value, link); + } else if (strcasecmp(key, "prepare-authorize") == 0) { + if (var != DBUS_TYPE_BOOLEAN) + return -EINVAL; + if (prep_authorize) + dbus_message_iter_get_basic(&value, + prep_authorize); + } + + dbus_message_iter_next(&dict); + } + + return 0; +} + +static DBusMessage *read_value(DBusMessage *msg, uint8_t *value, + uint16_t value_len) +{ + DBusMessage *reply; + DBusMessageIter iter, array; + + reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "y", &array); + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &value, value_len); + dbus_message_iter_close_container(&iter, &array); + + return reply; +} + +struct authorize_attribute_data { + DBusConnection *conn; + void *attribute; + uint16_t offset; +}; + +static void authorize_read_response(const char *input, void *user_data) +{ + struct authorize_attribute_data *aad = user_data; + struct chrc *chrc = aad->attribute; + DBusMessage *reply; + char *err; + + if (!strcmp(input, "no")) { + err = "org.bluez.Error.NotAuthorized"; + + goto error; + } + + if (aad->offset > chrc->value_len) { + err = "org.bluez.Error.InvalidOffset"; + + goto error; + } + + reply = read_value(pending_message, &chrc->value[aad->offset], + chrc->value_len - aad->offset); + + g_dbus_send_message(aad->conn, reply); + + g_free(aad); + + return; + +error: + g_dbus_send_error(aad->conn, pending_message, err, NULL); + g_free(aad); +} + +static bool is_device_trusted(const char *path) +{ + GDBusProxy *proxy; + DBusMessageIter iter; + bool trusted = false; + + proxy = bt_shell_get_env(path); + + if (g_dbus_proxy_get_property(proxy, "Trusted", &iter)) + dbus_message_iter_get_basic(&iter, &trusted); + + return trusted; +} + +struct read_attribute_data { + DBusMessage *msg; + uint16_t offset; +}; + +static void proxy_read_reply(DBusMessage *message, void *user_data) +{ + struct read_attribute_data *data = user_data; + DBusConnection *conn = bt_shell_get_env("DBUS_CONNECTION"); + DBusError error; + DBusMessageIter iter, array; + DBusMessage *reply; + uint8_t *value; + int len; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to read: %s\n", error.name); + dbus_error_free(&error); + g_dbus_send_error(conn, data->msg, error.name, "%s", + error.message); + goto done; + } + + dbus_message_iter_init(message, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + bt_shell_printf("Invalid response to read\n"); + g_dbus_send_error(conn, data->msg, + "org.bluez.Error.InvalidArguments", NULL); + goto done; + } + + dbus_message_iter_recurse(&iter, &array); + dbus_message_iter_get_fixed_array(&array, &value, &len); + + if (len < 0) { + bt_shell_printf("Unable to parse value\n"); + g_dbus_send_error(conn, data->msg, + "org.bluez.Error.InvalidArguments", NULL); + } + + reply = read_value(data->msg, value, len); + + g_dbus_send_message(conn, reply); + +done: + dbus_message_unref(data->msg); + free(data); +} + +static void proxy_read_setup(DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter dict; + struct read_attribute_data *data = user_data; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + g_dbus_dict_append_entry(&dict, "offset", DBUS_TYPE_UINT16, + &data->offset); + + dbus_message_iter_close_container(iter, &dict); +} + +static DBusMessage *proxy_read_value(struct GDBusProxy *proxy, DBusMessage *msg, + uint16_t offset) +{ + struct read_attribute_data *data; + + data = new0(struct read_attribute_data, 1); + data->msg = dbus_message_ref(msg); + data->offset = offset; + + if (g_dbus_proxy_method_call(proxy, "ReadValue", proxy_read_setup, + proxy_read_reply, data, NULL)) + return NULL; + + bt_shell_printf("Failed to read\n"); + + return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments", + NULL); +} + +static DBusMessage *chrc_read_value(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct chrc *chrc = user_data; + DBusMessageIter iter; + uint16_t offset = 0; + char *device, *link; + char *str; + + dbus_message_iter_init(msg, &iter); + + if (parse_options(&iter, &offset, NULL, &device, &link, NULL)) + return g_dbus_create_error(msg, + "org.bluez.Error.InvalidArguments", + NULL); + + bt_shell_printf("[%s (%s)] ReadValue: %s offset %u link %s\n", + chrc->path, bt_uuidstr_to_str(chrc->uuid), + path_to_address(device), offset, link); + + if (chrc->proxy) { + return proxy_read_value(chrc->proxy, msg, offset); + } + + if (!is_device_trusted(device) && chrc->authorization_req) { + struct authorize_attribute_data *aad; + + aad = g_new0(struct authorize_attribute_data, 1); + aad->conn = conn; + aad->attribute = chrc; + aad->offset = offset; + + str = g_strdup_printf("Authorize attribute(%s) read (yes/no):", + chrc->path); + + bt_shell_prompt_input("gatt", str, authorize_read_response, + aad); + g_free(str); + + pending_message = dbus_message_ref(msg); + + return NULL; + } + + if (offset > chrc->value_len) + return g_dbus_create_error(msg, "org.bluez.Error.InvalidOffset", + NULL); + + return read_value(msg, &chrc->value[offset], chrc->value_len - offset); +} + +static int parse_value_arg(DBusMessageIter *iter, uint8_t **value, int *len) +{ + DBusMessageIter array; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(iter, &array); + dbus_message_iter_get_fixed_array(&array, value, len); + + return 0; +} + +static int write_value(size_t *dst_len, uint8_t **dst_value, uint8_t *src_val, + size_t src_len, uint16_t offset, uint16_t max_len) +{ + if ((offset + src_len) > max_len) + return -EOVERFLOW; + + if ((offset + src_len) != *dst_len) { + *dst_len = offset + src_len; + *dst_value = g_realloc(*dst_value, *dst_len); + } + + memcpy(*dst_value + offset, src_val, src_len); + + return 0; +} + +static void authorize_write_response(const char *input, void *user_data) +{ + struct authorize_attribute_data *aad = user_data; + struct chrc *chrc = aad->attribute; + bool prep_authorize = false; + DBusMessageIter iter; + DBusMessage *reply; + int value_len; + uint8_t *value; + char *err; + + dbus_message_iter_init(pending_message, &iter); + if (parse_value_arg(&iter, &value, &value_len)) { + err = "org.bluez.Error.InvalidArguments"; + + goto error; + } + + dbus_message_iter_next(&iter); + if (parse_options(&iter, NULL, NULL, NULL, NULL, &prep_authorize)) { + err = "org.bluez.Error.InvalidArguments"; + + goto error; + } + + if (!strcmp(input, "no")) { + err = "org.bluez.Error.NotAuthorized"; + + goto error; + } + + /* Authorization check of prepare writes */ + if (prep_authorize) { + reply = g_dbus_create_reply(pending_message, DBUS_TYPE_INVALID); + g_dbus_send_message(aad->conn, reply); + g_free(aad); + + return; + } + + if (write_value(&chrc->value_len, &chrc->value, value, value_len, + aad->offset, chrc->max_val_len)) { + err = "org.bluez.Error.InvalidValueLength"; + + goto error; + } + + bt_shell_printf("[" COLORED_CHG "] Attribute %s (%s) written", + chrc->path, bt_uuidstr_to_str(chrc->uuid)); + + g_dbus_emit_property_changed(aad->conn, chrc->path, CHRC_INTERFACE, + "Value"); + + reply = g_dbus_create_reply(pending_message, DBUS_TYPE_INVALID); + g_dbus_send_message(aad->conn, reply); + + g_free(aad); + + return; + +error: + g_dbus_send_error(aad->conn, pending_message, err, NULL); + g_free(aad); +} + +static void proxy_write_reply(DBusMessage *message, void *user_data) +{ + struct write_attribute_data *data = user_data; + DBusConnection *conn = bt_shell_get_env("DBUS_CONNECTION"); + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message)) { + bt_shell_printf("Failed to write: %s\n", error.name); + g_dbus_send_error(conn, data->msg, error.name, "%s", + error.message); + } else + g_dbus_send_reply(conn, data->msg, DBUS_TYPE_INVALID); + + dbus_message_unref(data->msg); + free(data); +} + +static DBusMessage *proxy_write_value(struct GDBusProxy *proxy, + DBusMessage *msg, uint8_t *value, + int value_len, uint16_t offset) +{ + struct write_attribute_data *data; + + + data = new0(struct write_attribute_data, 1); + data->msg = dbus_message_ref(msg); + data->iov.iov_base = (void *) value; + data->iov.iov_len = value_len; + data->offset = offset; + + if (g_dbus_proxy_method_call(proxy, "WriteValue", write_setup, + proxy_write_reply, data, NULL)) + return NULL; + + + bt_shell_printf("Failed to write\n"); + + return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments", + NULL); +} + +static DBusMessage *chrc_write_value(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct chrc *chrc = user_data; + uint16_t offset = 0; + bool prep_authorize = false; + char *device = NULL, *link = NULL; + DBusMessageIter iter; + int value_len; + uint8_t *value; + char *str; + + dbus_message_iter_init(msg, &iter); + + if (parse_value_arg(&iter, &value, &value_len)) + return g_dbus_create_error(msg, + "org.bluez.Error.InvalidArguments", NULL); + + dbus_message_iter_next(&iter); + if (parse_options(&iter, &offset, NULL, &device, &link, + &prep_authorize)) + return g_dbus_create_error(msg, + "org.bluez.Error.InvalidArguments", NULL); + + bt_shell_printf("[%s (%s)] WriteValue: %s offset %u link %s\n", + chrc->path, bt_uuidstr_to_str(chrc->uuid), + path_to_address(device), offset, link); + + bt_shell_hexdump(value, value_len); + + if (chrc->proxy) + return proxy_write_value(chrc->proxy, msg, value, value_len, + offset); + + if (!is_device_trusted(device) && chrc->authorization_req) { + struct authorize_attribute_data *aad; + + aad = g_new0(struct authorize_attribute_data, 1); + aad->conn = conn; + aad->attribute = chrc; + aad->offset = offset; + + str = g_strdup_printf("Authorize attribute(%s) write (yes/no):", + chrc->path); + + bt_shell_prompt_input("gatt", str, authorize_write_response, + aad); + g_free(str); + + pending_message = dbus_message_ref(msg); + + return NULL; + } + + /* Authorization check of prepare writes */ + if (prep_authorize) + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + + if (write_value(&chrc->value_len, &chrc->value, value, value_len, + offset, chrc->max_val_len)) + return g_dbus_create_error(msg, + "org.bluez.Error.InvalidValueLength", NULL); + + bt_shell_printf("[" COLORED_CHG "] Attribute %s (%s) written", + chrc->path, bt_uuidstr_to_str(chrc->uuid)); + + g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE, "Value"); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *create_sock(struct chrc *chrc, DBusMessage *msg) +{ + int fds[2]; + struct io *io; + bool dir; + DBusMessage *reply; + + if (socketpair(AF_LOCAL, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, + 0, fds) < 0) + return g_dbus_create_error(msg, "org.bluez.Error.Failed", "%s", + strerror(errno)); + + dir = dbus_message_has_member(msg, "AcquireWrite"); + + io = sock_io_new(fds[!dir], chrc); + if (!io) { + close(fds[0]); + close(fds[1]); + return g_dbus_create_error(msg, "org.bluez.Error.Failed", "%s", + strerror(errno)); + } + + reply = g_dbus_create_reply(msg, DBUS_TYPE_UNIX_FD, &fds[dir], + DBUS_TYPE_UINT16, &chrc->mtu, + DBUS_TYPE_INVALID); + + close(fds[dir]); + + if (dir) + chrc->write_io = io; + else + chrc->notify_io = io; + + bt_shell_printf("[" COLORED_CHG "] Attribute %s %s sock acquired\n", + chrc->path, dir ? "Write" : "Notify"); + + return reply; +} + +static DBusMessage *chrc_acquire_write(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct chrc *chrc = user_data; + DBusMessageIter iter; + DBusMessage *reply; + char *device = NULL, *link= NULL; + + dbus_message_iter_init(msg, &iter); + + if (chrc->write_io) + return g_dbus_create_error(msg, + "org.bluez.Error.NotPermitted", + NULL); + + if (parse_options(&iter, NULL, &chrc->mtu, &device, &link, NULL)) + return g_dbus_create_error(msg, + "org.bluez.Error.InvalidArguments", + NULL); + + bt_shell_printf("AcquireWrite: %s link %s\n", path_to_address(device), + link); + + reply = create_sock(chrc, msg); + + if (chrc->write_io) + g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE, + "WriteAcquired"); + + return reply; +} + +static DBusMessage *chrc_acquire_notify(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct chrc *chrc = user_data; + DBusMessageIter iter; + DBusMessage *reply; + char *device = NULL, *link = NULL; + + dbus_message_iter_init(msg, &iter); + + if (chrc->notify_io) + return g_dbus_create_error(msg, + "org.bluez.Error.NotPermitted", + NULL); + + if (parse_options(&iter, NULL, &chrc->mtu, &device, &link, NULL)) + return g_dbus_create_error(msg, + "org.bluez.Error.InvalidArguments", + NULL); + + bt_shell_printf("AcquireNotify: %s link %s\n", path_to_address(device), + link); + + reply = create_sock(chrc, msg); + + if (chrc->notify_io) + g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE, + "NotifyAcquired"); + + return reply; +} + +struct notify_attribute_data { + struct chrc *chrc; + DBusMessage *msg; + bool enable; +}; + +static void proxy_notify_reply(DBusMessage *message, void *user_data) +{ + struct notify_attribute_data *data = user_data; + DBusConnection *conn = bt_shell_get_env("DBUS_CONNECTION"); + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to %s: %s\n", + data->enable ? "StartNotify" : "StopNotify", + error.name); + dbus_error_free(&error); + g_dbus_send_error(conn, data->msg, error.name, "%s", + error.message); + goto done; + } + + g_dbus_send_reply(conn, data->msg, DBUS_TYPE_INVALID); + + data->chrc->notifying = data->enable; + bt_shell_printf("[" COLORED_CHG "] Attribute %s (%s) " + "notifications %s\n", + data->chrc->path, + bt_uuidstr_to_str(data->chrc->uuid), + data->enable ? "enabled" : "disabled"); + g_dbus_emit_property_changed(conn, data->chrc->path, CHRC_INTERFACE, + "Notifying"); + +done: + dbus_message_unref(data->msg); + free(data); +} + +static DBusMessage *proxy_notify(struct chrc *chrc, DBusMessage *msg, + bool enable) +{ + struct notify_attribute_data *data; + const char *method; + + if (enable == TRUE) + method = "StartNotify"; + else + method = "StopNotify"; + + data = new0(struct notify_attribute_data, 1); + data->chrc = chrc; + data->msg = dbus_message_ref(msg); + data->enable = enable; + + if (g_dbus_proxy_method_call(chrc->proxy, method, NULL, + proxy_notify_reply, data, NULL)) + return NULL; + + return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments", + NULL); +} + +static DBusMessage *chrc_start_notify(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct chrc *chrc = user_data; + + if (chrc->notifying) + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + + if (chrc->proxy) + return proxy_notify(chrc, msg, true); + + chrc->notifying = true; + bt_shell_printf("[" COLORED_CHG "] Attribute %s (%s) notifications " + "enabled", chrc->path, bt_uuidstr_to_str(chrc->uuid)); + g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE, + "Notifying"); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *chrc_stop_notify(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct chrc *chrc = user_data; + + if (!chrc->notifying) + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + + if (chrc->proxy) + return proxy_notify(chrc, msg, false); + + chrc->notifying = false; + bt_shell_printf("[" COLORED_CHG "] Attribute %s (%s) notifications " + "disabled", chrc->path, bt_uuidstr_to_str(chrc->uuid)); + g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE, + "Notifying"); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *chrc_confirm(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct chrc *chrc = user_data; + + bt_shell_printf("Attribute %s (%s) indication confirm received", + chrc->path, bt_uuidstr_to_str(chrc->uuid)); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable chrc_methods[] = { + { GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({ "options", "a{sv}" }), + GDBUS_ARGS({ "value", "ay" }), + chrc_read_value) }, + { GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({ "value", "ay" }, + { "options", "a{sv}" }), + NULL, chrc_write_value) }, + { GDBUS_METHOD("AcquireWrite", GDBUS_ARGS({ "options", "a{sv}" }), + NULL, chrc_acquire_write) }, + { GDBUS_METHOD("AcquireNotify", GDBUS_ARGS({ "options", "a{sv}" }), + NULL, chrc_acquire_notify) }, + { GDBUS_ASYNC_METHOD("StartNotify", NULL, NULL, chrc_start_notify) }, + { GDBUS_METHOD("StopNotify", NULL, NULL, chrc_stop_notify) }, + { GDBUS_METHOD("Confirm", NULL, NULL, chrc_confirm) }, + { } +}; + +static void chrc_set_value(const char *input, void *user_data) +{ + struct chrc *chrc = user_data; + + g_free(chrc->value); + + chrc->value = str2bytearray((char *) input, &chrc->value_len); + + if (!chrc->value) { + print_chrc(chrc, COLORED_DEL); + chrc_unregister(chrc); + } + + chrc->max_val_len = chrc->value_len; +} + +static gboolean attr_authorization_flag_exists(char **flags) +{ + int i; + + for (i = 0; flags[i]; i++) { + if (!strcmp("authorize", flags[i])) + return TRUE; + } + + return FALSE; +} + +void gatt_register_chrc(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]) +{ + struct service *service; + struct chrc *chrc; + + if (!local_services) { + bt_shell_printf("No service registered\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + service = g_list_last(local_services)->data; + + chrc = g_new0(struct chrc, 1); + chrc->service = service; + chrc->uuid = g_strdup(argv[1]); + chrc->path = g_strdup_printf("%s/chrc%u", service->path, + g_list_length(service->chrcs)); + chrc->flags = g_strsplit(argv[2], ",", -1); + chrc->authorization_req = attr_authorization_flag_exists(chrc->flags); + + if (argc > 3) + chrc->handle = atoi(argv[3]); + + if (g_dbus_register_interface(conn, chrc->path, CHRC_INTERFACE, + chrc_methods, NULL, chrc_properties, + chrc, chrc_free) == FALSE) { + bt_shell_printf("Failed to register characteristic object\n"); + chrc_free(chrc); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + service->chrcs = g_list_append(service->chrcs, chrc); + + print_chrc(chrc, COLORED_NEW); + + bt_shell_prompt_input(chrc->path, "Enter value:", chrc_set_value, chrc); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct chrc *chrc_find(const char *pattern) +{ + GList *l, *lc; + struct service *service; + struct chrc *chrc; + + for (l = local_services; l; l = g_list_next(l)) { + service = l->data; + + for (lc = service->chrcs; lc; lc = g_list_next(lc)) { + chrc = lc->data; + + /* match object path */ + if (!strcmp(chrc->path, pattern)) + return chrc; + + /* match UUID */ + if (!strcmp(chrc->uuid, pattern)) + return chrc; + } + } + + return NULL; +} + +void gatt_unregister_chrc(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]) +{ + struct chrc *chrc; + + chrc = chrc_find(argv[1]); + if (!chrc) { + bt_shell_printf("Failed to unregister characteristic object\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + chrc->service->chrcs = g_list_remove(chrc->service->chrcs, chrc); + + chrc_unregister(chrc); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static DBusMessage *desc_read_value(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct desc *desc = user_data; + DBusMessageIter iter; + uint16_t offset = 0; + char *device = NULL, *link = NULL; + + dbus_message_iter_init(msg, &iter); + + if (parse_options(&iter, &offset, NULL, &device, &link, NULL)) + return g_dbus_create_error(msg, + "org.bluez.Error.InvalidArguments", + NULL); + + bt_shell_printf("[%s (%s)] ReadValue: %s offset %u link %s\n", + desc->path, bt_uuidstr_to_str(desc->uuid), + path_to_address(device), offset, link); + + if (offset > desc->value_len) + return g_dbus_create_error(msg, "org.bluez.Error.InvalidOffset", + NULL); + + return read_value(msg, &desc->value[offset], desc->value_len - offset); +} + +static DBusMessage *desc_write_value(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct desc *desc = user_data; + DBusMessageIter iter; + uint16_t offset = 0; + char *device = NULL, *link = NULL; + int value_len; + uint8_t *value; + + dbus_message_iter_init(msg, &iter); + + if (parse_value_arg(&iter, &value, &value_len)) + return g_dbus_create_error(msg, + "org.bluez.Error.InvalidArguments", NULL); + + dbus_message_iter_next(&iter); + if (parse_options(&iter, &offset, NULL, &device, &link, NULL)) + return g_dbus_create_error(msg, + "org.bluez.Error.InvalidArguments", NULL); + + if (write_value(&desc->value_len, &desc->value, value, + value_len, offset, desc->max_val_len)) + return g_dbus_create_error(msg, + "org.bluez.Error.InvalidValueLength", NULL); + + bt_shell_printf("[%s (%s)] WriteValue: %s offset %u link %s\n", + desc->path, bt_uuidstr_to_str(desc->uuid), + path_to_address(device), offset, link); + + bt_shell_printf("[" COLORED_CHG "] Attribute %s (%s) written", + desc->path, bt_uuidstr_to_str(desc->uuid)); + + g_dbus_emit_property_changed(conn, desc->path, CHRC_INTERFACE, "Value"); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static const GDBusMethodTable desc_methods[] = { + { GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({ "options", "a{sv}" }), + GDBUS_ARGS({ "value", "ay" }), + desc_read_value) }, + { GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({ "value", "ay" }, + { "options", "a{sv}" }), + NULL, desc_write_value) }, + { } +}; + +static gboolean desc_get_handle(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct desc *desc = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &desc->handle); + + return TRUE; +} + +static void desc_set_handle(const GDBusPropertyTable *property, + DBusMessageIter *value, GDBusPendingPropertySet id, + void *data) +{ + struct desc *desc = data; + + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT16) { + g_dbus_pending_property_error(id, "org.bluez.InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(value, &desc->handle); + + print_desc(desc, COLORED_CHG); + + g_dbus_pending_property_success(id); +} + +static gboolean desc_get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct desc *desc = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &desc->uuid); + + return TRUE; +} + +static gboolean desc_get_chrc(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct desc *desc = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &desc->chrc->path); + + return TRUE; +} + +static gboolean desc_get_value(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct desc *desc = data; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array); + + if (desc->value) + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &desc->value, + desc->value_len); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean desc_get_flags(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct desc *desc = data; + int i; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &array); + + for (i = 0; desc->flags[i]; i++) + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, + &desc->flags[i]); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static const GDBusPropertyTable desc_properties[] = { + { "Handle", "q", desc_get_handle, desc_set_handle, NULL }, + { "UUID", "s", desc_get_uuid, NULL, NULL }, + { "Characteristic", "o", desc_get_chrc, NULL, NULL }, + { "Value", "ay", desc_get_value, NULL, NULL }, + { "Flags", "as", desc_get_flags, NULL, NULL }, + { } +}; + +static void desc_set_value(const char *input, void *user_data) +{ + struct desc *desc = user_data; + + g_free(desc->value); + + desc->value = str2bytearray((char *) input, &desc->value_len); + + if (!desc->value) { + print_desc(desc, COLORED_DEL); + desc_unregister(desc); + } + + desc->max_val_len = desc->value_len; +} + +void gatt_register_desc(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]) +{ + struct service *service; + struct desc *desc; + + if (!local_services) { + bt_shell_printf("No service registered\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + service = g_list_last(local_services)->data; + + if (!service->chrcs) { + bt_shell_printf("No characteristic registered\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + desc = g_new0(struct desc, 1); + desc->chrc = g_list_last(service->chrcs)->data; + desc->uuid = g_strdup(argv[1]); + desc->path = g_strdup_printf("%s/desc%u", desc->chrc->path, + g_list_length(desc->chrc->descs)); + desc->flags = g_strsplit(argv[2], ",", -1); + + if (argc > 3) + desc->handle = atoi(argv[3]); + + if (g_dbus_register_interface(conn, desc->path, DESC_INTERFACE, + desc_methods, NULL, desc_properties, + desc, desc_free) == FALSE) { + bt_shell_printf("Failed to register descriptor object\n"); + desc_free(desc); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + desc->chrc->descs = g_list_append(desc->chrc->descs, desc); + + print_desc(desc, COLORED_NEW); + + bt_shell_prompt_input(desc->path, "Enter value:", desc_set_value, desc); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct desc *desc_find(const char *pattern) +{ + GList *l, *lc, *ld; + struct service *service; + struct chrc *chrc; + struct desc *desc; + + for (l = local_services; l; l = g_list_next(l)) { + service = l->data; + + for (lc = service->chrcs; lc; lc = g_list_next(lc)) { + chrc = lc->data; + + for (ld = chrc->descs; ld; ld = g_list_next(ld)) { + desc = ld->data; + + /* match object path */ + if (!strcmp(desc->path, pattern)) + return desc; + + /* match UUID */ + if (!strcmp(desc->uuid, pattern)) + return desc; + } + } + } + + return NULL; +} + +void gatt_unregister_desc(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]) +{ + struct desc *desc; + + desc = desc_find(argv[1]); + if (!desc) { + bt_shell_printf("Failed to unregister descriptor object\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + desc->chrc->descs = g_list_remove(desc->chrc->descs, desc); + + desc_unregister(desc); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static GDBusProxy *select_service(GDBusProxy *proxy) +{ + GList *l; + + for (l = services; l; l = g_list_next(l)) { + GDBusProxy *p = l->data; + + if (proxy == p || g_str_has_prefix(g_dbus_proxy_get_path(proxy), + g_dbus_proxy_get_path(p))) + return p; + } + + return NULL; +} + +static void proxy_property_changed(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + DBusConnection *conn = bt_shell_get_env("DBUS_CONNECTION"); + struct chrc *chrc = user_data; + + bt_shell_printf("[" COLORED_CHG "] Attribute %s (%s) %s:\n", + chrc->path, bt_uuidstr_to_str(chrc->uuid), name); + + if (!strcmp(name, "Value")) { + DBusMessageIter array; + uint8_t *value; + int len; + + if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY) { + dbus_message_iter_recurse(iter, &array); + dbus_message_iter_get_fixed_array(&array, &value, &len); + write_value(&chrc->value_len, &chrc->value, value, len, + 0, chrc->max_val_len); + bt_shell_hexdump(value, len); + } + } + + g_dbus_emit_property_changed(conn, chrc->path, CHRC_INTERFACE, name); +} + +static void clone_chrc(struct GDBusProxy *proxy) +{ + struct service *service; + struct chrc *chrc; + DBusMessageIter iter; + DBusMessageIter array; + const char *uuid; + char *flags[17]; + int i; + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &uuid); + + if (g_dbus_proxy_get_property(proxy, "Flags", &iter) == FALSE) + return; + + dbus_message_iter_recurse(&iter, &array); + + for (i = 0; dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING; + i++) { + dbus_message_iter_get_basic(&array, &flags[i]); + dbus_message_iter_next(&array); + } + + flags[i] = NULL; + + service = g_list_last(local_services)->data; + + chrc = g_new0(struct chrc, 1); + chrc->service = service; + chrc->proxy = proxy; + chrc->uuid = g_strdup(uuid); + chrc->path = g_strdup_printf("%s/chrc%u", service->path, + g_list_length(service->chrcs)); + chrc->flags = g_strdupv(flags); + + if (g_dbus_register_interface(service->conn, chrc->path, CHRC_INTERFACE, + chrc_methods, NULL, chrc_properties, + chrc, chrc_free) == FALSE) { + bt_shell_printf("Failed to register characteristic object\n"); + chrc_free(chrc); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + g_dbus_proxy_set_property_watch(proxy, proxy_property_changed, chrc); + + service->chrcs = g_list_append(service->chrcs, chrc); + + print_chrc(chrc, COLORED_NEW); +} + +static void clone_chrcs(struct GDBusProxy *proxy) +{ + GList *l; + + for (l = characteristics; l; l = g_list_next(l)) { + GDBusProxy *p = l->data; + + if (g_str_has_prefix(g_dbus_proxy_get_path(p), + g_dbus_proxy_get_path(proxy))) + clone_chrc(p); + } +} + +static void clone_service(struct GDBusProxy *proxy) +{ + struct service *service; + DBusMessageIter iter; + const char *uuid; + dbus_bool_t primary; + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &uuid); + + if (g_dbus_proxy_get_property(proxy, "Primary", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &primary); + + if (!strcmp(uuid, "00001800-0000-1000-8000-00805f9b34fb") || + !strcmp(uuid, "00001801-0000-1000-8000-00805f9b34fb")) + return; + + service = g_new0(struct service, 1); + service->conn = bt_shell_get_env("DBUS_CONNECTION"); + service->proxy = proxy; + service->path = g_strdup_printf("%s/service%u", APP_PATH, + g_list_length(local_services)); + service->uuid = g_strdup(uuid); + service->primary = primary; + + if (g_dbus_register_interface(service->conn, service->path, + SERVICE_INTERFACE, NULL, NULL, + service_properties, service, + service_free) == FALSE) { + bt_shell_printf("Failed to register service object\n"); + service_free(service); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print_service(service, COLORED_NEW); + + local_services = g_list_append(local_services, service); + + clone_chrcs(proxy); +} + +static void clone_device(struct GDBusProxy *proxy) +{ + GList *l; + + for (l = services; l; l = g_list_next(l)) { + struct GDBusProxy *p = l->data; + + if (g_str_has_prefix(g_dbus_proxy_get_path(p), + g_dbus_proxy_get_path(proxy))) + clone_service(p); + } +} + +static void service_clone(const char *input, void *user_data) +{ + struct GDBusProxy *proxy = user_data; + + if (!strcmp(input, "yes")) + return clone_service(proxy); + else if (!strcmp(input, "no")) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + else if (!strcmp(input, "all")) + return clone_device(proxy); + + bt_shell_printf("Invalid option: %s\n", input); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void device_clone(const char *input, void *user_data) +{ + struct GDBusProxy *proxy = user_data; + + if (!strcmp(input, "yes")) + return clone_device(proxy); + else if (!strcmp(input, "no")) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + bt_shell_printf("Invalid option: %s\n", input); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static const char *proxy_get_name(struct GDBusProxy *proxy) +{ + DBusMessageIter iter; + const char *uuid; + const char *str; + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + return NULL; + + dbus_message_iter_get_basic(&iter, &uuid); + + str = bt_uuidstr_to_str(uuid); + + return str ? str : uuid; +} + +static const char *proxy_get_alias(struct GDBusProxy *proxy) +{ + DBusMessageIter iter; + const char *alias; + + if (g_dbus_proxy_get_property(proxy, "Alias", &iter) == FALSE) + return NULL; + + dbus_message_iter_get_basic(&iter, &alias); + + return alias; +} + +void gatt_clone_attribute(GDBusProxy *proxy, int argc, char *argv[]) +{ + GDBusProxy *service = NULL; + + if (argc > 1) { + proxy = gatt_select_attribute(proxy, argv[1]); + if (!proxy) { + bt_shell_printf("Unable to find attribute %s\n", + argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + if (!strcmp(g_dbus_proxy_get_interface(proxy), DEVICE_INTERFACE)) { + bt_shell_prompt_input(proxy_get_alias(proxy), + "Clone (yes/no):", + device_clone, proxy); + } + + /* Only clone services */ + service = select_service(proxy); + if (service) { + bt_shell_prompt_input(proxy_get_name(proxy), + "Clone (yes/no/all):", + service_clone, service); + return; + } + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} diff --git a/client/gatt.h b/client/gatt.h new file mode 100644 index 0000000..09ca618 --- /dev/null +++ b/client/gatt.h @@ -0,0 +1,73 @@ +/* + * + * 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 + * + */ + +void gatt_add_service(GDBusProxy *proxy); +void gatt_remove_service(GDBusProxy *proxy); + +void gatt_add_characteristic(GDBusProxy *proxy); +void gatt_remove_characteristic(GDBusProxy *proxy); + +void gatt_add_descriptor(GDBusProxy *proxy); +void gatt_remove_descriptor(GDBusProxy *proxy); + +void gatt_list_attributes(const char *device); +GDBusProxy *gatt_select_attribute(GDBusProxy *parent, const char *path); +char *gatt_attribute_generator(const char *text, int state); + +void gatt_read_attribute(GDBusProxy *proxy, int argc, char *argv[]); +void gatt_write_attribute(GDBusProxy *proxy, int argc, char *argv[]); +void gatt_notify_attribute(GDBusProxy *proxy, bool enable); +void gatt_clone_attribute(GDBusProxy *proxy, int argc, char *argv[]); + +void gatt_acquire_write(GDBusProxy *proxy, const char *arg); +void gatt_release_write(GDBusProxy *proxy, const char *arg); + +void gatt_acquire_notify(GDBusProxy *proxy, const char *arg); +void gatt_release_notify(GDBusProxy *proxy, const char *arg); + +void gatt_add_manager(GDBusProxy *proxy); +void gatt_remove_manager(GDBusProxy *proxy); + +void gatt_register_app(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]); +void gatt_unregister_app(DBusConnection *conn, GDBusProxy *proxy); + +void gatt_register_service(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]); +void gatt_unregister_service(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]); + +void gatt_register_chrc(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]); +void gatt_unregister_chrc(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]); + +void gatt_register_desc(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]); +void gatt_unregister_desc(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]); + +void gatt_register_include(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]); +void gatt_unregister_include(DBusConnection *conn, GDBusProxy *proxy, + int argc, char *argv[]); diff --git a/client/main.c b/client/main.c new file mode 100644 index 0000000..68dabda --- /dev/null +++ b/client/main.c @@ -0,0 +1,2832 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#include "src/shared/shell.h" +#include "src/shared/util.h" +#include "gdbus/gdbus.h" +#include "agent.h" +#include "gatt.h" +#include "advertising.h" + +/* String display constants */ +#define COLORED_NEW COLOR_GREEN "NEW" COLOR_OFF +#define COLORED_CHG COLOR_YELLOW "CHG" COLOR_OFF +#define COLORED_DEL COLOR_RED "DEL" COLOR_OFF + +#define PROMPT_ON COLOR_BLUE "[bluetooth]" COLOR_OFF "# " +#define PROMPT_OFF "Waiting to connect to bluetoothd..." + +static DBusConnection *dbus_conn; + +static GDBusProxy *agent_manager; +static char *auto_register_agent = NULL; + +struct adapter { + GDBusProxy *proxy; + GDBusProxy *ad_proxy; + GList *devices; +}; + +static struct adapter *default_ctrl; +static GDBusProxy *default_dev; +static GDBusProxy *default_attr; +static GList *ctrl_list; + +static const char *agent_arguments[] = { + "on", + "off", + "DisplayOnly", + "DisplayYesNo", + "KeyboardDisplay", + "KeyboardOnly", + "NoInputNoOutput", + NULL +}; + +static const char *ad_arguments[] = { + "on", + "off", + "peripheral", + "broadcast", + NULL +}; + +static void proxy_leak(gpointer data) +{ + printf("Leaking proxy %p\n", data); +} + +static void setup_standard_input(void) +{ + bt_shell_attach(fileno(stdin)); +} + +static void connect_handler(DBusConnection *connection, void *user_data) +{ + bt_shell_set_prompt(PROMPT_ON); +} + +static void disconnect_handler(DBusConnection *connection, void *user_data) +{ + bt_shell_detach(); + + bt_shell_set_prompt(PROMPT_OFF); + + g_list_free_full(ctrl_list, proxy_leak); + ctrl_list = NULL; + + default_ctrl = NULL; +} + +static void print_adapter(GDBusProxy *proxy, const char *description) +{ + DBusMessageIter iter; + const char *address, *name; + + if (g_dbus_proxy_get_property(proxy, "Address", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &address); + + if (g_dbus_proxy_get_property(proxy, "Alias", &iter) == TRUE) + dbus_message_iter_get_basic(&iter, &name); + else + name = ""; + + bt_shell_printf("%s%s%sController %s %s %s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + address, name, + default_ctrl && + default_ctrl->proxy == proxy ? + "[default]" : ""); + +} + +static void print_device(GDBusProxy *proxy, const char *description) +{ + DBusMessageIter iter; + const char *address, *name; + + if (g_dbus_proxy_get_property(proxy, "Address", &iter) == FALSE) + return; + + dbus_message_iter_get_basic(&iter, &address); + + if (g_dbus_proxy_get_property(proxy, "Alias", &iter) == TRUE) + dbus_message_iter_get_basic(&iter, &name); + else + name = ""; + + bt_shell_printf("%s%s%sDevice %s %s\n", + description ? "[" : "", + description ? : "", + description ? "] " : "", + address, name); +} + +static void print_fixed_iter(const char *label, const char *name, + DBusMessageIter *iter) +{ + dbus_bool_t *valbool; + dbus_uint32_t *valu32; + dbus_uint16_t *valu16; + dbus_int16_t *vals16; + unsigned char *byte; + int len; + + switch (dbus_message_iter_get_arg_type(iter)) { + case DBUS_TYPE_BOOLEAN: + dbus_message_iter_get_fixed_array(iter, &valbool, &len); + + if (len <= 0) + return; + + bt_shell_printf("%s%s:\n", label, name); + bt_shell_hexdump((void *)valbool, len * sizeof(*valbool)); + + break; + case DBUS_TYPE_UINT32: + dbus_message_iter_get_fixed_array(iter, &valu32, &len); + + if (len <= 0) + return; + + bt_shell_printf("%s%s:\n", label, name); + bt_shell_hexdump((void *)valu32, len * sizeof(*valu32)); + + break; + case DBUS_TYPE_UINT16: + dbus_message_iter_get_fixed_array(iter, &valu16, &len); + + if (len <= 0) + return; + + bt_shell_printf("%s%s:\n", label, name); + bt_shell_hexdump((void *)valu16, len * sizeof(*valu16)); + + break; + case DBUS_TYPE_INT16: + dbus_message_iter_get_fixed_array(iter, &vals16, &len); + + if (len <= 0) + return; + + bt_shell_printf("%s%s:\n", label, name); + bt_shell_hexdump((void *)vals16, len * sizeof(*vals16)); + + break; + case DBUS_TYPE_BYTE: + dbus_message_iter_get_fixed_array(iter, &byte, &len); + + if (len <= 0) + return; + + bt_shell_printf("%s%s:\n", label, name); + bt_shell_hexdump((void *)byte, len * sizeof(*byte)); + + break; + default: + return; + }; +} + +static void print_iter(const char *label, const char *name, + DBusMessageIter *iter) +{ + dbus_bool_t valbool; + dbus_uint32_t valu32; + dbus_uint16_t valu16; + dbus_int16_t vals16; + unsigned char byte; + const char *valstr; + DBusMessageIter subiter; + char *entry; + + if (iter == NULL) { + bt_shell_printf("%s%s is nil\n", label, name); + return; + } + + switch (dbus_message_iter_get_arg_type(iter)) { + case DBUS_TYPE_INVALID: + bt_shell_printf("%s%s is invalid\n", label, name); + break; + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + dbus_message_iter_get_basic(iter, &valstr); + bt_shell_printf("%s%s: %s\n", label, name, valstr); + break; + case DBUS_TYPE_BOOLEAN: + dbus_message_iter_get_basic(iter, &valbool); + bt_shell_printf("%s%s: %s\n", label, name, + valbool == TRUE ? "yes" : "no"); + break; + case DBUS_TYPE_UINT32: + dbus_message_iter_get_basic(iter, &valu32); + bt_shell_printf("%s%s: 0x%08x\n", label, name, valu32); + break; + case DBUS_TYPE_UINT16: + dbus_message_iter_get_basic(iter, &valu16); + bt_shell_printf("%s%s: 0x%04x\n", label, name, valu16); + break; + case DBUS_TYPE_INT16: + dbus_message_iter_get_basic(iter, &vals16); + bt_shell_printf("%s%s: %d\n", label, name, vals16); + break; + case DBUS_TYPE_BYTE: + dbus_message_iter_get_basic(iter, &byte); + bt_shell_printf("%s%s: 0x%02x\n", label, name, byte); + break; + case DBUS_TYPE_VARIANT: + dbus_message_iter_recurse(iter, &subiter); + print_iter(label, name, &subiter); + break; + case DBUS_TYPE_ARRAY: + dbus_message_iter_recurse(iter, &subiter); + + if (dbus_type_is_fixed( + dbus_message_iter_get_arg_type(&subiter))) { + print_fixed_iter(label, name, &subiter); + break; + } + + while (dbus_message_iter_get_arg_type(&subiter) != + DBUS_TYPE_INVALID) { + print_iter(label, name, &subiter); + dbus_message_iter_next(&subiter); + } + break; + case DBUS_TYPE_DICT_ENTRY: + dbus_message_iter_recurse(iter, &subiter); + entry = g_strconcat(name, " Key", NULL); + print_iter(label, entry, &subiter); + g_free(entry); + + entry = g_strconcat(name, " Value", NULL); + dbus_message_iter_next(&subiter); + print_iter(label, entry, &subiter); + g_free(entry); + break; + default: + bt_shell_printf("%s%s has unsupported type\n", label, name); + break; + } +} + +static void print_property(GDBusProxy *proxy, const char *name) +{ + DBusMessageIter iter; + + if (g_dbus_proxy_get_property(proxy, name, &iter) == FALSE) + return; + + print_iter("\t", name, &iter); +} + +static void print_uuid(const char *uuid) +{ + const char *text; + + text = bt_uuidstr_to_str(uuid); + if (text) { + char str[26]; + unsigned int n; + + str[sizeof(str) - 1] = '\0'; + + n = snprintf(str, sizeof(str), "%s", text); + if (n > sizeof(str) - 1) { + str[sizeof(str) - 2] = '.'; + str[sizeof(str) - 3] = '.'; + if (str[sizeof(str) - 4] == ' ') + str[sizeof(str) - 4] = '.'; + + n = sizeof(str) - 1; + } + + bt_shell_printf("\tUUID: %s%*c(%s)\n", str, 26 - n, ' ', uuid); + } else + bt_shell_printf("\tUUID: %*c(%s)\n", 26, ' ', uuid); +} + +static void print_uuids(GDBusProxy *proxy) +{ + DBusMessageIter iter, value; + + if (g_dbus_proxy_get_property(proxy, "UUIDs", &iter) == FALSE) + return; + + dbus_message_iter_recurse(&iter, &value); + + while (dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRING) { + const char *uuid; + + dbus_message_iter_get_basic(&value, &uuid); + + print_uuid(uuid); + + dbus_message_iter_next(&value); + } +} + +static gboolean device_is_child(GDBusProxy *device, GDBusProxy *master) +{ + DBusMessageIter iter; + const char *adapter, *path; + + if (!master) + return FALSE; + + if (g_dbus_proxy_get_property(device, "Adapter", &iter) == FALSE) + return FALSE; + + dbus_message_iter_get_basic(&iter, &adapter); + path = g_dbus_proxy_get_path(master); + + if (!strcmp(path, adapter)) + return TRUE; + + return FALSE; +} + +static gboolean service_is_child(GDBusProxy *service) +{ + DBusMessageIter iter; + const char *device; + + if (g_dbus_proxy_get_property(service, "Device", &iter) == FALSE) + return FALSE; + + dbus_message_iter_get_basic(&iter, &device); + + if (!default_ctrl) + return FALSE; + + return g_dbus_proxy_lookup(default_ctrl->devices, NULL, device, + "org.bluez.Device1") != NULL; +} + +static struct adapter *find_parent(GDBusProxy *device) +{ + GList *list; + + for (list = g_list_first(ctrl_list); list; list = g_list_next(list)) { + struct adapter *adapter = list->data; + + if (device_is_child(device, adapter->proxy) == TRUE) + return adapter; + } + return NULL; +} + +static void set_default_device(GDBusProxy *proxy, const char *attribute) +{ + char *desc = NULL; + DBusMessageIter iter; + const char *path; + + default_dev = proxy; + + if (proxy == NULL) { + default_attr = NULL; + goto done; + } + + if (!g_dbus_proxy_get_property(proxy, "Alias", &iter)) { + if (!g_dbus_proxy_get_property(proxy, "Address", &iter)) + goto done; + } + + path = g_dbus_proxy_get_path(proxy); + + dbus_message_iter_get_basic(&iter, &desc); + desc = g_strdup_printf(COLOR_BLUE "[%s%s%s]" COLOR_OFF "# ", desc, + attribute ? ":" : "", + attribute ? attribute + strlen(path) : ""); + +done: + bt_shell_set_prompt(desc ? desc : PROMPT_ON); + g_free(desc); +} + +static void device_added(GDBusProxy *proxy) +{ + DBusMessageIter iter; + struct adapter *adapter = find_parent(proxy); + + if (!adapter) { + /* TODO: Error */ + return; + } + + adapter->devices = g_list_append(adapter->devices, proxy); + print_device(proxy, COLORED_NEW); + bt_shell_set_env(g_dbus_proxy_get_path(proxy), proxy); + + if (default_dev) + return; + + if (g_dbus_proxy_get_property(proxy, "Connected", &iter)) { + dbus_bool_t connected; + + dbus_message_iter_get_basic(&iter, &connected); + + if (connected) + set_default_device(proxy, NULL); + } +} + +static struct adapter *find_ctrl(GList *source, const char *path); + +static struct adapter *adapter_new(GDBusProxy *proxy) +{ + struct adapter *adapter = g_malloc0(sizeof(struct adapter)); + + ctrl_list = g_list_append(ctrl_list, adapter); + + if (!default_ctrl) + default_ctrl = adapter; + + return adapter; +} + +static void adapter_added(GDBusProxy *proxy) +{ + struct adapter *adapter; + adapter = find_ctrl(ctrl_list, g_dbus_proxy_get_path(proxy)); + if (!adapter) + adapter = adapter_new(proxy); + + adapter->proxy = proxy; + + print_adapter(proxy, COLORED_NEW); + bt_shell_set_env(g_dbus_proxy_get_path(proxy), proxy); +} + +static void ad_manager_added(GDBusProxy *proxy) +{ + struct adapter *adapter; + adapter = find_ctrl(ctrl_list, g_dbus_proxy_get_path(proxy)); + if (!adapter) + adapter = adapter_new(proxy); + + adapter->ad_proxy = proxy; +} + +static void proxy_added(GDBusProxy *proxy, void *user_data) +{ + const char *interface; + + interface = g_dbus_proxy_get_interface(proxy); + + if (!strcmp(interface, "org.bluez.Device1")) { + device_added(proxy); + } else if (!strcmp(interface, "org.bluez.Adapter1")) { + adapter_added(proxy); + } else if (!strcmp(interface, "org.bluez.AgentManager1")) { + if (!agent_manager) { + agent_manager = proxy; + + if (auto_register_agent && + !bt_shell_get_env("NON_INTERACTIVE")) + agent_register(dbus_conn, agent_manager, + auto_register_agent); + } + } else if (!strcmp(interface, "org.bluez.GattService1")) { + if (service_is_child(proxy)) + gatt_add_service(proxy); + } else if (!strcmp(interface, "org.bluez.GattCharacteristic1")) { + gatt_add_characteristic(proxy); + } else if (!strcmp(interface, "org.bluez.GattDescriptor1")) { + gatt_add_descriptor(proxy); + } else if (!strcmp(interface, "org.bluez.GattManager1")) { + gatt_add_manager(proxy); + } else if (!strcmp(interface, "org.bluez.LEAdvertisingManager1")) { + ad_manager_added(proxy); + } +} + +static void set_default_attribute(GDBusProxy *proxy) +{ + const char *path; + + default_attr = proxy; + + path = g_dbus_proxy_get_path(proxy); + + set_default_device(default_dev, path); +} + +static void device_removed(GDBusProxy *proxy) +{ + struct adapter *adapter = find_parent(proxy); + if (!adapter) { + /* TODO: Error */ + return; + } + + adapter->devices = g_list_remove(adapter->devices, proxy); + + print_device(proxy, COLORED_DEL); + bt_shell_set_env(g_dbus_proxy_get_path(proxy), NULL); + + if (default_dev == proxy) + set_default_device(NULL, NULL); +} + +static void adapter_removed(GDBusProxy *proxy) +{ + GList *ll; + + for (ll = g_list_first(ctrl_list); ll; ll = g_list_next(ll)) { + struct adapter *adapter = ll->data; + + if (adapter->proxy == proxy) { + print_adapter(proxy, COLORED_DEL); + bt_shell_set_env(g_dbus_proxy_get_path(proxy), NULL); + + if (default_ctrl && default_ctrl->proxy == proxy) { + default_ctrl = NULL; + set_default_device(NULL, NULL); + } + + ctrl_list = g_list_remove_link(ctrl_list, ll); + g_list_free(adapter->devices); + g_free(adapter); + g_list_free(ll); + return; + } + } +} + +static void proxy_removed(GDBusProxy *proxy, void *user_data) +{ + const char *interface; + + interface = g_dbus_proxy_get_interface(proxy); + + if (!strcmp(interface, "org.bluez.Device1")) { + device_removed(proxy); + } else if (!strcmp(interface, "org.bluez.Adapter1")) { + adapter_removed(proxy); + } else if (!strcmp(interface, "org.bluez.AgentManager1")) { + if (agent_manager == proxy) { + agent_manager = NULL; + if (auto_register_agent) + agent_unregister(dbus_conn, NULL); + } + } else if (!strcmp(interface, "org.bluez.GattService1")) { + gatt_remove_service(proxy); + + if (default_attr == proxy) + set_default_attribute(NULL); + } else if (!strcmp(interface, "org.bluez.GattCharacteristic1")) { + gatt_remove_characteristic(proxy); + + if (default_attr == proxy) + set_default_attribute(NULL); + } else if (!strcmp(interface, "org.bluez.GattDescriptor1")) { + gatt_remove_descriptor(proxy); + + if (default_attr == proxy) + set_default_attribute(NULL); + } else if (!strcmp(interface, "org.bluez.GattManager1")) { + gatt_remove_manager(proxy); + } else if (!strcmp(interface, "org.bluez.LEAdvertisingManager1")) { + ad_unregister(dbus_conn, NULL); + } +} + +static struct adapter *find_ctrl(GList *source, const char *path) +{ + GList *list; + + for (list = g_list_first(source); list; list = g_list_next(list)) { + struct adapter *adapter = list->data; + + if (!strcasecmp(g_dbus_proxy_get_path(adapter->proxy), path)) + return adapter; + } + + return NULL; +} + +static void property_changed(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + const char *interface; + struct adapter *ctrl; + + interface = g_dbus_proxy_get_interface(proxy); + + if (!strcmp(interface, "org.bluez.Device1")) { + if (default_ctrl && device_is_child(proxy, + default_ctrl->proxy) == TRUE) { + DBusMessageIter addr_iter; + char *str; + + if (g_dbus_proxy_get_property(proxy, "Address", + &addr_iter) == TRUE) { + const char *address; + + dbus_message_iter_get_basic(&addr_iter, + &address); + str = g_strdup_printf("[" COLORED_CHG + "] Device %s ", address); + } else + str = g_strdup(""); + + if (strcmp(name, "Connected") == 0) { + dbus_bool_t connected; + + dbus_message_iter_get_basic(iter, &connected); + + if (connected && default_dev == NULL) + set_default_device(proxy, NULL); + else if (!connected && default_dev == proxy) + set_default_device(NULL, NULL); + } + + print_iter(str, name, iter); + g_free(str); + } + } else if (!strcmp(interface, "org.bluez.Adapter1")) { + DBusMessageIter addr_iter; + char *str; + + if (g_dbus_proxy_get_property(proxy, "Address", + &addr_iter) == TRUE) { + const char *address; + + dbus_message_iter_get_basic(&addr_iter, &address); + str = g_strdup_printf("[" COLORED_CHG + "] Controller %s ", address); + } else + str = g_strdup(""); + + print_iter(str, name, iter); + g_free(str); + } else if (!strcmp(interface, "org.bluez.LEAdvertisingManager1")) { + DBusMessageIter addr_iter; + char *str; + + ctrl = find_ctrl(ctrl_list, g_dbus_proxy_get_path(proxy)); + if (!ctrl) + return; + + if (g_dbus_proxy_get_property(ctrl->proxy, "Address", + &addr_iter) == TRUE) { + const char *address; + + dbus_message_iter_get_basic(&addr_iter, &address); + str = g_strdup_printf("[" COLORED_CHG + "] Controller %s ", + address); + } else + str = g_strdup(""); + + print_iter(str, name, iter); + g_free(str); + } else if (proxy == default_attr) { + char *str; + + str = g_strdup_printf("[" COLORED_CHG "] Attribute %s ", + g_dbus_proxy_get_path(proxy)); + + print_iter(str, name, iter); + g_free(str); + } +} + +static void message_handler(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + bt_shell_printf("[SIGNAL] %s.%s\n", dbus_message_get_interface(message), + dbus_message_get_member(message)); +} + +static struct adapter *find_ctrl_by_address(GList *source, const char *address) +{ + GList *list; + + for (list = g_list_first(source); list; list = g_list_next(list)) { + struct adapter *adapter = list->data; + DBusMessageIter iter; + const char *str; + + if (g_dbus_proxy_get_property(adapter->proxy, + "Address", &iter) == FALSE) + continue; + + dbus_message_iter_get_basic(&iter, &str); + + if (!strcasecmp(str, address)) + return adapter; + } + + return NULL; +} + +static GDBusProxy *find_proxy_by_address(GList *source, const char *address) +{ + GList *list; + + for (list = g_list_first(source); list; list = g_list_next(list)) { + GDBusProxy *proxy = list->data; + DBusMessageIter iter; + const char *str; + + if (g_dbus_proxy_get_property(proxy, "Address", &iter) == FALSE) + continue; + + dbus_message_iter_get_basic(&iter, &str); + + if (!strcasecmp(str, address)) + return proxy; + } + + return NULL; +} + +static gboolean check_default_ctrl(void) +{ + if (!default_ctrl) { + bt_shell_printf("No default controller available\n"); + return FALSE; + } + + return TRUE; +} + +static gboolean parse_argument(int argc, char *argv[], const char **arg_table, + const char *msg, dbus_bool_t *value, + const char **option) +{ + const char **opt; + + if (!strcmp(argv[1], "help")) { + for (opt = arg_table; opt && *opt; opt++) + bt_shell_printf("%s\n", *opt); + bt_shell_noninteractive_quit(EXIT_SUCCESS); + return FALSE; + } + + if (!strcmp(argv[1], "on") || !strcmp(argv[1], "yes")) { + *value = TRUE; + if (option) + *option = ""; + return TRUE; + } + + if (!strcmp(argv[1], "off") || !strcmp(argv[1], "no")) { + *value = FALSE; + return TRUE; + } + + for (opt = arg_table; opt && *opt; opt++) { + if (strcmp(argv[1], *opt) == 0) { + *value = TRUE; + *option = *opt; + return TRUE; + } + } + + bt_shell_printf("Invalid argument %s\n", argv[1]); + return FALSE; +} + +static void cmd_list(int argc, char *argv[]) +{ + GList *list; + + for (list = g_list_first(ctrl_list); list; list = g_list_next(list)) { + struct adapter *adapter = list->data; + print_adapter(adapter->proxy, NULL); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_show(int argc, char *argv[]) +{ + struct adapter *adapter; + DBusMessageIter iter; + const char *address; + + if (argc < 2 || !strlen(argv[1])) { + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + adapter = default_ctrl; + } else { + adapter = find_ctrl_by_address(ctrl_list, argv[1]); + if (!adapter) { + bt_shell_printf("Controller %s not available\n", + argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + if (!g_dbus_proxy_get_property(adapter->proxy, "Address", &iter)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + dbus_message_iter_get_basic(&iter, &address); + + if (g_dbus_proxy_get_property(adapter->proxy, "AddressType", &iter)) { + const char *type; + + dbus_message_iter_get_basic(&iter, &type); + + bt_shell_printf("Controller %s (%s)\n", address, type); + } else { + bt_shell_printf("Controller %s\n", address); + } + + print_property(adapter->proxy, "Name"); + print_property(adapter->proxy, "Alias"); + print_property(adapter->proxy, "Class"); + print_property(adapter->proxy, "Powered"); + print_property(adapter->proxy, "Discoverable"); + print_property(adapter->proxy, "DiscoverableTimeout"); + print_property(adapter->proxy, "Pairable"); + print_uuids(adapter->proxy); + print_property(adapter->proxy, "Modalias"); + print_property(adapter->proxy, "Discovering"); + + if (adapter->ad_proxy) { + bt_shell_printf("Advertising Features:\n"); + print_property(adapter->ad_proxy, "ActiveInstances"); + print_property(adapter->ad_proxy, "SupportedInstances"); + print_property(adapter->ad_proxy, "SupportedIncludes"); + print_property(adapter->ad_proxy, "SupportedSecondaryChannels"); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_select(int argc, char *argv[]) +{ + struct adapter *adapter; + + adapter = find_ctrl_by_address(ctrl_list, argv[1]); + if (!adapter) { + bt_shell_printf("Controller %s not available\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (default_ctrl && default_ctrl->proxy == adapter->proxy) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + default_ctrl = adapter; + print_adapter(adapter->proxy, NULL); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_devices(int argc, char *argv[]) +{ + GList *ll; + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + for (ll = g_list_first(default_ctrl->devices); + ll; ll = g_list_next(ll)) { + GDBusProxy *proxy = ll->data; + print_device(proxy, NULL); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_paired_devices(int argc, char *argv[]) +{ + GList *ll; + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + for (ll = g_list_first(default_ctrl->devices); + ll; ll = g_list_next(ll)) { + GDBusProxy *proxy = ll->data; + DBusMessageIter iter; + dbus_bool_t paired; + + if (g_dbus_proxy_get_property(proxy, "Paired", &iter) == FALSE) + continue; + + dbus_message_iter_get_basic(&iter, &paired); + if (!paired) + continue; + + print_device(proxy, NULL); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void generic_callback(const DBusError *error, void *user_data) +{ + char *str = user_data; + + if (dbus_error_is_set(error)) { + bt_shell_printf("Failed to set %s: %s\n", str, error->name); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } else { + bt_shell_printf("Changing %s succeeded\n", str); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } +} + +static void cmd_system_alias(int argc, char *argv[]) +{ + char *name; + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + name = g_strdup(argv[1]); + + if (g_dbus_proxy_set_property_basic(default_ctrl->proxy, "Alias", + DBUS_TYPE_STRING, &name, + generic_callback, name, g_free) == TRUE) + return; + + g_free(name); +} + +static void cmd_reset_alias(int argc, char *argv[]) +{ + char *name; + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + name = g_strdup(""); + + if (g_dbus_proxy_set_property_basic(default_ctrl->proxy, "Alias", + DBUS_TYPE_STRING, &name, + generic_callback, name, g_free) == TRUE) + return; + + g_free(name); +} + +static void cmd_power(int argc, char *argv[]) +{ + dbus_bool_t powered; + char *str; + + if (!parse_argument(argc, argv, NULL, NULL, &powered, NULL)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + str = g_strdup_printf("power %s", powered == TRUE ? "on" : "off"); + + if (g_dbus_proxy_set_property_basic(default_ctrl->proxy, "Powered", + DBUS_TYPE_BOOLEAN, &powered, + generic_callback, str, g_free) == TRUE) + return; + + g_free(str); +} + +static void cmd_pairable(int argc, char *argv[]) +{ + dbus_bool_t pairable; + char *str; + + if (!parse_argument(argc, argv, NULL, NULL, &pairable, NULL)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + str = g_strdup_printf("pairable %s", pairable == TRUE ? "on" : "off"); + + if (g_dbus_proxy_set_property_basic(default_ctrl->proxy, "Pairable", + DBUS_TYPE_BOOLEAN, &pairable, + generic_callback, str, g_free) == TRUE) + return; + + g_free(str); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void cmd_discoverable(int argc, char *argv[]) +{ + dbus_bool_t discoverable; + char *str; + + if (!parse_argument(argc, argv, NULL, NULL, &discoverable, NULL)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + str = g_strdup_printf("discoverable %s", + discoverable == TRUE ? "on" : "off"); + + if (g_dbus_proxy_set_property_basic(default_ctrl->proxy, "Discoverable", + DBUS_TYPE_BOOLEAN, &discoverable, + generic_callback, str, g_free) == TRUE) + return; + + g_free(str); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void cmd_discoverable_timeout(int argc, char *argv[]) +{ + uint32_t value; + char *endptr = NULL; + char *str; + + if (argc < 2) { + DBusMessageIter iter; + + if (!g_dbus_proxy_get_property(default_ctrl->proxy, + "DiscoverableTimeout", &iter)) { + bt_shell_printf("Unable to get DiscoverableTimeout\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + dbus_message_iter_get_basic(&iter, &value); + + bt_shell_printf("DiscoverableTimeout: %d seconds\n", value); + + return; + } + + value = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0' || value > UINT32_MAX) { + bt_shell_printf("Invalid argument\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + str = g_strdup_printf("discoverable-timeout %d", value); + + if (g_dbus_proxy_set_property_basic(default_ctrl->proxy, + "DiscoverableTimeout", + DBUS_TYPE_UINT32, &value, + generic_callback, str, g_free)) + return; + + g_free(str); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void cmd_agent(int argc, char *argv[]) +{ + dbus_bool_t enable; + const char *capability; + + if (!parse_argument(argc, argv, agent_arguments, "capability", + &enable, &capability)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (enable == TRUE) { + g_free(auto_register_agent); + auto_register_agent = g_strdup(capability); + + if (agent_manager) + agent_register(dbus_conn, agent_manager, + auto_register_agent); + else + bt_shell_printf("Agent registration enabled\n"); + } else { + g_free(auto_register_agent); + auto_register_agent = NULL; + + if (agent_manager) + agent_unregister(dbus_conn, agent_manager); + else + bt_shell_printf("Agent registration disabled\n"); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_default_agent(int argc, char *argv[]) +{ + agent_default(dbus_conn, agent_manager); +} + +#define DISTANCE_VAL_INVALID 0x7FFF + +static struct set_discovery_filter_args { + char *transport; + dbus_uint16_t rssi; + dbus_int16_t pathloss; + char **uuids; + size_t uuids_len; + dbus_bool_t duplicate; + dbus_bool_t discoverable; + bool set; + bool active; +} filter = { + .rssi = DISTANCE_VAL_INVALID, + .pathloss = DISTANCE_VAL_INVALID, + .set = true, +}; + +static void start_discovery_reply(DBusMessage *message, void *user_data) +{ + dbus_bool_t enable = GPOINTER_TO_UINT(user_data); + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to %s discovery: %s\n", + enable == TRUE ? "start" : "stop", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Discovery %s\n", enable ? "started" : "stopped"); + + filter.active = enable; + /* Leave the discovery running even on noninteractive mode */ +} + +static void clear_discovery_filter(DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + dbus_message_iter_close_container(iter, &dict); +} + +static void set_discovery_filter_setup(DBusMessageIter *iter, void *user_data) +{ + struct set_discovery_filter_args *args = user_data; + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + g_dbus_dict_append_array(&dict, "UUIDs", DBUS_TYPE_STRING, + &args->uuids, + args->uuids_len); + + if (args->pathloss != DISTANCE_VAL_INVALID) + g_dbus_dict_append_entry(&dict, "Pathloss", DBUS_TYPE_UINT16, + &args->pathloss); + + if (args->rssi != DISTANCE_VAL_INVALID) + g_dbus_dict_append_entry(&dict, "RSSI", DBUS_TYPE_INT16, + &args->rssi); + + if (args->transport != NULL) + g_dbus_dict_append_entry(&dict, "Transport", DBUS_TYPE_STRING, + &args->transport); + + if (args->duplicate) + g_dbus_dict_append_entry(&dict, "DuplicateData", + DBUS_TYPE_BOOLEAN, + &args->duplicate); + + if (args->discoverable) + g_dbus_dict_append_entry(&dict, "Discoverable", + DBUS_TYPE_BOOLEAN, + &args->discoverable); + + dbus_message_iter_close_container(iter, &dict); +} + + +static void set_discovery_filter_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("SetDiscoveryFilter failed: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + filter.set = true; + + bt_shell_printf("SetDiscoveryFilter success\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void set_discovery_filter(bool cleared) +{ + GDBusSetupFunction func; + + if (check_default_ctrl() == FALSE || filter.set) + return; + + func = cleared ? clear_discovery_filter : set_discovery_filter_setup; + + if (g_dbus_proxy_method_call(default_ctrl->proxy, "SetDiscoveryFilter", + func, set_discovery_filter_reply, + &filter, NULL) == FALSE) { + bt_shell_printf("Failed to set discovery filter\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + filter.set = true; +} + +static void cmd_scan(int argc, char *argv[]) +{ + dbus_bool_t enable; + const char *method; + + if (!parse_argument(argc, argv, NULL, NULL, &enable, NULL)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (enable == TRUE) { + set_discovery_filter(false); + method = "StartDiscovery"; + } else + method = "StopDiscovery"; + + if (g_dbus_proxy_method_call(default_ctrl->proxy, method, + NULL, start_discovery_reply, + GUINT_TO_POINTER(enable), NULL) == FALSE) { + bt_shell_printf("Failed to %s discovery\n", + enable == TRUE ? "start" : "stop"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_scan_filter_uuids(int argc, char *argv[]) +{ + if (argc < 2 || !strlen(argv[1])) { + char **uuid; + + for (uuid = filter.uuids; uuid && *uuid; uuid++) + print_uuid(*uuid); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + g_strfreev(filter.uuids); + filter.uuids = NULL; + filter.uuids_len = 0; + + if (!strcmp(argv[1], "all")) + goto commit; + + filter.uuids = g_strdupv(&argv[1]); + if (!filter.uuids) { + bt_shell_printf("Failed to parse input\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + filter.uuids_len = g_strv_length(filter.uuids); + +commit: + filter.set = false; + + if (filter.active) + set_discovery_filter(false); +} + +static void cmd_scan_filter_rssi(int argc, char *argv[]) +{ + if (argc < 2 || !strlen(argv[1])) { + if (filter.rssi != DISTANCE_VAL_INVALID) + bt_shell_printf("RSSI: %d\n", filter.rssi); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + filter.pathloss = DISTANCE_VAL_INVALID; + filter.rssi = atoi(argv[1]); + + filter.set = false; + + if (filter.active) + set_discovery_filter(false); +} + +static void cmd_scan_filter_pathloss(int argc, char *argv[]) +{ + if (argc < 2 || !strlen(argv[1])) { + if (filter.pathloss != DISTANCE_VAL_INVALID) + bt_shell_printf("Pathloss: %d\n", + filter.pathloss); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + filter.rssi = DISTANCE_VAL_INVALID; + filter.pathloss = atoi(argv[1]); + + filter.set = false; + + if (filter.active) + set_discovery_filter(false); +} + +static void cmd_scan_filter_transport(int argc, char *argv[]) +{ + if (argc < 2 || !strlen(argv[1])) { + if (filter.transport) + bt_shell_printf("Transport: %s\n", + filter.transport); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + g_free(filter.transport); + filter.transport = g_strdup(argv[1]); + + filter.set = false; + + if (filter.active) + set_discovery_filter(false); +} + +static void cmd_scan_filter_duplicate_data(int argc, char *argv[]) +{ + if (argc < 2 || !strlen(argv[1])) { + bt_shell_printf("DuplicateData: %s\n", + filter.duplicate ? "on" : "off"); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (!strcmp(argv[1], "on")) + filter.duplicate = true; + else if (!strcmp(argv[1], "off")) + filter.duplicate = false; + else { + bt_shell_printf("Invalid option: %s\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + filter.set = false; + + if (filter.active) + set_discovery_filter(false); +} + +static void cmd_scan_filter_discoverable(int argc, char *argv[]) +{ + if (argc < 2 || !strlen(argv[1])) { + bt_shell_printf("Discoverable: %s\n", + filter.discoverable ? "on" : "off"); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + if (!strcmp(argv[1], "on")) + filter.discoverable = true; + else if (!strcmp(argv[1], "off")) + filter.discoverable = false; + else { + bt_shell_printf("Invalid option: %s\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + filter.set = false; + + if (filter.active) + set_discovery_filter(false); +} + +static void filter_clear_uuids(void) +{ + g_strfreev(filter.uuids); + filter.uuids = NULL; + filter.uuids_len = 0; +} + +static void filter_clear_rssi(void) +{ + filter.rssi = DISTANCE_VAL_INVALID; +} + +static void filter_clear_pathloss(void) +{ + filter.pathloss = DISTANCE_VAL_INVALID; +} + +static void filter_clear_transport(void) +{ + g_free(filter.transport); + filter.transport = NULL; +} + +static void filter_clear_duplicate(void) +{ + filter.duplicate = false; +} + +static void filter_clear_discoverable(void) +{ + filter.discoverable = false; +} + +struct clear_entry { + const char *name; + void (*clear) (void); +}; + +static const struct clear_entry filter_clear[] = { + { "uuids", filter_clear_uuids }, + { "rssi", filter_clear_rssi }, + { "pathloss", filter_clear_pathloss }, + { "transport", filter_clear_transport }, + { "duplicate-data", filter_clear_duplicate }, + { "discoverable", filter_clear_discoverable }, + {} +}; + +static char *filter_clear_generator(const char *text, int state) +{ + static int index, len; + const char *arg; + + if (!state) { + index = 0; + len = strlen(text); + } + + while ((arg = filter_clear[index].name)) { + index++; + + if (!strncmp(arg, text, len)) + return strdup(arg); + } + + return NULL; +} + +static gboolean data_clear(const struct clear_entry *entry_table, + const char *name) +{ + const struct clear_entry *entry; + bool all = false; + + if (!name || !strlen(name) || !strcmp("all", name)) + all = true; + + for (entry = entry_table; entry && entry->name; entry++) { + if (all || !strcmp(entry->name, name)) { + entry->clear(); + if (!all) + goto done; + } + } + + if (!all) { + bt_shell_printf("Invalid argument %s\n", name); + return FALSE; + } + +done: + return TRUE; +} + +static void cmd_scan_filter_clear(int argc, char *argv[]) +{ + bool all = false; + + if (argc < 2 || !strlen(argv[1])) + all = true; + + if (!data_clear(filter_clear, all ? "all" : argv[1])) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + filter.set = false; + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + set_discovery_filter(all); +} + +static struct GDBusProxy *find_device(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + if (argc < 2 || !strlen(argv[1])) { + if (default_dev) + return default_dev; + bt_shell_printf("Missing device address argument\n"); + return NULL; + } + + if (check_default_ctrl() == FALSE) + return NULL; + + proxy = find_proxy_by_address(default_ctrl->devices, argv[1]); + if (!proxy) { + bt_shell_printf("Device %s not available\n", argv[1]); + return NULL; + } + + return proxy; +} + +static void cmd_info(int argc, char *argv[]) +{ + GDBusProxy *proxy; + DBusMessageIter iter; + const char *address; + + proxy = find_device(argc, argv); + if (!proxy) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (g_dbus_proxy_get_property(proxy, "Address", &iter) == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + dbus_message_iter_get_basic(&iter, &address); + + if (g_dbus_proxy_get_property(proxy, "AddressType", &iter) == TRUE) { + const char *type; + + dbus_message_iter_get_basic(&iter, &type); + + bt_shell_printf("Device %s (%s)\n", address, type); + } else { + bt_shell_printf("Device %s\n", address); + } + + print_property(proxy, "Name"); + print_property(proxy, "Alias"); + print_property(proxy, "Class"); + print_property(proxy, "Appearance"); + print_property(proxy, "Icon"); + print_property(proxy, "Paired"); + print_property(proxy, "Trusted"); + print_property(proxy, "Blocked"); + print_property(proxy, "Connected"); + print_property(proxy, "LegacyPairing"); + print_uuids(proxy); + print_property(proxy, "Modalias"); + print_property(proxy, "ManufacturerData"); + print_property(proxy, "ServiceData"); + print_property(proxy, "RSSI"); + print_property(proxy, "TxPower"); + print_property(proxy, "AdvertisingFlags"); + print_property(proxy, "AdvertisingData"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void pair_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to pair: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Pairing successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static const char *proxy_address(GDBusProxy *proxy) +{ + DBusMessageIter iter; + const char *addr; + + if (!g_dbus_proxy_get_property(proxy, "Address", &iter)) + return NULL; + + dbus_message_iter_get_basic(&iter, &addr); + + return addr; +} + +static void cmd_pair(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + proxy = find_device(argc, argv); + if (!proxy) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (g_dbus_proxy_method_call(proxy, "Pair", NULL, pair_reply, + NULL, NULL) == FALSE) { + bt_shell_printf("Failed to pair\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to pair with %s\n", proxy_address(proxy)); +} + +static void cmd_trust(int argc, char *argv[]) +{ + GDBusProxy *proxy; + dbus_bool_t trusted; + char *str; + + proxy = find_device(argc, argv); + if (!proxy) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + trusted = TRUE; + + str = g_strdup_printf("%s trust", proxy_address(proxy)); + + if (g_dbus_proxy_set_property_basic(proxy, "Trusted", + DBUS_TYPE_BOOLEAN, &trusted, + generic_callback, str, g_free) == TRUE) + return; + + g_free(str); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void cmd_untrust(int argc, char *argv[]) +{ + GDBusProxy *proxy; + dbus_bool_t trusted; + char *str; + + proxy = find_device(argc, argv); + if (!proxy) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + trusted = FALSE; + + str = g_strdup_printf("%s untrust", proxy_address(proxy)); + + if (g_dbus_proxy_set_property_basic(proxy, "Trusted", + DBUS_TYPE_BOOLEAN, &trusted, + generic_callback, str, g_free) == TRUE) + return; + + g_free(str); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void cmd_block(int argc, char *argv[]) +{ + GDBusProxy *proxy; + dbus_bool_t blocked; + char *str; + + proxy = find_device(argc, argv); + if (!proxy) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + blocked = TRUE; + + str = g_strdup_printf("%s block", proxy_address(proxy)); + + if (g_dbus_proxy_set_property_basic(proxy, "Blocked", + DBUS_TYPE_BOOLEAN, &blocked, + generic_callback, str, g_free) == TRUE) + return; + + g_free(str); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void cmd_unblock(int argc, char *argv[]) +{ + GDBusProxy *proxy; + dbus_bool_t blocked; + char *str; + + proxy = find_device(argc, argv); + if (!proxy) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + blocked = FALSE; + + str = g_strdup_printf("%s unblock", proxy_address(proxy)); + + if (g_dbus_proxy_set_property_basic(proxy, "Blocked", + DBUS_TYPE_BOOLEAN, &blocked, + generic_callback, str, g_free) == TRUE) + return; + + g_free(str); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void remove_device_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to remove device: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Device has been removed\n"); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void remove_device_setup(DBusMessageIter *iter, void *user_data) +{ + const char *path = user_data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); +} + +static void remove_device(GDBusProxy *proxy) +{ + char *path; + + path = g_strdup(g_dbus_proxy_get_path(proxy)); + + if (!default_ctrl) + return; + + if (g_dbus_proxy_method_call(default_ctrl->proxy, "RemoveDevice", + remove_device_setup, + remove_device_reply, + path, g_free) == FALSE) { + bt_shell_printf("Failed to remove device\n"); + g_free(path); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_remove(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (strcmp(argv[1], "*") == 0) { + GList *list; + + for (list = default_ctrl->devices; list; + list = g_list_next(list)) { + GDBusProxy *proxy = list->data; + + remove_device(proxy); + } + return; + } + + proxy = find_proxy_by_address(default_ctrl->devices, argv[1]); + if (!proxy) { + bt_shell_printf("Device %s not available\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + remove_device(proxy); +} + +static void connect_reply(DBusMessage *message, void *user_data) +{ + GDBusProxy *proxy = user_data; + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to connect: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Connection successful\n"); + + set_default_device(proxy, NULL); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_connect(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + proxy = find_proxy_by_address(default_ctrl->devices, argv[1]); + if (!proxy) { + bt_shell_printf("Device %s not available\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (g_dbus_proxy_method_call(proxy, "Connect", NULL, connect_reply, + proxy, NULL) == FALSE) { + bt_shell_printf("Failed to connect\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to connect to %s\n", argv[1]); +} + +static void disconn_reply(DBusMessage *message, void *user_data) +{ + GDBusProxy *proxy = user_data; + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to disconnect: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Successful disconnected\n"); + + if (proxy == default_dev) + set_default_device(NULL, NULL); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_disconn(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + proxy = find_device(argc, argv); + if (!proxy) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (g_dbus_proxy_method_call(proxy, "Disconnect", NULL, disconn_reply, + proxy, NULL) == FALSE) { + bt_shell_printf("Failed to disconnect\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to disconnect from %s\n", + proxy_address(proxy)); +} + +static void cmd_list_attributes(int argc, char *argv[]) +{ + GDBusProxy *proxy; + const char *path; + + if (argc > 1 && !strcmp(argv[1], "local")) { + path = argv[1]; + goto done; + } + + proxy = find_device(argc, argv); + if (!proxy) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + path = g_dbus_proxy_get_path(proxy); + +done: + gatt_list_attributes(path); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_set_alias(int argc, char *argv[]) +{ + char *name; + + if (!default_dev) { + bt_shell_printf("No device connected\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + name = g_strdup(argv[1]); + + if (g_dbus_proxy_set_property_basic(default_dev, "Alias", + DBUS_TYPE_STRING, &name, + generic_callback, name, g_free) == TRUE) + return; + + g_free(name); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void cmd_select_attribute(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + if (!default_dev) { + bt_shell_printf("No device connected\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + proxy = gatt_select_attribute(default_attr, argv[1]); + if (proxy) { + set_default_attribute(proxy); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static struct GDBusProxy *find_attribute(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + if (argc < 2 || !strlen(argv[1])) { + if (default_attr) + return default_attr; + bt_shell_printf("Missing attribute argument\n"); + return NULL; + } + + proxy = gatt_select_attribute(default_attr, argv[1]); + if (!proxy) { + bt_shell_printf("Attribute %s not available\n", argv[1]); + return NULL; + } + + return proxy; +} + +static void cmd_attribute_info(int argc, char *argv[]) +{ + GDBusProxy *proxy; + DBusMessageIter iter; + const char *iface, *uuid, *text; + + proxy = find_attribute(argc, argv); + if (!proxy) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (g_dbus_proxy_get_property(proxy, "UUID", &iter) == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + dbus_message_iter_get_basic(&iter, &uuid); + + text = bt_uuidstr_to_str(uuid); + if (!text) + text = g_dbus_proxy_get_path(proxy); + + iface = g_dbus_proxy_get_interface(proxy); + if (!strcmp(iface, "org.bluez.GattService1")) { + bt_shell_printf("Service - %s\n", text); + + print_property(proxy, "UUID"); + print_property(proxy, "Primary"); + print_property(proxy, "Characteristics"); + print_property(proxy, "Includes"); + } else if (!strcmp(iface, "org.bluez.GattCharacteristic1")) { + bt_shell_printf("Characteristic - %s\n", text); + + print_property(proxy, "UUID"); + print_property(proxy, "Service"); + print_property(proxy, "Value"); + print_property(proxy, "Notifying"); + print_property(proxy, "Flags"); + print_property(proxy, "Descriptors"); + } else if (!strcmp(iface, "org.bluez.GattDescriptor1")) { + bt_shell_printf("Descriptor - %s\n", text); + + print_property(proxy, "UUID"); + print_property(proxy, "Characteristic"); + print_property(proxy, "Value"); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_read(int argc, char *argv[]) +{ + if (!default_attr) { + bt_shell_printf("No attribute selected\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + gatt_read_attribute(default_attr, argc, argv); +} + +static void cmd_write(int argc, char *argv[]) +{ + if (!default_attr) { + bt_shell_printf("No attribute selected\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + gatt_write_attribute(default_attr, argc, argv); +} + +static void cmd_acquire_write(int argc, char *argv[]) +{ + if (!default_attr) { + bt_shell_printf("No attribute selected\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + gatt_acquire_write(default_attr, argv[1]); +} + +static void cmd_release_write(int argc, char *argv[]) +{ + if (!default_attr) { + bt_shell_printf("No attribute selected\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + gatt_release_write(default_attr, argv[1]); +} + +static void cmd_acquire_notify(int argc, char *argv[]) +{ + if (!default_attr) { + bt_shell_printf("No attribute selected\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + gatt_acquire_notify(default_attr, argv[1]); +} + +static void cmd_release_notify(int argc, char *argv[]) +{ + if (!default_attr) { + bt_shell_printf("No attribute selected\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + gatt_release_notify(default_attr, argv[1]); +} + +static void cmd_notify(int argc, char *argv[]) +{ + dbus_bool_t enable; + + if (!parse_argument(argc, argv, NULL, NULL, &enable, NULL)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (!default_attr) { + bt_shell_printf("No attribute selected\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + gatt_notify_attribute(default_attr, enable ? true : false); +} + +static void cmd_clone(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + proxy = default_attr ? default_attr : default_dev; + if (!proxy) { + bt_shell_printf("Not connected\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + gatt_clone_attribute(proxy, argc, argv); +} + +static void cmd_register_app(int argc, char *argv[]) +{ + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + gatt_register_app(dbus_conn, default_ctrl->proxy, argc, argv); +} + +static void cmd_unregister_app(int argc, char *argv[]) +{ + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + gatt_unregister_app(dbus_conn, default_ctrl->proxy); +} + +static void cmd_register_service(int argc, char *argv[]) +{ + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + gatt_register_service(dbus_conn, default_ctrl->proxy, argc, argv); +} + +static void cmd_register_includes(int argc, char *argv[]) +{ + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + gatt_register_include(dbus_conn, default_ctrl->proxy, argc, argv); +} + +static void cmd_unregister_includes(int argc, char *argv[]) +{ + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + gatt_unregister_include(dbus_conn, default_ctrl->proxy, argc, argv); +} + +static void cmd_unregister_service(int argc, char *argv[]) +{ + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + gatt_unregister_service(dbus_conn, default_ctrl->proxy, argc, argv); +} + +static void cmd_register_characteristic(int argc, char *argv[]) +{ + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + gatt_register_chrc(dbus_conn, default_ctrl->proxy, argc, argv); +} + +static void cmd_unregister_characteristic(int argc, char *argv[]) +{ + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + gatt_unregister_chrc(dbus_conn, default_ctrl->proxy, argc, argv); +} + +static void cmd_register_descriptor(int argc, char *argv[]) +{ + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + gatt_register_desc(dbus_conn, default_ctrl->proxy, argc, argv); +} + +static void cmd_unregister_descriptor(int argc, char *argv[]) +{ + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + gatt_unregister_desc(dbus_conn, default_ctrl->proxy, argc, argv); +} + +static char *generic_generator(const char *text, int state, + GList *source, const char *property) +{ + static int index, len; + GList *list; + + if (!state) { + index = 0; + len = strlen(text); + } + + for (list = g_list_nth(source, index); list; + list = g_list_next(list)) { + GDBusProxy *proxy = list->data; + DBusMessageIter iter; + const char *str; + + index++; + + if (g_dbus_proxy_get_property(proxy, property, &iter) == FALSE) + continue; + + dbus_message_iter_get_basic(&iter, &str); + + if (!strncasecmp(str, text, len)) + return strdup(str); + } + + return NULL; +} + +static char *ctrl_generator(const char *text, int state) +{ + static int index = 0; + static int len = 0; + GList *list; + + if (!state) { + index = 0; + len = strlen(text); + } + + for (list = g_list_nth(ctrl_list, index); list; + list = g_list_next(list)) { + struct adapter *adapter = list->data; + DBusMessageIter iter; + const char *str; + + index++; + + if (g_dbus_proxy_get_property(adapter->proxy, + "Address", &iter) == FALSE) + continue; + + dbus_message_iter_get_basic(&iter, &str); + + if (!strncasecmp(str, text, len)) + return strdup(str); + } + + return NULL; +} + +static char *dev_generator(const char *text, int state) +{ + return generic_generator(text, state, + default_ctrl ? default_ctrl->devices : NULL, "Address"); +} + +static char *attribute_generator(const char *text, int state) +{ + return gatt_attribute_generator(text, state); +} + +static char *argument_generator(const char *text, int state, + const char *args_list[]) +{ + static int index, len; + const char *arg; + + if (!state) { + index = 0; + len = strlen(text); + } + + while ((arg = args_list[index])) { + index++; + + if (!strncmp(arg, text, len)) + return strdup(arg); + } + + return NULL; +} + +static char *capability_generator(const char *text, int state) +{ + return argument_generator(text, state, agent_arguments); +} + +static void cmd_advertise(int argc, char *argv[]) +{ + dbus_bool_t enable; + const char *type; + + if (!parse_argument(argc, argv, ad_arguments, "type", + &enable, &type)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (!default_ctrl || !default_ctrl->ad_proxy) { + bt_shell_printf("LEAdvertisingManager not found\n"); + bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (enable == TRUE) + ad_register(dbus_conn, default_ctrl->ad_proxy, type); + else + ad_unregister(dbus_conn, default_ctrl->ad_proxy); +} + +static char *ad_generator(const char *text, int state) +{ + return argument_generator(text, state, ad_arguments); +} + +static void cmd_advertise_uuids(int argc, char *argv[]) +{ + ad_advertise_uuids(dbus_conn, argc, argv); +} + +static void cmd_advertise_service(int argc, char *argv[]) +{ + ad_advertise_service(dbus_conn, argc, argv); +} + +static void cmd_advertise_manufacturer(int argc, char *argv[]) +{ + ad_advertise_manufacturer(dbus_conn, argc, argv); +} + +static void cmd_advertise_data(int argc, char *argv[]) +{ + ad_advertise_data(dbus_conn, argc, argv); +} + +static void cmd_advertise_discoverable(int argc, char *argv[]) +{ + dbus_bool_t discoverable; + + if (argc < 2) { + ad_advertise_discoverable(dbus_conn, NULL); + return; + } + + if (!parse_argument(argc, argv, NULL, NULL, &discoverable, NULL)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + ad_advertise_discoverable(dbus_conn, &discoverable); +} + +static void cmd_advertise_discoverable_timeout(int argc, char *argv[]) +{ + long int value; + char *endptr = NULL; + + if (argc < 2) { + ad_advertise_discoverable_timeout(dbus_conn, NULL); + return; + } + + value = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0' || value > UINT16_MAX) { + bt_shell_printf("Invalid argument\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ad_advertise_discoverable_timeout(dbus_conn, &value); +} + +static void cmd_advertise_tx_power(int argc, char *argv[]) +{ + dbus_bool_t powered; + + if (argc < 2) { + ad_advertise_tx_power(dbus_conn, NULL); + return; + } + + if (!parse_argument(argc, argv, NULL, NULL, &powered, NULL)) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + ad_advertise_tx_power(dbus_conn, &powered); +} + +static void cmd_advertise_name(int argc, char *argv[]) +{ + if (argc < 2) { + ad_advertise_local_name(dbus_conn, NULL); + return; + } + + if (strcmp(argv[1], "on") == 0 || strcmp(argv[1], "yes") == 0) { + ad_advertise_name(dbus_conn, true); + return; + } + + if (strcmp(argv[1], "off") == 0 || strcmp(argv[1], "no") == 0) { + ad_advertise_name(dbus_conn, false); + return; + } + + ad_advertise_local_name(dbus_conn, argv[1]); +} + +static void cmd_advertise_appearance(int argc, char *argv[]) +{ + long int value; + char *endptr = NULL; + + if (argc < 2) { + ad_advertise_local_appearance(dbus_conn, NULL); + return; + } + + if (strcmp(argv[1], "on") == 0 || strcmp(argv[1], "yes") == 0) { + ad_advertise_appearance(dbus_conn, true); + return; + } + + if (strcmp(argv[1], "off") == 0 || strcmp(argv[1], "no") == 0) { + ad_advertise_appearance(dbus_conn, false); + return; + } + + value = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0' || value > UINT16_MAX) { + bt_shell_printf("Invalid argument\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ad_advertise_local_appearance(dbus_conn, &value); +} + +static void cmd_advertise_duration(int argc, char *argv[]) +{ + long int value; + char *endptr = NULL; + + if (argc < 2) { + ad_advertise_duration(dbus_conn, NULL); + return; + } + + value = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0' || value > UINT16_MAX) { + bt_shell_printf("Invalid argument\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ad_advertise_duration(dbus_conn, &value); +} + +static void cmd_advertise_timeout(int argc, char *argv[]) +{ + long int value; + char *endptr = NULL; + + if (argc < 2) { + ad_advertise_timeout(dbus_conn, NULL); + return; + } + + value = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0' || value > UINT16_MAX) { + bt_shell_printf("Invalid argument\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ad_advertise_timeout(dbus_conn, &value); +} + +static void cmd_advertise_secondary(int argc, char *argv[]) +{ + if (argc < 2) { + ad_advertise_secondary(dbus_conn, NULL); + return; + } + + ad_advertise_secondary(dbus_conn, argv[1]); +} + +static void ad_clear_uuids(void) +{ + ad_disable_uuids(dbus_conn); +} + +static void ad_clear_service(void) +{ + ad_disable_service(dbus_conn); +} + +static void ad_clear_manufacturer(void) +{ + ad_disable_manufacturer(dbus_conn); +} + +static void ad_clear_data(void) +{ + ad_disable_data(dbus_conn); +} + +static void ad_clear_tx_power(void) +{ + dbus_bool_t powered = false; + + ad_advertise_tx_power(dbus_conn, &powered); +} + +static void ad_clear_name(void) +{ + ad_advertise_name(dbus_conn, false); +} + +static void ad_clear_appearance(void) +{ + ad_advertise_appearance(dbus_conn, false); +} + +static void ad_clear_duration(void) +{ + long int value = 0; + + ad_advertise_duration(dbus_conn, &value); +} + +static void ad_clear_timeout(void) +{ + long int value = 0; + + ad_advertise_timeout(dbus_conn, &value); +} + +static void ad_clear_secondary(void) +{ + const char *value = ""; + + ad_advertise_secondary(dbus_conn, value); +} + +static const struct clear_entry ad_clear[] = { + { "uuids", ad_clear_uuids }, + { "service", ad_clear_service }, + { "manufacturer", ad_clear_manufacturer }, + { "data", ad_clear_data }, + { "tx-power", ad_clear_tx_power }, + { "name", ad_clear_name }, + { "appearance", ad_clear_appearance }, + { "duration", ad_clear_duration }, + { "timeout", ad_clear_timeout }, + { "secondary", ad_clear_secondary }, + {} +}; + +static void cmd_ad_clear(int argc, char *argv[]) +{ + bool all = false; + + if (argc < 2 || !strlen(argv[1])) + all = true; + + if(!data_clear(ad_clear, all ? "all" : argv[1])) + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static const struct bt_shell_menu advertise_menu = { + .name = "advertise", + .desc = "Advertise Options Submenu", + .entries = { + { "uuids", "[uuid1 uuid2 ...]", cmd_advertise_uuids, + "Set/Get advertise uuids" }, + { "service", "[uuid] [data=xx xx ...]", cmd_advertise_service, + "Set/Get advertise service data" }, + { "manufacturer", "[id] [data=xx xx ...]", + cmd_advertise_manufacturer, + "Set/Get advertise manufacturer data" }, + { "data", "[type] [data=xx xx ...]", cmd_advertise_data, + "Set/Get advertise data" }, + { "discoverable", "[on/off]", cmd_advertise_discoverable, + "Set/Get advertise discoverable" }, + { "discoverable-timeout", "[seconds]", + cmd_advertise_discoverable_timeout, + "Set/Get advertise discoverable timeout" }, + { "tx-power", "[on/off]", cmd_advertise_tx_power, + "Show/Enable/Disable TX power to be advertised", + NULL }, + { "name", "[on/off/name]", cmd_advertise_name, + "Configure local name to be advertised" }, + { "appearance", "[on/off/value]", cmd_advertise_appearance, + "Configure custom appearance to be advertised" }, + { "duration", "[seconds]", cmd_advertise_duration, + "Set/Get advertise duration" }, + { "timeout", "[seconds]", cmd_advertise_timeout, + "Set/Get advertise timeout" }, + { "secondary", "[1M/2M/Coded]", cmd_advertise_secondary, + "Set/Get advertise secondary channel" }, + { "clear", "[uuids/service/manufacturer/config-name...]", cmd_ad_clear, + "Clear advertise config" }, + { } }, +}; + +static const struct bt_shell_menu scan_menu = { + .name = "scan", + .desc = "Scan Options Submenu", + .entries = { + { "uuids", "[all/uuid1 uuid2 ...]", cmd_scan_filter_uuids, + "Set/Get UUIDs filter" }, + { "rssi", "[rssi]", cmd_scan_filter_rssi, + "Set/Get RSSI filter, and clears pathloss" }, + { "pathloss", "[pathloss]", cmd_scan_filter_pathloss, + "Set/Get Pathloss filter, and clears RSSI" }, + { "transport", "[transport]", cmd_scan_filter_transport, + "Set/Get transport filter" }, + { "duplicate-data", "[on/off]", cmd_scan_filter_duplicate_data, + "Set/Get duplicate data filter", + NULL }, + { "discoverable", "[on/off]", cmd_scan_filter_discoverable, + "Set/Get discoverable filter", + NULL }, + { "clear", + "[uuids/rssi/pathloss/transport/duplicate-data/discoverable]", + cmd_scan_filter_clear, + "Clears discovery filter.", + filter_clear_generator }, + { } }, +}; + +static const struct bt_shell_menu gatt_menu = { + .name = "gatt", + .desc = "Generic Attribute Submenu", + .entries = { + { "list-attributes", "[dev/local]", cmd_list_attributes, + "List attributes", dev_generator }, + { "select-attribute", "", cmd_select_attribute, + "Select attribute", attribute_generator }, + { "attribute-info", "[attribute/UUID]", cmd_attribute_info, + "Select attribute", attribute_generator }, + { "read", "[offset]", cmd_read, "Read attribute value" }, + { "write", " [offset] [type]", cmd_write, + "Write attribute value" }, + { "acquire-write", NULL, cmd_acquire_write, + "Acquire Write file descriptor" }, + { "release-write", NULL, cmd_release_write, + "Release Write file descriptor" }, + { "acquire-notify", NULL, cmd_acquire_notify, + "Acquire Notify file descriptor" }, + { "release-notify", NULL, cmd_release_notify, + "Release Notify file descriptor" }, + { "notify", "", cmd_notify, "Notify attribute value", + NULL }, + { "clone", "[dev/attribute/UUID]", cmd_clone, + "Clone a device or attribute" }, + { "register-application", "[UUID ...]", cmd_register_app, + "Register profile to connect" }, + { "unregister-application", NULL, cmd_unregister_app, + "Unregister profile" }, + { "register-service", " [handle]", cmd_register_service, + "Register application service." }, + { "unregister-service", "", cmd_unregister_service, + "Unregister application service" }, + { "register-includes", " [handle]", cmd_register_includes, + "Register as Included service in." }, + { "unregister-includes", "", + cmd_unregister_includes, + "Unregister Included service." }, + { "register-characteristic", + " [handle]", + cmd_register_characteristic, + "Register application characteristic" }, + { "unregister-characteristic", "", + cmd_unregister_characteristic, + "Unregister application characteristic" }, + { "register-descriptor", " [handle]", + cmd_register_descriptor, + "Register application descriptor" }, + { "unregister-descriptor", "", + cmd_unregister_descriptor, + "Unregister application descriptor" }, + { } }, +}; + +static const struct bt_shell_menu main_menu = { + .name = "main", + .entries = { + { "list", NULL, cmd_list, "List available controllers" }, + { "show", "[ctrl]", cmd_show, "Controller information", + ctrl_generator }, + { "select", "", cmd_select, "Select default controller", + ctrl_generator }, + { "devices", NULL, cmd_devices, "List available devices" }, + { "paired-devices", NULL, cmd_paired_devices, + "List paired devices"}, + { "system-alias", "", cmd_system_alias, + "Set controller alias" }, + { "reset-alias", NULL, cmd_reset_alias, + "Reset controller alias" }, + { "power", "", cmd_power, "Set controller power", + NULL }, + { "pairable", "", cmd_pairable, + "Set controller pairable mode", + NULL }, + { "discoverable", "", cmd_discoverable, + "Set controller discoverable mode", + NULL }, + { "discoverable-timeout", "[value]", cmd_discoverable_timeout, + "Set discoverable timeout", NULL }, + { "agent", "", cmd_agent, + "Enable/disable agent with given capability", + capability_generator}, + { "default-agent",NULL, cmd_default_agent, + "Set agent as the default one" }, + { "advertise", "", cmd_advertise, + "Enable/disable advertising with given type", + ad_generator}, + { "set-alias", "", cmd_set_alias, "Set device alias" }, + { "scan", "", cmd_scan, "Scan for devices", NULL }, + { "info", "[dev]", cmd_info, "Device information", + dev_generator }, + { "pair", "[dev]", cmd_pair, "Pair with device", + dev_generator }, + { "trust", "[dev]", cmd_trust, "Trust device", + dev_generator }, + { "untrust", "[dev]", cmd_untrust, "Untrust device", + dev_generator }, + { "block", "[dev]", cmd_block, "Block device", + dev_generator }, + { "unblock", "[dev]", cmd_unblock, "Unblock device", + dev_generator }, + { "remove", "", cmd_remove, "Remove device", + dev_generator }, + { "connect", "", cmd_connect, "Connect device", + dev_generator }, + { "disconnect", "[dev]", cmd_disconn, "Disconnect device", + dev_generator }, + { } }, +}; + +static const struct option options[] = { + { "agent", required_argument, 0, 'a' }, + { 0, 0, 0, 0 } +}; + +static const char *agent_option; + +static const char **optargs[] = { + &agent_option +}; + +static const char *help[] = { + "Register agent handler: " +}; + +static const struct bt_shell_opt opt = { + .options = options, + .optno = sizeof(options) / sizeof(struct option), + .optstr = "a:", + .optarg = optargs, + .help = help, +}; + +static void client_ready(GDBusClient *client, void *user_data) +{ + setup_standard_input(); +} + +int main(int argc, char *argv[]) +{ + GDBusClient *client; + int status; + + bt_shell_init(argc, argv, &opt); + bt_shell_set_menu(&main_menu); + bt_shell_add_submenu(&advertise_menu); + bt_shell_add_submenu(&scan_menu); + bt_shell_add_submenu(&gatt_menu); + bt_shell_set_prompt(PROMPT_OFF); + + if (agent_option) + auto_register_agent = g_strdup(agent_option); + else + auto_register_agent = g_strdup(""); + + dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); + g_dbus_attach_object_manager(dbus_conn); + + bt_shell_set_env("DBUS_CONNECTION", dbus_conn); + + client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez"); + + g_dbus_client_set_connect_watch(client, connect_handler, NULL); + g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL); + g_dbus_client_set_signal_watch(client, message_handler, NULL); + + g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed, + property_changed, NULL); + + g_dbus_client_set_ready_watch(client, client_ready, NULL); + + status = bt_shell_run(); + + g_dbus_client_unref(client); + + dbus_connection_unref(dbus_conn); + + g_list_free_full(ctrl_list, proxy_leak); + + g_free(auto_register_agent); + + return status; +} diff --git a/compile b/compile new file mode 100755 index 0000000..99e5052 --- /dev/null +++ b/compile @@ -0,0 +1,348 @@ +#! /bin/sh +# Wrapper for compilers which do not understand '-c -o'. + +scriptversion=2018-03-07.03; # UTC + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Written by Tom Tromey . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +nl=' +' + +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent tools from complaining about whitespace usage. +IFS=" "" $nl" + +file_conv= + +# func_file_conv build_file lazy +# Convert a $build file to $host form and store it in $file +# Currently only supports Windows hosts. If the determined conversion +# type is listed in (the comma separated) LAZY, no conversion will +# take place. +func_file_conv () +{ + file=$1 + case $file in + / | /[!/]*) # absolute file, and not a UNC file + if test -z "$file_conv"; then + # lazily determine how to convert abs files + case `uname -s` in + MINGW*) + file_conv=mingw + ;; + CYGWIN*) + file_conv=cygwin + ;; + *) + file_conv=wine + ;; + esac + fi + case $file_conv/,$2, in + *,$file_conv,*) + ;; + mingw/*) + file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` + ;; + cygwin/*) + file=`cygpath -m "$file" || echo "$file"` + ;; + wine/*) + file=`winepath -w "$file" || echo "$file"` + ;; + esac + ;; + esac +} + +# func_cl_dashL linkdir +# Make cl look for libraries in LINKDIR +func_cl_dashL () +{ + func_file_conv "$1" + if test -z "$lib_path"; then + lib_path=$file + else + lib_path="$lib_path;$file" + fi + linker_opts="$linker_opts -LIBPATH:$file" +} + +# func_cl_dashl library +# Do a library search-path lookup for cl +func_cl_dashl () +{ + lib=$1 + found=no + save_IFS=$IFS + IFS=';' + for dir in $lib_path $LIB + do + IFS=$save_IFS + if $shared && test -f "$dir/$lib.dll.lib"; then + found=yes + lib=$dir/$lib.dll.lib + break + fi + if test -f "$dir/$lib.lib"; then + found=yes + lib=$dir/$lib.lib + break + fi + if test -f "$dir/lib$lib.a"; then + found=yes + lib=$dir/lib$lib.a + break + fi + done + IFS=$save_IFS + + if test "$found" != yes; then + lib=$lib.lib + fi +} + +# func_cl_wrapper cl arg... +# Adjust compile command to suit cl +func_cl_wrapper () +{ + # Assume a capable shell + lib_path= + shared=: + linker_opts= + for arg + do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + eat=1 + case $2 in + *.o | *.[oO][bB][jJ]) + func_file_conv "$2" + set x "$@" -Fo"$file" + shift + ;; + *) + func_file_conv "$2" + set x "$@" -Fe"$file" + shift + ;; + esac + ;; + -I) + eat=1 + func_file_conv "$2" mingw + set x "$@" -I"$file" + shift + ;; + -I*) + func_file_conv "${1#-I}" mingw + set x "$@" -I"$file" + shift + ;; + -l) + eat=1 + func_cl_dashl "$2" + set x "$@" "$lib" + shift + ;; + -l*) + func_cl_dashl "${1#-l}" + set x "$@" "$lib" + shift + ;; + -L) + eat=1 + func_cl_dashL "$2" + ;; + -L*) + func_cl_dashL "${1#-L}" + ;; + -static) + shared=false + ;; + -Wl,*) + arg=${1#-Wl,} + save_ifs="$IFS"; IFS=',' + for flag in $arg; do + IFS="$save_ifs" + linker_opts="$linker_opts $flag" + done + IFS="$save_ifs" + ;; + -Xlinker) + eat=1 + linker_opts="$linker_opts $2" + ;; + -*) + set x "$@" "$1" + shift + ;; + *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) + func_file_conv "$1" + set x "$@" -Tp"$file" + shift + ;; + *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) + func_file_conv "$1" mingw + set x "$@" "$file" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift + done + if test -n "$linker_opts"; then + linker_opts="-link$linker_opts" + fi + exec "$@" $linker_opts + exit 1 +} + +eat= + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: compile [--help] [--version] PROGRAM [ARGS] + +Wrapper for compilers which do not understand '-c -o'. +Remove '-o dest.o' from ARGS, run PROGRAM with the remaining +arguments, and rename the output as expected. + +If you are trying to build a whole package this is not the +right script to run: please start by reading the file 'INSTALL'. + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "compile $scriptversion" + exit $? + ;; + cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \ + icl | *[/\\]icl | icl.exe | *[/\\]icl.exe ) + func_cl_wrapper "$@" # Doesn't return... + ;; +esac + +ofile= +cfile= + +for arg +do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + # So we strip '-o arg' only if arg is an object. + eat=1 + case $2 in + *.o | *.obj) + ofile=$2 + ;; + *) + set x "$@" -o "$2" + shift + ;; + esac + ;; + *.c) + cfile=$1 + set x "$@" "$1" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift +done + +if test -z "$ofile" || test -z "$cfile"; then + # If no '-o' option was seen then we might have been invoked from a + # pattern rule where we don't need one. That is ok -- this is a + # normal compilation that the losing compiler can handle. If no + # '.c' file was seen then we are probably linking. That is also + # ok. + exec "$@" +fi + +# Name of file we expect compiler to create. +cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` + +# Create the lock directory. +# Note: use '[/\\:.-]' here to ensure that we don't use the same name +# that we are using for the .o file. Also, base the name on the expected +# object file name, since that is what matters with a parallel build. +lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d +while true; do + if mkdir "$lockdir" >/dev/null 2>&1; then + break + fi + sleep 1 +done +# FIXME: race condition here if user kills between mkdir and trap. +trap "rmdir '$lockdir'; exit 1" 1 2 15 + +# Run the compile. +"$@" +ret=$? + +if test -f "$cofile"; then + test "$cofile" = "$ofile" || mv "$cofile" "$ofile" +elif test -f "${cofile}bj"; then + test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" +fi + +rmdir "$lockdir" +exit $ret + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/completion/zsh/_bluetoothctl b/completion/zsh/_bluetoothctl new file mode 100644 index 0000000..bf35e50 --- /dev/null +++ b/completion/zsh/_bluetoothctl @@ -0,0 +1,98 @@ +#compdef bluetoothctl + +__bluetoothctl() { + bluetoothctl "$@" 2>/dev/null +} + +_bluezcomp_controller() { + local -a controllers + bluetoothctl list | + while read _ MAC NAME; do + controllers+="${MAC//:/\\:}:${NAME//:/\\:}" + done + _describe -t controllers 'controller' controllers +} + +_bluezcomp_device() { + local -a devices + bluetoothctl devices | + while read _ MAC NAME; do + devices+="${MAC//:/\\:}:${NAME//:/\\:}" + done + _describe -t devices 'device' devices +} + +_bluezcomp_agentcap() { + local -a agent_options=(${(f)"$(__bluetoothctl agent help)"}) + agent_options=( "${(@)agent_options:#(on|off)}" ) + compadd -a agent_options +} + +_bluetoothctl_agent() { + local -a agent_options=(${(f)"$(__bluetoothctl agent help)"}) + agent_options+=help + compadd -a agent_options +} + +_bluetoothctl_advertise() { + local -a ad_options=(${(f)"$(__bluetoothctl advertise help)"}) + ad_options+=help + compadd -a ad_options +} + +_bluetoothctl() { + local -a toggle_commands=( + "discoverable" "pairable" "power" "scan" + ) + + local -a controller_commands=( + "select" "show" + ) + + local -a device_commands=( + "block" "connect" "disconnect" "info" + "pair" "remove" "trust" "unblock" "untrust" + ) + + # Other commands may be handled by _bluetoothctl_$command + local -a all_commands=( "${(@f)$(__bluetoothctl --zsh-complete help)}" ) + + local curcontext=$curcontext state line ret=1 + _arguments -C \ + + '(info)' \ + {-h,--help}'[Show help message and exit]' \ + {-v,--version}'--version[Show version info and exit]' \ + + 'mod' \ + '(info)'{-a+,--agent=}'[Register agent handler]:agent:_bluezcomp_agentcap' \ + '(info)'{-t,--timeout}'[Timeout in seconds for non-interactive mode]' \ + '(info)'{-m,--monitor}'[Enable monitor output]' \ + + 'command' \ + '(info):command:->command' \ + '(info):: :->argument' \ + ': :_message "no more arguments"' + + if [[ $state == "command" ]]; then + _describe -t commands 'command' all_commands + elif [[ $state == "argument" ]]; then + if (( ! ${"${(@)all_commands%%:*}"[(I)${line[1]}]} )); then + _message "Unknown bluetoothctl command: $line[1]" + return 1; + fi + + curcontext="${curcontext%:*:*}:bluetoothctl-$line[1]:" + if ! _call_function ret _bluetoothctl_$line[1]; then + case $line[1] in + (${(~j.|.)toggle_commands}) + compadd on off + ;; + (${(~j.|.)device_commands}) + _bluezcomp_device + ;; + (${(~j.|.)controller_commands}) + _bluezcomp_controller + ;; + esac + fi + return ret + fi +} && _bluetoothctl diff --git a/config.guess b/config.guess new file mode 100755 index 0000000..f50dcdb --- /dev/null +++ b/config.guess @@ -0,0 +1,1480 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright 1992-2018 Free Software Foundation, Inc. + +timestamp='2018-02-24' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). +# +# Originally written by Per Bothner; maintained since 2000 by Ben Elliston. +# +# You can get the latest version of this script from: +# https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess +# +# Please send patches to . + + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Options: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright 1992-2018 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > "$dummy.c" ; + for c in cc gcc c89 c99 ; do + if ($c -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case "$UNAME_SYSTEM" in +Linux|GNU|GNU/*) + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + LIBC=gnu + + eval "$set_cc_for_build" + cat <<-EOF > "$dummy.c" + #include + #if defined(__UCLIBC__) + LIBC=uclibc + #elif defined(__dietlibc__) + LIBC=dietlibc + #else + LIBC=gnu + #endif + EOF + eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`" + + # If ldd exists, use it to detect musl libc. + if command -v ldd >/dev/null && \ + ldd --version 2>&1 | grep -q ^musl + then + LIBC=musl + fi + ;; +esac + +# Note: order is significant - the case branches are not exclusive. + +case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ + "/sbin/$sysctl" 2>/dev/null || \ + "/usr/sbin/$sysctl" 2>/dev/null || \ + echo unknown)` + case "$UNAME_MACHINE_ARCH" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + earmv*) + arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` + endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` + machine="${arch}${endian}"-unknown + ;; + *) machine="$UNAME_MACHINE_ARCH"-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently (or will in the future) and ABI. + case "$UNAME_MACHINE_ARCH" in + earm*) + os=netbsdelf + ;; + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval "$set_cc_for_build" + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ELF__ + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # Determine ABI tags. + case "$UNAME_MACHINE_ARCH" in + earm*) + expr='s/^earmv[0-9]/-eabi/;s/eb$//' + abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "$UNAME_VERSION" in + Debian*) + release='-gnu' + ;; + *) + release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "$machine-${os}${release}${abi}" + exit ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` + echo "$UNAME_MACHINE_ARCH"-unknown-bitrig"$UNAME_RELEASE" + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE" + exit ;; + *:LibertyBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` + echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE" + exit ;; + *:MidnightBSD:*:*) + echo "$UNAME_MACHINE"-unknown-midnightbsd"$UNAME_RELEASE" + exit ;; + *:ekkoBSD:*:*) + echo "$UNAME_MACHINE"-unknown-ekkobsd"$UNAME_RELEASE" + exit ;; + *:SolidBSD:*:*) + echo "$UNAME_MACHINE"-unknown-solidbsd"$UNAME_RELEASE" + exit ;; + macppc:MirBSD:*:*) + echo powerpc-unknown-mirbsd"$UNAME_RELEASE" + exit ;; + *:MirBSD:*:*) + echo "$UNAME_MACHINE"-unknown-mirbsd"$UNAME_RELEASE" + exit ;; + *:Sortix:*:*) + echo "$UNAME_MACHINE"-unknown-sortix + exit ;; + *:Redox:*:*) + echo "$UNAME_MACHINE"-unknown-redox + exit ;; + mips:OSF1:*.*) + echo mips-dec-osf1 + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE=alpha ;; + "EV4.5 (21064)") + UNAME_MACHINE=alpha ;; + "LCA4 (21066/21068)") + UNAME_MACHINE=alpha ;; + "EV5 (21164)") + UNAME_MACHINE=alphaev5 ;; + "EV5.6 (21164A)") + UNAME_MACHINE=alphaev56 ;; + "EV5.6 (21164PC)") + UNAME_MACHINE=alphapca56 ;; + "EV5.7 (21164PC)") + UNAME_MACHINE=alphapca57 ;; + "EV6 (21264)") + UNAME_MACHINE=alphaev6 ;; + "EV6.7 (21264A)") + UNAME_MACHINE=alphaev67 ;; + "EV6.8CB (21264C)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8AL (21264B)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8CX (21264D)") + UNAME_MACHINE=alphaev68 ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE=alphaev69 ;; + "EV7 (21364)") + UNAME_MACHINE=alphaev7 ;; + "EV7.9 (21364A)") + UNAME_MACHINE=alphaev79 ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo "$UNAME_MACHINE"-dec-osf"`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`" + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + exitcode=$? + trap '' 0 + exit $exitcode ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo "$UNAME_MACHINE"-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo "$UNAME_MACHINE"-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix"$UNAME_RELEASE" + exit ;; + arm*:riscos:*:*|arm*:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + s390x:SunOS:*:*) + echo "$UNAME_MACHINE"-ibm-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" + exit ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" + exit ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + echo i386-pc-auroraux"$UNAME_RELEASE" + exit ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + eval "$set_cc_for_build" + SUN_ARCH=i386 + # If there is a compiler, see if it is configured for 64-bit objects. + # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. + # This test works for both compilers. + if [ "$CC_FOR_BUILD" != no_compiler_found ]; then + if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH=x86_64 + fi + fi + echo "$SUN_ARCH"-pc-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos"`echo "$UNAME_RELEASE"|sed -e 's/-/_/'`" + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos"$UNAME_RELEASE" + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos"$UNAME_RELEASE" + ;; + sun4) + echo sparc-sun-sunos"$UNAME_RELEASE" + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos"$UNAME_RELEASE" + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint"$UNAME_RELEASE" + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint"$UNAME_RELEASE" + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint"$UNAME_RELEASE" + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten"$UNAME_RELEASE" + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten"$UNAME_RELEASE" + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix"$UNAME_RELEASE" + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix"$UNAME_RELEASE" + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix"$UNAME_RELEASE" + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o "$dummy" "$dummy.c" && + dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`"$dummy" "$dummyarg"` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos"$UNAME_RELEASE" + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ "$UNAME_PROCESSOR" = mc88100 ] || [ "$UNAME_PROCESSOR" = mc88110 ] + then + if [ "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx ] || \ + [ "$TARGET_BINARY_INTERFACE"x = x ] + then + echo m88k-dg-dgux"$UNAME_RELEASE" + else + echo m88k-dg-dguxbcs"$UNAME_RELEASE" + fi + else + echo i586-dg-dgux"$UNAME_RELEASE" + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix"`echo "$UNAME_RELEASE"|sed -e 's/-/_/g'`" + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" + fi + echo "$UNAME_MACHINE"-ibm-aix"$IBM_REV" + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[4567]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/lslpp ] ; then + IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | + awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` + else + IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" + fi + echo "$IBM_ARCH"-ibm-aix"$IBM_REV" + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd"$UNAME_RELEASE" # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` + case "$UNAME_MACHINE" in + 9000/31?) HP_ARCH=m68000 ;; + 9000/[34]??) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "$sc_cpu_version" in + 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 + 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "$sc_kernel_bits" in + 32) HP_ARCH=hppa2.0n ;; + 64) HP_ARCH=hppa2.0w ;; + '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "$HP_ARCH" = "" ]; then + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ "$HP_ARCH" = hppa2.0w ] + then + eval "$set_cc_for_build" + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | + grep -q __LP64__ + then + HP_ARCH=hppa2.0w + else + HP_ARCH=hppa64 + fi + fi + echo "$HP_ARCH"-hp-hpux"$HPUX_REV" + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux"$HPUX_REV" + exit ;; + 3050*:HI-UX:*:*) + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo "$UNAME_MACHINE"-unknown-osf1mk + else + echo "$UNAME_MACHINE"-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo "$UNAME_MACHINE"-pc-bsdi"$UNAME_RELEASE" + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi"$UNAME_RELEASE" + exit ;; + *:BSD/OS:*:*) + echo "$UNAME_MACHINE"-unknown-bsdi"$UNAME_RELEASE" + exit ;; + *:FreeBSD:*:*) + UNAME_PROCESSOR=`/usr/bin/uname -p` + case "$UNAME_PROCESSOR" in + amd64) + UNAME_PROCESSOR=x86_64 ;; + i386) + UNAME_PROCESSOR=i586 ;; + esac + echo "$UNAME_PROCESSOR"-unknown-freebsd"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" + exit ;; + i*:CYGWIN*:*) + echo "$UNAME_MACHINE"-pc-cygwin + exit ;; + *:MINGW64*:*) + echo "$UNAME_MACHINE"-pc-mingw64 + exit ;; + *:MINGW*:*) + echo "$UNAME_MACHINE"-pc-mingw32 + exit ;; + *:MSYS*:*) + echo "$UNAME_MACHINE"-pc-msys + exit ;; + i*:PW*:*) + echo "$UNAME_MACHINE"-pc-pw32 + exit ;; + *:Interix*:*) + case "$UNAME_MACHINE" in + x86) + echo i586-pc-interix"$UNAME_RELEASE" + exit ;; + authenticamd | genuineintel | EM64T) + echo x86_64-unknown-interix"$UNAME_RELEASE" + exit ;; + IA64) + echo ia64-unknown-interix"$UNAME_RELEASE" + exit ;; + esac ;; + i*:UWIN*:*) + echo "$UNAME_MACHINE"-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + *:GNU:*:*) + # the GNU system + echo "`echo "$UNAME_MACHINE"|sed -e 's,[-/].*$,,'`-unknown-$LIBC`echo "$UNAME_RELEASE"|sed -e 's,/.*$,,'`" + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo "$UNAME_MACHINE-unknown-`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"``echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`-$LIBC" + exit ;; + i*86:Minix:*:*) + echo "$UNAME_MACHINE"-pc-minix + exit ;; + aarch64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep -q ld.so.1 + if test "$?" = 0 ; then LIBC=gnulibc1 ; fi + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + arc:Linux:*:* | arceb:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + arm*:Linux:*:*) + eval "$set_cc_for_build" + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabi + else + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabihf + fi + fi + exit ;; + avr32*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + cris:Linux:*:*) + echo "$UNAME_MACHINE"-axis-linux-"$LIBC" + exit ;; + crisv32:Linux:*:*) + echo "$UNAME_MACHINE"-axis-linux-"$LIBC" + exit ;; + e2k:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + frv:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + hexagon:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + i*86:Linux:*:*) + echo "$UNAME_MACHINE"-pc-linux-"$LIBC" + exit ;; + ia64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + k1om:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + m32r*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + m68*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + mips:Linux:*:* | mips64:Linux:*:*) + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + #undef CPU + #undef ${UNAME_MACHINE} + #undef ${UNAME_MACHINE}el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=${UNAME_MACHINE}el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=${UNAME_MACHINE} + #else + CPU= + #endif + #endif +EOF + eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU'`" + test "x$CPU" != x && { echo "$CPU-unknown-linux-$LIBC"; exit; } + ;; + mips64el:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + openrisc*:Linux:*:*) + echo or1k-unknown-linux-"$LIBC" + exit ;; + or32:Linux:*:* | or1k*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + padre:Linux:*:*) + echo sparc-unknown-linux-"$LIBC" + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-"$LIBC" + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-"$LIBC" ;; + PA8*) echo hppa2.0-unknown-linux-"$LIBC" ;; + *) echo hppa-unknown-linux-"$LIBC" ;; + esac + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-"$LIBC" + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-"$LIBC" + exit ;; + ppc64le:Linux:*:*) + echo powerpc64le-unknown-linux-"$LIBC" + exit ;; + ppcle:Linux:*:*) + echo powerpcle-unknown-linux-"$LIBC" + exit ;; + riscv32:Linux:*:* | riscv64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo "$UNAME_MACHINE"-ibm-linux-"$LIBC" + exit ;; + sh64*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + sh*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + tile*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + vax:Linux:*:*) + echo "$UNAME_MACHINE"-dec-linux-"$LIBC" + exit ;; + x86_64:Linux:*:*) + if objdump -f /bin/sh | grep -q elf32-x86-64; then + echo "$UNAME_MACHINE"-pc-linux-"$LIBC"x32 + else + echo "$UNAME_MACHINE"-pc-linux-"$LIBC" + fi + exit ;; + xtensa*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo "$UNAME_MACHINE"-pc-sysv4.2uw"$UNAME_VERSION" + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo "$UNAME_MACHINE"-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo "$UNAME_MACHINE"-unknown-stop + exit ;; + i*86:atheos:*:*) + echo "$UNAME_MACHINE"-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo "$UNAME_MACHINE"-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + echo i386-unknown-lynxos"$UNAME_RELEASE" + exit ;; + i*86:*DOS:*:*) + echo "$UNAME_MACHINE"-pc-msdosdjgpp + exit ;; + i*86:*:4.*:*) + UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo "$UNAME_MACHINE"-univel-sysv"$UNAME_REL" + else + echo "$UNAME_MACHINE"-pc-sysv"$UNAME_REL" + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo "$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}{$UNAME_VERSION}" + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo "$UNAME_MACHINE"-pc-sco"$UNAME_REL" + else + echo "$UNAME_MACHINE"-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. + # Note: whatever this is, it MUST be the same as what config.sub + # prints for the "djgpp" host, or else GDB configure will decide that + # this is a cross-build. + echo i586-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv"$UNAME_RELEASE" # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv"$UNAME_RELEASE" # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + NCR*:*:4.2:* | MPRAS*:*:4.2:*) + OS_REL='.3' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos"$UNAME_RELEASE" + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos"$UNAME_RELEASE" + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos"$UNAME_RELEASE" + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + echo powerpc-unknown-lynxos"$UNAME_RELEASE" + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv"$UNAME_RELEASE" + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo "$UNAME_MACHINE"-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo "$UNAME_MACHINE"-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux"$UNAME_RELEASE" + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv"$UNAME_RELEASE" + else + echo mips-unknown-sysv"$UNAME_RELEASE" + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + echo i586-pc-haiku + exit ;; + x86_64:Haiku:*:*) + echo x86_64-unknown-haiku + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux"$UNAME_RELEASE" + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux"$UNAME_RELEASE" + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux"$UNAME_RELEASE" + exit ;; + SX-7:SUPER-UX:*:*) + echo sx7-nec-superux"$UNAME_RELEASE" + exit ;; + SX-8:SUPER-UX:*:*) + echo sx8-nec-superux"$UNAME_RELEASE" + exit ;; + SX-8R:SUPER-UX:*:*) + echo sx8r-nec-superux"$UNAME_RELEASE" + exit ;; + SX-ACE:SUPER-UX:*:*) + echo sxace-nec-superux"$UNAME_RELEASE" + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody"$UNAME_RELEASE" + exit ;; + *:Rhapsody:*:*) + echo "$UNAME_MACHINE"-apple-rhapsody"$UNAME_RELEASE" + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + eval "$set_cc_for_build" + if test "$UNAME_PROCESSOR" = unknown ; then + UNAME_PROCESSOR=powerpc + fi + if test "`echo "$UNAME_RELEASE" | sed -e 's/\..*//'`" -le 10 ; then + if [ "$CC_FOR_BUILD" != no_compiler_found ]; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + case $UNAME_PROCESSOR in + i386) UNAME_PROCESSOR=x86_64 ;; + powerpc) UNAME_PROCESSOR=powerpc64 ;; + esac + fi + # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc + if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_PPC >/dev/null + then + UNAME_PROCESSOR=powerpc + fi + fi + elif test "$UNAME_PROCESSOR" = i386 ; then + # Avoid executing cc on OS X 10.9, as it ships with a stub + # that puts up a graphical alert prompting to install + # developer tools. Any system running Mac OS X 10.7 or + # later (Darwin 11 and later) is required to have a 64-bit + # processor. This is not true of the ARM version of Darwin + # that Apple uses in portable devices. + UNAME_PROCESSOR=x86_64 + fi + echo "$UNAME_PROCESSOR"-apple-darwin"$UNAME_RELEASE" + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = x86; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo "$UNAME_PROCESSOR"-"$UNAME_MACHINE"-nto-qnx"$UNAME_RELEASE" + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NEO-*:NONSTOP_KERNEL:*:*) + echo neo-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSE-*:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSR-*:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSV-*:NONSTOP_KERNEL:*:*) + echo nsv-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSX-*:NONSTOP_KERNEL:*:*) + echo nsx-tandem-nsk"$UNAME_RELEASE" + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo "$UNAME_MACHINE"-"$UNAME_SYSTEM"-"$UNAME_RELEASE" + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = 386; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo "$UNAME_MACHINE"-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux"$UNAME_RELEASE" + exit ;; + *:DragonFly:*:*) + echo "$UNAME_MACHINE"-unknown-dragonfly"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "$UNAME_MACHINE" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo "$UNAME_MACHINE"-pc-skyos"`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'`" + exit ;; + i*86:rdos:*:*) + echo "$UNAME_MACHINE"-pc-rdos + exit ;; + i*86:AROS:*:*) + echo "$UNAME_MACHINE"-pc-aros + exit ;; + x86_64:VMkernel:*:*) + echo "$UNAME_MACHINE"-unknown-esx + exit ;; + amd64:Isilon\ OneFS:*:*) + echo x86_64-unknown-onefs + exit ;; +esac + +echo "$0: unable to guess system type" >&2 + +case "$UNAME_MACHINE:$UNAME_SYSTEM" in + mips:Linux | mips64:Linux) + # If we got here on MIPS GNU/Linux, output extra information. + cat >&2 <&2 </dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = "$UNAME_MACHINE" +UNAME_RELEASE = "$UNAME_RELEASE" +UNAME_SYSTEM = "$UNAME_SYSTEM" +UNAME_VERSION = "$UNAME_VERSION" +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-functions 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..2e8cd09 --- /dev/null +++ b/config.h.in @@ -0,0 +1,111 @@ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Directory for the Android daemon storage files */ +#undef ANDROID_STORAGEDIR + +/* Directory for the configuration files */ +#undef CONFIGDIR + +/* Define to 1 if you have the backtrace support. */ +#undef HAVE_BACKTRACE_SUPPORT + +/* Define to 1 if you have the header file. */ +#undef HAVE_DLFCN_H + +/* Define to 1 if you have the `explicit_bzero' function. */ +#undef HAVE_EXPLICIT_BZERO + +/* Define to 1 if you have the header file. */ +#undef HAVE_INTTYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_IF_ALG_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_TYPES_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_MEMORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_READLINE_READLINE_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDINT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STDLIB_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRINGS_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_STRING_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_STAT_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_SYS_TYPES_H + +/* Define to 1 if you have the udev_hwdb_new() function. */ +#undef HAVE_UDEV_HWDB_NEW + +/* Define to 1 if you have the header file. */ +#undef HAVE_UNISTD_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_VALGRIND_MEMCHECK_H + +/* Define to the sub-directory where libtool stores uninstalled libraries. */ +#undef LT_OBJDIR + +/* Directory for the mesh daemon storage files */ +#undef MESH_STORAGEDIR + +/* Define if threading support is required */ +#undef NEED_THREADS + +/* Name of package */ +#undef PACKAGE + +/* Define to the address where bug reports for this package should be sent. */ +#undef PACKAGE_BUGREPORT + +/* Define to the full name of this package. */ +#undef PACKAGE_NAME + +/* Define to the full name and version of this package. */ +#undef PACKAGE_STRING + +/* Define to the one symbol short name of this package. */ +#undef PACKAGE_TARNAME + +/* Define to the home page for this package. */ +#undef PACKAGE_URL + +/* Define to the version of this package. */ +#undef PACKAGE_VERSION + +/* Define to 1 if you have the ANSI C header files. */ +#undef STDC_HEADERS + +/* Directory for the storage files */ +#undef STORAGEDIR + +/* Version number of package */ +#undef VERSION + +/* Define to the equivalent of the C99 'restrict' keyword, or to + nothing if this is not supported. Do not define if restrict is + supported directly. */ +#undef restrict +/* Work around a bug in Sun C++: it does not support _Restrict or + __restrict__, even though the corresponding Sun C compiler ends up with + "#define restrict _Restrict" or "#define restrict __restrict__" in the + previous line. Perhaps some future version of Sun C++ will work with + restrict; if so, hopefully it defines __RESTRICT like Sun C does. */ +#if defined __SUNPRO_CC && !defined __RESTRICT +# define _Restrict +# define __restrict__ +#endif diff --git a/config.sub b/config.sub new file mode 100755 index 0000000..1d8e98b --- /dev/null +++ b/config.sub @@ -0,0 +1,1801 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright 1992-2018 Free Software Foundation, Inc. + +timestamp='2018-02-22' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). + + +# Please send patches to . +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# You can get the latest version of this script from: +# https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS + +Canonicalize a configuration name. + +Options: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright 1992-2018 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo "$1" + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \ + linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ + knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \ + kopensolaris*-gnu* | cloudabi*-eabi* | \ + storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + android-linux) + os=-linux-android + basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown + ;; + *) + basic_machine=`echo "$1" | sed 's/-[^-]*$//'` + if [ "$basic_machine" != "$1" ] + then os=`echo "$1" | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis | -knuth | -cray | -microblaze*) + os= + basic_machine=$1 + ;; + -bluegene*) + os=-cnk + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco6) + os=-sco5v6 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*178) + os=-lynxos178 + ;; + -lynx*5) + os=-lynxos5 + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo "$1" | sed -e 's/86-.*/86-sequent/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | aarch64 | aarch64_be \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arceb \ + | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ + | avr | avr32 \ + | ba \ + | be32 | be64 \ + | bfin \ + | c4x | c8051 | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | e2k | epiphany \ + | fido | fr30 | frv | ft32 \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | hexagon \ + | i370 | i860 | i960 | ia16 | ia64 \ + | ip2k | iq2000 \ + | k1om \ + | le32 | le64 \ + | lm32 \ + | m32c | m32r | m32rle | m68000 | m68k | m88k \ + | maxq | mb | microblaze | microblazeel | mcore | mep | metag \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64octeon | mips64octeonel \ + | mips64orion | mips64orionel \ + | mips64r5900 | mips64r5900el \ + | mips64vr | mips64vrel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mips64vr5900 | mips64vr5900el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa32r6 | mipsisa32r6el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64r6 | mipsisa64r6el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipsr5900 | mipsr5900el \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | moxie \ + | mt \ + | msp430 \ + | nds32 | nds32le | nds32be \ + | nios | nios2 | nios2eb | nios2el \ + | ns16k | ns32k \ + | open8 | or1k | or1knd | or32 \ + | pdp10 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle \ + | pru \ + | pyramid \ + | riscv32 | riscv64 \ + | rl78 | rx \ + | score \ + | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[234]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ + | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ + | spu \ + | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ + | ubicom32 \ + | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \ + | visium \ + | wasm32 \ + | x86 | xc16x | xstormy16 | xtensa \ + | z8k | z80) + basic_machine=$basic_machine-unknown + ;; + c54x) + basic_machine=tic54x-unknown + ;; + c55x) + basic_machine=tic55x-unknown + ;; + c6x) + basic_machine=tic6x-unknown + ;; + leon|leon[3-9]) + basic_machine=sparc-$basic_machine + ;; + m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip) + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65) + ;; + ms1) + basic_machine=mt-unknown + ;; + + strongarm | thumb | xscale) + basic_machine=arm-unknown + ;; + xgate) + basic_machine=$basic_machine-unknown + os=-none + ;; + xscaleeb) + basic_machine=armeb-unknown + ;; + + xscaleel) + basic_machine=armel-unknown + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`"$1"\': machine \`"$basic_machine"\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | aarch64-* | aarch64_be-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* | avr32-* \ + | ba-* \ + | be32-* | be64-* \ + | bfin-* | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* \ + | c8051-* | clipper-* | craynv-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | e2k-* | elxsi-* \ + | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | hexagon-* \ + | i*86-* | i860-* | i960-* | ia16-* | ia64-* \ + | ip2k-* | iq2000-* \ + | k1om-* \ + | le32-* | le64-* \ + | lm32-* \ + | m32c-* | m32r-* | m32rle-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \ + | microblaze-* | microblazeel-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64octeon-* | mips64octeonel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64r5900-* | mips64r5900el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mips64vr5900-* | mips64vr5900el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa32r6-* | mipsisa32r6el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64r6-* | mipsisa64r6el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipsr5900-* | mipsr5900el-* \ + | mipstx39-* | mipstx39el-* \ + | mmix-* \ + | mt-* \ + | msp430-* \ + | nds32-* | nds32le-* | nds32be-* \ + | nios-* | nios2-* | nios2eb-* | nios2el-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | open8-* \ + | or1k*-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ + | pru-* \ + | pyramid-* \ + | riscv32-* | riscv64-* \ + | rl78-* | romp-* | rs6000-* | rx-* \ + | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ + | sparclite-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx*-* \ + | tahoe-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tile*-* \ + | tron-* \ + | ubicom32-* \ + | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \ + | vax-* \ + | visium-* \ + | wasm32-* \ + | we32k-* \ + | x86-* | x86_64-* | xc16x-* | xps100-* \ + | xstormy16-* | xtensa*-* \ + | ymp-* \ + | z8k-* | z80-*) + ;; + # Recognize the basic CPU types without company name, with glob match. + xtensa*) + basic_machine=$basic_machine-unknown + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-pc + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aros) + basic_machine=i386-pc + os=-aros + ;; + asmjs) + basic_machine=asmjs-unknown + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + blackfin) + basic_machine=bfin-unknown + os=-linux + ;; + blackfin-*) + basic_machine=bfin-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=-linux + ;; + bluegene*) + basic_machine=powerpc-ibm + os=-cnk + ;; + c54x-*) + basic_machine=tic54x-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + c55x-*) + basic_machine=tic55x-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + c6x-*) + basic_machine=tic6x-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + cegcc) + basic_machine=arm-unknown + os=-cegcc + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + craynv) + basic_machine=craynv-cray + os=-unicosmp + ;; + cr16 | cr16-*) + basic_machine=cr16-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + crisv32 | crisv32-* | etraxfs*) + basic_machine=crisv32-axis + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dicos) + basic_machine=i686-pc + os=-dicos + ;; + djgpp) + basic_machine=i586-pc + os=-msdosdjgpp + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2*) + basic_machine=m68k-bull + os=-sysv3 + ;; + e500v[12]) + basic_machine=powerpc-unknown + os=$os"spe" + ;; + e500v[12]-*) + basic_machine=powerpc-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=$os"spe" + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; + i*86v32) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + leon-*|leon[3-9]-*) + basic_machine=sparc-`echo "$basic_machine" | sed 's/-.*//'` + ;; + m68knommu) + basic_machine=m68k-unknown + os=-linux + ;; + m68knommu-*) + basic_machine=m68k-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=-linux + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + microblaze*) + basic_machine=microblaze-xilinx + ;; + mingw64) + basic_machine=x86_64-pc + os=-mingw64 + ;; + mingw32) + basic_machine=i686-pc + os=-mingw32 + ;; + mingw32ce) + basic_machine=arm-unknown + os=-mingw32ce + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo "$basic_machine" | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo "$basic_machine" | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + moxiebox) + basic_machine=moxie-unknown + os=-moxiebox + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + ms1-*) + basic_machine=`echo "$basic_machine" | sed -e 's/ms1-/mt-/'` + ;; + msys) + basic_machine=i686-pc + os=-msys + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + nacl) + basic_machine=le32-unknown + os=-nacl + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + neo-tandem) + basic_machine=neo-tandem + ;; + nse-tandem) + basic_machine=nse-tandem + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + nsv-tandem) + basic_machine=nsv-tandem + ;; + nsx-tandem) + basic_machine=nsx-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + openrisc | openrisc-*) + basic_machine=or32-unknown + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + parisc) + basic_machine=hppa-unknown + os=-linux + ;; + parisc-*) + basic_machine=hppa-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=-linux + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pc98) + basic_machine=i386-pc + ;; + pc98-*) + basic_machine=i386-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc | ppcbe) basic_machine=powerpc-unknown + ;; + ppc-* | ppcbe-*) + basic_machine=powerpc-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rdos | rdos64) + basic_machine=x86_64-pc + os=-rdos + ;; + rdos32) + basic_machine=i386-pc + os=-rdos + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sde) + basic_machine=mipsisa32-sde + os=-elf + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh5el) + basic_machine=sh5le-unknown + ;; + simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + strongarm-* | thumb-*) + basic_machine=arm-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tile*) + basic_machine=$basic_machine-unknown + os=-linux-gnu + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + x64) + basic_machine=x86_64-pc + ;; + xbox) + basic_machine=i686-pc + os=-mingw32 + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + xscale-* | xscalee[bl]-*) + basic_machine=`echo "$basic_machine" | sed 's/^xscale/arm/'` + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + mmix) + basic_machine=mmix-knuth + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`"$1"\': machine \`"$basic_machine"\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo "$basic_machine" | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo "$basic_machine" | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases that might get confused + # with valid system types. + # -solaris* is a basic system type, with this one exception. + -auroraux) + os=-auroraux + ;; + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # es1800 is here to avoid being matched by es* (a different OS) + -es1800*) + os=-ose + ;; + # Now accept the basic system types. + # The portable systems comes first. + # Each alternative MUST end in a * to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \ + | -sym* | -kopensolaris* | -plan9* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* | -aros* | -cloudabi* | -sortix* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -knetbsd* | -mirbsd* | -netbsd* \ + | -bitrig* | -openbsd* | -solidbsd* | -libertybsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* | -cegcc* | -glidix* \ + | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -midipix* | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \ + | -linux-newlib* | -linux-musl* | -linux-uclibc* \ + | -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ + | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* \ + | -onefs* | -tirtos* | -phoenix* | -fuchsia* | -redox* | -bme* \ + | -midnightbsd*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -xray | -os68k* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo "$os" | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo "$os" | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo "$os" | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4*) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -zvmoe) + os=-zvmoe + ;; + -dicos*) + os=-dicos + ;; + -pikeos*) + # Until real need of OS specific support for + # particular features comes up, bare metal + # configurations are quite functional. + case $basic_machine in + arm*) + os=-eabi + ;; + *) + os=-elf + ;; + esac + ;; + -nacl*) + ;; + -ios) + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`"$1"\': system \`"$os"\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + score-*) + os=-elf + ;; + spu-*) + os=-elf + ;; + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + c8051-*) + os=-elf + ;; + hexagon-*) + os=-elf + ;; + tic54x-*) + os=-coff + ;; + tic55x-*) + os=-coff + ;; + tic6x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + ;; + m68*-cisco) + os=-aout + ;; + mep-*) + os=-elf + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + pru-*) + os=-elf + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-knuth) + os=-mmixware + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -cnk*|-aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo "$basic_machine" | sed "s/unknown/$vendor/"` + ;; +esac + +echo "$basic_machine$os" +exit + +# Local variables: +# eval: (add-hook 'write-file-functions 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/configure b/configure new file mode 100755 index 0000000..e254641 --- /dev/null +++ b/configure @@ -0,0 +1,17571 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69 for bluez 5.52. +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 + + test -n \"\${ZSH_VERSION+set}\${BASH_VERSION+set}\" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO + ECHO=\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO\$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test \"X\`printf %s \$ECHO\`\" = \"X\$ECHO\" \\ + || test \"X\`print -r -- \$ECHO\`\" = \"X\$ECHO\" ) || exit 1 +test \$(( 1 + 1 )) = 2 || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + +SHELL=${CONFIG_SHELL-/bin/sh} + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='bluez' +PACKAGE_TARNAME='bluez' +PACKAGE_VERSION='5.52' +PACKAGE_STRING='bluez 5.52' +PACKAGE_BUGREPORT='' +PACKAGE_URL='' + +ac_default_prefix=/usr/local +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef STDC_HEADERS +# include +# include +#else +# ifdef HAVE_STDLIB_H +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined STDC_HEADERS && defined HAVE_MEMORY_H +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_subst_vars='am__EXEEXT_FALSE +am__EXEEXT_TRUE +LTLIBOBJS +LIBOBJS +SPEEXDSP_LIBS +SPEEXDSP_CFLAGS +SBC_LIBS +SBC_CFLAGS +ANDROID_FALSE +ANDROID_TRUE +CONFIGDIR +LOGGER_FALSE +LOGGER_TRUE +SIXAXIS_FALSE +SIXAXIS_TRUE +DEPRECATED_FALSE +DEPRECATED_TRUE +EXPERIMENTAL_FALSE +EXPERIMENTAL_TRUE +TESTING_FALSE +TESTING_TRUE +MANPAGES_FALSE +MANPAGES_TRUE +DATAFILES_FALSE +DATAFILES_TRUE +SYSTEMD_USERUNITDIR +SYSTEMD_SYSTEMUNITDIR +SYSTEMD_FALSE +SYSTEMD_TRUE +READLINE_FALSE +READLINE_TRUE +CLIENT_FALSE +CLIENT_TRUE +LIBSHARED_ELL_FALSE +LIBSHARED_ELL_TRUE +EXTERNAL_ELL_FALSE +EXTERNAL_ELL_TRUE +ELL_LIBS +ELL_CFLAGS +BTPCLIENT_FALSE +BTPCLIENT_TRUE +OBEX_FALSE +OBEX_TRUE +ICAL_LIBS +ICAL_CFLAGS +ALSA_LIBS +ALSA_CFLAGS +MIDI_FALSE +MIDI_TRUE +JSON_LIBS +JSON_CFLAGS +JSONC_LIBS +JSONC_CFLAGS +MESH_FALSE +MESH_TRUE +CUPS_FALSE +CUPS_TRUE +HID2HCI_FALSE +HID2HCI_TRUE +UDEV_DIR +UDEV_FALSE +UDEV_TRUE +UDEV_LIBS +UDEV_CFLAGS +MONITOR_FALSE +MONITOR_TRUE +TOOLS_FALSE +TOOLS_TRUE +HEALTH_FALSE +HEALTH_TRUE +HOG_FALSE +HOG_TRUE +HID_FALSE +HID_TRUE +NETWORK_FALSE +NETWORK_TRUE +AVRCP_FALSE +AVRCP_TRUE +A2DP_FALSE +A2DP_TRUE +SAP_FALSE +SAP_TRUE +NFC_FALSE +NFC_TRUE +TEST_FALSE +TEST_TRUE +LIBRARY_FALSE +LIBRARY_TRUE +BACKTRACE_LIBS +BACKTRACE_CFLAGS +ZSH_COMPLETIONS_FALSE +ZSH_COMPLETIONS_TRUE +ZSH_COMPLETIONDIR +DBUS_SESSIONBUSDIR +DBUS_SYSTEMBUSDIR +DBUS_CONFDIR +DBUS_LIBS +DBUS_CFLAGS +GTHREAD_LIBS +GTHREAD_CFLAGS +GLIB_LIBS +GLIB_CFLAGS +MISC_LDFLAGS +MISC_CFLAGS +VALGRIND_FALSE +VALGRIND_TRUE +DBUS_RUN_SESSION_FALSE +DBUS_RUN_SESSION_TRUE +COVERAGE_FALSE +COVERAGE_TRUE +enable_valgrind +enable_dbus_run_session +enable_coverage +CPP +LT_SYS_LIBRARY_PATH +OTOOL64 +OTOOL +LIPO +NMEDIT +DSYMUTIL +MANIFEST_TOOL +RANLIB +ac_ct_AR +AR +DLLTOOL +OBJDUMP +LN_S +NM +ac_ct_DUMPBIN +DUMPBIN +LD +FGREP +EGREP +GREP +SED +host_os +host_vendor +host_cpu +host +build_os +build_vendor +build_cpu +build +LIBTOOL +am__fastdepCC_FALSE +am__fastdepCC_TRUE +CCDEPMODE +am__nodep +AMDEPBACKSLASH +AMDEP_FALSE +AMDEP_TRUE +am__include +DEPDIR +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +WARNING_CFLAGS +PKG_CONFIG_LIBDIR +PKG_CONFIG_PATH +PKG_CONFIG +MAINT +MAINTAINER_MODE_FALSE +MAINTAINER_MODE_TRUE +AM_BACKSLASH +AM_DEFAULT_VERBOSITY +AM_DEFAULT_V +AM_V +am__untar +am__tar +AMTAR +am__leading_dot +SET_MAKE +AWK +mkdir_p +MKDIR_P +INSTALL_STRIP_PROGRAM +STRIP +install_sh +MAKEINFO +AUTOHEADER +AUTOMAKE +AUTOCONF +ACLOCAL +VERSION +PACKAGE +CYGPATH_W +am__isrc +INSTALL_DATA +INSTALL_SCRIPT +INSTALL_PROGRAM +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +runstatedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL +am__quote' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +enable_silent_rules +enable_maintainer_mode +enable_dependency_tracking +enable_static +enable_shared +with_pic +enable_fast_install +with_aix_soname +with_gnu_ld +with_sysroot +enable_libtool_lock +enable_optimization +enable_debug +enable_pie +enable_threads +with_dbusconfdir +with_dbussystembusdir +with_dbussessionbusdir +with_zsh_completion_dir +enable_backtrace +enable_library +enable_test +enable_nfc +enable_sap +enable_a2dp +enable_avrcp +enable_network +enable_hid +enable_hog +enable_health +enable_tools +enable_monitor +enable_udev +with_udevdir +enable_cups +enable_mesh +enable_midi +enable_obex +enable_btpclient +enable_external_ell +enable_client +enable_systemd +with_systemdsystemunitdir +with_systemduserunitdir +enable_datafiles +enable_manpages +enable_testing +enable_experimental +enable_deprecated +enable_sixaxis +enable_logger +enable_android +' + ac_precious_vars='build_alias +host_alias +target_alias +PKG_CONFIG +PKG_CONFIG_PATH +PKG_CONFIG_LIBDIR +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS +LT_SYS_LIBRARY_PATH +CPP +GLIB_CFLAGS +GLIB_LIBS +GTHREAD_CFLAGS +GTHREAD_LIBS +DBUS_CFLAGS +DBUS_LIBS +UDEV_CFLAGS +UDEV_LIBS +JSONC_CFLAGS +JSONC_LIBS +ALSA_CFLAGS +ALSA_LIBS +ICAL_CFLAGS +ICAL_LIBS +ELL_CFLAGS +ELL_LIBS +SBC_CFLAGS +SBC_LIBS +SPEEXDSP_CFLAGS +SPEEXDSP_LIBS' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir runstatedir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures bluez 5.52 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/bluez] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF + +Program names: + --program-prefix=PREFIX prepend PREFIX to installed program names + --program-suffix=SUFFIX append SUFFIX to installed program names + --program-transform-name=PROGRAM run sed PROGRAM on installed program names + +System types: + --build=BUILD configure for building on BUILD [guessed] + --host=HOST cross-compile to build programs to run on HOST [BUILD] +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of bluez 5.52:";; + esac + cat <<\_ACEOF + +Optional Features: + --disable-option-checking ignore unrecognized --enable/--with options + --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) + --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-silent-rules less verbose build output (undo: "make V=1") + --disable-silent-rules verbose build output (undo: "make V=0") + --enable-maintainer-mode + enable make rules and dependencies not useful (and + sometimes confusing) to the casual installer + --enable-dependency-tracking + do not reject slow dependency extractors + --disable-dependency-tracking + speeds up one-time build + --enable-static[=PKGS] build static libraries [default=no] + --enable-shared[=PKGS] build shared libraries [default=yes] + --enable-fast-install[=PKGS] + optimize for fast installation [default=yes] + --disable-libtool-lock avoid locking (might break parallel builds) + --disable-optimization disable code optimization through compiler + --enable-debug enable compiling with debugging information + --enable-pie enable position independent executables flag + --enable-threads enable threading support + --enable-backtrace compile backtrace support + --enable-library install Bluetooth library + --enable-test enable test/example scripts + --enable-nfc enable NFC paring + --enable-sap enable SAP profile + --disable-a2dp disable A2DP profile + --disable-avrcp disable AVRCP profile + --disable-network disable network profiles + --disable-hid disable HID profile + --disable-hog disable HoG profile + --enable-health enable health profiles + --disable-tools disable Bluetooth tools + --disable-monitor disable Bluetooth monitor + --disable-udev disable udev device support + --disable-cups disable CUPS printer support + --enable-mesh enable Mesh profile support + --enable-midi enable MIDI support + --disable-obex disable OBEX profile support + --enable-btpclient enable BTP client + --enable-external-ell enable external Embedded Linux library + --disable-client disable command line client + --disable-systemd disable systemd integration + --disable-datafiles do not install configuration and data files + --enable-manpages enable building of manual pages + --enable-testing enable testing tools + --enable-experimental enable experimental tools + --enable-deprecated enable deprecated tools + --enable-sixaxis enable sixaxis plugin + --enable-logger enable HCI logger service + --enable-android enable BlueZ for Android + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-pic[=PKGS] try to use only PIC/non-PIC objects [default=use + both] + --with-aix-soname=aix|svr4|both + shared library versioning (aka "SONAME") variant to + provide on AIX, [default=aix]. + --with-gnu-ld assume the C compiler uses GNU ld [default=no] + --with-sysroot[=DIR] Search for dependent libraries within DIR (or the + compiler's sysroot if not specified). + --with-dbusconfdir=DIR path to D-Bus configuration directory + --with-dbussystembusdir=DIR + path to D-Bus system bus services directory + --with-dbussessionbusdir=DIR + path to D-Bus session bus services directory + --with-zsh-completion-dir=DIR + path to install zsh completions + --with-udevdir=DIR path to udev directory + --with-systemdsystemunitdir=DIR + path to systemd system unit directory + --with-systemduserunitdir=DIR + path to systemd user unit directory + +Some influential environment variables: + PKG_CONFIG path to pkg-config utility + PKG_CONFIG_PATH + directories to add to pkg-config's search path + PKG_CONFIG_LIBDIR + path overriding pkg-config's built-in search path + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + LT_SYS_LIBRARY_PATH + User-defined run-time library search path. + CPP C preprocessor + GLIB_CFLAGS C compiler flags for GLIB, overriding pkg-config + GLIB_LIBS linker flags for GLIB, overriding pkg-config + GTHREAD_CFLAGS + C compiler flags for GTHREAD, overriding pkg-config + GTHREAD_LIBS + linker flags for GTHREAD, overriding pkg-config + DBUS_CFLAGS C compiler flags for DBUS, overriding pkg-config + DBUS_LIBS linker flags for DBUS, overriding pkg-config + UDEV_CFLAGS C compiler flags for UDEV, overriding pkg-config + UDEV_LIBS linker flags for UDEV, overriding pkg-config + JSONC_CFLAGS + C compiler flags for JSONC, overriding pkg-config + JSONC_LIBS linker flags for JSONC, overriding pkg-config + ALSA_CFLAGS C compiler flags for ALSA, overriding pkg-config + ALSA_LIBS linker flags for ALSA, overriding pkg-config + ICAL_CFLAGS C compiler flags for ICAL, overriding pkg-config + ICAL_LIBS linker flags for ICAL, overriding pkg-config + ELL_CFLAGS C compiler flags for ELL, overriding pkg-config + ELL_LIBS linker flags for ELL, overriding pkg-config + SBC_CFLAGS C compiler flags for SBC, overriding pkg-config + SBC_LIBS linker flags for SBC, overriding pkg-config + SPEEXDSP_CFLAGS + C compiler flags for SPEEXDSP, overriding pkg-config + SPEEXDSP_LIBS + linker flags for SPEEXDSP, overriding pkg-config + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +bluez configure 5.52 +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_c_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_compile + +# ac_fn_c_try_cpp LINENO +# ---------------------- +# Try to preprocess conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_cpp () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } > conftest.i && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_cpp + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run + +# ac_fn_c_check_func LINENO FUNC VAR +# ---------------------------------- +# Tests whether FUNC exists, setting the cache variable VAR accordingly +ac_fn_c_check_func () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +/* Define $2 to an innocuous variant, in case declares $2. + For example, HP-UX 11i declares gettimeofday. */ +#define $2 innocuous_$2 + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $2 (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $2 + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $2 (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$2 || defined __stub___$2 +choke me +#endif + +int +main () +{ +return $2 (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + eval "$3=yes" +else + eval "$3=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_func + +# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES +# ------------------------------------------------------- +# Tests whether HEADER exists, giving a warning if it cannot be compiled using +# the include files in INCLUDES and setting the cache variable VAR +# accordingly. +ac_fn_c_check_header_mongrel () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if eval \${$3+:} false; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +else + # Is the header compilable? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 +$as_echo_n "checking $2 usability... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_header_compiler=yes +else + ac_header_compiler=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 +$as_echo "$ac_header_compiler" >&6; } + +# Is the header present? +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 +$as_echo_n "checking $2 presence... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <$2> +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + ac_header_preproc=yes +else + ac_header_preproc=no +fi +rm -f conftest.err conftest.i conftest.$ac_ext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 +$as_echo "$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #(( + yes:no: ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 +$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; + no:yes:* ) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 +$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 +$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 +$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 +$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 +$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} + ;; +esac + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +$as_echo_n "checking for $2... " >&6; } +if eval \${$3+:} false; then : + $as_echo_n "(cached) " >&6 +else + eval "$3=\$ac_header_compiler" +fi +eval ac_res=\$$3 + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_c_check_header_mongrel +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by bluez $as_me 5.52, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +am__api_version='1.16' + +ac_aux_dir= +for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do + if test -f "$ac_dir/install-sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install-sh -c" + break + elif test -f "$ac_dir/install.sh"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/install.sh -c" + break + elif test -f "$ac_dir/shtool"; then + ac_aux_dir=$ac_dir + ac_install_sh="$ac_aux_dir/shtool install -c" + break + fi +done +if test -z "$ac_aux_dir"; then + as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5 +fi + +# These three variables are undocumented and unsupported, +# and are intended to be withdrawn in a future Autoconf release. +# They can cause serious problems if a builder's source tree is in a directory +# whose full name contains unusual characters. +ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var. +ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. +ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. + + +# Find a good install program. We prefer a C program (faster), +# so one script is as good as another. But avoid the broken or +# incompatible versions: +# SysV /etc/install, /usr/sbin/install +# SunOS /usr/etc/install +# IRIX /sbin/install +# AIX /bin/install +# AmigaOS /C/install, which installs bootblocks on floppy discs +# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag +# AFS /usr/afsws/bin/install, which mishandles nonexistent args +# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff" +# OS/2's system install, which has a completely different semantic +# ./install, which can be erroneously created by make from ./install.sh. +# Reject install programs that cannot install multiple files. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a BSD-compatible install" >&5 +$as_echo_n "checking for a BSD-compatible install... " >&6; } +if test -z "$INSTALL"; then +if ${ac_cv_path_install+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + # Account for people who put trailing slashes in PATH elements. +case $as_dir/ in #(( + ./ | .// | /[cC]/* | \ + /etc/* | /usr/sbin/* | /usr/etc/* | /sbin/* | /usr/afsws/bin/* | \ + ?:[\\/]os2[\\/]install[\\/]* | ?:[\\/]OS2[\\/]INSTALL[\\/]* | \ + /usr/ucb/* ) ;; + *) + # OSF1 and SCO ODT 3.0 have their own names for install. + # Don't use installbsd from OSF since it installs stuff as root + # by default. + for ac_prog in ginstall scoinst install; do + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then + if test $ac_prog = install && + grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # AIX install. It has an incompatible calling convention. + : + elif test $ac_prog = install && + grep pwplus "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then + # program-specific install script used by HP pwplus--don't use. + : + else + rm -rf conftest.one conftest.two conftest.dir + echo one > conftest.one + echo two > conftest.two + mkdir conftest.dir + if "$as_dir/$ac_prog$ac_exec_ext" -c conftest.one conftest.two "`pwd`/conftest.dir" && + test -s conftest.one && test -s conftest.two && + test -s conftest.dir/conftest.one && + test -s conftest.dir/conftest.two + then + ac_cv_path_install="$as_dir/$ac_prog$ac_exec_ext -c" + break 3 + fi + fi + fi + done + done + ;; +esac + + done +IFS=$as_save_IFS + +rm -rf conftest.one conftest.two conftest.dir + +fi + if test "${ac_cv_path_install+set}" = set; then + INSTALL=$ac_cv_path_install + else + # As a last resort, use the slow shell script. Don't cache a + # value for INSTALL within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + INSTALL=$ac_install_sh + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $INSTALL" >&5 +$as_echo "$INSTALL" >&6; } + +# Use test -z because SunOS4 sh mishandles braces in ${var-val}. +# It thinks the first close brace ends the variable substitution. +test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}' + +test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL}' + +test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644' + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether build environment is sane" >&5 +$as_echo_n "checking whether build environment is sane... " >&6; } +# Reject unsafe characters in $srcdir or the absolute working directory +# name. Accept space and tab only in the latter. +am_lf=' +' +case `pwd` in + *[\\\"\#\$\&\'\`$am_lf]*) + as_fn_error $? "unsafe absolute working directory name" "$LINENO" 5;; +esac +case $srcdir in + *[\\\"\#\$\&\'\`$am_lf\ \ ]*) + as_fn_error $? "unsafe srcdir value: '$srcdir'" "$LINENO" 5;; +esac + +# Do 'set' in a subshell so we don't clobber the current shell's +# arguments. Must try -L first in case configure is actually a +# symlink; some systems play weird games with the mod time of symlinks +# (eg FreeBSD returns the mod time of the symlink's containing +# directory). +if ( + am_has_slept=no + for am_try in 1 2; do + echo "timestamp, slept: $am_has_slept" > conftest.file + set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null` + if test "$*" = "X"; then + # -L didn't work. + set X `ls -t "$srcdir/configure" conftest.file` + fi + if test "$*" != "X $srcdir/configure conftest.file" \ + && test "$*" != "X conftest.file $srcdir/configure"; then + + # If neither matched, then we have a broken ls. This can happen + # if, for instance, CONFIG_SHELL is bash and it inherits a + # broken ls alias from the environment. This has actually + # happened. Such a system could not be considered "sane". + as_fn_error $? "ls -t appears to fail. Make sure there is not a broken + alias in your environment" "$LINENO" 5 + fi + if test "$2" = conftest.file || test $am_try -eq 2; then + break + fi + # Just in case. + sleep 1 + am_has_slept=yes + done + test "$2" = conftest.file + ) +then + # Ok. + : +else + as_fn_error $? "newly created file is older than distributed files! +Check your system clock" "$LINENO" 5 +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +# If we didn't sleep, we still need to ensure time stamps of config.status and +# generated files are strictly newer. +am_sleep_pid= +if grep 'slept: no' conftest.file >/dev/null 2>&1; then + ( sleep 1 ) & + am_sleep_pid=$! +fi + +rm -f conftest.file + +test "$program_prefix" != NONE && + program_transform_name="s&^&$program_prefix&;$program_transform_name" +# Use a double $ so make ignores it. +test "$program_suffix" != NONE && + program_transform_name="s&\$&$program_suffix&;$program_transform_name" +# Double any \ or $. +# By default was `s,x,x', remove it if useless. +ac_script='s/[\\$]/&&/g;s/;s,x,x,$//' +program_transform_name=`$as_echo "$program_transform_name" | sed "$ac_script"` + +# Expand $ac_aux_dir to an absolute path. +am_aux_dir=`cd "$ac_aux_dir" && pwd` + +if test x"${MISSING+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + MISSING="\${SHELL} \"$am_aux_dir/missing\"" ;; + *) + MISSING="\${SHELL} $am_aux_dir/missing" ;; + esac +fi +# Use eval to expand $SHELL +if eval "$MISSING --is-lightweight"; then + am_missing_run="$MISSING " +else + am_missing_run= + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: 'missing' script is too old or missing" >&5 +$as_echo "$as_me: WARNING: 'missing' script is too old or missing" >&2;} +fi + +if test x"${install_sh+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi + +# Installed binaries are usually stripped using 'strip' when the user +# run "make install-strip". However 'strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the 'STRIP' environment variable to overrule this program. +if test "$cross_compiling" != no; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a thread-safe mkdir -p" >&5 +$as_echo_n "checking for a thread-safe mkdir -p... " >&6; } +if test -z "$MKDIR_P"; then + if ${ac_cv_path_mkdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in mkdir gmkdir; do + for ac_exec_ext in '' $ac_executable_extensions; do + as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue + case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #( + 'mkdir (GNU coreutils) '* | \ + 'mkdir (coreutils) '* | \ + 'mkdir (fileutils) '4.1*) + ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext + break 3;; + esac + done + done + done +IFS=$as_save_IFS + +fi + + test -d ./--version && rmdir ./--version + if test "${ac_cv_path_mkdir+set}" = set; then + MKDIR_P="$ac_cv_path_mkdir -p" + else + # As a last resort, use the slow shell script. Don't cache a + # value for MKDIR_P within a source directory, because that will + # break other packages using the cache if that directory is + # removed, or if the value is a relative name. + MKDIR_P="$ac_install_sh -d" + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $MKDIR_P" >&5 +$as_echo "$MKDIR_P" >&6; } + +for ac_prog in gawk mawk nawk awk +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AWK+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AWK"; then + ac_cv_prog_AWK="$AWK" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AWK="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AWK=$ac_cv_prog_AWK +if test -n "$AWK"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5 +$as_echo "$AWK" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AWK" && break +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} sets \$(MAKE)" >&5 +$as_echo_n "checking whether ${MAKE-make} sets \$(MAKE)... " >&6; } +set x ${MAKE-make} +ac_make=`$as_echo "$2" | sed 's/+/p/g; s/[^a-zA-Z0-9_]/_/g'` +if eval \${ac_cv_prog_make_${ac_make}_set+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat >conftest.make <<\_ACEOF +SHELL = /bin/sh +all: + @echo '@@@%%%=$(MAKE)=@@@%%%' +_ACEOF +# GNU make sometimes prints "make[1]: Entering ...", which would confuse us. +case `${MAKE-make} -f conftest.make 2>/dev/null` in + *@@@%%%=?*=@@@%%%*) + eval ac_cv_prog_make_${ac_make}_set=yes;; + *) + eval ac_cv_prog_make_${ac_make}_set=no;; +esac +rm -f conftest.make +fi +if eval test \$ac_cv_prog_make_${ac_make}_set = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + SET_MAKE= +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + SET_MAKE="MAKE=${MAKE-make}" +fi + +rm -rf .tst 2>/dev/null +mkdir .tst 2>/dev/null +if test -d .tst; then + am__leading_dot=. +else + am__leading_dot=_ +fi +rmdir .tst 2>/dev/null + +# Check whether --enable-silent-rules was given. +if test "${enable_silent_rules+set}" = set; then : + enableval=$enable_silent_rules; +fi + +case $enable_silent_rules in # ((( + yes) AM_DEFAULT_VERBOSITY=0;; + no) AM_DEFAULT_VERBOSITY=1;; + *) AM_DEFAULT_VERBOSITY=1;; +esac +am_make=${MAKE-make} +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 +$as_echo_n "checking whether $am_make supports nested variables... " >&6; } +if ${am_cv_make_support_nested_variables+:} false; then : + $as_echo_n "(cached) " >&6 +else + if $as_echo 'TRUE=$(BAR$(V)) +BAR0=false +BAR1=true +V=1 +am__doit: + @$(TRUE) +.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then + am_cv_make_support_nested_variables=yes +else + am_cv_make_support_nested_variables=no +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 +$as_echo "$am_cv_make_support_nested_variables" >&6; } +if test $am_cv_make_support_nested_variables = yes; then + AM_V='$(V)' + AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' +else + AM_V=$AM_DEFAULT_VERBOSITY + AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY +fi +AM_BACKSLASH='\' + +if test "`cd $srcdir && pwd`" != "`pwd`"; then + # Use -I$(srcdir) only when $(srcdir) != ., so that make's output + # is not polluted with repeated "-I." + am__isrc=' -I$(srcdir)' + # test to see if srcdir already configured + if test -f $srcdir/config.status; then + as_fn_error $? "source directory already configured; run \"make distclean\" there first" "$LINENO" 5 + fi +fi + +# test whether we have cygpath +if test -z "$CYGPATH_W"; then + if (cygpath --version) >/dev/null 2>/dev/null; then + CYGPATH_W='cygpath -w' + else + CYGPATH_W=echo + fi +fi + + +# Define the identity of the package. + PACKAGE='bluez' + VERSION='5.52' + + +cat >>confdefs.h <<_ACEOF +#define PACKAGE "$PACKAGE" +_ACEOF + + +cat >>confdefs.h <<_ACEOF +#define VERSION "$VERSION" +_ACEOF + +# Some tools Automake needs. + +ACLOCAL=${ACLOCAL-"${am_missing_run}aclocal-${am__api_version}"} + + +AUTOCONF=${AUTOCONF-"${am_missing_run}autoconf"} + + +AUTOMAKE=${AUTOMAKE-"${am_missing_run}automake-${am__api_version}"} + + +AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} + + +MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} + +# For better backward compatibility. To be removed once Automake 1.9.x +# dies out for good. For more background, see: +# +# +mkdir_p='$(MKDIR_P)' + +# We need awk for the "check" target (and possibly the TAP driver). The +# system "awk" is bad on some platforms. +# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AMTAR='$${TAR-tar}' + + +# We'll loop over all known methods to create a tar archive until one works. +_am_tools='gnutar pax cpio none' + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to create a pax tar archive" >&5 +$as_echo_n "checking how to create a pax tar archive... " >&6; } + + # Go ahead even if we have the value already cached. We do so because we + # need to set the values for the 'am__tar' and 'am__untar' variables. + _am_tools=${am_cv_prog_tar_pax-$_am_tools} + + for _am_tool in $_am_tools; do + case $_am_tool in + gnutar) + for _am_tar in tar gnutar gtar; do + { echo "$as_me:$LINENO: $_am_tar --version" >&5 + ($_am_tar --version) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && break + done + am__tar="$_am_tar --format=posix -chf - "'"$$tardir"' + am__tar_="$_am_tar --format=posix -chf - "'"$tardir"' + am__untar="$_am_tar -xf -" + ;; + plaintar) + # Must skip GNU tar: if it does not support --format= it doesn't create + # ustar tarball either. + (tar --version) >/dev/null 2>&1 && continue + am__tar='tar chf - "$$tardir"' + am__tar_='tar chf - "$tardir"' + am__untar='tar xf -' + ;; + pax) + am__tar='pax -L -x pax -w "$$tardir"' + am__tar_='pax -L -x pax -w "$tardir"' + am__untar='pax -r' + ;; + cpio) + am__tar='find "$$tardir" -print | cpio -o -H pax -L' + am__tar_='find "$tardir" -print | cpio -o -H pax -L' + am__untar='cpio -i -H pax -d' + ;; + none) + am__tar=false + am__tar_=false + am__untar=false + ;; + esac + + # If the value was cached, stop now. We just wanted to have am__tar + # and am__untar set. + test -n "${am_cv_prog_tar_pax}" && break + + # tar/untar a dummy directory, and stop if the command works. + rm -rf conftest.dir + mkdir conftest.dir + echo GrepMe > conftest.dir/file + { echo "$as_me:$LINENO: tardir=conftest.dir && eval $am__tar_ >conftest.tar" >&5 + (tardir=conftest.dir && eval $am__tar_ >conftest.tar) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + rm -rf conftest.dir + if test -s conftest.tar; then + { echo "$as_me:$LINENO: $am__untar &5 + ($am__untar &5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + { echo "$as_me:$LINENO: cat conftest.dir/file" >&5 + (cat conftest.dir/file) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + grep GrepMe conftest.dir/file >/dev/null 2>&1 && break + fi + done + rm -rf conftest.dir + + if ${am_cv_prog_tar_pax+:} false; then : + $as_echo_n "(cached) " >&6 +else + am_cv_prog_tar_pax=$_am_tool +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_tar_pax" >&5 +$as_echo "$am_cv_prog_tar_pax" >&6; } + + + + + + +# POSIX will say in a future version that running "rm -f" with no argument +# is OK; and we want to be able to make that assumption in our Makefile +# recipes. So use an aggressive probe to check that the usage we want is +# actually supported "in the wild" to an acceptable degree. +# See automake bug#10828. +# To make any issue more visible, cause the running configure to be aborted +# by default if the 'rm' program in use doesn't match our expectations; the +# user can still override this though. +if rm -f && rm -fr && rm -rf; then : OK; else + cat >&2 <<'END' +Oops! + +Your 'rm' program seems unable to run without file operands specified +on the command line, even when the '-f' option is present. This is contrary +to the behaviour of most rm programs out there, and not conforming with +the upcoming POSIX standard: + +Please tell bug-automake@gnu.org about your system, including the value +of your $PATH and any error possibly output before this message. This +can help us improve future automake versions. + +END + if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then + echo 'Configuration will proceed anyway, since you have set the' >&2 + echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2 + echo >&2 + else + cat >&2 <<'END' +Aborting the configuration process, to ensure you take notice of the issue. + +You can download and install GNU coreutils to get an 'rm' implementation +that behaves properly: . + +If you want to complete the configuration process using your problematic +'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM +to "yes", and re-run configure. + +END + as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5 + fi +fi + +ac_config_headers="$ac_config_headers config.h" + + +# Check whether --enable-silent-rules was given. +if test "${enable_silent_rules+set}" = set; then : + enableval=$enable_silent_rules; +fi + +case $enable_silent_rules in # ((( + yes) AM_DEFAULT_VERBOSITY=0;; + no) AM_DEFAULT_VERBOSITY=1;; + *) AM_DEFAULT_VERBOSITY=0;; +esac +am_make=${MAKE-make} +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $am_make supports nested variables" >&5 +$as_echo_n "checking whether $am_make supports nested variables... " >&6; } +if ${am_cv_make_support_nested_variables+:} false; then : + $as_echo_n "(cached) " >&6 +else + if $as_echo 'TRUE=$(BAR$(V)) +BAR0=false +BAR1=true +V=1 +am__doit: + @$(TRUE) +.PHONY: am__doit' | $am_make -f - >/dev/null 2>&1; then + am_cv_make_support_nested_variables=yes +else + am_cv_make_support_nested_variables=no +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_make_support_nested_variables" >&5 +$as_echo "$am_cv_make_support_nested_variables" >&6; } +if test $am_cv_make_support_nested_variables = yes; then + AM_V='$(V)' + AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)' +else + AM_V=$AM_DEFAULT_VERBOSITY + AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY +fi +AM_BACKSLASH='\' + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable maintainer-specific portions of Makefiles" >&5 +$as_echo_n "checking whether to enable maintainer-specific portions of Makefiles... " >&6; } + # Check whether --enable-maintainer-mode was given. +if test "${enable_maintainer_mode+set}" = set; then : + enableval=$enable_maintainer_mode; USE_MAINTAINER_MODE=$enableval +else + USE_MAINTAINER_MODE=no +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $USE_MAINTAINER_MODE" >&5 +$as_echo "$USE_MAINTAINER_MODE" >&6; } + if test $USE_MAINTAINER_MODE = yes; then + MAINTAINER_MODE_TRUE= + MAINTAINER_MODE_FALSE='#' +else + MAINTAINER_MODE_TRUE='#' + MAINTAINER_MODE_FALSE= +fi + + MAINT=$MAINTAINER_MODE_TRUE + + + + + + + + + + + + +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}pkg-config", so it can be a program name with args. +set dummy ${ac_tool_prefix}pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +PKG_CONFIG=$ac_cv_path_PKG_CONFIG +if test -n "$PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 +$as_echo "$PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_path_PKG_CONFIG"; then + ac_pt_PKG_CONFIG=$PKG_CONFIG + # Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_ac_pt_PKG_CONFIG+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $ac_pt_PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_ac_pt_PKG_CONFIG="$ac_pt_PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_ac_pt_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +ac_pt_PKG_CONFIG=$ac_cv_path_ac_pt_PKG_CONFIG +if test -n "$ac_pt_PKG_CONFIG"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_PKG_CONFIG" >&5 +$as_echo "$ac_pt_PKG_CONFIG" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_pt_PKG_CONFIG" = x; then + PKG_CONFIG="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + PKG_CONFIG=$ac_pt_PKG_CONFIG + fi +else + PKG_CONFIG="$ac_cv_path_PKG_CONFIG" +fi + +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=0.9.0 + { $as_echo "$as_me:${as_lineno-$LINENO}: checking pkg-config is at least version $_pkg_min_version" >&5 +$as_echo_n "checking pkg-config is at least version $_pkg_min_version... " >&6; } + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + PKG_CONFIG="" + fi +fi + + + with_cflags="" + if (test "$USE_MAINTAINER_MODE" = "yes"); then + with_cflags="$with_cflags -Wall -Werror -Wextra" + with_cflags="$with_cflags -Wno-unused-parameter" + with_cflags="$with_cflags -Wno-missing-field-initializers" + with_cflags="$with_cflags -Wdeclaration-after-statement" + with_cflags="$with_cflags -Wmissing-declarations" + with_cflags="$with_cflags -Wredundant-decls" + with_cflags="$with_cflags -Wcast-align" + with_cflags="$with_cflags -Wswitch-enum" + with_cflags="$with_cflags -Wformat -Wformat-security" + with_cflags="$with_cflags -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_32" + fi + WARNING_CFLAGS=$with_cflags + + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +DEPDIR="${am__leading_dot}deps" + +ac_config_commands="$ac_config_commands depfiles" + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5 +$as_echo_n "checking whether ${MAKE-make} supports the include directive... " >&6; } +cat > confinc.mk << 'END' +am__doit: + @echo this is the am__doit target >confinc.out +.PHONY: am__doit +END +am__include="#" +am__quote= +# BSD make does it like this. +echo '.include "confinc.mk" # ignored' > confmf.BSD +# Other make implementations (GNU, Solaris 10, AIX) do it like this. +echo 'include confinc.mk # ignored' > confmf.GNU +_am_result=no +for s in GNU BSD; do + { echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5 + (${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } + case $?:`cat confinc.out 2>/dev/null` in #( + '0:this is the am__doit target') : + case $s in #( + BSD) : + am__include='.include' am__quote='"' ;; #( + *) : + am__include='include' am__quote='' ;; +esac ;; #( + *) : + ;; +esac + if test "$am__include" != "#"; then + _am_result="yes ($s style)" + break + fi +done +rm -f confinc.* confmf.* +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5 +$as_echo "${_am_result}" >&6; } + +# Check whether --enable-dependency-tracking was given. +if test "${enable_dependency_tracking+set}" = set; then : + enableval=$enable_dependency_tracking; +fi + +if test "x$enable_dependency_tracking" != xno; then + am_depcomp="$ac_aux_dir/depcomp" + AMDEPBACKSLASH='\' + am__nodep='_no' +fi + if test "x$enable_dependency_tracking" != xno; then + AMDEP_TRUE= + AMDEP_FALSE='#' +else + AMDEP_TRUE='#' + AMDEP_FALSE= +fi + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 +$as_echo_n "checking whether $CC understands -c and -o together... " >&6; } +if ${am_cv_prog_cc_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 + ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 +$as_echo "$am_cv_prog_cc_c_o" >&6; } +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +depcc="$CC" am_compiler_list= + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if ${am_cv_CC_dependencies_compiler_type+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CC_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + am__universal=false + case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CC_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CC_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } +CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then + am__fastdepCC_TRUE= + am__fastdepCC_FALSE='#' +else + am__fastdepCC_TRUE='#' + am__fastdepCC_FALSE= +fi + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C/C++ restrict keyword" >&5 +$as_echo_n "checking for C/C++ restrict keyword... " >&6; } +if ${ac_cv_c_restrict+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_c_restrict=no + # The order here caters to the fact that C++ does not require restrict. + for ac_kw in __restrict __restrict__ _Restrict restrict; do + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +typedef int * int_ptr; + int foo (int_ptr $ac_kw ip) { + return ip[0]; + } +int +main () +{ +int s[1]; + int * $ac_kw t = s; + t[0] = 0; + return foo(t) + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_c_restrict=$ac_kw +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + test "$ac_cv_c_restrict" != no && break + done + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_restrict" >&5 +$as_echo "$ac_cv_c_restrict" >&6; } + + case $ac_cv_c_restrict in + restrict) ;; + no) $as_echo "#define restrict /**/" >>confdefs.h + ;; + *) cat >>confdefs.h <<_ACEOF +#define restrict $ac_cv_c_restrict +_ACEOF + ;; + esac + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC understands -c and -o together" >&5 +$as_echo_n "checking whether $CC understands -c and -o together... " >&6; } +if ${am_cv_prog_cc_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF + # Make sure it works both with $CC and with simple cc. + # Following AC_PROG_CC_C_O, we do the test twice because some + # compilers refuse to overwrite an existing .o file with -o, + # though they will create one. + am_cv_prog_cc_c_o=yes + for am_i in 1 2; do + if { echo "$as_me:$LINENO: $CC -c conftest.$ac_ext -o conftest2.$ac_objext" >&5 + ($CC -c conftest.$ac_ext -o conftest2.$ac_objext) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } \ + && test -f conftest2.$ac_objext; then + : OK + else + am_cv_prog_cc_c_o=no + break + fi + done + rm -f core conftest* + unset am_i +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_prog_cc_c_o" >&5 +$as_echo "$am_cv_prog_cc_c_o" >&6; } +if test "$am_cv_prog_cc_c_o" != yes; then + # Losing compiler, so override with the script. + # FIXME: It is wrong to rewrite CC. + # But if we don't then we get into trouble of one sort or another. + # A longer-term fix would be to have automake use am__CC in this case, + # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)" + CC="$am_aux_dir/compile $CC" +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +depcc="$CC" am_compiler_list= + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5 +$as_echo_n "checking dependency style of $depcc... " >&6; } +if ${am_cv_CC_dependencies_compiler_type+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then + # We make a subdir and do the tests there. Otherwise we can end up + # making bogus files that we don't know about and never remove. For + # instance it was reported that on HP-UX the gcc test will end up + # making a dummy file named 'D' -- because '-MD' means "put the output + # in D". + rm -rf conftest.dir + mkdir conftest.dir + # Copy depcomp to subdir because otherwise we won't find it if we're + # using a relative directory. + cp "$am_depcomp" conftest.dir + cd conftest.dir + # We will build objects and dependencies in a subdirectory because + # it helps to detect inapplicable dependency modes. For instance + # both Tru64's cc and ICC support -MD to output dependencies as a + # side effect of compilation, but ICC will put the dependencies in + # the current directory while Tru64 will put them in the object + # directory. + mkdir sub + + am_cv_CC_dependencies_compiler_type=none + if test "$am_compiler_list" = ""; then + am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp` + fi + am__universal=false + case " $depcc " in #( + *\ -arch\ *\ -arch\ *) am__universal=true ;; + esac + + for depmode in $am_compiler_list; do + # Setup a source with many dependencies, because some compilers + # like to wrap large dependency lists on column 80 (with \), and + # we should not choose a depcomp mode which is confused by this. + # + # We need to recreate these files for each test, as the compiler may + # overwrite some of them when testing with obscure command lines. + # This happens at least with the AIX C compiler. + : > sub/conftest.c + for i in 1 2 3 4 5 6; do + echo '#include "conftst'$i'.h"' >> sub/conftest.c + # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with + # Solaris 10 /bin/sh. + echo '/* dummy */' > sub/conftst$i.h + done + echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf + + # We check with '-c' and '-o' for the sake of the "dashmstdout" + # mode. It turns out that the SunPro C++ compiler does not properly + # handle '-M -o', and we need to detect this. Also, some Intel + # versions had trouble with output in subdirs. + am__obj=sub/conftest.${OBJEXT-o} + am__minus_obj="-o $am__obj" + case $depmode in + gcc) + # This depmode causes a compiler race in universal mode. + test "$am__universal" = false || continue + ;; + nosideeffect) + # After this tag, mechanisms are not by side-effect, so they'll + # only be used when explicitly requested. + if test "x$enable_dependency_tracking" = xyes; then + continue + else + break + fi + ;; + msvc7 | msvc7msys | msvisualcpp | msvcmsys) + # This compiler won't grok '-c -o', but also, the minuso test has + # not run yet. These depmodes are late enough in the game, and + # so weak that their functioning should not be impacted. + am__obj=conftest.${OBJEXT-o} + am__minus_obj= + ;; + none) break ;; + esac + if depmode=$depmode \ + source=sub/conftest.c object=$am__obj \ + depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \ + $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \ + >/dev/null 2>conftest.err && + grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 && + grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 && + grep $am__obj sub/conftest.Po > /dev/null 2>&1 && + ${MAKE-make} -s -f confmf > /dev/null 2>&1; then + # icc doesn't choke on unknown options, it will just issue warnings + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else + am_cv_CC_dependencies_compiler_type=$depmode + break + fi + fi + done + + cd .. + rm -rf conftest.dir +else + am_cv_CC_dependencies_compiler_type=none +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $am_cv_CC_dependencies_compiler_type" >&5 +$as_echo "$am_cv_CC_dependencies_compiler_type" >&6; } +CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type + + if + test "x$enable_dependency_tracking" != xno \ + && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then + am__fastdepCC_TRUE= + am__fastdepCC_FALSE='#' +else + am__fastdepCC_TRUE='#' + am__fastdepCC_FALSE= +fi + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CC-cc} accepts -fPIE" >&5 +$as_echo_n "checking whether ${CC-cc} accepts -fPIE... " >&6; } +if ${ac_cv_prog_cc_pie+:} false; then : + $as_echo_n "(cached) " >&6 +else + + 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* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_pie" >&5 +$as_echo "$ac_cv_prog_cc_pie" >&6; } + + + + + + + +# Check whether --enable-static was given. +if test "${enable_static+set}" = set; then : + enableval=$enable_static; p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_static=no +fi + + + + + + + + + +case `pwd` in + *\ * | *\ *) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&5 +$as_echo "$as_me: WARNING: Libtool does not cope well with whitespace in \`pwd\`" >&2;} ;; +esac + + + +macro_version='2.4.6' +macro_revision='2.4.6' + + + + + + + + + + + + + +ltmain=$ac_aux_dir/ltmain.sh + +# Make sure we can run config.sub. +$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 || + as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5 + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking build system type" >&5 +$as_echo_n "checking build system type... " >&6; } +if ${ac_cv_build+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_build_alias=$build_alias +test "x$ac_build_alias" = x && + ac_build_alias=`$SHELL "$ac_aux_dir/config.guess"` +test "x$ac_build_alias" = x && + as_fn_error $? "cannot guess build type; you must specify one" "$LINENO" 5 +ac_cv_build=`$SHELL "$ac_aux_dir/config.sub" $ac_build_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $ac_build_alias failed" "$LINENO" 5 + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_build" >&5 +$as_echo "$ac_cv_build" >&6; } +case $ac_cv_build in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical build" "$LINENO" 5;; +esac +build=$ac_cv_build +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_build +shift +build_cpu=$1 +build_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +build_os=$* +IFS=$ac_save_IFS +case $build_os in *\ *) build_os=`echo "$build_os" | sed 's/ /-/g'`;; esac + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking host system type" >&5 +$as_echo_n "checking host system type... " >&6; } +if ${ac_cv_host+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test "x$host_alias" = x; then + ac_cv_host=$ac_cv_build +else + ac_cv_host=`$SHELL "$ac_aux_dir/config.sub" $host_alias` || + as_fn_error $? "$SHELL $ac_aux_dir/config.sub $host_alias failed" "$LINENO" 5 +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_host" >&5 +$as_echo "$ac_cv_host" >&6; } +case $ac_cv_host in +*-*-*) ;; +*) as_fn_error $? "invalid value of canonical host" "$LINENO" 5;; +esac +host=$ac_cv_host +ac_save_IFS=$IFS; IFS='-' +set x $ac_cv_host +shift +host_cpu=$1 +host_vendor=$2 +shift; shift +# Remember, the first character of IFS is used to create $*, +# except with old shells: +host_os=$* +IFS=$ac_save_IFS +case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac + + +# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\(["`$\\]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\(["`\\]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' + +ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to print strings" >&5 +$as_echo_n "checking how to print strings... " >&6; } +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "" +} + +case $ECHO in + printf*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: printf" >&5 +$as_echo "printf" >&6; } ;; + print*) { $as_echo "$as_me:${as_lineno-$LINENO}: result: print -r" >&5 +$as_echo "print -r" >&6; } ;; + *) { $as_echo "$as_me:${as_lineno-$LINENO}: result: cat" >&5 +$as_echo "cat" >&6; } ;; +esac + + + + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a sed that does not truncate output" >&5 +$as_echo_n "checking for a sed that does not truncate output... " >&6; } +if ${ac_cv_path_SED+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for ac_i in 1 2 3 4 5 6 7; do + ac_script="$ac_script$as_nl$ac_script" + done + echo "$ac_script" 2>/dev/null | sed 99q >conftest.sed + { ac_script=; unset ac_script;} + if test -z "$SED"; then + ac_path_SED_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_SED="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_SED" || continue +# Check for GNU ac_path_SED and select it if it is found. + # Check for GNU $ac_path_SED +case `"$ac_path_SED" --version 2>&1` in +*GNU*) + ac_cv_path_SED="$ac_path_SED" ac_path_SED_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo '' >> "conftest.nl" + "$ac_path_SED" -f conftest.sed < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_SED_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_SED="$ac_path_SED" + ac_path_SED_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_SED_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_SED"; then + as_fn_error $? "no acceptable sed could be found in \$PATH" "$LINENO" 5 + fi +else + ac_cv_path_SED=$SED +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_SED" >&5 +$as_echo "$ac_cv_path_SED" >&6; } + SED="$ac_cv_path_SED" + rm -f conftest.sed + +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 +$as_echo_n "checking for grep that handles long lines and -e... " >&6; } +if ${ac_cv_path_GREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$GREP"; then + ac_path_GREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_GREP" || continue +# Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP +case `"$ac_path_GREP" --version 2>&1` in +*GNU*) + ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'GREP' >> "conftest.nl" + "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_GREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_GREP="$ac_path_GREP" + ac_path_GREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_GREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_GREP"; then + as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_GREP=$GREP +fi + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 +$as_echo "$ac_cv_path_GREP" >&6; } + GREP="$ac_cv_path_GREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 +$as_echo_n "checking for egrep... " >&6; } +if ${ac_cv_path_EGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 + then ac_cv_path_EGREP="$GREP -E" + else + if test -z "$EGREP"; then + ac_path_EGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_EGREP" || continue +# Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP +case `"$ac_path_EGREP" --version 2>&1` in +*GNU*) + ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'EGREP' >> "conftest.nl" + "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_EGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_EGREP="$ac_path_EGREP" + ac_path_EGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_EGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_EGREP"; then + as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_EGREP=$EGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 +$as_echo "$ac_cv_path_EGREP" >&6; } + EGREP="$ac_cv_path_EGREP" + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for fgrep" >&5 +$as_echo_n "checking for fgrep... " >&6; } +if ${ac_cv_path_FGREP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if echo 'ab*c' | $GREP -F 'ab*c' >/dev/null 2>&1 + then ac_cv_path_FGREP="$GREP -F" + else + if test -z "$FGREP"; then + ac_path_FGREP_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in fgrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_FGREP="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_FGREP" || continue +# Check for GNU ac_path_FGREP and select it if it is found. + # Check for GNU $ac_path_FGREP +case `"$ac_path_FGREP" --version 2>&1` in +*GNU*) + ac_cv_path_FGREP="$ac_path_FGREP" ac_path_FGREP_found=:;; +*) + ac_count=0 + $as_echo_n 0123456789 >"conftest.in" + while : + do + cat "conftest.in" "conftest.in" >"conftest.tmp" + mv "conftest.tmp" "conftest.in" + cp "conftest.in" "conftest.nl" + $as_echo 'FGREP' >> "conftest.nl" + "$ac_path_FGREP" FGREP < "conftest.nl" >"conftest.out" 2>/dev/null || break + diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break + as_fn_arith $ac_count + 1 && ac_count=$as_val + if test $ac_count -gt ${ac_path_FGREP_max-0}; then + # Best one so far, save it but keep looking for a better one + ac_cv_path_FGREP="$ac_path_FGREP" + ac_path_FGREP_max=$ac_count + fi + # 10*(2^10) chars as input seems more than enough + test $ac_count -gt 10 && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out;; +esac + + $ac_path_FGREP_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_FGREP"; then + as_fn_error $? "no acceptable fgrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + fi +else + ac_cv_path_FGREP=$FGREP +fi + + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_FGREP" >&5 +$as_echo "$ac_cv_path_FGREP" >&6; } + FGREP="$ac_cv_path_FGREP" + + +test -z "$GREP" && GREP=grep + + + + + + + + + + + + + + + + + + + +# Check whether --with-gnu-ld was given. +if test "${with_gnu_ld+set}" = set; then : + withval=$with_gnu_ld; test no = "$withval" || with_gnu_ld=yes +else + with_gnu_ld=no +fi + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ld used by $CC" >&5 +$as_echo_n "checking for ld used by $CC... " >&6; } + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [\\/]* | ?:[\\/]*) + re_direlt='/[^/][^/]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GNU ld" >&5 +$as_echo_n "checking for GNU ld... " >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for non-GNU ld" >&5 +$as_echo_n "checking for non-GNU ld... " >&6; } +fi +if ${lt_cv_path_LD+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &5 +$as_echo "$LD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +test -z "$LD" && as_fn_error $? "no acceptable ld found in \$PATH" "$LINENO" 5 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if the linker ($LD) is GNU ld" >&5 +$as_echo_n "checking if the linker ($LD) is GNU ld... " >&6; } +if ${lt_cv_prog_gnu_ld+:} false; then : + $as_echo_n "(cached) " >&6 +else + # I'd rather use --version here, but apparently some GNU lds only accept -v. +case `$LD -v 2>&1 &5 +$as_echo "$lt_cv_prog_gnu_ld" >&6; } +with_gnu_ld=$lt_cv_prog_gnu_ld + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for BSD- or MS-compatible name lister (nm)" >&5 +$as_echo_n "checking for BSD- or MS-compatible name lister (nm)... " >&6; } +if ${lt_cv_path_NM+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM=$NM +else + lt_nm_to_check=${ac_tool_prefix}nm + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + tmp_nm=$ac_dir/$lt_tmp_nm + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the 'sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty + case $build_os in + mingw*) lt_bad_file=conftest.nm/nofile ;; + *) lt_bad_file=/dev/null ;; + esac + case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in + *$lt_bad_file* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break 2 + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break 2 + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS=$lt_save_ifs + done + : ${lt_cv_path_NM=no} +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_NM" >&5 +$as_echo "$lt_cv_path_NM" >&6; } +if test no != "$lt_cv_path_NM"; then + NM=$lt_cv_path_NM +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + if test -n "$ac_tool_prefix"; then + for ac_prog in dumpbin "link -dump" + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DUMPBIN+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DUMPBIN"; then + ac_cv_prog_DUMPBIN="$DUMPBIN" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DUMPBIN="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DUMPBIN=$ac_cv_prog_DUMPBIN +if test -n "$DUMPBIN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DUMPBIN" >&5 +$as_echo "$DUMPBIN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$DUMPBIN" && break + done +fi +if test -z "$DUMPBIN"; then + ac_ct_DUMPBIN=$DUMPBIN + for ac_prog in dumpbin "link -dump" +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DUMPBIN+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DUMPBIN"; then + ac_cv_prog_ac_ct_DUMPBIN="$ac_ct_DUMPBIN" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DUMPBIN="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DUMPBIN=$ac_cv_prog_ac_ct_DUMPBIN +if test -n "$ac_ct_DUMPBIN"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DUMPBIN" >&5 +$as_echo "$ac_ct_DUMPBIN" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_DUMPBIN" && break +done + + if test "x$ac_ct_DUMPBIN" = x; then + DUMPBIN=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DUMPBIN=$ac_ct_DUMPBIN + fi +fi + + case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols -headers" + ;; + *) + DUMPBIN=: + ;; + esac + fi + + if test : != "$DUMPBIN"; then + NM=$DUMPBIN + fi +fi +test -z "$NM" && NM=nm + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the name lister ($NM) interface" >&5 +$as_echo_n "checking the name lister ($NM) interface... " >&6; } +if ${lt_cv_nm_interface+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&5) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&5 + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&5) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&5 + (eval echo "\"\$as_me:$LINENO: output\"" >&5) + cat conftest.out >&5 + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_nm_interface" >&5 +$as_echo "$lt_cv_nm_interface" >&6; } + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ln -s works" >&5 +$as_echo_n "checking whether ln -s works... " >&6; } +LN_S=$as_ln_s +if test "$LN_S" = "ln -s"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no, using $LN_S" >&5 +$as_echo "no, using $LN_S" >&6; } +fi + +# find the maximum length of command line arguments +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking the maximum length of command line arguments" >&5 +$as_echo_n "checking the maximum length of command line arguments... " >&6; } +if ${lt_cv_sys_max_cmd_len+:} false; then : + $as_echo_n "(cached) " >&6 +else + i=0 + teststring=ABCD + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[ ]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len" && \ + test undefined != "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test X`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test 17 != "$i" # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac + +fi + +if test -n "$lt_cv_sys_max_cmd_len"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sys_max_cmd_len" >&5 +$as_echo "$lt_cv_sys_max_cmd_len" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none" >&5 +$as_echo "none" >&6; } +fi +max_cmd_len=$lt_cv_sys_max_cmd_len + + + + + + +: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} + +if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi + + + + + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to $host format" >&5 +$as_echo_n "checking how to convert $build file names to $host format... " >&6; } +if ${lt_cv_to_host_file_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac + +fi + +to_host_file_cmd=$lt_cv_to_host_file_cmd +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_host_file_cmd" >&5 +$as_echo "$lt_cv_to_host_file_cmd" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to convert $build file names to toolchain format" >&5 +$as_echo_n "checking how to convert $build file names to toolchain format... " >&6; } +if ${lt_cv_to_tool_file_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + #assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac + +fi + +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_to_tool_file_cmd" >&5 +$as_echo "$lt_cv_to_tool_file_cmd" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $LD option to reload object files" >&5 +$as_echo_n "checking for $LD option to reload object files... " >&6; } +if ${lt_cv_ld_reload_flag+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_reload_flag='-r' +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_reload_flag" >&5 +$as_echo "$lt_cv_ld_reload_flag" >&6; } +reload_flag=$lt_cv_ld_reload_flag +case $reload_flag in +"" | " "*) ;; +*) reload_flag=" $reload_flag" ;; +esac +reload_cmds='$LD$reload_flag -o $output$reload_objs' +case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + if test yes != "$GCC"; then + reload_cmds=false + fi + ;; + darwin*) + if test yes = "$GCC"; then + reload_cmds='$LTCC $LTCFLAGS -nostdlib $wl-r -o $output$reload_objs' + else + reload_cmds='$LD$reload_flag -o $output$reload_objs' + fi + ;; +esac + + + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}objdump", so it can be a program name with args. +set dummy ${ac_tool_prefix}objdump; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OBJDUMP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OBJDUMP"; then + ac_cv_prog_OBJDUMP="$OBJDUMP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OBJDUMP="${ac_tool_prefix}objdump" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OBJDUMP=$ac_cv_prog_OBJDUMP +if test -n "$OBJDUMP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OBJDUMP" >&5 +$as_echo "$OBJDUMP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OBJDUMP"; then + ac_ct_OBJDUMP=$OBJDUMP + # Extract the first word of "objdump", so it can be a program name with args. +set dummy objdump; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OBJDUMP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OBJDUMP"; then + ac_cv_prog_ac_ct_OBJDUMP="$ac_ct_OBJDUMP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OBJDUMP="objdump" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OBJDUMP=$ac_cv_prog_ac_ct_OBJDUMP +if test -n "$ac_ct_OBJDUMP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OBJDUMP" >&5 +$as_echo "$ac_ct_OBJDUMP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OBJDUMP" = x; then + OBJDUMP="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OBJDUMP=$ac_ct_OBJDUMP + fi +else + OBJDUMP="$ac_cv_prog_OBJDUMP" +fi + +test -z "$OBJDUMP" && OBJDUMP=objdump + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to recognize dependent libraries" >&5 +$as_echo_n "checking how to recognize dependent libraries... " >&6; } +if ${lt_cv_deplibs_check_method+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# 'unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# that responds to the $file_magic_cmd with a given extended regex. +# If you have 'file' or equivalent on your system and you're not sure +# whether 'pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[4-9]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[45]*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[3-9]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF-[0-9][0-9]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]' + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9]\.[0-9]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[3-9]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd* | bitrig*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[^/]+(\.so\.[0-9]+\.[0-9]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +os2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_deplibs_check_method" >&5 +$as_echo "$lt_cv_deplibs_check_method" >&6; } + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[\1]\/[\1]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + + + + + + + + + + + + + + + + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dlltool", so it can be a program name with args. +set dummy ${ac_tool_prefix}dlltool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DLLTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DLLTOOL"; then + ac_cv_prog_DLLTOOL="$DLLTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DLLTOOL="${ac_tool_prefix}dlltool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DLLTOOL=$ac_cv_prog_DLLTOOL +if test -n "$DLLTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DLLTOOL" >&5 +$as_echo "$DLLTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DLLTOOL"; then + ac_ct_DLLTOOL=$DLLTOOL + # Extract the first word of "dlltool", so it can be a program name with args. +set dummy dlltool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DLLTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DLLTOOL"; then + ac_cv_prog_ac_ct_DLLTOOL="$ac_ct_DLLTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DLLTOOL="dlltool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DLLTOOL=$ac_cv_prog_ac_ct_DLLTOOL +if test -n "$ac_ct_DLLTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DLLTOOL" >&5 +$as_echo "$ac_ct_DLLTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DLLTOOL" = x; then + DLLTOOL="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DLLTOOL=$ac_ct_DLLTOOL + fi +else + DLLTOOL="$ac_cv_prog_DLLTOOL" +fi + +test -z "$DLLTOOL" && DLLTOOL=dlltool + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to associate runtime and link libraries" >&5 +$as_echo_n "checking how to associate runtime and link libraries... " >&6; } +if ${lt_cv_sharedlib_from_linklib_cmd+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh; + # decide which one to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd=$ECHO + ;; +esac + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_sharedlib_from_linklib_cmd" >&5 +$as_echo "$lt_cv_sharedlib_from_linklib_cmd" >&6; } +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + + + + + + + +if test -n "$ac_tool_prefix"; then + for ac_prog in ar + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$AR"; then + ac_cv_prog_AR="$AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AR="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +AR=$ac_cv_prog_AR +if test -n "$AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AR" >&5 +$as_echo "$AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$AR" && break + done +fi +if test -z "$AR"; then + ac_ct_AR=$AR + for ac_prog in ar +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_AR+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_AR"; then + ac_cv_prog_ac_ct_AR="$ac_ct_AR" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_AR="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_AR=$ac_cv_prog_ac_ct_AR +if test -n "$ac_ct_AR"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_AR" >&5 +$as_echo "$ac_ct_AR" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_AR" && break +done + + if test "x$ac_ct_AR" = x; then + AR="false" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + AR=$ac_ct_AR + fi +fi + +: ${AR=ar} +: ${AR_FLAGS=cru} + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for archiver @FILE support" >&5 +$as_echo_n "checking for archiver @FILE support... " >&6; } +if ${lt_cv_ar_at_file+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ar_at_file=no + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&5' + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 + (eval $lt_ar_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if test 0 -eq "$ac_status"; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$lt_ar_try\""; } >&5 + (eval $lt_ar_try) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if test 0 -ne "$ac_status"; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ar_at_file" >&5 +$as_echo "$lt_cv_ar_at_file" >&6; } + +if test no = "$lt_cv_ar_at_file"; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi + + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args. +set dummy ${ac_tool_prefix}strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$STRIP"; then + ac_cv_prog_STRIP="$STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_STRIP="${ac_tool_prefix}strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +STRIP=$ac_cv_prog_STRIP +if test -n "$STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $STRIP" >&5 +$as_echo "$STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_STRIP"; then + ac_ct_STRIP=$STRIP + # Extract the first word of "strip", so it can be a program name with args. +set dummy strip; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_STRIP+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_STRIP"; then + ac_cv_prog_ac_ct_STRIP="$ac_ct_STRIP" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_STRIP="strip" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_STRIP=$ac_cv_prog_ac_ct_STRIP +if test -n "$ac_ct_STRIP"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_STRIP" >&5 +$as_echo "$ac_ct_STRIP" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_STRIP" = x; then + STRIP=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + STRIP=$ac_ct_STRIP + fi +else + STRIP="$ac_cv_prog_STRIP" +fi + +test -z "$STRIP" && STRIP=: + + + + + + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args. +set dummy ${ac_tool_prefix}ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$RANLIB"; then + ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +RANLIB=$ac_cv_prog_RANLIB +if test -n "$RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RANLIB" >&5 +$as_echo "$RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_RANLIB"; then + ac_ct_RANLIB=$RANLIB + # Extract the first word of "ranlib", so it can be a program name with args. +set dummy ranlib; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_RANLIB+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_RANLIB"; then + ac_cv_prog_ac_ct_RANLIB="$ac_ct_RANLIB" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_RANLIB=$ac_cv_prog_ac_ct_RANLIB +if test -n "$ac_ct_RANLIB"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_RANLIB" >&5 +$as_echo "$ac_ct_RANLIB" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_RANLIB" = x; then + RANLIB=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + RANLIB=$ac_ct_RANLIB + fi +else + RANLIB="$ac_cv_prog_RANLIB" +fi + +test -z "$RANLIB" && RANLIB=: + + + + + + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + bitrig* | openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + + +# Check for command to grab the raw symbol name followed by C symbol from nm. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking command to parse $NM output from $compiler object" >&5 +$as_echo_n "checking command to parse $NM output from $compiler object... " >&6; } +if ${lt_cv_sys_global_symbol_pipe+:} false; then : + $as_echo_n "(cached) " >&6 +else + +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[BCDEGRST]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([_A-Za-z][_A-Za-z0-9]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[BCDT]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[ABCDGISTW]' + ;; +hpux*) + if test ia64 = "$host_cpu"; then + symcode='[ABCDEGRST]' + fi + ;; +irix* | nonstopux*) + symcode='[BCDEGRST]' + ;; +osf*) + symcode='[BCDEGQRST]' + ;; +solaris*) + symcode='[BDRT]' + ;; +sco3.2v5*) + symcode='[DT]' + ;; +sysv4.2uw2*) + symcode='[DT]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[ABDT]' + ;; +sysv4) + symcode='[DFNSTU]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[ABCDGIRSTW]' ;; +esac + +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Gets list of data symbols to import. + lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" + # Adjust the below global symbol transforms to fixup imported variables. + lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" + lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" + lt_c_name_lib_hook="\ + -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ + -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" +else + # Disable hooks by default. + lt_cv_sys_global_symbol_to_import= + lt_cdecl_hook= + lt_c_name_hook= + lt_c_name_lib_hook= +fi + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n"\ +$lt_cdecl_hook\ +" -e 's/^T .* \(.*\)$/extern int \1();/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ +$lt_c_name_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" + +# Transform an extracted symbol line into symbol name with lib prefix and +# symbol address. +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ +$lt_c_name_lib_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function, + # D for any global variable and I for any imported variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK '"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ +" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ +" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ +" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ +" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + # Now try to grab the symbols. + nlist=conftest.nm + $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&5 + if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&5 && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT_DLSYM_CONST +#else +# define LT_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS=conftstm.$ac_objext + CFLAGS="$CFLAGS$lt_prog_compiler_no_builtin_flag" + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s conftest$ac_exeext; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&5 + fi + else + echo "cannot find nm_test_var in $nlist" >&5 + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5 + fi + else + echo "$progname: failed program was:" >&5 + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test yes = "$pipe_works"; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done + +fi + +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5 +$as_echo "failed" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ok" >&5 +$as_echo "ok" >&6; } +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[@]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sysroot" >&5 +$as_echo_n "checking for sysroot... " >&6; } + +# Check whether --with-sysroot was given. +if test "${with_sysroot+set}" = set; then : + withval=$with_sysroot; +else + with_sysroot=no +fi + + +lt_sysroot= +case $with_sysroot in #( + yes) + if test yes = "$GCC"; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_sysroot" >&5 +$as_echo "$with_sysroot" >&6; } + as_fn_error $? "The sysroot must be an absolute path." "$LINENO" 5 + ;; +esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${lt_sysroot:-no}" >&5 +$as_echo "${lt_sysroot:-no}" >&6; } + + + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for a working dd" >&5 +$as_echo_n "checking for a working dd... " >&6; } +if ${ac_cv_path_lt_DD+:} false; then : + $as_echo_n "(cached) " >&6 +else + printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +: ${lt_DD:=$DD} +if test -z "$lt_DD"; then + ac_path_lt_DD_found=false + # Loop through the user's path and test for each of PROGNAME-LIST + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_prog in dd; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_lt_DD="$as_dir/$ac_prog$ac_exec_ext" + as_fn_executable_p "$ac_path_lt_DD" || continue +if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: +fi + $ac_path_lt_DD_found && break 3 + done + done + done +IFS=$as_save_IFS + if test -z "$ac_cv_path_lt_DD"; then + : + fi +else + ac_cv_path_lt_DD=$lt_DD +fi + +rm -f conftest.i conftest2.i conftest.out +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_lt_DD" >&5 +$as_echo "$ac_cv_path_lt_DD" >&6; } + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to truncate binary pipes" >&5 +$as_echo_n "checking how to truncate binary pipes... " >&6; } +if ${lt_cv_truncate_bin+:} false; then : + $as_echo_n "(cached) " >&6 +else + printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +lt_cv_truncate_bin= +if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" +fi +rm -f conftest.i conftest2.i conftest.out +test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q" +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_truncate_bin" >&5 +$as_echo "$lt_cv_truncate_bin" >&6; } + + + + + + + +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in $*""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} + +# Check whether --enable-libtool-lock was given. +if test "${enable_libtool_lock+set}" = set; then : + enableval=$enable_libtool_lock; +fi + +test no = "$enable_libtool_lock" || enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out what ABI is being produced by ac_compile, and set mode + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE=32 + ;; + *ELF-64*) + HPUX_IA64_MODE=64 + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '#line '$LINENO' "configure"' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + if test yes = "$lt_cv_prog_gnu_ld"; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +mips64*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '#line '$LINENO' "configure"' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + emul=elf + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + emul="${emul}32" + ;; + *64-bit*) + emul="${emul}64" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *MSB*) + emul="${emul}btsmip" + ;; + *LSB*) + emul="${emul}ltsmip" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *N32*) + emul="${emul}n32" + ;; + esac + LD="${LD-ld} -m $emul" + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. Note that the listed cases only cover the + # situations where additional linker options are needed (such as when + # doing 32-bit compilation for a host where ld defaults to 64-bit, or + # vice versa); the common cases where no linker options are needed do + # not appear in the list. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + case `/usr/bin/file conftest.o` in + *x86-64*) + LD="${LD-ld} -m elf32_x86_64" + ;; + *) + LD="${LD-ld} -m elf_i386" + ;; + esac + ;; + powerpc64le-*linux*) + LD="${LD-ld} -m elf32lppclinux" + ;; + powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + powerpcle-*linux*) + LD="${LD-ld} -m elf64lppc" + ;; + powerpc-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -belf" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler needs -belf" >&5 +$as_echo_n "checking whether the C compiler needs -belf... " >&6; } +if ${lt_cv_cc_needs_belf+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_cc_needs_belf=yes +else + lt_cv_cc_needs_belf=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_cc_needs_belf" >&5 +$as_echo "$lt_cv_cc_needs_belf" >&6; } + if test yes != "$lt_cv_cc_needs_belf"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS=$SAVE_CFLAGS + fi + ;; +*-*solaris*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*|x86_64-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD=${LD-ld}_sol2 + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks=$enable_libtool_lock + +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}mt", so it can be a program name with args. +set dummy ${ac_tool_prefix}mt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_MANIFEST_TOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$MANIFEST_TOOL"; then + ac_cv_prog_MANIFEST_TOOL="$MANIFEST_TOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_MANIFEST_TOOL="${ac_tool_prefix}mt" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +MANIFEST_TOOL=$ac_cv_prog_MANIFEST_TOOL +if test -n "$MANIFEST_TOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MANIFEST_TOOL" >&5 +$as_echo "$MANIFEST_TOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_MANIFEST_TOOL"; then + ac_ct_MANIFEST_TOOL=$MANIFEST_TOOL + # Extract the first word of "mt", so it can be a program name with args. +set dummy mt; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_MANIFEST_TOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_MANIFEST_TOOL"; then + ac_cv_prog_ac_ct_MANIFEST_TOOL="$ac_ct_MANIFEST_TOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_MANIFEST_TOOL="mt" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_MANIFEST_TOOL=$ac_cv_prog_ac_ct_MANIFEST_TOOL +if test -n "$ac_ct_MANIFEST_TOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_MANIFEST_TOOL" >&5 +$as_echo "$ac_ct_MANIFEST_TOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_MANIFEST_TOOL" = x; then + MANIFEST_TOOL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + MANIFEST_TOOL=$ac_ct_MANIFEST_TOOL + fi +else + MANIFEST_TOOL="$ac_cv_prog_MANIFEST_TOOL" +fi + +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $MANIFEST_TOOL is a manifest tool" >&5 +$as_echo_n "checking if $MANIFEST_TOOL is a manifest tool... " >&6; } +if ${lt_cv_path_mainfest_tool+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&5 + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&5 + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest* +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_path_mainfest_tool" >&5 +$as_echo "$lt_cv_path_mainfest_tool" >&6; } +if test yes != "$lt_cv_path_mainfest_tool"; then + MANIFEST_TOOL=: +fi + + + + + + + case $host_os in + rhapsody* | darwin*) + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}dsymutil", so it can be a program name with args. +set dummy ${ac_tool_prefix}dsymutil; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_DSYMUTIL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$DSYMUTIL"; then + ac_cv_prog_DSYMUTIL="$DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_DSYMUTIL="${ac_tool_prefix}dsymutil" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +DSYMUTIL=$ac_cv_prog_DSYMUTIL +if test -n "$DSYMUTIL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $DSYMUTIL" >&5 +$as_echo "$DSYMUTIL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_DSYMUTIL"; then + ac_ct_DSYMUTIL=$DSYMUTIL + # Extract the first word of "dsymutil", so it can be a program name with args. +set dummy dsymutil; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_DSYMUTIL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_DSYMUTIL"; then + ac_cv_prog_ac_ct_DSYMUTIL="$ac_ct_DSYMUTIL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_DSYMUTIL="dsymutil" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_DSYMUTIL=$ac_cv_prog_ac_ct_DSYMUTIL +if test -n "$ac_ct_DSYMUTIL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_DSYMUTIL" >&5 +$as_echo "$ac_ct_DSYMUTIL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_DSYMUTIL" = x; then + DSYMUTIL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + DSYMUTIL=$ac_ct_DSYMUTIL + fi +else + DSYMUTIL="$ac_cv_prog_DSYMUTIL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}nmedit", so it can be a program name with args. +set dummy ${ac_tool_prefix}nmedit; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_NMEDIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$NMEDIT"; then + ac_cv_prog_NMEDIT="$NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_NMEDIT="${ac_tool_prefix}nmedit" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +NMEDIT=$ac_cv_prog_NMEDIT +if test -n "$NMEDIT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NMEDIT" >&5 +$as_echo "$NMEDIT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_NMEDIT"; then + ac_ct_NMEDIT=$NMEDIT + # Extract the first word of "nmedit", so it can be a program name with args. +set dummy nmedit; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_NMEDIT+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_NMEDIT"; then + ac_cv_prog_ac_ct_NMEDIT="$ac_ct_NMEDIT" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_NMEDIT="nmedit" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_NMEDIT=$ac_cv_prog_ac_ct_NMEDIT +if test -n "$ac_ct_NMEDIT"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NMEDIT" >&5 +$as_echo "$ac_ct_NMEDIT" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_NMEDIT" = x; then + NMEDIT=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + NMEDIT=$ac_ct_NMEDIT + fi +else + NMEDIT="$ac_cv_prog_NMEDIT" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}lipo", so it can be a program name with args. +set dummy ${ac_tool_prefix}lipo; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_LIPO+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$LIPO"; then + ac_cv_prog_LIPO="$LIPO" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_LIPO="${ac_tool_prefix}lipo" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +LIPO=$ac_cv_prog_LIPO +if test -n "$LIPO"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LIPO" >&5 +$as_echo "$LIPO" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_LIPO"; then + ac_ct_LIPO=$LIPO + # Extract the first word of "lipo", so it can be a program name with args. +set dummy lipo; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_LIPO+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_LIPO"; then + ac_cv_prog_ac_ct_LIPO="$ac_ct_LIPO" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_LIPO="lipo" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_LIPO=$ac_cv_prog_ac_ct_LIPO +if test -n "$ac_ct_LIPO"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_LIPO" >&5 +$as_echo "$ac_ct_LIPO" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_LIPO" = x; then + LIPO=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + LIPO=$ac_ct_LIPO + fi +else + LIPO="$ac_cv_prog_LIPO" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}otool", so it can be a program name with args. +set dummy ${ac_tool_prefix}otool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OTOOL"; then + ac_cv_prog_OTOOL="$OTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OTOOL="${ac_tool_prefix}otool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OTOOL=$ac_cv_prog_OTOOL +if test -n "$OTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL" >&5 +$as_echo "$OTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OTOOL"; then + ac_ct_OTOOL=$OTOOL + # Extract the first word of "otool", so it can be a program name with args. +set dummy otool; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OTOOL+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OTOOL"; then + ac_cv_prog_ac_ct_OTOOL="$ac_ct_OTOOL" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OTOOL="otool" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OTOOL=$ac_cv_prog_ac_ct_OTOOL +if test -n "$ac_ct_OTOOL"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL" >&5 +$as_echo "$ac_ct_OTOOL" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OTOOL" = x; then + OTOOL=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OTOOL=$ac_ct_OTOOL + fi +else + OTOOL="$ac_cv_prog_OTOOL" +fi + + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}otool64", so it can be a program name with args. +set dummy ${ac_tool_prefix}otool64; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_OTOOL64+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$OTOOL64"; then + ac_cv_prog_OTOOL64="$OTOOL64" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_OTOOL64="${ac_tool_prefix}otool64" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +OTOOL64=$ac_cv_prog_OTOOL64 +if test -n "$OTOOL64"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $OTOOL64" >&5 +$as_echo "$OTOOL64" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_OTOOL64"; then + ac_ct_OTOOL64=$OTOOL64 + # Extract the first word of "otool64", so it can be a program name with args. +set dummy otool64; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_OTOOL64+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_OTOOL64"; then + ac_cv_prog_ac_ct_OTOOL64="$ac_ct_OTOOL64" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_OTOOL64="otool64" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_OTOOL64=$ac_cv_prog_ac_ct_OTOOL64 +if test -n "$ac_ct_OTOOL64"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_OTOOL64" >&5 +$as_echo "$ac_ct_OTOOL64" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_OTOOL64" = x; then + OTOOL64=":" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + OTOOL64=$ac_ct_OTOOL64 + fi +else + OTOOL64="$ac_cv_prog_OTOOL64" +fi + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -single_module linker flag" >&5 +$as_echo_n "checking for -single_module linker flag... " >&6; } +if ${lt_cv_apple_cc_single_mod+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_apple_cc_single_mod=no + if test -z "$LT_MULTI_MODULE"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&5 + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&5 + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test 0 = "$_lt_result"; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&5 + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_apple_cc_single_mod" >&5 +$as_echo "$lt_cv_apple_cc_single_mod" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -exported_symbols_list linker flag" >&5 +$as_echo_n "checking for -exported_symbols_list linker flag... " >&6; } +if ${lt_cv_ld_exported_symbols_list+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_ld_exported_symbols_list=yes +else + lt_cv_ld_exported_symbols_list=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_exported_symbols_list" >&5 +$as_echo "$lt_cv_ld_exported_symbols_list" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -force_load linker flag" >&5 +$as_echo_n "checking for -force_load linker flag... " >&6; } +if ${lt_cv_ld_force_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5 + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5 + echo "$AR cru libconftest.a conftest.o" >&5 + $AR cru libconftest.a conftest.o 2>&5 + echo "$RANLIB libconftest.a" >&5 + $RANLIB libconftest.a 2>&5 + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&5 + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&5 + elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&5 + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_ld_force_load" >&5 +$as_echo "$lt_cv_ld_force_load" >&6; } + case $host_os in + rhapsody* | darwin1.[012]) + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[91]*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + 10.[012][,.]*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test yes = "$lt_cv_apple_cc_single_mod"; then + _lt_dar_single_mod='$single_module' + fi + if test yes = "$lt_cv_ld_exported_symbols_list"; then + _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' + fi + if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac + +# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x$2 in + x) + ;; + *:) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" + ;; + x:*) + eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" + ;; + *) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" + ;; + esac +} + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5 +$as_echo_n "checking how to run the C preprocessor... " >&6; } +# On Suns, sometimes $CPP names a directory. +if test -n "$CPP" && test -d "$CPP"; then + CPP= +fi +if test -z "$CPP"; then + if ${ac_cv_prog_CPP+:} false; then : + $as_echo_n "(cached) " >&6 +else + # Double quotes because CPP needs to be expanded + for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp" + do + ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + break +fi + + done + ac_cv_prog_CPP=$CPP + +fi + CPP=$ac_cv_prog_CPP +else + ac_cv_prog_CPP=$CPP +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5 +$as_echo "$CPP" >&6; } +ac_preproc_ok=false +for ac_c_preproc_warn_flag in '' yes +do + # Use a header file that comes with gcc, so configuring glibc + # with a fresh cross-compiler works. + # Prefer to if __STDC__ is defined, since + # exists even on freestanding compilers. + # On the NeXT, cc -E runs the code through the compiler's parser, + # not just through cpp. "Syntax error" is here to catch this case. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#ifdef __STDC__ +# include +#else +# include +#endif + Syntax error +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + +else + # Broken: fails on valid input. +continue +fi +rm -f conftest.err conftest.i conftest.$ac_ext + + # OK, works on sane cases. Now check whether nonexistent headers + # can be detected and how. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +_ACEOF +if ac_fn_c_try_cpp "$LINENO"; then : + # Broken: success on invalid input. +continue +else + # Passes both tests. +ac_preproc_ok=: +break +fi +rm -f conftest.err conftest.i conftest.$ac_ext + +done +# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. +rm -f conftest.i conftest.err conftest.$ac_ext +if $ac_preproc_ok; then : + +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "C preprocessor \"$CPP\" fails sanity check +See \`config.log' for more details" "$LINENO" 5; } +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 +$as_echo_n "checking for ANSI C header files... " >&6; } +if ${ac_cv_header_stdc+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#include +#include + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_header_stdc=yes +else + ac_cv_header_stdc=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + +if test $ac_cv_header_stdc = yes; then + # SunOS 4.x string.h does not declare mem*, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "memchr" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "free" >/dev/null 2>&1; then : + +else + ac_cv_header_stdc=no +fi +rm -f conftest* + +fi + +if test $ac_cv_header_stdc = yes; then + # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. + if test "$cross_compiling" = yes; then : + : +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +#include +#if ((' ' & 0x0FF) == 0x020) +# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') +# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) +#else +# define ISLOWER(c) \ + (('a' <= (c) && (c) <= 'i') \ + || ('j' <= (c) && (c) <= 'r') \ + || ('s' <= (c) && (c) <= 'z')) +# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) +#endif + +#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +int +main () +{ + int i; + for (i = 0; i < 256; i++) + if (XOR (islower (i), ISLOWER (i)) + || toupper (i) != TOUPPER (i)) + return 2; + return 0; +} +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + +else + ac_cv_header_stdc=no +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + +fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 +$as_echo "$ac_cv_header_stdc" >&6; } +if test $ac_cv_header_stdc = yes; then + +$as_echo "#define STDC_HEADERS 1" >>confdefs.h + +fi + +# On IRIX 5.3, sys/types and inttypes.h are conflicting. +for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ + inttypes.h stdint.h unistd.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default +" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + +for ac_header in dlfcn.h +do : + ac_fn_c_check_header_compile "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default +" +if test "x$ac_cv_header_dlfcn_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_DLFCN_H 1 +_ACEOF + +fi + +done + + + + + +# Set options + + + + enable_dlopen=no + + + enable_win32_dll=no + + + # Check whether --enable-shared was given. +if test "${enable_shared+set}" = set; then : + enableval=$enable_shared; p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_shared=yes +fi + + + + + + + + + + + +# Check whether --with-pic was given. +if test "${with_pic+set}" = set; then : + withval=$with_pic; lt_p=${PACKAGE-default} + case $withval in + yes|no) pic_mode=$withval ;; + *) + pic_mode=default + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for lt_pkg in $withval; do + IFS=$lt_save_ifs + if test "X$lt_pkg" = "X$lt_p"; then + pic_mode=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + pic_mode=default +fi + + + + + + + + + # Check whether --enable-fast-install was given. +if test "${enable_fast_install+set}" = set; then : + enableval=$enable_fast_install; p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS=$lt_save_ifs + ;; + esac +else + enable_fast_install=yes +fi + + + + + + + + + shared_archive_member_spec= +case $host,$enable_shared in +power*-*-aix[5-9]*,yes) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking which variant of shared library versioning to provide" >&5 +$as_echo_n "checking which variant of shared library versioning to provide... " >&6; } + +# Check whether --with-aix-soname was given. +if test "${with_aix_soname+set}" = set; then : + withval=$with_aix_soname; case $withval in + aix|svr4|both) + ;; + *) + as_fn_error $? "Unknown argument to --with-aix-soname" "$LINENO" 5 + ;; + esac + lt_cv_with_aix_soname=$with_aix_soname +else + if ${lt_cv_with_aix_soname+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_with_aix_soname=aix +fi + + with_aix_soname=$lt_cv_with_aix_soname +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_aix_soname" >&5 +$as_echo "$with_aix_soname" >&6; } + if test aix != "$with_aix_soname"; then + # For the AIX way of multilib, we name the shared archive member + # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', + # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. + # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, + # the AIX toolchain works better with OBJECT_MODE set (default 32). + if test 64 = "${OBJECT_MODE-32}"; then + shared_archive_member_spec=shr_64 + else + shared_archive_member_spec=shr + fi + fi + ;; +*) + with_aix_soname=aix + ;; +esac + + + + + + + + + + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS=$ltmain + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +test -z "$LN_S" && LN_S="ln -s" + + + + + + + + + + + + + + +if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for objdir" >&5 +$as_echo_n "checking for objdir... " >&6; } +if ${lt_cv_objdir+:} false; then : + $as_echo_n "(cached) " >&6 +else + rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_objdir" >&5 +$as_echo "$lt_cv_objdir" >&6; } +objdir=$lt_cv_objdir + + + + + +cat >>confdefs.h <<_ACEOF +#define LT_OBJDIR "$lt_cv_objdir/" +_ACEOF + + + + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld=$lt_cv_prog_gnu_ld + +old_CC=$CC +old_CFLAGS=$CFLAGS + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +func_cc_basename $compiler +cc_basename=$func_cc_basename_result + + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${ac_tool_prefix}file" >&5 +$as_echo_n "checking for ${ac_tool_prefix}file... " >&6; } +if ${lt_cv_path_MAGIC_CMD+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/${ac_tool_prefix}file"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"${ac_tool_prefix}file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac +fi + +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + + + +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for file" >&5 +$as_echo_n "checking for file... " >&6; } +if ${lt_cv_path_MAGIC_CMD+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $MAGIC_CMD in +[\\/*] | ?:[\\/]*) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + ac_dummy="/usr/bin$PATH_SEPARATOR$PATH" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/file"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"file" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac +fi + +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $MAGIC_CMD" >&5 +$as_echo "$MAGIC_CMD" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + else + MAGIC_CMD=: + fi +fi + + fi + ;; +esac + +# Use C for the default configuration in the libtool script + +lt_save_CC=$CC +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +objext=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + + + + + + + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC + +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* + +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* + + +if test -n "$compiler"; then + +lt_prog_compiler_no_builtin_flag= + +if test yes = "$GCC"; then + case $cc_basename in + nvcc*) + lt_prog_compiler_no_builtin_flag=' -Xcompiler -fno-builtin' ;; + *) + lt_prog_compiler_no_builtin_flag=' -fno-builtin' ;; + esac + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -fno-rtti -fno-exceptions" >&5 +$as_echo_n "checking if $compiler supports -fno-rtti -fno-exceptions... " >&6; } +if ${lt_cv_prog_compiler_rtti_exceptions+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_rtti_exceptions=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="-fno-rtti -fno-exceptions" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_rtti_exceptions=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_rtti_exceptions" >&5 +$as_echo "$lt_cv_prog_compiler_rtti_exceptions" >&6; } + +if test yes = "$lt_cv_prog_compiler_rtti_exceptions"; then + lt_prog_compiler_no_builtin_flag="$lt_prog_compiler_no_builtin_flag -fno-rtti -fno-exceptions" +else + : +fi + +fi + + + + + + + lt_prog_compiler_wl= +lt_prog_compiler_pic= +lt_prog_compiler_static= + + + if test yes = "$GCC"; then + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_static='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + fi + lt_prog_compiler_pic='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + lt_prog_compiler_pic='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + lt_prog_compiler_pic='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + lt_prog_compiler_pic='-DDLL_EXPORT' + case $host_os in + os2*) + lt_prog_compiler_static='$wl-static' + ;; + esac + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + lt_prog_compiler_static= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + ;; + + interix[3-9]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + lt_prog_compiler_can_build_shared=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic=-Kconform_pic + fi + ;; + + *) + lt_prog_compiler_pic='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + lt_prog_compiler_wl='-Xlinker ' + if test -n "$lt_prog_compiler_pic"; then + lt_prog_compiler_pic="-Xcompiler $lt_prog_compiler_pic" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + lt_prog_compiler_wl='-Wl,' + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + lt_prog_compiler_static='-Bstatic' + else + lt_prog_compiler_static='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + lt_prog_compiler_pic='-fno-common' + case $cc_basename in + nagfor*) + # NAG Fortran compiler + lt_prog_compiler_wl='-Wl,-Wl,,' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + lt_prog_compiler_pic='-DDLL_EXPORT' + case $host_os in + os2*) + lt_prog_compiler_static='$wl-static' + ;; + esac + ;; + + hpux9* | hpux10* | hpux11*) + lt_prog_compiler_wl='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + lt_prog_compiler_pic='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + lt_prog_compiler_static='$wl-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + lt_prog_compiler_wl='-Wl,' + # PIC (with -KPIC) is the default. + lt_prog_compiler_static='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + # old Intel for x86_64, which still supported -KPIC. + ecc*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-static' + ;; + # flang / f18. f95 an alias for gfortran or flang on Debian + flang* | f18* | f95*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='--shared' + lt_prog_compiler_static='--static' + ;; + nagfor*) + # NAG Fortran compiler + lt_prog_compiler_wl='-Wl,-Wl,,' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + ccc*) + lt_prog_compiler_wl='-Wl,' + # All Alpha code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-qpic' + lt_prog_compiler_static='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [1-7].* | *Sun*Fortran*\ 8.[0-3]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='' + ;; + *Sun\ F* | *Sun*Fortran*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + lt_prog_compiler_wl='-Wl,' + ;; + *Intel*\ [CF]*Compiler*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fPIC' + lt_prog_compiler_static='-static' + ;; + *Portland\ Group*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-fpic' + lt_prog_compiler_static='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + lt_prog_compiler_pic='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + lt_prog_compiler_wl='-Wl,' + # All OSF/1 code is PIC. + lt_prog_compiler_static='-non_shared' + ;; + + rdos*) + lt_prog_compiler_static='-non_shared' + ;; + + solaris*) + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + lt_prog_compiler_wl='-Qoption ld ';; + *) + lt_prog_compiler_wl='-Wl,';; + esac + ;; + + sunos4*) + lt_prog_compiler_wl='-Qoption ld ' + lt_prog_compiler_pic='-PIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + lt_prog_compiler_pic='-Kconform_pic' + lt_prog_compiler_static='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_pic='-KPIC' + lt_prog_compiler_static='-Bstatic' + ;; + + unicos*) + lt_prog_compiler_wl='-Wl,' + lt_prog_compiler_can_build_shared=no + ;; + + uts4*) + lt_prog_compiler_pic='-pic' + lt_prog_compiler_static='-Bstatic' + ;; + + *) + lt_prog_compiler_can_build_shared=no + ;; + esac + fi + +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + lt_prog_compiler_pic= + ;; + *) + lt_prog_compiler_pic="$lt_prog_compiler_pic -DPIC" + ;; +esac + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $compiler option to produce PIC" >&5 +$as_echo_n "checking for $compiler option to produce PIC... " >&6; } +if ${lt_cv_prog_compiler_pic+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic=$lt_prog_compiler_pic +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic" >&5 +$as_echo "$lt_cv_prog_compiler_pic" >&6; } +lt_prog_compiler_pic=$lt_cv_prog_compiler_pic + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$lt_prog_compiler_pic"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler PIC flag $lt_prog_compiler_pic works" >&5 +$as_echo_n "checking if $compiler PIC flag $lt_prog_compiler_pic works... " >&6; } +if ${lt_cv_prog_compiler_pic_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_pic_works=no + ac_outfile=conftest.$ac_objext + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$lt_prog_compiler_pic -DPIC" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_pic_works=yes + fi + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_pic_works" >&5 +$as_echo "$lt_cv_prog_compiler_pic_works" >&6; } + +if test yes = "$lt_cv_prog_compiler_pic_works"; then + case $lt_prog_compiler_pic in + "" | " "*) ;; + *) lt_prog_compiler_pic=" $lt_prog_compiler_pic" ;; + esac +else + lt_prog_compiler_pic= + lt_prog_compiler_can_build_shared=no +fi + +fi + + + + + + + + + + + +# +# Check to make sure the static flag actually works. +# +wl=$lt_prog_compiler_wl eval lt_tmp_static_flag=\"$lt_prog_compiler_static\" +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler static flag $lt_tmp_static_flag works" >&5 +$as_echo_n "checking if $compiler static flag $lt_tmp_static_flag works... " >&6; } +if ${lt_cv_prog_compiler_static_works+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_static_works=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $lt_tmp_static_flag" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler_static_works=yes + fi + else + lt_cv_prog_compiler_static_works=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_static_works" >&5 +$as_echo "$lt_cv_prog_compiler_static_works" >&6; } + +if test yes = "$lt_cv_prog_compiler_static_works"; then + : +else + lt_prog_compiler_static= +fi + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $compiler supports -c -o file.$ac_objext" >&5 +$as_echo_n "checking if $compiler supports -c -o file.$ac_objext... " >&6; } +if ${lt_cv_prog_compiler_c_o+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler_c_o=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [^ ]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&5) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + lt_cv_prog_compiler_c_o=yes + fi + fi + chmod u+w . 2>&5 + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler_c_o" >&5 +$as_echo "$lt_cv_prog_compiler_c_o" >&6; } + + + + +hard_links=nottested +if test no = "$lt_cv_prog_compiler_c_o" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if we can lock with hard links" >&5 +$as_echo_n "checking if we can lock with hard links... " >&6; } + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $hard_links" >&5 +$as_echo "$hard_links" >&6; } + if test no = "$hard_links"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&5 +$as_echo "$as_me: WARNING: '$CC' does not support '-c -o', so 'make -j' may be unsafe" >&2;} + need_locks=warn + fi +else + need_locks=no +fi + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $compiler linker ($LD) supports shared libraries" >&5 +$as_echo_n "checking whether the $compiler linker ($LD) supports shared libraries... " >&6; } + + runpath_var= + allow_undefined_flag= + always_export_symbols=no + archive_cmds= + archive_expsym_cmds= + compiler_needs_object=no + enable_shared_with_static_runtimes=no + export_dynamic_flag_spec= + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + hardcode_automatic=no + hardcode_direct=no + hardcode_direct_absolute=no + hardcode_libdir_flag_spec= + hardcode_libdir_separator= + hardcode_minus_L=no + hardcode_shlibpath_var=unsupported + inherit_rpath=no + link_all_deplibs=unknown + module_cmds= + module_expsym_cmds= + old_archive_from_new_cmds= + old_archive_from_expsyms_cmds= + thread_safe_flag_spec= + whole_archive_flag_spec= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + include_expsyms= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ' (' and ')$', so one must not match beginning or + # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', + # as well as any symbol that contains 'd'. + exclude_expsyms='_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*' + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test yes != "$GCC"; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd* | bitrig*) + with_gnu_ld=no + ;; + linux* | k*bsd*-gnu | gnu*) + link_all_deplibs=no + ;; + esac + + ld_shlibs=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test yes = "$with_gnu_ld"; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[2-9]*) ;; + *\ \(GNU\ Binutils\)\ [3-9]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test yes = "$lt_use_gnu_ld_interface"; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='$wl' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + export_dynamic_flag_spec='$wl--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + whole_archive_flag_spec=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + whole_archive_flag_spec= + fi + supports_anon_versioning=no + case `$LD -v | $SED -e 's/(^)\+)\s\+//' 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [01].* | *\ 2.[0-9].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test ia64 != "$host_cpu"; then + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='' + ;; + m68k) + archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + allow_undefined_flag=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + ld_shlibs=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, ) is actually meaningless, + # as there is no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + export_dynamic_flag_spec='$wl--export-all-symbols' + allow_undefined_flag=unsupported + always_export_symbols=no + enable_shared_with_static_runtimes=yes + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1 DATA/;s/^.*[ ]__nm__\([^ ]*\)[ ][^ ]*/\1 DATA/;/^I[ ]/d;/^[AITW][ ]/s/.* //'\'' | sort | uniq > $export_symbols' + exclude_expsyms='[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname' + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + ld_shlibs=no + fi + ;; + + haiku*) + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + link_all_deplibs=yes + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + shrext_cmds=.dll + archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + enable_shared_with_static_runtimes=yes + ;; + + interix[3-9]*) + hardcode_direct=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + export_dynamic_flag_spec='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + archive_expsym_cmds='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test linux-dietlibc = "$host_os"; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test no = "$tmp_diet" + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + whole_archive_flag_spec= + tmp_sharedflag='--shared' ;; + nagfor*) # NAGFOR 5.3 + tmp_sharedflag='-Wl,-shared' ;; + xl[cC]* | bgxl[cC]* | mpixl[cC]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + whole_archive_flag_spec='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + compiler_needs_object=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + whole_archive_flag_spec='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + compiler_needs_object=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + archive_cmds='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + + if test yes = "$supports_anon_versioning"; then + archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + tcc*) + export_dynamic_flag_spec='-rdynamic' + ;; + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + whole_archive_flag_spec='--whole-archive$convenience --no-whole-archive' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test yes = "$supports_anon_versioning"; then + archive_expsym_cmds='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + ld_shlibs=no + fi + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) + ld_shlibs=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + ;; + + sunos4*) + archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + ld_shlibs=no + fi + ;; + esac + + if test no = "$ld_shlibs"; then + runpath_var= + hardcode_libdir_flag_spec= + export_dynamic_flag_spec= + whole_archive_flag_spec= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + allow_undefined_flag=unsupported + always_export_symbols=yes + archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + + aix[4-9]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + export_symbols_cmds='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && (substr(\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + export_symbols_cmds='`func_echo_all $NM | $SED -e '\''s/B\([^B]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && (substr(\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then + aix_use_runtimelinking=yes + break + fi + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + archive_cmds='' + hardcode_direct=yes + hardcode_direct_absolute=yes + hardcode_libdir_separator=':' + link_all_deplibs=yes + file_list_spec='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # traditional, no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + hardcode_direct=no + hardcode_direct_absolute=no + ;; + esac + + if test yes = "$GCC"; then + case $host_os in aix4.[012]|aix4.[012].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + ;; + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag="$shared_flag "'$wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + export_dynamic_flag_spec='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + always_export_symbols=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + allow_undefined_flag='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath_+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath_ +fi + + hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" + archive_expsym_cmds='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + hardcode_libdir_flag_spec='$wl-R $libdir:/usr/lib:/lib' + allow_undefined_flag="-z nodefs" + archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + if ${lt_cv_aix_libpath_+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + + lt_aix_libpath_sed=' + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }' + lt_cv_aix_libpath_=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + if test -z "$lt_cv_aix_libpath_"; then + lt_cv_aix_libpath_=/usr/lib:/lib + fi + +fi + + aix_libpath=$lt_cv_aix_libpath_ +fi + + hardcode_libdir_flag_spec='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + no_undefined_flag=' $wl-bernotok' + allow_undefined_flag=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + whole_archive_flag_spec='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + whole_archive_flag_spec='$convenience' + fi + archive_cmds_need_lc=yes + archive_expsym_cmds='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([, ]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared libraries. + archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + archive_expsym_cmds="$archive_expsym_cmds"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + archive_expsym_cmds="$archive_expsym_cmds"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + archive_expsym_cmds="$archive_expsym_cmds"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + archive_expsym_cmds='' + ;; + m68k) + archive_cmds='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + esac + ;; + + bsdi[45]*) + export_dynamic_flag_spec=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + always_export_symbols=yes + file_list_spec='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + archive_expsym_cmds='if test DEF = "`$SED -n -e '\''s/^[ ]*//'\'' -e '\''/^\(;.*\)*$/d'\'' -e '\''s/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p'\'' -e q $export_symbols`" ; then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, )='true' + enable_shared_with_static_runtimes=yes + exclude_expsyms='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[BCDGRS][ ]/s/.*[ ]\([^ ]*\)/\1,DATA/'\'' | $SED -e '\''/^[AITW][ ]/s/.*[ ]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + old_postinstall_cmds='chmod 644 $oldlib' + postlink_cmds='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + hardcode_libdir_flag_spec=' ' + allow_undefined_flag=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + archive_cmds='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + old_archive_from_new_cmds='true' + # FIXME: Should let the user specify the lib program. + old_archive_cmds='lib -OUT:$oldlib$oldobjs$old_deplibs' + enable_shared_with_static_runtimes=yes + ;; + esac + ;; + + darwin* | rhapsody*) + + + archive_cmds_need_lc=no + hardcode_direct=no + hardcode_automatic=yes + hardcode_shlibpath_var=unsupported + if test yes = "$lt_cv_ld_force_load"; then + whole_archive_flag_spec='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + + else + whole_archive_flag_spec='' + fi + link_all_deplibs=yes + allow_undefined_flag=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + archive_cmds="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + module_cmds="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + archive_expsym_cmds="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + module_expsym_cmds="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + + else + ld_shlibs=no + fi + + ;; + + dgux*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + hpux9*) + if test yes = "$GCC"; then + archive_cmds='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + archive_cmds='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + fi + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + export_dynamic_flag_spec='$wl-E' + ;; + + hpux10*) + if test yes,no = "$GCC,$with_gnu_ld"; then + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test no = "$with_gnu_ld"; then + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + hardcode_direct_absolute=yes + export_dynamic_flag_spec='$wl-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + fi + ;; + + hpux11*) + if test yes,no = "$GCC,$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + archive_cmds='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + archive_cmds='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + archive_cmds='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + archive_cmds='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if $CC understands -b" >&5 +$as_echo_n "checking if $CC understands -b... " >&6; } +if ${lt_cv_prog_compiler__b+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_prog_compiler__b=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -b" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&5 + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + lt_cv_prog_compiler__b=yes + fi + else + lt_cv_prog_compiler__b=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_prog_compiler__b" >&5 +$as_echo "$lt_cv_prog_compiler__b" >&6; } + +if test yes = "$lt_cv_prog_compiler__b"; then + archive_cmds='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' +else + archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' +fi + + ;; + esac + fi + if test no = "$with_gnu_ld"; then + hardcode_libdir_flag_spec='$wl+b $wl$libdir' + hardcode_libdir_separator=: + + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct=no + hardcode_shlibpath_var=no + ;; + *) + hardcode_direct=yes + hardcode_direct_absolute=yes + export_dynamic_flag_spec='$wl-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test yes = "$GCC"; then + archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the $host_os linker accepts -exported_symbol" >&5 +$as_echo_n "checking whether the $host_os linker accepts -exported_symbol... " >&6; } +if ${lt_cv_irix_exported_symbol+:} false; then : + $as_echo_n "(cached) " >&6 +else + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int foo (void) { return 0; } +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + lt_cv_irix_exported_symbol=yes +else + lt_cv_irix_exported_symbol=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_irix_exported_symbol" >&5 +$as_echo "$lt_cv_irix_exported_symbol" >&6; } + if test yes = "$lt_cv_irix_exported_symbol"; then + archive_expsym_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' + fi + link_all_deplibs=no + else + archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' + fi + archive_cmds_need_lc='no' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + inherit_rpath=yes + link_all_deplibs=yes + ;; + + linux*) + case $cc_basename in + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + ld_shlibs=yes + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + hardcode_shlibpath_var=no + ;; + + newsos6) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + hardcode_shlibpath_var=no + ;; + + *nto* | *qnx*) + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + hardcode_direct=yes + hardcode_shlibpath_var=no + hardcode_direct_absolute=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + export_dynamic_flag_spec='$wl-E' + else + archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + hardcode_libdir_flag_spec='$wl-rpath,$libdir' + fi + else + ld_shlibs=no + fi + ;; + + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + allow_undefined_flag=unsupported + shrext_cmds=.dll + archive_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + archive_expsym_cmds='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + old_archive_From_new_cmds='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + enable_shared_with_static_runtimes=yes + ;; + + osf3*) + if test yes = "$GCC"; then + allow_undefined_flag=' $wl-expect_unresolved $wl\*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + fi + archive_cmds_need_lc='no' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + hardcode_libdir_separator=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test yes = "$GCC"; then + allow_undefined_flag=' $wl-expect_unresolved $wl\*' + archive_cmds='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + hardcode_libdir_flag_spec='$wl-rpath $wl$libdir' + else + allow_undefined_flag=' -expect_unresolved \*' + archive_cmds='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + archive_expsym_cmds='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + hardcode_libdir_flag_spec='-rpath $libdir' + fi + archive_cmds_need_lc='no' + hardcode_libdir_separator=: + ;; + + solaris*) + no_undefined_flag=' -z defs' + if test yes = "$GCC"; then + wlarc='$wl' + archive_cmds='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + archive_cmds='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='$wl' + archive_cmds='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + hardcode_libdir_flag_spec='-R$libdir' + hardcode_shlibpath_var=no + case $host_os in + solaris2.[0-5] | solaris2.[0-5].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. GCC discards it without '$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test yes = "$GCC"; then + whole_archive_flag_spec='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + else + whole_archive_flag_spec='-z allextract$convenience -z defaultextract' + fi + ;; + esac + link_all_deplibs=yes + ;; + + sunos4*) + if test sequent = "$host_vendor"; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + archive_cmds='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + hardcode_shlibpath_var=no + ;; + + sysv4) + case $host_vendor in + sni) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags' + reload_cmds='$CC -r -o $output$reload_objs' + hardcode_direct=no + ;; + motorola) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_direct=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + hardcode_shlibpath_var=no + ;; + + sysv4.3*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + export_dynamic_flag_spec='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_shlibpath_var=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + ld_shlibs=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + no_undefined_flag='$wl-z,text' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + no_undefined_flag='$wl-z,text' + allow_undefined_flag='$wl-z,nodefs' + archive_cmds_need_lc=no + hardcode_shlibpath_var=no + hardcode_libdir_flag_spec='$wl-R,$libdir' + hardcode_libdir_separator=':' + link_all_deplibs=yes + export_dynamic_flag_spec='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + archive_cmds='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + archive_cmds='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + archive_expsym_cmds='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + hardcode_libdir_flag_spec='-L$libdir' + hardcode_shlibpath_var=no + ;; + + *) + ld_shlibs=no + ;; + esac + + if test sni = "$host_vendor"; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + export_dynamic_flag_spec='$wl-Blargedynsym' + ;; + esac + fi + fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ld_shlibs" >&5 +$as_echo "$ld_shlibs" >&6; } +test no = "$ld_shlibs" && can_build_shared=no + +with_gnu_ld=$with_gnu_ld + + + + + + + + + + + + + + + +# +# Do we need to explicitly link libc? +# +case "x$archive_cmds_need_lc" in +x|xyes) + # Assume -lc should be added + archive_cmds_need_lc=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $archive_cmds in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether -lc should be explicitly linked in" >&5 +$as_echo_n "checking whether -lc should be explicitly linked in... " >&6; } +if ${lt_cv_archive_cmds_need_lc+:} false; then : + $as_echo_n "(cached) " >&6 +else + $RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_compile\""; } >&5 + (eval $ac_compile) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$lt_prog_compiler_wl + pic_flag=$lt_prog_compiler_pic + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$allow_undefined_flag + allow_undefined_flag= + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1\""; } >&5 + (eval $archive_cmds 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + then + lt_cv_archive_cmds_need_lc=no + else + lt_cv_archive_cmds_need_lc=yes + fi + allow_undefined_flag=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_archive_cmds_need_lc" >&5 +$as_echo "$lt_cv_archive_cmds_need_lc" >&6; } + archive_cmds_need_lc=$lt_cv_archive_cmds_need_lc + ;; + esac + fi + ;; +esac + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking dynamic linker characteristics" >&5 +$as_echo_n "checking dynamic linker characteristics... " >&6; } + +if test yes = "$GCC"; then + case $host_os in + darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; + *) lt_awk_arg='/^libraries:/' ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq='s|=\([A-Za-z]:\)|\1|g' ;; + *) lt_sed_strip_eq='s|=/|/|g' ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary... + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + # ...but if some path component already ends with the multilib dir we assume + # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). + case "$lt_multi_os_dir; $lt_search_path_spec " in + "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) + lt_multi_os_dir= + ;; + esac + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" + elif test -n "$lt_multi_os_dir"; then + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS = " "; FS = "/|\n";} { + lt_foo = ""; + lt_count = 0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo = "/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[lt_foo]++; } + if (lt_freq[lt_foo] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's|/\([A-Za-z]:\)|\1|g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + + + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[4-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[01] | aix4.[01].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a(lib.so.V)' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V($shared_archive_member_spec.o), lib.a(lib.so.V)" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a(lib.so.V), lib.so.V($shared_archive_member_spec.o)" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[45]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api" + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([a-zA-Z]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | $GREP ';[c-zC-Z]:/' >/dev/null; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[.]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' + + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib" + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[23].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[01]* | freebsdelf3.[01]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[2-9]* | freebsdelf3.[2-9]* | \ + freebsd4.[0-5] | freebsdelf4.[0-5] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[3-9]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + hardcode_libdir_flag_spec='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + if ${lt_cv_shlibpath_overrides_runpath+:} false; then : + $as_echo_n "(cached) " >&6 +else + lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$lt_prog_compiler_wl\"; \ + LDFLAGS=\"\$LDFLAGS $hardcode_libdir_flag_spec\"" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + if ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null; then : + lt_cv_shlibpath_overrides_runpath=yes +fi +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + +fi + + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \$2)); skip = 1; } { if (!skip) print \$0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsdelf*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='NetBSD ld.elf_so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $dynamic_linker" >&5 +$as_echo "$dynamic_linker" >&6; } +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking how to hardcode library paths into programs" >&5 +$as_echo_n "checking how to hardcode library paths into programs... " >&6; } +hardcode_action= +if test -n "$hardcode_libdir_flag_spec" || + test -n "$runpath_var" || + test yes = "$hardcode_automatic"; then + + # We can hardcode non-existent directories. + if test no != "$hardcode_direct" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, )" && + test no != "$hardcode_minus_L"; then + # Linking always hardcodes the temporary library directory. + hardcode_action=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + hardcode_action=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + hardcode_action=unsupported +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $hardcode_action" >&5 +$as_echo "$hardcode_action" >&6; } + +if test relink = "$hardcode_action" || + test yes = "$inherit_rpath"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi + + + + + + + if test yes != "$enable_dlopen"; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen=load_add_on + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen=LoadLibrary + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl +else + + lt_cv_dlopen=dyld + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + +fi + + ;; + + tpf*) + # Don't try to run any link tests for TPF. We know it's impossible + # because TPF is a cross-compiler, and we know how we open DSOs. + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + lt_cv_dlopen_self=no + ;; + + *) + ac_fn_c_check_func "$LINENO" "shl_load" "ac_cv_func_shl_load" +if test "x$ac_cv_func_shl_load" = xyes; then : + lt_cv_dlopen=shl_load +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for shl_load in -ldld" >&5 +$as_echo_n "checking for shl_load in -ldld... " >&6; } +if ${ac_cv_lib_dld_shl_load+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char shl_load (); +int +main () +{ +return shl_load (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_shl_load=yes +else + ac_cv_lib_dld_shl_load=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_shl_load" >&5 +$as_echo "$ac_cv_lib_dld_shl_load" >&6; } +if test "x$ac_cv_lib_dld_shl_load" = xyes; then : + lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld +else + ac_fn_c_check_func "$LINENO" "dlopen" "ac_cv_func_dlopen" +if test "x$ac_cv_func_dlopen" = xyes; then : + lt_cv_dlopen=dlopen +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -lsvld" >&5 +$as_echo_n "checking for dlopen in -lsvld... " >&6; } +if ${ac_cv_lib_svld_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lsvld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_svld_dlopen=yes +else + ac_cv_lib_svld_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_svld_dlopen" >&5 +$as_echo "$ac_cv_lib_svld_dlopen" >&6; } +if test "x$ac_cv_lib_svld_dlopen" = xyes; then : + lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dld_link in -ldld" >&5 +$as_echo_n "checking for dld_link in -ldld... " >&6; } +if ${ac_cv_lib_dld_dld_link+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldld $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dld_link (); +int +main () +{ +return dld_link (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dld_dld_link=yes +else + ac_cv_lib_dld_dld_link=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dld_dld_link" >&5 +$as_echo "$ac_cv_lib_dld_dld_link" >&6; } +if test "x$ac_cv_lib_dld_dld_link" = xyes; then : + lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld +fi + + +fi + + +fi + + +fi + + +fi + + +fi + + ;; + esac + + if test no = "$lt_cv_dlopen"; then + enable_dlopen=no + else + enable_dlopen=yes + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS=$CPPFLAGS + test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS=$LDFLAGS + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS=$LIBS + LIBS="$lt_cv_dlopen_libs $LIBS" + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program can dlopen itself" >&5 +$as_echo_n "checking whether a program can dlopen itself... " >&6; } +if ${lt_cv_dlopen_self+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test yes = "$cross_compiling"; then : + lt_cv_dlopen_self=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +} +_LT_EOF + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self" >&5 +$as_echo "$lt_cv_dlopen_self" >&6; } + + if test yes = "$lt_cv_dlopen_self"; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a statically linked program can dlopen itself" >&5 +$as_echo_n "checking whether a statically linked program can dlopen itself... " >&6; } +if ${lt_cv_dlopen_self_static+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test yes = "$cross_compiling"; then : + lt_cv_dlopen_self_static=cross +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +} +_LT_EOF + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_link\""; } >&5 + (eval $ac_link) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&5 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;; + x$lt_dlunknown|x*) lt_cv_dlopen_self_static=no ;; + esac + else : + # compilation failed + lt_cv_dlopen_self_static=no + fi +fi +rm -fr conftest* + + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $lt_cv_dlopen_self_static" >&5 +$as_echo "$lt_cv_dlopen_self_static" >&6; } + fi + + CPPFLAGS=$save_CPPFLAGS + LDFLAGS=$save_LDFLAGS + LIBS=$save_LIBS + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi + + + + + + + + + + + + + + + + + +striplib= +old_striplib= +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether stripping libraries is possible" >&5 +$as_echo_n "checking whether stripping libraries is possible... " >&6; } +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP"; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + fi + ;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + ;; + esac +fi + + + + + + + + + + + + + # Report what library types will actually be built + { $as_echo "$as_me:${as_lineno-$LINENO}: checking if libtool supports shared libraries" >&5 +$as_echo_n "checking if libtool supports shared libraries... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $can_build_shared" >&5 +$as_echo "$can_build_shared" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build shared libraries" >&5 +$as_echo_n "checking whether to build shared libraries... " >&6; } + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[4-9]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_shared" >&5 +$as_echo "$enable_shared" >&6; } + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build static libraries" >&5 +$as_echo_n "checking whether to build static libraries... " >&6; } + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_static" >&5 +$as_echo "$enable_static" >&6; } + + + + +fi +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + +CC=$lt_save_CC + + + + + + + + + + + + + + + + ac_config_commands="$ac_config_commands libtool" + + + + +# Only expand once: + + + +if (test "$USE_MAINTAINER_MODE" = "yes"); then + # Extract the first word of "lcov", so it can be a program name with args. +set dummy lcov; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_enable_coverage+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$enable_coverage"; then + ac_cv_prog_enable_coverage="$enable_coverage" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_enable_coverage="yes" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_enable_coverage" && ac_cv_prog_enable_coverage="no" +fi +fi +enable_coverage=$ac_cv_prog_enable_coverage +if test -n "$enable_coverage"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_coverage" >&5 +$as_echo "$enable_coverage" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + # Extract the first word of "dbus-run-session", so it can be a program name with args. +set dummy dbus-run-session; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_enable_dbus_run_session+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$enable_dbus_run_session"; then + ac_cv_prog_enable_dbus_run_session="$enable_dbus_run_session" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_enable_dbus_run_session="yes" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +enable_dbus_run_session=$ac_cv_prog_enable_dbus_run_session +if test -n "$enable_dbus_run_session"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_dbus_run_session" >&5 +$as_echo "$enable_dbus_run_session" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + # Extract the first word of "valgrind", so it can be a program name with args. +set dummy valgrind; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_enable_valgrind+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$enable_valgrind"; then + ac_cv_prog_enable_valgrind="$enable_valgrind" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_enable_valgrind="yes" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +enable_valgrind=$ac_cv_prog_enable_valgrind +if test -n "$enable_valgrind"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_valgrind" >&5 +$as_echo "$enable_valgrind" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + for ac_header in valgrind/memcheck.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "valgrind/memcheck.h" "ac_cv_header_valgrind_memcheck_h" "$ac_includes_default" +if test "x$ac_cv_header_valgrind_memcheck_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_VALGRIND_MEMCHECK_H 1 +_ACEOF + +fi + +done + +fi + if test "${enable_coverage}" = "yes"; then + COVERAGE_TRUE= + COVERAGE_FALSE='#' +else + COVERAGE_TRUE='#' + COVERAGE_FALSE= +fi + + if test "${enable_dbus_run_session}" = "yes"; then + DBUS_RUN_SESSION_TRUE= + DBUS_RUN_SESSION_FALSE='#' +else + DBUS_RUN_SESSION_TRUE='#' + DBUS_RUN_SESSION_FALSE= +fi + + if test "${enable_valgrind}" = "yes"; then + VALGRIND_TRUE= + VALGRIND_FALSE='#' +else + VALGRIND_TRUE='#' + VALGRIND_FALSE= +fi + + + + misc_cflags="" + misc_ldflags="" + # Check whether --enable-optimization was given. +if test "${enable_optimization+set}" = set; then : + enableval=$enable_optimization; + if (test "${enableval}" = "no"); then + misc_cflags="$misc_cflags -O0" + fi + +fi + + # Check whether --enable-debug was given. +if test "${enable_debug+set}" = set; then : + enableval=$enable_debug; + if (test "${enableval}" = "yes" && + test "${ac_cv_prog_cc_g}" = "yes"); then + misc_cflags="$misc_cflags -g" + fi + +fi + + # Check whether --enable-pie was given. +if test "${enable_pie+set}" = set; then : + enableval=$enable_pie; + if (test "${enableval}" = "yes" && + test "${ac_cv_prog_cc_pie}" = "yes"); then + misc_cflags="$misc_cflags -fPIC" + misc_ldflags="$misc_ldflags -pie -Wl,-z,now" + fi + +fi + + if (test "$enable_coverage" = "yes"); then + misc_cflags="$misc_cflags --coverage" + misc_ldflags="$misc_ldflags --coverage" + fi + MISC_CFLAGS=$misc_cflags + + MISC_LDFLAGS=$misc_ldflags + + + +# Check whether --enable-threads was given. +if test "${enable_threads+set}" = set; then : + enableval=$enable_threads; enable_threads=${enableval} +fi + + +for ac_func in explicit_bzero +do : + ac_fn_c_check_func "$LINENO" "explicit_bzero" "ac_cv_func_explicit_bzero" +if test "x$ac_cv_func_explicit_bzero" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_EXPLICIT_BZERO 1 +_ACEOF + +fi +done + + +ac_fn_c_check_func "$LINENO" "signalfd" "ac_cv_func_signalfd" +if test "x$ac_cv_func_signalfd" = xyes; then : + dummy=yes +else + as_fn_error $? "signalfd support is required" "$LINENO" 5 +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_gettime in -lrt" >&5 +$as_echo_n "checking for clock_gettime in -lrt... " >&6; } +if ${ac_cv_lib_rt_clock_gettime+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lrt $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char clock_gettime (); +int +main () +{ +return clock_gettime (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_rt_clock_gettime=yes +else + ac_cv_lib_rt_clock_gettime=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rt_clock_gettime" >&5 +$as_echo "$ac_cv_lib_rt_clock_gettime" >&6; } +if test "x$ac_cv_lib_rt_clock_gettime" = xyes; then : + dummy=yes +else + as_fn_error $? "realtime clock support is required" "$LINENO" 5 +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthread" >&5 +$as_echo_n "checking for pthread_create in -lpthread... " >&6; } +if ${ac_cv_lib_pthread_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread_pthread_create=yes +else + ac_cv_lib_pthread_pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5 +$as_echo "$ac_cv_lib_pthread_pthread_create" >&6; } +if test "x$ac_cv_lib_pthread_pthread_create" = xyes; then : + dummy=yes +else + as_fn_error $? "posix thread support is required" "$LINENO" 5 +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlopen in -ldl" >&5 +$as_echo_n "checking for dlopen in -ldl... " >&6; } +if ${ac_cv_lib_dl_dlopen+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ldl $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char dlopen (); +int +main () +{ +return dlopen (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_dl_dlopen=yes +else + ac_cv_lib_dl_dlopen=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlopen" >&5 +$as_echo "$ac_cv_lib_dl_dlopen" >&6; } +if test "x$ac_cv_lib_dl_dlopen" = xyes; then : + dummy=yes +else + as_fn_error $? "dynamic linking loader is required" "$LINENO" 5 +fi + + +for ac_header in linux/types.h linux/if_alg.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + + + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for GLIB" >&5 +$as_echo_n "checking for GLIB... " >&6; } + +if test -n "$GLIB_CFLAGS"; then + pkg_cv_GLIB_CFLAGS="$GLIB_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"glib-2.0 >= 2.28\""; } >&5 + ($PKG_CONFIG --exists --print-errors "glib-2.0 >= 2.28") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_GLIB_CFLAGS=`$PKG_CONFIG --cflags "glib-2.0 >= 2.28" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$GLIB_LIBS"; then + pkg_cv_GLIB_LIBS="$GLIB_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"glib-2.0 >= 2.28\""; } >&5 + ($PKG_CONFIG --exists --print-errors "glib-2.0 >= 2.28") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_GLIB_LIBS=`$PKG_CONFIG --libs "glib-2.0 >= 2.28" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + GLIB_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "glib-2.0 >= 2.28" 2>&1` + else + GLIB_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "glib-2.0 >= 2.28" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$GLIB_PKG_ERRORS" >&5 + + as_fn_error $? "GLib >= 2.28 is required" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "GLib >= 2.28 is required" "$LINENO" 5 +else + GLIB_CFLAGS=$pkg_cv_GLIB_CFLAGS + GLIB_LIBS=$pkg_cv_GLIB_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + dummy=yes +fi + + + +if (test "${enable_threads}" = "yes"); then + +$as_echo "#define NEED_THREADS 1" >>confdefs.h + + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for GTHREAD" >&5 +$as_echo_n "checking for GTHREAD... " >&6; } + +if test -n "$GTHREAD_CFLAGS"; then + pkg_cv_GTHREAD_CFLAGS="$GTHREAD_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"gthread-2.0 >= 2.16\""; } >&5 + ($PKG_CONFIG --exists --print-errors "gthread-2.0 >= 2.16") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_GTHREAD_CFLAGS=`$PKG_CONFIG --cflags "gthread-2.0 >= 2.16" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$GTHREAD_LIBS"; then + pkg_cv_GTHREAD_LIBS="$GTHREAD_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"gthread-2.0 >= 2.16\""; } >&5 + ($PKG_CONFIG --exists --print-errors "gthread-2.0 >= 2.16") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_GTHREAD_LIBS=`$PKG_CONFIG --libs "gthread-2.0 >= 2.16" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + GTHREAD_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "gthread-2.0 >= 2.16" 2>&1` + else + GTHREAD_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "gthread-2.0 >= 2.16" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$GTHREAD_PKG_ERRORS" >&5 + + as_fn_error $? "GThread >= 2.16 is required" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "GThread >= 2.16 is required" "$LINENO" 5 +else + GTHREAD_CFLAGS=$pkg_cv_GTHREAD_CFLAGS + GTHREAD_LIBS=$pkg_cv_GTHREAD_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + dummy=yes +fi + GLIB_CFLAGS="$GLIB_CFLAGS $GTHREAD_CFLAGS" + GLIB_LIBS="$GLIB_LIBS $GTHREAD_LIBS" +fi + + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for DBUS" >&5 +$as_echo_n "checking for DBUS... " >&6; } + +if test -n "$DBUS_CFLAGS"; then + pkg_cv_DBUS_CFLAGS="$DBUS_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dbus-1 >= 1.6\""; } >&5 + ($PKG_CONFIG --exists --print-errors "dbus-1 >= 1.6") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_DBUS_CFLAGS=`$PKG_CONFIG --cflags "dbus-1 >= 1.6" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$DBUS_LIBS"; then + pkg_cv_DBUS_LIBS="$DBUS_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"dbus-1 >= 1.6\""; } >&5 + ($PKG_CONFIG --exists --print-errors "dbus-1 >= 1.6") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_DBUS_LIBS=`$PKG_CONFIG --libs "dbus-1 >= 1.6" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + DBUS_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "dbus-1 >= 1.6" 2>&1` + else + DBUS_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "dbus-1 >= 1.6" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$DBUS_PKG_ERRORS" >&5 + + as_fn_error $? "D-Bus >= 1.6 is required" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "D-Bus >= 1.6 is required" "$LINENO" 5 +else + DBUS_CFLAGS=$pkg_cv_DBUS_CFLAGS + DBUS_LIBS=$pkg_cv_DBUS_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + dummy=yes +fi + + + + +# Check whether --with-dbusconfdir was given. +if test "${with_dbusconfdir+set}" = set; then : + withval=$with_dbusconfdir; path_dbusconfdir=${withval} +fi + +if (test -z "${path_dbusconfdir}"); then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking D-Bus configuration directory" >&5 +$as_echo_n "checking D-Bus configuration directory... " >&6; } + path_dbusconfdir="`$PKG_CONFIG --variable=sysconfdir dbus-1`" + if (test -z "${path_dbusconfdir}"); then + as_fn_error $? "D-Bus configuration directory is required" "$LINENO" 5 + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${path_dbusconfdir}" >&5 +$as_echo "${path_dbusconfdir}" >&6; } +fi +DBUS_CONFDIR=${path_dbusconfdir} + + + +# Check whether --with-dbussystembusdir was given. +if test "${with_dbussystembusdir+set}" = set; then : + withval=$with_dbussystembusdir; path_dbussystembusdir=${withval} +fi + +if (test -z "${path_dbussystembusdir}"); then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking D-Bus system bus services dir" >&5 +$as_echo_n "checking D-Bus system bus services dir... " >&6; } + path_dbussystembusdir="`$PKG_CONFIG --variable=system_bus_services_dir dbus-1`" + if (test -z "${path_dbussystembusdir}"); then + as_fn_error $? "D-Bus system bus services directory is required" "$LINENO" 5 + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${path_dbussystembusdir}" >&5 +$as_echo "${path_dbussystembusdir}" >&6; } +fi +DBUS_SYSTEMBUSDIR=${path_dbussystembusdir} + + + +# Check whether --with-dbussessionbusdir was given. +if test "${with_dbussessionbusdir+set}" = set; then : + withval=$with_dbussessionbusdir; path_dbussessionbusdir=${withval} +fi + +if (test -z "${path_dbussessionbusdir}"); then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking D-Bus session bus services dir" >&5 +$as_echo_n "checking D-Bus session bus services dir... " >&6; } + path_dbussessionbusdir="`$PKG_CONFIG --variable=session_bus_services_dir dbus-1`" + if (test -z "${path_dbussessionbusdir}"); then + as_fn_error $? "D-Bus session bus services directory is required" "$LINENO" 5 + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${path_dbussessionbusdir}" >&5 +$as_echo "${path_dbussessionbusdir}" >&6; } +fi +DBUS_SESSIONBUSDIR=${path_dbussessionbusdir} + + + +# Check whether --with-zsh-completion-dir was given. +if test "${with_zsh_completion_dir+set}" = set; then : + withval=$with_zsh_completion_dir; path_zshcompletiondir=${withval} +else + path_zshcompletiondir="yes" +fi + + +if (test "${path_zshcompletiondir}" = "yes"); then + path_zshcompletiondir="$datarootdir/zsh/site-functions" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${path_zshcompletiondir}" >&5 +$as_echo "${path_zshcompletiondir}" >&6; } +fi +ZSH_COMPLETIONDIR=${path_zshcompletiondir} + + if test "${path_zshcompletiondir}" != "no"; then + ZSH_COMPLETIONS_TRUE= + ZSH_COMPLETIONS_FALSE='#' +else + ZSH_COMPLETIONS_TRUE='#' + ZSH_COMPLETIONS_FALSE= +fi + + +# Check whether --enable-backtrace was given. +if test "${enable_backtrace+set}" = set; then : + enableval=$enable_backtrace; enable_backtrace=${enableval} +fi + + +if (test "${enable_backtrace}" = "yes"); then + ac_fn_c_check_header_mongrel "$LINENO" "elfutils/libdwfl.h" "ac_cv_header_elfutils_libdwfl_h" "$ac_includes_default" +if test "x$ac_cv_header_elfutils_libdwfl_h" = xyes; then : + dummy=yes +else + as_fn_error $? "elfutils support is required" "$LINENO" 5 +fi + + + +$as_echo "#define HAVE_BACKTRACE_SUPPORT 1" >>confdefs.h + + BACKTRACE_CFLAGS="" + BACKTRACE_LIBS="-ldw" + + +fi + +# Check whether --enable-library was given. +if test "${enable_library+set}" = set; then : + enableval=$enable_library; enable_library=${enableval} +fi + + if test "${enable_library}" = "yes"; then + LIBRARY_TRUE= + LIBRARY_FALSE='#' +else + LIBRARY_TRUE='#' + LIBRARY_FALSE= +fi + + +# Check whether --enable-test was given. +if test "${enable_test+set}" = set; then : + enableval=$enable_test; enable_test=${enableval} +fi + + if test "${enable_test}" = "yes"; then + TEST_TRUE= + TEST_FALSE='#' +else + TEST_TRUE='#' + TEST_FALSE= +fi + + +# Check whether --enable-nfc was given. +if test "${enable_nfc+set}" = set; then : + enableval=$enable_nfc; enable_nfc=${enableval} +fi + + if test "${enable_nfc}" = "yes"; then + NFC_TRUE= + NFC_FALSE='#' +else + NFC_TRUE='#' + NFC_FALSE= +fi + + +# Check whether --enable-sap was given. +if test "${enable_sap+set}" = set; then : + enableval=$enable_sap; enable_sap=${enableval} +fi + + if test "${enable_sap}" = "yes"; then + SAP_TRUE= + SAP_FALSE='#' +else + SAP_TRUE='#' + SAP_FALSE= +fi + + +# Check whether --enable-a2dp was given. +if test "${enable_a2dp+set}" = set; then : + enableval=$enable_a2dp; enable_a2dp=${enableval} +fi + + if test "${enable_a2dp}" != "no"; then + A2DP_TRUE= + A2DP_FALSE='#' +else + A2DP_TRUE='#' + A2DP_FALSE= +fi + + +# Check whether --enable-avrcp was given. +if test "${enable_avrcp+set}" = set; then : + enableval=$enable_avrcp; enable_avrcp=${enableval} +fi + + if test "${enable_avrcp}" != "no"; then + AVRCP_TRUE= + AVRCP_FALSE='#' +else + AVRCP_TRUE='#' + AVRCP_FALSE= +fi + + +# Check whether --enable-network was given. +if test "${enable_network+set}" = set; then : + enableval=$enable_network; enable_network=${enableval} +fi + + if test "${enable_network}" != "no"; then + NETWORK_TRUE= + NETWORK_FALSE='#' +else + NETWORK_TRUE='#' + NETWORK_FALSE= +fi + + +# Check whether --enable-hid was given. +if test "${enable_hid+set}" = set; then : + enableval=$enable_hid; enable_hid=${enableval} +fi + + if test "${enable_hid}" != "no"; then + HID_TRUE= + HID_FALSE='#' +else + HID_TRUE='#' + HID_FALSE= +fi + + +# Check whether --enable-hog was given. +if test "${enable_hog+set}" = set; then : + enableval=$enable_hog; enable_hog=${enableval} +fi + + if test "${enable_hog}" != "no"; then + HOG_TRUE= + HOG_FALSE='#' +else + HOG_TRUE='#' + HOG_FALSE= +fi + + +# Check whether --enable-health was given. +if test "${enable_health+set}" = set; then : + enableval=$enable_health; enable_health=${enableval} +fi + + if test "${enable_health}" = "yes"; then + HEALTH_TRUE= + HEALTH_FALSE='#' +else + HEALTH_TRUE='#' + HEALTH_FALSE= +fi + + +# Check whether --enable-tools was given. +if test "${enable_tools+set}" = set; then : + enableval=$enable_tools; enable_tools=${enableval} +fi + + if test "${enable_tools}" != "no"; then + TOOLS_TRUE= + TOOLS_FALSE='#' +else + TOOLS_TRUE='#' + TOOLS_FALSE= +fi + + +# Check whether --enable-monitor was given. +if test "${enable_monitor+set}" = set; then : + enableval=$enable_monitor; enable_monitor=${enableval} +fi + + if test "${enable_monitor}" != "no"; then + MONITOR_TRUE= + MONITOR_FALSE='#' +else + MONITOR_TRUE='#' + MONITOR_FALSE= +fi + + +# Check whether --enable-udev was given. +if test "${enable_udev+set}" = set; then : + enableval=$enable_udev; enable_udev=${enableval} +fi + +if (test "${enable_tools}" != "no" && test "${enable_udev}" != "no"); then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for UDEV" >&5 +$as_echo_n "checking for UDEV... " >&6; } + +if test -n "$UDEV_CFLAGS"; then + pkg_cv_UDEV_CFLAGS="$UDEV_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libudev >= 172\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libudev >= 172") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_UDEV_CFLAGS=`$PKG_CONFIG --cflags "libudev >= 172" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$UDEV_LIBS"; then + pkg_cv_UDEV_LIBS="$UDEV_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libudev >= 172\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libudev >= 172") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_UDEV_LIBS=`$PKG_CONFIG --libs "libudev >= 172" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + UDEV_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libudev >= 172" 2>&1` + else + UDEV_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libudev >= 172" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$UDEV_PKG_ERRORS" >&5 + + as_fn_error $? "libudev >= 172 is required" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "libudev >= 172 is required" "$LINENO" 5 +else + UDEV_CFLAGS=$pkg_cv_UDEV_CFLAGS + UDEV_LIBS=$pkg_cv_UDEV_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + dummy=yes +fi + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for udev_hwdb_new in -ludev" >&5 +$as_echo_n "checking for udev_hwdb_new in -ludev... " >&6; } +if ${ac_cv_lib_udev_udev_hwdb_new+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-ludev $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char udev_hwdb_new (); +int +main () +{ +return udev_hwdb_new (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_udev_udev_hwdb_new=yes +else + ac_cv_lib_udev_udev_hwdb_new=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_udev_udev_hwdb_new" >&5 +$as_echo "$ac_cv_lib_udev_udev_hwdb_new" >&6; } +if test "x$ac_cv_lib_udev_udev_hwdb_new" = xyes; then : + +$as_echo "#define HAVE_UDEV_HWDB_NEW 1" >>confdefs.h + +fi + +fi + if test "${enable_udev}" != "no"; then + UDEV_TRUE= + UDEV_FALSE='#' +else + UDEV_TRUE='#' + UDEV_FALSE= +fi + + + +# Check whether --with-udevdir was given. +if test "${with_udevdir+set}" = set; then : + withval=$with_udevdir; path_udevdir=${withval} +fi + +if (test "${enable_udev}" != "no" && test -z "${path_udevdir}"); then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking udev directory" >&5 +$as_echo_n "checking udev directory... " >&6; } + path_udevdir="`$PKG_CONFIG --variable=udevdir udev`" + if (test -z "${path_udevdir}"); then + as_fn_error $? "udev directory is required" "$LINENO" 5 + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${path_udevdir}" >&5 +$as_echo "${path_udevdir}" >&6; } +fi +UDEV_DIR=${path_udevdir} + + + if test "${enable_tools}" != "no" && + test "${enable_udev}" != "no"; then + HID2HCI_TRUE= + HID2HCI_FALSE='#' +else + HID2HCI_TRUE='#' + HID2HCI_FALSE= +fi + + +# Check whether --enable-cups was given. +if test "${enable_cups+set}" = set; then : + enableval=$enable_cups; enable_cups=${enableval} +fi + + if test "${enable_cups}" != "no"; then + CUPS_TRUE= + CUPS_FALSE='#' +else + CUPS_TRUE='#' + CUPS_FALSE= +fi + + +# Check whether --enable-mesh was given. +if test "${enable_mesh+set}" = set; then : + enableval=$enable_mesh; enable_mesh=${enableval} +fi + + if test "${enable_mesh}" = "yes"; then + MESH_TRUE= + MESH_FALSE='#' +else + MESH_TRUE='#' + MESH_FALSE= +fi + + +if (test "${enable_mesh}" = "yes"); then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for JSONC" >&5 +$as_echo_n "checking for JSONC... " >&6; } + +if test -n "$JSONC_CFLAGS"; then + pkg_cv_JSONC_CFLAGS="$JSONC_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"json-c\""; } >&5 + ($PKG_CONFIG --exists --print-errors "json-c") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_JSONC_CFLAGS=`$PKG_CONFIG --cflags "json-c" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$JSONC_LIBS"; then + pkg_cv_JSONC_LIBS="$JSONC_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"json-c\""; } >&5 + ($PKG_CONFIG --exists --print-errors "json-c") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_JSONC_LIBS=`$PKG_CONFIG --libs "json-c" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + JSONC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "json-c" 2>&1` + else + JSONC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "json-c" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$JSONC_PKG_ERRORS" >&5 + + as_fn_error $? "json-c is required" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "json-c is required" "$LINENO" 5 +else + JSONC_CFLAGS=$pkg_cv_JSONC_CFLAGS + JSONC_LIBS=$pkg_cv_JSONC_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + dummy=yes +fi + + +fi + +# Check whether --enable-midi was given. +if test "${enable_midi+set}" = set; then : + enableval=$enable_midi; enable_midi=${enableval} +fi + + if test "${enable_midi}" = "yes"; then + MIDI_TRUE= + MIDI_FALSE='#' +else + MIDI_TRUE='#' + MIDI_FALSE= +fi + + +if (test "${enable_midi}" = "yes"); then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ALSA" >&5 +$as_echo_n "checking for ALSA... " >&6; } + +if test -n "$ALSA_CFLAGS"; then + pkg_cv_ALSA_CFLAGS="$ALSA_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"alsa\""; } >&5 + ($PKG_CONFIG --exists --print-errors "alsa") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_ALSA_CFLAGS=`$PKG_CONFIG --cflags "alsa" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$ALSA_LIBS"; then + pkg_cv_ALSA_LIBS="$ALSA_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"alsa\""; } >&5 + ($PKG_CONFIG --exists --print-errors "alsa") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_ALSA_LIBS=`$PKG_CONFIG --libs "alsa" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + ALSA_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "alsa" 2>&1` + else + ALSA_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "alsa" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$ALSA_PKG_ERRORS" >&5 + + as_fn_error $? "ALSA lib is required for MIDI support" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "ALSA lib is required for MIDI support" "$LINENO" 5 +else + ALSA_CFLAGS=$pkg_cv_ALSA_CFLAGS + ALSA_LIBS=$pkg_cv_ALSA_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + dummy=yes +fi + + +fi + +# Check whether --enable-obex was given. +if test "${enable_obex+set}" = set; then : + enableval=$enable_obex; enable_obex=${enableval} +fi + +if (test "${enable_obex}" != "no"); then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ICAL" >&5 +$as_echo_n "checking for ICAL... " >&6; } + +if test -n "$ICAL_CFLAGS"; then + pkg_cv_ICAL_CFLAGS="$ICAL_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libical\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libical") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_ICAL_CFLAGS=`$PKG_CONFIG --cflags "libical" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$ICAL_LIBS"; then + pkg_cv_ICAL_LIBS="$ICAL_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libical\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libical") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_ICAL_LIBS=`$PKG_CONFIG --libs "libical" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + ICAL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libical" 2>&1` + else + ICAL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libical" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$ICAL_PKG_ERRORS" >&5 + + as_fn_error $? "libical is required" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "libical is required" "$LINENO" 5 +else + ICAL_CFLAGS=$pkg_cv_ICAL_CFLAGS + ICAL_LIBS=$pkg_cv_ICAL_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + dummy=yes +fi + + +fi + if test "${enable_obex}" != "no"; then + OBEX_TRUE= + OBEX_FALSE='#' +else + OBEX_TRUE='#' + OBEX_FALSE= +fi + + +# Check whether --enable-btpclient was given. +if test "${enable_btpclient+set}" = set; then : + enableval=$enable_btpclient; enable_btpclient=${enableval} +fi + + if test "${enable_btpclient}" = "yes"; then + BTPCLIENT_TRUE= + BTPCLIENT_FALSE='#' +else + BTPCLIENT_TRUE='#' + BTPCLIENT_FALSE= +fi + + +# Check whether --enable-external_ell was given. +if test "${enable_external_ell+set}" = set; then : + enableval=$enable_external_ell; enable_external_ell=${enableval} +fi + +if (test "${enable_external_ell}" = "yes"); then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ELL" >&5 +$as_echo_n "checking for ELL... " >&6; } + +if test -n "$ELL_CFLAGS"; then + pkg_cv_ELL_CFLAGS="$ELL_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ell >= 0.26\""; } >&5 + ($PKG_CONFIG --exists --print-errors "ell >= 0.26") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_ELL_CFLAGS=`$PKG_CONFIG --cflags "ell >= 0.26" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$ELL_LIBS"; then + pkg_cv_ELL_LIBS="$ELL_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"ell >= 0.26\""; } >&5 + ($PKG_CONFIG --exists --print-errors "ell >= 0.26") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_ELL_LIBS=`$PKG_CONFIG --libs "ell >= 0.26" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + ELL_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "ell >= 0.26" 2>&1` + else + ELL_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "ell >= 0.26" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$ELL_PKG_ERRORS" >&5 + + as_fn_error $? "Embedded Linux library >= 0.26 is required" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "Embedded Linux library >= 0.26 is required" "$LINENO" 5 +else + ELL_CFLAGS=$pkg_cv_ELL_CFLAGS + ELL_LIBS=$pkg_cv_ELL_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + dummy=yes +fi + + +fi + if test "${enable_external_ell}" = "yes" || + (test "${enable_btpclient}" != "yes" && + test "${enable_mesh}" != "yes"); then + EXTERNAL_ELL_TRUE= + EXTERNAL_ELL_FALSE='#' +else + EXTERNAL_ELL_TRUE='#' + EXTERNAL_ELL_FALSE= +fi + + if test "${enable_btpclient}" = "yes" || + test "${enable_mesh}" = "yes"; then + LIBSHARED_ELL_TRUE= + LIBSHARED_ELL_FALSE='#' +else + LIBSHARED_ELL_TRUE='#' + LIBSHARED_ELL_FALSE= +fi + + +# Check whether --enable-client was given. +if test "${enable_client+set}" = set; then : + enableval=$enable_client; enable_client=${enableval} +fi + + if test "${enable_client}" != "no"; then + CLIENT_TRUE= + CLIENT_FALSE='#' +else + CLIENT_TRUE='#' + CLIENT_FALSE= +fi + + +if (test "${enable_client}" != "no" || test "${enable_mesh}" = "yes"); then + for ac_header in readline/readline.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "readline/readline.h" "ac_cv_header_readline_readline_h" "$ac_includes_default" +if test "x$ac_cv_header_readline_readline_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_READLINE_READLINE_H 1 +_ACEOF + enable_readline=yes +else + as_fn_error $? "readline header files are required" "$LINENO" 5 +fi + +done + +fi + if test "${enable_readline}" = "yes"; then + READLINE_TRUE= + READLINE_FALSE='#' +else + READLINE_TRUE='#' + READLINE_FALSE= +fi + + +# Check whether --enable-systemd was given. +if test "${enable_systemd+set}" = set; then : + enableval=$enable_systemd; enable_systemd=${enableval} +fi + + if test "${enable_systemd}" != "no"; then + SYSTEMD_TRUE= + SYSTEMD_FALSE='#' +else + SYSTEMD_TRUE='#' + SYSTEMD_FALSE= +fi + + + +# Check whether --with-systemdsystemunitdir was given. +if test "${with_systemdsystemunitdir+set}" = set; then : + withval=$with_systemdsystemunitdir; path_systemunitdir=${withval} +fi + +if (test "${enable_systemd}" != "no" && test -z "${path_systemunitdir}"); then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking systemd system unit dir" >&5 +$as_echo_n "checking systemd system unit dir... " >&6; } + path_systemunitdir="`$PKG_CONFIG --variable=systemdsystemunitdir systemd`" + if (test -z "${path_systemunitdir}"); then + as_fn_error $? "systemd system unit directory is required" "$LINENO" 5 + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${path_systemunitdir}" >&5 +$as_echo "${path_systemunitdir}" >&6; } +fi +SYSTEMD_SYSTEMUNITDIR=${path_systemunitdir} + + + +# Check whether --with-systemduserunitdir was given. +if test "${with_systemduserunitdir+set}" = set; then : + withval=$with_systemduserunitdir; path_userunitdir=${withval} +fi + +if (test "${enable_systemd}" != "no" && test -z "${path_userunitdir}"); then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking systemd user unit dir" >&5 +$as_echo_n "checking systemd user unit dir... " >&6; } + path_userunitdir="`$PKG_CONFIG --variable=systemduserunitdir systemd`" + if (test -z "${path_userunitdir}"); then + as_fn_error $? "systemd user unit directory is required" "$LINENO" 5 + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: ${path_userunitdir}" >&5 +$as_echo "${path_userunitdir}" >&6; } +fi +SYSTEMD_USERUNITDIR=${path_userunitdir} + + +# Check whether --enable-datafiles was given. +if test "${enable_datafiles+set}" = set; then : + enableval=$enable_datafiles; enable_datafiles=${enableval} +fi + + if test "${enable_datafiles}" != "no"; then + DATAFILES_TRUE= + DATAFILES_FALSE='#' +else + DATAFILES_TRUE='#' + DATAFILES_FALSE= +fi + + +# Check whether --enable-manpages was given. +if test "${enable_manpages+set}" = set; then : + enableval=$enable_manpages; enable_manpages=${enableval} +fi + + if test "${enable_manpages}" = "yes"; then + MANPAGES_TRUE= + MANPAGES_FALSE='#' +else + MANPAGES_TRUE='#' + MANPAGES_FALSE= +fi + + +# Check whether --enable-testing was given. +if test "${enable_testing+set}" = set; then : + enableval=$enable_testing; enable_testing=${enableval} +fi + + if test "${enable_testing}" = "yes"; then + TESTING_TRUE= + TESTING_FALSE='#' +else + TESTING_TRUE='#' + TESTING_FALSE= +fi + + +# Check whether --enable-experimental was given. +if test "${enable_experimental+set}" = set; then : + enableval=$enable_experimental; enable_experimental=${enableval} +fi + + if test "${enable_experimental}" = "yes"; then + EXPERIMENTAL_TRUE= + EXPERIMENTAL_FALSE='#' +else + EXPERIMENTAL_TRUE='#' + EXPERIMENTAL_FALSE= +fi + + +# Check whether --enable-deprecated was given. +if test "${enable_deprecated+set}" = set; then : + enableval=$enable_deprecated; enable_deprecated=${enableval} +fi + + if test "${enable_deprecated}" = "yes"; then + DEPRECATED_TRUE= + DEPRECATED_FALSE='#' +else + DEPRECATED_TRUE='#' + DEPRECATED_FALSE= +fi + + +# Check whether --enable-sixaxis was given. +if test "${enable_sixaxis+set}" = set; then : + enableval=$enable_sixaxis; enable_sixaxis=${enableval} +fi + + if test "${enable_sixaxis}" = "yes" && + test "${enable_udev}" != "no"; then + SIXAXIS_TRUE= + SIXAXIS_FALSE='#' +else + SIXAXIS_TRUE='#' + SIXAXIS_FALSE= +fi + + +# Check whether --enable-logger was given. +if test "${enable_logger+set}" = set; then : + enableval=$enable_logger; enable_logger=${enableval} +fi + + if test "${enable_logger}" = "yes"; then + LOGGER_TRUE= + LOGGER_FALSE='#' +else + LOGGER_TRUE='#' + LOGGER_FALSE= +fi + + +if (test "${prefix}" = "NONE"); then + if (test "$localstatedir" = '${prefix}/var'); then + localstatedir='/var' + + fi + + prefix="${ac_default_prefix}" +fi + +if (test "$localstatedir" = '${prefix}/var'); then + storagedir="${prefix}/var/lib/bluetooth" +else + storagedir="${localstatedir}/lib/bluetooth" +fi + +cat >>confdefs.h <<_ACEOF +#define STORAGEDIR "${storagedir}" +_ACEOF + + +if (test "$sysconfdir" = '${prefix}/etc'); then + configdir="${prefix}/etc/bluetooth" +else + configdir="${sysconfdir}/bluetooth" +fi + +cat >>confdefs.h <<_ACEOF +#define CONFIGDIR "${configdir}" +_ACEOF + +CONFIGDIR="${configdir}" + + + +cat >>confdefs.h <<_ACEOF +#define MESH_STORAGEDIR "${storagedir}/mesh" +_ACEOF + + +# Check whether --enable-android was given. +if test "${enable_android+set}" = set; then : + enableval=$enable_android; enable_android=${enableval} +fi + + if test "${enable_android}" = "yes"; then + ANDROID_TRUE= + ANDROID_FALSE='#' +else + ANDROID_TRUE='#' + ANDROID_FALSE= +fi + + +if (test "${enable_android}" = "yes"); then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SBC" >&5 +$as_echo_n "checking for SBC... " >&6; } + +if test -n "$SBC_CFLAGS"; then + pkg_cv_SBC_CFLAGS="$SBC_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"sbc >= 1.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "sbc >= 1.2") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_SBC_CFLAGS=`$PKG_CONFIG --cflags "sbc >= 1.2" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$SBC_LIBS"; then + pkg_cv_SBC_LIBS="$SBC_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"sbc >= 1.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "sbc >= 1.2") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_SBC_LIBS=`$PKG_CONFIG --libs "sbc >= 1.2" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + SBC_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "sbc >= 1.2" 2>&1` + else + SBC_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "sbc >= 1.2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$SBC_PKG_ERRORS" >&5 + + as_fn_error $? "SBC library >= 1.2 is required" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "SBC library >= 1.2 is required" "$LINENO" 5 +else + SBC_CFLAGS=$pkg_cv_SBC_CFLAGS + SBC_LIBS=$pkg_cv_SBC_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + dummy=yes +fi + + +fi + +if (test "${enable_android}" = "yes"); then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for SPEEXDSP" >&5 +$as_echo_n "checking for SPEEXDSP... " >&6; } + +if test -n "$SPEEXDSP_CFLAGS"; then + pkg_cv_SPEEXDSP_CFLAGS="$SPEEXDSP_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"speexdsp >= 1.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "speexdsp >= 1.2") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_SPEEXDSP_CFLAGS=`$PKG_CONFIG --cflags "speexdsp >= 1.2" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$SPEEXDSP_LIBS"; then + pkg_cv_SPEEXDSP_LIBS="$SPEEXDSP_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"speexdsp >= 1.2\""; } >&5 + ($PKG_CONFIG --exists --print-errors "speexdsp >= 1.2") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_SPEEXDSP_LIBS=`$PKG_CONFIG --libs "speexdsp >= 1.2" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + SPEEXDSP_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "speexdsp >= 1.2" 2>&1` + else + SPEEXDSP_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "speexdsp >= 1.2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$SPEEXDSP_PKG_ERRORS" >&5 + + as_fn_error $? "SPEEXDSP library >= 1.2 is required" "$LINENO" 5 +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + as_fn_error $? "SPEEXDSP library >= 1.2 is required" "$LINENO" 5 +else + SPEEXDSP_CFLAGS=$pkg_cv_SPEEXDSP_CFLAGS + SPEEXDSP_LIBS=$pkg_cv_SPEEXDSP_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + dummy=yes +fi + + +fi + + +cat >>confdefs.h <<_ACEOF +#define ANDROID_STORAGEDIR "${storagedir}/android" +_ACEOF + + +ac_config_files="$ac_config_files Makefile src/bluetoothd.8 lib/bluez.pc" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +DEFS=-DHAVE_CONFIG_H + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking that generated files are newer than configure" >&5 +$as_echo_n "checking that generated files are newer than configure... " >&6; } + if test -n "$am_sleep_pid"; then + # Hide warnings about reused PIDs. + wait $am_sleep_pid 2>/dev/null + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: done" >&5 +$as_echo "done" >&6; } + if test -n "$EXEEXT"; then + am__EXEEXT_TRUE= + am__EXEEXT_FALSE='#' +else + am__EXEEXT_TRUE='#' + am__EXEEXT_FALSE= +fi + +if test -z "${MAINTAINER_MODE_TRUE}" && test -z "${MAINTAINER_MODE_FALSE}"; then + as_fn_error $? "conditional \"MAINTAINER_MODE\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${AMDEP_TRUE}" && test -z "${AMDEP_FALSE}"; then + as_fn_error $? "conditional \"AMDEP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then + as_fn_error $? "conditional \"am__fastdepCC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then + as_fn_error $? "conditional \"am__fastdepCC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${COVERAGE_TRUE}" && test -z "${COVERAGE_FALSE}"; then + as_fn_error $? "conditional \"COVERAGE\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${DBUS_RUN_SESSION_TRUE}" && test -z "${DBUS_RUN_SESSION_FALSE}"; then + as_fn_error $? "conditional \"DBUS_RUN_SESSION\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${VALGRIND_TRUE}" && test -z "${VALGRIND_FALSE}"; then + as_fn_error $? "conditional \"VALGRIND\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${ZSH_COMPLETIONS_TRUE}" && test -z "${ZSH_COMPLETIONS_FALSE}"; then + as_fn_error $? "conditional \"ZSH_COMPLETIONS\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${LIBRARY_TRUE}" && test -z "${LIBRARY_FALSE}"; then + as_fn_error $? "conditional \"LIBRARY\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${TEST_TRUE}" && test -z "${TEST_FALSE}"; then + as_fn_error $? "conditional \"TEST\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${NFC_TRUE}" && test -z "${NFC_FALSE}"; then + as_fn_error $? "conditional \"NFC\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${SAP_TRUE}" && test -z "${SAP_FALSE}"; then + as_fn_error $? "conditional \"SAP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${A2DP_TRUE}" && test -z "${A2DP_FALSE}"; then + as_fn_error $? "conditional \"A2DP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${AVRCP_TRUE}" && test -z "${AVRCP_FALSE}"; then + as_fn_error $? "conditional \"AVRCP\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${NETWORK_TRUE}" && test -z "${NETWORK_FALSE}"; then + as_fn_error $? "conditional \"NETWORK\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${HID_TRUE}" && test -z "${HID_FALSE}"; then + as_fn_error $? "conditional \"HID\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${HOG_TRUE}" && test -z "${HOG_FALSE}"; then + as_fn_error $? "conditional \"HOG\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${HEALTH_TRUE}" && test -z "${HEALTH_FALSE}"; then + as_fn_error $? "conditional \"HEALTH\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${TOOLS_TRUE}" && test -z "${TOOLS_FALSE}"; then + as_fn_error $? "conditional \"TOOLS\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${MONITOR_TRUE}" && test -z "${MONITOR_FALSE}"; then + as_fn_error $? "conditional \"MONITOR\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${UDEV_TRUE}" && test -z "${UDEV_FALSE}"; then + as_fn_error $? "conditional \"UDEV\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${HID2HCI_TRUE}" && test -z "${HID2HCI_FALSE}"; then + as_fn_error $? "conditional \"HID2HCI\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${CUPS_TRUE}" && test -z "${CUPS_FALSE}"; then + as_fn_error $? "conditional \"CUPS\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${MESH_TRUE}" && test -z "${MESH_FALSE}"; then + as_fn_error $? "conditional \"MESH\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${MIDI_TRUE}" && test -z "${MIDI_FALSE}"; then + as_fn_error $? "conditional \"MIDI\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${OBEX_TRUE}" && test -z "${OBEX_FALSE}"; then + as_fn_error $? "conditional \"OBEX\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${BTPCLIENT_TRUE}" && test -z "${BTPCLIENT_FALSE}"; then + as_fn_error $? "conditional \"BTPCLIENT\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${EXTERNAL_ELL_TRUE}" && test -z "${EXTERNAL_ELL_FALSE}"; then + as_fn_error $? "conditional \"EXTERNAL_ELL\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${LIBSHARED_ELL_TRUE}" && test -z "${LIBSHARED_ELL_FALSE}"; then + as_fn_error $? "conditional \"LIBSHARED_ELL\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${CLIENT_TRUE}" && test -z "${CLIENT_FALSE}"; then + as_fn_error $? "conditional \"CLIENT\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${READLINE_TRUE}" && test -z "${READLINE_FALSE}"; then + as_fn_error $? "conditional \"READLINE\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${SYSTEMD_TRUE}" && test -z "${SYSTEMD_FALSE}"; then + as_fn_error $? "conditional \"SYSTEMD\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${DATAFILES_TRUE}" && test -z "${DATAFILES_FALSE}"; then + as_fn_error $? "conditional \"DATAFILES\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${MANPAGES_TRUE}" && test -z "${MANPAGES_FALSE}"; then + as_fn_error $? "conditional \"MANPAGES\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${TESTING_TRUE}" && test -z "${TESTING_FALSE}"; then + as_fn_error $? "conditional \"TESTING\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${EXPERIMENTAL_TRUE}" && test -z "${EXPERIMENTAL_FALSE}"; then + as_fn_error $? "conditional \"EXPERIMENTAL\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${DEPRECATED_TRUE}" && test -z "${DEPRECATED_FALSE}"; then + as_fn_error $? "conditional \"DEPRECATED\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${SIXAXIS_TRUE}" && test -z "${SIXAXIS_FALSE}"; then + as_fn_error $? "conditional \"SIXAXIS\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${LOGGER_TRUE}" && test -z "${LOGGER_FALSE}"; then + as_fn_error $? "conditional \"LOGGER\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${ANDROID_TRUE}" && test -z "${ANDROID_FALSE}"; then + as_fn_error $? "conditional \"ANDROID\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by bluez $as_me 5.52, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + +case $ac_config_headers in *" +"*) set x $ac_config_headers; shift; ac_config_headers=$*;; +esac + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" +config_headers="$ac_config_headers" +config_commands="$ac_config_commands" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + --header=FILE[:TEMPLATE] + instantiate the configuration header FILE + +Configuration files: +$config_files + +Configuration headers: +$config_headers + +Configuration commands: +$config_commands + +Report bugs to the package provider." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +bluez config.status 5.52 +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +INSTALL='$INSTALL' +MKDIR_P='$MKDIR_P' +AWK='$AWK' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --header | --heade | --head | --hea ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append CONFIG_HEADERS " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h) + # Conflict between --help and --header + as_fn_error $? "ambiguous option: \`$1' +Try \`$0 --help' for more information.";; + --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# +# INIT-COMMANDS +# +AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}" + + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +enable_static='`$ECHO "$enable_static" | $SED "$delay_single_quote_subst"`' +macro_version='`$ECHO "$macro_version" | $SED "$delay_single_quote_subst"`' +macro_revision='`$ECHO "$macro_revision" | $SED "$delay_single_quote_subst"`' +enable_shared='`$ECHO "$enable_shared" | $SED "$delay_single_quote_subst"`' +pic_mode='`$ECHO "$pic_mode" | $SED "$delay_single_quote_subst"`' +enable_fast_install='`$ECHO "$enable_fast_install" | $SED "$delay_single_quote_subst"`' +shared_archive_member_spec='`$ECHO "$shared_archive_member_spec" | $SED "$delay_single_quote_subst"`' +SHELL='`$ECHO "$SHELL" | $SED "$delay_single_quote_subst"`' +ECHO='`$ECHO "$ECHO" | $SED "$delay_single_quote_subst"`' +PATH_SEPARATOR='`$ECHO "$PATH_SEPARATOR" | $SED "$delay_single_quote_subst"`' +host_alias='`$ECHO "$host_alias" | $SED "$delay_single_quote_subst"`' +host='`$ECHO "$host" | $SED "$delay_single_quote_subst"`' +host_os='`$ECHO "$host_os" | $SED "$delay_single_quote_subst"`' +build_alias='`$ECHO "$build_alias" | $SED "$delay_single_quote_subst"`' +build='`$ECHO "$build" | $SED "$delay_single_quote_subst"`' +build_os='`$ECHO "$build_os" | $SED "$delay_single_quote_subst"`' +SED='`$ECHO "$SED" | $SED "$delay_single_quote_subst"`' +Xsed='`$ECHO "$Xsed" | $SED "$delay_single_quote_subst"`' +GREP='`$ECHO "$GREP" | $SED "$delay_single_quote_subst"`' +EGREP='`$ECHO "$EGREP" | $SED "$delay_single_quote_subst"`' +FGREP='`$ECHO "$FGREP" | $SED "$delay_single_quote_subst"`' +LD='`$ECHO "$LD" | $SED "$delay_single_quote_subst"`' +NM='`$ECHO "$NM" | $SED "$delay_single_quote_subst"`' +LN_S='`$ECHO "$LN_S" | $SED "$delay_single_quote_subst"`' +max_cmd_len='`$ECHO "$max_cmd_len" | $SED "$delay_single_quote_subst"`' +ac_objext='`$ECHO "$ac_objext" | $SED "$delay_single_quote_subst"`' +exeext='`$ECHO "$exeext" | $SED "$delay_single_quote_subst"`' +lt_unset='`$ECHO "$lt_unset" | $SED "$delay_single_quote_subst"`' +lt_SP2NL='`$ECHO "$lt_SP2NL" | $SED "$delay_single_quote_subst"`' +lt_NL2SP='`$ECHO "$lt_NL2SP" | $SED "$delay_single_quote_subst"`' +lt_cv_to_host_file_cmd='`$ECHO "$lt_cv_to_host_file_cmd" | $SED "$delay_single_quote_subst"`' +lt_cv_to_tool_file_cmd='`$ECHO "$lt_cv_to_tool_file_cmd" | $SED "$delay_single_quote_subst"`' +reload_flag='`$ECHO "$reload_flag" | $SED "$delay_single_quote_subst"`' +reload_cmds='`$ECHO "$reload_cmds" | $SED "$delay_single_quote_subst"`' +OBJDUMP='`$ECHO "$OBJDUMP" | $SED "$delay_single_quote_subst"`' +deplibs_check_method='`$ECHO "$deplibs_check_method" | $SED "$delay_single_quote_subst"`' +file_magic_cmd='`$ECHO "$file_magic_cmd" | $SED "$delay_single_quote_subst"`' +file_magic_glob='`$ECHO "$file_magic_glob" | $SED "$delay_single_quote_subst"`' +want_nocaseglob='`$ECHO "$want_nocaseglob" | $SED "$delay_single_quote_subst"`' +DLLTOOL='`$ECHO "$DLLTOOL" | $SED "$delay_single_quote_subst"`' +sharedlib_from_linklib_cmd='`$ECHO "$sharedlib_from_linklib_cmd" | $SED "$delay_single_quote_subst"`' +AR='`$ECHO "$AR" | $SED "$delay_single_quote_subst"`' +AR_FLAGS='`$ECHO "$AR_FLAGS" | $SED "$delay_single_quote_subst"`' +archiver_list_spec='`$ECHO "$archiver_list_spec" | $SED "$delay_single_quote_subst"`' +STRIP='`$ECHO "$STRIP" | $SED "$delay_single_quote_subst"`' +RANLIB='`$ECHO "$RANLIB" | $SED "$delay_single_quote_subst"`' +old_postinstall_cmds='`$ECHO "$old_postinstall_cmds" | $SED "$delay_single_quote_subst"`' +old_postuninstall_cmds='`$ECHO "$old_postuninstall_cmds" | $SED "$delay_single_quote_subst"`' +old_archive_cmds='`$ECHO "$old_archive_cmds" | $SED "$delay_single_quote_subst"`' +lock_old_archive_extraction='`$ECHO "$lock_old_archive_extraction" | $SED "$delay_single_quote_subst"`' +CC='`$ECHO "$CC" | $SED "$delay_single_quote_subst"`' +CFLAGS='`$ECHO "$CFLAGS" | $SED "$delay_single_quote_subst"`' +compiler='`$ECHO "$compiler" | $SED "$delay_single_quote_subst"`' +GCC='`$ECHO "$GCC" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_pipe='`$ECHO "$lt_cv_sys_global_symbol_pipe" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_cdecl='`$ECHO "$lt_cv_sys_global_symbol_to_cdecl" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_import='`$ECHO "$lt_cv_sys_global_symbol_to_import" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_c_name_address='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address" | $SED "$delay_single_quote_subst"`' +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix='`$ECHO "$lt_cv_sys_global_symbol_to_c_name_address_lib_prefix" | $SED "$delay_single_quote_subst"`' +lt_cv_nm_interface='`$ECHO "$lt_cv_nm_interface" | $SED "$delay_single_quote_subst"`' +nm_file_list_spec='`$ECHO "$nm_file_list_spec" | $SED "$delay_single_quote_subst"`' +lt_sysroot='`$ECHO "$lt_sysroot" | $SED "$delay_single_quote_subst"`' +lt_cv_truncate_bin='`$ECHO "$lt_cv_truncate_bin" | $SED "$delay_single_quote_subst"`' +objdir='`$ECHO "$objdir" | $SED "$delay_single_quote_subst"`' +MAGIC_CMD='`$ECHO "$MAGIC_CMD" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_no_builtin_flag='`$ECHO "$lt_prog_compiler_no_builtin_flag" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_pic='`$ECHO "$lt_prog_compiler_pic" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_wl='`$ECHO "$lt_prog_compiler_wl" | $SED "$delay_single_quote_subst"`' +lt_prog_compiler_static='`$ECHO "$lt_prog_compiler_static" | $SED "$delay_single_quote_subst"`' +lt_cv_prog_compiler_c_o='`$ECHO "$lt_cv_prog_compiler_c_o" | $SED "$delay_single_quote_subst"`' +need_locks='`$ECHO "$need_locks" | $SED "$delay_single_quote_subst"`' +MANIFEST_TOOL='`$ECHO "$MANIFEST_TOOL" | $SED "$delay_single_quote_subst"`' +DSYMUTIL='`$ECHO "$DSYMUTIL" | $SED "$delay_single_quote_subst"`' +NMEDIT='`$ECHO "$NMEDIT" | $SED "$delay_single_quote_subst"`' +LIPO='`$ECHO "$LIPO" | $SED "$delay_single_quote_subst"`' +OTOOL='`$ECHO "$OTOOL" | $SED "$delay_single_quote_subst"`' +OTOOL64='`$ECHO "$OTOOL64" | $SED "$delay_single_quote_subst"`' +libext='`$ECHO "$libext" | $SED "$delay_single_quote_subst"`' +shrext_cmds='`$ECHO "$shrext_cmds" | $SED "$delay_single_quote_subst"`' +extract_expsyms_cmds='`$ECHO "$extract_expsyms_cmds" | $SED "$delay_single_quote_subst"`' +archive_cmds_need_lc='`$ECHO "$archive_cmds_need_lc" | $SED "$delay_single_quote_subst"`' +enable_shared_with_static_runtimes='`$ECHO "$enable_shared_with_static_runtimes" | $SED "$delay_single_quote_subst"`' +export_dynamic_flag_spec='`$ECHO "$export_dynamic_flag_spec" | $SED "$delay_single_quote_subst"`' +whole_archive_flag_spec='`$ECHO "$whole_archive_flag_spec" | $SED "$delay_single_quote_subst"`' +compiler_needs_object='`$ECHO "$compiler_needs_object" | $SED "$delay_single_quote_subst"`' +old_archive_from_new_cmds='`$ECHO "$old_archive_from_new_cmds" | $SED "$delay_single_quote_subst"`' +old_archive_from_expsyms_cmds='`$ECHO "$old_archive_from_expsyms_cmds" | $SED "$delay_single_quote_subst"`' +archive_cmds='`$ECHO "$archive_cmds" | $SED "$delay_single_quote_subst"`' +archive_expsym_cmds='`$ECHO "$archive_expsym_cmds" | $SED "$delay_single_quote_subst"`' +module_cmds='`$ECHO "$module_cmds" | $SED "$delay_single_quote_subst"`' +module_expsym_cmds='`$ECHO "$module_expsym_cmds" | $SED "$delay_single_quote_subst"`' +with_gnu_ld='`$ECHO "$with_gnu_ld" | $SED "$delay_single_quote_subst"`' +allow_undefined_flag='`$ECHO "$allow_undefined_flag" | $SED "$delay_single_quote_subst"`' +no_undefined_flag='`$ECHO "$no_undefined_flag" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_flag_spec='`$ECHO "$hardcode_libdir_flag_spec" | $SED "$delay_single_quote_subst"`' +hardcode_libdir_separator='`$ECHO "$hardcode_libdir_separator" | $SED "$delay_single_quote_subst"`' +hardcode_direct='`$ECHO "$hardcode_direct" | $SED "$delay_single_quote_subst"`' +hardcode_direct_absolute='`$ECHO "$hardcode_direct_absolute" | $SED "$delay_single_quote_subst"`' +hardcode_minus_L='`$ECHO "$hardcode_minus_L" | $SED "$delay_single_quote_subst"`' +hardcode_shlibpath_var='`$ECHO "$hardcode_shlibpath_var" | $SED "$delay_single_quote_subst"`' +hardcode_automatic='`$ECHO "$hardcode_automatic" | $SED "$delay_single_quote_subst"`' +inherit_rpath='`$ECHO "$inherit_rpath" | $SED "$delay_single_quote_subst"`' +link_all_deplibs='`$ECHO "$link_all_deplibs" | $SED "$delay_single_quote_subst"`' +always_export_symbols='`$ECHO "$always_export_symbols" | $SED "$delay_single_quote_subst"`' +export_symbols_cmds='`$ECHO "$export_symbols_cmds" | $SED "$delay_single_quote_subst"`' +exclude_expsyms='`$ECHO "$exclude_expsyms" | $SED "$delay_single_quote_subst"`' +include_expsyms='`$ECHO "$include_expsyms" | $SED "$delay_single_quote_subst"`' +prelink_cmds='`$ECHO "$prelink_cmds" | $SED "$delay_single_quote_subst"`' +postlink_cmds='`$ECHO "$postlink_cmds" | $SED "$delay_single_quote_subst"`' +file_list_spec='`$ECHO "$file_list_spec" | $SED "$delay_single_quote_subst"`' +variables_saved_for_relink='`$ECHO "$variables_saved_for_relink" | $SED "$delay_single_quote_subst"`' +need_lib_prefix='`$ECHO "$need_lib_prefix" | $SED "$delay_single_quote_subst"`' +need_version='`$ECHO "$need_version" | $SED "$delay_single_quote_subst"`' +version_type='`$ECHO "$version_type" | $SED "$delay_single_quote_subst"`' +runpath_var='`$ECHO "$runpath_var" | $SED "$delay_single_quote_subst"`' +shlibpath_var='`$ECHO "$shlibpath_var" | $SED "$delay_single_quote_subst"`' +shlibpath_overrides_runpath='`$ECHO "$shlibpath_overrides_runpath" | $SED "$delay_single_quote_subst"`' +libname_spec='`$ECHO "$libname_spec" | $SED "$delay_single_quote_subst"`' +library_names_spec='`$ECHO "$library_names_spec" | $SED "$delay_single_quote_subst"`' +soname_spec='`$ECHO "$soname_spec" | $SED "$delay_single_quote_subst"`' +install_override_mode='`$ECHO "$install_override_mode" | $SED "$delay_single_quote_subst"`' +postinstall_cmds='`$ECHO "$postinstall_cmds" | $SED "$delay_single_quote_subst"`' +postuninstall_cmds='`$ECHO "$postuninstall_cmds" | $SED "$delay_single_quote_subst"`' +finish_cmds='`$ECHO "$finish_cmds" | $SED "$delay_single_quote_subst"`' +finish_eval='`$ECHO "$finish_eval" | $SED "$delay_single_quote_subst"`' +hardcode_into_libs='`$ECHO "$hardcode_into_libs" | $SED "$delay_single_quote_subst"`' +sys_lib_search_path_spec='`$ECHO "$sys_lib_search_path_spec" | $SED "$delay_single_quote_subst"`' +configure_time_dlsearch_path='`$ECHO "$configure_time_dlsearch_path" | $SED "$delay_single_quote_subst"`' +configure_time_lt_sys_library_path='`$ECHO "$configure_time_lt_sys_library_path" | $SED "$delay_single_quote_subst"`' +hardcode_action='`$ECHO "$hardcode_action" | $SED "$delay_single_quote_subst"`' +enable_dlopen='`$ECHO "$enable_dlopen" | $SED "$delay_single_quote_subst"`' +enable_dlopen_self='`$ECHO "$enable_dlopen_self" | $SED "$delay_single_quote_subst"`' +enable_dlopen_self_static='`$ECHO "$enable_dlopen_self_static" | $SED "$delay_single_quote_subst"`' +old_striplib='`$ECHO "$old_striplib" | $SED "$delay_single_quote_subst"`' +striplib='`$ECHO "$striplib" | $SED "$delay_single_quote_subst"`' + +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in SHELL \ +ECHO \ +PATH_SEPARATOR \ +SED \ +GREP \ +EGREP \ +FGREP \ +LD \ +NM \ +LN_S \ +lt_SP2NL \ +lt_NL2SP \ +reload_flag \ +OBJDUMP \ +deplibs_check_method \ +file_magic_cmd \ +file_magic_glob \ +want_nocaseglob \ +DLLTOOL \ +sharedlib_from_linklib_cmd \ +AR \ +AR_FLAGS \ +archiver_list_spec \ +STRIP \ +RANLIB \ +CC \ +CFLAGS \ +compiler \ +lt_cv_sys_global_symbol_pipe \ +lt_cv_sys_global_symbol_to_cdecl \ +lt_cv_sys_global_symbol_to_import \ +lt_cv_sys_global_symbol_to_c_name_address \ +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix \ +lt_cv_nm_interface \ +nm_file_list_spec \ +lt_cv_truncate_bin \ +lt_prog_compiler_no_builtin_flag \ +lt_prog_compiler_pic \ +lt_prog_compiler_wl \ +lt_prog_compiler_static \ +lt_cv_prog_compiler_c_o \ +need_locks \ +MANIFEST_TOOL \ +DSYMUTIL \ +NMEDIT \ +LIPO \ +OTOOL \ +OTOOL64 \ +shrext_cmds \ +export_dynamic_flag_spec \ +whole_archive_flag_spec \ +compiler_needs_object \ +with_gnu_ld \ +allow_undefined_flag \ +no_undefined_flag \ +hardcode_libdir_flag_spec \ +hardcode_libdir_separator \ +exclude_expsyms \ +include_expsyms \ +file_list_spec \ +variables_saved_for_relink \ +libname_spec \ +library_names_spec \ +soname_spec \ +install_override_mode \ +finish_eval \ +old_striplib \ +striplib; do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[\\\\\\\`\\"\\\$]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in reload_cmds \ +old_postinstall_cmds \ +old_postuninstall_cmds \ +old_archive_cmds \ +extract_expsyms_cmds \ +old_archive_from_new_cmds \ +old_archive_from_expsyms_cmds \ +archive_cmds \ +archive_expsym_cmds \ +module_cmds \ +module_expsym_cmds \ +export_symbols_cmds \ +prelink_cmds \ +postlink_cmds \ +postinstall_cmds \ +postuninstall_cmds \ +finish_cmds \ +sys_lib_search_path_spec \ +configure_time_dlsearch_path \ +configure_time_lt_sys_library_path; do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[\\\\\\\`\\"\\\$]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +ac_aux_dir='$ac_aux_dir' + +# See if we are running on zsh, and set the options that allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + + + PACKAGE='$PACKAGE' + VERSION='$VERSION' + RM='$RM' + ofile='$ofile' + + + + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "config.h") CONFIG_HEADERS="$CONFIG_HEADERS config.h" ;; + "depfiles") CONFIG_COMMANDS="$CONFIG_COMMANDS depfiles" ;; + "libtool") CONFIG_COMMANDS="$CONFIG_COMMANDS libtool" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + "src/bluetoothd.8") CONFIG_FILES="$CONFIG_FILES src/bluetoothd.8" ;; + "lib/bluez.pc") CONFIG_FILES="$CONFIG_FILES lib/bluez.pc" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test "${CONFIG_HEADERS+set}" = set || CONFIG_HEADERS=$config_headers + test "${CONFIG_COMMANDS+set}" = set || CONFIG_COMMANDS=$config_commands +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + +# Set up the scripts for CONFIG_HEADERS section. +# No need to generate them if there are no CONFIG_HEADERS. +# This happens for instance with `./config.status Makefile'. +if test -n "$CONFIG_HEADERS"; then +cat >"$ac_tmp/defines.awk" <<\_ACAWK || +BEGIN { +_ACEOF + +# Transform confdefs.h into an awk script `defines.awk', embedded as +# here-document in config.status, that substitutes the proper values into +# config.h.in to produce config.h. + +# Create a delimiter string that does not exist in confdefs.h, to ease +# handling of long lines. +ac_delim='%!_!# ' +for ac_last_try in false false :; do + ac_tt=`sed -n "/$ac_delim/p" confdefs.h` + if test -z "$ac_tt"; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_HEADERS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done + +# For the awk script, D is an array of macro values keyed by name, +# likewise P contains macro parameters if any. Preserve backslash +# newline sequences. + +ac_word_re=[_$as_cr_Letters][_$as_cr_alnum]* +sed -n ' +s/.\{148\}/&'"$ac_delim"'/g +t rset +:rset +s/^[ ]*#[ ]*define[ ][ ]*/ / +t def +d +:def +s/\\$// +t bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3"/p +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2"/p +d +:bsnl +s/["\\]/\\&/g +s/^ \('"$ac_word_re"'\)\(([^()]*)\)[ ]*\(.*\)/P["\1"]="\2"\ +D["\1"]=" \3\\\\\\n"\\/p +t cont +s/^ \('"$ac_word_re"'\)[ ]*\(.*\)/D["\1"]=" \2\\\\\\n"\\/p +t cont +d +:cont +n +s/.\{148\}/&'"$ac_delim"'/g +t clear +:clear +s/\\$// +t bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/"/p +d +:bsnlc +s/["\\]/\\&/g; s/^/"/; s/$/\\\\\\n"\\/p +b cont +' >$CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + for (key in D) D_is_set[key] = 1 + FS = "" +} +/^[\t ]*#[\t ]*(define|undef)[\t ]+$ac_word_re([\t (]|\$)/ { + line = \$ 0 + split(line, arg, " ") + if (arg[1] == "#") { + defundef = arg[2] + mac1 = arg[3] + } else { + defundef = substr(arg[1], 2) + mac1 = arg[2] + } + split(mac1, mac2, "(") #) + macro = mac2[1] + prefix = substr(line, 1, index(line, defundef) - 1) + if (D_is_set[macro]) { + # Preserve the white space surrounding the "#". + print prefix "define", macro P[macro] D[macro] + next + } else { + # Replace #undef with comments. This is necessary, for example, + # in the case of _POSIX_SOURCE, which is predefined and required + # on some systems where configure will not decide to define it. + if (defundef == "undef") { + print "/*", prefix defundef, macro, "*/" + next + } + } +} +{ print } +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + as_fn_error $? "could not setup config headers machinery" "$LINENO" 5 +fi # test -n "$CONFIG_HEADERS" + + +eval set X " :F $CONFIG_FILES :H $CONFIG_HEADERS :C $CONFIG_COMMANDS" +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + + case $INSTALL in + [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;; + *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;; + esac + ac_MKDIR_P=$MKDIR_P + case $MKDIR_P in + [\\/$]* | ?:[\\/]* ) ;; + */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;; + esac +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +s&@INSTALL@&$ac_INSTALL&;t t +s&@MKDIR_P@&$ac_MKDIR_P&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + :H) + # + # CONFIG_HEADER + # + if test x"$ac_file" != x-; then + { + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" + } >"$ac_tmp/config.h" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + if diff "$ac_file" "$ac_tmp/config.h" >/dev/null 2>&1; then + { $as_echo "$as_me:${as_lineno-$LINENO}: $ac_file is unchanged" >&5 +$as_echo "$as_me: $ac_file is unchanged" >&6;} + else + rm -f "$ac_file" + mv "$ac_tmp/config.h" "$ac_file" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + fi + else + $as_echo "/* $configure_input */" \ + && eval '$AWK -f "$ac_tmp/defines.awk"' "$ac_file_inputs" \ + || as_fn_error $? "could not create -" "$LINENO" 5 + fi +# Compute "$ac_file"'s index in $config_headers. +_am_arg="$ac_file" +_am_stamp_count=1 +for _am_header in $config_headers :; do + case $_am_header in + $_am_arg | $_am_arg:* ) + break ;; + * ) + _am_stamp_count=`expr $_am_stamp_count + 1` ;; + esac +done +echo "timestamp for $_am_arg" >`$as_dirname -- "$_am_arg" || +$as_expr X"$_am_arg" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$_am_arg" : 'X\(//\)[^/]' \| \ + X"$_am_arg" : 'X\(//\)$' \| \ + X"$_am_arg" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$_am_arg" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'`/stamp-h$_am_stamp_count + ;; + + :C) { $as_echo "$as_me:${as_lineno-$LINENO}: executing $ac_file commands" >&5 +$as_echo "$as_me: executing $ac_file commands" >&6;} + ;; + esac + + + case $ac_file$ac_mode in + "depfiles":C) test x"$AMDEP_TRUE" != x"" || { + # Older Autoconf quotes --file arguments for eval, but not when files + # are listed without --file. Let's play safe and only enable the eval + # if we detect the quoting. + # TODO: see whether this extra hack can be removed once we start + # requiring Autoconf 2.70 or later. + case $CONFIG_FILES in #( + *\'*) : + eval set x "$CONFIG_FILES" ;; #( + *) : + set x $CONFIG_FILES ;; #( + *) : + ;; +esac + shift + # Used to flag and report bootstrapping failures. + am_rc=0 + for am_mf + do + # Strip MF so we end up with the name of the file. + am_mf=`$as_echo "$am_mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile which includes + # dependency-tracking related rules and includes. + # Grep'ing the whole file directly is not great: AIX grep has a line + # limit of 2048, but all sed's we know have understand at least 4000. + sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ + || continue + am_dirpart=`$as_dirname -- "$am_mf" || +$as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$am_mf" : 'X\(//\)[^/]' \| \ + X"$am_mf" : 'X\(//\)$' \| \ + X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$am_mf" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + am_filepart=`$as_basename -- "$am_mf" || +$as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \ + X"$am_mf" : 'X\(//\)$' \| \ + X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$am_mf" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + { echo "$as_me:$LINENO: cd "$am_dirpart" \ + && sed -e '/# am--include-marker/d' "$am_filepart" \ + | $MAKE -f - am--depfiles" >&5 + (cd "$am_dirpart" \ + && sed -e '/# am--include-marker/d' "$am_filepart" \ + | $MAKE -f - am--depfiles) >&5 2>&5 + ac_status=$? + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } || am_rc=$? + done + if test $am_rc -ne 0; then + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "Something went wrong bootstrapping makefile fragments + for automatic dependency tracking. Try re-running configure with the + '--disable-dependency-tracking' option to at least be able to build + the package (albeit without support for automatic dependency tracking). +See \`config.log' for more details" "$LINENO" 5; } + fi + { am_dirpart=; unset am_dirpart;} + { am_filepart=; unset am_filepart;} + { am_mf=; unset am_mf;} + { am_rc=; unset am_rc;} + rm -f conftest-deps.mk +} + ;; + "libtool":C) + + # See if we are running on zsh, and set the options that allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST + fi + + cfgfile=${ofile}T + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL +# Generated automatically by $as_me ($PACKAGE) $VERSION +# NOTE: Changes made to this file will be lost: look at ltmain.sh. + +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit, 1996 + +# Copyright (C) 2014 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program or library that is built +# using GNU Libtool, you may include this file under the same +# distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +# The names of the tagged configurations supported by this script. +available_tags='' + +# Configured defaults for sys_lib_dlsearch_path munging. +: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} + +# ### BEGIN LIBTOOL CONFIG + +# Whether or not to build static libraries. +build_old_libs=$enable_static + +# Which release of libtool.m4 was used? +macro_version=$macro_version +macro_revision=$macro_revision + +# Whether or not to build shared libraries. +build_libtool_libs=$enable_shared + +# What type of objects to build. +pic_mode=$pic_mode + +# Whether or not to optimize for fast installation. +fast_install=$enable_fast_install + +# Shared archive member basename,for filename based shared library versioning on AIX. +shared_archive_member_spec=$shared_archive_member_spec + +# Shell to use when invoking shell scripts. +SHELL=$lt_SHELL + +# An echo program that protects backslashes. +ECHO=$lt_ECHO + +# The PATH separator for the build system. +PATH_SEPARATOR=$lt_PATH_SEPARATOR + +# The host system. +host_alias=$host_alias +host=$host +host_os=$host_os + +# The build system. +build_alias=$build_alias +build=$build +build_os=$build_os + +# A sed program that does not truncate output. +SED=$lt_SED + +# Sed that helps us avoid accidentally triggering echo(1) options like -n. +Xsed="\$SED -e 1s/^X//" + +# A grep program that handles long lines. +GREP=$lt_GREP + +# An ERE matcher. +EGREP=$lt_EGREP + +# A literal string matcher. +FGREP=$lt_FGREP + +# A BSD- or MS-compatible name lister. +NM=$lt_NM + +# Whether we need soft or hard links. +LN_S=$lt_LN_S + +# What is the maximum length of a command? +max_cmd_len=$max_cmd_len + +# Object file suffix (normally "o"). +objext=$ac_objext + +# Executable file suffix (normally ""). +exeext=$exeext + +# whether the shell understands "unset". +lt_unset=$lt_unset + +# turn spaces into newlines. +SP2NL=$lt_lt_SP2NL + +# turn newlines into spaces. +NL2SP=$lt_lt_NL2SP + +# convert \$build file names to \$host format. +to_host_file_cmd=$lt_cv_to_host_file_cmd + +# convert \$build files to toolchain format. +to_tool_file_cmd=$lt_cv_to_tool_file_cmd + +# An object symbol dumper. +OBJDUMP=$lt_OBJDUMP + +# Method to check whether dependent libraries are shared objects. +deplibs_check_method=$lt_deplibs_check_method + +# Command to use when deplibs_check_method = "file_magic". +file_magic_cmd=$lt_file_magic_cmd + +# How to find potential files when deplibs_check_method = "file_magic". +file_magic_glob=$lt_file_magic_glob + +# Find potential files using nocaseglob when deplibs_check_method = "file_magic". +want_nocaseglob=$lt_want_nocaseglob + +# DLL creation program. +DLLTOOL=$lt_DLLTOOL + +# Command to associate shared and link libraries. +sharedlib_from_linklib_cmd=$lt_sharedlib_from_linklib_cmd + +# The archiver. +AR=$lt_AR + +# Flags to create an archive. +AR_FLAGS=$lt_AR_FLAGS + +# How to feed a file listing to the archiver. +archiver_list_spec=$lt_archiver_list_spec + +# A symbol stripping program. +STRIP=$lt_STRIP + +# Commands used to install an old-style archive. +RANLIB=$lt_RANLIB +old_postinstall_cmds=$lt_old_postinstall_cmds +old_postuninstall_cmds=$lt_old_postuninstall_cmds + +# Whether to use a lock for old archive extraction. +lock_old_archive_extraction=$lock_old_archive_extraction + +# A C compiler. +LTCC=$lt_CC + +# LTCC compiler flags. +LTCFLAGS=$lt_CFLAGS + +# Take the output of nm and produce a listing of raw symbols and C names. +global_symbol_pipe=$lt_lt_cv_sys_global_symbol_pipe + +# Transform the output of nm in a proper C declaration. +global_symbol_to_cdecl=$lt_lt_cv_sys_global_symbol_to_cdecl + +# Transform the output of nm into a list of symbols to manually relocate. +global_symbol_to_import=$lt_lt_cv_sys_global_symbol_to_import + +# Transform the output of nm in a C name address pair. +global_symbol_to_c_name_address=$lt_lt_cv_sys_global_symbol_to_c_name_address + +# Transform the output of nm in a C name address pair when lib prefix is needed. +global_symbol_to_c_name_address_lib_prefix=$lt_lt_cv_sys_global_symbol_to_c_name_address_lib_prefix + +# The name lister interface. +nm_interface=$lt_lt_cv_nm_interface + +# Specify filename containing input files for \$NM. +nm_file_list_spec=$lt_nm_file_list_spec + +# The root where to search for dependent libraries,and where our libraries should be installed. +lt_sysroot=$lt_sysroot + +# Command to truncate a binary pipe. +lt_truncate_bin=$lt_lt_cv_truncate_bin + +# The name of the directory that contains temporary libtool files. +objdir=$objdir + +# Used to examine libraries when file_magic_cmd begins with "file". +MAGIC_CMD=$MAGIC_CMD + +# Must we lock files when doing compilation? +need_locks=$lt_need_locks + +# Manifest tool. +MANIFEST_TOOL=$lt_MANIFEST_TOOL + +# Tool to manipulate archived DWARF debug symbol files on Mac OS X. +DSYMUTIL=$lt_DSYMUTIL + +# Tool to change global to local symbols on Mac OS X. +NMEDIT=$lt_NMEDIT + +# Tool to manipulate fat objects and archives on Mac OS X. +LIPO=$lt_LIPO + +# ldd/readelf like tool for Mach-O binaries on Mac OS X. +OTOOL=$lt_OTOOL + +# ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4. +OTOOL64=$lt_OTOOL64 + +# Old archive suffix (normally "a"). +libext=$libext + +# Shared library suffix (normally ".so"). +shrext_cmds=$lt_shrext_cmds + +# The commands to extract the exported symbol list from a shared archive. +extract_expsyms_cmds=$lt_extract_expsyms_cmds + +# Variables whose values should be saved in libtool wrapper scripts and +# restored at link time. +variables_saved_for_relink=$lt_variables_saved_for_relink + +# Do we need the "lib" prefix for modules? +need_lib_prefix=$need_lib_prefix + +# Do we need a version for libraries? +need_version=$need_version + +# Library versioning type. +version_type=$version_type + +# Shared library runtime path variable. +runpath_var=$runpath_var + +# Shared library path variable. +shlibpath_var=$shlibpath_var + +# Is shlibpath searched before the hard-coded library search path? +shlibpath_overrides_runpath=$shlibpath_overrides_runpath + +# Format of library name prefix. +libname_spec=$lt_libname_spec + +# List of archive names. First name is the real one, the rest are links. +# The last name is the one that the linker finds with -lNAME +library_names_spec=$lt_library_names_spec + +# The coded name of the library, if different from the real name. +soname_spec=$lt_soname_spec + +# Permission mode override for installation of shared libraries. +install_override_mode=$lt_install_override_mode + +# Command to use after installation of a shared archive. +postinstall_cmds=$lt_postinstall_cmds + +# Command to use after uninstallation of a shared archive. +postuninstall_cmds=$lt_postuninstall_cmds + +# Commands used to finish a libtool library installation in a directory. +finish_cmds=$lt_finish_cmds + +# As "finish_cmds", except a single script fragment to be evaled but +# not shown. +finish_eval=$lt_finish_eval + +# Whether we should hardcode library paths into libraries. +hardcode_into_libs=$hardcode_into_libs + +# Compile-time system search path for libraries. +sys_lib_search_path_spec=$lt_sys_lib_search_path_spec + +# Detected run-time system search path for libraries. +sys_lib_dlsearch_path_spec=$lt_configure_time_dlsearch_path + +# Explicit LT_SYS_LIBRARY_PATH set during ./configure time. +configure_time_lt_sys_library_path=$lt_configure_time_lt_sys_library_path + +# Whether dlopen is supported. +dlopen_support=$enable_dlopen + +# Whether dlopen of programs is supported. +dlopen_self=$enable_dlopen_self + +# Whether dlopen of statically linked programs is supported. +dlopen_self_static=$enable_dlopen_self_static + +# Commands to strip libraries. +old_striplib=$lt_old_striplib +striplib=$lt_striplib + + +# The linker used to build libraries. +LD=$lt_LD + +# How to create reloadable object files. +reload_flag=$lt_reload_flag +reload_cmds=$lt_reload_cmds + +# Commands used to build an old-style archive. +old_archive_cmds=$lt_old_archive_cmds + +# A language specific compiler. +CC=$lt_compiler + +# Is the compiler the GNU compiler? +with_gcc=$GCC + +# Compiler flag to turn off builtin functions. +no_builtin_flag=$lt_lt_prog_compiler_no_builtin_flag + +# Additional compiler flags for building library objects. +pic_flag=$lt_lt_prog_compiler_pic + +# How to pass a linker flag through the compiler. +wl=$lt_lt_prog_compiler_wl + +# Compiler flag to prevent dynamic linking. +link_static_flag=$lt_lt_prog_compiler_static + +# Does compiler simultaneously support -c and -o options? +compiler_c_o=$lt_lt_cv_prog_compiler_c_o + +# Whether or not to add -lc for building shared libraries. +build_libtool_need_lc=$archive_cmds_need_lc + +# Whether or not to disallow shared libs when runtime libs are static. +allow_libtool_libs_with_static_runtimes=$enable_shared_with_static_runtimes + +# Compiler flag to allow reflexive dlopens. +export_dynamic_flag_spec=$lt_export_dynamic_flag_spec + +# Compiler flag to generate shared objects directly from archives. +whole_archive_flag_spec=$lt_whole_archive_flag_spec + +# Whether the compiler copes with passing no objects directly. +compiler_needs_object=$lt_compiler_needs_object + +# Create an old-style archive from a shared archive. +old_archive_from_new_cmds=$lt_old_archive_from_new_cmds + +# Create a temporary old-style archive to link instead of a shared archive. +old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds + +# Commands used to build a shared archive. +archive_cmds=$lt_archive_cmds +archive_expsym_cmds=$lt_archive_expsym_cmds + +# Commands used to build a loadable module if different from building +# a shared archive. +module_cmds=$lt_module_cmds +module_expsym_cmds=$lt_module_expsym_cmds + +# Whether we are building with GNU ld or not. +with_gnu_ld=$lt_with_gnu_ld + +# Flag that allows shared libraries with undefined symbols to be built. +allow_undefined_flag=$lt_allow_undefined_flag + +# Flag that enforces no undefined symbols. +no_undefined_flag=$lt_no_undefined_flag + +# Flag to hardcode \$libdir into a binary during linking. +# This must work even if \$libdir does not exist +hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec + +# Whether we need a single "-rpath" flag with a separated argument. +hardcode_libdir_separator=$lt_hardcode_libdir_separator + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary. +hardcode_direct=$hardcode_direct + +# Set to "yes" if using DIR/libNAME\$shared_ext during linking hardcodes +# DIR into the resulting binary and the resulting library dependency is +# "absolute",i.e impossible to change by setting \$shlibpath_var if the +# library is relocated. +hardcode_direct_absolute=$hardcode_direct_absolute + +# Set to "yes" if using the -LDIR flag during linking hardcodes DIR +# into the resulting binary. +hardcode_minus_L=$hardcode_minus_L + +# Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR +# into the resulting binary. +hardcode_shlibpath_var=$hardcode_shlibpath_var + +# Set to "yes" if building a shared library automatically hardcodes DIR +# into the library and all subsequent libraries and executables linked +# against it. +hardcode_automatic=$hardcode_automatic + +# Set to yes if linker adds runtime paths of dependent libraries +# to runtime path list. +inherit_rpath=$inherit_rpath + +# Whether libtool must link a program against all its dependency libraries. +link_all_deplibs=$link_all_deplibs + +# Set to "yes" if exported symbols are required. +always_export_symbols=$always_export_symbols + +# The commands to list exported symbols. +export_symbols_cmds=$lt_export_symbols_cmds + +# Symbols that should not be listed in the preloaded symbols. +exclude_expsyms=$lt_exclude_expsyms + +# Symbols that must always be exported. +include_expsyms=$lt_include_expsyms + +# Commands necessary for linking programs (against libraries) with templates. +prelink_cmds=$lt_prelink_cmds + +# Commands necessary for finishing linking programs. +postlink_cmds=$lt_postlink_cmds + +# Specify filename containing input files. +file_list_spec=$lt_file_list_spec + +# How to hardcode a shared library path into an executable. +hardcode_action=$hardcode_action + +# ### END LIBTOOL CONFIG + +_LT_EOF + + cat <<'_LT_EOF' >> "$cfgfile" + +# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE + +# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x$2 in + x) + ;; + *:) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'` \$$1\" + ;; + x:*) + eval $1=\"\$$1 `$ECHO $2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval $1=\"\$$1\ `$ECHO $2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval $1=\"`$ECHO $2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \$$1\" + ;; + *) + eval $1=\"`$ECHO $2 | $SED 's/:/ /g'`\" + ;; + esac +} + + +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in $*""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} + + +# ### END FUNCTIONS SHARED WITH CONFIGURE + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + +ltmain=$ac_aux_dir/ltmain.sh + + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" + + ;; + + esac +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..57a816f --- /dev/null +++ b/configure.ac @@ -0,0 +1,406 @@ +AC_PREREQ(2.60) +AC_INIT(bluez, 5.52) + +AM_INIT_AUTOMAKE([foreign subdir-objects color-tests silent-rules + tar-pax no-dist-gzip dist-xz]) +AC_CONFIG_HEADERS(config.h) + +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AM_MAINTAINER_MODE + +AC_PREFIX_DEFAULT(/usr/local) + +PKG_PROG_PKG_CONFIG + +COMPILER_FLAGS + +AC_LANG_C + +AC_C_RESTRICT + +AC_PROG_CC +AM_PROG_CC_C_O +AC_PROG_CC_PIE +AC_PROG_INSTALL +AC_PROG_MKDIR_P + +m4_define([_LT_AC_TAGCONFIG], []) +m4_ifdef([AC_LIBTOOL_TAGS], [AC_LIBTOOL_TAGS([])]) + +AC_DISABLE_STATIC +AC_PROG_LIBTOOL + +if (test "$USE_MAINTAINER_MODE" = "yes"); then + AC_CHECK_PROG(enable_coverage, [lcov], [yes], [no]) + AC_CHECK_PROG(enable_dbus_run_session, [dbus-run-session], [yes]) + AC_CHECK_PROG(enable_valgrind, [valgrind], [yes]) + AC_CHECK_HEADERS(valgrind/memcheck.h) +fi +AM_CONDITIONAL(COVERAGE, test "${enable_coverage}" = "yes") +AM_CONDITIONAL(DBUS_RUN_SESSION, test "${enable_dbus_run_session}" = "yes") +AM_CONDITIONAL(VALGRIND, test "${enable_valgrind}" = "yes") + +MISC_FLAGS + +AC_ARG_ENABLE(threads, AC_HELP_STRING([--enable-threads], + [enable threading support]), [enable_threads=${enableval}]) + +AC_CHECK_FUNCS(explicit_bzero) + +AC_CHECK_FUNC(signalfd, dummy=yes, + AC_MSG_ERROR(signalfd support is required)) + +AC_CHECK_LIB(rt, clock_gettime, dummy=yes, + AC_MSG_ERROR(realtime clock support is required)) + +AC_CHECK_LIB(pthread, pthread_create, dummy=yes, + AC_MSG_ERROR(posix thread support is required)) + +AC_CHECK_LIB(dl, dlopen, dummy=yes, + AC_MSG_ERROR(dynamic linking loader is required)) + +AC_CHECK_HEADERS(linux/types.h linux/if_alg.h) + +PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.28, dummy=yes, + AC_MSG_ERROR(GLib >= 2.28 is required)) +AC_SUBST(GLIB_CFLAGS) +AC_SUBST(GLIB_LIBS) + +if (test "${enable_threads}" = "yes"); then + AC_DEFINE(NEED_THREADS, 1, [Define if threading support is required]) + PKG_CHECK_MODULES(GTHREAD, gthread-2.0 >= 2.16, dummy=yes, + AC_MSG_ERROR(GThread >= 2.16 is required)) + GLIB_CFLAGS="$GLIB_CFLAGS $GTHREAD_CFLAGS" + GLIB_LIBS="$GLIB_LIBS $GTHREAD_LIBS" +fi + +PKG_CHECK_MODULES(DBUS, dbus-1 >= 1.6, dummy=yes, + AC_MSG_ERROR(D-Bus >= 1.6 is required)) +AC_SUBST(DBUS_CFLAGS) +AC_SUBST(DBUS_LIBS) + +AC_ARG_WITH([dbusconfdir], AC_HELP_STRING([--with-dbusconfdir=DIR], + [path to D-Bus configuration directory]), + [path_dbusconfdir=${withval}]) +if (test -z "${path_dbusconfdir}"); then + AC_MSG_CHECKING([D-Bus configuration directory]) + path_dbusconfdir="`$PKG_CONFIG --variable=sysconfdir dbus-1`" + if (test -z "${path_dbusconfdir}"); then + AC_MSG_ERROR([D-Bus configuration directory is required]) + fi + AC_MSG_RESULT([${path_dbusconfdir}]) +fi +AC_SUBST(DBUS_CONFDIR, [${path_dbusconfdir}]) + +AC_ARG_WITH([dbussystembusdir], AC_HELP_STRING([--with-dbussystembusdir=DIR], + [path to D-Bus system bus services directory]), + [path_dbussystembusdir=${withval}]) +if (test -z "${path_dbussystembusdir}"); then + AC_MSG_CHECKING([D-Bus system bus services dir]) + path_dbussystembusdir="`$PKG_CONFIG --variable=system_bus_services_dir dbus-1`" + if (test -z "${path_dbussystembusdir}"); then + AC_MSG_ERROR([D-Bus system bus services directory is required]) + fi + AC_MSG_RESULT([${path_dbussystembusdir}]) +fi +AC_SUBST(DBUS_SYSTEMBUSDIR, [${path_dbussystembusdir}]) + +AC_ARG_WITH([dbussessionbusdir], AC_HELP_STRING([--with-dbussessionbusdir=DIR], + [path to D-Bus session bus services directory]), + [path_dbussessionbusdir=${withval}]) +if (test -z "${path_dbussessionbusdir}"); then + AC_MSG_CHECKING([D-Bus session bus services dir]) + path_dbussessionbusdir="`$PKG_CONFIG --variable=session_bus_services_dir dbus-1`" + if (test -z "${path_dbussessionbusdir}"); then + AC_MSG_ERROR([D-Bus session bus services directory is required]) + fi + AC_MSG_RESULT([${path_dbussessionbusdir}]) +fi +AC_SUBST(DBUS_SESSIONBUSDIR, [${path_dbussessionbusdir}]) + +AC_ARG_WITH([zsh-completion-dir], AC_HELP_STRING([--with-zsh-completion-dir=DIR], + [path to install zsh completions]), + [path_zshcompletiondir=${withval}], + [path_zshcompletiondir="yes"]) + +if (test "${path_zshcompletiondir}" = "yes"); then + path_zshcompletiondir="$datarootdir/zsh/site-functions" + AC_MSG_RESULT([${path_zshcompletiondir}]) +fi +AC_SUBST(ZSH_COMPLETIONDIR, [${path_zshcompletiondir}]) +AM_CONDITIONAL(ZSH_COMPLETIONS, test "${path_zshcompletiondir}" != "no") + +AC_ARG_ENABLE(backtrace, AC_HELP_STRING([--enable-backtrace], + [compile backtrace support]), [enable_backtrace=${enableval}]) + +if (test "${enable_backtrace}" = "yes"); then + AC_CHECK_HEADER(elfutils/libdwfl.h, dummy=yes, + AC_MSG_ERROR(elfutils support is required)) + AC_DEFINE(HAVE_BACKTRACE_SUPPORT, 1, + [Define to 1 if you have the backtrace support.]) + BACKTRACE_CFLAGS="" + BACKTRACE_LIBS="-ldw" + AC_SUBST(BACKTRACE_CFLAGS) + AC_SUBST(BACKTRACE_LIBS) +fi + +AC_ARG_ENABLE(library, AC_HELP_STRING([--enable-library], + [install Bluetooth library]), [enable_library=${enableval}]) +AM_CONDITIONAL(LIBRARY, test "${enable_library}" = "yes") + +AC_ARG_ENABLE(test, AC_HELP_STRING([--enable-test], + [enable test/example scripts]), [enable_test=${enableval}]) +AM_CONDITIONAL(TEST, test "${enable_test}" = "yes") + +AC_ARG_ENABLE(nfc, AC_HELP_STRING([--enable-nfc], + [enable NFC paring]), [enable_nfc=${enableval}]) +AM_CONDITIONAL(NFC, test "${enable_nfc}" = "yes") + +AC_ARG_ENABLE(sap, AC_HELP_STRING([--enable-sap], + [enable SAP profile]), [enable_sap=${enableval}]) +AM_CONDITIONAL(SAP, test "${enable_sap}" = "yes") + +AC_ARG_ENABLE(a2dp, AC_HELP_STRING([--disable-a2dp], + [disable A2DP profile]), [enable_a2dp=${enableval}]) +AM_CONDITIONAL(A2DP, test "${enable_a2dp}" != "no") + +AC_ARG_ENABLE(avrcp, AC_HELP_STRING([--disable-avrcp], + [disable AVRCP profile]), [enable_avrcp=${enableval}]) +AM_CONDITIONAL(AVRCP, test "${enable_avrcp}" != "no") + +AC_ARG_ENABLE(network, AC_HELP_STRING([--disable-network], + [disable network profiles]), [enable_network=${enableval}]) +AM_CONDITIONAL(NETWORK, test "${enable_network}" != "no") + +AC_ARG_ENABLE(hid, AC_HELP_STRING([--disable-hid], + [disable HID profile]), [enable_hid=${enableval}]) +AM_CONDITIONAL(HID, test "${enable_hid}" != "no") + +AC_ARG_ENABLE(hog, AC_HELP_STRING([--disable-hog], + [disable HoG profile]), [enable_hog=${enableval}]) +AM_CONDITIONAL(HOG, test "${enable_hog}" != "no") + +AC_ARG_ENABLE(health, AC_HELP_STRING([--enable-health], + [enable health profiles]), [enable_health=${enableval}]) +AM_CONDITIONAL(HEALTH, test "${enable_health}" = "yes") + +AC_ARG_ENABLE(tools, AC_HELP_STRING([--disable-tools], + [disable Bluetooth tools]), [enable_tools=${enableval}]) +AM_CONDITIONAL(TOOLS, test "${enable_tools}" != "no") + +AC_ARG_ENABLE(monitor, AC_HELP_STRING([--disable-monitor], + [disable Bluetooth monitor]), [enable_monitor=${enableval}]) +AM_CONDITIONAL(MONITOR, test "${enable_monitor}" != "no") + +AC_ARG_ENABLE(udev, AC_HELP_STRING([--disable-udev], + [disable udev device support]), [enable_udev=${enableval}]) +if (test "${enable_tools}" != "no" && test "${enable_udev}" != "no"); then + PKG_CHECK_MODULES(UDEV, libudev >= 172, dummy=yes, + AC_MSG_ERROR(libudev >= 172 is required)) + AC_SUBST(UDEV_CFLAGS) + AC_SUBST(UDEV_LIBS) + AC_CHECK_LIB(udev, udev_hwdb_new, + AC_DEFINE(HAVE_UDEV_HWDB_NEW, 1, + [Define to 1 if you have the udev_hwdb_new() function.])) +fi +AM_CONDITIONAL(UDEV, test "${enable_udev}" != "no") + +AC_ARG_WITH([udevdir], AC_HELP_STRING([--with-udevdir=DIR], + [path to udev directory]), [path_udevdir=${withval}]) +if (test "${enable_udev}" != "no" && test -z "${path_udevdir}"); then + AC_MSG_CHECKING([udev directory]) + path_udevdir="`$PKG_CONFIG --variable=udevdir udev`" + if (test -z "${path_udevdir}"); then + AC_MSG_ERROR([udev directory is required]) + fi + AC_MSG_RESULT([${path_udevdir}]) +fi +AC_SUBST(UDEV_DIR, [${path_udevdir}]) + +AM_CONDITIONAL(HID2HCI, test "${enable_tools}" != "no" && + test "${enable_udev}" != "no") + +AC_ARG_ENABLE(cups, AC_HELP_STRING([--disable-cups], + [disable CUPS printer support]), [enable_cups=${enableval}]) +AM_CONDITIONAL(CUPS, test "${enable_cups}" != "no") + +AC_ARG_ENABLE(mesh, AC_HELP_STRING([--enable-mesh], + [enable Mesh profile support]), [enable_mesh=${enableval}]) +AM_CONDITIONAL(MESH, test "${enable_mesh}" = "yes") + +if (test "${enable_mesh}" = "yes"); then + PKG_CHECK_MODULES(JSONC, json-c, dummy=yes, + AC_MSG_ERROR(json-c is required)) + AC_SUBST(JSON_CFLAGS) + AC_SUBST(JSON_LIBS) +fi + +AC_ARG_ENABLE(midi, AC_HELP_STRING([--enable-midi], + [enable MIDI support]), [enable_midi=${enableval}]) +AM_CONDITIONAL(MIDI, test "${enable_midi}" = "yes") + +if (test "${enable_midi}" = "yes"); then + PKG_CHECK_MODULES(ALSA, alsa, dummy=yes, + AC_MSG_ERROR(ALSA lib is required for MIDI support)) + AC_SUBST(ALSA_CFLAGS) + AC_SUBST(ALSA_LIBS) +fi + +AC_ARG_ENABLE(obex, AC_HELP_STRING([--disable-obex], + [disable OBEX profile support]), [enable_obex=${enableval}]) +if (test "${enable_obex}" != "no"); then + PKG_CHECK_MODULES(ICAL, libical, dummy=yes, + AC_MSG_ERROR(libical is required)) + AC_SUBST(ICAL_CFLAGS) + AC_SUBST(ICAL_LIBS) +fi +AM_CONDITIONAL(OBEX, test "${enable_obex}" != "no") + +AC_ARG_ENABLE(btpclient, AC_HELP_STRING([--enable-btpclient], + [enable BTP client]), [enable_btpclient=${enableval}]) +AM_CONDITIONAL(BTPCLIENT, test "${enable_btpclient}" = "yes") + +AC_ARG_ENABLE([external_ell], AC_HELP_STRING([--enable-external-ell], + [enable external Embedded Linux library]), + [enable_external_ell=${enableval}]) +if (test "${enable_external_ell}" = "yes"); then + PKG_CHECK_MODULES(ELL, ell >= 0.26, dummy=yes, + AC_MSG_ERROR(Embedded Linux library >= 0.26 is required)) + AC_SUBST(ELL_CFLAGS) + AC_SUBST(ELL_LIBS) +fi +AM_CONDITIONAL(EXTERNAL_ELL, test "${enable_external_ell}" = "yes" || + (test "${enable_btpclient}" != "yes" && + test "${enable_mesh}" != "yes")) +AM_CONDITIONAL(LIBSHARED_ELL, test "${enable_btpclient}" = "yes" || + test "${enable_mesh}" = "yes") + +AC_ARG_ENABLE(client, AC_HELP_STRING([--disable-client], + [disable command line client]), [enable_client=${enableval}]) +AM_CONDITIONAL(CLIENT, test "${enable_client}" != "no") + +if (test "${enable_client}" != "no" || test "${enable_mesh}" = "yes"); then + AC_CHECK_HEADERS(readline/readline.h, enable_readline=yes, + AC_MSG_ERROR(readline header files are required)) +fi +AM_CONDITIONAL(READLINE, test "${enable_readline}" = "yes") + +AC_ARG_ENABLE(systemd, AC_HELP_STRING([--disable-systemd], + [disable systemd integration]), [enable_systemd=${enableval}]) +AM_CONDITIONAL(SYSTEMD, test "${enable_systemd}" != "no") + +AC_ARG_WITH([systemdsystemunitdir], + AC_HELP_STRING([--with-systemdsystemunitdir=DIR], + [path to systemd system unit directory]), + [path_systemunitdir=${withval}]) +if (test "${enable_systemd}" != "no" && test -z "${path_systemunitdir}"); then + AC_MSG_CHECKING([systemd system unit dir]) + path_systemunitdir="`$PKG_CONFIG --variable=systemdsystemunitdir systemd`" + if (test -z "${path_systemunitdir}"); then + AC_MSG_ERROR([systemd system unit directory is required]) + fi + AC_MSG_RESULT([${path_systemunitdir}]) +fi +AC_SUBST(SYSTEMD_SYSTEMUNITDIR, [${path_systemunitdir}]) + +AC_ARG_WITH([systemduserunitdir], + AC_HELP_STRING([--with-systemduserunitdir=DIR], + [path to systemd user unit directory]), + [path_userunitdir=${withval}]) +if (test "${enable_systemd}" != "no" && test -z "${path_userunitdir}"); then + AC_MSG_CHECKING([systemd user unit dir]) + path_userunitdir="`$PKG_CONFIG --variable=systemduserunitdir systemd`" + if (test -z "${path_userunitdir}"); then + AC_MSG_ERROR([systemd user unit directory is required]) + fi + AC_MSG_RESULT([${path_userunitdir}]) +fi +AC_SUBST(SYSTEMD_USERUNITDIR, [${path_userunitdir}]) + +AC_ARG_ENABLE(datafiles, AC_HELP_STRING([--disable-datafiles], + [do not install configuration and data files]), + [enable_datafiles=${enableval}]) +AM_CONDITIONAL(DATAFILES, test "${enable_datafiles}" != "no") + +AC_ARG_ENABLE(manpages, AC_HELP_STRING([--enable-manpages], + [enable building of manual pages]), + [enable_manpages=${enableval}]) +AM_CONDITIONAL(MANPAGES, test "${enable_manpages}" = "yes") + +AC_ARG_ENABLE(testing, AC_HELP_STRING([--enable-testing], + [enable testing tools]), + [enable_testing=${enableval}]) +AM_CONDITIONAL(TESTING, test "${enable_testing}" = "yes") + +AC_ARG_ENABLE(experimental, AC_HELP_STRING([--enable-experimental], + [enable experimental tools]), + [enable_experimental=${enableval}]) +AM_CONDITIONAL(EXPERIMENTAL, test "${enable_experimental}" = "yes") + +AC_ARG_ENABLE(deprecated, AC_HELP_STRING([--enable-deprecated], + [enable deprecated tools]), + [enable_deprecated=${enableval}]) +AM_CONDITIONAL(DEPRECATED, test "${enable_deprecated}" = "yes") + +AC_ARG_ENABLE(sixaxis, AC_HELP_STRING([--enable-sixaxis], + [enable sixaxis plugin]), [enable_sixaxis=${enableval}]) +AM_CONDITIONAL(SIXAXIS, test "${enable_sixaxis}" = "yes" && + test "${enable_udev}" != "no") + +AC_ARG_ENABLE(logger, AC_HELP_STRING([--enable-logger], + [enable HCI logger service]), [enable_logger=${enableval}]) +AM_CONDITIONAL(LOGGER, test "${enable_logger}" = "yes") + +if (test "${prefix}" = "NONE"); then + dnl no prefix and no localstatedir, so default to /var + if (test "$localstatedir" = '${prefix}/var'); then + AC_SUBST([localstatedir], ['/var']) + fi + + prefix="${ac_default_prefix}" +fi + +if (test "$localstatedir" = '${prefix}/var'); then + storagedir="${prefix}/var/lib/bluetooth" +else + storagedir="${localstatedir}/lib/bluetooth" +fi +AC_DEFINE_UNQUOTED(STORAGEDIR, "${storagedir}", + [Directory for the storage files]) + +if (test "$sysconfdir" = '${prefix}/etc'); then + configdir="${prefix}/etc/bluetooth" +else + configdir="${sysconfdir}/bluetooth" +fi +AC_DEFINE_UNQUOTED(CONFIGDIR, "${configdir}", + [Directory for the configuration files]) +AC_SUBST(CONFIGDIR, "${configdir}") + +AC_DEFINE_UNQUOTED(MESH_STORAGEDIR, "${storagedir}/mesh", + [Directory for the mesh daemon storage files]) + +AC_ARG_ENABLE(android, AC_HELP_STRING([--enable-android], + [enable BlueZ for Android]), + [enable_android=${enableval}]) +AM_CONDITIONAL(ANDROID, test "${enable_android}" = "yes") + +if (test "${enable_android}" = "yes"); then + PKG_CHECK_MODULES(SBC, sbc >= 1.2, dummy=yes, + AC_MSG_ERROR(SBC library >= 1.2 is required)) + AC_SUBST(SBC_CFLAGS) + AC_SUBST(SBC_LIBS) +fi + +if (test "${enable_android}" = "yes"); then + PKG_CHECK_MODULES(SPEEXDSP, speexdsp >= 1.2, dummy=yes, + AC_MSG_ERROR(SPEEXDSP library >= 1.2 is required)) + AC_SUBST(SPEEXDSP_CFLAGS) + AC_SUBST(SPEEXDSP_LIBS) +fi + +AC_DEFINE_UNQUOTED(ANDROID_STORAGEDIR, "${storagedir}/android", + [Directory for the Android daemon storage files]) + +AC_OUTPUT(Makefile src/bluetoothd.8 lib/bluez.pc) diff --git a/depcomp b/depcomp new file mode 100755 index 0000000..65cbf70 --- /dev/null +++ b/depcomp @@ -0,0 +1,791 @@ +#! /bin/sh +# depcomp - compile a program generating dependencies as side-effects + +scriptversion=2018-03-07.03; # UTC + +# Copyright (C) 1999-2018 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Alexandre Oliva . + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: depcomp [--help] [--version] PROGRAM [ARGS] + +Run PROGRAMS ARGS to compile a file, generating dependencies +as side-effects. + +Environment variables: + depmode Dependency tracking mode. + source Source file read by 'PROGRAMS ARGS'. + object Object file output by 'PROGRAMS ARGS'. + DEPDIR directory where to store dependencies. + depfile Dependency file to output. + tmpdepfile Temporary file to use when outputting dependencies. + libtool Whether libtool is used (yes/no). + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "depcomp $scriptversion" + exit $? + ;; +esac + +# Get the directory component of the given path, and save it in the +# global variables '$dir'. Note that this directory component will +# be either empty or ending with a '/' character. This is deliberate. +set_dir_from () +{ + case $1 in + */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; + *) dir=;; + esac +} + +# Get the suffix-stripped basename of the given path, and save it the +# global variable '$base'. +set_base_from () +{ + base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` +} + +# If no dependency file was actually created by the compiler invocation, +# we still have to create a dummy depfile, to avoid errors with the +# Makefile "include basename.Plo" scheme. +make_dummy_depfile () +{ + echo "#dummy" > "$depfile" +} + +# Factor out some common post-processing of the generated depfile. +# Requires the auxiliary global variable '$tmpdepfile' to be set. +aix_post_process_depfile () +{ + # If the compiler actually managed to produce a dependency file, + # post-process it. + if test -f "$tmpdepfile"; then + # Each line is of the form 'foo.o: dependency.h'. + # Do two passes, one to just change these to + # $object: dependency.h + # and one to simply output + # dependency.h: + # which is needed to avoid the deleted-header problem. + { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" + sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" + } > "$depfile" + rm -f "$tmpdepfile" + else + make_dummy_depfile + fi +} + +# A tabulation character. +tab=' ' +# A newline character. +nl=' +' +# Character ranges might be problematic outside the C locale. +# These definitions help. +upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ +lower=abcdefghijklmnopqrstuvwxyz +digits=0123456789 +alpha=${upper}${lower} + +if test -z "$depmode" || test -z "$source" || test -z "$object"; then + echo "depcomp: Variables source, object and depmode must be set" 1>&2 + exit 1 +fi + +# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. +depfile=${depfile-`echo "$object" | + sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} +tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} + +rm -f "$tmpdepfile" + +# Avoid interferences from the environment. +gccflag= dashmflag= + +# Some modes work just like other modes, but use different flags. We +# parameterize here, but still list the modes in the big case below, +# to make depend.m4 easier to write. Note that we *cannot* use a case +# here, because this file can only contain one case statement. +if test "$depmode" = hp; then + # HP compiler uses -M and no extra arg. + gccflag=-M + depmode=gcc +fi + +if test "$depmode" = dashXmstdout; then + # This is just like dashmstdout with a different argument. + dashmflag=-xM + depmode=dashmstdout +fi + +cygpath_u="cygpath -u -f -" +if test "$depmode" = msvcmsys; then + # This is just like msvisualcpp but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvisualcpp +fi + +if test "$depmode" = msvc7msys; then + # This is just like msvc7 but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvc7 +fi + +if test "$depmode" = xlc; then + # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. + gccflag=-qmakedep=gcc,-MF + depmode=gcc +fi + +case "$depmode" in +gcc3) +## gcc 3 implements dependency tracking that does exactly what +## we want. Yay! Note: for some reason libtool 1.4 doesn't like +## it if -MD -MP comes after the -MF stuff. Hmm. +## Unfortunately, FreeBSD c89 acceptance of flags depends upon +## the command line argument order; so add the flags where they +## appear in depend2.am. Note that the slowdown incurred here +## affects only configure: in makefiles, %FASTDEP% shortcuts this. + for arg + do + case $arg in + -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; + *) set fnord "$@" "$arg" ;; + esac + shift # fnord + shift # $arg + done + "$@" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + mv "$tmpdepfile" "$depfile" + ;; + +gcc) +## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. +## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. +## (see the conditional assignment to $gccflag above). +## There are various ways to get dependency output from gcc. Here's +## why we pick this rather obscure method: +## - Don't want to use -MD because we'd like the dependencies to end +## up in a subdir. Having to rename by hand is ugly. +## (We might end up doing this anyway to support other compilers.) +## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like +## -MM, not -M (despite what the docs say). Also, it might not be +## supported by the other compilers which use the 'gcc' depmode. +## - Using -M directly means running the compiler twice (even worse +## than renaming). + if test -z "$gccflag"; then + gccflag=-MD, + fi + "$@" -Wp,"$gccflag$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The second -e expression handles DOS-style file names with drive + # letters. + sed -e 's/^[^:]*: / /' \ + -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" +## This next piece of magic avoids the "deleted header file" problem. +## The problem is that when a header file which appears in a .P file +## is deleted, the dependency causes make to die (because there is +## typically no way to rebuild the header). We avoid this by adding +## dummy dependencies for each header file. Too bad gcc doesn't do +## this for us directly. +## Some versions of gcc put a space before the ':'. On the theory +## that the space means something, we add a space to the output as +## well. hp depmode also adds that space, but also prefixes the VPATH +## to the object. Take care to not repeat it in the output. +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +sgi) + if test "$libtool" = yes; then + "$@" "-Wp,-MDupdate,$tmpdepfile" + else + "$@" -MDupdate "$tmpdepfile" + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + + if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files + echo "$object : \\" > "$depfile" + # Clip off the initial element (the dependent). Don't try to be + # clever and replace this with sed code, as IRIX sed won't handle + # lines with more than a fixed number of characters (4096 in + # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; + # the IRIX cc adds comments like '#:fec' to the end of the + # dependency line. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ + | tr "$nl" ' ' >> "$depfile" + echo >> "$depfile" + # The second pass generates a dummy entry for each header file. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ + >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" + ;; + +xlc) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +aix) + # The C for AIX Compiler uses -M and outputs the dependencies + # in a .u file. In older versions, this file always lives in the + # current directory. Also, the AIX compiler puts '$object:' at the + # start of each line; $object doesn't have directory information. + # Version 6 uses the directory in both cases. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.u + tmpdepfile2=$base.u + tmpdepfile3=$dir.libs/$base.u + "$@" -Wc,-M + else + tmpdepfile1=$dir$base.u + tmpdepfile2=$dir$base.u + tmpdepfile3=$dir$base.u + "$@" -M + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + aix_post_process_depfile + ;; + +tcc) + # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 + # FIXME: That version still under development at the moment of writing. + # Make that this statement remains true also for stable, released + # versions. + # It will wrap lines (doesn't matter whether long or short) with a + # trailing '\', as in: + # + # foo.o : \ + # foo.c \ + # foo.h \ + # + # It will put a trailing '\' even on the last line, and will use leading + # spaces rather than leading tabs (at least since its commit 0394caf7 + # "Emit spaces for -MD"). + "$@" -MD -MF "$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. + # We have to change lines of the first kind to '$object: \'. + sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" + # And for each line of the second kind, we have to emit a 'dep.h:' + # dummy dependency, to avoid the deleted-header problem. + sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" + rm -f "$tmpdepfile" + ;; + +## The order of this option in the case statement is important, since the +## shell code in configure will try each of these formats in the order +## listed in this file. A plain '-MD' option would be understood by many +## compilers, so we must ensure this comes after the gcc and icc options. +pgcc) + # Portland's C compiler understands '-MD'. + # Will always output deps to 'file.d' where file is the root name of the + # source file under compilation, even if file resides in a subdirectory. + # The object file name does not affect the name of the '.d' file. + # pgcc 10.2 will output + # foo.o: sub/foo.c sub/foo.h + # and will wrap long lines using '\' : + # foo.o: sub/foo.c ... \ + # sub/foo.h ... \ + # ... + set_dir_from "$object" + # Use the source, not the object, to determine the base name, since + # that's sadly what pgcc will do too. + set_base_from "$source" + tmpdepfile=$base.d + + # For projects that build the same source file twice into different object + # files, the pgcc approach of using the *source* file root name can cause + # problems in parallel builds. Use a locking strategy to avoid stomping on + # the same $tmpdepfile. + lockdir=$base.d-lock + trap " + echo '$0: caught signal, cleaning up...' >&2 + rmdir '$lockdir' + exit 1 + " 1 2 13 15 + numtries=100 + i=$numtries + while test $i -gt 0; do + # mkdir is a portable test-and-set. + if mkdir "$lockdir" 2>/dev/null; then + # This process acquired the lock. + "$@" -MD + stat=$? + # Release the lock. + rmdir "$lockdir" + break + else + # If the lock is being held by a different process, wait + # until the winning process is done or we timeout. + while test -d "$lockdir" && test $i -gt 0; do + sleep 1 + i=`expr $i - 1` + done + fi + i=`expr $i - 1` + done + trap - 1 2 13 15 + if test $i -le 0; then + echo "$0: failed to acquire lock after $numtries attempts" >&2 + echo "$0: check lockdir '$lockdir'" >&2 + exit 1 + fi + + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each line is of the form `foo.o: dependent.h', + # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. + # Do two passes, one to just change these to + # `$object: dependent.h' and one to simply `dependent.h:'. + sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp2) + # The "hp" stanza above does not work with aCC (C++) and HP's ia64 + # compilers, which have integrated preprocessors. The correct option + # to use with these is +Maked; it writes dependencies to a file named + # 'foo.d', which lands next to the object file, wherever that + # happens to be. + # Much of this is similar to the tru64 case; see comments there. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir.libs/$base.d + "$@" -Wc,+Maked + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + "$@" +Maked + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" + do + test -f "$tmpdepfile" && break + done + if test -f "$tmpdepfile"; then + sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" + # Add 'dependent.h:' lines. + sed -ne '2,${ + s/^ *// + s/ \\*$// + s/$/:/ + p + }' "$tmpdepfile" >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" "$tmpdepfile2" + ;; + +tru64) + # The Tru64 compiler uses -MD to generate dependencies as a side + # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. + # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put + # dependencies in 'foo.d' instead, so we check for that too. + # Subdirectories are respected. + set_dir_from "$object" + set_base_from "$object" + + if test "$libtool" = yes; then + # Libtool generates 2 separate objects for the 2 libraries. These + # two compilations output dependencies in $dir.libs/$base.o.d and + # in $dir$base.o.d. We have to check for both files, because + # one of the two compilations can be disabled. We should prefer + # $dir$base.o.d over $dir.libs/$base.o.d because the latter is + # automatically cleaned when .libs/ is deleted, while ignoring + # the former would cause a distcleancheck panic. + tmpdepfile1=$dir$base.o.d # libtool 1.5 + tmpdepfile2=$dir.libs/$base.o.d # Likewise. + tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 + "$@" -Wc,-MD + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + tmpdepfile3=$dir$base.d + "$@" -MD + fi + + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + # Same post-processing that is required for AIX mode. + aix_post_process_depfile + ;; + +msvc7) + if test "$libtool" = yes; then + showIncludes=-Wc,-showIncludes + else + showIncludes=-showIncludes + fi + "$@" $showIncludes > "$tmpdepfile" + stat=$? + grep -v '^Note: including file: ' "$tmpdepfile" + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The first sed program below extracts the file names and escapes + # backslashes for cygpath. The second sed program outputs the file + # name when reading, but also accumulates all include files in the + # hold buffer in order to output them again at the end. This only + # works with sed implementations that can handle large buffers. + sed < "$tmpdepfile" -n ' +/^Note: including file: *\(.*\)/ { + s//\1/ + s/\\/\\\\/g + p +}' | $cygpath_u | sort -u | sed -n ' +s/ /\\ /g +s/\(.*\)/'"$tab"'\1 \\/p +s/.\(.*\) \\/\1:/ +H +$ { + s/.*/'"$tab"'/ + G + p +}' >> "$depfile" + echo >> "$depfile" # make sure the fragment doesn't end with a backslash + rm -f "$tmpdepfile" + ;; + +msvc7msys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +#nosideeffect) + # This comment above is used by automake to tell side-effect + # dependency tracking mechanisms from slower ones. + +dashmstdout) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout, regardless of -o. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + test -z "$dashmflag" && dashmflag=-M + # Require at least two characters before searching for ':' + # in the target name. This is to cope with DOS-style filenames: + # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. + "$@" $dashmflag | + sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this sed invocation + # correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +dashXmstdout) + # This case only exists to satisfy depend.m4. It is never actually + # run, as this mode is specially recognized in the preamble. + exit 1 + ;; + +makedepend) + "$@" || exit $? + # Remove any Libtool call + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + # X makedepend + shift + cleared=no eat=no + for arg + do + case $cleared in + no) + set ""; shift + cleared=yes ;; + esac + if test $eat = yes; then + eat=no + continue + fi + case "$arg" in + -D*|-I*) + set fnord "$@" "$arg"; shift ;; + # Strip any option that makedepend may not understand. Remove + # the object too, otherwise makedepend will parse it as a source file. + -arch) + eat=yes ;; + -*|$object) + ;; + *) + set fnord "$@" "$arg"; shift ;; + esac + done + obj_suffix=`echo "$object" | sed 's/^.*\././'` + touch "$tmpdepfile" + ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" + rm -f "$depfile" + # makedepend may prepend the VPATH from the source file name to the object. + # No need to regex-escape $object, excess matching of '.' is harmless. + sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process the last invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed '1,2d' "$tmpdepfile" \ + | tr ' ' "$nl" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" "$tmpdepfile".bak + ;; + +cpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + "$@" -E \ + | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + | sed '$ s: \\$::' > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + cat < "$tmpdepfile" >> "$depfile" + sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvisualcpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + IFS=" " + for arg + do + case "$arg" in + -o) + shift + ;; + $object) + shift + ;; + "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") + set fnord "$@" + shift + shift + ;; + *) + set fnord "$@" "$arg" + shift + shift + ;; + esac + done + "$@" -E 2>/dev/null | + sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" + echo "$tab" >> "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvcmsys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +none) + exec "$@" + ;; + +*) + echo "Unknown depmode $depmode" 1>&2 + exit 1 + ;; +esac + +exit 0 + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/doc/adapter-api.txt b/doc/adapter-api.txt new file mode 100644 index 0000000..2afd61b --- /dev/null +++ b/doc/adapter-api.txt @@ -0,0 +1,313 @@ +BlueZ D-Bus Adapter API description +*********************************** + + +Adapter hierarchy +================= + +Service org.bluez +Interface org.bluez.Adapter1 +Object path [variable prefix]/{hci0,hci1,...} + +Methods void StartDiscovery() + + This method starts the device discovery session. This + includes an inquiry procedure and remote device name + resolving. Use StopDiscovery to release the sessions + acquired. + + This process will start creating Device objects as + new devices are discovered. + + During discovery RSSI delta-threshold is imposed. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + + void StopDiscovery() + + This method will cancel any previous StartDiscovery + transaction. + + Note that a discovery procedure is shared between all + discovery sessions thus calling StopDiscovery will only + release a single session. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + org.bluez.Error.NotAuthorized + + void RemoveDevice(object device) + + This removes the remote device object at the given + path. It will remove also the pairing information. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.Failed + + void SetDiscoveryFilter(dict filter) + + This method sets the device discovery filter for the + caller. When this method is called with no filter + parameter, filter is removed. + + Parameters that may be set in the filter dictionary + include the following: + + array{string} UUIDs + + Filter by service UUIDs, empty means match + _any_ UUID. + + When a remote device is found that advertises + any UUID from UUIDs, it will be reported if: + - Pathloss and RSSI are both empty. + - only Pathloss param is set, device advertise + TX pwer, and computed pathloss is less than + Pathloss param. + - only RSSI param is set, and received RSSI is + higher than RSSI param. + + int16 RSSI + + RSSI threshold value. + + PropertiesChanged signals will be emitted + for already existing Device objects, with + updated RSSI value. If one or more discovery + filters have been set, the RSSI delta-threshold, + that is imposed by StartDiscovery by default, + will not be applied. + + uint16 Pathloss + + Pathloss threshold value. + + PropertiesChanged signals will be emitted + for already existing Device objects, with + updated Pathloss value. + + string Transport (Default "auto") + + Transport parameter determines the type of + scan. + + Possible values: + "auto" - interleaved scan + "bredr" - BR/EDR inquiry + "le" - LE scan only + + If "le" or "bredr" Transport is requested, + and the controller doesn't support it, + org.bluez.Error.Failed error will be returned. + If "auto" transport is requested, scan will use + LE, BREDR, or both, depending on what's + currently enabled on the controller. + + bool DuplicateData (Default: true) + + Disables duplicate detection of advertisement + data. + + When enabled PropertiesChanged signals will be + generated for either ManufacturerData and + ServiceData everytime they are discovered. + + bool Discoverable (Default: false) + + Make adapter discoverable while discovering, + if the adapter is already discoverable setting + this filter won't do anything. + + When discovery filter is set, Device objects will be + created as new devices with matching criteria are + discovered regardless of they are connectable or + discoverable which enables listening to + non-connectable and non-discoverable devices. + + When multiple clients call SetDiscoveryFilter, their + filters are internally merged, and notifications about + new devices are sent to all clients. Therefore, each + client must check that device updates actually match + its filter. + + When SetDiscoveryFilter is called multiple times by the + same client, last filter passed will be active for + given client. + + SetDiscoveryFilter can be called before StartDiscovery. + It is useful when client will create first discovery + session, to ensure that proper scan will be started + right after call to StartDiscovery. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.NotSupported + org.bluez.Error.Failed + + array{string} GetDiscoveryFilters() + + Return available filters that can be given to + SetDiscoveryFilter. + + Possible errors: None + + object ConnectDevice(dict properties) [experimental] + + This method connects to device without need of + performing General Discovery. Connection mechanism is + similar to Connect method from Device1 interface with + exception that this method returns success when physical + connection is established. After this method returns, + services discovery will continue and any supported + profile will be connected. There is no need for calling + Connect on Device1 after this call. If connection was + successful this method returns object path to created + device object. + + Parameters that may be set in the filter dictionary + include the following: + + string Address + + The Bluetooth device address of the remote + device. This parameter is mandatory. + + string AddressType + + The Bluetooth device Address Type. This is + address type that should be used for initial + connection. If this parameter is not present + BR/EDR device is created. + + Possible values: + "public" - Public address + "random" - Random address + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.AlreadyExists + org.bluez.Error.NotSupported + org.bluez.Error.NotReady + org.bluez.Error.Failed + +Properties string Address [readonly] + + The Bluetooth device address. + + string AddressType [readonly] + + The Bluetooth Address Type. For dual-mode and BR/EDR + only adapter this defaults to "public". Single mode LE + adapters may have either value. With privacy enabled + this contains type of Identity Address and not type of + address used for connection. + + Possible values: + "public" - Public address + "random" - Random address + + string Name [readonly] + + The Bluetooth system name (pretty hostname). + + This property is either a static system default + or controlled by an external daemon providing + access to the pretty hostname configuration. + + string Alias [readwrite] + + The Bluetooth friendly name. This value can be + changed. + + In case no alias is set, it will return the system + provided name. Setting an empty string as alias will + convert it back to the system provided name. + + When resetting the alias with an empty string, the + property will default back to system name. + + On a well configured system, this property never + needs to be changed since it defaults to the system + name and provides the pretty hostname. Only if the + local name needs to be different from the pretty + hostname, this property should be used as last + resort. + + uint32 Class [readonly] + + The Bluetooth class of device. + + This property represents the value that is either + automatically configured by DMI/ACPI information + or provided as static configuration. + + boolean Powered [readwrite] + + Switch an adapter on or off. This will also set the + appropriate connectable state of the controller. + + The value of this property is not persistent. After + restart or unplugging of the adapter it will reset + back to false. + + boolean Discoverable [readwrite] + + Switch an adapter to discoverable or non-discoverable + to either make it visible or hide it. This is a global + setting and should only be used by the settings + application. + + If the DiscoverableTimeout is set to a non-zero + value then the system will set this value back to + false after the timer expired. + + In case the adapter is switched off, setting this + value will fail. + + When changing the Powered property the new state of + this property will be updated via a PropertiesChanged + signal. + + For any new adapter this settings defaults to false. + + boolean Pairable [readwrite] + + Switch an adapter to pairable or non-pairable. This is + a global setting and should only be used by the + settings application. + + Note that this property only affects incoming pairing + requests. + + For any new adapter this settings defaults to true. + + uint32 PairableTimeout [readwrite] + + The pairable timeout in seconds. A value of zero + means that the timeout is disabled and it will stay in + pairable mode forever. + + The default value for pairable timeout should be + disabled (value 0). + + uint32 DiscoverableTimeout [readwrite] + + The discoverable timeout in seconds. A value of zero + means that the timeout is disabled and it will stay in + discoverable/limited mode forever. + + The default value for the discoverable timeout should + be 180 seconds (3 minutes). + + boolean Discovering [readonly] + + Indicates that a device discovery procedure is active. + + array{string} UUIDs [readonly] + + List of 128-bit UUIDs that represents the available + local services. + + string Modalias [readonly, optional] + + Local Device ID information in modalias format + used by the kernel and udev. diff --git a/doc/advertising-api.txt b/doc/advertising-api.txt new file mode 100644 index 0000000..b0565ea --- /dev/null +++ b/doc/advertising-api.txt @@ -0,0 +1,211 @@ +BlueZ D-Bus LE Advertising API Description +****************************************** + +Advertising packets are structured data which is broadcast on the LE Advertising +channels and available for all devices in range. Because of the limited space +available in LE Advertising packets (31 bytes), each packet's contents must be +carefully controlled. + +BlueZ acts as a store for the Advertisement Data which is meant to be sent. +It constructs the correct Advertisement Data from the structured +data and configured the kernel to send the correct advertisement. + +Advertisement Data objects are registered freely and then referenced by BlueZ +when constructing the data sent to the kernel. + +LE Advertisement Data hierarchy +=============================== + +Specifies the Advertisement Data to be broadcast and some advertising +parameters. Properties which are not present will not be included in the +data. Required advertisement data types will always be included. +All UUIDs are 128-bit versions in the API, and 16 or 32-bit +versions of the same UUID will be used in the advertising data as appropriate. + +Service org.bluez +Interface org.bluez.LEAdvertisement1 +Object path freely definable + +Methods void Release() [noreply] + + This method gets called when the service daemon + removes the Advertisement. A client can use it to do + cleanup tasks. There is no need to call + UnregisterAdvertisement because when this method gets + called it has already been unregistered. + +Properties string Type + + Determines the type of advertising packet requested. + + Possible values: "broadcast" or "peripheral" + + array{string} ServiceUUIDs + + List of UUIDs to include in the "Service UUID" field of + the Advertising Data. + + dict ManufacturerData + + Manufactuer Data fields to include in + the Advertising Data. Keys are the Manufacturer ID + to associate with the data. + + array{string} SolicitUUIDs + + Array of UUIDs to include in "Service Solicitation" + Advertisement Data. + + dict ServiceData + + Service Data elements to include. The keys are the + UUID to associate with the data. + + dict Data [Experimental] + + Advertising Type to include in the Advertising + Data. Key is the advertising type and value is the + data as byte array. + + Note: Types already handled by other properties shall + not be used. + + Possible values: + + ... + + Example: + + 0x26 0x01 0x01... + + bool Discoverable [Experimental] + + Advertise as general discoverable. When present this + will override adapter Discoverable property. + + Note: This property shall not be set when Type is set + to broadcast. + + uint16 DiscoverableTimeout [Experimental] + + The discoverable timeout in seconds. A value of zero + means that the timeout is disabled and it will stay in + discoverable/limited mode forever. + + Note: This property shall not be set when Type is set + to broadcast. + + array{string} Includes + + List of features to be included in the advertising + packet. + + Possible values: as found on + LEAdvertisingManager.SupportedIncludes + + string LocalName + + Local name to be used in the advertising report. If the + string is too big to fit into the packet it will be + truncated. + + If this property is available 'local-name' cannot be + present in the Includes. + + uint16 Appearance + + Appearance to be used in the advertising report. + + Possible values: as found on GAP Service. + + uint16_t Duration + + Duration of the advertisement in seconds. If there are + other applications advertising no duration is set the + default is 2 seconds. + + uint16_t Timeout + + Timeout of the advertisement in seconds. This defines + the lifetime of the advertisement. + + string SecondaryChannel [Experimental] + + Secondary channel to be used. Primary channel is + always set to "1M" except when "Coded" is set. + + Possible value: "1M" (default) + "2M" + "Coded" + +LE Advertising Manager hierarchy +================================ + +The Advertising Manager allows external applications to register Advertisement +Data which should be broadcast to devices. Advertisement Data elements must +follow the API for LE Advertisement Data described above. + +Service org.bluez +Interface org.bluez.LEAdvertisingManager1 +Object path /org/bluez/{hci0,hci1,...} + +Methods RegisterAdvertisement(object advertisement, dict options) + + Registers an advertisement object to be sent over the LE + Advertising channel. The service must be exported + under interface LEAdvertisement1. + + InvalidArguments error indicates that the object has + invalid or conflicting properties. + + InvalidLength error indicates that the data + provided generates a data packet which is too long. + + The properties of this object are parsed when it is + registered, and any changes are ignored. + + If the same object is registered twice it will result in + an AlreadyExists error. + + If the maximum number of advertisement instances is + reached it will result in NotPermitted error. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.AlreadyExists + org.bluez.Error.InvalidLength + org.bluez.Error.NotPermitted + + UnregisterAdvertisement(object advertisement) + + This unregisters an advertisement that has been + previously registered. The object path parameter must + match the same value that has been used on registration. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.DoesNotExist + +Properties byte ActiveInstances + + Number of active advertising instances. + + byte SupportedInstances + + Number of available advertising instances. + + array{string} SupportedIncludes + + List of supported system includes. + + Possible values: "tx-power" + "appearance" + "local-name" + + array{string} SupportedSecondaryChannels [Experimental] + + List of supported Secondary channels. Secondary + channels can be used to advertise with the + corresponding PHY. + + Possible values: "1M" + "2M" + "Coded" diff --git a/doc/agent-api.txt b/doc/agent-api.txt new file mode 100644 index 0000000..0d9347c --- /dev/null +++ b/doc/agent-api.txt @@ -0,0 +1,185 @@ +BlueZ D-Bus Agent API description +********************************** + + +Agent Manager hierarchy +======================= + +Service org.bluez +Interface org.bluez.AgentManager1 +Object path /org/bluez + + void RegisterAgent(object agent, string capability) + + This registers an agent handler. + + The object path defines the path of the agent + that will be called when user input is needed. + + Every application can register its own agent and + for all actions triggered by that application its + agent is used. + + It is not required by an application to register + an agent. If an application does chooses to not + register an agent, the default agent is used. This + is on most cases a good idea. Only application + like a pairing wizard should register their own + agent. + + An application can only register one agent. Multiple + agents per application is not supported. + + The capability parameter can have the values + "DisplayOnly", "DisplayYesNo", "KeyboardOnly", + "NoInputNoOutput" and "KeyboardDisplay" which + reflects the input and output capabilities of the + agent. + + If an empty string is used it will fallback to + "KeyboardDisplay". + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.AlreadyExists + + void UnregisterAgent(object agent) + + This unregisters the agent that has been previously + registered. The object path parameter must match the + same value that has been used on registration. + + Possible errors: org.bluez.Error.DoesNotExist + + void RequestDefaultAgent(object agent) + + This requests is to make the application agent + the default agent. The application is required + to register an agent. + + Special permission might be required to become + the default agent. + + Possible errors: org.bluez.Error.DoesNotExist + + +Agent hierarchy +=============== + +Service unique name +Interface org.bluez.Agent1 +Object path freely definable + +Methods void Release() + + This method gets called when the service daemon + unregisters the agent. An agent can use it to do + cleanup tasks. There is no need to unregister the + agent, because when this method gets called it has + already been unregistered. + + string RequestPinCode(object device) + + This method gets called when the service daemon + needs to get the passkey for an authentication. + + The return value should be a string of 1-16 characters + length. The string can be alphanumeric. + + Possible errors: org.bluez.Error.Rejected + org.bluez.Error.Canceled + + void DisplayPinCode(object device, string pincode) + + This method gets called when the service daemon + needs to display a pincode for an authentication. + + An empty reply should be returned. When the pincode + needs no longer to be displayed, the Cancel method + of the agent will be called. + + This is used during the pairing process of keyboards + that don't support Bluetooth 2.1 Secure Simple Pairing, + in contrast to DisplayPasskey which is used for those + that do. + + This method will only ever be called once since + older keyboards do not support typing notification. + + Note that the PIN will always be a 6-digit number, + zero-padded to 6 digits. This is for harmony with + the later specification. + + Possible errors: org.bluez.Error.Rejected + org.bluez.Error.Canceled + + uint32 RequestPasskey(object device) + + This method gets called when the service daemon + needs to get the passkey for an authentication. + + The return value should be a numeric value + between 0-999999. + + Possible errors: org.bluez.Error.Rejected + org.bluez.Error.Canceled + + void DisplayPasskey(object device, uint32 passkey, + uint16 entered) + + This method gets called when the service daemon + needs to display a passkey for an authentication. + + The entered parameter indicates the number of already + typed keys on the remote side. + + An empty reply should be returned. When the passkey + needs no longer to be displayed, the Cancel method + of the agent will be called. + + During the pairing process this method might be + called multiple times to update the entered value. + + Note that the passkey will always be a 6-digit number, + so the display should be zero-padded at the start if + the value contains less than 6 digits. + + void RequestConfirmation(object device, uint32 passkey) + + This method gets called when the service daemon + needs to confirm a passkey for an authentication. + + To confirm the value it should return an empty reply + or an error in case the passkey is invalid. + + Note that the passkey will always be a 6-digit number, + so the display should be zero-padded at the start if + the value contains less than 6 digits. + + Possible errors: org.bluez.Error.Rejected + org.bluez.Error.Canceled + + void RequestAuthorization(object device) + + This method gets called to request the user to + authorize an incoming pairing attempt which + would in other circumstances trigger the just-works + model, or when the user plugged in a device that + implements cable pairing. In the latter case, the + device would not be connected to the adapter via + Bluetooth yet. + + Possible errors: org.bluez.Error.Rejected + org.bluez.Error.Canceled + + void AuthorizeService(object device, string uuid) + + This method gets called when the service daemon + needs to authorize a connection/service request. + + Possible errors: org.bluez.Error.Rejected + org.bluez.Error.Canceled + + void Cancel() + + This method gets called to indicate that the agent + request failed before a reply was returned. diff --git a/doc/assigned-numbers.txt b/doc/assigned-numbers.txt new file mode 100644 index 0000000..ca171c4 --- /dev/null +++ b/doc/assigned-numbers.txt @@ -0,0 +1,24 @@ +RFCOMM Channels +=============== + +Since there are a limited amount of possible RFCOMM channels (1-31) +they've been pre-allocated for currently known profiles in order to +avoid conflicts. + +Profile Channel +----------------------- +DUN 1 +HFP HF 7 +OPP 9 +FTP 10 +BIP 11 +HSP AG 12 +HFP AG 13 +SYNCH (IrMC) 14 +PBAP 15 +MAP MAS 16 +MAP MNS 17 +SyncEvolution 19 +PC/Ovi Suite 24 +SyncML Client 25 +SyncML Server 26 diff --git a/doc/btmon.txt b/doc/btmon.txt new file mode 100644 index 0000000..7a7fc53 --- /dev/null +++ b/doc/btmon.txt @@ -0,0 +1,35 @@ +BTMON(1) +======== +:doctype: manpage + + +NAME +---- +btmon - Bluetooth monitor + + +SYNOPSIS +-------- +*btmon* ['OPTIONS'] + + +DESCRIPTION +----------- +The btmon(1) command provides access to the Bluetooth subsystem monitor +infrastructure for reading HCI traces. + + +AUTHOR +------ +btmon was originally written by Marcel Holtmann. + + +RESOURCES +--------- +See + + +COPYING +------- +Free use of this software is granted under ther terms of the GNU Lesser +General Public Licenses (LGPL). diff --git a/doc/btsnoop.txt b/doc/btsnoop.txt new file mode 100644 index 0000000..975a53f --- /dev/null +++ b/doc/btsnoop.txt @@ -0,0 +1,178 @@ +BTSnoop/Monitor protocol formats +******************************** + +Opcode definitions +================== + +New Index +--------- + + Code: 0x0000 + Parameters: Type (1 Octet + Bus (1 Octet) + BD_Addr (6 Octets) + Name (8 Octets) + + This opcode indicates that a new controller instance with a + given index was added. With some protocols, like the TTY-based + one there is only a single supported controller, meaning the + index is implicitly 0. + +Deleted Index +------------- + + Code: 0x0001 + + This opcode indicates that the controller with a specific index + was removed. + +Command Packet +-------------- + + Code: 0x0002 + + HCI command packet. + +Event Packet +------------ + + Code: 0x0003 + + HCI event packet. + +ACL TX Packet +------------- + + Code: 0x0004 + + Outgoing ACL packet. + +ACL RX Packet +------------- + + Code: 0x0005 + + Incoming ACL packet. + +SCO TX Packet +-------------- + + Code: 0x0006 + + Outgoing SCO packet. + +SCO RX Packet +------------- + + Code: 0x0007 + + Incomnig SCO packet. + +Open Index +---------- + + Code: 0x0008 + + The HCI transport for the specified controller has been opened. + +Close Index +----------- + + Code: 0x0009 + + The HCI transport for the specified controller has been closed. + +Index Information +----------------- + + Code: 0x000a + Parameters: BD_Addr (6 Octets) + Manufacturer (2 Octets) + + Information about a specific controller. + +Vendor Diagnostics +------------------ + + Code: 0x000b + + Vendor diagnostic information. + +System Note +----------- + + Code: 0x000c + + System note. + +User Logging +------------ + + Code: 0x000d + Parameters: Priority (1 Octet) + Ident_Length (1 Octet) + Ident (Ident_Length Octets) + + User logging information. + + +TTY-based protocol +================== + +This section covers the protocol that can be parsed by btmon when +passing it the --tty parameter. The protocol is little endian, packet +based, and has the following header for each packet: + +struct tty_hdr { + uint16_t data_len; + uint16_t opcode; + uint8_t flags; + uint8_t hdr_len; + uint8_t ext_hdr[0]; +} __attribute__ ((packed)); + +The actual payload starts at ext_hdr + hdr_len and has the length of +data_len - 4 - hdr_len. Each field of the header is defined as follows: + +data_len: + This is the total length of the entire packet, excuding the + data_len field itself. + +opcode: + The BTSnoop opcode + +flags: + Special flags for the packet. Currently no flags are defined. + +hdr_len: + Length of the extended header. + +ext_hdr: + This is a sequence of header extension fields formatted as: + + struct { + uint8_t type; + uint8_t value[length]; + } + + The length of the value is dependent on the type. Currently the + following types are defined: + + Type Length Meaning + ---------------------------------------------------------------- + 1 Command drops 1 byte Dropped HCI command packets + 2 Event drops 1 byte Dropped HCI event packets + 3 ACL TX drops 1 byte Dropped ACL TX packets + 4 ACL RX drops 1 byte Dropped ACL RX packets + 5 SCO TX drops 1 byte Dropped SCO TX packets + 6 SCO RX drops 1 byte Dropped SCO RX packets + 7 Other drops 1 byte Dropped other packets + 8 32-bit timestamp 4 bytes Timestamp in 1/10th ms + + The drops fields indicate the number of packets that the + implementation had to drop (e.g. due to lack of buffers) since + the last reported drop count. + + The fields of the extended header must be sorted by increasing + type. This is essential so that unknown types can be ignored and + the parser can jump to processing the payload. diff --git a/doc/device-api.txt b/doc/device-api.txt new file mode 100644 index 0000000..65d8fee --- /dev/null +++ b/doc/device-api.txt @@ -0,0 +1,269 @@ +BlueZ D-Bus Device API description +********************************** + + +Device hierarchy +================ + +Service org.bluez +Interface org.bluez.Device1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX + +Methods void Connect() + + This is a generic method to connect any profiles + the remote device supports that can be connected + to and have been flagged as auto-connectable on + our side. If only subset of profiles is already + connected it will try to connect currently disconnected + ones. + + If at least one profile was connected successfully this + method will indicate success. + + For dual-mode devices only one bearer is connected at + time, the conditions are in the following order: + + 1. Connect the disconnected bearer if already + connected. + + 2. Connect first the bonded bearer. If no + bearers are bonded or both are skip and check + latest seen bearer. + + 3. Connect last seen bearer, in case the + timestamps are the same BR/EDR takes + precedence. + + Possible errors: org.bluez.Error.NotReady + org.bluez.Error.Failed + org.bluez.Error.InProgress + org.bluez.Error.AlreadyConnected + + void Disconnect() + + This method gracefully disconnects all connected + profiles and then terminates low-level ACL connection. + + ACL connection will be terminated even if some profiles + were not disconnected properly e.g. due to misbehaving + device. + + This method can be also used to cancel a preceding + Connect call before a reply to it has been received. + + For non-trusted devices connected over LE bearer calling + this method will disable incoming connections until + Connect method is called again. + + Possible errors: org.bluez.Error.NotConnected + + void ConnectProfile(string uuid) + + This method connects a specific profile of this + device. The UUID provided is the remote service + UUID for the profile. + + Possible errors: org.bluez.Error.Failed + org.bluez.Error.InProgress + org.bluez.Error.InvalidArguments + org.bluez.Error.NotAvailable + org.bluez.Error.NotReady + + void DisconnectProfile(string uuid) + + This method disconnects a specific profile of + this device. The profile needs to be registered + client profile. + + There is no connection tracking for a profile, so + as long as the profile is registered this will always + succeed. + + Possible errors: org.bluez.Error.Failed + org.bluez.Error.InProgress + org.bluez.Error.InvalidArguments + org.bluez.Error.NotSupported + + void Pair() + + This method will connect to the remote device, + initiate pairing and then retrieve all SDP records + (or GATT primary services). + + If the application has registered its own agent, + then that specific agent will be used. Otherwise + it will use the default agent. + + Only for applications like a pairing wizard it + would make sense to have its own agent. In almost + all other cases the default agent will handle + this just fine. + + In case there is no application agent and also + no default agent present, this method will fail. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.Failed + org.bluez.Error.AlreadyExists + org.bluez.Error.AuthenticationCanceled + org.bluez.Error.AuthenticationFailed + org.bluez.Error.AuthenticationRejected + org.bluez.Error.AuthenticationTimeout + org.bluez.Error.ConnectionAttemptFailed + + void CancelPairing() + + This method can be used to cancel a pairing + operation initiated by the Pair method. + + Possible errors: org.bluez.Error.DoesNotExist + org.bluez.Error.Failed + +Properties string Address [readonly] + + The Bluetooth device address of the remote device. + + string AddressType [readonly] + + The Bluetooth device Address Type. For dual-mode and + BR/EDR only devices this defaults to "public". Single + mode LE devices may have either value. If remote device + uses privacy than before pairing this represents address + type used for connection and Identity Address after + pairing. + + Possible values: + "public" - Public address + "random" - Random address + + string Name [readonly, optional] + + The Bluetooth remote name. This value can not be + changed. Use the Alias property instead. + + This value is only present for completeness. It is + better to always use the Alias property when + displaying the devices name. + + If the Alias property is unset, it will reflect + this value which makes it more convenient. + + string Icon [readonly, optional] + + Proposed icon name according to the freedesktop.org + icon naming specification. + + uint32 Class [readonly, optional] + + The Bluetooth class of device of the remote device. + + uint16 Appearance [readonly, optional] + + External appearance of device, as found on GAP service. + + array{string} UUIDs [readonly, optional] + + List of 128-bit UUIDs that represents the available + remote services. + + boolean Paired [readonly] + + Indicates if the remote device is paired. + + boolean Connected [readonly] + + Indicates if the remote device is currently connected. + A PropertiesChanged signal indicate changes to this + status. + + boolean Trusted [readwrite] + + Indicates if the remote is seen as trusted. This + setting can be changed by the application. + + boolean Blocked [readwrite] + + If set to true any incoming connections from the + device will be immediately rejected. Any device + drivers will also be removed and no new ones will + be probed as long as the device is blocked. + + string Alias [readwrite] + + The name alias for the remote device. The alias can + be used to have a different friendly name for the + remote device. + + In case no alias is set, it will return the remote + device name. Setting an empty string as alias will + convert it back to the remote device name. + + When resetting the alias with an empty string, the + property will default back to the remote name. + + object Adapter [readonly] + + The object path of the adapter the device belongs to. + + boolean LegacyPairing [readonly] + + Set to true if the device only supports the pre-2.1 + pairing mechanism. This property is useful during + device discovery to anticipate whether legacy or + simple pairing will occur if pairing is initiated. + + Note that this property can exhibit false-positives + in the case of Bluetooth 2.1 (or newer) devices that + have disabled Extended Inquiry Response support. + + string Modalias [readonly, optional] + + Remote Device ID information in modalias format + used by the kernel and udev. + + int16 RSSI [readonly, optional] + + Received Signal Strength Indicator of the remote + device (inquiry or advertising). + + int16 TxPower [readonly, optional] + + Advertised transmitted power level (inquiry or + advertising). + + dict ManufacturerData [readonly, optional] + + Manufacturer specific advertisement data. Keys are + 16 bits Manufacturer ID followed by its byte array + value. + + dict ServiceData [readonly, optional] + + Service advertisement data. Keys are the UUIDs in + string format followed by its byte array value. + + bool ServicesResolved [readonly] + + Indicate whether or not service discovery has been + resolved. + + array{byte} AdvertisingFlags [readonly, experimental] + + The Advertising Data Flags of the remote device. + + dict AdvertisingData [readonly, experimental] + + The Advertising Data of the remote device. Keys are + are 8 bits AD Type followed by data as byte array. + + Note: Only types considered safe to be handled by + application are exposed. + + Possible values: + + ... + + Example: + + 0x26 0x01 0x01... diff --git a/doc/gatt-api.txt b/doc/gatt-api.txt new file mode 100644 index 0000000..98fe748 --- /dev/null +++ b/doc/gatt-api.txt @@ -0,0 +1,485 @@ +BlueZ D-Bus GATT API description +******************************** + +GATT local and remote services share the same high-level D-Bus API. Local +refers to GATT based service exported by a BlueZ plugin or an external +application. Remote refers to GATT services exported by the peer. + +BlueZ acts as a proxy, translating ATT operations to D-Bus method calls and +Properties (or the opposite). Support for D-Bus Object Manager is mandatory for +external services to allow seamless GATT declarations (Service, Characteristic +and Descriptors) discovery. Each GATT service tree is required to export a D-Bus +Object Manager at its root that is solely responsible for the objects that +belong to that service. + +Releasing a registered GATT service is not defined yet. Any API extension +should avoid breaking the defined API, and if possible keep an unified GATT +remote and local services representation. + +Service hierarchy +================= + +GATT remote and local service representation. Object path for local services +is freely definable. + +External applications implementing local services must register the services +using GattManager1 registration method and must implement the methods and +properties defined in GattService1 interface. + +Service org.bluez +Interface org.bluez.GattService1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXX + +Properties string UUID [read-only] + + 128-bit service UUID. + + boolean Primary [read-only] + + Indicates whether or not this GATT service is a + primary service. If false, the service is secondary. + + object Device [read-only, optional] + + Object path of the Bluetooth device the service + belongs to. Only present on services from remote + devices. + + array{object} Includes [read-only, optional] + + Array of object paths representing the included + services of this service. + + uint16 Handle [read-write, optional] (Server Only) + + Service handle. When available in the server it + would attempt to use to allocate into the database + which may fail, to auto allocate the value 0x0000 + shall be used which will cause the allocated handle to + be set once registered. + + +Characteristic hierarchy +======================== + +For local GATT defined services, the object paths need to follow the service +path hierarchy and are freely definable. + +Service org.bluez +Interface org.bluez.GattCharacteristic1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXX/charYYYY + +Methods array{byte} ReadValue(dict options) + + Issues a request to read the value of the + characteristic and returns the value if the + operation was successful. + + Possible options: "offset": uint16 offset + "mtu": Exchanged MTU (Server only) + "device": Object Device (Server only) + + Possible Errors: org.bluez.Error.Failed + org.bluez.Error.InProgress + org.bluez.Error.NotPermitted + org.bluez.Error.NotAuthorized + org.bluez.Error.InvalidOffset + org.bluez.Error.NotSupported + + void WriteValue(array{byte} value, dict options) + + Issues a request to write the value of the + characteristic. + + Possible options: "offset": Start offset + "type": string + Possible values: + "command": Write without + response + "request": Write with response + "reliable": Reliable Write + "mtu": Exchanged MTU (Server only) + "device": Device path (Server only) + "link": Link type (Server only) + "prepare-authorize": True if prepare + authorization + request + + Possible Errors: org.bluez.Error.Failed + org.bluez.Error.InProgress + org.bluez.Error.NotPermitted + org.bluez.Error.InvalidValueLength + org.bluez.Error.NotAuthorized + org.bluez.Error.NotSupported + + fd, uint16 AcquireWrite(dict options) [optional] + + Acquire file descriptor and MTU for writing. Only + sockets are supported. Usage of WriteValue will be + locked causing it to return NotPermitted error. + + For server the MTU returned shall be equal or smaller + than the negotiated MTU. + + For client it only works with characteristic that has + WriteAcquired property which relies on + write-without-response Flag. + + To release the lock the client shall close the file + descriptor, a HUP is generated in case the device + is disconnected. + + Note: the MTU can only be negotiated once and is + symmetric therefore this method may be delayed in + order to have the exchange MTU completed, because of + that the file descriptor is closed during + reconnections as the MTU has to be renegotiated. + + Possible options: "device": Object Device (Server only) + "mtu": Exchanged MTU (Server only) + "link": Link type (Server only) + + Possible Errors: org.bluez.Error.Failed + org.bluez.Error.NotSupported + + fd, uint16 AcquireNotify(dict options) [optional] + + Acquire file descriptor and MTU for notify. Only + sockets are support. Usage of StartNotify will be locked + causing it to return NotPermitted error. + + For server the MTU returned shall be equal or smaller + than the negotiated MTU. + + Only works with characteristic that has NotifyAcquired + which relies on notify Flag and no other client have + called StartNotify. + + Notification are enabled during this procedure so + StartNotify shall not be called, any notification + will be dispatched via file descriptor therefore the + Value property is not affected during the time where + notify has been acquired. + + To release the lock the client shall close the file + descriptor, a HUP is generated in case the device + is disconnected. + + Note: the MTU can only be negotiated once and is + symmetric therefore this method may be delayed in + order to have the exchange MTU completed, because of + that the file descriptor is closed during + reconnections as the MTU has to be renegotiated. + + Possible options: "device": Object Device (Server only) + "mtu": Exchanged MTU (Server only) + "link": Link type (Server only) + + Possible Errors: org.bluez.Error.Failed + org.bluez.Error.NotSupported + + void StartNotify() + + Starts a notification session from this characteristic + if it supports value notifications or indications. + + Possible Errors: org.bluez.Error.Failed + org.bluez.Error.NotPermitted + org.bluez.Error.InProgress + org.bluez.Error.NotSupported + + void StopNotify() + + This method will cancel any previous StartNotify + transaction. Note that notifications from a + characteristic are shared between sessions thus + calling StopNotify will release a single session. + + Possible Errors: org.bluez.Error.Failed + + void Confirm() [optional] (Server only) + + This method doesn't expect a reply so it is just a + confirmation that value was received. + + Possible Errors: org.bluez.Error.Failed + +Properties string UUID [read-only] + + 128-bit characteristic UUID. + + object Service [read-only] + + Object path of the GATT service the characteristic + belongs to. + + array{byte} Value [read-only, optional] + + The cached value of the characteristic. This property + gets updated only after a successful read request and + when a notification or indication is received, upon + which a PropertiesChanged signal will be emitted. + + boolean WriteAcquired [read-only, optional] + + True, if this characteristic has been acquired by any + client using AcquireWrite. + + For client properties is ommited in case + 'write-without-response' flag is not set. + + For server the presence of this property indicates + that AcquireWrite is supported. + + boolean NotifyAcquired [read-only, optional] + + True, if this characteristic has been acquired by any + client using AcquireNotify. + + For client this properties is ommited in case 'notify' + flag is not set. + + For server the presence of this property indicates + that AcquireNotify is supported. + + boolean Notifying [read-only, optional] + + True, if notifications or indications on this + characteristic are currently enabled. + + array{string} Flags [read-only] + + Defines how the characteristic value can be used. See + Core spec "Table 3.5: Characteristic Properties bit + field", and "Table 3.8: Characteristic Extended + Properties bit field". Allowed values: + + "broadcast" + "read" + "write-without-response" + "write" + "notify" + "indicate" + "authenticated-signed-writes" + "extended-properties" + "reliable-write" + "writable-auxiliaries" + "encrypt-read" + "encrypt-write" + "encrypt-authenticated-read" + "encrypt-authenticated-write" + "secure-read" (Server only) + "secure-write" (Server only) + "authorize" + + uint16 Handle [read-write, optional] (Server Only) + + Characteristic handle. When available in the server it + would attempt to use to allocate into the database + which may fail, to auto allocate the value 0x0000 + shall be used which will cause the allocated handle to + be set once registered. + +Characteristic Descriptors hierarchy +==================================== + +Local or remote GATT characteristic descriptors hierarchy. + +Service org.bluez +Interface org.bluez.GattDescriptor1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/serviceXX/charYYYY/descriptorZZZ + +Methods array{byte} ReadValue(dict flags) + + Issues a request to read the value of the + characteristic and returns the value if the + operation was successful. + + Possible options: "offset": Start offset + "device": Device path (Server only) + "link": Link type (Server only) + + Possible Errors: org.bluez.Error.Failed + org.bluez.Error.InProgress + org.bluez.Error.NotPermitted + org.bluez.Error.NotAuthorized + org.bluez.Error.NotSupported + + void WriteValue(array{byte} value, dict flags) + + Issues a request to write the value of the + characteristic. + + Possible options: "offset": Start offset + "device": Device path (Server only) + "link": Link type (Server only) + "prepare-authorize": boolean Is prepare + authorization + request + + Possible Errors: org.bluez.Error.Failed + org.bluez.Error.InProgress + org.bluez.Error.NotPermitted + org.bluez.Error.InvalidValueLength + org.bluez.Error.NotAuthorized + org.bluez.Error.NotSupported + +Properties string UUID [read-only] + + 128-bit descriptor UUID. + + object Characteristic [read-only] + + Object path of the GATT characteristic the descriptor + belongs to. + + array{byte} Value [read-only, optional] + + The cached value of the descriptor. This property + gets updated only after a successful read request, upon + which a PropertiesChanged signal will be emitted. + + array{string} Flags [read-only] + + Defines how the descriptor value can be used. + + Possible values: + + "read" + "write" + "encrypt-read" + "encrypt-write" + "encrypt-authenticated-read" + "encrypt-authenticated-write" + "secure-read" (Server Only) + "secure-write" (Server Only) + "authorize" + + uint16 Handle [read-write, optional] (Server Only) + + Characteristic handle. When available in the server it + would attempt to use to allocate into the database + which may fail, to auto allocate the value 0x0000 + shall be used which will cause the allocated handle to + be set once registered. + +GATT Profile hierarchy +===================== + +Local profile (GATT client) instance. By registering this type of object +an application effectively indicates support for a specific GATT profile +and requests automatic connections to be established to devices +supporting it. + +Service +Interface org.bluez.GattProfile1 +Object path + +Methods void Release() + + This method gets called when the service daemon + unregisters the profile. The profile can use it to + do cleanup tasks. There is no need to unregister the + profile, because when this method gets called it has + already been unregistered. + +Properties array{string} UUIDs [read-only] + + 128-bit GATT service UUIDs to auto connect. + + +GATT Manager hierarchy +====================== + +GATT Manager allows external applications to register GATT services and +profiles. + +Registering a profile allows applications to subscribe to *remote* services. +These must implement the GattProfile1 interface defined above. + +Registering a service allows applications to publish a *local* GATT service, +which then becomes available to remote devices. A GATT service is represented by +a D-Bus object hierarchy where the root node corresponds to a service and the +child nodes represent characteristics and descriptors that belong to that +service. Each node must implement one of GattService1, GattCharacteristic1, +or GattDescriptor1 interfaces described above, based on the attribute it +represents. Each node must also implement the standard D-Bus Properties +interface to expose their properties. These objects collectively represent a +GATT service definition. + +To make service registration simple, BlueZ requires that all objects that belong +to a GATT service be grouped under a D-Bus Object Manager that solely manages +the objects of that service. Hence, the standard DBus.ObjectManager interface +must be available on the root service path. An example application hierarchy +containing two separate GATT services may look like this: + +-> /com/example + | - org.freedesktop.DBus.ObjectManager + | + -> /com/example/service0 + | | - org.freedesktop.DBus.Properties + | | - org.bluez.GattService1 + | | + | -> /com/example/service0/char0 + | | - org.freedesktop.DBus.Properties + | | - org.bluez.GattCharacteristic1 + | | + | -> /com/example/service0/char1 + | | - org.freedesktop.DBus.Properties + | | - org.bluez.GattCharacteristic1 + | | + | -> /com/example/service0/char1/desc0 + | - org.freedesktop.DBus.Properties + | - org.bluez.GattDescriptor1 + | + -> /com/example/service1 + | - org.freedesktop.DBus.Properties + | - org.bluez.GattService1 + | + -> /com/example/service1/char0 + - org.freedesktop.DBus.Properties + - org.bluez.GattCharacteristic1 + +When a service is registered, BlueZ will automatically obtain information about +all objects using the service's Object Manager. Once a service has been +registered, the objects of a service should not be removed. If BlueZ receives an +InterfacesRemoved signal from a service's Object Manager, it will immediately +unregister the service. Similarly, if the application disconnects from the bus, +all of its registered services will be automatically unregistered. +InterfacesAdded signals will be ignored. + +Examples: + - Client + test/example-gatt-client + client/bluetoothctl + - Server + test/example-gatt-server + tools/gatt-service + + +Service org.bluez +Interface org.bluez.GattManager1 +Object path [variable prefix]/{hci0,hci1,...} + +Methods void RegisterApplication(object application, dict options) + + Registers a local GATT services hierarchy as described + above (GATT Server) and/or GATT profiles (GATT Client). + + The application object path together with the D-Bus + system bus connection ID define the identification of + the application registering a GATT based + service or profile. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.AlreadyExists + + void UnregisterApplication(object application) + + This unregisters the services that has been + previously registered. The object path parameter + must match the same value that has been used + on registration. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.DoesNotExist diff --git a/doc/health-api.txt b/doc/health-api.txt new file mode 100644 index 0000000..2c48ff2 --- /dev/null +++ b/doc/health-api.txt @@ -0,0 +1,152 @@ +BlueZ D-Bus Health API description +********************************** + + +HealthManager hierarchy +======================= + +Service org.bluez +Interface org.bluez.HealthManager1 +Object path /org/bluez/ + +Methods object CreateApplication(dict config) + + Returns the path of the new registered application. + Application will be closed by the call or implicitly + when the programs leaves the bus. + + config: + uint16 DataType: + + Mandatory + + string Role: + + Mandatory. Possible values: "source", + "sink" + + string Description: + + Optional + + ChannelType: + + Optional, just for sources. Possible + values: "reliable", "streaming" + + Possible Errors: org.bluez.Error.InvalidArguments + + void DestroyApplication(object application) + + Closes the HDP application identified by the object + path. Also application will be closed if the process + that started it leaves the bus. Only the creator of the + application will be able to destroy it. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotFound + org.bluez.Error.NotAllowed + + +HealthDevice hierarchy +====================== + +Service org.bluez +Interface org.bluez.HealthDevice1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX + +Methods boolean Echo() + + Sends an echo petition to the remote service. Returns + True if response matches with the buffer sent. If some + error is detected False value is returned. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.OutOfRange + + object CreateChannel(object application, string configuration) + + Creates a new data channel. The configuration should + indicate the channel quality of service using one of + this values "reliable", "streaming", "any". + + Returns the object path that identifies the data + channel that is already connected. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.HealthError + + void DestroyChannel(object channel) + + Destroys the data channel object. Only the creator of + the channel or the creator of the HealthApplication + that received the data channel will be able to destroy + it. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotFound + org.bluez.Error.NotAllowed + +Signals void ChannelConnected(object channel) + + This signal is launched when a new data channel is + created or when a known data channel is reconnected. + + void ChannelDeleted(object channel) + + This signal is launched when a data channel is deleted. + + After this signal the data channel path will not be + valid and its path can be reused for future data + channels. + +Properties object MainChannel [readonly] + + The first reliable channel opened. It is needed by + upper applications in order to send specific protocol + data units. The first reliable can change after a + reconnection. + + +HealthChannel hierarchy +======================= + +Service org.bluez +Interface org.bluez.HealthChannel1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/chanZZZ + +Only the process that created the data channel or the creator of the +HealthApplication that received it will be able to call these methods. + +Methods fd Acquire() + + Returns the file descriptor for this data channel. If + the data channel is not connected it will also + reconnect. + + Possible Errors: org.bluez.Error.NotConnected + org.bluez.Error.NotAllowed + + void Release() + + Releases the fd. Application should also need to + close() it. + + Possible Errors: org.bluez.Error.NotAcquired + org.bluez.Error.NotAllowed + +Properties string Type [readonly] + + The quality of service of the data channel. ("reliable" + or "streaming") + + object Device [readonly] + + Identifies the Remote Device that is connected with. + Maps with a HealthDevice object. + + object Application [readonly] + + Identifies the HealthApplication to which this channel + is related to (which indirectly defines its role and + data type). diff --git a/doc/input-api.txt b/doc/input-api.txt new file mode 100644 index 0000000..67da08b --- /dev/null +++ b/doc/input-api.txt @@ -0,0 +1,32 @@ +BlueZ D-Bus Input API description +********************************* + +Input hierarchy +=============== + +Service org.bluez +Interface org.bluez.Input1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX + +Properties string ReconnectMode [readonly] + + Determines the Connectability mode of the HID device as + defined by the HID Profile specification, Section 5.4.2. + + This mode is based in the two properties + HIDReconnectInitiate (see Section 5.3.4.6) and + HIDNormallyConnectable (see Section 5.3.4.14) which + define the following four possible values: + + "none" Device and host are not required to + automatically restore the connection. + + "host" Bluetooth HID host restores connection. + + "device" Bluetooth HID device restores + connection. + + "any" Bluetooth HID device shall attempt to + restore the lost connection, but + Bluetooth HID Host may also restore the + connection. diff --git a/doc/media-api.txt b/doc/media-api.txt new file mode 100644 index 0000000..07f7ac3 --- /dev/null +++ b/doc/media-api.txt @@ -0,0 +1,658 @@ +BlueZ D-Bus Media API description +********************************* + + +Media hierarchy +=============== + +Service org.bluez +Interface org.bluez.Media1 +Object path [variable prefix]/{hci0,hci1,...} + +Methods void RegisterEndpoint(object endpoint, dict properties) + + Register a local end point to sender, the sender can + register as many end points as it likes. + + Note: If the sender disconnects the end points are + automatically unregistered. + + possible properties: + + string UUID: + + UUID of the profile which the endpoint + is for. + + byte Codec: + + Assigned number of codec that the + endpoint implements. The values should + match the profile specification which + is indicated by the UUID. + + array{byte} Capabilities: + + Capabilities blob, it is used as it is + so the size and byte order must match. + + Possible Errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotSupported - emitted + when interface for the end-point is + disabled. + + void UnregisterEndpoint(object endpoint) + + Unregister sender end point. + + void RegisterPlayer(object player, dict properties) + + Register a media player object to sender, the sender + can register as many objects as it likes. + + Object must implement at least + org.mpris.MediaPlayer2.Player as defined in MPRIS 2.2 + spec: + + http://specifications.freedesktop.org/mpris-spec/latest/ + + Note: If the sender disconnects its objects are + automatically unregistered. + + Possible Errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotSupported + + void UnregisterPlayer(object player) + + Unregister sender media player. + + void RegisterApplication(object root, dict options) + + Register endpoints an player objects within root + object which must implement ObjectManager. + + The application object path together with the D-Bus + system bus connection ID define the identification of + the application. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.AlreadyExists + + void UnregisterApplication(object application) + + This unregisters the services that has been + previously registered. The object path parameter + must match the same value that has been used + on registration. + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.DoesNotExist +Media Control hierarchy +======================= + +Service org.bluez +Interface org.bluez.MediaControl1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX + +Methods void Play() [Deprecated] + + Resume playback. + + void Pause() [Deprecated] + + Pause playback. + + void Stop() [Deprecated] + + Stop playback. + + void Next() [Deprecated] + + Next item. + + void Previous() [Deprecated] + + Previous item. + + void VolumeUp() [Deprecated] + + Adjust remote volume one step up + + void VolumeDown() [Deprecated] + + Adjust remote volume one step down + + void FastForward() [Deprecated] + + Fast forward playback, this action is only stopped + when another method in this interface is called. + + void Rewind() [Deprecated] + + Rewind playback, this action is only stopped + when another method in this interface is called. + +Properties + + boolean Connected [readonly] + + object Player [readonly, optional] + + Addressed Player object path. + + +MediaPlayer1 hierarchy +====================== + +Service org.bluez (Controller role) +Interface org.bluez.MediaPlayer1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/playerX + +Methods void Play() + + Resume playback. + + Possible Errors: org.bluez.Error.NotSupported + org.bluez.Error.Failed + + void Pause() + + Pause playback. + + Possible Errors: org.bluez.Error.NotSupported + org.bluez.Error.Failed + + void Stop() + + Stop playback. + + Possible Errors: org.bluez.Error.NotSupported + org.bluez.Error.Failed + + void Next() + + Next item. + + Possible Errors: org.bluez.Error.NotSupported + org.bluez.Error.Failed + + void Previous() + + Previous item. + + Possible Errors: org.bluez.Error.NotSupported + org.bluez.Error.Failed + + void FastForward() + + Fast forward playback, this action is only stopped + when another method in this interface is called. + + Possible Errors: org.bluez.Error.NotSupported + org.bluez.Error.Failed + + void Rewind() + + Rewind playback, this action is only stopped + when another method in this interface is called. + + Possible Errors: org.bluez.Error.NotSupported + org.bluez.Error.Failed + +Properties string Equalizer [readwrite] + + Possible values: "off" or "on" + + string Repeat [readwrite] + + Possible values: "off", "singletrack", "alltracks" or + "group" + + string Shuffle [readwrite] + + Possible values: "off", "alltracks" or "group" + + string Scan [readwrite] + + Possible values: "off", "alltracks" or "group" + + string Status [readonly] + + Possible status: "playing", "stopped", "paused", + "forward-seek", "reverse-seek" + or "error" + + uint32 Position [readonly] + + Playback position in milliseconds. Changing the + position may generate additional events that will be + sent to the remote device. When position is 0 it means + the track is starting and when it's greater than or + equal to track's duration the track has ended. Note + that even if duration is not available in metadata it's + possible to signal its end by setting position to the + maximum uint32 value. + + dict Track [readonly] + + Track metadata. + + Possible values: + + string Title: + + Track title name + + string Artist: + + Track artist name + + string Album: + + Track album name + + string Genre: + + Track genre name + + uint32 NumberOfTracks: + + Number of tracks in total + + uint32 TrackNumber: + + Track number + + uint32 Duration: + + Track duration in milliseconds + + object Device [readonly] + + Device object path. + + string Name [readonly] + + Player name + + string Type [readonly] + + Player type + + Possible values: + + "Audio" + "Video" + "Audio Broadcasting" + "Video Broadcasting" + + string Subtype [readonly] + + Player subtype + + Possible values: + + "Audio Book" + "Podcast" + + boolean Browsable [readonly] + + If present indicates the player can be browsed using + MediaFolder interface. + + Possible values: + + True: Supported and active + False: Supported but inactive + + Note: If supported but inactive clients can enable it + by using MediaFolder interface but it might interfere + in the playback of other players. + + + boolean Searchable [readonly] + + If present indicates the player can be searched using + MediaFolder interface. + + Possible values: + + True: Supported and active + False: Supported but inactive + + Note: If supported but inactive clients can enable it + by using MediaFolder interface but it might interfere + in the playback of other players. + + object Playlist + + Playlist object path. + +MediaFolder1 hierarchy +====================== + +Service unique name (Target role) + org.bluez (Controller role) +Interface org.bluez.MediaFolder1 +Object path freely definable (Target role) + [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/playerX + (Controller role) + +Methods object Search(string value, dict filter) + + Return a folder object containing the search result. + + To list the items found use the folder object returned + and pass to ChangeFolder. + + Possible Errors: org.bluez.Error.NotSupported + org.bluez.Error.Failed + + array{objects, properties} ListItems(dict filter) + + Return a list of items found + + Possible Errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotSupported + org.bluez.Error.Failed + + void ChangeFolder(object folder) + + Change current folder. + + Note: By changing folder the items of previous folder + might be destroyed and have to be listed again, the + exception is NowPlaying folder which should be always + present while the player is active. + + Possible Errors: org.bluez.Error.InvalidArguments + org.bluez.Error.NotSupported + org.bluez.Error.Failed + +Properties uint32 NumberOfItems [readonly] + + Number of items in the folder + + string Name [readonly] + + Folder name: + + Possible values: + "/Filesystem/...": Filesystem scope + "/NowPlaying/...": NowPlaying scope + + Note: /NowPlaying folder might not be listed if player + is stopped, folders created by Search are virtual so + once another Search is perform or the folder is + changed using ChangeFolder it will no longer be listed. + +Filters uint32 Start: + + Offset of the first item. + + Default value: 0 + + uint32 End: + + Offset of the last item. + + Default value: NumbeOfItems + + array{string} Attributes + + Item properties that should be included in the list. + + Possible Values: + + "title", "artist", "album", "genre", + "number-of-tracks", "number", "duration" + + Default Value: All + +MediaItem1 hierarchy +==================== + +Service unique name (Target role) + org.bluez (Controller role) +Interface org.bluez.MediaItem1 +Object path freely definable (Target role) + [variable + prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/playerX/itemX + (Controller role) + +Methods void Play() + + Play item + + Possible Errors: org.bluez.Error.NotSupported + org.bluez.Error.Failed + + void AddtoNowPlaying() + + Add item to now playing list + + Possible Errors: org.bluez.Error.NotSupported + org.bluez.Error.Failed + +Properties object Player [readonly] + + Player object path the item belongs to + + string Name [readonly] + + Item displayable name + + string Type [readonly] + + Item type + + Possible values: "video", "audio", "folder" + + string FolderType [readonly, optional] + + Folder type. + + Possible values: "mixed", "titles", "albums", "artists" + + Available if property Type is "Folder" + + boolean Playable [readonly, optional] + + Indicates if the item can be played + + Available if property Type is "folder" + + dict Metadata [readonly] + + Item metadata. + + Possible values: + + string Title + + Item title name + + Available if property Type is "audio" + or "video" + + string Artist + + Item artist name + + Available if property Type is "audio" + or "video" + + string Album + + Item album name + + Available if property Type is "audio" + or "video" + + string Genre + + Item genre name + + Available if property Type is "audio" + or "video" + + uint32 NumberOfTracks + + Item album number of tracks in total + + Available if property Type is "audio" + or "video" + + uint32 Number + + Item album number + + Available if property Type is "audio" + or "video" + + uint32 Duration + + Item duration in milliseconds + + Available if property Type is "audio" + or "video" + +MediaEndpoint1 hierarchy +======================== + +Service unique name (Server role) + org.bluez (Client role) +Interface org.bluez.MediaEndpoint1 +Object path freely definable (Server role) + [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/sepX + (Client role) + +Methods void SetConfiguration(object transport, dict properties) + + Set configuration for the transport. + + For client role transport must be set with a server + endpoint oject which will be configured and the + properties must contain the following properties: + + array{byte} Capabilities + + array{byte} SelectConfiguration(array{byte} capabilities) + + Select preferable configuration from the supported + capabilities. + + Returns a configuration which can be used to setup + a transport. + + Note: There is no need to cache the selected + configuration since on success the configuration is + send back as parameter of SetConfiguration. + + void ClearConfiguration(object transport) + + Clear transport configuration. + + void Release() + + This method gets called when the service daemon + unregisters the endpoint. An endpoint can use it to do + cleanup tasks. There is no need to unregister the + endpoint, because when this method gets called it has + already been unregistered. + +Properties string UUID [readonly, optional]: + + UUID of the profile which the endpoint is for. + + byte Codec [readonly, optional]: + + Assigned number of codec that the endpoint implements. + The values should match the profile specification which + is indicated by the UUID. + + array{byte} Capabilities [readonly, optional]: + + Capabilities blob, it is used as it is so the size and + byte order must match. + + object Device [readonly, optional]: + + Device object which the endpoint is belongs to. + +MediaTransport1 hierarchy +========================= + +Service org.bluez +Interface org.bluez.MediaTransport1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/fdX + +Methods fd, uint16, uint16 Acquire() + + Acquire transport file descriptor and the MTU for read + and write respectively. + + Possible Errors: org.bluez.Error.NotAuthorized + org.bluez.Error.Failed + + fd, uint16, uint16 TryAcquire() + + Acquire transport file descriptor only if the transport + is in "pending" state at the time the message is + received by BlueZ. Otherwise no request will be sent + to the remote device and the function will just fail + with org.bluez.Error.NotAvailable. + + Possible Errors: org.bluez.Error.NotAuthorized + org.bluez.Error.Failed + org.bluez.Error.NotAvailable + + void Release() + + Releases file descriptor. + +Properties object Device [readonly] + + Device object which the transport is connected to. + + string UUID [readonly] + + UUID of the profile which the transport is for. + + byte Codec [readonly] + + Assigned number of codec that the transport support. + The values should match the profile specification which + is indicated by the UUID. + + array{byte} Configuration [readonly] + + Configuration blob, it is used as it is so the size and + byte order must match. + + string State [readonly] + + Indicates the state of the transport. Possible + values are: + "idle": not streaming + "pending": streaming but not acquired + "active": streaming and acquired + + uint16 Delay [readwrite] + + Optional. Transport delay in 1/10 of millisecond, this + property is only writeable when the transport was + acquired by the sender. + + uint16 Volume [readwrite] + + Optional. Indicates volume level of the transport, + this property is only writeable when the transport was + acquired by the sender. + + Possible Values: 0-127 + + object Endpoint [readonly, optional, experimental] + + Endpoint object which the transport is associated + with. diff --git a/doc/mgmt-api.txt b/doc/mgmt-api.txt new file mode 100644 index 0000000..0d11aa0 --- /dev/null +++ b/doc/mgmt-api.txt @@ -0,0 +1,3898 @@ +Bluetooth Management API +************************* + +Copyright (C) 2008-2009 Marcel Holtmann + + +Overview +======== + +This document describes the format of data used for communicating with +the kernel using a so-called Bluetooth Management sockets. These sockets +are available starting with Linux kernel version 3.4 + +The following kernel versions introduced new commands, new events or +important fixes to the Bluetooth Management API: + +Linux kernel v3.4 Version 1.0 +Linux kernel v3.5 Version 1.1 +Linux kernel v3.7 Version 1.2 +Linux kernel v3.9 Version 1.3 +Linux kernel v3.13 Version 1.4 +Linux kernel v3.15 Version 1.5 +Linux kernel v3.16 Version 1.6 +Linux kernel v3.17 Version 1.7 +Linux kernel v3.19 Version 1.8 +Linux kernel v4.1 Version 1.9 +Linux kernel v4.2 Version 1.10 +Linux kernel v4.5 Version 1.11 +Linux kernel v4.6 Version 1.12 +Linux kernel v4.8 Version 1.13 +Linux kernel v4.9 Version 1.14 + +Version 1.1 introduces Set Device ID command. + +Version 1.2 introduces Passkey Notify event. + +Version 1.3 does not introduce any new command or event. + +Version 1.4 introduces Set Advertising, Set BR/EDR, Set Static Address +and Set Scan Parameters commands. The existing Set Discoverable command +gained an extra setting for limited discoverable mode. The device name +is now provided in the scan response data for Low Energy. + +Version 1.5 introduces Set Secure Connections, Set Debug Keys, Set Privacy +and Load Identity Resolving Keys commands. It also introduces New Identity +Resolving Key and New Signature Resolving Key events. + +Version 1.6 introduces Get Connection Information command. It also updates +the Device Found event to combine advertising data and scan response data +into a single event. + +Version 1.7 introduces Get Clock Information, Add Device, Remove Device, +Load Connection Parameters, Read Unconfigured Index List, Read Controller +Configuration Information, Set External Configuration and Set Public Address +commands. It also introduces Device Added, Device Removed, New Connection +Parameter, Unconfigured Index Added, Unconfigured Index Removed and New +Configuration Options events. The existing Set Debug Keys command gained +an extra setting for enabling SSP debug mode. + +Version 1.8 introduces Start Service Discovery command. It also adds new +Long Term Key types for LE Secure Connection feature. + +Version 1.9 introduces Read Local Out Of Band Extended, Data, Read Extended +Controller Index List, Read Advertising Features, Add Advertising and Remove +Advertising commands. It also introduces Extended Index Added, Extended Index +Removed, Local Out Of Band Extended Data Updated, Advertising Added and +Advertising Removed events. The existing Set Advertising command gained an +extra setting for enabling undirected connectable advertising. It provides +support for a new static address setting and allows the usage of Set Fast +Connectable when controller is powered off. + +Version 1.10 does not introduce any new command or event. It extends the +advertising feature to support 5 parallel advertising instances. + +Version 1.11 introduces Get Advertising Size Information and Start Limited +Discovery commands. + +Version 1.12 introduces a new limited privacy mode (value 0x02 passed to +the Set Privacy command). + +Version 1.13 introduces a new authentication failure reason code for the +Device Disconnected event. + +Version 1.14 introduces Read Extended Controller Information command and +Extended Controller Information Changed event. It also adds Set Appearance +command. The advertising flags Appearance and Local Name for adding scan +response information are now supported. + + +Example +======= + +The Bluetooth management sockets can be created by setting the hci_channel +member of struct sockaddr_hci to HCI_CHANNEL_CONTROL (3) when creating a +raw HCI socket. In C the needed code would look something like the following: + +int mgmt_create(void) +{ + struct sockaddr_hci addr; + int fd; + + fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, + BTPROTO_HCI); + if (fd < 0) + return -errno; + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = HCI_DEV_NONE; + addr.hci_channel = HCI_CHANNEL_CONTROL; + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + int err = -errno; + close(fd); + return err; + } + + return fd; +} + +The process creating the mgmt socket is required to have the +CAP_NET_ADMIN capability (e.g. root would have this). + + +Packet Structures +================= + + Commands: + + 0 4 8 12 16 22 24 28 31 35 39 43 47 + +-------------------+-------------------+-------------------+ + | Command Code | Controller Index | Parameter Length | + +-------------------+-------------------+-------------------+ + | | + + Events: + + 0 4 8 12 16 22 24 28 31 35 39 43 47 + +-------------------+-------------------+-------------------+ + | Event Code | Controller Index | Parameter Length | + +-------------------+-------------------+-------------------+ + | | + +All fields are in little-endian byte order (least significant byte first). + +Controller Index can have a special value to indicate that +command or event is not related to any controller. Possible values: + + 0x0000 to 0xFFFE + 0xFFFF + + +Error Codes +=========== + +The following values have been defined for use with the Command Status +and Command Complete events: + +0x00 Success +0x01 Unknown Command +0x02 Not Connected +0x03 Failed +0x04 Connect Failed +0x05 Authentication Failed +0x06 Not Paired +0x07 No Resources +0x08 Timeout +0x09 Already Connected +0x0A Busy +0x0B Rejected +0x0C Not Supported +0x0D Invalid Parameters +0x0E Disconnected +0x0F Not Powered +0x10 Cancelled +0x11 Invalid Index +0x12 RFKilled +0x13 Already Paired +0x14 Permission Denied + +As a general rule all commands generate the events as specified below, +however invalid lengths or unknown commands will always generate a +Command Status response (with Unknown Command or Invalid Parameters +status). Sending a command with an invalid Controller Index value will +also always generate a Command Status event with the Invalid Index +status code. + + +Read Management Version Information Command +=========================================== + + Command Code: 0x0001 + Controller Index: + Command Parameters: + Return Parameters: Version (1 Octets) + Revision (2 Octets) + + This command returns the Management version and revision. + Besides, being informational the information can be used to + determine whether certain behavior has changed or bugs fixed + when interacting with the kernel. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + +Read Management Supported Commands Command +========================================== + + Command Code: 0x0002 + Controller Index: + Command Parameters: + Return Parameters: Num_Of_Commands (2 Octets) + Num_Of_Events (2 Octets) + Command1 (2 Octets) + Command2 (2 Octets) + ... + Event1 (2 Octets) + Event2 (2 Octets) + ... + + This command returns the list of supported Management commands + and events. + + The commands Read Management Version Information and Read + management Supported Commands are not included in this list. + Both commands are always supported and mandatory. + + The events Command Status and Command Complete are not included + in this list. Both are implicit and mandatory. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + +Read Controller Index List Command +================================== + + Command Code: 0x0003 + Controller Index: + Command Parameters: + Return Parameters: Num_Controllers (2 Octets) + Controller_Index[i] (2 Octets) + + This command returns the list of currently known controllers. + Controllers added or removed after calling this command can be + monitored using the Index Added and Index Removed events. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + +Read Controller Information Command +=================================== + + Command Code: 0x0004 + Controller Index: + Command Parameters: + Return Parameters: Address (6 Octets) + Bluetooth_Version (1 Octet) + Manufacturer (2 Octets) + Supported_Settings (4 Octets) + Current_Settings (4 Octets) + Class_Of_Device (3 Octets) + Name (249 Octets) + Short_Name (11 Octets) + + This command is used to retrieve the current state and basic + information of a controller. It is typically used right after + getting the response to the Read Controller Index List command + or an Index Added event. + + The Address parameter describes the controllers public address + and it can be expected that it is set. However in case of single + mode Low Energy only controllers it can be 00:00:00:00:00:00. To + power on the controller in this case, it is required to configure + a static address using Set Static Address command first. + + If the public address is set, then it will be used as identity + address for the controller. If no public address is available, + then the configured static address will be used as identity + address. + + In the case of a dual-mode controller with public address that + is configured as Low Energy only device (BR/EDR switched off), + the static address is used when set and public address otherwise. + + If no short name is set the Short_Name parameter will be empty + (begin with a nul byte). + + Current_Settings and Supported_Settings is a bitmask with + currently the following available bits: + + 0 Powered + 1 Connectable + 2 Fast Connectable + 3 Discoverable + 4 Bondable + 5 Link Level Security (Sec. mode 3) + 6 Secure Simple Pairing + 7 Basic Rate/Enhanced Data Rate + 8 High Speed + 9 Low Energy + 10 Advertising + 11 Secure Connections + 12 Debug Keys + 13 Privacy + 14 Controller Configuration + 15 Static Address + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Set Powered Command +=================== + + Command Code: 0x0005 + Controller Index: + Command Parameters: Powered (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to power on or off a controller. The + allowed Powered command parameter values are 0x00 and 0x01. All + other values will return Invalid Parameters. + + If discoverable setting is activated with a timeout, then + switching the controller off will expire this timeout and + disable discoverable. + + Settings programmed via Set Advertising and Add/Remove + Advertising while the controller was powered off will be activated + when powering the controller on. + + Switching the controller off will permanently cancel and remove + all advertising instances with a timeout set, i.e. time limited + advertising instances are not being remembered across power cycles. + Advertising Removed events will be issued accordingly. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Invalid Parameters + Invalid Index + + +Set Discoverable Command +======================== + + Command Code: 0x0006 + Controller Index: + Command Parameters: Discoverable (1 Octet) + Timeout (2 Octets) + Return Parameters: Current_Settings (4 Octets) + + This command is used to set the discoverable property of a + controller. The allowed Discoverable command parameter values + are 0x00, 0x01 and 0x02. All other values will return Invalid + Parameters. + + Timeout is the time in seconds and is only meaningful when + Discoverable is set to 0x01 or 0x02. Providing a timeout + with 0x00 return Invalid Parameters. For 0x02, the timeout + value is required. + + The value 0x00 disables discoverable, the value 0x01 enables + general discoverable and the value 0x02 enables limited + discoverable. + + This command is only available for BR/EDR capable controllers + (e.g. not for single-mode LE ones). It will return Not Supported + otherwise. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + However using a timeout when the controller is not powered will + return Not Powered error. + + When switching discoverable on and the connectable setting is + off it will return Rejected error. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Rejected + Not Supported + Invalid Parameters + Not Powered + Invalid Index + + +Set Connectable Command +======================= + + Command Code: 0x0007 + Controller Index: + Command Parameters: Connectable (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to set the connectable property of a + controller. The allowed Connectable command parameter values are + 0x00 and 0x01. All other values will return Invalid Parameters. + + This command is available for BR/EDR, LE-only and also dual + mode controllers. For BR/EDR is changes the page scan setting + and for LE controllers it changes the advertising type. For + dual mode controllers it affects both settings. + + For LE capable controllers the connectable setting takes effect + when advertising is enabled (peripheral) or when directed + advertising events are received (central). + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + When switching connectable off, it will also switch off the + discoverable setting. Switching connectable back on will not + restore a previous discoverable. It will stay off and needs + to be manually switched back on. + + When switching connectable off, it will expire a discoverable + setting with a timeout. + + This setting does not affect known devices from Add Device + command. These devices are always allowed to connect. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Not Supported + Invalid Parameters + Invalid Index + + +Set Fast Connectable Command +============================ + + Command Code: 0x0008 + Controller Index: + Command Parameters: Enable (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to set the controller into a connectable + state where the page scan parameters have been set in a way to + favor faster connect times with the expense of higher power + consumption. + + The allowed values of the Enable command parameter are 0x00 and + 0x01. All other values will return Invalid Parameters. + + This command is only available for BR/EDR capable controllers + (e.g. not for single-mode LE ones). It will return Not Supported + otherwise. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + The setting will be remembered during power down/up toggles. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Failed + Busy + Not Supported + Invalid Parameters + Invalid Index + + +Set Bondable Command +==================== + + Command Code: 0x0009 + Controller Index: + Command Parameters: Bondable (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to set the bondable property of an + controller. The allowed values for the Bondable command + parameter are 0x00 and 0x01. All other values will return + Invalid Parameters. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + Turning bondable on will not automatically switch the controller + into connectable mode. That needs to be done separately. + + The setting will be remembered during power down/up toggles. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Set Link Security Command +========================= + + Command Code: 0x000A + Controller Index: + Command Parameters: Link_Security (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to either enable or disable link level + security for an controller (also known as Security Mode 3). The + allowed values for the Link_Security command parameter are 0x00 + and 0x01. All other values will return Invalid Parameters. + + This command is only available for BR/EDR capable controllers + (e.g. not for single-mode LE ones). It will return Not Supported + otherwise. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Not Supported + Invalid Parameters + Invalid Index + + +Set Secure Simple Pairing Command +================================= + + Command Code: 0x000B + Controller Index: + Command Parameters: Secure_Simple_Pairing (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to enable/disable Secure Simple Pairing + support for a controller. The allowed values for the + Secure_Simple_Pairing command parameter are 0x00 and 0x01. All + other values will return Invalid Parameters. + + This command is only available for BR/EDR capable controllers + supporting the core specification version 2.1 or greater + (e.g. not for single-mode LE controllers or pre-2.1 ones). + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + In case the controller does not support Secure Simple Pairing, + the command will fail regardless with Not Supported error. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Not Supported + Invalid Parameters + Invalid Index + + +Set High Speed Command +====================== + + Command Code: 0x000C + Controller Index: + Command Parameters: High_Speed (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to enable/disable Bluetooth High Speed + support for a controller. The allowed values for the High_Speed + command parameter are 0x00 and 0x01. All other values will + return Invalid Parameters. + + This command is only available for BR/EDR capable controllers + (e.g. not for single-mode LE ones). + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + To enable High Speed support, it is required that Secure Simple + Pairing support is enabled first. High Speed support is not + possible for connections without Secure Simple Pairing. + + When switching Secure Simple Pairing off, the support for High + Speed will be switched off as well. Switching Secure Simple + Pairing back on, will not re-enable High Speed support. That + needs to be done manually. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Not Supported + Invalid Parameters + Invalid Index + + +Set Low Energy Command +====================== + + Command Code: 0x000D + Controller Index: + Command Parameters: Low_Energy (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to enable/disable Low Energy support for a + controller. The allowed values of the Low_Energy command + parameter are 0x00 and 0x01. All other values will return + Invalid Parameters. + + This command is only available for LE capable controllers and + will yield in a Not Supported error otherwise. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + In case the kernel subsystem does not support Low Energy or the + controller does not either, the command will fail regardless. + + Disabling LE support will permanently disable and remove all + advertising instances configured with the Add Advertising + command. Advertising Removed events will be issued accordingly. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Not Supported + Invalid Parameters + Invalid Index + + +Set Device Class +================ + + Command Code: 0x000E + Controller Index: + Command Parameters: Major_Class (1 Octet) + Minor_Class (1 Octet) + Return Parameters: Class_Of_Device (3 Octets) + + This command is used to set the major and minor device class for + BR/EDR capable controllers. + + This command will also implicitly disable caching of pending CoD + and EIR updates. + + This command is only available for BR/EDR capable controllers + (e.g. not for single-mode LE ones). + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + In case the controller is powered off, 0x000000 will be returned + for the class of device parameter. And after power on the new + value will be announced via class of device changed event. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Not Supported + Invalid Parameters + Invalid Index + + +Set Local Name Command +====================== + + Command Code: 0x000F + Controller Index: + Command Parameters: Name (249 Octets) + Short_Name (11 Octets) + Return Parameters: Name (249 Octets) + Short_Name (11 Octets) + + This command is used to set the local name of a controller. The + command parameters also include a short name which will be used + in case the full name doesn't fit within EIR/AD data. + + The name parameters need to always end with a null byte (failure + to do so will cause the command to fail). + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + The values of name and short name will be remembered when + switching the controller off and back on again. So the name + and short name only have to be set once when a new controller + is found and will stay until removed. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Add UUID Command +================ + + Command Code: 0x0010 + Controller Index: + Command Parameters: UUID (16 Octets) + SVC_Hint (1 Octet) + Return Parameters: Class_Of_Device (3 Octets) + + This command is used to add a UUID to be published in EIR data. + The accompanied SVC_Hint parameter is used to tell the kernel + whether the service class bits of the Class of Device value need + modifying due to this UUID. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + In case the controller is powered off, 0x000000 will be returned + for the class of device parameter. And after power on the new + value will be announced via class of device changed event. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Invalid Parameters + Invalid Index + + +Remove UUID Command +=================== + + Command Code: 0x0011 + Controller Index: + Command Parameters: UUID (16 Octets) + Return Parameters: Class_Of_Device (3 Octets) + + This command is used to remove a UUID previously added using the + Add UUID command. + + When the UUID parameter is an empty UUID (16 x 0x00), then all + previously loaded UUIDs will be removed. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + In case the controller is powered off, 0x000000 will be returned + for the class of device parameter. And after power on the new + value will be announced via class of device changed event. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Invalid Parameters + Invalid Index + + +Load Link Keys Command +====================== + + Command Code: 0x0012 + Controller Index: + Command Parameters: Debug_Keys (1 Octet) + Key_Count (2 Octets) + Key1 { + Address (6 Octets) + Address_Type (1 Octet) + Key_Type (1 Octet) + Value (16 Octets) + PIN_Length (1 Octet) + } + Key2 { } + ... + Return Parameters: + + This command is used to feed the kernel with currently known + link keys. The command does not need to be called again upon the + receipt of New Link Key events since the kernel updates its list + automatically. + + The Debug_Keys parameter is used to tell the kernel whether to + accept the usage of debug keys or not. The allowed values for + this parameter are 0x00 and 0x01. All other values will return + an Invalid Parameters response. + + Usage of the Debug_Keys parameter is deprecated and has been + replaced with the Set Debug Keys command. When setting the + Debug_Keys option via Load Link Keys command it has the same + affect as setting it via Set Debug Keys and applies to all + keys in the system. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 Reserved (not in use) + 2 Reserved (not in use) + + Public and random LE addresses are not valid and will be rejected. + + Currently defined Key_Type values are: + + 0x00 Combination key + 0x01 Local Unit key + 0x02 Remote Unit key + 0x03 Debug Combination key + 0x04 Unauthenticated Combination key from P-192 + 0x05 Authenticated Combination key from P-192 + 0x06 Changed Combination key + 0x07 Unauthenticated Combination key from P-256 + 0x08 Authenticated Combination key from P-256 + + This command can be used when the controller is not powered. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Load Long Term Keys Command +=========================== + + Command Code: 0x0013 + Controller Index: + Command Parameters: Key_Count (2 Octets) + Key1 { + Address (6 Octets) + Address_Type (1 Octet) + Key_Type (1 Octet) + Master (1 Octet) + Encryption_Size (1 Octet) + Encryption_Diversifier (2 Octets) + Random_Number (8 Octets) + Value (16 Octets) + } + Key2 { } + ... + Return Parameters: + + This command is used to feed the kernel with currently known + (SMP) Long Term Keys. The command does not need to be called + again upon the receipt of New Long Term Key events since the + kernel updates its list automatically. + + Possible values for the Address_Type parameter: + 0 Reserved (not in use) + 1 LE Public + 2 LE Random + + The provided Address and Address_Type are the identity of + a device. So either its public address or static random address. + + Unresolvable random addresses and resolvable random addresses are + not valid and will be rejected. + + Currently defined Key_Type values are: + + 0x00 Unauthenticated key + 0x01 Authenticated key + + This command can be used when the controller is not powered. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Disconnect Command +================== + + Command Code: 0x0014 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to force the disconnection of a currently + connected device. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Not Connected + Busy + Invalid Parameters + Not Powered + Invalid Index + + +Get Connections Command +======================= + + Command Code: 0x0015 + Controller Index: + Command Parameters: + Return Parameters: Connection_Count (2 Octets) + Address1 { + Address (6 Octets) + Address_Type (1 Octet) + } + Address2 { } + ... + + This command is used to retrieve a list of currently connected + devices. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + For devices using resolvable random addresses with a known + identity resolving key, the Address and Address_Type will + contain the identity information. + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Not Powered + Invalid Index + + +PIN Code Reply Command +======================= + + Command Code: 0x0016 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + PIN_Length (1 Octet) + PIN_Code (16 Octets) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to respond to a PIN Code request event. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Not Connected + Invalid Parameters + Not Powered + Invalid Index + + +PIN Code Negative Reply Command +=============================== + + Command Code: 0x0017 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to return a negative response to a PIN Code + Request event. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Not Connected + Invalid Parameters + Not Powered + Invalid Index + + +Set IO Capability Command +========================= + + Command Code: 0x0018 + Controller Index: + Command Parameters: IO_Capability (1 Octet) + Return Parameters: + + This command is used to set the IO Capability used for pairing. + The command accepts both SSP and SMP values. + + Possible values for the IO_Capability parameter: + 0 DisplayOnly + 1 DisplayYesNo + 2 KeyboardOnly + 3 NoInputNoOutput + 4 KeyboardDisplay + + Passing a value 4 (KeyboardDisplay) will cause the kernel to + convert it to 1 (DisplayYesNo) in the case of a BR/EDR + connection (as KeyboardDisplay is specific to SMP). + + This command can be used when the controller is not powered. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Pair Device Command +=================== + + Command Code: 0x0019 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + IO_Capability (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to trigger pairing with a remote device. + The IO_Capability command parameter is used to temporarily (for + this pairing event only) override the global IO Capability (set + using the Set IO Capability command). + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + Possible values for the IO_Capability parameter: + 0 DisplayOnly + 1 DisplayYesNo + 2 KeyboardOnly + 3 NoInputNoOutput + 4 KeyboardDisplay + + Passing a value 4 (KeyboardDisplay) will cause the kernel to + convert it to 1 (DisplayYesNo) in the case of a BR/EDR + connection (as KeyboardDisplay is specific to SMP). + + The Address and Address_Type of the return parameters will + return the identity address if known. In case of resolvable + random address given as command parameters and the remote + provides an identity resolving key, the return parameters + will provide the resolved address. + + To allow tracking of which resolvable random address changed + into which identity address, the New Identity Resolving Key + event will be sent before receiving Command Complete event + for this command. + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Reject status is used when requested transport is not enabled. + + Not Supported status is used if controller is not capable with + requested transport. + + Possible errors: Rejected + Not Supported + Connect Failed + Busy + Invalid Parameters + Not Powered + Invalid Index + Already Paired + + +Cancel Pair Device Command +========================== + + Command Code: 0x001A + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + The Address and Address_Type parameters should match what was + given to a preceding Pair Device command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Invalid Parameters + Not Powered + Invalid Index + + +Unpair Device Command +===================== + + Command Code: 0x001B + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Disconnect (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + Removes all keys associated with the remote device. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + The Disconnect parameter tells the kernel whether to forcefully + disconnect any existing connections to the device. It should in + practice always be 1 except for some special GAP qualification + test-cases where a key removal without disconnecting is needed. + + When unpairing a device its link key, long term key and if + provided identity resolving key will be purged. + + For devices using resolvable random addresses where the identity + resolving key was available, after this command they will now no + longer be resolved. The device will essentially become private + again. + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Not Paired + Invalid Parameters + Not Powered + Invalid Index + + +User Confirmation Reply Command +=============================== + + Command Code: 0x001C + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to respond to a User Confirmation Request + event. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Not Connected + Invalid Parameters + Not Powered + Invalid Index + + +User Confirmation Negative Reply Command +======================================== + + Command Code: 0x001D + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to return a negative response to a User + Confirmation Request event. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Not Connected + Invalid Parameters + Not Powered + Invalid Index + + +User Passkey Reply Command +========================== + + Command Code: 0x001E + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Passkey (4 Octets) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to respond to a User Confirmation Passkey + Request event. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Not Connected + Invalid Parameters + Not Powered + Invalid Index + + +User Passkey Negative Reply Command +=================================== + + Command Code: 0x001F + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to return a negative response to a User + Passkey Request event. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Not Connected + Invalid Parameters + Not Powered + Invalid Index + + +Read Local Out Of Band Data Command +=================================== + + Command Code: 0x0020 + Controller Index: + Command Parameters: + Return Parameters: Hash_192 (16 Octets) + Randomizer_192 (16 Octets) + Hash_256 (16 Octets, Optional) + Randomizer_256 (16 Octets, Optional) + + This command is used to read the local Out of Band data. + + This command can only be used when the controller is powered. + + If Secure Connections support is enabled, then this command + will return P-192 versions of hash and randomizer as well as + P-256 versions of both. + + Values returned by this command become invalid when the controller + is powered down. After each power-cycle it is required to call + this command again to get updated values. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Not Supported + Busy + Invalid Parameters + Not Powered + Invalid Index + + +Add Remote Out Of Band Data Command +=================================== + + Command Code: 0x0021 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Hash_192 (16 Octets) + Randomizer_192 (16 Octets) + Hash_256 (16 Octets, Optional) + Randomizer_256 (16 Octets, Optional) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to provide Out of Band data for a remote + device. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + Provided Out Of Band data is persistent over power down/up toggles. + + This command also accept optional P-256 versions of hash and + randomizer. If they are not provided, then they are set to + zero value. + + The P-256 versions of both can also be provided when the + support for Secure Connections is not enabled. However in + that case they will never be used. + + To only provide the P-256 versions of hash and randomizer, + it is valid to leave both P-192 fields as zero values. If + Secure Connections is disabled, then of course this is the + same as not providing any data at all. + + When providing data for remote LE devices, then the Hash_192 and + and Randomizer_192 fields are not used and shell be set to zero. + + The Hash_256 and Randomizer_256 fields can be used for LE secure + connections Out Of Band data. If only LE secure connections data + is provided the Hash_P192 and Randomizer_P192 fields can be set + to zero. Currently there is no support for providing the Security + Manager TK Value for LE legacy pairing. + + If Secure Connections Only mode has been enabled, then providing + Hash_P192 and Randomizer_P192 is not allowed. They are required + to be set to zero values. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Failed + Invalid Parameters + Not Powered + Invalid Index + + +Remove Remote Out Of Band Data Command +====================================== + + Command Code: 0x0022 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to remove data added using the Add Remote + Out Of Band Data command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + When the Address parameter is 00:00:00:00:00:00, then all + previously added data will be removed. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Invalid Parameters + Not Powered + Invalid Index + + +Start Discovery Command +======================= + + Command Code: 0x0023 + Controller Index: + Command Parameters: Address_Type (1 Octet) + Return Parameters: Address_Type (1 Octet) + + This command is used to start the process of discovering remote + devices. A Device Found event will be sent for each discovered + device. + + Possible values for the Address_Type parameter are a bit-wise or + of the following bits: + + 0 BR/EDR + 1 LE Public + 2 LE Random + + By combining these e.g. the following values are possible: + + 1 BR/EDR + 6 LE (public & random) + 7 BR/EDR/LE (interleaved discovery) + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Busy + Not Supported + Invalid Parameters + Not Powered + Invalid Index + + +Stop Discovery Command +====================== + + Command Code: 0x0024 + Controller Index: + Command Parameters: Address_Type (1 Octet) + Return Parameters: Address_Type (1 Octet) + + This command is used to stop the discovery process started using + the Start Discovery command. + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Rejected + Invalid Parameters + Invalid Index + + +Confirm Name Command +==================== + + Command Code: 0x0025 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Name_Known (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is only valid during device discovery and is + expected for each Device Found event with the Confirm Name + flag set. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + The Name_Known parameter should be set to 0x01 if user space + knows the name for the device and 0x00 if it doesn't. If set to + 0x00 the kernel will perform a name resolving procedure for the + device in question. + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Failed + Invalid Parameters + Invalid Index + + +Block Device Command +==================== + + Command Code: 0x0026 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to add a device to the list of devices + which should be blocked from being connected to the local + controller. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + For Low Energy devices, the blocking of a device takes precedence + over auto-connection actions provided by Add Device. Blocked + devices will not be auto-connected or even reported when found + during background scanning. If the controller is connectable + direct advertising from blocked devices will also be ignored. + + Connections created from advertising of the controller will + be dropped if the device is blocked. + + This command can be used when the controller is not powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Failed + Invalid Parameters + Invalid Index + + +Unblock Device Command +====================== + + Command Code: 0x0027 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to remove a device from the list of blocked + devices (where it was added to using the Block Device command). + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + When the Address parameter is 00:00:00:00:00:00, then all + previously blocked devices will be unblocked. + + This command can be used when the controller is not powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Set Device ID Command +===================== + + Command Code: 0x0028 + Controller Index: + Command Parameters: Source (2 Octets) + Vendor (2 Octets) + Product (2 Octets) + Version (2 Octets) + Return Parameters: + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + The Source parameter selects the organization that assigned the + Vendor parameter: + + 0x0000 Disable Device ID + 0x0001 Bluetooth SIG + 0x0002 USB Implementer's Forum + + The information is put into the EIR data. If the controller does + not support EIR or if SSP is disabled, this command will still + succeed. The information is stored for later use and will survive + toggling SSP on and off. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Set Advertising Command +======================= + + Command Code: 0x0029 + Controller Index: + Command Parameters: Advertising (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to enable LE advertising on a controller + that supports it. The allowed values for the Advertising command + parameter are 0x00, 0x01 and 0x02. All other values will return + Invalid Parameters. + + The value 0x00 disables advertising, the value 0x01 enables + advertising with considering of connectable setting and the + value 0x02 enables advertising in connectable mode. + + Using value 0x01 means that when connectable setting is disabled, + the advertising happens with undirected non-connectable advertising + packets and a non-resolvable random address is used. If connectable + setting is enabled, then undirected connectable advertising packets + and the identity address or resolvable private address are used. + + LE Devices configured via Add Device command with Action 0x01 + have no effect when using Advertising value 0x01 since only the + connectable setting is taken into account. + + To utilize undirected connectable advertising without changing the + connectable setting, the value 0x02 can be utilized. It makes the + device connectable via LE without the requirement for being + connectable on BR/EDR (and/or LE). + + The value 0x02 should be the preferred mode of operation when + implementing peripheral mode. + + Using this command will temporarily deactivate any configuration + made by the Add Advertising command. This command takes precedence. + Once a Set Advertising command with value 0x00 is issued any + previously made configurations via Add/Remove Advertising, including + such changes made while Set Advertising was active, will be re- + enabled. + + A pre-requisite is that LE is already enabled, otherwise this + command will return a "rejected" response. + + This command generates a Command Complete event on success or a + Command Status event on failure. + + Possible errors: Busy + Rejected + Not Supported + Invalid Parameters + Invalid Index + + +Set BR/EDR Command +================== + + Command Code: 0x002A + Controller Index: + Command Parameters: BR/EDR (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to enable or disable BR/EDR support + on a dual-mode controller. The allowed values for the Advertising + command parameter are 0x00 and 0x01. All other values will + return Invalid Parameters. + + A pre-requisite is that LE is already enabled, otherwise + this command will return a "rejected" response. Enabling BR/EDR + can be done both when powered on and powered off, however + disabling it can only be done when powered off (otherwise the + command will again return "rejected"). Disabling BR/EDR will + automatically disable all other BR/EDR related settings. + + This command generates a Command Complete event on success or a + Command Status event on failure. + + Possible errors: Busy + Rejected + Not Supported + Invalid Parameters + Invalid Index + + +Set Static Address Command +========================== + + Command Code: 0x002B + Controller Index: + Command Parameters: Address (6 Octets) + Return Parameters: Current_Settings (4 Octets) + + This command allows for setting the static random address. It is + only supported on controllers with LE support. The static random + address is suppose to be valid for the lifetime of the + controller or at least until the next power cycle. To ensure + such behavior, setting of the address is limited to when the + controller is powered off. + + The special BDADDR_ANY address (00:00:00:00:00:00) can be used + to disable the static address. + + When a controller has a public address (which is required for + all dual-mode controllers), this address is not used. If a dual-mode + controller is configured as Low Energy only devices (BR/EDR has + been switched off), then the static address is used. Only when + the controller information reports BDADDR_ANY (00:00:00:00:00:00), + it is required to configure a static address first. + + If privacy mode is enabled and the controller is single mode + LE only without a public address, the static random address is + used as identity address. + + The Static Address flag from the current settings can also be used + to determine if the configured static address is in use or not. + + This command generates a Command Complete event on success or a + Command Status event on failure. + + Possible errors: Rejected + Not Supported + Invalid Parameters + Invalid Index + + +Set Scan Parameters Command +=========================== + + Command Code: 0x002C + Controller Index: + Command Parameters: Interval (2 Octets) + Window (2 Octets) + Return Parameters: + + This command allows for setting the Low Energy scan parameters + used for connection establishment and passive scanning. It is + only supported on controllers with LE support. + + This command generates a Command Complete event on success or a + Command Status event on failure. + + Possible errors: Rejected + Not Supported + Invalid Parameters + Invalid Index + + +Set Secure Connections Command +============================== + + Command Code: 0x002D + Controller Index: + Command Parameters: Secure_Connections (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to enable/disable Secure Connections + support for a controller. The allowed values for the + Secure_Connections command parameter are 0x00, 0x01 and 0x02. + All other values will return Invalid Parameters. + + The value 0x00 disables Secure Connections, the value 0x01 + enables Secure Connections and the value 0x02 enables Secure + Connections Only mode. + + This command is only available for LE capable controllers as + well as controllers supporting the core specification version + 4.1 or greater. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + In case the controller does not support Secure Connections + the command will fail regardless with Not Supported error. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Not Supported + Invalid Parameters + Invalid Index + + +Set Debug Keys Command +====================== + + Command Code: 0x002E + Controller Index: + Command Parameters: Debug_Keys (1 Octet) + Return Parameters: Current_Settings (4 Octets) + + This command is used to tell the kernel whether to accept the + usage of debug keys or not. The allowed values for this parameter + are 0x00, 0x01 and 0x02. All other values will return an Invalid + Parameters response. + + With a value of 0x00 any generated debug key will be discarded + as soon as the connection terminates. + + With a value of 0x01 generated debug keys will be kept and can + be used for future connections. However debug keys are always + marked as non persistent and should not be stored. This means + a reboot or changing the value back to 0x00 will delete them. + + With a value of 0x02 generated debug keys will be kept and can + be used for future connections. This has the same affect as + with value 0x01. However in addition this value will also + enter the controller mode to generate debug keys for each + new pairing. Changing the value back to 0x01 or 0x00 will + disable the controller mode for generating debug keys. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Busy + Not Supported + Invalid Parameters + Invalid Index + + +Set Privacy Command +=================== + + Command Code: 0x002F + Controller Index: + Command Parameters: Privacy (1 Octet) + Identity_Resolving_Key (16 Octets) + Return Parameters: Current_Settings (4 Octets) + + This command is used to enable Low Energy Privacy feature using + resolvable private addresses. + + The value 0x00 disables privacy mode, the values 0x01 and 0x02 + enable privacy mode. + + With value 0x01 the kernel will always use the privacy mode. This + means resolvable private address is used when the controller is + discoverable and also when pairing is initiated. + + With value 0x02 the kernel will use a limited privacy mode with a + resolvable private address except when the controller is bondable + and discoverable, in which case the identity address is used. + + Exposing the identity address when bondable and discoverable or + during initiated pairing can be a privacy issue. For dual-mode + controllers this can be neglected since its public address will + be exposed over BR/EDR anyway. The benefit of exposing the + identity address for pairing purposes is that it makes matching + up devices with dual-mode topology during device discovery now + possible. + + If the privacy value 0x02 is used, then also the GATT database + should expose the Privacy Characteristic so that remote devices + can determine if the privacy feature is in use or not. + + When the controller has a public address (mandatory for dual-mode + controllers) it is used as identity address. In case the controller + is single mode LE only without a public address, it is required + to configure a static random address first. The privacy mode can + only be enabled when an identity address is available. + + The Identity_Resolving_Key is the local key assigned for the local + resolvable private address. + + Possible errors: Busy + Not Supported + Invalid Parameters + Invalid Index + + +Load Identity Resolving Keys Command +==================================== + + Command Code: 0x0030 + Controller Index: + Command Parameters: Key_Count (2 Octets) + Key1 { + Address (6 Octets) + Address_Type (1 Octet) + Value (16 Octets) + } + Key2 { } + ... + Return Parameters: + + This command is used to feed the kernel with currently known + identity resolving keys. The command does not need to be called + again upon the receipt of New Identity Resolving Key events + since the kernel updates its list automatically. + + Possible values for the Address_Type parameter: + 0 Reserved (not in use) + 1 LE Public + 2 LE Random + + The provided Address and Address_Type are the identity of + a device. So either its public address or static random address. + + Unresolvable random addresses and resolvable random addresses are + not valid and will be rejected. + + This command can be used when the controller is not powered. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Get Connection Information Command +================================== + + Command Code: 0x0031 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + RSSI (1 Octet) + TX_Power (1 Octet) + Max_TX_Power (1 Octet) + + This command is used to get connection information. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + TX_Power and Max_TX_Power can be set to 127 if values are invalid or + unknown. A value of 127 for RSSI indicates that it is not available. + + This command generates a Command Complete event on success and + on failure. In case of failure only Address and Address_Type fields + are valid and values of remaining parameters are considered invalid + and shall be ignored. + + Possible errors: Not Connected + Not Powered + Invalid Parameters + Invalid Index + + +Get Clock Information Command +============================= + + Command Code: 0x0032 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + Local_Clock (4 Octets) + Piconet_Clock (4 Octets) + Accuracy (2 Octets) + + This command is used to get local and piconet clock information. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 Reserved (not in use) + 2 Reserved (not in use) + + The Accuracy can be set to 0xffff which means the value is unknown. + + If the Address is set to 00:00:00:00:00:00, then only the + Local_Clock field has a valid value. The Piconet_Clock and + Accuracy fields are invalid and shall be ignored. + + This command generates a Command Complete event on success and + on failure. In case of failure only Address and Address_Type fields + are valid and values of remaining parameters are considered invalid + and shall be ignored. + + Possible errors: Not Connected + Not Powered + Invalid Parameters + Invalid Index + + +Add Device Command +================== + + Command Code: 0x0033 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Action (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to add a device to the action list. The + action list allows scanning for devices and enables incoming + connections from known devices. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + Possible values for the Action parameter: + 0 Background scan for device + 1 Allow incoming connection + 2 Auto-connect remote device + + With the Action 0, when the device is found, a new Device Found + event will be sent indicating this device is available. This + action is only valid for LE Public and LE Random address types. + + With the Action 1, the device is allowed to connect. For BR/EDR + address type this means an incoming connection. For LE Public + and LE Random address types, a connection will be established + to devices using directed advertising. If successful a Device + Connected event will be sent. + + With the Action 2, when the device is found, it will be connected + and if successful a Device Connected event will be sent. This + action is only valid for LE Public and LE Random address types. + + When a device is blocked using Block Device command, then it is + valid to add the device here, but all actions will be ignored + until the device is unblocked. + + Devices added with Action 1 are allowed to connect even if the + connectable setting is off. This acts as list of known trusted + devices. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Failed + Invalid Parameters + Invalid Index + + +Remove Device Command +===================== + + Command Code: 0x0034 + Controller Index: + Command Parameters: Address (6 Octets) + Address_Type (1 Octet) + Return Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This command is used to remove a device from the action list + previously added by using the Add Device command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + When the Address parameter is 00:00:00:00:00:00, then all + previously added devices will be removed. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Load Connection Parameters Command +================================== + + Command Code: 0x0035 + Controller Index: + Command Parameters: Param_Count (2 Octets) + Param1 { + Address (6 Octets) + Address_Type (1 Octet) + Min_Connection_Interval (2 Octets) + Max_Connection_Interval (2 Octets) + Connection_Latency (2 Octets) + Supervision_Timeout (2 Octets) + } + Param2 { } + ... + Return Parameters: + + This command is used to load connection parameters from several + devices into kernel. Currently this is only supported on controllers + with Low Energy support. + + Possible values for the Address_Type parameter: + 0 Reserved (not in use) + 1 LE Public + 2 LE Random + + The provided Address and Address_Type are the identity of + a device. So either its public address or static random address. + + The Min_Connection_Interval, Max_Connection_Interval, + Connection_Latency and Supervision_Timeout parameters should + be configured as described in Core 4.1 spec, Vol 2, 7.8.12. + + This command can be used when the controller is not powered. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + Not Supported + + +Read Unconfigured Controller Index List Command +=============================================== + + Command Code: 0x0036 + Controller Index: + Command Parameters: + Return Parameters: Num_Controllers (2 Octets) + Controller_Index[i] (2 Octets) + + This command returns the list of currently unconfigured controllers. + Unconfigured controllers added after calling this command can be + monitored using the Unconfigured Index Added event. + + An unconfigured controller can either move to a configured state + by indicating Unconfigured Index Removed event followed by an + Index Added event; or it can be removed from the system which + would be indicated by the Unconfigured Index Removed event. + + Only controllers that require configuration will be listed with + this command. A controller that is fully configured will not + be listed even if it supports configuration changes. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + +Read Controller Configuration Information Command +================================================= + + Command Code: 0x0037 + Controller Index: + Command Parameters: + Return Parameters: Manufacturer (2 Octets) + Supported_Options (4 Octets) + Missing_Options (4 Octets) + + This command is used to retrieve the supported configuration + options of a controller and the missing configuration options. + + The missing options are required to be configured before the + controller is considered fully configured and ready for standard + operation. The command is typically used right after getting the + response to Read Unconfigured Controller Index List command or + Unconfigured Index Added event. + + Supported_Options and Missing_Options is a bitmask with currently + the following available bits: + + 0 External configuration + 1 Bluetooth public address configuration + + It is valid to call this command on controllers that do not + require any configuration. It is possible that a fully configured + controller offers additional support for configuration. + + For example a controller may contain a valid Bluetooth public + device address, but also allows to configure it from the host + stack. In this case the general support for configurations will + be indicated by the Controller Configuration settings. For + controllers where no configuration options are available that + setting option will not be present. + + When all configurations have been completed and as a result the + Missing_Options mask would become empty, then the now ready + controller will be announced via Index Added event. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Set External Configuration Command +================================== + + Command Code: 0x0038 + Controller Index: + Command Parameters: Configuration (1 Octet) + Return Parameters: Missing_Options (4 Octets) + + This command allows to change external configuration option to + indicate that a controller is now configured or unconfigured. + + The value 0x00 sets unconfigured state and the value 0x01 sets + configured state of the controller. + + It is not mandatory that this configuration option is provided + by a controller. If it is provided, the configuration has to + happen externally using user channel operation or via vendor + specific methods. + + Setting this option and when Missing_Options returns zero, this + means that the controller will switch to configured state and it + can be expected that it will be announced via Index Added event. + + Wrongly configured controllers might still cause an error when + trying to power them via Set Powered command. + + This command generates a Command Complete event on success or a + Command Status event on failure. + + Possible errors: Rejected + Not Supported + Invalid Parameters + Invalid Index + + +Set Public Address Command +========================== + + Command Code: 0x0039 + Controller Index: + Command Parameters: Address (6 Octets) + Return Parameters: Missing_Options (4 Octets) + + This command allows configuration of public address. Since a vendor + specific procedure is required, this command might not be supported + by all controllers. Actually most likely only a handful embedded + controllers will offer support for this command. + + When the support for Bluetooth public address configuration is + indicated in the supported options mask, then this command + can be used to configure the public address. + + It is only possible to configure the public address when the + controller is powered off. + + For an unconfigured controller and when Missing_Options returns + an empty mask, this means that a Index Added event for the now + fully configured controller can be expected. + + For a fully configured controller, the current controller index + will become invalid and an Unconfigured Index Removed event will + be sent. Once the address has been successfully changed an Index + Added event will be sent. There is no guarantee that the controller + index stays the same. + + All previous configured parameters and settings are lost when + this command succeeds. The controller has to be treated as new + one. Use this command for a fully configured controller only when + you really know what you are doing. + + This command generates a Command Complete event on success or a + Command Status event on failure. + + Possible errors: Rejected + Not Supported + Invalid Parameters + Invalid Index + + +Start Service Discovery Command +=============================== + + Command Code: 0x003a + Controller Index: + Command Parameters: Address_Type (1 Octet) + RSSI_Threshold (1 Octet) + UUID_Count (2 Octets) + UUID[i] (16 Octets) + Return Parameters: Address_Type (1 Octet) + + This command is used to start the process of discovering remote + devices with a specific UUID. A Device Found event will be sent + for each discovered device. + + Possible values for the Address_Type parameter are a bit-wise or + of the following bits: + + 0 BR/EDR + 1 LE Public + 2 LE Random + + By combining these e.g. the following values are possible: + + 1 BR/EDR + 6 LE (public & random) + 7 BR/EDR/LE (interleaved discovery) + + The service discovery uses active scanning for Low Energy scanning + and will search for UUID in both advertising data and scan response + data. + + Found devices that have a RSSI value smaller than RSSI_Threshold + are not reported via DeviceFound event. Setting a value of 127 + will cause all devices to be reported. + + The list of UUIDs identifies a logical OR. Only one of the UUIDs + have to match to cause a DeviceFound event. Providing an empty + list of UUIDs with Num_UUID set to 0 which means that DeviceFound + events are send out for all devices above the RSSI_Threshold. + + In case RSSI_Threshold is set to 127 and UUID_Count is 0, then + this command behaves exactly the same as Start Discovery. + + When the discovery procedure starts the Discovery event will + notify this similar to Start Discovery. + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Busy + Not Supported + Invalid Parameters + Not Powered + Invalid Index + + +Read Local Out Of Band Extended Data Command +============================================ + + Command Code: 0x003b + Controller Index: + Command Parameters: Address_Type (1 Octet) + Return Parameters: Address_Type (1 Octet) + EIR_Data_Length (2 Octets) + EIR_Data (0-65535 Octets) + + This command is used to read the local Out of Band data + information and provide them encoded as extended inquiry + response information or advertising data. + + Possible values for the Address_Type parameter are a bit-wise or + of the following bits: + + 0 BR/EDR + 1 LE Public + 2 LE Random + + By combining these e.g. the following values are possible: + + 1 BR/EDR + 6 LE (public & random) + 7 Reserved (not in use) + + For BR/EDR controller (Address_Type 1) the returned information + will contain the following information: + + Class of Device + Simple Pairing Hash C-192 (optional) + Simple Pairing Randomizer R-192 (optional) + Simple Pairing Hash C-256 (optional) + Simple Pairing Randomizer R-256 (optional) + Service Class UUID (optional) + Bluetooth Local Name (optional) + + The Simple Pairing Hash C-256 and Simple Pairing Randomizer R-256 + fields are only included when secure connections has been enabled. + + The Device Address (BD_ADDR) is not included in the EIR_Data and + needs to be taken from controller information. + + For LE controller (Address_Type 6) the returned information + will contain the following information: + + LE Bluetooth Device Address + LE Role + LE Secure Connections Confirmation Value (optional) + LE Secure Connections Random Value (optional) + Appearance (optional) + Local Name (optional) + Flags + + The LE Secure Connections Confirmation Value and LE Secure Connections + Random Value fields are only included when secure connections has been + enabled. + + The Security Manager TK Value from the Bluetooth specification can + not be provided by this command. The Out Of Band information here are + for asymmetric exchanges based on Diffie-Hellman key exchange. The + Security Manager TK Value is a symmetric random number that has to + be acquired and agreed upon differently. + + The returned information from BR/EDR controller and LE controller + types are not related to each other. Once they have been used + over an Out Of Band link, a new set of information shall be + requested. + + When Secure Connections Only mode has been enabled, then the fields + for Simple Pairing Hash C-192 and Simple Pairing Randomizer R-192 + are not returned. Only the fields for the strong secure connections + pairing are included. + + This command can only be used when the controller is powered. + + Values returned by this command become invalid when the controller + is powered down. After each power-cycle it is required to call + this command again to get updated information. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Not Supported + Busy + Invalid Parameters + Not Powered + Invalid Index + + +Read Extended Controller Index List Command +=========================================== + + Command Code: 0x003c + Controller Index: + Command Parameters: + Return Parameters: Num_Controllers (2 Octets) + Controller_Index[i] (2 Octets) + Controller_Type[i] (1 Octet) + Controller_Bus[i] (1 Octet) + + This command returns the list of currently known controllers. It + includes configured, unconfigured and alternate controllers. + + Controllers added or removed after calling this command can be + be monitored using the Extended Index Added and Extended Index + Removed events. + + The existing Index Added, Index Removed, Unconfigured Index Added + and Unconfigured Index Removed are no longer sent after this command + has been used at least once. + + Instead of calling Read Controller Index List and Read Unconfigured + Controller Index List, this command combines all the information + and can be used to retrieve the controller list. + + The Controller_Type parameter has these values: + + 0x00 Primary Controller (BR/EDR and/or LE) + 0x01 Unconfigured Controller (BR/EDR and/or LE) + 0x02 Alternate MAC/PHY Controller (AMP) + + The 0x00 and 0x01 types indicate a primary BR/EDR and/or LE + controller. The difference is just if they need extra configuration + or if they are fully configured. + + Controllers in configured state will be listed as 0x00 and controllers + in unconfigured state will be listed as 0x01. A controller that is + fully configured and supports configuration changes will be listed + as 0x00. + + Alternate MAC/PHY controllers will be listed as 0x02. They do not + support the difference between configured and unconfigured state. + + The Controller_Bus parameter has these values: + + 0x00 Virtual + 0x01 USB + 0x02 PCMCIA + 0x03 UART + 0x04 RS232 + 0x05 PCI + 0x06 SDIO + 0x07 SPI + 0x08 I2C + 0x09 SMD + + Controllers marked as RAW only operation are currently not listed + by this command. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + +Read Advertising Features Command +================================= + + Command Code: 0x003d + Controller Index: + Command Parameters: + Return Parameters: Supported_Flags (4 Octets) + Max_Adv_Data_Len (1 Octet) + Max_Scan_Rsp_Len (1 Octet) + Max_Instances (1 Octet) + Num_Instances (1 Octet) + Instance[i] (1 Octet) + + This command is used to read the advertising features supported + by the controller and stack. + + With the Supported_Flags field the possible values for the Flags + field in Add Advertising command provided: + + 0 Switch into Connectable mode + 1 Advertise as Discoverable + 2 Advertise as Limited Discoverable + 3 Add Flags field to Adv_Data + 4 Add TX Power field to Adv_Data + 5 Add Appearance field to Scan_Rsp + 6 Add Local Name in Scan_Rsp + 7 Secondary Channel with LE 1M + 8 Secondary Channel with LE 2M + 9 Secondary Channel with LE Coded + + The Flags bit 0 indicates support for connectable advertising + and for switching to connectable advertising independent of the + connectable global setting. When this flag is not supported, then + the global connectable setting determines if undirected connectable, + undirected scannable or undirected non-connectable advertising is + used. It also determines the use of non-resolvable random address + versus identity address or resolvable private address. + + The Flags bit 1 indicates support for advertising with discoverable + mode enabled. Users of this flag will decrease the Max_Adv_Data_Len + by 3 octets. In this case the advertising data flags are managed + and added in front of the provided advertising data. + + The Flags bit 2 indicates support for advertising with limited + discoverable mode enabled. Users of this flag will decrease the + Max_Adv_Data_Len by 3 octets. In this case the advertising data + flags are managed and added in front of the provided advertising + data. + + The Flags bit 3 indicates support for automatically keeping the + Flags field of the advertising data updated. Users of this flag + will decrease the Max_Adv_Data_Len by 3 octets and need to keep + that in mind. The Flags field will be added in front of the + advertising data provided by the user. Note that with Flags bit 1 + and Flags bit 2, this one will be implicitly used even if it is + not marked as supported. + + The Flags bit 4 indicates support for automatically adding the + TX Power value to the advertising data. Users of this flag will + decrease the Max_Adv_Data_Len by 3 octets. The TX Power field will + be added at the end of the user provided advertising data. If the + controller does not support TX Power information, then this bit will + not be set. + + The Flags bit 5 indicates support for automatically adding the + Appearance value to the scan response data. Users of this flag + will decrease the Max_Scan_Rsp_len by 4 octets. The Appearance + field will be added in front of the scan response data provided + by the user. If the appearance value is not supported, then this + bit will not be set. + + The Flags bit 6 indicates support for automatically adding the + Local Name value to the scan response data. This flag indicates + an opportunistic approach for the Local Name. If enough space + in the scan response data is available, it will be added. If the + space is limited a short version or no name information. The + Local Name will be added at the end of the scan response data. + + The Flags bit 7 indicates support for advertising in secondary + channel in LE 1M PHY. + + The Flags bit 8 indicates support for advertising in secondary + channel in LE 2M PHY. Primary channel would be on 1M. + + The Flags bit 9 indicates support for advertising in secondary + channel in LE CODED PHY. + + The valid range for Instance identifiers is 1-254. The value 0 + is reserved for internal use and the value 255 is reserved for + future extensions. However the Max_Instances value for indicating + the number of supported Instances can be also 0 if the controller + does not support any advertising. + + The Max_Adv_Data_Len and Max_Scan_Rsp_Len provides extra + information about the maximum length of the data fields. For + now this will always return the value 31. Different flags + however might decrease the actual available length in these + data fields. + + With Num_Instances and Instance array the currently occupied + Instance identifiers can be retrieved. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Add Advertising Command +======================= + + Command Code: 0x003e + Controller Index: + Command Parameters: Instance (1 Octet) + Flags (4 Octets) + Duration (2 Octets) + Timeout (2 Octets) + Adv_Data_Len (1 Octet) + Scan_Rsp_len (1 Octet) + Adv_Data (0-255 Octets) + Scan_Rsp (0-255 Octets) + Return Parameters: Instance (1 Octet) + + This command is used to configure an advertising instance that + can be used to switch a Bluetooth Low Energy controller into + advertising mode. + + Added advertising information with this command will not be visible + immediately if advertising is enabled via the Set Advertising + command. The usage of the Set Advertising command takes precedence + over this command. Instance information is stored and will be + advertised once advertising via Set Advertising has been disabled. + + The Instance identifier is a value between 1 and the number of + supported instances. The value 0 is reserved. + + With the Flags value the type of advertising is controlled and + the following flags are defined: + + 0 Switch into Connectable mode + 1 Advertise as Discoverable + 2 Advertise as Limited Discoverable + 3 Add Flags field to Adv_Data + 4 Add TX Power field to Adv_Data + 5 Add Appearance field to Scan_Rsp + 6 Add Local Name in Scan_Rsp + 7 Secondary Channel with LE 1M + 8 Secondary Channel with LE 2M + 9 Secondary Channel with LE Coded + + When the connectable flag is set, then the controller will use + undirected connectable advertising. The value of the connectable + setting can be overwritten this way. This is useful to switch a + controller into connectable mode only for LE operation. This is + similar to the mode 0x02 from the Set Advertising command. + + When the connectable flag is not set, then the controller will + use advertising based on the connectable setting. When using + non-connectable or scannable advertising, the controller will + be programmed with a non-resolvable random address. When the + system is connectable, then the identity address or resolvable + private address will be used. + + Using the connectable flag is useful for peripheral mode support + where BR/EDR (and/or LE) is controlled by Add Device. This allows + making the peripheral connectable without having to interfere + with the global connectable setting. + + If Scan_Rsp_Len is zero and connectable flag is not set and + the global connectable setting is off, then non-connectable + advertising is used. If Scan_Rsp_Len is larger than zero and + connectable flag is not set and the global advertising is off, + then scannable advertising is used. This small difference is + supported to provide less air traffic for devices implementing + broadcaster role. + + Secondary channel flags can be used to advertise in secondary + channel with the corresponding PHYs. These flag bits are mutually + exclusive and setting multiple will result in Invalid Parameter + error. Choosing either LE 1M or LE 2M will result in using + extended advertising on the primary channel with LE 1M and the + respectively LE 1M or LE 2M on the secondary channel. Choosing + LE Coded will result in using extended advertising on the primary + and secondary channels with LE Coded. Choosing none of these flags + will result in legacy advertising. + + The Duration parameter configures the length of an Instance. The + value is in seconds. + + A value of 0 indicates a default value is chosen for the + Duration. The default is 2 seconds. + + If only one advertising Instance has been added, then the Duration + value will be ignored. It only applies for the case where multiple + Instances are configured. In that case every Instance will be + available for the Duration time and after that it switches to + the next one. This is a simple round-robin based approach. + + The Timeout parameter configures the life-time of an Instance. In + case the value 0 is used it indicates no expiration time. If a + timeout value is provided, then the advertising Instance will be + automatically removed when the timeout passes. The value for the + timeout is in seconds. Powering down a controller will invalidate + all advertising Instances and it is not possible to add a new + Instance with a timeout when the controller is powered down. + + When a Timeout is provided, then the Duration subtracts from + the actual Timeout value of that Instance. For example an Instance + with Timeout of 5 and Duration of 2 will be scheduled exactly 3 + times, twice with 2 seconds and once with one second. Other + Instances have no influence on the Timeout. + + Re-adding an already existing instance (i.e. issuing the Add + Advertising command with an Instance identifier of an existing + instance) will update that instance's configuration. + + An instance being added or changed while another instance is + being advertised will not be visible immediately but only when + the new/changed instance is being scheduled by the round robin + advertising algorithm. + + Changes to an instance that is currently being advertised will + cancel that instance and switch to the next instance. The changes + will be visible the next time the instance is scheduled for + advertising. In case a single instance is active, this means + that changes will be visible right away. + + A pre-requisite is that LE is already enabled, otherwise this + command will return a "rejected" response. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + This command generates a Command Complete event on success or a + Command Status event on failure. + + Possible errors: Failed + Rejected + Not Supported + Invalid Parameters + Invalid Index + + +Remove Advertising Command +========================== + + Command Code: 0x003f + Controller Index: + Command Parameters: Instance (1 Octet) + Return Parameters: Instance (1 Octet) + + This command is used to remove an advertising instance that + can be used to switch a Bluetooth Low Energy controller into + advertising mode. + + When the Instance parameter is zero, then all previously added + advertising Instances will be removed. + + Removing advertising information with this command will not be + visible as long as advertising is enabled via the Set Advertising + command. The usage of the Set Advertising command takes precedence + over this command. Changes to Instance information are stored and + will be advertised once advertising via Set Advertising has been + disabled. + + Removing an instance while it is being advertised will immediately + cancel the instance, even when it has been advertised less then its + configured Timeout or Duration. + + This command can be used when the controller is not powered and + all settings will be programmed once powered. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Get Advertising Size Information Command +======================================== + + Command Code: 0x0040 + Controller Index: + Command Parameters: Instance (1 Octet) + Flags (4 Octets) + Return Parameters: Instance (1 Octet) + Flags (4 Octets) + Max_Adv_Data_Len (1 Octet) + Max_Scan_Rsp_Len (1 Octet) + + The Read Advertising Features command returns the overall maximum + size of advertising data and scan response data fields. That size is + valid when no Flags are used. However when certain Flags are used, + then the size might decrease. This command can be used to request + detailed information about the maximum available size. + + The following Flags values are defined: + + 0 Switch into Connectable mode + 1 Advertise as Discoverable + 2 Advertise as Limited Discoverable + 3 Add Flags field to Adv_Data + 4 Add TX Power field to Adv_Data + 5 Add Appearance field to Scan_Rsp + 6 Add Local Name in Scan_Rsp + + To get accurate information about the available size, the same Flags + values should be used with the Add Advertising command. + + The Max_Adv_Data_Len and Max_Scan_Rsp_Len fields provide information + about the maximum length of the data fields for the given Flags + values. When the Flags field is zero, then these fields would contain + the same values as Read Advertising Features. + + Possible errors: Invalid Parameters + Invalid Index + + +Start Limited Discovery Command +=============================== + + Command Code: 0x0041 + Controller Index: + Command Parameters: Address_Type (1 Octet) + Return Parameters: Address_Type (1 Octet) + + This command is used to start the process of discovering remote + devices using the limited discovery procedure. A Device Found event + will be sent for each discovered device. + + Possible values for the Address_Type parameter are a bit-wise or + of the following bits: + + 0 BR/EDR + 1 LE Public + 2 LE Random + + By combining these e.g. the following values are possible: + + 1 BR/EDR + 6 LE (public & random) + 7 BR/EDR/LE (interleaved discovery) + + The limited discovery uses active scanning for Low Energy scanning + and will search for devices with the limited discoverability flag + configured. On BR/EDR it uses LIAC and filters on the limited + discoverability flag of the class of device. + + When the discovery procedure starts the Discovery event will + notify this similar to Start Discovery. + + This command can only be used when the controller is powered. + + This command generates a Command Complete event on success + or failure. + + Possible errors: Busy + Not Supported + Invalid Parameters + Not Powered + Invalid Index + + +Read Extended Controller Information Command +============================================ + + Command Code: 0x0042 + Controller Index: + Command Parameters: + Return Parameters: Address (6 Octets) + Bluetooth_Version (1 Octet) + Manufacturer (2 Octets) + Supported_Settings (4 Octets) + Current_Settings (4 Octets) + EIR_Data_Length (2 Octets) + EIR_Data (0-65535 Octets) + + This command is used to retrieve the current state and basic + information of a controller. It is typically used right after + getting the response to the Read Controller Index List command + or an Index Added event (or its extended counterparts). + + The Address parameter describes the controllers public address + and it can be expected that it is set. However in case of single + mode Low Energy only controllers it can be 00:00:00:00:00:00. To + power on the controller in this case, it is required to configure + a static address using Set Static Address command first. + + If the public address is set, then it will be used as identity + address for the controller. If no public address is available, + then the configured static address will be used as identity + address. + + In the case of a dual-mode controller with public address that + is configured as Low Energy only device (BR/EDR switched off), + the static address is used when set and public address otherwise. + + Current_Settings and Supported_Settings is a bitmask with + currently the following available bits: + + 0 Powered + 1 Connectable + 2 Fast Connectable + 3 Discoverable + 4 Bondable + 5 Link Level Security (Sec. mode 3) + 6 Secure Simple Pairing + 7 Basic Rate/Enhanced Data Rate + 8 High Speed + 9 Low Energy + 10 Advertising + 11 Secure Connections + 12 Debug Keys + 13 Privacy + 14 Controller Configuration + 15 Static Address + + The EIR_Data field contains information about class of device, + local name and other values. Not all of them might be present. For + example a Low Energy only device does not contain class of device + information. + + When any of the values in the EIR_Data field changes, the event + Extended Controller Information Changed will be used to inform + clients about the updated information. + + This command generates a Command Complete event on success or + a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Set Appearance Command +====================== + + Command Code: 0x0042 + Controller Index: + Command Parameters: Appearance (2 Octets) + Return Parameters: + + This command is used to set the appearance value of a controller. + + This command can be used when the controller is not + powered and all settings will be programmed once powered. + + The value of appearance will be remembered when switching + the controller off and back on again. So the appearance only + have to be set once when a new controller is found and will + stay until removed. + + This command generates a Command Complete event on success + or a Command Status event on failure. + + This command is only available for LE capable controllers. + It will return Not Supported otherwise. + + Possible errors: Not Supported + Invalid Parameters + Invalid Index + + +Get PHY Configuration Command +============================= + + Command Code: 0x0043 + Controller Index: + Command Parameters: + Return Parameters: Supported_PHYs (4 Octet) + Configurable_PHYs (4 Octets) + Selected_PHYs (4 Octet) + + The PHYs parameters are a bitmask with currently the + following available bits: + + 0 BR 1M 1-Slot + 1 BR 1M 3-Slot + 2 BR 1M 5-Slot + 3 EDR 2M 1-Slot + 4 EDR 2M 3-Slot + 5 EDR 2M 5-Slot + 6 EDR 3M 1-Slot + 7 EDR 3M 3-Slot + 8 EDR 3M 5-Slot + 9 LE 1M TX + 10 LE 1M RX + 11 LE 2M TX + 12 LE 2M RX + 13 LE Coded TX + 14 LE Coded RX + + If BR/EDR is supported, then BR 1M 1-Slot is supported by + default and can also not be deselected. If LE is supported, + then LE 1M TX and LE 1M RX are supported by default. + + Disabling BR/EDR completely or respectively LE has no impact + on the PHY configuration. It is remembered over power cycles. + + This command generates a Command Complete event on success + or a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Set PHY Configuration Command +============================= + + Command Code: 0x0044 + Controller Index: + Command Parameters: Selected_PHYs (4 Octet) + Return Parameters: + + This command is used to set the default PHY to the controller. + + This will be stored and used for all the subsequent scanning + and connection initiation. + + The list of supported PHYs can be retrieved via the + Get PHY Configuration command. Selecting unsupported or + deselecting default PHYs will result in an Invalid Parameter + error. + + This can be called at any point to change the Selected PHYs. + + Refer Get PHY Configuration command for PHYs parameter. + + This command generates a Command Complete event on success + or a Command Status event on failure. + + Possible errors: Invalid Parameters + Invalid Index + + +Command Complete Event +====================== + + Event Code: 0x0001 + Controller Index: or + Event Parameters: Command_Opcode (2 Octets) + Status (1 Octet) + Return_Parameters + + This event is an indication that a command has completed. The + fixed set of parameters includes the opcode to identify the + command that completed as well as a status value to indicate + success or failure. The rest of the parameters are command + specific and documented in the section for each command + separately. + + +Command Status Event +==================== + + Event Code: 0x0002 + Controller Index: or + Event Parameters: Command_Opcode (2 Octets) + Status (1 Octet) + + The command status event is used to indicate an early status for + a pending command. In the case that the status indicates failure + (anything else except success status) this also means that the + command has finished executing. + + +Controller Error Event +====================== + + Event Code: 0x0003 + Controller Index: + Event Parameters: Error_Code (1 Octet) + + This event maps straight to the HCI Hardware Error event and is + used to indicate something wrong with the controller hardware. + + +Index Added Event +================= + + Event Code: 0x0004 + Controller Index: + Event Parameters: + + This event indicates that a new controller has been added to the + system. It is usually followed by a Read Controller Information + command. + + Once the Read Extended Controller Index List command has been + used at least once, the Extended Index Added event will be + send instead of this one. + + +Index Removed Event +=================== + + Event Code: 0x0005 + Controller Index: + Event Parameters: + + This event indicates that a controller has been removed from the + system. + + Once the Read Extended Controller Index List command has been + used at least once, the Extended Index Removed event will be + send instead of this one. + + +New Settings Event +================== + + Event Code: 0x0006 + Controller Index: + Event Parameters: Current_Settings (4 Octets) + + This event indicates that one or more of the settings for a + controller has changed. + + +Class Of Device Changed Event +============================= + + Event Code: 0x0007 + Controller Index: + Event Parameters: Class_Of_Device (3 Octets) + + This event indicates that the Class of Device value for the + controller has changed. When the controller is powered off the + Class of Device value will always be reported as zero. + + +Local Name Changed Event +======================== + + Event Code: 0x0008 + Controller Index: + Event Parameters: Name (249 Octets) + Short_Name (11 Octets) + + This event indicates that the local name of the controller has + changed. + + +New Link Key Event +================== + + Event Code: 0x0009 + Controller Index: + Event Parameters: Store_Hint (1 Octet) + Key { + Address (6 Octets) + Address_Type (1 Octet) + Key_Type (1 Octet) + Value (16 Octets) + PIN_Length (1 Octet) + } + + This event indicates that a new link key has been generated for a + remote device. + + The Store_Hint parameter indicates whether the host is expected + to store the key persistently or not (e.g. this would not be set + if the authentication requirement was "No Bonding"). + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 Reserved (not in use) + 2 Reserved (not in use) + + Public and random LE addresses are not valid and will be rejected. + + Currently defined Key_Type values are: + + 0x00 Combination key + 0x01 Local Unit key + 0x02 Remote Unit key + 0x03 Debug Combination key + 0x04 Unauthenticated Combination key from P-192 + 0x05 Authenticated Combination key from P-192 + 0x06 Changed Combination key + 0x07 Unauthenticated Combination key from P-256 + 0x08 Authenticated Combination key from P-256 + + Receiving this event indicates that a pairing procedure has + been completed. + + +New Long Term Key Event +======================= + + Event Code: 0x000A + Controller Index: + Event Parameters: Store_Hint (1 Octet) + Key { + Address (6 Octets) + Address_Type (1 Octet) + Key_Type (1 Octet) + Master (1 Octet) + Encryption Size (1 Octet) + Enc. Diversifier (2 Octets) + Random Number (8 Octets) + Value (16 Octets) + } + + This event indicates that a new long term key has been generated + for a remote device. + + The Store_Hint parameter indicates whether the host is expected + to store the key persistently or not (e.g. this would not be set + if the authentication requirement was "No Bonding"). + + Possible values for the Address_Type parameter: + 0 Reserved (not in use) + 1 LE Public + 2 LE Random + + The provided Address and Address_Type are the identity of + a device. So either its public address or static random address. + + For unresolvable random addresses and resolvable random addresses + without identity information and identity resolving key, the + Store_Hint will be set to not store the long term key. + + Currently defined Key_Type values are: + + 0x00 Unauthenticated legacy key + 0x01 Authenticated legacy key + 0x02 Unauthenticated key from P-256 + 0x03 Authenticated key from P-256 + 0x04 Debug key from P-256 + + Receiving this event indicates that a pairing procedure has + been completed. + + +Device Connected Event +====================== + + Event Code: 0x000B + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + Flags (4 Octets) + EIR_Data_Length (2 Octets) + EIR_Data (0-65535 Octets) + + This event indicates that a successful baseband connection has + been created to the remote device. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + For devices using resolvable random addresses with a known + identity resolving key, the Address and Address_Type will + contain the identity information. + + It is possible that devices get connected via its resolvable + random address and after New Identity Resolving Key event + start using its identity. + + The following bits are defined for the Flags parameter: + 0 Reserved (not in use) + 1 Legacy Pairing + 2 Reserved (not in use) + + +Device Disconnected Event +========================= + + Event Code: 0x000C + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + Reason (1 Octet) + + This event indicates that the baseband connection was lost to a + remote device. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + For devices using resolvable random addresses with a known + identity resolving key, the Address and Address_Type will + contain the identity information. + + Possible values for the Reason parameter: + 0 Unspecified + 1 Connection timeout + 2 Connection terminated by local host + 3 Connection terminated by remote host + 4 Connection terminated due to authentication failure + + Note that the local/remote distinction just determines which side + terminated the low-level connection, regardless of the + disconnection of the higher-level profiles. + + This can sometimes be misleading and thus must be used with care. + For example, some hardware combinations would report a locally + initiated disconnection even if the user turned Bluetooth off in + the remote side. + + +Connect Failed Event +==================== + + Event Code: 0x000D + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + Status (1 Octet) + + This event indicates that a connection attempt failed to a + remote device. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + For devices using resolvable random addresses with a known + identity resolving key, the Address and Address_Type will + contain the identity information. + + +PIN Code Request Event +====================== + + Event Code: 0x000E + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + Secure (1 Octet) + + This event is used to request a PIN Code reply from user space. + The reply should either be returned using the PIN Code Reply or + the PIN Code Negative Reply command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + Secure: 0x01 secure PIN code required + 0x00 secure PIN code not required + + +User Confirmation Request Event +=============================== + + Event Code: 0x000F + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + Confirm_Hint (1 Octet) + Value (4 Octets) + + This event is used to request a user confirmation request from + user space. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + If the Confirm_Hint parameter value is 0x01 this means that + a simple "Yes/No" confirmation should be presented to the user + instead of a full numerical confirmation (in which case the + parameter value will be 0x00). + + User space should respond to this command either using the User + Confirmation Reply or the User Confirmation Negative Reply + command. + + +User Passkey Request Event +========================== + + Event Code: 0x0010 + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This event is used to request a passkey from user space. The + response to this event should either be the User Passkey Reply + command or the User Passkey Negative Reply command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + +Authentication Failed Event +=========================== + + Event Code: 0x0011 + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + Status (1 Octet) + + This event indicates that there was an authentication failure + with a remote device. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + +Device Found Event +================== + + Event Code: 0x0012 + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + RSSI (1 Octet) + Flags (4 Octets) + EIR_Data_Length (2 Octets) + EIR_Data (0-65535 Octets) + + This event indicates that a device was found during device + discovery. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + The following bits are defined for the Flags parameter: + 0 Confirm name + 1 Legacy Pairing + 2 Not Connectable + + For the RSSI field a value of 127 indicates that the RSSI is + not available. That can happen with Bluetooth 1.1 and earlier + controllers or with bad radio conditions. + + The Confirm name flag indicates that the kernel wants to know + whether user space knows the name for this device or not. If + this flag is set user space should respond to it using the + Confirm Name command. + + The Legacy Pairing flag indicates that Legacy Pairing is likely + to occur when pairing with this device. An application could use + this information to optimize the pairing process by locally + pre-generating a PIN code and thereby eliminate the risk of + local input timeout when pairing. Note that there is a risk of + false-positives for this flag so user space should be able to + handle getting something else as a PIN Request when pairing. + + The Not Connectable flag indicates that the device will not + accept any connections. This can be indicated by Low Energy + devices that are in broadcaster role. + + +Discovering Event +================= + + Event Code: 0x0013 + Controller Index: + Event Parameters: Address_Type (1 Octet) + Discovering (1 Octet) + + This event indicates that the controller has started discovering + devices. This discovering state can come and go multiple times + between a Start Discovery and a Stop Discovery commands. + + The Start Service Discovery command will also trigger this event. + + The valid values for the Discovering parameter are 0x01 + (enabled) and 0x00 (disabled). + + +Device Blocked Event +==================== + + Event Code: 0x0014 + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This event indicates that a device has been blocked using the + Block Device command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + The event will only be sent to Management sockets other than the + one through which the command was sent. + + +Device Unblocked Event +====================== + + Event Code: 0x0015 + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This event indicates that a device has been unblocked using the + Unblock Device command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + The event will only be sent to Management sockets other than the + one through which the command was sent. + + +Device Unpaired Event +===================== + + Event Code: 0x0016 + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This event indicates that a device has been unpaired (i.e. all + its keys have been removed from the kernel) using the Unpair + Device command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + For devices using resolvable random addresses with a known + identity resolving key, the event parameters will contain + the identity. After receiving this event, the device will + become essentially private again. + + The event will only be sent to Management sockets other than the + one through which the Unpair Device command was sent. + + +Passkey Notify Event +==================== + + Event Code: 0x0017 + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + Passkey (4 Octets) + Entered (1 Octet) + + This event is used to request passkey notification to the user. + Unlike the other authentication events it does not need + responding to using any Management command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + The Passkey parameter indicates the passkey to be shown to the + user whereas the Entered parameter indicates how many characters + the user has entered on the remote side. + + +New Identity Resolving Key Event +================================ + + Event Code: 0x0018 + Controller Index: + Event Parameters: Store_Hint (1 Octet) + Random_Address (6 Octets) + Key { + Address (6 Octets) + Address_Type (1 Octet) + Value (16 Octets) + } + + This event indicates that a new identity resolving key has been + generated for a remote device. + + The Store_Hint parameter indicates whether the host is expected + to store the key persistently or not. + + The Random_Address provides the resolvable random address that + was resolved into an identity. A value of 00:00:00:00:00:00 + indicates that the identity resolving key was provided for + a public address or static random address. + + Once this event has been send for a resolvable random address, + all further events mapping this device will send out using the + identity address information. + + This event also indicates that now the identity address should + be used for commands instead of the resolvable random address. + + It is possible that some devices allow discovering via its + identity address, but after pairing using resolvable private + address only. In such a case Store_Hint will be 0x00 and the + Random_Address will indicate 00:00:00:00:00:00. For these devices, + the Privacy Characteristic of the remote GATT database should + be consulted to decide if the identity resolving key must be + stored persistently or not. + + Devices using Set Privacy command with the option 0x02 would + be such type of device. + + Possible values for the Address_Type parameter: + 0 Reserved (not in use) + 1 LE Public + 2 LE Random + + The provided Address and Address_Type are the identity of + a device. So either its public address or static random address. + + +New Signature Resolving Key Event +================================= + + Event Code: 0x0019 + Controller Index: + Event Parameters: Store_Hint (1 Octet) + Key { + Address (6 Octets) + Address_Type (1 Octet) + Type (1 Octet) + Value (16 Octets) + } + + This event indicates that a new signature resolving key has been + generated for either the master or slave device. + + The Store_Hint parameter indicates whether the host is expected + to store the key persistently or not. + + The Type parameter has the following possible values: + + 0x00 Unauthenticated local CSRK + 0x01 Unauthenticated remote CSRK + 0x02 Authenticated local CSRK + 0x03 Authenticated remote CSRK + + The local keys are used for signing data to be sent to the + remote device, whereas the remote keys are used to verify + signatures received from the remote device. + + The local signature resolving key will be generated with each + pairing request. Only after receiving this event with the Type + indicating a local key is it possible to use ATT Signed Write + procedures. + + Possible values for the Address_Type parameter: + 0 Reserved (not in use) + 1 LE Public + 2 LE Random + + The provided Address and Address_Type are the identity of + a device. So either its public address or static random address. + + +Device Added Event +================== + + Event Code: 0x001a + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + Action (1 Octet) + + This event indicates that a device has been added using the + Add Device command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + The event will only be sent to management sockets other than the + one through which the command was sent. + + +Device Removed Event +==================== + + Event Code: 0x001b + Controller Index: + Event Parameters: Address (6 Octets) + Address_Type (1 Octet) + + This event indicates that a device has been removed using the + Remove Device command. + + Possible values for the Address_Type parameter: + 0 BR/EDR + 1 LE Public + 2 LE Random + + The event will only be sent to management sockets other than the + one through which the command was sent. + + +New Connection Parameter Event +============================== + + Event Code: 0x001c + Controller Index: + Event Parameters: Store_Hint (1 Octet) + Param { + Address (6 Octets) + Address_Type (1 Octet) + Min_Connection_Interval (2 Octets) + Max_Connection_Interval (2 Octets) + Connection_Latency (2 Octets) + Supervision_Timeout (2 Octets) + } + + This event indicates a new set of connection parameters from + a peripheral device. + + The Store_Hint parameter indicates whether the host is expected + to store this information persistently or not. + + Possible values for the Address_Type parameter: + 0 Reserved (not in use) + 1 LE Public + 2 LE Random + + The Min_Connection_Interval, Max_Connection_Interval, + Connection_Latency and Supervision_Timeout parameters are + encoded as described in Core 4.1 spec, Vol 2, 7.7.65.3. + + +Unconfigured Index Added Event +============================== + + Event Code: 0x001d + Controller Index: + Event Parameters: + + This event indicates that a new unconfigured controller has been + added to the system. It is usually followed by a Read Controller + Configuration Information command. + + Only when a controller requires further configuration, it will + be announced with this event. If it supports configuration, but + does not require it, then an Index Added event will be used. + + Once the Read Extended Controller Index List command has been + used at least once, the Extended Index Added event will be + send instead of this one. + + +Unconfigured Index Removed Event +================================ + + Event Code: 0x001e + Controller Index: + Event Parameters: + + This event indicates that an unconfigured controller has been + removed from the system. + + Once the Read Extended Controller Index List command has been + used at least once, the Extended Index Removed event will be + send instead of this one. + + +New Configuration Options Event +=============================== + + Event Code: 0x001f + Controller Index: + Event Parameters: Missing_Options (4 Octets) + + This event indicates that one or more of the options for the + controller configuration has changed. + + +Extended Index Added Event +========================== + + Event Code: 0x0020 + Controller Index: + Event Parameters: Controller_Type (1 Octet) + Controller_Bus (1 Octet) + + This event indicates that a new controller index has been + added to the system. + + This event will only be used after Read Extended Controller Index + List has been used at least once. If it has not been used, then + Index Added and Unconfigured Index Added are sent instead. + + +Extended Index Removed Event +============================ + + Event Code: 0x0021 + Controller Index: + Event Parameters: Controller_Type (1 Octet) + Controller_Bus (1 Octet) + + This event indicates that an existing controller index has been + removed from the system. + + This event will only be used after Read Extended Controller Index + List has been used at least once. If it has not been used, then + Index Removed and Unconfigured Index Removed are sent instead. + + +Local Out Of Band Extended Data Updated Event +============================================= + + Event Code: 0x0022 + Controller Index: + Event Parameters: Address_Type (1 Octet) + EIR_Data_Length (2 Octets) + EIR_Data (0-65535 Octets) + + This event is used when the Read Local Out Of Band Extended Data + command has been used and some other user requested a new set + of local out-of-band data. This allows for the original caller + to adjust the data. + + Possible values for the Address_Type parameter are a bit-wise or + of the following bits: + + 0 BR/EDR + 1 LE Public + 2 LE Random + + By combining these e.g. the following values are possible: + + 1 BR/EDR + 6 LE (public & random) + 7 Reserved (not in use) + + The value for EIR_Data_Length and content for EIR_Data is the + same as described in Read Local Out Of Band Extended Data command. + + When LE Privacy is used and LE Secure Connections out-of-band + data has been requested, then this event will be emitted every + time the Resolvable Private Address (RPA) gets changed. The new + RPA will be included in the EIR_Data. + + The event will only be sent to management sockets other than the + one through which the command was sent. It will additionally also + only be sent to sockets that have used the command at least once. + + +Advertising Added Event +======================= + + Event Code: 0x0023 + Controller Index: + Event Parameters: Instance (1 Octet) + + This event indicates that an advertising instance has been added + using the Add Advertising command. + + The event will only be sent to management sockets other than the + one through which the command was sent. + + +Advertising Removed Event +========================= + + Event Code: 0x0024 + Controller Index: + Event Parameters: Instance (1 Octet) + + This event indicates that an advertising instance has been removed + using the Remove Advertising command. + + The event will only be sent to management sockets other than the + one through which the command was sent. + + +Extended Controller Information Changed Event +============================================= + + Event Code: 0x0025 + Controller Index: + Event Parameters: EIR_Data_Length (2 Octets) + EIR_Data (0-65535 Octets) + + This event indicates that controller information has been updated + and new values are used. This includes the local name, class of + device, device id and LE address information. + + This event will only be used after Read Extended Controller + Information command has been used at least once. If it has not + been used the legacy events are used. + + The event will only be sent to management sockets other than the + one through which the change was triggered. + +PHY Configuration Changed Event +=============================== + + Event Code: 0x0026 + Controller Index: + Event Parameters: Selected_PHYs (4 Octets) + + This event indicates that default PHYs have been updated. + + This event will only be used after Set PHY Configuration + command has been used at least once. + + The event will only be sent to management sockets other than the + one through which the change was triggered. + + Refer Get PHY Configuration command for PHYs parameter. diff --git a/doc/network-api.txt b/doc/network-api.txt new file mode 100644 index 0000000..109da28 --- /dev/null +++ b/doc/network-api.txt @@ -0,0 +1,76 @@ +BlueZ D-Bus Network API description +*********************************** + + +Network hierarchy +================= + +Service org.bluez +Interface org.bluez.Network1 +Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX + +Methods string Connect(string uuid) + + Connect to the network device and return the network + interface name. Examples of the interface name are + bnep0, bnep1 etc. + + uuid can be either one of "gn", "panu" or "nap" (case + insensitive) or a traditional string representation of + UUID or a hexadecimal number. + + The connection will be closed and network device + released either upon calling Disconnect() or when + the client disappears from the message bus. + + Possible errors: org.bluez.Error.AlreadyConnected + org.bluez.Error.ConnectionAttemptFailed + + void Disconnect() + + Disconnect from the network device. + + To abort a connection attempt in case of errors or + timeouts in the client it is fine to call this method. + + Possible errors: org.bluez.Error.Failed + +Properties boolean Connected [readonly] + + Indicates if the device is connected. + + string Interface [readonly] + + Indicates the network interface name when available. + + string UUID [readonly] + + Indicates the connection role when available. + + +Network server hierarchy +======================== + +Service org.bluez +Interface org.bluez.NetworkServer1 +Object path /org/bluez/{hci0,hci1,...} + +Methods void Register(string uuid, string bridge) + + Register server for the provided UUID. Every new + connection to this server will be added the bridge + interface. + + Valid UUIDs are "gn", "panu" or "nap". + + Initially no network server SDP is provided. Only + after this method a SDP record will be available + and the BNEP server will be ready for incoming + connections. + + void Unregister(string uuid) + + Unregister the server for provided UUID. + + All servers will be automatically unregistered when + the calling application terminates. diff --git a/doc/obex-agent-api.txt b/doc/obex-agent-api.txt new file mode 100644 index 0000000..3923da6 --- /dev/null +++ b/doc/obex-agent-api.txt @@ -0,0 +1,61 @@ +OBEX D-Bus Agent API description +******************************** + + +Agent Manager hierarchy +======================= + +Service org.bluez.obex +Interface org.bluez.obex.AgentManager1 +Object path /org/bluez/obex + +Methods void RegisterAgent(object agent) + + Register an agent to request authorization of + the user to accept/reject objects. Object push + service needs to authorize each received object. + + Possible errors: org.bluez.obex.Error.AlreadyExists + + void UnregisterAgent(object agent) + + This unregisters the agent that has been previously + registered. The object path parameter must match the + same value that has been used on registration. + + Possible errors: org.bluez.obex.Error.DoesNotExist + + +Agent hierarchy +=============== + +Service unique name +Interface org.bluez.obex.Agent1 +Object path freely definable + +Methods void Release() + + This method gets called when the service daemon + unregisters the agent. An agent can use it to do + cleanup tasks. There is no need to unregister the + agent, because when this method gets called it has + already been unregistered. + + string AuthorizePush(object transfer) + + This method gets called when the service daemon + needs to accept/reject a Bluetooth object push request. + + Returns the full path (including the filename) where + the object shall be stored. The tranfer object will + contain a Filename property that contains the default + location and name that can be returned. + + Possible errors: org.bluez.obex.Error.Rejected + org.bluez.obex.Error.Canceled + + void Cancel() + + This method gets called to indicate that the agent + request failed before a reply was returned. It cancels + the previous request. diff --git a/doc/obex-api.txt b/doc/obex-api.txt new file mode 100644 index 0000000..f39355a --- /dev/null +++ b/doc/obex-api.txt @@ -0,0 +1,894 @@ +OBEX D-Bus API description +************************** + + +Client hierarchy +================ + +Service org.bluez.obex +Interface org.bluez.obex.Client1 +Object path /org/bluez/obex + +Methods object CreateSession(string destination, dict args) + + Create a new OBEX session for the given remote address. + + The last parameter is a dictionary to hold optional or + type-specific parameters. Typical parameters that can + be set in this dictionary include the following: + + string "Target" : type of session to be created + string "Source" : local address to be used + byte "Channel" + + The currently supported targets are the following: + + "ftp" + "map" + "opp" + "pbap" + "sync" + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + void RemoveSession(object session) + + Unregister session and abort pending transfers. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.NotAuthorized + +Session hierarchy +================= + +Service org.bluez.obex +Interface org.bluez.obex.Session1 +Object path /org/bluez/obex/server/session{0, 1, 2, ...} or + /org/bluez/obex/client/session{0, 1, 2, ...} + +Methods string GetCapabilities() + + Get remote device capabilities. + + Possible errors: org.bluez.obex.Error.NotSupported + org.bluez.obex.Error.Failed + +Properties string Source [readonly] + + Bluetooth adapter address + + string Destination [readonly] + + Bluetooth device address + + byte Channel [readonly] + + Bluetooth channel + + string Target [readonly] + + Target UUID + + string Root [readonly] + + Root path + + +Transfer hierarchy +================== + +Service org.bluez.obex +Interface org.bluez.obex.Transfer1 +Object path [Session object path]/transfer{0, 1, 2, ...} + +Methods void Cancel() + + Stops the current transference. + + Possible errors: org.bluez.obex.Error.NotAuthorized + org.bluez.obex.Error.InProgress + org.bluez.obex.Error.Failed + + void Suspend() + + Suspend transference. + + Possible errors: org.bluez.obex.Error.NotAuthorized + org.bluez.obex.Error.NotInProgress + + Note that it is not possible to suspend transfers + which are queued which is why NotInProgress is listed + as possible error. + + void Resume() + + Resume transference. + + Possible errors: org.bluez.obex.Error.NotAuthorized + org.bluez.obex.Error.NotInProgress + + Note that it is not possible to resume transfers + which are queued which is why NotInProgress is listed + as possible error. + +Properties string Status [readonly] + + Inform the current status of the transfer. + + Possible values: "queued", "active", "suspended", + "complete" or "error" + + object Session [readonly] + + The object path of the session the transfer belongs + to. + + string Name [readonly] + + Name of the transferred object. Either Name or Type + or both will be present. + + string Type [readonly] + + Type of the transferred object. Either Name or Type + or both will be present. + + uint64 Time [readonly, optional] + + Time of the transferred object if this is + provided by the remote party. + + uint64 Size [readonly, optional] + + Size of the transferred object. If the size is + unknown, then this property will not be present. + + uint64 Transferred [readonly, optional] + + Number of bytes transferred. For queued transfers, this + value will not be present. + + string Filename [readonly, optional] + + Complete name of the file being received or sent. + + For incoming object push transaction, this will be + the proposed default location and name. It can be + overwritten by the AuthorizePush agent callback + and will be then updated accordingly. + + +Object Push hierarchy +===================== + +Service org.bluez.obex +Interface org.bluez.obex.ObjectPush1 +Object path [Session object path] + +Methods object, dict SendFile(string sourcefile) + + Send one local file to the remote device. + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetProperties. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + object, dict PullBusinessCard(string targetfile) + + Request the business card from a remote device and + store it in the local file. + + If an empty target file is given, a name will be + automatically calculated for the temporary file. + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetProperties. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + object, dict ExchangeBusinessCards(string clientfile, + string targetfile) + + Push the client's business card to the remote device + and then retrieve the remote business card and store + it in a local file. + + If an empty target file is given, a name will be + automatically calculated for the temporary file. + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetProperties. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + +File Transfer hierarchy +======================= + +Service org.bluez.obex +Interface org.bluez.obex.FileTransfer +Object path [Session object path] + +Methods void ChangeFolder(string folder) + + Change the current folder of the remote device. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + void CreateFolder(string folder) + + Create a new folder in the remote device. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + array{dict} ListFolder() + + Returns a dictionary containing information about + the current folder content. + + The following keys are defined: + + string Name : Object name in UTF-8 format + string Type : Either "folder" or "file" + uint64 Size : Object size or number of items in + folder + string Permission : Group, owner and other + permission + uint64 Modified : Last change + uint64 Accessed : Last access + uint64 Created : Creation date + + Possible errors: org.bluez.obex.Error.Failed + + object, dict GetFile(string targetfile, string sourcefile) + + Copy the source file (from remote device) to the + target file (on local filesystem). + + If an empty target file is given, a name will be + automatically calculated for the temporary file. + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetProperties. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + object, dict PutFile(string sourcefile, string targetfile) + + Copy the source file (from local filesystem) to the + target file (on remote device). + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetProperties. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + void CopyFile(string sourcefile, string targetfile) + + Copy a file within the remote device from source file + to target file. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + void MoveFile(string sourcefile, string targetfile) + + Move a file within the remote device from source file + to the target file. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + void Delete(string file) + + Deletes the specified file/folder. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + +Phonebook Access hierarchy +========================== + +Service org.bluez.obex +Interface org.bluez.obex.PhonebookAccess1 +Object path [Session object path] + +Methods void Select(string location, string phonebook) + + Select the phonebook object for other operations. Should + be call before all the other operations. + + location : Where the phonebook is stored, possible + inputs : + "int" ( "internal" which is default ) + "sim" ( "sim1" ) + "sim2" + ... + + phonebook : Possible inputs : + "pb" : phonebook for the saved contacts + "ich": incoming call history + "och": outgoing call history + "mch": missing call history + "cch": combination of ich och mch + "spd": speed dials entry ( only for "internal" ) + "fav": favorites entry ( only for "internal" ) + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + object, dict PullAll(string targetfile, dict filters) + + Return the entire phonebook object from the PSE server + in plain string with vcard format, and store it in + a local file. + + If an empty target file is given, a name will be + automatically calculated for the temporary file. + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetProperties. + + Possible filters: Format, Order, Offset, MaxCount and + Fields + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Forbidden + + array{string vcard, string name} List(dict filters) + + Return an array of vcard-listing data where every entry + consists of a pair of strings containing the vcard + handle and the contact name. For example: + "1.vcf" : "John" + + Possible filters: Order, Offset and MaxCount + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Forbidden + + object, dict + Pull(string vcard, string targetfile, dict filters) + + Given a vcard handle, retrieve the vcard in the current + phonebook object and store it in a local file. + + If an empty target file is given, a name will be + automatically calculated for the temporary file. + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetProperties. + + Possbile filters: Format and Fields + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Forbidden + org.bluez.obex.Error.Failed + + array{string vcard, string name} + Search(string field, string value, dict filters) + + Search for entries matching the given condition and + return an array of vcard-listing data where every entry + consists of a pair of strings containing the vcard + handle and the contact name. + + vcard : name paired string match the search condition. + + field : the field in the vcard to search with + { "name" (default) | "number" | "sound" } + value : the string value to search for + + + Possible filters: Order, Offset and MaxCount + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Forbidden + org.bluez.obex.Error.Failed + + uint16 GetSize() + + Return the number of entries in the selected phonebook + object that are actually used (i.e. indexes that + correspond to non-NULL entries). + + Possible errors: org.bluez.obex.Error.Forbidden + org.bluez.obex.Error.Failed + + void UpdateVersion() + + Attempt to update PrimaryCounter and SecondaryCounter. + + Possible errors: org.bluez.obex.Error.NotSupported + org.bluez.obex.Error.Forbidden + org.bluez.obex.Error.Failed + + array{string} ListFilterFields() + + Return All Available fields that can be used in Fields + filter. + + Possible errors: None + +Filter: string Format: + + Items vcard format + + Possible values: "vcard21" (default) or "vcard30" + + string Order: + + Items order + + Possible values: "indexed" (default), "alphanumeric" or + "phonetic" + + uint16 Offset: + + Offset of the first item, default is 0 + + uint16 MaxCount: + + Maximum number of items, default is unlimited (65535) + + array{string} Fields: + + Item vcard fields, default is all values. + + Possible values can be query with ListFilterFields. + + array{string} FilterAll: + + Filter items by fields using AND logic, cannot be used + together with FilterAny. + + Possible values can be query with ListFilterFields. + + array{string} FilterAny: + + Filter items by fields using OR logic, cannot be used + together with FilterAll. + + Possible values can be query with ListFilterFields. + + bool ResetNewMissedCalls + + Reset new the missed calls items, shall only be used + for folders mch and cch. + +Properties string Folder [readonly] + + Current folder. + + string DatabaseIdentifier [readonly, optional] + + 128 bits persistent database identifier. + + Possible values: 32-character hexadecimal such + as A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6 + + string PrimaryCounter [readonly, optional] + + 128 bits primary version counter. + + Possible values: 32-character hexadecimal such + as A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6 + + string SecondaryCounter [readonly, optional] + + 128 bits secondary version counter. + + Possible values: 32-character hexadecimal such + as A1A2A3A4B1B2C1C2D1D2E1E2E3E4E5E6 + + bool FixedImageSize [readonly, optional] + + Indicate support for fixed image size. + + Possible values: True if image is JPEG 300x300 pixels + otherwise False. + +Synchronization hierarchy +========================= + +Service org.bluez.obex +Interface org.bluez.obex.Synchronization1 +Object path [Session object path] + +Methods void SetLocation(string location) + + Set the phonebook object store location for other + operations. Should be called before all the other + operations. + + location: Where the phonebook is stored, possible + values: + "int" ( "internal" which is default ) + "sim1" + "sim2" + ...... + + Possible errors: org.bluez.obex.Error.InvalidArguments + + object, dict GetPhonebook(string targetfile) + + Retrieve an entire Phonebook Object store from remote + device, and stores it in a local file. + + If an empty target file is given, a name will be + automatically calculated for the temporary file. + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetProperties. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + object, dict PutPhonebook(string sourcefile) + + Send an entire Phonebook Object store to remote device. + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetProperties. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + +Message Access hierarchy +========================= + +Service org.bluez.obex +Interface org.bluez.obex.MessageAccess1 +Object path [Session object path] + +Methods void SetFolder(string name) + + Set working directory for current session, *name* may + be the directory name or '..[/dir]'. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + array{dict} ListFolders(dict filter) + + Returns a dictionary containing information about + the current folder content. + + The following keys are defined: + + string Name : Folder name + + Possible filters: Offset and MaxCount + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + array{string} ListFilterFields() + + Return all available fields that can be used in Fields + filter. + + Possible errors: None + + array{object, dict} ListMessages(string folder, dict filter) + + Returns an array containing the messages found in the + given subfolder of the current folder, or in the + current folder if folder is empty. + + Possible Filters: Offset, MaxCount, SubjectLength, Fields, + Type, PeriodStart, PeriodEnd, Status, Recipient, Sender, + Priority + + Each message is represented by an object path followed + by a dictionary of the properties. + + Properties: + + string Subject: + + Message subject + + string Timestamp: + + Message timestamp + + string Sender: + + Message sender name + + string SenderAddress: + + Message sender address + + string ReplyTo: + + Message Reply-To address + + string Recipient: + + Message recipient name + + string RecipientAddress: + + Message recipient address + + string Type: + + Message type + + Possible values: "email", "sms-gsm", + "sms-cdma" and "mms" + + uint64 Size: + + Message size in bytes + + boolean Text: + + Message text flag + + Specifies whether message has textual + content or is binary only + + string Status: + + Message status + + Possible values for received messages: + "complete", "fractioned", "notification" + + Possible values for sent messages: + "delivery-success", "sending-success", + "delivery-failure", "sending-failure" + + uint64 AttachmentSize: + + Message overall attachment size in bytes + + boolean Priority: + + Message priority flag + + boolean Read: + + Message read flag + + boolean Sent: + + Message sent flag + + boolean Protected: + + Message protected flag + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + void UpdateInbox(void) + + Request remote to update its inbox. + + Possible errors: org.bluez.obex.Error.Failed + + object, dict + PushMessage(string sourcefile, string folder, dict args) + + Transfer a message (in bMessage format) to the + remote device. + + The message is transferred either to the given + subfolder of the current folder, or to the current + folder if folder is empty. + + Possible args: Transparent, Retry, Charset + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetAll. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + + +Filter: uint16 Offset: + + Offset of the first item, default is 0 + + uint16 MaxCount: + + Maximum number of items, default is 1024 + + byte SubjectLength: + + Maximum length of the Subject property in the + message, default is 256 + + array{string} Fields: + + Message fields, default is all values. + + Possible values can be query with ListFilterFields. + + array{string} Types: + + Filter messages by type. + + Possible values: "sms", "email", "mms". + + string PeriodBegin: + + Filter messages by starting period. + + Possible values: Date in "YYYYMMDDTHHMMSS" format. + + string PeriodEnd: + + Filter messages by ending period. + + Possible values: Date in "YYYYMMDDTHHMMSS" format. + + boolean Read: + + Filter messages by read flag. + + Possible values: True for read or False for unread + + string Recipient: + + Filter messages by recipient address. + + string Sender: + + Filter messages by sender address. + + boolean Priority: + + Filter messages by priority flag. + + Possible values: True for high priority or False for + non-high priority + +Message hierarchy +================= + +Service org.bluez.obex +Interface org.bluez.obex.Message1 +Object path [Session object path]/{message0,...} + +Methods object, dict Get(string targetfile, boolean attachment) + + Download message and store it in the target file. + + If an empty target file is given, a temporary file + will be automatically generated. + + The returned path represents the newly created transfer, + which should be used to find out if the content has been + successfully transferred or if the operation fails. + + The properties of this transfer are also returned along + with the object path, to avoid a call to GetProperties. + + Possible errors: org.bluez.obex.Error.InvalidArguments + org.bluez.obex.Error.Failed + +Properties string Folder [readonly] + + Folder which the message belongs to + + string Subject [readonly] + + Message subject + + string Timestamp [readonly] + + Message timestamp + + string Sender [readonly] + + Message sender name + + string SenderAddress [readonly] + + Message sender address + + string ReplyTo [readonly] + + Message Reply-To address + + string Recipient [readonly] + + Message recipient name + + string RecipientAddress [readonly] + + Message recipient address + + string Type [readonly] + + Message type + + Possible values: "email", "sms-gsm", + "sms-cdma" and "mms" + + uint64 Size [readonly] + + Message size in bytes + + string Status [readonly] + + Message reception status + + Possible values: "complete", + "fractioned" and "notification" + + boolean Priority [readonly] + + Message priority flag + + boolean Read [read/write] + + Message read flag + + boolean Deleted [writeonly] + + Message deleted flag + + boolean Sent [readonly] + + Message sent flag + + boolean Protected [readonly] + + Message protected flag diff --git a/doc/pics-opp.txt b/doc/pics-opp.txt new file mode 100644 index 0000000..fb4746d --- /dev/null +++ b/doc/pics-opp.txt @@ -0,0 +1,187 @@ +OPP PICS for the PTS tool. + +PTS version: 6.2.0 + +* - different than PTS defaults +# - not yet implemented/supported + +M - mandatory +O - optional + + Roles +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_1_1 True (*) Role: Object Push Client (C.1) +TSPC_OPP_1_2 True (*) Role: Object Push Server (C.1) +------------------------------------------------------------------------------- +C.1: Mandatory to support at least one of the defined roles. +------------------------------------------------------------------------------- + + + Client Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_1b_1 False Client supports OPP version 1.1. (C.1) +TSPC_OPP_1b_2 True (*) Client supports OPP version 1.2. (C.1) +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the profile versions. +------------------------------------------------------------------------------- + + + Client Application Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_2_1 True Client: Perform Service Discovery request (M) +TSPC_OPP_2_2 True Client: Authentication/PIN exchange supported. + (M) +TSPC_OPP_2_2a True (*) Client: Require Authentication/PIN by default. + (O) +TSPC_OPP_2_3 True Client: Object Push (M) +TSPC_OPP_2_4 True (*) Client: vCard 2.1 (C.3) +TSPC_OPP_2_5 False Client: vCalender 1.0 (O) +TSPC_OPP_2_6 False Client: vMsg as defined in IrMC 1.1 (O) +TSPC_OPP_2_7 False Client: vNote as defined in IrMC 1.1 (O) +TSPC_OPP_2_8 True (*) Client: Support content formats other than those + declared in TSPC_OPP_2_4 through + TSPC_OPP_2_7. (O) +TSPC_OPP_2_8a False Client: Support specific set of other content + formats. (C.4) +TSPC_OPP_2_8b True (*) Client: Support all content formats. (C.4) +TSPC_OPP_2_9 True (*) Client: Push multiple vCard objects. (O) +TSPC_OPP_2_9a True (*) Client: Push multiple vCard objects using + different PUT operations. (C.5) +TSPC_OPP_2_9b False Client: Push multiple vCard objects using the + same PUT operation. (C.5) +TSPC_OPP_2_10 True (*) Client: Push multiple vCalender objects. (O) +TSPC_OPP_2_10a True Client: Push multiple vCalendar objects using + different PUT operations. (C.6) +TSPC_OPP_2_10b False Client: Push multiple vCalendar objects using + the same PUT operation. (C.6) +TSPC_OPP_2_11 True (*) Client: Push multiple vMsg objects. (O) +TSPC_OPP_2_11a True (*) Client: Push multiple vMsg objects using + different PUT operations. (C.7) +TSPC_OPP_2_11b False Client: Push multiple vMsg objects using the + same PUT operation. (C.7) +TSPC_OPP_2_12 True (*) Client: Push multiple vNote objects. (O) +TSPC_OPP_2_12a True (*) Client: Push multiple vNote objects using + different PUT operations. (C.8) +TSPC_OPP_2_12b False Client: Push multiple vNote objects using the + same PUT operation. (C.8) +TSPC_OPP_2_13 True (*) Client: Pull business card (O) +TSPC_OPP_2_14 True (*) Client: vCard 2.1 (C.1) +TSPC_OPP_2_15 True (*) Client: Exchange business card (O) +TSPC_OPP_2_16 False Client: vCard 2.1 (C.2) +TSPC_OPP_2_17 True (*) GOEP v2 (C.9) +TSPC_OPP_2_18 True (*) GOEP v2 Backward Compatibility (C.9) +TSPC_OPP_2_19 True (*) OBEX over L2CAP (C.9) +TSPC_OPP_2_20 False OBEX Reliable Session (C.10) +TSPC_OPP_2_21 False OBEX SRM (C.10) +TSPC_OPP_2_22 False Send OBEX SRMP header (C.10) +TSPC_OPP_2_23 False Receive OBEX SRMP header (C.11) +------------------------------------------------------------------------------- +C.1: Mandatory to Support IF (TSPC_OPP_2_13) Business Card Pull is supported. +C.2: Mandatory to Support IF (TSPC_OPP_2_15) Business Card Exchange is + supported. +C.3: vCard 2.1 support is required for devices containing phonebook + applications. vCard 2.1 support optional for other devices. +C.4: Mandatory to support one of TSPC_OPP_2_8a or TSPC_OPP_2_8b if TSPC_OPP_2_8 + is supported. Otherwise, both items are excluded. +C.5: Mandatory to support at least one of TSPC_OPP_2_9a and TSPC_OPP_2_9b if + TSPC_OPP_2_9 is supported. Otherwise, both items are excluded. +C.6: Mandatory to support at least one of TSPC_OPP_2_10a and TSPC_OPP_2_10b if + TSPC_OPP_2_10 is supported. Otherwise, both items are excluded. +C.7: Mandatory to support at least one of TSPC_OPP_2_11a and TSPC_OPP_2_11b if + TSPC_OPP_2_11 is supported. Otherwise, both items are excluded. +C.8: Mandatory to support at least one of TSPC_OPP_2_12a and TSPC_OPP_2_12b if + TSPC_OPP_2_12 is supported. Otherwise, both items are excluded. +C.9: Mandatory if TSPC_OPP_1b_2 supported. +C.10: Optional to support if TSPC_OPP_1b_2 supported else excluded. +C.11: Mandatory if TSPC_OPP_17 and TSPC_OPP_21 supported else excluded. +------------------------------------------------------------------------------- + + + Server Profile Version +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_2b_1 False Server supports OPP version 1.1. +TSPC_OPP_2b_2 True (*) Server supports OPP version 1.2. +------------------------------------------------------------------------------- +C.1: It is mandatory to support at least one of the profile versions. +------------------------------------------------------------------------------- + + + Server Application Features +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_3_1 True Server: Provide information on supported + contents type on service discovery + request. (M) +TSPC_OPP_3_2 True Server: Authentication/PIN exchange supported. + (M) +TSPC_OPP_3_3 True Server: Object Push (M) +TSPC_OPP_3_3a True (*) Server: Receive multiple objects in the same + PUT operation. (O) +TSPC_OPP_3_4 True (*) Server: vCard 2.1 (C.3) +TSPC_OPP_3_5 False Server: vCalender 1.0 format (O) +TSPC_OPP_3_6 False Server: vMsg as defined in IrMC 1.1 (O) +TSPC_OPP_3_7 False Server: vNote as defined in IrMC 1.1 (O) +TSPC_OPP_3_8 True (*) Server: Support content formats other than those + declared in TSPC_OPP_3_4 through + TSPC_OPP_3_7. (O) +TSPC_OPP_3_8a False Server: Support specific set of other content + formats. (C.4) +TSPC_OPP_3_8b True (*) Server: Support all content formats. (C.4) +TSPC_OPP_3_9 True (*) Server: Object Push vCard reject. (O) +TSPC_OPP_3_10 True (*) Server: Object Push vCal reject. (O) +TSPC_OPP_3_11 True (*) Server: Object Push vMsg reject. (O) +TSPC_OPP_3_12 True (*) Server: Object Push vNote reject. (O) +TSPC_OPP_3_13 True (*) Server: Business card pull (O.1) +TSPC_OPP_3_14 True (*) Server: vCard 2.1 (C.1) +TSPC_OPP_3_15 True (*) Server: Business card pull reject. (O) +TSPC_OPP_3_16 True (*) Server: Business card exchange (O.2) +TSPC_OPP_3_17 True (*) Server: vCard 2.1 (C.2) +TSPC_OPP_3_18 True (*) Server: Business card exchange reject. (O) +TSPC_OPP_3_19 True (*) GOEP v2 (C.5) +TSPC_OPP_3_20 True (*) GOEP v2 Backward Compatibility (C.5) +TSPC_OPP_3_21 True (*) OBEX over L2CAP (C.5) +TSPC_OPP_3_22 False OBEX Reliable Session (C.16) +TSPC_OPP_3_23 False OBEX SRM (C.6) +TSPC_OPP_3_24 False Send OBEX SRMP header (C.6) +TSPC_OPP_3_25 False Receive OBEX SRMP header (C.7) +------------------------------------------------------------------------------- +O.1: IF NOT Supported, an error message must be sent on request for Business + Card Pull. +O.2: IF NOT Supported, an error message must be sent on request for Business + Card Exchange. +C.1: Mandatory to Support IF (TSPC_OPP_3_13) Business Card Pull is supported. +C.2: Mandatory to Support IF (TSPC_OPP_3_16) Business Card Exchange is + supported. +C.3: vCard 2.1 support is required for devices containing phonebook + applications. vCard 2.1 support optional for other devices. +C.4: Mandatory to support one of TSPC_OPP_3_8a or TSPC_OPP_3_8b if TSPC_OPP_3_8 + is supported. Otherwise, both items are excluded. +C.5: Mandatory if TSPC_OPP_2b_2 supported. +C.6: Optional to support if TSPC_OPP_2b_2 supported, else excluded. +C.7: Mandatory if TSPC_OPP_3_19 and TSPC_OPP_3_23 supported else excluded. +------------------------------------------------------------------------------- + + + Additional OPP Capabilities +------------------------------------------------------------------------------- +Parameter Name Selected Description +------------------------------------------------------------------------------- +TSPC_OPP_4_1 False Abort-Push Operation (O) +TSPC_OPP_4_2 False Intentionally Left Blank (N/A) +TSPC_OPP_4_3 True (*) Multiple vCards transferred as a single vObject + (C.1) +TSPC_OPP_4_4 True (*) Multiple vCards transfer (C.1) +TSPC_OPP_4_5 True (*) vCards with multiple Phone Number Fields (C.1) +TSPC_OPP_4_6 True (*) Push vCal to Different Time Zone Server (C.1) +------------------------------------------------------------------------------- +C.1: Optional if TSPC_OPP_1_2 is supported, otherwise excluded. +------------------------------------------------------------------------------- diff --git a/doc/pixit-opp.txt b/doc/pixit-opp.txt new file mode 100644 index 0000000..b7461d6 --- /dev/null +++ b/doc/pixit-opp.txt @@ -0,0 +1,27 @@ +OPP PIXIT for the PTS tool. + +PTS version: 6.2.0 + +* - different than PTS defaults +& - should be set to IUT Bluetooth address + + Required PIXIT settings +------------------------------------------------------------------------------- +Parameter Name Value +------------------------------------------------------------------------------- +TSPX_supported_extension bmp +TSPX_unsupported_extension pts +TSPX_client_class_of_device 100104 +TSPX_server_class_of_device 100104 +TSPX_auth_password 0000 +TSPX_auth_user_id PTS +TSPX_l2cap_psm 1003 +TSPX_rfcomm_channel 8 +TSPX_no_confirmations FALSE +TSPX_bd_addr_iut 112233445566 (*&) +TSPX_delete_link_key FALSE +TSPX_pin_code 0000 +TSPX_security_enabled FALSE +TSPX_time_guard 300000 +TSPX_use_implicit_send TRUE +------------------------------------------------------------------------------- diff --git a/doc/profile-api.txt b/doc/profile-api.txt new file mode 100644 index 0000000..ec18034 --- /dev/null +++ b/doc/profile-api.txt @@ -0,0 +1,147 @@ +BlueZ D-Bus Profile API description +*********************************** + + +Profile Manager hierarchy +========================= + +Service org.bluez +Interface org.bluez.ProfileManager1 +Object path /org/bluez + + void RegisterProfile(object profile, string uuid, dict options) + + This registers a profile implementation. + + If an application disconnects from the bus all + its registered profiles will be removed. + + HFP HS UUID: 0000111e-0000-1000-8000-00805f9b34fb + + Default RFCOMM channel is 6. And this requires + authentication. + + Available options: + + string Name + + Human readable name for the profile + + string Service + + The primary service class UUID + (if different from the actual + profile UUID) + + string Role + + For asymmetric profiles that do not + have UUIDs available to uniquely + identify each side this + parameter allows specifying the + precise local role. + + Possible values: "client", "server" + + uint16 Channel + + RFCOMM channel number that is used + for client and server UUIDs. + + If applicable it will be used in the + SDP record as well. + + uint16 PSM + + PSM number that is used for client + and server UUIDs. + + If applicable it will be used in the + SDP record as well. + + boolean RequireAuthentication + + Pairing is required before connections + will be established. No devices will + be connected if not paired. + + boolean RequireAuthorization + + Request authorization before any + connection will be established. + + boolean AutoConnect + + In case of a client UUID this will + force connection of the RFCOMM or + L2CAP channels when a remote device + is connected. + + string ServiceRecord + + Provide a manual SDP record. + + uint16 Version + + Profile version (for SDP record) + + uint16 Features + + Profile features (for SDP record) + + Possible errors: org.bluez.Error.InvalidArguments + org.bluez.Error.AlreadyExists + + void UnregisterProfile(object profile) + + This unregisters the profile that has been previously + registered. The object path parameter must match the + same value that has been used on registration. + + Possible errors: org.bluez.Error.DoesNotExist + + +Profile hierarchy +================= + +Service unique name +Interface org.bluez.Profile1 +Object path freely definable + +Methods void Release() [noreply] + + This method gets called when the service daemon + unregisters the profile. A profile can use it to do + cleanup tasks. There is no need to unregister the + profile, because when this method gets called it has + already been unregistered. + + void NewConnection(object device, fd, dict fd_properties) + + This method gets called when a new service level + connection has been made and authorized. + + Common fd_properties: + + uint16 Version Profile version (optional) + uint16 Features Profile features (optional) + + Possible errors: org.bluez.Error.Rejected + org.bluez.Error.Canceled + + void RequestDisconnection(object device) + + This method gets called when a profile gets + disconnected. + + The file descriptor is no longer owned by the service + daemon and the profile implementation needs to take + care of cleaning up all connections. + + If multiple file descriptors are indicated via + NewConnection, it is expected that all of them + are disconnected before returning from this + method call. + + Possible errors: org.bluez.Error.Rejected + org.bluez.Error.Canceled diff --git a/doc/pts-opp.txt b/doc/pts-opp.txt new file mode 100644 index 0000000..832028c --- /dev/null +++ b/doc/pts-opp.txt @@ -0,0 +1,119 @@ +PTS test results for OPP + +PTS version: 6.2.0 +Tested: 26-Aug-2015 + +Results: +PASS test passed +FAIL test failed +INC test is inconclusive +N/A test is disabled due to PICS setup +NONE test result is none + +------------------------------------------------------------------------------- +Test Name Result Notes +------------------------------------------------------------------------------- +TC_CLIENT_BC_BV_02_I PASS +TC_CLIENT_BC_BV_04_I PASS +TC_CLIENT_BCE_BV_01_I PASS +TC_CLIENT_BCE_BV_03_I N/A +TC_CLIENT_BCE_BV_04_I PASS +TC_CLIENT_BCE_BV_05_I PASS +TC_CLIENT_BCE_BV_06_I PASS +TC_CLIENT_BCE_BV_07_I PASS +TC_CLIENT_BCP_BV_01_I PASS +TC_CLIENT_BCP_BV_02_I PASS +TC_CLIENT_BCP_BV_03_I N/A +TC_CLIENT_BCP_BV_04_I PASS +TC_CLIENT_BCP_BV_05_I PASS +TC_CLIENT_CON_BV_01_C PASS +TC_CLIENT_OPH_BI_01_C PASS +TC_CLIENT_OPH_BV_01_I PASS +TC_CLIENT_OPH_BV_02_I N/A +TC_CLIENT_OPH_BV_03_I PASS +TC_CLIENT_OPH_BV_04_I PASS +TC_CLIENT_OPH_BV_05_I PASS +TC_CLIENT_OPH_BV_07_I N/A +TC_CLIENT_OPH_BV_08_I PASS +TC_CLIENT_OPH_BV_09_I N/A +TC_CLIENT_OPH_BV_10_I N/A +TC_CLIENT_OPH_BV_11_I N/A +TC_CLIENT_OPH_BV_12_I PASS +TC_CLIENT_OPH_BV_13_I N/A +TC_CLIENT_OPH_BV_14_I N/A +TC_CLIENT_OPH_BV_15_I N/A +TC_CLIENT_OPH_BV_16_I PASS +TC_CLIENT_OPH_BV_17_I N/A +TC_CLIENT_OPH_BV_18_I N/A +TC_CLIENT_OPH_BV_19_I PASS Send file other than vCard +TC_CLIENT_OPH_BV_20_I PASS +TC_CLIENT_OPH_BV_22_I PASS Send file greater than 2 MB +TC_CLIENT_OPH_BV_23_I N/A +TC_CLIENT_OPH_BV_24_I N/A +TC_CLIENT_OPH_BV_25_I N/A +TC_CLIENT_OPH_BV_26_I N/A +TC_CLIENT_SRM_BV_01_C N/A +TC_CLIENT_SRM_BV_03_C N/A +TC_CLIENT_SRM_BV_05_C N/A +TC_CLIENT_SRM_BV_07_C N/A +TC_CLIENT_SRMP_BI_01_C N/A +TC_CLIENT_SRMP_BV_01_C N/A +TC_CLIENT_SRMP_BV_04_C N/A +TC_CLIENT_SRMP_BV_05_C N/A +TC_CLIENT_SRMP_BV_06_C N/A +TC_SERVER_BC_BV_01_I PASS +TC_SERVER_BC_BV_03_I PASS +TC_SERVER_BCE_BV_01_I PASS +TC_SERVER_BCE_BV_03_I PASS +TC_SERVER_BCE_BV_04_I PASS +TC_SERVER_BCE_BV_05_I PASS +TC_SERVER_BCE_BV_06_I PASS +TC_SERVER_BCE_BV_07_I PASS +TC_SERVER_BCP_BV_01_I PASS +TC_SERVER_BCP_BV_02_I N/A +TC_SERVER_BCP_BV_03_I PASS +TC_SERVER_BCP_BV_04_I PASS +TC_SERVER_BCP_BV_05_I PASS +TC_SERVER_CON_BV_02_C PASS +TC_SERVER_OPH_BV_01_I PASS +TC_SERVER_OPH_BV_02_I PASS +TC_SERVER_OPH_BV_03_I PASS +TC_SERVER_OPH_BV_04_I PASS +TC_SERVER_OPH_BV_05_I PASS +TC_SERVER_OPH_BV_07_I N/A +TC_SERVER_OPH_BV_08_I N/A +TC_SERVER_OPH_BV_09_I PASS +TC_SERVER_OPH_BV_10_I PASS +TC_SERVER_OPH_BV_11_I N/A +TC_SERVER_OPH_BV_12_I N/A +TC_SERVER_OPH_BV_13_I PASS +TC_SERVER_OPH_BV_14_I PASS +TC_SERVER_OPH_BV_15_I N/A +TC_SERVER_OPH_BV_16_I N/A +TC_SERVER_OPH_BV_17_I PASS +TC_SERVER_OPH_BV_18_I PASS +TC_SERVER_OPH_BV_19_I PASS +TC_SERVER_OPH_BV_21_I N/A +TC_SERVER_OPH_BV_22_I PASS +TC_SERVER_OPH_BV_23_I PASS +TC_SERVER_OPH_BV_24_I N/A +TC_SERVER_OPH_BV_25_I N/A +TC_SERVER_OPH_BV_26_I N/A +TC_SERVER_ROB_BV_01_C PASS +TC_SERVER_ROB_BV_02_C PASS +TC_SERVER_SRM_BI_02_C N/A +TC_SERVER_SRM_BI_03_C PASS +TC_SERVER_SRM_BI_05_C N/A +TC_SERVER_SRM_BV_04_C N/A +TC_SERVER_SRM_BV_08_C N/A +TC_SERVER_SRMP_BV_02_C N/A +TC_SERVER_SRMP_BV_03_C N/A +TC_CLIENT_OPH_BV_27_I N/A +TC_CLIENT_OPH_BV_34_I PASS +TC_SERVER_OPH_BV_27_I N/A +TC_SERVER_OPH_BV_30_I PASS +TC_SERVER_OPH_BV_31_I PASS +TC_SERVER_OPH_BV_32_I PASS +TC_SERVER_OPH_BV_33_I N/A +TC_SERVER_OPH_BV_34_I PASS +------------------------------------------------------------------------------- diff --git a/doc/sap-api.txt b/doc/sap-api.txt new file mode 100644 index 0000000..b28c4e3 --- /dev/null +++ b/doc/sap-api.txt @@ -0,0 +1,20 @@ +BlueZ D-Bus Sim Access API description +************************************** + + +Sim Access Profile hierarchy +============================ + +Service org.bluez +Interface org.bluez.SimAccess1 +Object path [variable prefix]/{hci0,hci1,...} + +Methods void Disconnect() + + Disconnects SAP client from the server. + + Possible errors: org.bluez.Error.Failed + +Properties boolean Connected [readonly] + + Indicates if SAP client is connected to the server. diff --git a/doc/settings-storage.txt b/doc/settings-storage.txt new file mode 100644 index 0000000..5f6d251 --- /dev/null +++ b/doc/settings-storage.txt @@ -0,0 +1,326 @@ +BlueZ settings storage +********************** + +Purpose +======= + +The purpose of this document is to describe the directory structure of +BlueZ settings storage. In effect, this document will serve as the primary, +up to date source of BlueZ storage information. + +It is intended as reference for developers. Direct access to the storage +outside from bluetoothd is highly discouraged. + +Adapter and remote device info are read form the storage during object +initialization. Write to storage is performed immediately on every value +change. + +Default storage directory is /var/lib/bluetooth. This can be adjusted +by the --localstatedir configure switch. Default is --localstatedir=/var. + +All files are in ini-file format. + + +Storage directory structure +=========================== + +The storage root directory contains an optional addresses file that's +used for managing adapters that come without a pre-allocated address. +The format of the addresses file is: + + [Static] + = + +Each adapter with an assigned address has its own subdirectory under the +root, named based on the address, which contains: + + - a settings file for the local adapter + - an attributes file containing attributes of supported LE services + - a cache directory containing: + - one file per device, named by remote device address, which contains + device name + - one directory per remote device, named by remote device address, which + contains: + - an info file + - an attributes file containing attributes of remote LE services + - a ccc file containing persistent Client Characteristic Configuration + (CCC) descriptor information for GATT characteristics + +So the directory structure is: + /var/lib/bluetooth// + ./settings + ./attributes + ./cache/ + ./ + ./ + ... + .// + ./info + ./attributes + ./ccc + .// + ./info + ./attributes + ... + + +Settings file format +==================== + +Settings file contains one [General] group with adapter info like: + + Alias String Friendly user provided name advertised + for this adapter + + This value overwrites the system + name (pretty hostname) + + Discoverable Boolean Discoverability of the adapter + + PairableTimeout Integer How long to stay in pairable mode + before going back to non-pairable. + The value is in seconds. + 0 = disable timer, i.e. stay + pairable forever + + DiscoverableTimeout Integer How long to stay in discoverable mode + before going back to non-discoverable. + The value is in seconds. + 0 = disable timer, i.e. stay + discoverable forever + +Sample: + [General] + Name=My PC + Discoverable=false + Pairable=true + DiscoverableTimeout=0 + + +Identity file format +==================== +Identity file contains one [General] group that holds identity information +such as keys and adresses: + + IdentityResolvingKey String 128-bit value of the IRK + +Sample: + [General] + IdentityResolvingKey=00112233445566778899aabbccddeeff + + +Attributes file format +====================== + +The attributes file lists all attributes supported by the local adapter or +remote device. + +Attributes are stored using their handle as group name (decimal format). + +Each group contains: + + UUID String 128-bit UUID of the attribute + + Value String Value of the attribute as hexadecimal encoded + string + + EndGroupHandle Integer End group handle in decimal format + +Sample: + [1] + UUID=00002800-0000-1000-8000-00805f9b34fb + Value=0018 + + [4] + UUID=00002803-0000-1000-8000-00805f9b34fb + Value=020600002A + + [6] + UUID=00002a00-0000-1000-8000-00805f9b34fb + Value=4578616D706C6520446576696365 + + +CCC file format +====================== + +The ccc file stores the current CCC descriptor values for GATT characteristics +which have notification/indication enabled by the remote device. + +Information is stored using CCC attribute handle as group name (in decimal +format). + +Each group contains: + + Value String CCC descriptor value encoded in + hexadecimal + + +Cache directory file format +============================ + +Each file, named by remote device address, may includes multiple groups +(General, ServiceRecords, Attributes, Endpoints). + +In ServiceRecords, SDP records are stored using their handle as key +(hexadecimal format). + +In "Attributes" group GATT database is stored using attribute handle as key +(hexadecimal format). Value associated with this handle is serialized form of +all data required to re-create given attribute. ":" is used to separate fields. + +In "Endpoints" group A2DP remote endpoints are stored using the seid as key +(hexadecimal format) and ":" is used to separate fields. It may also contain +an entry which key is set to "LastUsed" which represented the last endpoint +used. + +[General] group contains: + + Name String Remote device friendly name + + ShortName String Remote device shortened name + +[ServiceRecords] group contains + + <0x...> String SDP record as hexadecimal encoded + string + +In [Attributes] group value always starts with attribute type, that determines +how to interpret rest of value: + + Primary service: + 2800:end_handle:uuid + + Secondary service: + 2801:end_handle:uuid + + Included service: + 2802:start_handle:end_handle:uuid + + Characteristic: + 2803:value_handle:properties:uuid + + Descriptor: + value:uuid + uuid + +Sample Attributes section: + [Attributes] + 0001=2800:0005:1801 + 0002=2803:0003:20:2a05 + 0014=2800:001c:1800 + 0015=2803:0016:02:2a00 + 0017=2803:0018:02:2a01 + 0019=2803:001a:02:2aa6 + 0028=2800:ffff:0000180d-0000-1000-8000-00805f9b34fb + 0029=2803:002a:10:00002a37-0000-1000-8000-00805f9b34fb + 002b=2803:002c:02:00002a38-0000-1000-8000-00805f9b34fb + 002d=2803:002e:08:00002a39-0000-1000-8000-00805f9b34fb + +[Endpoints] group contains: + + :: String First field is the endpoint type, + followed by codec type and its + capabilies as hexadecimal encoded + string. + LastUsed:: String LastUsed has two fields which are the + local and remote seids as hexadecimal + encoded string. + +Info file format +================ + +Info file may includes multiple groups (General, Device ID, Link key and +Long term key) related to a remote device. + +[General] group contains: + + Name String Remote device friendly name + + Alias String Alias name + + Class String Device class in hexadecimal, + i.e. 0x000000 + + Appearance String Device appearance in hexadecimal, + i.e. 0x0000 + + SupportedTechnologies List of List of technologies supported by + strings device, separated by ";" + Technologies can be BR/EDR or LE + + AddressType String An address can be "static" or "public" + + Trusted Boolean True if the remote device is trusted + + Blocked Boolean True if the remote device is blocked + + Services List of List of service UUIDs advertised by + strings remote in 128-bits UUID format, + separated by ";" + + +[DeviceID] group contains: + + Source Integer Assigner of Device ID + + Vendor Integer Device vendor + + Product Integer Device product + + Version Integer Device version + + +[LinkKey] group contains: + + Key String Key in hexadecimal format + + Type Integer Type of link key + + PINLength Integer Length of PIN + + +[LongTermKey] group contains: + + Key String Long term key in hexadecimal format + + Authenticated Boolean True if remote device has been + authenticated + + EncSize Integer Encrypted size + + EDiv Integer Encrypted diversifier + + Rand Integer Randomizer + + +[SlaveLongTermKey] group contains: + + Same as the [LongTermKey] group, except for slave keys. + + +[ConnectionParameters] group contains: + + MinInterval Integer Minimum Connection Interval + + MaxInterval Integer Maximum Connection Interval + + Latency Integer Connection Latency + + Timeout Integer Supervision Timeout + + +[LocalSignatureKey] and [RemoteSignatureKey] groups contain: + + Key String Key in hexadecimal format + + Counter Integer Signing counter + + Authenticated Boolean True if the key is authenticated + +[ServiceChanged] + + This section holds information related to Service Changed characteristic + of GATT core service. + + CCC_LE Integer CCC value for LE transport + CCC_BR/EDR Integer CCC value for BR/EDR transport diff --git a/doc/supported-features.txt b/doc/supported-features.txt new file mode 100644 index 0000000..f04cf4a --- /dev/null +++ b/doc/supported-features.txt @@ -0,0 +1,53 @@ +Supported features in BlueZ +=========================== + +Note that some profiles/roles will depend on external components such as +oFono or ConnMan. + +Profile/protocol Version Role(s) +--------------------------------------------------------------------------- + +GAP 4.2 (LE) Central, Peripheral, + Observer, Broadcaster +L2CAP 4.2 Server, Client +SDP 4.2 Server, Client +GATT 4.2 Server, Client +SDAP 1.1 Server, Client +RFCOMM 1.1 Server, Client +SPP 1.2 Server, Client + +PXP 1.0 Reporter, Monitor +HOGP 1.0 Host +HTP 1.0 +TIP 1.0 +CSCP 1.0 Collector + +SAP 1.1 Server +DUN 1.1 Server, Client + +DID 1.3 Server, Client + +HFP 1.6 AG, HF +HSP 1.2 AG, HS +GAVDTP 1.2 Source, Sink +AVDTP 1.3 Source, Sink +A2DP 1.3 Source, Sink +AVCTP 1.3 CT, TG +AVRCP 1.5 CT, TG + +GOEP 2.0 Client, Server +FTP 1.2 Client, Server +OPP 1.2 Client, Server +SYNCH 1.1 Client +PBAP 1.1 Client, Server +MAP 1.0 Client, Server + +HID 1.1 Host + +BNEP 1.0 +PAN 1.0 PANU, NAP, GN + +HCRP 1.2 + +MCAP 1.0 +HDP 1.0 diff --git a/doc/test-coverage.txt b/doc/test-coverage.txt new file mode 100644 index 0000000..741492a --- /dev/null +++ b/doc/test-coverage.txt @@ -0,0 +1,73 @@ +BlueZ test coverage +******************* + + +Automated unit testing +====================== + +Application Count Description +------------------------------------------- +test-crc 9 Link Layer CRC-24 checksum +test-eir 14 EIR and AD parsing +test-lib 14 SDP library functions +test-sdp 133 SDP qualification test cases +test-uuid 30 UUID conversion handling +test-mgmt 9 Management interface handling +test-crypto 5 Cryptographic toolbox helpers +test-textfile 4 Old textfile storage format +test-ringbuf 3 Ring buffer functionality +test-queue 6 Queue handling functionality +test-uhid 6 Userspace HID functionality +test-hfp 29 HFP Audio Gateway functionality +test-avdtp 60 AVDTP qualification test cases +test-avctp 9 AVCTP qualification test cases +test-avrcp 110 AVRCP qualification test cases +test-gobex 31 Generic OBEX functionality +test-gobex-packet 9 OBEX packet handling +test-gobex-header 28 OBEX header handling +test-gobex-apparam 18 OBEX apparam handling +test-gobex-transfer 36 OBEX transfer handling +test-gdbus-client 13 D-Bus client handling +test-gatt 180 GATT qualification test cases +test-hog 6 HID Over GATT qualification test cases + ----- + 761 + + +Automated end-to-end testing +============================ + +Application Count Description +------------------------------------------- +mgmt-tester 331 Kernel management interface testing +l2cap-tester 33 Kernel L2CAP implementation testing +rfcomm-tester 9 Kernel RFCOMM implementation testing +bnep-tester 1 Kernel BNEP implementation testing +smp-tester 8 Kernel SMP implementation testing +sco-tester 8 Kernel SCO implementation testing +gap-tester 1 Daemon D-Bus API testing +hci-tester 14 Controller hardware testing +userchan-tester 3 Kernel HCI User Channel testting + ----- + 408 + + +Android end-to-end testing +========================== + +Application Count Description +------------------------------------------- +android-tester 194 Android HAL interface testing +ipc-tester 132 Android IPC resistance testing + ----- + 326 + + +Android automated unit testing +============================== + +Application Count Description +------------------------------------------- +test-ipc 14 Android IPC library functions + ----- + 14 diff --git a/doc/test-runner.txt b/doc/test-runner.txt new file mode 100644 index 0000000..fe0a0d4 --- /dev/null +++ b/doc/test-runner.txt @@ -0,0 +1,75 @@ +Notes for test-runner usage +*************************** + + +Kernel configuration +==================== + +The test-runner tool requires a kernel that is at least build with these +minimal options for a successful boot. + + CONFIG_VIRTIO=y + CONFIG_VIRTIO_PCI=y + + CONFIG_NET=y + CONFIG_INET=y + + CONFIG_NET_9P=y + CONFIG_NET_9P_VIRTIO=y + + CONFIG_9P_FS=y + CONFIG_9P_FS_POSIX_ACL=y + + CONFIG_SERIAL_8250=y + CONFIG_SERIAL_8250_CONSOLE=y + CONFIG_SERIAL_8250_PCI=y + CONFIG_SERIAL_8250_NR_UARTS=4 + + CONFIG_TMPFS=y + CONFIG_TMPFS_POSIX_ACL=y + CONFIG_TMPFS_XATTR=y + + CONFIG_DEVTMPFS=y + CONFIG_DEBUG_FS=y + +For Bluetooth functionality: + + CONFIG_BT=y + CONFIG_BT_BREDR=y + CONFIG_BT_RFCOMM=y + CONFIG_BT_BNEP=y + CONFIG_BT_HIDP=y + CONFIG_BT_LE=y + + CONFIG_BT_HCIVHCI=y + + CONFIG_CRYPTO_CMAC=y + CONFIG_CRYPTO_USER_API=y + CONFIG_CRYPTO_USER_API_HASH=y + CONFIG_CRYPTO_USER_API_SKCIPHER=y + + CONFIG_UNIX=y + + CONFIG_UHID=y + + +These options should be installed as .config in the kernel source directory +followed by this command. + + make olddefconfig + +After that a default kernel with the required options can be built. More +option (like the Bluetooth subsystem) can be enabled on top of this. + +Lock debuging +------------- + +To catch locking related issues the following set of kernel config +options may be useful: + + CONFIG_LOCKDEP_SUPPORT=y + CONFIG_DEBUG_SPINLOCK=y + CONFIG_DEBUG_LOCK_ALLOC=y + CONFIG_PROVE_LOCKING=y + CONFIG_LOCKDEP=y + CONFIG_DEBUG_MUTEXES=y diff --git a/ell/checksum.c b/ell/checksum.c new file mode 100644 index 0000000..729399e --- /dev/null +++ b/ell/checksum.c @@ -0,0 +1,489 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "util.h" +#include "checksum.h" +#include "private.h" + +#ifndef HAVE_LINUX_IF_ALG_H +#ifndef HAVE_LINUX_TYPES_H +typedef uint8_t __u8; +typedef uint16_t __u16; +typedef uint32_t __u32; +#else +#include +#endif + +#ifndef AF_ALG +#define AF_ALG 38 +#define PF_ALG AF_ALG +#endif + +struct sockaddr_alg { + __u16 salg_family; + __u8 salg_type[14]; + __u32 salg_feat; + __u32 salg_mask; + __u8 salg_name[64]; +}; + +/* Socket options */ +#define ALG_SET_KEY 1 + +#else +#include +#endif + +#ifndef SOL_ALG +#define SOL_ALG 279 +#endif + +struct checksum_info { + const char *name; + uint8_t digest_len; + bool supported; +}; + +static struct checksum_info checksum_algs[] = { + [L_CHECKSUM_MD4] = { .name = "md4", .digest_len = 16 }, + [L_CHECKSUM_MD5] = { .name = "md5", .digest_len = 16 }, + [L_CHECKSUM_SHA1] = { .name = "sha1", .digest_len = 20 }, + [L_CHECKSUM_SHA256] = { .name = "sha256", .digest_len = 32 }, + [L_CHECKSUM_SHA384] = { .name = "sha384", .digest_len = 48 }, + [L_CHECKSUM_SHA512] = { .name = "sha512", .digest_len = 64 }, +}; + +static struct checksum_info checksum_cmac_aes_alg = + { .name = "cmac(aes)", .digest_len = 16 }; + +static struct checksum_info checksum_hmac_algs[] = { + [L_CHECKSUM_MD4] = { .name = "hmac(md4)", .digest_len = 16 }, + [L_CHECKSUM_MD5] = { .name = "hmac(md5)", .digest_len = 16 }, + [L_CHECKSUM_SHA1] = { .name = "hmac(sha1)", .digest_len = 20 }, + [L_CHECKSUM_SHA256] = { .name = "hmac(sha256)", .digest_len = 32 }, + [L_CHECKSUM_SHA384] = { .name = "hmac(sha384)", .digest_len = 48 }, + [L_CHECKSUM_SHA512] = { .name = "hmac(sha512)", .digest_len = 64 }, +}; + +static const struct { + struct checksum_info *list; + size_t n; +} checksum_info_table[] = { + { checksum_algs, L_ARRAY_SIZE(checksum_algs) }, + { &checksum_cmac_aes_alg, 1 }, + { checksum_hmac_algs, L_ARRAY_SIZE(checksum_hmac_algs) }, + {} +}; + +/** + * SECTION:checksum + * @short_description: Checksum handling + * + * Checksum handling + */ + +#define is_valid_index(array, i) ((i) >= 0 && (i) < L_ARRAY_SIZE(array)) + +/** + * l_checksum: + * + * Opaque object representing the checksum. + */ +struct l_checksum { + int sk; + const struct checksum_info *alg_info; +}; + +static int create_alg(const char *alg) +{ + struct sockaddr_alg salg; + int sk; + + sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + if (sk < 0) + return -1; + + memset(&salg, 0, sizeof(salg)); + salg.salg_family = AF_ALG; + strcpy((char *) salg.salg_type, "hash"); + strcpy((char *) salg.salg_name, alg); + + if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) { + close(sk); + return -1; + } + + return sk; +} + +/** + * l_checksum_new: + * @type: checksum type + * + * Creates new #l_checksum, using the checksum algorithm @type. + * + * Returns: a newly allocated #l_checksum object. + **/ +LIB_EXPORT struct l_checksum *l_checksum_new(enum l_checksum_type type) +{ + struct l_checksum *checksum; + int fd; + + if (!is_valid_index(checksum_algs, type) || !checksum_algs[type].name) + return NULL; + + checksum = l_new(struct l_checksum, 1); + checksum->alg_info = &checksum_algs[type]; + + fd = create_alg(checksum->alg_info->name); + if (fd < 0) + goto error; + + checksum->sk = accept4(fd, NULL, 0, SOCK_CLOEXEC); + close(fd); + + if (checksum->sk < 0) + goto error; + + return checksum; + +error: + l_free(checksum); + return NULL; +} + +LIB_EXPORT struct l_checksum *l_checksum_new_cmac_aes(const void *key, + size_t key_len) +{ + struct l_checksum *checksum; + int fd; + + fd = create_alg("cmac(aes)"); + if (fd < 0) + return NULL; + + if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_len) < 0) { + close(fd); + return NULL; + } + + checksum = l_new(struct l_checksum, 1); + checksum->sk = accept4(fd, NULL, 0, SOCK_CLOEXEC); + close(fd); + + if (checksum->sk < 0) { + l_free(checksum); + return NULL; + } + + checksum->alg_info = &checksum_cmac_aes_alg; + return checksum; +} + +LIB_EXPORT struct l_checksum *l_checksum_new_hmac(enum l_checksum_type type, + const void *key, size_t key_len) +{ + struct l_checksum *checksum; + int fd; + + if (!is_valid_index(checksum_hmac_algs, type) || + !checksum_hmac_algs[type].name) + return NULL; + + fd = create_alg(checksum_hmac_algs[type].name); + if (fd < 0) + return NULL; + + if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_len) < 0) { + close(fd); + return NULL; + } + + checksum = l_new(struct l_checksum, 1); + checksum->sk = accept4(fd, NULL, 0, SOCK_CLOEXEC); + close(fd); + + if (checksum->sk < 0) { + l_free(checksum); + return NULL; + } + + checksum->alg_info = &checksum_hmac_algs[type]; + return checksum; +} + +/** + * l_checksum_clone: + * @checksum: parent checksum object + * + * Creates a new checksum with an independent copy of parent @checksum's + * state. l_checksum_get_digest can then be called on the parent or the + * clone without affecting the state of the other object. + **/ +LIB_EXPORT struct l_checksum *l_checksum_clone(struct l_checksum *checksum) +{ + struct l_checksum *clone; + + if (unlikely(!checksum)) + return NULL; + + clone = l_new(struct l_checksum, 1); + clone->sk = accept4(checksum->sk, NULL, 0, SOCK_CLOEXEC); + + if (clone->sk < 0) { + l_free(clone); + return NULL; + } + + clone->alg_info = checksum->alg_info; + return clone; +} + +/** + * l_checksum_free: + * @checksum: checksum object + * + * Frees the memory allocated for @checksum. + **/ +LIB_EXPORT void l_checksum_free(struct l_checksum *checksum) +{ + if (unlikely(!checksum)) + return; + + close(checksum->sk); + l_free(checksum); +} + +/** + * l_checksum_reset: + * @checksum: checksum object + * + * Resets the internal state of @checksum. + **/ +LIB_EXPORT void l_checksum_reset(struct l_checksum *checksum) +{ + if (unlikely(!checksum)) + return; + + send(checksum->sk, NULL, 0, 0); +} + +/** + * l_checksum_update: + * @checksum: checksum object + * @data: data pointer + * @len: length of data + * + * Updates checksum from @data pointer with @len bytes. + * + * Returns: true if the operation succeeded, false otherwise. + **/ +LIB_EXPORT bool l_checksum_update(struct l_checksum *checksum, + const void *data, size_t len) +{ + ssize_t written; + + if (unlikely(!checksum)) + return false; + + written = send(checksum->sk, data, len, MSG_MORE); + if (written < 0) + return false; + + return true; +} + +/** + * l_checksum_updatev: + * @checksum: checksum object + * @iov: iovec pointer + * @iov_len: Number of iovec entries + * + * This is a iovec based version of l_checksum_update; it updates the checksum + * based on contents of @iov and @iov_len. + * + * Returns: true if the operation succeeded, false otherwise. + **/ +LIB_EXPORT bool l_checksum_updatev(struct l_checksum *checksum, + const struct iovec *iov, size_t iov_len) +{ + struct msghdr msg; + ssize_t written; + + if (unlikely(!checksum)) + return false; + + if (unlikely(!iov) || unlikely(!iov_len)) + return false; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = (struct iovec *) iov; + msg.msg_iovlen = iov_len; + + written = sendmsg(checksum->sk, &msg, MSG_MORE); + if (written < 0) + return false; + + return true; +} + +/** + * l_checksum_get_digest: + * @checksum: checksum object + * @digest: output data buffer + * @len: length of output buffer + * + * Writes the digest from @checksum as raw binary data into the provided + * buffer or, if the buffer is shorter, the initial @len bytes of the digest + * data. + * + * Returns: Number of bytes written, or negative value if an error occurred. + **/ +LIB_EXPORT ssize_t l_checksum_get_digest(struct l_checksum *checksum, + void *digest, size_t len) +{ + ssize_t result; + + if (unlikely(!checksum)) + return -EINVAL; + + if (unlikely(!digest)) + return -EFAULT; + + if (unlikely(!len)) + return -EINVAL; + + result = recv(checksum->sk, digest, len, 0); + if (result < 0) + return -errno; + + if ((size_t) result < len && result < checksum->alg_info->digest_len) + return -EIO; + + return result; +} + +/** + * l_checksum_get_string: + * @checksum: checksum object + * + * Gets the digest from @checksum as hex encoded string. + * + * Returns: a newly allocated hex string + **/ +LIB_EXPORT char *l_checksum_get_string(struct l_checksum *checksum) +{ + unsigned char digest[64]; + + if (unlikely(!checksum)) + return NULL; + + l_checksum_get_digest(checksum, digest, sizeof(digest)); + + return l_util_hexstring(digest, checksum->alg_info->digest_len); +} + +static void init_supported() +{ + static bool initialized = false; + struct sockaddr_alg salg; + int sk; + unsigned int i, j; + + if (likely(initialized)) + return; + + initialized = true; + + sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + if (sk < 0) + return; + + memset(&salg, 0, sizeof(salg)); + salg.salg_family = AF_ALG; + strcpy((char *) salg.salg_type, "hash"); + + for (i = 0; checksum_info_table[i].list; i++) + for (j = 0; j < checksum_info_table[i].n; j++) { + struct checksum_info *info; + + info = &checksum_info_table[i].list[j]; + if (!info->name) + continue; + + strcpy((char *) salg.salg_name, info->name); + + if (bind(sk, (struct sockaddr *) &salg, + sizeof(salg)) < 0) + continue; + + info->supported = true; + } + + close(sk); +} + +LIB_EXPORT bool l_checksum_is_supported(enum l_checksum_type type, + bool check_hmac) +{ + const struct checksum_info *list; + + init_supported(); + + if (!check_hmac) { + if (!is_valid_index(checksum_algs, type)) + return false; + + list = checksum_algs; + } else { + if (!is_valid_index(checksum_hmac_algs, type)) + return false; + + list = checksum_hmac_algs; + } + + return list[type].supported; +} + +LIB_EXPORT bool l_checksum_cmac_aes_supported() +{ + init_supported(); + + return checksum_cmac_aes_alg.supported; +} + +LIB_EXPORT ssize_t l_checksum_digest_length(enum l_checksum_type type) +{ + return is_valid_index(checksum_algs, type) ? + checksum_algs[type].digest_len : 0; +} diff --git a/ell/checksum.h b/ell/checksum.h new file mode 100644 index 0000000..08d74b7 --- /dev/null +++ b/ell/checksum.h @@ -0,0 +1,75 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_CHECKSUM_H +#define __ELL_CHECKSUM_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct l_checksum; + +enum l_checksum_type { + L_CHECKSUM_NONE, + L_CHECKSUM_MD4, + L_CHECKSUM_MD5, + L_CHECKSUM_SHA1, + L_CHECKSUM_SHA224, + L_CHECKSUM_SHA256, + L_CHECKSUM_SHA384, + L_CHECKSUM_SHA512, +}; + +struct l_checksum *l_checksum_new(enum l_checksum_type type); +struct l_checksum *l_checksum_new_cmac_aes(const void *key, size_t key_len); +struct l_checksum *l_checksum_new_hmac(enum l_checksum_type type, + const void *key, size_t key_len); +struct l_checksum *l_checksum_clone(struct l_checksum *checksum); + +void l_checksum_free(struct l_checksum *checksum); + +void l_checksum_reset(struct l_checksum *checksum); + +bool l_checksum_update(struct l_checksum *checksum, + const void *data, size_t len); +bool l_checksum_updatev(struct l_checksum *checksum, + const struct iovec *iov, + size_t iov_len); +ssize_t l_checksum_get_digest(struct l_checksum *checksum, + void *digest, size_t len); +char *l_checksum_get_string(struct l_checksum *checksum); + +bool l_checksum_is_supported(enum l_checksum_type type, bool check_hmac); +bool l_checksum_cmac_aes_supported(); + +ssize_t l_checksum_digest_length(enum l_checksum_type type); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_CHECKSUM_H */ diff --git a/ell/cipher.c b/ell/cipher.c new file mode 100644 index 0000000..4a27b5e --- /dev/null +++ b/ell/cipher.c @@ -0,0 +1,662 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2015 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "util.h" +#include "cipher.h" +#include "private.h" +#include "random.h" + +#ifndef HAVE_LINUX_IF_ALG_H +#ifndef HAVE_LINUX_TYPES_H +typedef uint8_t __u8; +typedef uint16_t __u16; +typedef uint32_t __u32; +#else +#include +#endif + +#ifndef AF_ALG +#define AF_ALG 38 +#define PF_ALG AF_ALG +#endif + +struct sockaddr_alg { + __u16 salg_family; + __u8 salg_type[14]; + __u32 salg_feat; + __u32 salg_mask; + __u8 salg_name[64]; +}; + +struct af_alg_iv { + __u32 ivlen; + __u8 iv[0]; +}; + +/* Socket options */ +#define ALG_SET_KEY 1 +#define ALG_SET_IV 2 +#define ALG_SET_OP 3 + +/* Operations */ +#define ALG_OP_DECRYPT 0 +#define ALG_OP_ENCRYPT 1 +#else +#include +#endif + +#ifndef SOL_ALG +#define SOL_ALG 279 +#endif + +#ifndef ALG_SET_AEAD_ASSOCLEN +#define ALG_SET_AEAD_ASSOCLEN 4 +#endif + +#ifndef ALG_SET_AEAD_AUTHSIZE +#define ALG_SET_AEAD_AUTHSIZE 5 +#endif + +#define is_valid_type(type) ((type) <= L_CIPHER_DES3_EDE_CBC) + +static uint32_t supported_ciphers; +static uint32_t supported_aead_ciphers; + +struct l_cipher { + int type; + int encrypt_sk; + int decrypt_sk; +}; + +struct l_aead_cipher { + int type; + int sk; +}; + +static int create_alg(const char *alg_type, const char *alg_name, + const void *key, size_t key_length, size_t tag_length) +{ + struct sockaddr_alg salg; + int sk; + int ret; + + sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + if (sk < 0) + return -errno; + + memset(&salg, 0, sizeof(salg)); + salg.salg_family = AF_ALG; + strcpy((char *) salg.salg_type, alg_type); + strcpy((char *) salg.salg_name, alg_name); + + if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) { + close(sk); + return -1; + } + + if (setsockopt(sk, SOL_ALG, ALG_SET_KEY, key, key_length) < 0) { + close(sk); + return -1; + } + + if (tag_length && setsockopt(sk, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, + tag_length)) { + close(sk); + return -1; + } + + ret = accept4(sk, NULL, 0, SOCK_CLOEXEC); + close(sk); + + return ret; +} + +static const char *cipher_type_to_name(enum l_cipher_type type) +{ + switch (type) { + case L_CIPHER_AES: + return "ecb(aes)"; + case L_CIPHER_AES_CBC: + return "cbc(aes)"; + case L_CIPHER_AES_CTR: + return "ctr(aes)"; + case L_CIPHER_ARC4: + return "ecb(arc4)"; + case L_CIPHER_DES: + return "ecb(des)"; + case L_CIPHER_DES_CBC: + return "cbc(des)"; + case L_CIPHER_DES3_EDE_CBC: + return "cbc(des3_ede)"; + } + + return NULL; +} + +LIB_EXPORT struct l_cipher *l_cipher_new(enum l_cipher_type type, + const void *key, + size_t key_length) +{ + struct l_cipher *cipher; + const char *uninitialized_var(alg_name); + + if (unlikely(!key)) + return NULL; + + if (!is_valid_type(type)) + return NULL; + + cipher = l_new(struct l_cipher, 1); + cipher->type = type; + alg_name = cipher_type_to_name(type); + + cipher->encrypt_sk = create_alg("skcipher", alg_name, key, key_length, + 0); + if (cipher->encrypt_sk < 0) + goto error_free; + + cipher->decrypt_sk = create_alg("skcipher", alg_name, key, key_length, + 0); + if (cipher->decrypt_sk < 0) + goto error_close; + + return cipher; + +error_close: + close(cipher->encrypt_sk); +error_free: + l_free(cipher); + return NULL; +} + +static const char *aead_cipher_type_to_name(enum l_aead_cipher_type type) +{ + switch (type) { + case L_AEAD_CIPHER_AES_CCM: + return "ccm(aes)"; + case L_AEAD_CIPHER_AES_GCM: + return "gcm(aes)"; + } + + return NULL; +} + +LIB_EXPORT struct l_aead_cipher *l_aead_cipher_new(enum l_aead_cipher_type type, + const void *key, + size_t key_length, + size_t tag_length) +{ + struct l_aead_cipher *cipher; + const char *alg_name; + + if (unlikely(!key)) + return NULL; + + if (type != L_AEAD_CIPHER_AES_CCM && type != L_AEAD_CIPHER_AES_GCM) + return NULL; + + cipher = l_new(struct l_aead_cipher, 1); + cipher->type = type; + alg_name = aead_cipher_type_to_name(type); + + cipher->sk = create_alg("aead", alg_name, key, key_length, tag_length); + if (cipher->sk >= 0) + return cipher; + + l_free(cipher); + return NULL; +} + +LIB_EXPORT void l_cipher_free(struct l_cipher *cipher) +{ + if (unlikely(!cipher)) + return; + + close(cipher->encrypt_sk); + close(cipher->decrypt_sk); + + l_free(cipher); +} + +LIB_EXPORT void l_aead_cipher_free(struct l_aead_cipher *cipher) +{ + if (unlikely(!cipher)) + return; + + close(cipher->sk); + + l_free(cipher); +} + +static ssize_t operate_cipher(int sk, __u32 operation, + const void *in, size_t in_len, + const void *ad, size_t ad_len, + const void *iv, size_t iv_len, + void *out, size_t out_len) +{ + char *c_msg_buf; + size_t c_msg_size; + struct msghdr msg; + struct cmsghdr *c_msg; + struct iovec iov[2]; + ssize_t result; + + c_msg_size = CMSG_SPACE(sizeof(operation)); + c_msg_size += ad_len ? CMSG_SPACE(sizeof(uint32_t)) : 0; + c_msg_size += iv_len ? + CMSG_SPACE(sizeof(struct af_alg_iv) + iv_len) : 0; + + c_msg_buf = alloca(c_msg_size); + + memset(c_msg_buf, 0, c_msg_size); + memset(&msg, 0, sizeof(msg)); + + msg.msg_iov = iov; + + msg.msg_control = c_msg_buf; + msg.msg_controllen = c_msg_size; + + c_msg = CMSG_FIRSTHDR(&msg); + c_msg->cmsg_level = SOL_ALG; + c_msg->cmsg_type = ALG_SET_OP; + c_msg->cmsg_len = CMSG_LEN(sizeof(operation)); + memcpy(CMSG_DATA(c_msg), &operation, sizeof(operation)); + + if (ad_len) { + uint32_t *ad_data; + + c_msg = CMSG_NXTHDR(&msg, c_msg); + c_msg->cmsg_level = SOL_ALG; + c_msg->cmsg_type = ALG_SET_AEAD_ASSOCLEN; + c_msg->cmsg_len = CMSG_LEN(sizeof(*ad_data)); + ad_data = (void *) CMSG_DATA(c_msg); + *ad_data = ad_len; + + iov[0].iov_base = (void *) ad; + iov[0].iov_len = ad_len; + iov[1].iov_base = (void *) in; + iov[1].iov_len = in_len; + msg.msg_iovlen = 2; + } else { + iov[0].iov_base = (void *) in; + iov[0].iov_len = in_len; + msg.msg_iovlen = 1; + } + + if (iv_len) { + struct af_alg_iv *algiv; + + c_msg = CMSG_NXTHDR(&msg, c_msg); + c_msg->cmsg_level = SOL_ALG; + c_msg->cmsg_type = ALG_SET_IV; + c_msg->cmsg_len = CMSG_LEN(sizeof(*algiv) + iv_len); + + algiv = (void *)CMSG_DATA(c_msg); + algiv->ivlen = iv_len; + memcpy(algiv->iv, iv, iv_len); + } + + result = sendmsg(sk, &msg, 0); + if (result < 0) + return -errno; + + if (ad_len) { + /* + * When AEAD additional data is passed to sendmsg() for + * use in computing the tag, those bytes also appear at + * the beginning of the encrypt or decrypt results. Rather + * than force the caller to pad their result buffer with + * the correct number of bytes for the additional data, + * the necessary space is allocated here and then the + * duplicate AAD is discarded. + */ + iov[0].iov_base = l_malloc(ad_len); + iov[0].iov_len = ad_len; + iov[1].iov_base = (void *) out; + iov[1].iov_len = out_len; + msg.msg_iovlen = 2; + + msg.msg_control = NULL; + msg.msg_controllen = 0; + + result = recvmsg(sk, &msg, 0); + + if (result >= (ssize_t) ad_len) + result -= ad_len; + else if (result > 0) + result = 0; + + l_free(iov[0].iov_base); + } else { + result = read(sk, out, out_len); + } + + if (result < 0) + return -errno; + + return result; +} + +static ssize_t operate_cipherv(int sk, __u32 operation, + const struct iovec *in, size_t in_cnt, + const struct iovec *out, size_t out_cnt) +{ + char *c_msg_buf; + size_t c_msg_size; + struct msghdr msg; + struct cmsghdr *c_msg; + ssize_t result; + + c_msg_size = CMSG_SPACE(sizeof(operation)); + c_msg_buf = alloca(c_msg_size); + + memset(c_msg_buf, 0, c_msg_size); + memset(&msg, 0, sizeof(msg)); + + msg.msg_iov = (struct iovec *) in; + msg.msg_iovlen = in_cnt; + + msg.msg_control = c_msg_buf; + msg.msg_controllen = c_msg_size; + + c_msg = CMSG_FIRSTHDR(&msg); + c_msg->cmsg_level = SOL_ALG; + c_msg->cmsg_type = ALG_SET_OP; + c_msg->cmsg_len = CMSG_LEN(sizeof(operation)); + memcpy(CMSG_DATA(c_msg), &operation, sizeof(operation)); + + result = sendmsg(sk, &msg, 0); + if (result < 0) + return -errno; + + result = readv(sk, out, out_cnt); + + if (result < 0) + return -errno; + + return result; +} + +LIB_EXPORT bool l_cipher_encrypt(struct l_cipher *cipher, + const void *in, void *out, size_t len) +{ + if (unlikely(!cipher)) + return false; + + if (unlikely(!in) || unlikely(!out)) + return false; + + return operate_cipher(cipher->encrypt_sk, ALG_OP_ENCRYPT, in, len, + NULL, 0, NULL, 0, out, len) >= 0; +} + +LIB_EXPORT bool l_cipher_encryptv(struct l_cipher *cipher, + const struct iovec *in, size_t in_cnt, + const struct iovec *out, size_t out_cnt) +{ + if (unlikely(!cipher)) + return false; + + if (unlikely(!in) || unlikely(!out)) + return false; + + return operate_cipherv(cipher->encrypt_sk, ALG_OP_ENCRYPT, in, in_cnt, + out, out_cnt) >= 0; +} + +LIB_EXPORT bool l_cipher_decrypt(struct l_cipher *cipher, + const void *in, void *out, size_t len) +{ + if (unlikely(!cipher)) + return false; + + if (unlikely(!in) || unlikely(!out)) + return false; + + return operate_cipher(cipher->decrypt_sk, ALG_OP_DECRYPT, in, len, + NULL, 0, NULL, 0, out, len) >= 0; +} + +LIB_EXPORT bool l_cipher_decryptv(struct l_cipher *cipher, + const struct iovec *in, size_t in_cnt, + const struct iovec *out, size_t out_cnt) +{ + if (unlikely(!cipher)) + return false; + + if (unlikely(!in) || unlikely(!out)) + return false; + + return operate_cipherv(cipher->decrypt_sk, ALG_OP_DECRYPT, in, in_cnt, + out, out_cnt) >= 0; +} + +LIB_EXPORT bool l_cipher_set_iv(struct l_cipher *cipher, const uint8_t *iv, + size_t iv_length) +{ + char c_msg_buf[CMSG_SPACE(4 + iv_length)]; + struct msghdr msg; + struct cmsghdr *c_msg; + uint32_t len = iv_length; + + if (unlikely(!cipher)) + return false; + + memset(&c_msg_buf, 0, sizeof(c_msg_buf)); + memset(&msg, 0, sizeof(struct msghdr)); + + msg.msg_control = c_msg_buf; + msg.msg_controllen = sizeof(c_msg_buf); + + c_msg = CMSG_FIRSTHDR(&msg); + c_msg->cmsg_level = SOL_ALG; + c_msg->cmsg_type = ALG_SET_IV; + c_msg->cmsg_len = CMSG_LEN(4 + iv_length); + memcpy(CMSG_DATA(c_msg) + 0, &len, 4); + memcpy(CMSG_DATA(c_msg) + 4, iv, iv_length); + + msg.msg_iov = NULL; + msg.msg_iovlen = 0; + + if (sendmsg(cipher->encrypt_sk, &msg, 0) < 0) + return false; + + if (sendmsg(cipher->decrypt_sk, &msg, 0) < 0) + return false; + + return true; +} + +#define CCM_IV_SIZE 16 + +static size_t l_aead_cipher_get_ivlen(struct l_aead_cipher *cipher) +{ + switch (cipher->type) { + case L_AEAD_CIPHER_AES_CCM: + return CCM_IV_SIZE; + case L_AEAD_CIPHER_AES_GCM: + return 12; + } + + return 0; +} + +/* RFC3610 Section 2.3 */ +static ssize_t build_ccm_iv(const void *nonce, uint8_t nonce_len, + uint8_t (*iv)[CCM_IV_SIZE]) +{ + const size_t iv_overhead = 2; + int lprime = 15 - nonce_len - 1; + + if (unlikely(nonce_len + iv_overhead > CCM_IV_SIZE || lprime > 7)) + return -EINVAL; + + (*iv)[0] = lprime; + memcpy(*iv + 1, nonce, nonce_len); + memset(*iv + 1 + nonce_len, 0, lprime + 1); + + return CCM_IV_SIZE; +} + +LIB_EXPORT bool l_aead_cipher_encrypt(struct l_aead_cipher *cipher, + const void *in, size_t in_len, + const void *ad, size_t ad_len, + const void *nonce, size_t nonce_len, + void *out, size_t out_len) +{ + uint8_t ccm_iv[CCM_IV_SIZE]; + const uint8_t *iv; + ssize_t iv_len; + + if (unlikely(!cipher)) + return false; + + if (unlikely(!in) || unlikely(!out)) + return false; + + if (cipher->type == L_AEAD_CIPHER_AES_CCM) { + iv_len = build_ccm_iv(nonce, nonce_len, &ccm_iv); + if (unlikely(iv_len < 0)) + return false; + + iv = ccm_iv; + } else { + if (unlikely(nonce_len != l_aead_cipher_get_ivlen(cipher))) + return false; + + iv = nonce; + iv_len = nonce_len; + } + + return operate_cipher(cipher->sk, ALG_OP_ENCRYPT, in, in_len, + ad, ad_len, iv, iv_len, out, out_len) == + (ssize_t)out_len; +} + +LIB_EXPORT bool l_aead_cipher_decrypt(struct l_aead_cipher *cipher, + const void *in, size_t in_len, + const void *ad, size_t ad_len, + const void *nonce, size_t nonce_len, + void *out, size_t out_len) +{ + uint8_t ccm_iv[CCM_IV_SIZE]; + const uint8_t *iv; + ssize_t iv_len; + + if (unlikely(!cipher)) + return false; + + if (unlikely(!in) || unlikely(!out)) + return false; + + if (cipher->type == L_AEAD_CIPHER_AES_CCM) { + iv_len = build_ccm_iv(nonce, nonce_len, &ccm_iv); + if (unlikely(iv_len < 0)) + return false; + + iv = ccm_iv; + } else { + if (unlikely(nonce_len != l_aead_cipher_get_ivlen(cipher))) + return false; + + iv = nonce; + iv_len = nonce_len; + } + + return operate_cipher(cipher->sk, ALG_OP_DECRYPT, in, in_len, + ad, ad_len, iv, iv_len, out, out_len) == + (ssize_t)out_len; +} + +static void init_supported() +{ + static bool initialized = false; + struct sockaddr_alg salg; + int sk; + enum l_cipher_type c; + enum l_aead_cipher_type a; + + if (likely(initialized)) + return; + + initialized = true; + + sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + if (sk < 0) + return; + + memset(&salg, 0, sizeof(salg)); + salg.salg_family = AF_ALG; + strcpy((char *) salg.salg_type, "skcipher"); + + for (c = L_CIPHER_AES; c <= L_CIPHER_DES3_EDE_CBC; c++) { + strcpy((char *) salg.salg_name, cipher_type_to_name(c)); + + if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) + continue; + + supported_ciphers |= 1 << c; + } + + strcpy((char *) salg.salg_type, "aead"); + + for (a = L_AEAD_CIPHER_AES_CCM; a <= L_AEAD_CIPHER_AES_GCM; a++) { + strcpy((char *) salg.salg_name, aead_cipher_type_to_name(a)); + + if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) + continue; + + supported_aead_ciphers |= 1 << a; + } + + close(sk); +} + +LIB_EXPORT bool l_cipher_is_supported(enum l_cipher_type type) +{ + if (!is_valid_type(type)) + return false; + + init_supported(); + + return supported_ciphers & (1 << type); +} + +LIB_EXPORT bool l_aead_cipher_is_supported(enum l_aead_cipher_type type) +{ + if (type != L_AEAD_CIPHER_AES_CCM && type != L_AEAD_CIPHER_AES_GCM) + return false; + + init_supported(); + + return supported_aead_ciphers & (1 << type); +} diff --git a/ell/cipher.h b/ell/cipher.h new file mode 100644 index 0000000..84f2988 --- /dev/null +++ b/ell/cipher.h @@ -0,0 +1,94 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2015 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 + * + */ + +#ifndef __ELL_CIPHER_H +#define __ELL_CIPHER_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct l_cipher; + +enum l_cipher_type { + L_CIPHER_AES = 0, + L_CIPHER_AES_CBC, + L_CIPHER_AES_CTR, + L_CIPHER_ARC4, + L_CIPHER_DES, + L_CIPHER_DES_CBC, + L_CIPHER_DES3_EDE_CBC, +}; + +struct l_cipher *l_cipher_new(enum l_cipher_type type, + const void *key, size_t key_length); + +void l_cipher_free(struct l_cipher *cipher); + +bool l_cipher_encrypt(struct l_cipher *cipher, + const void *in, void *out, size_t len); +bool l_cipher_encryptv(struct l_cipher *cipher, + const struct iovec *in, size_t in_cnt, + const struct iovec *out, size_t out_cnt); + +bool l_cipher_decrypt(struct l_cipher *cipher, + const void *in, void *out, size_t len); +bool l_cipher_decryptv(struct l_cipher *cipher, + const struct iovec *in, size_t in_cnt, + const struct iovec *out, size_t out_cnt); + +bool l_cipher_set_iv(struct l_cipher *cipher, const uint8_t *iv, + size_t iv_length); + +struct l_aead_cipher; + +enum l_aead_cipher_type { + L_AEAD_CIPHER_AES_CCM = 0, + L_AEAD_CIPHER_AES_GCM, +}; + +struct l_aead_cipher *l_aead_cipher_new(enum l_aead_cipher_type type, + const void *key, size_t key_length, + size_t tag_length); + +void l_aead_cipher_free(struct l_aead_cipher *cipher); + +bool l_aead_cipher_encrypt(struct l_aead_cipher *cipher, + const void *in, size_t in_len, + const void *ad, size_t ad_len, + const void *nonce, size_t nonce_len, + void *out, size_t out_len); + +bool l_aead_cipher_decrypt(struct l_aead_cipher *cipher, + const void *in, size_t in_len, + const void *ad, size_t ad_len, + const void *nonce, size_t nonce_len, + void *out, size_t out_len); + +bool l_cipher_is_supported(enum l_cipher_type type); +bool l_aead_cipher_is_supported(enum l_aead_cipher_type type); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_CIPHER_H */ diff --git a/ell/dbus-client.c b/ell/dbus-client.c new file mode 100644 index 0000000..541f3e8 --- /dev/null +++ b/ell/dbus-client.c @@ -0,0 +1,733 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-2014 Intel Corporation. All rights reserved. + * Copyright (C) 2017 Codecoup. 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 +#endif + +#include "dbus.h" +#include "dbus-client.h" +#include "queue.h" +#include "private.h" + +struct l_dbus_client { + struct l_dbus *dbus; + unsigned int watch; + unsigned int added_watch; + unsigned int removed_watch; + char *service; + uint32_t objects_call; + + l_dbus_watch_func_t connect_cb; + void *connect_cb_data; + l_dbus_destroy_func_t connect_cb_data_destroy; + + l_dbus_watch_func_t disconnect_cb; + void *disconnect_cb_data; + l_dbus_destroy_func_t disconnect_cb_data_destroy; + + l_dbus_client_ready_func_t ready_cb; + void *ready_cb_data; + l_dbus_destroy_func_t ready_cb_data_destroy; + + l_dbus_client_proxy_func_t proxy_added_cb; + l_dbus_client_proxy_func_t proxy_removed_cb; + l_dbus_client_property_function_t properties_changed_cb; + void *proxy_cb_data; + l_dbus_destroy_func_t proxy_cb_data_destroy; + + struct l_queue *proxies; +}; + +struct proxy_property { + char *name; + struct l_dbus_message *msg; +}; + +struct l_dbus_proxy { + struct l_dbus_client *client; + char *interface; + char *path; + uint32_t properties_watch; + bool ready; + + struct l_queue *properties; + struct l_queue *pending_calls; +}; + +LIB_EXPORT const char *l_dbus_proxy_get_path(struct l_dbus_proxy *proxy) +{ + if (unlikely(!proxy)) + return NULL; + + return proxy->path; +} + +LIB_EXPORT const char *l_dbus_proxy_get_interface(struct l_dbus_proxy *proxy) +{ + if (unlikely(!proxy)) + return NULL; + + return proxy->interface; +} + +static bool property_match_by_name(const void *a, const void *b) +{ + const struct proxy_property *prop = a; + const char *name = b; + + return !strcmp(prop->name, name); +} + +static struct proxy_property *find_property(struct l_dbus_proxy *proxy, + const char *name) +{ + return l_queue_find(proxy->properties, property_match_by_name, name); +} + +static struct proxy_property *get_property(struct l_dbus_proxy *proxy, + const char *name) +{ + struct proxy_property *prop; + + prop = find_property(proxy, name); + if (prop) + return prop; + + prop = l_new(struct proxy_property, 1); + prop->name = l_strdup(name); + + l_queue_push_tail(proxy->properties, prop); + + return prop; +} + +LIB_EXPORT bool l_dbus_proxy_get_property(struct l_dbus_proxy *proxy, + const char *name, + const char *signature, ...) +{ + struct proxy_property *prop; + va_list args; + bool res; + + if (unlikely(!proxy)) + return false; + + prop = find_property(proxy, name); + if (!prop) + return false; + + va_start(args, signature); + res = l_dbus_message_get_arguments_valist(prop->msg, signature, args); + va_end(args); + + return res; +} + +static void property_free(void *data) +{ + struct proxy_property *prop = data; + + if (prop->msg) + l_dbus_message_unref(prop->msg); + + l_free(prop->name); + l_free(prop); +} + +static void cancel_pending_calls(struct l_dbus_proxy *proxy) +{ + const struct l_queue_entry *entry; + + for (entry = l_queue_get_entries(proxy->pending_calls); entry; + entry = entry->next) { + uint32_t call_id = L_PTR_TO_UINT(entry->data); + + l_dbus_cancel(proxy->client->dbus, call_id); + } +} + +static void dbus_proxy_destroy(struct l_dbus_proxy *proxy) +{ + if (unlikely(!proxy)) + return; + + if (proxy->properties_watch) + l_dbus_remove_signal_watch(proxy->client->dbus, + proxy->properties_watch); + + cancel_pending_calls(proxy); + l_queue_destroy(proxy->pending_calls, NULL); + l_queue_destroy(proxy->properties, property_free); + l_free(proxy->interface); + l_free(proxy->path); + l_free(proxy); +} + +struct method_call_request +{ + struct l_dbus_proxy *proxy; + uint32_t call_id; + l_dbus_message_func_t setup; + l_dbus_client_proxy_result_func_t result; + void *user_data; + l_dbus_destroy_func_t destroy; +}; + +static void method_call_request_free(void *user_data) +{ + struct method_call_request *req = user_data; + + l_queue_remove(req->proxy->pending_calls, L_UINT_TO_PTR(req->call_id)); + + if (req->destroy) + req->destroy(req->user_data); + + l_free(req); +} + +static void method_call_setup(struct l_dbus_message *message, void *user_data) +{ + struct method_call_request *req = user_data; + + if (req->setup) + req->setup(message, req->user_data); + else + l_dbus_message_set_arguments(message, ""); +} + +static void method_call_reply(struct l_dbus_message *message, void *user_data) +{ + struct method_call_request *req = user_data; + + if (req->result) + req->result(req->proxy, message, req->user_data); +} + +LIB_EXPORT bool l_dbus_proxy_set_property(struct l_dbus_proxy *proxy, + l_dbus_client_proxy_result_func_t result, + void *user_data, l_dbus_destroy_func_t destroy, + const char *name, const char *signature, ...) +{ + struct l_dbus_client *client = proxy->client; + struct l_dbus_message_builder *builder; + struct method_call_request *req; + struct l_dbus_message *message; + struct proxy_property *prop; + va_list args; + + if (unlikely(!proxy)) + return false; + + prop = find_property(proxy, name); + if (!prop) + return false; + + if (strcmp(l_dbus_message_get_signature(prop->msg), signature)) + return false; + + message = l_dbus_message_new_method_call(client->dbus, client->service, + proxy->path, + L_DBUS_INTERFACE_PROPERTIES, + "Set"); + if (!message) + return false; + + builder = l_dbus_message_builder_new(message); + if (!builder) { + l_dbus_message_unref(message); + return false; + } + + l_dbus_message_builder_append_basic(builder, 's', proxy->interface); + l_dbus_message_builder_append_basic(builder, 's', name); + + l_dbus_message_builder_enter_variant(builder, signature); + + va_start(args, signature); + l_dbus_message_builder_append_from_valist(builder, signature, args); + va_end(args); + + l_dbus_message_builder_leave_variant(builder); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + req = l_new(struct method_call_request, 1); + req->proxy = proxy; + req->result = result; + req->user_data = user_data; + req->destroy = destroy; + + req->call_id = l_dbus_send_with_reply(client->dbus, message, + method_call_reply, req, + method_call_request_free); + if (!req->call_id) { + l_free(req); + return false; + } + + l_queue_push_tail(proxy->pending_calls, L_UINT_TO_PTR(req->call_id)); + + return true; +} + +LIB_EXPORT uint32_t l_dbus_proxy_method_call(struct l_dbus_proxy *proxy, + const char *method, + l_dbus_message_func_t setup, + l_dbus_client_proxy_result_func_t reply, + void *user_data, + l_dbus_destroy_func_t destroy) +{ + struct method_call_request *req; + + req = l_new(struct method_call_request, 1); + req->proxy = proxy; + req->setup = setup; + req->result = reply; + req->user_data = user_data; + req->destroy = destroy; + + req->call_id = l_dbus_method_call(proxy->client->dbus, + proxy->client->service, + proxy->path, proxy->interface, + method, method_call_setup, + method_call_reply, req, + method_call_request_free); + if (!req->call_id) { + l_free(req); + return 0; + } + + l_queue_push_tail(proxy->pending_calls, L_UINT_TO_PTR(req->call_id)); + + return req->call_id; +} + +static void proxy_update_property(struct l_dbus_proxy *proxy, + const char *name, + struct l_dbus_message_iter *property) +{ + struct l_dbus_message_builder *builder; + struct proxy_property *prop = get_property(proxy, name); + + l_dbus_message_unref(prop->msg); + + if (!property) { + prop->msg = NULL; + goto done; + } + + prop->msg = l_dbus_message_new_signal(proxy->client->dbus, proxy->path, + proxy->interface, name); + if (!prop->msg) + return; + + builder = l_dbus_message_builder_new(prop->msg); + l_dbus_message_builder_append_from_iter(builder, property); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + +done: + if (proxy->client->properties_changed_cb && proxy->ready) + proxy->client->properties_changed_cb(proxy, name, prop->msg, + proxy->client->proxy_cb_data); +} + +static void proxy_invalidate_properties(struct l_dbus_proxy *proxy, + struct l_dbus_message_iter* props) +{ + const char *name; + + while (l_dbus_message_iter_next_entry(props, &name)) + proxy_update_property(proxy, name, NULL); +} + +static void proxy_update_properties(struct l_dbus_proxy *proxy, + struct l_dbus_message_iter* props) +{ + struct l_dbus_message_iter variant; + const char *name; + + while (l_dbus_message_iter_next_entry(props, &name, &variant)) + proxy_update_property(proxy, name, &variant); +} + +static void properties_changed_callback(struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_proxy *proxy = user_data; + const char *interface; + struct l_dbus_message_iter changed; + struct l_dbus_message_iter invalidated; + + if (!l_dbus_message_get_arguments(message, "sa{sv}as", &interface, + &changed, &invalidated)) + return; + + proxy_update_properties(proxy, &changed); + proxy_invalidate_properties(proxy, &invalidated); +} + +static struct l_dbus_proxy *dbus_proxy_new(struct l_dbus_client *client, + const char *path, const char *interface) +{ + struct l_dbus_proxy *proxy = l_new(struct l_dbus_proxy, 1); + + proxy->properties_watch = l_dbus_add_signal_watch(client->dbus, + client->service, path, + L_DBUS_INTERFACE_PROPERTIES, + "PropertiesChanged", + L_DBUS_MATCH_ARGUMENT(0), + interface, L_DBUS_MATCH_NONE, + properties_changed_callback, + proxy); + if (!proxy->properties_watch) { + l_free(proxy); + return NULL; + } + + proxy->client = client; + proxy->interface = l_strdup(interface); + proxy->path = l_strdup(path); + proxy->properties = l_queue_new(); + proxy->pending_calls = l_queue_new();; + + l_queue_push_tail(client->proxies, proxy); + + return proxy; +} + +static bool is_ignorable(const char *interface) +{ + static const struct { + const char *interface; + } interfaces_to_ignore[] = { + { L_DBUS_INTERFACE_OBJECT_MANAGER }, + { L_DBUS_INTERFACE_INTROSPECTABLE }, + { L_DBUS_INTERFACE_PROPERTIES }, + }; + size_t i; + + for (i = 0; i < L_ARRAY_SIZE(interfaces_to_ignore); i++) + if (!strcmp(interfaces_to_ignore[i].interface, interface)) + return true; + + return false; +} + +static struct l_dbus_proxy *find_proxy(struct l_dbus_client *client, + const char *path, const char *interface) +{ + const struct l_queue_entry *entry; + + for (entry = l_queue_get_entries(client->proxies); entry; + entry = entry->next) { + struct l_dbus_proxy *proxy = entry->data; + + if (!strcmp(proxy->interface, interface) && + !strcmp(proxy->path, path)) + return proxy; + } + + return NULL; +} + +static void parse_interface(struct l_dbus_client *client, const char *path, + const char *interface, + struct l_dbus_message_iter *properties) +{ + struct l_dbus_proxy *proxy; + + if (is_ignorable(interface)) + return; + + proxy = find_proxy(client, path, interface); + if (!proxy) + proxy = dbus_proxy_new(client, path, interface); + + if (!proxy) + return; + + proxy_update_properties(proxy, properties); + + if (!proxy->ready) { + proxy->ready = true; + + if (client->proxy_added_cb) + client->proxy_added_cb(proxy, client->proxy_cb_data); + } +} + +static void parse_object(struct l_dbus_client *client, const char *path, + struct l_dbus_message_iter *object) +{ + const char *interface; + struct l_dbus_message_iter properties; + + if (!path) + return; + + while (l_dbus_message_iter_next_entry(object, &interface, &properties)) + parse_interface(client, path, interface, &properties); +} + +static void interfaces_added_callback(struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_client *client = user_data; + struct l_dbus_message_iter object; + const char *path; + + if (!l_dbus_message_get_arguments(message, "oa{sa{sv}}", &path, + &object)) + return; + + parse_object(client, path, &object); +} + +static void interfaces_removed_callback(struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_client *client = user_data; + struct l_dbus_message_iter interfaces; + const char *interface; + const char *path; + + if (!l_dbus_message_get_arguments(message, "oas", &path, &interfaces)) + return; + + while (l_dbus_message_iter_next_entry(&interfaces, &interface)) { + struct l_dbus_proxy *proxy; + + proxy = find_proxy(client, path, interface); + if (!proxy) + continue; + + l_queue_remove(proxy->client->proxies, proxy); + + if (client->proxy_removed_cb) + client->proxy_removed_cb(proxy, client->proxy_cb_data); + + dbus_proxy_destroy(proxy); + } +} + +static void get_managed_objects_reply(struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_client *client = user_data; + struct l_dbus_message_iter objects; + struct l_dbus_message_iter object; + const char *path; + + client->objects_call = 0; + + if (l_dbus_message_is_error(message)) + return; + + if (!l_dbus_message_get_arguments(message, "a{oa{sa{sv}}}", &objects)) + return; + + while (l_dbus_message_iter_next_entry(&objects, &path, &object)) + parse_object(client, path, &object); + + client->added_watch = l_dbus_add_signal_watch(client->dbus, + client->service, "/", + L_DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesAdded", + L_DBUS_MATCH_NONE, + interfaces_added_callback, + client); + + client->removed_watch = l_dbus_add_signal_watch(client->dbus, + client->service, "/", + L_DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesRemoved", + L_DBUS_MATCH_NONE, + interfaces_removed_callback, + client); + + if (client->ready_cb) + client->ready_cb(client, client->ready_cb_data); +} + +static void service_appeared_callback(struct l_dbus *dbus, void *user_data) +{ + struct l_dbus_client *client = user_data; + + /* TODO should we allow to set different root? */ + client->objects_call = l_dbus_method_call(dbus, client->service, "/", + L_DBUS_INTERFACE_OBJECT_MANAGER, + "GetManagedObjects", NULL, + get_managed_objects_reply, + client, NULL); + + if (client->connect_cb) + client->connect_cb(client->dbus, client->connect_cb_data); +} + +static void service_disappeared_callback(struct l_dbus *dbus, void *user_data) +{ + struct l_dbus_client *client = user_data; + + if (client->disconnect_cb) + client->disconnect_cb(client->dbus, client->disconnect_cb_data); + + l_queue_clear(client->proxies, + (l_queue_destroy_func_t)dbus_proxy_destroy); +} + +LIB_EXPORT struct l_dbus_client *l_dbus_client_new(struct l_dbus *dbus, + const char *service, const char *path) +{ + struct l_dbus_client *client = l_new(struct l_dbus_client, 1); + + client->dbus = dbus; + + client->watch = l_dbus_add_service_watch(dbus, service, + service_appeared_callback, + service_disappeared_callback, + client, NULL); + + if (!client->watch) { + l_free(client); + return NULL; + } + + client->service = l_strdup(service); + client->proxies = l_queue_new(); + + return client; +} + +LIB_EXPORT void l_dbus_client_destroy(struct l_dbus_client *client) +{ + if (unlikely(!client)) + return; + + if (client->watch) + l_dbus_remove_signal_watch(client->dbus, client->watch); + + if (client->added_watch) + l_dbus_remove_signal_watch(client->dbus, client->added_watch); + + if (client->removed_watch) + l_dbus_remove_signal_watch(client->dbus, client->removed_watch); + + if (client->connect_cb_data_destroy) + client->connect_cb_data_destroy(client->connect_cb_data); + + if (client->disconnect_cb_data_destroy) + client->disconnect_cb_data_destroy(client->disconnect_cb_data); + + if (client->ready_cb_data_destroy) + client->ready_cb_data_destroy(client->ready_cb_data); + + if (client->proxy_cb_data_destroy) + client->proxy_cb_data_destroy(client->proxy_cb_data); + + if (client->objects_call) + l_dbus_cancel(client->dbus, client->objects_call) +; + l_queue_destroy(client->proxies, + (l_queue_destroy_func_t)dbus_proxy_destroy); + + l_free(client->service); + l_free(client); +} + +LIB_EXPORT bool l_dbus_client_set_connect_handler(struct l_dbus_client *client, + l_dbus_watch_func_t function, + void *user_data, + l_dbus_destroy_func_t destroy) +{ + if (unlikely(!client)) + return false; + + if (client->connect_cb_data_destroy) + client->connect_cb_data_destroy(client->connect_cb_data); + + client->connect_cb = function; + client->connect_cb_data = user_data; + client->connect_cb_data_destroy = destroy; + + return true; +} + +LIB_EXPORT bool l_dbus_client_set_disconnect_handler(struct l_dbus_client *client, + l_dbus_watch_func_t function, + void *user_data, + l_dbus_destroy_func_t destroy) +{ + if (unlikely(!client)) + return false; + + if(client->disconnect_cb_data_destroy) + client->disconnect_cb_data_destroy(client->disconnect_cb_data); + + client->disconnect_cb = function; + client->disconnect_cb_data = user_data; + client->disconnect_cb_data_destroy = destroy; + + return true; +} + +LIB_EXPORT bool l_dbus_client_set_ready_handler(struct l_dbus_client *client, + l_dbus_client_ready_func_t function, + void *user_data, + l_dbus_destroy_func_t destroy) +{ + if (unlikely(!client)) + return false; + + if (client->ready_cb_data_destroy) + client->ready_cb_data_destroy(client->ready_cb_data); + + client->ready_cb = function; + client->ready_cb_data = user_data; + client->ready_cb_data_destroy = destroy; + + return true; +} + +LIB_EXPORT bool l_dbus_client_set_proxy_handlers(struct l_dbus_client *client, + l_dbus_client_proxy_func_t proxy_added, + l_dbus_client_proxy_func_t proxy_removed, + l_dbus_client_property_function_t property_changed, + void *user_data, l_dbus_destroy_func_t destroy) +{ + if (unlikely(!client)) + return false; + + if (client->proxy_cb_data_destroy) + client->proxy_cb_data_destroy(client->proxy_cb_data); + + client->proxy_added_cb = proxy_added; + client->proxy_removed_cb = proxy_removed; + client->properties_changed_cb = property_changed; + client->proxy_cb_data = user_data; + client->proxy_cb_data_destroy = destroy; + + return true; +} diff --git a/ell/dbus-client.h b/ell/dbus-client.h new file mode 100644 index 0000000..699d824 --- /dev/null +++ b/ell/dbus-client.h @@ -0,0 +1,98 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-2014 Intel Corporation. All rights reserved. + * Copyright (C) 2017 Codecoup. 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 + * + */ + +#ifndef __ELL_DBUS_CLIENT_H +#define __ELL_DBUS_CLIENT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct l_dbus; +struct l_dbus_message; +struct l_dbus_client; +struct l_dbus_proxy; + +typedef void (*l_dbus_client_ready_func_t)(struct l_dbus_client *client, + void *user_data); +typedef void (*l_dbus_client_proxy_func_t) (struct l_dbus_proxy *proxy, + void *user_data); +typedef void (*l_dbus_client_proxy_result_func_t) (struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data); +typedef void (*l_dbus_client_property_function_t) (struct l_dbus_proxy *proxy, + const char *name, + struct l_dbus_message *msg, + void *user_data); + +struct l_dbus_client *l_dbus_client_new(struct l_dbus *dbus, + const char *service, const char *path); +void l_dbus_client_destroy(struct l_dbus_client *client); + +bool l_dbus_client_set_connect_handler(struct l_dbus_client *client, + l_dbus_watch_func_t function, + void *user_data, + l_dbus_destroy_func_t destroy); + +bool l_dbus_client_set_disconnect_handler(struct l_dbus_client *client, + l_dbus_watch_func_t function, + void *user_data, + l_dbus_destroy_func_t destroy); + +bool l_dbus_client_set_ready_handler(struct l_dbus_client *client, + l_dbus_client_ready_func_t function, + void *user_data, + l_dbus_destroy_func_t destroy); + +bool l_dbus_client_set_proxy_handlers(struct l_dbus_client *client, + l_dbus_client_proxy_func_t proxy_added, + l_dbus_client_proxy_func_t proxy_removed, + l_dbus_client_property_function_t property_changed, + void *user_data, l_dbus_destroy_func_t destroy); + +const char *l_dbus_proxy_get_path(struct l_dbus_proxy *proxy); + +const char *l_dbus_proxy_get_interface(struct l_dbus_proxy *proxy); + +bool l_dbus_proxy_get_property(struct l_dbus_proxy *proxy, const char *name, + const char *signature, ...); + +bool l_dbus_proxy_set_property(struct l_dbus_proxy *proxy, + l_dbus_client_proxy_result_func_t result, + void *user_data, l_dbus_destroy_func_t destroy, + const char *name, const char *signature, ...); + +uint32_t l_dbus_proxy_method_call(struct l_dbus_proxy *proxy, + const char *method, + l_dbus_message_func_t setup, + l_dbus_client_proxy_result_func_t reply, + void *user_data, + l_dbus_destroy_func_t destroy); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_DBUS_CLIENT_H */ diff --git a/ell/dbus-filter.c b/ell/dbus-filter.c new file mode 100644 index 0000000..0af1b67 --- /dev/null +++ b/ell/dbus-filter.c @@ -0,0 +1,421 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2016 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 +#endif + +#define _GNU_SOURCE +#include +#include + +#include "util.h" +#include "queue.h" +#include "hashmap.h" +#include "string.h" +#include "dbus.h" +#include "dbus-private.h" +#include "gvariant-private.h" +#include "private.h" + +#define NODE_TYPE_CALLBACK L_DBUS_MATCH_NONE + +struct filter_node { + enum l_dbus_match_type type; + union { + struct { + char *value; + struct filter_node *children; + bool remote_rule; + } match; + struct { + l_dbus_message_func_t func; + void *user_data; + } callback; + }; + unsigned int id; + struct filter_node *next; +}; + +struct _dbus_filter { + struct l_dbus *dbus; + struct filter_node *root; + unsigned int signal_id; + unsigned int last_id; + const struct _dbus_filter_ops *driver; + struct _dbus_name_cache *name_cache; +}; + +static void filter_subtree_free(struct filter_node *node) +{ + struct filter_node *child, *next; + + if (node->type == NODE_TYPE_CALLBACK) { + l_free(node); + return; + } + + next = node->match.children; + + l_free(node->match.value); + l_free(node); + + while (next) { + child = next; + next = child->next; + + filter_subtree_free(child); + } +} + +static void dbus_filter_destroy(void *data) +{ + struct _dbus_filter *filter = data; + + if (filter->root) + filter_subtree_free(filter->root); + + l_free(filter); +} + +static void filter_dispatch_match_recurse(struct _dbus_filter *filter, + struct filter_node *node, + struct l_dbus_message *message) +{ + const char *value = NULL; + const char *alt_value = NULL; + struct filter_node *child; + + switch ((int) node->type) { + case NODE_TYPE_CALLBACK: + node->callback.func(message, node->callback.user_data); + return; + + case L_DBUS_MATCH_SENDER: + value = l_dbus_message_get_sender(message); + break; + + case L_DBUS_MATCH_TYPE: + value = _dbus_message_get_type_as_string(message); + break; + + case L_DBUS_MATCH_PATH: + value = l_dbus_message_get_path(message); + break; + + case L_DBUS_MATCH_INTERFACE: + value = l_dbus_message_get_interface(message); + break; + + case L_DBUS_MATCH_MEMBER: + value = l_dbus_message_get_member(message); + break; + + case L_DBUS_MATCH_ARG0...(L_DBUS_MATCH_ARG0 + 63): + value = _dbus_message_get_nth_string_argument(message, + node->type - L_DBUS_MATCH_ARG0); + break; + } + + if (!value) + return; + + if (node->type == L_DBUS_MATCH_SENDER && filter->name_cache) + alt_value = _dbus_name_cache_lookup(filter->name_cache, + node->match.value); + + if (strcmp(value, node->match.value) && + (!alt_value || strcmp(value, alt_value))) + return; + + for (child = node->match.children; child; child = child->next) + filter_dispatch_match_recurse(filter, child, message); +} + +void _dbus_filter_dispatch(struct l_dbus_message *message, void *user_data) +{ + struct _dbus_filter *filter = user_data; + + filter_dispatch_match_recurse(filter, filter->root, message); +} + +struct _dbus_filter *_dbus_filter_new(struct l_dbus *dbus, + const struct _dbus_filter_ops *driver, + struct _dbus_name_cache *name_cache) +{ + struct _dbus_filter *filter; + + filter = l_new(struct _dbus_filter, 1); + + filter->dbus = dbus; + filter->driver = driver; + filter->name_cache = name_cache; + + if (!filter->driver->skip_register) + filter->signal_id = l_dbus_register(dbus, _dbus_filter_dispatch, + filter, + dbus_filter_destroy); + + return filter; +} + +void _dbus_filter_free(struct _dbus_filter *filter) +{ + if (!filter) + return; + + if (!filter->driver->skip_register) + l_dbus_unregister(filter->dbus, filter->signal_id); + else + dbus_filter_destroy(filter); +} + +static int condition_compare(const void *a, const void *b) +{ + const struct _dbus_filter_condition *condition_a = a, *condition_b = b; + + return condition_a->type - condition_b->type; +} + +static bool remove_recurse(struct _dbus_filter *filter, + struct filter_node **node, unsigned int id) +{ + struct filter_node *tmp; + + for (; *node; node = &(*node)->next) { + if ((*node)->type == NODE_TYPE_CALLBACK && (*node)->id == id) + break; + + if ((*node)->type != NODE_TYPE_CALLBACK && + remove_recurse(filter, &(*node)->match.children, + id)) + break; + } + + if (!*node) + return false; + + if ((*node)->type == NODE_TYPE_CALLBACK || !(*node)->match.children) { + tmp = *node; + *node = tmp->next; + + if (tmp->match.remote_rule) + filter->driver->remove_match(filter->dbus, tmp->id); + + if (tmp->type == L_DBUS_MATCH_SENDER && filter->name_cache && + !_dbus_parse_unique_name(tmp->match.value, + NULL)) + _dbus_name_cache_remove(filter->name_cache, + tmp->match.value); + + filter_subtree_free(tmp); + } + + return true; +} + +unsigned int _dbus_filter_add_rule(struct _dbus_filter *filter, + const struct _dbus_filter_condition *rule, + int rule_len, + l_dbus_message_func_t signal_func, + void *user_data) +{ + struct filter_node **node_ptr = &filter->root; + struct filter_node *node; + struct filter_node *parent = filter->root; + bool remote_rule = false; + struct _dbus_filter_condition sorted[rule_len]; + struct _dbus_filter_condition *unused, *condition; + struct _dbus_filter_condition *end = sorted + rule_len; + + memcpy(sorted, rule, sizeof(sorted)); + qsort(sorted, rule_len, sizeof(*condition), condition_compare); + + /* + * Find or create a path in the tree with a node for each + * condition in the rule, loop until all conditions have been + * used. + */ + unused = sorted; + while (unused < end) { + /* + * Find a child of the node that matches any unused + * condition. Note there could be multiple matches, we're + * happy with the first we can find. + */ + while (*node_ptr) { + node = *node_ptr; + + for (condition = unused; condition < end; condition++) { + if (condition->type > node->type) { + condition = end; + break; + } + + if (condition->type < node->type || + condition->type == + L_DBUS_MATCH_NONE) + continue; + + if (!strcmp(node->match.value, + condition->value)) + break; + } + + if (condition < end) + break; + + node_ptr = &node->next; + } + + /* Add a node */ + if (!*node_ptr) { + condition = unused; + + node = l_new(struct filter_node, 1); + node->type = condition->type; + node->match.value = l_strdup(condition->value); + + *node_ptr = node; + + if (node->type == L_DBUS_MATCH_SENDER && + filter->name_cache && + !_dbus_parse_unique_name( + node->match.value, + NULL)) + _dbus_name_cache_add(filter->name_cache, + node->match.value); + + } + + /* + * Mark the condition used. We do this by setting + * condition->type to an invalid value unless it is the + * first condition left in which case we can push the + * rule start. Another option is to always push the rule + * start and memmove the still unused conditions by one + * if necessary. + */ + condition->type = L_DBUS_MATCH_NONE; + while (unused < end && unused[0].type == L_DBUS_MATCH_NONE) + unused++; + + node_ptr = &node->match.children; + + parent = node; + + /* + * Only have to call AddMatch if none of the parent nodes + * have yet created an AddMatch rule on the server. + */ + remote_rule |= node->match.remote_rule; + } + + node = l_new(struct filter_node, 1); + node->type = NODE_TYPE_CALLBACK; + node->callback.func = signal_func; + node->callback.user_data = user_data; + node->id = ++filter->last_id; + node->next = *node_ptr; + + *node_ptr = node; + + if (!remote_rule) { + if (!filter->driver->add_match(filter->dbus, node->id, + rule, rule_len)) + goto err; + + parent->id = node->id; + parent->match.remote_rule = true; + } + + return node->id; + +err: + /* Remove all the nodes we may have added */ + node->id = (unsigned int) -1; + remove_recurse(filter, &filter->root, node->id); + + return 0; +} + +bool _dbus_filter_remove_rule(struct _dbus_filter *filter, unsigned int id) +{ + return remove_recurse(filter, &filter->root, id); +} + +char *_dbus_filter_rule_to_str(const struct _dbus_filter_condition *rule, + int rule_len) +{ + struct l_string *str = l_string_new(63); + char *key, arg_buf[6]; + const char *value, *endp; + + for (; rule_len; rule++, rule_len--) { + switch ((int) rule->type) { + case L_DBUS_MATCH_SENDER: + key = "sender"; + break; + case L_DBUS_MATCH_TYPE: + key = "type"; + break; + case L_DBUS_MATCH_PATH: + key = "path"; + break; + case L_DBUS_MATCH_INTERFACE: + key = "interface"; + break; + case L_DBUS_MATCH_MEMBER: + key = "member"; + break; + case L_DBUS_MATCH_ARG0...(L_DBUS_MATCH_ARG0 + 63): + key = arg_buf; + snprintf(arg_buf, sizeof(arg_buf), "arg%i", + rule->type - L_DBUS_MATCH_ARG0); + break; + default: + l_string_free(str); + return NULL; + } + + l_string_append(str, key); + l_string_append(str, "='"); + + /* We only need to escape single-quotes in the values */ + value = rule->value; + + while ((endp = strchr(value, '\''))) { + l_string_append_fixed(str, value, endp - value); + l_string_append(str, "'\\''"); + + value = endp + 1; + } + + l_string_append(str, value); + l_string_append_c(str, '\''); + + if (rule_len > 1) + l_string_append_c(str, ','); + } + + return l_string_unwrap(str); +} diff --git a/ell/dbus-message.c b/ell/dbus-message.c new file mode 100644 index 0000000..32f0428 --- /dev/null +++ b/ell/dbus-message.c @@ -0,0 +1,1989 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "util.h" +#include "private.h" +#include "dbus.h" +#include "dbus-private.h" +#include "gvariant-private.h" + +#define DBUS_MESSAGE_LITTLE_ENDIAN ('l') +#define DBUS_MESSAGE_BIG_ENDIAN ('B') + +#define DBUS_MESSAGE_PROTOCOL_VERSION 1 + +#define DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED 0x01 +#define DBUS_MESSAGE_FLAG_NO_AUTO_START 0x02 + +#define DBUS_MESSAGE_FIELD_PATH 1 +#define DBUS_MESSAGE_FIELD_INTERFACE 2 +#define DBUS_MESSAGE_FIELD_MEMBER 3 +#define DBUS_MESSAGE_FIELD_ERROR_NAME 4 +#define DBUS_MESSAGE_FIELD_REPLY_SERIAL 5 +#define DBUS_MESSAGE_FIELD_DESTINATION 6 +#define DBUS_MESSAGE_FIELD_SENDER 7 +#define DBUS_MESSAGE_FIELD_SIGNATURE 8 +#define DBUS_MESSAGE_FIELD_UNIX_FDS 9 + +#define DBUS_MAX_NESTING 32 + +struct l_dbus_message { + int refcount; + void *header; + size_t header_size; + size_t header_end; + char *signature; + void *body; + size_t body_size; + char *path; + char *interface; + char *member; + char *error_name; + uint32_t reply_serial; + char *destination; + char *sender; + int fds[16]; + uint32_t num_fds; + + bool sealed : 1; + bool signature_free : 1; +}; + +struct l_dbus_message_builder { + struct l_dbus_message *message; + struct dbus_builder *builder; + struct builder_driver *driver; +}; + +static inline bool _dbus_message_is_gvariant(struct l_dbus_message *msg) +{ + struct dbus_header *hdr = msg->header; + + return hdr->version == 2; +} + +void *_dbus_message_get_header(struct l_dbus_message *msg, size_t *out_size) +{ + if (out_size) + *out_size = msg->header_size; + + return msg->header; +} + +void *_dbus_message_get_body(struct l_dbus_message *msg, size_t *out_size) +{ + if (out_size) + *out_size = msg->body_size; + + return msg->body; +} + +/* Get a buffer containing the final message contents except the header */ +void *_dbus_message_get_footer(struct l_dbus_message *msg, size_t *out_size) +{ + size_t size; + + if (_dbus_message_is_gvariant(msg)) { + size = _gvariant_message_finalize(msg->header_end, + msg->body, msg->body_size, + msg->signature); + size -= msg->header_size; + } else + size = msg->body_size; + + if (out_size) + *out_size = size; + + return msg->body; +} + +int *_dbus_message_get_fds(struct l_dbus_message *msg, uint32_t *num_fds) +{ + *num_fds = msg->num_fds; + + return msg->fds; +} + +void _dbus_message_set_serial(struct l_dbus_message *msg, uint32_t serial) +{ + struct dbus_header *hdr = msg->header; + + hdr->dbus1.serial = serial; +} + +uint32_t _dbus_message_get_serial(struct l_dbus_message *msg) +{ + struct dbus_header *hdr = msg->header; + + return hdr->dbus1.serial; +} + +LIB_EXPORT bool l_dbus_message_set_no_reply(struct l_dbus_message *msg, bool on) +{ + struct dbus_header *hdr; + + if (unlikely(!msg)) + return false; + + hdr = msg->header; + + if (on) + hdr->flags |= DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED; + else + hdr->flags &= ~DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED; + + return true; +} + +LIB_EXPORT bool l_dbus_message_get_no_reply(struct l_dbus_message *msg) +{ + struct dbus_header *hdr; + + if (unlikely(!msg)) + return false; + + hdr = msg->header; + + if (hdr->flags & DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED) + return true; + + return false; +} + +LIB_EXPORT bool l_dbus_message_set_no_autostart(struct l_dbus_message *msg, + bool on) +{ + struct dbus_header *hdr; + + if (unlikely(!msg)) + return false; + + hdr = msg->header; + + if (on) + hdr->flags |= DBUS_MESSAGE_FLAG_NO_AUTO_START; + else + hdr->flags &= ~DBUS_MESSAGE_FLAG_NO_AUTO_START; + + return true; +} + +LIB_EXPORT bool l_dbus_message_get_no_autostart(struct l_dbus_message *msg) +{ + struct dbus_header *hdr; + + if (unlikely(!msg)) + return false; + + hdr = msg->header; + + if (hdr->flags & DBUS_MESSAGE_FLAG_NO_AUTO_START) + return true; + + return false; + +} + +static struct l_dbus_message *message_new_common(uint8_t type, uint8_t flags, + uint8_t version) +{ + struct l_dbus_message *message; + struct dbus_header *hdr; + + message = l_new(struct l_dbus_message, 1); + message->refcount = 1; + + /* + * We allocate the header with the initial 12 bytes (up to the field + * length) so that we can store the basic information here. For + * GVariant we need 16 bytes. + */ + message->header_size = version == 1 ? 12 : 16; + message->header_end = message->header_size; + message->header = l_realloc(NULL, message->header_size); + + hdr = message->header; + hdr->endian = DBUS_NATIVE_ENDIAN; + hdr->message_type = type; + hdr->flags = flags; + hdr->version = version; + + return message; +} + +struct l_dbus_message *_dbus_message_new_method_call(uint8_t version, + const char *destination, + const char *path, + const char *interface, + const char *method) +{ + struct l_dbus_message *message; + + message = message_new_common(DBUS_MESSAGE_TYPE_METHOD_CALL, 0, version); + + message->destination = l_strdup(destination); + message->path = l_strdup(path); + message->interface = l_strdup(interface); + message->member = l_strdup(method); + + return message; +} + +LIB_EXPORT struct l_dbus_message *l_dbus_message_new_method_call( + struct l_dbus *dbus, + const char *destination, + const char *path, + const char *interface, + const char *method) +{ + uint8_t version; + + if (unlikely(!dbus)) + return NULL; + + version = _dbus_get_version(dbus); + + return _dbus_message_new_method_call(version, destination, path, + interface, method); +} + +struct l_dbus_message *_dbus_message_new_signal(uint8_t version, + const char *path, + const char *interface, + const char *name) +{ + struct l_dbus_message *message; + + message = message_new_common(DBUS_MESSAGE_TYPE_SIGNAL, + DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, + version); + + message->path = l_strdup(path); + message->interface = l_strdup(interface); + message->member = l_strdup(name); + + return message; +} + +LIB_EXPORT struct l_dbus_message *l_dbus_message_new_signal(struct l_dbus *dbus, + const char *path, + const char *interface, + const char *name) +{ + uint8_t version; + + if (unlikely(!dbus)) + return NULL; + + version = _dbus_get_version(dbus); + + return _dbus_message_new_signal(version, path, interface, name); +} + +LIB_EXPORT struct l_dbus_message *l_dbus_message_new_method_return( + struct l_dbus_message *method_call) +{ + struct l_dbus_message *message; + struct dbus_header *hdr = method_call->header; + const char *sender; + + message = message_new_common(DBUS_MESSAGE_TYPE_METHOD_RETURN, + DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, + hdr->version); + + if (!l_dbus_message_get_no_reply(method_call)) + message->reply_serial = _dbus_message_get_serial(method_call); + + sender = l_dbus_message_get_sender(method_call); + if (sender) + message->destination = l_strdup(sender); + + return message; +} + +struct l_dbus_message *_dbus_message_new_error(uint8_t version, + uint32_t reply_serial, + const char *destination, + const char *name, + const char *error) +{ + struct l_dbus_message *reply; + + if (!_dbus_valid_interface(name)) + return NULL; + + reply = message_new_common(DBUS_MESSAGE_TYPE_ERROR, + DBUS_MESSAGE_FLAG_NO_REPLY_EXPECTED, + version); + + reply->error_name = l_strdup(name); + reply->destination = l_strdup(destination); + reply->reply_serial = reply_serial; + + if (!l_dbus_message_set_arguments(reply, "s", error)) { + l_dbus_message_unref(reply); + return NULL; + } + + return reply; +} + +LIB_EXPORT struct l_dbus_message *l_dbus_message_new_error_valist( + struct l_dbus_message *method_call, + const char *name, + const char *format, va_list args) +{ + char str[1024]; + struct dbus_header *hdr = method_call->header; + uint32_t reply_serial = 0; + + vsnprintf(str, sizeof(str), format, args); + + if (!l_dbus_message_get_no_reply(method_call)) + reply_serial = _dbus_message_get_serial(method_call); + + return _dbus_message_new_error(hdr->version, reply_serial, + l_dbus_message_get_sender(method_call), + name, str); +} + +LIB_EXPORT struct l_dbus_message *l_dbus_message_new_error( + struct l_dbus_message *method_call, + const char *name, + const char *format, ...) +{ + va_list args; + struct l_dbus_message *reply; + + va_start(args, format); + reply = l_dbus_message_new_error_valist(method_call, name, + format, args); + va_end(args); + + return reply; +} + +LIB_EXPORT struct l_dbus_message *l_dbus_message_ref(struct l_dbus_message *message) +{ + if (unlikely(!message)) + return NULL; + + __sync_fetch_and_add(&message->refcount, 1); + + return message; +} + +LIB_EXPORT void l_dbus_message_unref(struct l_dbus_message *message) +{ + unsigned int i; + + if (unlikely(!message)) + return; + + if (__sync_sub_and_fetch(&message->refcount, 1)) + return; + + for (i = 0; i < message->num_fds; i++) + close(message->fds[i]); + + if (!message->sealed) { + l_free(message->destination); + l_free(message->path); + l_free(message->interface); + l_free(message->member); + l_free(message->error_name); + l_free(message->sender); + } + + if (message->signature_free) + l_free(message->signature); + + l_free(message->header); + l_free(message->body); + l_free(message); +} + +const char *_dbus_message_get_nth_string_argument( + struct l_dbus_message *message, int n) +{ + struct l_dbus_message_iter iter; + const char *signature, *value; + void *body; + size_t size; + char type; + bool (*skip_entry)(struct l_dbus_message_iter *); + bool (*get_basic)(struct l_dbus_message_iter *, char, void *); + + signature = l_dbus_message_get_signature(message); + body = _dbus_message_get_body(message, &size); + + if (!signature) + return NULL; + + if (_dbus_message_is_gvariant(message)) { + if (!_gvariant_iter_init(&iter, message, signature, NULL, + body, size)) + return NULL; + + skip_entry = _gvariant_iter_skip_entry; + get_basic = _gvariant_iter_next_entry_basic; + } else { + _dbus1_iter_init(&iter, message, signature, NULL, body, size); + + skip_entry = _dbus1_iter_skip_entry; + get_basic = _dbus1_iter_next_entry_basic; + } + + while (n--) + if (!skip_entry(&iter)) + return NULL; + + if (!iter.sig_start) + return NULL; + + type = iter.sig_start[iter.sig_pos]; + if (!strchr("sog", type)) + return NULL; + + if (!get_basic(&iter, type, &value)) + return NULL; + + return value; +} + +static bool message_iter_next_entry_valist(struct l_dbus_message_iter *orig, + va_list args) +{ + static const char *simple_types = "sogybnqiuxtd"; + struct l_dbus_message_iter *iter = orig; + const char *signature = orig->sig_start + orig->sig_pos; + const char *end; + struct l_dbus_message_iter *sub_iter; + struct l_dbus_message_iter stack[DBUS_MAX_NESTING]; + unsigned int indent = 0; + uint32_t uint32_val; + int fd; + void *arg; + bool (*get_basic)(struct l_dbus_message_iter *, char ,void *); + bool (*enter_struct)(struct l_dbus_message_iter *, + struct l_dbus_message_iter *); + bool (*enter_array)(struct l_dbus_message_iter *, + struct l_dbus_message_iter *); + bool (*enter_variant)(struct l_dbus_message_iter *, + struct l_dbus_message_iter *); + + if (_dbus_message_is_gvariant(orig->message)) { + get_basic = _gvariant_iter_next_entry_basic; + enter_struct = _gvariant_iter_enter_struct; + enter_array = _gvariant_iter_enter_array; + enter_variant = _gvariant_iter_enter_variant; + } else { + get_basic = _dbus1_iter_next_entry_basic; + enter_struct = _dbus1_iter_enter_struct; + enter_array = _dbus1_iter_enter_array; + enter_variant = _dbus1_iter_enter_variant; + } + + while (signature < orig->sig_start + orig->sig_len) { + if (strchr(simple_types, *signature)) { + arg = va_arg(args, void *); + if (!get_basic(iter, *signature, arg)) + return false; + + signature += 1; + continue; + } + + switch (*signature) { + case 'h': + if (!get_basic(iter, 'h', &uint32_val)) + return false; + + if (uint32_val < iter->message->num_fds) + fd = fcntl(iter->message->fds[uint32_val], + F_DUPFD_CLOEXEC, 3); + else + fd = -1; + + *va_arg(args, int *) = fd; + signature += 1; + break; + case '(': + case '{': + signature += 1; + indent += 1; + + if (indent > DBUS_MAX_NESTING) + return false; + + if (!enter_struct(iter, &stack[indent - 1])) + return false; + + iter = &stack[indent - 1]; + + break; + case ')': + case '}': + /* + * Sanity check in case of an unmatched paren/brace + * that isn't caught elsewhere. + */ + if (unlikely(indent == 0)) + return false; + + signature += 1; + indent -= 1; + + if (indent == 0) + iter = orig; + else + iter = &stack[indent - 1]; + break; + case 'a': + sub_iter = va_arg(args, void *); + + if (!enter_array(iter, sub_iter)) + return false; + + end = _dbus_signature_end(signature + 1); + signature = end + 1; + break; + case 'v': + sub_iter = va_arg(args, void *); + + if (!enter_variant(iter, sub_iter)) + return false; + + signature += 1; + break; + default: + return false; + } + } + + return true; +} + +static inline bool message_iter_next_entry(struct l_dbus_message_iter *iter, + ...) +{ + va_list args; + bool result; + + va_start(args, iter); + result = message_iter_next_entry_valist(iter, args); + va_end(args); + + return result; +} + +static bool get_header_field_from_iter_valist(struct l_dbus_message *message, + uint8_t type, char data_type, + va_list args) +{ + struct l_dbus_message_iter header; + struct l_dbus_message_iter array, iter; + uint8_t endian, message_type, flags, version; + uint32_t body_length, serial; + bool found; + + if (!message->sealed) + return false; + + if (_dbus_message_is_gvariant(message)) { + uint64_t field_type; + + if (!_gvariant_iter_init(&header, message, "a(tv)", NULL, + message->header + 16, + message->header_end - 16)) + return false; + + if (!_gvariant_iter_enter_array(&header, &array)) + return false; + + while ((found = message_iter_next_entry(&array, + &field_type, &iter))) + if (field_type == type) + break; + } else { + uint8_t field_type; + + _dbus1_iter_init(&header, message, "yyyyuua(yv)", NULL, + message->header, message->header_size); + + if (!message_iter_next_entry(&header, &endian, + &message_type, &flags, &version, + &body_length, &serial, &array)) + return false; + + while ((found = message_iter_next_entry(&array, + &field_type, &iter))) + if (field_type == type) + break; + } + + if (!found) + return false; + + if (iter.sig_start[iter.sig_pos] != data_type) + return false; + + return message_iter_next_entry_valist(&iter, args); +} + +static inline bool get_header_field(struct l_dbus_message *message, + uint8_t type, int data_type, ...) +{ + va_list args; + bool result; + + va_start(args, data_type); + result = get_header_field_from_iter_valist(message, type, data_type, + args); + va_end(args); + + return result; +} + +static bool valid_header(const struct dbus_header *hdr) +{ + if (hdr->endian != DBUS_MESSAGE_LITTLE_ENDIAN && + hdr->endian != DBUS_MESSAGE_BIG_ENDIAN) + return false; + + if (hdr->message_type < DBUS_MESSAGE_TYPE_METHOD_CALL || + hdr->message_type > DBUS_MESSAGE_TYPE_SIGNAL) + return false; + + if (hdr->version != 1 && hdr->version != 2) + return false; + + if (hdr->version == 1) { + if (hdr->dbus1.serial == 0) + return false; + } + + return true; +} + +unsigned int _dbus_message_unix_fds_from_header(const void *data, size_t size) +{ + struct l_dbus_message message; + uint32_t unix_fds; + + message.header = (uint8_t *) data; + message.header_size = size; + message.body_size = 0; + message.sealed = true; + + if (!get_header_field(&message, DBUS_MESSAGE_FIELD_UNIX_FDS, + 'u', &unix_fds)) + return 0; + + return unix_fds; +} + +struct l_dbus_message *dbus_message_from_blob(const void *data, size_t size, + int fds[], uint32_t num_fds) +{ + const struct dbus_header *hdr = data; + struct l_dbus_message *message; + size_t body_pos; + unsigned int i; + + if (unlikely(size < DBUS_HEADER_SIZE)) + return NULL; + + message = l_new(struct l_dbus_message, 1); + + message->refcount = 1; + + if (hdr->version == 1) { + message->header_size = align_len(DBUS_HEADER_SIZE + + hdr->dbus1.field_length, 8); + message->body_size = hdr->dbus1.body_length; + + if (message->header_size + message->body_size < size) + goto free; + + body_pos = message->header_size; + } else { + struct l_dbus_message_iter iter; + struct l_dbus_message_iter header, variant, body; + + /* + * GVariant message structure as per + * https://wiki.gnome.org/Projects/GLib/GDBus/Version2 + * is "(yyyyuta{tv}v)". As noted this is equivalent to + * some other types, this one lets us get iterators for + * the header and the body in the fewest steps. + */ + if (!_gvariant_iter_init(&iter, message, "(yyyyuta{tv})v", + NULL, data, size)) + goto free; + + if (!_gvariant_iter_enter_struct(&iter, &header)) + goto free; + + if (!_gvariant_iter_enter_variant(&iter, &variant)) + goto free; + + if (!_gvariant_iter_enter_struct(&variant, &body)) + goto free; + + message->header_size = align_len(header.len - header.pos, 8); + message->body_size = body.len - body.pos; + message->signature = l_strndup(body.sig_start + body.sig_pos, + body.sig_len - body.sig_pos); + message->signature_free = true; + message->header_end = header.len; + body_pos = body.data + body.pos - data; + } + + message->header = l_malloc(message->header_size); + message->body = l_malloc(message->body_size); + + memcpy(message->header, data, message->header_size); + memcpy(message->body, data + body_pos, message->body_size); + + message->sealed = true; + + /* If the field is absent message->signature will remain NULL */ + if (hdr->version == 1) + get_header_field(message, DBUS_MESSAGE_FIELD_SIGNATURE, + 'g', &message->signature); + + if (num_fds) { + uint32_t unix_fds, orig_fds = num_fds; + + if (!get_header_field(message, DBUS_MESSAGE_FIELD_UNIX_FDS, + 'u', &unix_fds)) + goto free; + + if (num_fds > unix_fds) + num_fds = unix_fds; + + if (num_fds > L_ARRAY_SIZE(message->fds)) + num_fds = L_ARRAY_SIZE(message->fds); + + for (i = num_fds; i < orig_fds; i++) + close(fds[i]); + + message->num_fds = num_fds; + memcpy(message->fds, fds, num_fds * sizeof(int)); + } + + return message; + +free: + l_dbus_message_unref(message); + + return NULL; +} + +struct l_dbus_message *dbus_message_build(void *header, size_t header_size, + void *body, size_t body_size, + int fds[], uint32_t num_fds) +{ + const struct dbus_header *hdr = header; + struct l_dbus_message *message; + unsigned int i; + + if (unlikely(header_size < DBUS_HEADER_SIZE)) + return NULL; + + if (unlikely(!valid_header(hdr))) + return NULL; + + /* + * With GVariant we need to know the signature, use + * dbus_message_from_blob instead. + */ + if (unlikely(hdr->version != 1)) + return NULL; + + message = l_new(struct l_dbus_message, 1); + + message->refcount = 1; + message->header_size = header_size; + message->header = header; + message->body_size = body_size; + message->body = body; + message->sealed = true; + + if (num_fds) { + uint32_t unix_fds, orig_fds = num_fds; + + if (!get_header_field(message, DBUS_MESSAGE_FIELD_UNIX_FDS, + 'u', &unix_fds)) { + l_free(message); + return NULL; + } + + if (num_fds > unix_fds) + num_fds = unix_fds; + + if (num_fds > L_ARRAY_SIZE(message->fds)) + num_fds = L_ARRAY_SIZE(message->fds); + + for (i = num_fds; i < orig_fds; i++) + close(fds[i]); + + message->num_fds = num_fds; + memcpy(message->fds, fds, num_fds * sizeof(int)); + } + + /* If the field is absent message->signature will remain NULL */ + get_header_field(message, DBUS_MESSAGE_FIELD_SIGNATURE, 'g', + &message->signature); + + return message; +} + +bool dbus_message_compare(struct l_dbus_message *message, + const void *data, size_t size) +{ + struct l_dbus_message *other; + bool ret = false; + + other = dbus_message_from_blob(data, size, NULL, 0); + + if (message->signature) { + if (!other->signature) + goto done; + + if (strcmp(message->signature, other->signature)) + goto done; + } else { + if (other->signature) + goto done; + } + + if (message->body_size != other->body_size) + goto done; + + if (message->header_size != other->header_size) + goto done; + + ret = !memcmp(message->body, other->body, message->body_size); + +done: + l_dbus_message_unref(other); + + return ret; +} + +struct builder_driver { + bool (*append_basic)(struct dbus_builder *, char, const void *); + bool (*enter_struct)(struct dbus_builder *, const char *); + bool (*leave_struct)(struct dbus_builder *); + bool (*enter_dict)(struct dbus_builder *, const char *); + bool (*leave_dict)(struct dbus_builder *); + bool (*enter_array)(struct dbus_builder *, const char *); + bool (*leave_array)(struct dbus_builder *); + bool (*enter_variant)(struct dbus_builder *, const char *); + bool (*leave_variant)(struct dbus_builder *); + char *(*finish)(struct dbus_builder *, void **, size_t *); + bool (*mark)(struct dbus_builder *); + bool (*rewind)(struct dbus_builder *); + struct dbus_builder *(*new)(void *, size_t); + void (*free)(struct dbus_builder *); +}; + +static struct builder_driver dbus1_driver = { + .append_basic = _dbus1_builder_append_basic, + .enter_struct = _dbus1_builder_enter_struct, + .leave_struct = _dbus1_builder_leave_struct, + .enter_dict = _dbus1_builder_enter_dict, + .leave_dict = _dbus1_builder_leave_dict, + .enter_variant = _dbus1_builder_enter_variant, + .leave_variant = _dbus1_builder_leave_variant, + .enter_array = _dbus1_builder_enter_array, + .leave_array = _dbus1_builder_leave_array, + .finish = _dbus1_builder_finish, + .mark = _dbus1_builder_mark, + .rewind = _dbus1_builder_rewind, + .new = _dbus1_builder_new, + .free = _dbus1_builder_free, +}; + +static struct builder_driver gvariant_driver = { + .append_basic = _gvariant_builder_append_basic, + .enter_struct = _gvariant_builder_enter_struct, + .leave_struct = _gvariant_builder_leave_struct, + .enter_dict = _gvariant_builder_enter_dict, + .leave_dict = _gvariant_builder_leave_dict, + .enter_variant = _gvariant_builder_enter_variant, + .leave_variant = _gvariant_builder_leave_variant, + .enter_array = _gvariant_builder_enter_array, + .leave_array = _gvariant_builder_leave_array, + .finish = _gvariant_builder_finish, + .mark = _gvariant_builder_mark, + .rewind = _gvariant_builder_rewind, + .new = _gvariant_builder_new, + .free = _gvariant_builder_free, +}; + +static void add_field(struct dbus_builder *builder, + struct builder_driver *driver, + uint8_t field, const char *type, const void *value) +{ + if (driver == &gvariant_driver) { + uint64_t long_field = field; + + driver->enter_struct(builder, "tv"); + driver->append_basic(builder, 't', &long_field); + } else { + driver->enter_struct(builder, "yv"); + driver->append_basic(builder, 'y', &field); + } + driver->enter_variant(builder, type); + driver->append_basic(builder, type[0], value); + driver->leave_variant(builder); + driver->leave_struct(builder); +} + +static void build_header(struct l_dbus_message *message, const char *signature) +{ + struct dbus_builder *builder; + struct builder_driver *driver; + char *generated_signature; + size_t header_size; + bool gvariant; + + gvariant = _dbus_message_is_gvariant(message); + + if (gvariant) + driver = &gvariant_driver; + else + driver = &dbus1_driver; + + builder = driver->new(message->header, message->header_size); + + driver->enter_array(builder, gvariant ? "(tv)" : "(yv)"); + + if (message->path) { + add_field(builder, driver, DBUS_MESSAGE_FIELD_PATH, + "o", message->path); + l_free(message->path); + message->path = NULL; + } + + if (message->member) { + add_field(builder, driver, DBUS_MESSAGE_FIELD_MEMBER, + "s", message->member); + l_free(message->member); + message->member = NULL; + } + + if (message->interface) { + add_field(builder, driver, DBUS_MESSAGE_FIELD_INTERFACE, + "s", message->interface); + l_free(message->interface); + message->interface = NULL; + } + + if (message->destination) { + add_field(builder, driver, DBUS_MESSAGE_FIELD_DESTINATION, + "s", message->destination); + l_free(message->destination); + message->destination = NULL; + } + + if (message->error_name != 0) { + add_field(builder, driver, DBUS_MESSAGE_FIELD_ERROR_NAME, + "s", message->error_name); + l_free(message->error_name); + message->error_name = NULL; + } + + if (message->reply_serial != 0) { + if (gvariant) { + uint64_t reply_serial = message->reply_serial; + + add_field(builder, driver, + DBUS_MESSAGE_FIELD_REPLY_SERIAL, + "t", &reply_serial); + } else { + add_field(builder, driver, + DBUS_MESSAGE_FIELD_REPLY_SERIAL, + "u", &message->reply_serial); + } + + message->reply_serial = 0; + } + + if (message->sender) { + add_field(builder, driver, DBUS_MESSAGE_FIELD_SENDER, + "s", message->sender); + l_free(message->sender); + message->sender = NULL; + } + + if (signature[0] != '\0' && !gvariant) + add_field(builder, driver, DBUS_MESSAGE_FIELD_SIGNATURE, + "g", signature); + + if (message->num_fds) + add_field(builder, driver, DBUS_MESSAGE_FIELD_UNIX_FDS, + "u", &message->num_fds); + + driver->leave_array(builder); + + generated_signature = driver->finish(builder, &message->header, + &header_size); + l_free(generated_signature); + + driver->free(builder); + + if (!_dbus_message_is_gvariant(message)) { + struct dbus_header *hdr = message->header; + + hdr->dbus1.body_length = message->body_size; + } + + /* We must align the end of the header to an 8-byte boundary */ + message->header_size = align_len(header_size, 8); + message->header = l_realloc(message->header, message->header_size); + memset(message->header + header_size, 0, + message->header_size - header_size); + message->header_end = header_size; +} + +struct container { + char type; + const char *sig_start; + const char *sig_end; + unsigned int n_items; +}; + +static bool append_arguments(struct l_dbus_message *message, + const char *signature, va_list args) +{ + struct l_dbus_message_builder *builder; + bool ret; + + builder = l_dbus_message_builder_new(message); + if (!builder) + return false; + + if (!l_dbus_message_builder_append_from_valist(builder, signature, + args)) { + l_dbus_message_builder_destroy(builder); + return false; + } + + l_dbus_message_builder_finalize(builder); + + ret = strcmp(signature, builder->message->signature) == 0; + + l_dbus_message_builder_destroy(builder); + + return ret; +} + +LIB_EXPORT bool l_dbus_message_get_error(struct l_dbus_message *message, + const char **name, const char **text) +{ + struct dbus_header *hdr; + const char *str; + + if (unlikely(!message)) + return false; + + hdr = message->header; + + if (hdr->message_type != DBUS_MESSAGE_TYPE_ERROR) + return false; + + if (!message->signature) + return false; + + if (message->signature[0] != 's') + return false; + + str = _dbus_message_get_nth_string_argument(message, 0); + if (!str) + return false; + + if (!message->error_name) + get_header_field(message, DBUS_MESSAGE_FIELD_ERROR_NAME, 's', + &message->error_name); + + if (name) + *name = message->error_name; + + if (text) + *text = str; + + return true; +} + +LIB_EXPORT bool l_dbus_message_is_error(struct l_dbus_message *message) +{ + struct dbus_header *hdr; + + if (unlikely(!message)) + return false; + + hdr = message->header; + return hdr->message_type == DBUS_MESSAGE_TYPE_ERROR; +} + +LIB_EXPORT bool l_dbus_message_get_arguments_valist( + struct l_dbus_message *message, + const char *signature, va_list args) +{ + struct l_dbus_message_iter iter; + + if (unlikely(!message)) + return false; + + if (!message->signature) { + /* An empty signature is valid */ + if (!signature || *signature == '\0') + return true; + + return false; + } + + if (!signature || strcmp(message->signature, signature)) + return false; + + if (_dbus_message_is_gvariant(message)) { + if (!_gvariant_iter_init(&iter, message, message->signature, + NULL, message->body, + message->body_size)) + return false; + } else + _dbus1_iter_init(&iter, message, message->signature, NULL, + message->body, message->body_size); + + return message_iter_next_entry_valist(&iter, args); +} + +LIB_EXPORT bool l_dbus_message_get_arguments(struct l_dbus_message *message, + const char *signature, ...) +{ + va_list args; + bool result; + + va_start(args, signature); + result = l_dbus_message_get_arguments_valist(message, signature, args); + va_end(args); + + return result; +} + +LIB_EXPORT bool l_dbus_message_set_arguments(struct l_dbus_message *message, + const char *signature, ...) +{ + va_list args; + bool result; + + if (unlikely(!message)) + return false; + + if (unlikely(message->sealed)) + return false; + + if (!signature) + return true; + + va_start(args, signature); + result = append_arguments(message, signature, args); + va_end(args); + + return result; +} + +LIB_EXPORT bool l_dbus_message_set_arguments_valist( + struct l_dbus_message *message, + const char *signature, va_list args) +{ + bool result; + + if (unlikely(!message)) + return false; + + if (!signature) + return true; + + result = append_arguments(message, signature, args); + + return result; +} + +LIB_EXPORT const char *l_dbus_message_get_path(struct l_dbus_message *message) +{ + if (unlikely(!message)) + return NULL; + + if (!message->path && message->sealed) + get_header_field(message, DBUS_MESSAGE_FIELD_PATH, 'o', + &message->path); + + return message->path; +} + +LIB_EXPORT const char *l_dbus_message_get_interface(struct l_dbus_message *message) +{ + if (unlikely(!message)) + return NULL; + + if (!message->interface && message->sealed) + get_header_field(message, DBUS_MESSAGE_FIELD_INTERFACE, 's', + &message->interface); + + return message->interface; +} + +LIB_EXPORT const char *l_dbus_message_get_member(struct l_dbus_message *message) +{ + if (unlikely(!message)) + return NULL; + + if (!message->member && message->sealed) + get_header_field(message, DBUS_MESSAGE_FIELD_MEMBER, 's', + &message->member); + + return message->member; +} + +LIB_EXPORT const char *l_dbus_message_get_destination(struct l_dbus_message *message) +{ + if (unlikely(!message)) + return NULL; + + if (!message->destination && message->sealed) + get_header_field(message, DBUS_MESSAGE_FIELD_DESTINATION, 's', + &message->destination); + + return message->destination; +} + +LIB_EXPORT const char *l_dbus_message_get_sender(struct l_dbus_message *message) +{ + if (unlikely(!message)) + return NULL; + + if (!message->sender && message->sealed) + get_header_field(message, DBUS_MESSAGE_FIELD_SENDER, 's', + &message->sender); + + return message->sender; +} + +LIB_EXPORT const char *l_dbus_message_get_signature( + struct l_dbus_message *message) +{ + if (unlikely(!message)) + return NULL; + + return message->signature; +} + +uint32_t _dbus_message_get_reply_serial(struct l_dbus_message *message) +{ + if (unlikely(!message)) + return 0; + + if (message->reply_serial == 0 && message->sealed) { + if (_dbus_message_is_gvariant(message)) { + uint64_t reply_serial = 0; + + get_header_field(message, + DBUS_MESSAGE_FIELD_REPLY_SERIAL, 't', + &reply_serial); + + message->reply_serial = reply_serial; + } else + get_header_field(message, + DBUS_MESSAGE_FIELD_REPLY_SERIAL, 'u', + &message->reply_serial); + } + + return message->reply_serial; +} + +enum dbus_message_type _dbus_message_get_type(struct l_dbus_message *message) +{ + struct dbus_header *header; + + header = message->header; + return header->message_type; +} + +const char * _dbus_message_get_type_as_string(struct l_dbus_message *message) +{ + struct dbus_header *header; + + header = message->header; + + switch (header->message_type) { + case DBUS_MESSAGE_TYPE_METHOD_CALL: + return "method_call"; + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + return "method_return"; + case DBUS_MESSAGE_TYPE_ERROR: + return "error"; + case DBUS_MESSAGE_TYPE_SIGNAL: + return "signal"; + } + + return NULL; +} + +uint8_t _dbus_message_get_endian(struct l_dbus_message *message) +{ + struct dbus_header *header = message->header; + + return header->endian; +} + +uint8_t _dbus_message_get_version(struct l_dbus_message *message) +{ + struct dbus_header *header = message->header; + + return header->version; +} + +LIB_EXPORT bool l_dbus_message_iter_next_entry(struct l_dbus_message_iter *iter, + ...) +{ + va_list args; + bool result; + + if (unlikely(!iter)) + return false; + + va_start(args, iter); + result = message_iter_next_entry_valist(iter, args); + va_end(args); + + return result; +} + +LIB_EXPORT bool l_dbus_message_iter_get_variant( + struct l_dbus_message_iter *iter, + const char *signature, ...) +{ + va_list args; + bool result; + + if (unlikely(!iter)) + return false; + + if (!iter->sig_start || strlen(signature) != iter->sig_len || + memcmp(iter->sig_start, signature, iter->sig_len)) + return false; + + va_start(args, signature); + result = message_iter_next_entry_valist(iter, args); + va_end(args); + + return result; +} + +LIB_EXPORT bool l_dbus_message_iter_get_fixed_array( + struct l_dbus_message_iter *iter, + void *out, uint32_t *n_elem) +{ + if (unlikely(!iter)) + return false; + + if (_dbus_message_is_gvariant(iter->message)) + return false; + + return _dbus1_iter_get_fixed_array(iter, out, n_elem); +} + +void _dbus_message_set_sender(struct l_dbus_message *message, + const char *sender) +{ + if (!_dbus_message_is_gvariant(message)) + return; + + l_free(message->sender); + + message->sender = l_strdup(sender); +} + +void _dbus_message_set_destination(struct l_dbus_message *message, + const char *destination) +{ + if (!_dbus_message_is_gvariant(message)) + return; + + l_free(message->destination); + + message->destination = l_strdup(destination); +} + +LIB_EXPORT struct l_dbus_message_builder *l_dbus_message_builder_new( + struct l_dbus_message *message) +{ + struct l_dbus_message_builder *ret; + + if (unlikely(!message)) + return NULL; + + if (message->sealed) + return NULL; + + ret = l_new(struct l_dbus_message_builder, 1); + ret->message = l_dbus_message_ref(message); + + if (_dbus_message_is_gvariant(message)) + ret->driver = &gvariant_driver; + else + ret->driver = &dbus1_driver; + + ret->builder = ret->driver->new(NULL, 0); + + return ret; +} + +LIB_EXPORT void l_dbus_message_builder_destroy( + struct l_dbus_message_builder *builder) +{ + if (unlikely(!builder)) + return; + + builder->driver->free(builder->builder); + l_dbus_message_unref(builder->message); + + l_free(builder); +} + +LIB_EXPORT bool l_dbus_message_builder_append_basic( + struct l_dbus_message_builder *builder, + char type, const void *value) +{ + if (unlikely(!builder)) + return false; + + return builder->driver->append_basic(builder->builder, type, value); +} + +LIB_EXPORT bool l_dbus_message_builder_enter_container( + struct l_dbus_message_builder *builder, + char container_type, + const char *signature) +{ + if (unlikely(!builder)) + return false; + + switch (container_type) { + case DBUS_CONTAINER_TYPE_ARRAY: + return builder->driver->enter_array(builder->builder, + signature); + case DBUS_CONTAINER_TYPE_DICT_ENTRY: + return builder->driver->enter_dict(builder->builder, signature); + case DBUS_CONTAINER_TYPE_STRUCT: + return builder->driver->enter_struct(builder->builder, + signature); + case DBUS_CONTAINER_TYPE_VARIANT: + return builder->driver->enter_variant(builder->builder, + signature); + default: + break; + } + + return false; +} + +LIB_EXPORT bool l_dbus_message_builder_leave_container( + struct l_dbus_message_builder *builder, + char container_type) +{ + if (unlikely(!builder)) + return false; + + switch (container_type) { + case DBUS_CONTAINER_TYPE_ARRAY: + return builder->driver->leave_array(builder->builder); + case DBUS_CONTAINER_TYPE_DICT_ENTRY: + return builder->driver->leave_dict(builder->builder); + case DBUS_CONTAINER_TYPE_STRUCT: + return builder->driver->leave_struct(builder->builder); + case DBUS_CONTAINER_TYPE_VARIANT: + return builder->driver->leave_variant(builder->builder); + default: + break; + } + + return false; +} + +LIB_EXPORT bool l_dbus_message_builder_enter_struct( + struct l_dbus_message_builder *builder, + const char *signature) +{ + return l_dbus_message_builder_enter_container(builder, 'r', signature); +} + +LIB_EXPORT bool l_dbus_message_builder_leave_struct( + struct l_dbus_message_builder *builder) +{ + return l_dbus_message_builder_leave_container(builder, 'r'); +} + +LIB_EXPORT bool l_dbus_message_builder_enter_array( + struct l_dbus_message_builder *builder, + const char *signature) +{ + return l_dbus_message_builder_enter_container(builder, 'a', signature); +} + +LIB_EXPORT bool l_dbus_message_builder_leave_array( + struct l_dbus_message_builder *builder) +{ + return l_dbus_message_builder_leave_container(builder, 'a'); +} + +LIB_EXPORT bool l_dbus_message_builder_enter_dict( + struct l_dbus_message_builder *builder, + const char *signature) +{ + return l_dbus_message_builder_enter_container(builder, 'e', signature); +} + +LIB_EXPORT bool l_dbus_message_builder_leave_dict( + struct l_dbus_message_builder *builder) +{ + return l_dbus_message_builder_leave_container(builder, 'e'); +} + +LIB_EXPORT bool l_dbus_message_builder_enter_variant( + struct l_dbus_message_builder *builder, + const char *signature) +{ + return l_dbus_message_builder_enter_container(builder, 'v', signature); +} + +LIB_EXPORT bool l_dbus_message_builder_leave_variant( + struct l_dbus_message_builder *builder) +{ + return l_dbus_message_builder_leave_container(builder, 'v'); +} + +/** + * l_dbus_message_builder_append_from_iter: + * @builder: message builder to receive a new value + * @from: message iterator to have its position moved by one value + * + * Copy one value from a message iterator onto a message builder. The + * value's signature is also copied. + * + * Returns: whether the value was correctly copied. On failure both + * the @from iterator and the @builder may have their positions + * moved to somewhere within the new value if it's of a + * container type. + **/ +LIB_EXPORT bool l_dbus_message_builder_append_from_iter( + struct l_dbus_message_builder *builder, + struct l_dbus_message_iter *from) +{ + static const char *simple_types = "sogybnqiuxtd"; + char type = from->sig_start[from->sig_pos]; + char container_type; + char signature[256]; + struct l_dbus_message_iter iter; + void *basic_ptr; + uint64_t basic; + uint32_t uint32_val; + bool (*get_basic)(struct l_dbus_message_iter *, char, void *); + bool (*enter_func)(struct l_dbus_message_iter *, + struct l_dbus_message_iter *); + bool (*enter_struct)(struct l_dbus_message_iter *, + struct l_dbus_message_iter *); + bool (*enter_array)(struct l_dbus_message_iter *, + struct l_dbus_message_iter *); + bool (*enter_variant)(struct l_dbus_message_iter *, + struct l_dbus_message_iter *); + + if (_dbus_message_is_gvariant(from->message)) { + get_basic = _gvariant_iter_next_entry_basic; + enter_struct = _gvariant_iter_enter_struct; + enter_array = _gvariant_iter_enter_array; + enter_variant = _gvariant_iter_enter_variant; + } else { + get_basic = _dbus1_iter_next_entry_basic; + enter_struct = _dbus1_iter_enter_struct; + enter_array = _dbus1_iter_enter_array; + enter_variant = _dbus1_iter_enter_variant; + } + + if (strchr(simple_types, type)) { + if (strchr("sog", type)) { + if (!get_basic(from, type, &basic_ptr)) + return false; + } else { + basic_ptr = &basic; + + if (!get_basic(from, type, basic_ptr)) + return false; + } + + if (!l_dbus_message_builder_append_basic(builder, type, + basic_ptr)) + return false; + + return true; + } + + switch (type) { + case 'h': + if (!get_basic(from, type, &uint32_val)) + return false; + + if (!l_dbus_message_builder_append_basic(builder, type, + &builder->message->num_fds)) + return false; + + if (builder->message->num_fds < + L_ARRAY_SIZE(builder->message->fds)) { + int fd; + + if (uint32_val < from->message->num_fds) + fd = fcntl(from->message->fds[uint32_val], + F_DUPFD_CLOEXEC, 3); + else + fd = -1; + + builder->message->fds[builder->message->num_fds++] = fd; + } + + return true; + case '(': + enter_func = enter_struct; + container_type = DBUS_CONTAINER_TYPE_STRUCT; + break; + case '{': + enter_func = enter_struct; + container_type = DBUS_CONTAINER_TYPE_DICT_ENTRY; + break; + case 'a': + enter_func = enter_array; + container_type = DBUS_CONTAINER_TYPE_ARRAY; + break; + case 'v': + enter_func = enter_variant; + container_type = DBUS_CONTAINER_TYPE_VARIANT; + break; + default: + return false; + } + + if (!enter_func(from, &iter)) + return false; + + memcpy(signature, iter.sig_start, iter.sig_len); + signature[iter.sig_len] = '\0'; + + if (!l_dbus_message_builder_enter_container(builder, + container_type, signature)) + return false; + + if (container_type == DBUS_CONTAINER_TYPE_ARRAY) + while(l_dbus_message_builder_append_from_iter(builder, &iter)); + else + while (iter.sig_pos < iter.sig_len) + if (!l_dbus_message_builder_append_from_iter(builder, + &iter)) + return false; + + if (!l_dbus_message_builder_leave_container(builder, + container_type)) + return false; + + return true; +} + +LIB_EXPORT bool l_dbus_message_builder_append_from_valist( + struct l_dbus_message_builder *builder, + const char *signature, va_list args) +{ + struct builder_driver *driver; + char subsig[256]; + const char *sigend; + /* Nesting requires an extra stack entry for the base level */ + struct container stack[DBUS_MAX_NESTING + 1]; + unsigned int stack_index = 0; + + if (unlikely(!builder)) + return false; + + driver = builder->driver; + + stack[stack_index].type = DBUS_CONTAINER_TYPE_STRUCT; + stack[stack_index].sig_start = signature; + stack[stack_index].sig_end = signature + strlen(signature); + stack[stack_index].n_items = 0; + + while (stack_index != 0 || stack[0].sig_start != stack[0].sig_end) { + const char *s; + const char *str; + + if (stack[stack_index].type == DBUS_CONTAINER_TYPE_ARRAY && + stack[stack_index].n_items == 0) + stack[stack_index].sig_start = + stack[stack_index].sig_end; + + if (stack[stack_index].sig_start == + stack[stack_index].sig_end) { + bool ret; + + /* + * Sanity check in case of an invalid signature that + * isn't caught elsewhere. + */ + if (unlikely(stack_index == 0)) + return false; + + switch (stack[stack_index].type) { + case DBUS_CONTAINER_TYPE_STRUCT: + ret = driver->leave_struct(builder->builder); + break; + case DBUS_CONTAINER_TYPE_DICT_ENTRY: + ret = driver->leave_dict(builder->builder); + break; + case DBUS_CONTAINER_TYPE_VARIANT: + ret = driver->leave_variant(builder->builder); + break; + case DBUS_CONTAINER_TYPE_ARRAY: + ret = driver->leave_array(builder->builder); + break; + default: + ret = false; + } + + if (!ret) + return false; + + stack_index -= 1; + continue; + } + + s = stack[stack_index].sig_start; + + if (stack[stack_index].type != DBUS_CONTAINER_TYPE_ARRAY) + stack[stack_index].sig_start += 1; + else + stack[stack_index].n_items -= 1; + + switch (*s) { + case 'o': + case 's': + case 'g': + str = va_arg(args, const char *); + + if (!driver->append_basic(builder->builder, *s, str)) + return false; + break; + case 'b': + case 'y': + { + uint8_t y = (uint8_t) va_arg(args, int); + + if (!driver->append_basic(builder->builder, *s, &y)) + return false; + + break; + } + case 'n': + case 'q': + { + uint16_t n = (uint16_t) va_arg(args, int); + + if (!driver->append_basic(builder->builder, *s, &n)) + return false; + + break; + } + case 'i': + case 'u': + { + uint32_t u = va_arg(args, uint32_t); + + if (!driver->append_basic(builder->builder, *s, &u)) + return false; + + break; + } + case 'h': + { + int fd = va_arg(args, int); + struct l_dbus_message *message = builder->message; + + if (!driver->append_basic(builder->builder, *s, + &message->num_fds)) + return false; + + if (message->num_fds < L_ARRAY_SIZE(message->fds)) + message->fds[message->num_fds++] = + fcntl(fd, F_DUPFD_CLOEXEC, 3); + + break; + } + case 'x': + case 't': + { + uint64_t x = va_arg(args, uint64_t); + + if (!driver->append_basic(builder->builder, *s, &x)) + return false; + break; + } + case 'd': + { + double d = va_arg(args, double); + + if (!driver->append_basic(builder->builder, *s, &d)) + return false; + break; + } + case '(': + case '{': + if (stack_index == DBUS_MAX_NESTING) + return false; + + sigend = _dbus_signature_end(s); + memcpy(subsig, s + 1, sigend - s - 1); + subsig[sigend - s - 1] = '\0'; + + if (*s == '(' && !driver->enter_struct(builder->builder, + subsig)) + return false; + + if (*s == '{' && !driver->enter_dict(builder->builder, + subsig)) + return false; + + if (stack[stack_index].type != + DBUS_CONTAINER_TYPE_ARRAY) + stack[stack_index].sig_start = sigend + 1; + + stack_index += 1; + stack[stack_index].sig_start = s + 1; + stack[stack_index].sig_end = sigend; + stack[stack_index].n_items = 0; + stack[stack_index].type = *s == '(' ? + DBUS_CONTAINER_TYPE_STRUCT : + DBUS_CONTAINER_TYPE_DICT_ENTRY; + + break; + case 'v': + if (stack_index == DBUS_MAX_NESTING) + return false; + + str = va_arg(args, const char *); + + if (!str) + return false; + + if (!driver->enter_variant(builder->builder, str)) + return false; + + stack_index += 1; + stack[stack_index].type = DBUS_CONTAINER_TYPE_VARIANT; + stack[stack_index].sig_start = str; + stack[stack_index].sig_end = str + strlen(str); + stack[stack_index].n_items = 0; + + break; + case 'a': + if (stack_index == DBUS_MAX_NESTING) + return false; + + sigend = _dbus_signature_end(s + 1) + 1; + memcpy(subsig, s + 1, sigend - s - 1); + subsig[sigend - s - 1] = '\0'; + + if (!driver->enter_array(builder->builder, subsig)) + return false; + + if (stack[stack_index].type != + DBUS_CONTAINER_TYPE_ARRAY) + stack[stack_index].sig_start = sigend; + + stack_index += 1; + stack[stack_index].sig_start = s + 1; + stack[stack_index].sig_end = sigend; + stack[stack_index].n_items = va_arg(args, unsigned int); + stack[stack_index].type = DBUS_CONTAINER_TYPE_ARRAY; + + break; + default: + return false; + } + } + + return true; +} + +LIB_EXPORT struct l_dbus_message *l_dbus_message_builder_finalize( + struct l_dbus_message_builder *builder) +{ + char *generated_signature; + + if (unlikely(!builder)) + return NULL; + + generated_signature = builder->driver->finish(builder->builder, + &builder->message->body, + &builder->message->body_size); + + build_header(builder->message, generated_signature); + builder->message->sealed = true; + builder->message->signature = generated_signature; + builder->message->signature_free = true; + + return builder->message; +} + +bool _dbus_message_builder_mark(struct l_dbus_message_builder *builder) +{ + if (unlikely(!builder)) + return false; + + return builder->driver->mark(builder->builder); +} + +bool _dbus_message_builder_rewind(struct l_dbus_message_builder *builder) +{ + if (unlikely(!builder)) + return false; + + return builder->driver->rewind(builder->builder); +} diff --git a/ell/dbus-name-cache.c b/ell/dbus-name-cache.c new file mode 100644 index 0000000..cb6a516 --- /dev/null +++ b/ell/dbus-name-cache.c @@ -0,0 +1,305 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2016 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 +#endif + +#define _GNU_SOURCE +#include +#include + +#include "util.h" +#include "hashmap.h" +#include "idle.h" +#include "dbus.h" +#include "dbus-private.h" + +struct _dbus_name_cache { + struct l_dbus *bus; + struct l_hashmap *names; + const struct _dbus_name_ops *driver; + unsigned int last_watch_id; + struct l_idle *watch_remove_work; +}; + +struct service_watch { + l_dbus_watch_func_t connect_func; + l_dbus_watch_func_t disconnect_func; + l_dbus_destroy_func_t destroy; + void *user_data; + unsigned int id; + bool removed; + struct service_watch *next; +}; + +struct name_cache_entry { + int ref_count; + char *unique_name; + struct service_watch *watches; +}; + +struct _dbus_name_cache *_dbus_name_cache_new(struct l_dbus *bus, + const struct _dbus_name_ops *driver) +{ + struct _dbus_name_cache *cache; + + cache = l_new(struct _dbus_name_cache, 1); + + cache->bus = bus; + cache->driver = driver; + + return cache; +} + +static void service_watch_destroy(void *data) +{ + struct service_watch *watch = data; + + if (watch->destroy) + watch->destroy(watch->user_data); + + l_free(watch); +} + +static void name_cache_entry_destroy(void *data) +{ + struct name_cache_entry *entry = data; + struct service_watch *watch; + + while (entry->watches) { + watch = entry->watches; + entry->watches = watch->next; + + service_watch_destroy(watch); + } + + l_free(entry->unique_name); + + l_free(entry); +} + +void _dbus_name_cache_free(struct _dbus_name_cache *cache) +{ + if (!cache) + return; + + if (cache->watch_remove_work) + l_idle_remove(cache->watch_remove_work); + + l_hashmap_destroy(cache->names, name_cache_entry_destroy); + + l_free(cache); +} + +bool _dbus_name_cache_add(struct _dbus_name_cache *cache, const char *name) +{ + struct name_cache_entry *entry; + + if (!_dbus_valid_bus_name(name)) + return false; + + if (!cache->names) + cache->names = l_hashmap_string_new(); + + entry = l_hashmap_lookup(cache->names, name); + + if (!entry) { + entry = l_new(struct name_cache_entry, 1); + + l_hashmap_insert(cache->names, name, entry); + + cache->driver->get_name_owner(cache->bus, name); + } + + entry->ref_count++; + + return true; +} + +bool _dbus_name_cache_remove(struct _dbus_name_cache *cache, const char *name) +{ + struct name_cache_entry *entry; + + entry = l_hashmap_lookup(cache->names, name); + + if (!entry) + return false; + + if (--entry->ref_count) + return true; + + l_hashmap_remove(cache->names, name); + + name_cache_entry_destroy(entry); + + return true; +} + +const char *_dbus_name_cache_lookup(struct _dbus_name_cache *cache, + const char *name) +{ + struct name_cache_entry *entry; + + entry = l_hashmap_lookup(cache->names, name); + + if (!entry) + return NULL; + + return entry->unique_name; +} + +void _dbus_name_cache_notify(struct _dbus_name_cache *cache, + const char *name, const char *owner) +{ + struct name_cache_entry *entry; + struct service_watch *watch; + bool prev_connected, connected; + + if (!cache) + return; + + entry = l_hashmap_lookup(cache->names, name); + + if (!entry) + return; + + prev_connected = !!entry->unique_name; + connected = owner && *owner != '\0'; + + l_free(entry->unique_name); + + entry->unique_name = connected ? l_strdup(owner) : NULL; + + /* + * This check also means we notify all watchers who have a connected + * callback when we first learn that the service is in fact connected. + */ + if (connected == prev_connected) + return; + + for (watch = entry->watches; watch; watch = watch->next) + if (connected && watch->connect_func) + watch->connect_func(cache->bus, watch->user_data); + else if (!connected && watch->disconnect_func) + watch->disconnect_func(cache->bus, watch->user_data); +} + +unsigned int _dbus_name_cache_add_watch(struct _dbus_name_cache *cache, + const char *name, + l_dbus_watch_func_t connect_func, + l_dbus_watch_func_t disconnect_func, + void *user_data, + l_dbus_destroy_func_t destroy) +{ + struct name_cache_entry *entry; + struct service_watch *watch; + + if (!_dbus_name_cache_add(cache, name)) + return 0; + + watch = l_new(struct service_watch, 1); + watch->id = ++cache->last_watch_id; + watch->connect_func = connect_func; + watch->disconnect_func = disconnect_func; + watch->user_data = user_data; + watch->destroy = destroy; + + entry = l_hashmap_lookup(cache->names, name); + + watch->next = entry->watches; + entry->watches = watch; + + if (entry->unique_name && connect_func) + watch->connect_func(cache->bus, watch->user_data); + + return watch->id; +} + +static bool service_watch_remove(const void *key, void *value, void *user_data) +{ + struct name_cache_entry *entry = value; + struct service_watch **watch, *tmp; + + for (watch = &entry->watches; *watch;) { + if (!(*watch)->removed) { + watch = &(*watch)->next; + continue; + } + + tmp = *watch; + *watch = tmp->next; + + service_watch_destroy(tmp); + + entry->ref_count--; + } + + if (entry->ref_count) + return false; + + name_cache_entry_destroy(entry); + + return true; +} + +static void service_watch_remove_all(struct l_idle *idle, void *user_data) +{ + struct _dbus_name_cache *cache = user_data; + + l_idle_remove(cache->watch_remove_work); + cache->watch_remove_work = NULL; + + l_hashmap_foreach_remove(cache->names, service_watch_remove, cache); +} + +static void service_watch_mark(const void *key, void *value, void *user_data) +{ + struct name_cache_entry *entry = value; + struct service_watch *watch; + unsigned int *id = user_data; + + for (watch = entry->watches; watch; watch = watch->next) + if (watch->id == *id) { + watch->removed = true; + watch->connect_func = NULL; + watch->disconnect_func = NULL; + *id = 0; + break; + } +} + +bool _dbus_name_cache_remove_watch(struct _dbus_name_cache *cache, + unsigned int id) +{ + l_hashmap_foreach(cache->names, service_watch_mark, &id); + + if (id) + return false; + + if (!cache->watch_remove_work) + cache->watch_remove_work = l_idle_create( + service_watch_remove_all, + cache, NULL); + + return true; +} diff --git a/ell/dbus-private.h b/ell/dbus-private.h new file mode 100644 index 0000000..be62691 --- /dev/null +++ b/ell/dbus-private.h @@ -0,0 +1,308 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#include + +enum dbus_message_type { + DBUS_MESSAGE_TYPE_METHOD_CALL = 1, + DBUS_MESSAGE_TYPE_METHOD_RETURN = 2, + DBUS_MESSAGE_TYPE_ERROR = 3, + DBUS_MESSAGE_TYPE_SIGNAL = 4, +}; + +enum dbus_container_type { + DBUS_CONTAINER_TYPE_ARRAY = 'a', + DBUS_CONTAINER_TYPE_STRUCT = 'r', + DBUS_CONTAINER_TYPE_VARIANT = 'v', + DBUS_CONTAINER_TYPE_DICT_ENTRY = 'e', +}; + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define DBUS_NATIVE_ENDIAN 'l' +#elif __BYTE_ORDER == __BIG_ENDIAN +#define DBUS_NATIVE_ENDIAN 'B' +#else +#error "Unknown byte order" +#endif + +struct dbus_header { + uint8_t endian; + uint8_t message_type; + uint8_t flags; + uint8_t version; + + union { + struct { + uint32_t body_length; + uint32_t serial; + uint32_t field_length; + } __attribute__ ((packed)) dbus1; + }; +} __attribute__ ((packed)); +#define DBUS_HEADER_SIZE 16 + +struct dbus_builder; +struct l_string; +struct l_dbus_interface; +struct _dbus_method; +struct _dbus_signal; +struct _dbus_property; +struct l_dbus_message_iter; +struct l_dbus_message; +struct l_dbus; +struct _dbus_filter; +struct _dbus_filter_condition; +struct _dbus_filter_ops; + +void _dbus1_iter_init(struct l_dbus_message_iter *iter, + struct l_dbus_message *message, + const char *sig_start, const char *sig_end, + const void *data, size_t len); +bool _dbus1_iter_next_entry_basic(struct l_dbus_message_iter *iter, char type, + void *out); +bool _dbus1_iter_get_fixed_array(struct l_dbus_message_iter *iter, + void *out, uint32_t *n_elem); +bool _dbus1_iter_enter_struct(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *structure); +bool _dbus1_iter_enter_variant(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *variant); +bool _dbus1_iter_enter_array(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *array); +bool _dbus1_iter_skip_entry(struct l_dbus_message_iter *iter); + +struct dbus_builder *_dbus1_builder_new(void *body, size_t body_size); +void _dbus1_builder_free(struct dbus_builder *builder); +bool _dbus1_builder_append_basic(struct dbus_builder *builder, + char type, const void *value); +bool _dbus1_builder_enter_struct(struct dbus_builder *builder, + const char *signature); +bool _dbus1_builder_leave_struct(struct dbus_builder *builder); +bool _dbus1_builder_enter_dict(struct dbus_builder *builder, + const char *signature); +bool _dbus1_builder_leave_dict(struct dbus_builder *builder); +bool _dbus1_builder_enter_variant(struct dbus_builder *builder, + const char *signature); +bool _dbus1_builder_leave_variant(struct dbus_builder *builder); +bool _dbus1_builder_enter_array(struct dbus_builder *builder, + const char *signature); +bool _dbus1_builder_leave_array(struct dbus_builder *builder); +char *_dbus1_builder_finish(struct dbus_builder *builder, + void **body, size_t *body_size); +bool _dbus1_builder_mark(struct dbus_builder *builder); +bool _dbus1_builder_rewind(struct dbus_builder *builder); + +void *_dbus_message_get_body(struct l_dbus_message *msg, size_t *out_size); +void *_dbus_message_get_header(struct l_dbus_message *msg, size_t *out_size); +void *_dbus_message_get_footer(struct l_dbus_message *msg, size_t *out_size); +int *_dbus_message_get_fds(struct l_dbus_message *msg, uint32_t *num_fds); +void _dbus_message_set_serial(struct l_dbus_message *msg, uint32_t serial); +uint32_t _dbus_message_get_serial(struct l_dbus_message *msg); +uint32_t _dbus_message_get_reply_serial(struct l_dbus_message *message); + +void _dbus_message_set_sender(struct l_dbus_message *message, + const char *sender); +void _dbus_message_set_destination(struct l_dbus_message *message, + const char *destination); + +enum dbus_message_type _dbus_message_get_type(struct l_dbus_message *message); +const char * _dbus_message_get_type_as_string(struct l_dbus_message *message); +uint8_t _dbus_message_get_version(struct l_dbus_message *message); +uint8_t _dbus_message_get_endian(struct l_dbus_message *message); +const char *_dbus_message_get_nth_string_argument( + struct l_dbus_message *message, int n); + +struct l_dbus_message *_dbus_message_new_method_call(uint8_t version, + const char *destination, + const char *path, + const char *interface, + const char *method); +struct l_dbus_message *_dbus_message_new_signal(uint8_t version, + const char *path, + const char *interface, + const char *name); +struct l_dbus_message *_dbus_message_new_error(uint8_t version, + uint32_t reply_serial, + const char *destination, + const char *name, + const char *error); + +struct l_dbus_message *dbus_message_from_blob(const void *data, size_t size, + int fds[], uint32_t num_fds); +struct l_dbus_message *dbus_message_build(void *header, size_t header_size, + void *body, size_t body_size, + int fds[], uint32_t num_fds); +bool dbus_message_compare(struct l_dbus_message *message, + const void *data, size_t size); + +bool _dbus_message_builder_mark(struct l_dbus_message_builder *builder); +bool _dbus_message_builder_rewind(struct l_dbus_message_builder *builder); + +unsigned int _dbus_message_unix_fds_from_header(const void *data, size_t size); + +const char *_dbus_signature_end(const char *signature); + +bool _dbus_valid_object_path(const char *path); +bool _dbus_valid_signature(const char *sig); +int _dbus_num_children(const char *sig); +bool _dbus_valid_interface(const char *interface); +bool _dbus_valid_method(const char *method); +bool _dbus_parse_unique_name(const char *name, uint64_t *out_id); +bool _dbus_valid_bus_name(const char *bus_name); + +bool _dbus1_header_is_valid(void *data, size_t size); + +void _dbus_method_introspection(struct _dbus_method *info, + struct l_string *buf); +void _dbus_signal_introspection(struct _dbus_signal *info, + struct l_string *buf); +void _dbus_property_introspection(struct _dbus_property *info, + struct l_string *buf); +void _dbus_interface_introspection(struct l_dbus_interface *interface, + struct l_string *buf); + +struct l_dbus_interface *_dbus_interface_new(const char *interface); +void _dbus_interface_free(struct l_dbus_interface *interface); + +struct _dbus_method *_dbus_interface_find_method(struct l_dbus_interface *i, + const char *method); +struct _dbus_signal *_dbus_interface_find_signal(struct l_dbus_interface *i, + const char *signal); +struct _dbus_property *_dbus_interface_find_property(struct l_dbus_interface *i, + const char *property); + +struct _dbus_object_tree *_dbus_object_tree_new(); +void _dbus_object_tree_free(struct _dbus_object_tree *tree); + +struct object_node *_dbus_object_tree_makepath(struct _dbus_object_tree *tree, + const char *path); +struct object_node *_dbus_object_tree_lookup(struct _dbus_object_tree *tree, + const char *path); +void _dbus_object_tree_prune_node(struct object_node *node); + +struct object_node *_dbus_object_tree_new_object(struct _dbus_object_tree *tree, + const char *path, + void *user_data, + void (*destroy) (void *)); +bool _dbus_object_tree_object_destroy(struct _dbus_object_tree *tree, + const char *path); + +bool _dbus_object_tree_register_interface(struct _dbus_object_tree *tree, + const char *interface, + void (*setup_func)(struct l_dbus_interface *), + void (*destroy) (void *), + bool old_style_properties); +bool _dbus_object_tree_unregister_interface(struct _dbus_object_tree *tree, + const char *interface); + +bool _dbus_object_tree_add_interface(struct _dbus_object_tree *tree, + const char *path, const char *interface, + void *user_data); +bool _dbus_object_tree_remove_interface(struct _dbus_object_tree *tree, + const char *path, + const char *interface); +void *_dbus_object_tree_get_interface_data(struct _dbus_object_tree *tree, + const char *path, + const char *interface); + +void _dbus_object_tree_introspect(struct _dbus_object_tree *tree, + const char *path, struct l_string *buf); +bool _dbus_object_tree_dispatch(struct _dbus_object_tree *tree, + struct l_dbus *dbus, + struct l_dbus_message *message); +struct l_dbus_message *_dbus_object_tree_get_objects( + struct _dbus_object_tree *tree, + struct l_dbus *dbus, + const char *path, + struct l_dbus_message *message); + +bool _dbus_object_tree_property_changed(struct l_dbus *dbus, + const char *path, + const char *interface_name, + const char *property_name); + +void _dbus_object_tree_signals_flush(struct l_dbus *dbus, const char *path); + +typedef void (*_dbus_name_owner_change_func_t)(const char *name, + uint64_t old_owner, + uint64_t new_owner, + void *user_data); + +uint8_t _dbus_get_version(struct l_dbus *dbus); +int _dbus_get_fd(struct l_dbus *dbus); +struct _dbus_object_tree *_dbus_get_tree(struct l_dbus *dbus); + +struct _dbus_name_ops { + bool (*get_name_owner)(struct l_dbus *bus, const char *name); +}; + +struct _dbus_name_cache; + +struct _dbus_name_cache *_dbus_name_cache_new(struct l_dbus *bus, + const struct _dbus_name_ops *driver); +void _dbus_name_cache_free(struct _dbus_name_cache *cache); + +bool _dbus_name_cache_add(struct _dbus_name_cache *cache, const char *name); +bool _dbus_name_cache_remove(struct _dbus_name_cache *cache, const char *name); +const char *_dbus_name_cache_lookup(struct _dbus_name_cache *cache, + const char *name); + +void _dbus_name_cache_notify(struct _dbus_name_cache *cache, + const char *name, const char *owner); + +unsigned int _dbus_name_cache_add_watch(struct _dbus_name_cache *cache, + const char *name, + l_dbus_watch_func_t connect_func, + l_dbus_watch_func_t disconnect_func, + void *user_data, + l_dbus_destroy_func_t destroy); +bool _dbus_name_cache_remove_watch(struct _dbus_name_cache *cache, + unsigned int id); + +struct _dbus_filter_condition { + enum l_dbus_match_type type; + const char *value; +}; + +struct _dbus_filter_ops { + bool skip_register; + bool (*add_match)(struct l_dbus *bus, unsigned int id, + const struct _dbus_filter_condition *rule, + int rule_len); + bool (*remove_match)(struct l_dbus *bus, unsigned int id); +}; + +struct _dbus_filter *_dbus_filter_new(struct l_dbus *dbus, + const struct _dbus_filter_ops *driver, + struct _dbus_name_cache *name_cache); +void _dbus_filter_free(struct _dbus_filter *filter); + +unsigned int _dbus_filter_add_rule(struct _dbus_filter *filter, + const struct _dbus_filter_condition *rule, + int rule_len, + l_dbus_message_func_t signal_func, + void *user_data); +bool _dbus_filter_remove_rule(struct _dbus_filter *filter, unsigned int id); + +char *_dbus_filter_rule_to_str(const struct _dbus_filter_condition *rule, + int rule_len); + +void _dbus_filter_dispatch(struct l_dbus_message *message, void *user_data); diff --git a/ell/dbus-service.c b/ell/dbus-service.c new file mode 100644 index 0000000..fb839f9 --- /dev/null +++ b/ell/dbus-service.c @@ -0,0 +1,2100 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "util.h" +#include "queue.h" +#include "string.h" +#include "hashmap.h" +#include "dbus.h" +#include "dbus-service.h" +#include "dbus-private.h" +#include "private.h" +#include "idle.h" + +#define XML_ID "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" +#define XML_DTD "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd" +#define XML_HEAD "\n" + +static const char *static_introspectable = + "\t\n" + "\t\t\n" + "\t\t\t\n" + "\t\t\n\t\n"; + +struct _dbus_method { + l_dbus_interface_method_cb_t cb; + uint32_t flags; + unsigned char name_len; + char metainfo[]; +}; + +struct _dbus_signal { + uint32_t flags; + unsigned char name_len; + char metainfo[]; +}; + +struct _dbus_property { + l_dbus_property_get_cb_t getter; + l_dbus_property_set_cb_t setter; + uint32_t flags; + unsigned char name_len; + char metainfo[]; +}; + +struct l_dbus_interface { + struct l_queue *methods; + struct l_queue *signals; + struct l_queue *properties; + bool handle_old_style_properties; + void (*instance_destroy)(void *); + char name[]; +}; + +struct child_node { + struct object_node *node; + struct child_node *next; + char subpath[]; +}; + +struct interface_instance { + struct l_dbus_interface *interface; + void *user_data; +}; + +struct object_node { + struct object_node *parent; + struct l_queue *instances; + struct child_node *children; + void *user_data; + void (*destroy) (void *); +}; + +struct object_manager { + char *path; + struct l_dbus *dbus; + struct l_queue *announce_added; + struct l_queue *announce_removed; +}; + +struct interface_add_record { + char *path; + struct object_node *object; + struct l_queue *instances; +}; + +struct interface_remove_record { + char *path; + struct object_node *object; + struct l_queue *interface_names; +}; + +struct property_change_record { + char *path; + struct object_node *object; + struct interface_instance *instance; + struct l_queue *properties; +}; + +struct _dbus_object_tree { + struct l_hashmap *interfaces; + struct l_hashmap *objects; + struct object_node *root; + struct l_queue *object_managers; + struct l_queue *property_changes; + struct l_idle *emit_signals_work; + bool flushing; +}; + +void _dbus_method_introspection(struct _dbus_method *info, + struct l_string *buf) +{ + const char *sig; + const char *end; + const char *pname; + unsigned int offset = info->name_len + 1; + + l_string_append_printf(buf, "\t\t\n", + info->metainfo); + + sig = info->metainfo + offset; + offset += strlen(sig) + 1; + + for (; *sig; sig++) { + end = _dbus_signature_end(sig); + pname = info->metainfo + offset; + + l_string_append_printf(buf, "\t\t\t\n", + pname, (int) (end - sig + 1), sig); + sig = end; + offset += strlen(pname) + 1; + } + + sig = info->metainfo + offset; + offset += strlen(sig) + 1; + + for (; *sig; sig++) { + end = _dbus_signature_end(sig); + pname = info->metainfo + offset; + + l_string_append_printf(buf, "\t\t\t\n", + pname, (int) (end - sig + 1), sig); + sig = end; + offset += strlen(pname) + 1; + } + + if (info->flags & L_DBUS_METHOD_FLAG_DEPRECATED) + l_string_append(buf, "\t\t\t\n"); + + if (info->flags & L_DBUS_METHOD_FLAG_NOREPLY) + l_string_append(buf, "\t\t\t\n"); + + l_string_append(buf, "\t\t\n"); +} + +void _dbus_signal_introspection(struct _dbus_signal *info, + struct l_string *buf) +{ + const char *sig; + const char *end; + const char *pname; + unsigned int offset = info->name_len + 1; + + l_string_append_printf(buf, "\t\t\n", + info->metainfo); + + sig = info->metainfo + offset; + offset += strlen(sig) + 1; + + for (; *sig; sig++) { + end = _dbus_signature_end(sig); + pname = info->metainfo + offset; + + l_string_append_printf(buf, "\t\t\t\n", + pname, (int) (end - sig + 1), sig); + sig = end; + offset += strlen(pname) + 1; + } + + if (info->flags & L_DBUS_SIGNAL_FLAG_DEPRECATED) + l_string_append(buf, "\t\t\t\n"); + + l_string_append(buf, "\t\t\n"); +} + +void _dbus_property_introspection(struct _dbus_property *info, + struct l_string *buf) +{ + unsigned int offset = info->name_len + 1; + const char *signature = info->metainfo + offset; + + l_string_append_printf(buf, "\t\tmetainfo, signature); + + if (info->setter) + l_string_append(buf, "access=\"readwrite\""); + else + l_string_append(buf, "access=\"read\""); + + if (info->flags & L_DBUS_METHOD_FLAG_DEPRECATED) { + l_string_append(buf, ">\n"); + l_string_append(buf, "\t\t\t\n"); + l_string_append(buf, "\t\t\n"); + } else + l_string_append(buf, "/>\n"); +} + +void _dbus_interface_introspection(struct l_dbus_interface *interface, + struct l_string *buf) +{ + l_string_append_printf(buf, "\t\n", + interface->name); + + l_queue_foreach(interface->methods, + (l_queue_foreach_func_t) _dbus_method_introspection, buf); + l_queue_foreach(interface->signals, + (l_queue_foreach_func_t) _dbus_signal_introspection, buf); + l_queue_foreach(interface->properties, + (l_queue_foreach_func_t) _dbus_property_introspection, buf); + + l_string_append(buf, "\t\n"); +} + +#define COPY_PARAMS(dest, signature, args) \ + do { \ + const char *pname; \ + const char *sig; \ + dest = stpcpy(dest, signature) + 1; \ + for (sig = signature; *sig; sig++) { \ + sig = _dbus_signature_end(sig); \ + pname = va_arg(args, const char *); \ + dest = stpcpy(dest, pname) + 1; \ + } \ + } while(0) + +#define SIZE_PARAMS(signature, args) \ + ({ \ + unsigned int len = strlen(signature) + 1; \ + const char *pname; \ + const char *sig; \ + for (sig = signature; *sig; sig++) { \ + sig = _dbus_signature_end(sig); \ + if (!sig) { \ + len = 0; \ + break; \ + } \ + pname = va_arg(args, const char *); \ + len += strlen(pname) + 1; \ + } \ + len; \ + }) + +LIB_EXPORT bool l_dbus_interface_method(struct l_dbus_interface *interface, + const char *name, uint32_t flags, + l_dbus_interface_method_cb_t cb, + const char *return_sig, + const char *param_sig, ...) +{ + va_list args; + unsigned int return_info_len; + unsigned int param_info_len; + struct _dbus_method *info; + char *p; + + if (!_dbus_valid_method(name)) + return false; + + if (unlikely(!return_sig || !param_sig)) + return false; + + if (return_sig[0] && !_dbus_valid_signature(return_sig)) + return false; + + if (param_sig[0] && !_dbus_valid_signature(param_sig)) + return false; + + /* Pre-calculate the needed meta-info length */ + va_start(args, param_sig); + + return_info_len = SIZE_PARAMS(return_sig, args); + param_info_len = SIZE_PARAMS(param_sig, args); + + va_end(args); + + if (!return_info_len || !param_info_len) + return false; + + info = l_malloc(sizeof(*info) + return_info_len + + param_info_len + strlen(name) + 1); + info->cb = cb; + info->flags = flags; + info->name_len = strlen(name); + strcpy(info->metainfo, name); + + va_start(args, param_sig); + + /* + * We store param signature + parameter names first, to speed up + * lookups during the message dispatch procedures. + */ + p = info->metainfo + info->name_len + param_info_len + 1; + COPY_PARAMS(p, return_sig, args); + + p = info->metainfo + info->name_len + 1; + COPY_PARAMS(p, param_sig, args); + + va_end(args); + + l_queue_push_tail(interface->methods, info); + + return true; +} + +LIB_EXPORT bool l_dbus_interface_signal(struct l_dbus_interface *interface, + const char *name, uint32_t flags, + const char *signature, ...) +{ + va_list args; + unsigned int metainfo_len; + struct _dbus_signal *info; + char *p; + + if (!_dbus_valid_method(name)) + return false; + + if (unlikely(!signature)) + return false; + + if (signature[0] && !_dbus_valid_signature(signature)) + return false; + + /* Pre-calculate the needed meta-info length */ + va_start(args, signature); + metainfo_len = SIZE_PARAMS(signature, args); + va_end(args); + + if (!metainfo_len) + return false; + + metainfo_len += strlen(name) + 1; + + info = l_malloc(sizeof(*info) + metainfo_len); + info->flags = flags; + info->name_len = strlen(name); + + p = stpcpy(info->metainfo, name) + 1; + + va_start(args, signature); + COPY_PARAMS(p, signature, args); + va_end(args); + + l_queue_push_tail(interface->signals, info); + + return true; +} + +LIB_EXPORT bool l_dbus_interface_property(struct l_dbus_interface *interface, + const char *name, uint32_t flags, + const char *signature, + l_dbus_property_get_cb_t getter, + l_dbus_property_set_cb_t setter) +{ + unsigned int metainfo_len; + struct _dbus_property *info; + char *p; + + if (!_dbus_valid_method(name)) + return false; + + if (unlikely(!signature || !getter)) + return false; + + if (_dbus_num_children(signature) != 1) + return false; + + /* Pre-calculate the needed meta-info length */ + metainfo_len = strlen(name) + 1; + metainfo_len += strlen(signature) + 1; + + info = l_malloc(sizeof(*info) + metainfo_len); + info->flags = flags; + info->name_len = strlen(name); + info->getter = getter; + info->setter = setter; + + p = stpcpy(info->metainfo, name) + 1; + strcpy(p, signature); + + l_queue_push_tail(interface->properties, info); + + return true; +} + +struct l_dbus_interface *_dbus_interface_new(const char *name) +{ + struct l_dbus_interface *interface; + + interface = l_malloc(sizeof(*interface) + strlen(name) + 1); + + interface->methods = l_queue_new(); + interface->signals = l_queue_new(); + interface->properties = l_queue_new(); + + strcpy(interface->name, name); + + return interface; +} + +void _dbus_interface_free(struct l_dbus_interface *interface) +{ + l_queue_destroy(interface->methods, l_free); + l_queue_destroy(interface->signals, l_free); + l_queue_destroy(interface->properties, l_free); + + l_free(interface); +} + +static bool match_method(const void *a, const void *b) +{ + const struct _dbus_method *method = a; + const char *name = b; + + if (!strcmp(method->metainfo, name)) + return true; + + return false; +} + +struct _dbus_method *_dbus_interface_find_method(struct l_dbus_interface *i, + const char *method) +{ + return l_queue_find(i->methods, match_method, (char *) method); +} + +static bool match_signal(const void *a, const void *b) +{ + const struct _dbus_signal *signal = a; + const char *name = b; + + if (!strcmp(signal->metainfo, name)) + return true; + + return false; +} + +struct _dbus_signal *_dbus_interface_find_signal(struct l_dbus_interface *i, + const char *signal) +{ + return l_queue_find(i->signals, match_signal, (char *) signal); +} + +static bool match_property(const void *a, const void *b) +{ + const struct _dbus_property *property = a; + const char *name = b; + + if (!strcmp(property->metainfo, name)) + return true; + + return false; +} + +struct _dbus_property *_dbus_interface_find_property(struct l_dbus_interface *i, + const char *property) +{ + return l_queue_find(i->properties, match_property, (char *) property); +} + +static void interface_instance_free(struct interface_instance *instance) +{ + if (instance->interface->instance_destroy) + instance->interface->instance_destroy(instance->user_data); + + l_free(instance); +} + +static bool match_interface_instance(const void *a, const void *b) +{ + const struct interface_instance *instance = a; + const char *name = b; + + if (!strcmp(instance->interface->name, name)) + return true; + + return false; +} + +static bool match_interface_instance_ptr(const void *a, const void *b) +{ + const struct interface_instance *instance = a; + + return instance->interface == b; +} + +static void interface_add_record_free(void *data) +{ + struct interface_add_record *rec = data; + + l_free(rec->path); + l_queue_destroy(rec->instances, NULL); + l_free(rec); +} + +static void interface_removed_record_free(void *data) +{ + struct interface_remove_record *rec = data; + + l_free(rec->path); + l_queue_destroy(rec->interface_names, l_free); + l_free(rec); +} + +static void property_change_record_free(void *data) +{ + struct property_change_record *rec = data; + + l_free(rec->path); + l_queue_destroy(rec->properties, NULL); + l_free(rec); +} + +static void properties_setup_func(struct l_dbus_interface *); +static void object_manager_setup_func(struct l_dbus_interface *); + +struct _dbus_object_tree *_dbus_object_tree_new() +{ + struct _dbus_object_tree *tree; + + tree = l_new(struct _dbus_object_tree, 1); + + tree->interfaces = l_hashmap_new(); + l_hashmap_set_hash_function(tree->interfaces, l_str_hash); + l_hashmap_set_compare_function(tree->interfaces, + (l_hashmap_compare_func_t)strcmp); + + tree->objects = l_hashmap_string_new(); + + tree->root = l_new(struct object_node, 1); + + tree->property_changes = l_queue_new(); + + _dbus_object_tree_register_interface(tree, L_DBUS_INTERFACE_PROPERTIES, + properties_setup_func, NULL, + false); + + tree->object_managers = l_queue_new(); + + _dbus_object_tree_register_interface(tree, + L_DBUS_INTERFACE_OBJECT_MANAGER, + object_manager_setup_func, NULL, + false); + + return tree; +} + +static void subtree_free(struct object_node *node) +{ + struct child_node *child; + + while (node->children) { + child = node->children; + node->children = child->next; + + subtree_free(child->node); + l_free(child); + } + + l_queue_destroy(node->instances, + (l_queue_destroy_func_t) interface_instance_free); + + if (node->destroy) + node->destroy(node->user_data); + + l_free(node); +} + +static void object_manager_free(void *data) +{ + struct object_manager *manager = data; + + l_free(manager->path); + l_queue_destroy(manager->announce_added, interface_add_record_free); + l_queue_destroy(manager->announce_removed, + interface_removed_record_free); + l_free(manager); +} + +void _dbus_object_tree_free(struct _dbus_object_tree *tree) +{ + subtree_free(tree->root); + + l_hashmap_destroy(tree->interfaces, + (l_hashmap_destroy_func_t) _dbus_interface_free); + l_hashmap_destroy(tree->objects, NULL); + + l_queue_destroy(tree->object_managers, object_manager_free); + + l_queue_destroy(tree->property_changes, property_change_record_free); + + if (tree->emit_signals_work) + l_idle_remove(tree->emit_signals_work); + + l_free(tree); +} + +static struct object_node *makepath_recurse(struct object_node *node, + const char *path) +{ + const char *end; + struct child_node *child; + + if (*path == '\0') + return node; + + path += 1; + end = strchrnul(path, '/'); + child = node->children; + + while (child) { + if (!strncmp(child->subpath, path, end - path) && + child->subpath[end - path] == '\0') + goto done; + + child = child->next; + } + + child = l_malloc(sizeof(*child) + end - path + 1); + child->node = l_new(struct object_node, 1); + child->node->parent = node; + memcpy(child->subpath, path, end - path); + child->subpath[end-path] = '\0'; + child->next = node->children; + node->children = child; + +done: + return makepath_recurse(child->node, end); +} + +struct object_node *_dbus_object_tree_makepath(struct _dbus_object_tree *tree, + const char *path) +{ + if (path[0] == '/' && path[1] == '\0') + return tree->root; + + return makepath_recurse(tree->root, path); +} + +static struct object_node *lookup_recurse(struct object_node *node, + const char *path) +{ + const char *end; + struct child_node *child; + + if (*path == '\0') + return node; + + path += 1; + end = strchrnul(path, '/'); + child = node->children; + + while (child) { + if (!strncmp(child->subpath, path, end - path) && + child->subpath[end - path] == '\0') + return lookup_recurse(child->node, end); + + child = child->next; + } + + return NULL; +} + +struct object_node *_dbus_object_tree_lookup(struct _dbus_object_tree *tree, + const char *path) +{ + if (path[0] == '/' && path[1] == '\0') + return tree->root; + + return lookup_recurse(tree->root, path); +} + +void _dbus_object_tree_prune_node(struct object_node *node) +{ + struct object_node *parent = node->parent; + struct child_node *p = NULL, *c; + + while (parent) { + for (c = parent->children, p = NULL; c; p = c, c = c->next) { + if (c->node != node) + continue; + + if (p) + p->next = c->next; + else + parent->children = c->next; + + subtree_free(c->node); + l_free(c); + + break; + } + + if (parent->children != NULL) + return; + + if (parent->instances) + return; + + node = parent; + parent = node->parent; + } +} + +struct object_node *_dbus_object_tree_new_object(struct _dbus_object_tree *tree, + const char *path, + void *user_data, + void (*destroy) (void *)) +{ + struct object_node *node; + + if (!_dbus_valid_object_path(path)) + return NULL; + + if (l_hashmap_lookup(tree->objects, path)) + return NULL; + + node = _dbus_object_tree_makepath(tree, path); + node->user_data = user_data; + node->destroy = destroy; + + /* + * Registered objects in the tree are marked by being present in the + * tree->objects hash and having non-null node->instances. Remaining + * nodes are intermediate path elements added and removed + * automatically. + */ + node->instances = l_queue_new(); + + l_hashmap_insert(tree->objects, path, node); + + return node; +} + +bool _dbus_object_tree_object_destroy(struct _dbus_object_tree *tree, + const char *path) +{ + struct object_node *node; + const struct l_queue_entry *entry; + const struct interface_instance *instance; + + node = l_hashmap_lookup(tree->objects, path); + if (!node) + return false; + + while ((entry = l_queue_get_entries(node->instances))) { + instance = entry->data; + + if (!_dbus_object_tree_remove_interface(tree, path, + instance->interface->name)) + return false; + } + + l_hashmap_remove(tree->objects, path); + + l_queue_destroy(node->instances, NULL); + node->instances = NULL; + + if (node->destroy) { + node->destroy(node->user_data); + node->destroy = NULL; + } + + if (!node->children) + _dbus_object_tree_prune_node(node); + + return true; +} + +static bool get_properties_dict(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + const struct l_dbus_interface *interface, + void *user_data) +{ + const struct l_queue_entry *entry; + const struct _dbus_property *property; + const char *signature; + + l_dbus_message_builder_enter_array(builder, "{sv}"); + _dbus_message_builder_mark(builder); + + for (entry = l_queue_get_entries(interface->properties); entry; + entry = entry->next) { + property = entry->data; + signature = property->metainfo + strlen(property->metainfo) + 1; + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', + property->metainfo); + l_dbus_message_builder_enter_variant(builder, signature); + + if (!property->getter(dbus, message, builder, user_data)) { + if (!_dbus_message_builder_rewind(builder)) + return false; + + continue; + } + + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); + _dbus_message_builder_mark(builder); + } + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static struct l_dbus_message *build_interfaces_removed_signal( + const struct object_manager *manager, + const struct interface_remove_record *rec) +{ + struct l_dbus_message *signal; + struct l_dbus_message_builder *builder; + const struct l_queue_entry *entry; + + signal = l_dbus_message_new_signal(manager->dbus, manager->path, + L_DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesRemoved"); + + builder = l_dbus_message_builder_new(signal); + + l_dbus_message_builder_append_basic(builder, 'o', rec->path); + l_dbus_message_builder_enter_array(builder, "s"); + + for (entry = l_queue_get_entries(rec->interface_names); entry; + entry = entry->next) + l_dbus_message_builder_append_basic(builder, 's', entry->data); + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + return signal; +} + +static struct l_dbus_message *build_interfaces_added_signal( + const struct object_manager *manager, + const struct interface_add_record *rec) +{ + struct l_dbus_message *signal; + struct l_dbus_message_builder *builder; + const struct l_queue_entry *entry; + const struct interface_instance *instance; + + signal = l_dbus_message_new_signal(manager->dbus, manager->path, + L_DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesAdded"); + + builder = l_dbus_message_builder_new(signal); + + l_dbus_message_builder_append_basic(builder, 'o', rec->path); + l_dbus_message_builder_enter_array(builder, "{sa{sv}}"); + + for (entry = l_queue_get_entries(rec->instances); entry; + entry = entry->next) { + instance = entry->data; + + l_dbus_message_builder_enter_dict(builder, "sa{sv}"); + l_dbus_message_builder_append_basic(builder, 's', + instance->interface->name); + + if (!get_properties_dict(manager->dbus, signal, builder, + instance->interface, + instance->user_data)) { + l_dbus_message_builder_destroy(builder); + l_dbus_message_unref(signal); + + return NULL; + } + + l_dbus_message_builder_leave_dict(builder); + } + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + return signal; +} + +static struct l_dbus_message *build_old_property_changed_signal( + struct l_dbus *dbus, + const struct property_change_record *rec, + const struct _dbus_property *property) +{ + struct l_dbus_message *signal; + struct l_dbus_message_builder *builder; + const char *signature; + + signature = property->metainfo + strlen(property->metainfo) + 1; + + signal = l_dbus_message_new_signal(dbus, rec->path, + rec->instance->interface->name, + "PropertyChanged"); + + builder = l_dbus_message_builder_new(signal); + + l_dbus_message_builder_append_basic(builder, 's', property->metainfo); + l_dbus_message_builder_enter_variant(builder, signature); + + if (!property->getter(dbus, signal, builder, + rec->instance->user_data)) { + l_dbus_message_builder_destroy(builder); + l_dbus_message_unref(signal); + + return NULL; + } + + l_dbus_message_builder_leave_variant(builder); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + return signal; +} + +static struct l_dbus_message *build_properties_changed_signal( + struct l_dbus *dbus, + const struct property_change_record *rec) +{ + struct l_dbus_message *signal; + struct l_dbus_message_builder *builder; + const struct l_queue_entry *entry; + const struct _dbus_property *property; + const char *signature; + struct l_queue *invalidated; + + signal = l_dbus_message_new_signal(dbus, rec->path, + L_DBUS_INTERFACE_PROPERTIES, + "PropertiesChanged"); + + builder = l_dbus_message_builder_new(signal); + + invalidated = l_queue_new(); + + l_dbus_message_builder_append_basic(builder, 's', + rec->instance->interface->name); + l_dbus_message_builder_enter_array(builder, "{sv}"); + + for (entry = l_queue_get_entries(rec->properties); entry; + entry = entry->next) { + property = entry->data; + signature = property->metainfo + strlen(property->metainfo) + 1; + + _dbus_message_builder_mark(builder); + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', + property->metainfo); + l_dbus_message_builder_enter_variant(builder, signature); + + if (!property->getter(dbus, signal, builder, + rec->instance->user_data)) { + if (!_dbus_message_builder_rewind(builder)) { + l_dbus_message_unref(signal); + signal = NULL; + + goto done; + } + + l_queue_push_tail(invalidated, (void *) property); + + continue; + } + + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); + } + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_enter_array(builder, "s"); + + while ((property = l_queue_pop_head(invalidated))) + l_dbus_message_builder_append_basic(builder, 's', + property->metainfo); + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_finalize(builder); + +done: + l_dbus_message_builder_destroy(builder); + + l_queue_destroy(invalidated, NULL); + + return signal; +} + +struct emit_signals_data { + struct l_dbus *dbus; + struct object_manager *manager; + struct object_node *node; +}; + +static bool emit_interfaces_removed(void *data, void *user_data) +{ + struct interface_remove_record *rec = data; + struct emit_signals_data *es = user_data; + struct l_dbus_message *signal; + + if (es->node && rec->object != es->node) + return false; + + signal = build_interfaces_removed_signal(es->manager, rec); + interface_removed_record_free(rec); + + if (signal) + l_dbus_send(es->manager->dbus, signal); + + return true; +} + +static bool emit_interfaces_added(void *data, void *user_data) +{ + struct interface_add_record *rec = data; + struct emit_signals_data *es = user_data; + struct l_dbus_message *signal; + + if (es->node && rec->object != es->node) + return false; + + signal = build_interfaces_added_signal(es->manager, rec); + interface_add_record_free(rec); + + if (signal) + l_dbus_send(es->manager->dbus, signal); + + return true; +} + +static bool emit_properties_changed(void *data, void *user_data) +{ + struct property_change_record *rec = data; + struct emit_signals_data *es = user_data; + struct l_dbus_message *signal; + const struct l_queue_entry *entry; + + if (es->node && rec->object != es->node) + return false; + + if (rec->instance->interface->handle_old_style_properties) + for (entry = l_queue_get_entries(rec->properties); + entry; entry = entry->next) { + signal = build_old_property_changed_signal(es->dbus, + rec, entry->data); + if (signal) + l_dbus_send(es->dbus, signal); + } + + if (l_queue_find(rec->object->instances, match_interface_instance, + L_DBUS_INTERFACE_PROPERTIES)) { + signal = build_properties_changed_signal(es->dbus, rec); + if (signal) + l_dbus_send(es->dbus, signal); + } + + property_change_record_free(rec); + + return true; +} + +void _dbus_object_tree_signals_flush(struct l_dbus *dbus, const char *path) +{ + struct _dbus_object_tree *tree = _dbus_get_tree(dbus); + const struct l_queue_entry *entry; + struct emit_signals_data data; + bool all_done = true; + + if (!tree->emit_signals_work || tree->flushing) + return; + + tree->flushing = true; + + data.dbus = dbus; + data.node = path ? _dbus_object_tree_lookup(tree, path) : NULL; + + for (entry = l_queue_get_entries(tree->object_managers); entry; + entry = entry->next) { + data.manager = entry->data; + + l_queue_foreach_remove(data.manager->announce_removed, + emit_interfaces_removed, &data); + + if (!l_queue_isempty(data.manager->announce_removed)) + all_done = false; + + l_queue_foreach_remove(data.manager->announce_added, + emit_interfaces_added, &data); + + if (!l_queue_isempty(data.manager->announce_added)) + all_done = false; + } + + l_queue_foreach_remove(tree->property_changes, + emit_properties_changed, &data); + + if (!l_queue_isempty(tree->property_changes)) + all_done = false; + + if (all_done) { + l_idle_remove(tree->emit_signals_work); + tree->emit_signals_work = NULL; + } + + tree->flushing = false; +} + +static void emit_signals(struct l_idle *idle, void *user_data) +{ + struct l_dbus *dbus = user_data; + + _dbus_object_tree_signals_flush(dbus, NULL); +} + +static void schedule_emit_signals(struct l_dbus *dbus) +{ + struct _dbus_object_tree *tree = _dbus_get_tree(dbus); + + if (tree->emit_signals_work) + return; + + tree->emit_signals_work = l_idle_create(emit_signals, dbus, NULL); +} + +static bool match_property_changes_instance(const void *a, const void *b) +{ + const struct property_change_record *rec = a; + + return rec->instance == b; +} + +static bool match_pointer(const void *a, const void *b) +{ + return a == b; +} + +bool _dbus_object_tree_property_changed(struct l_dbus *dbus, + const char *path, + const char *interface_name, + const char *property_name) +{ + struct property_change_record *rec; + struct object_node *object; + struct interface_instance *instance; + struct _dbus_property *property; + struct _dbus_object_tree *tree = _dbus_get_tree(dbus); + + object = l_hashmap_lookup(tree->objects, path); + if (!object) + return false; + + instance = l_queue_find(object->instances, match_interface_instance, + interface_name); + if (!instance) + return false; + + property = _dbus_interface_find_property(instance->interface, + property_name); + if (!property) + return false; + + rec = l_queue_find(tree->property_changes, + match_property_changes_instance, instance); + + if (rec) { + if (l_queue_find(rec->properties, match_pointer, property)) + return true; + } else { + rec = l_new(struct property_change_record, 1); + rec->path = l_strdup(path); + rec->object = object; + rec->instance = instance; + rec->properties = l_queue_new(); + + l_queue_push_tail(tree->property_changes, rec); + } + + l_queue_push_tail(rec->properties, property); + + schedule_emit_signals(dbus); + + return true; +} + +static void pending_property_set_done(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message *reply) +{ + const char *member; + const char *interface_name; + const char *property_name; + struct l_dbus_message_iter variant; + + if (!reply) { + reply = l_dbus_message_new_method_return(message); + l_dbus_message_set_arguments(reply, ""); + } + + l_dbus_send(dbus, l_dbus_message_ref(reply)); + + member = l_dbus_message_get_member(message); + if (!strcmp(member, "SetProperty")) { + if (!l_dbus_message_get_arguments(message, "sv", + &property_name, &variant)) + goto done; + + interface_name = l_dbus_message_get_interface(message); + } else if (strcmp(member, "Set") || + !l_dbus_message_get_arguments(message, "ssv", + &interface_name, + &property_name, + &variant)) + goto done; + + _dbus_object_tree_property_changed(dbus, + l_dbus_message_get_path(message), + interface_name, property_name); +done: + l_dbus_message_unref(message); + l_dbus_message_unref(reply); +} + +static struct l_dbus_message *old_set_property(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_interface *interface; + const char *property_name; + const struct _dbus_property *property; + struct l_dbus_message_iter variant; + struct _dbus_object_tree *tree = _dbus_get_tree(dbus); + struct l_dbus_message *reply; + + interface = l_hashmap_lookup(tree->interfaces, + l_dbus_message_get_interface(message)); + /* If we got here the interface must exist */ + + if (!l_dbus_message_get_arguments(message, "sv", &property_name, + &variant)) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Invalid arguments"); + + property = _dbus_interface_find_property(interface, property_name); + if (!property) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Unknown Property %s", + property_name); + + if (!property->setter) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Property %s is read-only", + property_name); + + reply = property->setter(dbus, l_dbus_message_ref(message), &variant, + pending_property_set_done, user_data); + + if (reply) + pending_property_set_done(dbus, message, reply); + + return NULL; +} + +static struct l_dbus_message *old_get_properties(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + const struct l_dbus_interface *interface; + struct l_dbus_message *reply; + struct l_dbus_message_builder *builder; + struct _dbus_object_tree *tree = _dbus_get_tree(dbus); + + interface = l_hashmap_lookup(tree->interfaces, + l_dbus_message_get_interface(message)); + /* If we got here the interface must exist */ + + reply = l_dbus_message_new_method_return(message); + builder = l_dbus_message_builder_new(reply); + + if (!get_properties_dict(dbus, message, builder, interface, + user_data)) { + l_dbus_message_unref(reply); + + reply = l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "Failed", + "Getting properties failed"); + } else + l_dbus_message_builder_finalize(builder); + + l_dbus_message_builder_destroy(builder); + + return reply; +} + +bool _dbus_object_tree_register_interface(struct _dbus_object_tree *tree, + const char *interface, + void (*setup_func)(struct l_dbus_interface *), + void (*destroy) (void *), + bool old_style_properties) +{ + struct l_dbus_interface *dbi; + + if (!_dbus_valid_interface(interface)) + return false; + + /* + * Check to make sure we do not have this interface already + * registered + */ + dbi = l_hashmap_lookup(tree->interfaces, interface); + if (dbi) + return false; + + dbi = _dbus_interface_new(interface); + dbi->instance_destroy = destroy; + dbi->handle_old_style_properties = old_style_properties; + + /* Add our methods first so we don't have to check for conflicts. */ + if (old_style_properties) { + l_dbus_interface_method(dbi, "SetProperty", 0, + old_set_property, "", "sv", + "name", "value"); + l_dbus_interface_method(dbi, "GetProperties", 0, + old_get_properties, "a{sv}", "", + "properties"); + + l_dbus_interface_signal(dbi, "PropertyChanged", 0, "sv", + "name", "value"); + } + + setup_func(dbi); + + l_hashmap_insert(tree->interfaces, dbi->name, dbi); + + return true; +} + +struct interface_check { + struct _dbus_object_tree *tree; + const char *interface; +}; + +static void check_interface_used(const void *key, void *value, void *user_data) +{ + const char *path = key; + struct object_node *node = value; + struct interface_check *state = user_data; + + if (!l_queue_find(node->instances, match_interface_instance, + (char *) state->interface)) + return; + + _dbus_object_tree_remove_interface(state->tree, path, state->interface); +} + +bool _dbus_object_tree_unregister_interface(struct _dbus_object_tree *tree, + const char *interface_name) +{ + struct l_dbus_interface *interface; + struct interface_check state = { tree, interface_name }; + + interface = l_hashmap_lookup(tree->interfaces, interface_name); + if (!interface) + return false; + + /* Check that the interface is not in use */ + l_hashmap_foreach(tree->objects, check_interface_used, &state); + + l_hashmap_remove(tree->interfaces, interface_name); + + _dbus_interface_free(interface); + + return true; +} + +static void collect_instances(struct object_node *node, + const char *path, + struct l_queue *announce) +{ + const struct l_queue_entry *entry; + struct interface_add_record *change_rec; + const struct child_node *child; + + if (!node->instances) + goto recurse; + + change_rec = l_new(struct interface_add_record, 1); + change_rec->path = l_strdup(path); + change_rec->object = node; + change_rec->instances = l_queue_new(); + + for (entry = l_queue_get_entries(node->instances); entry; + entry = entry->next) + l_queue_push_tail(change_rec->instances, entry->data); + + l_queue_push_tail(announce, change_rec); + +recurse: + if (!strcmp(path, "/")) + path = ""; + + for (child = node->children; child; child = child->next) { + char *child_path; + + child_path = l_strdup_printf("%s/%s", path, child->subpath); + + collect_instances(child->node, child_path, announce); + + l_free(child_path); + } +} + +static bool match_interfaces_added_object(const void *a, const void *b) +{ + const struct interface_add_record *rec = a; + + return rec->object == b; +} + +static bool match_interfaces_removed_object(const void *a, const void *b) +{ + const struct interface_remove_record *rec = a; + + return rec->object == b; +} + +bool _dbus_object_tree_add_interface(struct _dbus_object_tree *tree, + const char *path, const char *interface, + void *user_data) +{ + struct object_node *object; + struct l_dbus_interface *dbi; + struct interface_instance *instance; + const struct l_queue_entry *entry; + struct object_manager *manager; + size_t path_len; + struct interface_add_record *change_rec; + + dbi = l_hashmap_lookup(tree->interfaces, interface); + if (!dbi) + return false; + + object = l_hashmap_lookup(tree->objects, path); + if (!object) { + object = _dbus_object_tree_new_object(tree, path, NULL, NULL); + + if (!object) + return false; + } + + /* + * Check to make sure we do not have this interface already + * registered for this object + */ + if (l_queue_find(object->instances, match_interface_instance_ptr, dbi)) + return false; + + instance = l_new(struct interface_instance, 1); + instance->interface = dbi; + instance->user_data = user_data; + + l_queue_push_tail(object->instances, instance); + + for (entry = l_queue_get_entries(tree->object_managers); entry; + entry = entry->next) { + manager = entry->data; + path_len = strlen(manager->path); + + if (strncmp(path, manager->path, path_len) || + (path[path_len] != '\0' && + path[path_len] != '/' && path_len > 1)) + continue; + + change_rec = l_queue_find(manager->announce_added, + match_interfaces_added_object, + object); + if (!change_rec) { + change_rec = l_new(struct interface_add_record, 1); + change_rec->path = l_strdup(path); + change_rec->object = object; + change_rec->instances = l_queue_new(); + + l_queue_push_tail(manager->announce_added, change_rec); + } + + /* No need to check for duplicates here */ + l_queue_push_tail(change_rec->instances, instance); + + schedule_emit_signals(manager->dbus); + } + + if (!strcmp(interface, L_DBUS_INTERFACE_OBJECT_MANAGER)) { + manager = l_new(struct object_manager, 1); + manager->path = l_strdup(path); + manager->dbus = instance->user_data; + manager->announce_added = l_queue_new(); + manager->announce_removed = l_queue_new(); + + l_queue_push_tail(tree->object_managers, manager); + + /* Emit InterfacesAdded for interfaces added before OM */ + collect_instances(object, path, manager->announce_added); + + if (manager->dbus && !l_queue_isempty(manager->announce_added)) + schedule_emit_signals(manager->dbus); + } + + return true; +} + +void *_dbus_object_tree_get_interface_data(struct _dbus_object_tree *tree, + const char *path, + const char *interface) +{ + struct object_node *object; + struct interface_instance *instance; + + object = l_hashmap_lookup(tree->objects, path); + if (!object) + return NULL; + + instance = l_queue_find(object->instances, match_interface_instance, + (char *) interface); + if (!instance) + return NULL; + + return instance->user_data; +} + +static bool match_object_manager_path(const void *a, const void *b) +{ + const struct object_manager *manager = a; + + return !strcmp(manager->path, b); +} + +bool _dbus_object_tree_remove_interface(struct _dbus_object_tree *tree, + const char *path, const char *interface) +{ + struct object_node *node; + struct interface_instance *instance; + const struct l_queue_entry *entry; + struct object_manager *manager; + size_t path_len; + struct interface_add_record *interfaces_added_rec; + struct interface_remove_record *interfaces_removed_rec; + struct property_change_record *property_change_rec; + + node = l_hashmap_lookup(tree->objects, path); + if (!node) + return false; + + instance = l_queue_remove_if(node->instances, + match_interface_instance, (char *) interface); + if (!instance) + return false; + + if (!strcmp(interface, L_DBUS_INTERFACE_OBJECT_MANAGER)) { + manager = l_queue_remove_if(tree->object_managers, + match_object_manager_path, + (char *) path); + + if (manager) + object_manager_free(manager); + } + + for (entry = l_queue_get_entries(tree->object_managers); entry; + entry = entry->next) { + manager = entry->data; + path_len = strlen(manager->path); + + if (strncmp(path, manager->path, path_len) || + (path[path_len] != '\0' && + path[path_len] != '/' && path_len > 1)) + continue; + + interfaces_added_rec = l_queue_find(manager->announce_added, + match_interfaces_added_object, + node); + if (interfaces_added_rec && l_queue_remove( + interfaces_added_rec->instances, + instance)) { + if (l_queue_isempty(interfaces_added_rec->instances)) + l_queue_remove(manager->announce_added, + interfaces_added_rec); + + interface_add_record_free(interfaces_added_rec); + + continue; + } + + interfaces_removed_rec = l_queue_find(manager->announce_removed, + match_interfaces_removed_object, + node); + if (!interfaces_removed_rec) { + interfaces_removed_rec = + l_new(struct interface_remove_record, 1); + interfaces_removed_rec->path = l_strdup(path); + interfaces_removed_rec->object = node; + interfaces_removed_rec->interface_names = + l_queue_new(); + l_queue_push_tail(manager->announce_removed, + interfaces_removed_rec); + } + + /* No need to check for duplicates here */ + l_queue_push_tail(interfaces_removed_rec->interface_names, + l_strdup(interface)); + + schedule_emit_signals(manager->dbus); + } + + property_change_rec = l_queue_remove_if(tree->property_changes, + match_property_changes_instance, + instance); + if (property_change_rec) + property_change_record_free(property_change_rec); + + interface_instance_free(instance); + + return true; +} + +static void generate_interface_instance(void *data, void *user) +{ + struct interface_instance *instance = data; + struct l_string *buf = user; + + _dbus_interface_introspection(instance->interface, buf); +} + +void _dbus_object_tree_introspect(struct _dbus_object_tree *tree, + const char *path, struct l_string *buf) +{ + struct object_node *node; + struct child_node *child; + + node = l_hashmap_lookup(tree->objects, path); + if (!node) + node = _dbus_object_tree_lookup(tree, path); + + l_string_append(buf, XML_HEAD); + l_string_append(buf, "\n"); + + if (node) { + l_string_append(buf, static_introspectable); + l_queue_foreach(node->instances, + generate_interface_instance, buf); + + for (child = node->children; child; child = child->next) + l_string_append_printf(buf, "\t\n", + child->subpath); + } + + l_string_append(buf, "\n"); +} + +bool _dbus_object_tree_dispatch(struct _dbus_object_tree *tree, + struct l_dbus *dbus, + struct l_dbus_message *message) +{ + const char *path; + const char *interface; + const char *member; + const char *msg_sig; + const char *sig; + struct object_node *node; + struct interface_instance *instance; + struct _dbus_method *method; + struct l_dbus_message *reply; + + path = l_dbus_message_get_path(message); + interface = l_dbus_message_get_interface(message); + member = l_dbus_message_get_member(message); + msg_sig = l_dbus_message_get_signature(message); + + if (!msg_sig) + msg_sig = ""; + + if (!strcmp(interface, "org.freedesktop.DBus.Introspectable") && + !strcmp(member, "Introspect") && + !strcmp(msg_sig, "")) { + struct l_string *buf; + char *xml; + + buf = l_string_new(0); + _dbus_object_tree_introspect(tree, path, buf); + xml = l_string_unwrap(buf); + + reply = l_dbus_message_new_method_return(message); + l_dbus_message_set_arguments(reply, "s", xml); + l_dbus_send(dbus, reply); + + l_free(xml); + + return true; + } + + node = l_hashmap_lookup(tree->objects, path); + if (!node) + return false; + + instance = l_queue_find(node->instances, + match_interface_instance, (char *) interface); + if (!instance) + return false; + + method = _dbus_interface_find_method(instance->interface, member); + if (!method) + return false; + + sig = method->metainfo + method->name_len + 1; + + if (strcmp(msg_sig, sig)) + return false; + + reply = method->cb(dbus, message, instance->user_data); + if (reply) + l_dbus_send(dbus, reply); + + return true; +} + +LIB_EXPORT bool l_dbus_property_changed(struct l_dbus *dbus, const char *path, + const char *interface, + const char *property) +{ + return _dbus_object_tree_property_changed(dbus, path, interface, + property); +} + +static struct l_dbus_message *properties_get(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + const struct interface_instance *instance; + const char *interface_name, *property_name, *signature; + const struct _dbus_property *property; + struct _dbus_object_tree *tree = _dbus_get_tree(dbus); + const struct object_node *object; + struct l_dbus_message *reply; + struct l_dbus_message_builder *builder; + + if (!l_dbus_message_get_arguments(message, "ss", &interface_name, + &property_name)) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Invalid arguments"); + + object = l_hashmap_lookup(tree->objects, + l_dbus_message_get_path(message)); + /* If we got here the object must exist */ + + instance = l_queue_find(object->instances, + match_interface_instance, + (char *) interface_name); + if (!instance) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Object has no interface %s", + interface_name); + + property = _dbus_interface_find_property(instance->interface, + property_name); + if (!property) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Unknown Property %s", + property_name); + + + reply = l_dbus_message_new_method_return(message); + builder = l_dbus_message_builder_new(reply); + + signature = property->metainfo + strlen(property->metainfo) + 1; + + l_dbus_message_builder_enter_variant(builder, signature); + + if (property->getter(dbus, message, builder, instance->user_data)) { + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_finalize(builder); + } else { + l_dbus_message_unref(reply); + + reply = l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "Failed", + "Getting property value " + "failed"); + } + + l_dbus_message_builder_destroy(builder); + + return reply; +} + +static struct l_dbus_message *properties_set(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_interface *interface; + const struct interface_instance *instance; + const char *interface_name, *property_name; + const struct _dbus_property *property; + struct l_dbus_message_iter variant; + struct _dbus_object_tree *tree = _dbus_get_tree(dbus); + const struct object_node *object; + struct l_dbus_message *reply; + + if (!l_dbus_message_get_arguments(message, "ssv", &interface_name, + &property_name, &variant)) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Invalid arguments"); + + interface = l_hashmap_lookup(tree->interfaces, interface_name); + if (!interface) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Unknown Interface %s", + interface_name); + + property = _dbus_interface_find_property(interface, property_name); + if (!property) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Unknown Property %s", + property_name); + + if (!property->setter) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Property %s is read-only", + property_name); + + object = l_hashmap_lookup(tree->objects, + l_dbus_message_get_path(message)); + /* If we got here the object must exist */ + + instance = l_queue_find(object->instances, + match_interface_instance_ptr, interface); + if (!instance) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Object has no interface %s", + interface_name); + + reply = property->setter(dbus, l_dbus_message_ref(message), &variant, + pending_property_set_done, + instance->user_data); + + if (reply) + pending_property_set_done(dbus, message, reply); + + return NULL; +} + +static struct l_dbus_message *properties_get_all(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + const struct interface_instance *instance; + const char *interface_name; + struct _dbus_object_tree *tree = _dbus_get_tree(dbus); + const struct object_node *object; + struct l_dbus_message *reply; + struct l_dbus_message_builder *builder; + + if (!l_dbus_message_get_arguments(message, "s", &interface_name)) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Invalid arguments"); + + object = l_hashmap_lookup(tree->objects, + l_dbus_message_get_path(message)); + /* If we got here the object must exist */ + + instance = l_queue_find(object->instances, + match_interface_instance, + (char *) interface_name); + if (!instance) + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "InvalidArgs", + "Object has no interface %s", + interface_name); + + reply = l_dbus_message_new_method_return(message); + builder = l_dbus_message_builder_new(reply); + + if (!get_properties_dict(dbus, message, builder, instance->interface, + instance->user_data)) { + l_dbus_message_unref(reply); + + reply = l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "Failed", + "Getting property values " + "failed"); + } else + l_dbus_message_builder_finalize(builder); + + l_dbus_message_builder_destroy(builder); + + return reply; +} + +static void properties_setup_func(struct l_dbus_interface *interface) +{ + l_dbus_interface_method(interface, "Get", 0, + properties_get, "v", "ss", + "value", "interface_name", "property_name"); + l_dbus_interface_method(interface, "Set", 0, + properties_set, "", "ssv", + "interface_name", "property_name", "value"); + l_dbus_interface_method(interface, "GetAll", 0, + properties_get_all, "a{sv}", "s", + "props", "interface_name"); + + l_dbus_interface_signal(interface, "PropertiesChanged", 0, "sa{sv}as", + "interface_name", "changed_properties", + "invalidated_properties"); +} + +static bool collect_objects(struct l_dbus *dbus, struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + const struct object_node *node, + const char *path) +{ + const struct l_queue_entry *entry; + const struct child_node *child; + char *child_path; + const struct interface_instance *instance; + bool r; + + if (!node->instances) + goto recurse; + + l_dbus_message_builder_enter_dict(builder, "oa{sa{sv}}"); + l_dbus_message_builder_append_basic(builder, 'o', path); + l_dbus_message_builder_enter_array(builder, "{sa{sv}}"); + + for (entry = l_queue_get_entries(node->instances); entry; + entry = entry->next) { + instance = entry->data; + + l_dbus_message_builder_enter_dict(builder, "sa{sv}"); + l_dbus_message_builder_append_basic(builder, 's', + instance->interface->name); + + if (!get_properties_dict(dbus, message, builder, + instance->interface, + instance->user_data)) + return false; + + l_dbus_message_builder_leave_dict(builder); + } + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_leave_dict(builder); + +recurse: + if (!strcmp(path, "/")) + path = ""; + + for (child = node->children; child; child = child->next) { + child_path = l_strdup_printf("%s/%s", path, child->subpath); + + r = collect_objects(dbus, message, builder, + child->node, child_path); + + l_free(child_path); + + if (!r) + return false; + } + + return true; +} + +struct l_dbus_message *_dbus_object_tree_get_objects( + struct _dbus_object_tree *tree, + struct l_dbus *dbus, + const char *path, + struct l_dbus_message *message) +{ + const struct object_node *node; + struct l_dbus_message *reply; + struct l_dbus_message_builder *builder; + + node = l_hashmap_lookup(tree->objects, path); + + reply = l_dbus_message_new_method_return(message); + builder = l_dbus_message_builder_new(reply); + + l_dbus_message_builder_enter_array(builder, "{oa{sa{sv}}}"); + + if (!collect_objects(dbus, message, builder, node, path)) { + l_dbus_message_builder_destroy(builder); + l_dbus_message_unref(reply); + + return l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error." + "Failed", + "Getting property values " + "failed"); + } + + l_dbus_message_builder_leave_array(builder); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + return reply; +} + +static struct l_dbus_message *get_managed_objects(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct _dbus_object_tree *tree = _dbus_get_tree(dbus); + const char *path = l_dbus_message_get_path(message); + + return _dbus_object_tree_get_objects(tree, dbus, path, message); +} + +static void object_manager_setup_func(struct l_dbus_interface *interface) +{ + l_dbus_interface_method(interface, "GetManagedObjects", 0, + get_managed_objects, "a{oa{sa{sv}}}", "", + "objpath_interfaces_and_properties"); + + l_dbus_interface_signal(interface, "InterfacesAdded", 0, "oa{sa{sv}}", + "object_path", "interfaces_and_properties"); + l_dbus_interface_signal(interface, "InterfacesRemoved", 0, "oas", + "object_path", "interfaces"); +} diff --git a/ell/dbus-service.h b/ell/dbus-service.h new file mode 100644 index 0000000..153bb3a --- /dev/null +++ b/ell/dbus-service.h @@ -0,0 +1,93 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_SERVICE_H +#define __ELL_SERVICE_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct l_dbus; +struct l_dbus_interface; +struct l_dbus_message; + +enum l_dbus_method_flag { + L_DBUS_METHOD_FLAG_DEPRECATED = 1, + L_DBUS_METHOD_FLAG_NOREPLY = 2, + L_DBUS_METHOD_FLAG_ASYNC = 4, +}; + +enum l_dbus_signal_flag { + L_DBUS_SIGNAL_FLAG_DEPRECATED = 1, +}; + +enum l_dbus_property_flag { + L_DBUS_PROPERTY_FLAG_DEPRECATED = 1, +}; + +typedef struct l_dbus_message *(*l_dbus_interface_method_cb_t) (struct l_dbus *, + struct l_dbus_message *message, + void *user_data); + +typedef void (*l_dbus_property_complete_cb_t) (struct l_dbus *, + struct l_dbus_message *, + struct l_dbus_message *error); + +typedef struct l_dbus_message *(*l_dbus_property_set_cb_t) (struct l_dbus *, + struct l_dbus_message *message, + struct l_dbus_message_iter *new_value, + l_dbus_property_complete_cb_t complete, + void *user_data); + +typedef bool (*l_dbus_property_get_cb_t) (struct l_dbus *, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data); + +bool l_dbus_interface_method(struct l_dbus_interface *interface, + const char *name, uint32_t flags, + l_dbus_interface_method_cb_t cb, + const char *return_sig, const char *param_sig, + ...); + +bool l_dbus_interface_signal(struct l_dbus_interface *interface, + const char *name, uint32_t flags, + const char *signature, ...); + +bool l_dbus_interface_property(struct l_dbus_interface *interface, + const char *name, uint32_t flags, + const char *signature, + l_dbus_property_get_cb_t getter, + l_dbus_property_set_cb_t setter); + +bool l_dbus_property_changed(struct l_dbus *dbus, const char *path, + const char *interface, const char *property); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_DBUS_SERVICE_H */ diff --git a/ell/dbus-util.c b/ell/dbus-util.c new file mode 100644 index 0000000..acf428c --- /dev/null +++ b/ell/dbus-util.c @@ -0,0 +1,1303 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "dbus.h" +#include "private.h" +#include "dbus-private.h" +#include "string.h" +#include "queue.h" + +#define DBUS_MAX_INTERFACE_LEN 255 +#define DBUS_MAX_METHOD_LEN 255 + +static const char *simple_types = "sogybnqiuxtdh"; + +static int get_alignment(const char type) +{ + switch (type) { + case 'b': + return 4; + case 'y': + return 1; + case 'n': + case 'q': + return 2; + case 'u': + case 'i': + return 4; + case 'x': + case 't': + case 'd': + return 8; + case 's': + case 'o': + return 4; + case 'g': + return 1; + case 'a': + return 4; + case '(': + case '{': + return 8; + case 'v': + return 1; + case 'h': + return 4; + default: + return 0; + } +} + +static int get_basic_size(const char type) +{ + switch (type) { + case 'b': + return 4; + case 'y': + return 1; + case 'n': + case 'q': + return 2; + case 'i': + case 'u': + return 4; + case 'x': + case 't': + return 8; + case 'd': + return 8; + case 'h': + return 4; + default: + return 0; + } +} + +static inline bool is_valid_character(const char c, bool bus_name) +{ + if (c >= 'a' && c <= 'z') + return true; + + if (c >= 'A' && c <= 'Z') + return true; + + if (c >= '0' && c <= '9') + return true; + + if (c == '_') + return true; + + if (c == '-' && bus_name) + return true; + + return false; +} + +bool _dbus_valid_object_path(const char *path) +{ + unsigned int i; + char c = '\0'; + + if (path == NULL) + return false; + + if (path[0] == '\0') + return false; + + if (path[0] && !path[1] && path[0] == '/') + return true; + + if (path[0] != '/') + return false; + + for (i = 0; path[i]; i++) { + if (path[i] == '/' && c == '/') + return false; + + c = path[i]; + + if (is_valid_character(path[i], false) || path[i] == '/') + continue; + + return false; + } + + if (path[i-1] == '/') + return false; + + return true; +} + +static const char *validate_next_type(const char *sig) +{ + char s = *sig; + + if (s == '\0') + return NULL; + + if (strchr(simple_types, s) || s == 'v') + return sig + 1; + + switch (s) { + case 'a': + s = *++sig; + + if (s == '{') { + s = *++sig; + + /* Dictionary keys can only be simple types */ + if (!strchr(simple_types, s)) + return NULL; + + sig = validate_next_type(sig + 1); + + if (!sig) + return NULL; + + if (*sig != '}') + return NULL; + + return sig + 1; + } + + return validate_next_type(sig); + + case '(': + sig++; + + do + sig = validate_next_type(sig); + while (sig && *sig != ')'); + + if (!sig) + return NULL; + + return sig + 1; + } + + return NULL; +} + +static bool valid_dict_signature(const char *sig) +{ + char s = *sig; + + if (s != '{') + return false; + + s = *++sig; + + if (!strchr(simple_types, s)) + return false; + + sig = validate_next_type(sig + 1); + if (!sig) + return false; + + if (sig[0] != '}') + return false; + + if (sig[1] != '\0') + return false; + + return true; +} + +bool _dbus_valid_signature(const char *sig) +{ + const char *s = sig; + + do { + s = validate_next_type(s); + + if (!s) + return false; + } while (*s); + + return true; +} + +int _dbus_num_children(const char *sig) +{ + const char *s = sig; + int num_children = 0; + + do { + s = validate_next_type(s); + + if (!s) + return -1; + + num_children += 1; + } while (*s); + + return num_children; +} + +static bool valid_member_name(const char *start, const char *end, + bool bus_name) +{ + const char *p; + + if ((end - start) < 1) + return false; + + if (*start >= '0' && *start <= '9') + return false; + + for (p = start; p < end; p++) + if (!is_valid_character(*p, bus_name)) + return false; + + return true; +} + +bool _dbus_valid_method(const char *method) +{ + unsigned int i; + + if (!method) + return false; + + if (method[0] == '\0' || strlen(method) > DBUS_MAX_METHOD_LEN) + return false; + + if (method[0] >= '0' && method[0] <= '9') + return false; + + for (i = 0; method[i]; i++) + if (!is_valid_character(method[i], false)) + return false; + + return true; +} + +bool _dbus_valid_interface(const char *interface) +{ + const char *sep; + + if (!interface) + return false; + + if (interface[0] == '\0' || strlen(interface) > DBUS_MAX_INTERFACE_LEN) + return false; + + sep = strchrnul(interface, '.'); + if (*sep == '\0') + return false; + + while (true) { + if (!valid_member_name(interface, sep, false)) + return false; + + if (*sep == '\0') + break; + + interface = sep + 1; + sep = strchrnul(interface, '.'); + } + + return true; +} + +bool _dbus_parse_unique_name(const char *name, uint64_t *out_id) +{ + char *endp = NULL; + uint64_t r; + + if (!l_str_has_prefix(name, ":1.")) + return false; + + errno = 0; + r = strtoull(name + 3, &endp, 10); + if (!endp || endp == name || *endp || errno) + return false; + + if (out_id) + *out_id = r; + + return true; +} + +bool _dbus_valid_bus_name(const char *bus_name) +{ + const char *sep; + + if (!bus_name) + return false; + + if (bus_name[0] == '\0' || strlen(bus_name) > DBUS_MAX_INTERFACE_LEN) + return false; + + if (_dbus_parse_unique_name(bus_name, NULL)) + return true; + + sep = strchrnul(bus_name, '.'); + if (*sep == '\0') + return false; + + while (true) { + if (!valid_member_name(bus_name, sep, true)) + return false; + + if (*sep == '\0') + break; + + bus_name = sep + 1; + sep = strchrnul(bus_name, '.'); + } + + return true; +} + +const char *_dbus_signature_end(const char *signature) +{ + const char *ptr = signature; + unsigned int indent = 0; + char expect; + + switch (*signature) { + case '(': + expect = ')'; + break; + case '{': + expect = '}'; + break; + case 'a': + return _dbus_signature_end(signature + 1); + default: + return signature; + } + + for (ptr = signature; *ptr != '\0'; ptr++) { + if (*ptr == *signature) + indent++; + else if (*ptr == expect) + if (!--indent) + return ptr; + } + + return NULL; +} + +bool _dbus1_header_is_valid(void *data, size_t size) +{ + struct dbus_header *hdr; + size_t header_len; + + if (size < sizeof(struct dbus_header)) + return false; + + hdr = data; + + if (hdr->endian != DBUS_NATIVE_ENDIAN) + header_len = bswap_32(hdr->dbus1.field_length); + else + header_len = hdr->dbus1.field_length; + + header_len += sizeof(struct dbus_header); + return size >= header_len; +} + +static inline void dbus1_iter_init_internal(struct l_dbus_message_iter *iter, + struct l_dbus_message *message, + enum dbus_container_type type, + const char *sig_start, + const char *sig_end, + const void *data, size_t len, + size_t pos) +{ + size_t sig_len; + + iter->message = message; + + if (sig_end) + sig_len = sig_end - sig_start; + else + sig_len = strlen(sig_start); + + iter->sig_start = sig_start; + iter->sig_len = sig_len; + iter->sig_pos = 0; + iter->data = data; + iter->len = pos + len; + iter->pos = pos; + iter->container_type = type; +} + +void _dbus1_iter_init(struct l_dbus_message_iter *iter, + struct l_dbus_message *message, + const char *sig_start, const char *sig_end, + const void *data, size_t len) +{ + dbus1_iter_init_internal(iter, message, DBUS_CONTAINER_TYPE_STRUCT, + sig_start, sig_end, data, len, 0); +} + +static const char *calc_len_next_item(const char *signature, const void *data, + size_t data_pos, size_t data_len, + size_t *out_len) +{ + unsigned int alignment; + size_t pos; + size_t len; + const char *sig_end; + const char *var_sig; + + alignment = get_alignment(*signature); + if (alignment == 0) + return NULL; + + pos = align_len(data_pos, alignment); + if (pos > data_len) + return NULL; + + switch (*signature) { + case 'o': + case 's': + if (pos + 5 > data_len) + return NULL; + + pos += l_get_u32(data + pos) + 5; + break; + case 'g': + if (pos + 2 > data_len) + return NULL; + + pos += l_get_u8(data + pos) + 2; + break; + case 'y': + pos += 1; + break; + case 'n': + case 'q': + pos += 2; + break; + case 'b': + case 'i': + case 'u': + case 'h': + pos += 4; + break; + case 'x': + case 't': + case 'd': + pos += 8; + break; + case 'a': + if (pos + 4 > data_len) + return NULL; + + len = l_get_u32(data + pos); + pos += 4; + + alignment = get_alignment(signature[1]); + pos = align_len(pos, alignment); + pos += len; + + sig_end = _dbus_signature_end(signature) + 1; + goto done; + case '(': + sig_end = signature + 1; + + while (*sig_end != ')') { + sig_end = calc_len_next_item(sig_end, data, pos, + data_len, &len); + + if (!sig_end) + return NULL; + + pos += len; + } + + sig_end += 1; + goto done; + case '{': + sig_end = calc_len_next_item(signature + 1, data, pos, + data_len, &len); + + if (!sig_end) + return NULL; + + pos += len; + + sig_end = calc_len_next_item(sig_end, data, pos, + data_len, &len); + + if (!sig_end) + return NULL; + + pos += len; + sig_end += 1; + goto done; + case 'v': + if (!calc_len_next_item("g", data, pos, data_len, &len)) + return NULL; + + var_sig = data + pos + 1; + pos += len; + + if (!calc_len_next_item(var_sig, data, pos, data_len, &len)) + return NULL; + + pos += len; + break; + default: + return NULL; + } + + sig_end = signature + 1; + +done: + if (pos > data_len) + return NULL; + + *out_len = pos - data_pos; + return sig_end; +} + +bool _dbus1_iter_next_entry_basic(struct l_dbus_message_iter *iter, + char type, void *out) +{ + const char *str_val; + uint8_t uint8_val; + uint16_t uint16_val; + uint32_t uint32_val; + uint64_t uint64_val; + int16_t int16_val; + int32_t int32_val; + int64_t int64_val; + size_t pos; + + if (iter->pos >= iter->len) + return false; + + pos = align_len(iter->pos, get_alignment(type)); + + switch (type) { + case 'o': + case 's': + if (pos + 5 > iter->len) + return false; + uint32_val = l_get_u32(iter->data + pos); + str_val = iter->data + pos + 4; + *(const void **) out = str_val; + iter->pos = pos + uint32_val + 5; + break; + case 'g': + if (pos + 2 > iter->len) + return false; + uint8_val = l_get_u8(iter->data + pos); + str_val = iter->data + pos + 1; + *(const void **) out = str_val; + iter->pos = pos + uint8_val + 2; + break; + case 'b': + if (pos + 4 > iter->len) + return false; + uint32_val = l_get_u32(iter->data + pos); + *(bool *) out = !!uint32_val; + iter->pos = pos + 4; + break; + case 'y': + if (pos + 1 > iter->len) + return false; + uint8_val = l_get_u8(iter->data + pos); + *(uint8_t *) out = uint8_val; + iter->pos = pos + 1; + break; + case 'n': + if (pos + 2 > iter->len) + return false; + int16_val = l_get_s16(iter->data + pos); + *(int16_t *) out = int16_val; + iter->pos = pos + 2; + break; + case 'q': + if (pos + 2 > iter->len) + return false; + uint16_val = l_get_u16(iter->data + pos); + *(uint16_t *) out = uint16_val; + iter->pos = pos + 2; + break; + case 'i': + if (pos + 4 > iter->len) + return false; + int32_val = l_get_s32(iter->data + pos); + *(int32_t *) out = int32_val; + iter->pos = pos + 4; + break; + case 'u': + case 'h': + if (pos + 4 > iter->len) + return false; + uint32_val = l_get_u32(iter->data + pos); + *(uint32_t *) out = uint32_val; + iter->pos = pos + 4; + break; + case 'x': + if (pos + 8 > iter->len) + return false; + int64_val = l_get_s64(iter->data + pos); + *(int64_t *) out= int64_val; + iter->pos = pos + 8; + break; + case 't': + case 'd': + if (pos + 8 > iter->len) + return false; + uint64_val = l_get_u64(iter->data + pos); + *(uint64_t *) out = uint64_val; + iter->pos = pos + 8; + break; + default: + return false; + } + + if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) + iter->sig_pos += 1; + + return true; +} + +bool _dbus1_iter_get_fixed_array(struct l_dbus_message_iter *iter, + void *out, uint32_t *n_elem) +{ + char type; + uint32_t size; + + if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) + return false; + + type = iter->sig_start[iter->sig_pos]; + size = get_basic_size(type); + + /* Fail if the array is not a fixed size or contains file descriptors */ + if (!size || type == 'n') + return false; + + /* + * enter_array should already align us to our container type, so + * there is no need to align pos here + */ + *(const void **) out = iter->data + iter->pos; + *n_elem = (iter->len - iter->pos) / size; + + return true; +} + +bool _dbus1_iter_enter_struct(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *structure) +{ + size_t len; + size_t pos; + const char *sig_start; + const char *sig_end; + bool is_dict = iter->sig_start[iter->sig_pos] == '{'; + bool is_struct = iter->sig_start[iter->sig_pos] == '('; + + if (!is_dict && !is_struct) + return false; + + pos = align_len(iter->pos, 8); + if (pos >= iter->len) + return false; + + sig_start = iter->sig_start + iter->sig_pos + 1; + sig_end = _dbus_signature_end(iter->sig_start + iter->sig_pos); + + if (!calc_len_next_item(iter->sig_start + iter->sig_pos, + iter->data, pos, iter->len, &len)) + return false; + + dbus1_iter_init_internal(structure, iter->message, + DBUS_CONTAINER_TYPE_STRUCT, + sig_start, sig_end, iter->data, + len, pos); + + if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) + iter->sig_pos += sig_end - sig_start + 2; + + iter->pos = pos + len; + + return true; +} + +bool _dbus1_iter_enter_variant(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *variant) +{ + size_t pos; + uint8_t sig_len; + size_t len; + const char *sig_start; + + if (iter->sig_start[iter->sig_pos] != 'v') + return false; + + pos = align_len(iter->pos, 1); + if (pos + 2 > iter->len) + return false; + + sig_len = l_get_u8(iter->data + pos); + sig_start = iter->data + pos + 1; + + if (!calc_len_next_item(sig_start, iter->data, pos + sig_len + 2, + iter->len, &len)) + return false; + + dbus1_iter_init_internal(variant, iter->message, + DBUS_CONTAINER_TYPE_VARIANT, + sig_start, NULL, iter->data, + len, pos + sig_len + 2); + + if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) + iter->sig_pos += 1; + + iter->pos = pos + sig_len + 2 + len; + + return true; +} + +bool _dbus1_iter_enter_array(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *array) +{ + size_t pos; + size_t len; + const char *sig_start; + const char *sig_end; + + if (iter->sig_start[iter->sig_pos] != 'a') + return false; + + sig_start = iter->sig_start + iter->sig_pos + 1; + sig_end = _dbus_signature_end(sig_start) + 1; + + pos = align_len(iter->pos, 4); + if (pos + 4 > iter->len) + return false; + + len = l_get_u32(iter->data + pos); + pos += 4; + + pos = align_len(pos, get_alignment(*sig_start)); + dbus1_iter_init_internal(array, iter->message, + DBUS_CONTAINER_TYPE_ARRAY, + sig_start, sig_end, + iter->data, len, pos); + + if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) + iter->sig_pos += sig_end - sig_start + 1; + + iter->pos = pos + len; + + return true; +} + +bool _dbus1_iter_skip_entry(struct l_dbus_message_iter *iter) +{ + size_t len; + const char *sig_end; + + sig_end = calc_len_next_item(iter->sig_start + iter->sig_pos, + iter->data, iter->pos, iter->len, &len); + if (!sig_end) + return false; + + iter->pos += len; + iter->sig_pos = sig_end - iter->sig_start; + + return true; +} + +struct dbus_builder { + struct l_string *signature; + void *body; + size_t body_size; + size_t body_pos; + struct l_queue *containers; + struct { + struct container *container; + int sig_end; + size_t body_pos; + } mark; +}; + +struct container { + size_t start; + enum dbus_container_type type; + char signature[256]; + uint8_t sigindex; +}; + +static struct container *container_new(enum dbus_container_type type, + const char *signature, size_t start) +{ + struct container *ret; + + ret = l_new(struct container, 1); + + ret->type = type; + strcpy(ret->signature, signature); + ret->start = start; + + return ret; +} + +static void container_free(struct container *container) +{ + l_free(container); +} + +static inline size_t grow_body(struct dbus_builder *builder, + size_t len, unsigned int alignment) +{ + size_t size = align_len(builder->body_pos, alignment); + + if (size + len > builder->body_size) { + builder->body = l_realloc(builder->body, size + len); + builder->body_size = size + len; + } + + if (size - builder->body_pos > 0) + memset(builder->body + builder->body_pos, 0, + size - builder->body_pos); + + builder->body_pos = size + len; + + return size; +} + +struct dbus_builder *_dbus1_builder_new(void *body, size_t body_size) +{ + struct dbus_builder *builder; + struct container *root; + + builder = l_new(struct dbus_builder, 1); + builder->signature = l_string_new(63); + + builder->containers = l_queue_new(); + root = container_new(DBUS_CONTAINER_TYPE_STRUCT, "", 0); + l_queue_push_head(builder->containers, root); + + builder->body = body; + builder->body_size = body_size; + builder->body_pos = body_size; + + builder->mark.container = root; + builder->mark.sig_end = 0; + builder->mark.body_pos = 0; + + return builder; +} + +void _dbus1_builder_free(struct dbus_builder *builder) +{ + if (unlikely(!builder)) + return; + + l_string_free(builder->signature); + l_queue_destroy(builder->containers, + (l_queue_destroy_func_t) container_free); + l_free(builder->body); + + l_free(builder); +} + +bool _dbus1_builder_append_basic(struct dbus_builder *builder, + char type, const void *value) +{ + struct container *container = l_queue_peek_head(builder->containers); + size_t start; + unsigned int alignment; + size_t len; + + if (unlikely(!builder)) + return false; + + if (unlikely(!strchr(simple_types, type))) + return false; + + alignment = get_alignment(type); + if (!alignment) + return false; + + if (l_queue_length(builder->containers) == 1) + l_string_append_c(builder->signature, type); + else if (container->signature[container->sigindex] != type) + return false; + + len = get_basic_size(type); + + if (len) { + uint32_t b; + + start = grow_body(builder, len, alignment); + + if (type == 'b') { + b = *(bool *)value; + memcpy(builder->body + start, &b, len); + } else + memcpy(builder->body + start, value, len); + + if (container->type != DBUS_CONTAINER_TYPE_ARRAY) + container->sigindex += 1; + + return true; + } + + len = strlen(value); + + if (type == 'g') { + start = grow_body(builder, len + 2, 1); + l_put_u8(len, builder->body + start); + strcpy(builder->body + start + 1, value); + } else { + start = grow_body(builder, len + 5, 4); + l_put_u32(len, builder->body + start); + strcpy(builder->body + start + 4, value); + } + + if (container->type != DBUS_CONTAINER_TYPE_ARRAY) + container->sigindex += 1; + + return true; +} + +static bool enter_struct_dict_common(struct dbus_builder *builder, + const char *signature, + enum dbus_container_type type, + const char open, + const char close) +{ + size_t qlen = l_queue_length(builder->containers); + struct container *container = l_queue_peek_head(builder->containers); + size_t start; + + if (qlen == 1) { + if (l_string_length(builder->signature) + + strlen(signature) + 2 > 255) + return false; + } else { + /* Verify Signatures Match */ + char expect[256]; + const char *start; + const char *end; + + start = container->signature + container->sigindex; + end = _dbus_signature_end(start); + + if (*start != open || *end != close) + return false; + + memcpy(expect, start + 1, end - start - 1); + expect[end - start - 1] = '\0'; + + if (strcmp(expect, signature)) + return false; + } + + start = grow_body(builder, 0, 8); + + container = container_new(type, signature, start); + l_queue_push_head(builder->containers, container); + + return true; +} + +bool _dbus1_builder_enter_struct(struct dbus_builder *builder, + const char *signature) +{ + if (!_dbus_valid_signature(signature)) + return false; + + return enter_struct_dict_common(builder, signature, + DBUS_CONTAINER_TYPE_STRUCT, '(', ')'); +} + +bool _dbus1_builder_enter_dict(struct dbus_builder *builder, + const char *signature) +{ + if (_dbus_num_children(signature) != 2) + return false; + + if (!strchr(simple_types, signature[0])) + return false; + + return enter_struct_dict_common(builder, signature, + DBUS_CONTAINER_TYPE_DICT_ENTRY, + '{', '}'); +} + +static bool leave_struct_dict_common(struct dbus_builder *builder, + enum dbus_container_type type, + const char open, + const char close) +{ + struct container *container = l_queue_peek_head(builder->containers); + size_t qlen = l_queue_length(builder->containers); + struct container *parent; + + if (unlikely(qlen <= 1)) + return false; + + if (unlikely(container->type != type)) + return false; + + l_queue_pop_head(builder->containers); + qlen -= 1; + parent = l_queue_peek_head(builder->containers); + + if (qlen == 1) + l_string_append_printf(builder->signature, "%c%s%c", + open, + container->signature, + close); + else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) + parent->sigindex += strlen(container->signature) + 2; + + container_free(container); + + return true; +} + +bool _dbus1_builder_leave_struct(struct dbus_builder *builder) +{ + return leave_struct_dict_common(builder, DBUS_CONTAINER_TYPE_STRUCT, + '(', ')'); +} + +bool _dbus1_builder_leave_dict(struct dbus_builder *builder) +{ + return leave_struct_dict_common(builder, + DBUS_CONTAINER_TYPE_DICT_ENTRY, + '{', '}'); +} + +bool _dbus1_builder_enter_variant(struct dbus_builder *builder, + const char *signature) +{ + size_t qlen = l_queue_length(builder->containers); + struct container *container = l_queue_peek_head(builder->containers); + size_t start; + size_t siglen; + + if (_dbus_num_children(signature) != 1) + return false; + + if (qlen == 1) { + if (l_string_length(builder->signature) + 1 > 255) + return false; + } else if (container->signature[container->sigindex] != 'v') + return false; + + siglen = strlen(signature); + start = grow_body(builder, siglen + 2, 1); + l_put_u8(siglen, builder->body + start); + strcpy(builder->body + start + 1, signature); + + container = container_new(DBUS_CONTAINER_TYPE_VARIANT, + signature, start); + l_queue_push_head(builder->containers, container); + + return true; +} + +bool _dbus1_builder_leave_variant(struct dbus_builder *builder) +{ + struct container *container = l_queue_peek_head(builder->containers); + size_t qlen = l_queue_length(builder->containers); + struct container *parent; + + if (unlikely(qlen <= 1)) + return false; + + if (unlikely(container->type != DBUS_CONTAINER_TYPE_VARIANT)) + return false; + + l_queue_pop_head(builder->containers); + qlen -= 1; + parent = l_queue_peek_head(builder->containers); + + if (qlen == 1) + l_string_append_c(builder->signature, 'v'); + else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) + parent->sigindex += 1; + + container_free(container); + + return true; +} + +bool _dbus1_builder_enter_array(struct dbus_builder *builder, + const char *signature) +{ + size_t qlen = l_queue_length(builder->containers); + struct container *container = l_queue_peek_head(builder->containers); + size_t start; + int alignment; + + if (_dbus_num_children(signature) != 1 && + !valid_dict_signature(signature)) + return false; + + if (qlen == 1) { + if (l_string_length(builder->signature) + + strlen(signature) + 1 > 255) + return false; + } else { + /* Verify Signatures Match */ + char expect[256]; + const char *start; + const char *end; + + start = container->signature + container->sigindex; + end = validate_next_type(start); + + if (*start != 'a') + return false; + + memcpy(expect, start + 1, end - start - 1); + expect[end - start - 1] = '\0'; + + if (strcmp(expect, signature)) + return false; + } + + /* First grow the body enough to cover preceding length */ + start = grow_body(builder, 4, 4); + + /* Now align to element alignment */ + alignment = get_alignment(*signature); + grow_body(builder, 0, alignment); + + container = container_new(DBUS_CONTAINER_TYPE_ARRAY, signature, start); + l_queue_push_head(builder->containers, container); + + return true; +} + +bool _dbus1_builder_leave_array(struct dbus_builder *builder) +{ + struct container *container = l_queue_peek_head(builder->containers); + size_t qlen = l_queue_length(builder->containers); + struct container *parent; + size_t alignment; + size_t array_start; + + if (unlikely(qlen <= 1)) + return false; + + if (unlikely(container->type != DBUS_CONTAINER_TYPE_ARRAY)) + return false; + + l_queue_pop_head(builder->containers); + qlen -= 1; + parent = l_queue_peek_head(builder->containers); + + if (qlen == 1) + l_string_append_printf(builder->signature, "a%s", + container->signature); + else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) + parent->sigindex += strlen(container->signature) + 1; + + /* Update array length */ + alignment = get_alignment(container->signature[0]); + array_start = align_len(container->start + 4, alignment); + + l_put_u32(builder->body_pos - array_start, + builder->body + container->start); + + container_free(container); + + return true; +} + +bool _dbus1_builder_mark(struct dbus_builder *builder) +{ + struct container *container = l_queue_peek_head(builder->containers); + + builder->mark.container = container; + + if (l_queue_length(builder->containers) == 1) + builder->mark.sig_end = l_string_length(builder->signature); + else + builder->mark.sig_end = container->sigindex; + + builder->mark.body_pos = builder->body_pos; + + return true; +} + +bool _dbus1_builder_rewind(struct dbus_builder *builder) +{ + struct container *container; + + while ((container = l_queue_peek_head(builder->containers)) != + builder->mark.container) { + container_free(container); + l_queue_pop_head(builder->containers); + } + + builder->body_pos = builder->mark.body_pos; + + if (l_queue_length(builder->containers) == 1) + l_string_truncate(builder->signature, builder->mark.sig_end); + else + container->sigindex = builder->mark.sig_end; + + return true; +} + +char *_dbus1_builder_finish(struct dbus_builder *builder, + void **body, size_t *body_size) +{ + char *signature; + + if (unlikely(!builder)) + return NULL; + + if (unlikely(l_queue_length(builder->containers) != 1)) + return NULL; + + signature = l_string_unwrap(builder->signature); + builder->signature = NULL; + + *body = builder->body; + *body_size = builder->body_pos; + builder->body = NULL; + builder->body_size = 0; + + return signature; +} diff --git a/ell/dbus.c b/ell/dbus.c new file mode 100644 index 0000000..845ccb3 --- /dev/null +++ b/ell/dbus.c @@ -0,0 +1,1812 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "io.h" +#include "idle.h" +#include "queue.h" +#include "hashmap.h" +#include "dbus.h" +#include "private.h" +#include "dbus-private.h" + +#define DEFAULT_SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" + +#define DBUS_SERVICE_DBUS "org.freedesktop.DBus" + +#define DBUS_PATH_DBUS "/org/freedesktop/DBus" + +#define DBUS_MAXIMUM_MATCH_RULE_LENGTH 1024 + +enum auth_state { + WAITING_FOR_OK, + WAITING_FOR_AGREE_UNIX_FD, + SETUP_DONE +}; + +struct l_dbus_ops { + char version; + bool (*send_message)(struct l_dbus *bus, + struct l_dbus_message *message); + struct l_dbus_message *(*recv_message)(struct l_dbus *bus); + void (*free)(struct l_dbus *bus); + struct _dbus_name_ops name_ops; + struct _dbus_filter_ops filter_ops; + uint32_t (*name_acquire)(struct l_dbus *dbus, const char *name, + bool allow_replacement, bool replace_existing, + bool queue, l_dbus_name_acquire_func_t callback, + void *user_data); +}; + +struct l_dbus { + struct l_io *io; + char *guid; + bool negotiate_unix_fd; + bool support_unix_fd; + bool is_ready; + char *unique_name; + unsigned int next_id; + uint32_t next_serial; + struct l_queue *message_queue; + struct l_hashmap *message_list; + struct l_hashmap *signal_list; + l_dbus_ready_func_t ready_handler; + l_dbus_destroy_func_t ready_destroy; + void *ready_data; + l_dbus_disconnect_func_t disconnect_handler; + l_dbus_destroy_func_t disconnect_destroy; + void *disconnect_data; + l_dbus_debug_func_t debug_handler; + l_dbus_destroy_func_t debug_destroy; + void *debug_data; + struct _dbus_object_tree *tree; + struct _dbus_name_cache *name_cache; + struct _dbus_filter *filter; + bool name_notify_enabled; + + const struct l_dbus_ops *driver; +}; + +struct l_dbus_classic { + struct l_dbus super; + void *auth_command; + enum auth_state auth_state; + struct l_hashmap *match_strings; + int *fd_buf; + unsigned int num_fds; +}; + +struct message_callback { + uint32_t serial; + struct l_dbus_message *message; + l_dbus_message_func_t callback; + l_dbus_destroy_func_t destroy; + void *user_data; +}; + +struct signal_callback { + unsigned int id; + l_dbus_message_func_t callback; + l_dbus_destroy_func_t destroy; + void *user_data; +}; + +static void message_queue_destroy(void *data) +{ + struct message_callback *callback = data; + + l_dbus_message_unref(callback->message); + + if (callback->destroy) + callback->destroy(callback->user_data); + + l_free(callback); +} + +static void message_list_destroy(void *value) +{ + message_queue_destroy(value); +} + +static void signal_list_destroy(void *value) +{ + struct signal_callback *callback = value; + + if (callback->destroy) + callback->destroy(callback->user_data); + + l_free(callback); +} + +static bool message_write_handler(struct l_io *io, void *user_data) +{ + struct l_dbus *dbus = user_data; + struct l_dbus_message *message; + struct message_callback *callback; + const void *header, *body; + size_t header_size, body_size; + + callback = l_queue_pop_head(dbus->message_queue); + if (!callback) + return false; + + message = callback->message; + if (_dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL && + callback->callback == NULL) + l_dbus_message_set_no_reply(message, true); + + _dbus_message_set_serial(message, callback->serial); + + if (!dbus->driver->send_message(dbus, message)) { + message_queue_destroy(callback); + return false; + } + + header = _dbus_message_get_header(message, &header_size); + body = _dbus_message_get_body(message, &body_size); + l_util_hexdump_two(false, header, header_size, body, body_size, + dbus->debug_handler, dbus->debug_data); + + if (callback->callback == NULL) { + message_queue_destroy(callback); + goto done; + } + + l_hashmap_insert(dbus->message_list, + L_UINT_TO_PTR(callback->serial), callback); + +done: + if (l_queue_isempty(dbus->message_queue)) + return false; + + /* Only continue sending messges if the connection is ready */ + return dbus->is_ready; +} + +static void handle_method_return(struct l_dbus *dbus, + struct l_dbus_message *message) +{ + struct message_callback *callback; + uint32_t reply_serial; + + reply_serial = _dbus_message_get_reply_serial(message); + if (reply_serial == 0) + return; + + callback = l_hashmap_remove(dbus->message_list, + L_UINT_TO_PTR(reply_serial)); + if (!callback) + return; + + if (callback->callback) + callback->callback(message, callback->user_data); + + message_queue_destroy(callback); +} + +static void handle_error(struct l_dbus *dbus, struct l_dbus_message *message) +{ + struct message_callback *callback; + uint32_t reply_serial; + + reply_serial = _dbus_message_get_reply_serial(message); + if (reply_serial == 0) + return; + + callback = l_hashmap_remove(dbus->message_list, + L_UINT_TO_PTR(reply_serial)); + if (!callback) + return; + + if (callback->callback) + callback->callback(message, callback->user_data); + + message_queue_destroy(callback); +} + +static void process_signal(const void *key, void *value, void *user_data) +{ + struct signal_callback *callback = value; + struct l_dbus_message *message = user_data; + + if (callback->callback) + callback->callback(message, callback->user_data); +} + +static void handle_signal(struct l_dbus *dbus, struct l_dbus_message *message) +{ + l_hashmap_foreach(dbus->signal_list, process_signal, message); +} + +static bool message_read_handler(struct l_io *io, void *user_data) +{ + struct l_dbus *dbus = user_data; + struct l_dbus_message *message; + const void *header, *body; + size_t header_size, body_size; + enum dbus_message_type msgtype; + + message = dbus->driver->recv_message(dbus); + if (!message) + return true; + + header = _dbus_message_get_header(message, &header_size); + body = _dbus_message_get_body(message, &body_size); + l_util_hexdump_two(true, header, header_size, body, body_size, + dbus->debug_handler, dbus->debug_data); + + msgtype = _dbus_message_get_type(message); + + switch (msgtype) { + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + handle_method_return(dbus, message); + break; + case DBUS_MESSAGE_TYPE_ERROR: + handle_error(dbus, message); + break; + case DBUS_MESSAGE_TYPE_SIGNAL: + handle_signal(dbus, message); + break; + case DBUS_MESSAGE_TYPE_METHOD_CALL: + if (!_dbus_object_tree_dispatch(dbus->tree, dbus, message)) { + struct l_dbus_message *error; + + error = l_dbus_message_new_error(message, + "org.freedesktop.DBus.Error.NotFound", + "No matching method found"); + l_dbus_send(dbus, error); + } + + break; + } + + l_dbus_message_unref(message); + + return true; +} + +static uint32_t send_message(struct l_dbus *dbus, bool priority, + struct l_dbus_message *message, + l_dbus_message_func_t function, + void *user_data, l_dbus_destroy_func_t destroy) +{ + struct message_callback *callback; + enum dbus_message_type type; + const char *path; + + type = _dbus_message_get_type(message); + + if ((type == DBUS_MESSAGE_TYPE_METHOD_RETURN || + type == DBUS_MESSAGE_TYPE_ERROR) && + _dbus_message_get_reply_serial(message) == 0) { + l_dbus_message_unref(message); + return 0; + } + + /* Default empty signature for method return messages */ + if (type == DBUS_MESSAGE_TYPE_METHOD_RETURN && + !l_dbus_message_get_signature(message)) + l_dbus_message_set_arguments(message, ""); + + callback = l_new(struct message_callback, 1); + + callback->serial = dbus->next_serial++; + callback->message = message; + callback->callback = function; + callback->destroy = destroy; + callback->user_data = user_data; + + if (priority) { + l_queue_push_head(dbus->message_queue, callback); + + l_io_set_write_handler(dbus->io, message_write_handler, + dbus, NULL); + + return callback->serial; + } + + path = l_dbus_message_get_path(message); + if (path) + _dbus_object_tree_signals_flush(dbus, path); + + l_queue_push_tail(dbus->message_queue, callback); + + if (dbus->is_ready) + l_io_set_write_handler(dbus->io, message_write_handler, + dbus, NULL); + + return callback->serial; +} + +static void bus_ready(struct l_dbus *dbus) +{ + dbus->is_ready = true; + + if (dbus->ready_handler) + dbus->ready_handler(dbus->ready_data); + + l_io_set_read_handler(dbus->io, message_read_handler, dbus, NULL); + + /* Check for messages added before the connection was ready */ + if (l_queue_isempty(dbus->message_queue)) + return; + + l_io_set_write_handler(dbus->io, message_write_handler, dbus, NULL); +} + +static void hello_callback(struct l_dbus_message *message, void *user_data) +{ + struct l_dbus *dbus = user_data; + const char *signature; + const char *unique_name; + + signature = l_dbus_message_get_signature(message); + if (!signature || strcmp(signature, "s")) { + close(l_io_get_fd(dbus->io)); + return; + } + + if (!l_dbus_message_get_arguments(message, "s", &unique_name)) { + close(l_io_get_fd(dbus->io)); + return; + } + + dbus->unique_name = l_strdup(unique_name); + + bus_ready(dbus); +} + +static bool auth_write_handler(struct l_io *io, void *user_data) +{ + struct l_dbus_classic *classic = user_data; + struct l_dbus *dbus = &classic->super; + ssize_t written, len; + int fd; + + fd = l_io_get_fd(io); + + if (!classic->auth_command) + return false; + + len = strlen(classic->auth_command); + if (!len) + return false; + + written = L_TFR(send(fd, classic->auth_command, len, 0)); + if (written < 0) + return false; + + l_util_hexdump(false, classic->auth_command, written, + dbus->debug_handler, dbus->debug_data); + + if (written < len) { + memmove(classic->auth_command, classic->auth_command + written, + len + 1 - written); + return true; + } + + l_free(classic->auth_command); + classic->auth_command = NULL; + + if (classic->auth_state == SETUP_DONE) { + struct l_dbus_message *message; + + l_io_set_read_handler(dbus->io, message_read_handler, + dbus, NULL); + + message = l_dbus_message_new_method_call(dbus, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + L_DBUS_INTERFACE_DBUS, + "Hello"); + l_dbus_message_set_arguments(message, ""); + + send_message(dbus, true, message, hello_callback, dbus, NULL); + + return true; + } + + return false; +} + +static bool auth_read_handler(struct l_io *io, void *user_data) +{ + struct l_dbus_classic *classic = user_data; + struct l_dbus *dbus = &classic->super; + char buffer[64]; + char *ptr, *end; + ssize_t offset, len; + int fd; + + fd = l_io_get_fd(io); + + ptr = buffer; + offset = 0; + + while (1) { + len = L_TFR(recv(fd, ptr + offset, + sizeof(buffer) - offset, + MSG_DONTWAIT)); + if (len < 0) { + if (errno != EAGAIN) + return false; + + break; + } + + offset += len; + } + + ptr = buffer; + len = offset; + + if (!ptr || len < 3) + return true; + + end = strstr(ptr, "\r\n"); + if (!end) + return true; + + if (end - ptr + 2 != len) + return true; + + l_util_hexdump(true, ptr, len, dbus->debug_handler, dbus->debug_data); + + *end = '\0'; + + switch (classic->auth_state) { + case WAITING_FOR_OK: + if (!strncmp(ptr, "OK ", 3)) { + enum auth_state state; + const char *command; + + if (dbus->negotiate_unix_fd) { + command = "NEGOTIATE_UNIX_FD\r\n"; + state = WAITING_FOR_AGREE_UNIX_FD; + } else { + command = "BEGIN\r\n"; + state = SETUP_DONE; + } + + l_free(dbus->guid); + dbus->guid = l_strdup(ptr + 3); + + classic->auth_command = l_strdup(command); + classic->auth_state = state; + break; + } else if (!strncmp(ptr, "REJECTED ", 9)) { + static const char *command = "AUTH ANONYMOUS\r\n"; + + dbus->negotiate_unix_fd = true; + + classic->auth_command = l_strdup(command); + classic->auth_state = WAITING_FOR_OK; + } + break; + + case WAITING_FOR_AGREE_UNIX_FD: + if (!strncmp(ptr, "AGREE_UNIX_FD", 13)) { + static const char *command = "BEGIN\r\n"; + + dbus->support_unix_fd = true; + + classic->auth_command = l_strdup(command); + classic->auth_state = SETUP_DONE; + break; + } else if (!strncmp(ptr, "ERROR", 5)) { + static const char *command = "BEGIN\r\n"; + + dbus->support_unix_fd = false; + + classic->auth_command = l_strdup(command); + classic->auth_state = SETUP_DONE; + break; + } + break; + + case SETUP_DONE: + break; + } + + l_io_set_write_handler(io, auth_write_handler, dbus, NULL); + + return true; +} + +static void disconnect_handler(struct l_io *io, void *user_data) +{ + struct l_dbus *dbus = user_data; + + dbus->is_ready = false; + + l_util_debug(dbus->debug_handler, dbus->debug_data, "disconnect"); + + if (dbus->disconnect_handler) + dbus->disconnect_handler(dbus->disconnect_data); +} + +static void dbus_init(struct l_dbus *dbus, int fd) +{ + dbus->io = l_io_new(fd); + l_io_set_close_on_destroy(dbus->io, true); + l_io_set_disconnect_handler(dbus->io, disconnect_handler, dbus, NULL); + + dbus->is_ready = false; + dbus->next_id = 1; + dbus->next_serial = 1; + + dbus->message_queue = l_queue_new(); + dbus->message_list = l_hashmap_new(); + dbus->signal_list = l_hashmap_new(); + + dbus->tree = _dbus_object_tree_new(); +} + +static void classic_free(struct l_dbus *dbus) +{ + struct l_dbus_classic *classic = + l_container_of(dbus, struct l_dbus_classic, super); + unsigned int i; + + for (i = 0; i < classic->num_fds; i++) + close(classic->fd_buf[i]); + l_free(classic->fd_buf); + + l_free(classic->auth_command); + l_hashmap_destroy(classic->match_strings, l_free); + l_free(classic); +} + +static bool classic_send_message(struct l_dbus *dbus, + struct l_dbus_message *message) +{ + int fd = l_io_get_fd(dbus->io); + struct msghdr msg; + struct iovec iov[2], *iovpos; + ssize_t r; + int *fds = NULL; + uint32_t num_fds = 0; + struct cmsghdr *cmsg; + int iovlen; + + iov[0].iov_base = _dbus_message_get_header(message, &iov[0].iov_len); + iov[1].iov_base = _dbus_message_get_body(message, &iov[1].iov_len); + + if (dbus->support_unix_fd) + fds = _dbus_message_get_fds(message, &num_fds); + + iovpos = iov; + iovlen = 2; + + while (1) { + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iovpos; + msg.msg_iovlen = iovlen; + + if (num_fds) { + msg.msg_control = + alloca(CMSG_SPACE(num_fds * sizeof(int))); + msg.msg_controllen = CMSG_LEN(num_fds * sizeof(int)); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = msg.msg_controllen; + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(cmsg), fds, num_fds * sizeof(int)); + } + + r = L_TFR(sendmsg(fd, &msg, 0)); + if (r < 0) + return false; + + while ((size_t) r >= iovpos->iov_len) { + r -= iovpos->iov_len; + iovpos++; + iovlen--; + + if (!iovlen) + break; + } + + if (!iovlen) + break; + + iovpos->iov_base += r; + iovpos->iov_len -= r; + + /* The FDs have been transmitted, don't retransmit */ + num_fds = 0; + } + + return true; +} + +static struct l_dbus_message *classic_recv_message(struct l_dbus *dbus) +{ + struct l_dbus_classic *classic = + l_container_of(dbus, struct l_dbus_classic, super); + int fd = l_io_get_fd(dbus->io); + struct dbus_header hdr; + struct msghdr msg; + struct iovec iov[2], *iovpos; + struct cmsghdr *cmsg; + ssize_t len, r; + void *header, *body; + size_t header_size, body_size; + union { + uint8_t bytes[CMSG_SPACE(16 * sizeof(int))]; + struct cmsghdr align; + } fd_buf; + int *fds = NULL; + uint32_t num_fds = 0; + int iovlen; + struct l_dbus_message *message; + unsigned int i; + + len = recv(fd, &hdr, DBUS_HEADER_SIZE, MSG_PEEK | MSG_DONTWAIT); + if (len != DBUS_HEADER_SIZE) + return NULL; + + header_size = align_len(DBUS_HEADER_SIZE + hdr.dbus1.field_length, 8); + header = l_malloc(header_size); + + body_size = hdr.dbus1.body_length; + body = l_malloc(body_size); + + iov[0].iov_base = header; + iov[0].iov_len = header_size; + iov[1].iov_base = body; + iov[1].iov_len = body_size; + + iovpos = iov; + iovlen = 2; + + while (1) { + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iovpos; + msg.msg_iovlen = iovlen; + msg.msg_control = &fd_buf; + msg.msg_controllen = sizeof(fd_buf); + + r = L_TFR(recvmsg(fd, &msg, + MSG_CMSG_CLOEXEC | MSG_WAITALL)); + if (r < 0) + goto cmsg_fail; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) + continue; + + num_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + fds = (void *) CMSG_DATA(cmsg); + + /* Set FD_CLOEXEC on all file descriptors */ + for (i = 0; i < num_fds; i++) { + long flags; + + flags = fcntl(fds[i], F_GETFD, NULL); + if (flags < 0) + continue; + + if (!(flags & FD_CLOEXEC)) + fcntl(fds[i], F_SETFD, + flags | FD_CLOEXEC); + } + + classic->fd_buf = l_realloc(classic->fd_buf, + (classic->num_fds + num_fds) * + sizeof(int)); + memcpy(classic->fd_buf + classic->num_fds, fds, + num_fds * sizeof(int)); + classic->num_fds += num_fds; + } + + while ((size_t) r >= iovpos->iov_len) { + r -= iovpos->iov_len; + iovpos++; + iovlen--; + + if (!iovlen) + break; + } + + if (!iovlen) + break; + + iovpos->iov_base += r; + iovpos->iov_len -= r; + } + + if (hdr.endian != DBUS_NATIVE_ENDIAN) { + l_util_debug(dbus->debug_handler, + dbus->debug_data, "Endianness incorrect"); + goto bad_msg; + } + + if (hdr.version != 1) { + l_util_debug(dbus->debug_handler, + dbus->debug_data, "Protocol version incorrect"); + goto bad_msg; + } + + num_fds = _dbus_message_unix_fds_from_header(header, header_size); + if (num_fds > classic->num_fds) + goto bad_msg; + + message = dbus_message_build(header, header_size, body, body_size, + classic->fd_buf, num_fds); + + if (message && num_fds) { + if (classic->num_fds > num_fds) { + memmove(classic->fd_buf, classic->fd_buf + num_fds, + (classic->num_fds - num_fds) * sizeof(int)); + classic->num_fds -= num_fds; + } else { + l_free(classic->fd_buf); + + classic->fd_buf = NULL; + classic->num_fds = 0; + } + } + + if (message) + return message; + +bad_msg: +cmsg_fail: + for (i = 0; i < classic->num_fds; i++) + close(classic->fd_buf[i]); + + l_free(classic->fd_buf); + + classic->fd_buf = NULL; + classic->num_fds = 0; + + l_free(header); + l_free(body); + + return NULL; +} + +static bool classic_add_match(struct l_dbus *dbus, unsigned int id, + const struct _dbus_filter_condition *rule, + int rule_len) +{ + struct l_dbus_classic *classic = + l_container_of(dbus, struct l_dbus_classic, super); + char *match_str; + struct l_dbus_message *message; + + match_str = _dbus_filter_rule_to_str(rule, rule_len); + + l_hashmap_insert(classic->match_strings, L_UINT_TO_PTR(id), match_str); + + message = l_dbus_message_new_method_call(dbus, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + L_DBUS_INTERFACE_DBUS, + "AddMatch"); + + l_dbus_message_set_arguments(message, "s", match_str); + + send_message(dbus, false, message, NULL, NULL, NULL); + + return true; +} + +static bool classic_remove_match(struct l_dbus *dbus, unsigned int id) +{ + struct l_dbus_classic *classic = + l_container_of(dbus, struct l_dbus_classic, super); + char *match_str = l_hashmap_remove(classic->match_strings, + L_UINT_TO_PTR(id)); + struct l_dbus_message *message; + + if (!match_str) + return false; + + message = l_dbus_message_new_method_call(dbus, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + L_DBUS_INTERFACE_DBUS, + "RemoveMatch"); + + l_dbus_message_set_arguments(message, "s", match_str); + + send_message(dbus, false, message, NULL, NULL, NULL); + + l_free(match_str); + + return true; +} + +static void name_owner_changed_cb(struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus *dbus = user_data; + char *name, *old, *new; + + if (!l_dbus_message_get_arguments(message, "sss", &name, &old, &new)) + return; + + _dbus_name_cache_notify(dbus->name_cache, name, new); +} + +struct get_name_owner_request { + struct l_dbus_message *message; + struct l_dbus *dbus; +}; + +static void get_name_owner_reply_cb(struct l_dbus_message *reply, + void *user_data) +{ + struct get_name_owner_request *req = user_data; + const char *name, *owner; + + /* No name owner yet */ + if (l_dbus_message_is_error(reply)) + return; + + /* Shouldn't happen */ + if (!l_dbus_message_get_arguments(reply, "s", &owner)) + return; + + /* Shouldn't happen */ + if (!l_dbus_message_get_arguments(req->message, "s", &name)) + return; + + _dbus_name_cache_notify(req->dbus->name_cache, name, owner); +} + +static bool classic_get_name_owner(struct l_dbus *bus, const char *name) +{ + struct get_name_owner_request *req; + + /* Name resolution is not performed for DBUS_SERVICE_DBUS */ + if (!strcmp(name, DBUS_SERVICE_DBUS)) + return false; + + req = l_new(struct get_name_owner_request, 1); + req->dbus = bus; + req->message = l_dbus_message_new_method_call(bus, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + L_DBUS_INTERFACE_DBUS, + "GetNameOwner"); + + l_dbus_message_set_arguments(req->message, "s", name); + + send_message(bus, false, req->message, get_name_owner_reply_cb, + req, l_free); + + if (!bus->name_notify_enabled) { + static struct _dbus_filter_condition rule[] = { + { L_DBUS_MATCH_TYPE, "signal" }, + { L_DBUS_MATCH_SENDER, DBUS_SERVICE_DBUS }, + { L_DBUS_MATCH_PATH, DBUS_PATH_DBUS }, + { L_DBUS_MATCH_INTERFACE, L_DBUS_INTERFACE_DBUS }, + { L_DBUS_MATCH_MEMBER, "NameOwnerChanged" }, + }; + + if (!bus->filter) + bus->filter = _dbus_filter_new(bus, + &bus->driver->filter_ops, + bus->name_cache); + + _dbus_filter_add_rule(bus->filter, rule, L_ARRAY_SIZE(rule), + name_owner_changed_cb, bus); + + bus->name_notify_enabled = true; + } + + return true; +} + +struct name_request { + l_dbus_name_acquire_func_t callback; + void *user_data; + struct l_dbus *dbus; +}; + +enum dbus_name_flag { + DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x1, + DBUS_NAME_FLAG_REPLACE_EXISTING = 0x2, + DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x4, +}; + +enum dbus_name_reply { + DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1, + DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 2, + DBUS_REQUEST_NAME_REPLY_EXISTS = 3, + DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 4, +}; + +static void request_name_reply_cb(struct l_dbus_message *reply, void *user_data) +{ + struct name_request *req = user_data; + bool success = false, queued = false; + uint32_t retval; + + if (!req->callback) + return; + + /* No name owner yet */ + if (l_dbus_message_is_error(reply)) + goto call_back; + + /* Shouldn't happen */ + if (!l_dbus_message_get_arguments(reply, "u", &retval)) + goto call_back; + + success = (retval == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) || + (retval == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) || + (retval == DBUS_REQUEST_NAME_REPLY_IN_QUEUE); + queued = (retval == DBUS_REQUEST_NAME_REPLY_IN_QUEUE); + +call_back: + req->callback(req->dbus, success, queued, req->user_data); +} + +static uint32_t classic_name_acquire(struct l_dbus *dbus, const char *name, + bool allow_replacement, + bool replace_existing, bool queue, + l_dbus_name_acquire_func_t callback, + void *user_data) +{ + struct name_request *req; + struct l_dbus_message *message; + uint32_t flags = 0; + + req = l_new(struct name_request, 1); + req->dbus = dbus; + req->user_data = user_data; + req->callback = callback; + + message = l_dbus_message_new_method_call(dbus, DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + L_DBUS_INTERFACE_DBUS, + "RequestName"); + + if (allow_replacement) + flags |= DBUS_NAME_FLAG_ALLOW_REPLACEMENT; + + if (replace_existing) + flags |= DBUS_NAME_FLAG_REPLACE_EXISTING; + + if (!queue) + flags |= DBUS_NAME_FLAG_DO_NOT_QUEUE; + + l_dbus_message_set_arguments(message, "su", name, flags); + + return send_message(dbus, false, message, request_name_reply_cb, + req, free); +} + +static const struct l_dbus_ops classic_ops = { + .version = 1, + .send_message = classic_send_message, + .recv_message = classic_recv_message, + .free = classic_free, + .name_ops = { + .get_name_owner = classic_get_name_owner, + }, + .filter_ops = { + .add_match = classic_add_match, + .remove_match = classic_remove_match, + }, + .name_acquire = classic_name_acquire, +}; + +static struct l_dbus *setup_dbus1(int fd, const char *guid) +{ + static const unsigned char creds = 0x00; + char uid[6], hexuid[12], *ptr = hexuid; + struct l_dbus *dbus; + struct l_dbus_classic *classic; + ssize_t written; + unsigned int i; + + if (snprintf(uid, sizeof(uid), "%d", geteuid()) < 1) { + close(fd); + return NULL; + } + + for (i = 0; i < strlen(uid); i++) + ptr += sprintf(ptr, "%02x", uid[i]); + + /* Send special credentials-passing nul byte */ + written = L_TFR(send(fd, &creds, 1, 0)); + if (written < 1) { + close(fd); + return NULL; + } + + classic = l_new(struct l_dbus_classic, 1); + dbus = &classic->super; + dbus->driver = &classic_ops; + + classic->match_strings = l_hashmap_new(); + + dbus_init(dbus, fd); + dbus->guid = l_strdup(guid); + + classic->auth_command = l_strdup_printf("AUTH EXTERNAL %s\r\n", hexuid); + classic->auth_state = WAITING_FOR_OK; + + dbus->negotiate_unix_fd = true; + dbus->support_unix_fd = false; + + l_io_set_read_handler(dbus->io, auth_read_handler, dbus, NULL); + l_io_set_write_handler(dbus->io, auth_write_handler, dbus, NULL); + + return dbus; +} + +static struct l_dbus *setup_unix(char *params) +{ + char *path = NULL, *guid = NULL; + bool abstract = false; + struct sockaddr_un addr; + size_t len; + int fd; + + while (params) { + char *key = strsep(¶ms, ","); + char *value; + + if (!key) + break; + + value = strchr(key, '='); + if (!value) + continue; + + *value++ = '\0'; + + if (!strcmp(key, "path")) { + path = value; + abstract = false; + } else if (!strcmp(key, "abstract")) { + path = value; + abstract = true; + } else if (!strcmp(key, "guid")) + guid = value; + } + + if (!path) + return NULL; + + fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return NULL; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + + len = strlen(path); + + if (abstract) { + if (len > sizeof(addr.sun_path) - 1) { + close(fd); + return NULL; + } + + addr.sun_path[0] = '\0'; + strncpy(addr.sun_path + 1, path, sizeof(addr.sun_path) - 2); + len++; + } else { + if (len > sizeof(addr.sun_path)) { + close(fd); + return NULL; + } + + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + } + + if (connect(fd, (struct sockaddr *) &addr, + sizeof(addr.sun_family) + len) < 0) { + close(fd); + return NULL; + } + + return setup_dbus1(fd, guid); +} + +static struct l_dbus *setup_address(const char *address) +{ + struct l_dbus *dbus = NULL; + char *address_copy; + + address_copy = strdupa(address); + + while (address_copy) { + char *transport = strsep(&address_copy, ";"); + char *params; + + if (!transport) + break; + + params = strchr(transport, ':'); + if (params) + *params++ = '\0'; + + if (!strcmp(transport, "unix")) { + /* Function will modify params string */ + dbus = setup_unix(params); + break; + } + } + + return dbus; +} + +LIB_EXPORT struct l_dbus *l_dbus_new(const char *address) +{ + if (unlikely(!address)) + return NULL; + + return setup_address(address); +} + +LIB_EXPORT struct l_dbus *l_dbus_new_default(enum l_dbus_bus bus) +{ + const char *address; + + switch (bus) { + case L_DBUS_SYSTEM_BUS: + address = getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (!address) + address = DEFAULT_SYSTEM_BUS_ADDRESS; + break; + case L_DBUS_SESSION_BUS: + address = getenv("DBUS_SESSION_BUS_ADDRESS"); + if (!address) + return NULL; + break; + default: + return NULL; + } + + return setup_address(address); +} + +LIB_EXPORT void l_dbus_destroy(struct l_dbus *dbus) +{ + if (unlikely(!dbus)) + return; + + if (dbus->ready_destroy) + dbus->ready_destroy(dbus->ready_data); + + _dbus_filter_free(dbus->filter); + + _dbus_name_cache_free(dbus->name_cache); + + l_hashmap_destroy(dbus->signal_list, signal_list_destroy); + l_hashmap_destroy(dbus->message_list, message_list_destroy); + l_queue_destroy(dbus->message_queue, message_queue_destroy); + + l_io_destroy(dbus->io); + + if (dbus->disconnect_destroy) + dbus->disconnect_destroy(dbus->disconnect_data); + + if (dbus->debug_destroy) + dbus->debug_destroy(dbus->debug_data); + + l_free(dbus->guid); + l_free(dbus->unique_name); + + _dbus_object_tree_free(dbus->tree); + + dbus->driver->free(dbus); +} + +LIB_EXPORT bool l_dbus_set_ready_handler(struct l_dbus *dbus, + l_dbus_ready_func_t function, + void *user_data, l_dbus_destroy_func_t destroy) +{ + if (unlikely(!dbus)) + return false; + + if (dbus->ready_destroy) + dbus->ready_destroy(dbus->ready_data); + + dbus->ready_handler = function; + dbus->ready_destroy = destroy; + dbus->ready_data = user_data; + + return true; +} + +LIB_EXPORT bool l_dbus_set_disconnect_handler(struct l_dbus *dbus, + l_dbus_disconnect_func_t function, + void *user_data, l_dbus_destroy_func_t destroy) +{ + if (unlikely(!dbus)) + return false; + + if (dbus->disconnect_destroy) + dbus->disconnect_destroy(dbus->disconnect_data); + + dbus->disconnect_handler = function; + dbus->disconnect_destroy = destroy; + dbus->disconnect_data = user_data; + + return true; +} + +LIB_EXPORT bool l_dbus_set_debug(struct l_dbus *dbus, + l_dbus_debug_func_t function, + void *user_data, l_dbus_destroy_func_t destroy) +{ + if (unlikely(!dbus)) + return false; + + if (dbus->debug_destroy) + dbus->debug_destroy(dbus->debug_data); + + dbus->debug_handler = function; + dbus->debug_destroy = destroy; + dbus->debug_data = user_data; + + /* l_io_set_debug(dbus->io, function, user_data, NULL); */ + + return true; +} + +LIB_EXPORT uint32_t l_dbus_send_with_reply(struct l_dbus *dbus, + struct l_dbus_message *message, + l_dbus_message_func_t function, + void *user_data, + l_dbus_destroy_func_t destroy) +{ + if (unlikely(!dbus || !message)) + return 0; + + return send_message(dbus, false, message, function, user_data, destroy); +} + +LIB_EXPORT uint32_t l_dbus_send(struct l_dbus *dbus, + struct l_dbus_message *message) +{ + if (unlikely(!dbus || !message)) + return 0; + + return send_message(dbus, false, message, NULL, NULL, NULL); +} + +static bool remove_entry(void *data, void *user_data) +{ + struct message_callback *callback = data; + uint32_t serial = L_PTR_TO_UINT(user_data); + + if (callback->serial == serial) { + message_queue_destroy(callback); + return true; + } + + return false; +} + +LIB_EXPORT bool l_dbus_cancel(struct l_dbus *dbus, uint32_t serial) +{ + struct message_callback *callback; + unsigned int count; + + if (unlikely(!dbus || !serial)) + return false; + + callback = l_hashmap_remove(dbus->message_list, L_UINT_TO_PTR(serial)); + if (callback) { + message_queue_destroy(callback); + return true; + } + + count = l_queue_foreach_remove(dbus->message_queue, remove_entry, + L_UINT_TO_PTR(serial)); + if (!count) + return false; + + return true; +} + +LIB_EXPORT unsigned int l_dbus_register(struct l_dbus *dbus, + l_dbus_message_func_t function, + void *user_data, l_dbus_destroy_func_t destroy) +{ + struct signal_callback *callback; + + if (unlikely(!dbus)) + return 0; + + callback = l_new(struct signal_callback, 1); + + callback->id = dbus->next_id++; + callback->callback = function; + callback->destroy = destroy; + callback->user_data = user_data; + + l_hashmap_insert(dbus->signal_list, + L_UINT_TO_PTR(callback->id), callback); + + return callback->id; +} + +LIB_EXPORT bool l_dbus_unregister(struct l_dbus *dbus, unsigned int id) +{ + struct signal_callback *callback; + + if (unlikely(!dbus || !id)) + return false; + + callback = l_hashmap_remove(dbus->signal_list, L_UINT_TO_PTR(id)); + if (!callback) + return false; + + signal_list_destroy(callback); + + return true; +} + +LIB_EXPORT uint32_t l_dbus_method_call(struct l_dbus *dbus, + const char *destination, const char *path, + const char *interface, const char *method, + l_dbus_message_func_t setup, + l_dbus_message_func_t function, + void *user_data, l_dbus_destroy_func_t destroy) +{ + struct l_dbus_message *message; + + if (unlikely(!dbus)) + return 0; + + message = l_dbus_message_new_method_call(dbus, destination, path, + interface, method); + + if (setup) + setup(message, user_data); + else + l_dbus_message_set_arguments(message, ""); + + return send_message(dbus, false, message, function, user_data, destroy); +} + +uint8_t _dbus_get_version(struct l_dbus *dbus) +{ + return dbus->driver->version; +} + +int _dbus_get_fd(struct l_dbus *dbus) +{ + return l_io_get_fd(dbus->io); +} + +struct _dbus_object_tree *_dbus_get_tree(struct l_dbus *dbus) +{ + return dbus->tree; +} + +/** + * l_dbus_register_interface: + * @dbus: D-Bus connection as returned by @l_dbus_new* + * @interface: interface name string + * @setup_func: function that sets up the methods, signals and properties by + * using the #dbus-service.h API. + * @destroy: optional destructor to be called every time an instance of this + * interface is being removed from an object on this bus. + * @handle_old_style_properties: whether to automatically handle SetProperty and + * GetProperties for any properties registered by + * @setup_func. + * + * Registers an interface. If successful the interface can then be added + * to any number of objects with @l_dbus_object_add_interface. + * + * Returns: whether the interface was successfully registered + **/ +LIB_EXPORT bool l_dbus_register_interface(struct l_dbus *dbus, + const char *interface, + l_dbus_interface_setup_func_t setup_func, + l_dbus_destroy_func_t destroy, + bool handle_old_style_properties) +{ + if (unlikely(!dbus)) + return false; + + if (unlikely(!dbus->tree)) + return false; + + return _dbus_object_tree_register_interface(dbus->tree, interface, + setup_func, destroy, + handle_old_style_properties); +} + +LIB_EXPORT bool l_dbus_unregister_interface(struct l_dbus *dbus, + const char *interface) +{ + if (unlikely(!dbus)) + return false; + + if (unlikely(!dbus->tree)) + return false; + + return _dbus_object_tree_unregister_interface(dbus->tree, interface); +} + +/** + * l_dbus_register_object: + * @dbus: D-Bus connection + * @path: new object path + * @user_data: user pointer to be passed to @destroy if any + * @destroy: optional destructor to be called when object dropped from the tree + * @...: NULL-terminated list of 0 or more interfaces to be present on the + * object from the moment of creation. For every interface the interface + * name string is expected followed by the @user_data pointer same as + * would be passed as @l_dbus_object_add_interface's last two parameters. + * + * Create a new D-Bus object on the tree visible to D-Bus peers. For example: + * success = l_dbus_register_object(bus, "/org/example/ExampleManager", + * NULL, NULL, + * "org.example.Manager", + * manager_data, + * NULL); + * + * Returns: whether the object path was successfully registered + **/ +LIB_EXPORT bool l_dbus_register_object(struct l_dbus *dbus, const char *path, + void *user_data, + l_dbus_destroy_func_t destroy, ...) +{ + va_list args; + const char *interface; + void *if_user_data; + bool r = true;; + + if (unlikely(!dbus)) + return false; + + if (unlikely(!dbus->tree)) + return false; + + if (!_dbus_object_tree_new_object(dbus->tree, path, user_data, destroy)) + return false; + + va_start(args, destroy); + while ((interface = va_arg(args, const char *))) { + if_user_data = va_arg(args, void *); + + if (!_dbus_object_tree_add_interface(dbus->tree, path, + interface, + if_user_data)) { + _dbus_object_tree_object_destroy(dbus->tree, path); + r = false; + + break; + } + } + va_end(args); + + return r; +} + +LIB_EXPORT bool l_dbus_unregister_object(struct l_dbus *dbus, + const char *object) +{ + if (unlikely(!dbus)) + return false; + + if (unlikely(!dbus->tree)) + return false; + + return _dbus_object_tree_object_destroy(dbus->tree, object); +} + +/** + * l_dbus_object_add_interface: + * @dbus: D-Bus connection + * @object: object path as passed to @l_dbus_register_object + * @interface: interface name as passed to @l_dbus_register_interface + * @user_data: user data pointer to be passed to any method and property + * callbacks provided by the @setup_func and to the @destroy + * callback as passed to @l_dbus_register_interface + * + * Creates an instance of given interface at the given path in the + * connection's object tree. If no object was registered at this path + * before @l_dbus_register_object gets called automatically. + * + * The addition of an interface to the object may trigger a query of + * all the properties on this interface and + * #org.freedesktop.DBus.ObjectManager.InterfacesAdded signals. + * + * Returns: whether the interface was successfully added. + **/ +LIB_EXPORT bool l_dbus_object_add_interface(struct l_dbus *dbus, + const char *object, + const char *interface, + void *user_data) +{ + if (unlikely(!dbus)) + return false; + + if (unlikely(!dbus->tree)) + return false; + + return _dbus_object_tree_add_interface(dbus->tree, object, interface, + user_data); +} + +LIB_EXPORT bool l_dbus_object_remove_interface(struct l_dbus *dbus, + const char *object, + const char *interface) +{ + if (unlikely(!dbus)) + return false; + + if (unlikely(!dbus->tree)) + return false; + + return _dbus_object_tree_remove_interface(dbus->tree, object, + interface); +} + +LIB_EXPORT void *l_dbus_object_get_data(struct l_dbus *dbus, const char *object, + const char *interface) +{ + if (unlikely(!dbus)) + return false; + + if (unlikely(!dbus->tree)) + return false; + + return _dbus_object_tree_get_interface_data(dbus->tree, object, + interface); +} + +LIB_EXPORT bool l_dbus_object_manager_enable(struct l_dbus *dbus, + const char *root) +{ + if (unlikely(!dbus)) + return false; + + if (unlikely(!dbus->tree)) + return false; + + return _dbus_object_tree_add_interface(dbus->tree, root, + L_DBUS_INTERFACE_OBJECT_MANAGER, + dbus); +} + +LIB_EXPORT unsigned int l_dbus_add_disconnect_watch(struct l_dbus *dbus, + const char *name, + l_dbus_watch_func_t disconnect_func, + void *user_data, + l_dbus_destroy_func_t destroy) +{ + return l_dbus_add_service_watch(dbus, name, NULL, disconnect_func, + user_data, destroy); +} + +LIB_EXPORT unsigned int l_dbus_add_service_watch(struct l_dbus *dbus, + const char *name, + l_dbus_watch_func_t connect_func, + l_dbus_watch_func_t disconnect_func, + void *user_data, + l_dbus_destroy_func_t destroy) +{ + if (!name) + return 0; + + if (!dbus->name_cache) + dbus->name_cache = _dbus_name_cache_new(dbus, + &dbus->driver->name_ops); + + return _dbus_name_cache_add_watch(dbus->name_cache, name, connect_func, + disconnect_func, user_data, + destroy); +} + +LIB_EXPORT bool l_dbus_remove_watch(struct l_dbus *dbus, unsigned int id) +{ + if (!dbus->name_cache) + return false; + + return _dbus_name_cache_remove_watch(dbus->name_cache, id); +} + +/** + * l_dbus_add_signal_watch: + * @dbus: D-Bus connection + * @sender: bus name to match the signal sender against or NULL to + * match any sender + * @path: object path to match the signal path against or NULL to + * match any path + * @interface: interface name to match the signal interface against + * or NULL to match any interface + * @member: name to match the signal name against or NULL to match any + * signal + * @...: a list of further conditions to be met by the signal followed + * by three more mandatory parameters: + * enum l_dbus_match_type list_end_marker, + * l_dbus_message_func callback, + * void *user_data, + * The value L_DBUS_MATCH_NONE must be passed as the end of list + * marker, followed by the signal match callback and user_data. + * In the list, every condition is a pair of parameters: + * enum l_dbus_match_type match_type, const char *value. + * + * Subscribe to a group of signals based on a set of conditions that + * compare the signal's header fields and string arguments against given + * values. For example: + * signal_id = l_dbus_add_signal_watch(bus, "org.example", "/" + * "org.example.Manager", + * "PropertyChanged", + * L_DBUS_MATCH_ARGUMENT(0), + * "ExampleProperty", + * L_DBUS_MATCH_NONE + * manager_property_change_cb, + * NULL); + * + * Returns: a non-zero signal filter identifier that can be passed to + * l_dbus_remove_signal_watch to remove this filter rule, or + * zero on failure. + **/ +LIB_EXPORT unsigned int l_dbus_add_signal_watch(struct l_dbus *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, ...) +{ + struct _dbus_filter_condition *rule; + int rule_len; + va_list args; + const char *value; + l_dbus_message_func_t signal_func; + enum l_dbus_match_type type; + void *user_data; + unsigned int id; + + va_start(args, member); + + rule_len = 0; + while ((type = va_arg(args, enum l_dbus_match_type)) != + L_DBUS_MATCH_NONE) + rule_len++; + + va_end(args); + + rule = l_new(struct _dbus_filter_condition, rule_len + 5); + + rule_len = 0; + + rule[rule_len].type = L_DBUS_MATCH_TYPE; + rule[rule_len++].value = "signal"; + + if (sender) { + rule[rule_len].type = L_DBUS_MATCH_SENDER; + rule[rule_len++].value = sender; + } + + if (path) { + rule[rule_len].type = L_DBUS_MATCH_PATH; + rule[rule_len++].value = path; + } + + if (interface) { + rule[rule_len].type = L_DBUS_MATCH_INTERFACE; + rule[rule_len++].value = interface; + } + + if (member) { + rule[rule_len].type = L_DBUS_MATCH_MEMBER; + rule[rule_len++].value = member; + } + + va_start(args, member); + + while (true) { + type = va_arg(args, enum l_dbus_match_type); + if (type == L_DBUS_MATCH_NONE) + break; + + value = va_arg(args, const char *); + + rule[rule_len].type = type; + rule[rule_len++].value = value; + } + + signal_func = va_arg(args, l_dbus_message_func_t); + user_data = va_arg(args, void *); + + va_end(args); + + if (!dbus->filter) { + if (!dbus->name_cache) + dbus->name_cache = _dbus_name_cache_new(dbus, + &dbus->driver->name_ops); + + dbus->filter = _dbus_filter_new(dbus, + &dbus->driver->filter_ops, + dbus->name_cache); + } + + id = _dbus_filter_add_rule(dbus->filter, rule, rule_len, + signal_func, user_data); + + l_free(rule); + + return id; +} + +LIB_EXPORT bool l_dbus_remove_signal_watch(struct l_dbus *dbus, unsigned int id) +{ + if (!dbus->filter) + return false; + + return _dbus_filter_remove_rule(dbus->filter, id); +} + +/** + * l_dbus_name_acquire: + * @dbus: D-Bus connection + * @name: Well-known bus name to be acquired + * @allow_replacement: Whether to allow another peer's name request to + * take the name ownership away from this connection + * @replace_existing: Whether to allow D-Bus to take the name's ownership + * away from another peer in case the name is already + * owned and allows replacement. Ignored if name is + * currently free. + * @queue: Whether to allow the name request to be queued by D-Bus in + * case it cannot be acquired now, rather than to return a failure. + * @callback: Callback to receive the request result when done. + * + * Acquire a well-known bus name (service name) on the bus. + * + * Returns: a non-zero request serial that can be passed to l_dbus_cancel + * while waiting for the callback or zero if the callback has + * has happened while l_dbus_name_acquire was running. + **/ +LIB_EXPORT uint32_t l_dbus_name_acquire(struct l_dbus *dbus, const char *name, + bool allow_replacement, bool replace_existing, + bool queue, l_dbus_name_acquire_func_t callback, + void *user_data) +{ + return dbus->driver->name_acquire(dbus, name, allow_replacement, + replace_existing, queue, + callback, user_data); +} diff --git a/ell/dbus.h b/ell/dbus.h new file mode 100644 index 0000000..cbfb0b0 --- /dev/null +++ b/ell/dbus.h @@ -0,0 +1,277 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_DBUS_H +#define __ELL_DBUS_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define L_DBUS_INTERFACE_DBUS "org.freedesktop.DBus" +#define L_DBUS_INTERFACE_INTROSPECTABLE "org.freedesktop.DBus.Introspectable" +#define L_DBUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" +#define L_DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" + +enum l_dbus_bus { + L_DBUS_SYSTEM_BUS, + L_DBUS_SESSION_BUS, +}; + +enum l_dbus_match_type { + L_DBUS_MATCH_NONE = 0, + L_DBUS_MATCH_TYPE, + L_DBUS_MATCH_SENDER, + L_DBUS_MATCH_PATH, + L_DBUS_MATCH_INTERFACE, + L_DBUS_MATCH_MEMBER, + L_DBUS_MATCH_ARG0, +}; + +#define L_DBUS_MATCH_ARGUMENT(i) (L_DBUS_MATCH_ARG0 + (i)) + +struct l_dbus; +struct l_dbus_interface; +struct l_dbus_message_builder; + +typedef void (*l_dbus_ready_func_t) (void *user_data); +typedef void (*l_dbus_disconnect_func_t) (void *user_data); + +typedef void (*l_dbus_debug_func_t) (const char *str, void *user_data); +typedef void (*l_dbus_destroy_func_t) (void *user_data); +typedef void (*l_dbus_interface_setup_func_t) (struct l_dbus_interface *); + +typedef void (*l_dbus_watch_func_t) (struct l_dbus *dbus, void *user_data); + +typedef void (*l_dbus_name_acquire_func_t) (struct l_dbus *dbus, bool success, + bool queued, void *user_data); + +struct l_dbus *l_dbus_new(const char *address); +struct l_dbus *l_dbus_new_default(enum l_dbus_bus bus); +void l_dbus_destroy(struct l_dbus *dbus); + +bool l_dbus_set_ready_handler(struct l_dbus *dbus, l_dbus_ready_func_t function, + void *user_data, l_dbus_destroy_func_t destroy); +bool l_dbus_set_disconnect_handler(struct l_dbus *dbus, + l_dbus_disconnect_func_t function, + void *user_data, l_dbus_destroy_func_t destroy); + +bool l_dbus_set_debug(struct l_dbus *dbus, l_dbus_debug_func_t function, + void *user_data, l_dbus_destroy_func_t destroy); + +struct l_dbus_message; + +struct l_dbus_message_iter { + struct l_dbus_message *message; + const char *sig_start; + uint8_t sig_len; + uint8_t sig_pos; + const void *data; + size_t len; + size_t pos; + char container_type; + const void *offsets; +}; + +struct l_dbus_message *l_dbus_message_new_method_call(struct l_dbus *dbus, + const char *destination, + const char *path, + const char *interface, + const char *method); + +struct l_dbus_message *l_dbus_message_new_signal(struct l_dbus *dbus, + const char *path, + const char *interface, + const char *name); + +struct l_dbus_message *l_dbus_message_new_method_return( + struct l_dbus_message *method_call); + +struct l_dbus_message *l_dbus_message_new_error_valist( + struct l_dbus_message *method_call, + const char *name, + const char *format, va_list args); +struct l_dbus_message *l_dbus_message_new_error( + struct l_dbus_message *method_call, + const char *name, + const char *format, ...) + __attribute__((format(printf, 3, 4))); + +struct l_dbus_message *l_dbus_message_ref(struct l_dbus_message *message); +void l_dbus_message_unref(struct l_dbus_message *message); + +const char *l_dbus_message_get_path(struct l_dbus_message *message); +const char *l_dbus_message_get_interface(struct l_dbus_message *message); +const char *l_dbus_message_get_member(struct l_dbus_message *message); +const char *l_dbus_message_get_destination(struct l_dbus_message *message); +const char *l_dbus_message_get_sender(struct l_dbus_message *message); +const char *l_dbus_message_get_signature(struct l_dbus_message *message); + +bool l_dbus_message_set_no_reply(struct l_dbus_message *message, bool on); +bool l_dbus_message_get_no_reply(struct l_dbus_message *message); + +bool l_dbus_message_set_no_autostart(struct l_dbus_message *message, bool on); +bool l_dbus_message_get_no_autostart(struct l_dbus_message *message); + +typedef void (*l_dbus_message_func_t) (struct l_dbus_message *message, + void *user_data); + +uint32_t l_dbus_send_with_reply(struct l_dbus *dbus, + struct l_dbus_message *message, + l_dbus_message_func_t function, + void *user_data, l_dbus_destroy_func_t destroy); +uint32_t l_dbus_send(struct l_dbus *dbus, + struct l_dbus_message *message); +bool l_dbus_cancel(struct l_dbus *dbus, uint32_t serial); + +unsigned int l_dbus_register(struct l_dbus *dbus, + l_dbus_message_func_t function, + void *user_data, l_dbus_destroy_func_t destroy); +bool l_dbus_unregister(struct l_dbus *dbus, unsigned int id); + +uint32_t l_dbus_method_call(struct l_dbus *dbus, + const char *destination, const char *path, + const char *interface, const char *method, + l_dbus_message_func_t setup, + l_dbus_message_func_t function, + void *user_data, l_dbus_destroy_func_t destroy); + +bool l_dbus_message_is_error(struct l_dbus_message *message); +bool l_dbus_message_get_error(struct l_dbus_message *message, + const char **name, const char **text); +bool l_dbus_message_get_arguments(struct l_dbus_message *message, + const char *signature, ...); +bool l_dbus_message_get_arguments_valist(struct l_dbus_message *message, + const char *signature, va_list args); + +bool l_dbus_message_iter_next_entry(struct l_dbus_message_iter *iter, ...); +bool l_dbus_message_iter_get_variant(struct l_dbus_message_iter *iter, + const char *signature, ...); +bool l_dbus_message_iter_get_fixed_array(struct l_dbus_message_iter *iter, + void *out, uint32_t *n_elem); + +bool l_dbus_message_set_arguments(struct l_dbus_message *message, + const char *signature, ...); +bool l_dbus_message_set_arguments_valist(struct l_dbus_message *message, + const char *signature, va_list args); + +struct l_dbus_message_builder *l_dbus_message_builder_new( + struct l_dbus_message *message); +void l_dbus_message_builder_destroy(struct l_dbus_message_builder *builder); + +bool l_dbus_message_builder_append_basic(struct l_dbus_message_builder *builder, + char type, const void *value); + +bool l_dbus_message_builder_enter_container( + struct l_dbus_message_builder *builder, + char container_type, + const char *signature); +bool l_dbus_message_builder_leave_container( + struct l_dbus_message_builder *builder, + char container_type); + +bool l_dbus_message_builder_enter_struct(struct l_dbus_message_builder *builder, + const char *signature); +bool l_dbus_message_builder_leave_struct( + struct l_dbus_message_builder *builder); + +bool l_dbus_message_builder_enter_dict(struct l_dbus_message_builder *builder, + const char *signature); +bool l_dbus_message_builder_leave_dict(struct l_dbus_message_builder *builder); + +bool l_dbus_message_builder_enter_array(struct l_dbus_message_builder *builder, + const char *signature); +bool l_dbus_message_builder_leave_array(struct l_dbus_message_builder *builder); + +bool l_dbus_message_builder_enter_variant( + struct l_dbus_message_builder *builder, + const char *signature); +bool l_dbus_message_builder_leave_variant( + struct l_dbus_message_builder *builder); + +bool l_dbus_message_builder_append_from_iter( + struct l_dbus_message_builder *builder, + struct l_dbus_message_iter *from); + +bool l_dbus_message_builder_append_from_valist( + struct l_dbus_message_builder *builder, + const char *signature, va_list args); + +struct l_dbus_message *l_dbus_message_builder_finalize( + struct l_dbus_message_builder *builder); + +bool l_dbus_register_interface(struct l_dbus *dbus, const char *interface, + l_dbus_interface_setup_func_t setup_func, + l_dbus_destroy_func_t destroy, + bool handle_old_style_properties); +bool l_dbus_unregister_interface(struct l_dbus *dbus, const char *interface); + +bool l_dbus_register_object(struct l_dbus *dbus, const char *path, + void *user_data, l_dbus_destroy_func_t destroy, + ...); +bool l_dbus_unregister_object(struct l_dbus *dbus, const char *object); + +bool l_dbus_object_add_interface(struct l_dbus *dbus, const char *object, + const char *interface, void *user_data); +bool l_dbus_object_remove_interface(struct l_dbus *dbus, const char *object, + const char *interface); +void *l_dbus_object_get_data(struct l_dbus *dbus, const char *object, + const char *interface); + +bool l_dbus_object_manager_enable(struct l_dbus *dbus, const char *root); + +unsigned int l_dbus_add_service_watch(struct l_dbus *dbus, + const char *name, + l_dbus_watch_func_t connect_func, + l_dbus_watch_func_t disconnect_func, + void *user_data, + l_dbus_destroy_func_t destroy); + +unsigned int l_dbus_add_disconnect_watch(struct l_dbus *dbus, + const char *name, + l_dbus_watch_func_t disconnect_func, + void *user_data, + l_dbus_destroy_func_t destroy); +bool l_dbus_remove_watch(struct l_dbus *dbus, unsigned int id); + +unsigned int l_dbus_add_signal_watch(struct l_dbus *dbus, + const char *sender, + const char *path, + const char *interface, + const char *member, ...); +bool l_dbus_remove_signal_watch(struct l_dbus *dbus, unsigned int id); + +uint32_t l_dbus_name_acquire(struct l_dbus *dbus, const char *name, + bool allow_replacement, bool replace_existing, + bool queue, l_dbus_name_acquire_func_t callback, + void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_DBUS_H */ diff --git a/ell/ell.h b/ell/ell.h new file mode 100644 index 0000000..07c069c --- /dev/null +++ b/ell/ell.h @@ -0,0 +1,18 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/ell/gvariant-private.h b/ell/gvariant-private.h new file mode 100644 index 0000000..6c0005a --- /dev/null +++ b/ell/gvariant-private.h @@ -0,0 +1,69 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 l_dbus_message_iter; +struct dbus_builder; + +bool _gvariant_iter_init(struct l_dbus_message_iter *iter, + struct l_dbus_message *message, + const char *sig_start, const char *sig_end, + const void *data, size_t len); +bool _gvariant_iter_next_entry_basic(struct l_dbus_message_iter *iter, + char type, void *out_p); +bool _gvariant_iter_enter_struct(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *structure); +bool _gvariant_iter_enter_variant(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *variant); +bool _gvariant_iter_enter_array(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *array); +bool _gvariant_iter_skip_entry(struct l_dbus_message_iter *iter); + +bool _gvariant_valid_signature(const char *sig); +int _gvariant_get_alignment(const char *signature); +bool _gvariant_is_fixed_size(const char *signature); +int _gvariant_get_fixed_size(const char *signature); +int _gvariant_num_children(const char *sig); + +struct dbus_builder *_gvariant_builder_new(void *body, size_t body_size); +void _gvariant_builder_free(struct dbus_builder *builder); +bool _gvariant_builder_append_basic(struct dbus_builder *builder, + char type, const void *value); +bool _gvariant_builder_mark(struct dbus_builder *builder); +bool _gvariant_builder_rewind(struct dbus_builder *builder); +char *_gvariant_builder_finish(struct dbus_builder *builder, + void **body, size_t *body_size); +bool _gvariant_builder_enter_struct(struct dbus_builder *builder, + const char *signature); +bool _gvariant_builder_leave_struct(struct dbus_builder *builder); +bool _gvariant_builder_enter_dict(struct dbus_builder *builder, + const char *signature); +bool _gvariant_builder_leave_dict(struct dbus_builder *builder); +bool _gvariant_builder_enter_variant(struct dbus_builder *builder, + const char *signature); +bool _gvariant_builder_leave_variant(struct dbus_builder *builder); +bool _gvariant_builder_enter_array(struct dbus_builder *builder, + const char *signature); +bool _gvariant_builder_leave_array(struct dbus_builder *builder); + +size_t _gvariant_message_finalize(size_t header_end, + void *body, size_t body_size, + const char *signature); diff --git a/ell/gvariant-util.c b/ell/gvariant-util.c new file mode 100644 index 0000000..cf0161a --- /dev/null +++ b/ell/gvariant-util.c @@ -0,0 +1,1382 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "private.h" +#include "util.h" +#include "queue.h" +#include "string.h" +#include "log.h" +#include "dbus.h" +#include "dbus-private.h" +#include "gvariant-private.h" + +static const char *simple_types = "sogybnqiuxtdh"; +static const char *variable_types = "sogav"; +static const char *fixed_types = "bynqhiuxtd"; + +/* + * The alignment of a container type is equal to the largest alignment of + * any potential child of that container. This means that, even if an array + * of 32-bit integers is empty, it still must be aligned to the nearest + * multiple of 4 bytes. It also means that the variant type (described below) + * has an alignment of 8 (since it could potentially contain a value of any + * other type and the maximum alignment is 8). + */ +static int get_basic_alignment(const char type) +{ + switch (type) { + case 'b': + return 1; + case 'y': + return 1; + case 'n': + case 'q': + return 2; + case 'i': + case 'u': + return 4; + case 'x': + case 't': + case 'd': + return 8; + case 's': + case 'g': + case 'o': + return 1; + case 'h': + return 4; + case 'v': + return 8; + default: + return 0; + } +} + +static int get_basic_fixed_size(const char type) +{ + switch (type) { + case 'b': + return 1; + case 'y': + return 1; + case 'n': + case 'q': + return 2; + case 'i': + case 'u': + return 4; + case 'x': + case 't': + case 'd': + return 8; + case 'h': + return 4; + default: + return 0; + } +} + +static const char *validate_next_type(const char *sig, int *out_alignment) +{ + char s = *sig; + int alignment; + + if (s == '\0') + return NULL; + + if (strchr(simple_types, s) || s == 'v') { + *out_alignment = get_basic_alignment(s); + return sig + 1; + } + + switch (s) { + case 'a': + return validate_next_type(++sig, out_alignment); + + case '{': + s = *++sig; + + /* Dictionary keys can only be simple types */ + if (!strchr(simple_types, s)) + return NULL; + + alignment = get_basic_alignment(s); + + sig = validate_next_type(sig + 1, out_alignment); + + if (!sig) + return NULL; + + if (*sig != '}') + return NULL; + + if (alignment > *out_alignment) + *out_alignment = alignment; + + return sig + 1; + + case '(': + { + int max_alignment = 1, alignment; + + sig++; + + while (sig && *sig != ')') { + sig = validate_next_type(sig, &alignment); + + if (alignment > max_alignment) + max_alignment = alignment; + } + + if (!sig) + return NULL; + + if (*sig != ')') + return NULL; + + *out_alignment = max_alignment; + + return sig + 1; + } + } + + return NULL; +} + +bool _gvariant_valid_signature(const char *sig) +{ + const char *s = sig; + int a; + + if (strlen(sig) > 255) + return false; + + do { + s = validate_next_type(s, &a); + + if (!s) + return false; + } while (*s); + + return true; +} + +int _gvariant_num_children(const char *sig) +{ + const char *s = sig; + int a; + int num_children = 0; + + if (strlen(sig) > 255) + return false; + + do { + s = validate_next_type(s, &a); + + if (!s) + return -1; + + num_children += 1; + } while (*s); + + return num_children; +} + +int _gvariant_get_alignment(const char *sig) +{ + int max_alignment = 1, alignment; + const char *s = sig; + + /* 8 is the largest alignment possible, so quit if we reach it */ + while (*s && max_alignment != 8) { + s = validate_next_type(s, &alignment); + if (!s) + return 0; + + if (alignment > max_alignment) + max_alignment = alignment; + } + + return max_alignment; +} + +bool _gvariant_is_fixed_size(const char *sig) +{ + while (*sig != 0) { + if (strchr(variable_types, sig[0])) + return false; + + sig += 1; + } + + return true; +} + +int _gvariant_get_fixed_size(const char *sig) +{ + const char *s = sig; + const char *p; + int size = 0; + int alignment; + int max_alignment = 1; + int r; + + while (*s) { + if (strchr(variable_types, *s)) + return 0; + + if (strchr(fixed_types, *s)) { + alignment = get_basic_alignment(*s); + + if (alignment > max_alignment) + max_alignment = alignment; + + size = align_len(size, alignment); + size += get_basic_fixed_size(*s); + s++; + continue; + } + + if (*s == '}' || *s == ')') + break; + + p = validate_next_type(s, &alignment); + + if (!p) + return 0; + + if (alignment > max_alignment) + max_alignment = alignment; + + size = align_len(size, alignment); + + /* Handle special case of unit type */ + if (s[0] == '(' && s[1] == ')') + r = 1; + else + r = _gvariant_get_fixed_size(s + 1); + + if (r == 0) + return 0; + + size += r; + s = p; + } + + size = align_len(size, max_alignment); + + return size; +} + +static inline size_t offset_length(size_t size, size_t n_offsets) +{ + if (size + n_offsets <= 0xff) + return 1; + if (size + n_offsets * 2 <= 0xffff) + return 2; + if (size + n_offsets * 4 <= 0xffffffff) + return 4; + else + return 8; +} + +static inline size_t read_word_le(const void *p, size_t sz) { + union { + uint16_t u16; + uint32_t u32; + uint64_t u64; + } x; + + if (sz == 1) + return *(uint8_t *) p; + + memcpy(&x, p, sz); + + if (sz == 2) + return le16toh(x.u16); + else if (sz == 4) + return le32toh(x.u32); + else + return le64toh(x.u64); +} + +static inline void write_word_le(void *p, size_t value, size_t sz) { + union { + uint16_t u16; + uint32_t u32; + uint64_t u64; + } x; + + if (sz == 1) { + *(uint8_t *) p = value; + return; + } + + if (sz == 2) + x.u16 = htole16((uint16_t) value); + else if (sz == 4) + x.u32 = htole32((uint32_t) value); + else + x.u64 = htole64((uint64_t) value); + + memcpy(p, &x, sz); +} + +static bool gvariant_iter_init_internal(struct l_dbus_message_iter *iter, + struct l_dbus_message *message, + enum dbus_container_type type, + const char *sig_start, + const char *sig_end, const void *data, + size_t len) +{ + const char *p; + int i; + int v; + char subsig[256]; + unsigned int num_variable = 0; + unsigned int offset_len = offset_length(len, 0); + size_t last_offset; + struct gvariant_type_info { + uint8_t sig_start; + uint8_t sig_end; + bool fixed_size : 1; + unsigned int alignment : 4; + size_t end; /* Index past the end of the type */ + } *children; + int n_children; + + if (sig_end) { + size_t len = sig_end - sig_start; + memcpy(subsig, sig_start, len); + subsig[len] = '\0'; + } else + strcpy(subsig, sig_start); + + iter->message = message; + iter->sig_start = sig_start; + iter->sig_len = strlen(subsig); + iter->sig_pos = 0; + iter->data = data; + iter->len = len; + iter->pos = 0; + + if (subsig[0] != '\0') { + n_children = _gvariant_num_children(subsig); + if (n_children < 0) + return false; + + children = l_new(struct gvariant_type_info, n_children); + } else { + n_children = 0; + + children = NULL; + } + + for (p = sig_start, i = 0; i < n_children; i++) { + int alignment; + size_t size; + size_t len; + + children[i].sig_start = p - sig_start; + p = validate_next_type(p, &alignment); + children[i].sig_end = p - sig_start; + + len = children[i].sig_end - children[i].sig_start; + memcpy(subsig, sig_start + children[i].sig_start, len); + subsig[len] = '\0'; + + children[i].alignment = alignment; + children[i].fixed_size = _gvariant_is_fixed_size(subsig); + + if (children[i].fixed_size) { + size = _gvariant_get_fixed_size(subsig); + children[i].end = size; + } else if (i + 1 < n_children) + num_variable += 1; + } + + if (len < num_variable * offset_len) + goto fail; + + last_offset = len - num_variable * offset_len; + + if (num_variable > 0) + iter->offsets = iter->data + len - offset_len; + else + iter->offsets = NULL; + + for (i = 0, v = 0; i < n_children; i++) { + size_t o; + + if (children[i].fixed_size) { + if (i == 0) + continue; + + o = align_len(children[i-1].end, + children[i].alignment); + children[i].end += o; + + if (children[i].end > len) + goto fail; + + continue; + } + + if (num_variable == 0) { + children[i].end = last_offset; + continue; + } + + v += 1; + children[i].end = read_word_le(data + len - offset_len * v, + offset_len); + num_variable -= 1; + + if (children[i].end > len) + goto fail; + } + + iter->container_type = type; + + if (type == DBUS_CONTAINER_TYPE_ARRAY && + !children[0].fixed_size) { + size_t offset = read_word_le(iter->data + iter->len - + offset_len, offset_len); + iter->offsets = iter->data + offset; + } + + l_free(children); + + return true; + +fail: + l_free(children); + return false; +} + +bool _gvariant_iter_init(struct l_dbus_message_iter *iter, + struct l_dbus_message *message, + const char *sig_start, const char *sig_end, + const void *data, size_t len) +{ + return gvariant_iter_init_internal(iter, message, + DBUS_CONTAINER_TYPE_STRUCT, + sig_start, sig_end, data, len); +} + +static const void *next_item(struct l_dbus_message_iter *iter, + size_t *out_item_size) +{ + const void *start; + const char *p; + char sig[256]; + int alignment; + bool fixed_size; + bool last_member; + unsigned int sig_len; + unsigned int offset_len; + + memcpy(sig, iter->sig_start + iter->sig_pos, + iter->sig_len - iter->sig_pos); + sig[iter->sig_len - iter->sig_pos] = '\0'; + + /* + * Find the next type and make a note whether it is the last in the + * structure. Arrays will always have a single complete type, so + * last_member will always be true. + */ + p = validate_next_type(sig, &alignment); + if (!p) + return NULL; + + sig_len = p - sig; + + last_member = *p == '\0'; + sig[sig_len] = '\0'; + + fixed_size = _gvariant_is_fixed_size(sig); + + if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY) + iter->sig_pos += sig_len; + + iter->pos = align_len(iter->pos, alignment); + + if (fixed_size) { + *out_item_size = _gvariant_get_fixed_size(sig); + goto done; + } + + if (iter->container_type != DBUS_CONTAINER_TYPE_ARRAY && last_member) { + unsigned int len = iter->len; + + offset_len = offset_length(iter->len, 0); + + if (iter->offsets && iter->offsets + offset_len < + iter->data + len) + len = iter->offsets + offset_len - iter->data; + + *out_item_size = len - iter->pos; + goto done; + } + + if (iter->offsets >= iter->data + iter->len) + return NULL; + + offset_len = offset_length(iter->len, 0); + *out_item_size = read_word_le(iter->offsets, offset_len) - iter->pos; + + /* In structures the offsets are in reverse order */ + if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY) + iter->offsets += offset_len; + else + iter->offsets -= offset_len; + +done: + start = iter->data + iter->pos; + + if (start >= iter->data + iter->len) + return NULL; + + iter->pos += *out_item_size; + + return start; +} + +bool _gvariant_iter_next_entry_basic(struct l_dbus_message_iter *iter, + char type, void *out) +{ + size_t item_size = 0; + const void *start; + uint8_t uint8_val; + uint16_t uint16_val; + uint32_t uint32_val; + uint64_t uint64_val; + int16_t int16_val; + int32_t int32_val; + int64_t int64_val; + + if (iter->pos >= iter->len) + return false; + + if (iter->sig_start[iter->sig_pos] != type) + return false; + + start = next_item(iter, &item_size); + if (!start) + return false; + + switch (type) { + case 'o': + case 's': + case 'g': + { + const void *end = memchr(start, 0, item_size); + + if (!end) + return false; + + *(const char**) out = start; + break; + } + case 'b': + uint8_val = l_get_u8(start); + *(bool *) out = !!uint8_val; + break; + case 'y': + uint8_val = l_get_u8(start); + *(uint8_t *) out = uint8_val; + break; + case 'n': + int16_val = l_get_s16(start); + *(int16_t *) out = int16_val; + break; + case 'q': + uint16_val = l_get_u16(start); + *(uint16_t *) out = uint16_val; + break; + case 'i': + int32_val = l_get_s32(start); + *(int32_t *) out = int32_val; + break; + case 'h': + case 'u': + uint32_val = l_get_u32(start); + *(uint32_t *) out = uint32_val; + break; + case 'x': + int64_val = l_get_s64(start); + *(int64_t *) out = int64_val; + break; + case 't': + uint64_val = l_get_u64(start); + *(uint64_t *) out = uint64_val; + break; + case 'd': + uint64_val = l_get_u64(start); + *(uint64_t *) out = uint64_val; + break; + } + + return true; +} + +bool _gvariant_iter_enter_struct(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *structure) +{ + bool is_dict = iter->sig_start[iter->sig_pos] == '{'; + bool is_struct = iter->sig_start[iter->sig_pos] == '('; + const char *sig_start = iter->sig_start + iter->sig_pos + 1; + const char *sig_end; + const void *start; + size_t item_size; + enum dbus_container_type type; + + if (!is_dict && !is_struct) + return false; + + start = next_item(iter, &item_size); + if (!start) + return false; + + /* For ARRAY containers the sig_pos is never incremented */ + if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY) + sig_end = iter->sig_start + iter->sig_len - 1; + else + sig_end = iter->sig_start + iter->sig_pos - 1; + + type = is_dict ? DBUS_CONTAINER_TYPE_DICT_ENTRY : + DBUS_CONTAINER_TYPE_STRUCT; + + return gvariant_iter_init_internal(structure, iter->message, + type, sig_start, sig_end, + start, item_size); +} + +bool _gvariant_iter_enter_variant(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *variant) +{ + size_t item_size; + const void *start, *end, *nul; + char signature[256]; + + if (iter->sig_start[iter->sig_pos] != 'v') + return false; + + start = next_item(iter, &item_size); + if (!start) + return false; + + /* Find the signature */ + end = start + item_size; + nul = memrchr(start, 0, end - start); + + if (!nul) + return false; + + if (end - nul - 1 > 255) + return false; + + memcpy(signature, nul + 1, end - nul - 1); + signature[end - nul - 1] = '\0'; + + if (_gvariant_num_children(signature) != 1) + return false; + + return gvariant_iter_init_internal(variant, iter->message, + DBUS_CONTAINER_TYPE_VARIANT, + nul + 1, end, + start, nul - start); +} + +bool _gvariant_iter_enter_array(struct l_dbus_message_iter *iter, + struct l_dbus_message_iter *array) +{ + const char *sig_start; + const char *sig_end; + size_t item_size; + const void *start; + + if (iter->sig_start[iter->sig_pos] != 'a') + return false; + + sig_start = iter->sig_start + iter->sig_pos + 1; + + start = next_item(iter, &item_size); + if (!start) + return false; + + /* For ARRAY containers the sig_pos is never incremented */ + if (iter->container_type == DBUS_CONTAINER_TYPE_ARRAY) + sig_end = iter->sig_start + iter->sig_len; + else + sig_end = iter->sig_start + iter->sig_pos; + + return gvariant_iter_init_internal(array, iter->message, + DBUS_CONTAINER_TYPE_ARRAY, + sig_start, sig_end, + start, item_size); +} + +bool _gvariant_iter_skip_entry(struct l_dbus_message_iter *iter) +{ + size_t size; + + if (!next_item(iter, &size)) + return false; + + return true; +} + +struct dbus_builder { + struct l_string *signature; + void *body; + size_t body_size; + size_t body_pos; + struct l_queue *containers; + struct { + struct container *container; + int sig_end; + size_t body_pos; + size_t offset_index; + bool variable_is_last : 1; + } mark; +}; + +struct container { + size_t *offsets; + size_t offsets_size; + size_t offset_index; + size_t start; + bool variable_is_last : 1; + enum dbus_container_type type; + char signature[256]; + uint8_t sigindex; +}; + +static inline size_t grow_body(struct dbus_builder *builder, + size_t len, unsigned int alignment) +{ + size_t size = align_len(builder->body_pos, alignment); + + if (size + len > builder->body_size) { + builder->body = l_realloc(builder->body, size + len); + builder->body_size = size + len; + } + + if (size - builder->body_pos > 0) + memset(builder->body + builder->body_pos, 0, + size - builder->body_pos); + + builder->body_pos = size + len; + + return size; +} + +static inline bool grow_offsets(struct container *container) +{ + size_t needed; + + if (container->offset_index < container->offsets_size) + return true; + + needed = container->offsets_size * 2; + + if (needed > USHRT_MAX) + return false; + + if (needed == 0) + needed = 8; + + container->offsets = l_realloc(container->offsets, + needed * sizeof(size_t)); + container->offsets_size = needed; + + return true; +} + +static struct container *container_new(enum dbus_container_type type, + const char *signature, size_t start) +{ + struct container *ret; + + ret = l_new(struct container, 1); + + ret->type = type; + strcpy(ret->signature, signature); + ret->start = start; + + return ret; +} + +static void container_free(struct container *container) +{ + l_free(container->offsets); + l_free(container); +} + +static void container_append_struct_offsets(struct container *container, + struct dbus_builder *builder) +{ + size_t offset_size; + int i; + size_t start; + + if (container->variable_is_last) + container->offset_index -= 1; + + if (container->offset_index == 0) + return; + + offset_size = offset_length(builder->body_pos, + container->offset_index); + i = container->offset_index - 1; + + start = grow_body(builder, offset_size * container->offset_index, 1); + + for (i = container->offset_index - 1; i >= 0; i--) { + write_word_le(builder->body + start, + container->offsets[i], offset_size); + start += offset_size; + } +} + +static void container_append_array_offsets(struct container *container, + struct dbus_builder *builder) +{ + size_t offset_size; + unsigned int i; + size_t start; + + if (container->offset_index == 0) + return; + + offset_size = offset_length(builder->body_pos, + container->offset_index); + start = grow_body(builder, offset_size * container->offset_index, 1); + + for (i = 0; i < container->offset_index; i++) { + write_word_le(builder->body + start, + container->offsets[i], offset_size); + start += offset_size; + } +} + +struct dbus_builder *_gvariant_builder_new(void *body, size_t body_size) +{ + struct dbus_builder *builder; + struct container *root; + + builder = l_new(struct dbus_builder, 1); + builder->signature = l_string_new(63); + + builder->containers = l_queue_new(); + root = container_new(DBUS_CONTAINER_TYPE_STRUCT, "", 0); + l_queue_push_head(builder->containers, root); + + builder->body = body; + builder->body_size = body_size; + builder->body_pos = body_size; + + builder->mark.container = root; + + return builder; +} + +void _gvariant_builder_free(struct dbus_builder *builder) +{ + if (unlikely(!builder)) + return; + + l_string_free(builder->signature); + l_queue_destroy(builder->containers, + (l_queue_destroy_func_t) container_free); + l_free(builder->body); + + l_free(builder); +} + +static bool enter_struct_dict_common(struct dbus_builder *builder, + const char *signature, + enum dbus_container_type type, + const char open, + const char close) +{ + size_t qlen = l_queue_length(builder->containers); + struct container *container = l_queue_peek_head(builder->containers); + int alignment; + size_t start; + + if (qlen == 1) { + if (l_string_length(builder->signature) + + strlen(signature) + 2 > 255) + return false; + } else { + /* Verify Signatures Match */ + char expect[256]; + const char *start; + const char *end; + + start = container->signature + container->sigindex; + end = validate_next_type(start, &alignment) - 1; + + if (*start != open || *end != close) + return false; + + memcpy(expect, start + 1, end - start - 1); + expect[end - start - 1] = '\0'; + + if (strcmp(expect, signature)) + return false; + } + + alignment = _gvariant_get_alignment(signature); + start = grow_body(builder, 0, alignment); + + container = container_new(type, signature, start); + l_queue_push_head(builder->containers, container); + + return true; +} + +bool _gvariant_builder_enter_struct(struct dbus_builder *builder, + const char *signature) +{ + if (signature[0] && !_gvariant_valid_signature(signature)) + return false; + + return enter_struct_dict_common(builder, signature, + DBUS_CONTAINER_TYPE_STRUCT, '(', ')'); +} + +bool _gvariant_builder_enter_dict(struct dbus_builder *builder, + const char *signature) +{ + if (_gvariant_num_children(signature) != 2) + return false; + + if (!strchr(simple_types, signature[0])) + return false; + + return enter_struct_dict_common(builder, signature, + DBUS_CONTAINER_TYPE_DICT_ENTRY, + '{', '}'); +} + +static bool leave_struct_dict_common(struct dbus_builder *builder, + enum dbus_container_type type, + const char open, + const char close) +{ + struct container *container = l_queue_peek_head(builder->containers); + size_t qlen = l_queue_length(builder->containers); + struct container *parent; + + if (unlikely(qlen <= 1)) + return false; + + if (unlikely(container->type != type)) + return false; + + l_queue_pop_head(builder->containers); + qlen -= 1; + parent = l_queue_peek_head(builder->containers); + + if (_gvariant_is_fixed_size(container->signature)) { + int alignment = _gvariant_get_alignment(container->signature); + grow_body(builder, 0, alignment); + + /* Empty struct or "unit type" is encoded as a zero byte */ + if (container->signature[0] == '\0') { + size_t start = grow_body(builder, 1, 1); + + memset(builder->body + start, 0, 1); + } + + parent->variable_is_last = false; + } else { + size_t offset; + + if (!grow_offsets(parent)) + return false; + + container_append_struct_offsets(container, builder); + offset = builder->body_pos - parent->start; + parent->offsets[parent->offset_index++] = offset; + parent->variable_is_last = true; + } + + if (qlen == 1) + l_string_append_printf(builder->signature, "%c%s%c", + open, + container->signature, + close); + else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) + parent->sigindex += strlen(container->signature) + 2; + + container_free(container); + + return true; +} + +bool _gvariant_builder_leave_struct(struct dbus_builder *builder) +{ + return leave_struct_dict_common(builder, DBUS_CONTAINER_TYPE_STRUCT, + '(', ')'); +} + +bool _gvariant_builder_leave_dict(struct dbus_builder *builder) +{ + return leave_struct_dict_common(builder, + DBUS_CONTAINER_TYPE_DICT_ENTRY, + '{', '}'); +} + +bool _gvariant_builder_enter_variant(struct dbus_builder *builder, + const char *signature) +{ + size_t qlen = l_queue_length(builder->containers); + struct container *container = l_queue_peek_head(builder->containers); + size_t start; + + if (_gvariant_num_children(signature) != 1) + return false; + + if (qlen == 1) { + if (l_string_length(builder->signature) + 1 > 255) + return false; + } else if (container->signature[container->sigindex] != 'v') + return false; + + start = grow_body(builder, 0, 8); + + container = container_new(DBUS_CONTAINER_TYPE_VARIANT, + signature, start); + l_queue_push_head(builder->containers, container); + + return true; +} + +bool _gvariant_builder_leave_variant(struct dbus_builder *builder) +{ + struct container *container = l_queue_peek_head(builder->containers); + size_t qlen = l_queue_length(builder->containers); + struct container *parent; + size_t start; + size_t siglen; + size_t offset; + + if (unlikely(qlen <= 1)) + return false; + + if (unlikely(container->type != DBUS_CONTAINER_TYPE_VARIANT)) + return false; + + l_queue_pop_head(builder->containers); + qlen -= 1; + parent = l_queue_peek_head(builder->containers); + + siglen = strlen(container->signature); + start = grow_body(builder, siglen + 1, 1); + memset(builder->body + start, 0, 1); + memcpy(builder->body + start + 1, container->signature, siglen); + + if (!grow_offsets(parent)) + return false; + + offset = builder->body_pos - parent->start; + parent->offsets[parent->offset_index++] = offset; + parent->variable_is_last = true; + + if (qlen == 1) + l_string_append_c(builder->signature, 'v'); + else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) + parent->sigindex += 1; + + container_free(container); + + return true; +} + +bool _gvariant_builder_enter_array(struct dbus_builder *builder, + const char *signature) +{ + size_t qlen = l_queue_length(builder->containers); + struct container *container = l_queue_peek_head(builder->containers); + size_t start; + int alignment; + + if (_gvariant_num_children(signature) != 1) + return false; + + if (qlen == 1) { + if (l_string_length(builder->signature) + + strlen(signature) + 1 > 255) + return false; + } else { + /* Verify Signatures Match */ + char expect[256]; + const char *start; + const char *end; + + start = container->signature + container->sigindex; + end = validate_next_type(start, &alignment); + + if (*start != 'a') + return false; + + memcpy(expect, start + 1, end - start - 1); + expect[end - start - 1] = '\0'; + + if (strcmp(expect, signature)) + return false; + } + + alignment = _gvariant_get_alignment(signature); + start = grow_body(builder, 0, alignment); + + container = container_new(DBUS_CONTAINER_TYPE_ARRAY, signature, start); + l_queue_push_head(builder->containers, container); + + return true; +} + +bool _gvariant_builder_leave_array(struct dbus_builder *builder) +{ + struct container *container = l_queue_peek_head(builder->containers); + size_t qlen = l_queue_length(builder->containers); + struct container *parent; + size_t offset; + + if (unlikely(qlen <= 1)) + return false; + + if (unlikely(container->type != DBUS_CONTAINER_TYPE_ARRAY)) + return false; + + l_queue_pop_head(builder->containers); + qlen -= 1; + parent = l_queue_peek_head(builder->containers); + + if (!_gvariant_is_fixed_size(container->signature)) + container_append_array_offsets(container, builder); + + if (!grow_offsets(parent)) + return false; + + offset = builder->body_pos - parent->start; + parent->offsets[parent->offset_index++] = offset; + parent->variable_is_last = true; + + if (qlen == 1) + l_string_append_printf(builder->signature, "a%s", + container->signature); + else if (parent->type != DBUS_CONTAINER_TYPE_ARRAY) + parent->sigindex += strlen(container->signature) + 1; + + container_free(container); + + return true; +} + +bool _gvariant_builder_append_basic(struct dbus_builder *builder, + char type, const void *value) +{ + struct container *container = l_queue_peek_head(builder->containers); + size_t start; + unsigned int alignment; + size_t len; + size_t offset; + + if (unlikely(!builder)) + return false; + + if (unlikely(!strchr(simple_types, type))) + return false; + + alignment = get_basic_alignment(type); + if (!alignment) + return false; + + if (l_queue_length(builder->containers) == 1) + l_string_append_c(builder->signature, type); + else if (container->signature[container->sigindex] != type) + return false; + + len = get_basic_fixed_size(type); + + if (len) { + start = grow_body(builder, len, alignment); + memcpy(builder->body + start, value, len); + container->variable_is_last = false; + + if (container->type != DBUS_CONTAINER_TYPE_ARRAY) + container->sigindex += 1; + + return true; + } + + if (!grow_offsets(container)) + return false; + + len = strlen(value) + 1; + start = grow_body(builder, len, alignment); + memcpy(builder->body + start, value, len); + + offset = builder->body_pos - container->start; + container->offsets[container->offset_index++] = offset; + container->variable_is_last = true; + + if (container->type != DBUS_CONTAINER_TYPE_ARRAY) + container->sigindex += 1; + + return true; +} + +bool _gvariant_builder_mark(struct dbus_builder *builder) +{ + struct container *container = l_queue_peek_head(builder->containers); + + builder->mark.container = container; + + if (l_queue_length(builder->containers) == 1) + builder->mark.sig_end = l_string_length(builder->signature); + else + builder->mark.sig_end = container->sigindex; + + builder->mark.body_pos = builder->body_pos; + builder->mark.offset_index = container->offset_index; + builder->mark.variable_is_last = container->variable_is_last; + + return true; +} + +bool _gvariant_builder_rewind(struct dbus_builder *builder) +{ + struct container *container; + + while ((container = l_queue_peek_head(builder->containers)) != + builder->mark.container) { + container_free(container); + l_queue_pop_head(builder->containers); + } + + builder->body_pos = builder->mark.body_pos; + container->offset_index = builder->mark.offset_index; + container->variable_is_last = builder->mark.variable_is_last; + + if (l_queue_length(builder->containers) == 1) + l_string_truncate(builder->signature, builder->mark.sig_end); + else + container->sigindex = builder->mark.sig_end; + + return true; +} + +char *_gvariant_builder_finish(struct dbus_builder *builder, + void **body, size_t *body_size) +{ + char *signature; + struct container *root; + uint8_t *variant_buf; + size_t size; + + if (unlikely(!builder)) + return NULL; + + if (unlikely(l_queue_length(builder->containers) != 1)) + return NULL; + + root = l_queue_peek_head(builder->containers); + + signature = l_string_unwrap(builder->signature); + builder->signature = NULL; + + if (_gvariant_is_fixed_size(signature)) { + int alignment = _gvariant_get_alignment(signature); + grow_body(builder, 0, alignment); + + /* Empty struct or "unit type" is encoded as a zero byte */ + if (signature[0] == '\0') { + size_t start = grow_body(builder, 1, 1); + + memset(builder->body + start, 0, 1); + } + } else + container_append_struct_offsets(root, builder); + + /* + * Make sure there's enough space after the body for the variant + * signature written here but not included in the body size and + * one framing offset value to be written in + * _gvariant_message_finalize. + */ + size = 3 + strlen(signature) + 8; + if (builder->body_pos + size > builder->body_size) + builder->body = l_realloc(builder->body, + builder->body_pos + size); + + variant_buf = builder->body + builder->body_pos; + *variant_buf++ = 0; + *variant_buf++ = '('; + variant_buf = mempcpy(variant_buf, signature, strlen(signature)); + *variant_buf++ = ')'; + + *body = builder->body; + *body_size = builder->body_pos; + builder->body = NULL; + builder->body_size = 0; + + return signature; +} + +/* + * Write the header's framing offset after the body variant which is the + * last piece of data in the message after the header, the padding and + * the builder has written the message body. + */ +size_t _gvariant_message_finalize(size_t header_end, + void *body, size_t body_size, + const char *signature) +{ + size_t offset_start; + size_t offset_size; + + offset_start = body_size + 3 + strlen(signature); + + offset_size = offset_length(align_len(header_end, 8) + offset_start, 1); + + write_word_le(body + offset_start, header_end, offset_size); + + return align_len(header_end, 8) + offset_start + offset_size; +} diff --git a/ell/hashmap.c b/ell/hashmap.c new file mode 100644 index 0000000..0d552e5 --- /dev/null +++ b/ell/hashmap.c @@ -0,0 +1,653 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#include "util.h" +#include "hashmap.h" +#include "private.h" + +/** + * SECTION:hashmap + * @short_description: Hash table support + * + * Hash table support + */ + +#define NBUCKETS 127 + +struct entry { + void *key; + void *value; + struct entry *next; + unsigned int hash; +}; + +/** + * l_hashmap: + * + * Opague object representing the hash table. + */ +struct l_hashmap { + l_hashmap_hash_func_t hash_func; + l_hashmap_compare_func_t compare_func; + l_hashmap_key_new_func_t key_new_func; + l_hashmap_key_free_func_t key_free_func; + unsigned int entries; + struct entry buckets[NBUCKETS]; +}; + +static inline void *get_key_new(const struct l_hashmap *hashmap, + const void *key) +{ + if (hashmap->key_new_func) + return hashmap->key_new_func(key); + + return (void *)key; +} + +static inline void free_key(const struct l_hashmap *hashmap, void *key) +{ + if (hashmap->key_free_func) + hashmap->key_free_func(key); +} + +static inline unsigned int hash_superfast(const uint8_t *key, unsigned int len) +{ + /* + * Paul Hsieh (http://www.azillionmonkeys.com/qed/hash.html) + * used by WebCore (http://webkit.org/blog/8/hashtables-part-2/), + * EFL's eina, kmod and possible others. + */ + unsigned int tmp, hash = len, rem = len & 3; + + len /= 4; + + /* Main loop */ + for (; len > 0; len--) { + hash += l_get_u16(key); + tmp = (l_get_u16(key + 2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + key += 4; + hash += hash >> 11; + } + + /* Handle end cases */ + switch (rem) { + case 3: + hash += l_get_u16(key); + hash ^= hash << 16; + hash ^= key[2] << 18; + hash += hash >> 11; + break; + + case 2: + hash += l_get_u16(key); + hash ^= hash << 11; + hash += hash >> 17; + break; + + case 1: + hash += *key; + hash ^= hash << 10; + hash += hash >> 1; + break; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +static unsigned int direct_hash_func(const void *p) +{ + return L_PTR_TO_UINT(p); +} + +static int direct_compare_func(const void *a, const void *b) +{ + return a < b ? -1 : (a > b ? 1 : 0); +} + +/** + * l_hashmap_new: + * + * Create a new hash table. The keys are managed as pointers, that is, + * the pointer value is hashed and looked up. + * + * No error handling is needed since. In case of real memory allocation + * problems abort() will be called. + * + * See also l_hashmap_string_new(). + * + * Returns: a newly allocated #l_hashmap object + **/ +LIB_EXPORT struct l_hashmap *l_hashmap_new(void) +{ + struct l_hashmap *hashmap; + + hashmap = l_new(struct l_hashmap, 1); + + hashmap->hash_func = direct_hash_func; + hashmap->compare_func = direct_compare_func; + hashmap->entries = 0; + + return hashmap; +} + +LIB_EXPORT unsigned int l_str_hash(const void *p) +{ + const char *s = p; + size_t len = strlen(s); + + return hash_superfast((const uint8_t *)s, len); +} + +/** + * l_hashmap_string_new: + * + * Create a new hash table. The keys are considered strings and are + * copied. + * + * No error handling is needed since. In case of real memory allocation + * problems abort() will be called. + * + * See also l_hashmap_new(). + * + * Returns: a newly allocated #l_hashmap object + **/ +LIB_EXPORT struct l_hashmap *l_hashmap_string_new(void) +{ + struct l_hashmap *hashmap; + + hashmap = l_new(struct l_hashmap, 1); + + hashmap->hash_func = l_str_hash; + hashmap->compare_func = (l_hashmap_compare_func_t) strcmp; + hashmap->key_new_func = (l_hashmap_key_new_func_t) l_strdup; + hashmap->key_free_func = l_free; + hashmap->entries = 0; + + return hashmap; +} + +/** + * l_hashmap_set_hash_function: + * @hashmap: hash table object + * @func: Key hashing function + * + * Sets the hashing function to be used by this object. + * + * This function can only be called when the @hashmap is empty. + * + * Returns: #true when the hashing function could be updated successfully, + * and #false otherwise. + **/ +LIB_EXPORT bool l_hashmap_set_hash_function(struct l_hashmap *hashmap, + l_hashmap_hash_func_t func) +{ + if (unlikely(!hashmap)) + return false; + + if (hashmap->entries != 0) + return false; + + hashmap->hash_func = func; + + return true; +} + +/** + * l_hashmap_set_compare_function: + * @hashmap: hash table object + * @func: Key compare function + * + * Sets the key comparison function to be used by this object. + * + * This function can only be called when the @hashmap is empty. + * + * Returns: #true when the comparison function could be updated successfully, + * and #false otherwise. + **/ +LIB_EXPORT bool l_hashmap_set_compare_function(struct l_hashmap *hashmap, + l_hashmap_compare_func_t func) +{ + if (unlikely(!hashmap)) + return false; + + if (hashmap->entries != 0) + return false; + + hashmap->compare_func = func; + + return true; +} + +/** + * l_hashmap_set_key_copy_function: + * @hashmap: hash table object + * @func: Key duplication function + * + * Sets the key duplication function to be used by this object. If the + * function is NULL, then the keys are assigned directly. + * + * This function can only be called when the @hashmap is empty. + * + * Returns: #true when the key copy function could be updated successfully, + * and #false otherwise. + **/ +LIB_EXPORT bool l_hashmap_set_key_copy_function(struct l_hashmap *hashmap, + l_hashmap_key_new_func_t func) +{ + if (unlikely(!hashmap)) + return false; + + if (hashmap->entries != 0) + return false; + + hashmap->key_new_func = func; + + return true; +} + +/** + * l_hashmap_set_key_free_function: + * @hashmap: hash table object + * @func: Key destructor function + * + * Sets the key destructor function to be used by this object. This function + * should undo the result of the function specified in + * l_hashmap_set_key_copy_function(). This function can be NULL, in which + * case no destructor is called. + * + * This function can only be called when the @hashmap is empty. + * + * Returns: #true when the key free function could be updated successfully, + * and #false otherwise. + **/ +LIB_EXPORT bool l_hashmap_set_key_free_function(struct l_hashmap *hashmap, + l_hashmap_key_free_func_t func) +{ + if (unlikely(!hashmap)) + return false; + + if (hashmap->entries != 0) + return false; + + hashmap->key_free_func = func; + + return true; +} + +/** + * l_hashmap_destroy: + * @hashmap: hash table object + * @destroy: destroy function + * + * Free hash table and call @destory on all remaining entries. + * + * NOTE: While the destroy is in progress, the hashmap is assumed to be + * invariant. The behavior of adding or removing entries while a destroy + * operation is in progress is undefined. + **/ +LIB_EXPORT void l_hashmap_destroy(struct l_hashmap *hashmap, + l_hashmap_destroy_func_t destroy) +{ + unsigned int i; + + if (unlikely(!hashmap)) + return; + + for (i = 0; i < NBUCKETS; i++) { + struct entry *entry, *next, *head = &hashmap->buckets[i]; + + if (!head->next) + continue; + + for (entry = head;; entry = next) { + if (destroy) + destroy(entry->value); + + free_key(hashmap, entry->key); + + next = entry->next; + + if (entry != head) + l_free(entry); + + if (next == head) + break; + } + } + + l_free(hashmap); +} + +/** + * l_hashmap_insert: + * @hashmap: hash table object + * @key: key pointer + * @value: value pointer + * + * Insert new @value entry with @key. + * + * Returns: #true when value has been added and #false in case of failure + **/ +LIB_EXPORT bool l_hashmap_insert(struct l_hashmap *hashmap, + const void *key, void *value) +{ + struct entry *entry, *head; + unsigned int hash; + void *key_new; + + if (unlikely(!hashmap)) + return false; + + key_new = get_key_new(hashmap, key); + hash = hashmap->hash_func(key_new); + head = &hashmap->buckets[hash % NBUCKETS]; + + if (!head->next) { + head->key = key_new; + head->value = value; + head->hash = hash; + head->next = head; + goto done; + } + + entry = l_new(struct entry, 1); + entry->key = key_new; + entry->value = value; + entry->hash = hash; + entry->next = head; + + while (head->next != entry->next) + head = head->next; + + head->next = entry; + +done: + hashmap->entries++; + + return true; +} + +/** + * l_hashmap_remove: + * @hashmap: hash table object + * @key: key pointer + * + * Remove entry for @key. + * + * Returns: value pointer of the removed entry or #NULL in case of failure + **/ +LIB_EXPORT void *l_hashmap_remove(struct l_hashmap *hashmap, const void *key) +{ + struct entry *entry, *head, *prev; + unsigned int hash; + + if (unlikely(!hashmap)) + return NULL; + + hash = hashmap->hash_func(key); + head = &hashmap->buckets[hash % NBUCKETS]; + + if (!head->next) + return NULL; + + for (entry = head, prev = NULL;; prev = entry, entry = entry->next) { + void *value; + + if (entry->hash != hash) + goto next; + + if (hashmap->compare_func(key, entry->key)) + goto next; + + value = entry->value; + + if (entry == head) { + if (entry->next == head) { + free_key(hashmap, entry->key); + head->key = NULL; + head->value = NULL; + head->hash = 0; + head->next = NULL; + } else { + entry = entry->next; + free_key(hashmap, head->key); + head->key = entry->key; + head->value = entry->value; + head->hash = entry->hash; + head->next = entry->next; + l_free(entry); + } + } else { + prev->next = entry->next; + free_key(hashmap, entry->key); + l_free(entry); + } + + hashmap->entries--; + + return value; + +next: + if (entry->next == head) + break; + } + + return NULL; +} + +/** + * l_hashmap_lookup: + * @hashmap: hash table object + * @key: key pointer + * + * Lookup entry for @key. + * + * Returns: value pointer for @key or #NULL in case of failure + **/ +LIB_EXPORT void *l_hashmap_lookup(struct l_hashmap *hashmap, const void *key) +{ + struct entry *entry, *head; + unsigned int hash; + + if (unlikely(!hashmap)) + return NULL; + + hash = hashmap->hash_func(key); + head = &hashmap->buckets[hash % NBUCKETS]; + + if (!head->next) + return NULL; + + for (entry = head;; entry = entry->next) { + if (entry->hash == hash && + !hashmap->compare_func(key, entry->key)) + return entry->value; + + if (entry->next == head) + break; + } + + return NULL; +} + +/** + * l_hashmap_foreach: + * @hashmap: hash table object + * @function: callback function + * @user_data: user data given to callback function + * + * Call @function for every entry in @hashmap. + * + * NOTE: While the foreach is in progress, the hashmap is assumed to be + * invariant. The behavior of adding or removing entries while a foreach + * operation is in progress is undefined. + **/ +LIB_EXPORT void l_hashmap_foreach(struct l_hashmap *hashmap, + l_hashmap_foreach_func_t function, void *user_data) +{ + unsigned int i; + + if (unlikely(!hashmap || !function)) + return; + + for (i = 0; i < NBUCKETS; i++) { + struct entry *entry, *head = &hashmap->buckets[i]; + + if (!head->next) + continue; + + for (entry = head;; entry = entry->next) { + function(entry->key, entry->value, user_data); + + if (entry->next == head) + break; + } + } +} + +/** + * l_hashmap_foreach_remove: + * @hashmap: hash table object + * @function: callback function + * @user_data: user data given to callback function + * + * Call @function for every entry in @hashmap. If the @function returns + * true, then the object will be removed from the hashmap. + * + * NOTE: While the foreach is in progress, the hashmap is assumed to be + * invariant. The behavior of adding or removing entries while a foreach + * operation is in progress is undefined. + * + * Returns: Number of entries removed. + **/ +LIB_EXPORT unsigned int l_hashmap_foreach_remove(struct l_hashmap *hashmap, + l_hashmap_remove_func_t function, + void *user_data) +{ + unsigned int i; + unsigned int nremoved = 0; + + if (unlikely(!hashmap || !function)) + return 0; + + for (i = 0; i < NBUCKETS; i++) { + struct entry *head = &hashmap->buckets[i]; + struct entry *entry; + struct entry *prev; + bool remove; + + if (head->next == NULL) + continue; + + entry = head; + prev = NULL; + + while (true) { + remove = function(entry->key, entry->value, user_data); + + if (!remove) + goto next; + + nremoved += 1; + hashmap->entries -= 1; + + if (entry == head) { + if (entry->next == head) { + free_key(hashmap, entry->key); + head->key = NULL; + head->value = NULL; + head->hash = 0; + head->next = NULL; + break; + } else { + entry = entry->next; + free_key(hashmap, head->key); + head->key = entry->key; + head->value = entry->value; + head->hash = entry->hash; + head->next = entry->next; + l_free(entry); + entry = head; + continue; + } + } else { + prev->next = entry->next; + free_key(hashmap, entry->key); + l_free(entry); + entry = prev->next; + if (entry == head) + break; + continue; + } + +next: + if (entry->next == head) + break; + + prev = entry; + entry = entry->next; + } + } + + return nremoved; +} + +/** + * l_hashmap_size: + * @hashmap: hash table object + * + * Returns: entries in the hash table + **/ +LIB_EXPORT unsigned int l_hashmap_size(struct l_hashmap *hashmap) +{ + if (unlikely(!hashmap)) + return 0; + + return hashmap->entries; +} + +/** + * l_hashmap_isempty: + * @hashmap: hash table object + * + * Returns: #true if hash table is empty and #false if not + **/ +LIB_EXPORT bool l_hashmap_isempty(struct l_hashmap *hashmap) +{ + if (unlikely(!hashmap)) + return true; + + return hashmap->entries == 0; +} diff --git a/ell/hashmap.h b/ell/hashmap.h new file mode 100644 index 0000000..3d23c7e --- /dev/null +++ b/ell/hashmap.h @@ -0,0 +1,78 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_HASHMAP_H +#define __ELL_HASHMAP_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*l_hashmap_foreach_func_t) (const void *key, void *value, + void *user_data); +typedef void (*l_hashmap_destroy_func_t) (void *value); +typedef unsigned int (*l_hashmap_hash_func_t) (const void *p); +typedef int (*l_hashmap_compare_func_t) (const void *a, const void *b); +typedef void *(*l_hashmap_key_new_func_t) (const void *p); +typedef void (*l_hashmap_key_free_func_t) (void *p); +typedef bool (*l_hashmap_remove_func_t)(const void *key, void *value, + void *user_data); + +struct l_hashmap; + +unsigned int l_str_hash(const void *p); + +struct l_hashmap *l_hashmap_new(void); +struct l_hashmap *l_hashmap_string_new(void); + +bool l_hashmap_set_hash_function(struct l_hashmap *hashmap, + l_hashmap_hash_func_t func); +bool l_hashmap_set_compare_function(struct l_hashmap *hashmap, + l_hashmap_compare_func_t func); +bool l_hashmap_set_key_copy_function(struct l_hashmap *hashmap, + l_hashmap_key_new_func_t func); +bool l_hashmap_set_key_free_function(struct l_hashmap *hashmap, + l_hashmap_key_free_func_t func); + +void l_hashmap_destroy(struct l_hashmap *hashmap, + l_hashmap_destroy_func_t destroy); + +bool l_hashmap_insert(struct l_hashmap *hashmap, + const void *key, void *value); +void *l_hashmap_remove(struct l_hashmap *hashmap, const void *key); +void *l_hashmap_lookup(struct l_hashmap *hashmap, const void *key); + +void l_hashmap_foreach(struct l_hashmap *hashmap, + l_hashmap_foreach_func_t function, void *user_data); +unsigned int l_hashmap_foreach_remove(struct l_hashmap *hashmap, + l_hashmap_remove_func_t function, void *user_data); + +unsigned int l_hashmap_size(struct l_hashmap *hashmap); +bool l_hashmap_isempty(struct l_hashmap *hashmap); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_HASHMAP_H */ diff --git a/ell/idle.c b/ell/idle.c new file mode 100644 index 0000000..5c66280 --- /dev/null +++ b/ell/idle.c @@ -0,0 +1,165 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#include + +#include "util.h" +#include "idle.h" +#include "private.h" + +/** + * SECTION:idle + * @short_description: Idle processing support + * + * Idle processing support + */ + +/** + * l_idle: + * + * Opague object representing the idle time event. + */ +struct l_idle { + union { + l_idle_notify_cb_t callback; + l_idle_oneshot_cb_t oneshot; + }; + + l_idle_destroy_cb_t destroy; + void *user_data; + int id; +}; + +static void idle_destroy(void *user_data) +{ + struct l_idle *idle = user_data; + + if (idle->destroy) + idle->destroy(idle->user_data); + + l_free(idle); +} + +static void idle_callback(void *user_data) +{ + struct l_idle *idle = user_data; + + if (idle->callback) + idle->callback(idle, idle->user_data); +} + +static void oneshot_callback(void *user_data) +{ + struct l_idle *idle = user_data; + + if (idle->oneshot) + idle->oneshot(idle->user_data); + + idle_remove(idle->id); +} + +/** + * l_idle_create: + * @callback: idle callback function + * @user_data: user data provided to idle callback function + * @destroy: destroy function for user data + * + * Create a new idle event processing object. + * + * The idle callback will be called until canceled using l_idle_remove(). + * + * Returns: a newly allocated #l_idle object + **/ +LIB_EXPORT struct l_idle *l_idle_create(l_idle_notify_cb_t callback, + void *user_data, l_idle_destroy_cb_t destroy) +{ + struct l_idle *idle; + + if (unlikely(!callback)) + return NULL; + + idle = l_new(struct l_idle, 1); + + idle->callback = callback; + idle->destroy = destroy; + idle->user_data = user_data; + + idle->id = idle_add(idle_callback, idle, 0, idle_destroy); + if (idle->id < 0) { + l_free(idle); + return NULL; + } + + return idle; +} + +/** + * l_idle_oneshot: + * @callback: idle callback function + * @user_data: user data provided to idle callback function + * @destroy: destroy function for user data + * + * Create a new idle event processing object. The callback will be called + * only once at which point the object will be destroyed. + * + * Returns: true if the oneshot idle object could be created successfully. + **/ +LIB_EXPORT bool l_idle_oneshot(l_idle_oneshot_cb_t callback, void *user_data, + l_idle_destroy_cb_t destroy) +{ + struct l_idle *idle; + + if (unlikely(!callback)) + return NULL; + + idle = l_new(struct l_idle, 1); + + idle->oneshot = callback; + idle->destroy = destroy; + idle->user_data = user_data; + + idle->id = idle_add(oneshot_callback, idle, + IDLE_FLAG_NO_WARN_DANGLING, idle_destroy); + if (idle->id < 0) { + l_free(idle); + return false; + } + + return true; +} +/** + * l_idle_remove: + * @idle: idle object + * + * Remove idle event processing object. + **/ +LIB_EXPORT void l_idle_remove(struct l_idle *idle) +{ + if (unlikely(!idle)) + return; + + idle_remove(idle->id); +} diff --git a/ell/idle.h b/ell/idle.h new file mode 100644 index 0000000..19ae4d3 --- /dev/null +++ b/ell/idle.h @@ -0,0 +1,49 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_IDLE_H +#define __ELL_IDLE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct l_idle; + +typedef void (*l_idle_notify_cb_t) (struct l_idle *idle, void *user_data); +typedef void (*l_idle_oneshot_cb_t) (void *user_data); +typedef void (*l_idle_destroy_cb_t) (void *user_data); + +struct l_idle *l_idle_create(l_idle_notify_cb_t callback, + void *user_data, l_idle_destroy_cb_t destroy); +void l_idle_remove(struct l_idle *idle); + +bool l_idle_oneshot(l_idle_oneshot_cb_t callback, void *user_data, + l_idle_destroy_cb_t destroy); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_IDLE_H */ diff --git a/ell/internal b/ell/internal new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ell/internal diff --git a/ell/io.c b/ell/io.c new file mode 100644 index 0000000..878060c --- /dev/null +++ b/ell/io.c @@ -0,0 +1,409 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#include +#include +#include + +#include "util.h" +#include "io.h" +#include "private.h" + +/** + * SECTION:io + * @short_description: IO support + * + * IO support + */ + +/** + * l_io: + * + * Opague object representing the IO. + */ +struct l_io { + int fd; + uint32_t events; + bool close_on_destroy; + l_io_read_cb_t read_handler; + l_io_destroy_cb_t read_destroy; + void *read_data; + l_io_write_cb_t write_handler; + l_io_destroy_cb_t write_destroy; + void *write_data; + l_io_disconnect_cb_t disconnect_handler; + l_io_destroy_cb_t disconnect_destroy; + void *disconnect_data; + l_io_debug_cb_t debug_handler; + l_io_destroy_cb_t debug_destroy; + void *debug_data; +}; + +static void io_cleanup(void *user_data) +{ + struct l_io *io = user_data; + + l_util_debug(io->debug_handler, io->debug_data, "cleanup <%p>", io); + + if (io->write_destroy) + io->write_destroy(io->write_data); + + io->write_handler = NULL; + io->write_data = NULL; + + if (io->read_destroy) + io->read_destroy(io->read_data); + + io->read_handler = NULL; + io->read_data = NULL; + + if (io->close_on_destroy) + close(io->fd); + + io->fd = -1; +} + +static void io_closed(struct l_io *io) +{ + /* + * Save off copies of disconnect_handler, disconnect_destroy + * and disconnect_data in case the handler calls io_destroy + */ + l_io_disconnect_cb_t handler = io->disconnect_handler; + l_io_destroy_cb_t destroy = io->disconnect_destroy; + void *disconnect_data = io->disconnect_data; + + io->disconnect_handler = NULL; + io->disconnect_destroy = NULL; + io->disconnect_data = NULL; + + if (handler) + handler(io, disconnect_data); + + if (destroy) + destroy(disconnect_data); +} + +static void io_callback(int fd, uint32_t events, void *user_data) +{ + struct l_io *io = user_data; + + if (unlikely(events & (EPOLLERR | EPOLLHUP))) { + l_util_debug(io->debug_handler, io->debug_data, + "disconnect event <%p>", io); + watch_remove(io->fd); + io_closed(io); + return; + } + + if ((events & EPOLLIN) && io->read_handler) { + l_util_debug(io->debug_handler, io->debug_data, + "read event <%p>", io); + + if (!io->read_handler(io, io->read_data)) { + if (io->read_destroy) + io->read_destroy(io->read_data); + + io->read_handler = NULL; + io->read_destroy = NULL; + io->read_data = NULL; + + io->events &= ~EPOLLIN; + + if (watch_modify(io->fd, io->events, false) == -EBADF) { + io->close_on_destroy = false; + watch_clear(io->fd); + io_closed(io); + return; + } + } + } + + if ((events & EPOLLOUT) && io->write_handler) { + l_util_debug(io->debug_handler, io->debug_data, + "write event <%p>", io); + + if (!io->write_handler(io, io->write_data)) { + if (io->write_destroy) + io->write_destroy(io->write_data); + + io->write_handler = NULL; + io->write_destroy = NULL; + io->write_data = NULL; + + io->events &= ~EPOLLOUT; + + if (watch_modify(io->fd, io->events, false) == -EBADF) { + io->close_on_destroy = false; + watch_clear(io->fd); + io_closed(io); + return; + } + } + } +} + +/** + * l_io_new: + * @fd: file descriptor + * + * Create new IO handling for a given file descriptor. + * + * Returns: a newly allocated #l_io object + **/ +LIB_EXPORT struct l_io *l_io_new(int fd) +{ + struct l_io *io; + int err; + + if (unlikely(fd < 0)) + return NULL; + + io = l_new(struct l_io, 1); + + io->fd = fd; + io->events = EPOLLHUP | EPOLLERR; + io->close_on_destroy = false; + + err = watch_add(io->fd, io->events, io_callback, io, io_cleanup); + if (err) { + l_free(io); + return NULL; + } + + return io; +} + +/** + * l_io_destroy: + * @io: IO object + * + * Free IO object and close file descriptor (if enabled). + **/ +LIB_EXPORT void l_io_destroy(struct l_io *io) +{ + if (unlikely(!io)) + return; + + if (io->fd != -1) + watch_remove(io->fd); + + io_closed(io); + + if (io->debug_destroy) + io->debug_destroy(io->debug_data); + + l_free(io); +} + +/** + * l_io_get_fd: + * @io: IO object + * + * Returns: file descriptor associated with @io + **/ +LIB_EXPORT int l_io_get_fd(struct l_io *io) +{ + if (unlikely(!io)) + return -1; + + return io->fd; +} + +/** + * l_io_set_close_on_destroy: + * @io: IO object + * @do_close: setting for destroy handling + * + * Set the automatic closing of the file descriptor when destroying @io. + * + * Returns: #true on success and #false on failure + **/ +LIB_EXPORT bool l_io_set_close_on_destroy(struct l_io *io, bool do_close) +{ + if (unlikely(!io)) + return false; + + io->close_on_destroy = do_close; + + return true; +} + +/** + * l_io_set_read_handler: + * @io: IO object + * @callback: read handler callback function + * @user_data: user data provided to read handler callback function + * @destroy: destroy function for user data + * + * Set read function. + * + * Returns: #true on success and #false on failure + **/ +LIB_EXPORT bool l_io_set_read_handler(struct l_io *io, l_io_read_cb_t callback, + void *user_data, l_io_destroy_cb_t destroy) +{ + uint32_t events; + int err; + + if (unlikely(!io || io->fd < 0)) + return false; + + l_util_debug(io->debug_handler, io->debug_data, + "set read handler <%p>", io); + + if (io->read_destroy) + io->read_destroy(io->read_data); + + if (callback) + events = io->events | EPOLLIN; + else + events = io->events & ~EPOLLIN; + + io->read_handler = callback; + io->read_destroy = destroy; + io->read_data = user_data; + + if (events == io->events) + return true; + + err = watch_modify(io->fd, events, false); + if (err) + return false; + + io->events = events; + + return true; +} + +/** + * l_io_set_write_handler: + * @io: IO object + * @callback: write handler callback function + * @user_data: user data provided to write handler callback function + * @destroy: destroy function for user data + * + * Set write function. + * + * Returns: #true on success and #false on failure + **/ +LIB_EXPORT bool l_io_set_write_handler(struct l_io *io, l_io_write_cb_t callback, + void *user_data, l_io_destroy_cb_t destroy) +{ + uint32_t events; + int err; + + if (unlikely(!io || io->fd < 0)) + return false; + + l_util_debug(io->debug_handler, io->debug_data, + "set write handler <%p>", io); + + if (io->write_handler == callback && io->write_destroy == destroy && + io->write_data == user_data) + return true; + + if (io->write_destroy) + io->write_destroy(io->write_data); + + if (callback) + events = io->events | EPOLLOUT; + else + events = io->events & ~EPOLLOUT; + + io->write_handler = callback; + io->write_destroy = destroy; + io->write_data = user_data; + + if (events == io->events) + return true; + + err = watch_modify(io->fd, events, false); + if (err) + return false; + + io->events = events; + + return true; +} + +/** + * l_io_set_disconnect_handler: + * @io: IO object + * @callback: disconnect handler callback function + * @user_data: user data provided to disconnect handler callback function + * @destroy: destroy function for user data + * + * Set disconnect function. + * + * Returns: #true on success and #false on failure + **/ +LIB_EXPORT bool l_io_set_disconnect_handler(struct l_io *io, + l_io_disconnect_cb_t callback, + void *user_data, l_io_destroy_cb_t destroy) +{ + if (unlikely(!io || io->fd < 0)) + return false; + + l_util_debug(io->debug_handler, io->debug_data, + "set disconnect handler <%p>", io); + + if (io->disconnect_destroy) + io->disconnect_destroy(io->disconnect_data); + + io->disconnect_handler = callback; + io->disconnect_destroy = destroy; + io->disconnect_data = user_data; + + return true; +} + +/** + * l_io_set_debug: + * @io: IO object + * @callback: debug callback function + * @user_data: user data provided to debug callback function + * @destroy: destroy function for user data + * + * Set debug function. + * + * Returns: #true on success and #false on failure + **/ +LIB_EXPORT bool l_io_set_debug(struct l_io *io, l_io_debug_cb_t callback, + void *user_data, l_io_destroy_cb_t destroy) +{ + if (unlikely(!io)) + return false; + + if (io->debug_destroy) + io->debug_destroy(io->debug_data); + + io->debug_handler = callback; + io->debug_destroy = destroy; + io->debug_data = user_data; + + return true; +} diff --git a/ell/io.h b/ell/io.h new file mode 100644 index 0000000..0689d08 --- /dev/null +++ b/ell/io.h @@ -0,0 +1,62 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_IO_H +#define __ELL_IO_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct l_io; + +typedef void (*l_io_debug_cb_t) (const char *str, void *user_data); + +typedef bool (*l_io_read_cb_t) (struct l_io *io, void *user_data); +typedef bool (*l_io_write_cb_t) (struct l_io *io, void *user_data); +typedef void (*l_io_disconnect_cb_t) (struct l_io *io, void *user_data); +typedef void (*l_io_destroy_cb_t) (void *user_data); + +struct l_io *l_io_new(int fd); +void l_io_destroy(struct l_io *io); + +int l_io_get_fd(struct l_io *io); +bool l_io_set_close_on_destroy(struct l_io *io, bool do_close); + +bool l_io_set_read_handler(struct l_io *io, l_io_read_cb_t callback, + void *user_data, l_io_destroy_cb_t destroy); +bool l_io_set_write_handler(struct l_io *io, l_io_write_cb_t callback, + void *user_data, l_io_destroy_cb_t destroy); +bool l_io_set_disconnect_handler(struct l_io *io, + l_io_disconnect_cb_t callback, + void *user_data, l_io_destroy_cb_t destroy); + +bool l_io_set_debug(struct l_io *io, l_io_debug_cb_t callback, + void *user_data, l_io_destroy_cb_t destroy); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_IO_H */ diff --git a/ell/log.c b/ell/log.c new file mode 100644 index 0000000..f3f4b0c --- /dev/null +++ b/ell/log.c @@ -0,0 +1,455 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "queue.h" +#include "log.h" +#include "private.h" + +struct debug_section { + struct l_debug_desc *start; + struct l_debug_desc *end; +}; + +struct l_queue *debug_sections; + +/** + * SECTION:log + * @short_description: Logging framework + * + * Logging framework + */ + +/** + * l_debug_desc: + * + * Debug descriptor. + */ + +static void log_null(int priority, const char *file, const char *line, + const char *func, const char *format, va_list ap) +{ +} + +static l_log_func_t log_func = log_null; +static const char *log_ident = ""; +static int log_fd = -1; +static unsigned long log_pid; + +static inline void close_log(void) +{ + if (log_fd > 0) { + close(log_fd); + log_fd = -1; + } +} + +static int open_log(const char *path) +{ + struct sockaddr_un addr; + + log_fd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (log_fd < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (connect(log_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close_log(); + return -1; + } + + return 0; +} + +/** + * l_log_set_ident: + * @ident: string identifier + * + * Sets the log identifier string. + **/ +LIB_EXPORT void l_log_set_ident(const char *ident) +{ + log_ident = ident; +} + +/** + * l_log_set_handler: + * @function: log handler function + * + * Sets the log handler function. + **/ +LIB_EXPORT void l_log_set_handler(l_log_func_t function) +{ + L_DEBUG_SYMBOL(__debug_intern, ""); + + close_log(); + + if (!function) { + log_func = log_null; + return; + } + + log_func = function; +} + +/** + * l_log_set_null: + * + * Disable logging. + **/ +LIB_EXPORT void l_log_set_null(void) +{ + close_log(); + + log_func = log_null; +} + +static void log_stderr(int priority, const char *file, const char *line, + const char *func, const char *format, va_list ap) +{ + vfprintf(stderr, format, ap); +} + +/** + * l_log_set_stderr: + * + * Enable logging to stderr. + **/ +LIB_EXPORT void l_log_set_stderr(void) +{ + close_log(); + + log_func = log_stderr; +} + +static void log_syslog(int priority, const char *file, const char *line, + const char *func, const char *format, va_list ap) +{ + struct msghdr msg; + struct iovec iov[2]; + char hdr[64], *str; + int hdr_len, str_len; + + str_len = vasprintf(&str, format, ap); + if (str_len < 0) + return; + + hdr_len = snprintf(hdr, sizeof(hdr), "<%i>%s[%lu]: ", priority, + log_ident, (unsigned long) log_pid); + + iov[0].iov_base = hdr; + iov[0].iov_len = hdr_len; + iov[1].iov_base = str; + iov[1].iov_len = str_len; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + sendmsg(log_fd, &msg, 0); + + free(str); +} + +/** + * l_log_set_syslog: + * + * Enable logging to syslog. + **/ +LIB_EXPORT void l_log_set_syslog(void) +{ + close_log(); + + if (open_log("/dev/log") < 0) { + log_func = log_null; + return; + } + + log_pid = getpid(); + + log_func = log_syslog; +} + +static void log_journal(int priority, const char *file, const char *line, + const char *func, const char *format, va_list ap) +{ + struct msghdr msg; + struct iovec iov[12]; + char prio[16], *str; + int prio_len, str_len; + + str_len = vasprintf(&str, format, ap); + if (str_len < 0) + return; + + prio_len = snprintf(prio, sizeof(prio), "PRIORITY=%u\n", priority); + + iov[0].iov_base = "MESSAGE="; + iov[0].iov_len = 8; + iov[1].iov_base = str; + iov[1].iov_len = str_len; + iov[2].iov_base = prio; + iov[2].iov_len = prio_len; + iov[3].iov_base = "CODE_FILE="; + iov[3].iov_len = 10; + iov[4].iov_base = (char *) file; + iov[4].iov_len = strlen(file); + iov[5].iov_base = "\n"; + iov[5].iov_len = 1; + iov[6].iov_base = "CODE_LINE="; + iov[6].iov_len = 10; + iov[7].iov_base = (char *) line; + iov[7].iov_len = strlen(line); + iov[8].iov_base = "\n"; + iov[8].iov_len = 1; + iov[9].iov_base = "CODE_FUNC="; + iov[9].iov_len = 10; + iov[10].iov_base = (char *) func; + iov[10].iov_len = strlen(func); + iov[11].iov_base = "\n"; + iov[11].iov_len = 1; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 12; + + sendmsg(log_fd, &msg, 0); + + free(str); +} + +/** + * l_log_set_journal: + * + * Enable logging to journal. + **/ +LIB_EXPORT void l_log_set_journal(void) +{ + close_log(); + + if (open_log("/run/systemd/journal/socket") < 0) { + log_func = log_null; + return; + } + + log_pid = getpid(); + + log_func = log_journal; +} + +/** + * l_log_with_location: + * @priority: priority level + * @file: source file + * @line: source line + * @func: source function + * @format: format string + * @...: format arguments + * + * Log information. + **/ +LIB_EXPORT void l_log_with_location(int priority, + const char *file, const char *line, + const char *func, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + log_func(priority, file, line, func, format, ap); + va_end(ap); +} + +/** + * l_error: + * @format: format string + * @...: format arguments + * + **/ + +/** + * l_warn: + * @format: format string + * @...: format arguments + * + **/ + +/** + * l_info: + * @format: format string + * @...: format arguments + * + **/ + +/** + * l_debug: + * @format: format string + * @...: format arguments + **/ + +static const char *debug_pattern; + +void debug_enable(struct l_debug_desc *start, struct l_debug_desc *stop) +{ + struct l_debug_desc *desc; + char *pattern_copy; + + if (!debug_pattern) + return; + + pattern_copy = strdupa(debug_pattern); + + while (pattern_copy) { + char *str = strsep(&pattern_copy, ":,"); + if (!str) + break; + + for (desc = start; desc < stop; desc++) { + if (!fnmatch(str, desc->file, 0)) + desc->flags |= L_DEBUG_FLAG_PRINT; + if (!fnmatch(str, desc->func, 0)) + desc->flags |= L_DEBUG_FLAG_PRINT; + } + } +} + +void debug_disable(struct l_debug_desc *start, struct l_debug_desc *stop) +{ + struct l_debug_desc *desc; + + for (desc = start; desc < stop; desc++) + desc->flags &= ~L_DEBUG_FLAG_PRINT; +} + +/** + * l_debug_add_section: + * @start: start of the debug section + * @stop: stop of the debug section + * + * Add information about a debug section. This is used by shared libraries + * to tell ell about their debug section start & stopping points. This is used + * to make l_debug statements work across all shared libraries that might be + * linked into the executable + */ +LIB_EXPORT void l_debug_add_section(struct l_debug_desc *start, + struct l_debug_desc *end) +{ + const struct l_queue_entry *entry; + const struct debug_section *section; + struct debug_section *new_section; + + if (!debug_sections) { + debug_sections = l_queue_new(); + goto add; + } + + for (entry = l_queue_get_entries(debug_sections); entry; + entry = entry->next) { + section = entry->data; + + if (section->start == start && section->end == end) + return; + } + +add: + new_section = l_new(struct debug_section, 1); + new_section->start = start; + new_section->end = end; + + l_queue_push_head(debug_sections, new_section); +} + +/** + * l_debug_enable_full: + * @pattern: debug pattern + * @start: start of the debug section + * @stop: end of the debug section + * + * Enable debug sections based on @pattern. + **/ +LIB_EXPORT void l_debug_enable_full(const char *pattern, + struct l_debug_desc *start, + struct l_debug_desc *end) +{ + const struct l_queue_entry *entry; + const struct debug_section *section; + + if (!pattern) + return; + + debug_pattern = pattern; + + l_debug_add_section(start, end); + + for (entry = l_queue_get_entries(debug_sections); entry; + entry = entry->next) { + section = entry->data; + debug_enable(section->start, section->end); + } +} + +/** + * l_debug_disable: + * + * Disable all debug sections. + **/ +LIB_EXPORT void l_debug_disable(void) +{ + const struct l_queue_entry *entry; + const struct debug_section *section; + + for (entry = l_queue_get_entries(debug_sections); entry; + entry = entry->next) { + section = entry->data; + debug_disable(section->start, section->end); + } + + debug_pattern = NULL; +} + +__attribute__((constructor)) static void register_debug_section() +{ + extern struct l_debug_desc __start___ell_debug[]; + extern struct l_debug_desc __stop___ell_debug[]; + + l_debug_add_section(__start___ell_debug, __stop___ell_debug); +} + +__attribute__((destructor(65535))) static void free_debug_sections() +{ + l_queue_destroy(debug_sections, l_free); +} diff --git a/ell/log.h b/ell/log.h new file mode 100644 index 0000000..19bf10b --- /dev/null +++ b/ell/log.h @@ -0,0 +1,101 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_LOG_H +#define __ELL_LOG_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define L_LOG_ERR 3 +#define L_LOG_WARNING 4 +#define L_LOG_INFO 6 +#define L_LOG_DEBUG 7 + +typedef void (*l_log_func_t) (int priority, const char *file, const char *line, + const char *func, const char *format, va_list ap); + +void l_log_set_ident(const char *ident); +void l_log_set_handler(l_log_func_t function); +void l_log_set_null(void); +void l_log_set_stderr(void); +void l_log_set_syslog(void); +void l_log_set_journal(void); + +void l_log_with_location(int priority, const char *file, const char *line, + const char *func, const char *format, ...) + __attribute__((format(printf, 5, 6))); + +#define l_log(priority, format, ...) l_log_with_location(priority, \ + __FILE__, L_STRINGIFY(__LINE__), \ + __func__, format "\n", ##__VA_ARGS__) + +struct l_debug_desc { + const char *file; + const char *func; +#define L_DEBUG_FLAG_DEFAULT (0) +#define L_DEBUG_FLAG_PRINT (1 << 0) + unsigned int flags; +} __attribute__((aligned(8))); + +#define L_DEBUG_SYMBOL(symbol, format, ...) do { \ + static struct l_debug_desc symbol \ + __attribute__((used, section("__ell_debug"), aligned(8))) = { \ + .file = __FILE__, .func = __func__, \ + .flags = L_DEBUG_FLAG_DEFAULT, \ + }; \ + if (symbol.flags & L_DEBUG_FLAG_PRINT) \ + l_log(L_LOG_DEBUG, "%s:%s() " format, __FILE__, \ + __func__ , ##__VA_ARGS__); \ +} while (0) + +void l_debug_enable_full(const char *pattern, + struct l_debug_desc *start, + struct l_debug_desc *stop); +void l_debug_add_section(struct l_debug_desc *start, + struct l_debug_desc *end); + +#define l_debug_enable(pattern) do { \ +_Pragma("GCC diagnostic push") \ +_Pragma("GCC diagnostic ignored \"-Wredundant-decls\"") \ + extern struct l_debug_desc __start___ell_debug[]; \ + extern struct l_debug_desc __stop___ell_debug[]; \ + l_debug_enable_full(pattern, __start___ell_debug, __stop___ell_debug); \ +_Pragma("GCC diagnostic pop") \ +} while (0) + +void l_debug_disable(void); + +#define l_error(format, ...) l_log(L_LOG_ERR, format, ##__VA_ARGS__) +#define l_warn(format, ...) l_log(L_LOG_WARNING, format, ##__VA_ARGS__) +#define l_info(format, ...) l_log(L_LOG_INFO, format, ##__VA_ARGS__) +#define l_debug(format, ...) L_DEBUG_SYMBOL(__debug_desc, format, ##__VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_LOG_H */ diff --git a/ell/main.c b/ell/main.c new file mode 100644 index 0000000..3868f32 --- /dev/null +++ b/ell/main.c @@ -0,0 +1,663 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "signal.h" +#include "queue.h" +#include "log.h" +#include "util.h" +#include "main.h" +#include "private.h" +#include "timeout.h" + +/** + * SECTION:main + * @short_description: Main loop handling + * + * Main loop handling + */ + +#define MAX_EPOLL_EVENTS 10 + +#define IDLE_FLAG_DISPATCHING 1 +#define IDLE_FLAG_DESTROYED 2 + +#define WATCH_FLAG_DISPATCHING 1 +#define WATCH_FLAG_DESTROYED 2 + +#define WATCHDOG_TRIGGER_FREQ 2 + +static int epoll_fd; +static bool epoll_running; +static bool epoll_terminate; +static int idle_id; + +static int notify_fd; + +static struct l_timeout *watchdog; + +static struct l_queue *idle_list; + +struct watch_data { + int fd; + uint32_t events; + uint32_t flags; + watch_event_cb_t callback; + watch_destroy_cb_t destroy; + void *user_data; +}; + +#define DEFAULT_WATCH_ENTRIES 128 + +static unsigned int watch_entries; +static struct watch_data **watch_list; + +struct idle_data { + idle_event_cb_t callback; + idle_destroy_cb_t destroy; + void *user_data; + uint32_t flags; + int id; +}; + +static inline bool __attribute__ ((always_inline)) create_epoll(void) +{ + unsigned int i; + + epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (epoll_fd < 0) { + epoll_fd = 0; + return false; + } + + watch_list = malloc(DEFAULT_WATCH_ENTRIES * sizeof(void *)); + if (!watch_list) + goto close_epoll; + + idle_list = l_queue_new(); + + idle_id = 0; + + watch_entries = DEFAULT_WATCH_ENTRIES; + + for (i = 0; i < watch_entries; i++) + watch_list[i] = NULL; + + return true; + +close_epoll: + close(epoll_fd); + epoll_fd = 0; + + return false; +} + +int watch_add(int fd, uint32_t events, watch_event_cb_t callback, + void *user_data, watch_destroy_cb_t destroy) +{ + struct watch_data *data; + struct epoll_event ev; + int err; + + if (unlikely(fd < 0 || !callback)) + return -EINVAL; + + if (!epoll_fd) + return -EIO; + + if ((unsigned int) fd > watch_entries - 1) + return -ERANGE; + + data = l_new(struct watch_data, 1); + + data->fd = fd; + data->events = events; + data->flags = 0; + data->callback = callback; + data->destroy = destroy; + data->user_data = user_data; + + memset(&ev, 0, sizeof(ev)); + ev.events = events; + ev.data.ptr = data; + + err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, data->fd, &ev); + if (err < 0) { + l_free(data); + return -errno; + } + + watch_list[fd] = data; + + return 0; +} + +int watch_modify(int fd, uint32_t events, bool force) +{ + struct watch_data *data; + struct epoll_event ev; + int err; + + if (unlikely(fd < 0)) + return -EINVAL; + + if ((unsigned int) fd > watch_entries - 1) + return -ERANGE; + + data = watch_list[fd]; + if (!data) + return -ENXIO; + + if (data->events == events && !force) + return 0; + + memset(&ev, 0, sizeof(ev)); + ev.events = events; + ev.data.ptr = data; + + err = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, data->fd, &ev); + if (err < 0) + return -errno; + + data->events = events; + + return 0; +} + +int watch_clear(int fd) +{ + struct watch_data *data; + + if (unlikely(fd < 0)) + return -EINVAL; + + if ((unsigned int) fd > watch_entries - 1) + return -ERANGE; + + data = watch_list[fd]; + if (!data) + return -ENXIO; + + watch_list[fd] = NULL; + + if (data->destroy) + data->destroy(data->user_data); + + if (data->flags & WATCH_FLAG_DISPATCHING) + data->flags |= WATCH_FLAG_DESTROYED; + else + l_free(data); + + return 0; +} + +int watch_remove(int fd) +{ + int err = watch_clear(fd); + + if (err < 0) + return err; + + err = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL); + if (err < 0) + return -errno; + + return err; +} + +static bool idle_remove_by_id(void *data, void *user_data) +{ + struct idle_data *idle = data; + int id = L_PTR_TO_INT(user_data); + + if (idle->id != id) + return false; + + if (idle->destroy) + idle->destroy(idle->user_data); + + if (idle->flags & IDLE_FLAG_DISPATCHING) { + idle->flags |= IDLE_FLAG_DESTROYED; + return false; + } + + l_free(idle); + + return true; +} + +static bool idle_prune(void *data, void *user_data) +{ + struct idle_data *idle = data; + + if ((idle->flags & IDLE_FLAG_DESTROYED) == 0) + return false; + + l_free(idle); + + return true; +} + +int idle_add(idle_event_cb_t callback, void *user_data, uint32_t flags, + idle_destroy_cb_t destroy) +{ + struct idle_data *data; + + if (unlikely(!callback)) + return -EINVAL; + + if (!epoll_fd) + return -EIO; + + data = l_new(struct idle_data, 1); + + data->callback = callback; + data->destroy = destroy; + data->user_data = user_data; + data->flags = flags; + + if (!l_queue_push_tail(idle_list, data)) { + l_free(data); + return -ENOMEM; + } + + data->id = idle_id++; + + if (idle_id == INT_MAX) + idle_id = 0; + + return data->id; +} + +void idle_remove(int id) +{ + l_queue_foreach_remove(idle_list, idle_remove_by_id, + L_INT_TO_PTR(id)); +} + +static void idle_destroy(void *data) +{ + struct idle_data *idle = data; + + if (!(idle->flags & IDLE_FLAG_NO_WARN_DANGLING)) + l_error("Dangling idle descriptor %p, %d found", + data, idle->id); + + if (idle->destroy) + idle->destroy(idle->user_data); + + l_free(idle); +} + +static void idle_dispatch(void *data, void *user_data) +{ + struct idle_data *idle = data; + + if (!idle->callback) + return; + + idle->flags |= IDLE_FLAG_DISPATCHING; + idle->callback(idle->user_data); + idle->flags &= ~IDLE_FLAG_DISPATCHING; +} + +static int sd_notify(const char *state) +{ + int err; + + if (notify_fd <= 0) + return -ENOTCONN; + + err = send(notify_fd, state, strlen(state), MSG_NOSIGNAL); + if (err < 0) + return -errno; + + return 0; +} + +static void watchdog_callback(struct l_timeout *timeout, void *user_data) +{ + int msec = L_PTR_TO_INT(user_data); + + sd_notify("WATCHDOG=1"); + + l_timeout_modify_ms(timeout, msec); +} + +static void create_sd_notify_socket(void) +{ + const char *sock; + struct sockaddr_un addr; + const char *watchdog_usec; + int msec; + + /* check if NOTIFY_SOCKET has been set */ + sock = getenv("NOTIFY_SOCKET"); + if (!sock) + return; + + /* check for abstract socket or absolute path */ + if (sock[0] != '@' && sock[0] != '/') + return; + + notify_fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (notify_fd < 0) { + notify_fd = 0; + return; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sock, sizeof(addr.sun_path) - 1); + + if (addr.sun_path[0] == '@') + addr.sun_path[0] = '\0'; + + if (bind(notify_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(notify_fd); + notify_fd = 0; + return; + } + + watchdog_usec = getenv("WATCHDOG_USEC"); + if (!watchdog_usec) + return; + + msec = atoi(watchdog_usec) / 1000; + if (msec < WATCHDOG_TRIGGER_FREQ) + return; + + msec /= WATCHDOG_TRIGGER_FREQ; + + watchdog = l_timeout_create_ms(msec, watchdog_callback, + L_INT_TO_PTR(msec), NULL); +} + +/** + * l_main_init: + * + * Initialize the main loop. This must be called before l_main_run() + * and any other function that directly or indirectly sets up an idle + * or watch. A safe rule-of-thumb is to call it before any function + * prefixed with "l_". + * + * Returns: true if initialization was successful, false otherwise. + **/ +LIB_EXPORT bool l_main_init(void) +{ + if (unlikely(epoll_running)) + return false; + + if (!create_epoll()) + return false; + + create_sd_notify_socket(); + + epoll_terminate = false; + + return true; +} + +/** + * l_main_prepare: + * + * Prepare the iteration of the main loop + * + * Returns: The timeout to use. This will be 0 if idle-event processing is + * currently pending, or -1 otherwise. This value can be used to pass to + * l_main_iterate. + */ +LIB_EXPORT int l_main_prepare(void) +{ + return l_queue_isempty(idle_list) ? -1 : 0; +} + +/** + * l_main_iterate: + * + * Run one iteration of the main event loop + */ +LIB_EXPORT void l_main_iterate(int timeout) +{ + struct epoll_event events[MAX_EPOLL_EVENTS]; + struct watch_data *data; + int n, nfds; + + nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS, timeout); + + for (n = 0; n < nfds; n++) { + data = events[n].data.ptr; + + data->flags |= WATCH_FLAG_DISPATCHING; + } + + for (n = 0; n < nfds; n++) { + data = events[n].data.ptr; + + if (data->flags & WATCH_FLAG_DESTROYED) + continue; + + data->callback(data->fd, events[n].events, + data->user_data); + } + + for (n = 0; n < nfds; n++) { + data = events[n].data.ptr; + + if (data->flags & WATCH_FLAG_DESTROYED) + l_free(data); + else + data->flags = 0; + } + + l_queue_foreach(idle_list, idle_dispatch, NULL); + l_queue_foreach_remove(idle_list, idle_prune, NULL); +} + +/** + * l_main_run: + * + * Run the main loop + * + * The loop may be restarted by invoking this function after a + * previous invocation returns, provided that l_main_exit() has not + * been called first. + * + * Returns: #EXIT_SUCCESS after successful execution or #EXIT_FAILURE in + * case of failure + **/ +LIB_EXPORT int l_main_run(void) +{ + int timeout; + + /* Has l_main_init() been called? */ + if (unlikely(!epoll_fd)) + return EXIT_FAILURE; + + if (unlikely(epoll_running)) + return EXIT_FAILURE; + + epoll_running = true; + + for (;;) { + if (epoll_terminate) + break; + + timeout = l_main_prepare(); + l_main_iterate(timeout); + } + + epoll_running = false; + + if (notify_fd) { + close(notify_fd); + notify_fd = 0; + l_timeout_remove(watchdog); + watchdog = NULL; + } + + return EXIT_SUCCESS; +} + +/** + * l_main_exit: + * + * Clean up after main loop completes. + * + **/ +LIB_EXPORT bool l_main_exit(void) +{ + unsigned int i; + + if (epoll_running) { + l_error("Cleanup attempted on running main loop"); + return false; + } + + for (i = 0; i < watch_entries; i++) { + struct watch_data *data = watch_list[i]; + + if (!data) + continue; + + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, data->fd, NULL); + + if (data->destroy) + data->destroy(data->user_data); + else + l_error("Dangling file descriptor %d found", data->fd); + + l_free(data); + } + + watch_entries = 0; + + free(watch_list); + watch_list = NULL; + + l_queue_destroy(idle_list, idle_destroy); + idle_list = NULL; + + close(epoll_fd); + epoll_fd = 0; + + return true; +} + +/** + * l_main_quit: + * + * Teminate the running main loop + * + * Returns: #true when terminating the main loop or #false in case of failure + **/ +LIB_EXPORT bool l_main_quit(void) +{ + if (unlikely(!epoll_running)) + return false; + + epoll_terminate = true; + + return true; +} + +struct signal_data { + l_main_signal_cb_t callback; + void *user_data; +}; + +static void sigint_handler(void *user_data) +{ + struct signal_data *data = user_data; + + if (data->callback) + data->callback(SIGINT, data->user_data); +} + +static void sigterm_handler(void *user_data) +{ + struct signal_data *data = user_data; + + if (data->callback) + data->callback(SIGTERM, data->user_data); +} + +/** + * l_main_run_with_signal: + * + * Run the main loop with signal handling for SIGINT and SIGTERM + * + * Returns: #EXIT_SUCCESS after successful execution or #EXIT_FAILURE in + * case of failure + **/ +LIB_EXPORT int l_main_run_with_signal(l_main_signal_cb_t callback, + void *user_data) +{ + struct signal_data *data; + struct l_signal *sigint; + struct l_signal *sigterm; + int result; + + data = l_new(struct signal_data, 1); + + data->callback = callback; + data->user_data = user_data; + + sigint = l_signal_create(SIGINT, sigint_handler, data, NULL); + sigterm = l_signal_create(SIGTERM, sigterm_handler, data, NULL); + + result = l_main_run(); + + l_signal_remove(sigint); + l_signal_remove(sigterm); + + l_free(data); + + return result; +} + +/** + * l_main_get_epoll_fd: + * + * Can be used to obtain the epoll file descriptor in order to integrate + * the ell main event loop with other event loops. + * + * Returns: epoll file descriptor + **/ +LIB_EXPORT int l_main_get_epoll_fd(void) +{ + return epoll_fd; +} diff --git a/ell/main.h b/ell/main.h new file mode 100644 index 0000000..99b34ad --- /dev/null +++ b/ell/main.h @@ -0,0 +1,51 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_MAIN_H +#define __ELL_MAIN_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool l_main_init(void); +int l_main_prepare(void); +void l_main_iterate(int timeout); +int l_main_run(void); +bool l_main_exit(void); + +bool l_main_quit(void); + +typedef void (*l_main_signal_cb_t) (uint32_t signo, void *user_data); + +int l_main_run_with_signal(l_main_signal_cb_t callback, void *user_data); + +int l_main_get_epoll_fd(); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_MAIN_H */ diff --git a/ell/missing.h b/ell/missing.h new file mode 100644 index 0000000..37d5586 --- /dev/null +++ b/ell/missing.h @@ -0,0 +1,64 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#include + +#ifndef __NR_getrandom +# if defined __x86_64__ +# define __NR_getrandom 318 +# elif defined(__i386__) +# define __NR_getrandom 355 +# elif defined(__arm__) +# define __NR_getrandom 384 +# elif defined(__aarch64__) +# define __NR_getrandom 278 +# elif defined(__ia64__) +# define __NR_getrandom 1339 +# elif defined(__m68k__) +# define __NR_getrandom 352 +# elif defined(__s390x__) +# define __NR_getrandom 349 +# elif defined(__powerpc__) +# define __NR_getrandom 359 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_getrandom 4353 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_getrandom 6317 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_getrandom 5313 +# endif +# else +# warning "__NR_getrandom unknown for your architecture" +# define __NR_getrandom 0xffffffff +# endif +#endif + +#ifndef HAVE_EXPLICIT_BZERO +static inline void explicit_bzero(void *s, size_t n) +{ + memset(s, 0, n); + __asm__ __volatile__ ("" : : "r"(s) : "memory"); +} +#endif diff --git a/ell/private.h b/ell/private.h new file mode 100644 index 0000000..4dc48d7 --- /dev/null +++ b/ell/private.h @@ -0,0 +1,56 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#include +#include + +#include + +#define uninitialized_var(x) x = x + +#define align_len(len, boundary) (((len)+(boundary)-1) & ~((boundary)-1)) + +#define LIB_EXPORT __attribute__ ((visibility("default"))) + +struct l_debug_desc; + +void debug_enable(struct l_debug_desc *start, struct l_debug_desc *stop); +void debug_disable(struct l_debug_desc *start, struct l_debug_desc *stop); + +void plugin_update_debug(void); + +typedef void (*watch_event_cb_t) (int fd, uint32_t events, void *user_data); +typedef void (*watch_destroy_cb_t) (void *user_data); + +typedef void (*idle_event_cb_t) (void *user_data); +typedef void (*idle_destroy_cb_t) (void *user_data); + +int watch_add(int fd, uint32_t events, watch_event_cb_t callback, + void *user_data, watch_destroy_cb_t destroy); +int watch_modify(int fd, uint32_t events, bool force); +int watch_remove(int fd); +int watch_clear(int fd); + +#define IDLE_FLAG_NO_WARN_DANGLING 0x10000000 +int idle_add(idle_event_cb_t callback, void *user_data, uint32_t flags, + idle_destroy_cb_t destroy); +void idle_remove(int id); diff --git a/ell/queue.c b/ell/queue.c new file mode 100644 index 0000000..0f6ed3e --- /dev/null +++ b/ell/queue.c @@ -0,0 +1,589 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#include "util.h" +#include "queue.h" +#include "private.h" + +/** + * SECTION:queue + * @short_description: Queue support + * + * Queue support + */ + +/** + * l_queue: + * + * Opague object representing the queue. + */ +struct l_queue { + struct l_queue_entry *head; + struct l_queue_entry *tail; + unsigned int entries; +}; + +/** + * l_queue_new: + * + * Create a new queue. + * + * No error handling is needed since. In case of real memory allocation + * problems abort() will be called. + * + * Returns: a newly allocated #l_queue object + **/ +LIB_EXPORT struct l_queue *l_queue_new(void) +{ + struct l_queue *queue; + + queue = l_new(struct l_queue, 1); + + queue->head = NULL; + queue->tail = NULL; + queue->entries = 0; + + return queue; +} + +/** + * l_queue_destroy: + * @queue: queue object + * @destroy: destroy function + * + * Free queue and call @destory on all remaining entries. + **/ +LIB_EXPORT void l_queue_destroy(struct l_queue *queue, + l_queue_destroy_func_t destroy) +{ + l_queue_clear(queue, destroy); + l_free(queue); +} + +/** + * l_queue_clear: + * @queue: queue object + * @destroy: destroy function + * + * Clear queue and call @destory on all remaining entries. + **/ +LIB_EXPORT void l_queue_clear(struct l_queue *queue, + l_queue_destroy_func_t destroy) +{ + struct l_queue_entry *entry; + + if (unlikely(!queue)) + return; + + entry = queue->head; + + while (entry) { + struct l_queue_entry *tmp = entry; + + if (destroy) + destroy(entry->data); + + entry = entry->next; + + l_free(tmp); + } + + queue->head = NULL; + queue->tail = NULL; + queue->entries = 0; +} + +/** + * l_queue_push_tail: + * @queue: queue object + * @data: pointer to data + * + * Adds @data pointer at the end of the queue. + * + * Returns: #true when data has been added and #false in case an invalid + * @queue object has been provided + **/ +LIB_EXPORT bool l_queue_push_tail(struct l_queue *queue, void *data) +{ + struct l_queue_entry *entry; + + if (unlikely(!queue)) + return false; + + entry = l_new(struct l_queue_entry, 1); + + entry->data = data; + entry->next = NULL; + + if (queue->tail) + queue->tail->next = entry; + + queue->tail = entry; + + if (!queue->head) + queue->head = entry; + + queue->entries++; + + return true; +} + +/** + * l_queue_push_head: + * @queue: queue object + * @data: pointer to data + * + * Adds @data pointer at the start of the queue. + * + * Returns: #true when data has been added and #false in case an invalid + * @queue object has been provided + **/ +LIB_EXPORT bool l_queue_push_head(struct l_queue *queue, void *data) +{ + struct l_queue_entry *entry; + + if (unlikely(!queue)) + return false; + + entry = l_new(struct l_queue_entry, 1); + + entry->data = data; + entry->next = queue->head; + + queue->head = entry; + + if (!queue->tail) + queue->tail = entry; + + queue->entries++; + + return true; +} + +/** + * l_queue_pop_head: + * @queue: queue object + * + * Removes the first element of the queue an returns it. + * + * Returns: data pointer to first element or #NULL in case an empty queue + **/ +LIB_EXPORT void *l_queue_pop_head(struct l_queue *queue) +{ + struct l_queue_entry *entry; + void *data; + + if (unlikely(!queue)) + return NULL; + + if (!queue->head) + return NULL; + + entry = queue->head; + + if (!queue->head->next) { + queue->head = NULL; + queue->tail = NULL; + } else + queue->head = queue->head->next; + + data = entry->data; + + l_free(entry); + + queue->entries--; + + return data; +} + +/** + * l_queue_peek_head: + * @queue: queue object + * + * Peeks at the first element of the queue an returns it. + * + * Returns: data pointer to first element or #NULL in case an empty queue + **/ +LIB_EXPORT void *l_queue_peek_head(struct l_queue *queue) +{ + struct l_queue_entry *entry; + + if (unlikely(!queue)) + return NULL; + + if (!queue->head) + return NULL; + + entry = queue->head; + return entry->data; +} + +/** + * l_queue_peek_tail: + * @queue: queue object + * + * Peeks at the last element of the queue an returns it. + * + * Returns: data pointer to first element or #NULL in case an empty queue + **/ +LIB_EXPORT void *l_queue_peek_tail(struct l_queue *queue) +{ + struct l_queue_entry *entry; + + if (unlikely(!queue)) + return NULL; + + if (!queue->tail) + return NULL; + + entry = queue->tail; + return entry->data; +} + +/** + * l_queue_insert: + * @queue: queue object + * @data: pointer to data + * @function: compare function + * @user_data: user data given to compare function + * + * Inserts @data pointer at a position in the queue determined by the + * compare @function. @function should return > 0 if the @data (first + * parameter) should be inserted after the current entry (second parameter). + * + * Returns: #true when data has been added and #false in case of failure + **/ +LIB_EXPORT bool l_queue_insert(struct l_queue *queue, void *data, + l_queue_compare_func_t function, void *user_data) +{ + struct l_queue_entry *entry, *prev, *cur; + int cmp; + + if (unlikely(!queue || !function)) + return false; + + entry = l_new(struct l_queue_entry, 1); + + entry->data = data; + entry->next = NULL; + + if (!queue->head) { + queue->head = entry; + queue->tail = entry; + goto done; + } + + for (prev = NULL, cur = queue->head; cur; prev = cur, cur = cur->next) { + cmp = function(entry->data, cur->data, user_data); + + if (cmp >= 0) + continue; + + if (prev == NULL) { + entry->next = queue->head; + queue->head = entry; + goto done; + } + + entry->next = cur; + prev->next = entry; + + goto done; + } + + queue->tail->next = entry; + queue->tail = entry; + +done: + queue->entries++; + + return true; +} + +/** + * l_queue_find: + * @queue: queue object + * @function: match function + * @user_data: user data given to compare function + * + * Finds an entry in the queue by running the match @function + * + * Returns: Matching entry or NULL if no entry can be found + **/ +LIB_EXPORT void *l_queue_find(struct l_queue *queue, + l_queue_match_func_t function, + const void *user_data) +{ + struct l_queue_entry *entry; + + if (unlikely(!queue || !function)) + return NULL; + + for (entry = queue->head; entry; entry = entry->next) + if (function(entry->data, user_data)) + return entry->data; + + return NULL; +} + +/** + * l_queue_remove: + * @queue: queue object + * @data: pointer to data + * + * Remove given @data from the queue. + * + * Returns: #true when data has been removed and #false when data could not + * be found or an invalid @queue object has been provided + **/ +LIB_EXPORT bool l_queue_remove(struct l_queue *queue, void *data) +{ + struct l_queue_entry *entry, *prev; + + if (unlikely(!queue)) + return false; + + for (entry = queue->head, prev = NULL; entry; + prev = entry, entry = entry->next) { + if (entry->data != data) + continue; + + if (prev) + prev->next = entry->next; + else + queue->head = entry->next; + + if (!entry->next) + queue->tail = prev; + + l_free(entry); + + queue->entries--; + + return true; + } + + return false; +} + +/** + * l_queue_reverse: + * @queue: queue object + * + * Reverse entries in the queue. + * + * Returns: #true on success and #false on failure + **/ +LIB_EXPORT bool l_queue_reverse(struct l_queue *queue) +{ + struct l_queue_entry *entry, *prev = NULL; + + if (unlikely(!queue)) + return false; + + entry = queue->head; + + while (entry) { + struct l_queue_entry *next = entry->next; + + entry->next = prev; + + prev = entry; + entry = next; + } + + queue->tail = queue->head; + queue->head = prev; + + return true; +} + +/** + * l_queue_foreach: + * @queue: queue object + * @function: callback function + * @user_data: user data given to callback function + * + * Call @function for every given data in @queue. + **/ +LIB_EXPORT void l_queue_foreach(struct l_queue *queue, + l_queue_foreach_func_t function, void *user_data) +{ + struct l_queue_entry *entry; + + if (unlikely(!queue || !function)) + return; + + for (entry = queue->head; entry; entry = entry->next) + function(entry->data, user_data); +} + +/** + * l_queue_foreach_remove: + * @queue: queue object + * @function: callback function + * @user_data: user data given to callback function + * + * Remove all entries in the @queue where @function returns #true. + * + * Returns: number of removed entries + **/ +LIB_EXPORT unsigned int l_queue_foreach_remove(struct l_queue *queue, + l_queue_remove_func_t function, void *user_data) +{ + struct l_queue_entry *entry, *prev = NULL; + unsigned int count = 0; + + if (unlikely(!queue || !function)) + return 0; + + entry = queue->head; + + while (entry) { + if (function(entry->data, user_data)) { + struct l_queue_entry *tmp = entry; + + if (prev) + prev->next = entry->next; + else + queue->head = entry->next; + + if (!entry->next) + queue->tail = prev; + + entry = entry->next; + + l_free(tmp); + + count++; + } else { + prev = entry; + entry = entry->next; + } + } + + queue->entries -= count; + + return count; +} + +/** + * l_queue_remove_if + * @queue: queue object + * @function: callback function + * @user_data: user data given to callback function + * + * Remove the first entry in the @queue where the function returns #true. + * + * Returns: NULL if no entry was found, or the entry data if removal was + * successful. + **/ +LIB_EXPORT void *l_queue_remove_if(struct l_queue *queue, + l_queue_match_func_t function, + const void *user_data) +{ + struct l_queue_entry *entry, *prev = NULL; + + if (unlikely(!queue || !function)) + return NULL; + + entry = queue->head; + + while (entry) { + if (function(entry->data, user_data)) { + struct l_queue_entry *tmp = entry; + void *data; + + if (prev) + prev->next = entry->next; + else + queue->head = entry->next; + + if (!entry->next) + queue->tail = prev; + + entry = entry->next; + + data = tmp->data; + + l_free(tmp); + queue->entries--; + + return data; + } else { + prev = entry; + entry = entry->next; + } + } + + return NULL; +} + +/** + * l_queue_length: + * @queue: queue object + * + * Returns: entries of the queue + **/ +LIB_EXPORT unsigned int l_queue_length(struct l_queue *queue) +{ + if (unlikely(!queue)) + return 0; + + return queue->entries; +} + +/** + * l_queue_isempty: + * @queue: queue object + * + * Returns: #true if @queue is empty and #false is not + **/ +LIB_EXPORT bool l_queue_isempty(struct l_queue *queue) +{ + if (unlikely(!queue)) + return true; + + return queue->entries == 0; +} + +/** + * l_queue_get_entries: + * @queue: queue object + * + * This function gives direct, read-only access to the internal list structure + * of the queue. This can be used to efficiently traverse the elements. + * + * Returns: A pointer to the head of the queue. + **/ +LIB_EXPORT const struct l_queue_entry *l_queue_get_entries( + struct l_queue *queue) +{ + if (unlikely(!queue)) + return NULL; + + return queue->head; +} diff --git a/ell/queue.h b/ell/queue.h new file mode 100644 index 0000000..ff46a08 --- /dev/null +++ b/ell/queue.h @@ -0,0 +1,82 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_QUEUE_H +#define __ELL_QUEUE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*l_queue_foreach_func_t) (void *data, void *user_data); +typedef void (*l_queue_destroy_func_t) (void *data); +typedef int (*l_queue_compare_func_t) (const void *a, const void *b, + void *user_data); +typedef bool (*l_queue_match_func_t) (const void *a, const void *b); +typedef bool (*l_queue_remove_func_t) (void *data, void *user_data); + +struct l_queue; + +struct l_queue_entry { + void *data; + struct l_queue_entry *next; +}; + +struct l_queue *l_queue_new(void); +void l_queue_destroy(struct l_queue *queue, + l_queue_destroy_func_t destroy); +void l_queue_clear(struct l_queue *queue, + l_queue_destroy_func_t destroy); + +bool l_queue_push_tail(struct l_queue *queue, void *data); +bool l_queue_push_head(struct l_queue *queue, void *data); +void *l_queue_pop_head(struct l_queue *queue); +void *l_queue_peek_head(struct l_queue *queue); +void *l_queue_peek_tail(struct l_queue *queue); + +bool l_queue_insert(struct l_queue *queue, void *data, + l_queue_compare_func_t function, void *user_data); +void *l_queue_find(struct l_queue *queue, + l_queue_match_func_t function, const void *user_data); +bool l_queue_remove(struct l_queue *queue, void *data); +void *l_queue_remove_if(struct l_queue *queue, + l_queue_match_func_t function, const void *user_data); + +bool l_queue_reverse(struct l_queue *queue); + +void l_queue_foreach(struct l_queue *queue, + l_queue_foreach_func_t function, void *user_data); +unsigned int l_queue_foreach_remove(struct l_queue *queue, + l_queue_remove_func_t function, void *user_data); + +unsigned int l_queue_length(struct l_queue *queue); +bool l_queue_isempty(struct l_queue *queue); + +const struct l_queue_entry *l_queue_get_entries(struct l_queue *queue); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_QUEUE_H */ diff --git a/ell/random.c b/ell/random.c new file mode 100644 index 0000000..0b5b09e --- /dev/null +++ b/ell/random.c @@ -0,0 +1,105 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2015 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "random.h" +#include "private.h" +#include "missing.h" + +#ifndef GRND_NONBLOCK +#define GRND_NONBLOCK 0x0001 +#endif + +#ifndef GRND_RANDOM +#define GRND_RANDOM 0x0002 +#endif + +static inline int getrandom(void *buffer, size_t count, unsigned flags) { + return syscall(__NR_getrandom, buffer, count, flags); +} + +/** + * l_getrandom: + * @buf: buffer to fill with random data + * @len: length of random data requested + * + * Request a number of randomly generated bytes given by @len and put them + * into buffer @buf. + * + * Returns: true if the random data could be generated, false otherwise. + **/ +LIB_EXPORT bool l_getrandom(void *buf, size_t len) +{ + while (len) { + int ret; + + ret = L_TFR(getrandom(buf, len, 0)); + if (ret < 0) + return false; + + buf += ret; + len -= ret; + } + + return true; +} + +LIB_EXPORT bool l_getrandom_is_supported() +{ + static bool initialized = false; + static bool supported = true; + uint8_t buf[4]; + int ret; + + if (initialized) + return supported; + + ret = getrandom(buf, sizeof(buf), GRND_NONBLOCK); + + if (ret < 0 && errno == ENOSYS) + supported = false; + + initialized = true; + return supported; +} + +LIB_EXPORT uint32_t l_getrandom_uint32(void) +{ + int ret; + uint32_t u; + + ret = getrandom(&u, sizeof(u), GRND_NONBLOCK); + + if (ret == sizeof(u)) + return u; + + return random() * RAND_MAX + random(); +} diff --git a/ell/random.h b/ell/random.h new file mode 100644 index 0000000..49a4637 --- /dev/null +++ b/ell/random.h @@ -0,0 +1,43 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2015 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 + * + */ + +#ifndef __ELL_RANDOM_H +#define __ELL_RANDOM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +bool l_getrandom(void *buf, size_t len); +bool l_getrandom_is_supported(); + +uint32_t l_getrandom_uint32(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_RANDOM_H */ diff --git a/ell/signal.c b/ell/signal.c new file mode 100644 index 0000000..2afdb45 --- /dev/null +++ b/ell/signal.c @@ -0,0 +1,268 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "io.h" +#include "queue.h" +#include "signal.h" +#include "private.h" + +/** + * SECTION:signal + * @short_description: Unix signal support + * + * Unix signal support + */ + +/** + * l_signal: + * + * Opague object representing the signal. + */ +struct l_signal { + struct signal_desc *desc; + l_signal_notify_cb_t callback; + void *user_data; + l_signal_destroy_cb_t destroy; +}; + +struct signal_desc { + uint32_t signo; + struct l_queue *callbacks; +}; + +static struct l_io *signalfd_io = NULL; +static struct l_queue *signal_list = NULL; +static sigset_t signal_mask; + +static void handle_callback(struct signal_desc *desc) +{ + const struct l_queue_entry *entry; + + for (entry = l_queue_get_entries(desc->callbacks); entry; + entry = entry->next) { + struct l_signal *signal = entry->data; + + if (signal->callback) + signal->callback(signal->user_data); + } +} + +static bool desc_match_signo(const void *a, const void *b) +{ + const struct signal_desc *desc = a; + uint32_t signo = L_PTR_TO_UINT(b); + + return (desc->signo == signo); +} + +static bool signalfd_read_cb(struct l_io *io, void *user_data) +{ + int fd = l_io_get_fd(io); + struct signal_desc *desc; + struct signalfd_siginfo si; + ssize_t result; + + result = read(fd, &si, sizeof(si)); + if (result != sizeof(si)) + return true; + + desc = l_queue_find(signal_list, desc_match_signo, + L_UINT_TO_PTR(si.ssi_signo)); + if (desc) + handle_callback(desc); + + return true; +} + +static bool signalfd_add(int signo) +{ + int fd; + + if (!signalfd_io) { + fd = -1; + sigemptyset(&signal_mask); + } else + fd = l_io_get_fd(signalfd_io); + + sigaddset(&signal_mask, signo); + + fd = signalfd(fd, &signal_mask, SFD_CLOEXEC); + if (fd < 0) + return false; + + if (signalfd_io) + return true; + + signalfd_io = l_io_new(fd); + if (!signalfd_io) { + close(fd); + return false; + } + + l_io_set_close_on_destroy(signalfd_io, true); + + if (!l_io_set_read_handler(signalfd_io, signalfd_read_cb, NULL, NULL)) { + l_io_destroy(signalfd_io); + return false; + } + + signal_list = l_queue_new(); + + return true; +} + +static void signalfd_remove(int signo) +{ + if (!signalfd_io) + return; + + sigdelset(&signal_mask, signo); + + if (!sigisemptyset(&signal_mask)) { + signalfd(l_io_get_fd(signalfd_io), &signal_mask, SFD_CLOEXEC); + return; + } + + l_io_destroy(signalfd_io); + signalfd_io = NULL; + + l_queue_destroy(signal_list, NULL); + signal_list = NULL; +} + +/** + * l_signal_create: + * @callback: signal callback function + * @user_data: user data provided to signal callback function + * @destroy: destroy function for user data + * + * Create new signal callback handling for a given set of signals. + * + * Returns: a newly allocated #l_signal object + **/ +LIB_EXPORT struct l_signal *l_signal_create(uint32_t signo, + l_signal_notify_cb_t callback, + void *user_data, l_signal_destroy_cb_t destroy) +{ + struct l_signal *signal; + struct signal_desc *desc; + sigset_t mask, oldmask; + + if (signo <= 1 || signo >= _NSIG) + return NULL; + + signal = l_new(struct l_signal, 1); + signal->callback = callback; + signal->destroy = destroy; + signal->user_data = user_data; + + desc = l_queue_find(signal_list, desc_match_signo, + L_UINT_TO_PTR(signo)); + if (desc) + goto done; + + sigemptyset(&mask); + sigaddset(&mask, signo); + + if (sigprocmask(SIG_BLOCK, &mask, &oldmask) < 0) { + l_free(signal); + return NULL; + } + + if (!signalfd_add(signo)) { + sigprocmask(SIG_SETMASK, &oldmask, NULL); + l_free(signal); + return NULL; + } + + desc = l_new(struct signal_desc, 1); + desc->signo = signo; + desc->callbacks = l_queue_new(); + + l_queue_push_tail(signal_list, desc); + +done: + l_queue_push_tail(desc->callbacks, signal); + signal->desc = desc; + return signal; +} + +/** + * l_signal_remove: + * @signal: signal object + * + * Remove signal handling. + **/ +LIB_EXPORT void l_signal_remove(struct l_signal *signal) +{ + struct signal_desc *desc; + sigset_t mask; + + if (!signal) + return; + + desc = signal->desc; + l_queue_remove(desc->callbacks, signal); + + /* + * As long as the signal descriptor has callbacks registered, it is + * still needed to be active. + */ + if (!l_queue_isempty(desc->callbacks)) + goto done; + + if (!l_queue_remove(signal_list, desc)) + goto done; + + sigemptyset(&mask); + sigaddset(&mask, desc->signo); + + /* + * When the number of signals goes to zero, then this will close + * the signalfd file descriptor, otherwise it will only adjust the + * signal mask to account for the removed signal. + * + */ + signalfd_remove(desc->signo); + sigprocmask(SIG_UNBLOCK, &mask, NULL); + l_queue_destroy(desc->callbacks, NULL); + l_free(desc); + +done: + if (signal->destroy) + signal->destroy(signal->user_data); + + l_free(signal); +} diff --git a/ell/signal.h b/ell/signal.h new file mode 100644 index 0000000..1d98476 --- /dev/null +++ b/ell/signal.h @@ -0,0 +1,45 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_SIGNAL_H +#define __ELL_SIGNAL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct l_signal; + +typedef void (*l_signal_notify_cb_t) (void *user_data); +typedef void (*l_signal_destroy_cb_t) (void *user_data); + +struct l_signal *l_signal_create(uint32_t signo, l_signal_notify_cb_t callback, + void *user_data, l_signal_destroy_cb_t destroy); +void l_signal_remove(struct l_signal *signal); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_SIGNAL_H */ diff --git a/ell/siphash-private.h b/ell/siphash-private.h new file mode 100644 index 0000000..02e9ed0 --- /dev/null +++ b/ell/siphash-private.h @@ -0,0 +1,27 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#include +#include + +void _siphash24(uint8_t out[8], const uint8_t *in, size_t inlen, + const uint8_t k[16]); diff --git a/ell/siphash.c b/ell/siphash.c new file mode 100644 index 0000000..a37d228 --- /dev/null +++ b/ell/siphash.c @@ -0,0 +1,138 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#include "siphash-private.h" + +/* + * Based on public domain SipHash reference C implementation + * + * Written in 2012 by + * Jean-Philippe Aumasson + * Daniel J. Bernstein + * + */ + +#define ROTL(x,b) (uint64_t) (((x) << (b)) | ((x) >> (64 - (b)))) + +#define U32TO8_LE(p, v) \ + (p)[0] = (uint8_t) ((v)); \ + (p)[1] = (uint8_t) ((v) >> 8); \ + (p)[2] = (uint8_t) ((v) >> 16); \ + (p)[3] = (uint8_t) ((v) >> 24); + +#define U64TO8_LE(p, v) \ + U32TO8_LE((p), (uint32_t) ((v))); \ + U32TO8_LE((p) + 4, (uint32_t) ((v) >> 32)); + +#define U8TO64_LE(p) \ + (((uint64_t) ((p)[0])) | \ + ((uint64_t) ((p)[1]) << 8) | \ + ((uint64_t) ((p)[2]) << 16) | \ + ((uint64_t) ((p)[3]) << 24) | \ + ((uint64_t) ((p)[4]) << 32) | \ + ((uint64_t) ((p)[5]) << 40) | \ + ((uint64_t) ((p)[6]) << 48) | \ + ((uint64_t) ((p)[7]) << 56)) + +#define SIPROUND \ + do { \ + v0 += v1; v1=ROTL(v1, 13); \ + v1 ^= v0; v0=ROTL(v0, 32); \ + v2 += v3; v3=ROTL(v3, 16); \ + v3 ^= v2; \ + v0 += v3; v3=ROTL(v3, 21); \ + v3 ^= v0; \ + v2 += v1; v1=ROTL(v1, 17); \ + v1 ^= v2; v2=ROTL(v2, 32); \ + } while(0) + +void _siphash24(uint8_t out[8], const uint8_t *in, size_t inlen, + const uint8_t k[16]) +{ + /* "somepseudorandomlygeneratedbytes" */ + uint64_t v0 = 0x736f6d6570736575ULL; + uint64_t v1 = 0x646f72616e646f6dULL; + uint64_t v2 = 0x6c7967656e657261ULL; + uint64_t v3 = 0x7465646279746573ULL; + uint64_t b; + uint64_t k0 = U8TO64_LE(k); + uint64_t k1 = U8TO64_LE(k + 8); + uint64_t m; + const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); + const int left = inlen & 7; + + b = ((uint64_t) inlen) << 56; + v3 ^= k1; + v2 ^= k0; + v1 ^= k1; + v0 ^= k0; + + for (; in != end; in += 8) { + m = U8TO64_LE(in); + v3 ^= m; + SIPROUND; + SIPROUND; + v0 ^= m; + } + + switch (left) { + case 7: + b |= ((uint64_t) in[6]) << 48; + /* fall through */ + case 6: + b |= ((uint64_t) in[5]) << 40; + /* fall through */ + case 5: + b |= ((uint64_t) in[4]) << 32; + /* fall through */ + case 4: + b |= ((uint64_t) in[3]) << 24; + /* fall through */ + case 3: + b |= ((uint64_t) in[2]) << 16; + /* fall through */ + case 2: + b |= ((uint64_t) in[1]) << 8; + /* fall through */ + case 1: + b |= ((uint64_t) in[0]); + break; + case 0: + break; + } + + v3 ^= b; + SIPROUND; + SIPROUND; + v0 ^= b; + v2 ^= 0xff; + SIPROUND; + SIPROUND; + SIPROUND; + SIPROUND; + b = v0 ^ v1 ^ v2 ^ v3; + U64TO8_LE(out, b); +} diff --git a/ell/string.c b/ell/string.c new file mode 100644 index 0000000..37806e3 --- /dev/null +++ b/ell/string.c @@ -0,0 +1,518 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#include + +#include "util.h" +#include "strv.h" +#include "string.h" +#include "private.h" + +/** + * SECTION:string + * @short_description: Growable string buffer + * + * Growable string buffer support + */ + +/** + * l_string: + * + * Opague object representing the string buffer. + */ +struct l_string { + size_t max; + size_t len; + char *str; +}; + +static inline size_t next_power(size_t len) +{ + size_t n = 1; + + if (len > SIZE_MAX / 2) + return SIZE_MAX; + + while (n < len) + n = n << 1; + + return n; +} + +static void grow_string(struct l_string *str, size_t extra) +{ + if (str->len + extra < str->max) + return; + + str->max = next_power(str->len + extra + 1); + str->str = l_realloc(str->str, str->max); +} + +/** + * l_string_new: + * @initial_length: Initial length of the groable string + * + * Create new growable string. If the @initial_length is 0, then a safe + * default is chosen. + * + * Returns: a newly allocated #l_string object. + **/ +LIB_EXPORT struct l_string *l_string_new(size_t initial_length) +{ + static const size_t DEFAULT_INITIAL_LENGTH = 127; + struct l_string *ret; + + ret = l_new(struct l_string, 1); + + if (initial_length == 0) + initial_length = DEFAULT_INITIAL_LENGTH; + + grow_string(ret, initial_length); + ret->str[0] = '\0'; + + return ret; +} + +/** + * l_string_free: + * @string: growable string object + * + * Free the growable string object and all associated data + **/ +LIB_EXPORT void l_string_free(struct l_string *string) +{ + if (unlikely(!string)) + return; + + l_free(string->str); + l_free(string); +} + +/** + * l_string_unwrap: + * @string: growable string object + * + * Free the growable string object and return the internal string data. + * The caller is responsible for freeing the string data using l_free(), + * and the string object is no longer usable. + * + * Returns: @string's internal buffer + **/ +LIB_EXPORT char *l_string_unwrap(struct l_string *string) +{ + char *result; + + if (unlikely(!string)) + return NULL; + + result = string->str; + + l_free(string); + + return result; +} + +/** + * l_string_append: + * @dest: growable string object + * @src: C-style string to copy + * + * Appends the contents of @src to @dest. The internal buffer of @dest is + * grown if necessary. + * + * Returns: @dest + **/ +LIB_EXPORT struct l_string *l_string_append(struct l_string *dest, + const char *src) +{ + size_t size; + + if (unlikely(!dest || !src)) + return NULL; + + size = strlen(src); + + grow_string(dest, size); + + memcpy(dest->str + dest->len, src, size); + dest->len += size; + dest->str[dest->len] = '\0'; + + return dest; +} + +/** + * l_string_append_c: + * @dest: growable string object + * @c: Character + * + * Appends character given by @c to @dest. The internal buffer of @dest is + * grown if necessary. + * + * Returns: @dest + **/ +LIB_EXPORT struct l_string *l_string_append_c(struct l_string *dest, + const char c) +{ + if (unlikely(!dest)) + return NULL; + + grow_string(dest, 1); + dest->str[dest->len++] = c; + dest->str[dest->len] = '\0'; + + return dest; +} + +/** + * l_string_append_fixed: + * @dest: growable string object + * @src: Character array to copy from + * @max: Maximum number of characters to copy + * + * Appends the contents of a fixed size string array @src to @dest. + * The internal buffer of @dest is grown if necessary. Up to a maximum of + * @max characters are copied. If a null is encountered in the first @max + * characters, the string is copied only up to the NULL character. + * + * Returns: @dest + **/ +LIB_EXPORT struct l_string *l_string_append_fixed(struct l_string *dest, + const char *src, + size_t max) +{ + const char *nul; + + if (unlikely(!dest || !src || !max)) + return NULL; + + nul = memchr(src, 0, max); + if (nul) + max = nul - src; + + grow_string(dest, max); + + memcpy(dest->str + dest->len, src, max); + dest->len += max; + dest->str[dest->len] = '\0'; + + return dest; +} + +/** + * l_string_append_vprintf: + * @dest: growable string object + * @format: the string format. See the sprintf() documentation + * @args: the parameters to insert + * + * Appends a formatted string to the growable string buffer. This function + * is equivalent to l_string_append_printf except that the arguments are + * passed as a va_list. + **/ +LIB_EXPORT void l_string_append_vprintf(struct l_string *dest, + const char *format, va_list args) +{ + size_t len; + size_t have_space; + va_list args_copy; + + if (unlikely(!dest)) + return; + +#if __STDC_VERSION__ > 199409L + va_copy(args_copy, args); +#else + __va_copy(args_copy, args); +#endif + + have_space = dest->max - dest->len; + len = vsnprintf(dest->str + dest->len, have_space, format, args); + + if (len >= have_space) { + grow_string(dest, len); + len = vsprintf(dest->str + dest->len, format, args_copy); + } + + dest->len += len; + + va_end(args_copy); +} + +/** + * l_string_append_printf: + * @dest: growable string object + * @format: the string format. See the sprintf() documentation + * @...: the parameters to insert + * + * Appends a formatted string to the growable string buffer, growing it as + * necessary. + **/ +LIB_EXPORT void l_string_append_printf(struct l_string *dest, + const char *format, ...) +{ + va_list args; + + if (unlikely(!dest)) + return; + + va_start(args, format); + l_string_append_vprintf(dest, format, args); + va_end(args); +} + +/** + * l_string_length: + * @string: growable string object + * + * Returns: bytes used in the string. + **/ +LIB_EXPORT unsigned int l_string_length(struct l_string *string) +{ + if (unlikely(!string)) + return 0; + + return string->len; +} + +LIB_EXPORT struct l_string *l_string_truncate(struct l_string *string, + size_t new_size) +{ + if (unlikely(!string)) + return NULL; + + if (new_size >= string->len) + return string; + + string->len = new_size; + string->str[new_size] = '\0'; + + return string; +} + +struct arg { + size_t max_len; + size_t cur_len; + char *chars; +}; + +static inline void arg_init(struct arg *arg) +{ + arg->max_len = 0; + arg->cur_len = 0; + arg->chars = NULL; +} + +static void arg_putchar(struct arg *arg, char ch) +{ + if (arg->cur_len == arg->max_len) { + arg->max_len += 32; /* Grow by at least 32 bytes */ + arg->chars = l_realloc(arg->chars, 1 + arg->max_len); + } + + arg->chars[arg->cur_len++] = ch; + arg->chars[arg->cur_len] = '\0'; +} + +static void arg_putmem(struct arg *arg, const void *mem, size_t len) +{ + if (len == 0) + return; + + if (arg->cur_len + len > arg->max_len) { + size_t growby = len * 2; + + if (growby < 32) + growby = 32; + + arg->max_len += growby; + arg->chars = l_realloc(arg->chars, 1 + arg->max_len); + } + + memcpy(arg->chars + arg->cur_len, mem, len); + arg->cur_len += len; + arg->chars[arg->cur_len] = '\0'; +} + +static bool parse_backslash(struct arg *arg, const char *args, size_t *pos) +{ + /* We're at the backslash, not within double quotes */ + char c = args[*pos + 1]; + + switch (c) { + case 0: + return false; + case '\n': + break; + default: + arg_putchar(arg, c); + break; + } + + *pos += 1; + return true; +} + +static bool parse_quoted_backslash(struct arg *arg, + const char *args, size_t *pos) +{ + /* We're at the backslash, within double quotes */ + char c = args[*pos + 1]; + + switch (c) { + case 0: + return false; + case '\n': + break; + case '"': + case '\\': + arg_putchar(arg, c); + break; + default: + arg_putchar(arg, '\\'); + arg_putchar(arg, c); + break; + } + + *pos += 1; + return true; +} + +static bool parse_single_quote(struct arg *arg, const char *args, size_t *pos) +{ + /* We're just past the single quote */ + size_t start = *pos; + + for (; args[*pos]; *pos += 1) { + if (args[*pos] != '\'') + continue; + + arg_putmem(arg, args + start, *pos - start); + return true; + } + + /* Unterminated ' */ + return false; +} + +static bool parse_double_quote(struct arg *arg, const char *args, size_t *pos) +{ + /* We're just past the double quote */ + for (; args[*pos]; *pos += 1) { + char c = args[*pos]; + + switch (c) { + case '"': + return true; + case '\\': + if (!parse_quoted_backslash(arg, args, pos)) + return false; + + break; + default: + arg_putchar(arg, c); + break; + } + } + + /* Unterminated */ + return false; +} + +static void add_arg(char ***args, char *arg, int *n_args) +{ + *args = l_realloc(*args, sizeof(char *) * (2 + *n_args)); + (*args)[*n_args] = arg; + (*args)[*n_args + 1] = NULL; + + *n_args += 1; +} + +LIB_EXPORT char **l_parse_args(const char *args, int *out_n_args) +{ + size_t i; + struct arg arg; + char **ret = l_realloc(NULL, sizeof(char *)); + int n_args = 0; + + ret[0] = NULL; + arg_init(&arg); + + for (i = 0; args[i]; i++) { + switch (args[i]) { + case '\\': + if (!parse_backslash(&arg, args, &i)) + goto error; + break; + case '"': + i += 1; + if (!parse_double_quote(&arg, args, &i)) + goto error; + + /* Add an empty string */ + if (!arg.cur_len) + add_arg(&ret, l_strdup(""), &n_args); + + break; + case '\'': + i += 1; + if (!parse_single_quote(&arg, args, &i)) + goto error; + + /* Add an empty string */ + if (!arg.cur_len) + add_arg(&ret, l_strdup(""), &n_args); + + break; + default: + if (!strchr(" \t", args[i])) { + if (args[i] == '\n') + goto error; + + arg_putchar(&arg, args[i]); + continue; + } + + if (arg.cur_len) + add_arg(&ret, arg.chars, &n_args); + + arg_init(&arg); + break; + } + } + + if (arg.cur_len) + add_arg(&ret, arg.chars, &n_args); + + if (out_n_args) + *out_n_args = n_args; + + return ret; + +error: + l_free(arg.chars); + l_strfreev(ret); + return NULL; +} diff --git a/ell/string.h b/ell/string.h new file mode 100644 index 0000000..c1948ec --- /dev/null +++ b/ell/string.h @@ -0,0 +1,58 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_STRING_H +#define __ELL_STRING_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct l_string; + +struct l_string *l_string_new(size_t initial_length); +void l_string_free(struct l_string *string); +char *l_string_unwrap(struct l_string *string); + +struct l_string *l_string_append(struct l_string *dest, const char *src); +struct l_string *l_string_append_c(struct l_string *dest, const char c); +struct l_string *l_string_append_fixed(struct l_string *dest, const char *src, + size_t max); + +void l_string_append_vprintf(struct l_string *dest, + const char *format, va_list args); +void l_string_append_printf(struct l_string *dest, const char *format, ...) + __attribute__((format(printf, 2, 3))); + +struct l_string *l_string_truncate(struct l_string *string, size_t new_size); + +unsigned int l_string_length(struct l_string *string); + +char **l_parse_args(const char *args, int *out_n_args); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_STRING_H */ diff --git a/ell/strv.c b/ell/strv.c new file mode 100644 index 0000000..7c7dbf5 --- /dev/null +++ b/ell/strv.c @@ -0,0 +1,361 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include + +#include "util.h" +#include "strv.h" +#include "private.h" + +/** + * SECTION:strv + * @short_description: String array functions + * + * String array functions + */ + +/** + * l_strfreev: + * @strlist: String list to free + * + * Frees a list of strings + **/ +LIB_EXPORT void l_strfreev(char **strlist) +{ + l_strv_free(strlist); +} + +/** + * l_strsplit: + * @str: String to split + * @sep: The delimiter character + * + * Splits a string into pieces which do not contain the delimiter character. + * As a special case, an empty string is returned as an empty array, e.g. + * an array with just the NULL element. + * + * Note that this function only works with ASCII delimiters. + * + * Returns: A newly allocated %NULL terminated string array. This array + * should be freed using l_strfreev(). + **/ +LIB_EXPORT char **l_strsplit(const char *str, const char sep) +{ + int len; + int i; + const char *p; + char **ret; + + if (unlikely(!str)) + return NULL; + + if (str[0] == '\0') + return l_new(char *, 1); + + for (p = str, len = 1; *p; p++) + if (*p == sep) + len += 1; + + ret = l_new(char *, len + 1); + + i = 0; + p = str; + len = 0; + + while (p[len]) { + if (p[len] != sep) { + len += 1; + continue; + } + + ret[i++] = l_strndup(p, len); + p += len + 1; + len = 0; + } + + ret[i++] = l_strndup(p, len); + + return ret; +} + +/** + * l_strsplit_set: + * @str: String to split + * @separators: A set of delimiters + * + * Splits a string into pieces which do not contain the delimiter characters + * that can be found in @separators. + * As a special case, an empty string is returned as an empty array, e.g. + * an array with just the NULL element. + * + * Note that this function only works with ASCII delimiters. + * + * Returns: A newly allocated %NULL terminated string array. This array + * should be freed using l_strfreev(). + **/ +LIB_EXPORT char **l_strsplit_set(const char *str, const char *separators) +{ + int len; + int i; + const char *p; + char **ret; + bool sep_table[256]; + + if (unlikely(!str)) + return NULL; + + if (str[0] == '\0') + return l_new(char *, 1); + + memset(sep_table, 0, sizeof(sep_table)); + + for (p = separators; *p; p++) + sep_table[(unsigned char) *p] = true; + + for (p = str, len = 1; *p; p++) + if (sep_table[(unsigned char) *p] == true) + len += 1; + + ret = l_new(char *, len + 1); + + i = 0; + p = str; + len = 0; + + while (p[len]) { + if (sep_table[(unsigned char) p[len]] != true) { + len += 1; + continue; + } + + ret[i++] = l_strndup(p, len); + p += len + 1; + len = 0; + } + + ret[i++] = l_strndup(p, len); + + return ret; +} + +/** + * l_strjoinv: + * @str_array: a %NULL terminated array of strings to join + * @delim: Delimiting character + * + * Joins strings contanied in the @str_array into one long string delimited + * by @delim. + * + * Returns: A newly allocated string that should be freed using l_free() + */ +LIB_EXPORT char *l_strjoinv(char **str_array, const char delim) +{ + size_t len = 0; + unsigned int i; + char *ret; + char *p; + + if (unlikely(!str_array)) + return NULL; + + if (!str_array[0]) + return l_strdup(""); + + for (i = 0; str_array[i]; i++) + len += strlen(str_array[i]); + + len += 1 + i - 1; + + ret = l_malloc(len); + + p = stpcpy(ret, str_array[0]); + + for (i = 1; str_array[i]; i++) { + *p++ = delim; + p = stpcpy(p, str_array[i]); + } + + return ret; +} + +/** + * l_strv_new: + * + * Returns: new emptry string array + **/ +LIB_EXPORT char **l_strv_new(void) +{ + return l_new(char *, 1); +} + +/** + * l_strv_free: + * @str_array: a %NULL terminated array of strings + * + * Frees strings in @str_array and @str_array itself + **/ +LIB_EXPORT void l_strv_free(char **str_array) +{ + if (likely(str_array)) { + int i; + + for (i = 0; str_array[i]; i++) + l_free(str_array[i]); + + l_free(str_array); + } +} + +/** + * l_strv_length: + * @str_array: a %NULL terminated array of strings + * + * Returns: the number of strings in @str_array + */ +LIB_EXPORT unsigned int l_strv_length(char **str_array) +{ + unsigned int i = 0; + + if (unlikely(!str_array)) + return 0; + + while (str_array[i]) + i += 1; + + return i; +} + +/** + * l_strv_contains: + * @str_array: a %NULL terminated array of strings + * @item: An item to search for, must be not %NULL + * + * Returns: #true if @str_array contains item + */ +LIB_EXPORT bool l_strv_contains(char **str_array, const char *item) +{ + unsigned int i = 0; + + if (unlikely(!str_array || !item)) + return false; + + while (str_array[i]) { + if (!strcmp(str_array[i], item)) + return true; + + i += 1; + } + + return false; +} + +/** + * l_strv_append: + * @str_array: a %NULL terminated array of strings or %NULL + * @str: A string to be appened at the end of @str_array + * + * Returns: New %NULL terminated array of strings with @str added + */ +LIB_EXPORT char **l_strv_append(char **str_array, const char *str) +{ + char **ret; + unsigned int i, len; + + if (unlikely(!str)) + return str_array; + + len = l_strv_length(str_array); + ret = l_new(char *, len + 2); + + for (i = 0; i < len; i++) + ret[i] = str_array[i]; + + ret[i] = l_strdup(str); + + l_free(str_array); + + return ret; +} + +LIB_EXPORT char **l_strv_append_printf(char **str_array, + const char *format, ...) +{ + va_list args; + char **ret; + + va_start(args, format); + ret = l_strv_append_vprintf(str_array, format, args); + va_end(args); + + return ret; +} + +LIB_EXPORT char **l_strv_append_vprintf(char **str_array, + const char *format, va_list args) +{ + char **ret; + unsigned int i, len; + + if (unlikely(!format)) + return str_array; + + len = l_strv_length(str_array); + ret = l_new(char *, len + 2); + + for (i = 0; i < len; i++) + ret[i] = str_array[i]; + + ret[i] = l_strdup_vprintf(format, args); + + l_free(str_array); + + return ret; +} + +/** + * l_strv_copy: + * @str_array: a %NULL terminated array of strings or %NULL + * + * Returns: An independent copy of @str_array. + */ +LIB_EXPORT char **l_strv_copy(char **str_array) +{ + int i, len; + char **copy; + + if (unlikely(!str_array)) + return NULL; + + for (len = 0; str_array[len]; len++); + + copy = l_malloc(sizeof(char *) * (len + 1)); + + for (i = len; i >= 0; i--) + copy[i] = l_strdup(str_array[i]); + + return copy; +} diff --git a/ell/strv.h b/ell/strv.h new file mode 100644 index 0000000..b0ec17c --- /dev/null +++ b/ell/strv.h @@ -0,0 +1,50 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_STRV_H +#define __ELL_STRV_H + +#ifdef __cplusplus +extern "C" { +#endif + +void l_strfreev(char **strlist); +char **l_strsplit(const char *str, const char sep); +char **l_strsplit_set(const char *str, const char *separators); +char *l_strjoinv(char **str_array, const char delim); + +char **l_strv_new(void); +void l_strv_free(char **str_array); +unsigned int l_strv_length(char **str_array); +bool l_strv_contains(char **str_array, const char *item); +char **l_strv_append(char **str_array, const char *str); +char **l_strv_append_printf(char **str_array, const char *format, ...) + __attribute__((format(printf, 2, 3))); +char **l_strv_append_vprintf(char **str_array, const char *format, + va_list args); +char **l_strv_copy(char **str_array); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_STRV_H */ diff --git a/ell/timeout.c b/ell/timeout.c new file mode 100644 index 0000000..1f2cddb --- /dev/null +++ b/ell/timeout.c @@ -0,0 +1,313 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "timeout.h" +#include "private.h" + +/** + * SECTION:timeout + * @short_description: Timeout support + * + * Timeout support + */ + +/** + * l_timeout: + * + * Opague object representing the timeout. + */ +struct l_timeout { + int fd; + l_timeout_notify_cb_t callback; + l_timeout_destroy_cb_t destroy; + void *user_data; +}; + +static void timeout_destroy(void *user_data) +{ + struct l_timeout *timeout = user_data; + + close(timeout->fd); + timeout->fd = -1; + + if (timeout->destroy) + timeout->destroy(timeout->user_data); +} + +static void timeout_callback(int fd, uint32_t events, void *user_data) +{ + struct l_timeout *timeout = user_data; + uint64_t expired; + ssize_t result; + + result = read(timeout->fd, &expired, sizeof(expired)); + if (result != sizeof(expired)) + return; + + if (timeout->callback) + timeout->callback(timeout, timeout->user_data); +} + +static inline int timeout_set(int fd, unsigned int seconds, long nanoseconds) +{ + struct itimerspec itimer; + + memset(&itimer, 0, sizeof(itimer)); + itimer.it_interval.tv_sec = 0; + itimer.it_interval.tv_nsec = 0; + itimer.it_value.tv_sec = seconds; + itimer.it_value.tv_nsec = nanoseconds; + + return timerfd_settime(fd, 0, &itimer, NULL); +} + +static bool convert_ms(unsigned long milliseconds, unsigned int *seconds, + long *nanoseconds) +{ + unsigned long big_seconds = milliseconds / 1000; + + if (big_seconds > UINT_MAX) + return false; + + *seconds = big_seconds; + *nanoseconds = (milliseconds % 1000) * 1000000L; + + return true; +} + +/** + * timeout_create_with_nanoseconds: + * @seconds: number of seconds + * @nanoseconds: number of nanoseconds + * @callback: timeout callback function + * @user_data: user data provided to timeout callback function + * @destroy: destroy function for user data + * + * Create new timeout callback handling. + * + * The timeout will only fire once. The timeout handling needs to be rearmed + * with one of the l_timeout_modify functions to trigger again. + * + * Returns: a newly allocated #l_timeout object. On failure, the function + * returns NULL. + **/ +static struct l_timeout *timeout_create_with_nanoseconds(unsigned int seconds, + long nanoseconds, l_timeout_notify_cb_t callback, + void *user_data, l_timeout_destroy_cb_t destroy) +{ + struct l_timeout *timeout; + int err; + + if (unlikely(!callback)) + return NULL; + + timeout = l_new(struct l_timeout, 1); + + timeout->callback = callback; + timeout->destroy = destroy; + timeout->user_data = user_data; + + timeout->fd = timerfd_create(CLOCK_MONOTONIC, + TFD_NONBLOCK | TFD_CLOEXEC); + if (timeout->fd < 0) { + l_free(timeout); + return NULL; + } + + if (seconds > 0 || nanoseconds > 0) { + if (timeout_set(timeout->fd, seconds, nanoseconds) < 0) { + close(timeout->fd); + l_free(timeout); + return NULL; + } + } + + err = watch_add(timeout->fd, EPOLLIN | EPOLLONESHOT, timeout_callback, + timeout, timeout_destroy); + + if (err < 0) { + l_free(timeout); + return NULL; + } + + return timeout; +} + +/** + * l_timeout_create: + * @seconds: timeout in seconds + * @callback: timeout callback function + * @user_data: user data provided to timeout callback function + * @destroy: destroy function for user data + * + * Create new timeout callback handling. + * + * The timeout will only fire once. The timeout handling needs to be rearmed + * with one of the l_timeout_modify functions to trigger again. + * + * Returns: a newly allocated #l_timeout object. On failure, the function + * returns NULL. + **/ +LIB_EXPORT struct l_timeout *l_timeout_create(unsigned int seconds, + l_timeout_notify_cb_t callback, + void *user_data, l_timeout_destroy_cb_t destroy) +{ + return timeout_create_with_nanoseconds(seconds, 0, callback, + user_data, destroy); +} + +/** + * l_timeout_create_ms: + * @milliseconds: timeout in milliseconds + * @callback: timeout callback function + * @user_data: user data provided to timeout callback function + * @destroy: destroy function for user data + * + * Create new timeout callback handling. + * + * The timeout will only fire once. The timeout handling needs to be rearmed + * with one of the l_timeout_modify functions to trigger again. + * + * Returns: a newly allocated #l_timeout object. On failure, the function + * returns NULL. + **/ +LIB_EXPORT struct l_timeout *l_timeout_create_ms(unsigned long milliseconds, + l_timeout_notify_cb_t callback, + void *user_data, l_timeout_destroy_cb_t destroy) +{ + unsigned int seconds; + long nanoseconds; + + if (!convert_ms(milliseconds, &seconds, &nanoseconds)) + return NULL; + + return timeout_create_with_nanoseconds(seconds, nanoseconds, callback, + user_data, destroy); +} + +/** + * l_timeout_modify: + * @timeout: timeout object + * @seconds: timeout in seconds + * + * Modify an existing @timeout and rearm it. + **/ +LIB_EXPORT void l_timeout_modify(struct l_timeout *timeout, + unsigned int seconds) +{ + if (unlikely(!timeout)) + return; + + if (unlikely(timeout->fd < 0)) + return; + + if (seconds > 0) { + if (timeout_set(timeout->fd, seconds, 0) < 0) + return; + } + + watch_modify(timeout->fd, EPOLLIN | EPOLLONESHOT, true); +} + +/** + * l_timeout_modify_ms: + * @timeout: timeout object + * @milliseconds: number of milliseconds + * + * Modify an existing @timeout and rearm it. + **/ +LIB_EXPORT void l_timeout_modify_ms(struct l_timeout *timeout, + unsigned long milliseconds) +{ + if (unlikely(!timeout)) + return; + + if (unlikely(timeout->fd < 0)) + return; + + if (milliseconds > 0) { + unsigned int sec; + long nanosec; + + if (!convert_ms(milliseconds, &sec, &nanosec) || + timeout_set(timeout->fd, sec, nanosec) < 0) + return; + } + + watch_modify(timeout->fd, EPOLLIN | EPOLLONESHOT, true); +} + +/** + * l_timeout_remove: + * @timeout: timeout object + * + * Remove timeout handling. + **/ +LIB_EXPORT void l_timeout_remove(struct l_timeout *timeout) +{ + if (unlikely(!timeout)) + return; + + watch_remove(timeout->fd); + + l_free(timeout); +} + +/** + * l_timeout_set_callback: + * @timeout: timeout object + * @callback: The new callback + * @user_data: The new user_data + * @destroy: The new destroy function + * + * Sets the new notify callback for @timeout. If the old user_data object had + * a destroy function set, then that function will be called. + */ +LIB_EXPORT void l_timeout_set_callback(struct l_timeout *timeout, + l_timeout_notify_cb_t callback, + void *user_data, + l_timeout_destroy_cb_t destroy) +{ + if (unlikely(!timeout)) + return; + + if (timeout->destroy) + timeout->destroy(timeout->user_data); + + timeout->callback = callback; + timeout->user_data = user_data; + timeout->destroy = destroy; +} diff --git a/ell/timeout.h b/ell/timeout.h new file mode 100644 index 0000000..889a287 --- /dev/null +++ b/ell/timeout.h @@ -0,0 +1,55 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_TIMEOUT_H +#define __ELL_TIMEOUT_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct l_timeout; + +typedef void (*l_timeout_notify_cb_t) (struct l_timeout *timeout, + void *user_data); +typedef void (*l_timeout_destroy_cb_t) (void *user_data); + +struct l_timeout *l_timeout_create(unsigned int seconds, + l_timeout_notify_cb_t callback, + void *user_data, l_timeout_destroy_cb_t destroy); +struct l_timeout *l_timeout_create_ms(unsigned long milliseconds, + l_timeout_notify_cb_t callback, + void *user_data, l_timeout_destroy_cb_t destroy); +void l_timeout_modify(struct l_timeout *timeout, + unsigned int seconds); +void l_timeout_modify_ms(struct l_timeout *timeout, + unsigned long milliseconds); +void l_timeout_remove(struct l_timeout *timeout); +void l_timeout_set_callback(struct l_timeout *timeout, + l_timeout_notify_cb_t callback, void *user_data, + l_timeout_destroy_cb_t destroy); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_TIMEOUT_H */ diff --git a/ell/utf8.c b/ell/utf8.c new file mode 100644 index 0000000..44601dc --- /dev/null +++ b/ell/utf8.c @@ -0,0 +1,497 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#include +#include + +#include "util.h" +#include "strv.h" +#include "utf8.h" +#include "private.h" + +/** + * SECTION:utf8 + * @short_description: UTF-8 utility function + * + * UTF-8 string handling support + */ + +LIB_EXPORT unsigned char l_ascii_table[256] = { + [0x00 ... 0x08] = L_ASCII_CNTRL, + [0x09 ... 0x0D] = L_ASCII_CNTRL | L_ASCII_SPACE, + [0x0E ... 0x1F] = L_ASCII_CNTRL, + [0x20] = L_ASCII_PRINT | L_ASCII_SPACE, + [0x21 ... 0x2F] = L_ASCII_PRINT | L_ASCII_PUNCT, + [0x30 ... 0x39] = L_ASCII_DIGIT | L_ASCII_XDIGIT | L_ASCII_PRINT, + [0x3A ... 0x40] = L_ASCII_PRINT | L_ASCII_PUNCT, + [0x41 ... 0x46] = L_ASCII_PRINT | L_ASCII_XDIGIT | L_ASCII_UPPER, + [0x47 ... 0x5A] = L_ASCII_PRINT | L_ASCII_UPPER, + [0x5B ... 0x60] = L_ASCII_PRINT | L_ASCII_PUNCT, + [0x61 ... 0x66] = L_ASCII_PRINT | L_ASCII_XDIGIT | L_ASCII_LOWER, + [0x67 ... 0x7A] = L_ASCII_PRINT | L_ASCII_LOWER, + [0x7B ... 0x7E] = L_ASCII_PRINT | L_ASCII_PUNCT, + [0x7F] = L_ASCII_CNTRL, + [0x80 ... 0xFF] = 0, +}; + +static inline bool __attribute__ ((always_inline)) + valid_unicode(wchar_t c) +{ + if (c <= 0xd7ff) + return true; + + if (c < 0xe000 || c > 0x10ffff) + return false; + + if (c >= 0xfdd0 && c <= 0xfdef) + return false; + + if ((c & 0xfffe) == 0xfffe) + return false; + + return true; +} + +/** + * l_utf8_get_codepoint + * @str: a pointer to codepoint data + * @len: maximum bytes to read + * @cp: destination for codepoint + * + * Returns: number of bytes read, or -1 for invalid coddepoint + **/ +LIB_EXPORT int l_utf8_get_codepoint(const char *str, size_t len, wchar_t *cp) +{ + static const wchar_t mins[3] = { 1 << 7, 1 << 11, 1 << 16 }; + unsigned int expect_bytes; + wchar_t val; + size_t i; + + if (len == 0) + return 0; + + if ((signed char) str[0] > 0) { + *cp = str[0]; + return 1; + } + + expect_bytes = __builtin_clz(~((unsigned char)str[0] << 24)); + + if (expect_bytes < 2 || expect_bytes > 4) + goto error; + + if (expect_bytes > len) + goto error; + + val = str[0] & (0xff >> (expect_bytes + 1)); + + for (i = 1; i < expect_bytes; i++) { + if ((str[i] & 0xc0) != 0x80) + goto error; + + val <<= 6; + val |= str[i] & 0x3f; + } + + if (val < mins[expect_bytes - 2]) + goto error; + + if (valid_unicode(val) == false) + goto error; + + *cp = val; + return expect_bytes; + +error: + return -1; +} + +/** + * l_utf8_validate: + * @str: a pointer to character data + * @len: max bytes to validate + * @end: return location for end of valid data + * + * Validates UTF-8 encoded text. If @end is non-NULL, then the end of + * the valid range will be stored there (i.e. the start of the first + * invalid character if some bytes were invalid, or the end of the text + * being validated otherwise). + * + * Returns: Whether the text was valid UTF-8 + **/ +LIB_EXPORT bool l_utf8_validate(const char *str, size_t len, const char **end) +{ + size_t pos = 0; + int ret; + wchar_t val; + + while (pos < len && str[pos]) { + ret = l_utf8_get_codepoint(str + pos, len - pos, &val); + + if (ret < 0) + goto error; + + pos += ret; + } + +error: + if (end) + *end = str + pos; + + if (pos != len) + return false; + + return true; +} + +/** + * l_utf8_strlen: + * @str: a pointer to character data + * + * Computes the number of UTF-8 characters (not bytes) in the string given + * by @str. + * + * Returns: The number of UTF-8 characters in the string + **/ +LIB_EXPORT size_t l_utf8_strlen(const char *str) +{ + size_t l = 0; + size_t i; + unsigned char b; + + for (i = 0; str[i]; i++) { + b = str[i]; + + if ((b >> 6) == 2) + l += 1; + } + + return i - l; +} + +static inline int __attribute__ ((always_inline)) + utf8_length(wchar_t c) +{ + if (c <= 0x7f) + return 1; + + if (c <= 0x7ff) + return 2; + + if (c <= 0xffff) + return 3; + + return 4; +} + +static inline uint16_t __attribute__ ((always_inline)) + surrogate_value(uint16_t h, uint16_t l) +{ + return 0x10000 + (h - 0xd800) * 0x400 + l - 0xdc00; +} + +/* + * l_utf8_from_wchar: + * @c: a wide-character to convert + * @out_buf: Buffer to write out to + * + * Assumes c is valid unicode and out_buf contains enough space for a single + * utf8 character (maximum 4 bytes) + * Returns: number of characters written + */ +LIB_EXPORT size_t l_utf8_from_wchar(wchar_t c, char *out_buf) +{ + int len = utf8_length(c); + int i; + + if (len == 1) { + out_buf[0] = c; + return 1; + } + + for (i = len - 1; i; i--) { + out_buf[i] = (c & 0x3f) | 0x80; + c >>= 6; + } + + out_buf[0] = (0xff << (8 - len)) | c; + return len; +} + +/** + * l_utf8_from_utf16: + * @utf16: Array of UTF16 characters + * @utf16_size: The size of the @utf16 array in bytes. Must be a multiple of 2. + * + * Returns: A newly-allocated buffer containing UTF16 encoded string converted + * to UTF8. The UTF8 string will always be null terminated, even if the + * original UTF16 string was not. + **/ +LIB_EXPORT char *l_utf8_from_utf16(const void *utf16, ssize_t utf16_size) +{ + char *utf8; + size_t utf8_len = 0; + wchar_t high_surrogate = 0; + ssize_t i = 0; + uint16_t in; + wchar_t c; + + if (unlikely(utf16_size % 2)) + return NULL; + + while (utf16_size < 0 || i < utf16_size) { + in = l_get_u16(utf16 + i); + + if (!in) + break; + + if (in >= 0xdc00 && in < 0xe000) { + if (high_surrogate) + c = surrogate_value(high_surrogate, in); + else + return NULL; + + high_surrogate = 0; + } else { + if (high_surrogate) + return NULL; + + if (in >= 0xd800 && in < 0xdc00) { + high_surrogate = in; + goto next; + } + + c = in; + } + + if (!valid_unicode(c)) + return NULL; + + utf8_len += utf8_length(c); +next: + i += 2; + } + + if (high_surrogate) + return NULL; + + utf8 = l_malloc(utf8_len + 1); + utf8_len = 0; + i = 0; + + while (utf16_size < 0 || i < utf16_size) { + in = l_get_u16(utf16 + i); + + if (!in) + break; + + if (in >= 0xd800 && in < 0xdc00) { + high_surrogate = in; + i += 2; + in = l_get_u16(utf16 + i); + c = surrogate_value(high_surrogate, in); + } else + c = in; + + utf8_len += l_utf8_from_wchar(c, utf8 + utf8_len); + i += 2; + } + + utf8[utf8_len] = '\0'; + + return utf8; +} + +/** + * l_utf8_to_utf16: + * @utf8: UTF8 formatted string + * @out_size: The size in bytes of the converted utf16 string + * + * Converts a UTF8 formatted string to UTF16. It is assumed that the string + * is valid UTF8 and no sanity checking is performed. + * + * Returns: A newly-allocated buffer containing UTF8 encoded string converted + * to UTF16. The UTF16 string will always be null terminated. + **/ +LIB_EXPORT void *l_utf8_to_utf16(const char *utf8, size_t *out_size) +{ + const char *c; + wchar_t wc; + int len; + uint16_t *utf16; + size_t n_utf16; + + if (unlikely(!utf8)) + return NULL; + + c = utf8; + n_utf16 = 0; + + while (*c) { + len = l_utf8_get_codepoint(c, 4, &wc); + if (len < 0) + return NULL; + + if (wc < 0x10000) + n_utf16 += 1; + else + n_utf16 += 2; + + c += len; + } + + utf16 = l_malloc((n_utf16 + 1) * 2); + c = utf8; + n_utf16 = 0; + + while (*c) { + len = l_utf8_get_codepoint(c, 4, &wc); + + if (wc >= 0x10000) { + utf16[n_utf16++] = (wc - 0x1000) / 0x400 + 0xd800; + utf16[n_utf16++] = (wc - 0x1000) % 0x400 + 0xdc00; + } else + utf16[n_utf16++] = wc; + + c += len; + } + + utf16[n_utf16] = 0; + + if (out_size) + *out_size = (n_utf16 + 1) * 2; + + return utf16; +} + +/** + * l_utf8_from_ucs2be: + * @ucs2be: Array of UCS2 characters in big-endian format + * @ucs2be_size: The size of the @ucs2 array in bytes. Must be a multiple of 2. + * + * Returns: A newly-allocated buffer containing UCS2BE encoded string converted + * to UTF8. The UTF8 string will always be null terminated, even if the + * original UCS2BE string was not. + **/ +LIB_EXPORT char *l_utf8_from_ucs2be(const void *ucs2be, ssize_t ucs2be_size) +{ + char *utf8; + size_t utf8_len = 0; + ssize_t i = 0; + uint16_t in; + + if (unlikely(ucs2be_size % 2)) + return NULL; + + while (ucs2be_size < 0 || i < ucs2be_size) { + in = l_get_be16(ucs2be + i); + + if (!in) + break; + + if (in >= 0xd800 && in < 0xe000) + return NULL; + + if (!valid_unicode(in)) + return NULL; + + utf8_len += utf8_length(in); + i += 2; + } + + utf8 = l_malloc(utf8_len + 1); + utf8_len = 0; + i = 0; + + while (ucs2be_size < 0 || i < ucs2be_size) { + in = l_get_be16(ucs2be + i); + + if (!in) + break; + + utf8_len += l_utf8_from_wchar(in, utf8 + utf8_len); + i += 2; + } + + utf8[utf8_len] = '\0'; + + return utf8; +} + +/** + * l_utf8_to_ucs2be: + * @utf8: UTF8 formatted string + * @out_size: The size in bytes of the converted ucs2be string + * + * Converts a UTF8 formatted string to UCS2BE. It is assumed that the string + * is valid UTF8 and no sanity checking is performed. + * + * Returns: A newly-allocated buffer containing UTF8 encoded string converted + * to UCS2BE. The UCS2BE string will always be null terminated. + **/ +LIB_EXPORT void *l_utf8_to_ucs2be(const char *utf8, size_t *out_size) +{ + const char *c; + wchar_t wc; + int len; + uint16_t *ucs2be; + size_t n_ucs2be; + + if (unlikely(!utf8)) + return NULL; + + c = utf8; + n_ucs2be = 0; + + while (*c) { + len = l_utf8_get_codepoint(c, 4, &wc); + if (len < 0) + return NULL; + + if (wc >= 0x10000) + return NULL; + + n_ucs2be += 1; + c += len; + } + + ucs2be = l_malloc((n_ucs2be + 1) * 2); + c = utf8; + n_ucs2be = 0; + + while (*c) { + len = l_utf8_get_codepoint(c, 4, &wc); + ucs2be[n_ucs2be++] = L_CPU_TO_BE16(wc); + c += len; + } + + ucs2be[n_ucs2be] = 0; + + if (out_size) + *out_size = (n_ucs2be + 1) * 2; + + return ucs2be; +} diff --git a/ell/utf8.h b/ell/utf8.h new file mode 100644 index 0000000..c92da60 --- /dev/null +++ b/ell/utf8.h @@ -0,0 +1,120 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_UTF8_H +#define __ELL_UTF8_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern unsigned char l_ascii_table[]; + +enum l_ascii { + L_ASCII_CNTRL = 0x80, + L_ASCII_PRINT = 0x40, + L_ASCII_PUNCT = 0x20, + L_ASCII_SPACE = 0x10, + L_ASCII_XDIGIT = 0x08, + L_ASCII_UPPER = 0x04, + L_ASCII_LOWER = 0x02, + L_ASCII_DIGIT = 0x01, + L_ASCII_ALPHA = L_ASCII_LOWER | L_ASCII_UPPER, + L_ASCII_ALNUM = L_ASCII_ALPHA | L_ASCII_DIGIT, + L_ASCII_GRAPH = L_ASCII_ALNUM | L_ASCII_PUNCT, +}; + +#define l_ascii_isalnum(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_ALNUM) != 0) + +#define l_ascii_isalpha(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_ALPHA) != 0) + +#define l_ascii_iscntrl(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_CNTRL) != 0) + +#define l_ascii_isdigit(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_DIGIT) != 0) + +#define l_ascii_isgraph(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_GRAPH) != 0) + +#define l_ascii_islower(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_LOWER) != 0) + +#define l_ascii_isprint(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_PRINT) != 0) + +#define l_ascii_ispunct(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_PUNCT) != 0) + +#define l_ascii_isspace(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_SPACE) != 0) + +#define l_ascii_isupper(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_UPPER) != 0) + +#define l_ascii_isxdigit(c) \ + ((l_ascii_table[(unsigned char) (c)] & L_ASCII_XDIGIT) != 0) + +#if __STDC_VERSION__ <= 199409L +#define inline __inline__ +#endif + +static inline __attribute__ ((always_inline)) + bool l_ascii_isblank(unsigned char c) +{ + if (c == ' ' || c == '\t') + return true; + + return false; +} + +static inline __attribute__ ((always_inline)) bool l_ascii_isascii(int c) +{ + if (c <= 127) + return true; + + return false; +} + +bool l_utf8_validate(const char *src, size_t len, const char **end); +size_t l_utf8_strlen(const char *src); + +int l_utf8_get_codepoint(const char *str, size_t len, wchar_t *cp); +size_t l_utf8_from_wchar(wchar_t c, char *out_buf); + +char *l_utf8_from_utf16(const void *utf16, ssize_t utf16_size); +void *l_utf8_to_utf16(const char *utf8, size_t *out_size); + +char *l_utf8_from_ucs2be(const void *ucs2be, ssize_t ucs2be_size); +void *l_utf8_to_ucs2be(const char *utf8, size_t *out_size); + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_UTF8_H */ diff --git a/ell/util.c b/ell/util.c new file mode 100644 index 0000000..26cea8a --- /dev/null +++ b/ell/util.c @@ -0,0 +1,636 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "util.h" +#include "private.h" + +/** + * SECTION:util + * @short_description: Utility functions + * + * Utility functions + */ + +#define STRLOC __FILE__ ":" L_STRINGIFY(__LINE__) + +/** + * l_malloc: + * @size: memory size to allocate + * + * If for any reason the memory allocation fails, then execution will be + * halted via abort(). + * + * In case @size is 0 then #NULL will be returned. + * + * Returns: pointer to allocated memory + **/ +LIB_EXPORT void *l_malloc(size_t size) +{ + if (likely(size)) { + void *ptr; + + ptr = malloc(size); + if (ptr) + return ptr; + + fprintf(stderr, "%s:%s(): failed to allocate %zd bytes\n", + STRLOC, __func__, size); + abort(); + } + + return NULL; +} + +/** + * l_realloc: + * @mem: previously allocated memory, or NULL + * @size: memory size to allocate + * + * If for any reason the memory allocation fails, then execution will be + * halted via abort(). + * + * In case @mem is NULL, this function acts like l_malloc. + * In case @size is 0 then #NULL will be returned. + * + * Returns: pointer to allocated memory + **/ +LIB_EXPORT void *l_realloc(void *mem, size_t size) +{ + if (likely(size)) { + void *ptr; + + ptr = realloc(mem, size); + if (ptr) + return ptr; + + fprintf(stderr, "%s:%s(): failed to re-allocate %zd bytes\n", + STRLOC, __func__, size); + abort(); + } else + l_free(mem); + + return NULL; +} + +/** + * l_memdup: + * @mem: pointer to memory you want to duplicate + * @size: memory size + * + * If for any reason the memory allocation fails, then execution will be + * halted via abort(). + * + * In case @size is 0 then #NULL will be returned. + * + * Returns: pointer to duplicated memory buffer + **/ +LIB_EXPORT void *l_memdup(const void *mem, size_t size) +{ + void *ptr; + + ptr = l_malloc(size); + + memcpy(ptr, mem, size); + + return ptr; +} + +/** + * l_free: + * @ptr: memory pointer + * + * Free the allocated memory area. + **/ +LIB_EXPORT void l_free(void *ptr) +{ + free(ptr); +} + +/** + * l_strdup: + * @str: string pointer + * + * Allocates and duplicates sring + * + * Returns: a newly allocated string + **/ +LIB_EXPORT char *l_strdup(const char *str) +{ + if (likely(str)) { + char *tmp; + + tmp = strdup(str); + if (tmp) + return tmp; + + fprintf(stderr, "%s:%s(): failed to allocate string\n", + STRLOC, __func__); + abort(); + } + + return NULL; +} + +/** + * l_strndup: + * @str: string pointer + * @max: Maximum number of characters to copy + * + * Allocates and duplicates sring. If the string is longer than @max + * characters, only @max are copied and a null terminating character + * is added. + * + * Returns: a newly allocated string + **/ +LIB_EXPORT char *l_strndup(const char *str, size_t max) +{ + if (likely(str)) { + char *tmp; + + tmp = strndup(str, max); + if (tmp) + return tmp; + + fprintf(stderr, "%s:%s(): failed to allocate string\n", + STRLOC, __func__); + abort(); + } + + return NULL; +} + +/** + * l_strdup_printf: + * @format: string format + * @...: parameters to insert into format string + * + * Returns: a newly allocated string + **/ +LIB_EXPORT char *l_strdup_printf(const char *format, ...) +{ + va_list args; + char *str; + int len; + + va_start(args, format); + len = vasprintf(&str, format, args); + va_end(args); + + if (len < 0) { + fprintf(stderr, "%s:%s(): failed to allocate string\n", + STRLOC, __func__); + abort(); + + return NULL; + } + + return str; +} + +/** + * l_strdup_vprintf: + * @format: string format + * @args: parameters to insert into format string + * + * Returns: a newly allocated string + **/ +LIB_EXPORT char *l_strdup_vprintf(const char *format, va_list args) +{ + char *str; + int len; + + len = vasprintf(&str, format, args); + + if (len < 0) { + fprintf(stderr, "%s:%s(): failed to allocate string\n", + STRLOC, __func__); + abort(); + + return NULL; + } + + return str; +} + +/** + * l_strlcpy: + * @dst: Destination buffer for string + * @src: Source buffer containing null-terminated string to copy + * @len: Maximum destination buffer space to use + * + * Copies a string from the @src buffer to the @dst buffer, using no + * more than @len bytes in @dst. @dst is guaranteed to be + * null-terminated. The caller can determine if the copy was truncated by + * checking if the return value is greater than or equal to @len. + * + * NOTE: Passing in a NULL string results in a no-op + * + * Returns: The length of the @src string, not including the null + * terminator. + */ +LIB_EXPORT size_t l_strlcpy(char *dst, const char *src, size_t len) +{ + size_t src_len = src ? strlen(src) : 0; + + if (!src) + goto done; + + if (len) { + if (src_len < len) { + len = src_len + 1; + } else { + len -= 1; + dst[len] = '\0'; + } + + memcpy(dst, src, len); + } + +done: + return src_len; +} + +/** + * l_str_has_prefix: + * @str: A string to be examined + * @delim: Prefix string + * + * Determines if the string given by @str is prefixed by string given by + * @prefix. + * + * Returns: True if @str was prefixed by @prefix. False otherwise. + */ +LIB_EXPORT bool l_str_has_prefix(const char *str, const char *prefix) +{ + size_t str_len; + size_t prefix_len; + + if (unlikely(!str)) + return false; + + if (unlikely(!prefix)) + return false; + + str_len = strlen(str); + prefix_len = strlen(prefix); + + if (str_len < prefix_len) + return false; + + return !strncmp(str, prefix, prefix_len); +} + +/** + * l_str_has_suffix: + * @str: A string to be examined + * @suffix: Suffix string + * + * Determines if the string given by @str ends with the specified @suffix. + * + * Returns: True if @str ends with the specified @suffix. False otherwise. + */ +LIB_EXPORT bool l_str_has_suffix(const char *str, const char *suffix) +{ + size_t str_len; + size_t suffix_len; + size_t len_diff; + + if (unlikely(!str)) + return false; + + if (unlikely(!suffix)) + return false; + + str_len = strlen(str); + suffix_len = strlen(suffix); + + if (str_len < suffix_len) + return false; + + len_diff = str_len - suffix_len; + + return !strcmp(&str[len_diff], suffix); +} + +static char *hexstring_common(const unsigned char *buf, size_t len, + const char hexdigits[static 16]) +{ + char *str; + size_t i; + + if (unlikely(!buf) || unlikely(!len)) + return NULL; + + str = l_malloc(len * 2 + 1); + + for (i = 0; i < len; i++) { + str[(i * 2) + 0] = hexdigits[buf[i] >> 4]; + str[(i * 2) + 1] = hexdigits[buf[i] & 0xf]; + } + + str[len * 2] = '\0'; + + return str; +} + +/** + * l_util_hexstring: + * @buf: buffer pointer + * @len: length of buffer + * + * Returns: a newly allocated hex string. Note that the string will contain + * lower case hex digits a-f. If you require upper case hex digits, use + * @l_util_hexstring_upper + **/ +LIB_EXPORT char *l_util_hexstring(const unsigned char *buf, size_t len) +{ + static const char hexdigits[] = "0123456789abcdef"; + return hexstring_common(buf, len, hexdigits); +} + +/** + * l_util_hexstring_upper: + * @buf: buffer pointer + * @len: length of buffer + * + * Returns: a newly allocated hex string. Note that the string will contain + * upper case hex digits a-f. If you require lower case hex digits, use + * @l_util_hexstring + **/ +LIB_EXPORT char *l_util_hexstring_upper(const unsigned char *buf, size_t len) +{ + static const char hexdigits[] = "0123456789ABCDEF"; + return hexstring_common(buf, len, hexdigits); +} + +/** + * l_util_from_hexstring: + * @str: Null-terminated string containing the hex-encoded bytes + * @out_len: Number of bytes decoded + * + * Returns: a newly allocated byte array. Empty strings are treated as + * an error condition. + **/ +LIB_EXPORT unsigned char *l_util_from_hexstring(const char *str, + size_t *out_len) +{ + size_t i, j; + size_t len; + char c; + unsigned char *buf; + + if (unlikely(!str)) + return NULL; + + for (i = 0; str[i]; i++) { + c = toupper(str[i]); + + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) + continue; + + return NULL; + } + + if (!i) + return NULL; + + if ((i % 2) != 0) + return NULL; + + len = i; + buf = l_malloc(i >> 1); + + for (i = 0, j = 0; i < len; i++, j++) { + c = toupper(str[i]); + + if (c >= '0' && c <= '9') + buf[j] = c - '0'; + else if (c >= 'A' && c <= 'F') + buf[j] = 10 + c - 'A'; + + i += 1; + + c = toupper(str[i]); + + if (c >= '0' && c <= '9') + buf[j] = buf[j] * 16 + c - '0'; + else if (c >= 'A' && c <= 'F') + buf[j] = buf[j] * 16 + 10 + c - 'A'; + } + + if (out_len) + *out_len = j; + + return buf; +} + +static void hexdump(const char dir, const unsigned char *buf, size_t len, + l_util_hexdump_func_t function, void *user_data) +{ + static const char hexdigits[] = "0123456789abcdef"; + char str[68]; + size_t i; + + if (unlikely(!len)) + return; + + str[0] = dir; + + for (i = 0; i < len; i++) { + str[((i % 16) * 3) + 1] = ' '; + str[((i % 16) * 3) + 2] = hexdigits[buf[i] >> 4]; + str[((i % 16) * 3) + 3] = hexdigits[buf[i] & 0xf]; + str[(i % 16) + 51] = isprint(buf[i]) ? buf[i] : '.'; + + if ((i + 1) % 16 == 0) { + str[49] = ' '; + str[50] = ' '; + str[67] = '\0'; + function(str, user_data); + str[0] = ' '; + } + } + + if (i % 16 > 0) { + size_t j; + for (j = (i % 16); j < 16; j++) { + str[(j * 3) + 1] = ' '; + str[(j * 3) + 2] = ' '; + str[(j * 3) + 3] = ' '; + str[j + 51] = ' '; + } + str[49] = ' '; + str[50] = ' '; + str[67] = '\0'; + function(str, user_data); + } +} + +LIB_EXPORT void l_util_hexdump(bool in, const void *buf, size_t len, + l_util_hexdump_func_t function, void *user_data) +{ + if (likely(!function)) + return; + + hexdump(in ? '<' : '>', buf, len, function, user_data); +} + +LIB_EXPORT void l_util_hexdump_two(bool in, const void *buf1, size_t len1, + const void *buf2, size_t len2, + l_util_hexdump_func_t function, void *user_data) +{ + if (likely(!function)) + return; + + hexdump(in ? '<' : '>', buf1, len1, function, user_data); + hexdump(' ', buf2, len2, function, user_data); +} + +LIB_EXPORT void l_util_hexdumpv(bool in, const struct iovec *iov, + size_t n_iov, + l_util_hexdump_func_t function, + void *user_data) +{ + static const char hexdigits[] = "0123456789abcdef"; + char str[68]; + size_t i; + size_t len; + size_t c; + const uint8_t *buf; + + if (unlikely(!iov || !n_iov)) + return; + + str[0] = in ? '<' : '>'; + + for (i = 0, len = 0; i < n_iov; i++) + len += iov[i].iov_len; + + c = 0; + buf = iov[0].iov_base; + + for (i = 0; i < len; i++, c++) { + if (c == iov[0].iov_len) { + c = 0; + iov += 1; + buf = iov[0].iov_base; + } + + str[((i % 16) * 3) + 1] = ' '; + str[((i % 16) * 3) + 2] = hexdigits[buf[c] >> 4]; + str[((i % 16) * 3) + 3] = hexdigits[buf[c] & 0xf]; + str[(i % 16) + 51] = isprint(buf[c]) ? buf[c] : '.'; + + if ((i + 1) % 16 == 0) { + str[49] = ' '; + str[50] = ' '; + str[67] = '\0'; + function(str, user_data); + str[0] = ' '; + } + } + + if (i % 16 > 0) { + size_t j; + for (j = (i % 16); j < 16; j++) { + str[(j * 3) + 1] = ' '; + str[(j * 3) + 2] = ' '; + str[(j * 3) + 3] = ' '; + str[j + 51] = ' '; + } + str[49] = ' '; + str[50] = ' '; + str[67] = '\0'; + function(str, user_data); + } +} + +LIB_EXPORT void l_util_debug(l_util_hexdump_func_t function, void *user_data, + const char *format, ...) +{ + va_list args; + char *str; + int len; + + if (likely(!function)) + return; + + if (unlikely(!format)) + return; + + va_start(args, format); + len = vasprintf(&str, format, args); + va_end(args); + + if (unlikely(len < 0)) + return; + + function(str, user_data); + + free(str); +} + +/** + * l_util_get_debugfs_path: + * + * Returns: a pointer to mount point of debugfs + **/ +LIB_EXPORT const char *l_util_get_debugfs_path(void) +{ + static char path[PATH_MAX + 1]; + static bool found = false; + char type[100]; + FILE *fp; + + if (found) + return path; + + fp = fopen("/proc/mounts", "r"); + if (!fp) + return NULL; + + while (fscanf(fp, "%*s %" L_STRINGIFY(PATH_MAX) "s %99s %*s %*d %*d\n", + path, type) == 2) { + if (!strcmp(type, "debugfs")) { + found = true; + break; + } + } + + fclose(fp); + + if (!found) + return NULL; + + return path; +} diff --git a/ell/util.h b/ell/util.h new file mode 100644 index 0000000..4f20ef0 --- /dev/null +++ b/ell/util.h @@ -0,0 +1,318 @@ +/* + * + * Embedded Linux library + * + * Copyright (C) 2011-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 + * + */ + +#ifndef __ELL_UTIL_H +#define __ELL_UTIL_H + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define l_container_of(ptr, type, member) ({ \ + const __typeof__(((type *) 0)->member) *__mptr = (ptr); \ + (type *)((char *) __mptr - offsetof(type, member)); \ + }) + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + +#define L_STRINGIFY(val) L_STRINGIFY_ARG(val) +#define L_STRINGIFY_ARG(contents) #contents + +#define L_WARN_ON(condition) __extension__ ({ \ + bool r = !!(condition); \ + if (unlikely(r)) \ + l_warn("WARNING: %s:%s() condition %s failed", \ + __FILE__, __func__, \ + #condition); \ + unlikely(r); \ + }) + +#define L_PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) +#define L_UINT_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define L_PTR_TO_INT(p) ((int) ((intptr_t) (p))) +#define L_INT_TO_PTR(u) ((void *) ((intptr_t) (u))) + +#define L_GET_UNALIGNED(ptr) __extension__ \ +({ \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v; \ +}) + +#define L_PUT_UNALIGNED(val, ptr) \ +do { \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v = (val); \ +} while(0) + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define L_LE16_TO_CPU(val) (val) +#define L_LE32_TO_CPU(val) (val) +#define L_LE64_TO_CPU(val) (val) +#define L_CPU_TO_LE16(val) (val) +#define L_CPU_TO_LE32(val) (val) +#define L_CPU_TO_LE64(val) (val) +#define L_BE16_TO_CPU(val) bswap_16(val) +#define L_BE32_TO_CPU(val) bswap_32(val) +#define L_BE64_TO_CPU(val) bswap_64(val) +#define L_CPU_TO_BE16(val) bswap_16(val) +#define L_CPU_TO_BE32(val) bswap_32(val) +#define L_CPU_TO_BE64(val) bswap_64(val) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define L_LE16_TO_CPU(val) bswap_16(val) +#define L_LE32_TO_CPU(val) bswap_32(val) +#define L_LE64_TO_CPU(val) bswap_64(val) +#define L_CPU_TO_LE16(val) bswap_16(val) +#define L_CPU_TO_LE32(val) bswap_32(val) +#define L_CPU_TO_LE64(val) bswap_64(val) +#define L_BE16_TO_CPU(val) (val) +#define L_BE32_TO_CPU(val) (val) +#define L_BE64_TO_CPU(val) (val) +#define L_CPU_TO_BE16(val) (val) +#define L_CPU_TO_BE32(val) (val) +#define L_CPU_TO_BE64(val) (val) +#else +#error "Unknown byte order" +#endif + +#if __STDC_VERSION__ <= 199409L +#define inline __inline__ +#endif + +static inline uint8_t l_get_u8(const void *ptr) +{ + return *((const uint8_t *) ptr); +} + +static inline void l_put_u8(uint8_t val, void *ptr) +{ + *((uint8_t *) ptr) = val; +} + +static inline uint16_t l_get_u16(const void *ptr) +{ + return L_GET_UNALIGNED((const uint16_t *) ptr); +} + +static inline void l_put_u16(uint16_t val, void *ptr) +{ + L_PUT_UNALIGNED(val, (uint16_t *) ptr); +} + +static inline uint32_t l_get_u32(const void *ptr) +{ + return L_GET_UNALIGNED((const uint32_t *) ptr); +} + +static inline void l_put_u32(uint32_t val, void *ptr) +{ + L_PUT_UNALIGNED(val, (uint32_t *) ptr); +} + +static inline uint64_t l_get_u64(const void *ptr) +{ + return L_GET_UNALIGNED((const uint64_t *) ptr); +} + +static inline void l_put_u64(uint64_t val, void *ptr) +{ + L_PUT_UNALIGNED(val, (uint64_t *) ptr); +} + +static inline int16_t l_get_s16(const void *ptr) +{ + return L_GET_UNALIGNED((const int16_t *) ptr); +} + +static inline int32_t l_get_s32(const void *ptr) +{ + return L_GET_UNALIGNED((const int32_t *) ptr); +} + +static inline int64_t l_get_s64(const void *ptr) +{ + return L_GET_UNALIGNED((const int64_t *) ptr); +} + +static inline uint16_t l_get_le16(const void *ptr) +{ + return L_LE16_TO_CPU(L_GET_UNALIGNED((const uint16_t *) ptr)); +} + +static inline uint16_t l_get_be16(const void *ptr) +{ + return L_BE16_TO_CPU(L_GET_UNALIGNED((const uint16_t *) ptr)); +} + +static inline uint32_t l_get_le32(const void *ptr) +{ + return L_LE32_TO_CPU(L_GET_UNALIGNED((const uint32_t *) ptr)); +} + +static inline uint32_t l_get_be32(const void *ptr) +{ + return L_BE32_TO_CPU(L_GET_UNALIGNED((const uint32_t *) ptr)); +} + +static inline uint64_t l_get_le64(const void *ptr) +{ + return L_LE64_TO_CPU(L_GET_UNALIGNED((const uint64_t *) ptr)); +} + +static inline uint64_t l_get_be64(const void *ptr) +{ + return L_BE64_TO_CPU(L_GET_UNALIGNED((const uint64_t *) ptr)); +} + +static inline void l_put_le16(uint16_t val, void *ptr) +{ + L_PUT_UNALIGNED(L_CPU_TO_LE16(val), (uint16_t *) ptr); +} + +static inline void l_put_be16(uint16_t val, const void *ptr) +{ + L_PUT_UNALIGNED(L_CPU_TO_BE16(val), (uint16_t *) ptr); +} + +static inline void l_put_le32(uint32_t val, void *ptr) +{ + L_PUT_UNALIGNED(L_CPU_TO_LE32(val), (uint32_t *) ptr); +} + +static inline void l_put_be32(uint32_t val, void *ptr) +{ + L_PUT_UNALIGNED(L_CPU_TO_BE32(val), (uint32_t *) ptr); +} + +static inline void l_put_le64(uint64_t val, void *ptr) +{ + L_PUT_UNALIGNED(L_CPU_TO_LE64(val), (uint64_t *) ptr); +} + +static inline void l_put_be64(uint64_t val, void *ptr) +{ + L_PUT_UNALIGNED(L_CPU_TO_BE64(val), (uint64_t *) ptr); +} + +#define L_AUTO_CLEANUP_VAR(vartype,varname,destroy) \ + vartype varname __attribute__((cleanup(destroy))) + +#define L_AUTO_FREE_VAR(vartype,varname) \ + vartype varname __attribute__((cleanup(auto_free))) + +#define L_ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +void *l_malloc(size_t size) __attribute__ ((warn_unused_result, malloc)); +void *l_memdup(const void *mem, size_t size) + __attribute__ ((warn_unused_result, malloc)); +void l_free(void *ptr); + +void *l_realloc(void *mem, size_t size) + __attribute__ ((warn_unused_result, malloc)); + +static inline void auto_free(void *a) +{ + void **p = (void **)a; + l_free(*p); +} + +static inline size_t minsize(size_t a, size_t b) +{ + if (a <= b) + return a; + + return b; +} + +/** + * l_new: + * @type: type of structure + * @count: amount of structures + * + * Returns: pointer to allocated memory + **/ +#define l_new(type, count) \ + (type *) (__extension__ ({ \ + size_t __n = (size_t) (count); \ + size_t __s = sizeof(type); \ + void *__p; \ + __p = l_malloc(__n * __s); \ + memset(__p, 0, __n * __s); \ + __p; \ + })) + +char *l_strdup(const char *str); +char *l_strndup(const char *str, size_t max); +char *l_strdup_printf(const char *format, ...) + __attribute__((format(printf, 1, 2))); +char *l_strdup_vprintf(const char *format, va_list args); + +size_t l_strlcpy(char* dst, const char *src, size_t len); + +bool l_str_has_prefix(const char *str, const char *prefix); +bool l_str_has_suffix(const char *str, const char *suffix); + +char *l_util_hexstring(const unsigned char *buf, size_t len); +char *l_util_hexstring_upper(const unsigned char *buf, size_t len); +unsigned char *l_util_from_hexstring(const char *str, size_t *out_len); + +typedef void (*l_util_hexdump_func_t) (const char *str, void *user_data); + +void l_util_hexdump(bool in, const void *buf, size_t len, + l_util_hexdump_func_t function, void *user_data); +void l_util_hexdump_two(bool in, const void *buf1, size_t len1, + const void *buf2, size_t len2, + l_util_hexdump_func_t function, void *user_data); +void l_util_hexdumpv(bool in, const struct iovec *iov, size_t n_iov, + l_util_hexdump_func_t function, + void *user_data); +void l_util_debug(l_util_hexdump_func_t function, void *user_data, + const char *format, ...) + __attribute__((format(printf, 3, 4))); + +const char *l_util_get_debugfs_path(void); + +#define L_TFR(expression) \ + (__extension__ \ + ({ long int __result; \ + do __result = (long int) (expression); \ + while (__result == -1L && errno == EINTR); \ + __result; })) + +#ifdef __cplusplus +} +#endif + +#endif /* __ELL_UTIL_H */ diff --git a/emulator/amp.c b/emulator/amp.c new file mode 100644 index 0000000..605c6c8 --- /dev/null +++ b/emulator/amp.c @@ -0,0 +1,1053 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/util.h" +#include "src/shared/mainloop.h" +#include "monitor/bt.h" + +#include "amp.h" + +#define PHY_MODE_IDLE 0x00 +#define PHY_MODE_INITIATOR 0x01 +#define PHY_MODE_ACCEPTOR 0x02 + +#define MAX_ASSOC_LEN 672 + +struct bt_amp { + volatile int ref_count; + int vhci_fd; + + char phylink_path[32]; + int phylink_fd; + + uint8_t event_mask[16]; + uint16_t manufacturer; + uint8_t commands[64]; + uint8_t features[8]; + + uint8_t amp_status; + uint8_t amp_type; + uint8_t local_assoc[MAX_ASSOC_LEN]; + uint16_t local_assoc_len; + uint8_t remote_assoc[MAX_ASSOC_LEN]; + uint16_t remote_assoc_len; + + uint8_t phy_mode; + uint8_t phy_handle; + uint16_t logic_handle; +}; + +static void reset_defaults(struct bt_amp *amp) +{ + memset(amp->event_mask, 0, sizeof(amp->event_mask)); + amp->event_mask[1] |= 0x20; /* Command Complete */ + amp->event_mask[1] |= 0x40; /* Command Status */ + amp->event_mask[1] |= 0x80; /* Hardware Error */ + amp->event_mask[2] |= 0x01; /* Flush Occurred */ + amp->event_mask[2] |= 0x04; /* Number of Completed Packets */ + amp->event_mask[3] |= 0x02; /* Data Buffer Overflow */ + amp->event_mask[3] |= 0x20; /* QoS Violation */ + amp->event_mask[7] |= 0x01; /* Enhanced Flush Complete */ + + amp->event_mask[8] |= 0x01; /* Physical Link Complete */ + amp->event_mask[8] |= 0x02; /* Channel Selected */ + amp->event_mask[8] |= 0x04; /* Disconnection Physical Link Complete */ + amp->event_mask[8] |= 0x08; /* Physical Link Loss Early Warning */ + amp->event_mask[8] |= 0x10; /* Physical Link Recovery */ + amp->event_mask[8] |= 0x20; /* Logical Link Complete */ + amp->event_mask[8] |= 0x40; /* Disconection Logical Link Complete */ + amp->event_mask[8] |= 0x80; /* Flow Specification Modify Complete */ + amp->event_mask[9] |= 0x01; /* Number of Completed Data Blocks */ + amp->event_mask[9] |= 0x02; /* AMP Start Test */ + amp->event_mask[9] |= 0x04; /* AMP Test End */ + amp->event_mask[9] |= 0x08; /* AMP Receiver Report */ + amp->event_mask[9] |= 0x10; /* Short Range Mode Change Complete */ + amp->event_mask[9] |= 0x20; /* AMP Status Change */ + + amp->manufacturer = 0x003f; /* Bluetooth SIG (63) */ + + memset(amp->commands, 0, sizeof(amp->commands)); + amp->commands[5] |= 0x40; /* Set Event Mask */ + amp->commands[5] |= 0x80; /* Reset */ + //amp->commands[6] |= 0x01; /* Set Event Filter */ + //amp->commands[7] |= 0x04; /* Read Connection Accept Timeout */ + //amp->commands[7] |= 0x08; /* Write Connection Accept Timeout */ + //amp->commands[10] |= 0x80; /* Host Number of Completed Packets */ + //amp->commands[11] |= 0x01; /* Read Link Supervision Timeout */ + //amp->commands[11] |= 0x02; /* Write Link Supervision Timeout */ + amp->commands[14] |= 0x08; /* Read Local Version Information */ + amp->commands[14] |= 0x10; /* Read Local Supported Commands */ + amp->commands[14] |= 0x20; /* Read Local Supported Features */ + amp->commands[14] |= 0x80; /* Read Buffer Size */ + //amp->commands[15] |= 0x04; /* Read Failed Contact Counter */ + //amp->commands[15] |= 0x08; /* Reset Failed Contact Counter */ + //amp->commands[15] |= 0x10; /* Read Link Quality */ + //amp->commands[15] |= 0x20; /* Read RSSI */ + //amp->commands[16] |= 0x04; /* Enable Device Under Test Mode */ + //amp->commands[19] |= 0x40; /* Enhanced Flush */ + + amp->commands[21] |= 0x01; /* Create Physical Link */ + amp->commands[21] |= 0x02; /* Accept Physical Link */ + amp->commands[21] |= 0x04; /* Disconnect Phyiscal Link */ + amp->commands[21] |= 0x08; /* Create Logical Link */ + amp->commands[21] |= 0x10; /* Accept Logical Link */ + amp->commands[21] |= 0x20; /* Disconnect Logical Link */ + amp->commands[21] |= 0x40; /* Logical Link Cancel */ + //amp->commands[21] |= 0x80; /* Flow Specification Modify */ + //amp->commands[22] |= 0x01; /* Read Logical Link Accept Timeout */ + //amp->commands[22] |= 0x02; /* Write Logical Link Accept Timeout */ + amp->commands[22] |= 0x04; /* Set Event Mask Page 2 */ + amp->commands[22] |= 0x08; /* Read Location Data */ + amp->commands[22] |= 0x10; /* Write Location Data */ + amp->commands[22] |= 0x20; /* Read Local AMP Info */ + amp->commands[22] |= 0x40; /* Read Local AMP ASSOC */ + amp->commands[22] |= 0x80; /* Write Remote AMP ASSOC */ + amp->commands[23] |= 0x01; /* Read Flow Control Mode */ + amp->commands[23] |= 0x02; /* Write Flow Control Mode */ + amp->commands[23] |= 0x04; /* Read Data Block Size */ + //amp->commands[23] |= 0x20; /* Enable AMP Receiver Reports */ + //amp->commands[23] |= 0x40; /* AMP Test End */ + //amp->commands[23] |= 0x80; /* AMP Test */ + //amp->commands[24] |= 0x04; /* Read Best Effort Flush Timeout */ + //amp->commands[24] |= 0x08; /* Write Best Effort Flush Timeout */ + //amp->commands[24] |= 0x10; /* Short Range Mode */ + + memset(amp->features, 0, sizeof(amp->features)); + + amp->amp_status = 0x01; /* Used for Bluetooth only */ + amp->amp_type = 0x42; /* Fake virtual AMP type */ + + memset(amp->local_assoc, 0, sizeof(amp->local_assoc)); + amp->local_assoc_len = 0; + + memset(amp->remote_assoc, 0, sizeof(amp->remote_assoc)); + amp->remote_assoc_len = 0; + + amp->phy_mode = PHY_MODE_IDLE; + amp->phy_handle = 0x00; /* Invalid physical link handle */ + amp->logic_handle = 0x0000; +} + +static void send_packet(struct bt_amp *amp, const void *data, uint16_t len) +{ + if (write(amp->vhci_fd, data, len) < 0) + fprintf(stderr, "Write to /dev/vhci failed\n"); +} + +static void send_event(struct bt_amp *amp, uint8_t event, + const void *data, uint8_t len) +{ + struct bt_hci_evt_hdr *hdr; + uint16_t pkt_len; + void *pkt_data; + + pkt_len = 1 + sizeof(*hdr) + len; + + pkt_data = alloca(pkt_len); + if (!pkt_data) + return; + + ((uint8_t *) pkt_data)[0] = BT_H4_EVT_PKT; + + hdr = pkt_data + 1; + hdr->evt = event; + hdr->plen = len; + + if (len > 0) + memcpy(pkt_data + 1 + sizeof(*hdr), data, len); + + send_packet(amp, pkt_data, pkt_len); +} + +static void cmd_complete(struct bt_amp *amp, uint16_t opcode, + const void *data, uint8_t len) +{ + struct bt_hci_evt_hdr *hdr; + struct bt_hci_evt_cmd_complete *cc; + uint16_t pkt_len; + void *pkt_data; + + pkt_len = 1 + sizeof(*hdr) + sizeof(*cc) + len; + + pkt_data = alloca(pkt_len); + if (!pkt_data) + return; + + ((uint8_t *) pkt_data)[0] = BT_H4_EVT_PKT; + + hdr = pkt_data + 1; + hdr->evt = BT_HCI_EVT_CMD_COMPLETE; + hdr->plen = sizeof(*cc) + len; + + cc = pkt_data + 1 + sizeof(*hdr); + cc->ncmd = 0x01; + cc->opcode = cpu_to_le16(opcode); + + if (len > 0) + memcpy(pkt_data + 1 + sizeof(*hdr) + sizeof(*cc), data, len); + + send_packet(amp, pkt_data, pkt_len); +} + +static void cmd_status(struct bt_amp *amp, uint8_t status, uint16_t opcode) +{ + struct bt_hci_evt_hdr *hdr; + struct bt_hci_evt_cmd_status *cs; + uint16_t pkt_len; + void *pkt_data; + + pkt_len = 1 + sizeof(*hdr) + sizeof(*cs); + + pkt_data = alloca(pkt_len); + if (!pkt_data) + return; + + ((uint8_t *) pkt_data)[0] = BT_H4_EVT_PKT; + + hdr = pkt_data + 1; + hdr->evt = BT_HCI_EVT_CMD_STATUS; + hdr->plen = sizeof(*cs); + + cs = pkt_data + 1 + sizeof(*hdr); + cs->status = status; + cs->ncmd = 0x01; + cs->opcode = cpu_to_le16(opcode); + + send_packet(amp, pkt_data, pkt_len); +} + +static void cmd_set_event_mask(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_event_mask *cmd = data; + uint8_t status; + + memcpy(amp->event_mask, cmd->mask, 8); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(amp, BT_HCI_CMD_SET_EVENT_MASK, &status, sizeof(status)); +} + +static void cmd_reset(struct bt_amp *amp, const void *data, uint8_t size) +{ + uint8_t status; + + reset_defaults(amp); + + amp->local_assoc[0] = 0x00; + amp->local_assoc_len = 1; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(amp, BT_HCI_CMD_RESET, &status, sizeof(status)); +} + +static void cmd_read_local_version(struct bt_amp *amp, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_local_version rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.hci_ver = 0x05; + rsp.hci_rev = cpu_to_le16(0x0000); + rsp.lmp_ver = 0x01; + rsp.manufacturer = cpu_to_le16(amp->manufacturer); + rsp.lmp_subver = cpu_to_le16(0x0000); + + cmd_complete(amp, BT_HCI_CMD_READ_LOCAL_VERSION, &rsp, sizeof(rsp)); +} + +static void cmd_read_local_commands(struct bt_amp *amp, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_local_commands rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(rsp.commands, amp->commands, 64); + + cmd_complete(amp, BT_HCI_CMD_READ_LOCAL_COMMANDS, &rsp, sizeof(rsp)); +} + +static void cmd_read_local_features(struct bt_amp *amp, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_local_features rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(rsp.features, amp->features, 8); + + cmd_complete(amp, BT_HCI_CMD_READ_LOCAL_FEATURES, &rsp, sizeof(rsp)); +} + +static void cmd_read_buffer_size(struct bt_amp *amp, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_buffer_size rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.acl_mtu = cpu_to_le16(0x0000); + rsp.sco_mtu = 0x00; + rsp.acl_max_pkt = cpu_to_le16(0x0000); + rsp.sco_max_pkt = cpu_to_le16(0x0000); + + cmd_complete(amp, BT_HCI_CMD_READ_BUFFER_SIZE, &rsp, sizeof(rsp)); +} + +static void evt_phy_link_complete(struct bt_amp *amp) +{ + struct bt_hci_evt_phy_link_complete evt; + + evt.status = BT_HCI_ERR_SUCCESS; + evt.phy_handle = amp->phy_handle; + + send_event(amp, BT_HCI_EVT_PHY_LINK_COMPLETE, &evt, sizeof(evt)); +} + +static void evt_disconn_phy_link_complete(struct bt_amp *amp, uint8_t reason) +{ + struct bt_hci_evt_disconn_phy_link_complete evt; + + evt.status = BT_HCI_ERR_SUCCESS; + evt.phy_handle = amp->phy_handle; + evt.reason = reason; + + send_event(amp, BT_HCI_EVT_DISCONN_PHY_LINK_COMPLETE, + &evt, sizeof(evt)); +} + +static void link_callback(int fd, uint32_t events, void *user_data) +{ + struct bt_amp *amp = user_data; + + if (events & (EPOLLERR | EPOLLHUP)) { + close(fd); + mainloop_remove_fd(fd); + + evt_disconn_phy_link_complete(amp, 0x13); + + amp->phy_mode = PHY_MODE_IDLE; + amp->phy_handle = 0x00; + return; + } +} + +static void cmd_create_phy_link(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_create_phy_link *cmd = data; + + if (cmd->phy_handle == 0x00) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_CREATE_PHY_LINK); + return; + } + + if (amp->phy_mode != PHY_MODE_IDLE) { + cmd_status(amp, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_CREATE_PHY_LINK); + return; + } + + amp->phy_mode = PHY_MODE_INITIATOR; + amp->phy_handle = cmd->phy_handle; + + cmd_status(amp, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_CREATE_PHY_LINK); +} + +static void cmd_accept_phy_link(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_accept_phy_link *cmd = data; + + if (cmd->phy_handle == 0x00) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_ACCEPT_PHY_LINK); + return; + } + + if (amp->phy_mode != PHY_MODE_IDLE) { + cmd_status(amp, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_ACCEPT_PHY_LINK); + return; + } + + amp->phy_mode = PHY_MODE_ACCEPTOR; + amp->phy_handle = cmd->phy_handle; + + cmd_status(amp, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_ACCEPT_PHY_LINK); +} + +static void cmd_disconn_phy_link(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_disconn_phy_link *cmd = data; + + if (cmd->phy_handle == 0x00) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_DISCONN_PHY_LINK); + return; + } + + if (amp->phy_mode == PHY_MODE_IDLE) { + cmd_status(amp, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_DISCONN_PHY_LINK); + return; + } + + if (cmd->phy_handle != amp->phy_handle) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_DISCONN_PHY_LINK); + return; + } + + cmd_status(amp, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_DISCONN_PHY_LINK); + + mainloop_remove_fd(amp->phylink_fd); + close(amp->phylink_fd); + + evt_disconn_phy_link_complete(amp, cmd->reason); + + amp->phy_mode = PHY_MODE_IDLE; + amp->phy_handle = 0x00; +} + +static void evt_logic_link_complete(struct bt_amp *amp) +{ + struct bt_hci_evt_logic_link_complete evt; + + evt.status = BT_HCI_ERR_SUCCESS; + evt.handle = htobs(amp->logic_handle); + evt.phy_handle = amp->phy_handle; + evt.flow_spec = 0x00; + + send_event(amp, BT_HCI_EVT_LOGIC_LINK_COMPLETE, &evt, sizeof(evt)); +} + +static void evt_disconn_logic_link_complete(struct bt_amp *amp, uint8_t reason) +{ + struct bt_hci_evt_disconn_logic_link_complete evt; + + evt.status = BT_HCI_ERR_SUCCESS; + evt.handle = htobs(amp->logic_handle); + evt.reason = reason; + + send_event(amp, BT_HCI_EVT_DISCONN_LOGIC_LINK_COMPLETE, + &evt, sizeof(evt)); +} + +static void cmd_create_logic_link(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_create_logic_link *cmd = data; + + if (cmd->phy_handle == 0x00) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_CREATE_LOGIC_LINK); + return; + } + + if (amp->phy_mode != PHY_MODE_IDLE) { + cmd_status(amp, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_CREATE_LOGIC_LINK); + return; + } + + if (amp->logic_handle != 0x00) { + cmd_status(amp, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_CREATE_LOGIC_LINK); + return; + } + + cmd_status(amp, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_CREATE_LOGIC_LINK); + + amp->logic_handle = 0x0042; + + evt_logic_link_complete(amp); +} + +static void cmd_accept_logic_link(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_accept_logic_link *cmd = data; + + if (cmd->phy_handle == 0x00) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_ACCEPT_LOGIC_LINK); + return; + } + + if (amp->phy_mode != PHY_MODE_IDLE) { + cmd_status(amp, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_ACCEPT_LOGIC_LINK); + return; + } + + if (amp->logic_handle != 0x00) { + cmd_status(amp, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_ACCEPT_LOGIC_LINK); + return; + } + + cmd_status(amp, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_ACCEPT_LOGIC_LINK); + + amp->logic_handle = 0x0023; + + evt_logic_link_complete(amp); +} + +static void cmd_disconn_logic_link(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_disconn_logic_link *cmd = data; + + if (cmd->handle == 0x00) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_DISCONN_LOGIC_LINK); + return; + } + + if (cmd->handle != amp->logic_handle) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_DISCONN_LOGIC_LINK); + return; + } + + cmd_status(amp, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_DISCONN_LOGIC_LINK); + + evt_disconn_logic_link_complete(amp, 0x13); + + amp->logic_handle = 0x0000; +} + +static void cmd_logic_link_cancel(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_logic_link_cancel *cmd = data; + struct bt_hci_rsp_logic_link_cancel rsp; + + if (cmd->phy_handle == 0x00) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LOGIC_LINK_CANCEL); + return; + } + + if (amp->phy_mode != PHY_MODE_IDLE) { + cmd_status(amp, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LOGIC_LINK_CANCEL); + return; + } + + amp->logic_handle = 0x0000; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.phy_handle = amp->phy_handle; + rsp.flow_spec = 0x00; + + cmd_complete(amp, BT_HCI_CMD_LOGIC_LINK_CANCEL, &rsp, sizeof(rsp)); +} + +static void cmd_set_event_mask_page2(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_event_mask_page2 *cmd = data; + uint8_t status; + + memcpy(amp->event_mask + 8, cmd->mask, 8); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(amp, BT_HCI_CMD_SET_EVENT_MASK_PAGE2, + &status, sizeof(status)); +} + +static void cmd_read_location_data(struct bt_amp *amp, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_location_data rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.domain_aware = 0x00; + rsp.domain[0] = 0x58; + rsp.domain[1] = 0x58; + rsp.domain_options = 0x58; + rsp.options = 0x00; + + cmd_complete(amp, BT_HCI_CMD_READ_LOCATION_DATA, &rsp, sizeof(rsp)); +} + +static void cmd_write_location_data(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_location_data *cmd = data; + uint8_t status; + + if (cmd->domain_aware > 0x01) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_WRITE_LOCATION_DATA); + return; + } + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(amp, BT_HCI_CMD_WRITE_LOCATION_DATA, + &status, sizeof(status)); +} + +static void cmd_read_flow_control_mode(struct bt_amp *amp, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_flow_control_mode rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.mode = 0x01; + + cmd_complete(amp, BT_HCI_CMD_READ_FLOW_CONTROL_MODE, + &rsp, sizeof(rsp)); +} + +static void cmd_write_flow_control_mode(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_flow_control_mode *cmd = data; + uint8_t status; + + if (cmd->mode != 0x01) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_WRITE_FLOW_CONTROL_MODE); + return; + } + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(amp, BT_HCI_CMD_WRITE_FLOW_CONTROL_MODE, + &status, sizeof(status)); +} + +static void cmd_read_data_block_size(struct bt_amp *amp, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_data_block_size rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.max_acl_len = cpu_to_le16(1492); + rsp.block_len = cpu_to_le16(1492); + rsp.num_blocks = cpu_to_le16(1); + + cmd_complete(amp, BT_HCI_CMD_READ_DATA_BLOCK_SIZE, &rsp, sizeof(rsp)); +} + +static void cmd_read_local_amp_info(struct bt_amp *amp, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_local_amp_info rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.amp_status = amp->amp_status; + rsp.total_bw = cpu_to_le32(24000); + rsp.max_bw = cpu_to_le32(24000); + rsp.min_latency = cpu_to_le32(100); + rsp.max_pdu = cpu_to_le32(1492); + rsp.amp_type = amp->amp_type; + rsp.pal_cap = cpu_to_le16(0x0001); + rsp.max_assoc_len = cpu_to_le16(MAX_ASSOC_LEN); + rsp.max_flush_to = cpu_to_le32(20000); + rsp.be_flush_to = cpu_to_le32(20000); + + cmd_complete(amp, BT_HCI_CMD_READ_LOCAL_AMP_INFO, &rsp, sizeof(rsp)); +} + +static void cmd_read_local_amp_assoc(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_local_amp_assoc *cmd = data; + struct bt_hci_rsp_read_local_amp_assoc rsp; + uint16_t len_so_far, remain_assoc_len, fragment_len; + + if (cmd->phy_handle != amp->phy_handle) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_READ_LOCAL_AMP_ASSOC); + return; + } + + len_so_far = le16_to_cpu(cmd->len_so_far); + remain_assoc_len = amp->local_assoc_len - len_so_far; + fragment_len = remain_assoc_len > 248 ? 248 : remain_assoc_len; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.phy_handle = cmd->phy_handle; + rsp.remain_assoc_len = cpu_to_le16(remain_assoc_len); + memcpy(rsp.assoc_fragment, amp->local_assoc + len_so_far, + fragment_len); + + cmd_complete(amp, BT_HCI_CMD_READ_LOCAL_AMP_ASSOC, + &rsp, 4 + fragment_len); +} + +static int create_unix_server(const char *path) +{ + struct sockaddr_un addr; + int fd; + + fd = socket(PF_UNIX, SOCK_SEQPACKET, 0); + if (fd < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_path[0] = '\0'; + strcpy(addr.sun_path + 1, path); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(fd); + return -1; + } + + if (listen(fd, 1) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static int connect_unix_client(const char *path) +{ + struct sockaddr_un addr; + int fd; + + fd = socket(PF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (fd < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_path[0] = '\0'; + strcpy(addr.sun_path + 1, path); + + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static void accept_callback(int fd, uint32_t events, void *user_data) +{ + struct bt_amp *amp = user_data; + struct sockaddr_un addr; + socklen_t len; + int new_fd; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(fd); + return; + } + + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + + new_fd = accept4(fd, (struct sockaddr *) &addr, &len, + SOCK_CLOEXEC | SOCK_NONBLOCK); + if (new_fd < 0) + return; + + mainloop_remove_fd(fd); + close(fd); + + amp->phylink_fd = new_fd; + + evt_phy_link_complete(amp); + + mainloop_add_fd(new_fd, EPOLLIN, link_callback, amp, NULL); +} + +static void connect_callback(int fd, uint32_t events, void *user_data) +{ + struct bt_amp *amp = user_data; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(fd); + return; + } + + mainloop_remove_fd(fd); + + evt_phy_link_complete(amp); + + mainloop_add_fd(fd, EPOLLIN, link_callback, amp, NULL); +} + +static void cmd_write_remote_amp_assoc(struct bt_amp *amp, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_remote_amp_assoc *cmd = data; + struct bt_hci_rsp_write_remote_amp_assoc rsp; + int fd; + + if (cmd->phy_handle == 0x00) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_WRITE_REMOTE_AMP_ASSOC); + return; + } + + if (cmd->phy_handle != amp->phy_handle) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_WRITE_REMOTE_AMP_ASSOC); + return; + } + + switch (amp->phy_mode) { + case PHY_MODE_INITIATOR: + strcpy(amp->phylink_path, "amp"); + + fd = create_unix_server(amp->phylink_path); + if (fd < 0) { + cmd_status(amp, BT_HCI_ERR_UNSPECIFIED_ERROR, + BT_HCI_CMD_WRITE_REMOTE_AMP_ASSOC); + return; + } + + amp->local_assoc[0] = 0x01; + memcpy(amp->local_assoc + 1, amp->phylink_path, + strlen(amp->phylink_path) + 1); + amp->local_assoc_len = strlen(amp->phylink_path) + 2; + + mainloop_add_fd(fd, EPOLLIN, accept_callback, amp, NULL); + + amp->phylink_fd = fd; + break; + + case PHY_MODE_ACCEPTOR: + if (cmd->assoc_fragment[0] != 0x01) { + cmd_status(amp, BT_HCI_ERR_UNSPECIFIED_ERROR, + BT_HCI_CMD_WRITE_REMOTE_AMP_ASSOC); + return; + } + + memcpy(amp->phylink_path, cmd->assoc_fragment + 1, + cmd->remain_assoc_len - 1); + + fd = connect_unix_client(amp->phylink_path); + if (fd < 0) { + cmd_status(amp, BT_HCI_ERR_UNSPECIFIED_ERROR, + BT_HCI_CMD_WRITE_REMOTE_AMP_ASSOC); + return; + } + + mainloop_add_fd(fd, EPOLLOUT, connect_callback, amp, NULL); + + amp->phylink_fd = fd; + break; + + default: + cmd_status(amp, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_WRITE_REMOTE_AMP_ASSOC); + return; + } + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.phy_handle = amp->phy_handle; + + cmd_complete(amp, BT_HCI_CMD_WRITE_REMOTE_AMP_ASSOC, &rsp, sizeof(rsp)); + + if (amp->phy_mode == PHY_MODE_INITIATOR) { + struct bt_hci_evt_channel_selected evt; + + evt.phy_handle = amp->phy_handle; + + send_event(amp, BT_HCI_EVT_CHANNEL_SELECTED, &evt, sizeof(evt)); + } +} + +static const struct { + uint16_t opcode; + void (*func) (struct bt_amp *amp, const void *data, uint8_t size); + uint8_t size; + bool fixed; +} cmd_table[] = { + { BT_HCI_CMD_SET_EVENT_MASK, cmd_set_event_mask, 8, true }, + { BT_HCI_CMD_RESET, cmd_reset, 0, true }, + { BT_HCI_CMD_READ_LOCAL_VERSION, cmd_read_local_version, 0, true }, + { BT_HCI_CMD_READ_LOCAL_COMMANDS, cmd_read_local_commands, 0, true }, + { BT_HCI_CMD_READ_LOCAL_FEATURES, cmd_read_local_features, 0, true }, + { BT_HCI_CMD_READ_BUFFER_SIZE, cmd_read_buffer_size, 0, true }, + + { BT_HCI_CMD_CREATE_PHY_LINK, + cmd_create_phy_link, 3, false }, + { BT_HCI_CMD_ACCEPT_PHY_LINK, + cmd_accept_phy_link, 3, false }, + { BT_HCI_CMD_DISCONN_PHY_LINK, + cmd_disconn_phy_link, 2, true }, + { BT_HCI_CMD_CREATE_LOGIC_LINK, + cmd_create_logic_link, 33, true }, + { BT_HCI_CMD_ACCEPT_LOGIC_LINK, + cmd_accept_logic_link, 33, true }, + { BT_HCI_CMD_DISCONN_LOGIC_LINK, + cmd_disconn_logic_link, 2, true }, + { BT_HCI_CMD_LOGIC_LINK_CANCEL, + cmd_logic_link_cancel, 2, true }, + { BT_HCI_CMD_SET_EVENT_MASK_PAGE2, + cmd_set_event_mask_page2, 8, true }, + { BT_HCI_CMD_READ_LOCATION_DATA, + cmd_read_location_data, 0, true }, + { BT_HCI_CMD_WRITE_LOCATION_DATA, + cmd_write_location_data, 5, true }, + { BT_HCI_CMD_READ_FLOW_CONTROL_MODE, + cmd_read_flow_control_mode, 0, true }, + { BT_HCI_CMD_WRITE_FLOW_CONTROL_MODE, + cmd_write_flow_control_mode, 1, true }, + { BT_HCI_CMD_READ_DATA_BLOCK_SIZE, + cmd_read_data_block_size, 0, true }, + { BT_HCI_CMD_READ_LOCAL_AMP_INFO, + cmd_read_local_amp_info, 0, true }, + { BT_HCI_CMD_READ_LOCAL_AMP_ASSOC, + cmd_read_local_amp_assoc, 5, true }, + { BT_HCI_CMD_WRITE_REMOTE_AMP_ASSOC, + cmd_write_remote_amp_assoc, 6, false }, + { } +}; + +static void process_command(struct bt_amp *amp, const void *data, size_t size) +{ + const struct bt_hci_cmd_hdr *hdr = data; + uint16_t opcode; + unsigned int i; + + if (size < sizeof(*hdr)) + return; + + data += sizeof(*hdr); + size -= sizeof(*hdr); + + opcode = le16_to_cpu(hdr->opcode); + + if (hdr->plen != size) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, opcode); + return; + } + + for (i = 0; cmd_table[i].func; i++) { + if (cmd_table[i].opcode != opcode) + continue; + + if ((cmd_table[i].fixed && size != cmd_table[i].size) || + size < cmd_table[i].size) { + cmd_status(amp, BT_HCI_ERR_INVALID_PARAMETERS, opcode); + return; + } + + cmd_table[i].func(amp, data, size); + return; + } + + cmd_status(amp, BT_HCI_ERR_UNKNOWN_COMMAND, opcode); +} + +static void vhci_read_callback(int fd, uint32_t events, void *user_data) +{ + struct bt_amp *amp = user_data; + unsigned char buf[4096]; + ssize_t len; + + if (events & (EPOLLERR | EPOLLHUP)) + return; + + len = read(amp->vhci_fd, buf, sizeof(buf)); + if (len < 1) + return; + + switch (buf[0]) { + case BT_H4_CMD_PKT: + process_command(amp, buf + 1, len - 1); + break; + } +} + +struct bt_amp *bt_amp_new(void) +{ + unsigned char setup_cmd[2]; + struct bt_amp *amp; + + amp = calloc(1, sizeof(*amp)); + if (!amp) + return NULL; + + reset_defaults(amp); + + amp->vhci_fd = open("/dev/vhci", O_RDWR); + if (amp->vhci_fd < 0) { + free(amp); + return NULL; + } + + setup_cmd[0] = HCI_VENDOR_PKT; + setup_cmd[1] = HCI_AMP; + + if (write(amp->vhci_fd, setup_cmd, sizeof(setup_cmd)) < 0) { + close(amp->vhci_fd); + free(amp); + return NULL; + } + + mainloop_add_fd(amp->vhci_fd, EPOLLIN, vhci_read_callback, amp, NULL); + + return bt_amp_ref(amp); +} + +struct bt_amp *bt_amp_ref(struct bt_amp *amp) +{ + if (!amp) + return NULL; + + __sync_fetch_and_add(&->ref_count, 1); + + return amp; +} + +void bt_amp_unref(struct bt_amp *amp) +{ + if (!amp) + return; + + if (__sync_sub_and_fetch(&->ref_count, 1)) + return; + + mainloop_remove_fd(amp->vhci_fd); + + close(amp->vhci_fd); + + free(amp); +} diff --git a/emulator/amp.h b/emulator/amp.h new file mode 100644 index 0000000..189dfb7 --- /dev/null +++ b/emulator/amp.h @@ -0,0 +1,32 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +struct bt_amp; + +struct bt_amp *bt_amp_new(void); + +struct bt_amp *bt_amp_ref(struct bt_amp *amp); +void bt_amp_unref(struct bt_amp *amp); diff --git a/emulator/b1ee.c b/emulator/b1ee.c new file mode 100644 index 0000000..377181b --- /dev/null +++ b/emulator/b1ee.c @@ -0,0 +1,341 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/mainloop.h" + +#define DEFAULT_HOST_PORT "45550" /* 0xb1ee */ +#define DEFAULT_SNIFFER_PORT "45551" /* 0xb1ef */ + +static int sniffer_fd; +static int server_fd; +static int vhci_fd; + +static void usage(void) +{ + printf("b1ee - Bluetooth device testing tool over internet\n" + "Usage:\n"); + printf("\tb1ee [options] \n"); + printf("options:\n" + "\t-p, --port Specify the server port\n" + "\t-s, --sniffer-port Specify the sniffer port\n" + "\t-v, --version Show version information\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "port", required_argument, NULL, 'p' }, + { "sniffer-port", required_argument, NULL, 's' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +static char *set_port(char *str) +{ + char *c; + + if (str == NULL || str[0] == '\0') + return NULL; + + for (c = str; *c != '\0'; c++) + if (isdigit(*c) == 0) + return NULL; + + if (atol(str) > 65535) + return NULL; + + return strdup(str); +} + +static void sniffer_read_callback(int fd, uint32_t events, void *user_data) +{ + static uint8_t buf[4096]; + ssize_t len; + + if (events & (EPOLLERR | EPOLLHUP)) + return; + +again: + len = recv(fd, buf, sizeof(buf), MSG_DONTWAIT); + if (len < 0) { + if (errno == EAGAIN) + goto again; + return; + } + + printf("Sniffer received: %zi bytes\n", len); +} + +static uint8_t *server_pkt_data; +static uint8_t server_pkt_type; +static uint16_t server_pkt_expect; +static uint16_t server_pkt_len; +static uint16_t server_pkt_offset; + +static void server_read_callback(int fd, uint32_t events, void *user_data) +{ + static uint8_t buf[4096]; + uint8_t *ptr = buf; + ssize_t len; + uint16_t count; + + if (events & (EPOLLERR | EPOLLHUP)) + return; + +again: + len = recv(fd, buf + server_pkt_offset, + sizeof(buf) - server_pkt_offset, MSG_DONTWAIT); + if (len < 0) { + if (errno == EAGAIN) + goto again; + return; + } + + count = server_pkt_offset + len; + + while (count > 0) { + hci_event_hdr *evt_hdr; + + if (!server_pkt_data) { + server_pkt_type = ptr[0]; + + switch (server_pkt_type) { + case HCI_EVENT_PKT: + if (count < HCI_EVENT_HDR_SIZE + 1) { + server_pkt_offset += len; + return; + } + evt_hdr = (hci_event_hdr *) (ptr + 1); + server_pkt_expect = HCI_EVENT_HDR_SIZE + + evt_hdr->plen + 1; + server_pkt_data = malloc(server_pkt_expect); + server_pkt_len = 0; + break; + default: + fprintf(stderr, "Unknown packet from server\n"); + return; + } + + server_pkt_offset = 0; + } + + if (count >= server_pkt_expect) { + ssize_t written; + + memcpy(server_pkt_data + server_pkt_len, + ptr, server_pkt_expect); + ptr += server_pkt_expect; + count -= server_pkt_expect; + + written = write(vhci_fd, server_pkt_data, + server_pkt_len + server_pkt_expect); + if (written != server_pkt_len + server_pkt_expect) + fprintf(stderr, "Write to /dev/vhci failed\n"); + + free(server_pkt_data); + server_pkt_data = NULL; + } else { + memcpy(server_pkt_data + server_pkt_len, ptr, count); + server_pkt_len += count; + server_pkt_expect -= count; + count = 0; + } + } +} + +static void vhci_read_callback(int fd, uint32_t events, void *user_data) +{ + unsigned char buf[4096]; + ssize_t len, written; + + if (events & (EPOLLERR | EPOLLHUP)) + return; + + len = read(fd, buf, sizeof(buf)); + if (len < 0) + return; + + written = write(server_fd, buf, len); + if (written != len) + fprintf(stderr, "Write to server failed\n"); +} + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + } +} + +static int do_connect(const char *node, const char *service) +{ + struct addrinfo hints; + struct addrinfo *info, *res; + int err, fd = -1; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + err = getaddrinfo(node, service, &hints, &res); + if (err) { + perror(gai_strerror(err)); + exit(1); + } + + for (info = res; info; info = info->ai_next) { + char str[INET6_ADDRSTRLEN]; + + inet_ntop(info->ai_family, info->ai_addr->sa_data, + str, sizeof(str)); + + fd = socket(info->ai_family, info->ai_socktype, + info->ai_protocol); + if (fd < 0) + continue; + + printf("Trying to connect to %s on port %s\n", str, service); + + if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { + perror("Failed to connect"); + close(fd); + continue; + } + + printf("Successfully connected to %s on port %s\n", + str, service); + break; + } + + freeaddrinfo(res); + + if (res == NULL) + exit(1); + + return fd; +} + +int main(int argc, char *argv[]) +{ + const char sniff_cmd[] = { 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + char *server_port = NULL, *sniffer_port = NULL; + int ret = EXIT_FAILURE; + ssize_t written; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "s:p:vh", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'p': + server_port = set_port(optarg); + if (server_port == NULL) + goto usage; + + break; + case 's': + sniffer_port = set_port(optarg); + if (sniffer_port == NULL) + goto usage; + + break; + case 'v': + printf("%s\n", VERSION); + ret = EXIT_SUCCESS; + goto done; + case 'h': + ret = EXIT_SUCCESS; + goto usage; + default: + goto usage; + } + } + + argc = argc - optind; + argv = argv + optind; + optind = 0; + + if (argv[0] == NULL || argv[0][0] == '\0') + goto usage; + + server_fd = do_connect(argv[0], server_port ? : DEFAULT_HOST_PORT); + sniffer_fd = do_connect(argv[0], + sniffer_port ? : DEFAULT_SNIFFER_PORT); + + written = write(sniffer_fd, sniff_cmd, sizeof(sniff_cmd)); + if (written < 0) + perror("Failed to enable sniffer"); + + vhci_fd = open("/dev/vhci", O_RDWR | O_NONBLOCK); + if (vhci_fd < 0) { + perror("Failed to /dev/vhci"); + close(server_fd); + exit(1); + } + + mainloop_init(); + + mainloop_add_fd(sniffer_fd, EPOLLIN, sniffer_read_callback, NULL, NULL); + mainloop_add_fd(server_fd, EPOLLIN, server_read_callback, NULL, NULL); + mainloop_add_fd(vhci_fd, EPOLLIN, vhci_read_callback, NULL, NULL); + + ret = mainloop_run_with_signal(signal_callback, NULL); + + goto done; + +usage: + usage(); +done: + free(server_port); + free(sniffer_port); + + return ret; +} diff --git a/emulator/btdev.c b/emulator/btdev.c new file mode 100644 index 0000000..38d5b3b --- /dev/null +++ b/emulator/btdev.c @@ -0,0 +1,4115 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/util.h" +#include "src/shared/timeout.h" +#include "src/shared/crypto.h" +#include "src/shared/ecc.h" +#include "monitor/bt.h" +#include "btdev.h" + +#define has_bredr(btdev) (!((btdev)->features[4] & 0x20)) +#define has_le(btdev) (!!((btdev)->features[4] & 0x40)) + +struct hook { + btdev_hook_func handler; + void *user_data; + enum btdev_hook_type type; + uint16_t opcode; +}; + +#define MAX_HOOK_ENTRIES 16 + +struct btdev { + enum btdev_type type; + + struct btdev *conn; + + bool auth_init; + uint8_t link_key[16]; + uint16_t pin[16]; + uint8_t pin_len; + uint8_t io_cap; + uint8_t auth_req; + bool ssp_auth_complete; + uint8_t ssp_status; + + btdev_command_func command_handler; + void *command_data; + + btdev_send_func send_handler; + void *send_data; + + unsigned int inquiry_id; + unsigned int inquiry_timeout_id; + + struct hook *hook_list[MAX_HOOK_ENTRIES]; + + struct bt_crypto *crypto; + + uint16_t manufacturer; + uint8_t version; + uint16_t revision; + uint8_t commands[64]; + uint8_t max_page; + uint8_t features[8]; + uint8_t feat_page_2[8]; + uint16_t acl_mtu; + uint16_t acl_max_pkt; + uint8_t country_code; + uint8_t bdaddr[6]; + uint8_t random_addr[6]; + uint8_t le_features[8]; + uint8_t le_states[8]; + + uint16_t default_link_policy; + uint8_t event_mask[8]; + uint8_t event_mask_page2[8]; + uint8_t event_filter; + uint8_t name[248]; + uint8_t dev_class[3]; + uint16_t voice_setting; + uint16_t conn_accept_timeout; + uint16_t page_timeout; + uint8_t scan_enable; + uint16_t page_scan_interval; + uint16_t page_scan_window; + uint16_t page_scan_type; + uint8_t auth_enable; + uint16_t inquiry_scan_interval; + uint16_t inquiry_scan_window; + uint8_t inquiry_mode; + uint8_t afh_assessment_mode; + uint8_t ext_inquiry_fec; + uint8_t ext_inquiry_rsp[240]; + uint8_t simple_pairing_mode; + uint8_t ssp_debug_mode; + uint8_t secure_conn_support; + uint8_t host_flow_control; + uint8_t le_supported; + uint8_t le_simultaneous; + uint8_t le_event_mask[8]; + uint8_t le_adv_data[31]; + uint8_t le_adv_data_len; + uint8_t le_adv_type; + uint8_t le_adv_own_addr; + uint8_t le_adv_direct_addr_type; + uint8_t le_adv_direct_addr[6]; + uint8_t le_scan_data[31]; + uint8_t le_scan_data_len; + uint8_t le_scan_enable; + uint8_t le_scan_type; + uint8_t le_scan_own_addr_type; + uint8_t le_filter_dup; + uint8_t le_adv_enable; + uint8_t le_ltk[16]; + + uint8_t le_local_sk256[32]; + + uint16_t sync_train_interval; + uint32_t sync_train_timeout; + uint8_t sync_train_service_data; + + uint16_t le_ext_adv_type; +}; + +struct inquiry_data { + struct btdev *btdev; + int num_resp; + + int sent_count; + int iter; +}; + +#define DEFAULT_INQUIRY_INTERVAL 100 /* 100 miliseconds */ + +#define MAX_BTDEV_ENTRIES 16 + +static const uint8_t LINK_KEY_NONE[16] = { 0 }; +static const uint8_t LINK_KEY_DUMMY[16] = { 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 0, 1, 2, 3, 4, 5 }; + +static struct btdev *btdev_list[MAX_BTDEV_ENTRIES] = { }; + +static int get_hook_index(struct btdev *btdev, enum btdev_hook_type type, + uint16_t opcode) +{ + int i; + + for (i = 0; i < MAX_HOOK_ENTRIES; i++) { + if (btdev->hook_list[i] == NULL) + continue; + + if (btdev->hook_list[i]->type == type && + btdev->hook_list[i]->opcode == opcode) + return i; + } + + return -1; +} + +static bool run_hooks(struct btdev *btdev, enum btdev_hook_type type, + uint16_t opcode, const void *data, uint16_t len) +{ + int index = get_hook_index(btdev, type, opcode); + if (index < 0) + return true; + + return btdev->hook_list[index]->handler(data, len, + btdev->hook_list[index]->user_data); +} + +static inline int add_btdev(struct btdev *btdev) +{ + int i, index = -1; + + for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { + if (btdev_list[i] == NULL) { + index = i; + btdev_list[index] = btdev; + break; + } + } + + return index; +} + +static inline int del_btdev(struct btdev *btdev) +{ + int i, index = -1; + + for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { + if (btdev_list[i] == btdev) { + index = i; + btdev_list[index] = NULL; + break; + } + } + + return index; +} + +static inline struct btdev *find_btdev_by_bdaddr(const uint8_t *bdaddr) +{ + int i; + + for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { + if (btdev_list[i] && !memcmp(btdev_list[i]->bdaddr, bdaddr, 6)) + return btdev_list[i]; + } + + return NULL; +} + +static inline struct btdev *find_btdev_by_bdaddr_type(const uint8_t *bdaddr, + uint8_t bdaddr_type) +{ + int i; + + for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { + int cmp; + + if (!btdev_list[i]) + continue; + + if (bdaddr_type == 0x01) + cmp = memcmp(btdev_list[i]->random_addr, bdaddr, 6); + else + cmp = memcmp(btdev_list[i]->bdaddr, bdaddr, 6); + + if (!cmp) + return btdev_list[i]; + } + + return NULL; +} + +static void hexdump(const unsigned char *buf, uint16_t len) +{ + static const char hexdigits[] = "0123456789abcdef"; + char str[68]; + uint16_t i; + + if (!len) + return; + + for (i = 0; i < len; i++) { + str[((i % 16) * 3) + 0] = hexdigits[buf[i] >> 4]; + str[((i % 16) * 3) + 1] = hexdigits[buf[i] & 0xf]; + str[((i % 16) * 3) + 2] = ' '; + str[(i % 16) + 49] = isprint(buf[i]) ? buf[i] : '.'; + + if ((i + 1) % 16 == 0) { + str[47] = ' '; + str[48] = ' '; + str[65] = '\0'; + printf("%-12c%s\n", ' ', str); + str[0] = ' '; + } + } + + if (i % 16 > 0) { + uint16_t j; + for (j = (i % 16); j < 16; j++) { + str[(j * 3) + 0] = ' '; + str[(j * 3) + 1] = ' '; + str[(j * 3) + 2] = ' '; + str[j + 49] = ' '; + } + str[47] = ' '; + str[48] = ' '; + str[65] = '\0'; + printf("%-12c%s\n", ' ', str); + } +} + +static void get_bdaddr(uint16_t id, uint8_t index, uint8_t *bdaddr) +{ + bdaddr[0] = id & 0xff; + bdaddr[1] = id >> 8; + bdaddr[2] = index; + bdaddr[3] = 0x01; + bdaddr[4] = 0xaa; + bdaddr[5] = 0x00; +} + +static void set_common_commands_all(struct btdev *btdev) +{ + btdev->commands[5] |= 0x40; /* Set Event Mask */ + btdev->commands[5] |= 0x80; /* Reset */ + btdev->commands[14] |= 0x08; /* Read Local Version */ + btdev->commands[14] |= 0x10; /* Read Local Supported Commands */ + btdev->commands[14] |= 0x20; /* Read Local Supported Features */ + btdev->commands[14] |= 0x80; /* Read Buffer Size */ +} + +static void set_common_commands_bredrle(struct btdev *btdev) +{ + btdev->commands[0] |= 0x20; /* Disconnect */ + btdev->commands[2] |= 0x80; /* Read Remote Version Information */ + btdev->commands[10] |= 0x20; /* Set Host Flow Control */ + btdev->commands[10] |= 0x40; /* Host Buffer Size */ + btdev->commands[15] |= 0x02; /* Read BD ADDR */ +} + +static void set_common_commands_bredr20(struct btdev *btdev) +{ + btdev->commands[0] |= 0x01; /* Inquiry */ + btdev->commands[0] |= 0x02; /* Inquiry Cancel */ + btdev->commands[0] |= 0x10; /* Create Connection */ + btdev->commands[0] |= 0x40; /* Add SCO Connection */ + btdev->commands[0] |= 0x80; /* Cancel Create Connection */ + btdev->commands[1] |= 0x01; /* Accept Connection Request */ + btdev->commands[1] |= 0x02; /* Reject Connection Request */ + btdev->commands[1] |= 0x04; /* Link Key Request Reply */ + btdev->commands[1] |= 0x08; /* Link Key Request Negative Reply */ + btdev->commands[1] |= 0x10; /* PIN Code Request Reply */ + btdev->commands[1] |= 0x20; /* PIN Code Request Negative Reply */ + btdev->commands[1] |= 0x80; /* Authentication Requested */ + btdev->commands[2] |= 0x01; /* Set Connection Encryption */ + btdev->commands[2] |= 0x08; /* Remote Name Request */ + btdev->commands[2] |= 0x10; /* Cancel Remote Name Request */ + btdev->commands[2] |= 0x20; /* Read Remote Supported Features */ + btdev->commands[2] |= 0x40; /* Read Remote Extended Features */ + btdev->commands[3] |= 0x01; /* Read Clock Offset */ + btdev->commands[5] |= 0x08; /* Read Default Link Policy */ + btdev->commands[5] |= 0x10; /* Write Default Link Policy */ + btdev->commands[6] |= 0x01; /* Set Event Filter */ + btdev->commands[6] |= 0x20; /* Read Stored Link Key */ + btdev->commands[6] |= 0x40; /* Write Stored Link Key */ + btdev->commands[6] |= 0x80; /* Delete Stored Link Key */ + btdev->commands[7] |= 0x01; /* Write Local Name */ + btdev->commands[7] |= 0x02; /* Read Local Name */ + btdev->commands[7] |= 0x04; /* Read Connection Accept Timeout */ + btdev->commands[7] |= 0x08; /* Write Connection Accept Timeout */ + btdev->commands[7] |= 0x10; /* Read Page Timeout */ + btdev->commands[7] |= 0x20; /* Write Page Timeout */ + btdev->commands[7] |= 0x40; /* Read Scan Enable */ + btdev->commands[7] |= 0x80; /* Write Scan Enable */ + btdev->commands[8] |= 0x01; /* Read Page Scan Activity */ + btdev->commands[8] |= 0x02; /* Write Page Scan Activity */ + btdev->commands[8] |= 0x04; /* Read Inquiry Scan Activity */ + btdev->commands[8] |= 0x08; /* Write Inquiry Scan Activity */ + btdev->commands[8] |= 0x10; /* Read Authentication Enable */ + btdev->commands[8] |= 0x20; /* Write Authentication Enable */ + btdev->commands[9] |= 0x01; /* Read Class Of Device */ + btdev->commands[9] |= 0x02; /* Write Class Of Device */ + btdev->commands[9] |= 0x04; /* Read Voice Setting */ + btdev->commands[9] |= 0x08; /* Write Voice Setting */ + btdev->commands[11] |= 0x10; /* Write Current IAC LAP */ + btdev->commands[12] |= 0x40; /* Read Inquiry Mode */ + btdev->commands[12] |= 0x80; /* Write Inquiry Mode */ + btdev->commands[13] |= 0x01; /* Read Page Scan Type */ + btdev->commands[13] |= 0x02; /* Write Page Scan Type */ + btdev->commands[13] |= 0x04; /* Read AFH Assess Mode */ + btdev->commands[13] |= 0x08; /* Write AFH Assess Mode */ + btdev->commands[14] |= 0x40; /* Read Local Extended Features */ + btdev->commands[15] |= 0x01; /* Read Country Code */ + btdev->commands[16] |= 0x04; /* Enable Device Under Test Mode */ +} + +static void set_bredr_commands(struct btdev *btdev) +{ + set_common_commands_all(btdev); + set_common_commands_bredrle(btdev); + set_common_commands_bredr20(btdev); + + btdev->commands[16] |= 0x08; /* Setup Synchronous Connection */ + btdev->commands[17] |= 0x01; /* Read Extended Inquiry Response */ + btdev->commands[17] |= 0x02; /* Write Extended Inquiry Response */ + btdev->commands[17] |= 0x20; /* Read Simple Pairing Mode */ + btdev->commands[17] |= 0x40; /* Write Simple Pairing Mode */ + btdev->commands[17] |= 0x80; /* Read Local OOB Data */ + btdev->commands[18] |= 0x01; /* Read Inquiry Response TX Power */ + btdev->commands[18] |= 0x02; /* Write Inquiry Response TX Power */ + btdev->commands[18] |= 0x80; /* IO Capability Request Reply */ + btdev->commands[20] |= 0x10; /* Read Encryption Key Size */ + btdev->commands[23] |= 0x04; /* Read Data Block Size */ + btdev->commands[29] |= 0x20; /* Read Local Supported Codecs */ + btdev->commands[30] |= 0x08; /* Get MWS Transport Layer Config */ +} + +static void set_bredr20_commands(struct btdev *btdev) +{ + set_common_commands_all(btdev); + set_common_commands_bredrle(btdev); + set_common_commands_bredr20(btdev); +} + +static void set_le_50_commands(struct btdev *btdev) +{ + btdev->commands[36] |= 0x02; /* LE Set Adv Set Random Address */ + btdev->commands[36] |= 0x04; /* LE Set Ext Adv Parameters */ + btdev->commands[36] |= 0x08; /* LE Set Ext Adv Data */ + btdev->commands[36] |= 0x10; /* LE Set Ext Scan Response Data */ + btdev->commands[36] |= 0x20; /* LE Set Ext Adv Enable */ + btdev->commands[36] |= 0x40; /* LE Read Maximum Adv Data Length */ + btdev->commands[36] |= 0x80; /* LE Read Num of Supported Adv Sets */ + btdev->commands[37] |= 0x01; /* LE Remove Adv Set */ + btdev->commands[37] |= 0x02; /* LE Clear Adv Sets */ + btdev->commands[37] |= 0x04; /* LE Set Periodic Adv Parameters */ + btdev->commands[37] |= 0x08; /* LE Set Periodic Adv Data */ + btdev->commands[37] |= 0x10; /* LE Set Periodic Adv Enable */ + btdev->commands[37] |= 0x20; /* LE Set Ext Scan Parameters */ + btdev->commands[37] |= 0x40; /* LE Set Ext Scan Enable */ + btdev->commands[37] |= 0x80; /* LE Ext Create Connection */ + btdev->commands[38] |= 0x01; /* LE Periodic Adv Create Sync */ + btdev->commands[38] |= 0x02; /* LE Periodic Adv Create Sync Cancel */ + btdev->commands[38] |= 0x04; /* LE Periodic Adv Terminate Sync */ + btdev->commands[38] |= 0x08; /* LE Add Device To Periodic Adv List */ + btdev->commands[38] |= 0x10; /* LE Remove Periodic Adv List */ + btdev->commands[38] |= 0x20; /* LE Clear Periodic Adv List */ + btdev->commands[38] |= 0x40; /* LE Read Periodic Adv List Size */ +} + +static void set_le_commands(struct btdev *btdev) +{ + set_common_commands_all(btdev); + set_common_commands_bredrle(btdev); + + btdev->commands[24] |= 0x20; /* Read LE Host Supported */ + btdev->commands[24] |= 0x20; /* Write LE Host Supported */ + btdev->commands[25] |= 0x01; /* LE Set Event Mask */ + btdev->commands[25] |= 0x02; /* LE Read Buffer Size */ + btdev->commands[25] |= 0x04; /* LE Read Local Features */ + btdev->commands[25] |= 0x10; /* LE Set Random Address */ + btdev->commands[25] |= 0x20; /* LE Set Adv Parameters */ + btdev->commands[25] |= 0x40; /* LE Read Adv TX Power */ + btdev->commands[25] |= 0x80; /* LE Set Adv Data */ + btdev->commands[26] |= 0x01; /* LE Set Scan Response Data */ + btdev->commands[26] |= 0x02; /* LE Set Adv Enable */ + btdev->commands[26] |= 0x04; /* LE Set Scan Parameters */ + btdev->commands[26] |= 0x08; /* LE Set Scan Enable */ + btdev->commands[26] |= 0x10; /* LE Create Connection */ + btdev->commands[26] |= 0x40; /* LE Read White List Size */ + btdev->commands[26] |= 0x80; /* LE Clear White List */ + btdev->commands[27] |= 0x04; /* LE Connection Update */ + btdev->commands[27] |= 0x20; /* LE Read Remote Used Features */ + btdev->commands[27] |= 0x40; /* LE Encrypt */ + btdev->commands[27] |= 0x80; /* LE Rand */ + btdev->commands[28] |= 0x01; /* LE Start Encryption */ + btdev->commands[28] |= 0x02; /* LE Long Term Key Request Reply */ + btdev->commands[28] |= 0x04; /* LE Long Term Key Request Neg Reply */ + btdev->commands[28] |= 0x08; /* LE Read Supported States */ + btdev->commands[28] |= 0x10; /* LE Receiver Test */ + btdev->commands[28] |= 0x20; /* LE Transmitter Test */ + btdev->commands[28] |= 0x40; /* LE Test End */ + + /* Extra LE commands for >= 4.1 adapters */ + btdev->commands[33] |= 0x10; /* LE Remote Conn Param Req Reply */ + btdev->commands[33] |= 0x20; /* LE Remote Conn Param Req Neg Reply */ + + /* Extra LE commands for >= 4.2 adapters */ + btdev->commands[34] |= 0x02; /* LE Read Local P-256 Public Key */ + btdev->commands[34] |= 0x04; /* LE Generate DHKey */ + + /* Extra LE commands for >= 5.0 adapters */ + if (btdev->type >= BTDEV_TYPE_BREDRLE50) + set_le_50_commands(btdev); +} + +static void set_bredrle_commands(struct btdev *btdev) +{ + set_bredr_commands(btdev); + set_le_commands(btdev); + + /* Extra BR/EDR commands we want to only support for >= 4.0 + * adapters. + */ + btdev->commands[22] |= 0x04; /* Set Event Mask Page 2 */ + btdev->commands[31] |= 0x80; /* Read Sync Train Parameters */ + btdev->commands[32] |= 0x04; /* Read Secure Connections Support */ + btdev->commands[32] |= 0x08; /* Write Secure Connections Support */ + btdev->commands[32] |= 0x10; /* Read Auth Payload Timeout */ + btdev->commands[32] |= 0x20; /* Write Auth Payload Timeout */ + btdev->commands[32] |= 0x40; /* Read Local OOB Extended Data */ +} + +static void set_amp_commands(struct btdev *btdev) +{ + set_common_commands_all(btdev); + + btdev->commands[22] |= 0x20; /* Read Local AMP Info */ +} + +static void set_bredrle_features(struct btdev *btdev) +{ + btdev->features[0] |= 0x04; /* Encryption */ + btdev->features[0] |= 0x20; /* Role switch */ + btdev->features[0] |= 0x80; /* Sniff mode */ + btdev->features[1] |= 0x08; /* SCO link */ + btdev->features[2] |= 0x08; /* Transparent SCO */ + btdev->features[3] |= 0x40; /* RSSI with inquiry results */ + btdev->features[3] |= 0x80; /* Extended SCO link */ + btdev->features[4] |= 0x08; /* AFH capable slave */ + btdev->features[4] |= 0x10; /* AFH classification slave */ + btdev->features[4] |= 0x40; /* LE Supported */ + btdev->features[5] |= 0x02; /* Sniff subrating */ + btdev->features[5] |= 0x04; /* Pause encryption */ + btdev->features[5] |= 0x08; /* AFH capable master */ + btdev->features[5] |= 0x10; /* AFH classification master */ + btdev->features[6] |= 0x01; /* Extended Inquiry Response */ + btdev->features[6] |= 0x02; /* Simultaneous LE and BR/EDR */ + btdev->features[6] |= 0x08; /* Secure Simple Pairing */ + btdev->features[6] |= 0x10; /* Encapsulated PDU */ + btdev->features[6] |= 0x20; /* Erroneous Data Reporting */ + btdev->features[6] |= 0x40; /* Non-flushable Packet Boundary Flag */ + btdev->features[7] |= 0x01; /* Link Supervision Timeout Event */ + btdev->features[7] |= 0x02; /* Inquiry TX Power Level */ + btdev->features[7] |= 0x80; /* Extended features */ + + if (btdev->type >= BTDEV_TYPE_BREDRLE50) { + /* These BREDR features are added to test new configuration + * command. If this is added above it will break existing tests + */ + btdev->features[0] |= 0x01; /* 3 slot Packets */ + btdev->features[0] |= 0x02; /* 5 slot Packets */ + btdev->features[3] |= 0x02; /* EDR ACL 2M mode */ + btdev->features[3] |= 0x04; /* EDR ACL 3M mode */ + btdev->features[4] |= 0x80; /* 3 slot EDR ACL packets */ + btdev->features[5] |= 0x01; /* 5 slot EDR ACL packets */ + + btdev->le_features[1] |= 0x01; /* LE 2M PHY */ + btdev->le_features[1] |= 0x08; /* LE Coded PHY */ + btdev->le_features[1] |= 0x10; /* LE EXT ADV */ + } + + btdev->feat_page_2[0] |= 0x01; /* CSB - Master Operation */ + btdev->feat_page_2[0] |= 0x02; /* CSB - Slave Operation */ + btdev->feat_page_2[0] |= 0x04; /* Synchronization Train */ + btdev->feat_page_2[0] |= 0x08; /* Synchronization Scan */ + btdev->feat_page_2[0] |= 0x10; /* Inquiry Response Notification */ + btdev->feat_page_2[1] |= 0x01; /* Secure Connections */ + btdev->feat_page_2[1] |= 0x02; /* Ping */ + + btdev->max_page = 2; +} + +static void set_bredr_features(struct btdev *btdev) +{ + btdev->features[0] |= 0x04; /* Encryption */ + btdev->features[0] |= 0x20; /* Role switch */ + btdev->features[0] |= 0x80; /* Sniff mode */ + btdev->features[1] |= 0x08; /* SCO link */ + btdev->features[3] |= 0x40; /* RSSI with inquiry results */ + btdev->features[3] |= 0x80; /* Extended SCO link */ + btdev->features[4] |= 0x08; /* AFH capable slave */ + btdev->features[4] |= 0x10; /* AFH classification slave */ + btdev->features[5] |= 0x02; /* Sniff subrating */ + btdev->features[5] |= 0x04; /* Pause encryption */ + btdev->features[5] |= 0x08; /* AFH capable master */ + btdev->features[5] |= 0x10; /* AFH classification master */ + btdev->features[6] |= 0x01; /* Extended Inquiry Response */ + btdev->features[6] |= 0x08; /* Secure Simple Pairing */ + btdev->features[6] |= 0x10; /* Encapsulated PDU */ + btdev->features[6] |= 0x20; /* Erroneous Data Reporting */ + btdev->features[6] |= 0x40; /* Non-flushable Packet Boundary Flag */ + btdev->features[7] |= 0x01; /* Link Supervision Timeout Event */ + btdev->features[7] |= 0x02; /* Inquiry TX Power Level */ + btdev->features[7] |= 0x80; /* Extended features */ + + btdev->max_page = 1; +} + +static void set_bredr20_features(struct btdev *btdev) +{ + btdev->features[0] |= 0x04; /* Encryption */ + btdev->features[0] |= 0x20; /* Role switch */ + btdev->features[0] |= 0x80; /* Sniff mode */ + btdev->features[1] |= 0x08; /* SCO link */ + btdev->features[3] |= 0x40; /* RSSI with inquiry results */ + btdev->features[3] |= 0x80; /* Extended SCO link */ + btdev->features[4] |= 0x08; /* AFH capable slave */ + btdev->features[4] |= 0x10; /* AFH classification slave */ + btdev->features[5] |= 0x02; /* Sniff subrating */ + btdev->features[5] |= 0x04; /* Pause encryption */ + btdev->features[5] |= 0x08; /* AFH capable master */ + btdev->features[5] |= 0x10; /* AFH classification master */ + btdev->features[7] |= 0x80; /* Extended features */ + + btdev->max_page = 1; +} + +static void set_le_features(struct btdev *btdev) +{ + btdev->features[4] |= 0x20; /* BR/EDR Not Supported */ + btdev->features[4] |= 0x40; /* LE Supported */ + + btdev->max_page = 1; + + btdev->le_features[0] |= 0x01; /* LE Encryption */ + btdev->le_features[0] |= 0x02; /* Connection Parameters Request */ + btdev->le_features[0] |= 0x08; /* Slave-initiated Features Exchange */ +} + +static void set_le_states(struct btdev *btdev) +{ + /* Set all 41 bits as per Bluetooth 5.0 specification */ + btdev->le_states[0] = 0xff; + btdev->le_states[1] = 0xff; + btdev->le_states[2] = 0xff; + btdev->le_states[3] = 0xff; + btdev->le_states[4] = 0xff; + btdev->le_states[5] = 0x03; +} + +static void set_amp_features(struct btdev *btdev) +{ +} + +struct btdev *btdev_create(enum btdev_type type, uint16_t id) +{ + struct btdev *btdev; + int index; + + btdev = malloc(sizeof(*btdev)); + if (!btdev) + return NULL; + + memset(btdev, 0, sizeof(*btdev)); + + if (type == BTDEV_TYPE_BREDRLE || type == BTDEV_TYPE_LE + || type == BTDEV_TYPE_BREDRLE50) { + btdev->crypto = bt_crypto_new(); + if (!btdev->crypto) { + free(btdev); + return NULL; + } + } + + btdev->type = type; + + btdev->manufacturer = 63; + btdev->revision = 0x0000; + + switch (btdev->type) { + case BTDEV_TYPE_BREDRLE: + case BTDEV_TYPE_BREDRLE50: + btdev->version = 0x09; + set_bredrle_features(btdev); + set_bredrle_commands(btdev); + set_le_states(btdev); + break; + case BTDEV_TYPE_BREDR: + btdev->version = 0x05; + set_bredr_features(btdev); + set_bredr_commands(btdev); + break; + case BTDEV_TYPE_LE: + btdev->version = 0x09; + set_le_features(btdev); + set_le_commands(btdev); + set_le_states(btdev); + break; + case BTDEV_TYPE_AMP: + btdev->version = 0x01; + set_amp_features(btdev); + set_amp_commands(btdev); + break; + case BTDEV_TYPE_BREDR20: + btdev->version = 0x03; + set_bredr20_features(btdev); + set_bredr20_commands(btdev); + break; + } + + btdev->page_scan_interval = 0x0800; + btdev->page_scan_window = 0x0012; + btdev->page_scan_type = 0x00; + + btdev->sync_train_interval = 0x0080; + btdev->sync_train_timeout = 0x0002ee00; + btdev->sync_train_service_data = 0x00; + + btdev->acl_mtu = 192; + btdev->acl_max_pkt = 1; + + btdev->country_code = 0x00; + + index = add_btdev(btdev); + if (index < 0) { + bt_crypto_unref(btdev->crypto); + free(btdev); + return NULL; + } + + get_bdaddr(id, index, btdev->bdaddr); + + return btdev; +} + +void btdev_destroy(struct btdev *btdev) +{ + if (!btdev) + return; + + if (btdev->inquiry_id > 0) + timeout_remove(btdev->inquiry_id); + + bt_crypto_unref(btdev->crypto); + del_btdev(btdev); + + free(btdev); +} + +const uint8_t *btdev_get_bdaddr(struct btdev *btdev) +{ + return btdev->bdaddr; +} + +uint8_t *btdev_get_features(struct btdev *btdev) +{ + return btdev->features; +} + +uint8_t btdev_get_scan_enable(struct btdev *btdev) +{ + return btdev->scan_enable; +} + +uint8_t btdev_get_le_scan_enable(struct btdev *btdev) +{ + return btdev->le_scan_enable; +} + +void btdev_set_le_states(struct btdev *btdev, const uint8_t *le_states) +{ + memcpy(btdev->le_states, le_states, sizeof(btdev->le_states)); +} + +static bool use_ssp(struct btdev *btdev1, struct btdev *btdev2) +{ + if (btdev1->auth_enable || btdev2->auth_enable) + return false; + + return (btdev1->simple_pairing_mode && btdev2->simple_pairing_mode); +} + +void btdev_set_command_handler(struct btdev *btdev, btdev_command_func handler, + void *user_data) +{ + if (!btdev) + return; + + btdev->command_handler = handler; + btdev->command_data = user_data; +} + +void btdev_set_send_handler(struct btdev *btdev, btdev_send_func handler, + void *user_data) +{ + if (!btdev) + return; + + btdev->send_handler = handler; + btdev->send_data = user_data; +} + +static void send_packet(struct btdev *btdev, const struct iovec *iov, + int iovlen) +{ + if (!btdev->send_handler) + return; + + btdev->send_handler(iov, iovlen, btdev->send_data); +} + +static void send_event(struct btdev *btdev, uint8_t event, + const void *data, uint8_t len) +{ + struct bt_hci_evt_hdr hdr; + struct iovec iov[3]; + uint8_t pkt = BT_H4_EVT_PKT; + + iov[0].iov_base = &pkt; + iov[0].iov_len = sizeof(pkt); + + hdr.evt = event; + hdr.plen = len; + + iov[1].iov_base = &hdr; + iov[1].iov_len = sizeof(hdr); + + if (len > 0) { + iov[2].iov_base = (void *) data; + iov[2].iov_len = len; + } + + if (run_hooks(btdev, BTDEV_HOOK_POST_EVT, event, data, len)) + send_packet(btdev, iov, len > 0 ? 3 : 2); +} + +static void send_cmd(struct btdev *btdev, uint8_t evt, uint16_t opcode, + const struct iovec *iov, int iovlen) +{ + struct bt_hci_evt_hdr hdr; + struct iovec iov2[2 + iovlen]; + uint8_t pkt = BT_H4_EVT_PKT; + int i; + + iov2[0].iov_base = &pkt; + iov2[0].iov_len = sizeof(pkt); + + hdr.evt = evt; + hdr.plen = 0; + + iov2[1].iov_base = &hdr; + iov2[1].iov_len = sizeof(hdr); + + for (i = 0; i < iovlen; i++) { + hdr.plen += iov[i].iov_len; + iov2[2 + i].iov_base = iov[i].iov_base; + iov2[2 + i].iov_len = iov[i].iov_len; + } + + if (run_hooks(btdev, BTDEV_HOOK_POST_CMD, opcode, iov[i -1].iov_base, + iov[i -1].iov_len)) + send_packet(btdev, iov2, 2 + iovlen); +} + +static void cmd_complete(struct btdev *btdev, uint16_t opcode, + const void *data, uint8_t len) +{ + struct bt_hci_evt_cmd_complete cc; + struct iovec iov[2]; + + cc.ncmd = 0x01; + cc.opcode = cpu_to_le16(opcode); + + iov[0].iov_base = &cc; + iov[0].iov_len = sizeof(cc); + + iov[1].iov_base = (void *) data; + iov[1].iov_len = len; + + send_cmd(btdev, BT_HCI_EVT_CMD_COMPLETE, opcode, iov, 2); +} + +static void cmd_status(struct btdev *btdev, uint8_t status, uint16_t opcode) +{ + struct bt_hci_evt_cmd_status cs; + struct iovec iov; + + cs.status = status; + cs.ncmd = 0x01; + cs.opcode = cpu_to_le16(opcode); + + iov.iov_base = &cs; + iov.iov_len = sizeof(cs); + + send_cmd(btdev, BT_HCI_EVT_CMD_STATUS, opcode, &iov, 1); +} + +static void le_meta_event(struct btdev *btdev, uint8_t event, + void *data, uint8_t len) +{ + void *pkt_data; + + pkt_data = alloca(1 + len); + if (!pkt_data) + return; + + ((uint8_t *) pkt_data)[0] = event; + + if (len > 0) + memcpy(pkt_data + 1, data, len); + + send_event(btdev, BT_HCI_EVT_LE_META_EVENT, pkt_data, 1 + len); +} + +static void num_completed_packets(struct btdev *btdev) +{ + if (btdev->conn) { + struct bt_hci_evt_num_completed_packets ncp; + + ncp.num_handles = 1; + ncp.handle = cpu_to_le16(42); + ncp.count = cpu_to_le16(1); + + send_event(btdev, BT_HCI_EVT_NUM_COMPLETED_PACKETS, + &ncp, sizeof(ncp)); + } +} + +static bool inquiry_callback(void *user_data) +{ + struct inquiry_data *data = user_data; + struct btdev *btdev = data->btdev; + struct bt_hci_evt_inquiry_complete ic; + int sent = data->sent_count; + int i; + + /*Report devices only once and wait for inquiry timeout*/ + if (data->iter == MAX_BTDEV_ENTRIES) + return true; + + for (i = data->iter; i < MAX_BTDEV_ENTRIES; i++) { + /*Lets sent 10 inquiry results at once */ + if (sent + 10 == data->sent_count) + break; + + if (!btdev_list[i] || btdev_list[i] == btdev) + continue; + + if (!(btdev_list[i]->scan_enable & 0x02)) + continue; + + if (btdev->inquiry_mode == 0x02 && + btdev_list[i]->ext_inquiry_rsp[0]) { + struct bt_hci_evt_ext_inquiry_result ir; + + ir.num_resp = 0x01; + memcpy(ir.bdaddr, btdev_list[i]->bdaddr, 6); + ir.pscan_rep_mode = 0x00; + ir.pscan_period_mode = 0x00; + memcpy(ir.dev_class, btdev_list[i]->dev_class, 3); + ir.clock_offset = 0x0000; + ir.rssi = -60; + memcpy(ir.data, btdev_list[i]->ext_inquiry_rsp, 240); + + send_event(btdev, BT_HCI_EVT_EXT_INQUIRY_RESULT, + &ir, sizeof(ir)); + data->sent_count++; + continue; + } + + if (btdev->inquiry_mode > 0x00) { + struct bt_hci_evt_inquiry_result_with_rssi ir; + + ir.num_resp = 0x01; + memcpy(ir.bdaddr, btdev_list[i]->bdaddr, 6); + ir.pscan_rep_mode = 0x00; + ir.pscan_period_mode = 0x00; + memcpy(ir.dev_class, btdev_list[i]->dev_class, 3); + ir.clock_offset = 0x0000; + ir.rssi = -60; + + send_event(btdev, BT_HCI_EVT_INQUIRY_RESULT_WITH_RSSI, + &ir, sizeof(ir)); + data->sent_count++; + } else { + struct bt_hci_evt_inquiry_result ir; + + ir.num_resp = 0x01; + memcpy(ir.bdaddr, btdev_list[i]->bdaddr, 6); + ir.pscan_rep_mode = 0x00; + ir.pscan_period_mode = 0x00; + ir.pscan_mode = 0x00; + memcpy(ir.dev_class, btdev_list[i]->dev_class, 3); + ir.clock_offset = 0x0000; + + send_event(btdev, BT_HCI_EVT_INQUIRY_RESULT, + &ir, sizeof(ir)); + data->sent_count++; + } + } + data->iter = i; + + /* Check if we sent already required amount of responses*/ + if (data->num_resp && data->sent_count == data->num_resp) + goto finish; + + return true; + +finish: + /* Note that destroy will be called */ + ic.status = BT_HCI_ERR_SUCCESS; + send_event(btdev, BT_HCI_EVT_INQUIRY_COMPLETE, &ic, sizeof(ic)); + + return false; +} + +static void inquiry_destroy(void *user_data) +{ + struct inquiry_data *data = user_data; + struct btdev *btdev = data->btdev; + + if (!btdev) + goto finish; + + btdev->inquiry_id = 0; + + if (btdev->inquiry_timeout_id > 0) { + timeout_remove(btdev->inquiry_timeout_id); + btdev->inquiry_timeout_id = 0; + } + +finish: + free(data); +} + +static bool inquiry_timeout(void *user_data) +{ + struct inquiry_data *data = user_data; + struct btdev *btdev = data->btdev; + struct bt_hci_evt_inquiry_complete ic; + + timeout_remove(btdev->inquiry_id); + btdev->inquiry_timeout_id = 0; + + /* Inquiry is stopped, send Inquiry complete event. */ + ic.status = BT_HCI_ERR_SUCCESS; + send_event(btdev, BT_HCI_EVT_INQUIRY_COMPLETE, &ic, sizeof(ic)); + + return false; +} + +static void inquiry_cmd(struct btdev *btdev, const void *cmd) +{ + const struct bt_hci_cmd_inquiry *inq_cmd = cmd; + struct inquiry_data *data; + struct bt_hci_evt_inquiry_complete ic; + int status = BT_HCI_ERR_HARDWARE_FAILURE; + unsigned int inquiry_len_ms; + + if (btdev->inquiry_id > 0) { + status = BT_HCI_ERR_COMMAND_DISALLOWED; + goto failed; + } + + data = malloc(sizeof(*data)); + if (!data) + goto failed; + + memset(data, 0, sizeof(*data)); + data->btdev = btdev; + data->num_resp = inq_cmd->num_resp; + + /* Add timeout to cancel inquiry */ + inquiry_len_ms = 1280 * inq_cmd->length; + if (inquiry_len_ms) + btdev->inquiry_timeout_id = timeout_add(inquiry_len_ms, + inquiry_timeout, + data, NULL); + + btdev->inquiry_id = timeout_add(DEFAULT_INQUIRY_INTERVAL, + inquiry_callback, data, + inquiry_destroy); + /* Return if success */ + if (btdev->inquiry_id > 0) + return; + +failed: + ic.status = status; + send_event(btdev, BT_HCI_EVT_INQUIRY_COMPLETE, &ic, sizeof(ic)); +} + +static void inquiry_cancel(struct btdev *btdev) +{ + uint8_t status; + + if (!btdev->inquiry_id) { + status = BT_HCI_ERR_COMMAND_DISALLOWED; + cmd_complete(btdev, BT_HCI_CMD_INQUIRY_CANCEL, &status, + sizeof(status)); + return; + } + + timeout_remove(btdev->inquiry_timeout_id); + btdev->inquiry_timeout_id = 0; + timeout_remove(btdev->inquiry_id); + btdev->inquiry_id = 0; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, BT_HCI_CMD_INQUIRY_CANCEL, &status, + sizeof(status)); +} + +static void conn_complete(struct btdev *btdev, + const uint8_t *bdaddr, uint8_t status) +{ + struct bt_hci_evt_conn_complete cc; + + if (!status) { + struct btdev *remote = find_btdev_by_bdaddr(bdaddr); + + btdev->conn = remote; + remote->conn = btdev; + + cc.status = status; + memcpy(cc.bdaddr, btdev->bdaddr, 6); + cc.encr_mode = 0x00; + + cc.handle = cpu_to_le16(42); + cc.link_type = 0x01; + + send_event(remote, BT_HCI_EVT_CONN_COMPLETE, &cc, sizeof(cc)); + + cc.handle = cpu_to_le16(42); + cc.link_type = 0x01; + } else { + cc.handle = cpu_to_le16(0x0000); + cc.link_type = 0x01; + } + + cc.status = status; + memcpy(cc.bdaddr, bdaddr, 6); + cc.encr_mode = 0x00; + + send_event(btdev, BT_HCI_EVT_CONN_COMPLETE, &cc, sizeof(cc)); +} + +static void accept_conn_request_complete(struct btdev *btdev, + const uint8_t *bdaddr) +{ + struct btdev *remote = find_btdev_by_bdaddr(bdaddr); + + if (!remote) + return; + + if (btdev->auth_enable || remote->auth_enable) + send_event(remote, BT_HCI_EVT_LINK_KEY_REQUEST, + btdev->bdaddr, 6); + else + conn_complete(btdev, bdaddr, BT_HCI_ERR_SUCCESS); +} + +static void sync_conn_complete(struct btdev *btdev, uint16_t voice_setting, + uint8_t status) +{ + struct bt_hci_evt_sync_conn_complete cc; + + if (!btdev->conn) + return; + + cc.status = status; + memcpy(cc.bdaddr, btdev->conn->bdaddr, 6); + + cc.handle = cpu_to_le16(status == BT_HCI_ERR_SUCCESS ? 257 : 0); + cc.link_type = 0x02; + cc.tx_interval = 0x000c; + cc.retrans_window = 0x06; + cc.rx_pkt_len = 60; + cc.tx_pkt_len = 60; + cc.air_mode = (voice_setting == 0x0060) ? 0x02 : 0x03; + + send_event(btdev, BT_HCI_EVT_SYNC_CONN_COMPLETE, &cc, sizeof(cc)); +} + +static void sco_conn_complete(struct btdev *btdev, uint8_t status) +{ + struct bt_hci_evt_conn_complete cc; + + if (!btdev->conn) + return; + + cc.status = status; + memcpy(cc.bdaddr, btdev->conn->bdaddr, 6); + cc.handle = cpu_to_le16(status == BT_HCI_ERR_SUCCESS ? 257 : 0); + cc.link_type = 0x00; + cc.encr_mode = 0x00; + + send_event(btdev, BT_HCI_EVT_CONN_COMPLETE, &cc, sizeof(cc)); +} + +static void le_conn_complete(struct btdev *btdev, + const struct bt_hci_cmd_le_create_conn *lecc, + uint8_t status) +{ + char buf[1 + sizeof(struct bt_hci_evt_le_conn_complete)]; + struct bt_hci_evt_le_conn_complete *cc = (void *) &buf[1]; + + memset(buf, 0, sizeof(buf)); + + buf[0] = BT_HCI_EVT_LE_CONN_COMPLETE; + + if (!status) { + struct btdev *remote; + + remote = find_btdev_by_bdaddr_type(lecc->peer_addr, + lecc->peer_addr_type); + + btdev->conn = remote; + btdev->le_adv_enable = 0; + remote->conn = btdev; + remote->le_adv_enable = 0; + + cc->status = status; + cc->peer_addr_type = btdev->le_scan_own_addr_type; + if (cc->peer_addr_type == 0x01) + memcpy(cc->peer_addr, btdev->random_addr, 6); + else + memcpy(cc->peer_addr, btdev->bdaddr, 6); + + cc->role = 0x01; + cc->handle = cpu_to_le16(42); + cc->interval = lecc->max_interval; + cc->latency = lecc->latency; + cc->supv_timeout = lecc->supv_timeout; + + send_event(remote, BT_HCI_EVT_LE_META_EVENT, buf, sizeof(buf)); + } + + cc->status = status; + cc->peer_addr_type = lecc->peer_addr_type; + memcpy(cc->peer_addr, lecc->peer_addr, 6); + cc->role = 0x00; + + send_event(btdev, BT_HCI_EVT_LE_META_EVENT, buf, sizeof(buf)); +} + +static void le_ext_conn_complete(struct btdev *btdev, + const struct bt_hci_cmd_le_ext_create_conn *leecc, + uint8_t status) +{ + char buf[1 + sizeof(struct bt_hci_evt_le_enhanced_conn_complete)]; + struct bt_hci_evt_le_enhanced_conn_complete *cc = (void *) &buf[1]; + struct bt_hci_le_ext_create_conn *lecc = (void *)leecc->data; + + memset(buf, 0, sizeof(buf)); + + buf[0] = BT_HCI_EVT_LE_ENHANCED_CONN_COMPLETE; + + if (!status) { + struct btdev *remote; + + remote = find_btdev_by_bdaddr_type(leecc->peer_addr, + leecc->peer_addr_type); + + btdev->conn = remote; + btdev->le_adv_enable = 0; + remote->conn = btdev; + remote->le_adv_enable = 0; + + cc->status = status; + cc->peer_addr_type = btdev->le_scan_own_addr_type; + if (cc->peer_addr_type == 0x01) + memcpy(cc->peer_addr, btdev->random_addr, 6); + else + memcpy(cc->peer_addr, btdev->bdaddr, 6); + + cc->role = 0x01; + cc->handle = cpu_to_le16(42); + cc->interval = lecc->max_interval; + cc->latency = lecc->latency; + cc->supv_timeout = lecc->supv_timeout; + + send_event(remote, BT_HCI_EVT_LE_META_EVENT, buf, sizeof(buf)); + } + + cc->status = status; + cc->peer_addr_type = leecc->peer_addr_type; + memcpy(cc->peer_addr, leecc->peer_addr, 6); + cc->role = 0x00; + + send_event(btdev, BT_HCI_EVT_LE_META_EVENT, buf, sizeof(buf)); +} + +static const uint8_t *scan_addr(const struct btdev *btdev) +{ + if (btdev->le_scan_own_addr_type == 0x01) + return btdev->random_addr; + + return btdev->bdaddr; +} + +static const uint8_t *adv_addr(const struct btdev *btdev) +{ + if (btdev->le_adv_own_addr == 0x01) + return btdev->random_addr; + + return btdev->bdaddr; +} + +static bool adv_match(struct btdev *scan, struct btdev *adv) +{ + /* Match everything if this is not directed advertising */ + if (adv->le_adv_type != 0x01 && adv->le_adv_type != 0x04) + return true; + + if (scan->le_scan_own_addr_type != adv->le_adv_direct_addr_type) + return false; + + return !memcmp(scan_addr(scan), adv->le_adv_direct_addr, 6); +} + +static bool ext_adv_match(struct btdev *scan, struct btdev *adv) +{ + /* Match everything if this is not directed advertising */ + if (!(adv->le_ext_adv_type & 0x04)) + return true; + + if (scan->le_scan_own_addr_type != adv->le_adv_direct_addr_type) + return false; + + return !memcmp(scan_addr(scan), adv->le_adv_direct_addr, 6); +} + +static bool adv_connectable(struct btdev *btdev) +{ + if (!btdev->le_adv_enable) + return false; + + return btdev->le_adv_type != 0x03; +} + +static bool ext_adv_connectable(struct btdev *btdev) +{ + if (!btdev->le_adv_enable) + return false; + + return btdev->le_ext_adv_type & 0x01; +} + +static void le_conn_request(struct btdev *btdev, + const struct bt_hci_cmd_le_create_conn *lecc) +{ + struct btdev *remote = find_btdev_by_bdaddr_type(lecc->peer_addr, + lecc->peer_addr_type); + + if (remote && adv_connectable(remote) && adv_match(btdev, remote) && + remote->le_adv_own_addr == lecc->peer_addr_type) + le_conn_complete(btdev, lecc, 0); + else + le_conn_complete(btdev, lecc, + BT_HCI_ERR_CONN_FAILED_TO_ESTABLISH); +} + +static void le_ext_conn_request(struct btdev *btdev, + const struct bt_hci_cmd_le_ext_create_conn *leecc) +{ + struct btdev *remote = find_btdev_by_bdaddr_type(leecc->peer_addr, + leecc->peer_addr_type); + + if (remote && ext_adv_connectable(remote) && + ext_adv_match(btdev, remote) && + remote->le_adv_own_addr == leecc->peer_addr_type) + le_ext_conn_complete(btdev, leecc, 0); + else + le_ext_conn_complete(btdev, leecc, + BT_HCI_ERR_CONN_FAILED_TO_ESTABLISH); +} + +static void conn_request(struct btdev *btdev, const uint8_t *bdaddr) +{ + struct btdev *remote = find_btdev_by_bdaddr(bdaddr); + + if (remote && remote->scan_enable & 0x02) { + struct bt_hci_evt_conn_request cr; + + memcpy(cr.bdaddr, btdev->bdaddr, 6); + memcpy(cr.dev_class, btdev->dev_class, 3); + cr.link_type = 0x01; + + send_event(remote, BT_HCI_EVT_CONN_REQUEST, &cr, sizeof(cr)); + } else { + conn_complete(btdev, bdaddr, BT_HCI_ERR_PAGE_TIMEOUT); + } +} + +static void rej_le_conn_update(struct btdev *btdev, uint16_t handle, + uint8_t reason) +{ + struct btdev *remote = btdev->conn; + struct __packed { + uint8_t subevent; + struct bt_hci_evt_le_conn_update_complete ev; + } ev; + + if (!remote) + return; + + ev.subevent = BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE; + ev.ev.handle = cpu_to_le16(handle); + ev.ev.status = cpu_to_le16(reason); + + send_event(remote, BT_HCI_EVT_LE_META_EVENT, &ev, sizeof(ev)); +} + +static void le_conn_update(struct btdev *btdev, uint16_t handle, + uint16_t min_interval, uint16_t max_interval, + uint16_t latency, uint16_t supv_timeout, + uint16_t min_length, uint16_t max_length) +{ + struct btdev *remote = btdev->conn; + struct __packed { + uint8_t subevent; + struct bt_hci_evt_le_conn_update_complete ev; + } ev; + + ev.subevent = BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE; + ev.ev.handle = cpu_to_le16(handle); + ev.ev.interval = cpu_to_le16(min_interval); + ev.ev.latency = cpu_to_le16(latency); + ev.ev.supv_timeout = cpu_to_le16(supv_timeout); + + if (remote) + ev.ev.status = BT_HCI_ERR_SUCCESS; + else + ev.ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + + send_event(btdev, BT_HCI_EVT_LE_META_EVENT, &ev, sizeof(ev)); + + if (remote) + send_event(remote, BT_HCI_EVT_LE_META_EVENT, &ev, sizeof(ev)); +} + +static void le_conn_param_req(struct btdev *btdev, uint16_t handle, + uint16_t min_interval, uint16_t max_interval, + uint16_t latency, uint16_t supv_timeout, + uint16_t min_length, uint16_t max_length) +{ + struct btdev *remote = btdev->conn; + struct __packed { + uint8_t subevent; + struct bt_hci_evt_le_conn_param_request ev; + } ev; + + if (!remote) + return; + + ev.subevent = BT_HCI_EVT_LE_CONN_PARAM_REQUEST; + ev.ev.handle = cpu_to_le16(handle); + ev.ev.min_interval = cpu_to_le16(min_interval); + ev.ev.max_interval = cpu_to_le16(max_interval); + ev.ev.latency = cpu_to_le16(latency); + ev.ev.supv_timeout = cpu_to_le16(supv_timeout); + + send_event(remote, BT_HCI_EVT_LE_META_EVENT, &ev, sizeof(ev)); +} + +static void disconnect_complete(struct btdev *btdev, uint16_t handle, + uint8_t reason) +{ + struct bt_hci_evt_disconnect_complete dc; + struct btdev *remote = btdev->conn; + + if (!remote) { + dc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + dc.handle = cpu_to_le16(handle); + dc.reason = 0x00; + + send_event(btdev, BT_HCI_EVT_DISCONNECT_COMPLETE, + &dc, sizeof(dc)); + return; + } + + dc.status = BT_HCI_ERR_SUCCESS; + dc.handle = cpu_to_le16(handle); + dc.reason = reason; + + btdev->conn = NULL; + remote->conn = NULL; + + send_event(btdev, BT_HCI_EVT_DISCONNECT_COMPLETE, &dc, sizeof(dc)); + send_event(remote, BT_HCI_EVT_DISCONNECT_COMPLETE, &dc, sizeof(dc)); +} + +static void link_key_req_reply_complete(struct btdev *btdev, + const uint8_t *bdaddr, + const uint8_t *link_key) +{ + struct btdev *remote = btdev->conn; + struct bt_hci_evt_auth_complete ev; + + memcpy(btdev->link_key, link_key, 16); + + if (!remote) { + remote = find_btdev_by_bdaddr(bdaddr); + if (!remote) + return; + } + + if (!memcmp(remote->link_key, LINK_KEY_NONE, 16)) { + send_event(remote, BT_HCI_EVT_LINK_KEY_REQUEST, + btdev->bdaddr, 6); + return; + } + + ev.handle = cpu_to_le16(42); + + if (!memcmp(btdev->link_key, remote->link_key, 16)) + ev.status = BT_HCI_ERR_SUCCESS; + else + ev.status = BT_HCI_ERR_AUTH_FAILURE; + + send_event(btdev, BT_HCI_EVT_AUTH_COMPLETE, &ev, sizeof(ev)); + send_event(remote, BT_HCI_EVT_AUTH_COMPLETE, &ev, sizeof(ev)); +} + +static void link_key_req_neg_reply_complete(struct btdev *btdev, + const uint8_t *bdaddr) +{ + struct btdev *remote = btdev->conn; + + if (!remote) { + remote = find_btdev_by_bdaddr(bdaddr); + if (!remote) + return; + } + + if (use_ssp(btdev, remote)) { + struct bt_hci_evt_io_capability_request io_req; + + memcpy(io_req.bdaddr, bdaddr, 6); + send_event(btdev, BT_HCI_EVT_IO_CAPABILITY_REQUEST, &io_req, + sizeof(io_req)); + } else { + struct bt_hci_evt_pin_code_request pin_req; + + memcpy(pin_req.bdaddr, bdaddr, 6); + send_event(btdev, BT_HCI_EVT_PIN_CODE_REQUEST, &pin_req, + sizeof(pin_req)); + } +} + +static uint8_t get_link_key_type(struct btdev *btdev) +{ + struct btdev *remote = btdev->conn; + uint8_t auth, unauth; + + if (!remote) + return 0x00; + + if (!btdev->simple_pairing_mode) + return 0x00; + + if (btdev->ssp_debug_mode || remote->ssp_debug_mode) + return 0x03; + + if (btdev->secure_conn_support && remote->secure_conn_support) { + unauth = 0x07; + auth = 0x08; + } else { + unauth = 0x04; + auth = 0x05; + } + + if (btdev->io_cap == 0x03 || remote->io_cap == 0x03) + return unauth; + + if (!(btdev->auth_req & 0x01) && !(remote->auth_req & 0x01)) + return unauth; + + /* DisplayOnly only produces authenticated with KeyboardOnly */ + if (btdev->io_cap == 0x00 && remote->io_cap != 0x02) + return unauth; + + /* DisplayOnly only produces authenticated with KeyboardOnly */ + if (remote->io_cap == 0x00 && btdev->io_cap != 0x02) + return unauth; + + return auth; +} + +static void link_key_notify(struct btdev *btdev, const uint8_t *bdaddr, + const uint8_t *key) +{ + struct bt_hci_evt_link_key_notify ev; + + memcpy(btdev->link_key, key, 16); + + memcpy(ev.bdaddr, bdaddr, 6); + memcpy(ev.link_key, key, 16); + ev.key_type = get_link_key_type(btdev); + + send_event(btdev, BT_HCI_EVT_LINK_KEY_NOTIFY, &ev, sizeof(ev)); +} + +static void encrypt_change(struct btdev *btdev, uint8_t mode, uint8_t status) +{ + struct bt_hci_evt_encrypt_change ev; + + ev.status = status; + ev.handle = cpu_to_le16(42); + ev.encr_mode = mode; + + send_event(btdev, BT_HCI_EVT_ENCRYPT_CHANGE, &ev, sizeof(ev)); +} + +static void pin_code_req_reply_complete(struct btdev *btdev, + const uint8_t *bdaddr, uint8_t pin_len, + const uint8_t *pin_code) +{ + struct bt_hci_evt_auth_complete ev; + struct btdev *remote = btdev->conn; + + if (!remote) { + remote = find_btdev_by_bdaddr(bdaddr); + if (!remote) + return; + } + + memcpy(btdev->pin, pin_code, pin_len); + btdev->pin_len = pin_len; + + if (!remote->pin_len) { + struct bt_hci_evt_pin_code_request pin_req; + + memcpy(pin_req.bdaddr, btdev->bdaddr, 6); + send_event(remote, BT_HCI_EVT_PIN_CODE_REQUEST, &pin_req, + sizeof(pin_req)); + return; + } + + if (btdev->pin_len == remote->pin_len && + !memcmp(btdev->pin, remote->pin, btdev->pin_len)) { + link_key_notify(btdev, remote->bdaddr, LINK_KEY_DUMMY); + link_key_notify(remote, btdev->bdaddr, LINK_KEY_DUMMY); + ev.status = BT_HCI_ERR_SUCCESS; + } else { + ev.status = BT_HCI_ERR_AUTH_FAILURE; + } + + if (remote->conn) { + ev.handle = cpu_to_le16(42); + send_event(remote, BT_HCI_EVT_AUTH_COMPLETE, &ev, sizeof(ev)); + } else { + conn_complete(remote, btdev->bdaddr, ev.status); + } + + btdev->pin_len = 0; + remote->pin_len = 0; +} + +static void pin_code_req_neg_reply_complete(struct btdev *btdev, + const uint8_t *bdaddr) +{ + struct bt_hci_evt_auth_complete ev; + struct btdev *remote = btdev->conn; + + if (!remote) { + remote = find_btdev_by_bdaddr(bdaddr); + if (!remote) + return; + } + + ev.status = BT_HCI_ERR_PIN_OR_KEY_MISSING; + ev.handle = cpu_to_le16(42); + + if (btdev->conn) + send_event(btdev, BT_HCI_EVT_AUTH_COMPLETE, &ev, sizeof(ev)); + else + conn_complete(btdev, bdaddr, BT_HCI_ERR_PIN_OR_KEY_MISSING); + + if (remote->conn) { + if (remote->pin_len) + send_event(remote, BT_HCI_EVT_AUTH_COMPLETE, &ev, + sizeof(ev)); + } else { + conn_complete(remote, btdev->bdaddr, + BT_HCI_ERR_PIN_OR_KEY_MISSING); + } +} + +static void auth_request_complete(struct btdev *btdev, uint16_t handle) +{ + struct btdev *remote = btdev->conn; + + if (!remote) { + struct bt_hci_evt_auth_complete ev; + + ev.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + ev.handle = cpu_to_le16(handle); + + send_event(btdev, BT_HCI_EVT_AUTH_COMPLETE, &ev, sizeof(ev)); + + return; + } + + btdev->auth_init = true; + + send_event(btdev, BT_HCI_EVT_LINK_KEY_REQUEST, remote->bdaddr, 6); +} + +static void name_request_complete(struct btdev *btdev, + const uint8_t *bdaddr, uint8_t status) +{ + struct bt_hci_evt_remote_name_request_complete nc; + + nc.status = status; + memcpy(nc.bdaddr, bdaddr, 6); + memset(nc.name, 0, 248); + + if (!status) { + struct btdev *remote = find_btdev_by_bdaddr(bdaddr); + + if (remote) + memcpy(nc.name, remote->name, 248); + else + nc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + } + + send_event(btdev, BT_HCI_EVT_REMOTE_NAME_REQUEST_COMPLETE, + &nc, sizeof(nc)); +} + +static void remote_features_complete(struct btdev *btdev, uint16_t handle) +{ + struct bt_hci_evt_remote_features_complete rfc; + + if (btdev->conn) { + rfc.status = BT_HCI_ERR_SUCCESS; + rfc.handle = cpu_to_le16(handle); + memcpy(rfc.features, btdev->conn->features, 8); + } else { + rfc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + rfc.handle = cpu_to_le16(handle); + memset(rfc.features, 0, 8); + } + + send_event(btdev, BT_HCI_EVT_REMOTE_FEATURES_COMPLETE, + &rfc, sizeof(rfc)); +} + +static void btdev_get_host_features(struct btdev *btdev, uint8_t features[8]) +{ + memset(features, 0, 8); + if (btdev->simple_pairing_mode) + features[0] |= 0x01; + if (btdev->le_supported) + features[0] |= 0x02; + if (btdev->le_simultaneous) + features[0] |= 0x04; + if (btdev->secure_conn_support) + features[0] |= 0x08; +} + +static void remote_ext_features_complete(struct btdev *btdev, uint16_t handle, + uint8_t page) +{ + struct bt_hci_evt_remote_ext_features_complete refc; + + if (btdev->conn && page < 0x02) { + refc.handle = cpu_to_le16(handle); + refc.page = page; + refc.max_page = 0x01; + + switch (page) { + case 0x00: + refc.status = BT_HCI_ERR_SUCCESS; + memcpy(refc.features, btdev->conn->features, 8); + break; + case 0x01: + refc.status = BT_HCI_ERR_SUCCESS; + btdev_get_host_features(btdev->conn, refc.features); + break; + default: + refc.status = BT_HCI_ERR_INVALID_PARAMETERS; + memset(refc.features, 0, 8); + break; + } + } else { + refc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + refc.handle = cpu_to_le16(handle); + refc.page = page; + refc.max_page = 0x01; + memset(refc.features, 0, 8); + } + + send_event(btdev, BT_HCI_EVT_REMOTE_EXT_FEATURES_COMPLETE, + &refc, sizeof(refc)); +} + +static void remote_version_complete(struct btdev *btdev, uint16_t handle) +{ + struct bt_hci_evt_remote_version_complete rvc; + + if (btdev->conn) { + rvc.status = BT_HCI_ERR_SUCCESS; + rvc.handle = cpu_to_le16(handle); + rvc.lmp_ver = btdev->conn->version; + rvc.manufacturer = cpu_to_le16(btdev->conn->manufacturer); + rvc.lmp_subver = cpu_to_le16(btdev->conn->revision); + } else { + rvc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + rvc.handle = cpu_to_le16(handle); + rvc.lmp_ver = 0x00; + rvc.manufacturer = cpu_to_le16(0); + rvc.lmp_subver = cpu_to_le16(0); + } + + send_event(btdev, BT_HCI_EVT_REMOTE_VERSION_COMPLETE, + &rvc, sizeof(rvc)); +} + +static void remote_clock_offset_complete(struct btdev *btdev, uint16_t handle) +{ + struct bt_hci_evt_clock_offset_complete coc; + + if (btdev->conn) { + coc.status = BT_HCI_ERR_SUCCESS; + coc.handle = cpu_to_le16(handle); + coc.clock_offset = 0; + } else { + coc.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + coc.handle = cpu_to_le16(handle); + coc.clock_offset = 0; + } + + send_event(btdev, BT_HCI_EVT_CLOCK_OFFSET_COMPLETE, + &coc, sizeof(coc)); +} + +static void read_enc_key_size_complete(struct btdev *btdev, uint16_t handle) +{ + struct bt_hci_rsp_read_encrypt_key_size rsp; + + rsp.handle = cpu_to_le16(handle); + + if (btdev->conn) { + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.key_size = 16; + } else { + rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + rsp.key_size = 0; + } + + cmd_complete(btdev, BT_HCI_CMD_READ_ENCRYPT_KEY_SIZE, + &rsp, sizeof(rsp)); +} + +static void io_cap_req_reply_complete(struct btdev *btdev, + const uint8_t *bdaddr, + uint8_t capability, uint8_t oob_data, + uint8_t authentication) +{ + struct btdev *remote = btdev->conn; + struct bt_hci_evt_io_capability_response ev; + struct bt_hci_rsp_io_capability_request_reply rsp; + uint8_t status; + + if (!remote) { + status = BT_HCI_ERR_UNKNOWN_CONN_ID; + goto done; + } + + status = BT_HCI_ERR_SUCCESS; + + btdev->io_cap = capability; + btdev->auth_req = authentication; + + memcpy(ev.bdaddr, btdev->bdaddr, 6); + ev.capability = capability; + ev.oob_data = oob_data; + ev.authentication = authentication; + + send_event(remote, BT_HCI_EVT_IO_CAPABILITY_RESPONSE, &ev, sizeof(ev)); + + if (remote->io_cap) { + struct bt_hci_evt_user_confirm_request cfm; + + memcpy(cfm.bdaddr, btdev->bdaddr, 6); + cfm.passkey = 0; + + send_event(remote, BT_HCI_EVT_USER_CONFIRM_REQUEST, + &cfm, sizeof(cfm)); + + memcpy(cfm.bdaddr, bdaddr, 6); + send_event(btdev, BT_HCI_EVT_USER_CONFIRM_REQUEST, + &cfm, sizeof(cfm)); + } else { + send_event(remote, BT_HCI_EVT_IO_CAPABILITY_REQUEST, + btdev->bdaddr, 6); + } + +done: + rsp.status = status; + memcpy(rsp.bdaddr, bdaddr, 6); + cmd_complete(btdev, BT_HCI_CMD_IO_CAPABILITY_REQUEST_REPLY, + &rsp, sizeof(rsp)); +} + +static void io_cap_req_neg_reply_complete(struct btdev *btdev, + const uint8_t *bdaddr) +{ + struct bt_hci_rsp_io_capability_request_neg_reply rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(rsp.bdaddr, bdaddr, 6); + cmd_complete(btdev, BT_HCI_CMD_IO_CAPABILITY_REQUEST_NEG_REPLY, + &rsp, sizeof(rsp)); +} + +static void ssp_complete(struct btdev *btdev, const uint8_t *bdaddr, + uint8_t status, bool wait) +{ + struct bt_hci_evt_simple_pairing_complete iev, aev; + struct bt_hci_evt_auth_complete auth; + struct btdev *remote = btdev->conn; + struct btdev *init, *accp; + + if (!remote) + return; + + btdev->ssp_status = status; + btdev->ssp_auth_complete = true; + + if (!remote->ssp_auth_complete && wait) + return; + + if (status == BT_HCI_ERR_SUCCESS && + remote->ssp_status != BT_HCI_ERR_SUCCESS) + status = remote->ssp_status; + + iev.status = status; + aev.status = status; + + if (btdev->auth_init) { + init = btdev; + accp = remote; + memcpy(iev.bdaddr, bdaddr, 6); + memcpy(aev.bdaddr, btdev->bdaddr, 6); + } else { + init = remote; + accp = btdev; + memcpy(iev.bdaddr, btdev->bdaddr, 6); + memcpy(aev.bdaddr, bdaddr, 6); + } + + send_event(init, BT_HCI_EVT_SIMPLE_PAIRING_COMPLETE, &iev, + sizeof(iev)); + send_event(accp, BT_HCI_EVT_SIMPLE_PAIRING_COMPLETE, &aev, + sizeof(aev)); + + if (status == BT_HCI_ERR_SUCCESS) { + link_key_notify(init, iev.bdaddr, LINK_KEY_DUMMY); + link_key_notify(accp, aev.bdaddr, LINK_KEY_DUMMY); + } + + auth.status = status; + auth.handle = cpu_to_le16(42); + send_event(init, BT_HCI_EVT_AUTH_COMPLETE, &auth, sizeof(auth)); +} + +static void le_send_adv_report(struct btdev *btdev, const struct btdev *remote, + uint8_t type) +{ + struct __packed { + uint8_t subevent; + union { + struct bt_hci_evt_le_adv_report lar; + uint8_t raw[10 + 31 + 1]; + }; + } meta_event; + + meta_event.subevent = BT_HCI_EVT_LE_ADV_REPORT; + + memset(&meta_event.lar, 0, sizeof(meta_event.lar)); + meta_event.lar.num_reports = 1; + meta_event.lar.event_type = type; + meta_event.lar.addr_type = remote->le_adv_own_addr; + memcpy(meta_event.lar.addr, adv_addr(remote), 6); + + /* Scan or advertising response */ + if (type == 0x04) { + meta_event.lar.data_len = remote->le_scan_data_len; + memcpy(meta_event.lar.data, remote->le_scan_data, + meta_event.lar.data_len); + } else { + meta_event.lar.data_len = remote->le_adv_data_len; + memcpy(meta_event.lar.data, remote->le_adv_data, + meta_event.lar.data_len); + } + /* Not available */ + meta_event.raw[10 + meta_event.lar.data_len] = 127; + send_event(btdev, BT_HCI_EVT_LE_META_EVENT, &meta_event, + 1 + 10 + meta_event.lar.data_len + 1); +} + +static void send_ext_adv(struct btdev *btdev, const struct btdev *remote, + uint16_t type, bool is_scan_rsp) +{ + struct __packed { + uint8_t subevent; + uint8_t num_reports; + union { + struct bt_hci_le_ext_adv_report lear; + uint8_t raw[24 + 31]; + }; + } meta_event; + + meta_event.subevent = BT_HCI_EVT_LE_EXT_ADV_REPORT; + + memset(&meta_event.lear, 0, sizeof(meta_event.lear)); + meta_event.num_reports = 1; + meta_event.lear.event_type = cpu_to_le16(type); + meta_event.lear.addr_type = remote->le_adv_own_addr; + memcpy(meta_event.lear.addr, adv_addr(remote), 6); + meta_event.lear.rssi = 127; + meta_event.lear.tx_power = 127; + /* Right now we dont care about phy in adv report */ + meta_event.lear.primary_phy = 0x01; + meta_event.lear.secondary_phy = 0x01; + + /* Scan or advertising response */ + if (is_scan_rsp) { + meta_event.lear.data_len = remote->le_scan_data_len; + memcpy(meta_event.lear.data, remote->le_scan_data, + meta_event.lear.data_len); + } else { + meta_event.lear.data_len = remote->le_adv_data_len; + memcpy(meta_event.lear.data, remote->le_adv_data, + meta_event.lear.data_len); + } + + send_event(btdev, BT_HCI_EVT_LE_META_EVENT, &meta_event, + 1 + 1 + 24 + meta_event.lear.data_len); +} + +static uint8_t get_adv_report_type(uint8_t adv_type) +{ + /* + * Connectable low duty cycle directed advertising creates a + * connectable directed advertising report type. + */ + if (adv_type == 0x04) + return 0x01; + + return adv_type; +} + +static uint8_t get_ext_adv_type(uint8_t ext_adv_type) +{ + /* + * If legacy bit is not set then just reset high duty cycle directed + * bit. + */ + if (!(ext_adv_type & 0x10)) + return (ext_adv_type & 0xf7); + + /* + * Connectable low duty cycle directed advertising creates a + * connectable directed advertising report type. + */ + if (ext_adv_type == 0x001d) + return 0x0015; + + return ext_adv_type; +} + +static void le_set_adv_enable_complete(struct btdev *btdev) +{ + uint8_t report_type; + int i; + + report_type = get_adv_report_type(btdev->le_adv_type); + + for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { + if (!btdev_list[i] || btdev_list[i] == btdev) + continue; + + if (!btdev_list[i]->le_scan_enable) + continue; + + if (!adv_match(btdev_list[i], btdev)) + continue; + + le_send_adv_report(btdev_list[i], btdev, report_type); + + if (btdev_list[i]->le_scan_type != 0x01) + continue; + + /* ADV_IND & ADV_SCAN_IND generate a scan response */ + if (btdev->le_adv_type == 0x00 || btdev->le_adv_type == 0x02) + le_send_adv_report(btdev_list[i], btdev, 0x04); + } +} + +static void le_set_ext_adv_enable_complete(struct btdev *btdev) +{ + uint16_t report_type; + int i; + + report_type = get_ext_adv_type(btdev->le_ext_adv_type); + + for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { + if (!btdev_list[i] || btdev_list[i] == btdev) + continue; + + if (!btdev_list[i]->le_scan_enable) + continue; + + if (!ext_adv_match(btdev_list[i], btdev)) + continue; + + send_ext_adv(btdev_list[i], btdev, report_type, + false); + + if (btdev_list[i]->le_scan_type != 0x01) + continue; + + /* if scannable bit is set the send scan response */ + if (btdev->le_ext_adv_type & 0x02) { + if (btdev->le_ext_adv_type == 0x13) + report_type = 0x1b; + else if (btdev->le_ext_adv_type == 0x12) + report_type = 0x1a; + else if (!(btdev->le_ext_adv_type & 0x10)) + report_type &= 0x08; + else + continue; + + send_ext_adv(btdev_list[i], btdev, + report_type, true); + } + } +} + +static void le_set_scan_enable_complete(struct btdev *btdev) +{ + int i; + + for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { + uint8_t report_type; + + if (!btdev_list[i] || btdev_list[i] == btdev) + continue; + + if (!btdev_list[i]->le_adv_enable) + continue; + + if (!adv_match(btdev, btdev_list[i])) + continue; + + report_type = get_adv_report_type(btdev_list[i]->le_adv_type); + le_send_adv_report(btdev, btdev_list[i], report_type); + + if (btdev->le_scan_type != 0x01) + continue; + + /* ADV_IND & ADV_SCAN_IND generate a scan response */ + if (btdev_list[i]->le_adv_type == 0x00 || + btdev_list[i]->le_adv_type == 0x02) + le_send_adv_report(btdev, btdev_list[i], 0x04); + } +} + +static void le_set_ext_scan_enable_complete(struct btdev *btdev) +{ + int i; + + for (i = 0; i < MAX_BTDEV_ENTRIES; i++) { + uint16_t report_type; + + if (!btdev_list[i] || btdev_list[i] == btdev) + continue; + + if (!btdev_list[i]->le_adv_enable) + continue; + + if (!ext_adv_match(btdev, btdev_list[i])) + continue; + + report_type = get_ext_adv_type(btdev_list[i]->le_ext_adv_type); + send_ext_adv(btdev, btdev_list[i], report_type, false); + + if (btdev->le_scan_type != 0x01) + continue; + + /* if scannable bit is set the send scan response */ + if (btdev_list[i]->le_ext_adv_type & 0x02) { + if (btdev_list[i]->le_ext_adv_type == 0x13) + report_type = 0x1b; + else if (btdev_list[i]->le_ext_adv_type == 0x12) + report_type = 0x1a; + else if (!(btdev_list[i]->le_ext_adv_type & 0x10)) + report_type &= 0x08; + else + continue; + + send_ext_adv(btdev, btdev_list[i], report_type, true); + } + } +} + +static void le_read_remote_features_complete(struct btdev *btdev) +{ + char buf[1 + sizeof(struct bt_hci_evt_le_remote_features_complete)]; + struct bt_hci_evt_le_remote_features_complete *ev = (void *) &buf[1]; + struct btdev *remote = btdev->conn; + + if (!remote) { + cmd_status(btdev, BT_HCI_ERR_UNKNOWN_CONN_ID, + BT_HCI_CMD_LE_READ_REMOTE_FEATURES); + return; + } + + cmd_status(btdev, BT_HCI_ERR_SUCCESS, + BT_HCI_CMD_LE_READ_REMOTE_FEATURES); + + memset(buf, 0, sizeof(buf)); + buf[0] = BT_HCI_EVT_LE_REMOTE_FEATURES_COMPLETE; + ev->status = BT_HCI_ERR_SUCCESS; + ev->handle = cpu_to_le16(42); + memcpy(ev->features, remote->le_features, 8); + + send_event(btdev, BT_HCI_EVT_LE_META_EVENT, buf, sizeof(buf)); +} + +static void le_start_encrypt_complete(struct btdev *btdev, uint16_t ediv, + uint64_t rand) +{ + char buf[1 + sizeof(struct bt_hci_evt_le_long_term_key_request)]; + struct bt_hci_evt_le_long_term_key_request *ev = (void *) &buf[1]; + struct btdev *remote = btdev->conn; + + if (!remote) { + cmd_status(btdev, BT_HCI_ERR_UNKNOWN_CONN_ID, + BT_HCI_CMD_LE_START_ENCRYPT); + return; + } + + cmd_status(btdev, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_START_ENCRYPT); + + memset(buf, 0, sizeof(buf)); + buf[0] = BT_HCI_EVT_LE_LONG_TERM_KEY_REQUEST; + ev->handle = cpu_to_le16(42); + ev->ediv = ediv; + ev->rand = rand; + + send_event(remote, BT_HCI_EVT_LE_META_EVENT, buf, sizeof(buf)); +} + +static void le_encrypt_complete(struct btdev *btdev) +{ + struct bt_hci_evt_encrypt_change ev; + struct bt_hci_rsp_le_ltk_req_reply rp; + struct btdev *remote = btdev->conn; + + memset(&rp, 0, sizeof(rp)); + rp.handle = cpu_to_le16(42); + + if (!remote) { + rp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + cmd_complete(btdev, BT_HCI_CMD_LE_LTK_REQ_REPLY, &rp, + sizeof(rp)); + return; + } + + rp.status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, BT_HCI_CMD_LE_LTK_REQ_REPLY, &rp, sizeof(rp)); + + memset(&ev, 0, sizeof(ev)); + + if (memcmp(btdev->le_ltk, remote->le_ltk, 16)) { + ev.status = BT_HCI_ERR_AUTH_FAILURE; + ev.encr_mode = 0x00; + } else { + ev.status = BT_HCI_ERR_SUCCESS; + ev.encr_mode = 0x01; + } + + ev.handle = cpu_to_le16(42); + + send_event(btdev, BT_HCI_EVT_ENCRYPT_CHANGE, &ev, sizeof(ev)); + send_event(remote, BT_HCI_EVT_ENCRYPT_CHANGE, &ev, sizeof(ev)); +} + +static void ltk_neg_reply_complete(struct btdev *btdev) +{ + struct bt_hci_rsp_le_ltk_req_neg_reply rp; + struct bt_hci_evt_encrypt_change ev; + struct btdev *remote = btdev->conn; + + memset(&rp, 0, sizeof(rp)); + rp.handle = cpu_to_le16(42); + + if (!remote) { + rp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + cmd_complete(btdev, BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY, &rp, + sizeof(rp)); + return; + } + + rp.status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY, &rp, sizeof(rp)); + + memset(&ev, 0, sizeof(ev)); + ev.status = BT_HCI_ERR_PIN_OR_KEY_MISSING; + ev.handle = cpu_to_le16(42); + + send_event(remote, BT_HCI_EVT_ENCRYPT_CHANGE, &ev, sizeof(ev)); +} + +static void btdev_reset(struct btdev *btdev) +{ + /* FIXME: include here clearing of all states that should be + * cleared upon HCI_Reset + */ + + btdev->le_scan_enable = 0x00; + btdev->le_adv_enable = 0x00; +} + +static void default_cmd(struct btdev *btdev, uint16_t opcode, + const void *data, uint8_t len) +{ + const struct bt_hci_cmd_remote_name_request_cancel *rnrc; + const struct bt_hci_cmd_write_default_link_policy *wdlp; + const struct bt_hci_cmd_set_event_mask *sem; + const struct bt_hci_cmd_set_event_filter *sef; + const struct bt_hci_cmd_write_local_name *wln; + const struct bt_hci_cmd_write_conn_accept_timeout *wcat; + const struct bt_hci_cmd_write_page_timeout *wpt; + const struct bt_hci_cmd_write_scan_enable *wse; + const struct bt_hci_cmd_write_page_scan_activity *wpsa; + const struct bt_hci_cmd_write_inquiry_scan_activity *wisa; + const struct bt_hci_cmd_write_page_scan_type *wpst; + const struct bt_hci_cmd_write_auth_enable *wae; + const struct bt_hci_cmd_write_class_of_dev *wcod; + const struct bt_hci_cmd_write_voice_setting *wvs; + const struct bt_hci_cmd_set_host_flow_control *shfc; + const struct bt_hci_cmd_write_inquiry_mode *wim; + const struct bt_hci_cmd_write_afh_assessment_mode *waam; + const struct bt_hci_cmd_write_ext_inquiry_response *weir; + const struct bt_hci_cmd_write_simple_pairing_mode *wspm; + const struct bt_hci_cmd_io_capability_request_reply *icrr; + const struct bt_hci_cmd_io_capability_request_reply *icrnr; + const struct bt_hci_cmd_read_encrypt_key_size *reks; + const struct bt_hci_cmd_write_le_host_supported *wlhs; + const struct bt_hci_cmd_write_secure_conn_support *wscs; + const struct bt_hci_cmd_set_event_mask_page2 *semp2; + const struct bt_hci_cmd_le_set_event_mask *lsem; + const struct bt_hci_cmd_le_set_random_address *lsra; + const struct bt_hci_cmd_le_set_adv_parameters *lsap; + const struct bt_hci_cmd_le_set_adv_data *lsad; + const struct bt_hci_cmd_le_set_scan_rsp_data *lssrd; + const struct bt_hci_cmd_setup_sync_conn *ssc; + const struct bt_hci_cmd_write_ssp_debug_mode *wsdm; + const struct bt_hci_cmd_le_set_adv_enable *lsae; + const struct bt_hci_cmd_le_set_scan_parameters *lssp; + const struct bt_hci_cmd_le_set_scan_enable *lsse; + const struct bt_hci_cmd_le_start_encrypt *lse; + const struct bt_hci_cmd_le_ltk_req_reply *llrr; + const struct bt_hci_cmd_le_encrypt *lenc_cmd; + const struct bt_hci_cmd_le_generate_dhkey *dh; + const struct bt_hci_cmd_le_conn_param_req_reply *lcprr_cmd; + const struct bt_hci_cmd_le_conn_param_req_neg_reply *lcprnr_cmd; + const struct bt_hci_cmd_read_local_amp_assoc *rlaa_cmd; + const struct bt_hci_cmd_read_rssi *rrssi; + const struct bt_hci_cmd_read_tx_power *rtxp; + const struct bt_hci_cmd_le_set_adv_set_rand_addr *lsasra; + const struct bt_hci_cmd_le_set_ext_adv_params *lseap; + const struct bt_hci_cmd_le_set_ext_adv_enable *lseae; + const struct bt_hci_cmd_le_set_ext_adv_data *lsead; + const struct bt_hci_cmd_le_set_ext_scan_rsp_data *lsesrd; + const struct bt_hci_cmd_le_set_default_phy *phys; + const struct bt_hci_cmd_le_set_ext_scan_params *lsesp; + const struct bt_hci_le_scan_phy *lsp; + const struct bt_hci_cmd_le_set_ext_scan_enable *lsese; + struct bt_hci_rsp_read_default_link_policy rdlp; + struct bt_hci_rsp_read_stored_link_key rslk; + struct bt_hci_rsp_write_stored_link_key wslk; + struct bt_hci_rsp_delete_stored_link_key dslk; + struct bt_hci_rsp_read_local_name rln; + struct bt_hci_rsp_read_conn_accept_timeout rcat; + struct bt_hci_rsp_read_page_timeout rpt; + struct bt_hci_rsp_read_scan_enable rse; + struct bt_hci_rsp_read_page_scan_activity rpsa; + struct bt_hci_rsp_read_inquiry_scan_activity risa; + struct bt_hci_rsp_read_page_scan_type rpst; + struct bt_hci_rsp_read_auth_enable rae; + struct bt_hci_rsp_read_class_of_dev rcod; + struct bt_hci_rsp_read_voice_setting rvs; + struct bt_hci_rsp_read_num_supported_iac rnsi; + struct bt_hci_rsp_read_current_iac_lap *rcil; + struct bt_hci_rsp_read_inquiry_mode rim; + struct bt_hci_rsp_read_afh_assessment_mode raam; + struct bt_hci_rsp_read_ext_inquiry_response reir; + struct bt_hci_rsp_read_simple_pairing_mode rspm; + struct bt_hci_rsp_read_local_oob_data rlod; + struct bt_hci_rsp_read_inquiry_resp_tx_power rirtp; + struct bt_hci_rsp_read_le_host_supported rlhs; + struct bt_hci_rsp_read_secure_conn_support rscs; + struct bt_hci_rsp_read_local_oob_ext_data rloed; + struct bt_hci_rsp_read_sync_train_params rstp; + struct bt_hci_rsp_read_local_version rlv; + struct bt_hci_rsp_read_local_commands rlc; + struct bt_hci_rsp_read_local_features rlf; + struct bt_hci_rsp_read_local_ext_features rlef; + struct bt_hci_rsp_read_buffer_size rbs; + struct bt_hci_rsp_read_country_code rcc; + struct bt_hci_rsp_read_bd_addr rba; + struct bt_hci_rsp_read_data_block_size rdbs; + struct bt_hci_rsp_read_local_codecs *rlsc; + struct bt_hci_rsp_read_local_amp_info rlai; + struct bt_hci_rsp_read_local_amp_assoc rlaa_rsp; + struct bt_hci_rsp_get_mws_transport_config *gmtc; + struct bt_hci_rsp_le_conn_param_req_reply lcprr_rsp; + struct bt_hci_rsp_le_conn_param_req_neg_reply lcprnr_rsp; + struct bt_hci_rsp_le_read_buffer_size lrbs; + struct bt_hci_rsp_le_read_local_features lrlf; + struct bt_hci_rsp_le_read_adv_tx_power lratp; + struct bt_hci_rsp_le_read_supported_states lrss; + struct bt_hci_rsp_le_read_white_list_size lrwls; + struct bt_hci_rsp_le_encrypt lenc; + struct bt_hci_rsp_le_rand lr; + struct bt_hci_rsp_le_test_end lte; + struct bt_hci_rsp_remote_name_request_cancel rnrc_rsp; + struct bt_hci_rsp_link_key_request_reply lkrr_rsp; + struct bt_hci_rsp_link_key_request_neg_reply lkrnr_rsp; + struct bt_hci_rsp_pin_code_request_neg_reply pcrr_rsp; + struct bt_hci_rsp_pin_code_request_neg_reply pcrnr_rsp; + struct bt_hci_rsp_user_confirm_request_reply ucrr_rsp; + struct bt_hci_rsp_user_confirm_request_neg_reply ucrnr_rsp; + struct bt_hci_rsp_read_rssi rrssi_rsp; + struct bt_hci_rsp_read_tx_power rtxp_rsp; + struct bt_hci_evt_le_read_local_pk256_complete pk_evt; + struct bt_hci_evt_le_generate_dhkey_complete dh_evt; + struct bt_hci_rsp_le_read_num_supported_adv_sets rlrnsas; + struct bt_hci_rsp_le_set_ext_adv_params rlseap; + uint8_t status, page; + + switch (opcode) { + case BT_HCI_CMD_INQUIRY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_INQUIRY_CANCEL: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + inquiry_cancel(btdev); + break; + + case BT_HCI_CMD_CREATE_CONN: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_DISCONNECT: + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_CREATE_CONN_CANCEL: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_ACCEPT_CONN_REQUEST: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_REJECT_CONN_REQUEST: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_LINK_KEY_REQUEST_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + lkrr_rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(lkrr_rsp.bdaddr, data, 6); + cmd_complete(btdev, opcode, &lkrr_rsp, sizeof(lkrr_rsp)); + break; + + case BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + lkrnr_rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(lkrnr_rsp.bdaddr, data, 6); + cmd_complete(btdev, opcode, &lkrnr_rsp, sizeof(lkrnr_rsp)); + break; + + case BT_HCI_CMD_PIN_CODE_REQUEST_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + pcrr_rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(pcrr_rsp.bdaddr, data, 6); + cmd_complete(btdev, opcode, &pcrr_rsp, sizeof(pcrr_rsp)); + break; + + case BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + pcrnr_rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(pcrnr_rsp.bdaddr, data, 6); + cmd_complete(btdev, opcode, &pcrnr_rsp, sizeof(pcrnr_rsp)); + break; + + case BT_HCI_CMD_AUTH_REQUESTED: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_SET_CONN_ENCRYPT: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_REMOTE_NAME_REQUEST: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_REMOTE_NAME_REQUEST_CANCEL: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rnrc = data; + rnrc_rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(rnrc_rsp.bdaddr, rnrc->bdaddr, 6); + cmd_complete(btdev, opcode, &rnrc_rsp, sizeof(rnrc_rsp)); + break; + + case BT_HCI_CMD_READ_REMOTE_FEATURES: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_READ_REMOTE_EXT_FEATURES: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_READ_REMOTE_VERSION: + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_READ_CLOCK_OFFSET: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_READ_DEFAULT_LINK_POLICY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rdlp.status = BT_HCI_ERR_SUCCESS; + rdlp.policy = cpu_to_le16(btdev->default_link_policy); + cmd_complete(btdev, opcode, &rdlp, sizeof(rdlp)); + break; + + case BT_HCI_CMD_WRITE_DEFAULT_LINK_POLICY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wdlp = data; + btdev->default_link_policy = le16_to_cpu(wdlp->policy); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_SET_EVENT_MASK: + sem = data; + memcpy(btdev->event_mask, sem->mask, 8); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_RESET: + btdev_reset(btdev); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_SET_EVENT_FILTER: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + sef = data; + btdev->event_filter = sef->type; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_STORED_LINK_KEY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rslk.status = BT_HCI_ERR_SUCCESS; + rslk.max_num_keys = cpu_to_le16(0); + rslk.num_keys = cpu_to_le16(0); + cmd_complete(btdev, opcode, &rslk, sizeof(rslk)); + break; + + case BT_HCI_CMD_WRITE_STORED_LINK_KEY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wslk.status = BT_HCI_ERR_SUCCESS; + wslk.num_keys = 0; + cmd_complete(btdev, opcode, &wslk, sizeof(wslk)); + break; + + case BT_HCI_CMD_DELETE_STORED_LINK_KEY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + dslk.status = BT_HCI_ERR_SUCCESS; + dslk.num_keys = cpu_to_le16(0); + cmd_complete(btdev, opcode, &dslk, sizeof(dslk)); + break; + + case BT_HCI_CMD_WRITE_LOCAL_NAME: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wln = data; + memcpy(btdev->name, wln->name, 248); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_LOCAL_NAME: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rln.status = BT_HCI_ERR_SUCCESS; + memcpy(rln.name, btdev->name, 248); + cmd_complete(btdev, opcode, &rln, sizeof(rln)); + break; + + case BT_HCI_CMD_READ_CONN_ACCEPT_TIMEOUT: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rcat.status = BT_HCI_ERR_SUCCESS; + rcat.timeout = cpu_to_le16(btdev->conn_accept_timeout); + cmd_complete(btdev, opcode, &rcat, sizeof(rcat)); + break; + + case BT_HCI_CMD_WRITE_CONN_ACCEPT_TIMEOUT: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wcat = data; + btdev->conn_accept_timeout = le16_to_cpu(wcat->timeout); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_PAGE_TIMEOUT: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rpt.status = BT_HCI_ERR_SUCCESS; + rpt.timeout = cpu_to_le16(btdev->page_timeout); + cmd_complete(btdev, opcode, &rpt, sizeof(rpt)); + break; + + case BT_HCI_CMD_WRITE_PAGE_TIMEOUT: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wpt = data; + btdev->page_timeout = le16_to_cpu(wpt->timeout); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_SCAN_ENABLE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rse.status = BT_HCI_ERR_SUCCESS; + rse.enable = btdev->scan_enable; + cmd_complete(btdev, opcode, &rse, sizeof(rse)); + break; + + case BT_HCI_CMD_WRITE_SCAN_ENABLE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wse = data; + btdev->scan_enable = wse->enable; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_PAGE_SCAN_ACTIVITY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rpsa.status = BT_HCI_ERR_SUCCESS; + rpsa.interval = cpu_to_le16(btdev->page_scan_interval); + rpsa.window = cpu_to_le16(btdev->page_scan_window); + cmd_complete(btdev, opcode, &rpsa, sizeof(rpsa)); + break; + + case BT_HCI_CMD_WRITE_PAGE_SCAN_ACTIVITY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wpsa = data; + btdev->page_scan_interval = le16_to_cpu(wpsa->interval); + btdev->page_scan_window = le16_to_cpu(wpsa->window); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_INQUIRY_SCAN_ACTIVITY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + risa.status = BT_HCI_ERR_SUCCESS; + risa.interval = cpu_to_le16(btdev->inquiry_scan_interval); + risa.window = cpu_to_le16(btdev->inquiry_scan_window); + cmd_complete(btdev, opcode, &risa, sizeof(risa)); + break; + + case BT_HCI_CMD_WRITE_INQUIRY_SCAN_ACTIVITY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wisa = data; + btdev->inquiry_scan_interval = le16_to_cpu(wisa->interval); + btdev->inquiry_scan_window = le16_to_cpu(wisa->window); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_PAGE_SCAN_TYPE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rpst.status = BT_HCI_ERR_SUCCESS; + rpst.type = btdev->page_scan_type; + cmd_complete(btdev, opcode, &rpst, sizeof(rpst)); + break; + + case BT_HCI_CMD_WRITE_PAGE_SCAN_TYPE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wpst = data; + btdev->page_scan_type = wpst->type; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_AUTH_ENABLE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rae.status = BT_HCI_ERR_SUCCESS; + rae.enable = btdev->auth_enable; + cmd_complete(btdev, opcode, &rae, sizeof(rae)); + break; + + case BT_HCI_CMD_WRITE_AUTH_ENABLE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wae = data; + btdev->auth_enable = wae->enable; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_CLASS_OF_DEV: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rcod.status = BT_HCI_ERR_SUCCESS; + memcpy(rcod.dev_class, btdev->dev_class, 3); + cmd_complete(btdev, opcode, &rcod, sizeof(rcod)); + break; + + case BT_HCI_CMD_WRITE_CLASS_OF_DEV: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wcod = data; + memcpy(btdev->dev_class, wcod->dev_class, 3); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_VOICE_SETTING: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rvs.status = BT_HCI_ERR_SUCCESS; + rvs.setting = cpu_to_le16(btdev->voice_setting); + cmd_complete(btdev, opcode, &rvs, sizeof(rvs)); + break; + + case BT_HCI_CMD_WRITE_VOICE_SETTING: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wvs = data; + btdev->voice_setting = le16_to_cpu(wvs->setting); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_SET_HOST_FLOW_CONTROL: + shfc = data; + if (shfc->enable > 0x03) { + status = BT_HCI_ERR_INVALID_PARAMETERS; + } else { + btdev->host_flow_control = shfc->enable; + status = BT_HCI_ERR_SUCCESS; + } + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_HOST_BUFFER_SIZE: + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_HOST_NUM_COMPLETED_PACKETS: + /* This command is special in the sense that no event is + * normally generated after the command has completed. + */ + break; + + case BT_HCI_CMD_READ_NUM_SUPPORTED_IAC: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rnsi.status = BT_HCI_ERR_SUCCESS; + rnsi.num_iac = 0x01; + cmd_complete(btdev, opcode, &rnsi, sizeof(rnsi)); + break; + + case BT_HCI_CMD_READ_CURRENT_IAC_LAP: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rcil = alloca(sizeof(*rcil) + 3); + rcil->status = BT_HCI_ERR_SUCCESS; + rcil->num_iac = 0x01; + rcil->iac_lap[0] = 0x33; + rcil->iac_lap[1] = 0x8b; + rcil->iac_lap[2] = 0x9e; + cmd_complete(btdev, opcode, rcil, sizeof(*rcil) + 3); + break; + + case BT_HCI_CMD_WRITE_CURRENT_IAC_LAP: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_INQUIRY_MODE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rim.status = BT_HCI_ERR_SUCCESS; + rim.mode = btdev->inquiry_mode; + cmd_complete(btdev, opcode, &rim, sizeof(rim)); + break; + + case BT_HCI_CMD_WRITE_INQUIRY_MODE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wim = data; + btdev->inquiry_mode = wim->mode; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_AFH_ASSESSMENT_MODE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + raam.status = BT_HCI_ERR_SUCCESS; + raam.mode = btdev->afh_assessment_mode; + cmd_complete(btdev, opcode, &raam, sizeof(raam)); + break; + + case BT_HCI_CMD_WRITE_AFH_ASSESSMENT_MODE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + waam = data; + btdev->afh_assessment_mode = waam->mode; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_EXT_INQUIRY_RESPONSE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + reir.status = BT_HCI_ERR_SUCCESS; + reir.fec = btdev->ext_inquiry_fec; + memcpy(reir.data, btdev->ext_inquiry_rsp, 240); + cmd_complete(btdev, opcode, &reir, sizeof(reir)); + break; + + case BT_HCI_CMD_WRITE_EXT_INQUIRY_RESPONSE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + weir = data; + btdev->ext_inquiry_fec = weir->fec; + memcpy(btdev->ext_inquiry_rsp, weir->data, 240); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_SIMPLE_PAIRING_MODE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rspm.status = BT_HCI_ERR_SUCCESS; + rspm.mode = btdev->simple_pairing_mode; + cmd_complete(btdev, opcode, &rspm, sizeof(rspm)); + break; + + case BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wspm = data; + btdev->simple_pairing_mode = wspm->mode; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_IO_CAPABILITY_REQUEST_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + icrr = data; + io_cap_req_reply_complete(btdev, icrr->bdaddr, + icrr->capability, + icrr->oob_data, + icrr->authentication); + break; + + case BT_HCI_CMD_IO_CAPABILITY_REQUEST_NEG_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + icrnr = data; + io_cap_req_neg_reply_complete(btdev, icrnr->bdaddr); + ssp_complete(btdev, icrnr->bdaddr, BT_HCI_ERR_AUTH_FAILURE, + false); + break; + + case BT_HCI_CMD_USER_CONFIRM_REQUEST_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + ucrr_rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(ucrr_rsp.bdaddr, data, 6); + cmd_complete(btdev, opcode, &ucrr_rsp, sizeof(ucrr_rsp)); + ssp_complete(btdev, data, BT_HCI_ERR_SUCCESS, true); + break; + + case BT_HCI_CMD_USER_CONFIRM_REQUEST_NEG_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + ucrnr_rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(ucrnr_rsp.bdaddr, data, 6); + cmd_complete(btdev, opcode, &ucrnr_rsp, sizeof(ucrnr_rsp)); + ssp_complete(btdev, data, BT_HCI_ERR_AUTH_FAILURE, true); + break; + + case BT_HCI_CMD_READ_LOCAL_OOB_DATA: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rlod.status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &rlod, sizeof(rlod)); + break; + + case BT_HCI_CMD_READ_INQUIRY_RESP_TX_POWER: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rirtp.status = BT_HCI_ERR_SUCCESS; + rirtp.level = 0; + cmd_complete(btdev, opcode, &rirtp, sizeof(rirtp)); + break; + + case BT_HCI_CMD_READ_LE_HOST_SUPPORTED: + if (btdev->type != BTDEV_TYPE_BREDRLE && + btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + rlhs.status = BT_HCI_ERR_SUCCESS; + rlhs.supported = btdev->le_supported; + rlhs.simultaneous = btdev->le_simultaneous; + cmd_complete(btdev, opcode, &rlhs, sizeof(rlhs)); + break; + + case BT_HCI_CMD_WRITE_LE_HOST_SUPPORTED: + if (btdev->type != BTDEV_TYPE_BREDRLE && + btdev->type != BTDEV_TYPE_LE && + btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + wlhs = data; + btdev->le_supported = wlhs->supported; + btdev->le_simultaneous = wlhs->simultaneous; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_SECURE_CONN_SUPPORT: + if (btdev->type != BTDEV_TYPE_BREDRLE && + btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + rscs.status = BT_HCI_ERR_SUCCESS; + rscs.support = btdev->secure_conn_support; + cmd_complete(btdev, opcode, &rscs, sizeof(rscs)); + break; + + case BT_HCI_CMD_WRITE_SECURE_CONN_SUPPORT: + if (btdev->type != BTDEV_TYPE_BREDRLE && + btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + wscs = data; + btdev->secure_conn_support = wscs->support; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_READ_LOCAL_OOB_EXT_DATA: + if (btdev->type != BTDEV_TYPE_BREDRLE && + btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + rloed.status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &rloed, sizeof(rloed)); + break; + + case BT_HCI_CMD_READ_SYNC_TRAIN_PARAMS: + if (btdev->type != BTDEV_TYPE_BREDRLE && + btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + rstp.status = BT_HCI_ERR_SUCCESS; + rstp.interval = cpu_to_le16(btdev->sync_train_interval); + rstp.timeout = cpu_to_le32(btdev->sync_train_timeout); + rstp.service_data = btdev->sync_train_service_data; + cmd_complete(btdev, opcode, &rstp, sizeof(rstp)); + break; + + case BT_HCI_CMD_READ_LOCAL_VERSION: + rlv.status = BT_HCI_ERR_SUCCESS; + rlv.hci_ver = btdev->version; + rlv.hci_rev = cpu_to_le16(btdev->revision); + rlv.lmp_ver = btdev->version; + rlv.manufacturer = cpu_to_le16(btdev->manufacturer); + rlv.lmp_subver = cpu_to_le16(btdev->revision); + cmd_complete(btdev, opcode, &rlv, sizeof(rlv)); + break; + + case BT_HCI_CMD_READ_LOCAL_COMMANDS: + rlc.status = BT_HCI_ERR_SUCCESS; + memcpy(rlc.commands, btdev->commands, 64); + cmd_complete(btdev, opcode, &rlc, sizeof(rlc)); + break; + + case BT_HCI_CMD_READ_LOCAL_FEATURES: + rlf.status = BT_HCI_ERR_SUCCESS; + memcpy(rlf.features, btdev->features, 8); + cmd_complete(btdev, opcode, &rlf, sizeof(rlf)); + break; + + case BT_HCI_CMD_READ_LOCAL_EXT_FEATURES: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + + page = ((const uint8_t *) data)[0]; + + rlef.page = page; + rlef.max_page = btdev->max_page; + + if (page > btdev->max_page) { + rlef.status = BT_HCI_ERR_INVALID_PARAMETERS; + memset(rlef.features, 0, 8); + cmd_complete(btdev, opcode, &rlef, sizeof(rlef)); + break; + } + + switch (page) { + case 0x00: + rlef.status = BT_HCI_ERR_SUCCESS; + memcpy(rlef.features, btdev->features, 8); + break; + case 0x01: + rlef.status = BT_HCI_ERR_SUCCESS; + btdev_get_host_features(btdev, rlef.features); + break; + case 0x02: + rlef.status = BT_HCI_ERR_SUCCESS; + memcpy(rlef.features, btdev->feat_page_2, 8); + break; + default: + rlef.status = BT_HCI_ERR_INVALID_PARAMETERS; + memset(rlef.features, 0, 8); + break; + } + cmd_complete(btdev, opcode, &rlef, sizeof(rlef)); + break; + + case BT_HCI_CMD_READ_BUFFER_SIZE: + rbs.status = BT_HCI_ERR_SUCCESS; + rbs.acl_mtu = cpu_to_le16(btdev->acl_mtu); + rbs.sco_mtu = 0; + rbs.acl_max_pkt = cpu_to_le16(btdev->acl_max_pkt); + rbs.sco_max_pkt = cpu_to_le16(0); + cmd_complete(btdev, opcode, &rbs, sizeof(rbs)); + break; + + case BT_HCI_CMD_READ_COUNTRY_CODE: + rcc.status = BT_HCI_ERR_SUCCESS; + rcc.code = btdev->country_code; + cmd_complete(btdev, opcode, &rcc, sizeof(rcc)); + break; + + case BT_HCI_CMD_READ_BD_ADDR: + rba.status = BT_HCI_ERR_SUCCESS; + memcpy(rba.bdaddr, btdev->bdaddr, 6); + cmd_complete(btdev, opcode, &rba, sizeof(rba)); + break; + + case BT_HCI_CMD_READ_DATA_BLOCK_SIZE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rdbs.status = BT_HCI_ERR_SUCCESS; + rdbs.max_acl_len = cpu_to_le16(btdev->acl_mtu); + rdbs.block_len = cpu_to_le16(btdev->acl_mtu); + rdbs.num_blocks = cpu_to_le16(btdev->acl_max_pkt); + cmd_complete(btdev, opcode, &rdbs, sizeof(rdbs)); + break; + + case BT_HCI_CMD_READ_LOCAL_CODECS: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + rlsc = alloca(sizeof(*rlsc) + 7); + rlsc->status = BT_HCI_ERR_SUCCESS; + rlsc->num_codecs = 0x06; + rlsc->codec[0] = 0x00; + rlsc->codec[1] = 0x01; + rlsc->codec[2] = 0x02; + rlsc->codec[3] = 0x03; + rlsc->codec[4] = 0x04; + rlsc->codec[5] = 0x05; + rlsc->codec[6] = 0x00; + cmd_complete(btdev, opcode, rlsc, sizeof(*rlsc) + 7); + break; + + case BT_HCI_CMD_READ_RSSI: + rrssi = data; + + rrssi_rsp.status = BT_HCI_ERR_SUCCESS; + rrssi_rsp.handle = rrssi->handle; + rrssi_rsp.rssi = -1; /* non-zero so we can see it in tester */ + cmd_complete(btdev, opcode, &rrssi_rsp, sizeof(rrssi_rsp)); + break; + + case BT_HCI_CMD_READ_TX_POWER: + rtxp = data; + + switch (rtxp->type) { + case 0x00: + rtxp_rsp.status = BT_HCI_ERR_SUCCESS; + rtxp_rsp.level = -1; /* non-zero */ + break; + + case 0x01: + rtxp_rsp.status = BT_HCI_ERR_SUCCESS; + rtxp_rsp.level = 4; /* max for class 2 radio */ + break; + + default: + rtxp_rsp.level = 0; + rtxp_rsp.status = BT_HCI_ERR_INVALID_PARAMETERS; + break; + } + + rtxp_rsp.handle = rtxp->handle; + cmd_complete(btdev, opcode, &rtxp_rsp, sizeof(rtxp_rsp)); + break; + + case BT_HCI_CMD_READ_ENCRYPT_KEY_SIZE: + if (btdev->type != BTDEV_TYPE_BREDRLE && + btdev->type != BTDEV_TYPE_BREDR && + btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + reks = data; + read_enc_key_size_complete(btdev, le16_to_cpu(reks->handle)); + break; + + case BT_HCI_CMD_READ_LOCAL_AMP_INFO: + if (btdev->type != BTDEV_TYPE_AMP) + goto unsupported; + rlai.status = BT_HCI_ERR_SUCCESS; + rlai.amp_status = 0x01; /* Used for Bluetooth only */ + rlai.total_bw = cpu_to_le32(0); + rlai.max_bw = cpu_to_le32(0); + rlai.min_latency = cpu_to_le32(0); + rlai.max_pdu = cpu_to_le32(672); + rlai.amp_type = 0x01; /* 802.11 AMP Controller */ + rlai.pal_cap = cpu_to_le16(0x0000); + rlai.max_assoc_len = cpu_to_le16(672); + rlai.max_flush_to = cpu_to_le32(0xffffffff); + rlai.be_flush_to = cpu_to_le32(0xffffffff); + cmd_complete(btdev, opcode, &rlai, sizeof(rlai)); + break; + + case BT_HCI_CMD_READ_LOCAL_AMP_ASSOC: + if (btdev->type != BTDEV_TYPE_AMP) + goto unsupported; + rlaa_cmd = data; + rlaa_rsp.status = BT_HCI_ERR_SUCCESS; + rlaa_rsp.phy_handle = rlaa_cmd->phy_handle; + rlaa_rsp.remain_assoc_len = cpu_to_le16(1); + rlaa_rsp.assoc_fragment[0] = 0x42; + memset(rlaa_rsp.assoc_fragment + 1, 0, + sizeof(rlaa_rsp.assoc_fragment) - 1); + cmd_complete(btdev, opcode, &rlaa_rsp, sizeof(rlaa_rsp)); + break; + + case BT_HCI_CMD_GET_MWS_TRANSPORT_CONFIG: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + gmtc = alloca(sizeof(*gmtc)); + gmtc->status = BT_HCI_ERR_SUCCESS; + gmtc->num_transports = 0x00; + cmd_complete(btdev, opcode, gmtc, sizeof(*gmtc)); + break; + + case BT_HCI_CMD_SET_EVENT_MASK_PAGE2: + if (btdev->type == BTDEV_TYPE_BREDR20 || + btdev->type == BTDEV_TYPE_AMP) + goto unsupported; + semp2 = data; + memcpy(btdev->event_mask_page2, semp2->mask, 8); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_SET_EVENT_MASK: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lsem = data; + memcpy(btdev->le_event_mask, lsem->mask, 8); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_READ_BUFFER_SIZE: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lrbs.status = BT_HCI_ERR_SUCCESS; + lrbs.le_mtu = cpu_to_le16(btdev->acl_mtu); + lrbs.le_max_pkt = btdev->acl_max_pkt; + cmd_complete(btdev, opcode, &lrbs, sizeof(lrbs)); + break; + + case BT_HCI_CMD_LE_READ_LOCAL_FEATURES: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lrlf.status = BT_HCI_ERR_SUCCESS; + memcpy(lrlf.features, btdev->le_features, 8); + cmd_complete(btdev, opcode, &lrlf, sizeof(lrlf)); + break; + + case BT_HCI_CMD_LE_SET_RANDOM_ADDRESS: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lsra = data; + memcpy(btdev->random_addr, lsra->addr, 6); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_SET_ADV_PARAMETERS: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + + if (btdev->le_adv_enable) { + status = BT_HCI_ERR_COMMAND_DISALLOWED; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + } + + lsap = data; + btdev->le_adv_type = lsap->type; + btdev->le_adv_own_addr = lsap->own_addr_type; + btdev->le_adv_direct_addr_type = lsap->direct_addr_type; + memcpy(btdev->le_adv_direct_addr, lsap->direct_addr, 6); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_READ_ADV_TX_POWER: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lratp.status = BT_HCI_ERR_SUCCESS; + lratp.level = 0; + cmd_complete(btdev, opcode, &lratp, sizeof(lratp)); + break; + + case BT_HCI_CMD_LE_SET_ADV_ENABLE: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lsae = data; + if (btdev->le_adv_enable == lsae->enable) + status = BT_HCI_ERR_COMMAND_DISALLOWED; + else { + btdev->le_adv_enable = lsae->enable; + status = BT_HCI_ERR_SUCCESS; + } + cmd_complete(btdev, opcode, &status, sizeof(status)); + if (status == BT_HCI_ERR_SUCCESS && btdev->le_adv_enable) + le_set_adv_enable_complete(btdev); + break; + + case BT_HCI_CMD_LE_SET_SCAN_PARAMETERS: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + + lssp = data; + + if (btdev->le_scan_enable) + status = BT_HCI_ERR_COMMAND_DISALLOWED; + else { + status = BT_HCI_ERR_SUCCESS; + btdev->le_scan_type = lssp->type; + btdev->le_scan_own_addr_type = lssp->own_addr_type; + } + + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_SET_SCAN_ENABLE: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lsse = data; + if (btdev->le_scan_enable == lsse->enable) + status = BT_HCI_ERR_COMMAND_DISALLOWED; + else { + btdev->le_scan_enable = lsse->enable; + btdev->le_filter_dup = lsse->filter_dup; + status = BT_HCI_ERR_SUCCESS; + } + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_CREATE_CONN: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_LE_READ_WHITE_LIST_SIZE: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lrwls.status = BT_HCI_ERR_SUCCESS; + lrwls.size = 0; + cmd_complete(btdev, opcode, &lrwls, sizeof(lrwls)); + break; + + case BT_HCI_CMD_LE_CONN_UPDATE: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + + case BT_HCI_CMD_LE_CLEAR_WHITE_LIST: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_ENCRYPT: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lenc_cmd = data; + if (!bt_crypto_e(btdev->crypto, lenc_cmd->key, + lenc_cmd->plaintext, lenc.data)) { + cmd_status(btdev, BT_HCI_ERR_COMMAND_DISALLOWED, + opcode); + break; + } + lenc.status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &lenc, sizeof(lenc)); + break; + + case BT_HCI_CMD_LE_RAND: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + if (!bt_crypto_random_bytes(btdev->crypto, + (uint8_t *)&lr.number, 8)) { + cmd_status(btdev, BT_HCI_ERR_COMMAND_DISALLOWED, + opcode); + break; + } + lr.status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &lr, sizeof(lr)); + break; + + case BT_HCI_CMD_LE_READ_LOCAL_PK256: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + if (!ecc_make_key(pk_evt.local_pk256, btdev->le_local_sk256)) { + cmd_status(btdev, BT_HCI_ERR_COMMAND_DISALLOWED, + opcode); + break; + } + cmd_status(btdev, BT_HCI_ERR_SUCCESS, + BT_HCI_CMD_LE_READ_LOCAL_PK256); + pk_evt.status = BT_HCI_ERR_SUCCESS; + le_meta_event(btdev, BT_HCI_EVT_LE_READ_LOCAL_PK256_COMPLETE, + &pk_evt, sizeof(pk_evt)); + break; + + case BT_HCI_CMD_LE_GENERATE_DHKEY: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + dh = data; + if (!ecdh_shared_secret(dh->remote_pk256, btdev->le_local_sk256, + dh_evt.dhkey)) { + cmd_status(btdev, BT_HCI_ERR_COMMAND_DISALLOWED, + opcode); + break; + } + cmd_status(btdev, BT_HCI_ERR_SUCCESS, + BT_HCI_CMD_LE_GENERATE_DHKEY); + dh_evt.status = BT_HCI_ERR_SUCCESS; + le_meta_event(btdev, BT_HCI_EVT_LE_GENERATE_DHKEY_COMPLETE, + &dh_evt, sizeof(dh_evt)); + break; + + case BT_HCI_CMD_LE_READ_SUPPORTED_STATES: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lrss.status = BT_HCI_ERR_SUCCESS; + memcpy(lrss.states, btdev->le_states, 8); + cmd_complete(btdev, opcode, &lrss, sizeof(lrss)); + break; + + case BT_HCI_CMD_LE_SET_ADV_DATA: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lsad = data; + btdev->le_adv_data_len = lsad->len; + memcpy(btdev->le_adv_data, lsad->data, 31); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_SET_SCAN_RSP_DATA: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lssrd = data; + btdev->le_scan_data_len = lssrd->len; + memcpy(btdev->le_scan_data, lssrd->data, 31); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_READ_REMOTE_FEATURES: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + le_read_remote_features_complete(btdev); + break; + + case BT_HCI_CMD_LE_START_ENCRYPT: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lse = data; + memcpy(btdev->le_ltk, lse->ltk, 16); + le_start_encrypt_complete(btdev, lse->ediv, lse->rand); + break; + + case BT_HCI_CMD_LE_LTK_REQ_REPLY: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + llrr = data; + memcpy(btdev->le_ltk, llrr->ltk, 16); + le_encrypt_complete(btdev); + break; + + case BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + ltk_neg_reply_complete(btdev); + break; + + case BT_HCI_CMD_SETUP_SYNC_CONN: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + ssc = data; + status = BT_HCI_ERR_SUCCESS; + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + sync_conn_complete(btdev, ssc->voice_setting, + BT_HCI_ERR_SUCCESS); + break; + + case BT_HCI_CMD_ADD_SCO_CONN: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + sco_conn_complete(btdev, BT_HCI_ERR_SUCCESS); + break; + + case BT_HCI_CMD_ENABLE_DUT_MODE: + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_WRITE_SSP_DEBUG_MODE: + if (btdev->type == BTDEV_TYPE_LE) + goto unsupported; + wsdm = data; + btdev->ssp_debug_mode = wsdm->mode; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_RECEIVER_TEST: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_TRANSMITTER_TEST: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + + case BT_HCI_CMD_LE_TEST_END: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lte.status = BT_HCI_ERR_SUCCESS; + lte.num_packets = 0; + cmd_complete(btdev, opcode, <e, sizeof(lte)); + break; + + case BT_HCI_CMD_LE_CONN_PARAM_REQ_REPLY: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lcprr_cmd = data; + lcprr_rsp.handle = lcprr_cmd->handle; + lcprr_rsp.status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &lcprr_rsp, sizeof(lcprr_rsp)); + break; + case BT_HCI_CMD_LE_CONN_PARAM_REQ_NEG_REPLY: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + lcprnr_cmd = data; + lcprnr_rsp.handle = lcprnr_cmd->handle; + lcprnr_rsp.status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &lcprnr_rsp, sizeof(lcprnr_rsp)); + break; + case BT_HCI_CMD_LE_READ_NUM_SUPPORTED_ADV_SETS: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + rlrnsas.status = BT_HCI_ERR_SUCCESS; + /* Support one set as of now */ + rlrnsas.num_of_sets = 1; + cmd_complete(btdev, opcode, &rlrnsas, sizeof(rlrnsas)); + break; + case BT_HCI_CMD_LE_SET_ADV_SET_RAND_ADDR: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + lsasra = data; + memcpy(btdev->random_addr, lsasra->bdaddr, 6); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + case BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + if (btdev->le_adv_enable) { + status = BT_HCI_ERR_COMMAND_DISALLOWED; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + } + + lseap = data; + btdev->le_ext_adv_type = le16_to_cpu(lseap->evt_properties); + btdev->le_adv_own_addr = lseap->own_addr_type; + btdev->le_adv_direct_addr_type = lseap->peer_addr_type; + memcpy(btdev->le_adv_direct_addr, lseap->peer_addr, 6); + + rlseap.status = BT_HCI_ERR_SUCCESS; + rlseap.tx_power = 0; + cmd_complete(btdev, opcode, &rlseap, sizeof(rlseap)); + break; + case BT_HCI_CMD_LE_SET_EXT_ADV_ENABLE: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + lseae = data; + if (btdev->le_adv_enable == lseae->enable) + status = BT_HCI_ERR_COMMAND_DISALLOWED; + else { + btdev->le_adv_enable = lseae->enable; + status = BT_HCI_ERR_SUCCESS; + } + cmd_complete(btdev, opcode, &status, sizeof(status)); + if (status == BT_HCI_ERR_SUCCESS && btdev->le_adv_enable) + le_set_ext_adv_enable_complete(btdev); + break; + case BT_HCI_CMD_LE_SET_EXT_ADV_DATA: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + lsead = data; + btdev->le_adv_data_len = lsead->data_len; + memcpy(btdev->le_adv_data, lsead->data, 31); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + case BT_HCI_CMD_LE_SET_EXT_SCAN_RSP_DATA: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + lsesrd = data; + btdev->le_scan_data_len = lsesrd->data_len; + memcpy(btdev->le_scan_data, lsesrd->data, 31); + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + case BT_HCI_CMD_LE_REMOVE_ADV_SET: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + case BT_HCI_CMD_LE_CLEAR_ADV_SETS: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + case BT_HCI_CMD_LE_SET_DEFAULT_PHY: + if (btdev->type == BTDEV_TYPE_BREDR) + goto unsupported; + phys = data; + if (phys->all_phys > 0x03 || + (!(phys->all_phys & 0x01) && + (!phys->tx_phys || phys->tx_phys > 0x07)) || + (!(phys->all_phys & 0x02) && + (!phys->rx_phys || phys->rx_phys > 0x07))) + status = BT_HCI_ERR_INVALID_PARAMETERS; + else + status = BT_HCI_ERR_SUCCESS; + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + case BT_HCI_CMD_LE_SET_EXT_SCAN_PARAMS: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + lsesp = data; + lsp = (void *)lsesp->data; + + if (btdev->le_scan_enable) + status = BT_HCI_ERR_COMMAND_DISALLOWED; + else if (lsesp->num_phys == 0) + status = BT_HCI_ERR_INVALID_PARAMETERS; + else { + status = BT_HCI_ERR_SUCCESS; + /* Currently we dont support multiple types in single + * command So just take the first one will do. + */ + btdev->le_scan_type = lsp->type; + btdev->le_scan_own_addr_type = lsesp->own_addr_type; + } + + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + case BT_HCI_CMD_LE_SET_EXT_SCAN_ENABLE: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + lsese = data; + if (btdev->le_scan_enable == lsese->enable) + status = BT_HCI_ERR_COMMAND_DISALLOWED; + else { + btdev->le_scan_enable = lsese->enable; + btdev->le_filter_dup = lsese->filter_dup; + status = BT_HCI_ERR_SUCCESS; + } + cmd_complete(btdev, opcode, &status, sizeof(status)); + break; + case BT_HCI_CMD_LE_EXT_CREATE_CONN: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + goto unsupported; + + cmd_status(btdev, BT_HCI_ERR_SUCCESS, opcode); + break; + default: + goto unsupported; + } + + return; + +unsupported: + printf("Unsupported command 0x%4.4x\n", opcode); + hexdump(data, len); + cmd_status(btdev, BT_HCI_ERR_UNKNOWN_COMMAND, opcode); +} + +static void default_cmd_completion(struct btdev *btdev, uint16_t opcode, + const void *data, uint8_t len) +{ + const struct bt_hci_cmd_create_conn *cc; + const struct bt_hci_cmd_disconnect *dc; + const struct bt_hci_cmd_create_conn_cancel *ccc; + const struct bt_hci_cmd_accept_conn_request *acr; + const struct bt_hci_cmd_reject_conn_request *rcr; + const struct bt_hci_cmd_auth_requested *ar; + const struct bt_hci_cmd_set_conn_encrypt *sce; + const struct bt_hci_cmd_link_key_request_reply *lkrr; + const struct bt_hci_cmd_link_key_request_neg_reply *lkrnr; + const struct bt_hci_cmd_pin_code_request_neg_reply *pcrnr; + const struct bt_hci_cmd_pin_code_request_reply *pcrr; + const struct bt_hci_cmd_remote_name_request *rnr; + const struct bt_hci_cmd_remote_name_request_cancel *rnrc; + const struct bt_hci_cmd_read_remote_features *rrf; + const struct bt_hci_cmd_read_remote_ext_features *rref; + const struct bt_hci_cmd_read_remote_version *rrv; + const struct bt_hci_cmd_read_clock_offset *rco; + const struct bt_hci_cmd_le_create_conn *lecc; + const struct bt_hci_cmd_le_conn_update *lecu; + const struct bt_hci_cmd_le_conn_param_req_reply *lcprr; + const struct bt_hci_cmd_le_conn_param_req_neg_reply *lcprnr; + const struct bt_hci_cmd_le_set_scan_enable *lsse; + const struct bt_hci_cmd_le_set_ext_scan_enable *lsese; + const struct bt_hci_cmd_le_ext_create_conn *leecc; + + switch (opcode) { + case BT_HCI_CMD_INQUIRY: + if (btdev->type == BTDEV_TYPE_LE) + return; + inquiry_cmd(btdev, data); + break; + + case BT_HCI_CMD_CREATE_CONN: + if (btdev->type == BTDEV_TYPE_LE) + return; + cc = data; + conn_request(btdev, cc->bdaddr); + break; + + case BT_HCI_CMD_DISCONNECT: + dc = data; + disconnect_complete(btdev, le16_to_cpu(dc->handle), dc->reason); + break; + + case BT_HCI_CMD_CREATE_CONN_CANCEL: + if (btdev->type == BTDEV_TYPE_LE) + return; + ccc = data; + conn_complete(btdev, ccc->bdaddr, BT_HCI_ERR_UNKNOWN_CONN_ID); + break; + + case BT_HCI_CMD_ACCEPT_CONN_REQUEST: + if (btdev->type == BTDEV_TYPE_LE) + return; + acr = data; + accept_conn_request_complete(btdev, acr->bdaddr); + break; + + case BT_HCI_CMD_REJECT_CONN_REQUEST: + if (btdev->type == BTDEV_TYPE_LE) + return; + rcr = data; + conn_complete(btdev, rcr->bdaddr, BT_HCI_ERR_UNKNOWN_CONN_ID); + break; + + case BT_HCI_CMD_LINK_KEY_REQUEST_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + return; + lkrr = data; + link_key_req_reply_complete(btdev, lkrr->bdaddr, lkrr->link_key); + break; + + case BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + return; + lkrnr = data; + link_key_req_neg_reply_complete(btdev, lkrnr->bdaddr); + break; + + case BT_HCI_CMD_PIN_CODE_REQUEST_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + return; + pcrr = data; + pin_code_req_reply_complete(btdev, pcrr->bdaddr, pcrr->pin_len, + pcrr->pin_code); + break; + + case BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY: + if (btdev->type == BTDEV_TYPE_LE) + return; + pcrnr = data; + pin_code_req_neg_reply_complete(btdev, pcrnr->bdaddr); + break; + + case BT_HCI_CMD_AUTH_REQUESTED: + if (btdev->type == BTDEV_TYPE_LE) + return; + ar = data; + auth_request_complete(btdev, le16_to_cpu(ar->handle)); + break; + + case BT_HCI_CMD_SET_CONN_ENCRYPT: + if (btdev->type == BTDEV_TYPE_LE) + return; + sce = data; + if (btdev->conn) { + uint8_t mode; + + if (!sce->encr_mode) + mode = 0x00; + else if (btdev->secure_conn_support && + btdev->conn->secure_conn_support) + mode = 0x02; + else + mode = 0x01; + + encrypt_change(btdev, mode, BT_HCI_ERR_SUCCESS); + encrypt_change(btdev->conn, mode, BT_HCI_ERR_SUCCESS); + } + break; + + case BT_HCI_CMD_REMOTE_NAME_REQUEST: + if (btdev->type == BTDEV_TYPE_LE) + return; + rnr = data; + name_request_complete(btdev, rnr->bdaddr, BT_HCI_ERR_SUCCESS); + break; + + case BT_HCI_CMD_REMOTE_NAME_REQUEST_CANCEL: + if (btdev->type == BTDEV_TYPE_LE) + return; + rnrc = data; + name_request_complete(btdev, rnrc->bdaddr, + BT_HCI_ERR_UNKNOWN_CONN_ID); + break; + + case BT_HCI_CMD_READ_REMOTE_FEATURES: + if (btdev->type == BTDEV_TYPE_LE) + return; + rrf = data; + remote_features_complete(btdev, le16_to_cpu(rrf->handle)); + break; + + case BT_HCI_CMD_READ_REMOTE_EXT_FEATURES: + if (btdev->type == BTDEV_TYPE_LE) + return; + rref = data; + remote_ext_features_complete(btdev, le16_to_cpu(rref->handle), + rref->page); + break; + + case BT_HCI_CMD_READ_REMOTE_VERSION: + rrv = data; + remote_version_complete(btdev, le16_to_cpu(rrv->handle)); + break; + + case BT_HCI_CMD_READ_CLOCK_OFFSET: + if (btdev->type == BTDEV_TYPE_LE) + return; + rco = data; + remote_clock_offset_complete(btdev, le16_to_cpu(rco->handle)); + break; + + case BT_HCI_CMD_LE_CREATE_CONN: + if (btdev->type == BTDEV_TYPE_BREDR) + return; + lecc = data; + btdev->le_scan_own_addr_type = lecc->own_addr_type; + le_conn_request(btdev, lecc); + break; + + case BT_HCI_CMD_LE_CONN_UPDATE: + if (btdev->type == BTDEV_TYPE_BREDR) + return; + lecu = data; + if (btdev->le_features[0] & 0x02) + le_conn_param_req(btdev, le16_to_cpu(lecu->handle), + le16_to_cpu(lecu->min_interval), + le16_to_cpu(lecu->max_interval), + le16_to_cpu(lecu->latency), + le16_to_cpu(lecu->supv_timeout), + le16_to_cpu(lecu->min_length), + le16_to_cpu(lecu->max_length)); + else + le_conn_update(btdev, le16_to_cpu(lecu->handle), + le16_to_cpu(lecu->min_interval), + le16_to_cpu(lecu->max_interval), + le16_to_cpu(lecu->latency), + le16_to_cpu(lecu->supv_timeout), + le16_to_cpu(lecu->min_length), + le16_to_cpu(lecu->max_length)); + break; + case BT_HCI_CMD_LE_CONN_PARAM_REQ_REPLY: + if (btdev->type == BTDEV_TYPE_BREDR) + return; + lcprr = data; + le_conn_update(btdev, le16_to_cpu(lcprr->handle), + le16_to_cpu(lcprr->min_interval), + le16_to_cpu(lcprr->max_interval), + le16_to_cpu(lcprr->latency), + le16_to_cpu(lcprr->supv_timeout), + le16_to_cpu(lcprr->min_length), + le16_to_cpu(lcprr->max_length)); + break; + case BT_HCI_CMD_LE_CONN_PARAM_REQ_NEG_REPLY: + if (btdev->type == BTDEV_TYPE_BREDR) + return; + lcprnr = data; + rej_le_conn_update(btdev, le16_to_cpu(lcprnr->handle), + le16_to_cpu(lcprnr->reason)); + break; + break; + case BT_HCI_CMD_LE_SET_SCAN_ENABLE: + if (btdev->type == BTDEV_TYPE_BREDR) + return; + lsse = data; + if (btdev->le_scan_enable && lsse->enable) + le_set_scan_enable_complete(btdev); + break; + case BT_HCI_CMD_LE_SET_EXT_SCAN_ENABLE: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + return; + lsese = data; + if (btdev->le_scan_enable && lsese->enable) + le_set_ext_scan_enable_complete(btdev); + break; + case BT_HCI_CMD_LE_EXT_CREATE_CONN: + if (btdev->type != BTDEV_TYPE_BREDRLE50) + return; + leecc = data; + btdev->le_scan_own_addr_type = leecc->own_addr_type; + le_ext_conn_request(btdev, leecc); + break; + } +} + +struct btdev_callback { + void (*function)(btdev_callback callback, uint8_t response, + uint8_t status, const void *data, uint8_t len); + void *user_data; + uint16_t opcode; + const void *data; + uint8_t len; +}; + +void btdev_command_response(btdev_callback callback, uint8_t response, + uint8_t status, const void *data, uint8_t len) +{ + callback->function(callback, response, status, data, len); +} + +static void handler_callback(btdev_callback callback, uint8_t response, + uint8_t status, const void *data, uint8_t len) +{ + struct btdev *btdev = callback->user_data; + + switch (response) { + case BTDEV_RESPONSE_DEFAULT: + if (!run_hooks(btdev, BTDEV_HOOK_PRE_CMD, callback->opcode, + callback->data, callback->len)) + return; + default_cmd(btdev, callback->opcode, + callback->data, callback->len); + + if (!run_hooks(btdev, BTDEV_HOOK_PRE_EVT, callback->opcode, + callback->data, callback->len)) + return; + default_cmd_completion(btdev, callback->opcode, + callback->data, callback->len); + break; + case BTDEV_RESPONSE_COMMAND_STATUS: + cmd_status(btdev, status, callback->opcode); + break; + case BTDEV_RESPONSE_COMMAND_COMPLETE: + cmd_complete(btdev, callback->opcode, data, len); + break; + default: + cmd_status(btdev, BT_HCI_ERR_UNKNOWN_COMMAND, + callback->opcode); + break; + } +} + +static void process_cmd(struct btdev *btdev, const void *data, uint16_t len) +{ + struct btdev_callback callback; + const struct bt_hci_cmd_hdr *hdr = data; + + if (len < sizeof(*hdr)) + return; + + callback.function = handler_callback; + callback.user_data = btdev; + callback.opcode = le16_to_cpu(hdr->opcode); + callback.data = data + sizeof(*hdr); + callback.len = hdr->plen; + + if (btdev->command_handler) + btdev->command_handler(callback.opcode, + callback.data, callback.len, + &callback, btdev->command_data); + else { + if (!run_hooks(btdev, BTDEV_HOOK_PRE_CMD, callback.opcode, + callback.data, callback.len)) + return; + default_cmd(btdev, callback.opcode, + callback.data, callback.len); + + if (!run_hooks(btdev, BTDEV_HOOK_PRE_EVT, callback.opcode, + callback.data, callback.len)) + return; + default_cmd_completion(btdev, callback.opcode, + callback.data, callback.len); + } +} + +static void send_acl(struct btdev *conn, const void *data, uint16_t len) +{ + struct bt_hci_acl_hdr hdr; + struct iovec iov[3]; + + /* Packet type */ + iov[0].iov_base = (void *) data; + iov[0].iov_len = 1; + + /* ACL_START_NO_FLUSH is only allowed from host to controller. + * From controller to host this should be converted to ACL_START. + */ + memcpy(&hdr, data + 1, sizeof(hdr)); + if (acl_flags(hdr.handle) == ACL_START_NO_FLUSH) + hdr.handle = acl_handle_pack(acl_handle(hdr.handle), ACL_START); + + iov[1].iov_base = &hdr; + iov[1].iov_len = sizeof(hdr); + + iov[2].iov_base = (void *) (data + 1 + sizeof(hdr)); + iov[2].iov_len = len - 1 - sizeof(hdr); + + send_packet(conn, iov, 3); +} + +void btdev_receive_h4(struct btdev *btdev, const void *data, uint16_t len) +{ + uint8_t pkt_type; + + if (!btdev) + return; + + if (len < 1) + return; + + pkt_type = ((const uint8_t *) data)[0]; + + switch (pkt_type) { + case BT_H4_CMD_PKT: + process_cmd(btdev, data + 1, len - 1); + break; + case BT_H4_ACL_PKT: + if (btdev->conn) + send_acl(btdev->conn, data, len); + num_completed_packets(btdev); + break; + default: + printf("Unsupported packet 0x%2.2x\n", pkt_type); + break; + } +} + +int btdev_add_hook(struct btdev *btdev, enum btdev_hook_type type, + uint16_t opcode, btdev_hook_func handler, + void *user_data) +{ + int i; + + if (!btdev) + return -1; + + if (get_hook_index(btdev, type, opcode) > 0) + return -1; + + for (i = 0; i < MAX_HOOK_ENTRIES; i++) { + if (btdev->hook_list[i] == NULL) { + btdev->hook_list[i] = malloc(sizeof(struct hook)); + if (btdev->hook_list[i] == NULL) + return -1; + + btdev->hook_list[i]->handler = handler; + btdev->hook_list[i]->user_data = user_data; + btdev->hook_list[i]->opcode = opcode; + btdev->hook_list[i]->type = type; + return i; + } + } + + return -1; +} + +bool btdev_del_hook(struct btdev *btdev, enum btdev_hook_type type, + uint16_t opcode) +{ + int i; + + if (!btdev) + return false; + + for (i = 0; i < MAX_HOOK_ENTRIES; i++) { + if (btdev->hook_list[i] == NULL) + continue; + + if (btdev->hook_list[i]->type != type || + btdev->hook_list[i]->opcode != opcode) + continue; + + free(btdev->hook_list[i]); + btdev->hook_list[i] = NULL; + + return true; + } + + return false; +} diff --git a/emulator/btdev.h b/emulator/btdev.h new file mode 100644 index 0000000..362d1e7 --- /dev/null +++ b/emulator/btdev.h @@ -0,0 +1,102 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#define BTDEV_RESPONSE_DEFAULT 0 +#define BTDEV_RESPONSE_COMMAND_STATUS 1 +#define BTDEV_RESPONSE_COMMAND_COMPLETE 2 + +typedef struct btdev_callback * btdev_callback; + +void btdev_command_response(btdev_callback callback, uint8_t response, + uint8_t status, const void *data, uint8_t len); + +#define btdev_command_default(callback) \ + btdev_command_response(callback, \ + BTDEV_RESPONSE_DEFAULT, 0x00, NULL, 0); + +#define btdev_command_status(callback, status) \ + btdev_command_response(callback, \ + BTDEV_RESPONSE_COMMAND_STATUS, status, NULL, 0); + +#define btdev_command_complete(callback, data, len) \ + btdev_command_response(callback, \ + BTDEV_RESPONSE_COMMAND_COMPLETE, 0x00, data, len); + + +typedef void (*btdev_command_func) (uint16_t opcode, + const void *data, uint8_t len, + btdev_callback callback, void *user_data); + +typedef void (*btdev_send_func) (const struct iovec *iov, int iovlen, + void *user_data); + +typedef bool (*btdev_hook_func) (const void *data, uint16_t len, + void *user_data); + +enum btdev_type { + BTDEV_TYPE_BREDRLE, + BTDEV_TYPE_BREDR, + BTDEV_TYPE_LE, + BTDEV_TYPE_AMP, + BTDEV_TYPE_BREDR20, + BTDEV_TYPE_BREDRLE50, +}; + +enum btdev_hook_type { + BTDEV_HOOK_PRE_CMD, + BTDEV_HOOK_POST_CMD, + BTDEV_HOOK_PRE_EVT, + BTDEV_HOOK_POST_EVT, +}; + +struct btdev; + +struct btdev *btdev_create(enum btdev_type type, uint16_t id); +void btdev_destroy(struct btdev *btdev); + +const uint8_t *btdev_get_bdaddr(struct btdev *btdev); +uint8_t *btdev_get_features(struct btdev *btdev); + +uint8_t btdev_get_scan_enable(struct btdev *btdev); + +uint8_t btdev_get_le_scan_enable(struct btdev *btdev); + +void btdev_set_le_states(struct btdev *btdev, const uint8_t *le_states); + +void btdev_set_command_handler(struct btdev *btdev, btdev_command_func handler, + void *user_data); + +void btdev_set_send_handler(struct btdev *btdev, btdev_send_func handler, + void *user_data); +void btdev_receive_h4(struct btdev *btdev, const void *data, uint16_t len); + +int btdev_add_hook(struct btdev *btdev, enum btdev_hook_type type, + uint16_t opcode, btdev_hook_func handler, + void *user_data); + +bool btdev_del_hook(struct btdev *btdev, enum btdev_hook_type type, + uint16_t opcode); diff --git a/emulator/bthost.c b/emulator/bthost.c new file mode 100644 index 0000000..6482bbe --- /dev/null +++ b/emulator/bthost.c @@ -0,0 +1,2687 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" + +#include "src/shared/util.h" +#include "monitor/bt.h" +#include "monitor/rfcomm.h" +#include "bthost.h" + +#define lmp_bredr_capable(bthost) (!((bthost)->features[4] & 0x20)) + +/* ACL handle and flags pack/unpack */ +#define acl_handle_pack(h, f) (uint16_t)((h & 0x0fff)|(f << 12)) +#define acl_handle(h) (h & 0x0fff) +#define acl_flags(h) (h >> 12) + +#define L2CAP_FEAT_FIXED_CHAN 0x00000080 +#define L2CAP_FC_SIG_BREDR 0x02 +#define L2CAP_FC_SMP_BREDR 0x80 +#define L2CAP_IT_FEAT_MASK 0x0002 +#define L2CAP_IT_FIXED_CHAN 0x0003 + +/* RFCOMM setters */ +#define RFCOMM_ADDR(cr, dlci) (((dlci & 0x3f) << 2) | (cr << 1) | 0x01) +#define RFCOMM_CTRL(type, pf) (((type & 0xef) | (pf << 4))) +#define RFCOMM_LEN8(len) (((len) << 1) | 1) +#define RFCOMM_LEN16(len) ((len) << 1) +#define RFCOMM_MCC_TYPE(cr, type) (((type << 2) | (cr << 1) | 0x01)) + +/* RFCOMM FCS calculation */ +#define CRC(data) (rfcomm_crc_table[rfcomm_crc_table[0xff ^ data[0]] ^ data[1]]) + +static unsigned char rfcomm_crc_table[256] = { + 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, + 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, + 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, + 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, + + 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, + 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, + 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, + 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, + + 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, + 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, + 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, + 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, + + 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, + 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, + 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, + 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, + + 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, + 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, + 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, + 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, + + 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, + 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, + 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, + 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, + + 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, + 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, + 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, + 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, + + 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, + 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, + 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, + 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf +}; + +static uint8_t rfcomm_fcs2(uint8_t *data) +{ + return 0xff - rfcomm_crc_table[CRC(data) ^ data[2]]; +} + +static uint8_t rfcomm_fcs(uint8_t *data) +{ + return 0xff - CRC(data); +} + +struct cmd { + struct cmd *next; + struct cmd *prev; + uint8_t data[256 + sizeof(struct bt_hci_cmd_hdr)]; + uint16_t len; +}; + +struct cmd_queue { + struct cmd *head; + struct cmd *tail; +}; + +struct cid_hook { + uint16_t cid; + bthost_cid_hook_func_t func; + void *user_data; + struct cid_hook *next; +}; + +struct rfcomm_chan_hook { + uint8_t channel; + bthost_rfcomm_chan_hook_func_t func; + void *user_data; + struct rfcomm_chan_hook *next; +}; + +struct btconn { + uint16_t handle; + uint8_t bdaddr[6]; + uint8_t addr_type; + uint8_t encr_mode; + uint16_t next_cid; + uint64_t fixed_chan; + struct l2conn *l2conns; + struct rcconn *rcconns; + struct cid_hook *cid_hooks; + struct rfcomm_chan_hook *rfcomm_chan_hooks; + struct btconn *next; + void *smp_data; +}; + +struct l2conn { + uint16_t scid; + uint16_t dcid; + uint16_t psm; + struct l2conn *next; +}; + +struct rcconn { + uint8_t channel; + uint16_t scid; + struct rcconn *next; +}; + +struct l2cap_pending_req { + uint8_t ident; + bthost_l2cap_rsp_cb cb; + void *user_data; + struct l2cap_pending_req *next; +}; + +struct l2cap_conn_cb_data { + uint16_t psm; + bthost_l2cap_connect_cb func; + void *user_data; + struct l2cap_conn_cb_data *next; +}; + +struct rfcomm_conn_cb_data { + uint8_t channel; + bthost_rfcomm_connect_cb func; + void *user_data; + struct rfcomm_conn_cb_data *next; +}; + +struct rfcomm_connection_data { + uint8_t channel; + struct btconn *conn; + bthost_rfcomm_connect_cb cb; + void *user_data; +}; + +struct bthost { + bool ready; + bthost_ready_cb ready_cb; + uint8_t bdaddr[6]; + uint8_t features[8]; + bthost_send_func send_handler; + void *send_data; + struct cmd_queue cmd_q; + uint8_t ncmd; + struct btconn *conns; + bthost_cmd_complete_cb cmd_complete_cb; + void *cmd_complete_data; + bthost_new_conn_cb new_conn_cb; + void *new_conn_data; + struct rfcomm_connection_data *rfcomm_conn_data; + struct l2cap_conn_cb_data *new_l2cap_conn_data; + struct rfcomm_conn_cb_data *new_rfcomm_conn_data; + struct l2cap_pending_req *l2reqs; + uint8_t pin[16]; + uint8_t pin_len; + uint8_t io_capability; + uint8_t auth_req; + bool reject_user_confirm; + void *smp_data; + bool conn_init; + bool le; + bool sc; +}; + +struct bthost *bthost_create(void) +{ + struct bthost *bthost; + + bthost = new0(struct bthost, 1); + if (!bthost) + return NULL; + + bthost->smp_data = smp_start(bthost); + if (!bthost->smp_data) { + free(bthost); + return NULL; + } + + /* Set defaults */ + bthost->io_capability = 0x03; + + return bthost; +} + +static void l2conn_free(struct l2conn *conn) +{ + free(conn); +} + +static void btconn_free(struct btconn *conn) +{ + if (conn->smp_data) + smp_conn_del(conn->smp_data); + + while (conn->l2conns) { + struct l2conn *l2conn = conn->l2conns; + + conn->l2conns = l2conn->next; + l2conn_free(l2conn); + } + + while (conn->cid_hooks) { + struct cid_hook *hook = conn->cid_hooks; + + conn->cid_hooks = hook->next; + free(hook); + } + + while (conn->rcconns) { + struct rcconn *rcconn = conn->rcconns; + + conn->rcconns = rcconn->next; + free(rcconn); + } + + while (conn->rfcomm_chan_hooks) { + struct rfcomm_chan_hook *hook = conn->rfcomm_chan_hooks; + + conn->rfcomm_chan_hooks = hook->next; + free(hook); + } + + free(conn); +} + +static struct btconn *bthost_find_conn(struct bthost *bthost, uint16_t handle) +{ + struct btconn *conn; + + for (conn = bthost->conns; conn != NULL; conn = conn->next) { + if (conn->handle == handle) + return conn; + } + + return NULL; +} + +static struct btconn *bthost_find_conn_by_bdaddr(struct bthost *bthost, + const uint8_t *bdaddr) +{ + struct btconn *conn; + + for (conn = bthost->conns; conn != NULL; conn = conn->next) { + if (!memcmp(conn->bdaddr, bdaddr, 6)) + return conn; + } + + return NULL; +} + +static struct l2conn *bthost_add_l2cap_conn(struct bthost *bthost, + struct btconn *conn, + uint16_t scid, uint16_t dcid, + uint16_t psm) +{ + struct l2conn *l2conn; + + l2conn = malloc(sizeof(*l2conn)); + if (!l2conn) + return NULL; + + memset(l2conn, 0, sizeof(*l2conn)); + + l2conn->psm = psm; + l2conn->scid = scid; + l2conn->dcid = dcid; + + l2conn->next = conn->l2conns; + conn->l2conns = l2conn; + + return l2conn; +} + +static struct rcconn *bthost_add_rfcomm_conn(struct bthost *bthost, + struct btconn *conn, + struct l2conn *l2conn, + uint8_t channel) +{ + struct rcconn *rcconn; + + rcconn = malloc(sizeof(*rcconn)); + if (!rcconn) + return NULL; + + memset(rcconn, 0, sizeof(*rcconn)); + + rcconn->channel = channel; + rcconn->scid = l2conn->scid; + + rcconn->next = conn->rcconns; + conn->rcconns = rcconn; + + return rcconn; +} + +static struct rcconn *btconn_find_rfcomm_conn_by_channel(struct btconn *conn, + uint8_t chan) +{ + struct rcconn *rcconn; + + for (rcconn = conn->rcconns; rcconn != NULL; rcconn = rcconn->next) { + if (rcconn->channel == chan) + return rcconn; + } + + return NULL; +} + +static struct l2conn *btconn_find_l2cap_conn_by_scid(struct btconn *conn, + uint16_t scid) +{ + struct l2conn *l2conn; + + for (l2conn = conn->l2conns; l2conn != NULL; l2conn = l2conn->next) { + if (l2conn->scid == scid) + return l2conn; + } + + return NULL; +} + +static struct l2cap_conn_cb_data *bthost_find_l2cap_cb_by_psm( + struct bthost *bthost, uint16_t psm) +{ + struct l2cap_conn_cb_data *cb; + + for (cb = bthost->new_l2cap_conn_data; cb != NULL; cb = cb->next) { + if (cb->psm == psm) + return cb; + } + + return NULL; +} + +static struct rfcomm_conn_cb_data *bthost_find_rfcomm_cb_by_channel( + struct bthost *bthost, uint8_t channel) +{ + struct rfcomm_conn_cb_data *cb; + + for (cb = bthost->new_rfcomm_conn_data; cb != NULL; cb = cb->next) { + if (cb->channel == channel) + return cb; + } + + return NULL; +} + +void bthost_destroy(struct bthost *bthost) +{ + if (!bthost) + return; + + while (bthost->cmd_q.tail) { + struct cmd *cmd = bthost->cmd_q.tail; + + bthost->cmd_q.tail = cmd->prev; + free(cmd); + } + + while (bthost->conns) { + struct btconn *conn = bthost->conns; + + bthost->conns = conn->next; + btconn_free(conn); + } + + while (bthost->l2reqs) { + struct l2cap_pending_req *req = bthost->l2reqs; + + bthost->l2reqs = req->next; + req->cb(0, NULL, 0, req->user_data); + free(req); + } + + while (bthost->new_l2cap_conn_data) { + struct l2cap_conn_cb_data *cb = bthost->new_l2cap_conn_data; + + bthost->new_l2cap_conn_data = cb->next; + free(cb); + } + + while (bthost->new_rfcomm_conn_data) { + struct rfcomm_conn_cb_data *cb = bthost->new_rfcomm_conn_data; + + bthost->new_rfcomm_conn_data = cb->next; + free(cb); + } + + if (bthost->rfcomm_conn_data) + free(bthost->rfcomm_conn_data); + + smp_stop(bthost->smp_data); + + free(bthost); +} + +void bthost_set_send_handler(struct bthost *bthost, bthost_send_func handler, + void *user_data) +{ + if (!bthost) + return; + + bthost->send_handler = handler; + bthost->send_data = user_data; +} + +static void queue_command(struct bthost *bthost, const struct iovec *iov, + int iovlen) +{ + struct cmd_queue *cmd_q = &bthost->cmd_q; + struct cmd *cmd; + int i; + + cmd = malloc(sizeof(*cmd)); + if (!cmd) + return; + + memset(cmd, 0, sizeof(*cmd)); + + for (i = 0; i < iovlen; i++) { + memcpy(cmd->data + cmd->len, iov[i].iov_base, iov[i].iov_len); + cmd->len += iov[i].iov_len; + } + + if (cmd_q->tail) + cmd_q->tail->next = cmd; + else + cmd_q->head = cmd; + + cmd->prev = cmd_q->tail; + cmd_q->tail = cmd; +} + +static void send_packet(struct bthost *bthost, const struct iovec *iov, + int iovlen) +{ + if (!bthost->send_handler) + return; + + bthost->send_handler(iov, iovlen, bthost->send_data); +} + +static void send_iov(struct bthost *bthost, uint16_t handle, uint16_t cid, + const struct iovec *iov, int iovcnt) +{ + struct bt_hci_acl_hdr acl_hdr; + struct bt_l2cap_hdr l2_hdr; + uint8_t pkt = BT_H4_ACL_PKT; + struct iovec pdu[3 + iovcnt]; + int i, len = 0; + + for (i = 0; i < iovcnt; i++) { + pdu[3 + i].iov_base = iov[i].iov_base; + pdu[3 + i].iov_len = iov[i].iov_len; + len += iov[i].iov_len; + } + + pdu[0].iov_base = &pkt; + pdu[0].iov_len = sizeof(pkt); + + acl_hdr.handle = acl_handle_pack(handle, 0); + acl_hdr.dlen = cpu_to_le16(len + sizeof(l2_hdr)); + + pdu[1].iov_base = &acl_hdr; + pdu[1].iov_len = sizeof(acl_hdr); + + l2_hdr.cid = cpu_to_le16(cid); + l2_hdr.len = cpu_to_le16(len); + + pdu[2].iov_base = &l2_hdr; + pdu[2].iov_len = sizeof(l2_hdr); + + send_packet(bthost, pdu, 3 + iovcnt); +} + +static void send_acl(struct bthost *bthost, uint16_t handle, uint16_t cid, + const void *data, uint16_t len) +{ + struct iovec iov; + + iov.iov_base = (void *) data; + iov.iov_len = len; + + send_iov(bthost, handle, cid, &iov, 1); +} + +static uint8_t l2cap_sig_send(struct bthost *bthost, struct btconn *conn, + uint8_t code, uint8_t ident, + const void *data, uint16_t len) +{ + static uint8_t next_ident = 1; + struct bt_l2cap_hdr_sig hdr; + uint16_t cid; + struct iovec iov[2]; + + if (!ident) { + ident = next_ident++; + if (!ident) + ident = next_ident++; + } + + hdr.code = code; + hdr.ident = ident; + hdr.len = cpu_to_le16(len); + + iov[0].iov_base = &hdr; + iov[0].iov_len = sizeof(hdr); + + if (conn->addr_type == BDADDR_BREDR) + cid = 0x0001; + else + cid = 0x0005; + + if (len == 0) { + send_iov(bthost, conn->handle, cid, iov, 1); + return ident; + } + + iov[1].iov_base = (void *) data; + iov[1].iov_len = len; + + send_iov(bthost, conn->handle, cid, iov, 2); + + return ident; +} + +void bthost_add_cid_hook(struct bthost *bthost, uint16_t handle, uint16_t cid, + bthost_cid_hook_func_t func, void *user_data) +{ + struct cid_hook *hook; + struct btconn *conn; + + conn = bthost_find_conn(bthost, handle); + if (!conn) + return; + + hook = malloc(sizeof(*hook)); + if (!hook) + return; + + memset(hook, 0, sizeof(*hook)); + + hook->cid = cid; + hook->func = func; + hook->user_data = user_data; + + hook->next = conn->cid_hooks; + conn->cid_hooks = hook; +} + +void bthost_send_cid(struct bthost *bthost, uint16_t handle, uint16_t cid, + const void *data, uint16_t len) +{ + struct btconn *conn; + + conn = bthost_find_conn(bthost, handle); + if (!conn) + return; + + send_acl(bthost, handle, cid, data, len); +} + +void bthost_send_cid_v(struct bthost *bthost, uint16_t handle, uint16_t cid, + const struct iovec *iov, int iovcnt) +{ + struct btconn *conn; + + conn = bthost_find_conn(bthost, handle); + if (!conn) + return; + + send_iov(bthost, handle, cid, iov, iovcnt); +} + +bool bthost_l2cap_req(struct bthost *bthost, uint16_t handle, uint8_t code, + const void *data, uint16_t len, + bthost_l2cap_rsp_cb cb, void *user_data) +{ + struct l2cap_pending_req *req; + struct btconn *conn; + uint8_t ident; + + conn = bthost_find_conn(bthost, handle); + if (!conn) + return false; + + if (code == BT_L2CAP_PDU_CONN_REQ && + len == sizeof(struct bt_l2cap_pdu_conn_req)) { + const struct bt_l2cap_pdu_conn_req *req = data; + + bthost_add_l2cap_conn(bthost, conn, le16_to_cpu(req->scid), + le16_to_cpu(req->scid), + le16_to_cpu(req->psm)); + } + + ident = l2cap_sig_send(bthost, conn, code, 0, data, len); + if (!ident) + return false; + + if (!cb) + return true; + + req = malloc(sizeof(*req)); + if (!req) + return false; + + memset(req, 0, sizeof(*req)); + req->ident = ident; + req->cb = cb; + req->user_data = user_data; + + req->next = bthost->l2reqs; + bthost->l2reqs = req; + + return true; +} + +static void send_command(struct bthost *bthost, uint16_t opcode, + const void *data, uint8_t len) +{ + struct bt_hci_cmd_hdr hdr; + uint8_t pkt = BT_H4_CMD_PKT; + struct iovec iov[3]; + + iov[0].iov_base = &pkt; + iov[0].iov_len = sizeof(pkt); + + hdr.opcode = cpu_to_le16(opcode); + hdr.plen = len; + + iov[1].iov_base = &hdr; + iov[1].iov_len = sizeof(hdr); + + if (len > 0) { + iov[2].iov_base = (void *) data; + iov[2].iov_len = len; + } + + if (bthost->ncmd) { + send_packet(bthost, iov, len > 0 ? 3 : 2); + bthost->ncmd--; + } else { + queue_command(bthost, iov, len > 0 ? 3 : 2); + } +} + +static void next_cmd(struct bthost *bthost) +{ + struct cmd_queue *cmd_q = &bthost->cmd_q; + struct cmd *cmd = cmd_q->head; + struct cmd *next; + struct iovec iov; + + if (!cmd) + return; + + next = cmd->next; + + if (!bthost->ncmd) + return; + + iov.iov_base = cmd->data; + iov.iov_len = cmd->len; + + send_packet(bthost, &iov, 1); + bthost->ncmd--; + + if (next) + next->prev = NULL; + else + cmd_q->tail = NULL; + + cmd_q->head = next; + + free(cmd); +} + +static void read_bd_addr_complete(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_rsp_read_bd_addr *ev = data; + + if (len < sizeof(*ev)) + return; + + if (ev->status) + return; + + memcpy(bthost->bdaddr, ev->bdaddr, 6); + + bthost->ready = true; + + if (bthost->ready_cb) { + bthost->ready_cb(); + bthost->ready_cb = NULL; + } +} + +void bthost_notify_ready(struct bthost *bthost, bthost_ready_cb cb) +{ + if (bthost->ready) { + cb(); + return; + } + + bthost->ready_cb = cb; +} + +static void read_local_features_complete(struct bthost *bthost, + const void *data, uint8_t len) +{ + const struct bt_hci_rsp_read_local_features *ev = data; + + if (len < sizeof(*ev)) + return; + + if (ev->status) + return; + + memcpy(bthost->features, ev->features, 8); +} + +static void evt_cmd_complete(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_cmd_complete *ev = data; + const void *param; + uint16_t opcode; + + if (len < sizeof(*ev)) + return; + + param = data + sizeof(*ev); + + bthost->ncmd = ev->ncmd; + + opcode = le16toh(ev->opcode); + + switch (opcode) { + case BT_HCI_CMD_RESET: + break; + case BT_HCI_CMD_READ_LOCAL_FEATURES: + read_local_features_complete(bthost, param, len - sizeof(*ev)); + break; + case BT_HCI_CMD_READ_BD_ADDR: + read_bd_addr_complete(bthost, param, len - sizeof(*ev)); + break; + case BT_HCI_CMD_WRITE_SCAN_ENABLE: + break; + case BT_HCI_CMD_LE_SET_ADV_ENABLE: + break; + case BT_HCI_CMD_LE_SET_ADV_PARAMETERS: + break; + case BT_HCI_CMD_PIN_CODE_REQUEST_REPLY: + break; + case BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY: + break; + case BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY: + break; + case BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE: + break; + case BT_HCI_CMD_WRITE_LE_HOST_SUPPORTED: + break; + case BT_HCI_CMD_WRITE_SECURE_CONN_SUPPORT: + break; + case BT_HCI_CMD_IO_CAPABILITY_REQUEST_REPLY: + break; + case BT_HCI_CMD_USER_CONFIRM_REQUEST_REPLY: + break; + case BT_HCI_CMD_USER_CONFIRM_REQUEST_NEG_REPLY: + break; + case BT_HCI_CMD_LE_LTK_REQ_REPLY: + break; + case BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY: + break; + case BT_HCI_CMD_LE_SET_ADV_DATA: + break; + case BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS: + break; + case BT_HCI_CMD_LE_SET_EXT_ADV_DATA: + break; + case BT_HCI_CMD_LE_SET_EXT_ADV_ENABLE: + break; + default: + printf("Unhandled cmd_complete opcode 0x%04x\n", opcode); + break; + } + + if (bthost->cmd_complete_cb) + bthost->cmd_complete_cb(opcode, 0, param, len - sizeof(*ev), + bthost->cmd_complete_data); + + next_cmd(bthost); +} + +static void evt_cmd_status(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_cmd_status *ev = data; + uint16_t opcode; + + if (len < sizeof(*ev)) + return; + + bthost->ncmd = ev->ncmd; + + opcode = le16toh(ev->opcode); + + if (ev->status && bthost->cmd_complete_cb) + bthost->cmd_complete_cb(opcode, ev->status, NULL, 0, + bthost->cmd_complete_data); + + next_cmd(bthost); +} + +static void evt_conn_request(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_conn_request *ev = data; + struct bt_hci_cmd_accept_conn_request cmd; + + if (len < sizeof(*ev)) + return; + + memset(&cmd, 0, sizeof(cmd)); + memcpy(cmd.bdaddr, ev->bdaddr, sizeof(ev->bdaddr)); + + send_command(bthost, BT_HCI_CMD_ACCEPT_CONN_REQUEST, &cmd, + sizeof(cmd)); +} + +static void init_conn(struct bthost *bthost, uint16_t handle, + const uint8_t *bdaddr, uint8_t addr_type) +{ + struct btconn *conn; + const uint8_t *ia, *ra; + + conn = malloc(sizeof(*conn)); + if (!conn) + return; + + memset(conn, 0, sizeof(*conn)); + conn->handle = handle; + memcpy(conn->bdaddr, bdaddr, 6); + conn->addr_type = addr_type; + conn->next_cid = 0x0040; + + conn->next = bthost->conns; + bthost->conns = conn; + + if (bthost->conn_init) { + ia = bthost->bdaddr; + ra = conn->bdaddr; + } else { + ia = conn->bdaddr; + ra = bthost->bdaddr; + } + + conn->smp_data = smp_conn_add(bthost->smp_data, handle, ia, ra, + addr_type, bthost->conn_init); + + if (bthost->new_conn_cb) + bthost->new_conn_cb(conn->handle, bthost->new_conn_data); + + if (addr_type == BDADDR_BREDR) { + struct bt_l2cap_pdu_info_req req; + req.type = L2CAP_IT_FIXED_CHAN; + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_INFO_REQ, 1, + &req, sizeof(req)); + } +} + +static void evt_conn_complete(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_conn_complete *ev = data; + + if (len < sizeof(*ev)) + return; + + if (ev->status) + return; + + init_conn(bthost, le16_to_cpu(ev->handle), ev->bdaddr, BDADDR_BREDR); +} + +static void evt_disconn_complete(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_disconnect_complete *ev = data; + struct btconn **curr; + uint16_t handle; + + if (len < sizeof(*ev)) + return; + + if (ev->status) + return; + + handle = le16_to_cpu(ev->handle); + + for (curr = &bthost->conns; *curr;) { + struct btconn *conn = *curr; + + if (conn->handle == handle) { + *curr = conn->next; + btconn_free(conn); + } else { + curr = &conn->next; + } + } +} + +static void evt_num_completed_packets(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_num_completed_packets *ev = data; + + if (len < sizeof(*ev)) + return; +} + +static void evt_auth_complete(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_auth_complete *ev = data; + struct bt_hci_cmd_set_conn_encrypt cp; + + if (len < sizeof(*ev)) + return; + + if (ev->status) + return; + + cp.handle = ev->handle; + cp.encr_mode = 0x01; + + send_command(bthost, BT_HCI_CMD_SET_CONN_ENCRYPT, &cp, sizeof(cp)); +} + +static void evt_pin_code_request(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_pin_code_request *ev = data; + + if (len < sizeof(*ev)) + return; + + if (bthost->pin_len > 0) { + struct bt_hci_cmd_pin_code_request_reply cp; + + memset(&cp, 0, sizeof(cp)); + memcpy(cp.bdaddr, ev->bdaddr, 6); + cp.pin_len = bthost->pin_len; + memcpy(cp.pin_code, bthost->pin, bthost->pin_len); + + send_command(bthost, BT_HCI_CMD_PIN_CODE_REQUEST_REPLY, + &cp, sizeof(cp)); + } else { + struct bt_hci_cmd_pin_code_request_neg_reply cp; + + memcpy(cp.bdaddr, ev->bdaddr, 6); + send_command(bthost, BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY, + &cp, sizeof(cp)); + } +} + +static void evt_link_key_request(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_link_key_request *ev = data; + struct bt_hci_cmd_link_key_request_neg_reply cp; + + if (len < sizeof(*ev)) + return; + + memset(&cp, 0, sizeof(cp)); + memcpy(cp.bdaddr, ev->bdaddr, 6); + + send_command(bthost, BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY, + &cp, sizeof(cp)); +} + +static void evt_link_key_notify(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_link_key_notify *ev = data; + + if (len < sizeof(*ev)) + return; +} + +static void evt_encrypt_change(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_encrypt_change *ev = data; + struct btconn *conn; + uint16_t handle; + + if (len < sizeof(*ev)) + return; + + handle = acl_handle(ev->handle); + conn = bthost_find_conn(bthost, handle); + if (!conn) + return; + + if (ev->status) + return; + + conn->encr_mode = ev->encr_mode; + + if (conn->smp_data) + smp_conn_encrypted(conn->smp_data, conn->encr_mode); +} + +static void evt_io_cap_response(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_io_capability_response *ev = data; + struct btconn *conn; + + if (len < sizeof(*ev)) + return; + + conn = bthost_find_conn_by_bdaddr(bthost, ev->bdaddr); + if (!conn) + return; +} + +static void evt_io_cap_request(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_io_capability_request *ev = data; + struct bt_hci_cmd_io_capability_request_reply cp; + struct btconn *conn; + + if (len < sizeof(*ev)) + return; + + conn = bthost_find_conn_by_bdaddr(bthost, ev->bdaddr); + if (!conn) + return; + + memcpy(cp.bdaddr, ev->bdaddr, 6); + cp.capability = bthost->io_capability; + cp.oob_data = 0x00; + cp.authentication = bthost->auth_req; + + send_command(bthost, BT_HCI_CMD_IO_CAPABILITY_REQUEST_REPLY, + &cp, sizeof(cp)); +} + +static void evt_user_confirm_request(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_user_confirm_request *ev = data; + struct btconn *conn; + + if (len < sizeof(*ev)) + return; + + conn = bthost_find_conn_by_bdaddr(bthost, ev->bdaddr); + if (!conn) + return; + + if (bthost->reject_user_confirm) { + send_command(bthost, BT_HCI_CMD_USER_CONFIRM_REQUEST_NEG_REPLY, + ev->bdaddr, 6); + return; + } + + send_command(bthost, BT_HCI_CMD_USER_CONFIRM_REQUEST_REPLY, + ev->bdaddr, 6); +} + +static void evt_simple_pairing_complete(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_simple_pairing_complete *ev = data; + + if (len < sizeof(*ev)) + return; +} + +static void evt_le_conn_complete(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_le_conn_complete *ev = data; + uint8_t addr_type; + + if (len < sizeof(*ev)) + return; + + if (ev->status) + return; + + if (ev->peer_addr_type == 0x00) + addr_type = BDADDR_LE_PUBLIC; + else + addr_type = BDADDR_LE_RANDOM; + + init_conn(bthost, le16_to_cpu(ev->handle), ev->peer_addr, addr_type); +} + +static void evt_le_ext_conn_complete(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_le_enhanced_conn_complete *ev = data; + uint8_t addr_type; + + if (len < sizeof(*ev)) + return; + + if (ev->status) + return; + + if (ev->peer_addr_type == 0x00) + addr_type = BDADDR_LE_PUBLIC; + else + addr_type = BDADDR_LE_RANDOM; + + init_conn(bthost, le16_to_cpu(ev->handle), ev->peer_addr, addr_type); +} + +static void evt_le_conn_update_complete(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_le_conn_update_complete *ev = data; + + if (len < sizeof(*ev)) + return; + + if (ev->status) + return; +} + +static void evt_le_remote_features_complete(struct bthost *bthost, + const void *data, uint8_t len) +{ + const struct bt_hci_evt_le_remote_features_complete *ev = data; + + if (len < sizeof(*ev)) + return; + + if (ev->status) + return; +} + +static void evt_le_ltk_request(struct bthost *bthost, const void *data, + uint8_t len) +{ + const struct bt_hci_evt_le_long_term_key_request *ev = data; + struct bt_hci_cmd_le_ltk_req_reply cp; + struct bt_hci_cmd_le_ltk_req_neg_reply *neg_cp = (void *) &cp; + uint16_t handle, ediv; + uint64_t rand; + struct btconn *conn; + int err; + + if (len < sizeof(*ev)) + return; + + handle = acl_handle(ev->handle); + conn = bthost_find_conn(bthost, handle); + if (!conn) + return; + + rand = le64_to_cpu(ev->rand); + ediv = le16_to_cpu(ev->ediv); + + cp.handle = ev->handle; + + err = smp_get_ltk(conn->smp_data, rand, ediv, cp.ltk); + if (err < 0) + send_command(bthost, BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY, + neg_cp, sizeof(*neg_cp)); + else + send_command(bthost, BT_HCI_CMD_LE_LTK_REQ_REPLY, &cp, + sizeof(cp)); +} + +static void evt_le_meta_event(struct bthost *bthost, const void *data, + uint8_t len) +{ + const uint8_t *event = data; + const void *evt_data = data + 1; + + if (len < 1) + return; + + switch (*event) { + case BT_HCI_EVT_LE_CONN_COMPLETE: + evt_le_conn_complete(bthost, evt_data, len - 1); + break; + case BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE: + evt_le_conn_update_complete(bthost, evt_data, len - 1); + break; + case BT_HCI_EVT_LE_REMOTE_FEATURES_COMPLETE: + evt_le_remote_features_complete(bthost, evt_data, len - 1); + break; + case BT_HCI_EVT_LE_LONG_TERM_KEY_REQUEST: + evt_le_ltk_request(bthost, evt_data, len - 1); + break; + case BT_HCI_EVT_LE_ENHANCED_CONN_COMPLETE: + evt_le_ext_conn_complete(bthost, evt_data, len - 1); + break; + default: + printf("Unsupported LE Meta event 0x%2.2x\n", *event); + break; + } +} + +static void process_evt(struct bthost *bthost, const void *data, uint16_t len) +{ + const struct bt_hci_evt_hdr *hdr = data; + const void *param; + + if (len < sizeof(*hdr)) + return; + + if (sizeof(*hdr) + hdr->plen != len) + return; + + param = data + sizeof(*hdr); + + switch (hdr->evt) { + case BT_HCI_EVT_CMD_COMPLETE: + evt_cmd_complete(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_CMD_STATUS: + evt_cmd_status(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_CONN_REQUEST: + evt_conn_request(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_CONN_COMPLETE: + evt_conn_complete(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_DISCONNECT_COMPLETE: + evt_disconn_complete(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_NUM_COMPLETED_PACKETS: + evt_num_completed_packets(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_AUTH_COMPLETE: + evt_auth_complete(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_PIN_CODE_REQUEST: + evt_pin_code_request(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_LINK_KEY_REQUEST: + evt_link_key_request(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_LINK_KEY_NOTIFY: + evt_link_key_notify(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_ENCRYPT_CHANGE: + evt_encrypt_change(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_IO_CAPABILITY_RESPONSE: + evt_io_cap_response(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_IO_CAPABILITY_REQUEST: + evt_io_cap_request(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_USER_CONFIRM_REQUEST: + evt_user_confirm_request(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_SIMPLE_PAIRING_COMPLETE: + evt_simple_pairing_complete(bthost, param, hdr->plen); + break; + + case BT_HCI_EVT_LE_META_EVENT: + evt_le_meta_event(bthost, param, hdr->plen); + break; + + default: + printf("Unsupported event 0x%2.2x\n", hdr->evt); + break; + } +} + +static bool l2cap_cmd_rej(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_cmd_reject *rsp = data; + + if (len < sizeof(*rsp)) + return false; + + return true; +} + +static bool l2cap_conn_req(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_conn_req *req = data; + struct l2cap_conn_cb_data *cb_data; + struct bt_l2cap_pdu_conn_rsp rsp; + uint16_t psm; + + if (len < sizeof(*req)) + return false; + + psm = le16_to_cpu(req->psm); + + memset(&rsp, 0, sizeof(rsp)); + rsp.scid = req->scid; + + cb_data = bthost_find_l2cap_cb_by_psm(bthost, psm); + if (cb_data) + rsp.dcid = rsp.scid; + else + rsp.result = cpu_to_le16(0x0002); /* PSM Not Supported */ + + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CONN_RSP, ident, &rsp, + sizeof(rsp)); + + if (!rsp.result) { + struct bt_l2cap_pdu_config_req conf_req; + struct l2conn *l2conn; + + l2conn = bthost_add_l2cap_conn(bthost, conn, + le16_to_cpu(rsp.dcid), + le16_to_cpu(rsp.scid), + le16_to_cpu(psm)); + + memset(&conf_req, 0, sizeof(conf_req)); + conf_req.dcid = rsp.scid; + + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CONFIG_REQ, 0, + &conf_req, sizeof(conf_req)); + + if (cb_data && l2conn->psm == cb_data->psm && cb_data->func) + cb_data->func(conn->handle, l2conn->dcid, + cb_data->user_data); + } + + return true; +} + +static void rfcomm_sabm_send(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, uint8_t cr, uint8_t dlci) +{ + struct rfcomm_cmd cmd; + + cmd.address = RFCOMM_ADDR(cr, dlci); + cmd.control = RFCOMM_CTRL(RFCOMM_SABM, 1); + cmd.length = RFCOMM_LEN8(0); + cmd.fcs = rfcomm_fcs2((uint8_t *)&cmd); + + send_acl(bthost, conn->handle, l2conn->dcid, &cmd, sizeof(cmd)); +} + +static bool l2cap_conn_rsp(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_conn_rsp *rsp = data; + struct bt_l2cap_pdu_config_req req; + struct l2conn *l2conn; + + if (len < sizeof(*rsp)) + return false; + + l2conn = btconn_find_l2cap_conn_by_scid(conn, le16_to_cpu(rsp->scid)); + if (l2conn) + l2conn->dcid = le16_to_cpu(rsp->dcid); + else + return false; + + if (rsp->result) + return true; + + memset(&req, 0, sizeof(req)); + req.dcid = rsp->dcid; + + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CONFIG_REQ, 0, + &req, sizeof(req)); + + return true; +} + +static bool l2cap_config_req(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_config_req *req = data; + struct bt_l2cap_pdu_config_rsp rsp; + struct l2conn *l2conn; + uint16_t dcid; + + if (len < sizeof(*req)) + return false; + + dcid = le16_to_cpu(req->dcid); + + l2conn = btconn_find_l2cap_conn_by_scid(conn, dcid); + if (!l2conn) + return false; + + memset(&rsp, 0, sizeof(rsp)); + rsp.scid = cpu_to_le16(l2conn->dcid); + rsp.flags = req->flags; + + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CONFIG_RSP, ident, &rsp, + sizeof(rsp)); + + return true; +} + +static bool l2cap_config_rsp(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_config_rsp *rsp = data; + struct l2conn *l2conn; + + if (len < sizeof(*rsp)) + return false; + + l2conn = btconn_find_l2cap_conn_by_scid(conn, rsp->scid); + if (!l2conn) + return false; + + if (l2conn->psm == 0x0003 && !rsp->result && bthost->rfcomm_conn_data) + rfcomm_sabm_send(bthost, conn, l2conn, 1, 0); + + return true; +} + +static bool l2cap_disconn_req(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_disconn_req *req = data; + struct bt_l2cap_pdu_disconn_rsp rsp; + + if (len < sizeof(*req)) + return false; + + memset(&rsp, 0, sizeof(rsp)); + rsp.dcid = req->dcid; + rsp.scid = req->scid; + + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_DISCONN_RSP, ident, &rsp, + sizeof(rsp)); + + return true; +} + +static bool l2cap_info_req(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_info_req *req = data; + uint64_t fixed_chan; + uint16_t type; + uint8_t buf[12]; + struct bt_l2cap_pdu_info_rsp *rsp = (void *) buf; + + if (len < sizeof(*req)) + return false; + + memset(buf, 0, sizeof(buf)); + rsp->type = req->type; + + type = le16_to_cpu(req->type); + + switch (type) { + case L2CAP_IT_FEAT_MASK: + rsp->result = 0x0000; + put_le32(L2CAP_FEAT_FIXED_CHAN, rsp->data); + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_INFO_RSP, ident, + rsp, sizeof(*rsp) + 4); + break; + case L2CAP_IT_FIXED_CHAN: + rsp->result = 0x0000; + fixed_chan = L2CAP_FC_SIG_BREDR; + if (bthost->sc && bthost->le) + fixed_chan |= L2CAP_FC_SMP_BREDR; + put_le64(fixed_chan, rsp->data); + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_INFO_RSP, ident, + rsp, sizeof(*rsp) + sizeof(fixed_chan)); + break; + default: + rsp->result = cpu_to_le16(0x0001); /* Not Supported */ + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_INFO_RSP, ident, + rsp, sizeof(*rsp)); + break; + } + + return true; +} + +static bool l2cap_info_rsp(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_info_rsp *rsp = data; + uint16_t type; + + if (len < sizeof(*rsp)) + return false; + + if (rsp->result) + return true; + + type = le16_to_cpu(rsp->type); + + switch (type) { + case L2CAP_IT_FIXED_CHAN: + if (len < sizeof(*rsp) + 8) + return false; + conn->fixed_chan = get_le64(rsp->data); + if (conn->smp_data && conn->encr_mode) + smp_conn_encrypted(conn->smp_data, conn->encr_mode); + break; + default: + break; + } + + return true; +} + +static void handle_pending_l2reqs(struct bthost *bthost, struct btconn *conn, + uint8_t ident, uint8_t code, + const void *data, uint16_t len) +{ + struct l2cap_pending_req **curr; + + for (curr = &bthost->l2reqs; *curr != NULL;) { + struct l2cap_pending_req *req = *curr; + + if (req->ident != ident) { + curr = &req->next; + continue; + } + + *curr = req->next; + req->cb(code, data, len, req->user_data); + free(req); + } +} + +static void l2cap_sig(struct bthost *bthost, struct btconn *conn, + const void *data, uint16_t len) +{ + const struct bt_l2cap_hdr_sig *hdr = data; + struct bt_l2cap_pdu_cmd_reject rej; + uint16_t hdr_len; + bool ret; + + if (len < sizeof(*hdr)) + goto reject; + + hdr_len = le16_to_cpu(hdr->len); + + if (sizeof(*hdr) + hdr_len != len) + goto reject; + + switch (hdr->code) { + case BT_L2CAP_PDU_CMD_REJECT: + ret = l2cap_cmd_rej(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_CONN_REQ: + ret = l2cap_conn_req(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_CONN_RSP: + ret = l2cap_conn_rsp(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_CONFIG_REQ: + ret = l2cap_config_req(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_CONFIG_RSP: + ret = l2cap_config_rsp(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_DISCONN_REQ: + ret = l2cap_disconn_req(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_INFO_REQ: + ret = l2cap_info_req(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_INFO_RSP: + ret = l2cap_info_rsp(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + default: + printf("Unknown L2CAP code 0x%02x\n", hdr->code); + ret = false; + } + + handle_pending_l2reqs(bthost, conn, hdr->ident, hdr->code, + data + sizeof(*hdr), hdr_len); + + if (ret) + return; + +reject: + memset(&rej, 0, sizeof(rej)); + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CMD_REJECT, 0, + &rej, sizeof(rej)); +} + +static bool l2cap_conn_param_req(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_conn_param_req *req = data; + struct bt_l2cap_pdu_conn_param_rsp rsp; + struct bt_hci_cmd_le_conn_update hci_cmd; + + if (len < sizeof(*req)) + return false; + + memset(&hci_cmd, 0, sizeof(hci_cmd)); + hci_cmd.handle = cpu_to_le16(conn->handle); + hci_cmd.min_interval = req->min_interval; + hci_cmd.max_interval = req->max_interval; + hci_cmd.latency = req->latency; + hci_cmd.supv_timeout = req->timeout; + hci_cmd.min_length = cpu_to_le16(0x0001); + hci_cmd.max_length = cpu_to_le16(0x0001); + + send_command(bthost, BT_HCI_CMD_LE_CONN_UPDATE, + &hci_cmd, sizeof(hci_cmd)); + + memset(&rsp, 0, sizeof(rsp)); + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CONN_PARAM_RSP, ident, + &rsp, sizeof(rsp)); + + return true; +} + +static bool l2cap_conn_param_rsp(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_conn_param_req *rsp = data; + + if (len < sizeof(*rsp)) + return false; + + return true; +} + +static bool l2cap_le_conn_req(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_le_conn_req *req = data; + struct bt_l2cap_pdu_le_conn_rsp rsp; + uint16_t psm; + + if (len < sizeof(*req)) + return false; + + psm = le16_to_cpu(req->psm); + + memset(&rsp, 0, sizeof(rsp)); + + rsp.mtu = 23; + rsp.mps = 23; + rsp.credits = 1; + + if (bthost_find_l2cap_cb_by_psm(bthost, psm)) + rsp.dcid = cpu_to_le16(conn->next_cid++); + else + rsp.result = cpu_to_le16(0x0002); /* PSM Not Supported */ + + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_LE_CONN_RSP, ident, &rsp, + sizeof(rsp)); + + return true; +} + +static bool l2cap_le_conn_rsp(struct bthost *bthost, struct btconn *conn, + uint8_t ident, const void *data, uint16_t len) +{ + const struct bt_l2cap_pdu_le_conn_rsp *rsp = data; + + if (len < sizeof(*rsp)) + return false; + /* TODO add L2CAP connection before with proper PSM */ + bthost_add_l2cap_conn(bthost, conn, 0, le16_to_cpu(rsp->dcid), 0); + + return true; +} + +static void l2cap_le_sig(struct bthost *bthost, struct btconn *conn, + const void *data, uint16_t len) +{ + const struct bt_l2cap_hdr_sig *hdr = data; + struct bt_l2cap_pdu_cmd_reject rej; + uint16_t hdr_len; + bool ret; + + if (len < sizeof(*hdr)) + goto reject; + + hdr_len = le16_to_cpu(hdr->len); + + if (sizeof(*hdr) + hdr_len != len) + goto reject; + + switch (hdr->code) { + case BT_L2CAP_PDU_CMD_REJECT: + ret = l2cap_cmd_rej(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_DISCONN_REQ: + ret = l2cap_disconn_req(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_CONN_PARAM_REQ: + ret = l2cap_conn_param_req(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_CONN_PARAM_RSP: + ret = l2cap_conn_param_rsp(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_LE_CONN_REQ: + ret = l2cap_le_conn_req(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + case BT_L2CAP_PDU_LE_CONN_RSP: + ret = l2cap_le_conn_rsp(bthost, conn, hdr->ident, + data + sizeof(*hdr), hdr_len); + break; + + default: + printf("Unknown L2CAP code 0x%02x\n", hdr->code); + ret = false; + } + + handle_pending_l2reqs(bthost, conn, hdr->ident, hdr->code, + data + sizeof(*hdr), hdr_len); + + if (ret) + return; + +reject: + memset(&rej, 0, sizeof(rej)); + l2cap_sig_send(bthost, conn, BT_L2CAP_PDU_CMD_REJECT, 0, + &rej, sizeof(rej)); +} + +static struct cid_hook *find_cid_hook(struct btconn *conn, uint16_t cid) +{ + struct cid_hook *hook; + + for (hook = conn->cid_hooks; hook != NULL; hook = hook->next) { + if (hook->cid == cid) + return hook; + } + + return NULL; +} + +static struct rfcomm_chan_hook *find_rfcomm_chan_hook(struct btconn *conn, + uint8_t channel) +{ + struct rfcomm_chan_hook *hook; + + for (hook = conn->rfcomm_chan_hooks; hook != NULL; hook = hook->next) + if (hook->channel == channel) + return hook; + + return NULL; +} + +static void rfcomm_ua_send(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, uint8_t cr, uint8_t dlci) +{ + struct rfcomm_cmd cmd; + + cmd.address = RFCOMM_ADDR(cr, dlci); + cmd.control = RFCOMM_CTRL(RFCOMM_UA, 1); + cmd.length = RFCOMM_LEN8(0); + cmd.fcs = rfcomm_fcs2((uint8_t *)&cmd); + + send_acl(bthost, conn->handle, l2conn->dcid, &cmd, sizeof(cmd)); +} + +static void rfcomm_dm_send(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, uint8_t cr, uint8_t dlci) +{ + struct rfcomm_cmd cmd; + + cmd.address = RFCOMM_ADDR(cr, dlci); + cmd.control = RFCOMM_CTRL(RFCOMM_DM, 1); + cmd.length = RFCOMM_LEN8(0); + cmd.fcs = rfcomm_fcs2((uint8_t *)&cmd); + + send_acl(bthost, conn->handle, l2conn->dcid, &cmd, sizeof(cmd)); +} + +static void rfcomm_sabm_recv(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, const void *data, + uint16_t len) +{ + const struct rfcomm_cmd *hdr = data; + uint8_t dlci; + struct rfcomm_conn_cb_data *cb; + uint8_t chan; + + if (len < sizeof(*hdr)) + return; + + chan = RFCOMM_GET_CHANNEL(hdr->address); + dlci = RFCOMM_GET_DLCI(hdr->address); + + cb = bthost_find_rfcomm_cb_by_channel(bthost, chan); + if (!dlci || cb) { + bthost_add_rfcomm_conn(bthost, conn, l2conn, chan); + rfcomm_ua_send(bthost, conn, l2conn, 1, dlci); + if (cb && cb->func) + cb->func(conn->handle, l2conn->scid, cb->user_data, + true); + } else { + rfcomm_dm_send(bthost, conn, l2conn, 1, dlci); + } +} + +static void rfcomm_disc_recv(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, const void *data, + uint16_t len) +{ + const struct rfcomm_cmd *hdr = data; + uint8_t dlci; + + if (len < sizeof(*hdr)) + return; + + dlci = RFCOMM_GET_DLCI(hdr->address); + + rfcomm_ua_send(bthost, conn, l2conn, 0, dlci); +} + +static void rfcomm_uih_send(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, uint8_t address, + uint8_t type, const void *data, uint16_t len) +{ + struct rfcomm_hdr hdr; + struct rfcomm_mcc mcc; + uint8_t fcs; + struct iovec iov[4]; + + hdr.address = address; + hdr.control = RFCOMM_CTRL(RFCOMM_UIH, 0); + hdr.length = RFCOMM_LEN8(sizeof(mcc) + len); + + iov[0].iov_base = &hdr; + iov[0].iov_len = sizeof(hdr); + + mcc.type = type; + mcc.length = RFCOMM_LEN8(len); + + iov[1].iov_base = &mcc; + iov[1].iov_len = sizeof(mcc); + + iov[2].iov_base = (void *) data; + iov[2].iov_len = len; + + fcs = rfcomm_fcs((uint8_t *) &hdr); + + iov[3].iov_base = &fcs; + iov[3].iov_len = sizeof(fcs); + + send_iov(bthost, conn->handle, l2conn->dcid, iov, 4); +} + +static void rfcomm_ua_recv(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, const void *data, + uint16_t len) +{ + const struct rfcomm_cmd *ua_hdr = data; + uint8_t channel; + struct rfcomm_connection_data *conn_data = bthost->rfcomm_conn_data; + uint8_t type; + struct rfcomm_pn pn_cmd; + + if (len < sizeof(*ua_hdr)) + return; + + channel = RFCOMM_GET_CHANNEL(ua_hdr->address); + type = RFCOMM_GET_TYPE(ua_hdr->control); + + if (channel && conn_data && conn_data->channel == channel) { + bthost_add_rfcomm_conn(bthost, conn, l2conn, channel); + if (conn_data->cb) + conn_data->cb(conn->handle, l2conn->scid, + conn_data->user_data, true); + free(bthost->rfcomm_conn_data); + bthost->rfcomm_conn_data = NULL; + return; + } + + if (!conn_data || !RFCOMM_TEST_CR(type)) + return; + + bthost_add_rfcomm_conn(bthost, conn, l2conn, channel); + + pn_cmd.dlci = conn_data->channel * 2; + pn_cmd.priority = 7; + pn_cmd.ack_timer = 0; + pn_cmd.max_retrans = 0; + pn_cmd.mtu = 667; + pn_cmd.credits = 7; + + rfcomm_uih_send(bthost, conn, l2conn, RFCOMM_ADDR(1, 0), + RFCOMM_MCC_TYPE(1, RFCOMM_PN), &pn_cmd, sizeof(pn_cmd)); +} + +static void rfcomm_dm_recv(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, const void *data, + uint16_t len) +{ + const struct rfcomm_cmd *hdr = data; + uint8_t channel; + struct rfcomm_connection_data *conn_data = bthost->rfcomm_conn_data; + + if (len < sizeof(*hdr)) + return; + + channel = RFCOMM_GET_CHANNEL(hdr->address); + + if (conn_data && conn_data->channel == channel) { + if (conn_data->cb) + conn_data->cb(conn->handle, l2conn->scid, + conn_data->user_data, false); + free(bthost->rfcomm_conn_data); + bthost->rfcomm_conn_data = NULL; + } +} + +static void rfcomm_msc_recv(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, uint8_t cr, + const struct rfcomm_msc *msc) +{ + struct rfcomm_msc msc_cmd; + + msc_cmd.dlci = msc->dlci; + msc_cmd.v24_sig = msc->v24_sig; + + rfcomm_uih_send(bthost, conn, l2conn, RFCOMM_ADDR(0, 0), + RFCOMM_MCC_TYPE(cr, RFCOMM_MSC), &msc_cmd, + sizeof(msc_cmd)); +} + +static void rfcomm_pn_recv(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, uint8_t cr, + const struct rfcomm_pn *pn) +{ + struct rfcomm_pn pn_cmd; + + if (!cr) { + rfcomm_sabm_send(bthost, conn, l2conn, 1, + bthost->rfcomm_conn_data->channel * 2); + return; + } + + pn_cmd.dlci = pn->dlci; + pn_cmd.flow_ctrl = pn->flow_ctrl; + pn_cmd.priority = pn->priority; + pn_cmd.ack_timer = pn->ack_timer; + pn_cmd.max_retrans = pn->max_retrans; + pn_cmd.mtu = pn->mtu; + pn_cmd.credits = pn->credits; + + rfcomm_uih_send(bthost, conn, l2conn, RFCOMM_ADDR(1, 0), + RFCOMM_MCC_TYPE(0, RFCOMM_PN), &pn_cmd, sizeof(pn_cmd)); +} + +static void rfcomm_mcc_recv(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, const void *data, uint16_t len) +{ + const struct rfcomm_mcc *mcc = data; + const struct rfcomm_msc *msc; + const struct rfcomm_pn *pn; + + if (len < sizeof(*mcc)) + return; + + switch (RFCOMM_GET_MCC_TYPE(mcc->type)) { + case RFCOMM_MSC: + if (len - sizeof(*mcc) < sizeof(*msc)) + break; + + msc = data + sizeof(*mcc); + + rfcomm_msc_recv(bthost, conn, l2conn, + RFCOMM_TEST_CR(mcc->type) / 2, msc); + break; + case RFCOMM_PN: + if (len - sizeof(*mcc) < sizeof(*pn)) + break; + + pn = data + sizeof(*mcc); + + rfcomm_pn_recv(bthost, conn, l2conn, + RFCOMM_TEST_CR(mcc->type) / 2, pn); + break; + default: + break; + } +} + +#define GET_LEN8(length) ((length & 0xfe) >> 1) +#define GET_LEN16(length) ((length & 0xfffe) >> 1) + +static void rfcomm_uih_recv(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, const void *data, + uint16_t len) +{ + const struct rfcomm_hdr *hdr = data; + uint16_t hdr_len, data_len; + const void *p; + + if (len < sizeof(*hdr)) + return; + + if (RFCOMM_TEST_EA(hdr->length)) { + data_len = (uint16_t) GET_LEN8(hdr->length); + hdr_len = sizeof(*hdr); + } else { + uint8_t ex_len = *((uint8_t *)(data + sizeof(*hdr))); + data_len = ((uint16_t) hdr->length << 8) | ex_len; + hdr_len = sizeof(*hdr) + sizeof(uint8_t); + } + + if (len < hdr_len + data_len) + return; + + p = data + hdr_len; + + if (RFCOMM_GET_DLCI(hdr->address)) { + struct rfcomm_chan_hook *hook; + + hook = find_rfcomm_chan_hook(conn, + RFCOMM_GET_CHANNEL(hdr->address)); + if (hook && data_len) + hook->func(p, data_len, hook->user_data); + } else { + rfcomm_mcc_recv(bthost, conn, l2conn, p, data_len); + } +} + +static void process_rfcomm(struct bthost *bthost, struct btconn *conn, + struct l2conn *l2conn, const void *data, + uint16_t len) +{ + const struct rfcomm_hdr *hdr = data; + + switch (RFCOMM_GET_TYPE(hdr->control)) { + case RFCOMM_SABM: + rfcomm_sabm_recv(bthost, conn, l2conn, data, len); + break; + case RFCOMM_DISC: + rfcomm_disc_recv(bthost, conn, l2conn, data, len); + break; + case RFCOMM_UA: + rfcomm_ua_recv(bthost, conn, l2conn, data, len); + break; + case RFCOMM_DM: + rfcomm_dm_recv(bthost, conn, l2conn, data, len); + break; + case RFCOMM_UIH: + rfcomm_uih_recv(bthost, conn, l2conn, data, len); + break; + default: + printf("Unknown frame type\n"); + break; + } +} + +static void process_acl(struct bthost *bthost, const void *data, uint16_t len) +{ + const struct bt_hci_acl_hdr *acl_hdr = data; + const struct bt_l2cap_hdr *l2_hdr = data + sizeof(*acl_hdr); + uint16_t handle, cid, acl_len, l2_len; + struct cid_hook *hook; + struct btconn *conn; + struct l2conn *l2conn; + const void *l2_data; + + if (len < sizeof(*acl_hdr) + sizeof(*l2_hdr)) + return; + + acl_len = le16_to_cpu(acl_hdr->dlen); + if (len != sizeof(*acl_hdr) + acl_len) + return; + + handle = acl_handle(acl_hdr->handle); + conn = bthost_find_conn(bthost, handle); + if (!conn) { + printf("ACL data for unknown handle 0x%04x\n", handle); + return; + } + + l2_len = le16_to_cpu(l2_hdr->len); + if (len - sizeof(*acl_hdr) != sizeof(*l2_hdr) + l2_len) + return; + + l2_data = data + sizeof(*acl_hdr) + sizeof(*l2_hdr); + + cid = le16_to_cpu(l2_hdr->cid); + + hook = find_cid_hook(conn, cid); + if (hook) { + hook->func(l2_data, l2_len, hook->user_data); + return; + } + + switch (cid) { + case 0x0001: + l2cap_sig(bthost, conn, l2_data, l2_len); + break; + case 0x0005: + l2cap_le_sig(bthost, conn, l2_data, l2_len); + break; + case 0x0006: + smp_data(conn->smp_data, l2_data, l2_len); + break; + case 0x0007: + smp_bredr_data(conn->smp_data, l2_data, l2_len); + break; + default: + l2conn = btconn_find_l2cap_conn_by_scid(conn, cid); + if (l2conn && l2conn->psm == 0x0003) + process_rfcomm(bthost, conn, l2conn, l2_data, l2_len); + else + printf("Packet for unknown CID 0x%04x (%u)\n", cid, + cid); + break; + } +} + +void bthost_receive_h4(struct bthost *bthost, const void *data, uint16_t len) +{ + uint8_t pkt_type; + + if (!bthost) + return; + + if (len < 1) + return; + + pkt_type = ((const uint8_t *) data)[0]; + + switch (pkt_type) { + case BT_H4_EVT_PKT: + process_evt(bthost, data + 1, len - 1); + break; + case BT_H4_ACL_PKT: + process_acl(bthost, data + 1, len - 1); + break; + default: + printf("Unsupported packet 0x%2.2x\n", pkt_type); + break; + } +} + +void bthost_set_cmd_complete_cb(struct bthost *bthost, + bthost_cmd_complete_cb cb, void *user_data) +{ + bthost->cmd_complete_cb = cb; + bthost->cmd_complete_data = user_data; +} + +void bthost_set_connect_cb(struct bthost *bthost, bthost_new_conn_cb cb, + void *user_data) +{ + bthost->new_conn_cb = cb; + bthost->new_conn_data = user_data; +} + +void bthost_hci_connect(struct bthost *bthost, const uint8_t *bdaddr, + uint8_t addr_type) +{ + bthost->conn_init = true; + + if (addr_type == BDADDR_BREDR) { + struct bt_hci_cmd_create_conn cc; + + memset(&cc, 0, sizeof(cc)); + memcpy(cc.bdaddr, bdaddr, sizeof(cc.bdaddr)); + + send_command(bthost, BT_HCI_CMD_CREATE_CONN, &cc, sizeof(cc)); + } else { + struct bt_hci_cmd_le_create_conn cc; + + memset(&cc, 0, sizeof(cc)); + memcpy(cc.peer_addr, bdaddr, sizeof(cc.peer_addr)); + + if (addr_type == BDADDR_LE_RANDOM) + cc.peer_addr_type = 0x01; + + cc.scan_interval = cpu_to_le16(0x0060); + cc.scan_window = cpu_to_le16(0x0030); + cc.min_interval = cpu_to_le16(0x0028); + cc.max_interval = cpu_to_le16(0x0038); + cc.supv_timeout = cpu_to_le16(0x002a); + + send_command(bthost, BT_HCI_CMD_LE_CREATE_CONN, + &cc, sizeof(cc)); + } +} + +void bthost_hci_ext_connect(struct bthost *bthost, const uint8_t *bdaddr, + uint8_t addr_type) +{ + struct bt_hci_cmd_le_ext_create_conn *cc; + struct bt_hci_le_ext_create_conn *cp; + uint8_t buf[sizeof(*cc) + sizeof(*cp)]; + + cc = (void *)buf; + cp = (void *)cc->data; + + bthost->conn_init = true; + + memset(cc, 0, sizeof(*cc)); + memset(cp, 0, sizeof(*cp)); + + memcpy(cc->peer_addr, bdaddr, sizeof(cc->peer_addr)); + + if (addr_type == BDADDR_LE_RANDOM) + cc->peer_addr_type = 0x01; + + cc->phys = 0x01; + + cp->scan_interval = cpu_to_le16(0x0060); + cp->scan_window = cpu_to_le16(0x0030); + cp->min_interval = cpu_to_le16(0x0028); + cp->max_interval = cpu_to_le16(0x0038); + cp->supv_timeout = cpu_to_le16(0x002a); + + send_command(bthost, BT_HCI_CMD_LE_EXT_CREATE_CONN, + buf, sizeof(buf)); +} + +void bthost_hci_disconnect(struct bthost *bthost, uint16_t handle, + uint8_t reason) +{ + struct bt_hci_cmd_disconnect disc; + + disc.handle = cpu_to_le16(handle); + disc.reason = reason; + + send_command(bthost, BT_HCI_CMD_DISCONNECT, &disc, sizeof(disc)); +} + +void bthost_write_scan_enable(struct bthost *bthost, uint8_t scan) +{ + send_command(bthost, BT_HCI_CMD_WRITE_SCAN_ENABLE, &scan, 1); +} + +void bthost_set_adv_data(struct bthost *bthost, const uint8_t *data, + uint8_t len) +{ + struct bt_hci_cmd_le_set_adv_data adv_cp; + + memset(adv_cp.data, 0, 31); + + if (len) { + adv_cp.len = len; + memcpy(adv_cp.data, data, len); + } + + send_command(bthost, BT_HCI_CMD_LE_SET_ADV_DATA, &adv_cp, + sizeof(adv_cp)); +} + +void bthost_set_ext_adv_data(struct bthost *bthost, const uint8_t *data, + uint8_t len) +{ + struct bt_hci_cmd_le_set_ext_adv_data *adv_cp; + uint8_t buf[sizeof(*adv_cp) + 31]; + + adv_cp = (void *)buf; + + memset(adv_cp, 0, sizeof(*adv_cp)); + memset(adv_cp->data, 0, 31); + + adv_cp->operation = 0x03; + adv_cp->fragment_preference = 0x01; + + if (len) { + adv_cp->data_len = len; + memcpy(adv_cp->data, data, len); + } + + send_command(bthost, BT_HCI_CMD_LE_SET_EXT_ADV_DATA, buf, + sizeof(buf)); +} + +void bthost_set_adv_enable(struct bthost *bthost, uint8_t enable) +{ + struct bt_hci_cmd_le_set_adv_parameters cp; + + memset(&cp, 0, sizeof(cp)); + send_command(bthost, BT_HCI_CMD_LE_SET_ADV_PARAMETERS, + &cp, sizeof(cp)); + + send_command(bthost, BT_HCI_CMD_LE_SET_ADV_ENABLE, &enable, 1); +} + +void bthost_set_ext_adv_enable(struct bthost *bthost, uint8_t enable) +{ + struct bt_hci_cmd_le_set_ext_adv_params cp; + struct bt_hci_cmd_le_set_ext_adv_enable cp_enable; + + memset(&cp, 0, sizeof(cp)); + cp.evt_properties = cpu_to_le16(0x0013); + send_command(bthost, BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS, + &cp, sizeof(cp)); + + cp_enable.enable = enable; + send_command(bthost, BT_HCI_CMD_LE_SET_EXT_ADV_ENABLE, &cp_enable, + sizeof(cp_enable)); +} + +void bthost_write_ssp_mode(struct bthost *bthost, uint8_t mode) +{ + send_command(bthost, BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE, &mode, 1); +} + +void bthost_write_le_host_supported(struct bthost *bthost, uint8_t mode) +{ + struct bt_hci_cmd_write_le_host_supported cmd; + + bthost->le = mode; + + memset(&cmd, 0, sizeof(cmd)); + cmd.supported = mode; + send_command(bthost, BT_HCI_CMD_WRITE_LE_HOST_SUPPORTED, + &cmd, sizeof(cmd)); +} + +bool bthost_bredr_capable(struct bthost *bthost) +{ + return lmp_bredr_capable(bthost); +} + +void bthost_request_auth(struct bthost *bthost, uint16_t handle) +{ + struct btconn *conn; + + conn = bthost_find_conn(bthost, handle); + if (!conn) + return; + + if (conn->addr_type == BDADDR_BREDR) { + struct bt_hci_cmd_auth_requested cp; + + cp.handle = cpu_to_le16(handle); + send_command(bthost, BT_HCI_CMD_AUTH_REQUESTED, &cp, sizeof(cp)); + } else { + uint8_t auth_req = bthost->auth_req; + + if (bthost->sc) + auth_req |= 0x08; + + smp_pair(conn->smp_data, bthost->io_capability, auth_req); + } +} + +void bthost_le_start_encrypt(struct bthost *bthost, uint16_t handle, + const uint8_t ltk[16]) +{ + struct bt_hci_cmd_le_start_encrypt cmd; + + memset(&cmd, 0, sizeof(cmd)); + cmd.handle = htobs(handle); + memcpy(cmd.ltk, ltk, 16); + + send_command(bthost, BT_HCI_CMD_LE_START_ENCRYPT, &cmd, sizeof(cmd)); +} + +uint64_t bthost_conn_get_fixed_chan(struct bthost *bthost, uint16_t handle) +{ + struct btconn *conn; + + conn = bthost_find_conn(bthost, handle); + if (!conn) + return 0; + + return conn->fixed_chan; +} + +void bthost_add_l2cap_server(struct bthost *bthost, uint16_t psm, + bthost_l2cap_connect_cb func, void *user_data) +{ + struct l2cap_conn_cb_data *data; + + data = malloc(sizeof(struct l2cap_conn_cb_data)); + if (!data) + return; + + data->psm = psm; + data->user_data = user_data; + data->func = func; + data->next = bthost->new_l2cap_conn_data; + + bthost->new_l2cap_conn_data = data; +} + +void bthost_set_sc_support(struct bthost *bthost, bool enable) +{ + struct bt_hci_cmd_write_secure_conn_support cmd; + + bthost->sc = enable; + + if (!lmp_bredr_capable(bthost)) + return; + + cmd.support = enable; + send_command(bthost, BT_HCI_CMD_WRITE_SECURE_CONN_SUPPORT, + &cmd, sizeof(cmd)); +} + +void bthost_set_pin_code(struct bthost *bthost, const uint8_t *pin, + uint8_t pin_len) +{ + memcpy(bthost->pin, pin, pin_len); + bthost->pin_len = pin_len; +} + +void bthost_set_io_capability(struct bthost *bthost, uint8_t io_capability) +{ + bthost->io_capability = io_capability; +} + +uint8_t bthost_get_io_capability(struct bthost *bthost) +{ + return bthost->io_capability; +} + +void bthost_set_auth_req(struct bthost *bthost, uint8_t auth_req) +{ + bthost->auth_req = auth_req; +} + +uint8_t bthost_get_auth_req(struct bthost *bthost) +{ + uint8_t auth_req = bthost->auth_req; + + if (bthost->sc) + auth_req |= 0x08; + + return auth_req; +} + +void bthost_set_reject_user_confirm(struct bthost *bthost, bool reject) +{ + bthost->reject_user_confirm = reject; +} + +bool bthost_get_reject_user_confirm(struct bthost *bthost) +{ + return bthost->reject_user_confirm; +} + +void bthost_add_rfcomm_server(struct bthost *bthost, uint8_t channel, + bthost_rfcomm_connect_cb func, void *user_data) +{ + struct rfcomm_conn_cb_data *data; + + data = malloc(sizeof(struct rfcomm_conn_cb_data)); + if (!data) + return; + + data->channel = channel; + data->user_data = user_data; + data->func = func; + data->next = bthost->new_rfcomm_conn_data; + + bthost->new_rfcomm_conn_data = data; +} + +void bthost_start(struct bthost *bthost) +{ + if (!bthost) + return; + + bthost->ncmd = 1; + + send_command(bthost, BT_HCI_CMD_RESET, NULL, 0); + + send_command(bthost, BT_HCI_CMD_READ_LOCAL_FEATURES, NULL, 0); + send_command(bthost, BT_HCI_CMD_READ_BD_ADDR, NULL, 0); +} + +bool bthost_connect_rfcomm(struct bthost *bthost, uint16_t handle, + uint8_t channel, bthost_rfcomm_connect_cb func, + void *user_data) +{ + struct rfcomm_connection_data *data; + struct bt_l2cap_pdu_conn_req req; + struct btconn *conn; + + if (bthost->rfcomm_conn_data) + return false; + + conn = bthost_find_conn(bthost, handle); + if (!conn) + return false; + + data = malloc(sizeof(struct rfcomm_connection_data)); + if (!data) + return false; + + data->channel = channel; + data->conn = conn; + data->cb = func; + data->user_data = user_data; + + bthost->rfcomm_conn_data = data; + + req.psm = cpu_to_le16(0x0003); + req.scid = cpu_to_le16(conn->next_cid++); + + return bthost_l2cap_req(bthost, handle, BT_L2CAP_PDU_CONN_REQ, + &req, sizeof(req), NULL, NULL); +} + +void bthost_add_rfcomm_chan_hook(struct bthost *bthost, uint16_t handle, + uint8_t channel, + bthost_rfcomm_chan_hook_func_t func, + void *user_data) +{ + struct rfcomm_chan_hook *hook; + struct btconn *conn; + + conn = bthost_find_conn(bthost, handle); + if (!conn) + return; + + hook = malloc(sizeof(*hook)); + if (!hook) + return; + + memset(hook, 0, sizeof(*hook)); + + hook->channel = channel; + hook->func = func; + hook->user_data = user_data; + + hook->next = conn->rfcomm_chan_hooks; + conn->rfcomm_chan_hooks = hook; +} + +void bthost_send_rfcomm_data(struct bthost *bthost, uint16_t handle, + uint8_t channel, const void *data, + uint16_t len) +{ + struct btconn *conn; + struct rcconn *rcconn; + struct rfcomm_hdr *hdr; + uint8_t *uih_frame; + uint16_t uih_len; + + conn = bthost_find_conn(bthost, handle); + if (!conn) + return; + + rcconn = btconn_find_rfcomm_conn_by_channel(conn, channel); + if (!rcconn) + return; + + if (len > 127) + uih_len = len + sizeof(struct rfcomm_cmd) + sizeof(uint8_t); + else + uih_len = len + sizeof(struct rfcomm_cmd); + + uih_frame = malloc(uih_len); + if (!uih_frame) + return; + + hdr = (struct rfcomm_hdr *) uih_frame; + hdr->address = RFCOMM_ADDR(1, channel * 2); + hdr->control = RFCOMM_CTRL(RFCOMM_UIH, 0); + if (len > 127) { + hdr->length = RFCOMM_LEN16(cpu_to_le16(sizeof(*hdr) + len)); + memcpy(uih_frame + sizeof(*hdr) + 1, data, len); + } else { + hdr->length = RFCOMM_LEN8(sizeof(*hdr) + len); + memcpy(uih_frame + sizeof(*hdr), data, len); + } + + uih_frame[uih_len - 1] = rfcomm_fcs((void *)hdr); + send_acl(bthost, handle, rcconn->scid, uih_frame, uih_len); + + free(uih_frame); +} diff --git a/emulator/bthost.h b/emulator/bthost.h new file mode 100644 index 0000000..b5f3696 --- /dev/null +++ b/emulator/bthost.h @@ -0,0 +1,157 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +typedef void (*bthost_send_func) (const struct iovec *iov, int iovlen, + void *user_data); + +struct bthost; + +struct bthost *bthost_create(void); +void bthost_destroy(struct bthost *bthost); + +typedef void (*bthost_ready_cb) (void); +void bthost_notify_ready(struct bthost *bthost, bthost_ready_cb cb); + +void bthost_set_send_handler(struct bthost *bthost, bthost_send_func handler, + void *user_data); + +void bthost_receive_h4(struct bthost *bthost, const void *data, uint16_t len); + +typedef void (*bthost_cmd_complete_cb) (uint16_t opcode, uint8_t status, + const void *param, uint8_t len, + void *user_data); + +void bthost_set_cmd_complete_cb(struct bthost *bthost, + bthost_cmd_complete_cb cb, void *user_data); + +typedef void (*bthost_new_conn_cb) (uint16_t handle, void *user_data); + +void bthost_set_connect_cb(struct bthost *bthost, bthost_new_conn_cb cb, + void *user_data); + +void bthost_hci_connect(struct bthost *bthost, const uint8_t *bdaddr, + uint8_t addr_type); + +void bthost_hci_ext_connect(struct bthost *bthost, const uint8_t *bdaddr, + uint8_t addr_type); + +void bthost_hci_disconnect(struct bthost *bthost, uint16_t handle, + uint8_t reason); + +typedef void (*bthost_cid_hook_func_t)(const void *data, uint16_t len, + void *user_data); + +void bthost_add_cid_hook(struct bthost *bthost, uint16_t handle, uint16_t cid, + bthost_cid_hook_func_t func, void *user_data); + +void bthost_send_cid(struct bthost *bthost, uint16_t handle, uint16_t cid, + const void *data, uint16_t len); +void bthost_send_cid_v(struct bthost *bthost, uint16_t handle, uint16_t cid, + const struct iovec *iov, int iovcnt); + +typedef void (*bthost_l2cap_rsp_cb) (uint8_t code, const void *data, + uint16_t len, void *user_data); + +bool bthost_l2cap_req(struct bthost *bthost, uint16_t handle, uint8_t req, + const void *data, uint16_t len, + bthost_l2cap_rsp_cb cb, void *user_data); + +void bthost_write_scan_enable(struct bthost *bthost, uint8_t scan); + +void bthost_set_adv_data(struct bthost *bthost, const uint8_t *data, + uint8_t len); +void bthost_set_adv_enable(struct bthost *bthost, uint8_t enable); + +void bthost_set_ext_adv_data(struct bthost *bthost, const uint8_t *data, + uint8_t len); +void bthost_set_ext_adv_enable(struct bthost *bthost, uint8_t enable); + +void bthost_write_ssp_mode(struct bthost *bthost, uint8_t mode); + +void bthost_write_le_host_supported(struct bthost *bthost, uint8_t mode); + +void bthost_request_auth(struct bthost *bthost, uint16_t handle); + +void bthost_le_start_encrypt(struct bthost *bthost, uint16_t handle, + const uint8_t ltk[16]); +typedef void (*bthost_l2cap_connect_cb) (uint16_t handle, uint16_t cid, + void *user_data); + +void bthost_add_l2cap_server(struct bthost *bthost, uint16_t psm, + bthost_l2cap_connect_cb func, void *user_data); + +void bthost_set_sc_support(struct bthost *bthost, bool enable); + +void bthost_set_pin_code(struct bthost *bthost, const uint8_t *pin, + uint8_t pin_len); +void bthost_set_io_capability(struct bthost *bthost, uint8_t io_capability); +uint8_t bthost_get_io_capability(struct bthost *bthost); +void bthost_set_auth_req(struct bthost *bthost, uint8_t auth_req); +uint8_t bthost_get_auth_req(struct bthost *bthost); +void bthost_set_reject_user_confirm(struct bthost *bthost, bool reject); +bool bthost_get_reject_user_confirm(struct bthost *bthost); + +bool bthost_bredr_capable(struct bthost *bthost); + +uint64_t bthost_conn_get_fixed_chan(struct bthost *bthost, uint16_t handle); + +typedef void (*bthost_rfcomm_connect_cb) (uint16_t handle, uint16_t cid, + void *user_data, bool status); + +void bthost_add_rfcomm_server(struct bthost *bthost, uint8_t channel, + bthost_rfcomm_connect_cb func, void *user_data); + +bool bthost_connect_rfcomm(struct bthost *bthost, uint16_t handle, + uint8_t channel, bthost_rfcomm_connect_cb func, + void *user_data); + +typedef void (*bthost_rfcomm_chan_hook_func_t) (const void *data, uint16_t len, + void *user_data); + +void bthost_add_rfcomm_chan_hook(struct bthost *bthost, uint16_t handle, + uint8_t channel, + bthost_rfcomm_chan_hook_func_t func, + void *user_data); + +void bthost_send_rfcomm_data(struct bthost *bthost, uint16_t handle, + uint8_t channel, const void *data, + uint16_t len); + +void bthost_start(struct bthost *bthost); + +/* LE SMP support */ + +void *smp_start(struct bthost *bthost); +void smp_stop(void *smp_data); +void *smp_conn_add(void *smp_data, uint16_t handle, const uint8_t *ia, + const uint8_t *ra, uint8_t addr_type, bool conn_init); +void smp_conn_del(void *conn_data); +void smp_conn_encrypted(void *conn_data, uint8_t encrypt); +void smp_data(void *conn_data, const void *data, uint16_t len); +void smp_bredr_data(void *conn_data, const void *data, uint16_t len); +int smp_get_ltk(void *smp_data, uint64_t rand, uint16_t ediv, uint8_t *ltk); +void smp_pair(void *conn_data, uint8_t io_cap, uint8_t auth_req); diff --git a/emulator/hciemu.c b/emulator/hciemu.c new file mode 100644 index 0000000..1045043 --- /dev/null +++ b/emulator/hciemu.c @@ -0,0 +1,548 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "monitor/bt.h" +#include "emulator/btdev.h" +#include "emulator/bthost.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "emulator/hciemu.h" + +struct hciemu { + int ref_count; + enum btdev_type btdev_type; + struct bthost *host_stack; + struct btdev *master_dev; + struct btdev *client_dev; + guint host_source; + guint master_source; + guint client_source; + struct queue *post_command_hooks; + char bdaddr_str[18]; +}; + +struct hciemu_command_hook { + hciemu_command_func_t function; + void *user_data; +}; + +static void destroy_command_hook(void *data) +{ + struct hciemu_command_hook *hook = data; + + free(hook); +} + +struct run_data { + uint16_t opcode; + const void *data; + uint8_t len; +}; + +static void run_command_hook(void *data, void *user_data) +{ + struct hciemu_command_hook *hook = data; + struct run_data *run_data = user_data; + + if (hook->function) + hook->function(run_data->opcode, run_data->data, + run_data->len, hook->user_data); +} + +static void master_command_callback(uint16_t opcode, + const void *data, uint8_t len, + btdev_callback callback, void *user_data) +{ + struct hciemu *hciemu = user_data; + struct run_data run_data = { .opcode = opcode, + .data = data, .len = len }; + + btdev_command_default(callback); + + queue_foreach(hciemu->post_command_hooks, run_command_hook, &run_data); +} + +static void client_command_callback(uint16_t opcode, + const void *data, uint8_t len, + btdev_callback callback, void *user_data) +{ + btdev_command_default(callback); +} + +static void writev_callback(const struct iovec *iov, int iovlen, + void *user_data) +{ + GIOChannel *channel = user_data; + ssize_t written; + int fd; + + fd = g_io_channel_unix_get_fd(channel); + + written = writev(fd, iov, iovlen); + if (written < 0) + return; +} + +static gboolean receive_bthost(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + struct bthost *bthost = user_data; + unsigned char buf[4096]; + ssize_t len; + int fd; + + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) + return FALSE; + + fd = g_io_channel_unix_get_fd(channel); + + len = read(fd, buf, sizeof(buf)); + if (len < 0) + return FALSE; + + bthost_receive_h4(bthost, buf, len); + + return TRUE; +} + +static guint create_source_bthost(int fd, struct bthost *bthost) +{ + GIOChannel *channel; + guint source; + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + bthost_set_send_handler(bthost, writev_callback, channel); + + source = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + receive_bthost, bthost, NULL); + + g_io_channel_unref(channel); + + return source; +} + +static gboolean receive_btdev(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + struct btdev *btdev = user_data; + unsigned char buf[4096]; + ssize_t len; + int fd; + + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) + return FALSE; + + fd = g_io_channel_unix_get_fd(channel); + + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return TRUE; + + return FALSE; + } + + if (len < 1) + return FALSE; + + switch (buf[0]) { + case BT_H4_CMD_PKT: + case BT_H4_ACL_PKT: + case BT_H4_SCO_PKT: + btdev_receive_h4(btdev, buf, len); + break; + } + + return TRUE; +} + +static guint create_source_btdev(int fd, struct btdev *btdev) +{ + GIOChannel *channel; + guint source; + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + btdev_set_send_handler(btdev, writev_callback, channel); + + source = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + receive_btdev, btdev, NULL); + + g_io_channel_unref(channel); + + return source; +} + +static bool create_vhci(struct hciemu *hciemu) +{ + struct btdev *btdev; + uint8_t create_req[2]; + ssize_t written; + int fd; + + btdev = btdev_create(hciemu->btdev_type, 0x00); + if (!btdev) + return false; + + btdev_set_command_handler(btdev, master_command_callback, hciemu); + + fd = open("/dev/vhci", O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (fd < 0) { + perror("Opening /dev/vhci failed"); + btdev_destroy(btdev); + return false; + } + + create_req[0] = HCI_VENDOR_PKT; + create_req[1] = HCI_PRIMARY; + + written = write(fd, create_req, sizeof(create_req)); + if (written < 0) { + close(fd); + btdev_destroy(btdev); + return false; + } + + hciemu->master_dev = btdev; + + hciemu->master_source = create_source_btdev(fd, btdev); + + return true; +} + +struct bthost *hciemu_client_get_host(struct hciemu *hciemu) +{ + if (!hciemu) + return NULL; + + return hciemu->host_stack; +} + +static bool create_stack(struct hciemu *hciemu) +{ + struct btdev *btdev; + struct bthost *bthost; + int sv[2]; + + btdev = btdev_create(hciemu->btdev_type, 0x00); + if (!btdev) + return false; + + bthost = bthost_create(); + if (!bthost) { + btdev_destroy(btdev); + return false; + } + + btdev_set_command_handler(btdev, client_command_callback, hciemu); + + if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, + 0, sv) < 0) { + bthost_destroy(bthost); + btdev_destroy(btdev); + return false; + } + + hciemu->client_dev = btdev; + hciemu->host_stack = bthost; + + hciemu->client_source = create_source_btdev(sv[0], btdev); + hciemu->host_source = create_source_bthost(sv[1], bthost); + + return true; +} + +static gboolean start_stack(gpointer user_data) +{ + struct hciemu *hciemu = user_data; + + bthost_start(hciemu->host_stack); + + return FALSE; +} + +struct hciemu *hciemu_new(enum hciemu_type type) +{ + struct hciemu *hciemu; + + hciemu = new0(struct hciemu, 1); + if (!hciemu) + return NULL; + + switch (type) { + case HCIEMU_TYPE_BREDRLE: + hciemu->btdev_type = BTDEV_TYPE_BREDRLE; + break; + case HCIEMU_TYPE_BREDR: + hciemu->btdev_type = BTDEV_TYPE_BREDR; + break; + case HCIEMU_TYPE_LE: + hciemu->btdev_type = BTDEV_TYPE_LE; + break; + case HCIEMU_TYPE_LEGACY: + hciemu->btdev_type = BTDEV_TYPE_BREDR20; + break; + case HCIEMU_TYPE_BREDRLE50: + hciemu->btdev_type = BTDEV_TYPE_BREDRLE50; + break; + default: + return NULL; + } + + hciemu->post_command_hooks = queue_new(); + if (!hciemu->post_command_hooks) { + free(hciemu); + return NULL; + } + + if (!create_vhci(hciemu)) { + queue_destroy(hciemu->post_command_hooks, NULL); + free(hciemu); + return NULL; + } + + if (!create_stack(hciemu)) { + g_source_remove(hciemu->master_source); + btdev_destroy(hciemu->master_dev); + queue_destroy(hciemu->post_command_hooks, NULL); + free(hciemu); + return NULL; + } + + g_idle_add(start_stack, hciemu); + + return hciemu_ref(hciemu); +} + +struct hciemu *hciemu_ref(struct hciemu *hciemu) +{ + if (!hciemu) + return NULL; + + __sync_fetch_and_add(&hciemu->ref_count, 1); + + return hciemu; +} + +void hciemu_unref(struct hciemu *hciemu) +{ + if (!hciemu) + return; + + if (__sync_sub_and_fetch(&hciemu->ref_count, 1)) + return; + + queue_destroy(hciemu->post_command_hooks, destroy_command_hook); + + g_source_remove(hciemu->host_source); + g_source_remove(hciemu->client_source); + g_source_remove(hciemu->master_source); + + bthost_destroy(hciemu->host_stack); + btdev_destroy(hciemu->client_dev); + btdev_destroy(hciemu->master_dev); + + free(hciemu); +} + +const char *hciemu_get_address(struct hciemu *hciemu) +{ + const uint8_t *addr; + + if (!hciemu || !hciemu->master_dev) + return NULL; + + addr = btdev_get_bdaddr(hciemu->master_dev); + sprintf(hciemu->bdaddr_str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); + return hciemu->bdaddr_str; +} + +uint8_t *hciemu_get_features(struct hciemu *hciemu) +{ + if (!hciemu || !hciemu->master_dev) + return NULL; + + return btdev_get_features(hciemu->master_dev); +} + +const uint8_t *hciemu_get_master_bdaddr(struct hciemu *hciemu) +{ + if (!hciemu || !hciemu->master_dev) + return NULL; + + return btdev_get_bdaddr(hciemu->master_dev); +} + +const uint8_t *hciemu_get_client_bdaddr(struct hciemu *hciemu) +{ + if (!hciemu || !hciemu->client_dev) + return NULL; + + return btdev_get_bdaddr(hciemu->client_dev); +} + +uint8_t hciemu_get_master_scan_enable(struct hciemu *hciemu) +{ + if (!hciemu || !hciemu->master_dev) + return 0; + + return btdev_get_scan_enable(hciemu->master_dev); +} + +uint8_t hciemu_get_master_le_scan_enable(struct hciemu *hciemu) +{ + if (!hciemu || !hciemu->master_dev) + return 0; + + return btdev_get_le_scan_enable(hciemu->master_dev); +} + +void hciemu_set_master_le_states(struct hciemu *hciemu, const uint8_t *le_states) +{ + if (!hciemu || !hciemu->master_dev) + return; + + btdev_set_le_states(hciemu->master_dev, le_states); +} + +bool hciemu_add_master_post_command_hook(struct hciemu *hciemu, + hciemu_command_func_t function, void *user_data) +{ + struct hciemu_command_hook *hook; + + if (!hciemu) + return false; + + hook = new0(struct hciemu_command_hook, 1); + if (!hook) + return false; + + hook->function = function; + hook->user_data = user_data; + + if (!queue_push_tail(hciemu->post_command_hooks, hook)) { + free(hook); + return false; + } + + return true; +} + +bool hciemu_clear_master_post_command_hooks(struct hciemu *hciemu) +{ + if (!hciemu) + return false; + + queue_remove_all(hciemu->post_command_hooks, + NULL, NULL, destroy_command_hook); + return true; +} + +int hciemu_add_hook(struct hciemu *hciemu, enum hciemu_hook_type type, + uint16_t opcode, hciemu_hook_func_t function, + void *user_data) +{ + enum btdev_hook_type hook_type; + + if (!hciemu) + return -1; + + switch (type) { + case HCIEMU_HOOK_PRE_CMD: + hook_type = BTDEV_HOOK_PRE_CMD; + break; + case HCIEMU_HOOK_POST_CMD: + hook_type = BTDEV_HOOK_POST_CMD; + break; + case HCIEMU_HOOK_PRE_EVT: + hook_type = BTDEV_HOOK_PRE_EVT; + break; + case HCIEMU_HOOK_POST_EVT: + hook_type = BTDEV_HOOK_POST_EVT; + break; + default: + return -1; + } + + return btdev_add_hook(hciemu->master_dev, hook_type, opcode, function, + user_data); +} + +bool hciemu_del_hook(struct hciemu *hciemu, enum hciemu_hook_type type, + uint16_t opcode) +{ + enum btdev_hook_type hook_type; + + if (!hciemu) + return false; + + switch (type) { + case HCIEMU_HOOK_PRE_CMD: + hook_type = BTDEV_HOOK_PRE_CMD; + break; + case HCIEMU_HOOK_POST_CMD: + hook_type = BTDEV_HOOK_POST_CMD; + break; + case HCIEMU_HOOK_PRE_EVT: + hook_type = BTDEV_HOOK_PRE_EVT; + break; + case HCIEMU_HOOK_POST_EVT: + hook_type = BTDEV_HOOK_POST_EVT; + break; + default: + return false; + } + + return btdev_del_hook(hciemu->master_dev, hook_type, opcode); +} diff --git a/emulator/hciemu.h b/emulator/hciemu.h new file mode 100644 index 0000000..e37c069 --- /dev/null +++ b/emulator/hciemu.h @@ -0,0 +1,80 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include + +struct hciemu; + +enum hciemu_type { + HCIEMU_TYPE_BREDRLE, + HCIEMU_TYPE_BREDR, + HCIEMU_TYPE_LE, + HCIEMU_TYPE_LEGACY, + HCIEMU_TYPE_BREDRLE50, +}; + +enum hciemu_hook_type { + HCIEMU_HOOK_PRE_CMD, + HCIEMU_HOOK_POST_CMD, + HCIEMU_HOOK_PRE_EVT, + HCIEMU_HOOK_POST_EVT, +}; + +struct hciemu *hciemu_new(enum hciemu_type type); + +struct hciemu *hciemu_ref(struct hciemu *hciemu); +void hciemu_unref(struct hciemu *hciemu); + +struct bthost *hciemu_client_get_host(struct hciemu *hciemu); + +const char *hciemu_get_address(struct hciemu *hciemu); +uint8_t *hciemu_get_features(struct hciemu *hciemu); + +const uint8_t *hciemu_get_master_bdaddr(struct hciemu *hciemu); +const uint8_t *hciemu_get_client_bdaddr(struct hciemu *hciemu); + +uint8_t hciemu_get_master_scan_enable(struct hciemu *hciemu); + +uint8_t hciemu_get_master_le_scan_enable(struct hciemu *hciemu); + +void hciemu_set_master_le_states(struct hciemu *hciemu, + const uint8_t *le_states); + +typedef void (*hciemu_command_func_t)(uint16_t opcode, const void *data, + uint8_t len, void *user_data); + +typedef bool (*hciemu_hook_func_t)(const void *data, uint16_t len, + void *user_data); + +bool hciemu_add_master_post_command_hook(struct hciemu *hciemu, + hciemu_command_func_t function, void *user_data); + +bool hciemu_clear_master_post_command_hooks(struct hciemu *hciemu); + +int hciemu_add_hook(struct hciemu *hciemu, enum hciemu_hook_type type, + uint16_t opcode, hciemu_hook_func_t function, + void *user_data); + +bool hciemu_del_hook(struct hciemu *hciemu, enum hciemu_hook_type type, + uint16_t opcode); diff --git a/emulator/hfp.c b/emulator/hfp.c new file mode 100644 index 0000000..29ec63e --- /dev/null +++ b/emulator/hfp.c @@ -0,0 +1,112 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "src/shared/mainloop.h" +#include "src/shared/hfp.h" + +static void hfp_debug(const char *str, void *user_data) +{ + const char *prefix = user_data; + + printf("%s%s\n", prefix, str); +} + +static void command_handler(const char *command, void *user_data) +{ + struct hfp_gw *hfp = user_data; + + printf("Command: %s\n", command); + + hfp_gw_send_result(hfp, HFP_RESULT_ERROR); +} + +static bool open_connection(void) +{ + static const char SOCKET_PATH[] = "\0hfp-headset"; + struct hfp_gw *hfp; + struct sockaddr_un addr; + int fd; + + fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + perror("Failed to create Unix socket"); + return false; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + memcpy(addr.sun_path, SOCKET_PATH, sizeof(SOCKET_PATH)); + + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to connect Unix socket"); + close(fd); + return false; + } + + hfp = hfp_gw_new(fd); + if (!hfp) { + close(fd); + return false; + } + + hfp_gw_set_close_on_unref(hfp, true); + + hfp_gw_set_debug(hfp, hfp_debug, "HFP: ", NULL); + + hfp_gw_set_command_handler(hfp, command_handler, hfp, NULL); + + return true; +} + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + } +} + +int main(int argc, char *argv[]) +{ + mainloop_init(); + + if (!open_connection()) + return EXIT_FAILURE; + + return mainloop_run_with_signal(signal_callback, NULL); +} diff --git a/emulator/le.c b/emulator/le.c new file mode 100644 index 0000000..1c8ba28 --- /dev/null +++ b/emulator/le.c @@ -0,0 +1,2090 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/util.h" +#include "src/shared/crypto.h" +#include "src/shared/ecc.h" +#include "src/shared/mainloop.h" +#include "monitor/bt.h" + +#include "phy.h" +#include "le.h" + +#define WHITE_LIST_SIZE 16 +#define RESOLV_LIST_SIZE 16 +#define SCAN_CACHE_SIZE 64 + +#define DEFAULT_TX_LEN 0x001b +#define DEFAULT_TX_TIME 0x0148 +#define MAX_TX_LEN 0x00fb +#define MAX_TX_TIME 0x0848 +#define MAX_RX_LEN 0x00fb +#define MAX_RX_TIME 0x0848 + +#define DEFAULT_ALL_PHYS 0x03 +#define DEFAULT_TX_PHYS 0x00 +#define DEFAULT_RX_PHYS 0x00 + +struct bt_peer { + uint8_t addr_type; + uint8_t addr[6]; +}; + +struct bt_le { + volatile int ref_count; + int vhci_fd; + struct bt_phy *phy; + struct bt_crypto *crypto; + int adv_timeout_id; + int scan_timeout_id; + bool scan_window_active; + uint8_t scan_chan_idx; + + uint8_t event_mask[16]; + uint16_t manufacturer; + uint8_t commands[64]; + uint8_t features[8]; + uint8_t bdaddr[6]; + + uint8_t le_event_mask[8]; + uint16_t le_mtu; + uint8_t le_max_pkt; + uint8_t le_features[8]; + uint8_t le_random_addr[6]; + uint16_t le_adv_min_interval; + uint16_t le_adv_max_interval; + uint8_t le_adv_type; + uint8_t le_adv_own_addr_type; + uint8_t le_adv_direct_addr_type; + uint8_t le_adv_direct_addr[6]; + uint8_t le_adv_channel_map; + uint8_t le_adv_filter_policy; + int8_t le_adv_tx_power; + uint8_t le_adv_data_len; + uint8_t le_adv_data[31]; + uint8_t le_scan_rsp_data_len; + uint8_t le_scan_rsp_data[31]; + uint8_t le_adv_enable; + uint8_t le_scan_type; + uint16_t le_scan_interval; + uint16_t le_scan_window; + uint8_t le_scan_own_addr_type; + uint8_t le_scan_filter_policy; + uint8_t le_scan_enable; + uint8_t le_scan_filter_dup; + + uint8_t le_conn_peer_addr_type; + uint8_t le_conn_peer_addr[6]; + uint8_t le_conn_own_addr_type; + uint8_t le_conn_enable; + + uint8_t le_white_list_size; + uint8_t le_white_list[WHITE_LIST_SIZE][7]; + uint8_t le_states[8]; + + uint16_t le_default_tx_len; + uint16_t le_default_tx_time; + uint8_t le_local_sk256[32]; + uint8_t le_resolv_list[RESOLV_LIST_SIZE][39]; + uint8_t le_resolv_list_size; + uint8_t le_resolv_enable; + uint16_t le_resolv_timeout; + + uint8_t le_default_all_phys; + uint8_t le_default_tx_phys; + uint8_t le_default_rx_phys; + + struct bt_peer scan_cache[SCAN_CACHE_SIZE]; + uint8_t scan_cache_count; +}; + +static bool is_in_white_list(struct bt_le *hci, uint8_t addr_type, + const uint8_t addr[6]) +{ + int i; + + for (i = 0; i < hci->le_white_list_size; i++) { + if (hci->le_white_list[i][0] == addr_type && + !memcmp(&hci->le_white_list[i][1], addr, 6)) + return true; + } + + return false; +} + +static void clear_white_list(struct bt_le *hci) +{ + int i; + + for (i = 0; i < hci->le_white_list_size; i++) { + hci->le_white_list[i][0] = 0xff; + memset(&hci->le_white_list[i][1], 0, 6); + } +} + +static void resolve_peer_addr(struct bt_le *hci, uint8_t peer_addr_type, + const uint8_t peer_addr[6], + uint8_t *addr_type, uint8_t addr[6]) +{ + int i; + + if (!hci->le_resolv_enable) + goto done; + + if (peer_addr_type != 0x01) + goto done; + + if ((peer_addr[5] & 0xc0) != 0x40) + goto done; + + for (i = 0; i < hci->le_resolv_list_size; i++) { + uint8_t local_hash[3]; + + if (hci->le_resolv_list[i][0] == 0xff) + continue; + + bt_crypto_ah(hci->crypto, &hci->le_resolv_list[i][7], + peer_addr + 3, local_hash); + + if (!memcmp(peer_addr, local_hash, 3)) { + switch (hci->le_resolv_list[i][0]) { + case 0x00: + *addr_type = 0x02; + break; + case 0x01: + *addr_type = 0x03; + break; + default: + continue; + } + memcpy(addr, &hci->le_resolv_list[i][1], 6); + return; + } + } + +done: + *addr_type = peer_addr_type; + memcpy(addr, peer_addr, 6); +} + +static void clear_resolv_list(struct bt_le *hci) +{ + int i; + + for (i = 0; i < hci->le_resolv_list_size; i++) { + hci->le_resolv_list[i][0] = 0xff; + memset(&hci->le_resolv_list[i][1], 0, 38); + } +} + +static void reset_defaults(struct bt_le *hci) +{ + memset(hci->event_mask, 0, sizeof(hci->event_mask)); + hci->event_mask[0] |= 0x10; /* Disconnection Complete */ + hci->event_mask[0] |= 0x80; /* Encryption Change */ + hci->event_mask[1] |= 0x08; /* Read Remote Version Information Complete */ + hci->event_mask[1] |= 0x20; /* Command Complete */ + hci->event_mask[1] |= 0x40; /* Command Status */ + hci->event_mask[1] |= 0x80; /* Hardware Error */ + hci->event_mask[2] |= 0x04; /* Number of Completed Packets */ + hci->event_mask[3] |= 0x02; /* Data Buffer Overflow */ + hci->event_mask[5] |= 0x80; /* Encryption Key Refresh Complete */ + //hci->event_mask[7] |= 0x20; /* LE Meta Event */ + + hci->manufacturer = 0x003f; /* Bluetooth SIG (63) */ + + memset(hci->commands, 0, sizeof(hci->commands)); + hci->commands[0] |= 0x20; /* Disconnect */ + //hci->commands[2] |= 0x80; /* Read Remote Version Information */ + hci->commands[5] |= 0x40; /* Set Event Mask */ + hci->commands[5] |= 0x80; /* Reset */ + //hci->commands[10] |= 0x04; /* Read Transmit Power Level */ + hci->commands[14] |= 0x08; /* Read Local Version Information */ + hci->commands[14] |= 0x10; /* Read Local Supported Commands */ + hci->commands[14] |= 0x20; /* Read Local Supported Features */ + hci->commands[14] |= 0x80; /* Read Buffer Size */ + hci->commands[15] |= 0x02; /* Read BD ADDR */ + //hci->commands[15] |= 0x20; /* Read RSSI */ + hci->commands[22] |= 0x04; /* Set Event Mask Page 2 */ + hci->commands[25] |= 0x01; /* LE Set Event Mask */ + hci->commands[25] |= 0x02; /* LE Read Buffer Size */ + hci->commands[25] |= 0x04; /* LE Read Local Supported Features */ + hci->commands[25] |= 0x10; /* LE Set Random Address */ + hci->commands[25] |= 0x20; /* LE Set Advertising Parameters */ + hci->commands[25] |= 0x40; /* LE Read Advertising Channel TX Power */ + hci->commands[25] |= 0x80; /* LE Set Advertising Data */ + hci->commands[26] |= 0x01; /* LE Set Scan Response Data */ + hci->commands[26] |= 0x02; /* LE Set Advertise Enable */ + hci->commands[26] |= 0x04; /* LE Set Scan Parameters */ + hci->commands[26] |= 0x08; /* LE Set Scan Enable */ + hci->commands[26] |= 0x10; /* LE Create Connection */ + hci->commands[26] |= 0x20; /* LE Create Connection Cancel */ + hci->commands[26] |= 0x40; /* LE Read White List Size */ + hci->commands[26] |= 0x80; /* LE Clear White List */ + hci->commands[27] |= 0x01; /* LE Add Device To White List */ + hci->commands[27] |= 0x02; /* LE Remove Device From White List */ + //hci->commands[27] |= 0x04; /* LE Connection Update */ + //hci->commands[27] |= 0x08; /* LE Set Host Channel Classification */ + //hci->commands[27] |= 0x10; /* LE Read Channel Map */ + //hci->commands[27] |= 0x20; /* LE Read Remote Used Features */ + hci->commands[27] |= 0x40; /* LE Encrypt */ + hci->commands[27] |= 0x80; /* LE Rand */ + //hci->commands[28] |= 0x01; /* LE Start Encryption */ + //hci->commands[28] |= 0x02; /* LE Long Term Key Request Reply */ + //hci->commands[28] |= 0x04; /* LE Long Term Key Request Negative Reply */ + hci->commands[28] |= 0x08; /* LE Read Supported States */ + //hci->commands[28] |= 0x10; /* LE Receiver Test */ + //hci->commands[28] |= 0x20; /* LE Transmitter Test */ + //hci->commands[28] |= 0x40; /* LE Test End */ + //hci->commands[33] |= 0x10; /* LE Remote Connection Parameter Request Reply */ + //hci->commands[33] |= 0x20; /* LE Remote Connection Parameter Request Negative Reply */ + hci->commands[33] |= 0x40; /* LE Set Data Length */ + hci->commands[33] |= 0x80; /* LE Read Suggested Default Data Length */ + hci->commands[34] |= 0x01; /* LE Write Suggested Default Data Length */ + hci->commands[34] |= 0x02; /* LE Read Local P-256 Public Key */ + hci->commands[34] |= 0x04; /* LE Generate DHKey */ + hci->commands[34] |= 0x08; /* LE Add Device To Resolving List */ + hci->commands[34] |= 0x10; /* LE Remove Device From Resolving List */ + hci->commands[34] |= 0x20; /* LE Clear Resolving List */ + hci->commands[34] |= 0x40; /* LE Read Resolving List Size */ + hci->commands[34] |= 0x80; /* LE Read Peer Resolvable Address */ + hci->commands[35] |= 0x01; /* LE Read Local Resolvable Address */ + hci->commands[35] |= 0x02; /* LE Set Address Resolution Enable */ + hci->commands[35] |= 0x04; /* LE Set Resolvable Private Address Timeout */ + hci->commands[35] |= 0x08; /* LE Read Maximum Data Length */ + hci->commands[35] |= 0x10; /* LE Read PHY */ + hci->commands[35] |= 0x20; /* LE Set Default PHY */ + hci->commands[35] |= 0x40; /* LE Set PHY */ + //hci->commands[35] |= 0x80; /* LE Enhanced Receiver Test */ + //hci->commands[36] |= 0x01; /* LE Enhanced Transmitter Test */ + //hci->commands[36] |= 0x02; /* LE Set Advertising Set Random Address */ + //hci->commands[36] |= 0x04; /* LE Set Extended Advertising Parameters */ + //hci->commands[36] |= 0x08; /* LE Set Extended Advertising Data */ + //hci->commands[36] |= 0x10; /* LE Set Extended Scan Response Data */ + //hci->commands[36] |= 0x20; /* LE Set Extended Advertising Enable */ + //hci->commands[36] |= 0x40; /* LE Read Maximum Advertising Data Length */ + //hci->commands[36] |= 0x80; /* LE Read Number of Supported Advertising Sets */ + //hci->commands[37] |= 0x01; /* LE Remove Advertising Set */ + //hci->commands[37] |= 0x02; /* LE Clear Advertising Sets */ + //hci->commands[37] |= 0x04; /* LE Set Periodic Advertising Parameters */ + //hci->commands[37] |= 0x08; /* LE Set Periodic Advertising Data */ + //hci->commands[37] |= 0x10; /* LE Set Periodic Advertising Enable */ + //hci->commands[37] |= 0x20; /* LE Set Extended Scan Parameters */ + //hci->commands[37] |= 0x40; /* LE Set Extended Scan Enable */ + //hci->commands[37] |= 0x80; /* LE Extended Create Connection */ + //hci->commands[38] |= 0x01; /* LE Periodic Advertising Create Sync */ + //hci->commands[38] |= 0x02; /* LE Periodic Advertising Create Sync Cancel */ + //hci->commands[38] |= 0x04; /* LE Periodic Advertising Terminate Sync */ + //hci->commands[38] |= 0x08; /* LE Add Device To Periodic Advertiser List */ + //hci->commands[38] |= 0x10; /* LE Remove Device From Periodic Advertiser List */ + //hci->commands[38] |= 0x20; /* LE Clear Periodic Advertiser List */ + //hci->commands[38] |= 0x40; /* LE Read Periodic Advertiser List Size */ + //hci->commands[38] |= 0x80; /* LE Read Transmit Power */ + //hci->commands[39] |= 0x01; /* LE Read RF Path Compensation */ + //hci->commands[39] |= 0x02; /* LE Write RF Path Compensation */ + //hci->commands[39] |= 0x04; /* LE Set Privacy Mode */ + + memset(hci->features, 0, sizeof(hci->features)); + hci->features[4] |= 0x20; /* BR/EDR Not Supported */ + hci->features[4] |= 0x40; /* LE Supported */ + + memset(hci->bdaddr, 0, sizeof(hci->bdaddr)); + + memset(hci->le_event_mask, 0, sizeof(hci->le_event_mask)); + hci->le_event_mask[0] |= 0x01; /* LE Connection Complete */ + hci->le_event_mask[0] |= 0x02; /* LE Advertising Report */ + hci->le_event_mask[0] |= 0x04; /* LE Connection Update Complete */ + hci->le_event_mask[0] |= 0x08; /* LE Read Remote Used Features Complete */ + hci->le_event_mask[0] |= 0x10; /* LE Long Term Key Request */ + //hci->le_event_mask[0] |= 0x20; /* LE Remote Connection Parameter Request */ + //hci->le_event_mask[0] |= 0x40; /* LE Data Length Change */ + //hci->le_event_mask[0] |= 0x80; /* LE Read Local P-256 Public Key Complete */ + //hci->le_event_mask[1] |= 0x01; /* LE Generate DHKey Complete */ + //hci->le_event_mask[1] |= 0x02; /* LE Enhanced Connection Complete */ + //hci->le_event_mask[1] |= 0x04; /* LE Direct Advertising Report */ + //hci->le_event_mask[1] |= 0x08; /* LE PHY Update Complete */ + //hci->le_event_mask[1] |= 0x10; /* LE Extended Advertising Report */ + //hci->le_event_mask[1] |= 0x20; /* LE Periodic Advertising Sync Established */ + //hci->le_event_mask[1] |= 0x40; /* LE Periodic Advertising Report */ + //hci->le_event_mask[1] |= 0x80; /* LE Periodic Advertising Sync Lost */ + //hci->le_event_mask[2] |= 0x01; /* LE Extended Scan Timeout */ + //hci->le_event_mask[2] |= 0x02; /* LE Extended Advertising Set Terminated */ + //hci->le_event_mask[2] |= 0x04; /* LE Scan Request Received */ + //hci->le_event_mask[2] |= 0x08; /* LE Channel Selection Algorithm */ + + hci->le_mtu = 64; + hci->le_max_pkt = 1; + + memset(hci->le_features, 0, sizeof(hci->le_features)); + hci->le_features[0] |= 0x01; /* LE Encryption */ + //hci->le_features[0] |= 0x02; /* Connection Parameter Request Procedure */ + //hci->le_features[0] |= 0x04; /* Extended Reject Indication */ + //hci->le_features[0] |= 0x08; /* Slave-initiated Features Exchange */ + hci->le_features[0] |= 0x10; /* LE Ping */ + hci->le_features[0] |= 0x20; /* LE Data Packet Length Extension */ + hci->le_features[0] |= 0x40; /* LL Privacy */ + hci->le_features[0] |= 0x80; /* Extended Scanner Filter Policies */ + hci->le_features[1] |= 0x01; /* LE 2M PHY */ + hci->le_features[1] |= 0x02; /* Stable Modulation Index - Transmitter */ + hci->le_features[1] |= 0x04; /* Stable Modulation Index - Receiver */ + hci->le_features[1] |= 0x08; /* LE Coded PHY */ + //hci->le_features[1] |= 0x10; /* LE Extended Advertising */ + //hci->le_features[1] |= 0x20; /* LE Periodic Advertising */ + hci->le_features[1] |= 0x40; /* Channel Selection Algorithm #2 */ + hci->le_features[1] |= 0x80; /* LE Power Class 1 */ + hci->le_features[2] |= 0x01; /* Minimum Number of Used Channels Procedure */ + + memset(hci->le_random_addr, 0, sizeof(hci->le_random_addr)); + + hci->le_adv_min_interval = 0x0800; + hci->le_adv_max_interval = 0x0800; + hci->le_adv_type = 0x00; + hci->le_adv_own_addr_type = 0x00; + hci->le_adv_direct_addr_type = 0x00; + memset(hci->le_adv_direct_addr, 0, 6); + hci->le_adv_channel_map = 0x07; + hci->le_adv_filter_policy = 0x00; + + hci->le_adv_tx_power = 0; + + memset(hci->le_adv_data, 0, sizeof(hci->le_adv_data)); + hci->le_adv_data_len = 0; + + memset(hci->le_scan_rsp_data, 0, sizeof(hci->le_scan_rsp_data)); + hci->le_scan_rsp_data_len = 0; + + hci->le_adv_enable = 0x00; + + hci->le_scan_type = 0x00; /* Passive Scanning */ + hci->le_scan_interval = 0x0010; /* 10 ms */ + hci->le_scan_window = 0x0010; /* 10 ms */ + hci->le_scan_own_addr_type = 0x00; /* Public Device Address */ + hci->le_scan_filter_policy = 0x00; + hci->le_scan_enable = 0x00; + hci->le_scan_filter_dup = 0x00; + + hci->le_conn_enable = 0x00; + + hci->le_white_list_size = WHITE_LIST_SIZE; + clear_white_list(hci); + + memset(hci->le_states, 0, sizeof(hci->le_states)); + hci->le_states[0] |= 0x01; /* Non-connectable Advertising */ + hci->le_states[0] |= 0x02; /* Scannable Advertising */ + hci->le_states[0] |= 0x04; /* Connectable Advertising */ + hci->le_states[0] |= 0x08; /* High Duty Cycle Directed Advertising */ + hci->le_states[0] |= 0x10; /* Passive Scanning */ + hci->le_states[0] |= 0x20; /* Active Scanning */ + hci->le_states[0] |= 0x40; /* Initiating + Connection (Master Role) */ + hci->le_states[0] |= 0x80; /* Connection (Slave Role) */ + hci->le_states[1] |= 0x01; /* Passive Scanning + + * Non-connectable Advertising */ + + hci->le_default_tx_len = DEFAULT_TX_LEN; + hci->le_default_tx_time = DEFAULT_TX_TIME; + + memset(hci->le_local_sk256, 0, sizeof(hci->le_local_sk256)); + + hci->le_resolv_list_size = RESOLV_LIST_SIZE; + clear_resolv_list(hci); + hci->le_resolv_enable = 0x00; + hci->le_resolv_timeout = 0x0384; /* 900 secs or 15 minutes */ + + hci->le_default_all_phys = DEFAULT_ALL_PHYS; + hci->le_default_tx_phys = DEFAULT_TX_PHYS; + hci->le_default_rx_phys = DEFAULT_RX_PHYS; +} + +static void clear_scan_cache(struct bt_le *hci) +{ + memset(hci->scan_cache, 0, sizeof(hci->scan_cache)); + hci->scan_cache_count = 0; +} + +static bool add_to_scan_cache(struct bt_le *hci, uint8_t addr_type, + const uint8_t addr[6]) +{ + int i; + + for (i = 0; i < hci->scan_cache_count; i++) { + if (hci->scan_cache[i].addr_type == addr_type && + !memcmp(hci->scan_cache[i].addr, addr, 6)) + return false; + } + + if (hci->scan_cache_count >= SCAN_CACHE_SIZE) + return true; + + hci->scan_cache[hci->scan_cache_count].addr_type = addr_type; + memcpy(hci->scan_cache[hci->scan_cache_count].addr, addr, 6); + hci->scan_cache_count++; + + return true; +} + +static void send_event(struct bt_le *hci, uint8_t event, + void *data, uint8_t size) +{ + uint8_t type = BT_H4_EVT_PKT; + struct bt_hci_evt_hdr hdr; + struct iovec iov[3]; + int iovcnt; + + hdr.evt = event; + hdr.plen = size; + + iov[0].iov_base = &type; + iov[0].iov_len = 1; + iov[1].iov_base = &hdr; + iov[1].iov_len = sizeof(hdr); + + if (size > 0) { + iov[2].iov_base = data; + iov[2].iov_len = size; + iovcnt = 3; + } else + iovcnt = 2; + + if (writev(hci->vhci_fd, iov, iovcnt) < 0) + fprintf(stderr, "Write to /dev/vhci failed (%m)\n"); +} + +static void send_adv_pkt(struct bt_le *hci, uint8_t channel) +{ + struct bt_phy_pkt_adv pkt; + + memset(&pkt, 0, sizeof(pkt)); + pkt.chan_idx = channel; + pkt.pdu_type = hci->le_adv_type; + pkt.tx_addr_type = hci->le_adv_own_addr_type; + switch (hci->le_adv_own_addr_type) { + case 0x00: + case 0x02: + memcpy(pkt.tx_addr, hci->bdaddr, 6); + break; + case 0x01: + case 0x03: + memcpy(pkt.tx_addr, hci->le_random_addr, 6); + break; + } + pkt.rx_addr_type = hci->le_adv_direct_addr_type; + memcpy(pkt.rx_addr, hci->le_adv_direct_addr, 6); + pkt.adv_data_len = hci->le_adv_data_len; + pkt.scan_rsp_len = hci->le_scan_rsp_data_len; + + bt_phy_send_vector(hci->phy, BT_PHY_PKT_ADV, &pkt, sizeof(pkt), + hci->le_adv_data, pkt.adv_data_len, + hci->le_scan_rsp_data, pkt.scan_rsp_len); +} + +static unsigned int get_adv_delay(void) +{ + /* The advertising delay is a pseudo-random value with a range + * of 0 ms to 10 ms generated for each advertising event. + */ + srand(time(NULL)); + return (rand() % 11); +} + +static void adv_timeout_callback(int id, void *user_data) +{ + struct bt_le *hci = user_data; + unsigned int msec, min_msec, max_msec; + + if (hci->le_adv_channel_map & 0x01) + send_adv_pkt(hci, 37); + if (hci->le_adv_channel_map & 0x02) + send_adv_pkt(hci, 38); + if (hci->le_adv_channel_map & 0x04) + send_adv_pkt(hci, 39); + + min_msec = (hci->le_adv_min_interval * 625) / 1000; + max_msec = (hci->le_adv_max_interval * 625) / 1000; + + msec = ((min_msec + max_msec) / 2) + get_adv_delay(); + + if (mainloop_modify_timeout(id, msec) < 0) { + fprintf(stderr, "Setting advertising timeout failed\n"); + hci->le_adv_enable = 0x00; + } +} + +static bool start_adv(struct bt_le *hci) +{ + unsigned int msec; + + if (hci->adv_timeout_id >= 0) + return false; + + msec = ((hci->le_adv_min_interval * 625) / 1000) + get_adv_delay(); + + hci->adv_timeout_id = mainloop_add_timeout(msec, adv_timeout_callback, + hci, NULL); + if (hci->adv_timeout_id < 0) + return false; + + return true; +} + +static bool stop_adv(struct bt_le *hci) +{ + if (hci->adv_timeout_id < 0) + return false; + + mainloop_remove_timeout(hci->adv_timeout_id); + hci->adv_timeout_id = -1; + + return true; +} + +static void scan_timeout_callback(int id, void *user_data) +{ + struct bt_le *hci = user_data; + unsigned int msec; + + if (hci->le_scan_window == hci->le_scan_interval || + !hci->scan_window_active) { + msec = (hci->le_scan_window * 625) / 1000; + hci->scan_window_active = true; + + hci->scan_chan_idx++; + if (hci->scan_chan_idx > 39) + hci->scan_chan_idx = 37; + } else { + msec = ((hci->le_scan_interval - + hci->le_scan_window) * 625) / 1000; + hci->scan_window_active = false; + } + + if (mainloop_modify_timeout(id, msec) < 0) { + fprintf(stderr, "Setting scanning timeout failed\n"); + hci->le_scan_enable = 0x00; + hci->scan_window_active = false; + } +} + +static bool start_scan(struct bt_le *hci) +{ + unsigned int msec; + + if (hci->scan_timeout_id >= 0) + return false; + + msec = (hci->le_scan_window * 625) / 1000; + + hci->scan_timeout_id = mainloop_add_timeout(msec, scan_timeout_callback, + hci, NULL); + if (hci->scan_timeout_id < 0) + return false; + + hci->scan_window_active = true; + hci->scan_chan_idx = 37; + + return true; +} + +static bool stop_scan(struct bt_le *hci) +{ + if (hci->scan_timeout_id < 0) + return false; + + mainloop_remove_timeout(hci->scan_timeout_id); + hci->scan_timeout_id = -1; + + hci->scan_window_active = false; + + return true; +} + +static void cmd_complete(struct bt_le *hci, uint16_t opcode, + const void *data, uint8_t len) +{ + struct bt_hci_evt_cmd_complete *cc; + void *pkt_data; + + pkt_data = alloca(sizeof(*cc) + len); + if (!pkt_data) + return; + + cc = pkt_data; + cc->ncmd = 0x01; + cc->opcode = cpu_to_le16(opcode); + + if (len > 0) + memcpy(pkt_data + sizeof(*cc), data, len); + + send_event(hci, BT_HCI_EVT_CMD_COMPLETE, pkt_data, sizeof(*cc) + len); +} + +static void cmd_status(struct bt_le *hci, uint8_t status, uint16_t opcode) +{ + struct bt_hci_evt_cmd_status cs; + + cs.status = status; + cs.ncmd = 0x01; + cs.opcode = cpu_to_le16(opcode); + + send_event(hci, BT_HCI_EVT_CMD_STATUS, &cs, sizeof(cs)); +} + +static void le_meta_event(struct bt_le *hci, uint8_t event, + void *data, uint8_t len) +{ + void *pkt_data; + + if (!(hci->event_mask[7] & 0x20)) + return; + + pkt_data = alloca(1 + len); + if (!pkt_data) + return; + + ((uint8_t *) pkt_data)[0] = event; + + if (len > 0) + memcpy(pkt_data + 1, data, len); + + send_event(hci, BT_HCI_EVT_LE_META_EVENT, pkt_data, 1 + len); +} + +static void cmd_disconnect(struct bt_le *hci, const void *data, uint8_t size) +{ + cmd_status(hci, BT_HCI_ERR_UNKNOWN_CONN_ID, BT_HCI_CMD_DISCONNECT); +} + +static void cmd_set_event_mask(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_event_mask *cmd = data; + uint8_t status; + + memcpy(hci->event_mask, cmd->mask, 8); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_SET_EVENT_MASK, &status, sizeof(status)); +} + +static void cmd_reset(struct bt_le *hci, const void *data, uint8_t size) +{ + uint8_t status; + + stop_adv(hci); + stop_scan(hci); + reset_defaults(hci); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_RESET, &status, sizeof(status)); +} + +static void cmd_set_event_mask_page2(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_event_mask_page2 *cmd = data; + uint8_t status; + + memcpy(hci->event_mask + 8, cmd->mask, 8); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_SET_EVENT_MASK_PAGE2, + &status, sizeof(status)); +} + +static void cmd_read_local_version(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_local_version rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.hci_ver = 0x09; + rsp.hci_rev = cpu_to_le16(0x0000); + rsp.lmp_ver = 0x09; + rsp.manufacturer = cpu_to_le16(hci->manufacturer); + rsp.lmp_subver = cpu_to_le16(0x0000); + + cmd_complete(hci, BT_HCI_CMD_READ_LOCAL_VERSION, &rsp, sizeof(rsp)); +} + +static void cmd_read_local_commands(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_local_commands rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(rsp.commands, hci->commands, 64); + + cmd_complete(hci, BT_HCI_CMD_READ_LOCAL_COMMANDS, &rsp, sizeof(rsp)); +} + +static void cmd_read_local_features(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_local_features rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(rsp.features, hci->features, 8); + + cmd_complete(hci, BT_HCI_CMD_READ_LOCAL_FEATURES, &rsp, sizeof(rsp)); +} + +static void cmd_read_buffer_size(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_buffer_size rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.acl_mtu = cpu_to_le16(0x0000); + rsp.sco_mtu = 0x00; + rsp.acl_max_pkt = cpu_to_le16(0x0000); + rsp.sco_max_pkt = cpu_to_le16(0x0000); + + cmd_complete(hci, BT_HCI_CMD_READ_BUFFER_SIZE, &rsp, sizeof(rsp)); +} + +static void cmd_read_bd_addr(struct bt_le *hci, const void *data, uint8_t size) +{ + struct bt_hci_rsp_read_bd_addr rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(rsp.bdaddr, hci->bdaddr, 6); + + cmd_complete(hci, BT_HCI_CMD_READ_BD_ADDR, &rsp, sizeof(rsp)); +} + +static void cmd_le_set_event_mask(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_event_mask *cmd = data; + uint8_t status; + + memcpy(hci->le_event_mask, cmd->mask, 8); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_EVENT_MASK, + &status, sizeof(status)); +} + +static void cmd_le_read_buffer_size(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_le_read_buffer_size rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.le_mtu = cpu_to_le16(hci->le_mtu); + rsp.le_max_pkt = hci->le_max_pkt; + + cmd_complete(hci, BT_HCI_CMD_LE_READ_BUFFER_SIZE, &rsp, sizeof(rsp)); +} + +static void cmd_le_read_local_features(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_le_read_local_features rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(rsp.features, hci->le_features, 8); + + cmd_complete(hci, BT_HCI_CMD_LE_READ_LOCAL_FEATURES, + &rsp, sizeof(rsp)); +} + +static void cmd_le_set_random_address(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_random_address *cmd = data; + uint8_t status; + + memcpy(hci->le_random_addr, cmd->addr, 6); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, + &status, sizeof(status)); +} + +static void cmd_le_set_adv_parameters(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_adv_parameters *cmd = data; + uint16_t min_interval, max_interval; + uint8_t status; + + if (hci->le_adv_enable == 0x01) { + cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + + min_interval = le16_to_cpu(cmd->min_interval); + max_interval = le16_to_cpu(cmd->max_interval); + + /* Valid range for advertising type is 0x00 to 0x03 */ + switch (cmd->type) { + case 0x00: /* ADV_IND */ + /* Range for advertising interval min is 0x0020 to 0x4000 */ + if (min_interval < 0x0020 || min_interval > 0x4000) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + /* Range for advertising interval max is 0x0020 to 0x4000 */ + if (max_interval < 0x0020 || max_interval > 0x4000) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + /* Advertising interval max shall be less or equal */ + if (min_interval > max_interval) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + break; + + case 0x01: /* ADV_DIRECT_IND */ + /* Range for direct address type is 0x00 to 0x01 */ + if (cmd->direct_addr_type > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + break; + + case 0x02: /* ADV_SCAN_IND */ + case 0x03: /* ADV_NONCONN_IND */ + /* Range for advertising interval min is 0x00a0 to 0x4000 */ + if (min_interval < 0x00a0 || min_interval > 0x4000) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + /* Range for advertising interval max is 0x00a0 to 0x4000 */ + if (max_interval < 0x00a0 || max_interval > 0x4000) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + /* Advertising interval min shall be less or equal */ + if (min_interval > max_interval) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + break; + + default: + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + + /* Valid range for own address type is 0x00 to 0x03 */ + if (cmd->own_addr_type > 0x03) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + + /* Valid range for advertising channel map is 0x01 to 0x07 */ + if (cmd->channel_map < 0x01 || cmd->channel_map > 0x07) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + + /* Valid range for advertising filter policy is 0x00 to 0x03 */ + if (cmd->filter_policy > 0x03) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_PARAMETERS); + return; + } + + hci->le_adv_min_interval = min_interval; + hci->le_adv_max_interval = max_interval; + hci->le_adv_type = cmd->type; + hci->le_adv_own_addr_type = cmd->own_addr_type; + hci->le_adv_direct_addr_type = cmd->direct_addr_type; + memcpy(hci->le_adv_direct_addr, cmd->direct_addr, 6); + hci->le_adv_channel_map = cmd->channel_map; + hci->le_adv_filter_policy = cmd->filter_policy; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_ADV_PARAMETERS, + &status, sizeof(status)); +} + +static void cmd_le_read_adv_tx_power(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_le_read_adv_tx_power rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.level = hci->le_adv_tx_power; + + cmd_complete(hci, BT_HCI_CMD_LE_READ_ADV_TX_POWER, &rsp, sizeof(rsp)); +} + +static void cmd_le_set_adv_data(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_adv_data *cmd = data; + uint8_t status; + + /* Valid range for advertising data length is 0x00 to 0x1f */ + if (cmd->len > 0x1f) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_DATA); + return; + } + + hci->le_adv_data_len = cmd->len; + memcpy(hci->le_adv_data, cmd->data, 31); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_ADV_DATA, &status, sizeof(status)); +} + +static void cmd_le_set_scan_rsp_data(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_scan_rsp_data *cmd = data; + uint8_t status; + + /* Valid range for scan response data length is 0x00 to 0x1f */ + if (cmd->len > 0x1f) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_SCAN_RSP_DATA); + return; + } + + hci->le_scan_rsp_data_len = cmd->len; + memcpy(hci->le_scan_rsp_data, cmd->data, 31); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_SCAN_RSP_DATA, + &status, sizeof(status)); +} + +static void cmd_le_set_adv_enable(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_adv_enable *cmd = data; + uint8_t status; + bool result; + + /* Valid range for advertising enable is 0x00 to 0x01 */ + if (cmd->enable > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_ADV_ENABLE); + return; + } + + if (cmd->enable == hci->le_adv_enable) { + cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LE_SET_ADV_ENABLE); + return; + } + + if (cmd->enable == 0x01) + result = start_adv(hci); + else + result = stop_adv(hci); + + if (!result) { + cmd_status(hci, BT_HCI_ERR_UNSPECIFIED_ERROR, + BT_HCI_CMD_LE_SET_ADV_ENABLE); + return; + } + + hci->le_adv_enable = cmd->enable; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_ADV_ENABLE, + &status, sizeof(status)); +} + +static void cmd_le_set_scan_parameters(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_scan_parameters *cmd = data; + uint16_t interval, window; + uint8_t status; + + if (hci->le_scan_enable == 0x01) { + cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); + return; + } + + interval = le16_to_cpu(cmd->interval); + window = le16_to_cpu(cmd->window); + + /* Valid range for scan type is 0x00 to 0x01 */ + if (cmd->type > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); + return; + } + + /* Valid range for scan interval is 0x0004 to 0x4000 */ + if (interval < 0x0004 || interval > 0x4000) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); + return; + } + + /* Valid range for scan window is 0x0004 to 0x4000 */ + if (window < 0x0004 || window > 0x4000) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); + return; + } + + /* Scan window shall be less or equal than scan interval */ + if (window > interval) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); + return; + } + + /* Valid range for own address type is 0x00 to 0x03 */ + if (cmd->own_addr_type > 0x03) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); + return; + } + + /* Valid range for scanning filter policy is 0x00 to 0x03 */ + if (cmd->filter_policy > 0x03) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_SCAN_PARAMETERS); + return; + } + + hci->le_scan_type = cmd->type; + hci->le_scan_interval = interval; + hci->le_scan_window = window; + hci->le_scan_own_addr_type = cmd->own_addr_type; + hci->le_scan_filter_policy = cmd->filter_policy; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, + &status, sizeof(status)); +} + +static void cmd_le_set_scan_enable(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_scan_enable *cmd = data; + uint8_t status; + bool result; + + /* Valid range for scan enable is 0x00 to 0x01 */ + if (cmd->enable > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_SCAN_ENABLE); + return; + } + + /* Valid range for filter duplicates is 0x00 to 0x01 */ + if (cmd->filter_dup > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_SCAN_ENABLE); + return; + } + + if (cmd->enable == hci->le_scan_enable) { + cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LE_SET_SCAN_ENABLE); + return; + } + + clear_scan_cache(hci); + + if (cmd->enable == 0x01) + result = start_scan(hci); + else + result = stop_scan(hci); + + if (!result) { + cmd_status(hci, BT_HCI_ERR_UNSPECIFIED_ERROR, + BT_HCI_CMD_LE_SET_SCAN_ENABLE); + return; + } + + hci->le_scan_enable = cmd->enable; + hci->le_scan_filter_dup = cmd->filter_dup; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, + &status, sizeof(status)); +} + +static void cmd_le_create_conn(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_create_conn *cmd = data; + + if (hci->le_conn_enable == 0x01) { + cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LE_CREATE_CONN); + return; + } + + /* Valid range for peer address type is 0x00 to 0x03 */ + if (cmd->peer_addr_type > 0x03) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_CREATE_CONN); + return; + } + + /* Valid range for own address type is 0x00 to 0x03 */ + if (cmd->own_addr_type > 0x03) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_CREATE_CONN); + return; + } + + hci->le_conn_peer_addr_type = cmd->peer_addr_type; + memcpy(hci->le_conn_peer_addr, cmd->peer_addr, 6); + hci->le_conn_own_addr_type = cmd->own_addr_type; + hci->le_conn_enable = 0x01; + + cmd_status(hci, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_CREATE_CONN); +} + +static void cmd_le_create_conn_cancel(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_evt_le_conn_complete evt; + uint8_t status; + + if (hci->le_conn_enable == 0x00) { + cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LE_CREATE_CONN_CANCEL); + return; + } + + hci->le_conn_enable = 0x00; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_CREATE_CONN_CANCEL, + &status, sizeof(status)); + + evt.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + evt.handle = cpu_to_le16(0x0000); + evt.role = 0x00; + evt.peer_addr_type = 0x00; + memset(evt.peer_addr, 0, 6); + evt.interval = cpu_to_le16(0x0000); + evt.latency = cpu_to_le16(0x0000); + evt.supv_timeout = cpu_to_le16(0x0000); + evt.clock_accuracy = 0x00; + + if (hci->le_event_mask[0] & 0x01) + le_meta_event(hci, BT_HCI_EVT_LE_CONN_COMPLETE, + &evt, sizeof(evt)); +} + +static void cmd_le_read_white_list_size(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_le_read_white_list_size rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.size = hci->le_white_list_size; + + cmd_complete(hci, BT_HCI_CMD_LE_READ_WHITE_LIST_SIZE, + &rsp, sizeof(rsp)); +} + +static void cmd_le_clear_white_list(struct bt_le *hci, + const void *data, uint8_t size) +{ + uint8_t status; + + clear_white_list(hci); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_CLEAR_WHITE_LIST, + &status, sizeof(status)); +} + +static void cmd_le_add_to_white_list(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_add_to_white_list *cmd = data; + uint8_t status; + bool exists = false; + int i, pos = -1; + + /* Valid range for address type is 0x00 to 0x01 */ + if (cmd->addr_type > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_ADD_TO_WHITE_LIST); + return; + } + + for (i = 0; i < hci->le_white_list_size; i++) { + if (hci->le_white_list[i][0] == cmd->addr_type && + !memcmp(&hci->le_white_list[i][1], + cmd->addr, 6)) { + exists = true; + break; + } else if (pos < 0 && hci->le_white_list[i][0] == 0xff) + pos = i; + } + + if (exists) { + cmd_status(hci, BT_HCI_ERR_UNSPECIFIED_ERROR, + BT_HCI_CMD_LE_ADD_TO_WHITE_LIST); + return; + } + + if (pos < 0) { + cmd_status(hci, BT_HCI_ERR_MEM_CAPACITY_EXCEEDED, + BT_HCI_CMD_LE_ADD_TO_WHITE_LIST); + return; + } + + hci->le_white_list[pos][0] = cmd->addr_type; + memcpy(&hci->le_white_list[pos][1], cmd->addr, 6); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_ADD_TO_WHITE_LIST, + &status, sizeof(status)); +} + +static void cmd_le_remove_from_white_list(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_remove_from_white_list *cmd = data; + uint8_t status; + int i, pos = -1; + + /* Valid range for address type is 0x00 to 0x01 */ + if (cmd->addr_type > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_REMOVE_FROM_WHITE_LIST); + return; + } + + for (i = 0; i < hci->le_white_list_size; i++) { + if (hci->le_white_list[i][0] == cmd->addr_type && + !memcmp(&hci->le_white_list[i][1], + cmd->addr, 6)) { + pos = i; + break; + } + } + + if (pos < 0) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_REMOVE_FROM_WHITE_LIST); + return; + } + + hci->le_white_list[pos][0] = 0xff; + memset(&hci->le_white_list[pos][1], 0, 6); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_REMOVE_FROM_WHITE_LIST, + &status, sizeof(status)); +} + +static void cmd_le_encrypt(struct bt_le *hci, const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_encrypt *cmd = data; + struct bt_hci_rsp_le_encrypt rsp; + + if (!bt_crypto_e(hci->crypto, cmd->key, cmd->plaintext, rsp.data)) { + cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LE_ENCRYPT); + return; + } + + rsp.status = BT_HCI_ERR_SUCCESS; + + cmd_complete(hci, BT_HCI_CMD_LE_ENCRYPT, &rsp, sizeof(rsp)); +} + +static void cmd_le_rand(struct bt_le *hci, const void *data, uint8_t size) +{ + struct bt_hci_rsp_le_rand rsp; + uint8_t value[8]; + + if (!bt_crypto_random_bytes(hci->crypto, value, 8)) { + cmd_status(hci, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LE_RAND); + return; + } + + rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(&rsp.number, value, 8); + + cmd_complete(hci, BT_HCI_CMD_LE_RAND, &rsp, sizeof(rsp)); +} + +static void cmd_le_read_supported_states(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_le_read_supported_states rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + memcpy(rsp.states, hci->le_states, 8); + + cmd_complete(hci, BT_HCI_CMD_LE_READ_SUPPORTED_STATES, + &rsp, sizeof(rsp)); +} + +static void cmd_le_set_data_length(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_data_length *cmd = data; + struct bt_hci_rsp_le_set_data_length rsp; + uint16_t handle, tx_len, tx_time; + + handle = le16_to_cpu(cmd->handle); + tx_len = le16_to_cpu(cmd->tx_len); + tx_time = le16_to_cpu(cmd->tx_time); + + /* Valid range for connection handle is 0x0000 to 0x0eff */ + if (handle > 0x0eff) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_DATA_LENGTH); + return; + } + + /* Valid range for suggested max TX octets is 0x001b to 0x00fb */ + if (tx_len < 0x001b || tx_len > 0x00fb) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_DATA_LENGTH); + return; + } + + /* Valid range for suggested max TX time is 0x0148 to 0x0848 */ + if (tx_time < 0x0148 || tx_time > 0x0848) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_DATA_LENGTH); + return; + } + + /* Max TX len and time shall be less or equal supported */ + if (tx_len > MAX_TX_LEN || tx_time > MAX_TX_TIME) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_DATA_LENGTH); + return; + } + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.handle = cpu_to_le16(handle); + + cmd_complete(hci, BT_HCI_CMD_LE_SET_DATA_LENGTH, &rsp, sizeof(rsp)); +} + +static void cmd_le_read_default_data_length(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_le_read_default_data_length rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.tx_len = cpu_to_le16(hci->le_default_tx_len); + rsp.tx_time = cpu_to_le16(hci->le_default_tx_time); + + cmd_complete(hci, BT_HCI_CMD_LE_READ_DEFAULT_DATA_LENGTH, + &rsp, sizeof(rsp)); +} + +static void cmd_le_write_default_data_length(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_write_default_data_length *cmd = data; + uint16_t tx_len, tx_time; + uint8_t status; + + tx_len = le16_to_cpu(cmd->tx_len); + tx_time = le16_to_cpu(cmd->tx_time); + + /* Valid range for suggested max TX octets is 0x001b to 0x00fb */ + if (tx_len < 0x001b || tx_len > 0x00fb) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH); + return; + } + + /* Valid range for suggested max TX time is 0x0148 to 0x0848 */ + if (tx_time < 0x0148 || tx_time > 0x0848) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH); + return; + } + + /* Suggested max TX len and time shall be less or equal supported */ + if (tx_len > MAX_TX_LEN || tx_time > MAX_TX_TIME) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH); + return; + } + + hci->le_default_tx_len = tx_len; + hci->le_default_tx_time = tx_time; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH, + &status, sizeof(status)); +} + +static void cmd_le_read_local_pk256(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_evt_le_read_local_pk256_complete evt; + + cmd_status(hci, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_READ_LOCAL_PK256); + + evt.status = BT_HCI_ERR_SUCCESS; + ecc_make_key(evt.local_pk256, hci->le_local_sk256); + + if (hci->le_event_mask[0] & 0x80) + le_meta_event(hci, BT_HCI_EVT_LE_READ_LOCAL_PK256_COMPLETE, + &evt, sizeof(evt)); +} + +static void cmd_le_generate_dhkey(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_generate_dhkey *cmd = data; + struct bt_hci_evt_le_generate_dhkey_complete evt; + + cmd_status(hci, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_GENERATE_DHKEY); + + evt.status = BT_HCI_ERR_SUCCESS; + ecdh_shared_secret(cmd->remote_pk256, hci->le_local_sk256, evt.dhkey); + + if (hci->le_event_mask[1] & 0x01) + le_meta_event(hci, BT_HCI_EVT_LE_GENERATE_DHKEY_COMPLETE, + &evt, sizeof(evt)); +} + +static void cmd_le_add_to_resolv_list(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_add_to_resolv_list *cmd = data; + uint8_t status; + bool exists = false; + int i, pos = -1; + + /* Valid range for address type is 0x00 to 0x01 */ + if (cmd->addr_type > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST); + return; + } + + for (i = 0; i < hci->le_resolv_list_size; i++) { + if (hci->le_resolv_list[i][0] == cmd->addr_type && + !memcmp(&hci->le_resolv_list[i][1], + cmd->addr, 6)) { + exists = true; + break; + } else if (pos < 0 && hci->le_resolv_list[i][0] == 0xff) + pos = i; + } + + if (exists) { + cmd_status(hci, BT_HCI_ERR_UNSPECIFIED_ERROR, + BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST); + return; + } + + if (pos < 0) { + cmd_status(hci, BT_HCI_ERR_MEM_CAPACITY_EXCEEDED, + BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST); + return; + } + + hci->le_resolv_list[pos][0] = cmd->addr_type; + memcpy(&hci->le_resolv_list[pos][1], cmd->addr, 6); + memcpy(&hci->le_resolv_list[pos][7], cmd->peer_irk, 16); + memcpy(&hci->le_resolv_list[pos][23], cmd->local_irk, 16); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST, + &status, sizeof(status)); +} + +static void cmd_le_remove_from_resolv_list(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_remove_from_resolv_list *cmd = data; + uint8_t status; + int i, pos = -1; + + /* Valid range for address type is 0x00 to 0x01 */ + if (cmd->addr_type > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST); + return; + } + + for (i = 0; i < hci->le_resolv_list_size; i++) { + if (hci->le_resolv_list[i][0] == cmd->addr_type && + !memcmp(&hci->le_resolv_list[i][1], + cmd->addr, 6)) { + pos = i; + break; + } + } + + if (pos < 0) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST); + return; + } + + hci->le_resolv_list[pos][0] = 0xff; + memset(&hci->le_resolv_list[pos][1], 0, 38); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST, + &status, sizeof(status)); +} + +static void cmd_le_clear_resolv_list(struct bt_le *hci, + const void *data, uint8_t size) +{ + uint8_t status; + + clear_resolv_list(hci); + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_CLEAR_RESOLV_LIST, + &status, sizeof(status)); +} + +static void cmd_le_read_resolv_list_size(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_le_read_resolv_list_size rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.size = hci->le_resolv_list_size; + + cmd_complete(hci, BT_HCI_CMD_LE_READ_RESOLV_LIST_SIZE, + &rsp, sizeof(rsp)); +} + +static void cmd_le_read_peer_resolv_addr(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_read_peer_resolv_addr *cmd = data; + struct bt_hci_rsp_le_read_peer_resolv_addr rsp; + + /* Valid range for address type is 0x00 to 0x01 */ + if (cmd->addr_type > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_READ_PEER_RESOLV_ADDR); + return; + } + + rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + memset(rsp.addr, 0, 6); + + cmd_complete(hci, BT_HCI_CMD_LE_READ_PEER_RESOLV_ADDR, + &rsp, sizeof(rsp)); +} + +static void cmd_le_read_local_resolv_addr(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_read_local_resolv_addr *cmd = data; + struct bt_hci_rsp_le_read_local_resolv_addr rsp; + + /* Valid range for address type is 0x00 to 0x01 */ + if (cmd->addr_type > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_READ_LOCAL_RESOLV_ADDR); + return; + } + + rsp.status = BT_HCI_ERR_UNKNOWN_CONN_ID; + memset(rsp.addr, 0, 6); + + cmd_complete(hci, BT_HCI_CMD_LE_READ_LOCAL_RESOLV_ADDR, + &rsp, sizeof(rsp)); +} + +static void cmd_le_set_resolv_enable(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_resolv_enable *cmd = data; + uint8_t status; + + /* Valid range for address resolution enable is 0x00 to 0x01 */ + if (cmd->enable > 0x01) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_RESOLV_ENABLE); + return; + } + + hci->le_resolv_enable = cmd->enable; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_RESOLV_ENABLE, + &status, sizeof(status)); +} + +static void cmd_le_set_resolv_timeout(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_resolv_timeout *cmd = data; + uint16_t timeout; + uint8_t status; + + timeout = le16_to_cpu(cmd->timeout); + + /* Valid range for RPA timeout is 0x0001 to 0xa1b8 */ + if (timeout < 0x0001 || timeout > 0xa1b8) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_RESOLV_TIMEOUT); + return; + } + + hci->le_resolv_timeout = timeout; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_RESOLV_TIMEOUT, + &status, sizeof(status)); +} + +static void cmd_le_read_max_data_length(struct bt_le *hci, + const void *data, uint8_t size) +{ + struct bt_hci_rsp_le_read_max_data_length rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.max_tx_len = cpu_to_le16(MAX_TX_LEN); + rsp.max_tx_time = cpu_to_le16(MAX_TX_TIME); + rsp.max_rx_len = cpu_to_le16(MAX_RX_LEN); + rsp.max_rx_time = cpu_to_le16(MAX_RX_TIME); + + cmd_complete(hci, BT_HCI_CMD_LE_READ_MAX_DATA_LENGTH, + &rsp, sizeof(rsp)); +} + +static void cmd_le_read_phy(struct bt_le *hci, const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_read_phy *cmd = data; + struct bt_hci_rsp_le_read_phy rsp; + + rsp.status = BT_HCI_ERR_SUCCESS; + rsp.handle = cmd->handle; + rsp.tx_phy = 0x01; /* LE 1M */ + rsp.rx_phy = 0x01; /* LE 1M */ + + cmd_complete(hci, BT_HCI_CMD_LE_READ_PHY, &rsp, sizeof(rsp)); +} + +static void cmd_le_set_default_phy(struct bt_le *hci, + const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_default_phy *cmd = data; + uint8_t status, tx_phys, rx_phys; + uint8_t phys_mask; + + phys_mask = (true << 0) | ((!!(hci->le_features[1] & 0x01)) << 1) + | ((!!(hci->le_features[1] & 0x08)) << 2); + + if (cmd->all_phys > 0x03) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_DEFAULT_PHY); + return; + } + + /* Transmitter PHYs preferences */ + if (!(cmd->all_phys & 0x01)) { + /* At least one preference bit shall be set to 1 */ + if (!cmd->tx_phys) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_DEFAULT_PHY); + return; + } + + if (cmd->tx_phys & ~phys_mask) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_DEFAULT_PHY); + return; + } + + tx_phys = cmd->tx_phys; + } else + tx_phys = 0x00; + + /* Transmitter PHYs preferences */ + if (!(cmd->all_phys & 0x02)) { + /* At least one preference bit shall be set to 1 */ + if (!cmd->rx_phys) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_DEFAULT_PHY); + return; + } + + if (cmd->rx_phys & ~phys_mask) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, + BT_HCI_CMD_LE_SET_DEFAULT_PHY); + return; + } + + rx_phys = cmd->rx_phys; + } else + rx_phys = 0x00; + + hci->le_default_all_phys = cmd->all_phys; + hci->le_default_tx_phys = tx_phys; + hci->le_default_rx_phys = rx_phys; + + status = BT_HCI_ERR_SUCCESS; + cmd_complete(hci, BT_HCI_CMD_LE_SET_DEFAULT_PHY, + &status, sizeof(status)); +} + +static void cmd_le_set_phy(struct bt_le *hci, const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_phy *cmd = data; + struct bt_hci_evt_le_phy_update_complete evt; + + cmd_status(hci, BT_HCI_ERR_SUCCESS, BT_HCI_CMD_LE_SET_PHY); + + evt.status = BT_HCI_ERR_SUCCESS; + evt.handle = cmd->handle; + evt.tx_phy = 0x01; /* LE 1M */ + evt.rx_phy = 0x01; /* LE 1M */ + + if (hci->le_event_mask[1] & 0x08) + le_meta_event(hci, BT_HCI_EVT_LE_PHY_UPDATE_COMPLETE, + &evt, sizeof(evt)); +} + +static const struct { + uint16_t opcode; + void (*func) (struct bt_le *hci, const void *data, uint8_t size); + uint8_t size; + bool fixed; +} cmd_table[] = { + { BT_HCI_CMD_DISCONNECT, cmd_disconnect, 3, true }, + + { BT_HCI_CMD_SET_EVENT_MASK, cmd_set_event_mask, 8, true }, + { BT_HCI_CMD_RESET, cmd_reset, 0, true }, + { BT_HCI_CMD_SET_EVENT_MASK_PAGE2, cmd_set_event_mask_page2, 8, true }, + + { BT_HCI_CMD_READ_LOCAL_VERSION, cmd_read_local_version, 0, true }, + { BT_HCI_CMD_READ_LOCAL_COMMANDS, cmd_read_local_commands, 0, true }, + { BT_HCI_CMD_READ_LOCAL_FEATURES, cmd_read_local_features, 0, true }, + { BT_HCI_CMD_READ_BUFFER_SIZE, cmd_read_buffer_size, 0, true }, + { BT_HCI_CMD_READ_BD_ADDR, cmd_read_bd_addr, 0, true }, + + { BT_HCI_CMD_LE_SET_EVENT_MASK, + cmd_le_set_event_mask, 8, true }, + { BT_HCI_CMD_LE_READ_BUFFER_SIZE, + cmd_le_read_buffer_size, 0, true }, + { BT_HCI_CMD_LE_READ_LOCAL_FEATURES, + cmd_le_read_local_features, 0, true }, + { BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, + cmd_le_set_random_address, 6, true }, + { BT_HCI_CMD_LE_SET_ADV_PARAMETERS, + cmd_le_set_adv_parameters, 15, true }, + { BT_HCI_CMD_LE_READ_ADV_TX_POWER, + cmd_le_read_adv_tx_power, 0, true }, + { BT_HCI_CMD_LE_SET_ADV_DATA, + cmd_le_set_adv_data, 32, true }, + { BT_HCI_CMD_LE_SET_SCAN_RSP_DATA, + cmd_le_set_scan_rsp_data, 32, true }, + { BT_HCI_CMD_LE_SET_ADV_ENABLE, + cmd_le_set_adv_enable, 1, true }, + { BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, + cmd_le_set_scan_parameters, 7, true }, + { BT_HCI_CMD_LE_SET_SCAN_ENABLE, + cmd_le_set_scan_enable, 2, true }, + { BT_HCI_CMD_LE_CREATE_CONN, + cmd_le_create_conn, 25, true }, + { BT_HCI_CMD_LE_CREATE_CONN_CANCEL, + cmd_le_create_conn_cancel, 0, true }, + { BT_HCI_CMD_LE_READ_WHITE_LIST_SIZE, + cmd_le_read_white_list_size, 0, true }, + { BT_HCI_CMD_LE_CLEAR_WHITE_LIST, + cmd_le_clear_white_list, 0, true }, + { BT_HCI_CMD_LE_ADD_TO_WHITE_LIST, + cmd_le_add_to_white_list, 7, true }, + { BT_HCI_CMD_LE_REMOVE_FROM_WHITE_LIST, + cmd_le_remove_from_white_list, 7, true }, + + { BT_HCI_CMD_LE_ENCRYPT, cmd_le_encrypt, 32, true }, + { BT_HCI_CMD_LE_RAND, cmd_le_rand, 0, true }, + + { BT_HCI_CMD_LE_READ_SUPPORTED_STATES, + cmd_le_read_supported_states, 0, true }, + + { BT_HCI_CMD_LE_SET_DATA_LENGTH, + cmd_le_set_data_length, 6, true }, + { BT_HCI_CMD_LE_READ_DEFAULT_DATA_LENGTH, + cmd_le_read_default_data_length, 0, true }, + { BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH, + cmd_le_write_default_data_length, 4, true }, + { BT_HCI_CMD_LE_READ_LOCAL_PK256, + cmd_le_read_local_pk256, 0, true }, + { BT_HCI_CMD_LE_GENERATE_DHKEY, + cmd_le_generate_dhkey, 64, true }, + { BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST, + cmd_le_add_to_resolv_list, 39, true }, + { BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST, + cmd_le_remove_from_resolv_list, 7, true }, + { BT_HCI_CMD_LE_CLEAR_RESOLV_LIST, + cmd_le_clear_resolv_list, 0, true }, + { BT_HCI_CMD_LE_READ_RESOLV_LIST_SIZE, + cmd_le_read_resolv_list_size, 0, true }, + { BT_HCI_CMD_LE_READ_PEER_RESOLV_ADDR, + cmd_le_read_peer_resolv_addr, 7, true }, + { BT_HCI_CMD_LE_READ_LOCAL_RESOLV_ADDR, + cmd_le_read_local_resolv_addr, 7, true }, + { BT_HCI_CMD_LE_SET_RESOLV_ENABLE, + cmd_le_set_resolv_enable, 1, true }, + { BT_HCI_CMD_LE_SET_RESOLV_TIMEOUT, + cmd_le_set_resolv_timeout, 2, true }, + { BT_HCI_CMD_LE_READ_MAX_DATA_LENGTH, + cmd_le_read_max_data_length, 0, true }, + { BT_HCI_CMD_LE_READ_PHY, + cmd_le_read_phy, 2, true }, + { BT_HCI_CMD_LE_SET_DEFAULT_PHY, + cmd_le_set_default_phy, 3, true }, + { BT_HCI_CMD_LE_SET_PHY, + cmd_le_set_phy, 7, true }, + + { } +}; + +static void process_command(struct bt_le *hci, const void *data, size_t size) +{ + const struct bt_hci_cmd_hdr *hdr = data; + uint16_t opcode; + unsigned int i; + + if (size < sizeof(*hdr)) + return; + + data += sizeof(*hdr); + size -= sizeof(*hdr); + + opcode = le16_to_cpu(hdr->opcode); + + if (hdr->plen != size) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, opcode); + return; + } + + for (i = 0; cmd_table[i].func; i++) { + if (cmd_table[i].opcode != opcode) + continue; + + if ((cmd_table[i].fixed && size != cmd_table[i].size) || + size < cmd_table[i].size) { + cmd_status(hci, BT_HCI_ERR_INVALID_PARAMETERS, opcode); + return; + } + + cmd_table[i].func(hci, data, size); + return; + } + + cmd_status(hci, BT_HCI_ERR_UNKNOWN_COMMAND, opcode); +} + +static void vhci_read_callback(int fd, uint32_t events, void *user_data) +{ + struct bt_le *hci = user_data; + unsigned char buf[4096]; + ssize_t len; + + if (events & (EPOLLERR | EPOLLHUP)) + return; + + len = read(hci->vhci_fd, buf, sizeof(buf)); + if (len < 1) + return; + + switch (buf[0]) { + case BT_H4_CMD_PKT: + process_command(hci, buf + 1, len - 1); + break; + } +} + +static void phy_recv_callback(uint16_t type, const void *data, + size_t size, void *user_data) +{ + struct bt_le *hci = user_data; + + switch (type) { + case BT_PHY_PKT_ADV: + if (!(hci->le_event_mask[0] & 0x02)) + return; + + if (hci->scan_window_active) { + const struct bt_phy_pkt_adv *pkt = data; + uint8_t buf[100]; + struct bt_hci_evt_le_adv_report *evt = (void *) buf; + uint8_t tx_addr_type, tx_addr[6]; + + if (hci->scan_chan_idx != pkt->chan_idx) + break; + + resolve_peer_addr(hci, pkt->tx_addr_type, pkt->tx_addr, + &tx_addr_type, tx_addr); + + if (hci->le_scan_filter_policy == 0x01 || + hci->le_scan_filter_policy == 0x03) { + if (!is_in_white_list(hci, tx_addr_type, + tx_addr)) + break; + } + + if (hci->le_scan_filter_dup) { + if (!add_to_scan_cache(hci, tx_addr_type, + tx_addr)) + break; + } + + memset(buf, 0, sizeof(buf)); + evt->num_reports = 0x01; + evt->event_type = pkt->pdu_type; + evt->addr_type = tx_addr_type; + memcpy(evt->addr, tx_addr, 6); + evt->data_len = pkt->adv_data_len; + memcpy(buf + sizeof(*evt), data + sizeof(*pkt), + pkt->adv_data_len); + + le_meta_event(hci, BT_HCI_EVT_LE_ADV_REPORT, buf, + sizeof(*evt) + pkt->adv_data_len + 1); + + if (hci->le_scan_type == 0x00) + break; + + memset(buf, 0, sizeof(buf)); + evt->num_reports = 0x01; + evt->event_type = 0x04; + evt->addr_type = tx_addr_type; + memcpy(evt->addr, tx_addr, 6); + evt->data_len = pkt->scan_rsp_len; + memcpy(buf + sizeof(*evt), data + sizeof(*pkt) + + pkt->adv_data_len, + pkt->scan_rsp_len); + + le_meta_event(hci, BT_HCI_EVT_LE_ADV_REPORT, buf, + sizeof(*evt) + pkt->scan_rsp_len + 1); + } + break; + } +} + +struct bt_le *bt_le_new(void) +{ + unsigned char setup_cmd[2]; + struct bt_le *hci; + + hci = calloc(1, sizeof(*hci)); + if (!hci) + return NULL; + + hci->adv_timeout_id = -1; + hci->scan_timeout_id = -1; + hci->scan_window_active = false; + + reset_defaults(hci); + + hci->vhci_fd = open("/dev/vhci", O_RDWR); + if (hci->vhci_fd < 0) { + free(hci); + return NULL; + } + + setup_cmd[0] = HCI_VENDOR_PKT; + setup_cmd[1] = HCI_PRIMARY; + + if (write(hci->vhci_fd, setup_cmd, sizeof(setup_cmd)) < 0) { + close(hci->vhci_fd); + free(hci); + return NULL; + } + + mainloop_add_fd(hci->vhci_fd, EPOLLIN, vhci_read_callback, hci, NULL); + + hci->phy = bt_phy_new(); + hci->crypto = bt_crypto_new(); + + bt_phy_register(hci->phy, phy_recv_callback, hci); + + return bt_le_ref(hci); +} + +struct bt_le *bt_le_ref(struct bt_le *hci) +{ + if (!hci) + return NULL; + + __sync_fetch_and_add(&hci->ref_count, 1); + + return hci; +} + +void bt_le_unref(struct bt_le *hci) +{ + if (!hci) + return; + + if (__sync_sub_and_fetch(&hci->ref_count, 1)) + return; + + stop_adv(hci); + + bt_crypto_unref(hci->crypto); + bt_phy_unref(hci->phy); + + mainloop_remove_fd(hci->vhci_fd); + + close(hci->vhci_fd); + + free(hci); +} diff --git a/emulator/le.h b/emulator/le.h new file mode 100644 index 0000000..5e832e8 --- /dev/null +++ b/emulator/le.h @@ -0,0 +1,32 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +struct bt_le; + +struct bt_le *bt_le_new(void); + +struct bt_le *bt_le_ref(struct bt_le *le); +void bt_le_unref(struct bt_le *le); diff --git a/emulator/main.c b/emulator/main.c new file mode 100644 index 0000000..68c5348 --- /dev/null +++ b/emulator/main.c @@ -0,0 +1,224 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "src/shared/mainloop.h" +#include "serial.h" +#include "server.h" +#include "vhci.h" +#include "amp.h" +#include "le.h" + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + } +} + +static void usage(void) +{ + printf("btvirt - Bluetooth emulator\n" + "Usage:\n"); + printf("\tbtvirt [options]\n"); + printf("options:\n" + "\t-S Create local serial port\n" + "\t-s Create local server sockets\n" + "\t-l [num] Number of local controllers\n" + "\t-L Create LE only controller\n" + "\t-B Create BR/EDR only controller\n" + "\t-A Create AMP controller\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "serial", no_argument, NULL, 'S' }, + { "server", no_argument, NULL, 's' }, + { "local", optional_argument, NULL, 'l' }, + { "le", no_argument, NULL, 'L' }, + { "bredr", no_argument, NULL, 'B' }, + { "amp", no_argument, NULL, 'A' }, + { "letest", optional_argument, NULL, 'U' }, + { "amptest", optional_argument, NULL, 'T' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + struct server *server1; + struct server *server2; + struct server *server3; + struct server *server4; + struct server *server5; + bool server_enabled = false; + bool serial_enabled = false; + int letest_count = 0; + int amptest_count = 0; + int vhci_count = 0; + enum vhci_type vhci_type = VHCI_TYPE_BREDRLE; + int i; + + mainloop_init(); + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "Ssl::LBAUTvh", + main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'S': + serial_enabled = true; + break; + case 's': + server_enabled = true; + break; + case 'l': + if (optarg) + vhci_count = atoi(optarg); + else + vhci_count = 1; + break; + case 'L': + vhci_type = VHCI_TYPE_LE; + break; + case 'B': + vhci_type = VHCI_TYPE_BREDR; + break; + case 'A': + vhci_type = VHCI_TYPE_AMP; + break; + case 'U': + if (optarg) + letest_count = atoi(optarg); + else + letest_count = 1; + break; + case 'T': + if (optarg) + amptest_count = atoi(optarg); + else + amptest_count = 1; + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (letest_count < 1 && amptest_count < 1 && + vhci_count < 1 && !server_enabled && !serial_enabled) { + fprintf(stderr, "No emulator specified\n"); + return EXIT_FAILURE; + } + + printf("Bluetooth emulator ver %s\n", VERSION); + + for (i = 0; i < letest_count; i++) { + struct bt_le *le; + + le = bt_le_new(); + if (!le) { + fprintf(stderr, "Failed to create LE controller\n"); + return EXIT_FAILURE; + } + } + + for (i = 0; i < amptest_count; i++) { + struct bt_amp *amp; + + amp = bt_amp_new(); + if (!amp) { + fprintf(stderr, "Failed to create AMP controller\n"); + return EXIT_FAILURE; + } + } + + for (i = 0; i < vhci_count; i++) { + struct vhci *vhci; + + vhci = vhci_open(vhci_type); + if (!vhci) { + fprintf(stderr, "Failed to open Virtual HCI device\n"); + return EXIT_FAILURE; + } + } + + if (serial_enabled) { + struct serial *serial; + + serial = serial_open(SERIAL_TYPE_BREDRLE); + if (!serial) + fprintf(stderr, "Failed to open serial emulation\n"); + } + + if (server_enabled) { + server1 = server_open_unix(SERVER_TYPE_BREDRLE, + "/tmp/bt-server-bredrle"); + if (!server1) + fprintf(stderr, "Failed to open BR/EDR/LE server\n"); + + server2 = server_open_unix(SERVER_TYPE_BREDR, + "/tmp/bt-server-bredr"); + if (!server2) + fprintf(stderr, "Failed to open BR/EDR server\n"); + + server3 = server_open_unix(SERVER_TYPE_AMP, + "/tmp/bt-server-amp"); + if (!server3) + fprintf(stderr, "Failed to open AMP server\n"); + + server4 = server_open_unix(SERVER_TYPE_LE, + "/tmp/bt-server-le"); + if (!server4) + fprintf(stderr, "Failed to open LE server\n"); + + server5 = server_open_unix(SERVER_TYPE_MONITOR, + "/tmp/bt-server-mon"); + if (!server5) + fprintf(stderr, "Failed to open monitor server\n"); + } + + return mainloop_run_with_signal(signal_callback, NULL); +} diff --git a/emulator/phy.c b/emulator/phy.c new file mode 100644 index 0000000..beb8bbb --- /dev/null +++ b/emulator/phy.c @@ -0,0 +1,299 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "src/shared/util.h" +#include "src/shared/mainloop.h" + +#include "phy.h" + +#define BT_PHY_PORT 45023 + +struct bt_phy { + volatile int ref_count; + int rx_fd; + int tx_fd; + uint64_t id; + bt_phy_callback_func_t callback; + void *user_data; +}; + +struct bt_phy_hdr { + uint64_t id; + uint32_t flags; + uint16_t type; + uint16_t len; +} __attribute__ ((packed)); + +static bool get_random_bytes(void *buf, size_t num_bytes) +{ + ssize_t len; + int fd; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return false; + + len = read(fd, buf, num_bytes); + + close(fd); + + if (len < 0) + return false; + + return true; +} + +static void phy_rx_callback(int fd, uint32_t events, void *user_data) +{ + struct bt_phy *phy = user_data; + struct msghdr msg; + struct iovec iov[2]; + struct bt_phy_hdr hdr; + unsigned char buf[4096]; + ssize_t len; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(fd); + return; + } + + iov[0].iov_base = &hdr; + iov[0].iov_len = sizeof(hdr); + iov[1].iov_base = buf; + iov[1].iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + len = recvmsg(phy->rx_fd, &msg, MSG_DONTWAIT); + if (len < 0) + return; + + if ((size_t) len < sizeof(hdr)) + return; + + if (le64_to_cpu(hdr.id) == phy->id) + return; + + if (len - sizeof(hdr) != le16_to_cpu(hdr.len)) + return; + + if (phy->callback) + phy->callback(le16_to_cpu(hdr.type), + buf, len - sizeof(hdr), phy->user_data); +} + +static int create_rx_socket(void) +{ + struct sockaddr_in addr; + int fd, opt = 1; + + fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return -1; + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(BT_PHY_PORT); + addr.sin_addr.s_addr = INADDR_BROADCAST; + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static int create_tx_socket(void) +{ + int fd, opt = 1; + + fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) + return -1; + + setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)); + + return fd; +} + +struct bt_phy *bt_phy_new(void) +{ + struct bt_phy *phy; + + phy = calloc(1, sizeof(*phy)); + if (!phy) + return NULL; + + phy->rx_fd = create_rx_socket(); + if (phy->rx_fd < 0) { + free(phy); + return NULL; + } + + phy->tx_fd = create_tx_socket(); + if (phy->tx_fd < 0) { + close(phy->rx_fd); + free(phy); + return NULL; + } + + mainloop_add_fd(phy->rx_fd, EPOLLIN, phy_rx_callback, phy, NULL); + + if (!get_random_bytes(&phy->id, sizeof(phy->id))) { + srandom(time(NULL)); + phy->id = random(); + } + + bt_phy_send(phy, BT_PHY_PKT_NULL, NULL, 0); + + return bt_phy_ref(phy); +} + +struct bt_phy *bt_phy_ref(struct bt_phy *phy) +{ + if (!phy) + return NULL; + + __sync_fetch_and_add(&phy->ref_count, 1); + + return phy; +} + +void bt_phy_unref(struct bt_phy *phy) +{ + if (!phy) + return; + + if (__sync_sub_and_fetch(&phy->ref_count, 1)) + return; + + mainloop_remove_fd(phy->rx_fd); + + close(phy->tx_fd); + close(phy->rx_fd); + + free(phy); +} + +bool bt_phy_send(struct bt_phy *phy, uint16_t type, + const void *data, size_t size) +{ + return bt_phy_send_vector(phy, type, data, size, NULL, 0, NULL, 0); +} + +bool bt_phy_send_vector(struct bt_phy *phy, uint16_t type, + const void *data1, size_t size1, + const void *data2, size_t size2, + const void *data3, size_t size3) +{ + struct bt_phy_hdr hdr; + struct sockaddr_in addr; + struct msghdr msg; + struct iovec iov[4]; + ssize_t len; + + if (!phy) + return false; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(BT_PHY_PORT); + addr.sin_addr.s_addr = INADDR_BROADCAST; + + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &addr; + msg.msg_namelen = sizeof(addr); + msg.msg_iov = iov; + msg.msg_iovlen = 0; + + memset(&hdr, 0, sizeof(hdr)); + hdr.id = cpu_to_le64(phy->id); + hdr.flags = cpu_to_le32(0); + hdr.type = cpu_to_le16(type); + hdr.len = cpu_to_le16(size1 + size2 + size3); + + iov[msg.msg_iovlen].iov_base = &hdr; + iov[msg.msg_iovlen].iov_len = sizeof(hdr); + msg.msg_iovlen++; + + if (data1 && size1 > 0) { + iov[msg.msg_iovlen].iov_base = (void *) data1; + iov[msg.msg_iovlen].iov_len = size1; + msg.msg_iovlen++; + } + + if (data2 && size2 > 0) { + iov[msg.msg_iovlen].iov_base = (void *) data2; + iov[msg.msg_iovlen].iov_len = size2; + msg.msg_iovlen++; + } + + if (data3 && size3 > 0) { + iov[msg.msg_iovlen].iov_base = (void *) data3; + iov[msg.msg_iovlen].iov_len = size3; + msg.msg_iovlen++; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(BT_PHY_PORT); + addr.sin_addr.s_addr = INADDR_BROADCAST; + + len = sendmsg(phy->tx_fd, &msg, MSG_DONTWAIT); + if (len < 0) + return false; + + return true; +} + +bool bt_phy_register(struct bt_phy *phy, bt_phy_callback_func_t callback, + void *user_data) +{ + if (!phy) + return false; + + phy->callback = callback; + phy->user_data = user_data; + + return true; +} diff --git a/emulator/phy.h b/emulator/phy.h new file mode 100644 index 0000000..d5efa51 --- /dev/null +++ b/emulator/phy.h @@ -0,0 +1,72 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +struct bt_phy; + +struct bt_phy *bt_phy_new(void); + +struct bt_phy *bt_phy_ref(struct bt_phy *phy); +void bt_phy_unref(struct bt_phy *phy); + +bool bt_phy_send(struct bt_phy *phy, uint16_t type, + const void *data, size_t size); +bool bt_phy_send_vector(struct bt_phy *phy, uint16_t type, + const void *data1, size_t size1, + const void *data2, size_t size2, + const void *data3, size_t size3); + +typedef void (*bt_phy_callback_func_t)(uint16_t type, const void *data, + size_t size, void *user_data); + +bool bt_phy_register(struct bt_phy *phy, bt_phy_callback_func_t callback, + void *user_data); + +#define BT_PHY_PKT_NULL 0x0000 + +#define BT_PHY_PKT_ADV 0x0001 +struct bt_phy_pkt_adv { + uint8_t chan_idx; + uint8_t pdu_type; + uint8_t tx_addr_type; + uint8_t tx_addr[6]; + uint8_t rx_addr_type; + uint8_t rx_addr[6]; + uint8_t adv_data_len; + uint8_t scan_rsp_len; +} __attribute__ ((packed)); + +#define BT_PHY_PKT_CONN 0x0002 +struct bt_phy_pkt_conn { + uint8_t chan_idx; + uint8_t link_type; + uint8_t tx_addr_type; + uint8_t tx_addr[6]; + uint8_t rx_addr_type; + uint8_t rx_addr[6]; + uint8_t features[8]; + uint8_t id; +} __attribute__ ((packed)); diff --git a/emulator/serial.c b/emulator/serial.c new file mode 100644 index 0000000..1c324d5 --- /dev/null +++ b/emulator/serial.c @@ -0,0 +1,249 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/mainloop.h" +#include "btdev.h" +#include "serial.h" + +#define uninitialized_var(x) x = x + +struct serial { + enum serial_type type; + uint16_t id; + int fd; + char path[PATH_MAX]; + struct btdev *btdev; + uint8_t *pkt_data; + uint8_t pkt_type; + uint16_t pkt_expect; + uint16_t pkt_len; + uint16_t pkt_offset; +}; + +static void open_pty(struct serial *serial); + +static void serial_destroy(void *user_data) +{ + struct serial *serial = user_data; + + btdev_destroy(serial->btdev); + serial->btdev = NULL; + + close(serial->fd); + serial->fd = -1; +} + +static void serial_write_callback(const struct iovec *iov, int iovlen, + void *user_data) +{ + struct serial *serial = user_data; + ssize_t written; + + written = writev(serial->fd, iov, iovlen); + if (written < 0) + return; +} + +static void serial_read_callback(int fd, uint32_t events, void *user_data) +{ + struct serial *serial = user_data; + static uint8_t buf[4096]; + uint8_t *ptr = buf; + ssize_t len; + uint16_t count; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(serial->fd); + open_pty(serial); + return; + } + +again: + len = read(serial->fd, buf + serial->pkt_offset, + sizeof(buf) - serial->pkt_offset); + if (len < 0) { + if (errno == EAGAIN) + goto again; + return; + } + + if (!serial->btdev) + return; + + count = serial->pkt_offset + len; + + while (count > 0) { + hci_command_hdr *cmd_hdr; + + if (!serial->pkt_data) { + serial->pkt_type = ptr[0]; + + switch (serial->pkt_type) { + case HCI_COMMAND_PKT: + if (count < HCI_COMMAND_HDR_SIZE + 1) { + serial->pkt_offset += len; + return; + } + cmd_hdr = (hci_command_hdr *) (ptr + 1); + serial->pkt_expect = HCI_COMMAND_HDR_SIZE + + cmd_hdr->plen + 1; + serial->pkt_data = malloc(serial->pkt_expect); + serial->pkt_len = 0; + break; + default: + printf("packet error\n"); + return; + } + + serial->pkt_offset = 0; + } + + if (count >= serial->pkt_expect) { + memcpy(serial->pkt_data + serial->pkt_len, + ptr, serial->pkt_expect); + ptr += serial->pkt_expect; + count -= serial->pkt_expect; + + btdev_receive_h4(serial->btdev, serial->pkt_data, + serial->pkt_len + serial->pkt_expect); + + free(serial->pkt_data); + serial->pkt_data = NULL; + } else { + memcpy(serial->pkt_data + serial->pkt_len, ptr, count); + serial->pkt_len += count; + serial->pkt_expect -= count; + count = 0; + } + } +} + +static void open_pty(struct serial *serial) +{ + enum btdev_type uninitialized_var(type); + + serial->fd = posix_openpt(O_RDWR | O_NOCTTY); + if (serial->fd < 0) { + perror("Failed to get master pseudo terminal"); + return; + } + + if (grantpt(serial->fd) < 0) { + perror("Failed to grant slave pseudo terminal"); + close(serial->fd); + serial->fd = -1; + return; + } + + if (unlockpt(serial->fd) < 0) { + perror("Failed to unlock slave pseudo terminal"); + close(serial->fd); + serial->fd = -1; + return; + } + + ptsname_r(serial->fd, serial->path, sizeof(serial->path)); + + printf("Pseudo terminal at %s\n", serial->path); + + switch (serial->type) { + case SERIAL_TYPE_BREDRLE: + type = BTDEV_TYPE_BREDRLE; + break; + case SERIAL_TYPE_BREDR: + type = BTDEV_TYPE_BREDR; + break; + case SERIAL_TYPE_LE: + type = BTDEV_TYPE_LE; + break; + case SERIAL_TYPE_AMP: + type = BTDEV_TYPE_AMP; + break; + } + + serial->btdev = btdev_create(type, serial->id); + if (!serial->btdev) { + close(serial->fd); + serial->fd = -1; + return; + } + + btdev_set_send_handler(serial->btdev, serial_write_callback, serial); + + if (mainloop_add_fd(serial->fd, EPOLLIN, serial_read_callback, + serial, serial_destroy) < 0) { + btdev_destroy(serial->btdev); + serial->btdev = NULL; + close(serial->fd); + serial->fd = -1; + return; + } +} + +struct serial *serial_open(enum serial_type type) +{ + struct serial *serial; + enum btdev_type uninitialized_var(dev_type); + + serial = malloc(sizeof(*serial)); + if (!serial) + return NULL; + + memset(serial, 0, sizeof(*serial)); + serial->type = type; + serial->id = 0x42; + + open_pty(serial); + + return serial; +} + +void serial_close(struct serial *serial) +{ + if (!serial) + return; + + mainloop_remove_fd(serial->fd); + + free(serial); +} diff --git a/emulator/serial.h b/emulator/serial.h new file mode 100644 index 0000000..4e5a56f --- /dev/null +++ b/emulator/serial.h @@ -0,0 +1,37 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +enum serial_type { + SERIAL_TYPE_BREDRLE, + SERIAL_TYPE_BREDR, + SERIAL_TYPE_LE, + SERIAL_TYPE_AMP, +}; + +struct serial; + +struct serial *serial_open(enum serial_type type); +void serial_close(struct serial *serial); diff --git a/emulator/server.c b/emulator/server.c new file mode 100644 index 0000000..c28b15e --- /dev/null +++ b/emulator/server.c @@ -0,0 +1,395 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/mainloop.h" +#include "btdev.h" +#include "server.h" + +#define uninitialized_var(x) x = x + +struct server { + enum server_type type; + uint16_t id; + int fd; +}; + +struct client { + int fd; + struct btdev *btdev; + uint8_t *pkt_data; + uint8_t pkt_type; + uint16_t pkt_expect; + uint16_t pkt_len; + uint16_t pkt_offset; +}; + +static void server_destroy(void *user_data) +{ + struct server *server = user_data; + + close(server->fd); + + free(server); +} + +static void client_destroy(void *user_data) +{ + struct client *client = user_data; + + btdev_destroy(client->btdev); + + close(client->fd); + + free(client); +} + +static void client_write_callback(const struct iovec *iov, int iovlen, + void *user_data) +{ + struct client *client = user_data; + struct msghdr msg; + ssize_t written; + + memset(&msg, 0, sizeof(msg)); + + msg.msg_iov = (struct iovec *) iov; + msg.msg_iovlen = iovlen; + + written = sendmsg(client->fd, &msg, MSG_DONTWAIT); + if (written < 0) + return; +} + +static void client_read_callback(int fd, uint32_t events, void *user_data) +{ + struct client *client = user_data; + static uint8_t buf[4096]; + uint8_t *ptr = buf; + ssize_t len; + uint16_t count; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(client->fd); + return; + } + +again: + len = recv(fd, buf + client->pkt_offset, + sizeof(buf) - client->pkt_offset, MSG_DONTWAIT); + if (len < 0) { + if (errno == EAGAIN) + goto again; + return; + } + + if (!client->btdev) + return; + + count = client->pkt_offset + len; + + while (count > 0) { + hci_command_hdr *cmd_hdr; + hci_acl_hdr *acl_hdr; + + if (!client->pkt_data) { + client->pkt_type = ptr[0]; + + switch (client->pkt_type) { + case HCI_COMMAND_PKT: + if (count < HCI_COMMAND_HDR_SIZE + 1) { + client->pkt_offset += len; + return; + } + cmd_hdr = (hci_command_hdr *) (ptr + 1); + client->pkt_expect = HCI_COMMAND_HDR_SIZE + + cmd_hdr->plen + 1; + client->pkt_data = malloc(client->pkt_expect); + client->pkt_len = 0; + break; + case HCI_ACLDATA_PKT: + acl_hdr = (hci_acl_hdr*)(ptr + 1); + client->pkt_expect = HCI_ACL_HDR_SIZE + acl_hdr->dlen + 1; + client->pkt_data = malloc(client->pkt_expect); + client->pkt_len = 0; + break; + default: + printf("packet error\n"); + return; + } + + client->pkt_offset = 0; + } + + if (count >= client->pkt_expect) { + memcpy(client->pkt_data + client->pkt_len, + ptr, client->pkt_expect); + ptr += client->pkt_expect; + count -= client->pkt_expect; + + btdev_receive_h4(client->btdev, client->pkt_data, + client->pkt_len + client->pkt_expect); + + free(client->pkt_data); + client->pkt_data = NULL; + } else { + memcpy(client->pkt_data + client->pkt_len, ptr, count); + client->pkt_len += count; + client->pkt_expect -= count; + count = 0; + } + } +} + +static int accept_client(int fd) +{ + struct sockaddr_un addr; + socklen_t len; + int nfd; + + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + + if (getsockname(fd, (struct sockaddr *) &addr, &len) < 0) { + perror("Failed to get socket name"); + return -1; + } + + printf("Request for %s\n", addr.sun_path); + + nfd = accept(fd, (struct sockaddr *) &addr, &len); + if (nfd < 0) { + perror("Failed to accept client socket"); + return -1; + } + + return nfd; +} + +static void server_accept_callback(int fd, uint32_t events, void *user_data) +{ + struct server *server = user_data; + struct client *client; + enum btdev_type uninitialized_var(type); + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(server->fd); + return; + } + + client = malloc(sizeof(*client)); + if (!client) + return; + + memset(client, 0, sizeof(*client)); + + client->fd = accept_client(server->fd); + if (client->fd < 0) { + free(client); + return; + } + + switch (server->type) { + case SERVER_TYPE_BREDRLE: + type = BTDEV_TYPE_BREDRLE; + break; + case SERVER_TYPE_BREDR: + type = BTDEV_TYPE_BREDR; + break; + case SERVER_TYPE_LE: + type = BTDEV_TYPE_LE; + break; + case SERVER_TYPE_AMP: + type = BTDEV_TYPE_AMP; + break; + case SERVER_TYPE_MONITOR: + goto done; + } + + client->btdev = btdev_create(type, server->id); + if (!client->btdev) { + close(client->fd); + free(client); + return; + } + + btdev_set_send_handler(client->btdev, client_write_callback, client); + +done: + if (mainloop_add_fd(client->fd, EPOLLIN, client_read_callback, + client, client_destroy) < 0) { + btdev_destroy(client->btdev); + close(client->fd); + free(client); + } +} + +static int open_unix(const char *path) +{ + struct sockaddr_un addr; + int fd; + + unlink(path); + + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + perror("Failed to open server socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, path); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind server socket"); + close(fd); + return -1; + } + + if (listen(fd, 5) < 0) { + perror("Failed to listen server socket"); + close(fd); + return -1; + } + + return fd; +} + +struct server *server_open_unix(enum server_type type, const char *path) +{ + struct server *server; + + server = malloc(sizeof(*server)); + if (!server) + return NULL; + + memset(server, 0, sizeof(*server)); + server->type = type; + server->id = 0x42; + + server->fd = open_unix(path); + if (server->fd < 0) { + free(server); + return NULL; + } + + if (mainloop_add_fd(server->fd, EPOLLIN, server_accept_callback, + server, server_destroy) < 0) { + close(server->fd); + free(server); + return NULL; + } + + return server; +} + +static int open_tcp(void) +{ + struct sockaddr_in addr; + int fd, opt = 1; + + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) { + perror("Failed to open server socket"); + return -1; + } + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(45550); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind server socket"); + close(fd); + return -1; + } + + if (listen(fd, 5) < 0) { + perror("Failed to listen server socket"); + close(fd); + return -1; + } + + return fd; +} + +struct server *server_open_tcp(enum server_type type) +{ + struct server *server; + + server = malloc(sizeof(*server)); + if (!server) + return server; + + memset(server, 0, sizeof(*server)); + server->type = type; + server->id = 0x43; + + server->fd = open_tcp(); + if (server->fd < 0) { + free(server); + return NULL; + } + + if (mainloop_add_fd(server->fd, EPOLLIN, server_accept_callback, + server, server_destroy) < 0) { + close(server->fd); + free(server); + return NULL; + } + + return server; +} + +void server_close(struct server *server) +{ + if (!server) + return; + + mainloop_remove_fd(server->fd); +} diff --git a/emulator/server.h b/emulator/server.h new file mode 100644 index 0000000..bf725e7 --- /dev/null +++ b/emulator/server.h @@ -0,0 +1,39 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +enum server_type { + SERVER_TYPE_BREDRLE, + SERVER_TYPE_BREDR, + SERVER_TYPE_LE, + SERVER_TYPE_AMP, + SERVER_TYPE_MONITOR, +}; + +struct server; + +struct server *server_open_unix(enum server_type type, const char *path); +struct server *server_open_tcp(enum server_type type); +void server_close(struct server *server); diff --git a/emulator/smp.c b/emulator/smp.c new file mode 100644 index 0000000..c30de36 --- /dev/null +++ b/emulator/smp.c @@ -0,0 +1,911 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/util.h" +#include "src/shared/crypto.h" +#include "src/shared/ecc.h" +#include "monitor/bt.h" +#include "bthost.h" + +#define SMP_CID 0x0006 +#define SMP_BREDR_CID 0x0007 + +#define L2CAP_FC_SMP_BREDR 0x80 + +#define SMP_PASSKEY_ENTRY_FAILED 0x01 +#define SMP_OOB_NOT_AVAIL 0x02 +#define SMP_AUTH_REQUIREMENTS 0x03 +#define SMP_CONFIRM_FAILED 0x04 +#define SMP_PAIRING_NOTSUPP 0x05 +#define SMP_ENC_KEY_SIZE 0x06 +#define SMP_CMD_NOTSUPP 0x07 +#define SMP_UNSPECIFIED 0x08 +#define SMP_REPEATED_ATTEMPTS 0x09 +#define SMP_INVALID_PARAMS 0x0a +#define SMP_DHKEY_CHECK_FAILED 0x0b +#define SMP_NUMERIC_COMP_FAILED 0x0c +#define SMP_BREDR_PAIRING_IN_PROGRESS 0x0d + +#define DIST_ENC_KEY 0x01 +#define DIST_ID_KEY 0x02 +#define DIST_SIGN 0x04 +#define DIST_LINK_KEY 0x08 + +#define SC_NO_DIST (DIST_ENC_KEY | DIST_LINK_KEY) + +#define MAX_IO_CAP 0x04 + +#define SMP_AUTH_NONE 0x00 +#define SMP_AUTH_BONDING 0x01 +#define SMP_AUTH_MITM 0x04 +#define SMP_AUTH_SC 0x08 +#define SMP_AUTH_KEYPRESS 0x10 + +struct smp { + struct bthost *bthost; + struct smp_conn *conn; + struct bt_crypto *crypto; +}; + +struct smp_conn { + struct smp *smp; + uint16_t handle; + uint8_t addr_type; + bool out; + bool sc; + bool initiator; + uint8_t method; + uint8_t local_key_dist; + uint8_t remote_key_dist; + uint8_t ia[6]; + uint8_t ia_type; + uint8_t ra[6]; + uint8_t ra_type; + uint8_t tk[16]; + uint8_t prnd[16]; + uint8_t rrnd[16]; + uint8_t pcnf[16]; + uint8_t preq[7]; + uint8_t prsp[7]; + uint8_t ltk[16]; + + uint8_t local_sk[32]; + uint8_t local_pk[64]; + uint8_t remote_pk[64]; + uint8_t dhkey[32]; + uint8_t mackey[16]; + + uint8_t passkey_notify; + uint8_t passkey_round; +}; + +enum { + JUST_WORKS, + JUST_CFM, + REQ_PASSKEY, + CFM_PASSKEY, + REQ_OOB, + DSP_PASSKEY, + OVERLAP, +}; + +static const uint8_t gen_method[5][5] = { + { JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY }, + { JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY }, + { CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY }, + { JUST_WORKS, JUST_CFM, JUST_WORKS, JUST_WORKS, JUST_CFM }, + { CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, OVERLAP }, +}; + +static const uint8_t sc_method[5][5] = { + { JUST_WORKS, JUST_CFM, REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY }, + { JUST_WORKS, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY }, + { DSP_PASSKEY, DSP_PASSKEY, REQ_PASSKEY, JUST_WORKS, DSP_PASSKEY }, + { JUST_WORKS, JUST_CFM, JUST_WORKS, JUST_WORKS, JUST_CFM }, + { DSP_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY }, +}; + +static uint8_t get_auth_method(struct smp_conn *conn, uint8_t local_io, + uint8_t remote_io) +{ + /* If either side has unknown io_caps, use JUST_CFM (which gets + * converted later to JUST_WORKS if we're initiators. + */ + if (local_io > MAX_IO_CAP || remote_io > MAX_IO_CAP) + return JUST_CFM; + + if (conn->sc) + return sc_method[remote_io][local_io]; + + return gen_method[remote_io][local_io]; +} + +static uint8_t sc_select_method(struct smp_conn *conn) +{ + struct bt_l2cap_smp_pairing_request *local, *remote; + uint8_t local_mitm, remote_mitm, local_io, remote_io, method; + + if (conn->out) { + local = (void *) &conn->preq[1]; + remote = (void *) &conn->prsp[1]; + } else { + local = (void *) &conn->prsp[1]; + remote = (void *) &conn->preq[1]; + } + + local_io = local->io_capa; + remote_io = remote->io_capa; + + local_mitm = (local->auth_req & SMP_AUTH_MITM); + remote_mitm = (remote->auth_req & SMP_AUTH_MITM); + + /* If either side wants MITM, look up the method from the table, + * otherwise use JUST WORKS. + */ + if (local_mitm || remote_mitm) + method = get_auth_method(conn, local_io, remote_io); + else + method = JUST_WORKS; + + /* Don't confirm locally initiated pairing attempts */ + if (method == JUST_CFM && conn->initiator) + method = JUST_WORKS; + + return method; +} + +static uint8_t key_dist(struct bthost *host) +{ + if (!bthost_bredr_capable(host)) + return (DIST_ENC_KEY | DIST_ID_KEY | DIST_SIGN); + + return (DIST_ENC_KEY | DIST_ID_KEY | DIST_SIGN | DIST_LINK_KEY); +} + +static void smp_send(struct smp_conn *conn, uint8_t smp_cmd, const void *data, + uint8_t len) +{ + struct iovec iov[2]; + uint16_t cid; + + iov[0].iov_base = &smp_cmd; + iov[0].iov_len = 1; + + iov[1].iov_base = (void *) data; + iov[1].iov_len = len; + + if (conn->addr_type == BDADDR_BREDR) + cid = SMP_BREDR_CID; + else + cid = SMP_CID; + + bthost_send_cid_v(conn->smp->bthost, conn->handle, cid, iov, 2); +} + +static bool send_public_key(struct smp_conn *conn) +{ + if (!ecc_make_key(conn->local_pk, conn->local_sk)) + return false; + + smp_send(conn, BT_L2CAP_SMP_PUBLIC_KEY, conn->local_pk, 64); + + return true; +} + +static void sc_dhkey_check(struct smp_conn *conn) +{ + uint8_t io_cap[3], r[16], a[7], b[7], *local_addr, *remote_addr; + struct bt_l2cap_smp_dhkey_check check; + + memcpy(a, conn->ia, 6); + memcpy(b, conn->ra, 6); + a[6] = conn->ia_type; + b[6] = conn->ra_type; + + if (conn->out) { + local_addr = a; + remote_addr = b; + memcpy(io_cap, &conn->preq[1], 3); + } else { + local_addr = b; + remote_addr = a; + memcpy(io_cap, &conn->prsp[1], 3); + } + + memset(r, 0, sizeof(r)); + + bt_crypto_f6(conn->smp->crypto, conn->mackey, conn->prnd, conn->rrnd, + r, io_cap, local_addr, remote_addr, check.e); + + smp_send(conn, BT_L2CAP_SMP_DHKEY_CHECK, &check, sizeof(check)); +} + +static void sc_mackey_and_ltk(struct smp_conn *conn) +{ + uint8_t *na, *nb, a[7], b[7]; + + if (conn->out) { + na = conn->prnd; + nb = conn->rrnd; + } else { + na = conn->rrnd; + nb = conn->prnd; + } + + memcpy(a, conn->ia, 6); + memcpy(b, conn->ra, 6); + a[6] = conn->ia_type; + b[6] = conn->ra_type; + + bt_crypto_f5(conn->smp->crypto, conn->dhkey, na, nb, a, b, + conn->mackey, conn->ltk); +} + +static uint8_t sc_passkey_send_confirm(struct smp_conn *conn) +{ + struct bt_l2cap_smp_pairing_confirm cfm; + uint8_t r; + + r = ((conn->passkey_notify >> conn->passkey_round) & 0x01); + r |= 0x80; + + if (!bt_crypto_f4(conn->smp->crypto, conn->local_pk, conn->remote_pk, + conn->prnd, r, cfm.value)) + return SMP_UNSPECIFIED; + + smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, &cfm, sizeof(cfm)); + + return 0; +} + +static uint8_t sc_passkey_round(struct smp_conn *conn, uint8_t smp_op) +{ + uint8_t cfm[16], r; + + /* Ignore the PDU if we've already done 20 rounds (0 - 19) */ + if (conn->passkey_round >= 20) + return 0; + + switch (smp_op) { + case BT_L2CAP_SMP_PAIRING_RANDOM: + r = ((conn->passkey_notify >> conn->passkey_round) & 0x01); + r |= 0x80; + + if (!bt_crypto_f4(conn->smp->crypto, conn->remote_pk, + conn->local_pk, conn->rrnd, r, cfm)) + return SMP_UNSPECIFIED; + + if (memcmp(conn->pcnf, cfm, 16)) + return SMP_CONFIRM_FAILED; + + conn->passkey_round++; + + if (conn->passkey_round == 20) { + /* Generate MacKey and LTK */ + sc_mackey_and_ltk(conn); + } + + /* The round is only complete when the initiator + * receives pairing random. + */ + if (!conn->out) { + smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, + conn->prnd, sizeof(conn->prnd)); + return 0; + } + + /* Start the next round */ + if (conn->passkey_round != 20) + return sc_passkey_round(conn, 0); + + /* Passkey rounds are complete - start DHKey Check */ + sc_dhkey_check(conn); + + break; + + case BT_L2CAP_SMP_PAIRING_CONFIRM: + if (conn->out) { + smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, + conn->prnd, sizeof(conn->prnd)); + return 0; + } + + return sc_passkey_send_confirm(conn); + + case BT_L2CAP_SMP_PUBLIC_KEY: + default: + /* Initiating device starts the round */ + if (!conn->out) + return 0; + + return sc_passkey_send_confirm(conn); + } + + return 0; +} + +static bool verify_random(struct smp_conn *conn, const uint8_t rnd[16]) +{ + uint8_t confirm[16]; + + if (!bt_crypto_c1(conn->smp->crypto, conn->tk, conn->rrnd, conn->prsp, + conn->preq, conn->ia_type, conn->ia, + conn->ra_type, conn->ra, confirm)) + return false; + + if (memcmp(conn->pcnf, confirm, sizeof(conn->pcnf)) != 0) { + printf("Confirmation values don't match\n"); + return false; + } + + if (conn->out) { + bt_crypto_s1(conn->smp->crypto, conn->tk, conn->rrnd, + conn->prnd, conn->ltk); + bthost_le_start_encrypt(conn->smp->bthost, conn->handle, + conn->ltk); + } else { + bt_crypto_s1(conn->smp->crypto, conn->tk, conn->prnd, + conn->rrnd, conn->ltk); + } + + return true; +} + +static void distribute_keys(struct smp_conn *conn) +{ + uint8_t buf[16]; + + if (conn->local_key_dist & DIST_ENC_KEY) { + memset(buf, 0, sizeof(buf)); + smp_send(conn, BT_L2CAP_SMP_ENCRYPT_INFO, buf, sizeof(buf)); + smp_send(conn, BT_L2CAP_SMP_MASTER_IDENT, buf, 10); + } + + if (conn->local_key_dist & DIST_ID_KEY) { + memset(buf, 0, sizeof(buf)); + smp_send(conn, BT_L2CAP_SMP_IDENT_INFO, buf, sizeof(buf)); + + memset(buf, 0, sizeof(buf)); + + if (conn->out) { + buf[0] = conn->ia_type; + memcpy(&buf[1], conn->ia, 6); + } else { + buf[0] = conn->ra_type; + memcpy(&buf[1], conn->ra, 6); + } + + smp_send(conn, BT_L2CAP_SMP_IDENT_ADDR_INFO, buf, 7); + } + + if (conn->local_key_dist & DIST_SIGN) { + memset(buf, 0, sizeof(buf)); + smp_send(conn, BT_L2CAP_SMP_SIGNING_INFO, buf, sizeof(buf)); + } +} + +static void pairing_req(struct smp_conn *conn, const void *data, uint16_t len) +{ + struct bthost *bthost = conn->smp->bthost; + struct bt_l2cap_smp_pairing_response rsp; + + memcpy(conn->preq, data, sizeof(conn->preq)); + + if (conn->addr_type == BDADDR_BREDR) { + rsp.io_capa = 0x00; + rsp.oob_data = 0x00; + rsp.auth_req = 0x00; + } else { + rsp.io_capa = bthost_get_io_capability(bthost); + rsp.oob_data = 0x00; + rsp.auth_req = bthost_get_auth_req(bthost); + } + + rsp.max_key_size = 0x10; + rsp.init_key_dist = conn->preq[5] & key_dist(bthost); + rsp.resp_key_dist = conn->preq[6] & key_dist(bthost); + + conn->prsp[0] = BT_L2CAP_SMP_PAIRING_RESPONSE; + memcpy(&conn->prsp[1], &rsp, sizeof(rsp)); + + conn->local_key_dist = rsp.resp_key_dist; + conn->remote_key_dist = rsp.init_key_dist; + + if (((conn->prsp[3] & 0x08) && (conn->preq[3] & 0x08)) || + conn->addr_type == BDADDR_BREDR) { + conn->sc = true; + conn->local_key_dist &= ~SC_NO_DIST; + conn->remote_key_dist &= ~SC_NO_DIST; + } + + smp_send(conn, BT_L2CAP_SMP_PAIRING_RESPONSE, &rsp, sizeof(rsp)); + + if (conn->addr_type == BDADDR_BREDR) + distribute_keys(conn); +} + +static void pairing_rsp(struct smp_conn *conn, const void *data, uint16_t len) +{ + struct smp *smp = conn->smp; + uint8_t cfm[16]; + + memcpy(conn->prsp, data, sizeof(conn->prsp)); + + conn->local_key_dist = conn->prsp[5]; + conn->remote_key_dist = conn->prsp[6]; + + if (conn->addr_type == BDADDR_BREDR) { + conn->local_key_dist &= ~SC_NO_DIST; + conn->remote_key_dist &= ~SC_NO_DIST; + distribute_keys(conn); + return; + } + + if (((conn->prsp[3] & 0x08) && (conn->preq[3] & 0x08)) || + conn->addr_type == BDADDR_BREDR) { + conn->sc = true; + conn->local_key_dist &= ~SC_NO_DIST; + conn->remote_key_dist &= ~SC_NO_DIST; + if (conn->addr_type == BDADDR_BREDR) + distribute_keys(conn); + else + send_public_key(conn); + return; + } + + bt_crypto_c1(smp->crypto, conn->tk, conn->prnd, conn->prsp, + conn->preq, conn->ia_type, conn->ia, + conn->ra_type, conn->ra, cfm); + + smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, cfm, sizeof(cfm)); +} +static void sc_check_confirm(struct smp_conn *conn) +{ + if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY) { + sc_passkey_round(conn, BT_L2CAP_SMP_PAIRING_CONFIRM); + return; + } + + if (conn->out) + smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd, + sizeof(conn->prnd)); +} + +static void pairing_cfm(struct smp_conn *conn, const void *data, uint16_t len) +{ + uint8_t rsp[16]; + + memcpy(conn->pcnf, data + 1, 16); + + if (conn->sc) { + sc_check_confirm(conn); + return; + } + + if (conn->out) { + memset(rsp, 0, sizeof(rsp)); + smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, rsp, sizeof(rsp)); + } else { + bt_crypto_c1(conn->smp->crypto, conn->tk, conn->prnd, + conn->prsp, conn->preq, conn->ia_type, + conn->ia, conn->ra_type, conn->ra, rsp); + smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, rsp, sizeof(rsp)); + } +} + +static uint8_t sc_random(struct smp_conn *conn) +{ + /* Passkey entry has special treatment */ + if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY) + return sc_passkey_round(conn, BT_L2CAP_SMP_PAIRING_RANDOM); + + if (conn->out) { + uint8_t cfm[16]; + + bt_crypto_f4(conn->smp->crypto, conn->remote_pk, + conn->local_pk, conn->rrnd, 0, cfm); + + if (memcmp(conn->pcnf, cfm, 16)) + return 0x04; /* Confirm Value Failed */ + } else { + smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, conn->prnd, 16); + } + + sc_mackey_and_ltk(conn); + + if (conn->out) + sc_dhkey_check(conn); + + return 0; +} + +static void pairing_rnd(struct smp_conn *conn, const void *data, uint16_t len) +{ + uint8_t rsp[16]; + + memcpy(conn->rrnd, data + 1, 16); + + if (conn->sc) { + uint8_t reason = sc_random(conn); + if (reason) + smp_send(conn, BT_L2CAP_SMP_PAIRING_FAILED, &reason, + sizeof(reason)); + return; + } + + if (!verify_random(conn, data + 1)) + return; + + if (conn->out) + return; + + memset(rsp, 0, sizeof(rsp)); + smp_send(conn, BT_L2CAP_SMP_PAIRING_RANDOM, rsp, sizeof(rsp)); +} + +static void encrypt_info(struct smp_conn *conn, const void *data, uint16_t len) +{ +} + +static void master_ident(struct smp_conn *conn, const void *data, uint16_t len) +{ + conn->remote_key_dist &= ~DIST_ENC_KEY; + + if (conn->out && !conn->remote_key_dist) + distribute_keys(conn); +} + +static void ident_addr_info(struct smp_conn *conn, const void *data, + uint16_t len) +{ +} + +static void ident_info(struct smp_conn *conn, const void *data, uint16_t len) +{ + conn->remote_key_dist &= ~DIST_ID_KEY; + + if (conn->out && !conn->remote_key_dist) + distribute_keys(conn); +} + +static void signing_info(struct smp_conn *conn, const void *data, uint16_t len) +{ + conn->remote_key_dist &= ~DIST_SIGN; + + if (conn->out && !conn->remote_key_dist) + distribute_keys(conn); +} + +static void public_key(struct smp_conn *conn, const void *data, uint16_t len) +{ + struct smp *smp = conn->smp; + uint8_t buf[16]; + + memcpy(conn->remote_pk, data + 1, 64); + + if (!conn->out) { + if (!send_public_key(conn)) + return; + } + + if (!ecdh_shared_secret(conn->remote_pk, conn->local_sk, conn->dhkey)) + return; + + conn->method = sc_select_method(conn); + + if (conn->method == DSP_PASSKEY || conn->method == REQ_PASSKEY) { + sc_passkey_round(conn, BT_L2CAP_SMP_PUBLIC_KEY); + return; + } + + if (conn->out) + return; + + if (!bt_crypto_f4(smp->crypto, conn->local_pk, conn->remote_pk, + conn->prnd, 0, buf)) + return; + + smp_send(conn, BT_L2CAP_SMP_PAIRING_CONFIRM, buf, sizeof(buf)); +} + +static void dhkey_check(struct smp_conn *conn, const void *data, uint16_t len) +{ + const struct bt_l2cap_smp_dhkey_check *cmd = data + 1; + uint8_t a[7], b[7], *local_addr, *remote_addr; + uint8_t io_cap[3], r[16], e[16]; + + memcpy(a, &conn->ia, 6); + memcpy(b, &conn->ra, 6); + a[6] = conn->ia_type; + b[6] = conn->ra_type; + + if (conn->out) { + local_addr = a; + remote_addr = b; + memcpy(io_cap, &conn->prsp[1], 3); + } else { + local_addr = b; + remote_addr = a; + memcpy(io_cap, &conn->preq[1], 3); + } + + memset(r, 0, sizeof(r)); + + if (conn->method == REQ_PASSKEY || conn->method == DSP_PASSKEY) + put_le32(conn->passkey_notify, r); + + if (!bt_crypto_f6(conn->smp->crypto, conn->mackey, conn->rrnd, + conn->prnd, r, io_cap, remote_addr, local_addr, e)) + return; + + if (memcmp(cmd->e, e, 16)) { + uint8_t reason = 0x0b; /* DHKey Check Failed */ + smp_send(conn, BT_L2CAP_SMP_PAIRING_FAILED, &reason, + sizeof(reason)); + } + + if (conn->out) + bthost_le_start_encrypt(conn->smp->bthost, conn->handle, + conn->ltk); + else + sc_dhkey_check(conn); +} + +void smp_pair(void *conn_data, uint8_t io_cap, uint8_t auth_req) +{ + struct smp_conn *conn = conn_data; + struct bt_l2cap_smp_pairing_request req; + + req.io_capa = io_cap; + req.oob_data = 0x00; + req.auth_req = auth_req; + req.max_key_size = 0x10; + req.init_key_dist = key_dist(conn->smp->bthost); + req.resp_key_dist = key_dist(conn->smp->bthost); + + conn->preq[0] = BT_L2CAP_SMP_PAIRING_REQUEST; + memcpy(&conn->preq[1], &req, sizeof(req)); + + smp_send(conn, BT_L2CAP_SMP_PAIRING_REQUEST, &req, sizeof(req)); +} + +void smp_data(void *conn_data, const void *data, uint16_t len) +{ + struct smp_conn *conn = conn_data; + uint8_t opcode; + + if (len < 1) { + printf("Received too small SMP PDU\n"); + return; + } + + if (conn->addr_type == BDADDR_BREDR) { + printf("Received BR/EDR SMP data on LE link\n"); + return; + } + + opcode = *((const uint8_t *) data); + + switch (opcode) { + case BT_L2CAP_SMP_PAIRING_REQUEST: + pairing_req(conn, data, len); + break; + case BT_L2CAP_SMP_PAIRING_RESPONSE: + pairing_rsp(conn, data, len); + break; + case BT_L2CAP_SMP_PAIRING_CONFIRM: + pairing_cfm(conn, data, len); + break; + case BT_L2CAP_SMP_PAIRING_RANDOM: + pairing_rnd(conn, data, len); + break; + case BT_L2CAP_SMP_ENCRYPT_INFO: + encrypt_info(conn, data, len); + break; + case BT_L2CAP_SMP_MASTER_IDENT: + master_ident(conn, data, len); + break; + case BT_L2CAP_SMP_IDENT_ADDR_INFO: + ident_addr_info(conn, data, len); + break; + case BT_L2CAP_SMP_IDENT_INFO: + ident_info(conn, data, len); + break; + case BT_L2CAP_SMP_SIGNING_INFO: + signing_info(conn, data, len); + break; + case BT_L2CAP_SMP_PUBLIC_KEY: + public_key(conn, data, len); + break; + case BT_L2CAP_SMP_DHKEY_CHECK: + dhkey_check(conn, data, len); + break; + default: + break; + } +} + +void smp_bredr_data(void *conn_data, const void *data, uint16_t len) +{ + struct smp_conn *conn = conn_data; + uint8_t opcode; + + if (len < 1) { + printf("Received too small SMP PDU\n"); + return; + } + + if (conn->addr_type != BDADDR_BREDR) { + printf("Received LE SMP data on BR/EDR link\n"); + return; + } + + opcode = *((const uint8_t *) data); + + switch (opcode) { + case BT_L2CAP_SMP_PAIRING_REQUEST: + pairing_req(conn, data, len); + break; + case BT_L2CAP_SMP_PAIRING_RESPONSE: + pairing_rsp(conn, data, len); + break; + default: + break; + } +} + +int smp_get_ltk(void *smp_data, uint64_t rand, uint16_t ediv, uint8_t *ltk) +{ + struct smp_conn *conn = smp_data; + static const uint8_t no_ltk[16] = { 0 }; + + if (!memcmp(conn->ltk, no_ltk, 16)) + return -ENOENT; + + memcpy(ltk, conn->ltk, 16); + + return 0; +} + +static void smp_conn_bredr(struct smp_conn *conn, uint8_t encrypt) +{ + struct smp *smp = conn->smp; + struct bt_l2cap_smp_pairing_request req; + uint64_t fixed_chan; + + if (encrypt != 0x02) + return; + + conn->sc = true; + + if (!conn->out) + return; + + fixed_chan = bthost_conn_get_fixed_chan(smp->bthost, conn->handle); + if (!(fixed_chan & L2CAP_FC_SMP_BREDR)) + return; + + memset(&req, 0, sizeof(req)); + req.max_key_size = 0x10; + req.init_key_dist = key_dist(smp->bthost); + req.resp_key_dist = key_dist(smp->bthost); + + smp_send(conn, BT_L2CAP_SMP_PAIRING_REQUEST, &req, sizeof(req)); +} + +void smp_conn_encrypted(void *conn_data, uint8_t encrypt) +{ + struct smp_conn *conn = conn_data; + + if (!encrypt) + return; + + if (conn->addr_type == BDADDR_BREDR) { + smp_conn_bredr(conn, encrypt); + return; + } + + if (conn->out && conn->remote_key_dist) + return; + + distribute_keys(conn); +} + +void *smp_conn_add(void *smp_data, uint16_t handle, const uint8_t *ia, + const uint8_t *ra, uint8_t addr_type, bool conn_init) +{ + struct smp *smp = smp_data; + struct smp_conn *conn; + + conn = malloc(sizeof(struct smp_conn)); + if (!conn) + return NULL; + + memset(conn, 0, sizeof(*conn)); + + conn->smp = smp; + conn->handle = handle; + conn->addr_type = addr_type; + conn->out = conn_init; + + conn->ia_type = LE_PUBLIC_ADDRESS; + conn->ra_type = LE_PUBLIC_ADDRESS; + memcpy(conn->ia, ia, 6); + memcpy(conn->ra, ra, 6); + + return conn; +} + +void smp_conn_del(void *conn_data) +{ + struct smp_conn *conn = conn_data; + + free(conn); +} + +void *smp_start(struct bthost *bthost) +{ + struct smp *smp; + + smp = malloc(sizeof(struct smp)); + if (!smp) + return NULL; + + memset(smp, 0, sizeof(*smp)); + + smp->crypto = bt_crypto_new(); + if (!smp->crypto) { + free(smp); + return NULL; + } + + smp->bthost = bthost; + + return smp; +} + +void smp_stop(void *smp_data) +{ + struct smp *smp = smp_data; + + bt_crypto_unref(smp->crypto); + + free(smp); +} diff --git a/emulator/vhci.c b/emulator/vhci.c new file mode 100644 index 0000000..8dec20a --- /dev/null +++ b/emulator/vhci.c @@ -0,0 +1,172 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/mainloop.h" +#include "monitor/bt.h" +#include "btdev.h" +#include "vhci.h" + +#define uninitialized_var(x) x = x + +struct vhci { + enum vhci_type type; + int fd; + struct btdev *btdev; +}; + +static void vhci_destroy(void *user_data) +{ + struct vhci *vhci = user_data; + + btdev_destroy(vhci->btdev); + + close(vhci->fd); + + free(vhci); +} + +static void vhci_write_callback(const struct iovec *iov, int iovlen, + void *user_data) +{ + struct vhci *vhci = user_data; + ssize_t written; + + written = writev(vhci->fd, iov, iovlen); + if (written < 0) + return; +} + +static void vhci_read_callback(int fd, uint32_t events, void *user_data) +{ + struct vhci *vhci = user_data; + unsigned char buf[4096]; + ssize_t len; + + if (events & (EPOLLERR | EPOLLHUP)) + return; + + len = read(vhci->fd, buf, sizeof(buf)); + if (len < 1) + return; + + switch (buf[0]) { + case BT_H4_CMD_PKT: + case BT_H4_ACL_PKT: + case BT_H4_SCO_PKT: + btdev_receive_h4(vhci->btdev, buf, len); + break; + } +} + +struct vhci *vhci_open(enum vhci_type type) +{ + struct vhci *vhci; + enum btdev_type uninitialized_var(btdev_type); + unsigned char uninitialized_var(ctrl_type); + unsigned char setup_cmd[2]; + static uint8_t id = 0x23; + + switch (type) { + case VHCI_TYPE_BREDRLE: + btdev_type = BTDEV_TYPE_BREDRLE; + ctrl_type = HCI_PRIMARY; + break; + case VHCI_TYPE_BREDR: + btdev_type = BTDEV_TYPE_BREDR; + ctrl_type = HCI_PRIMARY; + break; + case VHCI_TYPE_LE: + btdev_type = BTDEV_TYPE_LE; + ctrl_type = HCI_PRIMARY; + break; + case VHCI_TYPE_AMP: + btdev_type = BTDEV_TYPE_AMP; + ctrl_type = HCI_AMP; + break; + } + + vhci = malloc(sizeof(*vhci)); + if (!vhci) + return NULL; + + memset(vhci, 0, sizeof(*vhci)); + vhci->type = type; + + vhci->fd = open("/dev/vhci", O_RDWR | O_NONBLOCK); + if (vhci->fd < 0) { + free(vhci); + return NULL; + } + + setup_cmd[0] = HCI_VENDOR_PKT; + setup_cmd[1] = ctrl_type; + + if (write(vhci->fd, setup_cmd, sizeof(setup_cmd)) < 0) { + close(vhci->fd); + free(vhci); + return NULL; + } + + vhci->btdev = btdev_create(btdev_type, id++); + if (!vhci->btdev) { + close(vhci->fd); + free(vhci); + return NULL; + } + + btdev_set_send_handler(vhci->btdev, vhci_write_callback, vhci); + + if (mainloop_add_fd(vhci->fd, EPOLLIN, vhci_read_callback, + vhci, vhci_destroy) < 0) { + btdev_destroy(vhci->btdev); + close(vhci->fd); + free(vhci); + return NULL; + } + + return vhci; +} + +void vhci_close(struct vhci *vhci) +{ + if (!vhci) + return; + + mainloop_remove_fd(vhci->fd); +} diff --git a/emulator/vhci.h b/emulator/vhci.h new file mode 100644 index 0000000..1ec7191 --- /dev/null +++ b/emulator/vhci.h @@ -0,0 +1,37 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +enum vhci_type { + VHCI_TYPE_BREDRLE, + VHCI_TYPE_BREDR, + VHCI_TYPE_LE, + VHCI_TYPE_AMP, +}; + +struct vhci; + +struct vhci *vhci_open(enum vhci_type type); +void vhci_close(struct vhci *vhci); diff --git a/gdbus/client.c b/gdbus/client.c new file mode 100644 index 0000000..86e1c76 --- /dev/null +++ b/gdbus/client.c @@ -0,0 +1,1544 @@ +/* + * + * D-Bus helper library + * + * Copyright (C) 2004-2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "gdbus.h" + +#define METHOD_CALL_TIMEOUT (300 * 1000) + +#ifndef DBUS_INTERFACE_OBJECT_MANAGER +#define DBUS_INTERFACE_OBJECT_MANAGER DBUS_INTERFACE_DBUS ".ObjectManager" +#endif + +struct GDBusClient { + int ref_count; + DBusConnection *dbus_conn; + char *service_name; + char *base_path; + char *root_path; + guint watch; + guint added_watch; + guint removed_watch; + GPtrArray *match_rules; + DBusPendingCall *pending_call; + DBusPendingCall *get_objects_call; + GDBusWatchFunction connect_func; + void *connect_data; + GDBusWatchFunction disconn_func; + gboolean connected; + void *disconn_data; + GDBusMessageFunction signal_func; + void *signal_data; + GDBusProxyFunction proxy_added; + GDBusProxyFunction proxy_removed; + GDBusClientFunction ready; + void *ready_data; + GDBusPropertyFunction property_changed; + void *user_data; + GList *proxy_list; +}; + +struct GDBusProxy { + int ref_count; + GDBusClient *client; + char *obj_path; + char *interface; + GHashTable *prop_list; + guint watch; + GDBusPropertyFunction prop_func; + void *prop_data; + GDBusProxyFunction removed_func; + void *removed_data; + DBusPendingCall *get_all_call; + gboolean pending; +}; + +struct prop_entry { + char *name; + int type; + DBusMessage *msg; +}; + +static void modify_match_reply(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, reply) == TRUE) + dbus_error_free(&error); + + dbus_message_unref(reply); +} + +static gboolean modify_match(DBusConnection *conn, const char *member, + const char *rule) +{ + DBusMessage *msg; + DBusPendingCall *call; + + msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, member); + if (msg == NULL) + return FALSE; + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &rule, + DBUS_TYPE_INVALID); + + if (g_dbus_send_message_with_reply(conn, msg, &call, -1) == FALSE) { + dbus_message_unref(msg); + return FALSE; + } + + dbus_pending_call_set_notify(call, modify_match_reply, NULL, NULL); + dbus_pending_call_unref(call); + + dbus_message_unref(msg); + + return TRUE; +} + +static void append_variant(DBusMessageIter *iter, int type, const void *val) +{ + DBusMessageIter value; + char sig[2] = { type, '\0' }; + + dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value); + + dbus_message_iter_append_basic(&value, type, val); + + dbus_message_iter_close_container(iter, &value); +} + +static void append_array_variant(DBusMessageIter *iter, int type, void *val, + int n_elements) +{ + DBusMessageIter variant, array; + char type_sig[2] = { type, '\0' }; + char array_sig[3] = { DBUS_TYPE_ARRAY, type, '\0' }; + + dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, + array_sig, &variant); + + dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, + type_sig, &array); + + if (dbus_type_is_fixed(type) == TRUE) { + dbus_message_iter_append_fixed_array(&array, type, val, + n_elements); + } else if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { + const char ***str_array = val; + int i; + + for (i = 0; i < n_elements; i++) + dbus_message_iter_append_basic(&array, type, + &((*str_array)[i])); + } + + dbus_message_iter_close_container(&variant, &array); + + dbus_message_iter_close_container(iter, &variant); +} + +static void dict_append_basic(DBusMessageIter *dict, int key_type, + const void *key, int type, void *val) +{ + DBusMessageIter entry; + + if (type == DBUS_TYPE_STRING) { + const char *str = *((const char **) val); + if (str == NULL) + return; + } + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + + dbus_message_iter_append_basic(&entry, key_type, key); + + append_variant(&entry, type, val); + + dbus_message_iter_close_container(dict, &entry); +} + +void g_dbus_dict_append_entry(DBusMessageIter *dict, + const char *key, int type, void *val) +{ + dict_append_basic(dict, DBUS_TYPE_STRING, &key, type, val); +} + +void g_dbus_dict_append_basic_array(DBusMessageIter *dict, int key_type, + const void *key, int type, void *val, + int n_elements) +{ + DBusMessageIter entry; + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + + dbus_message_iter_append_basic(&entry, key_type, key); + + append_array_variant(&entry, type, val, n_elements); + + dbus_message_iter_close_container(dict, &entry); +} + +void g_dbus_dict_append_array(DBusMessageIter *dict, + const char *key, int type, void *val, + int n_elements) +{ + g_dbus_dict_append_basic_array(dict, DBUS_TYPE_STRING, &key, type, val, + n_elements); +} + +static void iter_append_iter(DBusMessageIter *base, DBusMessageIter *iter) +{ + int type; + + type = dbus_message_iter_get_arg_type(iter); + + if (dbus_type_is_basic(type)) { + const void *value; + + dbus_message_iter_get_basic(iter, &value); + dbus_message_iter_append_basic(base, type, &value); + } else if (dbus_type_is_container(type)) { + DBusMessageIter iter_sub, base_sub; + char *sig; + + dbus_message_iter_recurse(iter, &iter_sub); + + switch (type) { + case DBUS_TYPE_ARRAY: + case DBUS_TYPE_VARIANT: + sig = dbus_message_iter_get_signature(&iter_sub); + break; + default: + sig = NULL; + break; + } + + dbus_message_iter_open_container(base, type, sig, &base_sub); + + if (sig != NULL) + dbus_free(sig); + + while (dbus_message_iter_get_arg_type(&iter_sub) != + DBUS_TYPE_INVALID) { + iter_append_iter(&base_sub, &iter_sub); + dbus_message_iter_next(&iter_sub); + } + + dbus_message_iter_close_container(base, &base_sub); + } +} + +static void prop_entry_update(struct prop_entry *prop, DBusMessageIter *iter) +{ + DBusMessage *msg; + DBusMessageIter base; + + msg = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN); + if (msg == NULL) + return; + + dbus_message_iter_init_append(msg, &base); + iter_append_iter(&base, iter); + + if (prop->msg != NULL) + dbus_message_unref(prop->msg); + + prop->msg = dbus_message_copy(msg); + dbus_message_unref(msg); +} + +static struct prop_entry *prop_entry_new(const char *name, + DBusMessageIter *iter) +{ + struct prop_entry *prop; + + prop = g_try_new0(struct prop_entry, 1); + if (prop == NULL) + return NULL; + + prop->name = g_strdup(name); + prop->type = dbus_message_iter_get_arg_type(iter); + + prop_entry_update(prop, iter); + + return prop; +} + +static void prop_entry_free(gpointer data) +{ + struct prop_entry *prop = data; + + if (prop->msg != NULL) + dbus_message_unref(prop->msg); + + g_free(prop->name); + + g_free(prop); +} + +static void add_property(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, gboolean send_changed) +{ + GDBusClient *client = proxy->client; + DBusMessageIter value; + struct prop_entry *prop; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) + return; + + dbus_message_iter_recurse(iter, &value); + + prop = g_hash_table_lookup(proxy->prop_list, name); + if (prop != NULL) { + prop_entry_update(prop, &value); + goto done; + } + + prop = prop_entry_new(name, &value); + if (prop == NULL) + return; + + g_hash_table_replace(proxy->prop_list, prop->name, prop); + +done: + if (proxy->prop_func) + proxy->prop_func(proxy, name, &value, proxy->prop_data); + + if (client == NULL || send_changed == FALSE) + return; + + if (client->property_changed) + client->property_changed(proxy, name, &value, + client->user_data); +} + +static void update_properties(GDBusProxy *proxy, DBusMessageIter *iter, + gboolean send_changed) +{ + DBusMessageIter dict; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return; + + dbus_message_iter_recurse(iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry; + const char *name; + + dbus_message_iter_recurse(&dict, &entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + break; + + dbus_message_iter_get_basic(&entry, &name); + dbus_message_iter_next(&entry); + + add_property(proxy, name, &entry, send_changed); + + dbus_message_iter_next(&dict); + } +} + +static void proxy_added(GDBusClient *client, GDBusProxy *proxy) +{ + if (!proxy->pending) + return; + + if (client->proxy_added) + client->proxy_added(proxy, client->user_data); + + proxy->pending = FALSE; +} + +static void get_all_properties_reply(DBusPendingCall *call, void *user_data) +{ + GDBusProxy *proxy = user_data; + GDBusClient *client = proxy->client; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusMessageIter iter; + DBusError error; + + g_dbus_client_ref(client); + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, reply) == TRUE) { + dbus_error_free(&error); + goto done; + } + + dbus_message_iter_init(reply, &iter); + + update_properties(proxy, &iter, FALSE); + +done: + proxy_added(client, proxy); + + dbus_message_unref(reply); + + dbus_pending_call_unref(proxy->get_all_call); + proxy->get_all_call = NULL; + + g_dbus_client_unref(client); +} + +static void get_all_properties(GDBusProxy *proxy) +{ + GDBusClient *client = proxy->client; + const char *service_name = client->service_name; + DBusMessage *msg; + + if (proxy->get_all_call) + return; + + msg = dbus_message_new_method_call(service_name, proxy->obj_path, + DBUS_INTERFACE_PROPERTIES, "GetAll"); + if (msg == NULL) + return; + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &proxy->interface, + DBUS_TYPE_INVALID); + + if (g_dbus_send_message_with_reply(client->dbus_conn, msg, + &proxy->get_all_call, -1) == FALSE) { + dbus_message_unref(msg); + return; + } + + dbus_pending_call_set_notify(proxy->get_all_call, + get_all_properties_reply, proxy, NULL); + + dbus_message_unref(msg); +} + +GDBusProxy *g_dbus_proxy_lookup(GList *list, int *index, const char *path, + const char *interface) +{ + GList *l; + + if (!interface) + return NULL; + + for (l = g_list_nth(list, index ? *index : 0); l; l = g_list_next(l)) { + GDBusProxy *proxy = l->data; + const char *proxy_iface = g_dbus_proxy_get_interface(proxy); + const char *proxy_path = g_dbus_proxy_get_path(proxy); + + if (index) + (*index)++; + + if (g_str_equal(proxy_iface, interface) == TRUE && + g_str_equal(proxy_path, path) == TRUE) + return proxy; + } + + return NULL; +} + +char *g_dbus_proxy_path_lookup(GList *list, int *index, const char *path) +{ + int len = strlen(path); + GList *l; + + for (l = g_list_nth(list, index ? *index : 0); l; l = g_list_next(l)) { + GDBusProxy *proxy = l->data; + const char *proxy_path = g_dbus_proxy_get_path(proxy); + + if (index) + (*index)++; + + if (!strncasecmp(proxy_path, path, len)) + return strdup(proxy_path); + } + + return NULL; +} + +static gboolean properties_changed(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + GDBusProxy *proxy = user_data; + GDBusClient *client = proxy->client; + DBusMessageIter iter, entry; + const char *interface; + + if (dbus_message_iter_init(msg, &iter) == FALSE) + return TRUE; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return TRUE; + + dbus_message_iter_get_basic(&iter, &interface); + dbus_message_iter_next(&iter); + + update_properties(proxy, &iter, TRUE); + + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + return TRUE; + + dbus_message_iter_recurse(&iter, &entry); + + while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) { + const char *name; + + dbus_message_iter_get_basic(&entry, &name); + + g_hash_table_remove(proxy->prop_list, name); + + if (proxy->prop_func) + proxy->prop_func(proxy, name, NULL, proxy->prop_data); + + if (client->property_changed) + client->property_changed(proxy, name, NULL, + client->user_data); + + dbus_message_iter_next(&entry); + } + + return TRUE; +} + +static GDBusProxy *proxy_new(GDBusClient *client, const char *path, + const char *interface) +{ + GDBusProxy *proxy; + + proxy = g_try_new0(GDBusProxy, 1); + if (proxy == NULL) + return NULL; + + proxy->client = client; + proxy->obj_path = g_strdup(path); + proxy->interface = g_strdup(interface); + + proxy->prop_list = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, prop_entry_free); + proxy->watch = g_dbus_add_properties_watch(client->dbus_conn, + client->service_name, + proxy->obj_path, + proxy->interface, + properties_changed, + proxy, NULL); + proxy->pending = TRUE; + + client->proxy_list = g_list_append(client->proxy_list, proxy); + + return g_dbus_proxy_ref(proxy); +} + +static void proxy_free(gpointer data) +{ + GDBusProxy *proxy = data; + + if (proxy->client) { + GDBusClient *client = proxy->client; + + if (proxy->get_all_call != NULL) { + dbus_pending_call_cancel(proxy->get_all_call); + dbus_pending_call_unref(proxy->get_all_call); + proxy->get_all_call = NULL; + } + + if (client->proxy_removed) + client->proxy_removed(proxy, client->user_data); + + g_dbus_remove_watch(client->dbus_conn, proxy->watch); + + g_hash_table_remove_all(proxy->prop_list); + + proxy->client = NULL; + } + + if (proxy->removed_func) + proxy->removed_func(proxy, proxy->removed_data); + + g_dbus_proxy_unref(proxy); +} + +static void proxy_remove(GDBusClient *client, const char *path, + const char *interface) +{ + GList *list; + + for (list = g_list_first(client->proxy_list); list; + list = g_list_next(list)) { + GDBusProxy *proxy = list->data; + + if (g_str_equal(proxy->interface, interface) == TRUE && + g_str_equal(proxy->obj_path, path) == TRUE) { + client->proxy_list = + g_list_delete_link(client->proxy_list, list); + proxy_free(proxy); + break; + } + } +} + +static void start_service(GDBusProxy *proxy) +{ + GDBusClient *client = proxy->client; + const char *service_name = client->service_name; + dbus_uint32_t flags = 0; + DBusMessage *msg; + + msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "StartServiceByName"); + if (msg == NULL) + return; + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &service_name, + DBUS_TYPE_UINT32, &flags, + DBUS_TYPE_INVALID); + + g_dbus_send_message(client->dbus_conn, msg); + return; +} + +GDBusProxy *g_dbus_proxy_new(GDBusClient *client, const char *path, + const char *interface) +{ + GDBusProxy *proxy; + + if (client == NULL) + return NULL; + + proxy = g_dbus_proxy_lookup(client->proxy_list, NULL, + path, interface); + if (proxy) + return g_dbus_proxy_ref(proxy); + + proxy = proxy_new(client, path, interface); + if (proxy == NULL) + return NULL; + + if (!client->connected) { + /* Force service to start */ + start_service(proxy); + return g_dbus_proxy_ref(proxy); + } + + if (!client->get_objects_call) + get_all_properties(proxy); + + return g_dbus_proxy_ref(proxy); +} + +GDBusProxy *g_dbus_proxy_ref(GDBusProxy *proxy) +{ + if (proxy == NULL) + return NULL; + + __sync_fetch_and_add(&proxy->ref_count, 1); + + return proxy; +} + +void g_dbus_proxy_unref(GDBusProxy *proxy) +{ + if (proxy == NULL) + return; + + if (__sync_sub_and_fetch(&proxy->ref_count, 1) > 0) + return; + + if (proxy->get_all_call != NULL) { + dbus_pending_call_cancel(proxy->get_all_call); + dbus_pending_call_unref(proxy->get_all_call); + } + + g_hash_table_destroy(proxy->prop_list); + + g_free(proxy->obj_path); + g_free(proxy->interface); + + g_free(proxy); +} + +const char *g_dbus_proxy_get_path(GDBusProxy *proxy) +{ + if (proxy == NULL) + return NULL; + + return proxy->obj_path; +} + +const char *g_dbus_proxy_get_interface(GDBusProxy *proxy) +{ + if (proxy == NULL) + return NULL; + + return proxy->interface; +} + +gboolean g_dbus_proxy_get_property(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter) +{ + struct prop_entry *prop; + + if (proxy == NULL || name == NULL) + return FALSE; + + prop = g_hash_table_lookup(proxy->prop_list, name); + if (prop == NULL) + return FALSE; + + if (prop->msg == NULL) + return FALSE; + + if (dbus_message_iter_init(prop->msg, iter) == FALSE) + return FALSE; + + return TRUE; +} + +struct refresh_property_data { + GDBusProxy *proxy; + char *name; +}; + +static void refresh_property_free(gpointer user_data) +{ + struct refresh_property_data *data = user_data; + + g_free(data->name); + g_free(data); +} + +static void refresh_property_reply(DBusPendingCall *call, void *user_data) +{ + struct refresh_property_data *data = user_data; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, reply) == FALSE) { + DBusMessageIter iter; + + dbus_message_iter_init(reply, &iter); + + add_property(data->proxy, data->name, &iter, TRUE); + } else + dbus_error_free(&error); + + dbus_message_unref(reply); +} + +gboolean g_dbus_proxy_refresh_property(GDBusProxy *proxy, const char *name) +{ + struct refresh_property_data *data; + GDBusClient *client; + DBusMessage *msg; + DBusMessageIter iter; + DBusPendingCall *call; + + if (proxy == NULL || name == NULL) + return FALSE; + + client = proxy->client; + if (client == NULL) + return FALSE; + + data = g_try_new0(struct refresh_property_data, 1); + if (data == NULL) + return FALSE; + + data->proxy = proxy; + data->name = g_strdup(name); + + msg = dbus_message_new_method_call(client->service_name, + proxy->obj_path, DBUS_INTERFACE_PROPERTIES, "Get"); + if (msg == NULL) { + refresh_property_free(data); + return FALSE; + } + + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, + &proxy->interface); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); + + if (g_dbus_send_message_with_reply(client->dbus_conn, msg, + &call, -1) == FALSE) { + dbus_message_unref(msg); + refresh_property_free(data); + return FALSE; + } + + dbus_pending_call_set_notify(call, refresh_property_reply, + data, refresh_property_free); + dbus_pending_call_unref(call); + + dbus_message_unref(msg); + + return TRUE; +} + +struct set_property_data { + GDBusResultFunction function; + void *user_data; + GDBusDestroyFunction destroy; +}; + +static void set_property_reply(DBusPendingCall *call, void *user_data) +{ + struct set_property_data *data = user_data; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError error; + + dbus_error_init(&error); + + dbus_set_error_from_message(&error, reply); + + if (data->function) + data->function(&error, data->user_data); + + if (data->destroy) + data->destroy(data->user_data); + + dbus_error_free(&error); + + dbus_message_unref(reply); +} + +gboolean g_dbus_proxy_set_property_basic(GDBusProxy *proxy, + const char *name, int type, const void *value, + GDBusResultFunction function, void *user_data, + GDBusDestroyFunction destroy) +{ + struct set_property_data *data; + GDBusClient *client; + DBusMessage *msg; + DBusMessageIter iter; + DBusPendingCall *call; + + if (proxy == NULL || name == NULL || value == NULL) + return FALSE; + + if (dbus_type_is_basic(type) == FALSE) + return FALSE; + + client = proxy->client; + if (client == NULL) + return FALSE; + + data = g_try_new0(struct set_property_data, 1); + if (data == NULL) + return FALSE; + + data->function = function; + data->user_data = user_data; + data->destroy = destroy; + + msg = dbus_message_new_method_call(client->service_name, + proxy->obj_path, DBUS_INTERFACE_PROPERTIES, "Set"); + if (msg == NULL) { + g_free(data); + return FALSE; + } + + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, + &proxy->interface); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); + + append_variant(&iter, type, value); + + if (g_dbus_send_message_with_reply(client->dbus_conn, msg, + &call, -1) == FALSE) { + dbus_message_unref(msg); + g_free(data); + return FALSE; + } + + dbus_pending_call_set_notify(call, set_property_reply, data, g_free); + dbus_pending_call_unref(call); + + dbus_message_unref(msg); + + return TRUE; +} + +gboolean g_dbus_proxy_set_property_array(GDBusProxy *proxy, + const char *name, int type, const void *value, + size_t size, GDBusResultFunction function, + void *user_data, GDBusDestroyFunction destroy) +{ + struct set_property_data *data; + GDBusClient *client; + DBusMessage *msg; + DBusMessageIter iter; + DBusPendingCall *call; + + if (!proxy || !name || !value) + return FALSE; + + if (!dbus_type_is_basic(type)) + return FALSE; + + client = proxy->client; + if (!client) + return FALSE; + + data = g_try_new0(struct set_property_data, 1); + if (!data) + return FALSE; + + data->function = function; + data->user_data = user_data; + data->destroy = destroy; + + msg = dbus_message_new_method_call(client->service_name, + proxy->obj_path, + DBUS_INTERFACE_PROPERTIES, + "Set"); + if (!msg) { + g_free(data); + return FALSE; + } + + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, + &proxy->interface); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); + + append_array_variant(&iter, type, &value, size); + + if (g_dbus_send_message_with_reply(client->dbus_conn, msg, + &call, -1) == FALSE) { + dbus_message_unref(msg); + g_free(data); + return FALSE; + } + + dbus_pending_call_set_notify(call, set_property_reply, data, g_free); + dbus_pending_call_unref(call); + + dbus_message_unref(msg); + + return TRUE; +} + +struct method_call_data { + GDBusReturnFunction function; + void *user_data; + GDBusDestroyFunction destroy; +}; + +static void method_call_reply(DBusPendingCall *call, void *user_data) +{ + struct method_call_data *data = user_data; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + + if (data->function) + data->function(reply, data->user_data); + + if (data->destroy) + data->destroy(data->user_data); + + dbus_message_unref(reply); +} + +gboolean g_dbus_proxy_method_call(GDBusProxy *proxy, const char *method, + GDBusSetupFunction setup, + GDBusReturnFunction function, void *user_data, + GDBusDestroyFunction destroy) +{ + struct method_call_data *data; + GDBusClient *client; + DBusMessage *msg; + DBusPendingCall *call; + + if (proxy == NULL || method == NULL) + return FALSE; + + client = proxy->client; + if (client == NULL) + return FALSE; + + msg = dbus_message_new_method_call(client->service_name, + proxy->obj_path, proxy->interface, method); + if (msg == NULL) + return FALSE; + + if (setup) { + DBusMessageIter iter; + + dbus_message_iter_init_append(msg, &iter); + setup(&iter, user_data); + } + + if (!function) + return g_dbus_send_message(client->dbus_conn, msg); + + data = g_try_new0(struct method_call_data, 1); + if (data == NULL) + return FALSE; + + data->function = function; + data->user_data = user_data; + data->destroy = destroy; + + + if (g_dbus_send_message_with_reply(client->dbus_conn, msg, + &call, METHOD_CALL_TIMEOUT) == FALSE) { + dbus_message_unref(msg); + g_free(data); + return FALSE; + } + + dbus_pending_call_set_notify(call, method_call_reply, data, g_free); + dbus_pending_call_unref(call); + + dbus_message_unref(msg); + + return TRUE; +} + +gboolean g_dbus_proxy_set_property_watch(GDBusProxy *proxy, + GDBusPropertyFunction function, void *user_data) +{ + if (proxy == NULL) + return FALSE; + + proxy->prop_func = function; + proxy->prop_data = user_data; + + return TRUE; +} + +gboolean g_dbus_proxy_set_removed_watch(GDBusProxy *proxy, + GDBusProxyFunction function, void *user_data) +{ + if (proxy == NULL) + return FALSE; + + proxy->removed_func = function; + proxy->removed_data = user_data; + + return TRUE; +} + +static void refresh_properties(GList *list) +{ + GList *l; + + for (l = g_list_first(list); l; l = g_list_next(l)) { + GDBusProxy *proxy = list->data; + + if (proxy->pending) + get_all_properties(proxy); + } +} + +static void parse_properties(GDBusClient *client, const char *path, + const char *interface, DBusMessageIter *iter) +{ + GDBusProxy *proxy; + + if (g_str_equal(interface, DBUS_INTERFACE_INTROSPECTABLE) == TRUE) + return; + + if (g_str_equal(interface, DBUS_INTERFACE_PROPERTIES) == TRUE) + return; + + proxy = g_dbus_proxy_lookup(client->proxy_list, NULL, + path, interface); + if (proxy && !proxy->pending) { + update_properties(proxy, iter, FALSE); + return; + } + + if (!proxy) { + proxy = proxy_new(client, path, interface); + if (proxy == NULL) + return; + } + + update_properties(proxy, iter, FALSE); + + proxy_added(client, proxy); +} + +static void parse_interfaces(GDBusClient *client, const char *path, + DBusMessageIter *iter) +{ + DBusMessageIter dict; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return; + + dbus_message_iter_recurse(iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry; + const char *interface; + + dbus_message_iter_recurse(&dict, &entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + break; + + dbus_message_iter_get_basic(&entry, &interface); + dbus_message_iter_next(&entry); + + parse_properties(client, path, interface, &entry); + + dbus_message_iter_next(&dict); + } +} + +static gboolean interfaces_added(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + GDBusClient *client = user_data; + DBusMessageIter iter; + const char *path; + + if (dbus_message_iter_init(msg, &iter) == FALSE) + return TRUE; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) + return TRUE; + + dbus_message_iter_get_basic(&iter, &path); + dbus_message_iter_next(&iter); + + g_dbus_client_ref(client); + + parse_interfaces(client, path, &iter); + + g_dbus_client_unref(client); + + return TRUE; +} + +static gboolean interfaces_removed(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + GDBusClient *client = user_data; + DBusMessageIter iter, entry; + const char *path; + + if (dbus_message_iter_init(msg, &iter) == FALSE) + return TRUE; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) + return TRUE; + + dbus_message_iter_get_basic(&iter, &path); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + return TRUE; + + dbus_message_iter_recurse(&iter, &entry); + + g_dbus_client_ref(client); + + while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) { + const char *interface; + + dbus_message_iter_get_basic(&entry, &interface); + proxy_remove(client, path, interface); + dbus_message_iter_next(&entry); + } + + g_dbus_client_unref(client); + + return TRUE; +} + +static void parse_managed_objects(GDBusClient *client, DBusMessage *msg) +{ + DBusMessageIter iter, dict; + + if (dbus_message_iter_init(msg, &iter) == FALSE) + return; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + return; + + dbus_message_iter_recurse(&iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry; + const char *path; + + dbus_message_iter_recurse(&dict, &entry); + + if (dbus_message_iter_get_arg_type(&entry) != + DBUS_TYPE_OBJECT_PATH) + break; + + dbus_message_iter_get_basic(&entry, &path); + dbus_message_iter_next(&entry); + + parse_interfaces(client, path, &entry); + + dbus_message_iter_next(&dict); + } +} + +static void get_managed_objects_reply(DBusPendingCall *call, void *user_data) +{ + GDBusClient *client = user_data; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError error; + + g_dbus_client_ref(client); + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, reply) == TRUE) { + dbus_error_free(&error); + goto done; + } + + parse_managed_objects(client, reply); + +done: + if (client->ready) + client->ready(client, client->ready_data); + + dbus_message_unref(reply); + + dbus_pending_call_unref(client->get_objects_call); + client->get_objects_call = NULL; + + refresh_properties(client->proxy_list); + + g_dbus_client_unref(client); +} + +static void get_managed_objects(GDBusClient *client) +{ + DBusMessage *msg; + + if (!client->connected) + return; + + if ((!client->proxy_added && !client->proxy_removed) || + !client->root_path) { + refresh_properties(client->proxy_list); + return; + } + + if (client->get_objects_call != NULL) + return; + + msg = dbus_message_new_method_call(client->service_name, + client->root_path, + DBUS_INTERFACE_OBJECT_MANAGER, + "GetManagedObjects"); + if (msg == NULL) + return; + + dbus_message_append_args(msg, DBUS_TYPE_INVALID); + + if (g_dbus_send_message_with_reply(client->dbus_conn, msg, + &client->get_objects_call, -1) == FALSE) { + dbus_message_unref(msg); + return; + } + + dbus_pending_call_set_notify(client->get_objects_call, + get_managed_objects_reply, + client, NULL); + + dbus_message_unref(msg); +} + +static void service_connect(DBusConnection *conn, void *user_data) +{ + GDBusClient *client = user_data; + + g_dbus_client_ref(client); + + client->connected = TRUE; + + get_managed_objects(client); + + if (client->connect_func) + client->connect_func(conn, client->connect_data); + + g_dbus_client_unref(client); +} + +static void service_disconnect(DBusConnection *conn, void *user_data) +{ + GDBusClient *client = user_data; + + client->connected = FALSE; + + g_list_free_full(client->proxy_list, proxy_free); + client->proxy_list = NULL; + + if (client->disconn_func) + client->disconn_func(conn, client->disconn_data); +} + +static DBusHandlerResult message_filter(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + GDBusClient *client = user_data; + const char *sender, *path, *interface; + + if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + sender = dbus_message_get_sender(message); + if (sender == NULL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + path = dbus_message_get_path(message); + interface = dbus_message_get_interface(message); + + if (g_str_has_prefix(path, client->base_path) == FALSE) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (g_str_equal(interface, DBUS_INTERFACE_PROPERTIES) == TRUE) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (client->signal_func) + client->signal_func(connection, message, client->signal_data); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +GDBusClient *g_dbus_client_new(DBusConnection *connection, + const char *service, const char *path) +{ + return g_dbus_client_new_full(connection, service, path, "/"); +} + +GDBusClient *g_dbus_client_new_full(DBusConnection *connection, + const char *service, + const char *path, + const char *root_path) +{ + GDBusClient *client; + unsigned int i; + + if (!connection || !service) + return NULL; + + client = g_try_new0(GDBusClient, 1); + if (client == NULL) + return NULL; + + if (dbus_connection_add_filter(connection, message_filter, + client, NULL) == FALSE) { + g_free(client); + return NULL; + } + + client->dbus_conn = dbus_connection_ref(connection); + client->service_name = g_strdup(service); + client->base_path = g_strdup(path); + client->root_path = g_strdup(root_path); + client->connected = FALSE; + + client->match_rules = g_ptr_array_sized_new(1); + g_ptr_array_set_free_func(client->match_rules, g_free); + + client->watch = g_dbus_add_service_watch(connection, service, + service_connect, + service_disconnect, + client, NULL); + + if (!root_path) + return g_dbus_client_ref(client); + + client->added_watch = g_dbus_add_signal_watch(connection, service, + client->root_path, + DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesAdded", + interfaces_added, + client, NULL); + client->removed_watch = g_dbus_add_signal_watch(connection, service, + client->root_path, + DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesRemoved", + interfaces_removed, + client, NULL); + g_ptr_array_add(client->match_rules, g_strdup_printf("type='signal'," + "sender='%s',path_namespace='%s'", + client->service_name, client->base_path)); + + for (i = 0; i < client->match_rules->len; i++) { + modify_match(client->dbus_conn, "AddMatch", + g_ptr_array_index(client->match_rules, i)); + } + + return g_dbus_client_ref(client); +} + +GDBusClient *g_dbus_client_ref(GDBusClient *client) +{ + if (client == NULL) + return NULL; + + __sync_fetch_and_add(&client->ref_count, 1); + + return client; +} + +void g_dbus_client_unref(GDBusClient *client) +{ + unsigned int i; + + if (client == NULL) + return; + + if (__sync_sub_and_fetch(&client->ref_count, 1) > 0) + return; + + if (client->pending_call != NULL) { + dbus_pending_call_cancel(client->pending_call); + dbus_pending_call_unref(client->pending_call); + } + + if (client->get_objects_call != NULL) { + dbus_pending_call_cancel(client->get_objects_call); + dbus_pending_call_unref(client->get_objects_call); + } + + for (i = 0; i < client->match_rules->len; i++) { + modify_match(client->dbus_conn, "RemoveMatch", + g_ptr_array_index(client->match_rules, i)); + } + + g_ptr_array_free(client->match_rules, TRUE); + + dbus_connection_remove_filter(client->dbus_conn, + message_filter, client); + + g_list_free_full(client->proxy_list, proxy_free); + + /* + * Don't call disconn_func twice if disconnection + * was previously reported. + */ + if (client->disconn_func && client->connected) + client->disconn_func(client->dbus_conn, client->disconn_data); + + g_dbus_remove_watch(client->dbus_conn, client->watch); + g_dbus_remove_watch(client->dbus_conn, client->added_watch); + g_dbus_remove_watch(client->dbus_conn, client->removed_watch); + + dbus_connection_unref(client->dbus_conn); + + g_free(client->service_name); + g_free(client->base_path); + g_free(client->root_path); + + g_free(client); +} + +gboolean g_dbus_client_set_connect_watch(GDBusClient *client, + GDBusWatchFunction function, void *user_data) +{ + if (client == NULL) + return FALSE; + + client->connect_func = function; + client->connect_data = user_data; + + return TRUE; +} + +gboolean g_dbus_client_set_disconnect_watch(GDBusClient *client, + GDBusWatchFunction function, void *user_data) +{ + if (client == NULL) + return FALSE; + + client->disconn_func = function; + client->disconn_data = user_data; + + return TRUE; +} + +gboolean g_dbus_client_set_signal_watch(GDBusClient *client, + GDBusMessageFunction function, void *user_data) +{ + if (client == NULL) + return FALSE; + + client->signal_func = function; + client->signal_data = user_data; + + return TRUE; +} + +gboolean g_dbus_client_set_ready_watch(GDBusClient *client, + GDBusClientFunction ready, void *user_data) +{ + if (client == NULL) + return FALSE; + + client->ready = ready; + client->ready_data = user_data; + + return TRUE; +} + +gboolean g_dbus_client_set_proxy_handlers(GDBusClient *client, + GDBusProxyFunction proxy_added, + GDBusProxyFunction proxy_removed, + GDBusPropertyFunction property_changed, + void *user_data) +{ + if (client == NULL) + return FALSE; + + client->proxy_added = proxy_added; + client->proxy_removed = proxy_removed; + client->property_changed = property_changed; + client->user_data = user_data; + + if (proxy_added || proxy_removed || property_changed) + get_managed_objects(client); + + return TRUE; +} diff --git a/gdbus/gdbus.h b/gdbus/gdbus.h new file mode 100644 index 0000000..1a601c5 --- /dev/null +++ b/gdbus/gdbus.h @@ -0,0 +1,416 @@ +/* + * + * D-Bus helper library + * + * Copyright (C) 2004-2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __GDBUS_H +#define __GDBUS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +typedef struct GDBusArgInfo GDBusArgInfo; +typedef struct GDBusMethodTable GDBusMethodTable; +typedef struct GDBusSignalTable GDBusSignalTable; +typedef struct GDBusPropertyTable GDBusPropertyTable; +typedef struct GDBusSecurityTable GDBusSecurityTable; + +typedef void (* GDBusWatchFunction) (DBusConnection *connection, + void *user_data); + +typedef void (* GDBusMessageFunction) (DBusConnection *connection, + DBusMessage *message, void *user_data); + +typedef gboolean (* GDBusSignalFunction) (DBusConnection *connection, + DBusMessage *message, void *user_data); + +DBusConnection *g_dbus_setup_bus(DBusBusType type, const char *name, + DBusError *error); + +DBusConnection *g_dbus_setup_private(DBusBusType type, const char *name, + DBusError *error); + +gboolean g_dbus_request_name(DBusConnection *connection, const char *name, + DBusError *error); + +gboolean g_dbus_set_disconnect_function(DBusConnection *connection, + GDBusWatchFunction function, + void *user_data, DBusFreeFunction destroy); + +typedef void (* GDBusDestroyFunction) (void *user_data); + +typedef DBusMessage * (* GDBusMethodFunction) (DBusConnection *connection, + DBusMessage *message, void *user_data); + +typedef gboolean (*GDBusPropertyGetter)(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data); + +typedef guint32 GDBusPendingPropertySet; + +typedef void (*GDBusPropertySetter)(const GDBusPropertyTable *property, + DBusMessageIter *value, GDBusPendingPropertySet id, + void *data); + +typedef gboolean (*GDBusPropertyExists)(const GDBusPropertyTable *property, + void *data); + +typedef guint32 GDBusPendingReply; + +typedef void (* GDBusSecurityFunction) (DBusConnection *connection, + const char *action, + gboolean interaction, + GDBusPendingReply pending); + +enum GDBusFlags { + G_DBUS_FLAG_ENABLE_EXPERIMENTAL = (1 << 0), +}; + +enum GDBusMethodFlags { + G_DBUS_METHOD_FLAG_DEPRECATED = (1 << 0), + G_DBUS_METHOD_FLAG_NOREPLY = (1 << 1), + G_DBUS_METHOD_FLAG_ASYNC = (1 << 2), + G_DBUS_METHOD_FLAG_EXPERIMENTAL = (1 << 3), +}; + +enum GDBusSignalFlags { + G_DBUS_SIGNAL_FLAG_DEPRECATED = (1 << 0), + G_DBUS_SIGNAL_FLAG_EXPERIMENTAL = (1 << 1), +}; + +enum GDBusPropertyFlags { + G_DBUS_PROPERTY_FLAG_DEPRECATED = (1 << 0), + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL = (1 << 1), +}; + +enum GDBusSecurityFlags { + G_DBUS_SECURITY_FLAG_DEPRECATED = (1 << 0), + G_DBUS_SECURITY_FLAG_BUILTIN = (1 << 1), + G_DBUS_SECURITY_FLAG_ALLOW_INTERACTION = (1 << 2), +}; + +enum GDbusPropertyChangedFlags { + G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH = (1 << 0), +}; + +typedef enum GDBusMethodFlags GDBusMethodFlags; +typedef enum GDBusSignalFlags GDBusSignalFlags; +typedef enum GDBusPropertyFlags GDBusPropertyFlags; +typedef enum GDBusSecurityFlags GDBusSecurityFlags; +typedef enum GDbusPropertyChangedFlags GDbusPropertyChangedFlags; + +struct GDBusArgInfo { + const char *name; + const char *signature; +}; + +struct GDBusMethodTable { + const char *name; + GDBusMethodFunction function; + GDBusMethodFlags flags; + unsigned int privilege; + const GDBusArgInfo *in_args; + const GDBusArgInfo *out_args; +}; + +struct GDBusSignalTable { + const char *name; + GDBusSignalFlags flags; + const GDBusArgInfo *args; +}; + +struct GDBusPropertyTable { + const char *name; + const char *type; + GDBusPropertyGetter get; + GDBusPropertySetter set; + GDBusPropertyExists exists; + GDBusPropertyFlags flags; +}; + +struct GDBusSecurityTable { + unsigned int privilege; + const char *action; + GDBusSecurityFlags flags; + GDBusSecurityFunction function; +}; + +#define GDBUS_ARGS(args...) (const GDBusArgInfo[]) { args, { } } + +#define GDBUS_METHOD(_name, _in_args, _out_args, _function) \ + .name = _name, \ + .in_args = _in_args, \ + .out_args = _out_args, \ + .function = _function + +#define GDBUS_ASYNC_METHOD(_name, _in_args, _out_args, _function) \ + .name = _name, \ + .in_args = _in_args, \ + .out_args = _out_args, \ + .function = _function, \ + .flags = G_DBUS_METHOD_FLAG_ASYNC + +#define GDBUS_DEPRECATED_METHOD(_name, _in_args, _out_args, _function) \ + .name = _name, \ + .in_args = _in_args, \ + .out_args = _out_args, \ + .function = _function, \ + .flags = G_DBUS_METHOD_FLAG_DEPRECATED + +#define GDBUS_DEPRECATED_ASYNC_METHOD(_name, _in_args, _out_args, _function) \ + .name = _name, \ + .in_args = _in_args, \ + .out_args = _out_args, \ + .function = _function, \ + .flags = G_DBUS_METHOD_FLAG_ASYNC | G_DBUS_METHOD_FLAG_DEPRECATED + +#define GDBUS_EXPERIMENTAL_METHOD(_name, _in_args, _out_args, _function) \ + .name = _name, \ + .in_args = _in_args, \ + .out_args = _out_args, \ + .function = _function, \ + .flags = G_DBUS_METHOD_FLAG_EXPERIMENTAL + +#define GDBUS_EXPERIMENTAL_ASYNC_METHOD(_name, _in_args, _out_args, _function) \ + .name = _name, \ + .in_args = _in_args, \ + .out_args = _out_args, \ + .function = _function, \ + .flags = G_DBUS_METHOD_FLAG_ASYNC | G_DBUS_METHOD_FLAG_EXPERIMENTAL + +#define GDBUS_NOREPLY_METHOD(_name, _in_args, _out_args, _function) \ + .name = _name, \ + .in_args = _in_args, \ + .out_args = _out_args, \ + .function = _function, \ + .flags = G_DBUS_METHOD_FLAG_NOREPLY + +#define GDBUS_SIGNAL(_name, _args) \ + .name = _name, \ + .args = _args + +#define GDBUS_DEPRECATED_SIGNAL(_name, _args) \ + .name = _name, \ + .args = _args, \ + .flags = G_DBUS_SIGNAL_FLAG_DEPRECATED + +#define GDBUS_EXPERIMENTAL_SIGNAL(_name, _args) \ + .name = _name, \ + .args = _args, \ + .flags = G_DBUS_SIGNAL_FLAG_EXPERIMENTAL + +void g_dbus_set_flags(int flags); +int g_dbus_get_flags(void); + +gboolean g_dbus_register_interface(DBusConnection *connection, + const char *path, const char *name, + const GDBusMethodTable *methods, + const GDBusSignalTable *signals, + const GDBusPropertyTable *properties, + void *user_data, + GDBusDestroyFunction destroy); +gboolean g_dbus_unregister_interface(DBusConnection *connection, + const char *path, const char *name); + +gboolean g_dbus_register_security(const GDBusSecurityTable *security); +gboolean g_dbus_unregister_security(const GDBusSecurityTable *security); + +void g_dbus_pending_success(DBusConnection *connection, + GDBusPendingReply pending); +void g_dbus_pending_error(DBusConnection *connection, + GDBusPendingReply pending, + const char *name, const char *format, ...) + __attribute__((format(printf, 4, 5))); +void g_dbus_pending_error_valist(DBusConnection *connection, + GDBusPendingReply pending, const char *name, + const char *format, va_list args); + +DBusMessage *g_dbus_create_error(DBusMessage *message, const char *name, + const char *format, ...) + __attribute__((format(printf, 3, 4))); +DBusMessage *g_dbus_create_error_valist(DBusMessage *message, const char *name, + const char *format, va_list args); +DBusMessage *g_dbus_create_reply(DBusMessage *message, int type, ...); +DBusMessage *g_dbus_create_reply_valist(DBusMessage *message, + int type, va_list args); + +gboolean g_dbus_send_message(DBusConnection *connection, DBusMessage *message); +gboolean g_dbus_send_message_with_reply(DBusConnection *connection, + DBusMessage *message, + DBusPendingCall **call, int timeout); +gboolean g_dbus_send_error(DBusConnection *connection, DBusMessage *message, + const char *name, const char *format, ...) + __attribute__((format(printf, 4, 5))); +gboolean g_dbus_send_error_valist(DBusConnection *connection, + DBusMessage *message, const char *name, + const char *format, va_list args); +gboolean g_dbus_send_reply(DBusConnection *connection, + DBusMessage *message, int type, ...); +gboolean g_dbus_send_reply_valist(DBusConnection *connection, + DBusMessage *message, int type, va_list args); + +gboolean g_dbus_emit_signal(DBusConnection *connection, + const char *path, const char *interface, + const char *name, int type, ...); +gboolean g_dbus_emit_signal_valist(DBusConnection *connection, + const char *path, const char *interface, + const char *name, int type, va_list args); + +guint g_dbus_add_service_watch(DBusConnection *connection, const char *name, + GDBusWatchFunction connect, + GDBusWatchFunction disconnect, + void *user_data, GDBusDestroyFunction destroy); +guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name, + GDBusWatchFunction function, + void *user_data, GDBusDestroyFunction destroy); +guint g_dbus_add_signal_watch(DBusConnection *connection, + const char *sender, const char *path, + const char *interface, const char *member, + GDBusSignalFunction function, void *user_data, + GDBusDestroyFunction destroy); +guint g_dbus_add_properties_watch(DBusConnection *connection, + const char *sender, const char *path, + const char *interface, + GDBusSignalFunction function, void *user_data, + GDBusDestroyFunction destroy); +gboolean g_dbus_remove_watch(DBusConnection *connection, guint tag); +void g_dbus_remove_all_watches(DBusConnection *connection); + +void g_dbus_pending_property_success(GDBusPendingPropertySet id); +void g_dbus_pending_property_error_valist(GDBusPendingReply id, + const char *name, const char *format, va_list args); +void g_dbus_pending_property_error(GDBusPendingReply id, const char *name, + const char *format, ...); + +/* + * Note that when multiple properties for a given object path are changed + * in the same mainloop iteration, they will be grouped with the last + * property changed. If this behaviour is undesired, use + * g_dbus_emit_property_changed_full() with the + * G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH flag, causing the signal to ignore + * any grouping. + */ +void g_dbus_emit_property_changed(DBusConnection *connection, + const char *path, const char *interface, + const char *name); +void g_dbus_emit_property_changed_full(DBusConnection *connection, + const char *path, const char *interface, + const char *name, + GDbusPropertyChangedFlags flags); +gboolean g_dbus_get_properties(DBusConnection *connection, const char *path, + const char *interface, DBusMessageIter *iter); + +gboolean g_dbus_attach_object_manager(DBusConnection *connection); +gboolean g_dbus_detach_object_manager(DBusConnection *connection); + +typedef struct GDBusClient GDBusClient; +typedef struct GDBusProxy GDBusProxy; + +GDBusProxy *g_dbus_proxy_new(GDBusClient *client, const char *path, + const char *interface); + +GDBusProxy *g_dbus_proxy_ref(GDBusProxy *proxy); +void g_dbus_proxy_unref(GDBusProxy *proxy); + +const char *g_dbus_proxy_get_path(GDBusProxy *proxy); +const char *g_dbus_proxy_get_interface(GDBusProxy *proxy); + +gboolean g_dbus_proxy_get_property(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter); + +GDBusProxy *g_dbus_proxy_lookup(GList *list, int *index, const char *path, + const char *interface); +char *g_dbus_proxy_path_lookup(GList *list, int *index, const char *path); + +gboolean g_dbus_proxy_refresh_property(GDBusProxy *proxy, const char *name); + +typedef void (* GDBusResultFunction) (const DBusError *error, void *user_data); + +gboolean g_dbus_proxy_set_property_basic(GDBusProxy *proxy, + const char *name, int type, const void *value, + GDBusResultFunction function, void *user_data, + GDBusDestroyFunction destroy); + +gboolean g_dbus_proxy_set_property_array(GDBusProxy *proxy, + const char *name, int type, const void *value, + size_t size, GDBusResultFunction function, + void *user_data, GDBusDestroyFunction destroy); + +void g_dbus_dict_append_entry(DBusMessageIter *dict, + const char *key, int type, void *val); +void g_dbus_dict_append_basic_array(DBusMessageIter *dict, int key_type, + const void *key, int type, void *val, + int n_elements); +void g_dbus_dict_append_array(DBusMessageIter *dict, + const char *key, int type, void *val, + int n_elements); + +typedef void (* GDBusSetupFunction) (DBusMessageIter *iter, void *user_data); +typedef void (* GDBusReturnFunction) (DBusMessage *message, void *user_data); + +gboolean g_dbus_proxy_method_call(GDBusProxy *proxy, const char *method, + GDBusSetupFunction setup, + GDBusReturnFunction function, void *user_data, + GDBusDestroyFunction destroy); + +typedef void (* GDBusClientFunction) (GDBusClient *client, void *user_data); +typedef void (* GDBusProxyFunction) (GDBusProxy *proxy, void *user_data); +typedef void (* GDBusPropertyFunction) (GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data); + +gboolean g_dbus_proxy_set_property_watch(GDBusProxy *proxy, + GDBusPropertyFunction function, void *user_data); + +gboolean g_dbus_proxy_set_removed_watch(GDBusProxy *proxy, + GDBusProxyFunction destroy, void *user_data); + +GDBusClient *g_dbus_client_new(DBusConnection *connection, + const char *service, const char *path); +GDBusClient *g_dbus_client_new_full(DBusConnection *connection, + const char *service, + const char *path, + const char *root_path); + +GDBusClient *g_dbus_client_ref(GDBusClient *client); +void g_dbus_client_unref(GDBusClient *client); + +gboolean g_dbus_client_set_connect_watch(GDBusClient *client, + GDBusWatchFunction function, void *user_data); +gboolean g_dbus_client_set_disconnect_watch(GDBusClient *client, + GDBusWatchFunction function, void *user_data); +gboolean g_dbus_client_set_signal_watch(GDBusClient *client, + GDBusMessageFunction function, void *user_data); +gboolean g_dbus_client_set_ready_watch(GDBusClient *client, + GDBusClientFunction ready, void *user_data); +gboolean g_dbus_client_set_proxy_handlers(GDBusClient *client, + GDBusProxyFunction proxy_added, + GDBusProxyFunction proxy_removed, + GDBusPropertyFunction property_changed, + void *user_data); + +#ifdef __cplusplus +} +#endif + +#endif /* __GDBUS_H */ diff --git a/gdbus/mainloop.c b/gdbus/mainloop.c new file mode 100644 index 0000000..b90a844 --- /dev/null +++ b/gdbus/mainloop.c @@ -0,0 +1,378 @@ +/* + * + * D-Bus helper library + * + * Copyright (C) 2004-2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "gdbus.h" + +#define info(fmt...) +#define error(fmt...) +#define debug(fmt...) + +struct timeout_handler { + guint id; + DBusTimeout *timeout; +}; + +struct watch_info { + guint id; + DBusWatch *watch; + DBusConnection *conn; +}; + +struct disconnect_data { + GDBusWatchFunction function; + void *user_data; +}; + +static gboolean disconnected_signal(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct disconnect_data *dc_data = data; + + error("Got disconnected from the system message bus"); + + dc_data->function(conn, dc_data->user_data); + + dbus_connection_unref(conn); + + return TRUE; +} + +static gboolean message_dispatch(void *data) +{ + DBusConnection *conn = data; + + /* Dispatch messages */ + while (dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS); + + dbus_connection_unref(conn); + + return FALSE; +} + +static inline void queue_dispatch(DBusConnection *conn, + DBusDispatchStatus status) +{ + if (status == DBUS_DISPATCH_DATA_REMAINS) + g_idle_add(message_dispatch, dbus_connection_ref(conn)); +} + +static gboolean watch_func(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + struct watch_info *info = data; + unsigned int flags = 0; + DBusDispatchStatus status; + DBusConnection *conn; + + if (cond & G_IO_IN) flags |= DBUS_WATCH_READABLE; + if (cond & G_IO_OUT) flags |= DBUS_WATCH_WRITABLE; + if (cond & G_IO_HUP) flags |= DBUS_WATCH_HANGUP; + if (cond & G_IO_ERR) flags |= DBUS_WATCH_ERROR; + + /* Protect connection from being destroyed by dbus_watch_handle */ + conn = dbus_connection_ref(info->conn); + + dbus_watch_handle(info->watch, flags); + + status = dbus_connection_get_dispatch_status(conn); + queue_dispatch(conn, status); + + dbus_connection_unref(conn); + + return TRUE; +} + +static void watch_info_free(void *data) +{ + struct watch_info *info = data; + + if (info->id > 0) { + g_source_remove(info->id); + info->id = 0; + } + + dbus_connection_unref(info->conn); + + g_free(info); +} + +static dbus_bool_t add_watch(DBusWatch *watch, void *data) +{ + DBusConnection *conn = data; + GIOCondition cond = G_IO_HUP | G_IO_ERR; + GIOChannel *chan; + struct watch_info *info; + unsigned int flags; + int fd; + + if (!dbus_watch_get_enabled(watch)) + return TRUE; + + info = g_new0(struct watch_info, 1); + + fd = dbus_watch_get_unix_fd(watch); + chan = g_io_channel_unix_new(fd); + + info->watch = watch; + info->conn = dbus_connection_ref(conn); + + dbus_watch_set_data(watch, info, watch_info_free); + + flags = dbus_watch_get_flags(watch); + + if (flags & DBUS_WATCH_READABLE) cond |= G_IO_IN; + if (flags & DBUS_WATCH_WRITABLE) cond |= G_IO_OUT; + + info->id = g_io_add_watch(chan, cond, watch_func, info); + + g_io_channel_unref(chan); + + return TRUE; +} + +static void remove_watch(DBusWatch *watch, void *data) +{ + if (dbus_watch_get_enabled(watch)) + return; + + /* will trigger watch_info_free() */ + dbus_watch_set_data(watch, NULL, NULL); +} + +static void watch_toggled(DBusWatch *watch, void *data) +{ + /* Because we just exit on OOM, enable/disable is + * no different from add/remove */ + if (dbus_watch_get_enabled(watch)) + add_watch(watch, data); + else + remove_watch(watch, data); +} + +static gboolean timeout_handler_dispatch(gpointer data) +{ + struct timeout_handler *handler = data; + + handler->id = 0; + + /* if not enabled should not be polled by the main loop */ + if (!dbus_timeout_get_enabled(handler->timeout)) + return FALSE; + + dbus_timeout_handle(handler->timeout); + + return FALSE; +} + +static void timeout_handler_free(void *data) +{ + struct timeout_handler *handler = data; + + if (handler->id > 0) { + g_source_remove(handler->id); + handler->id = 0; + } + + g_free(handler); +} + +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) +{ + int interval = dbus_timeout_get_interval(timeout); + struct timeout_handler *handler; + + if (!dbus_timeout_get_enabled(timeout)) + return TRUE; + + handler = g_new0(struct timeout_handler, 1); + + handler->timeout = timeout; + + dbus_timeout_set_data(timeout, handler, timeout_handler_free); + + handler->id = g_timeout_add(interval, timeout_handler_dispatch, + handler); + + return TRUE; +} + +static void remove_timeout(DBusTimeout *timeout, void *data) +{ + /* will trigger timeout_handler_free() */ + dbus_timeout_set_data(timeout, NULL, NULL); +} + +static void timeout_toggled(DBusTimeout *timeout, void *data) +{ + if (dbus_timeout_get_enabled(timeout)) + add_timeout(timeout, data); + else + remove_timeout(timeout, data); +} + +static void dispatch_status(DBusConnection *conn, + DBusDispatchStatus status, void *data) +{ + if (!dbus_connection_get_is_connected(conn)) + return; + + queue_dispatch(conn, status); +} + +static inline void setup_dbus_with_main_loop(DBusConnection *conn) +{ + dbus_connection_set_watch_functions(conn, add_watch, remove_watch, + watch_toggled, conn, NULL); + + dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, + timeout_toggled, NULL, NULL); + + dbus_connection_set_dispatch_status_function(conn, dispatch_status, + NULL, NULL); +} + +static gboolean setup_bus(DBusConnection *conn, const char *name, + DBusError *error) +{ + gboolean result; + DBusDispatchStatus status; + + if (name != NULL) { + result = g_dbus_request_name(conn, name, error); + + if (error != NULL) { + if (dbus_error_is_set(error) == TRUE) + return FALSE; + } + + if (result == FALSE) + return FALSE; + } + + setup_dbus_with_main_loop(conn); + + status = dbus_connection_get_dispatch_status(conn); + queue_dispatch(conn, status); + + return TRUE; +} + +DBusConnection *g_dbus_setup_bus(DBusBusType type, const char *name, + DBusError *error) +{ + DBusConnection *conn; + + conn = dbus_bus_get(type, error); + + if (error != NULL) { + if (dbus_error_is_set(error) == TRUE) + return NULL; + } + + if (conn == NULL) + return NULL; + + if (setup_bus(conn, name, error) == FALSE) { + dbus_connection_unref(conn); + return NULL; + } + + return conn; +} + +DBusConnection *g_dbus_setup_private(DBusBusType type, const char *name, + DBusError *error) +{ + DBusConnection *conn; + + conn = dbus_bus_get_private(type, error); + + if (error != NULL) { + if (dbus_error_is_set(error) == TRUE) + return NULL; + } + + if (conn == NULL) + return NULL; + + if (setup_bus(conn, name, error) == FALSE) { + dbus_connection_close(conn); + dbus_connection_unref(conn); + return NULL; + } + + return conn; +} + +gboolean g_dbus_request_name(DBusConnection *connection, const char *name, + DBusError *error) +{ + int result; + + result = dbus_bus_request_name(connection, name, + DBUS_NAME_FLAG_DO_NOT_QUEUE, error); + + if (error != NULL) { + if (dbus_error_is_set(error) == TRUE) + return FALSE; + } + + if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + if (error != NULL) + dbus_set_error(error, name, "Name already in use"); + + return FALSE; + } + + return TRUE; +} + +gboolean g_dbus_set_disconnect_function(DBusConnection *connection, + GDBusWatchFunction function, + void *user_data, DBusFreeFunction destroy) +{ + struct disconnect_data *dc_data; + + dc_data = g_new0(struct disconnect_data, 1); + + dc_data->function = function; + dc_data->user_data = user_data; + + dbus_connection_set_exit_on_disconnect(connection, FALSE); + + if (g_dbus_add_signal_watch(connection, NULL, NULL, + DBUS_INTERFACE_LOCAL, "Disconnected", + disconnected_signal, dc_data, g_free) == 0) { + error("Failed to add watch for D-Bus Disconnected signal"); + g_free(dc_data); + return FALSE; + } + + return TRUE; +} diff --git a/gdbus/object.c b/gdbus/object.c new file mode 100644 index 0000000..49b5780 --- /dev/null +++ b/gdbus/object.c @@ -0,0 +1,1858 @@ +/* + * + * D-Bus helper library + * + * Copyright (C) 2004-2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include "gdbus.h" + +#define info(fmt...) +#define error(fmt...) +#define debug(fmt...) + +#define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" + +#ifndef DBUS_ERROR_UNKNOWN_PROPERTY +#define DBUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" +#endif + +#ifndef DBUS_ERROR_PROPERTY_READ_ONLY +#define DBUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly" +#endif + +struct generic_data { + unsigned int refcount; + DBusConnection *conn; + char *path; + GSList *interfaces; + GSList *objects; + GSList *added; + GSList *removed; + guint process_id; + gboolean pending_prop; + char *introspect; + struct generic_data *parent; +}; + +struct interface_data { + char *name; + const GDBusMethodTable *methods; + const GDBusSignalTable *signals; + const GDBusPropertyTable *properties; + GSList *pending_prop; + void *user_data; + GDBusDestroyFunction destroy; +}; + +struct security_data { + GDBusPendingReply pending; + DBusMessage *message; + const GDBusMethodTable *method; + void *iface_user_data; +}; + +struct property_data { + DBusConnection *conn; + GDBusPendingPropertySet id; + DBusMessage *message; +}; + +static int global_flags = 0; +static struct generic_data *root; +static GSList *pending = NULL; + +static gboolean process_changes(gpointer user_data); +static void process_properties_from_interface(struct generic_data *data, + struct interface_data *iface); +static void process_property_changes(struct generic_data *data); + +static void print_arguments(GString *gstr, const GDBusArgInfo *args, + const char *direction) +{ + for (; args && args->name; args++) { + g_string_append_printf(gstr, + "name, args->signature); + + if (direction) + g_string_append_printf(gstr, + " direction=\"%s\"/>\n", direction); + else + g_string_append_printf(gstr, "/>\n"); + + } +} + +#define G_DBUS_ANNOTATE(name_, value_) \ + "" + +#define G_DBUS_ANNOTATE_DEPRECATED \ + G_DBUS_ANNOTATE("Deprecated", "true") + +#define G_DBUS_ANNOTATE_NOREPLY \ + G_DBUS_ANNOTATE("Method.NoReply", "true") + +static gboolean check_experimental(int flags, int flag) +{ + if (!(flags & flag)) + return FALSE; + + return !(global_flags & G_DBUS_FLAG_ENABLE_EXPERIMENTAL); +} + +static void generate_interface_xml(GString *gstr, struct interface_data *iface) +{ + const GDBusMethodTable *method; + const GDBusSignalTable *signal; + const GDBusPropertyTable *property; + + for (method = iface->methods; method && method->name; method++) { + if (check_experimental(method->flags, + G_DBUS_METHOD_FLAG_EXPERIMENTAL)) + continue; + + g_string_append_printf(gstr, "", + method->name); + print_arguments(gstr, method->in_args, "in"); + print_arguments(gstr, method->out_args, "out"); + + if (method->flags & G_DBUS_METHOD_FLAG_DEPRECATED) + g_string_append_printf(gstr, + G_DBUS_ANNOTATE_DEPRECATED); + + if (method->flags & G_DBUS_METHOD_FLAG_NOREPLY) + g_string_append_printf(gstr, G_DBUS_ANNOTATE_NOREPLY); + + g_string_append_printf(gstr, ""); + } + + for (signal = iface->signals; signal && signal->name; signal++) { + if (check_experimental(signal->flags, + G_DBUS_SIGNAL_FLAG_EXPERIMENTAL)) + continue; + + g_string_append_printf(gstr, "", + signal->name); + print_arguments(gstr, signal->args, NULL); + + if (signal->flags & G_DBUS_SIGNAL_FLAG_DEPRECATED) + g_string_append_printf(gstr, + G_DBUS_ANNOTATE_DEPRECATED); + + g_string_append_printf(gstr, "\n"); + } + + for (property = iface->properties; property && property->name; + property++) { + if (check_experimental(property->flags, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL)) + continue; + + g_string_append_printf(gstr, "", + property->name, property->type, + property->get ? "read" : "", + property->set ? "write" : ""); + + if (property->flags & G_DBUS_PROPERTY_FLAG_DEPRECATED) + g_string_append_printf(gstr, + G_DBUS_ANNOTATE_DEPRECATED); + + g_string_append_printf(gstr, ""); + } +} + +static void generate_introspection_xml(DBusConnection *conn, + struct generic_data *data, const char *path) +{ + GSList *list; + GString *gstr; + char **children; + int i; + + g_free(data->introspect); + + gstr = g_string_new(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE); + + g_string_append_printf(gstr, ""); + + for (list = data->interfaces; list; list = list->next) { + struct interface_data *iface = list->data; + + g_string_append_printf(gstr, "", + iface->name); + + generate_interface_xml(gstr, iface); + + g_string_append_printf(gstr, ""); + } + + if (!dbus_connection_list_registered(conn, path, &children)) + goto done; + + for (i = 0; children[i]; i++) + g_string_append_printf(gstr, "", + children[i]); + + dbus_free_string_array(children); + +done: + g_string_append_printf(gstr, ""); + + data->introspect = g_string_free(gstr, FALSE); +} + +static DBusMessage *introspect(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct generic_data *data = user_data; + DBusMessage *reply; + + if (data->introspect == NULL) + generate_introspection_xml(connection, data, + dbus_message_get_path(message)); + + reply = dbus_message_new_method_return(message); + if (reply == NULL) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &data->introspect, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusHandlerResult process_message(DBusConnection *connection, + DBusMessage *message, const GDBusMethodTable *method, + void *iface_user_data) +{ + DBusMessage *reply; + + reply = method->function(connection, message, iface_user_data); + + if (method->flags & G_DBUS_METHOD_FLAG_NOREPLY || + dbus_message_get_no_reply(message)) { + if (reply != NULL) + dbus_message_unref(reply); + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (method->flags & G_DBUS_METHOD_FLAG_ASYNC) { + if (reply == NULL) + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (reply == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + + g_dbus_send_message(connection, reply); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static GDBusPendingReply next_pending = 1; +static GSList *pending_security = NULL; + +static const GDBusSecurityTable *security_table = NULL; + +void g_dbus_pending_success(DBusConnection *connection, + GDBusPendingReply pending) +{ + GSList *list; + + for (list = pending_security; list; list = list->next) { + struct security_data *secdata = list->data; + + if (secdata->pending != pending) + continue; + + pending_security = g_slist_remove(pending_security, secdata); + + process_message(connection, secdata->message, + secdata->method, secdata->iface_user_data); + + dbus_message_unref(secdata->message); + g_free(secdata); + return; + } +} + +void g_dbus_pending_error_valist(DBusConnection *connection, + GDBusPendingReply pending, const char *name, + const char *format, va_list args) +{ + GSList *list; + + for (list = pending_security; list; list = list->next) { + struct security_data *secdata = list->data; + + if (secdata->pending != pending) + continue; + + pending_security = g_slist_remove(pending_security, secdata); + + g_dbus_send_error_valist(connection, secdata->message, + name, format, args); + + dbus_message_unref(secdata->message); + g_free(secdata); + return; + } +} + +void g_dbus_pending_error(DBusConnection *connection, + GDBusPendingReply pending, + const char *name, const char *format, ...) +{ + va_list args; + + va_start(args, format); + + g_dbus_pending_error_valist(connection, pending, name, format, args); + + va_end(args); +} + +int polkit_check_authorization(DBusConnection *conn, + const char *action, gboolean interaction, + void (*function) (dbus_bool_t authorized, + void *user_data), + void *user_data, int timeout); + +struct builtin_security_data { + DBusConnection *conn; + GDBusPendingReply pending; +}; + +static void builtin_security_result(dbus_bool_t authorized, void *user_data) +{ + struct builtin_security_data *data = user_data; + + if (authorized == TRUE) + g_dbus_pending_success(data->conn, data->pending); + else + g_dbus_pending_error(data->conn, data->pending, + DBUS_ERROR_AUTH_FAILED, NULL); + + g_free(data); +} + +static void builtin_security_function(DBusConnection *conn, + const char *action, + gboolean interaction, + GDBusPendingReply pending) +{ + struct builtin_security_data *data; + + data = g_new0(struct builtin_security_data, 1); + data->conn = conn; + data->pending = pending; + + if (polkit_check_authorization(conn, action, interaction, + builtin_security_result, data, 30000) < 0) + g_dbus_pending_error(conn, pending, NULL, NULL); +} + +static gboolean check_privilege(DBusConnection *conn, DBusMessage *msg, + const GDBusMethodTable *method, void *iface_user_data) +{ + const GDBusSecurityTable *security; + + for (security = security_table; security && security->privilege; + security++) { + struct security_data *secdata; + gboolean interaction; + + if (security->privilege != method->privilege) + continue; + + secdata = g_new(struct security_data, 1); + secdata->pending = next_pending++; + secdata->message = dbus_message_ref(msg); + secdata->method = method; + secdata->iface_user_data = iface_user_data; + + pending_security = g_slist_prepend(pending_security, secdata); + + if (security->flags & G_DBUS_SECURITY_FLAG_ALLOW_INTERACTION) + interaction = TRUE; + else + interaction = FALSE; + + if (!(security->flags & G_DBUS_SECURITY_FLAG_BUILTIN) && + security->function) + security->function(conn, security->action, + interaction, secdata->pending); + else + builtin_security_function(conn, security->action, + interaction, secdata->pending); + + return TRUE; + } + + return FALSE; +} + +static GDBusPendingPropertySet next_pending_property = 1; +static GSList *pending_property_set; + +static struct property_data *remove_pending_property_data( + GDBusPendingPropertySet id) +{ + struct property_data *propdata; + GSList *l; + + for (l = pending_property_set; l != NULL; l = l->next) { + propdata = l->data; + if (propdata->id != id) + continue; + + break; + } + + if (l == NULL) + return NULL; + + pending_property_set = g_slist_delete_link(pending_property_set, l); + + return propdata; +} + +void g_dbus_pending_property_success(GDBusPendingPropertySet id) +{ + struct property_data *propdata; + + propdata = remove_pending_property_data(id); + if (propdata == NULL) + return; + + g_dbus_send_reply(propdata->conn, propdata->message, + DBUS_TYPE_INVALID); + dbus_message_unref(propdata->message); + g_free(propdata); +} + +void g_dbus_pending_property_error_valist(GDBusPendingReply id, + const char *name, const char *format, + va_list args) +{ + struct property_data *propdata; + + propdata = remove_pending_property_data(id); + if (propdata == NULL) + return; + + g_dbus_send_error_valist(propdata->conn, propdata->message, name, + format, args); + + dbus_message_unref(propdata->message); + g_free(propdata); +} + +void g_dbus_pending_property_error(GDBusPendingReply id, const char *name, + const char *format, ...) +{ + va_list args; + + va_start(args, format); + + g_dbus_pending_property_error_valist(id, name, format, args); + + va_end(args); +} + +static void reset_parent(gpointer data, gpointer user_data) +{ + struct generic_data *child = data; + struct generic_data *parent = user_data; + + child->parent = parent; +} + +static void append_property(struct interface_data *iface, + const GDBusPropertyTable *p, DBusMessageIter *dict) +{ + DBusMessageIter entry, value; + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &p->name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, p->type, + &value); + + p->get(p, &value, iface->user_data); + + dbus_message_iter_close_container(&entry, &value); + dbus_message_iter_close_container(dict, &entry); +} + +static void append_properties(struct interface_data *data, + DBusMessageIter *iter) +{ + DBusMessageIter dict; + const GDBusPropertyTable *p; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + for (p = data->properties; p && p->name; p++) { + if (check_experimental(p->flags, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL)) + continue; + + if (p->get == NULL) + continue; + + if (p->exists != NULL && !p->exists(p, data->user_data)) + continue; + + append_property(data, p, &dict); + } + + dbus_message_iter_close_container(iter, &dict); +} + +static void append_interface(gpointer data, gpointer user_data) +{ + struct interface_data *iface = data; + DBusMessageIter *array = user_data; + DBusMessageIter entry; + + dbus_message_iter_open_container(array, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &iface->name); + append_properties(data, &entry); + dbus_message_iter_close_container(array, &entry); +} + +static void emit_interfaces_added(struct generic_data *data) +{ + DBusMessage *signal; + DBusMessageIter iter, array; + + if (root == NULL || data == root) + return; + + signal = dbus_message_new_signal(root->path, + DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesAdded"); + if (signal == NULL) + return; + + dbus_message_iter_init_append(signal, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, + &data->path); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_ARRAY_AS_STRING + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); + + g_slist_foreach(data->added, append_interface, &array); + g_slist_free(data->added); + data->added = NULL; + + dbus_message_iter_close_container(&iter, &array); + + /* Use dbus_connection_send to avoid recursive calls to g_dbus_flush */ + dbus_connection_send(data->conn, signal, NULL); + dbus_message_unref(signal); +} + +static struct interface_data *find_interface(GSList *interfaces, + const char *name) +{ + GSList *list; + + if (name == NULL) + return NULL; + + for (list = interfaces; list; list = list->next) { + struct interface_data *iface = list->data; + if (!strcmp(name, iface->name)) + return iface; + } + + return NULL; +} + +static gboolean g_dbus_args_have_signature(const GDBusArgInfo *args, + DBusMessage *message) +{ + const char *sig = dbus_message_get_signature(message); + const char *p = NULL; + + for (; args && args->signature && *sig; args++) { + p = args->signature; + + for (; *sig && *p; sig++, p++) { + if (*p != *sig) + return FALSE; + } + } + + if (*sig || (p && *p) || (args && args->signature)) + return FALSE; + + return TRUE; +} + +static void add_pending(struct generic_data *data) +{ + guint old_id = data->process_id; + + data->process_id = g_idle_add(process_changes, data); + + if (old_id > 0) { + /* + * If the element already had an old idler, remove the old one, + * no need to re-add it to the pending list. + */ + g_source_remove(old_id); + return; + } + + pending = g_slist_append(pending, data); +} + +static gboolean remove_interface(struct generic_data *data, const char *name) +{ + struct interface_data *iface; + + iface = find_interface(data->interfaces, name); + if (iface == NULL) + return FALSE; + + process_properties_from_interface(data, iface); + + data->interfaces = g_slist_remove(data->interfaces, iface); + + if (iface->destroy) { + iface->destroy(iface->user_data); + iface->user_data = NULL; + } + + /* + * Interface being removed was just added, on the same mainloop + * iteration? Don't send any signal + */ + if (g_slist_find(data->added, iface)) { + data->added = g_slist_remove(data->added, iface); + g_free(iface->name); + g_free(iface); + return TRUE; + } + + if (data->parent == NULL) { + g_free(iface->name); + g_free(iface); + return TRUE; + } + + data->removed = g_slist_prepend(data->removed, iface->name); + g_free(iface); + + add_pending(data); + + return TRUE; +} + +static struct generic_data *invalidate_parent_data(DBusConnection *conn, + const char *child_path) +{ + struct generic_data *data = NULL, *child = NULL, *parent = NULL; + char *parent_path, *slash; + + parent_path = g_strdup(child_path); + slash = strrchr(parent_path, '/'); + if (slash == NULL) + goto done; + + if (slash == parent_path && parent_path[1] != '\0') + parent_path[1] = '\0'; + else + *slash = '\0'; + + if (!strlen(parent_path)) + goto done; + + if (dbus_connection_get_object_path_data(conn, parent_path, + (void *) &data) == FALSE) { + goto done; + } + + parent = invalidate_parent_data(conn, parent_path); + + if (data == NULL) { + data = parent; + if (data == NULL) + goto done; + } + + g_free(data->introspect); + data->introspect = NULL; + + if (!dbus_connection_get_object_path_data(conn, child_path, + (void *) &child)) + goto done; + + if (child == NULL || g_slist_find(data->objects, child) != NULL) + goto done; + + data->objects = g_slist_prepend(data->objects, child); + child->parent = data; + +done: + g_free(parent_path); + return data; +} + +static inline const GDBusPropertyTable *find_property(const GDBusPropertyTable *properties, + const char *name) +{ + const GDBusPropertyTable *p; + + for (p = properties; p && p->name; p++) { + if (strcmp(name, p->name) != 0) + continue; + + if (check_experimental(p->flags, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL)) + break; + + return p; + } + + return NULL; +} + +static DBusMessage *properties_get(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct generic_data *data = user_data; + struct interface_data *iface; + const GDBusPropertyTable *property; + const char *interface, *name; + DBusMessageIter iter, value; + DBusMessage *reply; + + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return NULL; + + iface = find_interface(data->interfaces, interface); + if (iface == NULL) + return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, + "No such interface '%s'", interface); + + property = find_property(iface->properties, name); + if (property == NULL) + return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, + "No such property '%s'", name); + + if (property->exists != NULL && + !property->exists(property, iface->user_data)) + return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, + "No such property '%s'", name); + + if (property->get == NULL) + return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, + "Property '%s' is not readable", name); + + reply = dbus_message_new_method_return(message); + if (reply == NULL) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, + property->type, &value); + + if (!property->get(property, &value, iface->user_data)) { + dbus_message_unref(reply); + return NULL; + } + + dbus_message_iter_close_container(&iter, &value); + + return reply; +} + +static DBusMessage *properties_get_all(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct generic_data *data = user_data; + struct interface_data *iface; + const char *interface; + DBusMessageIter iter; + DBusMessage *reply; + + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_INVALID)) + return NULL; + + iface = find_interface(data->interfaces, interface); + if (iface == NULL) + return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, + "No such interface '%s'", interface); + + reply = dbus_message_new_method_return(message); + if (reply == NULL) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + append_properties(iface, &iter); + + return reply; +} + +static DBusMessage *properties_set(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct generic_data *data = user_data; + DBusMessageIter iter, sub; + struct interface_data *iface; + const GDBusPropertyTable *property; + const char *name, *interface; + struct property_data *propdata; + gboolean valid_signature; + char *signature; + + if (!dbus_message_iter_init(message, &iter)) + return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, + "No arguments given"); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, + "Invalid argument type: '%c'", + dbus_message_iter_get_arg_type(&iter)); + + dbus_message_iter_get_basic(&iter, &interface); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, + "Invalid argument type: '%c'", + dbus_message_iter_get_arg_type(&iter)); + + dbus_message_iter_get_basic(&iter, &name); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, + "Invalid argument type: '%c'", + dbus_message_iter_get_arg_type(&iter)); + + dbus_message_iter_recurse(&iter, &sub); + + iface = find_interface(data->interfaces, interface); + if (iface == NULL) + return g_dbus_create_error(message, DBUS_ERROR_INVALID_ARGS, + "No such interface '%s'", interface); + + property = find_property(iface->properties, name); + if (property == NULL) + return g_dbus_create_error(message, + DBUS_ERROR_UNKNOWN_PROPERTY, + "No such property '%s'", name); + + if (property->set == NULL) + return g_dbus_create_error(message, + DBUS_ERROR_PROPERTY_READ_ONLY, + "Property '%s' is not writable", name); + + if (property->exists != NULL && + !property->exists(property, iface->user_data)) + return g_dbus_create_error(message, + DBUS_ERROR_UNKNOWN_PROPERTY, + "No such property '%s'", name); + + signature = dbus_message_iter_get_signature(&sub); + valid_signature = strcmp(signature, property->type) ? FALSE : TRUE; + dbus_free(signature); + if (!valid_signature) + return g_dbus_create_error(message, + DBUS_ERROR_INVALID_SIGNATURE, + "Invalid signature for '%s'", name); + + propdata = g_new(struct property_data, 1); + propdata->id = next_pending_property++; + propdata->message = dbus_message_ref(message); + propdata->conn = connection; + pending_property_set = g_slist_prepend(pending_property_set, propdata); + + property->set(property, &sub, propdata->id, iface->user_data); + + return NULL; +} + +static const GDBusMethodTable properties_methods[] = { + { GDBUS_METHOD("Get", + GDBUS_ARGS({ "interface", "s" }, { "name", "s" }), + GDBUS_ARGS({ "value", "v" }), + properties_get) }, + { GDBUS_ASYNC_METHOD("Set", + GDBUS_ARGS({ "interface", "s" }, { "name", "s" }, + { "value", "v" }), + NULL, + properties_set) }, + { GDBUS_METHOD("GetAll", + GDBUS_ARGS({ "interface", "s" }), + GDBUS_ARGS({ "properties", "a{sv}" }), + properties_get_all) }, + { } +}; + +static const GDBusSignalTable properties_signals[] = { + { GDBUS_SIGNAL("PropertiesChanged", + GDBUS_ARGS({ "interface", "s" }, + { "changed_properties", "a{sv}" }, + { "invalidated_properties", "as"})) }, + { } +}; + +static void append_name(gpointer data, gpointer user_data) +{ + char *name = data; + DBusMessageIter *iter = user_data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &name); +} + +static void emit_interfaces_removed(struct generic_data *data) +{ + DBusMessage *signal; + DBusMessageIter iter, array; + + if (root == NULL || data == root) + return; + + signal = dbus_message_new_signal(root->path, + DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesRemoved"); + if (signal == NULL) + return; + + dbus_message_iter_init_append(signal, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, + &data->path); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array); + + g_slist_foreach(data->removed, append_name, &array); + g_slist_free_full(data->removed, g_free); + data->removed = NULL; + + dbus_message_iter_close_container(&iter, &array); + + /* Use dbus_connection_send to avoid recursive calls to g_dbus_flush */ + dbus_connection_send(data->conn, signal, NULL); + dbus_message_unref(signal); +} + +static void remove_pending(struct generic_data *data) +{ + if (data->process_id > 0) { + g_source_remove(data->process_id); + data->process_id = 0; + } + + pending = g_slist_remove(pending, data); +} + +static gboolean process_changes(gpointer user_data) +{ + struct generic_data *data = user_data; + + remove_pending(data); + + if (data->added != NULL) + emit_interfaces_added(data); + + /* Flush pending properties */ + if (data->pending_prop == TRUE) + process_property_changes(data); + + if (data->removed != NULL) + emit_interfaces_removed(data); + + data->process_id = 0; + + return FALSE; +} + +static void generic_unregister(DBusConnection *connection, void *user_data) +{ + struct generic_data *data = user_data; + struct generic_data *parent = data->parent; + + if (parent != NULL) + parent->objects = g_slist_remove(parent->objects, data); + + if (data->process_id > 0) { + g_source_remove(data->process_id); + data->process_id = 0; + process_changes(data); + } + + g_slist_foreach(data->objects, reset_parent, data->parent); + g_slist_free(data->objects); + + dbus_connection_unref(data->conn); + g_free(data->introspect); + g_free(data->path); + g_free(data); +} + +static DBusHandlerResult generic_message(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct generic_data *data = user_data; + struct interface_data *iface; + const GDBusMethodTable *method; + const char *interface; + + interface = dbus_message_get_interface(message); + + iface = find_interface(data->interfaces, interface); + if (iface == NULL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + for (method = iface->methods; method && + method->name && method->function; method++) { + + if (dbus_message_is_method_call(message, iface->name, + method->name) == FALSE) + continue; + + if (check_experimental(method->flags, + G_DBUS_METHOD_FLAG_EXPERIMENTAL)) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + if (g_dbus_args_have_signature(method->in_args, + message) == FALSE) + continue; + + if (check_privilege(connection, message, method, + iface->user_data) == TRUE) + return DBUS_HANDLER_RESULT_HANDLED; + + return process_message(connection, message, method, + iface->user_data); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusObjectPathVTable generic_table = { + .unregister_function = generic_unregister, + .message_function = generic_message, +}; + +static const GDBusMethodTable introspect_methods[] = { + { GDBUS_METHOD("Introspect", NULL, + GDBUS_ARGS({ "xml", "s" }), introspect) }, + { } +}; + +static void append_interfaces(struct generic_data *data, DBusMessageIter *iter) +{ + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_ARRAY_AS_STRING + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); + + g_slist_foreach(data->interfaces, append_interface, &array); + + dbus_message_iter_close_container(iter, &array); +} + +static void append_object(gpointer data, gpointer user_data) +{ + struct generic_data *child = data; + DBusMessageIter *array = user_data; + DBusMessageIter entry; + + dbus_message_iter_open_container(array, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, + &child->path); + append_interfaces(child, &entry); + dbus_message_iter_close_container(array, &entry); + + g_slist_foreach(child->objects, append_object, user_data); +} + +static DBusMessage *get_objects(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct generic_data *data = user_data; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter array; + + reply = dbus_message_new_method_return(message); + if (reply == NULL) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_OBJECT_PATH_AS_STRING + DBUS_TYPE_ARRAY_AS_STRING + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_ARRAY_AS_STRING + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &array); + + g_slist_foreach(data->objects, append_object, &array); + + dbus_message_iter_close_container(&iter, &array); + + return reply; +} + +static const GDBusMethodTable manager_methods[] = { + { GDBUS_METHOD("GetManagedObjects", NULL, + GDBUS_ARGS({ "objects", "a{oa{sa{sv}}}" }), get_objects) }, + { } +}; + +static const GDBusSignalTable manager_signals[] = { + { GDBUS_SIGNAL("InterfacesAdded", + GDBUS_ARGS({ "object", "o" }, + { "interfaces", "a{sa{sv}}" })) }, + { GDBUS_SIGNAL("InterfacesRemoved", + GDBUS_ARGS({ "object", "o" }, { "interfaces", "as" })) }, + { } +}; + +static gboolean add_interface(struct generic_data *data, + const char *name, + const GDBusMethodTable *methods, + const GDBusSignalTable *signals, + const GDBusPropertyTable *properties, + void *user_data, + GDBusDestroyFunction destroy) +{ + struct interface_data *iface; + const GDBusMethodTable *method; + const GDBusSignalTable *signal; + const GDBusPropertyTable *property; + + for (method = methods; method && method->name; method++) { + if (!check_experimental(method->flags, + G_DBUS_METHOD_FLAG_EXPERIMENTAL)) + goto done; + } + + for (signal = signals; signal && signal->name; signal++) { + if (!check_experimental(signal->flags, + G_DBUS_SIGNAL_FLAG_EXPERIMENTAL)) + goto done; + } + + for (property = properties; property && property->name; property++) { + if (!check_experimental(property->flags, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL)) + goto done; + } + + /* Nothing to register */ + return FALSE; + +done: + iface = g_new0(struct interface_data, 1); + iface->name = g_strdup(name); + iface->methods = methods; + iface->signals = signals; + iface->properties = properties; + iface->user_data = user_data; + iface->destroy = destroy; + + data->interfaces = g_slist_append(data->interfaces, iface); + if (data->parent == NULL) + return TRUE; + + data->added = g_slist_append(data->added, iface); + + add_pending(data); + + return TRUE; +} + +static struct generic_data *object_path_ref(DBusConnection *connection, + const char *path) +{ + struct generic_data *data; + + if (dbus_connection_get_object_path_data(connection, path, + (void *) &data) == TRUE) { + if (data != NULL) { + data->refcount++; + return data; + } + } + + data = g_new0(struct generic_data, 1); + data->conn = dbus_connection_ref(connection); + data->path = g_strdup(path); + data->refcount = 1; + + data->introspect = g_strdup(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE ""); + + if (!dbus_connection_register_object_path(connection, path, + &generic_table, data)) { + dbus_connection_unref(data->conn); + g_free(data->path); + g_free(data->introspect); + g_free(data); + return NULL; + } + + invalidate_parent_data(connection, path); + + add_interface(data, DBUS_INTERFACE_INTROSPECTABLE, introspect_methods, + NULL, NULL, data, NULL); + + return data; +} + +static void object_path_unref(DBusConnection *connection, const char *path) +{ + struct generic_data *data = NULL; + + if (dbus_connection_get_object_path_data(connection, path, + (void *) &data) == FALSE) + return; + + if (data == NULL) + return; + + data->refcount--; + + if (data->refcount > 0) + return; + + remove_interface(data, DBUS_INTERFACE_INTROSPECTABLE); + remove_interface(data, DBUS_INTERFACE_PROPERTIES); + + invalidate_parent_data(data->conn, data->path); + + dbus_connection_unregister_object_path(data->conn, data->path); +} + +static gboolean check_signal(DBusConnection *conn, const char *path, + const char *interface, const char *name, + const GDBusArgInfo **args) +{ + struct generic_data *data = NULL; + struct interface_data *iface; + const GDBusSignalTable *signal; + + *args = NULL; + if (!dbus_connection_get_object_path_data(conn, path, + (void *) &data) || data == NULL) { + error("dbus_connection_emit_signal: path %s isn't registered", + path); + return FALSE; + } + + iface = find_interface(data->interfaces, interface); + if (iface == NULL) { + error("dbus_connection_emit_signal: %s does not implement %s", + path, interface); + return FALSE; + } + + for (signal = iface->signals; signal && signal->name; signal++) { + if (strcmp(signal->name, name) != 0) + continue; + + if (signal->flags & G_DBUS_SIGNAL_FLAG_EXPERIMENTAL) { + const char *env = g_getenv("GDBUS_EXPERIMENTAL"); + if (g_strcmp0(env, "1") != 0) + break; + } + + *args = signal->args; + return TRUE; + } + + error("No signal named %s on interface %s", name, interface); + return FALSE; +} + +gboolean g_dbus_register_interface(DBusConnection *connection, + const char *path, const char *name, + const GDBusMethodTable *methods, + const GDBusSignalTable *signals, + const GDBusPropertyTable *properties, + void *user_data, + GDBusDestroyFunction destroy) +{ + struct generic_data *data; + + if (!dbus_validate_path(path, NULL)) { + error("Invalid object path: %s", path); + return FALSE; + } + + if (!dbus_validate_interface(name, NULL)) { + error("Invalid interface: %s", name); + return FALSE; + } + + data = object_path_ref(connection, path); + if (data == NULL) + return FALSE; + + if (find_interface(data->interfaces, name)) { + object_path_unref(connection, path); + return FALSE; + } + + if (!add_interface(data, name, methods, signals, properties, user_data, + destroy)) { + object_path_unref(connection, path); + return FALSE; + } + + if (properties != NULL && !find_interface(data->interfaces, + DBUS_INTERFACE_PROPERTIES)) + add_interface(data, DBUS_INTERFACE_PROPERTIES, + properties_methods, properties_signals, NULL, + data, NULL); + + g_free(data->introspect); + data->introspect = NULL; + + return TRUE; +} + +gboolean g_dbus_unregister_interface(DBusConnection *connection, + const char *path, const char *name) +{ + struct generic_data *data = NULL; + + if (path == NULL) + return FALSE; + + if (dbus_connection_get_object_path_data(connection, path, + (void *) &data) == FALSE) + return FALSE; + + if (data == NULL) + return FALSE; + + if (remove_interface(data, name) == FALSE) + return FALSE; + + g_free(data->introspect); + data->introspect = NULL; + + object_path_unref(connection, data->path); + + return TRUE; +} + +gboolean g_dbus_register_security(const GDBusSecurityTable *security) +{ + if (security_table != NULL) + return FALSE; + + security_table = security; + + return TRUE; +} + +gboolean g_dbus_unregister_security(const GDBusSecurityTable *security) +{ + security_table = NULL; + + return TRUE; +} + +DBusMessage *g_dbus_create_error_valist(DBusMessage *message, const char *name, + const char *format, va_list args) +{ + char str[1024]; + + /* Check if the message can be replied */ + if (dbus_message_get_no_reply(message)) + return NULL; + + if (format) + vsnprintf(str, sizeof(str), format, args); + else + str[0] = '\0'; + + return dbus_message_new_error(message, name, str); +} + +DBusMessage *g_dbus_create_error(DBusMessage *message, const char *name, + const char *format, ...) +{ + va_list args; + DBusMessage *reply; + + va_start(args, format); + + reply = g_dbus_create_error_valist(message, name, format, args); + + va_end(args); + + return reply; +} + +DBusMessage *g_dbus_create_reply_valist(DBusMessage *message, + int type, va_list args) +{ + DBusMessage *reply; + + /* Check if the message can be replied */ + if (dbus_message_get_no_reply(message)) + return NULL; + + reply = dbus_message_new_method_return(message); + if (reply == NULL) + return NULL; + + if (dbus_message_append_args_valist(reply, type, args) == FALSE) { + dbus_message_unref(reply); + return NULL; + } + + return reply; +} + +DBusMessage *g_dbus_create_reply(DBusMessage *message, int type, ...) +{ + va_list args; + DBusMessage *reply; + + va_start(args, type); + + reply = g_dbus_create_reply_valist(message, type, args); + + va_end(args); + + return reply; +} + +static void g_dbus_flush(DBusConnection *connection) +{ + GSList *l; + + for (l = pending; l;) { + struct generic_data *data = l->data; + + l = l->next; + if (data->conn != connection) + continue; + + process_changes(data); + } +} + +gboolean g_dbus_send_message(DBusConnection *connection, DBusMessage *message) +{ + dbus_bool_t result = FALSE; + + if (!message) + return FALSE; + + if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_CALL) + dbus_message_set_no_reply(message, TRUE); + else if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) { + const char *path = dbus_message_get_path(message); + const char *interface = dbus_message_get_interface(message); + const char *name = dbus_message_get_member(message); + const GDBusArgInfo *args; + + if (!check_signal(connection, path, interface, name, &args)) + goto out; + } + + /* Flush pending signal to guarantee message order */ + g_dbus_flush(connection); + + result = dbus_connection_send(connection, message, NULL); + +out: + dbus_message_unref(message); + + return result; +} + +gboolean g_dbus_send_message_with_reply(DBusConnection *connection, + DBusMessage *message, + DBusPendingCall **call, int timeout) +{ + dbus_bool_t ret; + + /* Flush pending signal to guarantee message order */ + g_dbus_flush(connection); + + ret = dbus_connection_send_with_reply(connection, message, call, + timeout); + + if (ret == TRUE && call != NULL && *call == NULL) { + error("Unable to send message (passing fd blocked?)"); + return FALSE; + } + + return ret; +} + +gboolean g_dbus_send_error_valist(DBusConnection *connection, + DBusMessage *message, const char *name, + const char *format, va_list args) +{ + DBusMessage *error; + + error = g_dbus_create_error_valist(message, name, format, args); + if (error == NULL) + return FALSE; + + return g_dbus_send_message(connection, error); +} + +gboolean g_dbus_send_error(DBusConnection *connection, DBusMessage *message, + const char *name, const char *format, ...) +{ + va_list args; + gboolean result; + + va_start(args, format); + + result = g_dbus_send_error_valist(connection, message, name, + format, args); + + va_end(args); + + return result; +} + +gboolean g_dbus_send_reply_valist(DBusConnection *connection, + DBusMessage *message, int type, va_list args) +{ + DBusMessage *reply; + + reply = g_dbus_create_reply_valist(message, type, args); + if (!reply) + return FALSE; + + return g_dbus_send_message(connection, reply); +} + +gboolean g_dbus_send_reply(DBusConnection *connection, + DBusMessage *message, int type, ...) +{ + va_list args; + gboolean result; + + va_start(args, type); + + result = g_dbus_send_reply_valist(connection, message, type, args); + + va_end(args); + + return result; +} + +gboolean g_dbus_emit_signal(DBusConnection *connection, + const char *path, const char *interface, + const char *name, int type, ...) +{ + va_list args; + gboolean result; + + va_start(args, type); + + result = g_dbus_emit_signal_valist(connection, path, interface, + name, type, args); + + va_end(args); + + return result; +} + +gboolean g_dbus_emit_signal_valist(DBusConnection *connection, + const char *path, const char *interface, + const char *name, int type, va_list args) +{ + DBusMessage *signal; + dbus_bool_t ret; + const GDBusArgInfo *args_info; + + if (!check_signal(connection, path, interface, name, &args_info)) + return FALSE; + + signal = dbus_message_new_signal(path, interface, name); + if (signal == NULL) { + error("Unable to allocate new %s.%s signal", interface, name); + return FALSE; + } + + ret = dbus_message_append_args_valist(signal, type, args); + if (!ret) + goto fail; + + if (g_dbus_args_have_signature(args_info, signal) == FALSE) { + error("%s.%s: got unexpected signature '%s'", interface, name, + dbus_message_get_signature(signal)); + ret = FALSE; + goto fail; + } + + return g_dbus_send_message(connection, signal); + +fail: + dbus_message_unref(signal); + + return ret; +} + +static void process_properties_from_interface(struct generic_data *data, + struct interface_data *iface) +{ + GSList *l; + DBusMessage *signal; + DBusMessageIter iter, dict, array; + GSList *invalidated; + + if (iface->pending_prop == NULL) + return; + + signal = dbus_message_new_signal(data->path, + DBUS_INTERFACE_PROPERTIES, "PropertiesChanged"); + if (signal == NULL) { + error("Unable to allocate new " DBUS_INTERFACE_PROPERTIES + ".PropertiesChanged signal"); + return; + } + + iface->pending_prop = g_slist_reverse(iface->pending_prop); + + dbus_message_iter_init_append(signal, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &iface->name); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + invalidated = NULL; + + for (l = iface->pending_prop; l != NULL; l = l->next) { + GDBusPropertyTable *p = l->data; + + if (p->get == NULL) + continue; + + if (p->exists != NULL && !p->exists(p, iface->user_data)) { + invalidated = g_slist_prepend(invalidated, p); + continue; + } + + append_property(iface, p, &dict); + } + + dbus_message_iter_close_container(&iter, &dict); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array); + for (l = invalidated; l != NULL; l = g_slist_next(l)) { + GDBusPropertyTable *p = l->data; + + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, + &p->name); + } + g_slist_free(invalidated); + dbus_message_iter_close_container(&iter, &array); + + g_slist_free(iface->pending_prop); + iface->pending_prop = NULL; + + /* Use dbus_connection_send to avoid recursive calls to g_dbus_flush */ + dbus_connection_send(data->conn, signal, NULL); + dbus_message_unref(signal); +} + +static void process_property_changes(struct generic_data *data) +{ + GSList *l; + + data->pending_prop = FALSE; + + for (l = data->interfaces; l != NULL; l = l->next) { + struct interface_data *iface = l->data; + + process_properties_from_interface(data, iface); + } +} + +void g_dbus_emit_property_changed_full(DBusConnection *connection, + const char *path, const char *interface, + const char *name, + GDbusPropertyChangedFlags flags) +{ + const GDBusPropertyTable *property; + struct generic_data *data; + struct interface_data *iface; + + if (path == NULL) + return; + + if (!dbus_connection_get_object_path_data(connection, path, + (void **) &data) || data == NULL) + return; + + iface = find_interface(data->interfaces, interface); + if (iface == NULL) + return; + + /* + * If ObjectManager is attached, don't emit property changed if + * interface is not yet published + */ + if (root && g_slist_find(data->added, iface)) + return; + + property = find_property(iface->properties, name); + if (property == NULL) { + error("Could not find property %s in %p", name, + iface->properties); + return; + } + + if (g_slist_find(iface->pending_prop, (void *) property) != NULL) + return; + + data->pending_prop = TRUE; + iface->pending_prop = g_slist_prepend(iface->pending_prop, + (void *) property); + + if (flags & G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH) + process_property_changes(data); + else + add_pending(data); +} + +void g_dbus_emit_property_changed(DBusConnection *connection, const char *path, + const char *interface, const char *name) +{ + g_dbus_emit_property_changed_full(connection, path, interface, name, 0); +} + +gboolean g_dbus_get_properties(DBusConnection *connection, const char *path, + const char *interface, DBusMessageIter *iter) +{ + struct generic_data *data; + struct interface_data *iface; + + if (path == NULL) + return FALSE; + + if (!dbus_connection_get_object_path_data(connection, path, + (void **) &data) || data == NULL) + return FALSE; + + iface = find_interface(data->interfaces, interface); + if (iface == NULL) + return FALSE; + + append_properties(iface, iter); + + return TRUE; +} + +gboolean g_dbus_attach_object_manager(DBusConnection *connection) +{ + struct generic_data *data; + + data = object_path_ref(connection, "/"); + if (data == NULL) + return FALSE; + + add_interface(data, DBUS_INTERFACE_OBJECT_MANAGER, + manager_methods, manager_signals, + NULL, data, NULL); + root = data; + + return TRUE; +} + +gboolean g_dbus_detach_object_manager(DBusConnection *connection) +{ + if (!g_dbus_unregister_interface(connection, "/", + DBUS_INTERFACE_OBJECT_MANAGER)) + return FALSE; + + root = NULL; + + return TRUE; +} + +void g_dbus_set_flags(int flags) +{ + global_flags = flags; +} + +int g_dbus_get_flags(void) +{ + return global_flags; +} diff --git a/gdbus/polkit.c b/gdbus/polkit.c new file mode 100644 index 0000000..9e95fa3 --- /dev/null +++ b/gdbus/polkit.c @@ -0,0 +1,202 @@ +/* + * + * D-Bus helper library + * + * Copyright (C) 2004-2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include + +int polkit_check_authorization(DBusConnection *conn, + const char *action, gboolean interaction, + void (*function) (dbus_bool_t authorized, + void *user_data), + void *user_data, int timeout); + +static void add_dict_with_string_value(DBusMessageIter *iter, + const char *key, const char *str) +{ + DBusMessageIter dict, entry, value; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &value); + dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &str); + dbus_message_iter_close_container(&entry, &value); + + dbus_message_iter_close_container(&dict, &entry); + dbus_message_iter_close_container(iter, &dict); +} + +static void add_empty_string_dict(DBusMessageIter *iter) +{ + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + dbus_message_iter_close_container(iter, &dict); +} + +static void add_arguments(DBusConnection *conn, DBusMessageIter *iter, + const char *action, dbus_uint32_t flags) +{ + const char *busname = dbus_bus_get_unique_name(conn); + const char *kind = "system-bus-name"; + const char *cancel = ""; + DBusMessageIter subject; + + dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT, + NULL, &subject); + dbus_message_iter_append_basic(&subject, DBUS_TYPE_STRING, &kind); + add_dict_with_string_value(&subject, "name", busname); + dbus_message_iter_close_container(iter, &subject); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &action); + add_empty_string_dict(iter); + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &flags); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &cancel); +} + +static dbus_bool_t parse_result(DBusMessageIter *iter) +{ + DBusMessageIter result; + dbus_bool_t authorized, challenge; + + dbus_message_iter_recurse(iter, &result); + + dbus_message_iter_get_basic(&result, &authorized); + dbus_message_iter_get_basic(&result, &challenge); + + return authorized; +} + +struct authorization_data { + void (*function) (dbus_bool_t authorized, void *user_data); + void *user_data; +}; + +static void authorization_reply(DBusPendingCall *call, void *user_data) +{ + struct authorization_data *data = user_data; + DBusMessage *reply; + DBusMessageIter iter; + dbus_bool_t authorized = FALSE; + + reply = dbus_pending_call_steal_reply(call); + + if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) + goto done; + + if (dbus_message_has_signature(reply, "(bba{ss})") == FALSE) + goto done; + + dbus_message_iter_init(reply, &iter); + + authorized = parse_result(&iter); + +done: + if (data->function != NULL) + data->function(authorized, data->user_data); + + dbus_message_unref(reply); + + dbus_pending_call_unref(call); +} + +#define AUTHORITY_DBUS "org.freedesktop.PolicyKit1" +#define AUTHORITY_INTF "org.freedesktop.PolicyKit1.Authority" +#define AUTHORITY_PATH "/org/freedesktop/PolicyKit1/Authority" + +int polkit_check_authorization(DBusConnection *conn, + const char *action, gboolean interaction, + void (*function) (dbus_bool_t authorized, + void *user_data), + void *user_data, int timeout) +{ + struct authorization_data *data; + DBusMessage *msg; + DBusMessageIter iter; + DBusPendingCall *call; + dbus_uint32_t flags = 0x00000000; + + if (conn == NULL) + return -EINVAL; + + data = dbus_malloc0(sizeof(*data)); + if (data == NULL) + return -ENOMEM; + + msg = dbus_message_new_method_call(AUTHORITY_DBUS, AUTHORITY_PATH, + AUTHORITY_INTF, "CheckAuthorization"); + if (msg == NULL) { + dbus_free(data); + return -ENOMEM; + } + + if (interaction == TRUE) + flags |= 0x00000001; + + if (action == NULL) + action = "org.freedesktop.policykit.exec"; + + dbus_message_iter_init_append(msg, &iter); + add_arguments(conn, &iter, action, flags); + + if (dbus_connection_send_with_reply(conn, msg, + &call, timeout) == FALSE) { + dbus_message_unref(msg); + dbus_free(data); + return -EIO; + } + + if (call == NULL) { + dbus_message_unref(msg); + dbus_free(data); + return -EIO; + } + + data->function = function; + data->user_data = user_data; + + dbus_pending_call_set_notify(call, authorization_reply, + data, dbus_free); + + dbus_message_unref(msg); + + return 0; +} diff --git a/gdbus/watch.c b/gdbus/watch.c new file mode 100644 index 0000000..447e486 --- /dev/null +++ b/gdbus/watch.c @@ -0,0 +1,818 @@ +/* + * + * D-Bus helper library + * + * Copyright (C) 2004-2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include "gdbus.h" + +#define info(fmt...) +#define error(fmt...) +#define debug(fmt...) + +static DBusHandlerResult message_filter(DBusConnection *connection, + DBusMessage *message, void *user_data); + +static guint listener_id = 0; +static GSList *listeners = NULL; + +struct service_data { + DBusConnection *conn; + DBusPendingCall *call; + char *name; + const char *owner; + guint id; + struct filter_callback *callback; +}; + +struct filter_callback { + GDBusWatchFunction conn_func; + GDBusWatchFunction disc_func; + GDBusSignalFunction signal_func; + GDBusDestroyFunction destroy_func; + struct service_data *data; + void *user_data; + guint id; +}; + +struct filter_data { + DBusConnection *connection; + DBusHandleMessageFunction handle_func; + char *name; + char *owner; + char *path; + char *interface; + char *member; + char *argument; + GSList *callbacks; + GSList *processed; + guint name_watch; + gboolean lock; + gboolean registered; +}; + +static struct filter_data *filter_data_find_match(DBusConnection *connection, + const char *name, + const char *owner, + const char *path, + const char *interface, + const char *member, + const char *argument) +{ + GSList *current; + + for (current = listeners; + current != NULL; current = current->next) { + struct filter_data *data = current->data; + + if (connection != data->connection) + continue; + + if (g_strcmp0(name, data->name) != 0) + continue; + + if (g_strcmp0(owner, data->owner) != 0) + continue; + + if (g_strcmp0(path, data->path) != 0) + continue; + + if (g_strcmp0(interface, data->interface) != 0) + continue; + + if (g_strcmp0(member, data->member) != 0) + continue; + + if (g_strcmp0(argument, data->argument) != 0) + continue; + + return data; + } + + return NULL; +} + +static struct filter_data *filter_data_find(DBusConnection *connection) +{ + GSList *current; + + for (current = listeners; + current != NULL; current = current->next) { + struct filter_data *data = current->data; + + if (connection != data->connection) + continue; + + return data; + } + + return NULL; +} + +static void format_rule(struct filter_data *data, char *rule, size_t size) +{ + const char *sender; + int offset; + + offset = snprintf(rule, size, "type='signal'"); + sender = data->name ? : data->owner; + + if (sender) + offset += snprintf(rule + offset, size - offset, + ",sender='%s'", sender); + if (data->path) + offset += snprintf(rule + offset, size - offset, + ",path='%s'", data->path); + if (data->interface) + offset += snprintf(rule + offset, size - offset, + ",interface='%s'", data->interface); + if (data->member) + offset += snprintf(rule + offset, size - offset, + ",member='%s'", data->member); + if (data->argument) + snprintf(rule + offset, size - offset, + ",arg0='%s'", data->argument); +} + +static gboolean add_match(struct filter_data *data, + DBusHandleMessageFunction filter) +{ + DBusError err; + char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH]; + + format_rule(data, rule, sizeof(rule)); + dbus_error_init(&err); + + dbus_bus_add_match(data->connection, rule, &err); + if (dbus_error_is_set(&err)) { + error("Adding match rule \"%s\" failed: %s", rule, + err.message); + dbus_error_free(&err); + return FALSE; + } + + data->handle_func = filter; + data->registered = TRUE; + + return TRUE; +} + +static gboolean remove_match(struct filter_data *data) +{ + DBusError err; + char rule[DBUS_MAXIMUM_MATCH_RULE_LENGTH]; + + format_rule(data, rule, sizeof(rule)); + + dbus_error_init(&err); + + dbus_bus_remove_match(data->connection, rule, &err); + if (dbus_error_is_set(&err)) { + error("Removing owner match rule for %s failed: %s", + rule, err.message); + dbus_error_free(&err); + return FALSE; + } + + return TRUE; +} + +static void filter_data_free(struct filter_data *data) +{ + GSList *l; + + /* Remove filter if there are no listeners left for the connection */ + if (filter_data_find(data->connection) == NULL) + dbus_connection_remove_filter(data->connection, message_filter, + NULL); + + for (l = data->callbacks; l != NULL; l = l->next) + g_free(l->data); + + g_slist_free(data->callbacks); + g_dbus_remove_watch(data->connection, data->name_watch); + g_free(data->name); + g_free(data->owner); + g_free(data->path); + g_free(data->interface); + g_free(data->member); + g_free(data->argument); + dbus_connection_unref(data->connection); + g_free(data); +} + +static struct filter_data *filter_data_get(DBusConnection *connection, + DBusHandleMessageFunction filter, + const char *sender, + const char *path, + const char *interface, + const char *member, + const char *argument) +{ + struct filter_data *data; + const char *name = NULL, *owner = NULL; + + if (filter_data_find(connection) == NULL) { + if (!dbus_connection_add_filter(connection, + message_filter, NULL, NULL)) { + error("dbus_connection_add_filter() failed"); + return NULL; + } + } + + if (sender == NULL) + goto proceed; + + if (sender[0] == ':') + owner = sender; + else + name = sender; + +proceed: + data = filter_data_find_match(connection, name, owner, path, + interface, member, argument); + if (data) + return data; + + data = g_new0(struct filter_data, 1); + + data->connection = dbus_connection_ref(connection); + data->name = g_strdup(name); + data->owner = g_strdup(owner); + data->path = g_strdup(path); + data->interface = g_strdup(interface); + data->member = g_strdup(member); + data->argument = g_strdup(argument); + + if (!add_match(data, filter)) { + filter_data_free(data); + return NULL; + } + + listeners = g_slist_append(listeners, data); + + return data; +} + +static struct filter_callback *filter_data_find_callback( + struct filter_data *data, + guint id) +{ + GSList *l; + + for (l = data->callbacks; l; l = l->next) { + struct filter_callback *cb = l->data; + if (cb->id == id) + return cb; + } + for (l = data->processed; l; l = l->next) { + struct filter_callback *cb = l->data; + if (cb->id == id) + return cb; + } + + return NULL; +} + +static void filter_data_call_and_free(struct filter_data *data) +{ + GSList *l; + + for (l = data->callbacks; l != NULL; l = l->next) { + struct filter_callback *cb = l->data; + if (cb->disc_func) + cb->disc_func(data->connection, cb->user_data); + if (cb->destroy_func) + cb->destroy_func(cb->user_data); + g_free(cb); + } + + filter_data_free(data); +} + +static struct filter_callback *filter_data_add_callback( + struct filter_data *data, + GDBusWatchFunction connect, + GDBusWatchFunction disconnect, + GDBusSignalFunction signal, + GDBusDestroyFunction destroy, + void *user_data) +{ + struct filter_callback *cb = NULL; + + cb = g_new0(struct filter_callback, 1); + + cb->conn_func = connect; + cb->disc_func = disconnect; + cb->signal_func = signal; + cb->destroy_func = destroy; + cb->user_data = user_data; + cb->id = ++listener_id; + + if (data->lock) + data->processed = g_slist_append(data->processed, cb); + else + data->callbacks = g_slist_append(data->callbacks, cb); + + return cb; +} + +static void service_data_free(struct service_data *data) +{ + struct filter_callback *callback = data->callback; + + dbus_connection_unref(data->conn); + + if (data->call) + dbus_pending_call_unref(data->call); + + if (data->id) + g_source_remove(data->id); + + g_free(data->name); + g_free(data); + + callback->data = NULL; +} + +/* Returns TRUE if data is freed */ +static gboolean filter_data_remove_callback(struct filter_data *data, + struct filter_callback *cb) +{ + data->callbacks = g_slist_remove(data->callbacks, cb); + data->processed = g_slist_remove(data->processed, cb); + + /* Cancel pending operations */ + if (cb->data) { + if (cb->data->call) + dbus_pending_call_cancel(cb->data->call); + service_data_free(cb->data); + } + + if (cb->destroy_func) + cb->destroy_func(cb->user_data); + + g_free(cb); + + /* Don't remove the filter if other callbacks exist or data is lock + * processing callbacks */ + if (data->callbacks || data->lock) + return FALSE; + + if (data->registered && !remove_match(data)) + return FALSE; + + listeners = g_slist_remove(listeners, data); + filter_data_free(data); + + return TRUE; +} + +static DBusHandlerResult signal_filter(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct filter_data *data = user_data; + struct filter_callback *cb; + + while (data->callbacks) { + cb = data->callbacks->data; + + if (cb->signal_func && !cb->signal_func(connection, message, + cb->user_data)) { + if (filter_data_remove_callback(data, cb)) + break; + + continue; + } + + /* Check if the watch was removed/freed by the callback + * function */ + if (!g_slist_find(data->callbacks, cb)) + continue; + + data->callbacks = g_slist_remove(data->callbacks, cb); + data->processed = g_slist_append(data->processed, cb); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static void update_name_cache(const char *name, const char *owner) +{ + GSList *l; + + for (l = listeners; l != NULL; l = l->next) { + struct filter_data *data = l->data; + + if (g_strcmp0(data->name, name) != 0) + continue; + + g_free(data->owner); + data->owner = g_strdup(owner); + } +} + +static const char *check_name_cache(const char *name) +{ + GSList *l; + + for (l = listeners; l != NULL; l = l->next) { + struct filter_data *data = l->data; + + if (g_strcmp0(data->name, name) != 0) + continue; + + return data->owner; + } + + return NULL; +} + +static DBusHandlerResult service_filter(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct filter_data *data = user_data; + struct filter_callback *cb; + char *name, *old, *new; + + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old, + DBUS_TYPE_STRING, &new, + DBUS_TYPE_INVALID)) { + error("Invalid arguments for NameOwnerChanged signal"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + update_name_cache(name, new); + + while (data->callbacks) { + cb = data->callbacks->data; + + if (*new == '\0') { + if (cb->disc_func) + cb->disc_func(connection, cb->user_data); + } else { + if (cb->conn_func) + cb->conn_func(connection, cb->user_data); + } + + /* Check if the watch was removed/freed by the callback + * function */ + if (!g_slist_find(data->callbacks, cb)) + continue; + + /* Only auto remove if it is a bus name watch */ + if (data->argument[0] == ':' && + (cb->conn_func == NULL || cb->disc_func == NULL)) { + if (filter_data_remove_callback(data, cb)) + break; + + continue; + } + + data->callbacks = g_slist_remove(data->callbacks, cb); + data->processed = g_slist_append(data->processed, cb); + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + + +static DBusHandlerResult message_filter(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct filter_data *data; + const char *sender, *path, *iface, *member, *arg = NULL; + GSList *current, *delete_listener = NULL; + + /* Only filter signals */ + if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + sender = dbus_message_get_sender(message); + path = dbus_message_get_path(message); + iface = dbus_message_get_interface(message); + member = dbus_message_get_member(message); + dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID); + + /* If sender != NULL it is always the owner */ + + for (current = listeners; current != NULL; current = current->next) { + data = current->data; + + if (connection != data->connection) + continue; + + if (!sender && data->owner) + continue; + + if (data->owner && g_str_equal(sender, data->owner) == FALSE) + continue; + + if (data->path && g_str_equal(path, data->path) == FALSE) + continue; + + if (data->interface && g_str_equal(iface, + data->interface) == FALSE) + continue; + + if (data->member && g_str_equal(member, data->member) == FALSE) + continue; + + if (data->argument && g_str_equal(arg, + data->argument) == FALSE) + continue; + + if (data->handle_func) { + data->lock = TRUE; + + data->handle_func(connection, message, data); + + data->callbacks = data->processed; + data->processed = NULL; + data->lock = FALSE; + } + + if (!data->callbacks) + delete_listener = g_slist_prepend(delete_listener, + current); + } + + if (delete_listener == NULL) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + for (current = delete_listener; current != NULL; + current = delete_listener->next) { + GSList *l = current->data; + + data = l->data; + + /* Has any other callback added callbacks back to this data? */ + if (data->callbacks != NULL) + continue; + + remove_match(data); + listeners = g_slist_delete_link(listeners, l); + + filter_data_free(data); + } + + g_slist_free(delete_listener); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static gboolean update_service(void *user_data) +{ + struct service_data *data = user_data; + struct filter_callback *cb = data->callback; + DBusConnection *conn; + + conn = dbus_connection_ref(data->conn); + service_data_free(data); + + if (cb->conn_func) + cb->conn_func(conn, cb->user_data); + + dbus_connection_unref(conn); + + return FALSE; +} + +static void service_reply(DBusPendingCall *call, void *user_data) +{ + struct service_data *data = user_data; + DBusMessage *reply; + DBusError err; + + reply = dbus_pending_call_steal_reply(call); + if (reply == NULL) + return; + + dbus_error_init(&err); + + if (dbus_set_error_from_message(&err, reply)) + goto fail; + + if (dbus_message_get_args(reply, &err, + DBUS_TYPE_STRING, &data->owner, + DBUS_TYPE_INVALID) == FALSE) + goto fail; + + update_service(data); + + goto done; + +fail: + error("%s", err.message); + dbus_error_free(&err); + service_data_free(data); +done: + dbus_message_unref(reply); +} + +static void check_service(DBusConnection *connection, + const char *name, + struct filter_callback *callback) +{ + DBusMessage *message; + struct service_data *data; + + data = g_try_malloc0(sizeof(*data)); + if (data == NULL) { + error("Can't allocate data structure"); + return; + } + + data->conn = dbus_connection_ref(connection); + data->name = g_strdup(name); + data->callback = callback; + callback->data = data; + + data->owner = check_name_cache(name); + if (data->owner != NULL) { + data->id = g_idle_add(update_service, data); + return; + } + + message = dbus_message_new_method_call(DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner"); + if (message == NULL) { + error("Can't allocate new message"); + g_free(data); + return; + } + + dbus_message_append_args(message, DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + + if (dbus_connection_send_with_reply(connection, message, + &data->call, -1) == FALSE) { + error("Failed to execute method call"); + g_free(data); + goto done; + } + + if (data->call == NULL) { + error("D-Bus connection not available"); + g_free(data); + goto done; + } + + dbus_pending_call_set_notify(data->call, service_reply, data, NULL); + +done: + dbus_message_unref(message); +} + +guint g_dbus_add_service_watch(DBusConnection *connection, const char *name, + GDBusWatchFunction connect, + GDBusWatchFunction disconnect, + void *user_data, GDBusDestroyFunction destroy) +{ + struct filter_data *data; + struct filter_callback *cb; + + if (name == NULL) + return 0; + + data = filter_data_get(connection, service_filter, + DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, "NameOwnerChanged", + name); + if (data == NULL) + return 0; + + cb = filter_data_add_callback(data, connect, disconnect, NULL, destroy, + user_data); + if (cb == NULL) + return 0; + + if (connect) + check_service(connection, name, cb); + + return cb->id; +} + +guint g_dbus_add_disconnect_watch(DBusConnection *connection, const char *name, + GDBusWatchFunction func, + void *user_data, GDBusDestroyFunction destroy) +{ + return g_dbus_add_service_watch(connection, name, NULL, func, + user_data, destroy); +} + +guint g_dbus_add_signal_watch(DBusConnection *connection, + const char *sender, const char *path, + const char *interface, const char *member, + GDBusSignalFunction function, void *user_data, + GDBusDestroyFunction destroy) +{ + struct filter_data *data; + struct filter_callback *cb; + + data = filter_data_get(connection, signal_filter, sender, path, + interface, member, NULL); + if (data == NULL) + return 0; + + cb = filter_data_add_callback(data, NULL, NULL, function, destroy, + user_data); + if (cb == NULL) + return 0; + + if (data->name != NULL && data->name_watch == 0) + data->name_watch = g_dbus_add_service_watch(connection, + data->name, NULL, + NULL, NULL, NULL); + + return cb->id; +} + +guint g_dbus_add_properties_watch(DBusConnection *connection, + const char *sender, const char *path, + const char *interface, + GDBusSignalFunction function, void *user_data, + GDBusDestroyFunction destroy) +{ + struct filter_data *data; + struct filter_callback *cb; + + data = filter_data_get(connection, signal_filter, sender, path, + DBUS_INTERFACE_PROPERTIES, "PropertiesChanged", + interface); + if (data == NULL) + return 0; + + cb = filter_data_add_callback(data, NULL, NULL, function, destroy, + user_data); + if (cb == NULL) + return 0; + + if (data->name != NULL && data->name_watch == 0) + data->name_watch = g_dbus_add_service_watch(connection, + data->name, NULL, + NULL, NULL, NULL); + + return cb->id; +} + +gboolean g_dbus_remove_watch(DBusConnection *connection, guint id) +{ + struct filter_data *data; + struct filter_callback *cb; + GSList *ldata; + + if (id == 0) + return FALSE; + + for (ldata = listeners; ldata; ldata = ldata->next) { + data = ldata->data; + + cb = filter_data_find_callback(data, id); + if (cb) { + filter_data_remove_callback(data, cb); + return TRUE; + } + } + + return FALSE; +} + +void g_dbus_remove_all_watches(DBusConnection *connection) +{ + struct filter_data *data; + + while ((data = filter_data_find(connection))) { + listeners = g_slist_remove(listeners, data); + filter_data_call_and_free(data); + } +} diff --git a/gobex/gobex-apparam.c b/gobex/gobex-apparam.c new file mode 100644 index 0000000..b16cee1 --- /dev/null +++ b/gobex/gobex-apparam.c @@ -0,0 +1,368 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2012 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 +#endif + +#include +#include +#include + +#include "gobex-apparam.h" +#include "gobex-debug.h" + +struct _GObexApparam { + GHashTable *tags; +}; + +struct apparam_tag { + guint8 id; + guint8 len; + union { + /* Data is stored in network order */ + char string[0]; + guint8 data[0]; + guint8 u8; + guint16 u16; + guint32 u32; + guint64 u64; + } value; +} __attribute__ ((packed)); + +static struct apparam_tag *tag_new(guint8 id, guint8 len, const void *data) +{ + struct apparam_tag *tag; + + tag = g_malloc0(2 + len); + tag->id = id; + tag->len = len; + memcpy(tag->value.data, data, len); + + return tag; +} + +static GObexApparam *g_obex_apparam_new(void) +{ + GObexApparam *apparam; + + apparam = g_new0(GObexApparam, 1); + apparam->tags = g_hash_table_new_full(g_direct_hash, g_direct_equal, + NULL, g_free); + + return apparam; +} + +static struct apparam_tag *apparam_tag_decode(const void *data, gsize size, + gsize *parsed) +{ + struct apparam_tag *tag; + const guint8 *ptr = data; + guint8 id; + guint8 len; + + if (size < 2) + return NULL; + + id = ptr[0]; + len = ptr[1]; + + if (len > size - 2) + return NULL; + + tag = tag_new(id, len, ptr + 2); + if (tag == NULL) + return NULL; + + *parsed = 2 + tag->len; + + return tag; +} + +GObexApparam *g_obex_apparam_decode(const void *data, gsize size) +{ + GObexApparam *apparam; + GHashTable *tags; + gsize count = 0; + + if (size < 2) + return NULL; + + apparam = g_obex_apparam_new(); + + tags = apparam->tags; + while (count < size) { + struct apparam_tag *tag; + gsize parsed; + guint id; + + tag = apparam_tag_decode(data + count, size - count, &parsed); + if (tag == NULL) + break; + + id = tag->id; + g_hash_table_insert(tags, GUINT_TO_POINTER(id), tag); + + count += parsed; + } + + if (count != size) { + g_obex_apparam_free(apparam); + return NULL; + } + + return apparam; +} + +static gssize tag_encode(struct apparam_tag *tag, void *buf, gsize len) +{ + gsize count = 2 + tag->len; + + if (len < count) + return -ENOBUFS; + + memcpy(buf, tag, count); + + return count; +} + +gssize g_obex_apparam_encode(GObexApparam *apparam, void *buf, gsize len) +{ + gsize count = 0; + gssize ret; + GHashTableIter iter; + gpointer key, value; + + if (!apparam) + return 0; + + g_hash_table_iter_init(&iter, apparam->tags); + while (g_hash_table_iter_next(&iter, &key, &value)) { + struct apparam_tag *tag = value; + + ret = tag_encode(tag, buf + count, len - count); + if (ret < 0) + return ret; + + count += ret; + } + + return count; +} + +GObexApparam *g_obex_apparam_set_bytes(GObexApparam *apparam, guint8 id, + const void *value, gsize len) +{ + struct apparam_tag *tag; + guint uid = id; + + if (apparam == NULL) + apparam = g_obex_apparam_new(); + + tag = tag_new(id, len, value); + g_hash_table_replace(apparam->tags, GUINT_TO_POINTER(uid), tag); + + return apparam; +} + +GObexApparam *g_obex_apparam_set_uint8(GObexApparam *apparam, guint8 id, + guint8 value) +{ + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x value %u", id, value); + + return g_obex_apparam_set_bytes(apparam, id, &value, 1); +} + +GObexApparam *g_obex_apparam_set_uint16(GObexApparam *apparam, guint8 id, + guint16 value) +{ + guint16 num = g_htons(value); + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x value %u", id, value); + + return g_obex_apparam_set_bytes(apparam, id, &num, 2); +} + +GObexApparam *g_obex_apparam_set_uint32(GObexApparam *apparam, guint8 id, + guint32 value) +{ + guint32 num = g_htonl(value); + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x value %u", id, value); + + return g_obex_apparam_set_bytes(apparam, id, &num, 4); +} + +GObexApparam *g_obex_apparam_set_uint64(GObexApparam *apparam, guint8 id, + guint64 value) +{ + guint64 num = GUINT64_TO_BE(value); + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x value %" + G_GUINT64_FORMAT, id, value); + + return g_obex_apparam_set_bytes(apparam, id, &num, 8); +} + +GObexApparam *g_obex_apparam_set_string(GObexApparam *apparam, guint8 id, + const char *value) +{ + gsize len; + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x value %s", id, value); + + len = strlen(value) + 1; + if (len > G_MAXUINT8) { + ((char *) value)[G_MAXUINT8 - 1] = '\0'; + len = G_MAXUINT8; + } + + return g_obex_apparam_set_bytes(apparam, id, value, len); +} + +static struct apparam_tag *g_obex_apparam_find_tag(GObexApparam *apparam, + guint id) +{ + return g_hash_table_lookup(apparam->tags, GUINT_TO_POINTER(id)); +} + +gboolean g_obex_apparam_get_uint8(GObexApparam *apparam, guint8 id, + guint8 *dest) +{ + struct apparam_tag *tag; + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x", id); + + tag = g_obex_apparam_find_tag(apparam, id); + if (tag == NULL) + return FALSE; + + *dest = tag->value.u8; + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "%u", *dest); + + return TRUE; +} + +gboolean g_obex_apparam_get_uint16(GObexApparam *apparam, guint8 id, + guint16 *dest) +{ + struct apparam_tag *tag; + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x", id); + + tag = g_obex_apparam_find_tag(apparam, id); + if (tag == NULL) + return FALSE; + + if (tag->len < sizeof(*dest)) + return FALSE; + + *dest = g_ntohs(tag->value.u16); + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "%u", *dest); + + return TRUE; +} + +gboolean g_obex_apparam_get_uint32(GObexApparam *apparam, guint8 id, + guint32 *dest) +{ + struct apparam_tag *tag; + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x", id); + + tag = g_obex_apparam_find_tag(apparam, id); + if (tag == NULL) + return FALSE; + + if (tag->len < sizeof(*dest)) + return FALSE; + + *dest = g_ntohl(tag->value.u32); + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "%u", *dest); + + return TRUE; +} + +gboolean g_obex_apparam_get_uint64(GObexApparam *apparam, guint8 id, + guint64 *dest) +{ + struct apparam_tag *tag; + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x", id); + + tag = g_obex_apparam_find_tag(apparam, id); + if (tag == NULL) + return FALSE; + + if (tag->len < sizeof(*dest)) + return FALSE; + + *dest = GUINT64_FROM_BE(tag->value.u64); + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "%" G_GUINT64_FORMAT, *dest); + + return TRUE; +} + +char *g_obex_apparam_get_string(GObexApparam *apparam, guint8 id) +{ + struct apparam_tag *tag; + char *string; + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x", id); + + tag = g_obex_apparam_find_tag(apparam, id); + if (tag == NULL) + return NULL; + + string = g_strndup(tag->value.string, tag->len); + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "%s", string); + + return string; +} + +gboolean g_obex_apparam_get_bytes(GObexApparam *apparam, guint8 id, + const guint8 **val, gsize *len) +{ + struct apparam_tag *tag; + + g_obex_debug(G_OBEX_DEBUG_APPARAM, "tag 0x%02x", id); + + tag = g_obex_apparam_find_tag(apparam, id); + if (tag == NULL) + return FALSE; + + *len = tag->len; + *val = tag->value.data; + + return TRUE; +} + +void g_obex_apparam_free(GObexApparam *apparam) +{ + g_hash_table_unref(apparam->tags); + g_free(apparam); +} diff --git a/gobex/gobex-apparam.h b/gobex/gobex-apparam.h new file mode 100644 index 0000000..6c08609 --- /dev/null +++ b/gobex/gobex-apparam.h @@ -0,0 +1,60 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2012 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 + * + */ + +#ifndef __GOBEX_APPARAM_H +#define __GOBEX_APPARAM_H + +#include + +typedef struct _GObexApparam GObexApparam; + +GObexApparam *g_obex_apparam_decode(const void *data, gsize size); +gssize g_obex_apparam_encode(GObexApparam *apparam, void *buf, gsize size); + +GObexApparam *g_obex_apparam_set_bytes(GObexApparam *apparam, guint8 id, + const void *value, gsize size); +GObexApparam *g_obex_apparam_set_uint8(GObexApparam *apparam, guint8 id, + guint8 value); +GObexApparam *g_obex_apparam_set_uint16(GObexApparam *apparam, guint8 id, + guint16 value); +GObexApparam *g_obex_apparam_set_uint32(GObexApparam *apparam, guint8 id, + guint32 value); +GObexApparam *g_obex_apparam_set_uint64(GObexApparam *apparam, guint8 id, + guint64 value); +GObexApparam *g_obex_apparam_set_string(GObexApparam *apparam, guint8 id, + const char *value); + +gboolean g_obex_apparam_get_bytes(GObexApparam *apparam, guint8 id, + const guint8 **val, gsize *len); +gboolean g_obex_apparam_get_uint8(GObexApparam *apparam, guint8 id, + guint8 *value); +gboolean g_obex_apparam_get_uint16(GObexApparam *apparam, guint8 id, + guint16 *value); +gboolean g_obex_apparam_get_uint32(GObexApparam *apparam, guint8 id, + guint32 *value); +gboolean g_obex_apparam_get_uint64(GObexApparam *apparam, guint8 id, + guint64 *value); +char *g_obex_apparam_get_string(GObexApparam *apparam, guint8 id); + +void g_obex_apparam_free(GObexApparam *apparam); + +#endif /* __GOBEX_APPARAM_H */ diff --git a/gobex/gobex-debug.h b/gobex/gobex-debug.h new file mode 100644 index 0000000..a98653d --- /dev/null +++ b/gobex/gobex-debug.h @@ -0,0 +1,78 @@ +/* + * OBEX library with GLib integration + * + * Copyright (C) 2011 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 + * + */ + +#ifndef __GOBEX_DEBUG_H +#define __GOBEX_DEBUG_H + +#include +#include +#include + +#define G_OBEX_DEBUG_NONE 1 +#define G_OBEX_DEBUG_ERROR (1 << 1) +#define G_OBEX_DEBUG_COMMAND (1 << 2) +#define G_OBEX_DEBUG_TRANSFER (1 << 3) +#define G_OBEX_DEBUG_HEADER (1 << 4) +#define G_OBEX_DEBUG_PACKET (1 << 5) +#define G_OBEX_DEBUG_DATA (1 << 6) +#define G_OBEX_DEBUG_APPARAM (1 << 7) + +extern guint gobex_debug; + +#define g_obex_debug(level, format, ...) \ + if (gobex_debug & level) \ + g_log("gobex", G_LOG_LEVEL_DEBUG, "%s:%s() " format, __FILE__, \ + __func__, ## __VA_ARGS__) + +static inline void g_obex_dump(guint level, const char *prefix, + const void *buf, gsize len) +{ + const guint8 *data = buf; + int n = 0; + + if (!(gobex_debug & level)) + return; + + while (len > 0) { + int i, size; + + printf("%s %04x:", prefix, n); + + size = len > 16 ? 16 : len; + + for (i = 0; i < size; i++) + printf("%02x%s", data[i], (i + 1) % 8 ? " " : " "); + + for (; i < 16; i++) + printf(" %s", (i + 1) % 8 ? " " : " "); + + for (i = 0; i < size; i++) + printf("%1c", isprint(data[i]) ? data[i] : '.'); + + printf("\n"); + + data += size; + len -= size; + n += size; + } +} + +#endif /* __GOBEX_DEBUG_H */ diff --git a/gobex/gobex-defs.c b/gobex/gobex-defs.c new file mode 100644 index 0000000..1c7c39a --- /dev/null +++ b/gobex/gobex-defs.c @@ -0,0 +1,34 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2011 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 +#endif + +#include + +#include "gobex-defs.h" + +GQuark g_obex_error_quark(void) +{ + return g_quark_from_static_string("g-obex-error-quark"); +} diff --git a/gobex/gobex-defs.h b/gobex/gobex-defs.h new file mode 100644 index 0000000..326e3cb --- /dev/null +++ b/gobex/gobex-defs.h @@ -0,0 +1,53 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2011 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 + * + */ + +#ifndef __GOBEX_DEFS_H +#define __GOBEX_DEFS_H + +#include + +typedef enum { + G_OBEX_DATA_INHERIT, + G_OBEX_DATA_COPY, + G_OBEX_DATA_REF, +} GObexDataPolicy; + +#define G_OBEX_ERROR_FIRST (0xff + 1) +#define G_OBEX_PROTO_ERROR(code) ((code) < G_OBEX_ERROR_FIRST) + +typedef enum { + G_OBEX_ERROR_PARSE_ERROR = G_OBEX_ERROR_FIRST, + G_OBEX_ERROR_INVALID_ARGS, + G_OBEX_ERROR_DISCONNECTED, + G_OBEX_ERROR_TIMEOUT, + G_OBEX_ERROR_CANCELLED, + G_OBEX_ERROR_FAILED, +} GObexError; + +typedef gssize (*GObexDataProducer) (void *buf, gsize len, gpointer user_data); +typedef gboolean (*GObexDataConsumer) (const void *buf, gsize len, + gpointer user_data); + +#define G_OBEX_ERROR g_obex_error_quark() +GQuark g_obex_error_quark(void); + +#endif /* __GOBEX_DEFS_H */ diff --git a/gobex/gobex-header.c b/gobex/gobex-header.c new file mode 100644 index 0000000..c594999 --- /dev/null +++ b/gobex/gobex-header.c @@ -0,0 +1,551 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2011 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 +#endif + +#include + +#include "gobex-header.h" +#include "gobex-debug.h" + +/* Header types */ +#define G_OBEX_HDR_ENC_UNICODE (0 << 6) +#define G_OBEX_HDR_ENC_BYTES (1 << 6) +#define G_OBEX_HDR_ENC_UINT8 (2 << 6) +#define G_OBEX_HDR_ENC_UINT32 (3 << 6) + +#define G_OBEX_HDR_ENC(id) ((id) & 0xc0) + +struct _GObexHeader { + guint8 id; + gboolean extdata; + gsize vlen; /* Length of value */ + gsize hlen; /* Length of full encoded header */ + union { + char *string; /* UTF-8 converted from UTF-16 */ + guint8 *data; /* Own buffer */ + const guint8 *extdata; /* Reference to external buffer */ + guint8 u8; + guint32 u32; + } v; +}; + +static glong utf8_to_utf16(gunichar2 **utf16, const char *utf8) { + glong utf16_len; + int i; + + if (*utf8 == '\0') { + *utf16 = NULL; + return 0; + } + + *utf16 = g_utf8_to_utf16(utf8, -1, NULL, &utf16_len, NULL); + if (*utf16 == NULL) + return -1; + + /* g_utf8_to_utf16 produces host-byteorder UTF-16, + * but OBEX requires network byteorder (big endian) */ + for (i = 0; i < utf16_len; i++) + (*utf16)[i] = g_htons((*utf16)[i]); + + utf16_len = (utf16_len + 1) << 1; + + return utf16_len; +} + +static guint8 *put_bytes(guint8 *to, const void *from, gsize count) +{ + memcpy(to, from, count); + return (to + count); +} + +static const guint8 *get_bytes(void *to, const guint8 *from, gsize count) +{ + memcpy(to, from, count); + return (from + count); +} + +gssize g_obex_header_encode(GObexHeader *header, void *buf, gsize buf_len) +{ + guint8 *ptr = buf; + guint16 u16; + guint32 u32; + gunichar2 *utf16; + glong utf16_len; + + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", + G_OBEX_HDR_ENC(header->id)); + + if (buf_len < header->hlen) + return -1; + + ptr = put_bytes(ptr, &header->id, sizeof(header->id)); + + switch (G_OBEX_HDR_ENC(header->id)) { + case G_OBEX_HDR_ENC_UNICODE: + utf16_len = utf8_to_utf16(&utf16, header->v.string); + if (utf16_len < 0 || (guint16) utf16_len > buf_len) + return -1; + g_assert_cmpuint(utf16_len + 3, ==, header->hlen); + u16 = g_htons(utf16_len + 3); + ptr = put_bytes(ptr, &u16, sizeof(u16)); + put_bytes(ptr, utf16, utf16_len); + g_free(utf16); + break; + case G_OBEX_HDR_ENC_BYTES: + u16 = g_htons(header->hlen); + ptr = put_bytes(ptr, &u16, sizeof(u16)); + if (header->extdata) + put_bytes(ptr, header->v.extdata, header->vlen); + else + put_bytes(ptr, header->v.data, header->vlen); + break; + case G_OBEX_HDR_ENC_UINT8: + *ptr = header->v.u8; + break; + case G_OBEX_HDR_ENC_UINT32: + u32 = g_htonl(header->v.u32); + put_bytes(ptr, &u32, sizeof(u32)); + break; + default: + g_assert_not_reached(); + } + + return header->hlen; +} + +GObexHeader *g_obex_header_decode(const void *data, gsize len, + GObexDataPolicy data_policy, gsize *parsed, + GError **err) +{ + GObexHeader *header; + const guint8 *ptr = data; + guint16 hdr_len; + gsize str_len; + GError *conv_err = NULL; + + if (len < 2) { + if (!err) + return NULL; + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, + "Too short header in packet"); + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); + return NULL; + } + + header = g_new0(GObexHeader, 1); + + ptr = get_bytes(&header->id, ptr, sizeof(header->id)); + + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", + G_OBEX_HDR_ENC(header->id)); + + switch (G_OBEX_HDR_ENC(header->id)) { + case G_OBEX_HDR_ENC_UNICODE: + if (len < 3) { + g_set_error(err, G_OBEX_ERROR, + G_OBEX_ERROR_PARSE_ERROR, + "Not enough data for unicode header (0x%02x)", + header->id); + goto failed; + } + ptr = get_bytes(&hdr_len, ptr, sizeof(hdr_len)); + hdr_len = g_ntohs(hdr_len); + + if (hdr_len == 3) { + header->v.string = g_strdup(""); + header->vlen = 0; + header->hlen = hdr_len; + *parsed = hdr_len; + break; + } + + if (hdr_len > len || hdr_len < 5) { + g_set_error(err, G_OBEX_ERROR, + G_OBEX_ERROR_PARSE_ERROR, + "Invalid unicode header (0x%02x) length (%u)", + header->id, hdr_len); + goto failed; + } + + header->v.string = g_convert((const char *) ptr, hdr_len - 5, + "UTF-8", "UTF-16BE", + NULL, &str_len, &conv_err); + if (header->v.string == NULL) { + g_set_error(err, G_OBEX_ERROR, + G_OBEX_ERROR_PARSE_ERROR, + "Unicode conversion failed: %s", + conv_err->message); + g_error_free(conv_err); + goto failed; + } + + header->vlen = (gsize) str_len; + header->hlen = hdr_len; + + *parsed = hdr_len; + + break; + case G_OBEX_HDR_ENC_BYTES: + if (len < 3) { + g_set_error(err, G_OBEX_ERROR, + G_OBEX_ERROR_PARSE_ERROR, + "Too short byte array header"); + goto failed; + } + ptr = get_bytes(&hdr_len, ptr, sizeof(hdr_len)); + hdr_len = g_ntohs(hdr_len); + if (hdr_len > len) { + g_set_error(err, G_OBEX_ERROR, + G_OBEX_ERROR_PARSE_ERROR, + "Too long byte array header"); + goto failed; + } + + if (hdr_len < 3) { + g_set_error(err, G_OBEX_ERROR, + G_OBEX_ERROR_PARSE_ERROR, + "Too small byte array length"); + goto failed; + } + + header->vlen = hdr_len - 3; + header->hlen = hdr_len; + + switch (data_policy) { + case G_OBEX_DATA_COPY: + header->v.data = g_memdup(ptr, header->vlen); + break; + case G_OBEX_DATA_REF: + header->extdata = TRUE; + header->v.extdata = ptr; + break; + case G_OBEX_DATA_INHERIT: + default: + g_set_error(err, G_OBEX_ERROR, + G_OBEX_ERROR_INVALID_ARGS, + "Invalid data policy"); + goto failed; + } + + *parsed = hdr_len; + + break; + case G_OBEX_HDR_ENC_UINT8: + header->vlen = 1; + header->hlen = 2; + header->v.u8 = *ptr; + *parsed = 2; + break; + case G_OBEX_HDR_ENC_UINT32: + if (len < 5) { + g_set_error(err, G_OBEX_ERROR, + G_OBEX_ERROR_PARSE_ERROR, + "Too short uint32 header"); + goto failed; + } + header->vlen = 4; + header->hlen = 5; + get_bytes(&header->v.u32, ptr, sizeof(header->v.u32)); + header->v.u32 = g_ntohl(header->v.u32); + *parsed = 5; + break; + default: + g_assert_not_reached(); + } + + return header; + +failed: + if (*err) + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); + g_obex_header_free(header); + return NULL; +} + +void g_obex_header_free(GObexHeader *header) +{ + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", + G_OBEX_HDR_ENC(header->id)); + + switch (G_OBEX_HDR_ENC(header->id)) { + case G_OBEX_HDR_ENC_UNICODE: + g_free(header->v.string); + break; + case G_OBEX_HDR_ENC_BYTES: + if (!header->extdata) + g_free(header->v.data); + break; + case G_OBEX_HDR_ENC_UINT8: + case G_OBEX_HDR_ENC_UINT32: + break; + default: + g_assert_not_reached(); + } + + g_free(header); +} + +gboolean g_obex_header_get_unicode(GObexHeader *header, const char **str) +{ + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", + G_OBEX_HDR_ENC(header->id)); + + if (G_OBEX_HDR_ENC(header->id) != G_OBEX_HDR_ENC_UNICODE) + return FALSE; + + *str = header->v.string; + + g_obex_debug(G_OBEX_DEBUG_HEADER, "%s", *str); + + return TRUE; +} + +gboolean g_obex_header_get_bytes(GObexHeader *header, const guint8 **val, + gsize *len) +{ + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", + G_OBEX_HDR_ENC(header->id)); + + if (G_OBEX_HDR_ENC(header->id) != G_OBEX_HDR_ENC_BYTES) + return FALSE; + + *len = header->vlen; + + if (header->extdata) + *val = header->v.extdata; + else + *val = header->v.data; + + return TRUE; +} + +GObexApparam *g_obex_header_get_apparam(GObexHeader *header) +{ + gboolean ret; + const guint8 *val; + gsize len; + + ret = g_obex_header_get_bytes(header, &val, &len); + if (!ret) + return NULL; + + return g_obex_apparam_decode(val, len); +} + +gboolean g_obex_header_get_uint8(GObexHeader *header, guint8 *val) +{ + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", + G_OBEX_HDR_ENC(header->id)); + + if (G_OBEX_HDR_ENC(header->id) != G_OBEX_HDR_ENC_UINT8) + return FALSE; + + *val = header->v.u8; + + g_obex_debug(G_OBEX_DEBUG_HEADER, "%u", *val); + + return TRUE; +} + +gboolean g_obex_header_get_uint32(GObexHeader *header, guint32 *val) +{ + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", + G_OBEX_HDR_ENC(header->id)); + + if (G_OBEX_HDR_ENC(header->id) != G_OBEX_HDR_ENC_UINT32) + return FALSE; + + *val = header->v.u32; + + g_obex_debug(G_OBEX_DEBUG_HEADER, "%u", *val); + + return TRUE; +} + +GObexHeader *g_obex_header_new_unicode(guint8 id, const char *str) +{ + GObexHeader *header; + gsize len; + + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", G_OBEX_HDR_ENC(id)); + + if (G_OBEX_HDR_ENC(id) != G_OBEX_HDR_ENC_UNICODE) + return NULL; + + header = g_new0(GObexHeader, 1); + + header->id = id; + + len = g_utf8_strlen(str, -1); + + header->vlen = len; + header->hlen = len == 0 ? 3 : 3 + ((len + 1) * 2); + header->v.string = g_strdup(str); + + g_obex_debug(G_OBEX_DEBUG_HEADER, "%s", header->v.string); + + return header; +} + +GObexHeader *g_obex_header_new_bytes(guint8 id, const void *data, gsize len) +{ + GObexHeader *header; + + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", G_OBEX_HDR_ENC(id)); + + if (G_OBEX_HDR_ENC(id) != G_OBEX_HDR_ENC_BYTES) + return NULL; + + header = g_new0(GObexHeader, 1); + + header->id = id; + header->vlen = len; + header->hlen = len + 3; + header->v.data = g_memdup(data, len); + + return header; +} + +GObexHeader *g_obex_header_new_tag(guint8 id, GObexApparam *apparam) +{ + guint8 buf[1024]; + gssize len; + + len = g_obex_apparam_encode(apparam, buf, sizeof(buf)); + if (len < 0) + return NULL; + + return g_obex_header_new_bytes(id, buf, len); +} + +GObexHeader *g_obex_header_new_apparam(GObexApparam *apparam) +{ + return g_obex_header_new_tag(G_OBEX_HDR_APPARAM, apparam); +} + +GObexHeader *g_obex_header_new_uint8(guint8 id, guint8 val) +{ + GObexHeader *header; + + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", G_OBEX_HDR_ENC(id)); + + if (G_OBEX_HDR_ENC(id) != G_OBEX_HDR_ENC_UINT8) + return NULL; + + header = g_new0(GObexHeader, 1); + + header->id = id; + header->vlen = 1; + header->hlen = 2; + header->v.u8 = val; + + g_obex_debug(G_OBEX_DEBUG_HEADER, "%u", header->v.u8); + + return header; +} + +GObexHeader *g_obex_header_new_uint32(guint8 id, guint32 val) +{ + GObexHeader *header; + + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", G_OBEX_HDR_ENC(id)); + + if (G_OBEX_HDR_ENC(id) != G_OBEX_HDR_ENC_UINT32) + return NULL; + + header = g_new0(GObexHeader, 1); + + header->id = id; + header->vlen = 4; + header->hlen = 5; + header->v.u32 = val; + + g_obex_debug(G_OBEX_DEBUG_HEADER, "%u", header->v.u32); + + return header; +} + +guint8 g_obex_header_get_id(GObexHeader *header) +{ + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x id 0x%02x", + G_OBEX_HDR_ENC(header->id), header->id); + + return header->id; +} + +guint16 g_obex_header_get_length(GObexHeader *header) +{ + g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x length %zu", + G_OBEX_HDR_ENC(header->id), header->hlen); + + return header->hlen; +} + +GSList *g_obex_header_create_list(guint8 first_hdr_id, va_list args, + gsize *total_len) +{ + unsigned int id = first_hdr_id; + GSList *l = NULL; + + g_obex_debug(G_OBEX_DEBUG_HEADER, ""); + + *total_len = 0; + + while (id != G_OBEX_HDR_INVALID) { + GObexHeader *hdr; + const char *str; + const void *bytes; + unsigned int val; + gsize len; + + switch (G_OBEX_HDR_ENC(id)) { + case G_OBEX_HDR_ENC_UNICODE: + str = va_arg(args, const char *); + hdr = g_obex_header_new_unicode(id, str); + break; + case G_OBEX_HDR_ENC_BYTES: + bytes = va_arg(args, void *); + len = va_arg(args, gsize); + hdr = g_obex_header_new_bytes(id, bytes, len); + break; + case G_OBEX_HDR_ENC_UINT8: + val = va_arg(args, unsigned int); + hdr = g_obex_header_new_uint8(id, val); + break; + case G_OBEX_HDR_ENC_UINT32: + val = va_arg(args, unsigned int); + hdr = g_obex_header_new_uint32(id, val); + break; + default: + g_assert_not_reached(); + } + + l = g_slist_append(l, hdr); + *total_len += hdr->hlen; + id = va_arg(args, int); + } + + return l; +} diff --git a/gobex/gobex-header.h b/gobex/gobex-header.h new file mode 100644 index 0000000..6600b1b --- /dev/null +++ b/gobex/gobex-header.h @@ -0,0 +1,103 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2011 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 + * + */ + +#ifndef __GOBEX_HEADER_H +#define __GOBEX_HEADER_H + +#include + +#include "gobex/gobex-defs.h" +#include "gobex/gobex-apparam.h" + +/* Header ID's */ +#define G_OBEX_HDR_INVALID 0x00 + +#define G_OBEX_HDR_COUNT 0xc0 +#define G_OBEX_HDR_NAME 0x01 +#define G_OBEX_HDR_TYPE 0x42 +#define G_OBEX_HDR_LENGTH 0xc3 +#define G_OBEX_HDR_TIME 0x44 +#define G_OBEX_HDR_DESCRIPTION 0x05 +#define G_OBEX_HDR_TARGET 0x46 +#define G_OBEX_HDR_HTTP 0x47 +#define G_OBEX_HDR_BODY 0x48 +#define G_OBEX_HDR_BODY_END 0x49 +#define G_OBEX_HDR_WHO 0x4a +#define G_OBEX_HDR_CONNECTION 0xcb +#define G_OBEX_HDR_APPARAM 0x4c +#define G_OBEX_HDR_AUTHCHAL 0x4d +#define G_OBEX_HDR_AUTHRESP 0x4e +#define G_OBEX_HDR_CREATOR 0xcf +#define G_OBEX_HDR_WANUUID 0x50 +#define G_OBEX_HDR_OBJECTCLASS 0x51 +#define G_OBEX_HDR_SESSIONPARAM 0x52 +#define G_OBEX_HDR_SESSIONSEQ 0x93 +#define G_OBEX_HDR_ACTION 0x94 +#define G_OBEX_HDR_DESTNAME 0x15 +#define G_OBEX_HDR_PERMISSIONS 0xd6 +#define G_OBEX_HDR_SRM 0x97 +#define G_OBEX_HDR_SRMP 0x98 + +/* Action header values */ +#define G_OBEX_ACTION_COPY 0x00 +#define G_OBEX_ACTION_MOVE 0x01 +#define G_OBEX_ACTION_SETPERM 0x02 + +/* SRM header values */ +#define G_OBEX_SRM_DISABLE 0x00 +#define G_OBEX_SRM_ENABLE 0x01 +#define G_OBEX_SRM_INDICATE 0x02 + +/* SRMP header values */ +#define G_OBEX_SRMP_NEXT 0x00 +#define G_OBEX_SRMP_WAIT 0x01 +#define G_OBEX_SRMP_NEXT_WAIT 0x02 + +typedef struct _GObexHeader GObexHeader; + +gboolean g_obex_header_get_unicode(GObexHeader *header, const char **str); +gboolean g_obex_header_get_bytes(GObexHeader *header, const guint8 **val, + gsize *len); +gboolean g_obex_header_get_uint8(GObexHeader *header, guint8 *val); +gboolean g_obex_header_get_uint32(GObexHeader *header, guint32 *val); +GObexApparam *g_obex_header_get_apparam(GObexHeader *header); + +GObexHeader *g_obex_header_new_unicode(guint8 id, const char *str); +GObexHeader *g_obex_header_new_bytes(guint8 id, const void *data, gsize len); +GObexHeader *g_obex_header_new_uint8(guint8 id, guint8 val); +GObexHeader *g_obex_header_new_uint32(guint8 id, guint32 val); +GObexHeader *g_obex_header_new_tag(guint8 id, GObexApparam *apparam); +GObexHeader *g_obex_header_new_apparam(GObexApparam *apparam); + +GSList *g_obex_header_create_list(guint8 first_hdr_id, va_list args, + gsize *total_len); + +guint8 g_obex_header_get_id(GObexHeader *header); +guint16 g_obex_header_get_length(GObexHeader *header); + +gssize g_obex_header_encode(GObexHeader *header, void *buf, gsize buf_len); +GObexHeader *g_obex_header_decode(const void *data, gsize len, + GObexDataPolicy data_policy, gsize *parsed, + GError **err); +void g_obex_header_free(GObexHeader *header); + +#endif /* __GOBEX_HEADER_H */ diff --git a/gobex/gobex-packet.c b/gobex/gobex-packet.c new file mode 100644 index 0000000..a89f5b6 --- /dev/null +++ b/gobex/gobex-packet.c @@ -0,0 +1,467 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2011 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 +#endif + +#include +#include + +#include "gobex-defs.h" +#include "gobex-packet.h" +#include "gobex-debug.h" + +#define FINAL_BIT 0x80 + +struct _GObexPacket { + guint8 opcode; + gboolean final; + + GObexDataPolicy data_policy; + + union { + void *buf; /* Non-header data */ + const void *buf_ref; /* Reference to non-header data */ + } data; + gsize data_len; + + gsize hlen; /* Length of all encoded headers */ + GSList *headers; + + GObexDataProducer get_body; + gpointer get_body_data; +}; + +GObexHeader *g_obex_packet_get_header(GObexPacket *pkt, guint8 id) +{ + GSList *l; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + for (l = pkt->headers; l != NULL; l = g_slist_next(l)) { + GObexHeader *hdr = l->data; + + if (g_obex_header_get_id(hdr) == id) + return hdr; + } + + return NULL; +} + +GObexHeader *g_obex_packet_get_body(GObexPacket *pkt) +{ + GObexHeader *body; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + body = g_obex_packet_get_header(pkt, G_OBEX_HDR_BODY); + if (body != NULL) + return body; + + return g_obex_packet_get_header(pkt, G_OBEX_HDR_BODY_END); +} + +guint8 g_obex_packet_get_operation(GObexPacket *pkt, gboolean *final) +{ + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + if (final) + *final = pkt->final; + + return pkt->opcode; +} + +gboolean g_obex_packet_prepend_header(GObexPacket *pkt, GObexHeader *header) +{ + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + pkt->headers = g_slist_prepend(pkt->headers, header); + pkt->hlen += g_obex_header_get_length(header); + + return TRUE; +} + +gboolean g_obex_packet_add_header(GObexPacket *pkt, GObexHeader *header) +{ + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + pkt->headers = g_slist_append(pkt->headers, header); + pkt->hlen += g_obex_header_get_length(header); + + return TRUE; +} + +gboolean g_obex_packet_add_body(GObexPacket *pkt, GObexDataProducer func, + gpointer user_data) +{ + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + if (pkt->get_body != NULL) + return FALSE; + + pkt->get_body = func; + pkt->get_body_data = user_data; + + return TRUE; +} + +gboolean g_obex_packet_add_unicode(GObexPacket *pkt, guint8 id, + const char *str) +{ + GObexHeader *hdr; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + hdr = g_obex_header_new_unicode(id, str); + if (hdr == NULL) + return FALSE; + + return g_obex_packet_add_header(pkt, hdr); +} + +gboolean g_obex_packet_add_bytes(GObexPacket *pkt, guint8 id, + const void *data, gsize len) +{ + GObexHeader *hdr; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + hdr = g_obex_header_new_bytes(id, data, len); + if (hdr == NULL) + return FALSE; + + return g_obex_packet_add_header(pkt, hdr); +} + +gboolean g_obex_packet_add_uint8(GObexPacket *pkt, guint8 id, guint8 val) +{ + GObexHeader *hdr; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + hdr = g_obex_header_new_uint8(id, val); + if (hdr == NULL) + return FALSE; + + return g_obex_packet_add_header(pkt, hdr); +} + +gboolean g_obex_packet_add_uint32(GObexPacket *pkt, guint8 id, guint32 val) +{ + GObexHeader *hdr; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + hdr = g_obex_header_new_uint32(id, val); + if (hdr == NULL) + return FALSE; + + return g_obex_packet_add_header(pkt, hdr); +} + +const void *g_obex_packet_get_data(GObexPacket *pkt, gsize *len) +{ + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + if (pkt->data_len == 0) { + *len = 0; + return NULL; + } + + *len = pkt->data_len; + + switch (pkt->data_policy) { + case G_OBEX_DATA_INHERIT: + case G_OBEX_DATA_COPY: + return pkt->data.buf; + case G_OBEX_DATA_REF: + return pkt->data.buf_ref; + } + + g_assert_not_reached(); +} + +gboolean g_obex_packet_set_data(GObexPacket *pkt, const void *data, gsize len, + GObexDataPolicy data_policy) +{ + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + if (pkt->data.buf || pkt->data.buf_ref) + return FALSE; + + pkt->data_policy = data_policy; + pkt->data_len = len; + + switch (data_policy) { + case G_OBEX_DATA_COPY: + pkt->data.buf = g_memdup(data, len); + break; + case G_OBEX_DATA_REF: + pkt->data.buf_ref = data; + break; + case G_OBEX_DATA_INHERIT: + pkt->data.buf = (void *) data; + break; + } + + return TRUE; +} + +GObexPacket *g_obex_packet_new_valist(guint8 opcode, gboolean final, + guint first_hdr_id, va_list args) +{ + GObexPacket *pkt; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", opcode); + + pkt = g_new0(GObexPacket, 1); + + pkt->opcode = opcode; + pkt->final = final; + pkt->headers = g_obex_header_create_list(first_hdr_id, args, + &pkt->hlen); + pkt->data_policy = G_OBEX_DATA_COPY; + + return pkt; +} + +GObexPacket *g_obex_packet_new(guint8 opcode, gboolean final, + guint first_hdr_id, ...) +{ + GObexPacket *pkt; + va_list args; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", opcode); + + va_start(args, first_hdr_id); + pkt = g_obex_packet_new_valist(opcode, final, first_hdr_id, args); + va_end(args); + + return pkt; +} + +static void header_free(void *data, void *user_data) +{ + g_obex_header_free(data); +} + +void g_obex_packet_free(GObexPacket *pkt) +{ + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + switch (pkt->data_policy) { + case G_OBEX_DATA_INHERIT: + case G_OBEX_DATA_COPY: + g_free(pkt->data.buf); + break; + case G_OBEX_DATA_REF: + break; + } + + g_slist_foreach(pkt->headers, header_free, NULL); + g_slist_free(pkt->headers); + g_free(pkt); +} + +static gboolean parse_headers(GObexPacket *pkt, const void *data, gsize len, + GObexDataPolicy data_policy, + GError **err) +{ + const guint8 *buf = data; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + while (len > 0) { + GObexHeader *header; + gsize parsed; + + header = g_obex_header_decode(buf, len, data_policy, &parsed, + err); + if (header == NULL) + return FALSE; + + pkt->headers = g_slist_append(pkt->headers, header); + pkt->hlen += parsed; + + len -= parsed; + buf += parsed; + } + + return TRUE; +} + +static const guint8 *get_bytes(void *to, const guint8 *from, gsize count) +{ + memcpy(to, from, count); + return (from + count); +} + +GObexPacket *g_obex_packet_decode(const void *data, gsize len, + gsize header_offset, + GObexDataPolicy data_policy, + GError **err) +{ + const guint8 *buf = data; + guint16 packet_len; + guint8 opcode; + GObexPacket *pkt; + gboolean final; + + g_obex_debug(G_OBEX_DEBUG_PACKET, ""); + + if (data_policy == G_OBEX_DATA_INHERIT) { + if (!err) + return NULL; + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_INVALID_ARGS, + "Invalid data policy"); + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); + return NULL; + } + + if (len < 3 + header_offset) { + if (!err) + return NULL; + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, + "Not enough data to decode packet"); + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); + return NULL; + } + + buf = get_bytes(&opcode, buf, sizeof(opcode)); + buf = get_bytes(&packet_len, buf, sizeof(packet_len)); + + packet_len = g_ntohs(packet_len); + if (packet_len != len) { + if (!err) + return NULL; + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, + "Incorrect packet length (%u != %zu)", + packet_len, len); + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); + return NULL; + } + + final = (opcode & FINAL_BIT) ? TRUE : FALSE; + opcode &= ~FINAL_BIT; + + pkt = g_obex_packet_new(opcode, final, G_OBEX_HDR_INVALID); + + if (header_offset == 0) + goto headers; + + g_obex_packet_set_data(pkt, buf, header_offset, data_policy); + buf += header_offset; + +headers: + if (!parse_headers(pkt, buf, len - (3 + header_offset), + data_policy, err)) + goto failed; + + return pkt; + +failed: + g_obex_packet_free(pkt); + return NULL; +} + +static gssize get_body(GObexPacket *pkt, guint8 *buf, gsize len) +{ + guint16 u16; + gssize ret; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + if (len < 3) + return -ENOBUFS; + + ret = pkt->get_body(buf + 3, len - 3, pkt->get_body_data); + if (ret < 0) + return ret; + + if (ret > 0) + buf[0] = G_OBEX_HDR_BODY; + else + buf[0] = G_OBEX_HDR_BODY_END; + + u16 = g_htons(ret + 3); + memcpy(&buf[1], &u16, sizeof(u16)); + + return ret; +} + +gssize g_obex_packet_encode(GObexPacket *pkt, guint8 *buf, gsize len) +{ + gssize ret; + gsize count; + guint16 u16; + GSList *l; + + g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); + + if (3 + pkt->data_len + pkt->hlen > len) + return -ENOBUFS; + + buf[0] = pkt->opcode; + if (pkt->final) + buf[0] |= FINAL_BIT; + + if (pkt->data_len > 0) { + if (pkt->data_policy == G_OBEX_DATA_REF) + memcpy(&buf[3], pkt->data.buf_ref, pkt->data_len); + else + memcpy(&buf[3], pkt->data.buf, pkt->data_len); + } + + count = 3 + pkt->data_len; + + for (l = pkt->headers; l != NULL; l = g_slist_next(l)) { + GObexHeader *hdr = l->data; + + if (count >= len) + return -ENOBUFS; + + ret = g_obex_header_encode(hdr, buf + count, len - count); + if (ret < 0) + return ret; + + count += ret; + } + + if (pkt->get_body) { + ret = get_body(pkt, buf + count, len - count); + if (ret < 0) + return ret; + if (ret == 0) { + if (pkt->opcode == G_OBEX_RSP_CONTINUE) + buf[0] = G_OBEX_RSP_SUCCESS; + buf[0] |= FINAL_BIT; + } + + count += ret + 3; + } + + u16 = g_htons(count); + memcpy(&buf[1], &u16, sizeof(u16)); + + return count; +} diff --git a/gobex/gobex-packet.h b/gobex/gobex-packet.h new file mode 100644 index 0000000..1d94ccf --- /dev/null +++ b/gobex/gobex-packet.h @@ -0,0 +1,112 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2011 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 + * + */ + +#ifndef __GOBEX_PACKET_H +#define __GOBEX_PACKET_H + +#include +#include + +#include "gobex/gobex-defs.h" +#include "gobex/gobex-header.h" + +/* Request opcodes */ +#define G_OBEX_OP_CONNECT 0x00 +#define G_OBEX_OP_DISCONNECT 0x01 +#define G_OBEX_OP_PUT 0x02 +#define G_OBEX_OP_GET 0x03 +#define G_OBEX_OP_SETPATH 0x05 +#define G_OBEX_OP_ACTION 0x06 +#define G_OBEX_OP_SESSION 0x07 +#define G_OBEX_OP_ABORT 0x7f + +/* Response codes */ +#define G_OBEX_RSP_CONTINUE 0x10 +#define G_OBEX_RSP_SUCCESS 0x20 +#define G_OBEX_RSP_CREATED 0x21 +#define G_OBEX_RSP_ACCEPTED 0x22 +#define G_OBEX_RSP_NON_AUTHORITATIVE 0x23 +#define G_OBEX_RSP_NO_CONTENT 0x24 +#define G_OBEX_RSP_RESET_CONTENT 0x25 +#define G_OBEX_RSP_PARTIAL_CONTENT 0x26 +#define G_OBEX_RSP_MULTIPLE_CHOICES 0x30 +#define G_OBEX_RSP_MOVED_PERMANENTLY 0x31 +#define G_OBEX_RSP_MOVED_TEMPORARILY 0x32 +#define G_OBEX_RSP_SEE_OTHER 0x33 +#define G_OBEX_RSP_NOT_MODIFIED 0x34 +#define G_OBEX_RSP_USE_PROXY 0x35 +#define G_OBEX_RSP_BAD_REQUEST 0x40 +#define G_OBEX_RSP_UNAUTHORIZED 0x41 +#define G_OBEX_RSP_PAYMENT_REQUIRED 0x42 +#define G_OBEX_RSP_FORBIDDEN 0x43 +#define G_OBEX_RSP_NOT_FOUND 0x44 +#define G_OBEX_RSP_METHOD_NOT_ALLOWED 0x45 +#define G_OBEX_RSP_NOT_ACCEPTABLE 0x46 +#define G_OBEX_RSP_PROXY_AUTH_REQUIRED 0x47 +#define G_OBEX_RSP_REQUEST_TIME_OUT 0x48 +#define G_OBEX_RSP_CONFLICT 0x49 +#define G_OBEX_RSP_GONE 0x4a +#define G_OBEX_RSP_LENGTH_REQUIRED 0x4b +#define G_OBEX_RSP_PRECONDITION_FAILED 0x4c +#define G_OBEX_RSP_REQ_ENTITY_TOO_LARGE 0x4d +#define G_OBEX_RSP_REQ_URL_TOO_LARGE 0x4e +#define G_OBEX_RSP_UNSUPPORTED_MEDIA_TYPE 0x4f +#define G_OBEX_RSP_INTERNAL_SERVER_ERROR 0x50 +#define G_OBEX_RSP_NOT_IMPLEMENTED 0x51 +#define G_OBEX_RSP_BAD_GATEWAY 0x52 +#define G_OBEX_RSP_SERVICE_UNAVAILABLE 0x53 +#define G_OBEX_RSP_GATEWAY_TIMEOUT 0x54 +#define G_OBEX_RSP_VERSION_NOT_SUPPORTED 0x55 +#define G_OBEX_RSP_DATABASE_FULL 0x60 +#define G_OBEX_RSP_DATABASE_LOCKED 0x61 + +typedef struct _GObexPacket GObexPacket; + +GObexHeader *g_obex_packet_get_header(GObexPacket *pkt, guint8 id); +GObexHeader *g_obex_packet_get_body(GObexPacket *pkt); +guint8 g_obex_packet_get_operation(GObexPacket *pkt, gboolean *final); +gboolean g_obex_packet_prepend_header(GObexPacket *pkt, GObexHeader *header); +gboolean g_obex_packet_add_header(GObexPacket *pkt, GObexHeader *header); +gboolean g_obex_packet_add_body(GObexPacket *pkt, GObexDataProducer func, + gpointer user_data); +gboolean g_obex_packet_add_unicode(GObexPacket *pkt, guint8 id, + const char *str); +gboolean g_obex_packet_add_bytes(GObexPacket *pkt, guint8 id, + const void *data, gsize len); +gboolean g_obex_packet_add_uint8(GObexPacket *pkt, guint8 id, guint8 val); +gboolean g_obex_packet_add_uint32(GObexPacket *pkt, guint8 id, guint32 val); +gboolean g_obex_packet_set_data(GObexPacket *pkt, const void *data, gsize len, + GObexDataPolicy data_policy); +const void *g_obex_packet_get_data(GObexPacket *pkt, gsize *len); +GObexPacket *g_obex_packet_new(guint8 opcode, gboolean final, + guint first_hdr_id, ...); +GObexPacket *g_obex_packet_new_valist(guint8 opcode, gboolean final, + guint first_hdr_id, va_list args); +void g_obex_packet_free(GObexPacket *pkt); + +GObexPacket *g_obex_packet_decode(const void *data, gsize len, + gsize header_offset, + GObexDataPolicy data_policy, + GError **err); +gssize g_obex_packet_encode(GObexPacket *pkt, guint8 *buf, gsize len); + +#endif /* __GOBEX_PACKET_H */ diff --git a/gobex/gobex-transfer.c b/gobex/gobex-transfer.c new file mode 100644 index 0000000..bc99306 --- /dev/null +++ b/gobex/gobex-transfer.c @@ -0,0 +1,670 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2011 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 +#endif + +#include +#include + +#include "gobex/gobex.h" +#include "gobex/gobex-debug.h" + +#define FIRST_PACKET_TIMEOUT 60 + +static GSList *transfers = NULL; + +static void transfer_response(GObex *obex, GError *err, GObexPacket *rsp, + gpointer user_data); + +struct transfer { + guint id; + guint8 opcode; + + GObex *obex; + + guint req_id; + + guint put_id; + guint get_id; + guint abort_id; + + GObexDataProducer data_producer; + GObexDataConsumer data_consumer; + GObexFunc complete_func; + + gpointer user_data; +}; + +static void transfer_free(struct transfer *transfer) +{ + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + transfers = g_slist_remove(transfers, transfer); + + if (transfer->req_id > 0) + g_obex_cancel_req(transfer->obex, transfer->req_id, TRUE); + + if (transfer->put_id > 0) + g_obex_remove_request_function(transfer->obex, + transfer->put_id); + + if (transfer->get_id > 0) + g_obex_remove_request_function(transfer->obex, + transfer->get_id); + + if (transfer->abort_id > 0) + g_obex_remove_request_function(transfer->obex, + transfer->abort_id); + + g_obex_unref(transfer->obex); + g_free(transfer); +} + +static struct transfer *find_transfer(guint id) +{ + GSList *l; + + for (l = transfers; l != NULL; l = g_slist_next(l)) { + struct transfer *t = l->data; + if (t->id == id) + return t; + } + + return NULL; +} + +static void transfer_complete(struct transfer *transfer, GError *err) +{ + guint id = transfer->id; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", id); + + transfer->complete_func(transfer->obex, err, transfer->user_data); + /* Check if the complete_func removed the transfer */ + if (find_transfer(id) == NULL) + return; + + transfer_free(transfer); +} + +static void transfer_abort_response(GObex *obex, GError *err, GObexPacket *rsp, + gpointer user_data) +{ + struct transfer *transfer = user_data; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + transfer->req_id = 0; + + /* Intentionally override error */ + err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED, + "Operation was aborted"); + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message); + transfer_complete(transfer, err); + g_error_free(err); +} + + +static gssize put_get_data(void *buf, gsize len, gpointer user_data) +{ + struct transfer *transfer = user_data; + GObexPacket *req; + GError *err = NULL; + gssize ret; + + ret = transfer->data_producer(buf, len, transfer->user_data); + if (ret == 0 || ret == -EAGAIN) + return ret; + + if (ret > 0) { + /* Check if SRM is active */ + if (!g_obex_srm_active(transfer->obex)) + return ret; + + /* Generate next packet */ + req = g_obex_packet_new(transfer->opcode, FALSE, + G_OBEX_HDR_INVALID); + g_obex_packet_add_body(req, put_get_data, transfer); + transfer->req_id = g_obex_send_req(transfer->obex, req, -1, + transfer_response, transfer, + &err); + goto done; + } + + transfer->req_id = g_obex_abort(transfer->obex, transfer_abort_response, + transfer, &err); +done: + if (err != NULL) { + transfer_complete(transfer, err); + g_error_free(err); + } + + return ret; +} + +static gboolean handle_get_body(struct transfer *transfer, GObexPacket *rsp, + GError **err) +{ + GObexHeader *body = g_obex_packet_get_body(rsp); + gboolean ret; + const guint8 *buf; + gsize len; + + if (body == NULL) + return TRUE; + + g_obex_header_get_bytes(body, &buf, &len); + if (len == 0) + return TRUE; + + ret = transfer->data_consumer(buf, len, transfer->user_data); + if (ret == FALSE) + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED, + "Data consumer callback failed"); + + return ret; +} + +static void transfer_response(GObex *obex, GError *err, GObexPacket *rsp, + gpointer user_data) +{ + struct transfer *transfer = user_data; + GObexPacket *req; + gboolean rspcode, final; + guint id; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + id = transfer->req_id; + transfer->req_id = 0; + + if (err != NULL) { + transfer_complete(transfer, err); + return; + } + + rspcode = g_obex_packet_get_operation(rsp, &final); + if (rspcode != G_OBEX_RSP_SUCCESS && rspcode != G_OBEX_RSP_CONTINUE) { + err = g_error_new(G_OBEX_ERROR, rspcode, "%s", + g_obex_strerror(rspcode)); + goto failed; + } + + if (transfer->opcode == G_OBEX_OP_GET) { + handle_get_body(transfer, rsp, &err); + if (err != NULL) + goto failed; + } + + if (rspcode == G_OBEX_RSP_SUCCESS) { + transfer_complete(transfer, NULL); + return; + } + + if (transfer->opcode == G_OBEX_OP_PUT) { + req = g_obex_packet_new(transfer->opcode, FALSE, + G_OBEX_HDR_INVALID); + g_obex_packet_add_body(req, put_get_data, transfer); + } else if (!g_obex_srm_active(transfer->obex)) { + req = g_obex_packet_new(transfer->opcode, TRUE, + G_OBEX_HDR_INVALID); + } else { + /* Keep id since request still outstanting */ + transfer->req_id = id; + return; + } + + transfer->req_id = g_obex_send_req(obex, req, -1, transfer_response, + transfer, &err); +failed: + if (err != NULL) { + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message); + transfer_complete(transfer, err); + g_error_free(err); + } +} + +static struct transfer *transfer_new(GObex *obex, guint8 opcode, + GObexFunc complete_func, gpointer user_data) +{ + static guint next_id = 1; + struct transfer *transfer; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p opcode %u", obex, opcode); + + transfer = g_new0(struct transfer, 1); + + transfer->id = next_id++; + transfer->opcode = opcode; + transfer->obex = g_obex_ref(obex); + transfer->complete_func = complete_func; + transfer->user_data = user_data; + + transfers = g_slist_append(transfers, transfer); + + return transfer; +} + +guint g_obex_put_req_pkt(GObex *obex, GObexPacket *req, + GObexDataProducer data_func, GObexFunc complete_func, + gpointer user_data, GError **err) +{ + struct transfer *transfer; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); + + if (g_obex_packet_get_operation(req, NULL) != G_OBEX_OP_PUT) + return 0; + + transfer = transfer_new(obex, G_OBEX_OP_PUT, complete_func, user_data); + transfer->data_producer = data_func; + + g_obex_packet_add_body(req, put_get_data, transfer); + + transfer->req_id = g_obex_send_req(obex, req, FIRST_PACKET_TIMEOUT, + transfer_response, transfer, err); + if (transfer->req_id == 0) { + transfer_free(transfer); + return 0; + } + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + return transfer->id; +} + +guint g_obex_put_req(GObex *obex, GObexDataProducer data_func, + GObexFunc complete_func, gpointer user_data, + GError **err, guint first_hdr_id, ...) +{ + GObexPacket *req; + va_list args; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); + + va_start(args, first_hdr_id); + req = g_obex_packet_new_valist(G_OBEX_OP_PUT, FALSE, + first_hdr_id, args); + va_end(args); + + return g_obex_put_req_pkt(obex, req, data_func, complete_func, + user_data, err); +} + +static void transfer_abort_req(GObex *obex, GObexPacket *req, gpointer user_data) +{ + struct transfer *transfer = user_data; + GObexPacket *rsp; + GError *err; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED, + "Request was aborted"); + rsp = g_obex_packet_new(G_OBEX_RSP_SUCCESS, TRUE, G_OBEX_HDR_INVALID); + g_obex_send(obex, rsp, NULL); + + transfer_complete(transfer, err); + g_error_free(err); +} + +static guint8 put_get_bytes(struct transfer *transfer, GObexPacket *req) +{ + GObexHeader *body; + gboolean final; + guint8 rsp; + const guint8 *buf; + gsize len; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + g_obex_packet_get_operation(req, &final); + if (final) + rsp = G_OBEX_RSP_SUCCESS; + else + rsp = G_OBEX_RSP_CONTINUE; + + body = g_obex_packet_get_body(req); + if (body == NULL) + return rsp; + + g_obex_header_get_bytes(body, &buf, &len); + if (len == 0) + return rsp; + + if (transfer->data_consumer(buf, len, transfer->user_data) == FALSE) + rsp = G_OBEX_RSP_FORBIDDEN; + + return rsp; +} + +static void transfer_put_req_first(struct transfer *transfer, GObexPacket *req, + guint8 first_hdr_id, va_list args) +{ + GError *err = NULL; + GObexPacket *rsp; + guint8 rspcode; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + rspcode = put_get_bytes(transfer, req); + + rsp = g_obex_packet_new_valist(rspcode, TRUE, first_hdr_id, args); + + if (!g_obex_send(transfer->obex, rsp, &err)) { + transfer_complete(transfer, err); + g_error_free(err); + return; + } + + if (rspcode != G_OBEX_RSP_CONTINUE) + transfer_complete(transfer, NULL); +} + +static void transfer_put_req(GObex *obex, GObexPacket *req, gpointer user_data) +{ + struct transfer *transfer = user_data; + GError *err = NULL; + GObexPacket *rsp; + guint8 rspcode; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + rspcode = put_get_bytes(transfer, req); + + /* Don't send continue while SRM is active */ + if (g_obex_srm_active(transfer->obex) && + rspcode == G_OBEX_RSP_CONTINUE) + goto done; + + rsp = g_obex_packet_new(rspcode, TRUE, G_OBEX_HDR_INVALID); + + if (!g_obex_send(obex, rsp, &err)) { + transfer_complete(transfer, err); + g_error_free(err); + return; + } + +done: + if (rspcode != G_OBEX_RSP_CONTINUE) + transfer_complete(transfer, NULL); +} + +guint g_obex_put_rsp(GObex *obex, GObexPacket *req, + GObexDataConsumer data_func, GObexFunc complete_func, + gpointer user_data, GError **err, + guint first_hdr_id, ...) +{ + struct transfer *transfer; + va_list args; + guint id; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); + + transfer = transfer_new(obex, G_OBEX_OP_PUT, complete_func, user_data); + transfer->data_consumer = data_func; + + va_start(args, first_hdr_id); + transfer_put_req_first(transfer, req, first_hdr_id, args); + va_end(args); + if (!g_slist_find(transfers, transfer)) + return 0; + + id = g_obex_add_request_function(obex, G_OBEX_OP_PUT, transfer_put_req, + transfer); + transfer->put_id = id; + + id = g_obex_add_request_function(obex, G_OBEX_OP_ABORT, + transfer_abort_req, transfer); + transfer->abort_id = id; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + return transfer->id; +} + +guint g_obex_get_req_pkt(GObex *obex, GObexPacket *req, + GObexDataConsumer data_func, GObexFunc complete_func, + gpointer user_data, GError **err) +{ + struct transfer *transfer; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); + + if (g_obex_packet_get_operation(req, NULL) != G_OBEX_OP_GET) + return 0; + + transfer = transfer_new(obex, G_OBEX_OP_GET, complete_func, user_data); + transfer->data_consumer = data_func; + transfer->req_id = g_obex_send_req(obex, req, FIRST_PACKET_TIMEOUT, + transfer_response, transfer, err); + if (transfer->req_id == 0) { + transfer_free(transfer); + return 0; + } + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + return transfer->id; +} + +guint g_obex_get_req(GObex *obex, GObexDataConsumer data_func, + GObexFunc complete_func, gpointer user_data, + GError **err, guint first_hdr_id, ...) +{ + struct transfer *transfer; + GObexPacket *req; + va_list args; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); + + transfer = transfer_new(obex, G_OBEX_OP_GET, complete_func, user_data); + transfer->data_consumer = data_func; + + va_start(args, first_hdr_id); + req = g_obex_packet_new_valist(G_OBEX_OP_GET, TRUE, + first_hdr_id, args); + va_end(args); + + transfer->req_id = g_obex_send_req(obex, req, FIRST_PACKET_TIMEOUT, + transfer_response, transfer, err); + if (transfer->req_id == 0) { + transfer_free(transfer); + return 0; + } + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + return transfer->id; +} + +static gssize get_get_data(void *buf, gsize len, gpointer user_data) +{ + struct transfer *transfer = user_data; + GObexPacket *req, *rsp; + GError *err = NULL; + gssize ret; + guint8 op; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + ret = transfer->data_producer(buf, len, transfer->user_data); + if (ret > 0) { + if (!g_obex_srm_active(transfer->obex)) + return ret; + + /* Generate next response */ + rsp = g_obex_packet_new(G_OBEX_RSP_CONTINUE, TRUE, + G_OBEX_HDR_INVALID); + g_obex_packet_add_body(rsp, get_get_data, transfer); + + if (!g_obex_send(transfer->obex, rsp, &err)) { + transfer_complete(transfer, err); + g_error_free(err); + } + + return ret; + } + + if (ret == -EAGAIN) + return ret; + + if (ret == 0) { + transfer_complete(transfer, NULL); + return ret; + } + + op = g_obex_errno_to_rsp(ret); + + req = g_obex_packet_new(op, TRUE, G_OBEX_HDR_INVALID); + g_obex_send(transfer->obex, req, NULL); + + err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED, + "Data producer function failed"); + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message); + transfer_complete(transfer, err); + g_error_free(err); + + return ret; +} + +static gboolean transfer_get_req_first(struct transfer *transfer, + GObexPacket *rsp) +{ + GError *err = NULL; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + g_obex_packet_add_body(rsp, get_get_data, transfer); + + if (!g_obex_send(transfer->obex, rsp, &err)) { + transfer_complete(transfer, err); + g_error_free(err); + return FALSE; + } + + return TRUE; +} + +static void transfer_get_req(GObex *obex, GObexPacket *req, gpointer user_data) +{ + struct transfer *transfer = user_data; + GError *err = NULL; + GObexPacket *rsp; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + rsp = g_obex_packet_new(G_OBEX_RSP_CONTINUE, TRUE, G_OBEX_HDR_INVALID); + g_obex_packet_add_body(rsp, get_get_data, transfer); + + if (!g_obex_send(obex, rsp, &err)) { + transfer_complete(transfer, err); + g_error_free(err); + } +} + +guint g_obex_get_rsp_pkt(GObex *obex, GObexPacket *rsp, + GObexDataProducer data_func, GObexFunc complete_func, + gpointer user_data, GError **err) +{ + struct transfer *transfer; + guint id; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); + + transfer = transfer_new(obex, G_OBEX_OP_GET, complete_func, user_data); + transfer->data_producer = data_func; + + if (!transfer_get_req_first(transfer, rsp)) + return 0; + + if (!g_slist_find(transfers, transfer)) + return 0; + + id = g_obex_add_request_function(obex, G_OBEX_OP_GET, transfer_get_req, + transfer); + transfer->get_id = id; + + id = g_obex_add_request_function(obex, G_OBEX_OP_ABORT, + transfer_abort_req, transfer); + transfer->abort_id = id; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", transfer->id); + + return transfer->id; +} + +guint g_obex_get_rsp(GObex *obex, GObexDataProducer data_func, + GObexFunc complete_func, gpointer user_data, + GError **err, guint first_hdr_id, ...) +{ + GObexPacket *rsp; + va_list args; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "obex %p", obex); + + va_start(args, first_hdr_id); + rsp = g_obex_packet_new_valist(G_OBEX_RSP_CONTINUE, TRUE, + first_hdr_id, args); + va_end(args); + + return g_obex_get_rsp_pkt(obex, rsp, data_func, complete_func, + user_data, err); +} + +gboolean g_obex_cancel_transfer(guint id, GObexFunc complete_func, + gpointer user_data) +{ + struct transfer *transfer = NULL; + gboolean ret = TRUE; + + g_obex_debug(G_OBEX_DEBUG_TRANSFER, "transfer %u", id); + + transfer = find_transfer(id); + + if (transfer == NULL) + return FALSE; + + if (complete_func == NULL) + goto done; + + transfer->complete_func = complete_func; + transfer->user_data = user_data; + + if (!transfer->req_id) { + transfer->req_id = g_obex_abort(transfer->obex, + transfer_abort_response, + transfer, NULL); + if (transfer->req_id) + return TRUE; + } + + ret = g_obex_cancel_req(transfer->obex, transfer->req_id, FALSE); + if (ret) + return TRUE; + +done: + transfer_free(transfer); + return ret; +} diff --git a/gobex/gobex.c b/gobex/gobex.c new file mode 100644 index 0000000..77f1aaa --- /dev/null +++ b/gobex/gobex.c @@ -0,0 +1,1717 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2011 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 +#endif + +#include +#include +#include + +#include "gobex.h" +#include "gobex-debug.h" + +#define G_OBEX_DEFAULT_MTU 4096 +#define G_OBEX_MINIMUM_MTU 255 +#define G_OBEX_MAXIMUM_MTU 65535 + +#define G_OBEX_DEFAULT_TIMEOUT 10 +#define G_OBEX_ABORT_TIMEOUT 5 + +#define G_OBEX_OP_NONE 0xff + +#define FINAL_BIT 0x80 + +#define CONNID_INVALID 0xffffffff + +/* Challenge request */ +#define NONCE_TAG 0x00 +#define NONCE_LEN 16 + +/* Challenge response */ +#define DIGEST_TAG 0x00 + +guint gobex_debug = 0; + +struct srm_config { + guint8 op; + gboolean enabled; + guint8 srm; + guint8 srmp; + gboolean outgoing; +}; + +struct _GObex { + int ref_count; + GIOChannel *io; + guint io_source; + + gboolean (*read) (GObex *obex, GError **err); + gboolean (*write) (GObex *obex, GError **err); + + guint8 *rx_buf; + size_t rx_data; + guint16 rx_pkt_len; + guint8 rx_last_op; + + guint8 *tx_buf; + size_t tx_data; + size_t tx_sent; + + gboolean suspended; + gboolean use_srm; + + struct srm_config *srm; + + guint write_source; + + gssize io_rx_mtu; + gssize io_tx_mtu; + + guint16 rx_mtu; + guint16 tx_mtu; + + guint32 conn_id; + GObexApparam *authchal; + + GQueue *tx_queue; + + GSList *req_handlers; + + GObexFunc disconn_func; + gpointer disconn_func_data; + + struct pending_pkt *pending_req; +}; + +struct pending_pkt { + guint id; + GObex *obex; + GObexPacket *pkt; + guint timeout; + guint timeout_id; + GObexResponseFunc rsp_func; + gpointer rsp_data; + gboolean cancelled; + gboolean suspended; + gboolean authenticating; +}; + +struct req_handler { + guint id; + guint8 opcode; + GObexRequestFunc func; + gpointer user_data; +}; + +struct connect_data { + guint8 version; + guint8 flags; + guint16 mtu; +} __attribute__ ((packed)); + +struct setpath_data { + guint8 flags; + guint8 constants; +} __attribute__ ((packed)); + +static struct error_code { + guint8 code; + const char *name; +} obex_errors[] = { + { G_OBEX_RSP_CONTINUE, "Continue" }, + { G_OBEX_RSP_SUCCESS, "Success" }, + { G_OBEX_RSP_CREATED, "Created" }, + { G_OBEX_RSP_ACCEPTED, "Accepted" }, + { G_OBEX_RSP_NON_AUTHORITATIVE, "Non Authoritative" }, + { G_OBEX_RSP_NO_CONTENT, "No Content" }, + { G_OBEX_RSP_RESET_CONTENT, "Reset Content" }, + { G_OBEX_RSP_PARTIAL_CONTENT, "Partial Content" }, + { G_OBEX_RSP_MULTIPLE_CHOICES, "Multiple Choices" }, + { G_OBEX_RSP_MOVED_PERMANENTLY, "Moved Permanently" }, + { G_OBEX_RSP_MOVED_TEMPORARILY, "Moved Temporarily" }, + { G_OBEX_RSP_SEE_OTHER, "See Other" }, + { G_OBEX_RSP_NOT_MODIFIED, "Not Modified" }, + { G_OBEX_RSP_USE_PROXY, "Use Proxy" }, + { G_OBEX_RSP_BAD_REQUEST, "Bad Request" }, + { G_OBEX_RSP_UNAUTHORIZED, "Unauthorized" }, + { G_OBEX_RSP_PAYMENT_REQUIRED, "Payment Required" }, + { G_OBEX_RSP_FORBIDDEN, "Forbidden" }, + { G_OBEX_RSP_NOT_FOUND, "Not Found" }, + { G_OBEX_RSP_METHOD_NOT_ALLOWED, "Method Not Allowed" }, + { G_OBEX_RSP_NOT_ACCEPTABLE, "Not Acceptable" }, + { G_OBEX_RSP_PROXY_AUTH_REQUIRED, "Proxy Authentication Required" }, + { G_OBEX_RSP_REQUEST_TIME_OUT, "Request Time Out" }, + { G_OBEX_RSP_CONFLICT, "Conflict" }, + { G_OBEX_RSP_GONE, "Gone" }, + { G_OBEX_RSP_LENGTH_REQUIRED, "Length Required" }, + { G_OBEX_RSP_PRECONDITION_FAILED, "Precondition Failed" }, + { G_OBEX_RSP_REQ_ENTITY_TOO_LARGE, "Request Entity Too Large" }, + { G_OBEX_RSP_REQ_URL_TOO_LARGE, "Request URL Too Large" }, + { G_OBEX_RSP_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type" }, + { G_OBEX_RSP_INTERNAL_SERVER_ERROR, "Internal Server Error" }, + { G_OBEX_RSP_NOT_IMPLEMENTED, "Not Implemented" }, + { G_OBEX_RSP_BAD_GATEWAY, "Bad Gateway" }, + { G_OBEX_RSP_SERVICE_UNAVAILABLE, "Service Unavailable" }, + { G_OBEX_RSP_GATEWAY_TIMEOUT, "Gateway Timeout" }, + { G_OBEX_RSP_VERSION_NOT_SUPPORTED, "Version Not Supported" }, + { G_OBEX_RSP_DATABASE_FULL, "Database Full" }, + { G_OBEX_RSP_DATABASE_LOCKED, "Database Locked" }, + { 0x00, NULL } +}; + +const char *g_obex_strerror(guint8 err_code) +{ + struct error_code *error; + + for (error = obex_errors; error->name != NULL; error++) { + if (error->code == err_code) + return error->name; + } + + return ""; +} + +static ssize_t req_header_offset(guint8 opcode) +{ + switch (opcode) { + case G_OBEX_OP_CONNECT: + return sizeof(struct connect_data); + case G_OBEX_OP_SETPATH: + return sizeof(struct setpath_data); + case G_OBEX_OP_DISCONNECT: + case G_OBEX_OP_PUT: + case G_OBEX_OP_GET: + case G_OBEX_OP_SESSION: + case G_OBEX_OP_ABORT: + case G_OBEX_OP_ACTION: + return 0; + default: + return -1; + } +} + +static ssize_t rsp_header_offset(guint8 opcode) +{ + switch (opcode) { + case G_OBEX_OP_CONNECT: + return sizeof(struct connect_data); + case G_OBEX_OP_SETPATH: + case G_OBEX_OP_DISCONNECT: + case G_OBEX_OP_PUT: + case G_OBEX_OP_GET: + case G_OBEX_OP_SESSION: + case G_OBEX_OP_ABORT: + case G_OBEX_OP_ACTION: + return 0; + default: + return -1; + } +} + +static void pending_pkt_free(struct pending_pkt *p) +{ + if (p->obex != NULL) + g_obex_unref(p->obex); + + if (p->timeout_id > 0) + g_source_remove(p->timeout_id); + + g_obex_packet_free(p->pkt); + + g_free(p); +} + +static gboolean req_timeout(gpointer user_data) +{ + GObex *obex = user_data; + struct pending_pkt *p = obex->pending_req; + GError *err; + + g_assert(p != NULL); + + p->timeout_id = 0; + obex->pending_req = NULL; + + err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_TIMEOUT, + "Timed out waiting for response"); + + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message); + + if (p->rsp_func) + p->rsp_func(obex, err, NULL, p->rsp_data); + + g_error_free(err); + pending_pkt_free(p); + + return FALSE; +} + +static gboolean write_stream(GObex *obex, GError **err) +{ + GIOStatus status; + gsize bytes_written; + char *buf; + + buf = (char *) &obex->tx_buf[obex->tx_sent]; + status = g_io_channel_write_chars(obex->io, buf, obex->tx_data, + &bytes_written, err); + if (status != G_IO_STATUS_NORMAL) + return FALSE; + + g_obex_dump(G_OBEX_DEBUG_DATA, "<", buf, bytes_written); + + obex->tx_sent += bytes_written; + obex->tx_data -= bytes_written; + + return TRUE; +} + +static gboolean write_packet(GObex *obex, GError **err) +{ + GIOStatus status; + gsize bytes_written; + char *buf; + + buf = (char *) &obex->tx_buf[obex->tx_sent]; + status = g_io_channel_write_chars(obex->io, buf, obex->tx_data, + &bytes_written, err); + if (status != G_IO_STATUS_NORMAL) + return FALSE; + + if (bytes_written != obex->tx_data) + return FALSE; + + g_obex_dump(G_OBEX_DEBUG_DATA, "<", buf, bytes_written); + + obex->tx_sent += bytes_written; + obex->tx_data -= bytes_written; + + return TRUE; +} + +static void set_srmp(GObex *obex, guint8 srmp, gboolean outgoing) +{ + struct srm_config *config = obex->srm; + + if (config == NULL) + return; + + /* Dont't reset if direction doesn't match */ + if (srmp > G_OBEX_SRMP_NEXT_WAIT && config->outgoing != outgoing) + return; + + config->srmp = srmp; + config->outgoing = outgoing; +} + +static void set_srm(GObex *obex, guint8 op, guint8 srm) +{ + struct srm_config *config = obex->srm; + gboolean enable; + + if (config == NULL) { + if (srm == G_OBEX_SRM_DISABLE) + return; + + config = g_new0(struct srm_config, 1); + config->op = op; + config->srm = srm; + obex->srm = config; + return; + } + + /* Indicate response, treat it as request */ + if (config->srm == G_OBEX_SRM_INDICATE) { + if (srm != G_OBEX_SRM_ENABLE) + goto done; + config->srm = srm; + return; + } + + enable = (srm == G_OBEX_SRM_ENABLE); + if (config->enabled == enable) + goto done; + + config->enabled = enable; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "SRM %s", config->enabled ? + "Enabled" : "Disabled"); + +done: + if (config->enabled) + return; + + g_free(obex->srm); + obex->srm = NULL; +} + +static gboolean g_obex_srm_enabled(GObex *obex) +{ + if (!obex->use_srm) + return FALSE; + + if (obex->srm == NULL) + return FALSE; + + return obex->srm->enabled; +} + +static void check_srm_final(GObex *obex, guint8 op) +{ + if (!g_obex_srm_enabled(obex)) + return; + + switch (obex->srm->op) { + case G_OBEX_OP_CONNECT: + return; + default: + if (op <= G_OBEX_RSP_CONTINUE) + return; + } + + set_srm(obex, op, G_OBEX_SRM_DISABLE); +} + +static void setup_srm(GObex *obex, GObexPacket *pkt, gboolean outgoing) +{ + GObexHeader *hdr; + guint8 op; + gboolean final; + + if (!obex->use_srm) + return; + + op = g_obex_packet_get_operation(pkt, &final); + + hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_SRM); + if (hdr != NULL) { + guint8 srm; + g_obex_header_get_uint8(hdr, &srm); + g_obex_debug(G_OBEX_DEBUG_COMMAND, "srm 0x%02x", srm); + set_srm(obex, op, srm); + } else if (!g_obex_srm_enabled(obex)) + set_srm(obex, op, G_OBEX_SRM_DISABLE); + + hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_SRMP); + if (hdr != NULL) { + guint8 srmp; + g_obex_header_get_uint8(hdr, &srmp); + g_obex_debug(G_OBEX_DEBUG_COMMAND, "srmp 0x%02x", srmp); + set_srmp(obex, srmp, outgoing); + } else if (obex->pending_req && obex->pending_req->suspended) + g_obex_packet_add_uint8(pkt, G_OBEX_HDR_SRMP, G_OBEX_SRMP_WAIT); + else + set_srmp(obex, -1, outgoing); + + if (final) + check_srm_final(obex, op); +} + +static gboolean write_data(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + GObex *obex = user_data; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) + goto stop_tx; + + if (obex->tx_data == 0) { + struct pending_pkt *p = g_queue_pop_head(obex->tx_queue); + ssize_t len; + + if (p == NULL) + goto stop_tx; + + setup_srm(obex, p->pkt, TRUE); + + if (g_obex_srm_enabled(obex)) + goto encode; + + /* Can't send a request while there's a pending one */ + if (obex->pending_req && p->id > 0) { + g_queue_push_head(obex->tx_queue, p); + goto stop_tx; + } + +encode: + len = g_obex_packet_encode(p->pkt, obex->tx_buf, obex->tx_mtu); + if (len == -EAGAIN) { + g_queue_push_head(obex->tx_queue, p); + g_obex_suspend(obex); + goto stop_tx; + } + + if (len < 0) { + pending_pkt_free(p); + goto done; + } + + if (p->id > 0) { + if (obex->pending_req != NULL) + pending_pkt_free(obex->pending_req); + obex->pending_req = p; + p->timeout_id = g_timeout_add_seconds(p->timeout, + req_timeout, obex); + } else { + /* During packet encode final bit can be set */ + if (obex->tx_buf[0] & FINAL_BIT) + check_srm_final(obex, + obex->tx_buf[0] & ~FINAL_BIT); + pending_pkt_free(p); + } + + obex->tx_data = len; + obex->tx_sent = 0; + } + + if (obex->suspended) { + obex->write_source = 0; + return FALSE; + } + + if (!obex->write(obex, NULL)) + goto stop_tx; + +done: + if (obex->tx_data > 0 || g_queue_get_length(obex->tx_queue) > 0) + return TRUE; + +stop_tx: + obex->rx_last_op = G_OBEX_OP_NONE; + obex->tx_data = 0; + obex->write_source = 0; + return FALSE; +} + +static void enable_tx(GObex *obex) +{ + GIOCondition cond; + + if (obex->suspended) + return; + + if (!obex->io || obex->write_source > 0) + return; + + cond = G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL; + obex->write_source = g_io_add_watch(obex->io, cond, write_data, obex); +} + +static gboolean g_obex_send_internal(GObex *obex, struct pending_pkt *p, + GError **err) +{ + + if (obex->io == NULL) { + if (!err) + return FALSE; + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_DISCONNECTED, + "The transport is not connected"); + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); + return FALSE; + } + + if (g_obex_packet_get_operation(p->pkt, NULL) == G_OBEX_OP_ABORT) + g_queue_push_head(obex->tx_queue, p); + else + g_queue_push_tail(obex->tx_queue, p); + + if (obex->pending_req == NULL || p->id == 0) + enable_tx(obex); + + return TRUE; +} + +static void init_connect_data(GObex *obex, struct connect_data *data) +{ + guint16 u16; + + memset(data, 0, sizeof(*data)); + + data->version = 0x10; + data->flags = 0; + + u16 = g_htons(obex->rx_mtu); + memcpy(&data->mtu, &u16, sizeof(u16)); +} + +static guint8 *digest_response(const guint8 *nonce) +{ + GChecksum *md5; + guint8 *result; + gsize size; + + result = g_new0(guint8, NONCE_LEN); + + md5 = g_checksum_new(G_CHECKSUM_MD5); + if (md5 == NULL) + return result; + + g_checksum_update(md5, nonce, NONCE_LEN); + g_checksum_update(md5, (guint8 *) ":BlueZ", 6); + + size = NONCE_LEN; + g_checksum_get_digest(md5, result, &size); + + g_checksum_free(md5); + + return result; +} + +static void prepare_auth_rsp(GObex *obex, GObexPacket *rsp) +{ + GObexHeader *hdr; + GObexApparam *authrsp; + const guint8 *nonce; + guint8 *result; + gsize len; + + /* Check if client is already responding to authentication challenge */ + hdr = g_obex_packet_get_header(rsp, G_OBEX_HDR_AUTHRESP); + if (hdr) + goto done; + + if (!g_obex_apparam_get_bytes(obex->authchal, NONCE_TAG, &nonce, &len)) + goto done; + + if (len != NONCE_LEN) + goto done; + + result = digest_response(nonce); + authrsp = g_obex_apparam_set_bytes(NULL, DIGEST_TAG, result, NONCE_LEN); + + hdr = g_obex_header_new_tag(G_OBEX_HDR_AUTHRESP, authrsp); + g_obex_packet_add_header(rsp, hdr); + + g_obex_apparam_free(authrsp); + g_free(result); + +done: + g_obex_apparam_free(obex->authchal); + obex->authchal = NULL; +} + +static void prepare_connect_rsp(GObex *obex, GObexPacket *rsp) +{ + GObexHeader *hdr; + struct connect_data data; + static guint32 next_connid = 1; + + init_connect_data(obex, &data); + g_obex_packet_set_data(rsp, &data, sizeof(data), G_OBEX_DATA_COPY); + + hdr = g_obex_packet_get_header(rsp, G_OBEX_HDR_CONNECTION); + if (hdr) { + g_obex_header_get_uint32(hdr, &obex->conn_id); + goto done; + } + + obex->conn_id = next_connid++; + + hdr = g_obex_header_new_uint32(G_OBEX_HDR_CONNECTION, obex->conn_id); + g_obex_packet_prepend_header(rsp, hdr); + +done: + if (obex->authchal) + prepare_auth_rsp(obex, rsp); +} + +static void prepare_srm_rsp(GObex *obex, GObexPacket *pkt) +{ + GObexHeader *hdr; + + if (!obex->use_srm || obex->srm == NULL) + return; + + if (obex->srm->enabled) + return; + + hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_SRM); + if (hdr != NULL) + return; + + hdr = g_obex_header_new_uint8(G_OBEX_HDR_SRM, G_OBEX_SRM_ENABLE); + g_obex_packet_prepend_header(pkt, hdr); +} + +gboolean g_obex_send(GObex *obex, GObexPacket *pkt, GError **err) +{ + struct pending_pkt *p; + gboolean ret; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id); + + if (obex == NULL || pkt == NULL) { + if (!err) + return FALSE; + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_INVALID_ARGS, + "Invalid arguments"); + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); + return FALSE; + } + + switch (obex->rx_last_op) { + case G_OBEX_OP_CONNECT: + prepare_connect_rsp(obex, pkt); + break; + case G_OBEX_OP_GET: + case G_OBEX_OP_PUT: + prepare_srm_rsp(obex, pkt); + break; + } + + p = g_new0(struct pending_pkt, 1); + p->pkt = pkt; + + ret = g_obex_send_internal(obex, p, err); + if (ret == FALSE) + pending_pkt_free(p); + + return ret; +} + +static void prepare_srm_req(GObex *obex, GObexPacket *pkt) +{ + GObexHeader *hdr; + + if (!obex->use_srm) + return; + + if (obex->srm != NULL && obex->srm->enabled) + return; + + hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_SRM); + if (hdr != NULL) + return; + + hdr = g_obex_header_new_uint8(G_OBEX_HDR_SRM, G_OBEX_SRM_ENABLE); + g_obex_packet_prepend_header(pkt, hdr); +} + +guint g_obex_send_req(GObex *obex, GObexPacket *req, int timeout, + GObexResponseFunc func, gpointer user_data, + GError **err) +{ + GObexHeader *hdr; + struct pending_pkt *p; + static guint id = 1; + guint8 op; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id); + + op = g_obex_packet_get_operation(req, NULL); + if (op == G_OBEX_OP_PUT || op == G_OBEX_OP_GET) { + /* Only enable SRM automatically for GET and PUT */ + prepare_srm_req(obex, req); + } + + if (obex->conn_id == CONNID_INVALID) + goto create_pending; + + if (obex->rx_last_op == G_OBEX_RSP_CONTINUE) + goto create_pending; + + if (g_obex_srm_enabled(obex) && obex->pending_req != NULL) + goto create_pending; + + hdr = g_obex_packet_get_header(req, G_OBEX_HDR_CONNECTION); + if (hdr != NULL) + goto create_pending; + + hdr = g_obex_header_new_uint32(G_OBEX_HDR_CONNECTION, obex->conn_id); + g_obex_packet_prepend_header(req, hdr); + +create_pending: + p = g_new0(struct pending_pkt, 1); + + p->pkt = req; + p->id = id++; + p->rsp_func = func; + p->rsp_data = user_data; + + if (timeout < 0) + p->timeout = G_OBEX_DEFAULT_TIMEOUT; + else + p->timeout = timeout; + + if (!g_obex_send_internal(obex, p, err)) { + pending_pkt_free(p); + return 0; + } + + return p->id; +} + +static int pending_pkt_cmp(gconstpointer a, gconstpointer b) +{ + const struct pending_pkt *p = a; + guint id = GPOINTER_TO_UINT(b); + + return (p->id - id); +} + +static gboolean pending_req_abort(GObex *obex, GError **err) +{ + struct pending_pkt *p = obex->pending_req; + GObexPacket *req; + + if (p->cancelled) + return TRUE; + + p->cancelled = TRUE; + + if (p->timeout_id > 0) + g_source_remove(p->timeout_id); + + p->timeout = G_OBEX_ABORT_TIMEOUT; + p->timeout_id = g_timeout_add_seconds(p->timeout, req_timeout, obex); + + req = g_obex_packet_new(G_OBEX_OP_ABORT, TRUE, G_OBEX_HDR_INVALID); + + return g_obex_send(obex, req, err); +} + +static gboolean cancel_complete(gpointer user_data) +{ + struct pending_pkt *p = user_data; + GObex *obex = p->obex; + GError *err; + + g_assert(p->rsp_func != NULL); + + err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED, + "The request was cancelled"); + p->rsp_func(obex, err, NULL, p->rsp_data); + + g_error_free(err); + + pending_pkt_free(p); + + return FALSE; +} + +gboolean g_obex_cancel_req(GObex *obex, guint req_id, gboolean remove_callback) +{ + GList *match; + struct pending_pkt *p; + + if (obex->pending_req && obex->pending_req->id == req_id) { + if (!pending_req_abort(obex, NULL)) { + p = obex->pending_req; + obex->pending_req = NULL; + goto immediate_completion; + } + + if (remove_callback) + obex->pending_req->rsp_func = NULL; + + return TRUE; + } + + match = g_queue_find_custom(obex->tx_queue, GUINT_TO_POINTER(req_id), + pending_pkt_cmp); + if (match == NULL) + return FALSE; + + p = match->data; + + g_queue_delete_link(obex->tx_queue, match); + +immediate_completion: + p->cancelled = TRUE; + p->obex = g_obex_ref(obex); + + if (remove_callback || p->rsp_func == NULL) + pending_pkt_free(p); + else + g_idle_add(cancel_complete, p); + + return TRUE; +} + +gboolean g_obex_send_rsp(GObex *obex, guint8 rspcode, GError **err, + guint first_hdr_type, ...) +{ + GObexPacket *rsp; + va_list args; + + va_start(args, first_hdr_type); + rsp = g_obex_packet_new_valist(rspcode, TRUE, first_hdr_type, args); + va_end(args); + + return g_obex_send(obex, rsp, err); +} + +void g_obex_set_disconnect_function(GObex *obex, GObexFunc func, + gpointer user_data) +{ + obex->disconn_func = func; + obex->disconn_func_data = user_data; +} + +static int req_handler_cmpop(gconstpointer a, gconstpointer b) +{ + const struct req_handler *handler = a; + guint opcode = GPOINTER_TO_UINT(b); + + return (int) handler->opcode - (int) opcode; +} + +static int req_handler_cmpid(gconstpointer a, gconstpointer b) +{ + const struct req_handler *handler = a; + guint id = GPOINTER_TO_UINT(b); + + return (int) handler->id - (int) id; +} + +guint g_obex_add_request_function(GObex *obex, guint8 opcode, + GObexRequestFunc func, + gpointer user_data) +{ + struct req_handler *handler; + static guint next_id = 1; + + handler = g_new0(struct req_handler, 1); + handler->id = next_id++; + handler->opcode = opcode; + handler->func = func; + handler->user_data = user_data; + + obex->req_handlers = g_slist_prepend(obex->req_handlers, handler); + + return handler->id; +} + +gboolean g_obex_remove_request_function(GObex *obex, guint id) +{ + struct req_handler *handler; + GSList *match; + + match = g_slist_find_custom(obex->req_handlers, GUINT_TO_POINTER(id), + req_handler_cmpid); + if (match == NULL) + return FALSE; + + handler = match->data; + + obex->req_handlers = g_slist_delete_link(obex->req_handlers, match); + g_free(handler); + + return TRUE; +} + +static void g_obex_srm_suspend(GObex *obex) +{ + struct pending_pkt *p = obex->pending_req; + GObexPacket *req; + + if (p->timeout_id > 0) { + g_source_remove(p->timeout_id); + p->timeout_id = 0; + } + + p->suspended = TRUE; + + req = g_obex_packet_new(G_OBEX_OP_GET, TRUE, + G_OBEX_HDR_SRMP, G_OBEX_SRMP_WAIT, + G_OBEX_HDR_INVALID); + + g_obex_send(obex, req, NULL); +} + +void g_obex_suspend(GObex *obex) +{ + struct pending_pkt *req = obex->pending_req; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id); + + if (!g_obex_srm_active(obex) || !req) + goto done; + + /* Send SRMP wait in case of GET */ + if (g_obex_packet_get_operation(req->pkt, NULL) == G_OBEX_OP_GET) { + g_obex_srm_suspend(obex); + return; + } + +done: + obex->suspended = TRUE; + + if (obex->write_source > 0) { + g_source_remove(obex->write_source); + obex->write_source = 0; + } +} + +static void g_obex_srm_resume(GObex *obex) +{ + struct pending_pkt *p = obex->pending_req; + GObexPacket *req; + + p->timeout_id = g_timeout_add_seconds(p->timeout, req_timeout, obex); + p->suspended = FALSE; + + req = g_obex_packet_new(G_OBEX_OP_GET, TRUE, G_OBEX_HDR_INVALID); + + g_obex_send(obex, req, NULL); +} + +void g_obex_resume(GObex *obex) +{ + struct pending_pkt *req = obex->pending_req; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id); + + obex->suspended = FALSE; + + if (g_obex_srm_active(obex) || !req) + goto done; + + if (g_obex_packet_get_operation(req->pkt, NULL) == G_OBEX_OP_GET) + g_obex_srm_resume(obex); + +done: + if (g_queue_get_length(obex->tx_queue) > 0 || obex->tx_data > 0) + enable_tx(obex); +} + +gboolean g_obex_srm_active(GObex *obex) +{ + gboolean ret = FALSE; + + if (!g_obex_srm_enabled(obex)) + goto done; + + if (obex->srm->srmp <= G_OBEX_SRMP_NEXT_WAIT) + goto done; + + ret = TRUE; +done: + g_obex_debug(G_OBEX_DEBUG_COMMAND, "%s", ret ? "yes" : "no"); + return ret; +} + +static void auth_challenge(GObex *obex) +{ + struct pending_pkt *p = obex->pending_req; + + if (p->authenticating) + return; + + p->authenticating = TRUE; + + prepare_auth_rsp(obex, p->pkt); + + /* Remove it as pending and add it back to the queue so it gets sent + * again */ + if (p->timeout_id > 0) { + g_source_remove(p->timeout_id); + p->timeout_id = 0; + } + obex->pending_req = NULL; + g_obex_send_internal(obex, p, NULL); +} + +static void parse_connect_data(GObex *obex, GObexPacket *pkt) +{ + const struct connect_data *data; + GObexHeader *hdr; + guint16 u16; + size_t data_len; + + data = g_obex_packet_get_data(pkt, &data_len); + if (data == NULL || data_len != sizeof(*data)) + return; + + memcpy(&u16, &data->mtu, sizeof(u16)); + + obex->tx_mtu = g_ntohs(u16); + if (obex->io_tx_mtu > 0 && obex->tx_mtu > obex->io_tx_mtu) + obex->tx_mtu = obex->io_tx_mtu; + obex->tx_buf = g_realloc(obex->tx_buf, obex->tx_mtu); + + hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_CONNECTION); + if (hdr) + g_obex_header_get_uint32(hdr, &obex->conn_id); + + hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_AUTHCHAL); + if (hdr) + obex->authchal = g_obex_header_get_apparam(hdr); +} + +static gboolean parse_response(GObex *obex, GObexPacket *rsp) +{ + struct pending_pkt *p = obex->pending_req; + guint8 opcode, rspcode; + gboolean final; + + rspcode = g_obex_packet_get_operation(rsp, &final); + + opcode = g_obex_packet_get_operation(p->pkt, NULL); + if (opcode == G_OBEX_OP_CONNECT) { + parse_connect_data(obex, rsp); + if (rspcode == G_OBEX_RSP_UNAUTHORIZED && obex->authchal) + auth_challenge(obex); + } + + setup_srm(obex, rsp, FALSE); + + if (!g_obex_srm_enabled(obex)) + return final; + + /* + * Resposes have final bit set but in case of GET with SRM + * we should not remove the request since the remote side will + * continue sending responses until the transfer is finished + */ + if (opcode == G_OBEX_OP_GET && rspcode == G_OBEX_RSP_CONTINUE) { + if (p->timeout_id > 0) + g_source_remove(p->timeout_id); + + p->timeout_id = g_timeout_add_seconds(p->timeout, req_timeout, + obex); + return FALSE; + } + + return final; +} + +static void handle_response(GObex *obex, GError *err, GObexPacket *rsp) +{ + struct pending_pkt *p; + gboolean disconn = err ? TRUE : FALSE, final_rsp = TRUE; + + if (rsp != NULL) + final_rsp = parse_response(obex, rsp); + + if (!obex->pending_req) + return; + + p = obex->pending_req; + + /* Reset if final so it can no longer be cancelled */ + if (final_rsp) + obex->pending_req = NULL; + + if (p->cancelled) + err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_CANCELLED, + "The operation was cancelled"); + + if (err) + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message); + + if (p->rsp_func) { + p->rsp_func(obex, err, rsp, p->rsp_data); + + /* Check if user callback removed the request */ + if (!final_rsp && p != obex->pending_req) + return; + } + + if (p->cancelled) + g_error_free(err); + + if (final_rsp) + pending_pkt_free(p); + + if (!disconn && g_queue_get_length(obex->tx_queue) > 0) + enable_tx(obex); +} + +static gboolean check_connid(GObex *obex, GObexPacket *pkt) +{ + GObexHeader *hdr; + guint32 id; + + if (obex->conn_id == CONNID_INVALID) + return TRUE; + + hdr = g_obex_packet_get_header(pkt, G_OBEX_HDR_CONNECTION); + if (hdr == NULL) + return TRUE; + + g_obex_header_get_uint32(hdr, &id); + + return obex->conn_id == id; +} + +static int parse_request(GObex *obex, GObexPacket *req) +{ + guint8 op; + gboolean final; + + op = g_obex_packet_get_operation(req, &final); + switch (op) { + case G_OBEX_OP_CONNECT: + parse_connect_data(obex, req); + break; + case G_OBEX_OP_ABORT: + break; + default: + if (check_connid(obex, req)) + break; + + return -G_OBEX_RSP_SERVICE_UNAVAILABLE; + } + + setup_srm(obex, req, FALSE); + + return op; +} + +static void handle_request(GObex *obex, GObexPacket *req) +{ + GSList *match; + int op; + + op = parse_request(obex, req); + if (op < 0) + goto fail; + + match = g_slist_find_custom(obex->req_handlers, GUINT_TO_POINTER(op), + req_handler_cmpop); + if (match) { + struct req_handler *handler = match->data; + handler->func(obex, req, handler->user_data); + return; + } + + op = -G_OBEX_RSP_NOT_IMPLEMENTED; + +fail: + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", g_obex_strerror(-op)); + g_obex_send_rsp(obex, -op, NULL, G_OBEX_HDR_INVALID); +} + +static gboolean read_stream(GObex *obex, GError **err) +{ + GIOChannel *io = obex->io; + GIOStatus status; + gsize rbytes, toread; + guint16 u16; + char *buf; + + if (obex->rx_data >= 3) + goto read_body; + + rbytes = 0; + toread = 3 - obex->rx_data; + buf = (char *) &obex->rx_buf[obex->rx_data]; + + status = g_io_channel_read_chars(io, buf, toread, &rbytes, NULL); + if (status != G_IO_STATUS_NORMAL) + return TRUE; + + obex->rx_data += rbytes; + if (obex->rx_data < 3) + goto done; + + memcpy(&u16, &buf[1], sizeof(u16)); + obex->rx_pkt_len = g_ntohs(u16); + + if (obex->rx_pkt_len > obex->rx_mtu) { + if (!err) + return FALSE; + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, + "Too big incoming packet"); + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); + return FALSE; + } + +read_body: + if (obex->rx_data >= obex->rx_pkt_len) + goto done; + + do { + toread = obex->rx_pkt_len - obex->rx_data; + buf = (char *) &obex->rx_buf[obex->rx_data]; + + status = g_io_channel_read_chars(io, buf, toread, &rbytes, NULL); + if (status != G_IO_STATUS_NORMAL) + goto done; + + obex->rx_data += rbytes; + } while (rbytes > 0 && obex->rx_data < obex->rx_pkt_len); + +done: + g_obex_dump(G_OBEX_DEBUG_DATA, ">", obex->rx_buf, obex->rx_data); + + return TRUE; +} + +static gboolean read_packet(GObex *obex, GError **err) +{ + GIOChannel *io = obex->io; + GError *read_err = NULL; + GIOStatus status; + gsize rbytes; + guint16 u16; + + if (obex->rx_data > 0) { + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, + "RX buffer not empty before reading packet"); + goto fail; + } + + status = g_io_channel_read_chars(io, (char *) obex->rx_buf, + obex->rx_mtu, &rbytes, &read_err); + if (status != G_IO_STATUS_NORMAL) { + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, + "Unable to read data: %s", read_err->message); + g_error_free(read_err); + goto fail; + } + + obex->rx_data += rbytes; + + if (rbytes < 3) { + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, + "Incomplete packet received"); + goto fail; + } + + memcpy(&u16, &obex->rx_buf[1], sizeof(u16)); + obex->rx_pkt_len = g_ntohs(u16); + + if (obex->rx_pkt_len != rbytes) { + g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, + "Data size doesn't match packet size (%zu != %u)", + rbytes, obex->rx_pkt_len); + return FALSE; + } + + g_obex_dump(G_OBEX_DEBUG_DATA, ">", obex->rx_buf, obex->rx_data); + + return TRUE; +fail: + if (err) + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); + + return FALSE; +} + +static gboolean incoming_data(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + GObex *obex = user_data; + GObexPacket *pkt; + ssize_t header_offset; + GError *err = NULL; + guint8 opcode; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) { + err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_DISCONNECTED, + "Transport got disconnected"); + goto failed; + } + + if (!obex->read(obex, &err)) + goto failed; + + if (obex->rx_data < 3 || obex->rx_data < obex->rx_pkt_len) + return TRUE; + + obex->rx_last_op = obex->rx_buf[0] & ~FINAL_BIT; + + if (obex->pending_req) { + struct pending_pkt *p = obex->pending_req; + opcode = g_obex_packet_get_operation(p->pkt, NULL); + header_offset = rsp_header_offset(opcode); + } else { + opcode = obex->rx_last_op; + /* Unexpected response -- fail silently */ + if (opcode > 0x1f && opcode != G_OBEX_OP_ABORT) { + obex->rx_data = 0; + return TRUE; + } + header_offset = req_header_offset(opcode); + } + + if (header_offset < 0) { + err = g_error_new(G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, + "Unknown header offset for opcode 0x%02x", + opcode); + goto failed; + } + + pkt = g_obex_packet_decode(obex->rx_buf, obex->rx_data, header_offset, + G_OBEX_DATA_REF, &err); + if (pkt == NULL) + goto failed; + + /* Protect against user callback freeing the object */ + g_obex_ref(obex); + + if (obex->pending_req) + handle_response(obex, NULL, pkt); + else + handle_request(obex, pkt); + + obex->rx_data = 0; + + g_obex_unref(obex); + + if (err != NULL) + g_error_free(err); + + if (pkt != NULL) + g_obex_packet_free(pkt); + + return TRUE; + +failed: + if (err) + g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", err->message); + + g_io_channel_unref(obex->io); + obex->io = NULL; + obex->io_source = 0; + obex->rx_data = 0; + + /* Protect against user callback freeing the object */ + g_obex_ref(obex); + + if (obex->pending_req) + handle_response(obex, err, NULL); + + if (obex->disconn_func) + obex->disconn_func(obex, err, obex->disconn_func_data); + + g_obex_unref(obex); + + g_error_free(err); + + return FALSE; +} + +static GDebugKey keys[] = { + { "error", G_OBEX_DEBUG_ERROR }, + { "command", G_OBEX_DEBUG_COMMAND }, + { "transfer", G_OBEX_DEBUG_TRANSFER }, + { "header", G_OBEX_DEBUG_HEADER }, + { "packet", G_OBEX_DEBUG_PACKET }, + { "data", G_OBEX_DEBUG_DATA }, + { "apparam", G_OBEX_DEBUG_APPARAM }, +}; + +GObex *g_obex_new(GIOChannel *io, GObexTransportType transport_type, + gssize io_rx_mtu, gssize io_tx_mtu) +{ + GObex *obex; + GIOCondition cond; + + if (gobex_debug == 0) { + const char *env = g_getenv("GOBEX_DEBUG"); + + if (env) { + gobex_debug = g_parse_debug_string(env, keys, 7); + g_setenv("G_MESSAGES_DEBUG", "gobex", FALSE); + } else + gobex_debug = G_OBEX_DEBUG_NONE; + } + + g_obex_debug(G_OBEX_DEBUG_COMMAND, ""); + + if (io == NULL) + return NULL; + + if (io_rx_mtu >= 0 && io_rx_mtu < G_OBEX_MINIMUM_MTU) + return NULL; + + if (io_tx_mtu >= 0 && io_tx_mtu < G_OBEX_MINIMUM_MTU) + return NULL; + + obex = g_new0(GObex, 1); + + obex->io = g_io_channel_ref(io); + obex->ref_count = 1; + obex->conn_id = CONNID_INVALID; + obex->rx_last_op = G_OBEX_OP_NONE; + + obex->io_rx_mtu = io_rx_mtu; + obex->io_tx_mtu = io_tx_mtu; + + if (io_rx_mtu > G_OBEX_MAXIMUM_MTU) + obex->rx_mtu = G_OBEX_MAXIMUM_MTU; + else if (io_rx_mtu < G_OBEX_MINIMUM_MTU) + obex->rx_mtu = G_OBEX_DEFAULT_MTU; + else + obex->rx_mtu = io_rx_mtu; + + obex->tx_mtu = G_OBEX_MINIMUM_MTU; + + obex->tx_queue = g_queue_new(); + obex->rx_buf = g_malloc(obex->rx_mtu); + obex->tx_buf = g_malloc(obex->tx_mtu); + + switch (transport_type) { + case G_OBEX_TRANSPORT_STREAM: + obex->read = read_stream; + obex->write = write_stream; + break; + case G_OBEX_TRANSPORT_PACKET: + obex->use_srm = TRUE; + obex->read = read_packet; + obex->write = write_packet; + break; + default: + g_obex_unref(obex); + return NULL; + } + + g_io_channel_set_encoding(io, NULL, NULL); + g_io_channel_set_buffered(io, FALSE); + cond = G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL; + obex->io_source = g_io_add_watch(io, cond, incoming_data, obex); + + return obex; +} + +GObex *g_obex_ref(GObex *obex) +{ + int refs; + + if (obex == NULL) + return NULL; + + refs = __sync_add_and_fetch(&obex->ref_count, 1); + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "ref %u", refs); + + return obex; +} + +static void tx_queue_free(void *data, void *user_data) +{ + pending_pkt_free(data); +} + +void g_obex_unref(GObex *obex) +{ + int refs; + + refs = __sync_sub_and_fetch(&obex->ref_count, 1); + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "ref %u", refs); + + if (refs > 0) + return; + + g_slist_free_full(obex->req_handlers, g_free); + + g_queue_foreach(obex->tx_queue, tx_queue_free, NULL); + g_queue_free(obex->tx_queue); + + if (obex->io != NULL) + g_io_channel_unref(obex->io); + + if (obex->io_source > 0) + g_source_remove(obex->io_source); + + if (obex->write_source > 0) + g_source_remove(obex->write_source); + + g_free(obex->rx_buf); + g_free(obex->tx_buf); + g_free(obex->srm); + + if (obex->pending_req) + pending_pkt_free(obex->pending_req); + + if (obex->authchal) + g_obex_apparam_free(obex->authchal); + + g_free(obex); +} + +/* Higher level functions */ + +guint g_obex_connect(GObex *obex, GObexResponseFunc func, gpointer user_data, + GError **err, guint first_hdr_id, ...) +{ + GObexPacket *req; + struct connect_data data; + va_list args; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, ""); + + va_start(args, first_hdr_id); + req = g_obex_packet_new_valist(G_OBEX_OP_CONNECT, TRUE, + first_hdr_id, args); + va_end(args); + + init_connect_data(obex, &data); + g_obex_packet_set_data(req, &data, sizeof(data), G_OBEX_DATA_COPY); + + return g_obex_send_req(obex, req, -1, func, user_data, err); +} + +guint g_obex_disconnect(GObex *obex, GObexResponseFunc func, gpointer user_data, + GError **err) +{ + GObexPacket *req; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, ""); + + req = g_obex_packet_new(G_OBEX_OP_DISCONNECT, TRUE, G_OBEX_HDR_INVALID); + + return g_obex_send_req(obex, req, -1, func, user_data, err); +} + +guint g_obex_setpath(GObex *obex, const char *path, GObexResponseFunc func, + gpointer user_data, GError **err) +{ + GObexPacket *req; + struct setpath_data data; + const char *folder; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id); + + req = g_obex_packet_new(G_OBEX_OP_SETPATH, TRUE, G_OBEX_HDR_INVALID); + + memset(&data, 0, sizeof(data)); + + if (path != NULL && strncmp("..", path, 2) == 0) { + data.flags = 0x03; + folder = (path[2] == '/') ? &path[3] : NULL; + } else { + data.flags = 0x02; + folder = path; + } + + if (folder != NULL) { + GObexHeader *hdr; + hdr = g_obex_header_new_unicode(G_OBEX_HDR_NAME, folder); + g_obex_packet_add_header(req, hdr); + } + + g_obex_packet_set_data(req, &data, sizeof(data), G_OBEX_DATA_COPY); + + return g_obex_send_req(obex, req, -1, func, user_data, err); +} + +guint g_obex_mkdir(GObex *obex, const char *path, GObexResponseFunc func, + gpointer user_data, GError **err) +{ + GObexPacket *req; + struct setpath_data data; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id); + + req = g_obex_packet_new(G_OBEX_OP_SETPATH, TRUE, G_OBEX_HDR_NAME, path, + G_OBEX_HDR_INVALID); + + memset(&data, 0, sizeof(data)); + g_obex_packet_set_data(req, &data, sizeof(data), G_OBEX_DATA_COPY); + + return g_obex_send_req(obex, req, -1, func, user_data, err); +} + +guint g_obex_delete(GObex *obex, const char *name, GObexResponseFunc func, + gpointer user_data, GError **err) +{ + GObexPacket *req; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id); + + req = g_obex_packet_new(G_OBEX_OP_PUT, TRUE, G_OBEX_HDR_NAME, name, + G_OBEX_HDR_INVALID); + + return g_obex_send_req(obex, req, -1, func, user_data, err); +} + +guint g_obex_copy(GObex *obex, const char *name, const char *dest, + GObexResponseFunc func, gpointer user_data, + GError **err) +{ + GObexPacket *req; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id); + + req = g_obex_packet_new(G_OBEX_OP_ACTION, TRUE, + G_OBEX_HDR_ACTION, G_OBEX_ACTION_COPY, + G_OBEX_HDR_NAME, name, + G_OBEX_HDR_DESTNAME, dest, + G_OBEX_HDR_INVALID); + + return g_obex_send_req(obex, req, -1, func, user_data, err); +} + +guint g_obex_move(GObex *obex, const char *name, const char *dest, + GObexResponseFunc func, gpointer user_data, + GError **err) +{ + GObexPacket *req; + + g_obex_debug(G_OBEX_DEBUG_COMMAND, "conn %u", obex->conn_id); + + req = g_obex_packet_new(G_OBEX_OP_ACTION, TRUE, + G_OBEX_HDR_ACTION, G_OBEX_ACTION_MOVE, + G_OBEX_HDR_NAME, name, + G_OBEX_HDR_DESTNAME, dest, + G_OBEX_HDR_INVALID); + + return g_obex_send_req(obex, req, -1, func, user_data, err); +} + +guint g_obex_abort(GObex *obex, GObexResponseFunc func, gpointer user_data, + GError **err) +{ + GObexPacket *req; + + req = g_obex_packet_new(G_OBEX_OP_ABORT, TRUE, G_OBEX_HDR_INVALID); + + return g_obex_send_req(obex, req, -1, func, user_data, err); +} + +guint8 g_obex_errno_to_rsp(int err) +{ + switch (err) { + case 0: + return G_OBEX_RSP_SUCCESS; + case -EPERM: + case -EACCES: + return G_OBEX_RSP_FORBIDDEN; + case -ENOENT: + return G_OBEX_RSP_NOT_FOUND; + case -EINVAL: + case -EBADR: + return G_OBEX_RSP_BAD_REQUEST; + case -EFAULT: + return G_OBEX_RSP_SERVICE_UNAVAILABLE; + case -ENOSYS: + return G_OBEX_RSP_NOT_IMPLEMENTED; + case -ENOTEMPTY: + case -EEXIST: + return G_OBEX_RSP_PRECONDITION_FAILED; + default: + return G_OBEX_RSP_INTERNAL_SERVER_ERROR; + } +} diff --git a/gobex/gobex.h b/gobex/gobex.h new file mode 100644 index 0000000..b223a2f --- /dev/null +++ b/gobex/gobex.h @@ -0,0 +1,138 @@ +/* + * + * OBEX library with GLib integration + * + * Copyright (C) 2011 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 + * + */ + +#ifndef __GOBEX_H +#define __GOBEX_H + +#include +#include + +#include "gobex/gobex-defs.h" +#include "gobex/gobex-packet.h" + +typedef enum { + G_OBEX_TRANSPORT_STREAM, + G_OBEX_TRANSPORT_PACKET, +} GObexTransportType; + +typedef struct _GObex GObex; + +typedef void (*GObexFunc) (GObex *obex, GError *err, gpointer user_data); +typedef void (*GObexRequestFunc) (GObex *obex, GObexPacket *req, + gpointer user_data); +typedef void (*GObexResponseFunc) (GObex *obex, GError *err, GObexPacket *rsp, + gpointer user_data); + +gboolean g_obex_send(GObex *obex, GObexPacket *pkt, GError **err); + +guint g_obex_send_req(GObex *obex, GObexPacket *req, int timeout, + GObexResponseFunc func, gpointer user_data, + GError **err); +gboolean g_obex_cancel_req(GObex *obex, guint req_id, + gboolean remove_callback); + +gboolean g_obex_send_rsp(GObex *obex, guint8 rspcode, GError **err, + guint first_hdr_type, ...); + +void g_obex_set_disconnect_function(GObex *obex, GObexFunc func, + gpointer user_data); +guint g_obex_add_request_function(GObex *obex, guint8 opcode, + GObexRequestFunc func, + gpointer user_data); +gboolean g_obex_remove_request_function(GObex *obex, guint id); + +void g_obex_suspend(GObex *obex); +void g_obex_resume(GObex *obex); +gboolean g_obex_srm_active(GObex *obex); + +GObex *g_obex_new(GIOChannel *io, GObexTransportType transport_type, + gssize rx_mtu, gssize tx_mtu); + +GObex *g_obex_ref(GObex *obex); +void g_obex_unref(GObex *obex); + +/* High level client functions */ + +guint g_obex_connect(GObex *obex, GObexResponseFunc func, gpointer user_data, + GError **err, guint first_hdr_id, ...); + +guint g_obex_disconnect(GObex *obex, GObexResponseFunc func, gpointer user_data, + GError **err); + +guint g_obex_setpath(GObex *obex, const char *path, GObexResponseFunc func, + gpointer user_data, GError **err); + +guint g_obex_mkdir(GObex *obex, const char *path, GObexResponseFunc func, + gpointer user_data, GError **err); + +guint g_obex_delete(GObex *obex, const char *name, GObexResponseFunc func, + gpointer user_data, GError **err); + +guint g_obex_copy(GObex *obex, const char *name, const char *dest, + GObexResponseFunc func, gpointer user_data, + GError **err); + +guint g_obex_move(GObex *obex, const char *name, const char *dest, + GObexResponseFunc func, gpointer user_data, + GError **err); + +guint g_obex_abort(GObex *obex, GObexResponseFunc func, gpointer user_data, + GError **err); + +/* Transfer related high-level functions */ + +guint g_obex_put_req(GObex *obex, GObexDataProducer data_func, + GObexFunc complete_func, gpointer user_data, + GError **err, guint first_hdr_id, ...); + +guint g_obex_put_req_pkt(GObex *obex, GObexPacket *req, + GObexDataProducer data_func, GObexFunc complete_func, + gpointer user_data, GError **err); + +guint g_obex_get_req(GObex *obex, GObexDataConsumer data_func, + GObexFunc complete_func, gpointer user_data, + GError **err, guint first_hdr_id, ...); + +guint g_obex_get_req_pkt(GObex *obex, GObexPacket *req, + GObexDataConsumer data_func, GObexFunc complete_func, + gpointer user_data, GError **err); + +guint g_obex_put_rsp(GObex *obex, GObexPacket *req, + GObexDataConsumer data_func, GObexFunc complete_func, + gpointer user_data, GError **err, + guint first_hdr_id, ...); + +guint g_obex_get_rsp(GObex *obex, GObexDataProducer data_func, + GObexFunc complete_func, gpointer user_data, + GError **err, guint first_hdr_id, ...); + +guint g_obex_get_rsp_pkt(GObex *obex, GObexPacket *rsp, + GObexDataProducer data_func, GObexFunc complete_func, + gpointer user_data, GError **err); + +gboolean g_obex_cancel_transfer(guint id, GObexFunc complete_func, + gpointer user_data); + +const char *g_obex_strerror(guint8 err_code); +guint8 g_obex_errno_to_rsp(int err); + +#endif /* __GOBEX_H */ diff --git a/install-sh b/install-sh new file mode 100755 index 0000000..8175c64 --- /dev/null +++ b/install-sh @@ -0,0 +1,518 @@ +#!/bin/sh +# install - install a program, script, or datafile + +scriptversion=2018-03-11.20; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# 'make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. + +tab=' ' +nl=' +' +IFS=" $tab$nl" + +# Set DOITPROG to "echo" to test this script. + +doit=${DOITPROG-} +doit_exec=${doit:-exec} + +# Put in absolute file names if you don't have them in your path; +# or use environment vars. + +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} + +posix_mkdir= + +# Desired mode of installed file. +mode=0755 + +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog +rmcmd="$rmprog -f" +stripcmd= + +src= +dst= +dir_arg= +dst_arg= + +copy_on_change=false +is_target_a_directory=possibly + +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... + +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. + +Options: + --help display this help and exit. + --version display version info and exit. + + -c (ignored) + -C install only if different (preserve the last data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -s $stripprog installed files. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. + +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG +" + +while test $# -ne 0; do + case $1 in + -c) ;; + + -C) copy_on_change=true;; + + -d) dir_arg=true;; + + -g) chgrpcmd="$chgrpprog $2" + shift;; + + --help) echo "$usage"; exit $?;; + + -m) mode=$2 + case $mode in + *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; + + -o) chowncmd="$chownprog $2" + shift;; + + -s) stripcmd=$stripprog;; + + -t) + is_target_a_directory=always + dst_arg=$2 + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + shift;; + + -T) is_target_a_directory=never;; + + --version) echo "$0 $scriptversion"; exit $?;; + + --) shift + break;; + + -*) echo "$0: invalid option: $1" >&2 + exit 1;; + + *) break;; + esac + shift +done + +# We allow the use of options -d and -T together, by making -d +# take the precedence; this is for compatibility with GNU install. + +if test -n "$dir_arg"; then + if test -n "$dst_arg"; then + echo "$0: target directory not allowed when installing a directory." >&2 + exit 1 + fi +fi + +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + done +fi + +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call 'install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + if test $# -gt 1 || test "$is_target_a_directory" = always; then + if test ! -d "$dst_arg"; then + echo "$0: $dst_arg: Is not a directory." >&2 + exit 1 + fi + fi +fi + +if test -z "$dir_arg"; then + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi + +for src +do + # Protect names problematic for 'test' and other utilities. + case $src in + -* | [=\(\)!]) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + dst=$dst_arg + + # If destination is a directory, append the input filename. + if test -d "$dst"; then + if test "$is_target_a_directory" = never; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dstbase=`basename "$src"` + case $dst in + */) dst=$dst$dstbase;; + *) dst=$dst/$dstbase;; + esac + dstdir_status=0 + else + dstdir=`dirname "$dst"` + test -d "$dstdir" + dstdir_status=$? + fi + fi + + case $dstdir in + */) dstdirslash=$dstdir;; + *) dstdirslash=$dstdir/;; + esac + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # Create intermediate dirs using mode 755 as modified by the umask. + # This is like FreeBSD 'install' as of 1997-10-28. + umask=`umask` + case $stripcmd.$umask in + # Optimize common cases. + *[2367][2367]) mkdir_umask=$umask;; + .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; + + *[0-7]) + mkdir_umask=`expr $umask + 22 \ + - $umask % 100 % 40 + $umask % 20 \ + - $umask % 10 % 4 + $umask % 2 + `;; + *) mkdir_umask=$umask,go-w;; + esac + + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + case $umask in + *[123567][0-7][0-7]) + # POSIX mkdir -p sets u+wx bits regardless of umask, which + # is incompatible with FreeBSD 'install' when (umask & 300) != 0. + ;; + *) + # Note that $RANDOM variable is not portable (e.g. dash); Use it + # here however when possible just to lower collision chance. + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + + trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0 + + # Because "mkdir -p" follows existing symlinks and we likely work + # directly in world-writeable /tmp, make sure that the '$tmpdir' + # directory is successfully created first before we actually test + # 'mkdir -p' feature. + if (umask $mkdir_umask && + $mkdirprog $mkdir_mode "$tmpdir" && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + test_tmpdir="$tmpdir/a" + ls_ld_tmpdir=`ls -ld "$test_tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null + fi + trap '' 0;; + esac;; + esac + + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # The umask is ridiculous, or mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + [-=\(\)!]*) prefix='./';; + *) prefix='';; + esac + + oIFS=$IFS + IFS=/ + set -f + set fnord $dstdir + shift + set +f + IFS=$oIFS + + prefixes= + + for d + do + test X"$d" = X && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask=$mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=${dstdirslash}_inst.$$_ + rmtmp=${dstdirslash}_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + set +f && + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd -f "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/lib/a2mp.h b/lib/a2mp.h new file mode 100644 index 0000000..da937d1 --- /dev/null +++ b/lib/a2mp.h @@ -0,0 +1,149 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Intel Corporation. All rights reserved. + * Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __A2MP_H +#define __A2MP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* A2MP Protocol */ + +/* A2MP command codes */ + +#define A2MP_COMMAND_REJ 0x01 +#define A2MP_DISCOVER_REQ 0x02 +#define A2MP_DISCOVER_RSP 0x03 +#define A2MP_CHANGE_NOTIFY 0x04 +#define A2MP_CHANGE_RSP 0x05 +#define A2MP_INFO_REQ 0x06 +#define A2MP_INFO_RSP 0x07 +#define A2MP_ASSOC_REQ 0x08 +#define A2MP_ASSOC_RSP 0x09 +#define A2MP_CREATE_REQ 0x0a +#define A2MP_CREATE_RSP 0x0b +#define A2MP_DISCONN_REQ 0x0c +#define A2MP_DISCONN_RSP 0x0d + +struct a2mp_hdr { + uint8_t code; + uint8_t ident; + uint16_t len; +} __attribute__ ((packed)); +#define A2MP_HDR_SIZE 4 + +struct a2mp_command_rej { + uint16_t reason; +} __attribute__ ((packed)); + +struct a2mp_discover_req { + uint16_t mtu; + uint16_t mask; +} __attribute__ ((packed)); + +struct a2mp_ctrl { + uint8_t id; + uint8_t type; + uint8_t status; +} __attribute__ ((packed)); + +struct a2mp_discover_rsp { + uint16_t mtu; + uint16_t mask; + struct a2mp_ctrl ctrl_list[0]; +} __attribute__ ((packed)); + +struct a2mp_info_req { + uint8_t id; +} __attribute__ ((packed)); + +struct a2mp_info_rsp { + uint8_t id; + uint8_t status; + uint32_t total_bw; + uint32_t max_bw; + uint32_t min_latency; + uint16_t pal_caps; + uint16_t assoc_size; +} __attribute__ ((packed)); + +struct a2mp_assoc_req { + uint8_t id; +} __attribute__ ((packed)); + +struct a2mp_assoc_rsp { + uint8_t id; + uint8_t status; + uint8_t assoc_data[0]; +} __attribute__ ((packed)); + +struct a2mp_create_req { + uint8_t local_id; + uint8_t remote_id; + uint8_t assoc_data[0]; +} __attribute__ ((packed)); + +struct a2mp_create_rsp { + uint8_t local_id; + uint8_t remote_id; + uint8_t status; +} __attribute__ ((packed)); + +struct a2mp_disconn_req { + uint8_t local_id; + uint8_t remote_id; +} __attribute__ ((packed)); + +struct a2mp_disconn_rsp { + uint8_t local_id; + uint8_t remote_id; + uint8_t status; +} __attribute__ ((packed)); + +#define A2MP_COMMAND_NOT_RECOGNIZED 0x0000 + +/* AMP controller status */ +#define AMP_CTRL_POWERED_DOWN 0x00 +#define AMP_CTRL_BLUETOOTH_ONLY 0x01 +#define AMP_CTRL_NO_CAPACITY 0x02 +#define AMP_CTRL_LOW_CAPACITY 0x03 +#define AMP_CTRL_MEDIUM_CAPACITY 0x04 +#define AMP_CTRL_HIGH_CAPACITY 0x05 +#define AMP_CTRL_FULL_CAPACITY 0x06 + +/* A2MP response status */ +#define A2MP_STATUS_SUCCESS 0x00 +#define A2MP_STATUS_INVALID_CTRL_ID 0x01 +#define A2MP_STATUS_UNABLE_START_LINK_CREATION 0x02 +#define A2MP_STATUS_NO_PHYSICAL_LINK_EXISTS 0x02 +#define A2MP_STATUS_COLLISION_OCCURED 0x03 +#define A2MP_STATUS_DISCONN_REQ_RECVD 0x04 +#define A2MP_STATUS_PHYS_LINK_EXISTS 0x05 +#define A2MP_STATUS_SECURITY_VIOLATION 0x06 + +#ifdef __cplusplus +} +#endif + +#endif /* __A2MP_H */ diff --git a/lib/amp.h b/lib/amp.h new file mode 100644 index 0000000..27aab1d --- /dev/null +++ b/lib/amp.h @@ -0,0 +1,172 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010-2011 Code Aurora Forum. All rights reserved. + * Copyright (C) 2012 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef __AMP_H +#define __AMP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define AMP_MGR_CID 0x03 + +/* AMP manager codes */ +#define AMP_COMMAND_REJ 0x01 +#define AMP_DISCOVER_REQ 0x02 +#define AMP_DISCOVER_RSP 0x03 +#define AMP_CHANGE_NOTIFY 0x04 +#define AMP_CHANGE_RSP 0x05 +#define AMP_INFO_REQ 0x06 +#define AMP_INFO_RSP 0x07 +#define AMP_ASSOC_REQ 0x08 +#define AMP_ASSOC_RSP 0x09 +#define AMP_LINK_REQ 0x0a +#define AMP_LINK_RSP 0x0b +#define AMP_DISCONN_REQ 0x0c +#define AMP_DISCONN_RSP 0x0d + +typedef struct { + uint8_t code; + uint8_t ident; + uint16_t len; +} __attribute__ ((packed)) amp_mgr_hdr; +#define AMP_MGR_HDR_SIZE 4 + +/* AMP ASSOC structure */ +typedef struct { + uint8_t type_id; + uint16_t len; + uint8_t data[0]; +} __attribute__ ((packed)) amp_assoc_tlv; + +typedef struct { + uint16_t reason; +} __attribute__ ((packed)) amp_cmd_rej_parms; + +typedef struct { + uint16_t mtu; + uint16_t mask; +} __attribute__ ((packed)) amp_discover_req_parms; + +typedef struct { + uint16_t mtu; + uint16_t mask; + uint8_t controller_list[0]; +} __attribute__ ((packed)) amp_discover_rsp_parms; + +typedef struct { + uint8_t id; +} __attribute__ ((packed)) amp_info_req_parms; + +typedef struct { + uint8_t id; + uint8_t status; + uint32_t total_bandwidth; + uint32_t max_bandwidth; + uint32_t min_latency; + uint16_t pal_caps; + uint16_t assoc_size; +} __attribute__ ((packed)) amp_info_rsp_parms; + +typedef struct { + uint8_t id; + uint8_t status; + amp_assoc_tlv assoc; +} __attribute__ ((packed)) amp_assoc_rsp_parms; + +typedef struct { + uint8_t local_id; + uint8_t remote_id; + amp_assoc_tlv assoc; +} __attribute__ ((packed)) amp_link_req_parms; + +typedef struct { + uint8_t local_id; + uint8_t remote_id; + uint8_t status; +} __attribute__ ((packed)) amp_link_rsp_parms; + +typedef struct { + uint8_t local_id; + uint8_t remote_id; +} __attribute__ ((packed)) amp_disconn_req_parms; + +#define A2MP_MAC_ADDR_TYPE 1 +#define A2MP_PREF_CHANLIST_TYPE 2 +#define A2MP_CONNECTED_CHAN 3 +#define A2MP_PAL_CAP_TYPE 4 +#define A2MP_PAL_VER_INFO 5 + +struct amp_tlv { + uint8_t type; + uint16_t len; + uint8_t val[0]; +} __attribute__ ((packed)); + +struct amp_pal_ver { + uint8_t ver; + uint16_t company_id; + uint16_t sub_ver; +} __attribute__ ((packed)); + +struct amp_country_triplet { + union { + struct { + uint8_t first_channel; + uint8_t num_channels; + int8_t max_power; + } __attribute__ ((packed)) chans; + struct { + uint8_t reg_extension_id; + uint8_t reg_class; + uint8_t coverage_class; + } __attribute__ ((packed)) ext; + }; +} __attribute__ ((packed)); + +struct amp_chan_list { + uint8_t country_code[3]; + struct amp_country_triplet triplets[0]; +} __attribute__ ((packed)); + +#define AMP_COMMAND_NOT_RECOGNIZED 0x0000 + +/* AMP controller status */ +#define AMP_CT_POWERED_DOWN 0x00 +#define AMP_CT_BLUETOOTH_ONLY 0x01 +#define AMP_CT_NO_CAPACITY 0x02 +#define AMP_CT_LOW_CAPACITY 0x03 +#define AMP_CT_MEDIUM_CAPACITY 0x04 +#define AMP_CT_HIGH_CAPACITY 0x05 +#define AMP_CT_FULL_CAPACITY 0x06 + +/* AMP response status */ +#define AMP_STATUS_SUCCESS 0x00 +#define AMP_STATUS_INVALID_CTRL_ID 0x01 +#define AMP_STATUS_UNABLE_START_LINK_CREATION 0x02 +#define AMP_STATUS_NO_PHYSICAL_LINK_EXISTS 0x02 +#define AMP_STATUS_COLLISION_OCCURED 0x03 +#define AMP_STATUS_DISCONN_REQ_RECVD 0x04 +#define AMP_STATUS_PHYS_LINK_EXISTS 0x05 +#define AMP_STATUS_SECURITY_VIOLATION 0x06 + +#ifdef __cplusplus +} +#endif + +#endif /* __AMP_H */ diff --git a/lib/bluetooth.c b/lib/bluetooth.c new file mode 100644 index 0000000..effc7f4 --- /dev/null +++ b/lib/bluetooth.c @@ -0,0 +1,2346 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bluetooth.h" +#include "hci.h" + +void baswap(bdaddr_t *dst, const bdaddr_t *src) +{ + register unsigned char *d = (unsigned char *) dst; + register const unsigned char *s = (const unsigned char *) src; + register int i; + + for (i = 0; i < 6; i++) + d[i] = s[5-i]; +} + +char *batostr(const bdaddr_t *ba) +{ + char *str = bt_malloc(18); + if (!str) + return NULL; + + sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + ba->b[0], ba->b[1], ba->b[2], + ba->b[3], ba->b[4], ba->b[5]); + + return str; +} + +bdaddr_t *strtoba(const char *str) +{ + bdaddr_t b; + bdaddr_t *ba = bt_malloc(sizeof(*ba)); + + if (ba) { + str2ba(str, &b); + baswap(ba, &b); + } + + return ba; +} + +int ba2str(const bdaddr_t *ba, char *str) +{ + return sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + ba->b[5], ba->b[4], ba->b[3], ba->b[2], ba->b[1], ba->b[0]); +} + +int str2ba(const char *str, bdaddr_t *ba) +{ + int i; + + if (bachk(str) < 0) { + memset(ba, 0, sizeof(*ba)); + return -1; + } + + for (i = 5; i >= 0; i--, str += 3) + ba->b[i] = strtol(str, NULL, 16); + + return 0; +} + +int ba2oui(const bdaddr_t *ba, char *str) +{ + return sprintf(str, "%2.2X-%2.2X-%2.2X", ba->b[5], ba->b[4], ba->b[3]); +} + +int bachk(const char *str) +{ + if (!str) + return -1; + + if (strlen(str) != 17) + return -1; + + while (*str) { + if (!isxdigit(*str++)) + return -1; + + if (!isxdigit(*str++)) + return -1; + + if (*str == 0) + break; + + if (*str++ != ':') + return -1; + } + + return 0; +} + +int baprintf(const char *format, ...) +{ + va_list ap; + int len; + + va_start(ap, format); + len = vprintf(format, ap); + va_end(ap); + + return len; +} + +int bafprintf(FILE *stream, const char *format, ...) +{ + va_list ap; + int len; + + va_start(ap, format); + len = vfprintf(stream, format, ap); + va_end(ap); + + return len; +} + +int basprintf(char *str, const char *format, ...) +{ + va_list ap; + int len; + + va_start(ap, format); + len = vsnprintf(str, (~0U) >> 1, format, ap); + va_end(ap); + + return len; +} + +int basnprintf(char *str, size_t size, const char *format, ...) +{ + va_list ap; + int len; + + va_start(ap, format); + len = vsnprintf(str, size, format, ap); + va_end(ap); + + return len; +} + +void *bt_malloc(size_t size) +{ + return malloc(size); +} + +void bt_free(void *ptr) +{ + free(ptr); +} + +/* Bluetooth error codes to Unix errno mapping */ +int bt_error(uint16_t code) +{ + switch (code) { + case 0: + return 0; + case HCI_UNKNOWN_COMMAND: + return EBADRQC; + case HCI_NO_CONNECTION: + return ENOTCONN; + case HCI_HARDWARE_FAILURE: + return EIO; + case HCI_PAGE_TIMEOUT: + return EHOSTDOWN; + case HCI_AUTHENTICATION_FAILURE: + return EACCES; + case HCI_PIN_OR_KEY_MISSING: + return EINVAL; + case HCI_MEMORY_FULL: + return ENOMEM; + case HCI_CONNECTION_TIMEOUT: + return ETIMEDOUT; + case HCI_MAX_NUMBER_OF_CONNECTIONS: + case HCI_MAX_NUMBER_OF_SCO_CONNECTIONS: + return EMLINK; + case HCI_ACL_CONNECTION_EXISTS: + return EALREADY; + case HCI_COMMAND_DISALLOWED: + case HCI_TRANSACTION_COLLISION: + case HCI_ROLE_SWITCH_PENDING: + return EBUSY; + case HCI_REJECTED_LIMITED_RESOURCES: + case HCI_REJECTED_PERSONAL: + case HCI_QOS_REJECTED: + return ECONNREFUSED; + case HCI_HOST_TIMEOUT: + return ETIMEDOUT; + case HCI_UNSUPPORTED_FEATURE: + case HCI_QOS_NOT_SUPPORTED: + case HCI_PAIRING_NOT_SUPPORTED: + case HCI_CLASSIFICATION_NOT_SUPPORTED: + case HCI_UNSUPPORTED_LMP_PARAMETER_VALUE: + case HCI_PARAMETER_OUT_OF_RANGE: + case HCI_QOS_UNACCEPTABLE_PARAMETER: + return EOPNOTSUPP; + case HCI_INVALID_PARAMETERS: + case HCI_SLOT_VIOLATION: + return EINVAL; + case HCI_OE_USER_ENDED_CONNECTION: + case HCI_OE_LOW_RESOURCES: + case HCI_OE_POWER_OFF: + return ECONNRESET; + case HCI_CONNECTION_TERMINATED: + return ECONNABORTED; + case HCI_REPEATED_ATTEMPTS: + return ELOOP; + case HCI_REJECTED_SECURITY: + case HCI_PAIRING_NOT_ALLOWED: + case HCI_INSUFFICIENT_SECURITY: + return EACCES; + case HCI_UNSUPPORTED_REMOTE_FEATURE: + return EPROTONOSUPPORT; + case HCI_SCO_OFFSET_REJECTED: + return ECONNREFUSED; + case HCI_UNKNOWN_LMP_PDU: + case HCI_INVALID_LMP_PARAMETERS: + case HCI_LMP_ERROR_TRANSACTION_COLLISION: + case HCI_LMP_PDU_NOT_ALLOWED: + case HCI_ENCRYPTION_MODE_NOT_ACCEPTED: + return EPROTO; + default: + return ENOSYS; + } +} + +const char *bt_compidtostr(int compid) +{ + switch (compid) { + case 0: + return "Ericsson Technology Licensing"; + case 1: + return "Nokia Mobile Phones"; + case 2: + return "Intel Corp."; + case 3: + return "IBM Corp."; + case 4: + return "Toshiba Corp."; + case 5: + return "3Com"; + case 6: + return "Microsoft"; + case 7: + return "Lucent"; + case 8: + return "Motorola"; + case 9: + return "Infineon Technologies AG"; + case 10: + return "Cambridge Silicon Radio"; + case 11: + return "Silicon Wave"; + case 12: + return "Digianswer A/S"; + case 13: + return "Texas Instruments Inc."; + case 14: + return "Ceva, Inc. (formerly Parthus Technologies, Inc.)"; + case 15: + return "Broadcom Corporation"; + case 16: + return "Mitel Semiconductor"; + case 17: + return "Widcomm, Inc"; + case 18: + return "Zeevo, Inc."; + case 19: + return "Atmel Corporation"; + case 20: + return "Mitsubishi Electric Corporation"; + case 21: + return "RTX Telecom A/S"; + case 22: + return "KC Technology Inc."; + case 23: + return "NewLogic"; + case 24: + return "Transilica, Inc."; + case 25: + return "Rohde & Schwarz GmbH & Co. KG"; + case 26: + return "TTPCom Limited"; + case 27: + return "Signia Technologies, Inc."; + case 28: + return "Conexant Systems Inc."; + case 29: + return "Qualcomm"; + case 30: + return "Inventel"; + case 31: + return "AVM Berlin"; + case 32: + return "BandSpeed, Inc."; + case 33: + return "Mansella Ltd"; + case 34: + return "NEC Corporation"; + case 35: + return "WavePlus Technology Co., Ltd."; + case 36: + return "Alcatel"; + case 37: + return "NXP Semiconductors (formerly Philips Semiconductors)"; + case 38: + return "C Technologies"; + case 39: + return "Open Interface"; + case 40: + return "R F Micro Devices"; + case 41: + return "Hitachi Ltd"; + case 42: + return "Symbol Technologies, Inc."; + case 43: + return "Tenovis"; + case 44: + return "Macronix International Co. Ltd."; + case 45: + return "GCT Semiconductor"; + case 46: + return "Norwood Systems"; + case 47: + return "MewTel Technology Inc."; + case 48: + return "ST Microelectronics"; + case 49: + return "Synopsys, Inc."; + case 50: + return "Red-M (Communications) Ltd"; + case 51: + return "Commil Ltd"; + case 52: + return "Computer Access Technology Corporation (CATC)"; + case 53: + return "Eclipse (HQ Espana) S.L."; + case 54: + return "Renesas Electronics Corporation"; + case 55: + return "Mobilian Corporation"; + case 56: + return "Terax"; + case 57: + return "Integrated System Solution Corp."; + case 58: + return "Matsushita Electric Industrial Co., Ltd."; + case 59: + return "Gennum Corporation"; + case 60: + return "BlackBerry Limited (formerly Research In Motion)"; + case 61: + return "IPextreme, Inc."; + case 62: + return "Systems and Chips, Inc."; + case 63: + return "Bluetooth SIG, Inc."; + case 64: + return "Seiko Epson Corporation"; + case 65: + return "Integrated Silicon Solution Taiwan, Inc."; + case 66: + return "CONWISE Technology Corporation Ltd"; + case 67: + return "PARROT SA"; + case 68: + return "Socket Mobile"; + case 69: + return "Atheros Communications, Inc."; + case 70: + return "MediaTek, Inc."; + case 71: + return "Bluegiga"; + case 72: + return "Marvell Technology Group Ltd."; + case 73: + return "3DSP Corporation"; + case 74: + return "Accel Semiconductor Ltd."; + case 75: + return "Continental Automotive Systems"; + case 76: + return "Apple, Inc."; + case 77: + return "Staccato Communications, Inc."; + case 78: + return "Avago Technologies"; + case 79: + return "APT Licensing Ltd."; + case 80: + return "SiRF Technology"; + case 81: + return "Tzero Technologies, Inc."; + case 82: + return "J&M Corporation"; + case 83: + return "Free2move AB"; + case 84: + return "3DiJoy Corporation"; + case 85: + return "Plantronics, Inc."; + case 86: + return "Sony Ericsson Mobile Communications"; + case 87: + return "Harman International Industries, Inc."; + case 88: + return "Vizio, Inc."; + case 89: + return "Nordic Semiconductor ASA"; + case 90: + return "EM Microelectronic-Marin SA"; + case 91: + return "Ralink Technology Corporation"; + case 92: + return "Belkin International, Inc."; + case 93: + return "Realtek Semiconductor Corporation"; + case 94: + return "Stonestreet One, LLC"; + case 95: + return "Wicentric, Inc."; + case 96: + return "RivieraWaves S.A.S"; + case 97: + return "RDA Microelectronics"; + case 98: + return "Gibson Guitars"; + case 99: + return "MiCommand Inc."; + case 100: + return "Band XI International, LLC"; + case 101: + return "Hewlett-Packard Company"; + case 102: + return "9Solutions Oy"; + case 103: + return "GN Netcom A/S"; + case 104: + return "General Motors"; + case 105: + return "A&D Engineering, Inc."; + case 106: + return "MindTree Ltd."; + case 107: + return "Polar Electro OY"; + case 108: + return "Beautiful Enterprise Co., Ltd."; + case 109: + return "BriarTek, Inc."; + case 110: + return "Summit Data Communications, Inc."; + case 111: + return "Sound ID"; + case 112: + return "Monster, LLC"; + case 113: + return "connectBlue AB"; + case 114: + return "ShangHai Super Smart Electronics Co. Ltd."; + case 115: + return "Group Sense Ltd."; + case 116: + return "Zomm, LLC"; + case 117: + return "Samsung Electronics Co. Ltd."; + case 118: + return "Creative Technology Ltd."; + case 119: + return "Laird Technologies"; + case 120: + return "Nike, Inc."; + case 121: + return "lesswire AG"; + case 122: + return "MStar Semiconductor, Inc."; + case 123: + return "Hanlynn Technologies"; + case 124: + return "A & R Cambridge"; + case 125: + return "Seers Technology Co. Ltd"; + case 126: + return "Sports Tracking Technologies Ltd."; + case 127: + return "Autonet Mobile"; + case 128: + return "DeLorme Publishing Company, Inc."; + case 129: + return "WuXi Vimicro"; + case 130: + return "Sennheiser Communications A/S"; + case 131: + return "TimeKeeping Systems, Inc."; + case 132: + return "Ludus Helsinki Ltd."; + case 133: + return "BlueRadios, Inc."; + case 134: + return "equinox AG"; + case 135: + return "Garmin International, Inc."; + case 136: + return "Ecotest"; + case 137: + return "GN ReSound A/S"; + case 138: + return "Jawbone"; + case 139: + return "Topcon Positioning Systems, LLC"; + case 140: + return "Gimbal Inc. (formerly Qualcomm Labs, Inc. and Qualcomm Retail Solutions, Inc.)"; + case 141: + return "Zscan Software"; + case 142: + return "Quintic Corp."; + case 143: + return "Telit Wireless Solutions GmbH (Formerly Stollman E+V GmbH)"; + case 144: + return "Funai Electric Co., Ltd."; + case 145: + return "Advanced PANMOBIL Systems GmbH & Co. KG"; + case 146: + return "ThinkOptics, Inc."; + case 147: + return "Universal Electronics, Inc."; + case 148: + return "Airoha Technology Corp."; + case 149: + return "NEC Lighting, Ltd."; + case 150: + return "ODM Technology, Inc."; + case 151: + return "ConnecteDevice Ltd."; + case 152: + return "zer01.tv GmbH"; + case 153: + return "i.Tech Dynamic Global Distribution Ltd."; + case 154: + return "Alpwise"; + case 155: + return "Jiangsu Toppower Automotive Electronics Co., Ltd."; + case 156: + return "Colorfy, Inc."; + case 157: + return "Geoforce Inc."; + case 158: + return "Bose Corporation"; + case 159: + return "Suunto Oy"; + case 160: + return "Kensington Computer Products Group"; + case 161: + return "SR-Medizinelektronik"; + case 162: + return "Vertu Corporation Limited"; + case 163: + return "Meta Watch Ltd."; + case 164: + return "LINAK A/S"; + case 165: + return "OTL Dynamics LLC"; + case 166: + return "Panda Ocean Inc."; + case 167: + return "Visteon Corporation"; + case 168: + return "ARP Devices Limited"; + case 169: + return "Magneti Marelli S.p.A"; + case 170: + return "CAEN RFID srl"; + case 171: + return "Ingenieur-Systemgruppe Zahn GmbH"; + case 172: + return "Green Throttle Games"; + case 173: + return "Peter Systemtechnik GmbH"; + case 174: + return "Omegawave Oy"; + case 175: + return "Cinetix"; + case 176: + return "Passif Semiconductor Corp"; + case 177: + return "Saris Cycling Group, Inc"; + case 178: + return "Bekey A/S"; + case 179: + return "Clarinox Technologies Pty. Ltd."; + case 180: + return "BDE Technology Co., Ltd."; + case 181: + return "Swirl Networks"; + case 182: + return "Meso international"; + case 183: + return "TreLab Ltd"; + case 184: + return "Qualcomm Innovation Center, Inc. (QuIC)"; + case 185: + return "Johnson Controls, Inc."; + case 186: + return "Starkey Laboratories Inc."; + case 187: + return "S-Power Electronics Limited"; + case 188: + return "Ace Sensor Inc"; + case 189: + return "Aplix Corporation"; + case 190: + return "AAMP of America"; + case 191: + return "Stalmart Technology Limited"; + case 192: + return "AMICCOM Electronics Corporation"; + case 193: + return "Shenzhen Excelsecu Data Technology Co.,Ltd"; + case 194: + return "Geneq Inc."; + case 195: + return "adidas AG"; + case 196: + return "LG Electronics"; + case 197: + return "Onset Computer Corporation"; + case 198: + return "Selfly BV"; + case 199: + return "Quuppa Oy."; + case 200: + return "GeLo Inc"; + case 201: + return "Evluma"; + case 202: + return "MC10"; + case 203: + return "Binauric SE"; + case 204: + return "Beats Electronics"; + case 205: + return "Microchip Technology Inc."; + case 206: + return "Elgato Systems GmbH"; + case 207: + return "ARCHOS SA"; + case 208: + return "Dexcom, Inc."; + case 209: + return "Polar Electro Europe B.V."; + case 210: + return "Dialog Semiconductor B.V."; + case 211: + return "Taixingbang Technology (HK) Co,. LTD."; + case 212: + return "Kawantech"; + case 213: + return "Austco Communication Systems"; + case 214: + return "Timex Group USA, Inc."; + case 215: + return "Qualcomm Technologies, Inc."; + case 216: + return "Qualcomm Connected Experiences, Inc."; + case 217: + return "Voyetra Turtle Beach"; + case 218: + return "txtr GmbH"; + case 219: + return "Biosentronics"; + case 220: + return "Procter & Gamble"; + case 221: + return "Hosiden Corporation"; + case 222: + return "Muzik LLC"; + case 223: + return "Misfit Wearables Corp"; + case 224: + return "Google"; + case 225: + return "Danlers Ltd"; + case 226: + return "Semilink Inc"; + case 227: + return "inMusic Brands, Inc"; + case 228: + return "L.S. Research Inc."; + case 229: + return "Eden Software Consultants Ltd."; + case 230: + return "Freshtemp"; + case 231: + return "KS Technologies"; + case 232: + return "ACTS Technologies"; + case 233: + return "Vtrack Systems"; + case 234: + return "Nielsen-Kellerman Company"; + case 235: + return "Server Technology, Inc."; + case 236: + return "BioResearch Associates"; + case 237: + return "Jolly Logic, LLC"; + case 238: + return "Above Average Outcomes, Inc."; + case 239: + return "Bitsplitters GmbH"; + case 240: + return "PayPal, Inc."; + case 241: + return "Witron Technology Limited"; + case 242: + return "Aether Things Inc. (formerly Morse Project Inc.)"; + case 243: + return "Kent Displays Inc."; + case 244: + return "Nautilus Inc."; + case 245: + return "Smartifier Oy"; + case 246: + return "Elcometer Limited"; + case 247: + return "VSN Technologies Inc."; + case 248: + return "AceUni Corp., Ltd."; + case 249: + return "StickNFind"; + case 250: + return "Crystal Code AB"; + case 251: + return "KOUKAAM a.s."; + case 252: + return "Delphi Corporation"; + case 253: + return "ValenceTech Limited"; + case 254: + return "Reserved"; + case 255: + return "Typo Products, LLC"; + case 256: + return "TomTom International BV"; + case 257: + return "Fugoo, Inc"; + case 258: + return "Keiser Corporation"; + case 259: + return "Bang & Olufsen A/S"; + case 260: + return "PLUS Locations Systems Pty Ltd"; + case 261: + return "Ubiquitous Computing Technology Corporation"; + case 262: + return "Innovative Yachtter Solutions"; + case 263: + return "William Demant Holding A/S"; + case 264: + return "Chicony Electronics Co., Ltd."; + case 265: + return "Atus BV"; + case 266: + return "Codegate Ltd."; + case 267: + return "ERi, Inc."; + case 268: + return "Transducers Direct, LLC"; + case 269: + return "Fujitsu Ten Limited"; + case 270: + return "Audi AG"; + case 271: + return "HiSilicon Technologies Co., Ltd."; + case 272: + return "Nippon Seiki Co., Ltd."; + case 273: + return "Steelseries ApS"; + case 274: + return "Visybl Inc."; + case 275: + return "Openbrain Technologies, Co., Ltd."; + case 276: + return "Xensr"; + case 277: + return "e.solutions"; + case 278: + return "1OAK Technologies"; + case 279: + return "Wimoto Technologies Inc"; + case 280: + return "Radius Networks, Inc."; + case 281: + return "Wize Technology Co., Ltd."; + case 282: + return "Qualcomm Labs, Inc."; + case 283: + return "Aruba Networks"; + case 284: + return "Baidu"; + case 285: + return "Arendi AG"; + case 286: + return "Skoda Auto a.s."; + case 287: + return "Volkswagen AG"; + case 288: + return "Porsche AG"; + case 289: + return "Sino Wealth Electronic Ltd."; + case 290: + return "AirTurn, Inc."; + case 291: + return "Kinsa, Inc."; + case 292: + return "HID Global"; + case 293: + return "SEAT es"; + case 294: + return "Promethean Ltd."; + case 295: + return "Salutica Allied Solutions"; + case 296: + return "GPSI Group Pty Ltd"; + case 297: + return "Nimble Devices Oy"; + case 298: + return "Changzhou Yongse Infotech Co., Ltd"; + case 299: + return "SportIQ"; + case 300: + return "TEMEC Instruments B.V."; + case 301: + return "Sony Corporation"; + case 302: + return "ASSA ABLOY"; + case 303: + return "Clarion Co., Ltd."; + case 304: + return "Warehouse Innovations"; + case 305: + return "Cypress Semiconductor Corporation"; + case 306: + return "MADS Inc"; + case 307: + return "Blue Maestro Limited"; + case 308: + return "Resolution Products, Inc."; + case 309: + return "Airewear LLC"; + case 310: + return "Seed Labs, Inc. (formerly ETC sp. z.o.o.)"; + case 311: + return "Prestigio Plaza Ltd."; + case 312: + return "NTEO Inc."; + case 313: + return "Focus Systems Corporation"; + case 314: + return "Tencent Holdings Limited"; + case 315: + return "Allegion"; + case 316: + return "Murata Manufacuring Co., Ltd."; + case 317: + return "WirelessWERX"; + case 318: + return "Nod, Inc."; + case 319: + return "B&B Manufacturing Company"; + case 320: + return "Alpine Electronics (China) Co., Ltd"; + case 321: + return "FedEx Services"; + case 322: + return "Grape Systems Inc."; + case 323: + return "Bkon Connect"; + case 324: + return "Lintech GmbH"; + case 325: + return "Novatel Wireless"; + case 326: + return "Ciright"; + case 327: + return "Mighty Cast, Inc."; + case 328: + return "Ambimat Electronics"; + case 329: + return "Perytons Ltd."; + case 330: + return "Tivoli Audio, LLC"; + case 331: + return "Master Lock"; + case 332: + return "Mesh-Net Ltd"; + case 333: + return "Huizhou Desay SV Automotive CO., LTD."; + case 334: + return "Tangerine, Inc."; + case 335: + return "B&W Group Ltd."; + case 336: + return "Pioneer Corporation"; + case 337: + return "OnBeep"; + case 338: + return "Vernier Software & Technology"; + case 339: + return "ROL Ergo"; + case 340: + return "Pebble Technology"; + case 341: + return "NETATMO"; + case 342: + return "Accumulate AB"; + case 343: + return "Anhui Huami Information Technology Co., Ltd."; + case 344: + return "Inmite s.r.o."; + case 345: + return "ChefSteps, Inc."; + case 346: + return "micas AG"; + case 347: + return "Biomedical Research Ltd."; + case 348: + return "Pitius Tec S.L."; + case 349: + return "Estimote, Inc."; + case 350: + return "Unikey Technologies, Inc."; + case 351: + return "Timer Cap Co."; + case 352: + return "AwoX"; + case 353: + return "yikes"; + case 354: + return "MADSGlobal NZ Ltd."; + case 355: + return "PCH International"; + case 356: + return "Qingdao Yeelink Information Technology Co., Ltd."; + case 357: + return "Milwaukee Tool (formerly Milwaukee Electric Tools)"; + case 358: + return "MISHIK Pte Ltd"; + case 359: + return "Bayer HealthCare"; + case 360: + return "Spicebox LLC"; + case 361: + return "emberlight"; + case 362: + return "Cooper-Atkins Corporation"; + case 363: + return "Qblinks"; + case 364: + return "MYSPHERA"; + case 365: + return "LifeScan Inc"; + case 366: + return "Volantic AB"; + case 367: + return "Podo Labs, Inc"; + case 368: + return "F. Hoffmann-La Roche AG"; + case 369: + return "Amazon Fulfillment Service"; + case 370: + return "Connovate Technology Private Limited"; + case 371: + return "Kocomojo, LLC"; + case 372: + return "Everykey LLC"; + case 373: + return "Dynamic Controls"; + case 374: + return "SentriLock"; + case 375: + return "I-SYST inc."; + case 376: + return "CASIO COMPUTER CO., LTD."; + case 377: + return "LAPIS Semiconductor Co., Ltd."; + case 378: + return "Telemonitor, Inc."; + case 379: + return "taskit GmbH"; + case 380: + return "Daimler AG"; + case 381: + return "BatAndCat"; + case 382: + return "BluDotz Ltd"; + case 383: + return "XTel ApS"; + case 384: + return "Gigaset Communications GmbH"; + case 385: + return "Gecko Health Innovations, Inc."; + case 386: + return "HOP Ubiquitous"; + case 387: + return "To Be Assigned"; + case 388: + return "Nectar"; + case 389: + return "bel'apps LLC"; + case 390: + return "CORE Lighting Ltd"; + case 391: + return "Seraphim Sense Ltd"; + case 392: + return "Unico RBC"; + case 393: + return "Physical Enterprises Inc."; + case 394: + return "Able Trend Technology Limited"; + case 395: + return "Konica Minolta, Inc."; + case 396: + return "Wilo SE"; + case 397: + return "Extron Design Services"; + case 398: + return "Fitbit, Inc."; + case 399: + return "Fireflies Systems"; + case 400: + return "Intelletto Technologies Inc."; + case 401: + return "FDK CORPORATION"; + case 402: + return "Cloudleaf, Inc"; + case 403: + return "Maveric Automation LLC"; + case 404: + return "Acoustic Stream Corporation"; + case 405: + return "Zuli"; + case 406: + return "Paxton Access Ltd"; + case 407: + return "WiSilica Inc"; + case 408: + return "VENGIT Korlátolt Felelősségű Társaság"; + case 409: + return "SALTO SYSTEMS S.L."; + case 410: + return "TRON Forum (formerly T-Engine Forum)"; + case 411: + return "CUBETECH s.r.o."; + case 412: + return "Cokiya Incorporated"; + case 413: + return "CVS Health"; + case 414: + return "Ceruus"; + case 415: + return "Strainstall Ltd"; + case 416: + return "Channel Enterprises (HK) Ltd."; + case 417: + return "FIAMM"; + case 418: + return "GIGALANE.CO.,LTD"; + case 419: + return "EROAD"; + case 420: + return "Mine Safety Appliances"; + case 421: + return "Icon Health and Fitness"; + case 422: + return "Asandoo GmbH"; + case 423: + return "ENERGOUS CORPORATION"; + case 424: + return "Taobao"; + case 425: + return "Canon Inc."; + case 426: + return "Geophysical Technology Inc."; + case 427: + return "Facebook, Inc."; + case 428: + return "Nipro Diagnostics, Inc."; + case 429: + return "FlightSafety International"; + case 430: + return "Earlens Corporation"; + case 431: + return "Sunrise Micro Devices, Inc."; + case 432: + return "Star Micronics Co., Ltd."; + case 433: + return "Netizens Sp. z o.o."; + case 434: + return "Nymi Inc."; + case 435: + return "Nytec, Inc."; + case 436: + return "Trineo Sp. z o.o."; + case 437: + return "Nest Labs Inc."; + case 438: + return "LM Technologies Ltd"; + case 439: + return "General Electric Company"; + case 440: + return "i+D3 S.L."; + case 441: + return "HANA Micron"; + case 442: + return "Stages Cycling LLC"; + case 443: + return "Cochlear Bone Anchored Solutions AB"; + case 444: + return "SenionLab AB"; + case 445: + return "Syszone Co., Ltd"; + case 446: + return "Pulsate Mobile Ltd."; + case 447: + return "Hong Kong HunterSun Electronic Limited"; + case 448: + return "pironex GmbH"; + case 449: + return "BRADATECH Corp."; + case 450: + return "Transenergooil AG"; + case 451: + return "Bunch"; + case 452: + return "DME Microelectronics"; + case 453: + return "Bitcraze AB"; + case 454: + return "HASWARE Inc."; + case 455: + return "Abiogenix Inc."; + case 456: + return "Poly-Control ApS"; + case 457: + return "Avi-on"; + case 458: + return "Laerdal Medical AS"; + case 459: + return "Fetch My Pet"; + case 460: + return "Sam Labs Ltd."; + case 461: + return "Chengdu Synwing Technology Ltd"; + case 462: + return "HOUWA SYSTEM DESIGN, k.k."; + case 463: + return "BSH"; + case 464: + return "Primus Inter Pares Ltd"; + case 465: + return "August"; + case 466: + return "Gill Electronics"; + case 467: + return "Sky Wave Design"; + case 468: + return "Newlab S.r.l."; + case 469: + return "ELAD srl"; + case 470: + return "G-wearables inc."; + case 471: + return "Squadrone Systems Inc."; + case 472: + return "Code Corporation"; + case 473: + return "Savant Systems LLC"; + case 474: + return "Logitech International SA"; + case 475: + return "Innblue Consulting"; + case 476: + return "iParking Ltd."; + case 477: + return "Koninklijke Philips Electronics N.V."; + case 478: + return "Minelab Electronics Pty Limited"; + case 479: + return "Bison Group Ltd."; + case 480: + return "Widex A/S"; + case 481: + return "Jolla Ltd"; + case 482: + return "Lectronix, Inc."; + case 483: + return "Caterpillar Inc"; + case 484: + return "Freedom Innovations"; + case 485: + return "Dynamic Devices Ltd"; + case 486: + return "Technology Solutions (UK) Ltd"; + case 487: + return "IPS Group Inc."; + case 488: + return "STIR"; + case 489: + return "Sano, Inc"; + case 490: + return "Advanced Application Design, Inc."; + case 491: + return "AutoMap LLC"; + case 492: + return "Spreadtrum Communications Shanghai Ltd"; + case 493: + return "CuteCircuit LTD"; + case 494: + return "Valeo Service"; + case 495: + return "Fullpower Technologies, Inc."; + case 496: + return "KloudNation"; + case 497: + return "Zebra Technologies Corporation"; + case 498: + return "Itron, Inc."; + case 499: + return "The University of Tokyo"; + case 500: + return "UTC Fire and Security"; + case 501: + return "Cool Webthings Limited"; + case 502: + return "DJO Global"; + case 503: + return "Gelliner Limited"; + case 504: + return "Anyka (Guangzhou) Microelectronics Technology Co, LTD"; + case 505: + return "Medtronic, Inc."; + case 506: + return "Gozio, Inc."; + case 507: + return "Form Lifting, LLC"; + case 508: + return "Wahoo Fitness, LLC"; + case 509: + return "Kontakt Micro-Location Sp. z o.o."; + case 510: + return "Radio System Corporation"; + case 511: + return "Freescale Semiconductor, Inc."; + case 512: + return "Verifone Systems PTe Ltd. Taiwan Branch"; + case 513: + return "AR Timing"; + case 514: + return "Rigado LLC"; + case 515: + return "Kemppi Oy"; + case 516: + return "Tapcentive Inc."; + case 517: + return "Smartbotics Inc."; + case 518: + return "Otter Products, LLC"; + case 519: + return "STEMP Inc."; + case 520: + return "LumiGeek LLC"; + case 521: + return "InvisionHeart Inc."; + case 522: + return "Macnica Inc."; + case 523: + return "Jaguar Land Rover Limited"; + case 524: + return "CoroWare Technologies, Inc"; + case 525: + return "Simplo Technology Co., LTD"; + case 526: + return "Omron Healthcare Co., LTD"; + case 527: + return "Comodule GMBH"; + case 528: + return "ikeGPS"; + case 529: + return "Telink Semiconductor Co. Ltd"; + case 530: + return "Interplan Co., Ltd"; + case 531: + return "Wyler AG"; + case 532: + return "IK Multimedia Production srl"; + case 533: + return "Lukoton Experience Oy"; + case 534: + return "MTI Ltd"; + case 535: + return "Tech4home, Lda"; + case 536: + return "Hiotech AB"; + case 537: + return "DOTT Limited"; + case 538: + return "Blue Speck Labs, LLC"; + case 539: + return "Cisco Systems Inc"; + case 540: + return "Mobicomm Inc"; + case 541: + return "Edamic"; + case 542: + return "Goodnet Ltd"; + case 543: + return "Luster Leaf Products Inc"; + case 544: + return "Manus Machina BV"; + case 545: + return "Mobiquity Networks Inc"; + case 546: + return "Praxis Dynamics"; + case 547: + return "Philip Morris Products S.A."; + case 548: + return "Comarch SA"; + case 549: + return "Nestlé Nespresso S.A."; + case 550: + return "Merlinia A/S"; + case 551: + return "LifeBEAM Technologies"; + case 552: + return "Twocanoes Labs, LLC"; + case 553: + return "Muoverti Limited"; + case 554: + return "Stamer Musikanlagen GMBH"; + case 555: + return "Tesla Motors"; + case 556: + return "Pharynks Corporation"; + case 557: + return "Lupine"; + case 558: + return "Siemens AG"; + case 559: + return "Huami (Shanghai) Culture Communication CO., LTD"; + case 560: + return "Foster Electric Company, Ltd"; + case 561: + return "ETA SA"; + case 562: + return "x-Senso Solutions Kft"; + case 563: + return "Shenzhen SuLong Communication Ltd"; + case 564: + return "FengFan (BeiJing) Technology Co, Ltd"; + case 565: + return "Qrio Inc"; + case 566: + return "Pitpatpet Ltd"; + case 567: + return "MSHeli s.r.l."; + case 568: + return "Trakm8 Ltd"; + case 569: + return "JIN CO, Ltd"; + case 570: + return "Alatech Technology"; + case 571: + return "Beijing CarePulse Electronic Technology Co, Ltd"; + case 572: + return "Awarepoint"; + case 573: + return "ViCentra B.V."; + case 574: + return "Raven Industries"; + case 575: + return "WaveWare Technologies"; + case 576: + return "Argenox Technologies"; + case 577: + return "Bragi GmbH"; + case 578: + return "16Lab Inc"; + case 579: + return "Masimo Corp"; + case 580: + return "Iotera Inc."; + case 581: + return "Endress+Hauser"; + case 582: + return "ACKme Networks, Inc."; + case 583: + return "FiftyThree Inc."; + case 584: + return "Parker Hannifin Corp"; + case 585: + return "Transcranial Ltd"; + case 586: + return "Uwatec AG"; + case 587: + return "Orlan LLC"; + case 588: + return "Blue Clover Devices"; + case 589: + return "M-Way Solutions GmbH"; + case 590: + return "Microtronics Engineering GmbH"; + case 591: + return "Schneider Schreibgeräte GmbH"; + case 592: + return "Sapphire Circuits LLC"; + case 593: + return "Lumo Bodytech Inc."; + case 594: + return "UKC Technosolution"; + case 595: + return "Xicato Inc."; + case 596: + return "Playbrush"; + case 597: + return "Dai Nippon Printing Co., Ltd."; + case 598: + return "G24 Power Limited"; + case 599: + return "AdBabble Local Commerce Inc."; + case 600: + return "Devialet SA"; + case 601: + return "ALTYOR"; + case 602: + return "University of Applied Sciences Valais/Haute Ecole Valaisanne"; + case 603: + return "Five Interactive, LLC dba Zendo"; + case 604: + return "NetEase (Hangzhou) Network co.Ltd."; + case 605: + return "Lexmark International Inc."; + case 606: + return "Fluke Corporation"; + case 607: + return "Yardarm Technologies"; + case 608: + return "SensaRx"; + case 609: + return "SECVRE GmbH"; + case 610: + return "Glacial Ridge Technologies"; + case 611: + return "Identiv, Inc."; + case 612: + return "DDS, Inc."; + case 613: + return "SMK Corporation"; + case 614: + return "Schawbel Technologies LLC"; + case 615: + return "XMI Systems SA"; + case 616: + return "Cerevo"; + case 617: + return "Torrox GmbH & Co KG"; + case 618: + return "Gemalto"; + case 619: + return "DEKA Research & Development Corp."; + case 620: + return "Domster Tadeusz Szydlowski"; + case 621: + return "Technogym SPA"; + case 622: + return "FLEURBAEY BVBA"; + case 623: + return "Aptcode Solutions"; + case 624: + return "LSI ADL Technology"; + case 625: + return "Animas Corp"; + case 626: + return "Alps Electric Co., Ltd."; + case 627: + return "OCEASOFT"; + case 628: + return "Motsai Research"; + case 629: + return "Geotab"; + case 630: + return "E.G.O. Elektro-Gerätebau GmbH"; + case 631: + return "bewhere inc"; + case 632: + return "Johnson Outdoors Inc"; + case 633: + return "steute Schaltgerate GmbH & Co. KG"; + case 634: + return "Ekomini inc."; + case 635: + return "DEFA AS"; + case 636: + return "Aseptika Ltd"; + case 637: + return "HUAWEI Technologies Co., Ltd. ( 华为技术有限公司 )"; + case 638: + return "HabitAware, LLC"; + case 639: + return "ruwido austria gmbh"; + case 640: + return "ITEC corporation"; + case 641: + return "StoneL"; + case 642: + return "Sonova AG"; + case 643: + return "Maven Machines, Inc."; + case 644: + return "Synapse Electronics"; + case 645: + return "Standard Innovation Inc."; + case 646: + return "RF Code, Inc."; + case 647: + return "Wally Ventures S.L."; + case 648: + return "Willowbank Electronics Ltd"; + case 649: + return "SK Telecom"; + case 650: + return "Jetro AS"; + case 651: + return "Code Gears LTD"; + case 652: + return "NANOLINK APS"; + case 653: + return "IF, LLC"; + case 654: + return "RF Digital Corp"; + case 655: + return "Church & Dwight Co., Inc"; + case 656: + return "Multibit Oy"; + case 657: + return "CliniCloud Inc"; + case 658: + return "SwiftSensors"; + case 659: + return "Blue Bite"; + case 660: + return "ELIAS GmbH"; + case 661: + return "Sivantos GmbH"; + case 662: + return "Petzl"; + case 663: + return "storm power ltd"; + case 664: + return "EISST Ltd"; + case 665: + return "Inexess Technology Simma KG"; + case 666: + return "Currant, Inc."; + case 667: + return "C2 Development, Inc."; + case 668: + return "Blue Sky Scientific, LLC"; + case 669: + return "ALOTTAZS LABS, LLC"; + case 670: + return "Kupson spol. s r.o."; + case 671: + return "Areus Engineering GmbH"; + case 672: + return "Impossible Camera GmbH"; + case 673: + return "InventureTrack Systems"; + case 674: + return "LockedUp"; + case 675: + return "Itude"; + case 676: + return "Pacific Lock Company"; + case 677: + return "Tendyron Corporation ( 天地融科技股份有限公司 )"; + case 678: + return "Robert Bosch GmbH"; + case 679: + return "Illuxtron international B.V."; + case 680: + return "miSport Ltd."; + case 681: + return "Chargelib"; + case 682: + return "Doppler Lab"; + case 683: + return "BBPOS Limited"; + case 684: + return "RTB Elektronik GmbH & Co. KG"; + case 685: + return "Rx Networks, Inc."; + case 686: + return "WeatherFlow, Inc."; + case 687: + return "Technicolor USA Inc."; + case 688: + return "Bestechnic(Shanghai),Ltd"; + case 689: + return "Raden Inc"; + case 690: + return "JouZen Oy"; + case 691: + return "CLABER S.P.A."; + case 692: + return "Hyginex, Inc."; + case 693: + return "HANSHIN ELECTRIC RAILWAY CO.,LTD."; + case 694: + return "Schneider Electric"; + case 695: + return "Oort Technologies LLC"; + case 696: + return "Chrono Therapeutics"; + case 697: + return "Rinnai Corporation"; + case 698: + return "Swissprime Technologies AG"; + case 699: + return "Koha.,Co.Ltd"; + case 700: + return "Genevac Ltd"; + case 701: + return "Chemtronics"; + case 702: + return "Seguro Technology Sp. z o.o."; + case 703: + return "Redbird Flight Simulations"; + case 704: + return "Dash Robotics"; + case 705: + return "LINE Corporation"; + case 706: + return "Guillemot Corporation"; + case 707: + return "Techtronic Power Tools Technology Limited"; + case 708: + return "Wilson Sporting Goods"; + case 709: + return "Lenovo (Singapore) Pte Ltd. ( 联想(新加坡) )"; + case 710: + return "Ayatan Sensors"; + case 711: + return "Electronics Tomorrow Limited"; + case 712: + return "VASCO Data Security International, Inc."; + case 713: + return "PayRange Inc."; + case 714: + return "ABOV Semiconductor"; + case 715: + return "AINA-Wireless Inc."; + case 716: + return "Eijkelkamp Soil & Water"; + case 717: + return "BMA ergonomics b.v."; + case 718: + return "Teva Branded Pharmaceutical Products R&D, Inc."; + case 719: + return "Anima"; + case 720: + return "3M"; + case 721: + return "Empatica Srl"; + case 722: + return "Afero, Inc."; + case 723: + return "Powercast Corporation"; + case 724: + return "Secuyou ApS"; + case 725: + return "OMRON Corporation"; + case 726: + return "Send Solutions"; + case 727: + return "NIPPON SYSTEMWARE CO.,LTD."; + case 728: + return "Neosfar"; + case 729: + return "Fliegl Agrartechnik GmbH"; + case 730: + return "Gilvader"; + case 731: + return "Digi International Inc (R)"; + case 732: + return "DeWalch Technologies, Inc."; + case 733: + return "Flint Rehabilitation Devices, LLC"; + case 734: + return "Samsung SDS Co., Ltd."; + case 735: + return "Blur Product Development"; + case 736: + return "University of Michigan"; + case 737: + return "Victron Energy BV"; + case 738: + return "NTT docomo"; + case 739: + return "Carmanah Technologies Corp."; + case 740: + return "Bytestorm Ltd."; + case 741: + return "Espressif Incorporated ( 乐鑫信息科技(上海)有限公司 )"; + case 742: + return "Unwire"; + case 743: + return "Connected Yard, Inc."; + case 744: + return "American Music Environments"; + case 745: + return "Sensogram Technologies, Inc."; + case 746: + return "Fujitsu Limited"; + case 747: + return "Ardic Technology"; + case 748: + return "Delta Systems, Inc"; + case 749: + return "HTC Corporation"; + case 750: + return "Citizen Holdings Co., Ltd."; + case 751: + return "SMART-INNOVATION.inc"; + case 752: + return "Blackrat Software"; + case 753: + return "The Idea Cave, LLC"; + case 754: + return "GoPro, Inc."; + case 755: + return "AuthAir, Inc"; + case 756: + return "Vensi, Inc."; + case 757: + return "Indagem Tech LLC"; + case 758: + return "Intemo Technologies"; + case 759: + return "DreamVisions co., Ltd."; + case 760: + return "Runteq Oy Ltd"; + case 761: + return "IMAGINATION TECHNOLOGIES LTD"; + case 762: + return "CoSTAR Technologies"; + case 763: + return "Clarius Mobile Health Corp."; + case 764: + return "Shanghai Frequen Microelectronics Co., Ltd."; + case 765: + return "Uwanna, Inc."; + case 766: + return "Lierda Science & Technology Group Co., Ltd."; + case 767: + return "Silicon Laboratories"; + case 768: + return "World Moto Inc."; + case 769: + return "Giatec Scientific Inc."; + case 770: + return "Loop Devices, Inc"; + case 771: + return "IACA electronique"; + case 772: + return "Martians Inc"; + case 773: + return "Swipp ApS"; + case 774: + return "Life Laboratory Inc."; + case 775: + return "FUJI INDUSTRIAL CO.,LTD."; + case 776: + return "Surefire, LLC"; + case 777: + return "Dolby Labs"; + case 778: + return "Ellisys"; + case 779: + return "Magnitude Lighting Converters"; + case 780: + return "Hilti AG"; + case 781: + return "Devdata S.r.l."; + case 782: + return "Deviceworx"; + case 783: + return "Shortcut Labs"; + case 784: + return "SGL Italia S.r.l."; + case 785: + return "PEEQ DATA"; + case 786: + return "Ducere Technologies Pvt Ltd"; + case 787: + return "DiveNav, Inc."; + case 788: + return "RIIG AI Sp. z o.o."; + case 789: + return "Thermo Fisher Scientific"; + case 790: + return "AG Measurematics Pvt. Ltd."; + case 791: + return "CHUO Electronics CO., LTD."; + case 792: + return "Aspenta International"; + case 793: + return "Eugster Frismag AG"; + case 794: + return "Amber wireless GmbH"; + case 795: + return "HQ Inc"; + case 796: + return "Lab Sensor Solutions"; + case 797: + return "Enterlab ApS"; + case 798: + return "Eyefi, Inc."; + case 799: + return "MetaSystem S.p.A"; + case 800: + return "SONO ELECTRONICS. CO., LTD"; + case 801: + return "Jewelbots"; + case 802: + return "Compumedics Limited"; + case 803: + return "Rotor Bike Components"; + case 804: + return "Astro, Inc."; + case 805: + return "Amotus Solutions"; + case 806: + return "Healthwear Technologies (Changzhou)Ltd"; + case 807: + return "Essex Electronics"; + case 808: + return "Grundfos A/S"; + case 809: + return "Eargo, Inc."; + case 810: + return "Electronic Design Lab"; + case 811: + return "ESYLUX"; + case 812: + return "NIPPON SMT.CO.,Ltd"; + case 813: + return "BM innovations GmbH"; + case 814: + return "indoormap"; + case 815: + return "OttoQ Inc"; + case 816: + return "North Pole Engineering"; + case 817: + return "3flares Technologies Inc."; + case 818: + return "Electrocompaniet A.S."; + case 819: + return "Mul-T-Lock"; + case 820: + return "Corentium AS"; + case 821: + return "Enlighted Inc"; + case 822: + return "GISTIC"; + case 823: + return "AJP2 Holdings, LLC"; + case 824: + return "COBI GmbH"; + case 825: + return "Blue Sky Scientific, LLC"; + case 826: + return "Appception, Inc."; + case 827: + return "Courtney Thorne Limited"; + case 828: + return "Virtuosys"; + case 829: + return "TPV Technology Limited"; + case 830: + return "Monitra SA"; + case 831: + return "Automation Components, Inc."; + case 832: + return "Letsense s.r.l."; + case 833: + return "Etesian Technologies LLC"; + case 834: + return "GERTEC BRASIL LTDA."; + case 835: + return "Drekker Development Pty. Ltd."; + case 836: + return "Whirl Inc"; + case 837: + return "Locus Positioning"; + case 838: + return "Acuity Brands Lighting, Inc"; + case 839: + return "Prevent Biometrics"; + case 840: + return "Arioneo"; + case 841: + return "VersaMe"; + case 842: + return "Vaddio"; + case 843: + return "Libratone A/S"; + case 844: + return "HM Electronics, Inc."; + case 845: + return "TASER International, Inc."; + case 846: + return "Safe Trust Inc."; + case 847: + return "Heartland Payment Systems"; + case 848: + return "Bitstrata Systems Inc."; + case 849: + return "Pieps GmbH"; + case 850: + return "iRiding(Xiamen)Technology Co.,Ltd."; + case 851: + return "Alpha Audiotronics, Inc."; + case 852: + return "TOPPAN FORMS CO.,LTD."; + case 853: + return "Sigma Designs, Inc."; + case 854: + return "Spectrum Brands, Inc."; + case 855: + return "Polymap Wireless"; + case 856: + return "MagniWare Ltd."; + case 857: + return "Novotec Medical GmbH"; + case 858: + return "Medicom Innovation Partner a/s"; + case 859: + return "Matrix Inc."; + case 860: + return "Eaton Corporation"; + case 861: + return "KYS"; + case 862: + return "Naya Health, Inc."; + case 863: + return "Acromag"; + case 864: + return "Insulet Corporation"; + case 865: + return "Wellinks Inc."; + case 866: + return "ON Semiconductor"; + case 867: + return "FREELAP SA"; + case 868: + return "Favero Electronics Srl"; + case 869: + return "BioMech Sensor LLC"; + case 870: + return "BOLTT Sports technologies Private limited"; + case 871: + return "Saphe International"; + case 872: + return "Metormote AB"; + case 873: + return "littleBits"; + case 874: + return "SetPoint Medical"; + case 875: + return "BRControls Products BV"; + case 876: + return "Zipcar"; + case 877: + return "AirBolt Pty Ltd"; + case 878: + return "KeepTruckin Inc"; + case 879: + return "Motiv, Inc."; + case 880: + return "Wazombi Labs OÜ"; + case 881: + return "ORBCOMM"; + case 882: + return "Nixie Labs, Inc."; + case 883: + return "AppNearMe Ltd"; + case 884: + return "Holman Industries"; + case 885: + return "Expain AS"; + case 886: + return "Electronic Temperature Instruments Ltd"; + case 887: + return "Plejd AB"; + case 888: + return "Propeller Health"; + case 889: + return "Shenzhen iMCO Electronic Technology Co.,Ltd"; + case 890: + return "Algoria"; + case 891: + return "Apption Labs Inc."; + case 892: + return "Cronologics Corporation"; + case 893: + return "MICRODIA Ltd."; + case 894: + return "lulabytes S.L."; + case 895: + return "Nestec S.A."; + case 896: + return "LLC \"MEGA-F service\""; + case 897: + return "Sharp Corporation"; + case 898: + return "Precision Outcomes Ltd"; + case 899: + return "Kronos Incorporated"; + case 900: + return "OCOSMOS Co., Ltd."; + case 901: + return "Embedded Electronic Solutions Ltd. dba e2Solutions"; + case 902: + return "Aterica Inc."; + case 903: + return "BluStor PMC, Inc."; + case 904: + return "Kapsch TrafficCom AB"; + case 905: + return "ActiveBlu Corporation"; + case 906: + return "Kohler Mira Limited"; + case 907: + return "Noke"; + case 908: + return "Appion Inc."; + case 909: + return "Resmed Ltd"; + case 910: + return "Crownstone B.V."; + case 911: + return "Xiaomi Inc."; + case 912: + return "INFOTECH s.r.o."; + case 913: + return "Thingsquare AB"; + case 914: + return "T&D"; + case 915: + return "LAVAZZA S.p.A."; + case 916: + return "Netclearance Systems, Inc."; + case 917: + return "SDATAWAY"; + case 918: + return "BLOKS GmbH"; + case 919: + return "LEGO System A/S"; + case 920: + return "Thetatronics Ltd"; + case 921: + return "Nikon Corporation"; + case 922: + return "NeST"; + case 923: + return "South Silicon Valley Microelectronics"; + case 924: + return "ALE International"; + case 925: + return "CareView Communications, Inc."; + case 926: + return "SchoolBoard Limited"; + case 927: + return "Molex Corporation"; + case 928: + return "IVT Wireless Limited"; + case 929: + return "Alpine Labs LLC"; + case 930: + return "Candura Instruments"; + case 931: + return "SmartMovt Technology Co., Ltd"; + case 932: + return "Token Zero Ltd"; + case 933: + return "ACE CAD Enterprise Co., Ltd. (ACECAD)"; + case 934: + return "Medela, Inc"; + case 935: + return "AeroScout"; + case 936: + return "Esrille Inc."; + case 937: + return "THINKERLY SRL"; + case 938: + return "Exon Sp. z o.o."; + case 939: + return "Meizu Technology Co., Ltd."; + case 940: + return "Smablo LTD"; + case 941: + return "XiQ"; + case 942: + return "Allswell Inc."; + case 943: + return "Comm-N-Sense Corp DBA Verigo"; + case 944: + return "VIBRADORM GmbH"; + case 945: + return "Otodata Wireless Network Inc."; + case 946: + return "Propagation Systems Limited"; + case 947: + return "Midwest Instruments & Controls"; + case 948: + return "Alpha Nodus, inc."; + case 949: + return "petPOMM, Inc"; + case 950: + return "Mattel"; + case 951: + return "Airbly Inc."; + case 952: + return "A-Safe Limited"; + case 953: + return "FREDERIQUE CONSTANT SA"; + case 954: + return "Maxscend Microelectronics Company Limited"; + case 955: + return "Abbott Diabetes Care"; + case 956: + return "ASB Bank Ltd"; + case 957: + return "amadas"; + case 958: + return "Applied Science, Inc."; + case 959: + return "iLumi Solutions Inc."; + case 960: + return "Arch Systems Inc."; + case 961: + return "Ember Technologies, Inc."; + case 962: + return "Snapchat Inc"; + case 963: + return "Casambi Technologies Oy"; + case 964: + return "Pico Technology Inc."; + case 965: + return "St. Jude Medical, Inc."; + case 966: + return "Intricon"; + case 967: + return "Structural Health Systems, Inc."; + case 968: + return "Avvel International"; + case 969: + return "Gallagher Group"; + case 970: + return "In2things Automation Pvt. Ltd."; + case 971: + return "SYSDEV Srl"; + case 972: + return "Vonkil Technologies Ltd"; + case 973: + return "Wynd Technologies, Inc."; + case 974: + return "CONTRINEX S.A."; + case 975: + return "MIRA, Inc."; + case 976: + return "Watteam Ltd"; + case 977: + return "Density Inc."; + case 978: + return "IOT Pot India Private Limited"; + case 979: + return "Sigma Connectivity AB"; + case 980: + return "PEG PEREGO SPA"; + case 981: + return "Wyzelink Systems Inc."; + case 982: + return "Yota Devices LTD"; + case 983: + return "FINSECUR"; + case 984: + return "Zen-Me Labs Ltd"; + case 985: + return "3IWare Co., Ltd."; + case 986: + return "EnOcean GmbH"; + case 987: + return "Instabeat, Inc"; + case 988: + return "Nima Labs"; + case 989: + return "Andreas Stihl AG & Co. KG"; + case 990: + return "Nathan Rhoades LLC"; + case 991: + return "Grob Technologies, LLC"; + case 992: + return "Actions (Zhuhai) Technology Co., Limited"; + case 993: + return "SPD Development Company Ltd"; + case 994: + return "Sensoan Oy"; + case 995: + return "Qualcomm Life Inc"; + case 996: + return "Chip-ing AG"; + case 997: + return "ffly4u"; + case 998: + return "IoT Instruments Oy"; + case 999: + return "TRUE Fitness Technology"; + case 1000: + return "Reiner Kartengeraete GmbH & Co. KG."; + case 1001: + return "SHENZHEN LEMONJOY TECHNOLOGY CO., LTD."; + case 1002: + return "Hello Inc."; + case 1003: + return "Evollve Inc."; + case 1004: + return "Jigowatts Inc."; + case 1005: + return "BASIC MICRO.COM,INC."; + case 1006: + return "CUBE TECHNOLOGIES"; + case 1007: + return "foolography GmbH"; + case 1008: + return "CLINK"; + case 1009: + return "Hestan Smart Cooking Inc."; + case 1010: + return "WindowMaster A/S"; + case 1011: + return "Flowscape AB"; + case 1012: + return "PAL Technologies Ltd"; + case 1013: + return "WHERE, Inc."; + case 1014: + return "Iton Technology Corp."; + case 1015: + return "Owl Labs Inc."; + case 1016: + return "Rockford Corp."; + case 1017: + return "Becon Technologies Co.,Ltd."; + case 1018: + return "Vyassoft Technologies Inc"; + case 1019: + return "Nox Medical"; + case 1020: + return "Kimberly-Clark"; + case 1021: + return "Trimble Navigation Ltd."; + case 1022: + return "Littelfuse"; + case 1023: + return "Withings"; + case 1024: + return "i-developer IT Beratung UG"; + case 1025: + return "リレーションズ株式会社"; + case 1026: + return "Sears Holdings Corporation"; + case 1027: + return "Gantner Electronic GmbH"; + case 1028: + return "Authomate Inc"; + case 1029: + return "Vertex International, Inc."; + case 1030: + return "Airtago"; + case 1031: + return "Swiss Audio SA"; + case 1032: + return "ToGetHome Inc."; + case 1033: + return "AXIS"; + case 1034: + return "Openmatics"; + case 1035: + return "Jana Care Inc."; + case 1036: + return "Senix Corporation"; + case 1037: + return "NorthStar Battery Company, LLC"; + case 65535: + return "internal use"; + default: + return "not assigned"; + } +} diff --git a/lib/bluetooth.h b/lib/bluetooth.h new file mode 100644 index 0000000..eb27926 --- /dev/null +++ b/lib/bluetooth.h @@ -0,0 +1,404 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __BLUETOOTH_H +#define __BLUETOOTH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include + +#ifndef AF_BLUETOOTH +#define AF_BLUETOOTH 31 +#define PF_BLUETOOTH AF_BLUETOOTH +#endif + +#define BTPROTO_L2CAP 0 +#define BTPROTO_HCI 1 +#define BTPROTO_SCO 2 +#define BTPROTO_RFCOMM 3 +#define BTPROTO_BNEP 4 +#define BTPROTO_CMTP 5 +#define BTPROTO_HIDP 6 +#define BTPROTO_AVDTP 7 + +#define SOL_HCI 0 +#define SOL_L2CAP 6 +#define SOL_SCO 17 +#define SOL_RFCOMM 18 + +#ifndef SOL_BLUETOOTH +#define SOL_BLUETOOTH 274 +#endif + +#define BT_SECURITY 4 +struct bt_security { + uint8_t level; + uint8_t key_size; +}; +#define BT_SECURITY_SDP 0 +#define BT_SECURITY_LOW 1 +#define BT_SECURITY_MEDIUM 2 +#define BT_SECURITY_HIGH 3 +#define BT_SECURITY_FIPS 4 + +#define BT_DEFER_SETUP 7 + +#define BT_FLUSHABLE 8 + +#define BT_FLUSHABLE_OFF 0 +#define BT_FLUSHABLE_ON 1 + +#define BT_POWER 9 +struct bt_power { + uint8_t force_active; +}; +#define BT_POWER_FORCE_ACTIVE_OFF 0 +#define BT_POWER_FORCE_ACTIVE_ON 1 + +#define BT_CHANNEL_POLICY 10 + +/* BR/EDR only (default policy) + * AMP controllers cannot be used. + * Channel move requests from the remote device are denied. + * If the L2CAP channel is currently using AMP, move the channel to BR/EDR. + */ +#define BT_CHANNEL_POLICY_BREDR_ONLY 0 + +/* BR/EDR Preferred + * Allow use of AMP controllers. + * If the L2CAP channel is currently on AMP, move it to BR/EDR. + * Channel move requests from the remote device are allowed. + */ +#define BT_CHANNEL_POLICY_BREDR_PREFERRED 1 + +/* AMP Preferred + * Allow use of AMP controllers + * If the L2CAP channel is currently on BR/EDR and AMP controller + * resources are available, initiate a channel move to AMP. + * Channel move requests from the remote device are allowed. + * If the L2CAP socket has not been connected yet, try to create + * and configure the channel directly on an AMP controller rather + * than BR/EDR. + */ +#define BT_CHANNEL_POLICY_AMP_PREFERRED 2 + +#define BT_VOICE 11 +struct bt_voice { + uint16_t setting; +}; + +#define BT_SNDMTU 12 +#define BT_RCVMTU 13 + +#define BT_VOICE_TRANSPARENT 0x0003 +#define BT_VOICE_CVSD_16BIT 0x0060 + +/* Connection and socket states */ +enum { + BT_CONNECTED = 1, /* Equal to TCP_ESTABLISHED to make net code happy */ + BT_OPEN, + BT_BOUND, + BT_LISTEN, + BT_CONNECT, + BT_CONNECT2, + BT_CONFIG, + BT_DISCONN, + BT_CLOSED +}; + +/* Byte order conversions */ +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htobs(d) (d) +#define htobl(d) (d) +#define htobll(d) (d) +#define btohs(d) (d) +#define btohl(d) (d) +#define btohll(d) (d) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define htobs(d) bswap_16(d) +#define htobl(d) bswap_32(d) +#define htobll(d) bswap_64(d) +#define btohs(d) bswap_16(d) +#define btohl(d) bswap_32(d) +#define btohll(d) bswap_64(d) +#else +#error "Unknown byte order" +#endif + +/* Bluetooth unaligned access */ +#define bt_get_unaligned(ptr) \ +__extension__ ({ \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v; \ +}) + +#define bt_put_unaligned(val, ptr) \ +do { \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v = (val); \ +} while(0) + +#if __BYTE_ORDER == __LITTLE_ENDIAN +static inline uint64_t bt_get_le64(const void *ptr) +{ + return bt_get_unaligned((const uint64_t *) ptr); +} + +static inline uint64_t bt_get_be64(const void *ptr) +{ + return bswap_64(bt_get_unaligned((const uint64_t *) ptr)); +} + +static inline uint32_t bt_get_le32(const void *ptr) +{ + return bt_get_unaligned((const uint32_t *) ptr); +} + +static inline uint32_t bt_get_be32(const void *ptr) +{ + return bswap_32(bt_get_unaligned((const uint32_t *) ptr)); +} + +static inline uint16_t bt_get_le16(const void *ptr) +{ + return bt_get_unaligned((const uint16_t *) ptr); +} + +static inline uint16_t bt_get_be16(const void *ptr) +{ + return bswap_16(bt_get_unaligned((const uint16_t *) ptr)); +} + +static inline void bt_put_le64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint64_t *) ptr); +} + +static inline void bt_put_be64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(bswap_64(val), (uint64_t *) ptr); +} + +static inline void bt_put_le32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint32_t *) ptr); +} + +static inline void bt_put_be32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(bswap_32(val), (uint32_t *) ptr); +} + +static inline void bt_put_le16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint16_t *) ptr); +} + +static inline void bt_put_be16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(bswap_16(val), (uint16_t *) ptr); +} + +#elif __BYTE_ORDER == __BIG_ENDIAN +static inline uint64_t bt_get_le64(const void *ptr) +{ + return bswap_64(bt_get_unaligned((const uint64_t *) ptr)); +} + +static inline uint64_t bt_get_be64(const void *ptr) +{ + return bt_get_unaligned((const uint64_t *) ptr); +} + +static inline uint32_t bt_get_le32(const void *ptr) +{ + return bswap_32(bt_get_unaligned((const uint32_t *) ptr)); +} + +static inline uint32_t bt_get_be32(const void *ptr) +{ + return bt_get_unaligned((const uint32_t *) ptr); +} + +static inline uint16_t bt_get_le16(const void *ptr) +{ + return bswap_16(bt_get_unaligned((const uint16_t *) ptr)); +} + +static inline uint16_t bt_get_be16(const void *ptr) +{ + return bt_get_unaligned((const uint16_t *) ptr); +} + +static inline void bt_put_le64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(bswap_64(val), (uint64_t *) ptr); +} + +static inline void bt_put_be64(uint64_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint64_t *) ptr); +} + +static inline void bt_put_le32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(bswap_32(val), (uint32_t *) ptr); +} + +static inline void bt_put_be32(uint32_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint32_t *) ptr); +} + +static inline void bt_put_le16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(bswap_16(val), (uint16_t *) ptr); +} + +static inline void bt_put_be16(uint16_t val, const void *ptr) +{ + bt_put_unaligned(val, (uint16_t *) ptr); +} +#else +#error "Unknown byte order" +#endif + +/* BD Address */ +typedef struct { + uint8_t b[6]; +} __attribute__((packed)) bdaddr_t; + +/* BD Address type */ +#define BDADDR_BREDR 0x00 +#define BDADDR_LE_PUBLIC 0x01 +#define BDADDR_LE_RANDOM 0x02 + +#define BDADDR_ANY (&(bdaddr_t) {{0, 0, 0, 0, 0, 0}}) +#define BDADDR_ALL (&(bdaddr_t) {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}) +#define BDADDR_LOCAL (&(bdaddr_t) {{0, 0, 0, 0xff, 0xff, 0xff}}) + +/* Copy, swap, convert BD Address */ +static inline int bacmp(const bdaddr_t *ba1, const bdaddr_t *ba2) +{ + return memcmp(ba1, ba2, sizeof(bdaddr_t)); +} +static inline void bacpy(bdaddr_t *dst, const bdaddr_t *src) +{ + memcpy(dst, src, sizeof(bdaddr_t)); +} + +void baswap(bdaddr_t *dst, const bdaddr_t *src); +bdaddr_t *strtoba(const char *str); +char *batostr(const bdaddr_t *ba); +int ba2str(const bdaddr_t *ba, char *str); +int str2ba(const char *str, bdaddr_t *ba); +int ba2oui(const bdaddr_t *ba, char *oui); +int bachk(const char *str); + +int baprintf(const char *format, ...); +int bafprintf(FILE *stream, const char *format, ...); +int basprintf(char *str, const char *format, ...); +int basnprintf(char *str, size_t size, const char *format, ...); + +void *bt_malloc(size_t size); +void bt_free(void *ptr); + +int bt_error(uint16_t code); +const char *bt_compidtostr(int id); + +typedef struct { + uint8_t data[16]; +} uint128_t; + +static inline void bswap_128(const void *src, void *dst) +{ + const uint8_t *s = (const uint8_t *) src; + uint8_t *d = (uint8_t *) dst; + int i; + + for (i = 0; i < 16; i++) + d[15 - i] = s[i]; +} + +#if __BYTE_ORDER == __BIG_ENDIAN + +#define ntoh64(x) (x) + +static inline void ntoh128(const uint128_t *src, uint128_t *dst) +{ + memcpy(dst, src, sizeof(uint128_t)); +} + +static inline void btoh128(const uint128_t *src, uint128_t *dst) +{ + bswap_128(src, dst); +} + +#else + +static inline uint64_t ntoh64(uint64_t n) +{ + uint64_t h; + uint64_t tmp = ntohl(n & 0x00000000ffffffff); + + h = ntohl(n >> 32); + h |= tmp << 32; + + return h; +} + +static inline void ntoh128(const uint128_t *src, uint128_t *dst) +{ + bswap_128(src, dst); +} + +static inline void btoh128(const uint128_t *src, uint128_t *dst) +{ + memcpy(dst, src, sizeof(uint128_t)); +} + +#endif + +#define hton64(x) ntoh64(x) +#define hton128(x, y) ntoh128(x, y) +#define htob128(x, y) btoh128(x, y) + +#ifdef __cplusplus +} +#endif + +#endif /* __BLUETOOTH_H */ diff --git a/lib/bluez.pc.in b/lib/bluez.pc.in new file mode 100644 index 0000000..3d6e596 --- /dev/null +++ b/lib/bluez.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: BlueZ +Description: Bluetooth protocol stack for Linux +Version: @VERSION@ +Libs: -L${libdir} -lbluetooth +Cflags: -I${includedir} diff --git a/lib/bnep.h b/lib/bnep.h new file mode 100644 index 0000000..e7c2c87 --- /dev/null +++ b/lib/bnep.h @@ -0,0 +1,162 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __BNEP_H +#define __BNEP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifndef ETH_ALEN +#define ETH_ALEN 6 /* from */ +#endif + +/* BNEP UUIDs */ +#define BNEP_BASE_UUID 0x0000000000001000800000805F9B34FB +#define BNEP_UUID16 0x02 +#define BNEP_UUID32 0x04 +#define BNEP_UUID128 0x16 + +#define BNEP_SVC_PANU 0x1115 +#define BNEP_SVC_NAP 0x1116 +#define BNEP_SVC_GN 0x1117 + +/* BNEP packet types */ +#define BNEP_GENERAL 0x00 +#define BNEP_CONTROL 0x01 +#define BNEP_COMPRESSED 0x02 +#define BNEP_COMPRESSED_SRC_ONLY 0x03 +#define BNEP_COMPRESSED_DST_ONLY 0x04 + +/* BNEP control types */ +#define BNEP_CMD_NOT_UNDERSTOOD 0x00 +#define BNEP_SETUP_CONN_REQ 0x01 +#define BNEP_SETUP_CONN_RSP 0x02 +#define BNEP_FILTER_NET_TYPE_SET 0x03 +#define BNEP_FILTER_NET_TYPE_RSP 0x04 +#define BNEP_FILTER_MULT_ADDR_SET 0x05 +#define BNEP_FILTER_MULT_ADDR_RSP 0x06 + +/* BNEP response messages */ +#define BNEP_SUCCESS 0x00 + +#define BNEP_CONN_INVALID_DST 0x01 +#define BNEP_CONN_INVALID_SRC 0x02 +#define BNEP_CONN_INVALID_SVC 0x03 +#define BNEP_CONN_NOT_ALLOWED 0x04 + +#define BNEP_FILTER_UNSUPPORTED_REQ 0x01 +#define BNEP_FILTER_INVALID_RANGE 0x02 +#define BNEP_FILTER_INVALID_MCADDR 0x02 +#define BNEP_FILTER_LIMIT_REACHED 0x03 +#define BNEP_FILTER_DENIED_SECURITY 0x04 + +/* L2CAP settings */ +#define BNEP_MTU 1691 +#define BNEP_FLUSH_TO 0xffff +#define BNEP_CONNECT_TO 15 +#define BNEP_FILTER_TO 15 + +#ifndef BNEP_PSM +#define BNEP_PSM 0x0f +#endif + +/* BNEP headers */ +#define BNEP_TYPE_MASK 0x7f +#define BNEP_EXT_HEADER 0x80 + +struct bnep_setup_conn_req { + uint8_t type; + uint8_t ctrl; + uint8_t uuid_size; + uint8_t service[0]; +} __attribute__((packed)); + +struct bnep_set_filter_req { + uint8_t type; + uint8_t ctrl; + uint16_t len; + uint8_t list[0]; +} __attribute__((packed)); + +struct bnep_ctrl_cmd_not_understood_cmd { + uint8_t type; + uint8_t ctrl; + uint8_t unkn_ctrl; +} __attribute__((packed)); + +struct bnep_control_rsp { + uint8_t type; + uint8_t ctrl; + uint16_t resp; +} __attribute__((packed)); + +struct bnep_ext_hdr { + uint8_t type; + uint8_t len; + uint8_t data[0]; +} __attribute__((packed)); + +/* BNEP ioctl defines */ +#define BNEPCONNADD _IOW('B', 200, int) +#define BNEPCONNDEL _IOW('B', 201, int) +#define BNEPGETCONNLIST _IOR('B', 210, int) +#define BNEPGETCONNINFO _IOR('B', 211, int) +#define BNEPGETSUPPFEAT _IOR('B', 212, int) + +#define BNEP_SETUP_RESPONSE 0 + +struct bnep_connadd_req { + int sock; /* Connected socket */ + uint32_t flags; + uint16_t role; + char device[16]; /* Name of the Ethernet device */ +}; + +struct bnep_conndel_req { + uint32_t flags; + uint8_t dst[ETH_ALEN]; +}; + +struct bnep_conninfo { + uint32_t flags; + uint16_t role; + uint16_t state; + uint8_t dst[ETH_ALEN]; + char device[16]; +}; + +struct bnep_connlist_req { + uint32_t cnum; + struct bnep_conninfo *ci; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __BNEP_H */ diff --git a/lib/cmtp.h b/lib/cmtp.h new file mode 100644 index 0000000..ce937bd --- /dev/null +++ b/lib/cmtp.h @@ -0,0 +1,69 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __CMTP_H +#define __CMTP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* CMTP defaults */ +#define CMTP_MINIMUM_MTU 152 +#define CMTP_DEFAULT_MTU 672 + +/* CMTP ioctl defines */ +#define CMTPCONNADD _IOW('C', 200, int) +#define CMTPCONNDEL _IOW('C', 201, int) +#define CMTPGETCONNLIST _IOR('C', 210, int) +#define CMTPGETCONNINFO _IOR('C', 211, int) + +#define CMTP_LOOPBACK 0 + +struct cmtp_connadd_req { + int sock; /* Connected socket */ + uint32_t flags; +}; + +struct cmtp_conndel_req { + bdaddr_t bdaddr; + uint32_t flags; +}; + +struct cmtp_conninfo { + bdaddr_t bdaddr; + uint32_t flags; + uint16_t state; + int num; +}; + +struct cmtp_connlist_req { + uint32_t cnum; + struct cmtp_conninfo *ci; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __CMTP_H */ diff --git a/lib/hci.c b/lib/hci.c new file mode 100644 index 0000000..7ae472c --- /dev/null +++ b/lib/hci.c @@ -0,0 +1,3124 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "bluetooth.h" +#include "hci.h" +#include "hci_lib.h" + +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +typedef struct { + char *str; + unsigned int val; +} hci_map; + +static char *hci_bit2str(hci_map *m, unsigned int val) +{ + char *str = malloc(120); + char *ptr = str; + + if (!str) + return NULL; + + *ptr = 0; + while (m->str) { + if ((unsigned int) m->val & val) + ptr += sprintf(ptr, "%s ", m->str); + m++; + } + return str; +} + +static int hci_str2bit(hci_map *map, char *str, unsigned int *val) +{ + char *t, *ptr; + hci_map *m; + int set; + + if (!str || !(str = ptr = strdup(str))) + return 0; + + *val = set = 0; + + while ((t = strsep(&ptr, ","))) { + for (m = map; m->str; m++) { + if (!strcasecmp(m->str, t)) { + *val |= (unsigned int) m->val; + set = 1; + } + } + } + free(str); + + return set; +} + +static char *hci_uint2str(hci_map *m, unsigned int val) +{ + char *str = malloc(50); + char *ptr = str; + + if (!str) + return NULL; + + *ptr = 0; + while (m->str) { + if ((unsigned int) m->val == val) { + ptr += sprintf(ptr, "%s", m->str); + break; + } + m++; + } + return str; +} + +static int hci_str2uint(hci_map *map, char *str, unsigned int *val) +{ + char *t, *ptr; + hci_map *m; + int set = 0; + + if (!str) + return 0; + + str = ptr = strdup(str); + + while ((t = strsep(&ptr, ","))) { + for (m = map; m->str; m++) { + if (!strcasecmp(m->str,t)) { + *val = (unsigned int) m->val; + set = 1; + break; + } + } + } + free(str); + + return set; +} + +char *hci_bustostr(int bus) +{ + switch (bus) { + case HCI_VIRTUAL: + return "Virtual"; + case HCI_USB: + return "USB"; + case HCI_PCCARD: + return "PCCARD"; + case HCI_UART: + return "UART"; + case HCI_RS232: + return "RS232"; + case HCI_PCI: + return "PCI"; + case HCI_SDIO: + return "SDIO"; + case HCI_SPI: + return "SPI"; + case HCI_I2C: + return "I2C"; + case HCI_SMD: + return "SMD"; + default: + return "Unknown"; + } +} + +char *hci_dtypetostr(int type) +{ + return hci_bustostr(type & 0x0f); +} + +char *hci_typetostr(int type) +{ + switch (type) { + case HCI_PRIMARY: + return "Primary"; + case HCI_AMP: + return "AMP"; + default: + return "Unknown"; + } +} + +/* HCI dev flags mapping */ +static hci_map dev_flags_map[] = { + { "UP", HCI_UP }, + { "INIT", HCI_INIT }, + { "RUNNING", HCI_RUNNING }, + { "RAW", HCI_RAW }, + { "PSCAN", HCI_PSCAN }, + { "ISCAN", HCI_ISCAN }, + { "INQUIRY", HCI_INQUIRY }, + { "AUTH", HCI_AUTH }, + { "ENCRYPT", HCI_ENCRYPT }, + { NULL } +}; + +char *hci_dflagstostr(uint32_t flags) +{ + char *str = bt_malloc(50); + char *ptr = str; + hci_map *m = dev_flags_map; + + if (!str) + return NULL; + + *ptr = 0; + + if (!hci_test_bit(HCI_UP, &flags)) + ptr += sprintf(ptr, "DOWN "); + + while (m->str) { + if (hci_test_bit(m->val, &flags)) + ptr += sprintf(ptr, "%s ", m->str); + m++; + } + return str; +} + +/* HCI packet type mapping */ +static hci_map pkt_type_map[] = { + { "DM1", HCI_DM1 }, + { "DM3", HCI_DM3 }, + { "DM5", HCI_DM5 }, + { "DH1", HCI_DH1 }, + { "DH3", HCI_DH3 }, + { "DH5", HCI_DH5 }, + { "HV1", HCI_HV1 }, + { "HV2", HCI_HV2 }, + { "HV3", HCI_HV3 }, + { "2-DH1", HCI_2DH1 }, + { "2-DH3", HCI_2DH3 }, + { "2-DH5", HCI_2DH5 }, + { "3-DH1", HCI_3DH1 }, + { "3-DH3", HCI_3DH3 }, + { "3-DH5", HCI_3DH5 }, + { NULL } +}; + +static hci_map sco_ptype_map[] = { + { "HV1", 0x0001 }, + { "HV2", 0x0002 }, + { "HV3", 0x0004 }, + { "EV3", HCI_EV3 }, + { "EV4", HCI_EV4 }, + { "EV5", HCI_EV5 }, + { "2-EV3", HCI_2EV3 }, + { "2-EV5", HCI_2EV5 }, + { "3-EV3", HCI_3EV3 }, + { "3-EV5", HCI_3EV5 }, + { NULL } +}; + +char *hci_ptypetostr(unsigned int ptype) +{ + return hci_bit2str(pkt_type_map, ptype); +} + +int hci_strtoptype(char *str, unsigned int *val) +{ + return hci_str2bit(pkt_type_map, str, val); +} + +char *hci_scoptypetostr(unsigned int ptype) +{ + return hci_bit2str(sco_ptype_map, ptype); +} + +int hci_strtoscoptype(char *str, unsigned int *val) +{ + return hci_str2bit(sco_ptype_map, str, val); +} + +/* Link policy mapping */ +static hci_map link_policy_map[] = { + { "NONE", 0 }, + { "RSWITCH", HCI_LP_RSWITCH }, + { "HOLD", HCI_LP_HOLD }, + { "SNIFF", HCI_LP_SNIFF }, + { "PARK", HCI_LP_PARK }, + { NULL } +}; + +char *hci_lptostr(unsigned int lp) +{ + return hci_bit2str(link_policy_map, lp); +} + +int hci_strtolp(char *str, unsigned int *val) +{ + return hci_str2bit(link_policy_map, str, val); +} + +/* Link mode mapping */ +static hci_map link_mode_map[] = { + { "NONE", 0 }, + { "ACCEPT", HCI_LM_ACCEPT }, + { "MASTER", HCI_LM_MASTER }, + { "AUTH", HCI_LM_AUTH }, + { "ENCRYPT", HCI_LM_ENCRYPT }, + { "TRUSTED", HCI_LM_TRUSTED }, + { "RELIABLE", HCI_LM_RELIABLE }, + { "SECURE", HCI_LM_SECURE }, + { NULL } +}; + +char *hci_lmtostr(unsigned int lm) +{ + char *s, *str = bt_malloc(50); + if (!str) + return NULL; + + *str = 0; + if (!(lm & HCI_LM_MASTER)) + strcpy(str, "SLAVE "); + + s = hci_bit2str(link_mode_map, lm); + if (!s) { + bt_free(str); + return NULL; + } + + strcat(str, s); + free(s); + return str; +} + +int hci_strtolm(char *str, unsigned int *val) +{ + return hci_str2bit(link_mode_map, str, val); +} + +/* Command mapping */ +static hci_map commands_map[] = { + { "Inquiry", 0 }, + { "Inquiry Cancel", 1 }, + { "Periodic Inquiry Mode", 2 }, + { "Exit Periodic Inquiry Mode", 3 }, + { "Create Connection", 4 }, + { "Disconnect", 5 }, + { "Add SCO Connection", 6 }, + { "Cancel Create Connection", 7 }, + + { "Accept Connection Request", 8 }, + { "Reject Connection Request", 9 }, + { "Link Key Request Reply", 10 }, + { "Link Key Request Negative Reply", 11 }, + { "PIN Code Request Reply", 12 }, + { "PIN Code Request Negative Reply", 13 }, + { "Change Connection Packet Type", 14 }, + { "Authentication Requested", 15 }, + + { "Set Connection Encryption", 16 }, + { "Change Connection Link Key", 17 }, + { "Master Link Key", 18 }, + { "Remote Name Request", 19 }, + { "Cancel Remote Name Request", 20 }, + { "Read Remote Supported Features", 21 }, + { "Read Remote Extended Features", 22 }, + { "Read Remote Version Information", 23 }, + + { "Read Clock Offset", 24 }, + { "Read LMP Handle", 25 }, + { "Reserved", 26 }, + { "Reserved", 27 }, + { "Reserved", 28 }, + { "Reserved", 29 }, + { "Reserved", 30 }, + { "Reserved", 31 }, + + { "Reserved", 32 }, + { "Hold Mode", 33 }, + { "Sniff Mode", 34 }, + { "Exit Sniff Mode", 35 }, + { "Park State", 36 }, + { "Exit Park State", 37 }, + { "QoS Setup", 38 }, + { "Role Discovery", 39 }, + + { "Switch Role", 40 }, + { "Read Link Policy Settings", 41 }, + { "Write Link Policy Settings", 42 }, + { "Read Default Link Policy Settings", 43 }, + { "Write Default Link Policy Settings", 44 }, + { "Flow Specification", 45 }, + { "Set Event Mask", 46 }, + { "Reset", 47 }, + + { "Set Event Filter", 48 }, + { "Flush", 49 }, + { "Read PIN Type", 50 }, + { "Write PIN Type", 51 }, + { "Create New Unit Key", 52 }, + { "Read Stored Link Key", 53 }, + { "Write Stored Link Key", 54 }, + { "Delete Stored Link Key", 55 }, + + { "Write Local Name", 56 }, + { "Read Local Name", 57 }, + { "Read Connection Accept Timeout", 58 }, + { "Write Connection Accept Timeout", 59 }, + { "Read Page Timeout", 60 }, + { "Write Page Timeout", 61 }, + { "Read Scan Enable", 62 }, + { "Write Scan Enable", 63 }, + + { "Read Page Scan Activity", 64 }, + { "Write Page Scan Activity", 65 }, + { "Read Inquiry Scan Activity", 66 }, + { "Write Inquiry Scan Activity", 67 }, + { "Read Authentication Enable", 68 }, + { "Write Authentication Enable", 69 }, + { "Read Encryption Mode", 70 }, + { "Write Encryption Mode", 71 }, + + { "Read Class Of Device", 72 }, + { "Write Class Of Device", 73 }, + { "Read Voice Setting", 74 }, + { "Write Voice Setting", 75 }, + { "Read Automatic Flush Timeout", 76 }, + { "Write Automatic Flush Timeout", 77 }, + { "Read Num Broadcast Retransmissions", 78 }, + { "Write Num Broadcast Retransmissions", 79 }, + + { "Read Hold Mode Activity", 80 }, + { "Write Hold Mode Activity", 81 }, + { "Read Transmit Power Level", 82 }, + { "Read Synchronous Flow Control Enable", 83 }, + { "Write Synchronous Flow Control Enable", 84 }, + { "Set Host Controller To Host Flow Control", 85 }, + { "Host Buffer Size", 86 }, + { "Host Number Of Completed Packets", 87 }, + + { "Read Link Supervision Timeout", 88 }, + { "Write Link Supervision Timeout", 89 }, + { "Read Number of Supported IAC", 90 }, + { "Read Current IAC LAP", 91 }, + { "Write Current IAC LAP", 92 }, + { "Read Page Scan Period Mode", 93 }, + { "Write Page Scan Period Mode", 94 }, + { "Read Page Scan Mode", 95 }, + + { "Write Page Scan Mode", 96 }, + { "Set AFH Channel Classification", 97 }, + { "Reserved", 98 }, + { "Reserved", 99 }, + { "Read Inquiry Scan Type", 100 }, + { "Write Inquiry Scan Type", 101 }, + { "Read Inquiry Mode", 102 }, + { "Write Inquiry Mode", 103 }, + + { "Read Page Scan Type", 104 }, + { "Write Page Scan Type", 105 }, + { "Read AFH Channel Assessment Mode", 106 }, + { "Write AFH Channel Assessment Mode", 107 }, + { "Reserved", 108 }, + { "Reserved", 109 }, + { "Reserved", 110 }, + { "Reserved", 111 }, + + { "Reserved", 112 }, + { "Reserved", 113 }, + { "Reserved", 114 }, + { "Read Local Version Information", 115 }, + { "Read Local Supported Commands", 116 }, + { "Read Local Supported Features", 117 }, + { "Read Local Extended Features", 118 }, + { "Read Buffer Size", 119 }, + + { "Read Country Code", 120 }, + { "Read BD ADDR", 121 }, + { "Read Failed Contact Counter", 122 }, + { "Reset Failed Contact Counter", 123 }, + { "Get Link Quality", 124 }, + { "Read RSSI", 125 }, + { "Read AFH Channel Map", 126 }, + { "Read BD Clock", 127 }, + + { "Read Loopback Mode", 128 }, + { "Write Loopback Mode", 129 }, + { "Enable Device Under Test Mode", 130 }, + { "Setup Synchronous Connection", 131 }, + { "Accept Synchronous Connection", 132 }, + { "Reject Synchronous Connection", 133 }, + { "Reserved", 134 }, + { "Reserved", 135 }, + + { "Read Extended Inquiry Response", 136 }, + { "Write Extended Inquiry Response", 137 }, + { "Refresh Encryption Key", 138 }, + { "Reserved", 139 }, + { "Sniff Subrating", 140 }, + { "Read Simple Pairing Mode", 141 }, + { "Write Simple Pairing Mode", 142 }, + { "Read Local OOB Data", 143 }, + + { "Read Inquiry Response Transmit Power Level", 144 }, + { "Write Inquiry Transmit Power Level", 145 }, + { "Read Default Erroneous Data Reporting", 146 }, + { "Write Default Erroneous Data Reporting", 147 }, + { "Reserved", 148 }, + { "Reserved", 149 }, + { "Reserved", 150 }, + { "IO Capability Request Reply", 151 }, + + { "User Confirmation Request Reply", 152 }, + { "User Confirmation Request Negative Reply", 153 }, + { "User Passkey Request Reply", 154 }, + { "User Passkey Request Negative Reply", 155 }, + { "Remote OOB Data Request Reply", 156 }, + { "Write Simple Pairing Debug Mode", 157 }, + { "Enhanced Flush", 158 }, + { "Remote OOB Data Request Negative Reply", 159 }, + + { "Reserved", 160 }, + { "Reserved", 161 }, + { "Send Keypress Notification", 162 }, + { "IO Capability Request Negative Reply", 163 }, + { "Read Encryption Key Size", 164 }, + { "Reserved", 165 }, + { "Reserved", 166 }, + { "Reserved", 167 }, + + { "Create Physical Link", 168 }, + { "Accept Physical Link", 169 }, + { "Disconnect Physical Link", 170 }, + { "Create Logical Link", 171 }, + { "Accept Logical Link", 172 }, + { "Disconnect Logical Link", 173 }, + { "Logical Link Cancel", 174 }, + { "Flow Specification Modify", 175 }, + + { "Read Logical Link Accept Timeout", 176 }, + { "Write Logical Link Accept Timeout", 177 }, + { "Set Event Mask Page 2", 178 }, + { "Read Location Data", 179 }, + { "Write Location Data", 180 }, + { "Read Local AMP Info", 181 }, + { "Read Local AMP_ASSOC", 182 }, + { "Write Remote AMP_ASSOC", 183 }, + + { "Read Flow Control Mode", 184 }, + { "Write Flow Control Mode", 185 }, + { "Read Data Block Size", 186 }, + { "Reserved", 187 }, + { "Reserved", 188 }, + { "Enable AMP Receiver Reports", 189 }, + { "AMP Test End", 190 }, + { "AMP Test Command", 191 }, + + { "Read Enhanced Transmit Power Level", 192 }, + { "Reserved", 193 }, + { "Read Best Effort Flush Timeout", 194 }, + { "Write Best Effort Flush Timeout", 195 }, + { "Short Range Mode", 196 }, + { "Read LE Host Support", 197 }, + { "Write LE Host Support", 198 }, + { "Reserved", 199 }, + + { "LE Set Event Mask", 200 }, + { "LE Read Buffer Size", 201 }, + { "LE Read Local Supported Features", 202 }, + { "Reserved", 203 }, + { "LE Set Random Address", 204 }, + { "LE Set Advertising Parameters", 205 }, + { "LE Read Advertising Channel TX Power", 206 }, + { "LE Set Advertising Data", 207 }, + + { "LE Set Scan Response Data", 208 }, + { "LE Set Advertise Enable", 209 }, + { "LE Set Scan Parameters", 210 }, + { "LE Set Scan Enable", 211 }, + { "LE Create Connection", 212 }, + { "LE Create Connection Cancel", 213 }, + { "LE Read White List Size", 214 }, + { "LE Clear White List", 215 }, + + { "LE Add Device To White List", 216 }, + { "LE Remove Device From White List", 217 }, + { "LE Connection Update", 218 }, + { "LE Set Host Channel Classification", 219 }, + { "LE Read Channel Map", 220 }, + { "LE Read Remote Used Features", 221 }, + { "LE Encrypt", 222 }, + { "LE Rand", 223 }, + + { "LE Start Encryption", 224 }, + { "LE Long Term Key Request Reply", 225 }, + { "LE Long Term Key Request Negative Reply", 226 }, + { "LE Read Supported States", 227 }, + { "LE Receiver Test", 228 }, + { "LE Transmitter Test", 229 }, + { "LE Test End", 230 }, + { "Reserved", 231 }, + + { NULL } +}; + +char *hci_cmdtostr(unsigned int cmd) +{ + return hci_uint2str(commands_map, cmd); +} + +char *hci_commandstostr(uint8_t *commands, char *pref, int width) +{ + unsigned int maxwidth = width - 3; + hci_map *m; + char *off, *ptr, *str; + int size = 10; + + m = commands_map; + + while (m->str) { + if (commands[m->val / 8] & (1 << (m->val % 8))) + size += strlen(m->str) + (pref ? strlen(pref) : 0) + 3; + m++; + } + + str = bt_malloc(size); + if (!str) + return NULL; + + ptr = str; *ptr = '\0'; + + if (pref) + ptr += sprintf(ptr, "%s", pref); + + off = ptr; + + m = commands_map; + + while (m->str) { + if (commands[m->val / 8] & (1 << (m->val % 8))) { + if (strlen(off) + strlen(m->str) > maxwidth) { + ptr += sprintf(ptr, "\n%s", pref ? pref : ""); + off = ptr; + } + ptr += sprintf(ptr, "'%s' ", m->str); + } + m++; + } + + return str; +} + +/* Version mapping */ +static hci_map ver_map[] = { + { "1.0b", 0x00 }, + { "1.1", 0x01 }, + { "1.2", 0x02 }, + { "2.0", 0x03 }, + { "2.1", 0x04 }, + { "3.0", 0x05 }, + { "4.0", 0x06 }, + { "4.1", 0x07 }, + { "4.2", 0x08 }, + { "5.0", 0x09 }, + { "5.1", 0x0a }, + { NULL } +}; + +char *hci_vertostr(unsigned int ver) +{ + return hci_uint2str(ver_map, ver); +} + +int hci_strtover(char *str, unsigned int *ver) +{ + return hci_str2uint(ver_map, str, ver); +} + +char *lmp_vertostr(unsigned int ver) +{ + return hci_uint2str(ver_map, ver); +} + +int lmp_strtover(char *str, unsigned int *ver) +{ + return hci_str2uint(ver_map, str, ver); +} + +static hci_map pal_map[] = { + { "3.0", 0x01 }, + { NULL } +}; + +char *pal_vertostr(unsigned int ver) +{ + return hci_uint2str(pal_map, ver); +} + +int pal_strtover(char *str, unsigned int *ver) +{ + return hci_str2uint(pal_map, str, ver); +} + +/* LMP features mapping */ +static hci_map lmp_features_map[8][9] = { + { /* Byte 0 */ + { "<3-slot packets>", LMP_3SLOT }, /* Bit 0 */ + { "<5-slot packets>", LMP_5SLOT }, /* Bit 1 */ + { "", LMP_ENCRYPT }, /* Bit 2 */ + { "", LMP_SOFFSET }, /* Bit 3 */ + { "", LMP_TACCURACY }, /* Bit 4 */ + { "", LMP_RSWITCH }, /* Bit 5 */ + { "", LMP_HOLD }, /* Bit 6 */ + { "", LMP_SNIFF }, /* Bit 7 */ + { NULL } + }, + { /* Byte 1 */ + { "", LMP_PARK }, /* Bit 0 */ + { "", LMP_RSSI }, /* Bit 1 */ + { "", LMP_QUALITY }, /* Bit 2 */ + { "", LMP_SCO }, /* Bit 3 */ + { "", LMP_HV2 }, /* Bit 4 */ + { "", LMP_HV3 }, /* Bit 5 */ + { "", LMP_ULAW }, /* Bit 6 */ + { "", LMP_ALAW }, /* Bit 7 */ + { NULL } + }, + { /* Byte 2 */ + { "", LMP_CVSD }, /* Bit 0 */ + { "", LMP_PSCHEME }, /* Bit 1 */ + { "", LMP_PCONTROL }, /* Bit 2 */ + { "", LMP_TRSP_SCO }, /* Bit 3 */ + { "",LMP_BCAST_ENC }, /* Bit 7 */ + { NULL } + }, + { /* Byte 3 */ + { "", 0x01 }, /* Bit 0 */ + { "", LMP_EDR_ACL_2M }, /* Bit 1 */ + { "", LMP_EDR_ACL_3M }, /* Bit 2 */ + { "", LMP_ENH_ISCAN }, /* Bit 3 */ + { "", LMP_ILACE_ISCAN }, /* Bit 4 */ + { "", LMP_ILACE_PSCAN }, /* Bit 5 */ + { "",LMP_RSSI_INQ }, /* Bit 6 */ + { "", LMP_ESCO }, /* Bit 7 */ + { NULL } + }, + { /* Byte 4 */ + { "", LMP_EV4 }, /* Bit 0 */ + { "", LMP_EV5 }, /* Bit 1 */ + { "", 0x04 }, /* Bit 2 */ + { "", LMP_AFH_CAP_SLV }, /* Bit 3 */ + { "", LMP_AFH_CLS_SLV }, /* Bit 4 */ + { "
", LMP_NO_BREDR }, /* Bit 5 */ + { "", LMP_LE }, /* Bit 6 */ + { "<3-slot EDR ACL>", LMP_EDR_3SLOT }, /* Bit 7 */ + { NULL } + }, + { /* Byte 5 */ + { "<5-slot EDR ACL>", LMP_EDR_5SLOT }, /* Bit 0 */ + { "", LMP_SNIFF_SUBR }, /* Bit 1 */ + { "", LMP_PAUSE_ENC }, /* Bit 2 */ + { "", LMP_AFH_CAP_MST }, /* Bit 3 */ + { "",LMP_AFH_CLS_MST }, /* Bit 4 */ + { "", LMP_EDR_ESCO_2M }, /* Bit 5 */ + { "", LMP_EDR_ESCO_3M }, /* Bit 6 */ + { "<3-slot EDR eSCO>", LMP_EDR_3S_ESCO }, /* Bit 7 */ + { NULL } + }, + { /* Byte 6 */ + { "", LMP_EXT_INQ }, /* Bit 0 */ + { "", LMP_LE_BREDR }, /* Bit 1 */ + { "", 0x04 }, /* Bit 2 */ + { "", LMP_SIMPLE_PAIR }, /* Bit 3 */ + { "", LMP_ENCAPS_PDU }, /* Bit 4 */ + { "", LMP_ERR_DAT_REP }, /* Bit 5 */ + { "", LMP_NFLUSH_PKTS }, /* Bit 6 */ + { "", 0x80 }, /* Bit 7 */ + { NULL } + }, + { /* Byte 7 */ + { "", LMP_LSTO }, /* Bit 1 */ + { "", LMP_INQ_TX_PWR }, /* Bit 1 */ + { "", LMP_EPC }, /* Bit 2 */ + { "", 0x08 }, /* Bit 3 */ + { "", 0x10 }, /* Bit 4 */ + { "", 0x20 }, /* Bit 5 */ + { "", 0x40 }, /* Bit 6 */ + { "",LMP_EXT_FEAT }, /* Bit 7 */ + { NULL } + }, +}; + +char *lmp_featurestostr(uint8_t *features, char *pref, int width) +{ + unsigned int maxwidth = width - 1; + char *off, *ptr, *str; + int i, size = 10; + + for (i = 0; i < 8; i++) { + hci_map *m = lmp_features_map[i]; + + while (m->str) { + if (m->val & features[i]) + size += strlen(m->str) + + (pref ? strlen(pref) : 0) + 1; + m++; + } + } + + str = bt_malloc(size); + if (!str) + return NULL; + + ptr = str; *ptr = '\0'; + + if (pref) + ptr += sprintf(ptr, "%s", pref); + + off = ptr; + + for (i = 0; i < 8; i++) { + hci_map *m = lmp_features_map[i]; + + while (m->str) { + if (m->val & features[i]) { + if (strlen(off) + strlen(m->str) > maxwidth) { + ptr += sprintf(ptr, "\n%s", + pref ? pref : ""); + off = ptr; + } + ptr += sprintf(ptr, "%s ", m->str); + } + m++; + } + } + + return str; +} + +/* HCI functions that do not require open device */ +int hci_for_each_dev(int flag, int (*func)(int dd, int dev_id, long arg), + long arg) +{ + struct hci_dev_list_req *dl; + struct hci_dev_req *dr; + int dev_id = -1; + int i, sk, err = 0; + + sk = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (sk < 0) + return -1; + + dl = malloc(HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl)); + if (!dl) { + err = errno; + goto done; + } + + memset(dl, 0, HCI_MAX_DEV * sizeof(*dr) + sizeof(*dl)); + + dl->dev_num = HCI_MAX_DEV; + dr = dl->dev_req; + + if (ioctl(sk, HCIGETDEVLIST, (void *) dl) < 0) { + err = errno; + goto free; + } + + for (i = 0; i < dl->dev_num; i++, dr++) { + if (hci_test_bit(flag, &dr->dev_opt)) + if (!func || func(sk, dr->dev_id, arg)) { + dev_id = dr->dev_id; + break; + } + } + + if (dev_id < 0) + err = ENODEV; + +free: + free(dl); + +done: + close(sk); + errno = err; + + return dev_id; +} + +static int __other_bdaddr(int dd, int dev_id, long arg) +{ + struct hci_dev_info di = { .dev_id = dev_id }; + + if (ioctl(dd, HCIGETDEVINFO, (void *) &di)) + return 0; + + if (hci_test_bit(HCI_RAW, &di.flags)) + return 0; + + return bacmp((bdaddr_t *) arg, &di.bdaddr); +} + +static int __same_bdaddr(int dd, int dev_id, long arg) +{ + struct hci_dev_info di = { .dev_id = dev_id }; + + if (ioctl(dd, HCIGETDEVINFO, (void *) &di)) + return 0; + + return !bacmp((bdaddr_t *) arg, &di.bdaddr); +} + +int hci_get_route(bdaddr_t *bdaddr) +{ + int dev_id; + + dev_id = hci_for_each_dev(HCI_UP, __other_bdaddr, + (long) (bdaddr ? bdaddr : BDADDR_ANY)); + if (dev_id < 0) + dev_id = hci_for_each_dev(HCI_UP, __same_bdaddr, + (long) (bdaddr ? bdaddr : BDADDR_ANY)); + + return dev_id; +} + +int hci_devid(const char *str) +{ + bdaddr_t ba; + int id = -1; + + if (!strncmp(str, "hci", 3) && strlen(str) >= 4) { + id = atoi(str + 3); + if (hci_devba(id, &ba) < 0) + return -1; + } else { + errno = ENODEV; + str2ba(str, &ba); + id = hci_for_each_dev(HCI_UP, __same_bdaddr, (long) &ba); + } + + return id; +} + +int hci_devinfo(int dev_id, struct hci_dev_info *di) +{ + int dd, err, ret; + + dd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (dd < 0) + return dd; + + memset(di, 0, sizeof(struct hci_dev_info)); + + di->dev_id = dev_id; + ret = ioctl(dd, HCIGETDEVINFO, (void *) di); + + err = errno; + close(dd); + errno = err; + + return ret; +} + +int hci_devba(int dev_id, bdaddr_t *bdaddr) +{ + struct hci_dev_info di; + + memset(&di, 0, sizeof(di)); + + if (hci_devinfo(dev_id, &di)) + return -1; + + if (!hci_test_bit(HCI_UP, &di.flags)) { + errno = ENETDOWN; + return -1; + } + + bacpy(bdaddr, &di.bdaddr); + + return 0; +} + +int hci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap, + inquiry_info **ii, long flags) +{ + struct hci_inquiry_req *ir; + uint8_t num_rsp = nrsp; + void *buf; + int dd, size, err, ret = -1; + + if (nrsp <= 0) { + num_rsp = 0; + nrsp = 255; + } + + if (dev_id < 0) { + dev_id = hci_get_route(NULL); + if (dev_id < 0) { + errno = ENODEV; + return -1; + } + } + + dd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (dd < 0) + return dd; + + buf = malloc(sizeof(*ir) + (sizeof(inquiry_info) * (nrsp))); + if (!buf) + goto done; + + ir = buf; + ir->dev_id = dev_id; + ir->num_rsp = num_rsp; + ir->length = len; + ir->flags = flags; + + if (lap) { + memcpy(ir->lap, lap, 3); + } else { + ir->lap[0] = 0x33; + ir->lap[1] = 0x8b; + ir->lap[2] = 0x9e; + } + + ret = ioctl(dd, HCIINQUIRY, (unsigned long) buf); + if (ret < 0) + goto free; + + size = sizeof(inquiry_info) * ir->num_rsp; + + if (!*ii) + *ii = malloc(size); + + if (*ii) { + memcpy((void *) *ii, buf + sizeof(*ir), size); + ret = ir->num_rsp; + } else + ret = -1; + +free: + free(buf); + +done: + err = errno; + close(dd); + errno = err; + + return ret; +} + +/* Open HCI device. + * Returns device descriptor (dd). */ +int hci_open_dev(int dev_id) +{ + struct sockaddr_hci a; + int dd, err; + + /* Check for valid device id */ + if (dev_id < 0) { + errno = ENODEV; + return -1; + } + + /* Create HCI socket */ + dd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (dd < 0) + return dd; + + /* Bind socket to the HCI device */ + memset(&a, 0, sizeof(a)); + a.hci_family = AF_BLUETOOTH; + a.hci_dev = dev_id; + if (bind(dd, (struct sockaddr *) &a, sizeof(a)) < 0) + goto failed; + + return dd; + +failed: + err = errno; + close(dd); + errno = err; + + return -1; +} + +int hci_close_dev(int dd) +{ + return close(dd); +} + +/* HCI functions that require open device + * dd - Device descriptor returned by hci_open_dev. */ + +int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param) +{ + uint8_t type = HCI_COMMAND_PKT; + hci_command_hdr hc; + struct iovec iv[3]; + int ivn; + + hc.opcode = htobs(cmd_opcode_pack(ogf, ocf)); + hc.plen= plen; + + iv[0].iov_base = &type; + iv[0].iov_len = 1; + iv[1].iov_base = &hc; + iv[1].iov_len = HCI_COMMAND_HDR_SIZE; + ivn = 2; + + if (plen) { + iv[2].iov_base = param; + iv[2].iov_len = plen; + ivn = 3; + } + + while (writev(dd, iv, ivn) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + return -1; + } + return 0; +} + +int hci_send_req(int dd, struct hci_request *r, int to) +{ + unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr; + uint16_t opcode = htobs(cmd_opcode_pack(r->ogf, r->ocf)); + struct hci_filter nf, of; + socklen_t olen; + hci_event_hdr *hdr; + int err, try; + + olen = sizeof(of); + if (getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen) < 0) + return -1; + + hci_filter_clear(&nf); + hci_filter_set_ptype(HCI_EVENT_PKT, &nf); + hci_filter_set_event(EVT_CMD_STATUS, &nf); + hci_filter_set_event(EVT_CMD_COMPLETE, &nf); + hci_filter_set_event(EVT_LE_META_EVENT, &nf); + hci_filter_set_event(r->event, &nf); + hci_filter_set_opcode(opcode, &nf); + if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) + return -1; + + if (hci_send_cmd(dd, r->ogf, r->ocf, r->clen, r->cparam) < 0) + goto failed; + + try = 10; + while (try--) { + evt_cmd_complete *cc; + evt_cmd_status *cs; + evt_remote_name_req_complete *rn; + evt_le_meta_event *me; + remote_name_req_cp *cp; + int len; + + if (to) { + struct pollfd p; + int n; + + p.fd = dd; p.events = POLLIN; + while ((n = poll(&p, 1, to)) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + goto failed; + } + + if (!n) { + errno = ETIMEDOUT; + goto failed; + } + + to -= 10; + if (to < 0) + to = 0; + + } + + while ((len = read(dd, buf, sizeof(buf))) < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + goto failed; + } + + hdr = (void *) (buf + 1); + ptr = buf + (1 + HCI_EVENT_HDR_SIZE); + len -= (1 + HCI_EVENT_HDR_SIZE); + + switch (hdr->evt) { + case EVT_CMD_STATUS: + cs = (void *) ptr; + + if (cs->opcode != opcode) + continue; + + if (r->event != EVT_CMD_STATUS) { + if (cs->status) { + errno = EIO; + goto failed; + } + break; + } + + r->rlen = MIN(len, r->rlen); + memcpy(r->rparam, ptr, r->rlen); + goto done; + + case EVT_CMD_COMPLETE: + cc = (void *) ptr; + + if (cc->opcode != opcode) + continue; + + ptr += EVT_CMD_COMPLETE_SIZE; + len -= EVT_CMD_COMPLETE_SIZE; + + r->rlen = MIN(len, r->rlen); + memcpy(r->rparam, ptr, r->rlen); + goto done; + + case EVT_REMOTE_NAME_REQ_COMPLETE: + if (hdr->evt != r->event) + break; + + rn = (void *) ptr; + cp = r->cparam; + + if (bacmp(&rn->bdaddr, &cp->bdaddr)) + continue; + + r->rlen = MIN(len, r->rlen); + memcpy(r->rparam, ptr, r->rlen); + goto done; + + case EVT_LE_META_EVENT: + me = (void *) ptr; + + if (me->subevent != r->event) + continue; + + len -= 1; + r->rlen = MIN(len, r->rlen); + memcpy(r->rparam, me->data, r->rlen); + goto done; + + default: + if (hdr->evt != r->event) + break; + + r->rlen = MIN(len, r->rlen); + memcpy(r->rparam, ptr, r->rlen); + goto done; + } + } + errno = ETIMEDOUT; + +failed: + err = errno; + setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); + errno = err; + return -1; + +done: + setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); + return 0; +} + +int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, + uint16_t clkoffset, uint8_t rswitch, + uint16_t *handle, int to) +{ + evt_conn_complete rp; + create_conn_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + cp.pkt_type = ptype; + cp.pscan_rep_mode = 0x02; + cp.clock_offset = clkoffset; + cp.role_switch = rswitch; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_CREATE_CONN; + rq.event = EVT_CONN_COMPLETE; + rq.cparam = &cp; + rq.clen = CREATE_CONN_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_CONN_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *handle = rp.handle; + return 0; +} + +int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to) +{ + evt_disconn_complete rp; + disconnect_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + cp.reason = reason; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_DISCONNECT; + rq.event = EVT_DISCONN_COMPLETE; + rq.cparam = &cp; + rq.clen = DISCONNECT_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_DISCONN_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + return 0; +} + +int hci_le_add_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to) +{ + struct hci_request rq; + le_add_device_to_white_list_cp cp; + uint8_t status; + + memset(&cp, 0, sizeof(cp)); + cp.bdaddr_type = type; + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_ADD_DEVICE_TO_WHITE_LIST; + rq.cparam = &cp; + rq.clen = LE_ADD_DEVICE_TO_WHITE_LIST_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_le_rm_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to) +{ + struct hci_request rq; + le_remove_device_from_white_list_cp cp; + uint8_t status; + + memset(&cp, 0, sizeof(cp)); + cp.bdaddr_type = type; + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST; + rq.cparam = &cp; + rq.clen = LE_REMOVE_DEVICE_FROM_WHITE_LIST_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_le_read_white_list_size(int dd, uint8_t *size, int to) +{ + struct hci_request rq; + le_read_white_list_size_rp rp; + + memset(&rp, 0, sizeof(rp)); + memset(&rq, 0, sizeof(rq)); + + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_READ_WHITE_LIST_SIZE; + rq.rparam = &rp; + rq.rlen = LE_READ_WHITE_LIST_SIZE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + if (size) + *size = rp.size; + + return 0; +} + +int hci_le_clear_white_list(int dd, int to) +{ + struct hci_request rq; + uint8_t status; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_CLEAR_WHITE_LIST; + rq.rparam = &status; + rq.rlen = 1; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_le_add_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, + uint8_t *peer_irk, uint8_t *local_irk, int to) +{ + struct hci_request rq; + le_add_device_to_resolv_list_cp cp; + uint8_t status; + + memset(&cp, 0, sizeof(cp)); + cp.bdaddr_type = type; + bacpy(&cp.bdaddr, bdaddr); + if (peer_irk) + memcpy(cp.peer_irk, peer_irk, 16); + if (local_irk) + memcpy(cp.local_irk, local_irk, 16); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_ADD_DEVICE_TO_RESOLV_LIST; + rq.cparam = &cp; + rq.clen = LE_ADD_DEVICE_TO_RESOLV_LIST_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_le_rm_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to) +{ + struct hci_request rq; + le_remove_device_from_resolv_list_cp cp; + uint8_t status; + + memset(&cp, 0, sizeof(cp)); + cp.bdaddr_type = type; + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_REMOVE_DEVICE_FROM_RESOLV_LIST; + rq.cparam = &cp; + rq.clen = LE_REMOVE_DEVICE_FROM_RESOLV_LIST_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_le_clear_resolving_list(int dd, int to) +{ + struct hci_request rq; + uint8_t status; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_CLEAR_RESOLV_LIST; + rq.rparam = &status; + rq.rlen = 1; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_le_read_resolving_list_size(int dd, uint8_t *size, int to) +{ + struct hci_request rq; + le_read_resolv_list_size_rp rp; + + memset(&rp, 0, sizeof(rp)); + memset(&rq, 0, sizeof(rq)); + + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_READ_RESOLV_LIST_SIZE; + rq.rparam = &rp; + rq.rlen = LE_READ_RESOLV_LIST_SIZE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + if (size) + *size = rp.size; + + return 0; +} + +int hci_le_set_address_resolution_enable(int dd, uint8_t enable, int to) +{ + struct hci_request rq; + le_set_address_resolution_enable_cp cp; + uint8_t status; + + memset(&cp, 0, sizeof(cp)); + cp.enable = enable; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_ADDRESS_RESOLUTION_ENABLE; + rq.cparam = &cp; + rq.clen = LE_SET_ADDRESS_RESOLUTION_ENABLE_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_read_local_name(int dd, int len, char *name, int to) +{ + read_local_name_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_LOCAL_NAME; + rq.rparam = &rp; + rq.rlen = READ_LOCAL_NAME_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + rp.name[247] = '\0'; + strncpy(name, (char *) rp.name, len); + return 0; +} + +int hci_write_local_name(int dd, const char *name, int to) +{ + change_local_name_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + strncpy((char *) cp.name, name, sizeof(cp.name) - 1); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_CHANGE_LOCAL_NAME; + rq.cparam = &cp; + rq.clen = CHANGE_LOCAL_NAME_CP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + return 0; +} + +int hci_read_remote_name_with_clock_offset(int dd, const bdaddr_t *bdaddr, + uint8_t pscan_rep_mode, + uint16_t clkoffset, + int len, char *name, int to) +{ + evt_remote_name_req_complete rn; + remote_name_req_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + cp.pscan_rep_mode = pscan_rep_mode; + cp.clock_offset = clkoffset; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_REMOTE_NAME_REQ; + rq.cparam = &cp; + rq.clen = REMOTE_NAME_REQ_CP_SIZE; + rq.event = EVT_REMOTE_NAME_REQ_COMPLETE; + rq.rparam = &rn; + rq.rlen = EVT_REMOTE_NAME_REQ_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rn.status) { + errno = EIO; + return -1; + } + + rn.name[247] = '\0'; + strncpy(name, (char *) rn.name, len); + return 0; +} + +int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, + int to) +{ + return hci_read_remote_name_with_clock_offset(dd, bdaddr, 0x02, 0x0000, + len, name, to); +} + +int hci_read_remote_name_cancel(int dd, const bdaddr_t *bdaddr, int to) +{ + remote_name_req_cancel_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_REMOTE_NAME_REQ_CANCEL; + rq.cparam = &cp; + rq.clen = REMOTE_NAME_REQ_CANCEL_CP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + return 0; +} + +int hci_read_remote_version(int dd, uint16_t handle, struct hci_version *ver, + int to) +{ + evt_read_remote_version_complete rp; + read_remote_version_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_READ_REMOTE_VERSION; + rq.event = EVT_READ_REMOTE_VERSION_COMPLETE; + rq.cparam = &cp; + rq.clen = READ_REMOTE_VERSION_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_READ_REMOTE_VERSION_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + ver->manufacturer = btohs(rp.manufacturer); + ver->lmp_ver = rp.lmp_ver; + ver->lmp_subver = btohs(rp.lmp_subver); + return 0; +} + +int hci_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to) +{ + evt_read_remote_features_complete rp; + read_remote_features_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_READ_REMOTE_FEATURES; + rq.event = EVT_READ_REMOTE_FEATURES_COMPLETE; + rq.cparam = &cp; + rq.clen = READ_REMOTE_FEATURES_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + if (features) + memcpy(features, rp.features, 8); + + return 0; +} + +int hci_read_remote_ext_features(int dd, uint16_t handle, uint8_t page, + uint8_t *max_page, uint8_t *features, + int to) +{ + evt_read_remote_ext_features_complete rp; + read_remote_ext_features_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + cp.page_num = page; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_READ_REMOTE_EXT_FEATURES; + rq.event = EVT_READ_REMOTE_EXT_FEATURES_COMPLETE; + rq.cparam = &cp; + rq.clen = READ_REMOTE_EXT_FEATURES_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + if (max_page) + *max_page = rp.max_page_num; + + if (features) + memcpy(features, rp.features, 8); + + return 0; +} + +int hci_read_clock_offset(int dd, uint16_t handle, uint16_t *clkoffset, int to) +{ + evt_read_clock_offset_complete rp; + read_clock_offset_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_READ_CLOCK_OFFSET; + rq.event = EVT_READ_CLOCK_OFFSET_COMPLETE; + rq.cparam = &cp; + rq.clen = READ_CLOCK_OFFSET_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *clkoffset = rp.clock_offset; + return 0; +} + +int hci_read_local_version(int dd, struct hci_version *ver, int to) +{ + read_local_version_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_INFO_PARAM; + rq.ocf = OCF_READ_LOCAL_VERSION; + rq.rparam = &rp; + rq.rlen = READ_LOCAL_VERSION_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + ver->manufacturer = btohs(rp.manufacturer); + ver->hci_ver = rp.hci_ver; + ver->hci_rev = btohs(rp.hci_rev); + ver->lmp_ver = rp.lmp_ver; + ver->lmp_subver = btohs(rp.lmp_subver); + return 0; +} + +int hci_read_local_commands(int dd, uint8_t *commands, int to) +{ + read_local_commands_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_INFO_PARAM; + rq.ocf = OCF_READ_LOCAL_COMMANDS; + rq.rparam = &rp; + rq.rlen = READ_LOCAL_COMMANDS_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + if (commands) + memcpy(commands, rp.commands, 64); + + return 0; +} + +int hci_read_local_features(int dd, uint8_t *features, int to) +{ + read_local_features_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_INFO_PARAM; + rq.ocf = OCF_READ_LOCAL_FEATURES; + rq.rparam = &rp; + rq.rlen = READ_LOCAL_FEATURES_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + if (features) + memcpy(features, rp.features, 8); + + return 0; +} + +int hci_read_local_ext_features(int dd, uint8_t page, uint8_t *max_page, + uint8_t *features, int to) +{ + read_local_ext_features_cp cp; + read_local_ext_features_rp rp; + struct hci_request rq; + + cp.page_num = page; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_INFO_PARAM; + rq.ocf = OCF_READ_LOCAL_EXT_FEATURES; + rq.cparam = &cp; + rq.clen = READ_LOCAL_EXT_FEATURES_CP_SIZE; + rq.rparam = &rp; + rq.rlen = READ_LOCAL_EXT_FEATURES_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + if (max_page) + *max_page = rp.max_page_num; + + if (features) + memcpy(features, rp.features, 8); + + return 0; +} + +int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to) +{ + read_bd_addr_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_INFO_PARAM; + rq.ocf = OCF_READ_BD_ADDR; + rq.rparam = &rp; + rq.rlen = READ_BD_ADDR_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + if (bdaddr) + bacpy(bdaddr, &rp.bdaddr); + + return 0; +} + +int hci_read_class_of_dev(int dd, uint8_t *cls, int to) +{ + read_class_of_dev_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_CLASS_OF_DEV; + rq.rparam = &rp; + rq.rlen = READ_CLASS_OF_DEV_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + memcpy(cls, rp.dev_class, 3); + return 0; +} + +int hci_write_class_of_dev(int dd, uint32_t cls, int to) +{ + write_class_of_dev_cp cp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + cp.dev_class[0] = cls & 0xff; + cp.dev_class[1] = (cls >> 8) & 0xff; + cp.dev_class[2] = (cls >> 16) & 0xff; + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_CLASS_OF_DEV; + rq.cparam = &cp; + rq.clen = WRITE_CLASS_OF_DEV_CP_SIZE; + return hci_send_req(dd, &rq, to); +} + +int hci_read_voice_setting(int dd, uint16_t *vs, int to) +{ + read_voice_setting_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_VOICE_SETTING; + rq.rparam = &rp; + rq.rlen = READ_VOICE_SETTING_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *vs = rp.voice_setting; + return 0; +} + +int hci_write_voice_setting(int dd, uint16_t vs, int to) +{ + write_voice_setting_cp cp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + cp.voice_setting = vs; + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_VOICE_SETTING; + rq.cparam = &cp; + rq.clen = WRITE_VOICE_SETTING_CP_SIZE; + + return hci_send_req(dd, &rq, to); +} + +int hci_read_current_iac_lap(int dd, uint8_t *num_iac, uint8_t *lap, int to) +{ + read_current_iac_lap_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_CURRENT_IAC_LAP; + rq.rparam = &rp; + rq.rlen = READ_CURRENT_IAC_LAP_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *num_iac = rp.num_current_iac; + memcpy(lap, rp.lap, rp.num_current_iac * 3); + return 0; +} + +int hci_write_current_iac_lap(int dd, uint8_t num_iac, uint8_t *lap, int to) +{ + write_current_iac_lap_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.num_current_iac = num_iac; + memcpy(&cp.lap, lap, num_iac * 3); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_CURRENT_IAC_LAP; + rq.cparam = &cp; + rq.clen = num_iac * 3 + 1; + + return hci_send_req(dd, &rq, to); +} + +int hci_read_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to) +{ + read_stored_link_key_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + cp.read_all = all; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_STORED_LINK_KEY; + rq.cparam = &cp; + rq.clen = READ_STORED_LINK_KEY_CP_SIZE; + + return hci_send_req(dd, &rq, to); +} + +int hci_write_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t *key, int to) +{ + unsigned char cp[WRITE_STORED_LINK_KEY_CP_SIZE + 6 + 16]; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 1; + bacpy((bdaddr_t *) (cp + 1), bdaddr); + memcpy(cp + 7, key, 16); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_STORED_LINK_KEY; + rq.cparam = &cp; + rq.clen = WRITE_STORED_LINK_KEY_CP_SIZE + 6 + 16; + + return hci_send_req(dd, &rq, to); +} + +int hci_delete_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to) +{ + delete_stored_link_key_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + cp.delete_all = all; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_DELETE_STORED_LINK_KEY; + rq.cparam = &cp; + rq.clen = DELETE_STORED_LINK_KEY_CP_SIZE; + + return hci_send_req(dd, &rq, to); +} + +int hci_authenticate_link(int dd, uint16_t handle, int to) +{ + auth_requested_cp cp; + evt_auth_complete rp; + struct hci_request rq; + + cp.handle = handle; + + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_AUTH_REQUESTED; + rq.event = EVT_AUTH_COMPLETE; + rq.cparam = &cp; + rq.clen = AUTH_REQUESTED_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_AUTH_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_encrypt_link(int dd, uint16_t handle, uint8_t encrypt, int to) +{ + set_conn_encrypt_cp cp; + evt_encrypt_change rp; + struct hci_request rq; + + cp.handle = handle; + cp.encrypt = encrypt; + + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_SET_CONN_ENCRYPT; + rq.event = EVT_ENCRYPT_CHANGE; + rq.cparam = &cp; + rq.clen = SET_CONN_ENCRYPT_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_ENCRYPT_CHANGE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_change_link_key(int dd, uint16_t handle, int to) +{ + change_conn_link_key_cp cp; + evt_change_conn_link_key_complete rp; + struct hci_request rq; + + cp.handle = handle; + + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_CHANGE_CONN_LINK_KEY; + rq.event = EVT_CHANGE_CONN_LINK_KEY_COMPLETE; + rq.cparam = &cp; + rq.clen = CHANGE_CONN_LINK_KEY_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_CHANGE_CONN_LINK_KEY_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_switch_role(int dd, bdaddr_t *bdaddr, uint8_t role, int to) +{ + switch_role_cp cp; + evt_role_change rp; + struct hci_request rq; + + bacpy(&cp.bdaddr, bdaddr); + cp.role = role; + rq.ogf = OGF_LINK_POLICY; + rq.ocf = OCF_SWITCH_ROLE; + rq.cparam = &cp; + rq.clen = SWITCH_ROLE_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_ROLE_CHANGE_SIZE; + rq.event = EVT_ROLE_CHANGE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_park_mode(int dd, uint16_t handle, uint16_t max_interval, + uint16_t min_interval, int to) +{ + park_mode_cp cp; + evt_mode_change rp; + struct hci_request rq; + + memset(&cp, 0, sizeof (cp)); + cp.handle = handle; + cp.max_interval = max_interval; + cp.min_interval = min_interval; + + memset(&rq, 0, sizeof (rq)); + rq.ogf = OGF_LINK_POLICY; + rq.ocf = OCF_PARK_MODE; + rq.event = EVT_MODE_CHANGE; + rq.cparam = &cp; + rq.clen = PARK_MODE_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_MODE_CHANGE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_exit_park_mode(int dd, uint16_t handle, int to) +{ + exit_park_mode_cp cp; + evt_mode_change rp; + struct hci_request rq; + + memset(&cp, 0, sizeof (cp)); + cp.handle = handle; + + memset (&rq, 0, sizeof (rq)); + rq.ogf = OGF_LINK_POLICY; + rq.ocf = OCF_EXIT_PARK_MODE; + rq.event = EVT_MODE_CHANGE; + rq.cparam = &cp; + rq.clen = EXIT_PARK_MODE_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_MODE_CHANGE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_read_inquiry_scan_type(int dd, uint8_t *type, int to) +{ + read_inquiry_scan_type_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_INQUIRY_SCAN_TYPE; + rq.rparam = &rp; + rq.rlen = READ_INQUIRY_SCAN_TYPE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *type = rp.type; + return 0; +} + +int hci_write_inquiry_scan_type(int dd, uint8_t type, int to) +{ + write_inquiry_scan_type_cp cp; + write_inquiry_scan_type_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.type = type; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_INQUIRY_SCAN_TYPE; + rq.cparam = &cp; + rq.clen = WRITE_INQUIRY_SCAN_TYPE_CP_SIZE; + rq.rparam = &rp; + rq.rlen = WRITE_INQUIRY_SCAN_TYPE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_read_inquiry_mode(int dd, uint8_t *mode, int to) +{ + read_inquiry_mode_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_INQUIRY_MODE; + rq.rparam = &rp; + rq.rlen = READ_INQUIRY_MODE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *mode = rp.mode; + return 0; +} + +int hci_write_inquiry_mode(int dd, uint8_t mode, int to) +{ + write_inquiry_mode_cp cp; + write_inquiry_mode_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.mode = mode; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_INQUIRY_MODE; + rq.cparam = &cp; + rq.clen = WRITE_INQUIRY_MODE_CP_SIZE; + rq.rparam = &rp; + rq.rlen = WRITE_INQUIRY_MODE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_read_afh_mode(int dd, uint8_t *mode, int to) +{ + read_afh_mode_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_AFH_MODE; + rq.rparam = &rp; + rq.rlen = READ_AFH_MODE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *mode = rp.mode; + return 0; +} + +int hci_write_afh_mode(int dd, uint8_t mode, int to) +{ + write_afh_mode_cp cp; + write_afh_mode_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.mode = mode; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_AFH_MODE; + rq.cparam = &cp; + rq.clen = WRITE_AFH_MODE_CP_SIZE; + rq.rparam = &rp; + rq.rlen = WRITE_AFH_MODE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_read_ext_inquiry_response(int dd, uint8_t *fec, uint8_t *data, int to) +{ + read_ext_inquiry_response_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_EXT_INQUIRY_RESPONSE; + rq.rparam = &rp; + rq.rlen = READ_EXT_INQUIRY_RESPONSE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *fec = rp.fec; + memcpy(data, rp.data, HCI_MAX_EIR_LENGTH); + + return 0; +} + +int hci_write_ext_inquiry_response(int dd, uint8_t fec, uint8_t *data, int to) +{ + write_ext_inquiry_response_cp cp; + write_ext_inquiry_response_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.fec = fec; + memcpy(cp.data, data, HCI_MAX_EIR_LENGTH); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_EXT_INQUIRY_RESPONSE; + rq.cparam = &cp; + rq.clen = WRITE_EXT_INQUIRY_RESPONSE_CP_SIZE; + rq.rparam = &rp; + rq.rlen = WRITE_EXT_INQUIRY_RESPONSE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_read_simple_pairing_mode(int dd, uint8_t *mode, int to) +{ + read_simple_pairing_mode_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_SIMPLE_PAIRING_MODE; + rq.rparam = &rp; + rq.rlen = READ_SIMPLE_PAIRING_MODE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *mode = rp.mode; + return 0; +} + +int hci_write_simple_pairing_mode(int dd, uint8_t mode, int to) +{ + write_simple_pairing_mode_cp cp; + write_simple_pairing_mode_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.mode = mode; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_SIMPLE_PAIRING_MODE; + rq.cparam = &cp; + rq.clen = WRITE_SIMPLE_PAIRING_MODE_CP_SIZE; + rq.rparam = &rp; + rq.rlen = WRITE_SIMPLE_PAIRING_MODE_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_read_local_oob_data(int dd, uint8_t *hash, uint8_t *randomizer, int to) +{ + read_local_oob_data_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_LOCAL_OOB_DATA; + rq.rparam = &rp; + rq.rlen = READ_LOCAL_OOB_DATA_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + memcpy(hash, rp.hash, 16); + memcpy(randomizer, rp.randomizer, 16); + return 0; +} + +int hci_read_inq_response_tx_power_level(int dd, int8_t *level, int to) +{ + read_inq_response_tx_power_level_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL; + rq.rparam = &rp; + rq.rlen = READ_INQ_RESPONSE_TX_POWER_LEVEL_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *level = rp.level; + return 0; +} + +int hci_read_inquiry_transmit_power_level(int dd, int8_t *level, int to) +{ + return hci_read_inq_response_tx_power_level(dd, level, to); +} + +int hci_write_inquiry_transmit_power_level(int dd, int8_t level, int to) +{ + write_inquiry_transmit_power_level_cp cp; + write_inquiry_transmit_power_level_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.level = level; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL; + rq.cparam = &cp; + rq.clen = WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_CP_SIZE; + rq.rparam = &rp; + rq.rlen = WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_read_transmit_power_level(int dd, uint16_t handle, uint8_t type, + int8_t *level, int to) +{ + read_transmit_power_level_cp cp; + read_transmit_power_level_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + cp.type = type; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_TRANSMIT_POWER_LEVEL; + rq.cparam = &cp; + rq.clen = READ_TRANSMIT_POWER_LEVEL_CP_SIZE; + rq.rparam = &rp; + rq.rlen = READ_TRANSMIT_POWER_LEVEL_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *level = rp.level; + return 0; +} + +int hci_read_link_policy(int dd, uint16_t handle, uint16_t *policy, int to) +{ + read_link_policy_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_POLICY; + rq.ocf = OCF_READ_LINK_POLICY; + rq.cparam = &handle; + rq.clen = 2; + rq.rparam = &rp; + rq.rlen = READ_LINK_POLICY_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *policy = rp.policy; + return 0; +} + +int hci_write_link_policy(int dd, uint16_t handle, uint16_t policy, int to) +{ + write_link_policy_cp cp; + write_link_policy_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + cp.policy = policy; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_POLICY; + rq.ocf = OCF_WRITE_LINK_POLICY; + rq.cparam = &cp; + rq.clen = WRITE_LINK_POLICY_CP_SIZE; + rq.rparam = &rp; + rq.rlen = WRITE_LINK_POLICY_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_read_link_supervision_timeout(int dd, uint16_t handle, + uint16_t *timeout, int to) +{ + read_link_supervision_timeout_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_LINK_SUPERVISION_TIMEOUT; + rq.cparam = &handle; + rq.clen = 2; + rq.rparam = &rp; + rq.rlen = READ_LINK_SUPERVISION_TIMEOUT_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *timeout = rp.timeout; + return 0; +} + +int hci_write_link_supervision_timeout(int dd, uint16_t handle, + uint16_t timeout, int to) +{ + write_link_supervision_timeout_cp cp; + write_link_supervision_timeout_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + cp.timeout = timeout; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_LINK_SUPERVISION_TIMEOUT; + rq.cparam = &cp; + rq.clen = WRITE_LINK_SUPERVISION_TIMEOUT_CP_SIZE; + rq.rparam = &rp; + rq.rlen = WRITE_LINK_SUPERVISION_TIMEOUT_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_set_afh_classification(int dd, uint8_t *map, int to) +{ + set_afh_classification_cp cp; + set_afh_classification_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + memcpy(cp.map, map, 10); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_SET_AFH_CLASSIFICATION; + rq.cparam = &cp; + rq.clen = SET_AFH_CLASSIFICATION_CP_SIZE; + rq.rparam = &rp; + rq.rlen = SET_AFH_CLASSIFICATION_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_read_link_quality(int dd, uint16_t handle, uint8_t *link_quality, + int to) +{ + read_link_quality_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_STATUS_PARAM; + rq.ocf = OCF_READ_LINK_QUALITY; + rq.cparam = &handle; + rq.clen = 2; + rq.rparam = &rp; + rq.rlen = READ_LINK_QUALITY_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *link_quality = rp.link_quality; + return 0; +} + +int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to) +{ + read_rssi_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_STATUS_PARAM; + rq.ocf = OCF_READ_RSSI; + rq.cparam = &handle; + rq.clen = 2; + rq.rparam = &rp; + rq.rlen = READ_RSSI_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *rssi = rp.rssi; + return 0; +} + +int hci_read_afh_map(int dd, uint16_t handle, uint8_t *mode, uint8_t *map, + int to) +{ + read_afh_map_rp rp; + struct hci_request rq; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_STATUS_PARAM; + rq.ocf = OCF_READ_AFH_MAP; + rq.cparam = &handle; + rq.clen = 2; + rq.rparam = &rp; + rq.rlen = READ_AFH_MAP_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *mode = rp.mode; + memcpy(map, rp.map, 10); + return 0; +} + +int hci_read_clock(int dd, uint16_t handle, uint8_t which, uint32_t *clock, + uint16_t *accuracy, int to) +{ + read_clock_cp cp; + read_clock_rp rp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + cp.which_clock = which; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_STATUS_PARAM; + rq.ocf = OCF_READ_CLOCK; + rq.cparam = &cp; + rq.clen = READ_CLOCK_CP_SIZE; + rq.rparam = &rp; + rq.rlen = READ_CLOCK_RP_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + *clock = rp.clock; + *accuracy = rp.accuracy; + return 0; +} + +int hci_le_set_scan_enable(int dd, uint8_t enable, uint8_t filter_dup, int to) +{ + struct hci_request rq; + le_set_scan_enable_cp scan_cp; + uint8_t status; + + memset(&scan_cp, 0, sizeof(scan_cp)); + scan_cp.enable = enable; + scan_cp.filter_dup = filter_dup; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_SCAN_ENABLE; + rq.cparam = &scan_cp; + rq.clen = LE_SET_SCAN_ENABLE_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_le_set_scan_parameters(int dd, uint8_t type, + uint16_t interval, uint16_t window, + uint8_t own_type, uint8_t filter, int to) +{ + struct hci_request rq; + le_set_scan_parameters_cp param_cp; + uint8_t status; + + memset(¶m_cp, 0, sizeof(param_cp)); + param_cp.type = type; + param_cp.interval = interval; + param_cp.window = window; + param_cp.own_bdaddr_type = own_type; + param_cp.filter = filter; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_SCAN_PARAMETERS; + rq.cparam = ¶m_cp; + rq.clen = LE_SET_SCAN_PARAMETERS_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_le_set_advertise_enable(int dd, uint8_t enable, int to) +{ + struct hci_request rq; + le_set_advertise_enable_cp adv_cp; + uint8_t status; + + memset(&adv_cp, 0, sizeof(adv_cp)); + adv_cp.enable = enable; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_ADVERTISE_ENABLE; + rq.cparam = &adv_cp; + rq.clen = LE_SET_ADVERTISE_ENABLE_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_le_create_conn(int dd, uint16_t interval, uint16_t window, + uint8_t initiator_filter, uint8_t peer_bdaddr_type, + bdaddr_t peer_bdaddr, uint8_t own_bdaddr_type, + uint16_t min_interval, uint16_t max_interval, + uint16_t latency, uint16_t supervision_timeout, + uint16_t min_ce_length, uint16_t max_ce_length, + uint16_t *handle, int to) +{ + struct hci_request rq; + le_create_connection_cp create_conn_cp; + evt_le_connection_complete conn_complete_rp; + + memset(&create_conn_cp, 0, sizeof(create_conn_cp)); + create_conn_cp.interval = interval; + create_conn_cp.window = window; + create_conn_cp.initiator_filter = initiator_filter; + create_conn_cp.peer_bdaddr_type = peer_bdaddr_type; + create_conn_cp.peer_bdaddr = peer_bdaddr; + create_conn_cp.own_bdaddr_type = own_bdaddr_type; + create_conn_cp.min_interval = min_interval; + create_conn_cp.max_interval = max_interval; + create_conn_cp.latency = latency; + create_conn_cp.supervision_timeout = supervision_timeout; + create_conn_cp.min_ce_length = min_ce_length; + create_conn_cp.max_ce_length = max_ce_length; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_CREATE_CONN; + rq.event = EVT_LE_CONN_COMPLETE; + rq.cparam = &create_conn_cp; + rq.clen = LE_CREATE_CONN_CP_SIZE; + rq.rparam = &conn_complete_rp; + rq.rlen = EVT_CONN_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (conn_complete_rp.status) { + errno = EIO; + return -1; + } + + if (handle) + *handle = conn_complete_rp.handle; + + return 0; +} + +int hci_le_conn_update(int dd, uint16_t handle, uint16_t min_interval, + uint16_t max_interval, uint16_t latency, + uint16_t supervision_timeout, int to) +{ + evt_le_connection_update_complete evt; + le_connection_update_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + cp.min_interval = min_interval; + cp.max_interval = max_interval; + cp.latency = latency; + cp.supervision_timeout = supervision_timeout; + cp.min_ce_length = htobs(0x0001); + cp.max_ce_length = htobs(0x0001); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_CONN_UPDATE; + rq.cparam = &cp; + rq.clen = LE_CONN_UPDATE_CP_SIZE; + rq.event = EVT_LE_CONN_UPDATE_COMPLETE; + rq.rparam = &evt; + rq.rlen = sizeof(evt); + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (evt.status) { + errno = EIO; + return -1; + } + + return 0; +} + +int hci_le_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to) +{ + evt_le_read_remote_used_features_complete rp; + le_read_remote_used_features_cp cp; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp.handle = handle; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_READ_REMOTE_USED_FEATURES; + rq.event = EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE; + rq.cparam = &cp; + rq.clen = LE_READ_REMOTE_USED_FEATURES_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE_SIZE; + + if (hci_send_req(dd, &rq, to) < 0) + return -1; + + if (rp.status) { + errno = EIO; + return -1; + } + + if (features) + memcpy(features, rp.features, 8); + + return 0; +} diff --git a/lib/hci.h b/lib/hci.h new file mode 100644 index 0000000..794333b --- /dev/null +++ b/lib/hci.h @@ -0,0 +1,2454 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __HCI_H +#define __HCI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define HCI_MAX_DEV 16 + +#define HCI_MAX_ACL_SIZE (1492 + 4) +#define HCI_MAX_SCO_SIZE 255 +#define HCI_MAX_EVENT_SIZE 260 +#define HCI_MAX_FRAME_SIZE (HCI_MAX_ACL_SIZE + 4) + +/* HCI dev events */ +#define HCI_DEV_REG 1 +#define HCI_DEV_UNREG 2 +#define HCI_DEV_UP 3 +#define HCI_DEV_DOWN 4 +#define HCI_DEV_SUSPEND 5 +#define HCI_DEV_RESUME 6 + +/* HCI bus types */ +#define HCI_VIRTUAL 0 +#define HCI_USB 1 +#define HCI_PCCARD 2 +#define HCI_UART 3 +#define HCI_RS232 4 +#define HCI_PCI 5 +#define HCI_SDIO 6 +#define HCI_SPI 7 +#define HCI_I2C 8 +#define HCI_SMD 9 + +/* HCI controller types */ +#define HCI_PRIMARY 0x00 +#define HCI_AMP 0x01 +#define HCI_BREDR HCI_PRIMARY + +/* HCI device flags */ +enum { + HCI_UP, + HCI_INIT, + HCI_RUNNING, + + HCI_PSCAN, + HCI_ISCAN, + HCI_AUTH, + HCI_ENCRYPT, + HCI_INQUIRY, + + HCI_RAW, +}; + +/* LE address type */ +enum { + LE_PUBLIC_ADDRESS = 0x00, + LE_RANDOM_ADDRESS = 0x01 +}; + +/* HCI ioctl defines */ +#define HCIDEVUP _IOW('H', 201, int) +#define HCIDEVDOWN _IOW('H', 202, int) +#define HCIDEVRESET _IOW('H', 203, int) +#define HCIDEVRESTAT _IOW('H', 204, int) + +#define HCIGETDEVLIST _IOR('H', 210, int) +#define HCIGETDEVINFO _IOR('H', 211, int) +#define HCIGETCONNLIST _IOR('H', 212, int) +#define HCIGETCONNINFO _IOR('H', 213, int) +#define HCIGETAUTHINFO _IOR('H', 215, int) + +#define HCISETRAW _IOW('H', 220, int) +#define HCISETSCAN _IOW('H', 221, int) +#define HCISETAUTH _IOW('H', 222, int) +#define HCISETENCRYPT _IOW('H', 223, int) +#define HCISETPTYPE _IOW('H', 224, int) +#define HCISETLINKPOL _IOW('H', 225, int) +#define HCISETLINKMODE _IOW('H', 226, int) +#define HCISETACLMTU _IOW('H', 227, int) +#define HCISETSCOMTU _IOW('H', 228, int) + +#define HCIBLOCKADDR _IOW('H', 230, int) +#define HCIUNBLOCKADDR _IOW('H', 231, int) + +#define HCIINQUIRY _IOR('H', 240, int) + +#ifndef __NO_HCI_DEFS + +/* HCI Packet types */ +#define HCI_COMMAND_PKT 0x01 +#define HCI_ACLDATA_PKT 0x02 +#define HCI_SCODATA_PKT 0x03 +#define HCI_EVENT_PKT 0x04 +#define HCI_VENDOR_PKT 0xff + +/* HCI Packet types */ +#define HCI_2DH1 0x0002 +#define HCI_3DH1 0x0004 +#define HCI_DM1 0x0008 +#define HCI_DH1 0x0010 +#define HCI_2DH3 0x0100 +#define HCI_3DH3 0x0200 +#define HCI_DM3 0x0400 +#define HCI_DH3 0x0800 +#define HCI_2DH5 0x1000 +#define HCI_3DH5 0x2000 +#define HCI_DM5 0x4000 +#define HCI_DH5 0x8000 + +#define HCI_HV1 0x0020 +#define HCI_HV2 0x0040 +#define HCI_HV3 0x0080 + +#define HCI_EV3 0x0008 +#define HCI_EV4 0x0010 +#define HCI_EV5 0x0020 +#define HCI_2EV3 0x0040 +#define HCI_3EV3 0x0080 +#define HCI_2EV5 0x0100 +#define HCI_3EV5 0x0200 + +#define SCO_PTYPE_MASK (HCI_HV1 | HCI_HV2 | HCI_HV3) +#define ACL_PTYPE_MASK (HCI_DM1 | HCI_DH1 | HCI_DM3 | HCI_DH3 | HCI_DM5 | HCI_DH5) + +/* HCI Error codes */ +#define HCI_UNKNOWN_COMMAND 0x01 +#define HCI_NO_CONNECTION 0x02 +#define HCI_HARDWARE_FAILURE 0x03 +#define HCI_PAGE_TIMEOUT 0x04 +#define HCI_AUTHENTICATION_FAILURE 0x05 +#define HCI_PIN_OR_KEY_MISSING 0x06 +#define HCI_MEMORY_FULL 0x07 +#define HCI_CONNECTION_TIMEOUT 0x08 +#define HCI_MAX_NUMBER_OF_CONNECTIONS 0x09 +#define HCI_MAX_NUMBER_OF_SCO_CONNECTIONS 0x0a +#define HCI_ACL_CONNECTION_EXISTS 0x0b +#define HCI_COMMAND_DISALLOWED 0x0c +#define HCI_REJECTED_LIMITED_RESOURCES 0x0d +#define HCI_REJECTED_SECURITY 0x0e +#define HCI_REJECTED_PERSONAL 0x0f +#define HCI_HOST_TIMEOUT 0x10 +#define HCI_UNSUPPORTED_FEATURE 0x11 +#define HCI_INVALID_PARAMETERS 0x12 +#define HCI_OE_USER_ENDED_CONNECTION 0x13 +#define HCI_OE_LOW_RESOURCES 0x14 +#define HCI_OE_POWER_OFF 0x15 +#define HCI_CONNECTION_TERMINATED 0x16 +#define HCI_REPEATED_ATTEMPTS 0x17 +#define HCI_PAIRING_NOT_ALLOWED 0x18 +#define HCI_UNKNOWN_LMP_PDU 0x19 +#define HCI_UNSUPPORTED_REMOTE_FEATURE 0x1a +#define HCI_SCO_OFFSET_REJECTED 0x1b +#define HCI_SCO_INTERVAL_REJECTED 0x1c +#define HCI_AIR_MODE_REJECTED 0x1d +#define HCI_INVALID_LMP_PARAMETERS 0x1e +#define HCI_UNSPECIFIED_ERROR 0x1f +#define HCI_UNSUPPORTED_LMP_PARAMETER_VALUE 0x20 +#define HCI_ROLE_CHANGE_NOT_ALLOWED 0x21 +#define HCI_LMP_RESPONSE_TIMEOUT 0x22 +#define HCI_LMP_ERROR_TRANSACTION_COLLISION 0x23 +#define HCI_LMP_PDU_NOT_ALLOWED 0x24 +#define HCI_ENCRYPTION_MODE_NOT_ACCEPTED 0x25 +#define HCI_UNIT_LINK_KEY_USED 0x26 +#define HCI_QOS_NOT_SUPPORTED 0x27 +#define HCI_INSTANT_PASSED 0x28 +#define HCI_PAIRING_NOT_SUPPORTED 0x29 +#define HCI_TRANSACTION_COLLISION 0x2a +#define HCI_QOS_UNACCEPTABLE_PARAMETER 0x2c +#define HCI_QOS_REJECTED 0x2d +#define HCI_CLASSIFICATION_NOT_SUPPORTED 0x2e +#define HCI_INSUFFICIENT_SECURITY 0x2f +#define HCI_PARAMETER_OUT_OF_RANGE 0x30 +#define HCI_ROLE_SWITCH_PENDING 0x32 +#define HCI_SLOT_VIOLATION 0x34 +#define HCI_ROLE_SWITCH_FAILED 0x35 +#define HCI_EIR_TOO_LARGE 0x36 +#define HCI_SIMPLE_PAIRING_NOT_SUPPORTED 0x37 +#define HCI_HOST_BUSY_PAIRING 0x38 + +/* ACL flags */ +#define ACL_START_NO_FLUSH 0x00 +#define ACL_CONT 0x01 +#define ACL_START 0x02 +#define ACL_ACTIVE_BCAST 0x04 +#define ACL_PICO_BCAST 0x08 + +/* Baseband links */ +#define SCO_LINK 0x00 +#define ACL_LINK 0x01 +#define ESCO_LINK 0x02 + +/* LMP features */ +#define LMP_3SLOT 0x01 +#define LMP_5SLOT 0x02 +#define LMP_ENCRYPT 0x04 +#define LMP_SOFFSET 0x08 +#define LMP_TACCURACY 0x10 +#define LMP_RSWITCH 0x20 +#define LMP_HOLD 0x40 +#define LMP_SNIFF 0x80 + +#define LMP_PARK 0x01 +#define LMP_RSSI 0x02 +#define LMP_QUALITY 0x04 +#define LMP_SCO 0x08 +#define LMP_HV2 0x10 +#define LMP_HV3 0x20 +#define LMP_ULAW 0x40 +#define LMP_ALAW 0x80 + +#define LMP_CVSD 0x01 +#define LMP_PSCHEME 0x02 +#define LMP_PCONTROL 0x04 +#define LMP_TRSP_SCO 0x08 +#define LMP_BCAST_ENC 0x80 + +#define LMP_EDR_ACL_2M 0x02 +#define LMP_EDR_ACL_3M 0x04 +#define LMP_ENH_ISCAN 0x08 +#define LMP_ILACE_ISCAN 0x10 +#define LMP_ILACE_PSCAN 0x20 +#define LMP_RSSI_INQ 0x40 +#define LMP_ESCO 0x80 + +#define LMP_EV4 0x01 +#define LMP_EV5 0x02 +#define LMP_AFH_CAP_SLV 0x08 +#define LMP_AFH_CLS_SLV 0x10 +#define LMP_NO_BREDR 0x20 +#define LMP_LE 0x40 +#define LMP_EDR_3SLOT 0x80 + +#define LMP_EDR_5SLOT 0x01 +#define LMP_SNIFF_SUBR 0x02 +#define LMP_PAUSE_ENC 0x04 +#define LMP_AFH_CAP_MST 0x08 +#define LMP_AFH_CLS_MST 0x10 +#define LMP_EDR_ESCO_2M 0x20 +#define LMP_EDR_ESCO_3M 0x40 +#define LMP_EDR_3S_ESCO 0x80 + +#define LMP_EXT_INQ 0x01 +#define LMP_LE_BREDR 0x02 +#define LMP_SIMPLE_PAIR 0x08 +#define LMP_ENCAPS_PDU 0x10 +#define LMP_ERR_DAT_REP 0x20 +#define LMP_NFLUSH_PKTS 0x40 + +#define LMP_LSTO 0x01 +#define LMP_INQ_TX_PWR 0x02 +#define LMP_EPC 0x04 +#define LMP_EXT_FEAT 0x80 + +/* Extended LMP features */ +#define LMP_HOST_SSP 0x01 +#define LMP_HOST_LE 0x02 +#define LMP_HOST_LE_BREDR 0x04 + +/* Link policies */ +#define HCI_LP_RSWITCH 0x0001 +#define HCI_LP_HOLD 0x0002 +#define HCI_LP_SNIFF 0x0004 +#define HCI_LP_PARK 0x0008 + +/* Link mode */ +#define HCI_LM_ACCEPT 0x8000 +#define HCI_LM_MASTER 0x0001 +#define HCI_LM_AUTH 0x0002 +#define HCI_LM_ENCRYPT 0x0004 +#define HCI_LM_TRUSTED 0x0008 +#define HCI_LM_RELIABLE 0x0010 +#define HCI_LM_SECURE 0x0020 + +/* Link Key types */ +#define HCI_LK_COMBINATION 0x00 +#define HCI_LK_LOCAL_UNIT 0x01 +#define HCI_LK_REMOTE_UNIT 0x02 +#define HCI_LK_DEBUG_COMBINATION 0x03 +#define HCI_LK_UNAUTH_COMBINATION 0x04 +#define HCI_LK_AUTH_COMBINATION 0x05 +#define HCI_LK_CHANGED_COMBINATION 0x06 +#define HCI_LK_INVALID 0xFF + +/* ----- HCI Commands ----- */ + +/* Link Control */ +#define OGF_LINK_CTL 0x01 + +#define OCF_INQUIRY 0x0001 +typedef struct { + uint8_t lap[3]; + uint8_t length; /* 1.28s units */ + uint8_t num_rsp; +} __attribute__ ((packed)) inquiry_cp; +#define INQUIRY_CP_SIZE 5 + +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) status_bdaddr_rp; +#define STATUS_BDADDR_RP_SIZE 7 + +#define OCF_INQUIRY_CANCEL 0x0002 + +#define OCF_PERIODIC_INQUIRY 0x0003 +typedef struct { + uint16_t max_period; /* 1.28s units */ + uint16_t min_period; /* 1.28s units */ + uint8_t lap[3]; + uint8_t length; /* 1.28s units */ + uint8_t num_rsp; +} __attribute__ ((packed)) periodic_inquiry_cp; +#define PERIODIC_INQUIRY_CP_SIZE 9 + +#define OCF_EXIT_PERIODIC_INQUIRY 0x0004 + +#define OCF_CREATE_CONN 0x0005 +typedef struct { + bdaddr_t bdaddr; + uint16_t pkt_type; + uint8_t pscan_rep_mode; + uint8_t pscan_mode; + uint16_t clock_offset; + uint8_t role_switch; +} __attribute__ ((packed)) create_conn_cp; +#define CREATE_CONN_CP_SIZE 13 + +#define OCF_DISCONNECT 0x0006 +typedef struct { + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)) disconnect_cp; +#define DISCONNECT_CP_SIZE 3 + +#define OCF_ADD_SCO 0x0007 +typedef struct { + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)) add_sco_cp; +#define ADD_SCO_CP_SIZE 4 + +#define OCF_CREATE_CONN_CANCEL 0x0008 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) create_conn_cancel_cp; +#define CREATE_CONN_CANCEL_CP_SIZE 6 + +#define OCF_ACCEPT_CONN_REQ 0x0009 +typedef struct { + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) accept_conn_req_cp; +#define ACCEPT_CONN_REQ_CP_SIZE 7 + +#define OCF_REJECT_CONN_REQ 0x000A +typedef struct { + bdaddr_t bdaddr; + uint8_t reason; +} __attribute__ ((packed)) reject_conn_req_cp; +#define REJECT_CONN_REQ_CP_SIZE 7 + +#define OCF_LINK_KEY_REPLY 0x000B +typedef struct { + bdaddr_t bdaddr; + uint8_t link_key[16]; +} __attribute__ ((packed)) link_key_reply_cp; +#define LINK_KEY_REPLY_CP_SIZE 22 + +#define OCF_LINK_KEY_NEG_REPLY 0x000C + +#define OCF_PIN_CODE_REPLY 0x000D +typedef struct { + bdaddr_t bdaddr; + uint8_t pin_len; + uint8_t pin_code[16]; +} __attribute__ ((packed)) pin_code_reply_cp; +#define PIN_CODE_REPLY_CP_SIZE 23 + +#define OCF_PIN_CODE_NEG_REPLY 0x000E + +#define OCF_SET_CONN_PTYPE 0x000F +typedef struct { + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)) set_conn_ptype_cp; +#define SET_CONN_PTYPE_CP_SIZE 4 + +#define OCF_AUTH_REQUESTED 0x0011 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) auth_requested_cp; +#define AUTH_REQUESTED_CP_SIZE 2 + +#define OCF_SET_CONN_ENCRYPT 0x0013 +typedef struct { + uint16_t handle; + uint8_t encrypt; +} __attribute__ ((packed)) set_conn_encrypt_cp; +#define SET_CONN_ENCRYPT_CP_SIZE 3 + +#define OCF_CHANGE_CONN_LINK_KEY 0x0015 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) change_conn_link_key_cp; +#define CHANGE_CONN_LINK_KEY_CP_SIZE 2 + +#define OCF_MASTER_LINK_KEY 0x0017 +typedef struct { + uint8_t key_flag; +} __attribute__ ((packed)) master_link_key_cp; +#define MASTER_LINK_KEY_CP_SIZE 1 + +#define OCF_REMOTE_NAME_REQ 0x0019 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_mode; + uint16_t clock_offset; +} __attribute__ ((packed)) remote_name_req_cp; +#define REMOTE_NAME_REQ_CP_SIZE 10 + +#define OCF_REMOTE_NAME_REQ_CANCEL 0x001A +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) remote_name_req_cancel_cp; +#define REMOTE_NAME_REQ_CANCEL_CP_SIZE 6 + +#define OCF_READ_REMOTE_FEATURES 0x001B +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_remote_features_cp; +#define READ_REMOTE_FEATURES_CP_SIZE 2 + +#define OCF_READ_REMOTE_EXT_FEATURES 0x001C +typedef struct { + uint16_t handle; + uint8_t page_num; +} __attribute__ ((packed)) read_remote_ext_features_cp; +#define READ_REMOTE_EXT_FEATURES_CP_SIZE 3 + +#define OCF_READ_REMOTE_VERSION 0x001D +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_remote_version_cp; +#define READ_REMOTE_VERSION_CP_SIZE 2 + +#define OCF_READ_CLOCK_OFFSET 0x001F +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_clock_offset_cp; +#define READ_CLOCK_OFFSET_CP_SIZE 2 + +#define OCF_READ_LMP_HANDLE 0x0020 + +#define OCF_SETUP_SYNC_CONN 0x0028 +typedef struct { + uint16_t handle; + uint32_t tx_bandwith; + uint32_t rx_bandwith; + uint16_t max_latency; + uint16_t voice_setting; + uint8_t retrans_effort; + uint16_t pkt_type; +} __attribute__ ((packed)) setup_sync_conn_cp; +#define SETUP_SYNC_CONN_CP_SIZE 17 + +#define OCF_ACCEPT_SYNC_CONN_REQ 0x0029 +typedef struct { + bdaddr_t bdaddr; + uint32_t tx_bandwith; + uint32_t rx_bandwith; + uint16_t max_latency; + uint16_t voice_setting; + uint8_t retrans_effort; + uint16_t pkt_type; +} __attribute__ ((packed)) accept_sync_conn_req_cp; +#define ACCEPT_SYNC_CONN_REQ_CP_SIZE 21 + +#define OCF_REJECT_SYNC_CONN_REQ 0x002A +typedef struct { + bdaddr_t bdaddr; + uint8_t reason; +} __attribute__ ((packed)) reject_sync_conn_req_cp; +#define REJECT_SYNC_CONN_REQ_CP_SIZE 7 + +#define OCF_IO_CAPABILITY_REPLY 0x002B +typedef struct { + bdaddr_t bdaddr; + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __attribute__ ((packed)) io_capability_reply_cp; +#define IO_CAPABILITY_REPLY_CP_SIZE 9 + +#define OCF_USER_CONFIRM_REPLY 0x002C +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) user_confirm_reply_cp; +#define USER_CONFIRM_REPLY_CP_SIZE 6 + +#define OCF_USER_CONFIRM_NEG_REPLY 0x002D + +#define OCF_USER_PASSKEY_REPLY 0x002E +typedef struct { + bdaddr_t bdaddr; + uint32_t passkey; +} __attribute__ ((packed)) user_passkey_reply_cp; +#define USER_PASSKEY_REPLY_CP_SIZE 10 + +#define OCF_USER_PASSKEY_NEG_REPLY 0x002F + +#define OCF_REMOTE_OOB_DATA_REPLY 0x0030 +typedef struct { + bdaddr_t bdaddr; + uint8_t hash[16]; + uint8_t randomizer[16]; +} __attribute__ ((packed)) remote_oob_data_reply_cp; +#define REMOTE_OOB_DATA_REPLY_CP_SIZE 38 + +#define OCF_REMOTE_OOB_DATA_NEG_REPLY 0x0033 + +#define OCF_IO_CAPABILITY_NEG_REPLY 0x0034 +typedef struct { + bdaddr_t bdaddr; + uint8_t reason; +} __attribute__ ((packed)) io_capability_neg_reply_cp; +#define IO_CAPABILITY_NEG_REPLY_CP_SIZE 7 + +#define OCF_CREATE_PHYSICAL_LINK 0x0035 +typedef struct { + uint8_t handle; + uint8_t key_length; + uint8_t key_type; + uint8_t key[32]; +} __attribute__ ((packed)) create_physical_link_cp; +#define CREATE_PHYSICAL_LINK_CP_SIZE 35 + +#define OCF_ACCEPT_PHYSICAL_LINK 0x0036 +typedef struct { + uint8_t handle; + uint8_t key_length; + uint8_t key_type; + uint8_t key[32]; +} __attribute__ ((packed)) accept_physical_link_cp; +#define ACCEPT_PHYSICAL_LINK_CP_SIZE 35 + +#define OCF_DISCONNECT_PHYSICAL_LINK 0x0037 +typedef struct { + uint8_t handle; + uint8_t reason; +} __attribute__ ((packed)) disconnect_physical_link_cp; +#define DISCONNECT_PHYSICAL_LINK_CP_SIZE 2 + +#define OCF_CREATE_LOGICAL_LINK 0x0038 +typedef struct { + uint8_t handle; + uint8_t tx_flow[16]; + uint8_t rx_flow[16]; +} __attribute__ ((packed)) create_logical_link_cp; +#define CREATE_LOGICAL_LINK_CP_SIZE 33 + +#define OCF_ACCEPT_LOGICAL_LINK 0x0039 + +#define OCF_DISCONNECT_LOGICAL_LINK 0x003A +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) disconnect_logical_link_cp; +#define DISCONNECT_LOGICAL_LINK_CP_SIZE 2 + +#define OCF_LOGICAL_LINK_CANCEL 0x003B +typedef struct { + uint8_t handle; + uint8_t tx_flow_id; +} __attribute__ ((packed)) cancel_logical_link_cp; +#define LOGICAL_LINK_CANCEL_CP_SIZE 2 +typedef struct { + uint8_t status; + uint8_t handle; + uint8_t tx_flow_id; +} __attribute__ ((packed)) cancel_logical_link_rp; +#define LOGICAL_LINK_CANCEL_RP_SIZE 3 + +#define OCF_FLOW_SPEC_MODIFY 0x003C + +/* Link Policy */ +#define OGF_LINK_POLICY 0x02 + +#define OCF_HOLD_MODE 0x0001 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; +} __attribute__ ((packed)) hold_mode_cp; +#define HOLD_MODE_CP_SIZE 6 + +#define OCF_SNIFF_MODE 0x0003 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; + uint16_t attempt; + uint16_t timeout; +} __attribute__ ((packed)) sniff_mode_cp; +#define SNIFF_MODE_CP_SIZE 10 + +#define OCF_EXIT_SNIFF_MODE 0x0004 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) exit_sniff_mode_cp; +#define EXIT_SNIFF_MODE_CP_SIZE 2 + +#define OCF_PARK_MODE 0x0005 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; +} __attribute__ ((packed)) park_mode_cp; +#define PARK_MODE_CP_SIZE 6 + +#define OCF_EXIT_PARK_MODE 0x0006 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) exit_park_mode_cp; +#define EXIT_PARK_MODE_CP_SIZE 2 + +#define OCF_QOS_SETUP 0x0007 +typedef struct { + uint8_t service_type; /* 1 = best effort */ + uint32_t token_rate; /* Byte per seconds */ + uint32_t peak_bandwidth; /* Byte per seconds */ + uint32_t latency; /* Microseconds */ + uint32_t delay_variation; /* Microseconds */ +} __attribute__ ((packed)) hci_qos; +#define HCI_QOS_CP_SIZE 17 +typedef struct { + uint16_t handle; + uint8_t flags; /* Reserved */ + hci_qos qos; +} __attribute__ ((packed)) qos_setup_cp; +#define QOS_SETUP_CP_SIZE (3 + HCI_QOS_CP_SIZE) + +#define OCF_ROLE_DISCOVERY 0x0009 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) role_discovery_cp; +#define ROLE_DISCOVERY_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t role; +} __attribute__ ((packed)) role_discovery_rp; +#define ROLE_DISCOVERY_RP_SIZE 4 + +#define OCF_SWITCH_ROLE 0x000B +typedef struct { + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) switch_role_cp; +#define SWITCH_ROLE_CP_SIZE 7 + +#define OCF_READ_LINK_POLICY 0x000C +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_link_policy_cp; +#define READ_LINK_POLICY_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t policy; +} __attribute__ ((packed)) read_link_policy_rp; +#define READ_LINK_POLICY_RP_SIZE 5 + +#define OCF_WRITE_LINK_POLICY 0x000D +typedef struct { + uint16_t handle; + uint16_t policy; +} __attribute__ ((packed)) write_link_policy_cp; +#define WRITE_LINK_POLICY_CP_SIZE 4 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) write_link_policy_rp; +#define WRITE_LINK_POLICY_RP_SIZE 3 + +#define OCF_READ_DEFAULT_LINK_POLICY 0x000E + +#define OCF_WRITE_DEFAULT_LINK_POLICY 0x000F + +#define OCF_FLOW_SPECIFICATION 0x0010 + +#define OCF_SNIFF_SUBRATING 0x0011 +typedef struct { + uint16_t handle; + uint16_t max_latency; + uint16_t min_remote_timeout; + uint16_t min_local_timeout; +} __attribute__ ((packed)) sniff_subrating_cp; +#define SNIFF_SUBRATING_CP_SIZE 8 + +/* Host Controller and Baseband */ +#define OGF_HOST_CTL 0x03 + +#define OCF_SET_EVENT_MASK 0x0001 +typedef struct { + uint8_t mask[8]; +} __attribute__ ((packed)) set_event_mask_cp; +#define SET_EVENT_MASK_CP_SIZE 8 + +#define OCF_RESET 0x0003 + +#define OCF_SET_EVENT_FLT 0x0005 +typedef struct { + uint8_t flt_type; + uint8_t cond_type; + uint8_t condition[0]; +} __attribute__ ((packed)) set_event_flt_cp; +#define SET_EVENT_FLT_CP_SIZE 2 + +/* Filter types */ +#define FLT_CLEAR_ALL 0x00 +#define FLT_INQ_RESULT 0x01 +#define FLT_CONN_SETUP 0x02 +/* INQ_RESULT Condition types */ +#define INQ_RESULT_RETURN_ALL 0x00 +#define INQ_RESULT_RETURN_CLASS 0x01 +#define INQ_RESULT_RETURN_BDADDR 0x02 +/* CONN_SETUP Condition types */ +#define CONN_SETUP_ALLOW_ALL 0x00 +#define CONN_SETUP_ALLOW_CLASS 0x01 +#define CONN_SETUP_ALLOW_BDADDR 0x02 +/* CONN_SETUP Conditions */ +#define CONN_SETUP_AUTO_OFF 0x01 +#define CONN_SETUP_AUTO_ON 0x02 + +#define OCF_FLUSH 0x0008 + +#define OCF_READ_PIN_TYPE 0x0009 +typedef struct { + uint8_t status; + uint8_t pin_type; +} __attribute__ ((packed)) read_pin_type_rp; +#define READ_PIN_TYPE_RP_SIZE 2 + +#define OCF_WRITE_PIN_TYPE 0x000A +typedef struct { + uint8_t pin_type; +} __attribute__ ((packed)) write_pin_type_cp; +#define WRITE_PIN_TYPE_CP_SIZE 1 + +#define OCF_CREATE_NEW_UNIT_KEY 0x000B + +#define OCF_READ_STORED_LINK_KEY 0x000D +typedef struct { + bdaddr_t bdaddr; + uint8_t read_all; +} __attribute__ ((packed)) read_stored_link_key_cp; +#define READ_STORED_LINK_KEY_CP_SIZE 7 +typedef struct { + uint8_t status; + uint16_t max_keys; + uint16_t num_keys; +} __attribute__ ((packed)) read_stored_link_key_rp; +#define READ_STORED_LINK_KEY_RP_SIZE 5 + +#define OCF_WRITE_STORED_LINK_KEY 0x0011 +typedef struct { + uint8_t num_keys; + /* variable length part */ +} __attribute__ ((packed)) write_stored_link_key_cp; +#define WRITE_STORED_LINK_KEY_CP_SIZE 1 +typedef struct { + uint8_t status; + uint8_t num_keys; +} __attribute__ ((packed)) write_stored_link_key_rp; +#define READ_WRITE_LINK_KEY_RP_SIZE 2 + +#define OCF_DELETE_STORED_LINK_KEY 0x0012 +typedef struct { + bdaddr_t bdaddr; + uint8_t delete_all; +} __attribute__ ((packed)) delete_stored_link_key_cp; +#define DELETE_STORED_LINK_KEY_CP_SIZE 7 +typedef struct { + uint8_t status; + uint16_t num_keys; +} __attribute__ ((packed)) delete_stored_link_key_rp; +#define DELETE_STORED_LINK_KEY_RP_SIZE 3 + +#define HCI_MAX_NAME_LENGTH 248 + +#define OCF_CHANGE_LOCAL_NAME 0x0013 +typedef struct { + uint8_t name[HCI_MAX_NAME_LENGTH]; +} __attribute__ ((packed)) change_local_name_cp; +#define CHANGE_LOCAL_NAME_CP_SIZE 248 + +#define OCF_READ_LOCAL_NAME 0x0014 +typedef struct { + uint8_t status; + uint8_t name[HCI_MAX_NAME_LENGTH]; +} __attribute__ ((packed)) read_local_name_rp; +#define READ_LOCAL_NAME_RP_SIZE 249 + +#define OCF_READ_CONN_ACCEPT_TIMEOUT 0x0015 +typedef struct { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)) read_conn_accept_timeout_rp; +#define READ_CONN_ACCEPT_TIMEOUT_RP_SIZE 3 + +#define OCF_WRITE_CONN_ACCEPT_TIMEOUT 0x0016 +typedef struct { + uint16_t timeout; +} __attribute__ ((packed)) write_conn_accept_timeout_cp; +#define WRITE_CONN_ACCEPT_TIMEOUT_CP_SIZE 2 + +#define OCF_READ_PAGE_TIMEOUT 0x0017 +typedef struct { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)) read_page_timeout_rp; +#define READ_PAGE_TIMEOUT_RP_SIZE 3 + +#define OCF_WRITE_PAGE_TIMEOUT 0x0018 +typedef struct { + uint16_t timeout; +} __attribute__ ((packed)) write_page_timeout_cp; +#define WRITE_PAGE_TIMEOUT_CP_SIZE 2 + +#define OCF_READ_SCAN_ENABLE 0x0019 +typedef struct { + uint8_t status; + uint8_t enable; +} __attribute__ ((packed)) read_scan_enable_rp; +#define READ_SCAN_ENABLE_RP_SIZE 2 + +#define OCF_WRITE_SCAN_ENABLE 0x001A + #define SCAN_DISABLED 0x00 + #define SCAN_INQUIRY 0x01 + #define SCAN_PAGE 0x02 + +#define OCF_READ_PAGE_ACTIVITY 0x001B +typedef struct { + uint8_t status; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) read_page_activity_rp; +#define READ_PAGE_ACTIVITY_RP_SIZE 5 + +#define OCF_WRITE_PAGE_ACTIVITY 0x001C +typedef struct { + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) write_page_activity_cp; +#define WRITE_PAGE_ACTIVITY_CP_SIZE 4 + +#define OCF_READ_INQ_ACTIVITY 0x001D +typedef struct { + uint8_t status; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) read_inq_activity_rp; +#define READ_INQ_ACTIVITY_RP_SIZE 5 + +#define OCF_WRITE_INQ_ACTIVITY 0x001E +typedef struct { + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) write_inq_activity_cp; +#define WRITE_INQ_ACTIVITY_CP_SIZE 4 + +#define OCF_READ_AUTH_ENABLE 0x001F + +#define OCF_WRITE_AUTH_ENABLE 0x0020 + #define AUTH_DISABLED 0x00 + #define AUTH_ENABLED 0x01 + +#define OCF_READ_ENCRYPT_MODE 0x0021 + +#define OCF_WRITE_ENCRYPT_MODE 0x0022 + #define ENCRYPT_DISABLED 0x00 + #define ENCRYPT_P2P 0x01 + #define ENCRYPT_BOTH 0x02 + +#define OCF_READ_CLASS_OF_DEV 0x0023 +typedef struct { + uint8_t status; + uint8_t dev_class[3]; +} __attribute__ ((packed)) read_class_of_dev_rp; +#define READ_CLASS_OF_DEV_RP_SIZE 4 + +#define OCF_WRITE_CLASS_OF_DEV 0x0024 +typedef struct { + uint8_t dev_class[3]; +} __attribute__ ((packed)) write_class_of_dev_cp; +#define WRITE_CLASS_OF_DEV_CP_SIZE 3 + +#define OCF_READ_VOICE_SETTING 0x0025 +typedef struct { + uint8_t status; + uint16_t voice_setting; +} __attribute__ ((packed)) read_voice_setting_rp; +#define READ_VOICE_SETTING_RP_SIZE 3 + +#define OCF_WRITE_VOICE_SETTING 0x0026 +typedef struct { + uint16_t voice_setting; +} __attribute__ ((packed)) write_voice_setting_cp; +#define WRITE_VOICE_SETTING_CP_SIZE 2 + +#define OCF_READ_AUTOMATIC_FLUSH_TIMEOUT 0x0027 + +#define OCF_WRITE_AUTOMATIC_FLUSH_TIMEOUT 0x0028 + +#define OCF_READ_NUM_BROADCAST_RETRANS 0x0029 + +#define OCF_WRITE_NUM_BROADCAST_RETRANS 0x002A + +#define OCF_READ_HOLD_MODE_ACTIVITY 0x002B + +#define OCF_WRITE_HOLD_MODE_ACTIVITY 0x002C + +#define OCF_READ_TRANSMIT_POWER_LEVEL 0x002D +typedef struct { + uint16_t handle; + uint8_t type; +} __attribute__ ((packed)) read_transmit_power_level_cp; +#define READ_TRANSMIT_POWER_LEVEL_CP_SIZE 3 +typedef struct { + uint8_t status; + uint16_t handle; + int8_t level; +} __attribute__ ((packed)) read_transmit_power_level_rp; +#define READ_TRANSMIT_POWER_LEVEL_RP_SIZE 4 + +#define OCF_READ_SYNC_FLOW_ENABLE 0x002E + +#define OCF_WRITE_SYNC_FLOW_ENABLE 0x002F + +#define OCF_SET_CONTROLLER_TO_HOST_FC 0x0031 + +#define OCF_HOST_BUFFER_SIZE 0x0033 +typedef struct { + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_max_pkt; + uint16_t sco_max_pkt; +} __attribute__ ((packed)) host_buffer_size_cp; +#define HOST_BUFFER_SIZE_CP_SIZE 7 + +#define OCF_HOST_NUM_COMP_PKTS 0x0035 +typedef struct { + uint8_t num_hndl; + /* variable length part */ +} __attribute__ ((packed)) host_num_comp_pkts_cp; +#define HOST_NUM_COMP_PKTS_CP_SIZE 1 + +#define OCF_READ_LINK_SUPERVISION_TIMEOUT 0x0036 +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)) read_link_supervision_timeout_rp; +#define READ_LINK_SUPERVISION_TIMEOUT_RP_SIZE 5 + +#define OCF_WRITE_LINK_SUPERVISION_TIMEOUT 0x0037 +typedef struct { + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)) write_link_supervision_timeout_cp; +#define WRITE_LINK_SUPERVISION_TIMEOUT_CP_SIZE 4 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) write_link_supervision_timeout_rp; +#define WRITE_LINK_SUPERVISION_TIMEOUT_RP_SIZE 3 + +#define OCF_READ_NUM_SUPPORTED_IAC 0x0038 + +#define MAX_IAC_LAP 0x40 +#define OCF_READ_CURRENT_IAC_LAP 0x0039 +typedef struct { + uint8_t status; + uint8_t num_current_iac; + uint8_t lap[MAX_IAC_LAP][3]; +} __attribute__ ((packed)) read_current_iac_lap_rp; +#define READ_CURRENT_IAC_LAP_RP_SIZE 2+3*MAX_IAC_LAP + +#define OCF_WRITE_CURRENT_IAC_LAP 0x003A +typedef struct { + uint8_t num_current_iac; + uint8_t lap[MAX_IAC_LAP][3]; +} __attribute__ ((packed)) write_current_iac_lap_cp; +#define WRITE_CURRENT_IAC_LAP_CP_SIZE 1+3*MAX_IAC_LAP + +#define OCF_READ_PAGE_SCAN_PERIOD_MODE 0x003B + +#define OCF_WRITE_PAGE_SCAN_PERIOD_MODE 0x003C + +#define OCF_READ_PAGE_SCAN_MODE 0x003D + +#define OCF_WRITE_PAGE_SCAN_MODE 0x003E + +#define OCF_SET_AFH_CLASSIFICATION 0x003F +typedef struct { + uint8_t map[10]; +} __attribute__ ((packed)) set_afh_classification_cp; +#define SET_AFH_CLASSIFICATION_CP_SIZE 10 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) set_afh_classification_rp; +#define SET_AFH_CLASSIFICATION_RP_SIZE 1 + +#define OCF_READ_INQUIRY_SCAN_TYPE 0x0042 +typedef struct { + uint8_t status; + uint8_t type; +} __attribute__ ((packed)) read_inquiry_scan_type_rp; +#define READ_INQUIRY_SCAN_TYPE_RP_SIZE 2 + +#define OCF_WRITE_INQUIRY_SCAN_TYPE 0x0043 +typedef struct { + uint8_t type; +} __attribute__ ((packed)) write_inquiry_scan_type_cp; +#define WRITE_INQUIRY_SCAN_TYPE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_inquiry_scan_type_rp; +#define WRITE_INQUIRY_SCAN_TYPE_RP_SIZE 1 + +#define OCF_READ_INQUIRY_MODE 0x0044 +typedef struct { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)) read_inquiry_mode_rp; +#define READ_INQUIRY_MODE_RP_SIZE 2 + +#define OCF_WRITE_INQUIRY_MODE 0x0045 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_inquiry_mode_cp; +#define WRITE_INQUIRY_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_inquiry_mode_rp; +#define WRITE_INQUIRY_MODE_RP_SIZE 1 + +#define OCF_READ_PAGE_SCAN_TYPE 0x0046 + +#define OCF_WRITE_PAGE_SCAN_TYPE 0x0047 + #define PAGE_SCAN_TYPE_STANDARD 0x00 + #define PAGE_SCAN_TYPE_INTERLACED 0x01 + +#define OCF_READ_AFH_MODE 0x0048 +typedef struct { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)) read_afh_mode_rp; +#define READ_AFH_MODE_RP_SIZE 2 + +#define OCF_WRITE_AFH_MODE 0x0049 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_afh_mode_cp; +#define WRITE_AFH_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_afh_mode_rp; +#define WRITE_AFH_MODE_RP_SIZE 1 + +#define HCI_MAX_EIR_LENGTH 240 + +#define OCF_READ_EXT_INQUIRY_RESPONSE 0x0051 +typedef struct { + uint8_t status; + uint8_t fec; + uint8_t data[HCI_MAX_EIR_LENGTH]; +} __attribute__ ((packed)) read_ext_inquiry_response_rp; +#define READ_EXT_INQUIRY_RESPONSE_RP_SIZE 242 + +#define OCF_WRITE_EXT_INQUIRY_RESPONSE 0x0052 +typedef struct { + uint8_t fec; + uint8_t data[HCI_MAX_EIR_LENGTH]; +} __attribute__ ((packed)) write_ext_inquiry_response_cp; +#define WRITE_EXT_INQUIRY_RESPONSE_CP_SIZE 241 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_ext_inquiry_response_rp; +#define WRITE_EXT_INQUIRY_RESPONSE_RP_SIZE 1 + +#define OCF_REFRESH_ENCRYPTION_KEY 0x0053 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) refresh_encryption_key_cp; +#define REFRESH_ENCRYPTION_KEY_CP_SIZE 2 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) refresh_encryption_key_rp; +#define REFRESH_ENCRYPTION_KEY_RP_SIZE 1 + +#define OCF_READ_SIMPLE_PAIRING_MODE 0x0055 +typedef struct { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)) read_simple_pairing_mode_rp; +#define READ_SIMPLE_PAIRING_MODE_RP_SIZE 2 + +#define OCF_WRITE_SIMPLE_PAIRING_MODE 0x0056 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_simple_pairing_mode_cp; +#define WRITE_SIMPLE_PAIRING_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_simple_pairing_mode_rp; +#define WRITE_SIMPLE_PAIRING_MODE_RP_SIZE 1 + +#define OCF_READ_LOCAL_OOB_DATA 0x0057 +typedef struct { + uint8_t status; + uint8_t hash[16]; + uint8_t randomizer[16]; +} __attribute__ ((packed)) read_local_oob_data_rp; +#define READ_LOCAL_OOB_DATA_RP_SIZE 33 + +#define OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL 0x0058 +typedef struct { + uint8_t status; + int8_t level; +} __attribute__ ((packed)) read_inq_response_tx_power_level_rp; +#define READ_INQ_RESPONSE_TX_POWER_LEVEL_RP_SIZE 2 + +#define OCF_READ_INQUIRY_TRANSMIT_POWER_LEVEL 0x0058 +typedef struct { + uint8_t status; + int8_t level; +} __attribute__ ((packed)) read_inquiry_transmit_power_level_rp; +#define READ_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE 2 + +#define OCF_WRITE_INQUIRY_TRANSMIT_POWER_LEVEL 0x0059 +typedef struct { + int8_t level; +} __attribute__ ((packed)) write_inquiry_transmit_power_level_cp; +#define WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_inquiry_transmit_power_level_rp; +#define WRITE_INQUIRY_TRANSMIT_POWER_LEVEL_RP_SIZE 1 + +#define OCF_READ_DEFAULT_ERROR_DATA_REPORTING 0x005A +typedef struct { + uint8_t status; + uint8_t reporting; +} __attribute__ ((packed)) read_default_error_data_reporting_rp; +#define READ_DEFAULT_ERROR_DATA_REPORTING_RP_SIZE 2 + +#define OCF_WRITE_DEFAULT_ERROR_DATA_REPORTING 0x005B +typedef struct { + uint8_t reporting; +} __attribute__ ((packed)) write_default_error_data_reporting_cp; +#define WRITE_DEFAULT_ERROR_DATA_REPORTING_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_default_error_data_reporting_rp; +#define WRITE_DEFAULT_ERROR_DATA_REPORTING_RP_SIZE 1 + +#define OCF_ENHANCED_FLUSH 0x005F +typedef struct { + uint16_t handle; + uint8_t type; +} __attribute__ ((packed)) enhanced_flush_cp; +#define ENHANCED_FLUSH_CP_SIZE 3 + +#define OCF_SEND_KEYPRESS_NOTIFY 0x0060 +typedef struct { + bdaddr_t bdaddr; + uint8_t type; +} __attribute__ ((packed)) send_keypress_notify_cp; +#define SEND_KEYPRESS_NOTIFY_CP_SIZE 7 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) send_keypress_notify_rp; +#define SEND_KEYPRESS_NOTIFY_RP_SIZE 1 + +#define OCF_READ_LOGICAL_LINK_ACCEPT_TIMEOUT 0x0061 +typedef struct { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)) read_log_link_accept_timeout_rp; +#define READ_LOGICAL_LINK_ACCEPT_TIMEOUT_RP_SIZE 3 + +#define OCF_WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT 0x0062 +typedef struct { + uint16_t timeout; +} __attribute__ ((packed)) write_log_link_accept_timeout_cp; +#define WRITE_LOGICAL_LINK_ACCEPT_TIMEOUT_CP_SIZE 2 + +#define OCF_SET_EVENT_MASK_PAGE_2 0x0063 + +#define OCF_READ_LOCATION_DATA 0x0064 + +#define OCF_WRITE_LOCATION_DATA 0x0065 + +#define OCF_READ_FLOW_CONTROL_MODE 0x0066 + +#define OCF_WRITE_FLOW_CONTROL_MODE 0x0067 + +#define OCF_READ_ENHANCED_TRANSMIT_POWER_LEVEL 0x0068 +typedef struct { + uint8_t status; + uint16_t handle; + int8_t level_gfsk; + int8_t level_dqpsk; + int8_t level_8dpsk; +} __attribute__ ((packed)) read_enhanced_transmit_power_level_rp; +#define READ_ENHANCED_TRANSMIT_POWER_LEVEL_RP_SIZE 6 + +#define OCF_READ_BEST_EFFORT_FLUSH_TIMEOUT 0x0069 +typedef struct { + uint8_t status; + uint32_t timeout; +} __attribute__ ((packed)) read_best_effort_flush_timeout_rp; +#define READ_BEST_EFFORT_FLUSH_TIMEOUT_RP_SIZE 5 + +#define OCF_WRITE_BEST_EFFORT_FLUSH_TIMEOUT 0x006A +typedef struct { + uint16_t handle; + uint32_t timeout; +} __attribute__ ((packed)) write_best_effort_flush_timeout_cp; +#define WRITE_BEST_EFFORT_FLUSH_TIMEOUT_CP_SIZE 6 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_best_effort_flush_timeout_rp; +#define WRITE_BEST_EFFORT_FLUSH_TIMEOUT_RP_SIZE 1 + +#define OCF_READ_LE_HOST_SUPPORTED 0x006C +typedef struct { + uint8_t status; + uint8_t le; + uint8_t simul; +} __attribute__ ((packed)) read_le_host_supported_rp; +#define READ_LE_HOST_SUPPORTED_RP_SIZE 3 + +#define OCF_WRITE_LE_HOST_SUPPORTED 0x006D +typedef struct { + uint8_t le; + uint8_t simul; +} __attribute__ ((packed)) write_le_host_supported_cp; +#define WRITE_LE_HOST_SUPPORTED_CP_SIZE 2 + +/* Informational Parameters */ +#define OGF_INFO_PARAM 0x04 + +#define OCF_READ_LOCAL_VERSION 0x0001 +typedef struct { + uint8_t status; + uint8_t hci_ver; + uint16_t hci_rev; + uint8_t lmp_ver; + uint16_t manufacturer; + uint16_t lmp_subver; +} __attribute__ ((packed)) read_local_version_rp; +#define READ_LOCAL_VERSION_RP_SIZE 9 + +#define OCF_READ_LOCAL_COMMANDS 0x0002 +typedef struct { + uint8_t status; + uint8_t commands[64]; +} __attribute__ ((packed)) read_local_commands_rp; +#define READ_LOCAL_COMMANDS_RP_SIZE 65 + +#define OCF_READ_LOCAL_FEATURES 0x0003 +typedef struct { + uint8_t status; + uint8_t features[8]; +} __attribute__ ((packed)) read_local_features_rp; +#define READ_LOCAL_FEATURES_RP_SIZE 9 + +#define OCF_READ_LOCAL_EXT_FEATURES 0x0004 +typedef struct { + uint8_t page_num; +} __attribute__ ((packed)) read_local_ext_features_cp; +#define READ_LOCAL_EXT_FEATURES_CP_SIZE 1 +typedef struct { + uint8_t status; + uint8_t page_num; + uint8_t max_page_num; + uint8_t features[8]; +} __attribute__ ((packed)) read_local_ext_features_rp; +#define READ_LOCAL_EXT_FEATURES_RP_SIZE 11 + +#define OCF_READ_BUFFER_SIZE 0x0005 +typedef struct { + uint8_t status; + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_max_pkt; + uint16_t sco_max_pkt; +} __attribute__ ((packed)) read_buffer_size_rp; +#define READ_BUFFER_SIZE_RP_SIZE 8 + +#define OCF_READ_COUNTRY_CODE 0x0007 + +#define OCF_READ_BD_ADDR 0x0009 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) read_bd_addr_rp; +#define READ_BD_ADDR_RP_SIZE 7 + +#define OCF_READ_DATA_BLOCK_SIZE 0x000A +typedef struct { + uint8_t status; + uint16_t max_acl_len; + uint16_t data_block_len; + uint16_t num_blocks; +} __attribute__ ((packed)) read_data_block_size_rp; + +/* Status params */ +#define OGF_STATUS_PARAM 0x05 + +#define OCF_READ_FAILED_CONTACT_COUNTER 0x0001 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t counter; +} __attribute__ ((packed)) read_failed_contact_counter_rp; +#define READ_FAILED_CONTACT_COUNTER_RP_SIZE 4 + +#define OCF_RESET_FAILED_CONTACT_COUNTER 0x0002 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) reset_failed_contact_counter_rp; +#define RESET_FAILED_CONTACT_COUNTER_RP_SIZE 3 + +#define OCF_READ_LINK_QUALITY 0x0003 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t link_quality; +} __attribute__ ((packed)) read_link_quality_rp; +#define READ_LINK_QUALITY_RP_SIZE 4 + +#define OCF_READ_RSSI 0x0005 +typedef struct { + uint8_t status; + uint16_t handle; + int8_t rssi; +} __attribute__ ((packed)) read_rssi_rp; +#define READ_RSSI_RP_SIZE 4 + +#define OCF_READ_AFH_MAP 0x0006 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint8_t map[10]; +} __attribute__ ((packed)) read_afh_map_rp; +#define READ_AFH_MAP_RP_SIZE 14 + +#define OCF_READ_CLOCK 0x0007 +typedef struct { + uint16_t handle; + uint8_t which_clock; +} __attribute__ ((packed)) read_clock_cp; +#define READ_CLOCK_CP_SIZE 3 +typedef struct { + uint8_t status; + uint16_t handle; + uint32_t clock; + uint16_t accuracy; +} __attribute__ ((packed)) read_clock_rp; +#define READ_CLOCK_RP_SIZE 9 + +#define OCF_READ_LOCAL_AMP_INFO 0x0009 +typedef struct { + uint8_t status; + uint8_t amp_status; + uint32_t total_bandwidth; + uint32_t max_guaranteed_bandwidth; + uint32_t min_latency; + uint32_t max_pdu_size; + uint8_t controller_type; + uint16_t pal_caps; + uint16_t max_amp_assoc_length; + uint32_t max_flush_timeout; + uint32_t best_effort_flush_timeout; +} __attribute__ ((packed)) read_local_amp_info_rp; +#define READ_LOCAL_AMP_INFO_RP_SIZE 31 + +#define OCF_READ_LOCAL_AMP_ASSOC 0x000A +typedef struct { + uint8_t handle; + uint16_t length_so_far; + uint16_t assoc_length; +} __attribute__ ((packed)) read_local_amp_assoc_cp; +#define READ_LOCAL_AMP_ASSOC_CP_SIZE 5 +typedef struct { + uint8_t status; + uint8_t handle; + uint16_t length; + uint8_t fragment[HCI_MAX_NAME_LENGTH]; +} __attribute__ ((packed)) read_local_amp_assoc_rp; +#define READ_LOCAL_AMP_ASSOC_RP_SIZE 252 + +#define OCF_WRITE_REMOTE_AMP_ASSOC 0x000B +typedef struct { + uint8_t handle; + uint16_t length_so_far; + uint16_t remaining_length; + uint8_t fragment[HCI_MAX_NAME_LENGTH]; +} __attribute__ ((packed)) write_remote_amp_assoc_cp; +#define WRITE_REMOTE_AMP_ASSOC_CP_SIZE 253 +typedef struct { + uint8_t status; + uint8_t handle; +} __attribute__ ((packed)) write_remote_amp_assoc_rp; +#define WRITE_REMOTE_AMP_ASSOC_RP_SIZE 2 + +/* Testing commands */ +#define OGF_TESTING_CMD 0x3e + +#define OCF_READ_LOOPBACK_MODE 0x0001 + +#define OCF_WRITE_LOOPBACK_MODE 0x0002 + +#define OCF_ENABLE_DEVICE_UNDER_TEST_MODE 0x0003 + +#define OCF_WRITE_SIMPLE_PAIRING_DEBUG_MODE 0x0004 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_simple_pairing_debug_mode_cp; +#define WRITE_SIMPLE_PAIRING_DEBUG_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_simple_pairing_debug_mode_rp; +#define WRITE_SIMPLE_PAIRING_DEBUG_MODE_RP_SIZE 1 + +/* LE commands */ +#define OGF_LE_CTL 0x08 + +#define OCF_LE_SET_EVENT_MASK 0x0001 +typedef struct { + uint8_t mask[8]; +} __attribute__ ((packed)) le_set_event_mask_cp; +#define LE_SET_EVENT_MASK_CP_SIZE 8 + +#define OCF_LE_READ_BUFFER_SIZE 0x0002 +typedef struct { + uint8_t status; + uint16_t pkt_len; + uint8_t max_pkt; +} __attribute__ ((packed)) le_read_buffer_size_rp; +#define LE_READ_BUFFER_SIZE_RP_SIZE 4 + +#define OCF_LE_READ_LOCAL_SUPPORTED_FEATURES 0x0003 +typedef struct { + uint8_t status; + uint8_t features[8]; +} __attribute__ ((packed)) le_read_local_supported_features_rp; +#define LE_READ_LOCAL_SUPPORTED_FEATURES_RP_SIZE 9 + +#define OCF_LE_SET_RANDOM_ADDRESS 0x0005 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) le_set_random_address_cp; +#define LE_SET_RANDOM_ADDRESS_CP_SIZE 6 + +#define OCF_LE_SET_ADVERTISING_PARAMETERS 0x0006 +typedef struct { + uint16_t min_interval; + uint16_t max_interval; + uint8_t advtype; + uint8_t own_bdaddr_type; + uint8_t direct_bdaddr_type; + bdaddr_t direct_bdaddr; + uint8_t chan_map; + uint8_t filter; +} __attribute__ ((packed)) le_set_advertising_parameters_cp; +#define LE_SET_ADVERTISING_PARAMETERS_CP_SIZE 15 + +#define OCF_LE_READ_ADVERTISING_CHANNEL_TX_POWER 0x0007 +typedef struct { + uint8_t status; + int8_t level; +} __attribute__ ((packed)) le_read_advertising_channel_tx_power_rp; +#define LE_READ_ADVERTISING_CHANNEL_TX_POWER_RP_SIZE 2 + +#define OCF_LE_SET_ADVERTISING_DATA 0x0008 +typedef struct { + uint8_t length; + uint8_t data[31]; +} __attribute__ ((packed)) le_set_advertising_data_cp; +#define LE_SET_ADVERTISING_DATA_CP_SIZE 32 + +#define OCF_LE_SET_SCAN_RESPONSE_DATA 0x0009 +typedef struct { + uint8_t length; + uint8_t data[31]; +} __attribute__ ((packed)) le_set_scan_response_data_cp; +#define LE_SET_SCAN_RESPONSE_DATA_CP_SIZE 32 + +#define OCF_LE_SET_ADVERTISE_ENABLE 0x000A +typedef struct { + uint8_t enable; +} __attribute__ ((packed)) le_set_advertise_enable_cp; +#define LE_SET_ADVERTISE_ENABLE_CP_SIZE 1 + +#define OCF_LE_SET_SCAN_PARAMETERS 0x000B +typedef struct { + uint8_t type; + uint16_t interval; + uint16_t window; + uint8_t own_bdaddr_type; + uint8_t filter; +} __attribute__ ((packed)) le_set_scan_parameters_cp; +#define LE_SET_SCAN_PARAMETERS_CP_SIZE 7 + +#define OCF_LE_SET_SCAN_ENABLE 0x000C +typedef struct { + uint8_t enable; + uint8_t filter_dup; +} __attribute__ ((packed)) le_set_scan_enable_cp; +#define LE_SET_SCAN_ENABLE_CP_SIZE 2 + +#define OCF_LE_CREATE_CONN 0x000D +typedef struct { + uint16_t interval; + uint16_t window; + uint8_t initiator_filter; + uint8_t peer_bdaddr_type; + bdaddr_t peer_bdaddr; + uint8_t own_bdaddr_type; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t supervision_timeout; + uint16_t min_ce_length; + uint16_t max_ce_length; +} __attribute__ ((packed)) le_create_connection_cp; +#define LE_CREATE_CONN_CP_SIZE 25 + +#define OCF_LE_CREATE_CONN_CANCEL 0x000E + +#define OCF_LE_READ_WHITE_LIST_SIZE 0x000F +typedef struct { + uint8_t status; + uint8_t size; +} __attribute__ ((packed)) le_read_white_list_size_rp; +#define LE_READ_WHITE_LIST_SIZE_RP_SIZE 2 + +#define OCF_LE_CLEAR_WHITE_LIST 0x0010 + +#define OCF_LE_ADD_DEVICE_TO_WHITE_LIST 0x0011 +typedef struct { + uint8_t bdaddr_type; + bdaddr_t bdaddr; +} __attribute__ ((packed)) le_add_device_to_white_list_cp; +#define LE_ADD_DEVICE_TO_WHITE_LIST_CP_SIZE 7 + +#define OCF_LE_REMOVE_DEVICE_FROM_WHITE_LIST 0x0012 +typedef struct { + uint8_t bdaddr_type; + bdaddr_t bdaddr; +} __attribute__ ((packed)) le_remove_device_from_white_list_cp; +#define LE_REMOVE_DEVICE_FROM_WHITE_LIST_CP_SIZE 7 + +#define OCF_LE_CONN_UPDATE 0x0013 +typedef struct { + uint16_t handle; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t supervision_timeout; + uint16_t min_ce_length; + uint16_t max_ce_length; +} __attribute__ ((packed)) le_connection_update_cp; +#define LE_CONN_UPDATE_CP_SIZE 14 + +#define OCF_LE_SET_HOST_CHANNEL_CLASSIFICATION 0x0014 +typedef struct { + uint8_t map[5]; +} __attribute__ ((packed)) le_set_host_channel_classification_cp; +#define LE_SET_HOST_CHANNEL_CLASSIFICATION_CP_SIZE 5 + +#define OCF_LE_READ_CHANNEL_MAP 0x0015 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) le_read_channel_map_cp; +#define LE_READ_CHANNEL_MAP_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t map[5]; +} __attribute__ ((packed)) le_read_channel_map_rp; +#define LE_READ_CHANNEL_MAP_RP_SIZE 8 + +#define OCF_LE_READ_REMOTE_USED_FEATURES 0x0016 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) le_read_remote_used_features_cp; +#define LE_READ_REMOTE_USED_FEATURES_CP_SIZE 2 + +#define OCF_LE_ENCRYPT 0x0017 +typedef struct { + uint8_t key[16]; + uint8_t plaintext[16]; +} __attribute__ ((packed)) le_encrypt_cp; +#define LE_ENCRYPT_CP_SIZE 32 +typedef struct { + uint8_t status; + uint8_t data[16]; +} __attribute__ ((packed)) le_encrypt_rp; +#define LE_ENCRYPT_RP_SIZE 17 + +#define OCF_LE_RAND 0x0018 +typedef struct { + uint8_t status; + uint64_t random; +} __attribute__ ((packed)) le_rand_rp; +#define LE_RAND_RP_SIZE 9 + +#define OCF_LE_START_ENCRYPTION 0x0019 +typedef struct { + uint16_t handle; + uint64_t random; + uint16_t diversifier; + uint8_t key[16]; +} __attribute__ ((packed)) le_start_encryption_cp; +#define LE_START_ENCRYPTION_CP_SIZE 28 + +#define OCF_LE_LTK_REPLY 0x001A +typedef struct { + uint16_t handle; + uint8_t key[16]; +} __attribute__ ((packed)) le_ltk_reply_cp; +#define LE_LTK_REPLY_CP_SIZE 18 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) le_ltk_reply_rp; +#define LE_LTK_REPLY_RP_SIZE 3 + +#define OCF_LE_LTK_NEG_REPLY 0x001B +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) le_ltk_neg_reply_cp; +#define LE_LTK_NEG_REPLY_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) le_ltk_neg_reply_rp; +#define LE_LTK_NEG_REPLY_RP_SIZE 3 + +#define OCF_LE_READ_SUPPORTED_STATES 0x001C +typedef struct { + uint8_t status; + uint64_t states; +} __attribute__ ((packed)) le_read_supported_states_rp; +#define LE_READ_SUPPORTED_STATES_RP_SIZE 9 + +#define OCF_LE_RECEIVER_TEST 0x001D +typedef struct { + uint8_t frequency; +} __attribute__ ((packed)) le_receiver_test_cp; +#define LE_RECEIVER_TEST_CP_SIZE 1 + +#define OCF_LE_TRANSMITTER_TEST 0x001E +typedef struct { + uint8_t frequency; + uint8_t length; + uint8_t payload; +} __attribute__ ((packed)) le_transmitter_test_cp; +#define LE_TRANSMITTER_TEST_CP_SIZE 3 + +#define OCF_LE_TEST_END 0x001F +typedef struct { + uint8_t status; + uint16_t num_pkts; +} __attribute__ ((packed)) le_test_end_rp; +#define LE_TEST_END_RP_SIZE 3 + +#define OCF_LE_ADD_DEVICE_TO_RESOLV_LIST 0x0027 +typedef struct { + uint8_t bdaddr_type; + bdaddr_t bdaddr; + uint8_t peer_irk[16]; + uint8_t local_irk[16]; +} __attribute__ ((packed)) le_add_device_to_resolv_list_cp; +#define LE_ADD_DEVICE_TO_RESOLV_LIST_CP_SIZE 39 + +#define OCF_LE_REMOVE_DEVICE_FROM_RESOLV_LIST 0x0028 +typedef struct { + uint8_t bdaddr_type; + bdaddr_t bdaddr; +} __attribute__ ((packed)) le_remove_device_from_resolv_list_cp; +#define LE_REMOVE_DEVICE_FROM_RESOLV_LIST_CP_SIZE 7 + +#define OCF_LE_CLEAR_RESOLV_LIST 0x0029 + +#define OCF_LE_READ_RESOLV_LIST_SIZE 0x002A +typedef struct { + uint8_t status; + uint8_t size; +} __attribute__ ((packed)) le_read_resolv_list_size_rp; +#define LE_READ_RESOLV_LIST_SIZE_RP_SIZE 2 + +#define OCF_LE_SET_ADDRESS_RESOLUTION_ENABLE 0x002D +typedef struct { + uint8_t enable; +} __attribute__ ((packed)) le_set_address_resolution_enable_cp; +#define LE_SET_ADDRESS_RESOLUTION_ENABLE_CP_SIZE 1 + +/* Vendor specific commands */ +#define OGF_VENDOR_CMD 0x3f + +/* ---- HCI Events ---- */ + +#define EVT_INQUIRY_COMPLETE 0x01 + +#define EVT_INQUIRY_RESULT 0x02 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +} __attribute__ ((packed)) inquiry_info; +#define INQUIRY_INFO_SIZE 14 + +#define EVT_CONN_COMPLETE 0x03 +typedef struct { + uint8_t status; + uint16_t handle; + bdaddr_t bdaddr; + uint8_t link_type; + uint8_t encr_mode; +} __attribute__ ((packed)) evt_conn_complete; +#define EVT_CONN_COMPLETE_SIZE 11 + +#define EVT_CONN_REQUEST 0x04 +typedef struct { + bdaddr_t bdaddr; + uint8_t dev_class[3]; + uint8_t link_type; +} __attribute__ ((packed)) evt_conn_request; +#define EVT_CONN_REQUEST_SIZE 10 + +#define EVT_DISCONN_COMPLETE 0x05 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)) evt_disconn_complete; +#define EVT_DISCONN_COMPLETE_SIZE 4 + +#define EVT_AUTH_COMPLETE 0x06 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_auth_complete; +#define EVT_AUTH_COMPLETE_SIZE 3 + +#define EVT_REMOTE_NAME_REQ_COMPLETE 0x07 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; + uint8_t name[HCI_MAX_NAME_LENGTH]; +} __attribute__ ((packed)) evt_remote_name_req_complete; +#define EVT_REMOTE_NAME_REQ_COMPLETE_SIZE 255 + +#define EVT_ENCRYPT_CHANGE 0x08 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t encrypt; +} __attribute__ ((packed)) evt_encrypt_change; +#define EVT_ENCRYPT_CHANGE_SIZE 4 + +#define EVT_CHANGE_CONN_LINK_KEY_COMPLETE 0x09 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_change_conn_link_key_complete; +#define EVT_CHANGE_CONN_LINK_KEY_COMPLETE_SIZE 3 + +#define EVT_MASTER_LINK_KEY_COMPLETE 0x0A +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t key_flag; +} __attribute__ ((packed)) evt_master_link_key_complete; +#define EVT_MASTER_LINK_KEY_COMPLETE_SIZE 4 + +#define EVT_READ_REMOTE_FEATURES_COMPLETE 0x0B +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __attribute__ ((packed)) evt_read_remote_features_complete; +#define EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE 11 + +#define EVT_READ_REMOTE_VERSION_COMPLETE 0x0C +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t lmp_ver; + uint16_t manufacturer; + uint16_t lmp_subver; +} __attribute__ ((packed)) evt_read_remote_version_complete; +#define EVT_READ_REMOTE_VERSION_COMPLETE_SIZE 8 + +#define EVT_QOS_SETUP_COMPLETE 0x0D +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t flags; /* Reserved */ + hci_qos qos; +} __attribute__ ((packed)) evt_qos_setup_complete; +#define EVT_QOS_SETUP_COMPLETE_SIZE (4 + HCI_QOS_CP_SIZE) + +#define EVT_CMD_COMPLETE 0x0E +typedef struct { + uint8_t ncmd; + uint16_t opcode; +} __attribute__ ((packed)) evt_cmd_complete; +#define EVT_CMD_COMPLETE_SIZE 3 + +#define EVT_CMD_STATUS 0x0F +typedef struct { + uint8_t status; + uint8_t ncmd; + uint16_t opcode; +} __attribute__ ((packed)) evt_cmd_status; +#define EVT_CMD_STATUS_SIZE 4 + +#define EVT_HARDWARE_ERROR 0x10 +typedef struct { + uint8_t code; +} __attribute__ ((packed)) evt_hardware_error; +#define EVT_HARDWARE_ERROR_SIZE 1 + +#define EVT_FLUSH_OCCURRED 0x11 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) evt_flush_occured; +#define EVT_FLUSH_OCCURRED_SIZE 2 + +#define EVT_ROLE_CHANGE 0x12 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) evt_role_change; +#define EVT_ROLE_CHANGE_SIZE 8 + +#define EVT_NUM_COMP_PKTS 0x13 +typedef struct { + uint8_t num_hndl; + /* variable length part */ +} __attribute__ ((packed)) evt_num_comp_pkts; +#define EVT_NUM_COMP_PKTS_SIZE 1 + +#define EVT_MODE_CHANGE 0x14 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint16_t interval; +} __attribute__ ((packed)) evt_mode_change; +#define EVT_MODE_CHANGE_SIZE 6 + +#define EVT_RETURN_LINK_KEYS 0x15 +typedef struct { + uint8_t num_keys; + /* variable length part */ +} __attribute__ ((packed)) evt_return_link_keys; +#define EVT_RETURN_LINK_KEYS_SIZE 1 + +#define EVT_PIN_CODE_REQ 0x16 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_pin_code_req; +#define EVT_PIN_CODE_REQ_SIZE 6 + +#define EVT_LINK_KEY_REQ 0x17 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_link_key_req; +#define EVT_LINK_KEY_REQ_SIZE 6 + +#define EVT_LINK_KEY_NOTIFY 0x18 +typedef struct { + bdaddr_t bdaddr; + uint8_t link_key[16]; + uint8_t key_type; +} __attribute__ ((packed)) evt_link_key_notify; +#define EVT_LINK_KEY_NOTIFY_SIZE 23 + +#define EVT_LOOPBACK_COMMAND 0x19 + +#define EVT_DATA_BUFFER_OVERFLOW 0x1A +typedef struct { + uint8_t link_type; +} __attribute__ ((packed)) evt_data_buffer_overflow; +#define EVT_DATA_BUFFER_OVERFLOW_SIZE 1 + +#define EVT_MAX_SLOTS_CHANGE 0x1B +typedef struct { + uint16_t handle; + uint8_t max_slots; +} __attribute__ ((packed)) evt_max_slots_change; +#define EVT_MAX_SLOTS_CHANGE_SIZE 3 + +#define EVT_READ_CLOCK_OFFSET_COMPLETE 0x1C +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t clock_offset; +} __attribute__ ((packed)) evt_read_clock_offset_complete; +#define EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE 5 + +#define EVT_CONN_PTYPE_CHANGED 0x1D +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t ptype; +} __attribute__ ((packed)) evt_conn_ptype_changed; +#define EVT_CONN_PTYPE_CHANGED_SIZE 5 + +#define EVT_QOS_VIOLATION 0x1E +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) evt_qos_violation; +#define EVT_QOS_VIOLATION_SIZE 2 + +#define EVT_PSCAN_REP_MODE_CHANGE 0x20 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; +} __attribute__ ((packed)) evt_pscan_rep_mode_change; +#define EVT_PSCAN_REP_MODE_CHANGE_SIZE 7 + +#define EVT_FLOW_SPEC_COMPLETE 0x21 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t flags; + uint8_t direction; + hci_qos qos; +} __attribute__ ((packed)) evt_flow_spec_complete; +#define EVT_FLOW_SPEC_COMPLETE_SIZE (5 + HCI_QOS_CP_SIZE) + +#define EVT_INQUIRY_RESULT_WITH_RSSI 0x22 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; +} __attribute__ ((packed)) inquiry_info_with_rssi; +#define INQUIRY_INFO_WITH_RSSI_SIZE 14 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; +} __attribute__ ((packed)) inquiry_info_with_rssi_and_pscan_mode; +#define INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE 15 + +#define EVT_READ_REMOTE_EXT_FEATURES_COMPLETE 0x23 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t page_num; + uint8_t max_page_num; + uint8_t features[8]; +} __attribute__ ((packed)) evt_read_remote_ext_features_complete; +#define EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE 13 + +#define EVT_SYNC_CONN_COMPLETE 0x2C +typedef struct { + uint8_t status; + uint16_t handle; + bdaddr_t bdaddr; + uint8_t link_type; + uint8_t trans_interval; + uint8_t retrans_window; + uint16_t rx_pkt_len; + uint16_t tx_pkt_len; + uint8_t air_mode; +} __attribute__ ((packed)) evt_sync_conn_complete; +#define EVT_SYNC_CONN_COMPLETE_SIZE 17 + +#define EVT_SYNC_CONN_CHANGED 0x2D +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t trans_interval; + uint8_t retrans_window; + uint16_t rx_pkt_len; + uint16_t tx_pkt_len; +} __attribute__ ((packed)) evt_sync_conn_changed; +#define EVT_SYNC_CONN_CHANGED_SIZE 9 + +#define EVT_SNIFF_SUBRATING 0x2E +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t max_tx_latency; + uint16_t max_rx_latency; + uint16_t min_remote_timeout; + uint16_t min_local_timeout; +} __attribute__ ((packed)) evt_sniff_subrating; +#define EVT_SNIFF_SUBRATING_SIZE 11 + +#define EVT_EXTENDED_INQUIRY_RESULT 0x2F +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[HCI_MAX_EIR_LENGTH]; +} __attribute__ ((packed)) extended_inquiry_info; +#define EXTENDED_INQUIRY_INFO_SIZE 254 + +#define EVT_ENCRYPTION_KEY_REFRESH_COMPLETE 0x30 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_encryption_key_refresh_complete; +#define EVT_ENCRYPTION_KEY_REFRESH_COMPLETE_SIZE 3 + +#define EVT_IO_CAPABILITY_REQUEST 0x31 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_io_capability_request; +#define EVT_IO_CAPABILITY_REQUEST_SIZE 6 + +#define EVT_IO_CAPABILITY_RESPONSE 0x32 +typedef struct { + bdaddr_t bdaddr; + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __attribute__ ((packed)) evt_io_capability_response; +#define EVT_IO_CAPABILITY_RESPONSE_SIZE 9 + +#define EVT_USER_CONFIRM_REQUEST 0x33 +typedef struct { + bdaddr_t bdaddr; + uint32_t passkey; +} __attribute__ ((packed)) evt_user_confirm_request; +#define EVT_USER_CONFIRM_REQUEST_SIZE 10 + +#define EVT_USER_PASSKEY_REQUEST 0x34 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_user_passkey_request; +#define EVT_USER_PASSKEY_REQUEST_SIZE 6 + +#define EVT_REMOTE_OOB_DATA_REQUEST 0x35 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_remote_oob_data_request; +#define EVT_REMOTE_OOB_DATA_REQUEST_SIZE 6 + +#define EVT_SIMPLE_PAIRING_COMPLETE 0x36 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_simple_pairing_complete; +#define EVT_SIMPLE_PAIRING_COMPLETE_SIZE 7 + +#define EVT_LINK_SUPERVISION_TIMEOUT_CHANGED 0x38 +typedef struct { + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)) evt_link_supervision_timeout_changed; +#define EVT_LINK_SUPERVISION_TIMEOUT_CHANGED_SIZE 4 + +#define EVT_ENHANCED_FLUSH_COMPLETE 0x39 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) evt_enhanced_flush_complete; +#define EVT_ENHANCED_FLUSH_COMPLETE_SIZE 2 + +#define EVT_USER_PASSKEY_NOTIFY 0x3B +typedef struct { + bdaddr_t bdaddr; + uint32_t passkey; +} __attribute__ ((packed)) evt_user_passkey_notify; +#define EVT_USER_PASSKEY_NOTIFY_SIZE 10 + +#define EVT_KEYPRESS_NOTIFY 0x3C +typedef struct { + bdaddr_t bdaddr; + uint8_t type; +} __attribute__ ((packed)) evt_keypress_notify; +#define EVT_KEYPRESS_NOTIFY_SIZE 7 + +#define EVT_REMOTE_HOST_FEATURES_NOTIFY 0x3D +typedef struct { + bdaddr_t bdaddr; + uint8_t features[8]; +} __attribute__ ((packed)) evt_remote_host_features_notify; +#define EVT_REMOTE_HOST_FEATURES_NOTIFY_SIZE 14 + +#define EVT_LE_META_EVENT 0x3E +typedef struct { + uint8_t subevent; + uint8_t data[0]; +} __attribute__ ((packed)) evt_le_meta_event; +#define EVT_LE_META_EVENT_SIZE 1 + +#define EVT_LE_CONN_COMPLETE 0x01 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t role; + uint8_t peer_bdaddr_type; + bdaddr_t peer_bdaddr; + uint16_t interval; + uint16_t latency; + uint16_t supervision_timeout; + uint8_t master_clock_accuracy; +} __attribute__ ((packed)) evt_le_connection_complete; +#define EVT_LE_CONN_COMPLETE_SIZE 18 + +#define EVT_LE_ADVERTISING_REPORT 0x02 +typedef struct { + uint8_t evt_type; + uint8_t bdaddr_type; + bdaddr_t bdaddr; + uint8_t length; + uint8_t data[0]; +} __attribute__ ((packed)) le_advertising_info; +#define LE_ADVERTISING_INFO_SIZE 9 + +#define EVT_LE_CONN_UPDATE_COMPLETE 0x03 +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t interval; + uint16_t latency; + uint16_t supervision_timeout; +} __attribute__ ((packed)) evt_le_connection_update_complete; +#define EVT_LE_CONN_UPDATE_COMPLETE_SIZE 9 + +#define EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE 0x04 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __attribute__ ((packed)) evt_le_read_remote_used_features_complete; +#define EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE_SIZE 11 + +#define EVT_LE_LTK_REQUEST 0x05 +typedef struct { + uint16_t handle; + uint64_t random; + uint16_t diversifier; +} __attribute__ ((packed)) evt_le_long_term_key_request; +#define EVT_LE_LTK_REQUEST_SIZE 12 + +#define EVT_PHYSICAL_LINK_COMPLETE 0x40 +typedef struct { + uint8_t status; + uint8_t handle; +} __attribute__ ((packed)) evt_physical_link_complete; +#define EVT_PHYSICAL_LINK_COMPLETE_SIZE 2 + +#define EVT_CHANNEL_SELECTED 0x41 + +#define EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE 0x42 +typedef struct { + uint8_t status; + uint8_t handle; + uint8_t reason; +} __attribute__ ((packed)) evt_disconn_physical_link_complete; +#define EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE_SIZE 3 + +#define EVT_PHYSICAL_LINK_LOSS_EARLY_WARNING 0x43 +typedef struct { + uint8_t handle; + uint8_t reason; +} __attribute__ ((packed)) evt_physical_link_loss_warning; +#define EVT_PHYSICAL_LINK_LOSS_WARNING_SIZE 2 + +#define EVT_PHYSICAL_LINK_RECOVERY 0x44 +typedef struct { + uint8_t handle; +} __attribute__ ((packed)) evt_physical_link_recovery; +#define EVT_PHYSICAL_LINK_RECOVERY_SIZE 1 + +#define EVT_LOGICAL_LINK_COMPLETE 0x45 +typedef struct { + uint8_t status; + uint16_t log_handle; + uint8_t handle; + uint8_t tx_flow_id; +} __attribute__ ((packed)) evt_logical_link_complete; +#define EVT_LOGICAL_LINK_COMPLETE_SIZE 5 + +#define EVT_DISCONNECT_LOGICAL_LINK_COMPLETE 0x46 + +#define EVT_FLOW_SPEC_MODIFY_COMPLETE 0x47 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_flow_spec_modify_complete; +#define EVT_FLOW_SPEC_MODIFY_COMPLETE_SIZE 3 + +#define EVT_NUMBER_COMPLETED_BLOCKS 0x48 +typedef struct { + uint16_t handle; + uint16_t num_cmplt_pkts; + uint16_t num_cmplt_blks; +} __attribute__ ((packed)) cmplt_handle; +typedef struct { + uint16_t total_num_blocks; + uint8_t num_handles; + cmplt_handle handles[0]; +} __attribute__ ((packed)) evt_num_completed_blocks; + +#define EVT_AMP_STATUS_CHANGE 0x4D +typedef struct { + uint8_t status; + uint8_t amp_status; +} __attribute__ ((packed)) evt_amp_status_change; +#define EVT_AMP_STATUS_CHANGE_SIZE 2 + +#define EVT_TESTING 0xFE + +#define EVT_VENDOR 0xFF + +/* Internal events generated by BlueZ stack */ +#define EVT_STACK_INTERNAL 0xFD +typedef struct { + uint16_t type; + uint8_t data[0]; +} __attribute__ ((packed)) evt_stack_internal; +#define EVT_STACK_INTERNAL_SIZE 2 + +#define EVT_SI_DEVICE 0x01 +typedef struct { + uint16_t event; + uint16_t dev_id; +} __attribute__ ((packed)) evt_si_device; +#define EVT_SI_DEVICE_SIZE 4 + +/* -------- HCI Packet structures -------- */ +#define HCI_TYPE_LEN 1 + +typedef struct { + uint16_t opcode; /* OCF & OGF */ + uint8_t plen; +} __attribute__ ((packed)) hci_command_hdr; +#define HCI_COMMAND_HDR_SIZE 3 + +typedef struct { + uint8_t evt; + uint8_t plen; +} __attribute__ ((packed)) hci_event_hdr; +#define HCI_EVENT_HDR_SIZE 2 + +typedef struct { + uint16_t handle; /* Handle & Flags(PB, BC) */ + uint16_t dlen; +} __attribute__ ((packed)) hci_acl_hdr; +#define HCI_ACL_HDR_SIZE 4 + +typedef struct { + uint16_t handle; + uint8_t dlen; +} __attribute__ ((packed)) hci_sco_hdr; +#define HCI_SCO_HDR_SIZE 3 + +typedef struct { + uint16_t device; + uint16_t type; + uint16_t plen; +} __attribute__ ((packed)) hci_msg_hdr; +#define HCI_MSG_HDR_SIZE 6 + +/* Command opcode pack/unpack */ +#define cmd_opcode_pack(ogf, ocf) (uint16_t)((ocf & 0x03ff)|(ogf << 10)) +#define cmd_opcode_ogf(op) (op >> 10) +#define cmd_opcode_ocf(op) (op & 0x03ff) + +/* ACL handle and flags pack/unpack */ +#define acl_handle_pack(h, f) (uint16_t)((h & 0x0fff)|(f << 12)) +#define acl_handle(h) (h & 0x0fff) +#define acl_flags(h) (h >> 12) + +#endif /* _NO_HCI_DEFS */ + +/* HCI Socket options */ +#define HCI_DATA_DIR 1 +#define HCI_FILTER 2 +#define HCI_TIME_STAMP 3 + +/* HCI CMSG flags */ +#define HCI_CMSG_DIR 0x0001 +#define HCI_CMSG_TSTAMP 0x0002 + +struct sockaddr_hci { + sa_family_t hci_family; + unsigned short hci_dev; + unsigned short hci_channel; +}; +#define HCI_DEV_NONE 0xffff + +#define HCI_CHANNEL_RAW 0 +#define HCI_CHANNEL_USER 1 +#define HCI_CHANNEL_MONITOR 2 +#define HCI_CHANNEL_CONTROL 3 +#define HCI_CHANNEL_LOGGING 4 + +struct hci_filter { + uint32_t type_mask; + uint32_t event_mask[2]; + uint16_t opcode; +}; + +#define HCI_FLT_TYPE_BITS 31 +#define HCI_FLT_EVENT_BITS 63 +#define HCI_FLT_OGF_BITS 63 +#define HCI_FLT_OCF_BITS 127 + +/* Ioctl requests structures */ +struct hci_dev_stats { + uint32_t err_rx; + uint32_t err_tx; + uint32_t cmd_tx; + uint32_t evt_rx; + uint32_t acl_tx; + uint32_t acl_rx; + uint32_t sco_tx; + uint32_t sco_rx; + uint32_t byte_rx; + uint32_t byte_tx; +}; + +struct hci_dev_info { + uint16_t dev_id; + char name[8]; + + bdaddr_t bdaddr; + + uint32_t flags; + uint8_t type; + + uint8_t features[8]; + + uint32_t pkt_type; + uint32_t link_policy; + uint32_t link_mode; + + uint16_t acl_mtu; + uint16_t acl_pkts; + uint16_t sco_mtu; + uint16_t sco_pkts; + + struct hci_dev_stats stat; +}; + +struct hci_conn_info { + uint16_t handle; + bdaddr_t bdaddr; + uint8_t type; + uint8_t out; + uint16_t state; + uint32_t link_mode; +}; + +struct hci_dev_req { + uint16_t dev_id; + uint32_t dev_opt; +}; + +struct hci_dev_list_req { + uint16_t dev_num; + struct hci_dev_req dev_req[0]; /* hci_dev_req structures */ +}; + +struct hci_conn_list_req { + uint16_t dev_id; + uint16_t conn_num; + struct hci_conn_info conn_info[0]; +}; + +struct hci_conn_info_req { + bdaddr_t bdaddr; + uint8_t type; + struct hci_conn_info conn_info[0]; +}; + +struct hci_auth_info_req { + bdaddr_t bdaddr; + uint8_t type; +}; + +struct hci_inquiry_req { + uint16_t dev_id; + uint16_t flags; + uint8_t lap[3]; + uint8_t length; + uint8_t num_rsp; +}; +#define IREQ_CACHE_FLUSH 0x0001 + +#ifdef __cplusplus +} +#endif + +#endif /* __HCI_H */ diff --git a/lib/hci_lib.h b/lib/hci_lib.h new file mode 100644 index 0000000..55aeb17 --- /dev/null +++ b/lib/hci_lib.h @@ -0,0 +1,242 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __HCI_LIB_H +#define __HCI_LIB_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct hci_request { + uint16_t ogf; + uint16_t ocf; + int event; + void *cparam; + int clen; + void *rparam; + int rlen; +}; + +struct hci_version { + uint16_t manufacturer; + uint8_t hci_ver; + uint16_t hci_rev; + uint8_t lmp_ver; + uint16_t lmp_subver; +}; + +int hci_open_dev(int dev_id); +int hci_close_dev(int dd); +int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param); +int hci_send_req(int dd, struct hci_request *req, int timeout); + +int hci_create_connection(int dd, const bdaddr_t *bdaddr, uint16_t ptype, uint16_t clkoffset, uint8_t rswitch, uint16_t *handle, int to); +int hci_disconnect(int dd, uint16_t handle, uint8_t reason, int to); + +int hci_inquiry(int dev_id, int len, int num_rsp, const uint8_t *lap, inquiry_info **ii, long flags); +int hci_devinfo(int dev_id, struct hci_dev_info *di); +int hci_devba(int dev_id, bdaddr_t *bdaddr); +int hci_devid(const char *str); + +int hci_read_local_name(int dd, int len, char *name, int to); +int hci_write_local_name(int dd, const char *name, int to); +int hci_read_remote_name(int dd, const bdaddr_t *bdaddr, int len, char *name, int to); +int hci_read_remote_name_with_clock_offset(int dd, const bdaddr_t *bdaddr, uint8_t pscan_rep_mode, uint16_t clkoffset, int len, char *name, int to); +int hci_read_remote_name_cancel(int dd, const bdaddr_t *bdaddr, int to); +int hci_read_remote_version(int dd, uint16_t handle, struct hci_version *ver, int to); +int hci_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to); +int hci_read_remote_ext_features(int dd, uint16_t handle, uint8_t page, uint8_t *max_page, uint8_t *features, int to); +int hci_read_clock_offset(int dd, uint16_t handle, uint16_t *clkoffset, int to); +int hci_read_local_version(int dd, struct hci_version *ver, int to); +int hci_read_local_commands(int dd, uint8_t *commands, int to); +int hci_read_local_features(int dd, uint8_t *features, int to); +int hci_read_local_ext_features(int dd, uint8_t page, uint8_t *max_page, uint8_t *features, int to); +int hci_read_bd_addr(int dd, bdaddr_t *bdaddr, int to); +int hci_read_class_of_dev(int dd, uint8_t *cls, int to); +int hci_write_class_of_dev(int dd, uint32_t cls, int to); +int hci_read_voice_setting(int dd, uint16_t *vs, int to); +int hci_write_voice_setting(int dd, uint16_t vs, int to); +int hci_read_current_iac_lap(int dd, uint8_t *num_iac, uint8_t *lap, int to); +int hci_write_current_iac_lap(int dd, uint8_t num_iac, uint8_t *lap, int to); +int hci_read_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to); +int hci_write_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t *key, int to); +int hci_delete_stored_link_key(int dd, bdaddr_t *bdaddr, uint8_t all, int to); +int hci_authenticate_link(int dd, uint16_t handle, int to); +int hci_encrypt_link(int dd, uint16_t handle, uint8_t encrypt, int to); +int hci_change_link_key(int dd, uint16_t handle, int to); +int hci_switch_role(int dd, bdaddr_t *bdaddr, uint8_t role, int to); +int hci_park_mode(int dd, uint16_t handle, uint16_t max_interval, uint16_t min_interval, int to); +int hci_exit_park_mode(int dd, uint16_t handle, int to); +int hci_read_inquiry_scan_type(int dd, uint8_t *type, int to); +int hci_write_inquiry_scan_type(int dd, uint8_t type, int to); +int hci_read_inquiry_mode(int dd, uint8_t *mode, int to); +int hci_write_inquiry_mode(int dd, uint8_t mode, int to); +int hci_read_afh_mode(int dd, uint8_t *mode, int to); +int hci_write_afh_mode(int dd, uint8_t mode, int to); +int hci_read_ext_inquiry_response(int dd, uint8_t *fec, uint8_t *data, int to); +int hci_write_ext_inquiry_response(int dd, uint8_t fec, uint8_t *data, int to); +int hci_read_simple_pairing_mode(int dd, uint8_t *mode, int to); +int hci_write_simple_pairing_mode(int dd, uint8_t mode, int to); +int hci_read_local_oob_data(int dd, uint8_t *hash, uint8_t *randomizer, int to); +int hci_read_inq_response_tx_power_level(int dd, int8_t *level, int to); +int hci_read_inquiry_transmit_power_level(int dd, int8_t *level, int to); +int hci_write_inquiry_transmit_power_level(int dd, int8_t level, int to); +int hci_read_transmit_power_level(int dd, uint16_t handle, uint8_t type, int8_t *level, int to); +int hci_read_link_policy(int dd, uint16_t handle, uint16_t *policy, int to); +int hci_write_link_policy(int dd, uint16_t handle, uint16_t policy, int to); +int hci_read_link_supervision_timeout(int dd, uint16_t handle, uint16_t *timeout, int to); +int hci_write_link_supervision_timeout(int dd, uint16_t handle, uint16_t timeout, int to); +int hci_set_afh_classification(int dd, uint8_t *map, int to); +int hci_read_link_quality(int dd, uint16_t handle, uint8_t *link_quality, int to); +int hci_read_rssi(int dd, uint16_t handle, int8_t *rssi, int to); +int hci_read_afh_map(int dd, uint16_t handle, uint8_t *mode, uint8_t *map, int to); +int hci_read_clock(int dd, uint16_t handle, uint8_t which, uint32_t *clock, uint16_t *accuracy, int to); + +int hci_le_set_scan_enable(int dev_id, uint8_t enable, uint8_t filter_dup, int to); +int hci_le_set_scan_parameters(int dev_id, uint8_t type, uint16_t interval, + uint16_t window, uint8_t own_type, + uint8_t filter, int to); +int hci_le_set_advertise_enable(int dev_id, uint8_t enable, int to); +int hci_le_create_conn(int dd, uint16_t interval, uint16_t window, + uint8_t initiator_filter, uint8_t peer_bdaddr_type, + bdaddr_t peer_bdaddr, uint8_t own_bdaddr_type, + uint16_t min_interval, uint16_t max_interval, + uint16_t latency, uint16_t supervision_timeout, + uint16_t min_ce_length, uint16_t max_ce_length, + uint16_t *handle, int to); +int hci_le_conn_update(int dd, uint16_t handle, uint16_t min_interval, + uint16_t max_interval, uint16_t latency, + uint16_t supervision_timeout, int to); +int hci_le_add_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to); +int hci_le_rm_white_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to); +int hci_le_read_white_list_size(int dd, uint8_t *size, int to); +int hci_le_clear_white_list(int dd, int to); +int hci_le_add_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, + uint8_t *peer_irk, uint8_t *local_irk, int to); +int hci_le_rm_resolving_list(int dd, const bdaddr_t *bdaddr, uint8_t type, int to); +int hci_le_clear_resolving_list(int dd, int to); +int hci_le_read_resolving_list_size(int dd, uint8_t *size, int to); +int hci_le_set_address_resolution_enable(int dev_id, uint8_t enable, int to); +int hci_le_read_remote_features(int dd, uint16_t handle, uint8_t *features, int to); + +int hci_for_each_dev(int flag, int(*func)(int dd, int dev_id, long arg), long arg); +int hci_get_route(bdaddr_t *bdaddr); + +char *hci_bustostr(int bus); +char *hci_typetostr(int type); +char *hci_dtypetostr(int type); +char *hci_dflagstostr(uint32_t flags); +char *hci_ptypetostr(unsigned int ptype); +int hci_strtoptype(char *str, unsigned int *val); +char *hci_scoptypetostr(unsigned int ptype); +int hci_strtoscoptype(char *str, unsigned int *val); +char *hci_lptostr(unsigned int ptype); +int hci_strtolp(char *str, unsigned int *val); +char *hci_lmtostr(unsigned int ptype); +int hci_strtolm(char *str, unsigned int *val); + +char *hci_cmdtostr(unsigned int cmd); +char *hci_commandstostr(uint8_t *commands, char *pref, int width); + +char *hci_vertostr(unsigned int ver); +int hci_strtover(char *str, unsigned int *ver); +char *lmp_vertostr(unsigned int ver); +int lmp_strtover(char *str, unsigned int *ver); +char *pal_vertostr(unsigned int ver); +int pal_strtover(char *str, unsigned int *ver); + +char *lmp_featurestostr(uint8_t *features, char *pref, int width); + +static inline void hci_set_bit(int nr, void *addr) +{ + *((uint32_t *) addr + (nr >> 5)) |= (1 << (nr & 31)); +} + +static inline void hci_clear_bit(int nr, void *addr) +{ + *((uint32_t *) addr + (nr >> 5)) &= ~(1 << (nr & 31)); +} + +static inline int hci_test_bit(int nr, void *addr) +{ + return *((uint32_t *) addr + (nr >> 5)) & (1 << (nr & 31)); +} + +/* HCI filter tools */ +static inline void hci_filter_clear(struct hci_filter *f) +{ + memset(f, 0, sizeof(*f)); +} +static inline void hci_filter_set_ptype(int t, struct hci_filter *f) +{ + hci_set_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask); +} +static inline void hci_filter_clear_ptype(int t, struct hci_filter *f) +{ + hci_clear_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask); +} +static inline int hci_filter_test_ptype(int t, struct hci_filter *f) +{ + return hci_test_bit((t == HCI_VENDOR_PKT) ? 0 : (t & HCI_FLT_TYPE_BITS), &f->type_mask); +} +static inline void hci_filter_all_ptypes(struct hci_filter *f) +{ + memset((void *) &f->type_mask, 0xff, sizeof(f->type_mask)); +} +static inline void hci_filter_set_event(int e, struct hci_filter *f) +{ + hci_set_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask); +} +static inline void hci_filter_clear_event(int e, struct hci_filter *f) +{ + hci_clear_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask); +} +static inline int hci_filter_test_event(int e, struct hci_filter *f) +{ + return hci_test_bit((e & HCI_FLT_EVENT_BITS), &f->event_mask); +} +static inline void hci_filter_all_events(struct hci_filter *f) +{ + memset((void *) f->event_mask, 0xff, sizeof(f->event_mask)); +} +static inline void hci_filter_set_opcode(int opcode, struct hci_filter *f) +{ + f->opcode = opcode; +} +static inline void hci_filter_clear_opcode(struct hci_filter *f) +{ + f->opcode = 0; +} +static inline int hci_filter_test_opcode(int opcode, struct hci_filter *f) +{ + return (f->opcode == opcode); +} + +#ifdef __cplusplus +} +#endif + +#endif /* __HCI_LIB_H */ diff --git a/lib/hidp.h b/lib/hidp.h new file mode 100644 index 0000000..c5e6a78 --- /dev/null +++ b/lib/hidp.h @@ -0,0 +1,85 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __HIDP_H +#define __HIDP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* HIDP defaults */ +#define HIDP_MINIMUM_MTU 48 +#define HIDP_DEFAULT_MTU 48 + +/* HIDP ioctl defines */ +#define HIDPCONNADD _IOW('H', 200, int) +#define HIDPCONNDEL _IOW('H', 201, int) +#define HIDPGETCONNLIST _IOR('H', 210, int) +#define HIDPGETCONNINFO _IOR('H', 211, int) + +#define HIDP_VIRTUAL_CABLE_UNPLUG 0 +#define HIDP_BOOT_PROTOCOL_MODE 1 +#define HIDP_BLUETOOTH_VENDOR_ID 9 + +struct hidp_connadd_req { + int ctrl_sock; /* Connected control socket */ + int intr_sock; /* Connected interrupt socket */ + uint16_t parser; /* Parser version */ + uint16_t rd_size; /* Report descriptor size */ + uint8_t *rd_data; /* Report descriptor data */ + uint8_t country; + uint8_t subclass; + uint16_t vendor; + uint16_t product; + uint16_t version; + uint32_t flags; + uint32_t idle_to; + char name[128]; /* Device name */ +}; + +struct hidp_conndel_req { + bdaddr_t bdaddr; + uint32_t flags; +}; + +struct hidp_conninfo { + bdaddr_t bdaddr; + uint32_t flags; + uint16_t state; + uint16_t vendor; + uint16_t product; + uint16_t version; + char name[128]; +}; + +struct hidp_connlist_req { + uint32_t cnum; + struct hidp_conninfo *ci; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __HIDP_H */ diff --git a/lib/l2cap.h b/lib/l2cap.h new file mode 100644 index 0000000..5ce94c4 --- /dev/null +++ b/lib/l2cap.h @@ -0,0 +1,279 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (c) 2012 Code Aurora Forum. All rights reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __L2CAP_H +#define __L2CAP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* L2CAP defaults */ +#define L2CAP_DEFAULT_MTU 672 +#define L2CAP_DEFAULT_FLUSH_TO 0xFFFF + +/* L2CAP socket address */ +struct sockaddr_l2 { + sa_family_t l2_family; + unsigned short l2_psm; + bdaddr_t l2_bdaddr; + unsigned short l2_cid; + uint8_t l2_bdaddr_type; +}; + +/* L2CAP socket options */ +#define L2CAP_OPTIONS 0x01 +struct l2cap_options { + uint16_t omtu; + uint16_t imtu; + uint16_t flush_to; + uint8_t mode; + uint8_t fcs; + uint8_t max_tx; + uint16_t txwin_size; +}; + +#define L2CAP_CONNINFO 0x02 +struct l2cap_conninfo { + uint16_t hci_handle; + uint8_t dev_class[3]; +}; + +#define L2CAP_LM 0x03 +#define L2CAP_LM_MASTER 0x0001 +#define L2CAP_LM_AUTH 0x0002 +#define L2CAP_LM_ENCRYPT 0x0004 +#define L2CAP_LM_TRUSTED 0x0008 +#define L2CAP_LM_RELIABLE 0x0010 +#define L2CAP_LM_SECURE 0x0020 + +/* L2CAP command codes */ +#define L2CAP_COMMAND_REJ 0x01 +#define L2CAP_CONN_REQ 0x02 +#define L2CAP_CONN_RSP 0x03 +#define L2CAP_CONF_REQ 0x04 +#define L2CAP_CONF_RSP 0x05 +#define L2CAP_DISCONN_REQ 0x06 +#define L2CAP_DISCONN_RSP 0x07 +#define L2CAP_ECHO_REQ 0x08 +#define L2CAP_ECHO_RSP 0x09 +#define L2CAP_INFO_REQ 0x0a +#define L2CAP_INFO_RSP 0x0b +#define L2CAP_CREATE_REQ 0x0c +#define L2CAP_CREATE_RSP 0x0d +#define L2CAP_MOVE_REQ 0x0e +#define L2CAP_MOVE_RSP 0x0f +#define L2CAP_MOVE_CFM 0x10 +#define L2CAP_MOVE_CFM_RSP 0x11 + +/* L2CAP extended feature mask */ +#define L2CAP_FEAT_FLOWCTL 0x00000001 +#define L2CAP_FEAT_RETRANS 0x00000002 +#define L2CAP_FEAT_BIDIR_QOS 0x00000004 +#define L2CAP_FEAT_ERTM 0x00000008 +#define L2CAP_FEAT_STREAMING 0x00000010 +#define L2CAP_FEAT_FCS 0x00000020 +#define L2CAP_FEAT_EXT_FLOW 0x00000040 +#define L2CAP_FEAT_FIXED_CHAN 0x00000080 +#define L2CAP_FEAT_EXT_WINDOW 0x00000100 +#define L2CAP_FEAT_UCD 0x00000200 + +/* L2CAP fixed channels */ +#define L2CAP_FC_L2CAP 0x02 +#define L2CAP_FC_CONNLESS 0x04 +#define L2CAP_FC_A2MP 0x08 + +/* L2CAP structures */ +typedef struct { + uint16_t len; + uint16_t cid; +} __attribute__ ((packed)) l2cap_hdr; +#define L2CAP_HDR_SIZE 4 + +typedef struct { + uint8_t code; + uint8_t ident; + uint16_t len; +} __attribute__ ((packed)) l2cap_cmd_hdr; +#define L2CAP_CMD_HDR_SIZE 4 + +typedef struct { + uint16_t reason; +} __attribute__ ((packed)) l2cap_cmd_rej; +#define L2CAP_CMD_REJ_SIZE 2 + +typedef struct { + uint16_t psm; + uint16_t scid; +} __attribute__ ((packed)) l2cap_conn_req; +#define L2CAP_CONN_REQ_SIZE 4 + +typedef struct { + uint16_t dcid; + uint16_t scid; + uint16_t result; + uint16_t status; +} __attribute__ ((packed)) l2cap_conn_rsp; +#define L2CAP_CONN_RSP_SIZE 8 + +/* connect result */ +#define L2CAP_CR_SUCCESS 0x0000 +#define L2CAP_CR_PEND 0x0001 +#define L2CAP_CR_BAD_PSM 0x0002 +#define L2CAP_CR_SEC_BLOCK 0x0003 +#define L2CAP_CR_NO_MEM 0x0004 + +/* connect status */ +#define L2CAP_CS_NO_INFO 0x0000 +#define L2CAP_CS_AUTHEN_PEND 0x0001 +#define L2CAP_CS_AUTHOR_PEND 0x0002 + +typedef struct { + uint16_t dcid; + uint16_t flags; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_conf_req; +#define L2CAP_CONF_REQ_SIZE 4 + +typedef struct { + uint16_t scid; + uint16_t flags; + uint16_t result; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_conf_rsp; +#define L2CAP_CONF_RSP_SIZE 6 + +#define L2CAP_CONF_SUCCESS 0x0000 +#define L2CAP_CONF_UNACCEPT 0x0001 +#define L2CAP_CONF_REJECT 0x0002 +#define L2CAP_CONF_UNKNOWN 0x0003 +#define L2CAP_CONF_PENDING 0x0004 +#define L2CAP_CONF_EFS_REJECT 0x0005 + +typedef struct { + uint8_t type; + uint8_t len; + uint8_t val[0]; +} __attribute__ ((packed)) l2cap_conf_opt; +#define L2CAP_CONF_OPT_SIZE 2 + +#define L2CAP_CONF_MTU 0x01 +#define L2CAP_CONF_FLUSH_TO 0x02 +#define L2CAP_CONF_QOS 0x03 +#define L2CAP_CONF_RFC 0x04 +#define L2CAP_CONF_FCS 0x05 +#define L2CAP_CONF_EFS 0x06 +#define L2CAP_CONF_EWS 0x07 + +#define L2CAP_CONF_MAX_SIZE 22 + +#define L2CAP_MODE_BASIC 0x00 +#define L2CAP_MODE_RETRANS 0x01 +#define L2CAP_MODE_FLOWCTL 0x02 +#define L2CAP_MODE_ERTM 0x03 +#define L2CAP_MODE_STREAMING 0x04 + +#define L2CAP_SERVTYPE_NOTRAFFIC 0x00 +#define L2CAP_SERVTYPE_BESTEFFORT 0x01 +#define L2CAP_SERVTYPE_GUARANTEED 0x02 + +typedef struct { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)) l2cap_disconn_req; +#define L2CAP_DISCONN_REQ_SIZE 4 + +typedef struct { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)) l2cap_disconn_rsp; +#define L2CAP_DISCONN_RSP_SIZE 4 + +typedef struct { + uint16_t type; +} __attribute__ ((packed)) l2cap_info_req; +#define L2CAP_INFO_REQ_SIZE 2 + +typedef struct { + uint16_t type; + uint16_t result; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_info_rsp; +#define L2CAP_INFO_RSP_SIZE 4 + +/* info type */ +#define L2CAP_IT_CL_MTU 0x0001 +#define L2CAP_IT_FEAT_MASK 0x0002 + +/* info result */ +#define L2CAP_IR_SUCCESS 0x0000 +#define L2CAP_IR_NOTSUPP 0x0001 + +typedef struct { + uint16_t psm; + uint16_t scid; + uint8_t id; +} __attribute__ ((packed)) l2cap_create_req; +#define L2CAP_CREATE_REQ_SIZE 5 + +typedef struct { + uint16_t dcid; + uint16_t scid; + uint16_t result; + uint16_t status; +} __attribute__ ((packed)) l2cap_create_rsp; +#define L2CAP_CREATE_RSP_SIZE 8 + +typedef struct { + uint16_t icid; + uint8_t id; +} __attribute__ ((packed)) l2cap_move_req; +#define L2CAP_MOVE_REQ_SIZE 3 + +typedef struct { + uint16_t icid; + uint16_t result; +} __attribute__ ((packed)) l2cap_move_rsp; +#define L2CAP_MOVE_RSP_SIZE 4 + +typedef struct { + uint16_t icid; + uint16_t result; +} __attribute__ ((packed)) l2cap_move_cfm; +#define L2CAP_MOVE_CFM_SIZE 4 + +typedef struct { + uint16_t icid; +} __attribute__ ((packed)) l2cap_move_cfm_rsp; +#define L2CAP_MOVE_CFM_RSP_SIZE 2 + +#ifdef __cplusplus +} +#endif + +#endif /* __L2CAP_H */ diff --git a/lib/mgmt.h b/lib/mgmt.h new file mode 100644 index 0000000..570dec9 --- /dev/null +++ b/lib/mgmt.h @@ -0,0 +1,972 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#define MGMT_INDEX_NONE 0xFFFF + +#define MGMT_STATUS_SUCCESS 0x00 +#define MGMT_STATUS_UNKNOWN_COMMAND 0x01 +#define MGMT_STATUS_NOT_CONNECTED 0x02 +#define MGMT_STATUS_FAILED 0x03 +#define MGMT_STATUS_CONNECT_FAILED 0x04 +#define MGMT_STATUS_AUTH_FAILED 0x05 +#define MGMT_STATUS_NOT_PAIRED 0x06 +#define MGMT_STATUS_NO_RESOURCES 0x07 +#define MGMT_STATUS_TIMEOUT 0x08 +#define MGMT_STATUS_ALREADY_CONNECTED 0x09 +#define MGMT_STATUS_BUSY 0x0a +#define MGMT_STATUS_REJECTED 0x0b +#define MGMT_STATUS_NOT_SUPPORTED 0x0c +#define MGMT_STATUS_INVALID_PARAMS 0x0d +#define MGMT_STATUS_DISCONNECTED 0x0e +#define MGMT_STATUS_NOT_POWERED 0x0f +#define MGMT_STATUS_CANCELLED 0x10 +#define MGMT_STATUS_INVALID_INDEX 0x11 +#define MGMT_STATUS_RFKILLED 0x12 +#define MGMT_STATUS_ALREADY_PAIRED 0x13 +#define MGMT_STATUS_PERMISSION_DENIED 0x14 + +struct mgmt_hdr { + uint16_t opcode; + uint16_t index; + uint16_t len; +} __packed; +#define MGMT_HDR_SIZE 6 + +struct mgmt_addr_info { + bdaddr_t bdaddr; + uint8_t type; +} __packed; + +#define MGMT_OP_READ_VERSION 0x0001 +struct mgmt_rp_read_version { + uint8_t version; + uint16_t revision; +} __packed; + +#define MGMT_OP_READ_COMMANDS 0x0002 +struct mgmt_rp_read_commands { + uint16_t num_commands; + uint16_t num_events; + uint16_t opcodes[0]; +} __packed; + +#define MGMT_OP_READ_INDEX_LIST 0x0003 +struct mgmt_rp_read_index_list { + uint16_t num_controllers; + uint16_t index[0]; +} __packed; + +/* Reserve one extra byte for names in management messages so that they + * are always guaranteed to be nul-terminated */ +#define MGMT_MAX_NAME_LENGTH (248 + 1) +#define MGMT_MAX_SHORT_NAME_LENGTH (10 + 1) + +#define MGMT_SETTING_POWERED 0x00000001 +#define MGMT_SETTING_CONNECTABLE 0x00000002 +#define MGMT_SETTING_FAST_CONNECTABLE 0x00000004 +#define MGMT_SETTING_DISCOVERABLE 0x00000008 +#define MGMT_SETTING_BONDABLE 0x00000010 +#define MGMT_SETTING_LINK_SECURITY 0x00000020 +#define MGMT_SETTING_SSP 0x00000040 +#define MGMT_SETTING_BREDR 0x00000080 +#define MGMT_SETTING_HS 0x00000100 +#define MGMT_SETTING_LE 0x00000200 +#define MGMT_SETTING_ADVERTISING 0x00000400 +#define MGMT_SETTING_SECURE_CONN 0x00000800 +#define MGMT_SETTING_DEBUG_KEYS 0x00001000 +#define MGMT_SETTING_PRIVACY 0x00002000 +#define MGMT_SETTING_CONFIGURATION 0x00004000 +#define MGMT_SETTING_STATIC_ADDRESS 0x00008000 +#define MGMT_SETTING_PHY_CONFIGURATION 0x00010000 + +#define MGMT_OP_READ_INFO 0x0004 +struct mgmt_rp_read_info { + bdaddr_t bdaddr; + uint8_t version; + uint16_t manufacturer; + uint32_t supported_settings; + uint32_t current_settings; + uint8_t dev_class[3]; + uint8_t name[MGMT_MAX_NAME_LENGTH]; + uint8_t short_name[MGMT_MAX_SHORT_NAME_LENGTH]; +} __packed; + +struct mgmt_mode { + uint8_t val; +} __packed; + +struct mgmt_cod { + uint8_t val[3]; +} __packed; + +#define MGMT_OP_SET_POWERED 0x0005 + +#define MGMT_OP_SET_DISCOVERABLE 0x0006 +struct mgmt_cp_set_discoverable { + uint8_t val; + uint16_t timeout; +} __packed; + +#define MGMT_OP_SET_CONNECTABLE 0x0007 + +#define MGMT_OP_SET_FAST_CONNECTABLE 0x0008 + +#define MGMT_OP_SET_BONDABLE 0x0009 + +#define MGMT_OP_SET_LINK_SECURITY 0x000A + +#define MGMT_OP_SET_SSP 0x000B + +#define MGMT_OP_SET_HS 0x000C + +#define MGMT_OP_SET_LE 0x000D + +#define MGMT_OP_SET_DEV_CLASS 0x000E +struct mgmt_cp_set_dev_class { + uint8_t major; + uint8_t minor; +} __packed; + +#define MGMT_OP_SET_LOCAL_NAME 0x000F +struct mgmt_cp_set_local_name { + uint8_t name[MGMT_MAX_NAME_LENGTH]; + uint8_t short_name[MGMT_MAX_SHORT_NAME_LENGTH]; +} __packed; + +#define MGMT_OP_ADD_UUID 0x0010 +struct mgmt_cp_add_uuid { + uint8_t uuid[16]; + uint8_t svc_hint; +} __packed; + +#define MGMT_OP_REMOVE_UUID 0x0011 +struct mgmt_cp_remove_uuid { + uint8_t uuid[16]; +} __packed; + +struct mgmt_link_key_info { + struct mgmt_addr_info addr; + uint8_t type; + uint8_t val[16]; + uint8_t pin_len; +} __packed; + +#define MGMT_OP_LOAD_LINK_KEYS 0x0012 +struct mgmt_cp_load_link_keys { + uint8_t debug_keys; + uint16_t key_count; + struct mgmt_link_key_info keys[0]; +} __packed; + +struct mgmt_ltk_info { + struct mgmt_addr_info addr; + uint8_t type; + uint8_t master; + uint8_t enc_size; + uint16_t ediv; + uint64_t rand; + uint8_t val[16]; +} __packed; + +#define MGMT_OP_LOAD_LONG_TERM_KEYS 0x0013 +struct mgmt_cp_load_long_term_keys { + uint16_t key_count; + struct mgmt_ltk_info keys[0]; +} __packed; + +#define MGMT_OP_DISCONNECT 0x0014 +struct mgmt_cp_disconnect { + struct mgmt_addr_info addr; +} __packed; +struct mgmt_rp_disconnect { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_GET_CONNECTIONS 0x0015 +struct mgmt_rp_get_connections { + uint16_t conn_count; + struct mgmt_addr_info addr[0]; +} __packed; + +#define MGMT_OP_PIN_CODE_REPLY 0x0016 +struct mgmt_cp_pin_code_reply { + struct mgmt_addr_info addr; + uint8_t pin_len; + uint8_t pin_code[16]; +} __packed; + +#define MGMT_OP_PIN_CODE_NEG_REPLY 0x0017 +struct mgmt_cp_pin_code_neg_reply { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_SET_IO_CAPABILITY 0x0018 +struct mgmt_cp_set_io_capability { + uint8_t io_capability; +} __packed; + +#define MGMT_OP_PAIR_DEVICE 0x0019 +struct mgmt_cp_pair_device { + struct mgmt_addr_info addr; + uint8_t io_cap; +} __packed; +struct mgmt_rp_pair_device { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_CANCEL_PAIR_DEVICE 0x001A + +#define MGMT_OP_UNPAIR_DEVICE 0x001B +struct mgmt_cp_unpair_device { + struct mgmt_addr_info addr; + uint8_t disconnect; +} __packed; +struct mgmt_rp_unpair_device { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_USER_CONFIRM_REPLY 0x001C +struct mgmt_cp_user_confirm_reply { + struct mgmt_addr_info addr; +} __packed; +struct mgmt_rp_user_confirm_reply { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_USER_CONFIRM_NEG_REPLY 0x001D + +#define MGMT_OP_USER_PASSKEY_REPLY 0x001E +struct mgmt_cp_user_passkey_reply { + struct mgmt_addr_info addr; + uint32_t passkey; +} __packed; +struct mgmt_rp_user_passkey_reply { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_USER_PASSKEY_NEG_REPLY 0x001F +struct mgmt_cp_user_passkey_neg_reply { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_READ_LOCAL_OOB_DATA 0x0020 +struct mgmt_rp_read_local_oob_data { + uint8_t hash192[16]; + uint8_t rand192[16]; + uint8_t hash256[16]; + uint8_t rand256[16]; +} __packed; + +#define MGMT_OP_ADD_REMOTE_OOB_DATA 0x0021 +struct mgmt_cp_add_remote_oob_data { + struct mgmt_addr_info addr; + uint8_t hash192[16]; + uint8_t rand192[16]; + uint8_t hash256[16]; + uint8_t rand256[16]; +} __packed; + +#define MGMT_OP_REMOVE_REMOTE_OOB_DATA 0x0022 +struct mgmt_cp_remove_remote_oob_data { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_START_DISCOVERY 0x0023 +struct mgmt_cp_start_discovery { + uint8_t type; +} __packed; + +#define MGMT_OP_STOP_DISCOVERY 0x0024 +struct mgmt_cp_stop_discovery { + uint8_t type; +} __packed; + +#define MGMT_OP_CONFIRM_NAME 0x0025 +struct mgmt_cp_confirm_name { + struct mgmt_addr_info addr; + uint8_t name_known; +} __packed; +struct mgmt_rp_confirm_name { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_BLOCK_DEVICE 0x0026 +struct mgmt_cp_block_device { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_UNBLOCK_DEVICE 0x0027 +struct mgmt_cp_unblock_device { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_SET_DEVICE_ID 0x0028 +struct mgmt_cp_set_device_id { + uint16_t source; + uint16_t vendor; + uint16_t product; + uint16_t version; +} __packed; + +#define MGMT_OP_SET_ADVERTISING 0x0029 + +#define MGMT_OP_SET_BREDR 0x002A + +#define MGMT_OP_SET_STATIC_ADDRESS 0x002B +struct mgmt_cp_set_static_address { + bdaddr_t bdaddr; +} __packed; + +#define MGMT_OP_SET_SCAN_PARAMS 0x002C +struct mgmt_cp_set_scan_params { + uint16_t interval; + uint16_t window; +} __packed; + +#define MGMT_OP_SET_SECURE_CONN 0x002D + +#define MGMT_OP_SET_DEBUG_KEYS 0x002E + +struct mgmt_irk_info { + struct mgmt_addr_info addr; + uint8_t val[16]; +} __packed; + +#define MGMT_OP_SET_PRIVACY 0x002F +struct mgmt_cp_set_privacy { + uint8_t privacy; + uint8_t irk[16]; +} __packed; + +#define MGMT_OP_LOAD_IRKS 0x0030 +struct mgmt_cp_load_irks { + uint16_t irk_count; + struct mgmt_irk_info irks[0]; +} __packed; + +#define MGMT_OP_GET_CONN_INFO 0x0031 +struct mgmt_cp_get_conn_info { + struct mgmt_addr_info addr; +} __packed; +struct mgmt_rp_get_conn_info { + struct mgmt_addr_info addr; + int8_t rssi; + int8_t tx_power; + int8_t max_tx_power; +} __packed; + +#define MGMT_OP_GET_CLOCK_INFO 0x0032 +struct mgmt_cp_get_clock_info { + struct mgmt_addr_info addr; +} __packed; +struct mgmt_rp_get_clock_info { + struct mgmt_addr_info addr; + uint32_t local_clock; + uint32_t piconet_clock; + uint16_t accuracy; +} __packed; + +#define MGMT_OP_ADD_DEVICE 0x0033 +struct mgmt_cp_add_device { + struct mgmt_addr_info addr; + uint8_t action; +} __packed; +struct mgmt_rp_add_device { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_OP_REMOVE_DEVICE 0x0034 +struct mgmt_cp_remove_device { + struct mgmt_addr_info addr; +} __packed; +struct mgmt_rp_remove_device { + struct mgmt_addr_info addr; +} __packed; + +struct mgmt_conn_param { + struct mgmt_addr_info addr; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t timeout; +} __packed; + +#define MGMT_OP_LOAD_CONN_PARAM 0x0035 +struct mgmt_cp_load_conn_param { + uint16_t param_count; + struct mgmt_conn_param params[0]; +} __packed; + +#define MGMT_OP_READ_UNCONF_INDEX_LIST 0x0036 +struct mgmt_rp_read_unconf_index_list { + uint16_t num_controllers; + uint16_t index[0]; +} __packed; + +#define MGMT_OPTION_EXTERNAL_CONFIG 0x00000001 +#define MGMT_OPTION_PUBLIC_ADDRESS 0x00000002 + +#define MGMT_OP_READ_CONFIG_INFO 0x0037 +struct mgmt_rp_read_config_info { + uint16_t manufacturer; + uint32_t supported_options; + uint32_t missing_options; +} __packed; + +#define MGMT_OP_SET_EXTERNAL_CONFIG 0x0038 +struct mgmt_cp_set_external_config { + uint8_t config; +} __packed; + +#define MGMT_OP_SET_PUBLIC_ADDRESS 0x0039 +struct mgmt_cp_set_public_address { + bdaddr_t bdaddr; +} __packed; + +#define MGMT_OP_START_SERVICE_DISCOVERY 0x003A +struct mgmt_cp_start_service_discovery { + uint8_t type; + int8_t rssi; + uint16_t uuid_count; + uint8_t uuids[0][16]; +} __packed; + +#define MGMT_OP_READ_LOCAL_OOB_EXT_DATA 0x003B +struct mgmt_cp_read_local_oob_ext_data { + uint8_t type; +} __packed; +struct mgmt_rp_read_local_oob_ext_data { + uint8_t type; + uint16_t eir_len; + uint8_t eir[0]; +} __packed; + +#define MGMT_OP_READ_EXT_INDEX_LIST 0x003C +struct mgmt_rp_read_ext_index_list { + uint16_t num_controllers; + struct { + uint16_t index; + uint8_t type; + uint8_t bus; + } entry[0]; +} __packed; + +#define MGMT_OP_READ_ADV_FEATURES 0x003D +struct mgmt_rp_read_adv_features { + uint32_t supported_flags; + uint8_t max_adv_data_len; + uint8_t max_scan_rsp_len; + uint8_t max_instances; + uint8_t num_instances; + uint8_t instance[0]; +} __packed; + +#define MGMT_OP_ADD_ADVERTISING 0x003E +struct mgmt_cp_add_advertising { + uint8_t instance; + uint32_t flags; + uint16_t duration; + uint16_t timeout; + uint8_t adv_data_len; + uint8_t scan_rsp_len; + uint8_t data[0]; +} __packed; +struct mgmt_rp_add_advertising { + uint8_t instance; +} __packed; + +#define MGMT_ADV_FLAG_CONNECTABLE (1 << 0) +#define MGMT_ADV_FLAG_DISCOV (1 << 1) +#define MGMT_ADV_FLAG_LIMITED_DISCOV (1 << 2) +#define MGMT_ADV_FLAG_MANAGED_FLAGS (1 << 3) +#define MGMT_ADV_FLAG_TX_POWER (1 << 4) +#define MGMT_ADV_FLAG_APPEARANCE (1 << 5) +#define MGMT_ADV_FLAG_LOCAL_NAME (1 << 6) +#define MGMT_ADV_FLAG_SEC_1M (1 << 7) +#define MGMT_ADV_FLAG_SEC_2M (1 << 8) +#define MGMT_ADV_FLAG_SEC_CODED (1 << 9) + +#define MGMT_OP_REMOVE_ADVERTISING 0x003F +struct mgmt_cp_remove_advertising { + uint8_t instance; +} __packed; +#define MGMT_REMOVE_ADVERTISING_SIZE 1 +struct mgmt_rp_remove_advertising { + uint8_t instance; +} __packed; + +#define MGMT_OP_GET_ADV_SIZE_INFO 0x0040 +struct mgmt_cp_get_adv_size_info { + uint8_t instance; + uint32_t flags; +} __packed; +#define MGMT_GET_ADV_SIZE_INFO_SIZE 5 +struct mgmt_rp_get_adv_size_info { + uint8_t instance; + uint32_t flags; + uint8_t max_adv_data_len; + uint8_t max_scan_rsp_len; +} __packed; + +#define MGMT_OP_START_LIMITED_DISCOVERY 0x0041 + +#define MGMT_OP_READ_EXT_INFO 0x0042 +struct mgmt_rp_read_ext_info { + bdaddr_t bdaddr; + uint8_t version; + uint16_t manufacturer; + uint32_t supported_settings; + uint32_t current_settings; + uint16_t eir_len; + uint8_t eir[0]; +} __packed; + +#define MGMT_OP_SET_APPEARANCE 0x0043 +struct mgmt_cp_set_appearance { + uint16_t appearance; +} __packed; + +#define MGMT_OP_GET_PHY_CONFIGURATION 0x0044 +struct mgmt_rp_get_phy_confguration { + uint32_t supported_phys; + uint32_t configurable_phys; + uint32_t selected_phys; +} __packed; + +#define MGMT_PHY_BR_1M_1SLOT 0x00000001 +#define MGMT_PHY_BR_1M_3SLOT 0x00000002 +#define MGMT_PHY_BR_1M_5SLOT 0x00000004 +#define MGMT_PHY_EDR_2M_1SLOT 0x00000008 +#define MGMT_PHY_EDR_2M_3SLOT 0x00000010 +#define MGMT_PHY_EDR_2M_5SLOT 0x00000020 +#define MGMT_PHY_EDR_3M_1SLOT 0x00000040 +#define MGMT_PHY_EDR_3M_3SLOT 0x00000080 +#define MGMT_PHY_EDR_3M_5SLOT 0x00000100 +#define MGMT_PHY_LE_1M_TX 0x00000200 +#define MGMT_PHY_LE_1M_RX 0x00000400 +#define MGMT_PHY_LE_2M_TX 0x00000800 +#define MGMT_PHY_LE_2M_RX 0x00001000 +#define MGMT_PHY_LE_CODED_TX 0x00002000 +#define MGMT_PHY_LE_CODED_RX 0x00004000 + +#define MGMT_PHY_LE_TX_MASK (MGMT_PHY_LE_1M_TX | MGMT_PHY_LE_2M_TX | \ + MGMT_PHY_LE_CODED_TX) +#define MGMT_PHY_LE_RX_MASK (MGMT_PHY_LE_1M_RX | MGMT_PHY_LE_2M_RX | \ + MGMT_PHY_LE_CODED_RX) + +#define MGMT_OP_SET_PHY_CONFIGURATION 0x0045 +struct mgmt_cp_set_phy_confguration { + uint32_t selected_phys; +} __packed; + + +#define MGMT_EV_CMD_COMPLETE 0x0001 +struct mgmt_ev_cmd_complete { + uint16_t opcode; + uint8_t status; + uint8_t data[0]; +} __packed; + +#define MGMT_EV_CMD_STATUS 0x0002 +struct mgmt_ev_cmd_status { + uint16_t opcode; + uint8_t status; +} __packed; + +#define MGMT_EV_CONTROLLER_ERROR 0x0003 +struct mgmt_ev_controller_error { + uint8_t error_code; +} __packed; + +#define MGMT_EV_INDEX_ADDED 0x0004 + +#define MGMT_EV_INDEX_REMOVED 0x0005 + +#define MGMT_EV_NEW_SETTINGS 0x0006 + +#define MGMT_EV_CLASS_OF_DEV_CHANGED 0x0007 +struct mgmt_ev_class_of_dev_changed { + uint8_t dev_class[3]; +} __packed; + +#define MGMT_EV_LOCAL_NAME_CHANGED 0x0008 +struct mgmt_ev_local_name_changed { + uint8_t name[MGMT_MAX_NAME_LENGTH]; + uint8_t short_name[MGMT_MAX_SHORT_NAME_LENGTH]; +} __packed; + +#define MGMT_EV_NEW_LINK_KEY 0x0009 +struct mgmt_ev_new_link_key { + uint8_t store_hint; + struct mgmt_link_key_info key; +} __packed; + +#define MGMT_EV_NEW_LONG_TERM_KEY 0x000A +struct mgmt_ev_new_long_term_key { + uint8_t store_hint; + struct mgmt_ltk_info key; +} __packed; + +#define MGMT_EV_DEVICE_CONNECTED 0x000B +struct mgmt_ev_device_connected { + struct mgmt_addr_info addr; + uint32_t flags; + uint16_t eir_len; + uint8_t eir[0]; +} __packed; + +#define MGMT_DEV_DISCONN_UNKNOWN 0x00 +#define MGMT_DEV_DISCONN_TIMEOUT 0x01 +#define MGMT_DEV_DISCONN_LOCAL_HOST 0x02 +#define MGMT_DEV_DISCONN_REMOTE 0x03 + +#define MGMT_EV_DEVICE_DISCONNECTED 0x000C +struct mgmt_ev_device_disconnected { + struct mgmt_addr_info addr; + uint8_t reason; +} __packed; + +#define MGMT_EV_CONNECT_FAILED 0x000D +struct mgmt_ev_connect_failed { + struct mgmt_addr_info addr; + uint8_t status; +} __packed; + +#define MGMT_EV_PIN_CODE_REQUEST 0x000E +struct mgmt_ev_pin_code_request { + struct mgmt_addr_info addr; + uint8_t secure; +} __packed; + +#define MGMT_EV_USER_CONFIRM_REQUEST 0x000F +struct mgmt_ev_user_confirm_request { + struct mgmt_addr_info addr; + uint8_t confirm_hint; + uint32_t value; +} __packed; + +#define MGMT_EV_USER_PASSKEY_REQUEST 0x0010 +struct mgmt_ev_user_passkey_request { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_EV_AUTH_FAILED 0x0011 +struct mgmt_ev_auth_failed { + struct mgmt_addr_info addr; + uint8_t status; +} __packed; + +#define MGMT_DEV_FOUND_CONFIRM_NAME 0x01 +#define MGMT_DEV_FOUND_LEGACY_PAIRING 0x02 +#define MGMT_DEV_FOUND_NOT_CONNECTABLE 0x04 + +#define MGMT_EV_DEVICE_FOUND 0x0012 +struct mgmt_ev_device_found { + struct mgmt_addr_info addr; + int8_t rssi; + uint32_t flags; + uint16_t eir_len; + uint8_t eir[0]; +} __packed; + +#define MGMT_EV_DISCOVERING 0x0013 +struct mgmt_ev_discovering { + uint8_t type; + uint8_t discovering; +} __packed; + +#define MGMT_EV_DEVICE_BLOCKED 0x0014 +struct mgmt_ev_device_blocked { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_EV_DEVICE_UNBLOCKED 0x0015 +struct mgmt_ev_device_unblocked { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_EV_DEVICE_UNPAIRED 0x0016 +struct mgmt_ev_device_unpaired { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_EV_PASSKEY_NOTIFY 0x0017 +struct mgmt_ev_passkey_notify { + struct mgmt_addr_info addr; + uint32_t passkey; + uint8_t entered; +} __packed; + +#define MGMT_EV_NEW_IRK 0x0018 +struct mgmt_ev_new_irk { + uint8_t store_hint; + bdaddr_t rpa; + struct mgmt_irk_info key; +} __packed; + +struct mgmt_csrk_info { + struct mgmt_addr_info addr; + uint8_t type; + uint8_t val[16]; +} __packed; + +#define MGMT_EV_NEW_CSRK 0x0019 +struct mgmt_ev_new_csrk { + uint8_t store_hint; + struct mgmt_csrk_info key; +} __packed; + +#define MGMT_EV_DEVICE_ADDED 0x001a +struct mgmt_ev_device_added { + struct mgmt_addr_info addr; + uint8_t action; +} __packed; + +#define MGMT_EV_DEVICE_REMOVED 0x001b +struct mgmt_ev_device_removed { + struct mgmt_addr_info addr; +} __packed; + +#define MGMT_EV_NEW_CONN_PARAM 0x001c +struct mgmt_ev_new_conn_param { + struct mgmt_addr_info addr; + uint8_t store_hint; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t timeout; +} __packed; + +#define MGMT_EV_UNCONF_INDEX_ADDED 0x001d + +#define MGMT_EV_UNCONF_INDEX_REMOVED 0x001e + +#define MGMT_EV_NEW_CONFIG_OPTIONS 0x001f + +#define MGMT_EV_EXT_INDEX_ADDED 0x0020 +struct mgmt_ev_ext_index_added { + uint8_t type; + uint8_t bus; +} __packed; + +#define MGMT_EV_EXT_INDEX_REMOVED 0x0021 +struct mgmt_ev_ext_index_removed { + uint8_t type; + uint8_t bus; +} __packed; + +#define MGMT_EV_LOCAL_OOB_DATA_UPDATED 0x0022 +struct mgmt_ev_local_oob_data_updated { + uint8_t type; + uint16_t eir_len; + uint8_t eir[0]; +} __packed; + +#define MGMT_EV_ADVERTISING_ADDED 0x0023 +struct mgmt_ev_advertising_added { + uint8_t instance; +} __packed; + +#define MGMT_EV_ADVERTISING_REMOVED 0x0024 +struct mgmt_ev_advertising_removed { + uint8_t instance; +} __packed; + +#define MGMT_EV_EXT_INFO_CHANGED 0x0025 +struct mgmt_ev_ext_info_changed { + uint16_t eir_len; + uint8_t eir[0]; +} __packed; + +#define MGMT_EV_PHY_CONFIGURATION_CHANGED 0x0026 +struct mgmt_ev_phy_configuration_changed { + uint16_t selected_phys; +} __packed; + +static const char *mgmt_op[] = { + "<0x0000>", + "Read Version", + "Read Commands", + "Read Index List", + "Read Controller Info", + "Set Powered", + "Set Discoverable", + "Set Connectable", + "Set Fast Connectable", /* 0x0008 */ + "Set Bondable", + "Set Link Security", + "Set Secure Simple Pairing", + "Set High Speed", + "Set Low Energy", + "Set Dev Class", + "Set Local Name", + "Add UUID", /* 0x0010 */ + "Remove UUID", + "Load Link Keys", + "Load Long Term Keys", + "Disconnect", + "Get Connections", + "PIN Code Reply", + "PIN Code Neg Reply", + "Set IO Capability", /* 0x0018 */ + "Pair Device", + "Cancel Pair Device", + "Unpair Device", + "User Confirm Reply", + "User Confirm Neg Reply", + "User Passkey Reply", + "User Passkey Neg Reply", + "Read Local OOB Data", /* 0x0020 */ + "Add Remote OOB Data", + "Remove Remove OOB Data", + "Start Discovery", + "Stop Discovery", + "Confirm Name", + "Block Device", + "Unblock Device", + "Set Device ID", /* 0x0028 */ + "Set Advertising", + "Set BR/EDR", + "Set Static Address", + "Set Scan Parameters", + "Set Secure Connections", + "Set Debug Keys", + "Set Privacy", + "Load Identity Resolving Keys", /* 0x0030 */ + "Get Connection Information", + "Get Clock Information", + "Add Device", + "Remove Device", + "Load Connection Parameters", + "Read Unconfigured Index List", + "Read Controller Configuration Information", + "Set External Configuration", /* 0x0038 */ + "Set Public Address", + "Start Service Discovery", + "Read Local Out Of Band Extended Data", + "Read Extended Controller Index List", + "Read Advertising Features", + "Add Advertising", + "Remove Advertising", + "Get Advertising Size Information", /* 0x0040 */ + "Start Limited Discovery", + "Read Extended Controller Information", + "Set Appearance", + "Get PHY Configuration", + "Set PHY Configuration", +}; + +static const char *mgmt_ev[] = { + "<0x0000>", + "Command Complete", + "Command Status", + "Controller Error", + "Index Added", + "Index Removed", + "New Settings", + "Class of Device Changed", + "Local Name Changed", /* 0x0008 */ + "New Link Key", + "New Long Term Key", + "Device Connected", + "Device Disconnected", + "Connect Failed", + "PIN Code Request", + "User Confirm Request", + "User Passkey Request", /* 0x0010 */ + "Authentication Failed", + "Device Found", + "Discovering", + "Device Blocked", + "Device Unblocked", + "Device Unpaired", + "Passkey Notify", + "New Identity Resolving Key", /* 0x0018 */ + "New Signature Resolving Key", + "Device Added", + "Device Removed", + "New Connection Parameter", + "Unconfigured Index Added", + "Unconfigured Index Removed", + "New Configuration Options", + "Extended Index Added", /* 0x0020 */ + "Extended Index Removed", + "Local Out Of Band Extended Data Updated", + "Advertising Added", + "Advertising Removed", + "Extended Controller Information Changed", + "PHY Configuration Changed", +}; + +static const char *mgmt_status[] = { + "Success", + "Unknown Command", + "Not Connected", + "Failed", + "Connect Failed", + "Authentication Failed", + "Not Paired", + "No Resources", + "Timeout", + "Already Connected", + "Busy", + "Rejected", + "Not Supported", + "Invalid Parameters", + "Disconnected", + "Not Powered", + "Cancelled", + "Invalid Index", + "Blocked through rfkill", + "Already Paired", + "Permission Denied", +}; + +#ifndef NELEM +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) +#endif + +static inline const char *mgmt_opstr(uint16_t op) +{ + if (op >= NELEM(mgmt_op)) + return ""; + return mgmt_op[op]; +} + +static inline const char *mgmt_evstr(uint16_t ev) +{ + if (ev >= NELEM(mgmt_ev)) + return ""; + return mgmt_ev[ev]; +} + +static inline const char *mgmt_errstr(uint8_t status) +{ + if (status >= NELEM(mgmt_status)) + return ""; + return mgmt_status[status]; +} diff --git a/lib/rfcomm.h b/lib/rfcomm.h new file mode 100644 index 0000000..ad6c0e1 --- /dev/null +++ b/lib/rfcomm.h @@ -0,0 +1,99 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __RFCOMM_H +#define __RFCOMM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* RFCOMM defaults */ +#define RFCOMM_DEFAULT_MTU 127 + +#define RFCOMM_PSM 3 + +/* RFCOMM socket address */ +struct sockaddr_rc { + sa_family_t rc_family; + bdaddr_t rc_bdaddr; + uint8_t rc_channel; +}; + +/* RFCOMM socket options */ +#define RFCOMM_CONNINFO 0x02 +struct rfcomm_conninfo { + uint16_t hci_handle; + uint8_t dev_class[3]; +}; + +#define RFCOMM_LM 0x03 +#define RFCOMM_LM_MASTER 0x0001 +#define RFCOMM_LM_AUTH 0x0002 +#define RFCOMM_LM_ENCRYPT 0x0004 +#define RFCOMM_LM_TRUSTED 0x0008 +#define RFCOMM_LM_RELIABLE 0x0010 +#define RFCOMM_LM_SECURE 0x0020 + +/* RFCOMM TTY support */ +#define RFCOMM_MAX_DEV 256 + +#define RFCOMMCREATEDEV _IOW('R', 200, int) +#define RFCOMMRELEASEDEV _IOW('R', 201, int) +#define RFCOMMGETDEVLIST _IOR('R', 210, int) +#define RFCOMMGETDEVINFO _IOR('R', 211, int) + +struct rfcomm_dev_req { + int16_t dev_id; + uint32_t flags; + bdaddr_t src; + bdaddr_t dst; + uint8_t channel; +}; +#define RFCOMM_REUSE_DLC 0 +#define RFCOMM_RELEASE_ONHUP 1 +#define RFCOMM_HANGUP_NOW 2 +#define RFCOMM_TTY_ATTACHED 3 + +struct rfcomm_dev_info { + int16_t id; + uint32_t flags; + uint16_t state; + bdaddr_t src; + bdaddr_t dst; + uint8_t channel; +}; + +struct rfcomm_dev_list_req { + uint16_t dev_num; + struct rfcomm_dev_info dev_info[0]; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __RFCOMM_H */ diff --git a/lib/sco.h b/lib/sco.h new file mode 100644 index 0000000..75336a5 --- /dev/null +++ b/lib/sco.h @@ -0,0 +1,62 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __SCO_H +#define __SCO_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* SCO defaults */ +#define SCO_DEFAULT_MTU 500 +#define SCO_DEFAULT_FLUSH_TO 0xFFFF + +#define SCO_CONN_TIMEOUT (HZ * 40) +#define SCO_DISCONN_TIMEOUT (HZ * 2) +#define SCO_CONN_IDLE_TIMEOUT (HZ * 60) + +/* SCO socket address */ +struct sockaddr_sco { + sa_family_t sco_family; + bdaddr_t sco_bdaddr; +}; + +/* set/get sockopt defines */ +#define SCO_OPTIONS 0x01 +struct sco_options { + uint16_t mtu; +}; + +#define SCO_CONNINFO 0x02 +struct sco_conninfo { + uint16_t hci_handle; + uint8_t dev_class[3]; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __SCO_H */ diff --git a/lib/sdp.c b/lib/sdp.c new file mode 100644 index 0000000..84311ed --- /dev/null +++ b/lib/sdp.c @@ -0,0 +1,4956 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bluetooth.h" +#include "hci.h" +#include "hci_lib.h" +#include "l2cap.h" +#include "sdp.h" +#include "sdp_lib.h" + +#define SDPINF(fmt, arg...) syslog(LOG_INFO, fmt "\n", ## arg) +#define SDPERR(fmt, arg...) syslog(LOG_ERR, "%s: " fmt "\n", __func__ , ## arg) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#ifdef SDP_DEBUG +#define SDPDBG(fmt, arg...) syslog(LOG_DEBUG, "%s: " fmt "\n", __func__ , ## arg) +#else +#define SDPDBG(fmt...) +#endif + +static uint128_t bluetooth_base_uuid = { + .data = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB } +}; + +#define SDP_MAX_ATTR_LEN 65535 + +/* match MTU used by RFCOMM */ +#define SDP_LARGE_L2CAP_MTU 1013 + +static sdp_data_t *sdp_copy_seq(sdp_data_t *data); +static int sdp_attr_add_new_with_length(sdp_record_t *rec, + uint16_t attr, uint8_t dtd, const void *value, uint32_t len); +static int sdp_gen_buffer(sdp_buf_t *buf, sdp_data_t *d); + +/* Message structure. */ +struct tupla { + int index; + char *str; +}; + +static struct tupla Protocol[] = { + { SDP_UUID, "SDP" }, + { UDP_UUID, "UDP" }, + { RFCOMM_UUID, "RFCOMM" }, + { TCP_UUID, "TCP" }, + { TCS_BIN_UUID, "TCS-BIN" }, + { TCS_AT_UUID, "TCS-AT" }, + { OBEX_UUID, "OBEX" }, + { IP_UUID, "IP" }, + { FTP_UUID, "FTP" }, + { HTTP_UUID, "HTTP" }, + { WSP_UUID, "WSP" }, + { BNEP_UUID, "BNEP" }, + { UPNP_UUID, "UPNP" }, + { HIDP_UUID, "HIDP" }, + { HCRP_CTRL_UUID, "HCRP-Ctrl" }, + { HCRP_DATA_UUID, "HCRP-Data" }, + { HCRP_NOTE_UUID, "HCRP-Notify" }, + { AVCTP_UUID, "AVCTP" }, + { AVDTP_UUID, "AVDTP" }, + { CMTP_UUID, "CMTP" }, + { UDI_UUID, "UDI" }, + { MCAP_CTRL_UUID, "MCAP-Ctrl" }, + { MCAP_DATA_UUID, "MCAP-Data" }, + { L2CAP_UUID, "L2CAP" }, + { ATT_UUID, "ATT" }, + { 0 } +}; + +static struct tupla ServiceClass[] = { + { SDP_SERVER_SVCLASS_ID, "SDP Server" }, + { BROWSE_GRP_DESC_SVCLASS_ID, "Browse Group Descriptor" }, + { PUBLIC_BROWSE_GROUP, "Public Browse Group" }, + { SERIAL_PORT_SVCLASS_ID, "Serial Port" }, + { LAN_ACCESS_SVCLASS_ID, "LAN Access Using PPP" }, + { DIALUP_NET_SVCLASS_ID, "Dialup Networking" }, + { IRMC_SYNC_SVCLASS_ID, "IrMC Sync" }, + { OBEX_OBJPUSH_SVCLASS_ID, "OBEX Object Push" }, + { OBEX_FILETRANS_SVCLASS_ID, "OBEX File Transfer" }, + { IRMC_SYNC_CMD_SVCLASS_ID, "IrMC Sync Command" }, + { HEADSET_SVCLASS_ID, "Headset" }, + { CORDLESS_TELEPHONY_SVCLASS_ID, "Cordless Telephony" }, + { AUDIO_SOURCE_SVCLASS_ID, "Audio Source" }, + { AUDIO_SINK_SVCLASS_ID, "Audio Sink" }, + { AV_REMOTE_TARGET_SVCLASS_ID, "AV Remote Target" }, + { ADVANCED_AUDIO_SVCLASS_ID, "Advanced Audio" }, + { AV_REMOTE_SVCLASS_ID, "AV Remote" }, + { AV_REMOTE_CONTROLLER_SVCLASS_ID, "AV Remote Controller" }, + { INTERCOM_SVCLASS_ID, "Intercom" }, + { FAX_SVCLASS_ID, "Fax" }, + { HEADSET_AGW_SVCLASS_ID, "Headset Audio Gateway" }, + { WAP_SVCLASS_ID, "WAP" }, + { WAP_CLIENT_SVCLASS_ID, "WAP Client" }, + { PANU_SVCLASS_ID, "PAN User" }, + { NAP_SVCLASS_ID, "Network Access Point" }, + { GN_SVCLASS_ID, "PAN Group Network" }, + { DIRECT_PRINTING_SVCLASS_ID, "Direct Printing" }, + { REFERENCE_PRINTING_SVCLASS_ID, "Reference Printing" }, + { IMAGING_SVCLASS_ID, "Imaging" }, + { IMAGING_RESPONDER_SVCLASS_ID, "Imaging Responder" }, + { IMAGING_ARCHIVE_SVCLASS_ID, "Imaging Automatic Archive" }, + { IMAGING_REFOBJS_SVCLASS_ID, "Imaging Referenced Objects" }, + { HANDSFREE_SVCLASS_ID, "Handsfree" }, + { HANDSFREE_AGW_SVCLASS_ID, "Handsfree Audio Gateway" }, + { DIRECT_PRT_REFOBJS_SVCLASS_ID, "Direct Printing Ref. Objects" }, + { REFLECTED_UI_SVCLASS_ID, "Reflected UI" }, + { BASIC_PRINTING_SVCLASS_ID, "Basic Printing" }, + { PRINTING_STATUS_SVCLASS_ID, "Printing Status" }, + { HID_SVCLASS_ID, "Human Interface Device" }, + { HCR_SVCLASS_ID, "Hardcopy Cable Replacement" }, + { HCR_PRINT_SVCLASS_ID, "HCR Print" }, + { HCR_SCAN_SVCLASS_ID, "HCR Scan" }, + { CIP_SVCLASS_ID, "Common ISDN Access" }, + { VIDEO_CONF_GW_SVCLASS_ID, "Video Conferencing Gateway" }, + { UDI_MT_SVCLASS_ID, "UDI MT" }, + { UDI_TA_SVCLASS_ID, "UDI TA" }, + { AV_SVCLASS_ID, "Audio/Video" }, + { SAP_SVCLASS_ID, "SIM Access" }, + { PBAP_PCE_SVCLASS_ID, "Phonebook Access - PCE" }, + { PBAP_PSE_SVCLASS_ID, "Phonebook Access - PSE" }, + { PBAP_SVCLASS_ID, "Phonebook Access" }, + { MAP_MSE_SVCLASS_ID, "Message Access - MAS" }, + { MAP_MCE_SVCLASS_ID, "Message Access - MNS" }, + { MAP_SVCLASS_ID, "Message Access" }, + { PNP_INFO_SVCLASS_ID, "PnP Information" }, + { GENERIC_NETWORKING_SVCLASS_ID, "Generic Networking" }, + { GENERIC_FILETRANS_SVCLASS_ID, "Generic File Transfer" }, + { GENERIC_AUDIO_SVCLASS_ID, "Generic Audio" }, + { GENERIC_TELEPHONY_SVCLASS_ID, "Generic Telephony" }, + { UPNP_SVCLASS_ID, "UPnP" }, + { UPNP_IP_SVCLASS_ID, "UPnP IP" }, + { UPNP_PAN_SVCLASS_ID, "UPnP PAN" }, + { UPNP_LAP_SVCLASS_ID, "UPnP LAP" }, + { UPNP_L2CAP_SVCLASS_ID, "UPnP L2CAP" }, + { VIDEO_SOURCE_SVCLASS_ID, "Video Source" }, + { VIDEO_SINK_SVCLASS_ID, "Video Sink" }, + { VIDEO_DISTRIBUTION_SVCLASS_ID, "Video Distribution" }, + { HDP_SVCLASS_ID, "HDP" }, + { HDP_SOURCE_SVCLASS_ID, "HDP Source" }, + { HDP_SINK_SVCLASS_ID, "HDP Sink" }, + { GENERIC_ACCESS_SVCLASS_ID, "Generic Access" }, + { GENERIC_ATTRIB_SVCLASS_ID, "Generic Attribute" }, + { APPLE_AGENT_SVCLASS_ID, "Apple Agent" }, + { 0 } +}; + +#define Profile ServiceClass + +static char *string_lookup(struct tupla *pt0, int index) +{ + struct tupla *pt; + + for (pt = pt0; pt->index; pt++) + if (pt->index == index) + return pt->str; + + return ""; +} + +static char *string_lookup_uuid(struct tupla *pt0, const uuid_t *uuid) +{ + uuid_t tmp_uuid; + + memcpy(&tmp_uuid, uuid, sizeof(tmp_uuid)); + + if (sdp_uuid128_to_uuid(&tmp_uuid)) { + switch (tmp_uuid.type) { + case SDP_UUID16: + return string_lookup(pt0, tmp_uuid.value.uuid16); + case SDP_UUID32: + return string_lookup(pt0, tmp_uuid.value.uuid32); + } + } + + return ""; +} + +/* + * Prints into a string the Protocol UUID + * coping a maximum of n characters. + */ +static int uuid2str(struct tupla *message, const uuid_t *uuid, char *str, size_t n) +{ + char *str2; + + if (!uuid) { + snprintf(str, n, "NULL"); + return -2; + } + + switch (uuid->type) { + case SDP_UUID16: + str2 = string_lookup(message, uuid->value.uuid16); + snprintf(str, n, "%s", str2); + break; + case SDP_UUID32: + str2 = string_lookup(message, uuid->value.uuid32); + snprintf(str, n, "%s", str2); + break; + case SDP_UUID128: + str2 = string_lookup_uuid(message, uuid); + snprintf(str, n, "%s", str2); + break; + default: + snprintf(str, n, "Type of UUID (%x) unknown.", uuid->type); + return -1; + } + + return 0; +} + +int sdp_proto_uuid2strn(const uuid_t *uuid, char *str, size_t n) +{ + return uuid2str(Protocol, uuid, str, n); +} + +int sdp_svclass_uuid2strn(const uuid_t *uuid, char *str, size_t n) +{ + return uuid2str(ServiceClass, uuid, str, n); +} + +int sdp_profile_uuid2strn(const uuid_t *uuid, char *str, size_t n) +{ + return uuid2str(Profile, uuid, str, n); +} + +/* + * convert the UUID to string, copying a maximum of n characters. + */ +int sdp_uuid2strn(const uuid_t *uuid, char *str, size_t n) +{ + if (!uuid) { + snprintf(str, n, "NULL"); + return -2; + } + switch (uuid->type) { + case SDP_UUID16: + snprintf(str, n, "%.4x", uuid->value.uuid16); + break; + case SDP_UUID32: + snprintf(str, n, "%.8x", uuid->value.uuid32); + break; + case SDP_UUID128:{ + unsigned int data0; + unsigned short data1; + unsigned short data2; + unsigned short data3; + unsigned int data4; + unsigned short data5; + + memcpy(&data0, &uuid->value.uuid128.data[0], 4); + memcpy(&data1, &uuid->value.uuid128.data[4], 2); + memcpy(&data2, &uuid->value.uuid128.data[6], 2); + memcpy(&data3, &uuid->value.uuid128.data[8], 2); + memcpy(&data4, &uuid->value.uuid128.data[10], 4); + memcpy(&data5, &uuid->value.uuid128.data[14], 2); + + snprintf(str, n, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x", + ntohl(data0), ntohs(data1), + ntohs(data2), ntohs(data3), + ntohl(data4), ntohs(data5)); + } + break; + default: + snprintf(str, n, "Type of UUID (%x) unknown.", uuid->type); + return -1; /* Enum type of UUID not set */ + } + return 0; +} + +#ifdef SDP_DEBUG +/* + * Function prints the UUID in hex as per defined syntax - + * + * 4bytes-2bytes-2bytes-2bytes-6bytes + * + * There is some ugly code, including hardcoding, but + * that is just the way it is converting 16 and 32 bit + * UUIDs to 128 bit as defined in the SDP doc + */ +void sdp_uuid_print(const uuid_t *uuid) +{ + if (uuid == NULL) { + SDPERR("Null passed to print UUID"); + return; + } + if (uuid->type == SDP_UUID16) { + SDPDBG(" uint16_t : 0x%.4x", uuid->value.uuid16); + } else if (uuid->type == SDP_UUID32) { + SDPDBG(" uint32_t : 0x%.8x", uuid->value.uuid32); + } else if (uuid->type == SDP_UUID128) { + unsigned int data0; + unsigned short data1; + unsigned short data2; + unsigned short data3; + unsigned int data4; + unsigned short data5; + + memcpy(&data0, &uuid->value.uuid128.data[0], 4); + memcpy(&data1, &uuid->value.uuid128.data[4], 2); + memcpy(&data2, &uuid->value.uuid128.data[6], 2); + memcpy(&data3, &uuid->value.uuid128.data[8], 2); + memcpy(&data4, &uuid->value.uuid128.data[10], 4); + memcpy(&data5, &uuid->value.uuid128.data[14], 2); + + SDPDBG(" uint128_t : 0x%.8x-%.4x-%.4x-%.4x-%.8x%.4x", + ntohl(data0), ntohs(data1), ntohs(data2), + ntohs(data3), ntohl(data4), ntohs(data5)); + } else + SDPERR("Enum type of UUID not set"); +} +#endif + +sdp_data_t *sdp_data_alloc_with_length(uint8_t dtd, const void *value, + uint32_t length) +{ + sdp_data_t *seq; + sdp_data_t *d = malloc(sizeof(sdp_data_t)); + + if (!d) + return NULL; + + memset(d, 0, sizeof(sdp_data_t)); + d->dtd = dtd; + d->unitSize = sizeof(uint8_t); + + switch (dtd) { + case SDP_DATA_NIL: + break; + case SDP_UINT8: + d->val.uint8 = *(uint8_t *) value; + d->unitSize += sizeof(uint8_t); + break; + case SDP_INT8: + case SDP_BOOL: + d->val.int8 = *(int8_t *) value; + d->unitSize += sizeof(int8_t); + break; + case SDP_UINT16: + d->val.uint16 = bt_get_unaligned((uint16_t *) value); + d->unitSize += sizeof(uint16_t); + break; + case SDP_INT16: + d->val.int16 = bt_get_unaligned((int16_t *) value); + d->unitSize += sizeof(int16_t); + break; + case SDP_UINT32: + d->val.uint32 = bt_get_unaligned((uint32_t *) value); + d->unitSize += sizeof(uint32_t); + break; + case SDP_INT32: + d->val.int32 = bt_get_unaligned((int32_t *) value); + d->unitSize += sizeof(int32_t); + break; + case SDP_INT64: + d->val.int64 = bt_get_unaligned((int64_t *) value); + d->unitSize += sizeof(int64_t); + break; + case SDP_UINT64: + d->val.uint64 = bt_get_unaligned((uint64_t *) value); + d->unitSize += sizeof(uint64_t); + break; + case SDP_UINT128: + memcpy(&d->val.uint128.data, value, sizeof(uint128_t)); + d->unitSize += sizeof(uint128_t); + break; + case SDP_INT128: + memcpy(&d->val.int128.data, value, sizeof(uint128_t)); + d->unitSize += sizeof(uint128_t); + break; + case SDP_UUID16: + sdp_uuid16_create(&d->val.uuid, bt_get_unaligned((uint16_t *) value)); + d->unitSize += sizeof(uint16_t); + break; + case SDP_UUID32: + sdp_uuid32_create(&d->val.uuid, bt_get_unaligned((uint32_t *) value)); + d->unitSize += sizeof(uint32_t); + break; + case SDP_UUID128: + sdp_uuid128_create(&d->val.uuid, value); + d->unitSize += sizeof(uint128_t); + break; + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + if (!value) { + free(d); + return NULL; + } + + d->unitSize += length; + if (length <= USHRT_MAX) { + d->val.str = malloc(length); + if (!d->val.str) { + free(d); + return NULL; + } + + memcpy(d->val.str, value, length); + } else { + SDPERR("Strings of size > USHRT_MAX not supported"); + free(d); + d = NULL; + } + break; + case SDP_URL_STR32: + case SDP_TEXT_STR32: + SDPERR("Strings of size > USHRT_MAX not supported"); + break; + case SDP_ALT8: + case SDP_ALT16: + case SDP_ALT32: + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + if (dtd == SDP_ALT8 || dtd == SDP_SEQ8) + d->unitSize += sizeof(uint8_t); + else if (dtd == SDP_ALT16 || dtd == SDP_SEQ16) + d->unitSize += sizeof(uint16_t); + else if (dtd == SDP_ALT32 || dtd == SDP_SEQ32) + d->unitSize += sizeof(uint32_t); + seq = (sdp_data_t *)value; + d->val.dataseq = seq; + for (; seq; seq = seq->next) + d->unitSize += seq->unitSize; + break; + default: + free(d); + d = NULL; + } + + return d; +} + +sdp_data_t *sdp_data_alloc(uint8_t dtd, const void *value) +{ + uint32_t length; + + switch (dtd) { + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + if (!value) + return NULL; + + length = strlen((char *) value); + break; + default: + length = 0; + break; + } + + return sdp_data_alloc_with_length(dtd, value, length); +} + +sdp_data_t *sdp_seq_append(sdp_data_t *seq, sdp_data_t *d) +{ + if (seq) { + sdp_data_t *p; + for (p = seq; p->next; p = p->next); + p->next = d; + } else + seq = d; + d->next = NULL; + return seq; +} + +sdp_data_t *sdp_seq_alloc_with_length(void **dtds, void **values, int *length, + int len) +{ + sdp_data_t *curr = NULL, *seq = NULL; + int i; + + for (i = 0; i < len; i++) { + sdp_data_t *data; + int8_t dtd = *(uint8_t *) dtds[i]; + + if (dtd >= SDP_SEQ8 && dtd <= SDP_ALT32) + data = (sdp_data_t *) values[i]; + else + data = sdp_data_alloc_with_length(dtd, values[i], length[i]); + + if (!data) + return NULL; + + if (curr) + curr->next = data; + else + seq = data; + + curr = data; + } + + return sdp_data_alloc(SDP_SEQ8, seq); +} + +sdp_data_t *sdp_seq_alloc(void **dtds, void **values, int len) +{ + sdp_data_t *curr = NULL, *seq = NULL; + int i; + + for (i = 0; i < len; i++) { + sdp_data_t *data; + uint8_t dtd = *(uint8_t *) dtds[i]; + + if (dtd >= SDP_SEQ8 && dtd <= SDP_ALT32) + data = (sdp_data_t *) values[i]; + else + data = sdp_data_alloc(dtd, values[i]); + + if (!data) + return NULL; + + if (curr) + curr->next = data; + else + seq = data; + + curr = data; + } + + return sdp_data_alloc(SDP_SEQ8, seq); +} + +static void extract_svclass_uuid(sdp_data_t *data, uuid_t *uuid) +{ + sdp_data_t *d; + + if (!data || !SDP_IS_SEQ(data->dtd)) + return; + + d = data->val.dataseq; + if (!d) + return; + + if (d->dtd < SDP_UUID16 || d->dtd > SDP_UUID128) + return; + + *uuid = d->val.uuid; +} + +int sdp_attr_add(sdp_record_t *rec, uint16_t attr, sdp_data_t *d) +{ + sdp_data_t *p = sdp_data_get(rec, attr); + + if (p) + return -1; + + d->attrId = attr; + rec->attrlist = sdp_list_insert_sorted(rec->attrlist, d, sdp_attrid_comp_func); + + if (attr == SDP_ATTR_SVCLASS_ID_LIST) + extract_svclass_uuid(d, &rec->svclass); + + return 0; +} + +void sdp_attr_remove(sdp_record_t *rec, uint16_t attr) +{ + sdp_data_t *d = sdp_data_get(rec, attr); + + if (d) + rec->attrlist = sdp_list_remove(rec->attrlist, d); + + if (attr == SDP_ATTR_SVCLASS_ID_LIST) + memset(&rec->svclass, 0, sizeof(rec->svclass)); +} + +void sdp_set_seq_len(uint8_t *ptr, uint32_t length) +{ + uint8_t dtd = *ptr++; + + switch (dtd) { + case SDP_SEQ8: + case SDP_ALT8: + case SDP_TEXT_STR8: + case SDP_URL_STR8: + *ptr = (uint8_t) length; + break; + case SDP_SEQ16: + case SDP_ALT16: + case SDP_TEXT_STR16: + case SDP_URL_STR16: + bt_put_be16(length, ptr); + break; + case SDP_SEQ32: + case SDP_ALT32: + case SDP_TEXT_STR32: + case SDP_URL_STR32: + bt_put_be32(length, ptr); + break; + } +} + +static int sdp_get_data_type_size(uint8_t dtd) +{ + int size = sizeof(uint8_t); + + switch (dtd) { + case SDP_SEQ8: + case SDP_TEXT_STR8: + case SDP_URL_STR8: + case SDP_ALT8: + size += sizeof(uint8_t); + break; + case SDP_SEQ16: + case SDP_TEXT_STR16: + case SDP_URL_STR16: + case SDP_ALT16: + size += sizeof(uint16_t); + break; + case SDP_SEQ32: + case SDP_TEXT_STR32: + case SDP_URL_STR32: + case SDP_ALT32: + size += sizeof(uint32_t); + break; + } + + return size; +} + +void sdp_set_attrid(sdp_buf_t *buf, uint16_t attr) +{ + uint8_t *p = buf->data; + + /* data type for attr */ + *p++ = SDP_UINT16; + buf->data_size = sizeof(uint8_t); + bt_put_be16(attr, p); + buf->data_size += sizeof(uint16_t); +} + +static int get_data_size(sdp_buf_t *buf, sdp_data_t *sdpdata) +{ + sdp_data_t *d; + int n = 0; + + for (d = sdpdata->val.dataseq; d; d = d->next) { + if (buf->data) + n += sdp_gen_pdu(buf, d); + else + n += sdp_gen_buffer(buf, d); + } + + return n; +} + +static int sdp_get_data_size(sdp_buf_t *buf, sdp_data_t *d) +{ + uint32_t data_size = 0; + uint8_t dtd = d->dtd; + + switch (dtd) { + case SDP_DATA_NIL: + break; + case SDP_UINT8: + data_size = sizeof(uint8_t); + break; + case SDP_UINT16: + data_size = sizeof(uint16_t); + break; + case SDP_UINT32: + data_size = sizeof(uint32_t); + break; + case SDP_UINT64: + data_size = sizeof(uint64_t); + break; + case SDP_UINT128: + data_size = sizeof(uint128_t); + break; + case SDP_INT8: + case SDP_BOOL: + data_size = sizeof(int8_t); + break; + case SDP_INT16: + data_size = sizeof(int16_t); + break; + case SDP_INT32: + data_size = sizeof(int32_t); + break; + case SDP_INT64: + data_size = sizeof(int64_t); + break; + case SDP_INT128: + data_size = sizeof(uint128_t); + break; + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + case SDP_TEXT_STR32: + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_URL_STR32: + data_size = d->unitSize - sizeof(uint8_t); + break; + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + data_size = get_data_size(buf, d); + break; + case SDP_ALT8: + case SDP_ALT16: + case SDP_ALT32: + data_size = get_data_size(buf, d); + break; + case SDP_UUID16: + data_size = sizeof(uint16_t); + break; + case SDP_UUID32: + data_size = sizeof(uint32_t); + break; + case SDP_UUID128: + data_size = sizeof(uint128_t); + break; + default: + break; + } + + return data_size; +} + +static int sdp_gen_buffer(sdp_buf_t *buf, sdp_data_t *d) +{ + int orig = buf->buf_size; + + if (buf->buf_size == 0 && d->dtd == 0) { + /* create initial sequence */ + buf->buf_size += sizeof(uint8_t); + + /* reserve space for sequence size */ + buf->buf_size += sizeof(uint8_t); + } + + /* attribute length */ + buf->buf_size += sizeof(uint8_t) + sizeof(uint16_t); + + buf->buf_size += sdp_get_data_type_size(d->dtd); + buf->buf_size += sdp_get_data_size(buf, d); + + if (buf->buf_size > UCHAR_MAX && d->dtd == SDP_SEQ8) + buf->buf_size += sizeof(uint8_t); + + return buf->buf_size - orig; +} + +int sdp_gen_pdu(sdp_buf_t *buf, sdp_data_t *d) +{ + uint32_t pdu_size, data_size; + unsigned char *src = NULL, is_seq = 0, is_alt = 0; + uint16_t u16; + uint32_t u32; + uint64_t u64; + uint128_t u128; + uint8_t *seqp = buf->data + buf->data_size; + uint32_t orig_data_size = buf->data_size; + +recalculate: + pdu_size = sdp_get_data_type_size(d->dtd); + buf->data_size += pdu_size; + + data_size = sdp_get_data_size(buf, d); + if (data_size > UCHAR_MAX && d->dtd == SDP_SEQ8) { + buf->data_size = orig_data_size; + d->dtd = SDP_SEQ16; + goto recalculate; + } + + *seqp = d->dtd; + + switch (d->dtd) { + case SDP_DATA_NIL: + break; + case SDP_UINT8: + src = &d->val.uint8; + break; + case SDP_UINT16: + u16 = htons(d->val.uint16); + src = (unsigned char *) &u16; + break; + case SDP_UINT32: + u32 = htonl(d->val.uint32); + src = (unsigned char *) &u32; + break; + case SDP_UINT64: + u64 = hton64(d->val.uint64); + src = (unsigned char *) &u64; + break; + case SDP_UINT128: + hton128(&d->val.uint128, &u128); + src = (unsigned char *) &u128; + break; + case SDP_INT8: + case SDP_BOOL: + src = (unsigned char *) &d->val.int8; + break; + case SDP_INT16: + u16 = htons(d->val.int16); + src = (unsigned char *) &u16; + break; + case SDP_INT32: + u32 = htonl(d->val.int32); + src = (unsigned char *) &u32; + break; + case SDP_INT64: + u64 = hton64(d->val.int64); + src = (unsigned char *) &u64; + break; + case SDP_INT128: + hton128(&d->val.int128, &u128); + src = (unsigned char *) &u128; + break; + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + case SDP_TEXT_STR32: + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_URL_STR32: + src = (unsigned char *) d->val.str; + sdp_set_seq_len(seqp, data_size); + break; + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + is_seq = 1; + sdp_set_seq_len(seqp, data_size); + break; + case SDP_ALT8: + case SDP_ALT16: + case SDP_ALT32: + is_alt = 1; + sdp_set_seq_len(seqp, data_size); + break; + case SDP_UUID16: + u16 = htons(d->val.uuid.value.uuid16); + src = (unsigned char *) &u16; + break; + case SDP_UUID32: + u32 = htonl(d->val.uuid.value.uuid32); + src = (unsigned char *) &u32; + break; + case SDP_UUID128: + src = (unsigned char *) &d->val.uuid.value.uuid128; + break; + default: + break; + } + + if (!is_seq && !is_alt) { + if (src && buf->buf_size >= buf->data_size + data_size) { + memcpy(buf->data + buf->data_size, src, data_size); + buf->data_size += data_size; + } else if (d->dtd != SDP_DATA_NIL) { + SDPDBG("Gen PDU : Can't copy from invalid source or dest"); + } + } + + pdu_size += data_size; + + return pdu_size; +} + +static void sdp_attr_pdu(void *value, void *udata) +{ + sdp_append_to_pdu((sdp_buf_t *)udata, (sdp_data_t *)value); +} + +static void sdp_attr_size(void *value, void *udata) +{ + sdp_gen_buffer((sdp_buf_t *)udata, (sdp_data_t *)value); +} + +int sdp_gen_record_pdu(const sdp_record_t *rec, sdp_buf_t *buf) +{ + memset(buf, 0, sizeof(sdp_buf_t)); + sdp_list_foreach(rec->attrlist, sdp_attr_size, buf); + + buf->data = malloc(buf->buf_size); + if (!buf->data) + return -ENOMEM; + buf->data_size = 0; + memset(buf->data, 0, buf->buf_size); + + sdp_list_foreach(rec->attrlist, sdp_attr_pdu, buf); + + return 0; +} + +void sdp_attr_replace(sdp_record_t *rec, uint16_t attr, sdp_data_t *d) +{ + sdp_data_t *p; + + if (!rec) + return; + + p = sdp_data_get(rec, attr); + if (p) { + rec->attrlist = sdp_list_remove(rec->attrlist, p); + sdp_data_free(p); + } + + d->attrId = attr; + rec->attrlist = sdp_list_insert_sorted(rec->attrlist, d, sdp_attrid_comp_func); + + if (attr == SDP_ATTR_SVCLASS_ID_LIST) + extract_svclass_uuid(d, &rec->svclass); +} + +int sdp_attrid_comp_func(const void *key1, const void *key2) +{ + const sdp_data_t *d1 = (const sdp_data_t *)key1; + const sdp_data_t *d2 = (const sdp_data_t *)key2; + + if (d1 && d2) + return d1->attrId - d2->attrId; + return 0; +} + +static void data_seq_free(sdp_data_t *seq) +{ + sdp_data_t *d = seq->val.dataseq; + + while (d) { + sdp_data_t *next = d->next; + sdp_data_free(d); + d = next; + } +} + +void sdp_data_free(sdp_data_t *d) +{ + switch (d->dtd) { + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + data_seq_free(d); + break; + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_URL_STR32: + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + case SDP_TEXT_STR32: + free(d->val.str); + break; + } + free(d); +} + +int sdp_uuid_extract(const uint8_t *p, int bufsize, uuid_t *uuid, int *scanned) +{ + uint8_t type; + + if (bufsize < (int) sizeof(uint8_t)) { + SDPERR("Unexpected end of packet"); + return -1; + } + + type = *(const uint8_t *) p; + + if (!SDP_IS_UUID(type)) { + SDPERR("Unknown data type : %d expecting a svc UUID", type); + return -1; + } + p += sizeof(uint8_t); + *scanned += sizeof(uint8_t); + bufsize -= sizeof(uint8_t); + if (type == SDP_UUID16) { + if (bufsize < (int) sizeof(uint16_t)) { + SDPERR("Not enough room for 16-bit UUID"); + return -1; + } + sdp_uuid16_create(uuid, bt_get_be16(p)); + *scanned += sizeof(uint16_t); + } else if (type == SDP_UUID32) { + if (bufsize < (int) sizeof(uint32_t)) { + SDPERR("Not enough room for 32-bit UUID"); + return -1; + } + sdp_uuid32_create(uuid, bt_get_be32(p)); + *scanned += sizeof(uint32_t); + } else { + if (bufsize < (int) sizeof(uint128_t)) { + SDPERR("Not enough room for 128-bit UUID"); + return -1; + } + sdp_uuid128_create(uuid, p); + *scanned += sizeof(uint128_t); + } + return 0; +} + +static sdp_data_t *extract_int(const void *p, int bufsize, int *len) +{ + sdp_data_t *d; + + if (bufsize < (int) sizeof(uint8_t)) { + SDPERR("Unexpected end of packet"); + return NULL; + } + + d = malloc(sizeof(sdp_data_t)); + if (!d) + return NULL; + + SDPDBG("Extracting integer"); + memset(d, 0, sizeof(sdp_data_t)); + d->dtd = *(uint8_t *) p; + p += sizeof(uint8_t); + *len += sizeof(uint8_t); + bufsize -= sizeof(uint8_t); + + switch (d->dtd) { + case SDP_DATA_NIL: + break; + case SDP_BOOL: + case SDP_INT8: + case SDP_UINT8: + if (bufsize < (int) sizeof(uint8_t)) { + SDPERR("Unexpected end of packet"); + free(d); + return NULL; + } + *len += sizeof(uint8_t); + d->val.uint8 = *(uint8_t *) p; + break; + case SDP_INT16: + case SDP_UINT16: + if (bufsize < (int) sizeof(uint16_t)) { + SDPERR("Unexpected end of packet"); + free(d); + return NULL; + } + *len += sizeof(uint16_t); + d->val.uint16 = bt_get_be16(p); + break; + case SDP_INT32: + case SDP_UINT32: + if (bufsize < (int) sizeof(uint32_t)) { + SDPERR("Unexpected end of packet"); + free(d); + return NULL; + } + *len += sizeof(uint32_t); + d->val.uint32 = bt_get_be32(p); + break; + case SDP_INT64: + case SDP_UINT64: + if (bufsize < (int) sizeof(uint64_t)) { + SDPERR("Unexpected end of packet"); + free(d); + return NULL; + } + *len += sizeof(uint64_t); + d->val.uint64 = bt_get_be64(p); + break; + case SDP_INT128: + case SDP_UINT128: + if (bufsize < (int) sizeof(uint128_t)) { + SDPERR("Unexpected end of packet"); + free(d); + return NULL; + } + *len += sizeof(uint128_t); + ntoh128((uint128_t *) p, &d->val.uint128); + break; + default: + free(d); + d = NULL; + } + return d; +} + +static sdp_data_t *extract_uuid(const uint8_t *p, int bufsize, int *len, + sdp_record_t *rec) +{ + sdp_data_t *d = malloc(sizeof(sdp_data_t)); + + if (!d) + return NULL; + + SDPDBG("Extracting UUID"); + memset(d, 0, sizeof(sdp_data_t)); + if (sdp_uuid_extract(p, bufsize, &d->val.uuid, len) < 0) { + free(d); + return NULL; + } + d->dtd = *p; + if (rec) + sdp_pattern_add_uuid(rec, &d->val.uuid); + return d; +} + +/* + * Extract strings from the PDU (could be service description and similar info) + */ +static sdp_data_t *extract_str(const void *p, int bufsize, int *len) +{ + char *s; + int n; + sdp_data_t *d; + + if (bufsize < (int) sizeof(uint8_t)) { + SDPERR("Unexpected end of packet"); + return NULL; + } + + d = malloc(sizeof(sdp_data_t)); + if (!d) + return NULL; + + memset(d, 0, sizeof(sdp_data_t)); + d->dtd = *(uint8_t *) p; + p += sizeof(uint8_t); + *len += sizeof(uint8_t); + bufsize -= sizeof(uint8_t); + + switch (d->dtd) { + case SDP_TEXT_STR8: + case SDP_URL_STR8: + if (bufsize < (int) sizeof(uint8_t)) { + SDPERR("Unexpected end of packet"); + free(d); + return NULL; + } + n = *(uint8_t *) p; + p += sizeof(uint8_t); + *len += sizeof(uint8_t); + bufsize -= sizeof(uint8_t); + break; + case SDP_TEXT_STR16: + case SDP_URL_STR16: + if (bufsize < (int) sizeof(uint16_t)) { + SDPERR("Unexpected end of packet"); + free(d); + return NULL; + } + n = bt_get_be16(p); + p += sizeof(uint16_t); + *len += sizeof(uint16_t); + bufsize -= sizeof(uint16_t); + break; + default: + SDPERR("Sizeof text string > UINT16_MAX"); + free(d); + return NULL; + } + + if (bufsize < n) { + SDPERR("String too long to fit in packet"); + free(d); + return NULL; + } + + s = malloc(n + 1); + if (!s) { + SDPERR("Not enough memory for incoming string"); + free(d); + return NULL; + } + memset(s, 0, n + 1); + memcpy(s, p, n); + + *len += n; + + SDPDBG("Len : %d", n); + SDPDBG("Str : %s", s); + + d->val.str = s; + d->unitSize = n + sizeof(uint8_t); + return d; +} + +/* + * Extract the sequence type and its length, and return offset into buf + * or 0 on failure. + */ +int sdp_extract_seqtype(const uint8_t *buf, int bufsize, uint8_t *dtdp, int *size) +{ + uint8_t dtd; + int scanned = sizeof(uint8_t); + + if (bufsize < (int) sizeof(uint8_t)) { + SDPERR("Unexpected end of packet"); + return 0; + } + + dtd = *(uint8_t *) buf; + buf += sizeof(uint8_t); + bufsize -= sizeof(uint8_t); + *dtdp = dtd; + switch (dtd) { + case SDP_SEQ8: + case SDP_ALT8: + if (bufsize < (int) sizeof(uint8_t)) { + SDPERR("Unexpected end of packet"); + return 0; + } + *size = *(uint8_t *) buf; + scanned += sizeof(uint8_t); + break; + case SDP_SEQ16: + case SDP_ALT16: + if (bufsize < (int) sizeof(uint16_t)) { + SDPERR("Unexpected end of packet"); + return 0; + } + *size = bt_get_be16(buf); + scanned += sizeof(uint16_t); + break; + case SDP_SEQ32: + case SDP_ALT32: + if (bufsize < (int) sizeof(uint32_t)) { + SDPERR("Unexpected end of packet"); + return 0; + } + *size = bt_get_be32(buf); + scanned += sizeof(uint32_t); + break; + default: + SDPERR("Unknown sequence type, aborting"); + return 0; + } + return scanned; +} + +static sdp_data_t *extract_seq(const void *p, int bufsize, int *len, + sdp_record_t *rec) +{ + int seqlen, n = 0; + sdp_data_t *curr, *prev; + sdp_data_t *d = malloc(sizeof(sdp_data_t)); + + if (!d) + return NULL; + + SDPDBG("Extracting SEQ"); + memset(d, 0, sizeof(sdp_data_t)); + *len = sdp_extract_seqtype(p, bufsize, &d->dtd, &seqlen); + SDPDBG("Sequence Type : 0x%x length : 0x%x", d->dtd, seqlen); + + if (*len == 0) + return d; + + if (*len > bufsize) { + SDPERR("Packet not big enough to hold sequence."); + free(d); + return NULL; + } + + p += *len; + bufsize -= *len; + prev = NULL; + while (n < seqlen) { + int attrlen = 0; + curr = sdp_extract_attr(p, bufsize, &attrlen, rec); + if (curr == NULL) + break; + + if (prev) + prev->next = curr; + else + d->val.dataseq = curr; + prev = curr; + p += attrlen; + n += attrlen; + bufsize -= attrlen; + + SDPDBG("Extracted: %d SequenceLength: %d", n, seqlen); + } + + *len += n; + return d; +} + +sdp_data_t *sdp_extract_attr(const uint8_t *p, int bufsize, int *size, + sdp_record_t *rec) +{ + sdp_data_t *elem; + int n = 0; + uint8_t dtd; + + if (bufsize < (int) sizeof(uint8_t)) { + SDPERR("Unexpected end of packet"); + return NULL; + } + + dtd = *(const uint8_t *)p; + + SDPDBG("extract_attr: dtd=0x%x", dtd); + switch (dtd) { + case SDP_DATA_NIL: + case SDP_BOOL: + case SDP_UINT8: + case SDP_UINT16: + case SDP_UINT32: + case SDP_UINT64: + case SDP_UINT128: + case SDP_INT8: + case SDP_INT16: + case SDP_INT32: + case SDP_INT64: + case SDP_INT128: + elem = extract_int(p, bufsize, &n); + break; + case SDP_UUID16: + case SDP_UUID32: + case SDP_UUID128: + elem = extract_uuid(p, bufsize, &n, rec); + break; + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + case SDP_TEXT_STR32: + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_URL_STR32: + elem = extract_str(p, bufsize, &n); + break; + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + case SDP_ALT8: + case SDP_ALT16: + case SDP_ALT32: + elem = extract_seq(p, bufsize, &n, rec); + break; + default: + SDPERR("Unknown data descriptor : 0x%x terminating", dtd); + return NULL; + } + *size += n; + return elem; +} + +#ifdef SDP_DEBUG +static void attr_print_func(void *value, void *userData) +{ + sdp_data_t *d = (sdp_data_t *)value; + + SDPDBG("====================================="); + SDPDBG("ATTRIBUTE IDENTIFIER : 0x%x", d->attrId); + SDPDBG("ATTRIBUTE VALUE PTR : %p", value); + if (d) + sdp_data_print(d); + else + SDPDBG("NULL value"); + SDPDBG("====================================="); +} + +void sdp_print_service_attr(sdp_list_t *svcAttrList) +{ + SDPDBG("Printing service attr list %p", svcAttrList); + sdp_list_foreach(svcAttrList, attr_print_func, NULL); + SDPDBG("Printed service attr list %p", svcAttrList); +} +#endif + +sdp_record_t *sdp_extract_pdu(const uint8_t *buf, int bufsize, int *scanned) +{ + int extracted = 0, seqlen = 0; + uint8_t dtd; + uint16_t attr; + sdp_record_t *rec = sdp_record_alloc(); + const uint8_t *p = buf; + + *scanned = sdp_extract_seqtype(buf, bufsize, &dtd, &seqlen); + p += *scanned; + bufsize -= *scanned; + rec->attrlist = NULL; + + while (extracted < seqlen && bufsize > 0) { + int n = sizeof(uint8_t), attrlen = 0; + sdp_data_t *data = NULL; + + SDPDBG("Extract PDU, sequenceLength: %d localExtractedLength: %d", + seqlen, extracted); + + if (bufsize < n + (int) sizeof(uint16_t)) { + SDPERR("Unexpected end of packet"); + break; + } + + dtd = *(uint8_t *) p; + attr = bt_get_be16(p + n); + n += sizeof(uint16_t); + + SDPDBG("DTD of attrId : %d Attr id : 0x%x ", dtd, attr); + + data = sdp_extract_attr(p + n, bufsize - n, &attrlen, rec); + + SDPDBG("Attr id : 0x%x attrValueLength : %d", attr, attrlen); + + n += attrlen; + if (data == NULL) { + SDPDBG("Terminating extraction of attributes"); + break; + } + + if (attr == SDP_ATTR_RECORD_HANDLE) + rec->handle = data->val.uint32; + + if (attr == SDP_ATTR_SVCLASS_ID_LIST) + extract_svclass_uuid(data, &rec->svclass); + + extracted += n; + p += n; + bufsize -= n; + sdp_attr_replace(rec, attr, data); + + SDPDBG("Extract PDU, seqLength: %d localExtractedLength: %d", + seqlen, extracted); + } +#ifdef SDP_DEBUG + SDPDBG("Successful extracting of Svc Rec attributes"); + sdp_print_service_attr(rec->attrlist); +#endif + *scanned += seqlen; + return rec; +} + +static void sdp_copy_pattern(void *value, void *udata) +{ + uuid_t *uuid = value; + sdp_record_t *rec = udata; + + sdp_pattern_add_uuid(rec, uuid); +} + +static void *sdp_data_value(sdp_data_t *data, uint32_t *len) +{ + void *val = NULL; + + switch (data->dtd) { + case SDP_DATA_NIL: + break; + case SDP_UINT8: + val = &data->val.uint8; + break; + case SDP_INT8: + case SDP_BOOL: + val = &data->val.int8; + break; + case SDP_UINT16: + val = &data->val.uint16; + break; + case SDP_INT16: + val = &data->val.int16; + break; + case SDP_UINT32: + val = &data->val.uint32; + break; + case SDP_INT32: + val = &data->val.int32; + break; + case SDP_INT64: + val = &data->val.int64; + break; + case SDP_UINT64: + val = &data->val.uint64; + break; + case SDP_UINT128: + val = &data->val.uint128; + break; + case SDP_INT128: + val = &data->val.int128; + break; + case SDP_UUID16: + val = &data->val.uuid.value.uuid16; + break; + case SDP_UUID32: + val = &data->val.uuid.value.uuid32; + break; + case SDP_UUID128: + val = &data->val.uuid.value.uuid128; + break; + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + case SDP_URL_STR32: + case SDP_TEXT_STR32: + val = data->val.str; + if (len) + *len = data->unitSize - 1; + break; + case SDP_ALT8: + case SDP_ALT16: + case SDP_ALT32: + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + val = sdp_copy_seq(data->val.dataseq); + break; + } + + return val; +} + +static sdp_data_t *sdp_copy_seq(sdp_data_t *data) +{ + sdp_data_t *tmp, *seq = NULL, *cur = NULL; + + for (tmp = data; tmp; tmp = tmp->next) { + sdp_data_t *datatmp; + void *value; + + value = sdp_data_value(tmp, NULL); + datatmp = sdp_data_alloc_with_length(tmp->dtd, value, + tmp->unitSize); + + if (cur) + cur->next = datatmp; + else + seq = datatmp; + + cur = datatmp; + } + + return seq; +} + +static void sdp_copy_attrlist(void *value, void *udata) +{ + sdp_data_t *data = value; + sdp_record_t *rec = udata; + void *val; + uint32_t len = 0; + + val = sdp_data_value(data, &len); + + if (!len) + sdp_attr_add_new(rec, data->attrId, data->dtd, val); + else + sdp_attr_add_new_with_length(rec, data->attrId, + data->dtd, val, len); +} + +sdp_record_t *sdp_copy_record(sdp_record_t *rec) +{ + sdp_record_t *cpy; + + cpy = sdp_record_alloc(); + + cpy->handle = rec->handle; + + sdp_list_foreach(rec->pattern, sdp_copy_pattern, cpy); + sdp_list_foreach(rec->attrlist, sdp_copy_attrlist, cpy); + + cpy->svclass = rec->svclass; + + return cpy; +} + +#ifdef SDP_DEBUG +static void print_dataseq(sdp_data_t *p) +{ + sdp_data_t *d; + + for (d = p; d; d = d->next) + sdp_data_print(d); +} +#endif + +void sdp_record_print(const sdp_record_t *rec) +{ + sdp_data_t *d = sdp_data_get(rec, SDP_ATTR_SVCNAME_PRIMARY); + if (d && SDP_IS_TEXT_STR(d->dtd)) + printf("Service Name: %.*s\n", d->unitSize, d->val.str); + d = sdp_data_get(rec, SDP_ATTR_SVCDESC_PRIMARY); + if (d && SDP_IS_TEXT_STR(d->dtd)) + printf("Service Description: %.*s\n", d->unitSize, d->val.str); + d = sdp_data_get(rec, SDP_ATTR_PROVNAME_PRIMARY); + if (d && SDP_IS_TEXT_STR(d->dtd)) + printf("Service Provider: %.*s\n", d->unitSize, d->val.str); +} + +#ifdef SDP_DEBUG +void sdp_data_print(sdp_data_t *d) +{ + switch (d->dtd) { + case SDP_DATA_NIL: + SDPDBG("NIL"); + break; + case SDP_BOOL: + case SDP_UINT8: + case SDP_UINT16: + case SDP_UINT32: + case SDP_UINT64: + case SDP_UINT128: + case SDP_INT8: + case SDP_INT16: + case SDP_INT32: + case SDP_INT64: + case SDP_INT128: + SDPDBG("Integer : 0x%x", d->val.uint32); + break; + case SDP_UUID16: + case SDP_UUID32: + case SDP_UUID128: + SDPDBG("UUID"); + sdp_uuid_print(&d->val.uuid); + break; + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + case SDP_TEXT_STR32: + SDPDBG("Text : %s", d->val.str); + break; + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_URL_STR32: + SDPDBG("URL : %s", d->val.str); + break; + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + print_dataseq(d->val.dataseq); + break; + case SDP_ALT8: + case SDP_ALT16: + case SDP_ALT32: + SDPDBG("Data Sequence Alternates"); + print_dataseq(d->val.dataseq); + break; + } +} +#endif + +sdp_data_t *sdp_data_get(const sdp_record_t *rec, uint16_t attrId) +{ + if (rec && rec->attrlist) { + sdp_data_t sdpTemplate; + sdp_list_t *p; + + sdpTemplate.attrId = attrId; + p = sdp_list_find(rec->attrlist, &sdpTemplate, sdp_attrid_comp_func); + if (p) + return p->data; + } + return NULL; +} + +static int sdp_send_req(sdp_session_t *session, uint8_t *buf, uint32_t size) +{ + uint32_t sent = 0; + + while (sent < size) { + int n = send(session->sock, buf + sent, size - sent, 0); + if (n < 0) + return -1; + sent += n; + } + return 0; +} + +static int sdp_read_rsp(sdp_session_t *session, uint8_t *buf, uint32_t size) +{ + fd_set readFds; + struct timeval timeout = { SDP_RESPONSE_TIMEOUT, 0 }; + + FD_ZERO(&readFds); + FD_SET(session->sock, &readFds); + SDPDBG("Waiting for response"); + if (select(session->sock + 1, &readFds, NULL, NULL, &timeout) == 0) { + SDPERR("Client timed out"); + errno = ETIMEDOUT; + return -1; + } + return recv(session->sock, buf, size, 0); +} + +/* + * generic send request, wait for response method. + */ +int sdp_send_req_w4_rsp(sdp_session_t *session, uint8_t *reqbuf, + uint8_t *rspbuf, uint32_t reqsize, uint32_t *rspsize) +{ + int n; + sdp_pdu_hdr_t *reqhdr = (sdp_pdu_hdr_t *) reqbuf; + sdp_pdu_hdr_t *rsphdr = (sdp_pdu_hdr_t *) rspbuf; + + SDPDBG(""); + if (0 > sdp_send_req(session, reqbuf, reqsize)) { + SDPERR("Error sending data:%m"); + return -1; + } + n = sdp_read_rsp(session, rspbuf, SDP_RSP_BUFFER_SIZE); + if (0 > n) + return -1; + SDPDBG("Read : %d", n); + if (n == 0 || reqhdr->tid != rsphdr->tid) { + errno = EPROTO; + return -1; + } + *rspsize = n; + return 0; +} + +/* + * singly-linked lists (after openobex implementation) + */ +sdp_list_t *sdp_list_append(sdp_list_t *p, void *d) +{ + sdp_list_t *q, *n = malloc(sizeof(sdp_list_t)); + + if (!n) + return NULL; + + n->data = d; + n->next = 0; + + if (!p) + return n; + + for (q = p; q->next; q = q->next); + q->next = n; + + return p; +} + +sdp_list_t *sdp_list_remove(sdp_list_t *list, void *d) +{ + sdp_list_t *p, *q; + + for (q = 0, p = list; p; q = p, p = p->next) + if (p->data == d) { + if (q) + q->next = p->next; + else + list = p->next; + free(p); + break; + } + + return list; +} + +sdp_list_t *sdp_list_insert_sorted(sdp_list_t *list, void *d, + sdp_comp_func_t f) +{ + sdp_list_t *q, *p, *n; + + n = malloc(sizeof(sdp_list_t)); + if (!n) + return NULL; + n->data = d; + for (q = 0, p = list; p; q = p, p = p->next) + if (f(p->data, d) >= 0) + break; + /* insert between q and p; if !q insert at head */ + if (q) + q->next = n; + else + list = n; + n->next = p; + return list; +} + +/* + * Every element of the list points to things which need + * to be free()'d. This method frees the list's contents + */ +void sdp_list_free(sdp_list_t *list, sdp_free_func_t f) +{ + sdp_list_t *next; + while (list) { + next = list->next; + if (f) + f(list->data); + free(list); + list = next; + } +} + +static inline int __find_port(sdp_data_t *seq, int proto) +{ + if (!seq || !seq->next) + return 0; + + if (SDP_IS_UUID(seq->dtd) && sdp_uuid_to_proto(&seq->val.uuid) == proto) { + seq = seq->next; + switch (seq->dtd) { + case SDP_UINT8: + return seq->val.uint8; + case SDP_UINT16: + return seq->val.uint16; + } + } + return 0; +} + +int sdp_get_proto_port(const sdp_list_t *list, int proto) +{ + if (proto != L2CAP_UUID && proto != RFCOMM_UUID) { + errno = EINVAL; + return -1; + } + + for (; list; list = list->next) { + sdp_list_t *p; + for (p = list->data; p; p = p->next) { + sdp_data_t *seq = p->data; + int port = __find_port(seq, proto); + if (port) + return port; + } + } + return 0; +} + +sdp_data_t *sdp_get_proto_desc(sdp_list_t *list, int proto) +{ + for (; list; list = list->next) { + sdp_list_t *p; + for (p = list->data; p; p = p->next) { + sdp_data_t *seq = p->data; + if (SDP_IS_UUID(seq->dtd) && + sdp_uuid_to_proto(&seq->val.uuid) == proto) + return seq->next; + } + } + return NULL; +} + +static int sdp_get_proto_descs(uint16_t attr_id, const sdp_record_t *rec, + sdp_list_t **pap) +{ + sdp_data_t *pdlist, *curr; + sdp_list_t *ap = NULL; + + pdlist = sdp_data_get(rec, attr_id); + if (pdlist == NULL) { + errno = ENODATA; + return -1; + } + + SDPDBG("Attribute value type: 0x%02x", pdlist->dtd); + + if (attr_id == SDP_ATTR_ADD_PROTO_DESC_LIST) { + if (!SDP_IS_SEQ(pdlist->dtd)) { + errno = EINVAL; + return -1; + } + pdlist = pdlist->val.dataseq; + } + + for (; pdlist; pdlist = pdlist->next) { + sdp_list_t *pds = NULL; + + if (!SDP_IS_SEQ(pdlist->dtd) && !SDP_IS_ALT(pdlist->dtd)) + goto failed; + + for (curr = pdlist->val.dataseq; curr; curr = curr->next) { + if (!SDP_IS_SEQ(curr->dtd)) { + sdp_list_free(pds, NULL); + goto failed; + } + pds = sdp_list_append(pds, curr->val.dataseq); + } + + ap = sdp_list_append(ap, pds); + } + + *pap = ap; + + return 0; + +failed: + sdp_list_foreach(ap, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(ap, NULL); + errno = EINVAL; + + return -1; +} + +int sdp_get_access_protos(const sdp_record_t *rec, sdp_list_t **pap) +{ + return sdp_get_proto_descs(SDP_ATTR_PROTO_DESC_LIST, rec, pap); +} + +int sdp_get_add_access_protos(const sdp_record_t *rec, sdp_list_t **pap) +{ + return sdp_get_proto_descs(SDP_ATTR_ADD_PROTO_DESC_LIST, rec, pap); +} + +int sdp_get_uuidseq_attr(const sdp_record_t *rec, uint16_t attr, + sdp_list_t **seqp) +{ + sdp_data_t *sdpdata = sdp_data_get(rec, attr); + + *seqp = NULL; + if (sdpdata && SDP_IS_SEQ(sdpdata->dtd)) { + sdp_data_t *d; + for (d = sdpdata->val.dataseq; d; d = d->next) { + uuid_t *u; + if (d->dtd < SDP_UUID16 || d->dtd > SDP_UUID128) { + errno = EINVAL; + goto fail; + } + + u = malloc(sizeof(uuid_t)); + if (!u) + goto fail; + + *u = d->val.uuid; + *seqp = sdp_list_append(*seqp, u); + } + return 0; + } +fail: + sdp_list_free(*seqp, free); + *seqp = NULL; + return -1; +} + +int sdp_set_uuidseq_attr(sdp_record_t *rec, uint16_t aid, sdp_list_t *seq) +{ + int status = 0, i, len; + void **dtds, **values; + uint8_t uuid16 = SDP_UUID16; + uint8_t uuid32 = SDP_UUID32; + uint8_t uuid128 = SDP_UUID128; + sdp_list_t *p; + + len = sdp_list_len(seq); + if (!seq || len == 0) + return -1; + dtds = malloc(len * sizeof(void *)); + if (!dtds) + return -1; + + values = malloc(len * sizeof(void *)); + if (!values) { + free(dtds); + return -1; + } + + for (p = seq, i = 0; i < len; i++, p = p->next) { + uuid_t *uuid = p->data; + if (uuid) + switch (uuid->type) { + case SDP_UUID16: + dtds[i] = &uuid16; + values[i] = &uuid->value.uuid16; + break; + case SDP_UUID32: + dtds[i] = &uuid32; + values[i] = &uuid->value.uuid32; + break; + case SDP_UUID128: + dtds[i] = &uuid128; + values[i] = &uuid->value.uuid128; + break; + default: + status = -1; + break; + } + else { + status = -1; + break; + } + } + if (status == 0) { + sdp_data_t *data = sdp_seq_alloc(dtds, values, len); + sdp_attr_replace(rec, aid, data); + sdp_pattern_add_uuidseq(rec, seq); + } + free(dtds); + free(values); + return status; +} + +int sdp_get_lang_attr(const sdp_record_t *rec, sdp_list_t **langSeq) +{ + sdp_lang_attr_t *lang; + sdp_data_t *sdpdata, *curr_data; + + *langSeq = NULL; + sdpdata = sdp_data_get(rec, SDP_ATTR_LANG_BASE_ATTR_ID_LIST); + if (sdpdata == NULL) { + errno = ENODATA; + return -1; + } + + if (!SDP_IS_SEQ(sdpdata->dtd)) + goto invalid; + curr_data = sdpdata->val.dataseq; + + while (curr_data) { + sdp_data_t *pCode, *pEncoding, *pOffset; + + pCode = curr_data; + if (pCode->dtd != SDP_UINT16) + goto invalid; + + /* LanguageBaseAttributeIDList entries are always grouped as + * triplets */ + if (!pCode->next || !pCode->next->next) + goto invalid; + + pEncoding = pCode->next; + if (pEncoding->dtd != SDP_UINT16) + goto invalid; + + pOffset = pEncoding->next; + if (pOffset->dtd != SDP_UINT16) + goto invalid; + + lang = malloc(sizeof(sdp_lang_attr_t)); + if (!lang) { + sdp_list_free(*langSeq, free); + *langSeq = NULL; + return -1; + } + lang->code_ISO639 = pCode->val.uint16; + lang->encoding = pEncoding->val.uint16; + lang->base_offset = pOffset->val.uint16; + SDPDBG("code_ISO639 : 0x%02x", lang->code_ISO639); + SDPDBG("encoding : 0x%02x", lang->encoding); + SDPDBG("base_offfset : 0x%02x", lang->base_offset); + *langSeq = sdp_list_append(*langSeq, lang); + + curr_data = pOffset->next; + } + + return 0; + +invalid: + sdp_list_free(*langSeq, free); + *langSeq = NULL; + errno = EINVAL; + + return -1; +} + +int sdp_get_profile_descs(const sdp_record_t *rec, sdp_list_t **profDescSeq) +{ + sdp_profile_desc_t *profDesc; + sdp_data_t *sdpdata, *seq; + + *profDescSeq = NULL; + sdpdata = sdp_data_get(rec, SDP_ATTR_PFILE_DESC_LIST); + if (sdpdata == NULL) { + errno = ENODATA; + return -1; + } + + if (!SDP_IS_SEQ(sdpdata->dtd) || sdpdata->val.dataseq == NULL) + goto invalid; + + for (seq = sdpdata->val.dataseq; seq; seq = seq->next) { + uuid_t *uuid = NULL; + uint16_t version = 0x100; + + if (SDP_IS_UUID(seq->dtd)) { + /* Mac OS X 10.7.3 and old Samsung phones do not comply + * to the SDP specification for + * BluetoothProfileDescriptorList. This workaround + * allows to properly parse UUID/version from SDP + * record published by these systems. */ + sdp_data_t *next = seq->next; + uuid = &seq->val.uuid; + if (next && next->dtd == SDP_UINT16) { + version = next->val.uint16; + seq = next; + } + } else if (SDP_IS_SEQ(seq->dtd)) { + sdp_data_t *puuid, *pVnum; + + puuid = seq->val.dataseq; + if (puuid == NULL || !SDP_IS_UUID(puuid->dtd)) + goto invalid; + + uuid = &puuid->val.uuid; + + pVnum = puuid->next; + if (pVnum == NULL || pVnum->dtd != SDP_UINT16) + goto invalid; + + version = pVnum->val.uint16; + } else + goto invalid; + + if (uuid != NULL) { + profDesc = malloc(sizeof(sdp_profile_desc_t)); + if (!profDesc) { + sdp_list_free(*profDescSeq, free); + *profDescSeq = NULL; + return -1; + } + profDesc->uuid = *uuid; + profDesc->version = version; +#ifdef SDP_DEBUG + sdp_uuid_print(&profDesc->uuid); + SDPDBG("Vnum : 0x%04x", profDesc->version); +#endif + *profDescSeq = sdp_list_append(*profDescSeq, profDesc); + } + } + return 0; + +invalid: + sdp_list_free(*profDescSeq, free); + *profDescSeq = NULL; + errno = EINVAL; + + return -1; +} + +int sdp_get_server_ver(const sdp_record_t *rec, sdp_list_t **u16) +{ + sdp_data_t *d, *curr; + + *u16 = NULL; + d = sdp_data_get(rec, SDP_ATTR_VERSION_NUM_LIST); + if (d == NULL) { + errno = ENODATA; + return -1; + } + + if (!SDP_IS_SEQ(d->dtd) || d->val.dataseq == NULL) + goto invalid; + + for (curr = d->val.dataseq; curr; curr = curr->next) { + if (curr->dtd != SDP_UINT16) + goto invalid; + *u16 = sdp_list_append(*u16, &curr->val.uint16); + } + + return 0; + +invalid: + sdp_list_free(*u16, NULL); + *u16 = NULL; + errno = EINVAL; + + return -1; +} + +/* flexible extraction of basic attributes - Jean II */ +/* How do we expect caller to extract predefined data sequences? */ +int sdp_get_int_attr(const sdp_record_t *rec, uint16_t attrid, int *value) +{ + sdp_data_t *sdpdata = sdp_data_get(rec, attrid); + + if (sdpdata) + /* Verify that it is what the caller expects */ + if (sdpdata->dtd == SDP_BOOL || sdpdata->dtd == SDP_UINT8 || + sdpdata->dtd == SDP_UINT16 || sdpdata->dtd == SDP_UINT32 || + sdpdata->dtd == SDP_INT8 || sdpdata->dtd == SDP_INT16 || + sdpdata->dtd == SDP_INT32) { + *value = sdpdata->val.uint32; + return 0; + } + errno = EINVAL; + return -1; +} + +int sdp_get_string_attr(const sdp_record_t *rec, uint16_t attrid, char *value, + int valuelen) +{ + sdp_data_t *sdpdata = sdp_data_get(rec, attrid); + if (sdpdata) + /* Verify that it is what the caller expects */ + if (SDP_IS_TEXT_STR(sdpdata->dtd)) + if ((int) strlen(sdpdata->val.str) < valuelen) { + strcpy(value, sdpdata->val.str); + return 0; + } + errno = EINVAL; + return -1; +} + +#define get_basic_attr(attrID, pAttrValue, fieldName) \ + sdp_data_t *data = sdp_data_get(rec, attrID); \ + if (data) { \ + *pAttrValue = data->val.fieldName; \ + return 0; \ + } \ + errno = EINVAL; \ + return -1; + +int sdp_get_service_id(const sdp_record_t *rec, uuid_t *uuid) +{ + get_basic_attr(SDP_ATTR_SERVICE_ID, uuid, uuid); +} + +int sdp_get_group_id(const sdp_record_t *rec, uuid_t *uuid) +{ + get_basic_attr(SDP_ATTR_GROUP_ID, uuid, uuid); +} + +int sdp_get_record_state(const sdp_record_t *rec, uint32_t *svcRecState) +{ + get_basic_attr(SDP_ATTR_RECORD_STATE, svcRecState, uint32); +} + +int sdp_get_service_avail(const sdp_record_t *rec, uint8_t *svcAvail) +{ + get_basic_attr(SDP_ATTR_SERVICE_AVAILABILITY, svcAvail, uint8); +} + +int sdp_get_service_ttl(const sdp_record_t *rec, uint32_t *svcTTLInfo) +{ + get_basic_attr(SDP_ATTR_SVCINFO_TTL, svcTTLInfo, uint32); +} + +int sdp_get_database_state(const sdp_record_t *rec, uint32_t *svcDBState) +{ + get_basic_attr(SDP_ATTR_SVCDB_STATE, svcDBState, uint32); +} + +/* + * NOTE that none of the setXXX() functions below will + * actually update the SDP server, unless the + * {register, update}sdp_record_t() function is invoked. + */ + +int sdp_attr_add_new(sdp_record_t *rec, uint16_t attr, uint8_t dtd, + const void *value) +{ + sdp_data_t *d = sdp_data_alloc(dtd, value); + if (d) { + sdp_attr_replace(rec, attr, d); + return 0; + } + return -1; +} + +static int sdp_attr_add_new_with_length(sdp_record_t *rec, + uint16_t attr, uint8_t dtd, const void *value, uint32_t len) +{ + sdp_data_t *d; + + d = sdp_data_alloc_with_length(dtd, value, len); + if (!d) + return -1; + + sdp_attr_replace(rec, attr, d); + + return 0; +} + +/* + * Set the information attributes of the service + * pointed to by rec. The attributes are + * service name, description and provider name + */ +void sdp_set_info_attr(sdp_record_t *rec, const char *name, const char *prov, + const char *desc) +{ + if (name) + sdp_attr_add_new(rec, SDP_ATTR_SVCNAME_PRIMARY, + SDP_TEXT_STR8, name); + if (prov) + sdp_attr_add_new(rec, SDP_ATTR_PROVNAME_PRIMARY, + SDP_TEXT_STR8, prov); + if (desc) + sdp_attr_add_new(rec, SDP_ATTR_SVCDESC_PRIMARY, + SDP_TEXT_STR8, desc); +} + +static sdp_data_t *access_proto_to_dataseq(sdp_record_t *rec, sdp_list_t *proto) +{ + sdp_data_t *seq = NULL; + void *dtds[10], *values[10]; + void **seqDTDs, **seqs; + int i, seqlen; + sdp_list_t *p; + + seqlen = sdp_list_len(proto); + seqDTDs = malloc(seqlen * sizeof(void *)); + if (!seqDTDs) + return NULL; + + seqs = malloc(seqlen * sizeof(void *)); + if (!seqs) { + free(seqDTDs); + return NULL; + } + + for (i = 0, p = proto; p; p = p->next, i++) { + sdp_list_t *elt = p->data; + sdp_data_t *s; + uuid_t *uuid = NULL; + unsigned int pslen = 0; + for (; elt && pslen < ARRAY_SIZE(dtds); elt = elt->next, pslen++) { + sdp_data_t *d = elt->data; + dtds[pslen] = &d->dtd; + switch (d->dtd) { + case SDP_UUID16: + uuid = (uuid_t *) d; + values[pslen] = &uuid->value.uuid16; + break; + case SDP_UUID32: + uuid = (uuid_t *) d; + values[pslen] = &uuid->value.uuid32; + break; + case SDP_UUID128: + uuid = (uuid_t *) d; + values[pslen] = &uuid->value.uuid128; + break; + case SDP_UINT8: + values[pslen] = &d->val.uint8; + break; + case SDP_UINT16: + values[pslen] = &d->val.uint16; + break; + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + values[pslen] = d; + break; + /* FIXME: more */ + } + } + s = sdp_seq_alloc(dtds, values, pslen); + if (s) { + seqDTDs[i] = &s->dtd; + seqs[i] = s; + if (uuid) + sdp_pattern_add_uuid(rec, uuid); + } + } + seq = sdp_seq_alloc(seqDTDs, seqs, seqlen); + free(seqDTDs); + free(seqs); + return seq; +} + +/* + * sets the access protocols of the service specified + * to the value specified in "access_proto" + * + * Note that if there are alternate mechanisms by + * which the service is accessed, then they should + * be specified as sequences + * + * Using a value of NULL for accessProtocols has + * effect of removing this attribute (if previously set) + * + * This function replaces the existing sdp_access_proto_t + * structure (if any) with the new one specified. + * + * returns 0 if successful or -1 if there is a failure. + */ +int sdp_set_access_protos(sdp_record_t *rec, const sdp_list_t *ap) +{ + const sdp_list_t *p; + sdp_data_t *protos = NULL; + + for (p = ap; p; p = p->next) { + sdp_data_t *seq = access_proto_to_dataseq(rec, p->data); + protos = sdp_seq_append(protos, seq); + } + + sdp_attr_add(rec, SDP_ATTR_PROTO_DESC_LIST, protos); + + return 0; +} + +int sdp_set_add_access_protos(sdp_record_t *rec, const sdp_list_t *ap) +{ + const sdp_list_t *p; + sdp_data_t *protos = NULL; + + for (p = ap; p; p = p->next) { + sdp_data_t *seq = access_proto_to_dataseq(rec, p->data); + protos = sdp_seq_append(protos, seq); + } + + sdp_attr_add(rec, SDP_ATTR_ADD_PROTO_DESC_LIST, + protos ? sdp_data_alloc(SDP_SEQ8, protos) : NULL); + + return 0; +} + +/* + * set the "LanguageBase" attributes of the service record + * record to the value specified in "langAttrList". + * + * "langAttrList" is a linked list of "sdp_lang_attr_t" + * objects, one for each language in which user visible + * attributes are present in the service record. + * + * Using a value of NULL for langAttrList has + * effect of removing this attribute (if previously set) + * + * This function replaces the exisiting sdp_lang_attr_t + * structure (if any) with the new one specified. + * + * returns 0 if successful or -1 if there is a failure. + */ +int sdp_set_lang_attr(sdp_record_t *rec, const sdp_list_t *seq) +{ + uint8_t uint16 = SDP_UINT16; + int status = 0, i = 0, seqlen = sdp_list_len(seq); + void **dtds, **values; + const sdp_list_t *p; + + dtds = malloc(3 * seqlen * sizeof(void *)); + if (!dtds) + return -1; + + values = malloc(3 * seqlen * sizeof(void *)); + if (!values) { + free(dtds); + return -1; + } + + for (p = seq; p; p = p->next) { + sdp_lang_attr_t *lang = p->data; + if (!lang) { + status = -1; + break; + } + dtds[i] = &uint16; + values[i] = &lang->code_ISO639; + i++; + dtds[i] = &uint16; + values[i] = &lang->encoding; + i++; + dtds[i] = &uint16; + values[i] = &lang->base_offset; + i++; + } + if (status == 0) { + sdp_data_t *seq = sdp_seq_alloc(dtds, values, 3 * seqlen); + sdp_attr_add(rec, SDP_ATTR_LANG_BASE_ATTR_ID_LIST, seq); + } + free(dtds); + free(values); + return status; +} + +/* + * set the "ServiceID" attribute of the service. + * + * This is the UUID of the service. + * + * returns 0 if successful or -1 if there is a failure. + */ +void sdp_set_service_id(sdp_record_t *rec, uuid_t uuid) +{ + switch (uuid.type) { + case SDP_UUID16: + sdp_attr_add_new(rec, SDP_ATTR_SERVICE_ID, SDP_UUID16, + &uuid.value.uuid16); + break; + case SDP_UUID32: + sdp_attr_add_new(rec, SDP_ATTR_SERVICE_ID, SDP_UUID32, + &uuid.value.uuid32); + break; + case SDP_UUID128: + sdp_attr_add_new(rec, SDP_ATTR_SERVICE_ID, SDP_UUID128, + &uuid.value.uuid128); + break; + } + sdp_pattern_add_uuid(rec, &uuid); +} + +/* + * set the GroupID attribute of the service record defining a group. + * + * This is the UUID of the group. + * + * returns 0 if successful or -1 if there is a failure. + */ +void sdp_set_group_id(sdp_record_t *rec, uuid_t uuid) +{ + switch (uuid.type) { + case SDP_UUID16: + sdp_attr_add_new(rec, SDP_ATTR_GROUP_ID, SDP_UUID16, + &uuid.value.uuid16); + break; + case SDP_UUID32: + sdp_attr_add_new(rec, SDP_ATTR_GROUP_ID, SDP_UUID32, + &uuid.value.uuid32); + break; + case SDP_UUID128: + sdp_attr_add_new(rec, SDP_ATTR_GROUP_ID, SDP_UUID128, + &uuid.value.uuid128); + break; + } + sdp_pattern_add_uuid(rec, &uuid); +} + +/* + * set the ProfileDescriptorList attribute of the service record + * pointed to by record to the value specified in "profileDesc". + * + * Each element in the list is an object of type + * sdp_profile_desc_t which is a definition of the + * Bluetooth profile that this service conforms to. + * + * Using a value of NULL for profileDesc has + * effect of removing this attribute (if previously set) + * + * This function replaces the exisiting ProfileDescriptorList + * structure (if any) with the new one specified. + * + * returns 0 if successful or -1 if there is a failure. + */ +int sdp_set_profile_descs(sdp_record_t *rec, const sdp_list_t *profiles) +{ + int status = 0; + uint8_t uuid16 = SDP_UUID16; + uint8_t uuid32 = SDP_UUID32; + uint8_t uuid128 = SDP_UUID128; + uint8_t uint16 = SDP_UINT16; + int i = 0, seqlen = sdp_list_len(profiles); + void **seqDTDs, **seqs; + const sdp_list_t *p; + sdp_data_t *pAPSeq; + + seqDTDs = malloc(seqlen * sizeof(void *)); + if (!seqDTDs) + return -1; + + seqs = malloc(seqlen * sizeof(void *)); + if (!seqs) { + free(seqDTDs); + return -1; + } + + for (p = profiles; p; p = p->next) { + sdp_data_t *seq; + void *dtds[2], *values[2]; + sdp_profile_desc_t *profile = p->data; + if (!profile) { + status = -1; + goto end; + } + switch (profile->uuid.type) { + case SDP_UUID16: + dtds[0] = &uuid16; + values[0] = &profile->uuid.value.uuid16; + break; + case SDP_UUID32: + dtds[0] = &uuid32; + values[0] = &profile->uuid.value.uuid32; + break; + case SDP_UUID128: + dtds[0] = &uuid128; + values[0] = &profile->uuid.value.uuid128; + break; + default: + status = -1; + goto end; + } + dtds[1] = &uint16; + values[1] = &profile->version; + seq = sdp_seq_alloc(dtds, values, 2); + + if (seq == NULL) { + status = -1; + goto end; + } + + seqDTDs[i] = &seq->dtd; + seqs[i] = seq; + sdp_pattern_add_uuid(rec, &profile->uuid); + i++; + } + + pAPSeq = sdp_seq_alloc(seqDTDs, seqs, seqlen); + sdp_attr_add(rec, SDP_ATTR_PFILE_DESC_LIST, pAPSeq); +end: + free(seqDTDs); + free(seqs); + return status; +} + +/* + * sets various URL attributes of the service + * pointed to by record. The URL include + * + * client: a URL to the client's + * platform specific (WinCE, PalmOS) executable + * code that can be used to access this service. + * + * doc: a URL pointing to service documentation + * + * icon: a URL to an icon that can be used to represent + * this service. + * + * Note that you need to pass NULL for any URLs + * that you don't want to set or remove + */ +void sdp_set_url_attr(sdp_record_t *rec, const char *client, const char *doc, + const char *icon) +{ + sdp_attr_add_new(rec, SDP_ATTR_CLNT_EXEC_URL, SDP_URL_STR8, client); + sdp_attr_add_new(rec, SDP_ATTR_DOC_URL, SDP_URL_STR8, doc); + sdp_attr_add_new(rec, SDP_ATTR_ICON_URL, SDP_URL_STR8, icon); +} + +uuid_t *sdp_uuid16_create(uuid_t *u, uint16_t val) +{ + memset(u, 0, sizeof(uuid_t)); + u->type = SDP_UUID16; + u->value.uuid16 = val; + return u; +} + +uuid_t *sdp_uuid32_create(uuid_t *u, uint32_t val) +{ + memset(u, 0, sizeof(uuid_t)); + u->type = SDP_UUID32; + u->value.uuid32 = val; + return u; +} + +uuid_t *sdp_uuid128_create(uuid_t *u, const void *val) +{ + memset(u, 0, sizeof(uuid_t)); + u->type = SDP_UUID128; + memcpy(&u->value.uuid128, val, sizeof(uint128_t)); + return u; +} + +/* + * UUID comparison function + * returns 0 if uuidValue1 == uuidValue2 else -1 + */ +int sdp_uuid_cmp(const void *p1, const void *p2) +{ + uuid_t *u1 = sdp_uuid_to_uuid128(p1); + uuid_t *u2 = sdp_uuid_to_uuid128(p2); + int ret; + + ret = sdp_uuid128_cmp(u1, u2); + + bt_free(u1); + bt_free(u2); + + return ret; +} + +/* + * UUID comparison function + * returns 0 if uuidValue1 == uuidValue2 else -1 + */ +int sdp_uuid16_cmp(const void *p1, const void *p2) +{ + const uuid_t *u1 = p1; + const uuid_t *u2 = p2; + return memcmp(&u1->value.uuid16, &u2->value.uuid16, sizeof(uint16_t)); +} + +/* + * UUID comparison function + * returns 0 if uuidValue1 == uuidValue2 else -1 + */ +int sdp_uuid128_cmp(const void *p1, const void *p2) +{ + const uuid_t *u1 = p1; + const uuid_t *u2 = p2; + return memcmp(&u1->value.uuid128, &u2->value.uuid128, sizeof(uint128_t)); +} + +/* + * 128 to 16 bit and 32 to 16 bit UUID conversion functions + * yet to be implemented. Note that the input is in NBO in + * both 32 and 128 bit UUIDs and conversion is needed + */ +void sdp_uuid16_to_uuid128(uuid_t *uuid128, const uuid_t *uuid16) +{ + /* + * We have a 16 bit value, which needs to be added to + * bytes 3 and 4 (at indices 2 and 3) of the Bluetooth base + */ + unsigned short data1; + + /* allocate a 128bit UUID and init to the Bluetooth base UUID */ + uuid128->value.uuid128 = bluetooth_base_uuid; + uuid128->type = SDP_UUID128; + + /* extract bytes 2 and 3 of 128bit BT base UUID */ + memcpy(&data1, &bluetooth_base_uuid.data[2], 2); + + /* add the given UUID (16 bits) */ + data1 += htons(uuid16->value.uuid16); + + /* set bytes 2 and 3 of the 128 bit value */ + memcpy(&uuid128->value.uuid128.data[2], &data1, 2); +} + +void sdp_uuid32_to_uuid128(uuid_t *uuid128, const uuid_t *uuid32) +{ + /* + * We have a 32 bit value, which needs to be added to + * bytes 1->4 (at indices 0 thru 3) of the Bluetooth base + */ + unsigned int data0; + + /* allocate a 128bit UUID and init to the Bluetooth base UUID */ + uuid128->value.uuid128 = bluetooth_base_uuid; + uuid128->type = SDP_UUID128; + + /* extract first 4 bytes */ + memcpy(&data0, &bluetooth_base_uuid.data[0], 4); + + /* add the given UUID (32bits) */ + data0 += htonl(uuid32->value.uuid32); + + /* set the 4 bytes of the 128 bit value */ + memcpy(&uuid128->value.uuid128.data[0], &data0, 4); +} + +uuid_t *sdp_uuid_to_uuid128(const uuid_t *uuid) +{ + uuid_t *uuid128 = bt_malloc(sizeof(uuid_t)); + + if (!uuid128) + return NULL; + + memset(uuid128, 0, sizeof(uuid_t)); + switch (uuid->type) { + case SDP_UUID128: + *uuid128 = *uuid; + break; + case SDP_UUID32: + sdp_uuid32_to_uuid128(uuid128, uuid); + break; + case SDP_UUID16: + sdp_uuid16_to_uuid128(uuid128, uuid); + break; + } + return uuid128; +} + +/* + * converts a 128-bit uuid to a 16/32-bit one if possible + * returns true if uuid contains a 16/32-bit UUID at exit + */ +int sdp_uuid128_to_uuid(uuid_t *uuid) +{ + uint128_t *b = &bluetooth_base_uuid; + uint128_t *u = &uuid->value.uuid128; + uint32_t data; + unsigned int i; + + if (uuid->type != SDP_UUID128) + return 1; + + for (i = 4; i < sizeof(b->data); i++) + if (b->data[i] != u->data[i]) + return 0; + + memcpy(&data, u->data, 4); + data = htonl(data); + if (data <= 0xffff) { + uuid->type = SDP_UUID16; + uuid->value.uuid16 = (uint16_t) data; + } else { + uuid->type = SDP_UUID32; + uuid->value.uuid32 = data; + } + return 1; +} + +/* + * convert a UUID to the 16-bit short-form + */ +int sdp_uuid_to_proto(uuid_t *uuid) +{ + uuid_t u = *uuid; + if (sdp_uuid128_to_uuid(&u)) { + switch (u.type) { + case SDP_UUID16: + return u.value.uuid16; + case SDP_UUID32: + return u.value.uuid32; + } + } + return 0; +} + +/* + * This function appends data to the PDU buffer "dst" from source "src". + * The data length is also computed and set. + * Should the PDU length exceed 2^8, then sequence type is + * set accordingly and the data is memmove()'d. + */ +void sdp_append_to_buf(sdp_buf_t *dst, uint8_t *data, uint32_t len) +{ + uint8_t *p = dst->data; + uint8_t dtd = *p; + + SDPDBG("Append src size: %d", len); + SDPDBG("Append dst size: %d", dst->data_size); + SDPDBG("Dst buffer size: %d", dst->buf_size); + + if (dst->data_size + len > dst->buf_size) { + SDPERR("Cannot append"); + return; + } + + if (dst->data_size == 0 && dtd == 0) { + /* create initial sequence */ + *p = SDP_SEQ8; + dst->data_size += sizeof(uint8_t); + /* reserve space for sequence size */ + dst->data_size += sizeof(uint8_t); + } + + memcpy(dst->data + dst->data_size, data, len); + dst->data_size += len; + + dtd = *(uint8_t *) dst->data; + if (dst->data_size > UCHAR_MAX && dtd == SDP_SEQ8) { + short offset = sizeof(uint8_t) + sizeof(uint8_t); + memmove(dst->data + offset + 1, dst->data + offset, + dst->data_size - offset); + *p = SDP_SEQ16; + dst->data_size += 1; + } + dtd = *(uint8_t *) p; + p += sizeof(uint8_t); + switch (dtd) { + case SDP_SEQ8: + *(uint8_t *) p = dst->data_size - sizeof(uint8_t) - sizeof(uint8_t); + break; + case SDP_SEQ16: + bt_put_be16(dst->data_size - sizeof(uint8_t) - sizeof(uint16_t), p); + break; + case SDP_SEQ32: + bt_put_be32(dst->data_size - sizeof(uint8_t) - sizeof(uint32_t), p); + break; + } +} + +void sdp_append_to_pdu(sdp_buf_t *pdu, sdp_data_t *d) +{ + sdp_buf_t append; + + memset(&append, 0, sizeof(sdp_buf_t)); + sdp_gen_buffer(&append, d); + append.data = malloc(append.buf_size); + if (!append.data) + return; + + sdp_set_attrid(&append, d->attrId); + sdp_gen_pdu(&append, d); + sdp_append_to_buf(pdu, append.data, append.data_size); + free(append.data); +} + +/* + * Registers an sdp record. + * + * It is incorrect to call this method on a record that + * has been already registered with the server. + * + * Returns zero on success, otherwise -1 (and sets errno). + */ +int sdp_device_record_register_binary(sdp_session_t *session, bdaddr_t *device, uint8_t *data, uint32_t size, uint8_t flags, uint32_t *handle) +{ + uint8_t *req, *rsp, *p; + uint32_t reqsize, rspsize; + sdp_pdu_hdr_t *reqhdr, *rsphdr; + int status; + + SDPDBG(""); + + if (!session->local) { + errno = EREMOTE; + return -1; + } + req = malloc(SDP_REQ_BUFFER_SIZE); + rsp = malloc(SDP_RSP_BUFFER_SIZE); + if (req == NULL || rsp == NULL) { + status = -1; + errno = ENOMEM; + goto end; + } + + reqhdr = (sdp_pdu_hdr_t *)req; + reqhdr->pdu_id = SDP_SVC_REGISTER_REQ; + reqhdr->tid = htons(sdp_gen_tid(session)); + reqsize = sizeof(sdp_pdu_hdr_t) + 1; + p = req + sizeof(sdp_pdu_hdr_t); + + if (bacmp(device, BDADDR_ANY)) { + *p++ = flags | SDP_DEVICE_RECORD; + bacpy((bdaddr_t *) p, device); + p += sizeof(bdaddr_t); + reqsize += sizeof(bdaddr_t); + } else + *p++ = flags; + + memcpy(p, data, size); + reqsize += size; + reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t)); + + status = sdp_send_req_w4_rsp(session, req, rsp, reqsize, &rspsize); + if (status < 0) + goto end; + + if (rspsize < sizeof(sdp_pdu_hdr_t)) { + SDPERR("Unexpected end of packet"); + errno = EPROTO; + status = -1; + goto end; + } + + rsphdr = (sdp_pdu_hdr_t *) rsp; + p = rsp + sizeof(sdp_pdu_hdr_t); + + if (rsphdr->pdu_id == SDP_ERROR_RSP) { + /* Invalid service record */ + errno = EINVAL; + status = -1; + } else if (rsphdr->pdu_id != SDP_SVC_REGISTER_RSP) { + errno = EPROTO; + status = -1; + } else { + if (rspsize < sizeof(sdp_pdu_hdr_t) + sizeof(uint32_t)) { + SDPERR("Unexpected end of packet"); + errno = EPROTO; + status = -1; + goto end; + } + if (handle) + *handle = bt_get_be32(p); + } + +end: + free(req); + free(rsp); + + return status; +} + +int sdp_device_record_register(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec, uint8_t flags) +{ + sdp_buf_t pdu; + uint32_t handle; + int err; + + SDPDBG(""); + + if (rec->handle && rec->handle != 0xffffffff) { + uint32_t handle = rec->handle; + sdp_data_t *data = sdp_data_alloc(SDP_UINT32, &handle); + sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, data); + } + + if (sdp_gen_record_pdu(rec, &pdu) < 0) { + errno = ENOMEM; + return -1; + } + + err = sdp_device_record_register_binary(session, device, + pdu.data, pdu.data_size, flags, &handle); + + free(pdu.data); + + if (err == 0) { + sdp_data_t *data = sdp_data_alloc(SDP_UINT32, &handle); + rec->handle = handle; + sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, data); + } + + return err; +} + +int sdp_record_register(sdp_session_t *session, sdp_record_t *rec, uint8_t flags) +{ + return sdp_device_record_register(session, BDADDR_ANY, rec, flags); +} + +/* + * unregister a service record + */ +int sdp_device_record_unregister_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle) +{ + uint8_t *reqbuf, *rspbuf, *p; + uint32_t reqsize = 0, rspsize = 0; + sdp_pdu_hdr_t *reqhdr, *rsphdr; + int status; + + SDPDBG(""); + + if (handle == SDP_SERVER_RECORD_HANDLE) { + errno = EINVAL; + return -1; + } + + if (!session->local) { + errno = EREMOTE; + return -1; + } + + reqbuf = malloc(SDP_REQ_BUFFER_SIZE); + rspbuf = malloc(SDP_RSP_BUFFER_SIZE); + if (!reqbuf || !rspbuf) { + errno = ENOMEM; + status = -1; + goto end; + } + reqhdr = (sdp_pdu_hdr_t *) reqbuf; + reqhdr->pdu_id = SDP_SVC_REMOVE_REQ; + reqhdr->tid = htons(sdp_gen_tid(session)); + + p = reqbuf + sizeof(sdp_pdu_hdr_t); + reqsize = sizeof(sdp_pdu_hdr_t); + bt_put_be32(handle, p); + reqsize += sizeof(uint32_t); + + reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t)); + status = sdp_send_req_w4_rsp(session, reqbuf, rspbuf, reqsize, &rspsize); + if (status < 0) + goto end; + + if (rspsize < sizeof(sdp_pdu_hdr_t) + sizeof(uint16_t)) { + SDPERR("Unexpected end of packet"); + errno = EPROTO; + status = -1; + goto end; + } + + rsphdr = (sdp_pdu_hdr_t *) rspbuf; + p = rspbuf + sizeof(sdp_pdu_hdr_t); + + if (rsphdr->pdu_id == SDP_ERROR_RSP) { + /* For this case the status always is invalid record handle */ + errno = EINVAL; + status = -1; + } else if (rsphdr->pdu_id != SDP_SVC_REMOVE_RSP) { + errno = EPROTO; + status = -1; + } else { + uint16_t tmp; + + memcpy(&tmp, p, sizeof(tmp)); + + status = tmp; + } +end: + free(reqbuf); + free(rspbuf); + + return status; +} + +int sdp_device_record_unregister(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec) +{ + int err; + + err = sdp_device_record_unregister_binary(session, device, rec->handle); + if (err == 0) + sdp_record_free(rec); + + return err; +} + +int sdp_record_unregister(sdp_session_t *session, sdp_record_t *rec) +{ + return sdp_device_record_unregister(session, BDADDR_ANY, rec); +} + +/* + * modify an existing service record + */ +int sdp_device_record_update_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle, uint8_t *data, uint32_t size) +{ + return -1; +} + +int sdp_device_record_update(sdp_session_t *session, bdaddr_t *device, const sdp_record_t *rec) +{ + uint8_t *reqbuf, *rspbuf, *p; + uint32_t reqsize, rspsize; + sdp_pdu_hdr_t *reqhdr, *rsphdr; + uint32_t handle; + sdp_buf_t pdu; + int status; + + SDPDBG(""); + + handle = rec->handle; + + if (handle == SDP_SERVER_RECORD_HANDLE) { + errno = EINVAL; + return -1; + } + if (!session->local) { + errno = EREMOTE; + return -1; + } + reqbuf = malloc(SDP_REQ_BUFFER_SIZE); + rspbuf = malloc(SDP_RSP_BUFFER_SIZE); + if (!reqbuf || !rspbuf) { + errno = ENOMEM; + status = -1; + goto end; + } + reqhdr = (sdp_pdu_hdr_t *) reqbuf; + reqhdr->pdu_id = SDP_SVC_UPDATE_REQ; + reqhdr->tid = htons(sdp_gen_tid(session)); + + p = reqbuf + sizeof(sdp_pdu_hdr_t); + reqsize = sizeof(sdp_pdu_hdr_t); + + bt_put_be32(handle, p); + reqsize += sizeof(uint32_t); + p += sizeof(uint32_t); + + if (sdp_gen_record_pdu(rec, &pdu) < 0) { + errno = ENOMEM; + status = -1; + goto end; + } + memcpy(p, pdu.data, pdu.data_size); + reqsize += pdu.data_size; + free(pdu.data); + + reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t)); + status = sdp_send_req_w4_rsp(session, reqbuf, rspbuf, reqsize, &rspsize); + if (status < 0) + goto end; + + if (rspsize < sizeof(sdp_pdu_hdr_t) + sizeof(uint16_t)) { + SDPERR("Unexpected end of packet"); + errno = EPROTO; + status = -1; + goto end; + } + + SDPDBG("Send req status : %d", status); + + rsphdr = (sdp_pdu_hdr_t *) rspbuf; + p = rspbuf + sizeof(sdp_pdu_hdr_t); + + if (rsphdr->pdu_id == SDP_ERROR_RSP) { + /* The status can be invalid sintax or invalid record handle */ + errno = EINVAL; + status = -1; + } else if (rsphdr->pdu_id != SDP_SVC_UPDATE_RSP) { + errno = EPROTO; + status = -1; + } else { + uint16_t tmp; + + memcpy(&tmp, p, sizeof(tmp)); + + status = tmp; + } +end: + free(reqbuf); + free(rspbuf); + return status; +} + +int sdp_record_update(sdp_session_t *session, const sdp_record_t *rec) +{ + return sdp_device_record_update(session, BDADDR_ANY, rec); +} + +sdp_record_t *sdp_record_alloc(void) +{ + sdp_record_t *rec = malloc(sizeof(sdp_record_t)); + + if (!rec) + return NULL; + + memset(rec, 0, sizeof(sdp_record_t)); + rec->handle = 0xffffffff; + return rec; +} + +/* + * Free the contents of a service record + */ +void sdp_record_free(sdp_record_t *rec) +{ + sdp_list_free(rec->attrlist, (sdp_free_func_t) sdp_data_free); + sdp_list_free(rec->pattern, free); + free(rec); +} + +void sdp_pattern_add_uuid(sdp_record_t *rec, uuid_t *uuid) +{ + uuid_t *uuid128 = sdp_uuid_to_uuid128(uuid); + + SDPDBG("Elements in target pattern : %d", sdp_list_len(rec->pattern)); + SDPDBG("Trying to add : 0x%lx", (unsigned long) uuid128); + + if (sdp_list_find(rec->pattern, uuid128, sdp_uuid128_cmp) == NULL) + rec->pattern = sdp_list_insert_sorted(rec->pattern, uuid128, sdp_uuid128_cmp); + else + bt_free(uuid128); + + SDPDBG("Elements in target pattern : %d", sdp_list_len(rec->pattern)); +} + +void sdp_pattern_add_uuidseq(sdp_record_t *rec, sdp_list_t *seq) +{ + for (; seq; seq = seq->next) { + uuid_t *uuid = (uuid_t *)seq->data; + sdp_pattern_add_uuid(rec, uuid); + } +} + +/* + * Extract a sequence of service record handles from a PDU buffer + * and add the entries to a sdp_list_t. Note that the service record + * handles are not in "data element sequence" form, but just like + * an array of service handles + */ +static void extract_record_handle_seq(uint8_t *pdu, int bufsize, sdp_list_t **seq, int count, unsigned int *scanned) +{ + sdp_list_t *pSeq = *seq; + uint8_t *pdata = pdu; + int n; + + for (n = 0; n < count; n++) { + uint32_t *pSvcRec; + if (bufsize < (int) sizeof(uint32_t)) { + SDPERR("Unexpected end of packet"); + break; + } + pSvcRec = malloc(sizeof(uint32_t)); + if (!pSvcRec) + break; + *pSvcRec = bt_get_be32(pdata); + pSeq = sdp_list_append(pSeq, pSvcRec); + pdata += sizeof(uint32_t); + *scanned += sizeof(uint32_t); + bufsize -= sizeof(uint32_t); + } + *seq = pSeq; +} +/* + * Generate the attribute sequence pdu form + * from sdp_list_t elements. Return length of attr seq + */ +static int gen_dataseq_pdu(uint8_t *dst, const sdp_list_t *seq, uint8_t dtd) +{ + sdp_data_t *dataseq; + void **types, **values; + sdp_buf_t buf; + int i, seqlen = sdp_list_len(seq); + + /* Fill up the value and the dtd arrays */ + SDPDBG(""); + + SDPDBG("Seq length : %d", seqlen); + + types = malloc(seqlen * sizeof(void *)); + if (!types) + return -ENOMEM; + + values = malloc(seqlen * sizeof(void *)); + if (!values) { + free(types); + return -ENOMEM; + } + + for (i = 0; i < seqlen; i++) { + void *data = seq->data; + types[i] = &dtd; + if (SDP_IS_UUID(dtd)) + data = &((uuid_t *)data)->value; + values[i] = data; + seq = seq->next; + } + + dataseq = sdp_seq_alloc(types, values, seqlen); + if (!dataseq) { + free(types); + free(values); + return -ENOMEM; + } + + memset(&buf, 0, sizeof(sdp_buf_t)); + sdp_gen_buffer(&buf, dataseq); + buf.data = malloc(buf.buf_size); + + if (!buf.data) { + sdp_data_free(dataseq); + free(types); + free(values); + return -ENOMEM; + } + + SDPDBG("Data Seq : 0x%p", seq); + seqlen = sdp_gen_pdu(&buf, dataseq); + SDPDBG("Copying : %d", buf.data_size); + memcpy(dst, buf.data, buf.data_size); + + sdp_data_free(dataseq); + + free(types); + free(values); + free(buf.data); + return seqlen; +} + +static int gen_searchseq_pdu(uint8_t *dst, const sdp_list_t *seq) +{ + uuid_t *uuid = seq->data; + return gen_dataseq_pdu(dst, seq, uuid->type); +} + +static int gen_attridseq_pdu(uint8_t *dst, const sdp_list_t *seq, uint8_t dataType) +{ + return gen_dataseq_pdu(dst, seq, dataType); +} + +typedef struct { + uint8_t length; + unsigned char data[16]; +} __attribute__ ((packed)) sdp_cstate_t; + +static int copy_cstate(uint8_t *pdata, int pdata_len, const sdp_cstate_t *cstate) +{ + if (cstate) { + uint8_t len = cstate->length; + if (len >= pdata_len) { + SDPERR("Continuation state size exceeds internal buffer"); + len = pdata_len - 1; + } + *pdata++ = len; + memcpy(pdata, cstate->data, len); + return len + 1; + } + *pdata = 0; + return 1; +} + +/* + * This is a service search request. + * + * INPUT : + * + * sdp_list_t *search + * Singly linked list containing elements of the search + * pattern. Each entry in the list is a UUID (DataTypeSDP_UUID16) + * of the service to be searched + * + * uint16_t max_rec_num + * A 16 bit integer which tells the service, the maximum + * entries that the client can handle in the response. The + * server is obliged not to return > max_rec_num entries + * + * OUTPUT : + * + * int return value + * 0: + * The request completed successfully. This does not + * mean the requested services were found + * -1: + * On any failure and sets errno + * + * sdp_list_t **rsp_list + * This variable is set on a successful return if there are + * non-zero service handles. It is a singly linked list of + * service record handles (uint16_t) + */ +int sdp_service_search_req(sdp_session_t *session, const sdp_list_t *search, + uint16_t max_rec_num, sdp_list_t **rsp) +{ + int status = 0; + uint32_t reqsize = 0, _reqsize; + uint32_t rspsize = 0, rsplen; + int seqlen = 0; + int rec_count; + unsigned scanned, pdata_len; + uint8_t *pdata, *_pdata; + uint8_t *reqbuf, *rspbuf; + sdp_pdu_hdr_t *reqhdr, *rsphdr; + sdp_cstate_t *cstate = NULL; + + reqbuf = malloc(SDP_REQ_BUFFER_SIZE); + rspbuf = malloc(SDP_RSP_BUFFER_SIZE); + if (!reqbuf || !rspbuf) { + errno = ENOMEM; + status = -1; + goto end; + } + reqhdr = (sdp_pdu_hdr_t *) reqbuf; + reqhdr->pdu_id = SDP_SVC_SEARCH_REQ; + pdata = reqbuf + sizeof(sdp_pdu_hdr_t); + reqsize = sizeof(sdp_pdu_hdr_t); + + /* add service class IDs for search */ + seqlen = gen_searchseq_pdu(pdata, search); + + SDPDBG("Data seq added : %d", seqlen); + + /* set the length and increment the pointer */ + reqsize += seqlen; + pdata += seqlen; + + /* specify the maximum svc rec count that client expects */ + bt_put_be16(max_rec_num, pdata); + reqsize += sizeof(uint16_t); + pdata += sizeof(uint16_t); + + _reqsize = reqsize; + _pdata = pdata; + *rsp = NULL; + + do { + /* Add continuation state or NULL (first time) */ + reqsize = _reqsize + copy_cstate(_pdata, + SDP_REQ_BUFFER_SIZE - _reqsize, cstate); + + /* Set the request header's param length */ + reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t)); + + reqhdr->tid = htons(sdp_gen_tid(session)); + /* + * Send the request, wait for response and if + * no error, set the appropriate values and return + */ + status = sdp_send_req_w4_rsp(session, reqbuf, rspbuf, reqsize, &rspsize); + if (status < 0) + goto end; + + if (rspsize < sizeof(sdp_pdu_hdr_t)) { + SDPERR("Unexpected end of packet"); + status = -1; + goto end; + } + + rsphdr = (sdp_pdu_hdr_t *) rspbuf; + rsplen = ntohs(rsphdr->plen); + + if (rsphdr->pdu_id == SDP_ERROR_RSP) { + SDPDBG("Status : 0x%x", rsphdr->pdu_id); + status = -1; + goto end; + } + scanned = 0; + pdata = rspbuf + sizeof(sdp_pdu_hdr_t); + pdata_len = rspsize - sizeof(sdp_pdu_hdr_t); + + if (pdata_len < sizeof(uint16_t) + sizeof(uint16_t)) { + SDPERR("Unexpected end of packet"); + status = -1; + goto end; + } + + /* net service record match count */ + pdata += sizeof(uint16_t); + scanned += sizeof(uint16_t); + pdata_len -= sizeof(uint16_t); + rec_count = bt_get_be16(pdata); + pdata += sizeof(uint16_t); + scanned += sizeof(uint16_t); + pdata_len -= sizeof(uint16_t); + + SDPDBG("Current svc count: %d", rec_count); + SDPDBG("ResponseLength: %d", rsplen); + + if (!rec_count) { + status = -1; + goto end; + } + extract_record_handle_seq(pdata, pdata_len, rsp, rec_count, &scanned); + SDPDBG("BytesScanned : %d", scanned); + + if (rsplen > scanned) { + uint8_t cstate_len; + + if (rspsize < sizeof(sdp_pdu_hdr_t) + scanned + sizeof(uint8_t)) { + SDPERR("Unexpected end of packet: continuation state data missing"); + status = -1; + goto end; + } + + pdata = rspbuf + sizeof(sdp_pdu_hdr_t) + scanned; + cstate_len = *(uint8_t *) pdata; + if (cstate_len > 0) { + cstate = (sdp_cstate_t *)pdata; + SDPDBG("Cont state length: %d", cstate_len); + } else + cstate = NULL; + } + } while (cstate); + +end: + free(reqbuf); + free(rspbuf); + + return status; +} + +/* + * This is a service attribute request. + * + * INPUT : + * + * uint32_t handle + * The handle of the service for which the attribute(s) are + * requested + * + * sdp_attrreq_type_t reqtype + * Attribute identifiers are 16 bit unsigned integers specified + * in one of 2 ways described below : + * SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers + * They are the actual attribute identifiers in ascending order + * + * SDP_ATTR_REQ_RANGE - 32bit identifier range + * The high-order 16bits is the start of range + * the low-order 16bits are the end of range + * 0x0000 to 0xFFFF gets all attributes + * + * sdp_list_t *attrid + * Singly linked list containing attribute identifiers desired. + * Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL) + * or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE) + * + * OUTPUT : + * return sdp_record_t * + * 0: + * On any error and sets errno + * !0: + * The service record + */ +sdp_record_t *sdp_service_attr_req(sdp_session_t *session, uint32_t handle, + sdp_attrreq_type_t reqtype, const sdp_list_t *attrids) +{ + uint32_t reqsize = 0, _reqsize; + uint32_t rspsize = 0, rsp_count; + int attr_list_len = 0; + int seqlen = 0; + unsigned int pdata_len; + uint8_t *pdata, *_pdata; + uint8_t *reqbuf, *rspbuf; + sdp_pdu_hdr_t *reqhdr, *rsphdr; + sdp_cstate_t *cstate = NULL; + uint8_t cstate_len = 0; + sdp_buf_t rsp_concat_buf; + sdp_record_t *rec = 0; + + if (reqtype != SDP_ATTR_REQ_INDIVIDUAL && reqtype != SDP_ATTR_REQ_RANGE) { + errno = EINVAL; + return NULL; + } + + memset(&rsp_concat_buf, 0, sizeof(sdp_buf_t)); + + reqbuf = malloc(SDP_REQ_BUFFER_SIZE); + rspbuf = malloc(SDP_RSP_BUFFER_SIZE); + if (!reqbuf || !rspbuf) { + errno = ENOMEM; + goto end; + } + reqhdr = (sdp_pdu_hdr_t *) reqbuf; + reqhdr->pdu_id = SDP_SVC_ATTR_REQ; + + pdata = reqbuf + sizeof(sdp_pdu_hdr_t); + reqsize = sizeof(sdp_pdu_hdr_t); + + /* add the service record handle */ + bt_put_be32(handle, pdata); + reqsize += sizeof(uint32_t); + pdata += sizeof(uint32_t); + + /* specify the response limit */ + bt_put_be16(65535, pdata); + reqsize += sizeof(uint16_t); + pdata += sizeof(uint16_t); + + /* get attr seq PDU form */ + seqlen = gen_attridseq_pdu(pdata, attrids, + reqtype == SDP_ATTR_REQ_INDIVIDUAL? SDP_UINT16 : SDP_UINT32); + if (seqlen == -1) { + errno = EINVAL; + goto end; + } + pdata += seqlen; + reqsize += seqlen; + SDPDBG("Attr list length : %d", seqlen); + + /* save before Continuation State */ + _pdata = pdata; + _reqsize = reqsize; + + do { + int status; + + /* add NULL continuation state */ + reqsize = _reqsize + copy_cstate(_pdata, + SDP_REQ_BUFFER_SIZE - _reqsize, cstate); + + /* set the request header's param length */ + reqhdr->tid = htons(sdp_gen_tid(session)); + reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t)); + + status = sdp_send_req_w4_rsp(session, reqbuf, rspbuf, reqsize, &rspsize); + if (status < 0) + goto end; + + if (rspsize < sizeof(sdp_pdu_hdr_t)) { + SDPERR("Unexpected end of packet"); + goto end; + } + + rsphdr = (sdp_pdu_hdr_t *) rspbuf; + if (rsphdr->pdu_id == SDP_ERROR_RSP) { + SDPDBG("PDU ID : 0x%x", rsphdr->pdu_id); + goto end; + } + pdata = rspbuf + sizeof(sdp_pdu_hdr_t); + pdata_len = rspsize - sizeof(sdp_pdu_hdr_t); + + if (pdata_len < sizeof(uint16_t)) { + SDPERR("Unexpected end of packet"); + goto end; + } + + rsp_count = bt_get_be16(pdata); + attr_list_len += rsp_count; + pdata += sizeof(uint16_t); + pdata_len -= sizeof(uint16_t); + + /* + * if continuation state set need to re-issue request before + * parsing + */ + if (pdata_len < rsp_count + sizeof(uint8_t)) { + SDPERR("Unexpected end of packet: continuation state data missing"); + goto end; + } + cstate_len = *(uint8_t *) (pdata + rsp_count); + + SDPDBG("Response id : %d", rsphdr->pdu_id); + SDPDBG("Attrlist byte count : %d", rsp_count); + SDPDBG("sdp_cstate_t length : %d", cstate_len); + + /* + * a split response: concatenate intermediate responses + * and the last one (which has cstate_len == 0) + */ + if (cstate_len > 0 || rsp_concat_buf.data_size != 0) { + uint8_t *targetPtr = NULL; + + cstate = cstate_len > 0 ? (sdp_cstate_t *) (pdata + rsp_count) : 0; + + /* build concatenated response buffer */ + rsp_concat_buf.data = realloc(rsp_concat_buf.data, rsp_concat_buf.data_size + rsp_count); + rsp_concat_buf.buf_size = rsp_concat_buf.data_size + rsp_count; + targetPtr = rsp_concat_buf.data + rsp_concat_buf.data_size; + memcpy(targetPtr, pdata, rsp_count); + rsp_concat_buf.data_size += rsp_count; + } + } while (cstate); + + if (attr_list_len > 0) { + int scanned = 0; + if (rsp_concat_buf.data_size != 0) { + pdata = rsp_concat_buf.data; + pdata_len = rsp_concat_buf.data_size; + } + rec = sdp_extract_pdu(pdata, pdata_len, &scanned); + } + +end: + free(reqbuf); + free(rsp_concat_buf.data); + free(rspbuf); + return rec; +} + +/* + * SDP transaction structure for asynchronous search + */ +struct sdp_transaction { + sdp_callback_t *cb; /* called when the transaction finishes */ + void *udata; /* client user data */ + uint8_t *reqbuf; /* pointer to request PDU */ + sdp_buf_t rsp_concat_buf; + uint32_t reqsize; /* without cstate */ + int err; /* ZERO if success or the errno if failed */ +}; + +/* + * Creates a new sdp session for asynchronous search + * INPUT: + * int sk + * non-blocking L2CAP socket + * + * RETURN: + * sdp_session_t * + * NULL - On memory allocation failure + */ +sdp_session_t *sdp_create(int sk, uint32_t flags) +{ + sdp_session_t *session; + struct sdp_transaction *t; + + session = malloc(sizeof(sdp_session_t)); + if (!session) { + errno = ENOMEM; + return NULL; + } + memset(session, 0, sizeof(*session)); + + session->flags = flags; + session->sock = sk; + + t = malloc(sizeof(struct sdp_transaction)); + if (!t) { + errno = ENOMEM; + free(session); + return NULL; + } + memset(t, 0, sizeof(*t)); + + session->priv = t; + + return session; +} + +/* + * Sets the callback function/user data used to notify the application + * that the asynchronous transaction finished. This function must be + * called before request an asynchronous search. + * + * INPUT: + * sdp_session_t *session + * Current sdp session to be handled + * sdp_callback_t *cb + * callback to be called when the transaction finishes + * void *udata + * user data passed to callback + * RETURN: + * 0 - Success + * -1 - Failure + */ +int sdp_set_notify(sdp_session_t *session, sdp_callback_t *func, void *udata) +{ + struct sdp_transaction *t; + + if (!session || !session->priv) + return -1; + + t = session->priv; + t->cb = func; + t->udata = udata; + + return 0; +} + +/* + * This function starts an asynchronous service search request. + * The incoming and outgoing data are stored in the transaction structure + * buffers. When there is incoming data the sdp_process function must be + * called to get the data and handle the continuation state. + * + * INPUT : + * sdp_session_t *session + * Current sdp session to be handled + * + * sdp_list_t *search + * Singly linked list containing elements of the search + * pattern. Each entry in the list is a UUID (DataTypeSDP_UUID16) + * of the service to be searched + * + * uint16_t max_rec_num + * A 16 bit integer which tells the service, the maximum + * entries that the client can handle in the response. The + * server is obliged not to return > max_rec_num entries + * + * OUTPUT : + * + * int return value + * 0 - if the request has been sent properly + * -1 - On any failure and sets errno + */ + +int sdp_service_search_async(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num) +{ + struct sdp_transaction *t; + sdp_pdu_hdr_t *reqhdr; + uint8_t *pdata; + int cstate_len, seqlen = 0; + + if (!session || !session->priv) + return -1; + + t = session->priv; + + /* clean possible allocated buffer */ + free(t->rsp_concat_buf.data); + memset(&t->rsp_concat_buf, 0, sizeof(sdp_buf_t)); + + if (!t->reqbuf) { + t->reqbuf = malloc(SDP_REQ_BUFFER_SIZE); + if (!t->reqbuf) { + t->err = ENOMEM; + goto end; + } + } + memset(t->reqbuf, 0, SDP_REQ_BUFFER_SIZE); + + reqhdr = (sdp_pdu_hdr_t *) t->reqbuf; + reqhdr->tid = htons(sdp_gen_tid(session)); + reqhdr->pdu_id = SDP_SVC_SEARCH_REQ; + + /* generate PDU */ + pdata = t->reqbuf + sizeof(sdp_pdu_hdr_t); + t->reqsize = sizeof(sdp_pdu_hdr_t); + + /* add service class IDs for search */ + seqlen = gen_searchseq_pdu(pdata, search); + + SDPDBG("Data seq added : %d", seqlen); + + /* now set the length and increment the pointer */ + t->reqsize += seqlen; + pdata += seqlen; + + bt_put_be16(max_rec_num, pdata); + t->reqsize += sizeof(uint16_t); + pdata += sizeof(uint16_t); + + /* set the request header's param length */ + cstate_len = copy_cstate(pdata, SDP_REQ_BUFFER_SIZE - t->reqsize, NULL); + reqhdr->plen = htons((t->reqsize + cstate_len) - sizeof(sdp_pdu_hdr_t)); + + if (sdp_send_req(session, t->reqbuf, t->reqsize + cstate_len) < 0) { + SDPERR("Error sending data:%m"); + t->err = errno; + goto end; + } + + return 0; +end: + + free(t->reqbuf); + t->reqbuf = NULL; + + return -1; +} + +/* + * This function starts an asynchronous service attribute request. + * The incoming and outgoing data are stored in the transaction structure + * buffers. When there is incoming data the sdp_process function must be + * called to get the data and handle the continuation state. + * + * INPUT : + * sdp_session_t *session + * Current sdp session to be handled + * + * uint32_t handle + * The handle of the service for which the attribute(s) are + * requested + * + * sdp_attrreq_type_t reqtype + * Attribute identifiers are 16 bit unsigned integers specified + * in one of 2 ways described below : + * SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers + * They are the actual attribute identifiers in ascending order + * + * SDP_ATTR_REQ_RANGE - 32bit identifier range + * The high-order 16bits is the start of range + * the low-order 16bits are the end of range + * 0x0000 to 0xFFFF gets all attributes + * + * sdp_list_t *attrid_list + * Singly linked list containing attribute identifiers desired. + * Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL) + * or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE) + * + * OUTPUT : + * int return value + * 0 - if the request has been sent properly + * -1 - On any failure and sets errno + */ + +int sdp_service_attr_async(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list) +{ + struct sdp_transaction *t; + sdp_pdu_hdr_t *reqhdr; + uint8_t *pdata; + int cstate_len, seqlen = 0; + + if (!session || !session->priv) + return -1; + + t = session->priv; + + /* clean possible allocated buffer */ + free(t->rsp_concat_buf.data); + memset(&t->rsp_concat_buf, 0, sizeof(sdp_buf_t)); + + if (!t->reqbuf) { + t->reqbuf = malloc(SDP_REQ_BUFFER_SIZE); + if (!t->reqbuf) { + t->err = ENOMEM; + goto end; + } + } + memset(t->reqbuf, 0, SDP_REQ_BUFFER_SIZE); + + reqhdr = (sdp_pdu_hdr_t *) t->reqbuf; + reqhdr->tid = htons(sdp_gen_tid(session)); + reqhdr->pdu_id = SDP_SVC_ATTR_REQ; + + /* generate PDU */ + pdata = t->reqbuf + sizeof(sdp_pdu_hdr_t); + t->reqsize = sizeof(sdp_pdu_hdr_t); + + /* add the service record handle */ + bt_put_be32(handle, pdata); + t->reqsize += sizeof(uint32_t); + pdata += sizeof(uint32_t); + + /* specify the response limit */ + bt_put_be16(65535, pdata); + t->reqsize += sizeof(uint16_t); + pdata += sizeof(uint16_t); + + /* get attr seq PDU form */ + seqlen = gen_attridseq_pdu(pdata, attrid_list, + reqtype == SDP_ATTR_REQ_INDIVIDUAL? SDP_UINT16 : SDP_UINT32); + if (seqlen == -1) { + t->err = EINVAL; + goto end; + } + + /* now set the length and increment the pointer */ + t->reqsize += seqlen; + pdata += seqlen; + SDPDBG("Attr list length : %d", seqlen); + + /* set the request header's param length */ + cstate_len = copy_cstate(pdata, SDP_REQ_BUFFER_SIZE - t->reqsize, NULL); + reqhdr->plen = htons((t->reqsize + cstate_len) - sizeof(sdp_pdu_hdr_t)); + + if (sdp_send_req(session, t->reqbuf, t->reqsize + cstate_len) < 0) { + SDPERR("Error sending data:%m"); + t->err = errno; + goto end; + } + + return 0; +end: + + free(t->reqbuf); + t->reqbuf = NULL; + + return -1; +} + +/* + * This function starts an asynchronous service search attributes. + * It is a service search request combined with attribute request. The incoming + * and outgoing data are stored in the transaction structure buffers. When there + * is incoming data the sdp_process function must be called to get the data + * and handle the continuation state. + * + * INPUT: + * sdp_session_t *session + * Current sdp session to be handled + * + * sdp_list_t *search + * Singly linked list containing elements of the search + * pattern. Each entry in the list is a UUID(DataTypeSDP_UUID16) + * of the service to be searched + * + * AttributeSpecification attrSpec + * Attribute identifiers are 16 bit unsigned integers specified + * in one of 2 ways described below : + * SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers + * They are the actual attribute identifiers in ascending order + * + * SDP_ATTR_REQ_RANGE - 32bit identifier range + * The high-order 16bits is the start of range + * the low-order 16bits are the end of range + * 0x0000 to 0xFFFF gets all attributes + * + * sdp_list_t *attrid_list + * Singly linked list containing attribute identifiers desired. + * Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL) + * or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE) + * + + * RETURN: + * 0 - if the request has been sent properly + * -1 - On any failure + */ +int sdp_service_search_attr_async(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list) +{ + struct sdp_transaction *t; + sdp_pdu_hdr_t *reqhdr; + uint8_t *pdata; + int cstate_len, seqlen = 0; + + if (!session || !session->priv) + return -1; + + t = session->priv; + + /* clean possible allocated buffer */ + free(t->rsp_concat_buf.data); + memset(&t->rsp_concat_buf, 0, sizeof(sdp_buf_t)); + + if (!t->reqbuf) { + t->reqbuf = malloc(SDP_REQ_BUFFER_SIZE); + if (!t->reqbuf) { + t->err = ENOMEM; + goto end; + } + } + memset(t->reqbuf, 0, SDP_REQ_BUFFER_SIZE); + + reqhdr = (sdp_pdu_hdr_t *) t->reqbuf; + reqhdr->tid = htons(sdp_gen_tid(session)); + reqhdr->pdu_id = SDP_SVC_SEARCH_ATTR_REQ; + + /* generate PDU */ + pdata = t->reqbuf + sizeof(sdp_pdu_hdr_t); + t->reqsize = sizeof(sdp_pdu_hdr_t); + + /* add service class IDs for search */ + seqlen = gen_searchseq_pdu(pdata, search); + + SDPDBG("Data seq added : %d", seqlen); + + /* now set the length and increment the pointer */ + t->reqsize += seqlen; + pdata += seqlen; + + bt_put_be16(SDP_MAX_ATTR_LEN, pdata); + t->reqsize += sizeof(uint16_t); + pdata += sizeof(uint16_t); + + SDPDBG("Max attr byte count : %d", SDP_MAX_ATTR_LEN); + + /* get attr seq PDU form */ + seqlen = gen_attridseq_pdu(pdata, attrid_list, + reqtype == SDP_ATTR_REQ_INDIVIDUAL ? SDP_UINT16 : SDP_UINT32); + if (seqlen == -1) { + t->err = EINVAL; + goto end; + } + + pdata += seqlen; + SDPDBG("Attr list length : %d", seqlen); + t->reqsize += seqlen; + + /* set the request header's param length */ + cstate_len = copy_cstate(pdata, SDP_REQ_BUFFER_SIZE - t->reqsize, NULL); + reqhdr->plen = htons((t->reqsize + cstate_len) - sizeof(sdp_pdu_hdr_t)); + + if (sdp_send_req(session, t->reqbuf, t->reqsize + cstate_len) < 0) { + SDPERR("Error sending data:%m"); + t->err = errno; + goto end; + } + + return 0; +end: + + free(t->reqbuf); + t->reqbuf = NULL; + + return -1; +} + +/* + * Function used to get the error reason after sdp_callback_t function has been called + * and the status is 0xffff or if sdp_service_{search, attr, search_attr}_async returns -1. + * It indicates that an error NOT related to SDP_ErrorResponse happened. Get errno directly + * is not safe because multiple transactions can be triggered. + * This function must be used with asynchronous sdp functions only. + * + * INPUT: + * sdp_session_t *session + * Current sdp session to be handled + * RETURN: + * 0 = No error in the current transaction + * -1 - if the session is invalid + * positive value - the errno value + * + */ +int sdp_get_error(sdp_session_t *session) +{ + struct sdp_transaction *t; + + if (!session || !session->priv) { + SDPERR("Invalid session"); + return -1; + } + + t = session->priv; + + return t->err; +} + +/* + * Receive the incoming SDP PDU. This function must be called when there is data + * available to be read. On continuation state, the original request (with a new + * transaction ID) and the continuation state data will be appended in the initial PDU. + * If an error happens or the transaction finishes the callback function will be called. + * + * INPUT: + * sdp_session_t *session + * Current sdp session to be handled + * RETURN: + * 0 - if the transaction is on continuation state + * -1 - On any failure or the transaction finished + */ +int sdp_process(sdp_session_t *session) +{ + struct sdp_transaction *t; + sdp_pdu_hdr_t *reqhdr, *rsphdr; + sdp_cstate_t *pcstate; + uint8_t *pdata, *rspbuf, *targetPtr; + int rsp_count, err = -1; + size_t size = 0; + int n, plen; + uint16_t status = 0xffff; + uint8_t pdu_id = 0x00; + + if (!session || !session->priv) { + SDPERR("Invalid session"); + return -1; + } + + rspbuf = malloc(SDP_RSP_BUFFER_SIZE); + if (!rspbuf) { + SDPERR("Response buffer alloc failure:%m (%d)", errno); + return -1; + } + + memset(rspbuf, 0, SDP_RSP_BUFFER_SIZE); + + t = session->priv; + reqhdr = (sdp_pdu_hdr_t *)t->reqbuf; + rsphdr = (sdp_pdu_hdr_t *)rspbuf; + + pdata = rspbuf + sizeof(sdp_pdu_hdr_t); + + n = sdp_read_rsp(session, rspbuf, SDP_RSP_BUFFER_SIZE); + if (n < 0) { + SDPERR("Read response:%m (%d)", errno); + t->err = errno; + goto end; + } + + if (reqhdr->tid != rsphdr->tid) { + t->err = EPROTO; + SDPERR("Protocol error: transaction id does not match"); + goto end; + } + + if (n != (int) (ntohs(rsphdr->plen) + sizeof(sdp_pdu_hdr_t))) { + t->err = EPROTO; + SDPERR("Protocol error: invalid length"); + goto end; + } + + pdu_id = rsphdr->pdu_id; + switch (rsphdr->pdu_id) { + uint8_t *ssr_pdata; + uint16_t tsrc, csrc; + case SDP_SVC_SEARCH_RSP: + /* + * TSRC: Total Service Record Count (2 bytes) + * CSRC: Current Service Record Count (2 bytes) + */ + ssr_pdata = pdata; + tsrc = bt_get_be16(ssr_pdata); + ssr_pdata += sizeof(uint16_t); + csrc = bt_get_be16(ssr_pdata); + + /* csrc should never be larger than tsrc */ + if (csrc > tsrc) { + t->err = EPROTO; + SDPERR("Protocol error: wrong current service record count value."); + goto end; + } + + SDPDBG("Total svc count: %d", tsrc); + SDPDBG("Current svc count: %d", csrc); + + /* parameter length without continuation state */ + plen = sizeof(tsrc) + sizeof(csrc) + csrc * 4; + + if (t->rsp_concat_buf.data_size == 0) { + /* first fragment */ + rsp_count = sizeof(tsrc) + sizeof(csrc) + csrc * 4; + } else if (t->rsp_concat_buf.data_size >= sizeof(uint16_t) * 2) { + /* point to the first csrc */ + uint8_t *pcsrc = t->rsp_concat_buf.data + 2; + uint16_t tcsrc, tcsrc2; + + /* FIXME: update the interface later. csrc doesn't need be passed to clients */ + + pdata += sizeof(uint16_t); /* point to csrc */ + + /* the first csrc contains the sum of partial csrc responses */ + memcpy(&tcsrc, pcsrc, sizeof(tcsrc)); + memcpy(&tcsrc2, pdata, sizeof(tcsrc2)); + tcsrc += tcsrc2; + memcpy(pcsrc, &tcsrc, sizeof(tcsrc)); + + pdata += sizeof(uint16_t); /* point to the first handle */ + rsp_count = csrc * 4; + } else { + t->err = EPROTO; + SDPERR("Protocol error: invalid PDU size"); + status = SDP_INVALID_PDU_SIZE; + goto end; + } + status = 0x0000; + break; + case SDP_SVC_ATTR_RSP: + case SDP_SVC_SEARCH_ATTR_RSP: + rsp_count = bt_get_be16(pdata); + SDPDBG("Attrlist byte count : %d", rsp_count); + + /* Valid range for rsp_count is 0x0002-0xFFFF */ + if (t->rsp_concat_buf.data_size == 0 && rsp_count < 0x0002) { + t->err = EPROTO; + SDPERR("Protocol error: invalid AttrList size"); + status = SDP_INVALID_PDU_SIZE; + goto end; + } + + /* + * Number of bytes in the AttributeLists parameter(without + * continuation state) + AttributeListsByteCount field size. + */ + plen = sizeof(uint16_t) + rsp_count; + + pdata += sizeof(uint16_t); /* points to attribute list */ + status = 0x0000; + break; + case SDP_ERROR_RSP: + status = bt_get_be16(pdata); + size = ntohs(rsphdr->plen); + + goto end; + default: + t->err = EPROTO; + SDPERR("Illegal PDU ID: 0x%x", rsphdr->pdu_id); + goto end; + } + + /* Out of bound check before using rsp_count as offset for + * continuation state, which has at least a one byte size + * field. + */ + if ((n - (int) sizeof(sdp_pdu_hdr_t)) < plen + 1) { + t->err = EPROTO; + SDPERR("Protocol error: invalid PDU size"); + status = SDP_INVALID_PDU_SIZE; + goto end; + } + + pcstate = (sdp_cstate_t *) (pdata + rsp_count); + + SDPDBG("Cstate length : %d", pcstate->length); + + /* + * Check out of bound. Continuation state must have at least + * 1 byte: ZERO to indicate that it is not a partial response. + */ + if ((n - (int) sizeof(sdp_pdu_hdr_t)) != (plen + pcstate->length + 1)) { + t->err = EPROTO; + SDPERR("Protocol error: wrong PDU size."); + status = 0xffff; + goto end; + } + + /* + * This is a split response, need to concatenate intermediate + * responses and the last one which will have cstate length == 0 + */ + t->rsp_concat_buf.data = realloc(t->rsp_concat_buf.data, t->rsp_concat_buf.data_size + rsp_count); + targetPtr = t->rsp_concat_buf.data + t->rsp_concat_buf.data_size; + t->rsp_concat_buf.buf_size = t->rsp_concat_buf.data_size + rsp_count; + memcpy(targetPtr, pdata, rsp_count); + t->rsp_concat_buf.data_size += rsp_count; + + if (pcstate->length > 0) { + int reqsize, cstate_len; + + reqhdr->tid = htons(sdp_gen_tid(session)); + + /* add continuation state */ + cstate_len = copy_cstate(t->reqbuf + t->reqsize, + SDP_REQ_BUFFER_SIZE - t->reqsize, pcstate); + + reqsize = t->reqsize + cstate_len; + + /* set the request header's param length */ + reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t)); + + if (sdp_send_req(session, t->reqbuf, reqsize) < 0) { + SDPERR("Error sending data:%m(%d)", errno); + status = 0xffff; + t->err = errno; + goto end; + } + err = 0; + } + +end: + if (err) { + if (t->rsp_concat_buf.data_size != 0) { + pdata = t->rsp_concat_buf.data; + size = t->rsp_concat_buf.data_size; + } + if (t->cb) + t->cb(pdu_id, status, pdata, size, t->udata); + } + + free(rspbuf); + + return err; +} + +/* + * This is a service search request combined with the service + * attribute request. First a service class match is done and + * for matching service, requested attributes are extracted + * + * INPUT : + * + * sdp_list_t *search + * Singly linked list containing elements of the search + * pattern. Each entry in the list is a UUID(DataTypeSDP_UUID16) + * of the service to be searched + * + * AttributeSpecification attrSpec + * Attribute identifiers are 16 bit unsigned integers specified + * in one of 2 ways described below : + * SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers + * They are the actual attribute identifiers in ascending order + * + * SDP_ATTR_REQ_RANGE - 32bit identifier range + * The high-order 16bits is the start of range + * the low-order 16bits are the end of range + * 0x0000 to 0xFFFF gets all attributes + * + * sdp_list_t *attrids + * Singly linked list containing attribute identifiers desired. + * Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL) + * or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE) + * + * OUTPUT : + * int return value + * 0: + * The request completed successfully. This does not + * mean the requested services were found + * -1: + * On any error and sets errno + * + * sdp_list_t **rsp + * This variable is set on a successful return to point to + * service(s) found. Each element of this list is of type + * sdp_record_t* (of the services which matched the search list) + */ +int sdp_service_search_attr_req(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrids, sdp_list_t **rsp) +{ + int status = 0; + uint32_t reqsize = 0, _reqsize; + uint32_t rspsize = 0; + int seqlen = 0, attr_list_len = 0; + int rsp_count = 0, cstate_len = 0; + unsigned int pdata_len; + uint8_t *pdata, *_pdata; + uint8_t *reqbuf, *rspbuf; + sdp_pdu_hdr_t *reqhdr, *rsphdr; + uint8_t dataType; + sdp_list_t *rec_list = NULL; + sdp_buf_t rsp_concat_buf; + sdp_cstate_t *cstate = NULL; + + if (reqtype != SDP_ATTR_REQ_INDIVIDUAL && reqtype != SDP_ATTR_REQ_RANGE) { + errno = EINVAL; + return -1; + } + + memset(&rsp_concat_buf, 0, sizeof(sdp_buf_t)); + + reqbuf = malloc(SDP_REQ_BUFFER_SIZE); + rspbuf = malloc(SDP_RSP_BUFFER_SIZE); + if (!reqbuf || !rspbuf) { + errno = ENOMEM; + status = -1; + goto end; + } + + reqhdr = (sdp_pdu_hdr_t *) reqbuf; + reqhdr->pdu_id = SDP_SVC_SEARCH_ATTR_REQ; + + /* generate PDU */ + pdata = reqbuf + sizeof(sdp_pdu_hdr_t); + reqsize = sizeof(sdp_pdu_hdr_t); + + /* add service class IDs for search */ + seqlen = gen_searchseq_pdu(pdata, search); + if (seqlen < 0) { + errno = EINVAL; + status = -1; + goto end; + } + + SDPDBG("Data seq added : %d", seqlen); + + /* now set the length and increment the pointer */ + reqsize += seqlen; + pdata += seqlen; + + bt_put_be16(SDP_MAX_ATTR_LEN, pdata); + reqsize += sizeof(uint16_t); + pdata += sizeof(uint16_t); + + SDPDBG("Max attr byte count : %d", SDP_MAX_ATTR_LEN); + + /* get attr seq PDU form */ + seqlen = gen_attridseq_pdu(pdata, attrids, + reqtype == SDP_ATTR_REQ_INDIVIDUAL ? SDP_UINT16 : SDP_UINT32); + if (seqlen == -1) { + errno = EINVAL; + status = -1; + goto end; + } + pdata += seqlen; + SDPDBG("Attr list length : %d", seqlen); + reqsize += seqlen; + *rsp = 0; + + /* save before Continuation State */ + _pdata = pdata; + _reqsize = reqsize; + + do { + reqhdr->tid = htons(sdp_gen_tid(session)); + + /* add continuation state (can be null) */ + reqsize = _reqsize + copy_cstate(_pdata, + SDP_REQ_BUFFER_SIZE - _reqsize, cstate); + + /* set the request header's param length */ + reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t)); + rsphdr = (sdp_pdu_hdr_t *) rspbuf; + status = sdp_send_req_w4_rsp(session, reqbuf, rspbuf, reqsize, &rspsize); + if (rspsize < sizeof(sdp_pdu_hdr_t)) { + SDPERR("Unexpected end of packet"); + status = -1; + goto end; + } + + if (status < 0) { + SDPDBG("Status : 0x%x", rsphdr->pdu_id); + goto end; + } + + if (rsphdr->pdu_id == SDP_ERROR_RSP) { + status = -1; + goto end; + } + + pdata = rspbuf + sizeof(sdp_pdu_hdr_t); + pdata_len = rspsize - sizeof(sdp_pdu_hdr_t); + + if (pdata_len < sizeof(uint16_t)) { + SDPERR("Unexpected end of packet"); + status = -1; + goto end; + } + + rsp_count = bt_get_be16(pdata); + attr_list_len += rsp_count; + pdata += sizeof(uint16_t); /* pdata points to attribute list */ + pdata_len -= sizeof(uint16_t); + + if (pdata_len < rsp_count + sizeof(uint8_t)) { + SDPERR("Unexpected end of packet: continuation state data missing"); + status = -1; + goto end; + } + + cstate_len = *(uint8_t *) (pdata + rsp_count); + + SDPDBG("Attrlist byte count : %d", attr_list_len); + SDPDBG("Response byte count : %d", rsp_count); + SDPDBG("Cstate length : %d", cstate_len); + /* + * This is a split response, need to concatenate intermediate + * responses and the last one which will have cstate_len == 0 + */ + if (cstate_len > 0 || rsp_concat_buf.data_size != 0) { + uint8_t *targetPtr = NULL; + + cstate = cstate_len > 0 ? (sdp_cstate_t *) (pdata + rsp_count) : 0; + + /* build concatenated response buffer */ + rsp_concat_buf.data = realloc(rsp_concat_buf.data, rsp_concat_buf.data_size + rsp_count); + targetPtr = rsp_concat_buf.data + rsp_concat_buf.data_size; + rsp_concat_buf.buf_size = rsp_concat_buf.data_size + rsp_count; + memcpy(targetPtr, pdata, rsp_count); + rsp_concat_buf.data_size += rsp_count; + } + } while (cstate); + + if (attr_list_len > 0) { + int scanned = 0; + + if (rsp_concat_buf.data_size != 0) { + pdata = rsp_concat_buf.data; + pdata_len = rsp_concat_buf.data_size; + } + + /* + * Response is a sequence of sequence(s) for one or + * more data element sequence(s) representing services + * for which attributes are returned + */ + scanned = sdp_extract_seqtype(pdata, pdata_len, &dataType, &seqlen); + + SDPDBG("Bytes scanned : %d", scanned); + SDPDBG("Seq length : %d", seqlen); + + if (scanned && seqlen) { + pdata += scanned; + pdata_len -= scanned; + do { + int recsize = 0; + sdp_record_t *rec = sdp_extract_pdu(pdata, pdata_len, &recsize); + if (rec == NULL) { + SDPERR("SVC REC is null"); + status = -1; + goto end; + } + if (!recsize) { + sdp_record_free(rec); + break; + } + scanned += recsize; + pdata += recsize; + pdata_len -= recsize; + + SDPDBG("Loc seq length : %d", recsize); + SDPDBG("Svc Rec Handle : 0x%x", rec->handle); + SDPDBG("Bytes scanned : %d", scanned); + SDPDBG("Attrlist byte count : %d", attr_list_len); + rec_list = sdp_list_append(rec_list, rec); + } while (scanned < attr_list_len && pdata_len > 0); + + SDPDBG("Successful scan of service attr lists"); + *rsp = rec_list; + } + } +end: + free(rsp_concat_buf.data); + free(reqbuf); + free(rspbuf); + return status; +} + +/* + * Find devices in the piconet. + */ +int sdp_general_inquiry(inquiry_info *ii, int num_dev, int duration, uint8_t *found) +{ + int n = hci_inquiry(-1, 10, num_dev, NULL, &ii, 0); + if (n < 0) { + SDPERR("Inquiry failed:%m"); + return -1; + } + *found = n; + return 0; +} + +int sdp_close(sdp_session_t *session) +{ + struct sdp_transaction *t; + int ret; + + if (!session) + return -1; + + ret = close(session->sock); + + t = session->priv; + + if (t) { + free(t->reqbuf); + + free(t->rsp_concat_buf.data); + + free(t); + } + free(session); + return ret; +} + +static inline int sdp_is_local(const bdaddr_t *device) +{ + return memcmp(device, BDADDR_LOCAL, sizeof(bdaddr_t)) == 0; +} + +static int sdp_connect_local(sdp_session_t *session) +{ + struct sockaddr_un sa; + + session->sock = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (session->sock < 0) + return -1; + session->local = 1; + + sa.sun_family = AF_UNIX; + strcpy(sa.sun_path, SDP_UNIX_PATH); + + return connect(session->sock, (struct sockaddr *) &sa, sizeof(sa)); +} + +static int set_l2cap_mtu(int sk, uint16_t mtu) +{ + struct l2cap_options l2o; + socklen_t len; + + memset(&l2o, 0, sizeof(l2o)); + len = sizeof(l2o); + + if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) + return -1; + + l2o.imtu = mtu; + l2o.omtu = mtu; + + if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o)) < 0) + return -1; + + return 0; +} + +static int sdp_connect_l2cap(const bdaddr_t *src, + const bdaddr_t *dst, sdp_session_t *session) +{ + uint32_t flags = session->flags; + struct sockaddr_l2 sa; + int sk; + int sockflags = SOCK_SEQPACKET | SOCK_CLOEXEC; + + if (flags & SDP_NON_BLOCKING) + sockflags |= SOCK_NONBLOCK; + + session->sock = socket(PF_BLUETOOTH, sockflags, BTPROTO_L2CAP); + if (session->sock < 0) + return -1; + session->local = 0; + + sk = session->sock; + + memset(&sa, 0, sizeof(sa)); + + sa.l2_family = AF_BLUETOOTH; + sa.l2_psm = 0; + + if (bacmp(src, BDADDR_ANY)) { + sa.l2_bdaddr = *src; + if (bind(sk, (struct sockaddr *) &sa, sizeof(sa)) < 0) + return -1; + } + + if (flags & SDP_WAIT_ON_CLOSE) { + struct linger l = { .l_onoff = 1, .l_linger = 1 }; + setsockopt(sk, SOL_SOCKET, SO_LINGER, &l, sizeof(l)); + } + + if ((flags & SDP_LARGE_MTU) && + set_l2cap_mtu(sk, SDP_LARGE_L2CAP_MTU) < 0) + return -1; + + sa.l2_psm = htobs(SDP_PSM); + sa.l2_bdaddr = *dst; + + do { + int ret = connect(sk, (struct sockaddr *) &sa, sizeof(sa)); + if (!ret) + return 0; + if (ret < 0 && (flags & SDP_NON_BLOCKING) && + (errno == EAGAIN || errno == EINPROGRESS)) + return 0; + } while (errno == EBUSY && (flags & SDP_RETRY_IF_BUSY)); + + return -1; +} + +sdp_session_t *sdp_connect(const bdaddr_t *src, + const bdaddr_t *dst, uint32_t flags) +{ + sdp_session_t *session; + int err; + + if ((flags & SDP_RETRY_IF_BUSY) && (flags & SDP_NON_BLOCKING)) { + errno = EINVAL; + return NULL; + } + + session = sdp_create(-1, flags); + if (!session) + return NULL; + + if (sdp_is_local(dst)) { + if (sdp_connect_local(session) < 0) + goto fail; + } else { + if (sdp_connect_l2cap(src, dst, session) < 0) + goto fail; + } + + return session; + +fail: + err = errno; + if (session->sock >= 0) + close(session->sock); + free(session->priv); + free(session); + errno = err; + + return NULL; +} + +int sdp_get_socket(const sdp_session_t *session) +{ + return session->sock; +} + +uint16_t sdp_gen_tid(sdp_session_t *session) +{ + return session->tid++; +} + +/* + * Set the supported features + */ +int sdp_set_supp_feat(sdp_record_t *rec, const sdp_list_t *sf) +{ + const sdp_list_t *p, *r; + sdp_data_t *feat, *seq_feat; + int seqlen, i; + void **seqDTDs, **seqVals; + + seqlen = sdp_list_len(sf); + seqDTDs = malloc(seqlen * sizeof(void *)); + if (!seqDTDs) + return -1; + seqVals = malloc(seqlen * sizeof(void *)); + if (!seqVals) { + free(seqDTDs); + return -1; + } + + for (p = sf, i = 0; p; p = p->next, i++) { + int plen, j; + void **dtds, **vals; + int *lengths; + + plen = sdp_list_len(p->data); + dtds = malloc(plen * sizeof(void *)); + if (!dtds) + goto fail; + vals = malloc(plen * sizeof(void *)); + if (!vals) { + free(dtds); + goto fail; + } + lengths = malloc(plen * sizeof(int)); + if (!lengths) { + free(dtds); + free(vals); + goto fail; + } + for (r = p->data, j = 0; r; r = r->next, j++) { + sdp_data_t *data = (sdp_data_t *) r->data; + dtds[j] = &data->dtd; + switch (data->dtd) { + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + vals[j] = data->val.str; + lengths[j] = data->unitSize - sizeof(uint8_t); + break; + case SDP_ALT8: + case SDP_ALT16: + case SDP_ALT32: + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + vals[j] = data->val.dataseq; + lengths[j] = 0; + break; + default: + vals[j] = &data->val; + lengths[j] = 0; + break; + } + } + feat = sdp_seq_alloc_with_length(dtds, vals, lengths, plen); + free(dtds); + free(vals); + free(lengths); + if (!feat) + goto fail; + seqDTDs[i] = &feat->dtd; + seqVals[i] = feat; + } + seq_feat = sdp_seq_alloc(seqDTDs, seqVals, seqlen); + if (!seq_feat) + goto fail; + sdp_attr_replace(rec, SDP_ATTR_SUPPORTED_FEATURES_LIST, seq_feat); + + free(seqVals); + free(seqDTDs); + return 0; + +fail: + free(seqVals); + free(seqDTDs); + return -1; +} + +/* + * Get the supported features + * If an error occurred -1 is returned and errno is set + */ +int sdp_get_supp_feat(const sdp_record_t *rec, sdp_list_t **seqp) +{ + sdp_data_t *sdpdata, *d; + sdp_list_t *tseq; + tseq = NULL; + + sdpdata = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES_LIST); + + if (!sdpdata || !SDP_IS_SEQ(sdpdata->dtd)) + return sdp_get_uuidseq_attr(rec, + SDP_ATTR_SUPPORTED_FEATURES_LIST, seqp); + + for (d = sdpdata->val.dataseq; d; d = d->next) { + sdp_data_t *dd; + sdp_list_t *subseq; + + if (!SDP_IS_SEQ(d->dtd)) + goto fail; + + subseq = NULL; + + for (dd = d->val.dataseq; dd; dd = dd->next) { + sdp_data_t *data; + void *val; + int length; + + switch (dd->dtd) { + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + val = dd->val.str; + length = dd->unitSize - sizeof(uint8_t); + break; + case SDP_UINT8: + case SDP_UINT16: + val = &dd->val; + length = 0; + break; + default: + sdp_list_free(subseq, free); + goto fail; + } + + data = sdp_data_alloc_with_length(dd->dtd, val, length); + if (data) + subseq = sdp_list_append(subseq, data); + } + tseq = sdp_list_append(tseq, subseq); + } + *seqp = tseq; + return 0; + +fail: + while (tseq) { + sdp_list_t * next; + + next = tseq->next; + sdp_list_free(tseq, free); + tseq = next; + } + errno = EINVAL; + return -1; +} + +void sdp_add_lang_attr(sdp_record_t *rec) +{ + sdp_lang_attr_t base_lang; + sdp_list_t *langs; + + base_lang.code_ISO639 = (0x65 << 8) | 0x6e; + base_lang.encoding = 106; + base_lang.base_offset = SDP_PRIMARY_LANG_BASE; + + langs = sdp_list_append(0, &base_lang); + sdp_set_lang_attr(rec, langs); + sdp_list_free(langs, NULL); +} diff --git a/lib/sdp.h b/lib/sdp.h new file mode 100644 index 0000000..f586eb5 --- /dev/null +++ b/lib/sdp.h @@ -0,0 +1,542 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __SDP_H +#define __SDP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define SDP_UNIX_PATH "/var/run/sdp" +#define SDP_RESPONSE_TIMEOUT 20 +#define SDP_REQ_BUFFER_SIZE 2048 +#define SDP_RSP_BUFFER_SIZE 65535 +#define SDP_PDU_CHUNK_SIZE 1024 + +/* + * All definitions are based on Bluetooth Assigned Numbers + * of the Bluetooth Specification + */ +#define SDP_PSM 0x0001 + +/* + * Protocol UUIDs + */ +#define SDP_UUID 0x0001 +#define UDP_UUID 0x0002 +#define RFCOMM_UUID 0x0003 +#define TCP_UUID 0x0004 +#define TCS_BIN_UUID 0x0005 +#define TCS_AT_UUID 0x0006 +#define ATT_UUID 0x0007 +#define OBEX_UUID 0x0008 +#define IP_UUID 0x0009 +#define FTP_UUID 0x000a +#define HTTP_UUID 0x000c +#define WSP_UUID 0x000e +#define BNEP_UUID 0x000f +#define UPNP_UUID 0x0010 +#define HIDP_UUID 0x0011 +#define HCRP_CTRL_UUID 0x0012 +#define HCRP_DATA_UUID 0x0014 +#define HCRP_NOTE_UUID 0x0016 +#define AVCTP_UUID 0x0017 +#define AVDTP_UUID 0x0019 +#define CMTP_UUID 0x001b +#define UDI_UUID 0x001d +#define MCAP_CTRL_UUID 0x001e +#define MCAP_DATA_UUID 0x001f +#define L2CAP_UUID 0x0100 + +/* + * Service class identifiers of standard services and service groups + */ +#define SDP_SERVER_SVCLASS_ID 0x1000 +#define BROWSE_GRP_DESC_SVCLASS_ID 0x1001 +#define PUBLIC_BROWSE_GROUP 0x1002 +#define SERIAL_PORT_SVCLASS_ID 0x1101 +#define LAN_ACCESS_SVCLASS_ID 0x1102 +#define DIALUP_NET_SVCLASS_ID 0x1103 +#define IRMC_SYNC_SVCLASS_ID 0x1104 +#define OBEX_OBJPUSH_SVCLASS_ID 0x1105 +#define OBEX_FILETRANS_SVCLASS_ID 0x1106 +#define IRMC_SYNC_CMD_SVCLASS_ID 0x1107 +#define HEADSET_SVCLASS_ID 0x1108 +#define CORDLESS_TELEPHONY_SVCLASS_ID 0x1109 +#define AUDIO_SOURCE_SVCLASS_ID 0x110a +#define AUDIO_SINK_SVCLASS_ID 0x110b +#define AV_REMOTE_TARGET_SVCLASS_ID 0x110c +#define ADVANCED_AUDIO_SVCLASS_ID 0x110d +#define AV_REMOTE_SVCLASS_ID 0x110e +#define AV_REMOTE_CONTROLLER_SVCLASS_ID 0x110f +#define INTERCOM_SVCLASS_ID 0x1110 +#define FAX_SVCLASS_ID 0x1111 +#define HEADSET_AGW_SVCLASS_ID 0x1112 +#define WAP_SVCLASS_ID 0x1113 +#define WAP_CLIENT_SVCLASS_ID 0x1114 +#define PANU_SVCLASS_ID 0x1115 +#define NAP_SVCLASS_ID 0x1116 +#define GN_SVCLASS_ID 0x1117 +#define DIRECT_PRINTING_SVCLASS_ID 0x1118 +#define REFERENCE_PRINTING_SVCLASS_ID 0x1119 +#define IMAGING_SVCLASS_ID 0x111a +#define IMAGING_RESPONDER_SVCLASS_ID 0x111b +#define IMAGING_ARCHIVE_SVCLASS_ID 0x111c +#define IMAGING_REFOBJS_SVCLASS_ID 0x111d +#define HANDSFREE_SVCLASS_ID 0x111e +#define HANDSFREE_AGW_SVCLASS_ID 0x111f +#define DIRECT_PRT_REFOBJS_SVCLASS_ID 0x1120 +#define REFLECTED_UI_SVCLASS_ID 0x1121 +#define BASIC_PRINTING_SVCLASS_ID 0x1122 +#define PRINTING_STATUS_SVCLASS_ID 0x1123 +#define HID_SVCLASS_ID 0x1124 +#define HCR_SVCLASS_ID 0x1125 +#define HCR_PRINT_SVCLASS_ID 0x1126 +#define HCR_SCAN_SVCLASS_ID 0x1127 +#define CIP_SVCLASS_ID 0x1128 +#define VIDEO_CONF_GW_SVCLASS_ID 0x1129 +#define UDI_MT_SVCLASS_ID 0x112a +#define UDI_TA_SVCLASS_ID 0x112b +#define AV_SVCLASS_ID 0x112c +#define SAP_SVCLASS_ID 0x112d +#define PBAP_PCE_SVCLASS_ID 0x112e +#define PBAP_PSE_SVCLASS_ID 0x112f +#define PBAP_SVCLASS_ID 0x1130 +#define MAP_MSE_SVCLASS_ID 0x1132 +#define MAP_MCE_SVCLASS_ID 0x1133 +#define MAP_SVCLASS_ID 0x1134 +#define GNSS_SVCLASS_ID 0x1135 +#define GNSS_SERVER_SVCLASS_ID 0x1136 +#define MPS_SC_SVCLASS_ID 0x113A +#define MPS_SVCLASS_ID 0x113B +#define PNP_INFO_SVCLASS_ID 0x1200 +#define GENERIC_NETWORKING_SVCLASS_ID 0x1201 +#define GENERIC_FILETRANS_SVCLASS_ID 0x1202 +#define GENERIC_AUDIO_SVCLASS_ID 0x1203 +#define GENERIC_TELEPHONY_SVCLASS_ID 0x1204 +#define UPNP_SVCLASS_ID 0x1205 +#define UPNP_IP_SVCLASS_ID 0x1206 +#define UPNP_PAN_SVCLASS_ID 0x1300 +#define UPNP_LAP_SVCLASS_ID 0x1301 +#define UPNP_L2CAP_SVCLASS_ID 0x1302 +#define VIDEO_SOURCE_SVCLASS_ID 0x1303 +#define VIDEO_SINK_SVCLASS_ID 0x1304 +#define VIDEO_DISTRIBUTION_SVCLASS_ID 0x1305 +#define HDP_SVCLASS_ID 0x1400 +#define HDP_SOURCE_SVCLASS_ID 0x1401 +#define HDP_SINK_SVCLASS_ID 0x1402 +#define GENERIC_ACCESS_SVCLASS_ID 0x1800 +#define GENERIC_ATTRIB_SVCLASS_ID 0x1801 +#define APPLE_AGENT_SVCLASS_ID 0x2112 + +/* + * Standard profile descriptor identifiers; note these + * may be identical to some of the service classes defined above + */ +#define SDP_SERVER_PROFILE_ID SDP_SERVER_SVCLASS_ID +#define BROWSE_GRP_DESC_PROFILE_ID BROWSE_GRP_DESC_SVCLASS_ID +#define SERIAL_PORT_PROFILE_ID SERIAL_PORT_SVCLASS_ID +#define LAN_ACCESS_PROFILE_ID LAN_ACCESS_SVCLASS_ID +#define DIALUP_NET_PROFILE_ID DIALUP_NET_SVCLASS_ID +#define IRMC_SYNC_PROFILE_ID IRMC_SYNC_SVCLASS_ID +#define OBEX_OBJPUSH_PROFILE_ID OBEX_OBJPUSH_SVCLASS_ID +#define OBEX_FILETRANS_PROFILE_ID OBEX_FILETRANS_SVCLASS_ID +#define IRMC_SYNC_CMD_PROFILE_ID IRMC_SYNC_CMD_SVCLASS_ID +#define HEADSET_PROFILE_ID HEADSET_SVCLASS_ID +#define CORDLESS_TELEPHONY_PROFILE_ID CORDLESS_TELEPHONY_SVCLASS_ID +#define AUDIO_SOURCE_PROFILE_ID AUDIO_SOURCE_SVCLASS_ID +#define AUDIO_SINK_PROFILE_ID AUDIO_SINK_SVCLASS_ID +#define AV_REMOTE_TARGET_PROFILE_ID AV_REMOTE_TARGET_SVCLASS_ID +#define ADVANCED_AUDIO_PROFILE_ID ADVANCED_AUDIO_SVCLASS_ID +#define AV_REMOTE_PROFILE_ID AV_REMOTE_SVCLASS_ID +#define INTERCOM_PROFILE_ID INTERCOM_SVCLASS_ID +#define FAX_PROFILE_ID FAX_SVCLASS_ID +#define HEADSET_AGW_PROFILE_ID HEADSET_AGW_SVCLASS_ID +#define WAP_PROFILE_ID WAP_SVCLASS_ID +#define WAP_CLIENT_PROFILE_ID WAP_CLIENT_SVCLASS_ID +#define PANU_PROFILE_ID PANU_SVCLASS_ID +#define NAP_PROFILE_ID NAP_SVCLASS_ID +#define GN_PROFILE_ID GN_SVCLASS_ID +#define DIRECT_PRINTING_PROFILE_ID DIRECT_PRINTING_SVCLASS_ID +#define REFERENCE_PRINTING_PROFILE_ID REFERENCE_PRINTING_SVCLASS_ID +#define IMAGING_PROFILE_ID IMAGING_SVCLASS_ID +#define IMAGING_RESPONDER_PROFILE_ID IMAGING_RESPONDER_SVCLASS_ID +#define IMAGING_ARCHIVE_PROFILE_ID IMAGING_ARCHIVE_SVCLASS_ID +#define IMAGING_REFOBJS_PROFILE_ID IMAGING_REFOBJS_SVCLASS_ID +#define HANDSFREE_PROFILE_ID HANDSFREE_SVCLASS_ID +#define HANDSFREE_AGW_PROFILE_ID HANDSFREE_AGW_SVCLASS_ID +#define DIRECT_PRT_REFOBJS_PROFILE_ID DIRECT_PRT_REFOBJS_SVCLASS_ID +#define REFLECTED_UI_PROFILE_ID REFLECTED_UI_SVCLASS_ID +#define BASIC_PRINTING_PROFILE_ID BASIC_PRINTING_SVCLASS_ID +#define PRINTING_STATUS_PROFILE_ID PRINTING_STATUS_SVCLASS_ID +#define HID_PROFILE_ID HID_SVCLASS_ID +#define HCR_PROFILE_ID HCR_SCAN_SVCLASS_ID +#define HCR_PRINT_PROFILE_ID HCR_PRINT_SVCLASS_ID +#define HCR_SCAN_PROFILE_ID HCR_SCAN_SVCLASS_ID +#define CIP_PROFILE_ID CIP_SVCLASS_ID +#define VIDEO_CONF_GW_PROFILE_ID VIDEO_CONF_GW_SVCLASS_ID +#define UDI_MT_PROFILE_ID UDI_MT_SVCLASS_ID +#define UDI_TA_PROFILE_ID UDI_TA_SVCLASS_ID +#define AV_PROFILE_ID AV_SVCLASS_ID +#define SAP_PROFILE_ID SAP_SVCLASS_ID +#define PBAP_PCE_PROFILE_ID PBAP_PCE_SVCLASS_ID +#define PBAP_PSE_PROFILE_ID PBAP_PSE_SVCLASS_ID +#define PBAP_PROFILE_ID PBAP_SVCLASS_ID +#define MAP_PROFILE_ID MAP_SVCLASS_ID +#define PNP_INFO_PROFILE_ID PNP_INFO_SVCLASS_ID +#define GENERIC_NETWORKING_PROFILE_ID GENERIC_NETWORKING_SVCLASS_ID +#define GENERIC_FILETRANS_PROFILE_ID GENERIC_FILETRANS_SVCLASS_ID +#define GENERIC_AUDIO_PROFILE_ID GENERIC_AUDIO_SVCLASS_ID +#define GENERIC_TELEPHONY_PROFILE_ID GENERIC_TELEPHONY_SVCLASS_ID +#define UPNP_PROFILE_ID UPNP_SVCLASS_ID +#define UPNP_IP_PROFILE_ID UPNP_IP_SVCLASS_ID +#define UPNP_PAN_PROFILE_ID UPNP_PAN_SVCLASS_ID +#define UPNP_LAP_PROFILE_ID UPNP_LAP_SVCLASS_ID +#define UPNP_L2CAP_PROFILE_ID UPNP_L2CAP_SVCLASS_ID +#define VIDEO_SOURCE_PROFILE_ID VIDEO_SOURCE_SVCLASS_ID +#define VIDEO_SINK_PROFILE_ID VIDEO_SINK_SVCLASS_ID +#define VIDEO_DISTRIBUTION_PROFILE_ID VIDEO_DISTRIBUTION_SVCLASS_ID +#define HDP_PROFILE_ID HDP_SVCLASS_ID +#define HDP_SOURCE_PROFILE_ID HDP_SOURCE_SVCLASS_ID +#define HDP_SINK_PROFILE_ID HDP_SINK_SVCLASS_ID +#define GENERIC_ACCESS_PROFILE_ID GENERIC_ACCESS_SVCLASS_ID +#define GENERIC_ATTRIB_PROFILE_ID GENERIC_ATTRIB_SVCLASS_ID +#define APPLE_AGENT_PROFILE_ID APPLE_AGENT_SVCLASS_ID +#define MPS_PROFILE_ID MPS_SC_SVCLASS_ID + +/* + * Compatibility macros for the old MDP acronym + */ +#define MDP_SVCLASS_ID HDP_SVCLASS_ID +#define MDP_SOURCE_SVCLASS_ID HDP_SOURCE_SVCLASS_ID +#define MDP_SINK_SVCLASS_ID HDP_SINK_SVCLASS_ID +#define MDP_PROFILE_ID HDP_PROFILE_ID +#define MDP_SOURCE_PROFILE_ID HDP_SOURCE_PROFILE_ID +#define MDP_SINK_PROFILE_ID HDP_SINK_PROFILE_ID + +/* + * Attribute identifier codes + */ +#define SDP_SERVER_RECORD_HANDLE 0x0000 + +/* + * Possible values for attribute-id are listed below. + * See SDP Spec, section "Service Attribute Definitions" for more details. + */ +#define SDP_ATTR_RECORD_HANDLE 0x0000 +#define SDP_ATTR_SVCLASS_ID_LIST 0x0001 +#define SDP_ATTR_RECORD_STATE 0x0002 +#define SDP_ATTR_SERVICE_ID 0x0003 +#define SDP_ATTR_PROTO_DESC_LIST 0x0004 +#define SDP_ATTR_BROWSE_GRP_LIST 0x0005 +#define SDP_ATTR_LANG_BASE_ATTR_ID_LIST 0x0006 +#define SDP_ATTR_SVCINFO_TTL 0x0007 +#define SDP_ATTR_SERVICE_AVAILABILITY 0x0008 +#define SDP_ATTR_PFILE_DESC_LIST 0x0009 +#define SDP_ATTR_DOC_URL 0x000a +#define SDP_ATTR_CLNT_EXEC_URL 0x000b +#define SDP_ATTR_ICON_URL 0x000c +#define SDP_ATTR_ADD_PROTO_DESC_LIST 0x000d + +#define SDP_ATTR_GROUP_ID 0x0200 +#define SDP_ATTR_IP_SUBNET 0x0200 +#define SDP_ATTR_VERSION_NUM_LIST 0x0200 +#define SDP_ATTR_SUPPORTED_FEATURES_LIST 0x0200 +#define SDP_ATTR_GOEP_L2CAP_PSM 0x0200 +#define SDP_ATTR_SVCDB_STATE 0x0201 + +#define SDP_ATTR_MPSD_SCENARIOS 0x0200 +#define SDP_ATTR_MPMD_SCENARIOS 0x0201 +#define SDP_ATTR_MPS_DEPENDENCIES 0x0202 + +#define SDP_ATTR_SERVICE_VERSION 0x0300 +#define SDP_ATTR_EXTERNAL_NETWORK 0x0301 +#define SDP_ATTR_SUPPORTED_DATA_STORES_LIST 0x0301 +#define SDP_ATTR_DATA_EXCHANGE_SPEC 0x0301 +#define SDP_ATTR_NETWORK 0x0301 +#define SDP_ATTR_FAX_CLASS1_SUPPORT 0x0302 +#define SDP_ATTR_REMOTE_AUDIO_VOLUME_CONTROL 0x0302 +#define SDP_ATTR_MCAP_SUPPORTED_PROCEDURES 0x0302 +#define SDP_ATTR_FAX_CLASS20_SUPPORT 0x0303 +#define SDP_ATTR_SUPPORTED_FORMATS_LIST 0x0303 +#define SDP_ATTR_FAX_CLASS2_SUPPORT 0x0304 +#define SDP_ATTR_AUDIO_FEEDBACK_SUPPORT 0x0305 +#define SDP_ATTR_NETWORK_ADDRESS 0x0306 +#define SDP_ATTR_WAP_GATEWAY 0x0307 +#define SDP_ATTR_HOMEPAGE_URL 0x0308 +#define SDP_ATTR_WAP_STACK_TYPE 0x0309 +#define SDP_ATTR_SECURITY_DESC 0x030a +#define SDP_ATTR_NET_ACCESS_TYPE 0x030b +#define SDP_ATTR_MAX_NET_ACCESSRATE 0x030c +#define SDP_ATTR_IP4_SUBNET 0x030d +#define SDP_ATTR_IP6_SUBNET 0x030e +#define SDP_ATTR_SUPPORTED_CAPABILITIES 0x0310 +#define SDP_ATTR_SUPPORTED_FEATURES 0x0311 +#define SDP_ATTR_SUPPORTED_FUNCTIONS 0x0312 +#define SDP_ATTR_TOTAL_IMAGING_DATA_CAPACITY 0x0313 +#define SDP_ATTR_SUPPORTED_REPOSITORIES 0x0314 +#define SDP_ATTR_MAS_INSTANCE_ID 0x0315 +#define SDP_ATTR_SUPPORTED_MESSAGE_TYPES 0x0316 +#define SDP_ATTR_PBAP_SUPPORTED_FEATURES 0x0317 +#define SDP_ATTR_MAP_SUPPORTED_FEATURES 0x0317 + +#define SDP_ATTR_SPECIFICATION_ID 0x0200 +#define SDP_ATTR_VENDOR_ID 0x0201 +#define SDP_ATTR_PRODUCT_ID 0x0202 +#define SDP_ATTR_VERSION 0x0203 +#define SDP_ATTR_PRIMARY_RECORD 0x0204 +#define SDP_ATTR_VENDOR_ID_SOURCE 0x0205 + +#define SDP_ATTR_HID_DEVICE_RELEASE_NUMBER 0x0200 +#define SDP_ATTR_HID_PARSER_VERSION 0x0201 +#define SDP_ATTR_HID_DEVICE_SUBCLASS 0x0202 +#define SDP_ATTR_HID_COUNTRY_CODE 0x0203 +#define SDP_ATTR_HID_VIRTUAL_CABLE 0x0204 +#define SDP_ATTR_HID_RECONNECT_INITIATE 0x0205 +#define SDP_ATTR_HID_DESCRIPTOR_LIST 0x0206 +#define SDP_ATTR_HID_LANG_ID_BASE_LIST 0x0207 +#define SDP_ATTR_HID_SDP_DISABLE 0x0208 +#define SDP_ATTR_HID_BATTERY_POWER 0x0209 +#define SDP_ATTR_HID_REMOTE_WAKEUP 0x020a +#define SDP_ATTR_HID_PROFILE_VERSION 0x020b +#define SDP_ATTR_HID_SUPERVISION_TIMEOUT 0x020c +#define SDP_ATTR_HID_NORMALLY_CONNECTABLE 0x020d +#define SDP_ATTR_HID_BOOT_DEVICE 0x020e + +/* + * These identifiers are based on the SDP spec stating that + * "base attribute id of the primary (universal) language must be 0x0100" + * + * Other languages should have their own offset; e.g.: + * #define XXXLangBase yyyy + * #define AttrServiceName_XXX 0x0000+XXXLangBase + */ +#define SDP_PRIMARY_LANG_BASE 0x0100 + +#define SDP_ATTR_SVCNAME_PRIMARY 0x0000 + SDP_PRIMARY_LANG_BASE +#define SDP_ATTR_SVCDESC_PRIMARY 0x0001 + SDP_PRIMARY_LANG_BASE +#define SDP_ATTR_PROVNAME_PRIMARY 0x0002 + SDP_PRIMARY_LANG_BASE + +/* + * The Data representation in SDP PDUs (pps 339, 340 of BT SDP Spec) + * These are the exact data type+size descriptor values + * that go into the PDU buffer. + * + * The datatype (leading 5bits) + size descriptor (last 3 bits) + * is 8 bits. The size descriptor is critical to extract the + * right number of bytes for the data value from the PDU. + * + * For most basic types, the datatype+size descriptor is + * straightforward. However for constructed types and strings, + * the size of the data is in the next "n" bytes following the + * 8 bits (datatype+size) descriptor. Exactly what the "n" is + * specified in the 3 bits of the data size descriptor. + * + * TextString and URLString can be of size 2^{8, 16, 32} bytes + * DataSequence and DataSequenceAlternates can be of size 2^{8, 16, 32} + * The size are computed post-facto in the API and are not known apriori + */ +#define SDP_DATA_NIL 0x00 +#define SDP_UINT8 0x08 +#define SDP_UINT16 0x09 +#define SDP_UINT32 0x0A +#define SDP_UINT64 0x0B +#define SDP_UINT128 0x0C +#define SDP_INT8 0x10 +#define SDP_INT16 0x11 +#define SDP_INT32 0x12 +#define SDP_INT64 0x13 +#define SDP_INT128 0x14 +#define SDP_UUID_UNSPEC 0x18 +#define SDP_UUID16 0x19 +#define SDP_UUID32 0x1A +#define SDP_UUID128 0x1C +#define SDP_TEXT_STR_UNSPEC 0x20 +#define SDP_TEXT_STR8 0x25 +#define SDP_TEXT_STR16 0x26 +#define SDP_TEXT_STR32 0x27 +#define SDP_BOOL 0x28 +#define SDP_SEQ_UNSPEC 0x30 +#define SDP_SEQ8 0x35 +#define SDP_SEQ16 0x36 +#define SDP_SEQ32 0x37 +#define SDP_ALT_UNSPEC 0x38 +#define SDP_ALT8 0x3D +#define SDP_ALT16 0x3E +#define SDP_ALT32 0x3F +#define SDP_URL_STR_UNSPEC 0x40 +#define SDP_URL_STR8 0x45 +#define SDP_URL_STR16 0x46 +#define SDP_URL_STR32 0x47 + +/* + * The PDU identifiers of SDP packets between client and server + */ +#define SDP_ERROR_RSP 0x01 +#define SDP_SVC_SEARCH_REQ 0x02 +#define SDP_SVC_SEARCH_RSP 0x03 +#define SDP_SVC_ATTR_REQ 0x04 +#define SDP_SVC_ATTR_RSP 0x05 +#define SDP_SVC_SEARCH_ATTR_REQ 0x06 +#define SDP_SVC_SEARCH_ATTR_RSP 0x07 + +/* + * Some additions to support service registration. + * These are outside the scope of the Bluetooth specification + */ +#define SDP_SVC_REGISTER_REQ 0x75 +#define SDP_SVC_REGISTER_RSP 0x76 +#define SDP_SVC_UPDATE_REQ 0x77 +#define SDP_SVC_UPDATE_RSP 0x78 +#define SDP_SVC_REMOVE_REQ 0x79 +#define SDP_SVC_REMOVE_RSP 0x80 + +/* + * SDP Error codes + */ +#define SDP_INVALID_VERSION 0x0001 +#define SDP_INVALID_RECORD_HANDLE 0x0002 +#define SDP_INVALID_SYNTAX 0x0003 +#define SDP_INVALID_PDU_SIZE 0x0004 +#define SDP_INVALID_CSTATE 0x0005 + +/* + * SDP PDU + */ +typedef struct { + uint8_t pdu_id; + uint16_t tid; + uint16_t plen; +} __attribute__ ((packed)) sdp_pdu_hdr_t; + +/* + * Common definitions for attributes in the SDP. + * Should the type of any of these change, you need only make a change here. + */ + +typedef struct { + uint8_t type; + union { + uint16_t uuid16; + uint32_t uuid32; + uint128_t uuid128; + } value; +} uuid_t; + +#define SDP_IS_UUID(x) ((x) == SDP_UUID16 || (x) == SDP_UUID32 || \ + (x) == SDP_UUID128) +#define SDP_IS_ALT(x) ((x) == SDP_ALT8 || (x) == SDP_ALT16 || (x) == SDP_ALT32) +#define SDP_IS_SEQ(x) ((x) == SDP_SEQ8 || (x) == SDP_SEQ16 || (x) == SDP_SEQ32) +#define SDP_IS_TEXT_STR(x) ((x) == SDP_TEXT_STR8 || (x) == SDP_TEXT_STR16 || \ + (x) == SDP_TEXT_STR32) + +typedef struct _sdp_list sdp_list_t; +struct _sdp_list { + sdp_list_t *next; + void *data; +}; + +/* + * User-visible strings can be in many languages + * in addition to the universal language. + * + * Language meta-data includes language code in ISO639 + * followed by the encoding format. The third field in this + * structure is the attribute offset for the language. + * User-visible strings in the specified language can be + * obtained at this offset. + */ +typedef struct { + uint16_t code_ISO639; + uint16_t encoding; + uint16_t base_offset; +} sdp_lang_attr_t; + +/* + * Profile descriptor is the Bluetooth profile metadata. If a + * service conforms to a well-known profile, then its profile + * identifier (UUID) is an attribute of the service. In addition, + * if the profile has a version number it is specified here. + */ +typedef struct { + uuid_t uuid; + uint16_t version; +} sdp_profile_desc_t; + +typedef struct { + uint8_t major; + uint8_t minor; +} sdp_version_t; + +typedef struct { + uint8_t *data; + uint32_t data_size; + uint32_t buf_size; +} sdp_buf_t; + +typedef struct { + uint32_t handle; + + /* Search pattern: a sequence of all UUIDs seen in this record */ + sdp_list_t *pattern; + sdp_list_t *attrlist; + + /* Main service class for Extended Inquiry Response */ + uuid_t svclass; +} sdp_record_t; + +typedef struct sdp_data_struct sdp_data_t; +struct sdp_data_struct { + uint8_t dtd; + uint16_t attrId; + union { + int8_t int8; + int16_t int16; + int32_t int32; + int64_t int64; + uint128_t int128; + uint8_t uint8; + uint16_t uint16; + uint32_t uint32; + uint64_t uint64; + uint128_t uint128; + uuid_t uuid; + char *str; + sdp_data_t *dataseq; + } val; + sdp_data_t *next; + int unitSize; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __SDP_H */ diff --git a/lib/sdp_lib.h b/lib/sdp_lib.h new file mode 100644 index 0000000..3ded393 --- /dev/null +++ b/lib/sdp_lib.h @@ -0,0 +1,634 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __SDP_LIB_H +#define __SDP_LIB_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * SDP lists + */ +typedef void(*sdp_list_func_t)(void *, void *); +typedef void(*sdp_free_func_t)(void *); +typedef int (*sdp_comp_func_t)(const void *, const void *); + +sdp_list_t *sdp_list_append(sdp_list_t *list, void *d); +sdp_list_t *sdp_list_remove(sdp_list_t *list, void *d); +sdp_list_t *sdp_list_insert_sorted(sdp_list_t *list, void *data, sdp_comp_func_t f); +void sdp_list_free(sdp_list_t *list, sdp_free_func_t f); + +static inline int sdp_list_len(const sdp_list_t *list) +{ + int n = 0; + for (; list; list = list->next) + n++; + return n; +} + +static inline sdp_list_t *sdp_list_find(sdp_list_t *list, void *u, sdp_comp_func_t f) +{ + for (; list; list = list->next) + if (f(list->data, u) == 0) + return list; + return NULL; +} + +static inline void sdp_list_foreach(sdp_list_t *list, sdp_list_func_t f, void *u) +{ + for (; list; list = list->next) + f(list->data, u); +} + +/* + * Values of the flags parameter to sdp_record_register + */ +#define SDP_RECORD_PERSIST 0x01 +#define SDP_DEVICE_RECORD 0x02 + +/* + * Values of the flags parameter to sdp_connect + */ +#define SDP_RETRY_IF_BUSY 0x01 +#define SDP_WAIT_ON_CLOSE 0x02 +#define SDP_NON_BLOCKING 0x04 +#define SDP_LARGE_MTU 0x08 + +/* + * a session with an SDP server + */ +typedef struct { + int sock; + int state; + int local; + int flags; + uint16_t tid; /* Current transaction ID */ + void *priv; +} sdp_session_t; + +typedef enum { + /* + * Attributes are specified as individual elements + */ + SDP_ATTR_REQ_INDIVIDUAL = 1, + /* + * Attributes are specified as a range + */ + SDP_ATTR_REQ_RANGE +} sdp_attrreq_type_t; + +/* + * When the pdu_id(type) is a sdp error response, check the status value + * to figure out the error reason. For status values 0x0001-0x0006 check + * Bluetooth SPEC. If the status is 0xffff, call sdp_get_error function + * to get the real reason: + * - wrong transaction ID(EPROTO) + * - wrong PDU id or(EPROTO) + * - I/O error + */ +typedef void sdp_callback_t(uint8_t type, uint16_t status, uint8_t *rsp, size_t size, void *udata); + +/* + * create an L2CAP connection to a Bluetooth device + * + * INPUT: + * + * bdaddr_t *src: + * Address of the local device to use to make the connection + * (or BDADDR_ANY) + * + * bdaddr_t *dst: + * Address of the SDP server device + */ +sdp_session_t *sdp_connect(const bdaddr_t *src, const bdaddr_t *dst, uint32_t flags); +int sdp_close(sdp_session_t *session); +int sdp_get_socket(const sdp_session_t *session); + +/* + * SDP transaction: functions for asynchronous search. + */ +sdp_session_t *sdp_create(int sk, uint32_t flags); +int sdp_get_error(sdp_session_t *session); +int sdp_process(sdp_session_t *session); +int sdp_set_notify(sdp_session_t *session, sdp_callback_t *func, void *udata); + +int sdp_service_search_async(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num); +int sdp_service_attr_async(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list); +int sdp_service_search_attr_async(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list); + +uint16_t sdp_gen_tid(sdp_session_t *session); + +/* + * find all devices in the piconet + */ +int sdp_general_inquiry(inquiry_info *ii, int dev_num, int duration, uint8_t *found); + +/* flexible extraction of basic attributes - Jean II */ +int sdp_get_int_attr(const sdp_record_t *rec, uint16_t attr, int *value); +int sdp_get_string_attr(const sdp_record_t *rec, uint16_t attr, char *value, int valuelen); + +/* + * Basic sdp data functions + */ +sdp_data_t *sdp_data_alloc(uint8_t dtd, const void *value); +sdp_data_t *sdp_data_alloc_with_length(uint8_t dtd, const void *value, uint32_t length); +void sdp_data_free(sdp_data_t *data); +sdp_data_t *sdp_data_get(const sdp_record_t *rec, uint16_t attr_id); + +sdp_data_t *sdp_seq_alloc(void **dtds, void **values, int len); +sdp_data_t *sdp_seq_alloc_with_length(void **dtds, void **values, int *length, int len); +sdp_data_t *sdp_seq_append(sdp_data_t *seq, sdp_data_t *data); + +int sdp_attr_add(sdp_record_t *rec, uint16_t attr, sdp_data_t *data); +void sdp_attr_remove(sdp_record_t *rec, uint16_t attr); +void sdp_attr_replace(sdp_record_t *rec, uint16_t attr, sdp_data_t *data); +int sdp_set_uuidseq_attr(sdp_record_t *rec, uint16_t attr, sdp_list_t *seq); +int sdp_get_uuidseq_attr(const sdp_record_t *rec, uint16_t attr, sdp_list_t **seqp); + +/* + * NOTE that none of the functions below will update the SDP server, + * unless the {register, update}sdp_record_t() function is invoked. + * All functions which return an integer value, return 0 on success + * or -1 on failure. + */ + +/* + * Create an attribute and add it to the service record's attribute list. + * This consists of the data type descriptor of the attribute, + * the value of the attribute and the attribute identifier. + */ +int sdp_attr_add_new(sdp_record_t *rec, uint16_t attr, uint8_t dtd, const void *p); + +/* + * Set the information attributes of the service record. + * The set of attributes comprises service name, description + * and provider name + */ +void sdp_set_info_attr(sdp_record_t *rec, const char *name, const char *prov, const char *desc); + +/* + * Set the ServiceClassID attribute to the sequence specified by seq. + * Note that the identifiers need to be in sorted order from the most + * specific to the most generic service class that this service + * conforms to. + */ +static inline int sdp_set_service_classes(sdp_record_t *rec, sdp_list_t *seq) +{ + return sdp_set_uuidseq_attr(rec, SDP_ATTR_SVCLASS_ID_LIST, seq); +} + +/* + * Get the service classes to which the service conforms. + * + * When set, the list contains elements of ServiceClassIdentifer(uint16_t) + * ordered from most specific to most generic + */ +static inline int sdp_get_service_classes(const sdp_record_t *rec, sdp_list_t **seqp) +{ + return sdp_get_uuidseq_attr(rec, SDP_ATTR_SVCLASS_ID_LIST, seqp); +} + +/* + * Set the BrowseGroupList attribute to the list specified by seq. + * + * A service can belong to one or more service groups + * and the list comprises such group identifiers (UUIDs) + */ +static inline int sdp_set_browse_groups(sdp_record_t *rec, sdp_list_t *seq) +{ + return sdp_set_uuidseq_attr(rec, SDP_ATTR_BROWSE_GRP_LIST, seq); +} + +/* + * Set the access protocols of the record to those specified in proto + */ +int sdp_set_access_protos(sdp_record_t *rec, const sdp_list_t *proto); + +/* + * Set the additional access protocols of the record to those specified in proto + */ +int sdp_set_add_access_protos(sdp_record_t *rec, const sdp_list_t *proto); + +/* + * Get protocol port (i.e. PSM for L2CAP, Channel for RFCOMM) + */ +int sdp_get_proto_port(const sdp_list_t *list, int proto); + +/* + * Get protocol descriptor. + */ +sdp_data_t *sdp_get_proto_desc(sdp_list_t *list, int proto); + +/* + * Set the LanguageBase attributes to the values specified in list + * (a linked list of sdp_lang_attr_t objects, one for each language in + * which user-visible attributes are present). + */ +int sdp_set_lang_attr(sdp_record_t *rec, const sdp_list_t *list); + +/* + * Set the ServiceInfoTimeToLive attribute of the service. + * This is the number of seconds that this record is guaranteed + * not to change after being obtained by a client. + */ +static inline int sdp_set_service_ttl(sdp_record_t *rec, uint32_t ttl) +{ + return sdp_attr_add_new(rec, SDP_ATTR_SVCINFO_TTL, SDP_UINT32, &ttl); +} + +/* + * Set the ServiceRecordState attribute of a service. This is + * guaranteed to change if there is any kind of modification to + * the record. + */ +static inline int sdp_set_record_state(sdp_record_t *rec, uint32_t state) +{ + return sdp_attr_add_new(rec, SDP_ATTR_RECORD_STATE, SDP_UINT32, &state); +} + +/* + * Set the ServiceID attribute of a service. + */ +void sdp_set_service_id(sdp_record_t *rec, uuid_t uuid); + +/* + * Set the GroupID attribute of a service + */ +void sdp_set_group_id(sdp_record_t *rec, uuid_t grouuuid); + +/* + * Set the ServiceAvailability attribute of a service. + * + * Note that this represents the relative availability + * of the service: 0x00 means completely unavailable; + * 0xFF means maximum availability. + */ +static inline int sdp_set_service_avail(sdp_record_t *rec, uint8_t avail) +{ + return sdp_attr_add_new(rec, SDP_ATTR_SERVICE_AVAILABILITY, SDP_UINT8, &avail); +} + +/* + * Set the profile descriptor list attribute of a record. + * + * Each element in the list is an object of type + * sdp_profile_desc_t which is a definition of the + * Bluetooth profile that this service conforms to. + */ +int sdp_set_profile_descs(sdp_record_t *rec, const sdp_list_t *desc); + +/* + * Set URL attributes of a record. + * + * ClientExecutableURL: a URL to a client's platform specific (WinCE, + * PalmOS) executable code that can be used to access this service. + * + * DocumentationURL: a URL pointing to service documentation + * + * IconURL: a URL to an icon that can be used to represent this service. + * + * Note: pass NULL for any URLs that you don't want to set or remove + */ +void sdp_set_url_attr(sdp_record_t *rec, const char *clientExecURL, const char *docURL, const char *iconURL); + +/* + * a service search request. + * + * INPUT : + * + * sdp_list_t *search + * list containing elements of the search + * pattern. Each entry in the list is a UUID + * of the service to be searched + * + * uint16_t max_rec_num + * An integer specifying the maximum number of + * entries that the client can handle in the response. + * + * OUTPUT : + * + * int return value + * 0 + * The request completed successfully. This does not + * mean the requested services were found + * -1 + * The request completed unsuccessfully + * + * sdp_list_t *rsp_list + * This variable is set on a successful return if there are + * non-zero service handles. It is a singly linked list of + * service record handles (uint16_t) + */ +int sdp_service_search_req(sdp_session_t *session, const sdp_list_t *search, uint16_t max_rec_num, sdp_list_t **rsp_list); + +/* + * a service attribute request. + * + * INPUT : + * + * uint32_t handle + * The handle of the service for which the attribute(s) are + * requested + * + * sdp_attrreq_type_t reqtype + * Attribute identifiers are 16 bit unsigned integers specified + * in one of 2 ways described below : + * SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers + * They are the actual attribute identifiers in ascending order + * + * SDP_ATTR_REQ_RANGE - 32bit identifier range + * The high-order 16bits is the start of range + * the low-order 16bits are the end of range + * 0x0000 to 0xFFFF gets all attributes + * + * sdp_list_t *attrid_list + * Singly linked list containing attribute identifiers desired. + * Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL) + * or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE) + * + * OUTPUT : + * int return value + * 0 + * The request completed successfully. This does not + * mean the requested services were found + * -1 + * The request completed unsuccessfully due to a timeout + */ +sdp_record_t *sdp_service_attr_req(sdp_session_t *session, uint32_t handle, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list); + +/* + * This is a service search request combined with the service + * attribute request. First a service class match is done and + * for matching service, requested attributes are extracted + * + * INPUT : + * + * sdp_list_t *search + * Singly linked list containing elements of the search + * pattern. Each entry in the list is a UUID(DataTypeSDP_UUID16) + * of the service to be searched + * + * AttributeSpecification attrSpec + * Attribute identifiers are 16 bit unsigned integers specified + * in one of 2 ways described below : + * SDP_ATTR_REQ_INDIVIDUAL - 16bit individual identifiers + * They are the actual attribute identifiers in ascending order + * + * SDP_ATTR_REQ_RANGE - 32bit identifier range + * The high-order 16bits is the start of range + * the low-order 16bits are the end of range + * 0x0000 to 0xFFFF gets all attributes + * + * sdp_list_t *attrid_list + * Singly linked list containing attribute identifiers desired. + * Every element is either a uint16_t(attrSpec = SDP_ATTR_REQ_INDIVIDUAL) + * or a uint32_t(attrSpec=SDP_ATTR_REQ_RANGE) + * + * OUTPUT : + * int return value + * 0 + * The request completed successfully. This does not + * mean the requested services were found + * -1 + * The request completed unsuccessfully due to a timeout + * + * sdp_list_t *rsp_list + * This variable is set on a successful return to point to + * service(s) found. Each element of this list is of type + * sdp_record_t *. + */ +int sdp_service_search_attr_req(sdp_session_t *session, const sdp_list_t *search, sdp_attrreq_type_t reqtype, const sdp_list_t *attrid_list, sdp_list_t **rsp_list); + +/* + * Allocate/free a service record and its attributes + */ +sdp_record_t *sdp_record_alloc(void); +void sdp_record_free(sdp_record_t *rec); + +/* + * Register a service record. + * + * Note: It is the responsbility of the Service Provider to create the + * record first and set its attributes using setXXX() methods. + * + * The service provider must then call sdp_record_register() to make + * the service record visible to SDP clients. This function returns 0 + * on success or -1 on failure (and sets errno). + */ +int sdp_device_record_register_binary(sdp_session_t *session, bdaddr_t *device, uint8_t *data, uint32_t size, uint8_t flags, uint32_t *handle); +int sdp_device_record_register(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec, uint8_t flags); +int sdp_record_register(sdp_session_t *session, sdp_record_t *rec, uint8_t flags); + +/* + * Unregister a service record. + */ +int sdp_device_record_unregister_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle); +int sdp_device_record_unregister(sdp_session_t *session, bdaddr_t *device, sdp_record_t *rec); +int sdp_record_unregister(sdp_session_t *session, sdp_record_t *rec); + +/* + * Update an existing service record. (Calling this function + * before a previous call to sdp_record_register() will result + * in an error.) + */ +int sdp_device_record_update_binary(sdp_session_t *session, bdaddr_t *device, uint32_t handle, uint8_t *data, uint32_t size); +int sdp_device_record_update(sdp_session_t *session, bdaddr_t *device, const sdp_record_t *rec); +int sdp_record_update(sdp_session_t *sess, const sdp_record_t *rec); + +void sdp_record_print(const sdp_record_t *rec); + +/* + * UUID functions + */ +uuid_t *sdp_uuid16_create(uuid_t *uuid, uint16_t data); +uuid_t *sdp_uuid32_create(uuid_t *uuid, uint32_t data); +uuid_t *sdp_uuid128_create(uuid_t *uuid, const void *data); +int sdp_uuid16_cmp(const void *p1, const void *p2); +int sdp_uuid128_cmp(const void *p1, const void *p2); +int sdp_uuid_cmp(const void *p1, const void *p2); +uuid_t *sdp_uuid_to_uuid128(const uuid_t *uuid); +void sdp_uuid16_to_uuid128(uuid_t *uuid128, const uuid_t *uuid16); +void sdp_uuid32_to_uuid128(uuid_t *uuid128, const uuid_t *uuid32); +int sdp_uuid128_to_uuid(uuid_t *uuid); +int sdp_uuid_to_proto(uuid_t *uuid); +int sdp_uuid_extract(const uint8_t *buffer, int bufsize, uuid_t *uuid, int *scanned); +void sdp_uuid_print(const uuid_t *uuid); + +#define MAX_LEN_UUID_STR 37 +#define MAX_LEN_PROTOCOL_UUID_STR 8 +#define MAX_LEN_SERVICECLASS_UUID_STR 28 +#define MAX_LEN_PROFILEDESCRIPTOR_UUID_STR 28 + +int sdp_uuid2strn(const uuid_t *uuid, char *str, size_t n); +int sdp_proto_uuid2strn(const uuid_t *uuid, char *str, size_t n); +int sdp_svclass_uuid2strn(const uuid_t *uuid, char *str, size_t n); +int sdp_profile_uuid2strn(const uuid_t *uuid, char *str, size_t n); + +/* + * In all the sdp_get_XXX(handle, XXX *xxx) functions below, + * the XXX * is set to point to the value, should it exist + * and 0 is returned. If the value does not exist, -1 is + * returned and errno set to ENODATA. + * + * In all the methods below, the memory management rules are + * simple. Don't free anything! The pointer returned, in the + * case of constructed types, is a pointer to the contents + * of the sdp_record_t. + */ + +/* + * Get the access protocols from the service record + */ +int sdp_get_access_protos(const sdp_record_t *rec, sdp_list_t **protos); + +/* + * Get the additional access protocols from the service record + */ +int sdp_get_add_access_protos(const sdp_record_t *rec, sdp_list_t **protos); + +/* + * Extract the list of browse groups to which the service belongs. + * When set, seqp contains elements of GroupID (uint16_t) + */ +static inline int sdp_get_browse_groups(const sdp_record_t *rec, sdp_list_t **seqp) +{ + return sdp_get_uuidseq_attr(rec, SDP_ATTR_BROWSE_GRP_LIST, seqp); +} + +/* + * Extract language attribute meta-data of the service record. + * For each language in the service record, LangSeq has a struct of type + * sdp_lang_attr_t. + */ +int sdp_get_lang_attr(const sdp_record_t *rec, sdp_list_t **langSeq); + +/* + * Extract the Bluetooth profile descriptor sequence from a record. + * Each element in the list is of type sdp_profile_desc_t + * which contains the UUID of the profile and its version number + * (encoded as major and minor in the high-order 8bits + * and low-order 8bits respectively of the uint16_t) + */ +int sdp_get_profile_descs(const sdp_record_t *rec, sdp_list_t **profDesc); + +/* + * Extract SDP server version numbers + * + * Note: that this is an attribute of the SDP server only and + * contains a list of uint16_t each of which represent the + * major and minor SDP version numbers supported by this server + */ +int sdp_get_server_ver(const sdp_record_t *rec, sdp_list_t **pVnumList); + +int sdp_get_service_id(const sdp_record_t *rec, uuid_t *uuid); +int sdp_get_group_id(const sdp_record_t *rec, uuid_t *uuid); +int sdp_get_record_state(const sdp_record_t *rec, uint32_t *svcRecState); +int sdp_get_service_avail(const sdp_record_t *rec, uint8_t *svcAvail); +int sdp_get_service_ttl(const sdp_record_t *rec, uint32_t *svcTTLInfo); +int sdp_get_database_state(const sdp_record_t *rec, uint32_t *svcDBState); + +static inline int sdp_get_service_name(const sdp_record_t *rec, char *str, int len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_SVCNAME_PRIMARY, str, len); +} + +static inline int sdp_get_service_desc(const sdp_record_t *rec, char *str, int len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_SVCDESC_PRIMARY, str, len); +} + +static inline int sdp_get_provider_name(const sdp_record_t *rec, char *str, int len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_PROVNAME_PRIMARY, str, len); +} + +static inline int sdp_get_doc_url(const sdp_record_t *rec, char *str, int len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_DOC_URL, str, len); +} + +static inline int sdp_get_clnt_exec_url(const sdp_record_t *rec, char *str, int len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_CLNT_EXEC_URL, str, len); +} + +static inline int sdp_get_icon_url(const sdp_record_t *rec, char *str, int len) +{ + return sdp_get_string_attr(rec, SDP_ATTR_ICON_URL, str, len); +} + +/* + * Set the supported features + * sf should be a list of list with each feature data + * Returns 0 on success -1 on fail + */ +int sdp_set_supp_feat(sdp_record_t *rec, const sdp_list_t *sf); + +/* + * Get the supported features + * seqp is set to a list of list with each feature data + * Returns 0 on success, if an error occurred -1 is returned and errno is set + */ +int sdp_get_supp_feat(const sdp_record_t *rec, sdp_list_t **seqp); + +sdp_record_t *sdp_extract_pdu(const uint8_t *pdata, int bufsize, int *scanned); +sdp_record_t *sdp_copy_record(sdp_record_t *rec); + +void sdp_data_print(sdp_data_t *data); +void sdp_print_service_attr(sdp_list_t *alist); + +int sdp_attrid_comp_func(const void *key1, const void *key2); + +void sdp_set_seq_len(uint8_t *ptr, uint32_t length); +void sdp_set_attrid(sdp_buf_t *pdu, uint16_t id); +void sdp_append_to_pdu(sdp_buf_t *dst, sdp_data_t *d); +void sdp_append_to_buf(sdp_buf_t *dst, uint8_t *data, uint32_t len); + +int sdp_gen_pdu(sdp_buf_t *pdu, sdp_data_t *data); +int sdp_gen_record_pdu(const sdp_record_t *rec, sdp_buf_t *pdu); + +int sdp_extract_seqtype(const uint8_t *buf, int bufsize, uint8_t *dtdp, int *size); + +sdp_data_t *sdp_extract_attr(const uint8_t *pdata, int bufsize, int *extractedLength, sdp_record_t *rec); + +void sdp_pattern_add_uuid(sdp_record_t *rec, uuid_t *uuid); +void sdp_pattern_add_uuidseq(sdp_record_t *rec, sdp_list_t *seq); + +int sdp_send_req_w4_rsp(sdp_session_t *session, uint8_t *req, uint8_t *rsp, uint32_t reqsize, uint32_t *rspsize); + +void sdp_add_lang_attr(sdp_record_t *rec); + +#ifdef __cplusplus +} +#endif + +#endif /* __SDP_LIB_H */ diff --git a/lib/uuid.c b/lib/uuid.c new file mode 100644 index 0000000..d4c7002 --- /dev/null +++ b/lib/uuid.c @@ -0,0 +1,312 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "lib/bluetooth.h" +#include "uuid.h" + +static uint128_t bluetooth_base_uuid = { + .data = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB } +}; + +#define BASE_UUID16_OFFSET 2 +#define BASE_UUID32_OFFSET 0 + +static void bt_uuid16_to_uuid128(const bt_uuid_t *src, bt_uuid_t *dst) +{ + uint16_t be16; + + dst->value.u128 = bluetooth_base_uuid; + dst->type = BT_UUID128; + + /* + * No matter the system: 128-bit UUIDs should be stored + * as big-endian. 16-bit UUIDs are stored on host order. + */ + + be16 = htons(src->value.u16); + memcpy(&dst->value.u128.data[BASE_UUID16_OFFSET], &be16, sizeof(be16)); +} + +static void bt_uuid32_to_uuid128(const bt_uuid_t *src, bt_uuid_t *dst) +{ + uint32_t be32; + + dst->value.u128 = bluetooth_base_uuid; + dst->type = BT_UUID128; + + /* + * No matter the system: 128-bit UUIDs should be stored + * as big-endian. 32-bit UUIDs are stored on host order. + */ + + be32 = htonl(src->value.u32); + memcpy(&dst->value.u128.data[BASE_UUID32_OFFSET], &be32, sizeof(be32)); +} + +void bt_uuid_to_uuid128(const bt_uuid_t *src, bt_uuid_t *dst) +{ + switch (src->type) { + case BT_UUID128: + *dst = *src; + break; + case BT_UUID32: + bt_uuid32_to_uuid128(src, dst); + break; + case BT_UUID16: + bt_uuid16_to_uuid128(src, dst); + break; + case BT_UUID_UNSPEC: + default: + break; + } +} + +static int bt_uuid128_cmp(const bt_uuid_t *u1, const bt_uuid_t *u2) +{ + return memcmp(&u1->value.u128, &u2->value.u128, sizeof(uint128_t)); +} + +int bt_uuid16_create(bt_uuid_t *btuuid, uint16_t value) +{ + memset(btuuid, 0, sizeof(bt_uuid_t)); + btuuid->type = BT_UUID16; + btuuid->value.u16 = value; + + return 0; +} + +int bt_uuid32_create(bt_uuid_t *btuuid, uint32_t value) +{ + memset(btuuid, 0, sizeof(bt_uuid_t)); + btuuid->type = BT_UUID32; + btuuid->value.u32 = value; + + return 0; +} + +int bt_uuid128_create(bt_uuid_t *btuuid, uint128_t value) +{ + memset(btuuid, 0, sizeof(bt_uuid_t)); + btuuid->type = BT_UUID128; + btuuid->value.u128 = value; + + return 0; +} + +int bt_uuid_cmp(const bt_uuid_t *uuid1, const bt_uuid_t *uuid2) +{ + bt_uuid_t u1, u2; + + bt_uuid_to_uuid128(uuid1, &u1); + bt_uuid_to_uuid128(uuid2, &u2); + + return bt_uuid128_cmp(&u1, &u2); +} + +/* + * convert the UUID to string, copying a maximum of n characters. + */ +int bt_uuid_to_string(const bt_uuid_t *uuid, char *str, size_t n) +{ + bt_uuid_t tmp; + unsigned int data0; + unsigned short data1; + unsigned short data2; + unsigned short data3; + unsigned int data4; + unsigned short data5; + const uint8_t *data; + + if (!uuid || uuid->type == BT_UUID_UNSPEC) { + snprintf(str, n, "NULL"); + return -EINVAL; + } + + /* Convert to 128 Bit format */ + bt_uuid_to_uuid128(uuid, &tmp); + data = (uint8_t *) &tmp.value.u128; + + memcpy(&data0, &data[0], 4); + memcpy(&data1, &data[4], 2); + memcpy(&data2, &data[6], 2); + memcpy(&data3, &data[8], 2); + memcpy(&data4, &data[10], 4); + memcpy(&data5, &data[14], 2); + + snprintf(str, n, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x", + ntohl(data0), ntohs(data1), + ntohs(data2), ntohs(data3), + ntohl(data4), ntohs(data5)); + + return 0; +} + +static inline int is_uuid128(const char *string) +{ + return (strlen(string) == 36 && + string[8] == '-' && + string[13] == '-' && + string[18] == '-' && + string[23] == '-'); +} + +static inline int is_base_uuid128(const char *string) +{ + uint16_t uuid; + char dummy[2]; + + if (!is_uuid128(string)) + return 0; + + return sscanf(string, + "0000%04hx-0000-1000-8000-00805%1[fF]9%1[bB]34%1[fF]%1[bB]", + &uuid, dummy, dummy, dummy, dummy) == 5; +} + +static inline int is_uuid32(const char *string) +{ + return (strlen(string) == 8 || strlen(string) == 10); +} + +static inline int is_uuid16(const char *string) +{ + return (strlen(string) == 4 || strlen(string) == 6); +} + +static int bt_string_to_uuid16(bt_uuid_t *uuid, const char *string) +{ + uint16_t u16; + char *endptr = NULL; + + u16 = strtol(string, &endptr, 16); + if (endptr && (*endptr == '\0' || *endptr == '-')) { + bt_uuid16_create(uuid, u16); + return 0; + } + + return -EINVAL; +} + +static int bt_string_to_uuid32(bt_uuid_t *uuid, const char *string) +{ + uint32_t u32; + char *endptr = NULL; + + u32 = strtol(string, &endptr, 16); + if (endptr && *endptr == '\0') { + bt_uuid32_create(uuid, u32); + return 0; + } + + return -EINVAL; +} + +static int bt_string_to_uuid128(bt_uuid_t *uuid, const char *string) +{ + uint32_t data0, data4; + uint16_t data1, data2, data3, data5; + uint128_t u128; + uint8_t *val = (uint8_t *) &u128; + + if (sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx", + &data0, &data1, &data2, + &data3, &data4, &data5) != 6) + return -EINVAL; + + data0 = htonl(data0); + data1 = htons(data1); + data2 = htons(data2); + data3 = htons(data3); + data4 = htonl(data4); + data5 = htons(data5); + + memcpy(&val[0], &data0, 4); + memcpy(&val[4], &data1, 2); + memcpy(&val[6], &data2, 2); + memcpy(&val[8], &data3, 2); + memcpy(&val[10], &data4, 4); + memcpy(&val[14], &data5, 2); + + bt_uuid128_create(uuid, u128); + + return 0; +} + +int bt_string_to_uuid(bt_uuid_t *uuid, const char *string) +{ + if (is_base_uuid128(string)) + return bt_string_to_uuid16(uuid, string + 4); + else if (is_uuid128(string)) + return bt_string_to_uuid128(uuid, string); + else if (is_uuid32(string)) + return bt_string_to_uuid32(uuid, string); + else if (is_uuid16(string)) + return bt_string_to_uuid16(uuid, string); + + return -EINVAL; +} + +int bt_uuid_strcmp(const void *a, const void *b) +{ + bt_uuid_t u1, u2; + + if (bt_string_to_uuid(&u1, a) < 0) + return -EINVAL; + + if (bt_string_to_uuid(&u2, b) < 0) + return -EINVAL; + + return bt_uuid_cmp(&u1, &u2); +} + +int bt_uuid_to_le(const bt_uuid_t *src, void *dst) +{ + bt_uuid_t uuid; + + switch (src->type) { + case BT_UUID16: + bt_put_le16(src->value.u16, dst); + return 0; + case BT_UUID32: + bt_uuid32_to_uuid128(src, &uuid); + src = &uuid; + /* Fallthrough */ + case BT_UUID128: + /* Convert from 128-bit BE to LE */ + bswap_128(&src->value.u128, dst); + return 0; + case BT_UUID_UNSPEC: + default: + return -EINVAL; + } +} diff --git a/lib/uuid.h b/lib/uuid.h new file mode 100644 index 0000000..fbc08f5 --- /dev/null +++ b/lib/uuid.h @@ -0,0 +1,196 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __BLUETOOTH_UUID_H +#define __BLUETOOTH_UUID_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805f9b34fb" + +#define HSP_HS_UUID "00001108-0000-1000-8000-00805f9b34fb" +#define HSP_AG_UUID "00001112-0000-1000-8000-00805f9b34fb" + +#define HFP_HS_UUID "0000111e-0000-1000-8000-00805f9b34fb" +#define HFP_AG_UUID "0000111f-0000-1000-8000-00805f9b34fb" + +#define ADVANCED_AUDIO_UUID "0000110d-0000-1000-8000-00805f9b34fb" + +#define A2DP_SOURCE_UUID "0000110a-0000-1000-8000-00805f9b34fb" +#define A2DP_SINK_UUID "0000110b-0000-1000-8000-00805f9b34fb" + +#define AVRCP_REMOTE_UUID "0000110e-0000-1000-8000-00805f9b34fb" +#define AVRCP_TARGET_UUID "0000110c-0000-1000-8000-00805f9b34fb" + +#define PANU_UUID "00001115-0000-1000-8000-00805f9b34fb" +#define NAP_UUID "00001116-0000-1000-8000-00805f9b34fb" +#define GN_UUID "00001117-0000-1000-8000-00805f9b34fb" +#define BNEP_SVC_UUID "0000000f-0000-1000-8000-00805f9b34fb" + +#define PNPID_UUID "00002a50-0000-1000-8000-00805f9b34fb" +#define DEVICE_INFORMATION_UUID "0000180a-0000-1000-8000-00805f9b34fb" + +#define GATT_UUID "00001801-0000-1000-8000-00805f9b34fb" +#define IMMEDIATE_ALERT_UUID "00001802-0000-1000-8000-00805f9b34fb" +#define LINK_LOSS_UUID "00001803-0000-1000-8000-00805f9b34fb" +#define TX_POWER_UUID "00001804-0000-1000-8000-00805f9b34fb" +#define BATTERY_UUID "0000180f-0000-1000-8000-00805f9b34fb" +#define SCAN_PARAMETERS_UUID "00001813-0000-1000-8000-00805f9b34fb" + +#define SAP_UUID "0000112D-0000-1000-8000-00805f9b34fb" + +#define HEART_RATE_UUID "0000180d-0000-1000-8000-00805f9b34fb" +#define HEART_RATE_MEASUREMENT_UUID "00002a37-0000-1000-8000-00805f9b34fb" +#define BODY_SENSOR_LOCATION_UUID "00002a38-0000-1000-8000-00805f9b34fb" +#define HEART_RATE_CONTROL_POINT_UUID "00002a39-0000-1000-8000-00805f9b34fb" + +#define HEALTH_THERMOMETER_UUID "00001809-0000-1000-8000-00805f9b34fb" +#define TEMPERATURE_MEASUREMENT_UUID "00002a1c-0000-1000-8000-00805f9b34fb" +#define TEMPERATURE_TYPE_UUID "00002a1d-0000-1000-8000-00805f9b34fb" +#define INTERMEDIATE_TEMPERATURE_UUID "00002a1e-0000-1000-8000-00805f9b34fb" +#define MEASUREMENT_INTERVAL_UUID "00002a21-0000-1000-8000-00805f9b34fb" + +#define CYCLING_SC_UUID "00001816-0000-1000-8000-00805f9b34fb" +#define CSC_MEASUREMENT_UUID "00002a5b-0000-1000-8000-00805f9b34fb" +#define CSC_FEATURE_UUID "00002a5c-0000-1000-8000-00805f9b34fb" +#define SENSOR_LOCATION_UUID "00002a5d-0000-1000-8000-00805f9b34fb" +#define SC_CONTROL_POINT_UUID "00002a55-0000-1000-8000-00805f9b34fb" + +#define RFCOMM_UUID_STR "00000003-0000-1000-8000-00805f9b34fb" + +#define HDP_UUID "00001400-0000-1000-8000-00805f9b34fb" +#define HDP_SOURCE_UUID "00001401-0000-1000-8000-00805f9b34fb" +#define HDP_SINK_UUID "00001402-0000-1000-8000-00805f9b34fb" + +#define HID_UUID "00001124-0000-1000-8000-00805f9b34fb" + +#define DUN_GW_UUID "00001103-0000-1000-8000-00805f9b34fb" + +#define GAP_UUID "00001800-0000-1000-8000-00805f9b34fb" +#define PNP_UUID "00001200-0000-1000-8000-00805f9b34fb" + +#define SPP_UUID "00001101-0000-1000-8000-00805f9b34fb" + +#define OBEX_SYNC_UUID "00001104-0000-1000-8000-00805f9b34fb" +#define OBEX_OPP_UUID "00001105-0000-1000-8000-00805f9b34fb" +#define OBEX_FTP_UUID "00001106-0000-1000-8000-00805f9b34fb" +#define OBEX_PCE_UUID "0000112e-0000-1000-8000-00805f9b34fb" +#define OBEX_PSE_UUID "0000112f-0000-1000-8000-00805f9b34fb" +#define OBEX_PBAP_UUID "00001130-0000-1000-8000-00805f9b34fb" +#define OBEX_MAS_UUID "00001132-0000-1000-8000-00805f9b34fb" +#define OBEX_MNS_UUID "00001133-0000-1000-8000-00805f9b34fb" +#define OBEX_MAP_UUID "00001134-0000-1000-8000-00805f9b34fb" + +/* GATT UUIDs section */ +#define GATT_PRIM_SVC_UUID 0x2800 +#define GATT_SND_SVC_UUID 0x2801 +#define GATT_INCLUDE_UUID 0x2802 +#define GATT_CHARAC_UUID 0x2803 + +/* GATT Characteristic Types */ +#define GATT_CHARAC_DEVICE_NAME 0x2A00 +#define GATT_CHARAC_APPEARANCE 0x2A01 +#define GATT_CHARAC_PERIPHERAL_PRIV_FLAG 0x2A02 +#define GATT_CHARAC_RECONNECTION_ADDRESS 0x2A03 +#define GATT_CHARAC_PERIPHERAL_PREF_CONN 0x2A04 +#define GATT_CHARAC_SERVICE_CHANGED 0x2A05 +#define GATT_CHARAC_BATTERY_LEVEL 0x2A19 +#define GATT_CHARAC_SYSTEM_ID 0x2A23 +#define GATT_CHARAC_MODEL_NUMBER_STRING 0x2A24 +#define GATT_CHARAC_SERIAL_NUMBER_STRING 0x2A25 +#define GATT_CHARAC_FIRMWARE_REVISION_STRING 0x2A26 +#define GATT_CHARAC_HARDWARE_REVISION_STRING 0x2A27 +#define GATT_CHARAC_SOFTWARE_REVISION_STRING 0x2A28 +#define GATT_CHARAC_MANUFACTURER_NAME_STRING 0x2A29 +#define GATT_CHARAC_PNP_ID 0x2A50 + +/* GATT Characteristic Descriptors */ +#define GATT_CHARAC_EXT_PROPER_UUID 0x2900 +#define GATT_CHARAC_USER_DESC_UUID 0x2901 +#define GATT_CLIENT_CHARAC_CFG_UUID 0x2902 +#define GATT_SERVER_CHARAC_CFG_UUID 0x2903 +#define GATT_CHARAC_FMT_UUID 0x2904 +#define GATT_CHARAC_AGREG_FMT_UUID 0x2905 +#define GATT_CHARAC_VALID_RANGE_UUID 0x2906 +#define GATT_EXTERNAL_REPORT_REFERENCE 0x2907 +#define GATT_REPORT_REFERENCE 0x2908 + +/* GATT Mesh Services */ +#define MESH_PROV_SVC_UUID "00001827-0000-1000-8000-00805f9b34fb" +#define MESH_PROXY_SVC_UUID "00001828-0000-1000-8000-00805f9b34fb" + +/* GATT Mesh Characteristic Types */ +#define MESH_PROVISIONING_DATA_IN 0x2ADB +#define MESH_PROVISIONING_DATA_OUT 0x2ADC +#define MESH_PROXY_DATA_IN 0x2ADD +#define MESH_PROXY_DATA_OUT 0x2ADE + +/* GATT Caching attributes */ +#define GATT_CHARAC_CLI_FEAT 0x2B29 +#define GATT_CHARAC_DB_HASH 0x2B2A + +typedef struct { + enum { + BT_UUID_UNSPEC = 0, + BT_UUID16 = 16, + BT_UUID32 = 32, + BT_UUID128 = 128, + } type; + union { + uint16_t u16; + uint32_t u32; + uint128_t u128; + } value; +} bt_uuid_t; + +int bt_uuid_strcmp(const void *a, const void *b); + +int bt_uuid16_create(bt_uuid_t *btuuid, uint16_t value); +int bt_uuid32_create(bt_uuid_t *btuuid, uint32_t value); +int bt_uuid128_create(bt_uuid_t *btuuid, uint128_t value); + +int bt_uuid_cmp(const bt_uuid_t *uuid1, const bt_uuid_t *uuid2); +void bt_uuid_to_uuid128(const bt_uuid_t *src, bt_uuid_t *dst); + +#define MAX_LEN_UUID_STR 37 + +int bt_uuid_to_string(const bt_uuid_t *uuid, char *str, size_t n); +int bt_string_to_uuid(bt_uuid_t *uuid, const char *string); + +int bt_uuid_to_le(const bt_uuid_t *uuid, void *dst); + +static inline int bt_uuid_len(const bt_uuid_t *uuid) +{ + return uuid->type / 8; +} + +#ifdef __cplusplus +} +#endif + +#endif /* __BLUETOOTH_UUID_H */ diff --git a/ltmain.sh b/ltmain.sh new file mode 100644 index 0000000..d11f1e0 --- /dev/null +++ b/ltmain.sh @@ -0,0 +1,11249 @@ +#! /bin/sh +## DO NOT EDIT - This file generated from ./build-aux/ltmain.in +## by inline-source v2014-01-03.01 + +# libtool (GNU libtool) 2.4.6 +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit , 1996 + +# Copyright (C) 1996-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, +# if you distribute this file as part of a program or library that +# is built using GNU Libtool, you may include this file under the +# same distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +PROGRAM=libtool +PACKAGE=libtool +VERSION="2.4.6 Debian-2.4.6-11" +package_revision=2.4.6 + + +## ------ ## +## Usage. ## +## ------ ## + +# Run './libtool --help' for help with using this script from the +# command line. + + +## ------------------------------- ## +## User overridable command paths. ## +## ------------------------------- ## + +# After configure completes, it has a better idea of some of the +# shell tools we need than the defaults used by the functions shared +# with bootstrap, so set those here where they can still be over- +# ridden by the user, but otherwise take precedence. + +: ${AUTOCONF="autoconf"} +: ${AUTOMAKE="automake"} + + +## -------------------------- ## +## Source external libraries. ## +## -------------------------- ## + +# Much of our low-level functionality needs to be sourced from external +# libraries, which are installed to $pkgauxdir. + +# Set a version string for this script. +scriptversion=2015-01-20.17; # UTC + +# General shell script boiler plate, and helper functions. +# Written by Gary V. Vaughan, 2004 + +# Copyright (C) 2004-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. + +# As a special exception to the GNU General Public License, if you distribute +# this file as part of a program or library that is built using GNU Libtool, +# you may include this file under the same distribution terms that you use +# for the rest of that program. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNES FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Please report bugs or propose patches to gary@gnu.org. + + +## ------ ## +## Usage. ## +## ------ ## + +# Evaluate this file near the top of your script to gain access to +# the functions and variables defined here: +# +# . `echo "$0" | ${SED-sed} 's|[^/]*$||'`/build-aux/funclib.sh +# +# If you need to override any of the default environment variable +# settings, do that before evaluating this file. + + +## -------------------- ## +## Shell normalisation. ## +## -------------------- ## + +# Some shells need a little help to be as Bourne compatible as possible. +# Before doing anything else, make sure all that help has been provided! + +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in *posix*) set -o posix ;; esac +fi + +# NLS nuisances: We save the old values in case they are required later. +_G_user_locale= +_G_safe_locale= +for _G_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES +do + eval "if test set = \"\${$_G_var+set}\"; then + save_$_G_var=\$$_G_var + $_G_var=C + export $_G_var + _G_user_locale=\"$_G_var=\\\$save_\$_G_var; \$_G_user_locale\" + _G_safe_locale=\"$_G_var=C; \$_G_safe_locale\" + fi" +done + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Make sure IFS has a sensible default +sp=' ' +nl=' +' +IFS="$sp $nl" + +# There are apparently some retarded systems that use ';' as a PATH separator! +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + + +## ------------------------- ## +## Locate command utilities. ## +## ------------------------- ## + + +# func_executable_p FILE +# ---------------------- +# Check that FILE is an executable regular file. +func_executable_p () +{ + test -f "$1" && test -x "$1" +} + + +# func_path_progs PROGS_LIST CHECK_FUNC [PATH] +# -------------------------------------------- +# Search for either a program that responds to --version with output +# containing "GNU", or else returned by CHECK_FUNC otherwise, by +# trying all the directories in PATH with each of the elements of +# PROGS_LIST. +# +# CHECK_FUNC should accept the path to a candidate program, and +# set $func_check_prog_result if it truncates its output less than +# $_G_path_prog_max characters. +func_path_progs () +{ + _G_progs_list=$1 + _G_check_func=$2 + _G_PATH=${3-"$PATH"} + + _G_path_prog_max=0 + _G_path_prog_found=false + _G_save_IFS=$IFS; IFS=${PATH_SEPARATOR-:} + for _G_dir in $_G_PATH; do + IFS=$_G_save_IFS + test -z "$_G_dir" && _G_dir=. + for _G_prog_name in $_G_progs_list; do + for _exeext in '' .EXE; do + _G_path_prog=$_G_dir/$_G_prog_name$_exeext + func_executable_p "$_G_path_prog" || continue + case `"$_G_path_prog" --version 2>&1` in + *GNU*) func_path_progs_result=$_G_path_prog _G_path_prog_found=: ;; + *) $_G_check_func $_G_path_prog + func_path_progs_result=$func_check_prog_result + ;; + esac + $_G_path_prog_found && break 3 + done + done + done + IFS=$_G_save_IFS + test -z "$func_path_progs_result" && { + echo "no acceptable sed could be found in \$PATH" >&2 + exit 1 + } +} + + +# We want to be able to use the functions in this file before configure +# has figured out where the best binaries are kept, which means we have +# to search for them ourselves - except when the results are already set +# where we skip the searches. + +# Unless the user overrides by setting SED, search the path for either GNU +# sed, or the sed that truncates its output the least. +test -z "$SED" && { + _G_sed_script=s/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/ + for _G_i in 1 2 3 4 5 6 7; do + _G_sed_script=$_G_sed_script$nl$_G_sed_script + done + echo "$_G_sed_script" 2>/dev/null | sed 99q >conftest.sed + _G_sed_script= + + func_check_prog_sed () + { + _G_path_prog=$1 + + _G_count=0 + printf 0123456789 >conftest.in + while : + do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo '' >> conftest.nl + "$_G_path_prog" -f conftest.sed conftest.out 2>/dev/null || break + diff conftest.out conftest.nl >/dev/null 2>&1 || break + _G_count=`expr $_G_count + 1` + if test "$_G_count" -gt "$_G_path_prog_max"; then + # Best one so far, save it but keep looking for a better one + func_check_prog_result=$_G_path_prog + _G_path_prog_max=$_G_count + fi + # 10*(2^10) chars as input seems more than enough + test 10 -lt "$_G_count" && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out + } + + func_path_progs "sed gsed" func_check_prog_sed $PATH:/usr/xpg4/bin + rm -f conftest.sed + SED=$func_path_progs_result +} + + +# Unless the user overrides by setting GREP, search the path for either GNU +# grep, or the grep that truncates its output the least. +test -z "$GREP" && { + func_check_prog_grep () + { + _G_path_prog=$1 + + _G_count=0 + _G_path_prog_max=0 + printf 0123456789 >conftest.in + while : + do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo 'GREP' >> conftest.nl + "$_G_path_prog" -e 'GREP$' -e '-(cannot match)-' conftest.out 2>/dev/null || break + diff conftest.out conftest.nl >/dev/null 2>&1 || break + _G_count=`expr $_G_count + 1` + if test "$_G_count" -gt "$_G_path_prog_max"; then + # Best one so far, save it but keep looking for a better one + func_check_prog_result=$_G_path_prog + _G_path_prog_max=$_G_count + fi + # 10*(2^10) chars as input seems more than enough + test 10 -lt "$_G_count" && break + done + rm -f conftest.in conftest.tmp conftest.nl conftest.out + } + + func_path_progs "grep ggrep" func_check_prog_grep $PATH:/usr/xpg4/bin + GREP=$func_path_progs_result +} + + +## ------------------------------- ## +## User overridable command paths. ## +## ------------------------------- ## + +# All uppercase variable names are used for environment variables. These +# variables can be overridden by the user before calling a script that +# uses them if a suitable command of that name is not already available +# in the command search PATH. + +: ${CP="cp -f"} +: ${ECHO="printf %s\n"} +: ${EGREP="$GREP -E"} +: ${FGREP="$GREP -F"} +: ${LN_S="ln -s"} +: ${MAKE="make"} +: ${MKDIR="mkdir"} +: ${MV="mv -f"} +: ${RM="rm -f"} +: ${SHELL="${CONFIG_SHELL-/bin/sh}"} + + +## -------------------- ## +## Useful sed snippets. ## +## -------------------- ## + +sed_dirname='s|/[^/]*$||' +sed_basename='s|^.*/||' + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='s|\([`"$\\]\)|\\\1|g' + +# Same as above, but do not quote variable references. +sed_double_quote_subst='s/\(["`\\]\)/\\\1/g' + +# Sed substitution that turns a string into a regex matching for the +# string literally. +sed_make_literal_regex='s|[].[^$\\*\/]|\\&|g' + +# Sed substitution that converts a w32 file name or path +# that contains forward slashes, into one that contains +# (escaped) backslashes. A very naive implementation. +sed_naive_backslashify='s|\\\\*|\\|g;s|/|\\|g;s|\\|\\\\|g' + +# Re-'\' parameter expansions in output of sed_double_quote_subst that +# were '\'-ed in input to the same. If an odd number of '\' preceded a +# '$' in input to sed_double_quote_subst, that '$' was protected from +# expansion. Since each input '\' is now two '\'s, look for any number +# of runs of four '\'s followed by two '\'s and then a '$'. '\' that '$'. +_G_bs='\\' +_G_bs2='\\\\' +_G_bs4='\\\\\\\\' +_G_dollar='\$' +sed_double_backslash="\ + s/$_G_bs4/&\\ +/g + s/^$_G_bs2$_G_dollar/$_G_bs&/ + s/\\([^$_G_bs]\\)$_G_bs2$_G_dollar/\\1$_G_bs2$_G_bs$_G_dollar/g + s/\n//g" + + +## ----------------- ## +## Global variables. ## +## ----------------- ## + +# Except for the global variables explicitly listed below, the following +# functions in the '^func_' namespace, and the '^require_' namespace +# variables initialised in the 'Resource management' section, sourcing +# this file will not pollute your global namespace with anything +# else. There's no portable way to scope variables in Bourne shell +# though, so actually running these functions will sometimes place +# results into a variable named after the function, and often use +# temporary variables in the '^_G_' namespace. If you are careful to +# avoid using those namespaces casually in your sourcing script, things +# should continue to work as you expect. And, of course, you can freely +# overwrite any of the functions or variables defined here before +# calling anything to customize them. + +EXIT_SUCCESS=0 +EXIT_FAILURE=1 +EXIT_MISMATCH=63 # $? = 63 is used to indicate version mismatch to missing. +EXIT_SKIP=77 # $? = 77 is used to indicate a skipped test to automake. + +# Allow overriding, eg assuming that you follow the convention of +# putting '$debug_cmd' at the start of all your functions, you can get +# bash to show function call trace with: +# +# debug_cmd='eval echo "${FUNCNAME[0]} $*" >&2' bash your-script-name +debug_cmd=${debug_cmd-":"} +exit_cmd=: + +# By convention, finish your script with: +# +# exit $exit_status +# +# so that you can set exit_status to non-zero if you want to indicate +# something went wrong during execution without actually bailing out at +# the point of failure. +exit_status=$EXIT_SUCCESS + +# Work around backward compatibility issue on IRIX 6.5. On IRIX 6.4+, sh +# is ksh but when the shell is invoked as "sh" and the current value of +# the _XPG environment variable is not equal to 1 (one), the special +# positional parameter $0, within a function call, is the name of the +# function. +progpath=$0 + +# The name of this program. +progname=`$ECHO "$progpath" |$SED "$sed_basename"` + +# Make sure we have an absolute progpath for reexecution: +case $progpath in + [\\/]*|[A-Za-z]:\\*) ;; + *[\\/]*) + progdir=`$ECHO "$progpath" |$SED "$sed_dirname"` + progdir=`cd "$progdir" && pwd` + progpath=$progdir/$progname + ;; + *) + _G_IFS=$IFS + IFS=${PATH_SEPARATOR-:} + for progdir in $PATH; do + IFS=$_G_IFS + test -x "$progdir/$progname" && break + done + IFS=$_G_IFS + test -n "$progdir" || progdir=`pwd` + progpath=$progdir/$progname + ;; +esac + + +## ----------------- ## +## Standard options. ## +## ----------------- ## + +# The following options affect the operation of the functions defined +# below, and should be set appropriately depending on run-time para- +# meters passed on the command line. + +opt_dry_run=false +opt_quiet=false +opt_verbose=false + +# Categories 'all' and 'none' are always available. Append any others +# you will pass as the first argument to func_warning from your own +# code. +warning_categories= + +# By default, display warnings according to 'opt_warning_types'. Set +# 'warning_func' to ':' to elide all warnings, or func_fatal_error to +# treat the next displayed warning as a fatal error. +warning_func=func_warn_and_continue + +# Set to 'all' to display all warnings, 'none' to suppress all +# warnings, or a space delimited list of some subset of +# 'warning_categories' to display only the listed warnings. +opt_warning_types=all + + +## -------------------- ## +## Resource management. ## +## -------------------- ## + +# This section contains definitions for functions that each ensure a +# particular resource (a file, or a non-empty configuration variable for +# example) is available, and if appropriate to extract default values +# from pertinent package files. Call them using their associated +# 'require_*' variable to ensure that they are executed, at most, once. +# +# It's entirely deliberate that calling these functions can set +# variables that don't obey the namespace limitations obeyed by the rest +# of this file, in order that that they be as useful as possible to +# callers. + + +# require_term_colors +# ------------------- +# Allow display of bold text on terminals that support it. +require_term_colors=func_require_term_colors +func_require_term_colors () +{ + $debug_cmd + + test -t 1 && { + # COLORTERM and USE_ANSI_COLORS environment variables take + # precedence, because most terminfo databases neglect to describe + # whether color sequences are supported. + test -n "${COLORTERM+set}" && : ${USE_ANSI_COLORS="1"} + + if test 1 = "$USE_ANSI_COLORS"; then + # Standard ANSI escape sequences + tc_reset='' + tc_bold=''; tc_standout='' + tc_red=''; tc_green='' + tc_blue=''; tc_cyan='' + else + # Otherwise trust the terminfo database after all. + test -n "`tput sgr0 2>/dev/null`" && { + tc_reset=`tput sgr0` + test -n "`tput bold 2>/dev/null`" && tc_bold=`tput bold` + tc_standout=$tc_bold + test -n "`tput smso 2>/dev/null`" && tc_standout=`tput smso` + test -n "`tput setaf 1 2>/dev/null`" && tc_red=`tput setaf 1` + test -n "`tput setaf 2 2>/dev/null`" && tc_green=`tput setaf 2` + test -n "`tput setaf 4 2>/dev/null`" && tc_blue=`tput setaf 4` + test -n "`tput setaf 5 2>/dev/null`" && tc_cyan=`tput setaf 5` + } + fi + } + + require_term_colors=: +} + + +## ----------------- ## +## Function library. ## +## ----------------- ## + +# This section contains a variety of useful functions to call in your +# scripts. Take note of the portable wrappers for features provided by +# some modern shells, which will fall back to slower equivalents on +# less featureful shells. + + +# func_append VAR VALUE +# --------------------- +# Append VALUE onto the existing contents of VAR. + + # We should try to minimise forks, especially on Windows where they are + # unreasonably slow, so skip the feature probes when bash or zsh are + # being used: + if test set = "${BASH_VERSION+set}${ZSH_VERSION+set}"; then + : ${_G_HAVE_ARITH_OP="yes"} + : ${_G_HAVE_XSI_OPS="yes"} + # The += operator was introduced in bash 3.1 + case $BASH_VERSION in + [12].* | 3.0 | 3.0*) ;; + *) + : ${_G_HAVE_PLUSEQ_OP="yes"} + ;; + esac + fi + + # _G_HAVE_PLUSEQ_OP + # Can be empty, in which case the shell is probed, "yes" if += is + # useable or anything else if it does not work. + test -z "$_G_HAVE_PLUSEQ_OP" \ + && (eval 'x=a; x+=" b"; test "a b" = "$x"') 2>/dev/null \ + && _G_HAVE_PLUSEQ_OP=yes + +if test yes = "$_G_HAVE_PLUSEQ_OP" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_append () + { + $debug_cmd + + eval "$1+=\$2" + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_append () + { + $debug_cmd + + eval "$1=\$$1\$2" + } +fi + + +# func_append_quoted VAR VALUE +# ---------------------------- +# Quote VALUE and append to the end of shell variable VAR, separated +# by a space. +if test yes = "$_G_HAVE_PLUSEQ_OP"; then + eval 'func_append_quoted () + { + $debug_cmd + + func_quote_for_eval "$2" + eval "$1+=\\ \$func_quote_for_eval_result" + }' +else + func_append_quoted () + { + $debug_cmd + + func_quote_for_eval "$2" + eval "$1=\$$1\\ \$func_quote_for_eval_result" + } +fi + + +# func_append_uniq VAR VALUE +# -------------------------- +# Append unique VALUE onto the existing contents of VAR, assuming +# entries are delimited by the first character of VALUE. For example: +# +# func_append_uniq options " --another-option option-argument" +# +# will only append to $options if " --another-option option-argument " +# is not already present somewhere in $options already (note spaces at +# each end implied by leading space in second argument). +func_append_uniq () +{ + $debug_cmd + + eval _G_current_value='`$ECHO $'$1'`' + _G_delim=`expr "$2" : '\(.\)'` + + case $_G_delim$_G_current_value$_G_delim in + *"$2$_G_delim"*) ;; + *) func_append "$@" ;; + esac +} + + +# func_arith TERM... +# ------------------ +# Set func_arith_result to the result of evaluating TERMs. + test -z "$_G_HAVE_ARITH_OP" \ + && (eval 'test 2 = $(( 1 + 1 ))') 2>/dev/null \ + && _G_HAVE_ARITH_OP=yes + +if test yes = "$_G_HAVE_ARITH_OP"; then + eval 'func_arith () + { + $debug_cmd + + func_arith_result=$(( $* )) + }' +else + func_arith () + { + $debug_cmd + + func_arith_result=`expr "$@"` + } +fi + + +# func_basename FILE +# ------------------ +# Set func_basename_result to FILE with everything up to and including +# the last / stripped. +if test yes = "$_G_HAVE_XSI_OPS"; then + # If this shell supports suffix pattern removal, then use it to avoid + # forking. Hide the definitions single quotes in case the shell chokes + # on unsupported syntax... + _b='func_basename_result=${1##*/}' + _d='case $1 in + */*) func_dirname_result=${1%/*}$2 ;; + * ) func_dirname_result=$3 ;; + esac' + +else + # ...otherwise fall back to using sed. + _b='func_basename_result=`$ECHO "$1" |$SED "$sed_basename"`' + _d='func_dirname_result=`$ECHO "$1" |$SED "$sed_dirname"` + if test "X$func_dirname_result" = "X$1"; then + func_dirname_result=$3 + else + func_append func_dirname_result "$2" + fi' +fi + +eval 'func_basename () +{ + $debug_cmd + + '"$_b"' +}' + + +# func_dirname FILE APPEND NONDIR_REPLACEMENT +# ------------------------------------------- +# Compute the dirname of FILE. If nonempty, add APPEND to the result, +# otherwise set result to NONDIR_REPLACEMENT. +eval 'func_dirname () +{ + $debug_cmd + + '"$_d"' +}' + + +# func_dirname_and_basename FILE APPEND NONDIR_REPLACEMENT +# -------------------------------------------------------- +# Perform func_basename and func_dirname in a single function +# call: +# dirname: Compute the dirname of FILE. If nonempty, +# add APPEND to the result, otherwise set result +# to NONDIR_REPLACEMENT. +# value returned in "$func_dirname_result" +# basename: Compute filename of FILE. +# value retuned in "$func_basename_result" +# For efficiency, we do not delegate to the functions above but instead +# duplicate the functionality here. +eval 'func_dirname_and_basename () +{ + $debug_cmd + + '"$_b"' + '"$_d"' +}' + + +# func_echo ARG... +# ---------------- +# Echo program name prefixed message. +func_echo () +{ + $debug_cmd + + _G_message=$* + + func_echo_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_IFS + $ECHO "$progname: $_G_line" + done + IFS=$func_echo_IFS +} + + +# func_echo_all ARG... +# -------------------- +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "$*" +} + + +# func_echo_infix_1 INFIX ARG... +# ------------------------------ +# Echo program name, followed by INFIX on the first line, with any +# additional lines not showing INFIX. +func_echo_infix_1 () +{ + $debug_cmd + + $require_term_colors + + _G_infix=$1; shift + _G_indent=$_G_infix + _G_prefix="$progname: $_G_infix: " + _G_message=$* + + # Strip color escape sequences before counting printable length + for _G_tc in "$tc_reset" "$tc_bold" "$tc_standout" "$tc_red" "$tc_green" "$tc_blue" "$tc_cyan" + do + test -n "$_G_tc" && { + _G_esc_tc=`$ECHO "$_G_tc" | $SED "$sed_make_literal_regex"` + _G_indent=`$ECHO "$_G_indent" | $SED "s|$_G_esc_tc||g"` + } + done + _G_indent="$progname: "`echo "$_G_indent" | $SED 's|.| |g'`" " ## exclude from sc_prohibit_nested_quotes + + func_echo_infix_1_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_infix_1_IFS + $ECHO "$_G_prefix$tc_bold$_G_line$tc_reset" >&2 + _G_prefix=$_G_indent + done + IFS=$func_echo_infix_1_IFS +} + + +# func_error ARG... +# ----------------- +# Echo program name prefixed message to standard error. +func_error () +{ + $debug_cmd + + $require_term_colors + + func_echo_infix_1 " $tc_standout${tc_red}error$tc_reset" "$*" >&2 +} + + +# func_fatal_error ARG... +# ----------------------- +# Echo program name prefixed message to standard error, and exit. +func_fatal_error () +{ + $debug_cmd + + func_error "$*" + exit $EXIT_FAILURE +} + + +# func_grep EXPRESSION FILENAME +# ----------------------------- +# Check whether EXPRESSION matches any line of FILENAME, without output. +func_grep () +{ + $debug_cmd + + $GREP "$1" "$2" >/dev/null 2>&1 +} + + +# func_len STRING +# --------------- +# Set func_len_result to the length of STRING. STRING may not +# start with a hyphen. + test -z "$_G_HAVE_XSI_OPS" \ + && (eval 'x=a/b/c; + test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ + && _G_HAVE_XSI_OPS=yes + +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_len () + { + $debug_cmd + + func_len_result=${#1} + }' +else + func_len () + { + $debug_cmd + + func_len_result=`expr "$1" : ".*" 2>/dev/null || echo $max_cmd_len` + } +fi + + +# func_mkdir_p DIRECTORY-PATH +# --------------------------- +# Make sure the entire path to DIRECTORY-PATH is available. +func_mkdir_p () +{ + $debug_cmd + + _G_directory_path=$1 + _G_dir_list= + + if test -n "$_G_directory_path" && test : != "$opt_dry_run"; then + + # Protect directory names starting with '-' + case $_G_directory_path in + -*) _G_directory_path=./$_G_directory_path ;; + esac + + # While some portion of DIR does not yet exist... + while test ! -d "$_G_directory_path"; do + # ...make a list in topmost first order. Use a colon delimited + # list incase some portion of path contains whitespace. + _G_dir_list=$_G_directory_path:$_G_dir_list + + # If the last portion added has no slash in it, the list is done + case $_G_directory_path in */*) ;; *) break ;; esac + + # ...otherwise throw away the child directory and loop + _G_directory_path=`$ECHO "$_G_directory_path" | $SED -e "$sed_dirname"` + done + _G_dir_list=`$ECHO "$_G_dir_list" | $SED 's|:*$||'` + + func_mkdir_p_IFS=$IFS; IFS=: + for _G_dir in $_G_dir_list; do + IFS=$func_mkdir_p_IFS + # mkdir can fail with a 'File exist' error if two processes + # try to create one of the directories concurrently. Don't + # stop in that case! + $MKDIR "$_G_dir" 2>/dev/null || : + done + IFS=$func_mkdir_p_IFS + + # Bail out if we (or some other process) failed to create a directory. + test -d "$_G_directory_path" || \ + func_fatal_error "Failed to create '$1'" + fi +} + + +# func_mktempdir [BASENAME] +# ------------------------- +# Make a temporary directory that won't clash with other running +# libtool processes, and avoids race conditions if possible. If +# given, BASENAME is the basename for that directory. +func_mktempdir () +{ + $debug_cmd + + _G_template=${TMPDIR-/tmp}/${1-$progname} + + if test : = "$opt_dry_run"; then + # Return a directory name, but don't create it in dry-run mode + _G_tmpdir=$_G_template-$$ + else + + # If mktemp works, use that first and foremost + _G_tmpdir=`mktemp -d "$_G_template-XXXXXXXX" 2>/dev/null` + + if test ! -d "$_G_tmpdir"; then + # Failing that, at least try and use $RANDOM to avoid a race + _G_tmpdir=$_G_template-${RANDOM-0}$$ + + func_mktempdir_umask=`umask` + umask 0077 + $MKDIR "$_G_tmpdir" + umask $func_mktempdir_umask + fi + + # If we're not in dry-run mode, bomb out on failure + test -d "$_G_tmpdir" || \ + func_fatal_error "cannot create temporary directory '$_G_tmpdir'" + fi + + $ECHO "$_G_tmpdir" +} + + +# func_normal_abspath PATH +# ------------------------ +# Remove doubled-up and trailing slashes, "." path components, +# and cancel out any ".." path components in PATH after making +# it an absolute path. +func_normal_abspath () +{ + $debug_cmd + + # These SED scripts presuppose an absolute path with a trailing slash. + _G_pathcar='s|^/\([^/]*\).*$|\1|' + _G_pathcdr='s|^/[^/]*||' + _G_removedotparts=':dotsl + s|/\./|/|g + t dotsl + s|/\.$|/|' + _G_collapseslashes='s|/\{1,\}|/|g' + _G_finalslash='s|/*$|/|' + + # Start from root dir and reassemble the path. + func_normal_abspath_result= + func_normal_abspath_tpath=$1 + func_normal_abspath_altnamespace= + case $func_normal_abspath_tpath in + "") + # Empty path, that just means $cwd. + func_stripname '' '/' "`pwd`" + func_normal_abspath_result=$func_stripname_result + return + ;; + # The next three entries are used to spot a run of precisely + # two leading slashes without using negated character classes; + # we take advantage of case's first-match behaviour. + ///*) + # Unusual form of absolute path, do nothing. + ;; + //*) + # Not necessarily an ordinary path; POSIX reserves leading '//' + # and for example Cygwin uses it to access remote file shares + # over CIFS/SMB, so we conserve a leading double slash if found. + func_normal_abspath_altnamespace=/ + ;; + /*) + # Absolute path, do nothing. + ;; + *) + # Relative path, prepend $cwd. + func_normal_abspath_tpath=`pwd`/$func_normal_abspath_tpath + ;; + esac + + # Cancel out all the simple stuff to save iterations. We also want + # the path to end with a slash for ease of parsing, so make sure + # there is one (and only one) here. + func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_removedotparts" -e "$_G_collapseslashes" -e "$_G_finalslash"` + while :; do + # Processed it all yet? + if test / = "$func_normal_abspath_tpath"; then + # If we ascended to the root using ".." the result may be empty now. + if test -z "$func_normal_abspath_result"; then + func_normal_abspath_result=/ + fi + break + fi + func_normal_abspath_tcomponent=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_pathcar"` + func_normal_abspath_tpath=`$ECHO "$func_normal_abspath_tpath" | $SED \ + -e "$_G_pathcdr"` + # Figure out what to do with it + case $func_normal_abspath_tcomponent in + "") + # Trailing empty path component, ignore it. + ;; + ..) + # Parent dir; strip last assembled component from result. + func_dirname "$func_normal_abspath_result" + func_normal_abspath_result=$func_dirname_result + ;; + *) + # Actual path component, append it. + func_append func_normal_abspath_result "/$func_normal_abspath_tcomponent" + ;; + esac + done + # Restore leading double-slash if one was found on entry. + func_normal_abspath_result=$func_normal_abspath_altnamespace$func_normal_abspath_result +} + + +# func_notquiet ARG... +# -------------------- +# Echo program name prefixed message only when not in quiet mode. +func_notquiet () +{ + $debug_cmd + + $opt_quiet || func_echo ${1+"$@"} + + # A bug in bash halts the script if the last line of a function + # fails when set -e is in force, so we need another command to + # work around that: + : +} + + +# func_relative_path SRCDIR DSTDIR +# -------------------------------- +# Set func_relative_path_result to the relative path from SRCDIR to DSTDIR. +func_relative_path () +{ + $debug_cmd + + func_relative_path_result= + func_normal_abspath "$1" + func_relative_path_tlibdir=$func_normal_abspath_result + func_normal_abspath "$2" + func_relative_path_tbindir=$func_normal_abspath_result + + # Ascend the tree starting from libdir + while :; do + # check if we have found a prefix of bindir + case $func_relative_path_tbindir in + $func_relative_path_tlibdir) + # found an exact match + func_relative_path_tcancelled= + break + ;; + $func_relative_path_tlibdir*) + # found a matching prefix + func_stripname "$func_relative_path_tlibdir" '' "$func_relative_path_tbindir" + func_relative_path_tcancelled=$func_stripname_result + if test -z "$func_relative_path_result"; then + func_relative_path_result=. + fi + break + ;; + *) + func_dirname $func_relative_path_tlibdir + func_relative_path_tlibdir=$func_dirname_result + if test -z "$func_relative_path_tlibdir"; then + # Have to descend all the way to the root! + func_relative_path_result=../$func_relative_path_result + func_relative_path_tcancelled=$func_relative_path_tbindir + break + fi + func_relative_path_result=../$func_relative_path_result + ;; + esac + done + + # Now calculate path; take care to avoid doubling-up slashes. + func_stripname '' '/' "$func_relative_path_result" + func_relative_path_result=$func_stripname_result + func_stripname '/' '/' "$func_relative_path_tcancelled" + if test -n "$func_stripname_result"; then + func_append func_relative_path_result "/$func_stripname_result" + fi + + # Normalisation. If bindir is libdir, return '.' else relative path. + if test -n "$func_relative_path_result"; then + func_stripname './' '' "$func_relative_path_result" + func_relative_path_result=$func_stripname_result + fi + + test -n "$func_relative_path_result" || func_relative_path_result=. + + : +} + + +# func_quote_for_eval ARG... +# -------------------------- +# Aesthetically quote ARGs to be evaled later. +# This function returns two values: +# i) func_quote_for_eval_result +# double-quoted, suitable for a subsequent eval +# ii) func_quote_for_eval_unquoted_result +# has all characters that are still active within double +# quotes backslashified. +func_quote_for_eval () +{ + $debug_cmd + + func_quote_for_eval_unquoted_result= + func_quote_for_eval_result= + while test 0 -lt $#; do + case $1 in + *[\\\`\"\$]*) + _G_unquoted_arg=`printf '%s\n' "$1" |$SED "$sed_quote_subst"` ;; + *) + _G_unquoted_arg=$1 ;; + esac + if test -n "$func_quote_for_eval_unquoted_result"; then + func_append func_quote_for_eval_unquoted_result " $_G_unquoted_arg" + else + func_append func_quote_for_eval_unquoted_result "$_G_unquoted_arg" + fi + + case $_G_unquoted_arg in + # Double-quote args containing shell metacharacters to delay + # word splitting, command substitution and variable expansion + # for a subsequent eval. + # Many Bourne shells cannot handle close brackets correctly + # in scan sets, so we specify it separately. + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + _G_quoted_arg=\"$_G_unquoted_arg\" + ;; + *) + _G_quoted_arg=$_G_unquoted_arg + ;; + esac + + if test -n "$func_quote_for_eval_result"; then + func_append func_quote_for_eval_result " $_G_quoted_arg" + else + func_append func_quote_for_eval_result "$_G_quoted_arg" + fi + shift + done +} + + +# func_quote_for_expand ARG +# ------------------------- +# Aesthetically quote ARG to be evaled later; same as above, +# but do not quote variable references. +func_quote_for_expand () +{ + $debug_cmd + + case $1 in + *[\\\`\"]*) + _G_arg=`$ECHO "$1" | $SED \ + -e "$sed_double_quote_subst" -e "$sed_double_backslash"` ;; + *) + _G_arg=$1 ;; + esac + + case $_G_arg in + # Double-quote args containing shell metacharacters to delay + # word splitting and command substitution for a subsequent eval. + # Many Bourne shells cannot handle close brackets correctly + # in scan sets, so we specify it separately. + *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"") + _G_arg=\"$_G_arg\" + ;; + esac + + func_quote_for_expand_result=$_G_arg +} + + +# func_stripname PREFIX SUFFIX NAME +# --------------------------------- +# strip PREFIX and SUFFIX from NAME, and store in func_stripname_result. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_stripname () + { + $debug_cmd + + # pdksh 5.2.14 does not do ${X%$Y} correctly if both X and Y are + # positional parameters, so assign one to ordinary variable first. + func_stripname_result=$3 + func_stripname_result=${func_stripname_result#"$1"} + func_stripname_result=${func_stripname_result%"$2"} + }' +else + func_stripname () + { + $debug_cmd + + case $2 in + .*) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%\\\\$2\$%%"`;; + *) func_stripname_result=`$ECHO "$3" | $SED -e "s%^$1%%" -e "s%$2\$%%"`;; + esac + } +fi + + +# func_show_eval CMD [FAIL_EXP] +# ----------------------------- +# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is +# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP +# is given, then evaluate it. +func_show_eval () +{ + $debug_cmd + + _G_cmd=$1 + _G_fail_exp=${2-':'} + + func_quote_for_expand "$_G_cmd" + eval "func_notquiet $func_quote_for_expand_result" + + $opt_dry_run || { + eval "$_G_cmd" + _G_status=$? + if test 0 -ne "$_G_status"; then + eval "(exit $_G_status); $_G_fail_exp" + fi + } +} + + +# func_show_eval_locale CMD [FAIL_EXP] +# ------------------------------------ +# Unless opt_quiet is true, then output CMD. Then, if opt_dryrun is +# not true, evaluate CMD. If the evaluation of CMD fails, and FAIL_EXP +# is given, then evaluate it. Use the saved locale for evaluation. +func_show_eval_locale () +{ + $debug_cmd + + _G_cmd=$1 + _G_fail_exp=${2-':'} + + $opt_quiet || { + func_quote_for_expand "$_G_cmd" + eval "func_echo $func_quote_for_expand_result" + } + + $opt_dry_run || { + eval "$_G_user_locale + $_G_cmd" + _G_status=$? + eval "$_G_safe_locale" + if test 0 -ne "$_G_status"; then + eval "(exit $_G_status); $_G_fail_exp" + fi + } +} + + +# func_tr_sh +# ---------- +# Turn $1 into a string suitable for a shell variable name. +# Result is stored in $func_tr_sh_result. All characters +# not in the set a-zA-Z0-9_ are replaced with '_'. Further, +# if $1 begins with a digit, a '_' is prepended as well. +func_tr_sh () +{ + $debug_cmd + + case $1 in + [0-9]* | *[!a-zA-Z0-9_]*) + func_tr_sh_result=`$ECHO "$1" | $SED -e 's/^\([0-9]\)/_\1/' -e 's/[^a-zA-Z0-9_]/_/g'` + ;; + * ) + func_tr_sh_result=$1 + ;; + esac +} + + +# func_verbose ARG... +# ------------------- +# Echo program name prefixed message in verbose mode only. +func_verbose () +{ + $debug_cmd + + $opt_verbose && func_echo "$*" + + : +} + + +# func_warn_and_continue ARG... +# ----------------------------- +# Echo program name prefixed warning message to standard error. +func_warn_and_continue () +{ + $debug_cmd + + $require_term_colors + + func_echo_infix_1 "${tc_red}warning$tc_reset" "$*" >&2 +} + + +# func_warning CATEGORY ARG... +# ---------------------------- +# Echo program name prefixed warning message to standard error. Warning +# messages can be filtered according to CATEGORY, where this function +# elides messages where CATEGORY is not listed in the global variable +# 'opt_warning_types'. +func_warning () +{ + $debug_cmd + + # CATEGORY must be in the warning_categories list! + case " $warning_categories " in + *" $1 "*) ;; + *) func_internal_error "invalid warning category '$1'" ;; + esac + + _G_category=$1 + shift + + case " $opt_warning_types " in + *" $_G_category "*) $warning_func ${1+"$@"} ;; + esac +} + + +# func_sort_ver VER1 VER2 +# ----------------------- +# 'sort -V' is not generally available. +# Note this deviates from the version comparison in automake +# in that it treats 1.5 < 1.5.0, and treats 1.4.4a < 1.4-p3a +# but this should suffice as we won't be specifying old +# version formats or redundant trailing .0 in bootstrap.conf. +# If we did want full compatibility then we should probably +# use m4_version_compare from autoconf. +func_sort_ver () +{ + $debug_cmd + + printf '%s\n%s\n' "$1" "$2" \ + | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n -k 6,6n -k 7,7n -k 8,8n -k 9,9n +} + +# func_lt_ver PREV CURR +# --------------------- +# Return true if PREV and CURR are in the correct order according to +# func_sort_ver, otherwise false. Use it like this: +# +# func_lt_ver "$prev_ver" "$proposed_ver" || func_fatal_error "..." +func_lt_ver () +{ + $debug_cmd + + test "x$1" = x`func_sort_ver "$1" "$2" | $SED 1q` +} + + +# Local variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" +# time-stamp-time-zone: "UTC" +# End: +#! /bin/sh + +# Set a version string for this script. +scriptversion=2015-10-07.11; # UTC + +# A portable, pluggable option parser for Bourne shell. +# Written by Gary V. Vaughan, 2010 + +# Copyright (C) 2010-2015 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Please report bugs or propose patches to gary@gnu.org. + + +## ------ ## +## Usage. ## +## ------ ## + +# This file is a library for parsing options in your shell scripts along +# with assorted other useful supporting features that you can make use +# of too. +# +# For the simplest scripts you might need only: +# +# #!/bin/sh +# . relative/path/to/funclib.sh +# . relative/path/to/options-parser +# scriptversion=1.0 +# func_options ${1+"$@"} +# eval set dummy "$func_options_result"; shift +# ...rest of your script... +# +# In order for the '--version' option to work, you will need to have a +# suitably formatted comment like the one at the top of this file +# starting with '# Written by ' and ending with '# warranty; '. +# +# For '-h' and '--help' to work, you will also need a one line +# description of your script's purpose in a comment directly above the +# '# Written by ' line, like the one at the top of this file. +# +# The default options also support '--debug', which will turn on shell +# execution tracing (see the comment above debug_cmd below for another +# use), and '--verbose' and the func_verbose function to allow your script +# to display verbose messages only when your user has specified +# '--verbose'. +# +# After sourcing this file, you can plug processing for additional +# options by amending the variables from the 'Configuration' section +# below, and following the instructions in the 'Option parsing' +# section further down. + +## -------------- ## +## Configuration. ## +## -------------- ## + +# You should override these variables in your script after sourcing this +# file so that they reflect the customisations you have added to the +# option parser. + +# The usage line for option parsing errors and the start of '-h' and +# '--help' output messages. You can embed shell variables for delayed +# expansion at the time the message is displayed, but you will need to +# quote other shell meta-characters carefully to prevent them being +# expanded when the contents are evaled. +usage='$progpath [OPTION]...' + +# Short help message in response to '-h' and '--help'. Add to this or +# override it after sourcing this library to reflect the full set of +# options your script accepts. +usage_message="\ + --debug enable verbose shell tracing + -W, --warnings=CATEGORY + report the warnings falling in CATEGORY [all] + -v, --verbose verbosely report processing + --version print version information and exit + -h, --help print short or long help message and exit +" + +# Additional text appended to 'usage_message' in response to '--help'. +long_help_message=" +Warning categories include: + 'all' show all warnings + 'none' turn off all the warnings + 'error' warnings are treated as fatal errors" + +# Help message printed before fatal option parsing errors. +fatal_help="Try '\$progname --help' for more information." + + + +## ------------------------- ## +## Hook function management. ## +## ------------------------- ## + +# This section contains functions for adding, removing, and running hooks +# to the main code. A hook is just a named list of of function, that can +# be run in order later on. + +# func_hookable FUNC_NAME +# ----------------------- +# Declare that FUNC_NAME will run hooks added with +# 'func_add_hook FUNC_NAME ...'. +func_hookable () +{ + $debug_cmd + + func_append hookable_fns " $1" +} + + +# func_add_hook FUNC_NAME HOOK_FUNC +# --------------------------------- +# Request that FUNC_NAME call HOOK_FUNC before it returns. FUNC_NAME must +# first have been declared "hookable" by a call to 'func_hookable'. +func_add_hook () +{ + $debug_cmd + + case " $hookable_fns " in + *" $1 "*) ;; + *) func_fatal_error "'$1' does not accept hook functions." ;; + esac + + eval func_append ${1}_hooks '" $2"' +} + + +# func_remove_hook FUNC_NAME HOOK_FUNC +# ------------------------------------ +# Remove HOOK_FUNC from the list of functions called by FUNC_NAME. +func_remove_hook () +{ + $debug_cmd + + eval ${1}_hooks='`$ECHO "\$'$1'_hooks" |$SED "s| '$2'||"`' +} + + +# func_run_hooks FUNC_NAME [ARG]... +# --------------------------------- +# Run all hook functions registered to FUNC_NAME. +# It is assumed that the list of hook functions contains nothing more +# than a whitespace-delimited list of legal shell function names, and +# no effort is wasted trying to catch shell meta-characters or preserve +# whitespace. +func_run_hooks () +{ + $debug_cmd + + _G_rc_run_hooks=false + + case " $hookable_fns " in + *" $1 "*) ;; + *) func_fatal_error "'$1' does not support hook funcions.n" ;; + esac + + eval _G_hook_fns=\$$1_hooks; shift + + for _G_hook in $_G_hook_fns; do + if eval $_G_hook '"$@"'; then + # store returned options list back into positional + # parameters for next 'cmd' execution. + eval _G_hook_result=\$${_G_hook}_result + eval set dummy "$_G_hook_result"; shift + _G_rc_run_hooks=: + fi + done + + $_G_rc_run_hooks && func_run_hooks_result=$_G_hook_result +} + + + +## --------------- ## +## Option parsing. ## +## --------------- ## + +# In order to add your own option parsing hooks, you must accept the +# full positional parameter list in your hook function, you may remove/edit +# any options that you action, and then pass back the remaining unprocessed +# options in '_result', escaped suitably for +# 'eval'. In this case you also must return $EXIT_SUCCESS to let the +# hook's caller know that it should pay attention to +# '_result'. Returning $EXIT_FAILURE signalizes that +# arguments are left untouched by the hook and therefore caller will ignore the +# result variable. +# +# Like this: +# +# my_options_prep () +# { +# $debug_cmd +# +# # Extend the existing usage message. +# usage_message=$usage_message' +# -s, --silent don'\''t print informational messages +# ' +# # No change in '$@' (ignored completely by this hook). There is +# # no need to do the equivalent (but slower) action: +# # func_quote_for_eval ${1+"$@"} +# # my_options_prep_result=$func_quote_for_eval_result +# false +# } +# func_add_hook func_options_prep my_options_prep +# +# +# my_silent_option () +# { +# $debug_cmd +# +# args_changed=false +# +# # Note that for efficiency, we parse as many options as we can +# # recognise in a loop before passing the remainder back to the +# # caller on the first unrecognised argument we encounter. +# while test $# -gt 0; do +# opt=$1; shift +# case $opt in +# --silent|-s) opt_silent=: +# args_changed=: +# ;; +# # Separate non-argument short options: +# -s*) func_split_short_opt "$_G_opt" +# set dummy "$func_split_short_opt_name" \ +# "-$func_split_short_opt_arg" ${1+"$@"} +# shift +# args_changed=: +# ;; +# *) # Make sure the first unrecognised option "$_G_opt" +# # is added back to "$@", we could need that later +# # if $args_changed is true. +# set dummy "$_G_opt" ${1+"$@"}; shift; break ;; +# esac +# done +# +# if $args_changed; then +# func_quote_for_eval ${1+"$@"} +# my_silent_option_result=$func_quote_for_eval_result +# fi +# +# $args_changed +# } +# func_add_hook func_parse_options my_silent_option +# +# +# my_option_validation () +# { +# $debug_cmd +# +# $opt_silent && $opt_verbose && func_fatal_help "\ +# '--silent' and '--verbose' options are mutually exclusive." +# +# false +# } +# func_add_hook func_validate_options my_option_validation +# +# You'll also need to manually amend $usage_message to reflect the extra +# options you parse. It's preferable to append if you can, so that +# multiple option parsing hooks can be added safely. + + +# func_options_finish [ARG]... +# ---------------------------- +# Finishing the option parse loop (call 'func_options' hooks ATM). +func_options_finish () +{ + $debug_cmd + + _G_func_options_finish_exit=false + if func_run_hooks func_options ${1+"$@"}; then + func_options_finish_result=$func_run_hooks_result + _G_func_options_finish_exit=: + fi + + $_G_func_options_finish_exit +} + + +# func_options [ARG]... +# --------------------- +# All the functions called inside func_options are hookable. See the +# individual implementations for details. +func_hookable func_options +func_options () +{ + $debug_cmd + + _G_rc_options=false + + for my_func in options_prep parse_options validate_options options_finish + do + if eval func_$my_func '${1+"$@"}'; then + eval _G_res_var='$'"func_${my_func}_result" + eval set dummy "$_G_res_var" ; shift + _G_rc_options=: + fi + done + + # Save modified positional parameters for caller. As a top-level + # options-parser function we always need to set the 'func_options_result' + # variable (regardless the $_G_rc_options value). + if $_G_rc_options; then + func_options_result=$_G_res_var + else + func_quote_for_eval ${1+"$@"} + func_options_result=$func_quote_for_eval_result + fi + + $_G_rc_options +} + + +# func_options_prep [ARG]... +# -------------------------- +# All initialisations required before starting the option parse loop. +# Note that when calling hook functions, we pass through the list of +# positional parameters. If a hook function modifies that list, and +# needs to propagate that back to rest of this script, then the complete +# modified list must be put in 'func_run_hooks_result' before +# returning $EXIT_SUCCESS (otherwise $EXIT_FAILURE is returned). +func_hookable func_options_prep +func_options_prep () +{ + $debug_cmd + + # Option defaults: + opt_verbose=false + opt_warning_types= + + _G_rc_options_prep=false + if func_run_hooks func_options_prep ${1+"$@"}; then + _G_rc_options_prep=: + # save modified positional parameters for caller + func_options_prep_result=$func_run_hooks_result + fi + + $_G_rc_options_prep +} + + +# func_parse_options [ARG]... +# --------------------------- +# The main option parsing loop. +func_hookable func_parse_options +func_parse_options () +{ + $debug_cmd + + func_parse_options_result= + + _G_rc_parse_options=false + # this just eases exit handling + while test $# -gt 0; do + # Defer to hook functions for initial option parsing, so they + # get priority in the event of reusing an option name. + if func_run_hooks func_parse_options ${1+"$@"}; then + eval set dummy "$func_run_hooks_result"; shift + _G_rc_parse_options=: + fi + + # Break out of the loop if we already parsed every option. + test $# -gt 0 || break + + _G_match_parse_options=: + _G_opt=$1 + shift + case $_G_opt in + --debug|-x) debug_cmd='set -x' + func_echo "enabling shell trace mode" + $debug_cmd + ;; + + --no-warnings|--no-warning|--no-warn) + set dummy --warnings none ${1+"$@"} + shift + ;; + + --warnings|--warning|-W) + if test $# = 0 && func_missing_arg $_G_opt; then + _G_rc_parse_options=: + break + fi + case " $warning_categories $1" in + *" $1 "*) + # trailing space prevents matching last $1 above + func_append_uniq opt_warning_types " $1" + ;; + *all) + opt_warning_types=$warning_categories + ;; + *none) + opt_warning_types=none + warning_func=: + ;; + *error) + opt_warning_types=$warning_categories + warning_func=func_fatal_error + ;; + *) + func_fatal_error \ + "unsupported warning category: '$1'" + ;; + esac + shift + ;; + + --verbose|-v) opt_verbose=: ;; + --version) func_version ;; + -\?|-h) func_usage ;; + --help) func_help ;; + + # Separate optargs to long options (plugins may need this): + --*=*) func_split_equals "$_G_opt" + set dummy "$func_split_equals_lhs" \ + "$func_split_equals_rhs" ${1+"$@"} + shift + ;; + + # Separate optargs to short options: + -W*) + func_split_short_opt "$_G_opt" + set dummy "$func_split_short_opt_name" \ + "$func_split_short_opt_arg" ${1+"$@"} + shift + ;; + + # Separate non-argument short options: + -\?*|-h*|-v*|-x*) + func_split_short_opt "$_G_opt" + set dummy "$func_split_short_opt_name" \ + "-$func_split_short_opt_arg" ${1+"$@"} + shift + ;; + + --) _G_rc_parse_options=: ; break ;; + -*) func_fatal_help "unrecognised option: '$_G_opt'" ;; + *) set dummy "$_G_opt" ${1+"$@"}; shift + _G_match_parse_options=false + break + ;; + esac + + $_G_match_parse_options && _G_rc_parse_options=: + done + + + if $_G_rc_parse_options; then + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + func_parse_options_result=$func_quote_for_eval_result + fi + + $_G_rc_parse_options +} + + +# func_validate_options [ARG]... +# ------------------------------ +# Perform any sanity checks on option settings and/or unconsumed +# arguments. +func_hookable func_validate_options +func_validate_options () +{ + $debug_cmd + + _G_rc_validate_options=false + + # Display all warnings if -W was not given. + test -n "$opt_warning_types" || opt_warning_types=" $warning_categories" + + if func_run_hooks func_validate_options ${1+"$@"}; then + # save modified positional parameters for caller + func_validate_options_result=$func_run_hooks_result + _G_rc_validate_options=: + fi + + # Bail if the options were screwed! + $exit_cmd $EXIT_FAILURE + + $_G_rc_validate_options +} + + + +## ----------------- ## +## Helper functions. ## +## ----------------- ## + +# This section contains the helper functions used by the rest of the +# hookable option parser framework in ascii-betical order. + + +# func_fatal_help ARG... +# ---------------------- +# Echo program name prefixed message to standard error, followed by +# a help hint, and exit. +func_fatal_help () +{ + $debug_cmd + + eval \$ECHO \""Usage: $usage"\" + eval \$ECHO \""$fatal_help"\" + func_error ${1+"$@"} + exit $EXIT_FAILURE +} + + +# func_help +# --------- +# Echo long help message to standard output and exit. +func_help () +{ + $debug_cmd + + func_usage_message + $ECHO "$long_help_message" + exit 0 +} + + +# func_missing_arg ARGNAME +# ------------------------ +# Echo program name prefixed message to standard error and set global +# exit_cmd. +func_missing_arg () +{ + $debug_cmd + + func_error "Missing argument for '$1'." + exit_cmd=exit +} + + +# func_split_equals STRING +# ------------------------ +# Set func_split_equals_lhs and func_split_equals_rhs shell variables after +# splitting STRING at the '=' sign. +test -z "$_G_HAVE_XSI_OPS" \ + && (eval 'x=a/b/c; + test 5aa/bb/cc = "${#x}${x%%/*}${x%/*}${x#*/}${x##*/}"') 2>/dev/null \ + && _G_HAVE_XSI_OPS=yes + +if test yes = "$_G_HAVE_XSI_OPS" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_split_equals () + { + $debug_cmd + + func_split_equals_lhs=${1%%=*} + func_split_equals_rhs=${1#*=} + test "x$func_split_equals_lhs" = "x$1" \ + && func_split_equals_rhs= + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_split_equals () + { + $debug_cmd + + func_split_equals_lhs=`expr "x$1" : 'x\([^=]*\)'` + func_split_equals_rhs= + test "x$func_split_equals_lhs" = "x$1" \ + || func_split_equals_rhs=`expr "x$1" : 'x[^=]*=\(.*\)$'` + } +fi #func_split_equals + + +# func_split_short_opt SHORTOPT +# ----------------------------- +# Set func_split_short_opt_name and func_split_short_opt_arg shell +# variables after splitting SHORTOPT after the 2nd character. +if test yes = "$_G_HAVE_XSI_OPS" +then + # This is an XSI compatible shell, allowing a faster implementation... + eval 'func_split_short_opt () + { + $debug_cmd + + func_split_short_opt_arg=${1#??} + func_split_short_opt_name=${1%"$func_split_short_opt_arg"} + }' +else + # ...otherwise fall back to using expr, which is often a shell builtin. + func_split_short_opt () + { + $debug_cmd + + func_split_short_opt_name=`expr "x$1" : 'x-\(.\)'` + func_split_short_opt_arg=`expr "x$1" : 'x-.\(.*\)$'` + } +fi #func_split_short_opt + + +# func_usage +# ---------- +# Echo short help message to standard output and exit. +func_usage () +{ + $debug_cmd + + func_usage_message + $ECHO "Run '$progname --help |${PAGER-more}' for full usage" + exit 0 +} + + +# func_usage_message +# ------------------ +# Echo short help message to standard output. +func_usage_message () +{ + $debug_cmd + + eval \$ECHO \""Usage: $usage"\" + echo + $SED -n 's|^# || + /^Written by/{ + x;p;x + } + h + /^Written by/q' < "$progpath" + echo + eval \$ECHO \""$usage_message"\" +} + + +# func_version +# ------------ +# Echo version message to standard output and exit. +func_version () +{ + $debug_cmd + + printf '%s\n' "$progname $scriptversion" + $SED -n ' + /(C)/!b go + :more + /\./!{ + N + s|\n# | | + b more + } + :go + /^# Written by /,/# warranty; / { + s|^# || + s|^# *$|| + s|\((C)\)[ 0-9,-]*[ ,-]\([1-9][0-9]* \)|\1 \2| + p + } + /^# Written by / { + s|^# || + p + } + /^warranty; /q' < "$progpath" + + exit $? +} + + +# Local variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-pattern: "10/scriptversion=%:y-%02m-%02d.%02H; # UTC" +# time-stamp-time-zone: "UTC" +# End: + +# Set a version string. +scriptversion='(GNU libtool) 2.4.6' + + +# func_echo ARG... +# ---------------- +# Libtool also displays the current mode in messages, so override +# funclib.sh func_echo with this custom definition. +func_echo () +{ + $debug_cmd + + _G_message=$* + + func_echo_IFS=$IFS + IFS=$nl + for _G_line in $_G_message; do + IFS=$func_echo_IFS + $ECHO "$progname${opt_mode+: $opt_mode}: $_G_line" + done + IFS=$func_echo_IFS +} + + +# func_warning ARG... +# ------------------- +# Libtool warnings are not categorized, so override funclib.sh +# func_warning with this simpler definition. +func_warning () +{ + $debug_cmd + + $warning_func ${1+"$@"} +} + + +## ---------------- ## +## Options parsing. ## +## ---------------- ## + +# Hook in the functions to make sure our own options are parsed during +# the option parsing loop. + +usage='$progpath [OPTION]... [MODE-ARG]...' + +# Short help message in response to '-h'. +usage_message="Options: + --config show all configuration variables + --debug enable verbose shell tracing + -n, --dry-run display commands without modifying any files + --features display basic configuration information and exit + --mode=MODE use operation mode MODE + --no-warnings equivalent to '-Wnone' + --preserve-dup-deps don't remove duplicate dependency libraries + --quiet, --silent don't print informational messages + --tag=TAG use configuration variables from tag TAG + -v, --verbose print more informational messages than default + --version print version information + -W, --warnings=CATEGORY report the warnings falling in CATEGORY [all] + -h, --help, --help-all print short, long, or detailed help message +" + +# Additional text appended to 'usage_message' in response to '--help'. +func_help () +{ + $debug_cmd + + func_usage_message + $ECHO "$long_help_message + +MODE must be one of the following: + + clean remove files from the build directory + compile compile a source file into a libtool object + execute automatically set library path, then run a program + finish complete the installation of libtool libraries + install install libraries or executables + link create a library or an executable + uninstall remove libraries from an installed directory + +MODE-ARGS vary depending on the MODE. When passed as first option, +'--mode=MODE' may be abbreviated as 'MODE' or a unique abbreviation of that. +Try '$progname --help --mode=MODE' for a more detailed description of MODE. + +When reporting a bug, please describe a test case to reproduce it and +include the following information: + + host-triplet: $host + shell: $SHELL + compiler: $LTCC + compiler flags: $LTCFLAGS + linker: $LD (gnu? $with_gnu_ld) + version: $progname $scriptversion Debian-2.4.6-11 + automake: `($AUTOMAKE --version) 2>/dev/null |$SED 1q` + autoconf: `($AUTOCONF --version) 2>/dev/null |$SED 1q` + +Report bugs to . +GNU libtool home page: . +General help using GNU software: ." + exit 0 +} + + +# func_lo2o OBJECT-NAME +# --------------------- +# Transform OBJECT-NAME from a '.lo' suffix to the platform specific +# object suffix. + +lo2o=s/\\.lo\$/.$objext/ +o2lo=s/\\.$objext\$/.lo/ + +if test yes = "$_G_HAVE_XSI_OPS"; then + eval 'func_lo2o () + { + case $1 in + *.lo) func_lo2o_result=${1%.lo}.$objext ;; + * ) func_lo2o_result=$1 ;; + esac + }' + + # func_xform LIBOBJ-OR-SOURCE + # --------------------------- + # Transform LIBOBJ-OR-SOURCE from a '.o' or '.c' (or otherwise) + # suffix to a '.lo' libtool-object suffix. + eval 'func_xform () + { + func_xform_result=${1%.*}.lo + }' +else + # ...otherwise fall back to using sed. + func_lo2o () + { + func_lo2o_result=`$ECHO "$1" | $SED "$lo2o"` + } + + func_xform () + { + func_xform_result=`$ECHO "$1" | $SED 's|\.[^.]*$|.lo|'` + } +fi + + +# func_fatal_configuration ARG... +# ------------------------------- +# Echo program name prefixed message to standard error, followed by +# a configuration failure hint, and exit. +func_fatal_configuration () +{ + func__fatal_error ${1+"$@"} \ + "See the $PACKAGE documentation for more information." \ + "Fatal configuration error." +} + + +# func_config +# ----------- +# Display the configuration for all the tags in this script. +func_config () +{ + re_begincf='^# ### BEGIN LIBTOOL' + re_endcf='^# ### END LIBTOOL' + + # Default configuration. + $SED "1,/$re_begincf CONFIG/d;/$re_endcf CONFIG/,\$d" < "$progpath" + + # Now print the configurations for the tags. + for tagname in $taglist; do + $SED -n "/$re_begincf TAG CONFIG: $tagname\$/,/$re_endcf TAG CONFIG: $tagname\$/p" < "$progpath" + done + + exit $? +} + + +# func_features +# ------------- +# Display the features supported by this script. +func_features () +{ + echo "host: $host" + if test yes = "$build_libtool_libs"; then + echo "enable shared libraries" + else + echo "disable shared libraries" + fi + if test yes = "$build_old_libs"; then + echo "enable static libraries" + else + echo "disable static libraries" + fi + + exit $? +} + + +# func_enable_tag TAGNAME +# ----------------------- +# Verify that TAGNAME is valid, and either flag an error and exit, or +# enable the TAGNAME tag. We also add TAGNAME to the global $taglist +# variable here. +func_enable_tag () +{ + # Global variable: + tagname=$1 + + re_begincf="^# ### BEGIN LIBTOOL TAG CONFIG: $tagname\$" + re_endcf="^# ### END LIBTOOL TAG CONFIG: $tagname\$" + sed_extractcf=/$re_begincf/,/$re_endcf/p + + # Validate tagname. + case $tagname in + *[!-_A-Za-z0-9,/]*) + func_fatal_error "invalid tag name: $tagname" + ;; + esac + + # Don't test for the "default" C tag, as we know it's + # there but not specially marked. + case $tagname in + CC) ;; + *) + if $GREP "$re_begincf" "$progpath" >/dev/null 2>&1; then + taglist="$taglist $tagname" + + # Evaluate the configuration. Be careful to quote the path + # and the sed script, to avoid splitting on whitespace, but + # also don't use non-portable quotes within backquotes within + # quotes we have to do it in 2 steps: + extractedcf=`$SED -n -e "$sed_extractcf" < "$progpath"` + eval "$extractedcf" + else + func_error "ignoring unknown tag $tagname" + fi + ;; + esac +} + + +# func_check_version_match +# ------------------------ +# Ensure that we are using m4 macros, and libtool script from the same +# release of libtool. +func_check_version_match () +{ + if test "$package_revision" != "$macro_revision"; then + if test "$VERSION" != "$macro_version"; then + if test -z "$macro_version"; then + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, but the +$progname: definition of this LT_INIT comes from an older release. +$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION +$progname: and run autoconf again. +_LT_EOF + else + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, but the +$progname: definition of this LT_INIT comes from $PACKAGE $macro_version. +$progname: You should recreate aclocal.m4 with macros from $PACKAGE $VERSION +$progname: and run autoconf again. +_LT_EOF + fi + else + cat >&2 <<_LT_EOF +$progname: Version mismatch error. This is $PACKAGE $VERSION, revision $package_revision, +$progname: but the definition of this LT_INIT comes from revision $macro_revision. +$progname: You should recreate aclocal.m4 with macros from revision $package_revision +$progname: of $PACKAGE $VERSION and run autoconf again. +_LT_EOF + fi + + exit $EXIT_MISMATCH + fi +} + + +# libtool_options_prep [ARG]... +# ----------------------------- +# Preparation for options parsed by libtool. +libtool_options_prep () +{ + $debug_mode + + # Option defaults: + opt_config=false + opt_dlopen= + opt_dry_run=false + opt_help=false + opt_mode= + opt_preserve_dup_deps=false + opt_quiet=false + + nonopt= + preserve_args= + + _G_rc_lt_options_prep=: + + # Shorthand for --mode=foo, only valid as the first argument + case $1 in + clean|clea|cle|cl) + shift; set dummy --mode clean ${1+"$@"}; shift + ;; + compile|compil|compi|comp|com|co|c) + shift; set dummy --mode compile ${1+"$@"}; shift + ;; + execute|execut|execu|exec|exe|ex|e) + shift; set dummy --mode execute ${1+"$@"}; shift + ;; + finish|finis|fini|fin|fi|f) + shift; set dummy --mode finish ${1+"$@"}; shift + ;; + install|instal|insta|inst|ins|in|i) + shift; set dummy --mode install ${1+"$@"}; shift + ;; + link|lin|li|l) + shift; set dummy --mode link ${1+"$@"}; shift + ;; + uninstall|uninstal|uninsta|uninst|unins|unin|uni|un|u) + shift; set dummy --mode uninstall ${1+"$@"}; shift + ;; + *) + _G_rc_lt_options_prep=false + ;; + esac + + if $_G_rc_lt_options_prep; then + # Pass back the list of options. + func_quote_for_eval ${1+"$@"} + libtool_options_prep_result=$func_quote_for_eval_result + fi + + $_G_rc_lt_options_prep +} +func_add_hook func_options_prep libtool_options_prep + + +# libtool_parse_options [ARG]... +# --------------------------------- +# Provide handling for libtool specific options. +libtool_parse_options () +{ + $debug_cmd + + _G_rc_lt_parse_options=false + + # Perform our own loop to consume as many options as possible in + # each iteration. + while test $# -gt 0; do + _G_match_lt_parse_options=: + _G_opt=$1 + shift + case $_G_opt in + --dry-run|--dryrun|-n) + opt_dry_run=: + ;; + + --config) func_config ;; + + --dlopen|-dlopen) + opt_dlopen="${opt_dlopen+$opt_dlopen +}$1" + shift + ;; + + --preserve-dup-deps) + opt_preserve_dup_deps=: ;; + + --features) func_features ;; + + --finish) set dummy --mode finish ${1+"$@"}; shift ;; + + --help) opt_help=: ;; + + --help-all) opt_help=': help-all' ;; + + --mode) test $# = 0 && func_missing_arg $_G_opt && break + opt_mode=$1 + case $1 in + # Valid mode arguments: + clean|compile|execute|finish|install|link|relink|uninstall) ;; + + # Catch anything else as an error + *) func_error "invalid argument for $_G_opt" + exit_cmd=exit + break + ;; + esac + shift + ;; + + --no-silent|--no-quiet) + opt_quiet=false + func_append preserve_args " $_G_opt" + ;; + + --no-warnings|--no-warning|--no-warn) + opt_warning=false + func_append preserve_args " $_G_opt" + ;; + + --no-verbose) + opt_verbose=false + func_append preserve_args " $_G_opt" + ;; + + --silent|--quiet) + opt_quiet=: + opt_verbose=false + func_append preserve_args " $_G_opt" + ;; + + --tag) test $# = 0 && func_missing_arg $_G_opt && break + opt_tag=$1 + func_append preserve_args " $_G_opt $1" + func_enable_tag "$1" + shift + ;; + + --verbose|-v) opt_quiet=false + opt_verbose=: + func_append preserve_args " $_G_opt" + ;; + + # An option not handled by this hook function: + *) set dummy "$_G_opt" ${1+"$@"} ; shift + _G_match_lt_parse_options=false + break + ;; + esac + $_G_match_lt_parse_options && _G_rc_lt_parse_options=: + done + + if $_G_rc_lt_parse_options; then + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + libtool_parse_options_result=$func_quote_for_eval_result + fi + + $_G_rc_lt_parse_options +} +func_add_hook func_parse_options libtool_parse_options + + + +# libtool_validate_options [ARG]... +# --------------------------------- +# Perform any sanity checks on option settings and/or unconsumed +# arguments. +libtool_validate_options () +{ + # save first non-option argument + if test 0 -lt $#; then + nonopt=$1 + shift + fi + + # preserve --debug + test : = "$debug_cmd" || func_append preserve_args " --debug" + + case $host in + # Solaris2 added to fix http://debbugs.gnu.org/cgi/bugreport.cgi?bug=16452 + # see also: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=59788 + *cygwin* | *mingw* | *pw32* | *cegcc* | *solaris2* | *os2*) + # don't eliminate duplications in $postdeps and $predeps + opt_duplicate_compiler_generated_deps=: + ;; + *) + opt_duplicate_compiler_generated_deps=$opt_preserve_dup_deps + ;; + esac + + $opt_help || { + # Sanity checks first: + func_check_version_match + + test yes != "$build_libtool_libs" \ + && test yes != "$build_old_libs" \ + && func_fatal_configuration "not configured to build any kind of library" + + # Darwin sucks + eval std_shrext=\"$shrext_cmds\" + + # Only execute mode is allowed to have -dlopen flags. + if test -n "$opt_dlopen" && test execute != "$opt_mode"; then + func_error "unrecognized option '-dlopen'" + $ECHO "$help" 1>&2 + exit $EXIT_FAILURE + fi + + # Change the help message to a mode-specific one. + generic_help=$help + help="Try '$progname --help --mode=$opt_mode' for more information." + } + + # Pass back the unparsed argument list + func_quote_for_eval ${1+"$@"} + libtool_validate_options_result=$func_quote_for_eval_result +} +func_add_hook func_validate_options libtool_validate_options + + +# Process options as early as possible so that --help and --version +# can return quickly. +func_options ${1+"$@"} +eval set dummy "$func_options_result"; shift + + + +## ----------- ## +## Main. ## +## ----------- ## + +magic='%%%MAGIC variable%%%' +magic_exe='%%%MAGIC EXE variable%%%' + +# Global variables. +extracted_archives= +extracted_serial=0 + +# If this variable is set in any of the actions, the command in it +# will be execed at the end. This prevents here-documents from being +# left over by shells. +exec_cmd= + + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +$1 +_LTECHO_EOF' +} + +# func_generated_by_libtool +# True iff stdin has been generated by Libtool. This function is only +# a basic sanity check; it will hardly flush out determined imposters. +func_generated_by_libtool_p () +{ + $GREP "^# Generated by .*$PACKAGE" > /dev/null 2>&1 +} + +# func_lalib_p file +# True iff FILE is a libtool '.la' library or '.lo' object file. +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_lalib_p () +{ + test -f "$1" && + $SED -e 4q "$1" 2>/dev/null | func_generated_by_libtool_p +} + +# func_lalib_unsafe_p file +# True iff FILE is a libtool '.la' library or '.lo' object file. +# This function implements the same check as func_lalib_p without +# resorting to external programs. To this end, it redirects stdin and +# closes it afterwards, without saving the original file descriptor. +# As a safety measure, use it only where a negative result would be +# fatal anyway. Works if 'file' does not exist. +func_lalib_unsafe_p () +{ + lalib_p=no + if test -f "$1" && test -r "$1" && exec 5<&0 <"$1"; then + for lalib_p_l in 1 2 3 4 + do + read lalib_p_line + case $lalib_p_line in + \#\ Generated\ by\ *$PACKAGE* ) lalib_p=yes; break;; + esac + done + exec 0<&5 5<&- + fi + test yes = "$lalib_p" +} + +# func_ltwrapper_script_p file +# True iff FILE is a libtool wrapper script +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_script_p () +{ + test -f "$1" && + $lt_truncate_bin < "$1" 2>/dev/null | func_generated_by_libtool_p +} + +# func_ltwrapper_executable_p file +# True iff FILE is a libtool wrapper executable +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_executable_p () +{ + func_ltwrapper_exec_suffix= + case $1 in + *.exe) ;; + *) func_ltwrapper_exec_suffix=.exe ;; + esac + $GREP "$magic_exe" "$1$func_ltwrapper_exec_suffix" >/dev/null 2>&1 +} + +# func_ltwrapper_scriptname file +# Assumes file is an ltwrapper_executable +# uses $file to determine the appropriate filename for a +# temporary ltwrapper_script. +func_ltwrapper_scriptname () +{ + func_dirname_and_basename "$1" "" "." + func_stripname '' '.exe' "$func_basename_result" + func_ltwrapper_scriptname_result=$func_dirname_result/$objdir/${func_stripname_result}_ltshwrapper +} + +# func_ltwrapper_p file +# True iff FILE is a libtool wrapper script or wrapper executable +# This function is only a basic sanity check; it will hardly flush out +# determined imposters. +func_ltwrapper_p () +{ + func_ltwrapper_script_p "$1" || func_ltwrapper_executable_p "$1" +} + + +# func_execute_cmds commands fail_cmd +# Execute tilde-delimited COMMANDS. +# If FAIL_CMD is given, eval that upon failure. +# FAIL_CMD may read-access the current command in variable CMD! +func_execute_cmds () +{ + $debug_cmd + + save_ifs=$IFS; IFS='~' + for cmd in $1; do + IFS=$sp$nl + eval cmd=\"$cmd\" + IFS=$save_ifs + func_show_eval "$cmd" "${2-:}" + done + IFS=$save_ifs +} + + +# func_source file +# Source FILE, adding directory component if necessary. +# Note that it is not necessary on cygwin/mingw to append a dot to +# FILE even if both FILE and FILE.exe exist: automatic-append-.exe +# behavior happens only for exec(3), not for open(2)! Also, sourcing +# 'FILE.' does not work on cygwin managed mounts. +func_source () +{ + $debug_cmd + + case $1 in + */* | *\\*) . "$1" ;; + *) . "./$1" ;; + esac +} + + +# func_resolve_sysroot PATH +# Replace a leading = in PATH with a sysroot. Store the result into +# func_resolve_sysroot_result +func_resolve_sysroot () +{ + func_resolve_sysroot_result=$1 + case $func_resolve_sysroot_result in + =*) + func_stripname '=' '' "$func_resolve_sysroot_result" + func_resolve_sysroot_result=$lt_sysroot$func_stripname_result + ;; + esac +} + +# func_replace_sysroot PATH +# If PATH begins with the sysroot, replace it with = and +# store the result into func_replace_sysroot_result. +func_replace_sysroot () +{ + case $lt_sysroot:$1 in + ?*:"$lt_sysroot"*) + func_stripname "$lt_sysroot" '' "$1" + func_replace_sysroot_result='='$func_stripname_result + ;; + *) + # Including no sysroot. + func_replace_sysroot_result=$1 + ;; + esac +} + +# func_infer_tag arg +# Infer tagged configuration to use if any are available and +# if one wasn't chosen via the "--tag" command line option. +# Only attempt this if the compiler in the base compile +# command doesn't match the default compiler. +# arg is usually of the form 'gcc ...' +func_infer_tag () +{ + $debug_cmd + + if test -n "$available_tags" && test -z "$tagname"; then + CC_quoted= + for arg in $CC; do + func_append_quoted CC_quoted "$arg" + done + CC_expanded=`func_echo_all $CC` + CC_quoted_expanded=`func_echo_all $CC_quoted` + case $@ in + # Blanks in the command may have been stripped by the calling shell, + # but not from the CC environment variable when configure was run. + " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ + " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) ;; + # Blanks at the start of $base_compile will cause this to fail + # if we don't check for them as well. + *) + for z in $available_tags; do + if $GREP "^# ### BEGIN LIBTOOL TAG CONFIG: $z$" < "$progpath" > /dev/null; then + # Evaluate the configuration. + eval "`$SED -n -e '/^# ### BEGIN LIBTOOL TAG CONFIG: '$z'$/,/^# ### END LIBTOOL TAG CONFIG: '$z'$/p' < $progpath`" + CC_quoted= + for arg in $CC; do + # Double-quote args containing other shell metacharacters. + func_append_quoted CC_quoted "$arg" + done + CC_expanded=`func_echo_all $CC` + CC_quoted_expanded=`func_echo_all $CC_quoted` + case "$@ " in + " $CC "* | "$CC "* | " $CC_expanded "* | "$CC_expanded "* | \ + " $CC_quoted"* | "$CC_quoted "* | " $CC_quoted_expanded "* | "$CC_quoted_expanded "*) + # The compiler in the base compile command matches + # the one in the tagged configuration. + # Assume this is the tagged configuration we want. + tagname=$z + break + ;; + esac + fi + done + # If $tagname still isn't set, then no tagged configuration + # was found and let the user know that the "--tag" command + # line option must be used. + if test -z "$tagname"; then + func_echo "unable to infer tagged configuration" + func_fatal_error "specify a tag with '--tag'" +# else +# func_verbose "using $tagname tagged configuration" + fi + ;; + esac + fi +} + + + +# func_write_libtool_object output_name pic_name nonpic_name +# Create a libtool object file (analogous to a ".la" file), +# but don't create it if we're doing a dry run. +func_write_libtool_object () +{ + write_libobj=$1 + if test yes = "$build_libtool_libs"; then + write_lobj=\'$2\' + else + write_lobj=none + fi + + if test yes = "$build_old_libs"; then + write_oldobj=\'$3\' + else + write_oldobj=none + fi + + $opt_dry_run || { + cat >${write_libobj}T </dev/null` + if test "$?" -eq 0 && test -n "$func_convert_core_file_wine_to_w32_tmp"; then + func_convert_core_file_wine_to_w32_result=`$ECHO "$func_convert_core_file_wine_to_w32_tmp" | + $SED -e "$sed_naive_backslashify"` + else + func_convert_core_file_wine_to_w32_result= + fi + fi +} +# end: func_convert_core_file_wine_to_w32 + + +# func_convert_core_path_wine_to_w32 ARG +# Helper function used by path conversion functions when $build is *nix, and +# $host is mingw, cygwin, or some other w32 environment. Relies on a correctly +# configured wine environment available, with the winepath program in $build's +# $PATH. Assumes ARG has no leading or trailing path separator characters. +# +# ARG is path to be converted from $build format to win32. +# Result is available in $func_convert_core_path_wine_to_w32_result. +# Unconvertible file (directory) names in ARG are skipped; if no directory names +# are convertible, then the result may be empty. +func_convert_core_path_wine_to_w32 () +{ + $debug_cmd + + # unfortunately, winepath doesn't convert paths, only file names + func_convert_core_path_wine_to_w32_result= + if test -n "$1"; then + oldIFS=$IFS + IFS=: + for func_convert_core_path_wine_to_w32_f in $1; do + IFS=$oldIFS + func_convert_core_file_wine_to_w32 "$func_convert_core_path_wine_to_w32_f" + if test -n "$func_convert_core_file_wine_to_w32_result"; then + if test -z "$func_convert_core_path_wine_to_w32_result"; then + func_convert_core_path_wine_to_w32_result=$func_convert_core_file_wine_to_w32_result + else + func_append func_convert_core_path_wine_to_w32_result ";$func_convert_core_file_wine_to_w32_result" + fi + fi + done + IFS=$oldIFS + fi +} +# end: func_convert_core_path_wine_to_w32 + + +# func_cygpath ARGS... +# Wrapper around calling the cygpath program via LT_CYGPATH. This is used when +# when (1) $build is *nix and Cygwin is hosted via a wine environment; or (2) +# $build is MSYS and $host is Cygwin, or (3) $build is Cygwin. In case (1) or +# (2), returns the Cygwin file name or path in func_cygpath_result (input +# file name or path is assumed to be in w32 format, as previously converted +# from $build's *nix or MSYS format). In case (3), returns the w32 file name +# or path in func_cygpath_result (input file name or path is assumed to be in +# Cygwin format). Returns an empty string on error. +# +# ARGS are passed to cygpath, with the last one being the file name or path to +# be converted. +# +# Specify the absolute *nix (or w32) name to cygpath in the LT_CYGPATH +# environment variable; do not put it in $PATH. +func_cygpath () +{ + $debug_cmd + + if test -n "$LT_CYGPATH" && test -f "$LT_CYGPATH"; then + func_cygpath_result=`$LT_CYGPATH "$@" 2>/dev/null` + if test "$?" -ne 0; then + # on failure, ensure result is empty + func_cygpath_result= + fi + else + func_cygpath_result= + func_error "LT_CYGPATH is empty or specifies non-existent file: '$LT_CYGPATH'" + fi +} +#end: func_cygpath + + +# func_convert_core_msys_to_w32 ARG +# Convert file name or path ARG from MSYS format to w32 format. Return +# result in func_convert_core_msys_to_w32_result. +func_convert_core_msys_to_w32 () +{ + $debug_cmd + + # awkward: cmd appends spaces to result + func_convert_core_msys_to_w32_result=`( cmd //c echo "$1" ) 2>/dev/null | + $SED -e 's/[ ]*$//' -e "$sed_naive_backslashify"` +} +#end: func_convert_core_msys_to_w32 + + +# func_convert_file_check ARG1 ARG2 +# Verify that ARG1 (a file name in $build format) was converted to $host +# format in ARG2. Otherwise, emit an error message, but continue (resetting +# func_to_host_file_result to ARG1). +func_convert_file_check () +{ + $debug_cmd + + if test -z "$2" && test -n "$1"; then + func_error "Could not determine host file name corresponding to" + func_error " '$1'" + func_error "Continuing, but uninstalled executables may not work." + # Fallback: + func_to_host_file_result=$1 + fi +} +# end func_convert_file_check + + +# func_convert_path_check FROM_PATHSEP TO_PATHSEP FROM_PATH TO_PATH +# Verify that FROM_PATH (a path in $build format) was converted to $host +# format in TO_PATH. Otherwise, emit an error message, but continue, resetting +# func_to_host_file_result to a simplistic fallback value (see below). +func_convert_path_check () +{ + $debug_cmd + + if test -z "$4" && test -n "$3"; then + func_error "Could not determine the host path corresponding to" + func_error " '$3'" + func_error "Continuing, but uninstalled executables may not work." + # Fallback. This is a deliberately simplistic "conversion" and + # should not be "improved". See libtool.info. + if test "x$1" != "x$2"; then + lt_replace_pathsep_chars="s|$1|$2|g" + func_to_host_path_result=`echo "$3" | + $SED -e "$lt_replace_pathsep_chars"` + else + func_to_host_path_result=$3 + fi + fi +} +# end func_convert_path_check + + +# func_convert_path_front_back_pathsep FRONTPAT BACKPAT REPL ORIG +# Modifies func_to_host_path_result by prepending REPL if ORIG matches FRONTPAT +# and appending REPL if ORIG matches BACKPAT. +func_convert_path_front_back_pathsep () +{ + $debug_cmd + + case $4 in + $1 ) func_to_host_path_result=$3$func_to_host_path_result + ;; + esac + case $4 in + $2 ) func_append func_to_host_path_result "$3" + ;; + esac +} +# end func_convert_path_front_back_pathsep + + +################################################## +# $build to $host FILE NAME CONVERSION FUNCTIONS # +################################################## +# invoked via '$to_host_file_cmd ARG' +# +# In each case, ARG is the path to be converted from $build to $host format. +# Result will be available in $func_to_host_file_result. + + +# func_to_host_file ARG +# Converts the file name ARG from $build format to $host format. Return result +# in func_to_host_file_result. +func_to_host_file () +{ + $debug_cmd + + $to_host_file_cmd "$1" +} +# end func_to_host_file + + +# func_to_tool_file ARG LAZY +# converts the file name ARG from $build format to toolchain format. Return +# result in func_to_tool_file_result. If the conversion in use is listed +# in (the comma separated) LAZY, no conversion takes place. +func_to_tool_file () +{ + $debug_cmd + + case ,$2, in + *,"$to_tool_file_cmd",*) + func_to_tool_file_result=$1 + ;; + *) + $to_tool_file_cmd "$1" + func_to_tool_file_result=$func_to_host_file_result + ;; + esac +} +# end func_to_tool_file + + +# func_convert_file_noop ARG +# Copy ARG to func_to_host_file_result. +func_convert_file_noop () +{ + func_to_host_file_result=$1 +} +# end func_convert_file_noop + + +# func_convert_file_msys_to_w32 ARG +# Convert file name ARG from (mingw) MSYS to (mingw) w32 format; automatic +# conversion to w32 is not available inside the cwrapper. Returns result in +# func_to_host_file_result. +func_convert_file_msys_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_msys_to_w32 "$1" + func_to_host_file_result=$func_convert_core_msys_to_w32_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_msys_to_w32 + + +# func_convert_file_cygwin_to_w32 ARG +# Convert file name ARG from Cygwin to w32 format. Returns result in +# func_to_host_file_result. +func_convert_file_cygwin_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + # because $build is cygwin, we call "the" cygpath in $PATH; no need to use + # LT_CYGPATH in this case. + func_to_host_file_result=`cygpath -m "$1"` + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_cygwin_to_w32 + + +# func_convert_file_nix_to_w32 ARG +# Convert file name ARG from *nix to w32 format. Requires a wine environment +# and a working winepath. Returns result in func_to_host_file_result. +func_convert_file_nix_to_w32 () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_file_wine_to_w32 "$1" + func_to_host_file_result=$func_convert_core_file_wine_to_w32_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_nix_to_w32 + + +# func_convert_file_msys_to_cygwin ARG +# Convert file name ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. +# Returns result in func_to_host_file_result. +func_convert_file_msys_to_cygwin () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + func_convert_core_msys_to_w32 "$1" + func_cygpath -u "$func_convert_core_msys_to_w32_result" + func_to_host_file_result=$func_cygpath_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_msys_to_cygwin + + +# func_convert_file_nix_to_cygwin ARG +# Convert file name ARG from *nix to Cygwin format. Requires Cygwin installed +# in a wine environment, working winepath, and LT_CYGPATH set. Returns result +# in func_to_host_file_result. +func_convert_file_nix_to_cygwin () +{ + $debug_cmd + + func_to_host_file_result=$1 + if test -n "$1"; then + # convert from *nix to w32, then use cygpath to convert from w32 to cygwin. + func_convert_core_file_wine_to_w32 "$1" + func_cygpath -u "$func_convert_core_file_wine_to_w32_result" + func_to_host_file_result=$func_cygpath_result + fi + func_convert_file_check "$1" "$func_to_host_file_result" +} +# end func_convert_file_nix_to_cygwin + + +############################################# +# $build to $host PATH CONVERSION FUNCTIONS # +############################################# +# invoked via '$to_host_path_cmd ARG' +# +# In each case, ARG is the path to be converted from $build to $host format. +# The result will be available in $func_to_host_path_result. +# +# Path separators are also converted from $build format to $host format. If +# ARG begins or ends with a path separator character, it is preserved (but +# converted to $host format) on output. +# +# All path conversion functions are named using the following convention: +# file name conversion function : func_convert_file_X_to_Y () +# path conversion function : func_convert_path_X_to_Y () +# where, for any given $build/$host combination the 'X_to_Y' value is the +# same. If conversion functions are added for new $build/$host combinations, +# the two new functions must follow this pattern, or func_init_to_host_path_cmd +# will break. + + +# func_init_to_host_path_cmd +# Ensures that function "pointer" variable $to_host_path_cmd is set to the +# appropriate value, based on the value of $to_host_file_cmd. +to_host_path_cmd= +func_init_to_host_path_cmd () +{ + $debug_cmd + + if test -z "$to_host_path_cmd"; then + func_stripname 'func_convert_file_' '' "$to_host_file_cmd" + to_host_path_cmd=func_convert_path_$func_stripname_result + fi +} + + +# func_to_host_path ARG +# Converts the path ARG from $build format to $host format. Return result +# in func_to_host_path_result. +func_to_host_path () +{ + $debug_cmd + + func_init_to_host_path_cmd + $to_host_path_cmd "$1" +} +# end func_to_host_path + + +# func_convert_path_noop ARG +# Copy ARG to func_to_host_path_result. +func_convert_path_noop () +{ + func_to_host_path_result=$1 +} +# end func_convert_path_noop + + +# func_convert_path_msys_to_w32 ARG +# Convert path ARG from (mingw) MSYS to (mingw) w32 format; automatic +# conversion to w32 is not available inside the cwrapper. Returns result in +# func_to_host_path_result. +func_convert_path_msys_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # Remove leading and trailing path separator characters from ARG. MSYS + # behavior is inconsistent here; cygpath turns them into '.;' and ';.'; + # and winepath ignores them completely. + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" + func_to_host_path_result=$func_convert_core_msys_to_w32_result + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_msys_to_w32 + + +# func_convert_path_cygwin_to_w32 ARG +# Convert path ARG from Cygwin to w32 format. Returns result in +# func_to_host_file_result. +func_convert_path_cygwin_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_to_host_path_result=`cygpath -m -p "$func_to_host_path_tmp1"` + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_cygwin_to_w32 + + +# func_convert_path_nix_to_w32 ARG +# Convert path ARG from *nix to w32 format. Requires a wine environment and +# a working winepath. Returns result in func_to_host_file_result. +func_convert_path_nix_to_w32 () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" + func_to_host_path_result=$func_convert_core_path_wine_to_w32_result + func_convert_path_check : ";" \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" ";" "$1" + fi +} +# end func_convert_path_nix_to_w32 + + +# func_convert_path_msys_to_cygwin ARG +# Convert path ARG from MSYS to Cygwin format. Requires LT_CYGPATH set. +# Returns result in func_to_host_file_result. +func_convert_path_msys_to_cygwin () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # See func_convert_path_msys_to_w32: + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_msys_to_w32 "$func_to_host_path_tmp1" + func_cygpath -u -p "$func_convert_core_msys_to_w32_result" + func_to_host_path_result=$func_cygpath_result + func_convert_path_check : : \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" : "$1" + fi +} +# end func_convert_path_msys_to_cygwin + + +# func_convert_path_nix_to_cygwin ARG +# Convert path ARG from *nix to Cygwin format. Requires Cygwin installed in a +# a wine environment, working winepath, and LT_CYGPATH set. Returns result in +# func_to_host_file_result. +func_convert_path_nix_to_cygwin () +{ + $debug_cmd + + func_to_host_path_result=$1 + if test -n "$1"; then + # Remove leading and trailing path separator characters from + # ARG. msys behavior is inconsistent here, cygpath turns them + # into '.;' and ';.', and winepath ignores them completely. + func_stripname : : "$1" + func_to_host_path_tmp1=$func_stripname_result + func_convert_core_path_wine_to_w32 "$func_to_host_path_tmp1" + func_cygpath -u -p "$func_convert_core_path_wine_to_w32_result" + func_to_host_path_result=$func_cygpath_result + func_convert_path_check : : \ + "$func_to_host_path_tmp1" "$func_to_host_path_result" + func_convert_path_front_back_pathsep ":*" "*:" : "$1" + fi +} +# end func_convert_path_nix_to_cygwin + + +# func_dll_def_p FILE +# True iff FILE is a Windows DLL '.def' file. +# Keep in sync with _LT_DLL_DEF_P in libtool.m4 +func_dll_def_p () +{ + $debug_cmd + + func_dll_def_p_tmp=`$SED -n \ + -e 's/^[ ]*//' \ + -e '/^\(;.*\)*$/d' \ + -e 's/^\(EXPORTS\|LIBRARY\)\([ ].*\)*$/DEF/p' \ + -e q \ + "$1"` + test DEF = "$func_dll_def_p_tmp" +} + + +# func_mode_compile arg... +func_mode_compile () +{ + $debug_cmd + + # Get the compilation command and the source file. + base_compile= + srcfile=$nonopt # always keep a non-empty value in "srcfile" + suppress_opt=yes + suppress_output= + arg_mode=normal + libobj= + later= + pie_flag= + + for arg + do + case $arg_mode in + arg ) + # do not "continue". Instead, add this to base_compile + lastarg=$arg + arg_mode=normal + ;; + + target ) + libobj=$arg + arg_mode=normal + continue + ;; + + normal ) + # Accept any command-line options. + case $arg in + -o) + test -n "$libobj" && \ + func_fatal_error "you cannot specify '-o' more than once" + arg_mode=target + continue + ;; + + -pie | -fpie | -fPIE) + func_append pie_flag " $arg" + continue + ;; + + -shared | -static | -prefer-pic | -prefer-non-pic) + func_append later " $arg" + continue + ;; + + -no-suppress) + suppress_opt=no + continue + ;; + + -Xcompiler) + arg_mode=arg # the next one goes into the "base_compile" arg list + continue # The current "srcfile" will either be retained or + ;; # replaced later. I would guess that would be a bug. + + -Wc,*) + func_stripname '-Wc,' '' "$arg" + args=$func_stripname_result + lastarg= + save_ifs=$IFS; IFS=, + for arg in $args; do + IFS=$save_ifs + func_append_quoted lastarg "$arg" + done + IFS=$save_ifs + func_stripname ' ' '' "$lastarg" + lastarg=$func_stripname_result + + # Add the arguments to base_compile. + func_append base_compile " $lastarg" + continue + ;; + + *) + # Accept the current argument as the source file. + # The previous "srcfile" becomes the current argument. + # + lastarg=$srcfile + srcfile=$arg + ;; + esac # case $arg + ;; + esac # case $arg_mode + + # Aesthetically quote the previous argument. + func_append_quoted base_compile "$lastarg" + done # for arg + + case $arg_mode in + arg) + func_fatal_error "you must specify an argument for -Xcompile" + ;; + target) + func_fatal_error "you must specify a target with '-o'" + ;; + *) + # Get the name of the library object. + test -z "$libobj" && { + func_basename "$srcfile" + libobj=$func_basename_result + } + ;; + esac + + # Recognize several different file suffixes. + # If the user specifies -o file.o, it is replaced with file.lo + case $libobj in + *.[cCFSifmso] | \ + *.ada | *.adb | *.ads | *.asm | \ + *.c++ | *.cc | *.ii | *.class | *.cpp | *.cxx | \ + *.[fF][09]? | *.for | *.java | *.go | *.obj | *.sx | *.cu | *.cup) + func_xform "$libobj" + libobj=$func_xform_result + ;; + esac + + case $libobj in + *.lo) func_lo2o "$libobj"; obj=$func_lo2o_result ;; + *) + func_fatal_error "cannot determine name of library object from '$libobj'" + ;; + esac + + func_infer_tag $base_compile + + for arg in $later; do + case $arg in + -shared) + test yes = "$build_libtool_libs" \ + || func_fatal_configuration "cannot build a shared library" + build_old_libs=no + continue + ;; + + -static) + build_libtool_libs=no + build_old_libs=yes + continue + ;; + + -prefer-pic) + pic_mode=yes + continue + ;; + + -prefer-non-pic) + pic_mode=no + continue + ;; + esac + done + + func_quote_for_eval "$libobj" + test "X$libobj" != "X$func_quote_for_eval_result" \ + && $ECHO "X$libobj" | $GREP '[]~#^*{};<>?"'"'"' &()|`$[]' \ + && func_warning "libobj name '$libobj' may not contain shell special characters." + func_dirname_and_basename "$obj" "/" "" + objname=$func_basename_result + xdir=$func_dirname_result + lobj=$xdir$objdir/$objname + + test -z "$base_compile" && \ + func_fatal_help "you must specify a compilation command" + + # Delete any leftover library objects. + if test yes = "$build_old_libs"; then + removelist="$obj $lobj $libobj ${libobj}T" + else + removelist="$lobj $libobj ${libobj}T" + fi + + # On Cygwin there's no "real" PIC flag so we must build both object types + case $host_os in + cygwin* | mingw* | pw32* | os2* | cegcc*) + pic_mode=default + ;; + esac + if test no = "$pic_mode" && test pass_all != "$deplibs_check_method"; then + # non-PIC code in shared libraries is not supported + pic_mode=default + fi + + # Calculate the filename of the output object if compiler does + # not support -o with -c + if test no = "$compiler_c_o"; then + output_obj=`$ECHO "$srcfile" | $SED 's%^.*/%%; s%\.[^.]*$%%'`.$objext + lockfile=$output_obj.lock + else + output_obj= + need_locks=no + lockfile= + fi + + # Lock this critical section if it is needed + # We use this script file to make the link, it avoids creating a new file + if test yes = "$need_locks"; then + until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do + func_echo "Waiting for $lockfile to be removed" + sleep 2 + done + elif test warn = "$need_locks"; then + if test -f "$lockfile"; then + $ECHO "\ +*** ERROR, $lockfile exists and contains: +`cat $lockfile 2>/dev/null` + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + func_append removelist " $output_obj" + $ECHO "$srcfile" > "$lockfile" + fi + + $opt_dry_run || $RM $removelist + func_append removelist " $lockfile" + trap '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' 1 2 15 + + func_to_tool_file "$srcfile" func_convert_file_msys_to_w32 + srcfile=$func_to_tool_file_result + func_quote_for_eval "$srcfile" + qsrcfile=$func_quote_for_eval_result + + # Only build a PIC object if we are building libtool libraries. + if test yes = "$build_libtool_libs"; then + # Without this assignment, base_compile gets emptied. + fbsd_hideous_sh_bug=$base_compile + + if test no != "$pic_mode"; then + command="$base_compile $qsrcfile $pic_flag" + else + # Don't build PIC code + command="$base_compile $qsrcfile" + fi + + func_mkdir_p "$xdir$objdir" + + if test -z "$output_obj"; then + # Place PIC objects in $objdir + func_append command " -o $lobj" + fi + + func_show_eval_locale "$command" \ + 'test -n "$output_obj" && $RM $removelist; exit $EXIT_FAILURE' + + if test warn = "$need_locks" && + test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then + $ECHO "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + + # Just move the object if needed, then go on to compile the next one + if test -n "$output_obj" && test "X$output_obj" != "X$lobj"; then + func_show_eval '$MV "$output_obj" "$lobj"' \ + 'error=$?; $opt_dry_run || $RM $removelist; exit $error' + fi + + # Allow error messages only from the first compilation. + if test yes = "$suppress_opt"; then + suppress_output=' >/dev/null 2>&1' + fi + fi + + # Only build a position-dependent object if we build old libraries. + if test yes = "$build_old_libs"; then + if test yes != "$pic_mode"; then + # Don't build PIC code + command="$base_compile $qsrcfile$pie_flag" + else + command="$base_compile $qsrcfile $pic_flag" + fi + if test yes = "$compiler_c_o"; then + func_append command " -o $obj" + fi + + # Suppress compiler output if we already did a PIC compilation. + func_append command "$suppress_output" + func_show_eval_locale "$command" \ + '$opt_dry_run || $RM $removelist; exit $EXIT_FAILURE' + + if test warn = "$need_locks" && + test "X`cat $lockfile 2>/dev/null`" != "X$srcfile"; then + $ECHO "\ +*** ERROR, $lockfile contains: +`cat $lockfile 2>/dev/null` + +but it should contain: +$srcfile + +This indicates that another process is trying to use the same +temporary object file, and libtool could not work around it because +your compiler does not support '-c' and '-o' together. If you +repeat this compilation, it may succeed, by chance, but you had better +avoid parallel builds (make -j) in this platform, or get a better +compiler." + + $opt_dry_run || $RM $removelist + exit $EXIT_FAILURE + fi + + # Just move the object if needed + if test -n "$output_obj" && test "X$output_obj" != "X$obj"; then + func_show_eval '$MV "$output_obj" "$obj"' \ + 'error=$?; $opt_dry_run || $RM $removelist; exit $error' + fi + fi + + $opt_dry_run || { + func_write_libtool_object "$libobj" "$objdir/$objname" "$objname" + + # Unlock the critical section if it was locked + if test no != "$need_locks"; then + removelist=$lockfile + $RM "$lockfile" + fi + } + + exit $EXIT_SUCCESS +} + +$opt_help || { + test compile = "$opt_mode" && func_mode_compile ${1+"$@"} +} + +func_mode_help () +{ + # We need to display help for each of the modes. + case $opt_mode in + "") + # Generic help is extracted from the usage comments + # at the start of this file. + func_help + ;; + + clean) + $ECHO \ +"Usage: $progname [OPTION]... --mode=clean RM [RM-OPTION]... FILE... + +Remove files from the build directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed +to RM. + +If FILE is a libtool library, object or program, all the files associated +with it are deleted. Otherwise, only FILE itself is deleted using RM." + ;; + + compile) + $ECHO \ +"Usage: $progname [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE + +Compile a source file into a libtool library object. + +This mode accepts the following additional options: + + -o OUTPUT-FILE set the output file name to OUTPUT-FILE + -no-suppress do not suppress compiler output for multiple passes + -prefer-pic try to build PIC objects only + -prefer-non-pic try to build non-PIC objects only + -shared do not build a '.o' file suitable for static linking + -static only build a '.o' file suitable for static linking + -Wc,FLAG pass FLAG directly to the compiler + +COMPILE-COMMAND is a command to be used in creating a 'standard' object file +from the given SOURCEFILE. + +The output file name is determined by removing the directory component from +SOURCEFILE, then substituting the C source code suffix '.c' with the +library object suffix, '.lo'." + ;; + + execute) + $ECHO \ +"Usage: $progname [OPTION]... --mode=execute COMMAND [ARGS]... + +Automatically set library path, then run a program. + +This mode accepts the following additional options: + + -dlopen FILE add the directory containing FILE to the library path + +This mode sets the library path environment variable according to '-dlopen' +flags. + +If any of the ARGS are libtool executable wrappers, then they are translated +into their corresponding uninstalled binary, and any of their required library +directories are added to the library path. + +Then, COMMAND is executed, with ARGS as arguments." + ;; + + finish) + $ECHO \ +"Usage: $progname [OPTION]... --mode=finish [LIBDIR]... + +Complete the installation of libtool libraries. + +Each LIBDIR is a directory that contains libtool libraries. + +The commands that this mode executes may require superuser privileges. Use +the '--dry-run' option if you just want to see what would be executed." + ;; + + install) + $ECHO \ +"Usage: $progname [OPTION]... --mode=install INSTALL-COMMAND... + +Install executables or libraries. + +INSTALL-COMMAND is the installation command. The first component should be +either the 'install' or 'cp' program. + +The following components of INSTALL-COMMAND are treated specially: + + -inst-prefix-dir PREFIX-DIR Use PREFIX-DIR as a staging area for installation + +The rest of the components are interpreted as arguments to that command (only +BSD-compatible install options are recognized)." + ;; + + link) + $ECHO \ +"Usage: $progname [OPTION]... --mode=link LINK-COMMAND... + +Link object files or libraries together to form another library, or to +create an executable program. + +LINK-COMMAND is a command using the C compiler that you would use to create +a program from several object files. + +The following components of LINK-COMMAND are treated specially: + + -all-static do not do any dynamic linking at all + -avoid-version do not add a version suffix if possible + -bindir BINDIR specify path to binaries directory (for systems where + libraries must be found in the PATH setting at runtime) + -dlopen FILE '-dlpreopen' FILE if it cannot be dlopened at runtime + -dlpreopen FILE link in FILE and add its symbols to lt_preloaded_symbols + -export-dynamic allow symbols from OUTPUT-FILE to be resolved with dlsym(3) + -export-symbols SYMFILE + try to export only the symbols listed in SYMFILE + -export-symbols-regex REGEX + try to export only the symbols matching REGEX + -LLIBDIR search LIBDIR for required installed libraries + -lNAME OUTPUT-FILE requires the installed library libNAME + -module build a library that can dlopened + -no-fast-install disable the fast-install mode + -no-install link a not-installable executable + -no-undefined declare that a library does not refer to external symbols + -o OUTPUT-FILE create OUTPUT-FILE from the specified objects + -objectlist FILE use a list of object files found in FILE to specify objects + -os2dllname NAME force a short DLL name on OS/2 (no effect on other OSes) + -precious-files-regex REGEX + don't remove output files matching REGEX + -release RELEASE specify package release information + -rpath LIBDIR the created library will eventually be installed in LIBDIR + -R[ ]LIBDIR add LIBDIR to the runtime path of programs and libraries + -shared only do dynamic linking of libtool libraries + -shrext SUFFIX override the standard shared library file extension + -static do not do any dynamic linking of uninstalled libtool libraries + -static-libtool-libs + do not do any dynamic linking of libtool libraries + -version-info CURRENT[:REVISION[:AGE]] + specify library version info [each variable defaults to 0] + -weak LIBNAME declare that the target provides the LIBNAME interface + -Wc,FLAG + -Xcompiler FLAG pass linker-specific FLAG directly to the compiler + -Wl,FLAG + -Xlinker FLAG pass linker-specific FLAG directly to the linker + -XCClinker FLAG pass link-specific FLAG to the compiler driver (CC) + +All other options (arguments beginning with '-') are ignored. + +Every other argument is treated as a filename. Files ending in '.la' are +treated as uninstalled libtool libraries, other files are standard or library +object files. + +If the OUTPUT-FILE ends in '.la', then a libtool library is created, +only library objects ('.lo' files) may be specified, and '-rpath' is +required, except when creating a convenience library. + +If OUTPUT-FILE ends in '.a' or '.lib', then a standard library is created +using 'ar' and 'ranlib', or on Windows using 'lib'. + +If OUTPUT-FILE ends in '.lo' or '.$objext', then a reloadable object file +is created, otherwise an executable program is created." + ;; + + uninstall) + $ECHO \ +"Usage: $progname [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE... + +Remove libraries from an installation directory. + +RM is the name of the program to use to delete files associated with each FILE +(typically '/bin/rm'). RM-OPTIONS are options (such as '-f') to be passed +to RM. + +If FILE is a libtool library, all the files associated with it are deleted. +Otherwise, only FILE itself is deleted using RM." + ;; + + *) + func_fatal_help "invalid operation mode '$opt_mode'" + ;; + esac + + echo + $ECHO "Try '$progname --help' for more information about other modes." +} + +# Now that we've collected a possible --mode arg, show help if necessary +if $opt_help; then + if test : = "$opt_help"; then + func_mode_help + else + { + func_help noexit + for opt_mode in compile link execute install finish uninstall clean; do + func_mode_help + done + } | $SED -n '1p; 2,$s/^Usage:/ or: /p' + { + func_help noexit + for opt_mode in compile link execute install finish uninstall clean; do + echo + func_mode_help + done + } | + $SED '1d + /^When reporting/,/^Report/{ + H + d + } + $x + /information about other modes/d + /more detailed .*MODE/d + s/^Usage:.*--mode=\([^ ]*\) .*/Description of \1 mode:/' + fi + exit $? +fi + + +# func_mode_execute arg... +func_mode_execute () +{ + $debug_cmd + + # The first argument is the command name. + cmd=$nonopt + test -z "$cmd" && \ + func_fatal_help "you must specify a COMMAND" + + # Handle -dlopen flags immediately. + for file in $opt_dlopen; do + test -f "$file" \ + || func_fatal_help "'$file' is not a file" + + dir= + case $file in + *.la) + func_resolve_sysroot "$file" + file=$func_resolve_sysroot_result + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$file" \ + || func_fatal_help "'$lib' is not a valid libtool archive" + + # Read the libtool library. + dlname= + library_names= + func_source "$file" + + # Skip this library if it cannot be dlopened. + if test -z "$dlname"; then + # Warn if it was a shared library. + test -n "$library_names" && \ + func_warning "'$file' was not linked with '-export-dynamic'" + continue + fi + + func_dirname "$file" "" "." + dir=$func_dirname_result + + if test -f "$dir/$objdir/$dlname"; then + func_append dir "/$objdir" + else + if test ! -f "$dir/$dlname"; then + func_fatal_error "cannot find '$dlname' in '$dir' or '$dir/$objdir'" + fi + fi + ;; + + *.lo) + # Just add the directory containing the .lo file. + func_dirname "$file" "" "." + dir=$func_dirname_result + ;; + + *) + func_warning "'-dlopen' is ignored for non-libtool libraries and objects" + continue + ;; + esac + + # Get the absolute pathname. + absdir=`cd "$dir" && pwd` + test -n "$absdir" && dir=$absdir + + # Now add the directory to shlibpath_var. + if eval "test -z \"\$$shlibpath_var\""; then + eval "$shlibpath_var=\"\$dir\"" + else + eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\"" + fi + done + + # This variable tells wrapper scripts just to set shlibpath_var + # rather than running their programs. + libtool_execute_magic=$magic + + # Check if any of the arguments is a wrapper script. + args= + for file + do + case $file in + -* | *.la | *.lo ) ;; + *) + # Do a test to see if this is really a libtool program. + if func_ltwrapper_script_p "$file"; then + func_source "$file" + # Transform arg to wrapped name. + file=$progdir/$program + elif func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + func_source "$func_ltwrapper_scriptname_result" + # Transform arg to wrapped name. + file=$progdir/$program + fi + ;; + esac + # Quote arguments (to preserve shell metacharacters). + func_append_quoted args "$file" + done + + if $opt_dry_run; then + # Display what would be done. + if test -n "$shlibpath_var"; then + eval "\$ECHO \"\$shlibpath_var=\$$shlibpath_var\"" + echo "export $shlibpath_var" + fi + $ECHO "$cmd$args" + exit $EXIT_SUCCESS + else + if test -n "$shlibpath_var"; then + # Export the shlibpath_var. + eval "export $shlibpath_var" + fi + + # Restore saved environment variables + for lt_var in LANG LANGUAGE LC_ALL LC_CTYPE LC_COLLATE LC_MESSAGES + do + eval "if test \"\${save_$lt_var+set}\" = set; then + $lt_var=\$save_$lt_var; export $lt_var + else + $lt_unset $lt_var + fi" + done + + # Now prepare to actually exec the command. + exec_cmd=\$cmd$args + fi +} + +test execute = "$opt_mode" && func_mode_execute ${1+"$@"} + + +# func_mode_finish arg... +func_mode_finish () +{ + $debug_cmd + + libs= + libdirs= + admincmds= + + for opt in "$nonopt" ${1+"$@"} + do + if test -d "$opt"; then + func_append libdirs " $opt" + + elif test -f "$opt"; then + if func_lalib_unsafe_p "$opt"; then + func_append libs " $opt" + else + func_warning "'$opt' is not a valid libtool archive" + fi + + else + func_fatal_error "invalid argument '$opt'" + fi + done + + if test -n "$libs"; then + if test -n "$lt_sysroot"; then + sysroot_regex=`$ECHO "$lt_sysroot" | $SED "$sed_make_literal_regex"` + sysroot_cmd="s/\([ ']\)$sysroot_regex/\1/g;" + else + sysroot_cmd= + fi + + # Remove sysroot references + if $opt_dry_run; then + for lib in $libs; do + echo "removing references to $lt_sysroot and '=' prefixes from $lib" + done + else + tmpdir=`func_mktempdir` + for lib in $libs; do + $SED -e "$sysroot_cmd s/\([ ']-[LR]\)=/\1/g; s/\([ ']\)=/\1/g" $lib \ + > $tmpdir/tmp-la + mv -f $tmpdir/tmp-la $lib + done + ${RM}r "$tmpdir" + fi + fi + + if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then + for libdir in $libdirs; do + if test -n "$finish_cmds"; then + # Do each command in the finish commands. + func_execute_cmds "$finish_cmds" 'admincmds="$admincmds +'"$cmd"'"' + fi + if test -n "$finish_eval"; then + # Do the single finish_eval. + eval cmds=\"$finish_eval\" + $opt_dry_run || eval "$cmds" || func_append admincmds " + $cmds" + fi + done + fi + + # Exit here if they wanted silent mode. + $opt_quiet && exit $EXIT_SUCCESS + + if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then + echo "----------------------------------------------------------------------" + echo "Libraries have been installed in:" + for libdir in $libdirs; do + $ECHO " $libdir" + done + echo + echo "If you ever happen to want to link against installed libraries" + echo "in a given directory, LIBDIR, you must either use libtool, and" + echo "specify the full pathname of the library, or use the '-LLIBDIR'" + echo "flag during linking and do at least one of the following:" + if test -n "$shlibpath_var"; then + echo " - add LIBDIR to the '$shlibpath_var' environment variable" + echo " during execution" + fi + if test -n "$runpath_var"; then + echo " - add LIBDIR to the '$runpath_var' environment variable" + echo " during linking" + fi + if test -n "$hardcode_libdir_flag_spec"; then + libdir=LIBDIR + eval flag=\"$hardcode_libdir_flag_spec\" + + $ECHO " - use the '$flag' linker flag" + fi + if test -n "$admincmds"; then + $ECHO " - have your system administrator run these commands:$admincmds" + fi + if test -f /etc/ld.so.conf; then + echo " - have your system administrator add LIBDIR to '/etc/ld.so.conf'" + fi + echo + + echo "See any operating system documentation about shared libraries for" + case $host in + solaris2.[6789]|solaris2.1[0-9]) + echo "more information, such as the ld(1), crle(1) and ld.so(8) manual" + echo "pages." + ;; + *) + echo "more information, such as the ld(1) and ld.so(8) manual pages." + ;; + esac + echo "----------------------------------------------------------------------" + fi + exit $EXIT_SUCCESS +} + +test finish = "$opt_mode" && func_mode_finish ${1+"$@"} + + +# func_mode_install arg... +func_mode_install () +{ + $debug_cmd + + # There may be an optional sh(1) argument at the beginning of + # install_prog (especially on Windows NT). + if test "$SHELL" = "$nonopt" || test /bin/sh = "$nonopt" || + # Allow the use of GNU shtool's install command. + case $nonopt in *shtool*) :;; *) false;; esac + then + # Aesthetically quote it. + func_quote_for_eval "$nonopt" + install_prog="$func_quote_for_eval_result " + arg=$1 + shift + else + install_prog= + arg=$nonopt + fi + + # The real first argument should be the name of the installation program. + # Aesthetically quote it. + func_quote_for_eval "$arg" + func_append install_prog "$func_quote_for_eval_result" + install_shared_prog=$install_prog + case " $install_prog " in + *[\\\ /]cp\ *) install_cp=: ;; + *) install_cp=false ;; + esac + + # We need to accept at least all the BSD install flags. + dest= + files= + opts= + prev= + install_type= + isdir=false + stripme= + no_mode=: + for arg + do + arg2= + if test -n "$dest"; then + func_append files " $dest" + dest=$arg + continue + fi + + case $arg in + -d) isdir=: ;; + -f) + if $install_cp; then :; else + prev=$arg + fi + ;; + -g | -m | -o) + prev=$arg + ;; + -s) + stripme=" -s" + continue + ;; + -*) + ;; + *) + # If the previous option needed an argument, then skip it. + if test -n "$prev"; then + if test X-m = "X$prev" && test -n "$install_override_mode"; then + arg2=$install_override_mode + no_mode=false + fi + prev= + else + dest=$arg + continue + fi + ;; + esac + + # Aesthetically quote the argument. + func_quote_for_eval "$arg" + func_append install_prog " $func_quote_for_eval_result" + if test -n "$arg2"; then + func_quote_for_eval "$arg2" + fi + func_append install_shared_prog " $func_quote_for_eval_result" + done + + test -z "$install_prog" && \ + func_fatal_help "you must specify an install program" + + test -n "$prev" && \ + func_fatal_help "the '$prev' option requires an argument" + + if test -n "$install_override_mode" && $no_mode; then + if $install_cp; then :; else + func_quote_for_eval "$install_override_mode" + func_append install_shared_prog " -m $func_quote_for_eval_result" + fi + fi + + if test -z "$files"; then + if test -z "$dest"; then + func_fatal_help "no file or destination specified" + else + func_fatal_help "you must specify a destination" + fi + fi + + # Strip any trailing slash from the destination. + func_stripname '' '/' "$dest" + dest=$func_stripname_result + + # Check to see that the destination is a directory. + test -d "$dest" && isdir=: + if $isdir; then + destdir=$dest + destname= + else + func_dirname_and_basename "$dest" "" "." + destdir=$func_dirname_result + destname=$func_basename_result + + # Not a directory, so check to see that there is only one file specified. + set dummy $files; shift + test "$#" -gt 1 && \ + func_fatal_help "'$dest' is not a directory" + fi + case $destdir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + for file in $files; do + case $file in + *.lo) ;; + *) + func_fatal_help "'$destdir' must be an absolute directory name" + ;; + esac + done + ;; + esac + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic=$magic + + staticlibs= + future_libdirs= + current_libdirs= + for file in $files; do + + # Do each installation. + case $file in + *.$libext) + # Do the static libraries later. + func_append staticlibs " $file" + ;; + + *.la) + func_resolve_sysroot "$file" + file=$func_resolve_sysroot_result + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$file" \ + || func_fatal_help "'$file' is not a valid libtool archive" + + library_names= + old_library= + relink_command= + func_source "$file" + + # Add the libdir to current_libdirs if it is the destination. + if test "X$destdir" = "X$libdir"; then + case "$current_libdirs " in + *" $libdir "*) ;; + *) func_append current_libdirs " $libdir" ;; + esac + else + # Note the libdir as a future libdir. + case "$future_libdirs " in + *" $libdir "*) ;; + *) func_append future_libdirs " $libdir" ;; + esac + fi + + func_dirname "$file" "/" "" + dir=$func_dirname_result + func_append dir "$objdir" + + if test -n "$relink_command"; then + # Determine the prefix the user has applied to our future dir. + inst_prefix_dir=`$ECHO "$destdir" | $SED -e "s%$libdir\$%%"` + + # Don't allow the user to place us outside of our expected + # location b/c this prevents finding dependent libraries that + # are installed to the same prefix. + # At present, this check doesn't affect windows .dll's that + # are installed into $libdir/../bin (currently, that works fine) + # but it's something to keep an eye on. + test "$inst_prefix_dir" = "$destdir" && \ + func_fatal_error "error: cannot install '$file' to a directory not ending in $libdir" + + if test -n "$inst_prefix_dir"; then + # Stick the inst_prefix_dir data into the link command. + relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%-inst-prefix-dir $inst_prefix_dir%"` + else + relink_command=`$ECHO "$relink_command" | $SED "s%@inst_prefix_dir@%%"` + fi + + func_warning "relinking '$file'" + func_show_eval "$relink_command" \ + 'func_fatal_error "error: relink '\''$file'\'' with the above command before installing it"' + fi + + # See the names of the shared library. + set dummy $library_names; shift + if test -n "$1"; then + realname=$1 + shift + + srcname=$realname + test -n "$relink_command" && srcname=${realname}T + + # Install the shared library and build the symlinks. + func_show_eval "$install_shared_prog $dir/$srcname $destdir/$realname" \ + 'exit $?' + tstripme=$stripme + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + case $realname in + *.dll.a) + tstripme= + ;; + esac + ;; + os2*) + case $realname in + *_dll.a) + tstripme= + ;; + esac + ;; + esac + if test -n "$tstripme" && test -n "$striplib"; then + func_show_eval "$striplib $destdir/$realname" 'exit $?' + fi + + if test "$#" -gt 0; then + # Delete the old symlinks, and create new ones. + # Try 'ln -sf' first, because the 'ln' binary might depend on + # the symlink we replace! Solaris /bin/ln does not understand -f, + # so we also need to try rm && ln -s. + for linkname + do + test "$linkname" != "$realname" \ + && func_show_eval "(cd $destdir && { $LN_S -f $realname $linkname || { $RM $linkname && $LN_S $realname $linkname; }; })" + done + fi + + # Do each command in the postinstall commands. + lib=$destdir/$realname + func_execute_cmds "$postinstall_cmds" 'exit $?' + fi + + # Install the pseudo-library for information purposes. + func_basename "$file" + name=$func_basename_result + instname=$dir/${name}i + func_show_eval "$install_prog $instname $destdir/$name" 'exit $?' + + # Maybe install the static library, too. + test -n "$old_library" && func_append staticlibs " $dir/$old_library" + ;; + + *.lo) + # Install (i.e. copy) a libtool object. + + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile=$destdir/$destname + else + func_basename "$file" + destfile=$func_basename_result + destfile=$destdir/$destfile + fi + + # Deduce the name of the destination old-style object file. + case $destfile in + *.lo) + func_lo2o "$destfile" + staticdest=$func_lo2o_result + ;; + *.$objext) + staticdest=$destfile + destfile= + ;; + *) + func_fatal_help "cannot copy a libtool object to '$destfile'" + ;; + esac + + # Install the libtool object if requested. + test -n "$destfile" && \ + func_show_eval "$install_prog $file $destfile" 'exit $?' + + # Install the old object if enabled. + if test yes = "$build_old_libs"; then + # Deduce the name of the old-style object file. + func_lo2o "$file" + staticobj=$func_lo2o_result + func_show_eval "$install_prog \$staticobj \$staticdest" 'exit $?' + fi + exit $EXIT_SUCCESS + ;; + + *) + # Figure out destination file name, if it wasn't already specified. + if test -n "$destname"; then + destfile=$destdir/$destname + else + func_basename "$file" + destfile=$func_basename_result + destfile=$destdir/$destfile + fi + + # If the file is missing, and there is a .exe on the end, strip it + # because it is most likely a libtool script we actually want to + # install + stripped_ext= + case $file in + *.exe) + if test ! -f "$file"; then + func_stripname '' '.exe' "$file" + file=$func_stripname_result + stripped_ext=.exe + fi + ;; + esac + + # Do a test to see if this is really a libtool program. + case $host in + *cygwin* | *mingw*) + if func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + wrapper=$func_ltwrapper_scriptname_result + else + func_stripname '' '.exe' "$file" + wrapper=$func_stripname_result + fi + ;; + *) + wrapper=$file + ;; + esac + if func_ltwrapper_script_p "$wrapper"; then + notinst_deplibs= + relink_command= + + func_source "$wrapper" + + # Check the variables that should have been set. + test -z "$generated_by_libtool_version" && \ + func_fatal_error "invalid libtool wrapper script '$wrapper'" + + finalize=: + for lib in $notinst_deplibs; do + # Check to see that each library is installed. + libdir= + if test -f "$lib"; then + func_source "$lib" + fi + libfile=$libdir/`$ECHO "$lib" | $SED 's%^.*/%%g'` + if test -n "$libdir" && test ! -f "$libfile"; then + func_warning "'$lib' has not been installed in '$libdir'" + finalize=false + fi + done + + relink_command= + func_source "$wrapper" + + outputname= + if test no = "$fast_install" && test -n "$relink_command"; then + $opt_dry_run || { + if $finalize; then + tmpdir=`func_mktempdir` + func_basename "$file$stripped_ext" + file=$func_basename_result + outputname=$tmpdir/$file + # Replace the output file specification. + relink_command=`$ECHO "$relink_command" | $SED 's%@OUTPUT@%'"$outputname"'%g'` + + $opt_quiet || { + func_quote_for_expand "$relink_command" + eval "func_echo $func_quote_for_expand_result" + } + if eval "$relink_command"; then : + else + func_error "error: relink '$file' with the above command before installing it" + $opt_dry_run || ${RM}r "$tmpdir" + continue + fi + file=$outputname + else + func_warning "cannot relink '$file'" + fi + } + else + # Install the binary that we compiled earlier. + file=`$ECHO "$file$stripped_ext" | $SED "s%\([^/]*\)$%$objdir/\1%"` + fi + fi + + # remove .exe since cygwin /usr/bin/install will append another + # one anyway + case $install_prog,$host in + */usr/bin/install*,*cygwin*) + case $file:$destfile in + *.exe:*.exe) + # this is ok + ;; + *.exe:*) + destfile=$destfile.exe + ;; + *:*.exe) + func_stripname '' '.exe' "$destfile" + destfile=$func_stripname_result + ;; + esac + ;; + esac + func_show_eval "$install_prog\$stripme \$file \$destfile" 'exit $?' + $opt_dry_run || if test -n "$outputname"; then + ${RM}r "$tmpdir" + fi + ;; + esac + done + + for file in $staticlibs; do + func_basename "$file" + name=$func_basename_result + + # Set up the ranlib parameters. + oldlib=$destdir/$name + func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 + tool_oldlib=$func_to_tool_file_result + + func_show_eval "$install_prog \$file \$oldlib" 'exit $?' + + if test -n "$stripme" && test -n "$old_striplib"; then + func_show_eval "$old_striplib $tool_oldlib" 'exit $?' + fi + + # Do each command in the postinstall commands. + func_execute_cmds "$old_postinstall_cmds" 'exit $?' + done + + test -n "$future_libdirs" && \ + func_warning "remember to run '$progname --finish$future_libdirs'" + + if test -n "$current_libdirs"; then + # Maybe just do a dry run. + $opt_dry_run && current_libdirs=" -n$current_libdirs" + exec_cmd='$SHELL "$progpath" $preserve_args --finish$current_libdirs' + else + exit $EXIT_SUCCESS + fi +} + +test install = "$opt_mode" && func_mode_install ${1+"$@"} + + +# func_generate_dlsyms outputname originator pic_p +# Extract symbols from dlprefiles and create ${outputname}S.o with +# a dlpreopen symbol table. +func_generate_dlsyms () +{ + $debug_cmd + + my_outputname=$1 + my_originator=$2 + my_pic_p=${3-false} + my_prefix=`$ECHO "$my_originator" | $SED 's%[^a-zA-Z0-9]%_%g'` + my_dlsyms= + + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + if test -n "$NM" && test -n "$global_symbol_pipe"; then + my_dlsyms=${my_outputname}S.c + else + func_error "not configured to extract global symbols from dlpreopened files" + fi + fi + + if test -n "$my_dlsyms"; then + case $my_dlsyms in + "") ;; + *.c) + # Discover the nlist of each of the dlfiles. + nlist=$output_objdir/$my_outputname.nm + + func_show_eval "$RM $nlist ${nlist}S ${nlist}T" + + # Parse the name list into a source file. + func_verbose "creating $output_objdir/$my_dlsyms" + + $opt_dry_run || $ECHO > "$output_objdir/$my_dlsyms" "\ +/* $my_dlsyms - symbol resolution table for '$my_outputname' dlsym emulation. */ +/* Generated by $PROGRAM (GNU $PACKAGE) $VERSION */ + +#ifdef __cplusplus +extern \"C\" { +#endif + +#if defined __GNUC__ && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 4)) || (__GNUC__ > 4)) +#pragma GCC diagnostic ignored \"-Wstrict-prototypes\" +#endif + +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT_DLSYM_CONST +#else +# define LT_DLSYM_CONST const +#endif + +#define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) + +/* External symbol declarations for the compiler. */\ +" + + if test yes = "$dlself"; then + func_verbose "generating symbol list for '$output'" + + $opt_dry_run || echo ': @PROGRAM@ ' > "$nlist" + + # Add our own program objects to the symbol list. + progfiles=`$ECHO "$objs$old_deplibs" | $SP2NL | $SED "$lo2o" | $NL2SP` + for progfile in $progfiles; do + func_to_tool_file "$progfile" func_convert_file_msys_to_w32 + func_verbose "extracting global C symbols from '$func_to_tool_file_result'" + $opt_dry_run || eval "$NM $func_to_tool_file_result | $global_symbol_pipe >> '$nlist'" + done + + if test -n "$exclude_expsyms"; then + $opt_dry_run || { + eval '$EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + } + fi + + if test -n "$export_symbols_regex"; then + $opt_dry_run || { + eval '$EGREP -e "$export_symbols_regex" "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + } + fi + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + export_symbols=$output_objdir/$outputname.exp + $opt_dry_run || { + $RM $export_symbols + eval "$SED -n -e '/^: @PROGRAM@ $/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"' + case $host in + *cygwin* | *mingw* | *cegcc* ) + eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' + eval 'cat "$export_symbols" >> "$output_objdir/$outputname.def"' + ;; + esac + } + else + $opt_dry_run || { + eval "$SED -e 's/\([].[*^$]\)/\\\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$outputname.exp"' + eval '$GREP -f "$output_objdir/$outputname.exp" < "$nlist" > "$nlist"T' + eval '$MV "$nlist"T "$nlist"' + case $host in + *cygwin* | *mingw* | *cegcc* ) + eval "echo EXPORTS "'> "$output_objdir/$outputname.def"' + eval 'cat "$nlist" >> "$output_objdir/$outputname.def"' + ;; + esac + } + fi + fi + + for dlprefile in $dlprefiles; do + func_verbose "extracting global C symbols from '$dlprefile'" + func_basename "$dlprefile" + name=$func_basename_result + case $host in + *cygwin* | *mingw* | *cegcc* ) + # if an import library, we need to obtain dlname + if func_win32_import_lib_p "$dlprefile"; then + func_tr_sh "$dlprefile" + eval "curr_lafile=\$libfile_$func_tr_sh_result" + dlprefile_dlbasename= + if test -n "$curr_lafile" && func_lalib_p "$curr_lafile"; then + # Use subshell, to avoid clobbering current variable values + dlprefile_dlname=`source "$curr_lafile" && echo "$dlname"` + if test -n "$dlprefile_dlname"; then + func_basename "$dlprefile_dlname" + dlprefile_dlbasename=$func_basename_result + else + # no lafile. user explicitly requested -dlpreopen . + $sharedlib_from_linklib_cmd "$dlprefile" + dlprefile_dlbasename=$sharedlib_from_linklib_result + fi + fi + $opt_dry_run || { + if test -n "$dlprefile_dlbasename"; then + eval '$ECHO ": $dlprefile_dlbasename" >> "$nlist"' + else + func_warning "Could not compute DLL name from $name" + eval '$ECHO ": $name " >> "$nlist"' + fi + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe | + $SED -e '/I __imp/d' -e 's/I __nm_/D /;s/_nm__//' >> '$nlist'" + } + else # not an import lib + $opt_dry_run || { + eval '$ECHO ": $name " >> "$nlist"' + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" + } + fi + ;; + *) + $opt_dry_run || { + eval '$ECHO ": $name " >> "$nlist"' + func_to_tool_file "$dlprefile" func_convert_file_msys_to_w32 + eval "$NM \"$func_to_tool_file_result\" 2>/dev/null | $global_symbol_pipe >> '$nlist'" + } + ;; + esac + done + + $opt_dry_run || { + # Make sure we have at least an empty file. + test -f "$nlist" || : > "$nlist" + + if test -n "$exclude_expsyms"; then + $EGREP -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T + $MV "$nlist"T "$nlist" + fi + + # Try sorting and uniquifying the output. + if $GREP -v "^: " < "$nlist" | + if sort -k 3 /dev/null 2>&1; then + sort -k 3 + else + sort +2 + fi | + uniq > "$nlist"S; then + : + else + $GREP -v "^: " < "$nlist" > "$nlist"S + fi + + if test -f "$nlist"S; then + eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$my_dlsyms"' + else + echo '/* NONE */' >> "$output_objdir/$my_dlsyms" + fi + + func_show_eval '$RM "${nlist}I"' + if test -n "$global_symbol_to_import"; then + eval "$global_symbol_to_import"' < "$nlist"S > "$nlist"I' + fi + + echo >> "$output_objdir/$my_dlsyms" "\ + +/* The mapping between symbol names and symbols. */ +typedef struct { + const char *name; + void *address; +} lt_dlsymlist; +extern LT_DLSYM_CONST lt_dlsymlist +lt_${my_prefix}_LTX_preloaded_symbols[];\ +" + + if test -s "$nlist"I; then + echo >> "$output_objdir/$my_dlsyms" "\ +static void lt_syminit(void) +{ + LT_DLSYM_CONST lt_dlsymlist *symbol = lt_${my_prefix}_LTX_preloaded_symbols; + for (; symbol->name; ++symbol) + {" + $SED 's/.*/ if (STREQ (symbol->name, \"&\")) symbol->address = (void *) \&&;/' < "$nlist"I >> "$output_objdir/$my_dlsyms" + echo >> "$output_objdir/$my_dlsyms" "\ + } +}" + fi + echo >> "$output_objdir/$my_dlsyms" "\ +LT_DLSYM_CONST lt_dlsymlist +lt_${my_prefix}_LTX_preloaded_symbols[] = +{ {\"$my_originator\", (void *) 0}," + + if test -s "$nlist"I; then + echo >> "$output_objdir/$my_dlsyms" "\ + {\"@INIT@\", (void *) <_syminit}," + fi + + case $need_lib_prefix in + no) + eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$my_dlsyms" + ;; + *) + eval "$global_symbol_to_c_name_address_lib_prefix" < "$nlist" >> "$output_objdir/$my_dlsyms" + ;; + esac + echo >> "$output_objdir/$my_dlsyms" "\ + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt_${my_prefix}_LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif\ +" + } # !$opt_dry_run + + pic_flag_for_symtable= + case "$compile_command " in + *" -static "*) ;; + *) + case $host in + # compiling the symbol table file with pic_flag works around + # a FreeBSD bug that causes programs to crash when -lm is + # linked before any other PIC object. But we must not use + # pic_flag when linking with -static. The problem exists in + # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1. + *-*-freebsd2.*|*-*-freebsd3.0*|*-*-freebsdelf3.0*) + pic_flag_for_symtable=" $pic_flag -DFREEBSD_WORKAROUND" ;; + *-*-hpux*) + pic_flag_for_symtable=" $pic_flag" ;; + *) + $my_pic_p && pic_flag_for_symtable=" $pic_flag" + ;; + esac + ;; + esac + symtab_cflags= + for arg in $LTCFLAGS; do + case $arg in + -pie | -fpie | -fPIE) ;; + *) func_append symtab_cflags " $arg" ;; + esac + done + + # Now compile the dynamic symbol file. + func_show_eval '(cd $output_objdir && $LTCC$symtab_cflags -c$no_builtin_flag$pic_flag_for_symtable "$my_dlsyms")' 'exit $?' + + # Clean up the generated files. + func_show_eval '$RM "$output_objdir/$my_dlsyms" "$nlist" "${nlist}S" "${nlist}T" "${nlist}I"' + + # Transform the symbol file into the correct name. + symfileobj=$output_objdir/${my_outputname}S.$objext + case $host in + *cygwin* | *mingw* | *cegcc* ) + if test -f "$output_objdir/$my_outputname.def"; then + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$output_objdir/$my_outputname.def $symfileobj%"` + else + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` + fi + ;; + *) + compile_command=`$ECHO "$compile_command" | $SED "s%@SYMFILE@%$symfileobj%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s%@SYMFILE@%$symfileobj%"` + ;; + esac + ;; + *) + func_fatal_error "unknown suffix for '$my_dlsyms'" + ;; + esac + else + # We keep going just in case the user didn't refer to + # lt_preloaded_symbols. The linker will fail if global_symbol_pipe + # really was required. + + # Nullify the symbol file. + compile_command=`$ECHO "$compile_command" | $SED "s% @SYMFILE@%%"` + finalize_command=`$ECHO "$finalize_command" | $SED "s% @SYMFILE@%%"` + fi +} + +# func_cygming_gnu_implib_p ARG +# This predicate returns with zero status (TRUE) if +# ARG is a GNU/binutils-style import library. Returns +# with nonzero status (FALSE) otherwise. +func_cygming_gnu_implib_p () +{ + $debug_cmd + + func_to_tool_file "$1" func_convert_file_msys_to_w32 + func_cygming_gnu_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $EGREP ' (_head_[A-Za-z0-9_]+_[ad]l*|[A-Za-z0-9_]+_[ad]l*_iname)$'` + test -n "$func_cygming_gnu_implib_tmp" +} + +# func_cygming_ms_implib_p ARG +# This predicate returns with zero status (TRUE) if +# ARG is an MS-style import library. Returns +# with nonzero status (FALSE) otherwise. +func_cygming_ms_implib_p () +{ + $debug_cmd + + func_to_tool_file "$1" func_convert_file_msys_to_w32 + func_cygming_ms_implib_tmp=`$NM "$func_to_tool_file_result" | eval "$global_symbol_pipe" | $GREP '_NULL_IMPORT_DESCRIPTOR'` + test -n "$func_cygming_ms_implib_tmp" +} + +# func_win32_libid arg +# return the library type of file 'arg' +# +# Need a lot of goo to handle *both* DLLs and import libs +# Has to be a shell function in order to 'eat' the argument +# that is supplied when $file_magic_command is called. +# Despite the name, also deal with 64 bit binaries. +func_win32_libid () +{ + $debug_cmd + + win32_libid_type=unknown + win32_fileres=`file -L $1 2>/dev/null` + case $win32_fileres in + *ar\ archive\ import\ library*) # definitely import + win32_libid_type="x86 archive import" + ;; + *ar\ archive*) # could be an import, or static + # Keep the egrep pattern in sync with the one in _LT_CHECK_MAGIC_METHOD. + if eval $OBJDUMP -f $1 | $SED -e '10q' 2>/dev/null | + $EGREP 'file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' >/dev/null; then + case $nm_interface in + "MS dumpbin") + if func_cygming_ms_implib_p "$1" || + func_cygming_gnu_implib_p "$1" + then + win32_nmres=import + else + win32_nmres= + fi + ;; + *) + func_to_tool_file "$1" func_convert_file_msys_to_w32 + win32_nmres=`eval $NM -f posix -A \"$func_to_tool_file_result\" | + $SED -n -e ' + 1,100{ + / I /{ + s|.*|import| + p + q + } + }'` + ;; + esac + case $win32_nmres in + import*) win32_libid_type="x86 archive import";; + *) win32_libid_type="x86 archive static";; + esac + fi + ;; + *DLL*) + win32_libid_type="x86 DLL" + ;; + *executable*) # but shell scripts are "executable" too... + case $win32_fileres in + *MS\ Windows\ PE\ Intel*) + win32_libid_type="x86 DLL" + ;; + esac + ;; + esac + $ECHO "$win32_libid_type" +} + +# func_cygming_dll_for_implib ARG +# +# Platform-specific function to extract the +# name of the DLL associated with the specified +# import library ARG. +# Invoked by eval'ing the libtool variable +# $sharedlib_from_linklib_cmd +# Result is available in the variable +# $sharedlib_from_linklib_result +func_cygming_dll_for_implib () +{ + $debug_cmd + + sharedlib_from_linklib_result=`$DLLTOOL --identify-strict --identify "$1"` +} + +# func_cygming_dll_for_implib_fallback_core SECTION_NAME LIBNAMEs +# +# The is the core of a fallback implementation of a +# platform-specific function to extract the name of the +# DLL associated with the specified import library LIBNAME. +# +# SECTION_NAME is either .idata$6 or .idata$7, depending +# on the platform and compiler that created the implib. +# +# Echos the name of the DLL associated with the +# specified import library. +func_cygming_dll_for_implib_fallback_core () +{ + $debug_cmd + + match_literal=`$ECHO "$1" | $SED "$sed_make_literal_regex"` + $OBJDUMP -s --section "$1" "$2" 2>/dev/null | + $SED '/^Contents of section '"$match_literal"':/{ + # Place marker at beginning of archive member dllname section + s/.*/====MARK====/ + p + d + } + # These lines can sometimes be longer than 43 characters, but + # are always uninteresting + /:[ ]*file format pe[i]\{,1\}-/d + /^In archive [^:]*:/d + # Ensure marker is printed + /^====MARK====/p + # Remove all lines with less than 43 characters + /^.\{43\}/!d + # From remaining lines, remove first 43 characters + s/^.\{43\}//' | + $SED -n ' + # Join marker and all lines until next marker into a single line + /^====MARK====/ b para + H + $ b para + b + :para + x + s/\n//g + # Remove the marker + s/^====MARK====// + # Remove trailing dots and whitespace + s/[\. \t]*$// + # Print + /./p' | + # we now have a list, one entry per line, of the stringified + # contents of the appropriate section of all members of the + # archive that possess that section. Heuristic: eliminate + # all those that have a first or second character that is + # a '.' (that is, objdump's representation of an unprintable + # character.) This should work for all archives with less than + # 0x302f exports -- but will fail for DLLs whose name actually + # begins with a literal '.' or a single character followed by + # a '.'. + # + # Of those that remain, print the first one. + $SED -e '/^\./d;/^.\./d;q' +} + +# func_cygming_dll_for_implib_fallback ARG +# Platform-specific function to extract the +# name of the DLL associated with the specified +# import library ARG. +# +# This fallback implementation is for use when $DLLTOOL +# does not support the --identify-strict option. +# Invoked by eval'ing the libtool variable +# $sharedlib_from_linklib_cmd +# Result is available in the variable +# $sharedlib_from_linklib_result +func_cygming_dll_for_implib_fallback () +{ + $debug_cmd + + if func_cygming_gnu_implib_p "$1"; then + # binutils import library + sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$7' "$1"` + elif func_cygming_ms_implib_p "$1"; then + # ms-generated import library + sharedlib_from_linklib_result=`func_cygming_dll_for_implib_fallback_core '.idata$6' "$1"` + else + # unknown + sharedlib_from_linklib_result= + fi +} + + +# func_extract_an_archive dir oldlib +func_extract_an_archive () +{ + $debug_cmd + + f_ex_an_ar_dir=$1; shift + f_ex_an_ar_oldlib=$1 + if test yes = "$lock_old_archive_extraction"; then + lockfile=$f_ex_an_ar_oldlib.lock + until $opt_dry_run || ln "$progpath" "$lockfile" 2>/dev/null; do + func_echo "Waiting for $lockfile to be removed" + sleep 2 + done + fi + func_show_eval "(cd \$f_ex_an_ar_dir && $AR x \"\$f_ex_an_ar_oldlib\")" \ + 'stat=$?; rm -f "$lockfile"; exit $stat' + if test yes = "$lock_old_archive_extraction"; then + $opt_dry_run || rm -f "$lockfile" + fi + if ($AR t "$f_ex_an_ar_oldlib" | sort | sort -uc >/dev/null 2>&1); then + : + else + func_fatal_error "object name conflicts in archive: $f_ex_an_ar_dir/$f_ex_an_ar_oldlib" + fi +} + + +# func_extract_archives gentop oldlib ... +func_extract_archives () +{ + $debug_cmd + + my_gentop=$1; shift + my_oldlibs=${1+"$@"} + my_oldobjs= + my_xlib= + my_xabs= + my_xdir= + + for my_xlib in $my_oldlibs; do + # Extract the objects. + case $my_xlib in + [\\/]* | [A-Za-z]:[\\/]*) my_xabs=$my_xlib ;; + *) my_xabs=`pwd`"/$my_xlib" ;; + esac + func_basename "$my_xlib" + my_xlib=$func_basename_result + my_xlib_u=$my_xlib + while :; do + case " $extracted_archives " in + *" $my_xlib_u "*) + func_arith $extracted_serial + 1 + extracted_serial=$func_arith_result + my_xlib_u=lt$extracted_serial-$my_xlib ;; + *) break ;; + esac + done + extracted_archives="$extracted_archives $my_xlib_u" + my_xdir=$my_gentop/$my_xlib_u + + func_mkdir_p "$my_xdir" + + case $host in + *-darwin*) + func_verbose "Extracting $my_xabs" + # Do not bother doing anything if just a dry run + $opt_dry_run || { + darwin_orig_dir=`pwd` + cd $my_xdir || exit $? + darwin_archive=$my_xabs + darwin_curdir=`pwd` + func_basename "$darwin_archive" + darwin_base_archive=$func_basename_result + darwin_arches=`$LIPO -info "$darwin_archive" 2>/dev/null | $GREP Architectures 2>/dev/null || true` + if test -n "$darwin_arches"; then + darwin_arches=`$ECHO "$darwin_arches" | $SED -e 's/.*are://'` + darwin_arch= + func_verbose "$darwin_base_archive has multiple architectures $darwin_arches" + for darwin_arch in $darwin_arches; do + func_mkdir_p "unfat-$$/$darwin_base_archive-$darwin_arch" + $LIPO -thin $darwin_arch -output "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" "$darwin_archive" + cd "unfat-$$/$darwin_base_archive-$darwin_arch" + func_extract_an_archive "`pwd`" "$darwin_base_archive" + cd "$darwin_curdir" + $RM "unfat-$$/$darwin_base_archive-$darwin_arch/$darwin_base_archive" + done # $darwin_arches + ## Okay now we've a bunch of thin objects, gotta fatten them up :) + darwin_filelist=`find unfat-$$ -type f -name \*.o -print -o -name \*.lo -print | $SED -e "$sed_basename" | sort -u` + darwin_file= + darwin_files= + for darwin_file in $darwin_filelist; do + darwin_files=`find unfat-$$ -name $darwin_file -print | sort | $NL2SP` + $LIPO -create -output "$darwin_file" $darwin_files + done # $darwin_filelist + $RM -rf unfat-$$ + cd "$darwin_orig_dir" + else + cd $darwin_orig_dir + func_extract_an_archive "$my_xdir" "$my_xabs" + fi # $darwin_arches + } # !$opt_dry_run + ;; + *) + func_extract_an_archive "$my_xdir" "$my_xabs" + ;; + esac + my_oldobjs="$my_oldobjs "`find $my_xdir -name \*.$objext -print -o -name \*.lo -print | sort | $NL2SP` + done + + func_extract_archives_result=$my_oldobjs +} + + +# func_emit_wrapper [arg=no] +# +# Emit a libtool wrapper script on stdout. +# Don't directly open a file because we may want to +# incorporate the script contents within a cygwin/mingw +# wrapper executable. Must ONLY be called from within +# func_mode_link because it depends on a number of variables +# set therein. +# +# ARG is the value that the WRAPPER_SCRIPT_BELONGS_IN_OBJDIR +# variable will take. If 'yes', then the emitted script +# will assume that the directory where it is stored is +# the $objdir directory. This is a cygwin/mingw-specific +# behavior. +func_emit_wrapper () +{ + func_emit_wrapper_arg1=${1-no} + + $ECHO "\ +#! $SHELL + +# $output - temporary wrapper script for $objdir/$outputname +# Generated by $PROGRAM (GNU $PACKAGE) $VERSION +# +# The $output program cannot be directly executed until all the libtool +# libraries that it depends on are installed. +# +# This wrapper script should never be moved out of the build directory. +# If it is, it will not operate correctly. + +# Sed substitution that helps us do robust quoting. It backslashifies +# metacharacters that are still active within double-quoted strings. +sed_quote_subst='$sed_quote_subst' + +# Be Bourne compatible +if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then + emulate sh + NULLCMD=: + # Zsh 3.x and 4.x performs word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in *posix*) set -o posix;; esac +fi +BIN_SH=xpg4; export BIN_SH # for Tru64 +DUALCASE=1; export DUALCASE # for MKS sh + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +relink_command=\"$relink_command\" + +# This environment variable determines our operation mode. +if test \"\$libtool_install_magic\" = \"$magic\"; then + # install mode needs the following variables: + generated_by_libtool_version='$macro_version' + notinst_deplibs='$notinst_deplibs' +else + # When we are sourced in execute mode, \$file and \$ECHO are already set. + if test \"\$libtool_execute_magic\" != \"$magic\"; then + file=\"\$0\"" + + qECHO=`$ECHO "$ECHO" | $SED "$sed_quote_subst"` + $ECHO "\ + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$1 +_LTECHO_EOF' +} + ECHO=\"$qECHO\" + fi + +# Very basic option parsing. These options are (a) specific to +# the libtool wrapper, (b) are identical between the wrapper +# /script/ and the wrapper /executable/ that is used only on +# windows platforms, and (c) all begin with the string "--lt-" +# (application programs are unlikely to have options that match +# this pattern). +# +# There are only two supported options: --lt-debug and +# --lt-dump-script. There is, deliberately, no --lt-help. +# +# The first argument to this parsing function should be the +# script's $0 value, followed by "$@". +lt_option_debug= +func_parse_lt_options () +{ + lt_script_arg0=\$0 + shift + for lt_opt + do + case \"\$lt_opt\" in + --lt-debug) lt_option_debug=1 ;; + --lt-dump-script) + lt_dump_D=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%/[^/]*$%%'\` + test \"X\$lt_dump_D\" = \"X\$lt_script_arg0\" && lt_dump_D=. + lt_dump_F=\`\$ECHO \"X\$lt_script_arg0\" | $SED -e 's/^X//' -e 's%^.*/%%'\` + cat \"\$lt_dump_D/\$lt_dump_F\" + exit 0 + ;; + --lt-*) + \$ECHO \"Unrecognized --lt- option: '\$lt_opt'\" 1>&2 + exit 1 + ;; + esac + done + + # Print the debug banner immediately: + if test -n \"\$lt_option_debug\"; then + echo \"$outputname:$output:\$LINENO: libtool wrapper (GNU $PACKAGE) $VERSION\" 1>&2 + fi +} + +# Used when --lt-debug. Prints its arguments to stdout +# (redirection is the responsibility of the caller) +func_lt_dump_args () +{ + lt_dump_args_N=1; + for lt_arg + do + \$ECHO \"$outputname:$output:\$LINENO: newargv[\$lt_dump_args_N]: \$lt_arg\" + lt_dump_args_N=\`expr \$lt_dump_args_N + 1\` + done +} + +# Core function for launching the target application +func_exec_program_core () +{ +" + case $host in + # Backslashes separate directories on plain windows + *-*-mingw | *-*-os2* | *-cegcc*) + $ECHO "\ + if test -n \"\$lt_option_debug\"; then + \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir\\\\\$program\" 1>&2 + func_lt_dump_args \${1+\"\$@\"} 1>&2 + fi + exec \"\$progdir\\\\\$program\" \${1+\"\$@\"} +" + ;; + + *) + $ECHO "\ + if test -n \"\$lt_option_debug\"; then + \$ECHO \"$outputname:$output:\$LINENO: newargv[0]: \$progdir/\$program\" 1>&2 + func_lt_dump_args \${1+\"\$@\"} 1>&2 + fi + exec \"\$progdir/\$program\" \${1+\"\$@\"} +" + ;; + esac + $ECHO "\ + \$ECHO \"\$0: cannot exec \$program \$*\" 1>&2 + exit 1 +} + +# A function to encapsulate launching the target application +# Strips options in the --lt-* namespace from \$@ and +# launches target application with the remaining arguments. +func_exec_program () +{ + case \" \$* \" in + *\\ --lt-*) + for lt_wr_arg + do + case \$lt_wr_arg in + --lt-*) ;; + *) set x \"\$@\" \"\$lt_wr_arg\"; shift;; + esac + shift + done ;; + esac + func_exec_program_core \${1+\"\$@\"} +} + + # Parse options + func_parse_lt_options \"\$0\" \${1+\"\$@\"} + + # Find the directory that this script lives in. + thisdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*$%%'\` + test \"x\$thisdir\" = \"x\$file\" && thisdir=. + + # Follow symbolic links until we get to the real thisdir. + file=\`ls -ld \"\$file\" | $SED -n 's/.*-> //p'\` + while test -n \"\$file\"; do + destdir=\`\$ECHO \"\$file\" | $SED 's%/[^/]*\$%%'\` + + # If there was a directory component, then change thisdir. + if test \"x\$destdir\" != \"x\$file\"; then + case \"\$destdir\" in + [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;; + *) thisdir=\"\$thisdir/\$destdir\" ;; + esac + fi + + file=\`\$ECHO \"\$file\" | $SED 's%^.*/%%'\` + file=\`ls -ld \"\$thisdir/\$file\" | $SED -n 's/.*-> //p'\` + done + + # Usually 'no', except on cygwin/mingw when embedded into + # the cwrapper. + WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=$func_emit_wrapper_arg1 + if test \"\$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR\" = \"yes\"; then + # special case for '.' + if test \"\$thisdir\" = \".\"; then + thisdir=\`pwd\` + fi + # remove .libs from thisdir + case \"\$thisdir\" in + *[\\\\/]$objdir ) thisdir=\`\$ECHO \"\$thisdir\" | $SED 's%[\\\\/][^\\\\/]*$%%'\` ;; + $objdir ) thisdir=. ;; + esac + fi + + # Try to get the absolute directory name. + absdir=\`cd \"\$thisdir\" && pwd\` + test -n \"\$absdir\" && thisdir=\"\$absdir\" +" + + if test yes = "$fast_install"; then + $ECHO "\ + program=lt-'$outputname'$exeext + progdir=\"\$thisdir/$objdir\" + + if test ! -f \"\$progdir/\$program\" || + { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | $SED 1q\`; \\ + test \"X\$file\" != \"X\$progdir/\$program\"; }; then + + file=\"\$\$-\$program\" + + if test ! -d \"\$progdir\"; then + $MKDIR \"\$progdir\" + else + $RM \"\$progdir/\$file\" + fi" + + $ECHO "\ + + # relink executable if necessary + if test -n \"\$relink_command\"; then + if relink_command_output=\`eval \$relink_command 2>&1\`; then : + else + \$ECHO \"\$relink_command_output\" >&2 + $RM \"\$progdir/\$file\" + exit 1 + fi + fi + + $MV \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null || + { $RM \"\$progdir/\$program\"; + $MV \"\$progdir/\$file\" \"\$progdir/\$program\"; } + $RM \"\$progdir/\$file\" + fi" + else + $ECHO "\ + program='$outputname' + progdir=\"\$thisdir/$objdir\" +" + fi + + $ECHO "\ + + if test -f \"\$progdir/\$program\"; then" + + # fixup the dll searchpath if we need to. + # + # Fix the DLL searchpath if we need to. Do this before prepending + # to shlibpath, because on Windows, both are PATH and uninstalled + # libraries must come first. + if test -n "$dllsearchpath"; then + $ECHO "\ + # Add the dll search path components to the executable PATH + PATH=$dllsearchpath:\$PATH +" + fi + + # Export our shlibpath_var if we have one. + if test yes = "$shlibpath_overrides_runpath" && test -n "$shlibpath_var" && test -n "$temp_rpath"; then + $ECHO "\ + # Add our own library path to $shlibpath_var + $shlibpath_var=\"$temp_rpath\$$shlibpath_var\" + + # Some systems cannot cope with colon-terminated $shlibpath_var + # The second colon is a workaround for a bug in BeOS R4 sed + $shlibpath_var=\`\$ECHO \"\$$shlibpath_var\" | $SED 's/::*\$//'\` + + export $shlibpath_var +" + fi + + $ECHO "\ + if test \"\$libtool_execute_magic\" != \"$magic\"; then + # Run the actual program with our arguments. + func_exec_program \${1+\"\$@\"} + fi + else + # The program doesn't exist. + \$ECHO \"\$0: error: '\$progdir/\$program' does not exist\" 1>&2 + \$ECHO \"This script is just a wrapper for \$program.\" 1>&2 + \$ECHO \"See the $PACKAGE documentation for more information.\" 1>&2 + exit 1 + fi +fi\ +" +} + + +# func_emit_cwrapperexe_src +# emit the source code for a wrapper executable on stdout +# Must ONLY be called from within func_mode_link because +# it depends on a number of variable set therein. +func_emit_cwrapperexe_src () +{ + cat < +#include +#ifdef _MSC_VER +# include +# include +# include +#else +# include +# include +# ifdef __CYGWIN__ +# include +# endif +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#define STREQ(s1, s2) (strcmp ((s1), (s2)) == 0) + +/* declarations of non-ANSI functions */ +#if defined __MINGW32__ +# ifdef __STRICT_ANSI__ +int _putenv (const char *); +# endif +#elif defined __CYGWIN__ +# ifdef __STRICT_ANSI__ +char *realpath (const char *, char *); +int putenv (char *); +int setenv (const char *, const char *, int); +# endif +/* #elif defined other_platform || defined ... */ +#endif + +/* portability defines, excluding path handling macros */ +#if defined _MSC_VER +# define setmode _setmode +# define stat _stat +# define chmod _chmod +# define getcwd _getcwd +# define putenv _putenv +# define S_IXUSR _S_IEXEC +#elif defined __MINGW32__ +# define setmode _setmode +# define stat _stat +# define chmod _chmod +# define getcwd _getcwd +# define putenv _putenv +#elif defined __CYGWIN__ +# define HAVE_SETENV +# define FOPEN_WB "wb" +/* #elif defined other platforms ... */ +#endif + +#if defined PATH_MAX +# define LT_PATHMAX PATH_MAX +#elif defined MAXPATHLEN +# define LT_PATHMAX MAXPATHLEN +#else +# define LT_PATHMAX 1024 +#endif + +#ifndef S_IXOTH +# define S_IXOTH 0 +#endif +#ifndef S_IXGRP +# define S_IXGRP 0 +#endif + +/* path handling portability macros */ +#ifndef DIR_SEPARATOR +# define DIR_SEPARATOR '/' +# define PATH_SEPARATOR ':' +#endif + +#if defined _WIN32 || defined __MSDOS__ || defined __DJGPP__ || \ + defined __OS2__ +# define HAVE_DOS_BASED_FILE_SYSTEM +# define FOPEN_WB "wb" +# ifndef DIR_SEPARATOR_2 +# define DIR_SEPARATOR_2 '\\' +# endif +# ifndef PATH_SEPARATOR_2 +# define PATH_SEPARATOR_2 ';' +# endif +#endif + +#ifndef DIR_SEPARATOR_2 +# define IS_DIR_SEPARATOR(ch) ((ch) == DIR_SEPARATOR) +#else /* DIR_SEPARATOR_2 */ +# define IS_DIR_SEPARATOR(ch) \ + (((ch) == DIR_SEPARATOR) || ((ch) == DIR_SEPARATOR_2)) +#endif /* DIR_SEPARATOR_2 */ + +#ifndef PATH_SEPARATOR_2 +# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR) +#else /* PATH_SEPARATOR_2 */ +# define IS_PATH_SEPARATOR(ch) ((ch) == PATH_SEPARATOR_2) +#endif /* PATH_SEPARATOR_2 */ + +#ifndef FOPEN_WB +# define FOPEN_WB "w" +#endif +#ifndef _O_BINARY +# define _O_BINARY 0 +#endif + +#define XMALLOC(type, num) ((type *) xmalloc ((num) * sizeof(type))) +#define XFREE(stale) do { \ + if (stale) { free (stale); stale = 0; } \ +} while (0) + +#if defined LT_DEBUGWRAPPER +static int lt_debug = 1; +#else +static int lt_debug = 0; +#endif + +const char *program_name = "libtool-wrapper"; /* in case xstrdup fails */ + +void *xmalloc (size_t num); +char *xstrdup (const char *string); +const char *base_name (const char *name); +char *find_executable (const char *wrapper); +char *chase_symlinks (const char *pathspec); +int make_executable (const char *path); +int check_executable (const char *path); +char *strendzap (char *str, const char *pat); +void lt_debugprintf (const char *file, int line, const char *fmt, ...); +void lt_fatal (const char *file, int line, const char *message, ...); +static const char *nonnull (const char *s); +static const char *nonempty (const char *s); +void lt_setenv (const char *name, const char *value); +char *lt_extend_str (const char *orig_value, const char *add, int to_end); +void lt_update_exe_path (const char *name, const char *value); +void lt_update_lib_path (const char *name, const char *value); +char **prepare_spawn (char **argv); +void lt_dump_script (FILE *f); +EOF + + cat <= 0) + && (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + return 1; + else + return 0; +} + +int +make_executable (const char *path) +{ + int rval = 0; + struct stat st; + + lt_debugprintf (__FILE__, __LINE__, "(make_executable): %s\n", + nonempty (path)); + if ((!path) || (!*path)) + return 0; + + if (stat (path, &st) >= 0) + { + rval = chmod (path, st.st_mode | S_IXOTH | S_IXGRP | S_IXUSR); + } + return rval; +} + +/* Searches for the full path of the wrapper. Returns + newly allocated full path name if found, NULL otherwise + Does not chase symlinks, even on platforms that support them. +*/ +char * +find_executable (const char *wrapper) +{ + int has_slash = 0; + const char *p; + const char *p_next; + /* static buffer for getcwd */ + char tmp[LT_PATHMAX + 1]; + size_t tmp_len; + char *concat_name; + + lt_debugprintf (__FILE__, __LINE__, "(find_executable): %s\n", + nonempty (wrapper)); + + if ((wrapper == NULL) || (*wrapper == '\0')) + return NULL; + + /* Absolute path? */ +#if defined HAVE_DOS_BASED_FILE_SYSTEM + if (isalpha ((unsigned char) wrapper[0]) && wrapper[1] == ':') + { + concat_name = xstrdup (wrapper); + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } + else + { +#endif + if (IS_DIR_SEPARATOR (wrapper[0])) + { + concat_name = xstrdup (wrapper); + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } +#if defined HAVE_DOS_BASED_FILE_SYSTEM + } +#endif + + for (p = wrapper; *p; p++) + if (*p == '/') + { + has_slash = 1; + break; + } + if (!has_slash) + { + /* no slashes; search PATH */ + const char *path = getenv ("PATH"); + if (path != NULL) + { + for (p = path; *p; p = p_next) + { + const char *q; + size_t p_len; + for (q = p; *q; q++) + if (IS_PATH_SEPARATOR (*q)) + break; + p_len = (size_t) (q - p); + p_next = (*q == '\0' ? q : q + 1); + if (p_len == 0) + { + /* empty path: current directory */ + if (getcwd (tmp, LT_PATHMAX) == NULL) + lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", + nonnull (strerror (errno))); + tmp_len = strlen (tmp); + concat_name = + XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, tmp, tmp_len); + concat_name[tmp_len] = '/'; + strcpy (concat_name + tmp_len + 1, wrapper); + } + else + { + concat_name = + XMALLOC (char, p_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, p, p_len); + concat_name[p_len] = '/'; + strcpy (concat_name + p_len + 1, wrapper); + } + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + } + } + /* not found in PATH; assume curdir */ + } + /* Relative path | not found in path: prepend cwd */ + if (getcwd (tmp, LT_PATHMAX) == NULL) + lt_fatal (__FILE__, __LINE__, "getcwd failed: %s", + nonnull (strerror (errno))); + tmp_len = strlen (tmp); + concat_name = XMALLOC (char, tmp_len + 1 + strlen (wrapper) + 1); + memcpy (concat_name, tmp, tmp_len); + concat_name[tmp_len] = '/'; + strcpy (concat_name + tmp_len + 1, wrapper); + + if (check_executable (concat_name)) + return concat_name; + XFREE (concat_name); + return NULL; +} + +char * +chase_symlinks (const char *pathspec) +{ +#ifndef S_ISLNK + return xstrdup (pathspec); +#else + char buf[LT_PATHMAX]; + struct stat s; + char *tmp_pathspec = xstrdup (pathspec); + char *p; + int has_symlinks = 0; + while (strlen (tmp_pathspec) && !has_symlinks) + { + lt_debugprintf (__FILE__, __LINE__, + "checking path component for symlinks: %s\n", + tmp_pathspec); + if (lstat (tmp_pathspec, &s) == 0) + { + if (S_ISLNK (s.st_mode) != 0) + { + has_symlinks = 1; + break; + } + + /* search backwards for last DIR_SEPARATOR */ + p = tmp_pathspec + strlen (tmp_pathspec) - 1; + while ((p > tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) + p--; + if ((p == tmp_pathspec) && (!IS_DIR_SEPARATOR (*p))) + { + /* no more DIR_SEPARATORS left */ + break; + } + *p = '\0'; + } + else + { + lt_fatal (__FILE__, __LINE__, + "error accessing file \"%s\": %s", + tmp_pathspec, nonnull (strerror (errno))); + } + } + XFREE (tmp_pathspec); + + if (!has_symlinks) + { + return xstrdup (pathspec); + } + + tmp_pathspec = realpath (pathspec, buf); + if (tmp_pathspec == 0) + { + lt_fatal (__FILE__, __LINE__, + "could not follow symlinks for %s", pathspec); + } + return xstrdup (tmp_pathspec); +#endif +} + +char * +strendzap (char *str, const char *pat) +{ + size_t len, patlen; + + assert (str != NULL); + assert (pat != NULL); + + len = strlen (str); + patlen = strlen (pat); + + if (patlen <= len) + { + str += len - patlen; + if (STREQ (str, pat)) + *str = '\0'; + } + return str; +} + +void +lt_debugprintf (const char *file, int line, const char *fmt, ...) +{ + va_list args; + if (lt_debug) + { + (void) fprintf (stderr, "%s:%s:%d: ", program_name, file, line); + va_start (args, fmt); + (void) vfprintf (stderr, fmt, args); + va_end (args); + } +} + +static void +lt_error_core (int exit_status, const char *file, + int line, const char *mode, + const char *message, va_list ap) +{ + fprintf (stderr, "%s:%s:%d: %s: ", program_name, file, line, mode); + vfprintf (stderr, message, ap); + fprintf (stderr, ".\n"); + + if (exit_status >= 0) + exit (exit_status); +} + +void +lt_fatal (const char *file, int line, const char *message, ...) +{ + va_list ap; + va_start (ap, message); + lt_error_core (EXIT_FAILURE, file, line, "FATAL", message, ap); + va_end (ap); +} + +static const char * +nonnull (const char *s) +{ + return s ? s : "(null)"; +} + +static const char * +nonempty (const char *s) +{ + return (s && !*s) ? "(empty)" : nonnull (s); +} + +void +lt_setenv (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_setenv) setting '%s' to '%s'\n", + nonnull (name), nonnull (value)); + { +#ifdef HAVE_SETENV + /* always make a copy, for consistency with !HAVE_SETENV */ + char *str = xstrdup (value); + setenv (name, str, 1); +#else + size_t len = strlen (name) + 1 + strlen (value) + 1; + char *str = XMALLOC (char, len); + sprintf (str, "%s=%s", name, value); + if (putenv (str) != EXIT_SUCCESS) + { + XFREE (str); + } +#endif + } +} + +char * +lt_extend_str (const char *orig_value, const char *add, int to_end) +{ + char *new_value; + if (orig_value && *orig_value) + { + size_t orig_value_len = strlen (orig_value); + size_t add_len = strlen (add); + new_value = XMALLOC (char, add_len + orig_value_len + 1); + if (to_end) + { + strcpy (new_value, orig_value); + strcpy (new_value + orig_value_len, add); + } + else + { + strcpy (new_value, add); + strcpy (new_value + add_len, orig_value); + } + } + else + { + new_value = xstrdup (add); + } + return new_value; +} + +void +lt_update_exe_path (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_update_exe_path) modifying '%s' by prepending '%s'\n", + nonnull (name), nonnull (value)); + + if (name && *name && value && *value) + { + char *new_value = lt_extend_str (getenv (name), value, 0); + /* some systems can't cope with a ':'-terminated path #' */ + size_t len = strlen (new_value); + while ((len > 0) && IS_PATH_SEPARATOR (new_value[len-1])) + { + new_value[--len] = '\0'; + } + lt_setenv (name, new_value); + XFREE (new_value); + } +} + +void +lt_update_lib_path (const char *name, const char *value) +{ + lt_debugprintf (__FILE__, __LINE__, + "(lt_update_lib_path) modifying '%s' by prepending '%s'\n", + nonnull (name), nonnull (value)); + + if (name && *name && value && *value) + { + char *new_value = lt_extend_str (getenv (name), value, 0); + lt_setenv (name, new_value); + XFREE (new_value); + } +} + +EOF + case $host_os in + mingw*) + cat <<"EOF" + +/* Prepares an argument vector before calling spawn(). + Note that spawn() does not by itself call the command interpreter + (getenv ("COMSPEC") != NULL ? getenv ("COMSPEC") : + ({ OSVERSIONINFO v; v.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&v); + v.dwPlatformId == VER_PLATFORM_WIN32_NT; + }) ? "cmd.exe" : "command.com"). + Instead it simply concatenates the arguments, separated by ' ', and calls + CreateProcess(). We must quote the arguments since Win32 CreateProcess() + interprets characters like ' ', '\t', '\\', '"' (but not '<' and '>') in a + special way: + - Space and tab are interpreted as delimiters. They are not treated as + delimiters if they are surrounded by double quotes: "...". + - Unescaped double quotes are removed from the input. Their only effect is + that within double quotes, space and tab are treated like normal + characters. + - Backslashes not followed by double quotes are not special. + - But 2*n+1 backslashes followed by a double quote become + n backslashes followed by a double quote (n >= 0): + \" -> " + \\\" -> \" + \\\\\" -> \\" + */ +#define SHELL_SPECIAL_CHARS "\"\\ \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" +#define SHELL_SPACE_CHARS " \001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037" +char ** +prepare_spawn (char **argv) +{ + size_t argc; + char **new_argv; + size_t i; + + /* Count number of arguments. */ + for (argc = 0; argv[argc] != NULL; argc++) + ; + + /* Allocate new argument vector. */ + new_argv = XMALLOC (char *, argc + 1); + + /* Put quoted arguments into the new argument vector. */ + for (i = 0; i < argc; i++) + { + const char *string = argv[i]; + + if (string[0] == '\0') + new_argv[i] = xstrdup ("\"\""); + else if (strpbrk (string, SHELL_SPECIAL_CHARS) != NULL) + { + int quote_around = (strpbrk (string, SHELL_SPACE_CHARS) != NULL); + size_t length; + unsigned int backslashes; + const char *s; + char *quoted_string; + char *p; + + length = 0; + backslashes = 0; + if (quote_around) + length++; + for (s = string; *s != '\0'; s++) + { + char c = *s; + if (c == '"') + length += backslashes + 1; + length++; + if (c == '\\') + backslashes++; + else + backslashes = 0; + } + if (quote_around) + length += backslashes + 1; + + quoted_string = XMALLOC (char, length + 1); + + p = quoted_string; + backslashes = 0; + if (quote_around) + *p++ = '"'; + for (s = string; *s != '\0'; s++) + { + char c = *s; + if (c == '"') + { + unsigned int j; + for (j = backslashes + 1; j > 0; j--) + *p++ = '\\'; + } + *p++ = c; + if (c == '\\') + backslashes++; + else + backslashes = 0; + } + if (quote_around) + { + unsigned int j; + for (j = backslashes; j > 0; j--) + *p++ = '\\'; + *p++ = '"'; + } + *p = '\0'; + + new_argv[i] = quoted_string; + } + else + new_argv[i] = (char *) string; + } + new_argv[argc] = NULL; + + return new_argv; +} +EOF + ;; + esac + + cat <<"EOF" +void lt_dump_script (FILE* f) +{ +EOF + func_emit_wrapper yes | + $SED -n -e ' +s/^\(.\{79\}\)\(..*\)/\1\ +\2/ +h +s/\([\\"]\)/\\\1/g +s/$/\\n/ +s/\([^\n]*\).*/ fputs ("\1", f);/p +g +D' + cat <<"EOF" +} +EOF +} +# end: func_emit_cwrapperexe_src + +# func_win32_import_lib_p ARG +# True if ARG is an import lib, as indicated by $file_magic_cmd +func_win32_import_lib_p () +{ + $debug_cmd + + case `eval $file_magic_cmd \"\$1\" 2>/dev/null | $SED -e 10q` in + *import*) : ;; + *) false ;; + esac +} + +# func_suncc_cstd_abi +# !!ONLY CALL THIS FOR SUN CC AFTER $compile_command IS FULLY EXPANDED!! +# Several compiler flags select an ABI that is incompatible with the +# Cstd library. Avoid specifying it if any are in CXXFLAGS. +func_suncc_cstd_abi () +{ + $debug_cmd + + case " $compile_command " in + *" -compat=g "*|*\ -std=c++[0-9][0-9]\ *|*" -library=stdcxx4 "*|*" -library=stlport4 "*) + suncc_use_cstd_abi=no + ;; + *) + suncc_use_cstd_abi=yes + ;; + esac +} + +# func_mode_link arg... +func_mode_link () +{ + $debug_cmd + + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + # It is impossible to link a dll without this setting, and + # we shouldn't force the makefile maintainer to figure out + # what system we are compiling for in order to pass an extra + # flag for every libtool invocation. + # allow_undefined=no + + # FIXME: Unfortunately, there are problems with the above when trying + # to make a dll that has undefined symbols, in which case not + # even a static library is built. For now, we need to specify + # -no-undefined on the libtool link line when we can be certain + # that all symbols are satisfied, otherwise we get a static library. + allow_undefined=yes + ;; + *) + allow_undefined=yes + ;; + esac + libtool_args=$nonopt + base_compile="$nonopt $@" + compile_command=$nonopt + finalize_command=$nonopt + + compile_rpath= + finalize_rpath= + compile_shlibpath= + finalize_shlibpath= + convenience= + old_convenience= + deplibs= + old_deplibs= + compiler_flags= + linker_flags= + dllsearchpath= + lib_search_path=`pwd` + inst_prefix_dir= + new_inherited_linker_flags= + + avoid_version=no + bindir= + dlfiles= + dlprefiles= + dlself=no + export_dynamic=no + export_symbols= + export_symbols_regex= + generated= + libobjs= + ltlibs= + module=no + no_install=no + objs= + os2dllname= + non_pic_objects= + precious_files_regex= + prefer_static_libs=no + preload=false + prev= + prevarg= + release= + rpath= + xrpath= + perm_rpath= + temp_rpath= + thread_safe=no + vinfo= + vinfo_number=no + weak_libs= + single_module=$wl-single_module + func_infer_tag $base_compile + + # We need to know -static, to get the right output filenames. + for arg + do + case $arg in + -shared) + test yes != "$build_libtool_libs" \ + && func_fatal_configuration "cannot build a shared library" + build_old_libs=no + break + ;; + -all-static | -static | -static-libtool-libs) + case $arg in + -all-static) + if test yes = "$build_libtool_libs" && test -z "$link_static_flag"; then + func_warning "complete static linking is impossible in this configuration" + fi + if test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=yes + ;; + -static) + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=built + ;; + -static-libtool-libs) + if test -z "$pic_flag" && test -n "$link_static_flag"; then + dlopen_self=$dlopen_self_static + fi + prefer_static_libs=yes + ;; + esac + build_libtool_libs=no + build_old_libs=yes + break + ;; + esac + done + + # See if our shared archives depend on static archives. + test -n "$old_archive_from_new_cmds" && build_old_libs=yes + + # Go through the arguments, transforming them on the way. + while test "$#" -gt 0; do + arg=$1 + shift + func_quote_for_eval "$arg" + qarg=$func_quote_for_eval_unquoted_result + func_append libtool_args " $func_quote_for_eval_result" + + # If the previous option needs an argument, assign it. + if test -n "$prev"; then + case $prev in + output) + func_append compile_command " @OUTPUT@" + func_append finalize_command " @OUTPUT@" + ;; + esac + + case $prev in + bindir) + bindir=$arg + prev= + continue + ;; + dlfiles|dlprefiles) + $preload || { + # Add the symbol object into the linking commands. + func_append compile_command " @SYMFILE@" + func_append finalize_command " @SYMFILE@" + preload=: + } + case $arg in + *.la | *.lo) ;; # We handle these cases below. + force) + if test no = "$dlself"; then + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + self) + if test dlprefiles = "$prev"; then + dlself=yes + elif test dlfiles = "$prev" && test yes != "$dlopen_self"; then + dlself=yes + else + dlself=needless + export_dynamic=yes + fi + prev= + continue + ;; + *) + if test dlfiles = "$prev"; then + func_append dlfiles " $arg" + else + func_append dlprefiles " $arg" + fi + prev= + continue + ;; + esac + ;; + expsyms) + export_symbols=$arg + test -f "$arg" \ + || func_fatal_error "symbol file '$arg' does not exist" + prev= + continue + ;; + expsyms_regex) + export_symbols_regex=$arg + prev= + continue + ;; + framework) + case $host in + *-*-darwin*) + case "$deplibs " in + *" $qarg.ltframework "*) ;; + *) func_append deplibs " $qarg.ltframework" # this is fixed later + ;; + esac + ;; + esac + prev= + continue + ;; + inst_prefix) + inst_prefix_dir=$arg + prev= + continue + ;; + mllvm) + # Clang does not use LLVM to link, so we can simply discard any + # '-mllvm $arg' options when doing the link step. + prev= + continue + ;; + objectlist) + if test -f "$arg"; then + save_arg=$arg + moreargs= + for fil in `cat "$save_arg"` + do +# func_append moreargs " $fil" + arg=$fil + # A libtool-controlled object. + + # Check to see that this really is a libtool object. + if func_lalib_unsafe_p "$arg"; then + pic_object= + non_pic_object= + + # Read the .lo file + func_source "$arg" + + if test -z "$pic_object" || + test -z "$non_pic_object" || + test none = "$pic_object" && + test none = "$non_pic_object"; then + func_fatal_error "cannot find name of object for '$arg'" + fi + + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + if test none != "$pic_object"; then + # Prepend the subdirectory the object is found in. + pic_object=$xdir$pic_object + + if test dlfiles = "$prev"; then + if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then + func_append dlfiles " $pic_object" + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + # CHECK ME: I think I busted this. -Ossama + if test dlprefiles = "$prev"; then + # Preload the old-style object. + func_append dlprefiles " $pic_object" + prev= + fi + + # A PIC object. + func_append libobjs " $pic_object" + arg=$pic_object + fi + + # Non-PIC object. + if test none != "$non_pic_object"; then + # Prepend the subdirectory the object is found in. + non_pic_object=$xdir$non_pic_object + + # A standard non-PIC object + func_append non_pic_objects " $non_pic_object" + if test -z "$pic_object" || test none = "$pic_object"; then + arg=$non_pic_object + fi + else + # If the PIC object exists, use it instead. + # $xdir was prepended to $pic_object above. + non_pic_object=$pic_object + func_append non_pic_objects " $non_pic_object" + fi + else + # Only an error if not doing a dry-run. + if $opt_dry_run; then + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + func_lo2o "$arg" + pic_object=$xdir$objdir/$func_lo2o_result + non_pic_object=$xdir$func_lo2o_result + func_append libobjs " $pic_object" + func_append non_pic_objects " $non_pic_object" + else + func_fatal_error "'$arg' is not a valid libtool object" + fi + fi + done + else + func_fatal_error "link input file '$arg' does not exist" + fi + arg=$save_arg + prev= + continue + ;; + os2dllname) + os2dllname=$arg + prev= + continue + ;; + precious_regex) + precious_files_regex=$arg + prev= + continue + ;; + release) + release=-$arg + prev= + continue + ;; + rpath | xrpath) + # We need an absolute path. + case $arg in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + func_fatal_error "only absolute run-paths are allowed" + ;; + esac + if test rpath = "$prev"; then + case "$rpath " in + *" $arg "*) ;; + *) func_append rpath " $arg" ;; + esac + else + case "$xrpath " in + *" $arg "*) ;; + *) func_append xrpath " $arg" ;; + esac + fi + prev= + continue + ;; + shrext) + shrext_cmds=$arg + prev= + continue + ;; + weak) + func_append weak_libs " $arg" + prev= + continue + ;; + xcclinker) + func_append linker_flags " $qarg" + func_append compiler_flags " $qarg" + prev= + func_append compile_command " $qarg" + func_append finalize_command " $qarg" + continue + ;; + xcompiler) + func_append compiler_flags " $qarg" + prev= + func_append compile_command " $qarg" + func_append finalize_command " $qarg" + continue + ;; + xlinker) + func_append linker_flags " $qarg" + func_append compiler_flags " $wl$qarg" + prev= + func_append compile_command " $wl$qarg" + func_append finalize_command " $wl$qarg" + continue + ;; + *) + eval "$prev=\"\$arg\"" + prev= + continue + ;; + esac + fi # test -n "$prev" + + prevarg=$arg + + case $arg in + -all-static) + if test -n "$link_static_flag"; then + # See comment for -static flag below, for more details. + func_append compile_command " $link_static_flag" + func_append finalize_command " $link_static_flag" + fi + continue + ;; + + -allow-undefined) + # FIXME: remove this flag sometime in the future. + func_fatal_error "'-allow-undefined' must not be used because it is the default" + ;; + + -avoid-version) + avoid_version=yes + continue + ;; + + -bindir) + prev=bindir + continue + ;; + + -dlopen) + prev=dlfiles + continue + ;; + + -dlpreopen) + prev=dlprefiles + continue + ;; + + -export-dynamic) + export_dynamic=yes + continue + ;; + + -export-symbols | -export-symbols-regex) + if test -n "$export_symbols" || test -n "$export_symbols_regex"; then + func_fatal_error "more than one -exported-symbols argument is not allowed" + fi + if test X-export-symbols = "X$arg"; then + prev=expsyms + else + prev=expsyms_regex + fi + continue + ;; + + -framework) + prev=framework + continue + ;; + + -inst-prefix-dir) + prev=inst_prefix + continue + ;; + + # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:* + # so, if we see these flags be careful not to treat them like -L + -L[A-Z][A-Z]*:*) + case $with_gcc/$host in + no/*-*-irix* | /*-*-irix*) + func_append compile_command " $arg" + func_append finalize_command " $arg" + ;; + esac + continue + ;; + + -L*) + func_stripname "-L" '' "$arg" + if test -z "$func_stripname_result"; then + if test "$#" -gt 0; then + func_fatal_error "require no space between '-L' and '$1'" + else + func_fatal_error "need path for '-L' option" + fi + fi + func_resolve_sysroot "$func_stripname_result" + dir=$func_resolve_sysroot_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + *) + absdir=`cd "$dir" && pwd` + test -z "$absdir" && \ + func_fatal_error "cannot determine absolute directory name of '$dir'" + dir=$absdir + ;; + esac + case "$deplibs " in + *" -L$dir "* | *" $arg "*) + # Will only happen for absolute or sysroot arguments + ;; + *) + # Preserve sysroot, but never include relative directories + case $dir in + [\\/]* | [A-Za-z]:[\\/]* | =*) func_append deplibs " $arg" ;; + *) func_append deplibs " -L$dir" ;; + esac + func_append lib_search_path " $dir" + ;; + esac + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + testbindir=`$ECHO "$dir" | $SED 's*/lib$*/bin*'` + case :$dllsearchpath: in + *":$dir:"*) ;; + ::) dllsearchpath=$dir;; + *) func_append dllsearchpath ":$dir";; + esac + case :$dllsearchpath: in + *":$testbindir:"*) ;; + ::) dllsearchpath=$testbindir;; + *) func_append dllsearchpath ":$testbindir";; + esac + ;; + esac + continue + ;; + + -l*) + if test X-lc = "X$arg" || test X-lm = "X$arg"; then + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-beos* | *-cegcc* | *-*-haiku*) + # These systems don't actually have a C or math library (as such) + continue + ;; + *-*-os2*) + # These systems don't actually have a C library (as such) + test X-lc = "X$arg" && continue + ;; + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig*) + # Do not include libc due to us having libc/libc_r. + test X-lc = "X$arg" && continue + ;; + *-*-rhapsody* | *-*-darwin1.[012]) + # Rhapsody C and math libraries are in the System framework + func_append deplibs " System.ltframework" + continue + ;; + *-*-sco3.2v5* | *-*-sco5v6*) + # Causes problems with __ctype + test X-lc = "X$arg" && continue + ;; + *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) + # Compiler inserts libc in the correct place for threads to work + test X-lc = "X$arg" && continue + ;; + esac + elif test X-lc_r = "X$arg"; then + case $host in + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly* | *-*-bitrig*) + # Do not include libc_r directly, use -pthread flag. + continue + ;; + esac + fi + func_append deplibs " $arg" + continue + ;; + + -mllvm) + prev=mllvm + continue + ;; + + -module) + module=yes + continue + ;; + + # Tru64 UNIX uses -model [arg] to determine the layout of C++ + # classes, name mangling, and exception handling. + # Darwin uses the -arch flag to determine output architecture. + -model|-arch|-isysroot|--sysroot) + func_append compiler_flags " $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + prev=xcompiler + continue + ;; + + -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \ + |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*) + func_append compiler_flags " $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + case "$new_inherited_linker_flags " in + *" $arg "*) ;; + * ) func_append new_inherited_linker_flags " $arg" ;; + esac + continue + ;; + + -multi_module) + single_module=$wl-multi_module + continue + ;; + + -no-fast-install) + fast_install=no + continue + ;; + + -no-install) + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-darwin* | *-cegcc*) + # The PATH hackery in wrapper scripts is required on Windows + # and Darwin in order for the loader to find any dlls it needs. + func_warning "'-no-install' is ignored for $host" + func_warning "assuming '-no-fast-install' instead" + fast_install=no + ;; + *) no_install=yes ;; + esac + continue + ;; + + -no-undefined) + allow_undefined=no + continue + ;; + + -objectlist) + prev=objectlist + continue + ;; + + -os2dllname) + prev=os2dllname + continue + ;; + + -o) prev=output ;; + + -precious-files-regex) + prev=precious_regex + continue + ;; + + -release) + prev=release + continue + ;; + + -rpath) + prev=rpath + continue + ;; + + -R) + prev=xrpath + continue + ;; + + -R*) + func_stripname '-R' '' "$arg" + dir=$func_stripname_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) ;; + =*) + func_stripname '=' '' "$dir" + dir=$lt_sysroot$func_stripname_result + ;; + *) + func_fatal_error "only absolute run-paths are allowed" + ;; + esac + case "$xrpath " in + *" $dir "*) ;; + *) func_append xrpath " $dir" ;; + esac + continue + ;; + + -shared) + # The effects of -shared are defined in a previous loop. + continue + ;; + + -shrext) + prev=shrext + continue + ;; + + -static | -static-libtool-libs) + # The effects of -static are defined in a previous loop. + # We used to do the same as -all-static on platforms that + # didn't have a PIC flag, but the assumption that the effects + # would be equivalent was wrong. It would break on at least + # Digital Unix and AIX. + continue + ;; + + -thread-safe) + thread_safe=yes + continue + ;; + + -version-info) + prev=vinfo + continue + ;; + + -version-number) + prev=vinfo + vinfo_number=yes + continue + ;; + + -weak) + prev=weak + continue + ;; + + -Wc,*) + func_stripname '-Wc,' '' "$arg" + args=$func_stripname_result + arg= + save_ifs=$IFS; IFS=, + for flag in $args; do + IFS=$save_ifs + func_quote_for_eval "$flag" + func_append arg " $func_quote_for_eval_result" + func_append compiler_flags " $func_quote_for_eval_result" + done + IFS=$save_ifs + func_stripname ' ' '' "$arg" + arg=$func_stripname_result + ;; + + -Wl,*) + func_stripname '-Wl,' '' "$arg" + args=$func_stripname_result + arg= + save_ifs=$IFS; IFS=, + for flag in $args; do + IFS=$save_ifs + func_quote_for_eval "$flag" + func_append arg " $wl$func_quote_for_eval_result" + func_append compiler_flags " $wl$func_quote_for_eval_result" + func_append linker_flags " $func_quote_for_eval_result" + done + IFS=$save_ifs + func_stripname ' ' '' "$arg" + arg=$func_stripname_result + ;; + + -Xcompiler) + prev=xcompiler + continue + ;; + + -Xlinker) + prev=xlinker + continue + ;; + + -XCClinker) + prev=xcclinker + continue + ;; + + # -msg_* for osf cc + -msg_*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + + # Flags to be passed through unchanged, with rationale: + # -64, -mips[0-9] enable 64-bit mode for the SGI compiler + # -r[0-9][0-9]* specify processor for the SGI compiler + # -xarch=*, -xtarget=* enable 64-bit mode for the Sun compiler + # +DA*, +DD* enable 64-bit mode for the HP compiler + # -q* compiler args for the IBM compiler + # -m*, -t[45]*, -txscale* architecture-specific flags for GCC + # -F/path path to uninstalled frameworks, gcc on darwin + # -p, -pg, --coverage, -fprofile-* profiling flags for GCC + # -fstack-protector* stack protector flags for GCC + # @file GCC response files + # -tp=* Portland pgcc target processor selection + # --sysroot=* for sysroot support + # -O*, -g*, -flto*, -fwhopr*, -fuse-linker-plugin GCC link-time optimization + # -specs=* GCC specs files + # -stdlib=* select c++ std lib with clang + # -fsanitize=* Clang/GCC memory and address sanitizer + # -fuse-ld=* Linker select flags for GCC + -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \ + -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \ + -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*| \ + -specs=*|-fsanitize=*|-fuse-ld=*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + func_append compile_command " $arg" + func_append finalize_command " $arg" + func_append compiler_flags " $arg" + continue + ;; + + -Z*) + if test os2 = "`expr $host : '.*\(os2\)'`"; then + # OS/2 uses -Zxxx to specify OS/2-specific options + compiler_flags="$compiler_flags $arg" + func_append compile_command " $arg" + func_append finalize_command " $arg" + case $arg in + -Zlinker | -Zstack) + prev=xcompiler + ;; + esac + continue + else + # Otherwise treat like 'Some other compiler flag' below + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + fi + ;; + + # Some other compiler flag. + -* | +*) + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + + *.$objext) + # A standard object. + func_append objs " $arg" + ;; + + *.lo) + # A libtool-controlled object. + + # Check to see that this really is a libtool object. + if func_lalib_unsafe_p "$arg"; then + pic_object= + non_pic_object= + + # Read the .lo file + func_source "$arg" + + if test -z "$pic_object" || + test -z "$non_pic_object" || + test none = "$pic_object" && + test none = "$non_pic_object"; then + func_fatal_error "cannot find name of object for '$arg'" + fi + + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + test none = "$pic_object" || { + # Prepend the subdirectory the object is found in. + pic_object=$xdir$pic_object + + if test dlfiles = "$prev"; then + if test yes = "$build_libtool_libs" && test yes = "$dlopen_support"; then + func_append dlfiles " $pic_object" + prev= + continue + else + # If libtool objects are unsupported, then we need to preload. + prev=dlprefiles + fi + fi + + # CHECK ME: I think I busted this. -Ossama + if test dlprefiles = "$prev"; then + # Preload the old-style object. + func_append dlprefiles " $pic_object" + prev= + fi + + # A PIC object. + func_append libobjs " $pic_object" + arg=$pic_object + } + + # Non-PIC object. + if test none != "$non_pic_object"; then + # Prepend the subdirectory the object is found in. + non_pic_object=$xdir$non_pic_object + + # A standard non-PIC object + func_append non_pic_objects " $non_pic_object" + if test -z "$pic_object" || test none = "$pic_object"; then + arg=$non_pic_object + fi + else + # If the PIC object exists, use it instead. + # $xdir was prepended to $pic_object above. + non_pic_object=$pic_object + func_append non_pic_objects " $non_pic_object" + fi + else + # Only an error if not doing a dry-run. + if $opt_dry_run; then + # Extract subdirectory from the argument. + func_dirname "$arg" "/" "" + xdir=$func_dirname_result + + func_lo2o "$arg" + pic_object=$xdir$objdir/$func_lo2o_result + non_pic_object=$xdir$func_lo2o_result + func_append libobjs " $pic_object" + func_append non_pic_objects " $non_pic_object" + else + func_fatal_error "'$arg' is not a valid libtool object" + fi + fi + ;; + + *.$libext) + # An archive. + func_append deplibs " $arg" + func_append old_deplibs " $arg" + continue + ;; + + *.la) + # A libtool-controlled library. + + func_resolve_sysroot "$arg" + if test dlfiles = "$prev"; then + # This library was specified with -dlopen. + func_append dlfiles " $func_resolve_sysroot_result" + prev= + elif test dlprefiles = "$prev"; then + # The library was specified with -dlpreopen. + func_append dlprefiles " $func_resolve_sysroot_result" + prev= + else + func_append deplibs " $func_resolve_sysroot_result" + fi + continue + ;; + + # Some other compiler argument. + *) + # Unknown arguments in both finalize_command and compile_command need + # to be aesthetically quoted because they are evaled later. + func_quote_for_eval "$arg" + arg=$func_quote_for_eval_result + ;; + esac # arg + + # Now actually substitute the argument into the commands. + if test -n "$arg"; then + func_append compile_command " $arg" + func_append finalize_command " $arg" + fi + done # argument parsing loop + + test -n "$prev" && \ + func_fatal_help "the '$prevarg' option requires an argument" + + if test yes = "$export_dynamic" && test -n "$export_dynamic_flag_spec"; then + eval arg=\"$export_dynamic_flag_spec\" + func_append compile_command " $arg" + func_append finalize_command " $arg" + fi + + oldlibs= + # calculate the name of the file, without its directory + func_basename "$output" + outputname=$func_basename_result + libobjs_save=$libobjs + + if test -n "$shlibpath_var"; then + # get the directories listed in $shlibpath_var + eval shlib_search_path=\`\$ECHO \"\$$shlibpath_var\" \| \$SED \'s/:/ /g\'\` + else + shlib_search_path= + fi + eval sys_lib_search_path=\"$sys_lib_search_path_spec\" + eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\" + + # Definition is injected by LT_CONFIG during libtool generation. + func_munge_path_list sys_lib_dlsearch_path "$LT_SYS_LIBRARY_PATH" + + func_dirname "$output" "/" "" + output_objdir=$func_dirname_result$objdir + func_to_tool_file "$output_objdir/" + tool_output_objdir=$func_to_tool_file_result + # Create the object directory. + func_mkdir_p "$output_objdir" + + # Determine the type of output + case $output in + "") + func_fatal_help "you must specify an output file" + ;; + *.$libext) linkmode=oldlib ;; + *.lo | *.$objext) linkmode=obj ;; + *.la) linkmode=lib ;; + *) linkmode=prog ;; # Anything else should be a program. + esac + + specialdeplibs= + + libs= + # Find all interdependent deplibs by searching for libraries + # that are linked more than once (e.g. -la -lb -la) + for deplib in $deplibs; do + if $opt_preserve_dup_deps; then + case "$libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append libs " $deplib" + done + + if test lib = "$linkmode"; then + libs="$predeps $libs $compiler_lib_search_path $postdeps" + + # Compute libraries that are listed more than once in $predeps + # $postdeps and mark them as special (i.e., whose duplicates are + # not to be eliminated). + pre_post_deps= + if $opt_duplicate_compiler_generated_deps; then + for pre_post_dep in $predeps $postdeps; do + case "$pre_post_deps " in + *" $pre_post_dep "*) func_append specialdeplibs " $pre_post_deps" ;; + esac + func_append pre_post_deps " $pre_post_dep" + done + fi + pre_post_deps= + fi + + deplibs= + newdependency_libs= + newlib_search_path= + need_relink=no # whether we're linking any uninstalled libtool libraries + notinst_deplibs= # not-installed libtool libraries + notinst_path= # paths that contain not-installed libtool libraries + + case $linkmode in + lib) + passes="conv dlpreopen link" + for file in $dlfiles $dlprefiles; do + case $file in + *.la) ;; + *) + func_fatal_help "libraries can '-dlopen' only libtool libraries: $file" + ;; + esac + done + ;; + prog) + compile_deplibs= + finalize_deplibs= + alldeplibs=false + newdlfiles= + newdlprefiles= + passes="conv scan dlopen dlpreopen link" + ;; + *) passes="conv" + ;; + esac + + for pass in $passes; do + # The preopen pass in lib mode reverses $deplibs; put it back here + # so that -L comes before libs that need it for instance... + if test lib,link = "$linkmode,$pass"; then + ## FIXME: Find the place where the list is rebuilt in the wrong + ## order, and fix it there properly + tmp_deplibs= + for deplib in $deplibs; do + tmp_deplibs="$deplib $tmp_deplibs" + done + deplibs=$tmp_deplibs + fi + + if test lib,link = "$linkmode,$pass" || + test prog,scan = "$linkmode,$pass"; then + libs=$deplibs + deplibs= + fi + if test prog = "$linkmode"; then + case $pass in + dlopen) libs=$dlfiles ;; + dlpreopen) libs=$dlprefiles ;; + link) + libs="$deplibs %DEPLIBS%" + test "X$link_all_deplibs" != Xno && libs="$libs $dependency_libs" + ;; + esac + fi + if test lib,dlpreopen = "$linkmode,$pass"; then + # Collect and forward deplibs of preopened libtool libs + for lib in $dlprefiles; do + # Ignore non-libtool-libs + dependency_libs= + func_resolve_sysroot "$lib" + case $lib in + *.la) func_source "$func_resolve_sysroot_result" ;; + esac + + # Collect preopened libtool deplibs, except any this library + # has declared as weak libs + for deplib in $dependency_libs; do + func_basename "$deplib" + deplib_base=$func_basename_result + case " $weak_libs " in + *" $deplib_base "*) ;; + *) func_append deplibs " $deplib" ;; + esac + done + done + libs=$dlprefiles + fi + if test dlopen = "$pass"; then + # Collect dlpreopened libraries + save_deplibs=$deplibs + deplibs= + fi + + for deplib in $libs; do + lib= + found=false + case $deplib in + -mt|-mthreads|-kthread|-Kthread|-pthread|-pthreads|--thread-safe \ + |-threads|-fopenmp|-openmp|-mp|-xopenmp|-omp|-qsmp=*) + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + func_append compiler_flags " $deplib" + if test lib = "$linkmode"; then + case "$new_inherited_linker_flags " in + *" $deplib "*) ;; + * ) func_append new_inherited_linker_flags " $deplib" ;; + esac + fi + fi + continue + ;; + -l*) + if test lib != "$linkmode" && test prog != "$linkmode"; then + func_warning "'-l' is ignored for archives/objects" + continue + fi + func_stripname '-l' '' "$deplib" + name=$func_stripname_result + if test lib = "$linkmode"; then + searchdirs="$newlib_search_path $lib_search_path $compiler_lib_search_dirs $sys_lib_search_path $shlib_search_path" + else + searchdirs="$newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path" + fi + for searchdir in $searchdirs; do + for search_ext in .la $std_shrext .so .a; do + # Search the libtool library + lib=$searchdir/lib$name$search_ext + if test -f "$lib"; then + if test .la = "$search_ext"; then + found=: + else + found=false + fi + break 2 + fi + done + done + if $found; then + # deplib is a libtool library + # If $allow_libtool_libs_with_static_runtimes && $deplib is a stdlib, + # We need to do some special things here, and not later. + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $deplib "*) + if func_lalib_p "$lib"; then + library_names= + old_library= + func_source "$lib" + for l in $old_library $library_names; do + ll=$l + done + if test "X$ll" = "X$old_library"; then # only static version available + found=false + func_dirname "$lib" "" "." + ladir=$func_dirname_result + lib=$ladir/$old_library + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs" + fi + continue + fi + fi + ;; + *) ;; + esac + fi + else + # deplib doesn't seem to be a libtool library + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + test lib = "$linkmode" && newdependency_libs="$deplib $newdependency_libs" + fi + continue + fi + ;; # -l + *.ltframework) + if test prog,link = "$linkmode,$pass"; then + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + deplibs="$deplib $deplibs" + if test lib = "$linkmode"; then + case "$new_inherited_linker_flags " in + *" $deplib "*) ;; + * ) func_append new_inherited_linker_flags " $deplib" ;; + esac + fi + fi + continue + ;; + -L*) + case $linkmode in + lib) + deplibs="$deplib $deplibs" + test conv = "$pass" && continue + newdependency_libs="$deplib $newdependency_libs" + func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + prog) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + continue + fi + if test scan = "$pass"; then + deplibs="$deplib $deplibs" + else + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + fi + func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + *) + func_warning "'-L' is ignored for archives/objects" + ;; + esac # linkmode + continue + ;; # -L + -R*) + if test link = "$pass"; then + func_stripname '-R' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + dir=$func_resolve_sysroot_result + # Make sure the xrpath contains only unique directories. + case "$xrpath " in + *" $dir "*) ;; + *) func_append xrpath " $dir" ;; + esac + fi + deplibs="$deplib $deplibs" + continue + ;; + *.la) + func_resolve_sysroot "$deplib" + lib=$func_resolve_sysroot_result + ;; + *.$libext) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + continue + fi + case $linkmode in + lib) + # Linking convenience modules into shared libraries is allowed, + # but linking other static libraries is non-portable. + case " $dlpreconveniencelibs " in + *" $deplib "*) ;; + *) + valid_a_lib=false + case $deplibs_check_method in + match_pattern*) + set dummy $deplibs_check_method; shift + match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` + if eval "\$ECHO \"$deplib\"" 2>/dev/null | $SED 10q \ + | $EGREP "$match_pattern_regex" > /dev/null; then + valid_a_lib=: + fi + ;; + pass_all) + valid_a_lib=: + ;; + esac + if $valid_a_lib; then + echo + $ECHO "*** Warning: Linking the shared library $output against the" + $ECHO "*** static library $deplib is not portable!" + deplibs="$deplib $deplibs" + else + echo + $ECHO "*** Warning: Trying to link with static lib archive $deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because the file extensions .$libext of this argument makes me believe" + echo "*** that it is just a static archive that I should not use here." + fi + ;; + esac + continue + ;; + prog) + if test link != "$pass"; then + deplibs="$deplib $deplibs" + else + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + fi + continue + ;; + esac # linkmode + ;; # *.$libext + *.lo | *.$objext) + if test conv = "$pass"; then + deplibs="$deplib $deplibs" + elif test prog = "$linkmode"; then + if test dlpreopen = "$pass" || test yes != "$dlopen_support" || test no = "$build_libtool_libs"; then + # If there is no dlopen support or we're linking statically, + # we need to preload. + func_append newdlprefiles " $deplib" + compile_deplibs="$deplib $compile_deplibs" + finalize_deplibs="$deplib $finalize_deplibs" + else + func_append newdlfiles " $deplib" + fi + fi + continue + ;; + %DEPLIBS%) + alldeplibs=: + continue + ;; + esac # case $deplib + + $found || test -f "$lib" \ + || func_fatal_error "cannot find the library '$lib' or unhandled argument '$deplib'" + + # Check to see that this really is a libtool archive. + func_lalib_unsafe_p "$lib" \ + || func_fatal_error "'$lib' is not a valid libtool archive" + + func_dirname "$lib" "" "." + ladir=$func_dirname_result + + dlname= + dlopen= + dlpreopen= + libdir= + library_names= + old_library= + inherited_linker_flags= + # If the library was installed with an old release of libtool, + # it will not redefine variables installed, or shouldnotlink + installed=yes + shouldnotlink=no + avoidtemprpath= + + + # Read the .la file + func_source "$lib" + + # Convert "-framework foo" to "foo.ltframework" + if test -n "$inherited_linker_flags"; then + tmp_inherited_linker_flags=`$ECHO "$inherited_linker_flags" | $SED 's/-framework \([^ $]*\)/\1.ltframework/g'` + for tmp_inherited_linker_flag in $tmp_inherited_linker_flags; do + case " $new_inherited_linker_flags " in + *" $tmp_inherited_linker_flag "*) ;; + *) func_append new_inherited_linker_flags " $tmp_inherited_linker_flag";; + esac + done + fi + dependency_libs=`$ECHO " $dependency_libs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + if test lib,link = "$linkmode,$pass" || + test prog,scan = "$linkmode,$pass" || + { test prog != "$linkmode" && test lib != "$linkmode"; }; then + test -n "$dlopen" && func_append dlfiles " $dlopen" + test -n "$dlpreopen" && func_append dlprefiles " $dlpreopen" + fi + + if test conv = "$pass"; then + # Only check for convenience libraries + deplibs="$lib $deplibs" + if test -z "$libdir"; then + if test -z "$old_library"; then + func_fatal_error "cannot find name of link library for '$lib'" + fi + # It is a libtool convenience library, so add in its objects. + func_append convenience " $ladir/$objdir/$old_library" + func_append old_convenience " $ladir/$objdir/$old_library" + tmp_libs= + for deplib in $dependency_libs; do + deplibs="$deplib $deplibs" + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append tmp_libs " $deplib" + done + elif test prog != "$linkmode" && test lib != "$linkmode"; then + func_fatal_error "'$lib' is not a convenience library" + fi + continue + fi # $pass = conv + + + # Get the name of the library we link against. + linklib= + if test -n "$old_library" && + { test yes = "$prefer_static_libs" || + test built,no = "$prefer_static_libs,$installed"; }; then + linklib=$old_library + else + for l in $old_library $library_names; do + linklib=$l + done + fi + if test -z "$linklib"; then + func_fatal_error "cannot find name of link library for '$lib'" + fi + + # This library was specified with -dlopen. + if test dlopen = "$pass"; then + test -z "$libdir" \ + && func_fatal_error "cannot -dlopen a convenience library: '$lib'" + if test -z "$dlname" || + test yes != "$dlopen_support" || + test no = "$build_libtool_libs" + then + # If there is no dlname, no dlopen support or we're linking + # statically, we need to preload. We also need to preload any + # dependent libraries so libltdl's deplib preloader doesn't + # bomb out in the load deplibs phase. + func_append dlprefiles " $lib $dependency_libs" + else + func_append newdlfiles " $lib" + fi + continue + fi # $pass = dlopen + + # We need an absolute path. + case $ladir in + [\\/]* | [A-Za-z]:[\\/]*) abs_ladir=$ladir ;; + *) + abs_ladir=`cd "$ladir" && pwd` + if test -z "$abs_ladir"; then + func_warning "cannot determine absolute directory name of '$ladir'" + func_warning "passing it literally to the linker, although it might fail" + abs_ladir=$ladir + fi + ;; + esac + func_basename "$lib" + laname=$func_basename_result + + # Find the relevant object directory and library name. + if test yes = "$installed"; then + if test ! -f "$lt_sysroot$libdir/$linklib" && test -f "$abs_ladir/$linklib"; then + func_warning "library '$lib' was moved." + dir=$ladir + absdir=$abs_ladir + libdir=$abs_ladir + else + dir=$lt_sysroot$libdir + absdir=$lt_sysroot$libdir + fi + test yes = "$hardcode_automatic" && avoidtemprpath=yes + else + if test ! -f "$ladir/$objdir/$linklib" && test -f "$abs_ladir/$linklib"; then + dir=$ladir + absdir=$abs_ladir + # Remove this search path later + func_append notinst_path " $abs_ladir" + else + dir=$ladir/$objdir + absdir=$abs_ladir/$objdir + # Remove this search path later + func_append notinst_path " $abs_ladir" + fi + fi # $installed = yes + func_stripname 'lib' '.la' "$laname" + name=$func_stripname_result + + # This library was specified with -dlpreopen. + if test dlpreopen = "$pass"; then + if test -z "$libdir" && test prog = "$linkmode"; then + func_fatal_error "only libraries may -dlpreopen a convenience library: '$lib'" + fi + case $host in + # special handling for platforms with PE-DLLs. + *cygwin* | *mingw* | *cegcc* ) + # Linker will automatically link against shared library if both + # static and shared are present. Therefore, ensure we extract + # symbols from the import library if a shared library is present + # (otherwise, the dlopen module name will be incorrect). We do + # this by putting the import library name into $newdlprefiles. + # We recover the dlopen module name by 'saving' the la file + # name in a special purpose variable, and (later) extracting the + # dlname from the la file. + if test -n "$dlname"; then + func_tr_sh "$dir/$linklib" + eval "libfile_$func_tr_sh_result=\$abs_ladir/\$laname" + func_append newdlprefiles " $dir/$linklib" + else + func_append newdlprefiles " $dir/$old_library" + # Keep a list of preopened convenience libraries to check + # that they are being used correctly in the link pass. + test -z "$libdir" && \ + func_append dlpreconveniencelibs " $dir/$old_library" + fi + ;; + * ) + # Prefer using a static library (so that no silly _DYNAMIC symbols + # are required to link). + if test -n "$old_library"; then + func_append newdlprefiles " $dir/$old_library" + # Keep a list of preopened convenience libraries to check + # that they are being used correctly in the link pass. + test -z "$libdir" && \ + func_append dlpreconveniencelibs " $dir/$old_library" + # Otherwise, use the dlname, so that lt_dlopen finds it. + elif test -n "$dlname"; then + func_append newdlprefiles " $dir/$dlname" + else + func_append newdlprefiles " $dir/$linklib" + fi + ;; + esac + fi # $pass = dlpreopen + + if test -z "$libdir"; then + # Link the convenience library + if test lib = "$linkmode"; then + deplibs="$dir/$old_library $deplibs" + elif test prog,link = "$linkmode,$pass"; then + compile_deplibs="$dir/$old_library $compile_deplibs" + finalize_deplibs="$dir/$old_library $finalize_deplibs" + else + deplibs="$lib $deplibs" # used for prog,scan pass + fi + continue + fi + + + if test prog = "$linkmode" && test link != "$pass"; then + func_append newlib_search_path " $ladir" + deplibs="$lib $deplibs" + + linkalldeplibs=false + if test no != "$link_all_deplibs" || test -z "$library_names" || + test no = "$build_libtool_libs"; then + linkalldeplibs=: + fi + + tmp_libs= + for deplib in $dependency_libs; do + case $deplib in + -L*) func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result" + func_append newlib_search_path " $func_resolve_sysroot_result" + ;; + esac + # Need to link against all dependency_libs? + if $linkalldeplibs; then + deplibs="$deplib $deplibs" + else + # Need to hardcode shared library paths + # or/and link against static libraries + newdependency_libs="$deplib $newdependency_libs" + fi + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $deplib "*) func_append specialdeplibs " $deplib" ;; + esac + fi + func_append tmp_libs " $deplib" + done # for deplib + continue + fi # $linkmode = prog... + + if test prog,link = "$linkmode,$pass"; then + if test -n "$library_names" && + { { test no = "$prefer_static_libs" || + test built,yes = "$prefer_static_libs,$installed"; } || + test -z "$old_library"; }; then + # We need to hardcode the library path + if test -n "$shlibpath_var" && test -z "$avoidtemprpath"; then + # Make sure the rpath contains only unique directories. + case $temp_rpath: in + *"$absdir:"*) ;; + *) func_append temp_rpath "$absdir:" ;; + esac + fi + + # Hardcode the library path. + # Skip directories that are in the system default run-time + # search path. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) func_append compile_rpath " $absdir" ;; + esac + ;; + esac + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + ;; + esac + fi # $linkmode,$pass = prog,link... + + if $alldeplibs && + { test pass_all = "$deplibs_check_method" || + { test yes = "$build_libtool_libs" && + test -n "$library_names"; }; }; then + # We only need to search for static libraries + continue + fi + fi + + link_static=no # Whether the deplib will be linked statically + use_static_libs=$prefer_static_libs + if test built = "$use_static_libs" && test yes = "$installed"; then + use_static_libs=no + fi + if test -n "$library_names" && + { test no = "$use_static_libs" || test -z "$old_library"; }; then + case $host in + *cygwin* | *mingw* | *cegcc* | *os2*) + # No point in relinking DLLs because paths are not encoded + func_append notinst_deplibs " $lib" + need_relink=no + ;; + *) + if test no = "$installed"; then + func_append notinst_deplibs " $lib" + need_relink=yes + fi + ;; + esac + # This is a shared library + + # Warn about portability, can't link against -module's on some + # systems (darwin). Don't bleat about dlopened modules though! + dlopenmodule= + for dlpremoduletest in $dlprefiles; do + if test "X$dlpremoduletest" = "X$lib"; then + dlopenmodule=$dlpremoduletest + break + fi + done + if test -z "$dlopenmodule" && test yes = "$shouldnotlink" && test link = "$pass"; then + echo + if test prog = "$linkmode"; then + $ECHO "*** Warning: Linking the executable $output against the loadable module" + else + $ECHO "*** Warning: Linking the shared library $output against the loadable module" + fi + $ECHO "*** $linklib is not portable!" + fi + if test lib = "$linkmode" && + test yes = "$hardcode_into_libs"; then + # Hardcode the library path. + # Skip directories that are in the system default run-time + # search path. + case " $sys_lib_dlsearch_path " in + *" $absdir "*) ;; + *) + case "$compile_rpath " in + *" $absdir "*) ;; + *) func_append compile_rpath " $absdir" ;; + esac + ;; + esac + case " $sys_lib_dlsearch_path " in + *" $libdir "*) ;; + *) + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + ;; + esac + fi + + if test -n "$old_archive_from_expsyms_cmds"; then + # figure out the soname + set dummy $library_names + shift + realname=$1 + shift + libname=`eval "\\$ECHO \"$libname_spec\""` + # use dlname if we got it. it's perfectly good, no? + if test -n "$dlname"; then + soname=$dlname + elif test -n "$soname_spec"; then + # bleh windows + case $host in + *cygwin* | mingw* | *cegcc* | *os2*) + func_arith $current - $age + major=$func_arith_result + versuffix=-$major + ;; + esac + eval soname=\"$soname_spec\" + else + soname=$realname + fi + + # Make a new name for the extract_expsyms_cmds to use + soroot=$soname + func_basename "$soroot" + soname=$func_basename_result + func_stripname 'lib' '.dll' "$soname" + newlib=libimp-$func_stripname_result.a + + # If the library has no export list, then create one now + if test -f "$output_objdir/$soname-def"; then : + else + func_verbose "extracting exported symbol list from '$soname'" + func_execute_cmds "$extract_expsyms_cmds" 'exit $?' + fi + + # Create $newlib + if test -f "$output_objdir/$newlib"; then :; else + func_verbose "generating import library for '$soname'" + func_execute_cmds "$old_archive_from_expsyms_cmds" 'exit $?' + fi + # make sure the library variables are pointing to the new library + dir=$output_objdir + linklib=$newlib + fi # test -n "$old_archive_from_expsyms_cmds" + + if test prog = "$linkmode" || test relink != "$opt_mode"; then + add_shlibpath= + add_dir= + add= + lib_linked=yes + case $hardcode_action in + immediate | unsupported) + if test no = "$hardcode_direct"; then + add=$dir/$linklib + case $host in + *-*-sco3.2v5.0.[024]*) add_dir=-L$dir ;; + *-*-sysv4*uw2*) add_dir=-L$dir ;; + *-*-sysv5OpenUNIX* | *-*-sysv5UnixWare7.[01].[10]* | \ + *-*-unixware7*) add_dir=-L$dir ;; + *-*-darwin* ) + # if the lib is a (non-dlopened) module then we cannot + # link against it, someone is ignoring the earlier warnings + if /usr/bin/file -L $add 2> /dev/null | + $GREP ": [^:]* bundle" >/dev/null; then + if test "X$dlopenmodule" != "X$lib"; then + $ECHO "*** Warning: lib $linklib is a module, not a shared library" + if test -z "$old_library"; then + echo + echo "*** And there doesn't seem to be a static archive available" + echo "*** The link will probably fail, sorry" + else + add=$dir/$old_library + fi + elif test -n "$old_library"; then + add=$dir/$old_library + fi + fi + esac + elif test no = "$hardcode_minus_L"; then + case $host in + *-*-sunos*) add_shlibpath=$dir ;; + esac + add_dir=-L$dir + add=-l$name + elif test no = "$hardcode_shlibpath_var"; then + add_shlibpath=$dir + add=-l$name + else + lib_linked=no + fi + ;; + relink) + if test yes = "$hardcode_direct" && + test no = "$hardcode_direct_absolute"; then + add=$dir/$linklib + elif test yes = "$hardcode_minus_L"; then + add_dir=-L$absdir + # Try looking first in the location we're being installed to. + if test -n "$inst_prefix_dir"; then + case $libdir in + [\\/]*) + func_append add_dir " -L$inst_prefix_dir$libdir" + ;; + esac + fi + add=-l$name + elif test yes = "$hardcode_shlibpath_var"; then + add_shlibpath=$dir + add=-l$name + else + lib_linked=no + fi + ;; + *) lib_linked=no ;; + esac + + if test yes != "$lib_linked"; then + func_fatal_configuration "unsupported hardcode properties" + fi + + if test -n "$add_shlibpath"; then + case :$compile_shlibpath: in + *":$add_shlibpath:"*) ;; + *) func_append compile_shlibpath "$add_shlibpath:" ;; + esac + fi + if test prog = "$linkmode"; then + test -n "$add_dir" && compile_deplibs="$add_dir $compile_deplibs" + test -n "$add" && compile_deplibs="$add $compile_deplibs" + else + test -n "$add_dir" && deplibs="$add_dir $deplibs" + test -n "$add" && deplibs="$add $deplibs" + if test yes != "$hardcode_direct" && + test yes != "$hardcode_minus_L" && + test yes = "$hardcode_shlibpath_var"; then + case :$finalize_shlibpath: in + *":$libdir:"*) ;; + *) func_append finalize_shlibpath "$libdir:" ;; + esac + fi + fi + fi + + if test prog = "$linkmode" || test relink = "$opt_mode"; then + add_shlibpath= + add_dir= + add= + # Finalize command for both is simple: just hardcode it. + if test yes = "$hardcode_direct" && + test no = "$hardcode_direct_absolute"; then + add=$libdir/$linklib + elif test yes = "$hardcode_minus_L"; then + add_dir=-L$libdir + add=-l$name + elif test yes = "$hardcode_shlibpath_var"; then + case :$finalize_shlibpath: in + *":$libdir:"*) ;; + *) func_append finalize_shlibpath "$libdir:" ;; + esac + add=-l$name + elif test yes = "$hardcode_automatic"; then + if test -n "$inst_prefix_dir" && + test -f "$inst_prefix_dir$libdir/$linklib"; then + add=$inst_prefix_dir$libdir/$linklib + else + add=$libdir/$linklib + fi + else + # We cannot seem to hardcode it, guess we'll fake it. + add_dir=-L$libdir + # Try looking first in the location we're being installed to. + if test -n "$inst_prefix_dir"; then + case $libdir in + [\\/]*) + func_append add_dir " -L$inst_prefix_dir$libdir" + ;; + esac + fi + add=-l$name + fi + + if test prog = "$linkmode"; then + test -n "$add_dir" && finalize_deplibs="$add_dir $finalize_deplibs" + test -n "$add" && finalize_deplibs="$add $finalize_deplibs" + else + test -n "$add_dir" && deplibs="$add_dir $deplibs" + test -n "$add" && deplibs="$add $deplibs" + fi + fi + elif test prog = "$linkmode"; then + # Here we assume that one of hardcode_direct or hardcode_minus_L + # is not unsupported. This is valid on all known static and + # shared platforms. + if test unsupported != "$hardcode_direct"; then + test -n "$old_library" && linklib=$old_library + compile_deplibs="$dir/$linklib $compile_deplibs" + finalize_deplibs="$dir/$linklib $finalize_deplibs" + else + compile_deplibs="-l$name -L$dir $compile_deplibs" + finalize_deplibs="-l$name -L$dir $finalize_deplibs" + fi + elif test yes = "$build_libtool_libs"; then + # Not a shared library + if test pass_all != "$deplibs_check_method"; then + # We're trying link a shared library against a static one + # but the system doesn't support it. + + # Just print a warning and add the library to dependency_libs so + # that the program can be linked against the static library. + echo + $ECHO "*** Warning: This system cannot link to static lib archive $lib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have." + if test yes = "$module"; then + echo "*** But as you try to build a module library, libtool will still create " + echo "*** a static module, that should work as long as the dlopening application" + echo "*** is linked with the -dlopen flag to resolve symbols at runtime." + if test -z "$global_symbol_pipe"; then + echo + echo "*** However, this would only work if libtool was able to extract symbol" + echo "*** lists from a program, using 'nm' or equivalent, but libtool could" + echo "*** not find such a program. So, this module is probably useless." + echo "*** 'nm' from GNU binutils and a full rebuild may help." + fi + if test no = "$build_old_libs"; then + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + fi + else + deplibs="$dir/$old_library $deplibs" + link_static=yes + fi + fi # link shared/static library? + + if test lib = "$linkmode"; then + if test -n "$dependency_libs" && + { test yes != "$hardcode_into_libs" || + test yes = "$build_old_libs" || + test yes = "$link_static"; }; then + # Extract -R from dependency_libs + temp_deplibs= + for libdir in $dependency_libs; do + case $libdir in + -R*) func_stripname '-R' '' "$libdir" + temp_xrpath=$func_stripname_result + case " $xrpath " in + *" $temp_xrpath "*) ;; + *) func_append xrpath " $temp_xrpath";; + esac;; + *) func_append temp_deplibs " $libdir";; + esac + done + dependency_libs=$temp_deplibs + fi + + func_append newlib_search_path " $absdir" + # Link against this library + test no = "$link_static" && newdependency_libs="$abs_ladir/$laname $newdependency_libs" + # ... and its dependency_libs + tmp_libs= + for deplib in $dependency_libs; do + newdependency_libs="$deplib $newdependency_libs" + case $deplib in + -L*) func_stripname '-L' '' "$deplib" + func_resolve_sysroot "$func_stripname_result";; + *) func_resolve_sysroot "$deplib" ;; + esac + if $opt_preserve_dup_deps; then + case "$tmp_libs " in + *" $func_resolve_sysroot_result "*) + func_append specialdeplibs " $func_resolve_sysroot_result" ;; + esac + fi + func_append tmp_libs " $func_resolve_sysroot_result" + done + + if test no != "$link_all_deplibs"; then + # Add the search paths of all dependency libraries + for deplib in $dependency_libs; do + path= + case $deplib in + -L*) path=$deplib ;; + *.la) + func_resolve_sysroot "$deplib" + deplib=$func_resolve_sysroot_result + func_dirname "$deplib" "" "." + dir=$func_dirname_result + # We need an absolute path. + case $dir in + [\\/]* | [A-Za-z]:[\\/]*) absdir=$dir ;; + *) + absdir=`cd "$dir" && pwd` + if test -z "$absdir"; then + func_warning "cannot determine absolute directory name of '$dir'" + absdir=$dir + fi + ;; + esac + if $GREP "^installed=no" $deplib > /dev/null; then + case $host in + *-*-darwin*) + depdepl= + eval deplibrary_names=`$SED -n -e 's/^library_names=\(.*\)$/\1/p' $deplib` + if test -n "$deplibrary_names"; then + for tmp in $deplibrary_names; do + depdepl=$tmp + done + if test -f "$absdir/$objdir/$depdepl"; then + depdepl=$absdir/$objdir/$depdepl + darwin_install_name=`$OTOOL -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'` + if test -z "$darwin_install_name"; then + darwin_install_name=`$OTOOL64 -L $depdepl | awk '{if (NR == 2) {print $1;exit}}'` + fi + func_append compiler_flags " $wl-dylib_file $wl$darwin_install_name:$depdepl" + func_append linker_flags " -dylib_file $darwin_install_name:$depdepl" + path= + fi + fi + ;; + *) + path=-L$absdir/$objdir + ;; + esac + else + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $deplib` + test -z "$libdir" && \ + func_fatal_error "'$deplib' is not a valid libtool archive" + test "$absdir" != "$libdir" && \ + func_warning "'$deplib' seems to be moved" + + path=-L$absdir + fi + ;; + esac + case " $deplibs " in + *" $path "*) ;; + *) deplibs="$path $deplibs" ;; + esac + done + fi # link_all_deplibs != no + fi # linkmode = lib + done # for deplib in $libs + if test link = "$pass"; then + if test prog = "$linkmode"; then + compile_deplibs="$new_inherited_linker_flags $compile_deplibs" + finalize_deplibs="$new_inherited_linker_flags $finalize_deplibs" + else + compiler_flags="$compiler_flags "`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + fi + fi + dependency_libs=$newdependency_libs + if test dlpreopen = "$pass"; then + # Link the dlpreopened libraries before other libraries + for deplib in $save_deplibs; do + deplibs="$deplib $deplibs" + done + fi + if test dlopen != "$pass"; then + test conv = "$pass" || { + # Make sure lib_search_path contains only unique directories. + lib_search_path= + for dir in $newlib_search_path; do + case "$lib_search_path " in + *" $dir "*) ;; + *) func_append lib_search_path " $dir" ;; + esac + done + newlib_search_path= + } + + if test prog,link = "$linkmode,$pass"; then + vars="compile_deplibs finalize_deplibs" + else + vars=deplibs + fi + for var in $vars dependency_libs; do + # Add libraries to $var in reverse order + eval tmp_libs=\"\$$var\" + new_libs= + for deplib in $tmp_libs; do + # FIXME: Pedantically, this is the right thing to do, so + # that some nasty dependency loop isn't accidentally + # broken: + #new_libs="$deplib $new_libs" + # Pragmatically, this seems to cause very few problems in + # practice: + case $deplib in + -L*) new_libs="$deplib $new_libs" ;; + -R*) ;; + *) + # And here is the reason: when a library appears more + # than once as an explicit dependence of a library, or + # is implicitly linked in more than once by the + # compiler, it is considered special, and multiple + # occurrences thereof are not removed. Compare this + # with having the same library being listed as a + # dependency of multiple other libraries: in this case, + # we know (pedantically, we assume) the library does not + # need to be listed more than once, so we keep only the + # last copy. This is not always right, but it is rare + # enough that we require users that really mean to play + # such unportable linking tricks to link the library + # using -Wl,-lname, so that libtool does not consider it + # for duplicate removal. + case " $specialdeplibs " in + *" $deplib "*) new_libs="$deplib $new_libs" ;; + *) + case " $new_libs " in + *" $deplib "*) ;; + *) new_libs="$deplib $new_libs" ;; + esac + ;; + esac + ;; + esac + done + tmp_libs= + for deplib in $new_libs; do + case $deplib in + -L*) + case " $tmp_libs " in + *" $deplib "*) ;; + *) func_append tmp_libs " $deplib" ;; + esac + ;; + *) func_append tmp_libs " $deplib" ;; + esac + done + eval $var=\"$tmp_libs\" + done # for var + fi + + # Add Sun CC postdeps if required: + test CXX = "$tagname" && { + case $host_os in + linux*) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C++ 5.9 + func_suncc_cstd_abi + + if test no != "$suncc_use_cstd_abi"; then + func_append postdeps ' -library=Cstd -library=Crun' + fi + ;; + esac + ;; + + solaris*) + func_cc_basename "$CC" + case $func_cc_basename_result in + CC* | sunCC*) + func_suncc_cstd_abi + + if test no != "$suncc_use_cstd_abi"; then + func_append postdeps ' -library=Cstd -library=Crun' + fi + ;; + esac + ;; + esac + } + + # Last step: remove runtime libs from dependency_libs + # (they stay in deplibs) + tmp_libs= + for i in $dependency_libs; do + case " $predeps $postdeps $compiler_lib_search_path " in + *" $i "*) + i= + ;; + esac + if test -n "$i"; then + func_append tmp_libs " $i" + fi + done + dependency_libs=$tmp_libs + done # for pass + if test prog = "$linkmode"; then + dlfiles=$newdlfiles + fi + if test prog = "$linkmode" || test lib = "$linkmode"; then + dlprefiles=$newdlprefiles + fi + + case $linkmode in + oldlib) + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + func_warning "'-dlopen' is ignored for archives" + fi + + case " $deplibs" in + *\ -l* | *\ -L*) + func_warning "'-l' and '-L' are ignored for archives" ;; + esac + + test -n "$rpath" && \ + func_warning "'-rpath' is ignored for archives" + + test -n "$xrpath" && \ + func_warning "'-R' is ignored for archives" + + test -n "$vinfo" && \ + func_warning "'-version-info/-version-number' is ignored for archives" + + test -n "$release" && \ + func_warning "'-release' is ignored for archives" + + test -n "$export_symbols$export_symbols_regex" && \ + func_warning "'-export-symbols' is ignored for archives" + + # Now set the variables for building old libraries. + build_libtool_libs=no + oldlibs=$output + func_append objs "$old_deplibs" + ;; + + lib) + # Make sure we only generate libraries of the form 'libNAME.la'. + case $outputname in + lib*) + func_stripname 'lib' '.la' "$outputname" + name=$func_stripname_result + eval shared_ext=\"$shrext_cmds\" + eval libname=\"$libname_spec\" + ;; + *) + test no = "$module" \ + && func_fatal_help "libtool library '$output' must begin with 'lib'" + + if test no != "$need_lib_prefix"; then + # Add the "lib" prefix for modules if required + func_stripname '' '.la' "$outputname" + name=$func_stripname_result + eval shared_ext=\"$shrext_cmds\" + eval libname=\"$libname_spec\" + else + func_stripname '' '.la' "$outputname" + libname=$func_stripname_result + fi + ;; + esac + + if test -n "$objs"; then + if test pass_all != "$deplibs_check_method"; then + func_fatal_error "cannot build libtool library '$output' from non-libtool objects on this host:$objs" + else + echo + $ECHO "*** Warning: Linking the shared library $output against the non-libtool" + $ECHO "*** objects $objs is not portable!" + func_append libobjs " $objs" + fi + fi + + test no = "$dlself" \ + || func_warning "'-dlopen self' is ignored for libtool libraries" + + set dummy $rpath + shift + test 1 -lt "$#" \ + && func_warning "ignoring multiple '-rpath's for a libtool library" + + install_libdir=$1 + + oldlibs= + if test -z "$rpath"; then + if test yes = "$build_libtool_libs"; then + # Building a libtool convenience library. + # Some compilers have problems with a '.al' extension so + # convenience libraries should have the same extension an + # archive normally would. + oldlibs="$output_objdir/$libname.$libext $oldlibs" + build_libtool_libs=convenience + build_old_libs=yes + fi + + test -n "$vinfo" && \ + func_warning "'-version-info/-version-number' is ignored for convenience libraries" + + test -n "$release" && \ + func_warning "'-release' is ignored for convenience libraries" + else + + # Parse the version information argument. + save_ifs=$IFS; IFS=: + set dummy $vinfo 0 0 0 + shift + IFS=$save_ifs + + test -n "$7" && \ + func_fatal_help "too many parameters to '-version-info'" + + # convert absolute version numbers to libtool ages + # this retains compatibility with .la files and attempts + # to make the code below a bit more comprehensible + + case $vinfo_number in + yes) + number_major=$1 + number_minor=$2 + number_revision=$3 + # + # There are really only two kinds -- those that + # use the current revision as the major version + # and those that subtract age and use age as + # a minor version. But, then there is irix + # that has an extra 1 added just for fun + # + case $version_type in + # correct linux to gnu/linux during the next big refactor + darwin|freebsd-elf|linux|osf|windows|none) + func_arith $number_major + $number_minor + current=$func_arith_result + age=$number_minor + revision=$number_revision + ;; + freebsd-aout|qnx|sunos) + current=$number_major + revision=$number_minor + age=0 + ;; + irix|nonstopux) + func_arith $number_major + $number_minor + current=$func_arith_result + age=$number_minor + revision=$number_minor + lt_irix_increment=no + ;; + *) + func_fatal_configuration "$modename: unknown library version type '$version_type'" + ;; + esac + ;; + no) + current=$1 + revision=$2 + age=$3 + ;; + esac + + # Check that each of the things are valid numbers. + case $current in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "CURRENT '$current' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + case $revision in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "REVISION '$revision' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + case $age in + 0|[1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]) ;; + *) + func_error "AGE '$age' must be a nonnegative integer" + func_fatal_error "'$vinfo' is not valid version information" + ;; + esac + + if test "$age" -gt "$current"; then + func_error "AGE '$age' is greater than the current interface number '$current'" + func_fatal_error "'$vinfo' is not valid version information" + fi + + # Calculate the version variables. + major= + versuffix= + verstring= + case $version_type in + none) ;; + + darwin) + # Like Linux, but with the current version available in + # verstring for coding it into the library header + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + # Darwin ld doesn't like 0 for these options... + func_arith $current + 1 + minor_current=$func_arith_result + xlcverstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision" + verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" + # On Darwin other compilers + case $CC in + nagfor*) + verstring="$wl-compatibility_version $wl$minor_current $wl-current_version $wl$minor_current.$revision" + ;; + *) + verstring="-compatibility_version $minor_current -current_version $minor_current.$revision" + ;; + esac + ;; + + freebsd-aout) + major=.$current + versuffix=.$current.$revision + ;; + + freebsd-elf) + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + ;; + + irix | nonstopux) + if test no = "$lt_irix_increment"; then + func_arith $current - $age + else + func_arith $current - $age + 1 + fi + major=$func_arith_result + + case $version_type in + nonstopux) verstring_prefix=nonstopux ;; + *) verstring_prefix=sgi ;; + esac + verstring=$verstring_prefix$major.$revision + + # Add in all the interfaces that we are compatible with. + loop=$revision + while test 0 -ne "$loop"; do + func_arith $revision - $loop + iface=$func_arith_result + func_arith $loop - 1 + loop=$func_arith_result + verstring=$verstring_prefix$major.$iface:$verstring + done + + # Before this point, $major must not contain '.'. + major=.$major + versuffix=$major.$revision + ;; + + linux) # correct to gnu/linux during the next big refactor + func_arith $current - $age + major=.$func_arith_result + versuffix=$major.$age.$revision + ;; + + osf) + func_arith $current - $age + major=.$func_arith_result + versuffix=.$current.$age.$revision + verstring=$current.$age.$revision + + # Add in all the interfaces that we are compatible with. + loop=$age + while test 0 -ne "$loop"; do + func_arith $current - $loop + iface=$func_arith_result + func_arith $loop - 1 + loop=$func_arith_result + verstring=$verstring:$iface.0 + done + + # Make executables depend on our current version. + func_append verstring ":$current.0" + ;; + + qnx) + major=.$current + versuffix=.$current + ;; + + sco) + major=.$current + versuffix=.$current + ;; + + sunos) + major=.$current + versuffix=.$current.$revision + ;; + + windows) + # Use '-' rather than '.', since we only want one + # extension on DOS 8.3 file systems. + func_arith $current - $age + major=$func_arith_result + versuffix=-$major + ;; + + *) + func_fatal_configuration "unknown library version type '$version_type'" + ;; + esac + + # Clear the version info if we defaulted, and they specified a release. + if test -z "$vinfo" && test -n "$release"; then + major= + case $version_type in + darwin) + # we can't check for "0.0" in archive_cmds due to quoting + # problems, so we reset it completely + verstring= + ;; + *) + verstring=0.0 + ;; + esac + if test no = "$need_version"; then + versuffix= + else + versuffix=.0.0 + fi + fi + + # Remove version info from name if versioning should be avoided + if test yes,no = "$avoid_version,$need_version"; then + major= + versuffix= + verstring= + fi + + # Check to see if the archive will have undefined symbols. + if test yes = "$allow_undefined"; then + if test unsupported = "$allow_undefined_flag"; then + if test yes = "$build_old_libs"; then + func_warning "undefined symbols not allowed in $host shared libraries; building static only" + build_libtool_libs=no + else + func_fatal_error "can't build $host shared library unless -no-undefined is specified" + fi + fi + else + # Don't allow undefined symbols. + allow_undefined_flag=$no_undefined_flag + fi + + fi + + func_generate_dlsyms "$libname" "$libname" : + func_append libobjs " $symfileobj" + test " " = "$libobjs" && libobjs= + + if test relink != "$opt_mode"; then + # Remove our outputs, but don't remove object files since they + # may have been created when compiling PIC objects. + removelist= + tempremovelist=`$ECHO "$output_objdir/*"` + for p in $tempremovelist; do + case $p in + *.$objext | *.gcno) + ;; + $output_objdir/$outputname | $output_objdir/$libname.* | $output_objdir/$libname$release.*) + if test -n "$precious_files_regex"; then + if $ECHO "$p" | $EGREP -e "$precious_files_regex" >/dev/null 2>&1 + then + continue + fi + fi + func_append removelist " $p" + ;; + *) ;; + esac + done + test -n "$removelist" && \ + func_show_eval "${RM}r \$removelist" + fi + + # Now set the variables for building old libraries. + if test yes = "$build_old_libs" && test convenience != "$build_libtool_libs"; then + func_append oldlibs " $output_objdir/$libname.$libext" + + # Transform .lo files to .o files. + oldobjs="$objs "`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; $lo2o" | $NL2SP` + fi + + # Eliminate all temporary directories. + #for path in $notinst_path; do + # lib_search_path=`$ECHO "$lib_search_path " | $SED "s% $path % %g"` + # deplibs=`$ECHO "$deplibs " | $SED "s% -L$path % %g"` + # dependency_libs=`$ECHO "$dependency_libs " | $SED "s% -L$path % %g"` + #done + + if test -n "$xrpath"; then + # If the user specified any rpath flags, then add them. + temp_xrpath= + for libdir in $xrpath; do + func_replace_sysroot "$libdir" + func_append temp_xrpath " -R$func_replace_sysroot_result" + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + done + if test yes != "$hardcode_into_libs" || test yes = "$build_old_libs"; then + dependency_libs="$temp_xrpath $dependency_libs" + fi + fi + + # Make sure dlfiles contains only unique files that won't be dlpreopened + old_dlfiles=$dlfiles + dlfiles= + for lib in $old_dlfiles; do + case " $dlprefiles $dlfiles " in + *" $lib "*) ;; + *) func_append dlfiles " $lib" ;; + esac + done + + # Make sure dlprefiles contains only unique files + old_dlprefiles=$dlprefiles + dlprefiles= + for lib in $old_dlprefiles; do + case "$dlprefiles " in + *" $lib "*) ;; + *) func_append dlprefiles " $lib" ;; + esac + done + + if test yes = "$build_libtool_libs"; then + if test -n "$rpath"; then + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-beos* | *-cegcc* | *-*-haiku*) + # these systems don't actually have a c library (as such)! + ;; + *-*-rhapsody* | *-*-darwin1.[012]) + # Rhapsody C library is in the System framework + func_append deplibs " System.ltframework" + ;; + *-*-netbsd*) + # Don't link with libc until the a.out ld.so is fixed. + ;; + *-*-openbsd* | *-*-freebsd* | *-*-dragonfly*) + # Do not include libc due to us having libc/libc_r. + ;; + *-*-sco3.2v5* | *-*-sco5v6*) + # Causes problems with __ctype + ;; + *-*-sysv4.2uw2* | *-*-sysv5* | *-*-unixware* | *-*-OpenUNIX*) + # Compiler inserts libc in the correct place for threads to work + ;; + *) + # Add libc to deplibs on all other systems if necessary. + if test yes = "$build_libtool_need_lc"; then + func_append deplibs " -lc" + fi + ;; + esac + fi + + # Transform deplibs into only deplibs that can be linked in shared. + name_save=$name + libname_save=$libname + release_save=$release + versuffix_save=$versuffix + major_save=$major + # I'm not sure if I'm treating the release correctly. I think + # release should show up in the -l (ie -lgmp5) so we don't want to + # add it in twice. Is that correct? + release= + versuffix= + major= + newdeplibs= + droppeddeps=no + case $deplibs_check_method in + pass_all) + # Don't check for shared/static. Everything works. + # This might be a little naive. We might want to check + # whether the library exists or not. But this is on + # osf3 & osf4 and I'm not really sure... Just + # implementing what was already the behavior. + newdeplibs=$deplibs + ;; + test_compile) + # This code stresses the "libraries are programs" paradigm to its + # limits. Maybe even breaks it. We compile a program, linking it + # against the deplibs as a proxy for the library. Then we can check + # whether they linked in statically or dynamically with ldd. + $opt_dry_run || $RM conftest.c + cat > conftest.c </dev/null` + $nocaseglob + else + potential_libs=`ls $i/$libnameglob[.-]* 2>/dev/null` + fi + for potent_lib in $potential_libs; do + # Follow soft links. + if ls -lLd "$potent_lib" 2>/dev/null | + $GREP " -> " >/dev/null; then + continue + fi + # The statement above tries to avoid entering an + # endless loop below, in case of cyclic links. + # We might still enter an endless loop, since a link + # loop can be closed while we follow links, + # but so what? + potlib=$potent_lib + while test -h "$potlib" 2>/dev/null; do + potliblink=`ls -ld $potlib | $SED 's/.* -> //'` + case $potliblink in + [\\/]* | [A-Za-z]:[\\/]*) potlib=$potliblink;; + *) potlib=`$ECHO "$potlib" | $SED 's|[^/]*$||'`"$potliblink";; + esac + done + if eval $file_magic_cmd \"\$potlib\" 2>/dev/null | + $SED -e 10q | + $EGREP "$file_magic_regex" > /dev/null; then + func_append newdeplibs " $a_deplib" + a_deplib= + break 2 + fi + done + done + fi + if test -n "$a_deplib"; then + droppeddeps=yes + echo + $ECHO "*** Warning: linker path does not have real file for library $a_deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because I did check the linker path looking for a file starting" + if test -z "$potlib"; then + $ECHO "*** with $libname but no candidates were found. (...for file magic test)" + else + $ECHO "*** with $libname and none of the candidates passed a file format test" + $ECHO "*** using a file magic. Last file checked: $potlib" + fi + fi + ;; + *) + # Add a -L argument. + func_append newdeplibs " $a_deplib" + ;; + esac + done # Gone through all deplibs. + ;; + match_pattern*) + set dummy $deplibs_check_method; shift + match_pattern_regex=`expr "$deplibs_check_method" : "$1 \(.*\)"` + for a_deplib in $deplibs; do + case $a_deplib in + -l*) + func_stripname -l '' "$a_deplib" + name=$func_stripname_result + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + case " $predeps $postdeps " in + *" $a_deplib "*) + func_append newdeplibs " $a_deplib" + a_deplib= + ;; + esac + fi + if test -n "$a_deplib"; then + libname=`eval "\\$ECHO \"$libname_spec\""` + for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do + potential_libs=`ls $i/$libname[.-]* 2>/dev/null` + for potent_lib in $potential_libs; do + potlib=$potent_lib # see symlink-check above in file_magic test + if eval "\$ECHO \"$potent_lib\"" 2>/dev/null | $SED 10q | \ + $EGREP "$match_pattern_regex" > /dev/null; then + func_append newdeplibs " $a_deplib" + a_deplib= + break 2 + fi + done + done + fi + if test -n "$a_deplib"; then + droppeddeps=yes + echo + $ECHO "*** Warning: linker path does not have real file for library $a_deplib." + echo "*** I have the capability to make that library automatically link in when" + echo "*** you link to this library. But I can only do this if you have a" + echo "*** shared version of the library, which you do not appear to have" + echo "*** because I did check the linker path looking for a file starting" + if test -z "$potlib"; then + $ECHO "*** with $libname but no candidates were found. (...for regex pattern test)" + else + $ECHO "*** with $libname and none of the candidates passed a file format test" + $ECHO "*** using a regex pattern. Last file checked: $potlib" + fi + fi + ;; + *) + # Add a -L argument. + func_append newdeplibs " $a_deplib" + ;; + esac + done # Gone through all deplibs. + ;; + none | unknown | *) + newdeplibs= + tmp_deplibs=`$ECHO " $deplibs" | $SED 's/ -lc$//; s/ -[LR][^ ]*//g'` + if test yes = "$allow_libtool_libs_with_static_runtimes"; then + for i in $predeps $postdeps; do + # can't use Xsed below, because $i might contain '/' + tmp_deplibs=`$ECHO " $tmp_deplibs" | $SED "s|$i||"` + done + fi + case $tmp_deplibs in + *[!\ \ ]*) + echo + if test none = "$deplibs_check_method"; then + echo "*** Warning: inter-library dependencies are not supported in this platform." + else + echo "*** Warning: inter-library dependencies are not known to be supported." + fi + echo "*** All declared inter-library dependencies are being dropped." + droppeddeps=yes + ;; + esac + ;; + esac + versuffix=$versuffix_save + major=$major_save + release=$release_save + libname=$libname_save + name=$name_save + + case $host in + *-*-rhapsody* | *-*-darwin1.[012]) + # On Rhapsody replace the C library with the System framework + newdeplibs=`$ECHO " $newdeplibs" | $SED 's/ -lc / System.ltframework /'` + ;; + esac + + if test yes = "$droppeddeps"; then + if test yes = "$module"; then + echo + echo "*** Warning: libtool could not satisfy all declared inter-library" + $ECHO "*** dependencies of module $libname. Therefore, libtool will create" + echo "*** a static module, that should work as long as the dlopening" + echo "*** application is linked with the -dlopen flag." + if test -z "$global_symbol_pipe"; then + echo + echo "*** However, this would only work if libtool was able to extract symbol" + echo "*** lists from a program, using 'nm' or equivalent, but libtool could" + echo "*** not find such a program. So, this module is probably useless." + echo "*** 'nm' from GNU binutils and a full rebuild may help." + fi + if test no = "$build_old_libs"; then + oldlibs=$output_objdir/$libname.$libext + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + else + echo "*** The inter-library dependencies that have been dropped here will be" + echo "*** automatically added whenever a program is linked with this library" + echo "*** or is declared to -dlopen it." + + if test no = "$allow_undefined"; then + echo + echo "*** Since this library must not contain undefined symbols," + echo "*** because either the platform does not support them or" + echo "*** it was explicitly requested with -no-undefined," + echo "*** libtool will only create a static version of it." + if test no = "$build_old_libs"; then + oldlibs=$output_objdir/$libname.$libext + build_libtool_libs=module + build_old_libs=yes + else + build_libtool_libs=no + fi + fi + fi + fi + # Done checking deplibs! + deplibs=$newdeplibs + fi + # Time to change all our "foo.ltframework" stuff back to "-framework foo" + case $host in + *-*-darwin*) + newdeplibs=`$ECHO " $newdeplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + new_inherited_linker_flags=`$ECHO " $new_inherited_linker_flags" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + deplibs=`$ECHO " $deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + ;; + esac + + # move library search paths that coincide with paths to not yet + # installed libraries to the beginning of the library search list + new_libs= + for path in $notinst_path; do + case " $new_libs " in + *" -L$path/$objdir "*) ;; + *) + case " $deplibs " in + *" -L$path/$objdir "*) + func_append new_libs " -L$path/$objdir" ;; + esac + ;; + esac + done + for deplib in $deplibs; do + case $deplib in + -L*) + case " $new_libs " in + *" $deplib "*) ;; + *) func_append new_libs " $deplib" ;; + esac + ;; + *) func_append new_libs " $deplib" ;; + esac + done + deplibs=$new_libs + + # All the library-specific variables (install_libdir is set above). + library_names= + old_library= + dlname= + + # Test again, we may have decided not to build it any more + if test yes = "$build_libtool_libs"; then + # Remove $wl instances when linking with ld. + # FIXME: should test the right _cmds variable. + case $archive_cmds in + *\$LD\ *) wl= ;; + esac + if test yes = "$hardcode_into_libs"; then + # Hardcode the library paths + hardcode_libdirs= + dep_rpath= + rpath=$finalize_rpath + test relink = "$opt_mode" || rpath=$compile_rpath$rpath + for libdir in $rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + func_replace_sysroot "$libdir" + libdir=$func_replace_sysroot_result + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append dep_rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) func_append perm_rpath " $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval "dep_rpath=\"$hardcode_libdir_flag_spec\"" + fi + if test -n "$runpath_var" && test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + func_append rpath "$dir:" + done + eval "$runpath_var='$rpath\$$runpath_var'; export $runpath_var" + fi + test -n "$dep_rpath" && deplibs="$dep_rpath $deplibs" + fi + + shlibpath=$finalize_shlibpath + test relink = "$opt_mode" || shlibpath=$compile_shlibpath$shlibpath + if test -n "$shlibpath"; then + eval "$shlibpath_var='$shlibpath\$$shlibpath_var'; export $shlibpath_var" + fi + + # Get the real and link names of the library. + eval shared_ext=\"$shrext_cmds\" + eval library_names=\"$library_names_spec\" + set dummy $library_names + shift + realname=$1 + shift + + if test -n "$soname_spec"; then + eval soname=\"$soname_spec\" + else + soname=$realname + fi + if test -z "$dlname"; then + dlname=$soname + fi + + lib=$output_objdir/$realname + linknames= + for link + do + func_append linknames " $link" + done + + # Use standard objects if they are pic + test -z "$pic_flag" && libobjs=`$ECHO "$libobjs" | $SP2NL | $SED "$lo2o" | $NL2SP` + test "X$libobjs" = "X " && libobjs= + + delfiles= + if test -n "$export_symbols" && test -n "$include_expsyms"; then + $opt_dry_run || cp "$export_symbols" "$output_objdir/$libname.uexp" + export_symbols=$output_objdir/$libname.uexp + func_append delfiles " $export_symbols" + fi + + orig_export_symbols= + case $host_os in + cygwin* | mingw* | cegcc*) + if test -n "$export_symbols" && test -z "$export_symbols_regex"; then + # exporting using user supplied symfile + func_dll_def_p "$export_symbols" || { + # and it's NOT already a .def file. Must figure out + # which of the given symbols are data symbols and tag + # them as such. So, trigger use of export_symbols_cmds. + # export_symbols gets reassigned inside the "prepare + # the list of exported symbols" if statement, so the + # include_expsyms logic still works. + orig_export_symbols=$export_symbols + export_symbols= + always_export_symbols=yes + } + fi + ;; + esac + + # Prepare the list of exported symbols + if test -z "$export_symbols"; then + if test yes = "$always_export_symbols" || test -n "$export_symbols_regex"; then + func_verbose "generating symbol list for '$libname.la'" + export_symbols=$output_objdir/$libname.exp + $opt_dry_run || $RM $export_symbols + cmds=$export_symbols_cmds + save_ifs=$IFS; IFS='~' + for cmd1 in $cmds; do + IFS=$save_ifs + # Take the normal branch if the nm_file_list_spec branch + # doesn't work or if tool conversion is not needed. + case $nm_file_list_spec~$to_tool_file_cmd in + *~func_convert_file_noop | *~func_convert_file_msys_to_w32 | ~*) + try_normal_branch=yes + eval cmd=\"$cmd1\" + func_len " $cmd" + len=$func_len_result + ;; + *) + try_normal_branch=no + ;; + esac + if test yes = "$try_normal_branch" \ + && { test "$len" -lt "$max_cmd_len" \ + || test "$max_cmd_len" -le -1; } + then + func_show_eval "$cmd" 'exit $?' + skipped_export=false + elif test -n "$nm_file_list_spec"; then + func_basename "$output" + output_la=$func_basename_result + save_libobjs=$libobjs + save_output=$output + output=$output_objdir/$output_la.nm + func_to_tool_file "$output" + libobjs=$nm_file_list_spec$func_to_tool_file_result + func_append delfiles " $output" + func_verbose "creating $NM input file list: $output" + for obj in $save_libobjs; do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" + done > "$output" + eval cmd=\"$cmd1\" + func_show_eval "$cmd" 'exit $?' + output=$save_output + libobjs=$save_libobjs + skipped_export=false + else + # The command line is too long to execute in one step. + func_verbose "using reloadable object file for export list..." + skipped_export=: + # Break out early, otherwise skipped_export may be + # set to false by a later but shorter cmd. + break + fi + done + IFS=$save_ifs + if test -n "$export_symbols_regex" && test : != "$skipped_export"; then + func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' + func_show_eval '$MV "${export_symbols}T" "$export_symbols"' + fi + fi + fi + + if test -n "$export_symbols" && test -n "$include_expsyms"; then + tmp_export_symbols=$export_symbols + test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols + $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"' + fi + + if test : != "$skipped_export" && test -n "$orig_export_symbols"; then + # The given exports_symbols file has to be filtered, so filter it. + func_verbose "filter symbol list for '$libname.la' to tag DATA exports" + # FIXME: $output_objdir/$libname.filter potentially contains lots of + # 's' commands, which not all seds can handle. GNU sed should be fine + # though. Also, the filter scales superlinearly with the number of + # global variables. join(1) would be nice here, but unfortunately + # isn't a blessed tool. + $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter + func_append delfiles " $export_symbols $output_objdir/$libname.filter" + export_symbols=$output_objdir/$libname.def + $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols + fi + + tmp_deplibs= + for test_deplib in $deplibs; do + case " $convenience " in + *" $test_deplib "*) ;; + *) + func_append tmp_deplibs " $test_deplib" + ;; + esac + done + deplibs=$tmp_deplibs + + if test -n "$convenience"; then + if test -n "$whole_archive_flag_spec" && + test yes = "$compiler_needs_object" && + test -z "$libobjs"; then + # extract the archives, so we have objects to list. + # TODO: could optimize this to just extract one archive. + whole_archive_flag_spec= + fi + if test -n "$whole_archive_flag_spec"; then + save_libobjs=$libobjs + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + test "X$libobjs" = "X " && libobjs= + else + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $convenience + func_append libobjs " $func_extract_archives_result" + test "X$libobjs" = "X " && libobjs= + fi + fi + + if test yes = "$thread_safe" && test -n "$thread_safe_flag_spec"; then + eval flag=\"$thread_safe_flag_spec\" + func_append linker_flags " $flag" + fi + + # Make a backup of the uninstalled library when relinking + if test relink = "$opt_mode"; then + $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}U && $MV $realname ${realname}U)' || exit $? + fi + + # Do each of the archive commands. + if test yes = "$module" && test -n "$module_cmds"; then + if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then + eval test_cmds=\"$module_expsym_cmds\" + cmds=$module_expsym_cmds + else + eval test_cmds=\"$module_cmds\" + cmds=$module_cmds + fi + else + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + eval test_cmds=\"$archive_expsym_cmds\" + cmds=$archive_expsym_cmds + else + eval test_cmds=\"$archive_cmds\" + cmds=$archive_cmds + fi + fi + + if test : != "$skipped_export" && + func_len " $test_cmds" && + len=$func_len_result && + test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then + : + else + # The command line is too long to link in one step, link piecewise + # or, if using GNU ld and skipped_export is not :, use a linker + # script. + + # Save the value of $output and $libobjs because we want to + # use them later. If we have whole_archive_flag_spec, we + # want to use save_libobjs as it was before + # whole_archive_flag_spec was expanded, because we can't + # assume the linker understands whole_archive_flag_spec. + # This may have to be revisited, in case too many + # convenience libraries get linked in and end up exceeding + # the spec. + if test -z "$convenience" || test -z "$whole_archive_flag_spec"; then + save_libobjs=$libobjs + fi + save_output=$output + func_basename "$output" + output_la=$func_basename_result + + # Clear the reloadable object creation command queue and + # initialize k to one. + test_cmds= + concat_cmds= + objlist= + last_robj= + k=1 + + if test -n "$save_libobjs" && test : != "$skipped_export" && test yes = "$with_gnu_ld"; then + output=$output_objdir/$output_la.lnkscript + func_verbose "creating GNU ld script: $output" + echo 'INPUT (' > $output + for obj in $save_libobjs + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" >> $output + done + echo ')' >> $output + func_append delfiles " $output" + func_to_tool_file "$output" + output=$func_to_tool_file_result + elif test -n "$save_libobjs" && test : != "$skipped_export" && test -n "$file_list_spec"; then + output=$output_objdir/$output_la.lnk + func_verbose "creating linker input file list: $output" + : > $output + set x $save_libobjs + shift + firstobj= + if test yes = "$compiler_needs_object"; then + firstobj="$1 " + shift + fi + for obj + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" >> $output + done + func_append delfiles " $output" + func_to_tool_file "$output" + output=$firstobj\"$file_list_spec$func_to_tool_file_result\" + else + if test -n "$save_libobjs"; then + func_verbose "creating reloadable object files..." + output=$output_objdir/$output_la-$k.$objext + eval test_cmds=\"$reload_cmds\" + func_len " $test_cmds" + len0=$func_len_result + len=$len0 + + # Loop over the list of objects to be linked. + for obj in $save_libobjs + do + func_len " $obj" + func_arith $len + $func_len_result + len=$func_arith_result + if test -z "$objlist" || + test "$len" -lt "$max_cmd_len"; then + func_append objlist " $obj" + else + # The command $test_cmds is almost too long, add a + # command to the queue. + if test 1 -eq "$k"; then + # The first file doesn't have a previous command to add. + reload_objs=$objlist + eval concat_cmds=\"$reload_cmds\" + else + # All subsequent reloadable object files will link in + # the last one created. + reload_objs="$objlist $last_robj" + eval concat_cmds=\"\$concat_cmds~$reload_cmds~\$RM $last_robj\" + fi + last_robj=$output_objdir/$output_la-$k.$objext + func_arith $k + 1 + k=$func_arith_result + output=$output_objdir/$output_la-$k.$objext + objlist=" $obj" + func_len " $last_robj" + func_arith $len0 + $func_len_result + len=$func_arith_result + fi + done + # Handle the remaining objects by creating one last + # reloadable object file. All subsequent reloadable object + # files will link in the last one created. + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + reload_objs="$objlist $last_robj" + eval concat_cmds=\"\$concat_cmds$reload_cmds\" + if test -n "$last_robj"; then + eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\" + fi + func_append delfiles " $output" + + else + output= + fi + + ${skipped_export-false} && { + func_verbose "generating symbol list for '$libname.la'" + export_symbols=$output_objdir/$libname.exp + $opt_dry_run || $RM $export_symbols + libobjs=$output + # Append the command to create the export file. + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + eval concat_cmds=\"\$concat_cmds$export_symbols_cmds\" + if test -n "$last_robj"; then + eval concat_cmds=\"\$concat_cmds~\$RM $last_robj\" + fi + } + + test -n "$save_libobjs" && + func_verbose "creating a temporary reloadable object file: $output" + + # Loop through the commands generated above and execute them. + save_ifs=$IFS; IFS='~' + for cmd in $concat_cmds; do + IFS=$save_ifs + $opt_quiet || { + func_quote_for_expand "$cmd" + eval "func_echo $func_quote_for_expand_result" + } + $opt_dry_run || eval "$cmd" || { + lt_exit=$? + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + ( cd "$output_objdir" && \ + $RM "${realname}T" && \ + $MV "${realname}U" "$realname" ) + fi + + exit $lt_exit + } + done + IFS=$save_ifs + + if test -n "$export_symbols_regex" && ${skipped_export-false}; then + func_show_eval '$EGREP -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"' + func_show_eval '$MV "${export_symbols}T" "$export_symbols"' + fi + fi + + ${skipped_export-false} && { + if test -n "$export_symbols" && test -n "$include_expsyms"; then + tmp_export_symbols=$export_symbols + test -n "$orig_export_symbols" && tmp_export_symbols=$orig_export_symbols + $opt_dry_run || eval '$ECHO "$include_expsyms" | $SP2NL >> "$tmp_export_symbols"' + fi + + if test -n "$orig_export_symbols"; then + # The given exports_symbols file has to be filtered, so filter it. + func_verbose "filter symbol list for '$libname.la' to tag DATA exports" + # FIXME: $output_objdir/$libname.filter potentially contains lots of + # 's' commands, which not all seds can handle. GNU sed should be fine + # though. Also, the filter scales superlinearly with the number of + # global variables. join(1) would be nice here, but unfortunately + # isn't a blessed tool. + $opt_dry_run || $SED -e '/[ ,]DATA/!d;s,\(.*\)\([ \,].*\),s|^\1$|\1\2|,' < $export_symbols > $output_objdir/$libname.filter + func_append delfiles " $export_symbols $output_objdir/$libname.filter" + export_symbols=$output_objdir/$libname.def + $opt_dry_run || $SED -f $output_objdir/$libname.filter < $orig_export_symbols > $export_symbols + fi + } + + libobjs=$output + # Restore the value of output. + output=$save_output + + if test -n "$convenience" && test -n "$whole_archive_flag_spec"; then + eval libobjs=\"\$libobjs $whole_archive_flag_spec\" + test "X$libobjs" = "X " && libobjs= + fi + # Expand the library linking commands again to reset the + # value of $libobjs for piecewise linking. + + # Do each of the archive commands. + if test yes = "$module" && test -n "$module_cmds"; then + if test -n "$export_symbols" && test -n "$module_expsym_cmds"; then + cmds=$module_expsym_cmds + else + cmds=$module_cmds + fi + else + if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then + cmds=$archive_expsym_cmds + else + cmds=$archive_cmds + fi + fi + fi + + if test -n "$delfiles"; then + # Append the command to remove temporary files to $cmds. + eval cmds=\"\$cmds~\$RM $delfiles\" + fi + + # Add any objects from preloaded convenience libraries + if test -n "$dlprefiles"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $dlprefiles + func_append libobjs " $func_extract_archives_result" + test "X$libobjs" = "X " && libobjs= + fi + + save_ifs=$IFS; IFS='~' + for cmd in $cmds; do + IFS=$sp$nl + eval cmd=\"$cmd\" + IFS=$save_ifs + $opt_quiet || { + func_quote_for_expand "$cmd" + eval "func_echo $func_quote_for_expand_result" + } + $opt_dry_run || eval "$cmd" || { + lt_exit=$? + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + ( cd "$output_objdir" && \ + $RM "${realname}T" && \ + $MV "${realname}U" "$realname" ) + fi + + exit $lt_exit + } + done + IFS=$save_ifs + + # Restore the uninstalled library and exit + if test relink = "$opt_mode"; then + $opt_dry_run || eval '(cd $output_objdir && $RM ${realname}T && $MV $realname ${realname}T && $MV ${realname}U $realname)' || exit $? + + if test -n "$convenience"; then + if test -z "$whole_archive_flag_spec"; then + func_show_eval '${RM}r "$gentop"' + fi + fi + + exit $EXIT_SUCCESS + fi + + # Create links to the real library. + for linkname in $linknames; do + if test "$realname" != "$linkname"; then + func_show_eval '(cd "$output_objdir" && $RM "$linkname" && $LN_S "$realname" "$linkname")' 'exit $?' + fi + done + + # If -module or -export-dynamic was specified, set the dlname. + if test yes = "$module" || test yes = "$export_dynamic"; then + # On all known operating systems, these are identical. + dlname=$soname + fi + fi + ;; + + obj) + if test -n "$dlfiles$dlprefiles" || test no != "$dlself"; then + func_warning "'-dlopen' is ignored for objects" + fi + + case " $deplibs" in + *\ -l* | *\ -L*) + func_warning "'-l' and '-L' are ignored for objects" ;; + esac + + test -n "$rpath" && \ + func_warning "'-rpath' is ignored for objects" + + test -n "$xrpath" && \ + func_warning "'-R' is ignored for objects" + + test -n "$vinfo" && \ + func_warning "'-version-info' is ignored for objects" + + test -n "$release" && \ + func_warning "'-release' is ignored for objects" + + case $output in + *.lo) + test -n "$objs$old_deplibs" && \ + func_fatal_error "cannot build library object '$output' from non-libtool objects" + + libobj=$output + func_lo2o "$libobj" + obj=$func_lo2o_result + ;; + *) + libobj= + obj=$output + ;; + esac + + # Delete the old objects. + $opt_dry_run || $RM $obj $libobj + + # Objects from convenience libraries. This assumes + # single-version convenience libraries. Whenever we create + # different ones for PIC/non-PIC, this we'll have to duplicate + # the extraction. + reload_conv_objs= + gentop= + # if reload_cmds runs $LD directly, get rid of -Wl from + # whole_archive_flag_spec and hope we can get by with turning comma + # into space. + case $reload_cmds in + *\$LD[\ \$]*) wl= ;; + esac + if test -n "$convenience"; then + if test -n "$whole_archive_flag_spec"; then + eval tmp_whole_archive_flags=\"$whole_archive_flag_spec\" + test -n "$wl" || tmp_whole_archive_flags=`$ECHO "$tmp_whole_archive_flags" | $SED 's|,| |g'` + reload_conv_objs=$reload_objs\ $tmp_whole_archive_flags + else + gentop=$output_objdir/${obj}x + func_append generated " $gentop" + + func_extract_archives $gentop $convenience + reload_conv_objs="$reload_objs $func_extract_archives_result" + fi + fi + + # If we're not building shared, we need to use non_pic_objs + test yes = "$build_libtool_libs" || libobjs=$non_pic_objects + + # Create the old-style object. + reload_objs=$objs$old_deplibs' '`$ECHO "$libobjs" | $SP2NL | $SED "/\.$libext$/d; /\.lib$/d; $lo2o" | $NL2SP`' '$reload_conv_objs + + output=$obj + func_execute_cmds "$reload_cmds" 'exit $?' + + # Exit if we aren't doing a library object file. + if test -z "$libobj"; then + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + exit $EXIT_SUCCESS + fi + + test yes = "$build_libtool_libs" || { + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + # Create an invalid libtool object if no PIC, so that we don't + # accidentally link it into a program. + # $show "echo timestamp > $libobj" + # $opt_dry_run || eval "echo timestamp > $libobj" || exit $? + exit $EXIT_SUCCESS + } + + if test -n "$pic_flag" || test default != "$pic_mode"; then + # Only do commands if we really have different PIC objects. + reload_objs="$libobjs $reload_conv_objs" + output=$libobj + func_execute_cmds "$reload_cmds" 'exit $?' + fi + + if test -n "$gentop"; then + func_show_eval '${RM}r "$gentop"' + fi + + exit $EXIT_SUCCESS + ;; + + prog) + case $host in + *cygwin*) func_stripname '' '.exe' "$output" + output=$func_stripname_result.exe;; + esac + test -n "$vinfo" && \ + func_warning "'-version-info' is ignored for programs" + + test -n "$release" && \ + func_warning "'-release' is ignored for programs" + + $preload \ + && test unknown,unknown,unknown = "$dlopen_support,$dlopen_self,$dlopen_self_static" \ + && func_warning "'LT_INIT([dlopen])' not used. Assuming no dlopen support." + + case $host in + *-*-rhapsody* | *-*-darwin1.[012]) + # On Rhapsody replace the C library is the System framework + compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's/ -lc / System.ltframework /'` + finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's/ -lc / System.ltframework /'` + ;; + esac + + case $host in + *-*-darwin*) + # Don't allow lazy linking, it breaks C++ global constructors + # But is supposedly fixed on 10.4 or later (yay!). + if test CXX = "$tagname"; then + case ${MACOSX_DEPLOYMENT_TARGET-10.0} in + 10.[0123]) + func_append compile_command " $wl-bind_at_load" + func_append finalize_command " $wl-bind_at_load" + ;; + esac + fi + # Time to change all our "foo.ltframework" stuff back to "-framework foo" + compile_deplibs=`$ECHO " $compile_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + finalize_deplibs=`$ECHO " $finalize_deplibs" | $SED 's% \([^ $]*\).ltframework% -framework \1%g'` + ;; + esac + + + # move library search paths that coincide with paths to not yet + # installed libraries to the beginning of the library search list + new_libs= + for path in $notinst_path; do + case " $new_libs " in + *" -L$path/$objdir "*) ;; + *) + case " $compile_deplibs " in + *" -L$path/$objdir "*) + func_append new_libs " -L$path/$objdir" ;; + esac + ;; + esac + done + for deplib in $compile_deplibs; do + case $deplib in + -L*) + case " $new_libs " in + *" $deplib "*) ;; + *) func_append new_libs " $deplib" ;; + esac + ;; + *) func_append new_libs " $deplib" ;; + esac + done + compile_deplibs=$new_libs + + + func_append compile_command " $compile_deplibs" + func_append finalize_command " $finalize_deplibs" + + if test -n "$rpath$xrpath"; then + # If the user specified any rpath flags, then add them. + for libdir in $rpath $xrpath; do + # This is the magic to use -rpath. + case "$finalize_rpath " in + *" $libdir "*) ;; + *) func_append finalize_rpath " $libdir" ;; + esac + done + fi + + # Now hardcode the library paths + rpath= + hardcode_libdirs= + for libdir in $compile_rpath $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$perm_rpath " in + *" $libdir "*) ;; + *) func_append perm_rpath " $libdir" ;; + esac + fi + case $host in + *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-cegcc*) + testbindir=`$ECHO "$libdir" | $SED -e 's*/lib$*/bin*'` + case :$dllsearchpath: in + *":$libdir:"*) ;; + ::) dllsearchpath=$libdir;; + *) func_append dllsearchpath ":$libdir";; + esac + case :$dllsearchpath: in + *":$testbindir:"*) ;; + ::) dllsearchpath=$testbindir;; + *) func_append dllsearchpath ":$testbindir";; + esac + ;; + esac + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + compile_rpath=$rpath + + rpath= + hardcode_libdirs= + for libdir in $finalize_rpath; do + if test -n "$hardcode_libdir_flag_spec"; then + if test -n "$hardcode_libdir_separator"; then + if test -z "$hardcode_libdirs"; then + hardcode_libdirs=$libdir + else + # Just accumulate the unique libdirs. + case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in + *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*) + ;; + *) + func_append hardcode_libdirs "$hardcode_libdir_separator$libdir" + ;; + esac + fi + else + eval flag=\"$hardcode_libdir_flag_spec\" + func_append rpath " $flag" + fi + elif test -n "$runpath_var"; then + case "$finalize_perm_rpath " in + *" $libdir "*) ;; + *) func_append finalize_perm_rpath " $libdir" ;; + esac + fi + done + # Substitute the hardcoded libdirs into the rpath. + if test -n "$hardcode_libdir_separator" && + test -n "$hardcode_libdirs"; then + libdir=$hardcode_libdirs + eval rpath=\" $hardcode_libdir_flag_spec\" + fi + finalize_rpath=$rpath + + if test -n "$libobjs" && test yes = "$build_old_libs"; then + # Transform all the library objects into standard objects. + compile_command=`$ECHO "$compile_command" | $SP2NL | $SED "$lo2o" | $NL2SP` + finalize_command=`$ECHO "$finalize_command" | $SP2NL | $SED "$lo2o" | $NL2SP` + fi + + func_generate_dlsyms "$outputname" "@PROGRAM@" false + + # template prelinking step + if test -n "$prelink_cmds"; then + func_execute_cmds "$prelink_cmds" 'exit $?' + fi + + wrappers_required=: + case $host in + *cegcc* | *mingw32ce*) + # Disable wrappers for cegcc and mingw32ce hosts, we are cross compiling anyway. + wrappers_required=false + ;; + *cygwin* | *mingw* ) + test yes = "$build_libtool_libs" || wrappers_required=false + ;; + *) + if test no = "$need_relink" || test yes != "$build_libtool_libs"; then + wrappers_required=false + fi + ;; + esac + $wrappers_required || { + # Replace the output file specification. + compile_command=`$ECHO "$compile_command" | $SED 's%@OUTPUT@%'"$output"'%g'` + link_command=$compile_command$compile_rpath + + # We have no uninstalled library dependencies, so finalize right now. + exit_status=0 + func_show_eval "$link_command" 'exit_status=$?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + # Delete the generated files. + if test -f "$output_objdir/${outputname}S.$objext"; then + func_show_eval '$RM "$output_objdir/${outputname}S.$objext"' + fi + + exit $exit_status + } + + if test -n "$compile_shlibpath$finalize_shlibpath"; then + compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command" + fi + if test -n "$finalize_shlibpath"; then + finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command" + fi + + compile_var= + finalize_var= + if test -n "$runpath_var"; then + if test -n "$perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $perm_rpath; do + func_append rpath "$dir:" + done + compile_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + if test -n "$finalize_perm_rpath"; then + # We should set the runpath_var. + rpath= + for dir in $finalize_perm_rpath; do + func_append rpath "$dir:" + done + finalize_var="$runpath_var=\"$rpath\$$runpath_var\" " + fi + fi + + if test yes = "$no_install"; then + # We don't need to create a wrapper script. + link_command=$compile_var$compile_command$compile_rpath + # Replace the output file specification. + link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output"'%g'` + # Delete the old output file. + $opt_dry_run || $RM $output + # Link the executable and exit + func_show_eval "$link_command" 'exit $?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + exit $EXIT_SUCCESS + fi + + case $hardcode_action,$fast_install in + relink,*) + # Fast installation is not supported + link_command=$compile_var$compile_command$compile_rpath + relink_command=$finalize_var$finalize_command$finalize_rpath + + func_warning "this platform does not like uninstalled shared libraries" + func_warning "'$output' will be relinked during installation" + ;; + *,yes) + link_command=$finalize_var$compile_command$finalize_rpath + relink_command=`$ECHO "$compile_var$compile_command$compile_rpath" | $SED 's%@OUTPUT@%\$progdir/\$file%g'` + ;; + *,no) + link_command=$compile_var$compile_command$compile_rpath + relink_command=$finalize_var$finalize_command$finalize_rpath + ;; + *,needless) + link_command=$finalize_var$compile_command$finalize_rpath + relink_command= + ;; + esac + + # Replace the output file specification. + link_command=`$ECHO "$link_command" | $SED 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'` + + # Delete the old output files. + $opt_dry_run || $RM $output $output_objdir/$outputname $output_objdir/lt-$outputname + + func_show_eval "$link_command" 'exit $?' + + if test -n "$postlink_cmds"; then + func_to_tool_file "$output_objdir/$outputname" + postlink_cmds=`func_echo_all "$postlink_cmds" | $SED -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g' -e 's%@TOOL_OUTPUT@%'"$func_to_tool_file_result"'%g'` + func_execute_cmds "$postlink_cmds" 'exit $?' + fi + + # Now create the wrapper script. + func_verbose "creating $output" + + # Quote the relink command for shipping. + if test -n "$relink_command"; then + # Preserve any variables that may affect compiler behavior + for var in $variables_saved_for_relink; do + if eval test -z \"\${$var+set}\"; then + relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command" + elif eval var_value=\$$var; test -z "$var_value"; then + relink_command="$var=; export $var; $relink_command" + else + func_quote_for_eval "$var_value" + relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command" + fi + done + relink_command="(cd `pwd`; $relink_command)" + relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"` + fi + + # Only actually do things if not in dry run mode. + $opt_dry_run || { + # win32 will think the script is a binary if it has + # a .exe suffix, so we strip it off here. + case $output in + *.exe) func_stripname '' '.exe' "$output" + output=$func_stripname_result ;; + esac + # test for cygwin because mv fails w/o .exe extensions + case $host in + *cygwin*) + exeext=.exe + func_stripname '' '.exe' "$outputname" + outputname=$func_stripname_result ;; + *) exeext= ;; + esac + case $host in + *cygwin* | *mingw* ) + func_dirname_and_basename "$output" "" "." + output_name=$func_basename_result + output_path=$func_dirname_result + cwrappersource=$output_path/$objdir/lt-$output_name.c + cwrapper=$output_path/$output_name.exe + $RM $cwrappersource $cwrapper + trap "$RM $cwrappersource $cwrapper; exit $EXIT_FAILURE" 1 2 15 + + func_emit_cwrapperexe_src > $cwrappersource + + # The wrapper executable is built using the $host compiler, + # because it contains $host paths and files. If cross- + # compiling, it, like the target executable, must be + # executed on the $host or under an emulation environment. + $opt_dry_run || { + $LTCC $LTCFLAGS -o $cwrapper $cwrappersource + $STRIP $cwrapper + } + + # Now, create the wrapper script for func_source use: + func_ltwrapper_scriptname $cwrapper + $RM $func_ltwrapper_scriptname_result + trap "$RM $func_ltwrapper_scriptname_result; exit $EXIT_FAILURE" 1 2 15 + $opt_dry_run || { + # note: this script will not be executed, so do not chmod. + if test "x$build" = "x$host"; then + $cwrapper --lt-dump-script > $func_ltwrapper_scriptname_result + else + func_emit_wrapper no > $func_ltwrapper_scriptname_result + fi + } + ;; + * ) + $RM $output + trap "$RM $output; exit $EXIT_FAILURE" 1 2 15 + + func_emit_wrapper no > $output + chmod +x $output + ;; + esac + } + exit $EXIT_SUCCESS + ;; + esac + + # See if we need to build an old-fashioned archive. + for oldlib in $oldlibs; do + + case $build_libtool_libs in + convenience) + oldobjs="$libobjs_save $symfileobj" + addlibs=$convenience + build_libtool_libs=no + ;; + module) + oldobjs=$libobjs_save + addlibs=$old_convenience + build_libtool_libs=no + ;; + *) + oldobjs="$old_deplibs $non_pic_objects" + $preload && test -f "$symfileobj" \ + && func_append oldobjs " $symfileobj" + addlibs=$old_convenience + ;; + esac + + if test -n "$addlibs"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $addlibs + func_append oldobjs " $func_extract_archives_result" + fi + + # Do each command in the archive commands. + if test -n "$old_archive_from_new_cmds" && test yes = "$build_libtool_libs"; then + cmds=$old_archive_from_new_cmds + else + + # Add any objects from preloaded convenience libraries + if test -n "$dlprefiles"; then + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + + func_extract_archives $gentop $dlprefiles + func_append oldobjs " $func_extract_archives_result" + fi + + # POSIX demands no paths to be encoded in archives. We have + # to avoid creating archives with duplicate basenames if we + # might have to extract them afterwards, e.g., when creating a + # static archive out of a convenience library, or when linking + # the entirety of a libtool archive into another (currently + # not supported by libtool). + if (for obj in $oldobjs + do + func_basename "$obj" + $ECHO "$func_basename_result" + done | sort | sort -uc >/dev/null 2>&1); then + : + else + echo "copying selected object files to avoid basename conflicts..." + gentop=$output_objdir/${outputname}x + func_append generated " $gentop" + func_mkdir_p "$gentop" + save_oldobjs=$oldobjs + oldobjs= + counter=1 + for obj in $save_oldobjs + do + func_basename "$obj" + objbase=$func_basename_result + case " $oldobjs " in + " ") oldobjs=$obj ;; + *[\ /]"$objbase "*) + while :; do + # Make sure we don't pick an alternate name that also + # overlaps. + newobj=lt$counter-$objbase + func_arith $counter + 1 + counter=$func_arith_result + case " $oldobjs " in + *[\ /]"$newobj "*) ;; + *) if test ! -f "$gentop/$newobj"; then break; fi ;; + esac + done + func_show_eval "ln $obj $gentop/$newobj || cp $obj $gentop/$newobj" + func_append oldobjs " $gentop/$newobj" + ;; + *) func_append oldobjs " $obj" ;; + esac + done + fi + func_to_tool_file "$oldlib" func_convert_file_msys_to_w32 + tool_oldlib=$func_to_tool_file_result + eval cmds=\"$old_archive_cmds\" + + func_len " $cmds" + len=$func_len_result + if test "$len" -lt "$max_cmd_len" || test "$max_cmd_len" -le -1; then + cmds=$old_archive_cmds + elif test -n "$archiver_list_spec"; then + func_verbose "using command file archive linking..." + for obj in $oldobjs + do + func_to_tool_file "$obj" + $ECHO "$func_to_tool_file_result" + done > $output_objdir/$libname.libcmd + func_to_tool_file "$output_objdir/$libname.libcmd" + oldobjs=" $archiver_list_spec$func_to_tool_file_result" + cmds=$old_archive_cmds + else + # the command line is too long to link in one step, link in parts + func_verbose "using piecewise archive linking..." + save_RANLIB=$RANLIB + RANLIB=: + objlist= + concat_cmds= + save_oldobjs=$oldobjs + oldobjs= + # Is there a better way of finding the last object in the list? + for obj in $save_oldobjs + do + last_oldobj=$obj + done + eval test_cmds=\"$old_archive_cmds\" + func_len " $test_cmds" + len0=$func_len_result + len=$len0 + for obj in $save_oldobjs + do + func_len " $obj" + func_arith $len + $func_len_result + len=$func_arith_result + func_append objlist " $obj" + if test "$len" -lt "$max_cmd_len"; then + : + else + # the above command should be used before it gets too long + oldobjs=$objlist + if test "$obj" = "$last_oldobj"; then + RANLIB=$save_RANLIB + fi + test -z "$concat_cmds" || concat_cmds=$concat_cmds~ + eval concat_cmds=\"\$concat_cmds$old_archive_cmds\" + objlist= + len=$len0 + fi + done + RANLIB=$save_RANLIB + oldobjs=$objlist + if test -z "$oldobjs"; then + eval cmds=\"\$concat_cmds\" + else + eval cmds=\"\$concat_cmds~\$old_archive_cmds\" + fi + fi + fi + func_execute_cmds "$cmds" 'exit $?' + done + + test -n "$generated" && \ + func_show_eval "${RM}r$generated" + + # Now create the libtool archive. + case $output in + *.la) + old_library= + test yes = "$build_old_libs" && old_library=$libname.$libext + func_verbose "creating $output" + + # Preserve any variables that may affect compiler behavior + for var in $variables_saved_for_relink; do + if eval test -z \"\${$var+set}\"; then + relink_command="{ test -z \"\${$var+set}\" || $lt_unset $var || { $var=; export $var; }; }; $relink_command" + elif eval var_value=\$$var; test -z "$var_value"; then + relink_command="$var=; export $var; $relink_command" + else + func_quote_for_eval "$var_value" + relink_command="$var=$func_quote_for_eval_result; export $var; $relink_command" + fi + done + # Quote the link command for shipping. + relink_command="(cd `pwd`; $SHELL \"$progpath\" $preserve_args --mode=relink $libtool_args @inst_prefix_dir@)" + relink_command=`$ECHO "$relink_command" | $SED "$sed_quote_subst"` + if test yes = "$hardcode_automatic"; then + relink_command= + fi + + # Only create the output if not a dry run. + $opt_dry_run || { + for installed in no yes; do + if test yes = "$installed"; then + if test -z "$install_libdir"; then + break + fi + output=$output_objdir/${outputname}i + # Replace all uninstalled libtool libraries with the installed ones + newdependency_libs= + for deplib in $dependency_libs; do + case $deplib in + *.la) + func_basename "$deplib" + name=$func_basename_result + func_resolve_sysroot "$deplib" + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $func_resolve_sysroot_result` + test -z "$libdir" && \ + func_fatal_error "'$deplib' is not a valid libtool archive" + func_append newdependency_libs " ${lt_sysroot:+=}$libdir/$name" + ;; + -L*) + func_stripname -L '' "$deplib" + func_replace_sysroot "$func_stripname_result" + func_append newdependency_libs " -L$func_replace_sysroot_result" + ;; + -R*) + func_stripname -R '' "$deplib" + func_replace_sysroot "$func_stripname_result" + func_append newdependency_libs " -R$func_replace_sysroot_result" + ;; + *) func_append newdependency_libs " $deplib" ;; + esac + done + dependency_libs=$newdependency_libs + newdlfiles= + + for lib in $dlfiles; do + case $lib in + *.la) + func_basename "$lib" + name=$func_basename_result + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib` + test -z "$libdir" && \ + func_fatal_error "'$lib' is not a valid libtool archive" + func_append newdlfiles " ${lt_sysroot:+=}$libdir/$name" + ;; + *) func_append newdlfiles " $lib" ;; + esac + done + dlfiles=$newdlfiles + newdlprefiles= + for lib in $dlprefiles; do + case $lib in + *.la) + # Only pass preopened files to the pseudo-archive (for + # eventual linking with the app. that links it) if we + # didn't already link the preopened objects directly into + # the library: + func_basename "$lib" + name=$func_basename_result + eval libdir=`$SED -n -e 's/^libdir=\(.*\)$/\1/p' $lib` + test -z "$libdir" && \ + func_fatal_error "'$lib' is not a valid libtool archive" + func_append newdlprefiles " ${lt_sysroot:+=}$libdir/$name" + ;; + esac + done + dlprefiles=$newdlprefiles + else + newdlfiles= + for lib in $dlfiles; do + case $lib in + [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;; + *) abs=`pwd`"/$lib" ;; + esac + func_append newdlfiles " $abs" + done + dlfiles=$newdlfiles + newdlprefiles= + for lib in $dlprefiles; do + case $lib in + [\\/]* | [A-Za-z]:[\\/]*) abs=$lib ;; + *) abs=`pwd`"/$lib" ;; + esac + func_append newdlprefiles " $abs" + done + dlprefiles=$newdlprefiles + fi + $RM $output + # place dlname in correct position for cygwin + # In fact, it would be nice if we could use this code for all target + # systems that can't hard-code library paths into their executables + # and that have no shared library path variable independent of PATH, + # but it turns out we can't easily determine that from inspecting + # libtool variables, so we have to hard-code the OSs to which it + # applies here; at the moment, that means platforms that use the PE + # object format with DLL files. See the long comment at the top of + # tests/bindir.at for full details. + tdlname=$dlname + case $host,$output,$installed,$module,$dlname in + *cygwin*,*lai,yes,no,*.dll | *mingw*,*lai,yes,no,*.dll | *cegcc*,*lai,yes,no,*.dll) + # If a -bindir argument was supplied, place the dll there. + if test -n "$bindir"; then + func_relative_path "$install_libdir" "$bindir" + tdlname=$func_relative_path_result/$dlname + else + # Otherwise fall back on heuristic. + tdlname=../bin/$dlname + fi + ;; + esac + $ECHO > $output "\ +# $outputname - a libtool library file +# Generated by $PROGRAM (GNU $PACKAGE) $VERSION +# +# Please DO NOT delete this file! +# It is necessary for linking the library. + +# The name that we can dlopen(3). +dlname='$tdlname' + +# Names of this library. +library_names='$library_names' + +# The name of the static archive. +old_library='$old_library' + +# Linker flags that cannot go in dependency_libs. +inherited_linker_flags='$new_inherited_linker_flags' + +# Libraries that this one depends upon. +dependency_libs='$dependency_libs' + +# Names of additional weak libraries provided by this library +weak_library_names='$weak_libs' + +# Version information for $libname. +current=$current +age=$age +revision=$revision + +# Is this an already installed library? +installed=$installed + +# Should we warn about portability when linking against -modules? +shouldnotlink=$module + +# Files to dlopen/dlpreopen +dlopen='$dlfiles' +dlpreopen='$dlprefiles' + +# Directory that this library needs to be installed in: +libdir='$install_libdir'" + if test no,yes = "$installed,$need_relink"; then + $ECHO >> $output "\ +relink_command=\"$relink_command\"" + fi + done + } + + # Do a symbolic link so that the libtool archive can be found in + # LD_LIBRARY_PATH before the program is installed. + func_show_eval '( cd "$output_objdir" && $RM "$outputname" && $LN_S "../$outputname" "$outputname" )' 'exit $?' + ;; + esac + exit $EXIT_SUCCESS +} + +if test link = "$opt_mode" || test relink = "$opt_mode"; then + func_mode_link ${1+"$@"} +fi + + +# func_mode_uninstall arg... +func_mode_uninstall () +{ + $debug_cmd + + RM=$nonopt + files= + rmforce=false + exit_status=0 + + # This variable tells wrapper scripts just to set variables rather + # than running their programs. + libtool_install_magic=$magic + + for arg + do + case $arg in + -f) func_append RM " $arg"; rmforce=: ;; + -*) func_append RM " $arg" ;; + *) func_append files " $arg" ;; + esac + done + + test -z "$RM" && \ + func_fatal_help "you must specify an RM program" + + rmdirs= + + for file in $files; do + func_dirname "$file" "" "." + dir=$func_dirname_result + if test . = "$dir"; then + odir=$objdir + else + odir=$dir/$objdir + fi + func_basename "$file" + name=$func_basename_result + test uninstall = "$opt_mode" && odir=$dir + + # Remember odir for removal later, being careful to avoid duplicates + if test clean = "$opt_mode"; then + case " $rmdirs " in + *" $odir "*) ;; + *) func_append rmdirs " $odir" ;; + esac + fi + + # Don't error if the file doesn't exist and rm -f was used. + if { test -L "$file"; } >/dev/null 2>&1 || + { test -h "$file"; } >/dev/null 2>&1 || + test -f "$file"; then + : + elif test -d "$file"; then + exit_status=1 + continue + elif $rmforce; then + continue + fi + + rmfiles=$file + + case $name in + *.la) + # Possibly a libtool archive, so verify it. + if func_lalib_p "$file"; then + func_source $dir/$name + + # Delete the libtool libraries and symlinks. + for n in $library_names; do + func_append rmfiles " $odir/$n" + done + test -n "$old_library" && func_append rmfiles " $odir/$old_library" + + case $opt_mode in + clean) + case " $library_names " in + *" $dlname "*) ;; + *) test -n "$dlname" && func_append rmfiles " $odir/$dlname" ;; + esac + test -n "$libdir" && func_append rmfiles " $odir/$name $odir/${name}i" + ;; + uninstall) + if test -n "$library_names"; then + # Do each command in the postuninstall commands. + func_execute_cmds "$postuninstall_cmds" '$rmforce || exit_status=1' + fi + + if test -n "$old_library"; then + # Do each command in the old_postuninstall commands. + func_execute_cmds "$old_postuninstall_cmds" '$rmforce || exit_status=1' + fi + # FIXME: should reinstall the best remaining shared library. + ;; + esac + fi + ;; + + *.lo) + # Possibly a libtool object, so verify it. + if func_lalib_p "$file"; then + + # Read the .lo file + func_source $dir/$name + + # Add PIC object to the list of files to remove. + if test -n "$pic_object" && test none != "$pic_object"; then + func_append rmfiles " $dir/$pic_object" + fi + + # Add non-PIC object to the list of files to remove. + if test -n "$non_pic_object" && test none != "$non_pic_object"; then + func_append rmfiles " $dir/$non_pic_object" + fi + fi + ;; + + *) + if test clean = "$opt_mode"; then + noexename=$name + case $file in + *.exe) + func_stripname '' '.exe' "$file" + file=$func_stripname_result + func_stripname '' '.exe' "$name" + noexename=$func_stripname_result + # $file with .exe has already been added to rmfiles, + # add $file without .exe + func_append rmfiles " $file" + ;; + esac + # Do a test to see if this is a libtool program. + if func_ltwrapper_p "$file"; then + if func_ltwrapper_executable_p "$file"; then + func_ltwrapper_scriptname "$file" + relink_command= + func_source $func_ltwrapper_scriptname_result + func_append rmfiles " $func_ltwrapper_scriptname_result" + else + relink_command= + func_source $dir/$noexename + fi + + # note $name still contains .exe if it was in $file originally + # as does the version of $file that was added into $rmfiles + func_append rmfiles " $odir/$name $odir/${name}S.$objext" + if test yes = "$fast_install" && test -n "$relink_command"; then + func_append rmfiles " $odir/lt-$name" + fi + if test "X$noexename" != "X$name"; then + func_append rmfiles " $odir/lt-$noexename.c" + fi + fi + fi + ;; + esac + func_show_eval "$RM $rmfiles" 'exit_status=1' + done + + # Try to remove the $objdir's in the directories where we deleted files + for dir in $rmdirs; do + if test -d "$dir"; then + func_show_eval "rmdir $dir >/dev/null 2>&1" + fi + done + + exit $exit_status +} + +if test uninstall = "$opt_mode" || test clean = "$opt_mode"; then + func_mode_uninstall ${1+"$@"} +fi + +test -z "$opt_mode" && { + help=$generic_help + func_fatal_help "you must specify a MODE" +} + +test -z "$exec_cmd" && \ + func_fatal_help "invalid operation mode '$opt_mode'" + +if test -n "$exec_cmd"; then + eval exec "$exec_cmd" + exit $EXIT_FAILURE +fi + +exit $exit_status + + +# The TAGs below are defined such that we never get into a situation +# where we disable both kinds of libraries. Given conflicting +# choices, we go for a static library, that is the most portable, +# since we can't tell whether shared libraries were disabled because +# the user asked for that or because the platform doesn't support +# them. This is particularly important on AIX, because we don't +# support having both static and shared libraries enabled at the same +# time on that platform, so we default to a shared-only configuration. +# If a disable-shared tag is given, we'll fallback to a static-only +# configuration. But we'll never go from static-only to shared-only. + +# ### BEGIN LIBTOOL TAG CONFIG: disable-shared +build_libtool_libs=no +build_old_libs=yes +# ### END LIBTOOL TAG CONFIG: disable-shared + +# ### BEGIN LIBTOOL TAG CONFIG: disable-static +build_old_libs=`case $build_libtool_libs in yes) echo no;; *) echo yes;; esac` +# ### END LIBTOOL TAG CONFIG: disable-static + +# Local Variables: +# mode:shell-script +# sh-indentation:2 +# End: diff --git a/mesh/agent.c b/mesh/agent.c new file mode 100644 index 0000000..4f99bad --- /dev/null +++ b/mesh/agent.c @@ -0,0 +1,645 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "mesh/mesh.h" +#include "mesh/error.h" +#include "mesh/dbus.h" +#include "mesh/agent.h" + +typedef enum { + MESH_AGENT_REQUEST_BLINK, + MESH_AGENT_REQUEST_BEEP, + MESH_AGENT_REQUEST_VIBRATE, + MESH_AGENT_REQUEST_OUT_NUMERIC, + MESH_AGENT_REQUEST_OUT_ALPHA, + MESH_AGENT_REQUEST_PUSH, + MESH_AGENT_REQUEST_TWIST, + MESH_AGENT_REQUEST_IN_NUMERIC, + MESH_AGENT_REQUEST_IN_ALPHA, + MESH_AGENT_REQUEST_STATIC_OOB, + MESH_AGENT_REQUEST_PRIVATE_KEY, + MESH_AGENT_REQUEST_PUBLIC_KEY +} agent_request_type_t; + +struct agent_request { + agent_request_type_t type; + struct l_dbus_message *msg; + void *cb; + void *user_data; +}; + +struct mesh_agent { + char *path; + char *owner; + struct mesh_agent_prov_caps caps; + struct agent_request *req; +}; + +struct prov_action { + const char *action; + uint16_t output; + uint16_t input; + uint8_t size; +}; + +struct oob_info { + const char *oob; + uint16_t mask; +}; + +static struct prov_action cap_table[] = { + {"blink", 0x0001, 0x0000, 1}, + {"beep", 0x0002, 0x0000, 1}, + {"vibrate", 0x0004, 0x0000, 1}, + {"out-numeric", 0x0008, 0x0000, 8}, + {"out-alpha", 0x0010, 0x0000, 8}, + {"push", 0x0000, 0x0001, 1}, + {"twist", 0x0000, 0x0002, 1}, + {"in-numeric", 0x0000, 0x0004, 8}, + {"in-alpha", 0x0000, 0x0008, 8} +}; + +static struct oob_info oob_table[] = { + {"other", 0x0001}, + {"uri", 0x0002}, + {"machine-code-2d", 0x0004}, + {"barcode", 0x0008}, + {"nfc", 0x0010}, + {"number", 0x0020}, + {"string", 0x0040}, + {"on-box", 0x0800}, + {"in-box", 0x1000}, + {"on-paper", 0x2000}, + {"in-manual", 0x4000}, + {"on-device", 0x8000} +}; + +static struct l_queue *agents; + +static bool simple_match(const void *a, const void *b) +{ + return a == b; +} + +static void parse_prov_caps(struct mesh_agent_prov_caps *caps, + struct l_dbus_message_iter *property) +{ + struct l_dbus_message_iter iter_caps; + const char *str; + uint32_t i; + + if (!l_dbus_message_iter_get_variant(property, "as", &iter_caps)) + return; + + while (l_dbus_message_iter_next_entry(&iter_caps, &str)) { + for (i = 0; i < L_ARRAY_SIZE(cap_table); i++) { + if (strcmp(str, cap_table[i].action)) + continue; + + caps->output_action |= cap_table[i].output; + if (cap_table[i].output && + caps->output_size < cap_table[i].size) + caps->output_size = cap_table[i].size; + + caps->input_action |= cap_table[i].input; + if (cap_table[i].input && + caps->input_size < cap_table[i].size) + caps->input_size = cap_table[i].size; + + break; + } + + if (!strcmp(str, "PublicOOB")) + caps->pub_type = 1; + else if (!strcmp(str, "StaticOOB")) + caps->static_type = 1; + } + +} + +static void parse_oob_info(struct mesh_agent_prov_caps *caps, + struct l_dbus_message_iter *property) +{ + struct l_dbus_message_iter iter_oob; + uint32_t i; + const char *str; + + if (!l_dbus_message_iter_get_variant(property, "as", &iter_oob)) + return; + + while (l_dbus_message_iter_next_entry(&iter_oob, &str)) { + for (i = 0; i < L_ARRAY_SIZE(oob_table); i++) { + if (strcmp(str, oob_table[i].oob)) + continue; + caps->oob_info |= oob_table[i].mask; + } + } +} + +static void agent_free(void *agent_data) +{ + struct mesh_agent *agent = agent_data; + int err; + mesh_agent_cb_t simple_cb; + mesh_agent_key_cb_t key_cb; + mesh_agent_number_cb_t number_cb; + + if (!l_queue_find(agents, simple_match, agent)) + return; + + err = MESH_ERROR_DOES_NOT_EXIST; + + if (agent->req && agent->req->cb) { + struct agent_request *req = agent->req; + + switch (req->type) { + case MESH_AGENT_REQUEST_PUSH: + case MESH_AGENT_REQUEST_TWIST: + case MESH_AGENT_REQUEST_IN_NUMERIC: + number_cb = req->cb; + number_cb(req->user_data, err, 0); + break; + case MESH_AGENT_REQUEST_IN_ALPHA: + case MESH_AGENT_REQUEST_STATIC_OOB: + case MESH_AGENT_REQUEST_PRIVATE_KEY: + case MESH_AGENT_REQUEST_PUBLIC_KEY: + key_cb = req->cb; + key_cb(req->user_data, err, NULL, 0); + break; + case MESH_AGENT_REQUEST_BLINK: + case MESH_AGENT_REQUEST_BEEP: + case MESH_AGENT_REQUEST_VIBRATE: + case MESH_AGENT_REQUEST_OUT_NUMERIC: + case MESH_AGENT_REQUEST_OUT_ALPHA: + simple_cb = agent->req->cb; + simple_cb(req->user_data, err); + default: + break; + } + + l_dbus_message_unref(req->msg); + l_free(req); + } + + l_free(agent->path); + l_free(agent->owner); +} + +void mesh_agent_remove(struct mesh_agent *agent) +{ + if (!agent || !l_queue_find(agents, simple_match, agent)) + return; + + agent_free(agent); + l_queue_remove(agents, agent); +} + +void mesh_agent_cleanup(void) +{ + if (!agents) + return; + + l_queue_destroy(agents, agent_free); + +} + +void mesh_agent_init(void) +{ + if (!agents) + agents = l_queue_new(); +} + +struct mesh_agent *mesh_agent_create(const char *path, const char *owner, + struct l_dbus_message_iter *properties) +{ + struct mesh_agent *agent; + const char *key, *uri_string; + struct l_dbus_message_iter variant; + + agent = l_new(struct mesh_agent, 1); + + while (l_dbus_message_iter_next_entry(properties, &key, &variant)) { + if (!strcmp(key, "Capabilities")) { + parse_prov_caps(&agent->caps, &variant); + } else if (!strcmp(key, "URI")) { + l_dbus_message_iter_get_variant(&variant, "s", + &uri_string); + /* TODO: compute hash */ + } else if (!strcmp(key, "OutOfBandInfo")) { + parse_oob_info(&agent->caps, &variant); + } + } + + agent->owner = l_strdup(owner); + agent->path = l_strdup(path); + + l_queue_push_tail(agents, agent); + + return agent; +} + +struct mesh_agent_prov_caps *mesh_agent_get_caps(struct mesh_agent *agent) +{ + if (!agent || !l_queue_find(agents, simple_match, agent)) + return NULL; + + return &agent->caps; +} + +static struct agent_request *create_request(agent_request_type_t type, + void *cb, void *data) +{ + struct agent_request *req; + + req = l_new(struct agent_request, 1); + + req->type = type; + req->cb = cb; + req->user_data = data; + + return req; +} + +static int get_reply_error(struct l_dbus_message *reply) +{ + const char *name, *desc; + + if (l_dbus_message_is_error(reply)) { + + l_dbus_message_get_error(reply, &name, &desc); + l_error("Agent failed output action (%s), %s", name, desc); + return MESH_ERROR_FAILED; + } + + return MESH_ERROR_NONE; +} + +static void simple_reply(struct l_dbus_message *reply, void *user_data) +{ + struct mesh_agent *agent = user_data; + struct agent_request *req; + mesh_agent_cb_t cb; + int err; + + if (!l_queue_find(agents, simple_match, agent) || !agent->req) + return; + + req = agent->req; + + err = get_reply_error(reply); + + l_dbus_message_unref(req->msg); + + if (req->cb) { + cb = req->cb; + cb(req->user_data, err); + } + + l_free(req); + agent->req = NULL; +} + +static void numeric_reply(struct l_dbus_message *reply, void *user_data) +{ + struct mesh_agent *agent = user_data; + struct agent_request *req; + mesh_agent_number_cb_t cb; + uint32_t count; + int err; + + if (!l_queue_find(agents, simple_match, agent) || !agent->req) + return; + + req = agent->req; + + err = get_reply_error(reply); + + count = 0; + + if (err == MESH_ERROR_NONE) { + if (!l_dbus_message_get_arguments(reply, "u", &count)) { + l_error("Failed to retrieve numeric input"); + err = MESH_ERROR_FAILED; + } + } + + l_dbus_message_unref(req->msg); + + if (req->cb) { + cb = req->cb; + cb(req->user_data, err, count); + } + + l_free(req); + agent->req = NULL; +} + +static void key_reply(struct l_dbus_message *reply, void *user_data) +{ + struct mesh_agent *agent = user_data; + struct agent_request *req; + mesh_agent_key_cb_t cb; + struct l_dbus_message_iter iter_array; + uint32_t n = 0, expected_len = 0; + uint8_t buf[64]; + int err; + + if (!l_queue_find(agents, simple_match, agent) || !agent->req) + return; + + req = agent->req; + + err = get_reply_error(reply); + + if (err != MESH_ERROR_NONE) + goto done; + + if (!l_dbus_message_get_arguments(reply, "au", &iter_array)) { + l_error("Failed to retrieve key input"); + err = MESH_ERROR_FAILED; + goto done; + } + + if (!l_dbus_message_iter_get_fixed_array(&iter_array, buf, &n)) { + l_error("Failed to retrieve key input"); + err = MESH_ERROR_FAILED; + goto done; + } + + if (req->type == MESH_AGENT_REQUEST_PRIVATE_KEY) + expected_len = 32; + else if (MESH_AGENT_REQUEST_PUBLIC_KEY) + expected_len = 64; + else + expected_len = 16; + + if (n != expected_len) { + l_error("Bad response length: %u (need %u)", n, expected_len); + err = MESH_ERROR_FAILED; + n = 0; + } + +done: + l_dbus_message_unref(req->msg); + + if (req->cb) { + cb = req->cb; + cb(req->user_data, err, buf, n); + } + + l_free(req); + agent->req = NULL; +} + +static int output_request(struct mesh_agent *agent, const char *action, + agent_request_type_t type, uint32_t cnt, + void *cb, void *user_data) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + struct l_dbus_message_builder *builder; + + if (!l_queue_find(agents, simple_match, agent)) + return MESH_ERROR_DOES_NOT_EXIST; + + if (agent->req) + return MESH_ERROR_BUSY; + + agent->req = create_request(type, cb, user_data); + msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, + MESH_PROVISION_AGENT_INTERFACE, + "DisplayNumeric"); + + builder = l_dbus_message_builder_new(msg); + l_dbus_message_builder_append_basic(builder, 's', action); + l_dbus_message_builder_append_basic(builder, 'u', &cnt); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + l_debug("Send DisplayNumeric request to %s %s", + agent->owner, agent->path); + + l_dbus_send_with_reply(dbus_get_bus(), msg, simple_reply, agent, + NULL); + + agent->req->msg = l_dbus_message_ref(msg); + + return MESH_ERROR_NONE; +} + +static int prompt_input(struct mesh_agent *agent, const char *action, + agent_request_type_t type, bool numeric, + void *cb, void *user_data) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + struct l_dbus_message_builder *builder; + const char *method_name; + l_dbus_message_func_t reply_cb; + + if (!l_queue_find(agents, simple_match, agent)) + return MESH_ERROR_DOES_NOT_EXIST; + + if (agent->req) + return MESH_ERROR_BUSY; + + agent->req = create_request(type, cb, user_data); + + method_name = numeric ? "PromptNumeric" : "PromptStatic"; + + msg = l_dbus_message_new_method_call(dbus, agent->owner, + agent->path, + MESH_PROVISION_AGENT_INTERFACE, + method_name); + + builder = l_dbus_message_builder_new(msg); + l_dbus_message_builder_append_basic(builder, 's', action); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + l_debug("Send \"%s\" input request to %s %s", action, + agent->owner, agent->path); + + reply_cb = numeric ? numeric_reply : key_reply; + + l_dbus_send_with_reply(dbus_get_bus(), msg, reply_cb, agent, NULL); + + agent->req->msg = l_dbus_message_ref(msg); + + return MESH_ERROR_NONE; +} + +static int request_key(struct mesh_agent *agent, + agent_request_type_t type, + void *cb, void *user_data) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + const char *method_name; + + if (!l_queue_find(agents, simple_match, agent)) + return MESH_ERROR_DOES_NOT_EXIST; + + if (agent->req) + return MESH_ERROR_BUSY; + + agent->req = create_request(type, cb, user_data); + + method_name = (type == MESH_AGENT_REQUEST_PRIVATE_KEY) ? + "PrivateKey" : "PublicKey"; + + msg = l_dbus_message_new_method_call(dbus, agent->owner, + agent->path, + MESH_PROVISION_AGENT_INTERFACE, + method_name); + + l_debug("Send key request to %s %s", agent->owner, agent->path); + + l_dbus_send_with_reply(dbus_get_bus(), msg, key_reply, agent, NULL); + + agent->req->msg = l_dbus_message_ref(msg); + + return MESH_ERROR_NONE; +} + +int mesh_agent_display_string(struct mesh_agent *agent, const char *str, + mesh_agent_cb_t cb, void *user_data) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + struct l_dbus_message_builder *builder; + + if (!l_queue_find(agents, simple_match, agent)) + return MESH_ERROR_DOES_NOT_EXIST; + + if (agent->req) + return MESH_ERROR_BUSY; + + agent->req = create_request(MESH_AGENT_REQUEST_OUT_ALPHA, + cb, user_data); + msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, + MESH_PROVISION_AGENT_INTERFACE, + "DisplayString"); + + builder = l_dbus_message_builder_new(msg); + l_dbus_message_builder_append_basic(builder, 's', str); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + l_debug("Send DisplayString request to %s %s", + agent->owner, agent->path); + + l_dbus_send_with_reply(dbus_get_bus(), msg, simple_reply, agent, + NULL); + + agent->req->msg = l_dbus_message_ref(msg); + + return MESH_ERROR_NONE; + +} + +int mesh_agent_display_number(struct mesh_agent *agent, bool initiator, + uint8_t action, uint32_t count, + mesh_agent_cb_t cb, void *user_data) +{ + const char *str_type; + agent_request_type_t type; + + type = action; + + if (initiator) + type = action + MESH_AGENT_REQUEST_PUSH; + + if (type >= L_ARRAY_SIZE(cap_table)) + return MESH_ERROR_INVALID_ARGS; + + str_type = cap_table[type].action; + + return output_request(agent, str_type, type, count, cb, user_data); +} + +int mesh_agent_prompt_number(struct mesh_agent *agent, bool initiator, + uint8_t action, + mesh_agent_number_cb_t cb, + void *user_data) +{ + const char *str_type; + agent_request_type_t type; + + type = action; + + if (!initiator) + type = action + MESH_AGENT_REQUEST_PUSH; + + if (type >= L_ARRAY_SIZE(cap_table)) + return MESH_ERROR_INVALID_ARGS; + + str_type = cap_table[type].action; + + return prompt_input(agent, str_type, type, true, cb, user_data); +} + +int mesh_agent_prompt_alpha(struct mesh_agent *agent, mesh_agent_key_cb_t cb, + void *user_data) +{ + return prompt_input(agent, "in-alpha", MESH_AGENT_REQUEST_IN_ALPHA, + false, cb, user_data); +} + +int mesh_agent_request_static(struct mesh_agent *agent, mesh_agent_key_cb_t cb, + void *user_data) +{ + return prompt_input(agent, "static-oob", MESH_AGENT_REQUEST_STATIC_OOB, + false, cb, user_data); +} + +int mesh_agent_request_private_key(struct mesh_agent *agent, + mesh_agent_key_cb_t cb, void *user_data) +{ + return request_key(agent, MESH_AGENT_REQUEST_PRIVATE_KEY, cb, + user_data); + +} + +int mesh_agent_request_public_key(struct mesh_agent *agent, + mesh_agent_key_cb_t cb, void *user_data) +{ + return request_key(agent, MESH_AGENT_REQUEST_PUBLIC_KEY, cb, + user_data); +} + +void mesh_agent_cancel(struct mesh_agent *agent) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + + if (!l_queue_find(agents, simple_match, agent)) + return; + + msg = l_dbus_message_new_method_call(dbus, agent->owner, agent->path, + MESH_PROVISION_AGENT_INTERFACE, + "Cancel"); + l_dbus_send(dbus, msg); +} diff --git a/mesh/agent.h b/mesh/agent.h new file mode 100644 index 0000000..0a499d2 --- /dev/null +++ b/mesh/agent.h @@ -0,0 +1,69 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +struct mesh_agent; + +struct mesh_agent_prov_caps { + uint32_t uri_hash; + uint16_t oob_info; + uint16_t output_action; + uint16_t input_action; + uint8_t pub_type; + uint8_t static_type; + uint8_t output_size; + uint8_t input_size; +}; + +typedef void (*mesh_agent_cb_t) (void *user_data, int err); + +typedef void (*mesh_agent_key_cb_t) (void *user_data, int err, uint8_t *key, + uint32_t len); + +typedef void (*mesh_agent_number_cb_t) (void *user_data, int err, + uint32_t number); + +void mesh_agent_init(void); +void mesh_agent_cleanup(void); +struct mesh_agent *mesh_agent_create(const char *path, const char *owner, + struct l_dbus_message_iter *properties); + +void mesh_agent_remove(struct mesh_agent *agent); +void mesh_agent_cancel(struct mesh_agent *agent); + +struct mesh_agent_prov_caps *mesh_agent_get_caps(struct mesh_agent *agent); + +int mesh_agent_display_number(struct mesh_agent *agent, bool initiator, + uint8_t action, uint32_t count, + mesh_agent_cb_t cb, void *user_data); +int mesh_agent_prompt_number(struct mesh_agent *agent, bool initiator, + uint8_t action, mesh_agent_number_cb_t cb, + void *user_data); +int mesh_agent_prompt_alpha(struct mesh_agent *agent, mesh_agent_key_cb_t cb, + void *user_data); +int mesh_agent_request_static(struct mesh_agent *agent, mesh_agent_key_cb_t cb, + void *user_data); +int mesh_agent_request_private_key(struct mesh_agent *agent, + mesh_agent_key_cb_t cb, + void *user_data); +int mesh_agent_request_public_key(struct mesh_agent *agent, + mesh_agent_key_cb_t cb, + void *user_data); +int mesh_agent_display_string(struct mesh_agent *agent, const char *str, + mesh_agent_cb_t cb, + void *user_data); diff --git a/mesh/appkey.c b/mesh/appkey.c new file mode 100644 index 0000000..e96d522 --- /dev/null +++ b/mesh/appkey.c @@ -0,0 +1,509 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2017-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include + +#include "mesh/mesh-defs.h" + +#include "mesh/node.h" +#include "mesh/net.h" +#include "mesh/crypto.h" +#include "mesh/util.h" +#include "mesh/model.h" +#include "mesh/mesh-config.h" +#include "mesh/appkey.h" + +struct mesh_app_key { + struct l_queue *replay_cache; + uint16_t net_idx; + uint16_t app_idx; + uint8_t key[16]; + uint8_t key_aid; + uint8_t new_key[16]; + uint8_t new_key_aid; +}; + +struct mesh_msg { + uint32_t iv_index; + uint32_t seq; + uint16_t src; +}; + +static bool match_key_index(const void *a, const void *b) +{ + const struct mesh_app_key *key = a; + uint16_t idx = L_PTR_TO_UINT(b); + + return key->app_idx == idx; +} + +static bool match_replay_cache(const void *a, const void *b) +{ + const struct mesh_msg *msg = a; + uint16_t src = L_PTR_TO_UINT(b); + + return src == msg->src; +} + +static bool clean_old_iv_index(void *a, void *b) +{ + struct mesh_msg *msg = a; + uint32_t iv_index = L_PTR_TO_UINT(b); + + if (iv_index < 2) + return false; + + if (msg->iv_index < iv_index - 1) { + l_free(msg); + return true; + } + + return false; +} + +bool appkey_msg_in_replay_cache(struct mesh_net *net, uint16_t idx, + uint16_t src, uint16_t crpl, uint32_t seq, + uint32_t iv_index) +{ + struct mesh_app_key *key; + struct mesh_msg *msg; + struct l_queue *app_keys; + + app_keys = mesh_net_get_app_keys(net); + if (!app_keys) + return false; + + l_debug("Test Replay src: %4.4x seq: %6.6x iv: %8.8x", + src, seq, iv_index); + + key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(idx)); + + if (!key) + return false; + + msg = l_queue_find(key->replay_cache, match_replay_cache, + L_UINT_TO_PTR(src)); + + if (msg) { + if (iv_index > msg->iv_index) { + msg->seq = seq; + msg->iv_index = iv_index; + return false; + } + + if (seq < msg->seq) { + l_debug("Ignoring packet with lower sequence number"); + return true; + } + + if (seq == msg->seq) { + l_debug("Message already processed (duplicate)"); + return true; + } + + msg->seq = seq; + + return false; + } + + l_debug("New Entry for %4.4x", src); + if (key->replay_cache == NULL) + key->replay_cache = l_queue_new(); + + /* Replay Cache is fixed sized */ + if (l_queue_length(key->replay_cache) >= crpl) { + int ret = l_queue_foreach_remove(key->replay_cache, + clean_old_iv_index, L_UINT_TO_PTR(iv_index)); + + if (!ret) + return true; + } + + msg = l_new(struct mesh_msg, 1); + msg->src = src; + msg->seq = seq; + msg->iv_index = iv_index; + l_queue_push_head(key->replay_cache, msg); + + return false; +} + +static struct mesh_app_key *app_key_new(void) +{ + struct mesh_app_key *key = l_new(struct mesh_app_key, 1); + + key->new_key_aid = 0xFF; + key->replay_cache = l_queue_new(); + return key; +} + +static bool set_key(struct mesh_app_key *key, uint16_t app_idx, + const uint8_t *key_value, bool is_new) +{ + uint8_t key_aid; + + if (!mesh_crypto_k4(key_value, &key_aid)) + return false; + + key_aid = KEY_ID_AKF | (key_aid << KEY_AID_SHIFT); + if (!is_new) + key->key_aid = key_aid; + else + key->new_key_aid = key_aid; + + memcpy(is_new ? key->new_key : key->key, key_value, 16); + + return true; +} + +void appkey_key_free(void *data) +{ + struct mesh_app_key *key = data; + + if (!key) + return; + + l_queue_destroy(key->replay_cache, l_free); + l_free(key); +} + +bool appkey_key_init(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, + uint8_t *key_value, uint8_t *new_key_value) +{ + struct mesh_app_key *key; + struct l_queue *app_keys; + + if (net_idx > MAX_KEY_IDX || app_idx > MAX_KEY_IDX) + return false; + + app_keys = mesh_net_get_app_keys(net); + if (!app_keys) + return NULL; + + key = app_key_new(); + if (!key) + return false; + + if (!mesh_net_have_key(net, net_idx)) + return false; + + key->net_idx = net_idx; + key->app_idx = app_idx; + + if (key_value && !set_key(key, app_idx, key_value, false)) + return false; + + if (new_key_value && !set_key(key, app_idx, new_key_value, true)) + return false; + + l_queue_push_tail(app_keys, key); + + return true; +} + +const uint8_t *appkey_get_key(struct mesh_net *net, uint16_t app_idx, + uint8_t *key_aid) +{ + struct mesh_app_key *app_key; + uint8_t phase; + struct l_queue *app_keys; + + app_keys = mesh_net_get_app_keys(net); + if (!app_keys) + return NULL; + + app_key = l_queue_find(app_keys, match_key_index, + L_UINT_TO_PTR(app_idx)); + if (!app_key) + return NULL; + + if (mesh_net_key_refresh_phase_get(net, app_key->net_idx, &phase) != + MESH_STATUS_SUCCESS) + return NULL; + + if (phase != KEY_REFRESH_PHASE_TWO) { + *key_aid = app_key->key_aid; + return app_key->key; + } + + if (app_key->new_key_aid == NET_NID_INVALID) + return NULL; + + *key_aid = app_key->new_key_aid; + return app_key->new_key; +} + +int appkey_get_key_idx(struct mesh_app_key *app_key, + const uint8_t **key, uint8_t *key_aid, + const uint8_t **new_key, uint8_t *new_key_aid) +{ + if (!app_key) + return -1; + + if (key && key_aid) { + *key = app_key->key; + *key_aid = app_key->key_aid; + } + + if (new_key && new_key_aid) { + *new_key = app_key->new_key; + *new_key_aid = app_key->new_key_aid; + } + + return app_key->app_idx; +} + +bool appkey_have_key(struct mesh_net *net, uint16_t app_idx) +{ + struct mesh_app_key *key; + struct l_queue *app_keys; + + app_keys = mesh_net_get_app_keys(net); + if (!app_keys) + return false; + + key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); + + if (!key) + return false; + else + return true; +} + +uint16_t appkey_net_idx(struct mesh_net *net, uint16_t app_idx) +{ + struct mesh_app_key *key; + struct l_queue *app_keys; + + app_keys = mesh_net_get_app_keys(net); + if (!app_keys) + return NET_IDX_INVALID; + + key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); + + if (!key) + return NET_IDX_INVALID; + else + return key->net_idx; +} + +int appkey_key_update(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, + const uint8_t *new_key) +{ + struct mesh_app_key *key; + struct l_queue *app_keys; + uint8_t phase = KEY_REFRESH_PHASE_NONE; + struct mesh_node *node; + + app_keys = mesh_net_get_app_keys(net); + if (!app_keys) + return MESH_STATUS_INSUFF_RESOURCES; + + if (!mesh_net_have_key(net, net_idx)) + return MESH_STATUS_INVALID_NETKEY; + + key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); + + if (!key) + return MESH_STATUS_INVALID_APPKEY; + + if (key->net_idx != net_idx) + return MESH_STATUS_INVALID_BINDING; + + mesh_net_key_refresh_phase_get(net, net_idx, &phase); + if (phase != KEY_REFRESH_PHASE_ONE) + return MESH_STATUS_CANNOT_UPDATE; + + /* Check if the key has been already successfully updated */ + if (memcmp(new_key, key->new_key, 16) == 0) + return MESH_STATUS_SUCCESS; + + if (!set_key(key, app_idx, new_key, true)) + return MESH_STATUS_INSUFF_RESOURCES; + + node = mesh_net_node_get(net); + + if (!mesh_config_app_key_update(node_config_get(node), app_idx, + new_key)) + return MESH_STATUS_STORAGE_FAIL; + + return MESH_STATUS_SUCCESS; +} + +int appkey_key_add(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, + const uint8_t *new_key) +{ + struct mesh_app_key *key; + struct l_queue *app_keys; + struct mesh_node *node; + + app_keys = mesh_net_get_app_keys(net); + if (!app_keys) + return MESH_STATUS_INSUFF_RESOURCES; + + key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); + if (key) { + if (memcmp(new_key, key->key, 16) == 0) + return MESH_STATUS_SUCCESS; + else + return MESH_STATUS_IDX_ALREADY_STORED; + } + + if (!mesh_net_have_key(net, net_idx)) + return MESH_STATUS_INVALID_NETKEY; + + if (l_queue_length(app_keys) >= MAX_APP_KEYS) + return MESH_STATUS_INSUFF_RESOURCES; + + key = app_key_new(); + + if (!set_key(key, app_idx, new_key, false)) { + appkey_key_free(key); + return MESH_STATUS_INSUFF_RESOURCES; + } + + node = mesh_net_node_get(net); + + if (!mesh_config_app_key_add(node_config_get(node), net_idx, app_idx, + new_key)) { + appkey_key_free(key); + return MESH_STATUS_STORAGE_FAIL; + } + + key->net_idx = net_idx; + key->app_idx = app_idx; + l_queue_push_tail(app_keys, key); + + l_queue_clear(key->replay_cache, l_free); + + return MESH_STATUS_SUCCESS; +} + +int appkey_key_delete(struct mesh_net *net, uint16_t net_idx, + uint16_t app_idx) +{ + struct mesh_app_key *key; + struct l_queue *app_keys; + struct mesh_node *node; + + app_keys = mesh_net_get_app_keys(net); + if (!app_keys) + return MESH_STATUS_INVALID_APPKEY; + + key = l_queue_find(app_keys, match_key_index, L_UINT_TO_PTR(app_idx)); + + if (!key) + return MESH_STATUS_INVALID_APPKEY; + + if (key->net_idx != net_idx) + return MESH_STATUS_INVALID_NETKEY; + + node_app_key_delete(net, mesh_net_get_address(net), net_idx, app_idx); + + l_queue_remove(app_keys, key); + appkey_key_free(key); + + node = mesh_net_node_get(net); + + if (!mesh_config_app_key_del(node_config_get(node), net_idx, app_idx)) + return MESH_STATUS_STORAGE_FAIL; + + return MESH_STATUS_SUCCESS; +} + +void appkey_delete_bound_keys(struct mesh_net *net, uint16_t net_idx) +{ + const struct l_queue_entry *entry; + struct l_queue *app_keys; + + app_keys = mesh_net_get_app_keys(net); + if (!app_keys) + return; + + entry = l_queue_get_entries(app_keys); + + for (; entry; entry = entry->next) { + struct mesh_app_key *key = entry->data; + + appkey_key_delete(net, net_idx, key->app_idx); + } +} + +uint8_t appkey_list(struct mesh_net *net, uint16_t net_idx, uint8_t *buf, + uint16_t buf_size, uint16_t *size) +{ + const struct l_queue_entry *entry; + uint32_t idx_pair; + int i; + uint16_t datalen; + struct l_queue *app_keys; + + *size = 0; + + if (!mesh_net_have_key(net, net_idx)) + return MESH_STATUS_INVALID_NETKEY; + + app_keys = mesh_net_get_app_keys(net); + if (!app_keys || l_queue_isempty(app_keys)) + return MESH_STATUS_SUCCESS; + + idx_pair = 0; + i = 0; + datalen = 0; + entry = l_queue_get_entries(app_keys); + + for (; entry; entry = entry->next) { + struct mesh_app_key *key = entry->data; + + if (net_idx != key->net_idx) + continue; + + if (!(i & 0x1)) { + idx_pair = key->app_idx; + } else { + idx_pair <<= 12; + idx_pair += key->app_idx; + /* Unlikely, but check for overflow*/ + if ((datalen + 3) > buf_size) { + l_warn("Appkey list too large"); + goto done; + } + l_put_le32(idx_pair, buf); + buf += 3; + datalen += 3; + } + i++; + } + + /* Process the last app key if present */ + if (i & 0x1 && ((datalen + 2) <= buf_size)) { + l_put_le16(idx_pair, buf); + datalen += 2; + } + +done: + *size = datalen; + + return MESH_STATUS_SUCCESS; +} diff --git a/mesh/appkey.h b/mesh/appkey.h new file mode 100644 index 0000000..b3e5480 --- /dev/null +++ b/mesh/appkey.h @@ -0,0 +1,45 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 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. + * + */ + +/* TODO: get this number from configuration */ +#define MAX_APP_KEYS 32 + +struct mesh_app_key; + +bool appkey_key_init(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, + uint8_t *key_value, uint8_t *new_key_value); +void appkey_key_free(void *data); +bool appkey_msg_in_replay_cache(struct mesh_net *net, uint16_t idx, + uint16_t src, uint16_t crpl, uint32_t seq, + uint32_t iv_index); +const uint8_t *appkey_get_key(struct mesh_net *net, uint16_t app_idx, + uint8_t *key_id); +int appkey_get_key_idx(struct mesh_app_key *app_key, + const uint8_t **key, uint8_t *key_aid, + const uint8_t **new_key, uint8_t *new_key_aid); +bool appkey_have_key(struct mesh_net *net, uint16_t app_idx); +uint16_t appkey_net_idx(struct mesh_net *net, uint16_t app_idx); +int appkey_key_add(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, + const uint8_t *new_key); +int appkey_key_update(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx, + const uint8_t *new_key); +int appkey_key_delete(struct mesh_net *net, uint16_t net_idx, uint16_t app_idx); +void appkey_delete_bound_keys(struct mesh_net *net, uint16_t net_idx); +uint8_t appkey_list(struct mesh_net *net, uint16_t net_idx, uint8_t *buf, + uint16_t buf_size, uint16_t *size); diff --git a/mesh/bluetooth-mesh.conf b/mesh/bluetooth-mesh.conf new file mode 100644 index 0000000..7fbbd64 --- /dev/null +++ b/mesh/bluetooth-mesh.conf @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/mesh/bluetooth-mesh.service.in b/mesh/bluetooth-mesh.service.in new file mode 100644 index 0000000..c8afbf5 --- /dev/null +++ b/mesh/bluetooth-mesh.service.in @@ -0,0 +1,18 @@ +[Unit] +Description=Bluetooth mesh service +ConditionPathIsDirectory=/sys/class/bluetooth + +[Service] +Type=dbus +BusName=org.bluez.mesh +ExecStart=@pkglibexecdir@/bluetooth-meshd +NotifyAccess=main +LimitNPROC=1 +ProtectHome=true +ProtectSystem=full +Restart=on-failure +RestartSec=5s + +[Install] +WantedBy=bluetooth.target +Alias=dbus-org.bluez.mesh.service diff --git a/mesh/cfgmod-server.c b/mesh/cfgmod-server.c new file mode 100644 index 0000000..55cc8e9 --- /dev/null +++ b/mesh/cfgmod-server.c @@ -0,0 +1,1306 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "mesh/mesh-defs.h" +#include "mesh/node.h" +#include "mesh/net.h" +#include "mesh/appkey.h" +#include "mesh/model.h" +#include "mesh/mesh-config.h" +#include "mesh/cfgmod.h" + +#define CFG_MAX_MSG_LEN 380 + +static void send_pub_status(struct mesh_node *node, uint16_t net_idx, + uint16_t src, uint16_t dst, + uint8_t status, uint16_t ele_addr, uint16_t pub_addr, + uint32_t mod_id, uint16_t idx, bool cred_flag, + uint8_t ttl, uint8_t period, uint8_t retransmit) +{ + uint8_t msg[16]; + size_t n; + + n = mesh_model_opcode_set(OP_CONFIG_MODEL_PUB_STATUS, msg); + msg[n++] = status; + l_put_le16(ele_addr, msg + n); + n += 2; + l_put_le16(pub_addr, msg + n); + n += 2; + idx |= cred_flag ? CREDFLAG_MASK : 0; + l_put_le16(idx, msg + n); + n += 2; + msg[n++] = ttl; + msg[n++] = period; + msg[n++] = retransmit; + + if (mod_id < 0x10000 || mod_id > VENDOR_ID_MASK) { + l_put_le16(mod_id, msg + n); + n += 2; + } else { + l_put_le16(mod_id >> 16, msg + n); + n += 2; + l_put_le16(mod_id, msg + n); + n += 2; + } + + mesh_model_send(node, dst, src, APP_IDX_DEV_LOCAL, net_idx, DEFAULT_TTL, + msg, n); +} + +static bool config_pub_get(struct mesh_node *node, uint16_t net_idx, + uint16_t src, uint16_t dst, + const uint8_t *pkt, uint16_t size) +{ + uint32_t mod_id; + uint16_t ele_addr; + int ele_idx; + struct mesh_model_pub *pub = NULL; + int status; + + if (size == 4) { + mod_id = l_get_le16(pkt + 2); + mod_id |= VENDOR_ID_MASK; + } else if (size == 6) { + mod_id = l_get_le16(pkt + 2) << 16; + mod_id |= l_get_le16(pkt + 4); + } else + return false; + + ele_addr = l_get_le16(pkt); + ele_idx = node_get_element_idx(node, ele_addr); + + if (ele_idx >= 0) + pub = mesh_model_pub_get(node, ele_addr, mod_id, &status); + else + status = MESH_STATUS_INVALID_ADDRESS; + + if (pub && status == MESH_STATUS_SUCCESS) + send_pub_status(node, net_idx, src, dst, status, ele_addr, + pub->addr, mod_id, pub->idx, pub->credential, + pub->ttl, pub->period, pub->retransmit); + else + send_pub_status(node, net_idx, src, dst, status, ele_addr, 0, + mod_id, 0, 0, 0, 0, 0); + return true; +} + +static bool config_pub_set(struct mesh_node *node, uint16_t net_idx, + uint16_t src, uint16_t dst, + const uint8_t *pkt, uint16_t size, + bool unreliable) +{ + uint32_t mod_id; + uint16_t ele_addr, idx, ota = 0; + const uint8_t *pub_addr; + uint16_t test_addr; + uint8_t ttl, period; + uint8_t retransmit; + int status; + bool cred_flag, b_virt = false; + bool vendor = false; + + switch (size) { + default: + return false; + + case 11: + idx = l_get_le16(pkt + 4); + ttl = pkt[6]; + period = pkt[7]; + retransmit = pkt[8]; + mod_id = l_get_le16(pkt + 9); + mod_id |= VENDOR_ID_MASK; + break; + + case 13: + idx = l_get_le16(pkt + 4); + ttl = pkt[6]; + period = pkt[7]; + retransmit = pkt[8]; + mod_id = l_get_le16(pkt + 9) << 16; + mod_id |= l_get_le16(pkt + 11); + vendor = true; + break; + + case 25: + b_virt = true; + idx = l_get_le16(pkt + 18); + ttl = pkt[20]; + period = pkt[21]; + retransmit = pkt[22]; + mod_id = l_get_le16(pkt + 23); + mod_id |= VENDOR_ID_MASK; + break; + + case 27: + b_virt = true; + idx = l_get_le16(pkt + 18); + ttl = pkt[20]; + period = pkt[21]; + retransmit = pkt[22]; + mod_id = l_get_le16(pkt + 23) << 16; + mod_id |= l_get_le16(pkt + 25); + vendor = true; + break; + } + + ele_addr = l_get_le16(pkt); + pub_addr = pkt + 2; + + /* Doesn't accept out-of-range TTLs */ + if (ttl > TTL_MASK && ttl != DEFAULT_TTL) + return false; + + /* Get cred_flag */ + cred_flag = !!(CREDFLAG_MASK & idx); + + /* Ignore non-IDX bits */ + idx &= APP_IDX_MASK; + + /* Doesn't accept virtual seeming addresses */ + test_addr = l_get_le16(pub_addr); + if (!b_virt && test_addr > 0x7fff && test_addr < 0xc000) + return false; + + status = mesh_model_pub_set(node, ele_addr, mod_id, pub_addr, idx, + cred_flag, ttl, period, retransmit, + b_virt, &ota); + + l_debug("pub_set: status %d, ea %4.4x, ota: %4.4x, mod: %x, idx: %3.3x", + status, ele_addr, ota, mod_id, idx); + + if (IS_UNASSIGNED(ota) && !b_virt) { + ttl = period = idx = 0; + + /* Remove model publication from config file */ + if (status == MESH_STATUS_SUCCESS) + mesh_config_model_pub_del(node_config_get(node), + ele_addr, vendor ? mod_id : mod_id & 0x0000ffff, + vendor); + goto done; + } + + if (status == MESH_STATUS_SUCCESS) { + struct mesh_config_pub db_pub = { + .virt = b_virt, + .addr = ota, + .idx = idx, + .ttl = ttl, + .credential = cred_flag, + .period = period, + .count = retransmit >> 5, + .interval = ((0x1f & retransmit) + 1) * 50 + }; + + if (b_virt) + memcpy(db_pub.virt_addr, pub_addr, 16); + + /* Save model publication to config file */ + if (!mesh_config_model_pub_add(node_config_get(node), ele_addr, + vendor ? mod_id : mod_id & 0x0000ffff, + vendor, &db_pub)) + status = MESH_STATUS_STORAGE_FAIL; + } + +done: + if (!unreliable) + send_pub_status(node, net_idx, src, dst, status, ele_addr, ota, + mod_id, idx, cred_flag, ttl, period, retransmit); + return true; +} + +static void send_sub_status(struct mesh_node *node, uint16_t net_idx, + uint16_t src, uint16_t dst, + uint8_t status, uint16_t ele_addr, + uint16_t addr, uint32_t mod) +{ + uint8_t msg[12]; + int n = mesh_model_opcode_set(OP_CONFIG_MODEL_SUB_STATUS, msg); + + msg[n++] = status; + l_put_le16(ele_addr, msg + n); + n += 2; + l_put_le16(addr, msg + n); + n += 2; + if (mod >= 0x10000 && mod < VENDOR_ID_MASK) { + l_put_le16(mod >> 16, msg + n); + l_put_le16(mod, msg + n + 2); + n += 4; + } else { + l_put_le16(mod, msg + n); + n += 2; + } + + mesh_model_send(node, dst, src, APP_IDX_DEV_LOCAL, net_idx, + DEFAULT_TTL, msg, n); +} + +static bool config_sub_get(struct mesh_node *node, uint16_t net_idx, + uint16_t src, uint16_t dst, + const uint8_t *pkt, uint16_t size) +{ + uint16_t ele_addr; + uint32_t mod_id; + uint16_t n = 0; + int status; + uint8_t *msg_status; + uint16_t buf_size; + uint8_t msg[5 + sizeof(uint16_t) * MAX_GRP_PER_MOD]; + + /* Incoming message has already been size-checked */ + ele_addr = l_get_le16(pkt); + + switch (size) { + default: + l_debug("Bad Len Cfg_Pub_Set: %d", size); + return false; + + case 4: + mod_id = l_get_le16(pkt + 2); + n = mesh_model_opcode_set(OP_CONFIG_MODEL_SUB_LIST, msg); + msg_status = msg + n; + msg[n++] = 0; + l_put_le16(ele_addr, msg + n); + n += 2; + l_put_le16(mod_id, msg + n); + n += 2; + mod_id |= VENDOR_ID_MASK; + break; + + case 6: + mod_id = l_get_le16(pkt + 2) << 16; + mod_id |= l_get_le16(pkt + 4); + n = mesh_model_opcode_set(OP_CONFIG_VEND_MODEL_SUB_LIST, msg); + msg_status = msg + n; + msg[n++] = 0; + l_put_le16(ele_addr, msg + n); + n += 2; + l_put_le16(mod_id >> 16, msg + n); + n += 2; + l_put_le16(mod_id, msg + n); + n += 2; + break; + } + + buf_size = sizeof(uint16_t) * MAX_GRP_PER_MOD; + status = mesh_model_sub_get(node, ele_addr, mod_id, msg + n, buf_size, + &size); + + if (status == MESH_STATUS_SUCCESS) + n += size; + + *msg_status = (uint8_t) status; + + mesh_model_send(node, dst, src, APP_IDX_DEV_LOCAL, net_idx, DEFAULT_TTL, + msg, n); + return true; +} + +static bool save_config_sub(struct mesh_node *node, uint16_t ele_addr, + uint32_t mod_id, bool vendor, + const uint8_t *addr, bool virt, + uint16_t grp, uint32_t opcode) +{ + struct mesh_config_sub db_sub = { + .virt = virt, + .src.addr = grp + }; + + if (virt) + memcpy(db_sub.src.virt_addr, addr, 16); + + if (opcode == OP_CONFIG_MODEL_SUB_VIRT_OVERWRITE || + opcode == OP_CONFIG_MODEL_SUB_OVERWRITE) + mesh_config_model_sub_del_all(node_config_get(node), + ele_addr, vendor ? mod_id : mod_id & 0x0000ffff, + vendor); + + if (opcode != OP_CONFIG_MODEL_SUB_VIRT_DELETE && + opcode != OP_CONFIG_MODEL_SUB_DELETE) + return mesh_config_model_sub_add(node_config_get(node), + ele_addr, + vendor ? mod_id : mod_id & 0x0000ffff, + vendor, &db_sub); + else + return mesh_config_model_sub_del(node_config_get(node), + ele_addr, + vendor ? mod_id : mod_id & 0x0000ffff, + vendor, &db_sub); +} + +static void config_sub_set(struct mesh_node *node, uint16_t net_idx, + uint16_t src, uint16_t dst, + const uint8_t *pkt, uint16_t size, + bool virt, uint32_t opcode) +{ + uint16_t grp, ele_addr; + bool unreliable = !!(opcode & OP_UNRELIABLE); + uint32_t mod_id; + const uint8_t *addr = NULL; + int status = MESH_STATUS_SUCCESS; + bool vendor = false; + + switch (size) { + default: + l_error("Bad Len Cfg_Sub_Set: %d", size); + return; + case 4: + if (opcode != OP_CONFIG_MODEL_SUB_DELETE_ALL) + return; + mod_id = l_get_le16(pkt + 2); + mod_id |= VENDOR_ID_MASK; + break; + case 6: + if (virt) + return; + if (opcode != OP_CONFIG_MODEL_SUB_DELETE_ALL) { + mod_id = l_get_le16(pkt + 4); + mod_id |= VENDOR_ID_MASK; + } else { + mod_id = l_get_le16(pkt + 2) << 16; + mod_id |= l_get_le16(pkt + 4); + vendor = true; + } + break; + case 8: + if (virt) + return; + mod_id = l_get_le16(pkt + 4) << 16; + mod_id |= l_get_le16(pkt + 6); + vendor = true; + break; + case 20: + if (!virt) + return; + mod_id = l_get_le16(pkt + 18); + mod_id |= VENDOR_ID_MASK; + break; + case 22: + if (!virt) + return; + mod_id = l_get_le16(pkt + 18) << 16; + mod_id |= l_get_le16(pkt + 20); + break; + } + + ele_addr = l_get_le16(pkt); + + if (opcode != OP_CONFIG_MODEL_SUB_DELETE_ALL) { + addr = pkt + 2; + grp = l_get_le16(addr); + } else + grp = UNASSIGNED_ADDRESS; + + switch (opcode & ~OP_UNRELIABLE) { + default: + l_debug("Bad opcode: %x", opcode); + return; + + case OP_CONFIG_MODEL_SUB_DELETE_ALL: + status = mesh_model_sub_del_all(node, ele_addr, mod_id); + + if (status == MESH_STATUS_SUCCESS) + mesh_config_model_sub_del_all(node_config_get(node), + ele_addr, vendor ? mod_id : mod_id & 0x0000ffff, + vendor); + break; + + case OP_CONFIG_MODEL_SUB_VIRT_OVERWRITE: + grp = UNASSIGNED_ADDRESS; + /* Fall Through */ + case OP_CONFIG_MODEL_SUB_OVERWRITE: + status = mesh_model_sub_ovr(node, ele_addr, mod_id, + addr, virt, &grp); + + if (status == MESH_STATUS_SUCCESS) + save_config_sub(node, ele_addr, mod_id, vendor, addr, + virt, grp, opcode); + break; + case OP_CONFIG_MODEL_SUB_VIRT_ADD: + grp = UNASSIGNED_ADDRESS; + /* Fall Through */ + case OP_CONFIG_MODEL_SUB_ADD: + status = mesh_model_sub_add(node, ele_addr, mod_id, + addr, virt, &grp); + + if (status == MESH_STATUS_SUCCESS && + !save_config_sub(node, ele_addr, mod_id, vendor, + addr, virt, grp, opcode)) + status = MESH_STATUS_STORAGE_FAIL; + + break; + case OP_CONFIG_MODEL_SUB_VIRT_DELETE: + grp = UNASSIGNED_ADDRESS; + /* Fall Through */ + case OP_CONFIG_MODEL_SUB_DELETE: + status = mesh_model_sub_del(node, ele_addr, mod_id, + addr, virt, &grp); + + if (status == MESH_STATUS_SUCCESS) + save_config_sub(node, ele_addr, mod_id, vendor, addr, + virt, grp, opcode); + + break; + } + + if (!unreliable) + send_sub_status(node, net_idx, src, dst, status, ele_addr, + grp, mod_id); + +} + +static void send_model_app_status(struct mesh_node *node, uint16_t net_idx, + uint16_t src, uint16_t dst, + uint8_t status, uint16_t addr, + uint32_t id, uint16_t idx) +{ + uint8_t msg[12]; + size_t n = mesh_model_opcode_set(OP_MODEL_APP_STATUS, msg); + + msg[n++] = status; + l_put_le16(addr, msg + n); + n += 2; + l_put_le16(idx, msg + n); + n += 2; + if (id >= 0x10000 && id < VENDOR_ID_MASK) { + l_put_le16(id >> 16, msg + n); + n += 2; + } + l_put_le16(id, msg + n); + n += 2; + + mesh_model_send(node, dst, src, APP_IDX_DEV_LOCAL, net_idx, DEFAULT_TTL, + msg, n); +} + +static void model_app_list(struct mesh_node *node, uint16_t net_idx, + uint16_t src, uint16_t dst, + const uint8_t *pkt, uint16_t size) +{ + uint16_t ele_addr; + uint32_t mod_id = 0xffff; + uint8_t *msg = NULL; + uint8_t *status; + uint16_t n, buf_size; + int result; + + buf_size = MAX_BINDINGS * sizeof(uint16_t); + msg = l_malloc(7 + buf_size); + if (!msg) + return; + + ele_addr = l_get_le16(pkt); + + switch (size) { + default: + l_free(msg); + return; + case 4: + n = mesh_model_opcode_set(OP_MODEL_APP_LIST, msg); + status = msg + n; + mod_id = l_get_le16(pkt + 2); + l_put_le16(ele_addr, msg + 1 + n); + l_put_le16(mod_id, msg + 3 + n); + mod_id |= VENDOR_ID_MASK; + n += 5; + break; + case 6: + n = mesh_model_opcode_set(OP_VEND_MODEL_APP_LIST, msg); + status = msg + n; + mod_id = l_get_le16(pkt + 2) << 16; + mod_id |= l_get_le16(pkt + 4); + + l_put_le16(ele_addr, msg + 1 + n); + l_put_le16(mod_id >> 16, msg + 3 + n); + l_put_le16(mod_id, msg + 5 + n); + n += 7; + break; + } + + + result = mesh_model_get_bindings(node, ele_addr, mod_id, msg + n, + buf_size, &size); + n += size; + + if (result >= 0) { + *status = result; + mesh_model_send(node, dst, src, APP_IDX_DEV_LOCAL, net_idx, + DEFAULT_TTL, msg, n); + } + + l_free(msg); +} + +static bool model_app_bind(struct mesh_node *node, uint16_t net_idx, + uint16_t src, uint16_t dst, + const uint8_t *pkt, uint16_t size, + bool unbind) +{ + uint16_t ele_addr; + uint32_t mod_id; + uint16_t idx; + int result; + + switch (size) { + default: + return false; + + case 6: + mod_id = l_get_le16(pkt + 4); + mod_id |= VENDOR_ID_MASK; + break; + case 8: + mod_id = l_get_le16(pkt + 4) << 16; + mod_id |= l_get_le16(pkt + 6); + break; + } + + ele_addr = l_get_le16(pkt); + idx = l_get_le16(pkt + 2); + + if (idx > 0xfff) + return false; + + if (unbind) + result = mesh_model_binding_del(node, ele_addr, mod_id, idx); + else + result = mesh_model_binding_add(node, ele_addr, mod_id, idx); + + send_model_app_status(node, net_idx, src, dst, result, ele_addr, + mod_id, idx); + + return true; +} + +static void hb_pub_timeout_func(struct l_timeout *timeout, void *user_data) +{ + struct mesh_net *net = user_data; + struct mesh_net_heartbeat *hb = mesh_net_heartbeat_get(net); + + mesh_net_heartbeat_send(net); + + if (hb->pub_count != 0xffff) + hb->pub_count--; + if (hb->pub_count > 0) + l_timeout_modify(hb->pub_timer, hb->pub_period); + else { + l_timeout_remove(hb->pub_timer); + hb->pub_timer = NULL; + } + l_debug("%d left", hb->pub_count); +} + +static void update_hb_pub_timer(struct mesh_net *net, + struct mesh_net_heartbeat *hb) +{ + if (IS_UNASSIGNED(hb->pub_dst) || hb->pub_count == 0) { + l_timeout_remove(hb->pub_timer); + hb->pub_timer = NULL; + return; + } + + if (!hb->pub_timer) + hb->pub_timer = l_timeout_create(hb->pub_period, + hb_pub_timeout_func, net, NULL); + else + l_timeout_modify(hb->pub_timer, hb->pub_period); +} + +static void hb_sub_timeout_func(struct l_timeout *timeout, void *user_data) +{ + struct mesh_net *net = user_data; + struct mesh_net_heartbeat *hb = mesh_net_heartbeat_get(net); + + l_debug("HB Subscription Ended"); + l_timeout_remove(hb->sub_timer); + hb->sub_timer = NULL; + hb->sub_enabled = false; +} + +static uint8_t uint32_to_log(uint32_t value) +{ + uint32_t val = 1; + uint8_t ret = 1; + + if (!value) + return 0; + else if (value > 0x10000) + return 0xff; + + while (val < value) { + val <<= 1; + ret++; + } + + return ret; +} + +static uint32_t log_to_uint32(uint8_t log, uint8_t offset) +{ + if (!log) + return 0x0000; + else if (log > 0x11) + return 0xffff; + else + return (1 << (log - offset)); +} + + +static int hb_subscription_set(struct mesh_net *net, uint16_t src, + uint16_t dst, uint8_t period_log) +{ + struct mesh_net_heartbeat *hb = mesh_net_heartbeat_get(net); + struct timeval time_now; + + /* SRC must be Unicast, DST can be any legal address except Virtual */ + if ((!IS_UNASSIGNED(src) && !IS_UNICAST(src)) || IS_VIRTUAL(dst)) + return -1; + + /* Check if the subscription should be disabled */ + if (IS_UNASSIGNED(src) || IS_UNASSIGNED(dst)) { + if (IS_GROUP(hb->sub_dst)) + mesh_net_dst_unreg(net, hb->sub_dst); + + l_timeout_remove(hb->sub_timer); + hb->sub_timer = NULL; + hb->sub_enabled = false; + hb->sub_dst = UNASSIGNED_ADDRESS; + hb->sub_src = UNASSIGNED_ADDRESS; + hb->sub_count = 0; + hb->sub_period = 0; + hb->sub_min_hops = 0; + hb->sub_max_hops = 0; + return MESH_STATUS_SUCCESS; + } else if (!period_log && src == hb->sub_src && dst == hb->sub_dst) { + /* Preserve collected data, but disable */ + l_timeout_remove(hb->sub_timer); + hb->sub_timer = NULL; + hb->sub_enabled = false; + hb->sub_period = 0; + return MESH_STATUS_SUCCESS; + } + + if (hb->sub_dst != dst) { + if (IS_GROUP(hb->sub_dst)) + mesh_net_dst_unreg(net, hb->sub_dst); + if (IS_GROUP(dst)) + mesh_net_dst_reg(net, dst); + } + + hb->sub_enabled = !!period_log; + hb->sub_src = src; + hb->sub_dst = dst; + hb->sub_count = 0; + hb->sub_period = log_to_uint32(period_log, 1); + hb->sub_min_hops = 0x00; + hb->sub_max_hops = 0x00; + + gettimeofday(&time_now, NULL); + hb->sub_start = time_now.tv_sec; + + if (!hb->sub_enabled) { + l_timeout_remove(hb->sub_timer); + hb->sub_timer = NULL; + return MESH_STATUS_SUCCESS; + } + + hb->sub_min_hops = 0xff; + + if (!hb->sub_timer) + hb->sub_timer = l_timeout_create(hb->sub_period, + hb_sub_timeout_func, net, NULL); + else + l_timeout_modify(hb->sub_timer, hb->sub_period); + + return MESH_STATUS_SUCCESS; +} + +static void node_reset(struct l_timeout *timeout, void *user_data) +{ + l_debug("Node Reset"); + l_timeout_remove(timeout); + l_main_quit(); +} + +static bool cfg_srv_pkt(uint16_t src, uint32_t dst, uint16_t unicast, + uint16_t app_idx, uint16_t net_idx, + const uint8_t *data, uint16_t size, + uint8_t ttl, const void *user_data) +{ + struct mesh_node *node = (struct mesh_node *) user_data; + struct mesh_net *net; + const uint8_t *pkt = data; + struct timeval time_now; + uint32_t opcode, tmp32; + int b_res = MESH_STATUS_SUCCESS; + uint8_t msg[11]; + uint8_t *long_msg = NULL; + struct mesh_net_heartbeat *hb; + uint16_t n_idx, a_idx; + uint8_t state, status; + uint8_t phase; + bool virt = false; + uint8_t count; + uint16_t interval; + uint16_t n; + + if (app_idx != APP_IDX_DEV_LOCAL) + return false; + + if (mesh_model_opcode_get(pkt, size, &opcode, &n)) { + size -= n; + pkt += n; + } else + return false; + + net = node_get_net(node); + hb = mesh_net_heartbeat_get(net); + l_debug("CONFIG-SRV-opcode 0x%x size %u idx %3.3x", opcode, size, + net_idx); + + n = 0; + + switch (opcode) { + default: + return false; + + case OP_DEV_COMP_GET: + if (size != 1) + return false; + + /* Only page 0 is currently supported */ + if (pkt[0] != 0) { + l_debug("Unsupported page number %d", pkt[0]); + l_debug("Returning page number 0"); + } + long_msg = l_malloc(CFG_MAX_MSG_LEN); + n = mesh_model_opcode_set(OP_DEV_COMP_STATUS, long_msg); + long_msg[n++] = 0; + n += node_generate_comp(node, long_msg + n, + CFG_MAX_MSG_LEN - n); + + break; + + case OP_CONFIG_DEFAULT_TTL_SET: + if (size != 1 || pkt[0] > TTL_MASK || pkt[0] == 1) + return true; + + if (pkt[0] <= TTL_MASK) + node_default_ttl_set(node, pkt[0]); + /* Fall Through */ + + case OP_CONFIG_DEFAULT_TTL_GET: + l_debug("Get/Set Default TTL"); + + n = mesh_model_opcode_set(OP_CONFIG_DEFAULT_TTL_STATUS, msg); + msg[n++] = node_default_ttl_get(node); + break; + + case OP_CONFIG_MODEL_PUB_VIRT_SET: + if (size != 25 && size != 27) + return true; + + config_pub_set(node, net_idx, src, unicast, pkt, size, + !!(opcode & OP_UNRELIABLE)); + break; + + case OP_CONFIG_MODEL_PUB_SET: + if (size != 11 && size != 13) + return true; + + config_pub_set(node, net_idx, src, unicast, pkt, size, + !!(opcode & OP_UNRELIABLE)); + break; + + case OP_CONFIG_MODEL_PUB_GET: + config_pub_get(node, net_idx, src, unicast, pkt, size); + break; + + case OP_CONFIG_VEND_MODEL_SUB_GET: + if (size != 6) + return true; + + config_sub_get(node, net_idx, src, unicast, pkt, size); + break; + + case OP_CONFIG_MODEL_SUB_GET: + if (size != 4) + return true; + + config_sub_get(node, net_idx, src, unicast, pkt, size); + break; + + case OP_CONFIG_MODEL_SUB_VIRT_OVERWRITE: + case OP_CONFIG_MODEL_SUB_VIRT_DELETE: + case OP_CONFIG_MODEL_SUB_VIRT_ADD: + virt = true; + /* Fall Through */ + case OP_CONFIG_MODEL_SUB_OVERWRITE: + case OP_CONFIG_MODEL_SUB_DELETE: + case OP_CONFIG_MODEL_SUB_ADD: + case OP_CONFIG_MODEL_SUB_DELETE_ALL: + config_sub_set(node, net_idx, src, unicast, pkt, size, virt, + opcode); + break; + + case OP_CONFIG_RELAY_SET: + if (size != 2 || pkt[0] > 0x01) + return true; + + count = (pkt[1] >> 5) + 1; + interval = ((pkt[1] & 0x1f) + 1) * 10; + node_relay_mode_set(node, !!pkt[0], count, interval); + /* Fall Through */ + + case OP_CONFIG_RELAY_GET: + n = mesh_model_opcode_set(OP_CONFIG_RELAY_STATUS, msg); + + msg[n++] = node_relay_mode_get(node, &count, &interval); + msg[n++] = ((count - 1) << 5) + ((interval/10 - 1) & 0x1f); + + l_debug("Get/Set Relay Config (%d)", msg[n-1]); + break; + + case OP_CONFIG_NETWORK_TRANSMIT_SET: + if (size != 1) + return true; + + count = (pkt[0] >> 5) + 1; + interval = ((pkt[0] & 0x1f) + 1) * 10; + + if (mesh_config_write_net_transmit(node_config_get(node), count, + interval)) + mesh_net_transmit_params_set(net, count, interval); + /* Fall Through */ + + case OP_CONFIG_NETWORK_TRANSMIT_GET: + n = mesh_model_opcode_set(OP_CONFIG_NETWORK_TRANSMIT_STATUS, + msg); + mesh_net_transmit_params_get(net, &count, &interval); + msg[n++] = ((count - 1) << 5) + ((interval/10 - 1) & 0x1f); + + l_debug("Get/Set Network Transmit Config"); + break; + + case OP_CONFIG_PROXY_SET: + if (size != 1 || pkt[0] > 0x01) + return true; + + node_proxy_mode_set(node, !!pkt[0]); + /* Fall Through */ + + case OP_CONFIG_PROXY_GET: + n = mesh_model_opcode_set(OP_CONFIG_PROXY_STATUS, msg); + + msg[n++] = node_proxy_mode_get(node); + l_debug("Get/Set Config Proxy (%d)", msg[n-1]); + break; + + case OP_NODE_IDENTITY_SET: + if (size != 3 || pkt[2] > 0x01) + return true; + + n_idx = l_get_le16(pkt); + if (n_idx > 0xfff) + return true; + + /* + * Currently no support for proxy: node identity not supported + */ + + /* Fall Through */ + + case OP_NODE_IDENTITY_GET: + if (size < 2) + return true; + + n_idx = l_get_le16(pkt); + if (n_idx > 0xfff) + return true; + + n = mesh_model_opcode_set(OP_NODE_IDENTITY_STATUS, msg); + + status = mesh_net_get_identity_mode(net, n_idx, &state); + + msg[n++] = status; + + l_put_le16(n_idx, msg + n); + n += 2; + + msg[n++] = state; + l_debug("Get/Set Config Identity (%d)", state); + break; + + case OP_CONFIG_BEACON_SET: + if (size != 1 || pkt[0] > 0x01) + return true; + + node_beacon_mode_set(node, !!pkt[0]); + /* Fall Through */ + + case OP_CONFIG_BEACON_GET: + n = mesh_model_opcode_set(OP_CONFIG_BEACON_STATUS, msg); + + msg[n++] = node_beacon_mode_get(node); + l_debug("Get/Set Config Beacon (%d)", msg[n-1]); + break; + + case OP_CONFIG_FRIEND_SET: + if (size != 1 || pkt[0] > 0x01) + return true; + + node_friend_mode_set(node, !!pkt[0]); + /* Fall Through */ + + case OP_CONFIG_FRIEND_GET: + + n = mesh_model_opcode_set(OP_CONFIG_FRIEND_STATUS, msg); + + msg[n++] = node_friend_mode_get(node); + l_debug("Get/Set Friend (%d)", msg[n-1]); + break; + + case OP_CONFIG_KEY_REFRESH_PHASE_SET: + if (size != 3 || pkt[2] > 0x03) + return true; + + b_res = mesh_net_key_refresh_phase_set(net, l_get_le16(pkt), + pkt[2]); + size = 2; + /* Fall Through */ + + case OP_CONFIG_KEY_REFRESH_PHASE_GET: + if (size != 2) + return true; + + n_idx = l_get_le16(pkt); + + n = mesh_model_opcode_set(OP_CONFIG_KEY_REFRESH_PHASE_STATUS, + msg); + + /* State: 0x00-0x03 phase of key refresh */ + status = mesh_net_key_refresh_phase_get(net, n_idx, + &phase); + if (status != MESH_STATUS_SUCCESS) { + b_res = status; + phase = KEY_REFRESH_PHASE_NONE; + } + + msg[n++] = b_res; + l_put_le16(n_idx, msg + n); + n += 2; + msg[n++] = phase; + + l_debug("Get/Set Key Refresh State (%d)", msg[n-1]); + break; + + case OP_APPKEY_ADD: + case OP_APPKEY_UPDATE: + if (size != 19) + return true; + + n_idx = l_get_le16(pkt) & 0xfff; + a_idx = l_get_le16(pkt + 1) >> 4; + + if (opcode == OP_APPKEY_ADD) + b_res = appkey_key_add(net, n_idx, a_idx, pkt + 3); + else + b_res = appkey_key_update(net, n_idx, a_idx, + pkt + 3); + + l_debug("Add/Update AppKey %s: Net_Idx %3.3x, App_Idx %3.3x", + (b_res == MESH_STATUS_SUCCESS) ? "success" : "fail", + n_idx, a_idx); + + + n = mesh_model_opcode_set(OP_APPKEY_STATUS, msg); + + msg[n++] = b_res; + msg[n++] = pkt[0]; + msg[n++] = pkt[1]; + msg[n++] = pkt[2]; + break; + + case OP_APPKEY_DELETE: + if (size != 3) + return true; + + n_idx = l_get_le16(pkt) & 0xfff; + a_idx = l_get_le16(pkt + 1) >> 4; + b_res = appkey_key_delete(net, n_idx, a_idx); + l_debug("Delete AppKey %s Net_Idx %3.3x to App_Idx %3.3x", + (b_res == MESH_STATUS_SUCCESS) ? "success" : "fail", + n_idx, a_idx); + + n = mesh_model_opcode_set(OP_APPKEY_STATUS, msg); + msg[n++] = b_res; + msg[n++] = pkt[0]; + msg[n++] = pkt[1]; + msg[n++] = pkt[2]; + break; + + case OP_APPKEY_GET: + if (size != 2) + return true; + + n_idx = l_get_le16(pkt); + + long_msg = l_malloc(CFG_MAX_MSG_LEN); + n = mesh_model_opcode_set(OP_APPKEY_LIST, long_msg); + + status = appkey_list(net, n_idx, long_msg + n + 3, + CFG_MAX_MSG_LEN - n - 3, &size); + + long_msg[n] = status; + l_put_le16(n_idx, long_msg + n + 1); + n += (size + 3); + break; + + case OP_NETKEY_ADD: + case OP_NETKEY_UPDATE: + if (size != 18) + return true; + + n_idx = l_get_le16(pkt); + + if (opcode == OP_NETKEY_ADD) + b_res = mesh_net_add_key(net, n_idx, pkt + 2); + else + b_res = mesh_net_update_key(net, n_idx, pkt + 2); + + l_debug("NetKey Add/Update %s", + (b_res == MESH_STATUS_SUCCESS) ? "success" : "fail"); + + n = mesh_model_opcode_set(OP_NETKEY_STATUS, msg); + msg[n++] = b_res; + l_put_le16(l_get_le16(pkt), msg + n); + n += 2; + break; + + case OP_NETKEY_DELETE: + if (size != 2) + return true; + + b_res = mesh_net_del_key(net, l_get_le16(pkt)); + + l_debug("NetKey delete %s", + (b_res == MESH_STATUS_SUCCESS) ? "success" : "fail"); + + n = mesh_model_opcode_set(OP_NETKEY_STATUS, msg); + msg[n++] = b_res; + l_put_le16(l_get_le16(pkt), msg + n); + n += 2; + break; + + case OP_NETKEY_GET: + long_msg = l_malloc(CFG_MAX_MSG_LEN); + n = mesh_model_opcode_set(OP_NETKEY_LIST, long_msg); + size = CFG_MAX_MSG_LEN - n; + + if (mesh_net_key_list_get(net, long_msg + n, &size)) + n += size; + else + n = 0; + break; + + case OP_MODEL_APP_BIND: + case OP_MODEL_APP_UNBIND: + model_app_bind(node, net_idx, src, unicast, pkt, size, + opcode != OP_MODEL_APP_BIND); + break; + + case OP_VEND_MODEL_APP_GET: + if (size != 6) + return true; + model_app_list(node, net_idx, src, unicast, pkt, size); + break; + + case OP_MODEL_APP_GET: + if (size != 4) + return true; + model_app_list(node, net_idx, src, unicast, pkt, size); + break; + + case OP_CONFIG_HEARTBEAT_PUB_SET: + l_debug("OP_CONFIG_HEARTBEAT_PUB_SET"); + if (size != 9) { + l_debug("bad size %d", size); + return true; + } + if (pkt[2] > 0x11 || pkt[3] > 0x10 || pkt[4] > 0x7f) + return true; + else if (IS_VIRTUAL(l_get_le16(pkt))) + b_res = MESH_STATUS_INVALID_ADDRESS; + else if (l_get_le16(pkt + 7) != mesh_net_get_primary_idx(net)) + /* Future work: check for valid subnets */ + b_res = MESH_STATUS_INVALID_NETKEY; + + n = mesh_model_opcode_set(OP_CONFIG_HEARTBEAT_PUB_STATUS, + msg); + msg[n++] = b_res; + + memcpy(&msg[n], pkt, 9); + + /* Ignore RFU bits in features */ + l_put_le16(l_get_le16(pkt + 5) & 0xf, &msg[n + 5]); + + /* Add octet count to status */ + n += 9; + + if (b_res != MESH_STATUS_SUCCESS) + break; + + hb->pub_dst = l_get_le16(pkt); + if (hb->pub_dst == UNASSIGNED_ADDRESS || + pkt[2] == 0 || pkt[3] == 0) { + /* + * We might still have a pub_dst here in case + * we need it for State Change heartbeat + */ + hb->pub_count = 0; + hb->pub_period = 0; + } else { + hb->pub_count = (pkt[2] != 0xff) ? + log_to_uint32(pkt[2], 1) : 0xffff; + hb->pub_period = log_to_uint32(pkt[3], 1); + } + + hb->pub_ttl = pkt[4]; + hb->pub_features = l_get_le16(pkt + 5) & 0xf; + hb->pub_net_idx = l_get_le16(pkt + 7); + update_hb_pub_timer(net, hb); + + break; + + case OP_CONFIG_HEARTBEAT_PUB_GET: + n = mesh_model_opcode_set(OP_CONFIG_HEARTBEAT_PUB_STATUS, msg); + msg[n++] = b_res; + l_put_le16(hb->pub_dst, msg + n); + n += 2; + msg[n++] = uint32_to_log(hb->pub_count); + msg[n++] = uint32_to_log(hb->pub_period); + msg[n++] = hb->pub_ttl; + l_put_le16(hb->pub_features, msg + n); + n += 2; + l_put_le16(hb->pub_net_idx, msg + n); + n += 2; + break; + + case OP_CONFIG_HEARTBEAT_SUB_SET: + if (size != 5) + return true; + + l_debug("Set Sub Period (Log %2.2x) %d sec", + pkt[4], log_to_uint32(pkt[4], 1)); + + b_res = hb_subscription_set(net, l_get_le16(pkt), + l_get_le16(pkt + 2), + pkt[4]); + if (b_res < 0) + return true; + + /* Fall through */ + + case OP_CONFIG_HEARTBEAT_SUB_GET: + gettimeofday(&time_now, NULL); + time_now.tv_sec -= hb->sub_start; + + if (time_now.tv_sec >= (long int) hb->sub_period) + time_now.tv_sec = 0; + else + time_now.tv_sec = hb->sub_period - time_now.tv_sec; + + l_debug("Sub Period (Log %2.2x) %d sec", + uint32_to_log(time_now.tv_sec), + (int) time_now.tv_sec); + + n = mesh_model_opcode_set(OP_CONFIG_HEARTBEAT_SUB_STATUS, msg); + msg[n++] = b_res; + l_put_le16(hb->sub_src, msg + n); + n += 2; + l_put_le16(hb->sub_dst, msg + n); + n += 2; + msg[n++] = uint32_to_log(time_now.tv_sec); + msg[n++] = uint32_to_log(hb->sub_count); + msg[n++] = hb->sub_count ? hb->sub_min_hops : 0; + msg[n++] = hb->sub_max_hops; + break; + + case OP_CONFIG_POLL_TIMEOUT_LIST: + if (size != 2 || l_get_le16(pkt) == 0 || + l_get_le16(pkt) > 0x7fff) + return true; + + n = mesh_model_opcode_set(OP_CONFIG_POLL_TIMEOUT_STATUS, msg); + l_put_le16(l_get_le16(pkt), msg + n); + n += 2; + tmp32 = mesh_net_friend_timeout(net, l_get_le16(pkt)); + msg[n++] = tmp32; + msg[n++] = tmp32 >> 8; + msg[n++] = tmp32 >> 16; + break; + + case OP_NODE_RESET: + n = mesh_model_opcode_set(OP_NODE_RESET_STATUS, msg); + l_timeout_create(1, node_reset, net, NULL); + break; + } + + if (n) { + /* print_packet("App Tx", long_msg ? long_msg : msg, n); */ + mesh_model_send(node, unicast, src, + APP_IDX_DEV_LOCAL, net_idx, DEFAULT_TTL, + long_msg ? long_msg : msg, n); + } + l_free(long_msg); + + return true; +} + +static void cfgmod_srv_unregister(void *user_data) +{ + struct mesh_node *node = user_data; + struct mesh_net *net = node_get_net(node); + struct mesh_net_heartbeat *hb = mesh_net_heartbeat_get(net); + + l_timeout_remove(hb->pub_timer); + l_timeout_remove(hb->sub_timer); + hb->pub_timer = hb->sub_timer = NULL; +} + +static const struct mesh_model_ops ops = { + .unregister = cfgmod_srv_unregister, + .recv = cfg_srv_pkt, + .bind = NULL, + .sub = NULL, + .pub = NULL +}; + +void cfgmod_server_init(struct mesh_node *node, uint8_t ele_idx) +{ + l_debug("%2.2x", ele_idx); + mesh_model_register(node, ele_idx, CONFIG_SRV_MODEL, &ops, node); +} diff --git a/mesh/cfgmod.h b/mesh/cfgmod.h new file mode 100644 index 0000000..da8b5b3 --- /dev/null +++ b/mesh/cfgmod.h @@ -0,0 +1,97 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#define CONFIG_SRV_MODEL (VENDOR_ID_MASK | 0x0000) +#define CONFIG_CLI_MODEL (VENDOR_ID_MASK | 0x0001) + +/* New List */ +#define OP_APPKEY_ADD 0x00 +#define OP_APPKEY_DELETE 0x8000 +#define OP_APPKEY_GET 0x8001 +#define OP_APPKEY_LIST 0x8002 +#define OP_APPKEY_STATUS 0x8003 +#define OP_APPKEY_UPDATE 0x01 +#define OP_DEV_COMP_GET 0x8008 +#define OP_DEV_COMP_STATUS 0x02 +#define OP_CONFIG_BEACON_GET 0x8009 +#define OP_CONFIG_BEACON_SET 0x800A +#define OP_CONFIG_BEACON_STATUS 0x800B +#define OP_CONFIG_DEFAULT_TTL_GET 0x800C +#define OP_CONFIG_DEFAULT_TTL_SET 0x800D +#define OP_CONFIG_DEFAULT_TTL_STATUS 0x800E +#define OP_CONFIG_FRIEND_GET 0x800F +#define OP_CONFIG_FRIEND_SET 0x8010 +#define OP_CONFIG_FRIEND_STATUS 0x8011 +#define OP_CONFIG_PROXY_GET 0x8012 +#define OP_CONFIG_PROXY_SET 0x8013 +#define OP_CONFIG_PROXY_STATUS 0x8014 +#define OP_CONFIG_KEY_REFRESH_PHASE_GET 0x8015 +#define OP_CONFIG_KEY_REFRESH_PHASE_SET 0x8016 +#define OP_CONFIG_KEY_REFRESH_PHASE_STATUS 0x8017 +#define OP_CONFIG_MODEL_PUB_GET 0x8018 +#define OP_CONFIG_MODEL_PUB_SET 0x03 +#define OP_CONFIG_MODEL_PUB_STATUS 0x8019 +#define OP_CONFIG_MODEL_PUB_VIRT_SET 0x801A +#define OP_CONFIG_MODEL_SUB_ADD 0x801B +#define OP_CONFIG_MODEL_SUB_DELETE 0x801C +#define OP_CONFIG_MODEL_SUB_DELETE_ALL 0x801D +#define OP_CONFIG_MODEL_SUB_OVERWRITE 0x801E +#define OP_CONFIG_MODEL_SUB_STATUS 0x801F +#define OP_CONFIG_MODEL_SUB_VIRT_ADD 0x8020 +#define OP_CONFIG_MODEL_SUB_VIRT_DELETE 0x8021 +#define OP_CONFIG_MODEL_SUB_VIRT_OVERWRITE 0x8022 +#define OP_CONFIG_NETWORK_TRANSMIT_GET 0x8023 +#define OP_CONFIG_NETWORK_TRANSMIT_SET 0x8024 +#define OP_CONFIG_NETWORK_TRANSMIT_STATUS 0x8025 +#define OP_CONFIG_RELAY_GET 0x8026 +#define OP_CONFIG_RELAY_SET 0x8027 +#define OP_CONFIG_RELAY_STATUS 0x8028 +#define OP_CONFIG_MODEL_SUB_GET 0x8029 +#define OP_CONFIG_MODEL_SUB_LIST 0x802A +#define OP_CONFIG_VEND_MODEL_SUB_GET 0x802B +#define OP_CONFIG_VEND_MODEL_SUB_LIST 0x802C +#define OP_CONFIG_POLL_TIMEOUT_LIST 0x802D +#define OP_CONFIG_POLL_TIMEOUT_STATUS 0x802E +/* Health opcodes in health-mod.h */ +#define OP_CONFIG_HEARTBEAT_PUB_GET 0x8038 +#define OP_CONFIG_HEARTBEAT_PUB_SET 0x8039 +#define OP_CONFIG_HEARTBEAT_PUB_STATUS 0x06 +#define OP_CONFIG_HEARTBEAT_SUB_GET 0x803A +#define OP_CONFIG_HEARTBEAT_SUB_SET 0x803B +#define OP_CONFIG_HEARTBEAT_SUB_STATUS 0x803C +#define OP_MODEL_APP_BIND 0x803D +#define OP_MODEL_APP_STATUS 0x803E +#define OP_MODEL_APP_UNBIND 0x803F +#define OP_NETKEY_ADD 0x8040 +#define OP_NETKEY_DELETE 0x8041 +#define OP_NETKEY_GET 0x8042 +#define OP_NETKEY_LIST 0x8043 +#define OP_NETKEY_STATUS 0x8044 +#define OP_NETKEY_UPDATE 0x8045 +#define OP_NODE_IDENTITY_GET 0x8046 +#define OP_NODE_IDENTITY_SET 0x8047 +#define OP_NODE_IDENTITY_STATUS 0x8048 +#define OP_NODE_RESET 0x8049 +#define OP_NODE_RESET_STATUS 0x804A +#define OP_MODEL_APP_GET 0x804B +#define OP_MODEL_APP_LIST 0x804C +#define OP_VEND_MODEL_APP_GET 0x804C +#define OP_VEND_MODEL_APP_LIST 0x804E + +void cfgmod_server_init(struct mesh_node *node, uint8_t ele_idx); diff --git a/mesh/crypto.c b/mesh/crypto.c new file mode 100644 index 0000000..15ee673 --- /dev/null +++ b/mesh/crypto.c @@ -0,0 +1,1176 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include "mesh/mesh-defs.h" +#include "mesh/net.h" +#include "mesh/crypto.h" + +/* Multiply used Zero array */ +static const uint8_t zero[16] = { 0, }; + +static bool aes_ecb_one(const uint8_t key[16], const uint8_t in[16], + uint8_t out[16]) +{ + void *cipher; + bool result = false; + + cipher = l_cipher_new(L_CIPHER_AES, key, 16); + + if (cipher) { + result = l_cipher_encrypt(cipher, in, out, 16); + l_cipher_free(cipher); + } + + return result; +} + +static bool aes_cmac(void *checksum, const uint8_t *msg, + size_t msg_len, uint8_t res[16]) +{ + if (!l_checksum_update(checksum, msg, msg_len)) + return false; + + if (16 == l_checksum_get_digest(checksum, res, 16)) + return true; + + return false; +} + +static bool aes_cmac_one(const uint8_t key[16], const void *msg, + size_t msg_len, uint8_t res[16]) +{ + void *checksum; + bool result; + + checksum = l_checksum_new_cmac_aes(key, 16); + if (!checksum) + return false; + + result = l_checksum_update(checksum, msg, msg_len); + + if (result) { + ssize_t len = l_checksum_get_digest(checksum, res, 16); + result = !!(len == 16); + } + + l_checksum_free(checksum); + + return result; +} + +bool mesh_crypto_aes_cmac(const uint8_t key[16], const uint8_t *msg, + size_t msg_len, uint8_t res[16]) +{ + return aes_cmac_one(key, msg, msg_len, res); +} + +bool mesh_crypto_aes_ccm_encrypt(const uint8_t nonce[13], const uint8_t key[16], + const uint8_t *aad, uint16_t aad_len, + const void *msg, uint16_t msg_len, + void *out_msg, + void *out_mic, size_t mic_size) +{ + void *cipher; + bool result; + + cipher = l_aead_cipher_new(L_AEAD_CIPHER_AES_CCM, key, 16, mic_size); + + result = l_aead_cipher_encrypt(cipher, msg, msg_len, aad, aad_len, + nonce, 13, out_msg, msg_len + mic_size); + + if (result && out_mic) { + if (mic_size == 4) + *(uint32_t *)out_mic = l_get_be32(out_msg + msg_len); + else + *(uint64_t *)out_mic = l_get_be64(out_msg + msg_len); + } + + l_aead_cipher_free(cipher); + + return result; +} + +bool mesh_crypto_aes_ccm_decrypt(const uint8_t nonce[13], const uint8_t key[16], + const uint8_t *aad, uint16_t aad_len, + const void *enc_msg, uint16_t enc_msg_len, + void *out_msg, + void *out_mic, size_t mic_size) +{ + void *cipher; + bool result; + size_t out_msg_len = enc_msg_len - mic_size; + + cipher = l_aead_cipher_new(L_AEAD_CIPHER_AES_CCM, key, 16, mic_size); + + result = l_aead_cipher_decrypt(cipher, enc_msg, enc_msg_len, + aad, aad_len, nonce, 13, + out_msg, out_msg_len); + + if (result && out_mic) { + if (mic_size == 4) + *(uint32_t *)out_mic = + l_get_be32(enc_msg + enc_msg_len - mic_size); + else + *(uint64_t *)out_mic = + l_get_be64(enc_msg + enc_msg_len - mic_size); + } + + l_aead_cipher_free(cipher); + + return result; +} + +bool mesh_crypto_k1(const uint8_t ikm[16], const uint8_t salt[16], + const void *info, size_t info_len, uint8_t okm[16]) +{ + uint8_t res[16]; + + if (!aes_cmac_one(salt, ikm, 16, res)) + return false; + + return aes_cmac_one(res, info, info_len, okm); +} + +bool mesh_crypto_k2(const uint8_t n[16], const uint8_t *p, size_t p_len, + uint8_t net_id[1], + uint8_t enc_key[16], + uint8_t priv_key[16]) +{ + void *checksum; + uint8_t output[16]; + uint8_t t[16]; + uint8_t *stage; + bool success = false; + + stage = l_malloc(sizeof(output) + p_len + 1); + if (!stage) + return false; + + if (!mesh_crypto_s1("smk2", 4, stage)) + goto fail; + + if (!aes_cmac_one(stage, n, 16, t)) + goto fail; + + checksum = l_checksum_new_cmac_aes(t, 16); + if (!checksum) + goto fail; + + memcpy(stage, p, p_len); + stage[p_len] = 1; + + if (!aes_cmac(checksum, stage, p_len + 1, output)) + goto done; + + net_id[0] = output[15] & 0x7f; + + memcpy(stage, output, 16); + memcpy(stage + 16, p, p_len); + stage[p_len + 16] = 2; + + if (!aes_cmac(checksum, stage, p_len + 16 + 1, output)) + goto done; + + memcpy(enc_key, output, 16); + + memcpy(stage, output, 16); + memcpy(stage + 16, p, p_len); + stage[p_len + 16] = 3; + + if (!aes_cmac(checksum, stage, p_len + 16 + 1, output)) + goto done; + + memcpy(priv_key, output, 16); + success = true; + +done: + l_checksum_free(checksum); +fail: + l_free(stage); + + return success; +} + +static bool crypto_128(const uint8_t n[16], const char *s, uint8_t out128[16]) +{ + const uint8_t id128[] = { 'i', 'd', '1', '2', '8', 0x01 }; + uint8_t salt[16]; + + if (!mesh_crypto_s1(s, 4, salt)) + return false; + + return mesh_crypto_k1(n, salt, id128, sizeof(id128), out128); +} + +bool mesh_crypto_nkik(const uint8_t n[16], uint8_t identity_key[16]) +{ + return crypto_128(n, "nkik", identity_key); +} + +bool mesh_crypto_identity(const uint8_t net_key[16], uint16_t addr, + uint8_t id[16]) +{ + uint8_t id_key[16]; + uint8_t tmp[16]; + + if (!mesh_crypto_nkik(net_key, id_key)) + return false; + + if (!l_get_be64(id + 8)) + l_getrandom(id + 8, 8); + + memset(tmp, 0, sizeof(tmp)); + memcpy(tmp + 6, id + 8, 8); + l_put_be16(addr, tmp + 14); + + if (!aes_ecb_one(id_key, tmp, tmp)) + return false; + + memcpy(id, tmp + 8, 8); + return true; +} + +bool mesh_crypto_nkbk(const uint8_t n[16], uint8_t beacon_key[16]) +{ + return crypto_128(n, "nkbk", beacon_key); +} + +bool mesh_crypto_nkpk(const uint8_t n[16], uint8_t proxy_key[16]) +{ + return crypto_128(n, "nkpk", proxy_key); +} + +bool mesh_crypto_k3(const uint8_t n[16], uint8_t out64[8]) +{ + const uint8_t id64[] = { 'i', 'd', '6', '4', 0x01 }; + uint8_t tmp[16]; + uint8_t t[16]; + + if (!mesh_crypto_s1("smk3", 4, tmp)) + return false; + + if (!aes_cmac_one(tmp, n, 16, t)) + return false; + + if (!aes_cmac_one(t, id64, sizeof(id64), tmp)) + return false; + + memcpy(out64, tmp + 8, 8); + + return true; +} + +bool mesh_crypto_k4(const uint8_t a[16], uint8_t out6[1]) +{ + const uint8_t id6[] = { 'i', 'd', '6', 0x01 }; + uint8_t tmp[16]; + uint8_t t[16]; + + if (!mesh_crypto_s1("smk4", 4, tmp)) + return false; + + if (!aes_cmac_one(tmp, a, 16, t)) + return false; + + if (!aes_cmac_one(t, id6, sizeof(id6), tmp)) + return false; + + out6[0] = tmp[15] & 0x3f; + return true; +} + +bool mesh_crypto_beacon_cmac(const uint8_t encryption_key[16], + const uint8_t network_id[8], + uint32_t iv_index, bool kr, bool iu, + uint64_t *cmac) +{ + uint8_t msg[13], tmp[16]; + + if (!cmac) + return false; + + msg[0] = kr ? 0x01 : 0x00; + msg[0] |= iu ? 0x02 : 0x00; + memcpy(msg + 1, network_id, 8); + l_put_be32(iv_index, msg + 9); + + if (!aes_cmac_one(encryption_key, msg, 13, tmp)) + return false; + + *cmac = l_get_be64(tmp); + + return true; +} + +bool mesh_crypto_network_nonce(bool ctl, uint8_t ttl, uint32_t seq, + uint16_t src, uint32_t iv_index, + uint8_t nonce[13]) +{ + nonce[0] = 0; + nonce[1] = (ttl & TTL_MASK) | (ctl ? CTL : 0x00); + nonce[2] = (seq >> 16) & 0xff; + nonce[3] = (seq >> 8) & 0xff; + nonce[4] = seq & 0xff; + + /* SRC */ + l_put_be16(src, nonce + 5); + + l_put_be16(0, nonce + 7); + + /* IV Index */ + l_put_be32(iv_index, nonce + 9); + + return true; +} + +bool mesh_crypto_network_encrypt(bool ctl, uint8_t ttl, + uint32_t seq, uint16_t src, + uint32_t iv_index, + const uint8_t net_key[16], + const uint8_t *enc_msg, uint8_t enc_msg_len, + uint8_t *out, void *net_mic) +{ + uint8_t nonce[13]; + + if (!mesh_crypto_network_nonce(ctl, ttl, seq, src, iv_index, nonce)) + return false; + + return mesh_crypto_aes_ccm_encrypt(nonce, net_key, NULL, 0, enc_msg, + enc_msg_len, out, net_mic, + ctl ? 8 : 4); +} + +bool mesh_crypto_network_decrypt(bool ctl, uint8_t ttl, + uint32_t seq, uint16_t src, + uint32_t iv_index, + const uint8_t net_key[16], + const uint8_t *enc_msg, uint8_t enc_msg_len, + uint8_t *out, void *net_mic, size_t mic_size) +{ + uint8_t nonce[13]; + + if (!mesh_crypto_network_nonce(ctl, ttl, seq, src, iv_index, nonce)) + return false; + + return mesh_crypto_aes_ccm_decrypt(nonce, net_key, NULL, 0, + enc_msg, enc_msg_len, out, + net_mic, mic_size); +} + +bool mesh_crypto_application_nonce(uint32_t seq, uint16_t src, + uint16_t dst, uint32_t iv_index, + bool aszmic, uint8_t nonce[13]) +{ + nonce[0] = 0x01; + nonce[1] = aszmic ? 0x80 : 0x00; + nonce[2] = (seq & 0x00ff0000) >> 16; + nonce[3] = (seq & 0x0000ff00) >> 8; + nonce[4] = (seq & 0x000000ff); + nonce[5] = (src & 0xff00) >> 8; + nonce[6] = (src & 0x00ff); + nonce[7] = (dst & 0xff00) >> 8; + nonce[8] = (dst & 0x00ff); + l_put_be32(iv_index, nonce + 9); + + return true; +} + +bool mesh_crypto_device_nonce(uint32_t seq, uint16_t src, + uint16_t dst, uint32_t iv_index, + bool aszmic, uint8_t nonce[13]) +{ + nonce[0] = 0x02; + nonce[1] = aszmic ? 0x80 : 0x00; + nonce[2] = (seq & 0x00ff0000) >> 16; + nonce[3] = (seq & 0x0000ff00) >> 8; + nonce[4] = (seq & 0x000000ff); + nonce[5] = (src & 0xff00) >> 8; + nonce[6] = (src & 0x00ff); + nonce[7] = (dst & 0xff00) >> 8; + nonce[8] = (dst & 0x00ff); + l_put_be32(iv_index, nonce + 9); + + return true; +} + +bool mesh_crypto_application_encrypt(uint8_t key_aid, uint32_t seq, + uint16_t src, uint16_t dst, + uint32_t iv_index, + const uint8_t app_key[16], + const uint8_t *aad, uint8_t aad_len, + const uint8_t *msg, uint8_t msg_len, + uint8_t *out, + void *app_mic, size_t mic_size) +{ + uint8_t nonce[13]; + bool aszmic = (mic_size == 8) ? true : false; + + if (!key_aid && !mesh_crypto_device_nonce(seq, src, dst, + iv_index, aszmic, nonce)) + return false; + + if (key_aid && !mesh_crypto_application_nonce(seq, src, dst, + iv_index, aszmic, nonce)) + return false; + + return mesh_crypto_aes_ccm_encrypt(nonce, app_key, aad, aad_len, + msg, msg_len, + out, app_mic, mic_size); +} + +bool mesh_crypto_application_decrypt(uint8_t key_aid, uint32_t seq, + uint16_t src, uint16_t dst, uint32_t iv_index, + const uint8_t app_key[16], + const uint8_t *aad, uint8_t aad_len, + const uint8_t *enc_msg, uint8_t enc_msg_len, + uint8_t *out, void *app_mic, size_t mic_size) +{ + uint8_t nonce[13]; + bool aszmic = (mic_size == 8) ? true : false; + + if (!key_aid && !mesh_crypto_device_nonce(seq, src, dst, + iv_index, aszmic, nonce)) + return false; + + if (key_aid && !mesh_crypto_application_nonce(seq, src, dst, + iv_index, aszmic, nonce)) + return false; + + return mesh_crypto_aes_ccm_decrypt(nonce, app_key, + aad, aad_len, enc_msg, + enc_msg_len, out, + app_mic, mic_size); +} + +bool mesh_crypto_session_key(const uint8_t secret[32], + const uint8_t salt[16], + uint8_t session_key[16]) +{ + const uint8_t prsk[4] = "prsk"; + + if (!aes_cmac_one(salt, secret, 32, session_key)) + return false; + + return aes_cmac_one(session_key, prsk, 4, session_key); +} + +bool mesh_crypto_nonce(const uint8_t secret[32], + const uint8_t salt[16], + uint8_t nonce[13]) +{ + const uint8_t prsn[4] = "prsn"; + uint8_t tmp[16]; + bool result; + + if (!aes_cmac_one(salt, secret, 32, tmp)) + return false; + + result = aes_cmac_one(tmp, prsn, 4, tmp); + + if (result) + memcpy(nonce, tmp + 3, 13); + + return result; +} + +bool mesh_crypto_s1(const void *info, size_t len, uint8_t salt[16]) +{ + return aes_cmac_one(zero, info, len, salt); +} + +bool mesh_crypto_prov_prov_salt(const uint8_t conf_salt[16], + const uint8_t prov_rand[16], + const uint8_t dev_rand[16], + uint8_t prov_salt[16]) +{ + uint8_t tmp[16 * 3]; + + memcpy(tmp, conf_salt, 16); + memcpy(tmp + 16, prov_rand, 16); + memcpy(tmp + 32, dev_rand, 16); + + return aes_cmac_one(zero, tmp, sizeof(tmp), prov_salt); +} + +bool mesh_crypto_prov_conf_key(const uint8_t secret[32], + const uint8_t salt[16], + uint8_t conf_key[16]) +{ + const uint8_t prck[4] = "prck"; + + if (!aes_cmac_one(salt, secret, 32, conf_key)) + return false; + + return aes_cmac_one(conf_key, prck, 4, conf_key); +} + +bool mesh_crypto_device_key(const uint8_t secret[32], + const uint8_t salt[16], + uint8_t device_key[16]) +{ + const uint8_t prdk[4] = "prdk"; + + if (!aes_cmac_one(salt, secret, 32, device_key)) + return false; + + return aes_cmac_one(device_key, prdk, 4, device_key); +} + +bool mesh_crypto_virtual_addr(const uint8_t virtual_label[16], + uint16_t *addr) +{ + uint8_t tmp[16]; + + if (!mesh_crypto_s1("vtad", 4, tmp)) + return false; + + if (!addr || !aes_cmac_one(tmp, virtual_label, 16, tmp)) + return false; + + *addr = (l_get_be16(tmp + 14) & 0x3fff) | 0x8000; + + return true; +} + +bool mesh_crypto_privacy_counter(uint32_t iv_index, + const uint8_t *payload, + uint8_t privacy_counter[16]) +{ + memset(privacy_counter, 0, 5); + l_put_be32(iv_index, privacy_counter + 5); + memcpy(privacy_counter + 9, payload, 7); + + return true; +} + +bool mesh_crypto_network_obfuscate(const uint8_t privacy_key[16], + const uint8_t privacy_counter[16], + bool ctl, uint8_t ttl, uint32_t seq, + uint16_t src, uint8_t *out) +{ + uint8_t ecb[16], tmp[16]; + int i; + + if (!aes_ecb_one(privacy_key, privacy_counter, ecb)) + return false; + + tmp[0] = ((!!ctl) << 7) | (ttl & TTL_MASK); + tmp[1] = (seq & 0xff0000) >> 16; + tmp[2] = (seq & 0x00ff00) >> 8; + tmp[3] = (seq & 0x0000ff); + tmp[4] = (src & 0xff00) >> 8; + tmp[5] = (src & 0x00ff); + + if (out) { + for (i = 0; i < 6; i++) + out[i] = ecb[i] ^ tmp[i]; + } + + return true; +} + +bool mesh_crypto_network_clarify(const uint8_t privacy_key[16], + const uint8_t privacy_counter[16], + const uint8_t net_hdr[6], + bool *ctl, uint8_t *ttl, + uint32_t *seq, uint16_t *src) +{ + uint8_t ecb[16], tmp[6]; + int i; + + if (!aes_ecb_one(privacy_key, privacy_counter, ecb)) + return false; + + for (i = 0; i < 6; i++) + tmp[i] = ecb[i] ^ net_hdr[i]; + + if (ctl) + *ctl = !!(tmp[0] & CTL); + + if (ttl) + *ttl = tmp[0] & TTL_MASK; + + if (seq) + *seq = l_get_be32(tmp) & SEQ_MASK; + + if (src) + *src = l_get_be16(tmp + 4); + + return true; +} + +bool mesh_crypto_packet_build(bool ctl, uint8_t ttl, + uint32_t seq, + uint16_t src, uint16_t dst, + uint8_t opcode, + bool segmented, uint8_t key_aid, + bool szmic, bool relay, uint16_t seqZero, + uint8_t segO, uint8_t segN, + const uint8_t *payload, uint8_t payload_len, + uint8_t *packet, uint8_t *packet_len) +{ + uint32_t hdr; + size_t n; + + l_put_be32(seq, packet + 1); + packet[1] = (ctl ? CTL : 0) | (ttl & TTL_MASK); + + l_put_be16(src, packet + 5); + l_put_be16(dst, packet + 7); + n = 9; + + if (!ctl) { + hdr = segmented << SEG_HDR_SHIFT; + hdr |= (key_aid & KEY_ID_MASK) << KEY_HDR_SHIFT; + if (segmented) { + hdr |= szmic << SZMIC_HDR_SHIFT; + hdr |= (seqZero & SEQ_ZERO_MASK) << SEQ_ZERO_HDR_SHIFT; + hdr |= (segO & SEG_MASK) << SEGO_HDR_SHIFT; + hdr |= (segN & SEG_MASK) << SEGN_HDR_SHIFT; + } + l_put_be32(hdr, packet + n); + + /* Only first octet is valid for unsegmented messages */ + if (segmented) + n += 4; + else + n += 1; + + memcpy(packet + n, payload, payload_len); + + l_put_be32(0x00000000, packet + payload_len + n); + if (packet_len) + *packet_len = payload_len + n + 4; + } else { + if ((opcode & OPCODE_MASK) != opcode) + return false; + + hdr = opcode << KEY_HDR_SHIFT; + l_put_be32(hdr, packet + n); + n += 1; + + memcpy(packet + n, payload, payload_len); + n += payload_len; + + l_put_be64(0x0000000000000000, packet + n); + if (packet_len) + *packet_len = n + 8; + } + + + return true; +} + +bool mesh_crypto_packet_parse(const uint8_t *packet, uint8_t packet_len, + bool *ctl, uint8_t *ttl, uint32_t *seq, + uint16_t *src, uint16_t *dst, + uint32_t *cookie, uint8_t *opcode, + bool *segmented, uint8_t *key_aid, + bool *szmic, bool *relay, uint16_t *seqZero, + uint8_t *segO, uint8_t *segN, + const uint8_t **payload, uint8_t *payload_len) +{ + uint32_t hdr; + uint16_t this_dst; + bool is_segmented; + + if (packet_len < 14) + return false; + + this_dst = l_get_be16(packet + 7); + + /* Try to keep bits in the order they exist within the packet */ + if (ctl) + *ctl = !!(packet[1] & CTL); + + if (ttl) + *ttl = packet[1] & TTL_MASK; + + if (seq) + *seq = l_get_be32(packet + 1) & SEQ_MASK; + + if (src) + *src = l_get_be16(packet + 5); + + if (dst) + *dst = this_dst; + + hdr = l_get_be32(packet + 9); + + is_segmented = !!((hdr >> SEG_HDR_SHIFT) & true); + if (segmented) + *segmented = is_segmented; + + if (packet[1] & CTL) { + uint8_t this_opcode = packet[9] & OPCODE_MASK; + + if (cookie) + *cookie = l_get_be32(packet + 9); + + if (opcode) + *opcode = this_opcode; + + if (this_dst && this_opcode == NET_OP_SEG_ACKNOWLEDGE) { + if (relay) + *relay = !!((hdr >> RELAY_HDR_SHIFT) & true); + + if (seqZero) + *seqZero = (hdr >> SEQ_ZERO_HDR_SHIFT) & + SEQ_ZERO_MASK; + + if (payload) + *payload = packet + 9; + + if (payload_len) + *payload_len = packet_len - 9; + } else { + if (payload) + *payload = packet + 10; + + if (payload_len) + *payload_len = packet_len - 10; + } + } else { + if (cookie) + *cookie = l_get_be32(packet + packet_len - 8); + + if (key_aid) + *key_aid = (hdr >> KEY_HDR_SHIFT) & KEY_ID_MASK; + + if (is_segmented) { + if (szmic) + *szmic = !!((hdr >> SZMIC_HDR_SHIFT) & true); + + if (seqZero) + *seqZero = (hdr >> SEQ_ZERO_HDR_SHIFT) & + SEQ_ZERO_MASK; + + if (segO) + *segO = (hdr >> SEGO_HDR_SHIFT) & SEG_MASK; + + if (segN) + *segN = (hdr >> SEGN_HDR_SHIFT) & SEG_MASK; + + if (payload) + *payload = packet + 13; + + if (payload_len) + *payload_len = packet_len - 13; + } else { + if (payload) + *payload = packet + 10; + + if (payload_len) + *payload_len = packet_len - 10; + } + } + + return true; +} + +bool mesh_crypto_payload_encrypt(uint8_t *aad, const uint8_t *payload, + uint8_t *out, uint16_t payload_len, + uint16_t src, uint16_t dst, uint8_t key_aid, + uint32_t seq_num, uint32_t iv_index, + bool aszmic, + const uint8_t application_key[16]) +{ + uint8_t application_nonce[13] = { 0x01, }; + + if (payload_len < 1) + return false; + + if (key_aid == APP_AID_DEV) + application_nonce[0] = 0x02; + + /* Seq Num */ + l_put_be32(seq_num, application_nonce + 1); + + /* ASZMIC */ + application_nonce[1] |= aszmic ? 0x80 : 0x00; + + /* SRC */ + l_put_be16(src, application_nonce + 5); + + /* DST */ + l_put_be16(dst, application_nonce + 7); + + /* IV Index */ + l_put_be32(iv_index, application_nonce + 9); + + if (!mesh_crypto_aes_ccm_encrypt(application_nonce, application_key, + aad, aad ? 16 : 0, + payload, payload_len, + out, NULL, + aszmic ? 8 : 4)) + return false; + + return true; +} + +bool mesh_crypto_payload_decrypt(uint8_t *aad, uint16_t aad_len, + const uint8_t *payload, uint16_t payload_len, + bool szmict, + uint16_t src, uint16_t dst, + uint8_t key_aid, uint32_t seq_num, + uint32_t iv_index, uint8_t *out, + const uint8_t app_key[16]) +{ + uint8_t app_nonce[13] = { 0x01, }; + uint32_t mic32; + uint64_t mic64; + + if (payload_len < 5 || !out) + return false; + + if (key_aid == APP_AID_DEV) + app_nonce[0] = 0x02; + + /* Seq Num */ + l_put_be32(seq_num, app_nonce + 1); + + /* ASZMIC */ + app_nonce[1] |= szmict ? 0x80 : 0x00; + + /* SRC */ + l_put_be16(src, app_nonce + 5); + + /* DST */ + l_put_be16(dst, app_nonce + 7); + + /* IV Index */ + l_put_be32(iv_index, app_nonce + 9); + + memcpy(out, payload, payload_len); + + if (szmict) { + if (!mesh_crypto_aes_ccm_decrypt(app_nonce, app_key, + aad, aad_len, + payload, payload_len, + out, &mic64, sizeof(mic64))) + return false; + + mic64 ^= l_get_be64(out + payload_len - 8); + l_put_be64(mic64, out + payload_len - 8); + + if (mic64) + return false; + } else { + if (!mesh_crypto_aes_ccm_decrypt(app_nonce, app_key, + aad, aad_len, + payload, payload_len, + out, &mic32, sizeof(mic32))) + return false; + + mic32 ^= l_get_be32(out + payload_len - 4); + l_put_be32(mic32, out + payload_len - 4); + + if (mic32) + return false; + } + + return true; +} + +bool mesh_crypto_packet_encode(uint8_t *packet, uint8_t packet_len, + const uint8_t network_key[16], + uint32_t iv_index, + const uint8_t privacy_key[16]) +{ + uint8_t network_nonce[13] = { 0x00, 0x00 }; + uint8_t privacy_counter[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, }; + uint8_t tmp[16]; + int i; + + if (packet_len < 14) + return false; + + /* Detect Proxy packet by CTL == true && DST == 0x0000 */ + if ((packet[1] & CTL) && l_get_be16(packet + 7) == 0) + network_nonce[0] = 0x03; /* Proxy Nonce */ + else + /* CTL + TTL */ + network_nonce[1] = packet[1]; + + /* Seq Num */ + network_nonce[2] = packet[2]; + network_nonce[3] = packet[3]; + network_nonce[4] = packet[4]; + + /* SRC */ + network_nonce[5] = packet[5]; + network_nonce[6] = packet[6]; + + /* DST not available */ + network_nonce[7] = 0; + network_nonce[8] = 0; + + /* IV Index */ + l_put_be32(iv_index, network_nonce + 9); + + /* Check for Long net-MIC */ + if (packet[1] & CTL) { + if (!mesh_crypto_aes_ccm_encrypt(network_nonce, network_key, + NULL, 0, + packet + 7, packet_len - 7 - 8, + packet + 7, NULL, 8)) + return false; + } else { + if (!mesh_crypto_aes_ccm_encrypt(network_nonce, network_key, + NULL, 0, + packet + 7, packet_len - 7 - 4, + packet + 7, NULL, 4)) + return false; + } + + l_put_be32(iv_index, privacy_counter + 5); + memcpy(privacy_counter + 9, packet + 7, 7); + + if (!aes_ecb_one(privacy_key, privacy_counter, tmp)) + return false; + + for (i = 0; i < 6; i++) + packet[1 + i] ^= tmp[i]; + + return true; +} + +bool mesh_crypto_packet_decode(const uint8_t *packet, uint8_t packet_len, + bool proxy, uint8_t *out, uint32_t iv_index, + const uint8_t network_key[16], + const uint8_t privacy_key[16]) +{ + uint8_t privacy_counter[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, }; + uint8_t network_nonce[13] = { 0x00, 0x00, }; + uint8_t tmp[16]; + uint16_t src; + int i; + + if (packet_len < 14) + return false; + + l_put_be32(iv_index, privacy_counter + 5); + memcpy(privacy_counter + 9, packet + 7, 7); + + if (!aes_ecb_one(privacy_key, privacy_counter, tmp)) + return false; + + memcpy(out, packet, packet_len); + for (i = 0; i < 6; i++) + out[1 + i] ^= tmp[i]; + + src = l_get_be16(out + 5); + + /* Pre-check SRC address for illegal values */ + if (!src || src >= 0x8000) + return false; + + /* Detect Proxy packet by CTL == true && proxy == true */ + if ((out[1] & CTL) && proxy) + network_nonce[0] = 0x03; /* Proxy Nonce */ + else + /* CTL + TTL */ + network_nonce[1] = out[1]; + + /* Seq Num */ + network_nonce[2] = out[2]; + network_nonce[3] = out[3]; + network_nonce[4] = out[4]; + + /* SRC */ + network_nonce[5] = out[5]; + network_nonce[6] = out[6]; + + /* DST not available */ + network_nonce[7] = 0; + network_nonce[8] = 0; + + /* IV Index */ + l_put_be32(iv_index, network_nonce + 9); + + /* Check for Long MIC */ + if (out[1] & CTL) { + uint64_t mic; + + if (!mesh_crypto_aes_ccm_decrypt(network_nonce, network_key, + NULL, 0, packet + 7, packet_len - 7, + out + 7, &mic, sizeof(mic))) + return false; + + mic ^= l_get_be64(out + packet_len - 8); + l_put_be64(mic, out + packet_len - 8); + + if (mic) + return false; + } else { + uint32_t mic; + + if (!mesh_crypto_aes_ccm_decrypt(network_nonce, network_key, + NULL, 0, packet + 7, packet_len - 7, + out + 7, &mic, sizeof(mic))) + return false; + + mic ^= l_get_be32(out + packet_len - 4); + l_put_be32(mic, out + packet_len - 4); + + if (mic) + return false; + } + + return true; +} + +bool mesh_crypto_packet_label(uint8_t *packet, uint8_t packet_len, + uint16_t iv_index, uint8_t network_id) +{ + packet[0] = (iv_index & 0x0001) << 7 | (network_id & 0x7f); + + return true; +} + +/* reversed, 8-bit, poly=0x07 */ +static const uint8_t crc_table[256] = { + 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, + 0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b, + 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69, + 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, + + 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, + 0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43, + 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51, + 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, + + 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, + 0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b, + 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19, + 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, + + 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, + 0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33, + 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21, + 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, + + 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, + 0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b, + 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89, + 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, + + 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, + 0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3, + 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1, + 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, + + 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, + 0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb, + 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9, + 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, + + 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, + 0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3, + 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1, + 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf +}; + +uint8_t mesh_crypto_compute_fcs(const uint8_t *packet, uint8_t packet_len) +{ + uint8_t fcs = 0xff; + int i; + + for (i = 0; i < packet_len; i++) + fcs = crc_table[fcs ^ packet[i]]; + + return 0xff - fcs; +} + +bool mesh_crypto_check_fcs(const uint8_t *packet, uint8_t packet_len, + uint8_t received_fcs) +{ + uint8_t fcs = 0xff; + int i; + + for (i = 0; i < packet_len; i++) + fcs = crc_table[fcs ^ packet[i]]; + + fcs = crc_table[fcs ^ received_fcs]; + + return fcs == 0xcf; +} + +/* This function performs a quick-check of ELL and Kernel AEAD encryption. + * Some kernel versions before v4.9 have a known AEAD bug. If the system + * running this test is using a v4.8 or earlier kernel, a failure here is + * likely unless AEAD encryption has been backported. + */ +static const uint8_t crypto_test_result[] = { + 0x75, 0x03, 0x7e, 0xe2, 0x89, 0x81, 0xbe, 0x59, + 0xbc, 0xe6, 0xdd, 0x23, 0x63, 0x5b, 0x16, 0x61, + 0xb7, 0x23, 0x92, 0xd4, 0x86, 0xee, 0x84, 0x29, + 0x9a, 0x2a, 0xbf, 0x96 +}; + +bool mesh_crypto_check_avail() +{ + void *cipher; + bool result; + uint8_t i; + union { + struct { + uint8_t key[16]; + uint8_t aad[16]; + uint8_t nonce[13]; + uint8_t data[20]; + uint8_t mic[8]; + } crypto; + uint8_t bytes[0]; + } u; + uint8_t out_msg[sizeof(u.crypto.data) + sizeof(u.crypto.mic)]; + + l_debug("Testing Crypto"); + for (i = 0; i < sizeof(u); i++) { + u.bytes[i] = 0x60 + i; + } + + cipher = l_aead_cipher_new(L_AEAD_CIPHER_AES_CCM, u.crypto.key, + sizeof(u.crypto.key), sizeof(u.crypto.mic)); + + if (!cipher) + return false; + + result = l_aead_cipher_encrypt(cipher, + u.crypto.data, sizeof(u.crypto.data), + u.crypto.aad, sizeof(u.crypto.aad), + u.crypto.nonce, sizeof(u.crypto.nonce), + out_msg, sizeof(out_msg)); + + if (result) + result = !memcmp(out_msg, crypto_test_result, sizeof(out_msg)); + + l_aead_cipher_free(cipher); + + return result; +} diff --git a/mesh/crypto.h b/mesh/crypto.h new file mode 100644 index 0000000..e5ce840 --- /dev/null +++ b/mesh/crypto.h @@ -0,0 +1,164 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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. + * + */ + +#include +#include + +bool mesh_crypto_aes_ccm_encrypt(const uint8_t nonce[13], const uint8_t key[16], + const uint8_t *aad, uint16_t aad_len, + const void *msg, uint16_t msg_len, + void *out_msg, + void *out_mic, size_t mic_size); +bool mesh_crypto_aes_ccm_decrypt(const uint8_t nonce[13], const uint8_t key[16], + const uint8_t *aad, uint16_t aad_len, + const void *enc_msg, uint16_t enc_msg_len, + void *out_msg, + void *out_mic, size_t mic_size); +bool mesh_aes_ecb_one(const uint8_t key[16], + const uint8_t plaintext[16], uint8_t encrypted[16]); +bool mesh_crypto_nkik(const uint8_t network_key[16], uint8_t identity_key[16]); +bool mesh_crypto_nkbk(const uint8_t network_key[16], uint8_t beacon_key[16]); +bool mesh_crypto_nkpk(const uint8_t network_key[16], uint8_t proxy_key[16]); +bool mesh_crypto_identity(const uint8_t net_key[16], uint16_t addr, + uint8_t id[16]); +bool mesh_crypto_beacon_cmac(const uint8_t encryption_key[16], + const uint8_t network_id[16], + uint32_t iv_index, bool kr, + bool iu, uint64_t *cmac); +bool mesh_crypto_network_nonce(bool frnd, uint8_t ttl, uint32_t seq, + uint16_t src, uint32_t iv_index, + uint8_t nonce[13]); +bool mesh_crypto_network_encrypt(bool ctl, uint8_t ttl, + uint32_t seq, uint16_t src, + uint32_t iv_index, + const uint8_t net_key[16], + const uint8_t *enc_msg, uint8_t enc_msg_len, + uint8_t *out, void *net_mic); +bool mesh_crypto_network_decrypt(bool frnd, uint8_t ttl, + uint32_t seq, uint16_t src, + uint32_t iv_index, + const uint8_t net_key[16], + const uint8_t *enc_msg, uint8_t enc_msg_len, + uint8_t *out, void *net_mic, size_t mic_size); +bool mesh_crypto_application_nonce(uint32_t seq, uint16_t src, + uint16_t dst, uint32_t iv_index, + bool aszmic, uint8_t nonce[13]); +bool mesh_crypto_device_nonce(uint32_t seq, uint16_t src, + uint16_t dst, uint32_t iv_index, + bool aszmic, uint8_t nonce[13]); +bool mesh_crypto_application_encrypt(uint8_t akf, uint32_t seq, uint16_t src, + uint16_t dst, uint32_t iv_index, + const uint8_t app_key[16], + const uint8_t *aad, uint8_t aad_len, + const uint8_t *msg, uint8_t msg_len, + uint8_t *out, + void *app_mic, size_t mic_size); +bool mesh_crypto_application_decrypt(uint8_t akf, uint32_t seq, uint16_t src, + uint16_t dst, uint32_t iv_index, + const uint8_t app_key[16], + const uint8_t *aad, uint8_t aad_len, + const uint8_t *enc_msg, uint8_t enc_msg_len, + uint8_t *out, void *app_mic, size_t mic_size); +bool mesh_crypto_device_key(const uint8_t secret[32], + const uint8_t salt[16], + uint8_t device_key[16]); +bool mesh_crypto_virtual_addr(const uint8_t virtual_label[16], + uint16_t *v_addr); +bool mesh_crypto_nonce(const uint8_t secret[32], + const uint8_t salt[16], + uint8_t nonce[13]); +bool mesh_crypto_k1(const uint8_t ikm[16], const uint8_t salt[16], + const void *info, size_t info_len, uint8_t okm[16]); +bool mesh_crypto_k2(const uint8_t n[16], const uint8_t *p, size_t p_len, + uint8_t net_id[1], + uint8_t enc_key[16], + uint8_t priv_key[16]); +bool mesh_crypto_k3(const uint8_t n[16], uint8_t out64[8]); +bool mesh_crypto_k4(const uint8_t a[16], uint8_t out5[1]); +bool mesh_crypto_s1(const void *info, size_t len, uint8_t salt[16]); +bool mesh_crypto_prov_prov_salt(const uint8_t conf_salt[16], + const uint8_t prov_rand[16], + const uint8_t dev_rand[16], + uint8_t prov_salt[16]); +bool mesh_crypto_prov_conf_key(const uint8_t secret[32], + const uint8_t salt[16], + uint8_t conf_key[16]); +bool mesh_crypto_session_key(const uint8_t secret[32], + const uint8_t salt[16], + uint8_t session_key[16]); +bool mesh_crypto_privacy_counter(uint32_t iv_index, + const uint8_t *payload, + uint8_t privacy_counter[16]); +bool mesh_crypto_network_obfuscate(const uint8_t privacy_key[16], + const uint8_t privacy_counter[16], + bool ctl, uint8_t ttl, uint32_t seq, + uint16_t src, uint8_t *out); +bool mesh_crypto_network_clarify(const uint8_t privacy_key[16], + const uint8_t privacy_counter[16], + const uint8_t net_hdr[6], + bool *ctl, uint8_t *ttl, + uint32_t *seq, uint16_t *src); + +bool mesh_crypto_packet_build(bool ctl, uint8_t ttl, + uint32_t seq, + uint16_t src, uint16_t dst, + uint8_t opcode, + bool segmented, uint8_t key_aid, + bool szmic, bool relay, uint16_t seqZero, + uint8_t segO, uint8_t segN, + const uint8_t *payload, uint8_t payload_len, + uint8_t *packet, uint8_t *packet_len); +bool mesh_crypto_packet_parse(const uint8_t *packet, uint8_t packet_len, + bool *ctl, uint8_t *ttl, uint32_t *seq, + uint16_t *src, uint16_t *dst, + uint32_t *cookie, uint8_t *opcode, + bool *segmented, uint8_t *key_aid, + bool *szmic, bool *relay, uint16_t *seqZero, + uint8_t *segO, uint8_t *segN, + const uint8_t **payload, uint8_t *payload_len); +bool mesh_crypto_payload_encrypt(uint8_t *aad, const uint8_t *payload, + uint8_t *out, uint16_t payload_len, + uint16_t src, uint16_t dst, uint8_t key_aid, + uint32_t seq_num, uint32_t iv_index, + bool aszmic, + const uint8_t application_key[16]); +bool mesh_crypto_payload_decrypt(uint8_t *aad, uint16_t aad_len, + const uint8_t *payload, uint16_t payload_len, + bool szmict, + uint16_t src, uint16_t dst, uint8_t key_aid, + uint32_t seq_num, uint32_t iv_index, + uint8_t *out, + const uint8_t application_key[16]); +bool mesh_crypto_packet_encode(uint8_t *packet, uint8_t packet_len, + const uint8_t network_key[16], + uint32_t iv_index, + const uint8_t privacy_key[16]); +bool mesh_crypto_packet_decode(const uint8_t *packet, uint8_t packet_len, + bool proxy, uint8_t *out, uint32_t iv_index, + const uint8_t network_key[16], + const uint8_t privacy_key[16]); +bool mesh_crypto_packet_label(uint8_t *packet, uint8_t packet_len, + uint16_t iv_index, uint8_t network_id); + +uint8_t mesh_crypto_compute_fcs(const uint8_t *packet, uint8_t packet_len); +bool mesh_crypto_check_fcs(const uint8_t *packet, uint8_t packet_len, + uint8_t received_fcs); +bool mesh_crypto_aes_cmac(const uint8_t key[16], const uint8_t *msg, + size_t msg_len, uint8_t res[16]); +bool mesh_crypto_check_avail(void); diff --git a/mesh/dbus.c b/mesh/dbus.c new file mode 100644 index 0000000..6b9694a --- /dev/null +++ b/mesh/dbus.c @@ -0,0 +1,145 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "mesh/mesh-defs.h" +#include "mesh/node.h" +#include "mesh/manager.h" +#include "mesh/mesh.h" +#include "mesh/error.h" +#include "mesh/dbus.h" + +static struct l_dbus *dbus; + +struct error_entry { + const char *dbus_err; + const char *default_desc; +}; + +/* + * Important: The entries in this table follow the order of + * enumerated values in mesh_error (file error.h) + */ +static struct error_entry const error_table[] = +{ + { NULL, NULL }, + { ERROR_INTERFACE ".Failed", "Operation failed" }, + { ERROR_INTERFACE ".NotAuthorized", "Permission denied"}, + { ERROR_INTERFACE ".NotFound", "Object not found"}, + { ERROR_INTERFACE ".InvalidArgs", "Invalid arguments"}, + { ERROR_INTERFACE ".InProgress", "Already in progress"}, + { ERROR_INTERFACE ".AlreadyExists", "Already exists"}, + { ERROR_INTERFACE ".DoesNotExist", "Does not exist"}, + { ERROR_INTERFACE ".Canceled", "Operation canceled"}, + { ERROR_INTERFACE ".NotImplemented", "Not implemented"}, +}; + +struct l_dbus_message *dbus_error(struct l_dbus_message *msg, int err, + const char *description) +{ + int array_len = L_ARRAY_SIZE(error_table); + + /* Default to ".Failed" */ + if (!err || err >= array_len) + err = MESH_ERROR_FAILED; + + if (description) + return l_dbus_message_new_error(msg, + error_table[err].dbus_err, + "%s", description); + else + return l_dbus_message_new_error(msg, + error_table[err].dbus_err, + "%s", error_table[err].default_desc); +} + +struct l_dbus *dbus_get_bus(void) +{ + return dbus; +} + +bool dbus_init(struct l_dbus *bus) +{ + /* Network interface */ + if (!mesh_dbus_init(bus)) + return false; + + /* Node interface */ + if (!node_dbus_init(bus)) + return false; + + /* Management interface */ + if (!manager_dbus_init(bus)) + return false; + + dbus = bus; + + return true; +} + +bool dbus_match_interface(struct l_dbus_message_iter *interfaces, + const char *match) +{ + const char *interface; + struct l_dbus_message_iter properties; + + while (l_dbus_message_iter_next_entry(interfaces, &interface, + &properties)) { + if (!strcmp(match, interface)) + return true; + } + + return false; +} + +void dbus_append_byte_array(struct l_dbus_message_builder *builder, + const uint8_t *data, int len) +{ + int i; + + if (!builder) + return; + + l_dbus_message_builder_enter_array(builder, "y"); + + for (i = 0; i < len; i++) + l_dbus_message_builder_append_basic(builder, 'y', data + i); + + l_dbus_message_builder_leave_array(builder); +} + +void dbus_append_dict_entry_basic(struct l_dbus_message_builder *builder, + const char *key, const char *signature, + const void *data) +{ + if (!builder) + return; + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', key); + l_dbus_message_builder_enter_variant(builder, signature); + l_dbus_message_builder_append_basic(builder, signature[0], data); + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); +} diff --git a/mesh/dbus.h b/mesh/dbus.h new file mode 100644 index 0000000..e7643a5 --- /dev/null +++ b/mesh/dbus.h @@ -0,0 +1,33 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#define BLUEZ_MESH_PATH "/org/bluez/mesh" +#define BLUEZ_MESH_SERVICE "org.bluez.mesh" + +bool dbus_init(struct l_dbus *dbus); +struct l_dbus *dbus_get_bus(void); +void dbus_append_byte_array(struct l_dbus_message_builder *builder, + const uint8_t *data, int len); +void dbus_append_dict_entry_basic(struct l_dbus_message_builder *builder, + const char *key, const char *signature, + const void *data); +bool dbus_match_interface(struct l_dbus_message_iter *interfaces, + const char *match); +struct l_dbus_message *dbus_error(struct l_dbus_message *msg, int err, + const char *description); diff --git a/mesh/error.h b/mesh/error.h new file mode 100644 index 0000000..f3e0f54 --- /dev/null +++ b/mesh/error.h @@ -0,0 +1,35 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 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. + * + */ + +/* + * Important: Changes in this table must be reflected in the + * the entries of error_table[] in dbus.c + */ +enum mesh_error { + MESH_ERROR_NONE, + MESH_ERROR_FAILED, + MESH_ERROR_NOT_AUTHORIZED, + MESH_ERROR_NOT_FOUND, + MESH_ERROR_INVALID_ARGS, + MESH_ERROR_BUSY, + MESH_ERROR_ALREADY_EXISTS, + MESH_ERROR_DOES_NOT_EXIST, + MESH_ERROR_CANCELED, + MESH_ERROR_NOT_IMPLEMENTED, +}; diff --git a/mesh/friend.c b/mesh/friend.c new file mode 100644 index 0000000..da27728 --- /dev/null +++ b/mesh/friend.c @@ -0,0 +1,1068 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "mesh/mesh-defs.h" + +#include "mesh/net-keys.h" +#include "mesh/net.h" +#include "mesh/model.h" +#include "mesh/util.h" + +#include "mesh/friend.h" + +#define MAX_FRND_GROUPS 20 +#define FRND_RELAY_WINDOW 250 /* 250 ms */ +#define FRND_CACHE_SIZE 16 +#define FRND_SUB_LIST_SIZE 8 + +#define RESPONSE_DELAY (100 - 12) /* 100 ms - 12ms hw delay */ +#define MIN_RESP_DELAY 10 /* 10 ms */ +#define MAX_RESP_DELAY 255 /* 255 ms */ + +/* Absolute maximum time to wait for LPN to choose us. */ +#define RESPONSE_POLL_DELAY 1300 /* 1.300 s */ + +static uint8_t frnd_relay_window = FRND_RELAY_WINDOW; +static uint8_t frnd_cache_size = FRND_CACHE_SIZE; +static uint8_t frnd_sublist_size = FRND_SUB_LIST_SIZE; + +struct frnd_negotiation { + struct l_timeout *timeout; + struct mesh_net *net; + uint32_t key_id; + uint32_t poll_timeout; + uint16_t low_power_node; + uint16_t old_relay; + uint8_t num_ele; + uint8_t lp_cnt; + uint8_t fn_cnt; + uint8_t wrfrw; + uint8_t receive_delay; + int8_t rssi; + bool clearing; +}; + +static struct l_queue *frnd_negotiations; +static uint16_t counter; + +static void response_timeout(struct l_timeout *timeout, void *user_data) +{ + struct frnd_negotiation *neg = user_data; + + /* LPN did not choose us */ + l_debug("Did not win negotiation for %4.4x", neg->low_power_node); + + net_key_unref(neg->key_id); + l_queue_remove(frnd_negotiations, neg); + l_timeout_remove(timeout); + l_free(neg); +} + +static void response_delay(struct l_timeout *timeout, void *user_data) +{ + struct frnd_negotiation *neg = user_data; + uint16_t net_idx = mesh_net_get_primary_idx(neg->net); + uint32_t key_id; + uint8_t msg[8]; + uint16_t n = 0; + bool res; + + l_timeout_remove(timeout); + + /* Create key Set for this offer */ + res = mesh_net_get_key(neg->net, false, net_idx, &key_id); + if (!res) + goto cleanup; + + neg->key_id = net_key_frnd_add(key_id, neg->low_power_node, + mesh_net_get_address(neg->net), + neg->lp_cnt, counter); + if (!neg->key_id) + goto cleanup; + + neg->fn_cnt = counter++; + + msg[n++] = NET_OP_FRND_OFFER; + msg[n++] = frnd_relay_window; + msg[n++] = frnd_cache_size; + msg[n++] = frnd_sublist_size; + msg[n++] = neg->rssi; + l_put_be16(neg->fn_cnt, msg + n); + n += 2; + print_packet("Tx-NET_OP_FRND_OFFER", msg, n); + mesh_net_transport_send(neg->net, 0, true, + mesh_net_get_iv_index(neg->net), 0, + 0, 0, neg->low_power_node, + msg, n); + + /* Offer expires in 1.3 seconds, which is the max time for LPN to + * receive all offers, 1 second to make decision, and a little extra + */ + neg->timeout = l_timeout_create_ms(1000 + MAX_RESP_DELAY, + response_timeout, neg, NULL); + + return; + +cleanup: + net_key_unref(neg->key_id); + l_queue_remove(frnd_negotiations, neg); + l_free(neg); +} + +static uint8_t cache_size(uint8_t power) +{ + return 1 << power; +} + +static bool match_by_lpn(const void *a, const void *b) +{ + const struct frnd_negotiation *neg = a; + uint16_t lpn = L_PTR_TO_UINT(b); + + return neg->low_power_node == lpn; +} + +static bool match_by_dst(const void *a, const void *b) +{ + const struct mesh_friend *frnd = a; + uint16_t dst = L_PTR_TO_UINT(b); + + return frnd->dst == dst; +} + +/* Scaling factors in 1/10 ms */ +static const int32_t scaling[] = { + 10, + 15, + 20, + 15, +}; + +void friend_request(struct mesh_net *net, uint16_t src, + uint8_t minReq, uint8_t delay, uint32_t timeout, + uint16_t prev, uint8_t num_ele, uint16_t cntr, + int8_t rssi) +{ + struct frnd_negotiation *neg; + uint8_t rssiScale = (minReq >> 5) & 3; + uint8_t winScale = (minReq >> 3) & 3; + uint8_t minCache = (minReq >> 0) & 7; + int32_t rsp_delay; + + l_debug("RSSI of Request: %d dbm", rssi); + l_debug("Delay: %d ms", delay); + l_debug("Poll Timeout of Request: %d ms", timeout * 100); + l_debug("Previous Friend: %4.4x", prev); + l_debug("Num Elem: %2.2x", num_ele); + l_debug("Cache Requested: %d", cache_size(minCache)); + l_debug("Cache to offer: %d", frnd_cache_size); + + /* Determine our own suitability before + * deciding to participate in negotiation + */ + if (minCache == 0 || num_ele == 0) + return; + + if (delay < 0x0A) + return; + + if (timeout < 0x00000A || timeout > 0x34BBFF) + return; + + if (cache_size(minCache) > frnd_cache_size) + return; + + if (frnd_negotiations == NULL) + frnd_negotiations = l_queue_new(); + + /* TODO: Check RSSI, and then start Negotiation if appropriate */ + + /* We are participating in this Negotiation */ + neg = l_new(struct frnd_negotiation, 1); + l_queue_push_head(frnd_negotiations, neg); + + neg->net = net; + neg->low_power_node = src; + neg->lp_cnt = cntr; + neg->rssi = rssi; + neg->receive_delay = delay; + neg->poll_timeout = timeout; + neg->old_relay = prev; + neg->num_ele = num_ele; + + /* RSSI (Negative Factor, larger values == less time) + * Scaling factor 0-3 == multiplier of 1.0 - 2.5 + * Minimum factor of 1. Bit 1 adds additional factor + * of 1, bit zero and additional 0.5 + */ + rsp_delay = -(rssi * scaling[rssiScale]); + l_debug("RSSI Factor: %d ms", rsp_delay / 10); + + /* Relay Window (Positive Factor, larger values == more time) + * Scaling factor 0-3 == multiplier of 1.0 - 2.5 + * Minimum factor of 1. Bit 1 adds additional factor + * of 1, bit zero and additional 0.5 + */ + rsp_delay += frnd_relay_window * scaling[winScale]; + l_debug("Win Size Factor: %d ms", + (frnd_relay_window * scaling[winScale]) / 10); + + /* Normalize to ms */ + rsp_delay /= 10; + + /* Range limits are 10-255 ms */ + if (rsp_delay < MIN_RESP_DELAY) + rsp_delay = MIN_RESP_DELAY; + else if (rsp_delay > MAX_RESP_DELAY) + rsp_delay = MAX_RESP_DELAY; + + l_debug("Total Response Delay: %d ms", rsp_delay); + + /* Add in 100ms delay before start of "Offer Period" */ + rsp_delay += RESPONSE_DELAY; + + neg->timeout = l_timeout_create_ms(rsp_delay, + response_delay, neg, NULL); +} + +static struct l_queue *retired_lpns; + +void friend_clear_confirm(struct mesh_net *net, uint16_t src, + uint16_t lpn, uint16_t lpnCounter) +{ + struct frnd_negotiation *neg = l_queue_remove_if(frnd_negotiations, + match_by_lpn, L_UINT_TO_PTR(lpn)); + + l_debug("Friend Clear confirmed %4.4x (cnt %4.4x)", lpn, lpnCounter); + + if (!neg) + return; + + l_timeout_remove(neg->timeout); + l_queue_remove(frnd_negotiations, neg); + l_free(neg); +} + +static void friend_poll_timeout(struct l_timeout *timeout, void *user_data) +{ + struct mesh_friend *frnd = user_data; + + if (mesh_friend_clear(frnd->net, frnd)) + l_debug("Friend Poll Timeout %4.4x", frnd->dst); + + l_timeout_remove(frnd->timeout); + frnd->timeout = NULL; + + /* Friend may be in either Network or Retired list, so try both */ + l_queue_remove(retired_lpns, frnd); + mesh_friend_free(frnd); +} + +void friend_clear(struct mesh_net *net, uint16_t src, uint16_t lpn, + uint16_t lpnCounter, struct mesh_friend *frnd) +{ + uint8_t msg[5] = { NET_OP_FRND_CLEAR_CONFIRM }; + bool removed = false; + uint16_t lpnDelta; + + if (frnd) { + lpnDelta = lpnCounter - frnd->lp_cnt; + + /* Ignore old Friend Clear commands */ + if (lpnDelta > 0x100) + return; + + /* Move friend from Network list to Retired list */ + removed = mesh_friend_clear(net, frnd); + if (removed) { + struct mesh_friend *old; + struct frnd_negotiation *neg = l_queue_remove_if( + frnd_negotiations, + match_by_lpn, + L_UINT_TO_PTR(frnd->dst)); + + /* Cancel any negotiations or clears */ + if (neg) { + l_timeout_remove(neg->timeout); + l_free(neg); + } + + /* Create Retired LPN list if needed */ + if (retired_lpns == NULL) + retired_lpns = l_queue_new(); + + /* Find any duplicates */ + old = l_queue_find(retired_lpns, match_by_dst, + L_UINT_TO_PTR(lpn)); + + /* Force time-out of old friendship */ + if (old) + friend_poll_timeout(old->timeout, old); + + /* Retire this LPN (keeps timeout running) */ + l_queue_push_tail(retired_lpns, frnd); + } + } else { + frnd = l_queue_find(retired_lpns, match_by_dst, + L_UINT_TO_PTR(lpn)); + if (!frnd) + return; + + lpnDelta = lpnCounter - frnd->lp_cnt; + + /* Ignore old Friend Clear commands */ + if (!lpnDelta || (lpnDelta > 0x100)) + return; + } + + l_debug("Friend Cleared %4.4x (%4.4x)", lpn, lpnCounter); + + l_put_be16(lpn, msg + 1); + l_put_be16(lpnCounter, msg + 3); + mesh_net_transport_send(net, 0, false, + mesh_net_get_iv_index(net), DEFAULT_TTL, + 0, 0, src, + msg, sizeof(msg)); +} + +static void clear_retry(struct l_timeout *timeout, void *user_data) +{ + struct frnd_negotiation *neg = user_data; + uint8_t msg[5] = { NET_OP_FRND_CLEAR }; + uint32_t secs = 1 << neg->receive_delay; + + + l_put_be16(neg->low_power_node, msg + 1); + l_put_be16(neg->lp_cnt, msg + 3); + mesh_net_transport_send(neg->net, 0, false, + mesh_net_get_iv_index(neg->net), DEFAULT_TTL, + 0, 0, neg->old_relay, + msg, sizeof(msg)); + + if (secs && ((secs << 1) < neg->poll_timeout/10)) { + neg->receive_delay++; + l_debug("Try FRND_CLR again in %d seconds (total timeout %d)", + secs, neg->poll_timeout/10); + l_timeout_modify(neg->timeout, secs); + } else { + l_debug("FRND_CLR timed out %d", secs); + l_timeout_remove(timeout); + l_queue_remove(frnd_negotiations, neg); + l_free(neg); + } +} + +static void friend_delay_rsp(struct l_timeout *timeout, void *user_data) +{ + struct mesh_friend *frnd = user_data; + struct mesh_friend_msg *pkt = frnd->pkt; + struct mesh_net *net = frnd->net; + uint32_t net_seq, iv_index; + uint8_t upd[7] = { NET_OP_FRND_UPDATE }; + + l_timeout_remove(timeout); + + if (pkt == NULL) + goto update; + + if (pkt->ctl) { + /* Make sure we don't change the bit-sense of MD, + * once it has been set because that would cause + * a "Dirty Nonce" security violation + */ + if (((pkt->u.one[0].hdr >> OPCODE_HDR_SHIFT) & OPCODE_MASK) == + NET_OP_SEG_ACKNOWLEDGE) { + bool rly = !!((pkt->u.one[0].hdr >> RELAY_HDR_SHIFT) & + true); + uint16_t seqZero = pkt->u.one[0].hdr >> + SEQ_ZERO_HDR_SHIFT; + + seqZero &= SEQ_ZERO_MASK; + + l_debug("Fwd ACK pkt %6.6x-%8.8x", + pkt->u.one[0].seq, + pkt->iv_index); + + pkt->u.one[0].sent = true; + mesh_net_ack_send(net, frnd->net_key_cur, + pkt->iv_index, pkt->ttl, + pkt->u.one[0].seq, pkt->src, pkt->dst, + rly, seqZero, + l_get_be32(pkt->u.one[0].data)); + + + } else { + l_debug("Fwd CTL pkt %6.6x-%8.8x", + pkt->u.one[0].seq, + pkt->iv_index); + + print_packet("Frnd-CTL", + pkt->u.one[0].data, pkt->last_len); + + pkt->u.one[0].sent = true; + mesh_net_transport_send(net, frnd->net_key_cur, false, + pkt->iv_index, pkt->ttl, + pkt->u.one[0].seq, pkt->src, pkt->dst, + pkt->u.one[0].data, pkt->last_len); + } + } else { + /* If segments after this one, then More Data must be TRUE */ + uint8_t len; + + if (pkt->cnt_out < pkt->cnt_in) + len = sizeof(pkt->u.s12[0].data); + else + len = pkt->last_len; + + l_debug("Fwd FRND pkt %6.6x", + pkt->u.s12[pkt->cnt_out].seq); + + print_packet("Frnd-Msg", pkt->u.s12[pkt->cnt_out].data, len); + + pkt->u.s12[pkt->cnt_out].sent = true; + mesh_net_send_seg(net, frnd->net_key_cur, + pkt->iv_index, + pkt->ttl, + pkt->u.s12[pkt->cnt_out].seq, + pkt->src, pkt->dst, + pkt->u.s12[pkt->cnt_out].hdr, + pkt->u.s12[pkt->cnt_out].data, len); + } + + return; + +update: + /* No More Data -- send Update message with md = false */ + net_seq = mesh_net_get_seq_num(net); + l_debug("Fwd FRND UPDATE %6.6x with MD == 0", net_seq); + + frnd->last = frnd->seq; + mesh_net_get_snb_state(net, upd + 1, &iv_index); + l_put_be32(iv_index, upd + 2); + upd[6] = false; /* Queue is Empty */ + print_packet("Update", upd, sizeof(upd)); + mesh_net_transport_send(net, frnd->net_key_cur, false, + mesh_net_get_iv_index(net), 0, + net_seq, 0, frnd->dst, + upd, sizeof(upd)); + mesh_net_next_seq_num(net); +} + + +void friend_poll(struct mesh_net *net, uint16_t src, bool seq, + struct mesh_friend *frnd) +{ + struct frnd_negotiation *neg; + struct mesh_friend_msg *pkt; + bool md; + + neg = l_queue_find(frnd_negotiations, match_by_lpn, L_UINT_TO_PTR(src)); + if (neg && !neg->clearing) { + uint8_t msg[5] = { NET_OP_FRND_CLEAR }; + + l_debug("Won negotiation for %4.4x", neg->low_power_node); + + /* This call will clean-up and replace if already friends */ + frnd = mesh_friend_new(net, src, neg->num_ele, + neg->receive_delay, + neg->wrfrw, + neg->poll_timeout, + neg->fn_cnt, neg->lp_cnt); + + frnd->timeout = l_timeout_create_ms( + frnd->poll_timeout * 100, + friend_poll_timeout, frnd, NULL); + + l_timeout_remove(neg->timeout); + net_key_unref(neg->key_id); + neg->key_id = 0; + + if (neg->old_relay == 0 || + neg->old_relay == mesh_net_get_address(net)) { + l_queue_remove(frnd_negotiations, neg); + l_free(neg); + } else { + neg->clearing = true; + l_put_be16(neg->low_power_node, msg + 1); + l_put_be16(neg->lp_cnt, msg + 3); + mesh_net_transport_send(net, 0, false, + mesh_net_get_iv_index(net), DEFAULT_TTL, + 0, 0, neg->old_relay, + msg, sizeof(msg)); + + /* Reuse receive_delay as a shift counter to + * time-out FRIEND_CLEAR + */ + neg->receive_delay = 1; + neg->timeout = l_timeout_create(1, clear_retry, + neg, NULL); + } + } + + if (!frnd) + return; + + /* Reset Poll Timeout */ + l_timeout_modify_ms(frnd->timeout, frnd->poll_timeout * 100); + + if (!l_queue_length(frnd->pkt_cache)) + goto update; + + if (frnd->seq != frnd->last && frnd->seq != seq) { + pkt = l_queue_peek_head(frnd->pkt_cache); + if (pkt->cnt_out < pkt->cnt_in) { + pkt->cnt_out++; + } else { + pkt = l_queue_pop_head(frnd->pkt_cache); + l_free(pkt); + } + } + + pkt = l_queue_peek_head(frnd->pkt_cache); + + if (!pkt) + goto update; + + frnd->seq = seq; + frnd->last = !seq; + md = !!(l_queue_length(frnd->pkt_cache) > 1); + + if (pkt->ctl) { + /* Make sure we don't change the bit-sense of MD, + * once it has been set because that would cause + * a "Dirty Nonce" security violation + */ + if (!(pkt->u.one[0].sent)) + pkt->u.one[0].md = md; + } else { + /* If segments after this one, then More Data must be TRUE */ + if (pkt->cnt_out < pkt->cnt_in) + md = true; + + /* Make sure we don't change the bit-sense of MD, once + * it has been set because that would cause a + * "Dirty Nonce" security violation + */ + if (!(pkt->u.s12[pkt->cnt_out].sent)) + pkt->u.s12[pkt->cnt_out].md = md; + } + frnd->pkt = pkt; + l_timeout_create_ms(frnd->frd, friend_delay_rsp, frnd, NULL); + + return; + +update: + frnd->pkt = NULL; + l_timeout_create_ms(frnd->frd, friend_delay_rsp, frnd, NULL); +} + +void friend_sub_add(struct mesh_net *net, struct mesh_friend *frnd, + const uint8_t *pkt, uint8_t len) +{ + uint16_t *new_list; + uint32_t net_seq; + uint8_t plen = len; + uint8_t msg[] = { NET_OP_PROXY_SUB_CONFIRM, 0 }; + + if (!frnd || MAX_FRND_GROUPS < frnd->grp_cnt + (len/2)) + return; + + msg[1] = *pkt++; + plen--; + + /* Sanity Check Values, abort if any illegal */ + while (plen >= 2) { + plen -= 2; + if (l_get_be16(pkt + plen) < 0x8000) + return; + } + + new_list = l_malloc(frnd->grp_cnt * sizeof(uint16_t) + len); + if (frnd->grp_list) + memcpy(new_list, frnd->grp_list, + frnd->grp_cnt * sizeof(uint16_t)); + + while (len >= 2) { + new_list[frnd->grp_cnt++] = l_get_be16(pkt); + pkt += 2; + len -= 2; + } + + l_free(frnd->grp_list); + frnd->grp_list = new_list; + + print_packet("Tx-NET_OP_PROXY_SUB_CONFIRM", msg, sizeof(msg)); + net_seq = mesh_net_get_seq_num(net); + mesh_net_transport_send(net, frnd->net_key_cur, false, + mesh_net_get_iv_index(net), 0, + net_seq, 0, frnd->dst, + msg, sizeof(msg)); + mesh_net_next_seq_num(net); +} + +void friend_sub_del(struct mesh_net *net, struct mesh_friend *frnd, + const uint8_t *pkt, uint8_t len) +{ + uint32_t net_seq; + uint8_t msg[] = { NET_OP_PROXY_SUB_CONFIRM, 0 }; + int i; + + if (!frnd) + return; + + msg[1] = *pkt++; + len--; + + while (len >= 2) { + uint16_t grp = l_get_be16(pkt); + + for (i = frnd->grp_cnt - 1; i >= 0; i--) { + if (frnd->grp_list[i] == grp) { + frnd->grp_cnt--; + memcpy(&frnd->grp_list[i], + &frnd->grp_list[i + 1], + (frnd->grp_cnt - i) * 2); + break; + } + } + len -= 2; + pkt += 2; + } + + print_packet("Tx-NET_OP_PROXY_SUB_CONFIRM", msg, sizeof(msg)); + net_seq = mesh_net_get_seq_num(net); + mesh_net_transport_send(net, frnd->net_key_cur, false, + mesh_net_get_iv_index(net), 0, + net_seq, 0, frnd->dst, + msg, sizeof(msg)); + mesh_net_next_seq_num(net); +} + +/* Low-Power-Node role */ +struct frnd_offers { + uint16_t fn_cnt; + uint16_t src; + uint8_t window; + uint8_t cache; + uint8_t sub_list_size; + int8_t local_rssi; + int8_t remote_rssi; +}; + +#define MAX_POLL_RETRIES 5 +static bool quick_pick; +static uint8_t poll_cnt; +static struct l_queue *offers; +static uint16_t old_friend; +static uint16_t fn_cnt, cnt = 0xffff; +static uint32_t poll_period_ms; +static struct l_timeout *poll_retry_to; +static struct l_timeout *poll_period_to; +static uint32_t lpn_key_id; +static uint32_t new_lpn_id; + +void frnd_offer(struct mesh_net *net, uint16_t src, uint8_t window, + uint8_t cache, uint8_t sub_list_size, + int8_t r_rssi, int8_t l_rssi, uint16_t fn_cnt) +{ + struct frnd_offers *offer; + + l_debug("RSSI of Offer: %d dbm", l_rssi); + + /* Ignore RFU window value 0 */ + if (window == 0) + return; + + if (mesh_net_get_friend(net)) + return; + + if (quick_pick) { + if (mesh_net_set_friend(net, src)) { + old_friend = src; + frnd_poll(net, false); + } + return; + } + + offer = l_new(struct frnd_offers, 1); + offer->src = src; + offer->window = window; + offer->cache = cache; + offer->sub_list_size = sub_list_size; + offer->local_rssi = l_rssi; + offer->remote_rssi = r_rssi; + offer->fn_cnt = fn_cnt; + + l_queue_push_tail(offers, offer); +} + +static void frnd_poll_timeout(struct l_timeout *timeout, void *user_data) +{ + struct mesh_net *net = user_data; + + frnd_poll(net, true); +} + +static void frnd_negotiated_to(struct l_timeout *timeout, void *user_data) +{ + struct mesh_net *net = user_data; + + l_debug("frnd_negotiated_to"); + if (!mesh_net_get_friend(net)) { + l_timeout_remove(poll_period_to); + poll_period_to = NULL; + return; + } + + if (!poll_retry_to) + frnd_poll(net, false); +} + +void frnd_poll_cancel(struct mesh_net *net) +{ + l_timeout_remove(poll_retry_to); + poll_retry_to = NULL; +} + +void frnd_poll(struct mesh_net *net, bool retry) +{ + uint32_t key_id = lpn_key_id; + uint32_t net_seq; + uint8_t msg[2] = { NET_OP_FRND_POLL }; + bool seq = mesh_net_get_frnd_seq(net); + + /* Check if we are in Phase 2 of Key Refresh */ + if (new_lpn_id) { + uint8_t phase; + uint16_t net_idx = mesh_net_get_primary_idx(net); + uint8_t status = + mesh_net_key_refresh_phase_get(net, net_idx, &phase); + + if (status == MESH_STATUS_SUCCESS && + phase == KEY_REFRESH_PHASE_TWO) + key_id = new_lpn_id; + } + + if (!retry) { + poll_cnt = MAX_POLL_RETRIES; + seq = !seq; + mesh_net_set_frnd_seq(net, seq); + } else if (!(poll_cnt--)) { + l_debug("Lost Friendship with %4.4x", old_friend); + l_timeout_remove(poll_period_to); + poll_period_to = NULL; + frnd_poll_cancel(net); + net_key_unref(lpn_key_id); + net_key_unref(new_lpn_id); + new_lpn_id = lpn_key_id = 0; + mesh_net_set_friend(net, 0); + return; + } + + if (poll_retry_to) + l_timeout_remove(poll_retry_to); + + l_debug("TX-FRIEND POLL %d", seq); + msg[1] = seq; + net_seq = mesh_net_get_seq_num(net); + mesh_net_transport_send(net, key_id, true, + mesh_net_get_iv_index(net), 0, + net_seq, 0, mesh_net_get_friend(net), + msg, sizeof(msg)); + mesh_net_next_seq_num(net); + poll_retry_to = l_timeout_create_ms(1000, frnd_poll_timeout, net, NULL); + + /* Reset Poll Period for next "Wake Up" */ + if (poll_period_to) + l_timeout_modify_ms(poll_period_to, poll_period_ms); + else + poll_period_to = l_timeout_create_ms(poll_period_ms, + frnd_negotiated_to, net, NULL); +} + +void frnd_ack_poll(struct mesh_net *net) +{ + /* Start new POLL, but only if not already Polling */ + if (poll_retry_to == NULL) + frnd_poll(net, false); +} + +static void req_timeout(struct l_timeout *timeout, void *user_data) +{ + struct mesh_net *net = user_data; + struct frnd_offers *best; + struct frnd_offers *offer = l_queue_pop_head(offers); + uint32_t key_id = 0; + bool res; + + l_timeout_remove(timeout); + + best = offer; + while (offer) { + /* Screen out clearly inferior RSSI friends first */ + if (offer->local_rssi < -40 && offer->remote_rssi < -40) { + if (best->local_rssi + 20 < offer->local_rssi || + best->remote_rssi + 20 < offer->remote_rssi) { + + l_free(best); + best = offer; + offer = l_queue_pop_head(offers); + continue; + } + } + + /* Otherwise use best Windows, with Cache size as tie breaker */ + if (best->window > offer->window || + (best->window == offer->window && + best->cache < offer->cache)) { + l_free(best); + best = offer; + } else if (best != offer) + l_free(offer); + + offer = l_queue_pop_head(offers); + } + + net_key_unref(lpn_key_id); + net_key_unref(new_lpn_id); + new_lpn_id = lpn_key_id = 0; + if (mesh_net_get_friend(net)) { + l_free(best); + return; + } else if (!best) { + l_debug("No Offers Received"); + return; + } + + fn_cnt = best->fn_cnt; + res = mesh_net_get_key(net, false, mesh_net_get_primary_idx(net), + &key_id); + if (!res) + return; + + lpn_key_id = net_key_frnd_add(key_id, mesh_net_get_address(net), + best->src, cnt, best->fn_cnt); + if (!lpn_key_id) + return; + + res = mesh_net_get_key(net, true, mesh_net_get_primary_idx(net), + &key_id); + + if (!res) + goto old_keys_only; + + new_lpn_id = net_key_frnd_add(key_id, mesh_net_get_address(net), + best->src, cnt, best->fn_cnt); + +old_keys_only: + + l_debug("Winning offer %4.4x RSSI: %ddb Window: %dms Cache sz: %d", + best->src, best->local_rssi, + best->window, best->cache); + + if (mesh_net_set_friend(net, best->src)) { + old_friend = best->src; + mesh_net_set_frnd_seq(net, true); + frnd_poll(net, false); + } + + l_free(best); +} + +void frnd_clear(struct mesh_net *net) +{ + uint8_t msg[12]; + uint8_t n = 0; + uint16_t frnd_addr = mesh_net_get_friend(net); + uint16_t my_addr = mesh_net_get_address(net); + + msg[n++] = NET_OP_FRND_CLEAR; + l_put_be16(my_addr, msg + n); + n += 2; + l_put_be16(cnt, msg + n); + n += 2; + + net_key_unref(lpn_key_id); + net_key_unref(new_lpn_id); + mesh_net_set_friend(net, 0); + + mesh_net_transport_send(net, 0, false, + mesh_net_get_iv_index(net), 0, + 0, 0, frnd_addr, + msg, n); +} + +void frnd_request_friend(struct mesh_net *net, uint8_t cache, + uint8_t offer_delay, uint8_t delay, uint32_t timeout) +{ + uint8_t msg[12]; + uint8_t n = 0; + + if (offers == NULL) + offers = l_queue_new(); + + msg[n++] = NET_OP_FRND_REQUEST; + msg[n] = cache & 0x07; /* MinRequirements - Cache */ + msg[n++] |= (offer_delay & 0x0f) << 3; /* Offer Delay */ + poll_period_ms = (timeout * 300) / 4; /* 3/4 of the time in ms */ + l_put_be32(timeout, msg + n); /* PollTimeout */ + msg[n++] = delay; /* ReceiveDelay */ + n += 3; + l_put_be16(old_friend, msg + n); /* PreviousAddress */ + n += 2; + msg[n++] = mesh_net_get_num_ele(net); /* NumElements */ + l_put_be16(cnt + 1, msg + n); /* Next counter */ + n += 2; + print_packet("Tx-NET_OP_FRND_REQUEST", msg, n); + mesh_net_transport_send(net, 0, false, + mesh_net_get_iv_index(net), 0, + 0, 0, FRIENDS_ADDRESS, + msg, n); + l_timeout_create_ms(1000, req_timeout, net, NULL); /* 1000 ms */ + mesh_net_set_friend(net, 0); + cnt++; +} + +static uint8_t trans_id; +void frnd_sub_add(struct mesh_net *net, uint32_t parms[7]) +{ + uint32_t key_id = lpn_key_id; + uint32_t net_seq; + uint8_t msg[15] = { NET_OP_PROXY_SUB_ADD }; + uint8_t i, n = 1; + + /* Check if we are in Phase 2 of Key Refresh */ + if (new_lpn_id) { + uint8_t phase; + uint16_t net_idx = mesh_net_get_primary_idx(net); + uint8_t status = mesh_net_key_refresh_phase_get(net, + net_idx, &phase); + + if (status == MESH_STATUS_SUCCESS && + phase == KEY_REFRESH_PHASE_TWO) + key_id = new_lpn_id; + } + + msg[n++] = ++trans_id; + for (i = 0; i < 7; i++) { + if (parms[i] < 0x8000 || parms[i] > 0xffff) + break; + + l_put_be16(parms[i], msg + n); + n += 2; + } + + net_seq = mesh_net_get_seq_num(net); + print_packet("Friend Sub Add", msg, n); + mesh_net_transport_send(net, key_id, false, + mesh_net_get_iv_index(net), 0, + net_seq, 0, mesh_net_get_friend(net), + msg, n); + mesh_net_next_seq_num(net); +} + +void frnd_sub_del(struct mesh_net *net, uint32_t parms[7]) +{ + uint32_t key_id = lpn_key_id; + uint32_t net_seq; + uint8_t msg[15] = { NET_OP_PROXY_SUB_REMOVE }; + uint8_t i, n = 1; + + /* Check if we are in Phase 2 of Key Refresh */ + if (new_lpn_id) { + uint8_t phase; + uint16_t net_idx = mesh_net_get_primary_idx(net); + uint8_t status = mesh_net_key_refresh_phase_get(net, + net_idx, &phase); + + if (status == MESH_STATUS_SUCCESS && + phase == KEY_REFRESH_PHASE_TWO) + key_id = new_lpn_id; + } + + msg[n++] = ++trans_id; + for (i = 0; i < 7; i++) { + if (parms[i] < 0x8000 || parms[i] > 0xffff) + break; + + l_put_be16(parms[i], msg + n); + n += 2; + } + + net_seq = mesh_net_get_seq_num(net); + print_packet("Friend Sub Del", msg, n); + mesh_net_transport_send(net, key_id, false, + mesh_net_get_iv_index(net), 0, + net_seq, 0, mesh_net_get_friend(net), + msg, n); + mesh_net_next_seq_num(net); +} + +void frnd_key_refresh(struct mesh_net *net, uint8_t phase) +{ + uint16_t net_idx = mesh_net_get_primary_idx(net); + uint32_t key_id; + + switch (phase) { + default: + case 0: + case 3: + if (new_lpn_id) { + l_debug("LPN Retiring KeySet %d", lpn_key_id); + net_key_unref(lpn_key_id); + lpn_key_id = new_lpn_id; + } + return; + + case 1: + net_key_unref(new_lpn_id); + if (!mesh_net_get_key(net, true, net_idx, &key_id)) { + new_lpn_id = 0; + return; + } + + new_lpn_id = net_key_frnd_add(key_id, mesh_net_get_address(net), + mesh_net_get_friend(net), + cnt, fn_cnt); + return; + + case 2: + /* Should we do anything here? Maybe not */ + return; + } +} + +uint32_t frnd_get_key(struct mesh_net *net) +{ + uint8_t idx = mesh_net_get_primary_idx(net); + uint8_t phase = 0; + + mesh_net_key_refresh_phase_get(net, idx, &phase); + + if (phase == 2) + return new_lpn_id; + else + return lpn_key_id; +} diff --git a/mesh/friend.h b/mesh/friend.h new file mode 100644 index 0000000..7ab5dea --- /dev/null +++ b/mesh/friend.h @@ -0,0 +1,57 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#define OP_FRND_REQUEST 0x8040 +#define OP_FRND_INQUIRY 0x8041 +#define OP_FRND_CONFIRM 0x8042 +#define OP_FRND_SUB_LIST_ADD 0x8043 +#define OP_FRND_SUB_LIST_CONFIRM 0x8044 +#define OP_FRND_SUB_LIST_REMOVE 0x8045 +#define OP_FRND_NEGOTIATE 0x8046 +#define OP_FRND_CLEAR 0x8047 + +void friend_poll(struct mesh_net *net, uint16_t src, bool seq, + struct mesh_friend *frnd); +void friend_request(struct mesh_net *net, uint16_t src, uint8_t minReq, + uint8_t delay, uint32_t timeout, uint16_t prev, + uint8_t num_elements, uint16_t cntr, int8_t rssi); +void friend_clear_confirm(struct mesh_net *net, uint16_t src, uint16_t lpn, + uint16_t lpnCounter); +void friend_clear(struct mesh_net *net, uint16_t src, uint16_t lpn, + uint16_t lpnCounter, struct mesh_friend *frnd); +void friend_sub_add(struct mesh_net *net, struct mesh_friend *frnd, + const uint8_t *pkt, uint8_t len); +void friend_sub_del(struct mesh_net *net, struct mesh_friend *frnd, + const uint8_t *pkt, uint8_t len); +void mesh_friend_relay_init(struct mesh_net *net, uint16_t addr); + +/* Low-Power-Node role */ +void frnd_sub_add(struct mesh_net *net, uint32_t parms[7]); +void frnd_sub_del(struct mesh_net *net, uint32_t parms[7]); +void frnd_poll(struct mesh_net *net, bool retry); +void frnd_clear(struct mesh_net *net); +void frnd_ack_poll(struct mesh_net *net); +void frnd_poll_cancel(struct mesh_net *net); +void frnd_request_friend(struct mesh_net *net, uint8_t cache, + uint8_t offer_delay, uint8_t delay, uint32_t timeout); +void frnd_offer(struct mesh_net *net, uint16_t src, uint8_t window, + uint8_t cache, uint8_t sub_list_size, + int8_t r_rssi, int8_t l_rssi, uint16_t fn_cnt); +void frnd_key_refresh(struct mesh_net *net, uint8_t phase); +uint32_t frnd_get_key(struct mesh_net *net); diff --git a/mesh/keyring.c b/mesh/keyring.c new file mode 100644 index 0000000..9fa7d6b --- /dev/null +++ b/mesh/keyring.c @@ -0,0 +1,365 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "mesh/mesh-defs.h" + +#include "mesh/node.h" +#include "mesh/keyring.h" + +const char *dev_key_dir = "/dev_keys"; +const char *app_key_dir = "/app_keys"; +const char *net_key_dir = "/net_keys"; + +bool keyring_put_net_key(struct mesh_node *node, uint16_t net_idx, + struct keyring_net_key *key) +{ + const char *node_path; + char key_file[PATH_MAX]; + bool result = false; + int fd; + + if (!node || !key) + return false; + + node_path = node_get_storage_dir(node); + + if (strlen(node_path) + strlen(net_key_dir) + 1 + 3 >= PATH_MAX) + return false; + + snprintf(key_file, PATH_MAX, "%s%s", node_path, net_key_dir); + mkdir(key_file, 0755); + snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, net_key_dir, + net_idx); + l_debug("Put Net Key %s", key_file); + + fd = open(key_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd >= 0) { + if (write(fd, key, sizeof(*key)) == sizeof(*key)) + result = true; + + close(fd); + } + + return result; +} + +bool keyring_put_app_key(struct mesh_node *node, uint16_t app_idx, + uint16_t net_idx, struct keyring_app_key *key) +{ + const char *node_path; + char key_file[PATH_MAX]; + bool result = false; + int fd; + + if (!node || !key) + return false; + + node_path = node_get_storage_dir(node); + + if (strlen(node_path) + strlen(app_key_dir) + 1 + 3 >= PATH_MAX) + return false; + + snprintf(key_file, PATH_MAX, "%s%s", node_path, app_key_dir); + mkdir(key_file, 0755); + snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, app_key_dir, + app_idx); + l_debug("Put App Key %s", key_file); + + fd = open(key_file, O_RDWR); + if (fd >= 0) { + struct keyring_app_key old_key; + + if (read(fd, &old_key, sizeof(old_key)) == sizeof(old_key)) { + if (old_key.net_idx != net_idx) { + close(fd); + return false; + } + } + + lseek(fd, 0, SEEK_SET); + } else + fd = open(key_file, O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR); + + if (fd >= 0) { + if (write(fd, key, sizeof(*key)) == sizeof(*key)) + result = true; + + close(fd); + } + + return result; +} + +static void finalize(const char *fpath, uint16_t net_idx) +{ + struct keyring_app_key key; + int fd; + + fd = open(fpath, O_RDWR); + + if (fd < 0) + return; + + if (read(fd, &key, sizeof(key)) != sizeof(key) || + key.net_idx != net_idx) + goto done; + + l_debug("Finalize %s", fpath); + memcpy(key.old_key, key.new_key, 16); + lseek(fd, 0, SEEK_SET); + write(fd, &key, sizeof(key)); + +done: + close(fd); +} + +bool keyring_finalize_app_keys(struct mesh_node *node, uint16_t net_idx) +{ + const char *node_path; + char key_dir[PATH_MAX]; + DIR *dir; + struct dirent *entry; + + if (!node) + return false; + + node_path = node_get_storage_dir(node); + + if (strlen(node_path) + strlen(app_key_dir) + 1 >= PATH_MAX) + return false; + + snprintf(key_dir, PATH_MAX, "%s%s", node_path, app_key_dir); + dir = opendir(key_dir); + if (!dir) { + l_error("Failed to App Key storage directory: %s", key_dir); + return false; + } + + while ((entry = readdir(dir)) != NULL) { + /* AppKeys are stored in regular files */ + if (entry->d_type == DT_REG) + finalize(entry->d_name, net_idx); + } + + closedir(dir); + + return true; +} + +bool keyring_put_remote_dev_key(struct mesh_node *node, uint16_t unicast, + uint8_t count, uint8_t dev_key[16]) +{ + const char *node_path; + char key_file[PATH_MAX]; + bool result = true; + int fd, i; + + if (!IS_UNICAST_RANGE(unicast, count)) + return false; + + if (!node) + return false; + + node_path = node_get_storage_dir(node); + + if (strlen(node_path) + strlen(dev_key_dir) + 1 + 4 >= PATH_MAX) + return false; + + snprintf(key_file, PATH_MAX, "%s%s", node_path, dev_key_dir); + mkdir(key_file, 0755); + + for (i = 0; i < count; i++) { + snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path, + dev_key_dir, unicast + i); + l_debug("Put Dev Key %s", key_file); + + fd = open(key_file, O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR); + if (fd >= 0) { + if (write(fd, dev_key, 16) != 16) + result = false; + + close(fd); + } else + result = false; + } + + return result; +} + +bool keyring_get_net_key(struct mesh_node *node, uint16_t net_idx, + struct keyring_net_key *key) +{ + const char *node_path; + char key_file[PATH_MAX]; + bool result = false; + int fd; + + if (!node || !key) + return false; + + node_path = node_get_storage_dir(node); + snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, net_key_dir, + net_idx); + + fd = open(key_file, O_RDONLY); + if (fd >= 0) { + if (read(fd, key, sizeof(*key)) == sizeof(*key)) + result = true; + + close(fd); + } + + return result; +} + +bool keyring_get_app_key(struct mesh_node *node, uint16_t app_idx, + struct keyring_app_key *key) +{ + const char *node_path; + char key_file[PATH_MAX]; + bool result = false; + int fd; + + if (!node || !key) + return false; + + node_path = node_get_storage_dir(node); + snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, app_key_dir, + app_idx); + + fd = open(key_file, O_RDONLY); + if (fd >= 0) { + if (read(fd, key, sizeof(*key)) == sizeof(*key)) + result = true; + + close(fd); + } + + return result; +} + +bool keyring_get_remote_dev_key(struct mesh_node *node, uint16_t unicast, + uint8_t dev_key[16]) +{ + const char *node_path; + char key_file[PATH_MAX]; + bool result = false; + int fd; + + if (!IS_UNICAST(unicast)) + return false; + + if (!node) + return false; + + node_path = node_get_storage_dir(node); + + snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path, dev_key_dir, + unicast); + + fd = open(key_file, O_RDONLY); + if (fd >= 0) { + if (read(fd, dev_key, 16) == 16) + result = true; + + close(fd); + } + + return result; +} + +bool keyring_del_net_key(struct mesh_node *node, uint16_t net_idx) +{ + const char *node_path; + char key_file[PATH_MAX]; + + if (!node) + return false; + + node_path = node_get_storage_dir(node); + snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, net_key_dir, + net_idx); + l_debug("RM Net Key %s", key_file); + remove(key_file); + + /* TODO: See if it is easiest to delete all bound App keys here */ + /* TODO: see nftw() */ + + return true; +} + +bool keyring_del_app_key(struct mesh_node *node, uint16_t app_idx) +{ + const char *node_path; + char key_file[PATH_MAX]; + + if (!node) + return false; + + node_path = node_get_storage_dir(node); + snprintf(key_file, PATH_MAX, "%s%s/%3.3x", node_path, app_key_dir, + app_idx); + l_debug("RM App Key %s", key_file); + remove(key_file); + + return true; +} + +bool keyring_del_remote_dev_key(struct mesh_node *node, uint16_t unicast, + uint8_t count) +{ + const char *node_path; + char key_file[PATH_MAX]; + int i; + + if (!IS_UNICAST_RANGE(unicast, count)) + return false; + + if (!node) + return false; + + node_path = node_get_storage_dir(node); + + for (i = 0; i < count; i++) { + snprintf(key_file, PATH_MAX, "%s%s/%4.4x", node_path, + dev_key_dir, unicast + i); + l_debug("RM Dev Key %s", key_file); + remove(key_file); + } + + return true; +} diff --git a/mesh/keyring.h b/mesh/keyring.h new file mode 100644 index 0000000..2fab6b0 --- /dev/null +++ b/mesh/keyring.h @@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +struct keyring_net_key { + uint16_t net_idx; + uint8_t phase; + uint8_t old_key[16]; + uint8_t new_key[16]; +}; + +struct keyring_app_key { + uint16_t app_idx; + uint16_t net_idx; + uint8_t old_key[16]; + uint8_t new_key[16]; +}; + +bool keyring_put_net_key(struct mesh_node *node, uint16_t net_idx, + struct keyring_net_key *key); +bool keyring_get_net_key(struct mesh_node *node, uint16_t net_idx, + struct keyring_net_key *key); +bool keyring_del_net_key(struct mesh_node *node, uint16_t net_idx); +bool keyring_put_app_key(struct mesh_node *node, uint16_t app_idx, + uint16_t net_idx, struct keyring_app_key *key); +bool keyring_finalize_app_keys(struct mesh_node *node, uint16_t net_id); +bool keyring_get_app_key(struct mesh_node *node, uint16_t app_idx, + struct keyring_app_key *key); +bool keyring_del_app_key(struct mesh_node *node, uint16_t app_idx); +bool keyring_get_remote_dev_key(struct mesh_node *node, uint16_t unicast, + uint8_t dev_key[16]); +bool keyring_put_remote_dev_key(struct mesh_node *node, uint16_t unicast, + uint8_t count, uint8_t dev_key[16]); +bool keyring_del_remote_dev_key(struct mesh_node *node, uint16_t unicast, + uint8_t count); diff --git a/mesh/main.c b/mesh/main.c new file mode 100644 index 0000000..6ea210c --- /dev/null +++ b/mesh/main.c @@ -0,0 +1,213 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2017-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/mgmt.h" + +#include "mesh/mesh.h" +#include "mesh/crypto.h" +#include "mesh/dbus.h" +#include "mesh/mesh-io.h" + +static const struct option main_options[] = { + { "index", required_argument, NULL, 'i' }, + { "config", optional_argument, NULL, 'c' }, + { "nodetach", no_argument, NULL, 'n' }, + { "debug", no_argument, NULL, 'd' }, + { "dbus-debug", no_argument, NULL, 'b' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +static void usage(void) +{ + l_info(""); + l_info("Usage:\n" + "\tbluetooth-meshd [options]\n"); + l_info("Options:\n" + "\t--index Use specified controller\n" + "\t--config Configuration directory\n" + "\t--nodetach Run in foreground\n" + "\t--debug Enable debug output\n" + "\t--dbus-debug Enable D-Bus debugging\n" + "\t--help Show %s information\n", __func__); +} + +static void do_debug(const char *str, void *user_data) +{ + const char *prefix = user_data; + + l_info("%s%s", prefix, str); +} + +static void request_name_callback(struct l_dbus *dbus, bool success, + bool queued, void *user_data) +{ + l_info("Request name %s", + success ? "success": "failed"); + + if (success) + dbus_init(dbus); + else + l_main_quit(); +} + +static void ready_callback(void *user_data) +{ + struct l_dbus *dbus = user_data; + + l_info("D-Bus ready"); + l_dbus_name_acquire(dbus, BLUEZ_MESH_NAME, false, false, false, + request_name_callback, NULL); + +} + +static void disconnect_callback(void *user_data) +{ + l_main_quit(); +} + +static void signal_handler(uint32_t signo, void *user_data) +{ + static bool terminated; + + if (terminated) + return; + + l_info("Terminating"); + l_main_quit(); + terminated = true; +} + +int main(int argc, char *argv[]) +{ + int status; + bool detached = true; + bool dbus_debug = false; + struct l_dbus *dbus = NULL; + const char *config_dir = NULL; + int index = MGMT_INDEX_NONE; + + if (!l_main_init()) + return -1; + + l_log_set_stderr(); + + if (!mesh_crypto_check_avail()) { + l_error("Mesh Crypto functions unavailable"); + status = l_main_run_with_signal(signal_handler, NULL); + goto done; + } + + for (;;) { + int opt; + const char *str; + + opt = getopt_long(argc, argv, "i:c:ndbh", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'i': + if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3)) + str = optarg + 3; + else + str = optarg; + if (!isdigit(*str)) { + l_error("Invalid controller index value"); + status = EXIT_FAILURE; + goto done; + } + + index = atoi(str); + + break; + case 'n': + detached = false; + break; + case 'd': + l_debug_enable("*"); + break; + case 'c': + config_dir = optarg; + break; + case 'b': + dbus_debug = true; + break; + case 'h': + usage(); + status = EXIT_SUCCESS; + goto done; + default: + usage(); + status = EXIT_FAILURE; + goto done; + } + } + + + if (!mesh_init(config_dir, MESH_IO_TYPE_GENERIC, &index)) { + l_error("Failed to initialize mesh"); + status = EXIT_FAILURE; + goto done; + } + + if (!detached) + umask(0077); + + dbus = l_dbus_new_default(L_DBUS_SYSTEM_BUS); + if (!dbus) { + l_error("unable to connect to D-Bus"); + status = EXIT_FAILURE; + goto done; + } + + if (dbus_debug) + l_dbus_set_debug(dbus, do_debug, "[DBUS] ", NULL); + l_dbus_set_ready_handler(dbus, ready_callback, dbus, NULL); + l_dbus_set_disconnect_handler(dbus, disconnect_callback, NULL, NULL); + + if (!l_dbus_object_manager_enable(dbus, "/")) { + l_error("Failed to enable Object Manager"); + status = EXIT_FAILURE; + goto done; + } + + status = l_main_run_with_signal(signal_handler, NULL); + +done: + mesh_cleanup(); + l_dbus_destroy(dbus); + l_main_exit(); + + return status; +} diff --git a/mesh/manager.c b/mesh/manager.c new file mode 100644 index 0000000..6335976 --- /dev/null +++ b/mesh/manager.c @@ -0,0 +1,744 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include + +#include "mesh/mesh-defs.h" +#include "mesh/dbus.h" +#include "mesh/error.h" +#include "mesh/mesh.h" +#include "mesh/mesh-io.h" +#include "mesh/node.h" +#include "mesh/net.h" +#include "mesh/keyring.h" +#include "mesh/agent.h" +#include "mesh/provision.h" +#include "mesh/manager.h" + +struct add_data{ + struct l_dbus_message *msg; + struct mesh_agent *agent; + struct mesh_node *node; + uint32_t disc_watch; + uint16_t primary; + uint16_t net_idx; + uint8_t num_ele; + uint8_t uuid[16]; +}; + +static int8_t scan_rssi; +static uint8_t scan_uuid[16]; +static struct mesh_node *scan_node; +static struct l_timeout *scan_timeout; +static struct add_data *add_pending; + +static void scan_cancel(struct l_timeout *timeout, void *user_data) +{ + struct mesh_node *node = user_data; + struct mesh_io *io; + struct mesh_net *net; + + l_debug("scan_cancel"); + + if (scan_timeout) + l_timeout_remove(scan_timeout); + + net = node_get_net(node); + io = mesh_net_get_io(net); + mesh_io_deregister_recv_cb(io, MESH_IO_FILTER_PROV_BEACON); + scan_node = NULL; + scan_timeout = NULL; +} + +static void free_pending_add_call() +{ + if (!add_pending) + return; + + if (add_pending->disc_watch) + l_dbus_remove_watch(dbus_get_bus(), + add_pending->disc_watch); + + mesh_agent_remove(add_pending->agent); + + l_free(add_pending); + add_pending = NULL; +} + +static void prov_disc_cb(struct l_dbus *bus, void *user_data) +{ + if (!add_pending) + return; + + initiator_cancel(add_pending); + add_pending->disc_watch = 0; + + free_pending_add_call(); +} + +static void send_add_failed(const char *owner, const char *path, + uint8_t status) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message_builder *builder; + struct l_dbus_message *msg; + + msg = l_dbus_message_new_method_call(dbus, owner, path, + MESH_PROVISIONER_INTERFACE, + "AddNodeFailed"); + + builder = l_dbus_message_builder_new(msg); + dbus_append_byte_array(builder, add_pending->uuid, 16); + l_dbus_message_builder_append_basic(builder, 's', + mesh_prov_status_str(status)); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + l_dbus_send(dbus, msg); + + free_pending_add_call(); +} + +static bool add_cmplt(void *user_data, uint8_t status, + struct mesh_prov_node_info *info) +{ + struct add_data *pending = user_data; + struct mesh_node *node = pending->node; + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message_builder *builder; + struct l_dbus_message *msg; + bool result; + + if (pending != add_pending) + return false; + + if (status != PROV_ERR_SUCCESS) { + send_add_failed(node_get_owner(node), node_get_app_path(node), + status); + return false; + } + + result = keyring_put_remote_dev_key(add_pending->node, info->unicast, + info->num_ele, info->device_key); + + if (!result) { + send_add_failed(node_get_owner(node), node_get_app_path(node), + PROV_ERR_CANT_ASSIGN_ADDR); + return false; + } + + msg = l_dbus_message_new_method_call(dbus, node_get_owner(node), + node_get_app_path(node), + MESH_PROVISIONER_INTERFACE, + "AddNodeComplete"); + + builder = l_dbus_message_builder_new(msg); + dbus_append_byte_array(builder, add_pending->uuid, 16); + l_dbus_message_builder_append_basic(builder, 'q', &info->unicast); + l_dbus_message_builder_append_basic(builder, 'y', &info->num_ele); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + l_dbus_send(dbus, msg); + + free_pending_add_call(); + + return true; +} + +static void mgr_prov_data (struct l_dbus_message *reply, void *user_data) +{ + struct add_data *pending = user_data; + uint16_t net_idx; + uint16_t primary; + + if (pending != add_pending) + return; + + if (l_dbus_message_is_error(reply)) + return; + + if (!l_dbus_message_get_arguments(reply, "qq", &net_idx, &primary)) + return; + + add_pending->primary = primary; + add_pending->net_idx = net_idx; + initiator_prov_data(net_idx, primary, add_pending); +} + +static bool add_data_get(void *user_data, uint8_t num_ele) +{ + struct add_data *pending = user_data; + struct l_dbus_message *msg; + struct l_dbus *dbus; + const char *app_path; + const char *sender; + + if (pending != add_pending) + return false; + + dbus = dbus_get_bus(); + app_path = node_get_app_path(add_pending->node); + sender = node_get_owner(add_pending->node); + + msg = l_dbus_message_new_method_call(dbus, sender, app_path, + MESH_PROVISIONER_INTERFACE, + "RequestProvData"); + + l_dbus_message_set_arguments(msg, "y", num_ele); + l_dbus_send_with_reply(dbus, msg, mgr_prov_data, add_pending, NULL); + + add_pending->num_ele = num_ele; + + return true; +} + +static struct l_dbus_message *add_node_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + struct l_dbus_message_iter iter_uuid; + struct l_dbus_message *reply; + uint8_t *uuid; + uint32_t n; + + l_debug("AddNode request"); + + if (!l_dbus_message_get_arguments(msg, "ay", &iter_uuid)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, &uuid, &n) + || n != 16) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad device UUID"); + + /* Allow AddNode to cancel Scanning if from the same node */ + if (scan_node) { + if (scan_node != node) + return dbus_error(msg, MESH_ERROR_BUSY, NULL); + + scan_cancel(NULL, node); + } + + /* Invoke Prov Initiator */ + + add_pending = l_new(struct add_data, 1); + memcpy(add_pending->uuid, uuid, 16); + add_pending->node = node; + add_pending->agent = node_get_agent(node);; + + if (!node_is_provisioner(node) || (add_pending->agent == NULL)) { + l_info("Provisioner: %d", node_is_provisioner(node)); + l_info("Agent: %p", add_pending->agent); + reply = dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, + "Missing Interfaces"); + goto fail; + } + + + if (!initiator_start(PB_ADV, uuid, 99, 0, 60, add_pending->agent, + add_data_get, add_cmplt, node, add_pending)) { + reply = dbus_error(msg, MESH_ERROR_FAILED, + "Failed to start provisioning initiator"); + goto fail; + } + + add_pending->disc_watch = l_dbus_add_disconnect_watch(dbus, + node_get_owner(node), + prov_disc_cb, NULL, NULL); + + return l_dbus_message_new_method_return(msg); + +fail: + l_free(add_pending); + add_pending = NULL; + return reply; +} + + +static struct l_dbus_message *import_node_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + struct l_dbus_message_iter iter_key; + uint16_t primary; + uint8_t num_ele; + uint8_t *key; + uint32_t n; + + if (!l_dbus_message_get_arguments(msg, "qyay", &primary, &num_ele, + &iter_key)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!l_dbus_message_iter_get_fixed_array(&iter_key, &key, &n) + || n != 16) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad device key"); + + if (!keyring_put_remote_dev_key(node, primary, num_ele, key)) + return dbus_error(msg, MESH_ERROR_FAILED, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *delete_node_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + struct mesh_net *net = node_get_net(node); + uint16_t primary; + uint8_t num_ele; + + if (!l_dbus_message_get_arguments(msg, "qy", &primary, &num_ele)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (mesh_net_is_local_address(net, primary, num_ele)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Cannot remove local device key"); + + keyring_del_remote_dev_key(node, primary, num_ele); + + return l_dbus_message_new_method_return(msg); +} + +static void prov_beacon_recv(void *user_data, struct mesh_io_recv_info *info, + const uint8_t *data, uint16_t len) +{ + struct mesh_node *node = user_data; + struct l_dbus_message_builder *builder; + struct l_dbus_message *msg; + struct l_dbus *dbus; + int16_t rssi; + + if (scan_node != node || len < sizeof(scan_uuid) + 2 || data[1] != 0x00) + return; + + if (!memcmp(data + 2, scan_uuid, sizeof(scan_uuid))) { + if (info->rssi <= scan_rssi) + return; + } + + memcpy(scan_uuid, data + 2, sizeof(scan_uuid)); + scan_rssi = info->rssi; + rssi = info->rssi; + + dbus = dbus_get_bus(); + msg = l_dbus_message_new_method_call(dbus, node_get_owner(node), + node_get_app_path(node), + MESH_PROVISIONER_INTERFACE, + "ScanResult"); + + builder = l_dbus_message_builder_new(msg); + l_dbus_message_builder_append_basic(builder, 'n', &rssi); + dbus_append_byte_array(builder, data + 2, len -2); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + l_dbus_send(dbus, msg); +} + +static struct l_dbus_message *start_scan_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + uint16_t duration; + struct mesh_io *io; + struct mesh_net *net; + + if (!l_dbus_message_get_arguments(msg, "q", &duration)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (scan_node && scan_node != node) + return dbus_error(msg, MESH_ERROR_BUSY, NULL); + + if (!node_is_provisioner(node)) + return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); + + if (scan_timeout) + l_timeout_remove(scan_timeout); + + memset(scan_uuid, 0, sizeof(scan_uuid)); + scan_rssi = -128; + scan_timeout = NULL; + net = node_get_net(node); + io = mesh_net_get_io(net); + scan_node = node; + mesh_io_register_recv_cb(io, MESH_IO_FILTER_PROV_BEACON, + prov_beacon_recv, node); + + if (duration) + scan_timeout = l_timeout_create(duration, scan_cancel, + node, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *cancel_scan_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + + if (scan_node != node) + return dbus_error(msg, MESH_ERROR_BUSY, NULL); + + scan_cancel(NULL, node); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *store_new_subnet(struct mesh_node *node, + struct l_dbus_message *msg, + uint16_t net_idx, uint8_t *new_key) +{ + struct keyring_net_key key; + + if (net_idx > MAX_KEY_IDX) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (keyring_get_net_key(node, net_idx, &key)) { + /* Allow redundant calls only if key values match */ + if (!memcmp(key.old_key, new_key, 16)) + return l_dbus_message_new_method_return(msg); + + return dbus_error(msg, MESH_ERROR_ALREADY_EXISTS, NULL); + } + + memcpy(key.old_key, new_key, 16); + memcpy(key.new_key, new_key, 16); + key.net_idx = net_idx; + key.phase = KEY_REFRESH_PHASE_NONE; + + if (!keyring_put_net_key(node, net_idx, &key)) + return dbus_error(msg, MESH_ERROR_FAILED, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *create_subnet_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + uint8_t key[16]; + uint16_t net_idx; + + if (!l_dbus_message_get_arguments(msg, "q", &net_idx) || + net_idx == PRIMARY_NET_IDX) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + /* Generate key and store */ + l_getrandom(key, sizeof(key)); + + return store_new_subnet(node, msg, net_idx, key); +} + +static struct l_dbus_message *update_subnet_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + struct keyring_net_key key; + uint16_t net_idx; + + if (!l_dbus_message_get_arguments(msg, "q", &net_idx) || + net_idx > MAX_KEY_IDX) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!keyring_get_net_key(node, net_idx, &key)) + return dbus_error(msg, MESH_ERROR_DOES_NOT_EXIST, NULL); + + switch (key.phase) { + case KEY_REFRESH_PHASE_NONE: + /* Generate Key and update phase */ + l_getrandom(key.new_key, sizeof(key.new_key)); + key.phase = KEY_REFRESH_PHASE_ONE; + + if (!keyring_put_net_key(node, net_idx, &key)) + return dbus_error(msg, MESH_ERROR_FAILED, NULL); + + /* Fall Through */ + + case KEY_REFRESH_PHASE_ONE: + /* Allow redundant calls to start Key Refresh */ + return l_dbus_message_new_method_return(msg); + + default: + break; + } + + /* All other phases mean KR already in progress over-the-air */ + return dbus_error(msg, MESH_ERROR_BUSY, "Key Refresh in progress"); +} + +static struct l_dbus_message *delete_subnet_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + uint16_t net_idx; + + if (!l_dbus_message_get_arguments(msg, "q", &net_idx) || + net_idx > MAX_KEY_IDX) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + keyring_del_net_key(node, net_idx); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *import_subnet_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + struct l_dbus_message_iter iter_key; + uint16_t net_idx; + uint8_t *key; + uint32_t n; + + if (!l_dbus_message_get_arguments(msg, "qay", &net_idx, &iter_key)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!l_dbus_message_iter_get_fixed_array(&iter_key, &key, &n) + || n != 16) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad network key"); + + return store_new_subnet(node, msg, net_idx, key); +} + +static struct l_dbus_message *store_new_appkey(struct mesh_node *node, + struct l_dbus_message *msg, + uint16_t net_idx, uint16_t app_idx, + uint8_t *new_key) +{ + struct keyring_net_key net_key; + struct keyring_app_key app_key; + + if (net_idx > MAX_KEY_IDX || app_idx > MAX_KEY_IDX) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!keyring_get_net_key(node, net_idx, &net_key)) + return dbus_error(msg, MESH_ERROR_DOES_NOT_EXIST, + "Bound net key not found"); + + if (keyring_get_app_key(node, app_idx, &app_key)) { + /* Allow redundant calls with identical values */ + if (!memcmp(app_key.old_key, new_key, 16) && + app_key.net_idx == net_idx) + return l_dbus_message_new_method_return(msg); + + return dbus_error(msg, MESH_ERROR_ALREADY_EXISTS, NULL); + } + + memcpy(app_key.old_key, new_key, 16); + memcpy(app_key.new_key, new_key, 16); + app_key.net_idx = net_idx; + app_key.app_idx = app_idx; + + if (!keyring_put_app_key(node, app_idx, net_idx, &app_key)) + return dbus_error(msg, MESH_ERROR_FAILED, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *create_appkey_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + uint16_t net_idx, app_idx; + uint8_t key[16]; + + if (!l_dbus_message_get_arguments(msg, "qq", &net_idx, &app_idx)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + l_getrandom(key, sizeof(key)); + + return store_new_appkey(node, msg, net_idx, app_idx, key); +} + +static struct l_dbus_message *update_appkey_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + struct keyring_net_key net_key; + struct keyring_app_key app_key; + uint16_t app_idx; + + if (!l_dbus_message_get_arguments(msg, "q", &app_idx) || + app_idx > MAX_KEY_IDX) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!keyring_get_app_key(node, app_idx, &app_key) || + !keyring_get_net_key(node, app_key.net_idx, &net_key)) + return dbus_error(msg, MESH_ERROR_DOES_NOT_EXIST, NULL); + + if (net_key.phase != KEY_REFRESH_PHASE_ONE) + return dbus_error(msg, MESH_ERROR_FAILED, "Invalid Phase"); + + /* Generate Key if in acceptable phase */ + l_getrandom(app_key.new_key, sizeof(app_key.new_key)); + + if (!keyring_put_app_key(node, app_idx, app_key.net_idx, &app_key)) + return dbus_error(msg, MESH_ERROR_FAILED, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *delete_appkey_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + uint16_t app_idx; + + if (!l_dbus_message_get_arguments(msg, "q", &app_idx)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + keyring_del_app_key(node, app_idx); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *import_appkey_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + struct l_dbus_message_iter iter_key; + uint16_t net_idx, app_idx; + uint8_t *key; + uint32_t n; + + if (!l_dbus_message_get_arguments(msg, "qqay", &net_idx, &app_idx, + &iter_key)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!l_dbus_message_iter_get_fixed_array(&iter_key, &key, &n) + || n != 16) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad application key"); + + return store_new_appkey(node, msg, net_idx, app_idx, key); +} + +static struct l_dbus_message *set_key_phase_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + struct keyring_net_key key; + uint16_t net_idx; + uint8_t phase; + + if (!l_dbus_message_get_arguments(msg, "qy", &net_idx, &phase) || + phase == KEY_REFRESH_PHASE_ONE || + phase > KEY_REFRESH_PHASE_THREE) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!keyring_get_net_key(node, net_idx, &key)) + return dbus_error(msg, MESH_ERROR_DOES_NOT_EXIST, NULL); + + /* Canceling Key Refresh only valid from Phase One */ + if (phase == KEY_REFRESH_PHASE_NONE && + key.phase >= KEY_REFRESH_PHASE_TWO) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (phase == KEY_REFRESH_PHASE_THREE) { + + /* If we are already in Phase None, then nothing to do */ + if (key.phase == KEY_REFRESH_PHASE_NONE) + return l_dbus_message_new_method_return(msg); + + memcpy(key.old_key, key.new_key, 16); + key.phase = KEY_REFRESH_PHASE_THREE; + + if (!keyring_put_net_key(node, net_idx, &key)) + return dbus_error(msg, MESH_ERROR_FAILED, NULL); + + if (!keyring_finalize_app_keys(node, net_idx)) + return dbus_error(msg, MESH_ERROR_FAILED, NULL); + + key.phase = KEY_REFRESH_PHASE_NONE; + } else + key.phase = phase; + + if (!keyring_put_net_key(node, net_idx, &key)) + return dbus_error(msg, MESH_ERROR_FAILED, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static void setup_management_interface(struct l_dbus_interface *iface) +{ + l_dbus_interface_method(iface, "AddNode", 0, add_node_call, "", "ay", + "uuid"); + l_dbus_interface_method(iface, "ImportRemoteNode", 0, import_node_call, + "", "qyay", "", "primary", + "count", "dev_key"); + l_dbus_interface_method(iface, "DeleteRemoteNode", 0, delete_node_call, + "", "qy", "", "primary", "count"); + l_dbus_interface_method(iface, "UnprovisionedScan", 0, start_scan_call, + "", "q", "", "seconds"); + l_dbus_interface_method(iface, "UnprovisionedScanCancel", 0, + cancel_scan_call, "", ""); + l_dbus_interface_method(iface, "CreateSubnet", 0, create_subnet_call, + "", "q", "", "net_index"); + l_dbus_interface_method(iface, "UpdateSubnet", 0, update_subnet_call, + "", "q", "", "net_index"); + l_dbus_interface_method(iface, "DeleteSubnet", 0, delete_subnet_call, + "", "q", "", "net_index"); + l_dbus_interface_method(iface, "ImportSubnet", 0, import_subnet_call, + "", "qay", "", "net_index", "net_key"); + l_dbus_interface_method(iface, "CreateAppKey", 0, create_appkey_call, + "", "qq", "", "net_index", "app_index"); + l_dbus_interface_method(iface, "UpdateAppKey", 0, update_appkey_call, + "", "q", "", "app_index"); + l_dbus_interface_method(iface, "DeleteAppKey", 0, delete_appkey_call, + "", "q", "", "app_index"); + l_dbus_interface_method(iface, "ImportAppKey", 0, import_appkey_call, + "", "qqay", "", "net_index", "app_index", + "app_key"); + l_dbus_interface_method(iface, "SetKeyPhase", 0, set_key_phase_call, + "", "qy", "", "net_index", "phase"); +} + +bool manager_dbus_init(struct l_dbus *bus) +{ + if (!l_dbus_register_interface(bus, MESH_MANAGEMENT_INTERFACE, + setup_management_interface, + NULL, false)) { + l_info("Unable to register %s interface", + MESH_MANAGEMENT_INTERFACE); + return false; + } + + return true; +} diff --git a/mesh/manager.h b/mesh/manager.h new file mode 100644 index 0000000..f27ca41 --- /dev/null +++ b/mesh/manager.h @@ -0,0 +1,20 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2019 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. + * + */ + +bool manager_dbus_init(struct l_dbus *dbus); diff --git a/mesh/mesh-config-json.c b/mesh/mesh-config-json.c new file mode 100644 index 0000000..b2cff68 --- /dev/null +++ b/mesh/mesh-config-json.c @@ -0,0 +1,2366 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "mesh/mesh-defs.h" +#include "mesh/util.h" +#include "mesh/mesh-config.h" + +/* To prevent local node JSON cache thrashing, minimum update times */ +#define MIN_SEQ_CACHE_TRIGGER 32 +#define MIN_SEQ_CACHE_VALUE (2 * 32) +#define MIN_SEQ_CACHE_TIME (5 * 60) + +#define CHECK_KEY_IDX_RANGE(x) ((x) <= 4095) + +struct mesh_config { + json_object *jnode; + char *node_dir_path; + uint8_t uuid[16]; + uint32_t write_seq; + struct timeval write_time; +}; + +struct write_info { + struct mesh_config *cfg; + void *user_data; + mesh_config_status_func_t cb; +}; + +static const char *cfgnode_name = "/node.json"; +static const char *bak_ext = ".bak"; +static const char *tmp_ext = ".tmp"; + +static bool save_config(json_object *jnode, const char *fname) +{ + FILE *outfile; + const char *str; + bool result = false; + + outfile = fopen(fname, "w"); + if (!outfile) { + l_error("Failed to save configuration to %s", fname); + return false; + } + + str = json_object_to_json_string_ext(jnode, JSON_C_TO_STRING_PRETTY); + + if (fwrite(str, sizeof(char), strlen(str), outfile) < strlen(str)) + l_warn("Incomplete write of mesh configuration"); + else + result = true; + + fclose(outfile); + + return result; +} + +static bool get_int(json_object *jobj, const char *keyword, int *value) +{ + json_object *jvalue; + + if (!json_object_object_get_ex(jobj, keyword, &jvalue)) + return false; + + *value = json_object_get_int(jvalue); + if (errno == EINVAL) + return false; + + return true; +} + +static bool add_u64_value(json_object *jobject, const char *desc, + const uint8_t u64[8]) +{ + json_object *jstring; + char hexstr[17]; + + hex2str((uint8_t *) u64, 8, hexstr, 17); + jstring = json_object_new_string(hexstr); + if (!jstring) + return false; + + json_object_object_add(jobject, desc, jstring); + return true; +} + +static bool add_key_value(json_object *jobject, const char *desc, + const uint8_t key[16]) +{ + json_object *jstring; + char hexstr[33]; + + hex2str((uint8_t *) key, 16, hexstr, 33); + jstring = json_object_new_string(hexstr); + if (!jstring) + return false; + + json_object_object_add(jobject, desc, jstring); + return true; +} + +static int get_element_index(json_object *jnode, uint16_t ele_addr) +{ + json_object *jvalue, *jelements; + uint16_t addr, num_ele; + char *str; + + if (!json_object_object_get_ex(jnode, "unicastAddress", &jvalue)) + return -1; + + str = (char *)json_object_get_string(jvalue); + if (sscanf(str, "%04hx", &addr) != 1) + return -1; + + if (!json_object_object_get_ex(jnode, "elements", &jelements)) + return -1; + + num_ele = json_object_array_length(jelements); + + if (ele_addr >= addr + num_ele || ele_addr < addr) + return -1; + + return ele_addr - addr; +} + +static json_object *get_element_model(json_object *jnode, int ele_idx, + uint32_t mod_id, bool vendor) +{ + json_object *jelements, *jelement, *jmodels; + int i, num_mods; + size_t len; + char buf[9]; + + if (!vendor) + snprintf(buf, 5, "%4.4x", (uint16_t)mod_id); + else + snprintf(buf, 9, "%8.8x", mod_id); + + if (!json_object_object_get_ex(jnode, "elements", &jelements)) + return NULL; + + jelement = json_object_array_get_idx(jelements, ele_idx); + if (!jelement) + return NULL; + + if (!json_object_object_get_ex(jelement, "models", &jmodels)) + return NULL; + + num_mods = json_object_array_length(jmodels); + if (!num_mods) + return NULL; + + if (!vendor) { + snprintf(buf, 5, "%4.4x", mod_id); + len = 4; + } else { + snprintf(buf, 9, "%8.8x", mod_id); + len = 8; + } + + for (i = 0; i < num_mods; ++i) { + json_object *jmodel, *jvalue; + char *str; + + jmodel = json_object_array_get_idx(jmodels, i); + if (!json_object_object_get_ex(jmodel, "modelId", &jvalue)) + return NULL; + + str = (char *)json_object_get_string(jvalue); + if (!str) + return NULL; + + if (!strncmp(str, buf, len)) + return jmodel; + } + + return NULL; +} + +static bool jarray_has_string(json_object *jarray, char *str, size_t len) +{ + int i, sz = json_object_array_length(jarray); + + for (i = 0; i < sz; ++i) { + json_object *jentry; + char *str_entry; + + jentry = json_object_array_get_idx(jarray, i); + str_entry = (char *)json_object_get_string(jentry); + if (!str_entry) + continue; + + if (!strncmp(str, str_entry, len)) + return true; + } + + return false; +} + +static json_object *jarray_string_del(json_object *jarray, char *str, + size_t len) +{ + int i, sz = json_object_array_length(jarray); + json_object *jarray_new; + + jarray_new = json_object_new_array(); + if (!jarray_new) + return NULL; + + for (i = 0; i < sz; ++i) { + json_object *jentry; + char *str_entry; + + jentry = json_object_array_get_idx(jarray, i); + str_entry = (char *)json_object_get_string(jentry); + if (str_entry && !strncmp(str, str_entry, len)) + continue; + + json_object_get(jentry); + json_object_array_add(jarray_new, jentry); + } + + return jarray_new; +} + +static json_object *get_key_object(json_object *jarray, uint16_t idx) +{ + int i, sz = json_object_array_length(jarray); + + for (i = 0; i < sz; ++i) { + json_object *jentry, *jvalue; + const char *str; + uint16_t jidx; + + jentry = json_object_array_get_idx(jarray, i); + if (!json_object_object_get_ex(jentry, "index", &jvalue)) + return NULL; + + str = json_object_get_string(jvalue); + if (sscanf(str, "%04hx", &jidx) != 1) + return NULL; + + if (jidx == idx) + return jentry; + } + + return NULL; +} + +static bool get_key_index(json_object *jobj, const char *keyword, + uint16_t *index) +{ + uint16_t idx; + json_object *jvalue; + const char *str; + + if (!json_object_object_get_ex(jobj, keyword, &jvalue)) + return false; + + str = json_object_get_string(jvalue); + + if (sscanf(str, "%04hx", &idx) != 1) + return false; + + if (!CHECK_KEY_IDX_RANGE(idx)) + return false; + + *index = (uint16_t) idx; + return true; +} + +static json_object *jarray_key_del(json_object *jarray, int16_t idx) +{ + json_object *jarray_new; + int i, sz = json_object_array_length(jarray); + + jarray_new = json_object_new_array(); + if (!jarray_new) + return NULL; + + for (i = 0; i < sz; ++i) { + json_object *jentry; + uint16_t nidx; + + jentry = json_object_array_get_idx(jarray, i); + + if (get_key_index(jentry, "index", &nidx) && nidx == idx) + continue; + + json_object_get(jentry); + json_object_array_add(jarray_new, jentry); + } + + return jarray_new; +} + +static bool read_unicast_address(json_object *jobj, uint16_t *unicast) +{ + json_object *jvalue; + char *str; + + if (!json_object_object_get_ex(jobj, "unicastAddress", &jvalue)) + return false; + + str = (char *)json_object_get_string(jvalue); + if (sscanf(str, "%04hx", unicast) != 1) + return false; + + return true; +} + +static bool read_default_ttl(json_object *jobj, uint8_t *ttl) +{ + json_object *jvalue; + int val; + + /* defaultTTL is optional */ + if (!json_object_object_get_ex(jobj, "defaultTTL", &jvalue)) + return true; + + val = json_object_get_int(jvalue); + + if (!val && errno == EINVAL) + return false; + + if (val < 0 || val == 1 || val > DEFAULT_TTL) + return false; + + *ttl = (uint8_t) val; + + return true; +} + +static bool read_seq_number(json_object *jobj, uint32_t *seq_number) +{ + json_object *jvalue; + int val; + + /* sequenceNumber is optional */ + if (!json_object_object_get_ex(jobj, "sequenceNumber", &jvalue)) + return true; + + val = json_object_get_int(jvalue); + + if (!val && errno == EINVAL) + return false; + + if (val < 0 || val > 0xffffff) + return false; + + *seq_number = (uint32_t) val; + + return true; +} + +static bool read_iv_index(json_object *jobj, uint32_t *idx, bool *update) +{ + int tmp; + + /* IV index */ + if (!get_int(jobj, "IVindex", &tmp)) + return false; + + *idx = (uint32_t) tmp; + + if (!get_int(jobj, "IVupdate", &tmp)) + return false; + + *update = tmp ? true : false; + + return true; +} + +static bool read_token(json_object *jobj, uint8_t token[8]) +{ + json_object *jvalue; + char *str; + + if (!token) + return false; + + if (!json_object_object_get_ex(jobj, "token", &jvalue)) + return false; + + str = (char *)json_object_get_string(jvalue); + if (!str2hex(str, strlen(str), token, 8)) + return false; + + return true; +} + +static bool read_device_key(json_object *jobj, uint8_t key_buf[16]) +{ + json_object *jvalue; + char *str; + + if (!key_buf) + return false; + + if (!json_object_object_get_ex(jobj, "deviceKey", &jvalue)) + return false; + + str = (char *)json_object_get_string(jvalue); + if (!str2hex(str, strlen(str), key_buf, 16)) + return false; + + return true; +} + +static bool read_app_keys(json_object *jobj, struct mesh_config_node *node) +{ + json_object *jarray; + int len; + int i; + + if (!json_object_object_get_ex(jobj, "appKeys", &jarray)) + return true; + + if (json_object_get_type(jarray) != json_type_array) + return false; + + /* Allow empty AppKey array */ + len = json_object_array_length(jarray); + if (!len) + return true; + + node->appkeys = l_queue_new(); + + for (i = 0; i < len; ++i) { + json_object *jtemp, *jvalue; + char *str; + struct mesh_config_appkey *appkey; + + appkey = l_new(struct mesh_config_appkey, 1); + + jtemp = json_object_array_get_idx(jarray, i); + + if (!get_key_index(jtemp, "index", &appkey->app_idx)) + goto fail; + + if (!get_key_index(jtemp, "boundNetKey", &appkey->net_idx)) + goto fail; + + if (!json_object_object_get_ex(jtemp, "key", &jvalue)) + goto fail; + + str = (char *)json_object_get_string(jvalue); + if (!str2hex(str, strlen(str), appkey->new_key, 16)) + goto fail; + + if (json_object_object_get_ex(jtemp, "oldKey", &jvalue)) + str = (char *)json_object_get_string(jvalue); + + if (!str2hex(str, strlen(str), appkey->key, 16)) + goto fail; + + l_queue_push_tail(node->appkeys, appkey); + } + + return true; +fail: + l_queue_destroy(node->appkeys, l_free); + node->appkeys = NULL; + + return false; +} + +static bool read_net_keys(json_object *jobj, struct mesh_config_node *node) +{ + json_object *jarray; + int len; + int i; + + /* At least one NetKey must be present for a provisioned node */ + if (!json_object_object_get_ex(jobj, "netKeys", &jarray)) + return false; + + if (json_object_get_type(jarray) != json_type_array) + return false; + + len = json_object_array_length(jarray); + if (!len) + return false; + + node->netkeys = l_queue_new(); + + for (i = 0; i < len; ++i) { + json_object *jtemp, *jvalue; + char *str; + struct mesh_config_netkey *netkey; + + netkey = l_new(struct mesh_config_netkey, 1); + + jtemp = json_object_array_get_idx(jarray, i); + + if (!get_key_index(jtemp, "index", &netkey->idx)) + goto fail; + + if (!json_object_object_get_ex(jtemp, "key", &jvalue)) + goto fail; + + str = (char *)json_object_get_string(jvalue); + if (!str2hex(str, strlen(str), netkey->new_key, 16)) + goto fail; + + if (!json_object_object_get_ex(jtemp, "keyRefresh", &jvalue)) + netkey->phase = KEY_REFRESH_PHASE_NONE; + else + netkey->phase = (uint8_t) json_object_get_int(jvalue); + + if (netkey->phase > KEY_REFRESH_PHASE_TWO) + goto fail; + + if (json_object_object_get_ex(jtemp, "oldKey", &jvalue)) { + if (netkey->phase == KEY_REFRESH_PHASE_NONE) + goto fail; + + str = (char *)json_object_get_string(jvalue); + } + + if (!str2hex(str, strlen(str), netkey->key, 16)) + goto fail; + + l_queue_push_tail(node->netkeys, netkey); + } + + return true; +fail: + l_queue_destroy(node->netkeys, l_free); + node->netkeys = NULL; + + return false; +} + +bool mesh_config_net_key_add(struct mesh_config *cfg, uint16_t idx, + const uint8_t key[16]) +{ + json_object *jnode, *jarray = NULL, *jentry = NULL, *jstring; + char buf[5]; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + l_debug("netKey %4.4x", idx); + json_object_object_get_ex(jnode, "netKeys", &jarray); + if (jarray) + jentry = get_key_object(jarray, idx); + + /* Do not allow direct overwrite */ + if (jentry) + return false; + + jentry = json_object_new_object(); + if (!jentry) + return false; + + snprintf(buf, 5, "%4.4x", idx); + jstring = json_object_new_string(buf); + if (!jstring) + goto fail; + + json_object_object_add(jentry, "index", jstring); + + if (!add_key_value(jentry, "key", key)) + goto fail; + + json_object_object_add(jentry, "keyRefresh", + json_object_new_int(KEY_REFRESH_PHASE_NONE)); + + if (!jarray) { + jarray = json_object_new_array(); + if (!jarray) + goto fail; + json_object_object_add(jnode, "netKeys", jarray); + } + + json_object_array_add(jarray, jentry); + + return save_config(jnode, cfg->node_dir_path); + +fail: + if (jentry) + json_object_put(jentry); + + return false; +} + +bool mesh_config_net_key_update(struct mesh_config *cfg, uint16_t idx, + const uint8_t key[16]) +{ + json_object *jnode, *jarray, *jentry, *jstring; + const char *str; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + if (!json_object_object_get_ex(jnode, "netKeys", &jarray)) + return false; + + jentry = get_key_object(jarray, idx); + /* Net key must be already recorded */ + if (!jentry) + return false; + + if (!json_object_object_get_ex(jentry, "key", &jstring)) + return false; + + str = json_object_get_string(jstring); + jstring = json_object_new_string(str); + json_object_object_add(jentry, "oldKey", jstring); + json_object_object_del(jentry, "key"); + + if (!add_key_value(jentry, "key", key)) + return false; + + json_object_object_add(jentry, "keyRefresh", + json_object_new_int(KEY_REFRESH_PHASE_ONE)); + + return save_config(jnode, cfg->node_dir_path); +} + +bool mesh_config_net_key_del(struct mesh_config *cfg, uint16_t idx) +{ + json_object *jnode, *jarray, *jarray_new; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + /* TODO: Decide if we treat this as an error: no network keys??? */ + if (!json_object_object_get_ex(jnode, "netKeys", &jarray)) + return true; + + /* Check if matching entry exists */ + if (!get_key_object(jarray, idx)) + return true; + + if (json_object_array_length(jarray) == 1) { + json_object_object_del(jnode, "netKeys"); + /* TODO: Do we raise an error here? */ + l_warn("Removing the last network key! Zero keys left."); + return save_config(jnode, cfg->node_dir_path); + } + + /* + * There is no easy way to delete a value from json array. + * Create a new copy without specified element and + * then remove old array. + */ + jarray_new = jarray_key_del(jarray, idx); + if (!jarray_new) + return false; + + json_object_object_del(jnode, "netKeys"); + json_object_object_add(jnode, "netKeys", jarray_new); + + return save_config(jnode, cfg->node_dir_path); +} + +bool mesh_config_write_device_key(struct mesh_config *cfg, uint8_t *key) +{ + if (!cfg || !add_key_value(cfg->jnode, "deviceKey", key)) + return false; + + return save_config(cfg->jnode, cfg->node_dir_path); +} + +bool mesh_config_write_token(struct mesh_config *cfg, uint8_t *token) +{ + if (!cfg || !add_u64_value(cfg->jnode, "token", token)) + return false; + + return save_config(cfg->jnode, cfg->node_dir_path); +} + +bool mesh_config_app_key_add(struct mesh_config *cfg, uint16_t net_idx, + uint16_t app_idx, const uint8_t key[16]) +{ + json_object *jnode, *jarray = NULL, *jentry = NULL, *jstring = NULL; + char buf[5]; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + json_object_object_get_ex(jnode, "appKeys", &jarray); + if (jarray) + jentry = get_key_object(jarray, app_idx); + + /* Do not allow direct overrwrite */ + if (jentry) + return false; + + jentry = json_object_new_object(); + if (!jentry) + return false; + + snprintf(buf, 5, "%4.4x", app_idx); + jstring = json_object_new_string(buf); + if (!jstring) + goto fail; + + json_object_object_add(jentry, "index", jstring); + + snprintf(buf, 5, "%4.4x", net_idx); + jstring = json_object_new_string(buf); + if (!jstring) + goto fail; + + json_object_object_add(jentry, "boundNetKey", jstring); + + if (!add_key_value(jentry, "key", key)) + goto fail; + + if (!jarray) { + jarray = json_object_new_array(); + if (!jarray) + goto fail; + json_object_object_add(jnode, "appKeys", jarray); + } + + json_object_array_add(jarray, jentry); + + return save_config(jnode, cfg->node_dir_path); + +fail: + + if (jentry) + json_object_put(jentry); + + return false; +} + +bool mesh_config_app_key_update(struct mesh_config *cfg, uint16_t app_idx, + const uint8_t key[16]) +{ + json_object *jnode, *jarray, *jentry = NULL, *jstring = NULL; + const char *str; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + if (!json_object_object_get_ex(jnode, "appKeys", &jarray)) + return false; + + /* The key entry should exist if the key is updated */ + jentry = get_key_object(jarray, app_idx); + if (!jentry) + return false; + + if (!json_object_object_get_ex(jentry, "key", &jstring)) + return false; + + str = json_object_get_string(jstring); + jstring = json_object_new_string(str); + json_object_object_add(jentry, "oldKey", jstring); + + json_object_object_del(jentry, "key"); + + /* TODO: "Rewind" if add_key_value fails */ + if (!add_key_value(jentry, "key", key)) + return false; + + return save_config(jnode, cfg->node_dir_path); +} + +bool mesh_config_app_key_del(struct mesh_config *cfg, uint16_t net_idx, + uint16_t idx) +{ + json_object *jnode, *jarray, *jarray_new; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + if (!json_object_object_get_ex(jnode, "appKeys", &jarray)) + return true; + + /* Check if matching entry exists */ + if (!get_key_object(jarray, idx)) + return true; + + if (json_object_array_length(jarray) == 1) { + json_object_object_del(jnode, "appKeys"); + return true; + } + + /* + * There is no easy way to delete a value from json array. + * Create a new copy without specified element and + * then remove old array. + */ + jarray_new = jarray_key_del(jarray, idx); + if (!jarray_new) + return false; + + json_object_object_del(jnode, "appKeys"); + json_object_object_add(jnode, "appKeys", jarray_new); + + return save_config(jnode, cfg->node_dir_path); +} + +bool mesh_config_model_binding_add(struct mesh_config *cfg, uint16_t ele_addr, + bool vendor, uint32_t mod_id, + uint16_t app_idx) +{ + json_object *jnode, *jmodel, *jstring, *jarray = NULL; + int ele_idx; + char buf[5]; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + ele_idx = get_element_index(jnode, ele_addr); + if (ele_idx < 0) + return false; + + jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); + if (!jmodel) + return false; + + snprintf(buf, 5, "%4.4x", app_idx); + + json_object_object_get_ex(jmodel, "bind", &jarray); + if (jarray && jarray_has_string(jarray, buf, 4)) + return true; + + jstring = json_object_new_string(buf); + if (!jstring) + return false; + + if (!jarray) { + jarray = json_object_new_array(); + if (!jarray) { + json_object_put(jstring); + return false; + } + json_object_object_add(jmodel, "bind", jarray); + } + + json_object_array_add(jarray, jstring); + + return save_config(jnode, cfg->node_dir_path); +} + +bool mesh_config_model_binding_del(struct mesh_config *cfg, uint16_t ele_addr, + bool vendor, uint32_t mod_id, + uint16_t app_idx) +{ + json_object *jnode, *jmodel, *jarray, *jarray_new; + int ele_idx; + char buf[5]; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + ele_idx = get_element_index(jnode, ele_addr); + if (ele_idx < 0) + return false; + + jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); + if (!jmodel) + return false; + + if (!json_object_object_get_ex(jmodel, "bind", &jarray)) + return true; + + snprintf(buf, 5, "%4.4x", app_idx); + + if (!jarray_has_string(jarray, buf, 4)) + return true; + + if (json_object_array_length(jarray) == 1) { + json_object_object_del(jmodel, "bind"); + return true; + } + + /* + * There is no easy way to delete a value from json array. + * Create a new copy without specified element and + * then remove old array. + */ + jarray_new = jarray_string_del(jarray, buf, 4); + if (!jarray_new) + return false; + + json_object_object_del(jmodel, "bind"); + json_object_object_add(jmodel, "bind", jarray_new); + + return save_config(jnode, cfg->node_dir_path); +} + +static void free_model(void *data) +{ + struct mesh_config_model *mod = data; + + l_free(mod->bindings); + l_free(mod->subs); + l_free(mod->pub); + l_free(mod); +} + +static void free_element(void *data) +{ + struct mesh_config_element *ele = data; + + l_queue_destroy(ele->models, free_model); + l_free(ele); +} + +static bool parse_bindings(json_object *jarray, struct mesh_config_model *mod) +{ + int cnt; + int i; + + cnt = json_object_array_length(jarray); + if (cnt > 0xffff) + return false; + + mod->num_bindings = cnt; + + /* Allow empty bindings list */ + if (!cnt) + return true; + + mod->bindings = l_new(uint16_t, cnt); + + for (i = 0; i < cnt; ++i) { + uint16_t idx; + const char *str; + json_object *jvalue; + + jvalue = json_object_array_get_idx(jarray, i); + if (!jvalue) + return false; + + str = json_object_get_string(jvalue); + + if (sscanf(str, "%04hx", &idx) != 1) + return false; + + if (!CHECK_KEY_IDX_RANGE(idx)) + return false; + + mod->bindings[i] = (uint16_t) idx; + } + + return true; +} + +static struct mesh_config_pub *parse_model_publication(json_object *jpub) +{ + json_object *jvalue; + struct mesh_config_pub *pub; + int len, value; + char *str; + + if (!json_object_object_get_ex(jpub, "address", &jvalue)) + return NULL; + + str = (char *)json_object_get_string(jvalue); + len = strlen(str); + pub = l_new(struct mesh_config_pub, 1); + + switch (len) { + case 4: + if (sscanf(str, "%04hx", &pub->addr) != 1) + goto fail; + break; + case 32: + if (!str2hex(str, len, pub->virt_addr, 16)) + goto fail; + pub->virt = true; + break; + default: + goto fail; + } + + if (!get_key_index(jpub, "index", &pub->idx)) + goto fail; + + if (!get_int(jpub, "ttl", &value)) + goto fail; + pub->ttl = (uint8_t) value; + + if (!get_int(jpub, "period", &value)) + goto fail; + pub->period = value; + + if (!get_int(jpub, "credentials", &value)) + goto fail; + pub->credential = (uint8_t) value; + + if (!json_object_object_get_ex(jpub, "retransmit", &jvalue)) + goto fail; + + if (!get_int(jvalue, "count", &value)) + goto fail; + pub->count = (uint8_t) value; + + if (!get_int(jvalue, "interval", &value)) + goto fail; + pub->interval = (uint8_t) value; + + return pub; + +fail: + l_free(pub); + return NULL; +} + +static bool parse_model_subscriptions(json_object *jsubs, + struct mesh_config_model *mod) +{ + struct mesh_config_sub *subs; + int i, cnt; + + if (json_object_get_type(jsubs) != json_type_array) + return NULL; + + cnt = json_object_array_length(jsubs); + /* Allow empty array */ + if (!cnt) + return true; + + subs = l_new(struct mesh_config_sub, cnt); + + for (i = 0; i < cnt; ++i) { + char *str; + int len; + json_object *jvalue; + + jvalue = json_object_array_get_idx(jsubs, i); + if (!jvalue) + return false; + + str = (char *)json_object_get_string(jvalue); + len = strlen(str); + + switch (len) { + case 4: + if (sscanf(str, "%04hx", &subs[i].src.addr) != 1) + goto fail; + break; + case 32: + if (!str2hex(str, len, subs[i].src.virt_addr, 16)) + goto fail; + subs[i].virt = true; + break; + default: + goto fail; + } + } + + mod->num_subs = cnt; + mod->subs = subs; + + return true; +fail: + l_free(subs); + return false; +} + +static bool parse_models(json_object *jmodels, struct mesh_config_element *ele) +{ + int i, num_models; + + num_models = json_object_array_length(jmodels); + if (!num_models) + return true; + + for (i = 0; i < num_models; ++i) { + json_object *jmodel, *jarray, *jvalue; + struct mesh_config_model *mod; + uint32_t id; + int len; + char *str; + + jmodel = json_object_array_get_idx(jmodels, i); + if (!jmodel) + goto fail; + + mod = l_new(struct mesh_config_model, 1); + + if (!json_object_object_get_ex(jmodel, "modelId", &jvalue)) + goto fail; + + str = (char *)json_object_get_string(jvalue); + + len = strlen(str); + + if (len != 4 && len != 8) + goto fail; + + if (len == 4) { + if (sscanf(str, "%04x", &id) != 1) + goto fail; + + id |= VENDOR_ID_MASK; + } else if (len == 8) { + if (sscanf(str, "%08x", &id) != 1) + goto fail; + } else + goto fail; + + mod->id = id; + + if (len == 8) + mod->vendor = true; + + if (json_object_object_get_ex(jmodel, "bind", &jarray)) { + if (json_object_get_type(jarray) != json_type_array || + !parse_bindings(jarray, mod)) + goto fail; + } + + if (json_object_object_get_ex(jmodel, "publish", &jvalue)) { + mod->pub = parse_model_publication(jvalue); + if (!mod->pub) + goto fail; + } + + if (json_object_object_get_ex(jmodel, "subscribe", &jarray)) { + if (!parse_model_subscriptions(jarray, mod)) + goto fail; + } + + l_queue_push_tail(ele->models, mod); + } + + return true; + +fail: + l_queue_destroy(ele->models, free_model); + return false; +} + +static bool parse_elements(json_object *jelems, struct mesh_config_node *node) +{ + int i, num_ele; + + if (json_object_get_type(jelems) != json_type_array) + return false; + + num_ele = json_object_array_length(jelems); + if (!num_ele) + /* Allow "empty" nodes */ + return true; + + node->elements = l_queue_new(); + + for (i = 0; i < num_ele; ++i) { + json_object *jelement; + json_object *jmodels; + json_object *jvalue; + struct mesh_config_element *ele; + int index; + char *str; + + jelement = json_object_array_get_idx(jelems, i); + if (!jelement) + goto fail; + + if (!get_int(jelement, "elementIndex", &index) || + index > num_ele) + goto fail; + + ele = l_new(struct mesh_config_element, 1); + ele->index = index; + ele->models = l_queue_new(); + + if (!json_object_object_get_ex(jelement, "location", &jvalue)) + goto fail; + + str = (char *)json_object_get_string(jvalue); + if (sscanf(str, "%04hx", &(ele->location)) != 1) + goto fail; + + if (json_object_object_get_ex(jelement, "models", &jmodels)) { + if (json_object_get_type(jmodels) != json_type_array || + !parse_models(jmodels, ele)) + goto fail; + } + + l_queue_push_tail(node->elements, ele); + } + + return true; + +fail: + l_queue_destroy(node->elements, free_element); + node->elements = NULL; + + return false; +} + +static int get_mode(json_object *jvalue) +{ + const char *str; + + str = json_object_get_string(jvalue); + if (!str) + return 0xffffffff; + + if (!strncasecmp(str, "disabled", strlen("disabled"))) + return MESH_MODE_DISABLED; + + if (!strncasecmp(str, "enabled", strlen("enabled"))) + return MESH_MODE_ENABLED; + + if (!strncasecmp(str, "unsupported", strlen("unsupported"))) + return MESH_MODE_UNSUPPORTED; + + return 0xffffffff; +} + +static void parse_features(json_object *jconfig, struct mesh_config_node *node) +{ + json_object *jvalue, *jrelay; + int mode, count; + uint16_t interval; + + if (json_object_object_get_ex(jconfig, "proxy", &jvalue)) { + mode = get_mode(jvalue); + if (mode <= MESH_MODE_UNSUPPORTED) + node->modes.proxy = mode; + } + + if (json_object_object_get_ex(jconfig, "friend", &jvalue)) { + mode = get_mode(jvalue); + if (mode <= MESH_MODE_UNSUPPORTED) + node->modes.friend = mode; + } + + if (json_object_object_get_ex(jconfig, "lowPower", &jvalue)) { + mode = get_mode(jvalue); + if (mode <= MESH_MODE_UNSUPPORTED) + node->modes.lpn = mode; + } + + if (json_object_object_get_ex(jconfig, "beacon", &jvalue)) { + mode = get_mode(jvalue); + if (mode <= MESH_MODE_UNSUPPORTED) + node->modes.beacon = mode; + } + + if (!json_object_object_get_ex(jconfig, "relay", &jrelay)) + return; + + if (json_object_object_get_ex(jrelay, "mode", &jvalue)) { + mode = get_mode(jvalue); + if (mode <= MESH_MODE_UNSUPPORTED) + node->modes.relay.state = mode; + else + return; + } else + return; + + if (!json_object_object_get_ex(jrelay, "count", &jvalue)) + return; + + /* TODO: check range */ + count = json_object_get_int(jvalue); + node->modes.relay.cnt = count; + + if (!json_object_object_get_ex(jrelay, "interval", &jvalue)) + return; + + /* TODO: check range */ + interval = json_object_get_int(jvalue); + node->modes.relay.interval = interval; +} + +static bool parse_composition(json_object *jcomp, struct mesh_config_node *node) +{ + json_object *jvalue; + char *str; + + /* All the fields in node composition are mandatory */ + if (!json_object_object_get_ex(jcomp, "cid", &jvalue)) + return false; + + str = (char *)json_object_get_string(jvalue); + if (sscanf(str, "%04hx", &node->cid) != 1) + return false; + + if (!json_object_object_get_ex(jcomp, "pid", &jvalue)) + return false; + + str = (char *)json_object_get_string(jvalue); + if (sscanf(str, "%04hx", &node->pid) != 1) + return false; + + if (!json_object_object_get_ex(jcomp, "vid", &jvalue)) + return false; + + str = (char *)json_object_get_string(jvalue); + if (sscanf(str, "%04hx", &node->vid) != 1) + return false; + + if (!json_object_object_get_ex(jcomp, "crpl", &jvalue)) + return false; + + str = (char *)json_object_get_string(jvalue); + if (sscanf(str, "%04hx", &node->crpl) != 1) + return false; + + return true; +} + +static bool read_net_transmit(json_object *jobj, struct mesh_config_node *node) +{ + json_object *jretransmit, *jvalue; + uint16_t interval; + uint8_t cnt; + + if (!json_object_object_get_ex(jobj, "retransmit", &jretransmit)) + return true; + + if (!json_object_object_get_ex(jretransmit, "count", &jvalue)) + return false; + + /* TODO: add range checking */ + cnt = (uint8_t) json_object_get_int(jvalue); + + if (!json_object_object_get_ex(jretransmit, "interval", &jvalue)) + return false; + + interval = (uint16_t) json_object_get_int(jvalue); + + node->net_transmit = l_new(struct mesh_config_transmit, 1); + node->net_transmit->count = cnt; + node->net_transmit->interval = interval; + + return true; +} + +static bool read_node(json_object *jnode, struct mesh_config_node *node) +{ + json_object *jvalue; + + if (!read_iv_index(jnode, &node->iv_index, &node->iv_update)) { + l_info("Failed to read IV index"); + return false; + } + + if (!read_token(jnode, node->token)) { + l_info("Failed to read node token"); + return false; + } + + if (!read_device_key(jnode, node->dev_key)) { + l_info("Failed to read node device key"); + return false; + } + + if (!parse_composition(jnode, node)) { + l_info("Failed to parse local node composition"); + return false; + } + + parse_features(jnode, node); + + if (!read_unicast_address(jnode, &node->unicast)) { + l_info("Failed to parse unicast address"); + return false; + } + + if (!read_default_ttl(jnode, &node->ttl)) { + l_info("Failed to parse default ttl"); + return false; + } + + if (!read_seq_number(jnode, &node->seq_number)) { + l_info("Failed to parse sequence number"); + return false; + } + + /* Check for required "elements" property */ + if (!json_object_object_get_ex(jnode, "elements", &jvalue)) + return false; + + if (!read_net_transmit(jnode, node)) { + l_info("Failed to read node net transmit parameters"); + return false; + } + + if (!read_net_keys(jnode, node)) { + l_info("Failed to read net keys"); + return false; + } + + if (!read_app_keys(jnode, node)) { + l_info("Failed to read app keys"); + return false; + } + + if (!parse_elements(jvalue, node)) { + l_info("Failed to parse elements"); + return false; + } + + return true; +} + +static bool write_uint16_hex(json_object *jobj, const char *desc, + uint16_t value) +{ + json_object *jstring; + char buf[5]; + + snprintf(buf, 5, "%4.4x", value); + jstring = json_object_new_string(buf); + if (!jstring) + return false; + + json_object_object_add(jobj, desc, jstring); + return true; +} + +static bool write_uint32_hex(json_object *jobj, const char *desc, uint32_t val) +{ + json_object *jstring; + char buf[9]; + + snprintf(buf, 9, "%8.8x", val); + jstring = json_object_new_string(buf); + if (!jstring) + return false; + + json_object_object_add(jobj, desc, jstring); + return true; +} + +static bool write_int(json_object *jobj, const char *keyword, int val) +{ + json_object *jvalue; + + json_object_object_del(jobj, keyword); + + jvalue = json_object_new_int(val); + if (!jvalue) + return false; + + json_object_object_add(jobj, keyword, jvalue); + return true; +} + +static const char *mode_to_string(int mode) +{ + switch (mode) { + case MESH_MODE_DISABLED: + return "disabled"; + case MESH_MODE_ENABLED: + return "enabled"; + default: + return "unsupported"; + } +} + +static bool write_mode(json_object *jobj, const char *keyword, int value) +{ + json_object *jstring; + + jstring = json_object_new_string(mode_to_string(value)); + + if (!jstring) + return false; + + json_object_object_add(jobj, keyword, jstring); + + return true; +} + +bool mesh_config_write_mode(struct mesh_config *cfg, const char *keyword, + int value) +{ + if (!cfg || !write_mode(cfg->jnode, keyword, value)) + return false; + + return save_config(cfg->jnode, cfg->node_dir_path); +} + +static bool write_relay_mode(json_object *jobj, uint8_t mode, + uint8_t count, uint16_t interval) +{ + json_object *jrelay; + + json_object_object_del(jobj, "relay"); + + jrelay = json_object_new_object(); + if (!jrelay) + return false; + + if (!write_mode(jrelay, "mode", mode)) + goto fail; + + if (!write_int(jrelay, "count", count)) + goto fail; + + if (!write_int(jrelay, "interval", interval)) + goto fail; + + json_object_object_add(jobj, "relay", jrelay); + + return true; +fail: + json_object_put(jrelay); + return false; +} + +bool mesh_config_write_unicast(struct mesh_config *cfg, uint16_t unicast) +{ + if (!cfg || !write_uint16_hex(cfg->jnode, "unicastAddress", unicast)) + return false; + + return save_config(cfg->jnode, cfg->node_dir_path); +} + +bool mesh_config_write_relay_mode(struct mesh_config *cfg, uint8_t mode, + uint8_t count, uint16_t interval) +{ + + if (!cfg || !write_relay_mode(cfg->jnode, mode, count, interval)) + return false; + + return save_config(cfg->jnode, cfg->node_dir_path); +} + +bool mesh_config_write_net_transmit(struct mesh_config *cfg, uint8_t cnt, + uint16_t interval) +{ + json_object *jnode, *jretransmit; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + jretransmit = json_object_new_object(); + if (!jretransmit) + return false; + + if (!write_int(jretransmit, "count", cnt)) + goto fail; + + if (!write_int(jretransmit, "interval", interval)) + goto fail; + + json_object_object_del(jnode, "retransmit"); + json_object_object_add(jnode, "retransmit", jretransmit); + + return save_config(cfg->jnode, cfg->node_dir_path); + +fail: + json_object_put(jretransmit); + return false; + +} + +bool mesh_config_write_iv_index(struct mesh_config *cfg, uint32_t idx, + bool update) +{ + json_object *jnode; + int tmp = update ? 1 : 0; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + if (!write_int(jnode, "IVindex", idx)) + return false; + + if (!write_int(jnode, "IVupdate", tmp)) + return false; + + return save_config(jnode, cfg->node_dir_path); +} + +static void add_model(void *a, void *b) +{ + struct mesh_config_model *mod = a; + json_object *jmodels = b, *jmodel; + + jmodel = json_object_new_object(); + if (!jmodel) + return; + + if (!mod->vendor) + write_uint16_hex(jmodel, "modelId", + (uint16_t) mod->id); + else + write_uint32_hex(jmodel, "modelId", mod->id); + + json_object_array_add(jmodels, jmodel); +} + +/* Add unprovisioned node (local) */ +static struct mesh_config *create_config(const char *cfg_path, + const uint8_t uuid[16], + struct mesh_config_node *node) +{ + struct mesh_config_modes *modes = &node->modes; + const struct l_queue_entry *entry; + json_object *jnode, *jelems; + struct mesh_config *cfg; + + if (!cfg_path || !node) + return NULL; + + jnode = json_object_new_object(); + + /* CID, PID, VID, crpl */ + if (!write_uint16_hex(jnode, "cid", node->cid)) + return NULL; + + if (!write_uint16_hex(jnode, "pid", node->pid)) + return NULL; + + if (!write_uint16_hex(jnode, "vid", node->vid)) + return NULL; + + if (!write_uint16_hex(jnode, "crpl", node->crpl)) + return NULL; + + /* Features: relay, LPN, friend, proxy*/ + if (!write_relay_mode(jnode, modes->relay.state, + modes->relay.cnt, modes->relay.interval)) + return NULL; + + if (!write_mode(jnode, "lowPower", modes->lpn)) + return NULL; + + if (!write_mode(jnode, "friend", modes->friend)) + return NULL; + + if (!write_mode(jnode, "proxy", modes->proxy)) + return NULL; + + /* Beaconing state */ + if (!write_mode(jnode, "beacon", modes->beacon)) + return NULL; + + /* Sequence number */ + json_object_object_add(jnode, "sequenceNumber", + json_object_new_int(node->seq_number)); + + /* Default TTL */ + json_object_object_add(jnode, "defaultTTL", + json_object_new_int(node->ttl)); + + /* Elements */ + jelems = json_object_new_array(); + if (!jelems) + return NULL; + + entry = l_queue_get_entries(node->elements); + + for (; entry; entry = entry->next) { + struct mesh_config_element *ele = entry->data; + json_object *jelement, *jmodels; + + jelement = json_object_new_object(); + + if (!jelement) { + json_object_put(jelems); + return NULL; + } + + write_int(jelement, "elementIndex", ele->index); + write_uint16_hex(jelement, "location", ele->location); + json_object_array_add(jelems, jelement); + + /* Models */ + if (l_queue_isempty(ele->models)) + continue; + + jmodels = json_object_new_array(); + if (!jmodels) { + json_object_put(jelems); + return NULL; + } + + json_object_object_add(jelement, "models", jmodels); + l_queue_foreach(ele->models, add_model, jmodels); + } + + json_object_object_add(jnode, "elements", jelems); + + cfg = l_new(struct mesh_config, 1); + + cfg->jnode = jnode; + memcpy(cfg->uuid, uuid, 16); + cfg->node_dir_path = l_strdup(cfg_path); + cfg->write_seq = node->seq_number; + gettimeofday(&cfg->write_time, NULL); + + return cfg; +} + +struct mesh_config *mesh_config_create(const char *cfgdir_name, + const uint8_t uuid[16], struct mesh_config_node *db_node) +{ + char uuid_buf[33]; + char name_buf[PATH_MAX]; + struct mesh_config *cfg; + size_t max_len = strlen(cfgnode_name) + strlen(bak_ext); + + if (!hex2str((uint8_t *) uuid, 16, uuid_buf, sizeof(uuid_buf))) + return NULL; + + snprintf(name_buf, PATH_MAX, "%s/%s", cfgdir_name, uuid_buf); + + if (strlen(name_buf) + max_len >= PATH_MAX) + return NULL; + + /* Create a new directory and node.json file */ + if (mkdir(name_buf, 0755) != 0) + return NULL; + + snprintf(name_buf, PATH_MAX, "%s/%s%s", cfgdir_name, uuid_buf, + cfgnode_name); + l_debug("New node config %s", name_buf); + + cfg = create_config(name_buf, uuid, db_node); + if (!cfg) + return NULL; + + if (!mesh_config_save(cfg, true, NULL, NULL)) { + mesh_config_release(cfg); + return NULL; + } + + return cfg; +} + +static void finish_key_refresh(json_object *jobj, uint16_t net_idx) +{ + json_object *jarray; + int i, len; + + /* Clean up all the bound appkeys */ + if (!json_object_object_get_ex(jobj, "appKeys", &jarray)) + return; + + len = json_object_array_length(jarray); + + for (i = 0; i < len; ++i) { + json_object *jentry; + uint16_t idx; + + jentry = json_object_array_get_idx(jarray, i); + + if (!get_key_index(jentry, "boundNetKey", &idx)) + continue; + + if (idx != net_idx) + continue; + + json_object_object_del(jentry, "oldKey"); + + if (!get_key_index(jentry, "index", &idx)) + continue; + } + +} + +bool mesh_config_net_key_set_phase(struct mesh_config *cfg, uint16_t idx, + uint8_t phase) +{ + json_object *jnode, *jarray, *jentry = NULL; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + if (json_object_object_get_ex(jnode, "netKeys", &jarray)) + jentry = get_key_object(jarray, idx); + + if (!jentry) + return false; + + json_object_object_del(jentry, "keyRefresh"); + json_object_object_add(jentry, "keyRefresh", + json_object_new_int(phase)); + + if (phase == KEY_REFRESH_PHASE_NONE) { + json_object_object_del(jentry, "oldKey"); + finish_key_refresh(jnode, idx); + } + + return save_config(jnode, cfg->node_dir_path); +} + +bool mesh_config_model_pub_add(struct mesh_config *cfg, uint16_t ele_addr, + uint32_t mod_id, bool vendor, + struct mesh_config_pub *pub) +{ + json_object *jnode, *jmodel, *jpub, *jretransmit; + bool res; + int ele_idx; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + ele_idx = get_element_index(jnode, ele_addr); + if (ele_idx < 0) + return false; + + jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); + if (!jmodel) + return false; + + json_object_object_del(jmodel, "publish"); + + jpub = json_object_new_object(); + if (!jpub) + return false; + + if (pub->virt) + res = add_key_value(jpub, "address", pub->virt_addr); + else + res = write_uint16_hex(jpub, "address", pub->addr); + + if (!res) + goto fail; + + if (!write_uint16_hex(jpub, "index", pub->idx)) + goto fail; + + if (!write_int(jpub, "ttl", pub->ttl)) + goto fail; + + if (!write_int(jpub, "period", pub->period)) + goto fail; + + if (!write_int(jpub, "credentials", + pub->credential ? 1 : 0)) + goto fail; + + jretransmit = json_object_new_object(); + if (!jretransmit) + goto fail; + + if (!write_int(jretransmit, "count", pub->count)) + goto fail; + + if (!write_int(jretransmit, "interval", pub->interval)) + goto fail; + + json_object_object_add(jpub, "retransmit", jretransmit); + json_object_object_add(jmodel, "publish", jpub); + + return save_config(jnode, cfg->node_dir_path); + +fail: + json_object_put(jpub); + return false; +} + +static bool delete_model_property(json_object *jnode, uint16_t ele_addr, + uint32_t mod_id, bool vendor, const char *keyword) +{ + json_object *jmodel; + int ele_idx; + + ele_idx = get_element_index(jnode, ele_addr); + if (ele_idx < 0) + return false; + + jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); + if (!jmodel) + return false; + + json_object_object_del(jmodel, keyword); + + return true; +} + +bool mesh_config_model_pub_del(struct mesh_config *cfg, uint16_t addr, + uint32_t mod_id, bool vendor) +{ + if (!cfg || !delete_model_property(cfg->jnode, addr, mod_id, vendor, + "publish")) + return false; + + return save_config(cfg->jnode, cfg->node_dir_path); +} + +bool mesh_config_model_sub_add(struct mesh_config *cfg, uint16_t ele_addr, + uint32_t mod_id, bool vendor, + struct mesh_config_sub *sub) +{ + json_object *jnode, *jmodel, *jstring, *jarray = NULL; + int ele_idx, len; + char buf[33]; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + ele_idx = get_element_index(jnode, ele_addr); + if (ele_idx < 0) + return false; + + jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); + if (!jmodel) + return false; + + if (!sub->virt) { + snprintf(buf, 5, "%4.4x", sub->src.addr); + len = 4; + } else { + hex2str(sub->src.virt_addr, 16, buf, 33); + len = 32; + } + + json_object_object_get_ex(jmodel, "subscribe", &jarray); + if (jarray && jarray_has_string(jarray, buf, len)) + return true; + + jstring = json_object_new_string(buf); + if (!jstring) + return false; + + if (!jarray) { + jarray = json_object_new_array(); + if (!jarray) { + json_object_put(jstring); + return false; + } + json_object_object_add(jmodel, "subscribe", jarray); + } + + json_object_array_add(jarray, jstring); + + return save_config(jnode, cfg->node_dir_path); +} + +bool mesh_config_model_sub_del(struct mesh_config *cfg, uint16_t ele_addr, + uint32_t mod_id, bool vendor, + struct mesh_config_sub *sub) +{ + json_object *jnode, *jmodel, *jarray, *jarray_new; + char buf[33]; + int len, ele_idx; + + if (!cfg) + return false; + + jnode = cfg->jnode; + + ele_idx = get_element_index(jnode, ele_addr); + if (ele_idx < 0) + return false; + + jmodel = get_element_model(jnode, ele_idx, mod_id, vendor); + if (!jmodel) + return false; + + if (!json_object_object_get_ex(jmodel, "subscribe", &jarray)) + return true; + + if (!sub->virt) { + snprintf(buf, 5, "%4.4x", sub->src.addr); + len = 4; + } else { + hex2str(sub->src.virt_addr, 16, buf, 33); + len = 32; + } + + if (!jarray_has_string(jarray, buf, len)) + return true; + + if (json_object_array_length(jarray) == 1) { + json_object_object_del(jmodel, "subscribe"); + return true; + } + + /* + * There is no easy way to delete a value from a json array. + * Create a new copy without specified element and + * then remove old array. + */ + jarray_new = jarray_string_del(jarray, buf, len); + if (!jarray_new) + return false; + + json_object_object_del(jmodel, "subscribe"); + json_object_object_add(jmodel, "subscribe", jarray_new); + + return save_config(jnode, cfg->node_dir_path); +} + +bool mesh_config_model_sub_del_all(struct mesh_config *cfg, uint16_t addr, + uint32_t mod_id, bool vendor) +{ + if (!cfg || !delete_model_property(cfg->jnode, addr, mod_id, vendor, + "subscribe")) + return false; + + return save_config(cfg->jnode, cfg->node_dir_path); +} + +bool mesh_config_write_seq_number(struct mesh_config *cfg, uint32_t seq, + bool cache) +{ + int value; + uint32_t cached = 0; + + if (!cfg) + return false; + + if (!cache) { + if (!write_int(cfg->jnode, "sequenceNumber", seq)) + return false; + + return mesh_config_save(cfg, true, NULL, NULL); + } + + /* If resetting seq to Zero, make sure cached value reset as well */ + if (seq && get_int(cfg->jnode, "sequenceNumber", &value)) + cached = (uint32_t)value; + + /* + * When sequence number approaches value stored on disk, calculate + * average time between sequence number updates, then overcommit the + * sequence number by MIN_SEQ_CACHE_TIME seconds worth of traffic or + * MIN_SEQ_CACHE_VALUE (whichever is greater) to avoid frequent writes + * to disk and to protect against crashes. + * + * The real value will be saved when daemon shuts down properly. + */ + if (seq + MIN_SEQ_CACHE_TRIGGER >= cached) { + struct timeval now; + struct timeval elapsed; + uint64_t elapsed_ms; + + gettimeofday(&now, NULL); + timersub(&now, &cfg->write_time, &elapsed); + elapsed_ms = elapsed.tv_sec * 1000 + elapsed.tv_usec / 1000; + + cached = seq + (seq - cfg->write_seq) * + 1000 * MIN_SEQ_CACHE_TIME / elapsed_ms; + + if (cached < seq + MIN_SEQ_CACHE_VALUE) + cached = seq + MIN_SEQ_CACHE_VALUE; + + l_debug("Seq Cache: %d -> %d", seq, cached); + + cfg->write_seq = seq; + + if (!write_int(cfg->jnode, "sequenceNumber", cached)) + return false; + + return mesh_config_save(cfg, false, NULL, NULL); + } + + return true; +} + +bool mesh_config_write_ttl(struct mesh_config *cfg, uint8_t ttl) +{ + if (!cfg || !write_int(cfg->jnode, "defaultTTL", ttl)) + return false; + + return save_config(cfg->jnode, cfg->node_dir_path); +} + +static bool load_node(const char *fname, const uint8_t uuid[16], + mesh_config_node_func_t cb, void *user_data) +{ + int fd; + char *str; + struct stat st; + ssize_t sz; + bool result = false; + json_object *jnode; + struct mesh_config_node node; + + if (!cb) { + l_info("Node read callback is required"); + return false; + } + + l_info("Loading configuration from %s", fname); + + fd = open(fname, O_RDONLY); + if (fd < 0) + return false; + + if (fstat(fd, &st) == -1) { + close(fd); + return false; + } + + str = (char *) l_new(char, st.st_size + 1); + if (!str) { + close(fd); + return false; + } + + sz = read(fd, str, st.st_size); + if (sz != st.st_size) { + l_error("Failed to read configuration file %s", fname); + goto done; + } + + jnode = json_tokener_parse(str); + if (!jnode) + goto done; + + memset(&node, 0, sizeof(node)); + result = read_node(jnode, &node); + + if (result) { + struct mesh_config *cfg = l_new(struct mesh_config, 1); + + cfg->jnode = jnode; + memcpy(cfg->uuid, uuid, 16); + cfg->node_dir_path = l_strdup(fname); + cfg->write_seq = node.seq_number; + gettimeofday(&cfg->write_time, NULL); + + result = cb(&node, uuid, cfg, user_data); + + if (!result) { + l_free(cfg->node_dir_path); + l_free(cfg); + } + } + + /* Done with the node: free resources */ + l_free(node.net_transmit); + l_queue_destroy(node.netkeys, l_free); + l_queue_destroy(node.appkeys, l_free); + + if (!result) + json_object_put(jnode); + +done: + close(fd); + if (str) + l_free(str); + + return result; +} + +void mesh_config_release(struct mesh_config *cfg) +{ + if (!cfg) + return; + + l_free(cfg->node_dir_path); + json_object_put(cfg->jnode); + l_free(cfg); +} + +static void idle_save_config(void *user_data) +{ + struct write_info *info = user_data; + char *fname_tmp, *fname_bak, *fname_cfg; + bool result = false; + + fname_cfg = info->cfg->node_dir_path; + fname_tmp = l_strdup_printf("%s%s", fname_cfg, tmp_ext); + fname_bak = l_strdup_printf("%s%s", fname_cfg, bak_ext); + remove(fname_tmp); + + result = save_config(info->cfg->jnode, fname_tmp); + + if (result) { + remove(fname_bak); + rename(fname_cfg, fname_bak); + rename(fname_tmp, fname_cfg); + } + + remove(fname_tmp); + + l_free(fname_tmp); + l_free(fname_bak); + + gettimeofday(&info->cfg->write_time, NULL); + + if (info->cb) + info->cb(info->user_data, result); + + l_free(info); + +} + +bool mesh_config_save(struct mesh_config *cfg, bool no_wait, + mesh_config_status_func_t cb, void *user_data) +{ + struct write_info *info; + + if (!cfg) + return false; + + info = l_new(struct write_info, 1); + info->cfg = cfg; + info->cb = cb; + info->user_data = user_data; + + if (no_wait) + idle_save_config(info); + else + l_idle_oneshot(idle_save_config, info, NULL); + + return true; +} + +bool mesh_config_load_nodes(const char *cfgdir_name, mesh_config_node_func_t cb, + void *user_data) +{ + DIR *cfgdir; + struct dirent *entry; + size_t path_len = strlen(cfgdir_name) + strlen(cfgnode_name) + + strlen(bak_ext); + + create_dir(cfgdir_name); + cfgdir = opendir(cfgdir_name); + if (!cfgdir) { + l_error("Failed to open mesh node storage directory: %s", + cfgdir_name); + return false; + } + + while ((entry = readdir(cfgdir)) != NULL) { + char *dirname, *fname, *bak; + uint8_t uuid[16]; + size_t node_len; + + if (entry->d_type != DT_DIR) + continue; + + /* Check path length */ + node_len = strlen(entry->d_name); + + if (path_len + node_len + 1 >= PATH_MAX) + continue; + + if (!str2hex(entry->d_name, node_len, uuid, sizeof(uuid))) + continue; + + dirname = l_strdup_printf("%s/%s", cfgdir_name, entry->d_name); + fname = l_strdup_printf("%s%s", dirname, cfgnode_name); + + if (!load_node(fname, uuid, cb, user_data)) { + + /* Fall-back to Backup version */ + bak = l_strdup_printf("%s%s", fname, bak_ext); + + if (load_node(bak, uuid, cb, user_data)) { + remove(fname); + rename(bak, fname); + } + + l_free(bak); + } + + l_free(fname); + l_free(dirname); + } + + closedir(cfgdir); + + return true; +} + +static int del_fobject(const char *fpath, const struct stat *sb, int typeflag, + struct FTW *ftwbuf) +{ + switch (typeflag) { + case FTW_DP: + rmdir(fpath); + l_debug("RMDIR %s", fpath); + break; + + case FTW_SL: + default: + remove(fpath); + l_debug("RM %s", fpath); + break; + } + return 0; +} + +void mesh_config_destroy(struct mesh_config *cfg) +{ + char *node_dir, *node_name; + char uuid[33]; + + if (!cfg) + return; + + node_dir = dirname(cfg->node_dir_path); + l_debug("Delete node config %s", node_dir); + + if (!hex2str(cfg->uuid, 16, uuid, sizeof(uuid))) + return; + + node_name = basename(node_dir); + + /* Make sure path name of node follows expected guidelines */ + if (strcmp(node_name, uuid)) + return; + + nftw(node_dir, del_fobject, 5, FTW_DEPTH | FTW_PHYS); + + /* Release node config object */ + mesh_config_release(cfg); +} diff --git a/mesh/mesh-config.h b/mesh/mesh-config.h new file mode 100644 index 0000000..a5b12bb --- /dev/null +++ b/mesh/mesh-config.h @@ -0,0 +1,174 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +struct mesh_config; + +struct mesh_config_sub { + bool virt; + union { + uint16_t addr; + uint8_t virt_addr[16]; + } src; +}; + +struct mesh_config_pub { + bool virt; + uint32_t period; + uint16_t addr; + uint16_t idx; + uint8_t ttl; + uint8_t credential; + uint8_t count; + uint8_t interval; + uint8_t virt_addr[16]; +}; + +struct mesh_config_model { + struct mesh_config_sub *subs; + struct mesh_config_pub *pub; + uint16_t *bindings; + uint32_t id; + bool vendor; + uint32_t num_bindings; + uint32_t num_subs; +}; + +struct mesh_config_element { + struct l_queue *models; + uint16_t location; + uint8_t index; +}; + +struct mesh_config_modes { + struct { + uint16_t interval; + uint8_t cnt; + uint8_t state; + } relay; + uint8_t lpn; + uint8_t friend; + uint8_t proxy; + uint8_t beacon; +}; + +struct mesh_config_netkey { + uint16_t idx; + uint8_t key[16]; + uint8_t new_key[16]; + uint8_t phase; +}; + +struct mesh_config_appkey { + uint16_t net_idx; + uint16_t app_idx; + uint8_t key[16]; + uint8_t new_key[16]; +}; + +struct mesh_config_transmit { + uint16_t interval; + uint8_t count; +}; + +struct mesh_config_node { + struct l_queue *elements; + struct l_queue *netkeys; + struct l_queue *appkeys; + uint32_t seq_number; + uint32_t iv_index; + bool iv_update; + uint16_t cid; + uint16_t pid; + uint16_t vid; + uint16_t crpl; + uint16_t unicast; + struct mesh_config_transmit *net_transmit; + struct mesh_config_modes modes; + uint8_t ttl; + uint8_t dev_key[16]; + uint8_t token[8]; +}; + +typedef void (*mesh_config_status_func_t)(void *user_data, bool result); +typedef bool (*mesh_config_node_func_t)(struct mesh_config_node *node, + const uint8_t uuid[16], + struct mesh_config *cfg, + void *user_data); + +bool mesh_config_load_nodes(const char *cfgdir_name, mesh_config_node_func_t cb, + void *user_data); +void mesh_config_release(struct mesh_config *cfg); +void mesh_config_destroy(struct mesh_config *cfg); +bool mesh_config_save(struct mesh_config *cfg, bool no_wait, + mesh_config_status_func_t cb, void *user_data); +struct mesh_config *mesh_config_create(const char *cfgdir_name, + const uint8_t uuid[16], + struct mesh_config_node *node); + +bool mesh_config_write_net_transmit(struct mesh_config *cfg, uint8_t cnt, + uint16_t interval); +bool mesh_config_write_device_key(struct mesh_config *cfg, uint8_t *key); +bool mesh_config_write_token(struct mesh_config *cfg, uint8_t *token); +bool mesh_config_write_network_key(struct mesh_config *cfg, uint16_t idx, + uint8_t *key, uint8_t *new_key, int phase); +bool mesh_config_write_app_key(struct mesh_config *cfg, uint16_t net_idx, + uint16_t app_idx, uint8_t *key, uint8_t *new_key); +bool mesh_config_write_seq_number(struct mesh_config *cfg, uint32_t seq, + bool cache); +bool mesh_config_write_unicast(struct mesh_config *cfg, uint16_t unicast); +bool mesh_config_write_relay_mode(struct mesh_config *cfg, uint8_t mode, + uint8_t count, uint16_t interval); +bool mesh_config_write_ttl(struct mesh_config *cfg, uint8_t ttl); +bool mesh_config_write_mode(struct mesh_config *cfg, const char *keyword, + int value); +bool mesh_config_model_binding_add(struct mesh_config *cfg, uint16_t ele_addr, + bool vendor, uint32_t mod_id, + uint16_t app_idx); +bool mesh_config_model_binding_del(struct mesh_config *cfg, uint16_t ele_addr, + bool vendor, uint32_t mod_id, + uint16_t app_idx); +bool mesh_config_model_pub_add(struct mesh_config *cfg, uint16_t ele_addr, + uint32_t mod_id, bool vendor, + struct mesh_config_pub *pub); +bool mesh_config_model_pub_del(struct mesh_config *cfg, uint16_t ele_addr, + uint32_t mod_id, bool vendor); +bool mesh_config_model_sub_add(struct mesh_config *cfg, uint16_t ele_addr, + uint32_t mod_id, bool vendor, + struct mesh_config_sub *sub); +bool mesh_config_model_sub_del(struct mesh_config *cfg, uint16_t ele_addr, + uint32_t mod_id, bool vendor, + struct mesh_config_sub *sub); +bool mesh_config_model_sub_del_all(struct mesh_config *cfg, uint16_t ele_addr, + uint32_t mod_id, bool vendor); +bool mesh_config_app_key_add(struct mesh_config *cfg, uint16_t net_idx, + uint16_t app_idx, const uint8_t key[16]); +bool mesh_config_app_key_update(struct mesh_config *cfg, uint16_t app_idx, + const uint8_t key[16]); +bool mesh_config_app_key_del(struct mesh_config *cfg, uint16_t net_idx, + uint16_t idx); +bool mesh_config_net_key_add(struct mesh_config *cfg, uint16_t net_idx, + const uint8_t key[16]); +bool mesh_config_net_key_update(struct mesh_config *cfg, uint16_t idx, + const uint8_t key[16]); +bool mesh_config_net_key_del(struct mesh_config *cfg, uint16_t net_idx); +bool mesh_config_net_key_set_phase(struct mesh_config *cfg, uint16_t idx, + uint8_t phase); +bool mesh_config_write_address(struct mesh_config *cfg, uint16_t address); +bool mesh_config_write_iv_index(struct mesh_config *cfg, uint32_t idx, + bool update); diff --git a/mesh/mesh-defs.h b/mesh/mesh-defs.h new file mode 100644 index 0000000..8f28fc8 --- /dev/null +++ b/mesh/mesh-defs.h @@ -0,0 +1,123 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * + */ + +#define MESH_AD_TYPE_PROVISION 0x29 +#define MESH_AD_TYPE_NETWORK 0x2A +#define MESH_AD_TYPE_BEACON 0x2B + +#define FEATURE_RELAY 1 +#define FEATURE_PROXY 2 +#define FEATURE_FRIEND 4 +#define FEATURE_LPN 8 + +#define MESH_MODE_DISABLED 0 +#define MESH_MODE_ENABLED 1 +#define MESH_MODE_UNSUPPORTED 2 + +#define KEY_REFRESH_PHASE_NONE 0x00 +#define KEY_REFRESH_PHASE_ONE 0x01 +#define KEY_REFRESH_PHASE_TWO 0x02 +#define KEY_REFRESH_PHASE_THREE 0x03 + +#define DEFAULT_TTL 0xff +#define TTL_MASK 0x7f + +/* Supported algorithms for provisioning */ +#define ALG_FIPS_256_ECC 0x0001 + +/* Input OOB action bit flags */ +#define OOB_IN_PUSH 0x0001 +#define OOB_IN_TWIST 0x0002 +#define OOB_IN_NUMBER 0x0004 +#define OOB_IN_ALPHA 0x0008 + +/* Output OOB action bit flags */ +#define OOB_OUT_BLINK 0x0001 +#define OOB_OUT_BEEP 0x0002 +#define OOB_OUT_VIBRATE 0x0004 +#define OOB_OUT_NUMBER 0x0008 +#define OOB_OUT_ALPHA 0x0010 + +/* Status codes */ +#define MESH_STATUS_SUCCESS 0x00 +#define MESH_STATUS_INVALID_ADDRESS 0x01 +#define MESH_STATUS_INVALID_MODEL 0x02 +#define MESH_STATUS_INVALID_APPKEY 0x03 +#define MESH_STATUS_INVALID_NETKEY 0x04 +#define MESH_STATUS_INSUFF_RESOURCES 0x05 +#define MESH_STATUS_IDX_ALREADY_STORED 0x06 +#define MESH_STATUS_INVALID_PUB_PARAM 0x07 +#define MESH_STATUS_NOT_SUB_MOD 0x08 +#define MESH_STATUS_STORAGE_FAIL 0x09 +#define MESH_STATUS_FEATURE_NO_SUPPORT 0x0a +#define MESH_STATUS_CANNOT_UPDATE 0x0b +#define MESH_STATUS_CANNOT_REMOVE 0x0c +#define MESH_STATUS_CANNOT_BIND 0x0d +#define MESH_STATUS_UNABLE_CHANGE_STATE 0x0e +#define MESH_STATUS_CANNOT_SET 0x0f +#define MESH_STATUS_UNSPECIFIED_ERROR 0x10 +#define MESH_STATUS_INVALID_BINDING 0x11 + +#define UNASSIGNED_ADDRESS 0x0000 +#define PROXIES_ADDRESS 0xfffc +#define FRIENDS_ADDRESS 0xfffd +#define RELAYS_ADDRESS 0xfffe +#define ALL_NODES_ADDRESS 0xffff +#define VIRTUAL_ADDRESS_LOW 0x8000 +#define VIRTUAL_ADDRESS_HIGH 0xbfff +#define GROUP_ADDRESS_LOW 0xc000 +#define GROUP_ADDRESS_HIGH 0xfeff +#define FIXED_GROUP_LOW 0xff00 +#define FIXED_GROUP_HIGH 0xffff + +#define NODE_IDENTITY_STOPPED 0x00 +#define NODE_IDENTITY_RUNNING 0x01 +#define NODE_IDENTITY_NOT_SUPPORTED 0x02 + +#define PRIMARY_ELE_IDX 0x00 + +#define PRIMARY_NET_IDX 0x0000 +#define MAX_KEY_IDX 0x0fff +#define MAX_MODEL_COUNT 0xff +#define MAX_ELE_COUNT 0xff + +#define MAX_MSG_LEN 380 + +#define VENDOR_ID_MASK 0xffff0000 + +#define NET_IDX_INVALID 0xffff +#define NET_NID_INVALID 0xff + +#define APP_IDX_MASK 0x0fff +#define APP_IDX_DEV_REMOTE 0x6fff +#define APP_IDX_DEV_LOCAL 0x7fff + +#define IS_UNASSIGNED(x) ((x) == UNASSIGNED_ADDRESS) +#define IS_UNICAST(x) (((x) > UNASSIGNED_ADDRESS) && \ + ((x) < VIRTUAL_ADDRESS_LOW)) +#define IS_UNICAST_RANGE(x, c) (IS_UNICAST(x) && IS_UNICAST(x + c - 1)) +#define IS_VIRTUAL(x) (((x) >= VIRTUAL_ADDRESS_LOW) && \ + ((x) <= VIRTUAL_ADDRESS_HIGH)) +#define IS_GROUP(x) ((((x) >= GROUP_ADDRESS_LOW) && \ + ((x) < FIXED_GROUP_HIGH)) || \ + ((x) == ALL_NODES_ADDRESS)) + +#define IS_FIXED_GROUP_ADDRESS(x) ((x) >= PROXIES_ADDRESS) +#define IS_ALL_NODES(x) ((x) == ALL_NODES_ADDRESS) diff --git a/mesh/mesh-io-api.h b/mesh/mesh-io-api.h new file mode 100644 index 0000000..4cdf1f8 --- /dev/null +++ b/mesh/mesh-io-api.h @@ -0,0 +1,57 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +struct mesh_io_private; + +typedef bool (*mesh_io_init_t)(struct mesh_io *io, void *opts); +typedef bool (*mesh_io_destroy_t)(struct mesh_io *io); +typedef bool (*mesh_io_caps_t)(struct mesh_io *io, struct mesh_io_caps *caps); +typedef bool (*mesh_io_send_t)(struct mesh_io *io, + struct mesh_io_send_info *info, + const uint8_t *data, uint16_t len); +typedef bool (*mesh_io_register_t)(struct mesh_io *io, uint8_t filter_id, + mesh_io_recv_func_t cb, void *user_data); +typedef bool (*mesh_io_deregister_t)(struct mesh_io *io, uint8_t filter_id); +typedef bool (*mesh_io_filter_set_t)(struct mesh_io *io, + uint8_t filter_id, const uint8_t *data, uint8_t len, + mesh_io_status_func_t callback, void *user_data); +typedef bool (*mesh_io_tx_cancel_t)(struct mesh_io *io, const uint8_t *pattern, + uint8_t len); + +struct mesh_io_api { + mesh_io_init_t init; + mesh_io_destroy_t destroy; + mesh_io_caps_t caps; + mesh_io_send_t send; + mesh_io_register_t reg; + mesh_io_deregister_t dereg; + mesh_io_filter_set_t set; + mesh_io_tx_cancel_t cancel; +}; + +struct mesh_io { + enum mesh_io_type type; + const struct mesh_io_api *api; + struct mesh_io_private *pvt; +}; + +struct mesh_io_table { + enum mesh_io_type type; + const struct mesh_io_api *api; +}; diff --git a/mesh/mesh-io-generic.c b/mesh/mesh-io-generic.c new file mode 100644 index 0000000..42bf64a --- /dev/null +++ b/mesh/mesh-io-generic.c @@ -0,0 +1,805 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "monitor/bt.h" +#include "src/shared/hci.h" +#include "lib/bluetooth.h" +#include "lib/mgmt.h" + +#include "mesh/mesh-mgmt.h" +#include "mesh/mesh-io.h" +#include "mesh/mesh-io-api.h" +#include "mesh/mesh-io-generic.h" + +struct mesh_io_private { + uint16_t index; + struct bt_hci *hci; + struct l_timeout *tx_timeout; + struct l_queue *rx_regs; + struct l_queue *tx_pkts; + uint8_t filters[4]; + bool sending; + struct tx_pkt *tx; + uint16_t interval; +}; + +struct pvt_rx_reg { + uint8_t filter_id; + mesh_io_recv_func_t cb; + void *user_data; +}; + +struct process_data { + struct mesh_io_private *pvt; + const uint8_t *data; + uint8_t len; + struct mesh_io_recv_info info; +}; + +struct tx_pkt { + struct mesh_io_send_info info; + bool delete; + uint8_t len; + uint8_t pkt[30]; +}; + +struct tx_pattern { + const uint8_t *data; + uint8_t len; +}; + +static uint32_t get_instant(void) +{ + struct timeval tm; + uint32_t instant; + + gettimeofday(&tm, NULL); + instant = tm.tv_sec * 1000; + instant += tm.tv_usec / 1000; + + return instant; +} + +static uint32_t instant_remaining_ms(uint32_t instant) +{ + instant -= get_instant(); + return instant; +} + +static void process_rx_callbacks(void *v_rx, void *v_reg) +{ + struct pvt_rx_reg *rx_reg = v_rx; + struct process_data *rx = v_reg; + uint8_t ad_type; + + ad_type = rx->pvt->filters[rx_reg->filter_id - 1]; + + if (rx->data[0] == ad_type && rx_reg->cb) + rx_reg->cb(rx_reg->user_data, &rx->info, rx->data, rx->len); +} + +static void process_rx(struct mesh_io_private *pvt, int8_t rssi, + uint32_t instant, + const uint8_t *data, uint8_t len) +{ + struct process_data rx = { + .pvt = pvt, + .data = data, + .len = len, + .info.instant = instant, + .info.chan = 7, + .info.rssi = rssi, + }; + + l_queue_foreach(pvt->rx_regs, process_rx_callbacks, &rx); +} + +static void event_adv_report(struct mesh_io *io, const void *buf, uint8_t size) +{ + const struct bt_hci_evt_le_adv_report *evt = buf; + const uint8_t *adv; + uint32_t instant; + uint8_t adv_len; + uint16_t len = 0; + int8_t rssi; + + if (evt->event_type != 0x03) + return; + + instant = get_instant(); + adv = evt->data; + adv_len = evt->data_len; + + /* rssi is just beyond last byte of data */ + rssi = (int8_t) adv[adv_len]; + + while (len < adv_len - 1) { + uint8_t field_len = adv[0]; + + /* Check for the end of advertising data */ + if (field_len == 0) + break; + + len += field_len + 1; + + /* Do not continue data parsing if got incorrect length */ + if (len > adv_len) + break; + + /* TODO: Create an Instant to use */ + process_rx(io->pvt, rssi, instant, adv + 1, adv[0]); + + adv += field_len + 1; + } +} + +static void event_callback(const void *buf, uint8_t size, void *user_data) +{ + uint8_t event = l_get_u8(buf); + struct mesh_io *io = user_data; + + switch (event) { + case BT_HCI_EVT_LE_ADV_REPORT: + event_adv_report(io, buf + 1, size - 1); + break; + + default: + l_info("Other Meta Evt - %d", event); + } +} + +static void local_commands_callback(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_read_local_commands *rsp = data; + + if (rsp->status) + l_error("Failed to read local commands"); +} + +static void local_features_callback(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_read_local_features *rsp = data; + + if (rsp->status) + l_error("Failed to read local features"); +} + +static void hci_generic_callback(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = l_get_u8(data); + + if (status) + l_error("Failed to initialize HCI"); +} + +static void configure_hci(struct mesh_io_private *io) +{ + struct bt_hci_cmd_le_set_scan_parameters cmd; + struct bt_hci_cmd_set_event_mask cmd_sem; + struct bt_hci_cmd_le_set_event_mask cmd_slem; + + /* Set scan parameters */ + cmd.type = 0x00; /* Passive Scanning. No scanning PDUs shall be sent */ + cmd.interval = 0x0030; /* Scan Interval = N * 0.625ms */ + cmd.window = 0x0030; /* Scan Window = N * 0.625ms */ + cmd.own_addr_type = 0x00; /* Public Device Address */ + /* Accept all advertising packets except directed advertising packets + * not addressed to this device (default). + */ + cmd.filter_policy = 0x00; + + /* Set event mask + * + * Mask: 0x2000800002008890 + * Disconnection Complete + * Encryption Change + * Read Remote Version Information Complete + * Hardware Error + * Data Buffer Overflow + * Encryption Key Refresh Complete + * LE Meta + */ + cmd_sem.mask[0] = 0x90; + cmd_sem.mask[1] = 0x88; + cmd_sem.mask[2] = 0x00; + cmd_sem.mask[3] = 0x02; + cmd_sem.mask[4] = 0x00; + cmd_sem.mask[5] = 0x80; + cmd_sem.mask[6] = 0x00; + cmd_sem.mask[7] = 0x20; + + /* Set LE event mask + * + * Mask: 0x000000000000087f + * LE Connection Complete + * LE Advertising Report + * LE Connection Update Complete + * LE Read Remote Used Features Complete + * LE Long Term Key Request + * LE Remote Connection Parameter Request + * LE Data Length Change + * LE PHY Update Complete + */ + cmd_slem.mask[0] = 0x7f; + cmd_slem.mask[1] = 0x08; + cmd_slem.mask[2] = 0x00; + cmd_slem.mask[3] = 0x00; + cmd_slem.mask[4] = 0x00; + cmd_slem.mask[5] = 0x00; + cmd_slem.mask[6] = 0x00; + cmd_slem.mask[7] = 0x00; + + /* TODO: Move to suitable place. Set suitable masks */ + /* Reset Command */ + bt_hci_send(io->hci, BT_HCI_CMD_RESET, NULL, 0, hci_generic_callback, + NULL, NULL); + + /* Read local supported commands */ + bt_hci_send(io->hci, BT_HCI_CMD_READ_LOCAL_COMMANDS, NULL, 0, + local_commands_callback, NULL, NULL); + + /* Read local supported features */ + bt_hci_send(io->hci, BT_HCI_CMD_READ_LOCAL_FEATURES, NULL, 0, + local_features_callback, NULL, NULL); + + /* Set event mask */ + bt_hci_send(io->hci, BT_HCI_CMD_SET_EVENT_MASK, &cmd_sem, + sizeof(cmd_sem), hci_generic_callback, NULL, NULL); + + /* Set LE event mask */ + bt_hci_send(io->hci, BT_HCI_CMD_LE_SET_EVENT_MASK, &cmd_slem, + sizeof(cmd_slem), hci_generic_callback, NULL, NULL); + + /* Scan Params */ + bt_hci_send(io->hci, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, &cmd, + sizeof(cmd), hci_generic_callback, NULL, NULL); +} + +static bool hci_init(struct mesh_io *io) +{ + io->pvt->hci = bt_hci_new_user_channel(io->pvt->index); + if (!io->pvt->hci) { + l_error("Failed to start mesh io (hci %u): %s", io->pvt->index, + strerror(errno)); + return false; + } + + configure_hci(io->pvt); + + bt_hci_register(io->pvt->hci, BT_HCI_EVT_LE_META_EVENT, + event_callback, io, NULL); + + l_debug("Started mesh on hci %u", io->pvt->index); + return true; +} + +static void read_info(int index, void *user_data) +{ + struct mesh_io *io = user_data; + + if (io->pvt->index != MGMT_INDEX_NONE && + index != io->pvt->index) { + l_debug("Ignore index %d", index); + return; + } + + io->pvt->index = index; + hci_init(io); +} + +static bool dev_init(struct mesh_io *io, void *opts) +{ + if (!io || io->pvt) + return false; + + io->pvt = l_new(struct mesh_io_private, 1); + io->pvt->index = *(int *)opts; + + io->pvt->rx_regs = l_queue_new(); + io->pvt->tx_pkts = l_queue_new(); + + if (io->pvt->index == MGMT_INDEX_NONE) + return mesh_mgmt_list(read_info, io); + else + return hci_init(io); +} + +static bool dev_destroy(struct mesh_io *io) +{ + struct mesh_io_private *pvt = io->pvt; + + if (!pvt) + return true; + + bt_hci_unref(pvt->hci); + l_timeout_remove(pvt->tx_timeout); + l_queue_destroy(pvt->rx_regs, l_free); + l_queue_destroy(pvt->tx_pkts, l_free); + l_free(pvt); + io->pvt = NULL; + + return true; +} + +static bool dev_caps(struct mesh_io *io, struct mesh_io_caps *caps) +{ + struct mesh_io_private *pvt = io->pvt; + + if (!pvt || !caps) + return false; + + caps->max_num_filters = sizeof(pvt->filters); + caps->window_accuracy = 50; + + return true; +} + +static void send_cancel_done(const void *buf, uint8_t size, + void *user_data) +{ + struct mesh_io_private *pvt = user_data; + struct bt_hci_cmd_le_set_random_address cmd; + + if (!pvt) + return; + + pvt->sending = false; + + /* At end of any burst of ADVs, change random address */ + l_getrandom(cmd.addr, 6); + cmd.addr[5] |= 0xc0; + bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, + &cmd, sizeof(cmd), NULL, NULL, NULL); +} + +static void send_cancel(struct mesh_io_private *pvt) +{ + struct bt_hci_cmd_le_set_adv_enable cmd; + + if (!pvt) + return; + + if (!pvt->sending) { + send_cancel_done(NULL, 0, pvt); + return; + } + + cmd.enable = 0x00; /* Disable advertising */ + bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_ADV_ENABLE, + &cmd, sizeof(cmd), + send_cancel_done, pvt, NULL); +} + +static void set_send_adv_enable(const void *buf, uint8_t size, + void *user_data) +{ + struct mesh_io_private *pvt = user_data; + struct bt_hci_cmd_le_set_adv_enable cmd; + + if (!pvt) + return; + + pvt->sending = true; + cmd.enable = 0x01; /* Enable advertising */ + bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_ADV_ENABLE, + &cmd, sizeof(cmd), NULL, NULL, NULL); +} + +static void set_send_adv_data(const void *buf, uint8_t size, + void *user_data) +{ + struct mesh_io_private *pvt = user_data; + struct tx_pkt *tx; + struct bt_hci_cmd_le_set_adv_data cmd; + + if (!pvt || !pvt->tx) + return; + + tx = pvt->tx; + if (tx->len >= sizeof(cmd.data)) + goto done; + + memset(&cmd, 0, sizeof(cmd)); + + cmd.len = tx->len + 1; + cmd.data[0] = tx->len; + memcpy(cmd.data + 1, tx->pkt, tx->len); + + bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_ADV_DATA, + &cmd, sizeof(cmd), + set_send_adv_enable, pvt, NULL); +done: + if (tx->delete) + l_free(tx); + + pvt->tx = NULL; +} + +static void set_send_adv_params(const void *buf, uint8_t size, + void *user_data) +{ + struct mesh_io_private *pvt = user_data; + struct bt_hci_cmd_le_set_adv_parameters cmd; + uint16_t hci_interval; + + if (!pvt) + return; + + hci_interval = (pvt->interval * 16) / 10; + cmd.min_interval = L_CPU_TO_LE16(hci_interval); + cmd.max_interval = L_CPU_TO_LE16(hci_interval); + cmd.type = 0x03; /* ADV_NONCONN_IND */ + cmd.own_addr_type = 0x01; /* ADDR_TYPE_RANDOM */ + cmd.direct_addr_type = 0x00; + memset(cmd.direct_addr, 0, 6); + cmd.channel_map = 0x07; + cmd.filter_policy = 0x03; + + bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_ADV_PARAMETERS, + &cmd, sizeof(cmd), + set_send_adv_data, pvt, NULL); +} + +static void send_pkt(struct mesh_io_private *pvt, struct tx_pkt *tx, + uint16_t interval) +{ + struct bt_hci_cmd_le_set_adv_enable cmd; + + pvt->tx = tx; + pvt->interval = interval; + + if (!pvt->sending) { + set_send_adv_params(NULL, 0, pvt); + return; + } + + cmd.enable = 0x00; /* Disable advertising */ + bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_ADV_ENABLE, + &cmd, sizeof(cmd), + set_send_adv_params, pvt, NULL); +} + +static void tx_timeout(struct l_timeout *timeout, void *user_data) +{ + struct mesh_io_private *pvt = user_data; + struct tx_pkt *tx; + uint16_t ms; + uint8_t count; + + if (!pvt) + return; + + tx = l_queue_pop_head(pvt->tx_pkts); + if (!tx) { + l_timeout_remove(timeout); + pvt->tx_timeout = NULL; + send_cancel(pvt); + return; + } + + if (tx->info.type == MESH_IO_TIMING_TYPE_GENERAL) { + ms = tx->info.u.gen.interval; + count = tx->info.u.gen.cnt; + if (count != MESH_IO_TX_COUNT_UNLIMITED) + tx->info.u.gen.cnt--; + } else { + ms = 25; + count = 1; + } + + tx->delete = !!(count == 1); + + send_pkt(pvt, tx, ms); + + if (count == 1) { + /* send_pkt will delete when done */ + tx = l_queue_peek_head(pvt->tx_pkts); + if (tx && tx->info.type == MESH_IO_TIMING_TYPE_POLL_RSP) { + ms = instant_remaining_ms(tx->info.u.poll_rsp.instant + + tx->info.u.poll_rsp.delay); + } + } else + l_queue_push_tail(pvt->tx_pkts, tx); + + if (timeout) { + pvt->tx_timeout = timeout; + l_timeout_modify_ms(timeout, ms); + } else + pvt->tx_timeout = l_timeout_create_ms(ms, tx_timeout, + pvt, NULL); +} + +static void tx_worker(void *user_data) +{ + struct mesh_io_private *pvt = user_data; + struct tx_pkt *tx; + uint32_t delay; + + tx = l_queue_peek_head(pvt->tx_pkts); + if (!tx) + return; + + switch (tx->info.type) { + case MESH_IO_TIMING_TYPE_GENERAL: + if (tx->info.u.gen.min_delay == tx->info.u.gen.max_delay) + delay = tx->info.u.gen.min_delay; + else { + l_getrandom(&delay, sizeof(delay)); + delay %= tx->info.u.gen.max_delay - + tx->info.u.gen.min_delay; + delay += tx->info.u.gen.min_delay; + } + break; + + case MESH_IO_TIMING_TYPE_POLL: + if (tx->info.u.poll.min_delay == tx->info.u.poll.max_delay) + delay = tx->info.u.poll.min_delay; + else { + l_getrandom(&delay, sizeof(delay)); + delay %= tx->info.u.poll.max_delay - + tx->info.u.poll.min_delay; + delay += tx->info.u.poll.min_delay; + } + break; + + case MESH_IO_TIMING_TYPE_POLL_RSP: + /* Delay until Instant + Delay */ + delay = instant_remaining_ms(tx->info.u.poll_rsp.instant + + tx->info.u.poll_rsp.delay); + if (delay > 255) + delay = 0; + break; + + default: + return; + } + + if (!delay) + tx_timeout(pvt->tx_timeout, pvt); + else if (pvt->tx_timeout) + l_timeout_modify_ms(pvt->tx_timeout, delay); + else + pvt->tx_timeout = l_timeout_create_ms(delay, tx_timeout, + pvt, NULL); +} + +static bool send_tx(struct mesh_io *io, struct mesh_io_send_info *info, + const uint8_t *data, uint16_t len) +{ + struct mesh_io_private *pvt = io->pvt; + struct tx_pkt *tx; + bool sending = false; + + if (!info || !data || !len || len > sizeof(tx->pkt)) + return false; + + tx = l_new(struct tx_pkt, 1); + if (!tx) + return false; + + memcpy(&tx->info, info, sizeof(tx->info)); + memcpy(&tx->pkt, data, len); + tx->len = len; + + if (info->type == MESH_IO_TIMING_TYPE_POLL_RSP) + l_queue_push_head(pvt->tx_pkts, tx); + else { + sending = !l_queue_isempty(pvt->tx_pkts); + l_queue_push_tail(pvt->tx_pkts, tx); + } + + if (!sending) { + l_timeout_remove(pvt->tx_timeout); + pvt->tx_timeout = NULL; + l_idle_oneshot(tx_worker, pvt, NULL); + } + + return true; +} + +static bool find_by_ad_type(const void *a, const void *b) +{ + const struct tx_pkt *tx = a; + uint8_t ad_type = L_PTR_TO_UINT(b); + + return !ad_type || ad_type == tx->pkt[0]; +} + +static bool find_by_pattern(const void *a, const void *b) +{ + const struct tx_pkt *tx = a; + const struct tx_pattern *pattern = b; + + if (tx->len < pattern->len) + return false; + + return (!memcmp(tx->pkt, pattern->data, pattern->len)); +} + +static bool tx_cancel(struct mesh_io *io, const uint8_t *data, uint8_t len) +{ + struct mesh_io_private *pvt = io->pvt; + struct tx_pkt *tx; + + if (!data) + return false; + + if (len == 1) { + do { + tx = l_queue_remove_if(pvt->tx_pkts, find_by_ad_type, + L_UINT_TO_PTR(data[0])); + l_free(tx); + + if (tx == pvt->tx) + pvt->tx = NULL; + + } while (tx); + } else { + struct tx_pattern pattern = { + .data = data, + .len = len + }; + + do { + tx = l_queue_remove_if(pvt->tx_pkts, find_by_pattern, + &pattern); + l_free(tx); + + if (tx == pvt->tx) + pvt->tx = NULL; + + } while (tx); + } + + if (l_queue_isempty(pvt->tx_pkts)) { + send_cancel(pvt); + l_timeout_remove(pvt->tx_timeout); + pvt->tx_timeout = NULL; + } + + return true; +} + +static bool find_by_filter_id(const void *a, const void *b) +{ + const struct pvt_rx_reg *rx_reg = a; + uint8_t filter_id = L_PTR_TO_UINT(b); + + return rx_reg->filter_id == filter_id; +} + +static void set_recv_scan_enable(const void *buf, uint8_t size, + void *user_data) +{ + struct mesh_io_private *pvt = user_data; + struct bt_hci_cmd_le_set_scan_enable cmd; + + cmd.enable = 0x01; /* Enable scanning */ + cmd.filter_dup = 0x00; /* Report duplicates */ + bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, + &cmd, sizeof(cmd), NULL, NULL, NULL); +} + +static bool recv_register(struct mesh_io *io, uint8_t filter_id, + mesh_io_recv_func_t cb, void *user_data) +{ + struct bt_hci_cmd_le_set_scan_parameters cmd; + struct mesh_io_private *pvt = io->pvt; + struct pvt_rx_reg *rx_reg; + bool already_scanning; + + l_info("%s %d", __func__, filter_id); + if (!cb || !filter_id || filter_id > sizeof(pvt->filters)) + return false; + + rx_reg = l_queue_remove_if(pvt->rx_regs, find_by_filter_id, + L_UINT_TO_PTR(filter_id)); + + if (!rx_reg) { + rx_reg = l_new(struct pvt_rx_reg, 1); + if (!rx_reg) + return false; + } + + rx_reg->filter_id = filter_id; + rx_reg->cb = cb; + rx_reg->user_data = user_data; + + already_scanning = !l_queue_isempty(pvt->rx_regs); + + l_queue_push_head(pvt->rx_regs, rx_reg); + + if (!already_scanning) { + cmd.type = 0x00; /* Passive scanning */ + cmd.interval = L_CPU_TO_LE16(0x0010); /* 10 ms */ + cmd.window = L_CPU_TO_LE16(0x0010); /* 10 ms */ + cmd.own_addr_type = 0x01; /* ADDR_TYPE_RANDOM */ + cmd.filter_policy = 0x00; /* Accept all */ + + bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, + &cmd, sizeof(cmd), + set_recv_scan_enable, pvt, NULL); + } + + return true; +} + +static bool recv_deregister(struct mesh_io *io, uint8_t filter_id) +{ + struct bt_hci_cmd_le_set_scan_enable cmd; + struct mesh_io_private *pvt = io->pvt; + + struct pvt_rx_reg *rx_reg; + + rx_reg = l_queue_remove_if(pvt->rx_regs, find_by_filter_id, + L_UINT_TO_PTR(filter_id)); + + if (rx_reg) + l_free(rx_reg); + + if (l_queue_isempty(pvt->rx_regs)) { + cmd.enable = 0x00; /* Disable scanning */ + cmd.filter_dup = 0x00; /* Report duplicates */ + bt_hci_send(pvt->hci, BT_HCI_CMD_LE_SET_SCAN_ENABLE, + &cmd, sizeof(cmd), NULL, NULL, NULL); + + } + + return true; +} + +static bool filter_set(struct mesh_io *io, + uint8_t filter_id, const uint8_t *data, uint8_t len, + mesh_io_status_func_t callback, void *user_data) +{ + struct mesh_io_private *pvt = io->pvt; + + l_info("%s id: %d, --> %2.2x", __func__, filter_id, data[0]); + if (!data || !len || !filter_id || filter_id > sizeof(pvt->filters)) + return false; + + pvt->filters[filter_id - 1] = data[0]; + + /* TODO: Delayed Call to successful status */ + + return true; +} + +const struct mesh_io_api mesh_io_generic = { + .init = dev_init, + .destroy = dev_destroy, + .caps = dev_caps, + .send = send_tx, + .reg = recv_register, + .dereg = recv_deregister, + .set = filter_set, + .cancel = tx_cancel, +}; diff --git a/mesh/mesh-io-generic.h b/mesh/mesh-io-generic.h new file mode 100644 index 0000000..4bf4d5c --- /dev/null +++ b/mesh/mesh-io-generic.h @@ -0,0 +1,20 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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. + * + */ + +extern const struct mesh_io_api mesh_io_generic; diff --git a/mesh/mesh-io.c b/mesh/mesh-io.c new file mode 100644 index 0000000..94a92e8 --- /dev/null +++ b/mesh/mesh-io.c @@ -0,0 +1,188 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "lib/bluetooth.h" + +#include "mesh/mesh-defs.h" +#include "mesh/mesh-io.h" +#include "mesh/mesh-io-api.h" + +/* List of Mesh-IO Type headers */ +#include "mesh/mesh-io-generic.h" + +/* List of Supported Mesh-IO Types */ +static const struct mesh_io_table table[] = { + {MESH_IO_TYPE_GENERIC, &mesh_io_generic} +}; + +static struct l_queue *io_list; + +static bool match_by_io(const void *a, const void *b) +{ + return a == b; +} + +static bool match_by_type(const void *a, const void *b) +{ + const struct mesh_io *io = a; + const enum mesh_io_type type = L_PTR_TO_UINT(b); + + return io->type == type; +} + +struct mesh_io *mesh_io_new(enum mesh_io_type type, void *opts) +{ + const struct mesh_io_api *api = NULL; + struct mesh_io *io; + uint16_t i; + + for (i = 0; i < L_ARRAY_SIZE(table); i++) { + if (table[i].type == type) { + api = table[i].api; + break; + } + } + + io = l_queue_find(io_list, match_by_type, L_UINT_TO_PTR(type)); + + if (!api || !api->init || io) + return NULL; + + io = l_new(struct mesh_io, 1); + + if (!io) + return NULL; + + io->type = type; + + io->api = api; + if (!api->init(io, opts)) + goto fail; + + if (!io_list) + io_list = l_queue_new(); + + if (api->set) { + uint8_t pkt = MESH_AD_TYPE_NETWORK; + uint8_t prv = MESH_AD_TYPE_PROVISION; + uint8_t snb[2] = {MESH_AD_TYPE_BEACON, 0x01}; + uint8_t prvb[2] = {MESH_AD_TYPE_BEACON, 0x00}; + + api->set(io, 1, snb, sizeof(snb), NULL, NULL); + api->set(io, 2, &prv, 1, NULL, NULL); + api->set(io, 3, &pkt, 1, NULL, NULL); + api->set(io, 4, prvb, sizeof(prvb), NULL, NULL); + } + + if (l_queue_push_head(io_list, io)) + return io; + +fail: + if (api->destroy) + api->destroy(io); + + l_free(io); + return NULL; +} + +void mesh_io_destroy(struct mesh_io *io) +{ + io = l_queue_remove_if(io_list, match_by_io, io); + + if (io && io->api && io->api->destroy) + io->api->destroy(io); + + l_free(io); + + if (l_queue_isempty(io_list)) { + l_queue_destroy(io_list, NULL); + io_list = NULL; + } +} + +bool mesh_io_get_caps(struct mesh_io *io, struct mesh_io_caps *caps) +{ + io = l_queue_find(io_list, match_by_io, io); + + if (io && io->api && io->api->caps) + return io->api->caps(io, caps); + + return false; +} + +bool mesh_io_register_recv_cb(struct mesh_io *io, uint8_t filter_id, + mesh_io_recv_func_t cb, void *user_data) +{ + io = l_queue_find(io_list, match_by_io, io); + + if (io && io->api && io->api->reg) + return io->api->reg(io, filter_id, cb, user_data); + + return false; +} + +bool mesh_io_deregister_recv_cb(struct mesh_io *io, uint8_t filter_id) +{ + io = l_queue_find(io_list, match_by_io, io); + + if (io && io->api && io->api->dereg) + return io->api->dereg(io, filter_id); + + return false; +} + +bool mesh_set_filter(struct mesh_io *io, uint8_t filter_id, + const uint8_t *data, uint8_t len, + mesh_io_status_func_t cb, void *user_data) +{ + io = l_queue_find(io_list, match_by_io, io); + + if (io && io->api && io->api->set) + return io->api->set(io, filter_id, data, len, cb, user_data); + + return false; +} + +bool mesh_io_send(struct mesh_io *io, struct mesh_io_send_info *info, + const uint8_t *data, uint16_t len) +{ + io = l_queue_find(io_list, match_by_io, io); + + if (io && io->api && io->api->send) + return io->api->send(io, info, data, len); + + return false; +} + +bool mesh_io_send_cancel(struct mesh_io *io, const uint8_t *pattern, + uint8_t len) +{ + io = l_queue_find(io_list, match_by_io, io); + + if (io && io->api && io->api->cancel) + return io->api->cancel(io, pattern, len); + + return false; +} diff --git a/mesh/mesh-io.h b/mesh/mesh-io.h new file mode 100644 index 0000000..1c10779 --- /dev/null +++ b/mesh/mesh-io.h @@ -0,0 +1,101 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +struct mesh_io; + +#define MESH_IO_FILTER_BEACON 1 +#define MESH_IO_FILTER_PROV 2 +#define MESH_IO_FILTER_NET 3 +#define MESH_IO_FILTER_PROV_BEACON 4 + +#define MESH_IO_TX_COUNT_UNLIMITED 0 + +enum mesh_io_type { + MESH_IO_TYPE_NONE = 0, + MESH_IO_TYPE_GENERIC +}; + +enum mesh_io_timing_type { + MESH_IO_TIMING_TYPE_GENERAL = 1, + MESH_IO_TIMING_TYPE_POLL, + MESH_IO_TIMING_TYPE_POLL_RSP +}; + +struct mesh_io_recv_info { + uint32_t instant; + uint8_t chan; + int8_t rssi; +}; + +struct mesh_io_send_info { + enum mesh_io_timing_type type; + union { + struct { + uint16_t interval; + uint8_t cnt; + uint8_t min_delay; + uint8_t max_delay; + } gen; + + struct { + uint16_t scan_duration; + uint8_t scan_delay; + uint8_t filter_ids[2]; + uint8_t min_delay; + uint8_t max_delay; + } poll; + + struct { + uint32_t instant; + uint8_t delay; + } poll_rsp; + + } u; +}; + +struct mesh_io_caps { + uint8_t max_num_filters; + uint8_t window_accuracy; +}; + +typedef void (*mesh_io_recv_func_t)(void *user_data, + struct mesh_io_recv_info *info, + const uint8_t *data, uint16_t len); + +typedef void (*mesh_io_status_func_t)(void *user_data, int status, + uint8_t filter_id); + +struct mesh_io *mesh_io_new(enum mesh_io_type type, void *opts); +void mesh_io_destroy(struct mesh_io *io); + +bool mesh_io_get_caps(struct mesh_io *io, struct mesh_io_caps *caps); + +bool mesh_io_register_recv_cb(struct mesh_io *io, uint8_t filter_id, + mesh_io_recv_func_t cb, void *user_data); + +bool mesh_io_deregister_recv_cb(struct mesh_io *io, uint8_t filter_id); + +bool mesh_set_filter(struct mesh_io *io, uint8_t filter_id, + const uint8_t *data, uint8_t len, + mesh_io_status_func_t cb, void *user_data); + +bool mesh_io_send(struct mesh_io *io, struct mesh_io_send_info *info, + const uint8_t *data, uint16_t len); +bool mesh_io_send_cancel(struct mesh_io *io, const uint8_t *pattern, + uint8_t len); diff --git a/mesh/mesh-mgmt.c b/mesh/mesh-mgmt.c new file mode 100644 index 0000000..27272d4 --- /dev/null +++ b/mesh/mesh-mgmt.c @@ -0,0 +1,204 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2019 SILVAIR sp. z o.o. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "lib/bluetooth.h" +#include "lib/mgmt.h" +#include "src/shared/mgmt.h" + +#include "ell/queue.h" +#include "ell/log.h" +#include "ell/util.h" + +#include "mesh/mesh-mgmt.h" + +struct read_info_reg { + mesh_mgmt_read_info_func_t cb; + void *user_data; +}; + +struct read_info_req { + int index; + struct mesh_io *io; +}; + +static struct mgmt *mgmt_mesh; +static struct l_queue *controllers; +static struct l_queue *read_info_regs; + +static bool simple_match(const void *a, const void *b) +{ + return a == b; +} + +static void process_read_info_req(void *data, void *user_data) +{ + struct read_info_reg *reg = data; + int index = L_PTR_TO_UINT(user_data); + + reg->cb(index, reg->user_data); +} + +static void read_info_cb(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + int index = L_PTR_TO_UINT(user_data); + const struct mgmt_rp_read_info *rp = param; + uint32_t current_settings, supported_settings; + + l_debug("hci %u status 0x%02x", index, status); + + if (status != MGMT_STATUS_SUCCESS) { + l_error("Failed to read info for hci index %u: %s (0x%02x)", + index, mgmt_errstr(status), status); + return; + } + + if (length < sizeof(*rp)) { + l_error("Read info response too short"); + return; + } + + current_settings = btohl(rp->current_settings); + supported_settings = btohl(rp->supported_settings); + + l_debug("settings: supp %8.8x curr %8.8x", + supported_settings, current_settings); + + if (current_settings & MGMT_SETTING_POWERED) { + l_info("Controller hci %u is in use", index); + return; + } + + if (!(supported_settings & MGMT_SETTING_LE)) { + l_info("Controller hci %u does not support LE", index); + return; + } + + l_queue_foreach(read_info_regs, process_read_info_req, + L_UINT_TO_PTR(index)); +} + +static void index_added(uint16_t index, uint16_t length, const void *param, + void *user_data) +{ + if (l_queue_find(controllers, simple_match, L_UINT_TO_PTR(index))) + return; + + l_queue_push_tail(controllers, L_UINT_TO_PTR(index)); + + if (mgmt_send(mgmt_mesh, MGMT_OP_READ_INFO, index, 0, NULL, + read_info_cb, L_UINT_TO_PTR(index), NULL) != 0) + return; + + l_queue_remove(controllers, L_UINT_TO_PTR(index)); +} + +static void index_removed(uint16_t index, uint16_t length, const void *param, + void *user_data) +{ + l_warn("Hci dev %4.4x removed", index); + l_queue_remove(controllers, L_UINT_TO_PTR(index)); +} + +static void read_index_list_cb(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_index_list *rp = param; + uint16_t num; + int i; + + if (status != MGMT_STATUS_SUCCESS) { + l_error("Failed to read index list: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + if (length < sizeof(*rp)) { + l_error("Read index list response sixe too short"); + return; + } + + num = btohs(rp->num_controllers); + + l_debug("Number of controllers: %u", num); + + if (num * sizeof(uint16_t) + sizeof(*rp) != length) { + l_error("Incorrect packet size for index list response"); + return; + } + + for (i = 0; i < num; i++) { + uint16_t index; + + index = btohs(rp->index[i]); + index_added(index, 0, NULL, user_data); + } +} + +static bool mesh_mgmt_init(void) +{ + if (!controllers) + controllers = l_queue_new(); + + if (!read_info_regs) + read_info_regs = l_queue_new(); + + if (!mgmt_mesh) { + mgmt_mesh = mgmt_new_default(); + + if (!mgmt_mesh) { + l_error("Failed to initialize mesh management"); + return false; + } + + mgmt_register(mgmt_mesh, MGMT_EV_INDEX_ADDED, + MGMT_INDEX_NONE, index_added, NULL, NULL); + mgmt_register(mgmt_mesh, MGMT_EV_INDEX_REMOVED, + MGMT_INDEX_NONE, index_removed, NULL, NULL); + } + + return true; +} + +bool mesh_mgmt_list(mesh_mgmt_read_info_func_t cb, void *user_data) +{ + struct read_info_reg *reg; + + if (!mesh_mgmt_init()) + return false; + + reg = l_new(struct read_info_reg, 1); + reg->cb = cb; + reg->user_data = user_data; + + l_queue_push_tail(read_info_regs, reg); + + /* Use MGMT to find a candidate controller */ + l_debug("send read index_list"); + if (mgmt_send(mgmt_mesh, MGMT_OP_READ_INDEX_LIST, + MGMT_INDEX_NONE, 0, NULL, + read_index_list_cb, NULL, NULL) <= 0) + return false; + + return true; +} diff --git a/mesh/mesh-mgmt.h b/mesh/mesh-mgmt.h new file mode 100644 index 0000000..93ad799 --- /dev/null +++ b/mesh/mesh-mgmt.h @@ -0,0 +1,23 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2019 SILVAIR sp. z o.o. 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. + * + */ +#include + +typedef void (*mesh_mgmt_read_info_func_t)(int index, void *user_data); + +bool mesh_mgmt_list(mesh_mgmt_read_info_func_t cb, void *user_data); diff --git a/mesh/mesh.c b/mesh/mesh.c new file mode 100644 index 0000000..9b2b207 --- /dev/null +++ b/mesh/mesh.c @@ -0,0 +1,759 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include + +#include "mesh/mesh-io.h" +#include "mesh/node.h" +#include "mesh/net.h" +#include "mesh/provision.h" +#include "mesh/model.h" +#include "mesh/dbus.h" +#include "mesh/error.h" +#include "mesh/agent.h" +#include "mesh/mesh.h" +#include "mesh/mesh-defs.h" + +/* + * The default values for mesh configuration. Can be + * overwritten by values from mesh.conf + */ +#define DEFAULT_PROV_TIMEOUT 60 +#define DEFAULT_ALGORITHMS 0x0001 + +/* TODO: add more default values */ + +struct scan_filter { + uint8_t id; + const char *pattern; +}; + +struct bt_mesh { + struct mesh_io *io; + struct l_queue *filters; + prov_rx_cb_t prov_rx; + void *prov_data; + uint32_t prov_timeout; + uint16_t algorithms; + uint16_t req_index; + uint8_t max_filters; +}; + +struct join_data{ + struct l_dbus_message *msg; + struct mesh_agent *agent; + const char *sender; + const char *app_path; + struct mesh_node *node; + uint32_t disc_watch; + uint8_t *uuid; +}; + +static struct bt_mesh mesh; + +/* We allow only one outstanding Join request */ +static struct join_data *join_pending; + +/* Pending method requests */ +static struct l_queue *pending_queue; + +static const char *storage_dir; + +static bool simple_match(const void *a, const void *b) +{ + return a == b; +} + +/* Used for any outbound traffic that doesn't have Friendship Constraints */ +/* This includes Beacons, Provisioning and unrestricted Network Traffic */ +bool mesh_send_pkt(uint8_t count, uint16_t interval, + void *data, uint16_t len) +{ + struct mesh_io_send_info info = { + .type = MESH_IO_TIMING_TYPE_GENERAL, + .u.gen.cnt = count, + .u.gen.interval = interval, + .u.gen.max_delay = 0, + .u.gen.min_delay = 0, + }; + + return mesh_io_send(mesh.io, &info, data, len); +} + +bool mesh_send_cancel(const uint8_t *filter, uint8_t len) +{ + return mesh_io_send_cancel(mesh.io, filter, len); +} + +static void prov_rx(void *user_data, struct mesh_io_recv_info *info, + const uint8_t *data, uint16_t len) +{ + if (user_data != &mesh) + return; + + if (mesh.prov_rx) + mesh.prov_rx(mesh.prov_data, data, len); +} + +bool mesh_reg_prov_rx(prov_rx_cb_t cb, void *user_data) +{ + if (mesh.prov_rx && mesh.prov_rx != cb) + return false; + + mesh.prov_rx = cb; + mesh.prov_data = user_data; + + return mesh_io_register_recv_cb(mesh.io, MESH_IO_FILTER_PROV, + prov_rx, &mesh); +} + +void mesh_unreg_prov_rx(prov_rx_cb_t cb) +{ + if (mesh.prov_rx != cb) + return; + + mesh.prov_rx = NULL; + mesh.prov_data = NULL; + mesh_io_deregister_recv_cb(mesh.io, MESH_IO_FILTER_PROV); +} + +bool mesh_init(const char *config_dir, enum mesh_io_type type, void *opts) +{ + struct mesh_io_caps caps; + + if (mesh.io) + return true; + + mesh_model_init(); + mesh_agent_init(); + + /* TODO: read mesh.conf */ + mesh.prov_timeout = DEFAULT_PROV_TIMEOUT; + mesh.algorithms = DEFAULT_ALGORITHMS; + + storage_dir = config_dir ? config_dir : MESH_STORAGEDIR; + + l_info("Loading node configuration from %s", storage_dir); + + if (!node_load_from_storage(storage_dir)) + return false; + + mesh.io = mesh_io_new(type, opts); + if (!mesh.io) + return false; + + l_debug("io %p", mesh.io); + mesh_io_get_caps(mesh.io, &caps); + mesh.max_filters = caps.max_num_filters; + + node_attach_io_all(mesh.io); + + return true; +} + +static void pending_request_exit(void *data) +{ + struct l_dbus_message *reply; + struct l_dbus_message *msg = data; + + reply = dbus_error(msg, MESH_ERROR_FAILED, "Failed. Exiting"); + l_dbus_send(dbus_get_bus(), reply); +} + +static void free_pending_join_call(bool failed) +{ + if (!join_pending) + return; + + if (join_pending->disc_watch) + l_dbus_remove_watch(dbus_get_bus(), + join_pending->disc_watch); + + mesh_agent_remove(join_pending->agent); + + if (failed) + node_remove(join_pending->node); + + l_free(join_pending); + join_pending = NULL; +} + +void mesh_cleanup(void) +{ + struct l_dbus_message *reply; + + mesh_io_destroy(mesh.io); + + if (join_pending) { + + if (join_pending->msg) { + reply = dbus_error(join_pending->msg, MESH_ERROR_FAILED, + "Failed. Exiting"); + l_dbus_send(dbus_get_bus(), reply); + } + + acceptor_cancel(&mesh); + free_pending_join_call(true); + } + + l_queue_destroy(pending_queue, pending_request_exit); + node_cleanup_all(); + mesh_model_cleanup(); + + l_dbus_object_remove_interface(dbus_get_bus(), BLUEZ_MESH_PATH, + MESH_NETWORK_INTERFACE); + l_dbus_unregister_interface(dbus_get_bus(), MESH_NETWORK_INTERFACE); +} + +const char *mesh_status_str(uint8_t err) +{ + switch (err) { + case MESH_STATUS_SUCCESS: return "Success"; + case MESH_STATUS_INVALID_ADDRESS: return "Invalid Address"; + case MESH_STATUS_INVALID_MODEL: return "Invalid Model"; + case MESH_STATUS_INVALID_APPKEY: return "Invalid AppKey"; + case MESH_STATUS_INVALID_NETKEY: return "Invalid NetKey"; + case MESH_STATUS_INSUFF_RESOURCES: return "Insufficient Resources"; + case MESH_STATUS_IDX_ALREADY_STORED: return "Key Idx Already Stored"; + case MESH_STATUS_INVALID_PUB_PARAM: return "Invalid Publish Parameters"; + case MESH_STATUS_NOT_SUB_MOD: return "Not a Subscribe Model"; + case MESH_STATUS_STORAGE_FAIL: return "Storage Failure"; + case MESH_STATUS_FEATURE_NO_SUPPORT: return "Feature Not Supported"; + case MESH_STATUS_CANNOT_UPDATE: return "Cannot Update"; + case MESH_STATUS_CANNOT_REMOVE: return "Cannot Remove"; + case MESH_STATUS_CANNOT_BIND: return "Cannot bind"; + case MESH_STATUS_UNABLE_CHANGE_STATE: return "Unable to change state"; + case MESH_STATUS_CANNOT_SET: return "Cannot set"; + case MESH_STATUS_UNSPECIFIED_ERROR: return "Unspecified error"; + case MESH_STATUS_INVALID_BINDING: return "Invalid Binding"; + + default: return "Unknown"; + } +} + +/* This is being called if the app exits unexpectedly */ +static void prov_disc_cb(struct l_dbus *bus, void *user_data) +{ + if (!join_pending) + return; + + if (join_pending->msg) + l_dbus_message_unref(join_pending->msg); + + acceptor_cancel(&mesh); + join_pending->disc_watch = 0; + + free_pending_join_call(true); +} + +const char *mesh_prov_status_str(uint8_t status) +{ + switch (status) { + case PROV_ERR_SUCCESS: + return "success"; + case PROV_ERR_INVALID_PDU: + case PROV_ERR_INVALID_FORMAT: + case PROV_ERR_UNEXPECTED_PDU: + return "bad-pdu"; + case PROV_ERR_CONFIRM_FAILED: + return "confirmation-failed"; + case PROV_ERR_INSUF_RESOURCE: + return "out-of-resources"; + case PROV_ERR_DECRYPT_FAILED: + return "decryption-error"; + case PROV_ERR_CANT_ASSIGN_ADDR: + return "cannot-assign-addresses"; + case PROV_ERR_TIMEOUT: + return "timeout"; + case PROV_ERR_UNEXPECTED_ERR: + default: + return "unexpected-error"; + } +} + +static void send_join_failed(const char *owner, const char *path, + uint8_t status) +{ + struct l_dbus_message *msg; + struct l_dbus *dbus = dbus_get_bus(); + + msg = l_dbus_message_new_method_call(dbus, owner, path, + MESH_APPLICATION_INTERFACE, + "JoinFailed"); + + l_dbus_message_set_arguments(msg, "s", mesh_prov_status_str(status)); + l_dbus_send(dbus_get_bus(), msg); + + free_pending_join_call(true); +} + +static bool prov_complete_cb(void *user_data, uint8_t status, + struct mesh_prov_node_info *info) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + const char *owner; + const char *path; + const uint8_t *token; + + l_debug("Provisioning complete %s", mesh_prov_status_str(status)); + + if (!join_pending) + return false; + + owner = join_pending->sender; + path = join_pending->app_path; + + if (status == PROV_ERR_SUCCESS && + !node_add_pending_local(join_pending->node, info)) + status = PROV_ERR_UNEXPECTED_ERR; + + if (status != PROV_ERR_SUCCESS) { + send_join_failed(owner, path, status); + return false; + } + + node_attach_io(join_pending->node, mesh.io); + token = node_get_token(join_pending->node); + + msg = l_dbus_message_new_method_call(dbus, owner, path, + MESH_APPLICATION_INTERFACE, + "JoinComplete"); + + l_dbus_message_set_arguments(msg, "t", l_get_be64(token)); + + l_dbus_send(dbus, msg); + + free_pending_join_call(false); + + return true; +} + +static void node_init_cb(struct mesh_node *node, struct mesh_agent *agent) +{ + struct l_dbus_message *reply; + uint8_t num_ele; + + if (!node) { + reply = dbus_error(join_pending->msg, MESH_ERROR_FAILED, + "Failed to create node from application"); + goto fail; + } + + join_pending->node = node; + num_ele = node_get_num_elements(node); + + if (!acceptor_start(num_ele, join_pending->uuid, mesh.algorithms, + mesh.prov_timeout, agent, prov_complete_cb, + &mesh)) + { + reply = dbus_error(join_pending->msg, MESH_ERROR_FAILED, + "Failed to start provisioning acceptor"); + goto fail; + } + + reply = l_dbus_message_new_method_return(join_pending->msg); + l_dbus_send(dbus_get_bus(), reply); + join_pending->msg = NULL; + + /* Setup disconnect watch */ + join_pending->disc_watch = l_dbus_add_disconnect_watch(dbus_get_bus(), + join_pending->sender, + prov_disc_cb, NULL, NULL); + + return; + +fail: + l_dbus_send(dbus_get_bus(), reply); + free_pending_join_call(true); +} + +static struct l_dbus_message *join_network_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + const char *app_path, *sender; + struct l_dbus_message_iter iter_uuid; + uint32_t n; + + l_debug("Join network request"); + + if (join_pending) + return dbus_error(msg, MESH_ERROR_BUSY, + "Provisioning in progress"); + + if (!l_dbus_message_get_arguments(msg, "oay", &app_path, + &iter_uuid)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + join_pending = l_new(struct join_data, 1); + + if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, + &join_pending->uuid, &n) + || n != 16) { + l_free(join_pending); + join_pending = NULL; + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad device UUID"); + } + + if (node_find_by_uuid(join_pending->uuid)) { + l_free(join_pending); + join_pending = NULL; + return dbus_error(msg, MESH_ERROR_ALREADY_EXISTS, + "Node already exists"); + } + + sender = l_dbus_message_get_sender(msg); + + join_pending->sender = l_strdup(sender); + join_pending->msg = l_dbus_message_ref(msg); + join_pending->app_path = app_path; + + /* Try to create a temporary node */ + node_join(app_path, sender, join_pending->uuid, node_init_cb); + + return NULL; +} + +static struct l_dbus_message *cancel_join_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct l_dbus_message *reply; + + l_debug("Cancel Join"); + + if (!join_pending) { + reply = dbus_error(msg, MESH_ERROR_DOES_NOT_EXIST, + "No join in progress"); + goto done; + } + + acceptor_cancel(&mesh); + + /* Return error to the original Join call */ + if (join_pending->msg) { + reply = dbus_error(join_pending->msg, MESH_ERROR_FAILED, NULL); + l_dbus_send(dbus_get_bus(), reply); + } + + reply = l_dbus_message_new_method_return(msg); + l_dbus_message_set_arguments(reply, ""); + + free_pending_join_call(true); +done: + return reply; +} + +static void attach_ready_cb(void *user_data, int status, struct mesh_node *node) +{ + struct l_dbus_message *reply; + struct l_dbus_message *pending_msg; + + pending_msg = l_queue_find(pending_queue, simple_match, user_data); + if (!pending_msg) + return; + + if (status != MESH_ERROR_NONE) { + const char *desc = (status == MESH_ERROR_NOT_FOUND) ? + "Node match not found" : "Attach failed"; + reply = dbus_error(pending_msg, status, desc); + goto done; + } + + reply = l_dbus_message_new_method_return(pending_msg); + + node_build_attach_reply(node, reply); + +done: + l_dbus_send(dbus_get_bus(), reply); + l_queue_remove(pending_queue, pending_msg); +} + +static struct l_dbus_message *attach_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + uint64_t token; + const char *app_path, *sender; + struct l_dbus_message *pending_msg; + int status; + + l_debug("Attach"); + + if (!l_dbus_message_get_arguments(msg, "ot", &app_path, &token)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + sender = l_dbus_message_get_sender(msg); + + pending_msg = l_dbus_message_ref(msg); + if (!pending_queue) + pending_queue = l_queue_new(); + + l_queue_push_tail(pending_queue, pending_msg); + + status = node_attach(app_path, sender, token, attach_ready_cb, + pending_msg); + if (status == MESH_ERROR_NONE) + return NULL; + + l_queue_remove(pending_queue, pending_msg); + + return dbus_error(msg, status, NULL); +} + +static struct l_dbus_message *leave_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + uint64_t token; + + l_debug("Leave"); + + if (!l_dbus_message_get_arguments(msg, "t", &token)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + node_remove(node_find_by_token(token)); + + return l_dbus_message_new_method_return(msg); +} + +static void create_node_ready_cb(void *user_data, int status, + struct mesh_node *node) +{ + struct l_dbus_message *reply; + struct l_dbus_message *pending_msg; + const uint8_t *token; + + pending_msg = l_queue_find(pending_queue, simple_match, user_data); + if (!pending_msg) + return; + + if (status != MESH_ERROR_NONE) { + reply = dbus_error(pending_msg, status, NULL); + goto done; + } + + node_attach_io(node, mesh.io); + + reply = l_dbus_message_new_method_return(pending_msg); + token = node_get_token(node); + + l_debug(); + l_dbus_message_set_arguments(reply, "t", l_get_be64(token)); + +done: + l_dbus_send(dbus_get_bus(), reply); + l_queue_remove(pending_queue, pending_msg); +} + +static struct l_dbus_message *create_network_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + const char *app_path, *sender; + struct l_dbus_message_iter iter_uuid; + struct l_dbus_message *pending_msg; + uint8_t *uuid; + uint32_t n; + + l_debug("Create network request"); + + if (!l_dbus_message_get_arguments(msg, "oay", &app_path, + &iter_uuid)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, &uuid, &n) + || n != 16) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad device UUID"); + + sender = l_dbus_message_get_sender(msg); + pending_msg = l_dbus_message_ref(msg); + if (!pending_queue) + pending_queue = l_queue_new(); + + l_queue_push_tail(pending_queue, pending_msg); + + node_create(app_path, sender, uuid, create_node_ready_cb, + pending_msg); + + return NULL; +} + +static struct l_dbus_message *import_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + const char *app_path, *sender; + struct l_dbus_message *pending_msg = NULL; + struct l_dbus_message_iter iter_uuid; + struct l_dbus_message_iter iter_dev_key; + struct l_dbus_message_iter iter_net_key; + struct l_dbus_message_iter iter_flags; + const char *key; + struct l_dbus_message_iter var; + + uint8_t *uuid; + uint8_t *dev_key; + uint8_t *net_key; + uint16_t net_idx; + bool kr = false; + bool ivu = false; + uint32_t iv_index; + uint16_t unicast; + uint32_t n; + + l_debug("Import local node request"); + + if (!l_dbus_message_get_arguments(msg, "oayayayqa{sv}uq", + &app_path, &iter_uuid, + &iter_dev_key, &iter_net_key, + &net_idx, &iter_flags, + &iv_index, + &unicast)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, &uuid, &n) || + n != 16) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad dev UUID"); + + if (node_find_by_uuid(uuid)) + return dbus_error(msg, MESH_ERROR_ALREADY_EXISTS, + "Node already exists"); + + if (!l_dbus_message_iter_get_fixed_array(&iter_dev_key, &dev_key, &n) || + n != 16) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad dev key"); + + if (!l_dbus_message_iter_get_fixed_array(&iter_net_key, &net_key, &n) || + n != 16) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad net key"); + + if (net_idx > MAX_KEY_IDX) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad net index"); + + while (l_dbus_message_iter_next_entry(&iter_flags, &key, &var)) { + if (!strcmp(key, "IVUpdate")) { + if (!l_dbus_message_iter_get_variant(&var, "b", + &ivu)) + goto fail; + continue; + } + + if (!strcmp(key, "KeyRefresh")) { + if (!l_dbus_message_iter_get_variant(&var, "b", + &kr)) + goto fail; + continue; + } + + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad flags"); + } + + if (!IS_UNICAST(unicast)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Bad address"); + + sender = l_dbus_message_get_sender(msg); + pending_msg = l_dbus_message_ref(msg); + + if (!pending_queue) + pending_queue = l_queue_new(); + + l_queue_push_tail(pending_queue, pending_msg); + + if (!node_import(app_path, sender, uuid, dev_key, net_key, net_idx, + kr, ivu, iv_index, unicast, + create_node_ready_cb, pending_msg)) + goto fail; + + return NULL; + +fail: + if (pending_msg) { + l_dbus_message_unref(msg); + l_queue_remove(pending_queue, pending_msg); + } + + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Node import failed"); +} + +static void setup_network_interface(struct l_dbus_interface *iface) +{ + l_dbus_interface_method(iface, "Join", 0, join_network_call, "", + "oay", "app", "uuid"); + + l_dbus_interface_method(iface, "Cancel", 0, cancel_join_call, "", ""); + + l_dbus_interface_method(iface, "Attach", 0, attach_call, + "oa(ya(qa{sv}))", "ot", "node", + "configuration", "app", "token"); + + l_dbus_interface_method(iface, "Leave", 0, leave_call, "", "t", + "token"); + + l_dbus_interface_method(iface, "CreateNetwork", 0, create_network_call, + "t", "oay", "token", "app", "uuid"); + + l_dbus_interface_method(iface, "Import", 0, + import_call, + "t", "oayayayqa{sv}uq", "token", + "app", "uuid", "dev_key", "net_key", + "net_index", "flags", "iv_index", + "unicast"); +} + +bool mesh_dbus_init(struct l_dbus *dbus) +{ + if (!l_dbus_register_interface(dbus, MESH_NETWORK_INTERFACE, + setup_network_interface, + NULL, false)) { + l_info("Unable to register %s interface", + MESH_NETWORK_INTERFACE); + return false; + } + + if (!l_dbus_object_add_interface(dbus, BLUEZ_MESH_PATH, + MESH_NETWORK_INTERFACE, NULL)) { + l_info("Unable to register the mesh object on '%s'", + MESH_NETWORK_INTERFACE); + l_dbus_unregister_interface(dbus, MESH_NETWORK_INTERFACE); + return false; + } + + l_info("Added Network Interface on %s", BLUEZ_MESH_PATH); + + return true; +} + +const char *mesh_get_storage_dir(void) +{ + return storage_dir; +} diff --git a/mesh/mesh.h b/mesh/mesh.h new file mode 100644 index 0000000..e0a3e1b --- /dev/null +++ b/mesh/mesh.h @@ -0,0 +1,45 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#define BLUEZ_MESH_NAME "org.bluez.mesh" + +#define MESH_NETWORK_INTERFACE "org.bluez.mesh.Network1" +#define MESH_NODE_INTERFACE "org.bluez.mesh.Node1" +#define MESH_MANAGEMENT_INTERFACE "org.bluez.mesh.Management1" +#define MESH_ELEMENT_INTERFACE "org.bluez.mesh.Element1" +#define MESH_APPLICATION_INTERFACE "org.bluez.mesh.Application1" +#define MESH_PROVISION_AGENT_INTERFACE "org.bluez.mesh.ProvisionAgent1" +#define MESH_PROVISIONER_INTERFACE "org.bluez.mesh.Provisioner1" +#define ERROR_INTERFACE "org.bluez.mesh.Error" + +enum mesh_io_type; + +typedef void (*prov_rx_cb_t)(void *user_data, const uint8_t *data, + uint16_t len); +bool mesh_init(const char *in_config_name, enum mesh_io_type type, void *opts); +void mesh_cleanup(void); +bool mesh_dbus_init(struct l_dbus *dbus); + +const char *mesh_status_str(uint8_t err); +bool mesh_send_pkt(uint8_t count, uint16_t interval, void *data, uint16_t len); +bool mesh_send_cancel(const uint8_t *filter, uint8_t len); +bool mesh_reg_prov_rx(prov_rx_cb_t cb, void *user_data); +void mesh_unreg_prov_rx(prov_rx_cb_t cb); +const char *mesh_prov_status_str(uint8_t status); +const char *mesh_get_storage_dir(void); diff --git a/mesh/model.c b/mesh/model.c new file mode 100644 index 0000000..84f1dc7 --- /dev/null +++ b/mesh/model.c @@ -0,0 +1,1696 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "mesh/mesh-defs.h" + +#include "mesh/mesh.h" +#include "mesh/crypto.h" +#include "mesh/node.h" +#include "mesh/mesh-config.h" +#include "mesh/net.h" +#include "mesh/appkey.h" +#include "mesh/cfgmod.h" +#include "mesh/error.h" +#include "mesh/dbus.h" +#include "mesh/util.h" +#include "mesh/model.h" +#include "mesh/keyring.h" + +/* Divide and round to ceiling (up) to calculate segment count */ +#define CEILDIV(val, div) (((val) + (div) - 1) / (div)) + +struct mesh_model { + const struct mesh_model_ops *cbs; + void *user_data; + struct l_queue *bindings; + struct l_queue *subs; + struct l_queue *virtuals; + struct mesh_model_pub *pub; + uint32_t id; + uint8_t ele_idx; +}; + +struct mesh_virtual { + uint32_t id; /* Internal ID of a stored virtual addr, min val 0x10000 */ + uint16_t ref_cnt; + uint16_t addr; /* 16-bit virtual address, used in messages */ + uint8_t label[16]; /* 128 bit label UUID */ +}; + +/* These struct is used to pass lots of params to l_queue_foreach */ +struct mod_forward { + struct mesh_virtual *virt; + const uint8_t *data; + uint16_t src; + uint16_t dst; + uint16_t unicast; + uint16_t app_idx; + uint16_t net_idx; + uint16_t size; + uint8_t ttl; + int8_t rssi; + bool szmict; + bool has_dst; + bool done; +}; + +static struct l_queue *mesh_virtuals; + +static uint32_t virt_id_next = VIRTUAL_BASE; +static struct timeval tx_start; + +static bool is_internal(uint32_t id) +{ + if (id == CONFIG_SRV_MODEL || id == CONFIG_CLI_MODEL) + return true; + + return false; +} + +static void unref_virt(void *data) +{ + struct mesh_virtual *virt = data; + + if (virt->ref_cnt > 0) + virt->ref_cnt--; + + if (virt->ref_cnt) + return; + + l_queue_remove(mesh_virtuals, virt); + l_free(virt); +} + +static bool simple_match(const void *a, const void *b) +{ + return a == b; +} + +static bool has_binding(struct l_queue *bindings, uint16_t idx) +{ + const struct l_queue_entry *l; + + for (l = l_queue_get_entries(bindings); l; l = l->next) { + if (L_PTR_TO_UINT(l->data) == idx) + return true; + } + return false; +} + +static bool find_virt_by_id(const void *a, const void *b) +{ + const struct mesh_virtual *virt = a; + uint32_t id = L_PTR_TO_UINT(b); + + return virt->id == id; +} + +static bool find_virt_by_label(const void *a, const void *b) +{ + const struct mesh_virtual *virt = a; + const uint8_t *label = b; + + return memcmp(virt->label, label, 16) == 0; +} + +static bool match_model_id(const void *a, const void *b) +{ + const struct mesh_model *model = a; + uint32_t id = L_PTR_TO_UINT(b); + + return (mesh_model_get_model_id(model) == id); +} + +static struct mesh_model *get_model(struct mesh_node *node, uint8_t ele_idx, + uint32_t id, int *status) +{ + struct l_queue *models; + struct mesh_model *model; + + models = node_get_element_models(node, ele_idx, status); + if (!models) { + *status = MESH_STATUS_INVALID_MODEL; + return NULL; + } + + model = l_queue_find(models, match_model_id, L_UINT_TO_PTR(id)); + + *status = (model) ? MESH_STATUS_SUCCESS : MESH_STATUS_INVALID_MODEL; + + return model; +} + +static struct mesh_model *find_model(struct mesh_node *node, uint16_t addr, + uint32_t mod_id, int *status) +{ + int ele_idx; + + ele_idx = node_get_element_idx(node, addr); + + if (ele_idx < 0) { + *status = MESH_STATUS_INVALID_ADDRESS; + return NULL; + } + + return get_model(node, (uint8_t) ele_idx, mod_id, status); +} + +static uint32_t pub_period_to_ms(uint8_t pub_period) +{ + int n; + + n = pub_period >> 2; + + switch (pub_period & 0x3) { + default: + return n * 100; + case 2: + n *= 10; + /* Fall Through */ + case 1: + return n * 1000; + case 3: + return n * 10 * 60 * 1000; + } +} + +static struct l_dbus_message *create_config_update_msg(struct mesh_node *node, + uint8_t ele_idx, uint32_t id, + struct l_dbus_message_builder **builder) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + const char *owner; + const char *path; + uint16_t model_id; + + owner = node_get_owner(node); + path = node_get_element_path(node, ele_idx); + if (!path || !owner) + return NULL; + + l_debug("Send \"UpdateModelConfiguration\""); + msg = l_dbus_message_new_method_call(dbus, owner, path, + MESH_ELEMENT_INTERFACE, + "UpdateModelConfiguration"); + + *builder = l_dbus_message_builder_new(msg); + + model_id = (uint16_t) id; + + l_dbus_message_builder_append_basic(*builder, 'q', &model_id); + + l_dbus_message_builder_enter_array(*builder, "{sv}"); + + if ((id & VENDOR_ID_MASK) != VENDOR_ID_MASK) { + uint16_t vendor = id >> 16; + dbus_append_dict_entry_basic(*builder, "Vendor", "q", &vendor); + } + + return msg; +} + +static void config_update_model_pub_period(struct mesh_node *node, + uint8_t ele_idx, uint32_t model_id, + uint32_t period) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + struct l_dbus_message_builder *builder; + + msg = create_config_update_msg(node, ele_idx, model_id, &builder); + if (!msg) + return; + + dbus_append_dict_entry_basic(builder, "PublicationPeriod", "u", + &period); + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + l_dbus_send(dbus, msg); +} + +static void append_dict_uint16_array(struct l_dbus_message_builder *builder, + struct l_queue *q, const char *key) +{ + const struct l_queue_entry *entry; + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', key); + l_dbus_message_builder_enter_variant(builder, "aq"); + l_dbus_message_builder_enter_array(builder, "q"); + + for (entry = l_queue_get_entries(q); entry; entry = entry->next) { + uint16_t value = (uint16_t) L_PTR_TO_UINT(entry->data); + + l_dbus_message_builder_append_basic(builder,'q', &value); + } + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); +} + +static void config_update_model_bindings(struct mesh_node *node, + struct mesh_model *mod) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + struct l_dbus_message_builder *builder; + + msg = create_config_update_msg(node, mod->ele_idx, mod->id, + &builder); + if (!msg) + return; + + append_dict_uint16_array(builder, mod->bindings, "Bindings"); + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + l_dbus_send(dbus, msg); +} + +static void forward_model(void *a, void *b) +{ + struct mesh_model *mod = a; + struct mod_forward *fwd = b; + struct mesh_virtual *virt; + uint32_t dst; + bool result; + + l_debug("model %8.8x with idx %3.3x", mod->id, fwd->app_idx); + + if (fwd->app_idx != APP_IDX_DEV_LOCAL && + fwd->app_idx != APP_IDX_DEV_REMOTE && + !has_binding(mod->bindings, fwd->app_idx)) + return; + + dst = fwd->dst; + if (dst == fwd->unicast || IS_FIXED_GROUP_ADDRESS(dst)) + fwd->has_dst = true; + else if (fwd->virt) { + virt = l_queue_find(mod->virtuals, simple_match, fwd->virt); + + /* Check that this is not own publication */ + if (mod->pub && (virt && virt->id == mod->pub->addr)) + return; + + if (virt) { + /* + * Map Virtual addresses to a usable namespace that + * prevents us for forwarding a false positive + * (multiple Virtual Addresses that map to the same + * 16-bit virtual address identifier) + */ + fwd->has_dst = true; + dst = virt->id; + } + } else { + if (l_queue_find(mod->subs, simple_match, L_UINT_TO_PTR(dst))) + fwd->has_dst = true; + } + + if (!fwd->has_dst) + return; + + /* Return, if this is not a internal model */ + if (!mod->cbs) + return; + + result = false; + + if (mod->cbs->recv) + result = mod->cbs->recv(fwd->src, dst, fwd->unicast, + fwd->app_idx, fwd->net_idx, + fwd->data, fwd->size, fwd->ttl, mod->user_data); + + if (dst == fwd->unicast && result) + fwd->done = true; +} + +static int app_packet_decrypt(struct mesh_net *net, const uint8_t *data, + uint16_t size, bool szmict, uint16_t src, + uint16_t dst, uint8_t *virt, uint16_t virt_size, + uint8_t key_aid, uint32_t seq, + uint32_t iv_idx, uint8_t *out) +{ + struct l_queue *app_keys = mesh_net_get_app_keys(net); + const struct l_queue_entry *entry; + + if (!app_keys) + return -1; + + for (entry = l_queue_get_entries(app_keys); entry; + entry = entry->next) { + const uint8_t *old_key = NULL, *new_key = NULL; + uint8_t old_key_aid, new_key_aid; + int app_idx; + bool decrypted; + + app_idx = appkey_get_key_idx(entry->data, + &old_key, &old_key_aid, + &new_key, &new_key_aid); + + if (app_idx < 0) + continue; + + if (old_key && old_key_aid == key_aid) { + decrypted = mesh_crypto_payload_decrypt(NULL, 0, data, + size, szmict, src, dst, key_aid, + seq, iv_idx, out, old_key); + + if (decrypted) { + print_packet("Used App Key", old_key, 16); + return app_idx; + } + + print_packet("Failed App Key", old_key, 16); + } + + if (new_key && new_key_aid == key_aid) { + decrypted = mesh_crypto_payload_decrypt(NULL, 0, data, + size, szmict, src, dst, key_aid, + seq, iv_idx, out, new_key); + + if (decrypted) { + print_packet("Used App Key", new_key, 16); + return app_idx; + } + + print_packet("Failed App Key", new_key, 16); + } + } + + return -1; +} + +static int dev_packet_decrypt(struct mesh_node *node, const uint8_t *data, + uint16_t size, bool szmict, uint16_t src, + uint16_t dst, uint8_t key_aid, uint32_t seq, + uint32_t iv_idx, uint8_t *out) +{ + uint8_t dev_key[16]; + const uint8_t *key; + + key = node_get_device_key(node); + if (!key) + return -1; + + if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict, src, + dst, key_aid, seq, iv_idx, out, key)) + return APP_IDX_DEV_LOCAL; + + if (!keyring_get_remote_dev_key(node, src, dev_key)) + return -1; + + key = dev_key; + if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict, src, + dst, key_aid, seq, iv_idx, out, key)) + return APP_IDX_DEV_REMOTE; + + return -1; +} + +static int virt_packet_decrypt(struct mesh_net *net, const uint8_t *data, + uint16_t size, bool szmict, uint16_t src, + uint16_t dst, uint8_t key_aid, uint32_t seq, + uint32_t iv_idx, uint8_t *out, + struct mesh_virtual **decrypt_virt) +{ + const struct l_queue_entry *v; + + for (v = l_queue_get_entries(mesh_virtuals); v; v = v->next) { + struct mesh_virtual *virt = v->data; + int decrypt_idx; + + if (virt->addr != dst) + continue; + + decrypt_idx = app_packet_decrypt(net, data, size, szmict, src, + dst, virt->label, 16, + key_aid, seq, iv_idx, + out); + + if (decrypt_idx >= 0) { + *decrypt_virt = virt; + return decrypt_idx; + } + } + + return -1; +} + +static void cmplt(uint16_t remote, uint8_t status, + void *data, uint16_t size, + void *user_data) +{ + struct timeval tx_end; + + if (status) + l_debug("Tx-->%4.4x (%d octets) Failed (%d)", + remote, size, status); + else + l_debug("Tx-->%4.4x (%d octets) Succeeded", remote, size); + + /* print_packet("Sent Data", data, size); */ + + gettimeofday(&tx_end, NULL); + if (tx_end.tv_sec == tx_start.tv_sec) { + l_debug("Duration 0.%6.6lu seconds", + tx_end.tv_usec - tx_start.tv_usec); + } else { + if (tx_start.tv_usec > tx_end.tv_usec) + l_debug("Duration %lu.%6.6lu seconds", + tx_end.tv_sec - tx_start.tv_sec - 1, + tx_end.tv_usec + 1000000 - tx_start.tv_usec); + else + l_debug("Duration %lu.%6.6lu seconds", + tx_end.tv_sec - tx_start.tv_sec, + tx_end.tv_usec - tx_start.tv_usec); + } +} + +static bool msg_send(struct mesh_node *node, bool credential, uint16_t src, + uint32_t dst, uint16_t app_idx, uint16_t net_idx, + uint8_t *label, uint8_t ttl, + const void *msg, uint16_t msg_len) +{ + uint8_t dev_key[16]; + uint32_t iv_index, seq_num; + const uint8_t *key; + uint8_t *out; + uint8_t key_aid = APP_AID_DEV; + bool szmic = false; + bool ret = false; + uint16_t out_len = msg_len + sizeof(uint32_t); + struct mesh_net *net = node_get_net(node); + + /* Use large MIC if it doesn't affect segmentation */ + if (msg_len > 11 && msg_len <= 376) { + if (CEILDIV(out_len, 12) == CEILDIV(out_len + 4, 12)) { + szmic = true; + out_len = msg_len + sizeof(uint64_t); + } + } + + if (app_idx == APP_IDX_DEV_LOCAL) { + key = node_get_device_key(node); + if (!key) + return false; + } else if (app_idx == APP_IDX_DEV_REMOTE) { + if (!keyring_get_remote_dev_key(node, dst, dev_key)) + return false; + + key = dev_key; + } else { + key = appkey_get_key(node_get_net(node), app_idx, &key_aid); + if (!key) { + l_debug("no app key for (%x)", app_idx); + return false; + } + + net_idx = appkey_net_idx(node_get_net(node), app_idx); + } + + l_debug("(%x) %p", app_idx, key); + l_debug("net_idx %x", net_idx); + + out = l_malloc(out_len); + + iv_index = mesh_net_get_iv_index(net); + + seq_num = mesh_net_get_seq_num(net); + if (!mesh_crypto_payload_encrypt(label, msg, out, msg_len, src, dst, + key_aid, seq_num, iv_index, szmic, key)) { + l_error("Failed to Encrypt Payload"); + goto done; + } + + /* print_packet("Encrypted with", key, 16); */ + + ret = mesh_net_app_send(net, credential, src, dst, key_aid, net_idx, + ttl, seq_num, iv_index, szmic, out, + out_len, cmplt, NULL); +done: + l_free(out); + return ret; +} + +static void remove_pub(struct mesh_node *node, struct mesh_model *mod) +{ + l_free(mod->pub); + mod->pub = NULL; + + if (!mod->cbs) + /* External models */ + config_update_model_pub_period(node, mod->ele_idx, mod->id, 0); + else if (mod->cbs && mod->cbs->pub) + /* Internal models */ + mod->cbs->pub(NULL); +} + +static void model_unbind_idx(struct mesh_node *node, struct mesh_model *mod, + uint16_t idx) +{ + l_queue_remove(mod->bindings, L_UINT_TO_PTR(idx)); + + if (!mod->cbs) + /* External model */ + config_update_model_bindings(node, mod); + else if (mod->cbs->bind) + /* Internal model */ + mod->cbs->bind(idx, ACTION_DELETE); + + if (mod->pub && idx != mod->pub->idx) + return; + + /* Remove model publication if the publication key is unbound */ + remove_pub(node, mod); +} + +static void model_bind_idx(struct mesh_node *node, struct mesh_model *mod, + uint16_t idx) +{ + if (!mod->bindings) + mod->bindings = l_queue_new(); + + l_queue_push_tail(mod->bindings, L_UINT_TO_PTR(idx)); + + l_debug("Add %4.4x to model %8.8x", idx, mod->id); + + if (!mod->cbs) + /* External model */ + config_update_model_bindings(node, mod); + else if (mod->cbs->bind) + /* Internal model */ + mod->cbs->bind(idx, ACTION_ADD); +} + +static int update_binding(struct mesh_node *node, uint16_t addr, uint32_t id, + uint16_t app_idx, bool unbind) +{ + int status; + struct mesh_model *mod; + bool is_present, is_vendor; + + mod = find_model(node, addr, id, &status); + if (!mod) { + l_debug("Model not found"); + return status; + } + + is_vendor = id < VENDOR_ID_MASK && id > 0xffff; + id = !is_vendor ? (id & 0xffff) : id; + + if (id == CONFIG_SRV_MODEL || id == CONFIG_CLI_MODEL) + return MESH_STATUS_INVALID_MODEL; + + if (!appkey_have_key(node_get_net(node), app_idx)) + return MESH_STATUS_INVALID_APPKEY; + + is_present = has_binding(mod->bindings, app_idx); + + if (!is_present && unbind) + return MESH_STATUS_SUCCESS; + + if (is_present && !unbind) + return MESH_STATUS_SUCCESS; + + if (unbind) { + model_unbind_idx(node, mod, app_idx); + if (!mesh_config_model_binding_del(node_config_get(node), + addr, is_vendor, id, app_idx)) + return MESH_STATUS_STORAGE_FAIL; + + return MESH_STATUS_SUCCESS; + } + + if (l_queue_length(mod->bindings) >= MAX_BINDINGS) + return MESH_STATUS_INSUFF_RESOURCES; + + if (!mesh_config_model_binding_add(node_config_get(node), + addr, is_vendor, id, app_idx)) + return MESH_STATUS_STORAGE_FAIL; + + model_bind_idx(node, mod, app_idx); + + return MESH_STATUS_SUCCESS; + +} + +static struct mesh_virtual *add_virtual(const uint8_t *v) +{ + struct mesh_virtual *virt = l_queue_find(mesh_virtuals, + find_virt_by_label, v); + + if (virt) { + virt->ref_cnt++; + return virt; + } + + virt = l_new(struct mesh_virtual, 1); + + if (!mesh_crypto_virtual_addr(v, &virt->addr)) { + l_free(virt); + return NULL; + } + + memcpy(virt->label, v, 16); + virt->ref_cnt = 1; + virt->id = virt_id_next++; + l_queue_push_head(mesh_virtuals, virt); + + return virt; +} + +static int set_pub(struct mesh_model *mod, const uint8_t *pub_addr, + uint16_t idx, bool cred_flag, uint8_t ttl, + uint8_t period, uint8_t retransmit, bool b_virt, + uint16_t *dst) +{ + struct mesh_virtual *virt = NULL; + uint16_t grp; + + if (dst) { + if (b_virt) + *dst = 0; + else + *dst = l_get_le16(pub_addr); + } + + if (b_virt) { + virt = add_virtual(pub_addr); + if (!virt) + return MESH_STATUS_STORAGE_FAIL; + + } + + /* If the old publication address is virtual, remove it from lists */ + if (mod->pub && mod->pub->addr >= VIRTUAL_BASE) { + struct mesh_virtual *old_virt; + + old_virt = l_queue_find(mod->virtuals, find_virt_by_id, + L_UINT_TO_PTR(mod->pub->addr)); + if (old_virt) { + l_queue_remove(mod->virtuals, old_virt); + unref_virt(old_virt); + } + } + + mod->pub = l_new(struct mesh_model_pub, 1); + + if (b_virt) { + l_queue_push_head(mod->virtuals, virt); + grp = virt->addr; + mod->pub->addr = virt->id; + } else { + grp = l_get_le16(pub_addr); + mod->pub->addr = grp; + } + + if (dst) + *dst = grp; + + mod->pub->credential = cred_flag; + mod->pub->idx = idx; + mod->pub->ttl = ttl; + mod->pub->period = period; + mod->pub->retransmit = retransmit; + + return MESH_STATUS_SUCCESS; +} + +static int add_sub(struct mesh_net *net, struct mesh_model *mod, + const uint8_t *group, bool b_virt, uint16_t *dst) +{ + struct mesh_virtual *virt = NULL; + uint16_t grp; + + if (b_virt) { + virt = add_virtual(group); + if (!virt) + return MESH_STATUS_STORAGE_FAIL; + + grp = virt->addr; + } else { + grp = l_get_le16(group); + } + + if (dst) + *dst = grp; + + if (!mod->subs) + mod->subs = l_queue_new(); + + /* Check if this group already exists */ + if (l_queue_find(mod->subs, simple_match, L_UINT_TO_PTR(grp))) { + if (b_virt) + unref_virt(virt); + + return MESH_STATUS_SUCCESS; + } + + if (b_virt) + l_queue_push_head(mod->virtuals, virt); + + l_queue_push_tail(mod->subs, L_UINT_TO_PTR(grp)); + + l_debug("Added %4.4x", grp); + + mesh_net_dst_reg(net, grp); + + return MESH_STATUS_SUCCESS; +} + +static void send_dev_key_msg_rcvd(struct mesh_node *node, uint8_t ele_idx, + uint16_t src, uint16_t app_idx, + uint16_t net_idx, uint16_t size, + const uint8_t *data) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + struct l_dbus_message_builder *builder; + const char *owner; + const char *path; + bool remote = (app_idx != APP_IDX_DEV_LOCAL); + + owner = node_get_owner(node); + path = node_get_element_path(node, ele_idx); + if (!path || !owner) + return; + + l_debug("Send \"DevKeyMessageReceived\""); + + msg = l_dbus_message_new_method_call(dbus, owner, path, + MESH_ELEMENT_INTERFACE, + "DevKeyMessageReceived"); + + builder = l_dbus_message_builder_new(msg); + + l_dbus_message_builder_append_basic(builder, 'q', &src); + l_dbus_message_builder_append_basic(builder, 'b', &remote); + l_dbus_message_builder_append_basic(builder, 'q', &net_idx); + dbus_append_byte_array(builder, data, size); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + l_dbus_send(dbus, msg); +} + +static void send_msg_rcvd(struct mesh_node *node, uint8_t ele_idx, bool is_sub, + uint16_t src, uint16_t app_idx, + uint16_t size, const uint8_t *data) +{ + struct l_dbus *dbus = dbus_get_bus(); + struct l_dbus_message *msg; + struct l_dbus_message_builder *builder; + const char *owner; + const char *path; + + owner = node_get_owner(node); + path = node_get_element_path(node, ele_idx); + if (!path || !owner) + return; + + l_debug("Send \"MessageReceived\""); + + msg = l_dbus_message_new_method_call(dbus, owner, path, + MESH_ELEMENT_INTERFACE, "MessageReceived"); + + builder = l_dbus_message_builder_new(msg); + + l_dbus_message_builder_append_basic(builder, 'q', &src); + l_dbus_message_builder_append_basic(builder, 'q', &app_idx); + l_dbus_message_builder_append_basic(builder, 'b', &is_sub); + + dbus_append_byte_array(builder, data, size); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + l_dbus_send(dbus, msg); +} + +bool mesh_model_rx(struct mesh_node *node, bool szmict, uint32_t seq0, + uint32_t seq, uint32_t iv_index, uint8_t ttl, + uint16_t net_idx, uint16_t src, uint16_t dst, + uint8_t key_aid, const uint8_t *data, uint16_t size) +{ + uint8_t *clear_text; + struct mod_forward forward = { + .src = src, + .dst = dst, + .data = NULL, + .size = size - (szmict ? 8 : 4), + .ttl = ttl, + .virt = NULL, + }; + struct mesh_net *net = node_get_net(node); + uint8_t num_ele; + int decrypt_idx, i, ele_idx; + uint16_t addr; + struct mesh_virtual *decrypt_virt = NULL; + bool result = false; + bool is_subscription; + + l_debug("iv_index %8.8x key_aid = %2.2x", iv_index, key_aid); + if (!dst) + return false; + + ele_idx = node_get_element_idx(node, dst); + + if (dst < 0x8000 && ele_idx < 0) + /* Unicast and not addressed to us */ + return false; + + clear_text = l_malloc(size); + if (!clear_text) + return false; + + forward.data = clear_text; + + /* + * The packet needs to be decoded by the correct key which + * is hinted by key_aid, but is not necessarily definitive + */ + if (key_aid == APP_AID_DEV || mesh_net_provisioner_mode_get(net)) + decrypt_idx = dev_packet_decrypt(node, data, size, szmict, src, + dst, key_aid, seq0, iv_index, + clear_text); + else if ((dst & 0xc000) == 0x8000) + decrypt_idx = virt_packet_decrypt(net, data, size, szmict, src, + dst, key_aid, seq0, + iv_index, clear_text, + &decrypt_virt); + else + decrypt_idx = app_packet_decrypt(net, data, size, szmict, src, + dst, NULL, 0, + key_aid, seq0, iv_index, + clear_text); + + if (decrypt_idx < 0) { + l_error("model.c - Failed to decrypt application payload"); + result = false; + goto done; + } + + /* print_packet("Clr Rx (pre-cache-check)", clear_text, size - 4); */ + + if (key_aid != APP_AID_DEV) { + uint16_t crpl = node_get_crpl(node); + + if (appkey_msg_in_replay_cache(net, (uint16_t) decrypt_idx, src, + crpl, seq, iv_index)) { + result = true; + goto done; + } + } + + print_packet("Clr Rx", clear_text, size - (szmict ? 8 : 4)); + + forward.virt = decrypt_virt; + forward.app_idx = decrypt_idx; + forward.net_idx = net_idx; + num_ele = node_get_num_elements(node); + addr = node_get_primary(node); + + if (!num_ele || IS_UNASSIGNED(addr)) + goto done; + + /* + * In case of fixed group addresses check if the + * corresponding mode is enabled. + */ + if (dst == PROXIES_ADDRESS && + (node_proxy_mode_get(node) != MESH_MODE_ENABLED)) + goto done; + + if (dst == FRIENDS_ADDRESS && + (node_friend_mode_get(node) != MESH_MODE_ENABLED)) + goto done; + + if (dst == RELAYS_ADDRESS) { + uint8_t cnt; + uint16_t interval; + + if (node_relay_mode_get(node, &cnt, &interval) != + MESH_MODE_ENABLED) + goto done; + } + + is_subscription = !(IS_UNICAST(dst)); + + + for (i = 0; i < num_ele; i++) { + struct l_queue *models; + + if (!is_subscription && ele_idx != i) + continue; + + forward.unicast = addr + i; + forward.has_dst = false; + + models = node_get_element_models(node, i, NULL); + + /* Internal models */ + l_queue_foreach(models, forward_model, &forward); + + /* + * Cycle through external models if the message has not been + * handled by internal models + */ + if (forward.has_dst && !forward.done) { + if ((decrypt_idx & APP_IDX_MASK) == decrypt_idx) + send_msg_rcvd(node, i, is_subscription, src, + forward.app_idx, forward.size, + forward.data); + else if (decrypt_idx == APP_IDX_DEV_REMOTE || + (decrypt_idx == APP_IDX_DEV_LOCAL && + mesh_net_is_local_address(net, src, 1))) + send_dev_key_msg_rcvd(node, i, src, decrypt_idx, + 0, forward.size, forward.data); + } + + /* + * Either the message has been processed internally or + * has been passed on to an external model. + */ + result = forward.has_dst | forward.done; + + /* If the message was to unicast address, we are done */ + if (!is_subscription && ele_idx == i) + break; + + /* + * For the fixed group addresses, i.e., all-proxies, + * all-friends, all-relays, all-nodes, the message is delivered + * to a primary element only. + */ + if (IS_FIXED_GROUP_ADDRESS(dst)) + break; + } + +done: + l_free(clear_text); + return result; +} + +int mesh_model_publish(struct mesh_node *node, uint32_t mod_id, + uint16_t src, uint8_t ttl, + const void *msg, uint16_t msg_len) +{ + struct mesh_net *net = node_get_net(node); + struct mesh_model *mod; + uint32_t target; + uint8_t *label = NULL; + uint16_t dst; + uint16_t net_idx; + bool result; + int status; + + /* print_packet("Mod Tx", msg, msg_len); */ + + if (!net || msg_len > 380) + return MESH_ERROR_INVALID_ARGS; + + /* If SRC is 0, use the Primary Element */ + if (src == 0) + src = mesh_net_get_address(net); + + mod = find_model(node, src, mod_id, &status); + if (!mod) { + l_debug("model %x not found", mod_id); + return MESH_ERROR_NOT_FOUND; + } + + if (!mod->pub) { + l_debug("publication doesn't exist (model %x)", mod_id); + return MESH_ERROR_DOES_NOT_EXIST; + } + + gettimeofday(&tx_start, NULL); + + target = mod->pub->addr; + + if (IS_UNASSIGNED(target)) + return MESH_ERROR_DOES_NOT_EXIST; + + if (target >= VIRTUAL_BASE) { + struct mesh_virtual *virt; + + virt = l_queue_find(mesh_virtuals, find_virt_by_id, + L_UINT_TO_PTR(target)); + if (!virt) + return MESH_ERROR_NOT_FOUND; + + label = virt->label; + dst = virt->addr; + } else { + dst = target; + } + + l_debug("publish dst=%x", dst); + + net_idx = appkey_net_idx(net, mod->pub->idx); + + result = msg_send(node, mod->pub->credential != 0, src, + dst, mod->pub->idx, net_idx, label, ttl, + msg, msg_len); + + return result ? MESH_ERROR_NONE : MESH_ERROR_FAILED; + +} + +bool mesh_model_send(struct mesh_node *node, uint16_t src, uint16_t dst, + uint16_t app_idx, uint16_t net_idx, + uint8_t ttl, + const void *msg, uint16_t msg_len) +{ + /* print_packet("Mod Tx", msg, msg_len); */ + + /* If SRC is 0, use the Primary Element */ + if (src == 0) + src = node_get_primary(node); + + gettimeofday(&tx_start, NULL); + + if (IS_UNASSIGNED(dst)) + return false; + + return msg_send(node, false, src, dst, app_idx, net_idx, + NULL, ttl, msg, msg_len); +} + +int mesh_model_pub_set(struct mesh_node *node, uint16_t addr, uint32_t id, + const uint8_t *pub_addr, uint16_t idx, bool cred_flag, + uint8_t ttl, uint8_t period, uint8_t retransmit, + bool b_virt, uint16_t *dst) +{ + struct mesh_model *mod; + int status; + + mod = find_model(node, addr, id, &status); + if (!mod) + return status; + + if (id == CONFIG_SRV_MODEL || id == CONFIG_CLI_MODEL) + return MESH_STATUS_INVALID_PUB_PARAM; + + if (!appkey_have_key(node_get_net(node), idx)) + return MESH_STATUS_INVALID_APPKEY; + + /* + * If the publication address is set to unassigned address value, + * remove the publication + */ + if (!b_virt && IS_UNASSIGNED(l_get_le16(pub_addr))) { + remove_pub(node, mod); + return MESH_STATUS_SUCCESS; + } + + status = set_pub(mod, pub_addr, idx, cred_flag, ttl, period, retransmit, + b_virt, dst); + + if (status != MESH_STATUS_SUCCESS) + return status; + + if (!mod->cbs) + /* External model */ + config_update_model_pub_period(node, mod->ele_idx, id, + pub_period_to_ms(period)); + else + /* Internal model, call registered callbacks */ + mod->cbs->pub(mod->pub); + + return MESH_STATUS_SUCCESS; +} + +struct mesh_model_pub *mesh_model_pub_get(struct mesh_node *node, uint16_t addr, + uint32_t mod_id, int *status) +{ + struct mesh_model *mod; + + mod = find_model(node, addr, mod_id, status); + if (!mod) + return NULL; + + return mod->pub; +} + +void mesh_model_free(void *data) +{ + struct mesh_model *mod = data; + + l_queue_destroy(mod->bindings, NULL); + l_queue_destroy(mod->subs, NULL); + l_queue_destroy(mod->virtuals, unref_virt); + l_free(mod->pub); + l_free(mod); +} + +static struct mesh_model *model_new(uint8_t ele_idx, uint32_t id) +{ + struct mesh_model *mod = l_new(struct mesh_model, 1); + + mod->id = id; + mod->ele_idx = ele_idx; + mod->virtuals = l_queue_new(); + return mod; +} + +struct mesh_model *mesh_model_new(uint8_t ele_idx, uint16_t id) +{ + return model_new(ele_idx, id | VENDOR_ID_MASK); +} + +struct mesh_model *mesh_model_vendor_new(uint8_t ele_idx, uint16_t vendor_id, + uint16_t mod_id) +{ + uint32_t id = mod_id | (vendor_id << 16); + + return model_new(ele_idx, id); +} + +/* Internal models only */ +static void restore_model_state(struct mesh_model *mod) +{ + const struct mesh_model_ops *cbs; + const struct l_queue_entry *b; + + cbs = mod->cbs; + if (!cbs) + return; + + if (!l_queue_isempty(mod->bindings) && cbs->bind) { + for (b = l_queue_get_entries(mod->bindings); b; b = b->next) { + if (cbs->bind(L_PTR_TO_UINT(b->data), ACTION_ADD) != + MESH_STATUS_SUCCESS) + break; + } + } + + if (mod->pub && cbs->pub) + cbs->pub(mod->pub); + +} + +uint32_t mesh_model_get_model_id(const struct mesh_model *model) +{ + return model->id; +} + +/* This registers an internal model, i.e. implemented within meshd */ +bool mesh_model_register(struct mesh_node *node, uint8_t ele_idx, + uint32_t mod_id, + const struct mesh_model_ops *cbs, + void *user_data) +{ + struct mesh_model *mod; + int status; + + /* Internal models are always SIG models */ + mod_id = VENDOR_ID_MASK | mod_id; + + mod = get_model(node, ele_idx, mod_id, &status); + if (!mod) + return false; + + mod->cbs = cbs; + mod->user_data = user_data; + + restore_model_state(mod); + + return true; +} + +void mesh_model_app_key_delete(struct mesh_node *node, struct l_queue *models, + uint16_t app_idx) +{ + const struct l_queue_entry *entry = l_queue_get_entries(models); + + for (; entry; entry = entry->next) { + struct mesh_model *model = entry->data; + + model_unbind_idx(node, model, app_idx); + } +} + +int mesh_model_binding_del(struct mesh_node *node, uint16_t addr, uint32_t id, + uint16_t app_idx) +{ + l_debug("0x%x, 0x%x, %d", addr, id, app_idx); + return update_binding(node, addr, id, app_idx, true); +} + +int mesh_model_binding_add(struct mesh_node *node, uint16_t addr, uint32_t id, + uint16_t app_idx) +{ + l_debug("0x%x, 0x%x, %d", addr, id, app_idx); + return update_binding(node, addr, id, app_idx, false); +} + +int mesh_model_get_bindings(struct mesh_node *node, uint16_t addr, uint32_t id, + uint8_t *buf, uint16_t buf_size, uint16_t *size) +{ + int status; + struct mesh_model *mod; + const struct l_queue_entry *entry; + uint16_t n; + uint32_t idx_pair; + int i; + + mod = find_model(node, addr, id, &status); + + if (!mod) { + *size = 0; + return status; + } + + entry = l_queue_get_entries(mod->bindings); + n = 0; + i = 0; + idx_pair = 0; + + for (; entry; entry = entry->next) { + uint16_t app_idx = (uint16_t) (L_PTR_TO_UINT(entry->data)); + + if (!(i & 0x1)) { + idx_pair = app_idx; + } else { + idx_pair <<= 12; + idx_pair += app_idx; + + /* Unlikely, but check for overflow*/ + if ((n + 3) > buf_size) { + l_warn("Binding list too large"); + goto done; + } + + l_put_le32(idx_pair, buf); + buf += 3; + n += 3; + } + + i++; + } + + /* Process the last app key if present */ + if (i & 0x1 && ((n + 2) <= buf_size)) { + l_put_le16(idx_pair, buf); + n += 2; + } + +done: + *size = n; + return MESH_STATUS_SUCCESS; +} + +int mesh_model_sub_get(struct mesh_node *node, uint16_t addr, uint32_t id, + uint8_t *buf, uint16_t buf_size, uint16_t *size) +{ + int status; + int16_t n; + struct mesh_model *mod; + const struct l_queue_entry *entry; + + mod = find_model(node, addr, id, &status); + if (!mod) + return status; + + entry = l_queue_get_entries(mod->subs); + *size = 0; + n = 0; + + for (; entry; entry = entry->next) { + if ((n + 2) > buf_size) + return MESH_STATUS_UNSPECIFIED_ERROR; + + l_put_le16((uint16_t) L_PTR_TO_UINT(entry->data), buf); + buf += 2; + n += 2; + } + + *size = n; + return MESH_STATUS_SUCCESS; +} + +int mesh_model_sub_add(struct mesh_node *node, uint16_t addr, uint32_t id, + const uint8_t *group, bool b_virt, uint16_t *dst) +{ + int status; + struct mesh_model *mod; + + mod = find_model(node, addr, id, &status); + if (!mod) + return status; + + return add_sub(node_get_net(node), mod, group, b_virt, dst); +} + +int mesh_model_sub_ovr(struct mesh_node *node, uint16_t addr, uint32_t id, + const uint8_t *group, bool b_virt, uint16_t *dst) +{ + int status; + struct l_queue *virtuals, *subs; + struct mesh_virtual *virt; + struct mesh_model *mod; + + mod = find_model(node, addr, id, &status); + if (!mod) + return status; + + subs = mod->subs; + virtuals = mod->virtuals; + mod->subs = l_queue_new(); + mod->virtuals = l_queue_new(); + + if (!mod->subs || !mod->virtuals) + return MESH_STATUS_INSUFF_RESOURCES; + + /* + * When overwriting the Subscription List, + * make sure any virtual Publication address is preserved + */ + if (mod->pub && mod->pub->addr >= VIRTUAL_BASE) { + virt = l_queue_find(virtuals, find_virt_by_id, + L_UINT_TO_PTR(mod->pub->addr)); + if (virt) { + virt->ref_cnt++; + l_queue_push_head(mod->virtuals, virt); + } + } + + status = mesh_model_sub_add(node, addr, id, group, b_virt, dst); + + if (status != MESH_STATUS_SUCCESS) { + /* Adding new group failed, so revert to old lists */ + l_queue_destroy(mod->subs, NULL); + mod->subs = subs; + l_queue_destroy(mod->virtuals, unref_virt); + mod->virtuals = virtuals; + } else { + const struct l_queue_entry *entry; + struct mesh_net *net = node_get_net(node); + + entry = l_queue_get_entries(subs); + + for (; entry; entry = entry->next) + mesh_net_dst_unreg(net, + (uint16_t) L_PTR_TO_UINT(entry->data)); + + /* Destroy old lists */ + l_queue_destroy(subs, NULL); + l_queue_destroy(virtuals, unref_virt); + } + + return status; +} + +int mesh_model_sub_del(struct mesh_node *node, uint16_t addr, uint32_t id, + const uint8_t *group, bool b_virt, uint16_t *dst) +{ + int status; + uint16_t grp; + struct mesh_model *mod; + + mod = find_model(node, addr, id, &status); + if (!mod) + return status; + + if (b_virt) { + struct mesh_virtual *virt; + + virt = l_queue_find(mod->virtuals, find_virt_by_label, group); + if (virt) { + l_queue_remove(mod->virtuals, virt); + grp = virt->addr; + unref_virt(virt); + } else { + if (!mesh_crypto_virtual_addr(group, &grp)) + return MESH_STATUS_STORAGE_FAIL; + } + } else { + grp = l_get_le16(group); + } + + *dst = grp; + + if (l_queue_remove(mod->subs, L_UINT_TO_PTR(grp))) + mesh_net_dst_unreg(node_get_net(node), grp); + + return MESH_STATUS_SUCCESS; +} + +int mesh_model_sub_del_all(struct mesh_node *node, uint16_t addr, uint32_t id) +{ + int status; + struct mesh_model *mod; + const struct l_queue_entry *entry; + struct mesh_net *net = node_get_net(node); + + mod = find_model(node, addr, id, &status); + if (!mod) + return status; + + entry = l_queue_get_entries(mod->subs); + + for (; entry; entry = entry->next) + mesh_net_dst_unreg(net, (uint16_t) L_PTR_TO_UINT(entry->data)); + + l_queue_destroy(mod->subs, NULL); + l_queue_destroy(mod->virtuals, unref_virt); + mod->virtuals = l_queue_new(); + + return MESH_STATUS_SUCCESS; +} + +struct mesh_model *mesh_model_setup(struct mesh_node *node, uint8_t ele_idx, + void *data) +{ + struct mesh_config_model *db_mod = data; + struct mesh_model *mod; + struct mesh_net *net; + struct mesh_config_pub *pub = db_mod->pub; + uint32_t i; + + if (db_mod->num_bindings > MAX_BINDINGS) { + l_warn("Binding list too long %u (max %u)", + db_mod->num_bindings, MAX_BINDINGS); + return NULL; + } + + mod = model_new(ele_idx, db_mod->vendor ? db_mod->id : + db_mod->id | VENDOR_ID_MASK); + + /* Implicitly bind config server model to device key */ + if (db_mod->id == CONFIG_SRV_MODEL) { + + if (ele_idx != PRIMARY_ELE_IDX) + return NULL; + + l_queue_push_head(mod->bindings, + L_UINT_TO_PTR(APP_IDX_DEV_LOCAL)); + return mod; + } + + if (db_mod->id == CONFIG_CLI_MODEL) { + l_queue_push_head(mod->bindings, + L_UINT_TO_PTR(APP_IDX_DEV_LOCAL)); + return mod; + } + + net = node_get_net(node); + + /* Add application key bindings if present */ + if (db_mod->bindings) { + mod->bindings = l_queue_new(); + for (i = 0; i < db_mod->num_bindings; i++) + model_bind_idx(node, mod, db_mod->bindings[i]); + } + + /* Add publication if present */ + if (pub && (pub->virt || !(IS_UNASSIGNED(pub->addr)))) { + uint8_t mod_addr[2]; + uint8_t *pub_addr; + uint8_t retransmit = (pub->count << 5) + + (pub->interval / 50 - 1); + + /* Add publication */ + l_put_le16(pub->addr, &mod_addr); + pub_addr = pub->virt ? pub->virt_addr : mod_addr; + + if (set_pub(mod, pub_addr, pub->idx, pub->credential, pub->ttl, + pub->period, retransmit, pub->virt, NULL) != + MESH_STATUS_SUCCESS) { + mesh_model_free(mod); + return NULL; + } + } + + /* Add subscriptions if present */ + if (!db_mod->subs) + return mod; + + for (i = 0; i < db_mod->num_subs; i++) { + uint16_t group; + uint8_t *src; + + /* + * To keep calculations for virtual label coherent, + * convert to little endian. + */ + l_put_le16(db_mod->subs[i].src.addr, &group); + src = db_mod->subs[i].virt ? db_mod->subs[i].src.virt_addr : + (uint8_t *) &group; + + if (add_sub(net, mod, src, db_mod->subs[i].virt, NULL) != + MESH_STATUS_SUCCESS) { + mesh_model_free(mod); + return NULL; + } + } + + return mod; +} + +uint16_t mesh_model_opcode_set(uint32_t opcode, uint8_t *buf) +{ + if (opcode <= 0x7e) { + buf[0] = opcode; + return 1; + } + + if (opcode >= 0x8000 && opcode <= 0xbfff) { + l_put_be16(opcode, buf); + return 2; + } + + if (opcode >= 0xc00000 && opcode <= 0xffffff) { + buf[0] = (opcode >> 16) & 0xff; + l_put_be16(opcode, buf + 1); + return 3; + } + + l_debug("Illegal Opcode %x", opcode); + return 0; +} + +bool mesh_model_opcode_get(const uint8_t *buf, uint16_t size, + uint32_t *opcode, uint16_t *n) +{ + if (!n || !opcode || size < 1) + return false; + + switch (buf[0] & 0xc0) { + case 0x00: + case 0x40: + /* RFU */ + if (buf[0] == 0x7f) + return false; + + *n = 1; + *opcode = buf[0]; + break; + + case 0x80: + if (size < 2) + return false; + + *n = 2; + *opcode = l_get_be16(buf); + break; + + case 0xc0: + if (size < 3) + return false; + + *n = 3; + *opcode = l_get_be16(buf + 1); + *opcode |= buf[0] << 16; + break; + + default: + print_packet("Bad", buf, size); + return false; + } + + return true; +} + +void model_build_config(void *model, void *msg_builder) +{ + struct l_dbus_message_builder *builder = msg_builder; + struct mesh_model *mod = model; + uint16_t id; + + if (is_internal(mod->id)) + return; + + if (!l_queue_length(mod->subs) && !l_queue_length(mod->virtuals) && + !mod->pub && !l_queue_length(mod->bindings)) + return; + + l_dbus_message_builder_enter_struct(builder, "qa{sv}"); + + /* Model id */ + id = mod->id & 0xffff; + l_dbus_message_builder_append_basic(builder, 'q', &id); + + l_dbus_message_builder_enter_array(builder, "{sv}"); + + /* For vendor models, add vendor id */ + if ((mod->id & VENDOR_ID_MASK) != VENDOR_ID_MASK) { + uint16_t vendor = mod->id >> 16; + dbus_append_dict_entry_basic(builder, "Vendor", "q", &vendor); + } + + /* Model bindings, if present */ + if (l_queue_length(mod->bindings)) + append_dict_uint16_array(builder, mod->bindings, "Bindings"); + + /* Model periodic publication interval, if present */ + if (mod->pub) { + uint32_t period = pub_period_to_ms(mod->pub->period); + dbus_append_dict_entry_basic(builder, "PublicationPeriod", "u", + &period); + } + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_leave_struct(builder); +} + +void mesh_model_init(void) +{ + mesh_virtuals = l_queue_new(); +} + +void mesh_model_cleanup(void) +{ + l_queue_destroy(mesh_virtuals, l_free); + mesh_virtuals = NULL; +} diff --git a/mesh/model.h b/mesh/model.h new file mode 100644 index 0000000..f30ca2e --- /dev/null +++ b/mesh/model.h @@ -0,0 +1,118 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +struct mesh_model; + +#define OP_UNRELIABLE 0x0100 + +#define MAX_BINDINGS 10 +#define MAX_GRP_PER_MOD 10 + +#define VIRTUAL_BASE 0x10000 + +#define OP_MODEL_TEST 0x8000fffe +#define OP_MODEL_INVALID 0x8000ffff + +#define USE_PUB_VALUE 0x00 + +#define ACTION_ADD 1 +#define ACTION_UPDATE 2 +#define ACTION_DELETE 3 + +struct mesh_model_pub { + uint32_t addr; + uint16_t idx; + uint8_t ttl; + uint8_t credential; + uint8_t period; + uint8_t retransmit; +}; + +typedef void (*mesh_model_unregister)(void *user_data); +typedef bool (*mesh_model_recv_cb)(uint16_t src, uint32_t dst, uint16_t unicast, + uint16_t app_idx, uint16_t net_idx, + const uint8_t *data, + uint16_t len, uint8_t ttl, + const void *user_data); +typedef int (*mesh_model_bind_cb)(uint16_t app_idx, int action); +typedef int (*mesh_model_pub_cb)(struct mesh_model_pub *pub); +typedef int (*mesh_model_sub_cb)(uint16_t sub_addr, int action); + +struct mesh_model_ops { + mesh_model_unregister unregister; + mesh_model_recv_cb recv; + mesh_model_bind_cb bind; + mesh_model_pub_cb pub; + mesh_model_sub_cb sub; +}; + +struct mesh_model *mesh_model_new(uint8_t ele_idx, uint16_t mod_id); +struct mesh_model *mesh_model_vendor_new(uint8_t ele_idx, uint16_t vendor_id, + uint16_t mod_id); +void mesh_model_free(void *data); +uint32_t mesh_model_get_model_id(const struct mesh_model *model); +bool mesh_model_register(struct mesh_node *node, uint8_t ele_idx, + uint32_t mod_id, const struct mesh_model_ops *cbs, + void *user_data); +struct mesh_model *mesh_model_setup(struct mesh_node *node, uint8_t ele_idx, + void *data); +struct mesh_model_pub *mesh_model_pub_get(struct mesh_node *node, + uint16_t addr, uint32_t mod_id, int *status); +int mesh_model_pub_set(struct mesh_node *node, uint16_t addr, uint32_t id, + const uint8_t *pub_addr, uint16_t idx, bool cred_flag, + uint8_t ttl, uint8_t period, uint8_t retransmit, + bool b_virt, uint16_t *dst); + +int mesh_model_binding_add(struct mesh_node *node, uint16_t addr, uint32_t id, + uint16_t idx); +int mesh_model_binding_del(struct mesh_node *node, uint16_t addr, uint32_t id, + uint16_t idx); +int mesh_model_get_bindings(struct mesh_node *node, uint16_t addr, uint32_t id, + uint8_t *buf, uint16_t buf_len, uint16_t *size); +int mesh_model_sub_add(struct mesh_node *node, uint16_t addr, uint32_t id, + const uint8_t *grp, bool b_virt, uint16_t *dst); +int mesh_model_sub_del(struct mesh_node *node, uint16_t addr, uint32_t id, + const uint8_t *grp, bool b_virt, uint16_t *dst); +int mesh_model_sub_del_all(struct mesh_node *node, uint16_t addr, uint32_t id); +int mesh_model_sub_ovr(struct mesh_node *node, uint16_t addr, uint32_t id, + const uint8_t *grp, bool b_virt, uint16_t *dst); +int mesh_model_sub_get(struct mesh_node *node, uint16_t addr, uint32_t id, + uint8_t *buf, uint16_t buf_size, uint16_t *size); +uint16_t mesh_model_cfg_blk(uint8_t *pkt); +bool mesh_model_send(struct mesh_node *node, uint16_t src, uint16_t dst, + uint16_t app_idx, uint16_t net_idx, + uint8_t ttl, + const void *msg, uint16_t msg_len); +int mesh_model_publish(struct mesh_node *node, uint32_t mod_id, uint16_t src, + uint8_t ttl, const void *msg, uint16_t msg_len); +bool mesh_model_rx(struct mesh_node *node, bool szmict, uint32_t seq0, + uint32_t seq, uint32_t iv_index, uint8_t ttl, + uint16_t net_idx, uint16_t src, uint16_t dst, + uint8_t key_aid, const uint8_t *data, uint16_t size); +void mesh_model_app_key_generate_new(struct mesh_node *node, uint16_t net_idx); +void mesh_model_app_key_delete(struct mesh_node *node, struct l_queue *models, + uint16_t idx); +struct l_queue *mesh_model_get_appkeys(struct mesh_node *node); +uint16_t mesh_model_opcode_set(uint32_t opcode, uint8_t *buf); +bool mesh_model_opcode_get(const uint8_t *buf, uint16_t size, uint32_t *opcode, + uint16_t *n); +void model_build_config(void *model, void *msg_builder); + +void mesh_model_init(void); +void mesh_model_cleanup(void); diff --git a/mesh/net-keys.c b/mesh/net-keys.c new file mode 100644 index 0000000..5be7e0b --- /dev/null +++ b/mesh/net-keys.c @@ -0,0 +1,322 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "mesh/crypto.h" +#include "mesh/net-keys.h" + +#define BEACON_TYPE_SNB 0x01 +#define KEY_REFRESH 0x01 +#define IV_INDEX_UPDATE 0x02 + +struct net_key { + uint32_t id; + uint16_t ref_cnt; + uint8_t friend_key; + uint8_t nid; + uint8_t master[16]; + uint8_t encrypt[16]; + uint8_t privacy[16]; + uint8_t beacon[16]; + uint8_t network[8]; +}; + +static struct l_queue *keys = NULL; +static uint32_t last_master_id = 0; + +/* To avoid re-decrypting same packet for multiple nodes, cache and check */ +static uint8_t cache_pkt[29]; +static uint8_t cache_plain[29]; +static size_t cache_len; +static size_t cache_plainlen; +static uint32_t cache_id; +static uint32_t cache_iv_index; + +static bool match_master(const void *a, const void *b) +{ + const struct net_key *key = a; + + return (memcmp(key->master, b, sizeof(key->master)) == 0); +} + +static bool match_id(const void *a, const void *b) +{ + const struct net_key *key = a; + uint32_t id = L_PTR_TO_UINT(b); + + return id == key->id; +} + +static bool match_network(const void *a, const void *b) +{ + const struct net_key *key = a; + const uint8_t *network = b; + + return memcmp(key->network, network, sizeof(key->network)) == 0; +} + +/* Key added from Provisioning, NetKey Add or NetKey update */ +uint32_t net_key_add(const uint8_t master[16]) +{ + struct net_key *key = l_queue_find(keys, match_master, master); + uint8_t p[] = {0}; + bool result; + + if (key) { + key->ref_cnt++; + return key->id; + } + + if (!keys) + keys = l_queue_new(); + + key = l_new(struct net_key, 1); + memcpy(key->master, master, 16); + key->ref_cnt++; + result = mesh_crypto_k2(master, p, sizeof(p), &key->nid, key->encrypt, + key->privacy); + if (!result) + goto fail; + + result = mesh_crypto_k3(master, key->network); + if (!result) + goto fail; + + result = mesh_crypto_nkbk(master, key->beacon); + if (!result) + goto fail; + + key->id = ++last_master_id; + l_queue_push_tail(keys, key); + return key->id; + +fail: + l_free(key); + return 0; +} + +uint32_t net_key_frnd_add(uint32_t master_id, uint16_t lpn, uint16_t frnd, + uint16_t lp_cnt, uint16_t fn_cnt) +{ + const struct net_key *key = l_queue_find(keys, match_id, + L_UINT_TO_PTR(master_id)); + struct net_key *frnd_key; + uint8_t p[9] = {0x01}; + bool result; + + if (!key || key->friend_key) + return 0; + + frnd_key = l_new(struct net_key, 1); + + l_put_be16(lpn, p + 1); + l_put_be16(frnd, p + 3); + l_put_be16(lp_cnt, p + 5); + l_put_be16(fn_cnt, p + 7); + + result = mesh_crypto_k2(key->master, p, sizeof(p), &frnd_key->nid, + frnd_key->encrypt, frnd_key->privacy); + + if (!result) { + l_free(frnd_key); + return 0; + } + + frnd_key->friend_key = true; + frnd_key->ref_cnt++; + frnd_key->id = ++last_master_id; + l_queue_push_head(keys, frnd_key); + + return frnd_key->id; +} + +void net_key_unref(uint32_t id) +{ + struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); + + if (key && key->ref_cnt) { + if (--key->ref_cnt == 0) { + l_queue_remove(keys, key); + l_free(key); + } + } +} + +bool net_key_confirm(uint32_t id, const uint8_t *master) +{ + struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); + + if (key) + return memcmp(key->master, master, sizeof(key->master)) == 0; + + return false; +} + +bool net_key_retrieve(uint32_t id, uint8_t *master) +{ + struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); + + if (key) { + memcpy(master, key->master, sizeof(key->master)); + return true; + } + + return false; +} + +static void decrypt_net_pkt(void *a, void *b) +{ + const struct net_key *key = a; + bool result; + + if (cache_id || !key->ref_cnt || (cache_pkt[0] & 0x7f) != key->nid) + return; + + result = mesh_crypto_packet_decode(cache_pkt, cache_len, false, + cache_plain, cache_iv_index, + key->encrypt, key->privacy); + + if (result) { + cache_id = key->id; + if (cache_plain[1] & 0x80) + cache_plainlen = cache_len - 8; + else + cache_plainlen = cache_len - 4; + } +} + +uint32_t net_key_decrypt(uint32_t iv_index, const uint8_t *pkt, size_t len, + uint8_t **plain, size_t *plain_len) +{ + /* If we already successfully decrypted this packet, use cached data */ + if (cache_id && cache_len == len && !memcmp(pkt, cache_pkt, len)) { + /* IV Index must match what was used to decrypt */ + if (cache_iv_index != iv_index) + return 0; + + goto done; + } + + cache_id = 0; + memcpy(cache_pkt, pkt, len); + cache_len = len; + cache_iv_index = iv_index; + + /* Try all network keys known to us */ + l_queue_foreach(keys, decrypt_net_pkt, NULL); + +done: + if (cache_id) { + *plain = cache_plain; + *plain_len = cache_plainlen; + } + + return cache_id; +} + +bool net_key_encrypt(uint32_t id, uint32_t iv_index, uint8_t *pkt, size_t len) +{ + struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); + bool result; + + if (!key) + return false; + + result = mesh_crypto_packet_encode(pkt, len, key->encrypt, iv_index, + key->privacy); + + if (!result) + return false; + + result = mesh_crypto_packet_label(pkt, len, iv_index, key->nid); + + return result; +} + +uint32_t net_key_network_id(const uint8_t network[8]) +{ + struct net_key *key = l_queue_find(keys, match_network, network); + + if (!key) + return 0; + + return key->id; +} + +bool net_key_snb_check(uint32_t id, uint32_t iv_index, bool kr, bool ivu, + uint64_t cmac) +{ + struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); + uint64_t cmac_check; + + if (!key) + return false; + + /* Any behavioral changes must pass CMAC test */ + if (!mesh_crypto_beacon_cmac(key->beacon, key->network, iv_index, kr, + ivu, &cmac_check)) { + l_error("mesh_crypto_beacon_cmac failed"); + return false; + } + + if (cmac != cmac_check) { + l_error("cmac compare failed 0x%16" PRIx64 " != 0x%16" PRIx64, + cmac, cmac_check); + return false; + } + + return true; +} + +bool net_key_snb_compose(uint32_t id, uint32_t iv_index, bool kr, bool ivu, + uint8_t *snb) +{ + struct net_key *key = l_queue_find(keys, match_id, L_UINT_TO_PTR(id)); + uint64_t cmac; + + if (!key) + return false; + + /* Any behavioral changes must pass CMAC test */ + if (!mesh_crypto_beacon_cmac(key->beacon, key->network, iv_index, kr, + ivu, &cmac)) { + l_error("mesh_crypto_beacon_cmac failed"); + return false; + } + + snb[0] = BEACON_TYPE_SNB; + snb[1] = 0; + + if (kr) + snb[1] |= KEY_REFRESH; + + if (ivu) + snb[1] |= IV_INDEX_UPDATE; + + memcpy(snb + 2, key->network, 8); + l_put_be32(iv_index, snb + 10); + l_put_be64(cmac, snb + 14); + + return true; +} diff --git a/mesh/net-keys.h b/mesh/net-keys.h new file mode 100644 index 0000000..d54c521 --- /dev/null +++ b/mesh/net-keys.h @@ -0,0 +1,33 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2019 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. + * + */ + +bool net_key_confirm(uint32_t id, const uint8_t master[16]); +bool net_key_retrieve(uint32_t id, uint8_t *master); +uint32_t net_key_add(const uint8_t master[16]); +uint32_t net_key_frnd_add(uint32_t master_id, uint16_t lpn, uint16_t frnd, + uint16_t lp_cnt, uint16_t fn_cnt); +void net_key_unref(uint32_t id); +uint32_t net_key_decrypt(uint32_t iv_index, const uint8_t *pkt, size_t len, + uint8_t **plain, size_t *plain_len); +bool net_key_encrypt(uint32_t id, uint32_t iv_index, uint8_t *pkt, size_t len); +uint32_t net_key_network_id(const uint8_t network[8]); +bool net_key_snb_check(uint32_t id, uint32_t iv_index, bool kr, bool ivu, + uint64_t cmac); +bool net_key_snb_compose(uint32_t id, uint32_t iv_index, bool kr, bool ivu, + uint8_t *snb); diff --git a/mesh/net.c b/mesh/net.c new file mode 100644 index 0000000..f07de4d --- /dev/null +++ b/mesh/net.c @@ -0,0 +1,3859 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE + +#include + +#include "mesh/mesh-defs.h" +#include "mesh/util.h" +#include "mesh/crypto.h" +#include "mesh/net-keys.h" +#include "mesh/node.h" +#include "mesh/net.h" +#include "mesh/mesh-io.h" +#include "mesh/friend.h" +#include "mesh/mesh-config.h" +#include "mesh/model.h" +#include "mesh/appkey.h" + +#define abs_diff(a, b) ((a) > (b) ? (a) - (b) : (b) - (a)) + +#define IV_IDX_DIFF_RANGE 42 + +/*#define IV_IDX_UPD_MIN (5 * 60) * 5 minute for Testing */ +#define IV_IDX_UPD_MIN (60 * 60 * 96) /* 96 Hours - per Spec */ +#define IV_IDX_UPD_HOLD (IV_IDX_UPD_MIN/2) +#define IV_IDX_UPD_MAX (IV_IDX_UPD_MIN + IV_IDX_UPD_HOLD) + +#define iv_is_updating(net) ((net)->iv_upd_state == IV_UPD_UPDATING) + +#define IV_UPDATE_SEQ_TRIGGER 0x800000 /* Half of Seq-Nums expended */ + +#define SEG_TO 2 +#define MSG_TO 60 + +#define DEFAULT_MIN_DELAY 0 +#define DEFAULT_MAX_DELAY 25 + +#define DEFAULT_TRANSMIT_COUNT 1 +#define DEFAULT_TRANSMIT_INTERVAL 100 + +#define BEACON_INTERVAL_MIN 10 +#define BEACON_INTERVAL_MAX 600 + +#define SAR_KEY(src, seq0) ((((uint32_t)(seq0)) << 16) | (src)) + +enum _relay_advice { + RELAY_NONE, /* Relay not enabled in node */ + RELAY_ALLOWED, /* Relay enabled, msg not to node's unicast */ + RELAY_DISALLOWED, /* Msg was unicast handled by this node */ + RELAY_ALWAYS /* Relay enabled, msg to a group */ +}; + +enum _iv_upd_state { + /* Allows acceptance of any iv_index secure net beacon */ + IV_UPD_INIT, + /* Normal, can transition, accept current or old */ + IV_UPD_NORMAL, + /* Updating proc running, we use old, accept old or new */ + IV_UPD_UPDATING, + /* Normal, can *not* transition, accept current or old iv_index */ + IV_UPD_NORMAL_HOLD, +}; + +struct net_key { + struct mesh_key_set key_set; + unsigned int beacon_id; + uint8_t key[16]; + uint8_t beacon_key[16]; + uint8_t network_id[8]; +}; + +struct mesh_beacon { + struct l_timeout *timeout; + uint32_t ts; + uint16_t observe_period; + uint16_t observed; + uint16_t expected; + uint8_t half_period; + uint8_t beacon[23]; +}; + +struct mesh_subnet { + struct mesh_net *net; + uint16_t idx; + uint32_t net_key_tx; + uint32_t net_key_cur; + uint32_t net_key_upd; + struct mesh_beacon snb; + uint8_t key_refresh; + uint8_t kr_phase; +}; + +struct mesh_net { + struct mesh_io *io; + struct mesh_node *node; + struct mesh_prov *prov; + struct l_queue *app_keys; + unsigned int pkt_id; + unsigned int bea_id; + unsigned int beacon_id; + unsigned int sar_id_next; + + bool friend_enable; + bool beacon_enable; + bool proxy_enable; + bool provisioner; + bool friend_seq; + struct l_timeout *iv_update_timeout; + enum _iv_upd_state iv_upd_state; + + bool iv_update; + uint32_t instant; /* Controller Instant of recent Rx */ + uint32_t iv_index; + uint32_t seq_num; + uint16_t src_addr; + uint16_t last_addr; + uint16_t friend_addr; + uint16_t tx_interval; + uint16_t tx_cnt; + uint8_t chan; /* Channel of recent Rx */ + uint8_t default_ttl; + uint8_t tid; + uint8_t window_accuracy; + + struct { + bool enable; + uint16_t interval; + uint8_t count; + } relay; + + struct mesh_net_heartbeat heartbeat; + + struct l_queue *subnets; + struct l_queue *msg_cache; + struct l_queue *sar_in; + struct l_queue *sar_out; + struct l_queue *frnd_msgs; + struct l_queue *friends; + struct l_queue *destinations; + + uint8_t prov_priv_key[32]; + + /* Unprovisioned Identity */ + char id_name[20]; + uint8_t id_uuid[16]; + + /* Provisioner: unicast address range */ + struct mesh_net_addr_range prov_uni_addr; + + /* Test Data */ + uint8_t prov_rand[16]; + uint8_t test_bd_addr[6]; + struct mesh_net_prov_caps prov_caps; + bool test_mode; +}; + +struct mesh_msg { + uint16_t src; + uint32_t seq; + uint32_t mic; +}; + +struct mesh_sar { + unsigned int id; + struct l_timeout *seg_timeout; + struct l_timeout *msg_timeout; + mesh_net_status_func_t status_func; + void *user_data; + uint32_t flags; + uint32_t last_nak; + uint32_t iv_index; + uint32_t seqAuth; + uint16_t seqZero; + uint16_t app_idx; + uint16_t src; + uint16_t remote; + uint16_t len; + bool szmic; + bool frnd; + bool frnd_cred; + uint8_t ttl; + uint8_t last_seg; + uint8_t key_aid; + uint8_t buf[4]; /* Large enough for ACK-Flags and MIC */ +}; + +struct mesh_destination { + uint16_t dst; + uint16_t ref_cnt; +}; + +struct msg_rx { + const uint8_t *data; + uint32_t iv_index; + uint32_t seq; + uint16_t src; + uint16_t dst; + uint16_t size; + uint8_t tc; + bool done; + bool szmic; + union { + struct { + uint16_t app_idx; + uint8_t key_aid; + } m; + struct { + uint16_t seq0; + } a; + struct { + uint8_t opcode; + } c; + } u; +}; + +struct net_decode { + struct mesh_net *net; + struct mesh_friend *frnd; + struct mesh_key_set *key_set; + uint8_t *packet; + uint32_t iv_index; + uint8_t size; + uint8_t nid; + bool proxy; +}; + +struct net_queue_data { + struct mesh_io_recv_info *info; + struct mesh_net *net; + const uint8_t *data; + uint8_t *out; + size_t out_size; + enum _relay_advice relay_advice; + uint32_t key_id; + uint32_t iv_index; + uint16_t len; +}; + +struct net_beacon_data { + const uint8_t *data; + uint16_t len; +}; + +#define FAST_CACHE_SIZE 8 +static struct l_queue *fast_cache; +static struct l_queue *nets; + +static void net_rx(void *net_ptr, void *user_data); + +static inline struct mesh_subnet *get_primary_subnet(struct mesh_net *net) +{ + return l_queue_peek_head(net->subnets); +} + +static bool match_key_index(const void *a, const void *b) +{ + const struct mesh_subnet *subnet = a; + uint16_t idx = L_PTR_TO_UINT(b); + + return subnet->idx == idx; +} + +static bool match_key_id(const void *a, const void *b) +{ + const struct mesh_subnet *subnet = a; + uint32_t key_id = L_PTR_TO_UINT(b); + + return (key_id == subnet->net_key_cur) || + (key_id == subnet->net_key_upd); +} + +static void idle_mesh_heartbeat_send(void *net) +{ + mesh_net_heartbeat_send(net); +} + +static void trigger_heartbeat(struct mesh_net *net, uint16_t feature, + bool in_use) +{ + struct mesh_net_heartbeat *hb = &net->heartbeat; + + l_info("%s: %4.4x --> %d", __func__, feature, in_use); + if (in_use) { + if (net->heartbeat.features & feature) + return; /* no change */ + + hb->features |= feature; + } else { + if (!(hb->features & feature)) + return; /* no change */ + + hb->features &= ~feature; + } + + if (!(hb->pub_features & feature)) + return; /* not interested in this feature */ + + l_idle_oneshot(idle_mesh_heartbeat_send, net, NULL); + +} + +static bool match_by_friend(const void *a, const void *b) +{ + const struct mesh_friend *frnd = a; + uint16_t dst = L_PTR_TO_UINT(b); + + return frnd->dst == dst; +} + +static void free_friend_internals(struct mesh_friend *frnd) +{ + if (frnd->pkt_cache) + l_queue_destroy(frnd->pkt_cache, l_free); + + if (frnd->grp_list) + l_free(frnd->grp_list); + + frnd->pkt_cache = NULL; + frnd->grp_list = NULL; + net_key_unref(frnd->net_key_cur); + net_key_unref(frnd->net_key_upd); +} + +static void frnd_kr_phase1(void *a, void *b) +{ + struct mesh_friend *frnd = a; + uint32_t key_id = L_PTR_TO_UINT(b); + + frnd->net_key_upd = net_key_frnd_add(key_id, frnd->dst, + frnd->net->src_addr, frnd->lp_cnt, frnd->fn_cnt); +} + +static void frnd_kr_phase2(void *a, void *b) +{ + struct mesh_friend *frnd = a; + + /* + * I think that a Friend should use Old Key as long as possible + * Because a Friend Node will enter Phase 3 before it's LPN. + * Alternatively, the FN could keep the Old Friend Keys until it + * receives it's first Poll using the new keys (?) + */ + + l_info("Use Both KeySet %d && %d for %4.4x", + frnd->net_key_cur, frnd->net_key_upd, frnd->dst); +} + +static void frnd_kr_phase3(void *a, void *b) +{ + struct mesh_friend *frnd = a; + + l_info("Replace KeySet %d with %d for %4.4x", + frnd->net_key_cur, frnd->net_key_upd, frnd->dst); + net_key_unref(frnd->net_key_cur); + frnd->net_key_cur = frnd->net_key_upd; + frnd->net_key_upd = 0; +} + +/* TODO: add net key idx? For now, use primary net key */ +struct mesh_friend *mesh_friend_new(struct mesh_net *net, uint16_t dst, + uint8_t ele_cnt, uint8_t frd, + uint8_t frw, uint32_t fpt, + uint16_t fn_cnt, uint16_t lp_cnt) +{ + struct mesh_subnet *subnet; + struct mesh_friend *frnd = l_queue_find(net->friends, + match_by_friend, L_UINT_TO_PTR(dst)); + + if (frnd) { + /* Kill all timers and empty cache for this friend */ + free_friend_internals(frnd); + l_timeout_remove(frnd->timeout); + frnd->timeout = NULL; + } else { + frnd = l_new(struct mesh_friend, 1); + l_queue_push_head(net->friends, frnd); + } + + /* add _k2 */ + frnd->net = net; + frnd->dst = dst; + frnd->frd = frd; + frnd->frw = frw; + frnd->fn_cnt = fn_cnt; + frnd->lp_cnt = lp_cnt; + frnd->poll_timeout = fpt; + frnd->ele_cnt = ele_cnt; + frnd->pkt_cache = l_queue_new(); + frnd->net_key_upd = 0; + + subnet = get_primary_subnet(net); + /* TODO: the primary key must be present, do we need to add check?. */ + + frnd->net_key_cur = net_key_frnd_add(subnet->net_key_cur, + dst, net->src_addr, + lp_cnt, fn_cnt); + + if (!subnet->net_key_upd) + return frnd; + + frnd->net_key_upd = net_key_frnd_add(subnet->net_key_upd, + dst, net->src_addr, + lp_cnt, fn_cnt); + + return frnd; +} + +void mesh_friend_free(void *data) +{ + struct mesh_friend *frnd = data; + + free_friend_internals(frnd); + l_timeout_remove(frnd->timeout); + l_free(frnd); +} + +bool mesh_friend_clear(struct mesh_net *net, struct mesh_friend *frnd) +{ + bool removed = l_queue_remove(net->friends, frnd); + + free_friend_internals(frnd); + + return removed; +} + +void mesh_friend_sub_add(struct mesh_net *net, uint16_t lpn, uint8_t ele_cnt, + uint8_t grp_cnt, + const uint8_t *list) +{ + uint16_t *new_list; + uint16_t *grp_list; + struct mesh_friend *frnd = l_queue_find(net->friends, + match_by_friend, + L_UINT_TO_PTR(lpn)); + if (!frnd) + return; + + new_list = l_malloc((grp_cnt + frnd->grp_cnt) * sizeof(uint16_t)); + grp_list = frnd->grp_list; + + if (grp_list && frnd->grp_cnt) + memcpy(new_list, grp_list, frnd->grp_cnt * sizeof(uint16_t)); + + memcpy(&new_list[frnd->grp_cnt], list, grp_cnt * sizeof(uint16_t)); + l_free(grp_list); + frnd->ele_cnt = ele_cnt; + frnd->grp_list = new_list; + frnd->grp_cnt += grp_cnt; +} + +void mesh_friend_sub_del(struct mesh_net *net, uint16_t lpn, + uint8_t cnt, + const uint8_t *del_list) +{ + uint16_t *grp_list; + int16_t i, grp_cnt; + size_t cnt16 = cnt * sizeof(uint16_t); + struct mesh_friend *frnd = l_queue_find(net->friends, + match_by_friend, + L_UINT_TO_PTR(lpn)); + if (!frnd) + return; + + grp_cnt = frnd->grp_cnt; + grp_list = frnd->grp_list; + + while (cnt-- && grp_cnt) { + cnt16 -= sizeof(uint16_t); + for (i = grp_cnt - 1; i >= 0; i--) { + if (l_get_le16(del_list + cnt16) == grp_list[i]) { + grp_cnt--; + memcpy(&grp_list[i], &grp_list[i + 1], + (grp_cnt - i) * sizeof(uint16_t)); + break; + } + } + } + + frnd->grp_cnt = grp_cnt; + + if (!grp_cnt) { + l_free(frnd->grp_list); + frnd->grp_list = NULL; + } +} + +uint32_t mesh_net_next_seq_num(struct mesh_net *net) +{ + uint32_t seq = net->seq_num++; + + node_set_sequence_number(net->node, net->seq_num); + return seq; +} + +static struct mesh_sar *mesh_sar_new(size_t len) +{ + size_t size = sizeof(struct mesh_sar) + len; + struct mesh_sar *sar; + + sar = l_malloc(size); + + memset(sar, 0, size); + + return sar; +} + +static void mesh_sar_free(void *data) +{ + struct mesh_sar *sar = data; + + if (!sar) + return; + + l_timeout_remove(sar->seg_timeout); + l_timeout_remove(sar->msg_timeout); + l_free(sar); +} + +static void mesh_msg_free(void *data) +{ + struct mesh_msg *msg = data; + + l_free(msg); +} + +static void subnet_free(void *data) +{ + struct mesh_subnet *subnet = data; + + net_key_unref(subnet->net_key_cur); + net_key_unref(subnet->net_key_upd); + l_free(subnet); +} + +static void lpn_process_beacon(void *user_data, const void *data, uint8_t size, + int8_t rssi); + +static struct mesh_subnet *subnet_new(struct mesh_net *net, uint16_t idx) +{ + struct mesh_subnet *subnet; + + subnet = l_new(struct mesh_subnet, 1); + if (!subnet) + return NULL; + + subnet->net = net; + subnet->idx = idx; + subnet->snb.beacon[0] = MESH_AD_TYPE_BEACON; + return subnet; +} + +static bool create_secure_beacon(struct mesh_net *net, + struct mesh_subnet *subnet, + uint8_t *beacon_data) +{ + bool kr = (subnet->kr_phase == KEY_REFRESH_PHASE_TWO); + + return net_key_snb_compose(subnet->net_key_tx, net->iv_index, + kr, net->iv_update, beacon_data); +} + +static void send_network_beacon(struct mesh_subnet *subnet, + struct mesh_net *net) +{ + struct mesh_io_send_info info = { + .type = MESH_IO_TIMING_TYPE_GENERAL, + .u.gen.interval = net->tx_interval, + .u.gen.cnt = 1, + .u.gen.min_delay = DEFAULT_MIN_DELAY, + .u.gen.max_delay = DEFAULT_MAX_DELAY + }; + + l_info("Send SNB on network %3.3x", subnet->idx); + mesh_io_send(net->io, &info, subnet->snb.beacon, + sizeof(subnet->snb.beacon)); +} + +static void network_beacon_timeout(struct l_timeout *timeout, void *user_data) +{ + struct mesh_subnet *subnet = user_data; + uint32_t interval; + + send_network_beacon(subnet, subnet->net); + + if (!subnet->snb.half_period) { + l_debug("beacon TO period %d, observed %d, expected %d", + subnet->snb.observe_period, + subnet->snb.observed, + subnet->snb.expected); + interval = subnet->snb.observe_period * + (subnet->snb.observed + 1) / subnet->snb.expected; + subnet->snb.observe_period = interval * 2; + subnet->snb.expected = subnet->snb.observe_period / 10; + subnet->snb.observed = 0; + } else + interval = subnet->snb.observe_period / 2; + + if (interval < BEACON_INTERVAL_MIN) + interval = BEACON_INTERVAL_MIN; + + if (interval > BEACON_INTERVAL_MAX) + interval = BEACON_INTERVAL_MAX; + + subnet->snb.ts = get_timestamp_secs(); + subnet->snb.half_period ^= 1; + l_timeout_modify(timeout, interval); +} + +static void start_network_beacon(void *a, void *b) +{ + struct mesh_subnet *subnet = a; + struct mesh_net *net = b; + + if (!net->beacon_enable) { + if (subnet->snb.timeout) + l_timeout_remove(subnet->snb.timeout); + subnet->snb.timeout = NULL; + return; + } + + /* If timeout is active, let it run it's course */ + if (subnet->snb.timeout) + return; + + send_network_beacon(subnet, subnet->net); + + subnet->snb.ts = get_timestamp_secs(); + subnet->snb.expected = 2; + subnet->snb.observed = 0; + subnet->snb.half_period = 1; + subnet->snb.observe_period = BEACON_INTERVAL_MIN * 2; + + subnet->snb.timeout = l_timeout_create(BEACON_INTERVAL_MIN, + network_beacon_timeout, subnet, NULL); +} + +struct mesh_net *mesh_net_new(struct mesh_node *node) +{ + struct mesh_net *net; + + net = l_new(struct mesh_net, 1); + + net->node = node; + net->pkt_id = 0; + net->bea_id = 0; + + net->beacon_enable = true; + net->proxy_enable = false; + net->relay.enable = false; + + net->seq_num = 0x000000; + net->src_addr = 0x0000; + net->default_ttl = 0x7f; + + net->provisioner = false; + + net->test_mode = false; + memset(&net->prov_caps, 0, sizeof(net->prov_caps)); + net->prov_caps.algorithms = 1; + + net->tx_cnt = DEFAULT_TRANSMIT_COUNT; + net->tx_interval = DEFAULT_TRANSMIT_INTERVAL; + + net->subnets = l_queue_new(); + net->msg_cache = l_queue_new(); + net->sar_in = l_queue_new(); + net->sar_out = l_queue_new(); + net->frnd_msgs = l_queue_new(); + net->friends = l_queue_new(); + net->destinations = l_queue_new(); + net->app_keys = l_queue_new(); + + memset(&net->heartbeat, 0, sizeof(net->heartbeat)); + + if (!nets) + nets = l_queue_new(); + + if (!fast_cache) + fast_cache = l_queue_new(); + + return net; +} + +void mesh_net_free(struct mesh_net *net) +{ + if (!net) + return; + + l_queue_destroy(net->subnets, subnet_free); + l_queue_destroy(net->msg_cache, mesh_msg_free); + l_queue_destroy(net->sar_in, mesh_sar_free); + l_queue_destroy(net->sar_out, mesh_sar_free); + l_queue_destroy(net->frnd_msgs, l_free); + l_queue_destroy(net->friends, mesh_friend_free); + l_queue_destroy(net->destinations, l_free); + l_queue_destroy(net->app_keys, appkey_key_free); + + l_free(net); +} + +bool mesh_net_set_seq_num(struct mesh_net *net, uint32_t seq) +{ + if (!net) + return false; + + net->seq_num = seq; + node_set_sequence_number(net->node, net->seq_num); + + return true; +} + +bool mesh_net_set_default_ttl(struct mesh_net *net, uint8_t ttl) +{ + if (!net) + return false; + + net->default_ttl = ttl; + + return true; +} + +uint32_t mesh_net_get_seq_num(struct mesh_net *net) +{ + if (!net) + return 0; + + return net->seq_num; +} + +uint8_t mesh_net_get_default_ttl(struct mesh_net *net) +{ + if (!net) + return 0; + + return net->default_ttl; +} + +uint16_t mesh_net_get_address(struct mesh_net *net) +{ + if (!net) + return 0; + + return net->src_addr; +} + +bool mesh_net_register_unicast(struct mesh_net *net, + uint16_t address, uint8_t num_ele) +{ + if (!net || !IS_UNICAST(address) || !num_ele) + return false; + + net->src_addr = address; + net->last_addr = address + num_ele - 1; + if (net->last_addr < net->src_addr) + return false; + + do { + mesh_net_dst_reg(net, address); + address++; + num_ele--; + } while (num_ele > 0); + + return true; +} + +uint8_t mesh_net_get_num_ele(struct mesh_net *net) +{ + if (!net) + return 0; + + return net->last_addr - net->src_addr + 1; +} + +bool mesh_net_set_proxy_mode(struct mesh_net *net, bool enable) +{ + if (!net) + return false; + + /* No support for proxy yet */ + if (enable) { + l_error("Proxy not supported!"); + return false; + } + + trigger_heartbeat(net, FEATURE_PROXY, enable); + return true; +} + +bool mesh_net_set_friend_mode(struct mesh_net *net, bool enable) +{ + if (!net) + return false; + + if (net->friend_enable && !enable) + l_queue_clear(net->friends, mesh_friend_free); + + net->friend_enable = enable; + trigger_heartbeat(net, FEATURE_FRIEND, enable); + return true; +} + +bool mesh_net_set_relay_mode(struct mesh_net *net, bool enable, + uint8_t cnt, uint8_t interval) +{ + if (!net) + return false; + + net->relay.enable = enable; + net->relay.count = cnt; + net->relay.interval = interval; + trigger_heartbeat(net, FEATURE_RELAY, enable); + return true; +} + +struct mesh_net_prov_caps *mesh_net_prov_caps_get(struct mesh_net *net) +{ + if (net) + return &net->prov_caps; + + return NULL; +} + +char *mesh_net_id_name(struct mesh_net *net) +{ + if (net && net->id_name[0]) + return net->id_name; + + return NULL; +} + +bool mesh_net_id_uuid_set(struct mesh_net *net, uint8_t uuid[16]) +{ + if (!net) + return false; + + memcpy(net->id_uuid, uuid, 16); + + return true; +} + +uint8_t *mesh_net_priv_key_get(struct mesh_net *net) +{ + if (net) + return net->prov_priv_key; + + return NULL; +} + +bool mesh_net_priv_key_set(struct mesh_net *net, uint8_t key[32]) +{ + if (!net) + return false; + + memcpy(net->prov_priv_key, key, 32); + return true; +} + +uint8_t *mesh_net_test_addr(struct mesh_net *net) +{ + const uint8_t zero_addr[] = {0, 0, 0, 0, 0, 0}; + + if (net && memcmp(net->test_bd_addr, zero_addr, 6)) + return net->test_bd_addr; + + return NULL; +} + +uint8_t *mesh_net_prov_rand(struct mesh_net *net) +{ + if (net) + return net->prov_rand; + + return NULL; +} + +uint16_t mesh_net_prov_uni(struct mesh_net *net, uint8_t ele_cnt) +{ + uint16_t uni; + uint16_t next; + + if (!net) + return 0; + + next = net->prov_uni_addr.next + ele_cnt; + if (next > 0x8000 || next > net->prov_uni_addr.high) + return UNASSIGNED_ADDRESS; + + uni = net->prov_uni_addr.next; + net->prov_uni_addr.next = next; + + return uni; +} + +bool mesh_net_test_mode(struct mesh_net *net) +{ + if (net) + return net->test_mode; + + return false; +} + +int mesh_net_get_identity_mode(struct mesh_net *net, uint16_t idx, + uint8_t *mode) +{ + struct mesh_subnet *subnet; + + if (!net) + return MESH_STATUS_UNSPECIFIED_ERROR; + + subnet = l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)); + if (!subnet) + return MESH_STATUS_INVALID_NETKEY; + + /* Currently, proxy mode is not supported */ + *mode = MESH_MODE_UNSUPPORTED; + + return MESH_STATUS_SUCCESS; +} + +int mesh_net_del_key(struct mesh_net *net, uint16_t idx) +{ + struct mesh_subnet *subnet; + + if (!net) + return MESH_STATUS_UNSPECIFIED_ERROR; + + /* Cannot remove primary key */ + if (l_queue_length(net->subnets) <= 1) + return MESH_STATUS_CANNOT_REMOVE; + + subnet = l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)); + if (!subnet) + return MESH_STATUS_CANNOT_REMOVE; + + /* Delete associated app keys */ + appkey_delete_bound_keys(net, idx); + + /* Disable hearbeat publication on this subnet */ + if (idx == net->heartbeat.pub_net_idx) + net->heartbeat.pub_dst = UNASSIGNED_ADDRESS; + + /* TODO: cancel beacon_enable on this subnet */ + + l_queue_remove(net->subnets, subnet); + subnet_free(subnet); + + if (!mesh_config_net_key_del(node_config_get(net->node), idx)) + return MESH_STATUS_STORAGE_FAIL; + + return MESH_STATUS_SUCCESS; +} + +static struct mesh_subnet *add_key(struct mesh_net *net, uint16_t idx, + const uint8_t *value) +{ + struct mesh_subnet *subnet; + + subnet = subnet_new(net, idx); + if (!subnet) + return NULL; + + subnet->net_key_tx = subnet->net_key_cur = net_key_add(value); + if (!subnet->net_key_cur) { + l_free(subnet); + return NULL; + } + + if (!create_secure_beacon(net, subnet, subnet->snb.beacon + 1)) { + subnet_free(subnet); + return NULL; + } + + l_queue_push_tail(net->subnets, subnet); + + return subnet; +} + +/* + * This function is called when Configuration Server Model receives + * a NETKEY_ADD command + */ +int mesh_net_add_key(struct mesh_net *net, uint16_t idx, const uint8_t *value) +{ + struct mesh_subnet *subnet; + + subnet = l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)); + + if (subnet) { + if (net_key_confirm(subnet->net_key_cur, value)) + return MESH_STATUS_SUCCESS; + else + return MESH_STATUS_IDX_ALREADY_STORED; + } + + subnet = add_key(net, idx, value); + if (!subnet) + return MESH_STATUS_INSUFF_RESOURCES; + + if (!mesh_config_net_key_add(node_config_get(net->node), idx, value)) { + l_queue_remove(net->subnets, subnet); + subnet_free(subnet); + return MESH_STATUS_STORAGE_FAIL; + } + + if (net->io) + start_network_beacon(subnet, net); + + return MESH_STATUS_SUCCESS; +} + +void mesh_net_flush_msg_queues(struct mesh_net *net) +{ + l_queue_clear(net->msg_cache, mesh_msg_free); +} + +uint32_t mesh_net_get_iv_index(struct mesh_net *net) +{ + if (!net) + return 0xffffffff; + + return net->iv_index - net->iv_update; +} + +/* TODO: net key index? */ +void mesh_net_get_snb_state(struct mesh_net *net, uint8_t *flags, + uint32_t *iv_index) +{ + struct mesh_subnet *subnet; + + if (!net || !flags || !iv_index) + return; + + *iv_index = net->iv_index; + *flags = (net->iv_upd_state == IV_UPD_UPDATING) ? 0x02 : 0x00; + + subnet = get_primary_subnet(net); + if (subnet) + *flags |= subnet->key_refresh ? 0x01 : 0x00; +} + +bool mesh_net_get_key(struct mesh_net *net, bool new_key, uint16_t idx, + uint32_t *key_id) +{ + struct mesh_subnet *subnet; + + if (!net) + return false; + + subnet = l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)); + if (!subnet) + return false; + + if (!new_key) { + *key_id = subnet->net_key_cur; + return true; + } + + if (!subnet->net_key_upd) + return false; + + *key_id = subnet->net_key_upd; + return true; +} + +bool mesh_net_key_list_get(struct mesh_net *net, uint8_t *buf, uint16_t *size) +{ + const struct l_queue_entry *entry; + uint16_t n, buf_size; + + if (!net || !buf || !size) + return false; + + buf_size = *size; + if (buf_size < l_queue_length(net->subnets) * 2) + return false; + + n = 0; + entry = l_queue_get_entries(net->subnets); + + for (; entry; entry = entry->next) { + struct mesh_subnet *subnet = entry->data; + + l_put_le16(subnet->idx, buf); + n += 2; + } + + *size = n; + return true; +} + +bool mesh_net_get_frnd_seq(struct mesh_net *net) +{ + if (!net) + return false; + + return net->friend_seq; +} + +void mesh_net_set_frnd_seq(struct mesh_net *net, bool seq) +{ + if (!net) + return; + + net->friend_seq = seq; +} + +static bool match_cache(const void *a, const void *b) +{ + const struct mesh_msg *msg = a; + const struct mesh_msg *tst = b; + + if (msg->seq != tst->seq || msg->mic != tst->mic || + msg->src != tst->src) + return false; + + return true; +} + +static bool msg_in_cache(struct mesh_net *net, uint16_t src, uint32_t seq, + uint32_t mic) +{ + struct mesh_msg *msg; + struct mesh_msg tst = { + .src = src, + .seq = seq, + .mic = mic, + }; + + msg = l_queue_remove_if(net->msg_cache, match_cache, &tst); + + if (msg) { + l_debug("Supressing duplicate %4.4x + %6.6x + %8.8x", + src, seq, mic); + l_queue_push_head(net->msg_cache, msg); + return true; + } + + msg = l_new(struct mesh_msg, 1); + *msg = tst; + l_queue_push_head(net->msg_cache, msg); + l_debug("Add %4.4x + %6.6x + %8.8x", src, seq, mic); + + if (l_queue_length(net->msg_cache) > MSG_CACHE_SIZE) { + msg = l_queue_peek_tail(net->msg_cache); + /* Remove Tail (oldest msg in cache) */ + l_debug("Remove %4.4x + %6.6x + %8.8x", + msg->src, msg->seq, msg->mic); + if (l_queue_remove(net->msg_cache, msg)) + l_free(msg); + } + + return false; +} + +static bool match_sar_seq0(const void *a, const void *b) +{ + const struct mesh_sar *sar = a; + uint16_t seqZero = L_PTR_TO_UINT(b); + + return sar->seqZero == seqZero; +} + +static bool match_sar_remote(const void *a, const void *b) +{ + const struct mesh_sar *sar = a; + uint16_t remote = L_PTR_TO_UINT(b); + + return sar->remote == remote; +} + +static bool match_msg_timeout(const void *a, const void *b) +{ + const struct mesh_sar *sar = a; + const struct l_timeout *msg_timeout = b; + + return sar->msg_timeout == msg_timeout; +} + +static bool match_seg_timeout(const void *a, const void *b) +{ + const struct mesh_sar *sar = a; + const struct l_timeout *seg_timeout = b; + + return sar->seg_timeout == seg_timeout; +} + +static bool match_dest_dst(const void *a, const void *b) +{ + const struct mesh_destination *dest = a; + uint16_t dst = L_PTR_TO_UINT(b); + + return dst == dest->dst; +} + +static bool match_frnd_dst(const void *a, const void *b) +{ + const struct mesh_friend *frnd = a; + uint16_t dst = L_PTR_TO_UINT(b); + int16_t i, grp_cnt = frnd->grp_cnt; + uint16_t *grp_list = frnd->grp_list; + + /* + * Determine if this message is for this friends unicast + * address, and/or one of it's group/virtual addresses + */ + if (dst >= frnd->dst && dst < (frnd->dst + frnd->ele_cnt)) + return true; + + if (!(dst & 0x8000)) + return false; + + for (i = 0; i < grp_cnt; i++) { + if (dst == grp_list[i]) + return true; + } + + return false; +} + +static bool is_lpn_friend(struct mesh_net *net, uint16_t addr, bool frnd) +{ + void *tst; + + if (!frnd) + return false; + + tst = l_queue_find(net->friends, match_frnd_dst, L_UINT_TO_PTR(addr)); + + return tst != NULL; +} + +static bool is_us(struct mesh_net *net, uint16_t addr, bool src) +{ + void *tst; + + if (IS_ALL_NODES(addr)) + return true; + + if (addr == FRIENDS_ADDRESS) + return net->friend_enable; + + if (addr == RELAYS_ADDRESS) + return net->relay.enable; + + if (addr == PROXIES_ADDRESS) + return net->proxy_enable; + + if (addr >= net->src_addr && addr <= net->last_addr) + return true; + + tst = l_queue_find(net->destinations, match_dest_dst, + L_UINT_TO_PTR(addr)); + + if (tst == NULL && !src) + tst = l_queue_find(net->friends, match_frnd_dst, + L_UINT_TO_PTR(addr)); + + return tst != NULL; +} + +static struct mesh_friend_msg *mesh_friend_msg_new(uint8_t seg_max) +{ + struct mesh_friend_msg *frnd_msg; + + if (seg_max) { + size_t size = sizeof(struct mesh_friend_msg) - + sizeof(struct mesh_friend_seg_one); + + size += (seg_max + 1) * sizeof(struct mesh_friend_seg_12); + frnd_msg = l_malloc(size); + } else + frnd_msg = l_new(struct mesh_friend_msg, 1); + + + return frnd_msg; +} + + +static bool match_ack(const void *a, const void *b) +{ + const struct mesh_friend_msg *old = a; + const struct mesh_friend_msg *rx = b; + uint32_t old_hdr; + uint32_t new_hdr; + + /* Determine if old pkt is ACK to same SAR message that new ACK is */ + if (!old->ctl || old->src != rx->src) + return false; + + /* Check the quickest items first before digging deeper */ + old_hdr = old->u.one[0].hdr & HDR_ACK_MASK; + new_hdr = rx->u.one[0].hdr & HDR_ACK_MASK; + + return old_hdr == new_hdr; +} + +static void enqueue_friend_pkt(void *a, void *b) +{ + struct mesh_friend *frnd = a; + struct mesh_friend_msg *pkt, *rx = b; + size_t size; + int16_t i; + + if (rx->done) + return; + + /* + * Determine if this message is for this friends unicast + * address, and/or one of it's group/virtual addresses + */ + if (rx->dst >= frnd->dst && (rx->dst - frnd->dst) < frnd->ele_cnt) { + rx->done = true; + goto enqueue; + } + + if (!(rx->dst & 0x8000)) + return; + + if (!IS_ALL_NODES(rx->dst)) { + for (i = 0; i < frnd->grp_cnt; i++) { + if (rx->dst == frnd->grp_list[i]) + goto enqueue; + } + return; + } + +enqueue: + /* Special handling for Seg Ack -- Only one per message queue */ + if (((rx->u.one[0].hdr >> OPCODE_HDR_SHIFT) & OPCODE_MASK) == + NET_OP_SEG_ACKNOWLEDGE) { + void *old_head = l_queue_peek_head(frnd->pkt_cache); + /* Suppress duplicate ACKs */ + do { + void *old = l_queue_remove_if(frnd->pkt_cache, + match_ack, rx); + + if (old) { + if (old_head == old) { + /* + * If we are discarding head for any + * reason, reset FRND SEQ + */ + frnd->last = frnd->seq; + } + + l_free(old); + } else + break; + + } while (true); + } + + l_debug("%s for %4.4x from %4.4x ttl: %2.2x (seq: %6.6x) (ctl: %d)", + __func__, frnd->dst, rx->src, rx->ttl, + rx->u.one[0].seq, rx->ctl); + + if (rx->cnt_in) { + size = sizeof(struct mesh_friend_msg) - + sizeof(struct mesh_friend_seg_one); + size += (rx->cnt_in + 1) * sizeof(struct mesh_friend_seg_12); + } else + size = sizeof(struct mesh_friend_msg); + + pkt = l_malloc(size); + memcpy(pkt, rx, size); + + l_queue_push_tail(frnd->pkt_cache, pkt); + + if (l_queue_length(frnd->pkt_cache) > FRND_CACHE_MAX) { + /* + * TODO: Guard against popping UPDATE packets + * (disallowed per spec) + */ + pkt = l_queue_pop_head(frnd->pkt_cache); + l_free(pkt); + frnd->last = frnd->seq; + } +} + +static void enqueue_update(void *a, void *b) +{ + struct mesh_friend *frnd = a; + struct mesh_friend_msg *pkt = b; + + pkt->dst = frnd->dst; + pkt->done = false; + enqueue_friend_pkt(frnd, pkt); +} + +static uint32_t seq_auth(uint32_t seq, uint16_t seqZero) +{ + uint32_t seqAuth = seqZero & SEQ_ZERO_MASK; + + seqAuth |= seq & (~SEQ_ZERO_MASK); + if (seqAuth > seq) + seqAuth -= (SEQ_ZERO_MASK + 1); + + return seqAuth; +} + +static bool friend_packet_queue(struct mesh_net *net, + uint32_t iv_index, + bool ctl, uint8_t ttl, + uint32_t seq, + uint16_t src, uint16_t dst, + uint32_t hdr, + const uint8_t *data, uint16_t size) +{ + struct mesh_friend_msg *frnd_msg; + uint8_t seg_max = SEG_TOTAL(hdr); + bool ret; + + if (seg_max && !IS_SEGMENTED(hdr)) + return false; + + frnd_msg = mesh_friend_msg_new(seg_max); + + if (IS_SEGMENTED(hdr)) { + uint32_t seqAuth = seq_auth(seq, hdr >> SEQ_ZERO_HDR_SHIFT); + uint8_t i; + + for (i = 0; i <= seg_max; i++) { + memcpy(frnd_msg->u.s12[i].data, data, 12); + frnd_msg->u.s12[i].hdr = hdr; + frnd_msg->u.s12[i].seq = seqAuth + i; + data += 12; + hdr += (1 << SEGO_HDR_SHIFT); + } + frnd_msg->u.s12[seg_max].seq = seq; + frnd_msg->cnt_in = seg_max; + frnd_msg->last_len = size % 12; + if (!frnd_msg->last_len) + frnd_msg->last_len = 12; + } else { + uint8_t opcode = hdr >> OPCODE_HDR_SHIFT; + + if (ctl && opcode != NET_OP_SEG_ACKNOWLEDGE) { + + /* Don't cache Friend Ctl opcodes */ + if (FRND_OPCODE(opcode)) { + l_free(frnd_msg); + return false; + } + + memcpy(frnd_msg->u.one[0].data + 1, data, size); + frnd_msg->last_len = size + 1; + frnd_msg->u.one[0].data[0] = opcode; + } else { + memcpy(frnd_msg->u.one[0].data, data, size); + frnd_msg->last_len = size; + } + frnd_msg->u.one[0].hdr = hdr; + frnd_msg->u.one[0].seq = seq; + } + + frnd_msg->iv_index = iv_index; + frnd_msg->src = src; + frnd_msg->dst = dst; + frnd_msg->ctl = ctl; + frnd_msg->ttl = ttl; + + /* Re-Package into Friend Delivery payload */ + l_queue_foreach(net->friends, enqueue_friend_pkt, frnd_msg); + ret = frnd_msg->done; + + /* TODO Optimization(?): Unicast messages keep this buffer */ + l_free(frnd_msg); + + return ret; +} + +static void friend_ack_rxed(struct mesh_net *net, uint32_t iv_index, + uint32_t seq, + uint16_t src, uint16_t dst, + const uint8_t *pkt) +{ + uint32_t hdr = l_get_be32(pkt) & + ((SEQ_ZERO_MASK << SEQ_ZERO_HDR_SHIFT) | /* Preserve SeqZero */ + (true << RELAY_HDR_SHIFT)); /* Preserve Relay bit */ + uint32_t flags = l_get_be32(pkt + 3); + struct mesh_friend_msg frnd_ack = { + .ctl = true, + .iv_index = iv_index, + .src = src, + .dst = dst, + .last_len = sizeof(flags), + .u.one[0].seq = seq, + .done = false, + }; + + hdr |= NET_OP_SEG_ACKNOWLEDGE << OPCODE_HDR_SHIFT; + frnd_ack.u.one[0].hdr = hdr; + l_put_be32(flags, frnd_ack.u.one[0].data); + l_queue_foreach(net->friends, enqueue_friend_pkt, &frnd_ack); +} + +static bool send_seg(struct mesh_net *net, struct mesh_sar *msg, uint8_t seg); + +static void send_frnd_ack(struct mesh_net *net, uint16_t src, uint16_t dst, + uint32_t hdr, uint32_t flags) +{ + uint32_t expected; + uint8_t msg[7]; + + /* We don't ACK from multicast destinations */ + if (src & 0x8000) + return; + + /* Calculate the "Full ACK" mask */ + expected = 0xffffffff >> (31 - SEG_TOTAL(hdr)); + + /* Clear Hdr bits that don't apply to Seg ACK */ + hdr &= ~((true << SEG_HDR_SHIFT) | + (OPCODE_MASK << OPCODE_HDR_SHIFT) | + (true << SZMIC_HDR_SHIFT) | + (SEG_MASK << SEGO_HDR_SHIFT) | + (SEG_MASK << SEGN_HDR_SHIFT)); + + hdr |= NET_OP_SEG_ACKNOWLEDGE << OPCODE_HDR_SHIFT; + hdr |= true << RELAY_HDR_SHIFT; + + /* Clear all unexpected bits */ + flags &= expected; + + l_put_be32(hdr, msg); + l_put_be32(flags, msg + 3); + + l_info("Send Friend ACK to Segs: %8.8x", flags); + + if (is_lpn_friend(net, dst, true)) { + /* If we are acking our LPN Friend, queue, don't send */ + friend_ack_rxed(net, mesh_net_get_iv_index(net), + mesh_net_next_seq_num(net), 0, dst, msg); + } else { + mesh_net_transport_send(net, 0, false, + mesh_net_get_iv_index(net), DEFAULT_TTL, + 0, 0, dst, msg, sizeof(msg)); + } +} + +static void send_net_ack(struct mesh_net *net, struct mesh_sar *sar, + uint32_t flags) +{ + uint8_t msg[7]; + uint32_t hdr; + uint16_t src = sar->src; + uint16_t dst = sar->remote; + + /* We don't ACK from multicast destinations */ + if (src & 0x8000) + return; + + /* We don't ACK segments as a Low Power Node */ + if (net->friend_addr) + return; + + hdr = NET_OP_SEG_ACKNOWLEDGE << OPCODE_HDR_SHIFT; + hdr |= sar->seqZero << SEQ_ZERO_HDR_SHIFT; + + if (is_lpn_friend(net, src, true)) + hdr |= true << RELAY_HDR_SHIFT; + + l_put_be32(hdr, msg); + l_put_be32(flags, msg + 3); + l_info("Send%s ACK to Segs: %8.8x", sar->frnd ? " Friend" : "", flags); + + if (is_lpn_friend(net, dst, true)) { + /* If we are acking our LPN Friend, queue, don't send */ + friend_ack_rxed(net, mesh_net_get_iv_index(net), + mesh_net_next_seq_num(net), src, dst, msg); + return; + } + + mesh_net_transport_send(net, 0, false, + mesh_net_get_iv_index(net), DEFAULT_TTL, + 0, src, dst, msg, sizeof(msg)); +} + +static void inseg_to(struct l_timeout *seg_timeout, void *user_data) +{ + struct mesh_net *net = user_data; + struct mesh_sar *sar = l_queue_find(net->sar_in, + match_seg_timeout, seg_timeout); + + l_timeout_remove(seg_timeout); + if (!sar) + return; + + /* Send NAK */ + l_info("Timeout %p %3.3x", sar, sar->app_idx); + send_net_ack(net, sar, sar->flags); + + sar->seg_timeout = l_timeout_create(SEG_TO, inseg_to, net, NULL); +} + +static void inmsg_to(struct l_timeout *msg_timeout, void *user_data) +{ + struct mesh_net *net = user_data; + struct mesh_sar *sar = l_queue_remove_if(net->sar_in, + match_msg_timeout, msg_timeout); + + l_timeout_remove(msg_timeout); + if (!sar) + return; + + sar->msg_timeout = NULL; + + /* print_packet("Incoming SAR Timeout", sar->buf, sar->len); */ + mesh_sar_free(sar); +} + +static void outmsg_to(struct l_timeout *msg_timeout, void *user_data) +{ + struct mesh_net *net = user_data; + struct mesh_sar *sar = l_queue_remove_if(net->sar_out, + match_msg_timeout, msg_timeout); + + l_timeout_remove(msg_timeout); + if (!sar) + return; + + sar->msg_timeout = NULL; + + if (sar->status_func) + sar->status_func(sar->remote, 1, + sar->buf, sar->len - 4, + sar->user_data); + + /* print_packet("Outgoing SAR Timeout", sar->buf, sar->len); */ + mesh_sar_free(sar); +} + +static void outseg_to(struct l_timeout *seg_timeout, void *user_data); +static void ack_received(struct mesh_net *net, bool timeout, + uint16_t src, uint16_t dst, + uint16_t seq0, uint32_t ack_flag) +{ + struct mesh_sar *outgoing; + uint32_t seg_flag = 0x00000001; + uint32_t ack_copy = ack_flag; + uint16_t i; + + l_info("ACK Rxed (%x) (to:%d): %8.8x", seq0, timeout, ack_flag); + + outgoing = l_queue_find(net->sar_out, match_sar_seq0, + L_UINT_TO_PTR(seq0)); + + if (!outgoing) { + l_info("Not Found: %4.4x", seq0); + return; + } + + /* + * TODO -- If we receive from different + * SRC than we are sending to, make sure the OBO flag is set + */ + + if ((!timeout && !ack_flag) || + (outgoing->flags & ack_flag) == outgoing->flags) { + l_debug("ob_sar_removal (%x)", outgoing->flags); + + /* Note: ack_flags == 0x00000000 is a remote Cancel request */ + if (outgoing->status_func) + outgoing->status_func(src, ack_flag ? 0 : 1, + outgoing->buf, + outgoing->len - 4, outgoing->user_data); + + l_queue_remove(net->sar_out, outgoing); + mesh_sar_free(outgoing); + + return; + } + + outgoing->last_nak |= ack_flag; + + ack_copy &= outgoing->flags; + + for (i = 0; i <= SEG_MAX(outgoing->len); i++, seg_flag <<= 1) { + if (seg_flag & ack_flag) { + l_debug("Skipping Seg %d of %d", + i, SEG_MAX(outgoing->len)); + continue; + } + + ack_copy |= seg_flag; + + l_info("Resend Seg %d net:%p dst:%x app_idx:%3.3x", + i, net, outgoing->remote, outgoing->app_idx); + + send_seg(net, outgoing, i); + } + + l_timeout_remove(outgoing->seg_timeout); + outgoing->seg_timeout = l_timeout_create(SEG_TO, outseg_to, net, NULL); +} + +static void outack_to(struct l_timeout *seg_timeout, void *user_data) +{ + struct mesh_net *net = user_data; + struct mesh_sar *sar = l_queue_find(net->sar_out, + match_seg_timeout, seg_timeout); + + l_timeout_remove(seg_timeout); + if (!sar) + return; + + sar->seg_timeout = NULL; + + /* Re-Send missing segments by faking NAK */ + ack_received(net, true, sar->remote, sar->src, + sar->seqZero, sar->last_nak); +} + +static void outseg_to(struct l_timeout *seg_timeout, void *user_data) +{ + struct mesh_net *net = user_data; + struct mesh_sar *sar = l_queue_find(net->sar_out, + match_seg_timeout, seg_timeout); + + l_timeout_remove(seg_timeout); + if (!sar) + return; + + sar->seg_timeout = NULL; + + if (net->friend_addr) { + /* We are LPN -- Poll for ACK */ + frnd_ack_poll(net); + sar->seg_timeout = l_timeout_create(SEG_TO, + outack_to, net, NULL); + } else { + /* Re-Send missing segments by faking NACK */ + ack_received(net, true, sar->remote, sar->src, + sar->seqZero, sar->last_nak); + } +} + +static bool msg_rxed(struct mesh_net *net, bool frnd, uint32_t iv_index, + uint8_t ttl, uint32_t seq, + uint16_t net_idx, + uint16_t src, uint16_t dst, + uint8_t key_aid, + bool szmic, uint16_t seqZero, + const uint8_t *data, uint16_t size) +{ + uint32_t seqAuth = seq_auth(seq, seqZero); + + /* Sanity check seqAuth */ + if (seqAuth > seq) + return false; + + /* Save un-decrypted messages for our friends */ + if (!frnd && l_queue_length(net->friends)) { + uint32_t hdr = key_aid << KEY_HDR_SHIFT; + uint8_t frnd_ttl = ttl; + + /* If not from us, decrement for our hop */ + if (src < net->src_addr || src > net->last_addr) { + if (frnd_ttl > 1) + frnd_ttl--; + else + goto not_for_friend; + } + + if (szmic || size > 15) { + hdr |= true << SEG_HDR_SHIFT; + hdr |= szmic << SZMIC_HDR_SHIFT; + hdr |= (seqZero & SEQ_ZERO_MASK) << SEQ_ZERO_HDR_SHIFT; + hdr |= SEG_MAX(size) << SEGN_HDR_SHIFT; + } + + if (friend_packet_queue(net, iv_index, false, frnd_ttl, + seq, src, dst, + hdr, data, size)) + return true; + } + +not_for_friend: + return mesh_model_rx(net->node, szmic, seqAuth, seq, iv_index, ttl, + net_idx, src, dst, key_aid, data, size); +} + +static bool match_frnd_sar_dst(const void *a, const void *b) +{ + const struct mesh_friend_msg *frnd_msg = a; + uint16_t dst = L_PTR_TO_UINT(b); + + return frnd_msg->dst == dst; +} + +static void friend_seg_rxed(struct mesh_net *net, + uint32_t iv_index, + uint8_t ttl, uint32_t seq, + uint16_t src, uint16_t dst, uint32_t hdr, + const uint8_t *data, uint8_t size) +{ + struct mesh_friend *frnd = NULL; + struct mesh_friend_msg *frnd_msg = NULL; + uint8_t cnt; + uint8_t segN = hdr & 0x1f; + uint8_t segO = ((hdr >> 5) & 0x1f); + uint32_t expected = 0xffffffff >> (31 - segN); + uint32_t this_seg_flag = 0x00000001 << segO; + uint32_t largest = (0xffffffff << segO) & expected; + uint32_t hdr_key = hdr & HDR_KEY_MASK; + + frnd = l_queue_find(net->friends, match_frnd_dst, + L_UINT_TO_PTR(dst)); + if (!frnd) + return; + + if (frnd->last_hdr == hdr_key) { + /* We are no longer receiving this msg. Resend final ACK */ + send_frnd_ack(net, dst, src, frnd->last_hdr, 0xffffffff); + return; + } + + /* Check if we have a SAR-in-progress that matches incoming segment */ + frnd_msg = l_queue_find(net->frnd_msgs, match_frnd_sar_dst, + L_UINT_TO_PTR(dst)); + + if (frnd_msg) { + /* Flush if SZMICN or IV Index has changed */ + if (frnd_msg->iv_index != iv_index) + frnd_msg->u.s12[0].hdr = 0; + + /* Flush incomplete old SAR message if it doesn't match */ + if ((frnd_msg->u.s12[0].hdr & HDR_KEY_MASK) != hdr_key) { + l_queue_remove(net->frnd_msgs, frnd_msg); + l_free(frnd_msg); + frnd_msg = NULL; + } + } + + if (!frnd_msg) { + frnd_msg = mesh_friend_msg_new(segN); + frnd_msg->iv_index = iv_index; + frnd_msg->src = src; + frnd_msg->dst = dst; + frnd_msg->ttl = ttl; + l_queue_push_tail(net->frnd_msgs, frnd_msg); + } else if (frnd_msg->flags & this_seg_flag) /* Ignore dup segs */ + return; + + cnt = frnd_msg->cnt_in; + frnd_msg->flags |= this_seg_flag; + + frnd_msg->u.s12[cnt].hdr = hdr; + frnd_msg->u.s12[cnt].seq = seq; + memcpy(frnd_msg->u.s12[cnt].data, data, size); + + /* Last segment could be short */ + if (segN == segO) + frnd_msg->last_len = size; + + l_info("RXed Seg %d, Flags %8.8x (cnt: %d)", + segO, frnd_msg->flags, cnt); + + /* In reality, if one of these is true, then *both* must be true */ + if ((cnt == segN) || (frnd_msg->flags == expected)) { + l_info("Full ACK"); + send_frnd_ack(net, dst, src, hdr, frnd_msg->flags); + + if (frnd_msg->ttl > 1) { + frnd_msg->ttl--; + /* Add to friends cache */ + l_queue_foreach(net->friends, + enqueue_friend_pkt, frnd_msg); + } + + /* Remove from "in progress" queue */ + l_queue_remove(net->frnd_msgs, frnd_msg); + + /* TODO Optimization(?): Unicast messages keep this buffer */ + l_free(frnd_msg); + return; + } + + /* Always ACK if this is the largest outstanding segment */ + if ((largest & frnd_msg->flags) == largest) { + l_info("Partial ACK"); + send_frnd_ack(net, dst, src, hdr, frnd_msg->flags); + } + + frnd_msg->cnt_in++; +} + +static bool seg_rxed(struct mesh_net *net, bool frnd, uint32_t iv_index, + uint8_t ttl, uint32_t seq, + uint16_t net_idx, + uint16_t src, uint16_t dst, + uint8_t key_aid, + bool szmic, uint16_t seqZero, + uint8_t segO, uint8_t segN, + const uint8_t *data, uint8_t size) +{ + struct mesh_sar *sar_in = NULL; + uint16_t seg_off = 0; + uint32_t expected, this_seg_flag, largest, seqAuth; + bool reset_seg_to = true; + + /* + * DST could receive additional Segments after + * completing due to a lost ACK, so re-ACK and discard + */ + sar_in = l_queue_find(net->sar_in, match_sar_remote, + L_UINT_TO_PTR(src)); + + /* Discard *old* incoming-SAR-in-progress if this segment newer */ + seqAuth = seq_auth(seq, seqZero); + if (sar_in && (sar_in->seqAuth != seqAuth || + sar_in->iv_index != iv_index)) { + bool newer; + + if (iv_index > sar_in->iv_index) + newer = true; + else if (iv_index == sar_in->iv_index) + newer = seqAuth > sar_in->seqAuth; + else + newer = false; + + if (newer) { + /* Cancel Old, start New */ + l_queue_remove(net->sar_in, sar_in); + mesh_sar_free(sar_in); + sar_in = NULL; + } else + /* Ignore Old */ + return false; + } + + expected = 0xffffffff >> (31 - segN); + + if (sar_in) { + l_info("RXed (old: %04x %06x size:%d) %d of %d", + seqZero, seq, size, segO, segN); + /* Sanity Check--> certain things must match */ + if (SEG_MAX(sar_in->len) != segN || + sar_in->key_aid != key_aid) + return false; + + if (sar_in->flags == expected) { + /* Re-Send ACK for full msg */ + if (!net->friend_addr) + send_net_ack(net, sar_in, expected); + return true; + } + } else { + uint16_t len = MAX_SEG_TO_LEN(segN); + + l_info("RXed (new: %04x %06x size: %d len: %d) %d of %d", + seqZero, seq, size, len, segO, segN); + l_debug("Queue Size: %d", l_queue_length(net->sar_in)); + sar_in = mesh_sar_new(len); + sar_in->seqAuth = seqAuth; + sar_in->iv_index = iv_index; + sar_in->src = dst; + sar_in->remote = src; + sar_in->seqZero = seqZero; + sar_in->key_aid = key_aid; + sar_in->len = len; + sar_in->last_seg = 0xff; + if (!net->friend_addr) + sar_in->msg_timeout = l_timeout_create(MSG_TO, + inmsg_to, net, NULL); + + l_debug("First Seg %4.4x", sar_in->flags); + l_queue_push_head(net->sar_in, sar_in); + } + /* print_packet("Seg", data, size); */ + + seg_off = segO * MAX_SEG_LEN; + memcpy(sar_in->buf + seg_off, data, size); + this_seg_flag = 0x00000001 << segO; + + /* Don't reset Seg TO or NAK if we already have this seg */ + if (this_seg_flag & sar_in->flags) + reset_seg_to = false; + + sar_in->flags |= this_seg_flag; + sar_in->ttl = ttl; + + l_debug("Have Frags %4.4x", sar_in->flags); + + /* Msg length only definitive on last segment */ + if (segO == segN) + sar_in->len = segN * MAX_SEG_LEN + size; + + if (sar_in->flags == expected) { + /* Got it all */ + if (!net->friend_addr) + send_net_ack(net, sar_in, expected); + + msg_rxed(net, frnd, iv_index, ttl, seq, net_idx, + sar_in->remote, dst, + key_aid, + szmic, sar_in->seqZero, + sar_in->buf, sar_in->len); + + /* Kill Inter-Seg timeout */ + l_timeout_remove(sar_in->seg_timeout); + sar_in->seg_timeout = NULL; + return true; + } + + if (reset_seg_to) { + /* Restart Inter-Seg Timeout */ + l_timeout_remove(sar_in->seg_timeout); + + /* if this is the largest outstanding segment, send NAK now */ + if (!net->friend_addr) { + largest = (0xffffffff << segO) & expected; + if ((largest & sar_in->flags) == largest) + send_net_ack(net, sar_in, sar_in->flags); + + sar_in->seg_timeout = l_timeout_create(SEG_TO, + inseg_to, net, NULL); + } else + largest = 0; + } else + largest = 0; + + l_debug("NAK: %d expected:%08x largest:%08x flags:%08x", + reset_seg_to, expected, largest, sar_in->flags); + return false; +} + +static bool ctl_received(struct mesh_net *net, bool frnd, uint32_t iv_index, + uint8_t ttl, + uint32_t seq, + uint16_t src, uint16_t dst, + uint8_t opcode, int8_t rssi, + const uint8_t *pkt, uint8_t len) +{ + uint8_t msg[12]; + uint8_t rsp_ttl = DEFAULT_TTL; + uint8_t n = 0; + + if (!frnd && ttl > 1) { + uint32_t hdr = opcode << OPCODE_HDR_SHIFT; + uint8_t frnd_ttl = ttl - 1; + + if (friend_packet_queue(net, iv_index, + true, frnd_ttl, + seq, + src, dst, + hdr, + pkt, len)) + return true; + } + + /* Don't process other peoples Unicast destinations */ + if (dst < 0x8000 && (dst < net->src_addr || dst > net->last_addr)) + return false; + + switch (opcode) { + default: + l_error("Unsupported Ctl Opcode: %2.2x", opcode); + break; + + case NET_OP_FRND_POLL: + if (len != 1 || ttl) + return false; + + print_packet("Rx-NET_OP_FRND_POLL", pkt, len); + friend_poll(net, src, !!(pkt[0]), + l_queue_find(net->friends, + match_by_friend, + L_UINT_TO_PTR(src))); + break; + + case NET_OP_FRND_UPDATE: + if (ttl) + return false; + + print_packet("Rx-NET_OP_FRND_UPDATE", pkt, len); + lpn_process_beacon(net, pkt, len, 0); + break; + + case NET_OP_FRND_REQUEST: + if (!net->friend_enable) + return false; + + if (!IS_ALL_NODES(dst) && dst != FRIENDS_ADDRESS) + return false; + + if (len != 10 || ttl) + return false; + + print_packet("Rx-NET_OP_FRND_REQUEST", pkt, len); + friend_request(net, src, pkt[0], pkt[1], + l_get_be32(pkt + 1) & 0xffffff, + l_get_be16(pkt + 5), pkt[7], + l_get_be16(pkt + 8), rssi); + break; + + case NET_OP_FRND_OFFER: + if (len != 6 || ttl) + return false; + + print_packet("Rx-NET_OP_FRND_OFFER", pkt, len); + frnd_offer(net, src, pkt[0], pkt[1], pkt[2], + (int8_t) pkt[3], rssi, l_get_be16(pkt + 4)); + break; + + case NET_OP_FRND_CLEAR_CONFIRM: + if (len != 4) + return false; + + print_packet("Rx-NET_OP_FRND_CLEAR_CONFIRM", pkt, len); + friend_clear_confirm(net, src, l_get_be16(pkt), + l_get_be16(pkt + 2)); + break; + + case NET_OP_FRND_CLEAR: + if (len != 4 || dst != net->src_addr) + return false; + + print_packet("Rx-NET_OP_FRND_CLEAR", pkt, len); + friend_clear(net, src, l_get_be16(pkt), l_get_be16(pkt + 2), + l_queue_find(net->friends, + match_by_friend, + L_UINT_TO_PTR(l_get_be16(pkt)))); + l_info("Remaining Friends: %d", l_queue_length(net->friends)); + break; + + case NET_OP_PROXY_SUB_ADD: + if (ttl) + return false; + + print_packet("Rx-NET_OP_PROXY_SUB_ADD", pkt, len); + friend_sub_add(net, l_queue_find(net->friends, + match_by_friend, L_UINT_TO_PTR(src)), + pkt, len); + break; + + case NET_OP_PROXY_SUB_REMOVE: + if (ttl) + return false; + + print_packet("Rx-NET_OP_PROXY_SUB_REMOVE", pkt, len); + friend_sub_del(net, l_queue_find(net->friends, + match_by_friend, L_UINT_TO_PTR(src)), + pkt, len); + break; + + case NET_OP_PROXY_SUB_CONFIRM: + if (ttl) + return false; + + print_packet("Rx-NET_OP_PROXY_SUB_CONFIRM", pkt, len); + break; + + case NET_OP_HEARTBEAT: + if (net->heartbeat.sub_enabled && + src == net->heartbeat.sub_src) { + uint8_t hops = pkt[0] - ttl + 1; + + print_packet("Rx-NET_OP_HEARTBEAT", pkt, len); + + if (net->heartbeat.sub_count != 0xffff) + net->heartbeat.sub_count++; + + if (net->heartbeat.sub_min_hops > hops) + net->heartbeat.sub_min_hops = hops; + + if (net->heartbeat.sub_max_hops < hops) + net->heartbeat.sub_max_hops = hops; + + l_info("HB: cnt:%4.4x min:%2.2x max:%2.2x", + net->heartbeat.sub_count, + net->heartbeat.sub_min_hops, + net->heartbeat.sub_max_hops); + } + break; + } + + if (n) { + mesh_net_transport_send(net, 0, false, + mesh_net_get_iv_index(net), rsp_ttl, + 0, dst & 0x8000 ? 0 : dst, src, + msg, n); + } + + return true; +} + +static bool find_fast_hash(const void *a, const void *b) +{ + const uint64_t *entry = a; + const uint64_t *test = b; + + return *entry == *test; +} + +static bool check_fast_cache(uint64_t hash) +{ + void *found = l_queue_find(fast_cache, find_fast_hash, &hash); + uint64_t *new_hash; + + if (found) + return false; + + if (l_queue_length(fast_cache) >= FAST_CACHE_SIZE) + new_hash = l_queue_pop_head(fast_cache); + else + new_hash = l_malloc(sizeof(hash)); + + *new_hash = hash; + l_queue_push_tail(fast_cache, new_hash); + + return true; +} + +static bool match_by_dst(const void *a, const void *b) +{ + const struct mesh_destination *dest = a; + uint16_t dst = L_PTR_TO_UINT(b); + + return dest->dst == dst; +} + +static void send_relay_pkt(struct mesh_net *net, uint8_t *data, uint8_t size) +{ + uint8_t packet[30]; + struct mesh_io *io = net->io; + struct mesh_io_send_info info = { + .type = MESH_IO_TIMING_TYPE_GENERAL, + .u.gen.interval = net->relay.interval, + .u.gen.cnt = net->relay.count, + .u.gen.min_delay = DEFAULT_MIN_DELAY, + .u.gen.max_delay = DEFAULT_MAX_DELAY + }; + + packet[0] = MESH_AD_TYPE_NETWORK; + memcpy(packet + 1, data, size); + + mesh_io_send(io, &info, packet, size + 1); +} + +static void send_msg_pkt(struct mesh_net *net, uint8_t *packet, uint8_t size) +{ + struct mesh_io *io = net->io; + struct mesh_io_send_info info; + struct net_queue_data net_data = { + .info = NULL, + .data = packet + 1, + .len = size - 1, + .relay_advice = RELAY_NONE, + }; + + /* Send to local nodes first */ + l_queue_foreach(nets, net_rx, &net_data); + + if (net_data.relay_advice == RELAY_DISALLOWED) + return; + + packet[0] = MESH_AD_TYPE_NETWORK; + info.type = MESH_IO_TIMING_TYPE_GENERAL; + info.u.gen.interval = net->tx_interval; + info.u.gen.cnt = net->tx_cnt; + info.u.gen.min_delay = DEFAULT_MIN_DELAY; + /* No extra randomization when sending regular mesh messages */ + info.u.gen.max_delay = DEFAULT_MIN_DELAY; + + mesh_io_send(io, &info, packet, size); +} + +static uint16_t key_id_to_net_idx(struct mesh_net *net, uint32_t key_id) +{ + struct mesh_subnet *subnet; + + if (!net) + return NET_IDX_INVALID; + + subnet = l_queue_find(net->subnets, match_key_id, + L_UINT_TO_PTR(key_id)); + + if (subnet) + return subnet->idx; + else + return NET_IDX_INVALID; +} + +static enum _relay_advice packet_received(void *user_data, + uint32_t key_id, uint32_t iv_index, + const void *data, uint8_t size, int8_t rssi) +{ + struct mesh_net *net = user_data; + const uint8_t *msg = data; + uint8_t app_msg_len; + uint8_t net_ttl, net_key_id, net_segO, net_segN, net_opcode; + uint32_t net_seq, cache_cookie; + uint16_t net_src, net_dst, net_seqZero; + uint16_t net_idx; + uint8_t packet[31]; + bool net_ctl, net_segmented, net_szmic, net_relay; + struct mesh_friend *net_frnd = NULL; + bool drop = false; + + /* Tester--Drop 90% of packets */ + /* l_getrandom(&iv_flag, 1); */ + /* if (iv_flag%10<9) drop = true; */ + + + memcpy(packet + 2, data, size); + + if (!drop) + print_packet("RX: Network [clr] :", packet + 2, size); + + net_idx = key_id_to_net_idx(net, key_id); + if (net_idx == NET_IDX_INVALID) + return RELAY_NONE; + + if (!mesh_crypto_packet_parse(packet + 2, size, + &net_ctl, &net_ttl, + &net_seq, + &net_src, &net_dst, + &cache_cookie, + &net_opcode, + &net_segmented, + &net_key_id, + &net_szmic, &net_relay, &net_seqZero, + &net_segO, &net_segN, + &msg, &app_msg_len)) { + l_error("Failed to parse packet content"); + return RELAY_NONE; + } + + /* Ignore incoming packets if we are LPN and frnd bit not set */ + if (net->friend_addr) { + struct mesh_subnet *subnet; + + subnet = l_queue_find(net->subnets, match_key_id, + L_UINT_TO_PTR(key_id)); + if (subnet) + return RELAY_NONE; + + /* If the queue is empty, stop polling */ + if (net_ctl && net_opcode == NET_OP_FRND_UPDATE && !msg[5]) + frnd_poll_cancel(net); + else + frnd_poll(net, false); + + } else if (net_dst == 0) { + l_error("illegal parms: DST: %4.4x Ctl: %d TTL: %2.2x", + net_dst, net_ctl, net_ttl); + return RELAY_NONE; + } + + /* Ignore if we originally sent this */ + if (is_us(net, net_src, true)) + return RELAY_NONE; + + if (drop) { + l_info("Dropping SEQ 0x%06x", net_seq); + return RELAY_NONE; + } + + l_debug("check %08x", cache_cookie); + + /* As a Relay, suppress repeats of last N packets that pass through */ + /* The "cache_cookie" should be unique part of App message */ + if (msg_in_cache(net, net_src, net_seq, cache_cookie)) + return RELAY_NONE; + + l_debug("RX: Network %04x -> %04x : TTL 0x%02x : IV : %8.8x SEQ 0x%06x", + net_src, net_dst, net_ttl, iv_index, net_seq); + + if (is_us(net, net_dst, false) || + is_lpn_friend(net, net_src, !!(net_frnd)) || + (net_ctl && net_opcode == NET_OP_HEARTBEAT)) { + + l_info("RX: App 0x%04x -> 0x%04x : TTL 0x%02x : SEQ 0x%06x", + net_src, net_dst, net_ttl, net_seq); + + l_debug("seq:%x seq0:%x", net_seq, net_seqZero); + if (net_ctl) { + l_debug("CTL - %4.4x RX", net_seqZero); + if (net_opcode == NET_OP_SEG_ACKNOWLEDGE) { + /* Illegal to send ACK to non-Unicast Addr */ + if (net_dst & 0x8000) + return RELAY_NONE; + + /* print_packet("Got ACK", msg, app_msg_len); */ + /* Pedantic check for correct size */ + if (app_msg_len != 7) + return RELAY_NONE; + + /* If this is an ACK to our friend queue-only */ + if (is_lpn_friend(net, net_dst, true)) + friend_ack_rxed(net, iv_index, net_seq, + net_src, net_dst, + msg); + else + ack_received(net, false, + net_src, net_dst, + net_seqZero, + l_get_be32(msg + 3)); + } else { + ctl_received(net, !!(net_frnd), iv_index, + net_ttl, net_seq, net_src, + net_dst, net_opcode, rssi, + msg, app_msg_len); + } + } else if (net_segmented) { + /* If we accept SAR packets to non-Unicast, then + * Friend Sar at least needs to be Unicast Only + */ + if (is_lpn_friend(net, net_dst, true) && + !(net_dst & 0x8000)) { + /* Check TTL >= 2 before accepting segments + * for Friends + */ + if (net_ttl >= 2) { + friend_seg_rxed(net, iv_index, + net_ttl, net_seq, + net_src, net_dst, + l_get_be32(packet + 2 + 9), + msg, app_msg_len); + } + } else { + seg_rxed(net, net_frnd, iv_index, net_ttl, + net_seq, net_idx, + net_src, net_dst, + net_key_id, + net_szmic, net_seqZero, + net_segO, net_segN, + msg, app_msg_len); + } + + } else { + msg_rxed(net, net_frnd, + iv_index, + net_ttl, + net_seq, + net_idx, + net_src, net_dst, + net_key_id, + false, net_seq & SEQ_ZERO_MASK, + msg, app_msg_len); + } + + if (!!(net_frnd)) + l_info("Ask for more data!"); + + /* If this is one of our Unicast addresses, disallow relay */ + if (IS_UNICAST(net_dst)) + return RELAY_DISALLOWED; + } + + /* If relay not enable, or no more hops allowed */ + if (!net->relay.enable || net_ttl < 0x02 || net_frnd) + return RELAY_NONE; + + /* Group or Virtual destinations should *always* be relayed */ + if (IS_GROUP(net_dst) || IS_VIRTUAL(net_dst)) + return RELAY_ALWAYS; + + /* Unicast destinations for other nodes *may* be relayed */ + else if (IS_UNICAST(net_dst)) + return RELAY_ALLOWED; + + /* Otherwise, do not make a relay decision */ + else + return RELAY_NONE; +} + +static void net_rx(void *net_ptr, void *user_data) +{ + struct net_queue_data *data = user_data; + struct mesh_net *net = net_ptr; + enum _relay_advice relay_advice; + uint8_t *out; + size_t out_size; + uint32_t key_id; + int8_t rssi = 0; + bool ivi_net = !!(net->iv_index & 1); + bool ivi_pkt = !!(data->data[0] & 0x80); + + /* if IVI flag differs, use previous IV Index */ + uint32_t iv_index = net->iv_index - (ivi_pkt ^ ivi_net); + + key_id = net_key_decrypt(iv_index, data->data, data->len, + &out, &out_size); + + if (!key_id) + return; + + print_packet("RX: Network [enc] :", data->data, data->len); + + if (data->info) { + net->instant = data->info->instant; + net->chan = data->info->chan; + rssi = data->info->rssi; + } + + relay_advice = packet_received(net, key_id, iv_index, + out, out_size, rssi); + if (relay_advice > data->relay_advice) { + data->iv_index = iv_index; + data->relay_advice = relay_advice; + data->key_id = key_id; + data->net = net; + data->out = out; + data->out_size = out_size; + } +} + +static void net_msg_recv(void *user_data, struct mesh_io_recv_info *info, + const uint8_t *data, uint16_t len) +{ + uint64_t hash; + bool isNew; + struct net_queue_data net_data = { + .info = info, + .data = data + 1, + .len = len - 1, + .relay_advice = RELAY_NONE, + }; + + if (len < 9) + return; + + hash = l_get_le64(data + 1); + + /* Only process packet once per reception */ + isNew = check_fast_cache(hash); + if (!isNew) + return; + + l_queue_foreach(nets, net_rx, &net_data); + + if (net_data.relay_advice == RELAY_ALWAYS || + net_data.relay_advice == RELAY_ALLOWED) { + uint8_t ttl = net_data.out[1] & TTL_MASK; + + net_data.out[1] &= ~TTL_MASK; + net_data.out[1] |= ttl - 1; + net_key_encrypt(net_data.key_id, net_data.iv_index, + net_data.out, net_data.out_size); + send_relay_pkt(net_data.net, net_data.out, net_data.out_size); + } +} + +static void set_network_beacon(void *a, void *b) +{ + struct mesh_subnet *subnet = a; + struct mesh_net *net = b; + uint8_t beacon_data[22]; + + if (!create_secure_beacon(net, subnet, beacon_data)) + return; + + if (memcmp(&subnet->snb.beacon[1], beacon_data, + sizeof(beacon_data)) == 0) + return; + + memcpy(&subnet->snb.beacon[1], beacon_data, sizeof(beacon_data)); + + if (net->beacon_enable && !net->friend_addr) { + print_packet("Set My Beacon to", + beacon_data, sizeof(beacon_data)); + start_network_beacon(subnet, net); + } + + if (l_queue_length(net->friends)) { + struct mesh_friend_msg update = { + .src = net->src_addr, + .iv_index = mesh_net_get_iv_index(net), + .last_len = 7, + .ctl = true, + }; + + update.u.one[0].hdr = NET_OP_FRND_UPDATE << OPCODE_HDR_SHIFT; + update.u.one[0].seq = mesh_net_next_seq_num(net); + update.u.one[0].data[0] = NET_OP_FRND_UPDATE; + update.u.one[0].data[1] = beacon_data[3]; + l_put_be32(net->iv_index, update.u.one[0].data + 2); + update.u.one[0].data[6] = 0x01; /* More Data */ + /* print_packet("Frnd-Beacon-SRC", + * beacon_data, sizeof(beacon_data)); + */ + /* print_packet("Frnd-Update", update.u.one[0].data, 6); */ + + l_queue_foreach(net->friends, enqueue_update, &update); + } +} + +static void iv_upd_to(struct l_timeout *upd_timeout, void *user_data) +{ + struct mesh_net *net = user_data; + + switch (net->iv_upd_state) { + case IV_UPD_UPDATING: + if (l_queue_length(net->sar_out)) { + l_info("don't leave IV Update until sar_out empty"); + l_timeout_modify(net->iv_update_timeout, 10); + break; + } + + l_debug("iv_upd_state = IV_UPD_NORMAL_HOLD"); + net->iv_upd_state = IV_UPD_NORMAL_HOLD; + l_timeout_modify(net->iv_update_timeout, IV_IDX_UPD_MIN); + if (net->iv_update) + mesh_net_set_seq_num(net, 0); + + net->iv_update = false; + mesh_config_write_iv_index(node_config_get(net->node), + net->iv_index, false); + l_queue_foreach(net->subnets, set_network_beacon, net); + mesh_net_flush_msg_queues(net); + break; + + case IV_UPD_INIT: + case IV_UPD_NORMAL_HOLD: + case IV_UPD_NORMAL: + l_timeout_remove(upd_timeout); + net->iv_update_timeout = NULL; + l_debug("iv_upd_state = IV_UPD_NORMAL"); + net->iv_upd_state = IV_UPD_NORMAL; + if (net->iv_update) + mesh_net_set_seq_num(net, 0); + + net->iv_update = false; + if (net->seq_num > IV_UPDATE_SEQ_TRIGGER) + mesh_net_iv_index_update(net); + break; + } +} + + +static int key_refresh_phase_two(struct mesh_net *net, uint16_t idx) +{ + struct mesh_subnet *subnet; + + if (!net) + return MESH_STATUS_UNSPECIFIED_ERROR; + + subnet = l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)); + + if (!subnet || !subnet->net_key_upd) + return MESH_STATUS_INVALID_NETKEY; + + l_info("Key refresh procedure phase 2: start using new net TX keys"); + subnet->key_refresh = 1; + subnet->net_key_tx = subnet->net_key_upd; + /* TODO: Provisioner may need to stay in phase three until + * it hears beacons from all the nodes + */ + subnet->kr_phase = KEY_REFRESH_PHASE_TWO; + set_network_beacon(subnet, net); + + if (net->friend_addr) + frnd_key_refresh(net, 2); + else + l_queue_foreach(net->friends, frnd_kr_phase2, net); + + mesh_config_net_key_set_phase(node_config_get(net->node), idx, + KEY_REFRESH_PHASE_TWO); + + return MESH_STATUS_SUCCESS; +} + +static int key_refresh_finish(struct mesh_net *net, uint16_t idx) +{ + struct mesh_subnet *subnet; + + if (!net) + return MESH_STATUS_UNSPECIFIED_ERROR; + + subnet = l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)); + if (!subnet || !subnet->net_key_upd) + return MESH_STATUS_INVALID_NETKEY; + + if (subnet->kr_phase == KEY_REFRESH_PHASE_NONE) + return MESH_STATUS_SUCCESS; + + l_info("Key refresh phase 3: use new keys only, discard old ones"); + + /* Switch to using new keys, discard old ones */ + net_key_unref(subnet->net_key_cur); + subnet->net_key_tx = subnet->net_key_cur = subnet->net_key_upd; + subnet->net_key_upd = 0; + subnet->key_refresh = 0; + subnet->kr_phase = KEY_REFRESH_PHASE_NONE; + set_network_beacon(subnet, net); + + if (net->friend_addr) + frnd_key_refresh(net, 3); + else + l_queue_foreach(net->friends, frnd_kr_phase3, net); + + mesh_config_net_key_set_phase(node_config_get(net->node), idx, + KEY_REFRESH_PHASE_NONE); + + return MESH_STATUS_SUCCESS; +} + +static void update_kr_state(struct mesh_subnet *subnet, bool kr, uint32_t id) +{ + /* Figure out the key refresh phase */ + if (kr) { + if (id == subnet->net_key_upd) { + l_debug("Beacon based KR phase 2 change"); + key_refresh_phase_two(subnet->net, subnet->idx); + } + } else { + if (id == subnet->net_key_upd) { + l_debug("Beacon based KR phase 3 change"); + key_refresh_finish(subnet->net, subnet->idx); + } + } +} + +static void update_iv_ivu_state(struct mesh_net *net, uint32_t iv_index, + bool ivu) +{ + uint32_t local_iv_index; + bool local_ivu; + + /* Save original settings to differentiate what has changed */ + local_iv_index = net->iv_index; + local_ivu = net->iv_update; + + if ((iv_index - ivu) > (local_iv_index - local_ivu)) { + /* Don't accept IV_Index changes when performing SAR Out */ + if (l_queue_length(net->sar_out)) + return; + } + + /* If first beacon seen, accept without judgement */ + if (net->iv_upd_state == IV_UPD_INIT) { + if (ivu) { + /* Other devices will be accepting old or new iv_index, + * but we don't know how far through update they are. + * Starting permissive state will allow us maximum + * (96 hours) to resync + */ + l_info("iv_upd_state = IV_UPD_UPDATING"); + net->iv_upd_state = IV_UPD_UPDATING; + net->iv_update_timeout = l_timeout_create( + IV_IDX_UPD_MIN, iv_upd_to, net, NULL); + } else { + l_info("iv_upd_state = IV_UPD_NORMAL"); + net->iv_upd_state = IV_UPD_NORMAL; + } + } else if (ivu) { + /* Ignore beacons with IVU if they come too soon */ + if (!local_ivu && net->iv_upd_state == IV_UPD_NORMAL_HOLD) { + l_error("Update attempted too soon"); + return; + } + + if (!local_ivu) { + l_info("iv_upd_state = IV_UPD_UPDATING"); + net->iv_upd_state = IV_UPD_UPDATING; + net->iv_update_timeout = l_timeout_create( + IV_IDX_UPD_MIN, iv_upd_to, net, NULL); + } + } else if (local_ivu) { + l_error("IVU clear attempted too soon"); + return; + } + + if ((iv_index - ivu) > (local_iv_index - local_ivu)) + mesh_net_set_seq_num(net, 0); + + if (ivu != net->iv_update || local_iv_index != net->iv_index) { + struct mesh_config *cfg = node_config_get(net->node); + + mesh_config_write_iv_index(cfg, iv_index, ivu); + } + + net->iv_index = iv_index; + net->iv_update = ivu; +} + +static void process_beacon(void *net_ptr, void *user_data) +{ + struct mesh_net *net = net_ptr; + const uint8_t *buf = *(uint8_t **)user_data; + uint32_t ivi; + bool ivu, kr, local_kr; + struct mesh_subnet *subnet; + uint32_t key_id; + + ivi = l_get_be32(buf + 10); + + /* Ignore out-of-range IV_Index for this network */ + if ((net->iv_index + IV_IDX_DIFF_RANGE < ivi) || (ivi < net->iv_index)) + return; + + /* Ignore Network IDs unknown to this mesh universe */ + key_id = net_key_network_id(buf + 2); + if (!key_id) + return; + + subnet = l_queue_find(net->subnets, match_key_id, + L_UINT_TO_PTR(key_id)); + if (!subnet) + return; + + /* Get IVU and KR boolean bits from beacon */ + ivu = !!(buf[1] & 0x02); + kr = !!(buf[1] & 0x01); + local_kr = !!(subnet->kr_phase != KEY_REFRESH_PHASE_TWO); + + if (net->iv_upd_state != IV_UPD_INIT) { + /* Ignore beacons that indicate *no change* */ + if (!memcmp(&subnet->snb.beacon[1], buf, 22)) { + subnet->snb.observed++; + return; + } + } + + /* Validate beacon before accepting */ + if (!net_key_snb_check(key_id, ivi, kr, ivu, l_get_be64(buf + 14))) { + l_error("mesh_crypto_beacon verify failed"); + return; + } + + /* We have officially *seen* this beacon now */ + subnet->snb.observed++; + + update_iv_ivu_state(net, ivi, ivu); + update_kr_state(subnet, kr, key_id); + + if (ivi != net->iv_index || ivu != net->iv_update || kr != local_kr) + set_network_beacon(subnet, net); +} + +static void lpn_process_beacon(void *user_data, const void *data, + uint8_t size, int8_t rssi) +{ + struct mesh_net *net = user_data; + const uint8_t *buf = data; + uint32_t ivi; + bool ivu, kr, local_kr; + struct mesh_subnet *subnet; + bool kr_transition = false; + + /* print_packet("lpn: Secure Net Beacon RXed", data, size); */ + kr = !!(buf[0] & 0x01); + ivu = !!(buf[0] & 0x02); + ivi = l_get_be32(buf + 1); + + l_debug("KR: %d -- IVU: %d -- IVI: %8.8x", kr, ivu, ivi); + + /* TODO: figure out actual network index (i.e., friendship subnet) */ + subnet = get_primary_subnet(net); + if (!subnet) + return; + + local_kr = (subnet->kr_phase == KEY_REFRESH_PHASE_TWO); + + /* Don't bother going further if nothing has changed */ + if (local_kr == kr && ivi == net->iv_index && ivu == net->iv_update && + net->iv_upd_state != IV_UPD_INIT) + return; + + update_iv_ivu_state(net, ivi, ivu); + + if (kr) + update_kr_state(subnet, kr_transition, subnet->net_key_upd); + else + update_kr_state(subnet, kr_transition, subnet->net_key_cur); +} + +static void beacon_recv(void *user_data, struct mesh_io_recv_info *info, + const uint8_t *data, uint16_t len) +{ + const uint8_t *ptr = data + 1; + + if (len != 23 || data[1] != 0x01) + return; + + l_debug("KR: %d -- IVU: %d -- IV: %8.8x", + data[2] & 1, !!(data[2] & 2), l_get_be32(data + 11)); + + l_queue_foreach(nets, process_beacon, &ptr); +} + +bool mesh_net_set_beacon_mode(struct mesh_net *net, bool enable) +{ + if (!net || !IS_UNASSIGNED(net->friend_addr)) + return false; + + if (net->beacon_enable != enable) { + uint8_t type = MESH_AD_TYPE_BEACON; + + net->beacon_enable = enable; + + if (!enable) + mesh_io_send_cancel(net->io, &type, 1); + + l_queue_foreach(net->subnets, start_network_beacon, net); + } + + return true; +} + +/* This function is called when network keys are restored from storage. */ +bool mesh_net_set_key(struct mesh_net *net, uint16_t idx, const uint8_t *key, + const uint8_t *new_key, uint8_t phase) +{ + struct mesh_subnet *subnet; + + /* Current key must be always present */ + if (!key) + return false; + + /* If key refresh is in progress, a new key must be present */ + if (phase != KEY_REFRESH_PHASE_NONE && !new_key) + return false; + + /* Check if the subnet with the specified index already exists */ + subnet = l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)); + if (subnet) + return false; + + subnet = add_key(net, idx, key); + if (!subnet) + return false; + + if (new_key && phase) + subnet->net_key_upd = net_key_add(new_key); + + /* Preserve key refresh state to generate secure beacon flags*/ + if (phase == KEY_REFRESH_PHASE_TWO) { + subnet->key_refresh = 1; + subnet->net_key_tx = subnet->net_key_upd; + } + + subnet->kr_phase = phase; + + set_network_beacon(subnet, net); + + if (net->io) + start_network_beacon(subnet, net); + + return true; +} + +static bool is_this_net(const void *a, const void *b) +{ + return a == b; +} + +bool mesh_net_attach(struct mesh_net *net, struct mesh_io *io) +{ + bool first; + + if (!net) + return false; + + first = l_queue_isempty(nets); + if (first) { + if (!nets) + nets = l_queue_new(); + + if (!fast_cache) + fast_cache = l_queue_new(); + + l_info("Register io cb"); + mesh_io_register_recv_cb(io, MESH_IO_FILTER_BEACON, + beacon_recv, NULL); + mesh_io_register_recv_cb(io, MESH_IO_FILTER_NET, + net_msg_recv, NULL); + l_queue_foreach(net->subnets, start_network_beacon, net); + } + + if (l_queue_find(nets, is_this_net, net)) + return false; + + l_queue_push_head(nets, net); + + net->io = io; + + return true; +} + +struct mesh_io *mesh_net_detach(struct mesh_net *net) +{ + struct mesh_io *io; + uint8_t type = 0; + + if (!net || !net->io) + return NULL; + + io = net->io; + + mesh_io_send_cancel(net->io, &type, 1); + mesh_io_deregister_recv_cb(io, MESH_IO_FILTER_BEACON); + mesh_io_deregister_recv_cb(io, MESH_IO_FILTER_NET); + + net->io = NULL; + l_queue_remove(nets, net); + + return io; +} + +bool mesh_net_iv_index_update(struct mesh_net *net) +{ + if (net->iv_upd_state != IV_UPD_NORMAL) + return false; + + l_info("iv_upd_state = IV_UPD_UPDATING"); + mesh_net_flush_msg_queues(net); + if (!mesh_config_write_iv_index(node_config_get(net->node), + net->iv_index + 1, true)) + return false; + + net->iv_upd_state = IV_UPD_UPDATING; + net->iv_index++; + net->iv_update = true; + l_queue_foreach(net->subnets, set_network_beacon, net); + net->iv_update_timeout = l_timeout_create( + IV_IDX_UPD_MIN, + iv_upd_to, net, NULL); + + return true; +} + + + +void mesh_net_sub_list_add(struct mesh_net *net, uint16_t addr) +{ + uint8_t msg[11] = { PROXY_OP_FILTER_ADD }; + uint8_t n = 1; + + l_put_be16(addr, msg + n); + n += 2; + + mesh_net_transport_send(net, 0, false, + mesh_net_get_iv_index(net), 0, + 0, 0, 0, msg, n); +} + +void mesh_net_sub_list_del(struct mesh_net *net, uint16_t addr) +{ + uint8_t msg[11] = { PROXY_OP_FILTER_DEL }; + uint8_t n = 1; + + l_put_be16(addr, msg + n); + n += 2; + + mesh_net_transport_send(net, 0, false, + mesh_net_get_iv_index(net), 0, + 0, 0, 0, msg, n); +} + +/* TODO: change to use net index */ +bool mesh_net_set_friend(struct mesh_net *net, uint16_t friend_addr) +{ + if (!net) + return false; + + net->bea_id = 0; + + l_info("Set Frnd addr: %4.4x", friend_addr); + if (!friend_addr) + trigger_heartbeat(net, FEATURE_LPN, false); + else + trigger_heartbeat(net, FEATURE_LPN, true); + + net->friend_addr = friend_addr; + + set_network_beacon(get_primary_subnet(net), net); + return true; +} + +uint16_t mesh_net_get_friend(struct mesh_net *net) +{ + if (!net) + return 0; + + return net->friend_addr; +} + +bool mesh_net_dst_reg(struct mesh_net *net, uint16_t dst) +{ + struct mesh_destination *dest = l_queue_find(net->destinations, + match_by_dst, L_UINT_TO_PTR(dst)); + + if (IS_UNASSIGNED(dst) || IS_ALL_NODES(dst)) + return false; + + if (!dest) { + dest = l_new(struct mesh_destination, 1); + + if (dst < 0x8000) + l_queue_push_head(net->destinations, dest); + else + l_queue_push_tail(net->destinations, dest); + + /* If LPN, and Group/Virtual, add to Subscription List */ + if (net->friend_addr) { + /* TODO: Fix this garbage */ + uint32_t u32_dst[7] = {dst, 0xffffffff}; + + frnd_sub_add(net, u32_dst); + } + } + + dest->dst = dst; + dest->ref_cnt++; + + return true; +} + +bool mesh_net_dst_unreg(struct mesh_net *net, uint16_t dst) +{ + struct mesh_destination *dest = l_queue_find(net->destinations, + match_by_dst, L_UINT_TO_PTR(dst)); + + if (!dest) + return false; + + if (dest->ref_cnt) + dest->ref_cnt--; + + if (dest->ref_cnt) + return true; + + /* TODO: If LPN, and Group/Virtual, remove from Subscription List */ + if (net->friend_addr) { + /* TODO: Fix this garbage */ + uint32_t u32_dst[7] = {dst, 0xffffffff}; + + frnd_sub_del(net, u32_dst); + } + + l_queue_remove(net->destinations, dest); + + l_free(dest); + return true; +} + +bool mesh_net_flush(struct mesh_net *net) +{ + if (!net) + return false; + + /* TODO mesh-io Flush */ + return true; +} + +/* TODO: add net key index */ +static bool send_seg(struct mesh_net *net, struct mesh_sar *msg, uint8_t segO) +{ + uint8_t seg_len; + uint8_t gatt_data[30]; + uint8_t *packet = gatt_data; + uint8_t packet_len; + uint8_t segN = SEG_MAX(msg->len); + uint16_t seg_off = SEG_OFF(segO); + uint32_t key_id = 0; + uint32_t seq_num = mesh_net_next_seq_num(net); + + if (segN) { + if (msg->len - seg_off > SEG_OFF(1)) + seg_len = SEG_OFF(1); + else + seg_len = msg->len - seg_off; + } else { + seg_len = msg->len; + } + + /* Start IV Update procedure when we hit our trigger point */ + if (!msg->frnd && net->seq_num > IV_UPDATE_SEQ_TRIGGER) + mesh_net_iv_index_update(net); + + l_debug("segN %d segment %d seg_off %d", segN, segO, seg_off); + /* print_packet("Sending", msg->buf + seg_off, seg_len); */ + { + /* TODO: Are we RXing on an LPN's behalf? Then set RLY bit */ + + if (!mesh_crypto_packet_build(false, msg->ttl, + seq_num, + msg->src, msg->remote, + 0, + segN ? true : false, msg->key_aid, + msg->szmic, false, msg->seqZero, + segO, segN, + msg->buf + seg_off, seg_len, + packet + 1, &packet_len)) { + l_error("Failed to build packet"); + return false; + } + } + print_packet("Clr-Net Tx", packet + 1, packet_len); + + if (msg->frnd_cred && net->friend_addr) + key_id = frnd_get_key(net); + + if (!key_id) { + struct mesh_subnet *subnet = get_primary_subnet(net); + + key_id = subnet->net_key_tx; + } + + if (!net_key_encrypt(key_id, msg->iv_index, packet + 1, packet_len)) { + l_error("Failed to encode packet"); + return false; + } + + /* print_packet("Step 4", packet + 1, packet_len); */ + + { + char *str; + + send_msg_pkt(net, packet, packet_len + 1); + + str = l_util_hexstring(packet + 1, packet_len); + l_info("TX: Network %04x -> %04x : %s (%u) : TTL %d : SEQ %06x", + msg->src, msg->remote, str, + packet_len, msg->ttl, + msg->frnd ? msg->seqAuth + segO : seq_num); + l_free(str); + } + + msg->last_seg = segO; + + return true; +} + +void mesh_net_send_seg(struct mesh_net *net, uint32_t net_key_id, + uint32_t iv_index, + uint8_t ttl, + uint32_t seq, + uint16_t src, uint16_t dst, + uint32_t hdr, + const void *seg, uint16_t seg_len) +{ + char *str; + uint8_t packet[30]; + uint8_t packet_len; + bool segmented = !!((hdr >> SEG_HDR_SHIFT) & true); + uint8_t app_key_id = (hdr >> KEY_HDR_SHIFT) & KEY_ID_MASK; + bool szmic = !!((hdr >> SZMIC_HDR_SHIFT) & true); + uint16_t seqZero = (hdr >> SEQ_ZERO_HDR_SHIFT) & SEQ_ZERO_MASK; + uint8_t segO = (hdr >> SEGO_HDR_SHIFT) & SEG_MASK; + uint8_t segN = (hdr >> SEGN_HDR_SHIFT) & SEG_MASK; + + /* TODO: Only used for current POLLed segments to LPNs */ + + l_debug("SEQ: %6.6x", seq + segO); + l_debug("SEQ0: %6.6x", seq); + l_debug("segO: %d", segO); + + if (!mesh_crypto_packet_build(false, ttl, + seq, + src, dst, + 0, + segmented, app_key_id, + szmic, false, seqZero, + segO, segN, + seg, seg_len, + packet + 1, &packet_len)) { + l_error("Failed to build packet"); + return; + } + + if (!net_key_encrypt(net_key_id, iv_index, packet + 1, packet_len)) { + l_error("Failed to encode packet"); + return; + } + + /* print_packet("Step 4", packet + 1, packet_len); */ + + send_msg_pkt(net, packet, packet_len + 1); + + str = l_util_hexstring(packet + 1, packet_len); + l_info("TX: Friend Seg-%d %04x -> %04x : %s (%u) : TTL %d : SEQ %06x", + segO, src, dst, str, packet_len, ttl, seq); + l_free(str); +} + +bool mesh_net_app_send(struct mesh_net *net, bool frnd_cred, uint16_t src, + uint16_t dst, uint8_t key_aid, uint16_t net_idx, + uint8_t ttl, uint32_t seq, uint32_t iv_index, + bool szmic, const void *msg, uint16_t msg_len, + mesh_net_status_func_t status_func, + void *user_data) +{ + struct mesh_sar *payload = NULL; + uint8_t seg, seg_max; + bool result; + + if (!net || msg_len > 384) + return false; + + if (!src) + src = net->src_addr; + + if (!src || !dst) + return false; + + if (ttl == DEFAULT_TTL) + ttl = net->default_ttl; + + seg_max = SEG_MAX(msg_len); + + /* First enqueue to any Friends and internal models */ + result = msg_rxed(net, false, iv_index, ttl, + seq + seg_max, + net_idx, + src, dst, + key_aid, + szmic, seq & SEQ_ZERO_MASK, + msg, msg_len); + + /* If successfully enqued or delivered + * to Unicast address, we are done + */ + if (result || src == dst || + (dst >= net->src_addr && dst <= net->last_addr)) { + /* Adjust our seq_num for "virtual" delivery */ + net->seq_num += seg_max; + mesh_net_next_seq_num(net); + return true; + } + + /* If Segmented, Cancel any OB segmented message to same DST */ + if (seg_max) { + payload = l_queue_remove_if(net->sar_out, match_sar_remote, + L_UINT_TO_PTR(dst)); + mesh_sar_free(payload); + } + + /* Setup OTA Network send */ + payload = mesh_sar_new(msg_len); + memcpy(payload->buf, msg, msg_len); + payload->len = msg_len; + payload->src = src; + payload->remote = dst; + payload->ttl = ttl; + payload->szmic = szmic; + payload->frnd_cred = frnd_cred; + payload->key_aid = key_aid; + if (seg_max) { + payload->flags = 0xffffffff >> (31 - seg_max); + payload->seqZero = seq & SEQ_ZERO_MASK; + } + + payload->iv_index = mesh_net_get_iv_index(net); + payload->seqAuth = net->seq_num; + + result = true; + if (!IS_UNICAST(dst) && seg_max) { + int i; + + for (i = 0; i < 4; i++) { + for (seg = 0; seg <= seg_max && result; seg++) + result = send_seg(net, payload, seg); + } + } else { + for (seg = 0; seg <= seg_max && result; seg++) + result = send_seg(net, payload, seg); + } + + /* Reliable: Cache; Unreliable: Flush*/ + if (result && seg_max && IS_UNICAST(dst)) { + l_queue_push_head(net->sar_out, payload); + payload->seg_timeout = + l_timeout_create(SEG_TO, outseg_to, net, NULL); + payload->msg_timeout = + l_timeout_create(MSG_TO, outmsg_to, net, NULL); + payload->status_func = status_func; + payload->user_data = user_data; + payload->id = ++net->sar_id_next; + } else + mesh_sar_free(payload); + + return result; +} + +void mesh_net_ack_send(struct mesh_net *net, uint32_t key_id, + uint32_t iv_index, + uint8_t ttl, + uint32_t seq, + uint16_t src, uint16_t dst, + bool rly, uint16_t seqZero, + uint32_t ack_flags) +{ + uint32_t hdr; + uint8_t data[7]; + uint8_t pkt_len; + uint8_t pkt[30]; + char *str; + + hdr = NET_OP_SEG_ACKNOWLEDGE << OPCODE_HDR_SHIFT; + hdr |= rly << RELAY_HDR_SHIFT; + hdr |= (seqZero & SEQ_ZERO_MASK) << SEQ_ZERO_HDR_SHIFT; + l_put_be32(hdr, data); + l_put_be32(ack_flags, data + 3); + if (!mesh_crypto_packet_build(true, ttl, + seq, + src, dst, + NET_OP_SEG_ACKNOWLEDGE, + false, /* Not Segmented */ + 0, /* No Key ID associated */ + false, rly, seqZero, + 0, 0, /* no segO or segN */ + data + 1, 6, + pkt + 1, &pkt_len)) { + return; + } + + if (!key_id) { + struct mesh_subnet *subnet = get_primary_subnet(net); + + key_id = subnet->net_key_tx; + } + + if (!net_key_encrypt(key_id, iv_index, pkt + 1, pkt_len)) { + l_error("Failed to encode packet"); + return; + } + + /* print_packet("Step 4", pkt, pkt_len); */ + send_msg_pkt(net, pkt, pkt_len + 1); + + str = l_util_hexstring(pkt + 1, pkt_len); + l_info("TX: Friend ACK %04x -> %04x : %s (%u) : TTL %d : SEQ %06x", + src, dst, str, pkt_len, + ttl, seq); + l_free(str); +} + +/* TODO: add net key index */ +void mesh_net_transport_send(struct mesh_net *net, uint32_t key_id, + bool fast, uint32_t iv_index, uint8_t ttl, + uint32_t seq, uint16_t src, uint16_t dst, + const uint8_t *msg, uint16_t msg_len) +{ + uint32_t use_seq = seq; + uint8_t pkt_len; + uint8_t pkt[30]; + bool result = false; + + if (!net->src_addr) + return; + + if (!src) + src = net->src_addr; + + if (src == dst) + return; + + if (ttl == DEFAULT_TTL) + ttl = net->default_ttl; + + /* Range check the Opcode and msg length*/ + if (*msg & 0xc0 || (9 + msg_len + 8 > 29)) + return; + + /* Enqueue for Friend if forwardable and from us */ + if (!key_id && src >= net->src_addr && src <= net->last_addr) { + uint32_t hdr = msg[0] << OPCODE_HDR_SHIFT; + uint8_t frnd_ttl = ttl; + + if (friend_packet_queue(net, iv_index, + true, frnd_ttl, + mesh_net_next_seq_num(net), + src, dst, + hdr, + msg + 1, msg_len - 1)) { + return; + } + } + + /* Deliver to Local entities if applicable */ + if (!(dst & 0x8000) && src >= net->src_addr && src <= net->last_addr) { + result = ctl_received(net, !!(key_id), + iv_index, ttl, + mesh_net_next_seq_num(net), + src, dst, + msg[0], 0, msg + 1, msg_len - 1); + } + + if (!key_id) { + struct mesh_subnet *subnet = get_primary_subnet(net); + + key_id = subnet->net_key_tx; + use_seq = mesh_net_next_seq_num(net); + + if (result || (dst >= net->src_addr && dst <= net->last_addr)) + return; + } + + if (!mesh_crypto_packet_build(true, ttl, + use_seq, + src, dst, + msg[0], + false, 0, + false, false, 0, + 0, 0, + msg + 1, msg_len - 1, + pkt + 1, &pkt_len)) + return; + + /* print_packet("Step 2", pkt + 1, pkt_len); */ + + if (!net_key_encrypt(key_id, iv_index, pkt + 1, pkt_len)) { + l_error("Failed to encode packet"); + return; + } + + /* print_packet("Step 4", pkt + 1, pkt_len); */ + + if (dst != 0) { + char *str; + + send_msg_pkt(net, pkt, pkt_len + 1); + + str = l_util_hexstring(pkt + 1, pkt_len); + l_info("TX: Network %04x -> %04x : %s (%u) : TTL %d : SEQ %06x", + src, dst, str, pkt_len, + ttl, use_seq); + l_free(str); + } +} + +uint8_t mesh_net_key_refresh_phase_set(struct mesh_net *net, uint16_t idx, + uint8_t transition) +{ + struct mesh_subnet *subnet; + + if (!net) + return MESH_STATUS_UNSPECIFIED_ERROR; + + subnet = l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)); + if (!subnet) + return MESH_STATUS_INVALID_NETKEY; + + if (transition == subnet->kr_phase) + return MESH_STATUS_SUCCESS; + + if ((transition != 2 && transition != 3) || + transition < subnet->kr_phase) + return MESH_STATUS_CANNOT_SET; + + switch (transition) { + case 2: + if (key_refresh_phase_two(net, idx) + != MESH_STATUS_SUCCESS) + return MESH_STATUS_CANNOT_SET; + break; + case 3: + if (key_refresh_finish(net, idx) + != MESH_STATUS_SUCCESS) + return MESH_STATUS_CANNOT_SET; + break; + default: + return MESH_STATUS_CANNOT_SET; + } + + return MESH_STATUS_SUCCESS; +} + +uint8_t mesh_net_key_refresh_phase_get(struct mesh_net *net, uint16_t idx, + uint8_t *phase) +{ + struct mesh_subnet *subnet; + + if (!net) + return MESH_STATUS_UNSPECIFIED_ERROR; + + subnet = l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)); + if (!subnet) + return MESH_STATUS_INVALID_NETKEY; + + *phase = subnet->kr_phase; + return MESH_STATUS_SUCCESS; +} + +/* + * This function is called when Configuration Server Model receives + * a NETKEY_UPDATE command + */ +int mesh_net_update_key(struct mesh_net *net, uint16_t idx, + const uint8_t *value) +{ + struct mesh_subnet *subnet; + + if (!net) + return MESH_STATUS_UNSPECIFIED_ERROR; + + subnet = l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)); + + if (!subnet) + return MESH_STATUS_INVALID_NETKEY; + + /* Check if the key has been already successfully updated */ + if (subnet->kr_phase == KEY_REFRESH_PHASE_ONE && + net_key_confirm(subnet->net_key_upd, value)) + return MESH_STATUS_SUCCESS; + + if (subnet->kr_phase != KEY_REFRESH_PHASE_NONE) + return MESH_STATUS_CANNOT_UPDATE; + + if (subnet->net_key_upd) { + net_key_unref(subnet->net_key_upd); + l_info("Warning: overwriting new keys"); + } + + /* Preserve starting data */ + subnet->net_key_upd = net_key_add(value); + + if (!subnet->net_key_upd) { + l_error("Failed to start key refresh phase one"); + return MESH_STATUS_CANNOT_UPDATE; + } + + /* If we are an LPN, generate our keys here */ + if (net->friend_addr) + frnd_key_refresh(net, 1); + else + /* If we are a Friend-Node, generate all our new keys */ + l_queue_foreach(net->friends, frnd_kr_phase1, + L_UINT_TO_PTR(subnet->net_key_upd)); + + l_info("key refresh phase 1: Key ID %d", subnet->net_key_upd); + + if (!mesh_config_net_key_update(node_config_get(net->node), idx, value)) + return MESH_STATUS_STORAGE_FAIL; + + subnet->kr_phase = KEY_REFRESH_PHASE_ONE; + + return MESH_STATUS_SUCCESS; +} + +uint16_t mesh_net_get_features(struct mesh_net *net) +{ + uint16_t features = 0; + + if (net->relay.enable) + features |= FEATURE_RELAY; + if (net->proxy_enable) + features |= FEATURE_PROXY; + if (!l_queue_isempty(net->friends)) + features |= FEATURE_FRIEND; + if (net->friend_addr != UNASSIGNED_ADDRESS) + features |= FEATURE_LPN; + + return features; +} + +struct mesh_net_heartbeat *mesh_net_heartbeat_get(struct mesh_net *net) +{ + return &net->heartbeat; +} + +void mesh_net_heartbeat_send(struct mesh_net *net) +{ + struct mesh_net_heartbeat *hb = &net->heartbeat; + uint8_t msg[4]; + int n = 0; + + if (hb->pub_dst == UNASSIGNED_ADDRESS) + return; + + msg[n++] = NET_OP_HEARTBEAT; + msg[n++] = hb->pub_ttl; + l_put_be16(hb->features, msg + n); + n += 2; + + mesh_net_transport_send(net, 0, false, mesh_net_get_iv_index(net), + hb->pub_ttl, 0, 0, hb->pub_dst, msg, n); +} + +void mesh_net_heartbeat_init(struct mesh_net *net) +{ + struct mesh_net_heartbeat *hb = &net->heartbeat; + + memset(hb, 0, sizeof(struct mesh_net_heartbeat)); + hb->sub_min_hops = 0xff; + hb->features = mesh_net_get_features(net); +} + +void mesh_net_uni_range_set(struct mesh_net *net, + struct mesh_net_addr_range *range) +{ + net->prov_uni_addr.low = range->low; + net->prov_uni_addr.high = range->high; + net->prov_uni_addr.next = range->next; +} + +struct mesh_net_addr_range mesh_net_uni_range_get(struct mesh_net *net) +{ + return net->prov_uni_addr; +} + +void mesh_net_set_iv_index(struct mesh_net *net, uint32_t index, bool update) +{ + net->iv_index = index; + net->iv_update = update; +} + +void mesh_net_provisioner_mode_set(struct mesh_net *net, bool mode) +{ + net->provisioner = mode; +} + +bool mesh_net_provisioner_mode_get(struct mesh_net *net) +{ + return net->provisioner; +} + +uint16_t mesh_net_get_primary_idx(struct mesh_net *net) +{ + struct mesh_subnet *subnet; + + if (!net) + return NET_IDX_INVALID; + + subnet = get_primary_subnet(net); + if (!subnet) + return NET_IDX_INVALID; + + return subnet->idx; +} + +uint32_t mesh_net_friend_timeout(struct mesh_net *net, uint16_t addr) +{ + struct mesh_friend *frnd = l_queue_find(net->friends, match_by_friend, + L_UINT_TO_PTR(addr)); + + if (!frnd) + return 0; + else + return frnd->poll_timeout; +} + +struct mesh_node *mesh_net_node_get(struct mesh_net *net) +{ + return net->node; +} + +struct l_queue *mesh_net_get_app_keys(struct mesh_net *net) +{ + if (!net) + return NULL; + + if (!net->app_keys) + net->app_keys = l_queue_new(); + + return net->app_keys; +} + +bool mesh_net_have_key(struct mesh_net *net, uint16_t idx) +{ + if (!net) + return false; + + return (l_queue_find(net->subnets, match_key_index, + L_UINT_TO_PTR(idx)) != NULL); +} + +bool mesh_net_is_local_address(struct mesh_net *net, uint16_t src, + uint16_t count) +{ + const uint16_t last = src + count - 1; + if (!net) + return false; + + return (src >= net->src_addr && src <= net->last_addr) && + (last >= net->src_addr && last <= net->last_addr); +} + +void mesh_net_set_window_accuracy(struct mesh_net *net, uint8_t accuracy) +{ + if (!net) + return; + + net->window_accuracy = accuracy; +} + +void mesh_net_transmit_params_set(struct mesh_net *net, uint8_t count, + uint16_t interval) +{ + if (!net) + return; + + net->tx_interval = interval; + net->tx_cnt = count; +} + +void mesh_net_transmit_params_get(struct mesh_net *net, uint8_t *count, + uint16_t *interval) +{ + if (!net) + return; + + *interval = net->tx_interval; + *count = net->tx_cnt; +} + +struct mesh_io *mesh_net_get_io(struct mesh_net *net) +{ + if (!net) + return NULL; + + return net->io; +} + +struct mesh_prov *mesh_net_get_prov(struct mesh_net *net) +{ + if (!net) + return NULL; + + return net->prov; +} + +void mesh_net_set_prov(struct mesh_net *net, struct mesh_prov *prov) +{ + if (!net) + return; + + net->prov = prov; +} + +uint32_t mesh_net_get_instant(struct mesh_net *net) +{ + return net->instant; +} diff --git a/mesh/net.h b/mesh/net.h new file mode 100644 index 0000000..150240f --- /dev/null +++ b/mesh/net.h @@ -0,0 +1,362 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 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. + * + */ + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +struct mesh_io; +struct mesh_node; + +#define DEV_ID 0 + +#define UNUSED_KEY_IDX 0xffff + +#define APP_AID_DEV 0x00 + +#define CTL 0x80 +#define SEQ_MASK 0xffffff + +#define CREDFLAG_MASK 0x1000 + +#define KEY_CACHE_SIZE 64 +#define FRND_CACHE_MAX 32 + +#define MAX_UNSEG_LEN 15 /* msg_len == 11 + sizeof(MIC) */ +#define MAX_SEG_LEN 12 /* UnSeg length - 3 octets overhead */ +#define SEG_MAX(len) (((len) <= MAX_UNSEG_LEN) ? 0 : \ + (((len) - 1) / MAX_SEG_LEN)) +#define SEG_OFF(seg) ((seg) * MAX_SEG_LEN) +#define MAX_SEG_TO_LEN(seg) ((seg) ? SEG_OFF((seg) + 1) : MAX_UNSEG_LEN) + +#define SEGMENTED 0x80 +#define UNSEGMENTED 0x00 +#define SEG_HDR_SHIFT 31 +#define IS_SEGMENTED(hdr) (!!((hdr) & (true << SEG_HDR_SHIFT))) + +#define KEY_ID_MASK 0x7f +#define KEY_AID_MASK 0x3f +#define KEY_ID_AKF 0x40 +#define KEY_AID_SHIFT 0 +#define AKF_HDR_SHIFT 30 +#define KEY_HDR_SHIFT 24 +#define HAS_APP_KEY(hdr) (!!((hdr) & (true << AKF_HDR_SHIFT))) + +#define OPCODE_MASK 0x7f +#define OPCODE_HDR_SHIFT 24 +#define RELAY 0x80 +#define RELAY_HDR_SHIFT 23 +#define SZMIC 0x80 +#define SZMIC_HDR_SHIFT 23 +#define SEQ_ZERO_MASK 0x1fff +#define SEQ_ZERO_HDR_SHIFT 10 +#define IS_RELAYED(hdr) (!!((hdr) & (true << RELAY_HDR_SHIFT))) +#define HAS_MIC64(hdr) (!!((hdr) & (true << SZMIC_HDR_SHIFT))) + +#define SEG_MASK 0x1f +#define SEGO_HDR_SHIFT 5 +#define SEGN_HDR_SHIFT 0 +#define SEG_TOTAL(hdr) (((hdr) >> SEGN_HDR_SHIFT) & SEG_MASK) + +/* Mask of Hdr bits which must be constant over entire incoming SAR message */ +/* (SEG || AKF || AID || SZMIC || SeqZero || SegN) */ +#define HDR_KEY_MASK ((true << SEG_HDR_SHIFT) | \ + (KEY_ID_MASK << KEY_HDR_SHIFT) | \ + (true << SZMIC_HDR_SHIFT) | \ + (SEQ_ZERO_MASK << SEQ_ZERO_HDR_SHIFT) | \ + (SEG_MASK << SEGN_HDR_SHIFT)) + +#define HDR_ACK_MASK ((OPCODE_MASK << OPCODE_HDR_SHIFT) | \ + (SEQ_ZERO_MASK << SEQ_ZERO_HDR_SHIFT)) + + + +#define MSG_CACHE_SIZE 70 +#define REPLAY_CACHE_SIZE 10 + +/* Proxy Configuration Opcodes */ +#define PROXY_OP_SET_FILTER_TYPE 0x00 +#define PROXY_OP_FILTER_ADD 0x01 +#define PROXY_OP_FILTER_DEL 0x02 +#define PROXY_OP_FILTER_STATUS 0x03 + +/* Proxy Filter Defines */ +#define PROXY_FILTER_WHITELIST 0x00 +#define PROXY_FILTER_BLACKLIST 0x01 + +/* Network Tranport Opcodes */ +#define NET_OP_SEG_ACKNOWLEDGE 0x00 +#define NET_OP_FRND_POLL 0x01 +#define NET_OP_FRND_UPDATE 0x02 +#define NET_OP_FRND_REQUEST 0x03 +#define NET_OP_FRND_OFFER 0x04 +#define NET_OP_FRND_CLEAR 0x05 +#define NET_OP_FRND_CLEAR_CONFIRM 0x06 + +#define NET_OP_PROXY_SUB_ADD 0x07 +#define NET_OP_PROXY_SUB_REMOVE 0x08 +#define NET_OP_PROXY_SUB_CONFIRM 0x09 +#define NET_OP_HEARTBEAT 0x0a + +#define FRND_OPCODE(x) \ + ((x) >= NET_OP_FRND_POLL && (x) <= NET_OP_FRND_CLEAR_CONFIRM) + +struct mesh_net_addr_range { + uint16_t low; + uint16_t high; + uint16_t next; +}; + +struct mesh_net_prov_caps { + uint8_t num_ele; + uint16_t algorithms; + uint8_t pub_type; + uint8_t static_type; + uint8_t output_size; + uint16_t output_action; + uint8_t input_size; + uint16_t input_action; +} __packed; + +struct mesh_net_heartbeat { + struct l_timeout *pub_timer; + struct l_timeout *sub_timer; + struct timeval sub_time; + bool sub_enabled; + uint32_t pub_period; + uint32_t sub_period; + uint32_t sub_start; + uint16_t pub_dst; + uint16_t pub_count; + uint16_t pub_features; + uint16_t features; + uint16_t pub_net_idx; + uint16_t sub_src; + uint16_t sub_dst; + uint16_t sub_count; + uint8_t pub_ttl; + uint8_t sub_min_hops; + uint8_t sub_max_hops; +}; + +struct mesh_key_set { + bool frnd; + uint8_t nid; + uint8_t enc_key[16]; + uint8_t privacy_key[16]; +}; + +struct mesh_friend { + struct mesh_net *net; + struct l_queue *pkt_cache; + struct l_timeout *timeout; + void *pkt; + uint16_t *grp_list; + uint32_t poll_timeout; + uint32_t last_hdr; + uint32_t net_key_cur; + uint32_t net_key_upd; + uint16_t dst; /* Primary Element unicast addr */ + uint16_t fn_cnt; + uint16_t lp_cnt; + int16_t grp_cnt; + uint8_t ele_cnt; + uint8_t frd; + uint8_t frw; + bool seq; + bool last; +}; + +struct mesh_frnd_pkt { + uint32_t iv_index; + uint32_t seq; + uint16_t src; + uint16_t dst; + uint16_t size; + uint8_t segN; + uint8_t segO; + uint8_t ttl; + uint8_t tc; + bool szmict; + union { + struct { + uint8_t key_id; + } m; + struct { + uint16_t seq0; + } a; + struct { + uint8_t opcode; + } c; + } u; + uint8_t data[]; +}; + +struct mesh_friend_seg_one { + uint32_t hdr; + uint32_t seq; + bool sent; + bool md; + uint8_t data[15]; +}; + +struct mesh_friend_seg_12 { + uint32_t hdr; + uint32_t seq; + bool sent; + bool md; + uint8_t data[12]; +}; + +struct mesh_friend_msg { + uint32_t iv_index; + uint32_t flags; + uint16_t src; + uint16_t dst; + uint8_t ttl; + uint8_t cnt_in; + uint8_t cnt_out; + uint8_t last_len; + bool done; + bool ctl; + union { + struct mesh_friend_seg_one one[1]; /* Single segment */ + struct mesh_friend_seg_12 s12[0]; /* Array of segments */ + } u; +}; + +typedef void (*mesh_status_func_t)(void *user_data, bool result); +typedef void (*mesh_net_status_func_t)(uint16_t remote, uint8_t status, + void *data, uint16_t size, + void *user_data); + +struct mesh_net *mesh_net_new(struct mesh_node *node); +void mesh_net_free(struct mesh_net *net); +void mesh_net_flush_msg_queues(struct mesh_net *net); +void mesh_net_set_iv_index(struct mesh_net *net, uint32_t index, bool update); +bool mesh_net_iv_index_update(struct mesh_net *net); +bool mesh_net_set_seq_num(struct mesh_net *net, uint32_t number); +uint32_t mesh_net_get_seq_num(struct mesh_net *net); +uint32_t mesh_net_next_seq_num(struct mesh_net *net); +bool mesh_net_set_default_ttl(struct mesh_net *net, uint8_t ttl); +uint8_t mesh_net_get_default_ttl(struct mesh_net *net); +bool mesh_net_get_frnd_seq(struct mesh_net *net); +void mesh_net_set_frnd_seq(struct mesh_net *net, bool seq); +uint16_t mesh_net_get_address(struct mesh_net *net); +bool mesh_net_register_unicast(struct mesh_net *net, + uint16_t unicast, uint8_t num_ele); +bool mesh_net_set_friend(struct mesh_net *net, uint16_t friend_addr); +uint16_t mesh_net_get_friend(struct mesh_net *net); +uint8_t mesh_net_get_num_ele(struct mesh_net *net); +bool mesh_net_set_beacon_mode(struct mesh_net *net, bool enable); +bool mesh_net_set_proxy_mode(struct mesh_net *net, bool enable); +bool mesh_net_set_relay_mode(struct mesh_net *net, bool enable, uint8_t cnt, + uint8_t interval); +bool mesh_net_set_friend_mode(struct mesh_net *net, bool enable); +int mesh_net_del_key(struct mesh_net *net, uint16_t net_idx); +int mesh_net_add_key(struct mesh_net *net, uint16_t net_idx, + const uint8_t *key); +int mesh_net_update_key(struct mesh_net *net, uint16_t net_idx, + const uint8_t *key); +bool mesh_net_set_key(struct mesh_net *net, uint16_t idx, const uint8_t *key, + const uint8_t *new_key, uint8_t phase); +uint32_t mesh_net_get_iv_index(struct mesh_net *net); +void mesh_net_get_snb_state(struct mesh_net *net, + uint8_t *flags, uint32_t *iv_index); +bool mesh_net_get_key(struct mesh_net *net, bool new_key, uint16_t idx, + uint32_t *key_id); +bool mesh_net_attach(struct mesh_net *net, struct mesh_io *io); +struct mesh_io *mesh_net_detach(struct mesh_net *net); +struct l_queue *mesh_net_get_app_keys(struct mesh_net *net); + +bool mesh_net_flush(struct mesh_net *net); +void mesh_net_transport_send(struct mesh_net *net, uint32_t key_id, + bool fast, uint32_t iv_index, uint8_t ttl, + uint32_t seq, uint16_t src, uint16_t dst, + const uint8_t *msg, uint16_t msg_len); + +bool mesh_net_app_send(struct mesh_net *net, bool frnd_cred, uint16_t src, + uint16_t dst, uint8_t key_id, uint16_t net_idx, + uint8_t ttl, uint32_t seq, uint32_t iv_index, + bool szmic, const void *msg, uint16_t msg_len, + mesh_net_status_func_t status_func, + void *user_data); +void mesh_net_ack_send(struct mesh_net *net, uint32_t key_id, + uint32_t iv_index, uint8_t ttl, uint32_t seq, + uint16_t src, uint16_t dst, bool rly, + uint16_t seqZero, uint32_t ack_flags); +struct mesh_net_prov_caps *mesh_net_prov_caps_get(struct mesh_net *net); +uint8_t *mesh_net_priv_key_get(struct mesh_net *net); +bool mesh_net_priv_key_set(struct mesh_net *net, uint8_t key[32]); +uint8_t *mesh_net_prov_rand(struct mesh_net *net); +uint16_t mesh_net_prov_uni(struct mesh_net *net, uint8_t ele_cnt); +bool mesh_net_id_uuid_set(struct mesh_net *net, uint8_t uuid[16]); +uint8_t *mesh_net_test_addr(struct mesh_net *net); +int mesh_net_get_identity_mode(struct mesh_net *net, uint16_t idx, + uint8_t *mode); +char *mesh_net_id_name(struct mesh_net *net); +bool mesh_net_test_mode(struct mesh_net *net); +bool mesh_net_dst_reg(struct mesh_net *net, uint16_t dst); +bool mesh_net_dst_unreg(struct mesh_net *net, uint16_t dst); +struct mesh_friend *mesh_friend_new(struct mesh_net *net, uint16_t dst, + uint8_t ele_cnt, uint8_t frd, + uint8_t frw, uint32_t fpt, + uint16_t fn_cnt, uint16_t lp_cnt); +void mesh_friend_free(void *frnd); +bool mesh_friend_clear(struct mesh_net *net, struct mesh_friend *frnd); +void mesh_friend_sub_add(struct mesh_net *net, uint16_t lpn, uint8_t ele_cnt, + uint8_t grp_cnt, + const uint8_t *list); +void mesh_friend_sub_del(struct mesh_net *net, uint16_t lpn, uint8_t cnt, + const uint8_t *del_list); +uint8_t mesh_net_key_refresh_phase_set(struct mesh_net *net, uint16_t net_idx, + uint8_t transition); +uint8_t mesh_net_key_refresh_phase_get(struct mesh_net *net, uint16_t net_idx, + uint8_t *phase); +void mesh_net_send_seg(struct mesh_net *net, uint32_t key_id, + uint32_t iv_index, uint8_t ttl, uint32_t seq, + uint16_t src, uint16_t dst, uint32_t hdr, + const void *seg, uint16_t seg_len); +uint16_t mesh_net_get_features(struct mesh_net *net); +struct mesh_net_heartbeat *mesh_net_heartbeat_get(struct mesh_net *net); +void mesh_net_heartbeat_init(struct mesh_net *net); +void mesh_net_heartbeat_send(struct mesh_net *net); +void mesh_net_uni_range_set(struct mesh_net *net, + struct mesh_net_addr_range *range); +struct mesh_net_addr_range mesh_net_uni_range_get(struct mesh_net *net); +void mesh_net_provisioner_mode_set(struct mesh_net *net, bool mode); +bool mesh_net_provisioner_mode_get(struct mesh_net *net); +bool mesh_net_key_list_get(struct mesh_net *net, uint8_t *buf, uint16_t *count); +uint16_t mesh_net_get_primary_idx(struct mesh_net *net); +void mesh_net_sub_list_add(struct mesh_net *net, uint16_t addr); +void mesh_net_sub_list_del(struct mesh_net *net, uint16_t addr); +uint32_t mesh_net_friend_timeout(struct mesh_net *net, uint16_t addr); +struct mesh_io *mesh_net_get_io(struct mesh_net *net); +struct mesh_node *mesh_net_node_get(struct mesh_net *net); +bool mesh_net_have_key(struct mesh_net *net, uint16_t net_idx); +bool mesh_net_is_local_address(struct mesh_net *net, uint16_t src, + uint16_t count); +void mesh_net_set_window_accuracy(struct mesh_net *net, uint8_t accuracy); +void mesh_net_transmit_params_set(struct mesh_net *net, uint8_t count, + uint16_t interval); +void mesh_net_transmit_params_get(struct mesh_net *net, uint8_t *count, + uint16_t *interval); +struct mesh_prov *mesh_net_get_prov(struct mesh_net *net); +void mesh_net_set_prov(struct mesh_net *net, struct mesh_prov *prov); +uint32_t mesh_net_get_instant(struct mesh_net *net); diff --git a/mesh/node.c b/mesh/node.c new file mode 100644 index 0000000..e23f32d --- /dev/null +++ b/mesh/node.c @@ -0,0 +1,2443 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2017-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include "mesh/mesh-defs.h" +#include "mesh/mesh.h" +#include "mesh/net.h" +#include "mesh/appkey.h" +#include "mesh/mesh-config.h" +#include "mesh/provision.h" +#include "mesh/keyring.h" +#include "mesh/model.h" +#include "mesh/cfgmod.h" +#include "mesh/util.h" +#include "mesh/error.h" +#include "mesh/dbus.h" +#include "mesh/agent.h" +#include "mesh/node.h" + +#define MIN_COMP_SIZE 14 + +#define MESH_NODE_PATH_PREFIX "/node" + +/* Default values for a new locally created node */ +#define DEFAULT_NEW_UNICAST 0x0001 +#define DEFAULT_IV_INDEX 0x0000 + +/* Default element location: unknown */ +#define DEFAULT_LOCATION 0x0000 + +#define DEFAULT_CRPL 10 +#define DEFAULT_SEQUENCE_NUMBER 0 + +enum request_type { + REQUEST_TYPE_JOIN, + REQUEST_TYPE_ATTACH, + REQUEST_TYPE_CREATE, + REQUEST_TYPE_IMPORT, +}; + +struct node_element { + char *path; + struct l_queue *models; + uint16_t location; + uint8_t idx; +}; + +struct node_composition { + uint16_t cid; + uint16_t pid; + uint16_t vid; + uint16_t crpl; +}; + +struct mesh_node { + struct mesh_net *net; + struct l_queue *elements; + char *app_path; + char *owner; + char *obj_path; + struct mesh_agent *agent; + struct mesh_config *cfg; + char *storage_dir; + uint32_t disc_watch; + uint32_t seq_number; + bool provisioner; + uint16_t primary; + struct node_composition *comp; + struct { + uint16_t interval; + uint8_t cnt; + uint8_t mode; + } relay; + uint8_t uuid[16]; + uint8_t dev_key[16]; + uint8_t token[8]; + uint8_t num_ele; + uint8_t ttl; + uint8_t lpn; + uint8_t proxy; + uint8_t friend; + uint8_t beacon; +}; + +struct node_import { + uint8_t dev_key[16]; + uint8_t net_key[16]; + uint16_t net_idx; + struct { + bool ivu; + bool kr; + } flags; + uint32_t iv_index; + uint16_t unicast; +}; + +struct managed_obj_request { + struct mesh_node *node; + union { + node_ready_func_t ready_cb; + node_join_ready_func_t join_ready_cb; + }; + struct l_dbus_message *pending_msg; + enum request_type type; + union { + struct mesh_node *attach; + struct node_import *import; + }; +}; + +static struct l_queue *nodes; + +static bool match_node_unicast(const void *a, const void *b) +{ + const struct mesh_node *node = a; + uint16_t dst = L_PTR_TO_UINT(b); + + return (dst >= node->primary && + dst <= (node->primary + node->num_ele - 1)); +} + +static bool match_device_uuid(const void *a, const void *b) +{ + const struct mesh_node *node = a; + const uint8_t *uuid = b; + + return (memcmp(node->uuid, uuid, 16) == 0); +} + +static bool match_token(const void *a, const void *b) +{ + const struct mesh_node *node = a; + const uint64_t *token = b; + const uint64_t tmp = l_get_be64(node->token); + + return *token == tmp; +} + +static bool match_element_idx(const void *a, const void *b) +{ + const struct node_element *element = a; + uint32_t index = L_PTR_TO_UINT(b); + + return (element->idx == index); +} + +static int compare_element_idx(const void *a, const void *b, void *user_data) +{ + uint32_t a_idx = ((const struct node_element *)a)->idx; + uint32_t b_idx = ((const struct node_element *)b)->idx; + + if (a_idx < b_idx) + return -1; + + if (a_idx > b_idx) + return 1; + + return 0; +} + +static bool match_element_path(const void *a, const void *b) +{ + const struct node_element *element = a; + const char *path = b; + + if (!element->path) + return false; + + return (!strcmp(element->path, path)); +} + +static bool match_model_id(const void *a, const void *b) +{ + const struct mesh_model *mod = a; + uint32_t mod_id = L_PTR_TO_UINT(b); + + return mesh_model_get_model_id(mod) == mod_id; +} + +static int compare_model_id(const void *a, const void *b, void *user_data) +{ + uint32_t a_id = mesh_model_get_model_id(a); + uint32_t b_id = mesh_model_get_model_id(b); + + if (a_id < b_id) + return -1; + + if (a_id > b_id) + return 1; + + return 0; +} + + +struct mesh_node *node_find_by_addr(uint16_t addr) +{ + if (!IS_UNICAST(addr)) + return NULL; + + return l_queue_find(nodes, match_node_unicast, L_UINT_TO_PTR(addr)); +} + +struct mesh_node *node_find_by_uuid(uint8_t uuid[16]) +{ + return l_queue_find(nodes, match_device_uuid, uuid); +} + +struct mesh_node *node_find_by_token(uint64_t token) +{ + return l_queue_find(nodes, match_token, (void *) &token); +} + +uint8_t *node_uuid_get(struct mesh_node *node) +{ + if (!node) + return NULL; + return node->uuid; +} + +static struct mesh_node *node_new(const uint8_t uuid[16]) +{ + struct mesh_node *node; + + node = l_new(struct mesh_node, 1); + node->net = mesh_net_new(node); + memcpy(node->uuid, uuid, sizeof(node->uuid)); + + return node; +} + +static void free_element_path(void *a, void *b) +{ + struct node_element *element = a; + + l_free(element->path); + element->path = NULL; +} + +static void element_free(void *data) +{ + struct node_element *element = data; + + l_queue_destroy(element->models, mesh_model_free); + l_free(element->path); + l_free(element); +} + +static void free_node_dbus_resources(struct mesh_node *node) +{ + if (!node) + return; + + if (node->disc_watch) { + l_dbus_remove_watch(dbus_get_bus(), node->disc_watch); + node->disc_watch = 0; + } + + l_queue_foreach(node->elements, free_element_path, NULL); + l_free(node->owner); + node->owner = NULL; + l_free(node->app_path); + node->app_path = NULL; + + if (node->obj_path) { + l_dbus_object_remove_interface(dbus_get_bus(), node->obj_path, + MESH_NODE_INTERFACE); + + l_dbus_object_remove_interface(dbus_get_bus(), node->obj_path, + MESH_MANAGEMENT_INTERFACE); + l_free(node->obj_path); + node->obj_path = NULL; + } +} + +static void free_node_resources(void *data) +{ + struct mesh_node *node = data; + + /* Unregister io callbacks */ + if (node->net) + mesh_net_detach(node->net); + + l_queue_destroy(node->elements, element_free); + node->elements = NULL; + + free_node_dbus_resources(node); + + mesh_net_free(node->net); + l_free(node->comp); + l_free(node->storage_dir); + l_free(node); +} + +/* + * This function is called to free resources and remove the + * configuration files for the specified node. + */ +void node_remove(struct mesh_node *node) +{ + if (!node) + return; + + l_queue_remove(nodes, node); + + if (node->cfg) + mesh_config_destroy(node->cfg); + + free_node_resources(node); +} + +static bool element_add_model(struct node_element *ele, struct mesh_model *mod) +{ + uint32_t mod_id = mesh_model_get_model_id(mod); + + if (l_queue_find(ele->models, match_model_id, L_UINT_TO_PTR(mod_id))) + return false; + + l_queue_insert(ele->models, mod, compare_model_id, NULL); + return true; +} + +static bool add_models(struct mesh_node *node, struct node_element *ele, + struct mesh_config_element *db_ele) +{ + const struct l_queue_entry *entry; + + if (!ele->models) + ele->models = l_queue_new(); + + entry = l_queue_get_entries(db_ele->models); + for (; entry; entry = entry->next) { + struct mesh_model *mod; + struct mesh_config_model *db_mod; + + db_mod = entry->data; + mod = mesh_model_setup(node, ele->idx, db_mod); + if (!mod) + return false; + + if (!element_add_model(ele, mod)) { + mesh_model_free(mod); + return false; + } + } + + return true; +} + +static void add_internal_model(struct mesh_node *node, uint32_t mod_id, + uint8_t ele_idx) +{ + struct node_element *ele; + struct mesh_model *mod; + struct mesh_config_model db_mod; + + ele = l_queue_find(node->elements, match_element_idx, + L_UINT_TO_PTR(ele_idx)); + + if (!ele) + return; + + memset(&db_mod, 0, sizeof(db_mod)); + db_mod.id = mod_id; + + mod = mesh_model_setup(node, ele_idx, &db_mod); + if (!mod) + return; + + if (!ele->models) + ele->models = l_queue_new(); + + if (!element_add_model(ele, mod)) + mesh_model_free(mod); +} + +static bool add_element(struct mesh_node *node, + struct mesh_config_element *db_ele) +{ + struct node_element *ele; + + ele = l_new(struct node_element, 1); + if (!ele) + return false; + + ele->idx = db_ele->index; + ele->location = db_ele->location; + + if (!db_ele->models || !add_models(node, ele, db_ele)) + return false; + + l_queue_push_tail(node->elements, ele); + return true; +} + +static bool add_elements(struct mesh_node *node, + struct mesh_config_node *db_node) +{ + const struct l_queue_entry *entry; + + if (!node->elements) + node->elements = l_queue_new(); + + entry = l_queue_get_entries(db_node->elements); + for (; entry; entry = entry->next) + if (!add_element(node, entry->data)) + return false; + + return true; +} + +static void set_net_key(void *a, void *b) +{ + struct mesh_config_netkey *netkey = a; + struct mesh_node *node = b; + + mesh_net_set_key(node->net, netkey->idx, netkey->key, netkey->new_key, + netkey->phase); +} + +static void set_appkey(void *a, void *b) +{ + struct mesh_config_appkey *appkey = a; + struct mesh_node *node = b; + + appkey_key_init(node->net, appkey->net_idx, appkey->app_idx, + appkey->key, appkey->new_key); +} + +static bool init_from_storage(struct mesh_config_node *db_node, + const uint8_t uuid[16], struct mesh_config *cfg, + void *user_data) +{ + unsigned int num_ele; + uint8_t mode; + + struct mesh_node *node = node_new(uuid); + + if (!nodes) + nodes = l_queue_new(); + + l_queue_push_tail(nodes, node); + + node->comp = l_new(struct node_composition, 1); + node->comp->cid = db_node->cid; + node->comp->pid = db_node->pid; + node->comp->vid = db_node->vid; + node->comp->crpl = db_node->crpl; + node->lpn = db_node->modes.lpn; + + node->proxy = db_node->modes.proxy; + node->friend = db_node->modes.friend; + node->relay.mode = db_node->modes.relay.state; + node->relay.cnt = db_node->modes.relay.cnt; + node->relay.interval = db_node->modes.relay.interval; + node->beacon = db_node->modes.beacon; + + l_debug("relay %2.2x, proxy %2.2x, lpn %2.2x, friend %2.2x", + node->relay.mode, node->proxy, node->lpn, node->friend); + node->ttl = db_node->ttl; + node->seq_number = db_node->seq_number; + + memcpy(node->dev_key, db_node->dev_key, 16); + memcpy(node->token, db_node->token, 8); + + num_ele = l_queue_length(db_node->elements); + if (num_ele > MAX_ELE_COUNT) + goto fail; + + node->num_ele = num_ele; + + if (num_ele != 0 && !add_elements(node, db_node)) + goto fail; + + node->primary = db_node->unicast; + + if (!db_node->netkeys) + goto fail; + + mesh_net_set_iv_index(node->net, db_node->iv_index, db_node->iv_update); + + if (db_node->net_transmit) + mesh_net_transmit_params_set(node->net, + db_node->net_transmit->count, + db_node->net_transmit->interval); + + l_queue_foreach(db_node->netkeys, set_net_key, node); + + if (db_node->appkeys) + l_queue_foreach(db_node->appkeys, set_appkey, node); + + mesh_net_set_seq_num(node->net, node->seq_number); + mesh_net_set_default_ttl(node->net, node->ttl); + + mode = node->proxy; + if (mode == MESH_MODE_ENABLED || mode == MESH_MODE_DISABLED) + mesh_net_set_proxy_mode(node->net, mode == MESH_MODE_ENABLED); + + mode = node->lpn; + if (mode == MESH_MODE_ENABLED || mode == MESH_MODE_DISABLED) + mesh_net_set_friend_mode(node->net, mode == MESH_MODE_ENABLED); + + mode = node->relay.mode; + if (mode == MESH_MODE_ENABLED || mode == MESH_MODE_DISABLED) + mesh_net_set_relay_mode(node->net, mode == MESH_MODE_ENABLED, + node->relay.cnt, node->relay.interval); + + mode = node->beacon; + if (mode == MESH_MODE_ENABLED || mode == MESH_MODE_DISABLED) + mesh_net_set_beacon_mode(node->net, mode == MESH_MODE_ENABLED); + + if (!IS_UNASSIGNED(node->primary) && + !mesh_net_register_unicast(node->net, node->primary, num_ele)) + goto fail; + + /* Initialize configuration server model */ + cfgmod_server_init(node, PRIMARY_ELE_IDX); + + node->cfg = cfg; + + return true; +fail: + node_remove(node); + return false; +} + +static void cleanup_node(void *data) +{ + struct mesh_node *node = data; + struct mesh_net *net = node->net; + + /* Preserve the last sequence number */ + if (node->cfg) + mesh_config_write_seq_number(node->cfg, + mesh_net_get_seq_num(net), + false); + + free_node_resources(node); +} + +/* + * This function is called to free resources and write the current + * sequence numbers to the configuration file for each known node. + */ +void node_cleanup_all(void) +{ + l_queue_destroy(nodes, cleanup_node); + l_dbus_unregister_interface(dbus_get_bus(), MESH_NODE_INTERFACE); + l_dbus_unregister_interface(dbus_get_bus(), MESH_MANAGEMENT_INTERFACE); +} + +bool node_is_provisioner(struct mesh_node *node) +{ + return node->provisioner; +} + +bool node_is_provisioned(struct mesh_node *node) +{ + return (!IS_UNASSIGNED(node->primary)); +} + +bool node_app_key_delete(struct mesh_net *net, uint16_t addr, + uint16_t net_idx, uint16_t app_idx) +{ + struct mesh_node *node; + const struct l_queue_entry *entry; + + node = node_find_by_addr(addr); + if (!node) + return false; + + entry = l_queue_get_entries(node->elements); + for (; entry; entry = entry->next) { + struct node_element *ele = entry->data; + + mesh_model_app_key_delete(node, ele->models, app_idx); + } + + return mesh_config_app_key_del(node->cfg, net_idx, app_idx); +} + +uint16_t node_get_primary(struct mesh_node *node) +{ + if (!node) + return UNASSIGNED_ADDRESS; + else + return node->primary; +} + +void node_set_device_key(struct mesh_node *node, uint8_t key[16]) +{ + memcpy(node->dev_key, key, 16); +} + +const uint8_t *node_get_device_key(struct mesh_node *node) +{ + if (!node) + return NULL; + else + return node->dev_key; +} + +void node_set_token(struct mesh_node *node, uint8_t token[8]) +{ + memcpy(node->token, token, 8); +} + +const uint8_t *node_get_token(struct mesh_node *node) +{ + if (!node) + return NULL; + else + return node->token; +} + +uint8_t node_get_num_elements(struct mesh_node *node) +{ + return node->num_ele; +} + +struct l_queue *node_get_element_models(struct mesh_node *node, + uint8_t ele_idx, int *status) +{ + struct node_element *ele; + + if (!node) { + if (status) + *status = MESH_STATUS_INVALID_ADDRESS; + return NULL; + } + + ele = l_queue_find(node->elements, match_element_idx, + L_UINT_TO_PTR(ele_idx)); + if (!ele) { + if (status) + *status = MESH_STATUS_INVALID_ADDRESS; + return NULL; + } + + if (status) + *status = MESH_STATUS_SUCCESS; + + return ele->models; +} + +uint8_t node_default_ttl_get(struct mesh_node *node) +{ + if (!node) + return DEFAULT_TTL; + return node->ttl; +} + +bool node_default_ttl_set(struct mesh_node *node, uint8_t ttl) +{ + bool res; + + if (!node) + return false; + + res = mesh_config_write_ttl(node->cfg, ttl); + + if (res) { + node->ttl = ttl; + mesh_net_set_default_ttl(node->net, ttl); + } + + return res; +} + +bool node_set_sequence_number(struct mesh_node *node, uint32_t seq) +{ + if (!node) + return false; + + node->seq_number = seq; + + return mesh_config_write_seq_number(node->cfg, node->seq_number, true); +} + +uint32_t node_get_sequence_number(struct mesh_node *node) +{ + if (!node) + return 0xffffffff; + + return node->seq_number; +} + +int node_get_element_idx(struct mesh_node *node, uint16_t ele_addr) +{ + uint16_t addr; + uint8_t num_ele; + + if (!node) + return -1; + + num_ele = node_get_num_elements(node); + if (!num_ele) + return -2; + + addr = node_get_primary(node); + + if (ele_addr < addr || ele_addr >= addr + num_ele) + return -3; + else + return ele_addr - addr; +} + +uint16_t node_get_crpl(struct mesh_node *node) +{ + if (!node) + return 0; + + return node->comp->crpl; +} + +uint8_t node_relay_mode_get(struct mesh_node *node, uint8_t *count, + uint16_t *interval) +{ + if (!node) { + *count = 0; + *interval = 0; + return MESH_MODE_DISABLED; + } + + *count = node->relay.cnt; + *interval = node->relay.interval; + return node->relay.mode; +} + +uint8_t node_lpn_mode_get(struct mesh_node *node) +{ + if (!node) + return MESH_MODE_DISABLED; + + return node->lpn; +} + +bool node_relay_mode_set(struct mesh_node *node, bool enable, uint8_t cnt, + uint16_t interval) +{ + bool res; + + if (!node || node->relay.mode == MESH_MODE_UNSUPPORTED) + return false; + + res = mesh_config_write_relay_mode(node->cfg, enable, cnt, interval); + + if (res) { + node->relay.mode = enable ? MESH_MODE_ENABLED : + MESH_MODE_DISABLED; + node->relay.cnt = cnt; + node->relay.interval = interval; + mesh_net_set_relay_mode(node->net, enable, cnt, interval); + } + + return res; +} + +bool node_proxy_mode_set(struct mesh_node *node, bool enable) +{ + bool res; + uint8_t proxy; + + if (!node || node->proxy == MESH_MODE_UNSUPPORTED) + return false; + + proxy = enable ? MESH_MODE_ENABLED : MESH_MODE_DISABLED; + res = mesh_config_write_mode(node->cfg, "proxy", proxy); + + if (res) { + node->proxy = proxy; + mesh_net_set_proxy_mode(node->net, enable); + } + + return res; +} + +uint8_t node_proxy_mode_get(struct mesh_node *node) +{ + if (!node) + return MESH_MODE_DISABLED; + + return node->proxy; +} + +bool node_beacon_mode_set(struct mesh_node *node, bool enable) +{ + bool res; + uint8_t beacon; + + if (!node) + return false; + + beacon = enable ? MESH_MODE_ENABLED : MESH_MODE_DISABLED; + res = mesh_config_write_mode(node->cfg, "beacon", beacon); + + if (res) { + node->beacon = beacon; + mesh_net_set_beacon_mode(node->net, enable); + } + + return res; +} + +uint8_t node_beacon_mode_get(struct mesh_node *node) +{ + if (!node) + return MESH_MODE_DISABLED; + + return node->beacon; +} + +bool node_friend_mode_set(struct mesh_node *node, bool enable) +{ + bool res; + uint8_t friend; + + if (!node || node->friend == MESH_MODE_UNSUPPORTED) + return false; + + friend = enable ? MESH_MODE_ENABLED : MESH_MODE_DISABLED; + res = mesh_config_write_mode(node->cfg, "friend", friend); + + if (res) { + node->friend = friend; + mesh_net_set_friend_mode(node->net, enable); + } + + return res; +} + +uint8_t node_friend_mode_get(struct mesh_node *node) +{ + if (!node) + return MESH_MODE_DISABLED; + + return node->friend; +} + +uint16_t node_generate_comp(struct mesh_node *node, uint8_t *buf, uint16_t sz) +{ + uint16_t n, features; + uint16_t num_ele = 0; + uint8_t *cfgmod_idx = NULL; + + const struct l_queue_entry *ele_entry; + + if (!node || !node->comp || sz < MIN_COMP_SIZE) + return 0; + + n = 0; + + l_put_le16(node->comp->cid, buf + n); + n += 2; + l_put_le16(node->comp->pid, buf + n); + n += 2; + l_put_le16(node->comp->vid, buf + n); + n += 2; + l_put_le16(node->comp->crpl, buf + n); + n += 2; + + features = 0; + + if (node->relay.mode != MESH_MODE_UNSUPPORTED) + features |= FEATURE_RELAY; + if (node->proxy != MESH_MODE_UNSUPPORTED) + features |= FEATURE_PROXY; + if (node->friend != MESH_MODE_UNSUPPORTED) + features |= FEATURE_FRIEND; + if (node->lpn != MESH_MODE_UNSUPPORTED) + features |= FEATURE_LPN; + + l_put_le16(features, buf + n); + n += 2; + + ele_entry = l_queue_get_entries(node->elements); + for (; ele_entry; ele_entry = ele_entry->next) { + struct node_element *ele = ele_entry->data; + const struct l_queue_entry *mod_entry; + uint8_t num_s = 0, num_v = 0; + uint8_t *mod_buf; + + if (ele->idx != num_ele) + return 0; + + num_ele++; + + /* At least fit location and zeros for number of models */ + if ((n + 4) > sz) + return n; + + l_put_le16(ele->location, buf + n); + n += 2; + + /* Store models IDs, store num_s and num_v later */ + mod_buf = buf + n; + n += 2; + + /* Get SIG models */ + mod_entry = l_queue_get_entries(ele->models); + for (; mod_entry; mod_entry = mod_entry->next) { + struct mesh_model *mod = mod_entry->data; + uint32_t mod_id; + + mod_id = mesh_model_get_model_id( + (const struct mesh_model *) mod); + + if (mod_id == CONFIG_SRV_MODEL) + cfgmod_idx = &ele->idx; + + if ((mod_id & VENDOR_ID_MASK) == VENDOR_ID_MASK) { + if (n + 2 > sz) + goto element_done; + + l_put_le16((uint16_t) (mod_id & 0xffff), + buf + n); + n += 2; + num_s++; + } + } + + /* Get vendor models */ + mod_entry = l_queue_get_entries(ele->models); + for (; mod_entry; mod_entry = mod_entry->next) { + struct mesh_model *mod = mod_entry->data; + uint32_t mod_id; + uint16_t vendor; + + mod_id = mesh_model_get_model_id( + (const struct mesh_model *) mod); + + vendor = (uint16_t) (mod_id >> 16); + if (vendor != 0xffff) { + if (n + 4 > sz) + goto element_done; + + l_put_le16(vendor, buf + n); + n += 2; + l_put_le16((uint16_t) (mod_id & 0xffff), + buf + n); + n += 2; + num_v++; + } + + } + +element_done: + mod_buf[0] = num_s; + mod_buf[1] = num_v; + + } + + if (!num_ele) + return 0; + + if (!cfgmod_idx || *cfgmod_idx != PRIMARY_ELE_IDX) + return 0; + + return n; +} + + +#define MIN_COMPOSITION_LEN 16 + +bool node_parse_composition(struct mesh_node *node, uint8_t *data, + uint16_t len) +{ + struct node_composition *comp; + uint16_t features; + uint8_t num_ele; + bool mode; + + if (!len) + return false; + + /* Skip page -- We only support Page Zero */ + data++; + len--; + + if (len < MIN_COMPOSITION_LEN) + return false; + + comp = l_new(struct node_composition, 1); + if (!comp) + return false; + + node->elements = l_queue_new(); + if (!node->elements) { + l_free(comp); + return false; + } + + node->comp = l_new(struct node_composition, 1); + comp->cid = l_get_le16(&data[0]); + comp->pid = l_get_le16(&data[2]); + comp->vid = l_get_le16(&data[4]); + comp->crpl = l_get_le16(&data[6]); + features = l_get_le16(&data[8]); + data += 10; + len -= 10; + + mode = !!(features & FEATURE_PROXY); + node->proxy = mode ? MESH_MODE_DISABLED : MESH_MODE_UNSUPPORTED; + + mode = !!(features & FEATURE_LPN); + node->lpn = mode ? MESH_MODE_DISABLED : MESH_MODE_UNSUPPORTED; + + mode = !!(features & FEATURE_FRIEND); + node->friend = mode ? MESH_MODE_DISABLED : MESH_MODE_UNSUPPORTED; + + mode = !!(features & FEATURE_RELAY); + node->relay.mode = mode ? MESH_MODE_DISABLED : MESH_MODE_UNSUPPORTED; + + num_ele = 0; + + do { + uint8_t m, v; + uint16_t mod_id; + uint16_t vendor_id; + struct node_element *ele; + struct mesh_model *mod; + + ele = l_new(struct node_element, 1); + if (!ele) + return false; + + ele->idx = num_ele; + ele->location = l_get_le16(data); + len -= 2; + data += 2; + + m = *data++; + v = *data++; + len -= 2; + + /* Parse SIG models */ + while (len >= 2 && m--) { + mod_id = l_get_le16(data); + mod = mesh_model_new(ele->idx, mod_id); + if (!mod || !element_add_model(ele, mod)) { + mesh_model_free(mod); + element_free(ele); + goto fail; + } + + data += 2; + len -= 2; + } + + if (v && len < 4) { + element_free(ele); + goto fail; + } + + /* Parse vendor models */ + while (len >= 4 && v--) { + mod_id = l_get_le16(data + 2); + vendor_id = l_get_le16(data); + mod_id |= (vendor_id << 16); + mod = mesh_model_vendor_new(ele->idx, vendor_id, + mod_id); + if (!mod || !element_add_model(ele, mod)) { + mesh_model_free(mod); + element_free(ele); + goto fail; + } + + data += 4; + len -= 4; + } + + num_ele++; + l_queue_push_tail(node->elements, ele); + + } while (len >= 6); + + /* Check the consistency for the remote node */ + if (node->num_ele > num_ele) + goto fail; + + node->comp = comp; + node->num_ele = num_ele; + + return true; + +fail: + l_queue_destroy(node->elements, element_free); + l_free(comp); + + return false; +} + +static void attach_io(void *a, void *b) +{ + struct mesh_node *node = a; + struct mesh_io *io = b; + + if (node->net) + mesh_net_attach(node->net, io); +} + +/* Register callback for the node's io */ +void node_attach_io(struct mesh_node *node, struct mesh_io *io) +{ + attach_io(node, io); +} + +/* Register callbacks for all nodes io */ +void node_attach_io_all(struct mesh_io *io) +{ + l_queue_foreach(nodes, attach_io, io); +} + +/* Register node object with D-Bus */ +static bool register_node_object(struct mesh_node *node) +{ + char uuid[33]; + + if (!hex2str(node->uuid, sizeof(node->uuid), uuid, sizeof(uuid))) + return false; + + node->obj_path = l_strdup_printf(BLUEZ_MESH_PATH MESH_NODE_PATH_PREFIX + "%s", uuid); + + if (!l_dbus_object_add_interface(dbus_get_bus(), node->obj_path, + MESH_NODE_INTERFACE, node)) + return false; + + if (!l_dbus_object_add_interface(dbus_get_bus(), node->obj_path, + MESH_MANAGEMENT_INTERFACE, node)) + return false; + + return true; +} + +static void app_disc_cb(struct l_dbus *bus, void *user_data) +{ + struct mesh_node *node = user_data; + + l_info("App %s disconnected (%u)", node->owner, node->disc_watch); + + node->disc_watch = 0; + free_node_dbus_resources(node); +} + +static void get_models_from_properties(struct node_element *ele, + struct l_dbus_message_iter *property, + bool vendor) +{ + struct l_dbus_message_iter ids; + uint16_t mod_id, vendor_id; + const char *signature = !vendor ? "aq" : "a(qq)"; + + if (!ele->models) + ele->models = l_queue_new(); + + if (!l_dbus_message_iter_get_variant(property, signature, &ids)) + return; + + /* Bluetooth SIG defined models */ + if (!vendor) { + while (l_dbus_message_iter_next_entry(&ids, &mod_id)) { + struct mesh_model *mod; + + mod = mesh_model_new(ele->idx, mod_id); + if (!element_add_model(ele, mod)) + mesh_model_free(mod); + } + return; + } + + /* Vendor defined models */ + while (l_dbus_message_iter_next_entry(&ids, &vendor_id, &mod_id)) { + struct mesh_model *mod; + + mod = mesh_model_vendor_new(ele->idx, vendor_id, mod_id); + if (!element_add_model(ele, mod)) + mesh_model_free(mod); + } +} + +static bool get_element_properties(struct mesh_node *node, const char *path, + struct l_dbus_message_iter *properties) +{ + struct node_element *ele = l_new(struct node_element, 1); + const char *key; + struct l_dbus_message_iter var; + bool idx = false; + bool mods = false; + bool vendor_mods = false; + + l_debug("path %s", path); + + ele->location = DEFAULT_LOCATION; + + while (l_dbus_message_iter_next_entry(properties, &key, &var)) { + if (!idx && !strcmp(key, "Index")) { + if (!l_dbus_message_iter_get_variant(&var, "y", + &ele->idx)) + goto fail; + idx = true; + continue; + } + + if (!mods && !strcmp(key, "Models")) { + get_models_from_properties(ele, &var, false); + mods = true; + continue; + } + + if (!vendor_mods && !strcmp(key, "VendorModels")) { + get_models_from_properties(ele, &var, true); + vendor_mods = true; + continue; + } + + if (!strcmp(key, "Location")) { + if (!l_dbus_message_iter_get_variant(&var, "q", + &ele->location)) + goto fail; + continue; + } + } + + if (!idx || !mods || !vendor_mods) + goto fail; + + if (l_queue_find(node->elements, match_element_idx, + L_UINT_TO_PTR(ele->idx))) + goto fail; + + l_queue_insert(node->elements, ele, compare_element_idx, NULL); + + ele->path = l_strdup(path); + + return true; +fail: + l_free(ele); + + return false; +} + +static void convert_node_to_storage(struct mesh_node *node, + struct mesh_config_node *db_node) +{ + const struct l_queue_entry *entry; + + db_node->cid = node->comp->cid; + db_node->pid = node->comp->pid; + db_node->vid = node->comp->vid; + db_node->crpl = node->comp->crpl; + db_node->modes.lpn = node->lpn; + db_node->modes.proxy = node->proxy; + + db_node->modes.friend = node->friend; + db_node->modes.relay.state = node->relay.mode; + db_node->modes.relay.cnt = node->relay.cnt; + db_node->modes.relay.interval = node->relay.interval; + db_node->modes.beacon = node->beacon; + + db_node->ttl = node->ttl; + db_node->seq_number = node->seq_number; + + db_node->elements = l_queue_new(); + + entry = l_queue_get_entries(node->elements); + + for (; entry; entry = entry->next) { + struct node_element *ele = entry->data; + struct mesh_config_element *db_ele; + const struct l_queue_entry *mod_entry; + + db_ele = l_new(struct mesh_config_element, 1); + + db_ele->index = ele->idx; + db_ele->location = ele->location; + db_ele->models = l_queue_new(); + + mod_entry = l_queue_get_entries(ele->models); + + for (; mod_entry; mod_entry = mod_entry->next) { + struct mesh_model *mod = mod_entry->data; + struct mesh_config_model *db_mod; + uint32_t mod_id = mesh_model_get_model_id(mod); + + db_mod = l_new(struct mesh_config_model, 1); + db_mod->id = mod_id; + db_mod->vendor = ((mod_id & VENDOR_ID_MASK) + != VENDOR_ID_MASK); + + l_queue_push_tail(db_ele->models, db_mod); + } + l_queue_push_tail(db_node->elements, db_ele); + } + +} + +static bool create_node_config(struct mesh_node *node, const uint8_t uuid[16]) +{ + struct mesh_config_node db_node; + const struct l_queue_entry *entry; + const char *storage_dir; + + convert_node_to_storage(node, &db_node); + storage_dir = mesh_get_storage_dir(); + node->cfg = mesh_config_create(storage_dir, uuid, &db_node); + + /* Free temporarily allocated resources */ + entry = l_queue_get_entries(db_node.elements); + for (; entry; entry = entry->next) { + struct mesh_config_element *db_ele = entry->data; + + l_queue_destroy(db_ele->models, l_free); + } + + l_queue_destroy(db_node.elements, l_free); + + return node->cfg != NULL; +} + +static void set_defaults(struct mesh_node *node) +{ + /* TODO: these values should come from mesh.conf */ + node->lpn = MESH_MODE_UNSUPPORTED; + node->proxy = MESH_MODE_UNSUPPORTED; + node->friend = MESH_MODE_UNSUPPORTED; + node->beacon = MESH_MODE_DISABLED; + node->relay.mode = MESH_MODE_DISABLED; + node->ttl = DEFAULT_TTL; + node->seq_number = DEFAULT_SEQUENCE_NUMBER; + + /* Add configuration server model on primary element */ + add_internal_model(node, CONFIG_SRV_MODEL, PRIMARY_ELE_IDX); +} + +static bool get_app_properties(struct mesh_node *node, const char *path, + struct l_dbus_message_iter *properties) +{ + const char *key; + struct l_dbus_message_iter variant; + bool cid = false; + bool pid = false; + bool vid = false; + + l_debug("path %s", path); + + node->comp = l_new(struct node_composition, 1); + node->comp->crpl = DEFAULT_CRPL; + + while (l_dbus_message_iter_next_entry(properties, &key, &variant)) { + if (!cid && !strcmp(key, "CompanyID")) { + if (!l_dbus_message_iter_get_variant(&variant, "q", + &node->comp->cid)) + goto fail; + cid = true; + continue; + } + + if (!pid && !strcmp(key, "ProductID")) { + if (!l_dbus_message_iter_get_variant(&variant, "q", + &node->comp->pid)) + goto fail; + pid = true; + continue; + } + + if (!vid && !strcmp(key, "VersionID")) { + if (!l_dbus_message_iter_get_variant(&variant, "q", + &node->comp->vid)) + return false; + vid = true; + continue; + } + + if (!strcmp(key, "CRPL")) { + if (!l_dbus_message_iter_get_variant(&variant, "q", + &node->comp->crpl)) + goto fail; + continue; + } + } + + if (!cid || !pid || !vid) + goto fail; + + return true; +fail: + l_free(node->comp); + node->comp = NULL; + + return false; +} + +static bool add_local_node(struct mesh_node *node, uint16_t unicast, bool kr, + bool ivu, uint32_t iv_idx, uint8_t dev_key[16], + uint16_t net_key_idx, uint8_t net_key[16]) +{ + node->net = mesh_net_new(node); + + if (!nodes) + nodes = l_queue_new(); + + l_queue_push_tail(nodes, node); + + if (!mesh_config_write_iv_index(node->cfg, iv_idx, ivu)) + return false; + + mesh_net_set_iv_index(node->net, iv_idx, ivu); + + if (!mesh_config_write_unicast(node->cfg, unicast)) + return false; + + l_getrandom(node->token, sizeof(node->token)); + if (!mesh_config_write_token(node->cfg, node->token)) + return false; + + memcpy(node->dev_key, dev_key, 16); + if (!mesh_config_write_device_key(node->cfg, dev_key)) + return false; + + node->primary = unicast; + mesh_net_register_unicast(node->net, unicast, node->num_ele); + + if (mesh_net_add_key(node->net, net_key_idx, net_key) != + MESH_STATUS_SUCCESS) + return false; + + if (kr) { + /* Duplicate net key, if the key refresh is on */ + if (mesh_net_update_key(node->net, net_key_idx, net_key) != + MESH_STATUS_SUCCESS) + return false; + + if (!mesh_config_net_key_set_phase(node->cfg, net_key_idx, + KEY_REFRESH_PHASE_TWO)) + return false; + } + + mesh_config_save(node->cfg, true, NULL, NULL); + + /* Initialize configuration server model */ + cfgmod_server_init(node, PRIMARY_ELE_IDX); + + return true; +} + +static bool init_storage_dir(struct mesh_node *node) +{ + char uuid[33]; + char dir_name[PATH_MAX]; + + if (node->storage_dir) + return true; + + if (!hex2str(node->uuid, 16, uuid, sizeof(uuid))) + return false; + + snprintf(dir_name, PATH_MAX, "%s/%s", mesh_get_storage_dir(), uuid); + + if (strlen(dir_name) >= PATH_MAX) + return false; + + create_dir(dir_name); + + node->storage_dir = l_strdup(dir_name); + + return true; +} + +static bool check_req_node(struct managed_obj_request *req) +{ + uint8_t node_comp[MAX_MSG_LEN - 2]; + uint8_t attach_comp[MAX_MSG_LEN - 2]; + + uint16_t node_len = node_generate_comp(req->node, node_comp, + sizeof(node_comp)); + + if (!node_len) + return false; + + if (req->type == REQUEST_TYPE_ATTACH) { + uint16_t attach_len = node_generate_comp(req->attach, + attach_comp, sizeof(attach_comp)); + + if (node_len != attach_len || + memcmp(node_comp, attach_comp, node_len)) { + l_debug("Failed to verify app's composition data"); + return false; + } + } + + return true; +} + +static struct mesh_node *attach_req_node(struct mesh_node *attach, + struct mesh_node *node) +{ + const struct l_queue_entry *attach_entry; + const struct l_queue_entry *node_entry; + + attach_entry = l_queue_get_entries(attach->elements); + node_entry = l_queue_get_entries(node->elements); + + /* + * Update existing node with paths collected in temporary node, + * then remove the temporary. + */ + while (attach_entry && node_entry) { + struct node_element *attach_ele = attach_entry->data; + struct node_element *node_ele = node_entry->data; + + attach_ele->path = node_ele->path; + node_ele->path = NULL; + + attach_entry = attach_entry->next; + node_entry = node_entry->next; + } + + mesh_agent_remove(attach->agent); + attach->agent = node->agent; + node->agent = NULL; + + attach->provisioner = node->provisioner; + + attach->app_path = node->app_path; + node->app_path = NULL; + + attach->owner = node->owner; + node->owner = NULL; + + node_remove(node); + + return attach; +} + +static void get_managed_objects_cb(struct l_dbus_message *msg, void *user_data) +{ + struct l_dbus_message_iter objects, interfaces; + struct managed_obj_request *req = user_data; + const char *path; + struct mesh_node *node = req->node; + void *agent = NULL; + bool have_app = false; + unsigned int num_ele; + + if (l_dbus_message_is_error(msg)) { + l_error("Failed to get app's dbus objects"); + goto fail; + } + + if (!l_dbus_message_get_arguments(msg, "a{oa{sa{sv}}}", &objects)) { + l_error("Failed to parse app's dbus objects"); + goto fail; + } + + if (!node->elements) + node->elements = l_queue_new(); + + while (l_dbus_message_iter_next_entry(&objects, &path, &interfaces)) { + struct l_dbus_message_iter properties; + const char *interface; + + while (l_dbus_message_iter_next_entry(&interfaces, &interface, + &properties)) { + bool res; + + if (!strcmp(MESH_ELEMENT_INTERFACE, interface)) { + res = get_element_properties(node, path, + &properties); + if (!res) + goto fail; + } else if (!strcmp(MESH_APPLICATION_INTERFACE, + interface)) { + res = get_app_properties(node, path, + &properties); + if (!res) + goto fail; + + have_app = true; + + } else if (!strcmp(MESH_PROVISION_AGENT_INTERFACE, + interface)) { + const char *sender; + + sender = l_dbus_message_get_sender(msg); + agent = mesh_agent_create(path, sender, + &properties); + if (!agent) + goto fail; + + node->agent = agent; + + } else if (!strcmp(MESH_PROVISIONER_INTERFACE, + interface)) { + node->provisioner = true; + } + } + } + + if (!have_app) { + l_error("Interface %s not found", MESH_APPLICATION_INTERFACE); + goto fail; + } + + if (l_queue_isempty(node->elements)) { + l_error("Interface %s not found", MESH_ELEMENT_INTERFACE); + goto fail; + } + + if (!l_queue_find(node->elements, match_element_idx, + L_UINT_TO_PTR(PRIMARY_ELE_IDX))) { + + l_debug("Primary element not detected"); + goto fail; + } + + set_defaults(node); + num_ele = l_queue_length(node->elements); + + if (num_ele > MAX_ELE_COUNT) + goto fail; + + node->num_ele = num_ele; + + if (!check_req_node(req)) + goto fail; + + if (req->type == REQUEST_TYPE_ATTACH) { + struct l_dbus *bus = dbus_get_bus(); + + node = attach_req_node(req->attach, node); + + if (!register_node_object(node)) + goto fail; + + /* + * TODO: For now always initialize directory for storing + * keyring info. Need to figure out what checks need + * to be performed to do this conditionally, i.e., presence of + * Provisioner interface, etc. + */ + init_storage_dir(node); + + node->disc_watch = l_dbus_add_disconnect_watch(bus, + node->owner, app_disc_cb, node, NULL); + + } else if (req->type == REQUEST_TYPE_JOIN) { + if (!node->agent) { + l_error("Interface %s not found", + MESH_PROVISION_AGENT_INTERFACE); + goto fail; + } + + if (!create_node_config(node, node->uuid)) + goto fail; + + } else if (req->type == REQUEST_TYPE_IMPORT) { + struct node_import *import = req->import; + + if (!create_node_config(node, node->uuid)) + goto fail; + + if (!add_local_node(node, import->unicast, import->flags.kr, + import->flags.ivu, + import->iv_index, import->dev_key, + import->net_idx, import->net_key)) + goto fail; + + /* Initialize directory for storing keyring info */ + init_storage_dir(node); + + } else { + /* Callback for create node request */ + struct keyring_net_key net_key; + uint8_t dev_key[16]; + + if (!create_node_config(node, node->uuid)) + goto fail; + + /* Generate device and primary network keys */ + l_getrandom(dev_key, sizeof(dev_key)); + l_getrandom(net_key.old_key, sizeof(net_key.old_key)); + net_key.net_idx = PRIMARY_NET_IDX; + net_key.phase = KEY_REFRESH_PHASE_NONE; + + if (!add_local_node(node, DEFAULT_NEW_UNICAST, false, false, + DEFAULT_IV_INDEX, dev_key, + PRIMARY_NET_IDX, + net_key.old_key)) + goto fail; + + /* Initialize directory for storing keyring info */ + init_storage_dir(node); + + if (!keyring_put_remote_dev_key(node, DEFAULT_NEW_UNICAST, + node->num_ele, dev_key)) + goto fail; + + if (!keyring_put_net_key(node, PRIMARY_NET_IDX, &net_key)) + goto fail; + + } + + if (req->type == REQUEST_TYPE_JOIN) + req->join_ready_cb(node, node->agent); + else + req->ready_cb(req->pending_msg, MESH_ERROR_NONE, node); + + goto done; + +fail: + if (agent) + mesh_agent_remove(agent); + + /* Handle failed requests */ + if (node) + node_remove(node); + + if (req->type == REQUEST_TYPE_JOIN) + req->join_ready_cb(NULL, NULL); + else + req->ready_cb(req->pending_msg, MESH_ERROR_FAILED, NULL); + +done: + if (req->type == REQUEST_TYPE_IMPORT) + l_free(req->import); +} + +/* Establish relationship between application and mesh node */ +int node_attach(const char *app_path, const char *sender, uint64_t token, + node_ready_func_t cb, void *user_data) +{ + struct managed_obj_request *req; + struct mesh_node *node; + + node = l_queue_find(nodes, match_token, (void *) &token); + if (!node) + return MESH_ERROR_NOT_FOUND; + + /* Check if the node is already in use */ + if (node->owner) { + l_warn("The node is already in use"); + return MESH_ERROR_ALREADY_EXISTS; + } + + req = l_new(struct managed_obj_request, 1); + + /* + * Create a temporary node to collect composition data from attaching + * application. Existing node is passed in req->attach. + */ + req->node = node_new(node->uuid); + req->node->app_path = l_strdup(app_path); + req->node->owner = l_strdup(sender); + req->ready_cb = cb; + req->pending_msg = user_data; + req->attach = node; + req->type = REQUEST_TYPE_ATTACH; + + l_dbus_method_call(dbus_get_bus(), sender, app_path, + L_DBUS_INTERFACE_OBJECT_MANAGER, + "GetManagedObjects", NULL, + get_managed_objects_cb, + req, l_free); + return MESH_ERROR_NONE; + +} + + +/* Create a temporary pre-provisioned node */ +void node_join(const char *app_path, const char *sender, const uint8_t *uuid, + node_join_ready_func_t cb) +{ + struct managed_obj_request *req; + + l_debug(""); + + req = l_new(struct managed_obj_request, 1); + req->node = node_new(uuid); + req->join_ready_cb = cb; + req->type = REQUEST_TYPE_JOIN; + + l_dbus_method_call(dbus_get_bus(), sender, app_path, + L_DBUS_INTERFACE_OBJECT_MANAGER, + "GetManagedObjects", NULL, + get_managed_objects_cb, + req, l_free); +} + +bool node_import(const char *app_path, const char *sender, const uint8_t *uuid, + const uint8_t dev_key[16], const uint8_t net_key[16], + uint16_t net_idx, bool kr, bool ivu, + uint32_t iv_index, uint16_t unicast, + node_ready_func_t cb, void *user_data) +{ + struct managed_obj_request *req; + + l_debug(""); + + req = l_new(struct managed_obj_request, 1); + + req->node = node_new(uuid); + req->ready_cb = cb; + req->pending_msg = user_data; + + req->import = l_new(struct node_import, 1); + memcpy(req->import->dev_key, dev_key, 16); + memcpy(req->import->net_key, net_key, 16); + req->import->net_idx = net_idx; + req->import->flags.kr = kr; + req->import->flags.ivu = ivu; + req->import->iv_index = iv_index; + req->import->unicast = unicast; + + req->type = REQUEST_TYPE_IMPORT; + + l_dbus_method_call(dbus_get_bus(), sender, app_path, + L_DBUS_INTERFACE_OBJECT_MANAGER, + "GetManagedObjects", NULL, + get_managed_objects_cb, + req, l_free); + return true; +} + +void node_create(const char *app_path, const char *sender, const uint8_t *uuid, + node_ready_func_t cb, void *user_data) +{ + struct managed_obj_request *req; + + l_debug(""); + + req = l_new(struct managed_obj_request, 1); + req->node = node_new(uuid); + req->ready_cb = cb; + req->pending_msg = user_data; + req->type = REQUEST_TYPE_CREATE; + + l_dbus_method_call(dbus_get_bus(), sender, app_path, + L_DBUS_INTERFACE_OBJECT_MANAGER, + "GetManagedObjects", NULL, + get_managed_objects_cb, + req, l_free); +} + +static void build_element_config(void *a, void *b) +{ + struct node_element *ele = a; + struct l_dbus_message_builder *builder = b; + + l_debug("Element %u", ele->idx); + + l_dbus_message_builder_enter_struct(builder, "ya(qa{sv})"); + + /* Element index */ + l_dbus_message_builder_append_basic(builder, 'y', &ele->idx); + + l_dbus_message_builder_enter_array(builder, "(qa{sv})"); + + /* Iterate over models */ + l_queue_foreach(ele->models, model_build_config, builder); + + l_dbus_message_builder_leave_array(builder); + + l_dbus_message_builder_leave_struct(builder); +} + +void node_build_attach_reply(struct mesh_node *node, + struct l_dbus_message *reply) +{ + struct l_dbus_message_builder *builder; + + builder = l_dbus_message_builder_new(reply); + + /* Node object path */ + l_dbus_message_builder_append_basic(builder, 'o', node->obj_path); + + /* Array of element configurations "a*/ + l_dbus_message_builder_enter_array(builder, "(ya(qa{sv}))"); + l_queue_foreach(node->elements, build_element_config, builder); + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static struct l_dbus_message *send_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + const char *sender, *ele_path; + struct l_dbus_message_iter iter_data; + struct node_element *ele; + uint16_t dst, app_idx, src; + uint8_t *data; + uint32_t len; + + l_debug("Send"); + + sender = l_dbus_message_get_sender(msg); + + if (strcmp(sender, node->owner)) + return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); + + if (!l_dbus_message_get_arguments(msg, "oqqay", &ele_path, &dst, + &app_idx, &iter_data)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + ele = l_queue_find(node->elements, match_element_path, ele_path); + if (!ele) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, + "Element not found"); + + src = node_get_primary(node) + ele->idx; + + if (!l_dbus_message_iter_get_fixed_array(&iter_data, &data, &len) || + !len || len > MAX_MSG_LEN) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Incorrect data"); + + if (app_idx & ~APP_IDX_MASK) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Invalid key_index"); + + if (!mesh_model_send(node, src, dst, app_idx, 0, DEFAULT_TTL, + data, len)) + return dbus_error(msg, MESH_ERROR_FAILED, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *dev_key_send_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + const char *sender, *ele_path; + struct l_dbus_message_iter iter_data; + struct node_element *ele; + uint16_t dst, app_idx, net_idx, src; + bool remote; + uint8_t *data; + uint32_t len; + + l_debug("DevKeySend"); + + sender = l_dbus_message_get_sender(msg); + + if (strcmp(sender, node->owner)) + return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); + + if (!l_dbus_message_get_arguments(msg, "oqbqay", &ele_path, &dst, + &remote, &net_idx, &iter_data)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + /* Loopbacks to local servers must use *remote* addressing */ + if (!remote && mesh_net_is_local_address(node->net, dst, 1)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + ele = l_queue_find(node->elements, match_element_path, ele_path); + if (!ele) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, + "Element not found"); + + src = node_get_primary(node) + ele->idx; + + if (!l_dbus_message_iter_get_fixed_array(&iter_data, &data, &len) || + !len || len > MAX_MSG_LEN) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Incorrect data"); + + app_idx = remote ? APP_IDX_DEV_REMOTE : APP_IDX_DEV_LOCAL; + if (!mesh_model_send(node, src, dst, app_idx, net_idx, DEFAULT_TTL, + data, len)) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *add_netkey_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + const char *sender, *ele_path; + struct node_element *ele; + uint16_t dst, sub_idx, net_idx, src; + bool update; + struct keyring_net_key key; + uint8_t data[20]; + + l_debug("AddNetKey"); + + sender = l_dbus_message_get_sender(msg); + + if (strcmp(sender, node->owner)) + return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); + + if (!l_dbus_message_get_arguments(msg, "oqqqb", &ele_path, &dst, + &sub_idx, &net_idx, &update)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + ele = l_queue_find(node->elements, match_element_path, ele_path); + if (!ele) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, + "Element not found"); + + src = node_get_primary(node) + ele->idx; + + if (!keyring_get_net_key(node, sub_idx, &key)) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, + "NetKey not found"); + + if (!update) { + l_put_be16(OP_NETKEY_ADD, data); + + if (key.phase != KEY_REFRESH_PHASE_TWO) + memcpy(data + 4, key.old_key, 16); + else + memcpy(data + 4, key.new_key, 16); + } else { + if (key.phase != KEY_REFRESH_PHASE_ONE) + return dbus_error(msg, MESH_ERROR_FAILED, + "Cannot update"); + l_put_be16(OP_NETKEY_UPDATE, data); + memcpy(data + 4, key.new_key, 16); + } + + l_put_le16(sub_idx, &data[2]); + + if (!mesh_model_send(node, src, dst, APP_IDX_DEV_REMOTE, net_idx, + DEFAULT_TTL, data, 20)) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *add_appkey_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + const char *sender, *ele_path; + struct node_element *ele; + uint16_t dst, app_idx, net_idx, src; + bool update; + struct keyring_net_key net_key; + struct keyring_app_key app_key; + uint8_t data[20]; + + l_debug("AddAppKey"); + + sender = l_dbus_message_get_sender(msg); + + if (strcmp(sender, node->owner)) + return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); + + if (!l_dbus_message_get_arguments(msg, "oqqqb", &ele_path, &dst, + &app_idx, &net_idx, &update)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + ele = l_queue_find(node->elements, match_element_path, ele_path); + if (!ele) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, + "Element not found"); + + src = node_get_primary(node) + ele->idx; + + if (!keyring_get_app_key(node, app_idx, &app_key)) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, + "AppKey not found"); + + if (!keyring_get_net_key(node, app_key.net_idx, &net_key)) { + return dbus_error(msg, MESH_ERROR_NOT_FOUND, + "Bound NetKey not found"); + } + + if (!update) { + data[0] = OP_APPKEY_ADD; + if (net_key.phase != KEY_REFRESH_PHASE_TWO) + memcpy(data + 4, app_key.old_key, 16); + else + memcpy(data + 4, app_key.new_key, 16); + } else { + if (net_key.phase != KEY_REFRESH_PHASE_ONE) + return dbus_error(msg, MESH_ERROR_FAILED, + "Cannot update"); + data[0] = OP_APPKEY_UPDATE; + memcpy(data + 4, app_key.new_key, 16); + } + + /* Pack bound NetKey and AppKey into 3 octets */ + data[1] = app_key.net_idx; + data[2] = ((app_key.net_idx >> 8) & 0xf) | ((app_idx << 4) & 0xf0); + data[3] = app_idx >> 4; + + if (!mesh_model_send(node, src, dst, APP_IDX_DEV_REMOTE, net_idx, + DEFAULT_TTL, data, 20)) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *publish_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + const char *sender, *ele_path; + struct l_dbus_message_iter iter_data; + uint16_t mod_id, src; + struct node_element *ele; + uint8_t *data; + uint32_t len; + int result; + + l_debug("Publish"); + + sender = l_dbus_message_get_sender(msg); + + if (strcmp(sender, node->owner)) + return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); + + if (!l_dbus_message_get_arguments(msg, "oqay", &ele_path, &mod_id, + &iter_data)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + ele = l_queue_find(node->elements, match_element_path, ele_path); + if (!ele) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, + "Element not found"); + + src = node_get_primary(node) + ele->idx; + + if (!l_dbus_message_iter_get_fixed_array(&iter_data, &data, &len) || + !len || len > MAX_MSG_LEN) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Incorrect data"); + + result = mesh_model_publish(node, VENDOR_ID_MASK | mod_id, src, + mesh_net_get_default_ttl(node->net), data, len); + + if (result != MESH_ERROR_NONE) + return dbus_error(msg, result, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static struct l_dbus_message *vendor_publish_call(struct l_dbus *dbus, + struct l_dbus_message *msg, + void *user_data) +{ + struct mesh_node *node = user_data; + const char *sender, *ele_path; + struct l_dbus_message_iter iter_data; + uint16_t src; + uint16_t model_id, vendor; + uint32_t vendor_mod_id; + struct node_element *ele; + uint8_t *data = NULL; + uint32_t len; + int result; + + l_debug("Publish"); + + sender = l_dbus_message_get_sender(msg); + + if (strcmp(sender, node->owner)) + return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL); + + if (!l_dbus_message_get_arguments(msg, "oqqay", &ele_path, &vendor, + &model_id, &iter_data)) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL); + + ele = l_queue_find(node->elements, match_element_path, ele_path); + if (!ele) + return dbus_error(msg, MESH_ERROR_NOT_FOUND, + "Element not found"); + + src = node_get_primary(node) + ele->idx; + + if (!l_dbus_message_iter_get_fixed_array(&iter_data, &data, &len) || + !len || len > MAX_MSG_LEN) + return dbus_error(msg, MESH_ERROR_INVALID_ARGS, + "Incorrect data"); + + vendor_mod_id = (vendor << 16) | model_id; + result = mesh_model_publish(node, vendor_mod_id, src, + mesh_net_get_default_ttl(node->net), data, len); + + if (result != MESH_ERROR_NONE) + return dbus_error(msg, result, NULL); + + return l_dbus_message_new_method_return(msg); +} + +static bool features_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct mesh_node *node = user_data; + uint8_t friend = node_friend_mode_get(node); + uint8_t lpn = node_lpn_mode_get(node); + uint8_t proxy = node_proxy_mode_get(node); + uint8_t count; + uint16_t interval; + uint8_t relay = node_relay_mode_get(node, &count, &interval); + + l_dbus_message_builder_enter_array(builder, "{sv}"); + + if (friend != MESH_MODE_UNSUPPORTED) + dbus_append_dict_entry_basic(builder, "Friend", "b", &friend); + + if (lpn != MESH_MODE_UNSUPPORTED) + dbus_append_dict_entry_basic(builder, "LowPower", "b", &lpn); + + if (proxy != MESH_MODE_UNSUPPORTED) + dbus_append_dict_entry_basic(builder, "Proxy", "b", &proxy); + + if (relay != MESH_MODE_UNSUPPORTED) + dbus_append_dict_entry_basic(builder, "Relay", "b", &relay); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool beacon_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct mesh_node *node = user_data; + bool beacon_mode = node_beacon_mode_get(node) == MESH_MODE_ENABLED; + + l_dbus_message_builder_append_basic(builder, 'b', &beacon_mode); + + return true; +} + +static bool beaconflags_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct mesh_node *node = user_data; + struct mesh_net *net = node_get_net(node); + uint8_t flags; + uint32_t iv_index; + + mesh_net_get_snb_state(net, &flags, &iv_index); + + l_dbus_message_builder_append_basic(builder, 'y', &flags); + + return true; +} + +static bool ivindex_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct mesh_node *node = user_data; + struct mesh_net *net = node_get_net(node); + uint8_t flags; + uint32_t iv_index; + + mesh_net_get_snb_state(net, &flags, &iv_index); + + l_dbus_message_builder_append_basic(builder, 'u', &iv_index); + + return true; +} + +static bool lastheard_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct mesh_node *node = user_data; + struct mesh_net *net = node_get_net(node); + struct timeval now; + uint32_t last_heard; + + gettimeofday(&now, NULL); + + last_heard = now.tv_sec - mesh_net_get_instant(net); + + l_dbus_message_builder_append_basic(builder, 'u', &last_heard); + + return true; + +} + +static bool addresses_getter(struct l_dbus *dbus, struct l_dbus_message *msg, + struct l_dbus_message_builder *builder, + void *user_data) +{ + struct mesh_node *node = user_data; + const struct l_queue_entry *entry; + + l_dbus_message_builder_enter_array(builder, "q"); + + entry = l_queue_get_entries(node->elements); + for (; entry; entry = entry->next) { + const struct node_element *ele = entry->data; + uint16_t address = node->primary + ele->idx; + + l_dbus_message_builder_append_basic(builder, 'q', &address); + } + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static void setup_node_interface(struct l_dbus_interface *iface) +{ + l_dbus_interface_method(iface, "Send", 0, send_call, "", "oqqay", + "element_path", "destination", + "key_index", "data"); + l_dbus_interface_method(iface, "DevKeySend", 0, dev_key_send_call, + "", "oqbqay", "element_path", + "destination", "remote", + "net_index", "data"); + l_dbus_interface_method(iface, "AddNetKey", 0, add_netkey_call, "", + "oqqqb", "element_path", "destination", + "subnet_index", "net_index", "update"); + l_dbus_interface_method(iface, "AddAppKey", 0, add_appkey_call, "", + "oqqqb", "element_path", "destination", + "app_index", "net_index", "update"); + l_dbus_interface_method(iface, "Publish", 0, publish_call, "", "oqay", + "element_path", "model_id", "data"); + l_dbus_interface_method(iface, "VendorPublish", 0, vendor_publish_call, + "", "oqqay", "element_path", + "vendor", "model_id", "data"); + + l_dbus_interface_property(iface, "Features", 0, "a{sv}", features_getter, + NULL); + l_dbus_interface_property(iface, "Beacon", 0, "b", beacon_getter, NULL); + l_dbus_interface_property(iface, "BeaconFlags", 0, "b", + beaconflags_getter, NULL); + l_dbus_interface_property(iface, "IvIndex", 0, "u", ivindex_getter, + NULL); + l_dbus_interface_property(iface, "SecondsSinceLastHeard", 0, "u", + lastheard_getter, NULL); + l_dbus_interface_property(iface, "Addresses", 0, "aq", addresses_getter, + NULL); +} + +bool node_dbus_init(struct l_dbus *bus) +{ + if (!l_dbus_register_interface(bus, MESH_NODE_INTERFACE, + setup_node_interface, + NULL, false)) { + l_info("Unable to register %s interface", MESH_NODE_INTERFACE); + return false; + } + + return true; +} + +const char *node_get_owner(struct mesh_node *node) +{ + return node->owner; +} + +const char *node_get_element_path(struct mesh_node *node, uint8_t ele_idx) +{ + struct node_element *ele; + + ele = l_queue_find(node->elements, match_element_idx, + L_UINT_TO_PTR(ele_idx)); + + if (!ele) + return NULL; + + return ele->path; +} + +bool node_add_pending_local(struct mesh_node *node, void *prov_node_info) +{ + struct mesh_prov_node_info *info = prov_node_info; + bool kr = !!(info->flags & PROV_FLAG_KR); + bool ivu = !!(info->flags & PROV_FLAG_IVU); + + return add_local_node(node, info->unicast, kr, ivu, info->iv_index, + info->device_key, info->net_index, info->net_key); +} + +struct mesh_config *node_config_get(struct mesh_node *node) +{ + return node->cfg; +} + +const char *node_get_storage_dir(struct mesh_node *node) +{ + return node->storage_dir; +} + +const char *node_get_app_path(struct mesh_node *node) +{ + if (!node) + return NULL; + + return node->app_path; +} + +struct mesh_net *node_get_net(struct mesh_node *node) +{ + return node->net; +} + +struct mesh_agent *node_get_agent(struct mesh_node *node) +{ + return node->agent; +} + +bool node_load_from_storage(const char *storage_dir) +{ + return mesh_config_load_nodes(storage_dir, init_from_storage, NULL); +} diff --git a/mesh/node.h b/mesh/node.h new file mode 100644 index 0000000..be57d5e --- /dev/null +++ b/mesh/node.h @@ -0,0 +1,104 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +struct mesh_net; +struct mesh_node; +struct mesh_io; +struct mesh_agent; +struct mesh_config; +struct mesh_config_node; + +typedef void (*node_ready_func_t) (void *user_data, int status, + struct mesh_node *node); + +typedef void (*node_join_ready_func_t) (struct mesh_node *node, + struct mesh_agent *agent); + +void node_remove(struct mesh_node *node); +void node_join(const char *app_path, const char *sender, const uint8_t *uuid, + node_join_ready_func_t cb); +uint8_t *node_uuid_get(struct mesh_node *node); +struct mesh_net *node_get_net(struct mesh_node *node); +struct mesh_node *node_find_by_addr(uint16_t addr); +struct mesh_node *node_find_by_uuid(uint8_t uuid[16]); +struct mesh_node *node_find_by_token(uint64_t token); +bool node_is_provisioner(struct mesh_node *node); +bool node_is_provisioned(struct mesh_node *node); +bool node_app_key_delete(struct mesh_net *net, uint16_t addr, + uint16_t net_idx, uint16_t idx); +uint16_t node_get_primary(struct mesh_node *node); +uint16_t node_get_primary_net_idx(struct mesh_node *node); +void node_set_token(struct mesh_node *node, uint8_t token[8]); +const uint8_t *node_get_token(struct mesh_node *node); +void node_set_device_key(struct mesh_node *node, uint8_t key[16]); +const uint8_t *node_get_device_key(struct mesh_node *node); +void node_set_num_elements(struct mesh_node *node, uint8_t num_ele); +uint8_t node_get_num_elements(struct mesh_node *node); +bool node_parse_composition(struct mesh_node *node, uint8_t *buf, uint16_t len); +bool node_add_binding(struct mesh_node *node, uint8_t ele_idx, + uint32_t model_id, uint16_t app_idx); +bool node_del_binding(struct mesh_node *node, uint8_t ele_idx, + uint32_t model_id, uint16_t app_idx); +uint8_t node_default_ttl_get(struct mesh_node *node); +bool node_default_ttl_set(struct mesh_node *node, uint8_t ttl); +bool node_set_sequence_number(struct mesh_node *node, uint32_t seq); +uint32_t node_get_sequence_number(struct mesh_node *node); +int node_get_element_idx(struct mesh_node *node, uint16_t ele_addr); +struct l_queue *node_get_element_models(struct mesh_node *node, uint8_t ele_idx, + int *status); +uint16_t node_get_crpl(struct mesh_node *node); +bool node_init_from_storage(struct mesh_node *node, const uint8_t uuid[16], + struct mesh_config_node *db_node); +uint16_t node_generate_comp(struct mesh_node *node, uint8_t *buf, uint16_t sz); +uint8_t node_lpn_mode_get(struct mesh_node *node); +bool node_relay_mode_set(struct mesh_node *node, bool enable, uint8_t cnt, + uint16_t interval); +uint8_t node_relay_mode_get(struct mesh_node *node, uint8_t *cnt, + uint16_t *interval); +bool node_proxy_mode_set(struct mesh_node *node, bool enable); +uint8_t node_proxy_mode_get(struct mesh_node *node); +bool node_beacon_mode_set(struct mesh_node *node, bool enable); +uint8_t node_beacon_mode_get(struct mesh_node *node); +bool node_friend_mode_set(struct mesh_node *node, bool enable); +uint8_t node_friend_mode_get(struct mesh_node *node); +const char *node_get_element_path(struct mesh_node *node, uint8_t ele_idx); +const char *node_get_owner(struct mesh_node *node); +const char *node_get_app_path(struct mesh_node *node); +bool node_add_pending_local(struct mesh_node *node, void *info); +void node_attach_io_all(struct mesh_io *io); +void node_attach_io(struct mesh_node *node, struct mesh_io *io); +int node_attach(const char *app_path, const char *sender, uint64_t token, + node_ready_func_t cb, void *user_data); +void node_build_attach_reply(struct mesh_node *node, + struct l_dbus_message *reply); +void node_create(const char *app_path, const char *sender, const uint8_t *uuid, + node_ready_func_t cb, void *user_data); +bool node_import(const char *app_path, const char *sender, const uint8_t *uuid, + const uint8_t dev_key[16], const uint8_t net_key[16], + uint16_t net_idx, bool kr, bool ivu, + uint32_t iv_index, uint16_t unicast, + node_ready_func_t cb, void *user_data); +void node_id_set(struct mesh_node *node, uint16_t node_id); +uint16_t node_id_get(struct mesh_node *node); +bool node_dbus_init(struct l_dbus *bus); +void node_cleanup_all(void); +struct mesh_config *node_config_get(struct mesh_node *node); +struct mesh_agent *node_get_agent(struct mesh_node *node); +const char *node_get_storage_dir(struct mesh_node *node); +bool node_load_from_storage(const char *storage_dir); diff --git a/mesh/org.bluez.mesh.service b/mesh/org.bluez.mesh.service new file mode 100644 index 0000000..a61c444 --- /dev/null +++ b/mesh/org.bluez.mesh.service @@ -0,0 +1,5 @@ +[D-BUS Service] +Name=org.bluez.mesh +Exec=/bin/false +User=root +SystemdService=dbus-org.bluez.mesh.service diff --git a/mesh/pb-adv.c b/mesh/pb-adv.c new file mode 100644 index 0000000..6b4a700 --- /dev/null +++ b/mesh/pb-adv.c @@ -0,0 +1,477 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "mesh/mesh-defs.h" +#include "mesh/crypto.h" +#include "mesh/net.h" +#include "mesh/mesh-io.h" +#include "mesh/mesh.h" +#include "mesh/prov.h" +#include "mesh/provision.h" +#include "mesh/pb-adv.h" + + +struct pb_adv_session { + mesh_prov_open_func_t open_cb; + mesh_prov_close_func_t close_cb; + mesh_prov_receive_func_t rx_cb; + mesh_prov_ack_func_t ack_cb; + struct l_timeout *tx_timeout; + uint32_t link_id; + uint16_t exp_len; + uint8_t exp_fcs; + uint8_t exp_segs; + uint8_t got_segs; + uint8_t trans_num; + uint8_t local_acked; + uint8_t local_trans_num; + uint8_t peer_trans_num; + uint8_t last_peer_trans_num; + uint8_t sar[80]; + uint8_t uuid[16]; + bool initiator; + bool opened; + void *user_data; +}; + +#define PB_ADV_ACK 0x01 +#define PB_ADV_OPEN_REQ 0x03 +#define PB_ADV_OPEN_CFM 0x07 +#define PB_ADV_CLOSE 0x0B + +#define PB_ADV_MTU 24 + +struct pb_ack { + uint8_t ad_type; + uint32_t link_id; + uint8_t trans_num; + uint8_t opcode; +} __packed; + +struct pb_open_req{ + uint8_t ad_type; + uint32_t link_id; + uint8_t trans_num; + uint8_t opcode; + uint8_t uuid[16]; +} __packed; + +struct pb_open_cfm{ + uint8_t ad_type; + uint32_t link_id; + uint8_t trans_num; + uint8_t opcode; +} __packed; + +struct pb_close_ind { + uint8_t ad_type; + uint32_t link_id; + uint8_t trans_num; + uint8_t opcode; + uint8_t reason; +} __packed; + +static struct pb_adv_session *pb_session = NULL; + +static const uint8_t filter[1] = { MESH_AD_TYPE_PROVISION }; + +static void send_adv_segs(struct pb_adv_session *session, const uint8_t *data, + uint16_t size) +{ + uint16_t init_size; + uint8_t buf[PB_ADV_MTU + 6] = { MESH_AD_TYPE_PROVISION }; + uint8_t max_seg; + uint8_t consumed; + int i; + + if (!size) + return; + + mesh_send_cancel(filter, sizeof(filter)); + + l_put_be32(session->link_id, buf + 1); + buf[1 + 4] = ++session->local_trans_num; + + if (size > PB_ADV_MTU - 4) { + max_seg = 1 + + (((size - (PB_ADV_MTU - 4)) - 1) / (PB_ADV_MTU - 1)); + init_size = PB_ADV_MTU - 4; + } else { + max_seg = 0; + init_size = size; + } + + /* print_packet("FULL-TX", data, size); */ + + l_debug("Sending %u fragments for %u octets", max_seg + 1, size); + + buf[6] = max_seg << 2; + l_put_be16(size, buf + 7); + buf[9] = mesh_crypto_compute_fcs(data, size); + memcpy(buf + 10, data, init_size); + + l_debug("max_seg: %2.2x", max_seg); + l_debug("size: %2.2x, CRC: %2.2x", size, buf[9]); + /* print_packet("PB-TX", buf + 1, init_size + 9); */ + mesh_send_pkt(MESH_IO_TX_COUNT_UNLIMITED, 200, buf, init_size + 10); + + consumed = init_size; + + for (i = 1; i <= max_seg; i++) { + uint8_t seg_size; /* Amount of payload data being sent */ + + if (size - consumed > PB_ADV_MTU - 1) + seg_size = PB_ADV_MTU - 1; + else + seg_size = size - consumed; + + buf[6] = (i << 2) | 0x02; + memcpy(buf + 7, data + consumed, seg_size); + + /* print_packet("PB-TX", buf + 1, seg_size + 6); */ + + mesh_send_pkt(MESH_IO_TX_COUNT_UNLIMITED, 200, + buf, seg_size + 7); + + consumed += seg_size; + } +} + +static void tx_timeout(struct l_timeout *timeout, void *user_data) +{ + struct pb_adv_session *session = user_data; + mesh_prov_close_func_t cb; + + if (!session || pb_session != session) + return; + + l_timeout_remove(session->tx_timeout); + session->tx_timeout = NULL; + + mesh_send_cancel(filter, sizeof(filter)); + + l_info("TX timeout"); + cb = pb_session->close_cb; + user_data = pb_session->user_data; + l_free(pb_session); + pb_session = NULL; + cb(user_data, 1); +} + +static void pb_adv_tx(void *user_data, void *data, uint16_t len) +{ + struct pb_adv_session *session = user_data; + + if (!session || pb_session != session) + return; + + l_timeout_remove(session->tx_timeout); + session->tx_timeout = l_timeout_create(30, tx_timeout, session, NULL); + + send_adv_segs(session, data, len); +} + +static void send_open_req(struct pb_adv_session *session) +{ + struct pb_open_req open_req = { MESH_AD_TYPE_PROVISION }; + + l_put_be32(session->link_id, &open_req.link_id); + open_req.trans_num = 0; + open_req.opcode = PB_ADV_OPEN_REQ; + memcpy(open_req.uuid, session->uuid, 16); + + mesh_send_cancel(filter, sizeof(filter)); + mesh_send_pkt(MESH_IO_TX_COUNT_UNLIMITED, 500, &open_req, + sizeof(open_req)); +} + +static void send_open_cfm(struct pb_adv_session *session) +{ + struct pb_open_cfm open_cfm = { MESH_AD_TYPE_PROVISION }; + + l_put_be32(session->link_id, &open_cfm.link_id); + open_cfm.trans_num = 0; + open_cfm.opcode = PB_ADV_OPEN_CFM; + + mesh_send_cancel(filter, sizeof(filter)); + mesh_send_pkt(MESH_IO_TX_COUNT_UNLIMITED, 500, &open_cfm, + sizeof(open_cfm)); +} + +static void send_ack(struct pb_adv_session *session, uint8_t trans_num) +{ + struct pb_ack ack = { MESH_AD_TYPE_PROVISION }; + + l_put_be32(session->link_id, &ack.link_id); + ack.trans_num = trans_num; + ack.opcode = PB_ADV_ACK; + + mesh_send_pkt(1, 100, &ack, sizeof(ack)); +} + +static void send_close_ind(struct pb_adv_session *session, uint8_t reason) +{ + struct pb_close_ind close_ind = { MESH_AD_TYPE_PROVISION }; + + if (!pb_session || pb_session != session) + return; + + l_put_be32(session->link_id, &close_ind.link_id); + close_ind.trans_num = 0; + close_ind.opcode = PB_ADV_CLOSE; + close_ind.reason = reason; + + mesh_send_cancel(filter, sizeof(filter)); + mesh_send_pkt(10, 100, &close_ind, sizeof(close_ind)); +} + +static void pb_adv_packet(void *user_data, const uint8_t *pkt, uint16_t len) +{ + struct pb_adv_session *session = user_data; + uint32_t link_id; + size_t offset; + uint8_t trans_num; + uint8_t type; + bool first; + + if (!session || pb_session != session) + return; + + link_id = l_get_be32(pkt + 1); + type = l_get_u8(pkt + 6); + + /* Validate new or existing Connection ID */ + if (session->link_id) { + if (session->link_id != link_id) + return; + } else if (type != 0x03) + return; + else if (!link_id) + return; + + trans_num = l_get_u8(pkt + 5); + pkt += 7; + len -= 7; + + switch (type) { + case PB_ADV_OPEN_CFM: + /* + * Ignore if: + * 1. We are acceptor + * 2. We are already provisioning on different link_id + */ + + if (!session->initiator) + return; + + first = !session->opened; + session->opened = true; + + /* Only call Open callback once */ + if (first) { + l_debug("PB-ADV open confirmed"); + session->open_cb(session->user_data, pb_adv_tx, + session, PB_ADV); + } + return; + + case PB_ADV_OPEN_REQ: + /* + * Ignore if: + * 1. We are initiator + * 2. Open request not addressed to us + * 3. We are already provisioning on different link_id + */ + + if (session->initiator) + return; + + if (memcmp(pkt, session->uuid, 16)) + return; + + first = !session->link_id; + session->link_id = link_id; + session->last_peer_trans_num = 0xFF; + session->local_acked = 0xFF; + session->peer_trans_num = 0x00; + session->local_trans_num = 0x7F; + session->opened = true; + + /* Only call Open callback once */ + if (first) { + l_debug("PB-ADV open requested"); + session->open_cb(session->user_data, pb_adv_tx, + session, PB_ADV); + } + + /* Send CFM once per received request */ + send_open_cfm(session); + break; + + case PB_ADV_CLOSE: + l_timeout_remove(session->tx_timeout); + l_debug("Link closed notification: %2.2x", pkt[0]); + /* Wrap callback for pre-cleaning */ + if (true) { + mesh_prov_close_func_t cb = session->close_cb; + void *user_data = session->user_data; + + l_free(session); + pb_session = NULL; + cb(user_data, pkt[0]); + } + break; + + case PB_ADV_ACK: + if (!session->opened) + return; + + if (trans_num != session->local_trans_num) + return; + + if (session->local_acked > trans_num) + return; + + mesh_send_cancel(filter, sizeof(filter)); + session->local_acked = trans_num; + session->ack_cb(session->user_data, trans_num); + break; + + default: /* DATA SEGMENT */ + if (!session->opened) + return; + + if (trans_num == session->last_peer_trans_num) { + send_ack(session, trans_num); + return; + } + + switch(type & 0x03) { + case 0x00: + session->peer_trans_num = trans_num; + session->exp_len = l_get_be16(pkt); + + l_debug("PB-ADV start with %u fragments, %d octets", + type >> 2, session->exp_len); + + if (session->exp_len > sizeof(session->sar)) { + l_debug("Incoming length exceeded: %d", + session->exp_len); + return; + } + + session->exp_fcs = l_get_u8(pkt + 2); + session->exp_segs = 0xff >> (7 - (type >> 2)); + + /* Save first segment */ + memcpy(session->sar, pkt + 3, len - 3); + session->got_segs |= 1; + break; + + case 0x02: + session->peer_trans_num = trans_num; + offset = 20 + (((type >> 2) - 1) * 23); + + if (offset + len - 3 > sizeof(session->sar)) { + l_debug("Length exceeded: %d", + session->exp_len); + return; + } + + l_debug("Processing fragment %u", type >> 2); + memcpy(session->sar + offset, pkt, len); + session->got_segs |= 1 << (type >> 2); + break; + + default: + /* Malformed or unrecognized */ + return; + } + + if (session->got_segs != session->exp_segs) + return; + + /* Validate RXed packet and pass up to Provisioning */ + if (!mesh_crypto_check_fcs(session->sar, + session->exp_len, + session->exp_fcs)) { + + /* This can be a false negative if first + * segment missed, and can almost always + * be ignored. + */ + + l_debug("Invalid FCS"); + return; + } + + if (session->last_peer_trans_num != session->peer_trans_num) { + session->got_segs = 0; + session->rx_cb(session->user_data, session->sar, + session->exp_len); + } + + session->last_peer_trans_num = session->peer_trans_num; + send_ack(session, session->last_peer_trans_num); + } +} + +bool pb_adv_reg(bool initiator, mesh_prov_open_func_t open_cb, + mesh_prov_close_func_t close_cb, + mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, + uint8_t uuid[16], void *user_data) +{ + if (pb_session) + return false; + + pb_session = l_new(struct pb_adv_session, 1); + pb_session->open_cb = open_cb; + pb_session->close_cb = close_cb; + pb_session->rx_cb = rx_cb; + pb_session->ack_cb = ack_cb; + pb_session->user_data = user_data; + pb_session->initiator = initiator; + memcpy(pb_session->uuid, uuid, 16); + + mesh_reg_prov_rx(pb_adv_packet, pb_session); + + if (initiator) { + l_getrandom(&pb_session->link_id, sizeof(pb_session->link_id)); + send_open_req(pb_session); + } + + return true; +} + +void pb_adv_unreg(void *user_data) +{ + if (!pb_session || pb_session->user_data != user_data) + return; + + l_timeout_remove(pb_session->tx_timeout); + send_close_ind(pb_session, 0); + l_free(pb_session); + pb_session = NULL; +} diff --git a/mesh/pb-adv.h b/mesh/pb-adv.h new file mode 100644 index 0000000..80d53d2 --- /dev/null +++ b/mesh/pb-adv.h @@ -0,0 +1,24 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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. + * + */ + +bool pb_adv_reg(bool initiator, mesh_prov_open_func_t open_cb, + mesh_prov_close_func_t close_cb, + mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb, + uint8_t uuid[16], void *user_data); +void pb_adv_unreg(void *user_data); diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c new file mode 100644 index 0000000..57eb1e7 --- /dev/null +++ b/mesh/prov-acceptor.c @@ -0,0 +1,682 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "src/shared/ecc.h" + +#include "mesh/mesh-defs.h" +#include "mesh/util.h" +#include "mesh/crypto.h" +#include "mesh/net.h" +#include "mesh/prov.h" +#include "mesh/provision.h" +#include "mesh/pb-adv.h" +#include "mesh/mesh.h" +#include "mesh/agent.h" + +/* Quick size sanity check */ +static const uint16_t expected_pdu_size[] = { + 2, /* PROV_INVITE */ + 12, /* PROV_CAPS */ + 6, /* PROV_START */ + 65, /* PROV_PUB_KEY */ + 1, /* PROV_INP_CMPLT */ + 17, /* PROV_CONFIRM */ + 17, /* PROV_RANDOM */ + 34, /* PROV_DATA */ + 1, /* PROV_COMPLETE */ + 2, /* PROV_FAILED */ +}; + +#define BEACON_TYPE_UNPROVISIONED 0x00 + +static const uint8_t pkt_filter = MESH_AD_TYPE_PROVISION; +static const uint8_t bec_filter[] = {MESH_AD_TYPE_BEACON, + BEACON_TYPE_UNPROVISIONED}; + +enum acp_state { + ACP_PROV_IDLE = 0, + ACP_PROV_CAPS_SENT, + ACP_PROV_CAPS_ACKED, + ACP_PROV_KEY_SENT, + ACP_PROV_KEY_ACKED, + ACP_PROV_INP_CMPLT_SENT, + ACP_PROV_INP_CMPLT_ACKED, + ACP_PROV_CONF_SENT, + ACP_PROV_CONF_ACKED, + ACP_PROV_RAND_SENT, + ACP_PROV_RAND_ACKED, + ACP_PROV_CMPLT_SENT, + ACP_PROV_FAIL_SENT, +}; + +#define MAT_REMOTE_PUBLIC 0x01 +#define MAT_LOCAL_PRIVATE 0x02 +#define MAT_RAND_AUTH 0x04 +#define MAT_SECRET (MAT_REMOTE_PUBLIC | MAT_LOCAL_PRIVATE) + +struct mesh_prov_acceptor { + mesh_prov_acceptor_complete_func_t cmplt; + prov_trans_tx_t trans_tx; + void *agent; + void *caller_data; + void *trans_data; + struct l_timeout *timeout; + uint32_t to_secs; + enum acp_state state; + uint8_t transport; + uint8_t material; + uint8_t expected; + int8_t previous; + struct conf_input conf_inputs; + uint8_t calc_key[16]; + uint8_t salt[16]; + uint8_t confirm[16]; + uint8_t s_key[16]; + uint8_t s_nonce[13]; + uint8_t private_key[32]; + uint8_t secret[32]; + uint8_t rand_auth_workspace[48]; +}; + +static struct mesh_prov_acceptor *prov = NULL; + +static void acceptor_free(void) +{ + if (!prov) + return; + + l_timeout_remove(prov->timeout); + + mesh_send_cancel(bec_filter, sizeof(bec_filter)); + mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); + + pb_adv_unreg(prov); + + l_free(prov); + prov = NULL; +} + +static void acp_prov_close(void *user_data, uint8_t reason) +{ + /* TODO: Handle Close */ +} + +static void prov_to(struct l_timeout *timeout, void *user_data) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + uint8_t fail_code[2] = {PROV_FAILED, PROV_ERR_UNEXPECTED_ERR}; + + if (rx_prov != prov) + return; + + prov->timeout = NULL; + + if (prov->cmplt && prov->trans_tx) { + prov->cmplt(prov->caller_data, PROV_ERR_TIMEOUT, NULL); + prov->cmplt = NULL; + prov->trans_tx(prov->trans_data, fail_code, 2); + prov->timeout = l_timeout_create(1, prov_to, prov, NULL); + return; + } + + acceptor_free(); +} + +static void acp_prov_open(void *user_data, prov_trans_tx_t trans_tx, + void *trans_data, uint8_t transport) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + + /* Only one provisioning session may be open at a time */ + if (rx_prov != prov) + return; + + /* Only one provisioning session may be open at a time */ + if (prov->trans_tx && prov->trans_tx != trans_tx && + prov->transport != transport) + return; + + if (transport != PB_ADV) + return; + + prov->trans_tx = trans_tx; + prov->transport = transport; + prov->trans_data = trans_data; + prov->timeout = l_timeout_create(prov->to_secs, prov_to, prov, NULL); +} + +static void swap_u256_bytes(uint8_t *u256) +{ + int i; + + /* End-to-End byte reflection of 32 octet buffer */ + for (i = 0; i < 16; i++) { + u256[i] ^= u256[31 - i]; + u256[31 - i] ^= u256[i]; + u256[i] ^= u256[31 - i]; + } +} + +static void prov_calc_secret(const uint8_t *pub, const uint8_t *priv, + uint8_t *secret) +{ + uint8_t tmp[64]; + + /* Convert to ECC byte order */ + memcpy(tmp, pub, 64); + swap_u256_bytes(tmp); + swap_u256_bytes(tmp + 32); + + ecdh_shared_secret(tmp, priv, secret); + + /* Convert to Mesh byte order */ + swap_u256_bytes(secret); +} + +static void acp_credentials(struct mesh_prov_acceptor *prov) +{ + prov_calc_secret(prov->conf_inputs.prv_pub_key, + prov->private_key, prov->secret); + + mesh_crypto_s1(&prov->conf_inputs, + sizeof(prov->conf_inputs), prov->salt); + + mesh_crypto_prov_conf_key(prov->secret, prov->salt, + prov->calc_key); + + l_getrandom(prov->rand_auth_workspace, 16); + + print_packet("PublicKeyProv", prov->conf_inputs.prv_pub_key, 64); + print_packet("PublicKeyDev", prov->conf_inputs.dev_pub_key, 64); + print_packet("PrivateKeyLocal", prov->private_key, 32); + print_packet("ConfirmationInputs", &prov->conf_inputs, + sizeof(prov->conf_inputs)); + print_packet("ECDHSecret", prov->secret, 32); + print_packet("LocalRandom", prov->rand_auth_workspace, 16); + print_packet("ConfirmationSalt", prov->salt, 16); + print_packet("ConfirmationKey", prov->calc_key, 16); +} + +static uint32_t digit_mod(uint8_t power) +{ + uint32_t ret = 1; + + while (power--) + ret *= 10; + + return ret; +} + +static void number_cb(void *user_data, int err, uint32_t number) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + struct prov_fail_msg msg; + + if (prov != rx_prov) + return; + + if (err) { + msg.opcode = PROV_FAILED; + msg.reason = PROV_ERR_UNEXPECTED_ERR; + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); + return; + } + + /* Save two copies, to generate two confirmation values */ + l_put_be32(number, prov->rand_auth_workspace + 28); + l_put_be32(number, prov->rand_auth_workspace + 44); + prov->material |= MAT_RAND_AUTH; + msg.opcode = PROV_INP_CMPLT; + prov->trans_tx(prov->trans_data, &msg.opcode, 1); +} + +static void static_cb(void *user_data, int err, uint8_t *key, uint32_t len) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + struct prov_fail_msg msg; + + if (prov != rx_prov) + return; + + if (err || !key || len != 16) { + msg.opcode = PROV_FAILED; + msg.reason = PROV_ERR_UNEXPECTED_ERR; + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); + return; + } + + /* Save two copies, to generate two confirmation values */ + memcpy(prov->rand_auth_workspace + 16, key, 16); + memcpy(prov->rand_auth_workspace + 32, key, 16); + prov->material |= MAT_RAND_AUTH; +} + +static void priv_key_cb(void *user_data, int err, uint8_t *key, uint32_t len) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + struct prov_fail_msg msg; + + if (prov != rx_prov) + return; + + if (err || !key || len != 32) { + msg.opcode = PROV_FAILED; + msg.reason = PROV_ERR_UNEXPECTED_ERR; + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); + return; + } + + memcpy(prov->private_key, key, 32); + ecc_make_public_key(prov->private_key, + prov->conf_inputs.dev_pub_key); + + /* Convert to Mesh byte order */ + swap_u256_bytes(prov->conf_inputs.dev_pub_key); + swap_u256_bytes(prov->conf_inputs.dev_pub_key + 32); + + prov->material |= MAT_LOCAL_PRIVATE; + if ((prov->material & MAT_SECRET) == MAT_SECRET) + acp_credentials(prov); +} + +static void send_caps(struct mesh_prov_acceptor *prov) +{ + struct prov_caps_msg msg; + + msg.opcode = PROV_CAPS; + memcpy(&msg.caps, &prov->conf_inputs.caps, + sizeof(prov->conf_inputs.caps)); + + prov->state = ACP_PROV_CAPS_SENT; + prov->expected = PROV_START; + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); +} + +static void send_pub_key(struct mesh_prov_acceptor *prov) +{ + struct prov_pub_key_msg msg; + + msg.opcode = PROV_PUB_KEY; + memcpy(msg.pub_key, prov->conf_inputs.dev_pub_key, sizeof(msg.pub_key)); + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); +} + +static void send_conf(struct mesh_prov_acceptor *prov) +{ + struct prov_conf_msg msg; + + msg.opcode = PROV_CONFIRM; + mesh_crypto_aes_cmac(prov->calc_key, prov->rand_auth_workspace, 32, + msg.conf); + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); +} + +static void send_rand(struct mesh_prov_acceptor *prov) +{ + struct prov_rand_msg msg; + + msg.opcode = PROV_RANDOM; + memcpy(msg.rand, prov->rand_auth_workspace, sizeof(msg.rand)); + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); +} + +static void acp_prov_rx(void *user_data, const uint8_t *data, uint16_t len) +{ + struct mesh_prov_acceptor *rx_prov = user_data; + struct mesh_prov_node_info *info; + struct prov_fail_msg fail; + uint8_t type = *data++; + uint32_t oob_key; + uint64_t decode_mic; + bool result; + + if (rx_prov != prov || !prov->trans_tx) + return; + + l_debug("Provisioning packet received type: %2.2x (%u octets)", + type, len); + + if (type == prov->previous) { + l_error("Ignore repeated %2.2x packet", type); + return; + } else if (type > prov->expected || type < prov->previous) { + l_error("Expected %2.2x, Got:%2.2x", prov->expected, type); + fail.reason = PROV_ERR_UNEXPECTED_PDU; + goto failure; + } + + if (type >= L_ARRAY_SIZE(expected_pdu_size) || + len != expected_pdu_size[type]) { + l_error("Expected PDU size %d, Got %d (type: %2.2x)", + len, expected_pdu_size[type], type); + fail.reason = PROV_ERR_INVALID_FORMAT; + goto failure; + } + + switch (type){ + case PROV_INVITE: /* Prov Invite */ + prov->conf_inputs.invite.attention = data[0]; + send_caps(prov); + break; + + case PROV_START: /* Prov Start */ + memcpy(&prov->conf_inputs.start, data, + sizeof(prov->conf_inputs.start)); + + if (prov->conf_inputs.start.algorithm || + prov->conf_inputs.start.pub_key > 1 || + prov->conf_inputs.start.auth_method > 3) { + fail.reason = PROV_ERR_INVALID_FORMAT; + goto failure; + } + + if (prov->conf_inputs.start.pub_key) { + if (prov->conf_inputs.caps.pub_type) { + /* Prompt Agent for Private Key of OOB */ + mesh_agent_request_private_key(prov->agent, + priv_key_cb, prov); + } else { + fail.reason = PROV_ERR_INVALID_PDU; + goto failure; + } + } else { + /* Ephemeral Public Key requested */ + ecc_make_key(prov->conf_inputs.dev_pub_key, + prov->private_key); + swap_u256_bytes(prov->conf_inputs.dev_pub_key); + swap_u256_bytes(prov->conf_inputs.dev_pub_key + 32); + prov->material |= MAT_LOCAL_PRIVATE; + } + + prov->expected = PROV_PUB_KEY; + break; + + case PROV_PUB_KEY: /* Public Key */ + /* Save Key */ + memcpy(prov->conf_inputs.prv_pub_key, data, 64); + prov->material |= MAT_REMOTE_PUBLIC; + prov->expected = PROV_CONFIRM; + + if ((prov->material & MAT_SECRET) != MAT_SECRET) + return; + + acp_credentials(prov); + + if (!prov->conf_inputs.start.pub_key) + send_pub_key(prov); + + /* Start Step 3 */ + switch (prov->conf_inputs.start.auth_method) { + default: + case 0: + /* Auth Type 3c - No OOB */ + break; + + case 1: + /* Auth Type 3c - Static OOB */ + /* Prompt Agent for Static OOB */ + fail.reason = mesh_agent_request_static(prov->agent, + static_cb, prov); + + if (fail.reason) + goto failure; + + break; + + case 2: + /* Auth Type 3a - Output OOB */ + l_getrandom(&oob_key, sizeof(oob_key)); + oob_key %= digit_mod(prov->conf_inputs.start.auth_size); + + /* Save two copies, for two confirmation values */ + l_put_be32(oob_key, prov->rand_auth_workspace + 28); + l_put_be32(oob_key, prov->rand_auth_workspace + 44); + prov->material |= MAT_RAND_AUTH; + + if (prov->conf_inputs.start.auth_action == + PROV_ACTION_OUT_ALPHA) { + /* TODO: Construst NUL-term string to pass */ + fail.reason = mesh_agent_display_string( + prov->agent, NULL, NULL, prov); + } else { + /* Ask Agent to Display U32 */ + fail.reason = mesh_agent_display_number( + prov->agent, false, + prov->conf_inputs.start.auth_action, + oob_key, NULL, prov); + } + + if (fail.reason) + goto failure; + + break; + + case 3: + /* Auth Type 3b - input OOB */ + /* Prompt Agent for Input OOB */ + if (prov->conf_inputs.start.auth_action == + PROV_ACTION_IN_ALPHA) { + fail.reason = mesh_agent_prompt_alpha( + prov->agent, + static_cb, prov); + } else { + fail.reason = mesh_agent_prompt_number( + prov->agent, false, + prov->conf_inputs.start.auth_action, + number_cb, prov); + } + + if (fail.reason) + goto failure; + + break; + } + + prov->expected = PROV_CONFIRM; + break; + + case PROV_CONFIRM: /* Confirmation */ + /* Save Provisioners confirmation for later compare */ + memcpy(prov->confirm, data, 16); + prov->expected = PROV_RANDOM; + + send_conf(prov); + break; + + case PROV_RANDOM: /* Random Value */ + /* Calculate Session key (needed later) while data is fresh */ + mesh_crypto_prov_prov_salt(prov->salt, data, + prov->rand_auth_workspace, + prov->salt); + mesh_crypto_session_key(prov->secret, prov->salt, prov->s_key); + mesh_crypto_nonce(prov->secret, prov->salt, prov->s_nonce); + + /* Calculate expected Provisioner Confirm */ + memcpy(prov->rand_auth_workspace + 16, data, 16); + mesh_crypto_aes_cmac(prov->calc_key, + prov->rand_auth_workspace + 16, 32, + prov->calc_key); + + /* Compare our calculation with Provisioners */ + if (memcmp(prov->calc_key, prov->confirm, 16)) { + fail.reason = PROV_ERR_CONFIRM_FAILED; + goto failure; + } + + /* Send Random value we used */ + send_rand(prov); + prov->expected = PROV_DATA; + break; + + case PROV_DATA: /* Provisioning Data */ + + /* Calculate our device key */ + mesh_crypto_device_key(prov->secret, + prov->salt, + prov->calc_key); + + /* Decrypt new node data into workspace */ + mesh_crypto_aes_ccm_decrypt(prov->s_nonce, prov->s_key, + NULL, 0, + data, len - 1, prov->rand_auth_workspace, + &decode_mic, sizeof(decode_mic)); + + /* Validate that the data hasn't been messed with in transit */ + if (l_get_be64(data + 25) != decode_mic) { + l_error("Provisioning Failed-MIC compare"); + fail.reason = PROV_ERR_DECRYPT_FAILED; + goto failure; + } + + info = l_malloc(sizeof(struct mesh_prov_node_info)); + + memcpy(info->device_key, prov->calc_key, 16); + memcpy(info->net_key, prov->rand_auth_workspace, 16); + info->net_index = l_get_be16(prov->rand_auth_workspace + 16); + info->flags = prov->rand_auth_workspace[18]; + info->iv_index = l_get_be32(prov->rand_auth_workspace + 19); + info->unicast = l_get_be16(prov->rand_auth_workspace + 23); + + result = prov->cmplt(prov->caller_data, PROV_ERR_SUCCESS, info); + prov->cmplt = NULL; + l_free(info); + + if (result) { + prov->rand_auth_workspace[0] = PROV_COMPLETE; + prov->trans_tx(prov->trans_data, + prov->rand_auth_workspace, 1); + goto cleanup; + } else { + fail.reason = PROV_ERR_UNEXPECTED_ERR; + goto failure; + } + break; + + case PROV_FAILED: /* Provisioning Error -- abort */ + /* TODO: Call Complete Callback (Fail)*/ + prov->cmplt(prov->caller_data, + data[0] ? data[0] : PROV_ERR_UNEXPECTED_ERR, + NULL); + prov->cmplt = NULL; + goto cleanup; + } + + if (prov) + prov->previous = type; + return; + +failure: + fail.opcode = PROV_FAILED; + prov->trans_tx(prov->trans_data, &fail, sizeof(fail)); + if (prov->cmplt) + prov->cmplt(prov->caller_data, fail.reason, NULL); + prov->cmplt = NULL; + +cleanup: + l_timeout_remove(prov->timeout); + + /* Give PB Link 5 seconds to end session */ + prov->timeout = l_timeout_create(5, prov_to, prov, NULL); +} + +static void acp_prov_ack(void *user_data, uint8_t msg_num) +{ + /* TODO: Handle PB-ADV Ack */ +} + + +/* This starts unprovisioned device beacon */ +bool acceptor_start(uint8_t num_ele, uint8_t uuid[16], + uint16_t algorithms, uint32_t timeout, + struct mesh_agent *agent, + mesh_prov_acceptor_complete_func_t complete_cb, + void *caller_data) +{ + struct mesh_agent_prov_caps *caps; + uint8_t beacon[24] = {MESH_AD_TYPE_BEACON, + BEACON_TYPE_UNPROVISIONED}; + uint8_t len = sizeof(beacon) - sizeof(uint32_t); + bool result; + + /* Invoked from Join() method in mesh-api.txt, to join a + * remote mesh network. + */ + + if (prov) + return false; + + prov = l_new(struct mesh_prov_acceptor, 1); + prov->to_secs = timeout; + prov->agent = agent; + prov->cmplt = complete_cb; + prov->previous = -1; + prov->caller_data = caller_data; + + caps = mesh_agent_get_caps(agent); + + /* TODO: Should we sanity check values here or elsewhere? */ + prov->conf_inputs.caps.num_ele = num_ele; + prov->conf_inputs.caps.pub_type = caps->pub_type; + prov->conf_inputs.caps.static_type = caps->static_type; + prov->conf_inputs.caps.output_size = caps->output_size; + prov->conf_inputs.caps.input_size = caps->input_size; + + /* Store UINT16 values in Over-the-Air order, in packed structure + * for crypto inputs + */ + l_put_be16(algorithms, &prov->conf_inputs.caps.algorithms); + l_put_be16(caps->output_action, &prov->conf_inputs.caps.output_action); + l_put_be16(caps->input_action, &prov->conf_inputs.caps.input_action); + + /* Compose Unprovisioned Beacon */ + memcpy(beacon + 2, uuid, 16); + l_put_be16(caps->oob_info, beacon + 18); + if (caps->oob_info & OOB_INFO_URI_HASH){ + l_put_be32(caps->uri_hash, beacon + 20); + len += sizeof(uint32_t); + } + + /* Infinitely Beacon until Canceled, or Provisioning Starts */ + result = mesh_send_pkt(0, 500, beacon, len); + + if (!result) + goto error_fail; + + /* Always register for PB-ADV */ + result = pb_adv_reg(false, acp_prov_open, acp_prov_close, acp_prov_rx, + acp_prov_ack, uuid, prov); + + if (result) + return true; + +error_fail: + acceptor_free(); + return false; +} + +void acceptor_cancel(void *user_data) +{ + acceptor_free(); +} diff --git a/mesh/prov-initiator.c b/mesh/prov-initiator.c new file mode 100644 index 0000000..5e45d68 --- /dev/null +++ b/mesh/prov-initiator.c @@ -0,0 +1,772 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018-2019 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "src/shared/ecc.h" + +#include "mesh/mesh-defs.h" +#include "mesh/util.h" +#include "mesh/crypto.h" +#include "mesh/net.h" +#include "mesh/node.h" +#include "mesh/keyring.h" +#include "mesh/prov.h" +#include "mesh/provision.h" +#include "mesh/pb-adv.h" +#include "mesh/mesh.h" +#include "mesh/agent.h" + +/* Quick size sanity check */ +static const uint16_t expected_pdu_size[] = { + 2, /* PROV_INVITE */ + 12, /* PROV_CAPS */ + 6, /* PROV_START */ + 65, /* PROV_PUB_KEY */ + 1, /* PROV_INP_CMPLT */ + 17, /* PROV_CONFIRM */ + 17, /* PROV_RANDOM */ + 34, /* PROV_DATA */ + 1, /* PROV_COMPLETE */ + 2, /* PROV_FAILED */ +}; + +#define BEACON_TYPE_UNPROVISIONED 0x00 + +static const uint8_t pkt_filter = MESH_AD_TYPE_PROVISION; + +enum int_state { + INT_PROV_IDLE = 0, + INT_PROV_INVITE_SENT, + INT_PROV_INVITE_ACKED, + INT_PROV_START_SENT, + INT_PROV_START_ACKED, + INT_PROV_KEY_SENT, + INT_PROV_KEY_ACKED, + INT_PROV_CONF_SENT, + INT_PROV_CONF_ACKED, + INT_PROV_RAND_SENT, + INT_PROV_RAND_ACKED, + INT_PROV_DATA_SENT, + INT_PROV_DATA_ACKED, +}; + +#define MAT_REMOTE_PUBLIC 0x01 +#define MAT_LOCAL_PRIVATE 0x02 +#define MAT_RAND_AUTH 0x04 +#define MAT_SECRET (MAT_REMOTE_PUBLIC | MAT_LOCAL_PRIVATE) + +struct mesh_prov_initiator { + mesh_prov_initiator_complete_func_t cmplt; + mesh_prov_initiator_data_req_func_t get_prov_data; + prov_trans_tx_t trans_tx; + void *agent; + void *caller_data; + void *trans_data; + struct mesh_node *node; + struct l_timeout *timeout; + uint32_t to_secs; + enum int_state state; + enum trans_type transport; + uint16_t net_idx; + uint16_t unicast; + uint8_t material; + uint8_t expected; + int8_t previous; + struct conf_input conf_inputs; + uint8_t calc_key[16]; + uint8_t salt[16]; + uint8_t confirm[16]; + uint8_t s_key[16]; + uint8_t s_nonce[13]; + uint8_t private_key[32]; + uint8_t secret[32]; + uint8_t rand_auth_workspace[48]; +}; + +static struct mesh_prov_initiator *prov = NULL; + +static void initiator_free(void) +{ + if (prov) + l_timeout_remove(prov->timeout); + + mesh_send_cancel(&pkt_filter, sizeof(pkt_filter)); + + pb_adv_unreg(prov); + + l_free(prov); + prov = NULL; +} + +static void int_prov_close(void *user_data, uint8_t reason) +{ + struct mesh_prov_initiator *prov = user_data; + struct mesh_prov_node_info info; + + if (reason != PROV_ERR_SUCCESS) { + prov->cmplt(prov->caller_data, reason, NULL); + initiator_free(); + return; + } + + memcpy(info.device_key, prov->calc_key, 16); + info.net_index = prov->net_idx; + info.unicast = prov->unicast; + info.num_ele = prov->conf_inputs.caps.num_ele; + + prov->cmplt(prov->caller_data, PROV_ERR_SUCCESS, &info); + initiator_free(); +} + +static void swap_u256_bytes(uint8_t *u256) +{ + int i; + + /* End-to-End byte reflection of 32 octet buffer */ + for (i = 0; i < 16; i++) { + u256[i] ^= u256[31 - i]; + u256[31 - i] ^= u256[i]; + u256[i] ^= u256[31 - i]; + } +} + +static void int_prov_open(void *user_data, prov_trans_tx_t trans_tx, + void *trans_data, uint8_t transport) +{ + struct mesh_prov_initiator *rx_prov = user_data; + struct prov_invite_msg msg = { PROV_INVITE, { 30 }}; + + /* Only one provisioning session may be open at a time */ + if (rx_prov != prov) + return; + + /* Only one provisioning session may be open at a time */ + if (prov->trans_tx && prov->trans_tx != trans_tx && + prov->transport != transport) + return; + + /* We only care here if transport does *not* match */ + if (transport != prov->transport) + return; + + /* Always use an ephemeral key when Initiator */ + ecc_make_key(prov->conf_inputs.prv_pub_key, prov->private_key); + swap_u256_bytes(prov->conf_inputs.prv_pub_key); + swap_u256_bytes(prov->conf_inputs.prv_pub_key + 32); + prov->material |= MAT_LOCAL_PRIVATE; + + prov->trans_tx = trans_tx; + prov->trans_data = trans_data; + prov->state = INT_PROV_INVITE_SENT; + prov->expected = PROV_CAPS; + + prov->conf_inputs.invite.attention = msg.invite.attention; + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); + return; +} + +static void prov_calc_secret(const uint8_t *pub, const uint8_t *priv, + uint8_t *secret) +{ + uint8_t tmp[64]; + + /* Convert to ECC byte order */ + memcpy(tmp, pub, 64); + swap_u256_bytes(tmp); + swap_u256_bytes(tmp + 32); + + ecdh_shared_secret(tmp, priv, secret); + + /* Convert to Mesh byte order */ + swap_u256_bytes(secret); +} + +static void int_credentials(struct mesh_prov_initiator *prov) +{ + prov_calc_secret(prov->conf_inputs.dev_pub_key, + prov->private_key, prov->secret); + + mesh_crypto_s1(&prov->conf_inputs, + sizeof(prov->conf_inputs), prov->salt); + + mesh_crypto_prov_conf_key(prov->secret, prov->salt, + prov->calc_key); + + l_getrandom(prov->rand_auth_workspace, 16); + + print_packet("PublicKeyProv", prov->conf_inputs.prv_pub_key, 64); + print_packet("PublicKeyDev", prov->conf_inputs.dev_pub_key, 64); + print_packet("PrivateKeyLocal", prov->private_key, 32); + print_packet("ConfirmationInputs", &prov->conf_inputs, + sizeof(prov->conf_inputs)); + print_packet("ECDHSecret", prov->secret, 32); + print_packet("LocalRandom", prov->rand_auth_workspace, 16); + print_packet("ConfirmationSalt", prov->salt, 16); + print_packet("ConfirmationKey", prov->calc_key, 16); +} + +static uint8_t u16_high_bit(uint16_t mask) +{ + uint8_t cnt = 0; + + if (!mask) + return 0xff; + + while (mask & 0xfffe) { + cnt++; + mask >>= 1; + } + + return cnt; +} + +static uint32_t digit_mod(uint8_t power) +{ + uint32_t ret = 1; + + while (power--) + ret *= 10; + + return ret; +} + +static void calc_local_material(const uint8_t *random) +{ + /* Calculate SessionKey while the data is fresh */ + mesh_crypto_prov_prov_salt(prov->salt, + prov->rand_auth_workspace, random, + prov->salt); + mesh_crypto_session_key(prov->secret, prov->salt, + prov->s_key); + mesh_crypto_nonce(prov->secret, prov->salt, prov->s_nonce); + + print_packet("SessionKey", prov->s_key, sizeof(prov->s_key)); + print_packet("Nonce", prov->s_nonce, sizeof(prov->s_nonce)); +} + +static void send_confirm(struct mesh_prov_initiator *prov) +{ + struct prov_conf_msg msg; + + msg.opcode = PROV_CONFIRM; + mesh_crypto_aes_cmac(prov->calc_key, prov->rand_auth_workspace, + 32, msg.conf); + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); + prov->state = INT_PROV_CONF_SENT; + prov->expected = PROV_CONFIRM; +} + +static void number_cb(void *user_data, int err, uint32_t number) +{ + struct mesh_prov_initiator *rx_prov = user_data; + struct prov_fail_msg msg; + + if (prov != rx_prov) + return; + + if (err) { + msg.opcode = PROV_FAILED; + msg.reason = PROV_ERR_UNEXPECTED_ERR; + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); + return; + } + + /* Save two copies, to generate two confirmation values */ + l_put_be32(number, prov->rand_auth_workspace + 28); + l_put_be32(number, prov->rand_auth_workspace + 44); + prov->material |= MAT_RAND_AUTH; + send_confirm(prov); +} + +static void static_cb(void *user_data, int err, uint8_t *key, uint32_t len) +{ + struct mesh_prov_initiator *rx_prov = user_data; + struct prov_fail_msg msg; + + if (prov != rx_prov) + return; + + if (err || !key || len != 16) { + msg.opcode = PROV_FAILED; + msg.reason = PROV_ERR_UNEXPECTED_ERR; + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); + return; + } + + memcpy(prov->rand_auth_workspace + 16, key, 16); + memcpy(prov->rand_auth_workspace + 32, key, 16); + prov->material |= MAT_RAND_AUTH; + send_confirm(prov); +} + +static void pub_key_cb(void *user_data, int err, uint8_t *key, uint32_t len) +{ + struct mesh_prov_initiator *rx_prov = user_data; + struct prov_fail_msg msg; + + if (prov != rx_prov) + return; + + if (err || !key || len != 64) { + msg.opcode = PROV_FAILED; + msg.reason = PROV_ERR_UNEXPECTED_ERR; + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); + return; + } + + memcpy(prov->conf_inputs.dev_pub_key, key, 64); + prov->material |= MAT_REMOTE_PUBLIC; + + if ((prov->material & MAT_SECRET) == MAT_SECRET) + int_credentials(prov); + + send_confirm(prov); +} + +static void send_pub_key(struct mesh_prov_initiator *prov) +{ + struct prov_pub_key_msg msg; + + msg.opcode = PROV_PUB_KEY; + memcpy(msg.pub_key, prov->conf_inputs.prv_pub_key, 64); + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); + prov->state = INT_PROV_KEY_SENT; +} + +static void send_random(struct mesh_prov_initiator *prov) +{ + struct prov_rand_msg msg; + + msg.opcode = PROV_RANDOM; + memcpy(msg.rand, prov->rand_auth_workspace, sizeof(msg.rand)); + prov->trans_tx(prov->trans_data, &msg, sizeof(msg)); + prov->state = INT_PROV_RAND_SENT; + prov->expected = PROV_RANDOM; +} + +void initiator_prov_data(uint16_t net_idx, uint16_t primary, void *caller_data) +{ + struct prov_data_msg prov_data; + struct prov_fail_msg prov_fail; + struct keyring_net_key key; + struct mesh_net *net; + uint32_t iv_index; + uint8_t snb_flags; + + if (!prov || caller_data != prov->caller_data) + return; + + if (prov->state != INT_PROV_RAND_ACKED) + return; + + net = node_get_net(prov->node); + prov->expected = PROV_COMPLETE; + + /* Calculate remote device key */ + mesh_crypto_device_key(prov->secret, + prov->salt, + prov->calc_key); + + print_packet("DevKey", prov->calc_key, 16); + + /* Fill Prov Data Structure */ + if (!keyring_get_net_key(prov->node, net_idx, &key)) { + prov_fail.reason = PROV_ERR_UNEXPECTED_ERR; + goto failure; + } + + prov->unicast = primary; + prov->net_idx = net_idx; + mesh_net_get_snb_state(net, &snb_flags, &iv_index); + + prov_data.opcode = PROV_DATA; + + if (key.phase == KEY_REFRESH_PHASE_TWO) { + memcpy(&prov_data.data.net_key, key.new_key, 16); + snb_flags |= PROV_FLAG_KR; + } else + memcpy(&prov_data.data.net_key, key.old_key, 16); + + l_put_be16(net_idx, &prov_data.data.net_idx); + l_put_u8(snb_flags, &prov_data.data.flags); + l_put_be32(iv_index, &prov_data.data.iv_index); + l_put_be16(primary, &prov_data.data.primary); + + print_packet("ProvData", &prov_data.data, sizeof(prov_data.data)); + /* Encrypt Prov Data */ + mesh_crypto_aes_ccm_encrypt(prov->s_nonce, prov->s_key, + NULL, 0, + &prov_data.data, + sizeof(prov_data.data), + &prov_data.data, + NULL, sizeof(prov_data.mic)); + print_packet("EncdData", &prov_data.data, sizeof(prov_data) - 1); + prov->trans_tx(prov->trans_data, &prov_data, sizeof(prov_data)); + prov->state = INT_PROV_DATA_SENT; + return; + +failure: + l_debug("Failing... %d", prov_fail.reason); + prov_fail.opcode = PROV_FAILED; + prov->trans_tx(prov->trans_data, &prov_fail, sizeof(prov_fail)); + /* TODO: Call Complete Callback (Fail)*/ +} + + +static void int_prov_rx(void *user_data, const uint8_t *data, uint16_t len) +{ + struct mesh_prov_initiator *rx_prov = user_data; + uint8_t *out; + uint8_t type = *data++; + uint8_t fail_code[2]; + uint32_t oob_key; + + if (rx_prov != prov || !prov->trans_tx) + return; + + l_debug("Provisioning packet received type: %2.2x (%u octets)", + type, len); + + if (type == prov->previous) { + l_error("Ignore repeated %2.2x packet", type); + return; + } else if (type > prov->expected || type < prov->previous) { + l_error("Expected %2.2x, Got:%2.2x", prov->expected, type); + fail_code[1] = PROV_ERR_UNEXPECTED_PDU; + goto failure; + } + + if (type >= L_ARRAY_SIZE(expected_pdu_size) || + len != expected_pdu_size[type]) { + l_error("Expected PDU size %d, Got %d (type: %2.2x)", + len, expected_pdu_size[type], type); + fail_code[1] = PROV_ERR_INVALID_FORMAT; + goto failure; + } + + switch (type) { + case PROV_CAPS: /* Capabilities */ + prov->state = INT_PROV_INVITE_ACKED; + memcpy(&prov->conf_inputs.caps, data, + sizeof(prov->conf_inputs.caps)); + + l_debug("Got Num Ele %d", data[0]); + l_debug("Got alg %4.4x", l_get_be16(data + 1)); + l_debug("Got pub_type %d", data[3]); + l_debug("Got static_type %d", data[4]); + l_debug("Got output_size %d", data[5]); + l_debug("Got output_action %d", l_get_be16(data + 6)); + l_debug("Got input_size %d", data[8]); + l_debug("Got input_action %d", l_get_be16(data + 9)); + + if (!(l_get_be16(data + 1) & 0x0001)) { + l_error("Unsupported Algorithm"); + fail_code[1] = PROV_ERR_INVALID_FORMAT; + goto failure; + } + + /* If Public Key available Out of Band, use it */ + if (prov->conf_inputs.caps.pub_type) { + prov->conf_inputs.start.pub_key = 0x01; + prov->expected = PROV_CONFIRM; + /* Prompt Agent for remote Public Key */ + mesh_agent_request_public_key(prov->agent, + pub_key_cb, prov); + + /* Nothing else for us to do now */ + } else + prov->expected = PROV_PUB_KEY; + + /* Parse OOB Options, prefer static, then out, then in */ + if (prov->conf_inputs.caps.static_type) { + + prov->conf_inputs.start.auth_method = 0x01; + + } else if (prov->conf_inputs.caps.output_size && + prov->conf_inputs.caps.output_action) { + + prov->conf_inputs.start.auth_method = 0x02; + prov->conf_inputs.start.auth_action = + u16_high_bit(l_get_be16(data + 6)); + prov->conf_inputs.start.auth_size = + (data[5] > 8 ? 8 : data[5]); + + } else if (prov->conf_inputs.caps.input_size && + prov->conf_inputs.caps.input_action) { + + prov->conf_inputs.start.auth_method = 0x03; + prov->conf_inputs.start.auth_action = + u16_high_bit(l_get_be16(data + 9)); + prov->conf_inputs.start.auth_size = + (data[8] > 8 ? 8 : data[8]); + + } + + out = l_malloc(1 + sizeof(prov->conf_inputs.start)); + out[0] = PROV_START; + memcpy(out + 1, &prov->conf_inputs.start, + sizeof(prov->conf_inputs.start)); + + prov->state = INT_PROV_START_SENT; + prov->trans_tx(prov->trans_data, out, + sizeof(prov->conf_inputs.start) + 1); + l_free(out); + break; + + case PROV_PUB_KEY: /* Public Key */ + /* If we expected Pub Key Out-Of-Band, then fail */ + if (prov->conf_inputs.start.pub_key) { + fail_code[1] = PROV_ERR_INVALID_PDU; + goto failure; + } + + memcpy(prov->conf_inputs.dev_pub_key, data, 64); + prov->material |= MAT_REMOTE_PUBLIC; + prov->expected = PROV_CONFIRM; + + if ((prov->material & MAT_SECRET) != MAT_SECRET) + return; + + int_credentials(prov); + prov->state = INT_PROV_KEY_ACKED; + + l_debug("auth_method: %d", prov->conf_inputs.start.auth_method); + memset(prov->rand_auth_workspace + 16, 0, 32); + switch (prov->conf_inputs.start.auth_method) { + default: + case 0: + /* Auth Type 3c - No OOB */ + prov->material |= MAT_RAND_AUTH; + break; + case 1: + /* Auth Type 3c - Static OOB */ + /* Prompt Agent for Static OOB */ + fail_code[1] = mesh_agent_request_static(prov->agent, + static_cb, prov); + + if (fail_code[1]) + goto failure; + + break; + case 2: + /* Auth Type 3a - Output OOB */ + /* Prompt Agent for Output OOB */ + if (prov->conf_inputs.start.auth_action == + PROV_ACTION_OUT_ALPHA) { + fail_code[1] = mesh_agent_prompt_alpha( + prov->agent, + static_cb, prov); + } else { + fail_code[1] = mesh_agent_prompt_number( + prov->agent, true, + prov->conf_inputs.start.auth_action, + number_cb, prov); + } + + if (fail_code[1]) + goto failure; + + break; + + case 3: + /* Auth Type 3b - input OOB */ + l_getrandom(&oob_key, sizeof(oob_key)); + oob_key %= digit_mod(prov->conf_inputs.start.auth_size); + + /* Save two copies, for two confirmation values */ + l_put_be32(oob_key, prov->rand_auth_workspace + 28); + l_put_be32(oob_key, prov->rand_auth_workspace + 44); + prov->material |= MAT_RAND_AUTH; + /* Ask Agent to Display U32 */ + if (prov->conf_inputs.start.auth_action == + PROV_ACTION_IN_ALPHA) { + /* TODO: Construst NUL-term string to pass */ + fail_code[1] = mesh_agent_display_string( + prov->agent, NULL, NULL, prov); + } else { + fail_code[1] = mesh_agent_display_number( + prov->agent, false, + prov->conf_inputs.start.auth_action, + oob_key, NULL, prov); + } + + if (fail_code[1]) + goto failure; + + break; + + } + + if (prov->material & MAT_RAND_AUTH) + send_confirm(prov); + + break; + + case PROV_INP_CMPLT: /* Provisioning Input Complete */ + /* TODO: Cancel Agent prompt */ + send_confirm(prov); + break; + + case PROV_CONFIRM: /* Confirmation */ + prov->state = INT_PROV_CONF_ACKED; + /* RXed Device Confirmation */ + memcpy(prov->confirm, data, 16); + print_packet("ConfirmationDevice", prov->confirm, 16); + send_random(prov); + break; + + case PROV_RANDOM: /* Random */ + prov->state = INT_PROV_RAND_ACKED; + + /* RXed Device Confirmation */ + calc_local_material(data); + memcpy(prov->rand_auth_workspace + 16, data, 16); + print_packet("RandomDevice", data, 16); + + mesh_crypto_aes_cmac(prov->calc_key, + prov->rand_auth_workspace + 16, + 32, prov->rand_auth_workspace); + + print_packet("Dev-Conf", prov->rand_auth_workspace, 16); + if (memcmp(prov->rand_auth_workspace, prov->confirm, 16)) { + l_error("Provisioning Failed-Confirm compare"); + fail_code[1] = PROV_ERR_CONFIRM_FAILED; + goto failure; + } + + if (!prov->get_prov_data(prov->caller_data, + prov->conf_inputs.caps.num_ele)) { + l_error("Provisioning Failed-Data Get"); + fail_code[1] = PROV_ERR_CANT_ASSIGN_ADDR; + goto failure; + } + break; + + case PROV_COMPLETE: /* Complete */ + l_info("Provisioning Complete"); + prov->state = INT_PROV_IDLE; + int_prov_close(prov, PROV_ERR_SUCCESS); + break; + + case PROV_FAILED: /* Failed */ + l_error("Provisioning Failed (reason: %d)", data[0]); + prov->state = INT_PROV_IDLE; + int_prov_close(prov, data[0]); + break; + + default: + l_error("Unknown Pkt %2.2x", type); + fail_code[1] = PROV_ERR_UNEXPECTED_PDU; + goto failure; + } + + if (prov) + prov->previous = type; + + return; + +failure: + l_debug("Failing... %d", fail_code[1]); + fail_code[0] = PROV_FAILED; + prov->trans_tx(prov->trans_data, fail_code, 2); + int_prov_close(prov, fail_code[1]); +} + +static void int_prov_ack(void *user_data, uint8_t msg_num) +{ + struct mesh_prov_initiator *rx_prov = user_data; + + if (rx_prov != prov || !prov->trans_tx) + return; + + switch (prov->state) { + case INT_PROV_START_SENT: + prov->state = INT_PROV_START_ACKED; + if (prov->conf_inputs.caps.pub_type == 0) + send_pub_key(prov); + break; + + case INT_PROV_DATA_SENT: + prov->state = INT_PROV_DATA_ACKED; + break; + + case INT_PROV_IDLE: + case INT_PROV_INVITE_SENT: + case INT_PROV_INVITE_ACKED: + case INT_PROV_START_ACKED: + case INT_PROV_KEY_SENT: + case INT_PROV_KEY_ACKED: + case INT_PROV_CONF_SENT: + case INT_PROV_CONF_ACKED: + case INT_PROV_RAND_SENT: + case INT_PROV_RAND_ACKED: + case INT_PROV_DATA_ACKED: + default: + break; + } +} + +bool initiator_start(enum trans_type transport, + uint8_t uuid[16], + uint16_t max_ele, + uint16_t server, /* Only valid for PB-Remote */ + uint32_t timeout, /* in seconds from mesh.conf */ + struct mesh_agent *agent, + mesh_prov_initiator_data_req_func_t get_prov_data, + mesh_prov_initiator_complete_func_t complete_cb, + void *node, void *caller_data) +{ + bool result; + + /* Invoked from Add() method in mesh-api.txt, to add a + * remote unprovisioned device network. + */ + + if (prov) + return false; + + prov = l_new(struct mesh_prov_initiator, 1); + prov->to_secs = timeout; + prov->node = node; + prov->agent = agent; + prov->cmplt = complete_cb; + prov->get_prov_data = get_prov_data; + prov->caller_data = caller_data; + prov->previous = -1; + + /* Always register for PB-ADV */ + result = pb_adv_reg(true, int_prov_open, int_prov_close, int_prov_rx, + int_prov_ack, uuid, prov); + + if (result) + return true; + + initiator_free(); + return false; +} + +void initiator_cancel(void *user_data) +{ + initiator_free(); +} diff --git a/mesh/prov.h b/mesh/prov.h new file mode 100644 index 0000000..11b20b3 --- /dev/null +++ b/mesh/prov.h @@ -0,0 +1,217 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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. + * + */ + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +struct mesh_net; +struct mesh_dev; + +enum mesh_trans { + MESH_TRANS_IDLE, + MESH_TRANS_TX, + MESH_TRANS_RX, +}; + +enum mesh_bearer { + MESH_BEARER_IDLE, + MESH_BEARER_ADV, +}; + +enum mesh_prov_mode { + MESH_PROV_MODE_NONE, + MESH_PROV_MODE_INITIATOR, + MESH_PROV_MODE_GATT_ACCEPTOR, + MESH_PROV_MODE_ADV_ACCEPTOR, + MESH_PROV_MODE_GATT_CLIENT, + MESH_PROV_MODE_MESH_SERVER, + MESH_PROV_MODE_MESH_CLIENT, + MESH_PROV_MODE_MESH_GATT_CLIENT, +}; + +struct mesh_prov; + +typedef void (*prov_trans_tx_t)(void *trans_data, void *data, uint16_t len); +typedef void (*mesh_prov_open_func_t)(void *user_data, prov_trans_tx_t trans_tx, + void *trans_data, uint8_t trans_type); + +typedef void (*mesh_prov_close_func_t)(void *user_data, uint8_t reason); +typedef void (*mesh_prov_send_func_t)(bool success, struct mesh_prov *prov); +typedef void (*mesh_prov_ack_func_t)(void *user_data, uint8_t msg_num); +typedef void (*mesh_prov_receive_func_t)(void *user_data, const uint8_t *data, + uint16_t size); + + +struct prov_invite { + uint8_t attention; +} __packed; + +struct prov_invite_msg { + uint8_t opcode; + struct prov_invite invite; +} __packed; + +struct prov_start { + uint8_t algorithm; + uint8_t pub_key; + uint8_t auth_method; + uint8_t auth_action; + uint8_t auth_size; +} __packed; + +struct prov_caps_msg { + uint8_t opcode; + struct mesh_net_prov_caps caps; +} __packed; + +struct prov_start_msg { + uint8_t opcode; + struct prov_start start; +} __packed; + +struct prov_pub_key_msg { + uint8_t opcode; + uint8_t pub_key[64]; +} __packed; + +struct prov_conf_msg { + uint8_t opcode; + uint8_t conf[16]; +} __packed; + +struct prov_rand_msg { + uint8_t opcode; + uint8_t rand[16]; +} __packed; + +struct prov_data { + uint8_t net_key[16]; + uint16_t net_idx; + uint8_t flags; + uint32_t iv_index; + uint16_t primary; +} __packed; + +struct prov_data_msg { + uint8_t opcode; + struct prov_data data; + uint64_t mic; +} __packed; + +struct prov_fail_msg { + uint8_t opcode; + uint8_t reason; +} __packed; + +struct conf_input { + struct prov_invite invite; + struct mesh_net_prov_caps caps; + struct prov_start start; + uint8_t prv_pub_key[64]; + uint8_t dev_pub_key[64]; +} __packed; + +struct mesh_prov { + int ref_count; + struct mesh_dev *dev; + struct mesh_net *net; + enum mesh_prov_mode mode; + enum mesh_trans trans; + enum mesh_bearer bearer; + uint8_t uuid[16]; + uint8_t caps[12]; + + uint32_t conn_id; + uint16_t net_idx; + uint16_t remote; + uint16_t addr; + uint16_t expected_len; + uint16_t packet_len; + uint8_t local_msg_num; + uint8_t peer_msg_num; + uint8_t last_peer_msg_num; + uint8_t got_segs; + uint8_t expected_segs; + uint8_t expected_fcs; + uint8_t packet_buf[80]; + uint8_t peer_buf[80]; + struct timeval tx_start; + struct l_timeout *tx_timeout; + + /* Provisioning credentials and crypto material */ + struct conf_input conf_inputs; + uint8_t dev_key[16]; + uint8_t conf_salt[16]; + uint8_t s_key[16]; + uint8_t s_nonce[13]; + uint8_t conf_key[16]; + uint8_t conf[16]; + uint8_t r_conf[16]; + uint8_t rand_auth[32]; + uint8_t prov_salt[16]; + uint8_t secret[32]; + uint8_t r_public[64]; + uint8_t l_public[64]; + /* End Provisioning credentials and crypto material */ + + mesh_prov_open_func_t open_callback; + mesh_prov_close_func_t close_callback; + mesh_prov_receive_func_t receive_callback; + void *receive_data; + mesh_prov_send_func_t send_callback; + void *send_data; +}; + +struct mesh_prov *mesh_prov_new(struct mesh_net *net, uint16_t remote); + +struct mesh_prov *mesh_prov_ref(struct mesh_prov *prov); +void mesh_prov_unref(struct mesh_prov *prov); + +bool mesh_prov_gatt_client(struct mesh_prov *prov, struct mesh_dev *dev, + uint8_t uuid[16], + mesh_prov_open_func_t open_callback, + mesh_prov_close_func_t close_callback, + mesh_prov_receive_func_t recv_callback, + void *user_data); + +bool mesh_prov_listen(struct mesh_net *net, uint8_t uuid[16], uint8_t caps[12], + mesh_prov_open_func_t open_callback, + mesh_prov_close_func_t close_callback, + mesh_prov_receive_func_t recv_callback, + void *user_data); + +bool mesh_prov_connect(struct mesh_prov *prov, struct mesh_dev *dev, + uint16_t net_idx, uint8_t uuid[16], + mesh_prov_open_func_t open_callback, + mesh_prov_close_func_t close_callback, + mesh_prov_receive_func_t recv_callback, + void *user_data); + +unsigned int mesh_prov_send(struct mesh_prov *prov, + const void *data, uint16_t size, + mesh_prov_send_func_t send_callback, + void *user_data); +bool mesh_prov_cancel(struct mesh_prov *prov, unsigned int id); + +bool mesh_prov_close(struct mesh_prov *prov, uint8_t reason); +void mesh_prov_set_addr(struct mesh_prov *prov, uint16_t addr); +uint16_t mesh_prov_get_addr(struct mesh_prov *prov); +void mesh_prov_set_idx(struct mesh_prov *prov, uint16_t net_idx); +uint16_t mesh_prov_get_idx(struct mesh_prov *prov); diff --git a/mesh/provision.h b/mesh/provision.h new file mode 100644 index 0000000..755d848 --- /dev/null +++ b/mesh/provision.h @@ -0,0 +1,128 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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. + * + */ + + +/* + * size: hard define (mesh.conf - OOB_NUMBEROOB_NUMBER) + * oob size - 8 if alpha or numeric + * else 1 if mask is non zero + * else 0 + */ +struct bt_mesh; +struct mesh_prov; +struct mesh_agent; + +/* Provisioner Agent Response Types */ +#define OOB_CANCEL 0x00 +#define OOB_PRIV_KEY 0x01 +#define OOB_PUB_KEY 0x02 +#define OOB_NUMBER 0x03 +#define OOB_STATIC 0x04 +#define OOB_NUMBER_DISPLAY 0x05 + +/* Spec defined Provisioning message types */ +#define PROV_INVITE 0x00 +#define PROV_CAPS 0x01 +#define PROV_START 0x02 +#define PROV_PUB_KEY 0x03 +#define PROV_INP_CMPLT 0x04 +#define PROV_CONFIRM 0x05 +#define PROV_RANDOM 0x06 +#define PROV_DATA 0x07 +#define PROV_COMPLETE 0x08 +#define PROV_FAILED 0x09 + +/* Spec defined Error Codes */ +#define PROV_ERR_SUCCESS 0x00 +#define PROV_ERR_INVALID_PDU 0x01 +#define PROV_ERR_INVALID_FORMAT 0x02 +#define PROV_ERR_UNEXPECTED_PDU 0x03 +#define PROV_ERR_CONFIRM_FAILED 0x04 +#define PROV_ERR_INSUF_RESOURCE 0x05 +#define PROV_ERR_DECRYPT_FAILED 0x06 +#define PROV_ERR_UNEXPECTED_ERR 0x07 +#define PROV_ERR_CANT_ASSIGN_ADDR 0x08 +/* Internally generated Error Codes */ +#define PROV_ERR_TIMEOUT 0xFF + +/* Provisioner Action values */ +/* IN */ +#define PROV_ACTION_PUSH 0x00 +#define PROV_ACTION_TWIST 0x01 +#define PROV_ACTION_IN_NUMERIC 0x02 +#define PROV_ACTION_IN_ALPHA 0x03 +/* OUT */ +#define PROV_ACTION_BLINK 0x00 +#define PROV_ACTION_BEEP 0x01 +#define PROV_ACTION_VIBRATE 0x02 +#define PROV_ACTION_OUT_NUMERIC 0x03 +#define PROV_ACTION_OUT_ALPHA 0x04 + +/* OOB_Info defines from Table 3.54 of Mesh profile Specification v1.0 */ +#define OOB_INFO_URI_HASH 0x0002 + +/* PB_REMOTE not supported from unprovisioned state */ +enum trans_type { + PB_ADV = 0, + PB_GATT, +}; + +#define PROV_FLAG_KR 0x01 +#define PROV_FLAG_IVU 0x02 + +struct mesh_prov_node_info { + uint32_t iv_index; + uint16_t unicast; + uint16_t net_index; + uint8_t num_ele; + uint8_t net_key[16]; + uint8_t device_key[16]; + uint8_t flags; /* IVU and KR bits */ +}; + +typedef bool (*mesh_prov_acceptor_complete_func_t)(void *user_data, + uint8_t status, + struct mesh_prov_node_info *info); + +typedef bool (*mesh_prov_initiator_data_req_func_t)(void *user_data, + uint8_t num_elem); + +typedef bool (*mesh_prov_initiator_complete_func_t)(void *user_data, + uint8_t status, + struct mesh_prov_node_info *info); + +/* This starts unprovisioned device beacon */ +bool acceptor_start(uint8_t num_ele, uint8_t uuid[16], + uint16_t algorithms, uint32_t timeout, + struct mesh_agent *agent, + mesh_prov_acceptor_complete_func_t complete_cb, + void *caller_data); +void acceptor_cancel(void *user_data); + +bool initiator_start(enum trans_type transport, + uint8_t uuid[16], + uint16_t max_ele, + uint16_t server, /* Only valid for PB-Remote */ + uint32_t timeout, /* in seconds from mesh.conf */ + struct mesh_agent *agent, + mesh_prov_initiator_data_req_func_t get_prov_data, + mesh_prov_initiator_complete_func_t complete_cb, + void *node, void *caller_data); +void initiator_prov_data(uint16_t net_idx, uint16_t primary, void *caller_data); +void initiator_cancel(void *caller_data); diff --git a/mesh/util.c b/mesh/util.c new file mode 100644 index 0000000..986ba4b --- /dev/null +++ b/mesh/util.c @@ -0,0 +1,131 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#include "mesh/util.h" + +void print_packet(const char *label, const void *data, uint16_t size) +{ + struct timeval pkt_time; + + gettimeofday(&pkt_time, NULL); + + if (size > 0) { + char *str; + + str = l_util_hexstring(data, size); + l_debug("%05d.%03d %s: %s", + (uint32_t) pkt_time.tv_sec % 100000, + (uint32_t) pkt_time.tv_usec/1000, label, str); + l_free(str); + } else + l_debug("%05d.%03d %s: empty", + (uint32_t) pkt_time.tv_sec % 100000, + (uint32_t) pkt_time.tv_usec/1000, label); +} + +uint32_t get_timestamp_secs(void) +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec; +} + +bool str2hex(const char *str, uint16_t in_len, uint8_t *out, + uint16_t out_len) +{ + uint16_t i; + + if (in_len < out_len * 2) + return false; + + for (i = 0; i < out_len; i++) { + if (sscanf(&str[i * 2], "%02hhx", &out[i]) != 1) + return false; + } + + return true; +} + +size_t hex2str(uint8_t *in, size_t in_len, char *out, size_t out_len) +{ + static const char hexdigits[] = "0123456789abcdef"; + size_t i; + + if (in_len * 2 > (out_len - 1)) + return 0; + + for (i = 0; i < in_len; i++) { + out[i * 2] = hexdigits[in[i] >> 4]; + out[i * 2 + 1] = hexdigits[in[i] & 0xf]; + } + + out[in_len * 2] = '\0'; + return i; +} + +int create_dir(const char *dir_name) +{ + struct stat st; + char dir[PATH_MAX + 1], *prev, *next; + int err; + + err = stat(dir_name, &st); + if (!err && S_ISREG(st.st_mode)) + return 0; + + memset(dir, 0, PATH_MAX + 1); + strcat(dir, "/"); + + prev = strchr(dir_name, '/'); + + while (prev) { + next = strchr(prev + 1, '/'); + if (!next) + break; + + if (next - prev == 1) { + prev = next; + continue; + } + + strncat(dir, prev + 1, next - prev); + mkdir(dir, 0755); + + prev = next; + } + + mkdir(dir_name, 0755); + + return 0; +} diff --git a/mesh/util.h b/mesh/util.h new file mode 100644 index 0000000..d1e83b5 --- /dev/null +++ b/mesh/util.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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. + * + */ + +uint32_t get_timestamp_secs(void); +bool str2hex(const char *str, uint16_t in_len, uint8_t *out, + uint16_t out_len); +size_t hex2str(uint8_t *in, size_t in_len, char *out, size_t out_len); +void print_packet(const char *label, const void *data, uint16_t size); +int create_dir(const char *dir_name); diff --git a/missing b/missing new file mode 100755 index 0000000..625aeb1 --- /dev/null +++ b/missing @@ -0,0 +1,215 @@ +#! /bin/sh +# Common wrapper for a few potentially missing GNU programs. + +scriptversion=2018-03-07.03; # UTC + +# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Originally written by Fran,cois Pinard , 1996. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +if test $# -eq 0; then + echo 1>&2 "Try '$0 --help' for more information" + exit 1 +fi + +case $1 in + + --is-lightweight) + # Used by our autoconf macros to check whether the available missing + # script is modern enough. + exit 0 + ;; + + --run) + # Back-compat with the calling convention used by older automake. + shift + ;; + + -h|--h|--he|--hel|--help) + echo "\ +$0 [OPTION]... PROGRAM [ARGUMENT]... + +Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due +to PROGRAM being missing or too old. + +Options: + -h, --help display this help and exit + -v, --version output version information and exit + +Supported PROGRAM values: + aclocal autoconf autoheader autom4te automake makeinfo + bison yacc flex lex help2man + +Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and +'g' are ignored when checking the name. + +Send bug reports to ." + exit $? + ;; + + -v|--v|--ve|--ver|--vers|--versi|--versio|--version) + echo "missing $scriptversion (GNU Automake)" + exit $? + ;; + + -*) + echo 1>&2 "$0: unknown '$1' option" + echo 1>&2 "Try '$0 --help' for more information" + exit 1 + ;; + +esac + +# Run the given program, remember its exit status. +"$@"; st=$? + +# If it succeeded, we are done. +test $st -eq 0 && exit 0 + +# Also exit now if we it failed (or wasn't found), and '--version' was +# passed; such an option is passed most likely to detect whether the +# program is present and works. +case $2 in --version|--help) exit $st;; esac + +# Exit code 63 means version mismatch. This often happens when the user +# tries to use an ancient version of a tool on a file that requires a +# minimum version. +if test $st -eq 63; then + msg="probably too old" +elif test $st -eq 127; then + # Program was missing. + msg="missing on your system" +else + # Program was found and executed, but failed. Give up. + exit $st +fi + +perl_URL=https://www.perl.org/ +flex_URL=https://github.com/westes/flex +gnu_software_URL=https://www.gnu.org/software + +program_details () +{ + case $1 in + aclocal|automake) + echo "The '$1' program is part of the GNU Automake package:" + echo "<$gnu_software_URL/automake>" + echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" + echo "<$gnu_software_URL/autoconf>" + echo "<$gnu_software_URL/m4/>" + echo "<$perl_URL>" + ;; + autoconf|autom4te|autoheader) + echo "The '$1' program is part of the GNU Autoconf package:" + echo "<$gnu_software_URL/autoconf/>" + echo "It also requires GNU m4 and Perl in order to run:" + echo "<$gnu_software_URL/m4/>" + echo "<$perl_URL>" + ;; + esac +} + +give_advice () +{ + # Normalize program name to check for. + normalized_program=`echo "$1" | sed ' + s/^gnu-//; t + s/^gnu//; t + s/^g//; t'` + + printf '%s\n' "'$1' is $msg." + + configure_deps="'configure.ac' or m4 files included by 'configure.ac'" + case $normalized_program in + autoconf*) + echo "You should only need it if you modified 'configure.ac'," + echo "or m4 files included by it." + program_details 'autoconf' + ;; + autoheader*) + echo "You should only need it if you modified 'acconfig.h' or" + echo "$configure_deps." + program_details 'autoheader' + ;; + automake*) + echo "You should only need it if you modified 'Makefile.am' or" + echo "$configure_deps." + program_details 'automake' + ;; + aclocal*) + echo "You should only need it if you modified 'acinclude.m4' or" + echo "$configure_deps." + program_details 'aclocal' + ;; + autom4te*) + echo "You might have modified some maintainer files that require" + echo "the 'autom4te' program to be rebuilt." + program_details 'autom4te' + ;; + bison*|yacc*) + echo "You should only need it if you modified a '.y' file." + echo "You may want to install the GNU Bison package:" + echo "<$gnu_software_URL/bison/>" + ;; + lex*|flex*) + echo "You should only need it if you modified a '.l' file." + echo "You may want to install the Fast Lexical Analyzer package:" + echo "<$flex_URL>" + ;; + help2man*) + echo "You should only need it if you modified a dependency" \ + "of a man page." + echo "You may want to install the GNU Help2man package:" + echo "<$gnu_software_URL/help2man/>" + ;; + makeinfo*) + echo "You should only need it if you modified a '.texi' file, or" + echo "any other file indirectly affecting the aspect of the manual." + echo "You might want to install the Texinfo package:" + echo "<$gnu_software_URL/texinfo/>" + echo "The spurious makeinfo call might also be the consequence of" + echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" + echo "want to install GNU make:" + echo "<$gnu_software_URL/make/>" + ;; + *) + echo "You might have modified some files without having the proper" + echo "tools for further handling them. Check the 'README' file, it" + echo "often tells you about the needed prerequisites for installing" + echo "this package. You may also peek at any GNU archive site, in" + echo "case some other package contains this missing '$1' program." + ;; + esac +} + +give_advice "$1" | sed -e '1s/^/WARNING: /' \ + -e '2,$s/^/ /' >&2 + +# Propagate the correct exit status (expected to be 127 for a program +# not found, 63 for a program that failed due to version mismatch). +exit $st + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/monitor/a2dp.c b/monitor/a2dp.c new file mode 100644 index 0000000..4a1d0e1 --- /dev/null +++ b/monitor/a2dp.c @@ -0,0 +1,914 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Andrzej Kaczmarek + * Copyright (C) 2018 Pali Rohár + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include "lib/bluetooth.h" + +#include "src/shared/util.h" +#include "bt.h" +#include "packet.h" +#include "display.h" +#include "l2cap.h" +#include "a2dp.h" + +#define BASE_INDENT 4 + +/* Codec Types */ +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG12 0x01 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_ATRAC 0x04 +#define A2DP_CODEC_VENDOR 0xff + +/* Vendor Specific A2DP Codecs */ +#define APTX_VENDOR_ID 0x0000004f +#define APTX_CODEC_ID 0x0001 +#define FASTSTREAM_VENDOR_ID 0x0000000a +#define FASTSTREAM_CODEC_ID 0x0001 +#define APTX_LL_VENDOR_ID 0x0000000a +#define APTX_LL_CODEC_ID 0x0002 +#define APTX_HD_VENDOR_ID 0x000000D7 +#define APTX_HD_CODEC_ID 0x0024 +#define LDAC_VENDOR_ID 0x0000012d +#define LDAC_CODEC_ID 0x00aa + +struct bit_desc { + uint8_t bit_num; + const char *str; +}; + +static const struct bit_desc sbc_frequency_table[] = { + { 7, "16000" }, + { 6, "32000" }, + { 5, "44100" }, + { 4, "48000" }, + { } +}; + +static const struct bit_desc sbc_channel_mode_table[] = { + { 3, "Mono" }, + { 2, "Dual Channel" }, + { 1, "Stereo" }, + { 0, "Joint Stereo" }, + { } +}; + +static const struct bit_desc sbc_blocklen_table[] = { + { 7, "4" }, + { 6, "8" }, + { 5, "12" }, + { 4, "16" }, + { } +}; + +static const struct bit_desc sbc_subbands_table[] = { + { 3, "4" }, + { 2, "8" }, + { } +}; + +static const struct bit_desc sbc_allocation_table[] = { + { 1, "SNR" }, + { 0, "Loudness" }, + { } +}; + +static const struct bit_desc mpeg12_layer_table[] = { + { 7, "Layer I (mp1)" }, + { 6, "Layer II (mp2)" }, + { 5, "Layer III (mp3)" }, + { } +}; + +static const struct bit_desc mpeg12_channel_mode_table[] = { + { 3, "Mono" }, + { 2, "Dual Channel" }, + { 1, "Stereo" }, + { 0, "Joint Stereo" }, + { } +}; + +static const struct bit_desc mpeg12_frequency_table[] = { + { 5, "16000" }, + { 4, "22050" }, + { 3, "24000" }, + { 2, "32000" }, + { 1, "44100" }, + { 0, "48000" }, + { } +}; + +static const struct bit_desc mpeg12_bitrate_table[] = { + { 14, "1110" }, + { 13, "1101" }, + { 12, "1100" }, + { 11, "1011" }, + { 10, "1010" }, + { 9, "1001" }, + { 8, "1000" }, + { 7, "0111" }, + { 6, "0110" }, + { 5, "0101" }, + { 4, "0100" }, + { 3, "0011" }, + { 2, "0010" }, + { 1, "0001" }, + { 0, "0000" }, + { } +}; + +static const struct bit_desc aac_object_type_table[] = { + { 7, "MPEG-2 AAC LC" }, + { 6, "MPEG-4 AAC LC" }, + { 5, "MPEG-4 AAC LTP" }, + { 4, "MPEG-4 AAC scalable" }, + { 3, "RFA (b3)" }, + { 2, "RFA (b2)" }, + { 1, "RFA (b1)" }, + { 0, "RFA (b0)" }, + { } +}; + +static const struct bit_desc aac_frequency_table[] = { + { 15, "8000" }, + { 14, "11025" }, + { 13, "12000" }, + { 12, "16000" }, + { 11, "22050" }, + { 10, "24000" }, + { 9, "32000" }, + { 8, "44100" }, + { 7, "48000" }, + { 6, "64000" }, + { 5, "88200" }, + { 4, "96000" }, + { } +}; + +static const struct bit_desc aac_channels_table[] = { + { 3, "1" }, + { 2, "2" }, + { } +}; + +static const struct bit_desc aptx_frequency_table[] = { + { 7, "16000" }, + { 6, "32000" }, + { 5, "44100" }, + { 4, "48000" }, + { } +}; + +static const struct bit_desc aptx_channel_mode_table[] = { + { 0, "Mono" }, + { 1, "Stereo" }, + { } +}; + +static const struct bit_desc faststream_direction_table[] = { + { 0, "Sink" }, + { 1, "Source" }, + { } +}; + +static const struct bit_desc faststream_sink_frequency_table[] = { + /* in config buffer, there may be more frequency bits + * and 48kHz takes precedence over 41kHz + */ + { 0, "48000" }, + { 1, "44100" }, + { } +}; + +static const struct bit_desc faststream_source_frequency_table[] = { + { 5, "16000" }, + { } +}; + +static void print_value_bits(uint8_t indent, uint32_t value, + const struct bit_desc *table) +{ + int i; + + for (i = 0; table[i].str; i++) { + if (value & (1 << table[i].bit_num)) + print_field("%*c%s", indent + 2, ' ', table[i].str); + } +} + +static const char *find_value_bit(uint32_t value, + const struct bit_desc *table) +{ + int i; + + for (i = 0; table[i].str; i++) { + if (value & (1 << table[i].bit_num)) + return table[i].str; + } + + return "Unknown"; +} + +struct vndcodec { + uint32_t vendor_id; + uint16_t codec_id; + char *codec_name; + bool (*codec_vendor_cap)(uint8_t losc, struct l2cap_frame *frame); + bool (*codec_vendor_cfg)(uint8_t losc, struct l2cap_frame *frame); +}; + +static bool codec_vendor_aptx_cap(uint8_t losc, struct l2cap_frame *frame); +static bool codec_vendor_aptx_cfg(uint8_t losc, struct l2cap_frame *frame); +static bool codec_vendor_faststream_cap(uint8_t losc, + struct l2cap_frame *frame); +static bool codec_vendor_faststream_cfg(uint8_t losc, + struct l2cap_frame *frame); +static bool codec_vendor_aptx_ll_cap(uint8_t losc, struct l2cap_frame *frame); +static bool codec_vendor_aptx_ll_cfg(uint8_t losc, struct l2cap_frame *frame); +static bool codec_vendor_aptx_hd_cap(uint8_t losc, struct l2cap_frame *frame); +static bool codec_vendor_aptx_hd_cfg(uint8_t losc, struct l2cap_frame *frame); +static bool codec_vendor_ldac(uint8_t losc, struct l2cap_frame *frame); + +static const struct vndcodec vndcodecs[] = { + { APTX_VENDOR_ID, APTX_CODEC_ID, "aptX", + codec_vendor_aptx_cap, codec_vendor_aptx_cfg }, + { FASTSTREAM_VENDOR_ID, FASTSTREAM_CODEC_ID, "FastStream", + codec_vendor_faststream_cap, codec_vendor_faststream_cfg }, + { APTX_LL_VENDOR_ID, APTX_LL_CODEC_ID, "aptX Low Latency", + codec_vendor_aptx_ll_cap, codec_vendor_aptx_ll_cfg }, + { APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID, "aptX HD", + codec_vendor_aptx_hd_cap, codec_vendor_aptx_hd_cfg }, + { LDAC_VENDOR_ID, LDAC_CODEC_ID, "LDAC", + codec_vendor_ldac, codec_vendor_ldac }, + { } +}; + +static const char *vndcodec2str(uint32_t vendor_id, uint16_t codec_id) +{ + size_t i; + + for (i = 0; i < sizeof(vndcodecs)/sizeof(*vndcodecs); i++) { + if (vndcodecs[i].vendor_id == vendor_id && + vndcodecs[i].codec_id == codec_id) + return vndcodecs[i].codec_name; + } + + return "Unknown"; +} + +static bool codec_sbc_cap(uint8_t losc, struct l2cap_frame *frame) +{ + uint8_t cap = 0; + + if (losc != 4) + return false; + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cFrequency: 0x%02x", BASE_INDENT, ' ', cap & 0xf0); + print_value_bits(BASE_INDENT, cap & 0xf0, sbc_frequency_table); + + print_field("%*cChannel Mode: 0x%02x", BASE_INDENT, ' ', cap & 0x0f); + print_value_bits(BASE_INDENT, cap & 0x0f, sbc_channel_mode_table); + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cBlock Length: 0x%02x", BASE_INDENT, ' ', cap & 0xf0); + print_value_bits(BASE_INDENT, cap & 0xf0, sbc_blocklen_table); + + print_field("%*cSubbands: 0x%02x", BASE_INDENT, ' ', cap & 0x0c); + print_value_bits(BASE_INDENT, cap & 0x0c, sbc_subbands_table); + + print_field("%*cAllocation Method: 0x%02x", BASE_INDENT, ' ', + cap & 0x03); + print_value_bits(BASE_INDENT, cap & 0x03, sbc_allocation_table); + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cMinimum Bitpool: %d", BASE_INDENT, ' ', cap); + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cMaximum Bitpool: %d", BASE_INDENT, ' ', cap); + + return true; +} + +static bool codec_sbc_cfg(uint8_t losc, struct l2cap_frame *frame) +{ + uint8_t cap = 0; + + if (losc != 4) + return false; + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(cap & 0xf0, sbc_frequency_table), + cap & 0xf0); + + print_field("%*cChannel Mode: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(cap & 0x0f, sbc_channel_mode_table), + cap & 0x0f); + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cBlock Length: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(cap & 0xf0, sbc_blocklen_table), + cap & 0xf0); + + print_field("%*cSubbands: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(cap & 0x0c, sbc_subbands_table), + cap & 0x0c); + + print_field("%*cAllocation Method: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(cap & 0x03, sbc_allocation_table), + cap & 0x03); + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cMinimum Bitpool: %d", BASE_INDENT, ' ', cap); + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cMaximum Bitpool: %d", BASE_INDENT, ' ', cap); + + return true; +} + +static bool codec_mpeg12_cap(uint8_t losc, struct l2cap_frame *frame) +{ + uint16_t cap = 0; + uint8_t layer; + uint8_t chan; + uint8_t freq; + uint16_t bitrate; + bool crc, mpf, vbr; + + if (losc != 4) + return false; + + l2cap_frame_get_be16(frame, &cap); + + layer = (cap >> 8) & 0xe0; + crc = cap & 0x1000; + chan = (cap >> 8) & 0x0f; + mpf = cap & 0x0040; + freq = cap & 0x003f; + + l2cap_frame_get_be16(frame, &cap); + + vbr = cap & 0x8000; + bitrate = cap & 0x7fff; + + print_field("%*cLayer: 0x%02x", BASE_INDENT, ' ', layer); + print_value_bits(BASE_INDENT, layer, mpeg12_layer_table); + + print_field("%*cCRC: %s", BASE_INDENT, ' ', crc ? "Yes" : "No"); + + print_field("%*cChannel Mode: 0x%02x", BASE_INDENT, ' ', chan); + print_value_bits(BASE_INDENT, chan, mpeg12_channel_mode_table); + + print_field("%*cMedia Payload Format: %s", BASE_INDENT, ' ', + mpf ? "RFC-2250 RFC-3119" : "RFC-2250"); + + print_field("%*cFrequency: 0x%02x", BASE_INDENT, ' ', freq); + print_value_bits(BASE_INDENT, freq, mpeg12_frequency_table); + + if (!vbr) { + print_field("%*cBitrate Index: 0x%04x", BASE_INDENT, ' ', + bitrate); + print_value_bits(BASE_INDENT, freq, mpeg12_bitrate_table); + } + + print_field("%*cVBR: %s", BASE_INDENT, ' ', vbr ? "Yes" : "No"); + + return true; +} + +static bool codec_mpeg12_cfg(uint8_t losc, struct l2cap_frame *frame) +{ + uint16_t cap = 0; + uint8_t layer; + uint8_t chan; + uint8_t freq; + uint16_t bitrate; + bool crc, mpf, vbr; + + if (losc != 4) + return false; + + l2cap_frame_get_be16(frame, &cap); + + layer = (cap >> 8) & 0xe0; + crc = cap & 0x1000; + chan = (cap >> 8) & 0x0f; + mpf = cap & 0x0040; + freq = cap & 0x003f; + + l2cap_frame_get_be16(frame, &cap); + + vbr = cap & 0x8000; + bitrate = cap & 0x7fff; + + print_field("%*cLayer: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(layer, mpeg12_layer_table), + layer); + + print_field("%*cCRC: %s", BASE_INDENT, ' ', crc ? "Yes" : "No"); + + print_field("%*cChannel Mode: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(chan, mpeg12_channel_mode_table), + chan); + + print_field("%*cMedia Payload Format: %s", BASE_INDENT, ' ', + mpf ? "RFC-2250 RFC-3119" : "RFC-2250"); + + print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(freq, mpeg12_frequency_table), + freq); + + if (!vbr) + print_field("%*cBitrate Index: %s (0x%04x)", BASE_INDENT, ' ', + find_value_bit(freq, mpeg12_bitrate_table), + bitrate); + + print_field("%*cVBR: %s", BASE_INDENT, ' ', vbr ? "Yes" : "No"); + + return true; +} + +static bool codec_aac_cap(uint8_t losc, struct l2cap_frame *frame) +{ + uint16_t cap = 0; + uint8_t type; + uint16_t freq; + uint8_t chan; + uint32_t bitrate; + bool vbr; + + if (losc != 6) + return false; + + l2cap_frame_get_be16(frame, &cap); + + type = cap >> 8; + freq = cap << 8; + + l2cap_frame_get_be16(frame, &cap); + + freq |= (cap >> 8) & 0xf0; + chan = (cap >> 8) & 0x0c; + bitrate = (cap << 16) & 0x7f0000; + vbr = cap & 0x0080; + + l2cap_frame_get_be16(frame, &cap); + + bitrate |= cap; + + print_field("%*cObject Type: 0x%02x", BASE_INDENT, ' ', type); + print_value_bits(BASE_INDENT, type, aac_object_type_table); + + print_field("%*cFrequency: 0x%02x", BASE_INDENT, ' ', freq); + print_value_bits(BASE_INDENT, freq, aac_frequency_table); + + print_field("%*cChannels: 0x%02x", BASE_INDENT, ' ', chan); + print_value_bits(BASE_INDENT, chan, aac_channels_table); + + print_field("%*cBitrate: %ubps", BASE_INDENT, ' ', bitrate); + print_field("%*cVBR: %s", BASE_INDENT, ' ', vbr ? "Yes" : "No"); + + return true; +} + +static bool codec_aac_cfg(uint8_t losc, struct l2cap_frame *frame) +{ + uint16_t cap = 0; + uint8_t type; + uint16_t freq; + uint8_t chan; + uint32_t bitrate; + bool vbr; + + if (losc != 6) + return false; + + l2cap_frame_get_be16(frame, &cap); + + type = cap >> 8; + freq = cap << 8; + + l2cap_frame_get_be16(frame, &cap); + + freq |= (cap >> 8) & 0xf0; + chan = (cap >> 8) & 0x0c; + bitrate = (cap << 16) & 0x7f0000; + vbr = cap & 0x0080; + + l2cap_frame_get_be16(frame, &cap); + + bitrate |= cap; + + print_field("%*cObject Type: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(type, aac_object_type_table), type); + + print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(freq, aac_frequency_table), freq); + + print_field("%*cChannels: %s (0x%02x)", BASE_INDENT, ' ', + find_value_bit(chan, aac_channels_table), chan); + + print_field("%*cBitrate: %ubps", BASE_INDENT, ' ', bitrate); + print_field("%*cVBR: %s", BASE_INDENT, ' ', vbr ? "Yes" : "No"); + + return true; +} + +static bool codec_vendor_aptx_cap(uint8_t losc, struct l2cap_frame *frame) +{ + uint8_t cap = 0; + + if (losc != 1) + return false; + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cFrequency: 0x%02x", BASE_INDENT + 2, ' ', cap & 0xf0); + print_value_bits(BASE_INDENT + 2, cap & 0xf0, aptx_frequency_table); + + print_field("%*cChannel Mode: 0x%02x", BASE_INDENT + 2, ' ', + cap & 0x0f); + print_value_bits(BASE_INDENT + 2, cap & 0x0f, aptx_channel_mode_table); + + return true; +} + +static bool codec_vendor_faststream_cap(uint8_t losc, struct l2cap_frame *frame) +{ + uint8_t cap = 0; + + if (losc != 2) + return false; + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cDirection: 0x%02x", BASE_INDENT + 2, ' ', cap); + print_value_bits(BASE_INDENT + 2, cap, faststream_direction_table); + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cSink Frequency: 0x%02x", BASE_INDENT + 2, ' ', + cap & 0x0f); + print_value_bits(BASE_INDENT + 2, cap & 0x0f, + faststream_sink_frequency_table); + + print_field("%*cSource Frequency: 0x%02x", BASE_INDENT + 2, ' ', + cap & 0xf0); + print_value_bits(BASE_INDENT + 2, cap & 0xf0, + faststream_source_frequency_table); + + return true; +} + +static bool codec_vendor_aptx_ll_cap(uint8_t losc, struct l2cap_frame *frame) +{ + uint8_t cap = 0; + uint16_t level = 0; + + if (losc != 2 && losc != 11) + return false; + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cFrequency: 0x%02x", BASE_INDENT + 2, ' ', cap & 0xf0); + print_value_bits(BASE_INDENT + 2, cap & 0xf0, aptx_frequency_table); + + print_field("%*cChannel Mode: 0x%02x", BASE_INDENT + 2, ' ', + cap & 0x0f); + print_value_bits(BASE_INDENT + 2, cap & 0x0f, aptx_channel_mode_table); + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cBidirectional link: %s", BASE_INDENT, ' ', + (cap & 1) ? "Yes" : "No"); + + if ((cap & 2) && losc == 11) { + /* reserved */ + l2cap_frame_get_u8(frame, &cap); + + l2cap_frame_get_le16(frame, &level); + print_field("%*cTarget codec buffer level: %u (0x%02x)", + BASE_INDENT + 2, ' ', level, level); + + l2cap_frame_get_le16(frame, &level); + print_field("%*cInitial codec buffer level: %u (0x%02x)", + BASE_INDENT + 2, ' ', level, level); + + l2cap_frame_get_u8(frame, &cap); + print_field("%*cSRA max rate: %g (0x%02x)", + BASE_INDENT + 2, ' ', cap / 10000.0, cap); + + l2cap_frame_get_u8(frame, &cap); + print_field("%*cSRA averaging time: %us (0x%02x)", + BASE_INDENT + 2, ' ', cap, cap); + + l2cap_frame_get_le16(frame, &level); + print_field("%*cGood working codec buffer level: %u (0x%02x)", + BASE_INDENT + 2, ' ', level, level); + } + + return true; +} + +static bool codec_vendor_aptx_hd_cap(uint8_t losc, struct l2cap_frame *frame) +{ + uint8_t cap = 0; + + if (losc != 5) + return false; + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cFrequency: 0x%02x", BASE_INDENT + 2, ' ', cap & 0xf0); + print_value_bits(BASE_INDENT + 2, cap & 0xf0, aptx_frequency_table); + + print_field("%*cChannel Mode: 0x%02x", BASE_INDENT + 2, ' ', + cap & 0x0f); + print_value_bits(BASE_INDENT + 2, cap & 0x0f, aptx_channel_mode_table); + + /* reserved */ + l2cap_frame_get_u8(frame, &cap); + l2cap_frame_get_u8(frame, &cap); + l2cap_frame_get_u8(frame, &cap); + l2cap_frame_get_u8(frame, &cap); + + return true; +} + +static bool codec_vendor_ldac(uint8_t losc, struct l2cap_frame *frame) +{ + uint16_t cap = 0; + + if (losc != 2) + return false; + + l2cap_frame_get_le16(frame, &cap); + + print_field("%*cUnknown: 0x%04x", BASE_INDENT + 2, ' ', cap); + + return true; +} + +static bool codec_vendor_cap(uint8_t losc, struct l2cap_frame *frame) +{ + uint32_t vendor_id = 0; + uint16_t codec_id = 0; + size_t i; + + if (losc < 6) + return false; + + l2cap_frame_get_le32(frame, &vendor_id); + l2cap_frame_get_le16(frame, &codec_id); + + losc -= 6; + + print_field("%*cVendor ID: %s (0x%08x)", BASE_INDENT, ' ', + bt_compidtostr(vendor_id), vendor_id); + + print_field("%*cVendor Specific Codec ID: %s (0x%04x)", BASE_INDENT, + ' ', vndcodec2str(vendor_id, codec_id), codec_id); + + for (i = 0; i < sizeof(vndcodecs)/sizeof(*vndcodecs); i++) { + if (vndcodecs[i].vendor_id == vendor_id && + vndcodecs[i].codec_id == codec_id) + return vndcodecs[i].codec_vendor_cap(losc, frame); + } + + packet_hexdump(frame->data, losc); + l2cap_frame_pull(frame, frame, losc); + + return true; +} + +static bool codec_vendor_aptx_cfg(uint8_t losc, struct l2cap_frame *frame) +{ + uint8_t cap = 0; + + if (losc != 1) + return false; + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT + 2, ' ', + find_value_bit(cap & 0xf0, aptx_frequency_table), + cap & 0xf0); + + print_field("%*cChannel Mode: %s (0x%02x)", BASE_INDENT + 2, ' ', + find_value_bit(cap & 0x0f, aptx_channel_mode_table), + cap & 0x0f); + + return true; +} + +static bool codec_vendor_faststream_cfg(uint8_t losc, struct l2cap_frame *frame) +{ + uint8_t cap = 0; + + if (losc != 2) + return false; + + l2cap_frame_get_u8(frame, &cap); + + /* FastStream codec is bi-directional */ + print_field("%*cDirection: 0x%02x", BASE_INDENT + 2, ' ', cap); + print_value_bits(BASE_INDENT + 2, cap, faststream_direction_table); + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cSink Frequency: %s (0x%02x)", BASE_INDENT + 2, ' ', + find_value_bit(cap & 0x0f, + faststream_sink_frequency_table), + cap & 0x0f); + + print_field("%*cSource Frequency: %s (0x%02x)", BASE_INDENT + 2, ' ', + find_value_bit(cap & 0xf0, + faststream_source_frequency_table), + cap & 0xf0); + + return true; +} + +static bool codec_vendor_aptx_ll_cfg(uint8_t losc, struct l2cap_frame *frame) +{ + uint8_t cap = 0; + uint16_t level = 0; + + if (losc != 2 && losc != 11) + return false; + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT + 2, ' ', + find_value_bit(cap & 0xf0, aptx_frequency_table), + cap & 0xf0); + + print_field("%*cChannel Mode: %s (0x%02x)", BASE_INDENT + 2, ' ', + find_value_bit(cap & 0x0f, aptx_channel_mode_table), + cap & 0x0f); + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cBidirectional link: %s", BASE_INDENT, ' ', + (cap & 1) ? "Yes" : "No"); + + if ((cap & 2) && losc == 11) { + /* reserved */ + l2cap_frame_get_u8(frame, &cap); + + l2cap_frame_get_le16(frame, &level); + print_field("%*cTarget codec buffer level: %u (0x%02x)", + BASE_INDENT + 2, ' ', level, level); + + l2cap_frame_get_le16(frame, &level); + print_field("%*cInitial codec buffer level: %u (0x%02x)", + BASE_INDENT + 2, ' ', level, level); + + l2cap_frame_get_u8(frame, &cap); + print_field("%*cSRA max rate: %g (0x%02x)", + BASE_INDENT + 2, ' ', cap / 10000.0, cap); + + l2cap_frame_get_u8(frame, &cap); + print_field("%*cSRA averaging time: %us (0x%02x)", + BASE_INDENT + 2, ' ', cap, cap); + + l2cap_frame_get_le16(frame, &level); + print_field("%*cGood working codec buffer level: %u (0x%02x)", + BASE_INDENT + 2, ' ', level, level); + } + + return true; +} + +static bool codec_vendor_aptx_hd_cfg(uint8_t losc, struct l2cap_frame *frame) +{ + uint8_t cap = 0; + + if (losc != 5) + return false; + + l2cap_frame_get_u8(frame, &cap); + + print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT + 2, ' ', + find_value_bit(cap & 0xf0, aptx_frequency_table), + cap & 0xf0); + + print_field("%*cChannel Mode: %s (0x%02x)", BASE_INDENT + 2, ' ', + find_value_bit(cap & 0x0f, aptx_channel_mode_table), + cap & 0x0f); + + /* reserved */ + l2cap_frame_get_u8(frame, &cap); + l2cap_frame_get_u8(frame, &cap); + l2cap_frame_get_u8(frame, &cap); + l2cap_frame_get_u8(frame, &cap); + + return true; +} + +static bool codec_vendor_cfg(uint8_t losc, struct l2cap_frame *frame) +{ + uint32_t vendor_id = 0; + uint16_t codec_id = 0; + size_t i; + + if (losc < 6) + return false; + + l2cap_frame_get_le32(frame, &vendor_id); + l2cap_frame_get_le16(frame, &codec_id); + + losc -= 6; + + print_field("%*cVendor ID: %s (0x%08x)", BASE_INDENT, ' ', + bt_compidtostr(vendor_id), vendor_id); + + print_field("%*cVendor Specific Codec ID: %s (0x%04x)", BASE_INDENT, + ' ', vndcodec2str(vendor_id, codec_id), codec_id); + + for (i = 0; i < sizeof(vndcodecs)/sizeof(*vndcodecs); i++) { + if (vndcodecs[i].vendor_id == vendor_id && + vndcodecs[i].codec_id == codec_id) + return vndcodecs[i].codec_vendor_cfg(losc, frame); + } + + packet_hexdump(frame->data, losc); + l2cap_frame_pull(frame, frame, losc); + + return true; +} + +bool a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame *frame) +{ + switch (codec) { + case A2DP_CODEC_SBC: + return codec_sbc_cap(losc, frame); + case A2DP_CODEC_MPEG12: + return codec_mpeg12_cap(losc, frame); + case A2DP_CODEC_MPEG24: + return codec_aac_cap(losc, frame); + case A2DP_CODEC_VENDOR: + return codec_vendor_cap(losc, frame); + default: + packet_hexdump(frame->data, losc); + l2cap_frame_pull(frame, frame, losc); + return true; + } +} + +bool a2dp_codec_cfg(uint8_t codec, uint8_t losc, struct l2cap_frame *frame) +{ + switch (codec) { + case A2DP_CODEC_SBC: + return codec_sbc_cfg(losc, frame); + case A2DP_CODEC_MPEG12: + return codec_mpeg12_cfg(losc, frame); + case A2DP_CODEC_MPEG24: + return codec_aac_cfg(losc, frame); + case A2DP_CODEC_VENDOR: + return codec_vendor_cfg(losc, frame); + default: + packet_hexdump(frame->data, losc); + l2cap_frame_pull(frame, frame, losc); + return true; + } +} diff --git a/monitor/a2dp.h b/monitor/a2dp.h new file mode 100644 index 0000000..72a8f1f --- /dev/null +++ b/monitor/a2dp.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Andrzej Kaczmarek + * + * + * 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 a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame *frame); + +bool a2dp_codec_cfg(uint8_t codec, uint8_t losc, struct l2cap_frame *frame); diff --git a/monitor/analyze.c b/monitor/analyze.c new file mode 100644 index 0000000..4dc2891 --- /dev/null +++ b/monitor/analyze.c @@ -0,0 +1,411 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "lib/bluetooth.h" + +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/btsnoop.h" +#include "monitor/bt.h" +#include "analyze.h" + +struct hci_dev { + uint16_t index; + uint8_t type; + uint8_t bdaddr[6]; + struct timeval time_added; + struct timeval time_removed; + unsigned long num_cmd; + unsigned long num_evt; + unsigned long num_acl; + unsigned long num_sco; + unsigned long vendor_diag; + unsigned long system_note; + unsigned long user_log; + unsigned long unknown; + uint16_t manufacturer; +}; + +static struct queue *dev_list; + +static void dev_destroy(void *data) +{ + struct hci_dev *dev = data; + const char *str; + + switch (dev->type) { + case 0x00: + str = "BR/EDR"; + break; + case 0x01: + str = "AMP"; + break; + default: + str = "unknown"; + break; + } + + printf("Found %s controller with index %u\n", str, dev->index); + printf(" BD_ADDR %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + dev->bdaddr[5], dev->bdaddr[4], dev->bdaddr[3], + dev->bdaddr[2], dev->bdaddr[1], dev->bdaddr[0]); + if (dev->manufacturer != 0xffff) + printf(" (%s)", bt_compidtostr(dev->manufacturer)); + printf("\n"); + + + printf(" %lu commands\n", dev->num_cmd); + printf(" %lu events\n", dev->num_evt); + printf(" %lu ACL packets\n", dev->num_acl); + printf(" %lu SCO packets\n", dev->num_sco); + printf(" %lu vendor diagnostics\n", dev->vendor_diag); + printf(" %lu system notes\n", dev->system_note); + printf(" %lu user logs\n", dev->user_log); + printf(" %lu unknown opcodes\n", dev->unknown); + printf("\n"); + + free(dev); +} + +static struct hci_dev *dev_alloc(uint16_t index) +{ + struct hci_dev *dev; + + dev = new0(struct hci_dev, 1); + + dev->index = index; + dev->manufacturer = 0xffff; + + return dev; +} + +static bool dev_match_index(const void *a, const void *b) +{ + const struct hci_dev *dev = a; + uint16_t index = PTR_TO_UINT(b); + + return dev->index == index; +} + +static struct hci_dev *dev_lookup(uint16_t index) +{ + struct hci_dev *dev; + + dev = queue_find(dev_list, dev_match_index, UINT_TO_PTR(index)); + if (!dev) { + fprintf(stderr, "Creating new device for unknown index\n"); + + dev = dev_alloc(index); + + queue_push_tail(dev_list, dev); + } + + return dev; +} + +static void new_index(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + const struct btsnoop_opcode_new_index *ni = data; + struct hci_dev *dev; + + dev = dev_alloc(index); + + dev->type = ni->type; + memcpy(dev->bdaddr, ni->bdaddr, 6); + + queue_push_tail(dev_list, dev); +} + +static void del_index(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + struct hci_dev *dev; + + dev = queue_remove_if(dev_list, dev_match_index, UINT_TO_PTR(index)); + if (!dev) { + fprintf(stderr, "Remove for an unexisting device\n"); + return; + } + + dev_destroy(dev); +} + +static void command_pkt(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + const struct bt_hci_cmd_hdr *hdr = data; + struct hci_dev *dev; + + data += sizeof(*hdr); + size -= sizeof(*hdr); + + dev = dev_lookup(index); + if (!dev) + return; + + dev->num_cmd++; +} + +static void rsp_read_bd_addr(struct hci_dev *dev, struct timeval *tv, + const void *data, uint16_t size) +{ + const struct bt_hci_rsp_read_bd_addr *rsp = data; + + printf("Read BD Addr event with status 0x%2.2x\n", rsp->status); + + if (rsp->status) + return; + + memcpy(dev->bdaddr, rsp->bdaddr, 6); +} + +static void evt_cmd_complete(struct hci_dev *dev, struct timeval *tv, + const void *data, uint16_t size) +{ + const struct bt_hci_evt_cmd_complete *evt = data; + uint16_t opcode; + + data += sizeof(*evt); + size -= sizeof(*evt); + + opcode = le16_to_cpu(evt->opcode); + + switch (opcode) { + case BT_HCI_CMD_READ_BD_ADDR: + rsp_read_bd_addr(dev, tv, data, size); + break; + } +} + +static void event_pkt(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + const struct bt_hci_evt_hdr *hdr = data; + struct hci_dev *dev; + + data += sizeof(*hdr); + size -= sizeof(*hdr); + + dev = dev_lookup(index); + if (!dev) + return; + + dev->num_evt++; + + switch (hdr->evt) { + case BT_HCI_EVT_CMD_COMPLETE: + evt_cmd_complete(dev, tv, data, size); + break; + } +} + +static void acl_pkt(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + const struct bt_hci_acl_hdr *hdr = data; + struct hci_dev *dev; + + data += sizeof(*hdr); + size -= sizeof(*hdr); + + dev = dev_lookup(index); + if (!dev) + return; + + dev->num_acl++; +} + +static void sco_pkt(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + const struct bt_hci_sco_hdr *hdr = data; + struct hci_dev *dev; + + data += sizeof(*hdr); + size -= sizeof(*hdr); + + dev = dev_lookup(index); + if (!dev) + return; + + dev->num_sco++; +} + +static void info_index(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + const struct btsnoop_opcode_index_info *hdr = data; + struct hci_dev *dev; + + data += sizeof(*hdr); + size -= sizeof(*hdr); + + dev = dev_lookup(index); + if (!dev) + return; + + dev->manufacturer = hdr->manufacturer; +} + +static void vendor_diag(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + struct hci_dev *dev; + + dev = dev_lookup(index); + if (!dev) + return; + + dev->vendor_diag++; +} + +static void system_note(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + struct hci_dev *dev; + + dev = dev_lookup(index); + if (!dev) + return; + + dev->system_note++; +} + +static void user_log(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + struct hci_dev *dev; + + dev = dev_lookup(index); + if (!dev) + return; + + dev->user_log++; +} + +static void unknown_opcode(struct timeval *tv, uint16_t index, + const void *data, uint16_t size) +{ + struct hci_dev *dev; + + dev = dev_lookup(index); + if (!dev) + return; + + dev->unknown++; +} + +void analyze_trace(const char *path) +{ + struct btsnoop *btsnoop_file; + unsigned long num_packets = 0; + uint32_t format; + + btsnoop_file = btsnoop_open(path, BTSNOOP_FLAG_PKLG_SUPPORT); + if (!btsnoop_file) + return; + + format = btsnoop_get_format(btsnoop_file); + + switch (format) { + case BTSNOOP_FORMAT_HCI: + case BTSNOOP_FORMAT_UART: + case BTSNOOP_FORMAT_MONITOR: + break; + default: + fprintf(stderr, "Unsupported packet format\n"); + goto done; + } + + dev_list = queue_new(); + + while (1) { + unsigned char buf[BTSNOOP_MAX_PACKET_SIZE]; + struct timeval tv; + uint16_t index, opcode, pktlen; + + if (!btsnoop_read_hci(btsnoop_file, &tv, &index, &opcode, + buf, &pktlen)) + break; + + switch (opcode) { + case BTSNOOP_OPCODE_NEW_INDEX: + new_index(&tv, index, buf, pktlen); + break; + case BTSNOOP_OPCODE_DEL_INDEX: + del_index(&tv, index, buf, pktlen); + break; + case BTSNOOP_OPCODE_COMMAND_PKT: + command_pkt(&tv, index, buf, pktlen); + break; + case BTSNOOP_OPCODE_EVENT_PKT: + event_pkt(&tv, index, buf, pktlen); + break; + case BTSNOOP_OPCODE_ACL_TX_PKT: + case BTSNOOP_OPCODE_ACL_RX_PKT: + acl_pkt(&tv, index, buf, pktlen); + break; + case BTSNOOP_OPCODE_SCO_TX_PKT: + case BTSNOOP_OPCODE_SCO_RX_PKT: + sco_pkt(&tv, index, buf, pktlen); + break; + case BTSNOOP_OPCODE_OPEN_INDEX: + case BTSNOOP_OPCODE_CLOSE_INDEX: + break; + case BTSNOOP_OPCODE_INDEX_INFO: + info_index(&tv, index, buf, pktlen); + break; + case BTSNOOP_OPCODE_VENDOR_DIAG: + vendor_diag(&tv, index, buf, pktlen); + break; + case BTSNOOP_OPCODE_SYSTEM_NOTE: + system_note(&tv, index, buf, pktlen); + break; + case BTSNOOP_OPCODE_USER_LOGGING: + user_log(&tv, index, buf, pktlen); + break; + default: + fprintf(stderr, "Unknown opcode %u\n", opcode); + unknown_opcode(&tv, index, buf, pktlen); + break; + } + + num_packets++; + } + + printf("Trace contains %lu packets\n\n", num_packets); + + queue_destroy(dev_list, dev_destroy); + +done: + btsnoop_unref(btsnoop_file); +} diff --git a/monitor/analyze.h b/monitor/analyze.h new file mode 100644 index 0000000..c643d35 --- /dev/null +++ b/monitor/analyze.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void analyze_trace(const char *path); diff --git a/monitor/avctp.c b/monitor/avctp.c new file mode 100644 index 0000000..6c2c2ca --- /dev/null +++ b/monitor/avctp.c @@ -0,0 +1,2555 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "bt.h" +#include "packet.h" +#include "display.h" +#include "l2cap.h" +#include "keys.h" +#include "sdp.h" +#include "avctp.h" + +/* ctype entries */ +#define AVC_CTYPE_CONTROL 0x0 +#define AVC_CTYPE_STATUS 0x1 +#define AVC_CTYPE_SPECIFIC_INQUIRY 0x2 +#define AVC_CTYPE_NOTIFY 0x3 +#define AVC_CTYPE_GENERAL_INQUIRY 0x4 +#define AVC_CTYPE_NOT_IMPLEMENTED 0x8 +#define AVC_CTYPE_ACCEPTED 0x9 +#define AVC_CTYPE_REJECTED 0xA +#define AVC_CTYPE_IN_TRANSITION 0xB +#define AVC_CTYPE_STABLE 0xC +#define AVC_CTYPE_CHANGED 0xD +#define AVC_CTYPE_INTERIM 0xF + +/* subunit type */ +#define AVC_SUBUNIT_MONITOR 0x00 +#define AVC_SUBUNIT_AUDIO 0x01 +#define AVC_SUBUNIT_PRINTER 0x02 +#define AVC_SUBUNIT_DISC 0x03 +#define AVC_SUBUNIT_TAPE 0x04 +#define AVC_SUBUNIT_TUNER 0x05 +#define AVC_SUBUNIT_CA 0x06 +#define AVC_SUBUNIT_CAMERA 0x07 +#define AVC_SUBUNIT_PANEL 0x09 +#define AVC_SUBUNIT_BULLETIN_BOARD 0x0a +#define AVC_SUBUNIT_CAMERA_STORAGE 0x0b +#define AVC_SUBUNIT_VENDOR_UNIQUE 0x0c +#define AVC_SUBUNIT_EXTENDED 0x1e +#define AVC_SUBUNIT_UNIT 0x1f + +/* opcodes */ +#define AVC_OP_VENDORDEP 0x00 +#define AVC_OP_UNITINFO 0x30 +#define AVC_OP_SUBUNITINFO 0x31 +#define AVC_OP_PASSTHROUGH 0x7c + +/* notification events */ +#define AVRCP_EVENT_PLAYBACK_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_BATT_STATUS_CHANGED 0x06 +#define AVRCP_EVENT_SYSTEM_STATUS_CHANGED 0x07 +#define AVRCP_EVENT_PLAYER_APPLICATION_SETTING_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 + +/* error statuses */ +#define AVRCP_STATUS_INVALID_COMMAND 0x00 +#define AVRCP_STATUS_INVALID_PARAMETER 0x01 +#define AVRCP_STATUS_NOT_FOUND 0x02 +#define AVRCP_STATUS_INTERNAL_ERROR 0x03 +#define AVRCP_STATUS_SUCCESS 0x04 +#define AVRCP_STATUS_UID_CHANGED 0x05 +#define AVRCP_STATUS_INVALID_DIRECTION 0x07 +#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_IS_DIRECTORY 0x0c +#define AVRCP_STATUS_MEDIA_IN_USE 0x0d +#define AVRCP_STATUS_NOW_PLAYING_LIST_FULL 0x0e +#define AVRCP_STATUS_SEARCH_NOT_SUPPORTED 0x0f +#define AVRCP_STATUS_SEARCH_IN_PROGRESS 0x10 +#define AVRCP_STATUS_INVALID_PLAYER_ID 0x11 +#define AVRCP_STATUS_PLAYER_NOT_BROWSABLE 0x12 +#define AVRCP_STATUS_PLAYER_NOT_ADDRESSED 0x13 +#define AVRCP_STATUS_NO_VALID_SEARCH_RESULTS 0x14 +#define AVRCP_STATUS_NO_AVAILABLE_PLAYERS 0x15 +#define AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED 0x16 + +/* 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_GET_TOTAL_NUMBER_OF_ITEMS 0x75 +#define AVRCP_SEARCH 0x80 +#define AVRCP_ADD_TO_NOW_PLAYING 0x90 +#define AVRCP_GENERAL_REJECT 0xA0 + +/* 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 + +/* 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 + +/* 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_TOTAL 0x05 +#define AVRCP_MEDIA_ATTRIBUTE_GENRE 0x06 +#define AVRCP_MEDIA_ATTRIBUTE_DURATION 0x07 + +/* play status */ +#define AVRCP_PLAY_STATUS_STOPPED 0x00 +#define AVRCP_PLAY_STATUS_PLAYING 0x01 +#define AVRCP_PLAY_STATUS_PAUSED 0x02 +#define AVRCP_PLAY_STATUS_FWD_SEEK 0x03 +#define AVRCP_PLAY_STATUS_REV_SEEK 0x04 +#define AVRCP_PLAY_STATUS_ERROR 0xFF + +/* 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 + +/* Media Item Type */ +#define AVRCP_MEDIA_PLAYER_ITEM_TYPE 0x01 +#define AVRCP_FOLDER_ITEM_TYPE 0x02 +#define AVRCP_MEDIA_ELEMENT_ITEM_TYPE 0x03 + +/* operands in passthrough commands */ +#define AVC_PANEL_VOLUME_UP 0x41 +#define AVC_PANEL_VOLUME_DOWN 0x42 +#define AVC_PANEL_MUTE 0x43 +#define AVC_PANEL_PLAY 0x44 +#define AVC_PANEL_STOP 0x45 +#define AVC_PANEL_PAUSE 0x46 +#define AVC_PANEL_RECORD 0x47 +#define AVC_PANEL_REWIND 0x48 +#define AVC_PANEL_FAST_FORWARD 0x49 +#define AVC_PANEL_EJECT 0x4a +#define AVC_PANEL_FORWARD 0x4b +#define AVC_PANEL_BACKWARD 0x4c + +struct avctp_frame { + uint8_t hdr; + uint8_t pt; + uint16_t pid; + struct l2cap_frame l2cap_frame; +}; + +static struct avrcp_continuing { + uint16_t num; + uint16_t size; +} avrcp_continuing; + +static const char *ctype2str(uint8_t ctype) +{ + switch (ctype & 0x0f) { + case AVC_CTYPE_CONTROL: + return "Control"; + case AVC_CTYPE_STATUS: + return "Status"; + case AVC_CTYPE_SPECIFIC_INQUIRY: + return "Specific Inquiry"; + case AVC_CTYPE_NOTIFY: + return "Notify"; + case AVC_CTYPE_GENERAL_INQUIRY: + return "General Inquiry"; + case AVC_CTYPE_NOT_IMPLEMENTED: + return "Not Implemented"; + case AVC_CTYPE_ACCEPTED: + return "Accepted"; + case AVC_CTYPE_REJECTED: + return "Rejected"; + case AVC_CTYPE_IN_TRANSITION: + return "In Transition"; + case AVC_CTYPE_STABLE: + return "Stable"; + case AVC_CTYPE_CHANGED: + return "Changed"; + case AVC_CTYPE_INTERIM: + return "Interim"; + default: + return "Unknown"; + } +} + +static const char *subunit2str(uint8_t subunit) +{ + switch (subunit) { + case AVC_SUBUNIT_MONITOR: + return "Monitor"; + case AVC_SUBUNIT_AUDIO: + return "Audio"; + case AVC_SUBUNIT_PRINTER: + return "Printer"; + case AVC_SUBUNIT_DISC: + return "Disc"; + case AVC_SUBUNIT_TAPE: + return "Tape"; + case AVC_SUBUNIT_TUNER: + return "Tuner"; + case AVC_SUBUNIT_CA: + return "CA"; + case AVC_SUBUNIT_CAMERA: + return "Camera"; + case AVC_SUBUNIT_PANEL: + return "Panel"; + case AVC_SUBUNIT_BULLETIN_BOARD: + return "Bulletin Board"; + case AVC_SUBUNIT_CAMERA_STORAGE: + return "Camera Storage"; + case AVC_SUBUNIT_VENDOR_UNIQUE: + return "Vendor Unique"; + case AVC_SUBUNIT_EXTENDED: + return "Extended to next byte"; + case AVC_SUBUNIT_UNIT: + return "Unit"; + default: + return "Reserved"; + } +} + +static const char *opcode2str(uint8_t opcode) +{ + switch (opcode) { + case AVC_OP_VENDORDEP: + return "Vendor Dependent"; + case AVC_OP_UNITINFO: + return "Unit Info"; + case AVC_OP_SUBUNITINFO: + return "Subunit Info"; + case AVC_OP_PASSTHROUGH: + return "Passthrough"; + default: + return "Unknown"; + } +} + +static char *cap2str(uint8_t cap) +{ + switch (cap) { + case 0x2: + return "CompanyID"; + case 0x3: + return "EventsID"; + default: + return "Unknown"; + } +} + +static char *event2str(uint8_t event) +{ + switch (event) { + case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED: + return "EVENT_PLAYBACK_STATUS_CHANGED"; + case AVRCP_EVENT_TRACK_CHANGED: + return "EVENT_TRACK_CHANGED"; + case AVRCP_EVENT_TRACK_REACHED_END: + return "EVENT_TRACK_REACHED_END"; + case AVRCP_EVENT_TRACK_REACHED_START: + return "EVENT_TRACK_REACHED_START"; + case AVRCP_EVENT_PLAYBACK_POS_CHANGED: + return "EVENT_PLAYBACK_POS_CHANGED"; + case AVRCP_EVENT_BATT_STATUS_CHANGED: + return "EVENT_BATT_STATUS_CHANGED"; + case AVRCP_EVENT_SYSTEM_STATUS_CHANGED: + return "EVENT_SYSTEM_STATUS_CHANGED"; + case AVRCP_EVENT_PLAYER_APPLICATION_SETTING_CHANGED: + return "EVENT_PLAYER_APPLICATION_SETTING_CHANGED"; + case AVRCP_EVENT_NOW_PLAYING_CONTENT_CHANGED: + return "EVENT_NOW_PLAYING_CONTENT_CHANGED"; + case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: + return "EVENT_AVAILABLE_PLAYERS_CHANGED"; + case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: + return "EVENT_ADDRESSED_PLAYER_CHANGED"; + case AVRCP_EVENT_UIDS_CHANGED: + return "EVENT_UIDS_CHANGED"; + case AVRCP_EVENT_VOLUME_CHANGED: + return "EVENT_VOLUME_CHANGED"; + default: + return "Reserved"; + } +} + +static const char *error2str(uint8_t status) +{ + switch (status) { + case AVRCP_STATUS_INVALID_COMMAND: + return "Invalid Command"; + case AVRCP_STATUS_INVALID_PARAMETER: + return "Invalid Parameter"; + case AVRCP_STATUS_NOT_FOUND: + return "Not Found"; + case AVRCP_STATUS_INTERNAL_ERROR: + return "Internal Error"; + case AVRCP_STATUS_SUCCESS: + return "Success"; + case AVRCP_STATUS_UID_CHANGED: + return "UID Changed"; + case AVRCP_STATUS_INVALID_DIRECTION: + return "Invalid Direction"; + case AVRCP_STATUS_NOT_DIRECTORY: + return "Not a Directory"; + case AVRCP_STATUS_DOES_NOT_EXIST: + return "Does Not Exist"; + case AVRCP_STATUS_INVALID_SCOPE: + return "Invalid Scope"; + case AVRCP_STATUS_OUT_OF_BOUNDS: + return "Range Out of Bounds"; + case AVRCP_STATUS_MEDIA_IN_USE: + return "Media in Use"; + case AVRCP_STATUS_IS_DIRECTORY: + return "UID is a Directory"; + case AVRCP_STATUS_NOW_PLAYING_LIST_FULL: + return "Now Playing List Full"; + case AVRCP_STATUS_SEARCH_NOT_SUPPORTED: + return "Search Not Supported"; + case AVRCP_STATUS_SEARCH_IN_PROGRESS: + return "Search in Progress"; + case AVRCP_STATUS_INVALID_PLAYER_ID: + return "Invalid Player ID"; + case AVRCP_STATUS_PLAYER_NOT_BROWSABLE: + return "Player Not Browsable"; + case AVRCP_STATUS_PLAYER_NOT_ADDRESSED: + return "Player Not Addressed"; + case AVRCP_STATUS_NO_VALID_SEARCH_RESULTS: + return "No Valid Search Result"; + case AVRCP_STATUS_NO_AVAILABLE_PLAYERS: + return "No Available Players"; + case AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED: + return "Addressed Player Changed"; + default: + return "Unknown"; + } +} + +static const char *pdu2str(uint8_t pduid) +{ + switch (pduid) { + case AVRCP_GET_CAPABILITIES: + return "GetCapabilities"; + case AVRCP_LIST_PLAYER_ATTRIBUTES: + return "ListPlayerApplicationSettingAttributes"; + case AVRCP_LIST_PLAYER_VALUES: + return "ListPlayerApplicationSettingValues"; + case AVRCP_GET_CURRENT_PLAYER_VALUE: + return "GetCurrentPlayerApplicationSettingValue"; + case AVRCP_SET_PLAYER_VALUE: + return "SetPlayerApplicationSettingValue"; + case AVRCP_GET_PLAYER_ATTRIBUTE_TEXT: + return "GetPlayerApplicationSettingAttributeText"; + case AVRCP_GET_PLAYER_VALUE_TEXT: + return "GetPlayerApplicationSettingValueText"; + case AVRCP_DISPLAYABLE_CHARSET: + return "InformDisplayableCharacterSet"; + case AVRCP_CT_BATTERY_STATUS: + return "InformBatteryStatusOfCT"; + case AVRCP_GET_ELEMENT_ATTRIBUTES: + return "GetElementAttributes"; + case AVRCP_GET_PLAY_STATUS: + return "GetPlayStatus"; + case AVRCP_REGISTER_NOTIFICATION: + return "RegisterNotification"; + case AVRCP_REQUEST_CONTINUING: + return "RequestContinuingResponse"; + case AVRCP_ABORT_CONTINUING: + return "AbortContinuingResponse"; + case AVRCP_SET_ABSOLUTE_VOLUME: + return "SetAbsoluteVolume"; + case AVRCP_SET_ADDRESSED_PLAYER: + return "SetAddressedPlayer"; + case AVRCP_SET_BROWSED_PLAYER: + return "SetBrowsedPlayer"; + case AVRCP_GET_FOLDER_ITEMS: + return "GetFolderItems"; + case AVRCP_CHANGE_PATH: + return "ChangePath"; + case AVRCP_GET_ITEM_ATTRIBUTES: + return "GetItemAttributes"; + case AVRCP_PLAY_ITEM: + return "PlayItem"; + case AVRCP_GET_TOTAL_NUMBER_OF_ITEMS: + return "GetTotalNumOfItems"; + case AVRCP_SEARCH: + return "Search"; + case AVRCP_ADD_TO_NOW_PLAYING: + return "AddToNowPlaying"; + case AVRCP_GENERAL_REJECT: + return "GeneralReject"; + default: + return "Unknown"; + } +} + +static const char *pt2str(uint8_t pt) +{ + switch (pt) { + case AVRCP_PACKET_TYPE_SINGLE: + return "Single"; + case AVRCP_PACKET_TYPE_START: + return "Start"; + case AVRCP_PACKET_TYPE_CONTINUING: + return "Continuing"; + case AVRCP_PACKET_TYPE_END: + return "End"; + default: + return "Unknown"; + } +} + +static const char *attr2str(uint8_t attr) +{ + switch (attr) { + case AVRCP_ATTRIBUTE_ILEGAL: + return "Illegal"; + case AVRCP_ATTRIBUTE_EQUALIZER: + return "Equalizer ON/OFF Status"; + case AVRCP_ATTRIBUTE_REPEAT_MODE: + return "Repeat Mode Status"; + case AVRCP_ATTRIBUTE_SHUFFLE: + return "Shuffle ON/OFF Status"; + case AVRCP_ATTRIBUTE_SCAN: + return "Scan ON/OFF Status"; + default: + return "Unknown"; + } +} + +static const char *value2str(uint8_t attr, uint8_t value) +{ + switch (attr) { + case AVRCP_ATTRIBUTE_ILEGAL: + return "Illegal"; + case AVRCP_ATTRIBUTE_EQUALIZER: + switch (value) { + case 0x01: + return "OFF"; + case 0x02: + return "ON"; + default: + return "Reserved"; + } + case AVRCP_ATTRIBUTE_REPEAT_MODE: + switch (value) { + case 0x01: + return "OFF"; + case 0x02: + return "Single Track Repeat"; + case 0x03: + return "All Track Repeat"; + case 0x04: + return "Group Repeat"; + default: + return "Reserved"; + } + case AVRCP_ATTRIBUTE_SHUFFLE: + switch (value) { + case 0x01: + return "OFF"; + case 0x02: + return "All Track Shuffle"; + case 0x03: + return "Group Shuffle"; + default: + return "Reserved"; + } + case AVRCP_ATTRIBUTE_SCAN: + switch (value) { + case 0x01: + return "OFF"; + case 0x02: + return "All Track Scan"; + case 0x03: + return "Group Scan"; + default: + return "Reserved"; + } + default: + return "Unknown"; + } +} + +static const char *charset2str(uint16_t charset) +{ + switch (charset) { + case 1: + case 2: + return "Reserved"; + case 3: + return "ASCII"; + case 4: + return "ISO_8859-1"; + case 5: + return "ISO_8859-2"; + case 6: + return "ISO_8859-3"; + case 7: + return "ISO_8859-4"; + case 8: + return "ISO_8859-5"; + case 9: + return "ISO_8859-6"; + case 10: + return "ISO_8859-7"; + case 11: + return "ISO_8859-8"; + case 12: + return "ISO_8859-9"; + case 106: + return "UTF-8"; + default: + return "Unknown"; + } +} + +static const char *mediattr2str(uint32_t attr) +{ + switch (attr) { + case AVRCP_MEDIA_ATTRIBUTE_ILLEGAL: + return "Illegal"; + case AVRCP_MEDIA_ATTRIBUTE_TITLE: + return "Title"; + case AVRCP_MEDIA_ATTRIBUTE_ARTIST: + return "Artist"; + case AVRCP_MEDIA_ATTRIBUTE_ALBUM: + return "Album"; + case AVRCP_MEDIA_ATTRIBUTE_TRACK: + return "Track"; + case AVRCP_MEDIA_ATTRIBUTE_TOTAL: + return "Track Total"; + case AVRCP_MEDIA_ATTRIBUTE_GENRE: + return "Genre"; + case AVRCP_MEDIA_ATTRIBUTE_DURATION: + return "Track duration"; + default: + return "Reserved"; + } +} + +static const char *playstatus2str(uint8_t status) +{ + switch (status) { + case AVRCP_PLAY_STATUS_STOPPED: + return "STOPPED"; + case AVRCP_PLAY_STATUS_PLAYING: + return "PLAYING"; + case AVRCP_PLAY_STATUS_PAUSED: + return "PAUSED"; + case AVRCP_PLAY_STATUS_FWD_SEEK: + return "FWD_SEEK"; + case AVRCP_PLAY_STATUS_REV_SEEK: + return "REV_SEEK"; + case AVRCP_PLAY_STATUS_ERROR: + return "ERROR"; + default: + return "Unknown"; + } +} + +static const char *status2str(uint8_t status) +{ + switch (status) { + case 0x0: + return "NORMAL"; + case 0x1: + return "WARNING"; + case 0x2: + return "CRITICAL"; + case 0x3: + return "EXTERNAL"; + case 0x4: + return "FULL_CHARGE"; + default: + return "Reserved"; + } +} + +static const char *scope2str(uint8_t scope) +{ + switch (scope) { + case AVRCP_MEDIA_PLAYER_LIST: + return "Media Player List"; + case AVRCP_MEDIA_PLAYER_VFS: + return "Media Player Virtual Filesystem"; + case AVRCP_MEDIA_SEARCH: + return "Search"; + case AVRCP_MEDIA_NOW_PLAYING: + return "Now Playing"; + default: + return "Unknown"; + } +} + +static char *op2str(uint8_t op) +{ + switch (op & 0x7f) { + case AVC_PANEL_VOLUME_UP: + return "VOLUME UP"; + case AVC_PANEL_VOLUME_DOWN: + return "VOLUME DOWN"; + case AVC_PANEL_MUTE: + return "MUTE"; + case AVC_PANEL_PLAY: + return "PLAY"; + case AVC_PANEL_STOP: + return "STOP"; + case AVC_PANEL_PAUSE: + return "PAUSE"; + case AVC_PANEL_RECORD: + return "RECORD"; + case AVC_PANEL_REWIND: + return "REWIND"; + case AVC_PANEL_FAST_FORWARD: + return "FAST FORWARD"; + case AVC_PANEL_EJECT: + return "EJECT"; + case AVC_PANEL_FORWARD: + return "FORWARD"; + case AVC_PANEL_BACKWARD: + return "BACKWARD"; + default: + return "UNKNOWN"; + } +} + +static const char *type2str(uint8_t type) +{ + switch (type) { + case AVRCP_MEDIA_PLAYER_ITEM_TYPE: + return "Media Player"; + case AVRCP_FOLDER_ITEM_TYPE: + return "Folder"; + case AVRCP_MEDIA_ELEMENT_ITEM_TYPE: + return "Media Element"; + default: + return "Unknown"; + } +} + +static const char *playertype2str(uint8_t type) +{ + switch (type & 0x0F) { + case 0x01: + return "Audio"; + case 0x02: + return "Video"; + case 0x03: + return "Audio, Video"; + case 0x04: + return "Audio Broadcasting"; + case 0x05: + return "Audio, Audio Broadcasting"; + case 0x06: + return "Video, Audio Broadcasting"; + case 0x07: + return "Audio, Video, Audio Broadcasting"; + case 0x08: + return "Video Broadcasting"; + case 0x09: + return "Audio, Video Broadcasting"; + case 0x0A: + return "Video, Video Broadcasting"; + case 0x0B: + return "Audio, Video, Video Broadcasting"; + case 0x0C: + return "Audio Broadcasting, Video Broadcasting"; + case 0x0D: + return "Audio, Audio Broadcasting, Video Broadcasting"; + case 0x0E: + return "Video, Audio Broadcasting, Video Broadcasting"; + case 0x0F: + return "Audio, Video, Audio Broadcasting, Video Broadcasting"; + } + + return "None"; +} + +static const char *playersubtype2str(uint32_t subtype) +{ + switch (subtype & 0x03) { + case 0x01: + return "Audio Book"; + case 0x02: + return "Podcast"; + case 0x03: + return "Audio Book, Podcast"; + } + + return "None"; +} + +static const char *foldertype2str(uint8_t type) +{ + switch (type) { + case 0x00: + return "Mixed"; + case 0x01: + return "Titles"; + case 0x02: + return "Albums"; + case 0x03: + return "Artists"; + case 0x04: + return "Genres"; + case 0x05: + return "Playlists"; + case 0x06: + return "Years"; + } + + return "Reserved"; +} + +static const char *elementtype2str(uint8_t type) +{ + switch (type) { + case 0x00: + return "Audio"; + case 0x01: + return "Video"; + } + + return "Reserved"; +} + +static bool avrcp_passthrough_packet(struct avctp_frame *avctp_frame, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t op, len; + + if (!l2cap_frame_get_u8(frame, &op)) + return false; + + print_field("%*cOperation: 0x%02x (%s %s)", (indent - 8), ' ', op, + op2str(op), op & 0x80 ? "Released" : "Pressed"); + + if (!l2cap_frame_get_u8(frame, &len)) + return false; + + print_field("%*cLength: 0x%02x", (indent - 8), ' ', len); + + packet_hexdump(frame->data, frame->size); + return true; +} + +static bool avrcp_get_capabilities(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t cap, count; + int i; + + if (!l2cap_frame_get_u8(frame, &cap)) + return false; + + print_field("%*cCapabilityID: 0x%02x (%s)", (indent - 8), ' ', cap, + cap2str(cap)); + + if (len == 1) + return true; + + if (!l2cap_frame_get_u8(frame, &count)) + return false; + + print_field("%*cCapabilityCount: 0x%02x", (indent - 8), ' ', count); + + switch (cap) { + case 0x2: + for (; count > 0; count--) { + uint8_t company[3]; + + if (!l2cap_frame_get_u8(frame, &company[0]) || + !l2cap_frame_get_u8(frame, &company[1]) || + !l2cap_frame_get_u8(frame, &company[2])) + return false; + + print_field("%*c%s: 0x%02x%02x%02x", (indent - 8), ' ', + cap2str(cap), company[0], company[1], + company[2]); + } + break; + case 0x3: + for (i = 0; count > 0; count--, i++) { + uint8_t event; + + if (!l2cap_frame_get_u8(frame, &event)) + return false; + + print_field("%*c%s: 0x%02x (%s)", (indent - 8), ' ', + cap2str(cap), event, event2str(event)); + } + break; + default: + packet_hexdump(frame->data, frame->size); + } + + return true; +} + +static bool avrcp_list_player_attributes(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t num; + int i; + + if (len == 0) + return true; + + if (!l2cap_frame_get_u8(frame, &num)) + return false; + + print_field("%*cAttributeCount: 0x%02x", (indent - 8), ' ', num); + + for (i = 0; num > 0; num--, i++) { + uint8_t attr; + + if (!l2cap_frame_get_u8(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), ' ', + attr, attr2str(attr)); + } + + return true; +} + +static bool avrcp_list_player_values(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + static uint8_t attr = 0; + uint8_t num; + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + goto response; + + if (!l2cap_frame_get_u8(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), ' ', + attr, attr2str(attr)); + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &num)) + return false; + + print_field("%*cValueCount: 0x%02x", (indent - 8), ' ', num); + + for (; num > 0; num--) { + uint8_t value; + + if (!l2cap_frame_get_u8(frame, &value)) + return false; + + print_field("%*cValueID: 0x%02x (%s)", (indent - 8), + ' ', value, value2str(attr, value)); + } + + return true; +} + +static bool avrcp_get_current_player_value(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t num; + + if (!l2cap_frame_get_u8(frame, &num)) + return false; + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + goto response; + + print_field("%*cAttributeCount: 0x%02x", (indent - 8), ' ', num); + + for (; num > 0; num--) { + uint8_t attr; + + if (!l2cap_frame_get_u8(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), + ' ', attr, attr2str(attr)); + } + + return true; + +response: + print_field("%*cValueCount: 0x%02x", (indent - 8), ' ', num); + + for (; num > 0; num--) { + uint8_t attr, value; + + if (!l2cap_frame_get_u8(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), + ' ', attr, attr2str(attr)); + + if (!l2cap_frame_get_u8(frame, &value)) + return false; + + print_field("%*cValueID: 0x%02x (%s)", (indent - 8), + ' ', value, value2str(attr, value)); + } + + return true; +} + +static bool avrcp_set_player_value(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t num; + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + return true; + + if (!l2cap_frame_get_u8(frame, &num)) + return false; + + print_field("%*cAttributeCount: 0x%02x", (indent - 8), ' ', num); + + for (; num > 0; num--) { + uint8_t attr, value; + + if (!l2cap_frame_get_u8(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), ' ', + attr, attr2str(attr)); + + if (!l2cap_frame_get_u8(frame, &value)) + return false; + + print_field("%*cValueID: 0x%02x (%s)", (indent - 8), ' ', + value, value2str(attr, value)); + } + + return true; +} + +static bool avrcp_get_player_attribute_text(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t num; + + if (!l2cap_frame_get_u8(frame, &num)) + return false; + + print_field("%*cAttributeCount: 0x%02x", (indent - 8), ' ', num); + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + goto response; + + for (; num > 0; num--) { + uint8_t attr; + + if (!l2cap_frame_get_u8(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), + ' ', attr, attr2str(attr)); + } + + return true; + +response: + for (; num > 0; num--) { + uint8_t attr, len; + uint16_t charset; + + if (!l2cap_frame_get_u8(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), + ' ', attr, attr2str(attr)); + + if (!l2cap_frame_get_be16(frame, &charset)) + return false; + + print_field("%*cCharsetID: 0x%04x (%s)", (indent - 8), + ' ', charset, charset2str(charset)); + + if (!l2cap_frame_get_u8(frame, &len)) + return false; + + print_field("%*cStringLength: 0x%02x", (indent - 8), ' ', len); + + printf("String: "); + for (; len > 0; len--) { + uint8_t c; + + if (!l2cap_frame_get_u8(frame, &c)) + return false; + + printf("%1c", isprint(c) ? c : '.'); + } + printf("\n"); + } + + return true; +} + +static bool avrcp_get_player_value_text(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + static uint8_t attr = 0; + uint8_t num; + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + goto response; + + if (!l2cap_frame_get_u8(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), ' ', + attr, attr2str(attr)); + + if (!l2cap_frame_get_u8(frame, &num)) + return false; + + print_field("%*cValueCount: 0x%02x", (indent - 8), ' ', num); + + for (; num > 0; num--) { + uint8_t value; + + if (!l2cap_frame_get_u8(frame, &value)) + return false; + + print_field("%*cValueID: 0x%02x (%s)", (indent - 8), + ' ', value, value2str(attr, value)); + } + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &num)) + return false; + + print_field("%*cValueCount: 0x%02x", (indent - 8), ' ', num); + + for (; num > 0; num--) { + uint8_t value, len; + uint16_t charset; + + if (!l2cap_frame_get_u8(frame, &value)) + return false; + + print_field("%*cValueID: 0x%02x (%s)", (indent - 8), ' ', + value, value2str(attr, value)); + + if (!l2cap_frame_get_be16(frame, &charset)) + return false; + + print_field("%*cCharsetIDID: 0x%02x (%s)", (indent - 8), ' ', + charset, charset2str(charset)); + + if (!l2cap_frame_get_u8(frame, &len)) + return false; + + print_field("%*cStringLength: 0x%02x", (indent - 8), ' ', len); + + printf("String: "); + for (; len > 0; len--) { + uint8_t c; + + if (!l2cap_frame_get_u8(frame, &c)) + return false; + + printf("%1c", isprint(c) ? c : '.'); + } + printf("\n"); + } + + return true; +} + +static bool avrcp_displayable_charset(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t num; + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + return true; + + if (!l2cap_frame_get_u8(frame, &num)) + return false; + + print_field("%*cCharsetCount: 0x%02x", (indent - 8), ' ', num); + + for (; num > 0; num--) { + uint16_t charset; + + if (!l2cap_frame_get_be16(frame, &charset)) + return false; + + print_field("%*cCharsetID: 0x%04x (%s)", (indent - 8), + ' ', charset, charset2str(charset)); + } + + return true; +} + +static bool avrcp_get_element_attributes(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint64_t id; + uint8_t num; + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + goto response; + + if (!l2cap_frame_get_be64(frame, &id)) + return false; + + print_field("%*cIdentifier: 0x%jx (%s)", (indent - 8), ' ', + id, id ? "Reserved" : "PLAYING"); + + if (!l2cap_frame_get_u8(frame, &num)) + return false; + + print_field("%*cAttributeCount: 0x%02x", (indent - 8), ' ', num); + + for (; num > 0; num--) { + uint32_t attr; + + if (!l2cap_frame_get_le32(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%08x (%s)", (indent - 8), + ' ', attr, mediattr2str(attr)); + } + + return true; + +response: + switch (avctp_frame->pt) { + case AVRCP_PACKET_TYPE_SINGLE: + case AVRCP_PACKET_TYPE_START: + if (!l2cap_frame_get_u8(frame, &num)) + return false; + + avrcp_continuing.num = num; + print_field("%*cAttributeCount: 0x%02x", (indent - 8), + ' ', num); + len--; + break; + case AVRCP_PACKET_TYPE_CONTINUING: + case AVRCP_PACKET_TYPE_END: + num = avrcp_continuing.num; + + if (avrcp_continuing.size > 0) { + char attrval[UINT8_MAX] = {0}; + uint16_t size; + uint8_t idx; + + if (avrcp_continuing.size > len) { + size = len; + avrcp_continuing.size -= len; + } else { + size = avrcp_continuing.size; + avrcp_continuing.size = 0; + } + + for (idx = 0; size > 0; idx++, size--) { + uint8_t c; + + if (!l2cap_frame_get_u8(frame, &c)) + goto failed; + + sprintf(&attrval[idx], "%1c", + isprint(c) ? c : '.'); + } + print_field("%*cContinuingAttributeValue: %s", + (indent - 8), ' ', attrval); + + len -= size; + } + break; + default: + goto failed; + } + + while (num > 0 && len > 0) { + uint32_t attr; + uint16_t charset, attrlen; + uint8_t idx; + char attrval[UINT8_MAX] = {0}; + + if (!l2cap_frame_get_be32(frame, &attr)) + goto failed; + + print_field("%*cAttribute: 0x%08x (%s)", (indent - 8), + ' ', attr, mediattr2str(attr)); + + if (!l2cap_frame_get_be16(frame, &charset)) + goto failed; + + print_field("%*cCharsetID: 0x%04x (%s)", (indent - 8), + ' ', charset, charset2str(charset)); + + if (!l2cap_frame_get_be16(frame, &attrlen)) + goto failed; + + print_field("%*cAttributeValueLength: 0x%04x", + (indent - 8), ' ', attrlen); + + len -= sizeof(attr) + sizeof(charset) + sizeof(attrlen); + num--; + + for (idx = 0; attrlen > 0 && len > 0; idx++, attrlen--, len--) { + uint8_t c; + + if (!l2cap_frame_get_u8(frame, &c)) + goto failed; + + sprintf(&attrval[idx], "%1c", isprint(c) ? c : '.'); + } + print_field("%*cAttributeValue: %s", (indent - 8), + ' ', attrval); + + if (attrlen > 0) + avrcp_continuing.size = attrlen; + } + + avrcp_continuing.num = num; + return true; + +failed: + avrcp_continuing.num = 0; + avrcp_continuing.size = 0; + return false; +} + +static bool avrcp_get_play_status(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint32_t interval; + uint8_t status; + + if (ctype <= AVC_CTYPE_GENERAL_INQUIRY) + return true; + + if (!l2cap_frame_get_be32(frame, &interval)) + return false; + + print_field("%*cSongLength: 0x%08x (%u miliseconds)", + (indent - 8), ' ', interval, interval); + + if (!l2cap_frame_get_be32(frame, &interval)) + return false; + + print_field("%*cSongPosition: 0x%08x (%u miliseconds)", + (indent - 8), ' ', interval, interval); + + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cPlayStatus: 0x%02x (%s)", (indent - 8), + ' ', status, playstatus2str(status)); + + return true; +} + +static bool avrcp_register_notification(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t event, status; + uint16_t uid; + uint32_t interval; + uint64_t id; + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + goto response; + + if (!l2cap_frame_get_u8(frame, &event)) + return false; + + print_field("%*cEventID: 0x%02x (%s)", (indent - 8), + ' ', event, event2str(event)); + + if (!l2cap_frame_get_be32(frame, &interval)) + return false; + + print_field("%*cInterval: 0x%08x (%u seconds)", + (indent - 8), ' ', interval, interval); + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &event)) + return false; + + print_field("%*cEventID: 0x%02x (%s)", (indent - 8), + ' ', event, event2str(event)); + + switch (event) { + case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cPlayStatus: 0x%02x (%s)", (indent - 8), + ' ', status, playstatus2str(status)); + break; + case AVRCP_EVENT_TRACK_CHANGED: + if (!l2cap_frame_get_be64(frame, &id)) + return false; + + print_field("%*cIdentifier: 0x%16" PRIx64 " (%" PRIu64 ")", + (indent - 8), ' ', id, id); + break; + case AVRCP_EVENT_PLAYBACK_POS_CHANGED: + if (!l2cap_frame_get_be32(frame, &interval)) + return false; + + print_field("%*cPosition: 0x%08x (%u miliseconds)", + (indent - 8), ' ', interval, interval); + break; + case AVRCP_EVENT_BATT_STATUS_CHANGED: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cBatteryStatus: 0x%02x (%s)", (indent - 8), + ' ', status, status2str(status)); + + break; + case AVRCP_EVENT_SYSTEM_STATUS_CHANGED: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cSystemStatus: 0x%02x ", (indent - 8), + ' ', status); + switch (status) { + case 0x00: + printf("(POWER_ON)\n"); + break; + case 0x01: + printf("(POWER_OFF)\n"); + break; + case 0x02: + printf("(UNPLUGGED)\n"); + break; + default: + printf("(UNKNOWN)\n"); + break; + } + break; + case AVRCP_EVENT_PLAYER_APPLICATION_SETTING_CHANGED: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cAttributeCount: 0x%02x", (indent - 8), + ' ', status); + + for (; status > 0; status--) { + uint8_t attr, value; + + if (!l2cap_frame_get_u8(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%02x (%s)", + (indent - 8), ' ', attr, attr2str(attr)); + + if (!l2cap_frame_get_u8(frame, &value)) + return false; + + print_field("%*cValueID: 0x%02x (%s)", (indent - 8), + ' ', value, value2str(attr, value)); + } + break; + case AVRCP_EVENT_VOLUME_CHANGED: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + status &= 0x7F; + + print_field("%*cVolume: %.2f%% (%d/127)", (indent - 8), + ' ', status/1.27, status); + break; + case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: + if (!l2cap_frame_get_be16(frame, &uid)) + return false; + + print_field("%*cPlayerID: 0x%04x (%u)", (indent - 8), + ' ', uid, uid); + + if (!l2cap_frame_get_be16(frame, &uid)) + return false; + + print_field("%*cUIDCounter: 0x%04x (%u)", (indent - 8), + ' ', uid, uid); + break; + case AVRCP_EVENT_UIDS_CHANGED: + if (!l2cap_frame_get_be16(frame, &uid)) + return false; + + print_field("%*cUIDCounter: 0x%04x (%u)", (indent - 8), + ' ', uid, uid); + break; + } + + return true; +} + +static bool avrcp_set_absolute_volume(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t value; + + if (!l2cap_frame_get_u8(frame, &value)) + return false; + + value &= 0x7F; + print_field("%*cVolume: %.2f%% (%d/127)", (indent - 8), + ' ', value/1.27, value); + + return true; +} + +static bool avrcp_set_addressed_player(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint16_t id; + uint8_t status; + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + goto response; + + if (!l2cap_frame_get_be16(frame, &id)) + return false; + + print_field("%*cPlayerID: 0x%04x (%u)", (indent - 8), ' ', id, id); + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cStatus: 0x%02x (%s)", (indent - 8), ' ', + status, error2str(status)); + + return true; +} + +static bool avrcp_play_item(struct avctp_frame *avctp_frame, uint8_t ctype, + uint8_t len, uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint64_t uid; + uint16_t uidcounter; + uint8_t scope, status; + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + goto response; + + if (!l2cap_frame_get_u8(frame, &scope)) + return false; + + print_field("%*cScope: 0x%02x (%s)", (indent - 8), ' ', + scope, scope2str(scope)); + + if (!l2cap_frame_get_be64(frame, &uid)) + return false; + + print_field("%*cUID: 0x%16" PRIx64 " (%" PRIu64 ")", (indent - 8), + ' ', uid, uid); + + if (!l2cap_frame_get_be16(frame, &uidcounter)) + return false; + + print_field("%*cUIDCounter: 0x%04x (%u)", (indent - 8), ' ', + uidcounter, uidcounter); + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cStatus: 0x%02x (%s)", (indent - 8), ' ', status, + error2str(status)); + + return true; +} + +static bool avrcp_add_to_now_playing(struct avctp_frame *avctp_frame, + uint8_t ctype, uint8_t len, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint64_t uid; + uint16_t uidcounter; + uint8_t scope, status; + + if (ctype > AVC_CTYPE_GENERAL_INQUIRY) + goto response; + + if (!l2cap_frame_get_u8(frame, &scope)) + return false; + + print_field("%*cScope: 0x%02x (%s)", (indent - 8), ' ', + scope, scope2str(scope)); + + if (!l2cap_frame_get_be64(frame, &uid)) + return false; + + print_field("%*cUID: 0x%16" PRIx64 " (%" PRIu64 ")", (indent - 8), + ' ', uid, uid); + + if (!l2cap_frame_get_be16(frame, &uidcounter)) + return false; + + print_field("%*cUIDCounter: 0x%04x (%u)", (indent - 8), ' ', + uidcounter, uidcounter); + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cStatus: 0x%02x (%s)", (indent - 8), ' ', status, + error2str(status)); + + return true; +} + +struct avrcp_ctrl_pdu_data { + uint8_t pduid; + bool (*func) (struct avctp_frame *avctp_frame, uint8_t ctype, + uint8_t len, uint8_t indent); +}; + +static const struct avrcp_ctrl_pdu_data avrcp_ctrl_pdu_table[] = { + { 0x10, avrcp_get_capabilities }, + { 0x11, avrcp_list_player_attributes }, + { 0x12, avrcp_list_player_values }, + { 0x13, avrcp_get_current_player_value }, + { 0x14, avrcp_set_player_value }, + { 0x15, avrcp_get_player_attribute_text }, + { 0x16, avrcp_get_player_value_text }, + { 0x17, avrcp_displayable_charset }, + { 0x20, avrcp_get_element_attributes }, + { 0x30, avrcp_get_play_status }, + { 0x31, avrcp_register_notification }, + { 0x50, avrcp_set_absolute_volume }, + { 0x60, avrcp_set_addressed_player }, + { 0x74, avrcp_play_item }, + { 0x90, avrcp_add_to_now_playing }, + { } +}; + +static bool avrcp_rejected_packet(struct l2cap_frame *frame, uint8_t indent) +{ + uint8_t status; + + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cError: 0x%02x (%s)", (indent - 8), ' ', status, + error2str(status)); + + return true; +} + +static bool avrcp_pdu_packet(struct avctp_frame *avctp_frame, uint8_t ctype, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t pduid; + uint16_t len; + int i; + const struct avrcp_ctrl_pdu_data *ctrl_pdu_data = NULL; + + if (!l2cap_frame_get_u8(frame, &pduid)) + return false; + + if (!l2cap_frame_get_u8(frame, &avctp_frame->pt)) + return false; + + if (!l2cap_frame_get_be16(frame, &len)) + return false; + + print_indent(indent, COLOR_OFF, "AVRCP: ", pdu2str(pduid), COLOR_OFF, + " pt %s len 0x%04x", pt2str(avctp_frame->pt), len); + + if (frame->size != len) + return false; + + if (ctype == 0xA) + return avrcp_rejected_packet(frame, indent + 2); + + for (i = 0; avrcp_ctrl_pdu_table[i].func; i++) { + if (avrcp_ctrl_pdu_table[i].pduid == pduid) { + ctrl_pdu_data = &avrcp_ctrl_pdu_table[i]; + break; + } + } + + if (!ctrl_pdu_data || !ctrl_pdu_data->func) { + packet_hexdump(frame->data, frame->size); + return true; + } + + return ctrl_pdu_data->func(avctp_frame, ctype, len, indent + 2); +} + +static bool avrcp_control_packet(struct avctp_frame *avctp_frame) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + + uint8_t ctype, address, subunit, opcode, company[3], indent = 2; + + if (!l2cap_frame_get_u8(frame, &ctype) || + !l2cap_frame_get_u8(frame, &address) || + !l2cap_frame_get_u8(frame, &opcode)) + return false; + + print_field("AV/C: %s: address 0x%02x opcode 0x%02x", + ctype2str(ctype), address, opcode); + + subunit = address >> 3; + + print_field("%*cSubunit: %s", indent, ' ', subunit2str(subunit)); + + print_field("%*cOpcode: %s", indent, ' ', opcode2str(opcode)); + + /* Skip non-panel subunit packets */ + if (subunit != 0x09) { + packet_hexdump(frame->data, frame->size); + return true; + } + + /* Not implemented should not contain any operand */ + if (ctype == 0x8) { + packet_hexdump(frame->data, frame->size); + return true; + } + + switch (opcode) { + case 0x7c: + return avrcp_passthrough_packet(avctp_frame, 10); + case 0x00: + if (!l2cap_frame_get_u8(frame, &company[0]) || + !l2cap_frame_get_u8(frame, &company[1]) || + !l2cap_frame_get_u8(frame, &company[2])) + return false; + + print_field("%*cCompany ID: 0x%02x%02x%02x", indent, ' ', + company[0], company[1], company[2]); + + return avrcp_pdu_packet(avctp_frame, ctype, 10); + default: + packet_hexdump(frame->data, frame->size); + return true; + } +} + +static const char *dir2str(uint8_t dir) +{ + switch (dir) { + case 0x00: + return "Folder Up"; + case 0x01: + return "Folder Down"; + } + + return "Reserved"; +} + +static bool avrcp_change_path(struct avctp_frame *avctp_frame) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint64_t uid; + uint32_t items; + uint16_t uidcounter; + uint8_t dir, status, indent = 2; + + if (avctp_frame->hdr & 0x02) + goto response; + + if (frame->size < 11) { + print_field("%*cPDU Malformed", indent, ' '); + packet_hexdump(frame->data, frame->size); + return false; + } + + if (!l2cap_frame_get_be16(frame, &uidcounter)) + return false; + + print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', + uidcounter, uidcounter); + + if (!l2cap_frame_get_u8(frame, &dir)) + return false; + + print_field("%*cDirection: 0x%02x (%s)", indent, ' ', + dir, dir2str(dir)); + + if (!l2cap_frame_get_be64(frame, &uid)) + return false; + + print_field("%*cFolderUID: 0x%16" PRIx64 " (%" PRIu64 ")", indent, ' ', + uid, uid); + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cStatus: 0x%02x (%s)", indent, ' ', + status, error2str(status)); + + if (frame->size == 1) + return false; + + if (!l2cap_frame_get_be32(frame, &items)) + return false; + + print_field("%*cNumber of Items: 0x%04x (%u)", indent, ' ', + items, items); + + return true; +} + + +static struct { + const char *str; + bool reserved; +} features_table[] = { + /* Ignore passthrough bits */ + [58] = { "Advanced Control Player" }, + [59] = { "Browsing" }, + [60] = { "Searching" }, + [61] = { "AddToNowPlaying" }, + [62] = { "Unique UIDs" }, + [63] = { "OnlyBrowsableWhenAddressed" }, + [64] = { "OnlySearchableWhenAddressed" }, + [65] = { "NowPlaying" }, + [66] = { "UIDPersistency" }, + /* 67-127 reserved */ + [67 ... 127] = { .reserved = true }, +}; + +static void print_features(uint8_t features[16], uint8_t indent) +{ + int i; + + for (i = 0; i < 127; i++) { + if (!(features[i / 8] & (1 << (i % 8)))) + continue; + + if (features_table[i].reserved) { + print_text(COLOR_WHITE_BG, "Unknown bit %u", i); + continue; + } + + if (!features_table[i].str) + continue; + + print_field("%*c%s", indent, ' ', features_table[i].str); + } +} + +static bool avrcp_media_player_item(struct avctp_frame *avctp_frame, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint16_t id, charset, namelen; + uint8_t type, status, i; + uint32_t subtype; + uint8_t features[16]; + + if (!l2cap_frame_get_be16(frame, &id)) + return false; + + print_field("%*cPlayerID: 0x%04x (%u)", indent, ' ', id, id); + + if (!l2cap_frame_get_u8(frame, &type)) + return false; + + print_field("%*cPlayerType: 0x%04x (%s)", indent, ' ', + type, playertype2str(type)); + + if (!l2cap_frame_get_be32(frame, &subtype)) + return false; + + print_field("%*cPlayerSubType: 0x%08x (%s)", indent, ' ', + subtype, playersubtype2str(subtype)); + + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cPlayStatus: 0x%02x (%s)", indent, ' ', + status, playstatus2str(status)); + + printf("%*cFeatures: 0x", indent+8, ' '); + + for (i = 0; i < 16; i++) { + if (!l2cap_frame_get_u8(frame, &features[i])) + return false; + + printf("%02x", features[i]); + } + + printf("\n"); + + print_features(features, indent + 2); + + if (!l2cap_frame_get_be16(frame, &charset)) + return false; + + print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', + charset, charset2str(charset)); + + if (!l2cap_frame_get_be16(frame, &namelen)) + return false; + + print_field("%*cNameLength: 0x%04x (%u)", indent, ' ', + namelen, namelen); + + printf("%*cName: ", indent+8, ' '); + for (; namelen > 0; namelen--) { + uint8_t c; + + if (!l2cap_frame_get_u8(frame, &c)) + return false; + printf("%1c", isprint(c) ? c : '.'); + } + printf("\n"); + + return true; +} + +static bool avrcp_folder_item(struct avctp_frame *avctp_frame, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t type, playable; + uint16_t charset, namelen; + uint64_t uid; + + if (frame->size < 14) { + printf("PDU Malformed\n"); + return false; + } + + if (!l2cap_frame_get_be64(frame, &uid)) + return false; + + print_field("%*cFolderUID: 0x%16" PRIx64 " (%" PRIu64 ")", indent, ' ', + uid, uid); + + if (!l2cap_frame_get_u8(frame, &type)) + return false; + + print_field("%*cFolderType: 0x%02x (%s)", indent, ' ', + type, foldertype2str(type)); + + if (!l2cap_frame_get_u8(frame, &playable)) + return false; + + print_field("%*cIsPlayable: 0x%02x (%s)", indent, ' ', playable, + playable & 0x01 ? "True" : "False"); + + if (!l2cap_frame_get_be16(frame, &charset)) + return false; + + print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', + charset, charset2str(charset)); + + if (!l2cap_frame_get_be16(frame, &namelen)) + return false; + + print_field("%*cNameLength: 0x%04x (%u)", indent, ' ', + namelen, namelen); + + printf("%*cName: ", indent+8, ' '); + for (; namelen > 0; namelen--) { + uint8_t c; + if (!l2cap_frame_get_u8(frame, &c)) + return false; + + printf("%1c", isprint(c) ? c : '.'); + } + printf("\n"); + + return true; +} + +static bool avrcp_attribute_entry_list(struct avctp_frame *avctp_frame, + uint8_t indent, uint8_t count) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + + for (; count > 0; count--) { + uint32_t attr; + uint16_t charset, len; + + if (!l2cap_frame_get_be32(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%08x (%s)", indent, ' ', + attr, mediattr2str(attr)); + + if (!l2cap_frame_get_be16(frame, &charset)) + return false; + + print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', + charset, charset2str(charset)); + + if (!l2cap_frame_get_be16(frame, &len)) + return false; + + print_field("%*cAttributeLength: 0x%04x (%u)", indent, ' ', + len, len); + + printf("%*cAttributeValue: ", indent+8, ' '); + for (; len > 0; len--) { + uint8_t c; + + if (!l2cap_frame_get_u8(frame, &c)) + return false; + + printf("%1c", isprint(c) ? c : '.'); + } + printf("\n"); + } + + return true; +} + +static bool avrcp_media_element_item(struct avctp_frame *avctp_frame, + uint8_t indent) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint64_t uid; + uint16_t charset, namelen; + uint8_t type, count; + + if (!l2cap_frame_get_be64(frame, &uid)) + return false; + + print_field("%*cElementUID: 0x%16" PRIx64 " (%" PRIu64 ")", + indent, ' ', uid, uid); + + if (!l2cap_frame_get_u8(frame, &type)) + return false; + + print_field("%*cElementType: 0x%02x (%s)", indent, ' ', + type, elementtype2str(type)); + + if (!l2cap_frame_get_be16(frame, &charset)) + return false; + + print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', + charset, charset2str(charset)); + + if (!l2cap_frame_get_be16(frame, &namelen)) + return false; + + print_field("%*cNameLength: 0x%04x (%u)", indent, ' ', + namelen, namelen); + + printf("%*cName: ", indent+8, ' '); + for (; namelen > 0; namelen--) { + uint8_t c; + if (!l2cap_frame_get_u8(frame, &c)) + return false; + + printf("%1c", isprint(c) ? c : '.'); + } + printf("\n"); + + if (!l2cap_frame_get_u8(frame, &count)) + return false; + + print_field("%*cAttributeCount: 0x%02x (%u)", indent, ' ', + count, count); + + if (!avrcp_attribute_entry_list(avctp_frame, indent, count)) + return false; + + return true; +} + +static bool avrcp_general_reject(struct avctp_frame *avctp_frame) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t status, indent = 2; + + if (avctp_frame->hdr & 0x02) + goto response; + + print_field("%*cPDU Malformed", indent, ' '); + packet_hexdump(frame->data, frame->size); + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cStatus: 0x%02x (%s)", indent, ' ', + status, error2str(status)); + + return true; +} + +static bool avrcp_get_total_number_of_items(struct avctp_frame *avctp_frame) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint32_t num_of_items; + uint16_t uidcounter; + uint8_t scope, status, indent = 2; + + if (avctp_frame->hdr & 0x02) + goto response; + + if (frame->size < 4) { + printf("PDU Malformed\n"); + packet_hexdump(frame->data, frame->size); + return false; + } + + if (!l2cap_frame_get_u8(frame, &scope)) + return false; + + print_field("%*cScope: 0x%02x (%s)", (indent - 8), ' ', + scope, scope2str(scope)); + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cStatus: 0x%02x (%s)", indent, ' ', + status, error2str(status)); + + if (frame->size == 1) + return false; + + if (!l2cap_frame_get_be16(frame, &uidcounter)) + return false; + + print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', + uidcounter, uidcounter); + + if (!l2cap_frame_get_be32(frame, &num_of_items)) + return false; + + print_field("%*cNumber of Items: 0x%04x (%u)", indent, ' ', + num_of_items, num_of_items); + + return true; +} + +static bool avrcp_search_item(struct avctp_frame *avctp_frame) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint32_t items; + uint16_t charset, namelen, uidcounter; + uint8_t status, indent = 2; + + if (avctp_frame->hdr & 0x02) + goto response; + + if (frame->size < 4) { + printf("PDU Malformed\n"); + packet_hexdump(frame->data, frame->size); + return false; + } + + if (!l2cap_frame_get_be16(frame, &charset)) + return false; + + print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', + charset, charset2str(charset)); + + if (!l2cap_frame_get_be16(frame, &namelen)) + return false; + + print_field("%*cLength: 0x%04x (%u)", indent, ' ', namelen, namelen); + + printf("%*cString: ", indent+8, ' '); + for (; namelen > 0; namelen--) { + uint8_t c; + + if (!l2cap_frame_get_u8(frame, &c)) + return false; + + printf("%1c", isprint(c) ? c : '.'); + } + + printf("\n"); + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cStatus: 0x%02x (%s)", indent, ' ', + status, error2str(status)); + + if (frame->size == 1) + return false; + + if (!l2cap_frame_get_be16(frame, &uidcounter)) + return false; + + print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', + uidcounter, uidcounter); + + if (!l2cap_frame_get_be32(frame, &items)) + return false; + + print_field("%*cNumber of Items: 0x%04x (%u)", indent, ' ', + items, items); + + return true; +} + +static bool avrcp_get_item_attributes(struct avctp_frame *avctp_frame) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint64_t uid; + uint16_t uidcounter; + uint8_t scope, count, status, indent = 2; + + if (avctp_frame->hdr & 0x02) + goto response; + + if (frame->size < 12) { + print_field("%*cPDU Malformed", indent, ' '); + packet_hexdump(frame->data, frame->size); + return false; + } + + if (!l2cap_frame_get_u8(frame, &scope)) + return false; + + print_field("%*cScope: 0x%02x (%s)", indent, ' ', + scope, scope2str(scope)); + + if (!l2cap_frame_get_be64(frame, &uid)) + return false; + + print_field("%*cUID: 0x%016" PRIx64 " (%" PRIu64 ")", indent, + ' ', uid, uid); + + if (!l2cap_frame_get_be16(frame, &uidcounter)) + return false; + + print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', + uidcounter, uidcounter); + + if (!l2cap_frame_get_u8(frame, &count)) + return false; + + print_field("%*cAttributeCount: 0x%02x (%u)", indent, ' ', + count, count); + + for (; count > 0; count--) { + uint32_t attr; + + if (!l2cap_frame_get_be32(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%08x (%s)", indent, ' ', + attr, mediattr2str(attr)); + } + + return true; + +response: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cStatus: 0x%02x (%s)", indent, ' ', + status, error2str(status)); + + if (frame->size == 1) + return false; + + if (!l2cap_frame_get_u8(frame, &count)) + return false; + + print_field("%*cAttributeCount: 0x%02x (%u)", indent, ' ', + count, count); + + if (!avrcp_attribute_entry_list(avctp_frame, indent, count)) + return false; + + return true; +} + +static bool avrcp_get_folder_items(struct avctp_frame *avctp_frame) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint8_t scope, count, status, indent = 2; + uint32_t start, end; + uint16_t uid, num; + + if (avctp_frame->hdr & 0x02) + goto response; + + if (!l2cap_frame_get_u8(frame, &scope)) + return false; + + print_field("%*cScope: 0x%02x (%s)", indent, ' ', + scope, scope2str(scope)); + + if (!l2cap_frame_get_be32(frame, &start)) + return false; + + print_field("%*cStartItem: 0x%08x (%u)", indent, ' ', start, start); + + if (!l2cap_frame_get_be32(frame, &end)) + return false; + + print_field("%*cEndItem: 0x%08x (%u)", indent, ' ', end, end); + + if (!l2cap_frame_get_u8(frame, &count)) + return false; + + print_field("%*cAttributeCount: 0x%02x (%u)", indent, ' ', + count, count); + + for (; count > 0; count--) { + uint32_t attr; + + if (!l2cap_frame_get_be32(frame, &attr)) + return false; + + print_field("%*cAttributeID: 0x%08x (%s)", indent, ' ', + attr, mediattr2str(attr)); + } + + return false; + +response: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cStatus: 0x%02x (%s)", indent, ' ', + status, error2str(status)); + + if (!l2cap_frame_get_be16(frame, &uid)) + return false; + + print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', uid, uid); + + if (!l2cap_frame_get_be16(frame, &num)) + return false; + + print_field("%*cNumOfItems: 0x%04x (%u)", indent, ' ', num, num); + + for (; num > 0; num--) { + uint8_t type; + uint16_t len; + + if (!l2cap_frame_get_u8(frame, &type)) + return false; + + if (!l2cap_frame_get_be16(frame, &len)) + return false; + + print_field("%*cItem: 0x%02x (%s) ", indent, ' ', + type, type2str(type)); + print_field("%*cLength: 0x%04x (%u)", indent, ' ', len, len); + + switch (type) { + case AVRCP_MEDIA_PLAYER_ITEM_TYPE: + avrcp_media_player_item(avctp_frame, indent); + break; + case AVRCP_FOLDER_ITEM_TYPE: + avrcp_folder_item(avctp_frame, indent); + break; + case AVRCP_MEDIA_ELEMENT_ITEM_TYPE: + avrcp_media_element_item(avctp_frame, indent); + break; + default: + print_field("%*cUnknown Media Item type", indent, ' '); + packet_hexdump(frame->data, frame->size); + break; + } + } + return true; +} + +static bool avrcp_set_browsed_player(struct avctp_frame *avctp_frame) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint32_t items; + uint16_t id, uids, charset; + uint8_t status, folders, indent = 2; + + if (avctp_frame->hdr & 0x02) + goto response; + + if (!l2cap_frame_get_be16(frame, &id)) + return false; + + print_field("%*cPlayerID: 0x%04x (%u)", indent, ' ', id, id); + return true; + +response: + if (!l2cap_frame_get_u8(frame, &status)) + return false; + + print_field("%*cStatus: 0x%02x (%s)", indent, ' ', status, + error2str(status)); + + if (!l2cap_frame_get_be16(frame, &uids)) + return false; + + print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', uids, uids); + + if (!l2cap_frame_get_be32(frame, &items)) + return false; + + print_field("%*cNumber of Items: 0x%08x (%u)", indent, ' ', + items, items); + + if (!l2cap_frame_get_be16(frame, &charset)) + return false; + + print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', charset, + charset2str(charset)); + + if (!l2cap_frame_get_u8(frame, &folders)) + return false; + + print_field("%*cFolder Depth: 0x%02x (%u)", indent, ' ', folders, + folders); + + for (; folders > 0; folders--) { + uint8_t len; + + if (!l2cap_frame_get_u8(frame, &len)) + return false; + + if (!len) { + print_field("%*cFolder: ", indent, ' '); + continue; + } + + printf("%*cFolder: ", indent+8, ' '); + for (; len > 0; len--) { + uint8_t c; + + if (!l2cap_frame_get_u8(frame, &c)) + return false; + + printf("%1c", isprint(c) ? c : '.'); + } + printf("\n"); + } + + return true; +} + +static bool avrcp_browsing_packet(struct avctp_frame *avctp_frame) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + uint16_t len; + uint8_t pduid; + + if (!l2cap_frame_get_u8(frame, &pduid)) + return false; + + if (!l2cap_frame_get_be16(frame, &len)) + return false; + + print_field("AVRCP: %s: len 0x%04x", pdu2str(pduid), len); + + switch (pduid) { + case AVRCP_SET_BROWSED_PLAYER: + avrcp_set_browsed_player(avctp_frame); + break; + case AVRCP_GET_FOLDER_ITEMS: + avrcp_get_folder_items(avctp_frame); + break; + case AVRCP_CHANGE_PATH: + avrcp_change_path(avctp_frame); + break; + case AVRCP_GET_ITEM_ATTRIBUTES: + avrcp_get_item_attributes(avctp_frame); + break; + case AVRCP_GET_TOTAL_NUMBER_OF_ITEMS: + avrcp_get_total_number_of_items(avctp_frame); + break; + case AVRCP_SEARCH: + avrcp_search_item(avctp_frame); + break; + case AVRCP_GENERAL_REJECT: + avrcp_general_reject(avctp_frame); + break; + default: + packet_hexdump(frame->data, frame->size); + } + + return true; +} + +static void avrcp_packet(struct avctp_frame *avctp_frame) +{ + struct l2cap_frame *frame = &avctp_frame->l2cap_frame; + bool ret; + + switch (frame->psm) { + case 0x17: + ret = avrcp_control_packet(avctp_frame); + break; + case 0x1B: + ret = avrcp_browsing_packet(avctp_frame); + break; + default: + packet_hexdump(frame->data, frame->size); + return; + } + + if (!ret) { + print_text(COLOR_ERROR, "PDU malformed"); + packet_hexdump(frame->data, frame->size); + } +} + +void avctp_packet(const struct l2cap_frame *frame) +{ + struct l2cap_frame *l2cap_frame; + struct avctp_frame avctp_frame; + const char *pdu_color; + + l2cap_frame_pull(&avctp_frame.l2cap_frame, frame, 0); + + l2cap_frame = &avctp_frame.l2cap_frame; + + if (!l2cap_frame_get_u8(l2cap_frame, &avctp_frame.hdr) || + !l2cap_frame_get_be16(l2cap_frame, &avctp_frame.pid)) { + print_text(COLOR_ERROR, "frame too short"); + packet_hexdump(frame->data, frame->size); + return; + } + + if (frame->in) + pdu_color = COLOR_MAGENTA; + else + pdu_color = COLOR_BLUE; + + print_indent(6, pdu_color, "AVCTP", "", COLOR_OFF, + " %s: %s: type 0x%02x label %d PID 0x%04x", + frame->psm == 23 ? "Control" : "Browsing", + avctp_frame.hdr & 0x02 ? "Response" : "Command", + avctp_frame.hdr & 0x0c, avctp_frame.hdr >> 4, + avctp_frame.pid); + + if (avctp_frame.pid == 0x110e || avctp_frame.pid == 0x110c) + avrcp_packet(&avctp_frame); + else + packet_hexdump(frame->data, frame->size); +} diff --git a/monitor/avctp.h b/monitor/avctp.h new file mode 100644 index 0000000..2613f14 --- /dev/null +++ b/monitor/avctp.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void avctp_packet(const struct l2cap_frame *frame); diff --git a/monitor/avdtp.c b/monitor/avdtp.c new file mode 100644 index 0000000..6a342a2 --- /dev/null +++ b/monitor/avdtp.c @@ -0,0 +1,789 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Andrzej Kaczmarek + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include "lib/bluetooth.h" + +#include "src/shared/util.h" +#include "bt.h" +#include "packet.h" +#include "display.h" +#include "l2cap.h" +#include "avdtp.h" +#include "a2dp.h" + +/* Message Types */ +#define AVDTP_MSG_TYPE_COMMAND 0x00 +#define AVDTP_MSG_TYPE_GENERAL_REJECT 0x01 +#define AVDTP_MSG_TYPE_RESPONSE_ACCEPT 0x02 +#define AVDTP_MSG_TYPE_RESPONSE_REJECT 0x03 + +/* Signal Identifiers */ +#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_DELAYREPORT 0x0d + +/* Service 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 + +struct avdtp_frame { + uint8_t hdr; + uint8_t sig_id; + struct l2cap_frame l2cap_frame; +}; + +static inline bool is_configuration_sig_id(uint8_t sig_id) +{ + return (sig_id == AVDTP_SET_CONFIGURATION) || + (sig_id == AVDTP_GET_CONFIGURATION) || + (sig_id == AVDTP_RECONFIGURE); +} + +static const char *msgtype2str(uint8_t msgtype) +{ + switch (msgtype) { + case 0: + return "Command"; + case 1: + return "General Reject"; + case 2: + return "Response Accept"; + case 3: + return "Response Reject"; + } + + return ""; +} + +static const char *sigid2str(uint8_t sigid) +{ + switch (sigid) { + case AVDTP_DISCOVER: + return "Discover"; + case AVDTP_GET_CAPABILITIES: + return "Get Capabilities"; + case AVDTP_SET_CONFIGURATION: + return "Set Configuration"; + case AVDTP_GET_CONFIGURATION: + return "Get Configuration"; + case AVDTP_RECONFIGURE: + return "Reconfigure"; + case AVDTP_OPEN: + return "Open"; + case AVDTP_START: + return "Start"; + case AVDTP_CLOSE: + return "Close"; + case AVDTP_SUSPEND: + return "Suspend"; + case AVDTP_ABORT: + return "Abort"; + case AVDTP_SECURITY_CONTROL: + return "Security Control"; + case AVDTP_GET_ALL_CAPABILITIES: + return "Get All Capabilities"; + case AVDTP_DELAYREPORT: + return "Delay Report"; + default: + return "Reserved"; + } +} + +static const char *error2str(uint8_t error) +{ + switch (error) { + case 0x01: + return "BAD_HEADER_FORMAT"; + case 0x11: + return "BAD_LENGTH"; + case 0x12: + return "BAD_ACP_SEID"; + case 0x13: + return "SEP_IN_USE"; + case 0x14: + return "SEP_NOT_IN_USER"; + case 0x17: + return "BAD_SERV_CATEGORY"; + case 0x18: + return "BAD_PAYLOAD_FORMAT"; + case 0x19: + return "NOT_SUPPORTED_COMMAND"; + case 0x1a: + return "INVALID_CAPABILITIES"; + case 0x22: + return "BAD_RECOVERY_TYPE"; + case 0x23: + return "BAD_MEDIA_TRANSPORT_FORMAT"; + case 0x25: + return "BAD_RECOVERY_FORMAT"; + case 0x26: + return "BAD_ROHC_FORMAT"; + case 0x27: + return "BAD_CP_FORMAT"; + case 0x28: + return "BAD_MULTIPLEXING_FORMAT"; + case 0x29: + return "UNSUPPORTED_CONFIGURATION"; + case 0x31: + return "BAD_STATE"; + default: + return "Unknown"; + } +} + +static const char *mediatype2str(uint8_t media_type) +{ + switch (media_type) { + case 0x00: + return "Audio"; + case 0x01: + return "Video"; + case 0x02: + return "Multimedia"; + default: + return "Reserved"; + } +} + +static const char *mediacodec2str(uint8_t codec) +{ + switch (codec) { + case 0x00: + return "SBC"; + case 0x01: + return "MPEG-1,2 Audio"; + case 0x02: + return "MPEG-2,4 AAC"; + case 0x04: + return "ATRAC Family"; + case 0xff: + return "Non-A2DP"; + default: + return "Reserved"; + } +} + +static const char *cptype2str(uint8_t cp) +{ + switch (cp) { + case 0x0001: + return "DTCP"; + case 0x0002: + return "SCMS-T"; + default: + return "Reserved"; + } +} + +static const char *servicecat2str(uint8_t service_cat) +{ + switch (service_cat) { + case AVDTP_MEDIA_TRANSPORT: + return "Media Transport"; + case AVDTP_REPORTING: + return "Reporting"; + case AVDTP_RECOVERY: + return "Recovery"; + case AVDTP_CONTENT_PROTECTION: + return "Content Protection"; + case AVDTP_HEADER_COMPRESSION: + return "Header Compression"; + case AVDTP_MULTIPLEXING: + return "Multiplexing"; + case AVDTP_MEDIA_CODEC: + return "Media Codec"; + case AVDTP_DELAY_REPORTING: + return "Delay Reporting"; + default: + return "Reserved"; + } +} + +static bool avdtp_reject_common(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t error; + + if (!l2cap_frame_get_u8(frame, &error)) + return false; + + print_field("Error code: %s (0x%02x)", error2str(error), error); + + return true; +} + +static bool service_content_protection(struct avdtp_frame *avdtp_frame, + uint8_t losc) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint16_t type = 0; + + if (losc < 2) + return false; + + if (!l2cap_frame_get_le16(frame, &type)) + return false; + + losc -= 2; + + print_field("%*cContent Protection Type: %s (0x%04x)", 2, ' ', + cptype2str(type), type); + + /* TODO: decode protection specific information */ + packet_hexdump(frame->data, losc); + + l2cap_frame_pull(frame, frame, losc); + + return true; +} + +static bool service_media_codec(struct avdtp_frame *avdtp_frame, uint8_t losc) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = 0; + uint8_t codec = 0; + + if (losc < 2) + return false; + + l2cap_frame_get_u8(frame, &type); + l2cap_frame_get_u8(frame, &codec); + + losc -= 2; + + print_field("%*cMedia Type: %s (0x%02x)", 2, ' ', + mediatype2str(type >> 4), type >> 4); + + print_field("%*cMedia Codec: %s (0x%02x)", 2, ' ', + mediacodec2str(codec), codec); + + if (is_configuration_sig_id(avdtp_frame->sig_id)) + return a2dp_codec_cfg(codec, losc, frame); + else + return a2dp_codec_cap(codec, losc, frame); +} + +static bool decode_capabilities(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t service_cat; + uint8_t losc; + + while (l2cap_frame_get_u8(frame, &service_cat)) { + print_field("Service Category: %s (0x%02x)", + servicecat2str(service_cat), service_cat); + + if (!l2cap_frame_get_u8(frame, &losc)) + return false; + + if (frame->size < losc) + return false; + + switch (service_cat) { + case AVDTP_CONTENT_PROTECTION: + if (!service_content_protection(avdtp_frame, losc)) + return false; + break; + case AVDTP_MEDIA_CODEC: + if (!service_media_codec(avdtp_frame, losc)) + return false; + break; + case AVDTP_MEDIA_TRANSPORT: + case AVDTP_REPORTING: + case AVDTP_RECOVERY: + case AVDTP_HEADER_COMPRESSION: + case AVDTP_MULTIPLEXING: + case AVDTP_DELAY_REPORTING: + default: + packet_hexdump(frame->data, losc); + l2cap_frame_pull(frame, frame, losc); + } + + } + + return true; +} + +static bool avdtp_discover(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + uint8_t info; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + return true; + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + while (l2cap_frame_get_u8(frame, &seid)) { + print_field("ACP SEID: %d", seid >> 2); + + if (!l2cap_frame_get_u8(frame, &info)) + return false; + + print_field("%*cMedia Type: %s (0x%02x)", 2, ' ', + mediatype2str(info >> 4), info >> 4); + print_field("%*cSEP Type: %s (0x%02x)", 2, ' ', + info & 0x08 ? "SNK" : "SRC", + (info >> 3) & 0x01); + print_field("%*cIn use: %s", 2, ' ', + seid & 0x02 ? "Yes" : "No"); + } + return true; + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_get_capabilities(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + return true; + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + return decode_capabilities(avdtp_frame); + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_set_configuration(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t acp_seid, int_seid; + uint8_t service_cat; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &acp_seid)) + return false; + + print_field("ACP SEID: %d", acp_seid >> 2); + + if (!l2cap_frame_get_u8(frame, &int_seid)) + return false; + + print_field("INT SEID: %d", int_seid >> 2); + + return decode_capabilities(avdtp_frame); + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + return true; + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + if (!l2cap_frame_get_u8(frame, &service_cat)) + return false; + + print_field("Service Category: %s (0x%02x)", + servicecat2str(service_cat), service_cat); + + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_get_configuration(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + return true; + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + return decode_capabilities(avdtp_frame); + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_reconfigure(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + uint8_t service_cat; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + return decode_capabilities(avdtp_frame); + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + return true; + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + if (!l2cap_frame_get_u8(frame, &service_cat)) + return false; + + print_field("Service Category: %s (0x%02x)", + servicecat2str(service_cat), service_cat); + + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_open(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + return true; + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + return true; + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_start(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + while (l2cap_frame_get_u8(frame, &seid)) + print_field("ACP SEID: %d", seid >> 2); + + return true; + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + return true; + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_close(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + return true; + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + return true; + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_suspend(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + while (l2cap_frame_get_u8(frame, &seid)) + print_field("ACP SEID: %d", seid >> 2); + + return true; + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + return true; + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_abort(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + return true; + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + return true; + } + + return false; +} + +static bool avdtp_security_control(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + /* TODO: decode more information */ + packet_hexdump(frame->data, frame->size); + return true; + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + /* TODO: decode more information */ + packet_hexdump(frame->data, frame->size); + return true; + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_delayreport(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + uint8_t type = avdtp_frame->hdr & 0x03; + uint8_t seid; + uint16_t delay; + + switch (type) { + case AVDTP_MSG_TYPE_COMMAND: + if (!l2cap_frame_get_u8(frame, &seid)) + return false; + + print_field("ACP SEID: %d", seid >> 2); + + if (!l2cap_frame_get_be16(frame, &delay)) + return false; + + print_field("Delay: %d.%dms", delay / 10, delay % 10); + + return true; + case AVDTP_MSG_TYPE_RESPONSE_ACCEPT: + return true; + case AVDTP_MSG_TYPE_RESPONSE_REJECT: + return avdtp_reject_common(avdtp_frame); + } + + return false; +} + +static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame) +{ + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame; + const char *pdu_color; + uint8_t hdr; + uint8_t sig_id; + uint8_t nosp = 0; + + if (frame->in) + pdu_color = COLOR_MAGENTA; + else + pdu_color = COLOR_BLUE; + + if (!l2cap_frame_get_u8(frame, &hdr)) + return false; + + avdtp_frame->hdr = hdr; + + /* Continue Packet || End Packet */ + if (((hdr & 0x0c) == 0x08) || ((hdr & 0x0c) == 0x0c)) { + /* TODO: handle fragmentation */ + packet_hexdump(frame->data, frame->size); + return true; + } + + /* Start Packet */ + if ((hdr & 0x0c) == 0x04) { + if (!l2cap_frame_get_u8(frame, &nosp)) + return false; + } + + if (!l2cap_frame_get_u8(frame, &sig_id)) + return false; + + sig_id &= 0x3f; + + avdtp_frame->sig_id = sig_id; + + print_indent(6, pdu_color, "AVDTP: ", sigid2str(sig_id), COLOR_OFF, + " (0x%02x) %s (0x%02x) type 0x%02x label %d nosp %d", + sig_id, msgtype2str(hdr & 0x03), hdr & 0x03, + hdr & 0x0c, hdr >> 4, nosp); + + /* Start Packet */ + if ((hdr & 0x0c) == 0x04) { + /* TODO: handle fragmentation */ + packet_hexdump(frame->data, frame->size); + return true; + } + + /* General Reject */ + if ((hdr & 0x03) == 0x03) + return true; + + switch (sig_id) { + case AVDTP_DISCOVER: + return avdtp_discover(avdtp_frame); + case AVDTP_GET_CAPABILITIES: + case AVDTP_GET_ALL_CAPABILITIES: + return avdtp_get_capabilities(avdtp_frame); + case AVDTP_SET_CONFIGURATION: + return avdtp_set_configuration(avdtp_frame); + case AVDTP_GET_CONFIGURATION: + return avdtp_get_configuration(avdtp_frame); + case AVDTP_RECONFIGURE: + return avdtp_reconfigure(avdtp_frame); + case AVDTP_OPEN: + return avdtp_open(avdtp_frame); + case AVDTP_START: + return avdtp_start(avdtp_frame); + case AVDTP_CLOSE: + return avdtp_close(avdtp_frame); + case AVDTP_SUSPEND: + return avdtp_suspend(avdtp_frame); + case AVDTP_ABORT: + return avdtp_abort(avdtp_frame); + case AVDTP_SECURITY_CONTROL: + return avdtp_security_control(avdtp_frame); + case AVDTP_DELAYREPORT: + return avdtp_delayreport(avdtp_frame); + } + + packet_hexdump(frame->data, frame->size); + + return true; +} + +void avdtp_packet(const struct l2cap_frame *frame) +{ + struct avdtp_frame avdtp_frame; + bool ret; + + l2cap_frame_pull(&avdtp_frame.l2cap_frame, frame, 0); + + switch (frame->seq_num) { + case 1: + ret = avdtp_signalling_packet(&avdtp_frame); + break; + default: + if (packet_has_filter(PACKET_FILTER_SHOW_A2DP_STREAM)) + packet_hexdump(frame->data, frame->size); + return; + } + + if (!ret) { + print_text(COLOR_ERROR, "PDU malformed"); + packet_hexdump(frame->data, frame->size); + } +} diff --git a/monitor/avdtp.h b/monitor/avdtp.h new file mode 100644 index 0000000..f77d82e --- /dev/null +++ b/monitor/avdtp.h @@ -0,0 +1,24 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Andrzej Kaczmarek + * + * + * 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 + * + */ + +void avdtp_packet(const struct l2cap_frame *frame); diff --git a/monitor/bnep.c b/monitor/bnep.c new file mode 100644 index 0000000..6af54e0 --- /dev/null +++ b/monitor/bnep.c @@ -0,0 +1,483 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "bt.h" +#include "packet.h" +#include "display.h" +#include "l2cap.h" +#include "keys.h" +#include "sdp.h" +#include "bnep.h" + +#define GET_PKT_TYPE(type) (type & 0x7f) +#define GET_EXTENSION(type) (type & 0x80) + +/* BNEP Extension Type */ +#define BNEP_EXTENSION_CONTROL 0x00 + +#define BNEP_CONTROL 0x01 + +uint16_t proto = 0x0000; + +struct bnep_frame { + uint8_t type; + int extension; + struct l2cap_frame l2cap_frame; +}; + +static bool get_macaddr(struct bnep_frame *bnep_frame, char *str) +{ + uint8_t addr[6]; + struct l2cap_frame *frame = &bnep_frame->l2cap_frame; + int i; + + for (i = 0; i < 6; i++) + if (!l2cap_frame_get_u8(frame, &addr[i])) + return false; + + sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + + return true; +} + +static bool bnep_general(struct bnep_frame *bnep_frame, + uint8_t indent, int hdr_len) +{ + struct l2cap_frame *frame; + char src_addr[20], dest_addr[20]; + + if (!get_macaddr(bnep_frame, dest_addr)) + return false; + + if (!get_macaddr(bnep_frame, src_addr)) + return false; + + frame = &bnep_frame->l2cap_frame; + + if (!l2cap_frame_get_be16(frame, &proto)) + return false; + + print_field("%*cdst %s src %s [proto 0x%04x] ", indent, + ' ', dest_addr, src_addr, proto); + + return true; + +} + +static bool cmd_nt_understood(struct bnep_frame *bnep_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &bnep_frame->l2cap_frame; + uint8_t ptype; + + if (!l2cap_frame_get_u8(frame, &ptype)) + return false; + + print_field("%*cType: 0x%02x ", indent, ' ', ptype); + + return true; +} + +static bool setup_conn_req(struct bnep_frame *bnep_frame, uint8_t indent) +{ + + struct l2cap_frame *frame = &bnep_frame->l2cap_frame; + uint8_t uuid_size; + uint32_t src_uuid = 0, dst_uuid = 0; + + if (!l2cap_frame_get_u8(frame, &uuid_size)) + return false; + + print_field("%*cSize: 0x%02x ", indent, ' ', uuid_size); + + switch (uuid_size) { + case 2: + if (!l2cap_frame_get_be16(frame, (uint16_t *) &dst_uuid)) + return false; + + if (!l2cap_frame_get_be16(frame, (uint16_t *) &src_uuid)) + return false; + break; + case 4: + if (!l2cap_frame_get_be32(frame, &dst_uuid)) + return false; + + if (!l2cap_frame_get_be32(frame, &src_uuid)) + return false; + break; + case 16: + if (!l2cap_frame_get_be32(frame, &dst_uuid)) + return false; + + l2cap_frame_pull(frame, frame, 12); + + if (!l2cap_frame_get_be32(frame, &src_uuid)) + return false; + + l2cap_frame_pull(frame, frame, 12); + break; + default: + l2cap_frame_pull(frame, frame, (uuid_size * 2)); + return true; + } + + print_field("%*cDst: 0x%x(%s)", indent, ' ', dst_uuid, + bt_uuid32_to_str(dst_uuid)); + print_field("%*cSrc: 0x%x(%s)", indent, ' ', src_uuid, + bt_uuid32_to_str(src_uuid)); + return true; +} + +static const char *value2str(uint16_t value) +{ + switch (value) { + case 0x00: + return "Operation Successful"; + case 0x01: + return "Operation Failed - Invalid Dst Srv UUID"; + case 0x02: + return "Operation Failed - Invalid Src Srv UUID"; + case 0x03: + return "Operation Failed - Invalid Srv UUID size"; + case 0x04: + return "Operation Failed - Conn not allowed"; + default: + return "Unknown"; + } +} + +static bool print_rsp_msg(struct bnep_frame *bnep_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &bnep_frame->l2cap_frame; + uint16_t rsp_msg; + + if (!l2cap_frame_get_be16(frame, &rsp_msg)) + return false; + + print_field("%*cRsp msg: %s(0x%04x) ", indent, ' ', + value2str(rsp_msg), rsp_msg); + + return true; +} + +static bool filter_nettype_req(struct bnep_frame *bnep_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &bnep_frame->l2cap_frame; + uint16_t length, start_range, end_range; + int i; + + if (!l2cap_frame_get_be16(frame, &length)) + return false; + + print_field("%*cLength: 0x%04x", indent, ' ', length); + + for (i = 0; i < length / 4; i++) { + + if (!l2cap_frame_get_be16(frame, &start_range)) + return false; + + if (!l2cap_frame_get_be16(frame, &end_range)) + return false; + + print_field("%*c0x%04x - 0x%04x", indent, ' ', + start_range, end_range); + } + + return true; +} + +static bool filter_multaddr_req(struct bnep_frame *bnep_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &bnep_frame->l2cap_frame; + uint16_t length; + char start_addr[20], end_addr[20]; + int i; + + if (!l2cap_frame_get_be16(frame, &length)) + return false; + + print_field("%*cLength: 0x%04x", indent, ' ', length); + + for (i = 0; i < length / 12; i++) { + + if (!get_macaddr(bnep_frame, start_addr)) + return false; + + if (!get_macaddr(bnep_frame, end_addr)) + return false; + + print_field("%*c%s - %s", indent, ' ', start_addr, end_addr); + } + + return true; +} + +struct bnep_control_data { + uint8_t type; + const char *str; + bool (*func) (struct bnep_frame *frame, uint8_t indent); +}; + +static const struct bnep_control_data bnep_control_table[] = { + { 0x00, "Command Not Understood", cmd_nt_understood }, + { 0x01, "Setup Conn Req", setup_conn_req }, + { 0x02, "Setup Conn Rsp", print_rsp_msg }, + { 0x03, "Filter NetType Set", filter_nettype_req }, + { 0x04, "Filter NetType Rsp", print_rsp_msg }, + { 0x05, "Filter MultAddr Set", filter_multaddr_req }, + { 0x06, "Filter MultAddr Rsp", print_rsp_msg }, + { } +}; + +static bool bnep_control(struct bnep_frame *bnep_frame, + uint8_t indent, int hdr_len) +{ + uint8_t ctype; + struct l2cap_frame *frame = &bnep_frame->l2cap_frame; + const struct bnep_control_data *bnep_control_data = NULL; + const char *type_str; + int i; + + if (!l2cap_frame_get_u8(frame, &ctype)) + return false; + + for (i = 0; bnep_control_table[i].str; i++) { + if (bnep_control_table[i].type == ctype) { + bnep_control_data = &bnep_control_table[i]; + break; + } + } + + if (bnep_control_data) + type_str = bnep_control_data->str; + else + type_str = "Unknown control type"; + + print_field("%*c%s (0x%02x) ", indent, ' ', type_str, ctype); + + if (!bnep_control_data || !bnep_control_data->func) { + packet_hexdump(frame->data, hdr_len - 1); + l2cap_frame_pull(frame, frame, hdr_len - 1); + goto done; + } + + if (!bnep_control_data->func(bnep_frame, indent+2)) + return false; + +done: + return true; +} + +static bool bnep_compressed(struct bnep_frame *bnep_frame, + uint8_t indent, int hdr_len) +{ + + struct l2cap_frame *frame = &bnep_frame->l2cap_frame; + + if (!l2cap_frame_get_be16(frame, &proto)) + return false; + + print_field("%*c[proto 0x%04x] ", indent, ' ', proto); + + return true; +} + +static bool bnep_src_only(struct bnep_frame *bnep_frame, + uint8_t indent, int hdr_len) +{ + + struct l2cap_frame *frame; + char src_addr[20]; + + if (!get_macaddr(bnep_frame, src_addr)) + return false; + + frame = &bnep_frame->l2cap_frame; + + if (!l2cap_frame_get_be16(frame, &proto)) + return false; + + print_field("%*csrc %s [proto 0x%04x] ", indent, + ' ', src_addr, proto); + + return true; +} + +static bool bnep_dst_only(struct bnep_frame *bnep_frame, + uint8_t indent, int hdr_len) +{ + + struct l2cap_frame *frame; + char dest_addr[20]; + + if (!get_macaddr(bnep_frame, dest_addr)) + return false; + + frame = &bnep_frame->l2cap_frame; + + if (!l2cap_frame_get_be16(frame, &proto)) + return false; + + print_field("%*cdst %s [proto 0x%04x] ", indent, + ' ', dest_addr, proto); + + return true; +} + +static bool bnep_eval_extension(struct bnep_frame *bnep_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &bnep_frame->l2cap_frame; + uint8_t type, length; + int extension; + + if (!l2cap_frame_get_u8(frame, &type)) + return false; + + if (!l2cap_frame_get_u8(frame, &length)) + return false; + + extension = GET_EXTENSION(type); + type = GET_PKT_TYPE(type); + + switch (type) { + case BNEP_EXTENSION_CONTROL: + print_field("%*cExt Control(0x%02x|%s) len 0x%02x", indent, + ' ', type, extension ? "1" : "0", length); + if (!bnep_control(bnep_frame, indent+2, length)) + return false; + break; + + default: + print_field("%*cExt Unknown(0x%02x|%s) len 0x%02x", indent, + ' ', type, extension ? "1" : "0", length); + packet_hexdump(frame->data, length); + l2cap_frame_pull(frame, frame, length); + } + + if (extension) + if (!bnep_eval_extension(bnep_frame, indent)) + return false; + + return true; +} + +struct bnep_data { + uint8_t type; + const char *str; + bool (*func) (struct bnep_frame *frame, uint8_t indent, int hdr_len); +}; + +static const struct bnep_data bnep_table[] = { + { 0x00, "General Ethernet", bnep_general }, + { 0x01, "Control", bnep_control }, + { 0x02, "Compressed Ethernet", bnep_compressed }, + { 0x03, "Compressed Ethernet SrcOnly", bnep_src_only }, + { 0x04, "Compressed Ethernet DestOnly", bnep_dst_only }, + { } +}; + +void bnep_packet(const struct l2cap_frame *frame) +{ + uint8_t type, indent = 1; + struct bnep_frame bnep_frame; + struct l2cap_frame *l2cap_frame; + const struct bnep_data *bnep_data = NULL; + const char *pdu_color, *pdu_str; + int i; + + l2cap_frame_pull(&bnep_frame.l2cap_frame, frame, 0); + l2cap_frame = &bnep_frame.l2cap_frame; + + if (!l2cap_frame_get_u8(l2cap_frame, &type)) + goto fail; + + bnep_frame.extension = GET_EXTENSION(type); + bnep_frame.type = GET_PKT_TYPE(type); + + for (i = 0; bnep_table[i].str; i++) { + if (bnep_table[i].type == bnep_frame.type) { + bnep_data = &bnep_table[i]; + break; + } + } + + if (bnep_data) { + if (bnep_data->func) { + if (frame->in) + pdu_color = COLOR_MAGENTA; + else + pdu_color = COLOR_BLUE; + } else + pdu_color = COLOR_WHITE_BG; + pdu_str = bnep_data->str; + } else { + pdu_color = COLOR_WHITE_BG; + pdu_str = "Unknown packet type"; + } + + print_indent(6, pdu_color, "BNEP: ", pdu_str, COLOR_OFF, + " (0x%02x|%s)", bnep_frame.type, + bnep_frame.extension ? "1" : "0"); + + if (!bnep_data || !bnep_data->func) { + packet_hexdump(l2cap_frame->data, l2cap_frame->size); + return; + } + + if (!bnep_data->func(&bnep_frame, indent, -1)) + goto fail; + + /* Extension info */ + if (bnep_frame.extension) + if (!bnep_eval_extension(&bnep_frame, indent+2)) + goto fail; + + /* Control packet => No payload info */ + if (bnep_frame.type == BNEP_CONTROL) + return; + + /* TODO: Handle BNEP IP packet */ + packet_hexdump(l2cap_frame->data, l2cap_frame->size); + + return; + +fail: + print_text(COLOR_ERROR, "frame too short"); + packet_hexdump(frame->data, frame->size); +} diff --git a/monitor/bnep.h b/monitor/bnep.h new file mode 100644 index 0000000..38340d6 --- /dev/null +++ b/monitor/bnep.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void bnep_packet(const struct l2cap_frame *frame); diff --git a/monitor/broadcom.c b/monitor/broadcom.c new file mode 100644 index 0000000..787ff29 --- /dev/null +++ b/monitor/broadcom.c @@ -0,0 +1,724 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include + +#include "src/shared/util.h" +#include "display.h" +#include "packet.h" +#include "lmp.h" +#include "ll.h" +#include "vendor.h" +#include "broadcom.h" + +#define COLOR_UNKNOWN_FEATURE_BIT COLOR_WHITE_BG + +static void print_status(uint8_t status) +{ + packet_print_error("Status", status); +} + +static void print_handle(uint16_t handle) +{ + packet_print_handle(handle); +} + +static void print_rssi(int8_t rssi) +{ + packet_print_rssi(rssi); +} + +static void print_sco_routing(uint8_t routing) +{ + const char *str; + + switch (routing) { + case 0x00: + str = "PCM"; + break; + case 0x01: + str = "Transport"; + break; + case 0x02: + str = "Codec"; + break; + case 0x03: + str = "I2S"; + break; + default: + str = "Reserved"; + break; + } + + print_field("SCO routing: %s (0x%2.2x)", str, routing); +} + +static void print_pcm_interface_rate(uint8_t rate) +{ + const char *str; + + switch (rate) { + case 0x00: + str = "128 KBps"; + break; + case 0x01: + str = "256 KBps"; + break; + case 0x02: + str = "512 KBps"; + break; + case 0x03: + str = "1024 KBps"; + break; + case 0x04: + str = "2048 KBps"; + break; + default: + str = "Reserved"; + break; + } + + print_field("PCM interface rate: %s (0x%2.2x)", str, rate); +} + +static void print_frame_type(uint8_t type) +{ + const char *str; + + switch (type) { + case 0x00: + str = "Short"; + break; + case 0x01: + str = "Long"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Frame type: %s (0x%2.2x)", str, type); +} + +static void print_sync_mode(uint8_t mode) +{ + const char *str; + + switch (mode) { + case 0x00: + str = "Slave"; + break; + case 0x01: + str = "Master"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Sync mode: %s (0x%2.2x)", str, mode); +} + +static void print_clock_mode(uint8_t mode) +{ + const char *str; + + switch (mode) { + case 0x00: + str = "Slave"; + break; + case 0x01: + str = "Master"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Clock mode: %s (0x%2.2x)", str, mode); +} + +static void print_sleep_mode(uint8_t mode) +{ + const char *str; + + switch (mode) { + case 0x00: + str = "No sleep mode"; + break; + case 0x01: + str = "UART"; + break; + case 0x02: + str = "UART with messaging"; + break; + case 0x03: + str = "USB"; + break; + case 0x04: + str = "H4IBSS"; + break; + case 0x05: + str = "USB with Host wake"; + break; + case 0x06: + str = "SDIO"; + break; + case 0x07: + str = "UART CS-N"; + break; + case 0x08: + str = "SPI"; + break; + case 0x09: + str = "H5"; + break; + case 0x0a: + str = "H4DS"; + break; + case 0x0c: + str = "UART with BREAK"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Sleep mode: %s (0x%2.2x)", str, mode); +} + +static void print_clock_setting(uint8_t clock) +{ + const char *str; + + switch (clock) { + case 0x01: + str = "48 Mhz"; + break; + case 0x02: + str = "24 Mhz"; + break; + default: + str = "Reserved"; + break; + } + + print_field("UART clock: %s (0x%2.2x)", str, clock); +} + +static void null_cmd(const void *data, uint8_t size) +{ +} + +static void status_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + + print_status(status); +} + +static void write_bd_addr_cmd(const void *data, uint8_t size) +{ + packet_print_addr("Address", data, false); +} + +static void update_uart_baud_rate_cmd(const void *data, uint8_t size) +{ + uint16_t enc_rate = get_le16(data); + uint32_t exp_rate = get_le32(data + 2); + + if (enc_rate == 0x0000) + print_field("Encoded baud rate: Not used (0x0000)"); + else + print_field("Encoded baud rate: 0x%4.4x", enc_rate); + + print_field("Explicit baud rate: %u Mbps", exp_rate); +} + +static void write_sco_pcm_int_param_cmd(const void *data, uint8_t size) +{ + uint8_t routing = get_u8(data); + uint8_t rate = get_u8(data + 1); + uint8_t frame_type = get_u8(data + 2); + uint8_t sync_mode = get_u8(data + 3); + uint8_t clock_mode = get_u8(data + 4); + + print_sco_routing(routing); + print_pcm_interface_rate(rate); + print_frame_type(frame_type); + print_sync_mode(sync_mode); + print_clock_mode(clock_mode); +} + +static void read_sco_pcm_int_param_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + uint8_t routing = get_u8(data + 1); + uint8_t rate = get_u8(data + 2); + uint8_t frame_type = get_u8(data + 3); + uint8_t sync_mode = get_u8(data + 4); + uint8_t clock_mode = get_u8(data + 5); + + print_status(status); + print_sco_routing(routing); + print_pcm_interface_rate(rate); + print_frame_type(frame_type); + print_sync_mode(sync_mode); + print_clock_mode(clock_mode); +} + +static void set_sleepmode_param_cmd(const void *data, uint8_t size) +{ + uint8_t mode = get_u8(data); + + print_sleep_mode(mode); + + packet_hexdump(data + 1, size - 1); +} + +static void read_sleepmode_param_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + uint8_t mode = get_u8(data + 1); + + print_status(status); + print_sleep_mode(mode); + + packet_hexdump(data + 2, size - 2); +} + +static void enable_radio_cmd(const void *data, uint8_t size) +{ + uint8_t mode = get_u8(data); + const char *str; + + switch (mode) { + case 0x00: + str = "Disable the radio"; + break; + case 0x01: + str = "Enable the radio"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Mode: %s (0x%2.2x)", str, mode); +} + +static void enable_usb_hid_emulation_cmd(const void *data, uint8_t size) +{ + uint8_t enable = get_u8(data); + const char *str; + + switch (enable) { + case 0x00: + str = "Bluetooth mode"; + break; + case 0x01: + str = "HID Mode"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Enable: %s (0x%2.2x)", str, enable); +} + +static void read_uart_clock_setting_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + uint8_t clock = get_u8(data + 1); + + print_status(status); + print_clock_setting(clock); +} + +static void write_uart_clock_setting_cmd(const void *data, uint8_t size) +{ + uint8_t clock = get_u8(data); + + print_clock_setting(clock); +} + +static void read_raw_rssi_cmd(const void *data, uint8_t size) +{ + uint16_t handle = get_le16(data); + + print_handle(handle); +} + +static void read_raw_rssi_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + uint16_t handle = get_le16(data + 1); + int8_t rssi = get_s8(data + 3); + + print_status(status); + print_handle(handle); + print_rssi(rssi); +} + +static void write_ram_cmd(const void *data, uint8_t size) +{ + uint32_t addr = get_le32(data); + + print_field("Address: 0x%8.8x", addr); + + packet_hexdump(data + 4, size - 4); +} + +static void read_ram_cmd(const void *data, uint8_t size) +{ + uint32_t addr = get_le32(data); + uint8_t length = get_u8(data + 4); + + print_field("Address: 0x%8.8x", addr); + print_field("Length: %u", length); +} + +static void read_ram_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + + print_status(status); + + packet_hexdump(data + 1, size - 1); +} + +static void launch_ram_cmd(const void *data, uint8_t size) +{ + uint32_t addr = get_le32(data); + + print_field("Address: 0x%8.8x", addr); +} + +static void read_vid_pid_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + uint16_t vid = get_le16(data + 1); + uint16_t pid = get_le16(data + 3); + + print_status(status); + print_field("Product: %4.4x:%4.4x", vid, pid); +} + +static void write_high_priority_connection_cmd(const void *data, uint8_t size) +{ + uint16_t handle = get_le16(data); + uint8_t priority = get_u8(data + 2); + const char *str; + + print_handle(handle); + + switch (priority) { + case 0x00: + str = "Low"; + break; + case 0x01: + str = "High"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Priority: %s (0x%2.2x)", str, priority); +} + +static const struct { + uint8_t bit; + const char *str; +} features_table[] = { + { 0, "Multi-AV transport bandwidth reducer" }, + { 1, "WBS SBC" }, + { 2, "FW LC-PLC" }, + { 3, "FM SBC internal stack" }, + { } +}; + +static void print_features(const uint8_t *features_array) +{ + uint64_t mask, features = 0; + char str[41]; + int i; + + for (i = 0; i < 8; i++) { + sprintf(str + (i * 5), " 0x%2.2x", features_array[i]); + features |= ((uint64_t) features_array[i]) << (i * 8); + } + + print_field("Features:%s", str); + + mask = features; + + for (i = 0; features_table[i].str; i++) { + if (features & (((uint64_t) 1) << features_table[i].bit)) { + print_field(" %s", features_table[i].str); + mask &= ~(((uint64_t) 1) << features_table[i].bit); + } + } + + if (mask) + print_text(COLOR_UNKNOWN_FEATURE_BIT, " Unknown features " + "(0x%16.16" PRIx64 ")", mask); +} + +static void read_controller_features_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + + print_status(status); + print_features(data + 1); +} + +static void read_verbose_version_info_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + uint8_t chip_id = get_u8(data + 1); + uint8_t target_id = get_u8(data + 2); + uint16_t build_base = get_le16(data + 3); + uint16_t build_num = get_le16(data + 5); + const char *str; + + print_status(status); + print_field("Chip ID: %u (0x%2.2x)", chip_id, chip_id); + + switch (target_id) { + case 254: + str = "Invalid"; + break; + case 255: + str = "Undefined"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Build target: %s (%u)", str, target_id); + print_field("Build baseline: %u (0x%4.4x)", build_base, build_base); + print_field("Build number: %u (0x%4.4x)", build_num, build_num); +} + +static void enable_wbs_cmd(const void *data, uint8_t size) +{ + uint8_t mode = get_u8(data); + uint16_t codec = get_le16(data + 1); + const char *str; + + switch (mode) { + case 0x00: + str = "Disable WBS"; + break; + case 0x01: + str = "Enable WBS"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Mode: %s (0x%2.2x)", str, mode); + + switch (codec) { + case 0x0000: + str = "None"; + break; + case 0x0001: + str = "CVSD"; + break; + case 0x0002: + str = "mSBC"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Codec: %s (0x%4.4x)", str, codec); +} + +static const struct vendor_ocf vendor_ocf_table[] = { + { 0x001, "Write BD ADDR", + write_bd_addr_cmd, 6, true, + status_rsp, 1, true }, + { 0x018, "Update UART Baud Rate", + update_uart_baud_rate_cmd, 6, true, + status_rsp, 1, true }, + { 0x01c, "Write SCO PCM Int Param", + write_sco_pcm_int_param_cmd, 5, true, + status_rsp, 1, true }, + { 0x01d, "Read SCO PCM Int Param", + null_cmd, 0, true, + read_sco_pcm_int_param_rsp, 6, true }, + { 0x027, "Set Sleepmode Param", + set_sleepmode_param_cmd, 12, true, + status_rsp, 1, true }, + { 0x028, "Read Sleepmode Param", + null_cmd, 0, true, + read_sleepmode_param_rsp, 13, true }, + { 0x02e, "Download Minidriver", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x034, "Enable Radio", + enable_radio_cmd, 1, true, + status_rsp, 1, true }, + { 0x03b, "Enable USB HID Emulation", + enable_usb_hid_emulation_cmd, 1, true, + status_rsp, 1, true }, + { 0x044, "Read UART Clock Setting", + null_cmd, 0, true, + read_uart_clock_setting_rsp, 1, true }, + { 0x045, "Write UART Clock Setting", + write_uart_clock_setting_cmd, 1, true, + status_rsp, 1, true }, + { 0x048, "Read Raw RSSI", + read_raw_rssi_cmd, 2, true, + read_raw_rssi_rsp, 4, true }, + { 0x04c, "Write RAM", + write_ram_cmd, 4, false, + status_rsp, 1, true }, + { 0x04d, "Read RAM", + read_ram_cmd, 5, true, + read_ram_rsp, 1, false }, + { 0x04e, "Launch RAM", + launch_ram_cmd, 4, true, + status_rsp, 1, true }, + { 0x05a, "Read VID PID", + null_cmd, 0, true, + read_vid_pid_rsp, 5, true }, + { 0x057, "Write High Priority Connection", + write_high_priority_connection_cmd, 3, true, + status_rsp, 1, true }, + { 0x06d, "Write I2SPCM Interface Param" }, + { 0x06e, "Read Controller Features", + null_cmd, 0, true, + read_controller_features_rsp, 9, true }, + { 0x079, "Read Verbose Config Version Info", + null_cmd, 0, true, + read_verbose_version_info_rsp, 7, true }, + { 0x07e, "Enable WBS", + enable_wbs_cmd, 3, true, + status_rsp, 1, true }, + { } +}; + +const struct vendor_ocf *broadcom_vendor_ocf(uint16_t ocf) +{ + int i; + + for (i = 0; vendor_ocf_table[i].str; i++) { + if (vendor_ocf_table[i].ocf == ocf) + return &vendor_ocf_table[i]; + } + + return NULL; +} + +void broadcom_lm_diag(const void *data, uint8_t size) +{ + uint8_t type; + uint32_t clock; + const uint8_t *addr; + const char *str; + + if (size != 63) { + packet_hexdump(data, size); + return; + } + + type = *((uint8_t *) data); + clock = get_be32(data + 1); + + switch (type) { + case 0x00: + str = "LMP sent"; + break; + case 0x01: + str = "LMP receive"; + break; + case 0x80: + str = "LL sent"; + break; + case 0x81: + str = "LL receive"; + break; + default: + str = "Unknown"; + break; + } + + print_field("Type: %s (%u)", str, type); + print_field("Clock: 0x%8.8x", clock); + + switch (type) { + case 0x00: + addr = data + 5; + print_field("Address: --:--:%2.2X:%2.2X:%2.2X:%2.2X", + addr[0], addr[1], addr[2], addr[3]); + packet_hexdump(data + 9, 1); + lmp_packet(data + 10, size - 10, true); + break; + case 0x01: + addr = data + 5; + print_field("Address: --:--:%2.2X:%2.2X:%2.2X:%2.2X", + addr[0], addr[1], addr[2], addr[3]); + packet_hexdump(data + 9, 4); + lmp_packet(data + 13, size - 13, true); + break; + case 0x80: + case 0x81: + packet_hexdump(data + 5, 7); + llcp_packet(data + 12, size - 12, true); + break; + default: + packet_hexdump(data + 9, size - 9); + break; + } +} + +static const struct vendor_evt vendor_evt_table[] = { + { } +}; + +const struct vendor_evt *broadcom_vendor_evt(uint8_t evt) +{ + int i; + + for (i = 0; vendor_evt_table[i].str; i++) { + if (vendor_evt_table[i].evt == evt) + return &vendor_evt_table[i]; + } + + return NULL; +} diff --git a/monitor/broadcom.h b/monitor/broadcom.h new file mode 100644 index 0000000..ceda0e1 --- /dev/null +++ b/monitor/broadcom.h @@ -0,0 +1,32 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +struct vendor_ocf; +struct vendor_evt; + +const struct vendor_ocf *broadcom_vendor_ocf(uint16_t ocf); +const struct vendor_evt *broadcom_vendor_evt(uint8_t evt); +void broadcom_lm_diag(const void *data, uint8_t size); diff --git a/monitor/bt.h b/monitor/bt.h new file mode 100644 index 0000000..8edc895 --- /dev/null +++ b/monitor/bt.h @@ -0,0 +1,3540 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +struct bt_ll_hdr { + uint8_t preamble; + uint32_t access_addr; +} __attribute__ ((packed)); + +#define BT_LL_CONN_UPDATE_REQ 0x00 +struct bt_ll_conn_update_req { + uint8_t win_size; + uint16_t win_offset; + uint16_t interval; + uint16_t latency; + uint16_t timeout; + uint16_t instant; +} __attribute__ ((packed)); + +#define BT_LL_CHANNEL_MAP_REQ 0x01 +struct bt_ll_channel_map_req { + uint8_t map[5]; + uint16_t instant; +} __attribute__ ((packed)); + +#define BT_LL_TERMINATE_IND 0x02 +struct bt_ll_terminate_ind { + uint8_t error; +} __attribute__ ((packed)); + +#define BT_LL_ENC_REQ 0x03 +struct bt_ll_enc_req { + uint64_t rand; + uint16_t ediv; + uint64_t skd; + uint32_t iv; +} __attribute__ ((packed)); + +#define BT_LL_ENC_RSP 0x04 +struct bt_ll_enc_rsp { + uint64_t skd; + uint32_t iv; +} __attribute__ ((packed)); + +#define BT_LL_START_ENC_REQ 0x05 + +#define BT_LL_START_ENC_RSP 0x06 + +#define BT_LL_UNKNOWN_RSP 0x07 +struct bt_ll_unknown_rsp { + uint8_t type; +} __attribute__ ((packed)); + +#define BT_LL_FEATURE_REQ 0x08 +struct bt_ll_feature_req { + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_LL_FEATURE_RSP 0x09 +struct bt_ll_feature_rsp { + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_LL_PAUSE_ENC_REQ 0x0a + +#define BT_LL_PAUSE_ENC_RSP 0x0b + +#define BT_LL_VERSION_IND 0x0c +struct bt_ll_version_ind { + uint8_t version; + uint16_t company; + uint16_t subversion; +} __attribute__ ((packed)); + +#define BT_LL_REJECT_IND 0x0d +struct bt_ll_reject_ind { + uint8_t error; +} __attribute__ ((packed)); + +#define BT_LL_SLAVE_FEATURE_REQ 0x0e +struct bt_ll_slave_feature_req { + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_LL_CONN_PARAM_REQ 0x0f + +#define BT_LL_CONN_PARAM_RSP 0x10 + +#define BT_LL_REJECT_IND_EXT 0x11 +struct bt_ll_reject_ind_ext { + uint8_t opcode; + uint8_t error; +} __attribute__ ((packed)); + +#define BT_LL_PING_REQ 0x12 + +#define BT_LL_PING_RSP 0x13 + +#define BT_LL_LENGTH_REQ 0x14 +struct bt_ll_length { + uint16_t rx_len; + uint16_t rx_time; + uint16_t tx_len; + uint16_t tx_time; +} __attribute__ ((packed)); + +#define BT_LL_LENGTH_RSP 0x15 + +#define BT_LL_PHY_REQ 0x16 +struct bt_ll_phy { + uint8_t tx_phys; + uint8_t rx_phys; +} __attribute__ ((packed)); + +#define BT_LL_PHY_RSP 0x17 + +#define BT_LL_PHY_UPDATE_IND 0x18 +struct bt_ll_phy_update_ind { + uint8_t m_phy; + uint8_t s_phy; + uint16_t instant; +} __attribute__ ((packed)); + +#define BT_LL_MIN_USED_CHANNELS 0x19 +struct bt_ll_min_used_channels { + uint8_t phys; + uint8_t min_channels; +} __attribute__ ((packed)); + +#define BT_LL_CTE_REQ 0x1a +struct bt_ll_cte_req { + uint8_t cte; +} __attribute__ ((packed)); + +#define BT_LL_CTE_RSP 0x1b + +#define BT_LL_PERIODIC_SYNC_IND 0x1c +struct bt_ll_periodic_sync_ind { + uint16_t id; + uint8_t info[18]; + uint16_t event_count; + uint16_t last_counter; + uint8_t adv_info; + uint8_t phy; + uint8_t adv_addr[6]; + uint16_t sync_counter; +} __attribute__ ((packed)); + +#define BT_LL_CLOCK_ACCURACY_REQ 0x1d +struct bt_ll_clock_acc { + uint8_t sca; +} __attribute__ ((packed)); + +#define BT_LL_CLOCK_ACCURACY_RSP 0x1e + +#define LMP_ESC4(x) ((127 << 8) | (x)) + +#define BT_LMP_NAME_REQ 1 +struct bt_lmp_name_req { + uint8_t offset; +} __attribute__ ((packed)); + +#define BT_LMP_NAME_RSP 2 +struct bt_lmp_name_rsp { + uint8_t offset; + uint8_t length; + uint8_t fragment[14]; +} __attribute__ ((packed)); + +#define BT_LMP_ACCEPTED 3 +struct bt_lmp_accepted { + uint8_t opcode; +} __attribute__ ((packed)); + +#define BT_LMP_NOT_ACCEPTED 4 +struct bt_lmp_not_accepted { + uint8_t opcode; + uint8_t error; +} __attribute__ ((packed)); + +#define BT_LMP_CLKOFFSET_REQ 5 + +#define BT_LMP_CLKOFFSET_RSP 6 +struct bt_lmp_clkoffset_rsp { + uint16_t offset; +} __attribute__ ((packed)); + +#define BT_LMP_DETACH 7 +struct bt_lmp_detach { + uint8_t error; +} __attribute__ ((packed)); + +#define BT_LMP_AU_RAND 11 +struct bt_lmp_au_rand { + uint8_t number[16]; +} __attribute__ ((packed)); + +#define BT_LMP_SRES 12 +struct bt_lmp_sres { + uint8_t response[4]; +} __attribute__ ((packed)); + +#define BT_LMP_ENCRYPTION_MODE_REQ 15 +struct bt_lmp_encryption_mode_req { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_LMP_ENCRYPTION_KEY_SIZE_REQ 16 +struct bt_lmp_encryption_key_size_req { + uint8_t key_size; +} __attribute__ ((packed)); + +#define BT_LMP_START_ENCRYPTION_REQ 17 +struct bt_lmp_start_encryption_req { + uint8_t number[16]; +} __attribute__ ((packed)); + +#define BT_LMP_STOP_ENCRYPTION_REQ 18 + +#define BT_LMP_SWITCH_REQ 19 +struct bt_lmp_switch_req { + uint32_t instant; +} __attribute__ ((packed)); + +#define BT_LMP_UNSNIFF_REQ 24 + +#define BT_LMP_MAX_POWER 33 + +#define BT_LMP_MIN_POWER 34 + +#define BT_LMP_AUTO_RATE 35 + +#define BT_LMP_PREFERRED_RATE 36 +struct bt_lmp_preferred_rate { + uint8_t rate; +} __attribute__ ((packed)); + +#define BT_LMP_VERSION_REQ 37 +struct bt_lmp_version_req { + uint8_t version; + uint16_t company; + uint16_t subversion; +} __attribute__ ((packed)); + +#define BT_LMP_VERSION_RES 38 +struct bt_lmp_version_res { + uint8_t version; + uint16_t company; + uint16_t subversion; +} __attribute__ ((packed)); + +#define BT_LMP_FEATURES_REQ 39 +struct bt_lmp_features_req { + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_LMP_FEATURES_RES 40 +struct bt_lmp_features_res { + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_LMP_MAX_SLOT 45 +struct bt_lmp_max_slot { + uint8_t slots; +} __attribute__ ((packed)); + +#define BT_LMP_MAX_SLOT_REQ 46 +struct bt_lmp_max_slot_req { + uint8_t slots; +} __attribute__ ((packed)); + +#define BT_LMP_TIMING_ACCURACY_REQ 47 + +#define BT_LMP_TIMING_ACCURACY_RES 48 +struct bt_lmp_timing_accuracy_res { + uint8_t drift; + uint8_t jitter; +} __attribute__ ((packed)); + +#define BT_LMP_SETUP_COMPLETE 49 + +#define BT_LMP_USE_SEMI_PERMANENT_KEY 50 + +#define BT_LMP_HOST_CONNECTION_REQ 51 + +#define BT_LMP_SLOT_OFFSET 52 +struct bt_lmp_slot_offset { + uint16_t offset; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_LMP_PAGE_SCAN_MODE_REQ 54 +struct bt_lmp_page_scan_mode_req { + uint8_t scheme; + uint8_t settings; +} __attribute__ ((packed)); + +#define BT_LMP_TEST_ACTIVATE 56 + +#define BT_LMP_ENCRYPTION_KEY_SIZE_MASK_REQ 58 + +#define BT_LMP_SET_AFH 60 +struct bt_lmp_set_afh { + uint32_t instant; + uint8_t mode; + uint8_t map[10]; +} __attribute__ ((packed)); + +#define BT_LMP_ENCAPSULATED_HEADER 61 +struct bt_lmp_encapsulated_header { + uint8_t major; + uint8_t minor; + uint8_t length; +} __attribute__ ((packed)); + +#define BT_LMP_ENCAPSULATED_PAYLOAD 62 +struct bt_lmp_encapsulated_payload { + uint8_t data[16]; +} __attribute__ ((packed)); + +#define BT_LMP_SIMPLE_PAIRING_CONFIRM 63 +struct bt_lmp_simple_pairing_confirm { + uint8_t value[16]; +} __attribute__ ((packed)); + +#define BT_LMP_SIMPLE_PAIRING_NUMBER 64 +struct bt_lmp_simple_pairing_number { + uint8_t value[16]; +} __attribute__ ((packed)); + +#define BT_LMP_DHKEY_CHECK 65 +struct bt_lmp_dhkey_check { + uint8_t value[16]; +} __attribute__ ((packed)); + +#define BT_LMP_PAUSE_ENCRYPTION_AES_REQ 66 + +#define BT_LMP_ACCEPTED_EXT LMP_ESC4(1) +struct bt_lmp_accepted_ext { + uint8_t escape; + uint8_t opcode; +} __attribute__ ((packed)); + +#define BT_LMP_NOT_ACCEPTED_EXT LMP_ESC4(2) +struct bt_lmp_not_accepted_ext { + uint8_t escape; + uint8_t opcode; + uint8_t error; +} __attribute__ ((packed)); + +#define BT_LMP_FEATURES_REQ_EXT LMP_ESC4(3) +struct bt_lmp_features_req_ext { + uint8_t page; + uint8_t max_page; + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_LMP_FEATURES_RES_EXT LMP_ESC4(4) +struct bt_lmp_features_res_ext { + uint8_t page; + uint8_t max_page; + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_LMP_PACKET_TYPE_TABLE_REQ LMP_ESC4(11) +struct bt_lmp_packet_type_table_req { + uint8_t table; +} __attribute__ ((packed)); + +#define BT_LMP_CHANNEL_CLASSIFICATION_REQ LMP_ESC4(16) +struct bt_lmp_channel_classification_req { + uint8_t mode; + uint16_t min_interval; + uint16_t max_interval; +} __attribute__ ((packed)); + +#define BT_LMP_CHANNEL_CLASSIFICATION LMP_ESC4(17) +struct bt_lmp_channel_classification { + uint8_t classification[10]; +} __attribute__ ((packed)); + +#define BT_LMP_PAUSE_ENCRYPTION_REQ LMP_ESC4(23) + +#define BT_LMP_RESUME_ENCRYPTION_REQ LMP_ESC4(24) + +#define BT_LMP_IO_CAPABILITY_REQ LMP_ESC4(25) +struct bt_lmp_io_capability_req { + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __attribute__ ((packed)); + +#define BT_LMP_IO_CAPABILITY_RES LMP_ESC4(26) +struct bt_lmp_io_capability_res { + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __attribute__ ((packed)); + +#define BT_LMP_NUMERIC_COMPARISON_FAILED LMP_ESC(27) + +#define BT_LMP_PASSKEY_FAILED LMP_ESC4(28) + +#define BT_LMP_OOB_FAILED LMP_ESC(29) + +#define BT_LMP_POWER_CONTROL_REQ LMP_ESC4(31) +struct bt_lmp_power_control_req { + uint8_t request; +} __attribute__ ((packed)); + +#define BT_LMP_POWER_CONTROL_RES LMP_ESC4(32) +struct bt_lmp_power_control_res { + uint8_t response; +} __attribute__ ((packed)); + +#define BT_LMP_PING_REQ LMP_ESC4(33) + +#define BT_LMP_PING_RES LMP_ESC4(34) + +#define BT_H4_CMD_PKT 0x01 +#define BT_H4_ACL_PKT 0x02 +#define BT_H4_SCO_PKT 0x03 +#define BT_H4_EVT_PKT 0x04 + +struct bt_hci_cmd_hdr { + uint16_t opcode; + uint8_t plen; +} __attribute__ ((packed)); + +struct bt_hci_acl_hdr { + uint16_t handle; + uint16_t dlen; +} __attribute__ ((packed)); + +struct bt_hci_sco_hdr { + uint16_t handle; + uint8_t dlen; +} __attribute__ ((packed)); + +struct bt_hci_evt_hdr { + uint8_t evt; + uint8_t plen; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_NOP 0x0000 + +#define BT_HCI_CMD_INQUIRY 0x0401 +struct bt_hci_cmd_inquiry { + uint8_t lap[3]; + uint8_t length; + uint8_t num_resp; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_INQUIRY_CANCEL 0x0402 + +#define BT_HCI_CMD_PERIODIC_INQUIRY 0x0403 +struct bt_hci_cmd_periodic_inquiry { + uint16_t max_period; + uint16_t min_period; + uint8_t lap[3]; + uint8_t length; + uint8_t num_resp; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_EXIT_PERIODIC_INQUIRY 0x0404 + +#define BT_HCI_CMD_CREATE_CONN 0x0405 +struct bt_hci_cmd_create_conn { + uint8_t bdaddr[6]; + uint16_t pkt_type; + uint8_t pscan_rep_mode; + uint8_t pscan_mode; + uint16_t clock_offset; + uint8_t role_switch; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_DISCONNECT 0x0406 +struct bt_hci_cmd_disconnect { + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_ADD_SCO_CONN 0x0407 +struct bt_hci_cmd_add_sco_conn { + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_CREATE_CONN_CANCEL 0x0408 +struct bt_hci_cmd_create_conn_cancel { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_ACCEPT_CONN_REQUEST 0x0409 +struct bt_hci_cmd_accept_conn_request { + uint8_t bdaddr[6]; + uint8_t role; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_REJECT_CONN_REQUEST 0x040a +struct bt_hci_cmd_reject_conn_request { + uint8_t bdaddr[6]; + uint8_t reason; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LINK_KEY_REQUEST_REPLY 0x040b +struct bt_hci_cmd_link_key_request_reply { + uint8_t bdaddr[6]; + uint8_t link_key[16]; +} __attribute__ ((packed)); +struct bt_hci_rsp_link_key_request_reply { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LINK_KEY_REQUEST_NEG_REPLY 0x040c +struct bt_hci_cmd_link_key_request_neg_reply { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); +struct bt_hci_rsp_link_key_request_neg_reply { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_PIN_CODE_REQUEST_REPLY 0x040d +struct bt_hci_cmd_pin_code_request_reply { + uint8_t bdaddr[6]; + uint8_t pin_len; + uint8_t pin_code[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_PIN_CODE_REQUEST_NEG_REPLY 0x040e +struct bt_hci_cmd_pin_code_request_neg_reply { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); +struct bt_hci_rsp_pin_code_request_neg_reply { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_CHANGE_CONN_PKT_TYPE 0x040f +struct bt_hci_cmd_change_conn_pkt_type { + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_AUTH_REQUESTED 0x0411 +struct bt_hci_cmd_auth_requested { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SET_CONN_ENCRYPT 0x0413 +struct bt_hci_cmd_set_conn_encrypt { + uint16_t handle; + uint8_t encr_mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_CHANGE_CONN_LINK_KEY 0x0415 +struct bt_hci_cmd_change_conn_link_key { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_MASTER_LINK_KEY 0x0417 +struct bt_hci_cmd_master_link_key { + uint8_t key_flag; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_REMOTE_NAME_REQUEST 0x0419 +struct bt_hci_cmd_remote_name_request { + uint8_t bdaddr[6]; + uint8_t pscan_rep_mode; + uint8_t pscan_mode; + uint16_t clock_offset; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_REMOTE_NAME_REQUEST_CANCEL 0x041a +struct bt_hci_cmd_remote_name_request_cancel { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); +struct bt_hci_rsp_remote_name_request_cancel { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_REMOTE_FEATURES 0x041b +struct bt_hci_cmd_read_remote_features { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_REMOTE_EXT_FEATURES 0x041c +struct bt_hci_cmd_read_remote_ext_features { + uint16_t handle; + uint8_t page; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_REMOTE_VERSION 0x041d +struct bt_hci_cmd_read_remote_version { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_CLOCK_OFFSET 0x041f +struct bt_hci_cmd_read_clock_offset { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LMP_HANDLE 0x0420 +struct bt_hci_cmd_read_lmp_handle { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_lmp_handle { + uint8_t status; + uint16_t handle; + uint8_t lmp_handle; + uint32_t reserved; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SETUP_SYNC_CONN 0x0428 +struct bt_hci_cmd_setup_sync_conn { + uint16_t handle; + uint32_t tx_bandwidth; + uint32_t rx_bandwidth; + uint16_t max_latency; + uint16_t voice_setting; + uint8_t retrans_effort; + uint16_t pkt_type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_ACCEPT_SYNC_CONN_REQUEST 0x0429 +struct bt_hci_cmd_accept_sync_conn_request { + uint8_t bdaddr[6]; + uint32_t tx_bandwidth; + uint32_t rx_bandwidth; + uint16_t max_latency; + uint16_t voice_setting; + uint8_t retrans_effort; + uint16_t pkt_type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_REJECT_SYNC_CONN_REQUEST 0x042a +struct bt_hci_cmd_reject_sync_conn_request { + uint8_t bdaddr[6]; + uint8_t reason; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_IO_CAPABILITY_REQUEST_REPLY 0x042b +struct bt_hci_cmd_io_capability_request_reply { + uint8_t bdaddr[6]; + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __attribute__ ((packed)); +struct bt_hci_rsp_io_capability_request_reply { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_USER_CONFIRM_REQUEST_REPLY 0x042c +struct bt_hci_cmd_user_confirm_request_reply { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); +struct bt_hci_rsp_user_confirm_request_reply { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_USER_CONFIRM_REQUEST_NEG_REPLY 0x042d +struct bt_hci_cmd_user_confirm_request_neg_reply { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); +struct bt_hci_rsp_user_confirm_request_neg_reply { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_USER_PASSKEY_REQUEST_REPLY 0x042e +struct bt_hci_cmd_user_passkey_request_reply { + uint8_t bdaddr[6]; + uint32_t passkey; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_USER_PASSKEY_REQUEST_NEG_REPLY 0x042f +struct bt_hci_cmd_user_passkey_request_neg_reply { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_REMOTE_OOB_DATA_REQUEST_REPLY 0x0430 +struct bt_hci_cmd_remote_oob_data_request_reply { + uint8_t bdaddr[6]; + uint8_t hash[16]; + uint8_t randomizer[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_REMOTE_OOB_DATA_REQUEST_NEG_REPLY 0x0433 +struct bt_hci_cmd_remote_oob_data_request_neg_reply { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_IO_CAPABILITY_REQUEST_NEG_REPLY 0x0434 +struct bt_hci_cmd_io_capability_request_neg_reply { + uint8_t bdaddr[6]; + uint8_t reason; +} __attribute__ ((packed)); +struct bt_hci_rsp_io_capability_request_neg_reply { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_CREATE_PHY_LINK 0x0435 +struct bt_hci_cmd_create_phy_link { + uint8_t phy_handle; + uint8_t key_len; + uint8_t key_type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_ACCEPT_PHY_LINK 0x0436 +struct bt_hci_cmd_accept_phy_link { + uint8_t phy_handle; + uint8_t key_len; + uint8_t key_type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_DISCONN_PHY_LINK 0x0437 +struct bt_hci_cmd_disconn_phy_link { + uint8_t phy_handle; + uint8_t reason; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_CREATE_LOGIC_LINK 0x0438 +struct bt_hci_cmd_create_logic_link { + uint8_t phy_handle; + uint8_t tx_flow_spec[16]; + uint8_t rx_flow_spec[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_ACCEPT_LOGIC_LINK 0x0439 +struct bt_hci_cmd_accept_logic_link { + uint8_t phy_handle; + uint8_t tx_flow_spec[16]; + uint8_t rx_flow_spec[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_DISCONN_LOGIC_LINK 0x043a +struct bt_hci_cmd_disconn_logic_link { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LOGIC_LINK_CANCEL 0x043b +struct bt_hci_cmd_logic_link_cancel { + uint8_t phy_handle; + uint8_t flow_spec; +} __attribute__ ((packed)); +struct bt_hci_rsp_logic_link_cancel { + uint8_t status; + uint8_t phy_handle; + uint8_t flow_spec; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_FLOW_SPEC_MODIFY 0x043c +struct bt_hci_cmd_flow_spec_modify { + uint16_t handle; + uint8_t tx_flow_spec[16]; + uint8_t rx_flow_spec[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_ENHANCED_SETUP_SYNC_CONN 0x043d +struct bt_hci_cmd_enhanced_setup_sync_conn { + uint16_t handle; + uint32_t tx_bandwidth; + uint32_t rx_bandwidth; + uint8_t tx_coding_format[5]; + uint8_t rx_coding_format[5]; + uint16_t tx_codec_frame_size; + uint16_t rx_codec_frame_size; + uint32_t input_bandwidth; + uint32_t output_bandwidth; + uint8_t input_coding_format[5]; + uint8_t output_coding_format[5]; + uint16_t input_coded_data_size; + uint16_t output_coded_data_size; + uint8_t input_pcm_data_format; + uint8_t output_pcm_data_format; + uint8_t input_pcm_msb_position; + uint8_t output_pcm_msb_position; + uint8_t input_data_path; + uint8_t output_data_path; + uint8_t input_unit_size; + uint8_t output_unit_size; + uint16_t max_latency; + uint16_t pkt_type; + uint8_t retrans_effort; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_ENHANCED_ACCEPT_SYNC_CONN_REQUEST 0x043e +struct bt_hci_cmd_enhanced_accept_sync_conn_request { + uint8_t bdaddr[6]; + uint32_t tx_bandwidth; + uint32_t rx_bandwidth; + uint8_t tx_coding_format[5]; + uint8_t rx_coding_format[5]; + uint16_t tx_codec_frame_size; + uint16_t rx_codec_frame_size; + uint32_t input_bandwidth; + uint32_t output_bandwidth; + uint8_t input_coding_format[5]; + uint8_t output_coding_format[5]; + uint16_t input_coded_data_size; + uint16_t output_coded_data_size; + uint8_t input_pcm_data_format; + uint8_t output_pcm_data_format; + uint8_t input_pcm_msb_position; + uint8_t output_pcm_msb_position; + uint8_t input_data_path; + uint8_t output_data_path; + uint8_t input_unit_size; + uint8_t output_unit_size; + uint16_t max_latency; + uint16_t pkt_type; + uint8_t retrans_effort; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_TRUNCATED_PAGE 0x043f +struct bt_hci_cmd_truncated_page { + uint8_t bdaddr[6]; + uint8_t pscan_rep_mode; + uint16_t clock_offset; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_TRUNCATED_PAGE_CANCEL 0x0440 +struct bt_hci_cmd_truncated_page_cancel { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SET_SLAVE_BROADCAST 0x0441 +struct bt_hci_cmd_set_slave_broadcast { + uint8_t enable; + uint8_t lt_addr; + uint8_t lpo_allowed; + uint16_t pkt_type; + uint16_t min_interval; + uint16_t max_interval; + uint16_t timeout; +} __attribute__ ((packed)); +struct bt_hci_rsp_set_slave_broadcast { + uint8_t status; + uint8_t lt_addr; + uint16_t interval; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SET_SLAVE_BROADCAST_RECEIVE 0x0442 +struct bt_hci_cmd_set_slave_broadcast_receive { + uint8_t enable; + uint8_t bdaddr[6]; + uint8_t lt_addr; + uint16_t interval; + uint32_t offset; + uint32_t instant; + uint16_t timeout; + uint8_t accuracy; + uint8_t skip; + uint16_t pkt_type; + uint8_t map[10]; +} __attribute__ ((packed)); +struct bt_hci_rsp_set_slave_broadcast_receive { + uint8_t status; + uint8_t bdaddr[6]; + uint8_t lt_addr; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_START_SYNC_TRAIN 0x0443 + +#define BT_HCI_CMD_RECEIVE_SYNC_TRAIN 0x0444 +struct bt_hci_cmd_receive_sync_train { + uint8_t bdaddr[6]; + uint16_t timeout; + uint16_t window; + uint16_t interval; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_REMOTE_OOB_EXT_DATA_REQUEST_REPLY 0x0445 +struct bt_hci_cmd_remote_oob_ext_data_request_reply { + uint8_t bdaddr[6]; + uint8_t hash192[16]; + uint8_t randomizer192[16]; + uint8_t hash256[16]; + uint8_t randomizer256[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_HOLD_MODE 0x0801 +struct bt_hci_cmd_hold_mode { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SNIFF_MODE 0x0803 +struct bt_hci_cmd_sniff_mode { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; + uint16_t attempt; + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_EXIT_SNIFF_MODE 0x0804 +struct bt_hci_cmd_exit_sniff_mode { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_PARK_STATE 0x0805 +struct bt_hci_cmd_park_state { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_EXIT_PARK_STATE 0x0806 +struct bt_hci_cmd_exit_park_state { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_QOS_SETUP 0x0807 +struct bt_hci_cmd_qos_setup { + uint16_t handle; + uint8_t flags; + uint8_t service_type; + uint32_t token_rate; + uint32_t peak_bandwidth; + uint32_t latency; + uint32_t delay_variation; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_ROLE_DISCOVERY 0x0809 +struct bt_hci_cmd_role_discovery { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_role_discovery { + uint8_t status; + uint16_t handle; + uint8_t role; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SWITCH_ROLE 0x080b +struct bt_hci_cmd_switch_role { + uint8_t bdaddr[6]; + uint8_t role; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LINK_POLICY 0x080c +struct bt_hci_cmd_read_link_policy { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_link_policy { + uint8_t status; + uint16_t handle; + uint16_t policy; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_LINK_POLICY 0x080d +struct bt_hci_cmd_write_link_policy { + uint16_t handle; + uint16_t policy; +} __attribute__ ((packed)); +struct bt_hci_rsp_write_link_policy { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_DEFAULT_LINK_POLICY 0x080e +struct bt_hci_rsp_read_default_link_policy { + uint8_t status; + uint16_t policy; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_DEFAULT_LINK_POLICY 0x080f +struct bt_hci_cmd_write_default_link_policy { + uint16_t policy; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_FLOW_SPEC 0x0810 +struct bt_hci_cmd_flow_spec { + uint16_t handle; + uint8_t flags; + uint8_t direction; + uint8_t service_type; + uint32_t token_rate; + uint32_t token_bucket_size; + uint32_t peak_bandwidth; + uint32_t access_latency; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SNIFF_SUBRATING 0x0811 +struct bt_hci_cmd_sniff_subrating { + uint16_t handle; + uint16_t max_latency; + uint16_t min_remote_timeout; + uint16_t min_local_timeout; +} __attribute__ ((packed)); +struct bt_hci_rsp_sniff_subrating { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SET_EVENT_MASK 0x0c01 +struct bt_hci_cmd_set_event_mask { + uint8_t mask[8]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_RESET 0x0c03 + +#define BT_HCI_CMD_SET_EVENT_FILTER 0x0c05 +struct bt_hci_cmd_set_event_filter { + uint8_t type; + uint8_t cond_type; + uint8_t cond[0]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_FLUSH 0x0c08 +struct bt_hci_cmd_flush { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_flush { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_PIN_TYPE 0x0c09 +struct bt_hci_rsp_read_pin_type { + uint8_t status; + uint8_t pin_type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_PIN_TYPE 0x0c0a +struct bt_hci_cmd_write_pin_type { + uint8_t pin_type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_CREATE_NEW_UNIT_KEY 0x0c0b + +#define BT_HCI_CMD_READ_STORED_LINK_KEY 0x0c0d +struct bt_hci_cmd_read_stored_link_key { + uint8_t bdaddr[6]; + uint8_t read_all; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_stored_link_key { + uint8_t status; + uint16_t max_num_keys; + uint16_t num_keys; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_STORED_LINK_KEY 0x0c11 +struct bt_hci_cmd_write_stored_link_key { + uint8_t num_keys; +} __attribute__ ((packed)); +struct bt_hci_rsp_write_stored_link_key { + uint8_t status; + uint8_t num_keys; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_DELETE_STORED_LINK_KEY 0x0c12 +struct bt_hci_cmd_delete_stored_link_key { + uint8_t bdaddr[6]; + uint8_t delete_all; +} __attribute__ ((packed)); +struct bt_hci_rsp_delete_stored_link_key { + uint8_t status; + uint16_t num_keys; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_LOCAL_NAME 0x0c13 +struct bt_hci_cmd_write_local_name { + uint8_t name[248]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCAL_NAME 0x0c14 +struct bt_hci_rsp_read_local_name { + uint8_t status; + uint8_t name[248]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_CONN_ACCEPT_TIMEOUT 0x0c15 +struct bt_hci_rsp_read_conn_accept_timeout { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_CONN_ACCEPT_TIMEOUT 0x0c16 +struct bt_hci_cmd_write_conn_accept_timeout { + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_PAGE_TIMEOUT 0x0c17 +struct bt_hci_rsp_read_page_timeout { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_PAGE_TIMEOUT 0x0c18 +struct bt_hci_cmd_write_page_timeout { + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_SCAN_ENABLE 0x0c19 +struct bt_hci_rsp_read_scan_enable { + uint8_t status; + uint8_t enable; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_SCAN_ENABLE 0x0c1a +struct bt_hci_cmd_write_scan_enable { + uint8_t enable; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_PAGE_SCAN_ACTIVITY 0x0c1b +struct bt_hci_rsp_read_page_scan_activity { + uint8_t status; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_PAGE_SCAN_ACTIVITY 0x0c1c +struct bt_hci_cmd_write_page_scan_activity { + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_INQUIRY_SCAN_ACTIVITY 0x0c1d +struct bt_hci_rsp_read_inquiry_scan_activity { + uint8_t status; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_INQUIRY_SCAN_ACTIVITY 0x0c1e +struct bt_hci_cmd_write_inquiry_scan_activity { + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_AUTH_ENABLE 0x0c1f +struct bt_hci_rsp_read_auth_enable { + uint8_t status; + uint8_t enable; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_AUTH_ENABLE 0x0c20 +struct bt_hci_cmd_write_auth_enable { + uint8_t enable; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_ENCRYPT_MODE 0x0c21 +struct bt_hci_rsp_read_encrypt_mode { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_ENCRYPT_MODE 0x0c22 +struct bt_hci_cmd_write_encrypt_mode { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_CLASS_OF_DEV 0x0c23 +struct bt_hci_rsp_read_class_of_dev { + uint8_t status; + uint8_t dev_class[3]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_CLASS_OF_DEV 0x0c24 +struct bt_hci_cmd_write_class_of_dev { + uint8_t dev_class[3]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_VOICE_SETTING 0x0c25 +struct bt_hci_rsp_read_voice_setting { + uint8_t status; + uint16_t setting; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_VOICE_SETTING 0x0c26 +struct bt_hci_cmd_write_voice_setting { + uint16_t setting; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_AUTO_FLUSH_TIMEOUT 0x0c27 +struct bt_hci_cmd_read_auto_flush_timeout { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_auto_flush_timeout { + uint8_t status; + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_AUTO_FLUSH_TIMEOUT 0x0c28 +struct bt_hci_cmd_write_auto_flush_timeout { + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)); +struct bt_hci_rsp_write_auto_flush_timeout { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_NUM_BROADCAST_RETRANS 0x0c29 +struct bt_hci_rsp_read_num_broadcast_retrans { + uint8_t status; + uint8_t num_retrans; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_NUM_BROADCAST_RETRANS 0x0c2a +struct bt_hci_cmd_write_num_broadcast_retrans { + uint8_t num_retrans; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_HOLD_MODE_ACTIVITY 0x0c2b +struct bt_hci_rsp_read_hold_mode_activity { + uint8_t status; + uint8_t activity; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_HOLD_MODE_ACTIVITY 0x0c2c +struct bt_hci_cmd_write_hold_mode_activity { + uint8_t activity; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_TX_POWER 0x0c2d +struct bt_hci_cmd_read_tx_power { + uint16_t handle; + uint8_t type; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_tx_power { + uint8_t status; + uint16_t handle; + int8_t level; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_SYNC_FLOW_CONTROL 0x0c2e +struct bt_hci_rsp_read_sync_flow_control { + uint8_t status; + uint8_t enable; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_SYNC_FLOW_CONTROL 0x0c2f +struct bt_hci_cmd_write_sync_flow_control { + uint8_t enable; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SET_HOST_FLOW_CONTROL 0x0c31 +struct bt_hci_cmd_set_host_flow_control { + uint8_t enable; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_HOST_BUFFER_SIZE 0x0c33 +struct bt_hci_cmd_host_buffer_size { + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_max_pkt; + uint16_t sco_max_pkt; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_HOST_NUM_COMPLETED_PACKETS 0x0c35 +struct bt_hci_cmd_host_num_completed_packets { + uint8_t num_handles; + uint16_t handle; + uint16_t count; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LINK_SUPV_TIMEOUT 0x0c36 +struct bt_hci_cmd_read_link_supv_timeout { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_link_supv_timeout { + uint8_t status; + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_LINK_SUPV_TIMEOUT 0x0c37 +struct bt_hci_cmd_write_link_supv_timeout { + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)); +struct bt_hci_rsp_write_link_supv_timeout { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_NUM_SUPPORTED_IAC 0x0c38 +struct bt_hci_rsp_read_num_supported_iac { + uint8_t status; + uint8_t num_iac; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_CURRENT_IAC_LAP 0x0c39 +struct bt_hci_rsp_read_current_iac_lap { + uint8_t status; + uint8_t num_iac; + uint8_t iac_lap[0]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_CURRENT_IAC_LAP 0x0c3a +struct bt_hci_cmd_write_current_iac_lap { + uint8_t num_iac; + uint8_t iac_lap[0]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_PAGE_SCAN_PERIOD_MODE 0x0c3b +struct bt_hci_rsp_read_page_scan_period_mode { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_PAGE_SCAN_PERIOD_MODE 0x0c3c +struct bt_hci_cmd_write_page_scan_period_mode { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_PAGE_SCAN_MODE 0x0c3d +struct bt_hci_rsp_read_page_scan_mode { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_PAGE_SCAN_MODE 0x0c3e +struct bt_hci_cmd_write_page_scan_mode { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SET_AFH_HOST_CLASSIFICATION 0x0c3f +struct bt_hci_cmd_set_afh_host_classification { + uint8_t map[10]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_INQUIRY_SCAN_TYPE 0x0c42 +struct bt_hci_rsp_read_inquiry_scan_type { + uint8_t status; + uint8_t type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_INQUIRY_SCAN_TYPE 0x0c43 +struct bt_hci_cmd_write_inquiry_scan_type { + uint8_t type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_INQUIRY_MODE 0x0c44 +struct bt_hci_rsp_read_inquiry_mode { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_INQUIRY_MODE 0x0c45 +struct bt_hci_cmd_write_inquiry_mode { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_PAGE_SCAN_TYPE 0x0c46 +struct bt_hci_rsp_read_page_scan_type { + uint8_t status; + uint8_t type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_PAGE_SCAN_TYPE 0x0c47 +struct bt_hci_cmd_write_page_scan_type { + uint8_t type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_AFH_ASSESSMENT_MODE 0x0c48 +struct bt_hci_rsp_read_afh_assessment_mode { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_AFH_ASSESSMENT_MODE 0x0c49 +struct bt_hci_cmd_write_afh_assessment_mode { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_EXT_INQUIRY_RESPONSE 0x0c51 +struct bt_hci_rsp_read_ext_inquiry_response { + uint8_t status; + uint8_t fec; + uint8_t data[240]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_EXT_INQUIRY_RESPONSE 0x0c52 +struct bt_hci_cmd_write_ext_inquiry_response { + uint8_t fec; + uint8_t data[240]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_REFRESH_ENCRYPT_KEY 0x0c53 +struct bt_hci_cmd_refresh_encrypt_key { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_SIMPLE_PAIRING_MODE 0x0c55 +struct bt_hci_rsp_read_simple_pairing_mode { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE 0x0c56 +struct bt_hci_cmd_write_simple_pairing_mode { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCAL_OOB_DATA 0x0c57 +struct bt_hci_rsp_read_local_oob_data { + uint8_t status; + uint8_t hash[16]; + uint8_t randomizer[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_INQUIRY_RESP_TX_POWER 0x0c58 +struct bt_hci_rsp_read_inquiry_resp_tx_power { + uint8_t status; + int8_t level; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_INQUIRY_TX_POWER 0x0c59 +struct bt_hci_cmd_write_inquiry_tx_power { + int8_t level; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_ERRONEOUS_REPORTING 0x0c5a +struct bt_hci_rsp_read_erroneous_reporting { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_ERRONEOUS_REPORTING 0x0c5b +struct bt_hci_cmd_write_erroneous_reporting { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_ENHANCED_FLUSH 0x0c5f +struct bt_hci_cmd_enhanced_flush { + uint16_t handle; + uint8_t type; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SEND_KEYPRESS_NOTIFY 0x0c60 +struct bt_hci_cmd_send_keypress_notify { + uint8_t bdaddr[6]; + uint8_t type; +} __attribute__ ((packed)); +struct bt_hci_rsp_send_keypress_notify { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SET_EVENT_MASK_PAGE2 0x0c63 +struct bt_hci_cmd_set_event_mask_page2 { + uint8_t mask[8]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCATION_DATA 0x0c64 +struct bt_hci_rsp_read_location_data { + uint8_t status; + uint8_t domain_aware; + uint8_t domain[2]; + uint8_t domain_options; + uint8_t options; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_LOCATION_DATA 0x0c65 +struct bt_hci_cmd_write_location_data { + uint8_t domain_aware; + uint8_t domain[2]; + uint8_t domain_options; + uint8_t options; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_FLOW_CONTROL_MODE 0x0c66 +struct bt_hci_rsp_read_flow_control_mode { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_FLOW_CONTROL_MODE 0x0c67 +struct bt_hci_cmd_write_flow_control_mode { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_ENHANCED_TX_POWER 0x0c68 +struct bt_hci_cmd_read_enhanced_tx_power { + uint16_t handle; + uint8_t type; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_enhanced_tx_power { + uint8_t status; + uint16_t handle; + int8_t level_gfsk; + int8_t level_dqpsk; + int8_t level_8dpsk; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SHORT_RANGE_MODE 0x0c6b +struct bt_hci_cmd_short_range_mode { + uint8_t phy_handle; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LE_HOST_SUPPORTED 0x0c6c +struct bt_hci_rsp_read_le_host_supported { + uint8_t status; + uint8_t supported; + uint8_t simultaneous; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_LE_HOST_SUPPORTED 0x0c6d +struct bt_hci_cmd_write_le_host_supported { + uint8_t supported; + uint8_t simultaneous; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SET_RESERVED_LT_ADDR 0x0c74 +struct bt_hci_cmd_set_reserved_lt_addr { + uint8_t lt_addr; +} __attribute__ ((packed)); +struct bt_hci_rsp_set_reserved_lt_addr { + uint8_t status; + uint8_t lt_addr; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_DELETE_RESERVED_LT_ADDR 0x0c75 +struct bt_hci_cmd_delete_reserved_lt_addr { + uint8_t lt_addr; +} __attribute__ ((packed)); +struct bt_hci_rsp_delete_reserved_lt_addr { + uint8_t status; + uint8_t lt_addr; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SET_SLAVE_BROADCAST_DATA 0x0c76 +struct bt_hci_cmd_set_slave_broadcast_data { + uint8_t lt_addr; + uint8_t fragment; + uint8_t length; +} __attribute__ ((packed)); +struct bt_hci_rsp_set_slave_broadcast_data { + uint8_t status; + uint8_t lt_addr; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_SYNC_TRAIN_PARAMS 0x0c77 +struct bt_hci_rsp_read_sync_train_params { + uint8_t status; + uint16_t interval; + uint32_t timeout; + uint8_t service_data; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_SYNC_TRAIN_PARAMS 0x0c78 +struct bt_hci_cmd_write_sync_train_params { + uint16_t min_interval; + uint16_t max_interval; + uint32_t timeout; + uint8_t service_data; +} __attribute__ ((packed)); +struct bt_hci_rsp_write_sync_train_params { + uint8_t status; + uint16_t interval; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_SECURE_CONN_SUPPORT 0x0c79 +struct bt_hci_rsp_read_secure_conn_support { + uint8_t status; + uint8_t support; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_SECURE_CONN_SUPPORT 0x0c7a +struct bt_hci_cmd_write_secure_conn_support { + uint8_t support; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_AUTH_PAYLOAD_TIMEOUT 0x0c7b +struct bt_hci_cmd_read_auth_payload_timeout { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_auth_payload_timeout { + uint8_t status; + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_AUTH_PAYLOAD_TIMEOUT 0x0c7c +struct bt_hci_cmd_write_auth_payload_timeout { + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)); +struct bt_hci_rsp_write_auth_payload_timeout { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCAL_OOB_EXT_DATA 0x0c7d +struct bt_hci_rsp_read_local_oob_ext_data { + uint8_t status; + uint8_t hash192[16]; + uint8_t randomizer192[16]; + uint8_t hash256[16]; + uint8_t randomizer256[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_EXT_PAGE_TIMEOUT 0x0c7e +struct bt_hci_rsp_read_ext_page_timeout { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_EXT_PAGE_TIMEOUT 0x0c7f +struct bt_hci_cmd_write_ext_page_timeout { + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_EXT_INQUIRY_LENGTH 0x0c80 +struct bt_hci_rsp_read_ext_inquiry_length { + uint8_t status; + uint16_t interval; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_EXT_INQUIRY_LENGTH 0x0c81 +struct bt_hci_cmd_write_ext_inquiry_length { + uint16_t interval; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCAL_VERSION 0x1001 +struct bt_hci_rsp_read_local_version { + uint8_t status; + uint8_t hci_ver; + uint16_t hci_rev; + uint8_t lmp_ver; + uint16_t manufacturer; + uint16_t lmp_subver; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCAL_COMMANDS 0x1002 +struct bt_hci_rsp_read_local_commands { + uint8_t status; + uint8_t commands[64]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCAL_FEATURES 0x1003 +struct bt_hci_rsp_read_local_features { + uint8_t status; + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCAL_EXT_FEATURES 0x1004 +struct bt_hci_cmd_read_local_ext_features { + uint8_t page; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_local_ext_features { + uint8_t status; + uint8_t page; + uint8_t max_page; + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_BUFFER_SIZE 0x1005 +struct bt_hci_rsp_read_buffer_size { + uint8_t status; + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_max_pkt; + uint16_t sco_max_pkt; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_COUNTRY_CODE 0x1007 +struct bt_hci_rsp_read_country_code { + uint8_t status; + uint8_t code; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_BD_ADDR 0x1009 +struct bt_hci_rsp_read_bd_addr { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_DATA_BLOCK_SIZE 0x100a +struct bt_hci_rsp_read_data_block_size { + uint8_t status; + uint16_t max_acl_len; + uint16_t block_len; + uint16_t num_blocks; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCAL_CODECS 0x100b +struct bt_hci_rsp_read_local_codecs { + uint8_t status; + uint8_t num_codecs; + uint8_t codec[0]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_FAILED_CONTACT_COUNTER 0x1401 +struct bt_hci_cmd_read_failed_contact_counter { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_failed_contact_counter { + uint8_t status; + uint16_t handle; + uint16_t counter; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_RESET_FAILED_CONTACT_COUNTER 0x1402 +struct bt_hci_cmd_reset_failed_contact_counter { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_reset_failed_contact_counter { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LINK_QUALITY 0x1403 +struct bt_hci_cmd_read_link_quality { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_link_quality { + uint8_t status; + uint16_t handle; + uint8_t link_quality; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_RSSI 0x1405 +struct bt_hci_cmd_read_rssi { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_rssi { + uint8_t status; + uint16_t handle; + int8_t rssi; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_AFH_CHANNEL_MAP 0x1406 +struct bt_hci_cmd_read_afh_channel_map { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_afh_channel_map { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint8_t map[10]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_CLOCK 0x1407 +struct bt_hci_cmd_read_clock { + uint16_t handle; + uint8_t type; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_clock { + uint8_t status; + uint16_t handle; + uint32_t clock; + uint16_t accuracy; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_ENCRYPT_KEY_SIZE 0x1408 +struct bt_hci_cmd_read_encrypt_key_size { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_encrypt_key_size { + uint8_t status; + uint16_t handle; + uint8_t key_size; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCAL_AMP_INFO 0x1409 +struct bt_hci_rsp_read_local_amp_info { + uint8_t status; + uint8_t amp_status; + uint32_t total_bw; + uint32_t max_bw; + uint32_t min_latency; + uint32_t max_pdu; + uint8_t amp_type; + uint16_t pal_cap; + uint16_t max_assoc_len; + uint32_t max_flush_to; + uint32_t be_flush_to; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOCAL_AMP_ASSOC 0x140a +struct bt_hci_cmd_read_local_amp_assoc { + uint8_t phy_handle; + uint16_t len_so_far; + uint16_t max_assoc_len; +} __attribute__ ((packed)); +struct bt_hci_rsp_read_local_amp_assoc { + uint8_t status; + uint8_t phy_handle; + uint16_t remain_assoc_len; + uint8_t assoc_fragment[248]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_REMOTE_AMP_ASSOC 0x140b +struct bt_hci_cmd_write_remote_amp_assoc { + uint8_t phy_handle; + uint16_t len_so_far; + uint16_t remain_assoc_len; + uint8_t assoc_fragment[248]; +} __attribute__ ((packed)); +struct bt_hci_rsp_write_remote_amp_assoc { + uint8_t status; + uint8_t phy_handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_GET_MWS_TRANSPORT_CONFIG 0x140c +struct bt_hci_rsp_get_mws_transport_config { + uint8_t status; + uint8_t num_transports; + uint8_t transport[0]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_SET_TRIGGERED_CLOCK_CAPTURE 0x140d +struct bt_hci_cmd_set_triggered_clock_capture { + uint16_t handle; + uint8_t enable; + uint8_t type; + uint8_t lpo_allowed; + uint8_t num_filter; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_READ_LOOPBACK_MODE 0x1801 +struct bt_hci_rsp_read_loopback_mode { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_WRITE_LOOPBACK_MODE 0x1802 +struct bt_hci_cmd_write_loopback_mode { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_ENABLE_DUT_MODE 0x1803 + +#define BT_HCI_CMD_WRITE_SSP_DEBUG_MODE 0x1804 +struct bt_hci_cmd_write_ssp_debug_mode { + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_EVENT_MASK 0x2001 +struct bt_hci_cmd_le_set_event_mask { + uint8_t mask[8]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_BUFFER_SIZE 0x2002 +struct bt_hci_rsp_le_read_buffer_size { + uint8_t status; + uint16_t le_mtu; + uint8_t le_max_pkt; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_LOCAL_FEATURES 0x2003 +struct bt_hci_rsp_le_read_local_features { + uint8_t status; + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_RANDOM_ADDRESS 0x2005 +struct bt_hci_cmd_le_set_random_address { + uint8_t addr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_ADV_PARAMETERS 0x2006 +struct bt_hci_cmd_le_set_adv_parameters { + uint16_t min_interval; + uint16_t max_interval; + uint8_t type; + uint8_t own_addr_type; + uint8_t direct_addr_type; + uint8_t direct_addr[6]; + uint8_t channel_map; + uint8_t filter_policy; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_ADV_TX_POWER 0x2007 +struct bt_hci_rsp_le_read_adv_tx_power { + uint8_t status; + int8_t level; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_ADV_DATA 0x2008 +struct bt_hci_cmd_le_set_adv_data { + uint8_t len; + uint8_t data[31]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_SCAN_RSP_DATA 0x2009 +struct bt_hci_cmd_le_set_scan_rsp_data { + uint8_t len; + uint8_t data[31]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_ADV_ENABLE 0x200a +struct bt_hci_cmd_le_set_adv_enable { + uint8_t enable; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_SCAN_PARAMETERS 0x200b +struct bt_hci_cmd_le_set_scan_parameters { + uint8_t type; + uint16_t interval; + uint16_t window; + uint8_t own_addr_type; + uint8_t filter_policy; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_SCAN_ENABLE 0x200c +struct bt_hci_cmd_le_set_scan_enable { + uint8_t enable; + uint8_t filter_dup; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_CREATE_CONN 0x200d +struct bt_hci_cmd_le_create_conn { + uint16_t scan_interval; + uint16_t scan_window; + uint8_t filter_policy; + uint8_t peer_addr_type; + uint8_t peer_addr[6]; + uint8_t own_addr_type; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t supv_timeout; + uint16_t min_length; + uint16_t max_length; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_CREATE_CONN_CANCEL 0x200e + +#define BT_HCI_CMD_LE_READ_WHITE_LIST_SIZE 0x200f +struct bt_hci_rsp_le_read_white_list_size { + uint8_t status; + uint8_t size; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_CLEAR_WHITE_LIST 0x2010 + +#define BT_HCI_CMD_LE_ADD_TO_WHITE_LIST 0x2011 +struct bt_hci_cmd_le_add_to_white_list { + uint8_t addr_type; + uint8_t addr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_REMOVE_FROM_WHITE_LIST 0x2012 +struct bt_hci_cmd_le_remove_from_white_list { + uint8_t addr_type; + uint8_t addr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_CONN_UPDATE 0x2013 +struct bt_hci_cmd_le_conn_update { + uint16_t handle; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t supv_timeout; + uint16_t min_length; + uint16_t max_length; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_HOST_CLASSIFICATION 0x2014 +struct bt_hci_cmd_le_set_host_classification { + uint8_t map[5]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_CHANNEL_MAP 0x2015 +struct bt_hci_cmd_le_read_channel_map { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_le_read_channel_map { + uint8_t status; + uint16_t handle; + uint8_t map[5]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_REMOTE_FEATURES 0x2016 +struct bt_hci_cmd_le_read_remote_features { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_ENCRYPT 0x2017 +struct bt_hci_cmd_le_encrypt { + uint8_t key[16]; + uint8_t plaintext[16]; +} __attribute__ ((packed)); +struct bt_hci_rsp_le_encrypt { + uint8_t status; + uint8_t data[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_RAND 0x2018 +struct bt_hci_rsp_le_rand { + uint8_t status; + uint64_t number; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_START_ENCRYPT 0x2019 +struct bt_hci_cmd_le_start_encrypt { + uint16_t handle; + uint64_t rand; + uint16_t ediv; + uint8_t ltk[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_LTK_REQ_REPLY 0x201a +struct bt_hci_cmd_le_ltk_req_reply { + uint16_t handle; + uint8_t ltk[16]; +} __attribute__ ((packed)); +struct bt_hci_rsp_le_ltk_req_reply { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_LTK_REQ_NEG_REPLY 0x201b +struct bt_hci_cmd_le_ltk_req_neg_reply { + uint16_t handle; +} __attribute__ ((packed)); +struct bt_hci_rsp_le_ltk_req_neg_reply { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_SUPPORTED_STATES 0x201c +struct bt_hci_rsp_le_read_supported_states { + uint8_t status; + uint8_t states[8]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_RECEIVER_TEST 0x201d +struct bt_hci_cmd_le_receiver_test { + uint8_t frequency; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_TRANSMITTER_TEST 0x201e +struct bt_hci_cmd_le_transmitter_test { + uint8_t frequency; + uint8_t data_len; + uint8_t payload; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_TEST_END 0x201f +struct bt_hci_rsp_le_test_end { + uint8_t status; + uint16_t num_packets; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_CONN_PARAM_REQ_REPLY 0x2020 +struct bt_hci_cmd_le_conn_param_req_reply { + uint16_t handle; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t supv_timeout; + uint16_t min_length; + uint16_t max_length; +} __attribute__ ((packed)); +struct bt_hci_rsp_le_conn_param_req_reply { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_CONN_PARAM_REQ_NEG_REPLY 0x2021 +struct bt_hci_cmd_le_conn_param_req_neg_reply { + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)); +struct bt_hci_rsp_le_conn_param_req_neg_reply { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_DATA_LENGTH 0x2022 +struct bt_hci_cmd_le_set_data_length { + uint16_t handle; + uint16_t tx_len; + uint16_t tx_time; +} __attribute__ ((packed)); +struct bt_hci_rsp_le_set_data_length { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_DEFAULT_DATA_LENGTH 0x2023 +struct bt_hci_rsp_le_read_default_data_length { + uint8_t status; + uint16_t tx_len; + uint16_t tx_time; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_WRITE_DEFAULT_DATA_LENGTH 0x2024 +struct bt_hci_cmd_le_write_default_data_length { + uint16_t tx_len; + uint16_t tx_time; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_LOCAL_PK256 0x2025 + +#define BT_HCI_CMD_LE_GENERATE_DHKEY 0x2026 +struct bt_hci_cmd_le_generate_dhkey { + uint8_t remote_pk256[64]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST 0x2027 +struct bt_hci_cmd_le_add_to_resolv_list { + uint8_t addr_type; + uint8_t addr[6]; + uint8_t peer_irk[16]; + uint8_t local_irk[16]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_REMOVE_FROM_RESOLV_LIST 0x2028 +struct bt_hci_cmd_le_remove_from_resolv_list { + uint8_t addr_type; + uint8_t addr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_CLEAR_RESOLV_LIST 0x2029 + +#define BT_HCI_CMD_LE_READ_RESOLV_LIST_SIZE 0x202a +struct bt_hci_rsp_le_read_resolv_list_size { + uint8_t status; + uint8_t size; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_PEER_RESOLV_ADDR 0x202b +struct bt_hci_cmd_le_read_peer_resolv_addr { + uint8_t addr_type; + uint8_t addr[6]; +} __attribute__ ((packed)); +struct bt_hci_rsp_le_read_peer_resolv_addr { + uint8_t status; + uint8_t addr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_LOCAL_RESOLV_ADDR 0x202c +struct bt_hci_cmd_le_read_local_resolv_addr { + uint8_t addr_type; + uint8_t addr[6]; +} __attribute__ ((packed)); +struct bt_hci_rsp_le_read_local_resolv_addr { + uint8_t status; + uint8_t addr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_RESOLV_ENABLE 0x202d +struct bt_hci_cmd_le_set_resolv_enable { + uint8_t enable; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_RESOLV_TIMEOUT 0x202e +struct bt_hci_cmd_le_set_resolv_timeout { + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_MAX_DATA_LENGTH 0x202f +struct bt_hci_rsp_le_read_max_data_length { + uint8_t status; + uint16_t max_tx_len; + uint16_t max_tx_time; + uint16_t max_rx_len; + uint16_t max_rx_time; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_PHY 0x2030 +struct bt_hci_cmd_le_read_phy { + uint16_t handle; +} __attribute__((packed)); +struct bt_hci_rsp_le_read_phy { + uint8_t status; + uint16_t handle; + uint8_t tx_phy; + uint8_t rx_phy; +} __attribute__((packed)); + +#define BT_HCI_CMD_LE_SET_DEFAULT_PHY 0x2031 +struct bt_hci_cmd_le_set_default_phy { + uint8_t all_phys; + uint8_t tx_phys; + uint8_t rx_phys; +} __attribute__((packed)); + +#define BT_HCI_CMD_LE_SET_PHY 0x2032 +struct bt_hci_cmd_le_set_phy { + uint16_t handle; + uint8_t all_phys; + uint8_t tx_phys; + uint8_t rx_phys; + uint16_t phy_opts; +} __attribute__((packed)); + +#define BT_HCI_CMD_LE_ENHANCED_RECEIVER_TEST 0x2033 +struct bt_hci_cmd_le_enhanced_receiver_test { + uint8_t rx_channel; + uint8_t phy; + uint8_t modulation_index; +} __attribute__((packed)); + +#define BT_HCI_CMD_LE_ENHANCED_TRANSMITTER_TEST 0x2034 +struct bt_hci_cmd_le_enhanced_transmitter_test { + uint8_t tx_channel; + uint8_t data_len; + uint8_t payload; + uint8_t phy; +} __attribute__((packed)); + +#define BT_HCI_CMD_LE_SET_ADV_SET_RAND_ADDR 0x2035 +struct bt_hci_cmd_le_set_adv_set_rand_addr { + uint8_t handle; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_EXT_ADV_PARAMS 0x2036 +struct bt_hci_cmd_le_set_ext_adv_params { + uint8_t handle; + uint16_t evt_properties; + uint8_t min_interval[3]; + uint8_t max_interval[3]; + uint8_t channel_map; + uint8_t own_addr_type; + uint8_t peer_addr_type; + uint8_t peer_addr[6]; + uint8_t filter_policy; + uint8_t tx_power; + uint8_t primary_phy; + uint8_t secondary_max_skip; + uint8_t secondary_phy; + uint8_t sid; + uint8_t notif_enable; +} __attribute__ ((packed)); +struct bt_hci_rsp_le_set_ext_adv_params { + uint8_t status; + uint8_t tx_power; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_EXT_ADV_DATA 0x2037 +struct bt_hci_cmd_le_set_ext_adv_data { + uint8_t handle; + uint8_t operation; + uint8_t fragment_preference; + uint8_t data_len; + uint8_t data[0]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_EXT_SCAN_RSP_DATA 0x2038 +struct bt_hci_cmd_le_set_ext_scan_rsp_data { + uint8_t handle; + uint8_t operation; + uint8_t fragment_preference; + uint8_t data_len; + uint8_t data[0]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_EXT_ADV_ENABLE 0x2039 +struct bt_hci_cmd_le_set_ext_adv_enable { + uint8_t enable; + uint8_t num_of_sets; +} __attribute__ ((packed)); +struct bt_hci_cmd_ext_adv_set { + uint8_t handle; + uint16_t duration; + uint8_t max_events; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_MAX_ADV_DATA_LEN 0x203a +struct bt_hci_rsp_le_read_max_adv_data_len { + uint8_t status; + uint16_t max_len; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_NUM_SUPPORTED_ADV_SETS 0x203b +struct bt_hci_rsp_le_read_num_supported_adv_sets { + uint8_t status; + uint8_t num_of_sets; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_REMOVE_ADV_SET 0x203c +struct bt_hci_cmd_le_remove_adv_set { + uint8_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_CLEAR_ADV_SETS 0x203d + +#define BT_HCI_CMD_LE_SET_PERIODIC_ADV_PARAMS 0x203e +struct bt_hci_cmd_le_set_periodic_adv_params { + uint8_t handle; + uint16_t min_interval; + uint16_t max_interval; + uint16_t properties; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_PERIODIC_ADV_DATA 0x203f +struct bt_hci_cmd_le_set_periodic_adv_data { + uint8_t handle; + uint8_t operation; + uint8_t data_len; + uint8_t data[0]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_PERIODIC_ADV_ENABLE 0x2040 +struct bt_hci_cmd_le_set_periodic_adv_enable { + uint8_t enable; + uint8_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_EXT_SCAN_PARAMS 0x2041 +struct bt_hci_cmd_le_set_ext_scan_params { + uint8_t own_addr_type; + uint8_t filter_policy; + uint8_t num_phys; + uint8_t data[0]; +} __attribute__ ((packed)); +struct bt_hci_le_scan_phy { + uint8_t type; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_EXT_SCAN_ENABLE 0x2042 +struct bt_hci_cmd_le_set_ext_scan_enable { + uint8_t enable; + uint8_t filter_dup; + uint16_t duration; + uint16_t period; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_EXT_CREATE_CONN 0x2043 +struct bt_hci_cmd_le_ext_create_conn { + uint8_t filter_policy; + uint8_t own_addr_type; + uint8_t peer_addr_type; + uint8_t peer_addr[6]; + uint8_t phys; + uint8_t data[0]; +} __attribute__ ((packed)); +struct bt_hci_le_ext_create_conn { + uint16_t scan_interval; + uint16_t scan_window; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t supv_timeout; + uint16_t min_length; + uint16_t max_length; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_PERIODIC_ADV_CREATE_SYNC 0x2044 +struct bt_hci_cmd_le_periodic_adv_create_sync { + uint8_t filter_policy; + uint8_t sid; + uint8_t addr_type; + uint8_t addr[6]; + uint16_t skip; + uint16_t sync_timeout; + uint8_t unused; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_PERIODIC_ADV_CREATE_SYNC_CANCEL 0x2045 + +#define BT_HCI_CMD_LE_PERIODIC_ADV_TERM_SYNC 0x2046 +struct bt_hci_cmd_le_periodic_adv_term_sync { + uint16_t sync_handle; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_ADD_DEV_PERIODIC_ADV_LIST 0x2047 +struct bt_hci_cmd_le_add_dev_periodic_adv_list { + uint8_t addr_type; + uint8_t addr[6]; + uint8_t sid; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_REMOVE_DEV_PERIODIC_ADV_LIST 0x2048 +struct bt_hci_cmd_le_remove_dev_periodic_adv_list { + uint8_t addr_type; + uint8_t addr[6]; + uint8_t sid; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_CLEAR_PERIODIC_ADV_LIST 0x2049 + +#define BT_HCI_CMD_LE_READ_PERIODIC_ADV_LIST_SIZE 0x204a +struct bt_hci_rsp_le_read_dev_periodic_adv_list_size { + uint8_t status; + uint8_t list_size; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_TX_POWER 0x204b +struct bt_hci_rsp_le_read_tx_power { + uint8_t status; + int8_t min_tx_power; + int8_t max_tx_power; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_READ_RF_PATH_COMPENSATION 0x204c +struct bt_hci_rsp_le_read_rf_path_comp { + uint8_t status; + uint16_t rf_tx_path_comp; + uint16_t rf_rx_path_comp; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_WRITE_RF_PATH_COMPENSATION 0x204d +struct bt_hci_cmd_le_write_rf_path_comp { + uint16_t rf_tx_path_comp; + uint16_t rf_rx_path_comp; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_SET_PRIV_MODE 0x204e +struct bt_hci_cmd_le_set_priv_mode { + uint8_t peer_id_addr_type; + uint8_t peer_id_addr[6]; + uint8_t priv_mode; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_RECEIVER_TEST_V3 0x204f +struct bt_hci_cmd_le_receiver_test_v3 { + uint8_t rx_chan; + uint8_t phy; + uint8_t mod_index; + uint8_t cte_len; + uint8_t cte_type; + uint8_t duration; + uint8_t num_antenna_id; + uint8_t antenna_ids[0]; +} __attribute__ ((packed)); + +#define BT_HCI_CMD_LE_TX_TEST_V3 0x2050 +struct bt_hci_cmd_le_tx_test_v3 { + uint8_t chan; + uint8_t data_len; + uint8_t payload; + uint8_t phy; + uint8_t cte_len; + uint8_t cte_type; + uint8_t duration; + uint8_t num_antenna_id; + uint8_t antenna_ids[0]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_INQUIRY_COMPLETE 0x01 +struct bt_hci_evt_inquiry_complete { + uint8_t status; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_INQUIRY_RESULT 0x02 +struct bt_hci_evt_inquiry_result { + uint8_t num_resp; + uint8_t bdaddr[6]; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_CONN_COMPLETE 0x03 +struct bt_hci_evt_conn_complete { + uint8_t status; + uint16_t handle; + uint8_t bdaddr[6]; + uint8_t link_type; + uint8_t encr_mode; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_CONN_REQUEST 0x04 +struct bt_hci_evt_conn_request { + uint8_t bdaddr[6]; + uint8_t dev_class[3]; + uint8_t link_type; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_DISCONNECT_COMPLETE 0x05 +struct bt_hci_evt_disconnect_complete { + uint8_t status; + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_AUTH_COMPLETE 0x06 +struct bt_hci_evt_auth_complete { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_REMOTE_NAME_REQUEST_COMPLETE 0x07 +struct bt_hci_evt_remote_name_request_complete { + uint8_t status; + uint8_t bdaddr[6]; + uint8_t name[248]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_ENCRYPT_CHANGE 0x08 +struct bt_hci_evt_encrypt_change { + uint8_t status; + uint16_t handle; + uint8_t encr_mode; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_CHANGE_CONN_LINK_KEY_COMPLETE 0x09 +struct bt_hci_evt_change_conn_link_key_complete { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_MASTER_LINK_KEY_COMPLETE 0x0a +struct bt_hci_evt_master_link_key_complete { + uint8_t status; + uint16_t handle; + uint8_t key_flag; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_REMOTE_FEATURES_COMPLETE 0x0b +struct bt_hci_evt_remote_features_complete { + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_REMOTE_VERSION_COMPLETE 0x0c +struct bt_hci_evt_remote_version_complete { + uint8_t status; + uint16_t handle; + uint8_t lmp_ver; + uint16_t manufacturer; + uint16_t lmp_subver; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_QOS_SETUP_COMPLETE 0x0d +struct bt_hci_evt_qos_setup_complete { + uint8_t status; + uint16_t handle; + uint8_t flags; + uint8_t service_type; + uint32_t token_rate; + uint32_t peak_bandwidth; + uint32_t latency; + uint32_t delay_variation; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_CMD_COMPLETE 0x0e +struct bt_hci_evt_cmd_complete { + uint8_t ncmd; + uint16_t opcode; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_CMD_STATUS 0x0f +struct bt_hci_evt_cmd_status { + uint8_t status; + uint8_t ncmd; + uint16_t opcode; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_HARDWARE_ERROR 0x10 +struct bt_hci_evt_hardware_error { + uint8_t code; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_FLUSH_OCCURRED 0x11 +struct bt_hci_evt_flush_occurred { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_ROLE_CHANGE 0x12 +struct bt_hci_evt_role_change { + uint8_t status; + uint8_t bdaddr[6]; + uint8_t role; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_NUM_COMPLETED_PACKETS 0x13 +struct bt_hci_evt_num_completed_packets { + uint8_t num_handles; + uint16_t handle; + uint16_t count; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_MODE_CHANGE 0x14 +struct bt_hci_evt_mode_change { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint16_t interval; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_RETURN_LINK_KEYS 0x15 +struct bt_hci_evt_return_link_keys { + uint8_t num_keys; + uint8_t keys[0]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_PIN_CODE_REQUEST 0x16 +struct bt_hci_evt_pin_code_request { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LINK_KEY_REQUEST 0x17 +struct bt_hci_evt_link_key_request { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LINK_KEY_NOTIFY 0x18 +struct bt_hci_evt_link_key_notify { + uint8_t bdaddr[6]; + uint8_t link_key[16]; + uint8_t key_type; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LOOPBACK_COMMAND 0x19 + +#define BT_HCI_EVT_DATA_BUFFER_OVERFLOW 0x1a +struct bt_hci_evt_data_buffer_overflow { + uint8_t link_type; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_MAX_SLOTS_CHANGE 0x1b +struct bt_hci_evt_max_slots_change { + uint16_t handle; + uint8_t max_slots; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_CLOCK_OFFSET_COMPLETE 0x1c +struct bt_hci_evt_clock_offset_complete { + uint8_t status; + uint16_t handle; + uint16_t clock_offset; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_CONN_PKT_TYPE_CHANGED 0x1d +struct bt_hci_evt_conn_pkt_type_changed { + uint8_t status; + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_QOS_VIOLATION 0x1e +struct bt_hci_evt_qos_violation { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_PSCAN_MODE_CHANGE 0x1f +struct bt_hci_evt_pscan_mode_change { + uint8_t bdaddr[6]; + uint8_t pscan_mode; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_PSCAN_REP_MODE_CHANGE 0x20 +struct bt_hci_evt_pscan_rep_mode_change { + uint8_t bdaddr[6]; + uint8_t pscan_rep_mode; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_FLOW_SPEC_COMPLETE 0x21 +struct bt_hci_evt_flow_spec_complete { + uint8_t status; + uint16_t handle; + uint8_t flags; + uint8_t direction; + uint8_t service_type; + uint32_t token_rate; + uint32_t token_bucket_size; + uint32_t peak_bandwidth; + uint32_t access_latency; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_INQUIRY_RESULT_WITH_RSSI 0x22 +struct bt_hci_evt_inquiry_result_with_rssi { + uint8_t num_resp; + uint8_t bdaddr[6]; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_REMOTE_EXT_FEATURES_COMPLETE 0x23 +struct bt_hci_evt_remote_ext_features_complete { + uint8_t status; + uint16_t handle; + uint8_t page; + uint8_t max_page; + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_SYNC_CONN_COMPLETE 0x2c +struct bt_hci_evt_sync_conn_complete { + uint8_t status; + uint16_t handle; + uint8_t bdaddr[6]; + uint8_t link_type; + uint8_t tx_interval; + uint8_t retrans_window; + uint16_t rx_pkt_len; + uint16_t tx_pkt_len; + uint8_t air_mode; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_SYNC_CONN_CHANGED 0x2d +struct bt_hci_evt_sync_conn_changed { + uint8_t status; + uint16_t handle; + uint8_t tx_interval; + uint8_t retrans_window; + uint16_t rx_pkt_len; + uint16_t tx_pkt_len; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_SNIFF_SUBRATING 0x2e +struct bt_hci_evt_sniff_subrating { + uint8_t status; + uint16_t handle; + uint16_t max_tx_latency; + uint16_t max_rx_latency; + uint16_t min_remote_timeout; + uint16_t min_local_timeout; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_EXT_INQUIRY_RESULT 0x2f +struct bt_hci_evt_ext_inquiry_result { + uint8_t num_resp; + uint8_t bdaddr[6]; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_ENCRYPT_KEY_REFRESH_COMPLETE 0x30 +struct bt_hci_evt_encrypt_key_refresh_complete { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_IO_CAPABILITY_REQUEST 0x31 +struct bt_hci_evt_io_capability_request { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_IO_CAPABILITY_RESPONSE 0x32 +struct bt_hci_evt_io_capability_response { + uint8_t bdaddr[6]; + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_USER_CONFIRM_REQUEST 0x33 +struct bt_hci_evt_user_confirm_request { + uint8_t bdaddr[6]; + uint32_t passkey; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_USER_PASSKEY_REQUEST 0x34 +struct bt_hci_evt_user_passkey_request { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_REMOTE_OOB_DATA_REQUEST 0x35 +struct bt_hci_evt_remote_oob_data_request { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_SIMPLE_PAIRING_COMPLETE 0x36 +struct bt_hci_evt_simple_pairing_complete { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LINK_SUPV_TIMEOUT_CHANGED 0x38 +struct bt_hci_evt_link_supv_timeout_changed { + uint16_t handle; + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_ENHANCED_FLUSH_COMPLETE 0x39 +struct bt_hci_evt_enhanced_flush_complete { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_USER_PASSKEY_NOTIFY 0x3b +struct bt_hci_evt_user_passkey_notify { + uint8_t bdaddr[6]; + uint32_t passkey; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_KEYPRESS_NOTIFY 0x3c +struct bt_hci_evt_keypress_notify { + uint8_t bdaddr[6]; + uint8_t type; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_REMOTE_HOST_FEATURES_NOTIFY 0x3d +struct bt_hci_evt_remote_host_features_notify { + uint8_t bdaddr[6]; + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_META_EVENT 0x3e + +#define BT_HCI_EVT_PHY_LINK_COMPLETE 0x40 +struct bt_hci_evt_phy_link_complete { + uint8_t status; + uint8_t phy_handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_CHANNEL_SELECTED 0x41 +struct bt_hci_evt_channel_selected { + uint8_t phy_handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_DISCONN_PHY_LINK_COMPLETE 0x42 +struct bt_hci_evt_disconn_phy_link_complete { + uint8_t status; + uint8_t phy_handle; + uint8_t reason; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_PHY_LINK_LOSS_EARLY_WARNING 0x43 +struct bt_hci_evt_phy_link_loss_early_warning { + uint8_t phy_handle; + uint8_t reason; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_PHY_LINK_RECOVERY 0x44 +struct bt_hci_evt_phy_link_recovery { + uint8_t phy_handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LOGIC_LINK_COMPLETE 0x45 +struct bt_hci_evt_logic_link_complete { + uint8_t status; + uint16_t handle; + uint8_t phy_handle; + uint8_t flow_spec; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_DISCONN_LOGIC_LINK_COMPLETE 0x46 +struct bt_hci_evt_disconn_logic_link_complete { + uint8_t status; + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_FLOW_SPEC_MODIFY_COMPLETE 0x47 +struct bt_hci_evt_flow_spec_modify_complete { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_NUM_COMPLETED_DATA_BLOCKS 0x48 +struct bt_hci_evt_num_completed_data_blocks { + uint16_t total_num_blocks; + uint8_t num_handles; + uint16_t handle; + uint16_t num_packets; + uint16_t num_blocks; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_SHORT_RANGE_MODE_CHANGE 0x4c +struct bt_hci_evt_short_range_mode_change { + uint8_t status; + uint8_t phy_handle; + uint8_t mode; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_AMP_STATUS_CHANGE 0x4d +struct bt_hci_evt_amp_status_change { + uint8_t status; + uint8_t amp_status; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_TRIGGERED_CLOCK_CAPTURE 0x4e +struct bt_hci_evt_triggered_clock_capture { + uint16_t handle; + uint8_t type; + uint32_t clock; + uint16_t clock_offset; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_SYNC_TRAIN_COMPLETE 0x4f +struct bt_hci_evt_sync_train_complete { + uint8_t status; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_SYNC_TRAIN_RECEIVED 0x50 +struct bt_hci_evt_sync_train_received { + uint8_t status; + uint8_t bdaddr[6]; + uint32_t offset; + uint8_t map[10]; + uint8_t lt_addr; + uint32_t instant; + uint16_t interval; + uint8_t service_data; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_SLAVE_BROADCAST_RECEIVE 0x51 +struct bt_hci_evt_slave_broadcast_receive { + uint8_t bdaddr[6]; + uint8_t lt_addr; + uint32_t clock; + uint32_t offset; + uint8_t status; + uint8_t fragment; + uint8_t length; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_SLAVE_BROADCAST_TIMEOUT 0x52 +struct bt_hci_evt_slave_broadcast_timeout { + uint8_t bdaddr[6]; + uint8_t lt_addr; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_TRUNCATED_PAGE_COMPLETE 0x53 +struct bt_hci_evt_truncated_page_complete { + uint8_t status; + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_SLAVE_PAGE_RESPONSE_TIMEOUT 0x54 + +#define BT_HCI_EVT_SLAVE_BROADCAST_CHANNEL_MAP_CHANGE 0x55 +struct bt_hci_evt_slave_broadcast_channel_map_change { + uint8_t map[10]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_INQUIRY_RESPONSE_NOTIFY 0x56 +struct bt_hci_evt_inquiry_response_notify { + uint8_t lap[3]; + int8_t rssi; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_AUTH_PAYLOAD_TIMEOUT_EXPIRED 0x57 +struct bt_hci_evt_auth_payload_timeout_expired { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_CONN_COMPLETE 0x01 +struct bt_hci_evt_le_conn_complete { + uint8_t status; + uint16_t handle; + uint8_t role; + uint8_t peer_addr_type; + uint8_t peer_addr[6]; + uint16_t interval; + uint16_t latency; + uint16_t supv_timeout; + uint8_t clock_accuracy; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_ADV_REPORT 0x02 +struct bt_hci_evt_le_adv_report { + uint8_t num_reports; + uint8_t event_type; + uint8_t addr_type; + uint8_t addr[6]; + uint8_t data_len; + uint8_t data[0]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE 0x03 +struct bt_hci_evt_le_conn_update_complete { + uint8_t status; + uint16_t handle; + uint16_t interval; + uint16_t latency; + uint16_t supv_timeout; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_REMOTE_FEATURES_COMPLETE 0x04 +struct bt_hci_evt_le_remote_features_complete { + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_LONG_TERM_KEY_REQUEST 0x05 +struct bt_hci_evt_le_long_term_key_request { + uint16_t handle; + uint64_t rand; + uint16_t ediv; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_CONN_PARAM_REQUEST 0x06 +struct bt_hci_evt_le_conn_param_request { + uint16_t handle; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t supv_timeout; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_DATA_LENGTH_CHANGE 0x07 +struct bt_hci_evt_le_data_length_change { + uint16_t handle; + uint16_t max_tx_len; + uint16_t max_tx_time; + uint16_t max_rx_len; + uint16_t max_rx_time; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_READ_LOCAL_PK256_COMPLETE 0x08 +struct bt_hci_evt_le_read_local_pk256_complete { + uint8_t status; + uint8_t local_pk256[64]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_GENERATE_DHKEY_COMPLETE 0x09 +struct bt_hci_evt_le_generate_dhkey_complete { + uint8_t status; + uint8_t dhkey[32]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_ENHANCED_CONN_COMPLETE 0x0a +struct bt_hci_evt_le_enhanced_conn_complete { + uint8_t status; + uint16_t handle; + uint8_t role; + uint8_t peer_addr_type; + uint8_t peer_addr[6]; + uint8_t local_rpa[6]; + uint8_t peer_rpa[6]; + uint16_t interval; + uint16_t latency; + uint16_t supv_timeout; + uint8_t clock_accuracy; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_DIRECT_ADV_REPORT 0x0b +struct bt_hci_evt_le_direct_adv_report { + uint8_t num_reports; + uint8_t event_type; + uint8_t addr_type; + uint8_t addr[6]; + uint8_t direct_addr_type; + uint8_t direct_addr[6]; + int8_t rssi; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_PHY_UPDATE_COMPLETE 0x0c +struct bt_hci_evt_le_phy_update_complete { + uint8_t status; + uint16_t handle; + uint8_t tx_phy; + uint8_t rx_phy; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_EXT_ADV_REPORT 0x0d +struct bt_hci_evt_le_ext_adv_report { + uint8_t num_reports; +} __attribute__ ((packed)); +struct bt_hci_le_ext_adv_report { + uint16_t event_type; + uint8_t addr_type; + uint8_t addr[6]; + uint8_t primary_phy; + uint8_t secondary_phy; + uint8_t sid; + uint8_t tx_power; + int8_t rssi; + uint16_t interval; + uint8_t direct_addr_type; + uint8_t direct_addr[6]; + uint8_t data_len; + uint8_t data[0]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_PER_SYNC_ESTABLISHED 0x0e +struct bt_hci_evt_le_per_sync_established { + uint8_t status; + uint16_t handle; + uint8_t sid; + uint8_t addr_type; + uint8_t addr[6]; + uint8_t phy; + uint16_t interval; + uint8_t clock_accuracy; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_PER_ADV_REPORT 0x0f +struct bt_hci_le_per_adv_report { + uint16_t handle; + uint8_t tx_power; + int8_t rssi; + uint8_t unused; + uint8_t data_status; + uint8_t data_len; + uint8_t data[0]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_PER_SYNC_LOST 0x10 +struct bt_hci_evt_le_per_sync_lost { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_ADV_SET_TERM 0x12 +struct bt_hci_evt_le_adv_set_term { + uint8_t status; + uint8_t handle; + uint16_t conn_handle; + uint8_t num_evts; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_SCAN_REQ_RECEIVED 0x13 +struct bt_hci_evt_le_scan_req_received { + uint8_t handle; + uint8_t scanner_addr_type; + uint8_t scanner_addr[6]; +} __attribute__ ((packed)); + +#define BT_HCI_EVT_LE_CHAN_SELECT_ALG 0x14 +struct bt_hci_evt_le_chan_select_alg { + uint16_t handle; + uint8_t algorithm; +} __attribute__ ((packed)); + +#define BT_HCI_ERR_SUCCESS 0x00 +#define BT_HCI_ERR_UNKNOWN_COMMAND 0x01 +#define BT_HCI_ERR_UNKNOWN_CONN_ID 0x02 +#define BT_HCI_ERR_HARDWARE_FAILURE 0x03 +#define BT_HCI_ERR_PAGE_TIMEOUT 0x04 +#define BT_HCI_ERR_AUTH_FAILURE 0x05 +#define BT_HCI_ERR_PIN_OR_KEY_MISSING 0x06 +#define BT_HCI_ERR_MEM_CAPACITY_EXCEEDED 0x07 +#define BT_HCI_ERR_COMMAND_DISALLOWED 0x0c +#define BT_HCI_ERR_UNSUPPORTED_FEATURE 0x11 +#define BT_HCI_ERR_INVALID_PARAMETERS 0x12 +#define BT_HCI_ERR_UNSPECIFIED_ERROR 0x1f +#define BT_HCI_ERR_CONN_FAILED_TO_ESTABLISH 0x3e + +struct bt_l2cap_hdr { + uint16_t len; + uint16_t cid; +} __attribute__ ((packed)); + +struct bt_l2cap_hdr_sig { + uint8_t code; + uint8_t ident; + uint16_t len; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_CMD_REJECT 0x01 +struct bt_l2cap_pdu_cmd_reject { + uint16_t reason; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_CONN_REQ 0x02 +struct bt_l2cap_pdu_conn_req { + uint16_t psm; + uint16_t scid; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_CONN_RSP 0x03 +struct bt_l2cap_pdu_conn_rsp { + uint16_t dcid; + uint16_t scid; + uint16_t result; + uint16_t status; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_CONFIG_REQ 0x04 +struct bt_l2cap_pdu_config_req { + uint16_t dcid; + uint16_t flags; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_CONFIG_RSP 0x05 +struct bt_l2cap_pdu_config_rsp { + uint16_t scid; + uint16_t flags; + uint16_t result; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_DISCONN_REQ 0x06 +struct bt_l2cap_pdu_disconn_req { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_DISCONN_RSP 0x07 +struct bt_l2cap_pdu_disconn_rsp { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_ECHO_REQ 0x08 + +#define BT_L2CAP_PDU_ECHO_RSP 0x09 + +#define BT_L2CAP_PDU_INFO_REQ 0x0a +struct bt_l2cap_pdu_info_req { + uint16_t type; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_INFO_RSP 0x0b +struct bt_l2cap_pdu_info_rsp { + uint16_t type; + uint16_t result; + uint8_t data[0]; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_CREATE_CHAN_REQ 0x0c +struct bt_l2cap_pdu_create_chan_req { + uint16_t psm; + uint16_t scid; + uint8_t ctrlid; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_CREATE_CHAN_RSP 0x0d +struct bt_l2cap_pdu_create_chan_rsp { + uint16_t dcid; + uint16_t scid; + uint16_t result; + uint16_t status; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_MOVE_CHAN_REQ 0x0e +struct bt_l2cap_pdu_move_chan_req { + uint16_t icid; + uint8_t ctrlid; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_MOVE_CHAN_RSP 0x0f +struct bt_l2cap_pdu_move_chan_rsp { + uint16_t icid; + uint16_t result; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_MOVE_CHAN_CFM 0x10 +struct bt_l2cap_pdu_move_chan_cfm { + uint16_t icid; + uint16_t result; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_MOVE_CHAN_CFM_RSP 0x11 +struct bt_l2cap_pdu_move_chan_cfm_rsp { + uint16_t icid; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_CONN_PARAM_REQ 0x12 +struct bt_l2cap_pdu_conn_param_req { + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t timeout; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_CONN_PARAM_RSP 0x13 +struct bt_l2cap_pdu_conn_param_rsp { + uint16_t result; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_LE_CONN_REQ 0x14 +struct bt_l2cap_pdu_le_conn_req { + uint16_t psm; + uint16_t scid; + uint16_t mtu; + uint16_t mps; + uint16_t credits; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_LE_CONN_RSP 0x15 +struct bt_l2cap_pdu_le_conn_rsp { + uint16_t dcid; + uint16_t mtu; + uint16_t mps; + uint16_t credits; + uint16_t result; +} __attribute__ ((packed)); + +#define BT_L2CAP_PDU_LE_FLOWCTL_CREDS 0x16 +struct bt_l2cap_pdu_le_flowctl_creds { + uint16_t cid; + uint16_t credits; +} __attribute__ ((packed)); + +struct bt_l2cap_hdr_connless { + uint16_t psm; +} __attribute__ ((packed)); + +struct bt_l2cap_hdr_amp { + uint8_t code; + uint8_t ident; + uint16_t len; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_CMD_REJECT 0x01 +struct bt_l2cap_amp_cmd_reject { + uint16_t reason; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_DISCOVER_REQ 0x02 +struct bt_l2cap_amp_discover_req { + uint16_t size; + uint16_t features; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_DISCOVER_RSP 0x03 +struct bt_l2cap_amp_discover_rsp { + uint16_t size; + uint16_t features; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_CHANGE_NOTIFY 0x04 + +#define BT_L2CAP_AMP_CHANGE_RESPONSE 0x05 + +#define BT_L2CAP_AMP_GET_INFO_REQ 0x06 +struct bt_l2cap_amp_get_info_req { + uint8_t ctrlid; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_GET_INFO_RSP 0x07 +struct bt_l2cap_amp_get_info_rsp { + uint8_t ctrlid; + uint8_t status; + uint32_t total_bw; + uint32_t max_bw; + uint32_t min_latency; + uint16_t pal_cap; + uint16_t max_assoc_len; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_GET_ASSOC_REQ 0x08 +struct bt_l2cap_amp_get_assoc_req { + uint8_t ctrlid; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_GET_ASSOC_RSP 0x09 +struct bt_l2cap_amp_get_assoc_rsp { + uint8_t ctrlid; + uint8_t status; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_CREATE_PHY_LINK_REQ 0x0a +struct bt_l2cap_amp_create_phy_link_req { + uint8_t local_ctrlid; + uint8_t remote_ctrlid; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_CREATE_PHY_LINK_RSP 0x0b +struct bt_l2cap_amp_create_phy_link_rsp { + uint8_t local_ctrlid; + uint8_t remote_ctrlid; + uint8_t status; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_DISCONN_PHY_LINK_REQ 0x0c +struct bt_l2cap_amp_disconn_phy_link_req { + uint8_t local_ctrlid; + uint8_t remote_ctrlid; +} __attribute__ ((packed)); + +#define BT_L2CAP_AMP_DISCONN_PHY_LINK_RSP 0x0d +struct bt_l2cap_amp_disconn_phy_link_rsp { + uint8_t local_ctrlid; + uint8_t remote_ctrlid; + uint8_t status; +} __attribute__ ((packed)); + +struct bt_l2cap_hdr_att { + uint8_t code; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_ERROR_RESPONSE 0x01 +struct bt_l2cap_att_error_response { + uint8_t request; + uint16_t handle; + uint8_t error; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_EXCHANGE_MTU_REQ 0x02 +struct bt_l2cap_att_exchange_mtu_req { + uint16_t mtu; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_EXCHANGE_MTU_RSP 0x03 +struct bt_l2cap_att_exchange_mtu_rsp { + uint16_t mtu; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_READ_TYPE_REQ 0x08 +struct bt_l2cap_att_read_type_req { + uint16_t start_handle; + uint16_t end_handle; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_READ_TYPE_RSP 0x09 +struct bt_l2cap_att_read_type_rsp { + uint8_t length; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_READ_REQ 0x0a +struct bt_l2cap_att_read_req { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_READ_RSP 0x0b + +#define BT_L2CAP_ATT_READ_GROUP_TYPE_REQ 0x10 +struct bt_l2cap_att_read_group_type_req { + uint16_t start_handle; + uint16_t end_handle; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_READ_GROUP_TYPE_RSP 0x11 +struct bt_l2cap_att_read_group_type_rsp { + uint8_t length; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_HANDLE_VALUE_NOTIFY 0x1b +struct bt_l2cap_att_handle_value_notify { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_HANDLE_VALUE_IND 0x1d +struct bt_l2cap_att_handle_value_ind { + uint16_t handle; +} __attribute__ ((packed)); + +#define BT_L2CAP_ATT_HANDLE_VALUE_CONF 0x1e + +struct bt_l2cap_hdr_smp { + uint8_t code; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_PAIRING_REQUEST 0x01 +struct bt_l2cap_smp_pairing_request { + uint8_t io_capa; + uint8_t oob_data; + uint8_t auth_req; + uint8_t max_key_size; + uint8_t init_key_dist; + uint8_t resp_key_dist; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_PAIRING_RESPONSE 0x02 +struct bt_l2cap_smp_pairing_response { + uint8_t io_capa; + uint8_t oob_data; + uint8_t auth_req; + uint8_t max_key_size; + uint8_t init_key_dist; + uint8_t resp_key_dist; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_PAIRING_CONFIRM 0x03 +struct bt_l2cap_smp_pairing_confirm { + uint8_t value[16]; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_PAIRING_RANDOM 0x04 +struct bt_l2cap_smp_pairing_random { + uint8_t value[16]; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_PAIRING_FAILED 0x05 +struct bt_l2cap_smp_pairing_failed { + uint8_t reason; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_ENCRYPT_INFO 0x06 +struct bt_l2cap_smp_encrypt_info { + uint8_t ltk[16]; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_MASTER_IDENT 0x07 +struct bt_l2cap_smp_master_ident { + uint16_t ediv; + uint64_t rand; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_IDENT_INFO 0x08 +struct bt_l2cap_smp_ident_info { + uint8_t irk[16]; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_IDENT_ADDR_INFO 0x09 +struct bt_l2cap_smp_ident_addr_info { + uint8_t addr_type; + uint8_t addr[6]; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_SIGNING_INFO 0x0a +struct bt_l2cap_smp_signing_info { + uint8_t csrk[16]; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_SECURITY_REQUEST 0x0b +struct bt_l2cap_smp_security_request { + uint8_t auth_req; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_PUBLIC_KEY 0x0c +struct bt_l2cap_smp_public_key { + uint8_t x[32]; + uint8_t y[32]; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_DHKEY_CHECK 0x0d +struct bt_l2cap_smp_dhkey_check { + uint8_t e[16]; +} __attribute__ ((packed)); + +#define BT_L2CAP_SMP_KEYPRESS_NOTIFY 0x0e +struct bt_l2cap_smp_keypress_notify { + uint8_t type; +} __attribute__ ((packed)); + +struct bt_sdp_hdr { + uint8_t pdu; + uint16_t tid; + uint16_t plen; +} __attribute__ ((packed)); diff --git a/monitor/control.c b/monitor/control.c new file mode 100644 index 0000000..1e9054d --- /dev/null +++ b/monitor/control.c @@ -0,0 +1,1570 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/mgmt.h" + +#include "src/shared/util.h" +#include "src/shared/btsnoop.h" +#include "src/shared/mainloop.h" + +#include "display.h" +#include "packet.h" +#include "hcidump.h" +#include "ellisys.h" +#include "tty.h" +#include "control.h" +#include "jlink.h" + +static struct btsnoop *btsnoop_file = NULL; +static bool hcidump_fallback = false; +static bool decode_control = true; +static uint16_t filter_index = HCI_DEV_NONE; + +struct control_data { + uint16_t channel; + int fd; + unsigned char buf[BTSNOOP_MAX_PACKET_SIZE]; + uint16_t offset; +}; + +static void free_data(void *user_data) +{ + struct control_data *data = user_data; + + close(data->fd); + + free(data); +} + +static void mgmt_index_added(uint16_t len, const void *buf) +{ + printf("@ Index Added\n"); + + packet_hexdump(buf, len); +} + +static void mgmt_index_removed(uint16_t len, const void *buf) +{ + printf("@ Index Removed\n"); + + packet_hexdump(buf, len); +} + +static void mgmt_unconf_index_added(uint16_t len, const void *buf) +{ + printf("@ Unconfigured Index Added\n"); + + packet_hexdump(buf, len); +} + +static void mgmt_unconf_index_removed(uint16_t len, const void *buf) +{ + printf("@ Unconfigured Index Removed\n"); + + packet_hexdump(buf, len); +} + +static void mgmt_ext_index_added(uint16_t len, const void *buf) +{ + const struct mgmt_ev_ext_index_added *ev = buf; + + if (len < sizeof(*ev)) { + printf("* Malformed Extended Index Added control\n"); + return; + } + + printf("@ Extended Index Added: %u (%u)\n", ev->type, ev->bus); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_ext_index_removed(uint16_t len, const void *buf) +{ + const struct mgmt_ev_ext_index_removed *ev = buf; + + if (len < sizeof(*ev)) { + printf("* Malformed Extended Index Removed control\n"); + return; + } + + printf("@ Extended Index Removed: %u (%u)\n", ev->type, ev->bus); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_controller_error(uint16_t len, const void *buf) +{ + const struct mgmt_ev_controller_error *ev = buf; + + if (len < sizeof(*ev)) { + printf("* Malformed Controller Error control\n"); + return; + } + + printf("@ Controller Error: 0x%2.2x\n", ev->error_code); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +#ifndef NELEM +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) +#endif + +static const char *config_options_str[] = { + "external", "public-address", +}; + +static void mgmt_new_config_options(uint16_t len, const void *buf) +{ + uint32_t options; + unsigned int i; + + if (len < 4) { + printf("* Malformed New Configuration Options control\n"); + return; + } + + options = get_le32(buf); + + printf("@ New Configuration Options: 0x%4.4x\n", options); + + if (options) { + printf("%-12c", ' '); + for (i = 0; i < NELEM(config_options_str); i++) { + if (options & (1 << i)) + printf("%s ", config_options_str[i]); + } + printf("\n"); + } + + buf += 4; + len -= 4; + + packet_hexdump(buf, len); +} + +static const char *settings_str[] = { + "powered", "connectable", "fast-connectable", "discoverable", + "bondable", "link-security", "ssp", "br/edr", "hs", "le", + "advertising", "secure-conn", "debug-keys", "privacy", + "configuration", "static-addr", +}; + +static void mgmt_new_settings(uint16_t len, const void *buf) +{ + uint32_t settings; + unsigned int i; + + if (len < 4) { + printf("* Malformed New Settings control\n"); + return; + } + + settings = get_le32(buf); + + printf("@ New Settings: 0x%4.4x\n", settings); + + if (settings) { + printf("%-12c", ' '); + for (i = 0; i < NELEM(settings_str); i++) { + if (settings & (1 << i)) + printf("%s ", settings_str[i]); + } + printf("\n"); + } + + buf += 4; + len -= 4; + + packet_hexdump(buf, len); +} + +static void mgmt_class_of_dev_changed(uint16_t len, const void *buf) +{ + const struct mgmt_ev_class_of_dev_changed *ev = buf; + + if (len < sizeof(*ev)) { + printf("* Malformed Class of Device Changed control\n"); + return; + } + + printf("@ Class of Device Changed: 0x%2.2x%2.2x%2.2x\n", + ev->dev_class[2], + ev->dev_class[1], + ev->dev_class[0]); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_local_name_changed(uint16_t len, const void *buf) +{ + const struct mgmt_ev_local_name_changed *ev = buf; + + if (len < sizeof(*ev)) { + printf("* Malformed Local Name Changed control\n"); + return; + } + + printf("@ Local Name Changed: %s (%s)\n", ev->name, ev->short_name); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_new_link_key(uint16_t len, const void *buf) +{ + const struct mgmt_ev_new_link_key *ev = buf; + const char *type; + char str[18]; + static const char *types[] = { + "Combination key", + "Local Unit key", + "Remote Unit key", + "Debug Combination key", + "Unauthenticated Combination key from P-192", + "Authenticated Combination key from P-192", + "Changed Combination key", + "Unauthenticated Combination key from P-256", + "Authenticated Combination key from P-256", + }; + + if (len < sizeof(*ev)) { + printf("* Malformed New Link Key control\n"); + return; + } + + if (ev->key.type < NELEM(types)) + type = types[ev->key.type]; + else + type = "Reserved"; + + ba2str(&ev->key.addr.bdaddr, str); + + printf("@ New Link Key: %s (%d) %s (%u)\n", str, + ev->key.addr.type, type, ev->key.type); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_new_long_term_key(uint16_t len, const void *buf) +{ + const struct mgmt_ev_new_long_term_key *ev = buf; + const char *type; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed New Long Term Key control\n"); + return; + } + + /* LE SC keys are both for master and slave */ + switch (ev->key.type) { + case 0x00: + if (ev->key.master) + type = "Master (Unauthenticated)"; + else + type = "Slave (Unauthenticated)"; + break; + case 0x01: + if (ev->key.master) + type = "Master (Authenticated)"; + else + type = "Slave (Authenticated)"; + break; + case 0x02: + type = "SC (Unauthenticated)"; + break; + case 0x03: + type = "SC (Authenticated)"; + break; + case 0x04: + type = "SC (Debug)"; + break; + default: + type = ""; + break; + } + + ba2str(&ev->key.addr.bdaddr, str); + + printf("@ New Long Term Key: %s (%d) %s 0x%02x\n", str, + ev->key.addr.type, type, ev->key.type); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_device_connected(uint16_t len, const void *buf) +{ + const struct mgmt_ev_device_connected *ev = buf; + uint32_t flags; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed Device Connected control\n"); + return; + } + + flags = le32_to_cpu(ev->flags); + ba2str(&ev->addr.bdaddr, str); + + printf("@ Device Connected: %s (%d) flags 0x%4.4x\n", + str, ev->addr.type, flags); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_device_disconnected(uint16_t len, const void *buf) +{ + const struct mgmt_ev_device_disconnected *ev = buf; + char str[18]; + uint8_t reason; + uint16_t consumed_len; + + if (len < sizeof(struct mgmt_addr_info)) { + printf("* Malformed Device Disconnected control\n"); + return; + } + + if (len < sizeof(*ev)) { + reason = MGMT_DEV_DISCONN_UNKNOWN; + consumed_len = len; + } else { + reason = ev->reason; + consumed_len = sizeof(*ev); + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ Device Disconnected: %s (%d) reason %u\n", str, ev->addr.type, + reason); + + buf += consumed_len; + len -= consumed_len; + + packet_hexdump(buf, len); +} + +static void mgmt_connect_failed(uint16_t len, const void *buf) +{ + const struct mgmt_ev_connect_failed *ev = buf; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed Connect Failed control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ Connect Failed: %s (%d) status 0x%2.2x\n", + str, ev->addr.type, ev->status); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_pin_code_request(uint16_t len, const void *buf) +{ + const struct mgmt_ev_pin_code_request *ev = buf; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed PIN Code Request control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ PIN Code Request: %s (%d) secure 0x%2.2x\n", + str, ev->addr.type, ev->secure); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_user_confirm_request(uint16_t len, const void *buf) +{ + const struct mgmt_ev_user_confirm_request *ev = buf; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed User Confirmation Request control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ User Confirmation Request: %s (%d) hint %d value %d\n", + str, ev->addr.type, ev->confirm_hint, ev->value); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_user_passkey_request(uint16_t len, const void *buf) +{ + const struct mgmt_ev_user_passkey_request *ev = buf; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed User Passkey Request control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ User Passkey Request: %s (%d)\n", str, ev->addr.type); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_auth_failed(uint16_t len, const void *buf) +{ + const struct mgmt_ev_auth_failed *ev = buf; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed Authentication Failed control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ Authentication Failed: %s (%d) status 0x%2.2x\n", + str, ev->addr.type, ev->status); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_device_found(uint16_t len, const void *buf) +{ + const struct mgmt_ev_device_found *ev = buf; + uint32_t flags; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed Device Found control\n"); + return; + } + + flags = le32_to_cpu(ev->flags); + ba2str(&ev->addr.bdaddr, str); + + printf("@ Device Found: %s (%d) rssi %d flags 0x%4.4x\n", + str, ev->addr.type, ev->rssi, flags); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_discovering(uint16_t len, const void *buf) +{ + const struct mgmt_ev_discovering *ev = buf; + + if (len < sizeof(*ev)) { + printf("* Malformed Discovering control\n"); + return; + } + + printf("@ Discovering: 0x%2.2x (%d)\n", ev->discovering, ev->type); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_device_blocked(uint16_t len, const void *buf) +{ + const struct mgmt_ev_device_blocked *ev = buf; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed Device Blocked control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ Device Blocked: %s (%d)\n", str, ev->addr.type); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_device_unblocked(uint16_t len, const void *buf) +{ + const struct mgmt_ev_device_unblocked *ev = buf; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed Device Unblocked control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ Device Unblocked: %s (%d)\n", str, ev->addr.type); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_device_unpaired(uint16_t len, const void *buf) +{ + const struct mgmt_ev_device_unpaired *ev = buf; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed Device Unpaired control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ Device Unpaired: %s (%d)\n", str, ev->addr.type); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_passkey_notify(uint16_t len, const void *buf) +{ + const struct mgmt_ev_passkey_notify *ev = buf; + uint32_t passkey; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed Passkey Notify control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + passkey = le32_to_cpu(ev->passkey); + + printf("@ Passkey Notify: %s (%d) passkey %06u entered %u\n", + str, ev->addr.type, passkey, ev->entered); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_new_irk(uint16_t len, const void *buf) +{ + const struct mgmt_ev_new_irk *ev = buf; + char addr[18], rpa[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed New IRK control\n"); + return; + } + + ba2str(&ev->rpa, rpa); + ba2str(&ev->key.addr.bdaddr, addr); + + printf("@ New IRK: %s (%d) %s\n", addr, ev->key.addr.type, rpa); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_new_csrk(uint16_t len, const void *buf) +{ + const struct mgmt_ev_new_csrk *ev = buf; + const char *type; + char addr[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed New CSRK control\n"); + return; + } + + ba2str(&ev->key.addr.bdaddr, addr); + + switch (ev->key.type) { + case 0x00: + type = "Local Unauthenticated"; + break; + case 0x01: + type = "Remote Unauthenticated"; + break; + case 0x02: + type = "Local Authenticated"; + break; + case 0x03: + type = "Remote Authenticated"; + break; + default: + type = ""; + break; + } + + printf("@ New CSRK: %s (%d) %s (%u)\n", addr, ev->key.addr.type, + type, ev->key.type); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_device_added(uint16_t len, const void *buf) +{ + const struct mgmt_ev_device_added *ev = buf; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed Device Added control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ Device Added: %s (%d) %d\n", str, ev->addr.type, ev->action); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_device_removed(uint16_t len, const void *buf) +{ + const struct mgmt_ev_device_removed *ev = buf; + char str[18]; + + if (len < sizeof(*ev)) { + printf("* Malformed Device Removed control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, str); + + printf("@ Device Removed: %s (%d)\n", str, ev->addr.type); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_new_conn_param(uint16_t len, const void *buf) +{ + const struct mgmt_ev_new_conn_param *ev = buf; + char addr[18]; + uint16_t min, max, latency, timeout; + + if (len < sizeof(*ev)) { + printf("* Malformed New Connection Parameter control\n"); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + min = le16_to_cpu(ev->min_interval); + max = le16_to_cpu(ev->max_interval); + latency = le16_to_cpu(ev->latency); + timeout = le16_to_cpu(ev->timeout); + + printf("@ New Conn Param: %s (%d) hint %d min 0x%4.4x max 0x%4.4x " + "latency 0x%4.4x timeout 0x%4.4x\n", addr, ev->addr.type, + ev->store_hint, min, max, latency, timeout); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_advertising_added(uint16_t len, const void *buf) +{ + const struct mgmt_ev_advertising_added *ev = buf; + + if (len < sizeof(*ev)) { + printf("* Malformed Advertising Added control\n"); + return; + } + + printf("@ Advertising Added: %u\n", ev->instance); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +static void mgmt_advertising_removed(uint16_t len, const void *buf) +{ + const struct mgmt_ev_advertising_removed *ev = buf; + + if (len < sizeof(*ev)) { + printf("* Malformed Advertising Removed control\n"); + return; + } + + printf("@ Advertising Removed: %u\n", ev->instance); + + buf += sizeof(*ev); + len -= sizeof(*ev); + + packet_hexdump(buf, len); +} + +void control_message(uint16_t opcode, const void *data, uint16_t size) +{ + if (!decode_control) + return; + + switch (opcode) { + case MGMT_EV_INDEX_ADDED: + mgmt_index_added(size, data); + break; + case MGMT_EV_INDEX_REMOVED: + mgmt_index_removed(size, data); + break; + case MGMT_EV_CONTROLLER_ERROR: + mgmt_controller_error(size, data); + break; + case MGMT_EV_NEW_SETTINGS: + mgmt_new_settings(size, data); + break; + case MGMT_EV_CLASS_OF_DEV_CHANGED: + mgmt_class_of_dev_changed(size, data); + break; + case MGMT_EV_LOCAL_NAME_CHANGED: + mgmt_local_name_changed(size, data); + break; + case MGMT_EV_NEW_LINK_KEY: + mgmt_new_link_key(size, data); + break; + case MGMT_EV_NEW_LONG_TERM_KEY: + mgmt_new_long_term_key(size, data); + break; + case MGMT_EV_DEVICE_CONNECTED: + mgmt_device_connected(size, data); + break; + case MGMT_EV_DEVICE_DISCONNECTED: + mgmt_device_disconnected(size, data); + break; + case MGMT_EV_CONNECT_FAILED: + mgmt_connect_failed(size, data); + break; + case MGMT_EV_PIN_CODE_REQUEST: + mgmt_pin_code_request(size, data); + break; + case MGMT_EV_USER_CONFIRM_REQUEST: + mgmt_user_confirm_request(size, data); + break; + case MGMT_EV_USER_PASSKEY_REQUEST: + mgmt_user_passkey_request(size, data); + break; + case MGMT_EV_AUTH_FAILED: + mgmt_auth_failed(size, data); + break; + case MGMT_EV_DEVICE_FOUND: + mgmt_device_found(size, data); + break; + case MGMT_EV_DISCOVERING: + mgmt_discovering(size, data); + break; + case MGMT_EV_DEVICE_BLOCKED: + mgmt_device_blocked(size, data); + break; + case MGMT_EV_DEVICE_UNBLOCKED: + mgmt_device_unblocked(size, data); + break; + case MGMT_EV_DEVICE_UNPAIRED: + mgmt_device_unpaired(size, data); + break; + case MGMT_EV_PASSKEY_NOTIFY: + mgmt_passkey_notify(size, data); + break; + case MGMT_EV_NEW_IRK: + mgmt_new_irk(size, data); + break; + case MGMT_EV_NEW_CSRK: + mgmt_new_csrk(size, data); + break; + case MGMT_EV_DEVICE_ADDED: + mgmt_device_added(size, data); + break; + case MGMT_EV_DEVICE_REMOVED: + mgmt_device_removed(size, data); + break; + case MGMT_EV_NEW_CONN_PARAM: + mgmt_new_conn_param(size, data); + break; + case MGMT_EV_UNCONF_INDEX_ADDED: + mgmt_unconf_index_added(size, data); + break; + case MGMT_EV_UNCONF_INDEX_REMOVED: + mgmt_unconf_index_removed(size, data); + break; + case MGMT_EV_NEW_CONFIG_OPTIONS: + mgmt_new_config_options(size, data); + break; + case MGMT_EV_EXT_INDEX_ADDED: + mgmt_ext_index_added(size, data); + break; + case MGMT_EV_EXT_INDEX_REMOVED: + mgmt_ext_index_removed(size, data); + break; + case MGMT_EV_ADVERTISING_ADDED: + mgmt_advertising_added(size, data); + break; + case MGMT_EV_ADVERTISING_REMOVED: + mgmt_advertising_removed(size, data); + break; + default: + printf("* Unknown control (code %d len %d)\n", opcode, size); + packet_hexdump(data, size); + break; + } +} + +static void data_callback(int fd, uint32_t events, void *user_data) +{ + struct control_data *data = user_data; + unsigned char control[64]; + struct mgmt_hdr hdr; + struct msghdr msg; + struct iovec iov[2]; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(data->fd); + return; + } + + iov[0].iov_base = &hdr; + iov[0].iov_len = MGMT_HDR_SIZE; + iov[1].iov_base = data->buf; + iov[1].iov_len = sizeof(data->buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + while (1) { + struct cmsghdr *cmsg; + struct timeval *tv = NULL; + struct timeval ctv; + struct ucred *cred = NULL; + struct ucred ccred; + uint16_t opcode, index, pktlen; + ssize_t len; + + len = recvmsg(data->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; + } + + if (cmsg->cmsg_type == SCM_CREDENTIALS) { + memcpy(&ccred, CMSG_DATA(cmsg), sizeof(ccred)); + cred = &ccred; + } + } + + opcode = le16_to_cpu(hdr.opcode); + index = le16_to_cpu(hdr.index); + pktlen = le16_to_cpu(hdr.len); + + switch (data->channel) { + case HCI_CHANNEL_CONTROL: + packet_control(tv, cred, index, opcode, + data->buf, pktlen); + break; + case HCI_CHANNEL_MONITOR: + btsnoop_write_hci(btsnoop_file, tv, index, opcode, 0, + data->buf, pktlen); + ellisys_inject_hci(tv, index, opcode, + data->buf, pktlen); + packet_monitor(tv, cred, index, opcode, + data->buf, pktlen); + break; + } + } +} + +static int open_socket(uint16_t channel) +{ + struct sockaddr_hci addr; + int fd, opt = 1; + + fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (fd < 0) { + perror("Failed to open channel"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = HCI_DEV_NONE; + addr.hci_channel = channel; + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + if (errno == EINVAL) { + /* Fallback to hcidump support */ + hcidump_fallback = true; + close(fd); + return -1; + } + perror("Failed to bind channel"); + close(fd); + return -1; + } + + if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &opt, sizeof(opt)) < 0) { + perror("Failed to enable timestamps"); + close(fd); + return -1; + } + + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &opt, sizeof(opt)) < 0) { + perror("Failed to enable credentials"); + close(fd); + return -1; + } + + return fd; +} + +static void attach_index_filter(int fd, uint16_t index) +{ + struct sock_filter filters[] = { + /* Load MGMT index: + * A <- MGMT index + */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, + offsetof(struct mgmt_hdr, index)), + /* Accept if index is HCI_DEV_NONE: + * A == HCI_DEV_NONE + */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, HCI_DEV_NONE, 0, 1), + /* return */ + BPF_STMT(BPF_RET|BPF_K, 0x0fffffff), /* pass */ + /* Accept if index match: + * A == index + */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, index, 0, 1), + /* returns */ + BPF_STMT(BPF_RET|BPF_K, 0x0fffffff), /* pass */ + BPF_STMT(BPF_RET|BPF_K, 0), /* reject */ + }; + struct sock_fprog fprog = { + .len = sizeof(filters) / sizeof(filters[0]), + /* casting const away: */ + .filter = filters, + }; + + setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); +} + +static int open_channel(uint16_t channel) +{ + struct control_data *data; + + data = malloc(sizeof(*data)); + if (!data) + return -1; + + memset(data, 0, sizeof(*data)); + data->channel = channel; + + data->fd = open_socket(channel); + if (data->fd < 0) { + free(data); + return -1; + } + + if (filter_index != HCI_DEV_NONE) + attach_index_filter(data->fd, filter_index); + + mainloop_add_fd(data->fd, EPOLLIN, data_callback, data, free_data); + + return 0; +} + +static void client_callback(int fd, uint32_t events, void *user_data) +{ + struct control_data *data = user_data; + ssize_t len; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(data->fd); + return; + } + + len = recv(data->fd, data->buf + data->offset, + sizeof(data->buf) - data->offset, MSG_DONTWAIT); + if (len < 0) + return; + + data->offset += len; + + while (data->offset >= MGMT_HDR_SIZE) { + struct mgmt_hdr *hdr = (struct mgmt_hdr *) data->buf; + uint16_t pktlen = le16_to_cpu(hdr->len); + uint16_t opcode, index; + + if (data->offset < pktlen + MGMT_HDR_SIZE) + return; + + opcode = le16_to_cpu(hdr->opcode); + index = le16_to_cpu(hdr->index); + + packet_monitor(NULL, NULL, index, opcode, + data->buf + MGMT_HDR_SIZE, pktlen); + + data->offset -= pktlen + MGMT_HDR_SIZE; + + if (data->offset > 0) + memmove(data->buf, data->buf + MGMT_HDR_SIZE + pktlen, + data->offset); + } +} + +static void server_accept_callback(int fd, uint32_t events, void *user_data) +{ + struct control_data *data; + struct sockaddr_un addr; + socklen_t len; + int nfd; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(fd); + return; + } + + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + + nfd = accept(fd, (struct sockaddr *) &addr, &len); + if (nfd < 0) { + perror("Failed to accept client socket"); + return; + } + + printf("--- New monitor connection ---\n"); + + data = malloc(sizeof(*data)); + if (!data) { + close(nfd); + return; + } + + memset(data, 0, sizeof(*data)); + data->channel = HCI_CHANNEL_MONITOR; + data->fd = nfd; + + mainloop_add_fd(data->fd, EPOLLIN, client_callback, data, free_data); +} + +static int server_fd = -1; + +void control_server(const char *path) +{ + struct sockaddr_un addr; + size_t len; + int fd; + + if (server_fd >= 0) + return; + + len = strlen(path); + if (len > sizeof(addr.sun_path) - 1) { + fprintf(stderr, "Socket name too long\n"); + return; + } + + unlink(path); + + fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + perror("Failed to open server socket"); + return; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, len - 1); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind server socket"); + close(fd); + return; + } + + if (listen(fd, 5) < 0) { + perror("Failed to listen server socket"); + close(fd); + return; + } + + if (mainloop_add_fd(fd, EPOLLIN, server_accept_callback, + NULL, NULL) < 0) { + close(fd); + return; + } + + server_fd = fd; +} + +static bool parse_drops(uint8_t **data, uint8_t *len, uint8_t *drops, + uint32_t *total) +{ + if (*len < 1) + return false; + + *drops = **data; + *total += *drops; + (*data)++; + (*len)--; + + return true; +} + +static bool tty_parse_header(uint8_t *hdr, uint8_t len, struct timeval **tv, + struct timeval *ctv, uint32_t *drops) +{ + uint8_t cmd = 0; + uint8_t evt = 0; + uint8_t acl_tx = 0; + uint8_t acl_rx = 0; + uint8_t sco_tx = 0; + uint8_t sco_rx = 0; + uint8_t other = 0; + uint32_t total = 0; + uint32_t ts32; + + while (len) { + uint8_t type = hdr[0]; + + hdr++; len--; + + switch (type) { + case TTY_EXTHDR_COMMAND_DROPS: + if (!parse_drops(&hdr, &len, &cmd, &total)) + return false; + break; + case TTY_EXTHDR_EVENT_DROPS: + if (!parse_drops(&hdr, &len, &evt, &total)) + return false; + break; + case TTY_EXTHDR_ACL_TX_DROPS: + if (!parse_drops(&hdr, &len, &acl_tx, &total)) + return false; + break; + case TTY_EXTHDR_ACL_RX_DROPS: + if (!parse_drops(&hdr, &len, &acl_rx, &total)) + return false; + break; + case TTY_EXTHDR_SCO_TX_DROPS: + if (!parse_drops(&hdr, &len, &sco_tx, &total)) + return false; + break; + case TTY_EXTHDR_SCO_RX_DROPS: + if (!parse_drops(&hdr, &len, &sco_rx, &total)) + return false; + break; + case TTY_EXTHDR_OTHER_DROPS: + if (!parse_drops(&hdr, &len, &other, &total)) + return false; + break; + case TTY_EXTHDR_TS32: + if (len < sizeof(ts32)) + return false; + ts32 = get_le32(hdr); + hdr += sizeof(ts32); len -= sizeof(ts32); + /* ts32 is in units of 1/10th of a millisecond */ + ctv->tv_sec = ts32 / 10000; + ctv->tv_usec = (ts32 % 10000) * 100; + *tv = ctv; + break; + default: + printf("Unknown extended header type %u\n", type); + return false; + } + } + + if (total) { + *drops += total; + printf("* Drops: cmd %u evt %u acl_tx %u acl_rx %u sco_tx %u " + "sco_rx %u other %u\n", cmd, evt, acl_tx, acl_rx, + sco_tx, sco_rx, other); + } + + return true; +} + +static void process_data(struct control_data *data) +{ + while (data->offset >= sizeof(struct tty_hdr)) { + struct tty_hdr *hdr = (struct tty_hdr *) data->buf; + uint16_t pktlen, opcode, data_len; + struct timeval *tv = NULL; + struct timeval ctv; + uint32_t drops = 0; + + data_len = le16_to_cpu(hdr->data_len); + + if (data->offset < 2 + data_len) + return; + + if (data->offset < sizeof(*hdr) + hdr->hdr_len) { + fprintf(stderr, "Received corrupted data from TTY\n"); + memmove(data->buf, data->buf + 2 + data_len, + data->offset); + return; + } + + if (!tty_parse_header(hdr->ext_hdr, hdr->hdr_len, + &tv, &ctv, &drops)) + fprintf(stderr, "Unable to parse extended header\n"); + + opcode = le16_to_cpu(hdr->opcode); + pktlen = data_len - 4 - hdr->hdr_len; + + btsnoop_write_hci(btsnoop_file, tv, 0, opcode, drops, + hdr->ext_hdr + hdr->hdr_len, pktlen); + ellisys_inject_hci(tv, 0, opcode, hdr->ext_hdr + hdr->hdr_len, + pktlen); + packet_monitor(tv, NULL, 0, opcode, + hdr->ext_hdr + hdr->hdr_len, pktlen); + + data->offset -= 2 + data_len; + + if (data->offset > 0) + memmove(data->buf, data->buf + 2 + data_len, + data->offset); + } +} + +static void tty_callback(int fd, uint32_t events, void *user_data) +{ + struct control_data *data = user_data; + ssize_t len; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(data->fd); + return; + } + + len = read(data->fd, data->buf + data->offset, + sizeof(data->buf) - data->offset); + if (len < 0) + return; + + data->offset += len; + + process_data(data); +} + +int control_tty(const char *path, unsigned int speed) +{ + struct control_data *data; + struct termios ti; + int fd, err; + + fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (fd < 0) { + err = -errno; + perror("Failed to open serial port"); + return err; + } + + if (tcflush(fd, TCIOFLUSH) < 0) { + err = -errno; + perror("Failed to flush serial port"); + close(fd); + return err; + } + + memset(&ti, 0, sizeof(ti)); + /* Switch TTY to raw mode */ + cfmakeraw(&ti); + + ti.c_cflag |= (CLOCAL | CREAD); + ti.c_cflag &= ~CRTSCTS; + + cfsetspeed(&ti, speed); + + if (tcsetattr(fd, TCSANOW, &ti) < 0) { + err = -errno; + perror("Failed to set serial port settings"); + close(fd); + return err; + } + + printf("--- %s opened ---\n", path); + + data = malloc(sizeof(*data)); + if (!data) { + close(fd); + return -ENOMEM; + } + + memset(data, 0, sizeof(*data)); + data->channel = HCI_CHANNEL_MONITOR; + data->fd = fd; + + mainloop_add_fd(data->fd, EPOLLIN, tty_callback, data, free_data); + + return 0; +} + +static void rtt_callback(int id, void *user_data) +{ + struct control_data *data = user_data; + ssize_t len; + + do { + len = jlink_rtt_read(data->buf + data->offset, + sizeof(data->buf) - data->offset); + data->offset += len; + process_data(data); + } while (len > 0); + + if (mainloop_modify_timeout(id, 1) < 0) + mainloop_exit_failure(); +} + +int control_rtt(char *jlink, char *rtt) +{ + struct control_data *data; + + if (jlink_init() < 0) { + fprintf(stderr, "Failed to initialize J-Link library\n"); + return -EIO; + } + + if (jlink_connect(jlink) < 0) { + fprintf(stderr, "Failed to connect to target device\n"); + return -ENODEV; + } + + if (jlink_start_rtt(rtt) < 0) { + fprintf(stderr, "Failed to initialize RTT\n"); + return -ENODEV; + } + + printf("--- RTT opened ---\n"); + + data = new0(struct control_data, 1); + data->channel = HCI_CHANNEL_MONITOR; + data->fd = -1; + + if (mainloop_add_timeout(1, rtt_callback, data, free_data) < 0) { + free(data); + return -EIO; + } + + return 0; +} + +bool control_writer(const char *path) +{ + btsnoop_file = btsnoop_create(path, 0, 0, BTSNOOP_FORMAT_MONITOR); + + return !!btsnoop_file; +} + +void control_reader(const char *path, bool pager) +{ + unsigned char buf[BTSNOOP_MAX_PACKET_SIZE]; + uint16_t pktlen; + uint32_t format; + struct timeval tv; + + btsnoop_file = btsnoop_open(path, BTSNOOP_FLAG_PKLG_SUPPORT); + if (!btsnoop_file) + return; + + format = btsnoop_get_format(btsnoop_file); + + switch (format) { + case BTSNOOP_FORMAT_HCI: + case BTSNOOP_FORMAT_UART: + case BTSNOOP_FORMAT_SIMULATOR: + packet_del_filter(PACKET_FILTER_SHOW_INDEX); + break; + + case BTSNOOP_FORMAT_MONITOR: + packet_add_filter(PACKET_FILTER_SHOW_INDEX); + break; + } + + if (pager) + open_pager(); + + switch (format) { + case BTSNOOP_FORMAT_HCI: + case BTSNOOP_FORMAT_UART: + case BTSNOOP_FORMAT_MONITOR: + while (1) { + uint16_t index, opcode; + + if (!btsnoop_read_hci(btsnoop_file, &tv, &index, + &opcode, buf, &pktlen)) + break; + + if (opcode == 0xffff) + continue; + + packet_monitor(&tv, NULL, index, opcode, buf, pktlen); + ellisys_inject_hci(&tv, index, opcode, buf, pktlen); + } + break; + + case BTSNOOP_FORMAT_SIMULATOR: + while (1) { + uint16_t frequency; + + if (!btsnoop_read_phy(btsnoop_file, &tv, &frequency, + buf, &pktlen)) + break; + + packet_simulator(&tv, frequency, buf, pktlen); + } + break; + } + + if (pager) + close_pager(); + + btsnoop_unref(btsnoop_file); +} + +int control_tracing(void) +{ + packet_add_filter(PACKET_FILTER_SHOW_INDEX); + + if (server_fd >= 0) + return 0; + + if (open_channel(HCI_CHANNEL_MONITOR) < 0) { + if (!hcidump_fallback) + return -1; + if (hcidump_tracing() < 0) + return -1; + return 0; + } + + open_channel(HCI_CHANNEL_CONTROL); + + return 0; +} + +void control_disable_decoding(void) +{ + decode_control = false; +} + +void control_filter_index(uint16_t index) +{ + filter_index = index; +} diff --git a/monitor/control.h b/monitor/control.h new file mode 100644 index 0000000..ddf485f --- /dev/null +++ b/monitor/control.h @@ -0,0 +1,36 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +bool control_writer(const char *path); +void control_reader(const char *path, bool pager); +void control_server(const char *path); +int control_tty(const char *path, unsigned int speed); +int control_rtt(char *jlink, char *rtt); +int control_tracing(void); +void control_disable_decoding(void); +void control_filter_index(uint16_t index); + +void control_message(uint16_t opcode, const void *data, uint16_t size); diff --git a/monitor/crc.c b/monitor/crc.c new file mode 100644 index 0000000..912b37e --- /dev/null +++ b/monitor/crc.c @@ -0,0 +1,84 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "crc.h" + +uint32_t crc24_bit_reverse(uint32_t value) +{ + uint32_t result = 0; + uint8_t i; + + for (i = 0; i < 24; i++) + result |= ((value >> i) & 1) << (23 - i); + + return result; +} + +uint32_t crc24_calculate(uint32_t preset, const uint8_t *data, uint8_t len) +{ + uint32_t state = preset; + uint8_t i; + + for (i = 0; i < len; i++) { + uint8_t n, cur = data[i]; + + for (n = 0; n < 8; n++) { + int next_bit = (state ^ cur) & 1; + + cur >>= 1; + state >>= 1; + if (next_bit) { + state |= 1 << 23; + state ^= 0x5a6000; + } + } + } + + return state; +} + +uint32_t crc24_reverse(uint32_t crc, const uint8_t *data, uint8_t len) +{ + uint32_t state = crc; + uint8_t i; + + for (i = 0; i < len; i++) { + uint8_t n, cur = data[len - i - 1]; + + for (n = 0; n < 8; n++) { + int top_bit = state >> 23; + + state = (state << 1) & 0xffffff; + state |= top_bit ^ ((cur >> (7 - n)) & 1); + if (top_bit) + state ^= 0xb4c000; + } + } + + return state; +} diff --git a/monitor/crc.h b/monitor/crc.h new file mode 100644 index 0000000..772388b --- /dev/null +++ b/monitor/crc.h @@ -0,0 +1,30 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +uint32_t crc24_bit_reverse(uint32_t value); + +uint32_t crc24_calculate(uint32_t preset, const uint8_t *data, uint8_t len); +uint32_t crc24_reverse(uint32_t crc, const uint8_t *data, uint8_t len); diff --git a/monitor/display.c b/monitor/display.c new file mode 100644 index 0000000..9903954 --- /dev/null +++ b/monitor/display.c @@ -0,0 +1,172 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "display.h" + +static pid_t pager_pid = 0; + +bool use_color(void) +{ + static int cached_use_color = -1; + + if (__builtin_expect(!!(cached_use_color < 0), 0)) + cached_use_color = isatty(STDOUT_FILENO) > 0 || pager_pid > 0; + + return cached_use_color; +} + +int num_columns(void) +{ + static int cached_num_columns = -1; + + if (__builtin_expect(!!(cached_num_columns < 0), 0)) { + struct winsize ws; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0 || + ws.ws_col == 0) + cached_num_columns = FALLBACK_TERMINAL_WIDTH; + else + cached_num_columns = ws.ws_col; + } + + return cached_num_columns; +} + +static void close_pipe(int p[]) +{ + if (p[0] >= 0) + close(p[0]); + if (p[1] >= 0) + close(p[1]); +} + +static void wait_for_terminate(pid_t pid) +{ + siginfo_t dummy; + + for (;;) { + memset(&dummy, 0, sizeof(dummy)); + + if (waitid(P_PID, pid, &dummy, WEXITED) < 0) { + if (errno == EINTR) + continue; + return; + } + + return; + } +} + +void open_pager(void) +{ + const char *pager; + pid_t parent_pid; + int fd[2]; + + if (pager_pid > 0) + return; + + pager = getenv("PAGER"); + if (pager) { + if (!*pager || strcmp(pager, "cat") == 0) + return; + } + + if (!(isatty(STDOUT_FILENO) > 0)) + return; + + num_columns(); + + if (pipe(fd) < 0) { + perror("Failed to create pager pipe"); + return; + } + + parent_pid = getpid(); + + pager_pid = fork(); + if (pager_pid < 0) { + perror("Failed to fork pager"); + close_pipe(fd); + return; + } + + if (pager_pid == 0) { + dup2(fd[0], STDIN_FILENO); + close_pipe(fd); + + setenv("LESS", "FRSX", 0); + + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + if (pager) { + execlp(pager, pager, NULL); + execl("/bin/sh", "sh", "-c", pager, NULL); + } + + execlp("pager", "pager", NULL); + execlp("less", "less", NULL); + execlp("more", "more", NULL); + + _exit(EXIT_FAILURE); + } + + if (dup2(fd[1], STDOUT_FILENO) < 0) { + perror("Failed to duplicate pager pipe"); + return; + } + + close_pipe(fd); +} + +void close_pager(void) +{ + if (pager_pid <= 0) + return; + + fclose(stdout); + kill(pager_pid, SIGCONT); + wait_for_terminate(pager_pid); + pager_pid = 0; +} diff --git a/monitor/display.h b/monitor/display.h new file mode 100644 index 0000000..98073b9 --- /dev/null +++ b/monitor/display.h @@ -0,0 +1,92 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +bool use_color(void); + +#define COLOR_OFF "\x1B[0m" +#define COLOR_BLACK "\x1B[0;30m" +#define COLOR_RED "\x1B[0;31m" +#define COLOR_GREEN "\x1B[0;32m" +#define COLOR_YELLOW "\x1B[0;33m" +#define COLOR_BLUE "\x1B[0;34m" +#define COLOR_MAGENTA "\x1B[0;35m" +#define COLOR_CYAN "\x1B[0;36m" +#define COLOR_WHITE "\x1B[0;37m" +#define COLOR_WHITE_BG "\x1B[0;47;30m" +#define COLOR_HIGHLIGHT "\x1B[1;39m" + +#define COLOR_RED_BOLD "\x1B[1;31m" +#define COLOR_GREEN_BOLD "\x1B[1;32m" +#define COLOR_BLUE_BOLD "\x1B[1;34m" +#define COLOR_MAGENTA_BOLD "\x1B[1;35m" + +#define COLOR_ERROR "\x1B[1;31m" +#define COLOR_WARN "\x1B[1m" +#define COLOR_INFO COLOR_OFF +#define COLOR_DEBUG COLOR_WHITE + +#define FALLBACK_TERMINAL_WIDTH 80 + +#define print_indent(indent, color1, prefix, title, color2, fmt, args...) \ +do { \ + printf("%*c%s%s%s%s" fmt "%s\n", (indent), ' ', \ + use_color() ? (color1) : "", prefix, title, \ + use_color() ? (color2) : "", ## args, \ + use_color() ? COLOR_OFF : ""); \ +} while (0) + +#define print_text(color, fmt, args...) \ + print_indent(8, COLOR_OFF, "", "", color, fmt, ## args) + +#define print_field(fmt, args...) \ + print_indent(8, COLOR_OFF, "", "", COLOR_OFF, fmt, ## args) + +struct bitfield_data { + uint64_t bit; + const char *str; +}; + +static inline uint64_t print_bitfield(int indent, uint64_t val, + const struct bitfield_data *table) +{ + uint64_t mask = val; + int i; + + for (i = 0; table[i].str; i++) { + if (val & (((uint64_t) 1) << table[i].bit)) { + print_field("%*c%s", indent, ' ', table[i].str); + mask &= ~(((uint64_t) 1) << table[i].bit); + } + } + + return mask; +} + +int num_columns(void); + +void open_pager(void); +void close_pager(void); diff --git a/monitor/ellisys.c b/monitor/ellisys.c new file mode 100644 index 0000000..e533eec --- /dev/null +++ b/monitor/ellisys.c @@ -0,0 +1,163 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "src/shared/btsnoop.h" +#include "ellisys.h" + +static int ellisys_fd = -1; +static uint16_t ellisys_index = 0xffff; + +void ellisys_enable(const char *server, uint16_t port) +{ + struct sockaddr_in addr; + int fd; + + if (ellisys_fd >= 0) { + fprintf(stderr, "Ellisys injection already enabled\n"); + return; + } + + fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + perror("Failed to open UDP injection socket"); + return; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(server); + addr.sin_port = htons(port); + + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to connect UDP injection socket"); + close(fd); + return; + } + + ellisys_fd = fd; +} + +void ellisys_inject_hci(struct timeval *tv, uint16_t index, uint16_t opcode, + const void *data, uint16_t size) +{ + uint8_t msg[] = { + /* HCI Injection Service, Version 1 */ + 0x02, 0x00, 0x01, + /* DateTimeNs Object */ + 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Bitrate Object, 12000000 bps */ + 0x80, 0x00, 0x1b, 0x37, 0x4b, + /* HCI Packet Type Object */ + 0x81, 0x00, + /* HCI Packet Data Object */ + 0x82 + }; + long nsec; + time_t t; + struct tm tm; + struct iovec iov[2]; + int iovcnt; + + if (!tv) + return; + + if (ellisys_fd < 0) + return; + + if (ellisys_index == 0xffff) + ellisys_index = index; + + if (index != ellisys_index) + return; + + t = tv->tv_sec; + localtime_r(&t, &tm); + + nsec = ((tm.tm_sec + (tm.tm_min * 60) + + (tm.tm_hour * 3600)) * 1000000l + tv->tv_usec) * 1000l; + + msg[4] = (1900 + tm.tm_year) & 0xff; + msg[5] = (1900 + tm.tm_year) >> 8; + msg[6] = (tm.tm_mon + 1) & 0xff; + msg[7] = tm.tm_mday & 0xff; + msg[8] = (nsec & 0x0000000000ffl); + msg[9] = (nsec & 0x00000000ff00l) >> 8; + msg[10] = (nsec & 0x000000ff0000l) >> 16; + msg[11] = (nsec & 0x0000ff000000l) >> 24; + msg[12] = (nsec & 0x00ff00000000l) >> 32; + msg[13] = (nsec & 0xff0000000000l) >> 40; + + switch (opcode) { + case BTSNOOP_OPCODE_COMMAND_PKT: + msg[20] = 0x01; + break; + case BTSNOOP_OPCODE_EVENT_PKT: + msg[20] = 0x84; + break; + case BTSNOOP_OPCODE_ACL_TX_PKT: + msg[20] = 0x02; + break; + case BTSNOOP_OPCODE_ACL_RX_PKT: + msg[20] = 0x82; + break; + case BTSNOOP_OPCODE_SCO_TX_PKT: + msg[20] = 0x03; + break; + case BTSNOOP_OPCODE_SCO_RX_PKT: + msg[20] = 0x83; + break; + default: + return; + } + + iov[0].iov_base = msg; + iov[0].iov_len = sizeof(msg); + + if (size > 0) { + iov[1].iov_base = (void *) data; + iov[1].iov_len = size; + iovcnt = 2; + } else + iovcnt = 1; + + if (writev(ellisys_fd, iov, iovcnt) < 0) + perror("Failed to send Ellisys injection packet"); +} diff --git a/monitor/ellisys.h b/monitor/ellisys.h new file mode 100644 index 0000000..8be888d --- /dev/null +++ b/monitor/ellisys.h @@ -0,0 +1,30 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +void ellisys_enable(const char *server, uint16_t port); + +void ellisys_inject_hci(struct timeval *tv, uint16_t index, uint16_t opcode, + const void *data, uint16_t size); diff --git a/monitor/hcidump.c b/monitor/hcidump.c new file mode 100644 index 0000000..8b6f846 --- /dev/null +++ b/monitor/hcidump.c @@ -0,0 +1,413 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "src/shared/mainloop.h" + +#include "packet.h" +#include "hcidump.h" + +struct hcidump_data { + uint16_t index; + int fd; +}; + +static void free_data(void *user_data) +{ + struct hcidump_data *data = user_data; + + close(data->fd); + + free(data); +} + +static int open_hci_dev(uint16_t index) +{ + struct sockaddr_hci addr; + struct hci_filter flt; + int fd, opt = 1; + + fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (fd < 0) { + perror("Failed to open channel"); + return -1; + } + + /* Setup filter */ + hci_filter_clear(&flt); + hci_filter_all_ptypes(&flt); + hci_filter_all_events(&flt); + + if (setsockopt(fd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + perror("Failed to set HCI filter"); + close(fd); + return -1; + } + + if (setsockopt(fd, SOL_HCI, HCI_DATA_DIR, &opt, sizeof(opt)) < 0) { + perror("Failed to enable HCI data direction info"); + close(fd); + return -1; + } + + if (setsockopt(fd, SOL_HCI, HCI_TIME_STAMP, &opt, sizeof(opt)) < 0) { + perror("Failed to enable HCI time stamps"); + close(fd); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = index; + addr.hci_channel = HCI_CHANNEL_RAW; + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind channel"); + close(fd); + return -1; + } + + return fd; +} + +static void device_callback(int fd, uint32_t events, void *user_data) +{ + struct hcidump_data *data = user_data; + unsigned char buf[HCI_MAX_FRAME_SIZE * 2]; + unsigned char control[64]; + struct msghdr msg; + struct iovec iov; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(fd); + return; + } + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + while (1) { + struct cmsghdr *cmsg; + struct timeval *tv = NULL; + struct timeval ctv; + int dir = -1; + ssize_t len; + + len = recvmsg(fd, &msg, MSG_DONTWAIT); + if (len < 0) + break; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_HCI) + continue; + + switch (cmsg->cmsg_type) { + case HCI_DATA_DIR: + memcpy(&dir, CMSG_DATA(cmsg), sizeof(dir)); + break; + case HCI_CMSG_TSTAMP: + memcpy(&ctv, CMSG_DATA(cmsg), sizeof(ctv)); + tv = &ctv; + break; + } + } + + if (dir < 0 || len < 1) + continue; + + switch (buf[0]) { + case HCI_COMMAND_PKT: + packet_hci_command(tv, NULL, data->index, + buf + 1, len - 1); + break; + case HCI_EVENT_PKT: + packet_hci_event(tv, NULL, data->index, + buf + 1, len - 1); + break; + case HCI_ACLDATA_PKT: + packet_hci_acldata(tv, NULL, data->index, !!dir, + buf + 1, len - 1); + break; + case HCI_SCODATA_PKT: + packet_hci_scodata(tv, NULL, data->index, !!dir, + buf + 1, len - 1); + break; + } + } +} + +static void open_device(uint16_t index) +{ + struct hcidump_data *data; + + data = malloc(sizeof(*data)); + if (!data) + return; + + memset(data, 0, sizeof(*data)); + data->index = index; + + data->fd = open_hci_dev(index); + if (data->fd < 0) { + free(data); + return; + } + + mainloop_add_fd(data->fd, EPOLLIN, device_callback, data, free_data); +} + +static void device_info(int fd, uint16_t index, uint8_t *type, uint8_t *bus, + bdaddr_t *bdaddr, char *name) +{ + struct hci_dev_info di; + + memset(&di, 0, sizeof(di)); + di.dev_id = index; + + if (ioctl(fd, HCIGETDEVINFO, (void *) &di) < 0) { + perror("Failed to get device information"); + return; + } + + *type = di.type >> 4; + *bus = di.type & 0x0f; + + bacpy(bdaddr, &di.bdaddr); + memcpy(name, di.name, 8); +} + +static void device_list(int fd, int max_dev) +{ + struct hci_dev_list_req *dl; + struct hci_dev_req *dr; + int i; + + dl = malloc(max_dev * sizeof(*dr) + sizeof(*dl)); + if (!dl) { + perror("Failed to allocate device list memory"); + return; + } + + memset(dl, 0, max_dev * sizeof(*dr) + sizeof(*dl)); + dl->dev_num = max_dev; + + dr = dl->dev_req; + + if (ioctl(fd, HCIGETDEVLIST, (void *) dl) < 0) { + perror("Failed to get device list"); + goto done; + } + + for (i = 0; i < dl->dev_num; i++, dr++) { + struct timeval tmp_tv, *tv = NULL; + uint8_t type = 0xff, bus = 0xff; + char str[18], name[8] = ""; + bdaddr_t bdaddr; + + bacpy(&bdaddr, BDADDR_ANY); + + if (!gettimeofday(&tmp_tv, NULL)) + tv = &tmp_tv; + + device_info(fd, dr->dev_id, &type, &bus, &bdaddr, name); + ba2str(&bdaddr, str); + packet_new_index(tv, dr->dev_id, str, type, bus, name); + open_device(dr->dev_id); + } + +done: + free(dl); +} + +static int open_stack_internal(void) +{ + struct sockaddr_hci addr; + struct hci_filter flt; + int fd, opt = 1; + + fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (fd < 0) { + perror("Failed to open channel"); + return -1; + } + + /* Setup filter */ + hci_filter_clear(&flt); + hci_filter_set_ptype(HCI_EVENT_PKT, &flt); + hci_filter_set_event(EVT_STACK_INTERNAL, &flt); + + if (setsockopt(fd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + perror("Failed to set HCI filter"); + close(fd); + return -1; + } + + if (setsockopt(fd, SOL_HCI, HCI_TIME_STAMP, &opt, sizeof(opt)) < 0) { + perror("Failed to enable HCI time stamps"); + close(fd); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = HCI_DEV_NONE; + addr.hci_channel = HCI_CHANNEL_RAW; + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind channel"); + close(fd); + return -1; + } + + device_list(fd, HCI_MAX_DEV); + + return fd; +} + +static void stack_internal_callback(int fd, uint32_t events, void *user_data) +{ + unsigned char buf[HCI_MAX_FRAME_SIZE]; + unsigned char control[32]; + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsg; + ssize_t len; + hci_event_hdr *eh; + evt_stack_internal *si; + evt_si_device *sd; + struct timeval *tv = NULL; + struct timeval ctv; + uint8_t type = 0xff, bus = 0xff; + char str[18], name[8] = ""; + bdaddr_t bdaddr; + + bacpy(&bdaddr, BDADDR_ANY); + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(fd); + return; + } + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + len = recvmsg(fd, &msg, MSG_DONTWAIT); + if (len < 0) + return; + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_HCI) + continue; + + switch (cmsg->cmsg_type) { + case HCI_CMSG_TSTAMP: + memcpy(&ctv, CMSG_DATA(cmsg), sizeof(ctv)); + tv = &ctv; + break; + } + } + + if (len < 1 + HCI_EVENT_HDR_SIZE + EVT_STACK_INTERNAL_SIZE + + EVT_SI_DEVICE_SIZE) + return; + + if (buf[0] != HCI_EVENT_PKT) + return; + + eh = (hci_event_hdr *) (buf + 1); + if (eh->evt != EVT_STACK_INTERNAL) + return; + + si = (evt_stack_internal *) (buf + 1 + HCI_EVENT_HDR_SIZE); + if (si->type != EVT_SI_DEVICE) + return; + + sd = (evt_si_device *) &si->data; + + switch (sd->event) { + case HCI_DEV_REG: + device_info(fd, sd->dev_id, &type, &bus, &bdaddr, name); + ba2str(&bdaddr, str); + packet_new_index(tv, sd->dev_id, str, type, bus, name); + open_device(sd->dev_id); + break; + case HCI_DEV_UNREG: + ba2str(&bdaddr, str); + packet_del_index(tv, sd->dev_id, str); + break; + } +} + +int hcidump_tracing(void) +{ + struct hcidump_data *data; + + data = malloc(sizeof(*data)); + if (!data) + return -1; + + memset(data, 0, sizeof(*data)); + data->index = HCI_DEV_NONE; + + data->fd = open_stack_internal(); + if (data->fd < 0) { + free(data); + return -1; + } + + mainloop_add_fd(data->fd, EPOLLIN, stack_internal_callback, + data, free_data); + + return 0; +} diff --git a/monitor/hcidump.h b/monitor/hcidump.h new file mode 100644 index 0000000..c908650 --- /dev/null +++ b/monitor/hcidump.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int hcidump_tracing(void); diff --git a/monitor/hwdb.c b/monitor/hwdb.c new file mode 100644 index 0000000..f2431fa --- /dev/null +++ b/monitor/hwdb.c @@ -0,0 +1,138 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include + +#include "hwdb.h" + +#ifdef HAVE_UDEV_HWDB_NEW +#include + +bool hwdb_get_vendor_model(const char *modalias, char **vendor, char **model) +{ + struct udev *udev; + struct udev_hwdb *hwdb; + struct udev_list_entry *head, *entry; + bool result; + + udev = udev_new(); + if (!udev) + return false; + + hwdb = udev_hwdb_new(udev); + if (!hwdb) { + result = false; + goto done; + } + + *vendor = NULL; + *model = NULL; + + head = udev_hwdb_get_properties_list_entry(hwdb, modalias, 0); + + udev_list_entry_foreach(entry, head) { + const char *name = udev_list_entry_get_name(entry); + + if (!name) + continue; + + if (!*vendor && !strcmp(name, "ID_VENDOR_FROM_DATABASE")) + *vendor = strdup(udev_list_entry_get_value(entry)); + else if (!*model && !strcmp(name, "ID_MODEL_FROM_DATABASE")) + *model = strdup(udev_list_entry_get_value(entry)); + } + + hwdb = udev_hwdb_unref(hwdb); + + result = true; + +done: + udev = udev_unref(udev); + + return result; +} + +bool hwdb_get_company(const uint8_t *bdaddr, char **company) +{ + struct udev *udev; + struct udev_hwdb *hwdb; + struct udev_list_entry *head, *entry; + char modalias[11]; + bool result; + + if (!bdaddr[2] && !bdaddr[1] && !bdaddr[0]) + return false; + + sprintf(modalias, "OUI:%2.2X%2.2X%2.2X", + bdaddr[5], bdaddr[4], bdaddr[3]); + + udev = udev_new(); + if (!udev) + return false; + + hwdb = udev_hwdb_new(udev); + if (!hwdb) { + result = false; + goto done; + } + + *company = NULL; + + head = udev_hwdb_get_properties_list_entry(hwdb, modalias, 0); + + udev_list_entry_foreach(entry, head) { + const char *name = udev_list_entry_get_name(entry); + + if (name && !strcmp(name, "ID_OUI_FROM_DATABASE")) { + *company = strdup(udev_list_entry_get_value(entry)); + break; + } + } + + hwdb = udev_hwdb_unref(hwdb); + + result = true; + +done: + udev = udev_unref(udev); + + return result; +} +#else +bool hwdb_get_vendor_model(const char *modalias, char **vendor, char **model) +{ + return false; +} + +bool hwdb_get_company(const uint8_t *bdaddr, char **company) +{ + return false; +} +#endif diff --git a/monitor/hwdb.h b/monitor/hwdb.h new file mode 100644 index 0000000..79f505a --- /dev/null +++ b/monitor/hwdb.h @@ -0,0 +1,29 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +bool hwdb_get_vendor_model(const char *modalias, char **vendor, char **model); +bool hwdb_get_company(const uint8_t *bdaddr, char **company); diff --git a/monitor/intel.c b/monitor/intel.c new file mode 100644 index 0000000..41e70ba --- /dev/null +++ b/monitor/intel.c @@ -0,0 +1,994 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/util.h" +#include "display.h" +#include "packet.h" +#include "lmp.h" +#include "ll.h" +#include "vendor.h" +#include "intel.h" + +#define COLOR_UNKNOWN_EVENT_MASK COLOR_WHITE_BG +#define COLOR_UNKNOWN_SCAN_STATUS COLOR_WHITE_BG + +static void print_status(uint8_t status) +{ + packet_print_error("Status", status); +} + +static void print_module(uint8_t module) +{ + const char *str; + + switch (module) { + case 0x01: + str = "BC"; + break; + case 0x02: + str = "HCI"; + break; + case 0x03: + str = "LLC"; + break; + case 0x04: + str = "OS"; + break; + case 0x05: + str = "LM"; + break; + case 0x06: + str = "SC"; + break; + case 0x07: + str = "SP"; + break; + case 0x08: + str = "OSAL"; + break; + case 0x09: + str = "LC"; + break; + case 0x0a: + str = "APP"; + break; + case 0x0b: + str = "TLD"; + break; + case 0xf0: + str = "Debug"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Module: %s (0x%2.2x)", str, module); +} + +static void null_cmd(const void *data, uint8_t size) +{ +} + +static void status_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + + print_status(status); +} + +static void reset_cmd(const void *data, uint8_t size) +{ + uint8_t reset_type = get_u8(data); + uint8_t patch_enable = get_u8(data + 1); + uint8_t ddc_reload = get_u8(data + 2); + uint8_t boot_option = get_u8(data + 3); + uint32_t boot_addr = get_le32(data + 4); + const char *str; + + switch (reset_type) { + case 0x00: + str = "Soft software reset"; + break; + case 0x01: + str = "Hard software reset"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Reset type: %s (0x%2.2x)", str, reset_type); + + switch (patch_enable) { + case 0x00: + str = "Do not enable"; + break; + case 0x01: + str = "Enable"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Patch vectors: %s (0x%2.2x)", str, patch_enable); + + switch (ddc_reload) { + case 0x00: + str = "Do not reload"; + break; + case 0x01: + str = "Reload from OTP"; + break; + default: + str = "Reserved"; + break; + } + + print_field("DDC parameters: %s (0x%2.2x)", str, ddc_reload); + + switch (boot_option) { + case 0x00: + str = "Current image"; + break; + case 0x01: + str = "Specified address"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Boot option: %s (0x%2.2x)", str, boot_option); + print_field("Boot address: 0x%8.8x", boot_addr); +} + +static void read_version_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + uint8_t hw_platform = get_u8(data + 1); + uint8_t hw_variant = get_u8(data + 2); + uint8_t hw_revision = get_u8(data + 3); + uint8_t fw_variant = get_u8(data + 4); + uint8_t fw_revision = get_u8(data + 5); + uint8_t fw_build_nn = get_u8(data + 6); + uint8_t fw_build_cw = get_u8(data + 7); + uint8_t fw_build_yy = get_u8(data + 8); + uint8_t fw_patch = get_u8(data + 9); + + print_status(status); + print_field("Hardware platform: 0x%2.2x", hw_platform); + print_field("Hardware variant: 0x%2.2x", hw_variant); + print_field("Hardware revision: %u.%u", hw_revision >> 4, + hw_revision & 0x0f); + print_field("Firmware variant: 0x%2.2x", fw_variant); + print_field("Firmware revision: %u.%u", fw_revision >> 4, + fw_revision & 0x0f); + + print_field("Firmware build: %u-%u.%u", fw_build_nn, + fw_build_cw, 2000 + fw_build_yy); + print_field("Firmware patch: %u", fw_patch); +} + +static void set_uart_baudrate_cmd(const void *data, uint8_t size) +{ + uint8_t baudrate = get_u8(data); + const char *str; + + switch (baudrate) { + case 0x00: + str = "9600 Baud"; + break; + case 0x01: + str = "19200 Baud"; + break; + case 0x02: + str = "38400 Baud"; + break; + case 0x03: + str = "57600 Baud"; + break; + case 0x04: + str = "115200 Baud"; + break; + case 0x05: + str = "230400 Baud"; + break; + case 0x06: + str = "460800 Baud"; + break; + case 0x07: + str = "921600 Baud"; + break; + case 0x08: + str = "1843200 Baud"; + break; + case 0x09: + str = "3250000 baud"; + break; + case 0x0a: + str = "2000000 baud"; + break; + case 0x0b: + str = "3000000 baud"; + break; + case 0x0c: + str = "3714286 baud"; + break; + case 0x0d: + str = "4333333 baud"; + break; + case 0x0e: + str = "6500000 baud"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Baudrate: %s (0x%2.2x)", str, baudrate); +} + +static void secure_send_cmd(const void *data, uint8_t size) +{ + uint8_t type = get_u8(data); + const char *str; + + switch (type) { + case 0x00: + str = "Init"; + break; + case 0x01: + str = "Data"; + break; + case 0x02: + str = "Sign"; + break; + case 0x03: + str = "PKey"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s fragment (0x%2.2x)", str, type); + + packet_hexdump(data + 1, size - 1); +} + +static void manufacturer_mode_cmd(const void *data, uint8_t size) +{ + uint8_t mode = get_u8(data); + uint8_t reset = get_u8(data + 1); + const char *str; + + switch (mode) { + case 0x00: + str = "Disabled"; + break; + case 0x01: + str = "Enabled"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Mode switch: %s (0x%2.2x)", str, mode); + + switch (reset) { + case 0x00: + str = "No reset"; + break; + case 0x01: + str = "Reset and deactivate patches"; + break; + case 0x02: + str = "Reset and activate patches"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Reset behavior: %s (0x%2.2x)", str, reset); +} + +static void write_bd_data_cmd(const void *data, uint8_t size) +{ + uint8_t features[8]; + + packet_print_addr("Address", data, false); + packet_hexdump(data + 6, 6); + + memcpy(features, data + 12, 8); + packet_print_features_lmp(features, 0); + + memcpy(features, data + 20, 1); + memset(features + 1, 0, 7); + packet_print_features_ll(features); + + packet_hexdump(data + 21, size - 21); +} + +static void read_bd_data_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + + print_status(status); + packet_print_addr("Address", data + 1, false); + packet_hexdump(data + 7, size - 7); +} + +static void write_bd_address_cmd(const void *data, uint8_t size) +{ + packet_print_addr("Address", data, false); +} + +static void act_deact_traces_cmd(const void *data, uint8_t size) +{ + uint8_t tx = get_u8(data); + uint8_t tx_arq = get_u8(data + 1); + uint8_t rx = get_u8(data + 2); + + print_field("Transmit traces: 0x%2.2x", tx); + print_field("Transmit ARQ: 0x%2.2x", tx_arq); + print_field("Receive traces: 0x%2.2x", rx); +} + +static void stimulate_exception_cmd(const void *data, uint8_t size) +{ + uint8_t type = get_u8(data); + const char *str; + + switch (type) { + case 0x00: + str = "Fatal Exception"; + break; + case 0x01: + str = "Debug Exception"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, type); +} + +static const struct { + uint8_t bit; + const char *str; +} events_table[] = { + { 0, "Bootup" }, + { 1, "SCO Rejected via LMP" }, + { 2, "PTT Switch Notification" }, + { 7, "Scan Status" }, + { 9, "Debug Exception" }, + { 10, "Fatal Exception" }, + { 11, "System Exception" }, + { 13, "LE Link Established" }, + { 14, "FW Trace String" }, + { } +}; + +static void set_event_mask_cmd(const void *data, uint8_t size) +{ + const uint8_t *events_array = data; + uint64_t mask, events = 0; + int i; + + for (i = 0; i < 8; i++) + events |= ((uint64_t) events_array[i]) << (i * 8); + + print_field("Mask: 0x%16.16" PRIx64, events); + + mask = events; + + for (i = 0; events_table[i].str; i++) { + if (events & (((uint64_t) 1) << events_table[i].bit)) { + print_field(" %s", events_table[i].str); + mask &= ~(((uint64_t) 1) << events_table[i].bit); + } + } + + if (mask) + print_text(COLOR_UNKNOWN_EVENT_MASK, " Unknown mask " + "(0x%16.16" PRIx64 ")", mask); +} + +static void ddc_config_write_cmd(const void *data, uint8_t size) +{ + while (size > 0) { + uint8_t param_len = get_u8(data); + uint16_t param_id = get_le16(data + 1); + + print_field("Identifier: 0x%4.4x", param_id); + packet_hexdump(data + 3, param_len - 2); + + data += param_len + 1; + size -= param_len + 1; + } +} + +static void ddc_config_write_rsp(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + uint16_t param_id = get_le16(data + 1); + + print_status(status); + print_field("Identifier: 0x%4.4x", param_id); +} + +static void memory_write_cmd(const void *data, uint8_t size) +{ + uint32_t addr = get_le32(data); + uint8_t mode = get_u8(data + 4); + uint8_t length = get_u8(data + 5); + const char *str; + + print_field("Address: 0x%8.8x", addr); + + switch (mode) { + case 0x00: + str = "Byte access"; + break; + case 0x01: + str = "Half word access"; + break; + case 0x02: + str = "Word access"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Mode: %s (0x%2.2x)", str, mode); + print_field("Length: %u", length); + + packet_hexdump(data + 6, size - 6); +} + +static const struct vendor_ocf vendor_ocf_table[] = { + { 0x001, "Reset", + reset_cmd, 8, true, + status_rsp, 1, true }, + { 0x002, "No Operation" }, + { 0x005, "Read Version", + null_cmd, 0, true, + read_version_rsp, 10, true }, + { 0x006, "Set UART Baudrate", + set_uart_baudrate_cmd, 1, true, + status_rsp, 1, true }, + { 0x007, "Enable LPM" }, + { 0x008, "PCM Write Configuration" }, + { 0x009, "Secure Send", + secure_send_cmd, 1, false, + status_rsp, 1, true }, + { 0x00d, "Read Secure Boot Params", + null_cmd, 0, true }, + { 0x00e, "Write Secure Boot Params" }, + { 0x00f, "Unlock" }, + { 0x010, "Change UART Baudrate" }, + { 0x011, "Manufacturer Mode", + manufacturer_mode_cmd, 2, true, + status_rsp, 1, true }, + { 0x012, "Read Link RSSI" }, + { 0x022, "Get Exception Info" }, + { 0x024, "Clear Exception Info" }, + { 0x02f, "Write BD Data", + write_bd_data_cmd, 6, false }, + { 0x030, "Read BD Data", + null_cmd, 0, true, + read_bd_data_rsp, 7, false }, + { 0x031, "Write BD Address", + write_bd_address_cmd, 6, true, + status_rsp, 1, true }, + { 0x032, "Flow Specification" }, + { 0x034, "Read Secure ID" }, + { 0x038, "Set Synchronous USB Interface Type" }, + { 0x039, "Config Synchronous Interface" }, + { 0x03f, "SW RF Kill", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x043, "Activate Deactivate Traces", + act_deact_traces_cmd, 3, true }, + { 0x04d, "Stimulate Exception", + stimulate_exception_cmd, 1, true, + status_rsp, 1, true }, + { 0x050, "Read HW Version" }, + { 0x052, "Set Event Mask", + set_event_mask_cmd, 8, true, + status_rsp, 1, true }, + { 0x053, "Config_Link_Controller" }, + { 0x089, "DDC Write" }, + { 0x08a, "DDC Read" }, + { 0x08b, "DDC Config Write", + ddc_config_write_cmd, 3, false, + ddc_config_write_rsp, 3, true }, + { 0x08c, "DDC Config Read" }, + { 0x08d, "Memory Read" }, + { 0x08e, "Memory Write", + memory_write_cmd, 6, false, + status_rsp, 1, true }, + { } +}; + +const struct vendor_ocf *intel_vendor_ocf(uint16_t ocf) +{ + int i; + + for (i = 0; vendor_ocf_table[i].str; i++) { + if (vendor_ocf_table[i].ocf == ocf) + return &vendor_ocf_table[i]; + } + + return NULL; +} + +static void startup_evt(const void *data, uint8_t size) +{ +} + +static void fatal_exception_evt(const void *data, uint8_t size) +{ + uint16_t line = get_le16(data); + uint8_t module = get_u8(data + 2); + uint8_t reason = get_u8(data + 3); + + print_field("Line: %u", line); + print_module(module); + print_field("Reason: 0x%2.2x", reason); +} + +static void bootup_evt(const void *data, uint8_t size) +{ + uint8_t zero = get_u8(data); + uint8_t num_packets = get_u8(data + 1); + uint8_t source = get_u8(data + 2); + uint8_t reset_type = get_u8(data + 3); + uint8_t reset_reason = get_u8(data + 4); + uint8_t ddc_status = get_u8(data + 5); + const char *str; + + print_field("Zero: 0x%2.2x", zero); + print_field("Number of packets: %d", num_packets); + + switch (source) { + case 0x00: + str = "Bootloader"; + break; + case 0x01: + str = "Operational firmware"; + break; + case 0x02: + str = "Self test firmware"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Source: %s (0x%2.2x)", str, source); + + switch (reset_type) { + case 0x00: + str = "Hardware reset"; + break; + case 0x01: + str = "Soft watchdog reset"; + break; + case 0x02: + str = "Soft software reset"; + break; + case 0x03: + str = "Hard watchdog reset"; + break; + case 0x04: + str = "Hard software reset"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Reset type: %s (0x%2.2x)", str, reset_type); + + switch (reset_reason) { + case 0x00: + str = "Power on"; + break; + case 0x01: + str = "Reset command"; + break; + case 0x02: + str = "Intel reset command"; + break; + case 0x03: + str = "Watchdog"; + break; + case 0x04: + str = "Fatal exception"; + break; + case 0x05: + str = "System exception"; + break; + case 0xff: + str = "Unknown"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Reset reason: %s (0x%2.2x)", str, reset_reason); + + switch (ddc_status) { + case 0x00: + str = "Firmware default"; + break; + case 0x01: + str = "Firmware default plus OTP"; + break; + case 0x02: + str = "Persistent RAM"; + break; + case 0x03: + str = "Not used"; + break; + default: + str = "Reserved"; + break; + } + + print_field("DDC status: %s (0x%2.2x)", str, ddc_status); +} + +static void default_bd_data_evt(const void *data, uint8_t size) +{ + uint8_t mem_status = get_u8(data); + const char *str; + + switch (mem_status) { + case 0x02: + str = "Invalid manufacturing data"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Memory status: %s (0x%2.2x)", str, mem_status); +} + +static void secure_send_commands_result_evt(const void *data, uint8_t size) +{ + uint8_t result = get_u8(data); + uint16_t opcode = get_le16(data + 1); + uint16_t ogf = cmd_opcode_ogf(opcode); + uint16_t ocf = cmd_opcode_ocf(opcode); + uint8_t status = get_u8(data + 3); + const char *str; + + switch (result) { + case 0x00: + str = "Success"; + break; + case 0x01: + str = "General failure"; + break; + case 0x02: + str = "Hardware failure"; + break; + case 0x03: + str = "Signature verification failed"; + break; + case 0x04: + str = "Parsing error of command buffer"; + break; + case 0x05: + str = "Command execution failure"; + break; + case 0x06: + str = "Command parameters error"; + break; + case 0x07: + str = "Command missing"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Result: %s (0x%2.2x)", str, result); + print_field("Opcode: 0x%4.4x (0x%2.2x|0x%4.4x)", opcode, ogf, ocf); + print_status(status); +} + +static void debug_exception_evt(const void *data, uint8_t size) +{ + uint16_t line = get_le16(data); + uint8_t module = get_u8(data + 2); + uint8_t reason = get_u8(data + 3); + + print_field("Line: %u", line); + print_module(module); + print_field("Reason: 0x%2.2x", reason); +} + +static void le_link_established_evt(const void *data, uint8_t size) +{ + uint16_t handle = get_le16(data); + uint32_t access_addr = get_le32(data + 10); + + print_field("Handle: %u", handle); + + packet_hexdump(data + 2, 8); + + print_field("Access address: 0x%8.8x", access_addr); + + packet_hexdump(data + 14, size - 14); +} + +static void scan_status_evt(const void *data, uint8_t size) +{ + uint8_t enable = get_u8(data); + + print_field("Inquiry scan: %s", + (enable & 0x01) ? "Enabled" : "Disabled"); + print_field("Page scan: %s", + (enable & 0x02) ? "Enabled" : "Disabled"); + + if (enable & 0xfc) + print_text(COLOR_UNKNOWN_SCAN_STATUS, + " Unknown status (0x%2.2x)", enable & 0xfc); + +} + +static void act_deact_traces_complete_evt(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + + print_status(status); +} + +static void lmp_pdu_trace_evt(const void *data, uint8_t size) +{ + uint8_t type, len, id; + uint16_t handle, count; + uint32_t clock; + const char *str; + + type = get_u8(data); + handle = get_le16(data + 1); + + switch (type) { + case 0x00: + str = "RX LMP"; + break; + case 0x01: + str = "TX LMP"; + break; + case 0x02: + str = "ACK LMP"; + break; + case 0x03: + str = "RX LL"; + break; + case 0x04: + str = "TX LL"; + break; + case 0x05: + str = "ACK LL"; + break; + default: + str = "Unknown"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, type); + print_field("Handle: %u", handle); + + switch (type) { + case 0x00: + len = size - 8; + clock = get_le32(data + 4 + len); + + packet_hexdump(data + 3, 1); + lmp_packet(data + 4, len, false); + print_field("Clock: 0x%8.8x", clock); + break; + case 0x01: + len = size - 9; + clock = get_le32(data + 4 + len); + id = get_u8(data + 4 + len + 4); + + packet_hexdump(data + 3, 1); + lmp_packet(data + 4, len, false); + print_field("Clock: 0x%8.8x", clock); + print_field("ID: 0x%2.2x", id); + break; + case 0x02: + clock = get_le32(data + 3); + id = get_u8(data + 3 + 4); + + print_field("Clock: 0x%8.8x", clock); + print_field("ID: 0x%2.2x", id); + break; + case 0x03: + len = size - 8; + count = get_le16(data + 3); + + print_field("Count: 0x%4.4x", count); + packet_hexdump(data + 3 + 2 + 1, 2); + llcp_packet(data + 8, len, false); + break; + case 0x04: + len = size - 8; + count = get_le16(data + 3); + id = get_u8(data + 3 + 2); + + print_field("Count: 0x%4.4x", count); + print_field("ID: 0x%2.2x", id); + packet_hexdump(data + 3 + 2 + 1, 2); + llcp_packet(data + 8, len, false); + break; + case 0x05: + count = get_le16(data + 3); + id = get_u8(data + 3 + 2); + + print_field("Count: 0x%4.4x", count); + print_field("ID: 0x%2.2x", id); + break; + default: + packet_hexdump(data + 3, size - 3); + break; + } +} + +static void write_bd_data_complete_evt(const void *data, uint8_t size) +{ + uint8_t status = get_u8(data); + + print_status(status); +} + +static void sco_rejected_via_lmp_evt(const void *data, uint8_t size) +{ + uint8_t reason = get_u8(data + 6); + + packet_print_addr("Address", data, false); + packet_print_error("Reason", reason); +} + +static void ptt_switch_notification_evt(const void *data, uint8_t size) +{ + uint16_t handle = get_le16(data); + uint8_t table = get_u8(data + 2); + const char *str; + + print_field("Handle: %u", handle); + + switch (table) { + case 0x00: + str = "Basic rate"; + break; + case 0x01: + str = "Enhanced data rate"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Packet type table: %s (0x%2.2x)", str, table); +} + +static void system_exception_evt(const void *data, uint8_t size) +{ + uint8_t type = get_u8(data); + const char *str; + + switch (type) { + case 0x00: + str = "No Exception"; + break; + case 0x01: + str = "Undefined Instruction"; + break; + case 0x02: + str = "Prefetch abort"; + break; + case 0x03: + str = "Data abort"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, type); + + packet_hexdump(data + 1, size - 1); +} + +static const struct vendor_evt vendor_evt_table[] = { + { 0x00, "Startup", + startup_evt, 0, true }, + { 0x01, "Fatal Exception", + fatal_exception_evt, 4, true }, + { 0x02, "Bootup", + bootup_evt, 6, true }, + { 0x05, "Default BD Data", + default_bd_data_evt, 1, true }, + { 0x06, "Secure Send Commands Result", + secure_send_commands_result_evt, 4, true }, + { 0x08, "Debug Exception", + debug_exception_evt, 4, true }, + { 0x0f, "LE Link Established", + le_link_established_evt, 26, true }, + { 0x11, "Scan Status", + scan_status_evt, 1, true }, + { 0x16, "Activate Deactivate Traces Complete", + act_deact_traces_complete_evt, 1, true }, + { 0x17, "LMP PDU Trace", + lmp_pdu_trace_evt, 3, false }, + { 0x19, "Write BD Data Complete", + write_bd_data_complete_evt, 1, true }, + { 0x25, "SCO Rejected via LMP", + sco_rejected_via_lmp_evt, 7, true }, + { 0x26, "PTT Switch Notification", + ptt_switch_notification_evt, 3, true }, + { 0x29, "System Exception", + system_exception_evt, 133, true }, + { 0x2c, "FW Trace String" }, + { 0x2e, "FW Trace Binary" }, + { } +}; + +const struct vendor_evt *intel_vendor_evt(uint8_t evt) +{ + int i; + + for (i = 0; vendor_evt_table[i].str; i++) { + if (vendor_evt_table[i].evt == evt) + return &vendor_evt_table[i]; + } + + return NULL; +} diff --git a/monitor/intel.h b/monitor/intel.h new file mode 100644 index 0000000..573b23f --- /dev/null +++ b/monitor/intel.h @@ -0,0 +1,31 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +struct vendor_ocf; +struct vendor_evt; + +const struct vendor_ocf *intel_vendor_ocf(uint16_t ocf); +const struct vendor_evt *intel_vendor_evt(uint8_t evt); diff --git a/monitor/jlink.c b/monitor/jlink.c new file mode 100644 index 0000000..afa9d93 --- /dev/null +++ b/monitor/jlink.c @@ -0,0 +1,283 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Codecoup + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "jlink.h" + +#define RTT_CONTROL_START 0 +#define RTT_CONTROL_STOP 1 +#define RTT_CONTROL_GET_DESC 2 +#define RTT_CONTROL_GET_NUM_BUF 3 +#define RTT_CONTROL_GET_STAT 4 + +#define RTT_DIRECTION_UP 0 +#define RTT_DIRECTION_DOWN 1 + +static const char * const jlink_so_name[] = { + "/usr/lib/libjlinkarm.so", + "/usr/lib/libjlinkarm.so.6", + "/opt/SEGGER/JLink/libjlinkarm.so", + "/opt/SEGGER/JLink/libjlinkarm.so.6", +}; + +struct rtt_desc { + uint32_t index; + uint32_t direction; + char name[32]; + uint32_t size; + uint32_t flags; +}; + +static struct rtt_desc rtt_desc; + +typedef int (*jlink_emu_selectbyusbsn_func) (unsigned int sn); +typedef int (*jlink_open_func) (void); +typedef int (*jlink_execcommand_func) (char *in, char *out, int size); +typedef int (*jlink_tif_select_func) (int); +typedef void (*jlink_setspeed_func) (long int speed); +typedef int (*jlink_connect_func) (void); +typedef unsigned int (*jlink_getsn_func) (void); +typedef void (*jlink_emu_getproductname_func) (char *out, int size); +typedef int (*jlink_rtterminal_control_func) (int cmd, void *data); +typedef int (*jlink_rtterminal_read_func) (int cmd, char *buf, int size); + +struct jlink { + jlink_emu_selectbyusbsn_func emu_selectbyusbsn; + jlink_open_func open; + jlink_execcommand_func execcommand; + jlink_tif_select_func tif_select; + jlink_setspeed_func setspeed; + jlink_connect_func connect; + jlink_getsn_func getsn; + jlink_emu_getproductname_func emu_getproductname; + jlink_rtterminal_control_func rtterminal_control; + jlink_rtterminal_read_func rtterminal_read; +}; + +static struct jlink jlink; + +#ifndef NELEM +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) +#endif + +int jlink_init(void) +{ + void *so; + unsigned int i; + + for (i = 0; i < NELEM(jlink_so_name); i++) { + so = dlopen(jlink_so_name[i], RTLD_LAZY); + if (so) + break; + } + + if (!so) + return -EIO; + + jlink.emu_selectbyusbsn = dlsym(so, "JLINK_EMU_SelectByUSBSN"); + jlink.open = dlsym(so, "JLINK_Open"); + jlink.execcommand = dlsym(so, "JLINK_ExecCommand"); + jlink.tif_select = dlsym(so, "JLINK_TIF_Select"); + jlink.setspeed = dlsym(so, "JLINK_SetSpeed"); + jlink.connect = dlsym(so, "JLINK_Connect"); + jlink.getsn = dlsym(so, "JLINK_GetSN"); + jlink.emu_getproductname = dlsym(so, "JLINK_EMU_GetProductName"); + jlink.rtterminal_control = dlsym(so, "JLINK_RTTERMINAL_Control"); + jlink.rtterminal_read = dlsym(so, "JLINK_RTTERMINAL_Read"); + + if (!jlink.emu_selectbyusbsn || !jlink.open || !jlink.execcommand || + !jlink.tif_select || !jlink.setspeed || + !jlink.connect || !jlink.getsn || + !jlink.emu_getproductname || + !jlink.rtterminal_control || !jlink.rtterminal_read) + return -EIO; + + return 0; +} + +int jlink_connect(char *cfg) +{ + const char *device = NULL; + int tif = 1; + unsigned int speed = 1000; + unsigned int serial_no = 0; + char *tok; + char buf[64]; + + tok = strtok(cfg, ","); + device = tok; + + tok = strtok(NULL, ","); + if (!tok) + goto connect; + if (strlen(tok)) + serial_no = atoi(tok); + + tok = strtok(NULL, ","); + if (!tok) + goto connect; + if (strlen(tok)) { + if (!strcasecmp("swd", tok)) + tif = 1; + else + return -EINVAL; + } + + tok = strtok(NULL, ","); + if (!tok) + goto connect; + if (strlen(tok)) + speed = atoi(tok); + +connect: + if (serial_no) + if (jlink.emu_selectbyusbsn(serial_no) < 0) { + fprintf(stderr, "Failed to select emu by SN\n"); + return -ENODEV; + } + + if (jlink.open() < 0) { + fprintf(stderr, "Failed to open J-Link\n"); + return -ENODEV; + } + + snprintf(buf, sizeof(buf), "device=%s", device); + if (jlink.execcommand(buf, NULL, 0) < 0) { + fprintf(stderr, "Failed to select target device\n"); + return -ENODEV; + } + + if (jlink.tif_select(tif) < 0) { + fprintf(stderr, "Failed to select target interface\n"); + return -ENODEV; + } + + jlink.setspeed(speed); + + if (jlink.connect() < 0) { + fprintf(stderr, "Failed to open target\n"); + return -EIO; + } + + serial_no = jlink.getsn(); + jlink.emu_getproductname(buf, sizeof(buf)); + + printf("Connected to %s (S/N: %u)\n", buf, serial_no); + + return 0; +} + +int jlink_start_rtt(char *cfg) +{ + unsigned int address = 0; + unsigned int area_size = 0; + const char *buffer = "btmonitor"; + char *tok; + char cmd[64]; + int rtt_dir; + int count; + int i; + + if (!cfg) + goto find_rttcb; + + tok = strtok(cfg, ","); + if (strlen(tok)) { + address = strtol(tok, NULL, 0); + area_size = 0x1000; + } + + tok = strtok(NULL, ","); + if (!tok) + goto find_rttcb; + if (strlen(tok)) + area_size = strtol(tok, NULL, 0); + + tok = strtok(NULL, ","); + if (!tok) + goto find_rttcb; + if (strlen(tok)) + buffer = tok; + +find_rttcb: + if (address || area_size) { + if (!area_size) + snprintf(cmd, sizeof(cmd), "SetRTTAddr 0x%x", address); + else + snprintf(cmd, sizeof(cmd), + "SetRTTSearchRanges 0x%x 0x%x", + address, area_size); + + if (jlink.execcommand(cmd, NULL, 0) < 0) + return -EIO; + } + + if (jlink.rtterminal_control(RTT_CONTROL_START, NULL) < 0) { + fprintf(stderr, "Failed to initialize RTT\n"); + return -1; + } + + /* RTT may need some time to find control block so we need to wait */ + do { + usleep(100); + rtt_dir = RTT_DIRECTION_UP; + count = jlink.rtterminal_control(RTT_CONTROL_GET_NUM_BUF, + &rtt_dir); + } while (count < 0); + + for (i = 0; i < count; i++) { + memset(&rtt_desc, 0, sizeof(rtt_desc)); + rtt_desc.index = i; + rtt_desc.direction = RTT_DIRECTION_UP; + + if (jlink.rtterminal_control(RTT_CONTROL_GET_DESC, + &rtt_desc) < 0) + continue; + + if (rtt_desc.size > 0 && !strcmp(buffer, rtt_desc.name)) + break; + } + + if (i == count) + return -ENODEV; + + printf("Using RTT up buffer #%d (size: %d)\n", i, rtt_desc.size); + + return 0; +} + +int jlink_rtt_read(void *buf, size_t size) +{ + return jlink.rtterminal_read(rtt_desc.index, buf, size); +} diff --git a/monitor/jlink.h b/monitor/jlink.h new file mode 100644 index 0000000..d7c7670 --- /dev/null +++ b/monitor/jlink.h @@ -0,0 +1,27 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Codecoup + * + * + * 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 + * + */ + +int jlink_init(void); +int jlink_connect(char *cfg); +int jlink_start_rtt(char *cfg); +int jlink_rtt_read(void *buf, size_t size); diff --git a/monitor/keys.c b/monitor/keys.c new file mode 100644 index 0000000..e60aa93 --- /dev/null +++ b/monitor/keys.c @@ -0,0 +1,127 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/crypto.h" + +#include "keys.h" + +static const uint8_t empty_key[16] = { 0x00, }; +static const uint8_t empty_addr[6] = { 0x00, }; + +static struct bt_crypto *crypto; + +struct irk_data { + uint8_t key[16]; + uint8_t addr[6]; + uint8_t addr_type; +}; + +static struct queue *irk_list; + +void keys_setup(void) +{ + crypto = bt_crypto_new(); + + irk_list = queue_new(); +} + +void keys_cleanup(void) +{ + bt_crypto_unref(crypto); + + queue_destroy(irk_list, free); +} + +void keys_update_identity_key(const uint8_t key[16]) +{ + struct irk_data *irk; + + irk = queue_peek_tail(irk_list); + if (irk && !memcmp(irk->key, empty_key, 16)) { + memcpy(irk->key, key, 16); + return; + } + + irk = new0(struct irk_data, 1); + if (irk) { + memcpy(irk->key, key, 16); + if (!queue_push_tail(irk_list, irk)) + free(irk); + } +} + +void keys_update_identity_addr(const uint8_t addr[6], uint8_t addr_type) +{ + struct irk_data *irk; + + irk = queue_peek_tail(irk_list); + if (irk && !memcmp(irk->addr, empty_addr, 6)) { + memcpy(irk->addr, addr, 6); + irk->addr_type = addr_type; + return; + } + + irk = new0(struct irk_data, 1); + if (irk) { + memcpy(irk->addr, addr, 6); + irk->addr_type = addr_type; + if (!queue_push_tail(irk_list, irk)) + free(irk); + } +} + +static bool match_resolve_irk(const void *data, const void *match_data) +{ + const struct irk_data *irk = data; + const uint8_t *addr = match_data; + uint8_t local_hash[3]; + + bt_crypto_ah(crypto, irk->key, addr + 3, local_hash); + + return !memcmp(addr, local_hash, 3); +} + +bool keys_resolve_identity(const uint8_t addr[6], uint8_t ident[6], + uint8_t *ident_type) +{ + struct irk_data *irk; + + irk = queue_find(irk_list, match_resolve_irk, addr); + + if (irk) { + memcpy(ident, irk->addr, 6); + *ident_type = irk->addr_type; + return true; + } + + return false; +} diff --git a/monitor/keys.h b/monitor/keys.h new file mode 100644 index 0000000..61ec50a --- /dev/null +++ b/monitor/keys.h @@ -0,0 +1,35 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +void keys_setup(void); +void keys_cleanup(void); + +void keys_update_identity_key(const uint8_t key[16]); +void keys_update_identity_addr(const uint8_t addr[6], uint8_t addr_type); + +bool keys_resolve_identity(const uint8_t addr[6], uint8_t ident[6], + uint8_t *ident_type); diff --git a/monitor/l2cap.c b/monitor/l2cap.c new file mode 100644 index 0000000..d4feca4 --- /dev/null +++ b/monitor/l2cap.c @@ -0,0 +1,3277 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "bt.h" +#include "packet.h" +#include "display.h" +#include "l2cap.h" +#include "keys.h" +#include "sdp.h" +#include "avctp.h" +#include "avdtp.h" +#include "rfcomm.h" +#include "bnep.h" + + +#define L2CAP_MODE_BASIC 0x00 +#define L2CAP_MODE_RETRANS 0x01 +#define L2CAP_MODE_FLOWCTL 0x02 +#define L2CAP_MODE_ERTM 0x03 +#define L2CAP_MODE_STREAMING 0x04 +#define L2CAP_MODE_LE_FLOWCTL 0x80 + +/* L2CAP Control Field bit masks */ +#define L2CAP_CTRL_SAR_MASK 0xC000 +#define L2CAP_CTRL_REQSEQ_MASK 0x3F00 +#define L2CAP_CTRL_TXSEQ_MASK 0x007E +#define L2CAP_CTRL_SUPERVISE_MASK 0x000C + +#define L2CAP_CTRL_RETRANS 0x0080 +#define L2CAP_CTRL_FINAL 0x0080 +#define L2CAP_CTRL_POLL 0x0010 +#define L2CAP_CTRL_FRAME_TYPE 0x0001 /* I- or S-Frame */ + +#define L2CAP_CTRL_TXSEQ_SHIFT 1 +#define L2CAP_CTRL_SUPER_SHIFT 2 +#define L2CAP_CTRL_REQSEQ_SHIFT 8 +#define L2CAP_CTRL_SAR_SHIFT 14 + +#define L2CAP_EXT_CTRL_TXSEQ_MASK 0xFFFC0000 +#define L2CAP_EXT_CTRL_SAR_MASK 0x00030000 +#define L2CAP_EXT_CTRL_SUPERVISE_MASK 0x00030000 +#define L2CAP_EXT_CTRL_REQSEQ_MASK 0x0000FFFC + +#define L2CAP_EXT_CTRL_POLL 0x00040000 +#define L2CAP_EXT_CTRL_FINAL 0x00000002 +#define L2CAP_EXT_CTRL_FRAME_TYPE 0x00000001 /* I- or S-Frame */ + +#define L2CAP_EXT_CTRL_REQSEQ_SHIFT 2 +#define L2CAP_EXT_CTRL_SAR_SHIFT 16 +#define L2CAP_EXT_CTRL_SUPER_SHIFT 16 +#define L2CAP_EXT_CTRL_TXSEQ_SHIFT 18 + +/* L2CAP Supervisory Function */ +#define L2CAP_SUPER_RR 0x00 +#define L2CAP_SUPER_REJ 0x01 +#define L2CAP_SUPER_RNR 0x02 +#define L2CAP_SUPER_SREJ 0x03 + +/* L2CAP Segmentation and Reassembly */ +#define L2CAP_SAR_UNSEGMENTED 0x00 +#define L2CAP_SAR_START 0x01 +#define L2CAP_SAR_END 0x02 +#define L2CAP_SAR_CONTINUE 0x03 + +#define MAX_CHAN 64 + +struct chan_data { + uint16_t index; + uint16_t handle; + uint8_t ident; + uint16_t scid; + uint16_t dcid; + uint16_t psm; + uint8_t ctrlid; + uint8_t mode; + uint8_t ext_ctrl; + uint8_t seq_num; + uint16_t sdu; +}; + +static struct chan_data chan_list[MAX_CHAN]; + +static void assign_scid(const struct l2cap_frame *frame, uint16_t scid, + uint16_t psm, uint8_t mode, uint8_t ctrlid) +{ + int i, n = -1; + uint8_t seq_num = 1; + + for (i = 0; i < MAX_CHAN; i++) { + if (n < 0 && chan_list[i].handle == 0x0000) { + n = i; + continue; + } + + if (chan_list[i].index != frame->index) + continue; + + if (chan_list[i].handle != frame->handle) + continue; + + if (chan_list[i].psm == psm) + seq_num++; + + /* Don't break on match - we still need to go through all + * channels to find proper seq_num. + */ + if (frame->in) { + if (chan_list[i].dcid == scid) + n = i; + } else { + if (chan_list[i].scid == scid) + n = i; + } + } + + if (n < 0) + return; + + memset(&chan_list[n], 0, sizeof(chan_list[n])); + chan_list[n].index = frame->index; + chan_list[n].handle = frame->handle; + chan_list[n].ident = frame->ident; + + if (frame->in) + chan_list[n].dcid = scid; + else + chan_list[n].scid = scid; + + chan_list[n].psm = psm; + chan_list[n].ctrlid = ctrlid; + chan_list[n].mode = mode; + + chan_list[n].seq_num = seq_num; +} + +static void release_scid(const struct l2cap_frame *frame, uint16_t scid) +{ + int i; + + for (i = 0; i < MAX_CHAN; i++) { + if (chan_list[i].index != frame->index) + continue; + + if (chan_list[i].handle != frame->handle) + continue; + + if (frame->in) { + if (chan_list[i].scid == scid) { + chan_list[i].handle = 0; + break; + } + } else { + if (chan_list[i].dcid == scid) { + chan_list[i].handle = 0; + break; + } + } + } +} + +static void assign_dcid(const struct l2cap_frame *frame, uint16_t dcid, + uint16_t scid) +{ + int i; + + for (i = 0; i < MAX_CHAN; i++) { + if (chan_list[i].index != frame->index) + continue; + + if (chan_list[i].handle != frame->handle) + continue; + + if (frame->ident != 0 && chan_list[i].ident != frame->ident) + continue; + + if (frame->in) { + if (scid) { + if (chan_list[i].scid == scid) { + chan_list[i].dcid = dcid; + break; + } + } else { + if (chan_list[i].scid && !chan_list[i].dcid) { + chan_list[i].dcid = dcid; + break; + } + } + } else { + if (scid) { + if (chan_list[i].dcid == scid) { + chan_list[i].scid = dcid; + break; + } + } else { + if (chan_list[i].dcid && !chan_list[i].scid) { + chan_list[i].scid = dcid; + break; + } + } + } + } +} + +static void assign_mode(const struct l2cap_frame *frame, + uint8_t mode, uint16_t dcid) +{ + int i; + + for (i = 0; i < MAX_CHAN; i++) { + if (chan_list[i].index != frame->index) + continue; + + if (chan_list[i].handle != frame->handle) + continue; + + if (frame->in) { + if (chan_list[i].scid == dcid) { + chan_list[i].mode = mode; + break; + } + } else { + if (chan_list[i].dcid == dcid) { + chan_list[i].mode = mode; + break; + } + } + } +} + +static int get_chan_data_index(const struct l2cap_frame *frame) +{ + int i; + + for (i = 0; i < MAX_CHAN; i++) { + if (chan_list[i].index != frame->index && + chan_list[i].ctrlid == 0) + continue; + + if (chan_list[i].ctrlid != 0 && + chan_list[i].ctrlid != frame->index) + continue; + + if (chan_list[i].handle != frame->handle) + continue; + + if (frame->in) { + if (chan_list[i].scid == frame->cid) + return i; + } else { + if (chan_list[i].dcid == frame->cid) + return i; + } + } + + return -1; +} + +static struct chan_data *get_chan(const struct l2cap_frame *frame) +{ + int i; + + if (frame->chan != UINT16_MAX) + return &chan_list[frame->chan]; + + i = get_chan_data_index(frame); + if (i < 0) + return NULL; + + return &chan_list[i]; +} + +static uint16_t get_psm(const struct l2cap_frame *frame) +{ + struct chan_data *data = get_chan(frame); + + if (!data) + return 0; + + return data->psm; +} + +static uint8_t get_mode(const struct l2cap_frame *frame) +{ + struct chan_data *data = get_chan(frame); + + if (!data) + return 0; + + return data->mode; +} + +static uint8_t get_seq_num(const struct l2cap_frame *frame) +{ + struct chan_data *data = get_chan(frame); + + if (!data) + return 0; + + return data->seq_num; +} + +static void assign_ext_ctrl(const struct l2cap_frame *frame, + uint8_t ext_ctrl, uint16_t dcid) +{ + int i; + + for (i = 0; i < MAX_CHAN; i++) { + if (chan_list[i].index != frame->index) + continue; + + if (chan_list[i].handle != frame->handle) + continue; + + if (frame->in) { + if (chan_list[i].scid == dcid) { + chan_list[i].ext_ctrl = ext_ctrl; + break; + } + } else { + if (chan_list[i].dcid == dcid) { + chan_list[i].ext_ctrl = ext_ctrl; + break; + } + } + } +} + +static uint8_t get_ext_ctrl(const struct l2cap_frame *frame) +{ + struct chan_data *data = get_chan(frame); + + if (!data) + return 0; + + return data->ext_ctrl; +} + +static char *sar2str(uint8_t sar) +{ + switch (sar) { + case L2CAP_SAR_UNSEGMENTED: + return "Unsegmented"; + case L2CAP_SAR_START: + return "Start"; + case L2CAP_SAR_END: + return "End"; + case L2CAP_SAR_CONTINUE: + return "Continuation"; + default: + return "Bad SAR"; + } +} + +static char *supervisory2str(uint8_t supervisory) +{ + switch (supervisory) { + case L2CAP_SUPER_RR: + return "Receiver Ready (RR)"; + case L2CAP_SUPER_REJ: + return "Reject (REJ)"; + case L2CAP_SUPER_RNR: + return "Receiver Not Ready (RNR)"; + case L2CAP_SUPER_SREJ: + return "Select Reject (SREJ)"; + default: + return "Bad Supervisory"; + } +} + +static char *mode2str(uint8_t mode) +{ + switch (mode) { + case L2CAP_MODE_BASIC: + return "Basic"; + case L2CAP_MODE_RETRANS: + return "Retransmission"; + case L2CAP_MODE_FLOWCTL: + return "Flow Control"; + case L2CAP_MODE_ERTM: + return "Enhanced Retransmission"; + case L2CAP_MODE_STREAMING: + return "Streaming"; + case L2CAP_MODE_LE_FLOWCTL: + return "LE Flow Control"; + default: + return "Unknown"; + } +} + +static void l2cap_ctrl_ext_parse(struct l2cap_frame *frame, uint32_t ctrl) +{ + printf(" %s:", + ctrl & L2CAP_EXT_CTRL_FRAME_TYPE ? "S-frame" : "I-frame"); + + if (ctrl & L2CAP_EXT_CTRL_FRAME_TYPE) { + printf(" %s", + supervisory2str((ctrl & L2CAP_EXT_CTRL_SUPERVISE_MASK) >> + L2CAP_EXT_CTRL_SUPER_SHIFT)); + + if (ctrl & L2CAP_EXT_CTRL_POLL) + printf(" P-bit"); + } else { + uint8_t sar = (ctrl & L2CAP_EXT_CTRL_SAR_MASK) >> + L2CAP_EXT_CTRL_SAR_SHIFT; + printf(" %s", sar2str(sar)); + if (sar == L2CAP_SAR_START) { + uint16_t len; + + if (!l2cap_frame_get_le16(frame, &len)) + return; + + printf(" (len %d)", len); + } + printf(" TxSeq %d", (ctrl & L2CAP_EXT_CTRL_TXSEQ_MASK) >> + L2CAP_EXT_CTRL_TXSEQ_SHIFT); + } + + printf(" ReqSeq %d", (ctrl & L2CAP_EXT_CTRL_REQSEQ_MASK) >> + L2CAP_EXT_CTRL_REQSEQ_SHIFT); + + if (ctrl & L2CAP_EXT_CTRL_FINAL) + printf(" F-bit"); +} + +static void l2cap_ctrl_parse(struct l2cap_frame *frame, uint32_t ctrl) +{ + printf(" %s:", + ctrl & L2CAP_CTRL_FRAME_TYPE ? "S-frame" : "I-frame"); + + if (ctrl & 0x01) { + printf(" %s", + supervisory2str((ctrl & L2CAP_CTRL_SUPERVISE_MASK) >> + L2CAP_CTRL_SUPER_SHIFT)); + + if (ctrl & L2CAP_CTRL_POLL) + printf(" P-bit"); + } else { + uint8_t sar; + + sar = (ctrl & L2CAP_CTRL_SAR_MASK) >> L2CAP_CTRL_SAR_SHIFT; + printf(" %s", sar2str(sar)); + if (sar == L2CAP_SAR_START) { + uint16_t len; + + if (!l2cap_frame_get_le16(frame, &len)) + return; + + printf(" (len %d)", len); + } + printf(" TxSeq %d", (ctrl & L2CAP_CTRL_TXSEQ_MASK) >> + L2CAP_CTRL_TXSEQ_SHIFT); + } + + printf(" ReqSeq %d", (ctrl & L2CAP_CTRL_REQSEQ_MASK) >> + L2CAP_CTRL_REQSEQ_SHIFT); + + if (ctrl & L2CAP_CTRL_FINAL) + printf(" F-bit"); +} + +#define MAX_INDEX 16 + +struct index_data { + void *frag_buf; + uint16_t frag_pos; + uint16_t frag_len; + uint16_t frag_cid; +}; + +static struct index_data index_list[MAX_INDEX][2]; + +static void clear_fragment_buffer(uint16_t index, bool in) +{ + free(index_list[index][in].frag_buf); + index_list[index][in].frag_buf = NULL; + index_list[index][in].frag_pos = 0; + index_list[index][in].frag_len = 0; +} + +static void print_psm(uint16_t psm) +{ + print_field("PSM: %d (0x%4.4x)", le16_to_cpu(psm), le16_to_cpu(psm)); +} + +static void print_cid(const char *type, uint16_t cid) +{ + print_field("%s CID: %d", type, le16_to_cpu(cid)); +} + +static void print_reject_reason(uint16_t reason) +{ + const char *str; + + switch (le16_to_cpu(reason)) { + case 0x0000: + str = "Command not understood"; + break; + case 0x0001: + str = "Signaling MTU exceeded"; + break; + case 0x0002: + str = "Invalid CID in request"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Reason: %s (0x%4.4x)", str, le16_to_cpu(reason)); +} + +static void print_conn_result(uint16_t result) +{ + const char *str; + + switch (le16_to_cpu(result)) { + case 0x0000: + str = "Connection successful"; + break; + case 0x0001: + str = "Connection pending"; + break; + case 0x0002: + str = "Connection refused - PSM not supported"; + break; + case 0x0003: + str = "Connection refused - security block"; + break; + case 0x0004: + str = "Connection refused - no resources available"; + break; + case 0x0006: + str = "Connection refused - Invalid Source CID"; + break; + case 0x0007: + str = "Connection refused - Source CID already allocated"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Result: %s (0x%4.4x)", str, le16_to_cpu(result)); +} + +static void print_le_conn_result(uint16_t result) +{ + const char *str; + + switch (le16_to_cpu(result)) { + case 0x0000: + str = "Connection successful"; + break; + case 0x0002: + str = "Connection refused - PSM not supported"; + break; + case 0x0004: + str = "Connection refused - no resources available"; + break; + case 0x0005: + str = "Connection refused - insufficient authentication"; + break; + case 0x0006: + str = "Connection refused - insufficient authorization"; + break; + case 0x0007: + str = "Connection refused - insufficient encryption key size"; + break; + case 0x0008: + str = "Connection refused - insufficient encryption"; + break; + case 0x0009: + str = "Connection refused - Invalid Source CID"; + break; + case 0x000a: + str = "Connection refused - Source CID already allocated"; + break; + case 0x000b: + str = "Connection refused - unacceptable parameters"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Result: %s (0x%4.4x)", str, le16_to_cpu(result)); +} + +static void print_create_chan_result(uint16_t result) +{ + const char *str; + + switch (le16_to_cpu(result)) { + case 0x0000: + str = "Connection successful"; + break; + case 0x0001: + str = "Connection pending"; + break; + case 0x0002: + str = "Connection refused - PSM not supported"; + break; + case 0x0003: + str = "Connection refused - security block"; + break; + case 0x0004: + str = "Connection refused - no resources available"; + break; + case 0x0005: + str = "Connection refused - Controller ID not supported"; + break; + case 0x0006: + str = "Connection refused - Invalid Source CID"; + break; + case 0x0007: + str = "Connection refused - Source CID already allocated"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Result: %s (0x%4.4x)", str, le16_to_cpu(result)); +} + +static void print_conn_status(uint16_t status) +{ + const char *str; + + switch (le16_to_cpu(status)) { + case 0x0000: + str = "No further information available"; + break; + case 0x0001: + str = "Authentication pending"; + break; + case 0x0002: + str = "Authorization pending"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Status: %s (0x%4.4x)", str, le16_to_cpu(status)); +} + +static void print_config_flags(uint16_t flags) +{ + const char *str; + + if (le16_to_cpu(flags) & 0x0001) + str = " (continuation)"; + else + str = ""; + + print_field("Flags: 0x%4.4x%s", le16_to_cpu(flags), str); +} + +static void print_config_result(uint16_t result) +{ + const char *str; + + switch (le16_to_cpu(result)) { + case 0x0000: + str = "Success"; + break; + case 0x0001: + str = "Failure - unacceptable parameters"; + break; + case 0x0002: + str = "Failure - rejected"; + break; + case 0x0003: + str = "Failure - unknown options"; + break; + case 0x0004: + str = "Pending"; + break; + case 0x0005: + str = "Failure - flow spec rejected"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Result: %s (0x%4.4x)", str, le16_to_cpu(result)); +} + +static struct { + uint8_t type; + uint8_t len; + const char *str; +} options_table[] = { + { 0x01, 2, "Maximum Transmission Unit" }, + { 0x02, 2, "Flush Timeout" }, + { 0x03, 22, "Quality of Service" }, + { 0x04, 9, "Retransmission and Flow Control" }, + { 0x05, 1, "Frame Check Sequence" }, + { 0x06, 16, "Extended Flow Specification" }, + { 0x07, 2, "Extended Window Size" }, + { } +}; + +static void print_config_options(const struct l2cap_frame *frame, + uint8_t offset, uint16_t cid, bool response) +{ + const uint8_t *data = frame->data + offset; + uint16_t size = frame->size - offset; + uint16_t consumed = 0; + + while (consumed < size - 2) { + const char *str = "Unknown"; + uint8_t type = data[consumed] & 0x7f; + uint8_t hint = data[consumed] & 0x80; + uint8_t len = data[consumed + 1]; + uint8_t expect_len = 0; + int i; + + for (i = 0; options_table[i].str; i++) { + if (options_table[i].type == type) { + str = options_table[i].str; + expect_len = options_table[i].len; + break; + } + } + + print_field("Option: %s (0x%2.2x) [%s]", str, type, + hint ? "hint" : "mandatory"); + + if (expect_len == 0) { + consumed += 2; + break; + } + + if (len != expect_len) { + print_text(COLOR_ERROR, "wrong option size (%d != %d)", + len, expect_len); + consumed += 2; + break; + } + + switch (type) { + case 0x01: + print_field(" MTU: %d", + get_le16(data + consumed + 2)); + break; + case 0x02: + print_field(" Flush timeout: %d", + get_le16(data + consumed + 2)); + break; + case 0x03: + switch (data[consumed + 3]) { + case 0x00: + str = "No Traffic"; + break; + case 0x01: + str = "Best Effort"; + break; + case 0x02: + str = "Guaranteed"; + break; + default: + str = "Reserved"; + break; + } + print_field(" Flags: 0x%2.2x", data[consumed + 2]); + print_field(" Service type: %s (0x%2.2x)", + str, data[consumed + 3]); + print_field(" Token rate: 0x%8.8x", + get_le32(data + consumed + 4)); + print_field(" Token bucket size: 0x%8.8x", + get_le32(data + consumed + 8)); + print_field(" Peak bandwidth: 0x%8.8x", + get_le32(data + consumed + 12)); + print_field(" Latency: 0x%8.8x", + get_le32(data + consumed + 16)); + print_field(" Delay variation: 0x%8.8x", + get_le32(data + consumed + 20)); + break; + case 0x04: + if (response) + assign_mode(frame, data[consumed + 2], cid); + + print_field(" Mode: %s (0x%2.2x)", + mode2str(data[consumed + 2]), + data[consumed + 2]); + print_field(" TX window size: %d", data[consumed + 3]); + print_field(" Max transmit: %d", data[consumed + 4]); + print_field(" Retransmission timeout: %d", + get_le16(data + consumed + 5)); + print_field(" Monitor timeout: %d", + get_le16(data + consumed + 7)); + print_field(" Maximum PDU size: %d", + get_le16(data + consumed + 9)); + break; + case 0x05: + switch (data[consumed + 2]) { + case 0x00: + str = "No FCS"; + break; + case 0x01: + str = "16-bit FCS"; + break; + default: + str = "Reserved"; + break; + } + print_field(" FCS: %s (0x%2.2d)", + str, data[consumed + 2]); + break; + case 0x06: + switch (data[consumed + 3]) { + case 0x00: + str = "No traffic"; + break; + case 0x01: + str = "Best effort"; + break; + case 0x02: + str = "Guaranteed"; + break; + default: + str = "Reserved"; + break; + } + print_field(" Identifier: 0x%2.2x", + data[consumed + 2]); + print_field(" Service type: %s (0x%2.2x)", + str, data[consumed + 3]); + print_field(" Maximum SDU size: 0x%4.4x", + get_le16(data + consumed + 4)); + print_field(" SDU inter-arrival time: 0x%8.8x", + get_le32(data + consumed + 6)); + print_field(" Access latency: 0x%8.8x", + get_le32(data + consumed + 10)); + print_field(" Flush timeout: 0x%8.8x", + get_le32(data + consumed + 14)); + break; + case 0x07: + print_field(" Extended window size: %d", + get_le16(data + consumed + 2)); + assign_ext_ctrl(frame, 1, cid); + break; + default: + packet_hexdump(data + consumed + 2, len); + break; + } + + consumed += len + 2; + } + + if (consumed < size) + packet_hexdump(data + consumed, size - consumed); +} + +static void print_info_type(uint16_t type) +{ + const char *str; + + switch (le16_to_cpu(type)) { + case 0x0001: + str = "Connectionless MTU"; + break; + case 0x0002: + str = "Extended features supported"; + break; + case 0x0003: + str = "Fixed channels supported"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%4.4x)", str, le16_to_cpu(type)); +} + +static void print_info_result(uint16_t result) +{ + const char *str; + + switch (le16_to_cpu(result)) { + case 0x0000: + str = "Success"; + break; + case 0x0001: + str = "Not supported"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Result: %s (0x%4.4x)", str, le16_to_cpu(result)); +} + +static const struct bitfield_data features_table[] = { + { 0, "Flow control mode" }, + { 1, "Retransmission mode" }, + { 2, "Bi-directional QoS" }, + { 3, "Enhanced Retransmission Mode" }, + { 4, "Streaming Mode" }, + { 5, "FCS Option" }, + { 6, "Extended Flow Specification for BR/EDR" }, + { 7, "Fixed Channels" }, + { 8, "Extended Window Size" }, + { 9, "Unicast Connectionless Data Reception" }, + { 31, "Reserved for feature mask extension" }, + { } +}; + +static void print_features(uint32_t features) +{ + uint32_t mask; + + print_field("Features: 0x%8.8x", features); + + mask = print_bitfield(2, features, features_table); + if (mask) + print_field(" Unknown features (0x%8.8x)", mask); +} + +static const struct bitfield_data channels_table[] = { + { 0x0000, "Null identifier" }, + { 0x0001, "L2CAP Signaling (BR/EDR)" }, + { 0x0002, "Connectionless reception" }, + { 0x0003, "AMP Manager Protocol" }, + { 0x0004, "Attribute Protocol" }, + { 0x0005, "L2CAP Signaling (LE)" }, + { 0x0006, "Security Manager (LE)" }, + { 0x0007, "Security Manager (BR/EDR)" }, + { 0x003f, "AMP Test Manager" }, + { } +}; + +static void print_channels(uint64_t channels) +{ + uint64_t mask; + + print_field("Channels: 0x%16.16" PRIx64, channels); + + mask = print_bitfield(2, channels, channels_table); + if (mask) + print_field(" Unknown channels (0x%8.8" PRIx64 ")", mask); +} + +static void print_move_result(uint16_t result) +{ + const char *str; + + switch (le16_to_cpu(result)) { + case 0x0000: + str = "Move success"; + break; + case 0x0001: + str = "Move pending"; + break; + case 0x0002: + str = "Move refused - Controller ID not supported"; + break; + case 0x0003: + str = "Move refused - new Controller ID is same"; + break; + case 0x0004: + str = "Move refused - Configuration not supported"; + break; + case 0x0005: + str = "Move refused - Move Channel collision"; + break; + case 0x0006: + str = "Move refused - Channel not allowed to be moved"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Result: %s (0x%4.4x)", str, le16_to_cpu(result)); +} + +static void print_move_cfm_result(uint16_t result) +{ + const char *str; + + switch (le16_to_cpu(result)) { + case 0x0000: + str = "Move success - both sides succeed"; + break; + case 0x0001: + str = "Move failure - one or both sides refuse"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Result: %s (0x%4.4x)", str, le16_to_cpu(result)); +} + +static void print_conn_param_result(uint16_t result) +{ + const char *str; + + switch (le16_to_cpu(result)) { + case 0x0000: + str = "Connection Parameters accepted"; + break; + case 0x0001: + str = "Connection Parameters rejected"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Result: %s (0x%4.4x)", str, le16_to_cpu(result)); +} + +static void sig_cmd_reject(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_cmd_reject *pdu = frame->data; + const void *data = frame->data; + uint16_t size = frame->size; + uint16_t scid, dcid; + + print_reject_reason(pdu->reason); + + data += sizeof(*pdu); + size -= sizeof(*pdu); + + switch (le16_to_cpu(pdu->reason)) { + case 0x0000: + if (size != 0) { + print_text(COLOR_ERROR, "invalid data size"); + packet_hexdump(data, size); + break; + } + break; + case 0x0001: + if (size != 2) { + print_text(COLOR_ERROR, "invalid data size"); + packet_hexdump(data, size); + break; + } + print_field("MTU: %d", get_le16(data)); + break; + case 0x0002: + if (size != 4) { + print_text(COLOR_ERROR, "invalid data size"); + packet_hexdump(data, size); + break; + } + dcid = get_le16(data); + scid = get_le16(data + 2); + print_cid("Destination", cpu_to_le16(dcid)); + print_cid("Source", cpu_to_le16(scid)); + break; + default: + packet_hexdump(data, size); + break; + } +} + +static void sig_conn_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_conn_req *pdu = frame->data; + + print_psm(pdu->psm); + print_cid("Source", pdu->scid); + + assign_scid(frame, le16_to_cpu(pdu->scid), le16_to_cpu(pdu->psm), + L2CAP_MODE_BASIC, 0); +} + +static void sig_conn_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_conn_rsp *pdu = frame->data; + + print_cid("Destination", pdu->dcid); + print_cid("Source", pdu->scid); + print_conn_result(pdu->result); + print_conn_status(pdu->status); + + assign_dcid(frame, le16_to_cpu(pdu->dcid), le16_to_cpu(pdu->scid)); +} + +static void sig_config_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_config_req *pdu = frame->data; + + print_cid("Destination", pdu->dcid); + print_config_flags(pdu->flags); + print_config_options(frame, 4, le16_to_cpu(pdu->dcid), false); +} + +static void sig_config_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_config_rsp *pdu = frame->data; + + print_cid("Source", pdu->scid); + print_config_flags(pdu->flags); + print_config_result(pdu->result); + print_config_options(frame, 6, le16_to_cpu(pdu->scid), true); +} + +static void sig_disconn_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_disconn_req *pdu = frame->data; + + print_cid("Destination", pdu->dcid); + print_cid("Source", pdu->scid); +} + +static void sig_disconn_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_disconn_rsp *pdu = frame->data; + + print_cid("Destination", pdu->dcid); + print_cid("Source", pdu->scid); + + release_scid(frame, le16_to_cpu(pdu->scid)); +} + +static void sig_echo_req(const struct l2cap_frame *frame) +{ + packet_hexdump(frame->data, frame->size); +} + +static void sig_echo_rsp(const struct l2cap_frame *frame) +{ + packet_hexdump(frame->data, frame->size); +} + +static void sig_info_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_info_req *pdu = frame->data; + + print_info_type(pdu->type); +} + +static void sig_info_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_info_rsp *pdu = frame->data; + const void *data = frame->data; + uint16_t size = frame->size; + + print_info_type(pdu->type); + print_info_result(pdu->result); + + data += sizeof(*pdu); + size -= sizeof(*pdu); + + if (le16_to_cpu(pdu->result) != 0x0000) { + if (size > 0) { + print_text(COLOR_ERROR, "invalid data size"); + packet_hexdump(data, size); + } + return; + } + + switch (le16_to_cpu(pdu->type)) { + case 0x0001: + if (size != 2) { + print_text(COLOR_ERROR, "invalid data size"); + packet_hexdump(data, size); + break; + } + print_field("MTU: %d", get_le16(data)); + break; + case 0x0002: + if (size != 4) { + print_text(COLOR_ERROR, "invalid data size"); + packet_hexdump(data, size); + break; + } + print_features(get_le32(data)); + break; + case 0x0003: + if (size != 8) { + print_text(COLOR_ERROR, "invalid data size"); + packet_hexdump(data, size); + break; + } + print_channels(get_le64(data)); + break; + default: + packet_hexdump(data, size); + break; + } +} + +static void sig_create_chan_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_create_chan_req *pdu = frame->data; + + print_psm(pdu->psm); + print_cid("Source", pdu->scid); + print_field("Controller ID: %d", pdu->ctrlid); + + assign_scid(frame, le16_to_cpu(pdu->scid), le16_to_cpu(pdu->psm), + L2CAP_MODE_BASIC, pdu->ctrlid); +} + +static void sig_create_chan_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_create_chan_rsp *pdu = frame->data; + + print_cid("Destination", pdu->dcid); + print_cid("Source", pdu->scid); + print_create_chan_result(pdu->result); + print_conn_status(pdu->status); + + assign_dcid(frame, le16_to_cpu(pdu->dcid), le16_to_cpu(pdu->scid)); +} + +static void sig_move_chan_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_move_chan_req *pdu = frame->data; + + print_cid("Initiator", pdu->icid); + print_field("Controller ID: %d", pdu->ctrlid); +} + +static void sig_move_chan_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_move_chan_rsp *pdu = frame->data; + + print_cid("Initiator", pdu->icid); + print_move_result(pdu->result); +} + +static void sig_move_chan_cfm(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_move_chan_cfm *pdu = frame->data; + + print_cid("Initiator", pdu->icid); + print_move_cfm_result(pdu->result); +} + +static void sig_move_chan_cfm_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_move_chan_cfm_rsp *pdu = frame->data; + + print_cid("Initiator", pdu->icid); +} + +static void sig_conn_param_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_conn_param_req *pdu = frame->data; + + print_field("Min interval: %d", le16_to_cpu(pdu->min_interval)); + print_field("Max interval: %d", le16_to_cpu(pdu->max_interval)); + print_field("Slave latency: %d", le16_to_cpu(pdu->latency)); + print_field("Timeout multiplier: %d", le16_to_cpu(pdu->timeout)); +} + +static void sig_conn_param_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_conn_param_rsp *pdu = frame->data; + + print_conn_param_result(pdu->result); +} + +static void sig_le_conn_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_le_conn_req *pdu = frame->data; + + print_psm(pdu->psm); + print_cid("Source", pdu->scid); + print_field("MTU: %u", le16_to_cpu(pdu->mtu)); + print_field("MPS: %u", le16_to_cpu(pdu->mps)); + print_field("Credits: %u", le16_to_cpu(pdu->credits)); + + assign_scid(frame, le16_to_cpu(pdu->scid), le16_to_cpu(pdu->psm), + L2CAP_MODE_LE_FLOWCTL, 0); +} + +static void sig_le_conn_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_le_conn_rsp *pdu = frame->data; + + print_cid("Destination", pdu->dcid); + print_field("MTU: %u", le16_to_cpu(pdu->mtu)); + print_field("MPS: %u", le16_to_cpu(pdu->mps)); + print_field("Credits: %u", le16_to_cpu(pdu->credits)); + print_le_conn_result(pdu->result); + + assign_dcid(frame, le16_to_cpu(pdu->dcid), 0); +} + +static void sig_le_flowctl_creds(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_pdu_le_flowctl_creds *pdu = frame->data; + + print_cid("Source", pdu->cid); + print_field("Credits: %u", le16_to_cpu(pdu->credits)); +} + +struct sig_opcode_data { + uint8_t opcode; + const char *str; + void (*func) (const struct l2cap_frame *frame); + uint16_t size; + bool fixed; +}; + +static const struct sig_opcode_data bredr_sig_opcode_table[] = { + { 0x01, "Command Reject", + sig_cmd_reject, 2, false }, + { 0x02, "Connection Request", + sig_conn_req, 4, true }, + { 0x03, "Connection Response", + sig_conn_rsp, 8, true }, + { 0x04, "Configure Request", + sig_config_req, 4, false }, + { 0x05, "Configure Response", + sig_config_rsp, 6, false }, + { 0x06, "Disconnection Request", + sig_disconn_req, 4, true }, + { 0x07, "Disconnection Response", + sig_disconn_rsp, 4, true }, + { 0x08, "Echo Request", + sig_echo_req, 0, false }, + { 0x09, "Echo Response", + sig_echo_rsp, 0, false }, + { 0x0a, "Information Request", + sig_info_req, 2, true }, + { 0x0b, "Information Response", + sig_info_rsp, 4, false }, + { 0x0c, "Create Channel Request", + sig_create_chan_req, 5, true }, + { 0x0d, "Create Channel Response", + sig_create_chan_rsp, 8, true }, + { 0x0e, "Move Channel Request", + sig_move_chan_req, 3, true }, + { 0x0f, "Move Channel Response", + sig_move_chan_rsp, 4, true }, + { 0x10, "Move Channel Confirmation", + sig_move_chan_cfm, 4, true }, + { 0x11, "Move Channel Confirmation Response", + sig_move_chan_cfm_rsp, 2, true }, + { }, +}; + +static const struct sig_opcode_data le_sig_opcode_table[] = { + { 0x01, "Command Reject", + sig_cmd_reject, 2, false }, + { 0x06, "Disconnection Request", + sig_disconn_req, 4, true }, + { 0x07, "Disconnection Response", + sig_disconn_rsp, 4, true }, + { 0x12, "Connection Parameter Update Request", + sig_conn_param_req, 8, true }, + { 0x13, "Connection Parameter Update Response", + sig_conn_param_rsp, 2, true }, + { 0x14, "LE Connection Request", + sig_le_conn_req, 10, true }, + { 0x15, "LE Connection Response", + sig_le_conn_rsp, 10, true }, + { 0x16, "LE Flow Control Credit", + sig_le_flowctl_creds, 4, true }, + { }, +}; + +static void l2cap_frame_init(struct l2cap_frame *frame, uint16_t index, bool in, + uint16_t handle, uint8_t ident, + uint16_t cid, uint16_t psm, + const void *data, uint16_t size) +{ + frame->index = index; + frame->in = in; + frame->handle = handle; + frame->ident = ident; + frame->cid = cid; + frame->data = data; + frame->size = size; + frame->chan = get_chan_data_index(frame); + frame->psm = psm ? psm : get_psm(frame); + frame->mode = get_mode(frame); + frame->seq_num = psm ? 1 : get_seq_num(frame); +} + +static void bredr_sig_packet(uint16_t index, bool in, uint16_t handle, + uint16_t cid, const void *data, uint16_t size) +{ + struct l2cap_frame frame; + + while (size > 0) { + const struct bt_l2cap_hdr_sig *hdr = data; + const struct sig_opcode_data *opcode_data = NULL; + const char *opcode_color, *opcode_str; + uint16_t len; + int i; + + if (size < 4) { + print_text(COLOR_ERROR, "malformed signal packet"); + packet_hexdump(data, size); + return; + } + + len = le16_to_cpu(hdr->len); + + data += 4; + size -= 4; + + if (size < len) { + print_text(COLOR_ERROR, "invalid signal packet size"); + packet_hexdump(data, size); + return; + } + + for (i = 0; bredr_sig_opcode_table[i].str; i++) { + if (bredr_sig_opcode_table[i].opcode == hdr->code) { + opcode_data = &bredr_sig_opcode_table[i]; + break; + } + } + + if (opcode_data) { + if (opcode_data->func) { + if (in) + opcode_color = COLOR_MAGENTA; + else + opcode_color = COLOR_BLUE; + } else + opcode_color = COLOR_WHITE_BG; + opcode_str = opcode_data->str; + } else { + opcode_color = COLOR_WHITE_BG; + opcode_str = "Unknown"; + } + + print_indent(6, opcode_color, "L2CAP: ", opcode_str, + COLOR_OFF, + " (0x%2.2x) ident %d len %d", + hdr->code, hdr->ident, len); + + if (!opcode_data || !opcode_data->func) { + packet_hexdump(data, len); + data += len; + size -= len; + return; + } + + if (opcode_data->fixed) { + if (len != opcode_data->size) { + print_text(COLOR_ERROR, "invalid size"); + packet_hexdump(data, len); + data += len; + size -= len; + continue; + } + } else { + if (len < opcode_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data, size); + data += len; + size -= len; + continue; + } + } + + l2cap_frame_init(&frame, index, in, handle, hdr->ident, cid, 0, + data, len); + opcode_data->func(&frame); + + data += len; + size -= len; + } + + packet_hexdump(data, size); +} + +static void le_sig_packet(uint16_t index, bool in, uint16_t handle, + uint16_t cid, const void *data, uint16_t size) +{ + struct l2cap_frame frame; + const struct bt_l2cap_hdr_sig *hdr = data; + const struct sig_opcode_data *opcode_data = NULL; + const char *opcode_color, *opcode_str; + uint16_t len; + int i; + + if (size < 4) { + print_text(COLOR_ERROR, "malformed signal packet"); + packet_hexdump(data, size); + return; + } + + len = le16_to_cpu(hdr->len); + + data += 4; + size -= 4; + + if (size != len) { + print_text(COLOR_ERROR, "invalid signal packet size"); + packet_hexdump(data, size); + return; + } + + for (i = 0; le_sig_opcode_table[i].str; i++) { + if (le_sig_opcode_table[i].opcode == hdr->code) { + opcode_data = &le_sig_opcode_table[i]; + break; + } + } + + if (opcode_data) { + if (opcode_data->func) { + if (in) + opcode_color = COLOR_MAGENTA; + else + opcode_color = COLOR_BLUE; + } else + opcode_color = COLOR_WHITE_BG; + opcode_str = opcode_data->str; + } else { + opcode_color = COLOR_WHITE_BG; + opcode_str = "Unknown"; + } + + print_indent(6, opcode_color, "LE L2CAP: ", opcode_str, COLOR_OFF, + " (0x%2.2x) ident %d len %d", + hdr->code, hdr->ident, len); + + if (!opcode_data || !opcode_data->func) { + packet_hexdump(data, len); + return; + } + + if (opcode_data->fixed) { + if (len != opcode_data->size) { + print_text(COLOR_ERROR, "invalid size"); + packet_hexdump(data, len); + return; + } + } else { + if (len < opcode_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data, size); + return; + } + } + + l2cap_frame_init(&frame, index, in, handle, hdr->ident, cid, 0, + data, len); + opcode_data->func(&frame); +} + +static void connless_packet(uint16_t index, bool in, uint16_t handle, + uint16_t cid, const void *data, uint16_t size) +{ + struct l2cap_frame frame; + const struct bt_l2cap_hdr_connless *hdr = data; + uint16_t psm; + + if (size < 2) { + print_text(COLOR_ERROR, "malformed connectionless packet"); + packet_hexdump(data, size); + return; + } + + psm = le16_to_cpu(hdr->psm); + + data += 2; + size -= 2; + + print_indent(6, COLOR_CYAN, "L2CAP: Connectionless", "", COLOR_OFF, + " len %d [PSM %d]", size, psm); + + switch (psm) { + default: + packet_hexdump(data, size); + break; + } + + l2cap_frame_init(&frame, index, in, handle, 0, cid, 0, data, size); +} + +static void print_controller_list(const uint8_t *data, uint16_t size) +{ + while (size > 2) { + const char *str; + + print_field("Controller ID: %d", data[0]); + + switch (data[1]) { + case 0x00: + str = "Primary BR/EDR Controller"; + break; + case 0x01: + str = "802.11 AMP Controller"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" Type: %s (0x%2.2x)", str, data[1]); + + switch (data[2]) { + case 0x00: + str = "Present"; + break; + case 0x01: + str = "Bluetooth only"; + break; + case 0x02: + str = "No capacity"; + break; + case 0x03: + str = "Low capacity"; + break; + case 0x04: + str = "Medium capacity"; + break; + case 0x05: + str = "High capacity"; + break; + case 0x06: + str = "Full capacity"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" Status: %s (0x%2.2x)", str, data[2]); + + data += 3; + size -= 3; + } + + packet_hexdump(data, size); +} + +static void amp_cmd_reject(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_cmd_reject *pdu = frame->data; + + print_field("Reason: 0x%4.4x", le16_to_cpu(pdu->reason)); +} + +static void amp_discover_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_discover_req *pdu = frame->data; + + print_field("MTU/MPS size: %d", le16_to_cpu(pdu->size)); + print_field("Extended feature mask: 0x%4.4x", + le16_to_cpu(pdu->features)); +} + +static void amp_discover_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_discover_rsp *pdu = frame->data; + + print_field("MTU/MPS size: %d", le16_to_cpu(pdu->size)); + print_field("Extended feature mask: 0x%4.4x", + le16_to_cpu(pdu->features)); + + print_controller_list(frame->data + 4, frame->size - 4); +} + +static void amp_change_notify(const struct l2cap_frame *frame) +{ + print_controller_list(frame->data, frame->size); +} + +static void amp_change_response(const struct l2cap_frame *frame) +{ +} + +static void amp_get_info_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_get_info_req *pdu = frame->data; + + print_field("Controller ID: %d", pdu->ctrlid); +} + +static void amp_get_info_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_get_info_rsp *pdu = frame->data; + const char *str; + + print_field("Controller ID: %d", pdu->ctrlid); + + switch (pdu->status) { + case 0x00: + str = "Success"; + break; + case 0x01: + str = "Invalid Controller ID"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Status: %s (0x%2.2x)", str, pdu->status); + + print_field("Total bandwidth: %d kbps", le32_to_cpu(pdu->total_bw)); + print_field("Max guaranteed bandwidth: %d kbps", + le32_to_cpu(pdu->max_bw)); + print_field("Min latency: %d", le32_to_cpu(pdu->min_latency)); + + print_field("PAL capabilities: 0x%4.4x", le16_to_cpu(pdu->pal_cap)); + print_field("Max ASSOC length: %d", le16_to_cpu(pdu->max_assoc_len)); +} + +static void amp_get_assoc_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_get_assoc_req *pdu = frame->data; + + print_field("Controller ID: %d", pdu->ctrlid); +} + +static void amp_get_assoc_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_get_assoc_rsp *pdu = frame->data; + const char *str; + + print_field("Controller ID: %d", pdu->ctrlid); + + switch (pdu->status) { + case 0x00: + str = "Success"; + break; + case 0x01: + str = "Invalid Controller ID"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Status: %s (0x%2.2x)", str, pdu->status); + + packet_hexdump(frame->data + 2, frame->size - 2); +} + +static void amp_create_phy_link_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_create_phy_link_req *pdu = frame->data; + + print_field("Local controller ID: %d", pdu->local_ctrlid); + print_field("Remote controller ID: %d", pdu->remote_ctrlid); + + packet_hexdump(frame->data + 2, frame->size - 2); +} + +static void amp_create_phy_link_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_create_phy_link_rsp *pdu = frame->data; + const char *str; + + print_field("Local controller ID: %d", pdu->local_ctrlid); + print_field("Remote controller ID: %d", pdu->remote_ctrlid); + + switch (pdu->status) { + case 0x00: + str = "Success"; + break; + case 0x01: + str = "Invalid Controller ID"; + break; + case 0x02: + str = "Failed - Unable to start link creation"; + break; + case 0x03: + str = "Failed - Collision occurred"; + break; + case 0x04: + str = "Failed - Disconnected link packet received"; + break; + case 0x05: + str = "Failed - Link already exists"; + break; + case 0x06: + str = "Failed - Security violation"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Status: %s (0x%2.2x)", str, pdu->status); +} + +static void amp_disconn_phy_link_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_disconn_phy_link_req *pdu = frame->data; + + print_field("Local controller ID: %d", pdu->local_ctrlid); + print_field("Remote controller ID: %d", pdu->remote_ctrlid); +} + +static void amp_disconn_phy_link_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_amp_disconn_phy_link_rsp *pdu = frame->data; + const char *str; + + print_field("Local controller ID: %d", pdu->local_ctrlid); + print_field("Remote controller ID: %d", pdu->remote_ctrlid); + + switch (pdu->status) { + case 0x00: + str = "Success"; + break; + case 0x01: + str = "Invalid Controller ID"; + break; + case 0x02: + str = "Failed - No link exists"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Status: %s (0x%2.2x)", str, pdu->status); +} + +struct amp_opcode_data { + uint8_t opcode; + const char *str; + void (*func) (const struct l2cap_frame *frame); + uint16_t size; + bool fixed; +}; + +static const struct amp_opcode_data amp_opcode_table[] = { + { 0x01, "Command Reject", + amp_cmd_reject, 2, false }, + { 0x02, "Discover Request", + amp_discover_req, 4, true }, + { 0x03, "Discover Response", + amp_discover_rsp, 7, false }, + { 0x04, "Change Notify", + amp_change_notify, 3, false }, + { 0x05, "Change Response", + amp_change_response, 0, true }, + { 0x06, "Get Info Request", + amp_get_info_req, 1, true }, + { 0x07, "Get Info Response", + amp_get_info_rsp, 18, true }, + { 0x08, "Get Assoc Request", + amp_get_assoc_req, 1, true }, + { 0x09, "Get Assoc Response", + amp_get_assoc_rsp, 2, false }, + { 0x0a, "Create Physical Link Request", + amp_create_phy_link_req, 2, false }, + { 0x0b, "Create Physical Link Response", + amp_create_phy_link_rsp, 3, true }, + { 0x0c, "Disconnect Physical Link Request", + amp_disconn_phy_link_req, 2, true }, + { 0x0d, "Disconnect Physical Link Response", + amp_disconn_phy_link_rsp, 3, true }, + { }, +}; + +static void amp_packet(uint16_t index, bool in, uint16_t handle, + uint16_t cid, const void *data, uint16_t size) +{ + struct l2cap_frame frame; + uint16_t control, fcs, len; + uint8_t opcode, ident; + const struct amp_opcode_data *opcode_data = NULL; + const char *opcode_color, *opcode_str; + int i; + + if (size < 4) { + print_text(COLOR_ERROR, "malformed info frame packet"); + packet_hexdump(data, size); + return; + } + + control = get_le16(data); + fcs = get_le16(data + size - 2); + + print_indent(6, COLOR_CYAN, "Channel:", "", COLOR_OFF, + " %d dlen %d control 0x%4.4x fcs 0x%4.4x", + 3, size, control, fcs); + + if (control & 0x01) + return; + + if (size < 8) { + print_text(COLOR_ERROR, "malformed manager packet"); + packet_hexdump(data, size); + return; + } + + opcode = *((const uint8_t *) (data + 2)); + ident = *((const uint8_t *) (data + 3)); + len = get_le16(data + 4); + + if (len != size - 8) { + print_text(COLOR_ERROR, "invalid manager packet size"); + packet_hexdump(data + 2, size - 4); + return; + } + + for (i = 0; amp_opcode_table[i].str; i++) { + if (amp_opcode_table[i].opcode == opcode) { + opcode_data = &_opcode_table[i]; + break; + } + } + + if (opcode_data) { + if (opcode_data->func) { + if (in) + opcode_color = COLOR_MAGENTA; + else + opcode_color = COLOR_BLUE; + } else + opcode_color = COLOR_WHITE_BG; + opcode_str = opcode_data->str; + } else { + opcode_color = COLOR_WHITE_BG; + opcode_str = "Unknown"; + } + + print_indent(6, opcode_color, "AMP: ", opcode_str, COLOR_OFF, + " (0x%2.2x) ident %d len %d", opcode, ident, len); + + if (!opcode_data || !opcode_data->func) { + packet_hexdump(data + 6, size - 8); + return; + } + + if (opcode_data->fixed) { + if (len != opcode_data->size) { + print_text(COLOR_ERROR, "invalid size"); + packet_hexdump(data + 6, size - 8); + return; + } + } else { + if (len < opcode_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data + 6, size - 8); + return; + } + } + + l2cap_frame_init(&frame, index, in, handle, 0, cid, 0, data + 6, len); + opcode_data->func(&frame); +} + +static void print_hex_field(const char *label, const uint8_t *data, + uint8_t len) +{ + char str[len * 2 + 1]; + uint8_t i; + + str[0] = '\0'; + + for (i = 0; i < len; i++) + sprintf(str + (i * 2), "%2.2x", data[i]); + + print_field("%s: %s", label, str); +} + +static void print_uuid(const char *label, const void *data, uint16_t size) +{ + const char *str; + char uuidstr[MAX_LEN_UUID_STR]; + + switch (size) { + case 2: + str = bt_uuid16_to_str(get_le16(data)); + print_field("%s: %s (0x%4.4x)", label, str, get_le16(data)); + break; + case 4: + str = bt_uuid32_to_str(get_le32(data)); + print_field("%s: %s (0x%8.8x)", label, str, get_le32(data)); + break; + case 16: + sprintf(uuidstr, "%8.8x-%4.4x-%4.4x-%4.4x-%8.8x%4.4x", + get_le32(data + 12), get_le16(data + 10), + get_le16(data + 8), get_le16(data + 6), + get_le32(data + 2), get_le16(data + 0)); + str = bt_uuidstr_to_str(uuidstr); + print_field("%s: %s (%s)", label, str, uuidstr); + break; + default: + packet_hexdump(data, size); + break; + } +} + +static void print_handle_range(const char *label, const void *data) +{ + print_field("%s: 0x%4.4x-0x%4.4x", label, + get_le16(data), get_le16(data + 2)); +} + +static void print_data_list(const char *label, uint8_t length, + const void *data, uint16_t size) +{ + uint8_t count; + + if (length == 0) + return; + + count = size / length; + + print_field("%s: %u entr%s", label, count, count == 1 ? "y" : "ies"); + + while (size >= length) { + print_field("Handle: 0x%4.4x", get_le16(data)); + print_hex_field("Value", data + 2, length - 2); + + data += length; + size -= length; + } + + packet_hexdump(data, size); +} + +static void print_attribute_info(uint16_t type, const void *data, uint16_t len) +{ + const char *str = bt_uuid16_to_str(type); + + print_field("%s: %s (0x%4.4x)", "Attribute type", str, type); + + switch (type) { + case 0x2800: /* Primary Service */ + case 0x2801: /* Secondary Service */ + print_uuid(" UUID", data, len); + break; + case 0x2802: /* Include */ + if (len < 4) { + print_hex_field(" Value", data, len); + break; + } + print_handle_range(" Handle range", data); + print_uuid(" UUID", data + 4, len - 4); + break; + case 0x2803: /* Characteristic */ + if (len < 3) { + print_hex_field(" Value", data, len); + break; + } + print_field(" Properties: 0x%2.2x", *((uint8_t *) data)); + print_field(" Handle: 0x%2.2x", get_le16(data + 1)); + print_uuid(" UUID", data + 3, len - 3); + break; + default: + print_hex_field("Value", data, len); + break; + } +} + +static const char *att_opcode_to_str(uint8_t opcode); + +static void att_error_response(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_error_response *pdu = frame->data; + const char *str; + + switch (pdu->error) { + case 0x01: + str = "Invalid Handle"; + break; + case 0x02: + str = "Read Not Permitted"; + break; + case 0x03: + str = "Write Not Permitted"; + break; + case 0x04: + str = "Invalid PDU"; + break; + case 0x05: + str = "Insufficient Authentication"; + break; + case 0x06: + str = "Request Not Supported"; + break; + case 0x07: + str = "Invalid Offset"; + break; + case 0x08: + str = "Insufficient Authorization"; + break; + case 0x09: + str = "Prepare Queue Full"; + break; + case 0x0a: + str = "Attribute Not Found"; + break; + case 0x0b: + str = "Attribute Not Long"; + break; + case 0x0c: + str = "Insufficient Encryption Key Size"; + break; + case 0x0d: + str = "Invalid Attribute Value Length"; + break; + case 0x0e: + str = "Unlikely Error"; + break; + case 0x0f: + str = "Insufficient Encryption"; + break; + case 0x10: + str = "Unsupported Group Type"; + break; + case 0x11: + str = "Insufficient Resources"; + break; + case 0x12: + str = "Database Out of Sync"; + break; + case 0x13: + str = "Value Not Allowed"; + break; + case 0xfd: + str = "CCC Improperly Configured"; + break; + case 0xfe: + str = "Procedure Already in Progress"; + break; + case 0xff: + str = "Out of Range"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s (0x%2.2x)", att_opcode_to_str(pdu->request), + pdu->request); + print_field("Handle: 0x%4.4x", le16_to_cpu(pdu->handle)); + print_field("Error: %s (0x%2.2x)", str, pdu->error); +} + +static void att_exchange_mtu_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_exchange_mtu_req *pdu = frame->data; + + print_field("Client RX MTU: %d", le16_to_cpu(pdu->mtu)); +} + +static void att_exchange_mtu_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_exchange_mtu_rsp *pdu = frame->data; + + print_field("Server RX MTU: %d", le16_to_cpu(pdu->mtu)); +} + +static void att_find_info_req(const struct l2cap_frame *frame) +{ + print_handle_range("Handle range", frame->data); +} + +static const char *att_format_str(uint8_t format) +{ + switch (format) { + case 0x01: + return "UUID-16"; + case 0x02: + return "UUID-128"; + default: + return "unknown"; + } +} + +static uint16_t print_info_data_16(const void *data, uint16_t len) +{ + while (len >= 4) { + print_field("Handle: 0x%4.4x", get_le16(data)); + print_uuid("UUID", data + 2, 2); + data += 4; + len -= 4; + } + + return len; +} + +static uint16_t print_info_data_128(const void *data, uint16_t len) +{ + while (len >= 18) { + print_field("Handle: 0x%4.4x", get_le16(data)); + print_uuid("UUID", data + 2, 16); + data += 18; + len -= 18; + } + + return len; +} + +static void att_find_info_rsp(const struct l2cap_frame *frame) +{ + const uint8_t *format = frame->data; + uint16_t len; + + print_field("Format: %s (0x%2.2x)", att_format_str(*format), *format); + + if (*format == 0x01) + len = print_info_data_16(frame->data + 1, frame->size - 1); + else if (*format == 0x02) + len = print_info_data_128(frame->data + 1, frame->size - 1); + else + len = frame->size - 1; + + packet_hexdump(frame->data + (frame->size - len), len); +} + +static void att_find_by_type_val_req(const struct l2cap_frame *frame) +{ + uint16_t type; + + print_handle_range("Handle range", frame->data); + + type = get_le16(frame->data + 4); + print_attribute_info(type, frame->data + 6, frame->size - 6); +} + +static void att_find_by_type_val_rsp(const struct l2cap_frame *frame) +{ + const uint8_t *ptr = frame->data; + uint16_t len = frame->size; + + while (len >= 4) { + print_handle_range("Handle range", ptr); + ptr += 4; + len -= 4; + } + + packet_hexdump(ptr, len); +} + +static void att_read_type_req(const struct l2cap_frame *frame) +{ + print_handle_range("Handle range", frame->data); + print_uuid("Attribute type", frame->data + 4, frame->size - 4); +} + +static void att_read_type_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_read_group_type_rsp *pdu = frame->data; + + print_field("Attribute data length: %d", pdu->length); + print_data_list("Attribute data list", pdu->length, + frame->data + 1, frame->size - 1); +} + +static void att_read_req(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_read_req *pdu = frame->data; + + print_field("Handle: 0x%4.4x", le16_to_cpu(pdu->handle)); +} + +static void att_read_rsp(const struct l2cap_frame *frame) +{ + print_hex_field("Value", frame->data, frame->size); +} + +static void att_read_blob_req(const struct l2cap_frame *frame) +{ + print_field("Handle: 0x%4.4x", get_le16(frame->data)); + print_field("Offset: 0x%4.4x", get_le16(frame->data + 2)); +} + +static void att_read_blob_rsp(const struct l2cap_frame *frame) +{ + packet_hexdump(frame->data, frame->size); +} + +static void att_read_multiple_req(const struct l2cap_frame *frame) +{ + int i, count; + + count = frame->size / 2; + + for (i = 0; i < count; i++) + print_field("Handle: 0x%4.4x", + get_le16(frame->data + (i * 2))); +} + +static void att_read_group_type_req(const struct l2cap_frame *frame) +{ + print_handle_range("Handle range", frame->data); + print_uuid("Attribute group type", frame->data + 4, frame->size - 4); +} + +static void print_group_list(const char *label, uint8_t length, + const void *data, uint16_t size) +{ + uint8_t count; + + if (length == 0) + return; + + count = size / length; + + print_field("%s: %u entr%s", label, count, count == 1 ? "y" : "ies"); + + while (size >= length) { + print_handle_range("Handle range", data); + print_uuid("UUID", data + 4, length - 4); + + data += length; + size -= length; + } + + packet_hexdump(data, size); +} + +static void att_read_group_type_rsp(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_read_group_type_rsp *pdu = frame->data; + + print_field("Attribute data length: %d", pdu->length); + print_group_list("Attribute group list", pdu->length, + frame->data + 1, frame->size - 1); +} + +static void att_write_req(const struct l2cap_frame *frame) +{ + print_field("Handle: 0x%4.4x", get_le16(frame->data)); + print_hex_field(" Data", frame->data + 2, frame->size - 2); +} + +static void att_write_rsp(const struct l2cap_frame *frame) +{ +} + +static void att_prepare_write_req(const struct l2cap_frame *frame) +{ + print_field("Handle: 0x%4.4x", get_le16(frame->data)); + print_field("Offset: 0x%4.4x", get_le16(frame->data + 2)); + print_hex_field(" Data", frame->data + 4, frame->size - 4); +} + +static void att_prepare_write_rsp(const struct l2cap_frame *frame) +{ + print_field("Handle: 0x%4.4x", get_le16(frame->data)); + print_field("Offset: 0x%4.4x", get_le16(frame->data + 2)); + print_hex_field(" Data", frame->data + 4, frame->size - 4); +} + +static void att_execute_write_req(const struct l2cap_frame *frame) +{ + uint8_t flags = *(uint8_t *) frame->data; + const char *flags_str; + + switch (flags) { + case 0x00: + flags_str = "Cancel all prepared writes"; + break; + case 0x01: + flags_str = "Immediately write all pending values"; + break; + default: + flags_str = "Unknown"; + break; + } + + print_field("Flags: %s (0x%02x)", flags_str, flags); +} + +static void att_handle_value_notify(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_handle_value_notify *pdu = frame->data; + + print_field("Handle: 0x%4.4x", le16_to_cpu(pdu->handle)); + print_hex_field(" Data", frame->data + 2, frame->size - 2); +} + +static void att_handle_value_ind(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_att_handle_value_ind *pdu = frame->data; + + print_field("Handle: 0x%4.4x", le16_to_cpu(pdu->handle)); + print_hex_field(" Data", frame->data + 2, frame->size - 2); +} + +static void att_handle_value_conf(const struct l2cap_frame *frame) +{ +} + +static void att_write_command(const struct l2cap_frame *frame) +{ + print_field("Handle: 0x%4.4x", get_le16(frame->data)); + print_hex_field(" Data", frame->data + 2, frame->size - 2); +} + +static void att_signed_write_command(const struct l2cap_frame *frame) +{ + print_field("Handle: 0x%4.4x", get_le16(frame->data)); + print_hex_field(" Data", frame->data + 2, frame->size - 2 - 12); + print_hex_field(" Signature", frame->data + frame->size - 12, 12); +} + +struct att_opcode_data { + uint8_t opcode; + const char *str; + void (*func) (const struct l2cap_frame *frame); + uint8_t size; + bool fixed; +}; + +static const struct att_opcode_data att_opcode_table[] = { + { 0x01, "Error Response", + att_error_response, 4, true }, + { 0x02, "Exchange MTU Request", + att_exchange_mtu_req, 2, true }, + { 0x03, "Exchange MTU Response", + att_exchange_mtu_rsp, 2, true }, + { 0x04, "Find Information Request", + att_find_info_req, 4, true }, + { 0x05, "Find Information Response", + att_find_info_rsp, 5, false }, + { 0x06, "Find By Type Value Request", + att_find_by_type_val_req, 6, false }, + { 0x07, "Find By Type Value Response", + att_find_by_type_val_rsp, 4, false }, + { 0x08, "Read By Type Request", + att_read_type_req, 6, false }, + { 0x09, "Read By Type Response", + att_read_type_rsp, 3, false }, + { 0x0a, "Read Request", + att_read_req, 2, true }, + { 0x0b, "Read Response", + att_read_rsp, 0, false }, + { 0x0c, "Read Blob Request", + att_read_blob_req, 4, true }, + { 0x0d, "Read Blob Response", + att_read_blob_rsp, 0, false }, + { 0x0e, "Read Multiple Request", + att_read_multiple_req, 4, false }, + { 0x0f, "Read Multiple Response" }, + { 0x10, "Read By Group Type Request", + att_read_group_type_req, 6, false }, + { 0x11, "Read By Group Type Response", + att_read_group_type_rsp, 4, false }, + { 0x12, "Write Request" , + att_write_req, 2, false }, + { 0x13, "Write Response", + att_write_rsp, 0, true }, + { 0x16, "Prepare Write Request", + att_prepare_write_req, 4, false }, + { 0x17, "Prepare Write Response", + att_prepare_write_rsp, 4, false }, + { 0x18, "Execute Write Request", + att_execute_write_req, 1, true }, + { 0x19, "Execute Write Response" }, + { 0x1b, "Handle Value Notification", + att_handle_value_notify, 2, false }, + { 0x1d, "Handle Value Indication", + att_handle_value_ind, 2, false }, + { 0x1e, "Handle Value Confirmation", + att_handle_value_conf, 0, true }, + { 0x52, "Write Command", + att_write_command, 2, false }, + { 0xd2, "Signed Write Command", att_signed_write_command, 14, false }, + { } +}; + +static const char *att_opcode_to_str(uint8_t opcode) +{ + int i; + + for (i = 0; att_opcode_table[i].str; i++) { + if (att_opcode_table[i].opcode == opcode) + return att_opcode_table[i].str; + } + + return "Unknown"; +} + +static void att_packet(uint16_t index, bool in, uint16_t handle, + uint16_t cid, const void *data, uint16_t size) +{ + struct l2cap_frame frame; + uint8_t opcode = *((const uint8_t *) data); + const struct att_opcode_data *opcode_data = NULL; + const char *opcode_color, *opcode_str; + int i; + + if (size < 1) { + print_text(COLOR_ERROR, "malformed attribute packet"); + packet_hexdump(data, size); + return; + } + + for (i = 0; att_opcode_table[i].str; i++) { + if (att_opcode_table[i].opcode == opcode) { + opcode_data = &att_opcode_table[i]; + break; + } + } + + if (opcode_data) { + if (opcode_data->func) { + if (in) + opcode_color = COLOR_MAGENTA; + else + opcode_color = COLOR_BLUE; + } else + opcode_color = COLOR_WHITE_BG; + opcode_str = opcode_data->str; + } else { + opcode_color = COLOR_WHITE_BG; + opcode_str = "Unknown"; + } + + print_indent(6, opcode_color, "ATT: ", opcode_str, COLOR_OFF, + " (0x%2.2x) len %d", opcode, size - 1); + + if (!opcode_data || !opcode_data->func) { + packet_hexdump(data + 1, size - 1); + return; + } + + if (opcode_data->fixed) { + if (size - 1 != opcode_data->size) { + print_text(COLOR_ERROR, "invalid size"); + packet_hexdump(data + 1, size - 1); + return; + } + } else { + if (size - 1 < opcode_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data + 1, size - 1); + return; + } + } + + l2cap_frame_init(&frame, index, in, handle, 0, cid, 0, + data + 1, size - 1); + opcode_data->func(&frame); +} + +static void print_addr(const uint8_t *addr, uint8_t addr_type) +{ + const char *str; + + switch (addr_type) { + case 0x00: + print_field("Address: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + addr[5], addr[4], addr[3], + addr[2], addr[1], addr[0]); + break; + case 0x01: + switch ((addr[5] & 0xc0) >> 6) { + case 0x00: + str = "Non-Resolvable"; + break; + case 0x01: + str = "Resolvable"; + break; + case 0x03: + str = "Static"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Address: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X" + " (%s)", addr[5], addr[4], addr[3], + addr[2], addr[1], addr[0], str); + break; + default: + print_field("Address: %2.2X-%2.2X-%2.2X-%2.2X-%2.2X-%2.2X", + addr[5], addr[4], addr[3], + addr[2], addr[1], addr[0]); + break; + } +} + +static void print_addr_type(uint8_t addr_type) +{ + const char *str; + + switch (addr_type) { + case 0x00: + str = "Public"; + break; + case 0x01: + str = "Random"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Address type: %s (0x%2.2x)", str, addr_type); +} + +static void print_smp_io_capa(uint8_t io_capa) +{ + const char *str; + + switch (io_capa) { + case 0x00: + str = "DisplayOnly"; + break; + case 0x01: + str = "DisplayYesNo"; + break; + case 0x02: + str = "KeyboardOnly"; + break; + case 0x03: + str = "NoInputNoOutput"; + break; + case 0x04: + str = "KeyboardDisplay"; + break; + default: + str = "Reserved"; + break; + } + + print_field("IO capability: %s (0x%2.2x)", str, io_capa); +} + +static void print_smp_oob_data(uint8_t oob_data) +{ + const char *str; + + switch (oob_data) { + case 0x00: + str = "Authentication data not present"; + break; + case 0x01: + str = "Authentication data from remote device present"; + break; + default: + str = "Reserved"; + break; + } + + print_field("OOB data: %s (0x%2.2x)", str, oob_data); +} + +static void print_smp_auth_req(uint8_t auth_req) +{ + const char *bond, *mitm, *sc, *kp, *ct2; + + switch (auth_req & 0x03) { + case 0x00: + bond = "No bonding"; + break; + case 0x01: + bond = "Bonding"; + break; + default: + bond = "Reserved"; + break; + } + + if (auth_req & 0x04) + mitm = "MITM"; + else + mitm = "No MITM"; + + if (auth_req & 0x08) + sc = "SC"; + else + sc = "Legacy"; + + if (auth_req & 0x10) + kp = "Keypresses"; + else + kp = "No Keypresses"; + + if (auth_req & 0x20) + ct2 = ", CT2"; + else + ct2 = ""; + + print_field("Authentication requirement: %s, %s, %s, %s%s (0x%2.2x)", + bond, mitm, sc, kp, ct2, auth_req); +} + +static void print_smp_key_dist(const char *label, uint8_t dist) +{ + char str[27]; + + if (!(dist & 0x07)) { + strcpy(str, " "); + } else { + str[0] = '\0'; + if (dist & 0x01) + strcat(str, "EncKey "); + if (dist & 0x02) + strcat(str, "IdKey "); + if (dist & 0x04) + strcat(str, "Sign "); + if (dist & 0x08) + strcat(str, "LinkKey "); + } + + print_field("%s: %s(0x%2.2x)", label, str, dist); +} + +static void smp_pairing_request(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_pairing_request *pdu = frame->data; + + print_smp_io_capa(pdu->io_capa); + print_smp_oob_data(pdu->oob_data); + print_smp_auth_req(pdu->auth_req); + + print_field("Max encryption key size: %d", pdu->max_key_size); + print_smp_key_dist("Initiator key distribution", pdu->init_key_dist); + print_smp_key_dist("Responder key distribution", pdu->resp_key_dist); +} + +static void smp_pairing_response(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_pairing_response *pdu = frame->data; + + print_smp_io_capa(pdu->io_capa); + print_smp_oob_data(pdu->oob_data); + print_smp_auth_req(pdu->auth_req); + + print_field("Max encryption key size: %d", pdu->max_key_size); + print_smp_key_dist("Initiator key distribution", pdu->init_key_dist); + print_smp_key_dist("Responder key distribution", pdu->resp_key_dist); +} + +static void smp_pairing_confirm(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_pairing_confirm *pdu = frame->data; + + print_hex_field("Confim value", pdu->value, 16); +} + +static void smp_pairing_random(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_pairing_random *pdu = frame->data; + + print_hex_field("Random value", pdu->value, 16); +} + +static void smp_pairing_failed(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_pairing_failed *pdu = frame->data; + const char *str; + + switch (pdu->reason) { + case 0x01: + str = "Passkey entry failed"; + break; + case 0x02: + str = "OOB not available"; + break; + case 0x03: + str = "Authentication requirements"; + break; + case 0x04: + str = "Confirm value failed"; + break; + case 0x05: + str = "Pairing not supported"; + break; + case 0x06: + str = "Encryption key size"; + break; + case 0x07: + str = "Command not supported"; + break; + case 0x08: + str = "Unspecified reason"; + break; + case 0x09: + str = "Repeated attempts"; + break; + case 0x0a: + str = "Invalid parameters"; + break; + case 0x0b: + str = "DHKey check failed"; + break; + case 0x0c: + str = "Numeric comparison failed"; + break; + case 0x0d: + str = "BR/EDR pairing in progress"; + break; + case 0x0e: + str = "Cross-transport Key Derivation/Generation not allowed"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Reason: %s (0x%2.2x)", str, pdu->reason); +} + +static void smp_encrypt_info(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_encrypt_info *pdu = frame->data; + + print_hex_field("Long term key", pdu->ltk, 16); +} + +static void smp_master_ident(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_master_ident *pdu = frame->data; + + print_field("EDIV: 0x%4.4x", le16_to_cpu(pdu->ediv)); + print_field("Rand: 0x%16.16" PRIx64, le64_to_cpu(pdu->rand)); +} + +static void smp_ident_info(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_ident_info *pdu = frame->data; + + print_hex_field("Identity resolving key", pdu->irk, 16); + + keys_update_identity_key(pdu->irk); +} + +static void smp_ident_addr_info(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_ident_addr_info *pdu = frame->data; + + print_addr_type(pdu->addr_type); + print_addr(pdu->addr, pdu->addr_type); + + keys_update_identity_addr(pdu->addr, pdu->addr_type); +} + +static void smp_signing_info(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_signing_info *pdu = frame->data; + + print_hex_field("Signature key", pdu->csrk, 16); +} + +static void smp_security_request(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_security_request *pdu = frame->data; + + print_smp_auth_req(pdu->auth_req); +} + +static void smp_pairing_public_key(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_public_key *pdu = frame->data; + + print_hex_field("X", pdu->x, 32); + print_hex_field("Y", pdu->y, 32); +} + +static void smp_pairing_dhkey_check(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_dhkey_check *pdu = frame->data; + + print_hex_field("E", pdu->e, 16); +} + +static void smp_pairing_keypress_notification(const struct l2cap_frame *frame) +{ + const struct bt_l2cap_smp_keypress_notify *pdu = frame->data; + const char *str; + + switch (pdu->type) { + case 0x00: + str = "Passkey entry started"; + break; + case 0x01: + str = "Passkey digit entered"; + break; + case 0x02: + str = "Passkey digit erased"; + break; + case 0x03: + str = "Passkey cleared"; + break; + case 0x04: + str = "Passkey entry completed"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, pdu->type); +} + +struct smp_opcode_data { + uint8_t opcode; + const char *str; + void (*func) (const struct l2cap_frame *frame); + uint8_t size; + bool fixed; +}; + +static const struct smp_opcode_data smp_opcode_table[] = { + { 0x01, "Pairing Request", + smp_pairing_request, 6, true }, + { 0x02, "Pairing Response", + smp_pairing_response, 6, true }, + { 0x03, "Pairing Confirm", + smp_pairing_confirm, 16, true }, + { 0x04, "Pairing Random", + smp_pairing_random, 16, true }, + { 0x05, "Pairing Failed", + smp_pairing_failed, 1, true }, + { 0x06, "Encryption Information", + smp_encrypt_info, 16, true }, + { 0x07, "Master Identification", + smp_master_ident, 10, true }, + { 0x08, "Identity Information", + smp_ident_info, 16, true }, + { 0x09, "Identity Address Information", + smp_ident_addr_info, 7, true }, + { 0x0a, "Signing Information", + smp_signing_info, 16, true }, + { 0x0b, "Security Request", + smp_security_request, 1, true }, + { 0x0c, "Pairing Public Key", + smp_pairing_public_key, 64, true }, + { 0x0d, "Pairing DHKey Check", + smp_pairing_dhkey_check, 16, true }, + { 0x0e, "Pairing Keypress Notification", + smp_pairing_keypress_notification, 1, true }, + { } +}; + +static void smp_packet(uint16_t index, bool in, uint16_t handle, + uint16_t cid, const void *data, uint16_t size) +{ + struct l2cap_frame frame; + uint8_t opcode = *((const uint8_t *) data); + const struct smp_opcode_data *opcode_data = NULL; + const char *opcode_color, *opcode_str; + int i; + + if (size < 1) { + print_text(COLOR_ERROR, "malformed attribute packet"); + packet_hexdump(data, size); + return; + } + + for (i = 0; smp_opcode_table[i].str; i++) { + if (smp_opcode_table[i].opcode == opcode) { + opcode_data = &smp_opcode_table[i]; + break; + } + } + + if (opcode_data) { + if (opcode_data->func) { + if (in) + opcode_color = COLOR_MAGENTA; + else + opcode_color = COLOR_BLUE; + } else + opcode_color = COLOR_WHITE_BG; + opcode_str = opcode_data->str; + } else { + opcode_color = COLOR_WHITE_BG; + opcode_str = "Unknown"; + } + + print_indent(6, opcode_color, cid == 0x0006 ? "SMP: " : "BR/EDR SMP: ", + opcode_str, COLOR_OFF, " (0x%2.2x) len %d", + opcode, size - 1); + + if (!opcode_data || !opcode_data->func) { + packet_hexdump(data + 1, size - 1); + return; + } + + if (opcode_data->fixed) { + if (size - 1 != opcode_data->size) { + print_text(COLOR_ERROR, "invalid size"); + packet_hexdump(data + 1, size - 1); + return; + } + } else { + if (size - 1 < opcode_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data + 1, size - 1); + return; + } + } + + l2cap_frame_init(&frame, index, in, handle, 0, cid, 0, + data + 1, size - 1); + opcode_data->func(&frame); +} + +void l2cap_frame(uint16_t index, bool in, uint16_t handle, uint16_t cid, + uint16_t psm, const void *data, uint16_t size) +{ + struct l2cap_frame frame; + struct chan_data *chan; + uint32_t ctrl32 = 0; + uint16_t ctrl16 = 0; + uint8_t ext_ctrl; + + switch (cid) { + case 0x0001: + bredr_sig_packet(index, in, handle, cid, data, size); + break; + case 0x0002: + connless_packet(index, in, handle, cid, data, size); + break; + case 0x0003: + amp_packet(index, in, handle, cid, data, size); + break; + case 0x0004: + att_packet(index, in, handle, cid, data, size); + break; + case 0x0005: + le_sig_packet(index, in, handle, cid, data, size); + break; + case 0x0006: + case 0x0007: + smp_packet(index, in, handle, cid, data, size); + break; + default: + l2cap_frame_init(&frame, index, in, handle, 0, cid, psm, + data, size); + + switch (frame.mode) { + case L2CAP_MODE_LE_FLOWCTL: + chan = get_chan(&frame); + if (!chan->sdu) { + if (!l2cap_frame_get_le16(&frame, &chan->sdu)) + return; + } + print_indent(6, COLOR_CYAN, "Channel:", "", + COLOR_OFF, " %d len %d sdu %d" + " [PSM %d mode %s (0x%02x)] {chan %d}", + cid, size, chan->sdu, frame.psm, + mode2str(frame.mode), frame.mode, + frame.chan); + chan->sdu -= frame.size; + break; + case L2CAP_MODE_BASIC: + print_indent(6, COLOR_CYAN, "Channel:", "", COLOR_OFF, + " %d len %d [PSM %d mode %s (0x%02x)] " + "{chan %d}", cid, size, frame.psm, + mode2str(frame.mode), frame.mode, + frame.chan); + break; + default: + ext_ctrl = get_ext_ctrl(&frame); + + if (ext_ctrl) { + if (!l2cap_frame_get_le32(&frame, &ctrl32)) + return; + + print_indent(6, COLOR_CYAN, "Channel:", "", + COLOR_OFF, " %d len %d" + " ext_ctrl 0x%8.8x" + " [PSM %d mode %s (0x%02x)] " + "{chan %d}", cid, size, ctrl32, + frame.psm, mode2str(frame.mode), + frame.mode, frame.chan); + + l2cap_ctrl_ext_parse(&frame, ctrl32); + } else { + if (!l2cap_frame_get_le16(&frame, &ctrl16)) + return; + + print_indent(6, COLOR_CYAN, "Channel:", "", + COLOR_OFF, " %d len %d" + " ctrl 0x%4.4x" + " [PSM %d mode %s (0x%02x)] " + "{chan %d}", cid, size, ctrl16, + frame.psm, mode2str(frame.mode), + frame.mode, frame.chan); + + l2cap_ctrl_parse(&frame, ctrl16); + } + + printf("\n"); + break; + } + + switch (frame.psm) { + case 0x0001: + sdp_packet(&frame); + break; + case 0x0003: + rfcomm_packet(&frame); + break; + case 0x000f: + bnep_packet(&frame); + break; + case 0x001f: + att_packet(index, in, handle, cid, data, size); + break; + case 0x0017: + case 0x001B: + avctp_packet(&frame); + break; + case 0x0019: + avdtp_packet(&frame); + break; + default: + packet_hexdump(data, size); + break; + } + break; + } +} + +void l2cap_packet(uint16_t index, bool in, uint16_t handle, uint8_t flags, + const void *data, uint16_t size) +{ + const struct bt_l2cap_hdr *hdr = data; + uint16_t len, cid; + + if (index > MAX_INDEX - 1) { + print_text(COLOR_ERROR, "controller index too large"); + packet_hexdump(data, size); + return; + } + + switch (flags) { + case 0x00: /* start of a non-automatically-flushable PDU */ + case 0x02: /* start of an automatically-flushable PDU */ + if (index_list[index][in].frag_len) { + print_text(COLOR_ERROR, "unexpected start frame"); + packet_hexdump(data, size); + clear_fragment_buffer(index, in); + return; + } + + if (size < sizeof(*hdr)) { + print_text(COLOR_ERROR, "frame too short"); + packet_hexdump(data, size); + return; + } + + len = le16_to_cpu(hdr->len); + cid = le16_to_cpu(hdr->cid); + + data += sizeof(*hdr); + size -= sizeof(*hdr); + + if (len == size) { + /* complete frame */ + l2cap_frame(index, in, handle, cid, 0, data, len); + return; + } + + if (size > len) { + print_text(COLOR_ERROR, "frame too long"); + packet_hexdump(data, size); + return; + } + + index_list[index][in].frag_buf = malloc(len); + if (!index_list[index][in].frag_buf) { + print_text(COLOR_ERROR, "failed buffer allocation"); + packet_hexdump(data, size); + return; + } + + memcpy(index_list[index][in].frag_buf, data, size); + index_list[index][in].frag_pos = size; + index_list[index][in].frag_len = len - size; + index_list[index][in].frag_cid = cid; + break; + + case 0x01: /* continuing fragment */ + if (!index_list[index][in].frag_len) { + print_text(COLOR_ERROR, "unexpected continuation"); + packet_hexdump(data, size); + return; + } + + if (size > index_list[index][in].frag_len) { + print_text(COLOR_ERROR, "fragment too long"); + packet_hexdump(data, size); + clear_fragment_buffer(index, in); + return; + } + + memcpy(index_list[index][in].frag_buf + + index_list[index][in].frag_pos, data, size); + index_list[index][in].frag_pos += size; + index_list[index][in].frag_len -= size; + + if (!index_list[index][in].frag_len) { + /* complete frame */ + l2cap_frame(index, in, handle, + index_list[index][in].frag_cid, 0, + index_list[index][in].frag_buf, + index_list[index][in].frag_pos); + clear_fragment_buffer(index, in); + return; + } + break; + + case 0x03: /* complete automatically-flushable PDU */ + if (index_list[index][in].frag_len) { + print_text(COLOR_ERROR, "unexpected complete frame"); + packet_hexdump(data, size); + clear_fragment_buffer(index, in); + return; + } + + if (size < sizeof(*hdr)) { + print_text(COLOR_ERROR, "frame too short"); + packet_hexdump(data, size); + return; + } + + len = le16_to_cpu(hdr->len); + cid = le16_to_cpu(hdr->cid); + + data += sizeof(*hdr); + size -= sizeof(*hdr); + + if (len != size) { + print_text(COLOR_ERROR, "wrong frame size"); + packet_hexdump(data, size); + return; + } + + /* complete frame */ + l2cap_frame(index, in, handle, cid, 0, data, len); + break; + + default: + print_text(COLOR_ERROR, "invalid packet flags (0x%2.2x)", + flags); + packet_hexdump(data, size); + return; + } +} diff --git a/monitor/l2cap.h b/monitor/l2cap.h new file mode 100644 index 0000000..07864ca --- /dev/null +++ b/monitor/l2cap.h @@ -0,0 +1,179 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +struct l2cap_frame { + uint16_t index; + bool in; + uint16_t handle; + uint8_t ident; + uint16_t cid; + uint16_t psm; + uint16_t chan; + uint8_t mode; + uint8_t seq_num; + const void *data; + uint16_t size; +}; + +static inline void l2cap_frame_pull(struct l2cap_frame *frame, + const struct l2cap_frame *source, uint16_t len) +{ + if (frame != source) { + frame->index = source->index; + frame->in = source->in; + frame->handle = source->handle; + frame->ident = source->ident; + frame->cid = source->cid; + frame->psm = source->psm; + frame->chan = source->chan; + frame->mode = source->mode; + } + + frame->data = source->data + len; + frame->size = source->size - len; +} + +static inline bool l2cap_frame_get_u8(struct l2cap_frame *frame, uint8_t *value) +{ + if (frame->size < sizeof(*value)) + return false; + + if (value) + *value = *((uint8_t *) frame->data); + + l2cap_frame_pull(frame, frame, sizeof(*value)); + + return true; +} + +static inline bool l2cap_frame_get_be16(struct l2cap_frame *frame, + uint16_t *value) +{ + if (frame->size < sizeof(*value)) + return false; + + if (value) + *value = get_be16(frame->data); + + l2cap_frame_pull(frame, frame, sizeof(*value)); + + return true; +} + +static inline bool l2cap_frame_get_le16(struct l2cap_frame *frame, + uint16_t *value) +{ + if (frame->size < sizeof(*value)) + return false; + + if (value) + *value = get_le16(frame->data); + + l2cap_frame_pull(frame, frame, sizeof(*value)); + + return true; +} + +static inline bool l2cap_frame_get_be32(struct l2cap_frame *frame, + uint32_t *value) +{ + if (frame->size < sizeof(*value)) + return false; + + if (value) + *value = get_be32(frame->data); + + l2cap_frame_pull(frame, frame, sizeof(*value)); + + return true; +} + +static inline bool l2cap_frame_get_le32(struct l2cap_frame *frame, + uint32_t *value) +{ + if (frame->size < sizeof(*value)) + return false; + + if (value) + *value = get_le32(frame->data); + + l2cap_frame_pull(frame, frame, sizeof(*value)); + + return true; +} + +static inline bool l2cap_frame_get_be64(struct l2cap_frame *frame, + uint64_t *value) +{ + if (frame->size < sizeof(*value)) + return false; + + if (value) + *value = get_be64(frame->data); + + l2cap_frame_pull(frame, frame, sizeof(*value)); + + return true; +} + +static inline bool l2cap_frame_get_le64(struct l2cap_frame *frame, + uint64_t *value) +{ + if (frame->size < sizeof(*value)) + return false; + + if (value) + *value = get_le64(frame->data); + + l2cap_frame_pull(frame, frame, sizeof(*value)); + + return true; +} + +static inline bool l2cap_frame_get_be128(struct l2cap_frame *frame, + uint64_t *lvalue, uint64_t *rvalue) +{ + if (frame->size < (sizeof(*lvalue) + sizeof(*rvalue))) + return false; + + if (lvalue && rvalue) { + *lvalue = get_be64(frame->data); + *rvalue = get_be64(frame->data); + } + + l2cap_frame_pull(frame, frame, (sizeof(*lvalue) + sizeof(*rvalue))); + + return true; +} + +void l2cap_frame(uint16_t index, bool in, uint16_t handle, uint16_t cid, + uint16_t psm, const void *data, uint16_t size); + +void l2cap_packet(uint16_t index, bool in, uint16_t handle, uint8_t flags, + const void *data, uint16_t size); + +void rfcomm_packet(const struct l2cap_frame *frame); diff --git a/monitor/ll.c b/monitor/ll.c new file mode 100644 index 0000000..5141f2b --- /dev/null +++ b/monitor/ll.c @@ -0,0 +1,707 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include + +#include "src/shared/util.h" +#include "display.h" +#include "packet.h" +#include "crc.h" +#include "bt.h" +#include "ll.h" + +#define COLOR_OPCODE COLOR_MAGENTA +#define COLOR_OPCODE_UNKNOWN COLOR_WHITE_BG +#define COLOR_UNKNOWN_OPTIONS_BIT COLOR_WHITE_BG + +#define MAX_CHANNEL 16 + +struct channel_data { + uint32_t access_addr; + uint32_t crc_init; +}; + +static struct channel_data channel_list[MAX_CHANNEL]; + +static void set_crc_init(uint32_t access_addr, uint32_t crc_init) +{ + int i; + + for (i = 0; i < MAX_CHANNEL; i++) { + if (channel_list[i].access_addr == 0x00000000 || + channel_list[i].access_addr == access_addr) { + channel_list[i].access_addr = access_addr; + channel_list[i].crc_init = crc_init; + break; + } + } +} + +static uint32_t get_crc_init(uint32_t access_addr) +{ + int i; + + for (i = 0; i < MAX_CHANNEL; i++) { + if (channel_list[i].access_addr == access_addr) + return channel_list[i].crc_init; + } + + return 0x00000000; +} + +static void advertising_packet(const void *data, uint8_t size) +{ + const uint8_t *ptr = data; + uint8_t pdu_type, length, win_size, hop, sca; + bool tx_add, rx_add; + uint32_t access_addr, crc_init; + uint16_t win_offset, interval, latency, timeout; + const char *str; + + if (size < 2) { + print_text(COLOR_ERROR, "packet too short"); + packet_hexdump(data, size); + return; + } + + pdu_type = ptr[0] & 0x0f; + tx_add = !!(ptr[0] & 0x40); + rx_add = !!(ptr[0] & 0x80); + length = ptr[1] & 0x3f; + + switch (pdu_type) { + case 0x00: + str = "ADV_IND"; + break; + case 0x01: + str = "ADV_DIRECT_IND"; + break; + case 0x02: + str = "ADV_NONCONN_IND"; + break; + case 0x03: + str = "SCAN_REQ"; + break; + case 0x04: + str = "SCAN_RSP"; + break; + case 0x05: + str = "CONNECT_REQ"; + break; + case 0x06: + str = "ADV_SCAN_IND"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, pdu_type); + print_field("TxAdd: %u", tx_add); + print_field("RxAdd: %u", rx_add); + print_field("Length: %u", length); + + if (length != size - 2) { + print_text(COLOR_ERROR, "packet size mismatch"); + packet_hexdump(data + 2, size - 2); + return; + } + + switch (pdu_type) { + case 0x00: /* ADV_IND */ + case 0x02: /* AVD_NONCONN_IND */ + case 0x06: /* ADV_SCAN_IND */ + case 0x04: /* SCAN_RSP */ + if (length < 6) { + print_text(COLOR_ERROR, "payload too short"); + packet_hexdump(data + 2, length); + return; + } + + packet_print_addr("Advertiser address", data + 2, tx_add); + packet_print_ad(data + 8, length - 6); + break; + + case 0x01: /* ADV_DIRECT_IND */ + if (length < 12) { + print_text(COLOR_ERROR, "payload too short"); + packet_hexdump(data + 2, length); + return; + } + + packet_print_addr("Advertiser address", data + 2, tx_add); + packet_print_addr("Inititator address", data + 8, rx_add); + break; + + case 0x03: /* SCAN_REQ */ + if (length < 12) { + print_text(COLOR_ERROR, "payload too short"); + packet_hexdump(data + 2, length); + return; + } + + packet_print_addr("Scanner address", data + 2, tx_add); + packet_print_addr("Advertiser address", data + 8, rx_add); + break; + + case 0x05: /* CONNECT_REQ */ + if (length < 34) { + print_text(COLOR_ERROR, "payload too short"); + packet_hexdump(data + 2, length); + return; + } + + packet_print_addr("Inititator address", data + 2, tx_add); + packet_print_addr("Advertiser address", data + 8, rx_add); + + access_addr = ptr[14] | ptr[15] << 8 | + ptr[16] << 16 | ptr[17] << 24; + crc_init = ptr[18] | ptr[19] << 8 | ptr[20] << 16; + + print_field("Access address: 0x%8.8x", access_addr); + print_field("CRC init: 0x%6.6x", crc_init); + + set_crc_init(access_addr, crc24_bit_reverse(crc_init)); + + win_size = ptr[21]; + win_offset = ptr[22] | ptr[23] << 8; + interval = ptr[24] | ptr[25] << 8; + latency = ptr[26] | ptr[27] << 8; + timeout = ptr[28] | ptr[29] << 8; + + print_field("Transmit window size: %u", win_size); + print_field("Transmit window offset: %u", win_offset); + print_field("Connection interval: %u", interval); + print_field("Connection slave latency: %u", latency); + print_field("Connection supervision timeout: %u", timeout); + + packet_print_channel_map_ll(ptr + 30); + + hop = ptr[35] & 0x1f; + sca = (ptr[35] & 0xe0) >> 5; + + switch (sca) { + case 0: + str = "251 ppm to 500 ppm"; + break; + case 1: + str = "151 ppm to 250 ppm"; + break; + case 2: + str = "101 ppm to 150ppm"; + break; + case 3: + str = "76 ppm to 100 ppm"; + break; + case 4: + str = "51 ppm to 75 ppm"; + break; + case 5: + str = "31 ppm to 50 ppm"; + break; + case 6: + str = "21 ppm to 30 ppm"; + break; + case 7: + str = "0 ppm to 20 ppm"; + break; + default: + str = "Invalid"; + break; + } + + print_field("Hop increment: %u", hop); + print_field("Sleep clock accuracy: %s (%u)", str, sca); + break; + + default: + packet_hexdump(data + 2, length); + break; + } +} + +static void data_packet(const void *data, uint8_t size, bool padded) +{ + const uint8_t *ptr = data; + uint8_t llid, length; + bool nesn, sn, md; + const char *str; + + if (size < 2) { + print_text(COLOR_ERROR, "packet too short"); + packet_hexdump(data, size); + return; + } + + llid = ptr[0] & 0x03; + nesn = !!(ptr[0] & 0x04); + sn = !!(ptr[0] & 0x08); + md = !!(ptr[0] & 0x10); + length = ptr[1] & 0x1f; + + switch (llid) { + case 0x01: + if (length > 0) + str = "Continuation fragement of L2CAP message"; + else + str = "Empty message"; + break; + case 0x02: + str = "Start of L2CAP message"; + break; + case 0x03: + str = "Control"; + break; + default: + str = "Reserved"; + break; + } + + print_field("LLID: %s (0x%2.2x)", str, llid); + print_field("Next expected sequence number: %u", nesn); + print_field("Sequence number: %u", sn); + print_field("More data: %u", md); + print_field("Length: %u", length); + + switch (llid) { + case 0x03: + llcp_packet(data + 2, size - 2, padded); + break; + + default: + packet_hexdump(data + 2, size - 2); + break; + } +} + +void ll_packet(uint16_t frequency, const void *data, uint8_t size, bool padded) +{ + const struct bt_ll_hdr *hdr = data; + uint8_t channel = (frequency - 2402) / 2; + uint32_t access_addr; + char access_str[12]; + const char *channel_label, *channel_color; + const uint8_t *pdu_data; + uint8_t pdu_len; + uint32_t pdu_crc, crc, crc_init; + + if (size < sizeof(*hdr)) { + print_text(COLOR_ERROR, "packet missing header"); + packet_hexdump(data, size); + return; + } + + if (size < sizeof(*hdr) + 3) { + print_text(COLOR_ERROR, "packet missing checksum"); + packet_hexdump(data, size); + return; + } + + if (hdr->preamble != 0xaa && hdr->preamble != 0x55) { + print_text(COLOR_ERROR, "invalid preamble"); + packet_hexdump(data, size); + return; + } + + access_addr = le32_to_cpu(hdr->access_addr); + + pdu_data = data + sizeof(*hdr); + pdu_len = size - sizeof(*hdr) - 3; + + pdu_crc = pdu_data[pdu_len + 0] | (pdu_data[pdu_len + 1] << 8) | + (pdu_data[pdu_len + 2] << 16); + + if (access_addr == 0x8e89bed6) { + channel_label = "Advertising channel: "; + channel_color = COLOR_MAGENTA; + } else { + channel_label = "Data channel: "; + channel_color = COLOR_CYAN; + } + + sprintf(access_str, "0x%8.8x", access_addr); + + print_indent(6, channel_color, channel_label, access_str, COLOR_OFF, + " (channel %d) len %d crc 0x%6.6x", channel, pdu_len, pdu_crc); + + if (access_addr == 0x8e89bed6) + crc_init = 0xaaaaaa; + else + crc_init = get_crc_init(access_addr); + + if (crc_init) { + crc = crc24_calculate(crc_init, pdu_data, pdu_len); + + if (crc != pdu_crc) { + print_text(COLOR_ERROR, "invalid checksum"); + packet_hexdump(pdu_data, pdu_len); + return; + } + } else + print_text(COLOR_ERROR, "unknown access address"); + + if (access_addr == 0x8e89bed6) + advertising_packet(pdu_data, pdu_len); + else + data_packet(pdu_data, pdu_len, padded); +} + +static void null_pdu(const void *data, uint8_t size) +{ +} + +static void conn_update_req(const void *data, uint8_t size) +{ + const struct bt_ll_conn_update_req *pdu = data; + + print_field("Transmit window size: %u", pdu->win_size); + print_field("Transmit window offset: %u", le16_to_cpu(pdu->win_offset)); + print_field("Connection interval: %u", le16_to_cpu(pdu->interval)); + print_field("Connection slave latency: %u", le16_to_cpu(pdu->latency)); + print_field("Connection supervision timeout: %u", le16_to_cpu(pdu->timeout)); + print_field("Connection instant: %u", le16_to_cpu(pdu->instant)); +} + +static void channel_map_req(const void *data, uint8_t size) +{ + const struct bt_ll_channel_map_req *pdu = data; + + packet_print_channel_map_ll(pdu->map); + print_field("Connection instant: %u", le16_to_cpu(pdu->instant)); +} + +static void terminate_ind(const void *data, uint8_t size) +{ + const struct bt_ll_terminate_ind *pdu = data; + + packet_print_error("Error code", pdu->error); +} + +static void enc_req(const void *data, uint8_t size) +{ + const struct bt_ll_enc_req *pdu = data; + + print_field("Rand: 0x%16.16" PRIx64, le64_to_cpu(pdu->rand)); + print_field("EDIV: 0x%4.4x", le16_to_cpu(pdu->ediv)); + print_field("SKD (master): 0x%16.16" PRIx64, le64_to_cpu(pdu->skd)); + print_field("IV (master): 0x%8.8x", le32_to_cpu(pdu->iv)); +} + +static void enc_rsp(const void *data, uint8_t size) +{ + const struct bt_ll_enc_rsp *pdu = data; + + print_field("SKD (slave): 0x%16.16" PRIx64, le64_to_cpu(pdu->skd)); + print_field("IV (slave): 0x%8.8x", le32_to_cpu(pdu->iv)); +} + +static const char *opcode_to_string(uint8_t opcode); + +static void unknown_rsp(const void *data, uint8_t size) +{ + const struct bt_ll_unknown_rsp *pdu = data; + + print_field("Unknown type: %s (0x%2.2x)", + opcode_to_string(pdu->type), pdu->type); +} + +static void feature_req(const void *data, uint8_t size) +{ + const struct bt_ll_feature_req *pdu = data; + + packet_print_features_ll(pdu->features); +} + +static void feature_rsp(const void *data, uint8_t size) +{ + const struct bt_ll_feature_rsp *pdu = data; + + packet_print_features_ll(pdu->features); +} + +static void version_ind(const void *data, uint8_t size) +{ + const struct bt_ll_version_ind *pdu = data; + + packet_print_version("Version", pdu->version, + "Subversion", le16_to_cpu(pdu->subversion)); + packet_print_company("Company", le16_to_cpu(pdu->company)); +} + +static void reject_ind(const void *data, uint8_t size) +{ + const struct bt_ll_reject_ind *pdu = data; + + packet_print_error("Error code", pdu->error); +} + +static void slave_feature_req(const void *data, uint8_t size) +{ + const struct bt_ll_slave_feature_req *pdu = data; + + packet_print_features_ll(pdu->features); +} + +static void reject_ind_ext(const void *data, uint8_t size) +{ + const struct bt_ll_reject_ind_ext *pdu = data; + + print_field("Reject opcode: %u (0x%2.2x)", pdu->opcode, pdu->opcode); + packet_print_error("Error code", pdu->error); +} + +static void length_req_rsp(const void *data, uint8_t size) +{ + const struct bt_ll_length *pdu = data; + + print_field("MaxRxOctets: %u", pdu->rx_len); + print_field("MaxRxTime: %u", pdu->rx_time); + print_field("MaxTxOctets: %u", pdu->tx_len); + print_field("MaxtxTime: %u", pdu->tx_time); +} + +static const struct bitfield_data le_phys[] = { + { 0, "LE 1M" }, + { 1, "LE 2M" }, + { 2, "LE Coded"}, + { } +}; + +static void phy_req_rsp(const void *data, uint8_t size) +{ + const struct bt_ll_phy *pdu = data; + uint8_t mask; + + print_field("RX PHYs: 0x%2.2x", pdu->rx_phys); + + mask = print_bitfield(2, pdu->rx_phys, le_phys); + if (mask) + print_text(COLOR_UNKNOWN_OPTIONS_BIT, " Reserved" + " (0x%2.2x)", mask); + print_field("TX PHYs: 0x%2.2x", pdu->tx_phys); + + mask = print_bitfield(2, pdu->tx_phys, le_phys); + if (mask) + print_text(COLOR_UNKNOWN_OPTIONS_BIT, " Reserved" + " (0x%2.2x)", mask); +} + +static void phy_update_ind(const void *data, uint8_t size) +{ + const struct bt_ll_phy_update_ind *pdu = data; + uint8_t mask; + + print_field("M_TO_S_PHY: 0x%2.2x", pdu->m_phy); + + mask = print_bitfield(2, pdu->m_phy, le_phys); + if (mask) + print_text(COLOR_UNKNOWN_OPTIONS_BIT, " Reserved" + " (0x%2.2x)", mask); + + print_field("S_TO_M_PHY: 0x%2.2x", pdu->s_phy); + + mask = print_bitfield(2, pdu->s_phy, le_phys); + if (mask) + print_text(COLOR_UNKNOWN_OPTIONS_BIT, " Reserved" + " (0x%2.2x)", mask); + + print_field("Instant: 0x%4.4x", pdu->instant); +} + +static void min_used_channels(const void *data, uint8_t size) +{ + const struct bt_ll_min_used_channels *pdu = data; + uint8_t mask; + + print_field("PHYS: 0x%2.2x", pdu->phys); + + mask = print_bitfield(2, pdu->phys, le_phys); + if (mask) + print_text(COLOR_UNKNOWN_OPTIONS_BIT, " Reserved" + " (0x%2.2x)", mask); + + print_field("MinUsedChannels: 0x%2.2x", pdu->min_channels); +} + +static void cte_req(const void *data, uint8_t size) +{ + const struct bt_ll_cte_req *pdu = data; + + print_field("MinCTELenReq: 0x%2.2x", pdu->cte & 0xf8); + print_field("CTETypeReq: 0x%2.2x", pdu->cte & 0x03); + + switch (pdu->cte & 0x03) { + case 0x00: + print_field(" AoA Constant Tone Extension"); + break; + case 0x01: + print_field(" AoD Constant Tone Extension with 1 μs slots"); + break; + case 0x02: + print_field(" AoD Constant Tone Extension with 2 μs slots"); + break; + } +} + +static void periodic_sync_ind(const void *data, uint8_t size) +{ + const struct bt_ll_periodic_sync_ind *pdu = data; + uint8_t mask; + + print_field("ID: 0x%4.4x", pdu->id); + print_field("SyncInfo:"); + packet_hexdump(pdu->info, sizeof(pdu->info)); + print_field("connEventCount: 0x%4.4x", pdu->event_count); + print_field("lastPaEventCounter: 0x%4.4x", pdu->last_counter); + print_field("SID: 0x%2.2x", pdu->adv_info & 0xf0); + print_field("AType: %s", pdu->adv_info & 0x08 ? "random" : "public"); + print_field("SCA: 0x%2.2x", pdu->adv_info & 0x07); + print_field("PHY: 0x%2.2x", pdu->phy); + + mask = print_bitfield(2, pdu->phy, le_phys); + if (mask) + print_text(COLOR_UNKNOWN_OPTIONS_BIT, " Reserved" + " (0x%2.2x)", mask); + + packet_print_addr("AdvA", pdu->adv_addr, pdu->adv_info & 0x08); + print_field("syncConnEventCount: 0x%4.4x", pdu->sync_counter); +} + +static void clock_acc_req_rsp(const void *data, uint8_t size) +{ + const struct bt_ll_clock_acc *pdu = data; + + print_field("SCA: 0x%2.2x", pdu->sca); +} + +struct llcp_data { + uint8_t opcode; + const char *str; + void (*func) (const void *data, uint8_t size); + uint8_t size; + bool fixed; +}; + +static const struct llcp_data llcp_table[] = { + { 0x00, "LL_CONNECTION_UPDATE_REQ", conn_update_req, 11, true }, + { 0x01, "LL_CHANNEL_MAP_REQ", channel_map_req, 7, true }, + { 0x02, "LL_TERMINATE_IND", terminate_ind, 1, true }, + { 0x03, "LL_ENC_REQ", enc_req, 22, true }, + { 0x04, "LL_ENC_RSP", enc_rsp, 12, true }, + { 0x05, "LL_START_ENC_REQ", null_pdu, 0, true }, + { 0x06, "LL_START_ENC_RSP", null_pdu, 0, true }, + { 0x07, "LL_UNKNOWN_RSP", unknown_rsp, 1, true }, + { 0x08, "LL_FEATURE_REQ", feature_req, 8, true }, + { 0x09, "LL_FEATURE_RSP", feature_rsp, 8, true }, + { 0x0a, "LL_PAUSE_ENC_REQ", null_pdu, 0, true }, + { 0x0b, "LL_PAUSE_ENC_RSP", null_pdu, 0, true }, + { 0x0c, "LL_VERSION_IND", version_ind, 5, true }, + { 0x0d, "LL_REJECT_IND", reject_ind, 1, true }, + { 0x0e, "LL_SLAVE_FEATURE_REQ", slave_feature_req, 8, true }, + { 0x0f, "LL_CONNECTION_PARAM_REQ", NULL, 23, true }, + { 0x10, "LL_CONNECTION_PARAM_RSP", NULL, 23, true }, + { 0x11, "LL_REJECT_IND_EXT", reject_ind_ext, 2, true }, + { 0x12, "LL_PING_REQ", null_pdu, 0, true }, + { 0x13, "LL_PING_RSP", null_pdu, 0, true }, + { 0x14, "LL_LENGTH_REQ", length_req_rsp, 8, true }, + { 0x15, "LL_LENGTH_RSP", length_req_rsp, 8, true }, + { 0x16, "LL_PHY_REQ", phy_req_rsp, 2, true }, + { 0x17, "LL_PHY_RSP", phy_req_rsp, 2, true }, + { 0x18, "LL_PHY_UPDATE_IND", phy_update_ind, 4, true }, + { 0x19, "LL_MIN_USED_CHANNELS_IND", min_used_channels, 2, true }, + { 0x1a, "LL_CTE_REQ", cte_req, 1, true }, + { 0x1b, "LL_CTE_RSP", null_pdu, 0, true }, + { 0x1c, "LL_PERIODIC_SYNC_IND", periodic_sync_ind, 34, true }, + { 0x1d, "LL_CLOCK_ACCURACY_REQ", clock_acc_req_rsp, 1, true }, + { 0x1e, "LL_CLOCK_ACCURACY_RSP", clock_acc_req_rsp, 1, true }, + { } +}; + +static const char *opcode_to_string(uint8_t opcode) +{ + int i; + + for (i = 0; llcp_table[i].str; i++) { + if (llcp_table[i].opcode == opcode) + return llcp_table[i].str; + } + + return "Unknown"; +} + +void llcp_packet(const void *data, uint8_t size, bool padded) +{ + uint8_t opcode = ((const uint8_t *) data)[0]; + const struct llcp_data *llcp_data = NULL; + const char *opcode_color, *opcode_str; + int i; + + for (i = 0; llcp_table[i].str; i++) { + if (llcp_table[i].opcode == opcode) { + llcp_data = &llcp_table[i]; + break; + } + } + + if (llcp_data) { + if (llcp_data->func) + opcode_color = COLOR_OPCODE; + else + opcode_color = COLOR_OPCODE_UNKNOWN; + opcode_str = llcp_data->str; + } else { + opcode_color = COLOR_OPCODE_UNKNOWN; + opcode_str = "Unknown"; + } + + print_indent(6, opcode_color, "", opcode_str, COLOR_OFF, + " (0x%2.2x)", opcode); + + if (!llcp_data || !llcp_data->func) { + packet_hexdump(data + 1, size - 1); + return; + } + + if (llcp_data->fixed && !padded) { + if (size - 1 != llcp_data->size) { + print_text(COLOR_ERROR, "invalid packet size"); + packet_hexdump(data + 1, size - 1); + return; + } + } else { + if (size - 1 < llcp_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data + 1, size - 1); + return; + } + } + + llcp_data->func(data + 1, size - 1); +} diff --git a/monitor/ll.h b/monitor/ll.h new file mode 100644 index 0000000..98e0bf4 --- /dev/null +++ b/monitor/ll.h @@ -0,0 +1,28 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +void ll_packet(uint16_t frequency, const void *data, uint8_t size, bool padded); +void llcp_packet(const void *data, uint8_t size, bool padded); diff --git a/monitor/lmp.c b/monitor/lmp.c new file mode 100644 index 0000000..3088c5e --- /dev/null +++ b/monitor/lmp.c @@ -0,0 +1,937 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include + +#include "src/shared/util.h" +#include "display.h" +#include "packet.h" +#include "bt.h" +#include "lmp.h" + +#define COLOR_OPCODE COLOR_MAGENTA +#define COLOR_OPCODE_UNKNOWN COLOR_WHITE_BG + +static const char *get_opcode_str(uint16_t opcode); + +static void print_opcode(uint16_t opcode) +{ + const char *str; + + str = get_opcode_str(opcode); + if (!str) + str = "Unknown"; + + if (opcode & 0xff00) + print_field("Operation: %s (%u/%u)", str, + opcode >> 8, opcode & 0xff); + else + print_field("Operation: %s (%u)", str, opcode); +} + +static void name_req(const void *data, uint8_t size) +{ + const struct bt_lmp_name_req *pdu = data; + + print_field("Offset: %u", pdu->offset); +} + +static void name_rsp(const void *data, uint8_t size) +{ + const struct bt_lmp_name_rsp *pdu = data; + char str[15]; + + memcpy(str, pdu->fragment, 14); + str[14] = '\0'; + + print_field("Offset: %u", pdu->offset); + print_field("Length: %u", pdu->length); + print_field("Fragment: %s", str); +} + +static void accepted(const void *data, uint8_t size) +{ + const struct bt_lmp_accepted *pdu = data; + + print_opcode(pdu->opcode); +} + +static void not_accepted(const void *data, uint8_t size) +{ + const struct bt_lmp_not_accepted *pdu = data; + + print_opcode(pdu->opcode); + packet_print_error("Error code", pdu->error); +} + +static void clkoffset_req(const void *data, uint8_t size) +{ +} + +static void clkoffset_rsp(const void *data, uint8_t size) +{ + const struct bt_lmp_clkoffset_rsp *pdu = data; + + print_field("Clock offset: 0x%4.4x", le16_to_cpu(pdu->offset)); +} + +static void detach(const void *data, uint8_t size) +{ + const struct bt_lmp_detach *pdu = data; + + packet_print_error("Error code", pdu->error); +} + +static void au_rand(const void *data, uint8_t size) +{ + const struct bt_lmp_au_rand *pdu = data; + + packet_hexdump(pdu->number, 16); +} + +static void sres(const void *data, uint8_t size) +{ + const struct bt_lmp_sres *pdu = data; + + packet_hexdump(pdu->response, 4); +} + +static void encryption_mode_req(const void *data, uint8_t size) +{ + const struct bt_lmp_encryption_mode_req *pdu = data; + const char *str; + + switch (pdu->mode) { + case 0x00: + str = "No encryption"; + break; + case 0x01: + str = "Encryption"; + break; + case 0x02: + str = "Encryption"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Mode: %s (%u)", str, pdu->mode); +} + +static void encryption_key_size_req(const void *data, uint8_t size) +{ + const struct bt_lmp_encryption_key_size_req *pdu = data; + + print_field("Key size: %u", pdu->key_size); +} + +static void start_encryption_req(const void *data, uint8_t size) +{ + const struct bt_lmp_start_encryption_req *pdu = data; + + packet_hexdump(pdu->number, 16); +} + +static void stop_encryption_req(const void *data, uint8_t size) +{ +} + +static void switch_req(const void *data, uint8_t size) +{ + const struct bt_lmp_switch_req *pdu = data; + + print_field("Instant: 0x%8.8x", le32_to_cpu(pdu->instant)); +} + +static void unsniff_req(const void *data, uint8_t size) +{ +} + +static void max_power(const void *data, uint8_t size) +{ +} + +static void min_power(const void *data, uint8_t size) +{ +} + +static void auto_rate(const void *data, uint8_t size) +{ +} + +static void preferred_rate(const void *data, uint8_t size) +{ + const struct bt_lmp_preferred_rate *pdu = data; + const char *str; + + str = (pdu->rate & 0x01) ? "do not use FEC" : "use FEC"; + + print_field("Basic data rate: %s (0x%02x)", str, pdu->rate & 0x01); + + switch ((pdu->rate & 0x06) >> 1) { + case 0: + str = "No packet-size preference available"; + break; + case 1: + str = "use 1-slot packets"; + break; + case 2: + str = "use 3-slot packets"; + break; + case 3: + str = "use 5-slot packets"; + break; + } + + print_field("Basic data rate: %s (0x%02x)", str, pdu->rate & 0x06); + + switch ((pdu->rate & 0x11) >> 3) { + case 0: + str = "use DM1 packets"; + break; + case 1: + str = "use 2 Mb/s packets"; + break; + case 2: + str = "use 3 MB/s packets"; + break; + case 3: + str = "reserved"; + break; + } + + print_field("Enhanced data rate: %s (0x%2.2x)", str, pdu->rate & 0x11); + + switch ((pdu->rate & 0x60) >> 5) { + case 0: + str = "No packet-size preference available"; + break; + case 1: + str = "use 1-slot packets"; + break; + case 2: + str = "use 3-slot packets"; + break; + case 3: + str = "use 5-slot packets"; + break; + } + + print_field("Enhanced data rate: %s (0x%2.2x)", str, pdu->rate & 0x60); +} + +static void version_req(const void *data, uint8_t size) +{ + const struct bt_lmp_version_req *pdu = data; + + packet_print_version("Version", pdu->version, + "Subversion", le16_to_cpu(pdu->subversion)); + packet_print_company("Company", le16_to_cpu(pdu->company)); +} + +static void version_res(const void *data, uint8_t size) +{ + const struct bt_lmp_version_res *pdu = data; + + packet_print_version("Version", pdu->version, + "Subversion", le16_to_cpu(pdu->subversion)); + packet_print_company("Company", le16_to_cpu(pdu->company)); +} + +static void features_req(const void *data, uint8_t size) +{ + const struct bt_lmp_features_req *pdu = data; + + packet_print_features_lmp(pdu->features, 0x00); +} + +static void features_res(const void *data, uint8_t size) +{ + const struct bt_lmp_features_res *pdu = data; + + packet_print_features_lmp(pdu->features, 0x00); +} + +static void max_slot(const void *data, uint8_t size) +{ + const struct bt_lmp_max_slot *pdu = data; + + print_field("Slots: 0x%4.4x", pdu->slots); +} + +static void max_slot_req(const void *data, uint8_t size) +{ + const struct bt_lmp_max_slot_req *pdu = data; + + print_field("Slots: 0x%4.4x", pdu->slots); +} + +static void timing_accuracy_req(const void *data, uint8_t size) +{ +} + +static void timing_accuracy_res(const void *data, uint8_t size) +{ + const struct bt_lmp_timing_accuracy_res *pdu = data; + + print_field("Drift: %u ppm", pdu->drift); + print_field("Jitter: %u usec", pdu->jitter); +} + +static void setup_complete(const void *data, uint8_t size) +{ +} + +static void use_semi_permanent_key(const void *data, uint8_t size) +{ +} + +static void host_connection_req(const void *data, uint8_t size) +{ +} + +static void slot_offset(const void *data, uint8_t size) +{ + const struct bt_lmp_slot_offset *pdu = data; + + print_field("Offset: %u usec", le16_to_cpu(pdu->offset)); + packet_print_addr("Address", pdu->bdaddr, false); +} + +static void page_scan_mode_req(const void *data, uint8_t size) +{ + const struct bt_lmp_page_scan_mode_req *pdu = data; + const char *str; + + switch (pdu->scheme) { + case 0x00: + str = "Mandatory"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Paging scheme: %s (%u)", str, pdu->scheme); + + if (pdu->scheme == 0x00) { + switch (pdu->settings) { + case 0x00: + str = "R0"; + break; + case 0x01: + str = "R1"; + break; + case 0x02: + str = "R2"; + break; + default: + str = "Reserved"; + break; + } + } else + str = "Reserved"; + + print_field("Paging scheme settings: %s (%u)", str, pdu->settings); +} + +static void test_activate(const void *data, uint8_t size) +{ +} + +static void encryption_key_size_mask_req(const void *data, uint8_t size) +{ +} + +static void set_afh(const void *data, uint8_t size) +{ + const struct bt_lmp_set_afh *pdu = data; + const char *str; + + print_field("Instant: %u", le32_to_cpu(pdu->instant)); + + switch (pdu->mode) { + case 0x00: + str = "Disabled"; + break; + case 0x01: + str = "Enabled"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Mode: %s (0x%2.2x)", str, pdu->mode); + packet_print_channel_map_lmp(pdu->map); +} + +static void encapsulated_header(const void *data, uint8_t size) +{ + const struct bt_lmp_encapsulated_header *pdu = data; + const char *str; + + print_field("Major type: %u", pdu->major); + print_field("Minor type: %u", pdu->minor); + + if (pdu->major == 0x01) { + switch (pdu->minor) { + case 0x01: + str = "P-192 Public Key"; + break; + case 0x02: + str = "P-256 Public Key"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" %s", str); + } + + print_field("Length: %u", pdu->length); +} + +static void encapsulated_payload(const void *data, uint8_t size) +{ + const struct bt_lmp_encapsulated_payload *pdu = data; + + packet_hexdump(pdu->data, 16); +} + +static void simple_pairing_confirm(const void *data, uint8_t size) +{ + const struct bt_lmp_simple_pairing_confirm *pdu = data; + + packet_hexdump(pdu->value, 16); +} + +static void simple_pairing_number(const void *data, uint8_t size) +{ + const struct bt_lmp_simple_pairing_number *pdu = data; + + packet_hexdump(pdu->value, 16); +} + +static void dhkey_check(const void *data, uint8_t size) +{ + const struct bt_lmp_dhkey_check *pdu = data; + + packet_hexdump(pdu->value, 16); +} + +static void accepted_ext(const void *data, uint8_t size) +{ + const struct bt_lmp_accepted_ext *pdu = data; + uint16_t opcode; + + switch (pdu->escape) { + case 127: + opcode = LMP_ESC4(pdu->opcode); + break; + default: + return; + } + + print_opcode(opcode); +} + +static void not_accepted_ext(const void *data, uint8_t size) +{ + const struct bt_lmp_not_accepted_ext *pdu = data; + uint16_t opcode; + + switch (pdu->escape) { + case 127: + opcode = LMP_ESC4(pdu->opcode); + break; + default: + return; + } + + print_opcode(opcode); + print_field("Error code: %u", pdu->error); +} + +static void features_req_ext(const void *data, uint8_t size) +{ + const struct bt_lmp_features_req_ext *pdu = data; + + print_field("Features page: %u", pdu->page); + print_field("Max supported page: %u", pdu->max_page); + packet_print_features_lmp(pdu->features, pdu->page); +} + +static void features_res_ext(const void *data, uint8_t size) +{ + const struct bt_lmp_features_res_ext *pdu = data; + + print_field("Features page: %u", pdu->page); + print_field("Max supported page: %u", pdu->max_page); + packet_print_features_lmp(pdu->features, pdu->page); +} + +static void packet_type_table_req(const void *data, uint8_t size) +{ + const struct bt_lmp_packet_type_table_req *pdu = data; + const char *str; + + switch (pdu->table) { + case 0x00: + str = "1 Mbps only"; + break; + case 0x01: + str = "2/3 Mbps"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Table: %s (0x%2.2x)", str, pdu->table); +} + +static void channel_classification_req(const void *data, uint8_t size) +{ + const struct bt_lmp_channel_classification_req *pdu = data; + const char *str; + + switch (pdu->mode) { + case 0x00: + str = "Disabled"; + break; + case 0x01: + str = "Enabled"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Reporting mode: %s (0x%2.2x)", str, pdu->mode); + print_field("Min interval: 0x%2.2x", pdu->min_interval); + print_field("Max interval: 0x%2.2x", pdu->max_interval); +} + +static void channel_classification(const void *data, uint8_t size) +{ + const struct bt_lmp_channel_classification *pdu = data; + char str[21]; + int i; + + for (i = 0; i < 10; i++) + sprintf(str + (i * 2), "%2.2x", pdu->classification[i]); + + print_field("Classification: 0x%s", str); +} + +static void pause_encryption_req(const void *data, uint8_t size) +{ +} + +static void resume_encryption_req(const void *data, uint8_t size) +{ +} + +static void io_capability_req(const void *data, uint8_t size) +{ + const struct bt_lmp_io_capability_req *pdu = data; + const char *str; + + packet_print_io_capability(pdu->capability); + + switch (pdu->oob_data) { + case 0x00: + str = "No authentication data received"; + break; + case 0x01: + str = "Authentication data received"; + break; + default: + str = "Reserved"; + break; + } + + print_field("OOB data: %s (0x%2.2x)", str, pdu->oob_data); + + packet_print_io_authentication(pdu->authentication); +} + +static void io_capability_res(const void *data, uint8_t size) +{ + const struct bt_lmp_io_capability_res *pdu = data; + const char *str; + + packet_print_io_capability(pdu->capability); + + switch (pdu->oob_data) { + case 0x00: + str = "No authentication data received"; + break; + case 0x01: + str = "Authentication data received"; + break; + default: + str = "Reserved"; + break; + } + + print_field("OOB data: %s (0x%2.2x)", str, pdu->oob_data); + + packet_print_io_authentication(pdu->authentication); +} + +static void numeric_comparison_failed(const void *data, uint8_t size) +{ +} + +static void passkey_failed(const void *data, uint8_t size) +{ +} + +static void oob_failed(const void *data, uint8_t size) +{ +} + +static void power_control_req(const void *data, uint8_t size) +{ + const struct bt_lmp_power_control_req *pdu = data; + const char *str; + + switch (pdu->request) { + case 0x00: + str = "Decrement power one step"; + break; + case 0x01: + str = "Increment power one step"; + break; + case 0x02: + str = "Increase to maximum power"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Request: %s (0x%2.2x)", str, pdu->request); +} + +static void power_control_res(const void *data, uint8_t size) +{ + const struct bt_lmp_power_control_res *pdu = data; + const char *str; + + print_field("Response: 0x%2.2x", pdu->response); + + switch (pdu->response & 0x03) { + case 0x00: + str = "Not supported"; + break; + case 0x01: + str = "Changed one step"; + break; + case 0x02: + str = "Max power"; + break; + case 0x03: + str = "Min power"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" GFSK: %s", str); + + switch ((pdu->response & 0x0c) >> 2) { + case 0x00: + str = "Not supported"; + break; + case 0x01: + str = "Changed one step"; + break; + case 0x02: + str = "Max power"; + break; + case 0x03: + str = "Min power"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" DQPSK: %s", str); + + switch ((pdu->response & 0x30) >> 4) { + case 0x00: + str = "Not supported"; + break; + case 0x01: + str = "Changed one step"; + break; + case 0x02: + str = "Max power"; + break; + case 0x03: + str = "Min power"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" 8DPSK: %s", str); +} + +static void ping_req(const void *data, uint8_t size) +{ +} + +static void ping_res(const void *data, uint8_t size) +{ +} + +struct lmp_data { + uint16_t opcode; + const char *str; + void (*func) (const void *data, uint8_t size); + uint8_t size; + bool fixed; +}; + +static const struct lmp_data lmp_table[] = { + { 1, "LMP_name_req", name_req, 1, true }, + { 2, "LMP_name_res", name_rsp, 16, true }, + { 3, "LMP_accepted", accepted, 1, true }, + { 4, "LMP_not_accepted", not_accepted, 2, true }, + { 5, "LMP_clkoffset_req", clkoffset_req, 0, true }, + { 6, "LMP_clkoffset_res", clkoffset_rsp, 2, true }, + { 7, "LMP_detach", detach, 1, true }, + { 8, "LMP_in_rand" }, + { 9, "LMP_comb_key" }, + { 10, "LMP_unit_key" }, + { 11, "LMP_au_rand", au_rand, 16, true }, + { 12, "LMP_sres", sres, 4, true }, + { 13, "LMP_temp_rand" }, + { 14, "LMP_temp_key" }, + { 15, "LMP_encryption_mode_req", encryption_mode_req, 1, true }, + { 16, "LMP_encryption_key_size_req", encryption_key_size_req, 1, true }, + { 17, "LMP_start_encryption_req", start_encryption_req, 16, true }, + { 18, "LMP_stop_encryption_req", stop_encryption_req, 0, true }, + { 19, "LMP_switch_req", switch_req, 4, true }, + { 20, "LMP_hold" }, + { 21, "LMP_hold_req" }, + { 22, "LMP_sniff" }, + { 23, "LMP_sniff_req" }, + { 24, "LMP_unsniff_req", unsniff_req, 0, true }, + { 25, "LMP_park_req" }, + { 26, "LMP_park" }, + { 27, "LMP_set_broadcast_scan_window" }, + { 28, "LMP_modify_beacon" }, + { 29, "LMP_unpark_BD_ADDR_req" }, + { 30, "LMP_unpark_PM_ADDR_req" }, + { 31, "LMP_incr_power_req" }, + { 32, "LMP_decr_power_req" }, + { 33, "LMP_max_power", max_power, 0, true }, + { 34, "LMP_min_power", min_power, 0, true }, + { 35, "LMP_auto_rate", auto_rate, 0, true }, + { 36, "LMP_preferred_rate", preferred_rate, 1, true }, + { 37, "LMP_version_req", version_req, 5, true }, + { 38, "LMP_version_res", version_res, 5, true }, + { 39, "LMP_features_req", features_req, 8, true }, + { 40, "LMP_features_res", features_res, 8, true }, + { 41, "LMP_quality_of_service" }, + { 42, "LMP_quality_of_service_req" }, + { 43, "LMP_SCO_link_req" }, + { 44, "LMP_remove_SCO_link_req" }, + { 45, "LMP_max_slot", max_slot, 1, true }, + { 46, "LMP_max_slot_req", max_slot_req, 1, true }, + { 47, "LMP_timing_accuracy_req", timing_accuracy_req, 0, true }, + { 48, "LMP_timing_accuracy_res", timing_accuracy_res, 2, true }, + { 49, "LMP_setup_complete", setup_complete, 0, true }, + { 50, "LMP_use_semi_permanent_key", use_semi_permanent_key, 0, true }, + { 51, "LMP_host_connection_req", host_connection_req, 0, true }, + { 52, "LMP_slot_offset", slot_offset, 8, true }, + { 53, "LMP_page_mode_req" }, + { 54, "LMP_page_scan_mode_req", page_scan_mode_req, 2, true }, + { 55, "LMP_supervision_timeout" }, + { 56, "LMP_test_activate", test_activate, 0, true }, + { 57, "LMP_test_control" }, + { 58, "LMP_encryption_key_size_mask_req", encryption_key_size_mask_req, 0, true }, + { 59, "LMP_encryption_key_size_mask_res" }, + { 60, "LMP_set_AFH", set_afh, 15, true }, + { 61, "LMP_encapsulated_header", encapsulated_header, 3, true }, + { 62, "LMP_encapsulated_payload", encapsulated_payload, 16, true }, + { 63, "LMP_simple_pairing_confirm", simple_pairing_confirm, 16, true }, + { 64, "LMP_simple_pairing_number", simple_pairing_number, 16, true }, + { 65, "LMP_DHkey_check", dhkey_check, 16, true }, + { 66, "LMP_pause_encryption_aes_req" }, + { LMP_ESC4(1), "LMP_accepted_ext", accepted_ext, 2, true }, + { LMP_ESC4(2), "LMP_not_accepted_ext", not_accepted_ext, 3, true }, + { LMP_ESC4(3), "LMP_features_req_ext", features_req_ext, 10, true }, + { LMP_ESC4(4), "LMP_features_res_ext", features_res_ext, 10, true }, + { LMP_ESC4(5), "LMP_clk_adj" }, + { LMP_ESC4(6), "LMP_clk_adj_ack" }, + { LMP_ESC4(7), "LMP_clk_adj_req" }, + { LMP_ESC4(11), "LMP_packet_type_table_req", packet_type_table_req, 1, true }, + { LMP_ESC4(12), "LMP_eSCO_link_req" }, + { LMP_ESC4(13), "LMP_remove_eSCO_link_req" }, + { LMP_ESC4(16), "LMP_channel_classification_req", channel_classification_req, 5, true }, + { LMP_ESC4(17), "LMP_channel_classification", channel_classification, 10, true }, + { LMP_ESC4(21), "LMP_sniff_subrating_req" }, + { LMP_ESC4(22), "LMP_sniff_subrating_res" }, + { LMP_ESC4(23), "LMP_pause_encryption_req", pause_encryption_req, 0, true }, + { LMP_ESC4(24), "LMP_resume_encryption_req", resume_encryption_req, 0, true }, + { LMP_ESC4(25), "LMP_IO_capability_req", io_capability_req, 3, true }, + { LMP_ESC4(26), "LMP_IO_capability_res", io_capability_res, 3, true }, + { LMP_ESC4(27), "LMP_numeric_comparison_failed", numeric_comparison_failed, 0, true }, + { LMP_ESC4(28), "LMP_passkey_failed", passkey_failed, 0, true }, + { LMP_ESC4(29), "LMP_oob_failed", oob_failed, 0, true }, + { LMP_ESC4(30), "LMP_keypress_notification" }, + { LMP_ESC4(31), "LMP_power_control_req", power_control_req, 1, true }, + { LMP_ESC4(32), "LMP_power_control_res", power_control_res, 1, true }, + { LMP_ESC4(33), "LMP_ping_req", ping_req, 0, true }, + { LMP_ESC4(34), "LMP_ping_res", ping_res, 0, true }, + { LMP_ESC4(35), "LMP_SAM_set_type0" }, + { LMP_ESC4(36), "LMP_SAM_define_map" }, + { LMP_ESC4(37), "LMP_SAM_switch" }, + { } +}; + +static const char *get_opcode_str(uint16_t opcode) +{ + int i; + + for (i = 0; lmp_table[i].str; i++) { + if (lmp_table[i].opcode == opcode) + return lmp_table[i].str; + } + + return NULL; +} + +void lmp_packet(const void *data, uint8_t size, bool padded) +{ + const struct lmp_data *lmp_data = NULL; + const char *opcode_color, *opcode_str; + uint16_t opcode; + uint8_t tid, off; + const char *tid_str; + int i; + + tid = ((const uint8_t *) data)[0] & 0x01; + opcode = (((const uint8_t *) data)[0] & 0xfe) >> 1; + + tid_str = tid == 0x00 ? "Master" : "Slave"; + + switch (opcode) { + case 127: + if (size < 2) { + print_text(COLOR_ERROR, "extended opcode too short"); + packet_hexdump(data, size); + return; + } + opcode = LMP_ESC4(((const uint8_t *) data)[1]); + off = 2; + break; + case 126: + case 125: + case 124: + return; + default: + off = 1; + break; + } + + for (i = 0; lmp_table[i].str; i++) { + if (lmp_table[i].opcode == opcode) { + lmp_data = &lmp_table[i]; + break; + } + } + + if (lmp_data) { + if (lmp_data->func) + opcode_color = COLOR_OPCODE; + else + opcode_color = COLOR_OPCODE_UNKNOWN; + opcode_str = lmp_data->str; + } else { + opcode_color = COLOR_OPCODE_UNKNOWN; + opcode_str = "Unknown"; + } + + if (opcode & 0xff00) + print_indent(6, opcode_color, "", opcode_str, COLOR_OFF, + " (%u/%u) %s transaction (%u)", + opcode >> 8, opcode & 0xff, tid_str, tid); + else + print_indent(6, opcode_color, "", opcode_str, COLOR_OFF, + " (%u) %s transaction (%d)", + opcode, tid_str, tid); + + if (!lmp_data || !lmp_data->func) { + packet_hexdump(data + off, size - off); + return; + } + + if (lmp_data->fixed && !padded) { + if (size - off != lmp_data->size) { + print_text(COLOR_ERROR, "invalid packet size"); + packet_hexdump(data + off, size - off); + return; + } + } else { + if (size - off < lmp_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data + off, size - off); + return; + } + } + + lmp_data->func(data + off, size - off); +} + +void lmp_todo(void) +{ + int i; + + printf("LMP operations with missing decodings:\n"); + + for (i = 0; lmp_table[i].str; i++) { + if (lmp_table[i].func) + continue; + + printf("\t%s\n", lmp_table[i].str); + } +} diff --git a/monitor/lmp.h b/monitor/lmp.h new file mode 100644 index 0000000..9564c77 --- /dev/null +++ b/monitor/lmp.h @@ -0,0 +1,29 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +void lmp_packet(const void *data, uint8_t size, bool padded); + +void lmp_todo(void); diff --git a/monitor/main.c b/monitor/main.c new file mode 100644 index 0000000..479df85 --- /dev/null +++ b/monitor/main.c @@ -0,0 +1,277 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "src/shared/mainloop.h" +#include "src/shared/tty.h" + +#include "packet.h" +#include "lmp.h" +#include "keys.h" +#include "analyze.h" +#include "ellisys.h" +#include "control.h" + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + } +} + +static void usage(void) +{ + printf("btmon - Bluetooth monitor\n" + "Usage:\n"); + printf("\tbtmon [options]\n"); + printf("options:\n" + "\t-r, --read Read traces in btsnoop format\n" + "\t-w, --write Save traces in btsnoop format\n" + "\t-a, --analyze Analyze traces in btsnoop format\n" + "\t-s, --server Start monitor server socket\n" + "\t-p, --priority Show only priority or lower\n" + "\t-i, --index Show only specified controller\n" + "\t-d, --tty Read data from TTY\n" + "\t-B, --tty-speed Set TTY speed (default 115200)\n" + "\t-V, --vendor Set default company identifier\n" + "\t-t, --time Show time instead of time offset\n" + "\t-T, --date Show time and date information\n" + "\t-S, --sco Dump SCO traffic\n" + "\t-A, --a2dp Dump A2DP stream traffic\n" + "\t-E, --ellisys [ip] Send Ellisys HCI Injection\n" + "\t-P, --no-pager Disable pager usage\n" + "\t-J --jlink ,[],[],[]\n" + "\t Read data from RTT\n" + "\t-R --rtt [
],[],[]\n" + "\t RTT control block parameters\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "read", required_argument, NULL, 'r' }, + { "write", required_argument, NULL, 'w' }, + { "analyze", required_argument, NULL, 'a' }, + { "server", required_argument, NULL, 's' }, + { "priority", required_argument, NULL, 'p' }, + { "index", required_argument, NULL, 'i' }, + { "tty", required_argument, NULL, 'd' }, + { "tty-speed", required_argument, NULL, 'B' }, + { "vendor", required_argument, NULL, 'V' }, + { "time", no_argument, NULL, 't' }, + { "date", no_argument, NULL, 'T' }, + { "sco", no_argument, NULL, 'S' }, + { "a2dp", no_argument, NULL, 'A' }, + { "ellisys", required_argument, NULL, 'E' }, + { "no-pager", no_argument, NULL, 'P' }, + { "jlink", required_argument, NULL, 'J' }, + { "rtt", required_argument, NULL, 'R' }, + { "todo", no_argument, NULL, '#' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + unsigned long filter_mask = 0; + bool use_pager = true; + const char *reader_path = NULL; + const char *writer_path = NULL; + const char *analyze_path = NULL; + const char *ellisys_server = NULL; + const char *tty = NULL; + unsigned int tty_speed = B115200; + unsigned short ellisys_port = 0; + const char *str; + char *jlink = NULL; + char *rtt = NULL; + int exit_status; + + mainloop_init(); + + filter_mask |= PACKET_FILTER_SHOW_TIME_OFFSET; + + for (;;) { + int opt; + struct sockaddr_un addr; + + opt = getopt_long(argc, argv, "r:w:a:s:p:i:d:B:V:tTSAE:PJ:R:vh", + main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'r': + reader_path = optarg; + break; + case 'w': + writer_path = optarg; + break; + case 'a': + analyze_path = optarg; + break; + case 's': + if (strlen(optarg) > sizeof(addr.sun_path) - 1) { + fprintf(stderr, "Socket name too long\n"); + return EXIT_FAILURE; + } + control_server(optarg); + break; + case 'p': + packet_set_priority(optarg); + break; + case 'i': + if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3)) + str = optarg + 3; + else + str = optarg; + if (!isdigit(*str)) { + usage(); + return EXIT_FAILURE; + } + packet_select_index(atoi(str)); + break; + case 'd': + tty = optarg; + break; + case 'B': + tty_speed = tty_get_speed(atoi(optarg)); + if (!tty_speed) { + fprintf(stderr, "Unknown speed: %s\n", optarg); + return EXIT_FAILURE; + } + break; + case 'V': + str = optarg; + packet_set_fallback_manufacturer(atoi(str)); + break; + case 't': + filter_mask &= ~PACKET_FILTER_SHOW_TIME_OFFSET; + filter_mask |= PACKET_FILTER_SHOW_TIME; + break; + case 'T': + filter_mask &= ~PACKET_FILTER_SHOW_TIME_OFFSET; + filter_mask |= PACKET_FILTER_SHOW_TIME; + filter_mask |= PACKET_FILTER_SHOW_DATE; + break; + case 'S': + filter_mask |= PACKET_FILTER_SHOW_SCO_DATA; + break; + case 'A': + filter_mask |= PACKET_FILTER_SHOW_A2DP_STREAM; + break; + case 'E': + ellisys_server = optarg; + ellisys_port = 24352; + break; + case 'P': + use_pager = false; + break; + case 'J': + jlink = optarg; + break; + case 'R': + rtt = optarg; + break; + case '#': + packet_todo(); + lmp_todo(); + return EXIT_SUCCESS; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind > 0) { + fprintf(stderr, "Invalid command line parameters\n"); + return EXIT_FAILURE; + } + + if (reader_path && analyze_path) { + fprintf(stderr, "Display and analyze can't be combined\n"); + return EXIT_FAILURE; + } + + printf("Bluetooth monitor ver %s\n", VERSION); + + keys_setup(); + + packet_set_filter(filter_mask); + + if (analyze_path) { + analyze_trace(analyze_path); + return EXIT_SUCCESS; + } + + if (reader_path) { + if (ellisys_server) + ellisys_enable(ellisys_server, ellisys_port); + + control_reader(reader_path, use_pager); + return EXIT_SUCCESS; + } + + if (writer_path && !control_writer(writer_path)) { + printf("Failed to open '%s'\n", writer_path); + return EXIT_FAILURE; + } + + if (ellisys_server) + ellisys_enable(ellisys_server, ellisys_port); + + if (!tty && !jlink && control_tracing() < 0) + return EXIT_FAILURE; + + if (tty && control_tty(tty, tty_speed) < 0) + return EXIT_FAILURE; + + if (jlink && control_rtt(jlink, rtt) < 0) + return EXIT_FAILURE; + + exit_status = mainloop_run_with_signal(signal_callback, NULL); + + keys_cleanup(); + + return exit_status; +} diff --git a/monitor/packet.c b/monitor/packet.c new file mode 100644 index 0000000..ab8bbde --- /dev/null +++ b/monitor/packet.c @@ -0,0 +1,13029 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "src/shared/util.h" +#include "src/shared/btsnoop.h" +#include "display.h" +#include "bt.h" +#include "ll.h" +#include "hwdb.h" +#include "keys.h" +#include "l2cap.h" +#include "control.h" +#include "vendor.h" +#include "intel.h" +#include "broadcom.h" +#include "packet.h" + +#define COLOR_CHANNEL_LABEL COLOR_WHITE +#define COLOR_FRAME_LABEL COLOR_WHITE +#define COLOR_INDEX_LABEL COLOR_WHITE +#define COLOR_TIMESTAMP COLOR_YELLOW + +#define COLOR_NEW_INDEX COLOR_GREEN +#define COLOR_DEL_INDEX COLOR_RED +#define COLOR_OPEN_INDEX COLOR_GREEN +#define COLOR_CLOSE_INDEX COLOR_RED +#define COLOR_INDEX_INFO COLOR_GREEN +#define COLOR_VENDOR_DIAG COLOR_YELLOW +#define COLOR_SYSTEM_NOTE COLOR_OFF + +#define COLOR_HCI_COMMAND COLOR_BLUE +#define COLOR_HCI_COMMAND_UNKNOWN COLOR_WHITE_BG +#define COLOR_HCI_EVENT COLOR_MAGENTA +#define COLOR_HCI_EVENT_UNKNOWN COLOR_WHITE_BG +#define COLOR_HCI_ACLDATA COLOR_CYAN +#define COLOR_HCI_SCODATA COLOR_YELLOW + +#define COLOR_UNKNOWN_ERROR COLOR_WHITE_BG +#define COLOR_UNKNOWN_FEATURE_BIT COLOR_WHITE_BG +#define COLOR_UNKNOWN_COMMAND_BIT COLOR_WHITE_BG +#define COLOR_UNKNOWN_EVENT_MASK COLOR_WHITE_BG +#define COLOR_UNKNOWN_LE_STATES COLOR_WHITE_BG +#define COLOR_UNKNOWN_SERVICE_CLASS COLOR_WHITE_BG +#define COLOR_UNKNOWN_PKT_TYPE_BIT COLOR_WHITE_BG + +#define COLOR_CTRL_OPEN COLOR_GREEN_BOLD +#define COLOR_CTRL_CLOSE COLOR_RED_BOLD +#define COLOR_CTRL_COMMAND COLOR_BLUE_BOLD +#define COLOR_CTRL_COMMAND_UNKNOWN COLOR_WHITE_BG +#define COLOR_CTRL_EVENT COLOR_MAGENTA_BOLD +#define COLOR_CTRL_EVENT_UNKNOWN COLOR_WHITE_BG + +#define COLOR_UNKNOWN_OPTIONS_BIT COLOR_WHITE_BG +#define COLOR_UNKNOWN_SETTINGS_BIT COLOR_WHITE_BG +#define COLOR_UNKNOWN_ADDRESS_TYPE COLOR_WHITE_BG +#define COLOR_UNKNOWN_DEVICE_FLAG COLOR_WHITE_BG +#define COLOR_UNKNOWN_ADV_FLAG COLOR_WHITE_BG +#define COLOR_UNKNOWN_PHY COLOR_WHITE_BG + +#define COLOR_PHY_PACKET COLOR_BLUE + +#define UNKNOWN_MANUFACTURER 0xffff + +static time_t time_offset = ((time_t) -1); +static int priority_level = BTSNOOP_PRIORITY_INFO; +static unsigned long filter_mask = 0; +static bool index_filter = false; +static uint16_t index_current = 0; +static uint16_t fallback_manufacturer = UNKNOWN_MANUFACTURER; + +#define CTRL_RAW 0x0000 +#define CTRL_USER 0x0001 +#define CTRL_MGMT 0x0002 + +#define MAX_CTRL 64 + +struct ctrl_data { + bool used; + uint32_t cookie; + uint16_t format; + char name[20]; +}; + +static struct ctrl_data ctrl_list[MAX_CTRL]; + +static void assign_ctrl(uint32_t cookie, uint16_t format, const char *name) +{ + int i; + + for (i = 0; i < MAX_CTRL; i++) { + if (!ctrl_list[i].used) { + ctrl_list[i].used = true; + ctrl_list[i].cookie = cookie; + ctrl_list[i].format = format; + if (name) { + strncpy(ctrl_list[i].name, name, 19); + ctrl_list[i].name[19] = '\0'; + } else + strcpy(ctrl_list[i].name, "null"); + break; + } + } +} + +static void release_ctrl(uint32_t cookie, uint16_t *format, char *name) +{ + int i; + + if (format) + *format = 0xffff; + + for (i = 0; i < MAX_CTRL; i++) { + if (ctrl_list[i].used && ctrl_list[i].cookie == cookie) { + ctrl_list[i].used = false; + if (format) + *format = ctrl_list[i].format; + if (name) + strncpy(name, ctrl_list[i].name, 20); + break; + } + } +} + +static uint16_t get_format(uint32_t cookie) +{ + int i; + + for (i = 0; i < MAX_CTRL; i++) { + if (ctrl_list[i].used && ctrl_list[i].cookie == cookie) + return ctrl_list[i].format; + } + + return 0xffff; +} + +#define MAX_CONN 16 + +struct conn_data { + uint16_t handle; + uint8_t type; +}; + +static struct conn_data conn_list[MAX_CONN]; + +static void assign_handle(uint16_t handle, uint8_t type) +{ + int i; + + for (i = 0; i < MAX_CONN; i++) { + if (conn_list[i].handle == 0x0000) { + conn_list[i].handle = handle; + conn_list[i].type = type; + break; + } + } +} + +static void release_handle(uint16_t handle) +{ + int i; + + for (i = 0; i < MAX_CONN; i++) { + if (conn_list[i].handle == handle) { + conn_list[i].handle = 0x0000; + conn_list[i].type = 0x00; + break; + } + } +} + +static uint8_t get_type(uint16_t handle) +{ + int i; + + for (i = 0; i < MAX_CONN; i++) { + if (conn_list[i].handle == handle) + return conn_list[i].type; + } + + return 0xff; +} + +bool packet_has_filter(unsigned long filter) +{ + return filter_mask & filter; +} + +void packet_set_filter(unsigned long filter) +{ + filter_mask = filter; +} + +void packet_add_filter(unsigned long filter) +{ + if (index_filter) + filter &= ~PACKET_FILTER_SHOW_INDEX; + + filter_mask |= filter; +} + +void packet_del_filter(unsigned long filter) +{ + filter_mask &= ~filter; +} + +void packet_set_priority(const char *priority) +{ + if (!priority) + return; + + if (!strcasecmp(priority, "debug")) + priority_level = BTSNOOP_PRIORITY_DEBUG; + else + priority_level = atoi(priority); +} + +void packet_select_index(uint16_t index) +{ + filter_mask &= ~PACKET_FILTER_SHOW_INDEX; + + control_filter_index(index); + + index_filter = true; +} + +#define print_space(x) printf("%*c", (x), ' '); + +#define MAX_INDEX 16 + +struct index_data { + uint8_t type; + uint8_t bdaddr[6]; + uint16_t manufacturer; + size_t frame; +}; + +static struct index_data index_list[MAX_INDEX]; + +void packet_set_fallback_manufacturer(uint16_t manufacturer) +{ + int i; + + for (i = 0; i < MAX_INDEX; i++) + index_list[i].manufacturer = manufacturer; + + fallback_manufacturer = manufacturer; +} + +static void print_packet(struct timeval *tv, struct ucred *cred, char ident, + uint16_t index, const char *channel, + const char *color, const char *label, + const char *text, const char *extra) +{ + int col = num_columns(); + char line[256], ts_str[96]; + int n, ts_len = 0, ts_pos = 0, len = 0, pos = 0; + static size_t last_frame; + + if (channel) { + if (use_color()) { + n = sprintf(ts_str + ts_pos, "%s", COLOR_CHANNEL_LABEL); + if (n > 0) + ts_pos += n; + } + + n = sprintf(ts_str + ts_pos, " {%s}", channel); + if (n > 0) { + ts_pos += n; + ts_len += n; + } + } else if (index != HCI_DEV_NONE && index < MAX_INDEX && + index_list[index].frame != last_frame) { + if (use_color()) { + n = sprintf(ts_str + ts_pos, "%s", COLOR_FRAME_LABEL); + if (n > 0) + ts_pos += n; + } + + n = sprintf(ts_str + ts_pos, " #%zu", index_list[index].frame); + if (n > 0) { + ts_pos += n; + ts_len += n; + } + last_frame = index_list[index].frame; + } + + if ((filter_mask & PACKET_FILTER_SHOW_INDEX) && + index != HCI_DEV_NONE) { + if (use_color()) { + n = sprintf(ts_str + ts_pos, "%s", COLOR_INDEX_LABEL); + if (n > 0) + ts_pos += n; + } + + n = sprintf(ts_str + ts_pos, " [hci%d]", index); + if (n > 0) { + ts_pos += n; + ts_len += n; + } + } + + if (tv) { + time_t t = tv->tv_sec; + struct tm tm; + + localtime_r(&t, &tm); + + if (use_color()) { + n = sprintf(ts_str + ts_pos, "%s", COLOR_TIMESTAMP); + if (n > 0) + ts_pos += n; + } + + if (filter_mask & PACKET_FILTER_SHOW_DATE) { + n = sprintf(ts_str + ts_pos, " %04d-%02d-%02d", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + if (n > 0) { + ts_pos += n; + ts_len += n; + } + } + + if (filter_mask & PACKET_FILTER_SHOW_TIME) { + n = sprintf(ts_str + ts_pos, " %02d:%02d:%02d.%06lu", + tm.tm_hour, tm.tm_min, tm.tm_sec, tv->tv_usec); + if (n > 0) { + ts_pos += n; + ts_len += n; + } + } + + if (filter_mask & PACKET_FILTER_SHOW_TIME_OFFSET) { + n = sprintf(ts_str + ts_pos, " %lu.%06lu", + tv->tv_sec - time_offset, tv->tv_usec); + if (n > 0) { + ts_pos += n; + ts_len += n; + } + } + } + + if (use_color()) { + n = sprintf(ts_str + ts_pos, "%s", COLOR_OFF); + if (n > 0) + ts_pos += n; + } + + if (use_color()) { + n = sprintf(line + pos, "%s", color); + if (n > 0) + pos += n; + } + + n = sprintf(line + pos, "%c %s", ident, label ? label : ""); + if (n > 0) { + pos += n; + len += n; + } + + if (text) { + int extra_len = extra ? strlen(extra) : 0; + int max_len = col - len - extra_len - ts_len - 3; + + n = snprintf(line + pos, max_len + 1, "%s%s", + label ? ": " : "", text); + if (n > max_len) { + line[pos + max_len - 1] = '.'; + line[pos + max_len - 2] = '.'; + if (line[pos + max_len - 3] == ' ') + line[pos + max_len - 3] = '.'; + + n = max_len; + } + + if (n > 0) { + pos += n; + len += n; + } + } + + if (use_color()) { + n = sprintf(line + pos, "%s", COLOR_OFF); + if (n > 0) + pos += n; + } + + if (extra) { + n = sprintf(line + pos, " %s", extra); + if (n > 0) { + pos += n; + len += n; + } + } + + if (ts_len > 0) { + printf("%s", line); + if (len < col) + print_space(col - len - ts_len - 1); + printf("%s%s\n", use_color() ? COLOR_TIMESTAMP : "", ts_str); + } else + printf("%s\n", line); +} + +static const struct { + uint8_t error; + const char *str; +} error2str_table[] = { + { 0x00, "Success" }, + { 0x01, "Unknown HCI Command" }, + { 0x02, "Unknown Connection Identifier" }, + { 0x03, "Hardware Failure" }, + { 0x04, "Page Timeout" }, + { 0x05, "Authentication Failure" }, + { 0x06, "PIN or Key Missing" }, + { 0x07, "Memory Capacity Exceeded" }, + { 0x08, "Connection Timeout" }, + { 0x09, "Connection Limit Exceeded" }, + { 0x0a, "Synchronous Connection Limit to a Device Exceeded" }, + { 0x0b, "ACL Connection Already Exists" }, + { 0x0c, "Command Disallowed" }, + { 0x0d, "Connection Rejected due to Limited Resources" }, + { 0x0e, "Connection Rejected due to Security Reasons" }, + { 0x0f, "Connection Rejected due to Unacceptable BD_ADDR" }, + { 0x10, "Connection Accept Timeout Exceeded" }, + { 0x11, "Unsupported Feature or Parameter Value" }, + { 0x12, "Invalid HCI Command Parameters" }, + { 0x13, "Remote User Terminated Connection" }, + { 0x14, "Remote Device Terminated due to Low Resources" }, + { 0x15, "Remote Device Terminated due to Power Off" }, + { 0x16, "Connection Terminated By Local Host" }, + { 0x17, "Repeated Attempts" }, + { 0x18, "Pairing Not Allowed" }, + { 0x19, "Unknown LMP PDU" }, + { 0x1a, "Unsupported Remote Feature / Unsupported LMP Feature" }, + { 0x1b, "SCO Offset Rejected" }, + { 0x1c, "SCO Interval Rejected" }, + { 0x1d, "SCO Air Mode Rejected" }, + { 0x1e, "Invalid LMP Parameters / Invalid LL Parameters" }, + { 0x1f, "Unspecified Error" }, + { 0x20, "Unsupported LMP Parameter Value / " + "Unsupported LL Parameter Value" }, + { 0x21, "Role Change Not Allowed" }, + { 0x22, "LMP Response Timeout / LL Response Timeout" }, + { 0x23, "LMP Error Transaction Collision" }, + { 0x24, "LMP PDU Not Allowed" }, + { 0x25, "Encryption Mode Not Acceptable" }, + { 0x26, "Link Key cannot be Changed" }, + { 0x27, "Requested QoS Not Supported" }, + { 0x28, "Instant Passed" }, + { 0x29, "Pairing With Unit Key Not Supported" }, + { 0x2a, "Different Transaction Collision" }, + { 0x2b, "Reserved" }, + { 0x2c, "QoS Unacceptable Parameter" }, + { 0x2d, "QoS Rejected" }, + { 0x2e, "Channel Classification Not Supported" }, + { 0x2f, "Insufficient Security" }, + { 0x30, "Parameter Out Of Manadatory Range" }, + { 0x31, "Reserved" }, + { 0x32, "Role Switch Pending" }, + { 0x33, "Reserved" }, + { 0x34, "Reserved Slot Violation" }, + { 0x35, "Role Switch Failed" }, + { 0x36, "Extended Inquiry Response Too Large" }, + { 0x37, "Secure Simple Pairing Not Supported By Host" }, + { 0x38, "Host Busy - Pairing" }, + { 0x39, "Connection Rejected due to No Suitable Channel Found" }, + { 0x3a, "Controller Busy" }, + { 0x3b, "Unacceptable Connection Parameters" }, + { 0x3c, "Advertising Timeout" }, + { 0x3d, "Connection Terminated due to MIC Failure" }, + { 0x3e, "Connection Failed to be Established" }, + { 0x3f, "MAC Connection Failed" }, + { 0x40, "Coarse Clock Adjustment Rejected " + "but Will Try to Adjust Using Clock Dragging" }, + { 0x41, "Type0 Submap Not Defined" }, + { 0x42, "Unknown Advertising Identifier" }, + { 0x43, "Limit Reached" }, + { 0x44, "Operation Cancelled by Host" }, + { } +}; + +static void print_error(const char *label, uint8_t error) +{ + const char *str = "Unknown"; + const char *color_on, *color_off; + bool unknown = true; + int i; + + for (i = 0; error2str_table[i].str; i++) { + if (error2str_table[i].error == error) { + str = error2str_table[i].str; + unknown = false; + break; + } + } + + if (use_color()) { + if (error) { + if (unknown) + color_on = COLOR_UNKNOWN_ERROR; + else + color_on = COLOR_RED; + } else + color_on = COLOR_GREEN; + color_off = COLOR_OFF; + } else { + color_on = ""; + color_off = ""; + } + + print_field("%s: %s%s%s (0x%2.2x)", label, + color_on, str, color_off, error); +} + +static void print_status(uint8_t status) +{ + print_error("Status", status); +} + +static void print_reason(uint8_t reason) +{ + print_error("Reason", reason); +} + +void packet_print_error(const char *label, uint8_t error) +{ + print_error(label, error); +} + +static void print_enable(const char *label, uint8_t enable) +{ + const char *str; + + switch (enable) { + case 0x00: + str = "Disabled"; + break; + case 0x01: + str = "Enabled"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s: %s (0x%2.2x)", label, str, enable); +} + +static void print_addr_type(const char *label, uint8_t addr_type) +{ + const char *str; + + switch (addr_type) { + case 0x00: + str = "Public"; + break; + case 0x01: + str = "Random"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s: %s (0x%2.2x)", label, str, addr_type); +} + +static void print_own_addr_type(uint8_t addr_type) +{ + const char *str; + + switch (addr_type) { + case 0x00: + case 0x02: + str = "Public"; + break; + case 0x01: + case 0x03: + str = "Random"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Own address type: %s (0x%2.2x)", str, addr_type); +} + +static void print_peer_addr_type(const char *label, uint8_t addr_type) +{ + const char *str; + + switch (addr_type) { + case 0x00: + str = "Public"; + break; + case 0x01: + str = "Random"; + break; + case 0x02: + str = "Resolved Public"; + break; + case 0x03: + str = "Resolved Random"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s: %s (0x%2.2x)", label, str, addr_type); +} + +static void print_addr_resolve(const char *label, const uint8_t *addr, + uint8_t addr_type, bool resolve) +{ + const char *str; + char *company; + + switch (addr_type) { + case 0x00: + case 0x02: + if (!hwdb_get_company(addr, &company)) + company = NULL; + + if (company) { + print_field("%s: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X" + " (%s)", label, addr[5], addr[4], + addr[3], addr[2], + addr[1], addr[0], + company); + free(company); + } else { + print_field("%s: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X" + " (OUI %2.2X-%2.2X-%2.2X)", label, + addr[5], addr[4], addr[3], + addr[2], addr[1], addr[0], + addr[5], addr[4], addr[3]); + } + break; + case 0x01: + case 0x03: + switch ((addr[5] & 0xc0) >> 6) { + case 0x00: + str = "Non-Resolvable"; + break; + case 0x01: + str = "Resolvable"; + break; + case 0x03: + str = "Static"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X (%s)", + label, addr[5], addr[4], addr[3], + addr[2], addr[1], addr[0], str); + + if (resolve && (addr[5] & 0xc0) == 0x40) { + uint8_t ident[6], ident_type; + + if (keys_resolve_identity(addr, ident, &ident_type)) { + print_addr_type(" Identity type", ident_type); + print_addr_resolve(" Identity", ident, + ident_type, false); + } + } + break; + default: + print_field("%s: %2.2X-%2.2X-%2.2X-%2.2X-%2.2X-%2.2X", + label, addr[5], addr[4], addr[3], + addr[2], addr[1], addr[0]); + break; + } +} + +static void print_addr(const char *label, const uint8_t *addr, + uint8_t addr_type) +{ + print_addr_resolve(label, addr, addr_type, true); +} + +static void print_bdaddr(const uint8_t *bdaddr) +{ + print_addr("Address", bdaddr, 0x00); +} + +static void print_lt_addr(uint8_t lt_addr) +{ + print_field("LT address: %d", lt_addr); +} + +static void print_handle_native(uint16_t handle) +{ + print_field("Handle: %d", handle); +} + +static void print_handle(uint16_t handle) +{ + print_handle_native(le16_to_cpu(handle)); +} + +static void print_phy_handle(uint8_t phy_handle) +{ + print_field("Physical handle: %d", phy_handle); +} + +static const struct bitfield_data pkt_type_table[] = { + { 1, "2-DH1 may not be used" }, + { 2, "3-DH1 may not be used" }, + { 3, "DM1 may be used" }, + { 4, "DH1 may be used" }, + { 8, "2-DH3 may not be used" }, + { 9, "3-DH3 may not be used" }, + { 10, "DM3 may be used" }, + { 11, "DH3 may be used" }, + { 12, "2-DH5 may not be used" }, + { 13, "3-DH5 may not be used" }, + { 14, "DM5 may be used" }, + { 15, "DH5 may be used" }, + { } +}; + +static void print_pkt_type(uint16_t pkt_type) +{ + uint16_t mask = le16_to_cpu(pkt_type); + + print_field("Packet type: 0x%4.4x", mask); + + mask = print_bitfield(2, mask, pkt_type_table); + if (mask) + print_text(COLOR_UNKNOWN_PKT_TYPE_BIT, + " Unknown packet types (0x%4.4x)", mask); +} + +static const struct bitfield_data pkt_type_sco_table[] = { + { 0, "HV1 may be used" }, + { 1, "HV2 may be used" }, + { 2, "HV3 may be used" }, + { 3, "EV3 may be used" }, + { 4, "EV4 may be used" }, + { 5, "EV5 may be used" }, + { 6, "2-EV3 may not be used" }, + { 7, "3-EV3 may not be used" }, + { 8, "2-EV5 may not be used" }, + { 9, "3-EV5 may not be used" }, + { } +}; + +static void print_pkt_type_sco(uint16_t pkt_type) +{ + uint16_t mask = le16_to_cpu(pkt_type); + + print_field("Packet type: 0x%4.4x", mask); + + mask = print_bitfield(2, mask, pkt_type_sco_table); + if (mask) + print_text(COLOR_UNKNOWN_PKT_TYPE_BIT, + " Unknown packet types (0x%4.4x)", mask); +} + +static void print_iac(const uint8_t *lap) +{ + const char *str = ""; + + if (lap[2] == 0x9e && lap[1] == 0x8b) { + switch (lap[0]) { + case 0x33: + str = " (General Inquiry)"; + break; + case 0x00: + str = " (Limited Inquiry)"; + break; + } + } + + print_field("Access code: 0x%2.2x%2.2x%2.2x%s", + lap[2], lap[1], lap[0], str); +} + +static void print_auth_enable(uint8_t enable) +{ + const char *str; + + switch (enable) { + case 0x00: + str = "Authentication not required"; + break; + case 0x01: + str = "Authentication required for all connections"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Enable: %s (0x%2.2x)", str, enable); +} + +static void print_encrypt_mode(uint8_t mode) +{ + const char *str; + + switch (mode) { + case 0x00: + str = "Encryption not required"; + break; + case 0x01: + str = "Encryption required for all connections"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Mode: %s (0x%2.2x)", str, mode); +} + +static const struct bitfield_data svc_class_table[] = { + { 0, "Positioning (Location identification)" }, + { 1, "Networking (LAN, Ad hoc)" }, + { 2, "Rendering (Printing, Speaker)" }, + { 3, "Capturing (Scanner, Microphone)" }, + { 4, "Object Transfer (v-Inbox, v-Folder)" }, + { 5, "Audio (Speaker, Microphone, Headset)" }, + { 6, "Telephony (Cordless telephony, Modem, Headset)" }, + { 7, "Information (WEB-server, WAP-server)" }, + { } +}; + +static const struct { + uint8_t val; + const char *str; +} major_class_computer_table[] = { + { 0x00, "Uncategorized, code for device not assigned" }, + { 0x01, "Desktop workstation" }, + { 0x02, "Server-class computer" }, + { 0x03, "Laptop" }, + { 0x04, "Handheld PC/PDA (clam shell)" }, + { 0x05, "Palm sized PC/PDA" }, + { 0x06, "Wearable computer (Watch sized)" }, + { 0x07, "Tablet" }, + { } +}; + +static const char *major_class_computer(uint8_t minor) +{ + int i; + + for (i = 0; major_class_computer_table[i].str; i++) { + if (major_class_computer_table[i].val == minor) + return major_class_computer_table[i].str; + } + + return NULL; +} + +static const struct { + uint8_t val; + const char *str; +} major_class_phone_table[] = { + { 0x00, "Uncategorized, code for device not assigned" }, + { 0x01, "Cellular" }, + { 0x02, "Cordless" }, + { 0x03, "Smart phone" }, + { 0x04, "Wired modem or voice gateway" }, + { 0x05, "Common ISDN Access" }, + { } +}; + +static const char *major_class_phone(uint8_t minor) +{ + int i; + + for (i = 0; major_class_phone_table[i].str; i++) { + if (major_class_phone_table[i].val == minor) + return major_class_phone_table[i].str; + } + + return NULL; +} + +static const struct { + uint8_t val; + const char *str; +} major_class_av_table[] = { + { 0x00, "Uncategorized, code for device not assigned" }, + { 0x01, "Wearable Headset Device" }, + { 0x02, "Hands-free Device" }, + { 0x04, "Microphone" }, + { 0x05, "Loudspeaker" }, + { 0x06, "Headphones" }, + { 0x07, "Portable Audio" }, + { 0x08, "Car audio" }, + { 0x09, "Set-top box" }, + { 0x0a, "HiFi Audio Device" }, + { 0x0b, "VCR" }, + { 0x0c, "Video Camera" }, + { 0x0d, "Camcorder" }, + { 0x0e, "Video Monitor" }, + { 0x0f, "Video Display and Loudspeaker" }, + { 0x10, "Video Conferencing" }, + { 0x12, "Gaming/Toy" }, + { } +}; + +static const char *major_class_av(uint8_t minor) +{ + int i; + + for (i = 0; major_class_av_table[i].str; i++) { + if (major_class_av_table[i].val == minor) + return major_class_av_table[i].str; + } + + return NULL; +} + +static const struct { + uint8_t val; + const char *str; +} major_class_wearable_table[] = { + { 0x01, "Wrist Watch" }, + { 0x02, "Pager" }, + { 0x03, "Jacket" }, + { 0x04, "Helmet" }, + { 0x05, "Glasses" }, + { } +}; + +static const char *major_class_wearable(uint8_t minor) +{ + int i; + + for (i = 0; major_class_wearable_table[i].str; i++) { + if (major_class_wearable_table[i].val == minor) + return major_class_wearable_table[i].str; + } + + return NULL; +} + +static const struct { + uint8_t val; + const char *str; + const char *(*func)(uint8_t minor); +} major_class_table[] = { + { 0x00, "Miscellaneous" }, + { 0x01, "Computer (desktop, notebook, PDA, organizers)", + major_class_computer }, + { 0x02, "Phone (cellular, cordless, payphone, modem)", + major_class_phone }, + { 0x03, "LAN /Network Access point" }, + { 0x04, "Audio/Video (headset, speaker, stereo, video, vcr)", + major_class_av }, + { 0x05, "Peripheral (mouse, joystick, keyboards)" }, + { 0x06, "Imaging (printing, scanner, camera, display)" }, + { 0x07, "Wearable", major_class_wearable }, + { 0x08, "Toy" }, + { 0x09, "Health" }, + { 0x1f, "Uncategorized, specific device code not specified" }, + { } +}; + +static void print_dev_class(const uint8_t *dev_class) +{ + uint8_t mask, major_cls, minor_cls; + const char *major_str = NULL; + const char *minor_str = NULL; + int i; + + print_field("Class: 0x%2.2x%2.2x%2.2x", + dev_class[2], dev_class[1], dev_class[0]); + + if ((dev_class[0] & 0x03) != 0x00) { + print_field(" Format type: 0x%2.2x", dev_class[0] & 0x03); + print_text(COLOR_ERROR, " invalid format type"); + return; + } + + major_cls = dev_class[1] & 0x1f; + minor_cls = (dev_class[0] & 0xfc) >> 2; + + for (i = 0; major_class_table[i].str; i++) { + if (major_class_table[i].val == major_cls) { + major_str = major_class_table[i].str; + + if (!major_class_table[i].func) + break; + + minor_str = major_class_table[i].func(minor_cls); + break; + } + } + + if (major_str) { + print_field(" Major class: %s", major_str); + if (minor_str) + print_field(" Minor class: %s", minor_str); + else + print_field(" Minor class: 0x%2.2x", minor_cls); + } else { + print_field(" Major class: 0x%2.2x", major_cls); + print_field(" Minor class: 0x%2.2x", minor_cls); + } + + if (dev_class[1] & 0x20) + print_field(" Limited Discoverable Mode"); + + if ((dev_class[1] & 0xc0) != 0x00) { + print_text(COLOR_ERROR, " invalid service class"); + return; + } + + mask = print_bitfield(2, dev_class[2], svc_class_table); + if (mask) + print_text(COLOR_UNKNOWN_SERVICE_CLASS, + " Unknown service class (0x%2.2x)", mask); +} + +static void print_appearance(uint16_t appearance) +{ + print_field("Appearance: %s (0x%4.4x)", bt_appear_to_str(appearance), + appearance); +} + +static void print_num_broadcast_retrans(uint8_t num_retrans) +{ + print_field("Number of broadcast retransmissions: %u", num_retrans); +} + +static void print_hold_mode_activity(uint8_t activity) +{ + print_field("Activity: 0x%2.2x", activity); + + if (activity == 0x00) { + print_field(" Maintain current Power State"); + return; + } + + if (activity & 0x01) + print_field(" Suspend Page Scan"); + if (activity & 0x02) + print_field(" Suspend Inquiry Scan"); + if (activity & 0x04) + print_field(" Suspend Periodic Inquiries"); +} + +static void print_power_type(uint8_t type) +{ + const char *str; + + switch (type) { + case 0x00: + str = "Current Transmit Power Level"; + break; + case 0x01: + str = "Maximum Transmit Power Level"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, type); +} + +static void print_power_level(int8_t level, const char *type) +{ + print_field("TX power%s%s%s: %d dbm (0x%2.2x)", + type ? " (" : "", type ? type : "", type ? ")" : "", + level, (uint8_t) level); +} + +static void print_host_flow_control(uint8_t enable) +{ + const char *str; + + switch (enable) { + case 0x00: + str = "Off"; + break; + case 0x01: + str = "ACL Data Packets"; + break; + case 0x02: + str = "Synchronous Data Packets"; + break; + case 0x03: + str = "ACL and Synchronous Data Packets"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Flow control: %s (0x%2.2x)", str, enable); +} + +static void print_voice_setting(uint16_t setting) +{ + uint8_t input_coding = (le16_to_cpu(setting) & 0x0300) >> 8; + uint8_t input_data_format = (le16_to_cpu(setting) & 0xc0) >> 6; + uint8_t air_coding_format = le16_to_cpu(setting) & 0x0003; + const char *str; + + print_field("Setting: 0x%4.4x", le16_to_cpu(setting)); + + switch (input_coding) { + case 0x00: + str = "Linear"; + break; + case 0x01: + str = "u-law"; + break; + case 0x02: + str = "A-law"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" Input Coding: %s", str); + + switch (input_data_format) { + case 0x00: + str = "1's complement"; + break; + case 0x01: + str = "2's complement"; + break; + case 0x02: + str = "Sign-Magnitude"; + break; + case 0x03: + str = "Unsigned"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" Input Data Format: %s", str); + + if (input_coding == 0x00) { + print_field(" Input Sample Size: %s", + le16_to_cpu(setting) & 0x20 ? "16-bit" : "8-bit"); + print_field(" # of bits padding at MSB: %d", + (le16_to_cpu(setting) & 0x1c) >> 2); + } + + switch (air_coding_format) { + case 0x00: + str = "CVSD"; + break; + case 0x01: + str = "u-law"; + break; + case 0x02: + str = "A-law"; + break; + case 0x03: + str = "Transparent Data"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" Air Coding Format: %s", str); +} + +static void print_retransmission_effort(uint8_t effort) +{ + const char *str; + + switch (effort) { + case 0x00: + str = "No retransmissions"; + break; + case 0x01: + str = "Optimize for power consumption"; + break; + case 0x02: + str = "Optimize for link quality"; + break; + case 0xff: + str = "Don't care"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Retransmission effort: %s (0x%2.2x)", str, effort); +} + +static void print_scan_enable(uint8_t scan_enable) +{ + const char *str; + + switch (scan_enable) { + case 0x00: + str = "No Scans"; + break; + case 0x01: + str = "Inquiry Scan"; + break; + case 0x02: + str = "Page Scan"; + break; + case 0x03: + str = "Inquiry Scan + Page Scan"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Scan enable: %s (0x%2.2x)", str, scan_enable); +} + +static void print_link_policy(uint16_t link_policy) +{ + uint16_t policy = le16_to_cpu(link_policy); + + print_field("Link policy: 0x%4.4x", policy); + + if (policy == 0x0000) { + print_field(" Disable All Modes"); + return; + } + + if (policy & 0x0001) + print_field(" Enable Role Switch"); + if (policy & 0x0002) + print_field(" Enable Hold Mode"); + if (policy & 0x0004) + print_field(" Enable Sniff Mode"); + if (policy & 0x0008) + print_field(" Enable Park State"); +} + +static void print_air_mode(uint8_t mode) +{ + const char *str; + + switch (mode) { + case 0x00: + str = "u-law log"; + break; + case 0x01: + str = "A-law log"; + break; + case 0x02: + str = "CVSD"; + break; + case 0x03: + str = "Transparent"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Air mode: %s (0x%2.2x)", str, mode); +} + +static void print_codec(const char *label, uint8_t codec) +{ + const char *str; + + switch (codec) { + case 0x00: + str = "u-law log"; + break; + case 0x01: + str = "A-law log"; + break; + case 0x02: + str = "CVSD"; + break; + case 0x03: + str = "Transparent"; + break; + case 0x04: + str = "Linear PCM"; + break; + case 0x05: + str = "mSBC"; + break; + case 0xff: + str = "Vendor specific"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s: %s (0x%2.2x)", label, str, codec); +} + +static void print_inquiry_mode(uint8_t mode) +{ + const char *str; + + switch (mode) { + case 0x00: + str = "Standard Inquiry Result"; + break; + case 0x01: + str = "Inquiry Result with RSSI"; + break; + case 0x02: + str = "Inquiry Result with RSSI or Extended Inquiry Result"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Mode: %s (0x%2.2x)", str, mode); +} + +static void print_inquiry_scan_type(uint8_t type) +{ + const char *str; + + switch (type) { + case 0x00: + str = "Standard Scan"; + break; + case 0x01: + str = "Interlaced Scan"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, type); +} + +static void print_pscan_type(uint8_t type) +{ + const char *str; + + switch (type) { + case 0x00: + str = "Standard Scan"; + break; + case 0x01: + str = "Interlaced Scan"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, type); +} + +static void print_loopback_mode(uint8_t mode) +{ + const char *str; + + switch (mode) { + case 0x00: + str = "No Loopback"; + break; + case 0x01: + str = "Local Loopback"; + break; + case 0x02: + str = "Remote Loopback"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Mode: %s (0x%2.2x)", str, mode); +} + +static void print_auth_payload_timeout(uint16_t timeout) +{ + print_field("Timeout: %d msec (0x%4.4x)", + le16_to_cpu(timeout) * 10, le16_to_cpu(timeout)); +} + +static void print_pscan_rep_mode(uint8_t pscan_rep_mode) +{ + const char *str; + + switch (pscan_rep_mode) { + case 0x00: + str = "R0"; + break; + case 0x01: + str = "R1"; + break; + case 0x02: + str = "R2"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Page scan repetition mode: %s (0x%2.2x)", + str, pscan_rep_mode); +} + +static void print_pscan_period_mode(uint8_t pscan_period_mode) +{ + const char *str; + + switch (pscan_period_mode) { + case 0x00: + str = "P0"; + break; + case 0x01: + str = "P1"; + break; + case 0x02: + str = "P2"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Page period mode: %s (0x%2.2x)", str, pscan_period_mode); +} + +static void print_pscan_mode(uint8_t pscan_mode) +{ + const char *str; + + switch (pscan_mode) { + case 0x00: + str = "Mandatory"; + break; + case 0x01: + str = "Optional I"; + break; + case 0x02: + str = "Optional II"; + break; + case 0x03: + str = "Optional III"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Page scan mode: %s (0x%2.2x)", str, pscan_mode); +} + +static void print_clock_offset(uint16_t clock_offset) +{ + print_field("Clock offset: 0x%4.4x", le16_to_cpu(clock_offset)); +} + +static void print_clock(uint32_t clock) +{ + print_field("Clock: 0x%8.8x", le32_to_cpu(clock)); +} + +static void print_clock_type(uint8_t type) +{ + const char *str; + + switch (type) { + case 0x00: + str = "Local clock"; + break; + case 0x01: + str = "Piconet clock"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, type); +} + +static void print_clock_accuracy(uint16_t accuracy) +{ + if (le16_to_cpu(accuracy) == 0xffff) + print_field("Accuracy: Unknown (0x%4.4x)", + le16_to_cpu(accuracy)); + else + print_field("Accuracy: %.4f msec (0x%4.4x)", + le16_to_cpu(accuracy) * 0.3125, + le16_to_cpu(accuracy)); +} + +static void print_lpo_allowed(uint8_t lpo_allowed) +{ + print_field("LPO allowed: 0x%2.2x", lpo_allowed); +} + +static void print_broadcast_fragment(uint8_t fragment) +{ + const char *str; + + switch (fragment) { + case 0x00: + str = "Continuation fragment"; + break; + case 0x01: + str = "Starting fragment"; + break; + case 0x02: + str = "Ending fragment"; + break; + case 0x03: + str = "No fragmentation"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Fragment: %s (0x%2.2x)", str, fragment); +} + +static void print_link_type(uint8_t link_type) +{ + const char *str; + + switch (link_type) { + case 0x00: + str = "SCO"; + break; + case 0x01: + str = "ACL"; + break; + case 0x02: + str = "eSCO"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Link type: %s (0x%2.2x)", str, link_type); +} + +static void print_encr_mode_change(uint8_t encr_mode, uint16_t handle) +{ + const char *str; + uint8_t conn_type; + + conn_type = get_type(le16_to_cpu(handle)); + + switch (encr_mode) { + case 0x00: + str = "Disabled"; + break; + case 0x01: + switch (conn_type) { + case 0x00: + str = "Enabled with E0"; + break; + case 0x01: + str = "Enabled with AES-CCM"; + break; + default: + str = "Enabled"; + break; + } + break; + case 0x02: + str = "Enabled with AES-CCM"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Encryption: %s (0x%2.2x)", str, encr_mode); +} + +static void print_pin_type(uint8_t pin_type) +{ + const char *str; + + switch (pin_type) { + case 0x00: + str = "Variable"; + break; + case 0x01: + str = "Fixed"; + break; + default: + str = "Reserved"; + break; + } + + print_field("PIN type: %s (0x%2.2x)", str, pin_type); +} + +static void print_key_flag(uint8_t key_flag) +{ + const char *str; + + switch (key_flag) { + case 0x00: + str = "Semi-permanent"; + break; + case 0x01: + str = "Temporary"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Key flag: %s (0x%2.2x)", str, key_flag); +} + +static void print_key_len(uint8_t key_len) +{ + const char *str; + + switch (key_len) { + case 32: + str = "802.11 PAL"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Key length: %s (%d)", str, key_len); +} + +static void print_key_type(uint8_t key_type) +{ + const char *str; + + switch (key_type) { + case 0x00: + str = "Combination key"; + break; + case 0x01: + str = "Local Unit key"; + break; + case 0x02: + str = "Remote Unit key"; + break; + case 0x03: + str = "Debug Combination key"; + break; + case 0x04: + str = "Unauthenticated Combination key from P-192"; + break; + case 0x05: + str = "Authenticated Combination key from P-192"; + break; + case 0x06: + str = "Changed Combination key"; + break; + case 0x07: + str = "Unauthenticated Combination key from P-256"; + break; + case 0x08: + str = "Authenticated Combination key from P-256"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Key type: %s (0x%2.2x)", str, key_type); +} + +static void print_key_size(uint8_t key_size) +{ + print_field("Key size: %d", key_size); +} + +static void print_hex_field(const char *label, const uint8_t *data, + uint8_t len) +{ + char str[len * 2 + 1]; + uint8_t i; + + str[0] = '\0'; + + for (i = 0; i < len; i++) + sprintf(str + (i * 2), "%2.2x", data[i]); + + print_field("%s: %s", label, str); +} + +static void print_key(const char *label, const uint8_t *link_key) +{ + print_hex_field(label, link_key, 16); +} + +static void print_link_key(const uint8_t *link_key) +{ + print_key("Link key", link_key); +} + +static void print_pin_code(const uint8_t *pin_code, uint8_t pin_len) +{ + char str[pin_len + 1]; + uint8_t i; + + for (i = 0; i < pin_len; i++) + sprintf(str + i, "%c", (const char) pin_code[i]); + + print_field("PIN code: %s", str); +} + +static void print_hash_p192(const uint8_t *hash) +{ + print_key("Hash C from P-192", hash); +} + +static void print_hash_p256(const uint8_t *hash) +{ + print_key("Hash C from P-256", hash); +} + +static void print_randomizer_p192(const uint8_t *randomizer) +{ + print_key("Randomizer R with P-192", randomizer); +} + +static void print_randomizer_p256(const uint8_t *randomizer) +{ + print_key("Randomizer R with P-256", randomizer); +} + +static void print_pk256(const char *label, const uint8_t *key) +{ + print_field("%s:", label); + print_hex_field(" X", &key[0], 32); + print_hex_field(" Y", &key[32], 32); +} + +static void print_dhkey(const uint8_t *dhkey) +{ + print_hex_field("Diffie-Hellman key", dhkey, 32); +} + +static void print_passkey(uint32_t passkey) +{ + print_field("Passkey: %06d", le32_to_cpu(passkey)); +} + +static void print_io_capability(uint8_t capability) +{ + const char *str; + + switch (capability) { + case 0x00: + str = "DisplayOnly"; + break; + case 0x01: + str = "DisplayYesNo"; + break; + case 0x02: + str = "KeyboardOnly"; + break; + case 0x03: + str = "NoInputNoOutput"; + break; + default: + str = "Reserved"; + break; + } + + print_field("IO capability: %s (0x%2.2x)", str, capability); +} + +static void print_oob_data(uint8_t oob_data) +{ + const char *str; + + switch (oob_data) { + case 0x00: + str = "Authentication data not present"; + break; + case 0x01: + str = "P-192 authentication data present"; + break; + case 0x02: + str = "P-256 authentication data present"; + break; + case 0x03: + str = "P-192 and P-256 authentication data present"; + break; + default: + str = "Reserved"; + break; + } + + print_field("OOB data: %s (0x%2.2x)", str, oob_data); +} + +static void print_oob_data_response(uint8_t oob_data) +{ + const char *str; + + switch (oob_data) { + case 0x00: + str = "Authentication data not present"; + break; + case 0x01: + str = "Authentication data present"; + break; + default: + str = "Reserved"; + break; + } + + print_field("OOB data: %s (0x%2.2x)", str, oob_data); +} + +static void print_authentication(uint8_t authentication) +{ + const char *str; + + switch (authentication) { + case 0x00: + str = "No Bonding - MITM not required"; + break; + case 0x01: + str = "No Bonding - MITM required"; + break; + case 0x02: + str = "Dedicated Bonding - MITM not required"; + break; + case 0x03: + str = "Dedicated Bonding - MITM required"; + break; + case 0x04: + str = "General Bonding - MITM not required"; + break; + case 0x05: + str = "General Bonding - MITM required"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Authentication: %s (0x%2.2x)", str, authentication); +} + +void packet_print_io_capability(uint8_t capability) +{ + print_io_capability(capability); +} + +void packet_print_io_authentication(uint8_t authentication) +{ + print_authentication(authentication); +} + +static void print_location_domain_aware(uint8_t aware) +{ + const char *str; + + switch (aware) { + case 0x00: + str = "Regulatory domain unknown"; + break; + case 0x01: + str = "Regulatory domain known"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Domain aware: %s (0x%2.2x)", str, aware); +} + +static void print_location_domain(const uint8_t *domain) +{ + print_field("Domain: %c%c (0x%2.2x%2.2x)", + (char) domain[0], (char) domain[1], domain[0], domain[1]); +} + +static void print_location_domain_options(uint8_t options) +{ + print_field("Domain options: %c (0x%2.2x)", (char) options, options); +} + +static void print_location_options(uint8_t options) +{ + print_field("Options: 0x%2.2x", options); +} + +static void print_flow_control_mode(uint8_t mode) +{ + const char *str; + + switch (mode) { + case 0x00: + str = "Packet based"; + break; + case 0x01: + str = "Data block based"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Flow control mode: %s (0x%2.2x)", str, mode); +} + +static void print_flow_direction(uint8_t direction) +{ + const char *str; + + switch (direction) { + case 0x00: + str = "Outgoing"; + break; + case 0x01: + str = "Incoming"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Flow direction: %s (0x%2.2x)", str, direction); +} + +static void print_service_type(uint8_t service_type) +{ + const char *str; + + switch (service_type) { + case 0x00: + str = "No Traffic"; + break; + case 0x01: + str = "Best Effort"; + break; + case 0x02: + str = "Guaranteed"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Service type: %s (0x%2.2x)", str, service_type); +} + +static void print_flow_spec(const char *label, const uint8_t *data) +{ + const char *str; + + switch (data[1]) { + case 0x00: + str = "No traffic"; + break; + case 0x01: + str = "Best effort"; + break; + case 0x02: + str = "Guaranteed"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s flow spec: 0x%2.2x", label, data[0]); + print_field(" Service type: %s (0x%2.2x)", str, data[1]); + print_field(" Maximum SDU size: 0x%4.4x", get_le16(data + 2)); + print_field(" SDU inter-arrival time: 0x%8.8x", get_le32(data + 4)); + print_field(" Access latency: 0x%8.8x", get_le32(data + 8)); + print_field(" Flush timeout: 0x%8.8x", get_le32(data + 12)); +} + +static void print_amp_status(uint8_t amp_status) +{ + const char *str; + + switch (amp_status) { + case 0x00: + str = "Present"; + break; + case 0x01: + str = "Bluetooth only"; + break; + case 0x02: + str = "No capacity"; + break; + case 0x03: + str = "Low capacity"; + break; + case 0x04: + str = "Medium capacity"; + break; + case 0x05: + str = "High capacity"; + break; + case 0x06: + str = "Full capacity"; + break; + default: + str = "Reserved"; + break; + } + + print_field("AMP status: %s (0x%2.2x)", str, amp_status); +} + +static void print_num_resp(uint8_t num_resp) +{ + print_field("Num responses: %d", num_resp); +} + +static void print_num_reports(uint8_t num_reports) +{ + print_field("Num reports: %d", num_reports); +} + +static void print_adv_event_type(const char *label, uint8_t type) +{ + const char *str; + + switch (type) { + case 0x00: + str = "Connectable undirected - ADV_IND"; + break; + case 0x01: + str = "Connectable directed - ADV_DIRECT_IND"; + break; + case 0x02: + str = "Scannable undirected - ADV_SCAN_IND"; + break; + case 0x03: + str = "Non connectable undirected - ADV_NONCONN_IND"; + break; + case 0x04: + str = "Scan response - SCAN_RSP"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s: %s (0x%2.2x)", label, str, type); +} + +static void print_adv_channel_map(const char *label, uint8_t value) +{ + const char *str; + + switch (value) { + case 0x01: + str = "37"; + break; + case 0x02: + str = "38"; + break; + case 0x03: + str = "37, 38"; + break; + case 0x04: + str = "39"; + break; + case 0x05: + str = "37, 39"; + break; + case 0x06: + str = "38, 39"; + break; + case 0x07: + str = "37, 38, 39"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s: %s (0x%2.2x)", label, str, value); +} + +static void print_adv_filter_policy(const char *label, uint8_t value) +{ + const char *str; + + switch (value) { + case 0x00: + str = "Allow Scan Request from Any, " + "Allow Connect Request from Any"; + break; + case 0x01: + str = "Allow Scan Request from White List Only, " + "Allow Connect Request from Any"; + break; + case 0x02: + str = "Allow Scan Request from Any, " + "Allow Connect Request from White List Only"; + break; + case 0x03: + str = "Allow Scan Request from White List Only, " + "Allow Connect Request from White List Only"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s: %s (0x%2.2x)", label, str, value); +} + +static void print_rssi(int8_t rssi) +{ + if ((uint8_t) rssi == 0x99 || rssi == 127) + print_field("RSSI: invalid (0x%2.2x)", (uint8_t) rssi); + else + print_field("RSSI: %d dBm (0x%2.2x)", rssi, (uint8_t) rssi); +} + +static void print_slot_625(const char *label, uint16_t value) +{ + print_field("%s: %.3f msec (0x%4.4x)", label, + le16_to_cpu(value) * 0.625, le16_to_cpu(value)); +} + +static void print_slot_125(const char *label, uint16_t value) +{ + print_field("%s: %.2f msec (0x%4.4x)", label, + le16_to_cpu(value) * 1.25, le16_to_cpu(value)); +} + +static void print_timeout(uint16_t timeout) +{ + print_slot_625("Timeout", timeout); +} + +static void print_interval(uint16_t interval) +{ + print_slot_625("Interval", interval); +} + +static void print_window(uint16_t window) +{ + print_slot_625("Window", window); +} + +static void print_conn_latency(const char *label, uint16_t value) +{ + print_field("%s: %u (0x%4.4x)", label, le16_to_cpu(value), + le16_to_cpu(value)); +} + +static void print_role(uint8_t role) +{ + const char *str; + + switch (role) { + case 0x00: + str = "Master"; + break; + case 0x01: + str = "Slave"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Role: %s (0x%2.2x)", str, role); +} + +static void print_mode(uint8_t mode) +{ + const char *str; + + switch (mode) { + case 0x00: + str = "Active"; + break; + case 0x01: + str = "Hold"; + break; + case 0x02: + str = "Sniff"; + break; + case 0x03: + str = "Park"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Mode: %s (0x%2.2x)", str, mode); +} + +static void print_name(const uint8_t *name) +{ + char str[249]; + + memcpy(str, name, 248); + str[248] = '\0'; + + print_field("Name: %s", str); +} + +static void print_channel_map(const uint8_t *map) +{ + unsigned int count = 0, start = 0; + char str[21]; + int i, n; + + for (i = 0; i < 10; i++) + sprintf(str + (i * 2), "%2.2x", map[i]); + + print_field("Channel map: 0x%s", str); + + for (i = 0; i < 10; i++) { + for (n = 0; n < 8; n++) { + if (map[i] & (1 << n)) { + if (count == 0) + start = (i * 8) + n; + count++; + continue; + } + + if (count > 1) { + print_field(" Channel %u-%u", + start, start + count - 1); + count = 0; + } else if (count > 0) { + print_field(" Channel %u", start); + count = 0; + } + } + } +} + +void packet_print_channel_map_lmp(const uint8_t *map) +{ + print_channel_map(map); +} + +static void print_flush_timeout(uint16_t timeout) +{ + if (timeout) + print_timeout(timeout); + else + print_field("Timeout: No Automatic Flush"); +} + +void packet_print_version(const char *label, uint8_t version, + const char *sublabel, uint16_t subversion) +{ + const char *str; + + switch (version) { + case 0x00: + str = "Bluetooth 1.0b"; + break; + case 0x01: + str = "Bluetooth 1.1"; + break; + case 0x02: + str = "Bluetooth 1.2"; + break; + case 0x03: + str = "Bluetooth 2.0"; + break; + case 0x04: + str = "Bluetooth 2.1"; + break; + case 0x05: + str = "Bluetooth 3.0"; + break; + case 0x06: + str = "Bluetooth 4.0"; + break; + case 0x07: + str = "Bluetooth 4.1"; + break; + case 0x08: + str = "Bluetooth 4.2"; + break; + case 0x09: + str = "Bluetooth 5.0"; + break; + case 0x0a: + str = "Bluetooth 5.1"; + break; + default: + str = "Reserved"; + break; + } + + if (sublabel) + print_field("%s: %s (0x%2.2x) - %s %d (0x%4.4x)", + label, str, version, + sublabel, subversion, subversion); + else + print_field("%s: %s (0x%2.2x)", label, str, version); +} + +static void print_hci_version(uint8_t version, uint16_t revision) +{ + packet_print_version("HCI version", version, + "Revision", le16_to_cpu(revision)); +} + +static void print_lmp_version(uint8_t version, uint16_t subversion) +{ + packet_print_version("LMP version", version, + "Subversion", le16_to_cpu(subversion)); +} + +static void print_pal_version(uint8_t version, uint16_t subversion) +{ + const char *str; + + switch (version) { + case 0x01: + str = "Bluetooth 3.0"; + break; + default: + str = "Reserved"; + break; + } + + print_field("PAL version: %s (0x%2.2x) - Subversion %d (0x%4.4x)", + str, version, + le16_to_cpu(subversion), + le16_to_cpu(subversion)); +} + +void packet_print_company(const char *label, uint16_t company) +{ + print_field("%s: %s (%d)", label, bt_compidtostr(company), company); +} + +static void print_manufacturer(uint16_t manufacturer) +{ + packet_print_company("Manufacturer", le16_to_cpu(manufacturer)); +} + +static const struct { + uint16_t ver; + const char *str; +} broadcom_uart_subversion_table[] = { + { 0x210b, "BCM43142A0" }, /* 001.001.011 */ + { 0x410e, "BCM43341B0" }, /* 002.001.014 */ + { 0x4406, "BCM4324B3" }, /* 002.004.006 */ + { } +}; + +static const struct { + uint16_t ver; + const char *str; +} broadcom_usb_subversion_table[] = { + { 0x210b, "BCM43142A0" }, /* 001.001.011 */ + { 0x2112, "BCM4314A0" }, /* 001.001.018 */ + { 0x2118, "BCM20702A0" }, /* 001.001.024 */ + { 0x2126, "BCM4335A0" }, /* 001.001.038 */ + { 0x220e, "BCM20702A1" }, /* 001.002.014 */ + { 0x230f, "BCM4354A2" }, /* 001.003.015 */ + { 0x4106, "BCM4335B0" }, /* 002.001.006 */ + { 0x410e, "BCM20702B0" }, /* 002.001.014 */ + { 0x6109, "BCM4335C0" }, /* 003.001.009 */ + { 0x610c, "BCM4354" }, /* 003.001.012 */ + { } +}; + +static void print_manufacturer_broadcom(uint16_t subversion, uint16_t revision) +{ + uint16_t ver = le16_to_cpu(subversion); + uint16_t rev = le16_to_cpu(revision); + const char *str = NULL; + int i; + + switch ((rev & 0xf000) >> 12) { + case 0: + case 3: + for (i = 0; broadcom_uart_subversion_table[i].str; i++) { + if (broadcom_uart_subversion_table[i].ver == ver) { + str = broadcom_uart_subversion_table[i].str; + break; + } + } + break; + case 1: + case 2: + for (i = 0; broadcom_usb_subversion_table[i].str; i++) { + if (broadcom_usb_subversion_table[i].ver == ver) { + str = broadcom_usb_subversion_table[i].str; + break; + } + } + break; + } + + if (str) + print_field(" Firmware: %3.3u.%3.3u.%3.3u (%s)", + (ver & 0xe000) >> 13, + (ver & 0x1f00) >> 8, ver & 0x00ff, str); + else + print_field(" Firmware: %3.3u.%3.3u.%3.3u", + (ver & 0xe000) >> 13, + (ver & 0x1f00) >> 8, ver & 0x00ff); + + if (rev != 0xffff) + print_field(" Build: %4.4u", rev & 0x0fff); +} + +static const char *get_supported_command(int bit); + +static void print_commands(const uint8_t *commands) +{ + unsigned int count = 0; + int i, n; + + for (i = 0; i < 64; i++) { + for (n = 0; n < 8; n++) { + if (commands[i] & (1 << n)) + count++; + } + } + + print_field("Commands: %u entr%s", count, count == 1 ? "y" : "ies"); + + for (i = 0; i < 64; i++) { + for (n = 0; n < 8; n++) { + const char *cmd; + + if (!(commands[i] & (1 << n))) + continue; + + cmd = get_supported_command((i * 8) + n); + if (cmd) + print_field(" %s (Octet %d - Bit %d)", + cmd, i, n); + else + print_text(COLOR_UNKNOWN_COMMAND_BIT, + " Octet %d - Bit %d ", i, n); + } + } +} + +static const struct bitfield_data features_page0[] = { + { 0, "3 slot packets" }, + { 1, "5 slot packets" }, + { 2, "Encryption" }, + { 3, "Slot offset" }, + { 4, "Timing accuracy" }, + { 5, "Role switch" }, + { 6, "Hold mode" }, + { 7, "Sniff mode" }, + { 8, "Park state" }, + { 9, "Power control requests" }, + { 10, "Channel quality driven data rate (CQDDR)"}, + { 11, "SCO link" }, + { 12, "HV2 packets" }, + { 13, "HV3 packets" }, + { 14, "u-law log synchronous data" }, + { 15, "A-law log synchronous data" }, + { 16, "CVSD synchronous data" }, + { 17, "Paging parameter negotiation" }, + { 18, "Power control" }, + { 19, "Transparent synchronous data" }, + { 20, "Flow control lag (least significant bit)"}, + { 21, "Flow control lag (middle bit)" }, + { 22, "Flow control lag (most significant bit)" }, + { 23, "Broadcast Encryption" }, + { 25, "Enhanced Data Rate ACL 2 Mbps mode" }, + { 26, "Enhanced Data Rate ACL 3 Mbps mode" }, + { 27, "Enhanced inquiry scan" }, + { 28, "Interlaced inquiry scan" }, + { 29, "Interlaced page scan" }, + { 30, "RSSI with inquiry results" }, + { 31, "Extended SCO link (EV3 packets)" }, + { 32, "EV4 packets" }, + { 33, "EV5 packets" }, + { 35, "AFH capable slave" }, + { 36, "AFH classification slave" }, + { 37, "BR/EDR Not Supported" }, + { 38, "LE Supported (Controller)" }, + { 39, "3-slot Enhanced Data Rate ACL packets" }, + { 40, "5-slot Enhanced Data Rate ACL packets" }, + { 41, "Sniff subrating" }, + { 42, "Pause encryption" }, + { 43, "AFH capable master" }, + { 44, "AFH classification master" }, + { 45, "Enhanced Data Rate eSCO 2 Mbps mode" }, + { 46, "Enhanced Data Rate eSCO 3 Mbps mode" }, + { 47, "3-slot Enhanced Data Rate eSCO packets" }, + { 48, "Extended Inquiry Response" }, + { 49, "Simultaneous LE and BR/EDR (Controller)" }, + { 51, "Secure Simple Pairing" }, + { 52, "Encapsulated PDU" }, + { 53, "Erroneous Data Reporting" }, + { 54, "Non-flushable Packet Boundary Flag" }, + { 56, "Link Supervision Timeout Changed Event" }, + { 57, "Inquiry TX Power Level" }, + { 58, "Enhanced Power Control" }, + { 63, "Extended features" }, + { } +}; + +static const struct bitfield_data features_page1[] = { + { 0, "Secure Simple Pairing (Host Support)" }, + { 1, "LE Supported (Host)" }, + { 2, "Simultaneous LE and BR/EDR (Host)" }, + { 3, "Secure Connections (Host Support)" }, + { } +}; + +static const struct bitfield_data features_page2[] = { + { 0, "Connectionless Slave Broadcast - Master" }, + { 1, "Connectionless Slave Broadcast - Slave" }, + { 2, "Synchronization Train" }, + { 3, "Synchronization Scan" }, + { 4, "Inquiry Response Notification Event" }, + { 5, "Generalized interlaced scan" }, + { 6, "Coarse Clock Adjustment" }, + { 8, "Secure Connections (Controller Support)" }, + { 9, "Ping" }, + { 10, "Slot Availability Mask" }, + { 11, "Train nudging" }, + { } +}; + +static const struct bitfield_data features_le[] = { + { 0, "LE Encryption" }, + { 1, "Connection Parameter Request Procedure" }, + { 2, "Extended Reject Indication" }, + { 3, "Slave-initiated Features Exchange" }, + { 4, "LE Ping" }, + { 5, "LE Data Packet Length Extension" }, + { 6, "LL Privacy" }, + { 7, "Extended Scanner Filter Policies" }, + { 8, "LE 2M PHY" }, + { 9, "Stable Modulation Index - Transmitter" }, + { 10, "Stable Modulation Index - Receiver" }, + { 11, "LE Coded PHY" }, + { 12, "LE Extended Advertising" }, + { 13, "LE Periodic Advertising" }, + { 14, "Channel Selection Algorithm #2" }, + { 15, "LE Power Class 1" }, + { 16, "Minimum Number of Used Channels Procedure" }, + { 17, "Connection CTE Request" }, + { 18, "Connection CTE Response" }, + { 19, "Connectionless CTE Transmitter" }, + { 20, "Connectionless CTE Receiver" }, + { 21, "Antenna Switching During CTE Transmission (AoD)" }, + { 22, "Antenna Switching During CTE Reception (AoA)" }, + { 23, "Receiving Constant Tone Extensions" }, + { 24, "Periodic Advertising Sync Transfer - Sender" }, + { 25, "Periodic Advertising Sync Transfer - Recipient" }, + { 26, "Sleep Clock Accuracy Updates" }, + { 27, "Remote Public Key Validation" }, + { } +}; + +static void print_features(uint8_t page, const uint8_t *features_array, + uint8_t type) +{ + const struct bitfield_data *features_table = NULL; + uint64_t mask, features = 0; + char str[41]; + int i; + + for (i = 0; i < 8; i++) { + sprintf(str + (i * 5), " 0x%2.2x", features_array[i]); + features |= ((uint64_t) features_array[i]) << (i * 8); + } + + print_field("Features:%s", str); + + switch (type) { + case 0x00: + switch (page) { + case 0: + features_table = features_page0; + break; + case 1: + features_table = features_page1; + break; + case 2: + features_table = features_page2; + break; + } + break; + case 0x01: + switch (page) { + case 0: + features_table = features_le; + break; + } + break; + } + + if (!features_table) + return; + + mask = print_bitfield(2, features, features_table); + if (mask) + print_text(COLOR_UNKNOWN_FEATURE_BIT, " Unknown features " + "(0x%16.16" PRIx64 ")", mask); +} + +void packet_print_features_lmp(const uint8_t *features, uint8_t page) +{ + print_features(page, features, 0x00); +} + +void packet_print_features_ll(const uint8_t *features) +{ + print_features(0, features, 0x01); +} + +#define LE_STATE_SCAN_ADV 0x0001 +#define LE_STATE_CONN_ADV 0x0002 +#define LE_STATE_NONCONN_ADV 0x0004 +#define LE_STATE_HIGH_DIRECT_ADV 0x0008 +#define LE_STATE_LOW_DIRECT_ADV 0x0010 +#define LE_STATE_ACTIVE_SCAN 0x0020 +#define LE_STATE_PASSIVE_SCAN 0x0040 +#define LE_STATE_INITIATING 0x0080 +#define LE_STATE_CONN_MASTER 0x0100 +#define LE_STATE_CONN_SLAVE 0x0200 +#define LE_STATE_MASTER_MASTER 0x0400 +#define LE_STATE_SLAVE_SLAVE 0x0800 +#define LE_STATE_MASTER_SLAVE 0x1000 + +static const struct bitfield_data le_states_desc_table[] = { + { 0, "Scannable Advertising State" }, + { 1, "Connectable Advertising State" }, + { 2, "Non-connectable Advertising State" }, + { 3, "High Duty Cycle Directed Advertising State" }, + { 4, "Low Duty Cycle Directed Advertising State" }, + { 5, "Active Scanning State" }, + { 6, "Passive Scanning State" }, + { 7, "Initiating State" }, + { 8, "Connection State (Master Role)" }, + { 9, "Connection State (Slave Role)" }, + { 10, "Master Role & Master Role" }, + { 11, "Slave Role & Slave Role" }, + { 12, "Master Role & Slave Role" }, + { } +}; + +static const struct { + uint8_t bit; + uint16_t states; +} le_states_comb_table[] = { + { 0, LE_STATE_NONCONN_ADV }, + { 1, LE_STATE_SCAN_ADV }, + { 2, LE_STATE_CONN_ADV }, + { 3, LE_STATE_HIGH_DIRECT_ADV }, + { 4, LE_STATE_PASSIVE_SCAN }, + { 5, LE_STATE_ACTIVE_SCAN }, + { 6, LE_STATE_INITIATING | LE_STATE_CONN_MASTER }, + { 7, LE_STATE_CONN_SLAVE }, + { 8, LE_STATE_PASSIVE_SCAN | LE_STATE_NONCONN_ADV }, + { 9, LE_STATE_PASSIVE_SCAN | LE_STATE_SCAN_ADV }, + { 10, LE_STATE_PASSIVE_SCAN | LE_STATE_CONN_ADV }, + { 11, LE_STATE_PASSIVE_SCAN | LE_STATE_HIGH_DIRECT_ADV }, + { 12, LE_STATE_ACTIVE_SCAN | LE_STATE_NONCONN_ADV }, + { 13, LE_STATE_ACTIVE_SCAN | LE_STATE_SCAN_ADV }, + { 14, LE_STATE_ACTIVE_SCAN | LE_STATE_CONN_ADV }, + { 15, LE_STATE_ACTIVE_SCAN | LE_STATE_HIGH_DIRECT_ADV }, + { 16, LE_STATE_INITIATING | LE_STATE_NONCONN_ADV }, + { 17, LE_STATE_INITIATING | LE_STATE_SCAN_ADV }, + { 18, LE_STATE_CONN_MASTER | LE_STATE_NONCONN_ADV }, + { 19, LE_STATE_CONN_MASTER | LE_STATE_SCAN_ADV }, + { 20, LE_STATE_CONN_SLAVE | LE_STATE_NONCONN_ADV }, + { 21, LE_STATE_CONN_SLAVE | LE_STATE_SCAN_ADV }, + { 22, LE_STATE_INITIATING | LE_STATE_PASSIVE_SCAN }, + { 23, LE_STATE_INITIATING | LE_STATE_ACTIVE_SCAN }, + { 24, LE_STATE_CONN_MASTER | LE_STATE_PASSIVE_SCAN }, + { 25, LE_STATE_CONN_MASTER | LE_STATE_ACTIVE_SCAN }, + { 26, LE_STATE_CONN_SLAVE | LE_STATE_PASSIVE_SCAN }, + { 27, LE_STATE_CONN_SLAVE | LE_STATE_ACTIVE_SCAN }, + { 28, LE_STATE_INITIATING | LE_STATE_CONN_MASTER | + LE_STATE_MASTER_MASTER }, + { 29, LE_STATE_LOW_DIRECT_ADV }, + { 30, LE_STATE_LOW_DIRECT_ADV | LE_STATE_PASSIVE_SCAN }, + { 31, LE_STATE_LOW_DIRECT_ADV | LE_STATE_ACTIVE_SCAN }, + { 32, LE_STATE_INITIATING | LE_STATE_CONN_ADV | + LE_STATE_MASTER_SLAVE }, + { 33, LE_STATE_INITIATING | LE_STATE_HIGH_DIRECT_ADV | + LE_STATE_MASTER_SLAVE }, + { 34, LE_STATE_INITIATING | LE_STATE_LOW_DIRECT_ADV | + LE_STATE_MASTER_SLAVE }, + { 35, LE_STATE_CONN_MASTER | LE_STATE_CONN_ADV | + LE_STATE_MASTER_SLAVE }, + { 36, LE_STATE_CONN_MASTER | LE_STATE_HIGH_DIRECT_ADV | + LE_STATE_MASTER_SLAVE }, + { 37, LE_STATE_CONN_MASTER | LE_STATE_LOW_DIRECT_ADV | + LE_STATE_MASTER_SLAVE }, + { 38, LE_STATE_CONN_SLAVE | LE_STATE_CONN_ADV | + LE_STATE_MASTER_SLAVE }, + { 39, LE_STATE_CONN_SLAVE | LE_STATE_HIGH_DIRECT_ADV | + LE_STATE_SLAVE_SLAVE }, + { 40, LE_STATE_CONN_SLAVE | LE_STATE_LOW_DIRECT_ADV | + LE_STATE_SLAVE_SLAVE }, + { 41, LE_STATE_INITIATING | LE_STATE_CONN_SLAVE | + LE_STATE_MASTER_SLAVE }, + { } +}; + +static void print_le_states(const uint8_t *states_array) +{ + uint64_t mask, states = 0; + int i, n; + + for (i = 0; i < 8; i++) + states |= ((uint64_t) states_array[i]) << (i * 8); + + print_field("States: 0x%16.16" PRIx64, states); + + mask = states; + + for (i = 0; le_states_comb_table[i].states; i++) { + uint64_t val = (((uint64_t) 1) << le_states_comb_table[i].bit); + const char *str[3] = { NULL, }; + int num = 0; + + if (!(states & val)) + continue; + + for (n = 0; n < 16; n++) { + if (le_states_comb_table[i].states & (1 << n)) + str[num++] = le_states_desc_table[n].str; + } + + if (num > 0) { + print_field(" %s", str[0]); + for (n = 1; n < num; n++) + print_field(" and %s", str[n]); + } + + mask &= ~val; + } + + if (mask) + print_text(COLOR_UNKNOWN_LE_STATES, " Unknown states " + "(0x%16.16" PRIx64 ")", mask); +} + +static void print_le_channel_map(const uint8_t *map) +{ + unsigned int count = 0, start = 0; + char str[11]; + int i, n; + + for (i = 0; i < 5; i++) + sprintf(str + (i * 2), "%2.2x", map[i]); + + print_field("Channel map: 0x%s", str); + + for (i = 0; i < 5; i++) { + for (n = 0; n < 8; n++) { + if (map[i] & (1 << n)) { + if (count == 0) + start = (i * 8) + n; + count++; + continue; + } + + if (count > 1) { + print_field(" Channel %u-%u", + start, start + count - 1); + count = 0; + } else if (count > 0) { + print_field(" Channel %u", start); + count = 0; + } + } + } +} + +void packet_print_channel_map_ll(const uint8_t *map) +{ + print_le_channel_map(map); +} + +static void print_random_number(uint64_t rand) +{ + print_field("Random number: 0x%16.16" PRIx64, le64_to_cpu(rand)); +} + +static void print_encrypted_diversifier(uint16_t ediv) +{ + print_field("Encrypted diversifier: 0x%4.4x", le16_to_cpu(ediv)); +} + +static const struct bitfield_data events_table[] = { + { 0, "Inquiry Complete" }, + { 1, "Inquiry Result" }, + { 2, "Connection Complete" }, + { 3, "Connection Request" }, + { 4, "Disconnection Complete" }, + { 5, "Authentication Complete" }, + { 6, "Remote Name Request Complete" }, + { 7, "Encryption Change" }, + { 8, "Change Connection Link Key Complete" }, + { 9, "Master Link Key Complete" }, + { 10, "Read Remote Supported Features Complete" }, + { 11, "Read Remote Version Information Complete" }, + { 12, "QoS Setup Complete" }, + { 13, "Command Complete" }, + { 14, "Command Status" }, + { 15, "Hardware Error" }, + { 16, "Flush Occurred" }, + { 17, "Role Change" }, + { 18, "Number of Completed Packets" }, + { 19, "Mode Change" }, + { 20, "Return Link Keys" }, + { 21, "PIN Code Request" }, + { 22, "Link Key Request" }, + { 23, "Link Key Notification" }, + { 24, "Loopback Command" }, + { 25, "Data Buffer Overflow" }, + { 26, "Max Slots Change" }, + { 27, "Read Clock Offset Complete" }, + { 28, "Connection Packet Type Changed" }, + { 29, "QoS Violation" }, + { 30, "Page Scan Mode Change" }, + { 31, "Page Scan Repetition Mode Change" }, + { 32, "Flow Specification Complete" }, + { 33, "Inquiry Result with RSSI" }, + { 34, "Read Remote Extended Features Complete" }, + { 43, "Synchronous Connection Complete" }, + { 44, "Synchronous Connection Changed" }, + { 45, "Sniff Subrating" }, + { 46, "Extended Inquiry Result" }, + { 47, "Encryption Key Refresh Complete" }, + { 48, "IO Capability Request" }, + { 49, "IO Capability Request Reply" }, + { 50, "User Confirmation Request" }, + { 51, "User Passkey Request" }, + { 52, "Remote OOB Data Request" }, + { 53, "Simple Pairing Complete" }, + { 55, "Link Supervision Timeout Changed" }, + { 56, "Enhanced Flush Complete" }, + { 58, "User Passkey Notification" }, + { 59, "Keypress Notification" }, + { 60, "Remote Host Supported Features Notification" }, + { 61, "LE Meta" }, + { } +}; + +static void print_event_mask(const uint8_t *events_array, + const struct bitfield_data *table) +{ + uint64_t mask, events = 0; + int i; + + for (i = 0; i < 8; i++) + events |= ((uint64_t) events_array[i]) << (i * 8); + + print_field("Mask: 0x%16.16" PRIx64, events); + + mask = print_bitfield(2, events, table); + if (mask) + print_text(COLOR_UNKNOWN_EVENT_MASK, " Unknown mask " + "(0x%16.16" PRIx64 ")", mask); +} + +static const struct bitfield_data events_page2_table[] = { + { 0, "Physical Link Complete" }, + { 1, "Channel Selected" }, + { 2, "Disconnection Physical Link Complete" }, + { 3, "Physical Link Loss Early Warning" }, + { 4, "Physical Link Recovery" }, + { 5, "Logical Link Complete" }, + { 6, "Disconnection Logical Link Complete" }, + { 7, "Flow Specification Modify Complete" }, + { 8, "Number of Completed Data Blocks" }, + { 9, "AMP Start Test" }, + { 10, "AMP Test End" }, + { 11, "AMP Receiver Report" }, + { 12, "Short Range Mode Change Complete" }, + { 13, "AMP Status Change" }, + { 14, "Triggered Clock Capture" }, + { 15, "Synchronization Train Complete" }, + { 16, "Synchronization Train Received" }, + { 17, "Connectionless Slave Broadcast Receive" }, + { 18, "Connectionless Slave Broadcast Timeout" }, + { 19, "Truncated Page Complete" }, + { 20, "Slave Page Response Timeout" }, + { 21, "Connectionless Slave Broadcast Channel Map Change" }, + { 22, "Inquiry Response Notification" }, + { 23, "Authenticated Payload Timeout Expired" }, + { 24, "SAM Status Change" }, + { } +}; + +static const struct bitfield_data events_le_table[] = { + { 0, "LE Connection Complete" }, + { 1, "LE Advertising Report" }, + { 2, "LE Connection Update Complete" }, + { 3, "LE Read Remote Used Features Complete" }, + { 4, "LE Long Term Key Request" }, + { 5, "LE Remote Connection Parameter Request" }, + { 6, "LE Data Length Change" }, + { 7, "LE Read Local P-256 Public Key Complete" }, + { 8, "LE Generate DHKey Complete" }, + { 9, "LE Enhanced Connection Complete" }, + { 10, "LE Direct Advertising Report" }, + { 11, "LE PHY Update Complete" }, + { 12, "LE Extended Advertising Report" }, + { 13, "LE Periodic Advertising Sync Established"}, + { 14, "LE Periodic Advertising Report" }, + { 15, "LE Periodic Advertising Sync Lost" }, + { 16, "LE Extended Scan Timeout" }, + { 17, "LE Extended Advertising Set Terminated" }, + { 18, "LE Scan Request Received" }, + { 19, "LE Channel Selection Algorithm" }, + { } +}; + +static void print_fec(uint8_t fec) +{ + const char *str; + + switch (fec) { + case 0x00: + str = "Not required"; + break; + case 0x01: + str = "Required"; + break; + default: + str = "Reserved"; + break; + } + + print_field("FEC: %s (0x%02x)", str, fec); +} + +#define BT_EIR_FLAGS 0x01 +#define BT_EIR_UUID16_SOME 0x02 +#define BT_EIR_UUID16_ALL 0x03 +#define BT_EIR_UUID32_SOME 0x04 +#define BT_EIR_UUID32_ALL 0x05 +#define BT_EIR_UUID128_SOME 0x06 +#define BT_EIR_UUID128_ALL 0x07 +#define BT_EIR_NAME_SHORT 0x08 +#define BT_EIR_NAME_COMPLETE 0x09 +#define BT_EIR_TX_POWER 0x0a +#define BT_EIR_CLASS_OF_DEV 0x0d +#define BT_EIR_SSP_HASH_P192 0x0e +#define BT_EIR_SSP_RANDOMIZER_P192 0x0f +#define BT_EIR_DEVICE_ID 0x10 +#define BT_EIR_SMP_TK 0x10 +#define BT_EIR_SMP_OOB_FLAGS 0x11 +#define BT_EIR_SLAVE_CONN_INTERVAL 0x12 +#define BT_EIR_SERVICE_UUID16 0x14 +#define BT_EIR_SERVICE_UUID128 0x15 +#define BT_EIR_SERVICE_DATA 0x16 +#define BT_EIR_PUBLIC_ADDRESS 0x17 +#define BT_EIR_RANDOM_ADDRESS 0x18 +#define BT_EIR_GAP_APPEARANCE 0x19 +#define BT_EIR_ADVERTISING_INTERVAL 0x1a +#define BT_EIR_LE_DEVICE_ADDRESS 0x1b +#define BT_EIR_LE_ROLE 0x1c +#define BT_EIR_SSP_HASH_P256 0x1d +#define BT_EIR_SSP_RANDOMIZER_P256 0x1e +#define BT_EIR_SERVICE_UUID32 0x1f +#define BT_EIR_SERVICE_DATA32 0x20 +#define BT_EIR_SERVICE_DATA128 0x21 +#define BT_EIR_LE_SC_CONFIRM_VALUE 0x22 +#define BT_EIR_LE_SC_RANDOM_VALUE 0x23 +#define BT_EIR_URI 0x24 +#define BT_EIR_INDOOR_POSITIONING 0x25 +#define BT_EIR_TRANSPORT_DISCOVERY 0x26 +#define BT_EIR_LE_SUPPORTED_FEATURES 0x27 +#define BT_EIR_CHANNEL_MAP_UPDATE_IND 0x28 +#define BT_EIR_MESH_PROV 0x29 +#define BT_EIR_MESH_DATA 0x2a +#define BT_EIR_MESH_BEACON 0x2b +#define BT_EIR_3D_INFO_DATA 0x3d +#define BT_EIR_MANUFACTURER_DATA 0xff + +static void print_manufacturer_apple(const void *data, uint8_t data_len) +{ + uint8_t type = *((uint8_t *) data); + + if (data_len < 1) + return; + + if (type == 0x01) { + char identifier[100]; + + snprintf(identifier, sizeof(identifier) - 1, "%s", + (const char *) (data + 1)); + + print_field(" Identifier: %s", identifier); + return; + } + + while (data_len > 0) { + uint8_t len; + const char *str; + + type = *((uint8_t *) data); + data++; + data_len--; + + if (type == 0x00) + continue; + + if (data_len < 1) + break; + + switch (type) { + case 0x02: + str = "iBeacon"; + break; + case 0x05: + str = "AirDrop"; + break; + case 0x09: + str = "Apple TV"; + break; + default: + str = "Unknown"; + break; + } + + print_field(" Type: %s (%u)", str, type); + + len = *((uint8_t *) data); + data++; + data_len--; + + if (len < 1) + continue; + + if (len > data_len) + break; + + if (type == 0x02 && len == 0x15) { + const uint8_t *uuid; + uint16_t minor, major; + int8_t tx_power; + + uuid = data; + print_field(" UUID: %8.8x-%4.4x-%4.4x-%4.4x-%8.8x%4.4x", + get_le32(&uuid[12]), get_le16(&uuid[10]), + get_le16(&uuid[8]), get_le16(&uuid[6]), + get_le32(&uuid[2]), get_le16(&uuid[0])); + + major = get_le16(data + 16); + minor = get_le16(data + 18); + print_field(" Version: %u.%u", major, minor); + + tx_power = *(int8_t *) (data + 20); + print_field(" TX power: %d dB", tx_power); + } else + print_hex_field(" Data", data, len); + + data += len; + data_len -= len; + } + + packet_hexdump(data, data_len); +} + +static void print_manufacturer_data(const void *data, uint8_t data_len) +{ + uint16_t company = get_le16(data); + + packet_print_company("Company", company); + + switch (company) { + case 76: + case 19456: + print_manufacturer_apple(data + 2, data_len - 2); + break; + default: + print_hex_field(" Data", data + 2, data_len - 2); + break; + } +} + +static void print_device_id(const void *data, uint8_t data_len) +{ + uint16_t source, vendor, product, version; + char modalias[26], *vendor_str, *product_str; + const char *str; + + if (data_len < 8) + return; + + source = get_le16(data); + vendor = get_le16(data + 2); + product = get_le16(data + 4); + version = get_le16(data + 6); + + switch (source) { + case 0x0001: + str = "Bluetooth SIG assigned"; + sprintf(modalias, "bluetooth:v%04Xp%04Xd%04X", + vendor, product, version); + break; + case 0x0002: + str = "USB Implementer's Forum assigned"; + sprintf(modalias, "usb:v%04Xp%04Xd%04X", + vendor, product, version); + break; + default: + str = "Reserved"; + modalias[0] = '\0'; + break; + } + + print_field("Device ID: %s (0x%4.4x)", str, source); + + if (!hwdb_get_vendor_model(modalias, &vendor_str, &product_str)) { + vendor_str = NULL; + product_str = NULL; + } + + if (source != 0x0001) { + if (vendor_str) + print_field(" Vendor: %s (0x%4.4x)", + vendor_str, vendor); + else + print_field(" Vendor: 0x%4.4x", vendor); + } else + packet_print_company(" Vendor", vendor); + + if (product_str) + print_field(" Product: %s (0x%4.4x)", product_str, product); + else + print_field(" Product: 0x%4.4x", product); + + print_field(" Version: %u.%u.%u (0x%4.4x)", + (version & 0xff00) >> 8, + (version & 0x00f0) >> 4, + (version & 0x000f), version); + + free(vendor_str); + free(product_str); +} + +static void print_uuid16_list(const char *label, const void *data, + uint8_t data_len) +{ + uint8_t count = data_len / sizeof(uint16_t); + unsigned int i; + + print_field("%s: %u entr%s", label, count, count == 1 ? "y" : "ies"); + + for (i = 0; i < count; i++) { + uint16_t uuid = get_le16(data + (i * 2)); + print_field(" %s (0x%4.4x)", bt_uuid16_to_str(uuid), uuid); + } +} + +static void print_uuid32_list(const char *label, const void *data, + uint8_t data_len) +{ + uint8_t count = data_len / sizeof(uint32_t); + unsigned int i; + + print_field("%s: %u entr%s", label, count, count == 1 ? "y" : "ies"); + + for (i = 0; i < count; i++) { + uint32_t uuid = get_le32(data + (i * 4)); + print_field(" %s (0x%8.8x)", bt_uuid32_to_str(uuid), uuid); + } +} + +static void print_uuid128_list(const char *label, const void *data, + uint8_t data_len) +{ + uint8_t count = data_len / 16; + unsigned int i; + char uuidstr[MAX_LEN_UUID_STR]; + + print_field("%s: %u entr%s", label, count, count == 1 ? "y" : "ies"); + + for (i = 0; i < count; i++) { + const uint8_t *uuid = data + (i * 16); + + sprintf(uuidstr, "%8.8x-%4.4x-%4.4x-%4.4x-%8.8x%4.4x", + get_le32(&uuid[12]), get_le16(&uuid[10]), + get_le16(&uuid[8]), get_le16(&uuid[6]), + get_le32(&uuid[2]), get_le16(&uuid[0])); + print_field(" %s (%s)", bt_uuidstr_to_str(uuidstr), uuidstr); + } +} + +static const struct bitfield_data eir_flags_table[] = { + { 0, "LE Limited Discoverable Mode" }, + { 1, "LE General Discoverable Mode" }, + { 2, "BR/EDR Not Supported" }, + { 3, "Simultaneous LE and BR/EDR (Controller)" }, + { 4, "Simultaneous LE and BR/EDR (Host)" }, + { } +}; + +static const struct bitfield_data eir_3d_table[] = { + { 0, "Association Notification" }, + { 1, "Battery Level Reporting" }, + { 2, "Send Battery Level Report on Start-up Synchronization" }, + { 7, "Factory Test Mode" }, + { } +}; + +static const struct bitfield_data mesh_oob_table[] = { + { 0, "Other" }, + { 1, "Electronic / URI" }, + { 2, "2D machine-readable code" }, + { 3, "Bar code" }, + { 4, "Near Field Communication (NFC)" }, + { 5, "Number" }, + { 6, "String" }, + { 11, "On box" }, + { 12, "Inside box" }, + { 13, "On piece of paper" }, + { 14, "Inside manual" }, + { 15, "On device" }, + { } +}; + +static void print_mesh_beacon(const uint8_t *data, uint8_t len) +{ + uint16_t oob; + + print_hex_field("Mesh Beacon", data, len); + + if (len < 1) + return; + + switch (data[0]) { + case 0x00: + print_field(" Unprovisioned Device Beacon (0x00)"); + if (len < 18) { + packet_hexdump(data + 1, len - 1); + break; + } + + print_hex_field(" Device UUID", data + 1, 16); + + oob = get_be16(data + 17); + print_field(" OOB Information: 0x%4.4x", oob); + + print_bitfield(4, oob, mesh_oob_table); + + if (len < 23) { + packet_hexdump(data + 18, len - 18); + break; + } + + print_field(" URI Hash: 0x%8.8x", get_be32(data + 19)); + packet_hexdump(data + 23, len - 23); + break; + case 0x01: + print_field(" Secure Network Beacon (0x01)"); + if (len < 22) { + packet_hexdump(data + 1, len - 1); + break; + } + + print_field(" Flags: 0x%2.2x", data[0]); + + if (data[1] & 0x01) + print_field(" Key Refresh"); + + if (data[1] & 0x02) + print_field(" IV Update"); + + print_hex_field(" Network Id", data + 2, 8); + print_field(" IV Index: 0x%08x", get_be32(data + 10)); + print_hex_field(" Authentication Value", data + 14, 8); + packet_hexdump(data + 22, len - 22); + break; + default: + print_field(" Invalid Beacon (0x%02x)", data[0]); + packet_hexdump(data, len); + break; + } +} + +static void print_mesh_prov(const uint8_t *data, uint8_t len) +{ + print_hex_field("Mesh Provisioning", data, len); + + if (len < 6) { + packet_hexdump(data, len); + return; + } + + print_field(" Link ID: 0x%08x", get_be32(data)); + print_field(" Transaction Number: %u", data[4]); + + data += 5; + len -= 5; + + switch (data[0] & 0x03) { + case 0x00: + print_field(" Transaction Start (0x00)"); + if (len < 5) { + packet_hexdump(data + 1, len - 1); + return; + } + print_field(" SeqN: %u", data[0] & 0xfc >> 2); + print_field(" TotalLength: %u", get_be16(data + 1)); + print_field(" FCS: 0x%2.2x", data[3]); + print_hex_field(" Data", data + 4, len - 4); + packet_hexdump(data + 5, len - 5); + break; + case 0x01: + print_field(" Transaction Acknowledgment (0x01)"); + packet_hexdump(data + 1, len - 1); + break; + case 0x02: + print_field(" Transaction Continuation (0x02)"); + print_field(" SegmentIndex: %u", data[0] >> 2); + if (len < 2) { + packet_hexdump(data + 1, len - 1); + return; + } + print_hex_field(" Data", data + 1, len - 1); + packet_hexdump(data + 2, len - 2); + break; + case 0x03: + print_field(" Provisioning Bearer Control (0x03)"); + switch (data[0] >> 2) { + case 0x00: + print_field(" Link Open (0x00)"); + if (len < 17) { + packet_hexdump(data + 1, len - 1); + break; + } + print_hex_field(" Device UUID", data, 16); + break; + case 0x01: + print_field(" Link Ack (0x01)"); + break; + case 0x02: + print_field(" Link Close (0x02)"); + if (len < 2) { + packet_hexdump(data + 1, len - 1); + break; + } + + switch (data[1]) { + case 0x00: + print_field(" Reason: Success (0x00)"); + break; + case 0x01: + print_field(" Reason: Timeout (0x01)"); + break; + case 0x02: + print_field(" Reason: Fail (0x02)"); + break; + default: + print_field(" Reason: Unrecognized (0x%2.2x)", + data[1]); + } + packet_hexdump(data + 2, len - 2); + break; + default: + packet_hexdump(data + 1, len - 1); + break; + } + break; + default: + print_field(" Invalid Command (0x%02x)", data[0]); + packet_hexdump(data, len); + break; + } +} + +static void print_mesh_data(const uint8_t *data, uint8_t len) +{ + print_hex_field("Mesh Data", data, len); + + if (len < 1) + return; + + print_field(" IVI: %u", data[0] >> 7); + print_field(" NID: 0x%2.2x", data[0] & 0x7f); + packet_hexdump(data + 1, len - 1); +} + +static void print_transport_data(const uint8_t *data, uint8_t len) +{ + print_field("Transport Discovery Data"); + + if (len < 3) + return; + + print_field(" Organization: %s (0x%02x)", + data[0] == 0x01 ? "Bluetooth SIG" : "RFU", data[0]); + print_field(" Flags: 0x%2.2x", data[1]); + print_field(" Role: 0x%2.2x", data[1] & 0x03); + + switch (data[1] & 0x03) { + case 0x00: + print_field(" Not Specified"); + break; + case 0x01: + print_field(" Seeker Only"); + break; + case 0x02: + print_field(" Provider Only"); + break; + case 0x03: + print_field(" Both Seeker an Provider"); + break; + } + + print_field(" Transport Data Incomplete: %s (0x%2.2x)", + data[1] & 0x04 ? "True" : "False", data[1] & 0x04); + + print_field(" Transport State: 0x%2.2x", data[1] & 0x18); + + switch (data[1] & 0x18) { + case 0x00: + print_field(" Off"); + break; + case 0x08: + print_field(" On"); + break; + case 0x10: + print_field(" Temporary Unavailable"); + break; + case 0x18: + print_field(" RFU"); + break; + } + + print_field(" Length: %u", data[2]); + print_hex_field(" Data", data + 3, len - 3); +} + +static void print_eir(const uint8_t *eir, uint8_t eir_len, bool le) +{ + uint16_t len = 0; + + if (eir_len == 0) + return; + + while (len < eir_len - 1) { + uint8_t field_len = eir[0]; + const uint8_t *data = &eir[2]; + uint8_t data_len; + char name[239], label[100]; + uint8_t flags, mask; + + /* Check for the end of EIR */ + if (field_len == 0) + break; + + len += field_len + 1; + + /* Do not continue EIR Data parsing if got incorrect length */ + if (len > eir_len) { + len -= field_len + 1; + break; + } + + data_len = field_len - 1; + + switch (eir[1]) { + case BT_EIR_FLAGS: + flags = *data; + + print_field("Flags: 0x%2.2x", flags); + + mask = print_bitfield(2, flags, eir_flags_table); + if (mask) + print_text(COLOR_UNKNOWN_SERVICE_CLASS, + " Unknown flags (0x%2.2x)", mask); + break; + + case BT_EIR_UUID16_SOME: + if (data_len < sizeof(uint16_t)) + break; + print_uuid16_list("16-bit Service UUIDs (partial)", + data, data_len); + break; + + case BT_EIR_UUID16_ALL: + if (data_len < sizeof(uint16_t)) + break; + print_uuid16_list("16-bit Service UUIDs (complete)", + data, data_len); + break; + + case BT_EIR_UUID32_SOME: + if (data_len < sizeof(uint32_t)) + break; + print_uuid32_list("32-bit Service UUIDs (partial)", + data, data_len); + break; + + case BT_EIR_UUID32_ALL: + if (data_len < sizeof(uint32_t)) + break; + print_uuid32_list("32-bit Service UUIDs (complete)", + data, data_len); + break; + + case BT_EIR_UUID128_SOME: + if (data_len < 16) + break; + print_uuid128_list("128-bit Service UUIDs (partial)", + data, data_len); + break; + + case BT_EIR_UUID128_ALL: + if (data_len < 16) + break; + print_uuid128_list("128-bit Service UUIDs (complete)", + data, data_len); + break; + + case BT_EIR_NAME_SHORT: + memset(name, 0, sizeof(name)); + memcpy(name, data, data_len); + print_field("Name (short): %s", name); + break; + + case BT_EIR_NAME_COMPLETE: + memset(name, 0, sizeof(name)); + memcpy(name, data, data_len); + print_field("Name (complete): %s", name); + break; + + case BT_EIR_TX_POWER: + if (data_len < 1) + break; + print_field("TX power: %d dBm", (int8_t) *data); + break; + + case BT_EIR_CLASS_OF_DEV: + if (data_len < 3) + break; + print_dev_class(data); + break; + + case BT_EIR_SSP_HASH_P192: + if (data_len < 16) + break; + print_hash_p192(data); + break; + + case BT_EIR_SSP_RANDOMIZER_P192: + if (data_len < 16) + break; + print_randomizer_p192(data); + break; + + case BT_EIR_DEVICE_ID: + /* SMP TK has the same value as Device ID */ + if (le) + print_hex_field("SMP TK", data, data_len); + else if (data_len >= 8) + print_device_id(data, data_len); + break; + + case BT_EIR_SMP_OOB_FLAGS: + print_field("SMP OOB Flags: 0x%2.2x", *data); + break; + + case BT_EIR_SLAVE_CONN_INTERVAL: + if (data_len < 4) + break; + print_field("Slave Conn. Interval: 0x%4.4x - 0x%4.4x", + get_le16(&data[0]), + get_le16(&data[2])); + break; + + case BT_EIR_SERVICE_UUID16: + if (data_len < sizeof(uint16_t)) + break; + print_uuid16_list("16-bit Service UUIDs", + data, data_len); + break; + + case BT_EIR_SERVICE_UUID128: + if (data_len < 16) + break; + print_uuid128_list("128-bit Service UUIDs", + data, data_len); + break; + + case BT_EIR_SERVICE_DATA: + if (data_len < 2) + break; + sprintf(label, "Service Data (UUID 0x%4.4x)", + get_le16(&data[0])); + print_hex_field(label, &data[2], data_len - 2); + break; + + case BT_EIR_RANDOM_ADDRESS: + if (data_len < 6) + break; + print_addr("Random Address", data, 0x01); + break; + + case BT_EIR_PUBLIC_ADDRESS: + if (data_len < 6) + break; + print_addr("Public Address", data, 0x00); + break; + + case BT_EIR_GAP_APPEARANCE: + if (data_len < 2) + break; + print_appearance(get_le16(data)); + break; + + case BT_EIR_SSP_HASH_P256: + if (data_len < 16) + break; + print_hash_p256(data); + break; + + case BT_EIR_SSP_RANDOMIZER_P256: + if (data_len < 16) + break; + print_randomizer_p256(data); + break; + + case BT_EIR_TRANSPORT_DISCOVERY: + print_transport_data(data, data_len); + break; + + case BT_EIR_3D_INFO_DATA: + print_hex_field("3D Information Data", data, data_len); + if (data_len < 2) + break; + + flags = *data; + + print_field(" Features: 0x%2.2x", flags); + + mask = print_bitfield(4, flags, eir_3d_table); + if (mask) + print_text(COLOR_UNKNOWN_FEATURE_BIT, + " Unknown features (0x%2.2x)", mask); + + print_field(" Path Loss Threshold: %d", data[1]); + break; + + case BT_EIR_MESH_DATA: + print_mesh_data(data, data_len); + break; + + case BT_EIR_MESH_PROV: + print_mesh_prov(data, data_len); + break; + + case BT_EIR_MESH_BEACON: + print_mesh_beacon(data, data_len); + break; + + case BT_EIR_MANUFACTURER_DATA: + if (data_len < 2) + break; + print_manufacturer_data(data, data_len); + break; + + default: + sprintf(label, "Unknown EIR field 0x%2.2x", eir[1]); + print_hex_field(label, data, data_len); + break; + } + + eir += field_len + 1; + } + + if (len < eir_len && eir[0] != 0) + packet_hexdump(eir, eir_len - len); +} + +void packet_print_addr(const char *label, const void *data, bool random) +{ + print_addr(label ? : "Address", data, random ? 0x01 : 0x00); +} + +void packet_print_handle(uint16_t handle) +{ + print_handle_native(handle); +} + +void packet_print_rssi(int8_t rssi) +{ + print_rssi(rssi); +} + +void packet_print_ad(const void *data, uint8_t size) +{ + print_eir(data, size, true); +} + +struct broadcast_message { + uint32_t frame_sync_instant; + uint16_t bluetooth_clock_phase; + uint16_t left_open_offset; + uint16_t left_close_offset; + uint16_t right_open_offset; + uint16_t right_close_offset; + uint16_t frame_sync_period; + uint8_t frame_sync_period_fraction; +} __attribute__ ((packed)); + +static void print_3d_broadcast(const void *data, uint8_t size) +{ + const struct broadcast_message *msg = data; + uint32_t instant; + uint16_t left_open, left_close, right_open, right_close; + uint16_t phase, period; + uint8_t period_frac; + bool mode; + + instant = le32_to_cpu(msg->frame_sync_instant); + mode = !!(instant & 0x40000000); + phase = le16_to_cpu(msg->bluetooth_clock_phase); + left_open = le16_to_cpu(msg->left_open_offset); + left_close = le16_to_cpu(msg->left_close_offset); + right_open = le16_to_cpu(msg->right_open_offset); + right_close = le16_to_cpu(msg->right_close_offset); + period = le16_to_cpu(msg->frame_sync_period); + period_frac = msg->frame_sync_period_fraction; + + print_field(" Frame sync instant: 0x%8.8x", instant & 0x7fffffff); + print_field(" Video mode: %s (%d)", mode ? "Dual View" : "3D", mode); + print_field(" Bluetooth clock phase: %d usec (0x%4.4x)", + phase, phase); + print_field(" Left lense shutter open offset: %d usec (0x%4.4x)", + left_open, left_open); + print_field(" Left lense shutter close offset: %d usec (0x%4.4x)", + left_close, left_close); + print_field(" Right lense shutter open offset: %d usec (0x%4.4x)", + right_open, right_open); + print_field(" Right lense shutter close offset: %d usec (0x%4.4x)", + right_close, right_close); + print_field(" Frame sync period: %d.%d usec (0x%4.4x 0x%2.2x)", + period, period_frac * 256, + period, period_frac); +} + +void packet_hexdump(const unsigned char *buf, uint16_t len) +{ + static const char hexdigits[] = "0123456789abcdef"; + char str[68]; + uint16_t i; + + if (!len) + return; + + for (i = 0; i < len; i++) { + str[((i % 16) * 3) + 0] = hexdigits[buf[i] >> 4]; + str[((i % 16) * 3) + 1] = hexdigits[buf[i] & 0xf]; + str[((i % 16) * 3) + 2] = ' '; + str[(i % 16) + 49] = isprint(buf[i]) ? buf[i] : '.'; + + if ((i + 1) % 16 == 0) { + str[47] = ' '; + str[48] = ' '; + str[65] = '\0'; + print_text(COLOR_WHITE, "%s", str); + str[0] = ' '; + } + } + + if (i % 16 > 0) { + uint16_t j; + for (j = (i % 16); j < 16; j++) { + str[(j * 3) + 0] = ' '; + str[(j * 3) + 1] = ' '; + str[(j * 3) + 2] = ' '; + str[j + 49] = ' '; + } + str[47] = ' '; + str[48] = ' '; + str[65] = '\0'; + print_text(COLOR_WHITE, "%s", str); + } +} + +void packet_control(struct timeval *tv, struct ucred *cred, + uint16_t index, uint16_t opcode, + const void *data, uint16_t size) +{ + control_message(opcode, data, size); +} + +static int addr2str(const uint8_t *addr, char *str) +{ + return sprintf(str, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); +} + +void packet_monitor(struct timeval *tv, struct ucred *cred, + uint16_t index, uint16_t opcode, + const void *data, uint16_t size) +{ + const struct btsnoop_opcode_new_index *ni; + const struct btsnoop_opcode_index_info *ii; + const struct btsnoop_opcode_user_logging *ul; + char str[18], extra_str[24]; + uint16_t manufacturer; + const char *ident; + + if (index != HCI_DEV_NONE) { + index_current = index; + } + + if (index != HCI_DEV_NONE && index > MAX_INDEX) { + print_field("Invalid index (%d)", index); + return; + } + + if (tv && time_offset == ((time_t) -1)) + time_offset = tv->tv_sec; + + switch (opcode) { + case BTSNOOP_OPCODE_NEW_INDEX: + ni = data; + + if (index < MAX_INDEX) { + index_list[index].type = ni->type; + memcpy(index_list[index].bdaddr, ni->bdaddr, 6); + index_list[index].manufacturer = fallback_manufacturer; + } + + addr2str(ni->bdaddr, str); + packet_new_index(tv, index, str, ni->type, ni->bus, ni->name); + break; + case BTSNOOP_OPCODE_DEL_INDEX: + if (index < MAX_INDEX) + addr2str(index_list[index].bdaddr, str); + else + sprintf(str, "00:00:00:00:00:00"); + + packet_del_index(tv, index, str); + break; + case BTSNOOP_OPCODE_COMMAND_PKT: + packet_hci_command(tv, cred, index, data, size); + break; + case BTSNOOP_OPCODE_EVENT_PKT: + packet_hci_event(tv, cred, index, data, size); + break; + case BTSNOOP_OPCODE_ACL_TX_PKT: + packet_hci_acldata(tv, cred, index, false, data, size); + break; + case BTSNOOP_OPCODE_ACL_RX_PKT: + packet_hci_acldata(tv, cred, index, true, data, size); + break; + case BTSNOOP_OPCODE_SCO_TX_PKT: + packet_hci_scodata(tv, cred, index, false, data, size); + break; + case BTSNOOP_OPCODE_SCO_RX_PKT: + packet_hci_scodata(tv, cred, index, true, data, size); + break; + case BTSNOOP_OPCODE_OPEN_INDEX: + if (index < MAX_INDEX) + addr2str(index_list[index].bdaddr, str); + else + sprintf(str, "00:00:00:00:00:00"); + + packet_open_index(tv, index, str); + break; + case BTSNOOP_OPCODE_CLOSE_INDEX: + if (index < MAX_INDEX) + addr2str(index_list[index].bdaddr, str); + else + sprintf(str, "00:00:00:00:00:00"); + + packet_close_index(tv, index, str); + break; + case BTSNOOP_OPCODE_INDEX_INFO: + ii = data; + manufacturer = le16_to_cpu(ii->manufacturer); + + if (index < MAX_INDEX) { + memcpy(index_list[index].bdaddr, ii->bdaddr, 6); + index_list[index].manufacturer = manufacturer; + } + + addr2str(ii->bdaddr, str); + packet_index_info(tv, index, str, manufacturer); + break; + case BTSNOOP_OPCODE_VENDOR_DIAG: + if (index < MAX_INDEX) + manufacturer = index_list[index].manufacturer; + else + manufacturer = fallback_manufacturer; + + packet_vendor_diag(tv, index, manufacturer, data, size); + break; + case BTSNOOP_OPCODE_SYSTEM_NOTE: + packet_system_note(tv, cred, index, data); + break; + case BTSNOOP_OPCODE_USER_LOGGING: + ul = data; + ident = ul->ident_len ? data + sizeof(*ul) : NULL; + + packet_user_logging(tv, cred, index, ul->priority, ident, + data + sizeof(*ul) + ul->ident_len, + size - (sizeof(*ul) + ul->ident_len)); + break; + case BTSNOOP_OPCODE_CTRL_OPEN: + control_disable_decoding(); + packet_ctrl_open(tv, cred, index, data, size); + break; + case BTSNOOP_OPCODE_CTRL_CLOSE: + packet_ctrl_close(tv, cred, index, data, size); + break; + case BTSNOOP_OPCODE_CTRL_COMMAND: + packet_ctrl_command(tv, cred, index, data, size); + break; + case BTSNOOP_OPCODE_CTRL_EVENT: + packet_ctrl_event(tv, cred, index, data, size); + break; + default: + sprintf(extra_str, "(code %d len %d)", opcode, size); + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Unknown packet", NULL, extra_str); + packet_hexdump(data, size); + break; + } +} + +void packet_simulator(struct timeval *tv, uint16_t frequency, + const void *data, uint16_t size) +{ + char str[10]; + + if (tv && time_offset == ((time_t) -1)) + time_offset = tv->tv_sec; + + sprintf(str, "%u MHz", frequency); + + print_packet(tv, NULL, '*', 0, NULL, COLOR_PHY_PACKET, + "Physical packet:", NULL, str); + + ll_packet(frequency, data, size, false); +} + +static void null_cmd(const void *data, uint8_t size) +{ +} + +static void status_rsp(const void *data, uint8_t size) +{ + uint8_t status = *((const uint8_t *) data); + + print_status(status); +} + +static void status_bdaddr_rsp(const void *data, uint8_t size) +{ + uint8_t status = *((const uint8_t *) data); + + print_status(status); + print_bdaddr(data + 1); +} + +static void inquiry_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_inquiry *cmd = data; + + print_iac(cmd->lap); + print_field("Length: %.2fs (0x%2.2x)", + cmd->length * 1.28, cmd->length); + print_num_resp(cmd->num_resp); +} + +static void periodic_inquiry_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_periodic_inquiry *cmd = data; + + print_field("Max period: %.2fs (0x%2.2x)", + cmd->max_period * 1.28, cmd->max_period); + print_field("Min period: %.2fs (0x%2.2x)", + cmd->min_period * 1.28, cmd->min_period); + print_iac(cmd->lap); + print_field("Length: %.2fs (0x%2.2x)", + cmd->length * 1.28, cmd->length); + print_num_resp(cmd->num_resp); +} + +static void create_conn_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_create_conn *cmd = data; + const char *str; + + print_bdaddr(cmd->bdaddr); + print_pkt_type(cmd->pkt_type); + print_pscan_rep_mode(cmd->pscan_rep_mode); + print_pscan_mode(cmd->pscan_mode); + print_clock_offset(cmd->clock_offset); + + switch (cmd->role_switch) { + case 0x00: + str = "Stay master"; + break; + case 0x01: + str = "Allow slave"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Role switch: %s (0x%2.2x)", str, cmd->role_switch); +} + +static void disconnect_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_disconnect *cmd = data; + + print_handle(cmd->handle); + print_reason(cmd->reason); +} + +static void add_sco_conn_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_add_sco_conn *cmd = data; + + print_handle(cmd->handle); + print_pkt_type_sco(cmd->pkt_type); +} + +static void create_conn_cancel_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_create_conn_cancel *cmd = data; + + print_bdaddr(cmd->bdaddr); +} + +static void accept_conn_request_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_accept_conn_request *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_role(cmd->role); +} + +static void reject_conn_request_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_reject_conn_request *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_reason(cmd->reason); +} + +static void link_key_request_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_link_key_request_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_link_key(cmd->link_key); +} + +static void link_key_request_neg_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_link_key_request_neg_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); +} + +static void pin_code_request_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_pin_code_request_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_field("PIN length: %d", cmd->pin_len); + print_pin_code(cmd->pin_code, cmd->pin_len); +} + +static void pin_code_request_neg_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_pin_code_request_neg_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); +} + +static void change_conn_pkt_type_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_change_conn_pkt_type *cmd = data; + + print_handle(cmd->handle); + print_pkt_type(cmd->pkt_type); +} + +static void auth_requested_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_auth_requested *cmd = data; + + print_handle(cmd->handle); +} + +static void set_conn_encrypt_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_conn_encrypt *cmd = data; + + print_handle(cmd->handle); + print_enable("Encryption", cmd->encr_mode); +} + +static void change_conn_link_key_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_change_conn_link_key *cmd = data; + + print_handle(cmd->handle); +} + +static void master_link_key_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_master_link_key *cmd = data; + + print_key_flag(cmd->key_flag); +} + +static void remote_name_request_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_remote_name_request *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_pscan_rep_mode(cmd->pscan_rep_mode); + print_pscan_mode(cmd->pscan_mode); + print_clock_offset(cmd->clock_offset); +} + +static void remote_name_request_cancel_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_remote_name_request_cancel *cmd = data; + + print_bdaddr(cmd->bdaddr); +} + +static void read_remote_features_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_remote_features *cmd = data; + + print_handle(cmd->handle); +} + +static void read_remote_ext_features_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_remote_ext_features *cmd = data; + + print_handle(cmd->handle); + print_field("Page: %d", cmd->page); +} + +static void read_remote_version_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_remote_version *cmd = data; + + print_handle(cmd->handle); +} + +static void read_clock_offset_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_clock_offset *cmd = data; + + print_handle(cmd->handle); +} + +static void read_lmp_handle_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_lmp_handle *cmd = data; + + print_handle(cmd->handle); +} + +static void read_lmp_handle_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_lmp_handle *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_field("LMP handle: %d", rsp->lmp_handle); + print_field("Reserved: %d", le32_to_cpu(rsp->reserved)); +} + +static void setup_sync_conn_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_setup_sync_conn *cmd = data; + + print_handle(cmd->handle); + print_field("Transmit bandwidth: %d", le32_to_cpu(cmd->tx_bandwidth)); + print_field("Receive bandwidth: %d", le32_to_cpu(cmd->rx_bandwidth)); + print_field("Max latency: %d", le16_to_cpu(cmd->max_latency)); + print_voice_setting(cmd->voice_setting); + print_retransmission_effort(cmd->retrans_effort); + print_pkt_type_sco(cmd->pkt_type); +} + +static void accept_sync_conn_request_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_accept_sync_conn_request *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_field("Transmit bandwidth: %d", le32_to_cpu(cmd->tx_bandwidth)); + print_field("Receive bandwidth: %d", le32_to_cpu(cmd->rx_bandwidth)); + print_field("Max latency: %d", le16_to_cpu(cmd->max_latency)); + print_voice_setting(cmd->voice_setting); + print_retransmission_effort(cmd->retrans_effort); + print_pkt_type_sco(cmd->pkt_type); +} + +static void reject_sync_conn_request_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_reject_sync_conn_request *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_reason(cmd->reason); +} + +static void io_capability_request_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_io_capability_request_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_io_capability(cmd->capability); + print_oob_data(cmd->oob_data); + print_authentication(cmd->authentication); +} + +static void user_confirm_request_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_user_confirm_request_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); +} + +static void user_confirm_request_neg_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_user_confirm_request_neg_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); +} + +static void user_passkey_request_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_user_passkey_request_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_passkey(cmd->passkey); +} + +static void user_passkey_request_neg_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_user_passkey_request_neg_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); +} + +static void remote_oob_data_request_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_remote_oob_data_request_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_hash_p192(cmd->hash); + print_randomizer_p192(cmd->randomizer); +} + +static void remote_oob_data_request_neg_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_remote_oob_data_request_neg_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); +} + +static void io_capability_request_neg_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_io_capability_request_neg_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_reason(cmd->reason); +} + +static void create_phy_link_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_create_phy_link *cmd = data; + + print_phy_handle(cmd->phy_handle); + print_key_len(cmd->key_len); + print_key_type(cmd->key_type); + + packet_hexdump(data + 3, size - 3); +} + +static void accept_phy_link_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_accept_phy_link *cmd = data; + + print_phy_handle(cmd->phy_handle); + print_key_len(cmd->key_len); + print_key_type(cmd->key_type); + + packet_hexdump(data + 3, size - 3); +} + +static void disconn_phy_link_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_disconn_phy_link *cmd = data; + + print_phy_handle(cmd->phy_handle); + print_reason(cmd->reason); +} + +static void create_logic_link_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_create_logic_link *cmd = data; + + print_phy_handle(cmd->phy_handle); + print_flow_spec("TX", cmd->tx_flow_spec); + print_flow_spec("RX", cmd->rx_flow_spec); +} + +static void accept_logic_link_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_accept_logic_link *cmd = data; + + print_phy_handle(cmd->phy_handle); + print_flow_spec("TX", cmd->tx_flow_spec); + print_flow_spec("RX", cmd->rx_flow_spec); +} + +static void disconn_logic_link_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_disconn_logic_link *cmd = data; + + print_handle(cmd->handle); +} + +static void logic_link_cancel_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_logic_link_cancel *cmd = data; + + print_phy_handle(cmd->phy_handle); + print_field("TX flow spec: 0x%2.2x", cmd->flow_spec); +} + +static void logic_link_cancel_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_logic_link_cancel *rsp = data; + + print_status(rsp->status); + print_phy_handle(rsp->phy_handle); + print_field("TX flow spec: 0x%2.2x", rsp->flow_spec); +} + +static void flow_spec_modify_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_flow_spec_modify *cmd = data; + + print_handle(cmd->handle); + print_flow_spec("TX", cmd->tx_flow_spec); + print_flow_spec("RX", cmd->rx_flow_spec); +} + +static void enhanced_setup_sync_conn_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_enhanced_setup_sync_conn *cmd = data; + + print_handle(cmd->handle); + print_field("Transmit bandwidth: %d", le32_to_cpu(cmd->tx_bandwidth)); + print_field("Receive bandwidth: %d", le32_to_cpu(cmd->rx_bandwidth)); + + /* TODO */ + + print_field("Max latency: %d", le16_to_cpu(cmd->max_latency)); + print_pkt_type_sco(cmd->pkt_type); + print_retransmission_effort(cmd->retrans_effort); +} + +static void enhanced_accept_sync_conn_request_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_enhanced_accept_sync_conn_request *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_field("Transmit bandwidth: %d", le32_to_cpu(cmd->tx_bandwidth)); + print_field("Receive bandwidth: %d", le32_to_cpu(cmd->rx_bandwidth)); + + /* TODO */ + + print_field("Max latency: %d", le16_to_cpu(cmd->max_latency)); + print_pkt_type_sco(cmd->pkt_type); + print_retransmission_effort(cmd->retrans_effort); +} + +static void truncated_page_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_truncated_page *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_pscan_rep_mode(cmd->pscan_rep_mode); + print_clock_offset(cmd->clock_offset); +} + +static void truncated_page_cancel_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_truncated_page_cancel *cmd = data; + + print_bdaddr(cmd->bdaddr); +} + +static void set_slave_broadcast_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_slave_broadcast *cmd = data; + + print_field("Enable: 0x%2.2x", cmd->enable); + print_lt_addr(cmd->lt_addr); + print_lpo_allowed(cmd->lpo_allowed); + print_pkt_type(cmd->pkt_type); + print_slot_625("Min interval", cmd->min_interval); + print_slot_625("Max interval", cmd->max_interval); + print_slot_625("Supervision timeout", cmd->timeout); +} + +static void set_slave_broadcast_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_set_slave_broadcast *rsp = data; + + print_status(rsp->status); + print_lt_addr(rsp->lt_addr); + print_interval(rsp->interval); +} + +static void set_slave_broadcast_receive_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_slave_broadcast_receive *cmd = data; + + print_field("Enable: 0x%2.2x", cmd->enable); + print_bdaddr(cmd->bdaddr); + print_lt_addr(cmd->lt_addr); + print_interval(cmd->interval); + print_field("Offset: 0x%8.8x", le32_to_cpu(cmd->offset)); + print_field("Next broadcast instant: 0x%4.4x", + le16_to_cpu(cmd->instant)); + print_slot_625("Supervision timeout", cmd->timeout); + print_field("Remote timing accuracy: %d ppm", cmd->accuracy); + print_field("Skip: 0x%2.2x", cmd->skip); + print_pkt_type(cmd->pkt_type); + print_channel_map(cmd->map); +} + +static void set_slave_broadcast_receive_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_set_slave_broadcast_receive *rsp = data; + + print_status(rsp->status); + print_bdaddr(rsp->bdaddr); + print_lt_addr(rsp->lt_addr); +} + +static void receive_sync_train_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_receive_sync_train *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_timeout(cmd->timeout); + print_window(cmd->window); + print_interval(cmd->interval); +} + +static void remote_oob_ext_data_request_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_remote_oob_ext_data_request_reply *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_hash_p192(cmd->hash192); + print_randomizer_p192(cmd->randomizer192); + print_hash_p256(cmd->hash256); + print_randomizer_p256(cmd->randomizer256); +} + +static void hold_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_hold_mode *cmd = data; + + print_handle(cmd->handle); + print_slot_625("Hold max interval", cmd->max_interval); + print_slot_625("Hold min interval", cmd->min_interval); +} + +static void sniff_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_sniff_mode *cmd = data; + + print_handle(cmd->handle); + print_slot_625("Sniff max interval", cmd->max_interval); + print_slot_625("Sniff min interval", cmd->min_interval); + print_slot_125("Sniff attempt", cmd->attempt); + print_slot_125("Sniff timeout", cmd->timeout); +} + +static void exit_sniff_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_exit_sniff_mode *cmd = data; + + print_handle(cmd->handle); +} + +static void park_state_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_park_state *cmd = data; + + print_handle(cmd->handle); + print_slot_625("Beacon max interval", cmd->max_interval); + print_slot_625("Beacon min interval", cmd->min_interval); +} + +static void exit_park_state_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_exit_park_state *cmd = data; + + print_handle(cmd->handle); +} + +static void qos_setup_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_qos_setup *cmd = data; + + print_handle(cmd->handle); + print_field("Flags: 0x%2.2x", cmd->flags); + + print_service_type(cmd->service_type); + + print_field("Token rate: %d", le32_to_cpu(cmd->token_rate)); + print_field("Peak bandwidth: %d", le32_to_cpu(cmd->peak_bandwidth)); + print_field("Latency: %d", le32_to_cpu(cmd->latency)); + print_field("Delay variation: %d", le32_to_cpu(cmd->delay_variation)); +} + +static void role_discovery_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_role_discovery *cmd = data; + + print_handle(cmd->handle); +} + +static void role_discovery_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_role_discovery *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_role(rsp->role); +} + +static void switch_role_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_switch_role *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_role(cmd->role); +} + +static void read_link_policy_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_link_policy *cmd = data; + + print_handle(cmd->handle); +} + +static void read_link_policy_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_link_policy *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_link_policy(rsp->policy); +} + +static void write_link_policy_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_link_policy *cmd = data; + + print_handle(cmd->handle); + print_link_policy(cmd->policy); +} + +static void write_link_policy_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_write_link_policy *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void read_default_link_policy_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_default_link_policy *rsp = data; + + print_status(rsp->status); + print_link_policy(rsp->policy); +} + +static void write_default_link_policy_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_default_link_policy *cmd = data; + + print_link_policy(cmd->policy); +} + +static void flow_spec_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_flow_spec *cmd = data; + + print_handle(cmd->handle); + print_field("Flags: 0x%2.2x", cmd->flags); + + print_flow_direction(cmd->direction); + print_service_type(cmd->service_type); + + print_field("Token rate: %d", le32_to_cpu(cmd->token_rate)); + print_field("Token bucket size: %d", + le32_to_cpu(cmd->token_bucket_size)); + print_field("Peak bandwidth: %d", le32_to_cpu(cmd->peak_bandwidth)); + print_field("Access latency: %d", le32_to_cpu(cmd->access_latency)); +} + +static void sniff_subrating_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_sniff_subrating *cmd = data; + + print_handle(cmd->handle); + print_slot_625("Max latency", cmd->max_latency); + print_slot_625("Min remote timeout", cmd->min_remote_timeout); + print_slot_625("Min local timeout", cmd->min_local_timeout); +} + +static void sniff_subrating_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_sniff_subrating *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void set_event_mask_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_event_mask *cmd = data; + + print_event_mask(cmd->mask, events_table); +} + +static void set_event_filter_cmd(const void *data, uint8_t size) +{ + uint8_t type = *((const uint8_t *) data); + uint8_t filter; + const char *str; + + switch (type) { + case 0x00: + str = "Clear All Filters"; + break; + case 0x01: + str = "Inquiry Result"; + break; + case 0x02: + str = "Connection Setup"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, type); + + switch (type) { + case 0x00: + if (size > 1) { + print_text(COLOR_ERROR, " invalid parameter size"); + packet_hexdump(data + 1, size - 1); + } + break; + + case 0x01: + if (size < 2) { + print_text(COLOR_ERROR, " invalid parameter size"); + break; + } + filter = *((const uint8_t *) (data + 1)); + + switch (filter) { + case 0x00: + str = "Return responses from all devices"; + break; + case 0x01: + str = "Device with specific Class of Device"; + break; + case 0x02: + str = "Device with specific BD_ADDR"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Filter: %s (0x%2.2x)", str, filter); + packet_hexdump(data + 2, size - 2); + break; + + case 0x02: + filter = *((const uint8_t *) (data + 1)); + + switch (filter) { + case 0x00: + str = "Allow connections all devices"; + break; + case 0x01: + str = "Allow connections with specific Class of Device"; + break; + case 0x02: + str = "Allow connections with specific BD_ADDR"; + break; + default: + str = "Reserved"; + break; + } + + if (size < 2) { + print_text(COLOR_ERROR, " invalid parameter size"); + break; + } + + print_field("Filter: %s (0x%2.2x)", str, filter); + packet_hexdump(data + 2, size - 2); + break; + + default: + if (size < 2) { + print_text(COLOR_ERROR, " invalid parameter size"); + break; + } + + filter = *((const uint8_t *) (data + 1)); + + print_field("Filter: Reserved (0x%2.2x)", filter); + packet_hexdump(data + 2, size - 2); + break; + } +} + +static void flush_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_flush *cmd = data; + + print_handle(cmd->handle); +} + +static void flush_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_flush *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void read_pin_type_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_pin_type *rsp = data; + + print_status(rsp->status); + print_pin_type(rsp->pin_type); +} + +static void write_pin_type_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_pin_type *cmd = data; + + print_pin_type(cmd->pin_type); +} + +static void read_stored_link_key_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_stored_link_key *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_field("Read all: 0x%2.2x", cmd->read_all); +} + +static void read_stored_link_key_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_stored_link_key *rsp = data; + + print_status(rsp->status); + print_field("Max num keys: %d", le16_to_cpu(rsp->max_num_keys)); + print_field("Num keys: %d", le16_to_cpu(rsp->num_keys)); +} + +static void write_stored_link_key_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_stored_link_key *cmd = data; + + print_field("Num keys: %d", cmd->num_keys); + + packet_hexdump(data + 1, size - 1); +} + +static void write_stored_link_key_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_write_stored_link_key *rsp = data; + + print_status(rsp->status); + print_field("Num keys: %d", rsp->num_keys); +} + +static void delete_stored_link_key_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_delete_stored_link_key *cmd = data; + + print_bdaddr(cmd->bdaddr); + print_field("Delete all: 0x%2.2x", cmd->delete_all); +} + +static void delete_stored_link_key_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_delete_stored_link_key *rsp = data; + + print_status(rsp->status); + print_field("Num keys: %d", le16_to_cpu(rsp->num_keys)); +} + +static void write_local_name_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_local_name *cmd = data; + + print_name(cmd->name); +} + +static void read_local_name_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_local_name *rsp = data; + + print_status(rsp->status); + print_name(rsp->name); +} + +static void read_conn_accept_timeout_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_conn_accept_timeout *rsp = data; + + print_status(rsp->status); + print_timeout(rsp->timeout); +} + +static void write_conn_accept_timeout_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_conn_accept_timeout *cmd = data; + + print_timeout(cmd->timeout); +} + +static void read_page_timeout_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_page_timeout *rsp = data; + + print_status(rsp->status); + print_timeout(rsp->timeout); +} + +static void write_page_timeout_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_page_timeout *cmd = data; + + print_timeout(cmd->timeout); +} + +static void read_scan_enable_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_scan_enable *rsp = data; + + print_status(rsp->status); + print_scan_enable(rsp->enable); +} + +static void write_scan_enable_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_scan_enable *cmd = data; + + print_scan_enable(cmd->enable); +} + +static void read_page_scan_activity_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_page_scan_activity *rsp = data; + + print_status(rsp->status); + print_interval(rsp->interval); + print_window(rsp->window); +} + +static void write_page_scan_activity_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_page_scan_activity *cmd = data; + + print_interval(cmd->interval); + print_window(cmd->window); +} + +static void read_inquiry_scan_activity_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_inquiry_scan_activity *rsp = data; + + print_status(rsp->status); + print_interval(rsp->interval); + print_window(rsp->window); +} + +static void write_inquiry_scan_activity_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_inquiry_scan_activity *cmd = data; + + print_interval(cmd->interval); + print_window(cmd->window); +} + +static void read_auth_enable_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_auth_enable *rsp = data; + + print_status(rsp->status); + print_auth_enable(rsp->enable); +} + +static void write_auth_enable_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_auth_enable *cmd = data; + + print_auth_enable(cmd->enable); +} + +static void read_encrypt_mode_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_encrypt_mode *rsp = data; + + print_status(rsp->status); + print_encrypt_mode(rsp->mode); +} + +static void write_encrypt_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_encrypt_mode *cmd = data; + + print_encrypt_mode(cmd->mode); +} + +static void read_class_of_dev_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_class_of_dev *rsp = data; + + print_status(rsp->status); + print_dev_class(rsp->dev_class); +} + +static void write_class_of_dev_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_class_of_dev *cmd = data; + + print_dev_class(cmd->dev_class); +} + +static void read_voice_setting_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_voice_setting *rsp = data; + + print_status(rsp->status); + print_voice_setting(rsp->setting); +} + +static void write_voice_setting_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_voice_setting *cmd = data; + + print_voice_setting(cmd->setting); +} + +static void read_auto_flush_timeout_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_auto_flush_timeout *cmd = data; + + print_handle(cmd->handle); +} + +static void read_auto_flush_timeout_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_auto_flush_timeout *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_flush_timeout(rsp->timeout); +} + +static void write_auto_flush_timeout_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_auto_flush_timeout *cmd = data; + + print_handle(cmd->handle); + print_flush_timeout(cmd->timeout); +} + +static void write_auto_flush_timeout_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_write_auto_flush_timeout *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void read_num_broadcast_retrans_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_num_broadcast_retrans *rsp = data; + + print_status(rsp->status); + print_num_broadcast_retrans(rsp->num_retrans); +} + +static void write_num_broadcast_retrans_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_num_broadcast_retrans *cmd = data; + + print_num_broadcast_retrans(cmd->num_retrans); +} + +static void read_hold_mode_activity_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_hold_mode_activity *rsp = data; + + print_status(rsp->status); + print_hold_mode_activity(rsp->activity); +} + +static void write_hold_mode_activity_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_hold_mode_activity *cmd = data; + + print_hold_mode_activity(cmd->activity); +} + +static void read_tx_power_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_tx_power *cmd = data; + + print_handle(cmd->handle); + print_power_type(cmd->type); +} + +static void read_tx_power_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_tx_power *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_power_level(rsp->level, NULL); +} + +static void read_sync_flow_control_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_sync_flow_control *rsp = data; + + print_status(rsp->status); + print_enable("Flow control", rsp->enable); +} + +static void write_sync_flow_control_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_sync_flow_control *cmd = data; + + print_enable("Flow control", cmd->enable); +} + +static void set_host_flow_control_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_host_flow_control *cmd = data; + + print_host_flow_control(cmd->enable); +} + +static void host_buffer_size_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_host_buffer_size *cmd = data; + + print_field("ACL MTU: %-4d ACL max packet: %d", + le16_to_cpu(cmd->acl_mtu), + le16_to_cpu(cmd->acl_max_pkt)); + print_field("SCO MTU: %-4d SCO max packet: %d", + cmd->sco_mtu, + le16_to_cpu(cmd->sco_max_pkt)); +} + +static void host_num_completed_packets_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_host_num_completed_packets *cmd = data; + + print_field("Num handles: %d", cmd->num_handles); + print_handle(cmd->handle); + print_field("Count: %d", le16_to_cpu(cmd->count)); + + if (size > sizeof(*cmd)) + packet_hexdump(data + sizeof(*cmd), size - sizeof(*cmd)); +} + +static void read_link_supv_timeout_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_link_supv_timeout *cmd = data; + + print_handle(cmd->handle); +} + +static void read_link_supv_timeout_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_link_supv_timeout *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_timeout(rsp->timeout); +} + +static void write_link_supv_timeout_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_link_supv_timeout *cmd = data; + + print_handle(cmd->handle); + print_timeout(cmd->timeout); +} + +static void write_link_supv_timeout_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_write_link_supv_timeout *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void read_num_supported_iac_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_num_supported_iac *rsp = data; + + print_status(rsp->status); + print_field("Number of IAC: %d", rsp->num_iac); +} + +static void read_current_iac_lap_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_current_iac_lap *rsp = data; + uint8_t i; + + print_status(rsp->status); + print_field("Number of IAC: %d", rsp->num_iac); + + for (i = 0; i < rsp->num_iac; i++) + print_iac(rsp->iac_lap + (i * 3)); +} + +static void write_current_iac_lap_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_current_iac_lap *cmd = data; + uint8_t i; + + print_field("Number of IAC: %d", cmd->num_iac); + + for (i = 0; i < cmd->num_iac; i++) + print_iac(cmd->iac_lap + (i * 3)); +} + +static void read_page_scan_period_mode_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_page_scan_period_mode *rsp = data; + + print_status(rsp->status); + print_pscan_period_mode(rsp->mode); +} + +static void write_page_scan_period_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_page_scan_period_mode *cmd = data; + + print_pscan_period_mode(cmd->mode); +} + +static void read_page_scan_mode_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_page_scan_mode *rsp = data; + + print_status(rsp->status); + print_pscan_mode(rsp->mode); +} + +static void write_page_scan_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_page_scan_mode *cmd = data; + + print_pscan_mode(cmd->mode); +} + +static void set_afh_host_classification_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_afh_host_classification *cmd = data; + + print_channel_map(cmd->map); +} + +static void read_inquiry_scan_type_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_inquiry_scan_type *rsp = data; + + print_status(rsp->status); + print_inquiry_scan_type(rsp->type); +} + +static void write_inquiry_scan_type_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_inquiry_scan_type *cmd = data; + + print_inquiry_scan_type(cmd->type); +} + +static void read_inquiry_mode_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_inquiry_mode *rsp = data; + + print_status(rsp->status); + print_inquiry_mode(rsp->mode); +} + +static void write_inquiry_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_inquiry_mode *cmd = data; + + print_inquiry_mode(cmd->mode); +} + +static void read_page_scan_type_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_page_scan_type *rsp = data; + + print_status(rsp->status); + print_pscan_type(rsp->type); +} + +static void write_page_scan_type_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_page_scan_type *cmd = data; + + print_pscan_type(cmd->type); +} + +static void read_afh_assessment_mode_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_afh_assessment_mode *rsp = data; + + print_status(rsp->status); + print_enable("Mode", rsp->mode); +} + +static void write_afh_assessment_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_afh_assessment_mode *cmd = data; + + print_enable("Mode", cmd->mode); +} + +static void read_ext_inquiry_response_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_ext_inquiry_response *rsp = data; + + print_status(rsp->status); + print_fec(rsp->fec); + print_eir(rsp->data, sizeof(rsp->data), false); +} + +static void write_ext_inquiry_response_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_ext_inquiry_response *cmd = data; + + print_fec(cmd->fec); + print_eir(cmd->data, sizeof(cmd->data), false); +} + +static void refresh_encrypt_key_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_refresh_encrypt_key *cmd = data; + + print_handle(cmd->handle); +} + +static void read_simple_pairing_mode_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_simple_pairing_mode *rsp = data; + + print_status(rsp->status); + print_enable("Mode", rsp->mode); +} + +static void write_simple_pairing_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_simple_pairing_mode *cmd = data; + + print_enable("Mode", cmd->mode); +} + +static void read_local_oob_data_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_local_oob_data *rsp = data; + + print_status(rsp->status); + print_hash_p192(rsp->hash); + print_randomizer_p192(rsp->randomizer); +} + +static void read_inquiry_resp_tx_power_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_inquiry_resp_tx_power *rsp = data; + + print_status(rsp->status); + print_power_level(rsp->level, NULL); +} + +static void write_inquiry_tx_power_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_inquiry_tx_power *cmd = data; + + print_power_level(cmd->level, NULL); +} + +static void read_erroneous_reporting_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_erroneous_reporting *rsp = data; + + print_status(rsp->status); + print_enable("Mode", rsp->mode); +} + +static void write_erroneous_reporting_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_erroneous_reporting *cmd = data; + + print_enable("Mode", cmd->mode); +} + +static void enhanced_flush_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_enhanced_flush *cmd = data; + const char *str; + + print_handle(cmd->handle); + + switch (cmd->type) { + case 0x00: + str = "Automatic flushable only"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, cmd->type); +} + +static void send_keypress_notify_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_send_keypress_notify *cmd = data; + const char *str; + + print_bdaddr(cmd->bdaddr); + + switch (cmd->type) { + case 0x00: + str = "Passkey entry started"; + break; + case 0x01: + str = "Passkey digit entered"; + break; + case 0x02: + str = "Passkey digit erased"; + break; + case 0x03: + str = "Passkey cleared"; + break; + case 0x04: + str = "Passkey entry completed"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, cmd->type); +} + +static void send_keypress_notify_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_send_keypress_notify *rsp = data; + + print_status(rsp->status); + print_bdaddr(rsp->bdaddr); +} + +static void set_event_mask_page2_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_event_mask_page2 *cmd = data; + + print_event_mask(cmd->mask, events_page2_table); +} + +static void read_location_data_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_location_data *rsp = data; + + print_status(rsp->status); + print_location_domain_aware(rsp->domain_aware); + print_location_domain(rsp->domain); + print_location_domain_options(rsp->domain_options); + print_location_options(rsp->options); +} + +static void write_location_data_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_location_data *cmd = data; + + print_location_domain_aware(cmd->domain_aware); + print_location_domain(cmd->domain); + print_location_domain_options(cmd->domain_options); + print_location_options(cmd->options); +} + +static void read_flow_control_mode_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_flow_control_mode *rsp = data; + + print_status(rsp->status); + print_flow_control_mode(rsp->mode); +} + +static void write_flow_control_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_flow_control_mode *cmd = data; + + print_flow_control_mode(cmd->mode); +} + +static void read_enhanced_tx_power_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_enhanced_tx_power *cmd = data; + + print_handle(cmd->handle); + print_power_type(cmd->type); +} + +static void read_enhanced_tx_power_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_enhanced_tx_power *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_power_level(rsp->level_gfsk, "GFSK"); + print_power_level(rsp->level_dqpsk, "DQPSK"); + print_power_level(rsp->level_8dpsk, "8DPSK"); +} + +static void short_range_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_short_range_mode *cmd = data; + + print_phy_handle(cmd->phy_handle); + print_enable("Short range mode", cmd->mode); +} + +static void read_le_host_supported_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_le_host_supported *rsp = data; + + print_status(rsp->status); + print_field("Supported: 0x%2.2x", rsp->supported); + print_field("Simultaneous: 0x%2.2x", rsp->simultaneous); +} + +static void write_le_host_supported_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_le_host_supported *cmd = data; + + print_field("Supported: 0x%2.2x", cmd->supported); + print_field("Simultaneous: 0x%2.2x", cmd->simultaneous); +} + +static void set_reserved_lt_addr_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_reserved_lt_addr *cmd = data; + + print_lt_addr(cmd->lt_addr); +} + +static void set_reserved_lt_addr_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_set_reserved_lt_addr *rsp = data; + + print_status(rsp->status); + print_lt_addr(rsp->lt_addr); +} + +static void delete_reserved_lt_addr_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_delete_reserved_lt_addr *cmd = data; + + print_lt_addr(cmd->lt_addr); +} + +static void delete_reserved_lt_addr_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_delete_reserved_lt_addr *rsp = data; + + print_status(rsp->status); + print_lt_addr(rsp->lt_addr); +} + +static void set_slave_broadcast_data_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_slave_broadcast_data *cmd = data; + + print_lt_addr(cmd->lt_addr); + print_broadcast_fragment(cmd->fragment); + print_field("Length: %d", cmd->length); + + if (size - 3 != cmd->length) + print_text(COLOR_ERROR, "invalid data size (%d != %d)", + size - 3, cmd->length); + + packet_hexdump(data + 3, size - 3); +} + +static void set_slave_broadcast_data_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_set_slave_broadcast_data *rsp = data; + + print_status(rsp->status); + print_lt_addr(rsp->lt_addr); +} + +static void read_sync_train_params_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_sync_train_params *rsp = data; + + print_status(rsp->status); + print_interval(rsp->interval); + print_field("Timeout: %.3f msec (0x%8.8x)", + le32_to_cpu(rsp->timeout) * 0.625, + le32_to_cpu(rsp->timeout)); + print_field("Service data: 0x%2.2x", rsp->service_data); +} + +static void write_sync_train_params_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_sync_train_params *cmd = data; + + print_slot_625("Min interval", cmd->min_interval); + print_slot_625("Max interval", cmd->max_interval); + print_field("Timeout: %.3f msec (0x%8.8x)", + le32_to_cpu(cmd->timeout) * 0.625, + le32_to_cpu(cmd->timeout)); + print_field("Service data: 0x%2.2x", cmd->service_data); +} + +static void write_sync_train_params_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_write_sync_train_params *rsp = data; + + print_status(rsp->status); + print_interval(rsp->interval); +} + +static void read_secure_conn_support_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_secure_conn_support *rsp = data; + + print_status(rsp->status); + print_enable("Support", rsp->support); +} + +static void write_secure_conn_support_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_secure_conn_support *cmd = data; + + print_enable("Support", cmd->support); +} + +static void read_auth_payload_timeout_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_auth_payload_timeout *cmd = data; + + print_handle(cmd->handle); +} + +static void read_auth_payload_timeout_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_auth_payload_timeout *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_auth_payload_timeout(rsp->timeout); +} + +static void write_auth_payload_timeout_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_auth_payload_timeout *cmd = data; + + print_handle(cmd->handle); + print_auth_payload_timeout(cmd->timeout); +} + +static void write_auth_payload_timeout_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_write_auth_payload_timeout *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void read_local_oob_ext_data_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_local_oob_ext_data *rsp = data; + + print_status(rsp->status); + print_hash_p192(rsp->hash192); + print_randomizer_p192(rsp->randomizer192); + print_hash_p256(rsp->hash256); + print_randomizer_p256(rsp->randomizer256); +} + +static void read_ext_page_timeout_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_ext_page_timeout *rsp = data; + + print_status(rsp->status); + print_timeout(rsp->timeout); +} + +static void write_ext_page_timeout_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_ext_page_timeout *cmd = data; + + print_timeout(cmd->timeout); +} + +static void read_ext_inquiry_length_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_ext_inquiry_length *rsp = data; + + print_status(rsp->status); + print_interval(rsp->interval); +} + +static void write_ext_inquiry_length_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_ext_inquiry_length *cmd = data; + + print_interval(cmd->interval); +} + +static void read_local_version_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_local_version *rsp = data; + uint16_t manufacturer; + + print_status(rsp->status); + print_hci_version(rsp->hci_ver, rsp->hci_rev); + + manufacturer = le16_to_cpu(rsp->manufacturer); + + if (index_current < MAX_INDEX) { + switch (index_list[index_current].type) { + case HCI_PRIMARY: + print_lmp_version(rsp->lmp_ver, rsp->lmp_subver); + break; + case HCI_AMP: + print_pal_version(rsp->lmp_ver, rsp->lmp_subver); + break; + } + + index_list[index_current].manufacturer = manufacturer; + } + + print_manufacturer(rsp->manufacturer); + + switch (manufacturer) { + case 15: + print_manufacturer_broadcom(rsp->lmp_subver, rsp->hci_rev); + break; + } +} + +static void read_local_commands_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_local_commands *rsp = data; + + print_status(rsp->status); + print_commands(rsp->commands); +} + +static void read_local_features_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_local_features *rsp = data; + + print_status(rsp->status); + print_features(0, rsp->features, 0x00); +} + +static void read_local_ext_features_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_local_ext_features *cmd = data; + + print_field("Page: %d", cmd->page); +} + +static void read_local_ext_features_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_local_ext_features *rsp = data; + + print_status(rsp->status); + print_field("Page: %d/%d", rsp->page, rsp->max_page); + print_features(rsp->page, rsp->features, 0x00); +} + +static void read_buffer_size_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_buffer_size *rsp = data; + + print_status(rsp->status); + print_field("ACL MTU: %-4d ACL max packet: %d", + le16_to_cpu(rsp->acl_mtu), + le16_to_cpu(rsp->acl_max_pkt)); + print_field("SCO MTU: %-4d SCO max packet: %d", + rsp->sco_mtu, + le16_to_cpu(rsp->sco_max_pkt)); +} + +static void read_country_code_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_country_code *rsp = data; + const char *str; + + print_status(rsp->status); + + switch (rsp->code) { + case 0x00: + str = "North America, Europe*, Japan"; + break; + case 0x01: + str = "France"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Country code: %s (0x%2.2x)", str, rsp->code); +} + +static void read_bd_addr_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_bd_addr *rsp = data; + + print_status(rsp->status); + print_bdaddr(rsp->bdaddr); + + if (index_current < MAX_INDEX) + memcpy(index_list[index_current].bdaddr, rsp->bdaddr, 6); +} + +static void read_data_block_size_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_data_block_size *rsp = data; + + print_status(rsp->status); + print_field("Max ACL length: %d", le16_to_cpu(rsp->max_acl_len)); + print_field("Block length: %d", le16_to_cpu(rsp->block_len)); + print_field("Num blocks: %d", le16_to_cpu(rsp->num_blocks)); +} + +static void read_local_codecs_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_local_codecs *rsp = data; + uint8_t i, num_vnd_codecs; + + if (rsp->num_codecs + 3 > size) { + print_field("Invalid number of codecs."); + return; + } + + print_status(rsp->status); + print_field("Number of supported codecs: %d", rsp->num_codecs); + + for (i = 0; i < rsp->num_codecs; i++) + print_codec(" Codec", rsp->codec[i]); + + num_vnd_codecs = rsp->codec[rsp->num_codecs]; + + print_field("Number of vendor codecs: %d", num_vnd_codecs); + + packet_hexdump(data + rsp->num_codecs + 3, + size - rsp->num_codecs - 3); +} + +static void read_failed_contact_counter_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_failed_contact_counter *cmd = data; + + print_handle(cmd->handle); +} + +static void read_failed_contact_counter_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_failed_contact_counter *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_field("Counter: %u", le16_to_cpu(rsp->counter)); +} + +static void reset_failed_contact_counter_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_reset_failed_contact_counter *cmd = data; + + print_handle(cmd->handle); +} + +static void reset_failed_contact_counter_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_reset_failed_contact_counter *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void read_link_quality_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_link_quality *cmd = data; + + print_handle(cmd->handle); +} + +static void read_link_quality_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_link_quality *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_field("Link quality: 0x%2.2x", rsp->link_quality); +} + +static void read_rssi_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_rssi *cmd = data; + + print_handle(cmd->handle); +} + +static void read_rssi_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_rssi *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_rssi(rsp->rssi); +} + +static void read_afh_channel_map_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_afh_channel_map *cmd = data; + + print_handle(cmd->handle); +} + +static void read_afh_channel_map_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_afh_channel_map *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_enable("Mode", rsp->mode); + print_channel_map(rsp->map); +} + +static void read_clock_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_clock *cmd = data; + + print_handle(cmd->handle); + print_clock_type(cmd->type); +} + +static void read_clock_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_clock *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_clock(rsp->clock); + print_clock_accuracy(rsp->accuracy); +} + +static void read_encrypt_key_size_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_encrypt_key_size *cmd = data; + + print_handle(cmd->handle); +} + +static void read_encrypt_key_size_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_encrypt_key_size *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_key_size(rsp->key_size); +} + +static void read_local_amp_info_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_local_amp_info *rsp = data; + const char *str; + + print_status(rsp->status); + print_amp_status(rsp->amp_status); + + print_field("Total bandwidth: %d kbps", le32_to_cpu(rsp->total_bw)); + print_field("Max guaranteed bandwidth: %d kbps", + le32_to_cpu(rsp->max_bw)); + print_field("Min latency: %d", le32_to_cpu(rsp->min_latency)); + print_field("Max PDU size: %d", le32_to_cpu(rsp->max_pdu)); + + switch (rsp->amp_type) { + case 0x00: + str = "Primary BR/EDR Controller"; + break; + case 0x01: + str = "802.11 AMP Controller"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Controller type: %s (0x%2.2x)", str, rsp->amp_type); + + print_field("PAL capabilities: 0x%4.4x", le16_to_cpu(rsp->pal_cap)); + print_field("Max ASSOC length: %d", le16_to_cpu(rsp->max_assoc_len)); + print_field("Max flush timeout: %d", le32_to_cpu(rsp->max_flush_to)); + print_field("Best effort flush timeout: %d", + le32_to_cpu(rsp->be_flush_to)); +} + +static void read_local_amp_assoc_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_read_local_amp_assoc *cmd = data; + + print_phy_handle(cmd->phy_handle); + print_field("Length so far: %d", le16_to_cpu(cmd->len_so_far)); + print_field("Max ASSOC length: %d", le16_to_cpu(cmd->max_assoc_len)); +} + +static void read_local_amp_assoc_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_local_amp_assoc *rsp = data; + + print_status(rsp->status); + print_phy_handle(rsp->phy_handle); + print_field("Remaining ASSOC length: %d", + le16_to_cpu(rsp->remain_assoc_len)); + + packet_hexdump(data + 4, size - 4); +} + +static void write_remote_amp_assoc_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_remote_amp_assoc *cmd = data; + + print_phy_handle(cmd->phy_handle); + print_field("Length so far: %d", le16_to_cpu(cmd->len_so_far)); + print_field("Remaining ASSOC length: %d", + le16_to_cpu(cmd->remain_assoc_len)); + + packet_hexdump(data + 5, size - 5); +} + +static void write_remote_amp_assoc_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_write_remote_amp_assoc *rsp = data; + + print_status(rsp->status); + print_phy_handle(rsp->phy_handle); +} + +static void get_mws_transport_config_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_get_mws_transport_config *rsp = data; + uint8_t sum_baud_rates = 0; + int i; + + print_status(rsp->status); + print_field("Number of transports: %d", rsp->num_transports); + + for (i = 0; i < rsp->num_transports; i++) { + uint8_t transport = rsp->transport[0]; + uint8_t num_baud_rates = rsp->transport[1]; + const char *str; + + switch (transport) { + case 0x00: + str = "Disbabled"; + break; + case 0x01: + str = "WCI-1"; + break; + case 0x02: + str = "WCI-2"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" Transport layer: %s (0x%2.2x)", str, transport); + print_field(" Number of baud rates: %d", num_baud_rates); + + sum_baud_rates += num_baud_rates; + } + + print_field("Baud rate list: %u entr%s", sum_baud_rates, + sum_baud_rates == 1 ? "y" : "ies"); + + for (i = 0; i < sum_baud_rates; i++) { + uint32_t to_baud_rate, from_baud_rate; + + to_baud_rate = get_le32(data + 2 + + rsp->num_transports * 2 + i * 4); + from_baud_rate = get_le32(data + 2 + + rsp->num_transports * 2 + + sum_baud_rates * 4 + i * 4); + + print_field(" Bluetooth to MWS: %d", to_baud_rate); + print_field(" MWS to Bluetooth: %d", from_baud_rate); + } + + packet_hexdump(data + 2 + rsp->num_transports * 2 + sum_baud_rates * 8, + size - 2 - rsp->num_transports * 2 - sum_baud_rates * 8); +} + +static void set_triggered_clock_capture_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_set_triggered_clock_capture *cmd = data; + + print_handle(cmd->handle); + print_enable("Capture", cmd->enable); + print_clock_type(cmd->type); + print_lpo_allowed(cmd->lpo_allowed); + print_field("Clock captures to filter: %u", cmd->num_filter); +} + +static void read_loopback_mode_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_read_loopback_mode *rsp = data; + + print_status(rsp->status); + print_loopback_mode(rsp->mode); +} + +static void write_loopback_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_loopback_mode *cmd = data; + + print_loopback_mode(cmd->mode); +} + +static void write_ssp_debug_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_write_ssp_debug_mode *cmd = data; + + print_enable("Debug Mode", cmd->mode); +} + +static void le_set_event_mask_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_event_mask *cmd = data; + + print_event_mask(cmd->mask, events_le_table); +} + +static void le_read_buffer_size_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_buffer_size *rsp = data; + + print_status(rsp->status); + print_field("Data packet length: %d", le16_to_cpu(rsp->le_mtu)); + print_field("Num data packets: %d", rsp->le_max_pkt); +} + +static void le_read_local_features_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_local_features *rsp = data; + + print_status(rsp->status); + print_features(0, rsp->features, 0x01); +} + +static void le_set_random_address_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_random_address *cmd = data; + + print_addr("Address", cmd->addr, 0x01); +} + +static void le_set_adv_parameters_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_adv_parameters *cmd = data; + const char *str; + + print_slot_625("Min advertising interval", cmd->min_interval); + print_slot_625("Max advertising interval", cmd->max_interval); + + switch (cmd->type) { + case 0x00: + str = "Connectable undirected - ADV_IND"; + break; + case 0x01: + str = "Connectable directed - ADV_DIRECT_IND (high duty cycle)"; + break; + case 0x02: + str = "Scannable undirected - ADV_SCAN_IND"; + break; + case 0x03: + str = "Non connectable undirected - ADV_NONCONN_IND"; + break; + case 0x04: + str = "Connectable directed - ADV_DIRECT_IND (low duty cycle)"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Type: %s (0x%2.2x)", str, cmd->type); + + print_own_addr_type(cmd->own_addr_type); + print_addr_type("Direct address type", cmd->direct_addr_type); + print_addr("Direct address", cmd->direct_addr, cmd->direct_addr_type); + print_adv_channel_map("Channel map", cmd->channel_map); + print_adv_filter_policy("Filter policy", cmd->filter_policy); +} + +static void le_read_adv_tx_power_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_adv_tx_power *rsp = data; + + print_status(rsp->status); + print_power_level(rsp->level, NULL); +} + +static void le_set_adv_data_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_adv_data *cmd = data; + + print_field("Length: %d", cmd->len); + print_eir(cmd->data, cmd->len, true); +} + +static void le_set_scan_rsp_data_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_scan_rsp_data *cmd = data; + + print_field("Length: %d", cmd->len); + print_eir(cmd->data, cmd->len, true); +} + +static void le_set_adv_enable_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_adv_enable *cmd = data; + + print_enable("Advertising", cmd->enable); +} + +static void print_scan_type(const char *label, uint8_t type) +{ + const char *str; + + switch (type) { + case 0x00: + str = "Passive"; + break; + case 0x01: + str = "Active"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s: %s (0x%2.2x)", label, str, type); +} + +static void print_scan_filter_policy(uint8_t policy) +{ + const char *str; + + switch (policy) { + case 0x00: + str = "Accept all advertisement"; + break; + case 0x01: + str = "Ignore not in white list"; + break; + case 0x02: + str = "Accept all advertisement, inc. directed unresolved RPA"; + break; + case 0x03: + str = "Ignore not in white list, exc. directed unresolved RPA"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Filter policy: %s (0x%2.2x)", str, policy); +} + +static void le_set_scan_parameters_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_scan_parameters *cmd = data; + + print_scan_type("Type", cmd->type); + print_interval(cmd->interval); + print_window(cmd->window); + print_own_addr_type(cmd->own_addr_type); + print_scan_filter_policy(cmd->filter_policy); +} + +static void le_set_scan_enable_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_scan_enable *cmd = data; + + print_enable("Scanning", cmd->enable); + print_enable("Filter duplicates", cmd->filter_dup); +} + +static void le_create_conn_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_create_conn *cmd = data; + const char *str; + + print_slot_625("Scan interval", cmd->scan_interval); + print_slot_625("Scan window", cmd->scan_window); + + switch (cmd->filter_policy) { + case 0x00: + str = "White list is not used"; + break; + case 0x01: + str = "White list is used"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Filter policy: %s (0x%2.2x)", str, cmd->filter_policy); + + print_peer_addr_type("Peer address type", cmd->peer_addr_type); + print_addr("Peer address", cmd->peer_addr, cmd->peer_addr_type); + print_own_addr_type(cmd->own_addr_type); + + print_slot_125("Min connection interval", cmd->min_interval); + print_slot_125("Max connection interval", cmd->max_interval); + print_conn_latency("Connection latency", cmd->latency); + print_field("Supervision timeout: %d msec (0x%4.4x)", + le16_to_cpu(cmd->supv_timeout) * 10, + le16_to_cpu(cmd->supv_timeout)); + print_slot_625("Min connection length", cmd->min_length); + print_slot_625("Max connection length", cmd->max_length); +} + +static void le_read_white_list_size_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_white_list_size *rsp = data; + + print_status(rsp->status); + print_field("Size: %u", rsp->size); +} + +static void le_add_to_white_list_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_add_to_white_list *cmd = data; + + print_addr_type("Address type", cmd->addr_type); + print_addr("Address", cmd->addr, cmd->addr_type); +} + +static void le_remove_from_white_list_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_remove_from_white_list *cmd = data; + + print_addr_type("Address type", cmd->addr_type); + print_addr("Address", cmd->addr, cmd->addr_type); +} + +static void le_conn_update_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_conn_update *cmd = data; + + print_handle(cmd->handle); + print_slot_125("Min connection interval", cmd->min_interval); + print_slot_125("Max connection interval", cmd->max_interval); + print_conn_latency("Connection latency", cmd->latency); + print_field("Supervision timeout: %d msec (0x%4.4x)", + le16_to_cpu(cmd->supv_timeout) * 10, + le16_to_cpu(cmd->supv_timeout)); + print_slot_625("Min connection length", cmd->min_length); + print_slot_625("Max connection length", cmd->max_length); +} + +static void le_set_host_classification_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_host_classification *cmd = data; + + print_le_channel_map(cmd->map); +} + +static void le_read_channel_map_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_read_channel_map *cmd = data; + + print_handle(cmd->handle); +} + +static void le_read_channel_map_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_channel_map *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_le_channel_map(rsp->map); +} + +static void le_read_remote_features_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_read_remote_features *cmd = data; + + print_handle(cmd->handle); +} + +static void le_encrypt_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_encrypt *cmd = data; + + print_key("Key", cmd->key); + print_key("Plaintext data", cmd->plaintext); +} + +static void le_encrypt_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_encrypt *rsp = data; + + print_status(rsp->status); + print_key("Encrypted data", rsp->data); +} + +static void le_rand_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_rand *rsp = data; + + print_status(rsp->status); + print_random_number(rsp->number); +} + +static void le_start_encrypt_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_start_encrypt *cmd = data; + + print_handle(cmd->handle); + print_random_number(cmd->rand); + print_encrypted_diversifier(cmd->ediv); + print_key("Long term key", cmd->ltk); +} + +static void le_ltk_req_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_ltk_req_reply *cmd = data; + + print_handle(cmd->handle); + print_key("Long term key", cmd->ltk); +} + +static void le_ltk_req_reply_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_ltk_req_reply *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void le_ltk_req_neg_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_ltk_req_neg_reply *cmd = data; + + print_handle(cmd->handle); +} + +static void le_ltk_req_neg_reply_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_ltk_req_neg_reply *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void le_read_supported_states_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_supported_states *rsp = data; + + print_status(rsp->status); + print_le_states(rsp->states); +} + +static void le_receiver_test_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_receiver_test *cmd = data; + + print_field("RX frequency: %d MHz (0x%2.2x)", + (cmd->frequency * 2) + 2402, cmd->frequency); +} + +static void le_transmitter_test_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_transmitter_test *cmd = data; + + print_field("TX frequency: %d MHz (0x%2.2x)", + (cmd->frequency * 2) + 2402, cmd->frequency); + print_field("Test data length: %d bytes", cmd->data_len); + print_field("Packet payload: 0x%2.2x", cmd->payload); +} + +static void le_test_end_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_test_end *rsp = data; + + print_status(rsp->status); + print_field("Number of packets: %d", le16_to_cpu(rsp->num_packets)); +} + +static void le_conn_param_req_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_conn_param_req_reply *cmd = data; + + print_handle(cmd->handle); + print_slot_125("Min connection interval", cmd->min_interval); + print_slot_125("Max connection interval", cmd->max_interval); + print_conn_latency("Connection latency", cmd->latency); + print_field("Supervision timeout: %d msec (0x%4.4x)", + le16_to_cpu(cmd->supv_timeout) * 10, + le16_to_cpu(cmd->supv_timeout)); + print_slot_625("Min connection length", cmd->min_length); + print_slot_625("Max connection length", cmd->max_length); +} + +static void le_conn_param_req_reply_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_conn_param_req_reply *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void le_conn_param_req_neg_reply_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_conn_param_req_neg_reply *cmd = data; + + print_handle(cmd->handle); + print_reason(cmd->reason); +} + +static void le_conn_param_req_neg_reply_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_conn_param_req_neg_reply *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void le_set_data_length_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_data_length *cmd = data; + + print_handle(cmd->handle); + print_field("TX octets: %d", le16_to_cpu(cmd->tx_len)); + print_field("TX time: %d", le16_to_cpu(cmd->tx_time)); +} + +static void le_set_data_length_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_set_data_length *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); +} + +static void le_read_default_data_length_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_default_data_length *rsp = data; + + print_status(rsp->status); + print_field("TX octets: %d", le16_to_cpu(rsp->tx_len)); + print_field("TX time: %d", le16_to_cpu(rsp->tx_time)); +} + +static void le_write_default_data_length_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_write_default_data_length *cmd = data; + + print_field("TX octets: %d", le16_to_cpu(cmd->tx_len)); + print_field("TX time: %d", le16_to_cpu(cmd->tx_time)); +} + +static void le_generate_dhkey_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_generate_dhkey *cmd = data; + + print_pk256("Remote P-256 public key", cmd->remote_pk256); +} + +static void le_add_to_resolv_list_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_add_to_resolv_list *cmd = data; + + print_addr_type("Address type", cmd->addr_type); + print_addr("Address", cmd->addr, cmd->addr_type); + print_key("Peer identity resolving key", cmd->peer_irk); + print_key("Local identity resolving key", cmd->local_irk); +} + +static void le_remove_from_resolv_list_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_remove_from_resolv_list *cmd = data; + + print_addr_type("Address type", cmd->addr_type); + print_addr("Address", cmd->addr, cmd->addr_type); +} + +static void le_read_resolv_list_size_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_resolv_list_size *rsp = data; + + print_status(rsp->status); + print_field("Size: %u", rsp->size); +} + +static void le_read_peer_resolv_addr_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_read_peer_resolv_addr *cmd = data; + + print_addr_type("Address type", cmd->addr_type); + print_addr("Address", cmd->addr, cmd->addr_type); +} + +static void le_read_peer_resolv_addr_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_peer_resolv_addr *rsp = data; + + print_status(rsp->status); + print_addr("Address", rsp->addr, 0x01); +} + +static void le_read_local_resolv_addr_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_read_local_resolv_addr *cmd = data; + + print_addr_type("Address type", cmd->addr_type); + print_addr("Address", cmd->addr, cmd->addr_type); +} + +static void le_read_local_resolv_addr_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_local_resolv_addr *rsp = data; + + print_status(rsp->status); + print_addr("Address", rsp->addr, 0x01); +} + +static void le_set_resolv_enable_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_resolv_enable *cmd = data; + + print_enable("Address resolution", cmd->enable); +} + +static void le_set_resolv_timeout_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_resolv_timeout *cmd = data; + + print_field("Timeout: %u seconds", le16_to_cpu(cmd->timeout)); +} + +static void le_read_max_data_length_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_max_data_length *rsp = data; + + print_status(rsp->status); + print_field("Max TX octets: %d", le16_to_cpu(rsp->max_tx_len)); + print_field("Max TX time: %d", le16_to_cpu(rsp->max_tx_time)); + print_field("Max RX octets: %d", le16_to_cpu(rsp->max_rx_len)); + print_field("Max RX time: %d", le16_to_cpu(rsp->max_rx_time)); +} + +static void le_read_phy_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_read_phy *cmd = data; + + print_handle(cmd->handle); +} + +static void print_le_phy(const char *prefix, uint8_t phy) +{ + const char *str; + + switch (phy) { + case 0x01: + str = "LE 1M"; + break; + case 0x02: + str = "LE 2M"; + break; + case 0x03: + str = "LE Coded"; + break; + default: + str = "Reserved"; + break; + } + + print_field("%s: %s (0x%2.2x)", prefix, str, phy); +} + +static void le_read_phy_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_phy *rsp = data; + + print_status(rsp->status); + print_handle(rsp->handle); + print_le_phy("TX PHY", rsp->tx_phy); + print_le_phy("RX PHY", rsp->rx_phy); +} + +static const struct bitfield_data le_phys[] = { + { 0, "LE 1M" }, + { 1, "LE 2M" }, + { 2, "LE Coded"}, + { } +}; + +static const struct bitfield_data le_phy_preference[] = { + { 0, "No TX PHY preference" }, + { 1, "No RX PHY preference" }, + { } +}; + +static void print_le_phys_preference(uint8_t all_phys, uint8_t tx_phys, + uint8_t rx_phys) +{ + uint8_t mask; + + print_field("All PHYs preference: 0x%2.2x", all_phys); + + mask = print_bitfield(2, all_phys, le_phy_preference); + if (mask) + print_text(COLOR_UNKNOWN_OPTIONS_BIT, " Reserved" + " (0x%2.2x)", mask); + + print_field("TX PHYs preference: 0x%2.2x", tx_phys); + mask = tx_phys; + + mask = print_bitfield(2, tx_phys, le_phys); + if (mask) + print_text(COLOR_UNKNOWN_OPTIONS_BIT, " Reserved" + " (0x%2.2x)", mask); + + print_field("RX PHYs preference: 0x%2.2x", rx_phys); + mask = rx_phys; + + mask = print_bitfield(2, rx_phys, le_phys); + if (mask) + print_text(COLOR_UNKNOWN_OPTIONS_BIT, " Reserved" + " (0x%2.2x)", mask); +} + +static void le_set_default_phy_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_default_phy *cmd = data; + + print_le_phys_preference(cmd->all_phys, cmd->tx_phys, cmd->rx_phys); +} + +static void le_set_phy_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_phy *cmd = data; + const char *str; + + print_handle(cmd->handle); + print_le_phys_preference(cmd->all_phys, cmd->tx_phys, cmd->rx_phys); + switch (le16_to_cpu(cmd->phy_opts)) { + case 0x0001: + str = "S2 coding"; + break; + case 0x0002: + str = "S8 coding"; + break; + default: + str = "Reserved"; + break; + } + + print_field("PHY options preference: %s (0x%4.4x)", str, cmd->phy_opts); +} + +static void le_enhanced_receiver_test_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_enhanced_receiver_test *cmd = data; + const char *str; + + print_field("RX channel frequency: %d MHz (0x%2.2x)", + (cmd->rx_channel * 2) + 2402, cmd->rx_channel); + print_le_phy("PHY", cmd->phy); + + switch (cmd->modulation_index) { + case 0x00: + str = "Standard"; + break; + case 0x01: + str = "Stable"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Modulation index: %s (0x%2.2x)", str, + cmd->modulation_index); +} + +static void le_enhanced_transmitter_test_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_enhanced_transmitter_test *cmd = data; + const char *str; + + print_field("TX channel frequency: %d MHz (0x%2.2x)", + (cmd->tx_channel * 2) + 2402, cmd->tx_channel); + print_field("Test data length: %d bytes", cmd->data_len); + print_field("Packet payload: 0x%2.2x", cmd->payload); + + switch (cmd->phy) { + case 0x01: + str = "LE 1M"; + break; + case 0x02: + str = "LE 2M"; + break; + case 0x03: + str = "LE Coded with S=8"; + break; + case 0x04: + str = "LE Coded with S=2"; + break; + default: + str = "Reserved"; + break; + } + + print_field("PHY: %s (0x%2.2x)", str, cmd->phy); +} + +static void le_set_adv_set_rand_addr(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_adv_set_rand_addr *cmd = data; + + print_field("Advertising handle: 0x%2.2x", cmd->handle); + print_addr("Advertising random address", cmd->bdaddr, 0x00); +} + +static const struct bitfield_data ext_adv_properties_table[] = { + { 0, "Connectable" }, + { 1, "Scannable" }, + { 2, "Directed" }, + { 3, "High Duty Cycle Directed Connectable" }, + { 4, "Use legacy advertising PDUs" }, + { 5, "Anonymous advertising" }, + { 6, "Include TxPower" }, + { } +}; + +static const char *get_adv_pdu_desc(uint16_t flags) +{ + const char *str; + + switch (flags) { + case 0x10: + str = "ADV_NONCONN_IND"; + break; + case 0x12: + str = "ADV_SCAN_IND"; + break; + case 0x13: + str = "ADV_IND"; + break; + case 0x15: + str = "ADV_DIRECT_IND (low duty cycle)"; + break; + case 0x1d: + str = "ADV_DIRECT_IND (high duty cycle)"; + break; + default: + str = "Reserved"; + break; + } + + return str; +} + +static void print_ext_adv_properties(uint16_t flags) +{ + uint16_t mask = flags; + const char *property; + int i; + + print_field("Properties: 0x%4.4x", flags); + + for (i = 0; ext_adv_properties_table[i].str; i++) { + if (flags & (1 << ext_adv_properties_table[i].bit)) { + property = ext_adv_properties_table[i].str; + + if (ext_adv_properties_table[i].bit == 4) { + print_field(" %s: %s", property, + get_adv_pdu_desc(flags)); + } else { + print_field(" %s", property); + } + mask &= ~(1 << ext_adv_properties_table[i].bit); + } + } + + if (mask) + print_text(COLOR_UNKNOWN_ADV_FLAG, + " Unknown advertising properties (0x%4.4x)", + mask); +} + +static void print_ext_slot_625(const char *label, const uint8_t value[3]) +{ + uint32_t value_cpu = value[0]; + + value_cpu |= value[1] << 8; + value_cpu |= value[2] << 16; + + print_field("%s: %.3f msec (0x%4.4x)", label, + value_cpu * 0.625, value_cpu); +} + +static void le_set_ext_adv_params_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_ext_adv_params *cmd = data; + const char *str; + + print_field("Handle: 0x%2.2x", cmd->handle); + print_ext_adv_properties(le16_to_cpu(cmd->evt_properties)); + + print_ext_slot_625("Min advertising interval", cmd->min_interval); + print_ext_slot_625("Max advertising interval", cmd->max_interval); + print_adv_channel_map("Channel map", cmd->channel_map); + print_own_addr_type(cmd->own_addr_type); + print_peer_addr_type("Peer address type", cmd->peer_addr_type); + print_addr("Peer address", cmd->peer_addr, cmd->peer_addr_type); + print_adv_filter_policy("Filter policy", cmd->filter_policy); + if (cmd->tx_power == 0xff) + print_field("TX power: Host has no preference (0xff)"); + else + print_power_level(cmd->tx_power, NULL); + + switch (cmd->primary_phy) { + case 0x01: + str = "LE 1M"; + break; + case 0x03: + str = "LE Coded"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Primary PHY: %s (0x%2.2x)", str, cmd->primary_phy); + print_field("Secondary max skip: 0x%2.2x", cmd->secondary_max_skip); + print_le_phy("Secondary PHY", cmd->secondary_phy); + print_field("SID: 0x%2.2x", cmd->sid); + print_enable("Scan request notifications", cmd->notif_enable); +} + +static void le_set_ext_adv_params_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_set_ext_adv_params *rsp = data; + + print_status(rsp->status); + print_power_level(rsp->tx_power, "selected"); +} + +static void le_set_ext_adv_data_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_ext_adv_data *cmd = data; + const char *str; + + print_field("Handle: 0x%2.2x", cmd->handle); + + switch (cmd->operation) { + case 0x00: + str = "Immediate fragment"; + break; + case 0x01: + str = "First fragment"; + break; + case 0x02: + str = "Last fragment"; + break; + case 0x03: + str = "Complete extended advertising data"; + break; + case 0x04: + str = "Unchanged data"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Operation: %s (0x%2.2x)", str, cmd->operation); + + switch (cmd->fragment_preference) { + case 0x00: + str = "Fragment all"; + break; + case 0x01: + str = "Minimize fragmentation"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Fragment preference: %s (0x%2.2x)", str, + cmd->fragment_preference); + print_field("Data length: 0x%2.2x", cmd->data_len); + packet_print_ad(cmd->data, size - sizeof(*cmd)); +} + +static void le_set_ext_scan_rsp_data_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_ext_scan_rsp_data *cmd = data; + const char *str; + + print_field("Handle: 0x%2.2x", cmd->handle); + + switch (cmd->operation) { + case 0x00: + str = "Immediate fragment"; + break; + case 0x01: + str = "First fragment"; + break; + case 0x02: + str = "Last fragment"; + break; + case 0x03: + str = "Complete scan response data"; + break; + case 0x04: + str = "Unchanged data"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Operation: %s (0x%2.2x)", str, cmd->operation); + + switch (cmd->fragment_preference) { + case 0x00: + str = "Fragment all"; + break; + case 0x01: + str = "Minimize fragmentation"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Fragment preference: %s (0x%2.2x)", str, + cmd->fragment_preference); + print_field("Data length: 0x%2.2x", cmd->data_len); + packet_print_ad(cmd->data, size - sizeof(*cmd)); +} + +static void le_set_ext_adv_enable_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_ext_adv_enable *cmd = data; + const struct bt_hci_cmd_ext_adv_set *adv_set; + int i; + + print_enable("Extended advertising", cmd->enable); + + if (cmd->num_of_sets == 0) + print_field("Number of sets: Disable all sets (0x%2.2x)", + cmd->num_of_sets); + else if (cmd->num_of_sets > 0x3f) + print_field("Number of sets: Reserved (0x%2.2x)", + cmd->num_of_sets); + else + print_field("Number of sets: %u (0x%2.2x)", cmd->num_of_sets, + cmd->num_of_sets); + + for (i = 0; i < cmd->num_of_sets; ++i) { + adv_set = data + 2 + i * sizeof(struct bt_hci_cmd_ext_adv_set); + print_field("Entry %d", i); + print_field(" Handle: 0x%2.2x", adv_set->handle); + print_field(" Duration: %d ms (0x%2.2x)", + adv_set->duration * 10, adv_set->duration); + print_field(" Max ext adv events: %d", adv_set->max_events); + } +} + +static void le_read_max_adv_data_len_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_max_adv_data_len *rsp = data; + + print_status(rsp->status); + print_field("Max length: %d", rsp->max_len); +} + +static void le_read_num_supported_adv_sets_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_num_supported_adv_sets *rsp = data; + + print_status(rsp->status); + print_field("Num supported adv sets: %d", rsp->num_of_sets); +} + +static void le_remove_adv_set_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_remove_adv_set *cmd = data; + + print_handle(cmd->handle); +} + +static const struct bitfield_data periodic_adv_properties_table[] = { + { 6, "Include TxPower" }, + { } +}; + +static void print_periodic_adv_properties(uint16_t flags) +{ + uint16_t mask; + + print_field("Properties: 0x%4.4x", flags); + + mask = print_bitfield(2, flags, periodic_adv_properties_table); + if (mask) + print_text(COLOR_UNKNOWN_ADV_FLAG, + " Unknown advertising properties (0x%4.4x)", + mask); +} + +static void le_set_periodic_adv_params_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_periodic_adv_params *cmd = data; + + print_handle(cmd->handle); + print_slot_125("Min interval", cmd->min_interval); + print_slot_125("Max interval", cmd->max_interval); + print_periodic_adv_properties(cmd->properties); +} + +static void le_set_periodic_adv_data_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_periodic_adv_data *cmd = data; + const char *str; + + print_handle(cmd->handle); + + switch (cmd->operation) { + case 0x00: + str = "Immediate fragment"; + break; + case 0x01: + str = "First fragment"; + break; + case 0x02: + str = "Last fragment"; + break; + case 0x03: + str = "Complete ext advertising data"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Operation: %s (0x%2.2x)", str, cmd->operation); + print_field("Data length: 0x%2.2x", cmd->data_len); + print_eir(cmd->data, cmd->data_len, true); +} + +static void le_set_periodic_adv_enable_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_periodic_adv_enable *cmd = data; + + print_enable("Periodic advertising", cmd->enable); + print_handle(cmd->handle); +} + +static const struct bitfield_data ext_scan_phys_table[] = { + { 0, "LE 1M" }, + { 2, "LE Coded" }, + { } +}; + +static void print_ext_scan_phys(const void *data, uint8_t flags) +{ + const struct bt_hci_le_scan_phy *scan_phy; + uint8_t mask = flags; + int bits_set = 0; + int i; + + print_field("PHYs: 0x%2.2x", flags); + + for (i = 0; ext_scan_phys_table[i].str; i++) { + if (flags & (1 << ext_scan_phys_table[i].bit)) { + scan_phy = data + bits_set * sizeof(*scan_phy); + mask &= ~(1 << ext_scan_phys_table[i].bit); + + print_field("Entry %d: %s", bits_set, + ext_scan_phys_table[i].str); + print_scan_type(" Type", scan_phy->type); + print_slot_625(" Interval", scan_phy->interval); + print_slot_625(" Window", scan_phy->window); + + ++bits_set; + } + } + + if (mask) + print_text(COLOR_UNKNOWN_ADV_FLAG, " Unknown scanning PHYs" + " (0x%2.2x)", mask); +} + +static void le_set_ext_scan_params_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_ext_scan_params *cmd = data; + + print_own_addr_type(cmd->own_addr_type); + print_scan_filter_policy(cmd->filter_policy); + print_ext_scan_phys(cmd->data, cmd->num_phys); +} + +static void le_set_ext_scan_enable_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_ext_scan_enable *cmd = data; + + print_enable("Extended scan", cmd->enable); + print_enable("Filter duplicates", cmd->filter_dup); + + print_field("Duration: %d msec (0x%4.4x)", + le16_to_cpu(cmd->duration) * 10, + le16_to_cpu(cmd->duration)); + print_field("Period: %.2f sec (0x%4.4x)", + le16_to_cpu(cmd->period) * 1.28, + le16_to_cpu(cmd->period)); +} + +static const struct bitfield_data ext_conn_phys_table[] = { + { 0, "LE 1M" }, + { 1, "LE 2M" }, + { 2, "LE Coded" }, + { } +}; + +static void print_ext_conn_phys(const void *data, uint8_t flags) +{ + const struct bt_hci_le_ext_create_conn *entry; + uint8_t mask = flags; + int bits_set = 0; + int i; + + print_field("Initiating PHYs: 0x%2.2x", flags); + + for (i = 0; ext_conn_phys_table[i].str; i++) { + if (flags & (1 << ext_conn_phys_table[i].bit)) { + entry = data + bits_set * sizeof(*entry); + mask &= ~(1 << ext_conn_phys_table[i].bit); + + print_field("Entry %d: %s", bits_set, + ext_conn_phys_table[i].str); + print_slot_625(" Scan interval", entry->scan_interval); + print_slot_625(" Scan window", entry->scan_window); + print_slot_125(" Min connection interval", + entry->min_interval); + print_slot_125(" Max connection interval", + entry->max_interval); + print_conn_latency(" Connection latency", + entry->latency); + print_field(" Supervision timeout: %d msec (0x%4.4x)", + le16_to_cpu(entry->supv_timeout) * 10, + le16_to_cpu(entry->supv_timeout)); + print_slot_625(" Min connection length", + entry->min_length); + print_slot_625(" Max connection length", + entry->max_length); + + ++bits_set; + } + } + + if (mask) + print_text(COLOR_UNKNOWN_ADV_FLAG, " Unknown scanning PHYs" + " (0x%2.2x)", mask); +} + +static void le_ext_create_conn_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_ext_create_conn *cmd = data; + const char *str; + + switch (cmd->filter_policy) { + case 0x00: + str = "White list is not used"; + break; + case 0x01: + str = "White list is used"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Filter policy: %s (0x%2.2x)", str, cmd->filter_policy); + + print_own_addr_type(cmd->own_addr_type); + print_peer_addr_type("Peer address type", cmd->peer_addr_type); + print_addr("Peer address", cmd->peer_addr, cmd->peer_addr_type); + print_ext_conn_phys(cmd->data, cmd->phys); +} + +static void le_periodic_adv_create_sync_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_periodic_adv_create_sync *cmd = data; + const char *str; + + switch (cmd->filter_policy) { + case 0x00: + str = "Use specified advertising parameters"; + break; + case 0x01: + str = "Use Periodic Advertiser List"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Filter policy: %s (0x%2.2x)", str, cmd->filter_policy); + print_field("SID: 0x%2.2x", cmd->sid); + print_addr_type("Adv address type", cmd->addr_type); + print_addr("Adv address", cmd->addr, cmd->addr_type); + print_field("Skip: 0x%4.4x", cmd->skip); + print_field("Sync timeout: %d msec (0x%4.4x)", + le16_to_cpu(cmd->sync_timeout) * 10, + le16_to_cpu(cmd->sync_timeout)); + print_field("Unused: 0x%2.2x", cmd->unused); +} + +static void le_periodic_adv_term_sync_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_periodic_adv_term_sync *cmd = data; + + print_field("Sync handle: 0x%4.4x", cmd->sync_handle); +} + +static void le_add_dev_periodic_adv_list_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_add_dev_periodic_adv_list *cmd = data; + + print_addr_type("Adv address type", cmd->addr_type); + print_addr("Adv address", cmd->addr, cmd->addr_type); + print_field("SID: 0x%2.2x", cmd->sid); +} + +static void le_remove_dev_periodic_adv_list_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_remove_dev_periodic_adv_list *cmd = data; + + print_addr_type("Adv address type", cmd->addr_type); + print_addr("Adv address", cmd->addr, cmd->addr_type); + print_field("SID: 0x%2.2x", cmd->sid); +} + +static void le_read_periodic_adv_list_size_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_dev_periodic_adv_list_size *rsp = data; + + print_status(rsp->status); + print_field("List size: 0x%2.2x", rsp->list_size); +} + +static void le_read_tx_power_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_tx_power *rsp = data; + + print_status(rsp->status); + print_field("Min Tx power: %d dBm", rsp->min_tx_power); + print_field("Max Tx power: %d dBm", rsp->max_tx_power); +} + +static void le_read_rf_path_comp_rsp(const void *data, uint8_t size) +{ + const struct bt_hci_rsp_le_read_rf_path_comp *rsp = data; + + print_status(rsp->status); + print_field("RF Tx Path Compensation Value: 0x%4.4x", + rsp->rf_tx_path_comp); + print_field("RF Rx Path Compensation Value: 0x%4.4x", + rsp->rf_rx_path_comp); +} + +static void le_write_rf_path_comp_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_write_rf_path_comp *cmd = data; + + print_field("RF Tx Path Compensation Value: 0x%4.4x", + cmd->rf_tx_path_comp); + print_field("RF Rx Path Compensation Value: 0x%4.4x", + cmd->rf_rx_path_comp); +} + +static void le_set_priv_mode_cmd(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_set_priv_mode *cmd = data; + const char *str; + + print_addr_type("Peer Identity address type", cmd->peer_id_addr_type); + print_addr("Peer Identity address", cmd->peer_id_addr, + cmd->peer_id_addr_type); + + switch (cmd->priv_mode) { + case 0x00: + str = "Use Network Privacy"; + break; + case 0x01: + str = "Use Device Privacy"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Privacy Mode: %s (0x%2.2x)", str, cmd->priv_mode); +} + +static void le_receiver_test_cmd_v3(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_receiver_test_v3 *cmd = data; + uint8_t i; + + print_field("RX Channel: %u MHz (0x%2.2x)", cmd->rx_chan * 2 + 2402, + cmd->rx_chan); + + switch (cmd->phy) { + case 0x01: + print_field("PHY: LE 1M (0x%2.2x)", cmd->phy); + break; + case 0x02: + print_field("PHY: LE 2M (0x%2.2x)", cmd->phy); + break; + case 0x03: + print_field("PHY: LE Coded (0x%2.2x)", cmd->phy); + break; + } + + print_field("Modulation Index: %s (0x%2.2x)", + cmd->mod_index ? "stable" : "standard", cmd->mod_index); + print_field("Expected CTE Length: %u us (0x%2.2x)", cmd->cte_len * 8, + cmd->cte_len); + print_field("Expected CTE Type: %u us slots (0x%2.2x)", cmd->cte_type, + cmd->cte_type); + print_field("Slot Duration: %u us (0x%2.2x)", cmd->duration, + cmd->duration); + print_field("Number of Antenna IDs: %u", cmd->num_antenna_id); + + if (size < sizeof(*cmd) + cmd->num_antenna_id) + return; + + for (i = 0; i < cmd->num_antenna_id; i++) + print_field(" Antenna ID: %u", cmd->antenna_ids[i]); +} + +static const char *parse_tx_test_payload(uint8_t payload) +{ + switch (payload) { + case 0x00: + return "PRBS9 sequence 11111111100000111101..."; + case 0x01: + return "Repeated 11110000"; + case 0x02: + return "Repeated 10101010"; + case 0x03: + return "PRBS15"; + case 0x04: + return "Repeated 11111111"; + case 0x05: + return "Repeated 00000000"; + case 0x06: + return "Repeated 00001111"; + case 0x07: + return "Repeated 01010101"; + default: + return "Reserved"; + } +} + +static void le_tx_test_cmd_v3(const void *data, uint8_t size) +{ + const struct bt_hci_cmd_le_tx_test_v3 *cmd = data; + uint8_t i; + + print_field("TX Channel: %u MHz (0x%2.2x)", cmd->chan * 2 + 2402, + cmd->chan); + print_field("Length of Test Data: %u", cmd->data_len); + print_field("Packet Payload: %s (0x%2.2x)", + parse_tx_test_payload(cmd->payload), cmd->payload); + + switch (cmd->phy) { + case 0x01: + print_field("PHY: LE 1M (0x%2.2x)", cmd->phy); + break; + case 0x02: + print_field("PHY: LE 2M (0x%2.2x)", cmd->phy); + break; + case 0x03: + print_field("PHY: LE Coded with S=8 (0x%2.2x)", cmd->phy); + break; + case 0x04: + print_field("PHY: LE Coded with S=2 (0x%2.2x)", cmd->phy); + break; + } + + print_field("Expected CTE Length: %u us (0x%2.2x)", cmd->cte_len * 8, + cmd->cte_len); + print_field("Expected CTE Type: %u us slots (0x%2.2x)", cmd->cte_type, + cmd->cte_type); + print_field("Slot Duration: %u us (0x%2.2x)", cmd->duration, + cmd->duration); + print_field("Number of Antenna IDs: %u", cmd->num_antenna_id); + + if (size < sizeof(*cmd) + cmd->num_antenna_id) + return; + + for (i = 0; i < cmd->num_antenna_id; i++) + print_field(" Antenna ID: %u", cmd->antenna_ids[i]); +} + +struct opcode_data { + uint16_t opcode; + int bit; + const char *str; + void (*cmd_func) (const void *data, uint8_t size); + uint8_t cmd_size; + bool cmd_fixed; + void (*rsp_func) (const void *data, uint8_t size); + uint8_t rsp_size; + bool rsp_fixed; +}; + +static const struct opcode_data opcode_table[] = { + { 0x0000, -1, "NOP" }, + + /* OGF 1 - Link Control */ + { 0x0401, 0, "Inquiry", + inquiry_cmd, 5, true }, + { 0x0402, 1, "Inquiry Cancel", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x0403, 2, "Periodic Inquiry Mode", + periodic_inquiry_cmd, 9, true, + status_rsp, 1, true }, + { 0x0404, 3, "Exit Periodic Inquiry Mode", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x0405, 4, "Create Connection", + create_conn_cmd, 13, true }, + { 0x0406, 5, "Disconnect", + disconnect_cmd, 3, true }, + { 0x0407, 6, "Add SCO Connection", + add_sco_conn_cmd, 4, true }, + { 0x0408, 7, "Create Connection Cancel", + create_conn_cancel_cmd, 6, true, + status_bdaddr_rsp, 7, true }, + { 0x0409, 8, "Accept Connection Request", + accept_conn_request_cmd, 7, true }, + { 0x040a, 9, "Reject Connection Request", + reject_conn_request_cmd, 7, true }, + { 0x040b, 10, "Link Key Request Reply", + link_key_request_reply_cmd, 22, true, + status_bdaddr_rsp, 7, true }, + { 0x040c, 11, "Link Key Request Negative Reply", + link_key_request_neg_reply_cmd, 6, true, + status_bdaddr_rsp, 7, true }, + { 0x040d, 12, "PIN Code Request Reply", + pin_code_request_reply_cmd, 23, true, + status_bdaddr_rsp, 7, true }, + { 0x040e, 13, "PIN Code Request Negative Reply", + pin_code_request_neg_reply_cmd, 6, true, + status_bdaddr_rsp, 7, true }, + { 0x040f, 14, "Change Connection Packet Type", + change_conn_pkt_type_cmd, 4, true }, + { 0x0411, 15, "Authentication Requested", + auth_requested_cmd, 2, true }, + { 0x0413, 16, "Set Connection Encryption", + set_conn_encrypt_cmd, 3, true }, + { 0x0415, 17, "Change Connection Link Key", + change_conn_link_key_cmd, 2, true }, + { 0x0417, 18, "Master Link Key", + master_link_key_cmd, 1, true }, + { 0x0419, 19, "Remote Name Request", + remote_name_request_cmd, 10, true }, + { 0x041a, 20, "Remote Name Request Cancel", + remote_name_request_cancel_cmd, 6, true, + status_bdaddr_rsp, 7, true }, + { 0x041b, 21, "Read Remote Supported Features", + read_remote_features_cmd, 2, true }, + { 0x041c, 22, "Read Remote Extended Features", + read_remote_ext_features_cmd, 3, true }, + { 0x041d, 23, "Read Remote Version Information", + read_remote_version_cmd, 2, true }, + { 0x041f, 24, "Read Clock Offset", + read_clock_offset_cmd, 2, true }, + { 0x0420, 25, "Read LMP Handle", + read_lmp_handle_cmd, 2, true, + read_lmp_handle_rsp, 8, true }, + { 0x0428, 131, "Setup Synchronous Connection", + setup_sync_conn_cmd, 17, true }, + { 0x0429, 132, "Accept Synchronous Connection Request", + accept_sync_conn_request_cmd, 21, true }, + { 0x042a, 133, "Reject Synchronous Connection Request", + reject_sync_conn_request_cmd, 7, true }, + { 0x042b, 151, "IO Capability Request Reply", + io_capability_request_reply_cmd, 9, true, + status_bdaddr_rsp, 7, true }, + { 0x042c, 152, "User Confirmation Request Reply", + user_confirm_request_reply_cmd, 6, true, + status_bdaddr_rsp, 7, true }, + { 0x042d, 153, "User Confirmation Request Neg Reply", + user_confirm_request_neg_reply_cmd, 6, true, + status_bdaddr_rsp, 7, true }, + { 0x042e, 154, "User Passkey Request Reply", + user_passkey_request_reply_cmd, 10, true, + status_bdaddr_rsp, 7, true }, + { 0x042f, 155, "User Passkey Request Negative Reply", + user_passkey_request_neg_reply_cmd, 6, true, + status_bdaddr_rsp, 7, true }, + { 0x0430, 156, "Remote OOB Data Request Reply", + remote_oob_data_request_reply_cmd, 38, true, + status_bdaddr_rsp, 7, true }, + { 0x0433, 159, "Remote OOB Data Request Neg Reply", + remote_oob_data_request_neg_reply_cmd, 6, true, + status_bdaddr_rsp, 7, true }, + { 0x0434, 163, "IO Capability Request Negative Reply", + io_capability_request_neg_reply_cmd, 7, true, + status_bdaddr_rsp, 7, true }, + { 0x0435, 168, "Create Physical Link", + create_phy_link_cmd, 3, false }, + { 0x0436, 169, "Accept Physical Link", + accept_phy_link_cmd, 3, false }, + { 0x0437, 170, "Disconnect Physical Link", + disconn_phy_link_cmd, 2, true }, + { 0x0438, 171, "Create Logical Link", + create_logic_link_cmd, 33, true }, + { 0x0439, 172, "Accept Logical Link", + accept_logic_link_cmd, 33, true }, + { 0x043a, 173, "Disconnect Logical Link", + disconn_logic_link_cmd, 2, true }, + { 0x043b, 174, "Logical Link Cancel", + logic_link_cancel_cmd, 2, true, + logic_link_cancel_rsp, 3, true }, + { 0x043c, 175, "Flow Specifcation Modify", + flow_spec_modify_cmd, 34, true }, + { 0x043d, 235, "Enhanced Setup Synchronous Connection", + enhanced_setup_sync_conn_cmd, 59, true }, + { 0x043e, 236, "Enhanced Accept Synchronous Connection Request", + enhanced_accept_sync_conn_request_cmd, 63, true }, + { 0x043f, 246, "Truncated Page", + truncated_page_cmd, 9, true }, + { 0x0440, 247, "Truncated Page Cancel", + truncated_page_cancel_cmd, 6, true, + status_bdaddr_rsp, 7, true }, + { 0x0441, 248, "Set Connectionless Slave Broadcast", + set_slave_broadcast_cmd, 11, true, + set_slave_broadcast_rsp, 4, true }, + { 0x0442, 249, "Set Connectionless Slave Broadcast Receive", + set_slave_broadcast_receive_cmd, 34, true, + set_slave_broadcast_receive_rsp, 8, true }, + { 0x0443, 250, "Start Synchronization Train", + null_cmd, 0, true }, + { 0x0444, 251, "Receive Synchronization Train", + receive_sync_train_cmd, 12, true }, + { 0x0445, 257, "Remote OOB Extended Data Request Reply", + remote_oob_ext_data_request_reply_cmd, 70, true, + status_bdaddr_rsp, 7, true }, + + /* OGF 2 - Link Policy */ + { 0x0801, 33, "Hold Mode", + hold_mode_cmd, 6, true }, + { 0x0803, 34, "Sniff Mode", + sniff_mode_cmd, 10, true }, + { 0x0804, 35, "Exit Sniff Mode", + exit_sniff_mode_cmd, 2, true }, + { 0x0805, 36, "Park State", + park_state_cmd, 6, true }, + { 0x0806, 37, "Exit Park State", + exit_park_state_cmd, 2, true }, + { 0x0807, 38, "QoS Setup", + qos_setup_cmd, 20, true }, + { 0x0809, 39, "Role Discovery", + role_discovery_cmd, 2, true, + role_discovery_rsp, 4, true }, + { 0x080b, 40, "Switch Role", + switch_role_cmd, 7, true }, + { 0x080c, 41, "Read Link Policy Settings", + read_link_policy_cmd, 2, true, + read_link_policy_rsp, 5, true }, + { 0x080d, 42, "Write Link Policy Settings", + write_link_policy_cmd, 4, true, + write_link_policy_rsp, 3, true }, + { 0x080e, 43, "Read Default Link Policy Settings", + null_cmd, 0, true, + read_default_link_policy_rsp, 3, true }, + { 0x080f, 44, "Write Default Link Policy Settings", + write_default_link_policy_cmd, 2, true, + status_rsp, 1, true }, + { 0x0810, 45, "Flow Specification", + flow_spec_cmd, 21, true }, + { 0x0811, 140, "Sniff Subrating", + sniff_subrating_cmd, 8, true, + sniff_subrating_rsp, 3, true }, + + /* OGF 3 - Host Control */ + { 0x0c01, 46, "Set Event Mask", + set_event_mask_cmd, 8, true, + status_rsp, 1, true }, + { 0x0c03, 47, "Reset", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x0c05, 48, "Set Event Filter", + set_event_filter_cmd, 1, false, + status_rsp, 1, true }, + { 0x0c08, 49, "Flush", + flush_cmd, 2, true, + flush_rsp, 3, true }, + { 0x0c09, 50, "Read PIN Type", + null_cmd, 0, true, + read_pin_type_rsp, 2, true }, + { 0x0c0a, 51, "Write PIN Type", + write_pin_type_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c0b, 52, "Create New Unit Key", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x0c0d, 53, "Read Stored Link Key", + read_stored_link_key_cmd, 7, true, + read_stored_link_key_rsp, 5, true }, + { 0x0c11, 54, "Write Stored Link Key", + write_stored_link_key_cmd, 1, false, + write_stored_link_key_rsp, 2, true }, + { 0x0c12, 55, "Delete Stored Link Key", + delete_stored_link_key_cmd, 7, true, + delete_stored_link_key_rsp, 3, true }, + { 0x0c13, 56, "Write Local Name", + write_local_name_cmd, 248, true, + status_rsp, 1, true }, + { 0x0c14, 57, "Read Local Name", + null_cmd, 0, true, + read_local_name_rsp, 249, true }, + { 0x0c15, 58, "Read Connection Accept Timeout", + null_cmd, 0, true, + read_conn_accept_timeout_rsp, 3, true }, + { 0x0c16, 59, "Write Connection Accept Timeout", + write_conn_accept_timeout_cmd, 2, true, + status_rsp, 1, true }, + { 0x0c17, 60, "Read Page Timeout", + null_cmd, 0, true, + read_page_timeout_rsp, 3, true }, + { 0x0c18, 61, "Write Page Timeout", + write_page_timeout_cmd, 2, true, + status_rsp, 1, true }, + { 0x0c19, 62, "Read Scan Enable", + null_cmd, 0, true, + read_scan_enable_rsp, 2, true }, + { 0x0c1a, 63, "Write Scan Enable", + write_scan_enable_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c1b, 64, "Read Page Scan Activity", + null_cmd, 0, true, + read_page_scan_activity_rsp, 5, true }, + { 0x0c1c, 65, "Write Page Scan Activity", + write_page_scan_activity_cmd, 4, true, + status_rsp, 1, true }, + { 0x0c1d, 66, "Read Inquiry Scan Activity", + null_cmd, 0, true, + read_inquiry_scan_activity_rsp, 5, true }, + { 0x0c1e, 67, "Write Inquiry Scan Activity", + write_inquiry_scan_activity_cmd, 4, true, + status_rsp, 1, true }, + { 0x0c1f, 68, "Read Authentication Enable", + null_cmd, 0, true, + read_auth_enable_rsp, 2, true }, + { 0x0c20, 69, "Write Authentication Enable", + write_auth_enable_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c21, 70, "Read Encryption Mode", + null_cmd, 0, true, + read_encrypt_mode_rsp, 2, true }, + { 0x0c22, 71, "Write Encryption Mode", + write_encrypt_mode_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c23, 72, "Read Class of Device", + null_cmd, 0, true, + read_class_of_dev_rsp, 4, true }, + { 0x0c24, 73, "Write Class of Device", + write_class_of_dev_cmd, 3, true, + status_rsp, 1, true }, + { 0x0c25, 74, "Read Voice Setting", + null_cmd, 0, true, + read_voice_setting_rsp, 3, true }, + { 0x0c26, 75, "Write Voice Setting", + write_voice_setting_cmd, 2, true, + status_rsp, 1, true }, + { 0x0c27, 76, "Read Automatic Flush Timeout", + read_auto_flush_timeout_cmd, 2, true, + read_auto_flush_timeout_rsp, 5, true }, + { 0x0c28, 77, "Write Automatic Flush Timeout", + write_auto_flush_timeout_cmd, 4, true, + write_auto_flush_timeout_rsp, 3, true }, + { 0x0c29, 78, "Read Num Broadcast Retransmissions", + null_cmd, 0, true, + read_num_broadcast_retrans_rsp, 2, true }, + { 0x0c2a, 79, "Write Num Broadcast Retransmissions", + write_num_broadcast_retrans_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c2b, 80, "Read Hold Mode Activity", + null_cmd, 0, true, + read_hold_mode_activity_rsp, 2, true }, + { 0x0c2c, 81, "Write Hold Mode Activity", + write_hold_mode_activity_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c2d, 82, "Read Transmit Power Level", + read_tx_power_cmd, 3, true, + read_tx_power_rsp, 4, true }, + { 0x0c2e, 83, "Read Sync Flow Control Enable", + null_cmd, 0, true, + read_sync_flow_control_rsp, 2, true }, + { 0x0c2f, 84, "Write Sync Flow Control Enable", + write_sync_flow_control_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c31, 85, "Set Controller To Host Flow Control", + set_host_flow_control_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c33, 86, "Host Buffer Size", + host_buffer_size_cmd, 7, true, + status_rsp, 1, true }, + { 0x0c35, 87, "Host Number of Completed Packets", + host_num_completed_packets_cmd, 5, false }, + { 0x0c36, 88, "Read Link Supervision Timeout", + read_link_supv_timeout_cmd, 2, true, + read_link_supv_timeout_rsp, 5, true }, + { 0x0c37, 89, "Write Link Supervision Timeout", + write_link_supv_timeout_cmd, 4, true, + write_link_supv_timeout_rsp, 3, true }, + { 0x0c38, 90, "Read Number of Supported IAC", + null_cmd, 0, true, + read_num_supported_iac_rsp, 2, true }, + { 0x0c39, 91, "Read Current IAC LAP", + null_cmd, 0, true, + read_current_iac_lap_rsp, 2, false }, + { 0x0c3a, 92, "Write Current IAC LAP", + write_current_iac_lap_cmd, 1, false, + status_rsp, 1, true }, + { 0x0c3b, 93, "Read Page Scan Period Mode", + null_cmd, 0, true, + read_page_scan_period_mode_rsp, 2, true }, + { 0x0c3c, 94, "Write Page Scan Period Mode", + write_page_scan_period_mode_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c3d, 95, "Read Page Scan Mode", + null_cmd, 0, true, + read_page_scan_mode_rsp, 2, true }, + { 0x0c3e, 96, "Write Page Scan Mode", + write_page_scan_mode_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c3f, 97, "Set AFH Host Channel Classification", + set_afh_host_classification_cmd, 10, true, + status_rsp, 1, true }, + { 0x0c42, 100, "Read Inquiry Scan Type", + null_cmd, 0, true, + read_inquiry_scan_type_rsp, 2, true }, + { 0x0c43, 101, "Write Inquiry Scan Type", + write_inquiry_scan_type_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c44, 102, "Read Inquiry Mode", + null_cmd, 0, true, + read_inquiry_mode_rsp, 2, true }, + { 0x0c45, 103, "Write Inquiry Mode", + write_inquiry_mode_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c46, 104, "Read Page Scan Type", + null_cmd, 0, true, + read_page_scan_type_rsp, 2, true }, + { 0x0c47, 105, "Write Page Scan Type", + write_page_scan_type_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c48, 106, "Read AFH Channel Assessment Mode", + null_cmd, 0, true, + read_afh_assessment_mode_rsp, 2, true }, + { 0x0c49, 107, "Write AFH Channel Assessment Mode", + write_afh_assessment_mode_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c51, 136, "Read Extended Inquiry Response", + null_cmd, 0, true, + read_ext_inquiry_response_rsp, 242, true }, + { 0x0c52, 137, "Write Extended Inquiry Response", + write_ext_inquiry_response_cmd, 241, true, + status_rsp, 1, true }, + { 0x0c53, 138, "Refresh Encryption Key", + refresh_encrypt_key_cmd, 2, true }, + { 0x0c55, 141, "Read Simple Pairing Mode", + null_cmd, 0, true, + read_simple_pairing_mode_rsp, 2, true }, + { 0x0c56, 142, "Write Simple Pairing Mode", + write_simple_pairing_mode_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c57, 143, "Read Local OOB Data", + null_cmd, 0, true, + read_local_oob_data_rsp, 33, true }, + { 0x0c58, 144, "Read Inquiry Response TX Power Level", + null_cmd, 0, true, + read_inquiry_resp_tx_power_rsp, 2, true }, + { 0x0c59, 145, "Write Inquiry Transmit Power Level", + write_inquiry_tx_power_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c5a, 146, "Read Default Erroneous Data Reporting", + null_cmd, 0, true, + read_erroneous_reporting_rsp, 2, true }, + { 0x0c5b, 147, "Write Default Erroneous Data Reporting", + write_erroneous_reporting_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c5f, 158, "Enhanced Flush", + enhanced_flush_cmd, 3, true }, + { 0x0c60, 162, "Send Keypress Notification", + send_keypress_notify_cmd, 7, true, + send_keypress_notify_rsp, 7, true }, + { 0x0c61, 176, "Read Logical Link Accept Timeout" }, + { 0x0c62, 177, "Write Logical Link Accept Timeout" }, + { 0x0c63, 178, "Set Event Mask Page 2", + set_event_mask_page2_cmd, 8, true, + status_rsp, 1, true }, + { 0x0c64, 179, "Read Location Data", + null_cmd, 0, true, + read_location_data_rsp, 6, true }, + { 0x0c65, 180, "Write Location Data", + write_location_data_cmd, 5, true, + status_rsp, 1, true }, + { 0x0c66, 184, "Read Flow Control Mode", + null_cmd, 0, true, + read_flow_control_mode_rsp, 2, true }, + { 0x0c67, 185, "Write Flow Control Mode", + write_flow_control_mode_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c68, 192, "Read Enhanced Transmit Power Level", + read_enhanced_tx_power_cmd, 3, true, + read_enhanced_tx_power_rsp, 6, true }, + { 0x0c69, 194, "Read Best Effort Flush Timeout" }, + { 0x0c6a, 195, "Write Best Effort Flush Timeout" }, + { 0x0c6b, 196, "Short Range Mode", + short_range_mode_cmd, 2, true }, + { 0x0c6c, 197, "Read LE Host Supported", + null_cmd, 0, true, + read_le_host_supported_rsp, 3, true }, + { 0x0c6d, 198, "Write LE Host Supported", + write_le_host_supported_cmd, 2, true, + status_rsp, 1, true }, + { 0x0c6e, 238, "Set MWS Channel Parameters" }, + { 0x0c6f, 239, "Set External Frame Configuration" }, + { 0x0c70, 240, "Set MWS Signaling" }, + { 0x0c71, 241, "Set MWS Transport Layer" }, + { 0x0c72, 242, "Set MWS Scan Frequency Table" }, + { 0x0c73, 244, "Set MWS Pattern Configuration" }, + { 0x0c74, 252, "Set Reserved LT_ADDR", + set_reserved_lt_addr_cmd, 1, true, + set_reserved_lt_addr_rsp, 2, true }, + { 0x0c75, 253, "Delete Reserved LT_ADDR", + delete_reserved_lt_addr_cmd, 1, true, + delete_reserved_lt_addr_rsp, 2, true }, + { 0x0c76, 254, "Set Connectionless Slave Broadcast Data", + set_slave_broadcast_data_cmd, 3, false, + set_slave_broadcast_data_rsp, 2, true }, + { 0x0c77, 255, "Read Synchronization Train Parameters", + null_cmd, 0, true, + read_sync_train_params_rsp, 8, true }, + { 0x0c78, 256, "Write Synchronization Train Parameters", + write_sync_train_params_cmd, 9, true, + write_sync_train_params_rsp, 3, true }, + { 0x0c79, 258, "Read Secure Connections Host Support", + null_cmd, 0, true, + read_secure_conn_support_rsp, 2, true }, + { 0x0c7a, 259, "Write Secure Connections Host Support", + write_secure_conn_support_cmd, 1, true, + status_rsp, 1, true }, + { 0x0c7b, 260, "Read Authenticated Payload Timeout", + read_auth_payload_timeout_cmd, 2, true, + read_auth_payload_timeout_rsp, 5, true }, + { 0x0c7c, 261, "Write Authenticated Payload Timeout", + write_auth_payload_timeout_cmd, 4, true, + write_auth_payload_timeout_rsp, 3, true }, + { 0x0c7d, 262, "Read Local OOB Extended Data", + null_cmd, 0, true, + read_local_oob_ext_data_rsp, 65, true }, + { 0x0c7e, 264, "Read Extended Page Timeout", + null_cmd, 0, true, + read_ext_page_timeout_rsp, 3, true }, + { 0x0c7f, 265, "Write Extended Page Timeout", + write_ext_page_timeout_cmd, 2, true, + status_rsp, 1, true }, + { 0x0c80, 266, "Read Extended Inquiry Length", + null_cmd, 0, true, + read_ext_inquiry_length_rsp, 3, true }, + { 0x0c81, 267, "Write Extended Inquiry Length", + write_ext_inquiry_length_cmd, 2, true, + status_rsp, 1, true }, + + /* OGF 4 - Information Parameter */ + { 0x1001, 115, "Read Local Version Information", + null_cmd, 0, true, + read_local_version_rsp, 9, true }, + { 0x1002, 116, "Read Local Supported Commands", + null_cmd, 0, true, + read_local_commands_rsp, 65, true }, + { 0x1003, 117, "Read Local Supported Features", + null_cmd, 0, true, + read_local_features_rsp, 9, true }, + { 0x1004, 118, "Read Local Extended Features", + read_local_ext_features_cmd, 1, true, + read_local_ext_features_rsp, 11, true }, + { 0x1005, 119, "Read Buffer Size", + null_cmd, 0, true, + read_buffer_size_rsp, 8, true }, + { 0x1007, 120, "Read Country Code", + null_cmd, 0, true, + read_country_code_rsp, 2, true }, + { 0x1009, 121, "Read BD ADDR", + null_cmd, 0, true, + read_bd_addr_rsp, 7, true }, + { 0x100a, 186, "Read Data Block Size", + null_cmd, 0, true, + read_data_block_size_rsp, 7, true }, + { 0x100b, 237, "Read Local Supported Codecs", + null_cmd, 0, true, + read_local_codecs_rsp, 3, false }, + + /* OGF 5 - Status Parameter */ + { 0x1401, 122, "Read Failed Contact Counter", + read_failed_contact_counter_cmd, 2, true, + read_failed_contact_counter_rsp, 5, true }, + { 0x1402, 123, "Reset Failed Contact Counter", + reset_failed_contact_counter_cmd, 2, true, + reset_failed_contact_counter_rsp, 3, true }, + { 0x1403, 124, "Read Link Quality", + read_link_quality_cmd, 2, true, + read_link_quality_rsp, 4, true }, + { 0x1405, 125, "Read RSSI", + read_rssi_cmd, 2, true, + read_rssi_rsp, 4, true }, + { 0x1406, 126, "Read AFH Channel Map", + read_afh_channel_map_cmd, 2, true, + read_afh_channel_map_rsp, 14, true }, + { 0x1407, 127, "Read Clock", + read_clock_cmd, 3, true, + read_clock_rsp, 9, true }, + { 0x1408, 164, "Read Encryption Key Size", + read_encrypt_key_size_cmd, 2, true, + read_encrypt_key_size_rsp, 4, true }, + { 0x1409, 181, "Read Local AMP Info", + null_cmd, 0, true, + read_local_amp_info_rsp, 31, true }, + { 0x140a, 182, "Read Local AMP ASSOC", + read_local_amp_assoc_cmd, 5, true, + read_local_amp_assoc_rsp, 5, false }, + { 0x140b, 183, "Write Remote AMP ASSOC", + write_remote_amp_assoc_cmd, 6, false, + write_remote_amp_assoc_rsp, 2, true }, + { 0x140c, 243, "Get MWS Transport Layer Configuration", + null_cmd, 0, true, + get_mws_transport_config_rsp, 2, false }, + { 0x140d, 245, "Set Triggered Clock Capture", + set_triggered_clock_capture_cmd, 6, true, + status_rsp, 1, true }, + + /* OGF 6 - Testing */ + { 0x1801, 128, "Read Loopback Mode", + null_cmd, 0, true, + read_loopback_mode_rsp, 2, true }, + { 0x1802, 129, "Write Loopback Mode", + write_loopback_mode_cmd, 1, true, + status_rsp, 1, true }, + { 0x1803, 130, "Enable Device Under Test Mode", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x1804, 157, "Write Simple Pairing Debug Mode", + write_ssp_debug_mode_cmd, 1, true, + status_rsp, 1, true }, + { 0x1807, 189, "Enable AMP Receiver Reports" }, + { 0x1808, 190, "AMP Test End" }, + { 0x1809, 191, "AMP Test" }, + { 0x180a, 263, "Write Secure Connections Test Mode" }, + + /* OGF 8 - LE Control */ + { 0x2001, 200, "LE Set Event Mask", + le_set_event_mask_cmd, 8, true, + status_rsp, 1, true }, + { 0x2002, 201, "LE Read Buffer Size", + null_cmd, 0, true, + le_read_buffer_size_rsp, 4, true }, + { 0x2003, 202, "LE Read Local Supported Features", + null_cmd, 0, true, + le_read_local_features_rsp, 9, true }, + { 0x2005, 204, "LE Set Random Address", + le_set_random_address_cmd, 6, true, + status_rsp, 1, true }, + { 0x2006, 205, "LE Set Advertising Parameters", + le_set_adv_parameters_cmd, 15, true, + status_rsp, 1, true }, + { 0x2007, 206, "LE Read Advertising Channel TX Power", + null_cmd, 0, true, + le_read_adv_tx_power_rsp, 2, true }, + { 0x2008, 207, "LE Set Advertising Data", + le_set_adv_data_cmd, 32, true, + status_rsp, 1, true }, + { 0x2009, 208, "LE Set Scan Response Data", + le_set_scan_rsp_data_cmd, 32, true, + status_rsp, 1, true }, + { 0x200a, 209, "LE Set Advertise Enable", + le_set_adv_enable_cmd, 1, true, + status_rsp, 1, true }, + { 0x200b, 210, "LE Set Scan Parameters", + le_set_scan_parameters_cmd, 7, true, + status_rsp, 1, true }, + { 0x200c, 211, "LE Set Scan Enable", + le_set_scan_enable_cmd, 2, true, + status_rsp, 1, true }, + { 0x200d, 212, "LE Create Connection", + le_create_conn_cmd, 25, true }, + { 0x200e, 213, "LE Create Connection Cancel", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x200f, 214, "LE Read White List Size", + null_cmd, 0, true, + le_read_white_list_size_rsp, 2, true }, + { 0x2010, 215, "LE Clear White List", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x2011, 216, "LE Add Device To White List", + le_add_to_white_list_cmd, 7, true, + status_rsp, 1, true }, + { 0x2012, 217, "LE Remove Device From White List", + le_remove_from_white_list_cmd, 7, true, + status_rsp, 1, true }, + { 0x2013, 218, "LE Connection Update", + le_conn_update_cmd, 14, true }, + { 0x2014, 219, "LE Set Host Channel Classification", + le_set_host_classification_cmd, 5, true, + status_rsp, 1, true }, + { 0x2015, 220, "LE Read Channel Map", + le_read_channel_map_cmd, 2, true, + le_read_channel_map_rsp, 8, true }, + { 0x2016, 221, "LE Read Remote Used Features", + le_read_remote_features_cmd, 2, true }, + { 0x2017, 222, "LE Encrypt", + le_encrypt_cmd, 32, true, + le_encrypt_rsp, 17, true }, + { 0x2018, 223, "LE Rand", + null_cmd, 0, true, + le_rand_rsp, 9, true }, + { 0x2019, 224, "LE Start Encryption", + le_start_encrypt_cmd, 28, true }, + { 0x201a, 225, "LE Long Term Key Request Reply", + le_ltk_req_reply_cmd, 18, true, + le_ltk_req_reply_rsp, 3, true }, + { 0x201b, 226, "LE Long Term Key Request Neg Reply", + le_ltk_req_neg_reply_cmd, 2, true, + le_ltk_req_neg_reply_rsp, 3, true }, + { 0x201c, 227, "LE Read Supported States", + null_cmd, 0, true, + le_read_supported_states_rsp, 9, true }, + { 0x201d, 228, "LE Receiver Test", + le_receiver_test_cmd, 1, true, + status_rsp, 1, true }, + { 0x201e, 229, "LE Transmitter Test", + le_transmitter_test_cmd, 3, true, + status_rsp, 1, true }, + { 0x201f, 230, "LE Test End", + null_cmd, 0, true, + le_test_end_rsp, 3, true }, + { 0x2020, 268, "LE Remote Connection Parameter Request Reply", + le_conn_param_req_reply_cmd, 14, true, + le_conn_param_req_reply_rsp, 3, true }, + { 0x2021, 269, "LE Remote Connection Parameter Request Negative Reply", + le_conn_param_req_neg_reply_cmd, 3, true, + le_conn_param_req_neg_reply_rsp, 3, true }, + { 0x2022, 270, "LE Set Data Length", + le_set_data_length_cmd, 6, true, + le_set_data_length_rsp, 3, true }, + { 0x2023, 271, "LE Read Suggested Default Data Length", + null_cmd, 0, true, + le_read_default_data_length_rsp, 5, true }, + { 0x2024, 272, "LE Write Suggested Default Data Length", + le_write_default_data_length_cmd, 4, true, + status_rsp, 1, true }, + { 0x2025, 273, "LE Read Local P-256 Public Key", + null_cmd, 0, true }, + { 0x2026, 274, "LE Generate DHKey", + le_generate_dhkey_cmd, 64, true }, + { 0x2027, 275, "LE Add Device To Resolving List", + le_add_to_resolv_list_cmd, 39, true, + status_rsp, 1, true }, + { 0x2028, 276, "LE Remove Device From Resolving List", + le_remove_from_resolv_list_cmd, 7, true, + status_rsp, 1, true }, + { 0x2029, 277, "LE Clear Resolving List", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x202a, 278, "LE Read Resolving List Size", + null_cmd, 0, true, + le_read_resolv_list_size_rsp, 2, true }, + { 0x202b, 279, "LE Read Peer Resolvable Address", + le_read_peer_resolv_addr_cmd, 7, true, + le_read_peer_resolv_addr_rsp, 7, true }, + { 0x202c, 280, "LE Read Local Resolvable Address", + le_read_local_resolv_addr_cmd, 7, true, + le_read_local_resolv_addr_rsp, 7, true }, + { 0x202d, 281, "LE Set Address Resolution Enable", + le_set_resolv_enable_cmd, 1, true, + status_rsp, 1, true }, + { 0x202e, 282, "LE Set Resolvable Private Address Timeout", + le_set_resolv_timeout_cmd, 2, true, + status_rsp, 1, true }, + { 0x202f, 283, "LE Read Maximum Data Length", + null_cmd, 0, true, + le_read_max_data_length_rsp, 9, true }, + { 0x2030, 284, "LE Read PHY", + le_read_phy_cmd, 2, true, + le_read_phy_rsp, 5, true}, + { 0x2031, 285, "LE Set Default PHY", + le_set_default_phy_cmd, 3, true, + status_rsp, 1, true }, + { 0x2032, 286, "LE Set PHY", + le_set_phy_cmd, 7, true}, + { 0x2033, 287, "LE Enhanced Receiver Test", + le_enhanced_receiver_test_cmd, 3, true, + status_rsp, 1, true }, + { 0x2034, 288, "LE Enhanced Transmitter Test", + le_enhanced_transmitter_test_cmd, 4, true, + status_rsp, 1, true }, + { 0x2035, 289, "LE Set Advertising Set Random Address", + le_set_adv_set_rand_addr, 7, true, + status_rsp, 1, true }, + { 0x2036, 290, "LE Set Extended Advertising Parameters", + le_set_ext_adv_params_cmd, 25, true, + le_set_ext_adv_params_rsp, 2, true }, + { 0x2037, 291, "LE Set Extended Advertising Data", + le_set_ext_adv_data_cmd, 4, false, + status_rsp, 1, true }, + { 0x2038, 292, "LE Set Extended Scan Response Data", + le_set_ext_scan_rsp_data_cmd, 4, false, + status_rsp, 1, true }, + { 0x2039, 293, "LE Set Extended Advertising Enable", + le_set_ext_adv_enable_cmd, 2, false, + status_rsp, 1, true }, + { 0x203a, 294, "LE Read Maximum Advertising Data Length", + null_cmd, 0, true, + le_read_max_adv_data_len_rsp, 3, true }, + { 0x203b, 295, "LE Read Number of Supported Advertising Sets", + null_cmd, 0, true, + le_read_num_supported_adv_sets_rsp, 2, true }, + { 0x203c, 296, "LE Remove Advertising Set", + le_remove_adv_set_cmd, 1, true, + status_rsp, 1, true }, + { 0x203d, 297, "LE Clear Advertising Sets", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x203e, 298, "LE Set Periodic Advertising Parameters", + le_set_periodic_adv_params_cmd, 7, true, + status_rsp, 1, true }, + { 0x203f, 299, "LE Set Periodic Advertising Data", + le_set_periodic_adv_data_cmd, 3, false, + status_rsp, 1, true }, + { 0x2040, 300, "LE Set Periodic Advertising Enable", + le_set_periodic_adv_enable_cmd, 2, true, + status_rsp, 1, true }, + { 0x2041, 301, "LE Set Extended Scan Parameters", + le_set_ext_scan_params_cmd, 3, false, + status_rsp, 1, true }, + { 0x2042, 302, "LE Set Extended Scan Enable", + le_set_ext_scan_enable_cmd, 6, true, + status_rsp, 1, true }, + { 0x2043, 303, "LE Extended Create Connection", + le_ext_create_conn_cmd, 10, false, + status_rsp, 1, true }, + { 0x2044, 304, "LE Periodic Advertising Create Sync", + le_periodic_adv_create_sync_cmd, 14, true, + status_rsp, 1, true }, + { 0x2045, 305, "LE Periodic Advertising Create Sync Cancel", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x2046, 306, "LE Periodic Advertising Terminate Sync", + le_periodic_adv_term_sync_cmd, 2, true, + status_rsp, 1, true }, + { 0x2047, 307, "LE Add Device To Periodic Advertiser List", + le_add_dev_periodic_adv_list_cmd, 8, true, + status_rsp, 1, true }, + { 0x2048, 308, "LE Remove Device From Periodic Advertiser List", + le_remove_dev_periodic_adv_list_cmd, 8, true, + status_rsp, 1, true }, + { 0x2049, 309, "LE Clear Periodic Advertiser List", + null_cmd, 0, true, + status_rsp, 1, true }, + { 0x204a, 310, "LE Read Periodic Advertiser List Size", + null_cmd, 0, true, + le_read_periodic_adv_list_size_rsp, 2, true }, + { 0x204b, 311, "LE Read Transmit Power", + null_cmd, 0, true, + le_read_tx_power_rsp, 3, true }, + { 0x204c, 312, "LE Read RF Path Compensation", + null_cmd, 0, true, + le_read_rf_path_comp_rsp, 5, true }, + { 0x204d, 313, "LE Write RF Path Compensation", + le_write_rf_path_comp_cmd, 4, true, + status_rsp, 1, true }, + { 0x204e, 314, "LE Set Privacy Mode", + le_set_priv_mode_cmd, 8, true, + status_rsp, 1, true }, + { 0x204f, 315, "LE Receiver Test command [v3]", + le_receiver_test_cmd_v3, 7, false, + status_rsp, 1, true }, + { 0x2050, 316, "LE Transmitter Test command [v3]", + le_tx_test_cmd_v3, 9, false, + status_rsp, 1, true }, + { } +}; + +static const char *get_supported_command(int bit) +{ + int i; + + for (i = 0; opcode_table[i].str; i++) { + if (opcode_table[i].bit == bit) + return opcode_table[i].str; + } + + return NULL; +} + +static const char *current_vendor_str(void) +{ + uint16_t manufacturer; + + if (index_current < MAX_INDEX) + manufacturer = index_list[index_current].manufacturer; + else + manufacturer = fallback_manufacturer; + + switch (manufacturer) { + case 2: + return "Intel"; + case 15: + return "Broadcom"; + } + + return NULL; +} + +static const struct vendor_ocf *current_vendor_ocf(uint16_t ocf) +{ + uint16_t manufacturer; + + if (index_current < MAX_INDEX) + manufacturer = index_list[index_current].manufacturer; + else + manufacturer = fallback_manufacturer; + + switch (manufacturer) { + case 2: + return intel_vendor_ocf(ocf); + case 15: + return broadcom_vendor_ocf(ocf); + } + + return NULL; +} + +static const struct vendor_evt *current_vendor_evt(uint8_t evt) +{ + uint16_t manufacturer; + + if (index_current < MAX_INDEX) + manufacturer = index_list[index_current].manufacturer; + else + manufacturer = fallback_manufacturer; + + switch (manufacturer) { + case 2: + return intel_vendor_evt(evt); + case 15: + return broadcom_vendor_evt(evt); + } + + return NULL; +} + +static void inquiry_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_inquiry_complete *evt = data; + + print_status(evt->status); +} + +static void inquiry_result_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_inquiry_result *evt = data; + + print_num_resp(evt->num_resp); + print_bdaddr(evt->bdaddr); + print_pscan_rep_mode(evt->pscan_rep_mode); + print_pscan_period_mode(evt->pscan_period_mode); + print_pscan_mode(evt->pscan_mode); + print_dev_class(evt->dev_class); + print_clock_offset(evt->clock_offset); + + if (size > sizeof(*evt)) + packet_hexdump(data + sizeof(*evt), size - sizeof(*evt)); +} + +static void conn_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_conn_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_bdaddr(evt->bdaddr); + print_link_type(evt->link_type); + print_enable("Encryption", evt->encr_mode); + + if (evt->status == 0x00) + assign_handle(le16_to_cpu(evt->handle), 0x00); +} + +static void conn_request_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_conn_request *evt = data; + + print_bdaddr(evt->bdaddr); + print_dev_class(evt->dev_class); + print_link_type(evt->link_type); +} + +static void disconnect_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_disconnect_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_reason(evt->reason); + + if (evt->status == 0x00) + release_handle(le16_to_cpu(evt->handle)); +} + +static void auth_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_auth_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); +} + +static void remote_name_request_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_remote_name_request_complete *evt = data; + + print_status(evt->status); + print_bdaddr(evt->bdaddr); + print_name(evt->name); +} + +static void encrypt_change_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_encrypt_change *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_encr_mode_change(evt->encr_mode, evt->handle); +} + +static void change_conn_link_key_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_change_conn_link_key_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); +} + +static void master_link_key_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_master_link_key_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_key_flag(evt->key_flag); +} + +static void remote_features_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_remote_features_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_features(0, evt->features, 0x00); +} + +static void remote_version_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_remote_version_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_lmp_version(evt->lmp_ver, evt->lmp_subver); + print_manufacturer(evt->manufacturer); + + switch (le16_to_cpu(evt->manufacturer)) { + case 15: + print_manufacturer_broadcom(evt->lmp_subver, 0xffff); + break; + } +} + +static void qos_setup_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_qos_setup_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_field("Flags: 0x%2.2x", evt->flags); + + print_service_type(evt->service_type); + + print_field("Token rate: %d", le32_to_cpu(evt->token_rate)); + print_field("Peak bandwidth: %d", le32_to_cpu(evt->peak_bandwidth)); + print_field("Latency: %d", le32_to_cpu(evt->latency)); + print_field("Delay variation: %d", le32_to_cpu(evt->delay_variation)); +} + +static void cmd_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_cmd_complete *evt = data; + uint16_t opcode = le16_to_cpu(evt->opcode); + uint16_t ogf = cmd_opcode_ogf(opcode); + uint16_t ocf = cmd_opcode_ocf(opcode); + struct opcode_data vendor_data; + const struct opcode_data *opcode_data = NULL; + const char *opcode_color, *opcode_str; + char vendor_str[150]; + int i; + + for (i = 0; opcode_table[i].str; i++) { + if (opcode_table[i].opcode == opcode) { + opcode_data = &opcode_table[i]; + break; + } + } + + if (opcode_data) { + if (opcode_data->rsp_func) + opcode_color = COLOR_HCI_COMMAND; + else + opcode_color = COLOR_HCI_COMMAND_UNKNOWN; + opcode_str = opcode_data->str; + } else { + if (ogf == 0x3f) { + const struct vendor_ocf *vnd = current_vendor_ocf(ocf); + + if (vnd) { + const char *str = current_vendor_str(); + + if (str) { + snprintf(vendor_str, sizeof(vendor_str), + "%s %s", str, vnd->str); + vendor_data.str = vendor_str; + } else + vendor_data.str = vnd->str; + vendor_data.rsp_func = vnd->rsp_func; + vendor_data.rsp_size = vnd->rsp_size; + vendor_data.rsp_fixed = vnd->rsp_fixed; + + opcode_data = &vendor_data; + + if (opcode_data->rsp_func) + opcode_color = COLOR_HCI_COMMAND; + else + opcode_color = COLOR_HCI_COMMAND_UNKNOWN; + opcode_str = opcode_data->str; + } else { + opcode_color = COLOR_HCI_COMMAND; + opcode_str = "Vendor"; + } + } else { + opcode_color = COLOR_HCI_COMMAND_UNKNOWN; + opcode_str = "Unknown"; + } + } + + print_indent(6, opcode_color, "", opcode_str, COLOR_OFF, + " (0x%2.2x|0x%4.4x) ncmd %d", ogf, ocf, evt->ncmd); + + if (!opcode_data || !opcode_data->rsp_func) { + if (size > 3) { + uint8_t status = *((uint8_t *) (data + 3)); + + print_status(status); + packet_hexdump(data + 4, size - 4); + } + return; + } + + if (opcode_data->rsp_size > 1 && size - 3 == 1) { + uint8_t status = *((uint8_t *) (data + 3)); + + print_status(status); + return; + } + + if (opcode_data->rsp_fixed) { + if (size - 3 != opcode_data->rsp_size) { + print_text(COLOR_ERROR, "invalid packet size"); + packet_hexdump(data + 3, size - 3); + return; + } + } else { + if (size - 3 < opcode_data->rsp_size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data + 3, size - 3); + return; + } + } + + opcode_data->rsp_func(data + 3, size - 3); +} + +static void cmd_status_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_cmd_status *evt = data; + uint16_t opcode = le16_to_cpu(evt->opcode); + uint16_t ogf = cmd_opcode_ogf(opcode); + uint16_t ocf = cmd_opcode_ocf(opcode); + const struct opcode_data *opcode_data = NULL; + const char *opcode_color, *opcode_str; + char vendor_str[150]; + int i; + + for (i = 0; opcode_table[i].str; i++) { + if (opcode_table[i].opcode == opcode) { + opcode_data = &opcode_table[i]; + break; + } + } + + if (opcode_data) { + opcode_color = COLOR_HCI_COMMAND; + opcode_str = opcode_data->str; + } else { + if (ogf == 0x3f) { + const struct vendor_ocf *vnd = current_vendor_ocf(ocf); + + if (vnd) { + const char *str = current_vendor_str(); + + if (str) { + snprintf(vendor_str, sizeof(vendor_str), + "%s %s", str, vnd->str); + opcode_str = vendor_str; + } else + opcode_str = vnd->str; + + opcode_color = COLOR_HCI_COMMAND; + } else { + opcode_color = COLOR_HCI_COMMAND; + opcode_str = "Vendor"; + } + } else { + opcode_color = COLOR_HCI_COMMAND_UNKNOWN; + opcode_str = "Unknown"; + } + } + + print_indent(6, opcode_color, "", opcode_str, COLOR_OFF, + " (0x%2.2x|0x%4.4x) ncmd %d", ogf, ocf, evt->ncmd); + + print_status(evt->status); +} + +static void hardware_error_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_hardware_error *evt = data; + + print_field("Code: 0x%2.2x", evt->code); +} + +static void flush_occurred_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_flush_occurred *evt = data; + + print_handle(evt->handle); +} + +static void role_change_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_role_change *evt = data; + + print_status(evt->status); + print_bdaddr(evt->bdaddr); + print_role(evt->role); +} + +static void num_completed_packets_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_num_completed_packets *evt = data; + + print_field("Num handles: %d", evt->num_handles); + print_handle(evt->handle); + print_field("Count: %d", le16_to_cpu(evt->count)); + + if (size > sizeof(*evt)) + packet_hexdump(data + sizeof(*evt), size - sizeof(*evt)); +} + +static void mode_change_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_mode_change *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_mode(evt->mode); + print_interval(evt->interval); +} + +static void return_link_keys_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_return_link_keys *evt = data; + uint8_t i; + + print_field("Num keys: %d", evt->num_keys); + + for (i = 0; i < evt->num_keys; i++) { + print_bdaddr(evt->keys + (i * 22)); + print_link_key(evt->keys + (i * 22) + 6); + } +} + +static void pin_code_request_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_pin_code_request *evt = data; + + print_bdaddr(evt->bdaddr); +} + +static void link_key_request_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_link_key_request *evt = data; + + print_bdaddr(evt->bdaddr); +} + +static void link_key_notify_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_link_key_notify *evt = data; + + print_bdaddr(evt->bdaddr); + print_link_key(evt->link_key); + print_key_type(evt->key_type); +} + +static void loopback_command_evt(const void *data, uint8_t size) +{ + packet_hexdump(data, size); +} + +static void data_buffer_overflow_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_data_buffer_overflow *evt = data; + + print_link_type(evt->link_type); +} + +static void max_slots_change_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_max_slots_change *evt = data; + + print_handle(evt->handle); + print_field("Max slots: %d", evt->max_slots); +} + +static void clock_offset_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_clock_offset_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_clock_offset(evt->clock_offset); +} + +static void conn_pkt_type_changed_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_conn_pkt_type_changed *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_pkt_type(evt->pkt_type); +} + +static void qos_violation_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_qos_violation *evt = data; + + print_handle(evt->handle); +} + +static void pscan_mode_change_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_pscan_mode_change *evt = data; + + print_bdaddr(evt->bdaddr); + print_pscan_mode(evt->pscan_mode); +} + +static void pscan_rep_mode_change_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_pscan_rep_mode_change *evt = data; + + print_bdaddr(evt->bdaddr); + print_pscan_rep_mode(evt->pscan_rep_mode); +} + +static void flow_spec_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_flow_spec_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_field("Flags: 0x%2.2x", evt->flags); + + print_flow_direction(evt->direction); + print_service_type(evt->service_type); + + print_field("Token rate: %d", le32_to_cpu(evt->token_rate)); + print_field("Token bucket size: %d", + le32_to_cpu(evt->token_bucket_size)); + print_field("Peak bandwidth: %d", le32_to_cpu(evt->peak_bandwidth)); + print_field("Access latency: %d", le32_to_cpu(evt->access_latency)); +} + +static void inquiry_result_with_rssi_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_inquiry_result_with_rssi *evt = data; + + print_num_resp(evt->num_resp); + print_bdaddr(evt->bdaddr); + print_pscan_rep_mode(evt->pscan_rep_mode); + print_pscan_period_mode(evt->pscan_period_mode); + print_dev_class(evt->dev_class); + print_clock_offset(evt->clock_offset); + print_rssi(evt->rssi); + + if (size > sizeof(*evt)) + packet_hexdump(data + sizeof(*evt), size - sizeof(*evt)); +} + +static void remote_ext_features_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_remote_ext_features_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_field("Page: %d/%d", evt->page, evt->max_page); + print_features(evt->page, evt->features, 0x00); +} + +static void sync_conn_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_sync_conn_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_bdaddr(evt->bdaddr); + print_link_type(evt->link_type); + print_field("Transmission interval: 0x%2.2x", evt->tx_interval); + print_field("Retransmission window: 0x%2.2x", evt->retrans_window); + print_field("RX packet length: %d", le16_to_cpu(evt->rx_pkt_len)); + print_field("TX packet length: %d", le16_to_cpu(evt->tx_pkt_len)); + print_air_mode(evt->air_mode); +} + +static void sync_conn_changed_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_sync_conn_changed *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_field("Transmission interval: 0x%2.2x", evt->tx_interval); + print_field("Retransmission window: 0x%2.2x", evt->retrans_window); + print_field("RX packet length: %d", le16_to_cpu(evt->rx_pkt_len)); + print_field("TX packet length: %d", le16_to_cpu(evt->tx_pkt_len)); +} + +static void sniff_subrating_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_sniff_subrating *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_slot_625("Max transmit latency", evt->max_tx_latency); + print_slot_625("Max receive latency", evt->max_rx_latency); + print_slot_625("Min remote timeout", evt->min_remote_timeout); + print_slot_625("Min local timeout", evt->min_local_timeout); +} + +static void ext_inquiry_result_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_ext_inquiry_result *evt = data; + + print_num_resp(evt->num_resp); + print_bdaddr(evt->bdaddr); + print_pscan_rep_mode(evt->pscan_rep_mode); + print_pscan_period_mode(evt->pscan_period_mode); + print_dev_class(evt->dev_class); + print_clock_offset(evt->clock_offset); + print_rssi(evt->rssi); + print_eir(evt->data, sizeof(evt->data), false); +} + +static void encrypt_key_refresh_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_encrypt_key_refresh_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); +} + +static void io_capability_request_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_io_capability_request *evt = data; + + print_bdaddr(evt->bdaddr); +} + +static void io_capability_response_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_io_capability_response *evt = data; + + print_bdaddr(evt->bdaddr); + print_io_capability(evt->capability); + print_oob_data_response(evt->oob_data); + print_authentication(evt->authentication); +} + +static void user_confirm_request_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_user_confirm_request *evt = data; + + print_bdaddr(evt->bdaddr); + print_passkey(evt->passkey); +} + +static void user_passkey_request_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_user_passkey_request *evt = data; + + print_bdaddr(evt->bdaddr); +} + +static void remote_oob_data_request_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_remote_oob_data_request *evt = data; + + print_bdaddr(evt->bdaddr); +} + +static void simple_pairing_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_simple_pairing_complete *evt = data; + + print_status(evt->status); + print_bdaddr(evt->bdaddr); +} + +static void link_supv_timeout_changed_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_link_supv_timeout_changed *evt = data; + + print_handle(evt->handle); + print_timeout(evt->timeout); +} + +static void enhanced_flush_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_enhanced_flush_complete *evt = data; + + print_handle(evt->handle); +} + +static void user_passkey_notify_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_user_passkey_notify *evt = data; + + print_bdaddr(evt->bdaddr); + print_passkey(evt->passkey); +} + +static void keypress_notify_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_keypress_notify *evt = data; + const char *str; + + print_bdaddr(evt->bdaddr); + + switch (evt->type) { + case 0x00: + str = "Passkey entry started"; + break; + case 0x01: + str = "Passkey digit entered"; + break; + case 0x02: + str = "Passkey digit erased"; + break; + case 0x03: + str = "Passkey clared"; + break; + case 0x04: + str = "Passkey entry completed"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Notification type: %s (0x%2.2x)", str, evt->type); +} + +static void remote_host_features_notify_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_remote_host_features_notify *evt = data; + + print_bdaddr(evt->bdaddr); + print_features(1, evt->features, 0x00); +} + +static void phy_link_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_phy_link_complete *evt = data; + + print_status(evt->status); + print_phy_handle(evt->phy_handle); +} + +static void channel_selected_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_channel_selected *evt = data; + + print_phy_handle(evt->phy_handle); +} + +static void disconn_phy_link_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_disconn_phy_link_complete *evt = data; + + print_status(evt->status); + print_phy_handle(evt->phy_handle); + print_reason(evt->reason); +} + +static void phy_link_loss_early_warning_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_phy_link_loss_early_warning *evt = data; + const char *str; + + print_phy_handle(evt->phy_handle); + + switch (evt->reason) { + case 0x00: + str = "Unknown"; + break; + case 0x01: + str = "Range related"; + break; + case 0x02: + str = "Bandwidth related"; + break; + case 0x03: + str = "Resolving conflict"; + break; + case 0x04: + str = "Interference"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Reason: %s (0x%2.2x)", str, evt->reason); +} + +static void phy_link_recovery_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_phy_link_recovery *evt = data; + + print_phy_handle(evt->phy_handle); +} + +static void logic_link_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_logic_link_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_phy_handle(evt->phy_handle); + print_field("TX flow spec: 0x%2.2x", evt->flow_spec); +} + +static void disconn_logic_link_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_disconn_logic_link_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_reason(evt->reason); +} + +static void flow_spec_modify_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_flow_spec_modify_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); +} + +static void num_completed_data_blocks_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_num_completed_data_blocks *evt = data; + + print_field("Total num data blocks: %d", + le16_to_cpu(evt->total_num_blocks)); + print_field("Num handles: %d", evt->num_handles); + print_handle(evt->handle); + print_field("Num packets: %d", evt->num_packets); + print_field("Num blocks: %d", evt->num_blocks); + + if (size > sizeof(*evt)) + packet_hexdump(data + sizeof(*evt), size - sizeof(*evt)); +} + +static void short_range_mode_change_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_short_range_mode_change *evt = data; + + print_status(evt->status); + print_phy_handle(evt->phy_handle); + print_enable("Short range mode", evt->mode); +} + +static void amp_status_change_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_amp_status_change *evt = data; + + print_status(evt->status); + print_amp_status(evt->amp_status); +} + +static void triggered_clock_capture_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_triggered_clock_capture *evt = data; + + print_handle(evt->handle); + print_clock_type(evt->type); + print_clock(evt->clock); + print_clock_offset(evt->clock_offset); +} + +static void sync_train_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_sync_train_complete *evt = data; + + print_status(evt->status); +} + +static void sync_train_received_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_sync_train_received *evt = data; + + print_status(evt->status); + print_bdaddr(evt->bdaddr); + print_field("Offset: 0x%8.8x", le32_to_cpu(evt->offset)); + print_channel_map(evt->map); + print_lt_addr(evt->lt_addr); + print_field("Next broadcast instant: 0x%4.4x", + le16_to_cpu(evt->instant)); + print_interval(evt->interval); + print_field("Service Data: 0x%2.2x", evt->service_data); +} + +static void slave_broadcast_receive_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_slave_broadcast_receive *evt = data; + + print_bdaddr(evt->bdaddr); + print_lt_addr(evt->lt_addr); + print_field("Clock: 0x%8.8x", le32_to_cpu(evt->clock)); + print_field("Offset: 0x%8.8x", le32_to_cpu(evt->offset)); + print_field("Receive status: 0x%2.2x", evt->status); + print_broadcast_fragment(evt->fragment); + print_field("Length: %d", evt->length); + + if (size - 18 != evt->length) + print_text(COLOR_ERROR, "invalid data size (%d != %d)", + size - 18, evt->length); + + if (evt->lt_addr == 0x01 && evt->length == 17) + print_3d_broadcast(data + 18, size - 18); + else + packet_hexdump(data + 18, size - 18); +} + +static void slave_broadcast_timeout_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_slave_broadcast_timeout *evt = data; + + print_bdaddr(evt->bdaddr); + print_lt_addr(evt->lt_addr); +} + +static void truncated_page_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_truncated_page_complete *evt = data; + + print_status(evt->status); + print_bdaddr(evt->bdaddr); +} + +static void slave_page_response_timeout_evt(const void *data, uint8_t size) +{ +} + +static void slave_broadcast_channel_map_change_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_slave_broadcast_channel_map_change *evt = data; + + print_channel_map(evt->map); +} + +static void inquiry_response_notify_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_inquiry_response_notify *evt = data; + + print_iac(evt->lap); + print_rssi(evt->rssi); +} + +static void auth_payload_timeout_expired_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_auth_payload_timeout_expired *evt = data; + + print_handle(evt->handle); +} + +static void le_conn_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_conn_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_role(evt->role); + print_peer_addr_type("Peer address type", evt->peer_addr_type); + print_addr("Peer address", evt->peer_addr, evt->peer_addr_type); + print_slot_125("Connection interval", evt->interval); + print_conn_latency("Connection latency", evt->latency); + print_field("Supervision timeout: %d msec (0x%4.4x)", + le16_to_cpu(evt->supv_timeout) * 10, + le16_to_cpu(evt->supv_timeout)); + print_field("Master clock accuracy: 0x%2.2x", evt->clock_accuracy); + + if (evt->status == 0x00) + assign_handle(le16_to_cpu(evt->handle), 0x01); +} + +static void le_adv_report_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_adv_report *evt = data; + uint8_t evt_len; + int8_t *rssi; + + print_num_reports(evt->num_reports); + +report: + print_adv_event_type("Event type", evt->event_type); + print_peer_addr_type("Address type", evt->addr_type); + print_addr("Address", evt->addr, evt->addr_type); + print_field("Data length: %d", evt->data_len); + print_eir(evt->data, evt->data_len, true); + + rssi = (int8_t *) (evt->data + evt->data_len); + print_rssi(*rssi); + + evt_len = sizeof(*evt) + evt->data_len + 1; + + if (size > evt_len) { + data += evt_len - 1; + size -= evt_len - 1; + evt = data; + goto report; + } +} + +static void le_conn_update_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_conn_update_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_slot_125("Connection interval", evt->interval); + print_conn_latency("Connection latency", evt->latency); + print_field("Supervision timeout: %d msec (0x%4.4x)", + le16_to_cpu(evt->supv_timeout) * 10, + le16_to_cpu(evt->supv_timeout)); +} + +static void le_remote_features_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_remote_features_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_features(0, evt->features, 0x01); +} + +static void le_long_term_key_request_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_long_term_key_request *evt = data; + + print_handle(evt->handle); + print_random_number(evt->rand); + print_encrypted_diversifier(evt->ediv); +} + +static void le_conn_param_request_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_conn_param_request *evt = data; + + print_handle(evt->handle); + print_slot_125("Min connection interval", evt->min_interval); + print_slot_125("Max connection interval", evt->max_interval); + print_conn_latency("Connection latency", evt->latency); + print_field("Supervision timeout: %d msec (0x%4.4x)", + le16_to_cpu(evt->supv_timeout) * 10, + le16_to_cpu(evt->supv_timeout)); +} + +static void le_data_length_change_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_data_length_change *evt = data; + + print_handle(evt->handle); + print_field("Max TX octets: %d", le16_to_cpu(evt->max_tx_len)); + print_field("Max TX time: %d", le16_to_cpu(evt->max_tx_time)); + print_field("Max RX octets: %d", le16_to_cpu(evt->max_rx_len)); + print_field("Max RX time: %d", le16_to_cpu(evt->max_rx_time)); +} + +static void le_read_local_pk256_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_read_local_pk256_complete *evt = data; + + print_status(evt->status); + print_pk256("Local P-256 public key", evt->local_pk256); +} + +static void le_generate_dhkey_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_generate_dhkey_complete *evt = data; + + print_status(evt->status); + print_dhkey(evt->dhkey); +} + +static void le_enhanced_conn_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_enhanced_conn_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_role(evt->role); + print_peer_addr_type("Peer address type", evt->peer_addr_type); + print_addr("Peer address", evt->peer_addr, evt->peer_addr_type); + print_addr("Local resolvable private address", evt->local_rpa, 0x01); + print_addr("Peer resolvable private address", evt->peer_rpa, 0x01); + print_slot_125("Connection interval", evt->interval); + print_conn_latency("Connection latency", evt->latency); + print_field("Supervision timeout: %d msec (0x%4.4x)", + le16_to_cpu(evt->supv_timeout) * 10, + le16_to_cpu(evt->supv_timeout)); + print_field("Master clock accuracy: 0x%2.2x", evt->clock_accuracy); + + if (evt->status == 0x00) + assign_handle(le16_to_cpu(evt->handle), 0x01); +} + +static void le_direct_adv_report_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_direct_adv_report *evt = data; + + print_num_reports(evt->num_reports); + + print_adv_event_type("Event type", evt->event_type); + print_peer_addr_type("Address type", evt->addr_type); + print_addr("Address", evt->addr, evt->addr_type); + print_addr_type("Direct address type", evt->direct_addr_type); + print_addr("Direct address", evt->direct_addr, evt->direct_addr_type); + print_rssi(evt->rssi); + + if (size > sizeof(*evt)) + packet_hexdump(data + sizeof(*evt), size - sizeof(*evt)); +} + +static void le_phy_update_complete_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_phy_update_complete *evt = data; + + print_status(evt->status); + print_handle(evt->handle); + print_le_phy("TX PHY", evt->tx_phy); + print_le_phy("RX PHY", evt->rx_phy); +} + +static const struct bitfield_data ext_adv_report_evt_type[] = { + { 0, "Connectable" }, + { 1, "Scannable" }, + { 2, "Directed" }, + { 3, "Scan response" }, + { 4, "Use legacy advertising PDUs" }, + { } +}; + +static void print_ext_adv_report_evt_type(const char *indent, uint16_t flags) +{ + uint16_t mask = flags; + uint16_t props = flags; + uint8_t data_status; + const char *str; + const char *color_on; + int i; + + print_field("%sEvent type: 0x%4.4x", indent, flags); + + props &= 0x1f; + print_field("%s Props: 0x%4.4x", indent, props); + for (i = 0; ext_adv_report_evt_type[i].str; i++) { + if (flags & (1 << ext_adv_report_evt_type[i].bit)) { + print_field("%s %s", indent, + ext_adv_report_evt_type[i].str); + mask &= ~(1 << ext_adv_report_evt_type[i].bit); + } + } + + data_status = (flags >> 5) & 3; + mask &= ~(data_status << 5); + + switch (data_status) { + case 0x00: + str = "Complete"; + color_on = COLOR_GREEN; + break; + case 0x01: + str = "Incomplete, more data to come"; + color_on = COLOR_YELLOW; + break; + case 0x02: + str = "Incomplete, data truncated, no more to come"; + color_on = COLOR_RED; + break; + default: + str = "Reserved"; + color_on = COLOR_RED; + break; + } + + print_field("%s Data status: %s%s%s", indent, color_on, str, COLOR_OFF); + + if (mask) + print_text(COLOR_UNKNOWN_ADV_FLAG, + "%s Reserved (0x%4.4x)", indent, mask); +} + +static void print_legacy_adv_report_pdu(uint16_t flags) +{ + const char *str; + + if (!(flags & (1 << 4))) + return; + + switch (flags) { + case 0x10: + str = "ADV_NONCONN_IND"; + break; + case 0x12: + str = "ADV_SCAN_IND"; + break; + case 0x13: + str = "ADV_IND"; + break; + case 0x15: + str = "ADV_DIRECT_IND"; + break; + case 0x1a: + str = "SCAN_RSP to an ADV_IND"; + break; + case 0x1b: + str = "SCAN_RSP to an ADV_SCAN_IND"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" Legacy PDU Type: %s (0x%4.4x)", str, flags); +} + +static void le_ext_adv_report_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_ext_adv_report *evt = data; + const struct bt_hci_le_ext_adv_report *report; + const char *str; + int i; + + print_num_reports(evt->num_reports); + + data += sizeof(evt->num_reports); + + for (i = 0; i < evt->num_reports; ++i) { + report = data; + print_field("Entry %d", i); + print_ext_adv_report_evt_type(" ", report->event_type); + print_legacy_adv_report_pdu(report->event_type); + print_peer_addr_type(" Address type", report->addr_type); + print_addr(" Address", report->addr, report->addr_type); + + switch (report->primary_phy) { + case 0x01: + str = "LE 1M"; + break; + case 0x03: + str = "LE Coded"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" Primary PHY: %s", str); + + switch (report->secondary_phy) { + case 0x00: + str = "No packets"; + break; + case 0x01: + str = "LE 1M"; + break; + case 0x02: + str = "LE 2M"; + break; + case 0x03: + str = "LE Coded"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" Secondary PHY: %s", str); + + if (report->sid == 0xff) + print_field(" SID: no ADI field (0x%2.2x)", + report->sid); + else if (report->sid > 0x0f) + print_field(" SID: Reserved (0x%2.2x)", report->sid); + else + print_field(" SID: 0x%2.2x", report->sid); + + print_field(" TX power: %d dBm", report->tx_power); + + if (report->rssi == 127) + print_field(" RSSI: not available (0x%2.2x)", + (uint8_t) report->rssi); + else if (report->rssi >= -127 && report->rssi <= 20) + print_field(" RSSI: %d dBm (0x%2.2x)", + report->rssi, (uint8_t) report->rssi); + else + print_field(" RSSI: reserved (0x%2.2x)", + (uint8_t) report->rssi); + + print_slot_125(" Periodic advertising invteral", + report->interval); + print_peer_addr_type(" Direct address type", + report->direct_addr_type); + print_addr(" Direct address", report->direct_addr, + report->direct_addr_type); + print_field(" Data length: 0x%2.2x", report->data_len); + data += sizeof(struct bt_hci_le_ext_adv_report); + packet_hexdump(data, report->data_len); + data += report->data_len; + } +} + +static void le_per_adv_sync(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_per_sync_established *evt = data; + + print_status(evt->status); + print_field("Sync handle: %d", evt->handle); + if (evt->sid > 0x0f) + print_field("Advertising SID: Reserved (0x%2.2x)", evt->sid); + else + print_field("Advertising SID: 0x%2.2x", evt->sid); + + print_peer_addr_type("Advertiser address type", evt->addr_type); + print_addr("Advertiser address", evt->addr, evt->addr_type); + print_le_phy("Advertiser PHY", evt->phy); + print_slot_125("Periodic advertising invteral", evt->interval); + print_field("Advertiser clock accuracy: 0x%2.2x", evt->clock_accuracy); +} + +static void le_per_adv_report_evt(const void *data, uint8_t size) +{ + const struct bt_hci_le_per_adv_report *evt = data; + const char *color_on; + const char *str; + + print_field("Sync handle: %d", evt->handle); + print_power_level(evt->tx_power, NULL); + if (evt->rssi == 127) + print_field("RSSI: not available (0x%2.2x)", + (uint8_t) evt->rssi); + else if (evt->rssi >= -127 && evt->rssi <= 20) + print_field("RSSI: %d dBm (0x%2.2x)", + evt->rssi, (uint8_t) evt->rssi); + else + print_field("RSSI: reserved (0x%2.2x)", + (uint8_t) evt->rssi); + print_field("Unused: (0x%2.2x)", evt->unused); + + switch (evt->data_status) { + case 0x00: + str = "Complete"; + color_on = COLOR_GREEN; + break; + case 0x01: + str = "Incomplete, more data to come"; + color_on = COLOR_YELLOW; + break; + case 0x02: + str = "Incomplete, data truncated, no more to come"; + color_on = COLOR_RED; + break; + default: + str = "Reserved"; + color_on = COLOR_RED; + break; + } + + print_field("Data status: %s%s%s", color_on, str, COLOR_OFF); + print_field("Data length: 0x%2.2x", evt->data_len); + packet_hexdump(evt->data, evt->data_len); +} + +static void le_per_adv_sync_lost(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_per_sync_lost *evt = data; + + print_field("Sync handle: %d", evt->handle); +} + +static void le_adv_set_term_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_adv_set_term *evt = data; + + print_status(evt->status); + print_field("Handle: %d", evt->handle); + print_field("Connection handle: %d", evt->conn_handle); + print_field("Number of completed extended advertising events: %d", + evt->num_evts); +} + +static void le_scan_req_received_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_scan_req_received *evt = data; + + print_field("Handle: %d", evt->handle); + print_peer_addr_type("Scanner address type", evt->scanner_addr_type); + print_addr("Scanner address", evt->scanner_addr, + evt->scanner_addr_type); +} + +static void le_chan_select_alg_evt(const void *data, uint8_t size) +{ + const struct bt_hci_evt_le_chan_select_alg *evt = data; + const char *str; + + print_handle(evt->handle); + + switch (evt->algorithm) { + case 0x00: + str = "#1"; + break; + case 0x01: + str = "#2"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Algorithm: %s (0x%2.2x)", str, evt->algorithm); +} + +struct subevent_data { + uint8_t subevent; + const char *str; + void (*func) (const void *data, uint8_t size); + uint8_t size; + bool fixed; +}; + +static void print_subevent(const struct subevent_data *subevent_data, + const void *data, uint8_t size) +{ + const char *subevent_color; + + if (subevent_data->func) + subevent_color = COLOR_HCI_EVENT; + else + subevent_color = COLOR_HCI_EVENT_UNKNOWN; + + print_indent(6, subevent_color, "", subevent_data->str, COLOR_OFF, + " (0x%2.2x)", subevent_data->subevent); + + if (!subevent_data->func) { + packet_hexdump(data, size); + return; + } + + if (subevent_data->fixed) { + if (size != subevent_data->size) { + print_text(COLOR_ERROR, "invalid packet size"); + packet_hexdump(data, size); + return; + } + } else { + if (size < subevent_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data, size); + return; + } + } + + subevent_data->func(data, size); +} + +static const struct subevent_data le_meta_event_table[] = { + { 0x01, "LE Connection Complete", + le_conn_complete_evt, 18, true }, + { 0x02, "LE Advertising Report", + le_adv_report_evt, 1, false }, + { 0x03, "LE Connection Update Complete", + le_conn_update_complete_evt, 9, true }, + { 0x04, "LE Read Remote Used Features", + le_remote_features_complete_evt, 11, true }, + { 0x05, "LE Long Term Key Request", + le_long_term_key_request_evt, 12, true }, + { 0x06, "LE Remote Connection Parameter Request", + le_conn_param_request_evt, 10, true }, + { 0x07, "LE Data Length Change", + le_data_length_change_evt, 10, true }, + { 0x08, "LE Read Local P-256 Public Key Complete", + le_read_local_pk256_complete_evt, 65, true }, + { 0x09, "LE Generate DHKey Complete", + le_generate_dhkey_complete_evt, 33, true }, + { 0x0a, "LE Enhanced Connection Complete", + le_enhanced_conn_complete_evt, 30, true }, + { 0x0b, "LE Direct Advertising Report", + le_direct_adv_report_evt, 1, false }, + { 0x0c, "LE PHY Update Complete", + le_phy_update_complete_evt, 5, true}, + { 0x0d, "LE Extended Advertising Report", + le_ext_adv_report_evt, 1, false}, + { 0x0e, "LE Periodic Advertising Sync Established", + le_per_adv_sync, 15, true }, + { 0x0f, "LE Periodic Advertising Report", + le_per_adv_report_evt, 7, false}, + { 0x10, "LE Periodic Advertising Sync Lost", + le_per_adv_sync_lost, 2, true}, + { 0x11, "LE Scan Timeout" }, + { 0x12, "LE Advertising Set Terminated", + le_adv_set_term_evt, 5, true}, + { 0x13, "LE Scan Request Received", + le_scan_req_received_evt, 8, true}, + { 0x14, "LE Channel Selection Algorithm", + le_chan_select_alg_evt, 3, true}, + { } +}; + +static void le_meta_event_evt(const void *data, uint8_t size) +{ + uint8_t subevent = *((const uint8_t *) data); + struct subevent_data unknown; + const struct subevent_data *subevent_data = &unknown; + int i; + + unknown.subevent = subevent; + unknown.str = "Unknown"; + unknown.func = NULL; + unknown.size = 0; + unknown.fixed = true; + + for (i = 0; le_meta_event_table[i].str; i++) { + if (le_meta_event_table[i].subevent == subevent) { + subevent_data = &le_meta_event_table[i]; + break; + } + } + + print_subevent(subevent_data, data + 1, size - 1); +} + +static void vendor_evt(const void *data, uint8_t size) +{ + uint8_t subevent = *((const uint8_t *) data); + struct subevent_data vendor_data; + char vendor_str[150]; + const struct vendor_evt *vnd = current_vendor_evt(subevent); + + if (vnd) { + const char *str = current_vendor_str(); + + if (str) { + snprintf(vendor_str, sizeof(vendor_str), + "%s %s", str, vnd->str); + vendor_data.str = vendor_str; + } else + vendor_data.str = vnd->str; + vendor_data.subevent = subevent; + vendor_data.func = vnd->evt_func; + vendor_data.size = vnd->evt_size; + vendor_data.fixed = vnd->evt_fixed; + + print_subevent(&vendor_data, data + 1, size - 1); + } else { + uint16_t manufacturer; + + if (index_current < MAX_INDEX) + manufacturer = index_list[index_current].manufacturer; + else + manufacturer = fallback_manufacturer; + + vendor_event(manufacturer, data, size); + } +} + +struct event_data { + uint8_t event; + const char *str; + void (*func) (const void *data, uint8_t size); + uint8_t size; + bool fixed; +}; + +static const struct event_data event_table[] = { + { 0x01, "Inquiry Complete", + inquiry_complete_evt, 1, true }, + { 0x02, "Inquiry Result", + inquiry_result_evt, 1, false }, + { 0x03, "Connect Complete", + conn_complete_evt, 11, true }, + { 0x04, "Connect Request", + conn_request_evt, 10, true }, + { 0x05, "Disconnect Complete", + disconnect_complete_evt, 4, true }, + { 0x06, "Auth Complete", + auth_complete_evt, 3, true }, + { 0x07, "Remote Name Req Complete", + remote_name_request_complete_evt, 255, true }, + { 0x08, "Encryption Change", + encrypt_change_evt, 4, true }, + { 0x09, "Change Connection Link Key Complete", + change_conn_link_key_complete_evt, 3, true }, + { 0x0a, "Master Link Key Complete", + master_link_key_complete_evt, 4, true }, + { 0x0b, "Read Remote Supported Features", + remote_features_complete_evt, 11, true }, + { 0x0c, "Read Remote Version Complete", + remote_version_complete_evt, 8, true }, + { 0x0d, "QoS Setup Complete", + qos_setup_complete_evt, 21, true }, + { 0x0e, "Command Complete", + cmd_complete_evt, 3, false }, + { 0x0f, "Command Status", + cmd_status_evt, 4, true }, + { 0x10, "Hardware Error", + hardware_error_evt, 1, true }, + { 0x11, "Flush Occurred", + flush_occurred_evt, 2, true }, + { 0x12, "Role Change", + role_change_evt, 8, true }, + { 0x13, "Number of Completed Packets", + num_completed_packets_evt, 1, false }, + { 0x14, "Mode Change", + mode_change_evt, 6, true }, + { 0x15, "Return Link Keys", + return_link_keys_evt, 1, false }, + { 0x16, "PIN Code Request", + pin_code_request_evt, 6, true }, + { 0x17, "Link Key Request", + link_key_request_evt, 6, true }, + { 0x18, "Link Key Notification", + link_key_notify_evt, 23, true }, + { 0x19, "Loopback Command", + loopback_command_evt, 3, false }, + { 0x1a, "Data Buffer Overflow", + data_buffer_overflow_evt, 1, true }, + { 0x1b, "Max Slots Change", + max_slots_change_evt, 3, true }, + { 0x1c, "Read Clock Offset Complete", + clock_offset_complete_evt, 5, true }, + { 0x1d, "Connection Packet Type Changed", + conn_pkt_type_changed_evt, 5, true }, + { 0x1e, "QoS Violation", + qos_violation_evt, 2, true }, + { 0x1f, "Page Scan Mode Change", + pscan_mode_change_evt, 7, true }, + { 0x20, "Page Scan Repetition Mode Change", + pscan_rep_mode_change_evt, 7, true }, + { 0x21, "Flow Specification Complete", + flow_spec_complete_evt, 22, true }, + { 0x22, "Inquiry Result with RSSI", + inquiry_result_with_rssi_evt, 1, false }, + { 0x23, "Read Remote Extended Features", + remote_ext_features_complete_evt, 13, true }, + { 0x2c, "Synchronous Connect Complete", + sync_conn_complete_evt, 17, true }, + { 0x2d, "Synchronous Connect Changed", + sync_conn_changed_evt, 9, true }, + { 0x2e, "Sniff Subrating", + sniff_subrating_evt, 11, true }, + { 0x2f, "Extended Inquiry Result", + ext_inquiry_result_evt, 1, false }, + { 0x30, "Encryption Key Refresh Complete", + encrypt_key_refresh_complete_evt, 3, true }, + { 0x31, "IO Capability Request", + io_capability_request_evt, 6, true }, + { 0x32, "IO Capability Response", + io_capability_response_evt, 9, true }, + { 0x33, "User Confirmation Request", + user_confirm_request_evt, 10, true }, + { 0x34, "User Passkey Request", + user_passkey_request_evt, 6, true }, + { 0x35, "Remote OOB Data Request", + remote_oob_data_request_evt, 6, true }, + { 0x36, "Simple Pairing Complete", + simple_pairing_complete_evt, 7, true }, + { 0x38, "Link Supervision Timeout Changed", + link_supv_timeout_changed_evt, 4, true }, + { 0x39, "Enhanced Flush Complete", + enhanced_flush_complete_evt, 2, true }, + { 0x3b, "User Passkey Notification", + user_passkey_notify_evt, 10, true }, + { 0x3c, "Keypress Notification", + keypress_notify_evt, 7, true }, + { 0x3d, "Remote Host Supported Features", + remote_host_features_notify_evt, 14, true }, + { 0x3e, "LE Meta Event", + le_meta_event_evt, 1, false }, + { 0x40, "Physical Link Complete", + phy_link_complete_evt, 2, true }, + { 0x41, "Channel Selected", + channel_selected_evt, 1, true }, + { 0x42, "Disconnect Physical Link Complete", + disconn_phy_link_complete_evt, 3, true }, + { 0x43, "Physical Link Loss Early Warning", + phy_link_loss_early_warning_evt, 2, true }, + { 0x44, "Physical Link Recovery", + phy_link_recovery_evt, 1, true }, + { 0x45, "Logical Link Complete", + logic_link_complete_evt, 5, true }, + { 0x46, "Disconnect Logical Link Complete", + disconn_logic_link_complete_evt, 4, true }, + { 0x47, "Flow Specification Modify Complete", + flow_spec_modify_complete_evt, 3, true }, + { 0x48, "Number of Completed Data Blocks", + num_completed_data_blocks_evt, 3, false }, + { 0x49, "AMP Start Test" }, + { 0x4a, "AMP Test End" }, + { 0x4b, "AMP Receiver Report" }, + { 0x4c, "Short Range Mode Change Complete", + short_range_mode_change_evt, 3, true }, + { 0x4d, "AMP Status Change", + amp_status_change_evt, 2, true }, + { 0x4e, "Triggered Clock Capture", + triggered_clock_capture_evt, 9, true }, + { 0x4f, "Synchronization Train Complete", + sync_train_complete_evt, 1, true }, + { 0x50, "Synchronization Train Received", + sync_train_received_evt, 29, true }, + { 0x51, "Connectionless Slave Broadcast Receive", + slave_broadcast_receive_evt, 18, false }, + { 0x52, "Connectionless Slave Broadcast Timeout", + slave_broadcast_timeout_evt, 7, true }, + { 0x53, "Truncated Page Complete", + truncated_page_complete_evt, 7, true }, + { 0x54, "Slave Page Response Timeout", + slave_page_response_timeout_evt, 0, true }, + { 0x55, "Connectionless Slave Broadcast Channel Map Change", + slave_broadcast_channel_map_change_evt, 10, true }, + { 0x56, "Inquiry Response Notification", + inquiry_response_notify_evt, 4, true }, + { 0x57, "Authenticated Payload Timeout Expired", + auth_payload_timeout_expired_evt, 2, true }, + { 0x58, "SAM Status Change" }, + { 0xfe, "Testing" }, + { 0xff, "Vendor", vendor_evt, 0, false }, + { } +}; + +void packet_new_index(struct timeval *tv, uint16_t index, const char *label, + uint8_t type, uint8_t bus, const char *name) +{ + char details[48]; + + sprintf(details, "(%s,%s,%s)", hci_typetostr(type), + hci_bustostr(bus), name); + + print_packet(tv, NULL, '=', index, NULL, COLOR_NEW_INDEX, + "New Index", label, details); +} + +void packet_del_index(struct timeval *tv, uint16_t index, const char *label) +{ + print_packet(tv, NULL, '=', index, NULL, COLOR_DEL_INDEX, + "Delete Index", label, NULL); +} + +void packet_open_index(struct timeval *tv, uint16_t index, const char *label) +{ + print_packet(tv, NULL, '=', index, NULL, COLOR_OPEN_INDEX, + "Open Index", label, NULL); +} + +void packet_close_index(struct timeval *tv, uint16_t index, const char *label) +{ + print_packet(tv, NULL, '=', index, NULL, COLOR_CLOSE_INDEX, + "Close Index", label, NULL); +} + +void packet_index_info(struct timeval *tv, uint16_t index, const char *label, + uint16_t manufacturer) +{ + char details[128]; + + sprintf(details, "(%s)", bt_compidtostr(manufacturer)); + + print_packet(tv, NULL, '=', index, NULL, COLOR_INDEX_INFO, + "Index Info", label, details); +} + +void packet_vendor_diag(struct timeval *tv, uint16_t index, + uint16_t manufacturer, + const void *data, uint16_t size) +{ + char extra_str[16]; + + sprintf(extra_str, "(len %d)", size); + + print_packet(tv, NULL, '=', index, NULL, COLOR_VENDOR_DIAG, + "Vendor Diagnostic", NULL, extra_str); + + switch (manufacturer) { + case 15: + broadcom_lm_diag(data, size); + break; + default: + packet_hexdump(data, size); + break; + } +} + +void packet_system_note(struct timeval *tv, struct ucred *cred, + uint16_t index, const void *message) +{ + print_packet(tv, cred, '=', index, NULL, COLOR_SYSTEM_NOTE, + "Note", message, NULL); +} + +struct monitor_l2cap_hdr { + uint16_t cid; + uint16_t psm; +}; + +static void packet_decode(struct timeval *tv, struct ucred *cred, char dir, + uint16_t index, const char *color, + const char *label, const void *data, + uint16_t size) +{ + const struct monitor_l2cap_hdr *hdr = data; + + if (size < sizeof(*hdr)) { + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed User Data packet", NULL, NULL); + } + + print_packet(tv, cred, dir, index, NULL, COLOR_HCI_ACLDATA, label, + dir == '>' ? "User Data RX" : "User Data TX", + NULL); + + /* Discard last byte since it just a filler */ + l2cap_frame(index, dir == '>', 0, hdr->cid, hdr->psm, + data + sizeof(*hdr), size - (sizeof(*hdr) + 1)); +} + +void packet_user_logging(struct timeval *tv, struct ucred *cred, + uint16_t index, uint8_t priority, + const char *ident, const void *data, + uint16_t size) +{ + char pid_str[140]; + const char *label; + const char *color; + + if (priority > priority_level) + return; + + switch (priority) { + case BTSNOOP_PRIORITY_ERR: + color = COLOR_ERROR; + break; + case BTSNOOP_PRIORITY_WARNING: + color = COLOR_WARN; + break; + case BTSNOOP_PRIORITY_INFO: + color = COLOR_INFO; + break; + case BTSNOOP_PRIORITY_DEBUG: + color = COLOR_DEBUG; + break; + default: + color = COLOR_WHITE_BG; + break; + } + + if (cred) { + char *path = alloca(24); + char line[128]; + FILE *fp; + + snprintf(path, 23, "/proc/%u/comm", cred->pid); + + fp = fopen(path, "re"); + if (fp) { + if (fgets(line, sizeof(line), fp)) { + line[strcspn(line, "\r\n")] = '\0'; + snprintf(pid_str, sizeof(pid_str), "%s[%u]", + line, cred->pid); + } else + snprintf(pid_str, sizeof(pid_str), "%u", + cred->pid); + fclose(fp); + } else + snprintf(pid_str, sizeof(pid_str), "%u", cred->pid); + + label = pid_str; + } else { + if (ident) + label = ident; + else + label = "Message"; + } + + if (ident[0] == '<' || ident[0] == '>') { + packet_decode(tv, cred, ident[0], index, color, + label == ident ? &ident[2] : label, + data, size); + return; + } + + print_packet(tv, cred, '=', index, NULL, color, label, data, NULL); +} + +void packet_hci_command(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size) +{ + const hci_command_hdr *hdr = data; + uint16_t opcode = le16_to_cpu(hdr->opcode); + uint16_t ogf = cmd_opcode_ogf(opcode); + uint16_t ocf = cmd_opcode_ocf(opcode); + struct opcode_data vendor_data; + const struct opcode_data *opcode_data = NULL; + const char *opcode_color, *opcode_str; + char extra_str[25], vendor_str[150]; + int i; + + if (index > MAX_INDEX) { + print_field("Invalid index (%d).", index); + return; + } + + index_list[index].frame++; + + if (size < HCI_COMMAND_HDR_SIZE || size > BTSNOOP_MAX_PACKET_SIZE) { + sprintf(extra_str, "(len %d)", size); + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed HCI Command packet", NULL, extra_str); + return; + } + + data += HCI_COMMAND_HDR_SIZE; + size -= HCI_COMMAND_HDR_SIZE; + + for (i = 0; opcode_table[i].str; i++) { + if (opcode_table[i].opcode == opcode) { + opcode_data = &opcode_table[i]; + break; + } + } + + if (opcode_data) { + if (opcode_data->cmd_func) + opcode_color = COLOR_HCI_COMMAND; + else + opcode_color = COLOR_HCI_COMMAND_UNKNOWN; + opcode_str = opcode_data->str; + } else { + if (ogf == 0x3f) { + const struct vendor_ocf *vnd = current_vendor_ocf(ocf); + + if (vnd) { + const char *str = current_vendor_str(); + + if (str) { + snprintf(vendor_str, sizeof(vendor_str), + "%s %s", str, vnd->str); + vendor_data.str = vendor_str; + } else + vendor_data.str = vnd->str; + vendor_data.cmd_func = vnd->cmd_func; + vendor_data.cmd_size = vnd->cmd_size; + vendor_data.cmd_fixed = vnd->cmd_fixed; + + opcode_data = &vendor_data; + + if (opcode_data->cmd_func) + opcode_color = COLOR_HCI_COMMAND; + else + opcode_color = COLOR_HCI_COMMAND_UNKNOWN; + opcode_str = opcode_data->str; + } else { + opcode_color = COLOR_HCI_COMMAND; + opcode_str = "Vendor"; + } + } else { + opcode_color = COLOR_HCI_COMMAND_UNKNOWN; + opcode_str = "Unknown"; + } + } + + sprintf(extra_str, "(0x%2.2x|0x%4.4x) plen %d", ogf, ocf, hdr->plen); + + print_packet(tv, cred, '<', index, NULL, opcode_color, "HCI Command", + opcode_str, extra_str); + + if (!opcode_data || !opcode_data->cmd_func) { + packet_hexdump(data, size); + return; + } + + if (size != hdr->plen) { + print_text(COLOR_ERROR, "invalid packet size (%u != %u)", size, + hdr->plen); + packet_hexdump(data, size); + return; + } + + if (opcode_data->cmd_fixed) { + if (hdr->plen != opcode_data->cmd_size) { + print_text(COLOR_ERROR, "invalid packet size"); + packet_hexdump(data, size); + return; + } + } else { + if (hdr->plen < opcode_data->cmd_size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data, size); + return; + } + } + + opcode_data->cmd_func(data, hdr->plen); +} + +void packet_hci_event(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size) +{ + const hci_event_hdr *hdr = data; + const struct event_data *event_data = NULL; + const char *event_color, *event_str; + char extra_str[25]; + int i; + + if (index > MAX_INDEX) { + print_field("Invalid index (%d).", index); + return; + } + + + index_list[index].frame++; + + if (size < HCI_EVENT_HDR_SIZE) { + sprintf(extra_str, "(len %d)", size); + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed HCI Event packet", NULL, extra_str); + packet_hexdump(data, size); + return; + } + + data += HCI_EVENT_HDR_SIZE; + size -= HCI_EVENT_HDR_SIZE; + + for (i = 0; event_table[i].str; i++) { + if (event_table[i].event == hdr->evt) { + event_data = &event_table[i]; + break; + } + } + + if (event_data) { + if (event_data->func) + event_color = COLOR_HCI_EVENT; + else + event_color = COLOR_HCI_EVENT_UNKNOWN; + event_str = event_data->str; + } else { + event_color = COLOR_HCI_EVENT_UNKNOWN; + event_str = "Unknown"; + } + + sprintf(extra_str, "(0x%2.2x) plen %d", hdr->evt, hdr->plen); + + print_packet(tv, cred, '>', index, NULL, event_color, "HCI Event", + event_str, extra_str); + + if (!event_data || !event_data->func) { + packet_hexdump(data, size); + return; + } + + if (size != hdr->plen) { + print_text(COLOR_ERROR, "invalid packet size (%u != %u)", size, + hdr->plen); + packet_hexdump(data, size); + return; + } + + if (event_data->fixed) { + if (hdr->plen != event_data->size) { + print_text(COLOR_ERROR, "invalid packet size"); + packet_hexdump(data, size); + return; + } + } else { + if (hdr->plen < event_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data, size); + return; + } + } + + event_data->func(data, hdr->plen); +} + +void packet_hci_acldata(struct timeval *tv, struct ucred *cred, uint16_t index, + bool in, const void *data, uint16_t size) +{ + const struct bt_hci_acl_hdr *hdr = data; + uint16_t handle = le16_to_cpu(hdr->handle); + uint16_t dlen = le16_to_cpu(hdr->dlen); + uint8_t flags = acl_flags(handle); + char handle_str[16], extra_str[32]; + + if (index > MAX_INDEX) { + print_field("Invalid index (%d).", index); + return; + } + + index_list[index].frame++; + + if (size < HCI_ACL_HDR_SIZE) { + if (in) + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed ACL Data RX packet", NULL, NULL); + else + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed ACL Data TX packet", NULL, NULL); + packet_hexdump(data, size); + return; + } + + data += HCI_ACL_HDR_SIZE; + size -= HCI_ACL_HDR_SIZE; + + sprintf(handle_str, "Handle %d", acl_handle(handle)); + sprintf(extra_str, "flags 0x%2.2x dlen %d", flags, dlen); + + print_packet(tv, cred, in ? '>' : '<', index, NULL, COLOR_HCI_ACLDATA, + in ? "ACL Data RX" : "ACL Data TX", + handle_str, extra_str); + + if (size != dlen) { + print_text(COLOR_ERROR, "invalid packet size (%d != %d)", + size, dlen); + packet_hexdump(data, size); + return; + } + + if (filter_mask & PACKET_FILTER_SHOW_ACL_DATA) + packet_hexdump(data, size); + + l2cap_packet(index, in, acl_handle(handle), flags, data, size); +} + +void packet_hci_scodata(struct timeval *tv, struct ucred *cred, uint16_t index, + bool in, const void *data, uint16_t size) +{ + const hci_sco_hdr *hdr = data; + uint16_t handle = le16_to_cpu(hdr->handle); + uint8_t flags = acl_flags(handle); + char handle_str[16], extra_str[32]; + + if (index > MAX_INDEX) { + print_field("Invalid index (%d).", index); + return; + } + + index_list[index].frame++; + + if (size < HCI_SCO_HDR_SIZE) { + if (in) + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed SCO Data RX packet", NULL, NULL); + else + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed SCO Data TX packet", NULL, NULL); + packet_hexdump(data, size); + return; + } + + data += HCI_SCO_HDR_SIZE; + size -= HCI_SCO_HDR_SIZE; + + sprintf(handle_str, "Handle %d", acl_handle(handle)); + sprintf(extra_str, "flags 0x%2.2x dlen %d", flags, hdr->dlen); + + print_packet(tv, cred, in ? '>' : '<', index, NULL, COLOR_HCI_SCODATA, + in ? "SCO Data RX" : "SCO Data TX", + handle_str, extra_str); + + if (size != hdr->dlen) { + print_text(COLOR_ERROR, "invalid packet size (%d != %d)", + size, hdr->dlen); + packet_hexdump(data, size); + return; + } + + if (filter_mask & PACKET_FILTER_SHOW_SCO_DATA) + packet_hexdump(data, size); +} + +void packet_ctrl_open(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size) +{ + uint32_t cookie; + uint16_t format; + char channel[11]; + + if (size < 6) { + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed Control Open packet", NULL, NULL); + packet_hexdump(data, size); + return; + } + + cookie = get_le32(data); + format = get_le16(data + 4); + + data += 6; + size -= 6; + + sprintf(channel, "0x%4.4x", cookie); + + if ((format == CTRL_RAW || format == CTRL_USER || format == CTRL_MGMT) + && size >= 8) { + uint8_t version; + uint16_t revision; + uint32_t flags; + uint8_t ident_len; + const char *comm; + char details[48]; + const char *title; + + version = get_u8(data); + revision = get_le16(data + 1); + flags = get_le32(data + 3); + ident_len = get_u8(data + 7); + + if ((8 + ident_len) > size) { + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed Control Open packet", NULL, NULL); + return; + } + + data += 8; + size -= 8; + + comm = ident_len > 0 ? data : "unknown"; + + data += ident_len; + size -= ident_len; + + assign_ctrl(cookie, format, comm); + + sprintf(details, "%sversion %u.%u", + flags & 0x0001 ? "(privileged) " : "", + version, revision); + + switch (format) { + case CTRL_RAW: + title = "RAW Open"; + break; + case CTRL_USER: + title = "USER Open"; + break; + case CTRL_MGMT: + title = "MGMT Open"; + break; + default: + title = "Control Open"; + break; + } + + print_packet(tv, cred, '@', index, channel, COLOR_CTRL_OPEN, + title, comm, details); + } else { + char label[7]; + + assign_ctrl(cookie, format, NULL); + + sprintf(label, "0x%4.4x", format); + + print_packet(tv, cred, '@', index, channel, COLOR_CTRL_OPEN, + "Control Open", label, NULL); + } + + packet_hexdump(data, size); +} + +void packet_ctrl_close(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size) +{ + uint32_t cookie; + uint16_t format; + char channel[11], label[22]; + const char *title; + + if (size < 4) { + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed Control Close packet", NULL, NULL); + packet_hexdump(data, size); + return; + } + + cookie = get_le32(data); + + data += 4; + size -= 4; + + sprintf(channel, "0x%4.4x", cookie); + + release_ctrl(cookie, &format, label); + + switch (format) { + case CTRL_RAW: + title = "RAW Close"; + break; + case CTRL_USER: + title = "USER Close"; + break; + case CTRL_MGMT: + title = "MGMT Close"; + break; + default: + sprintf(label, "0x%4.4x", format); + title = "Control Close"; + break; + } + + print_packet(tv, cred, '@', index, channel, COLOR_CTRL_CLOSE, + title, label, NULL); + + packet_hexdump(data, size); +} + +static const struct { + uint8_t status; + const char *str; +} mgmt_status_table[] = { + { 0x00, "Success" }, + { 0x01, "Unknown Command" }, + { 0x02, "Not Connected" }, + { 0x03, "Failed" }, + { 0x04, "Connect Failed" }, + { 0x05, "Authentication Failed" }, + { 0x06, "Not Paired" }, + { 0x07, "No Resources" }, + { 0x08, "Timeout" }, + { 0x09, "Already Connected" }, + { 0x0a, "Busy" }, + { 0x0b, "Rejected" }, + { 0x0c, "Not Supported" }, + { 0x0d, "Invalid Parameters" }, + { 0x0e, "Disconnected" }, + { 0x0f, "Not Powered" }, + { 0x10, "Cancelled" }, + { 0x11, "Invalid Index" }, + { 0x12, "RFKilled" }, + { 0x13, "Already Paired" }, + { 0x14, "Permission Denied" }, + { } +}; + +static void mgmt_print_status(uint8_t status) +{ + const char *str = "Unknown"; + const char *color_on, *color_off; + bool unknown = true; + int i; + + for (i = 0; mgmt_status_table[i].str; i++) { + if (mgmt_status_table[i].status == status) { + str = mgmt_status_table[i].str; + unknown = false; + break; + } + } + + if (use_color()) { + if (status) { + if (unknown) + color_on = COLOR_UNKNOWN_ERROR; + else + color_on = COLOR_RED; + } else + color_on = COLOR_GREEN; + color_off = COLOR_OFF; + } else { + color_on = ""; + color_off = ""; + } + + print_field("Status: %s%s%s (0x%2.2x)", + color_on, str, color_off, status); +} + +static void mgmt_print_address(const uint8_t *address, uint8_t type) +{ + switch (type) { + case 0x00: + print_addr_resolve("BR/EDR Address", address, 0x00, false); + break; + case 0x01: + print_addr_resolve("LE Address", address, 0x00, false); + break; + case 0x02: + print_addr_resolve("LE Address", address, 0x01, false); + break; + default: + print_addr_resolve("Address", address, 0xff, false); + break; + } +} + +static const struct bitfield_data mgmt_address_type_table[] = { + { 0, "BR/EDR" }, + { 1, "LE Public" }, + { 2, "LE Random" }, + { } +}; + +static void mgmt_print_address_type(uint8_t type) +{ + uint8_t mask; + + print_field("Address type: 0x%2.2x", type); + + mask = print_bitfield(2, type, mgmt_address_type_table); + if (mask) + print_text(COLOR_UNKNOWN_ADDRESS_TYPE, " Unknown address type" + " (0x%2.2x)", mask); +} + +static void mgmt_print_version(uint8_t version) +{ + packet_print_version("Version", version, NULL, 0x0000); +} + +static void mgmt_print_manufacturer(uint16_t manufacturer) +{ + packet_print_company("Manufacturer", manufacturer); +} + +static const struct bitfield_data mgmt_options_table[] = { + { 0, "External configuration" }, + { 1, "Bluetooth public address configuration" }, + { } +}; + +static void mgmt_print_options(const char *label, uint32_t options) +{ + uint32_t mask; + + print_field("%s: 0x%8.8x", label, options); + + mask = print_bitfield(2, options, mgmt_options_table); + if (mask) + print_text(COLOR_UNKNOWN_OPTIONS_BIT, " Unknown options" + " (0x%8.8x)", mask); +} + +static const struct bitfield_data mgmt_settings_table[] = { + { 0, "Powered" }, + { 1, "Connectable" }, + { 2, "Fast Connectable" }, + { 3, "Discoverable" }, + { 4, "Bondable" }, + { 5, "Link Security" }, + { 6, "Secure Simple Pairing" }, + { 7, "BR/EDR" }, + { 8, "High Speed" }, + { 9, "Low Energy" }, + { 10, "Advertising" }, + { 11, "Secure Connections" }, + { 12, "Debug Keys" }, + { 13, "Privacy" }, + { 14, "Controller Configuration"}, + { 15, "Static Address" }, + { 16, "PHY Configuration" }, + { } +}; + +static void mgmt_print_settings(const char *label, uint32_t settings) +{ + uint32_t mask; + + print_field("%s: 0x%8.8x", label, settings); + + mask = print_bitfield(2, settings, mgmt_settings_table); + if (mask) + print_text(COLOR_UNKNOWN_SETTINGS_BIT, " Unknown settings" + " (0x%8.8x)", mask); +} + +static void mgmt_print_name(const void *data) +{ + print_field("Name: %s", (char *) data); + print_field("Short name: %s", (char *) (data + 249)); +} + +static void mgmt_print_uuid(const void *data) +{ + const uint8_t *uuid = data; + + print_field("UUID: %8.8x-%4.4x-%4.4x-%4.4x-%8.8x%4.4x", + get_le32(&uuid[12]), get_le16(&uuid[10]), + get_le16(&uuid[8]), get_le16(&uuid[6]), + get_le32(&uuid[2]), get_le16(&uuid[0])); +} + +static void mgmt_print_io_capability(uint8_t capability) +{ + const char *str; + + switch (capability) { + case 0x00: + str = "DisplayOnly"; + break; + case 0x01: + str = "DisplayYesNo"; + break; + case 0x02: + str = "KeyboardOnly"; + break; + case 0x03: + str = "NoInputNoOutput"; + break; + case 0x04: + str = "KeyboardDisplay"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Capability: %s (0x%2.2x)", str, capability); +} + +static const struct bitfield_data mgmt_device_flags_table[] = { + { 0, "Confirm Name" }, + { 1, "Legacy Pairing" }, + { 2, "Not Connectable" }, + { } +}; + +static void mgmt_print_device_flags(uint32_t flags) +{ + uint32_t mask; + + print_field("Flags: 0x%8.8x", flags); + + mask = print_bitfield(2, flags, mgmt_device_flags_table); + if (mask) + print_text(COLOR_UNKNOWN_DEVICE_FLAG, " Unknown device flag" + " (0x%8.8x)", mask); +} + +static void mgmt_print_device_action(uint8_t action) +{ + const char *str; + + switch (action) { + case 0x00: + str = "Background scan for device"; + break; + case 0x01: + str = "Allow incoming connection"; + break; + case 0x02: + str = "Auto-connect remote device"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Action: %s (0x%2.2x)", str, action); +} + +static const struct bitfield_data mgmt_adv_flags_table[] = { + { 0, "Switch into Connectable mode" }, + { 1, "Advertise as Discoverable" }, + { 2, "Advertise as Limited Discoverable" }, + { 3, "Add Flags field to Advertising Data" }, + { 4, "Add TX Power field to Advertising Data" }, + { 5, "Add Appearance field to Scan Response" }, + { 6, "Add Local Name in Scan Response" }, + { 7, "Advertise in 1M on Secondary channel" }, + { 8, "Advertise in 2M on Secondary channel" }, + { 9, "Advertise in CODED on Secondary channel" }, + { } +}; + +static void mgmt_print_adv_flags(uint32_t flags) +{ + uint32_t mask; + + print_field("Flags: 0x%8.8x", flags); + + mask = print_bitfield(2, flags, mgmt_adv_flags_table); + if (mask) + print_text(COLOR_UNKNOWN_ADV_FLAG, " Unknown advertising flag" + " (0x%8.8x)", mask); +} + +static void mgmt_print_store_hint(uint8_t hint) +{ + const char *str; + + switch (hint) { + case 0x00: + str = "No"; + break; + case 0x01: + str = "Yes"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Store hint: %s (0x%2.2x)", str, hint); +} + +static void mgmt_print_connection_parameter(const void *data) +{ + uint8_t address_type = get_u8(data + 6); + uint16_t min_conn_interval = get_le16(data + 7); + uint16_t max_conn_interval = get_le16(data + 9); + uint16_t conn_latency = get_le16(data + 11); + uint16_t supv_timeout = get_le16(data + 13); + + mgmt_print_address(data, address_type); + print_field("Min connection interval: %u", min_conn_interval); + print_field("Max connection interval: %u", max_conn_interval); + print_conn_latency("Connection latency", conn_latency); + print_field("Supervision timeout: %u", supv_timeout); +} + +static void mgmt_print_link_key(const void *data) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t key_type = get_u8(data + 7); + uint8_t pin_len = get_u8(data + 24); + + mgmt_print_address(data, address_type); + print_key_type(key_type); + print_link_key(data + 8); + print_field("PIN length: %d", pin_len); +} + +static void mgmt_print_long_term_key(const void *data) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t key_type = get_u8(data + 7); + uint8_t master = get_u8(data + 8); + uint8_t enc_size = get_u8(data + 9); + const char *str; + + mgmt_print_address(data, address_type); + + switch (key_type) { + case 0x00: + str = "Unauthenticated legacy key"; + break; + case 0x01: + str = "Authenticated legacy key"; + break; + case 0x02: + str = "Unauthenticated key from P-256"; + break; + case 0x03: + str = "Authenticated key from P-256"; + break; + case 0x04: + str = "Debug key from P-256"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Key type: %s (0x%2.2x)", str, key_type); + print_field("Master: 0x%2.2x", master); + print_field("Encryption size: %u", enc_size); + print_hex_field("Diversifier", data + 10, 2); + print_hex_field("Randomizer", data + 12, 8); + print_hex_field("Key", data + 20, 16); +} + +static void mgmt_print_identity_resolving_key(const void *data) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); + print_hex_field("Key", data + 7, 16); +} + +static void mgmt_print_signature_resolving_key(const void *data) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t key_type = get_u8(data + 7); + const char *str; + + mgmt_print_address(data, address_type); + + switch (key_type) { + case 0x00: + str = "Unauthenticated local CSRK"; + break; + case 0x01: + str = "Unauthenticated remote CSRK"; + break; + case 0x02: + str = "Authenticated local CSRK"; + break; + case 0x03: + str = "Authenticated remote CSRK"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Key type: %s (0x%2.2x)", str, key_type); + print_hex_field("Key", data + 8, 16); +} + +static void mgmt_print_oob_data(const void *data) +{ + print_hash_p192(data); + print_randomizer_p192(data + 16); + print_hash_p256(data + 32); + print_randomizer_p256(data + 48); +} + +static void mgmt_null_cmd(const void *data, uint16_t size) +{ +} + +static void mgmt_null_rsp(const void *data, uint16_t size) +{ +} + +static void mgmt_read_version_info_rsp(const void *data, uint16_t size) +{ + uint8_t version; + uint16_t revision; + + version = get_u8(data); + revision = get_le16(data + 1); + + print_field("Version: %u.%u", version, revision); +} + +static void mgmt_print_commands(const void *data, uint16_t num); +static void mgmt_print_events(const void *data, uint16_t num); + +static void mgmt_read_supported_commands_rsp(const void *data, uint16_t size) +{ + uint16_t num_commands = get_le16(data); + uint16_t num_events = get_le16(data + 2); + + if (size - 4 != (num_commands * 2) + (num_events *2)) { + packet_hexdump(data, size); + return; + } + + mgmt_print_commands(data + 4, num_commands); + mgmt_print_events(data + 4 + num_commands * 2, num_events); +} + +static void mgmt_read_index_list_rsp(const void *data, uint16_t size) +{ + uint16_t num_controllers = get_le16(data); + int i; + + print_field("Controllers: %u", num_controllers); + + if (size - 2 != num_controllers * 2) { + packet_hexdump(data + 2, size - 2); + return; + } + + for (i = 0; i < num_controllers; i++) { + uint16_t index = get_le16(data + 2 + (i * 2)); + + print_field(" hci%u", index); + } +} + +static void mgmt_read_controller_info_rsp(const void *data, uint16_t size) +{ + uint8_t version = get_u8(data + 6); + uint16_t manufacturer = get_le16(data + 7); + uint32_t supported_settings = get_le32(data + 9); + uint32_t current_settings = get_le32(data + 13); + + print_addr_resolve("Address", data, 0x00, false); + mgmt_print_version(version); + mgmt_print_manufacturer(manufacturer); + mgmt_print_settings("Supported settings", supported_settings); + mgmt_print_settings("Current settings", current_settings); + print_dev_class(data + 17); + mgmt_print_name(data + 20); +} + +static void mgmt_set_powered_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + + print_enable("Powered", enable); +} + +static void mgmt_set_discoverable_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + uint16_t timeout = get_le16(data + 1); + const char *str; + + switch (enable) { + case 0x00: + str = "Disabled"; + break; + case 0x01: + str = "General"; + break; + case 0x02: + str = "Limited"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Discoverable: %s (0x%2.2x)", str, enable); + print_field("Timeout: %u", timeout); +} + +static void mgmt_set_connectable_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + + print_enable("Connectable", enable); +} + +static void mgmt_set_fast_connectable_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + + print_enable("Fast Connectable", enable); +} + +static void mgmt_set_bondable_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + + print_enable("Bondable", enable); +} + +static void mgmt_set_link_security_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + + print_enable("Link Security", enable); +} + +static void mgmt_set_secure_simple_pairing_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + + print_enable("Secure Simple Pairing", enable); +} + +static void mgmt_set_high_speed_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + + print_enable("High Speed", enable); +} + +static void mgmt_set_low_energy_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + + print_enable("Low Energy", enable); +} + +static void mgmt_new_settings_rsp(const void *data, uint16_t size) +{ + uint32_t current_settings = get_le32(data); + + mgmt_print_settings("Current settings", current_settings); +} + +static void mgmt_set_device_class_cmd(const void *data, uint16_t size) +{ + uint8_t major = get_u8(data); + uint8_t minor = get_u8(data + 1); + + print_field("Major class: 0x%2.2x", major); + print_field("Minor class: 0x%2.2x", minor); +} + +static void mgmt_set_device_class_rsp(const void *data, uint16_t size) +{ + print_dev_class(data); +} + +static void mgmt_set_local_name_cmd(const void *data, uint16_t size) +{ + mgmt_print_name(data); +} + +static void mgmt_set_local_name_rsp(const void *data, uint16_t size) +{ + mgmt_print_name(data); +} + +static void mgmt_add_uuid_cmd(const void *data, uint16_t size) +{ + uint8_t service_class = get_u8(data + 16); + + mgmt_print_uuid(data); + print_field("Service class: 0x%2.2x", service_class); +} + +static void mgmt_add_uuid_rsp(const void *data, uint16_t size) +{ + print_dev_class(data); +} + +static void mgmt_remove_uuid_cmd(const void *data, uint16_t size) +{ + mgmt_print_uuid(data); +} + +static void mgmt_remove_uuid_rsp(const void *data, uint16_t size) +{ + print_dev_class(data); +} + +static void mgmt_load_link_keys_cmd(const void *data, uint16_t size) +{ + uint8_t debug_keys = get_u8(data); + uint16_t num_keys = get_le16(data + 1); + int i; + + print_enable("Debug keys", debug_keys); + print_field("Keys: %u", num_keys); + + if (size - 3 != num_keys * 25) { + packet_hexdump(data + 3, size - 3); + return; + } + + for (i = 0; i < num_keys; i++) + mgmt_print_link_key(data + 3 + (i * 25)); +} + +static void mgmt_load_long_term_keys_cmd(const void *data, uint16_t size) +{ + uint16_t num_keys = get_le16(data + 1); + int i; + + print_field("Keys: %u", num_keys); + + if (size - 2 != num_keys * 36) { + packet_hexdump(data + 2, size - 2); + return; + } + + for (i = 0; i < num_keys; i++) + mgmt_print_long_term_key(data + 2 + (i * 36)); +} + +static void mgmt_disconnect_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_disconnect_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_get_connections_rsp(const void *data, uint16_t size) +{ + uint16_t num_connections = get_le16(data); + int i; + + print_field("Connections: %u", num_connections); + + if (size - 2 != num_connections * 7) { + packet_hexdump(data + 2, size - 2); + return; + } + + for (i = 0; i < num_connections; i++) { + uint8_t address_type = get_u8(data + 2 + (i * 7) + 6); + + mgmt_print_address(data + 2 + (i * 7), address_type); + } +} + +static void mgmt_pin_code_reply_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t pin_len = get_u8(data + 7); + + mgmt_print_address(data, address_type); + print_field("PIN length: %u", pin_len); + print_hex_field("PIN code", data + 8, 16); +} + +static void mgmt_pin_code_reply_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_pin_code_neg_reply_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_pin_code_neg_reply_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_set_io_capability_cmd(const void *data, uint16_t size) +{ + uint8_t capability = get_u8(data); + + mgmt_print_io_capability(capability); +} + +static void mgmt_pair_device_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t capability = get_u8(data + 7); + + mgmt_print_address(data, address_type); + mgmt_print_io_capability(capability); +} + +static void mgmt_pair_device_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_cancel_pair_device_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_cancel_pair_device_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_unpair_device_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t disconnect = get_u8(data + 7); + + mgmt_print_address(data, address_type); + print_enable("Disconnect", disconnect); +} + +static void mgmt_unpair_device_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_user_confirmation_reply_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_user_confirmation_reply_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_user_confirmation_neg_reply_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_user_confirmation_neg_reply_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_user_passkey_reply_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint32_t passkey = get_le32(data + 7); + + mgmt_print_address(data, address_type); + print_field("Passkey: 0x%4.4x", passkey); +} + +static void mgmt_user_passkey_reply_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_user_passkey_neg_reply_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_user_passkey_neg_reply_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_read_local_oob_data_rsp(const void *data, uint16_t size) +{ + mgmt_print_oob_data(data); +} + +static void mgmt_add_remote_oob_data_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); + mgmt_print_oob_data(data + 7); +} + +static void mgmt_add_remote_oob_data_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_remove_remote_oob_data_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_remove_remote_oob_data_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_start_discovery_cmd(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + + mgmt_print_address_type(type); +} + +static void mgmt_start_discovery_rsp(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + + mgmt_print_address_type(type); +} + +static void mgmt_stop_discovery_cmd(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + + mgmt_print_address_type(type); +} + +static void mgmt_stop_discovery_rsp(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + + mgmt_print_address_type(type); +} + +static void mgmt_confirm_name_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t name_known = get_u8(data + 7); + const char *str; + + mgmt_print_address(data, address_type); + + switch (name_known) { + case 0x00: + str = "No"; + break; + case 0x01: + str = "Yes"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Name known: %s (0x%2.2x)", str, name_known); +} + +static void mgmt_confirm_name_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_block_device_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_block_device_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_unblock_device_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_unblock_device_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_set_device_id_cmd(const void *data, uint16_t size) +{ + print_device_id(data, size); +} + +static void mgmt_set_advertising_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + const char *str; + + switch (enable) { + case 0x00: + str = "Disabled"; + break; + case 0x01: + str = "Enabled"; + break; + case 0x02: + str = "Connectable"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Advertising: %s (0x%2.2x)", str, enable); +} + +static void mgmt_set_bredr_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + + print_enable("BR/EDR", enable); +} + +static void mgmt_set_static_address_cmd(const void *data, uint16_t size) +{ + print_addr_resolve("Address", data, 0x01, false); +} + +static void mgmt_set_scan_parameters_cmd(const void *data, uint16_t size) +{ + uint16_t interval = get_le16(data); + uint16_t window = get_le16(data + 2); + + print_field("Interval: %u (0x%2.2x)", interval, interval); + print_field("Window: %u (0x%2.2x)", window, window); +} + +static void mgmt_set_secure_connections_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + const char *str; + + switch (enable) { + case 0x00: + str = "Disabled"; + break; + case 0x01: + str = "Enabled"; + break; + case 0x02: + str = "Only"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Secure Connections: %s (0x%2.2x)", str, enable); +} + +static void mgmt_set_debug_keys_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + const char *str; + + switch (enable) { + case 0x00: + str = "Disabled"; + break; + case 0x01: + str = "Enabled"; + break; + case 0x02: + str = "Generate"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Debug Keys: %s (0x%2.2x)", str, enable); +} + +static void mgmt_set_privacy_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + const char *str; + + switch (enable) { + case 0x00: + str = "Disabled"; + break; + case 0x01: + str = "Enabled"; + break; + case 0x02: + str = "Limited"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Privacy: %s (0x%2.2x)", str, enable); + print_hex_field("Key", data + 1, 16); +} + +static void mgmt_load_identity_resolving_keys_cmd(const void *data, uint16_t size) +{ + uint16_t num_keys = get_le16(data + 1); + int i; + + print_field("Keys: %u", num_keys); + + if (size - 2 != num_keys * 23) { + packet_hexdump(data + 2, size - 2); + return; + } + + for (i = 0; i < num_keys; i++) + mgmt_print_identity_resolving_key(data + 2 + (i * 23)); +} + +static void mgmt_get_connection_information_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_get_connection_information_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + int8_t rssi = get_s8(data + 7); + int8_t tx_power = get_s8(data + 8); + int8_t max_tx_power = get_s8(data + 9); + + mgmt_print_address(data, address_type); + print_rssi(rssi); + print_power_level(tx_power, NULL); + print_power_level(max_tx_power, "max"); +} + +static void mgmt_get_clock_information_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_get_clock_information_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint32_t local_clock = get_le32(data + 7); + uint32_t piconet_clock = get_le32(data + 11); + uint16_t accuracy = get_le16(data + 15); + + mgmt_print_address(data, address_type); + print_field("Local clock: 0x%8.8x", local_clock); + print_field("Piconet clock: 0x%8.8x", piconet_clock); + print_field("Accuracy: 0x%4.4x", accuracy); +} + +static void mgmt_add_device_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t action = get_u8(data + 7); + + mgmt_print_address(data, address_type); + mgmt_print_device_action(action); +} + +static void mgmt_add_device_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_remove_device_cmd(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_remove_device_rsp(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_load_connection_parameters_cmd(const void *data, uint16_t size) +{ + uint16_t num_parameters = get_le16(data); + int i; + + print_field("Parameters: %u", num_parameters); + + if (size - 2 != num_parameters * 15) { + packet_hexdump(data + 2, size - 2); + return; + } + + for (i = 0; i < num_parameters; i++) + mgmt_print_connection_parameter(data + 2 + (i * 15)); +} + +static void mgmt_read_unconf_index_list_rsp(const void *data, uint16_t size) +{ + uint16_t num_controllers = get_le16(data); + int i; + + print_field("Controllers: %u", num_controllers); + + if (size - 2 != num_controllers * 2) { + packet_hexdump(data + 2, size - 2); + return; + } + + for (i = 0; i < num_controllers; i++) { + uint16_t index = get_le16(data + 2 + (i * 2)); + + print_field(" hci%u", index); + } +} + +static void mgmt_read_controller_conf_info_rsp(const void *data, uint16_t size) +{ + uint16_t manufacturer = get_le16(data); + uint32_t supported_options = get_le32(data + 2); + uint32_t missing_options = get_le32(data + 6); + + mgmt_print_manufacturer(manufacturer); + mgmt_print_options("Supported options", supported_options); + mgmt_print_options("Missing options", missing_options); +} + +static void mgmt_set_external_configuration_cmd(const void *data, uint16_t size) +{ + uint8_t enable = get_u8(data); + + print_enable("Configuration", enable); +} + +static void mgmt_set_public_address_cmd(const void *data, uint16_t size) +{ + print_addr_resolve("Address", data, 0x00, false); +} + +static void mgmt_new_options_rsp(const void *data, uint16_t size) +{ + uint32_t missing_options = get_le32(data); + + mgmt_print_options("Missing options", missing_options); +} + +static void mgmt_start_service_discovery_cmd(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + int8_t rssi = get_s8(data + 1); + uint16_t num_uuids = get_le16(data + 2); + int i; + + mgmt_print_address_type(type); + print_rssi(rssi); + print_field("UUIDs: %u", num_uuids); + + if (size - 4 != num_uuids * 16) { + packet_hexdump(data + 4, size - 4); + return; + } + + for (i = 0; i < num_uuids; i++) + mgmt_print_uuid(data + 4 + (i * 16)); +} + +static void mgmt_start_service_discovery_rsp(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + + mgmt_print_address_type(type); +} + +static void mgmt_read_ext_index_list_rsp(const void *data, uint16_t size) +{ + uint16_t num_controllers = get_le16(data); + int i; + + print_field("Controllers: %u", num_controllers); + + if (size - 2 != num_controllers * 4) { + packet_hexdump(data + 2, size - 2); + return; + } + + for (i = 0; i < num_controllers; i++) { + uint16_t index = get_le16(data + 2 + (i * 4)); + uint8_t type = get_u8(data + 4 + (i * 4)); + uint8_t bus = get_u8(data + 5 + (i * 4)); + const char *str; + + switch (type) { + case 0x00: + str = "Primary"; + break; + case 0x01: + str = "Unconfigured"; + break; + case 0x02: + str = "AMP"; + break; + default: + str = "Reserved"; + break; + } + + print_field(" hci%u (%s,%s)", index, str, hci_bustostr(bus)); + } +} + +static void mgmt_read_local_oob_ext_data_cmd(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + + mgmt_print_address_type(type); +} + +static void mgmt_read_local_oob_ext_data_rsp(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + uint16_t data_len = get_le16(data + 1); + + mgmt_print_address_type(type); + print_field("Data length: %u", data_len); + print_eir(data + 3, size - 3, true); +} + +static void mgmt_read_advertising_features_rsp(const void *data, uint16_t size) +{ + uint32_t flags = get_le32(data); + uint8_t adv_data_len = get_u8(data + 4); + uint8_t scan_rsp_len = get_u8(data + 5); + uint8_t max_instances = get_u8(data + 6); + uint8_t num_instances = get_u8(data + 7); + int i; + + mgmt_print_adv_flags(flags); + print_field("Advertising data length: %u", adv_data_len); + print_field("Scan response length: %u", scan_rsp_len); + print_field("Max instances: %u", max_instances); + print_field("Instances: %u", num_instances); + + if (size - 8 != num_instances) { + packet_hexdump(data + 8, size - 8); + return; + } + + for (i = 0; i < num_instances; i++) { + uint8_t instance = get_u8(data + 8 + i); + + print_field(" %u", instance); + } +} + +static void mgmt_add_advertising_cmd(const void *data, uint16_t size) +{ + uint8_t instance = get_u8(data); + uint32_t flags = get_le32(data + 1); + uint16_t duration = get_le16(data + 5); + uint16_t timeout = get_le16(data + 7); + uint8_t adv_data_len = get_u8(data + 9); + uint8_t scan_rsp_len = get_u8(data + 10); + + print_field("Instance: %u", instance); + mgmt_print_adv_flags(flags); + print_field("Duration: %u", duration); + print_field("Timeout: %u", timeout); + print_field("Advertising data length: %u", adv_data_len); + print_eir(data + 11, adv_data_len, false); + print_field("Scan response length: %u", scan_rsp_len); + print_eir(data + 11 + adv_data_len, scan_rsp_len, false); +} + +static void mgmt_add_advertising_rsp(const void *data, uint16_t size) +{ + uint8_t instance = get_u8(data); + + print_field("Instance: %u", instance); +} + +static void mgmt_remove_advertising_cmd(const void *data, uint16_t size) +{ + uint8_t instance = get_u8(data); + + print_field("Instance: %u", instance); +} + +static void mgmt_remove_advertising_rsp(const void *data, uint16_t size) +{ + uint8_t instance = get_u8(data); + + print_field("Instance: %u", instance); +} + +static void mgmt_get_advertising_size_info_cmd(const void *data, uint16_t size) +{ + uint8_t instance = get_u8(data); + uint32_t flags = get_le32(data + 1); + + print_field("Instance: %u", instance); + mgmt_print_adv_flags(flags); +} + +static void mgmt_get_advertising_size_info_rsp(const void *data, uint16_t size) +{ + uint8_t instance = get_u8(data); + uint32_t flags = get_le32(data + 1); + uint8_t adv_data_len = get_u8(data + 5); + uint8_t scan_rsp_len = get_u8(data + 6); + + print_field("Instance: %u", instance); + mgmt_print_adv_flags(flags); + print_field("Advertising data length: %u", adv_data_len); + print_field("Scan response length: %u", scan_rsp_len); +} + +static void mgmt_start_limited_discovery_cmd(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + + mgmt_print_address_type(type); +} + +static void mgmt_start_limited_discovery_rsp(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + + mgmt_print_address_type(type); +} + +static void mgmt_read_ext_controller_info_rsp(const void *data, uint16_t size) +{ + uint8_t version = get_u8(data + 6); + uint16_t manufacturer = get_le16(data + 7); + uint32_t supported_settings = get_le32(data + 9); + uint32_t current_settings = get_le32(data + 13); + uint16_t data_len = get_le16(data + 17); + + print_addr_resolve("Address", data, 0x00, false); + mgmt_print_version(version); + mgmt_print_manufacturer(manufacturer); + mgmt_print_settings("Supported settings", supported_settings); + mgmt_print_settings("Current settings", current_settings); + print_field("Data length: %u", data_len); + print_eir(data + 19, size - 19, false); +} + +static void mgmt_set_apperance_cmd(const void *data, uint16_t size) +{ + uint16_t appearance = get_le16(data); + + print_appearance(appearance); +} + +static const struct bitfield_data mgmt_phy_table[] = { + { 0, "BR 1M 1SLOT" }, + { 1, "BR 1M 3SLOT" }, + { 2, "BR 1M 5SLOT" }, + { 3, "EDR 2M 1SLOT" }, + { 4, "EDR 2M 3SLOT" }, + { 5, "EDR 2M 5SLOT" }, + { 6, "EDR 3M 1SLOT" }, + { 7, "EDR 3M 3SLOT" }, + { 8, "EDR 3M 5SLOT" }, + { 9, "LE 1M TX" }, + { 10, "LE 1M RX" }, + { 11, "LE 2M TX" }, + { 12, "LE 2M RX" }, + { 13, "LE CODED TX" }, + { 14, "LE CODED RX" }, + { } +}; + +static void mgmt_print_phys(const char *label, uint16_t phys) +{ + uint16_t mask; + + print_field("%s: 0x%4.4x", label, phys); + + mask = print_bitfield(2, phys, mgmt_phy_table); + if (mask) + print_text(COLOR_UNKNOWN_PHY, " Unknown PHYs" + " (0x%8.8x)", mask); +} + +static void mgmt_get_phy_rsp(const void *data, uint16_t size) +{ + uint32_t supported_phys = get_le32(data); + uint32_t configurable_phys = get_le32(data + 4); + uint32_t selected_phys = get_le32(data + 8); + + mgmt_print_phys("Supported PHYs", supported_phys); + mgmt_print_phys("Configurable PHYs", configurable_phys); + mgmt_print_phys("Selected PHYs", selected_phys); +} + +static void mgmt_set_phy_cmd(const void *data, uint16_t size) +{ + uint32_t selected_phys = get_le32(data); + + mgmt_print_phys("Selected PHYs", selected_phys); +} + +struct mgmt_data { + uint16_t opcode; + const char *str; + void (*func) (const void *data, uint16_t size); + uint16_t size; + bool fixed; + void (*rsp_func) (const void *data, uint16_t size); + uint16_t rsp_size; + bool rsp_fixed; +}; + +static const struct mgmt_data mgmt_command_table[] = { + { 0x0001, "Read Management Version Information", + mgmt_null_cmd, 0, true, + mgmt_read_version_info_rsp, 3, true }, + { 0x0002, "Read Management Supported Commands", + mgmt_null_cmd, 0, true, + mgmt_read_supported_commands_rsp, 4, false }, + { 0x0003, "Read Controller Index List", + mgmt_null_cmd, 0, true, + mgmt_read_index_list_rsp, 2, false }, + { 0x0004, "Read Controller Information", + mgmt_null_cmd, 0, true, + mgmt_read_controller_info_rsp, 280, true }, + { 0x0005, "Set Powered", + mgmt_set_powered_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x0006, "Set Discoverable", + mgmt_set_discoverable_cmd, 3, true, + mgmt_new_settings_rsp, 4, true }, + { 0x0007, "Set Connectable", + mgmt_set_connectable_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x0008, "Set Fast Connectable", + mgmt_set_fast_connectable_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x0009, "Set Bondable", + mgmt_set_bondable_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x000a, "Set Link Security", + mgmt_set_link_security_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x000b, "Set Secure Simple Pairing", + mgmt_set_secure_simple_pairing_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x000c, "Set High Speed", + mgmt_set_high_speed_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x000d, "Set Low Energy", + mgmt_set_low_energy_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x000e, "Set Device Class", + mgmt_set_device_class_cmd, 2, true, + mgmt_set_device_class_rsp, 3, true }, + { 0x000f, "Set Local Name", + mgmt_set_local_name_cmd, 260, true, + mgmt_set_local_name_rsp, 260, true }, + { 0x0010, "Add UUID", + mgmt_add_uuid_cmd, 17, true, + mgmt_add_uuid_rsp, 3, true }, + { 0x0011, "Remove UUID", + mgmt_remove_uuid_cmd, 16, true, + mgmt_remove_uuid_rsp, 3, true }, + { 0x0012, "Load Link Keys", + mgmt_load_link_keys_cmd, 3, false, + mgmt_null_rsp, 0, true }, + { 0x0013, "Load Long Term Keys", + mgmt_load_long_term_keys_cmd, 2, false, + mgmt_null_rsp, 0, true }, + { 0x0014, "Disconnect", + mgmt_disconnect_cmd, 7, true, + mgmt_disconnect_rsp, 7, true }, + { 0x0015, "Get Connections", + mgmt_null_cmd, 0, true, + mgmt_get_connections_rsp, 2, false }, + { 0x0016, "PIN Code Reply", + mgmt_pin_code_reply_cmd, 24, true, + mgmt_pin_code_reply_rsp, 7, true }, + { 0x0017, "PIN Code Negative Reply", + mgmt_pin_code_neg_reply_cmd, 7, true, + mgmt_pin_code_neg_reply_rsp, 7, true }, + { 0x0018, "Set IO Capability", + mgmt_set_io_capability_cmd, 1, true, + mgmt_null_rsp, 0, true }, + { 0x0019, "Pair Device", + mgmt_pair_device_cmd, 8, true, + mgmt_pair_device_rsp, 7, true }, + { 0x001a, "Cancel Pair Device", + mgmt_cancel_pair_device_cmd, 7, true, + mgmt_cancel_pair_device_rsp, 7, true }, + { 0x001b, "Unpair Device", + mgmt_unpair_device_cmd, 8, true, + mgmt_unpair_device_rsp, 7, true }, + { 0x001c, "User Confirmation Reply", + mgmt_user_confirmation_reply_cmd, 7, true, + mgmt_user_confirmation_reply_rsp, 7, true }, + { 0x001d, "User Confirmation Negative Reply", + mgmt_user_confirmation_neg_reply_cmd, 7, true, + mgmt_user_confirmation_neg_reply_rsp, 7, true }, + { 0x001e, "User Passkey Reply", + mgmt_user_passkey_reply_cmd, 11, true, + mgmt_user_passkey_reply_rsp, 7, true }, + { 0x001f, "User Passkey Negative Reply", + mgmt_user_passkey_neg_reply_cmd, 7, true, + mgmt_user_passkey_neg_reply_rsp, 7, true }, + { 0x0020, "Read Local Out Of Band Data", + mgmt_null_cmd, 0, true, + mgmt_read_local_oob_data_rsp, 64, true }, + { 0x0021, "Add Remote Out Of Band Data", + mgmt_add_remote_oob_data_cmd, 71, true, + mgmt_add_remote_oob_data_rsp, 7, true }, + { 0x0022, "Remove Remote Out Of Band Data", + mgmt_remove_remote_oob_data_cmd, 7, true, + mgmt_remove_remote_oob_data_rsp, 7, true }, + { 0x0023, "Start Discovery", + mgmt_start_discovery_cmd, 1, true, + mgmt_start_discovery_rsp, 1, true }, + { 0x0024, "Stop Discovery", + mgmt_stop_discovery_cmd, 1, true, + mgmt_stop_discovery_rsp, 1, true }, + { 0x0025, "Confirm Name", + mgmt_confirm_name_cmd, 8, true, + mgmt_confirm_name_rsp, 7, true }, + { 0x0026, "Block Device", + mgmt_block_device_cmd, 7, true, + mgmt_block_device_rsp, 7, true }, + { 0x0027, "Unblock Device", + mgmt_unblock_device_cmd, 7, true, + mgmt_unblock_device_rsp, 7, true }, + { 0x0028, "Set Device ID", + mgmt_set_device_id_cmd, 8, true, + mgmt_null_rsp, 0, true }, + { 0x0029, "Set Advertising", + mgmt_set_advertising_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x002a, "Set BR/EDR", + mgmt_set_bredr_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x002b, "Set Static Address", + mgmt_set_static_address_cmd, 6, true, + mgmt_new_settings_rsp, 4, true }, + { 0x002c, "Set Scan Parameters", + mgmt_set_scan_parameters_cmd, 4, true, + mgmt_null_rsp, 0, true }, + { 0x002d, "Set Secure Connections", + mgmt_set_secure_connections_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x002e, "Set Debug Keys", + mgmt_set_debug_keys_cmd, 1, true, + mgmt_new_settings_rsp, 4, true }, + { 0x002f, "Set Privacy", + mgmt_set_privacy_cmd, 17, true, + mgmt_new_settings_rsp, 4, true }, + { 0x0030, "Load Identity Resolving Keys", + mgmt_load_identity_resolving_keys_cmd, 2, false, + mgmt_null_rsp, 0, true }, + { 0x0031, "Get Connection Information", + mgmt_get_connection_information_cmd, 7, true, + mgmt_get_connection_information_rsp, 10, true }, + { 0x0032, "Get Clock Information", + mgmt_get_clock_information_cmd, 7, true, + mgmt_get_clock_information_rsp, 17, true }, + { 0x0033, "Add Device", + mgmt_add_device_cmd, 8, true, + mgmt_add_device_rsp, 7, true }, + { 0x0034, "Remove Device", + mgmt_remove_device_cmd, 7, true, + mgmt_remove_device_rsp, 7, true }, + { 0x0035, "Load Connection Parameters", + mgmt_load_connection_parameters_cmd, 2, false, + mgmt_null_rsp, 0, true }, + { 0x0036, "Read Unconfigured Controller Index List", + mgmt_null_cmd, 0, true, + mgmt_read_unconf_index_list_rsp, 2, false }, + { 0x0037, "Read Controller Configuration Information", + mgmt_null_cmd, 0, true, + mgmt_read_controller_conf_info_rsp, 10, true }, + { 0x0038, "Set External Configuration", + mgmt_set_external_configuration_cmd, 1, true, + mgmt_new_options_rsp, 4, true }, + { 0x0039, "Set Public Address", + mgmt_set_public_address_cmd, 6, true, + mgmt_new_options_rsp, 4, true }, + { 0x003a, "Start Service Discovery", + mgmt_start_service_discovery_cmd, 3, false, + mgmt_start_service_discovery_rsp, 1, true }, + { 0x003b, "Read Local Out Of Band Extended Data", + mgmt_read_local_oob_ext_data_cmd, 1, true, + mgmt_read_local_oob_ext_data_rsp, 3, false }, + { 0x003c, "Read Extended Controller Index List", + mgmt_null_cmd, 0, true, + mgmt_read_ext_index_list_rsp, 2, false }, + { 0x003d, "Read Advertising Features", + mgmt_null_cmd, 0, true, + mgmt_read_advertising_features_rsp, 8, false }, + { 0x003e, "Add Advertising", + mgmt_add_advertising_cmd, 11, false, + mgmt_add_advertising_rsp, 1, true }, + { 0x003f, "Remove Advertising", + mgmt_remove_advertising_cmd, 1, true, + mgmt_remove_advertising_rsp, 1, true }, + { 0x0040, "Get Advertising Size Information", + mgmt_get_advertising_size_info_cmd, 5, true, + mgmt_get_advertising_size_info_rsp, 7, true }, + { 0x0041, "Start Limited Discovery", + mgmt_start_limited_discovery_cmd, 1, true, + mgmt_start_limited_discovery_rsp, 1, true }, + { 0x0042, "Read Extended Controller Information", + mgmt_null_cmd, 0, true, + mgmt_read_ext_controller_info_rsp, 19, false }, + { 0x0043, "Set Appearance", + mgmt_set_apperance_cmd, 2, true, + mgmt_null_rsp, 0, true }, + { 0x0044, "Get PHY Configuration", + mgmt_null_cmd, 0, true, + mgmt_get_phy_rsp, 12, true }, + { 0x0045, "Set PHY Configuration", + mgmt_set_phy_cmd, 4, true, + mgmt_null_rsp, 0, true }, + { } +}; + +static void mgmt_null_evt(const void *data, uint16_t size) +{ +} + +static void mgmt_command_complete_evt(const void *data, uint16_t size) +{ + uint16_t opcode; + uint8_t status; + const struct mgmt_data *mgmt_data = NULL; + const char *mgmt_color, *mgmt_str; + int i; + + opcode = get_le16(data); + status = get_u8(data + 2); + + data += 3; + size -= 3; + + for (i = 0; mgmt_command_table[i].str; i++) { + if (mgmt_command_table[i].opcode == opcode) { + mgmt_data = &mgmt_command_table[i]; + break; + } + } + + if (mgmt_data) { + if (mgmt_data->rsp_func) + mgmt_color = COLOR_CTRL_COMMAND; + else + mgmt_color = COLOR_CTRL_COMMAND_UNKNOWN; + mgmt_str = mgmt_data->str; + } else { + mgmt_color = COLOR_CTRL_COMMAND_UNKNOWN; + mgmt_str = "Unknown"; + } + + print_indent(6, mgmt_color, "", mgmt_str, COLOR_OFF, + " (0x%4.4x) plen %u", opcode, size); + + mgmt_print_status(status); + + if (!mgmt_data || !mgmt_data->rsp_func) { + packet_hexdump(data, size); + return; + } + + if (mgmt_data->rsp_fixed) { + if (size != mgmt_data->rsp_size) { + print_text(COLOR_ERROR, "invalid packet size"); + packet_hexdump(data, size); + return; + } + } else { + if (size < mgmt_data->rsp_size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data, size); + return; + } + } + + mgmt_data->rsp_func(data, size); +} + +static void mgmt_command_status_evt(const void *data, uint16_t size) +{ + uint16_t opcode; + uint8_t status; + const struct mgmt_data *mgmt_data = NULL; + const char *mgmt_color, *mgmt_str; + int i; + + opcode = get_le16(data); + status = get_u8(data + 2); + + for (i = 0; mgmt_command_table[i].str; i++) { + if (mgmt_command_table[i].opcode == opcode) { + mgmt_data = &mgmt_command_table[i]; + break; + } + } + + if (mgmt_data) { + mgmt_color = COLOR_CTRL_COMMAND; + mgmt_str = mgmt_data->str; + } else { + mgmt_color = COLOR_CTRL_COMMAND_UNKNOWN; + mgmt_str = "Unknown"; + } + + print_indent(6, mgmt_color, "", mgmt_str, COLOR_OFF, + " (0x%4.4x)", opcode); + + mgmt_print_status(status); +} + +static void mgmt_controller_error_evt(const void *data, uint16_t size) +{ + uint8_t error = get_u8(data); + + print_field("Error: 0x%2.2x", error); +} + +static void mgmt_new_settings_evt(const void *data, uint16_t size) +{ + uint32_t settings = get_le32(data); + + mgmt_print_settings("Current settings", settings); +} + +static void mgmt_class_of_dev_changed_evt(const void *data, uint16_t size) +{ + print_dev_class(data); +} + +static void mgmt_local_name_changed_evt(const void *data, uint16_t size) +{ + mgmt_print_name(data); +} + +static void mgmt_new_link_key_evt(const void *data, uint16_t size) +{ + uint8_t store_hint = get_u8(data); + + mgmt_print_store_hint(store_hint); + mgmt_print_link_key(data + 1); +} + +static void mgmt_new_long_term_key_evt(const void *data, uint16_t size) +{ + uint8_t store_hint = get_u8(data); + + mgmt_print_store_hint(store_hint); + mgmt_print_long_term_key(data + 1); +} + +static void mgmt_device_connected_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint32_t flags = get_le32(data + 7); + uint16_t data_len = get_le16(data + 11); + + mgmt_print_address(data, address_type); + mgmt_print_device_flags(flags); + print_field("Data length: %u", data_len); + print_eir(data + 13, size - 13, false); +} + +static void mgmt_device_disconnected_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t reason = get_u8(data + 7); + const char *str; + + mgmt_print_address(data, address_type); + + switch (reason) { + case 0x00: + str = "Unspecified"; + break; + case 0x01: + str = "Connection timeout"; + break; + case 0x02: + str = "Connection terminated by local host"; + break; + case 0x03: + str = "Connection terminated by remote host"; + break; + case 0x04: + str = "Connection terminated due to authentication failure"; + break; + default: + str = "Reserved"; + break; + } + + print_field("Reason: %s (0x%2.2x)", str, reason); +} + +static void mgmt_connect_failed_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t status = get_u8(data + 7); + + mgmt_print_address(data, address_type); + mgmt_print_status(status); +} + +static void mgmt_pin_code_request_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t secure_pin = get_u8(data + 7); + + mgmt_print_address(data, address_type); + print_field("Secure PIN: 0x%2.2x", secure_pin); +} + +static void mgmt_user_confirmation_request_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t confirm_hint = get_u8(data + 7); + uint32_t value = get_le32(data + 8); + + mgmt_print_address(data, address_type); + print_field("Confirm hint: 0x%2.2x", confirm_hint); + print_field("Value: 0x%8.8x", value); +} + +static void mgmt_user_passkey_request_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_authentication_failed_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t status = get_u8(data + 7); + + mgmt_print_address(data, address_type); + mgmt_print_status(status); +} + +static void mgmt_device_found_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + int8_t rssi = get_s8(data + 7); + uint32_t flags = get_le32(data + 8); + uint16_t data_len = get_le16(data + 12); + + mgmt_print_address(data, address_type); + print_rssi(rssi); + mgmt_print_device_flags(flags); + print_field("Data length: %u", data_len); + print_eir(data + 14, size - 14, false); +} + +static void mgmt_discovering_evt(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + uint8_t enable = get_u8(data + 1); + + mgmt_print_address_type(type); + print_enable("Discovery", enable); +} + +static void mgmt_device_blocked_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_device_unblocked_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_device_unpaired_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_passkey_notify_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint32_t passkey = get_le32(data + 7); + uint8_t entered = get_u8(data + 11); + + mgmt_print_address(data, address_type); + print_field("Passkey: 0x%8.8x", passkey); + print_field("Entered: %u", entered); +} + +static void mgmt_new_identity_resolving_key_evt(const void *data, uint16_t size) +{ + uint8_t store_hint = get_u8(data); + + mgmt_print_store_hint(store_hint); + print_addr_resolve("Random address", data + 1, 0x01, false); + mgmt_print_identity_resolving_key(data + 7); +} + +static void mgmt_new_signature_resolving_key_evt(const void *data, uint16_t size) +{ + uint8_t store_hint = get_u8(data); + + mgmt_print_store_hint(store_hint); + mgmt_print_signature_resolving_key(data + 1); +} + +static void mgmt_device_added_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + uint8_t action = get_u8(data + 7); + + mgmt_print_address(data, address_type); + mgmt_print_device_action(action); +} + +static void mgmt_device_removed_evt(const void *data, uint16_t size) +{ + uint8_t address_type = get_u8(data + 6); + + mgmt_print_address(data, address_type); +} + +static void mgmt_new_connection_parameter_evt(const void *data, uint16_t size) +{ + uint8_t store_hint = get_u8(data); + + mgmt_print_store_hint(store_hint); + mgmt_print_connection_parameter(data + 1); +} + +static void mgmt_new_conf_options_evt(const void *data, uint16_t size) +{ + uint32_t missing_options = get_le32(data); + + mgmt_print_options("Missing options", missing_options); +} + +static void mgmt_ext_index_added_evt(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + uint8_t bus = get_u8(data + 1); + + print_field("type 0x%2.2x - bus 0x%2.2x", type, bus); +} + +static void mgmt_ext_index_removed_evt(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + uint8_t bus = get_u8(data + 1); + + print_field("type 0x%2.2x - bus 0x%2.2x", type, bus); +} + +static void mgmt_local_oob_ext_data_updated_evt(const void *data, uint16_t size) +{ + uint8_t type = get_u8(data); + uint16_t data_len = get_le16(data + 1); + + mgmt_print_address_type(type); + print_field("Data length: %u", data_len); + print_eir(data + 3, size - 3, true); +} + +static void mgmt_advertising_added_evt(const void *data, uint16_t size) +{ + uint8_t instance = get_u8(data); + + print_field("Instance: %u", instance); +} + +static void mgmt_advertising_removed_evt(const void *data, uint16_t size) +{ + uint8_t instance = get_u8(data); + + print_field("Instance: %u", instance); +} + +static void mgmt_ext_controller_info_changed_evt(const void *data, uint16_t size) +{ + uint16_t data_len = get_le16(data); + + print_field("Data length: %u", data_len); + print_eir(data + 2, size - 2, false); +} + +static void mgmt_phy_changed_evt(const void *data, uint16_t size) +{ + uint32_t selected_phys = get_le32(data); + + mgmt_print_phys("Selected PHYs", selected_phys); +} + +static const struct mgmt_data mgmt_event_table[] = { + { 0x0001, "Command Complete", + mgmt_command_complete_evt, 3, false }, + { 0x0002, "Command Status", + mgmt_command_status_evt, 3, true }, + { 0x0003, "Controller Error", + mgmt_controller_error_evt, 1, true }, + { 0x0004, "Index Added", + mgmt_null_evt, 0, true }, + { 0x0005, "Index Removed", + mgmt_null_evt, 0, true }, + { 0x0006, "New Settings", + mgmt_new_settings_evt, 4, true }, + { 0x0007, "Class Of Device Changed", + mgmt_class_of_dev_changed_evt, 3, true }, + { 0x0008, "Local Name Changed", + mgmt_local_name_changed_evt, 260, true }, + { 0x0009, "New Link Key", + mgmt_new_link_key_evt, 26, true }, + { 0x000a, "New Long Term Key", + mgmt_new_long_term_key_evt, 37, true }, + { 0x000b, "Device Connected", + mgmt_device_connected_evt, 13, false }, + { 0x000c, "Device Disconnected", + mgmt_device_disconnected_evt, 8, true }, + { 0x000d, "Connect Failed", + mgmt_connect_failed_evt, 8, true }, + { 0x000e, "PIN Code Request", + mgmt_pin_code_request_evt, 8, true }, + { 0x000f, "User Confirmation Request", + mgmt_user_confirmation_request_evt, 12, true }, + { 0x0010, "User Passkey Request", + mgmt_user_passkey_request_evt, 7, true }, + { 0x0011, "Authentication Failed", + mgmt_authentication_failed_evt, 8, true }, + { 0x0012, "Device Found", + mgmt_device_found_evt, 14, false }, + { 0x0013, "Discovering", + mgmt_discovering_evt, 2, true }, + { 0x0014, "Device Blocked", + mgmt_device_blocked_evt, 7, true }, + { 0x0015, "Device Unblocked", + mgmt_device_unblocked_evt, 7, true }, + { 0x0016, "Device Unpaired", + mgmt_device_unpaired_evt, 7, true }, + { 0x0017, "Passkey Notify", + mgmt_passkey_notify_evt, 12, true }, + { 0x0018, "New Identity Resolving Key", + mgmt_new_identity_resolving_key_evt, 30, true }, + { 0x0019, "New Signature Resolving Key", + mgmt_new_signature_resolving_key_evt, 25, true }, + { 0x001a, "Device Added", + mgmt_device_added_evt, 8, true }, + { 0x001b, "Device Removed", + mgmt_device_removed_evt, 7, true }, + { 0x001c, "New Connection Parameter", + mgmt_new_connection_parameter_evt, 16, true }, + { 0x001d, "Unconfigured Index Added", + mgmt_null_evt, 0, true }, + { 0x001e, "Unconfigured Index Removed", + mgmt_null_evt, 0, true }, + { 0x001f, "New Configuration Options", + mgmt_new_conf_options_evt, 4, true }, + { 0x0020, "Extended Index Added", + mgmt_ext_index_added_evt, 2, true }, + { 0x0021, "Extended Index Removed", + mgmt_ext_index_removed_evt, 2, true }, + { 0x0022, "Local Out Of Band Extended Data Updated", + mgmt_local_oob_ext_data_updated_evt, 3, false }, + { 0x0023, "Advertising Added", + mgmt_advertising_added_evt, 1, true }, + { 0x0024, "Advertising Removed", + mgmt_advertising_removed_evt, 1, true }, + { 0x0025, "Extended Controller Information Changed", + mgmt_ext_controller_info_changed_evt, 2, false }, + { 0x0026, "PHY Configuration Changed", + mgmt_phy_changed_evt, 4, true }, + { } +}; + +static void mgmt_print_commands(const void *data, uint16_t num) +{ + int i; + + print_field("Commands: %u", num); + + for (i = 0; i < num; i++) { + uint16_t opcode = get_le16(data + (i * 2)); + const char *str = NULL; + int n; + + for (n = 0; mgmt_command_table[n].str; n++) { + if (mgmt_command_table[n].opcode == opcode) { + str = mgmt_command_table[n].str; + break; + } + } + + print_field(" %s (0x%4.4x)", str ?: "Reserved", opcode); + } +} + +static void mgmt_print_events(const void *data, uint16_t num) +{ + int i; + + print_field("Events: %u", num); + + for (i = 0; i < num; i++) { + uint16_t opcode = get_le16(data + (i * 2)); + const char *str = NULL; + int n; + + for (n = 0; mgmt_event_table[n].str; n++) { + if (mgmt_event_table[n].opcode == opcode) { + str = mgmt_event_table[n].str; + break; + } + } + + print_field(" %s (0x%4.4x)", str ?: "Reserved", opcode); + } +} + +void packet_ctrl_command(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size) +{ + uint32_t cookie; + uint16_t format, opcode; + const struct mgmt_data *mgmt_data = NULL; + const char *mgmt_color, *mgmt_str; + char channel[11], extra_str[25]; + int i; + + if (size < 4) { + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed Control Command packet", NULL, NULL); + packet_hexdump(data, size); + return; + } + + cookie = get_le32(data); + + data += 4; + size -= 4; + + sprintf(channel, "0x%4.4x", cookie); + + format = get_format(cookie); + + if (format != CTRL_MGMT) { + char label[7]; + + sprintf(label, "0x%4.4x", format); + + print_packet(tv, cred, '@', index, channel, COLOR_CTRL_CLOSE, + "Control Command", label, NULL); + packet_hexdump(data, size); + return; + } + + if (size < 2) { + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed MGMT Command packet", NULL, NULL); + packet_hexdump(data, size); + return; + } + + opcode = get_le16(data); + + data += 2; + size -= 2; + + for (i = 0; mgmt_command_table[i].str; i++) { + if (mgmt_command_table[i].opcode == opcode) { + mgmt_data = &mgmt_command_table[i]; + break; + } + } + + if (mgmt_data) { + if (mgmt_data->func) + mgmt_color = COLOR_CTRL_COMMAND; + else + mgmt_color = COLOR_CTRL_COMMAND_UNKNOWN; + mgmt_str = mgmt_data->str; + } else { + mgmt_color = COLOR_CTRL_COMMAND_UNKNOWN; + mgmt_str = "Unknown"; + } + + sprintf(extra_str, "(0x%4.4x) plen %d", opcode, size); + + print_packet(tv, cred, '@', index, channel, mgmt_color, + "MGMT Command", mgmt_str, extra_str); + + if (!mgmt_data || !mgmt_data->func) { + packet_hexdump(data, size); + return; + } + + if (mgmt_data->fixed) { + if (size != mgmt_data->size) { + print_text(COLOR_ERROR, "invalid packet size"); + packet_hexdump(data, size); + return; + } + } else { + if (size < mgmt_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data, size); + return; + } + } + + mgmt_data->func(data, size); +} + +void packet_ctrl_event(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size) +{ + uint32_t cookie; + uint16_t format, opcode; + const struct mgmt_data *mgmt_data = NULL; + const char *mgmt_color, *mgmt_str; + char channel[11], extra_str[25]; + int i; + + if (size < 4) { + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed Control Event packet", NULL, NULL); + packet_hexdump(data, size); + return; + } + + cookie = get_le32(data); + + data += 4; + size -= 4; + + sprintf(channel, "0x%4.4x", cookie); + + format = get_format(cookie); + + if (format != CTRL_MGMT) { + char label[7]; + + sprintf(label, "0x%4.4x", format); + + print_packet(tv, cred, '@', index, channel, COLOR_CTRL_CLOSE, + "Control Event", label, NULL); + packet_hexdump(data, size); + return; + } + + if (size < 2) { + print_packet(tv, cred, '*', index, NULL, COLOR_ERROR, + "Malformed MGMT Event packet", NULL, NULL); + packet_hexdump(data, size); + return; + } + + opcode = get_le16(data); + + data += 2; + size -= 2; + + for (i = 0; mgmt_event_table[i].str; i++) { + if (mgmt_event_table[i].opcode == opcode) { + mgmt_data = &mgmt_event_table[i]; + break; + } + } + + if (mgmt_data) { + if (mgmt_data->func) + mgmt_color = COLOR_CTRL_EVENT; + else + mgmt_color = COLOR_CTRL_EVENT_UNKNOWN; + mgmt_str = mgmt_data->str; + } else { + mgmt_color = COLOR_CTRL_EVENT_UNKNOWN; + mgmt_str = "Unknown"; + } + + sprintf(extra_str, "(0x%4.4x) plen %d", opcode, size); + + print_packet(tv, cred, '@', index, channel, mgmt_color, + "MGMT Event", mgmt_str, extra_str); + + if (!mgmt_data || !mgmt_data->func) { + packet_hexdump(data, size); + return; + } + + if (mgmt_data->fixed) { + if (size != mgmt_data->size) { + print_text(COLOR_ERROR, "invalid packet size"); + packet_hexdump(data, size); + return; + } + } else { + if (size < mgmt_data->size) { + print_text(COLOR_ERROR, "too short packet"); + packet_hexdump(data, size); + return; + } + } + + mgmt_data->func(data, size); +} + +void packet_todo(void) +{ + int i; + + printf("HCI commands with missing decodings:\n"); + + for (i = 0; opcode_table[i].str; i++) { + if (opcode_table[i].bit < 0) + continue; + + if (opcode_table[i].cmd_func) + continue; + + printf("\t%s\n", opcode_table[i].str); + } + + printf("HCI events with missing decodings:\n"); + + for (i = 0; event_table[i].str; i++) { + if (event_table[i].func) + continue; + + printf("\t%s\n", event_table[i].str); + } + + for (i = 0; le_meta_event_table[i].str; i++) { + if (le_meta_event_table[i].func) + continue; + + printf("\t%s\n", le_meta_event_table[i].str); + } +} diff --git a/monitor/packet.h b/monitor/packet.h new file mode 100644 index 0000000..199e15e --- /dev/null +++ b/monitor/packet.h @@ -0,0 +1,107 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include + +#define PACKET_FILTER_SHOW_INDEX (1 << 0) +#define PACKET_FILTER_SHOW_DATE (1 << 1) +#define PACKET_FILTER_SHOW_TIME (1 << 2) +#define PACKET_FILTER_SHOW_TIME_OFFSET (1 << 3) +#define PACKET_FILTER_SHOW_ACL_DATA (1 << 4) +#define PACKET_FILTER_SHOW_SCO_DATA (1 << 5) +#define PACKET_FILTER_SHOW_A2DP_STREAM (1 << 6) + +bool packet_has_filter(unsigned long filter); +void packet_set_filter(unsigned long filter); +void packet_add_filter(unsigned long filter); +void packet_del_filter(unsigned long filter); + +void packet_set_priority(const char *priority); +void packet_select_index(uint16_t index); +void packet_set_fallback_manufacturer(uint16_t manufacturer); + +void packet_hexdump(const unsigned char *buf, uint16_t len); +void packet_print_error(const char *label, uint8_t error); +void packet_print_version(const char *label, uint8_t version, + const char *sublabel, uint16_t subversion); +void packet_print_company(const char *label, uint16_t company); +void packet_print_addr(const char *label, const void *data, bool random); +void packet_print_handle(uint16_t handle); +void packet_print_rssi(int8_t rssi); +void packet_print_ad(const void *data, uint8_t size); +void packet_print_features_lmp(const uint8_t *features, uint8_t page); +void packet_print_features_ll(const uint8_t *features); +void packet_print_channel_map_lmp(const uint8_t *map); +void packet_print_channel_map_ll(const uint8_t *map); +void packet_print_io_capability(uint8_t capability); +void packet_print_io_authentication(uint8_t authentication); + +void packet_control(struct timeval *tv, struct ucred *cred, + uint16_t index, uint16_t opcode, + const void *data, uint16_t size); +void packet_monitor(struct timeval *tv, struct ucred *cred, + uint16_t index, uint16_t opcode, + const void *data, uint16_t size); +void packet_simulator(struct timeval *tv, uint16_t frequency, + const void *data, uint16_t size); + +void packet_new_index(struct timeval *tv, uint16_t index, const char *label, + uint8_t type, uint8_t bus, const char *name); +void packet_del_index(struct timeval *tv, uint16_t index, const char *label); +void packet_open_index(struct timeval *tv, uint16_t index, const char *label); +void packet_close_index(struct timeval *tv, uint16_t index, const char *label); +void packet_index_info(struct timeval *tv, uint16_t index, const char *label, + uint16_t manufacturer); +void packet_vendor_diag(struct timeval *tv, uint16_t index, + uint16_t manufacturer, + const void *data, uint16_t size); +void packet_system_note(struct timeval *tv, struct ucred *cred, + uint16_t index, const void *message); +void packet_user_logging(struct timeval *tv, struct ucred *cred, + uint16_t index, uint8_t priority, + const char *ident, const void *data, + uint16_t size); + +void packet_hci_command(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size); +void packet_hci_event(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size); +void packet_hci_acldata(struct timeval *tv, struct ucred *cred, uint16_t index, + bool in, const void *data, uint16_t size); +void packet_hci_scodata(struct timeval *tv, struct ucred *cred, uint16_t index, + bool in, const void *data, uint16_t size); + +void packet_ctrl_open(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size); +void packet_ctrl_close(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size); +void packet_ctrl_command(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size); +void packet_ctrl_event(struct timeval *tv, struct ucred *cred, uint16_t index, + const void *data, uint16_t size); + +void packet_todo(void); diff --git a/monitor/rfcomm.c b/monitor/rfcomm.c new file mode 100644 index 0000000..94d28de --- /dev/null +++ b/monitor/rfcomm.c @@ -0,0 +1,516 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "bt.h" +#include "packet.h" +#include "display.h" +#include "l2cap.h" +#include "keys.h" +#include "sdp.h" +#include "rfcomm.h" + +static char *cr_str[] = { + "RSP", + "CMD" +}; + +/* RFCOMM frame parsing macros */ +#define CR_STR(type) cr_str[GET_CR(type)] +#define GET_LEN8(length) ((length & 0xfe) >> 1) +#define GET_LEN16(length) ((length & 0xfffe) >> 1) +#define GET_CR(type) ((type & 0x02) >> 1) +#define GET_PF(ctr) (((ctr) >> 4) & 0x1) + +/* MSC macros */ +#define GET_V24_FC(sigs) ((sigs & 0x02) >> 1) +#define GET_V24_RTC(sigs) ((sigs & 0x04) >> 2) +#define GET_V24_RTR(sigs) ((sigs & 0x08) >> 3) +#define GET_V24_IC(sigs) ((sigs & 0x40) >> 6) +#define GET_V24_DV(sigs) ((sigs & 0x80) >> 7) + +/* RPN macros */ +#define GET_RPN_DB(parity) (parity & 0x03) +#define GET_RPN_SB(parity) ((parity & 0x04) >> 2) +#define GET_RPN_PARITY(parity) ((parity & 0x08) >> 3) +#define GET_RPN_PTYPE(parity) ((parity & 0x30) >> 4) +#define GET_RPN_XIN(io) (io & 0x01) +#define GET_RPN_XOUT(io) ((io & 0x02) >> 1) +#define GET_RPN_RTRI(io) ((io & 0x04) >> 2) +#define GET_RPN_RTRO(io) ((io & 0x08) >> 3) +#define GET_RPN_RTCI(io) ((io & 0x10) >> 4) +#define GET_RPN_RTCO(io) ((io & 0x20) >> 5) + +/* RLS macro */ +#define GET_ERROR(err) (err & 0x0f) + +/* PN macros */ +#define GET_FRM_TYPE(ctrl) ((ctrl & 0x0f)) +#define GET_CRT_FLOW(ctrl) ((ctrl & 0xf0) >> 4) +#define GET_PRIORITY(prio) ((prio & 0x3f)) +#define GET_PN_DLCI(dlci) ((dlci & 0x3f)) + +struct rfcomm_lhdr { + uint8_t address; + uint8_t control; + uint16_t length; + uint8_t fcs; + uint8_t credits; /* only for UIH frame */ +}; + +struct rfcomm_lmsc { + uint8_t dlci; + uint8_t v24_sig; + uint8_t break_sig; +}; + +struct rfcomm_rpn { + uint8_t dlci; + uint8_t bit_rate; + uint8_t parity; + uint8_t io; + uint8_t xon; + uint8_t xoff; + uint16_t pm; +}; + +struct rfcomm_rls { + uint8_t dlci; + uint8_t error; +}; + +struct rfcomm_nsc { + uint8_t cmd_type; +}; + +struct rfcomm_lmcc { + uint8_t type; + uint16_t length; +}; + +struct rfcomm_frame { + struct rfcomm_lhdr hdr; + struct rfcomm_lmcc mcc; + struct l2cap_frame l2cap_frame; +}; + +static void print_rfcomm_hdr(struct rfcomm_frame *rfcomm_frame, uint8_t indent) +{ + struct rfcomm_lhdr hdr = rfcomm_frame->hdr; + + /* Address field */ + print_field("%*cAddress: 0x%2.2x cr %d dlci 0x%2.2x", indent, ' ', + hdr.address, GET_CR(hdr.address), + RFCOMM_GET_DLCI(hdr.address)); + + /* Control field */ + print_field("%*cControl: 0x%2.2x poll/final %d", indent, ' ', + hdr.control, GET_PF(hdr.control)); + + /* Length and FCS */ + print_field("%*cLength: %d", indent, ' ', hdr.length); + print_field("%*cFCS: 0x%2.2x", indent, ' ', hdr.fcs); +} + +static inline bool mcc_test(struct rfcomm_frame *rfcomm_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &rfcomm_frame->l2cap_frame; + uint8_t data; + + printf("%*cTest Data: 0x ", indent, ' '); + + while (frame->size > 1) { + if (!l2cap_frame_get_u8(frame, &data)) + return false; + printf("%2.2x ", data); + } + + printf("\n"); + return true; +} + +static inline bool mcc_msc(struct rfcomm_frame *rfcomm_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &rfcomm_frame->l2cap_frame; + struct rfcomm_lmsc msc; + + if (!l2cap_frame_get_u8(frame, &msc.dlci)) + return false; + + print_field("%*cdlci %d ", indent, ' ', RFCOMM_GET_DLCI(msc.dlci)); + + if (!l2cap_frame_get_u8(frame, &msc.v24_sig)) + return false; + + /* v24 control signals */ + print_field("%*cfc %d rtc %d rtr %d ic %d dv %d", indent, ' ', + GET_V24_FC(msc.v24_sig), GET_V24_RTC(msc.v24_sig), + GET_V24_RTR(msc.v24_sig), GET_V24_IC(msc.v24_sig), + GET_V24_DV(msc.v24_sig)); + + if (frame->size < 2) + goto done; + + /* + * TODO: Implement the break signals decoding. + */ + + packet_hexdump(frame->data, frame->size); + +done: + return true; +} + +static inline bool mcc_rpn(struct rfcomm_frame *rfcomm_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &rfcomm_frame->l2cap_frame; + struct rfcomm_rpn rpn; + + if (!l2cap_frame_get_u8(frame, &rpn.dlci)) + return false; + + print_field("%*cdlci %d", indent, ' ', RFCOMM_GET_DLCI(rpn.dlci)); + + if (frame->size < 7) + goto done; + + /* port value octets (optional) */ + + if (!l2cap_frame_get_u8(frame, &rpn.bit_rate)) + return false; + + if (!l2cap_frame_get_u8(frame, &rpn.parity)) + return false; + + if (!l2cap_frame_get_u8(frame, &rpn.io)) + return false; + + print_field("%*cbr %d db %d sb %d p %d pt %d xi %d xo %d", indent, ' ', + rpn.bit_rate, GET_RPN_DB(rpn.parity), GET_RPN_SB(rpn.parity), + GET_RPN_PARITY(rpn.parity), GET_RPN_PTYPE(rpn.parity), + GET_RPN_XIN(rpn.io), GET_RPN_XOUT(rpn.io)); + + if (!l2cap_frame_get_u8(frame, &rpn.xon)) + return false; + + if (!l2cap_frame_get_u8(frame, &rpn.xoff)) + return false; + + print_field("%*crtri %d rtro %d rtci %d rtco %d xon %d xoff %d", + indent, ' ', GET_RPN_RTRI(rpn.io), GET_RPN_RTRO(rpn.io), + GET_RPN_RTCI(rpn.io), GET_RPN_RTCO(rpn.io), rpn.xon, + rpn.xoff); + + if (!l2cap_frame_get_le16(frame, &rpn.pm)) + return false; + + print_field("%*cpm 0x%04x", indent, ' ', rpn.pm); + +done: + return true; +} + +static inline bool mcc_rls(struct rfcomm_frame *rfcomm_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &rfcomm_frame->l2cap_frame; + struct rfcomm_rls rls; + + if (!l2cap_frame_get_u8(frame, &rls.dlci)) + return false; + + if (!l2cap_frame_get_u8(frame, &rls.error)) + return false; + + print_field("%*cdlci %d error: %d", indent, ' ', + RFCOMM_GET_DLCI(rls.dlci), GET_ERROR(rls.error)); + + return true; +} + +static inline bool mcc_pn(struct rfcomm_frame *rfcomm_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &rfcomm_frame->l2cap_frame; + struct rfcomm_pn pn; + uint16_t mtu; + + /* rfcomm_pn struct is defined in rfcomm.h */ + + if (!l2cap_frame_get_u8(frame, &pn.dlci)) + return false; + + if (!l2cap_frame_get_u8(frame, &pn.flow_ctrl)) + return false; + + if (!l2cap_frame_get_u8(frame, &pn.priority)) + return false; + + print_field("%*cdlci %d frame_type %d credit_flow %d pri %d", indent, + ' ', GET_PN_DLCI(pn.dlci), GET_FRM_TYPE(pn.flow_ctrl), + GET_CRT_FLOW(pn.flow_ctrl), GET_PRIORITY(pn.priority)); + + if (!l2cap_frame_get_u8(frame, &pn.ack_timer)) + return false; + + if (!l2cap_frame_get_le16(frame, &mtu)) + return false; + + pn.mtu = mtu; + + if (!l2cap_frame_get_u8(frame, &pn.max_retrans)) + return false; + + if (!l2cap_frame_get_u8(frame, &pn.credits)) + return false; + + print_field("%*cack_timer %d frame_size %d max_retrans %d credits %d", + indent, ' ', pn.ack_timer, pn.mtu, pn.max_retrans, + pn.credits); + + return true; +} + +static inline bool mcc_nsc(struct rfcomm_frame *rfcomm_frame, uint8_t indent) +{ + struct l2cap_frame *frame = &rfcomm_frame->l2cap_frame; + struct rfcomm_nsc nsc; + + if (!l2cap_frame_get_u8(frame, &nsc.cmd_type)) + return false; + + print_field("%*ccr %d, mcc_cmd_type %x", indent, ' ', + GET_CR(nsc.cmd_type), RFCOMM_GET_MCC_TYPE(nsc.cmd_type)); + + return true; +} + +struct mcc_data { + uint8_t type; + const char *str; +}; + +static const struct mcc_data mcc_table[] = { + { 0x08, "Test Command" }, + { 0x28, "Flow Control On Command" }, + { 0x18, "Flow Control Off Command" }, + { 0x38, "Modem Status Command" }, + { 0x24, "Remote Port Negotiation Command" }, + { 0x14, "Remote Line Status" }, + { 0x20, "DLC Parameter Negotiation" }, + { 0x04, "Non Supported Command" }, + { } +}; + +static inline bool mcc_frame(struct rfcomm_frame *rfcomm_frame, uint8_t indent) +{ + uint8_t length, ex_length, type; + const char *type_str; + int i; + struct l2cap_frame *frame = &rfcomm_frame->l2cap_frame; + struct rfcomm_lmcc mcc; + const struct mcc_data *mcc_data = NULL; + + if (!l2cap_frame_get_u8(frame, &mcc.type) || + !l2cap_frame_get_u8(frame, &length)) + return false; + + if (RFCOMM_TEST_EA(length)) + mcc.length = (uint16_t) GET_LEN8(length); + else { + if (!l2cap_frame_get_u8(frame, &ex_length)) + return false; + mcc.length = ((uint16_t) length << 8) | ex_length; + mcc.length = GET_LEN16(mcc.length); + } + + type = RFCOMM_GET_MCC_TYPE(mcc.type); + + for (i = 0; mcc_table[i].str; i++) { + if (mcc_table[i].type == type) { + mcc_data = &mcc_table[i]; + break; + } + } + + if (mcc_data) + type_str = mcc_data->str; + else + type_str = "Unknown"; + + print_field("%*cMCC Message type: %s %s (0x%2.2x)", indent, ' ', + type_str, CR_STR(mcc.type), type); + + print_field("%*cLength: %d", indent+2, ' ', mcc.length); + + rfcomm_frame->mcc = mcc; + + switch (type) { + case RFCOMM_TEST: + return mcc_test(rfcomm_frame, indent+10); + case RFCOMM_MSC: + return mcc_msc(rfcomm_frame, indent+2); + case RFCOMM_RPN: + return mcc_rpn(rfcomm_frame, indent+2); + case RFCOMM_RLS: + return mcc_rls(rfcomm_frame, indent+2); + case RFCOMM_PN: + return mcc_pn(rfcomm_frame, indent+2); + case RFCOMM_NSC: + return mcc_nsc(rfcomm_frame, indent+2); + default: + packet_hexdump(frame->data, frame->size); + } + + return true; +} + +static bool uih_frame(struct rfcomm_frame *rfcomm_frame, uint8_t indent) +{ + uint8_t credits; + struct l2cap_frame *frame = &rfcomm_frame->l2cap_frame; + struct rfcomm_lhdr *hdr = &rfcomm_frame->hdr; + + if (!RFCOMM_GET_CHANNEL(hdr->address)) + return mcc_frame(rfcomm_frame, indent); + + /* fetching credits from UIH frame */ + if (GET_PF(hdr->control)) { + if (!l2cap_frame_get_u8(frame, &credits)) + return false; + hdr->credits = credits; + print_field("%*cCredits: %d", indent, ' ', hdr->credits); + } + + packet_hexdump(frame->data, frame->size); + return true; +} + +struct rfcomm_data { + uint8_t frame; + const char *str; +}; + +static const struct rfcomm_data rfcomm_table[] = { + { 0x2f, "Set Async Balance Mode (SABM)" }, + { 0x63, "Unnumbered Ack (UA)" }, + { 0x0f, "Disconnect Mode (DM)" }, + { 0x43, "Disconnect (DISC)" }, + { 0xef, "Unnumbered Info with Header Check (UIH)" }, + { } +}; + +void rfcomm_packet(const struct l2cap_frame *frame) +{ + uint8_t ctype, length, ex_length, indent = 1; + const char *frame_str, *frame_color; + struct l2cap_frame *l2cap_frame, tmp_frame; + struct rfcomm_frame rfcomm_frame; + struct rfcomm_lhdr hdr; + const struct rfcomm_data *rfcomm_data = NULL; + int i; + + l2cap_frame_pull(&rfcomm_frame.l2cap_frame, frame, 0); + + l2cap_frame = &rfcomm_frame.l2cap_frame; + + if (frame->size < 4) + goto fail; + + if (!l2cap_frame_get_u8(l2cap_frame, &hdr.address) || + !l2cap_frame_get_u8(l2cap_frame, &hdr.control) || + !l2cap_frame_get_u8(l2cap_frame, &length)) + goto fail; + + /* length maybe 1 or 2 octets */ + if (RFCOMM_TEST_EA(length)) + hdr.length = (uint16_t) GET_LEN8(length); + else { + if (!l2cap_frame_get_u8(l2cap_frame, &ex_length)) + goto fail; + hdr.length = ((uint16_t)length << 8) | ex_length; + hdr.length = GET_LEN16(hdr.length); + } + + l2cap_frame_pull(&tmp_frame, l2cap_frame, l2cap_frame->size-1); + + if (!l2cap_frame_get_u8(&tmp_frame, &hdr.fcs)) + goto fail; + + /* Decoding frame type */ + ctype = RFCOMM_GET_TYPE(hdr.control); + + for (i = 0; rfcomm_table[i].str; i++) { + if (rfcomm_table[i].frame == ctype) { + rfcomm_data = &rfcomm_table[i]; + break; + } + } + + if (rfcomm_data) { + if (frame->in) + frame_color = COLOR_MAGENTA; + else + frame_color = COLOR_BLUE; + frame_str = rfcomm_data->str; + } else { + frame_color = COLOR_WHITE_BG; + frame_str = "Unknown"; + } + + if (!rfcomm_data) { + packet_hexdump(frame->data, frame->size); + return; + } + + print_indent(6, frame_color, "RFCOMM: ", frame_str, COLOR_OFF, + " (0x%2.2x)", ctype); + + rfcomm_frame.hdr = hdr; + print_rfcomm_hdr(&rfcomm_frame, indent); + + /* UIH frame */ + if (ctype == 0xef) + if (!uih_frame(&rfcomm_frame, indent)) + goto fail; + + return; + +fail: + print_text(COLOR_ERROR, "Frame too short"); + packet_hexdump(frame->data, frame->size); + return; +} diff --git a/monitor/rfcomm.h b/monitor/rfcomm.h new file mode 100644 index 0000000..c157352 --- /dev/null +++ b/monitor/rfcomm.h @@ -0,0 +1,80 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define RFCOMM_SABM 0x2f +#define RFCOMM_DISC 0x43 +#define RFCOMM_UA 0x63 +#define RFCOMM_DM 0x0f +#define RFCOMM_UIH 0xef + +#define RFCOMM_GET_TYPE(control) ((control) & 0xef) +#define RFCOMM_GET_DLCI(address) ((address & 0xfc) >> 2) +#define RFCOMM_GET_CHANNEL(address) ((address & 0xf8) >> 3) +#define RFCOMM_GET_DIR(address) ((address & 0x04) >> 2) +#define RFCOMM_TEST_EA(length) ((length & 0x01)) + +struct rfcomm_hdr { + uint8_t address; + uint8_t control; + uint8_t length; +} __attribute__((packed)); + +struct rfcomm_cmd { + uint8_t address; + uint8_t control; + uint8_t length; + uint8_t fcs; +} __attribute__((packed)); + +#define RFCOMM_TEST 0x08 +#define RFCOMM_FCON 0x28 +#define RFCOMM_FCOFF 0x18 +#define RFCOMM_MSC 0x38 +#define RFCOMM_RPN 0x24 +#define RFCOMM_RLS 0x14 +#define RFCOMM_PN 0x20 +#define RFCOMM_NSC 0x04 + +#define RFCOMM_TEST_CR(type) ((type & 0x02)) +#define RFCOMM_GET_MCC_TYPE(type) ((type & 0xfc) >> 2) + +struct rfcomm_mcc { + uint8_t type; + uint8_t length; +} __attribute__((packed)); + +struct rfcomm_msc { + uint8_t dlci; + uint8_t v24_sig; +} __attribute__((packed)); + +struct rfcomm_pn { + uint8_t dlci; + uint8_t flow_ctrl; + uint8_t priority; + uint8_t ack_timer; + uint16_t mtu; + uint8_t max_retrans; + uint8_t credits; +} __attribute__((packed)); diff --git a/monitor/sdp.c b/monitor/sdp.c new file mode 100644 index 0000000..575eda5 --- /dev/null +++ b/monitor/sdp.c @@ -0,0 +1,785 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" + +#include "bt.h" +#include "packet.h" +#include "display.h" +#include "l2cap.h" +#include "sdp.h" + +#define MAX_TID 16 +#define MAX_CONT_SIZE 17 + +struct tid_data { + bool inuse; + uint16_t tid; + uint16_t channel; + uint8_t cont[MAX_CONT_SIZE]; +}; + +static struct tid_data tid_list[MAX_TID]; + +static struct tid_data *get_tid(uint16_t tid, uint16_t channel) +{ + int i, n = -1; + + for (i = 0; i < MAX_TID; i++) { + if (!tid_list[i].inuse) { + if (n < 0) + n = i; + continue; + } + + if (tid_list[i].tid == tid && tid_list[i].channel == channel) + return &tid_list[i]; + } + + if (n < 0) + return NULL; + + tid_list[n].inuse = true; + tid_list[n].tid = tid; + tid_list[n].channel = channel; + + return &tid_list[n]; +} + +static void clear_tid(struct tid_data *tid) +{ + if (tid) + tid->inuse = false; +} + +static void print_uint(uint8_t indent, const uint8_t *data, uint32_t size) +{ + switch (size) { + case 1: + print_field("%*c0x%2.2x", indent, ' ', data[0]); + break; + case 2: + print_field("%*c0x%4.4x", indent, ' ', get_be16(data)); + break; + case 4: + print_field("%*c0x%8.8x", indent, ' ', get_be32(data)); + break; + case 8: + print_field("%*c0x%16.16" PRIx64, indent, ' ', get_be64(data)); + break; + default: + packet_hexdump(data, size); + break; + } +} + +static void print_sint(uint8_t indent, const uint8_t *data, uint32_t size) +{ + packet_hexdump(data, size); +} + +static void print_uuid(uint8_t indent, const uint8_t *data, uint32_t size) +{ + switch (size) { + case 2: + print_field("%*c%s (0x%4.4x)", indent, ' ', + bt_uuid16_to_str(get_be16(data)), get_be16(data)); + break; + case 4: + print_field("%*c%s (0x%8.8x)", indent, ' ', + bt_uuid32_to_str(get_be32(data)), get_be32(data)); + break; + case 16: + /* BASE_UUID = 00000000-0000-1000-8000-00805F9B34FB */ + print_field("%*c%8.8x-%4.4x-%4.4x-%4.4x-%4.4x%8.4x", + indent, ' ', + get_be32(data), get_be16(data + 4), + get_be16(data + 6), get_be16(data + 8), + get_be16(data + 10), get_be32(data + 12)); + if (get_be16(data + 4) == 0x0000 && + get_be16(data + 6) == 0x1000 && + get_be16(data + 8) == 0x8000 && + get_be16(data + 10) == 0x0080 && + get_be32(data + 12) == 0x5F9B34FB) + print_field("%*c%s", indent, ' ', + bt_uuid32_to_str(get_be32(data))); + break; + default: + packet_hexdump(data, size); + break; + } +} + +static void print_string(uint8_t indent, const uint8_t *data, uint32_t size) +{ + char *str = alloca(size + 1); + + str[size] = '\0'; + strncpy(str, (const char *) data, size); + + print_field("%*c%s [len %d]", indent, ' ', str, size); +} + +static void print_boolean(uint8_t indent, const uint8_t *data, uint32_t size) +{ + print_field("%*c%s", indent, ' ', data[0] ? "true" : "false"); +} + +#define SIZES(args...) ((uint8_t[]) { args, 0xff } ) + +static struct { + uint8_t value; + uint8_t *sizes; + bool recurse; + const char *str; + void (*print) (uint8_t indent, const uint8_t *data, uint32_t size); +} type_table[] = { + { 0, SIZES(0), false, "Nil" }, + { 1, SIZES(0, 1, 2, 3, 4), false, "Unsigned Integer", print_uint }, + { 2, SIZES(0, 1, 2, 3, 4), false, "Signed Integer", print_sint }, + { 3, SIZES(1, 2, 4), false, "UUID", print_uuid }, + { 4, SIZES(5, 6, 7), false, "String", print_string }, + { 5, SIZES(0), false, "Boolean", print_boolean }, + { 6, SIZES(5, 6, 7), true, "Sequence" }, + { 7, SIZES(5, 6, 7), true, "Alternative" }, + { 8, SIZES(5, 6, 7), false, "URL", print_string }, + { } +}; + +static struct { + uint8_t index; + uint8_t bits; + uint8_t size; + const char *str; +} size_table[] = { + { 0, 0, 1, "1 byte" }, + { 1, 0, 2, "2 bytes" }, + { 2, 0, 4, "4 bytes" }, + { 3, 0, 8, "8 bytes" }, + { 4, 0, 16, "16 bytes" }, + { 5, 8, 0, "8 bits" }, + { 6, 16, 0, "16 bits" }, + { 7, 32, 0, "32 bits" }, + { } +}; + +static bool valid_size(uint8_t size, uint8_t *sizes) +{ + int i; + + for (i = 0; sizes[i] != 0xff; i++) { + if (sizes[i] == size) + return true; + } + + return false; +} + +static uint8_t get_bits(const uint8_t *data, uint32_t size) +{ + int i; + + for (i = 0; size_table[i].str; i++) { + if (size_table[i].index == (data[0] & 0x07)) + return size_table[i].bits; + } + + return 0; +} + +static uint32_t get_size(const uint8_t *data, uint32_t size) +{ + int i; + + for (i = 0; size_table[i].str; i++) { + if (size_table[i].index == (data[0] & 0x07)) { + switch (size_table[i].bits) { + case 0: + if ((data[0] & 0xf8) == 0) + return 0; + else + return size_table[i].size; + case 8: + return data[1]; + case 16: + return get_be16(data + 1); + case 32: + return get_be32(data + 1); + default: + return 0; + } + } + } + + return 0; +} + +static void decode_data_elements(uint32_t position, uint8_t indent, + const uint8_t *data, uint32_t size, + void (*print_func) (uint32_t, uint8_t, uint8_t, + const uint8_t *, uint32_t)) + +{ + uint32_t datalen, elemlen, extrabits; + int i; + + if (!size) + return; + + extrabits = get_bits(data, size); + + if (size < 1 + (extrabits / 8)) { + print_text(COLOR_ERROR, "data element descriptor too short"); + packet_hexdump(data, size); + return; + } + + datalen = get_size(data, size); + + if (size < 1 + (extrabits / 8) + datalen) { + print_text(COLOR_ERROR, "data element size too short"); + packet_hexdump(data, size); + return; + } + + elemlen = 1 + (extrabits / 8) + datalen; + + for (i = 0; type_table[i].str; i++) { + uint8_t type = (data[0] & 0xf8) >> 3; + + if (type_table[i].value != type) + continue; + + if (print_func) { + print_func(position, indent, type, + data + 1 + (extrabits / 8), datalen); + break; + } + + print_field("%*c%s (%d) with %u byte%s [%u extra bits] len %u", + indent, ' ', type_table[i].str, type, + datalen, datalen == 1 ? "" : "s", + extrabits, elemlen); + if (!valid_size(data[0] & 0x07, type_table[i].sizes)) { + print_text(COLOR_ERROR, "invalid data element size"); + packet_hexdump(data + 1 + (extrabits / 8), datalen); + break; + } + + if (type_table[i].recurse) + decode_data_elements(0, indent + 2, + data + 1 + (extrabits / 8), datalen, + print_func); + else if (type_table[i].print) + type_table[i].print(indent + 2, + data + 1 + (extrabits / 8), datalen); + break; + } + + if (elemlen > size) { + print_text(COLOR_ERROR, "invalid data element size"); + return; + } + + data += elemlen; + size -= elemlen; + + decode_data_elements(position + 1, indent, data, size, print_func); +} + +static uint32_t get_bytes(const uint8_t *data, uint32_t size) +{ + switch (data[0] & 0x07) { + case 5: + return 2 + data[1]; + case 6: + return 3 + get_be16(data + 1); + case 7: + return 5 + get_be32(data + 1); + } + + return 0; +} + +static struct { + uint16_t id; + const char *str; +} attribute_table[] = { + { 0x0000, "Service Record Handle" }, + { 0x0001, "Service Class ID List" }, + { 0x0002, "Service Record State" }, + { 0x0003, "Service ID" }, + { 0x0004, "Protocol Descriptor List" }, + { 0x0005, "Browse Group List" }, + { 0x0006, "Language Base Attribute ID List" }, + { 0x0007, "Service Info Time To Live" }, + { 0x0008, "Service Availability" }, + { 0x0009, "Bluetooth Profile Descriptor List" }, + { 0x000a, "Documentation URL" }, + { 0x000b, "Client Executable URL" }, + { 0x000c, "Icon URL" }, + { 0x000d, "Additional Protocol Descriptor List" }, + { } +}; + +static void print_attr(uint32_t position, uint8_t indent, uint8_t type, + const uint8_t *data, uint32_t size) +{ + int i; + + if ((position % 2) == 0) { + uint16_t id = get_be16(data); + const char *str = "Unknown"; + + for (i = 0; attribute_table[i].str; i++) { + if (attribute_table[i].id == id) + str = attribute_table[i].str; + } + + print_field("%*cAttribute: %s (0x%4.4x) [len %d]", + indent, ' ', str, id, size); + return; + } + + for (i = 0; type_table[i].str; i++) { + if (type_table[i].value != type) + continue; + + if (type_table[i].recurse) + decode_data_elements(0, indent + 2, data, size, NULL); + else if (type_table[i].print) + type_table[i].print(indent + 2, data, size); + break; + } +} + +static void print_attr_list(uint32_t position, uint8_t indent, uint8_t type, + const uint8_t *data, uint32_t size) +{ + print_field("%*cAttribute list: [len %d] {position %d}", + indent, ' ', size, position); + + decode_data_elements(0, indent + 2, data, size, print_attr); +} + +static void print_attr_lists(uint32_t position, uint8_t indent, uint8_t type, + const uint8_t *data, uint32_t size) +{ + decode_data_elements(0, indent, data, size, print_attr_list); +} + +static void print_continuation(const uint8_t *data, uint16_t size) +{ + if (data[0] != size - 1) { + print_text(COLOR_ERROR, "invalid continuation state"); + packet_hexdump(data, size); + return; + } + + print_field("Continuation state: %d", data[0]); + packet_hexdump(data + 1, size - 1); +} + +static void store_continuation(struct tid_data *tid, + const uint8_t *data, uint16_t size) +{ + if (size > MAX_CONT_SIZE) { + print_text(COLOR_ERROR, "invalid continuation size"); + return; + } + memcpy(tid->cont, data, size); + print_continuation(data, size); +} + +#define MAX_CONT 8 + +struct cont_data { + uint16_t channel; + uint8_t cont[17]; + void *data; + uint32_t size; +}; + +static struct cont_data cont_list[MAX_CONT]; + +static void handle_continuation(struct tid_data *tid, bool nested, + uint16_t bytes, const uint8_t *data, uint16_t size) +{ + uint8_t *newdata; + int i, n = -1; + + if (bytes + 1 > size) { + print_text(COLOR_ERROR, "missing continuation state"); + return; + } + + if (tid->cont[0] == 0x00 && data[bytes] == 0x00) { + decode_data_elements(0, 2, data, bytes, + nested ? print_attr_lists : print_attr_list); + + print_continuation(data + bytes, size - bytes); + return; + } + + for (i = 0; i < MAX_CONT; i++) { + if (cont_list[i].cont[0] == 0x00) { + if (n < 0) + n = i; + continue; + } + + if (cont_list[i].channel != tid->channel) + continue; + + if (cont_list[i].cont[0] != tid->cont[0]) + continue; + + if (!memcmp(cont_list[i].cont + 1, + tid->cont + 1, tid->cont[0])) { + n = i; + break; + } + } + + print_continuation(data + bytes, size - bytes); + + if (n < 0) + return; + + newdata = realloc(cont_list[n].data, cont_list[n].size + bytes); + if (!newdata) { + print_text(COLOR_ERROR, "failed buffer allocation"); + free(cont_list[n].data); + cont_list[n].data = NULL; + cont_list[n].size = 0; + return; + } + + cont_list[n].channel = tid->channel; + cont_list[n].data = newdata; + + if (bytes > 0) { + memcpy(cont_list[n].data + cont_list[n].size, data, bytes); + cont_list[n].size += bytes; + } + + if (data[bytes] == 0x00) { + print_field("Combined attribute bytes: %d", cont_list[n].size); + + decode_data_elements(0, 2, cont_list[n].data, cont_list[n].size, + nested ? print_attr_lists : print_attr_list); + + free(cont_list[n].data); + cont_list[n].data = NULL; + cont_list[n].size = 0; + } else + memcpy(cont_list[i].cont, data + bytes, data[bytes] + 1); +} + +static uint16_t common_rsp(const struct l2cap_frame *frame, + struct tid_data *tid) +{ + uint16_t bytes; + + if (frame->size < 2) { + print_text(COLOR_ERROR, "invalid size"); + packet_hexdump(frame->data, frame->size); + return 0; + } + + bytes = get_be16(frame->data); + print_field("Attribute bytes: %d", bytes); + + if (bytes > frame->size - 2) { + print_text(COLOR_ERROR, "invalid attribute size"); + packet_hexdump(frame->data + 2, frame->size - 2); + return 0; + } + + return bytes; +} + +static const char *error_str(uint16_t code) +{ + switch (code) { + case 0x0001: + return "Invalid Version"; + case 0x0002: + return "Invalid Record Handle"; + case 0x0003: + return "Invalid Syntax"; + case 0x0004: + return "Invalid PDU Size"; + case 0x0005: + return "Invalid Continuation State"; + default: + return "Unknown"; + } +} + +static void error_rsp(const struct l2cap_frame *frame, struct tid_data *tid) +{ + uint16_t error; + + clear_tid(tid); + + if (frame->size < 2) { + print_text(COLOR_ERROR, "invalid size"); + packet_hexdump(frame->data, frame->size); + return; + } + + error = get_be16(frame->data); + + print_field("Error code: %s (0x%4.4x)", error_str(error), error); +} + +static void service_req(const struct l2cap_frame *frame, struct tid_data *tid) +{ + uint32_t search_bytes; + + search_bytes = get_bytes(frame->data, frame->size); + print_field("Search pattern: [len %d]", search_bytes); + + if (search_bytes + 2 > frame->size) { + print_text(COLOR_ERROR, "invalid search list length"); + packet_hexdump(frame->data, frame->size); + return; + } + + decode_data_elements(0, 2, frame->data, search_bytes, NULL); + + print_field("Max record count: %d", + get_be16(frame->data + search_bytes)); + + print_continuation(frame->data + search_bytes + 2, + frame->size - search_bytes - 2); +} + +static void service_rsp(const struct l2cap_frame *frame, struct tid_data *tid) +{ + uint16_t count; + int i; + + clear_tid(tid); + + if (frame->size < 4) { + print_text(COLOR_ERROR, "invalid size"); + packet_hexdump(frame->data, frame->size); + return; + } + + count = get_be16(frame->data + 2); + if (count * 4 > frame->size) { + print_text(COLOR_ERROR, "invalid record count"); + return; + } + + print_field("Total record count: %d", get_be16(frame->data)); + print_field("Current record count: %d", count); + + for (i = 0; i < count; i++) + print_field("Record handle: 0x%4.4x", + get_be32(frame->data + 4 + (i * 4))); + + print_continuation(frame->data + 4 + (count * 4), + frame->size - 4 - (count * 4)); +} + +static void attr_req(const struct l2cap_frame *frame, struct tid_data *tid) +{ + uint32_t attr_bytes; + + if (frame->size < 6) { + print_text(COLOR_ERROR, "invalid size"); + packet_hexdump(frame->data, frame->size); + return; + } + + print_field("Record handle: 0x%4.4x", get_be32(frame->data)); + print_field("Max attribute bytes: %d", get_be16(frame->data + 4)); + + attr_bytes = get_bytes(frame->data + 6, frame->size - 6); + print_field("Attribute list: [len %d]", attr_bytes); + + if (attr_bytes + 6 > frame->size) { + print_text(COLOR_ERROR, "invalid attribute list length"); + packet_hexdump(frame->data, frame->size); + return; + } + + decode_data_elements(0, 2, frame->data + 6, attr_bytes, NULL); + + store_continuation(tid, frame->data + 6 + attr_bytes, + frame->size - 6 - attr_bytes); +} + +static void attr_rsp(const struct l2cap_frame *frame, struct tid_data *tid) +{ + uint16_t bytes; + + bytes = common_rsp(frame, tid); + + handle_continuation(tid, false, bytes, + frame->data + 2, frame->size - 2); + + clear_tid(tid); +} + +static void search_attr_req(const struct l2cap_frame *frame, + struct tid_data *tid) +{ + uint32_t search_bytes, attr_bytes; + + search_bytes = get_bytes(frame->data, frame->size); + print_field("Search pattern: [len %d]", search_bytes); + + if (search_bytes + 2 > frame->size) { + print_text(COLOR_ERROR, "invalid search list length"); + packet_hexdump(frame->data, frame->size); + return; + } + + decode_data_elements(0, 2, frame->data, search_bytes, NULL); + + print_field("Max record count: %d", + get_be16(frame->data + search_bytes)); + + attr_bytes = get_bytes(frame->data + search_bytes + 2, + frame->size - search_bytes - 2); + print_field("Attribute list: [len %d]", attr_bytes); + + if (search_bytes + attr_bytes > frame->size) { + print_text(COLOR_ERROR, "invalid attribute list length"); + return; + } + + decode_data_elements(0, 2, frame->data + search_bytes + 2, + attr_bytes, NULL); + + store_continuation(tid, frame->data + search_bytes + 2 + attr_bytes, + frame->size - search_bytes - 2 - attr_bytes); +} + +static void search_attr_rsp(const struct l2cap_frame *frame, + struct tid_data *tid) +{ + uint16_t bytes; + + bytes = common_rsp(frame, tid); + + handle_continuation(tid, true, bytes, frame->data + 2, frame->size - 2); + + clear_tid(tid); +} + +struct sdp_data { + uint8_t pdu; + const char *str; + void (*func) (const struct l2cap_frame *frame, struct tid_data *tid); +}; + +static const struct sdp_data sdp_table[] = { + { 0x01, "Error Response", error_rsp }, + { 0x02, "Service Search Request", service_req }, + { 0x03, "Service Search Response", service_rsp }, + { 0x04, "Service Attribute Request", attr_req }, + { 0x05, "Service Attribute Response", attr_rsp }, + { 0x06, "Service Search Attribute Request", search_attr_req }, + { 0x07, "Service Search Attribute Response", search_attr_rsp }, + { } +}; + +void sdp_packet(const struct l2cap_frame *frame) +{ + uint8_t pdu; + uint16_t tid, plen; + struct l2cap_frame sdp_frame; + struct tid_data *tid_info; + const struct sdp_data *sdp_data = NULL; + const char *pdu_color, *pdu_str; + int i; + + l2cap_frame_pull(&sdp_frame, frame, 0); + + if (!l2cap_frame_get_u8(&sdp_frame, &pdu) || + !l2cap_frame_get_be16(&sdp_frame, &tid) || + !l2cap_frame_get_be16(&sdp_frame, &plen)) { + print_text(COLOR_ERROR, "frame too short"); + packet_hexdump(frame->data, frame->size); + return; + } + + if (sdp_frame.size != plen) { + print_text(COLOR_ERROR, "invalid frame size"); + packet_hexdump(sdp_frame.data, sdp_frame.size); + return; + } + + for (i = 0; sdp_table[i].str; i++) { + if (sdp_table[i].pdu == pdu) { + sdp_data = &sdp_table[i]; + break; + } + } + + if (sdp_data) { + if (sdp_data->func) { + if (frame->in) + pdu_color = COLOR_MAGENTA; + else + pdu_color = COLOR_BLUE; + } else + pdu_color = COLOR_WHITE_BG; + pdu_str = sdp_data->str; + } else { + pdu_color = COLOR_WHITE_BG; + pdu_str = "Unknown"; + } + + print_indent(6, pdu_color, "SDP: ", pdu_str, COLOR_OFF, + " (0x%2.2x) tid %d len %d", pdu, tid, plen); + + tid_info = get_tid(tid, frame->chan); + + if (!sdp_data || !sdp_data->func || !tid_info) { + packet_hexdump(sdp_frame.data, sdp_frame.size); + return; + } + + sdp_data->func(&sdp_frame, tid_info); +} diff --git a/monitor/sdp.h b/monitor/sdp.h new file mode 100644 index 0000000..c8a9bb0 --- /dev/null +++ b/monitor/sdp.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void sdp_packet(const struct l2cap_frame *frame); diff --git a/monitor/tty.h b/monitor/tty.h new file mode 100644 index 0000000..f0ba0c5 --- /dev/null +++ b/monitor/tty.h @@ -0,0 +1,41 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2016 Intel Corporation + * + * + * 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 + +struct tty_hdr { + uint16_t data_len; + uint16_t opcode; + uint8_t flags; + uint8_t hdr_len; + uint8_t ext_hdr[0]; +} __attribute__ ((packed)); + +#define TTY_EXTHDR_COMMAND_DROPS 1 +#define TTY_EXTHDR_EVENT_DROPS 2 +#define TTY_EXTHDR_ACL_TX_DROPS 3 +#define TTY_EXTHDR_ACL_RX_DROPS 4 +#define TTY_EXTHDR_SCO_TX_DROPS 5 +#define TTY_EXTHDR_SCO_RX_DROPS 6 +#define TTY_EXTHDR_OTHER_DROPS 7 +#define TTY_EXTHDR_TS32 8 diff --git a/monitor/vendor.c b/monitor/vendor.c new file mode 100644 index 0000000..3dda2ae --- /dev/null +++ b/monitor/vendor.c @@ -0,0 +1,36 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include "packet.h" +#include "vendor.h" + +void vendor_event(uint16_t manufacturer, const void *data, uint8_t size) +{ + packet_hexdump(data, size); +} diff --git a/monitor/vendor.h b/monitor/vendor.h new file mode 100644 index 0000000..f5792b3 --- /dev/null +++ b/monitor/vendor.h @@ -0,0 +1,46 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +struct vendor_ocf { + uint16_t ocf; + const char *str; + void (*cmd_func) (const void *data, uint8_t size); + uint8_t cmd_size; + bool cmd_fixed; + void (*rsp_func) (const void *data, uint8_t size); + uint8_t rsp_size; + bool rsp_fixed; +}; + +struct vendor_evt { + uint8_t evt; + const char *str; + void (*evt_func) (const void *data, uint8_t size); + uint8_t evt_size; + bool evt_fixed; +}; + +void vendor_event(uint16_t manufacturer, const void *data, uint8_t size); diff --git a/obexd/client/bluetooth.c b/obexd/client/bluetooth.c new file mode 100644 index 0000000..0c043e0 --- /dev/null +++ b/obexd/client/bluetooth.c @@ -0,0 +1,536 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2012 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 +#endif + +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/rfcomm.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "gdbus/gdbus.h" +#include "btio/btio.h" + +#include "obexd/src/log.h" +#include "transport.h" +#include "bluetooth.h" + +#define BT_RX_MTU 32767 +#define BT_TX_MTU 32767 + +#define OBC_BT_ERROR obc_bt_error_quark() + +struct bluetooth_session { + guint id; + bdaddr_t src; + bdaddr_t dst; + uint16_t port; + sdp_session_t *sdp; + sdp_record_t *sdp_record; + GIOChannel *io; + char *service; + obc_transport_func func; + void *user_data; +}; + +static GSList *sessions = NULL; + +static GQuark obc_bt_error_quark(void) +{ + return g_quark_from_static_string("obc-bluetooth-error-quark"); +} + +static void session_destroy(struct bluetooth_session *session) +{ + DBG("%p", session); + + if (g_slist_find(sessions, session) == NULL) + return; + + sessions = g_slist_remove(sessions, session); + + if (session->io != NULL) { + g_io_channel_shutdown(session->io, TRUE, NULL); + g_io_channel_unref(session->io); + } + + if (session->sdp) + sdp_close(session->sdp); + + if (session->sdp_record) + sdp_record_free(session->sdp_record); + + g_free(session->service); + g_free(session); +} + +static void transport_callback(GIOChannel *io, GError *err, gpointer user_data) +{ + struct bluetooth_session *session = user_data; + + DBG(""); + + if (session->func) + session->func(io, err, session->user_data); + + if (err != NULL) + session_destroy(session); +} + +static GIOChannel *transport_connect(const bdaddr_t *src, const bdaddr_t *dst, + uint16_t port, BtIOConnect function, + gpointer user_data) +{ + GIOChannel *io; + GError *err = NULL; + + DBG("port %u", port); + + if (port > 31) { + io = bt_io_connect(function, user_data, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_PSM, port, + BT_IO_OPT_MODE, BT_IO_MODE_ERTM, + BT_IO_OPT_OMTU, BT_TX_MTU, + BT_IO_OPT_IMTU, BT_RX_MTU, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + } else { + io = bt_io_connect(function, user_data, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_CHANNEL, port, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + } + + if (io != NULL) + return io; + + error("%s", err->message); + g_error_free(err); + return NULL; +} + +static void search_callback(uint8_t type, uint16_t status, + uint8_t *rsp, size_t size, void *user_data) +{ + struct bluetooth_session *session = user_data; + unsigned int scanned, bytesleft = size; + int seqlen = 0; + uint8_t dataType; + uint16_t port = 0; + GError *gerr = NULL; + + if (status || type != SDP_SVC_SEARCH_ATTR_RSP) + goto failed; + + scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen); + if (!scanned || !seqlen) + goto failed; + + rsp += scanned; + bytesleft -= scanned; + do { + sdp_record_t *rec; + sdp_list_t *protos; + sdp_data_t *data; + int recsize, ch = -1; + + recsize = 0; + rec = sdp_extract_pdu(rsp, bytesleft, &recsize); + if (!rec) + break; + + if (!recsize) { + sdp_record_free(rec); + break; + } + + if (!sdp_get_access_protos(rec, &protos)) { + ch = sdp_get_proto_port(protos, RFCOMM_UUID); + sdp_list_foreach(protos, + (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(protos, NULL); + protos = NULL; + } + + data = sdp_data_get(rec, 0x0200); + /* PSM must be odd and lsb of upper byte must be 0 */ + if (data != NULL && (data->val.uint16 & 0x0101) == 0x0001) + ch = data->val.uint16; + + /* Cache the sdp record associated with the service that we + * attempt to connect. This allows reading its application + * specific service attributes. */ + if (ch > 0) { + port = ch; + session->sdp_record = rec; + break; + } + + sdp_record_free(rec); + + scanned += recsize; + rsp += recsize; + bytesleft -= recsize; + } while (scanned < size && bytesleft > 0); + + if (port == 0) + goto failed; + + session->port = port; + + g_io_channel_set_close_on_unref(session->io, FALSE); + g_io_channel_unref(session->io); + + session->io = transport_connect(&session->src, &session->dst, port, + transport_callback, session); + if (session->io != NULL) { + sdp_close(session->sdp); + session->sdp = NULL; + return; + } + +failed: + if (session->io != NULL) { + g_io_channel_shutdown(session->io, TRUE, NULL); + g_io_channel_unref(session->io); + session->io = NULL; + } + + g_set_error(&gerr, OBC_BT_ERROR, -EIO, + "Unable to find service record"); + if (session->func) + session->func(session->io, gerr, session->user_data); + + g_clear_error(&gerr); + + session_destroy(session); +} + +static gboolean process_callback(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct bluetooth_session *session = user_data; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) + return FALSE; + + if (sdp_process(session->sdp) < 0) + return FALSE; + + return TRUE; +} + +static int bt_string2uuid(uuid_t *uuid, const char *string) +{ + uint32_t data0, data4; + uint16_t data1, data2, data3, data5; + + if (sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx", + &data0, &data1, &data2, &data3, &data4, &data5) == 6) { + uint8_t val[16]; + + data0 = g_htonl(data0); + data1 = g_htons(data1); + data2 = g_htons(data2); + data3 = g_htons(data3); + data4 = g_htonl(data4); + data5 = g_htons(data5); + + memcpy(&val[0], &data0, 4); + memcpy(&val[4], &data1, 2); + memcpy(&val[6], &data2, 2); + memcpy(&val[8], &data3, 2); + memcpy(&val[10], &data4, 4); + memcpy(&val[14], &data5, 2); + + sdp_uuid128_create(uuid, val); + + return 0; + } + + return -EINVAL; +} + +static gboolean service_callback(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct bluetooth_session *session = user_data; + sdp_list_t *search, *attrid; + uint32_t range = 0x0000ffff; + GError *gerr = NULL; + uuid_t uuid; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & G_IO_ERR) + goto failed; + + if (sdp_set_notify(session->sdp, search_callback, session) < 0) + goto failed; + + if (bt_string2uuid(&uuid, session->service) < 0) + goto failed; + + sdp_uuid128_to_uuid(&uuid); + + search = sdp_list_append(NULL, &uuid); + attrid = sdp_list_append(NULL, &range); + + if (sdp_service_search_attr_async(session->sdp, + search, SDP_ATTR_REQ_RANGE, attrid) < 0) { + sdp_list_free(attrid, NULL); + sdp_list_free(search, NULL); + goto failed; + } + + sdp_list_free(attrid, NULL); + sdp_list_free(search, NULL); + + g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + process_callback, session); + + return FALSE; + +failed: + g_io_channel_shutdown(session->io, TRUE, NULL); + g_io_channel_unref(session->io); + session->io = NULL; + + g_set_error(&gerr, OBC_BT_ERROR, -EIO, + "Unable to find service record"); + if (session->func) + session->func(session->io, gerr, session->user_data); + g_clear_error(&gerr); + + session_destroy(session); + return FALSE; +} + +static sdp_session_t *service_connect(const bdaddr_t *src, const bdaddr_t *dst, + GIOFunc function, gpointer user_data) +{ + struct bluetooth_session *session = user_data; + sdp_session_t *sdp; + GIOChannel *io; + + DBG(""); + + sdp = sdp_connect(src, dst, SDP_NON_BLOCKING); + if (sdp == NULL) + return NULL; + + io = g_io_channel_unix_new(sdp_get_socket(sdp)); + if (io == NULL) { + sdp_close(sdp); + return NULL; + } + + g_io_add_watch(io, G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + function, user_data); + + session->io = io; + + return sdp; +} + +static int session_connect(struct bluetooth_session *session) +{ + int err; + + DBG("session %p", session); + + if (session->port > 0) { + session->io = transport_connect(&session->src, &session->dst, + session->port, + transport_callback, + session); + err = (session->io == NULL) ? -EINVAL : 0; + } else { + session->sdp = service_connect(&session->src, &session->dst, + service_callback, session); + err = (session->sdp == NULL) ? -ENOMEM : 0; + } + + return err; +} + +static guint bluetooth_connect(const char *source, const char *destination, + const char *service, uint16_t port, + obc_transport_func func, void *user_data) +{ + struct bluetooth_session *session; + static guint id = 0; + + DBG("src %s dest %s service %s port %u", + source, destination, service, port); + + if (destination == NULL) + return 0; + + session = g_try_malloc0(sizeof(*session)); + if (session == NULL) + return 0; + + session->id = ++id; + session->func = func; + session->port = port; + session->user_data = user_data; + + str2ba(destination, &session->dst); + str2ba(source, &session->src); + + if (session_connect(session) < 0) { + g_free(session); + return 0; + } + + session->service = g_strdup(service); + sessions = g_slist_prepend(sessions, session); + + return session->id; +} + +static void bluetooth_disconnect(guint id) +{ + GSList *l; + + DBG(""); + + for (l = sessions; l; l = l->next) { + struct bluetooth_session *session = l->data; + + if (session->id == id) { + session_destroy(session); + return; + } + } +} + +static int bluetooth_getpacketopt(GIOChannel *io, int *tx_mtu, int *rx_mtu) +{ + int sk = g_io_channel_unix_get_fd(io); + int type; + uint16_t omtu = BT_TX_MTU; + uint16_t imtu = BT_RX_MTU; + socklen_t len = sizeof(int); + + DBG(""); + + if (getsockopt(sk, SOL_SOCKET, SO_TYPE, &type, &len) < 0) + return -errno; + + if (type != SOCK_SEQPACKET) + return -EINVAL; + + if (!bt_io_get(io, NULL, BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_INVALID)) + return -EINVAL; + + if (tx_mtu) + *tx_mtu = omtu; + + if (rx_mtu) + *rx_mtu = imtu; + + return 0; +} + +static const void *bluetooth_getattribute(guint id, int attribute_id) +{ + GSList *l; + sdp_data_t *data; + + for (l = sessions; l; l = l->next) { + struct bluetooth_session *session = l->data; + + if (session->id != id) + continue; + + if (session->sdp_record == NULL) + break; + + /* Read version since UUID is already known */ + if (attribute_id == SDP_ATTR_PFILE_DESC_LIST) { + sdp_list_t *descs; + void *ret = NULL; + + if (sdp_get_profile_descs(session->sdp_record, + &descs) < 0) + return NULL; + + if (descs && descs->data) { + sdp_profile_desc_t *desc = descs->data; + ret = GINT_TO_POINTER(desc->version); + } + + sdp_list_free(descs, free); + + return ret; + } + + data = sdp_data_get(session->sdp_record, attribute_id); + if (!data) + break; + + return &data->val; + } + return NULL; +} + +static struct obc_transport bluetooth = { + .name = "Bluetooth", + .connect = bluetooth_connect, + .getpacketopt = bluetooth_getpacketopt, + .disconnect = bluetooth_disconnect, + .getattribute = bluetooth_getattribute, +}; + +int bluetooth_init(void) +{ + DBG(""); + + return obc_transport_register(&bluetooth); +} + +void bluetooth_exit(void) +{ + DBG(""); + + obc_transport_unregister(&bluetooth); +} diff --git a/obexd/client/bluetooth.h b/obexd/client/bluetooth.h new file mode 100644 index 0000000..968e131 --- /dev/null +++ b/obexd/client/bluetooth.h @@ -0,0 +1,25 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2011 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 + * + */ + +int bluetooth_init(void); +void bluetooth_exit(void); diff --git a/obexd/client/driver.c b/obexd/client/driver.c new file mode 100644 index 0000000..0dae356 --- /dev/null +++ b/obexd/client/driver.c @@ -0,0 +1,89 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include "gdbus/gdbus.h" + +#include "obexd/src/log.h" +#include "transfer.h" +#include "session.h" +#include "driver.h" + +static GSList *drivers = NULL; + +struct obc_driver *obc_driver_find(const char *pattern) +{ + GSList *l; + + for (l = drivers; l; l = l->next) { + struct obc_driver *driver = l->data; + + if (strcasecmp(pattern, driver->service) == 0) + return driver; + + if (strcasecmp(pattern, driver->uuid) == 0) + return driver; + } + + return NULL; +} + +int obc_driver_register(struct obc_driver *driver) +{ + if (!driver) { + error("Invalid driver"); + return -EINVAL; + } + + if (obc_driver_find(driver->service)) { + error("Permission denied: service %s already registered", + driver->service); + return -EPERM; + } + + DBG("driver %p service %s registered", driver, driver->service); + + drivers = g_slist_append(drivers, driver); + + return 0; +} + +void obc_driver_unregister(struct obc_driver *driver) +{ + if (!g_slist_find(drivers, driver)) { + error("Unable to unregister: No such driver %p", driver); + return; + } + + DBG("driver %p service %s unregistered", driver, driver->service); + + drivers = g_slist_remove(drivers, driver); +} diff --git a/obexd/client/driver.h b/obexd/client/driver.h new file mode 100644 index 0000000..0112219 --- /dev/null +++ b/obexd/client/driver.h @@ -0,0 +1,36 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct obc_driver { + const char *service; + const char *uuid; + void *target; + gsize target_len; + void *(*supported_features) (struct obc_session *session); + int (*probe) (struct obc_session *session); + void (*remove) (struct obc_session *session); +}; + +int obc_driver_register(struct obc_driver *driver); +void obc_driver_unregister(struct obc_driver *driver); +struct obc_driver *obc_driver_find(const char *pattern); diff --git a/obexd/client/ftp.c b/obexd/client/ftp.c new file mode 100644 index 0000000..5e30654 --- /dev/null +++ b/obexd/client/ftp.c @@ -0,0 +1,510 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include + +#include "gdbus/gdbus.h" + +#include "obexd/src/log.h" +#include "transfer.h" +#include "session.h" +#include "driver.h" +#include "ftp.h" + +#define OBEX_FTP_UUID \ + "\xF9\xEC\x7B\xC4\x95\x3C\x11\xD2\x98\x4E\x52\x54\x00\xDC\x9E\x09" +#define OBEX_FTP_UUID_LEN 16 + +#define FTP_INTERFACE "org.bluez.obex.FileTransfer1" +#define ERROR_INTERFACE "org.bluez.obex.Error" +#define FTP_UUID "00001106-0000-1000-8000-00805f9b34fb" +#define PCSUITE_UUID "00005005-0000-1000-8000-0002ee000001" + +static DBusConnection *conn = NULL; + +struct ftp_data { + struct obc_session *session; +}; + +static void async_cb(struct obc_session *session, struct obc_transfer *transfer, + GError *err, void *user_data) +{ + DBusMessage *reply, *msg = user_data; + + if (err != NULL) + reply = g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "%s", err->message); + else + reply = dbus_message_new_method_return(msg); + + g_dbus_send_message(conn, reply); + dbus_message_unref(msg); +} + +static DBusMessage *change_folder(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct ftp_data *ftp = user_data; + struct obc_session *session = ftp->session; + const char *folder; + GError *err = NULL; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &folder, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + obc_session_setpath(session, folder, async_cb, message, &err); + if (err != NULL) { + DBusMessage *reply; + reply = g_dbus_create_error(message, + ERROR_INTERFACE ".Failed", + "%s", err->message); + g_error_free(err); + return reply; + } + + dbus_message_ref(message); + + return NULL; +} + +static void xml_element(GMarkupParseContext *ctxt, + const char *element, + const char **names, + const char **values, + gpointer user_data, + GError **gerr) +{ + DBusMessageIter dict, *iter = user_data; + char *key; + int i; + + if (strcasecmp("folder", element) != 0 && strcasecmp("file", element) != 0) + return; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + g_dbus_dict_append_entry(&dict, "Type", DBUS_TYPE_STRING, &element); + + /* FIXME: User, Group, Other permission must be reviewed */ + + i = 0; + for (key = (char *) names[i]; key; key = (char *) names[++i]) { + key[0] = g_ascii_toupper(key[0]); + if (g_str_equal("Size", key) == TRUE) { + guint64 size; + size = g_ascii_strtoll(values[i], NULL, 10); + g_dbus_dict_append_entry(&dict, key, DBUS_TYPE_UINT64, + &size); + } else + g_dbus_dict_append_entry(&dict, key, DBUS_TYPE_STRING, + &values[i]); + } + + dbus_message_iter_close_container(iter, &dict); +} + +static const GMarkupParser parser = { + xml_element, + NULL, + NULL, + NULL, + NULL +}; + +static void list_folder_callback(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + DBusMessage *msg = user_data; + GMarkupParseContext *ctxt; + DBusMessage *reply; + DBusMessageIter iter, array; + char *contents; + size_t size; + + reply = dbus_message_new_method_return(msg); + + if (obc_transfer_get_contents(transfer, &contents, &size) < 0) + goto done; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_ARRAY_AS_STRING + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); + ctxt = g_markup_parse_context_new(&parser, 0, &array, NULL); + g_markup_parse_context_parse(ctxt, contents, size, NULL); + g_markup_parse_context_free(ctxt); + dbus_message_iter_close_container(&iter, &array); + g_free(contents); + +done: + g_dbus_send_message(conn, reply); + dbus_message_unref(msg); +} + +static DBusMessage *create_folder(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct ftp_data *ftp = user_data; + struct obc_session *session = ftp->session; + const char *folder; + GError *err = NULL; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &folder, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + obc_session_mkdir(session, folder, async_cb, message, &err); + if (err != NULL) { + DBusMessage *reply; + reply = g_dbus_create_error(message, + ERROR_INTERFACE ".Failed", + "%s", err->message); + g_error_free(err); + return reply; + } + + dbus_message_ref(message); + + return NULL; +} + +static DBusMessage *list_folder(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct ftp_data *ftp = user_data; + struct obc_session *session = ftp->session; + struct obc_transfer *transfer; + GError *err = NULL; + DBusMessage *reply; + + transfer = obc_transfer_get("x-obex/folder-listing", NULL, NULL, &err); + if (transfer == NULL) + goto fail; + + if (obc_session_queue(session, transfer, list_folder_callback, + message, &err)) { + dbus_message_ref(message); + return NULL; + } + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static DBusMessage *get_file(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct ftp_data *ftp = user_data; + struct obc_session *session = ftp->session; + struct obc_transfer *transfer; + const char *target_file, *source_file; + GError *err = NULL; + DBusMessage *reply; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &target_file, + DBUS_TYPE_STRING, &source_file, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + transfer = obc_transfer_get(NULL, source_file, target_file, &err); + if (transfer == NULL) + goto fail; + + if (!obc_session_queue(session, transfer, NULL, NULL, &err)) + goto fail; + + return obc_transfer_create_dbus_reply(transfer, message); + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static DBusMessage *put_file(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct ftp_data *ftp = user_data; + struct obc_session *session = ftp->session; + struct obc_transfer *transfer; + char *sourcefile, *targetfile; + GError *err = NULL; + DBusMessage *reply; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &sourcefile, + DBUS_TYPE_STRING, &targetfile, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + + transfer = obc_transfer_put(NULL, targetfile, sourcefile, NULL, 0, + &err); + if (transfer == NULL) + goto fail; + + if (!obc_session_queue(session, transfer, NULL, NULL, &err)) + goto fail; + + return obc_transfer_create_dbus_reply(transfer, message); + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static DBusMessage *copy_file(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct ftp_data *ftp = user_data; + struct obc_session *session = ftp->session; + const char *filename, *destname; + GError *err = NULL; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &filename, + DBUS_TYPE_STRING, &destname, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + obc_session_copy(session, filename, destname, async_cb, message, &err); + if (err != NULL) { + DBusMessage *reply; + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", + "%s", err->message); + g_error_free(err); + return reply; + } + + dbus_message_ref(message); + + return NULL; +} + +static DBusMessage *move_file(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct ftp_data *ftp = user_data; + struct obc_session *session = ftp->session; + const char *filename, *destname; + GError *err = NULL; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &filename, + DBUS_TYPE_STRING, &destname, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + obc_session_move(session, filename, destname, async_cb, message, &err); + if (err != NULL) { + DBusMessage *reply; + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", + "%s", err->message); + g_error_free(err); + return reply; + } + + dbus_message_ref(message); + + return NULL; +} + +static DBusMessage *delete(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct ftp_data *ftp = user_data; + struct obc_session *session = ftp->session; + const char *file; + GError *err = NULL; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &file, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + obc_session_delete(session, file, async_cb, message, &err); + if (err != NULL) { + DBusMessage *reply; + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", + "%s", err->message); + g_error_free(err); + return reply; + } + + dbus_message_ref(message); + + return NULL; +} + +static const GDBusMethodTable ftp_methods[] = { + { GDBUS_ASYNC_METHOD("ChangeFolder", + GDBUS_ARGS({ "folder", "s" }), NULL, change_folder) }, + { GDBUS_ASYNC_METHOD("CreateFolder", + GDBUS_ARGS({ "folder", "s" }), NULL, create_folder) }, + { GDBUS_ASYNC_METHOD("ListFolder", + NULL, GDBUS_ARGS({ "folderinfo", "aa{sv}" }), list_folder) }, + { GDBUS_METHOD("GetFile", + GDBUS_ARGS({ "targetfile", "s" }, { "sourcefile", "s" }), + GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), + get_file) }, + { GDBUS_METHOD("PutFile", + GDBUS_ARGS({ "sourcefile", "s" }, { "targetfile", "s" }), + GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), + put_file) }, + { GDBUS_ASYNC_METHOD("CopyFile", + GDBUS_ARGS({ "sourcefile", "s" }, { "targetfile", "s" }), NULL, + copy_file) }, + { GDBUS_ASYNC_METHOD("MoveFile", + GDBUS_ARGS({ "sourcefile", "s" }, { "targetfile", "s" }), NULL, + move_file) }, + { GDBUS_ASYNC_METHOD("Delete", + GDBUS_ARGS({ "file", "s" }), NULL, delete) }, + { } +}; + +static void ftp_free(void *data) +{ + struct ftp_data *ftp = data; + + obc_session_unref(ftp->session); + g_free(ftp); +} + +static int ftp_probe(struct obc_session *session) +{ + struct ftp_data *ftp; + const char *path; + + path = obc_session_get_path(session); + + DBG("%s", path); + + ftp = g_try_new0(struct ftp_data, 1); + if (!ftp) + return -ENOMEM; + + ftp->session = obc_session_ref(session); + + if (!g_dbus_register_interface(conn, path, FTP_INTERFACE, ftp_methods, + NULL, NULL, ftp, ftp_free)) { + ftp_free(ftp); + return -ENOMEM; + } + + return 0; +} + +static void ftp_remove(struct obc_session *session) +{ + const char *path = obc_session_get_path(session); + + DBG("%s", path); + + g_dbus_unregister_interface(conn, path, FTP_INTERFACE); +} + +static struct obc_driver ftp = { + .service = "FTP", + .uuid = FTP_UUID, + .target = OBEX_FTP_UUID, + .target_len = OBEX_FTP_UUID_LEN, + .probe = ftp_probe, + .remove = ftp_remove +}; + +static struct obc_driver pcsuite = { + .service = "PCSUITE", + .uuid = PCSUITE_UUID, + .target = OBEX_FTP_UUID, + .target_len = OBEX_FTP_UUID_LEN, + .probe = ftp_probe, + .remove = ftp_remove +}; + +int ftp_init(void) +{ + int err; + + DBG(""); + + conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); + if (!conn) + return -EIO; + + err = obc_driver_register(&ftp); + if (err < 0) + goto failed; + + err = obc_driver_register(&pcsuite); + if (err < 0) { + obc_driver_unregister(&ftp); + goto failed; + } + + return 0; + +failed: + dbus_connection_unref(conn); + conn = NULL; + return err; +} + +void ftp_exit(void) +{ + DBG(""); + + dbus_connection_unref(conn); + conn = NULL; + + obc_driver_unregister(&ftp); + obc_driver_unregister(&pcsuite); +} diff --git a/obexd/client/ftp.h b/obexd/client/ftp.h new file mode 100644 index 0000000..3d90968 --- /dev/null +++ b/obexd/client/ftp.h @@ -0,0 +1,25 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int ftp_init(void); +void ftp_exit(void); diff --git a/obexd/client/manager.c b/obexd/client/manager.c new file mode 100644 index 0000000..fbcad6d --- /dev/null +++ b/obexd/client/manager.c @@ -0,0 +1,316 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" + +#include "obexd/src/log.h" +#include "obexd/src/manager.h" +#include "transfer.h" +#include "session.h" +#include "bluetooth.h" +#include "opp.h" +#include "ftp.h" +#include "pbap.h" +#include "sync.h" +#include "map.h" +#include "manager.h" + +#define CLIENT_INTERFACE "org.bluez.obex.Client1" +#define ERROR_INTERFACE "org.bluez.obex.Error" +#define CLIENT_PATH "/org/bluez/obex" + +struct send_data { + DBusConnection *connection; + DBusMessage *message; +}; + +static GSList *sessions = NULL; + +static void shutdown_session(struct obc_session *session) +{ + obc_session_shutdown(session); + obc_session_unref(session); +} + +static void release_session(struct obc_session *session) +{ + sessions = g_slist_remove(sessions, session); + shutdown_session(session); +} + +static void unregister_session(void *data) +{ + struct obc_session *session = data; + + if (g_slist_find(sessions, session) == NULL) + return; + + sessions = g_slist_remove(sessions, session); + obc_session_unref(session); +} + +static void create_callback(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct send_data *data = user_data; + const char *path; + + if (err != NULL) { + DBusMessage *error = g_dbus_create_error(data->message, + ERROR_INTERFACE ".Failed", + "%s", err->message); + g_dbus_send_message(data->connection, error); + shutdown_session(session); + goto done; + } + + + path = obc_session_register(session, unregister_session); + if (path == NULL) { + DBusMessage *error = g_dbus_create_error(data->message, + ERROR_INTERFACE ".Failed", + NULL); + g_dbus_send_message(data->connection, error); + shutdown_session(session); + goto done; + } + + sessions = g_slist_append(sessions, session); + g_dbus_send_reply(data->connection, data->message, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + +done: + dbus_message_unref(data->message); + dbus_connection_unref(data->connection); + g_free(data); +} + +static int parse_device_dict(DBusMessageIter *iter, + const char **source, const char **target, uint8_t *channel) +{ + while (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter entry, value; + const char *key; + + dbus_message_iter_recurse(iter, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + switch (dbus_message_iter_get_arg_type(&value)) { + case DBUS_TYPE_STRING: + if (g_str_equal(key, "Source") == TRUE) + dbus_message_iter_get_basic(&value, source); + else if (g_str_equal(key, "Target") == TRUE) + dbus_message_iter_get_basic(&value, target); + break; + case DBUS_TYPE_BYTE: + if (g_str_equal(key, "Channel") == TRUE) + dbus_message_iter_get_basic(&value, channel); + break; + } + + dbus_message_iter_next(iter); + } + + return 0; +} + +static struct obc_session *find_session(const char *path) +{ + GSList *l; + + for (l = sessions; l; l = l->next) { + struct obc_session *session = l->data; + + if (g_strcmp0(obc_session_get_path(session), path) == 0) + return session; + } + + return NULL; +} + +static DBusMessage *create_session(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + DBusMessageIter iter, dict; + struct obc_session *session; + struct send_data *data; + const char *source = NULL, *dest = NULL, *target = NULL; + uint8_t channel = 0; + + dbus_message_iter_init(message, &iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + dbus_message_iter_get_basic(&iter, &dest); + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + dbus_message_iter_recurse(&iter, &dict); + + parse_device_dict(&dict, &source, &target, &channel); + if (dest == NULL || target == NULL) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + data = g_try_malloc0(sizeof(*data)); + if (data == NULL) + return g_dbus_create_error(message, + ERROR_INTERFACE ".Error.NoMemory", NULL); + + data->connection = dbus_connection_ref(connection); + data->message = dbus_message_ref(message); + + session = obc_session_create(source, dest, target, channel, + dbus_message_get_sender(message), + create_callback, data); + if (session != NULL) { + return NULL; + } + + dbus_message_unref(data->message); + dbus_connection_unref(data->connection); + g_free(data); + + return g_dbus_create_error(message, ERROR_INTERFACE ".Failed", NULL); +} + +static DBusMessage *remove_session(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct obc_session *session; + const char *sender, *path; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + session = find_session(path); + if (session == NULL) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + sender = dbus_message_get_sender(message); + if (g_str_equal(sender, obc_session_get_owner(session)) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".NotAuthorized", + "Not Authorized"); + + release_session(session); + + return dbus_message_new_method_return(message); +} + +static const GDBusMethodTable client_methods[] = { + { GDBUS_ASYNC_METHOD("CreateSession", + GDBUS_ARGS({ "destination", "s" }, { "args", "a{sv}" }), + GDBUS_ARGS({ "session", "o" }), create_session) }, + { GDBUS_ASYNC_METHOD("RemoveSession", + GDBUS_ARGS({ "session", "o" }), NULL, remove_session) }, + { } +}; + +static DBusConnection *conn = NULL; + +static struct obc_module { + const char *name; + int (*init) (void); + void (*exit) (void); +} modules[] = { + { "bluetooth", bluetooth_init, bluetooth_exit }, + { "opp", opp_init, opp_exit }, + { "ftp", ftp_init, ftp_exit }, + { "pbap", pbap_init, pbap_exit }, + { "sync", sync_init, sync_exit }, + { "map", map_init, map_exit }, + { } +}; + +int client_manager_init(void) +{ + DBusError derr; + struct obc_module *module; + + dbus_error_init(&derr); + + conn = manager_dbus_get_connection(); + if (conn == NULL) { + error("Can't get client D-Bus connection"); + return -1; + } + + if (g_dbus_register_interface(conn, CLIENT_PATH, CLIENT_INTERFACE, + client_methods, NULL, NULL, + NULL, NULL) == FALSE) { + error("Can't register client interface"); + dbus_connection_unref(conn); + conn = NULL; + return -1; + } + + for (module = modules; module && module->init; module++) { + if (module->init() < 0) + continue; + + DBG("Module %s loaded", module->name); + } + + return 0; +} + +void client_manager_exit(void) +{ + struct obc_module *module; + + if (conn == NULL) + return; + + for (module = modules; module && module->exit; module++) + module->exit(); + + g_dbus_unregister_interface(conn, CLIENT_PATH, CLIENT_INTERFACE); + + dbus_connection_unref(conn); +} diff --git a/obexd/client/manager.h b/obexd/client/manager.h new file mode 100644 index 0000000..e4068de --- /dev/null +++ b/obexd/client/manager.h @@ -0,0 +1,25 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int client_manager_init(void); +void client_manager_exit(void); diff --git a/obexd/client/map-event.c b/obexd/client/map-event.c new file mode 100644 index 0000000..e164e86 --- /dev/null +++ b/obexd/client/map-event.c @@ -0,0 +1,113 @@ +/* + * + * OBEX + * + * Copyright (C) 2013 BMW Car IT GmbH. 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 +#endif + +#include +#include +#include +#include + +#include "gdbus/gdbus.h" + +#include "obexd/src/log.h" +#include "map-event.h" + +#include "transfer.h" +#include "session.h" + +static GSList *handlers; + +struct mns_handler { + int mas_id; + struct obc_session *session; + map_event_cb cb; + void *user_data; +}; + +static struct mns_handler *find_handler(int mas_id, const char *device) +{ + GSList *list; + + for (list = handlers; list; list = list->next) { + struct mns_handler *handler = list->data; + + if (mas_id != handler->mas_id) + continue; + + if (g_str_equal(device, + obc_session_get_destination(handler->session))) + return handler; + } + + return NULL; +} + +bool map_register_event_handler(struct obc_session *session, + int mas_id, map_event_cb cb, + void *user_data) +{ + struct mns_handler *handler; + + handler = find_handler(mas_id, obc_session_get_destination(session)); + if (handler != NULL) + return FALSE; + + handler = g_new0(struct mns_handler, 1); + handler->mas_id = mas_id; + handler->session = session; + handler->cb = cb; + handler->user_data = user_data; + + handlers = g_slist_prepend(handlers, handler); + DBG("Handler %p for %s:%d registered", handler, + obc_session_get_destination(session), mas_id); + + return TRUE; +} + +void map_unregister_event_handler(struct obc_session *session, int mas_id) +{ + struct mns_handler *handler; + + handler = find_handler(mas_id, obc_session_get_destination(session)); + if (handler == NULL) + return; + + handlers = g_slist_remove(handlers, handler); + DBG("Handler %p for %s:%d unregistered", handler, + obc_session_get_destination(session), mas_id); + g_free(handler); +} + +void map_dispatch_event(int mas_id, const char *device, + struct map_event *event) +{ + struct mns_handler *handler; + + handler = find_handler(mas_id, device); + if (handler) + handler->cb(event, handler->user_data); +} diff --git a/obexd/client/map-event.h b/obexd/client/map-event.h new file mode 100644 index 0000000..5414b26 --- /dev/null +++ b/obexd/client/map-event.h @@ -0,0 +1,71 @@ +/* + * + * OBEX + * + * Copyright (C) 2013 BMW Car IT GmbH. 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 + * + */ + +struct obc_session; + +enum map_event_type { + MAP_ET_NEW_MESSAGE, + MAP_ET_DELIVERY_SUCCESS, + MAP_ET_SENDING_SUCCESS, + MAP_ET_DELIVERY_FAILURE, + MAP_ET_SENDING_FAILURE, + MAP_ET_MEMORY_FULL, + MAP_ET_MEMORY_AVAILABLE, + MAP_ET_MESSAGE_DELETED, + MAP_ET_MESSAGE_SHIFT +}; + +struct map_event { + enum map_event_type type; + uint64_t handle; + char *folder; + char *old_folder; + char *msg_type; + char *datetime; + char *subject; + char *sender_name; + char *priority; +}; + +/* Handle notification in map client. + * + * event: Event report. + * + * Callback shall be called for every received event. + */ +typedef void (*map_event_cb) (struct map_event *event, void *user_data); + +/* Registers client notification handler callback for events that are + * addressed to the given mas instance id for the given device. + */ +bool map_register_event_handler(struct obc_session *session, int mas_id, + map_event_cb cb, void *user_data); + +/* Unregisters client notification handler callback. + */ +void map_unregister_event_handler(struct obc_session *session, int mas_id); + +/* Dispatch notification to a registered notification handler callback. + */ +void map_dispatch_event(int mas_id, const char *device, + struct map_event *event); diff --git a/obexd/client/map.c b/obexd/client/map.c new file mode 100644 index 0000000..550c5af --- /dev/null +++ b/obexd/client/map.c @@ -0,0 +1,2100 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2011 Bartosz Szatkowski for Comarch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/sdp.h" + +#include "gobex/gobex-apparam.h" +#include "gdbus/gdbus.h" + +#include "obexd/src/log.h" +#include "obexd/src/map_ap.h" +#include "map-event.h" + +#include "map.h" +#include "transfer.h" +#include "session.h" +#include "driver.h" + +#define OBEX_MAS_UUID \ + "\xBB\x58\x2B\x40\x42\x0C\x11\xDB\xB0\xDE\x08\x00\x20\x0C\x9A\x66" +#define OBEX_MAS_UUID_LEN 16 + +#define MAP_INTERFACE "org.bluez.obex.MessageAccess1" +#define MAP_MSG_INTERFACE "org.bluez.obex.Message1" +#define ERROR_INTERFACE "org.bluez.obex.Error" +#define MAS_UUID "00001132-0000-1000-8000-00805f9b34fb" + +#define DEFAULT_COUNT 1024 +#define DEFAULT_OFFSET 0 + +#define CHARSET_NATIVE 0 +#define CHARSET_UTF8 1 + +static const char * const filter_list[] = { + "subject", + "timestamp", + "sender", + "sender-address", + "recipient", + "recipient-address", + "type", + "size", + "status", + "text", + "attachment", + "priority", + "read", + "sent", + "protected", + "replyto", + NULL +}; + +#define FILTER_BIT_MAX 15 +#define FILTER_ALL 0x0000FFFF + +#define FILTER_READ_STATUS_NONE 0x00 +#define FILTER_READ_STATUS_ONLY_UNREAD 0x01 +#define FILTER_READ_STATUS_ONLY_READ 0x02 + +#define FILTER_PRIORITY_NONE 0x00 +#define FILTER_PRIORITY_ONLY_HIGH 0x01 +#define FILTER_PRIORITY_ONLY_NONHIGH 0x02 + +#define STATUS_READ 0 +#define STATUS_DELETE 1 +#define FILLER_BYTE 0x30 + +struct map_data { + struct obc_session *session; + GHashTable *messages; + int16_t mas_instance_id; + uint8_t supported_message_types; + uint32_t supported_features; +}; + +struct pending_request { + struct map_data *map; + DBusMessage *msg; + char *folder; +}; + +#define MAP_MSG_FLAG_PRIORITY 0x01 +#define MAP_MSG_FLAG_READ 0x02 +#define MAP_MSG_FLAG_SENT 0x04 +#define MAP_MSG_FLAG_PROTECTED 0x08 +#define MAP_MSG_FLAG_TEXT 0x10 + +struct map_msg { + struct map_data *data; + char *path; + uint64_t handle; + char *subject; + char *timestamp; + char *sender; + char *sender_address; + char *replyto; + char *recipient; + char *recipient_address; + char *type; + uint64_t size; + char *status; + uint64_t attachment_size; + uint8_t flags; + char *folder; + GDBusPendingPropertySet pending; +}; + +struct map_parser { + struct pending_request *request; + DBusMessageIter *iter; +}; + +static DBusConnection *conn = NULL; + +static struct pending_request *pending_request_new(struct map_data *map, + DBusMessage *message) +{ + struct pending_request *p; + + p = g_new0(struct pending_request, 1); + p->map = map; + p->msg = dbus_message_ref(message); + + return p; +} + +static void pending_request_free(struct pending_request *p) +{ + dbus_message_unref(p->msg); + + g_free(p->folder); + g_free(p); +} + +static void simple_cb(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct pending_request *request = user_data; + DBusMessage *reply; + + if (err != NULL) + reply = g_dbus_create_error(request->msg, + ERROR_INTERFACE ".Failed", + "%s", err->message); + else + reply = dbus_message_new_method_return(request->msg); + + g_dbus_send_message(conn, reply); + pending_request_free(request); +} + +static DBusMessage *map_setpath(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct map_data *map = user_data; + const char *folder; + struct pending_request *request; + GError *err = NULL; + + if (dbus_message_get_args(message, NULL, DBUS_TYPE_STRING, &folder, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", + NULL); + + request = pending_request_new(map, message); + + obc_session_setpath(map->session, folder, simple_cb, request, &err); + if (err != NULL) { + DBusMessage *reply; + reply = g_dbus_create_error(message, + ERROR_INTERFACE ".Failed", + "%s", err->message); + g_error_free(err); + pending_request_free(request); + return reply; + } + + return NULL; +} + +static void folder_element(GMarkupParseContext *ctxt, const char *element, + const char **names, const char **values, + gpointer user_data, GError **gerr) +{ + DBusMessageIter dict, *iter = user_data; + const char *key; + int i; + + if (strcasecmp("folder", element) != 0) + return; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + for (i = 0, key = names[i]; key; key = names[++i]) { + if (strcasecmp("name", key) == 0) + g_dbus_dict_append_entry(&dict, "Name", + DBUS_TYPE_STRING, + &values[i]); + } + + dbus_message_iter_close_container(iter, &dict); +} + +static const GMarkupParser folder_parser = { + folder_element, + NULL, + NULL, + NULL, + NULL +}; + +static void folder_listing_cb(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct pending_request *request = user_data; + GMarkupParseContext *ctxt; + DBusMessage *reply; + DBusMessageIter iter, array; + char *contents; + size_t size; + int perr; + + if (err != NULL) { + reply = g_dbus_create_error(request->msg, + ERROR_INTERFACE ".Failed", + "%s", err->message); + goto done; + } + + perr = obc_transfer_get_contents(transfer, &contents, &size); + if (perr < 0) { + reply = g_dbus_create_error(request->msg, + ERROR_INTERFACE ".Failed", + "Error reading contents: %s", + strerror(-perr)); + goto done; + } + + reply = dbus_message_new_method_return(request->msg); + if (reply == NULL) { + g_free(contents); + goto clean; + } + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_ARRAY_AS_STRING + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &array); + ctxt = g_markup_parse_context_new(&folder_parser, 0, &array, NULL); + g_markup_parse_context_parse(ctxt, contents, size, NULL); + g_markup_parse_context_free(ctxt); + dbus_message_iter_close_container(&iter, &array); + g_free(contents); + +done: + g_dbus_send_message(conn, reply); +clean: + pending_request_free(request); +} + +static DBusMessage *get_folder_listing(struct map_data *map, + DBusMessage *message, + GObexApparam *apparam) +{ + struct pending_request *request; + struct obc_transfer *transfer; + GError *err = NULL; + DBusMessage *reply; + + transfer = obc_transfer_get("x-obex/folder-listing", NULL, NULL, &err); + if (transfer == NULL) { + g_obex_apparam_free(apparam); + goto fail; + } + + obc_transfer_set_apparam(transfer, apparam); + + request = pending_request_new(map, message); + + if (!obc_session_queue(map->session, transfer, folder_listing_cb, + request, &err)) { + pending_request_free(request); + goto fail; + } + + return NULL; + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static GObexApparam *parse_offset(GObexApparam *apparam, DBusMessageIter *iter) +{ + guint16 num; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) + return NULL; + + dbus_message_iter_get_basic(iter, &num); + + return g_obex_apparam_set_uint16(apparam, MAP_AP_STARTOFFSET, num); +} + +static GObexApparam *parse_max_count(GObexApparam *apparam, + DBusMessageIter *iter) +{ + guint16 num; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) + return NULL; + + dbus_message_iter_get_basic(iter, &num); + + return g_obex_apparam_set_uint16(apparam, MAP_AP_MAXLISTCOUNT, num); +} + +static GObexApparam *parse_folder_filters(GObexApparam *apparam, + DBusMessageIter *iter) +{ + DBusMessageIter array; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return NULL; + + dbus_message_iter_recurse(iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + + dbus_message_iter_recurse(&array, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + if (strcasecmp(key, "Offset") == 0) { + if (parse_offset(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "MaxCount") == 0) { + if (parse_max_count(apparam, &value) == NULL) + return NULL; + } + + dbus_message_iter_next(&array); + } + + return apparam; +} + +static DBusMessage *map_list_folders(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct map_data *map = user_data; + GObexApparam *apparam; + DBusMessageIter args; + + dbus_message_iter_init(message, &args); + + apparam = g_obex_apparam_set_uint16(NULL, MAP_AP_MAXLISTCOUNT, + DEFAULT_COUNT); + apparam = g_obex_apparam_set_uint16(apparam, MAP_AP_STARTOFFSET, + DEFAULT_OFFSET); + + if (parse_folder_filters(apparam, &args) == NULL) { + g_obex_apparam_free(apparam); + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + } + + return get_folder_listing(map, message, apparam); +} + +static void map_msg_free(void *data) +{ + struct map_msg *msg = data; + + g_free(msg->path); + g_free(msg->subject); + g_free(msg->folder); + g_free(msg->timestamp); + g_free(msg->sender); + g_free(msg->sender_address); + g_free(msg->replyto); + g_free(msg->recipient); + g_free(msg->recipient_address); + g_free(msg->type); + g_free(msg->status); + g_free(msg); +} + +static DBusMessage *map_msg_get(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct map_msg *msg = user_data; + struct obc_transfer *transfer; + const char *target_file; + gboolean attachment; + GError *err = NULL; + DBusMessage *reply; + GObexApparam *apparam; + char handle[17]; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &target_file, + DBUS_TYPE_BOOLEAN, &attachment, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + snprintf(handle, sizeof(handle), "%" PRIx64, msg->handle); + + transfer = obc_transfer_get("x-bt/message", handle, target_file, &err); + if (transfer == NULL) + goto fail; + + apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_ATTACHMENT, + attachment); + apparam = g_obex_apparam_set_uint8(apparam, MAP_AP_CHARSET, + CHARSET_UTF8); + + obc_transfer_set_apparam(transfer, apparam); + + if (!obc_session_queue(msg->data->session, transfer, NULL, NULL, &err)) + goto fail; + + return obc_transfer_create_dbus_reply(transfer, message); + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static void set_message_status_cb(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct map_msg *msg = user_data; + + if (err != NULL) { + g_dbus_pending_property_error(msg->pending, + ERROR_INTERFACE ".Failed", + "%s", err->message); + goto done; + } + + g_dbus_pending_property_success(msg->pending); + +done: + msg->pending = 0; +} + +static gboolean get_folder(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->folder); + + return TRUE; +} + +static gboolean subject_exists(const GDBusPropertyTable *property, void *data) +{ + struct map_msg *msg = data; + + return msg->subject != NULL; +} + +static gboolean get_subject(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->subject); + + return TRUE; +} + +static gboolean timestamp_exists(const GDBusPropertyTable *property, void *data) +{ + struct map_msg *msg = data; + + return msg->timestamp != NULL; +} + +static gboolean get_timestamp(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->timestamp); + + return TRUE; +} + +static gboolean sender_exists(const GDBusPropertyTable *property, void *data) +{ + struct map_msg *msg = data; + + return msg->sender != NULL; +} + +static gboolean get_sender(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->sender); + + return TRUE; +} + +static gboolean sender_address_exists(const GDBusPropertyTable *property, + void *data) +{ + struct map_msg *msg = data; + + return msg->sender_address != NULL; +} + +static gboolean get_sender_address(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &msg->sender_address); + + return TRUE; +} + +static gboolean replyto_exists(const GDBusPropertyTable *property, void *data) +{ + struct map_msg *msg = data; + + return msg->replyto != NULL; +} + +static gboolean get_replyto(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->replyto); + + return TRUE; +} + +static gboolean recipient_exists(const GDBusPropertyTable *property, void *data) +{ + struct map_msg *msg = data; + + return msg->recipient != NULL; +} + +static gboolean get_recipient(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->recipient); + + return TRUE; +} + +static gboolean recipient_address_exists(const GDBusPropertyTable *property, + void *data) +{ + struct map_msg *msg = data; + + return msg->recipient_address != NULL; +} + +static gboolean get_recipient_address(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &msg->recipient_address); + + return TRUE; +} + +static gboolean type_exists(const GDBusPropertyTable *property, void *data) +{ + struct map_msg *msg = data; + + return msg->type != NULL; +} + +static gboolean get_type(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->type); + + return TRUE; +} + +static gboolean get_size(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &msg->size); + + return TRUE; +} + +static gboolean reception_status_exists(const GDBusPropertyTable *property, + void *data) +{ + struct map_msg *msg = data; + + return msg->status != NULL; +} + +static gboolean get_reception_status(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &msg->status); + + return TRUE; +} + +static gboolean get_attachment_size(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct map_msg *msg = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, + &msg->attachment_size); + + return TRUE; +} + +static gboolean get_flag(const GDBusPropertyTable *property, + DBusMessageIter *iter, uint8_t flag, + void *data) +{ + struct map_msg *msg = data; + dbus_bool_t value = (msg->flags & flag) != 0; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); + + return TRUE; +} + +static gboolean get_priority(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + return get_flag(property, iter, MAP_MSG_FLAG_PRIORITY, data); +} + +static gboolean get_read(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + return get_flag(property, iter, MAP_MSG_FLAG_READ, data); +} + +static gboolean get_sent(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + return get_flag(property, iter, MAP_MSG_FLAG_SENT, data); +} + +static gboolean get_protected(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + return get_flag(property, iter, MAP_MSG_FLAG_PROTECTED, data); +} + +static gboolean get_text(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + return get_flag(property, iter, MAP_MSG_FLAG_TEXT, data); +} + +static void set_status(const GDBusPropertyTable *property, + DBusMessageIter *iter, GDBusPendingPropertySet id, + uint8_t status, void *data) +{ + struct map_msg *msg = data; + struct obc_transfer *transfer; + gboolean value; + GError *err = NULL; + GObexApparam *apparam; + char contents[1]; + char handle[17]; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(iter, &value); + + contents[0] = FILLER_BYTE; + + snprintf(handle, sizeof(handle), "%" PRIx64, msg->handle); + + transfer = obc_transfer_put("x-bt/messageStatus", handle, NULL, + contents, sizeof(contents), &err); + if (transfer == NULL) + goto fail; + + apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_STATUSINDICATOR, + status); + apparam = g_obex_apparam_set_uint8(apparam, MAP_AP_STATUSVALUE, + value); + obc_transfer_set_apparam(transfer, apparam); + + if (!obc_session_queue(msg->data->session, transfer, + set_message_status_cb, msg, &err)) + goto fail; + + msg->pending = id; + return; + +fail: + g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); +} + +static void set_read(const GDBusPropertyTable *property, + DBusMessageIter *iter, GDBusPendingPropertySet id, + void *data) +{ + set_status(property, iter, id, STATUS_READ, data); +} + +static void set_deleted(const GDBusPropertyTable *property, + DBusMessageIter *iter, GDBusPendingPropertySet id, + void *data) +{ + set_status(property, iter, id, STATUS_DELETE, data); +} + +static const GDBusMethodTable map_msg_methods[] = { + { GDBUS_METHOD("Get", + GDBUS_ARGS({ "targetfile", "s" }, + { "attachment", "b" }), + GDBUS_ARGS({ "transfer", "o" }, + { "properties", "a{sv}" }), + map_msg_get) }, + { } +}; + +static const GDBusPropertyTable map_msg_properties[] = { + { "Folder", "s", get_folder }, + { "Subject", "s", get_subject, NULL, subject_exists }, + { "Timestamp", "s", get_timestamp, NULL, timestamp_exists }, + { "Sender", "s", get_sender, NULL, sender_exists }, + { "SenderAddress", "s", get_sender_address, NULL, + sender_address_exists }, + { "ReplyTo", "s", get_replyto, NULL, replyto_exists }, + { "Recipient", "s", get_recipient, NULL, recipient_exists }, + { "RecipientAddress", "s", get_recipient_address, NULL, + recipient_address_exists }, + { "Type", "s", get_type, NULL, type_exists }, + { "Size", "t", get_size }, + { "Text", "b", get_text }, + { "Status", "s", get_reception_status, NULL, reception_status_exists }, + { "AttachmentSize", "t", get_attachment_size }, + { "Priority", "b", get_priority }, + { "Read", "b", get_read, set_read }, + { "Sent", "b", get_sent }, + { "Protected", "b", get_protected }, + { "Deleted", "b", NULL, set_deleted }, + { } +}; + +static void parse_type(struct map_msg *msg, const char *value) +{ + const char *type = NULL; + + if (strcasecmp(value, "SMS_GSM") == 0) + type = "sms-gsm"; + else if (strcasecmp(value, "SMS_CDMA") == 0) + type = "sms-cdma"; + else if (strcasecmp(value, "EMAIL") == 0) + type = "email"; + else if (strcasecmp(value, "MMS") == 0) + type = "mms"; + + if (g_strcmp0(msg->type, type) == 0) + return; + + g_free(msg->type); + msg->type = g_strdup(type); + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Type"); +} + +static struct map_msg *map_msg_create(struct map_data *data, uint64_t handle, + const char *folder, const char *type) +{ + struct map_msg *msg; + + msg = g_new0(struct map_msg, 1); + msg->data = data; + msg->handle = handle; + msg->path = g_strdup_printf("%s/message%" PRIu64, + obc_session_get_path(data->session), + msg->handle); + msg->folder = g_strdup(folder); + + if (!g_dbus_register_interface(conn, msg->path, MAP_MSG_INTERFACE, + map_msg_methods, NULL, + map_msg_properties, + msg, map_msg_free)) { + map_msg_free(msg); + return NULL; + } + + g_hash_table_insert(data->messages, &msg->handle, msg); + + if (type) + parse_type(msg, type); + + return msg; +} + +static void parse_subject(struct map_msg *msg, const char *value) +{ + if (g_strcmp0(msg->subject, value) == 0) + return; + + g_free(msg->subject); + msg->subject = g_strdup(value); + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Subject"); +} + +static void parse_datetime(struct map_msg *msg, const char *value) +{ + if (g_strcmp0(msg->timestamp, value) == 0) + return; + + g_free(msg->timestamp); + msg->timestamp = g_strdup(value); + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Timestamp"); +} + +static void parse_sender(struct map_msg *msg, const char *value) +{ + if (g_strcmp0(msg->sender, value) == 0) + return; + + g_free(msg->sender); + msg->sender = g_strdup(value); + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Sender"); +} + +static void parse_sender_address(struct map_msg *msg, const char *value) +{ + if (g_strcmp0(msg->sender_address, value) == 0) + return; + + g_free(msg->sender_address); + msg->sender_address = g_strdup(value); + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "SenderAddress"); +} + +static void parse_replyto(struct map_msg *msg, const char *value) +{ + if (g_strcmp0(msg->replyto, value) == 0) + return; + + g_free(msg->replyto); + msg->replyto = g_strdup(value); + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "ReplyTo"); +} + +static void parse_recipient(struct map_msg *msg, const char *value) +{ + if (g_strcmp0(msg->recipient, value) == 0) + return; + + g_free(msg->recipient); + msg->recipient = g_strdup(value); + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Recipient"); +} + +static void parse_recipient_address(struct map_msg *msg, const char *value) +{ + if (g_strcmp0(msg->recipient_address, value) == 0) + return; + + g_free(msg->recipient_address); + msg->recipient_address = g_strdup(value); + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "RecipientAddress"); +} + +static void parse_size(struct map_msg *msg, const char *value) +{ + uint64_t size = g_ascii_strtoll(value, NULL, 10); + + if (msg->size == size) + return; + + msg->size = size; + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Size"); +} + +static void parse_text(struct map_msg *msg, const char *value) +{ + gboolean flag = strcasecmp(value, "no") != 0; + uint8_t oldflags = msg->flags; + + if (flag) + msg->flags |= MAP_MSG_FLAG_TEXT; + else + msg->flags &= ~MAP_MSG_FLAG_TEXT; + + if (msg->flags != oldflags) + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Text"); +} + +static void parse_status(struct map_msg *msg, const char *value) +{ + if (g_strcmp0(msg->status, value) == 0) + return; + + g_free(msg->status); + msg->status = g_strdup(value); + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Status"); +} + +static void parse_attachment_size(struct map_msg *msg, const char *value) +{ + uint64_t attachment_size = g_ascii_strtoll(value, NULL, 10); + + if (msg->attachment_size == attachment_size) + return; + + msg->attachment_size = attachment_size; + + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "AttachmentSize"); +} + +static void parse_priority(struct map_msg *msg, const char *value) +{ + gboolean flag = strcasecmp(value, "no") != 0; + uint8_t oldflags = msg->flags; + + if (flag) + msg->flags |= MAP_MSG_FLAG_PRIORITY; + else + msg->flags &= ~MAP_MSG_FLAG_PRIORITY; + + if (msg->flags != oldflags) + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Priority"); +} + +static void parse_read(struct map_msg *msg, const char *value) +{ + gboolean flag = strcasecmp(value, "no") != 0; + uint8_t oldflags = msg->flags; + + if (flag) + msg->flags |= MAP_MSG_FLAG_READ; + else + msg->flags &= ~MAP_MSG_FLAG_READ; + + if (msg->flags != oldflags) + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Read"); +} + +static void parse_sent(struct map_msg *msg, const char *value) +{ + gboolean flag = strcasecmp(value, "no") != 0; + uint8_t oldflags = msg->flags; + + if (flag) + msg->flags |= MAP_MSG_FLAG_SENT; + else + msg->flags &= ~MAP_MSG_FLAG_SENT; + + if (msg->flags != oldflags) + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Sent"); +} + +static void parse_protected(struct map_msg *msg, const char *value) +{ + gboolean flag = strcasecmp(value, "no") != 0; + uint8_t oldflags = msg->flags; + + if (flag) + msg->flags |= MAP_MSG_FLAG_PROTECTED; + else + msg->flags &= ~MAP_MSG_FLAG_PROTECTED; + + if (msg->flags != oldflags) + g_dbus_emit_property_changed(conn, msg->path, + MAP_MSG_INTERFACE, "Protected"); +} + +static struct map_msg_parser { + const char *name; + void (*func) (struct map_msg *msg, const char *value); +} msg_parsers[] = { + { "subject", parse_subject }, + { "datetime", parse_datetime }, + { "sender_name", parse_sender }, + { "sender_addressing", parse_sender_address }, + { "replyto_addressing", parse_replyto }, + { "recipient_name", parse_recipient }, + { "recipient_addressing", parse_recipient_address }, + { "type", parse_type }, + { "size", parse_size }, + { "text", parse_text }, + { "reception_status", parse_status }, + { "attachment_size", parse_attachment_size }, + { "priority", parse_priority }, + { "read", parse_read }, + { "sent", parse_sent }, + { "protected", parse_protected }, + { } +}; + +static void msg_element(GMarkupParseContext *ctxt, const char *element, + const char **names, const char **values, + gpointer user_data, GError **gerr) +{ + struct map_parser *parser = user_data; + struct map_data *data = parser->request->map; + DBusMessageIter entry, *iter = parser->iter; + struct map_msg *msg; + const char *key; + int i; + uint64_t handle; + + if (strcasecmp("msg", element) != 0) + return; + + for (i = 0, key = names[i]; key; key = names[++i]) { + if (strcasecmp(key, "handle") == 0) + break; + } + + handle = strtoull(values[i], NULL, 16); + + msg = g_hash_table_lookup(data->messages, &handle); + if (msg == NULL) { + msg = map_msg_create(data, handle, parser->request->folder, + NULL); + if (msg == NULL) + return; + } + + dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, + &msg->path); + + for (i = 0, key = names[i]; key; key = names[++i]) { + struct map_msg_parser *parser; + + for (parser = msg_parsers; parser && parser->name; parser++) { + if (strcasecmp(key, parser->name) == 0) { + if (values[i]) + parser->func(msg, values[i]); + break; + } + } + } + + g_dbus_get_properties(conn, msg->path, MAP_MSG_INTERFACE, &entry); + + dbus_message_iter_close_container(iter, &entry); +} + +static const GMarkupParser msg_parser = { + msg_element, + NULL, + NULL, + NULL, + NULL +}; + +static void message_listing_cb(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct pending_request *request = user_data; + struct map_parser *parser; + GMarkupParseContext *ctxt; + DBusMessage *reply; + DBusMessageIter iter, array; + char *contents; + size_t size; + int perr; + + if (err != NULL) { + reply = g_dbus_create_error(request->msg, + ERROR_INTERFACE ".Failed", + "%s", err->message); + goto done; + } + + perr = obc_transfer_get_contents(transfer, &contents, &size); + if (perr < 0) { + reply = g_dbus_create_error(request->msg, + ERROR_INTERFACE ".Failed", + "Error reading contents: %s", + strerror(-perr)); + goto done; + } + + reply = dbus_message_new_method_return(request->msg); + if (reply == NULL) { + g_free(contents); + goto clean; + } + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_OBJECT_PATH_AS_STRING + DBUS_TYPE_ARRAY_AS_STRING + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &array); + + parser = g_new(struct map_parser, 1); + parser->request = request; + parser->iter = &array; + + ctxt = g_markup_parse_context_new(&msg_parser, 0, parser, NULL); + g_markup_parse_context_parse(ctxt, contents, size, NULL); + g_markup_parse_context_free(ctxt); + dbus_message_iter_close_container(&iter, &array); + g_free(contents); + g_free(parser); + +done: + g_dbus_send_message(conn, reply); +clean: + pending_request_free(request); +} + +static char *get_absolute_folder(struct map_data *map, const char *subfolder) +{ + const char *root = obc_session_get_folder(map->session); + + if (!subfolder || strlen(subfolder) == 0) + return g_strdup(root); + else if (g_str_has_suffix(root, "/")) + return g_strconcat(root, subfolder, NULL); + else + return g_strconcat(root, "/", subfolder, NULL); +} + +static DBusMessage *get_message_listing(struct map_data *map, + DBusMessage *message, + const char *folder, + GObexApparam *apparam) +{ + struct pending_request *request; + struct obc_transfer *transfer; + GError *err = NULL; + DBusMessage *reply; + + transfer = obc_transfer_get("x-bt/MAP-msg-listing", folder, NULL, &err); + if (transfer == NULL) { + g_obex_apparam_free(apparam); + goto fail; + } + + obc_transfer_set_apparam(transfer, apparam); + + request = pending_request_new(map, message); + request->folder = get_absolute_folder(map, folder); + + if (!obc_session_queue(map->session, transfer, message_listing_cb, + request, &err)) { + pending_request_free(request); + goto fail; + } + + return NULL; + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static GObexApparam *parse_subject_length(GObexApparam *apparam, + DBusMessageIter *iter) +{ + guint8 num; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BYTE) + return NULL; + + dbus_message_iter_get_basic(iter, &num); + + return g_obex_apparam_set_uint8(apparam, MAP_AP_SUBJECTLENGTH, num); +} + +static uint64_t get_filter_mask(const char *filterstr) +{ + int i; + + if (!filterstr) + return 0; + + if (!g_ascii_strcasecmp(filterstr, "ALL")) + return FILTER_ALL; + + for (i = 0; filter_list[i] != NULL; i++) + if (!g_ascii_strcasecmp(filterstr, filter_list[i])) + return 1ULL << i; + + return 0; +} + +static int set_field(guint32 *filter, const char *filterstr) +{ + guint64 mask; + + mask = get_filter_mask(filterstr); + + if (mask == 0) + return -EINVAL; + + *filter |= mask; + return 0; +} + +static GObexApparam *parse_fields(GObexApparam *apparam, DBusMessageIter *iter) +{ + DBusMessageIter array; + guint32 filter = 0; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return NULL; + + dbus_message_iter_recurse(iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { + const char *string; + + dbus_message_iter_get_basic(&array, &string); + + if (set_field(&filter, string) < 0) + return NULL; + + dbus_message_iter_next(&array); + } + + return g_obex_apparam_set_uint32(apparam, MAP_AP_PARAMETERMASK, + filter); +} + +static GObexApparam *parse_filter_type(GObexApparam *apparam, + DBusMessageIter *iter) +{ + DBusMessageIter array; + guint8 types = 0; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return NULL; + + dbus_message_iter_recurse(iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { + const char *string; + + dbus_message_iter_get_basic(&array, &string); + + if (!g_ascii_strcasecmp(string, "sms")) + types |= 0x03; /* sms-gsm and sms-cdma */ + else if (!g_ascii_strcasecmp(string, "email")) + types |= 0x04; /* email */ + else if (!g_ascii_strcasecmp(string, "mms")) + types |= 0x08; /* mms */ + else + return NULL; + + dbus_message_iter_next(&array); + } + + return g_obex_apparam_set_uint8(apparam, MAP_AP_FILTERMESSAGETYPE, + types); +} + +static GObexApparam *parse_period_begin(GObexApparam *apparam, + DBusMessageIter *iter) +{ + const char *string; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return NULL; + + dbus_message_iter_get_basic(iter, &string); + + return g_obex_apparam_set_string(apparam, MAP_AP_FILTERPERIODBEGIN, + string); +} + +static GObexApparam *parse_period_end(GObexApparam *apparam, + DBusMessageIter *iter) +{ + const char *string; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return NULL; + + dbus_message_iter_get_basic(iter, &string); + + return g_obex_apparam_set_string(apparam, MAP_AP_FILTERPERIODEND, + string); +} + +static GObexApparam *parse_filter_read(GObexApparam *apparam, + DBusMessageIter *iter) +{ + guint8 status = FILTER_READ_STATUS_NONE; + dbus_bool_t dbus_status = FALSE; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) + return NULL; + + dbus_message_iter_get_basic(iter, &dbus_status); + + if (dbus_status) + status = FILTER_READ_STATUS_ONLY_READ; + else + status = FILTER_READ_STATUS_ONLY_UNREAD; + + return g_obex_apparam_set_uint8(apparam, MAP_AP_FILTERREADSTATUS, + status); +} + +static GObexApparam *parse_filter_recipient(GObexApparam *apparam, + DBusMessageIter *iter) +{ + const char *string; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return NULL; + + dbus_message_iter_get_basic(iter, &string); + + return g_obex_apparam_set_string(apparam, MAP_AP_FILTERRECIPIENT, + string); +} + +static GObexApparam *parse_filter_sender(GObexApparam *apparam, + DBusMessageIter *iter) +{ + const char *string; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return NULL; + + dbus_message_iter_get_basic(iter, &string); + + return g_obex_apparam_set_string(apparam, MAP_AP_FILTERORIGINATOR, + string); +} + +static GObexApparam *parse_filter_priority(GObexApparam *apparam, + DBusMessageIter *iter) +{ + guint8 priority = FILTER_PRIORITY_NONE; + dbus_bool_t dbus_priority = FALSE; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) + return NULL; + + dbus_message_iter_get_basic(iter, &dbus_priority); + + if (dbus_priority) + priority = FILTER_PRIORITY_ONLY_HIGH; + else + priority = FILTER_PRIORITY_ONLY_NONHIGH; + + return g_obex_apparam_set_uint8(apparam, MAP_AP_FILTERPRIORITY, + priority); +} + +static GObexApparam *parse_message_filters(GObexApparam *apparam, + DBusMessageIter *iter) +{ + DBusMessageIter array; + + DBG(""); + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return NULL; + + dbus_message_iter_recurse(iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + + dbus_message_iter_recurse(&array, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + if (strcasecmp(key, "Offset") == 0) { + if (parse_offset(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "MaxCount") == 0) { + if (parse_max_count(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "SubjectLength") == 0) { + if (parse_subject_length(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Fields") == 0) { + if (parse_fields(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Types") == 0) { + if (parse_filter_type(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "PeriodBegin") == 0) { + if (parse_period_begin(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "PeriodEnd") == 0) { + if (parse_period_end(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Read") == 0) { + if (parse_filter_read(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Recipient") == 0) { + if (parse_filter_recipient(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Sender") == 0) { + if (parse_filter_sender(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Priority") == 0) { + if (parse_filter_priority(apparam, &value) == NULL) + return NULL; + } + + dbus_message_iter_next(&array); + } + + return apparam; +} + +static DBusMessage *map_list_messages(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct map_data *map = user_data; + const char *folder; + GObexApparam *apparam; + DBusMessageIter args; + + dbus_message_iter_init(message, &args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + dbus_message_iter_get_basic(&args, &folder); + + apparam = g_obex_apparam_set_uint16(NULL, MAP_AP_MAXLISTCOUNT, + DEFAULT_COUNT); + apparam = g_obex_apparam_set_uint16(apparam, MAP_AP_STARTOFFSET, + DEFAULT_OFFSET); + + dbus_message_iter_next(&args); + + if (parse_message_filters(apparam, &args) == NULL) { + g_obex_apparam_free(apparam); + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + } + + return get_message_listing(map, message, folder, apparam); +} + +static char **get_filter_strs(uint64_t filter, int *size) +{ + char **list, **item; + int i; + + list = g_malloc0(sizeof(char **) * (FILTER_BIT_MAX + 2)); + + item = list; + + for (i = 0; filter_list[i] != NULL; i++) + if (filter & (1ULL << i)) + *(item++) = g_strdup(filter_list[i]); + + *item = NULL; + *size = item - list; + return list; +} + +static DBusMessage *map_list_filter_fields(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + char **filters = NULL; + int size; + DBusMessage *reply; + + filters = get_filter_strs(FILTER_ALL, &size); + reply = dbus_message_new_method_return(message); + dbus_message_append_args(reply, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING, &filters, size, + DBUS_TYPE_INVALID); + + g_strfreev(filters); + return reply; +} + +static void update_inbox_cb(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct pending_request *request = user_data; + DBusMessage *reply; + + if (err != NULL) { + reply = g_dbus_create_error(request->msg, + ERROR_INTERFACE ".Failed", + "%s", err->message); + goto done; + } + + reply = dbus_message_new_method_return(request->msg); + +done: + g_dbus_send_message(conn, reply); + pending_request_free(request); +} + +static DBusMessage *map_update_inbox(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct map_data *map = user_data; + DBusMessage *reply; + char contents[1]; + struct obc_transfer *transfer; + GError *err = NULL; + struct pending_request *request; + + contents[0] = FILLER_BYTE; + + transfer = obc_transfer_put("x-bt/MAP-messageUpdate", NULL, NULL, + contents, sizeof(contents), + &err); + if (transfer == NULL) + goto fail; + + request = pending_request_new(map, message); + + if (!obc_session_queue(map->session, transfer, update_inbox_cb, + request, &err)) { + pending_request_free(request); + goto fail; + } + + return NULL; + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static DBusMessage *push_message(struct map_data *map, + DBusMessage *message, + const char *filename, + const char *folder, + GObexApparam *apparam) +{ + struct obc_transfer *transfer; + GError *err = NULL; + DBusMessage *reply; + + transfer = obc_transfer_put("x-bt/message", folder, filename, + NULL, 0, &err); + if (transfer == NULL) { + g_obex_apparam_free(apparam); + goto fail; + } + + obc_transfer_set_apparam(transfer, apparam); + + if (!obc_session_queue(map->session, transfer, NULL, NULL, &err)) + goto fail; + + return obc_transfer_create_dbus_reply(transfer, message); + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static GObexApparam *parse_transparent(GObexApparam *apparam, + DBusMessageIter *iter) +{ + dbus_bool_t transparent; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) + return NULL; + + dbus_message_iter_get_basic(iter, &transparent); + + return g_obex_apparam_set_uint8(apparam, MAP_AP_TRANSPARENT, + transparent ? TRUE : FALSE); +} + +static GObexApparam *parse_retry(GObexApparam *apparam, DBusMessageIter *iter) +{ + dbus_bool_t retry; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) + return NULL; + + dbus_message_iter_get_basic(iter, &retry); + + return g_obex_apparam_set_uint8(apparam, MAP_AP_RETRY, + retry ? TRUE : FALSE); +} + +static GObexApparam *parse_charset(GObexApparam *apparam, DBusMessageIter *iter) +{ + guint8 charset = 0; + const char *string; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return NULL; + + dbus_message_iter_get_basic(iter, &string); + + if (strcasecmp(string, "native") == 0) + charset = CHARSET_NATIVE; + else if (strcasecmp(string, "utf8") == 0) + charset = CHARSET_UTF8; + else + return NULL; + + return g_obex_apparam_set_uint8(apparam, MAP_AP_CHARSET, charset); +} + +static GObexApparam *parse_push_options(GObexApparam *apparam, + DBusMessageIter *iter) +{ + DBusMessageIter array; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return NULL; + + dbus_message_iter_recurse(iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + + dbus_message_iter_recurse(&array, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + if (strcasecmp(key, "Transparent") == 0) { + if (parse_transparent(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Retry") == 0) { + if (parse_retry(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Charset") == 0) { + if (parse_charset(apparam, &value) == NULL) + return NULL; + } + + dbus_message_iter_next(&array); + } + + return apparam; +} + +static DBusMessage *map_push_message(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct map_data *map = user_data; + char *filename; + char *folder; + GObexApparam *apparam; + DBusMessageIter args; + + dbus_message_iter_init(message, &args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + dbus_message_iter_get_basic(&args, &filename); + + dbus_message_iter_next(&args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) { + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + } + + dbus_message_iter_get_basic(&args, &folder); + + dbus_message_iter_next(&args); + + apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_CHARSET, CHARSET_UTF8); + + if (parse_push_options(apparam, &args) == NULL) { + g_obex_apparam_free(apparam); + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + } + + return push_message(map, message, filename, folder, apparam); +} + +static const GDBusMethodTable map_methods[] = { + { GDBUS_ASYNC_METHOD("SetFolder", + GDBUS_ARGS({ "name", "s" }), NULL, + map_setpath) }, + { GDBUS_ASYNC_METHOD("ListFolders", + GDBUS_ARGS({ "filters", "a{sv}" }), + GDBUS_ARGS({ "content", "aa{sv}" }), + map_list_folders) }, + { GDBUS_ASYNC_METHOD("ListMessages", + GDBUS_ARGS({ "folder", "s" }, { "filter", "a{sv}" }), + GDBUS_ARGS({ "messages", "a{oa{sv}}" }), + map_list_messages) }, + { GDBUS_METHOD("ListFilterFields", + NULL, + GDBUS_ARGS({ "fields", "as" }), + map_list_filter_fields) }, + { GDBUS_ASYNC_METHOD("UpdateInbox", + NULL, + NULL, + map_update_inbox) }, + { GDBUS_ASYNC_METHOD("PushMessage", + GDBUS_ARGS({ "file", "s" }, { "folder", "s" }, + { "args", "a{sv}" }), + GDBUS_ARGS({ "transfer", "o" }, + { "properties", "a{sv}" }), + map_push_message) }, + { } +}; + +static void map_msg_remove(void *data) +{ + struct map_msg *msg = data; + char *path; + + path = msg->path; + msg->path = NULL; + g_dbus_unregister_interface(conn, path, MAP_MSG_INTERFACE); + g_free(path); +} + +static void map_handle_new_message(struct map_data *map, + struct map_event *event) +{ + struct map_msg *msg; + + msg = g_hash_table_lookup(map->messages, &event->handle); + /* New message event can be used if a new message replaces an old one */ + if (msg) + g_hash_table_remove(map->messages, &event->handle); + + map_msg_create(map, event->handle, event->folder, event->msg_type); +} + +static void map_handle_status_changed(struct map_data *map, + struct map_event *event, + const char *status) +{ + struct map_msg *msg; + + msg = g_hash_table_lookup(map->messages, &event->handle); + if (msg == NULL) + return; + + if (g_strcmp0(msg->status, status) == 0) + return; + + g_free(msg->status); + msg->status = g_strdup(status); + + g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, + "Status"); +} + +static void map_handle_folder_changed(struct map_data *map, + struct map_event *event, + const char *folder) +{ + struct map_msg *msg; + + if (!folder) + return; + + msg = g_hash_table_lookup(map->messages, &event->handle); + if (!msg) + return; + + if (g_strcmp0(msg->folder, folder) == 0) + return; + + g_free(msg->folder); + msg->folder = g_strdup(folder); + + g_dbus_emit_property_changed(conn, msg->path, MAP_MSG_INTERFACE, + "Folder"); +} + +static void map_handle_notification(struct map_event *event, void *user_data) +{ + struct map_data *map = user_data; + + DBG("Event report for %s:%d", obc_session_get_destination(map->session), + map->mas_instance_id); + DBG("type=%x handle=%" PRIx64 " folder=%s old_folder=%s msg_type=%s", + event->type, event->handle, event->folder, event->old_folder, + event->msg_type); + + switch (event->type) { + case MAP_ET_NEW_MESSAGE: + map_handle_new_message(map, event); + break; + case MAP_ET_DELIVERY_SUCCESS: + map_handle_status_changed(map, event, "delivery-success"); + break; + case MAP_ET_SENDING_SUCCESS: + map_handle_status_changed(map, event, "sending-success"); + break; + case MAP_ET_DELIVERY_FAILURE: + map_handle_status_changed(map, event, "delivery-failure"); + break; + case MAP_ET_SENDING_FAILURE: + map_handle_status_changed(map, event, "sending-failure"); + break; + case MAP_ET_MESSAGE_DELETED: + map_handle_folder_changed(map, event, "/telecom/msg/deleted"); + break; + case MAP_ET_MESSAGE_SHIFT: + map_handle_folder_changed(map, event, event->folder); + break; + case MAP_ET_MEMORY_FULL: + case MAP_ET_MEMORY_AVAILABLE: + default: + break; + } +} + +static bool set_notification_registration(struct map_data *map, bool status) +{ + struct obc_transfer *transfer; + GError *err = NULL; + GObexApparam *apparam; + char contents[1]; + const char *address; + + address = obc_session_get_destination(map->session); + if (!address || map->mas_instance_id < 0) + return FALSE; + + if (status) { + map_register_event_handler(map->session, map->mas_instance_id, + &map_handle_notification, map); + } else { + map_unregister_event_handler(map->session, + map->mas_instance_id); + } + + contents[0] = FILLER_BYTE; + + transfer = obc_transfer_put("x-bt/MAP-NotificationRegistration", NULL, + NULL, contents, sizeof(contents), &err); + + if (transfer == NULL) + return false; + + apparam = g_obex_apparam_set_uint8(NULL, MAP_AP_NOTIFICATIONSTATUS, + status); + + obc_transfer_set_apparam(transfer, apparam); + + if (obc_session_queue(map->session, transfer, NULL, map, &err)) + return true; + + return false; +} + +static void map_free(void *data) +{ + struct map_data *map = data; + + set_notification_registration(map, false); + + obc_session_unref(map->session); + g_hash_table_unref(map->messages); + g_free(map); +} + +static void parse_service_record(struct map_data *map) +{ + const void *data; + + /* MAS instance id */ + map->mas_instance_id = -1; + data = obc_session_get_attribute(map->session, + SDP_ATTR_MAS_INSTANCE_ID); + if (data != NULL) + map->mas_instance_id = *(uint8_t *)data; + else + DBG("Failed to read MAS instance id"); + + /* Supported Message Types */ + data = obc_session_get_attribute(map->session, + SDP_ATTR_SUPPORTED_MESSAGE_TYPES); + if (data != NULL) + map->supported_message_types = *(uint8_t *)data; + else + DBG("Failed to read supported message types"); + + /* Supported Feature Bits */ + data = obc_session_get_attribute(map->session, + SDP_ATTR_MAP_SUPPORTED_FEATURES); + if(data != NULL) + map->supported_features = *(uint32_t *) data; + else + map->supported_features = 0x0000001f; +} + +static int map_probe(struct obc_session *session) +{ + struct map_data *map; + const char *path; + + path = obc_session_get_path(session); + + map = g_try_new0(struct map_data, 1); + if (!map) + return -ENOMEM; + + map->session = obc_session_ref(session); + map->messages = g_hash_table_new_full(g_int64_hash, g_int64_equal, NULL, + map_msg_remove); + + parse_service_record(map); + + DBG("%s, instance id %d", path, map->mas_instance_id); + + set_notification_registration(map, true); + + if (!g_dbus_register_interface(conn, path, MAP_INTERFACE, map_methods, + NULL, NULL, map, map_free)) { + map_free(map); + + return -ENOMEM; + } + + return 0; +} + +static void map_remove(struct obc_session *session) +{ + const char *path = obc_session_get_path(session); + + DBG("%s", path); + + g_dbus_unregister_interface(conn, path, MAP_INTERFACE); +} + +static struct obc_driver map = { + .service = "MAP", + .uuid = MAS_UUID, + .target = OBEX_MAS_UUID, + .target_len = OBEX_MAS_UUID_LEN, + .probe = map_probe, + .remove = map_remove +}; + +int map_init(void) +{ + int err; + + DBG(""); + + conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); + if (!conn) + return -EIO; + + err = obc_driver_register(&map); + if (err < 0) { + dbus_connection_unref(conn); + conn = NULL; + return err; + } + + return 0; +} + +void map_exit(void) +{ + DBG(""); + + dbus_connection_unref(conn); + conn = NULL; + + obc_driver_unregister(&map); +} diff --git a/obexd/client/map.h b/obexd/client/map.h new file mode 100644 index 0000000..86f6b95 --- /dev/null +++ b/obexd/client/map.h @@ -0,0 +1,24 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2011 Bartosz Szatkowski for Comarch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int map_init(void); +void map_exit(void); diff --git a/obexd/client/mns.c b/obexd/client/mns.c new file mode 100644 index 0000000..4912cb9 --- /dev/null +++ b/obexd/client/mns.c @@ -0,0 +1,407 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2013 BMW Car IT GmbH. 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "gobex/gobex.h" +#include "gobex/gobex-apparam.h" + +#include "obexd/src/obexd.h" +#include "obexd/src/plugin.h" +#include "obexd/src/log.h" +#include "obexd/src/obex.h" +#include "obexd/src/service.h" +#include "obexd/src/mimetype.h" +#include "obexd/src/map_ap.h" +#include "map-event.h" + +#include "obexd/src/manager.h" + +struct mns_session { + GString *buffer; + GObexApparam *inparams; + char *remote_address; + uint8_t mas_instance_id; +}; + +static const uint8_t MNS_TARGET[TARGET_SIZE] = { + 0xbb, 0x58, 0x2b, 0x41, 0x42, 0x0c, 0x11, 0xdb, + 0xb0, 0xde, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 }; + +static int get_params(struct obex_session *os, struct mns_session *mns) +{ + const uint8_t *buffer; + ssize_t size; + + size = obex_get_apparam(os, &buffer); + if (size < 0) + size = 0; + + mns->inparams = g_obex_apparam_decode(buffer, size); + if (mns->inparams == NULL) { + DBG("Error when parsing parameters!"); + return -EBADR; + } + + return 0; +} + +static void reset_request(struct mns_session *mns) +{ + if (mns->buffer) { + g_string_free(mns->buffer, TRUE); + mns->buffer = NULL; + } + + if (mns->inparams) { + g_obex_apparam_free(mns->inparams); + mns->inparams = NULL; + } +} + +static void mns_session_free(struct mns_session *mns) +{ + reset_request(mns); + + if (mns->remote_address) + g_free(mns->remote_address); + + g_free(mns); +} + +static void *mns_connect(struct obex_session *os, int *err) +{ + struct mns_session *mns; + char *address; + + manager_register_session(os); + + mns = g_new0(struct mns_session, 1); + + if (obex_getpeername(os, &address) == 0) { + mns->remote_address = g_strdup(address); + g_free(address); + } + + DBG("MNS connected to %s", mns->remote_address); + + if (err) + *err = 0; + + return mns; +} + +static void mns_disconnect(struct obex_session *os, void *user_data) +{ + struct mns_session *mns = user_data; + + DBG("MNS disconnected from %s", mns->remote_address); + + manager_unregister_session(os); + + mns_session_free(mns); +} + +static int mns_put(struct obex_session *os, void *user_data) +{ + struct mns_session *mns = user_data; + const char *type = obex_get_type(os); + const char *name = obex_get_name(os); + int ret; + + DBG("PUT: name %s type %s mns %p", name, type, mns); + + if (type == NULL) + return -EBADR; + + ret = get_params(os, mns); + if (ret < 0) + goto failed; + + ret = obex_put_stream_start(os, name); + if (ret < 0) + goto failed; + + return 0; + +failed: + reset_request(mns); + + return ret; +} + +static void parse_event_report_type(struct map_event *event, const char *value) +{ + if (!g_ascii_strcasecmp(value, "NewMessage")) + event->type = MAP_ET_NEW_MESSAGE; + else if (!g_ascii_strcasecmp(value, "DeliverySuccess")) + event->type = MAP_ET_DELIVERY_SUCCESS; + else if (!g_ascii_strcasecmp(value, "SendingSuccess")) + event->type = MAP_ET_SENDING_SUCCESS; + else if (!g_ascii_strcasecmp(value, "DeliveryFailure")) + event->type = MAP_ET_DELIVERY_FAILURE; + else if (!g_ascii_strcasecmp(value, "SendingFailure")) + event->type = MAP_ET_SENDING_FAILURE; + else if (!g_ascii_strcasecmp(value, "MemoryFull")) + event->type = MAP_ET_MEMORY_FULL; + else if (!g_ascii_strcasecmp(value, "MemoryAvailable")) + event->type = MAP_ET_MEMORY_AVAILABLE; + else if (!g_ascii_strcasecmp(value, "MessageDeleted")) + event->type = MAP_ET_MESSAGE_DELETED; + else if (!g_ascii_strcasecmp(value, "MessageShift")) + event->type = MAP_ET_MESSAGE_SHIFT; +} + +static void parse_event_report_handle(struct map_event *event, + const char *value) +{ + event->handle = strtoull(value, NULL, 16); +} + +static void parse_event_report_folder(struct map_event *event, + const char *value) +{ + g_free(event->folder); + + if (g_str_has_prefix(value, "/")) + event->folder = g_strdup(value); + else + event->folder = g_strconcat("/", value, NULL); +} + +static void parse_event_report_old_folder(struct map_event *event, + const char *value) +{ + g_free(event->old_folder); + + if (g_str_has_prefix(value, "/")) + event->old_folder = g_strdup(value); + else + event->old_folder = g_strconcat("/", value, NULL); +} + +static void parse_event_report_msg_type(struct map_event *event, + const char *value) +{ + g_free(event->msg_type); + event->msg_type = g_strdup(value); +} + +static void parse_event_report_date_time(struct map_event *event, + const char *value) +{ + g_free(event->datetime); + event->datetime = g_strdup(value); +} + +static void parse_event_report_subject(struct map_event *event, + const char *value) +{ + g_free(event->subject); + event->subject = g_strdup(value); +} + +static void parse_event_report_sender_name(struct map_event *event, + const char *value) +{ + g_free(event->sender_name); + event->sender_name = g_strdup(value); +} + +static void parse_event_report_priority(struct map_event *event, + const char *value) +{ + g_free(event->priority); + event->priority = g_strdup(value); +} + +static struct map_event_report_parser { + const char *name; + void (*func) (struct map_event *event, const char *value); +} event_report_parsers[] = { + { "type", parse_event_report_type }, + { "handle", parse_event_report_handle }, + { "folder", parse_event_report_folder }, + { "old_folder", parse_event_report_old_folder }, + { "msg_type", parse_event_report_msg_type }, + { "datetime", parse_event_report_date_time }, + { "subject", parse_event_report_subject }, + { "sender_name", parse_event_report_sender_name }, + { "priority", parse_event_report_priority }, + { } +}; + +static void event_report_element(GMarkupParseContext *ctxt, + const char *element, const char **names, + const char **values, gpointer user_data, + GError **gerr) +{ + struct map_event *event = user_data; + const char *key; + int i; + + if (strcasecmp("event", element) != 0) + return; + + for (i = 0, key = names[i]; key; key = names[++i]) { + struct map_event_report_parser *parser; + + for (parser = event_report_parsers; parser && parser->name; + parser++) { + if (strcasecmp(key, parser->name) == 0) { + if (values[i]) + parser->func(event, values[i]); + break; + } + } + } +} + +static const GMarkupParser event_report_parser = { + event_report_element, + NULL, + NULL, + NULL, + NULL +}; + +static void map_event_free(struct map_event *event) +{ + g_free(event->folder); + g_free(event->old_folder); + g_free(event->msg_type); + g_free(event->datetime); + g_free(event->subject); + g_free(event->sender_name); + g_free(event->priority); + g_free(event); +} + +static void *event_report_open(const char *name, int oflag, mode_t mode, + void *driver_data, size_t *size, int *err) +{ + struct mns_session *mns = driver_data; + + DBG(""); + + g_obex_apparam_get_uint8(mns->inparams, MAP_AP_MASINSTANCEID, + &mns->mas_instance_id); + + mns->buffer = g_string_new(""); + + if (err != NULL) + *err = 0; + + return mns; +} + +static int event_report_close(void *obj) +{ + struct mns_session *mns = obj; + GMarkupParseContext *ctxt; + struct map_event *event; + + DBG(""); + + event = g_new0(struct map_event, 1); + ctxt = g_markup_parse_context_new(&event_report_parser, 0, event, + NULL); + g_markup_parse_context_parse(ctxt, mns->buffer->str, mns->buffer->len, + NULL); + g_markup_parse_context_free(ctxt); + + map_dispatch_event(mns->mas_instance_id, mns->remote_address, event); + map_event_free(event); + + reset_request(mns); + + return 0; +} + +static ssize_t event_report_write(void *obj, const void *buf, size_t count) +{ + struct mns_session *mns = obj; + + DBG(""); + + g_string_append_len(mns->buffer, buf, count); + return count; +} + +static struct obex_service_driver mns = { + .name = "Message Notification server", + .service = OBEX_MNS, + .target = MNS_TARGET, + .target_size = TARGET_SIZE, + .connect = mns_connect, + .put = mns_put, + .disconnect = mns_disconnect, +}; + +static struct obex_mime_type_driver mime_event_report = { + .target = MNS_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "x-bt/MAP-event-report", + .open = event_report_open, + .close = event_report_close, + .write = event_report_write, +}; + +static int mns_init(void) +{ + int err; + + err = obex_mime_type_driver_register(&mime_event_report); + if (err < 0) + goto fail_mime_event; + + err = obex_service_driver_register(&mns); + if (err < 0) + goto fail_mns_reg; + + return 0; + +fail_mns_reg: + obex_mime_type_driver_unregister(&mime_event_report); +fail_mime_event: + return err; +} + +static void mns_exit(void) +{ + obex_service_driver_unregister(&mns); + obex_mime_type_driver_unregister(&mime_event_report); +} + +OBEX_PLUGIN_DEFINE(mns, mns_init, mns_exit) diff --git a/obexd/client/opp.c b/obexd/client/opp.c new file mode 100644 index 0000000..92785f6 --- /dev/null +++ b/obexd/client/opp.c @@ -0,0 +1,216 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2011 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 +#endif + +#include + +#include "gdbus/gdbus.h" + +#include "obexd/src/log.h" + +#include "transfer.h" +#include "session.h" +#include "driver.h" +#include "opp.h" + +#define OPP_UUID "00001105-0000-1000-8000-00805f9b34fb" +#define OPP_INTERFACE "org.bluez.obex.ObjectPush1" +#define ERROR_INTERFACE "org.bluez.obex.Error" + +struct opp_data { + struct obc_session *session; +}; + +static DBusConnection *conn = NULL; + +static DBusMessage *opp_send_file(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct opp_data *opp = user_data; + struct obc_transfer *transfer; + DBusMessage *reply; + char *filename; + char *basename; + GError *err = NULL; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &filename, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + basename = g_path_get_basename(filename); + + transfer = obc_transfer_put(NULL, basename, filename, NULL, 0, &err); + + g_free(basename); + + if (transfer == NULL) + goto fail; + + if (!obc_session_queue(opp->session, transfer, NULL, NULL, &err)) + goto fail; + + return obc_transfer_create_dbus_reply(transfer, message); + +fail: + reply = g_dbus_create_error(message, + ERROR_INTERFACE ".Failed", "%s", err->message); + g_error_free(err); + return reply; +} + +static DBusMessage *opp_pull_business_card(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct opp_data *opp = user_data; + struct obc_transfer *pull; + DBusMessage *reply; + const char *filename = NULL; + GError *err = NULL; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &filename, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + pull = obc_transfer_get("text/x-vcard", NULL, filename, &err); + if (pull == NULL) + goto fail; + + if (!obc_session_queue(opp->session, pull, NULL, NULL, &err)) + goto fail; + + return obc_transfer_create_dbus_reply(pull, message); + +fail: + reply = g_dbus_create_error(message, + ERROR_INTERFACE ".Failed", "%s", err->message); + g_error_free(err); + return reply; +} + +static DBusMessage *opp_exchange_business_cards(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + return g_dbus_create_error(message, ERROR_INTERFACE ".Failed", + "Not Implemented"); +} + +static const GDBusMethodTable opp_methods[] = { + { GDBUS_METHOD("SendFile", + GDBUS_ARGS({ "sourcefile", "s" }), + GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), + opp_send_file) }, + { GDBUS_METHOD("PullBusinessCard", + GDBUS_ARGS({ "targetfile", "s" }), + GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), + opp_pull_business_card) }, + { GDBUS_METHOD("ExchangeBusinessCards", + GDBUS_ARGS({ "clientfile", "s" }, { "targetfile", "s" }), + GDBUS_ARGS({ "transfer", "o" }, { "properties", "a{sv}" }), + opp_exchange_business_cards) }, + { } +}; + +static void opp_free(void *data) +{ + struct opp_data *opp = data; + + obc_session_unref(opp->session); + g_free(opp); +} + +static int opp_probe(struct obc_session *session) +{ + struct opp_data *opp; + const char *path; + + path = obc_session_get_path(session); + + DBG("%s", path); + + opp = g_try_new0(struct opp_data, 1); + if (!opp) + return -ENOMEM; + + opp->session = obc_session_ref(session); + + if (!g_dbus_register_interface(conn, path, OPP_INTERFACE, opp_methods, + NULL, NULL, opp, opp_free)) { + opp_free(opp); + return -ENOMEM; + } + + return 0; +} + +static void opp_remove(struct obc_session *session) +{ + const char *path = obc_session_get_path(session); + + DBG("%s", path); + + g_dbus_unregister_interface(conn, path, OPP_INTERFACE); +} + +static struct obc_driver opp = { + .service = "OPP", + .uuid = OPP_UUID, + .probe = opp_probe, + .remove = opp_remove +}; + +int opp_init(void) +{ + int err; + + DBG(""); + + conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); + if (!conn) + return -EIO; + + err = obc_driver_register(&opp); + if (err < 0) { + dbus_connection_unref(conn); + conn = NULL; + return err; + } + + return 0; +} + +void opp_exit(void) +{ + DBG(""); + + dbus_connection_unref(conn); + conn = NULL; + + obc_driver_unregister(&opp); +} diff --git a/obexd/client/opp.h b/obexd/client/opp.h new file mode 100644 index 0000000..a23e94e --- /dev/null +++ b/obexd/client/opp.h @@ -0,0 +1,25 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2011 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 + * + */ + +int opp_init(void); +void opp_exit(void); diff --git a/obexd/client/pbap.c b/obexd/client/pbap.c new file mode 100644 index 0000000..3f5665f --- /dev/null +++ b/obexd/client/pbap.c @@ -0,0 +1,1339 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Intel Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "gobex/gobex-apparam.h" +#include "gdbus/gdbus.h" + +#include "obexd/src/log.h" + +#include "transfer.h" +#include "session.h" +#include "driver.h" +#include "pbap.h" + +#define OBEX_PBAP_UUID \ + "\x79\x61\x35\xF0\xF0\xC5\x11\xD8\x09\x66\x08\x00\x20\x0C\x9A\x66" +#define OBEX_PBAP_UUID_LEN 16 + +#define FORMAT_VCARD21 0x0 +#define FORMAT_VCARD30 0x1 + +#define ORDER_INDEXED 0x0 +#define ORDER_ALPHANUMERIC 0x1 +#define ORDER_PHONETIC 0x2 + +#define ATTRIB_NAME 0x0 +#define ATTRIB_NUMBER 0x1 +#define ATTRIB_SOUND 0x2 + +#define DEFAULT_COUNT 65535 +#define DEFAULT_OFFSET 0 + +#define PULLPHONEBOOK 0x1 +#define GETPHONEBOOKSIZE 0x2 + +#define ORDER_TAG 0x01 +#define SEARCHVALUE_TAG 0x02 +#define SEARCHATTRIB_TAG 0x03 +#define MAXLISTCOUNT_TAG 0x04 +#define LISTSTARTOFFSET_TAG 0x05 +#define FILTER_TAG 0x06 +#define FORMAT_TAG 0X07 +#define PHONEBOOKSIZE_TAG 0X08 +#define NEWMISSEDCALLS_TAG 0X09 +#define PRIMARY_COUNTER_TAG 0X0A +#define SECONDARY_COUNTER_TAG 0X0B +#define DATABASEID_TAG 0X0D +#define SUPPORTED_FEATURES_TAG 0x10 + +#define DOWNLOAD_FEATURE 0x00000001 +#define BROWSE_FEATURE 0x00000002 +#define DATABASEID_FEATURE 0x00000004 +#define FOLDER_VERSION_FEATURE 0x00000008 +#define VCARD_SELECTING_FEATURE 0x00000010 +#define ENHANCED_CALLS_FEATURE 0x00000020 +#define UCI_FEATURE 0x00000040 +#define UID_FEATURE 0x00000080 +#define REFERENCING_FEATURE 0x00000100 +#define DEFAULT_IMAGE_FEATURE 0x00000200 + +static const char *filter_list[] = { + "VERSION", + "FN", + "N", + "PHOTO", + "BDAY", + "ADR", + "LABEL", + "TEL", + "EMAIL", + "MAILER", + "TZ", + "GEO", + "TITLE", + "ROLE", + "LOGO", + "AGENT", + "ORG", + "NOTE", + "REV", + "SOUND", + "URL", + "UID", + "KEY", + "NICKNAME", + "CATEGORIES", + "PROID", + "CLASS", + "SORT-STRING", + "X-IRMC-CALL-DATETIME", + "X-BT-SPEEDDIALKEY", + "X-BT-UCI", + "X-BT-UID", + NULL +}; + +#define FILTER_BIT_MAX 63 +#define FILTER_ALL 0xFFFFFFFFFFFFFFFFULL + +#define PBAP_INTERFACE "org.bluez.obex.PhonebookAccess1" +#define ERROR_INTERFACE "org.bluez.obex.Error" +#define PBAP_UUID "0000112f-0000-1000-8000-00805f9b34fb" + +struct pbap_data { + struct obc_session *session; + char *path; + uint16_t version; + uint32_t supported_features; + uint8_t databaseid[16]; + uint8_t primary[16]; + uint8_t secondary[16]; +}; + +struct pending_request { + struct pbap_data *pbap; + DBusMessage *msg; +}; + +static DBusConnection *conn = NULL; + +static struct pending_request *pending_request_new(struct pbap_data *pbap, + DBusMessage *message) +{ + struct pending_request *p; + + p = g_new0(struct pending_request, 1); + p->pbap = pbap; + p->msg = dbus_message_ref(message); + + return p; +} + +static void pending_request_free(struct pending_request *p) +{ + dbus_message_unref(p->msg); + g_free(p); +} + +static void listing_element(GMarkupParseContext *ctxt, + const char *element, + const char **names, + const char **values, + gpointer user_data, + GError **gerr) +{ + DBusMessageIter *item = user_data, entry; + char **key; + const char *handle = NULL, *vcardname = NULL; + + if (g_str_equal(element, "card") != TRUE) + return; + + for (key = (char **) names; *key; key++, values++) { + if (g_str_equal(*key, "handle") == TRUE) + handle = *values; + else if (g_str_equal(*key, "name") == TRUE) + vcardname = *values; + } + + if (!handle || !vcardname) + return; + + dbus_message_iter_open_container(item, DBUS_TYPE_STRUCT, NULL, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &handle); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &vcardname); + dbus_message_iter_close_container(item, &entry); +} + +static const GMarkupParser listing_parser = { + listing_element, + NULL, + NULL, + NULL, + NULL +}; + +static char *build_phonebook_path(const char *location, const char *item) +{ + char *path = NULL, *tmp, *tmp1; + gboolean internal = FALSE; + + if (!g_ascii_strcasecmp(location, "int") || + !g_ascii_strcasecmp(location, "internal")) { + path = g_strdup("/telecom"); + internal = TRUE; + } else if (!g_ascii_strncasecmp(location, "sim", 3)) { + if (strlen(location) == 3) + tmp = g_strdup("sim1"); + else + tmp = g_ascii_strup(location, 4); + + path = g_build_filename("/", tmp, "telecom", NULL); + g_free(tmp); + } else + return NULL; + + if (!g_ascii_strcasecmp(item, "pb") || + !g_ascii_strcasecmp(item, "ich") || + !g_ascii_strcasecmp(item, "och") || + !g_ascii_strcasecmp(item, "mch") || + !g_ascii_strcasecmp(item, "cch") || + (internal && !g_ascii_strcasecmp(item, "spd")) || + (internal && !g_ascii_strcasecmp(item, "fav"))) { + tmp = path; + tmp1 = g_ascii_strdown(item, -1); + path = g_build_filename(tmp, tmp1, NULL); + g_free(tmp); + g_free(tmp1); + } else { + g_free(path); + return NULL; + } + + return path; +} + +/* should only be called inside pbap_set_path */ +static void pbap_reset_path(struct pbap_data *pbap) +{ + if (!pbap->path) + return; + + obc_session_setpath(pbap->session, pbap->path, NULL, NULL, NULL); +} + +static void pbap_setpath_cb(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct pending_request *request = user_data; + struct pbap_data *pbap = request->pbap; + + if (err != NULL) + pbap_reset_path(pbap); + else + g_dbus_emit_property_changed(conn, + obc_session_get_path(pbap->session), + PBAP_INTERFACE, "Folder"); + + if (err) { + DBusMessage *reply = g_dbus_create_error(request->msg, + ERROR_INTERFACE ".Failed", + "%s", err->message); + g_dbus_send_message(conn, reply); + } else + g_dbus_send_reply(conn, request->msg, DBUS_TYPE_INVALID); + + pending_request_free(request); +} + +static void read_version(struct pbap_data *pbap, GObexApparam *apparam) +{ + const guint8 *data; + uint8_t value[16]; + gsize len; + + if (!(pbap->supported_features & FOLDER_VERSION_FEATURE)) + return; + + if (!g_obex_apparam_get_bytes(apparam, PRIMARY_COUNTER_TAG, &data, + &len)) { + len = sizeof(value); + memset(value, 0, len); + data = value; + } + + if (memcmp(pbap->primary, data, len)) { + memcpy(pbap->primary, data, len); + g_dbus_emit_property_changed(conn, + obc_session_get_path(pbap->session), + PBAP_INTERFACE, "PrimaryCounter"); + } + + if (!g_obex_apparam_get_bytes(apparam, SECONDARY_COUNTER_TAG, &data, + &len)) { + len = sizeof(value); + memset(value, 0, len); + data = value; + } + + if (memcmp(pbap->secondary, data, len)) { + memcpy(pbap->secondary, data, len); + g_dbus_emit_property_changed(conn, + obc_session_get_path(pbap->session), + PBAP_INTERFACE, "SecondaryCounter"); + } +} + +static void read_databaseid(struct pbap_data *pbap, GObexApparam *apparam) +{ + const guint8 *data; + guint8 value[16]; + gsize len; + + if (!(pbap->supported_features & DATABASEID_FEATURE)) + return; + + if (!g_obex_apparam_get_bytes(apparam, DATABASEID_TAG, &data, &len)) { + len = sizeof(value); + memset(value, 0, len); + data = value; + } + + if (memcmp(data, pbap->databaseid, len)) { + memcpy(pbap->databaseid, data, len); + g_dbus_emit_property_changed(conn, + obc_session_get_path(pbap->session), + PBAP_INTERFACE, "DatabaseIdentifier"); + } +} + +static void read_return_apparam(struct obc_transfer *transfer, + struct pbap_data *pbap, + guint16 *phone_book_size, + guint8 *new_missed_calls) +{ + GObexApparam *apparam; + + *phone_book_size = 0; + *new_missed_calls = 0; + + apparam = obc_transfer_get_apparam(transfer); + if (apparam == NULL) + return; + + g_obex_apparam_get_uint16(apparam, PHONEBOOKSIZE_TAG, + phone_book_size); + g_obex_apparam_get_uint8(apparam, NEWMISSEDCALLS_TAG, + new_missed_calls); + + read_version(pbap, apparam); + read_databaseid(pbap, apparam); +} + +static void phonebook_size_callback(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct pending_request *request = user_data; + DBusMessage *reply; + guint16 phone_book_size; + guint8 new_missed_calls; + + if (err) { + reply = g_dbus_create_error(request->msg, + ERROR_INTERFACE ".Failed", + "%s", err->message); + goto send; + } + + reply = dbus_message_new_method_return(request->msg); + + read_return_apparam(transfer, request->pbap, &phone_book_size, + &new_missed_calls); + + if (dbus_message_is_method_call(request->msg, PBAP_INTERFACE, + "GetSize")) + dbus_message_append_args(reply, + DBUS_TYPE_UINT16, &phone_book_size, + DBUS_TYPE_INVALID); + +send: + g_dbus_send_message(conn, reply); + pending_request_free(request); +} + +static void pull_vcard_listing_callback(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct pending_request *request = user_data; + GMarkupParseContext *ctxt; + DBusMessage *reply; + DBusMessageIter iter, array; + char *contents; + size_t size; + int perr; + + if (err) { + reply = g_dbus_create_error(request->msg, + ERROR_INTERFACE ".Failed", + "%s", err->message); + goto send; + } + + perr = obc_transfer_get_contents(transfer, &contents, &size); + if (perr < 0) { + reply = g_dbus_create_error(request->msg, + ERROR_INTERFACE ".Failed", + "Error reading contents: %s", + strerror(-perr)); + goto send; + } + + reply = dbus_message_new_method_return(request->msg); + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_STRUCT_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_STRING_AS_STRING + DBUS_STRUCT_END_CHAR_AS_STRING, &array); + ctxt = g_markup_parse_context_new(&listing_parser, 0, &array, NULL); + g_markup_parse_context_parse(ctxt, contents, size, NULL); + g_markup_parse_context_free(ctxt); + dbus_message_iter_close_container(&iter, &array); + g_free(contents); + +send: + g_dbus_send_message(conn, reply); + pending_request_free(request); +} + +static GObexApparam *parse_format(GObexApparam *apparam, DBusMessageIter *iter) +{ + const char *string; + guint8 format; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return NULL; + + dbus_message_iter_get_basic(iter, &string); + + if (!string || g_str_equal(string, "")) + format = FORMAT_VCARD21; + else if (!g_ascii_strcasecmp(string, "vcard21")) + format = FORMAT_VCARD21; + else if (!g_ascii_strcasecmp(string, "vcard30")) + format = FORMAT_VCARD30; + else + return NULL; + + return g_obex_apparam_set_uint8(apparam, FORMAT_TAG, format); +} + +static GObexApparam *parse_order(GObexApparam *apparam, DBusMessageIter *iter) +{ + const char *string; + guint8 order; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return NULL; + + dbus_message_iter_get_basic(iter, &string); + + if (!string || g_str_equal(string, "")) + order = ORDER_INDEXED; + else if (!g_ascii_strcasecmp(string, "indexed")) + order = ORDER_INDEXED; + else if (!g_ascii_strcasecmp(string, "alphanumeric")) + order = ORDER_ALPHANUMERIC; + else if (!g_ascii_strcasecmp(string, "phonetic")) + order = ORDER_PHONETIC; + else + return NULL; + + return g_obex_apparam_set_uint8(apparam, ORDER_TAG, order); +} + +static GObexApparam *parse_offset(GObexApparam *apparam, DBusMessageIter *iter) +{ + guint16 num; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) + return NULL; + + dbus_message_iter_get_basic(iter, &num); + + return g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, num); +} + +static GObexApparam *parse_max_count(GObexApparam *apparam, + DBusMessageIter *iter) +{ + guint16 num; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) + return NULL; + + dbus_message_iter_get_basic(iter, &num); + + return g_obex_apparam_set_uint16(apparam, MAXLISTCOUNT_TAG, num); +} + +static uint64_t get_filter_mask(const char *filterstr) +{ + int i, bit = -1; + + if (!filterstr) + return 0; + + if (!g_ascii_strcasecmp(filterstr, "ALL")) + return FILTER_ALL; + + for (i = 0; filter_list[i] != NULL; i++) + if (!g_ascii_strcasecmp(filterstr, filter_list[i])) + return 1ULL << i; + + if (strlen(filterstr) < 4 || strlen(filterstr) > 5 + || g_ascii_strncasecmp(filterstr, "bit", 3) != 0) + return 0; + + sscanf(&filterstr[3], "%d", &bit); + if (bit >= 0 && bit <= FILTER_BIT_MAX) + return 1ULL << bit; + else + return 0; +} + +static int set_field(guint64 *filter, const char *filterstr) +{ + guint64 mask; + + mask = get_filter_mask(filterstr); + + if (mask == 0) + return -EINVAL; + + *filter |= mask; + return 0; +} + +static GObexApparam *parse_fields(GObexApparam *apparam, DBusMessageIter *iter) +{ + DBusMessageIter array; + guint64 filter = 0; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return NULL; + + dbus_message_iter_recurse(iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { + const char *string; + + dbus_message_iter_get_basic(&array, &string); + + if (set_field(&filter, string) < 0) + return NULL; + + dbus_message_iter_next(&array); + } + + return g_obex_apparam_set_uint64(apparam, FILTER_TAG, filter); +} + +static GObexApparam *parse_filters(GObexApparam *apparam, + DBusMessageIter *iter) +{ + DBusMessageIter array; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return NULL; + + dbus_message_iter_recurse(iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + + dbus_message_iter_recurse(&array, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + if (strcasecmp(key, "Format") == 0) { + if (parse_format(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Order") == 0) { + if (parse_order(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Offset") == 0) { + if (parse_offset(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "MaxCount") == 0) { + if (parse_max_count(apparam, &value) == NULL) + return NULL; + } else if (strcasecmp(key, "Fields") == 0) { + if (parse_fields(apparam, &value) == NULL) + return NULL; + } + + dbus_message_iter_next(&array); + } + + return apparam; +} + +static DBusMessage *pull_phonebook(struct pbap_data *pbap, + DBusMessage *message, + guint8 type, + const char *targetfile, + GObexApparam *apparam) +{ + struct pending_request *request; + struct obc_transfer *transfer; + char *name; + session_callback_t func; + DBusMessage *reply; + GError *err = NULL; + + name = g_strconcat(g_path_skip_root(pbap->path), ".vcf", NULL); + + transfer = obc_transfer_get("x-bt/phonebook", name, targetfile, &err); + if (transfer == NULL) { + g_obex_apparam_free(apparam); + goto fail; + } + + switch (type) { + case PULLPHONEBOOK: + func = NULL; + request = NULL; + break; + case GETPHONEBOOKSIZE: + func = phonebook_size_callback; + request = pending_request_new(pbap, message); + break; + default: + error("Unexpected type : 0x%2x", type); + return NULL; + } + + obc_transfer_set_apparam(transfer, apparam); + + if (!obc_session_queue(pbap->session, transfer, func, request, &err)) { + if (request != NULL) + pending_request_free(request); + + goto fail; + } + + g_free(name); + + if (targetfile == NULL) + return NULL; + + return obc_transfer_create_dbus_reply(transfer, message); + +fail: + g_free(name); + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static DBusMessage *pull_vcard_listing(struct pbap_data *pbap, + DBusMessage *message, const char *name, + GObexApparam *apparam) +{ + struct pending_request *request; + struct obc_transfer *transfer; + GError *err = NULL; + DBusMessage *reply; + + transfer = obc_transfer_get("x-bt/vcard-listing", name, NULL, &err); + if (transfer == NULL) { + g_obex_apparam_free(apparam); + goto fail; + } + + obc_transfer_set_apparam(transfer, apparam); + + request = pending_request_new(pbap, message); + if (obc_session_queue(pbap->session, transfer, + pull_vcard_listing_callback, request, &err)) + return NULL; + + pending_request_free(request); + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static DBusMessage *pbap_select(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct pbap_data *pbap = user_data; + const char *item, *location; + char *path; + struct pending_request *request; + GError *err = NULL; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &location, + DBUS_TYPE_STRING, &item, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + path = build_phonebook_path(location, item); + if (path == NULL) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", + "Invalid path"); + + if (pbap->path != NULL && g_str_equal(pbap->path, path)) { + g_free(path); + return dbus_message_new_method_return(message); + } + + request = pending_request_new(pbap, message); + + obc_session_setpath(pbap->session, path, pbap_setpath_cb, request, + &err); + if (err != NULL) { + DBusMessage *reply; + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", + "%s", err->message); + g_error_free(err); + g_free(path); + pending_request_free(request); + return reply; + } + + g_free(pbap->path); + pbap->path = path; + + return NULL; +} + +static DBusMessage *pbap_pull_all(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct pbap_data *pbap = user_data; + const char *targetfile; + GObexApparam *apparam; + DBusMessageIter args; + + if (!pbap->path) + return g_dbus_create_error(message, + ERROR_INTERFACE ".Forbidden", + "Call Select first of all"); + + dbus_message_iter_init(message, &args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + dbus_message_iter_get_basic(&args, &targetfile); + dbus_message_iter_next(&args); + + apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, + DEFAULT_COUNT); + apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, + DEFAULT_OFFSET); + + if (parse_filters(apparam, &args) == NULL) { + g_obex_apparam_free(apparam); + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + } + + return pull_phonebook(pbap, message, PULLPHONEBOOK, targetfile, + apparam); +} + +static DBusMessage *pull_vcard(struct pbap_data *pbap, DBusMessage *message, + const char *name, const char *targetfile, + GObexApparam *apparam) +{ + struct obc_transfer *transfer; + DBusMessage *reply; + GError *err = NULL; + + transfer = obc_transfer_get("x-bt/vcard", name, targetfile, &err); + if (transfer == NULL) { + g_obex_apparam_free(apparam); + goto fail; + } + + obc_transfer_set_apparam(transfer, apparam); + + if (!obc_session_queue(pbap->session, transfer, NULL, NULL, &err)) + goto fail; + + return obc_transfer_create_dbus_reply(transfer, message); + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static DBusMessage *pbap_pull_vcard(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct pbap_data *pbap = user_data; + GObexApparam *apparam; + const char *name, *targetfile; + DBusMessageIter args; + + if (!pbap->path) + return g_dbus_create_error(message, + ERROR_INTERFACE ".Forbidden", + "Call Select first of all"); + + dbus_message_iter_init(message, &args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + dbus_message_iter_get_basic(&args, &name); + dbus_message_iter_next(&args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + dbus_message_iter_get_basic(&args, &targetfile); + dbus_message_iter_next(&args); + + apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, + DEFAULT_COUNT); + apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, + DEFAULT_OFFSET); + + if (parse_filters(apparam, &args) == NULL) { + g_obex_apparam_free(apparam); + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + } + + return pull_vcard(pbap, message, name, targetfile, apparam); +} + +static DBusMessage *pbap_list(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct pbap_data *pbap = user_data; + GObexApparam *apparam; + DBusMessageIter args; + + if (!pbap->path) + return g_dbus_create_error(message, + ERROR_INTERFACE ".Forbidden", + "Call Select first of all"); + + dbus_message_iter_init(message, &args); + + apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, + DEFAULT_COUNT); + apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, + DEFAULT_OFFSET); + + if (parse_filters(apparam, &args) == NULL) { + g_obex_apparam_free(apparam); + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + } + + return pull_vcard_listing(pbap, message, "", apparam); +} + +static GObexApparam *parse_attribute(GObexApparam *apparam, const char *field) +{ + guint8 attrib; + + if (!field || g_str_equal(field, "")) + attrib = ATTRIB_NAME; + else if (!g_ascii_strcasecmp(field, "name")) + attrib = ATTRIB_NAME; + else if (!g_ascii_strcasecmp(field, "number")) + attrib = ATTRIB_NUMBER; + else if (!g_ascii_strcasecmp(field, "sound")) + attrib = ATTRIB_SOUND; + else + return NULL; + + return g_obex_apparam_set_uint8(apparam, SEARCHATTRIB_TAG, attrib); +} + +static DBusMessage *pbap_search(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct pbap_data *pbap = user_data; + char *field, *value; + GObexApparam *apparam; + DBusMessageIter args; + + if (!pbap->path) + return g_dbus_create_error(message, + ERROR_INTERFACE ".Forbidden", + "Call Select first of all"); + + dbus_message_iter_init(message, &args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + dbus_message_iter_get_basic(&args, &field); + dbus_message_iter_next(&args); + + apparam = parse_attribute(NULL, field); + if (apparam == NULL) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + + dbus_message_iter_get_basic(&args, &value); + dbus_message_iter_next(&args); + + apparam = g_obex_apparam_set_uint16(apparam, MAXLISTCOUNT_TAG, + DEFAULT_COUNT); + apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, + DEFAULT_OFFSET); + apparam = g_obex_apparam_set_string(apparam, SEARCHVALUE_TAG, value); + + if (parse_filters(apparam, &args) == NULL) { + g_obex_apparam_free(apparam); + return g_dbus_create_error(message, + ERROR_INTERFACE ".InvalidArguments", NULL); + } + + return pull_vcard_listing(pbap, message, "", apparam); +} + +static DBusMessage *pbap_get_size(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct pbap_data *pbap = user_data; + GObexApparam *apparam; + DBusMessageIter args; + + if (!pbap->path) + return g_dbus_create_error(message, + ERROR_INTERFACE ".Forbidden", + "Call Select first of all"); + + dbus_message_iter_init(message, &args); + + apparam = g_obex_apparam_set_uint16(NULL, MAXLISTCOUNT_TAG, 0); + apparam = g_obex_apparam_set_uint16(apparam, LISTSTARTOFFSET_TAG, + DEFAULT_OFFSET); + + return pull_phonebook(pbap, message, GETPHONEBOOKSIZE, NULL, apparam); +} + +static char **get_filter_strs(uint64_t filter, int *size) +{ + char **list, **item; + int i; + + list = g_malloc0(sizeof(char **) * (FILTER_BIT_MAX + 2)); + + item = list; + + for (i = 0; filter_list[i] != NULL; i++) + if (filter & (1ULL << i)) + *(item++) = g_strdup(filter_list[i]); + + for (; i <= FILTER_BIT_MAX; i++) + if (filter & (1ULL << i)) + *(item++) = g_strdup_printf("%s%d", "BIT", i); + + *item = NULL; + *size = item - list; + return list; +} + +static DBusMessage *pbap_list_filter_fields(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + char **filters = NULL; + int size; + DBusMessage *reply; + + filters = get_filter_strs(FILTER_ALL, &size); + reply = dbus_message_new_method_return(message); + dbus_message_append_args(reply, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING, &filters, size, + DBUS_TYPE_INVALID); + + g_strfreev(filters); + return reply; +} + +static DBusMessage *pbap_update_version(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct pbap_data *pbap = user_data; + + if (!(pbap->supported_features & FOLDER_VERSION_FEATURE)) + return g_dbus_create_error(message, + ERROR_INTERFACE ".NotSupported", + "Operation is not supported"); + + return pbap_get_size(connection, message, user_data); +} + +static const GDBusMethodTable pbap_methods[] = { + { GDBUS_ASYNC_METHOD("Select", + GDBUS_ARGS({ "location", "s" }, { "phonebook", "s" }), + NULL, pbap_select) }, + { GDBUS_METHOD("PullAll", + GDBUS_ARGS({ "targetfile", "s" }, + { "filters", "a{sv}" }), + GDBUS_ARGS({ "transfer", "o" }, + { "properties", "a{sv}" }), + pbap_pull_all) }, + { GDBUS_METHOD("Pull", + GDBUS_ARGS({ "vcard", "s" }, { "targetfile", "s" }, + { "filters", "a{sv}" }), + GDBUS_ARGS({ "transfer", "o" }, + { "properties", "a{sv}" }), + pbap_pull_vcard) }, + { GDBUS_ASYNC_METHOD("List", + GDBUS_ARGS({ "filters", "a{sv}" }), + GDBUS_ARGS({ "vcard_listing", "a(ss)" }), + pbap_list) }, + { GDBUS_ASYNC_METHOD("Search", + GDBUS_ARGS({ "field", "s" }, { "value", "s" }, + { "filters", "a{sv}" }), + GDBUS_ARGS({ "vcard_listing", "a(ss)" }), + pbap_search) }, + { GDBUS_ASYNC_METHOD("GetSize", + NULL, GDBUS_ARGS({ "size", "q" }), + pbap_get_size) }, + { GDBUS_METHOD("ListFilterFields", + NULL, GDBUS_ARGS({ "fields", "as" }), + pbap_list_filter_fields) }, + { GDBUS_ASYNC_METHOD("UpdateVersion", NULL, NULL, + pbap_update_version) }, + { } +}; + +static gboolean folder_exists(const GDBusPropertyTable *property, void *data) +{ + struct pbap_data *pbap = data; + + return pbap->path != NULL; +} + +static gboolean get_folder(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct pbap_data *pbap = data; + + if (!pbap->path) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pbap->path); + + return TRUE; +} + +static gboolean databaseid_exists(const GDBusPropertyTable *property, + void *data) +{ + struct pbap_data *pbap = data; + + return pbap->supported_features & DATABASEID_FEATURE; +} + +static int u128_to_string(uint8_t *data, char *str, size_t len) +{ + return snprintf(str, len, "%02X%02X%02X%02X%02X%02X%02X%02X" + "%02X%02X%02X%02X%02X%02X%02X%02X", + data[0], data[1], data[2], data[3], + data[3], data[5], data[6], data[7], + data[8], data[9], data[10], data[11], + data[12], data[13], data[14], data[15]); +} + +static gboolean get_databaseid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct pbap_data *pbap = data; + char value[33]; + const char *pvalue = value; + + if (u128_to_string(pbap->databaseid, value, sizeof(value)) < 0) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pvalue); + + return TRUE; +} + +static gboolean version_exists(const GDBusPropertyTable *property, + void *data) +{ + struct pbap_data *pbap = data; + + return pbap->supported_features & FOLDER_VERSION_FEATURE; +} + +static gboolean get_primary(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct pbap_data *pbap = data; + char value[33]; + const char *pvalue = value; + + if (u128_to_string(pbap->primary, value, sizeof(value)) < 0) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pvalue); + + return TRUE; +} + +static gboolean get_secondary(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct pbap_data *pbap = data; + char value[33]; + const char *pvalue = value; + + if (u128_to_string(pbap->secondary, value, sizeof(value)) < 0) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &pvalue); + + return TRUE; +} + +static gboolean image_size_exists(const GDBusPropertyTable *property, + void *data) +{ + struct pbap_data *pbap = data; + + return pbap->supported_features & DEFAULT_IMAGE_FEATURE; +} + +static gboolean get_image_size(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + dbus_bool_t value = TRUE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); + + return TRUE; +} + +static const GDBusPropertyTable pbap_properties[] = { + { "Folder", "s", get_folder, NULL, folder_exists }, + { "DatabaseIdentifier", "s", get_databaseid, NULL, databaseid_exists }, + { "PrimaryCounter", "s", get_primary, NULL, version_exists }, + { "SecondaryCounter", "s", get_secondary, NULL, version_exists }, + { "FixedImageSize", "b", get_image_size, NULL, image_size_exists }, + { } +}; + +static void pbap_free(void *data) +{ + struct pbap_data *pbap = data; + + obc_session_unref(pbap->session); + g_free(pbap->path); + g_free(pbap); +} + +static void parse_service_record(struct pbap_data *pbap) +{ + const void *data; + + /* Version */ + data = obc_session_get_attribute(pbap->session, + SDP_ATTR_PFILE_DESC_LIST); + if (!data) + return; + + pbap->version = GPOINTER_TO_UINT(data); + + /* + * If the PbapSupportedFeatures attribute is not present + * 0x00000003 shall be assumed for a remote PSE. + */ + pbap->supported_features = 0x00000003; + + if (pbap->version < 0x0102) + return; + + /* Supported Feature Bits */ + data = obc_session_get_attribute(pbap->session, + SDP_ATTR_PBAP_SUPPORTED_FEATURES); + if (data) + pbap->supported_features = *(uint32_t *) data; + +} + +static void *pbap_supported_features(struct obc_session *session) +{ + const void *data; + uint16_t version; + + /* Version */ + data = obc_session_get_attribute(session, SDP_ATTR_PFILE_DESC_LIST); + if (!data) + return NULL; + + version = GPOINTER_TO_UINT(data); + + if (version < 0x0102) + return NULL; + + /* Supported Feature Bits */ + data = obc_session_get_attribute(session, + SDP_ATTR_PBAP_SUPPORTED_FEATURES); + if (!data) + return NULL; + + return g_obex_apparam_set_uint32(NULL, SUPPORTED_FEATURES_TAG, + DOWNLOAD_FEATURE | + BROWSE_FEATURE | + DATABASEID_FEATURE | + FOLDER_VERSION_FEATURE | + VCARD_SELECTING_FEATURE | + ENHANCED_CALLS_FEATURE | + UCI_FEATURE | + UID_FEATURE | + REFERENCING_FEATURE | + DEFAULT_IMAGE_FEATURE); +} + +static int pbap_probe(struct obc_session *session) +{ + struct pbap_data *pbap; + const char *path; + + path = obc_session_get_path(session); + + DBG("%s", path); + + pbap = g_try_new0(struct pbap_data, 1); + if (!pbap) + return -ENOMEM; + + pbap->session = obc_session_ref(session); + + parse_service_record(pbap); + + DBG("%s, version 0x%04x supported features 0x%08x", path, pbap->version, + pbap->supported_features); + + if (!g_dbus_register_interface(conn, path, PBAP_INTERFACE, pbap_methods, + NULL, pbap_properties, pbap, + pbap_free)) { + pbap_free(pbap); + return -ENOMEM; + } + + return 0; +} + +static void pbap_remove(struct obc_session *session) +{ + const char *path = obc_session_get_path(session); + + DBG("%s", path); + + g_dbus_unregister_interface(conn, path, PBAP_INTERFACE); +} + +static struct obc_driver pbap = { + .service = "PBAP", + .uuid = PBAP_UUID, + .target = OBEX_PBAP_UUID, + .target_len = OBEX_PBAP_UUID_LEN, + .supported_features = pbap_supported_features, + .probe = pbap_probe, + .remove = pbap_remove +}; + +int pbap_init(void) +{ + int err; + + DBG(""); + + conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); + if (!conn) + return -EIO; + + err = obc_driver_register(&pbap); + if (err < 0) { + dbus_connection_unref(conn); + conn = NULL; + return err; + } + + return 0; +} + +void pbap_exit(void) +{ + DBG(""); + + dbus_connection_unref(conn); + conn = NULL; + + obc_driver_unregister(&pbap); +} diff --git a/obexd/client/pbap.h b/obexd/client/pbap.h new file mode 100644 index 0000000..ce56258 --- /dev/null +++ b/obexd/client/pbap.h @@ -0,0 +1,26 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Intel Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int pbap_init(void); +void pbap_exit(void); diff --git a/obexd/client/session.c b/obexd/client/session.c new file mode 100644 index 0000000..5bd2d26 --- /dev/null +++ b/obexd/client/session.c @@ -0,0 +1,1414 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Marcel Holtmann + * Copyright (C) 2011-2012 BMW Car IT GmbH. 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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" +#include "gobex/gobex.h" + +#include "obexd/src/log.h" +#include "transfer.h" +#include "session.h" +#include "driver.h" +#include "transport.h" + +#define SESSION_INTERFACE "org.bluez.obex.Session1" +#define ERROR_INTERFACE "org.bluez.obex.Error" +#define SESSION_BASEPATH "/org/bluez/obex/client" + +#define OBEX_IO_ERROR obex_io_error_quark() +#define OBEX_IO_ERROR_FIRST (0xff + 1) + +enum { + OBEX_IO_DISCONNECTED = OBEX_IO_ERROR_FIRST, + OBEX_IO_BUSY, +}; + +static guint64 counter = 0; + +struct callback_data { + struct obc_session *session; + guint id; + session_callback_t func; + void *data; +}; + +struct pending_request; +typedef int (*session_process_t) (struct pending_request *p, GError **err); +typedef void (*destroy_t) (void *data); + +struct pending_request { + guint id; + guint req_id; + struct obc_session *session; + session_process_t process; + struct obc_transfer *transfer; + session_callback_t func; + void *data; + destroy_t destroy; +}; + +struct setpath_data { + char **remaining; + int index; + session_callback_t func; + void *user_data; +}; + +struct file_data { + char *srcname; + char *destname; + session_callback_t func; + void *user_data; +}; + +struct obc_session { + guint id; + int refcount; + char *source; + char *destination; + uint8_t channel; + struct obc_transport *transport; + struct obc_driver *driver; + char *path; /* Session path */ + DBusConnection *conn; + GObex *obex; + struct pending_request *p; + char *owner; /* Session owner */ + guint watch; + GQueue *queue; + guint process_id; + char *folder; + struct callback_data *callback; +}; + +static GSList *sessions = NULL; + +static void session_process_queue(struct obc_session *session); +static void session_terminate_transfer(struct obc_session *session, + struct obc_transfer *transfer, + GError *gerr); +static void transfer_complete(struct obc_transfer *transfer, + GError *err, void *user_data); + +static GQuark obex_io_error_quark(void) +{ + return g_quark_from_static_string("obex-io-error-quark"); +} + +struct obc_session *obc_session_ref(struct obc_session *session) +{ + int refs = __sync_add_and_fetch(&session->refcount, 1); + + DBG("%p: ref=%d", session, refs); + + return session; +} + +static void session_unregistered(struct obc_session *session) +{ + char *path; + + if (session->driver && session->driver->remove) + session->driver->remove(session); + + path = session->path; + session->path = NULL; + + g_dbus_unregister_interface(session->conn, path, SESSION_INTERFACE); + + DBG("Session(%p) unregistered %s", session, path); + + g_free(path); +} + +static struct pending_request *pending_request_new(struct obc_session *session, + session_process_t process, + struct obc_transfer *transfer, + session_callback_t func, + void *data, + destroy_t destroy) +{ + struct pending_request *p; + static guint id = 0; + + p = g_new0(struct pending_request, 1); + p->id = ++id; + p->session = obc_session_ref(session); + p->process = process; + p->destroy = destroy; + p->transfer = transfer; + p->func = func; + p->data = data; + + return p; +} + +static void pending_request_free(struct pending_request *p) +{ + if (p->req_id > 0) + g_obex_cancel_req(p->session->obex, p->req_id, TRUE); + + if (p->destroy) + p->destroy(p->data); + + if (p->transfer) + obc_transfer_unregister(p->transfer); + + if (p->session) + obc_session_unref(p->session); + + g_free(p); +} + +static void setpath_data_free(void *process_data) +{ + struct setpath_data *data = process_data; + + g_strfreev(data->remaining); + g_free(data); +} + +static void file_data_free(void *process_data) +{ + struct file_data *data = process_data; + + g_free(data->srcname); + g_free(data->destname); + g_free(data); +} + +static void request_free(void *data, void *user_data) +{ + pending_request_free(data); +} + +static void session_free(struct obc_session *session) +{ + DBG("%p", session); + + if (session->process_id != 0) + g_source_remove(session->process_id); + + if (session->queue) { + g_queue_foreach(session->queue, request_free, NULL); + g_queue_free(session->queue); + } + + if (session->watch) + g_dbus_remove_watch(session->conn, session->watch); + + if (session->obex) { + g_obex_set_disconnect_function(session->obex, NULL, NULL); + g_obex_unref(session->obex); + } + + if (session->id > 0 && session->transport != NULL) + session->transport->disconnect(session->id); + + if (session->path) + session_unregistered(session); + + if (session->conn) + dbus_connection_unref(session->conn); + + if (session->p) + pending_request_free(session->p); + + g_free(session->path); + g_free(session->owner); + g_free(session->source); + g_free(session->destination); + g_free(session->folder); + g_free(session); +} + +static void disconnect_complete(GObex *obex, GError *err, GObexPacket *rsp, + void *user_data) +{ + struct obc_session *session = user_data; + + DBG(""); + + if (err) + error("%s", err->message); + + /* Disconnect transport */ + if (session->id > 0 && session->transport != NULL) { + session->transport->disconnect(session->id); + session->id = 0; + } + + session_free(session); +} + +void obc_session_unref(struct obc_session *session) +{ + int refs; + + refs = __sync_sub_and_fetch(&session->refcount, 1); + + DBG("%p: ref=%d", session, refs); + + if (refs > 0) + return; + + sessions = g_slist_remove(sessions, session); + + if (!session->obex) + goto disconnect; + + /* Wait OBEX Disconnect to complete if command succeed otherwise + * proceed with transport disconnection since there is nothing else to + * be done */ + if (g_obex_disconnect(session->obex, disconnect_complete, session, + NULL)) + return; + +disconnect: + /* Disconnect transport */ + if (session->id > 0 && session->transport != NULL) { + session->transport->disconnect(session->id); + session->id = 0; + } + + session_free(session); +} + +static void callback_destroy(struct callback_data *callback, GError *err) +{ + struct obc_session *session = callback->session; + + if (callback->id > 0) + g_obex_cancel_req(session->obex, callback->id, TRUE); + + callback->func(session, NULL, err, callback->data); + g_free(callback); + session->callback = NULL; + obc_session_unref(session); +} + +static void connect_cb(GObex *obex, GError *err, GObexPacket *rsp, + gpointer user_data) +{ + struct callback_data *callback = user_data; + GError *gerr = NULL; + uint8_t rsp_code; + + callback->id = 0; + + if (err != NULL) { + error("connect_cb: %s", err->message); + gerr = g_error_copy(err); + goto done; + } + + rsp_code = g_obex_packet_get_operation(rsp, NULL); + if (rsp_code != G_OBEX_RSP_SUCCESS) + gerr = g_error_new(OBEX_IO_ERROR, -EIO, + "OBEX Connect failed with 0x%02x", rsp_code); + +done: + callback_destroy(callback, gerr); + if (gerr != NULL) + g_error_free(gerr); +} + +static void session_disconnected(GObex *obex, GError *err, gpointer user_data) +{ + struct obc_session *session = user_data; + + if (err) + error("%s", err->message); + + obc_session_shutdown(session); +} + +static void transport_func(GIOChannel *io, GError *err, gpointer user_data) +{ + struct callback_data *callback = user_data; + struct obc_session *session = callback->session; + struct obc_driver *driver = session->driver; + struct obc_transport *transport = session->transport; + GObex *obex; + GObexApparam *apparam; + GObexTransportType type; + int tx_mtu = -1; + int rx_mtu = -1; + + DBG(""); + + if (err != NULL) { + error("%s", err->message); + goto done; + } + + g_io_channel_set_close_on_unref(io, FALSE); + + if (transport->getpacketopt && + transport->getpacketopt(io, &tx_mtu, &rx_mtu) == 0) + type = G_OBEX_TRANSPORT_PACKET; + else + type = G_OBEX_TRANSPORT_STREAM; + + obex = g_obex_new(io, type, tx_mtu, rx_mtu); + if (obex == NULL) + goto done; + + g_io_channel_set_close_on_unref(io, TRUE); + + apparam = NULL; + + if (driver->supported_features) + apparam = driver->supported_features(session); + + if (apparam) { + uint8_t buf[1024]; + ssize_t len; + + len = g_obex_apparam_encode(apparam, buf, sizeof(buf)); + if (driver->target) + callback->id = g_obex_connect(obex, connect_cb, + callback, &err, + G_OBEX_HDR_TARGET, + driver->target, driver->target_len, + G_OBEX_HDR_APPARAM, + buf, len, + G_OBEX_HDR_INVALID); + else + callback->id = g_obex_connect(obex, connect_cb, + callback, &err, + G_OBEX_HDR_APPARAM, buf, len, + G_OBEX_HDR_INVALID); + g_obex_apparam_free(apparam); + } else if (driver->target) + callback->id = g_obex_connect(obex, connect_cb, callback, &err, + G_OBEX_HDR_TARGET, driver->target, driver->target_len, + G_OBEX_HDR_INVALID); + else + callback->id = g_obex_connect(obex, connect_cb, callback, + &err, G_OBEX_HDR_INVALID); + + if (err != NULL) { + error("%s", err->message); + g_obex_unref(obex); + goto done; + } + + session->obex = obex; + sessions = g_slist_prepend(sessions, session); + + g_obex_set_disconnect_function(obex, session_disconnected, session); + + return; +done: + callback_destroy(callback, err); +} + +static void owner_disconnected(DBusConnection *connection, void *user_data) +{ + struct obc_session *session = user_data; + GError *err; + + DBG(""); + + /* + * If connection still connecting notify the callback to destroy the + * session. + */ + if (session->callback) { + err = g_error_new(OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, + "Session closed by user"); + callback_destroy(session->callback, err); + g_error_free(err); + return; + } + + obc_session_shutdown(session); +} + +int obc_session_set_owner(struct obc_session *session, const char *name, + GDBusWatchFunction func) +{ + if (session == NULL) + return -EINVAL; + + if (session->watch) + g_dbus_remove_watch(session->conn, session->watch); + + session->watch = g_dbus_add_disconnect_watch(session->conn, name, func, + session, NULL); + if (session->watch == 0) + return -EINVAL; + + session->owner = g_strdup(name); + + return 0; +} + + +static struct obc_session *session_find(const char *source, + const char *destination, + const char *service, + uint8_t channel, + const char *owner) +{ + GSList *l; + + for (l = sessions; l; l = l->next) { + struct obc_session *session = l->data; + + if (g_strcmp0(session->destination, destination)) + continue; + + if (g_strcmp0(service, session->driver->service)) + continue; + + if (source && g_strcmp0(session->source, source)) + continue; + + if (channel && session->channel != channel) + continue; + + if (g_strcmp0(owner, session->owner)) + continue; + + return session; + } + + return NULL; +} + +static gboolean connection_complete(gpointer data) +{ + struct callback_data *cb = data; + + cb->func(cb->session, NULL, NULL, cb->data); + + obc_session_unref(cb->session); + + g_free(cb); + + return FALSE; +} + +static int session_connect(struct obc_session *session, + session_callback_t function, void *user_data) +{ + struct callback_data *callback; + struct obc_transport *transport = session->transport; + struct obc_driver *driver = session->driver; + + callback = g_try_malloc0(sizeof(*callback)); + if (callback == NULL) + return -ENOMEM; + + callback->func = function; + callback->data = user_data; + callback->session = obc_session_ref(session); + + /* Connection completed */ + if (session->obex) { + g_idle_add(connection_complete, callback); + return 0; + } + + /* Ongoing connection */ + if (session->id > 0) { + obc_session_unref(callback->session); + g_free(callback); + return 0; + } + + session->id = transport->connect(session->source, session->destination, + driver->uuid, session->channel, + transport_func, callback); + if (session->id == 0) { + obc_session_unref(callback->session); + g_free(callback); + return -EINVAL; + } + + session->callback = callback; + + return 0; +} + +struct obc_session *obc_session_create(const char *source, + const char *destination, + const char *service, + uint8_t channel, + const char *owner, + session_callback_t function, + void *user_data) +{ + DBusConnection *conn; + struct obc_session *session; + struct obc_transport *transport; + struct obc_driver *driver; + + if (destination == NULL) + return NULL; + + session = session_find(source, destination, service, channel, owner); + if (session != NULL) + goto proceed; + + /* FIXME: Do proper transport lookup when the API supports it */ + transport = obc_transport_find("Bluetooth"); + if (transport == NULL) + return NULL; + + driver = obc_driver_find(service); + if (driver == NULL) + return NULL; + + conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); + if (conn == NULL) + return NULL; + + session = g_try_malloc0(sizeof(*session)); + if (session == NULL) + return NULL; + + session->refcount = 1; + session->transport = transport; + session->driver = driver; + session->conn = conn; + session->source = g_strdup(source); + session->destination = g_strdup(destination); + session->channel = channel; + session->queue = g_queue_new(); + session->folder = g_strdup("/"); + + if (owner) + obc_session_set_owner(session, owner, owner_disconnected); + +proceed: + if (session_connect(session, function, user_data) < 0) { + obc_session_unref(session); + return NULL; + } + + DBG("session %p transport %s driver %s", session, + session->transport->name, session->driver->service); + + return session; +} + +void obc_session_shutdown(struct obc_session *session) +{ + struct pending_request *p; + GError *err; + + DBG("%p", session); + + obc_session_ref(session); + + /* Unregister any pending transfer */ + err = g_error_new(OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, + "Session closed by user"); + + if (session->p != NULL && session->p->id != 0) { + p = session->p; + session->p = NULL; + + if (p->func) + p->func(session, p->transfer, err, p->data); + + pending_request_free(p); + } + + while ((p = g_queue_pop_head(session->queue))) { + if (p->func) + p->func(session, p->transfer, err, p->data); + + pending_request_free(p); + } + + g_error_free(err); + + /* Unregister interfaces */ + if (session->path) + session_unregistered(session); + + obc_session_unref(session); +} + +static void capabilities_complete_callback(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + DBusMessage *message = user_data; + char *contents; + size_t size; + int perr; + + if (err != NULL) { + DBusMessage *error = g_dbus_create_error(message, + ERROR_INTERFACE ".Failed", + "%s", err->message); + g_dbus_send_message(session->conn, error); + goto done; + } + + perr = obc_transfer_get_contents(transfer, &contents, &size); + if (perr < 0) { + DBusMessage *error = g_dbus_create_error(message, + ERROR_INTERFACE ".Failed", + "Error reading contents: %s", + strerror(-perr)); + g_dbus_send_message(session->conn, error); + goto done; + } + + g_dbus_send_reply(session->conn, message, + DBUS_TYPE_STRING, &contents, + DBUS_TYPE_INVALID); + g_free(contents); + +done: + dbus_message_unref(message); +} + +static DBusMessage *get_capabilities(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct obc_session *session = user_data; + struct obc_transfer *pull; + DBusMessage *reply; + GError *gerr = NULL; + + pull = obc_transfer_get("x-obex/capability", NULL, NULL, &gerr); + if (pull == NULL) + goto fail; + + if (!obc_session_queue(session, pull, capabilities_complete_callback, + message, &gerr)) + goto fail; + + dbus_message_ref(message); + + return NULL; + +fail: + reply = g_dbus_create_error(message, ERROR_INTERFACE ".Failed", "%s", + gerr->message); + g_error_free(gerr); + return reply; + +} + +static gboolean get_source(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obc_session *session = data; + + if (session->source == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &session->source); + + return TRUE; +} + +static gboolean source_exists(const GDBusPropertyTable *property, void *data) +{ + struct obc_session *session = data; + + return session->source != NULL; +} + +static gboolean get_destination(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obc_session *session = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &session->destination); + + return TRUE; +} + +static gboolean get_channel(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obc_session *session = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, + &session->channel); + + return TRUE; +} + +static const GDBusMethodTable session_methods[] = { + { GDBUS_ASYNC_METHOD("GetCapabilities", + NULL, GDBUS_ARGS({ "capabilities", "s" }), + get_capabilities) }, + { } +}; + +static gboolean get_target(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obc_session *session = data; + + if (session->driver->uuid == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &session->driver->uuid); + + return TRUE; +} + +static gboolean target_exists(const GDBusPropertyTable *property, void *data) +{ + struct obc_session *session = data; + + return session->driver->uuid != NULL; +} + +static const GDBusPropertyTable session_properties[] = { + { "Source", "s", get_source, NULL, source_exists }, + { "Destination", "s", get_destination }, + { "Channel", "y", get_channel }, + { "Target", "s", get_target, NULL, target_exists }, + { } +}; + +static gboolean session_process(gpointer data) +{ + struct obc_session *session = data; + + session->process_id = 0; + + session_process_queue(session); + + return FALSE; +} + +static void session_queue(struct pending_request *p) +{ + g_queue_push_tail(p->session->queue, p); + + if (p->session->process_id == 0) + p->session->process_id = g_idle_add(session_process, + p->session); +} + +static int session_process_transfer(struct pending_request *p, GError **err) +{ + if (!obc_transfer_start(p->transfer, p->session->obex, err)) + return -1; + + DBG("Tranfer(%p) started", p->transfer); + p->session->p = p; + return 0; +} + +guint obc_session_queue(struct obc_session *session, + struct obc_transfer *transfer, + session_callback_t func, void *user_data, + GError **err) +{ + struct pending_request *p; + + if (session->obex == NULL) { + obc_transfer_unregister(transfer); + g_set_error(err, OBEX_IO_ERROR, -ENOTCONN, + "Session not connected"); + return 0; + } + + if (!obc_transfer_register(transfer, session->conn, session->path, + session->owner, err)) { + obc_transfer_unregister(transfer); + return 0; + } + + obc_transfer_set_callback(transfer, transfer_complete, session); + + p = pending_request_new(session, session_process_transfer, transfer, + func, user_data, NULL); + session_queue(p); + return p->id; +} + +static void session_process_queue(struct obc_session *session) +{ + struct pending_request *p; + + if (session->p != NULL) + return; + + if (session->queue == NULL || g_queue_is_empty(session->queue)) + return; + + obc_session_ref(session); + + while ((p = g_queue_pop_head(session->queue))) { + GError *gerr = NULL; + + if (p->process(p, &gerr) == 0) + break; + + if (p->func) + p->func(session, p->transfer, gerr, p->data); + + g_clear_error(&gerr); + + pending_request_free(p); + } + + obc_session_unref(session); +} + +static int pending_transfer_cmptransfer(gconstpointer a, gconstpointer b) +{ + const struct pending_request *p = a; + const struct obc_transfer *transfer = b; + + if (p->transfer == transfer) + return 0; + + return -1; +} + +static void session_terminate_transfer(struct obc_session *session, + struct obc_transfer *transfer, + GError *gerr) +{ + struct pending_request *p = session->p; + + if (p == NULL || p->transfer != transfer) { + GList *match; + + match = g_list_find_custom(session->queue->head, transfer, + pending_transfer_cmptransfer); + if (match == NULL) + return; + + p = match->data; + g_queue_delete_link(session->queue, match); + } else + session->p = NULL; + + obc_session_ref(session); + + if (p->func) + p->func(session, p->transfer, gerr, p->data); + + pending_request_free(p); + + if (session->p == NULL) + session_process_queue(session); + + obc_session_unref(session); +} + +static void session_notify_complete(struct obc_session *session, + struct obc_transfer *transfer) +{ + DBG("Transfer(%p) complete", transfer); + + session_terminate_transfer(session, transfer, NULL); +} + +static void session_notify_error(struct obc_session *session, + struct obc_transfer *transfer, + GError *err) +{ + error("Transfer(%p) Error: %s", transfer, err->message); + + session_terminate_transfer(session, transfer, err); +} + +static void transfer_complete(struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct obc_session *session = user_data; + + if (err != 0) + goto fail; + + session_notify_complete(session, transfer); + + return; + +fail: + session_notify_error(session, transfer, err); +} + +const char *obc_session_register(struct obc_session *session, + GDBusDestroyFunction destroy) +{ + if (session->path) + return session->path; + + session->path = g_strdup_printf("%s/session%ju", + SESSION_BASEPATH, counter++); + + if (g_dbus_register_interface(session->conn, session->path, + SESSION_INTERFACE, session_methods, + NULL, session_properties, session, + destroy) == FALSE) + goto fail; + + if (session->driver->probe && session->driver->probe(session) < 0) { + g_dbus_unregister_interface(session->conn, session->path, + SESSION_INTERFACE); + goto fail; + } + + DBG("Session(%p) registered %s", session, session->path); + + return session->path; + +fail: + g_free(session->path); + session->path = NULL; + return NULL; +} + +const void *obc_session_get_attribute(struct obc_session *session, + int attribute_id) +{ + if (session == NULL || session->id == 0) + return NULL; + + return session->transport->getattribute(session->id, attribute_id); +} + +const char *obc_session_get_owner(struct obc_session *session) +{ + if (session == NULL) + return NULL; + + return session->owner; +} + +const char *obc_session_get_destination(struct obc_session *session) +{ + return session->destination; +} + +const char *obc_session_get_path(struct obc_session *session) +{ + return session->path; +} + +const char *obc_session_get_target(struct obc_session *session) +{ + return session->driver->target; +} + +const char *obc_session_get_folder(struct obc_session *session) +{ + return session->folder; +} + +static void setpath_complete(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct pending_request *p = user_data; + + if (p->func) + p->func(session, NULL, err, p->data); + + if (session->p == p) + session->p = NULL; + + pending_request_free(p); + + session_process_queue(session); +} + +static void setpath_op_complete(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct setpath_data *data = user_data; + + if (data->func) + data->func(session, NULL, err, data->user_data); +} + +static void setpath_set_folder(struct obc_session *session, const char *cur) +{ + char *folder = NULL; + const char *delim; + + delim = strrchr(session->folder, '/'); + if (strlen(cur) == 0 || delim == NULL || + (strcmp(cur, "..") == 0 && delim == session->folder)) { + folder = g_strdup("/"); + } else { + if (strcmp(cur, "..") == 0) { + folder = g_strndup(session->folder, + delim - session->folder); + } else { + if (g_str_has_suffix(session->folder, "/")) + folder = g_strconcat(session->folder, + cur, NULL); + else + folder = g_strconcat(session->folder, "/", + cur, NULL); + } + } + g_free(session->folder); + session->folder = folder; +} + +static void setpath_cb(GObex *obex, GError *err, GObexPacket *rsp, + gpointer user_data) +{ + struct pending_request *p = user_data; + struct setpath_data *data = p->data; + char *next; + char *current; + guint8 code; + + p->req_id = 0; + + if (err != NULL) { + setpath_complete(p->session, NULL, err, user_data); + return; + } + + code = g_obex_packet_get_operation(rsp, NULL); + if (code != G_OBEX_RSP_SUCCESS) { + GError *gerr = NULL; + g_set_error(&gerr, OBEX_IO_ERROR, code, "%s", + g_obex_strerror(code)); + setpath_complete(p->session, NULL, gerr, user_data); + g_clear_error(&gerr); + return; + } + + current = data->remaining[data->index - 1]; + setpath_set_folder(p->session, current); + + /* Ignore empty folder names to avoid resetting the current path */ + while ((next = data->remaining[data->index]) && strlen(next) == 0) + data->index++; + + if (next == NULL) { + setpath_complete(p->session, NULL, NULL, user_data); + return; + } + + data->index++; + + p->req_id = g_obex_setpath(obex, next, setpath_cb, p, &err); + if (err != NULL) { + setpath_complete(p->session, NULL, err, user_data); + g_error_free(err); + } +} + +static int session_process_setpath(struct pending_request *p, GError **err) +{ + struct setpath_data *req = p->data; + const char *first = ""; + + /* Relative path */ + if (req->remaining[0][0] != '/') + first = req->remaining[req->index]; + req->index++; + + p->req_id = g_obex_setpath(p->session->obex, first, setpath_cb, p, err); + if (*err != NULL) + return (*err)->code; + + p->session->p = p; + + return 0; +} + +guint obc_session_setpath(struct obc_session *session, const char *path, + session_callback_t func, void *user_data, + GError **err) +{ + struct setpath_data *data; + struct pending_request *p; + + if (session->obex == NULL) { + g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, + "Session disconnected"); + return 0; + } + + data = g_new0(struct setpath_data, 1); + data->func = func; + data->user_data = user_data; + data->remaining = g_strsplit(strlen(path) ? path : "/", "/", 0); + + if (!data->remaining || !data->remaining[0]) { + error("obc_session_setpath: invalid path %s", path); + g_set_error(err, OBEX_IO_ERROR, -EINVAL, "Invalid argument"); + setpath_data_free(data); + return 0; + } + + p = pending_request_new(session, session_process_setpath, NULL, + setpath_op_complete, data, setpath_data_free); + session_queue(p); + return p->id; +} + +static void async_cb(GObex *obex, GError *err, GObexPacket *rsp, + gpointer user_data) +{ + struct pending_request *p = user_data; + struct obc_session *session = p->session; + GError *gerr = NULL; + uint8_t code; + + p->req_id = 0; + + if (err != NULL) { + if (p->func) + p->func(p->session, NULL, err, p->data); + goto done; + } + + code = g_obex_packet_get_operation(rsp, NULL); + if (code != G_OBEX_RSP_SUCCESS) + g_set_error(&gerr, OBEX_IO_ERROR, code, "%s", + g_obex_strerror(code)); + + if (p->func) + p->func(p->session, NULL, gerr, p->data); + + if (gerr != NULL) + g_clear_error(&gerr); + +done: + pending_request_free(p); + session->p = NULL; + + session_process_queue(session); +} + +static void file_op_complete(struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data) +{ + struct file_data *data = user_data; + + if (data->func) + data->func(session, NULL, err, data->user_data); +} + +static int session_process_mkdir(struct pending_request *p, GError **err) +{ + struct file_data *req = p->data; + + p->req_id = g_obex_mkdir(p->session->obex, req->srcname, async_cb, p, + err); + if (*err != NULL) + return (*err)->code; + + p->session->p = p; + + return 0; +} + +guint obc_session_mkdir(struct obc_session *session, const char *folder, + session_callback_t func, void *user_data, + GError **err) +{ + struct file_data *data; + struct pending_request *p; + + if (session->obex == NULL) { + g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, + "Session disconnected"); + return 0; + } + + data = g_new0(struct file_data, 1); + data->srcname = g_strdup(folder); + data->func = func; + data->user_data = user_data; + + p = pending_request_new(session, session_process_mkdir, NULL, + file_op_complete, data, file_data_free); + session_queue(p); + return p->id; +} + +static int session_process_copy(struct pending_request *p, GError **err) +{ + struct file_data *req = p->data; + + p->req_id = g_obex_copy(p->session->obex, req->srcname, req->destname, + async_cb, p, err); + if (*err != NULL) + return (*err)->code; + + p->session->p = p; + + return 0; +} + +guint obc_session_copy(struct obc_session *session, const char *srcname, + const char *destname, session_callback_t func, + void *user_data, GError **err) +{ + struct file_data *data; + struct pending_request *p; + + if (session->obex == NULL) { + g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, + "Session disconnected"); + return 0; + } + + data = g_new0(struct file_data, 1); + data->srcname = g_strdup(srcname); + data->destname = g_strdup(destname); + data->func = func; + data->user_data = user_data; + + p = pending_request_new(session, session_process_copy, NULL, + file_op_complete, data, file_data_free); + session_queue(p); + return p->id; +} + +static int session_process_move(struct pending_request *p, GError **err) +{ + struct file_data *req = p->data; + + p->req_id = g_obex_move(p->session->obex, req->srcname, req->destname, + async_cb, p, err); + if (*err != NULL) + return (*err)->code; + + p->session->p = p; + + return 0; +} + +guint obc_session_move(struct obc_session *session, const char *srcname, + const char *destname, session_callback_t func, + void *user_data, GError **err) +{ + struct file_data *data; + struct pending_request *p; + + if (session->obex == NULL) { + g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, + "Session disconnected"); + return 0; + } + + data = g_new0(struct file_data, 1); + data->srcname = g_strdup(srcname); + data->destname = g_strdup(destname); + data->func = func; + data->user_data = user_data; + + p = pending_request_new(session, session_process_move, NULL, + file_op_complete, data, file_data_free); + session_queue(p); + return p->id; +} + +static int session_process_delete(struct pending_request *p, GError **err) +{ + struct file_data *req = p->data; + + p->req_id = g_obex_delete(p->session->obex, req->srcname, async_cb, p, + err); + if (*err != NULL) + return (*err)->code; + + p->session->p = p; + + return 0; +} + +guint obc_session_delete(struct obc_session *session, const char *file, + session_callback_t func, void *user_data, + GError **err) +{ + struct file_data *data; + struct pending_request *p; + + if (session->obex == NULL) { + g_set_error(err, OBEX_IO_ERROR, OBEX_IO_DISCONNECTED, + "Session disconnected"); + return 0; + } + + data = g_new0(struct file_data, 1); + data->srcname = g_strdup(file); + data->func = func; + data->user_data = user_data; + + p = pending_request_new(session, session_process_delete, NULL, + file_op_complete, data, file_data_free); + session_queue(p); + return p->id; +} + +void obc_session_cancel(struct obc_session *session, guint id, + gboolean remove) +{ + struct pending_request *p = session->p; + + if (p == NULL || p->id != id) + return; + + if (p->req_id == 0) + return; + + g_obex_cancel_req(session->obex, p->req_id, remove); + p->req_id = 0; + + if (!remove) + return; + + pending_request_free(p); + session->p = NULL; + + session_process_queue(session); +} diff --git a/obexd/client/session.h b/obexd/client/session.h new file mode 100644 index 0000000..b561b7e --- /dev/null +++ b/obexd/client/session.h @@ -0,0 +1,82 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Marcel Holtmann + * Copyright (C) 2011-2012 BMW Car IT GmbH. 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 + * + */ + +#include +#include + +struct obc_session; + +typedef void (*session_callback_t) (struct obc_session *session, + struct obc_transfer *transfer, + GError *err, void *user_data); + +struct obc_session *obc_session_create(const char *source, + const char *destination, + const char *service, + uint8_t channel, + const char *owner, + session_callback_t function, + void *user_data); + +struct obc_session *obc_session_ref(struct obc_session *session); +void obc_session_unref(struct obc_session *session); +void obc_session_shutdown(struct obc_session *session); + +int obc_session_set_owner(struct obc_session *session, const char *name, + GDBusWatchFunction func); +const char *obc_session_get_owner(struct obc_session *session); + +const char *obc_session_get_destination(struct obc_session *session); +const char *obc_session_get_path(struct obc_session *session); +const char *obc_session_get_target(struct obc_session *session); + +const char *obc_session_register(struct obc_session *session, + GDBusDestroyFunction destroy); + +const void *obc_session_get_attribute(struct obc_session *session, + int attribute_id); + +const char *obc_session_get_folder(struct obc_session *session); + +guint obc_session_queue(struct obc_session *session, + struct obc_transfer *transfer, + session_callback_t func, void *user_data, + GError **err); +guint obc_session_setpath(struct obc_session *session, const char *path, + session_callback_t func, void *user_data, + GError **err); +guint obc_session_mkdir(struct obc_session *session, const char *folder, + session_callback_t func, void *user_data, + GError **err); +guint obc_session_copy(struct obc_session *session, const char *srcname, + const char *destname, session_callback_t func, + void *user_data, GError **err); +guint obc_session_move(struct obc_session *session, const char *srcname, + const char *destname, session_callback_t func, + void *user_data, GError **err); +guint obc_session_delete(struct obc_session *session, const char *file, + session_callback_t func, void *user_data, + GError **err); +void obc_session_cancel(struct obc_session *session, guint id, + gboolean remove); diff --git a/obexd/client/sync.c b/obexd/client/sync.c new file mode 100644 index 0000000..548c318 --- /dev/null +++ b/obexd/client/sync.c @@ -0,0 +1,262 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Intel Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "gdbus/gdbus.h" + +#include "obexd/src/log.h" + +#include "transfer.h" +#include "session.h" +#include "driver.h" +#include "sync.h" + +#define OBEX_SYNC_UUID "IRMC-SYNC" +#define OBEX_SYNC_UUID_LEN 9 + +#define SYNC_INTERFACE "org.bluez.obex.Synchronization1" +#define ERROR_INF SYNC_INTERFACE ".Error" +#define SYNC_UUID "00001104-0000-1000-8000-00805f9b34fb" + +struct sync_data { + struct obc_session *session; + char *phonebook_path; + DBusMessage *msg; +}; + +static DBusConnection *conn = NULL; + +static DBusMessage *sync_setlocation(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct sync_data *sync = user_data; + const char *location; + char *path = NULL, *tmp; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &location, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INF ".InvalidArguments", NULL); + + if (!g_ascii_strcasecmp(location, "int") || + !g_ascii_strcasecmp(location, "internal")) + path = g_strdup("telecom/pb.vcf"); + else if (!g_ascii_strncasecmp(location, "sim", 3)) { + tmp = g_ascii_strup(location, 4); + path = g_build_filename(tmp, "telecom/pb.vcf", NULL); + g_free(tmp); + } else + return g_dbus_create_error(message, + ERROR_INF ".InvalidArguments", "InvalidPhonebook"); + + g_free(sync->phonebook_path); + sync->phonebook_path = path; + + return dbus_message_new_method_return(message); +} + +static DBusMessage *sync_getphonebook(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct sync_data *sync = user_data; + struct obc_transfer *transfer; + const char *target_file; + GError *err = NULL; + DBusMessage *reply; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &target_file, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INF ".InvalidArguments", + "Invalid arguments in method call"); + + if (sync->msg) + return g_dbus_create_error(message, + ERROR_INF ".InProgress", "Transfer in progress"); + + /* set default phonebook_path to memory internal phonebook */ + if (!sync->phonebook_path) + sync->phonebook_path = g_strdup("telecom/pb.vcf"); + + transfer = obc_transfer_get("phonebook", sync->phonebook_path, + target_file, &err); + if (transfer == NULL) + goto fail; + + if (!obc_session_queue(sync->session, transfer, NULL, NULL, &err)) + goto fail; + + return obc_transfer_create_dbus_reply(transfer, message); + +fail: + reply = g_dbus_create_error(message, ERROR_INF ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static DBusMessage *sync_putphonebook(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct sync_data *sync = user_data; + struct obc_transfer *transfer; + const char *source_file; + GError *err = NULL; + DBusMessage *reply; + + if (dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &source_file, + DBUS_TYPE_INVALID) == FALSE) + return g_dbus_create_error(message, + ERROR_INF ".InvalidArguments", + "Invalid arguments in method call"); + + /* set default phonebook_path to memory internal phonebook */ + if (!sync->phonebook_path) + sync->phonebook_path = g_strdup("telecom/pb.vcf"); + + transfer = obc_transfer_put(NULL, sync->phonebook_path, source_file, + NULL, 0, &err); + if (transfer == NULL) + goto fail; + + if (!obc_session_queue(sync->session, transfer, NULL, NULL, &err)) + goto fail; + + return obc_transfer_create_dbus_reply(transfer, message); + +fail: + reply = g_dbus_create_error(message, ERROR_INF ".Failed", "%s", + err->message); + g_error_free(err); + return reply; +} + +static const GDBusMethodTable sync_methods[] = { + { GDBUS_METHOD("SetLocation", + GDBUS_ARGS({ "location", "s" }), NULL, + sync_setlocation) }, + { GDBUS_METHOD("GetPhonebook", + GDBUS_ARGS({ "targetfile", "s" }), + GDBUS_ARGS({ "transfer", "o" }, + { "properties", "a{sv}" }), + sync_getphonebook) }, + { GDBUS_METHOD("PutPhonebook", + GDBUS_ARGS({ "sourcefile", "s" }), + GDBUS_ARGS({ "transfer", "o" }, + { "properties", "a{sv}" }), + sync_putphonebook) }, + { } +}; + +static void sync_free(void *data) +{ + struct sync_data *sync = data; + + obc_session_unref(sync->session); + g_free(sync->phonebook_path); + g_free(sync); +} + +static int sync_probe(struct obc_session *session) +{ + struct sync_data *sync; + const char *path; + + path = obc_session_get_path(session); + + DBG("%s", path); + + sync = g_try_new0(struct sync_data, 1); + if (!sync) + return -ENOMEM; + + sync->session = obc_session_ref(session); + + if (!g_dbus_register_interface(conn, path, SYNC_INTERFACE, sync_methods, + NULL, NULL, sync, sync_free)) { + sync_free(sync); + return -ENOMEM; + } + + return 0; +} + +static void sync_remove(struct obc_session *session) +{ + const char *path = obc_session_get_path(session); + + DBG("%s", path); + + g_dbus_unregister_interface(conn, path, SYNC_INTERFACE); +} + +static struct obc_driver sync = { + .service = "SYNC", + .uuid = SYNC_UUID, + .target = OBEX_SYNC_UUID, + .target_len = OBEX_SYNC_UUID_LEN, + .probe = sync_probe, + .remove = sync_remove +}; + +int sync_init(void) +{ + int err; + + DBG(""); + + conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); + if (!conn) + return -EIO; + + err = obc_driver_register(&sync); + if (err < 0) { + dbus_connection_unref(conn); + conn = NULL; + return err; + } + + return 0; +} + +void sync_exit(void) +{ + DBG(""); + + dbus_connection_unref(conn); + conn = NULL; + + obc_driver_unregister(&sync); +} diff --git a/obexd/client/sync.h b/obexd/client/sync.h new file mode 100644 index 0000000..8adc5f8 --- /dev/null +++ b/obexd/client/sync.h @@ -0,0 +1,26 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Intel Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int sync_init(void); +void sync_exit(void); diff --git a/obexd/client/transfer.c b/obexd/client/transfer.c new file mode 100644 index 0000000..b53dffa --- /dev/null +++ b/obexd/client/transfer.c @@ -0,0 +1,980 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Marcel Holtmann + * Copyright (C) 2011-2012 BMW Car IT GmbH. 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" +#include "gobex/gobex.h" + +#include "obexd/src/log.h" +#include "transfer.h" + +#define TRANSFER_INTERFACE "org.bluez.obex.Transfer1" +#define ERROR_INTERFACE "org.bluez.obex.Error" + +#define OBC_TRANSFER_ERROR obc_transfer_error_quark() + +#define FIRST_PACKET_TIMEOUT 60 + +static guint64 counter = 0; + +struct transfer_callback { + transfer_callback_t func; + void *data; +}; + +enum { + TRANSFER_STATUS_QUEUED = 0, + TRANSFER_STATUS_ACTIVE, + TRANSFER_STATUS_SUSPENDED, + TRANSFER_STATUS_COMPLETE, + TRANSFER_STATUS_SUSPENDED_QUEUED, + TRANSFER_STATUS_ERROR +}; + +struct obc_transfer { + GObex *obex; + uint8_t status; + GObexApparam *apparam; + guint8 op; + struct transfer_callback *callback; + DBusConnection *conn; + DBusMessage *msg; + char *session; /* Session path */ + char *owner; /* Transfer initiator */ + char *path; /* Transfer path */ + char *filename; /* Transfer file location */ + char *name; /* Transfer object name */ + char *type; /* Transfer object type */ + int fd; + guint req; + guint xfer; + gint64 size; + gint64 transferred; + gint64 progress; + guint progress_id; +}; + +static GQuark obc_transfer_error_quark(void) +{ + return g_quark_from_static_string("obc-transfer-error-quark"); +} + +DBusMessage *obc_transfer_create_dbus_reply(struct obc_transfer *transfer, + DBusMessage *message) +{ + DBusMessage *reply; + DBusMessageIter iter; + + reply = dbus_message_new_method_return(message); + if (reply == NULL) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, + &transfer->path); + g_dbus_get_properties(transfer->conn, transfer->path, + TRANSFER_INTERFACE, &iter); + + return reply; +} + +static void abort_complete(GObex *obex, GError *err, gpointer user_data) +{ + struct obc_transfer *transfer = user_data; + struct transfer_callback *callback = transfer->callback; + DBusMessage *reply; + + transfer->xfer = 0; + + reply = dbus_message_new_method_return(transfer->msg); + if (reply) + g_dbus_send_message(transfer->conn, reply); + + dbus_message_unref(transfer->msg); + transfer->msg = NULL; + + if (callback == NULL) + return; + + if (err) { + callback->func(transfer, err, callback->data); + } else { + GError *abort_err; + + abort_err = g_error_new(OBC_TRANSFER_ERROR, -ECANCELED, "%s", + "Transfer cancelled by user"); + callback->func(transfer, abort_err, callback->data); + g_error_free(abort_err); + } +} + +static DBusMessage *obc_transfer_cancel(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct obc_transfer *transfer = user_data; + const char *sender; + + sender = dbus_message_get_sender(message); + if (g_strcmp0(transfer->owner, sender) != 0) + return g_dbus_create_error(message, + ERROR_INTERFACE ".NotAuthorized", + "Not Authorized"); + + if (transfer->msg != NULL) + return g_dbus_create_error(message, + ERROR_INTERFACE ".InProgress", + "Cancellation already in progress"); + + if (transfer->status == TRANSFER_STATUS_SUSPENDED) + g_obex_resume(transfer->obex); + + if (transfer->req > 0) { + if (!g_obex_cancel_req(transfer->obex, transfer->req, TRUE)) + return g_dbus_create_error(message, + ERROR_INTERFACE ".Failed", + "Failed"); + transfer->req = 0; + } + + if (transfer->xfer == 0) { + struct transfer_callback *callback = transfer->callback; + + if (callback != NULL) { + GError *err; + + err = g_error_new(OBC_TRANSFER_ERROR, -ECANCELED, "%s", + "Transfer cancelled by user"); + callback->func(transfer, err, callback->data); + g_error_free(err); + } + + return dbus_message_new_method_return(message); + } + + if (transfer->progress_id != 0) { + g_source_remove(transfer->progress_id); + transfer->progress_id = 0; + } + + if (!g_obex_cancel_transfer(transfer->xfer, abort_complete, transfer)) + return g_dbus_create_error(message, + ERROR_INTERFACE ".Failed", + "Failed"); + + transfer->msg = dbus_message_ref(message); + + return NULL; +} + +static void transfer_set_status(struct obc_transfer *transfer, uint8_t status) +{ + if (transfer->status == status) + return; + + transfer->status = status; + + g_dbus_emit_property_changed(transfer->conn, transfer->path, + TRANSFER_INTERFACE, "Status"); +} + +static DBusMessage *obc_transfer_suspend(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct obc_transfer *transfer = user_data; + const char *sender; + uint8_t status; + + sender = dbus_message_get_sender(message); + if (g_strcmp0(transfer->owner, sender) != 0) + return g_dbus_create_error(message, + ERROR_INTERFACE ".NotAuthorized", + "Not Authorized"); + + switch (transfer->status) { + case TRANSFER_STATUS_QUEUED: + status = TRANSFER_STATUS_SUSPENDED_QUEUED; + break; + case TRANSFER_STATUS_ACTIVE: + if (transfer->xfer) + g_obex_suspend(transfer->obex); + status = TRANSFER_STATUS_SUSPENDED; + break; + default: + return g_dbus_create_error(message, + ERROR_INTERFACE ".NotInProgress", + "Not in progress"); + } + + transfer_set_status(transfer, status); + + return g_dbus_create_reply(message, DBUS_TYPE_INVALID); +} + +static DBusMessage *obc_transfer_resume(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + struct obc_transfer *transfer = user_data; + const char *sender; + uint8_t status; + + sender = dbus_message_get_sender(message); + if (g_strcmp0(transfer->owner, sender) != 0) + return g_dbus_create_error(message, + ERROR_INTERFACE ".NotAuthorized", + "Not Authorized"); + + switch (transfer->status) { + case TRANSFER_STATUS_SUSPENDED_QUEUED: + status = TRANSFER_STATUS_QUEUED; + break; + case TRANSFER_STATUS_SUSPENDED: + if (transfer->xfer) + g_obex_resume(transfer->obex); + else + obc_transfer_start(transfer, NULL, NULL); + status = TRANSFER_STATUS_ACTIVE; + break; + default: + return g_dbus_create_error(message, + ERROR_INTERFACE ".NotInProgress", + "Not in progress"); + } + + transfer_set_status(transfer, status); + + return g_dbus_create_reply(message, DBUS_TYPE_INVALID); +} + +static gboolean name_exists(const GDBusPropertyTable *property, void *data) +{ + struct obc_transfer *transfer = data; + + return transfer->name != NULL; +} + +static gboolean get_name(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obc_transfer *transfer = data; + + if (transfer->name == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &transfer->name); + + return TRUE; +} + +static gboolean get_size(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obc_transfer *transfer = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, + &transfer->size); + + return TRUE; +} + +static gboolean filename_exists(const GDBusPropertyTable *property, void *data) +{ + struct obc_transfer *transfer = data; + + return transfer->filename != NULL; +} + +static gboolean get_filename(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obc_transfer *transfer = data; + + if (transfer->filename == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &transfer->filename); + + return TRUE; +} + +static gboolean transferred_exists(const GDBusPropertyTable *property, + void *data) +{ + struct obc_transfer *transfer = data; + + return transfer->obex != NULL; +} + +static gboolean get_transferred(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obc_transfer *transfer = data; + + if (transfer->obex == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, + &transfer->progress); + + return TRUE; +} + +static const char *status2str(uint8_t status) +{ + switch (status) { + case TRANSFER_STATUS_QUEUED: + return "queued"; + case TRANSFER_STATUS_ACTIVE: + return "active"; + case TRANSFER_STATUS_SUSPENDED_QUEUED: + case TRANSFER_STATUS_SUSPENDED: + return "suspended"; + case TRANSFER_STATUS_COMPLETE: + return "complete"; + case TRANSFER_STATUS_ERROR: + default: + return "error"; + } +} + +static gboolean get_status(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obc_transfer *transfer = data; + const char *status = status2str(transfer->status); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status); + + return TRUE; +} + +static gboolean get_session(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obc_transfer *transfer = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &transfer->session); + + return TRUE; +} + +static const GDBusMethodTable obc_transfer_methods[] = { + { GDBUS_METHOD("Suspend", NULL, NULL, obc_transfer_suspend) }, + { GDBUS_METHOD("Resume", NULL, NULL, obc_transfer_resume) }, + { GDBUS_ASYNC_METHOD("Cancel", NULL, NULL, + obc_transfer_cancel) }, + { } +}; + +static const GDBusPropertyTable obc_transfer_properties[] = { + { "Status", "s", get_status }, + { "Name", "s", get_name, NULL, name_exists }, + { "Size", "t", get_size }, + { "Filename", "s", get_filename, NULL, filename_exists }, + { "Transferred", "t", get_transferred, NULL, transferred_exists }, + { "Session", "o", get_session }, + { } +}; + +static void obc_transfer_free(struct obc_transfer *transfer) +{ + DBG("%p", transfer); + + if (transfer->status == TRANSFER_STATUS_SUSPENDED) + g_obex_resume(transfer->obex); + + if (transfer->req > 0) + g_obex_cancel_req(transfer->obex, transfer->req, TRUE); + + if (transfer->xfer) + g_obex_cancel_transfer(transfer->xfer, NULL, NULL); + + if (transfer->progress_id != 0) { + g_source_remove(transfer->progress_id); + transfer->progress_id = 0; + } + + if (transfer->op == G_OBEX_OP_GET && + transfer->status != TRANSFER_STATUS_COMPLETE && + transfer->filename) + remove(transfer->filename); + + if (transfer->fd > 0) + close(transfer->fd); + + if (transfer->apparam != NULL) + g_obex_apparam_free(transfer->apparam); + + if (transfer->conn) + dbus_connection_unref(transfer->conn); + + if (transfer->msg) + dbus_message_unref(transfer->msg); + + if (transfer->obex) + g_obex_unref(transfer->obex); + + g_free(transfer->callback); + g_free(transfer->owner); + g_free(transfer->filename); + g_free(transfer->name); + g_free(transfer->type); + g_free(transfer->session); + g_free(transfer->path); + g_free(transfer); +} + +static struct obc_transfer *obc_transfer_create(guint8 op, + const char *filename, + const char *name, + const char *type) +{ + struct obc_transfer *transfer; + + transfer = g_new0(struct obc_transfer, 1); + transfer->op = op; + transfer->filename = g_strdup(filename); + transfer->name = g_strdup(name); + transfer->type = g_strdup(type); + + return transfer; +} + +gboolean obc_transfer_register(struct obc_transfer *transfer, + DBusConnection *conn, + const char *session, + const char *owner, + GError **err) +{ + transfer->owner = g_strdup(owner); + + transfer->session = g_strdup(session); + transfer->path = g_strdup_printf("%s/transfer%ju", session, counter++); + + transfer->conn = dbus_connection_ref(conn); + if (transfer->conn == NULL) { + g_set_error(err, OBC_TRANSFER_ERROR, -EFAULT, + "Unable to connect to D-Bus"); + return FALSE; + } + + if (g_dbus_register_interface(transfer->conn, transfer->path, + TRANSFER_INTERFACE, + obc_transfer_methods, NULL, + obc_transfer_properties, transfer, + NULL) == FALSE) { + g_set_error(err, OBC_TRANSFER_ERROR, -EFAULT, + "Unable to register to D-Bus"); + return FALSE; + } + + DBG("%p registered %s", transfer, transfer->path); + + return TRUE; +} + +static gboolean transfer_open(struct obc_transfer *transfer, int flags, + mode_t mode, GError **err) +{ + int fd; + char *filename; + + if (transfer->filename != NULL && strcmp(transfer->filename, "") != 0) { + fd = open(transfer->filename, flags, mode); + if (fd < 0) { + error("open(): %s(%d)", strerror(errno), errno); + g_set_error(err, OBC_TRANSFER_ERROR, -errno, + "Unable to open file"); + return FALSE; + } + goto done; + } + + fd = g_file_open_tmp("obex-clientXXXXXX", &filename, err); + if (fd < 0) { + error("g_file_open_tmp(): %s", (*err)->message); + return FALSE; + } + + if (transfer->filename == NULL) { + remove(filename); /* remove always only if NULL was given */ + g_free(filename); + } else { + g_free(transfer->filename); + transfer->filename = filename; + } + +done: + transfer->fd = fd; + return TRUE; +} + +struct obc_transfer *obc_transfer_get(const char *type, const char *name, + const char *filename, GError **err) +{ + struct obc_transfer *transfer; + int perr; + + transfer = obc_transfer_create(G_OBEX_OP_GET, filename, name, type); + + perr = transfer_open(transfer, O_WRONLY | O_CREAT | O_TRUNC, 0600, err); + if (perr < 0) { + obc_transfer_free(transfer); + return NULL; + } + + return transfer; +} + +struct obc_transfer *obc_transfer_put(const char *type, const char *name, + const char *filename, + const void *contents, size_t size, + GError **err) +{ + struct obc_transfer *transfer; + struct stat st; + int perr; + + if ((filename == NULL || strcmp(filename, "") == 0) && + contents == NULL) { + g_set_error(err, OBC_TRANSFER_ERROR, -EINVAL, + "Invalid filename given"); + return NULL; + } + + transfer = obc_transfer_create(G_OBEX_OP_PUT, filename, name, type); + + if (contents != NULL) { + ssize_t w; + + if (!transfer_open(transfer, O_RDWR, 0, err)) + goto fail; + + w = write(transfer->fd, contents, size); + if (w < 0) { + perr = errno; + error("write(): %s(%d)", strerror(perr), perr); + g_set_error(err, OBC_TRANSFER_ERROR, -perr, + "Writing to file failed"); + goto fail; + } else if ((size_t) w != size) { + error("Unable to write all contents to file"); + g_set_error(err, OBC_TRANSFER_ERROR, -EFAULT, + "Writing all contents to file failed"); + goto fail; + } + lseek(transfer->fd, 0, SEEK_SET); + } else { + if (!transfer_open(transfer, O_RDONLY, 0, err)) + goto fail; + } + + if (fstat(transfer->fd, &st) < 0) { + perr = errno; + error("fstat(): %s(%d)", strerror(perr), perr); + g_set_error(err, OBC_TRANSFER_ERROR, -perr, + "Unable to get file status"); + goto fail; + } + + transfer->size = st.st_size; + + return transfer; + +fail: + obc_transfer_free(transfer); + return NULL; +} + +void obc_transfer_unregister(struct obc_transfer *transfer) +{ + if (transfer->path) { + g_dbus_unregister_interface(transfer->conn, + transfer->path, TRANSFER_INTERFACE); + } + + DBG("%p unregistered %s", transfer, transfer->path); + + obc_transfer_free(transfer); +} + +static gboolean get_xfer_progress(const void *buf, gsize len, + gpointer user_data) +{ + struct obc_transfer *transfer = user_data; + + if (transfer->fd > 0) { + int w; + + w = write(transfer->fd, buf, len); + if (w < 0) + return FALSE; + + transfer->transferred += w; + } + + return TRUE; +} + +static void xfer_complete(GObex *obex, GError *err, gpointer user_data) +{ + struct obc_transfer *transfer = user_data; + struct transfer_callback *callback = transfer->callback; + + transfer->xfer = 0; + + if (transfer->progress_id != 0) { + g_source_remove(transfer->progress_id); + transfer->progress_id = 0; + } + + if (transfer->status == TRANSFER_STATUS_SUSPENDED) + g_obex_resume(transfer->obex); + + if (err) + transfer_set_status(transfer, TRANSFER_STATUS_ERROR); + else + transfer_set_status(transfer, TRANSFER_STATUS_COMPLETE); + + if (callback) + callback->func(transfer, err, callback->data); +} + +static void get_xfer_progress_first(GObex *obex, GError *err, GObexPacket *rsp, + gpointer user_data) +{ + struct obc_transfer *transfer = user_data; + GObexPacket *req; + GObexHeader *hdr; + GObexApparam *apparam; + const guint8 *buf; + gsize len; + guint8 rspcode; + gboolean final; + + if (err != NULL) { + xfer_complete(obex, err, transfer); + return; + } + + rspcode = g_obex_packet_get_operation(rsp, &final); + if (rspcode != G_OBEX_RSP_SUCCESS && rspcode != G_OBEX_RSP_CONTINUE) { + err = g_error_new(OBC_TRANSFER_ERROR, rspcode, "%s", + g_obex_strerror(rspcode)); + xfer_complete(obex, err, transfer); + g_error_free(err); + return; + } + + hdr = g_obex_packet_get_header(rsp, G_OBEX_HDR_LENGTH); + if (hdr) { + uint32_t len; + if (g_obex_header_get_uint32(hdr, &len)) { + transfer->size = len; + g_dbus_emit_property_changed(transfer->conn, + transfer->path, + TRANSFER_INTERFACE, "Size"); + } + } + + hdr = g_obex_packet_get_header(rsp, G_OBEX_HDR_APPARAM); + if (hdr) { + apparam = g_obex_header_get_apparam(hdr); + if (apparam != NULL) + obc_transfer_set_apparam(transfer, apparam); + } + + hdr = g_obex_packet_get_body(rsp); + if (hdr) { + g_obex_header_get_bytes(hdr, &buf, &len); + if (len != 0) + get_xfer_progress(buf, len, transfer); + } + + if (rspcode == G_OBEX_RSP_SUCCESS) { + transfer->req = 0; + xfer_complete(obex, err, transfer); + return; + } + + if (g_obex_srm_active(obex) || + transfer->status == TRANSFER_STATUS_SUSPENDED) + return; + + transfer->req = 0; + + req = g_obex_packet_new(G_OBEX_OP_GET, TRUE, G_OBEX_HDR_INVALID); + + transfer->xfer = g_obex_get_req_pkt(obex, req, get_xfer_progress, + xfer_complete, transfer, + &err); +} + +static gssize put_xfer_progress(void *buf, gsize len, gpointer user_data) +{ + struct obc_transfer *transfer = user_data; + gssize size; + + size = read(transfer->fd, buf, len); + if (size <= 0) + return size; + + transfer->transferred += size; + + return size; +} + +gboolean obc_transfer_set_callback(struct obc_transfer *transfer, + transfer_callback_t func, + void *user_data) +{ + struct transfer_callback *callback; + + if (transfer->callback != NULL) + return FALSE; + + callback = g_new0(struct transfer_callback, 1); + callback->func = func; + callback->data = user_data; + + transfer->callback = callback; + + return TRUE; +} + +static gboolean report_progress(gpointer data) +{ + struct obc_transfer *transfer = data; + + if (transfer->transferred == transfer->progress) + return TRUE; + + transfer->progress = transfer->transferred; + + if (transfer->transferred == transfer->size) { + transfer->progress_id = 0; + return FALSE; + } + + if (transfer->status != TRANSFER_STATUS_ACTIVE && + transfer->status != TRANSFER_STATUS_SUSPENDED) + transfer_set_status(transfer, TRANSFER_STATUS_ACTIVE); + + g_dbus_emit_property_changed(transfer->conn, transfer->path, + TRANSFER_INTERFACE, "Transferred"); + + return TRUE; +} + +static gboolean transfer_start_get(struct obc_transfer *transfer, GError **err) +{ + GObexPacket *req; + GObexHeader *hdr; + + if (transfer->xfer > 0) { + g_set_error(err, OBC_TRANSFER_ERROR, -EALREADY, + "Transfer already started"); + return FALSE; + } + + req = g_obex_packet_new(G_OBEX_OP_GET, TRUE, G_OBEX_HDR_INVALID); + + if (transfer->name != NULL) + g_obex_packet_add_unicode(req, G_OBEX_HDR_NAME, + transfer->name); + + if (transfer->type != NULL) + g_obex_packet_add_bytes(req, G_OBEX_HDR_TYPE, transfer->type, + strlen(transfer->type) + 1); + + if (transfer->apparam != NULL) { + hdr = g_obex_header_new_apparam(transfer->apparam); + g_obex_packet_add_header(req, hdr); + } + + transfer->req = g_obex_send_req(transfer->obex, req, + FIRST_PACKET_TIMEOUT, + get_xfer_progress_first, + transfer, err); + if (transfer->req == 0) + return FALSE; + + if (transfer->path == NULL) + return TRUE; + + transfer->progress_id = g_timeout_add_seconds(1, report_progress, + transfer); + + return TRUE; +} + +static gboolean transfer_start_put(struct obc_transfer *transfer, GError **err) +{ + GObexPacket *req; + GObexHeader *hdr; + + if (transfer->xfer > 0) { + g_set_error(err, OBC_TRANSFER_ERROR, -EALREADY, + "Transfer already started"); + return FALSE; + } + + req = g_obex_packet_new(G_OBEX_OP_PUT, FALSE, G_OBEX_HDR_INVALID); + + if (transfer->name != NULL) + g_obex_packet_add_unicode(req, G_OBEX_HDR_NAME, + transfer->name); + + if (transfer->type != NULL) + g_obex_packet_add_bytes(req, G_OBEX_HDR_TYPE, transfer->type, + strlen(transfer->type) + 1); + + if (transfer->size < UINT32_MAX) + g_obex_packet_add_uint32(req, G_OBEX_HDR_LENGTH, transfer->size); + + if (transfer->apparam != NULL) { + hdr = g_obex_header_new_apparam(transfer->apparam); + g_obex_packet_add_header(req, hdr); + } + + transfer->xfer = g_obex_put_req_pkt(transfer->obex, req, + put_xfer_progress, xfer_complete, + transfer, err); + if (transfer->xfer == 0) + return FALSE; + + if (transfer->path == NULL) + return TRUE; + + transfer->progress_id = g_timeout_add_seconds(1, report_progress, + transfer); + + return TRUE; +} + +gboolean obc_transfer_start(struct obc_transfer *transfer, void *obex, + GError **err) +{ + if (!transfer->obex) + transfer->obex = g_obex_ref(obex); + + if (transfer->status == TRANSFER_STATUS_SUSPENDED_QUEUED) { + /* Reset status so the transfer can be resumed */ + transfer->status = TRANSFER_STATUS_SUSPENDED; + return TRUE; + } + + switch (transfer->op) { + case G_OBEX_OP_GET: + return transfer_start_get(transfer, err); + case G_OBEX_OP_PUT: + return transfer_start_put(transfer, err); + } + + g_set_error(err, OBC_TRANSFER_ERROR, -ENOTSUP, "Not supported"); + return FALSE; +} + +guint8 obc_transfer_get_operation(struct obc_transfer *transfer) +{ + return transfer->op; +} + +void obc_transfer_set_apparam(struct obc_transfer *transfer, void *data) +{ + if (transfer->apparam != NULL) + g_obex_apparam_free(transfer->apparam); + + if (data == NULL) + return; + + transfer->apparam = data; +} + +void *obc_transfer_get_apparam(struct obc_transfer *transfer) +{ + return transfer->apparam; +} + +int obc_transfer_get_contents(struct obc_transfer *transfer, char **contents, + size_t *size) +{ + struct stat st; + ssize_t ret; + + if (contents == NULL) + return -EINVAL; + + if (fstat(transfer->fd, &st) < 0) { + error("fstat(): %s(%d)", strerror(errno), errno); + return -errno; + } + + if (lseek(transfer->fd, 0, SEEK_SET) < 0) { + error("lseek(): %s(%d)", strerror(errno), errno); + return -errno; + } + + *contents = g_malloc(st.st_size + 1); + + ret = read(transfer->fd, *contents, st.st_size); + if (ret < 0) { + error("read(): %s(%d)", strerror(errno), errno); + g_free(*contents); + return -errno; + } + + (*contents)[ret] = '\0'; + + if (size) + *size = ret; + + return 0; +} + +const char *obc_transfer_get_path(struct obc_transfer *transfer) +{ + return transfer->path; +} + +gint64 obc_transfer_get_size(struct obc_transfer *transfer) +{ + return transfer->size; +} diff --git a/obexd/client/transfer.h b/obexd/client/transfer.h new file mode 100644 index 0000000..b6b835d --- /dev/null +++ b/obexd/client/transfer.h @@ -0,0 +1,62 @@ +/* + * + * OBEX Client + * + * Copyright (C) 2007-2010 Marcel Holtmann + * Copyright (C) 2011-2012 BMW Car IT GmbH. 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 + * + */ + +struct obc_transfer; + +typedef void (*transfer_callback_t) (struct obc_transfer *transfer, + GError *err, void *user_data); + +struct obc_transfer *obc_transfer_get(const char *type, const char *name, + const char *filename, GError **err); +struct obc_transfer *obc_transfer_put(const char *type, const char *name, + const char *filename, + const void *contents, size_t size, + GError **err); + +gboolean obc_transfer_register(struct obc_transfer *transfer, + DBusConnection *conn, + const char *session, + const char *owner, + GError **err); + +void obc_transfer_unregister(struct obc_transfer *transfer); + +gboolean obc_transfer_set_callback(struct obc_transfer *transfer, + transfer_callback_t func, + void *user_data); + +gboolean obc_transfer_start(struct obc_transfer *transfer, void *obex, + GError **err); +guint8 obc_transfer_get_operation(struct obc_transfer *transfer); + +void obc_transfer_set_apparam(struct obc_transfer *transfer, void *data); +void *obc_transfer_get_apparam(struct obc_transfer *transfer); +int obc_transfer_get_contents(struct obc_transfer *transfer, char **contents, + size_t *size); + +const char *obc_transfer_get_path(struct obc_transfer *transfer); +gint64 obc_transfer_get_size(struct obc_transfer *transfer); + +DBusMessage *obc_transfer_create_dbus_reply(struct obc_transfer *transfer, + DBusMessage *message); diff --git a/obexd/client/transport.c b/obexd/client/transport.c new file mode 100644 index 0000000..4cd7706 --- /dev/null +++ b/obexd/client/transport.c @@ -0,0 +1,83 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2012 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "obexd/src/log.h" +#include "transport.h" + +static GSList *transports = NULL; + +struct obc_transport *obc_transport_find(const char *name) +{ + GSList *l; + + for (l = transports; l; l = l->next) { + struct obc_transport *transport = l->data; + + if (strcasecmp(name, transport->name) == 0) + return transport; + } + + return NULL; +} + +int obc_transport_register(struct obc_transport *transport) +{ + if (!transport) { + error("Invalid transport"); + return -EINVAL; + } + + if (obc_transport_find(transport->name)) { + error("Permission denied: transport %s already registered", + transport->name); + return -EPERM; + } + + DBG("transport %p name %s registered", transport, transport->name); + + transports = g_slist_append(transports, transport); + + return 0; +} + +void obc_transport_unregister(struct obc_transport *transport) +{ + if (!g_slist_find(transports, transport)) { + error("Unable to unregister: No such transport %p", transport); + return; + } + + DBG("transport %p name %s unregistered", transport, transport->name); + + transports = g_slist_remove(transports, transport); +} diff --git a/obexd/client/transport.h b/obexd/client/transport.h new file mode 100644 index 0000000..b035cfc --- /dev/null +++ b/obexd/client/transport.h @@ -0,0 +1,39 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2012 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 + * + */ + +typedef void (*obc_transport_func)(GIOChannel *io, GError *err, + gpointer user_data); + +struct obc_transport { + const char *name; + guint (*connect) (const char *source, const char *destination, + const char *service, uint16_t port, + obc_transport_func func, void *user_data); + int (*getpacketopt) (GIOChannel *io, int *tx_mtu, int *rx_mtu); + void (*disconnect) (guint id); + const void *(*getattribute) (guint id, int attribute_id); +}; + +int obc_transport_register(struct obc_transport *transport); +void obc_transport_unregister(struct obc_transport *transport); +struct obc_transport *obc_transport_find(const char *name); diff --git a/obexd/plugins/bluetooth.c b/obexd/plugins/bluetooth.c new file mode 100644 index 0000000..ba1e0a9 --- /dev/null +++ b/obexd/plugins/bluetooth.c @@ -0,0 +1,466 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Nokia Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "btio/btio.h" +#include "obexd/src/obexd.h" +#include "obexd/src/plugin.h" +#include "obexd/src/server.h" +#include "obexd/src/obex.h" +#include "obexd/src/transport.h" +#include "obexd/src/service.h" +#include "obexd/src/log.h" + +#define BT_RX_MTU 32767 +#define BT_TX_MTU 32767 + +struct bluetooth_profile { + struct obex_server *server; + struct obex_service_driver *driver; + char *uuid; + char *path; +}; + +static GSList *profiles = NULL; + +static DBusConnection *connection = NULL; + +static DBusMessage *profile_release(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static void connect_event(GIOChannel *io, GError *err, void *user_data) +{ + int sk = g_io_channel_unix_get_fd(io); + struct bluetooth_profile *profile = user_data; + struct obex_server *server = profile->server; + int type; + uint16_t omtu = BT_TX_MTU; + uint16_t imtu = BT_RX_MTU; + gboolean stream = TRUE; + socklen_t len = sizeof(int); + + if (err) + goto drop; + + if (getsockopt(sk, SOL_SOCKET, SO_TYPE, &type, &len) < 0) + goto done; + + if (type != SOCK_SEQPACKET) + goto done; + + stream = FALSE; + + /* Read MTU if io is an L2CAP socket */ + bt_io_get(io, NULL, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_INVALID); + +done: + if (obex_server_new_connection(server, io, omtu, imtu, stream) < 0) + g_io_channel_shutdown(io, TRUE, NULL); + + return; + +drop: + error("%s", err->message); + g_io_channel_shutdown(io, TRUE, NULL); + return; +} + +static DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, "org.bluez.Error.InvalidArguments", + "Invalid arguments in method call"); +} + +static DBusMessage *profile_new_connection(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBusMessageIter args; + const char *device; + int fd; + GIOChannel *io; + + dbus_message_iter_init(msg, &args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) + return invalid_args(msg); + + dbus_message_iter_get_basic(&args, &device); + + dbus_message_iter_next(&args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_UNIX_FD) + return invalid_args(msg); + + dbus_message_iter_get_basic(&args, &fd); + + if (fd < 0) { + error("bluetooth: NewConnection invalid fd"); + return invalid_args(msg); + } + + /* Read fd flags to make sure it can be used */ + if (fcntl(fd, F_GETFD) < 0) { + error("bluetooth: fcntl(%d, F_GETFD): %s (%d)", fd, + strerror(errno), errno); + close(fd); + return invalid_args(msg); + } + + io = g_io_channel_unix_new(fd); + if (io == NULL) { + close(fd); + return invalid_args(msg); + } + + DBG("device %s", device); + + connect_event(io, NULL, data); + g_io_channel_unref(io); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *profile_request_disconnection(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *profile_cancel(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static const GDBusMethodTable profile_methods[] = { + { GDBUS_METHOD("Release", + NULL, NULL, + profile_release) }, + { GDBUS_METHOD("NewConnection", + GDBUS_ARGS({ "device", "o" }, { "fd", "h" }, + { "options", "a{sv}" }), NULL, + profile_new_connection) }, + { GDBUS_METHOD("RequestDisconnection", + GDBUS_ARGS({ "device", "o" }), NULL, + profile_request_disconnection) }, + { GDBUS_METHOD("Cancel", + NULL, NULL, + profile_cancel) }, + { } +}; + +static void unregister_profile(struct bluetooth_profile *profile) +{ + g_dbus_unregister_interface(connection, profile->path, + "org.bluez.Profile1"); + g_free(profile->path); + profile->path = NULL; +} + +static void register_profile_reply(DBusPendingCall *call, void *user_data) +{ + struct bluetooth_profile *profile = user_data; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError derr; + + dbus_error_init(&derr); + if (!dbus_set_error_from_message(&derr, reply)) { + DBG("Profile %s registered", profile->path); + goto done; + } + + unregister_profile(profile); + + error("bluetooth: RequestProfile error: %s, %s", derr.name, + derr.message); + dbus_error_free(&derr); +done: + dbus_message_unref(reply); +} + +static void profile_free(void *data) +{ + struct bluetooth_profile *profile = data; + + if (profile->path != NULL) + unregister_profile(profile); + + g_free(profile->uuid); + g_free(profile); +} + +static int register_profile(struct bluetooth_profile *profile) +{ + DBusMessage *msg; + DBusMessageIter iter, opt; + DBusPendingCall *call; + dbus_bool_t auto_connect = FALSE; + char *xml; + int ret = 0; + + profile->path = g_strconcat("/org/bluez/obex/", profile->uuid, NULL); + g_strdelimit(profile->path, "-", '_'); + + if (!g_dbus_register_interface(connection, profile->path, + "org.bluez.Profile1", profile_methods, + NULL, NULL, + profile, NULL)) { + error("D-Bus failed to register %s", profile->path); + g_free(profile->path); + profile->path = NULL; + return -1; + } + + msg = dbus_message_new_method_call("org.bluez", "/org/bluez", + "org.bluez.ProfileManager1", + "RegisterProfile"); + + dbus_message_iter_init_append(msg, &iter); + + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, + &profile->path); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, + &profile->uuid); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &opt); + g_dbus_dict_append_entry(&opt, "AutoConnect", DBUS_TYPE_BOOLEAN, + &auto_connect); + if (profile->driver->record) { + if (profile->driver->port != 0) + xml = g_markup_printf_escaped(profile->driver->record, + profile->driver->channel, + profile->driver->name, + profile->driver->port); + else + xml = g_markup_printf_escaped(profile->driver->record, + profile->driver->channel, + profile->driver->name); + g_dbus_dict_append_entry(&opt, "ServiceRecord", + DBUS_TYPE_STRING, &xml); + g_free(xml); + } + dbus_message_iter_close_container(&iter, &opt); + + if (!g_dbus_send_message_with_reply(connection, msg, &call, -1)) { + ret = -1; + unregister_profile(profile); + goto failed; + } + + dbus_pending_call_set_notify(call, register_profile_reply, profile, + NULL); + dbus_pending_call_unref(call); + +failed: + dbus_message_unref(msg); + return ret; +} + +static const char *service2uuid(uint16_t service) +{ + switch (service) { + case OBEX_OPP: + return OBEX_OPP_UUID; + case OBEX_FTP: + return OBEX_FTP_UUID; + case OBEX_PBAP: + return OBEX_PSE_UUID; + case OBEX_IRMC: + return OBEX_SYNC_UUID; + case OBEX_PCSUITE: + return "00005005-0000-1000-8000-0002ee000001"; + case OBEX_SYNCEVOLUTION: + return "00000002-0000-1000-8000-0002ee000002"; + case OBEX_MAS: + return OBEX_MAS_UUID; + case OBEX_MNS: + return OBEX_MNS_UUID; + } + + return NULL; +} + +static void name_acquired(DBusConnection *conn, void *user_data) +{ + GSList *l; + + DBG("org.bluez appeared"); + + for (l = profiles; l; l = l->next) { + struct bluetooth_profile *profile = l->data; + + if (profile->path != NULL) + continue; + + if (register_profile(profile) < 0) { + error("bluetooth: Failed to register profile %s", + profile->path); + g_free(profile->path); + profile->path = NULL; + } + } +} + +static void name_released(DBusConnection *conn, void *user_data) +{ + GSList *l; + + DBG("org.bluez disappered"); + + for (l = profiles; l; l = l->next) { + struct bluetooth_profile *profile = l->data; + + if (profile->path == NULL) + continue; + + unregister_profile(profile); + } +} + +static void *bluetooth_start(struct obex_server *server, int *err) +{ + const GSList *l; + + for (l = server->drivers; l; l = l->next) { + struct obex_service_driver *driver = l->data; + struct bluetooth_profile *profile; + const char *uuid; + + uuid = service2uuid(driver->service); + if (uuid == NULL) + continue; + + profile = g_new0(struct bluetooth_profile, 1); + profile->driver = driver; + profile->server = server; + profile->uuid = g_strdup(uuid); + + profiles = g_slist_prepend(profiles, profile); + } + + return profiles; +} + +static void bluetooth_stop(void *data) +{ + g_slist_free_full(profiles, profile_free); + profiles = NULL; +} + +static int bluetooth_getpeername(GIOChannel *io, char **name) +{ + GError *gerr = NULL; + char address[18]; + + bt_io_get(io, &gerr, BT_IO_OPT_DEST, address, BT_IO_OPT_INVALID); + + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + return -EINVAL; + } + + *name = g_strdup(address); + + return 0; +} + +static int bluetooth_getsockname(GIOChannel *io, char **name) +{ + GError *gerr = NULL; + char address[18]; + + bt_io_get(io, &gerr, BT_IO_OPT_SOURCE, address, BT_IO_OPT_INVALID); + + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + return -EINVAL; + } + + *name = g_strdup(address); + + return 0; +} + +static struct obex_transport_driver driver = { + .name = "bluetooth", + .start = bluetooth_start, + .getpeername = bluetooth_getpeername, + .getsockname = bluetooth_getsockname, + .stop = bluetooth_stop +}; + +static unsigned int listener_id = 0; + +static int bluetooth_init(void) +{ + connection = g_dbus_setup_private(DBUS_BUS_SYSTEM, NULL, NULL); + if (connection == NULL) + return -EPERM; + + listener_id = g_dbus_add_service_watch(connection, "org.bluez", + name_acquired, name_released, NULL, NULL); + + return obex_transport_driver_register(&driver); +} + +static void bluetooth_exit(void) +{ + g_dbus_remove_watch(connection, listener_id); + + g_slist_free_full(profiles, profile_free); + + if (connection) + dbus_connection_unref(connection); + + obex_transport_driver_unregister(&driver); +} + +OBEX_PLUGIN_DEFINE(bluetooth, bluetooth_init, bluetooth_exit) diff --git a/obexd/plugins/filesystem.c b/obexd/plugins/filesystem.c new file mode 100644 index 0000000..48239a8 --- /dev/null +++ b/obexd/plugins/filesystem.c @@ -0,0 +1,723 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2009-2010 Intel Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "obexd/src/obexd.h" +#include "obexd/src/plugin.h" +#include "obexd/src/log.h" +#include "obexd/src/mimetype.h" +#include "filesystem.h" + +#define EOL_CHARS "\n" + +#define FL_VERSION "" EOL_CHARS + +#define FL_TYPE "" EOL_CHARS + +#define FL_TYPE_PCSUITE " ]>" EOL_CHARS + +#define FL_BODY_BEGIN "" EOL_CHARS + +#define FL_BODY_END "" EOL_CHARS + +#define FL_PARENT_FOLDER_ELEMENT "" EOL_CHARS + +#define FL_FILE_ELEMENT "" EOL_CHARS + +#define FL_FOLDER_ELEMENT "" EOL_CHARS + +#define FL_FOLDER_ELEMENT_PCSUITE "" EOL_CHARS + +#define FTP_TARGET_SIZE 16 + +static const uint8_t FTP_TARGET[FTP_TARGET_SIZE] = { + 0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, + 0x98, 0x4E, 0x52, 0x54, 0x00, 0xDC, 0x9E, 0x09 }; + +#define PCSUITE_WHO_SIZE 8 + +static const uint8_t PCSUITE_WHO[PCSUITE_WHO_SIZE] = { + 'P', 'C', ' ', 'S', 'u', 'i', 't', 'e' }; + +gboolean is_filename(const char *name) +{ + if (strchr(name, '/')) + return FALSE; + + if (strcmp(name, ".") == 0) + return FALSE; + + if (strcmp(name, "..") == 0) + return FALSE; + + return TRUE; +} + +int verify_path(const char *path) +{ + char *t; + int ret = 0; + + if (obex_option_symlinks()) + return 0; + + t = realpath(path, NULL); + if (t == NULL) + return -errno; + + if (!g_str_has_prefix(t, obex_option_root_folder())) + ret = -EPERM; + + free(t); + + return ret; +} + +static char *file_stat_line(char *filename, struct stat *fstat, + struct stat *dstat, gboolean root, + gboolean pcsuite) +{ + char perm[51], atime[18], ctime[18], mtime[18]; + char *escaped, *ret = NULL; + + snprintf(perm, 50, "user-perm=\"%s%s%s\" group-perm=\"%s%s%s\" " + "other-perm=\"%s%s%s\"", + (fstat->st_mode & S_IRUSR ? "R" : ""), + (fstat->st_mode & S_IWUSR ? "W" : ""), + (dstat->st_mode & S_IWUSR ? "D" : ""), + (fstat->st_mode & S_IRGRP ? "R" : ""), + (fstat->st_mode & S_IWGRP ? "W" : ""), + (dstat->st_mode & S_IWGRP ? "D" : ""), + (fstat->st_mode & S_IROTH ? "R" : ""), + (fstat->st_mode & S_IWOTH ? "W" : ""), + (dstat->st_mode & S_IWOTH ? "D" : "")); + + strftime(atime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_atime)); + strftime(ctime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_ctime)); + strftime(mtime, 17, "%Y%m%dT%H%M%SZ", gmtime(&fstat->st_mtime)); + + escaped = g_markup_escape_text(filename, -1); + + if (S_ISDIR(fstat->st_mode)) { + if (pcsuite && root && g_str_equal(filename, "Data")) + ret = g_strdup_printf(FL_FOLDER_ELEMENT_PCSUITE, + escaped, perm, atime, + mtime, ctime); + else + ret = g_strdup_printf(FL_FOLDER_ELEMENT, escaped, perm, + atime, mtime, ctime); + } else if (S_ISREG(fstat->st_mode)) + ret = g_strdup_printf(FL_FILE_ELEMENT, escaped, + (uint64_t) fstat->st_size, + perm, atime, mtime, ctime); + + g_free(escaped); + + return ret; +} + +static void *filesystem_open(const char *name, int oflag, mode_t mode, + void *context, size_t *size, int *err) +{ + struct stat stats; + struct statvfs buf; + int fd, ret; + uint64_t avail; + + fd = open(name, oflag, mode); + if (fd < 0) { + if (err) + *err = -errno; + return NULL; + } + + if (fstat(fd, &stats) < 0) { + if (err) + *err = -errno; + goto failed; + } + + ret = verify_path(name); + if (ret < 0) { + if (err) + *err = ret; + goto failed; + } + + if (oflag == O_RDONLY) { + if (size) + *size = stats.st_size; + goto done; + } + + if (fstatvfs(fd, &buf) < 0) { + if (err) + *err = -errno; + goto failed; + } + + if (size == NULL) + goto done; + + avail = (uint64_t) buf.f_bsize * buf.f_bavail; + if (avail < *size) { + if (err) + *err = -ENOSPC; + goto failed; + } + +done: + if (err) + *err = 0; + + return GINT_TO_POINTER(fd); + +failed: + close(fd); + return NULL; +} + +static int filesystem_close(void *object) +{ + if (close(GPOINTER_TO_INT(object)) < 0) + return -errno; + + return 0; +} + +static ssize_t filesystem_read(void *object, void *buf, size_t count) +{ + ssize_t ret; + + ret = read(GPOINTER_TO_INT(object), buf, count); + if (ret < 0) + return -errno; + + return ret; +} + +static ssize_t filesystem_write(void *object, const void *buf, size_t count) +{ + ssize_t ret; + + ret = write(GPOINTER_TO_INT(object), buf, count); + if (ret < 0) + return -errno; + + return ret; +} + +static int filesystem_rename(const char *name, const char *destname) +{ + int ret; + + ret = rename(name, destname); + if (ret < 0) { + error("rename(%s, %s): %s (%d)", name, destname, + strerror(errno), errno); + return -errno; + } + + return ret; +} + +static int sendfile_async(int out_fd, int in_fd, off_t *offset, size_t count) +{ + int pid; + + /* Run sendfile on child process */ + pid = fork(); + switch (pid) { + case 0: + break; + case -1: + error("fork() %s (%d)", strerror(errno), errno); + return -errno; + default: + DBG("child %d forked", pid); + return pid; + } + + /* At child */ + if (sendfile(out_fd, in_fd, offset, count) < 0) + error("sendfile(): %s (%d)", strerror(errno), errno); + + close(in_fd); + close(out_fd); + + exit(errno); +} + +static int filesystem_copy(const char *name, const char *destname) +{ + void *in, *out; + ssize_t ret; + size_t size; + struct stat st; + int in_fd, out_fd, err; + + in = filesystem_open(name, O_RDONLY, 0, NULL, &size, &err); + if (in == NULL) { + error("open(%s): %s (%d)", name, strerror(-err), -err); + return -err; + } + + in_fd = GPOINTER_TO_INT(in); + ret = fstat(in_fd, &st); + if (ret < 0) { + error("stat(%s): %s (%d)", name, strerror(errno), errno); + return -errno; + } + + out = filesystem_open(destname, O_WRONLY | O_CREAT | O_TRUNC, + st.st_mode, NULL, &size, &err); + if (out == NULL) { + error("open(%s): %s (%d)", destname, strerror(-err), -err); + filesystem_close(in); + return -errno; + } + + out_fd = GPOINTER_TO_INT(out); + + /* Check if sendfile is supported */ + ret = sendfile(out_fd, in_fd, NULL, 0); + if (ret < 0) { + ret = -errno; + error("sendfile: %s (%zd)", strerror(-ret), -ret); + goto done; + } + + ret = sendfile_async(out_fd, in_fd, NULL, st.st_size); + if (ret < 0) + goto done; + + return 0; + +done: + filesystem_close(in); + filesystem_close(out); + + return ret; +} + +struct capability_object { + int pid; + int output; + int err; + gboolean aborted; + GString *buffer; +}; + +static void script_exited(GPid pid, int status, void *data) +{ + struct capability_object *object = data; + char buf[128]; + + object->pid = -1; + + DBG("pid: %d status: %d", pid, status); + + g_spawn_close_pid(pid); + + /* free the object if aborted */ + if (object->aborted) { + if (object->buffer != NULL) + g_string_free(object->buffer, TRUE); + + g_free(object); + return; + } + + if (WEXITSTATUS(status) != EXIT_SUCCESS) { + memset(buf, 0, sizeof(buf)); + if (read(object->err, buf, sizeof(buf)) > 0) + error("%s", buf); + obex_object_set_io_flags(data, G_IO_ERR, -EPERM); + } else + obex_object_set_io_flags(data, G_IO_IN, 0); +} + +static int capability_exec(const char **argv, int *output, int *err) +{ + GError *gerr = NULL; + int pid; + GSpawnFlags flags = G_SPAWN_DO_NOT_REAP_CHILD | G_SPAWN_SEARCH_PATH; + + if (!g_spawn_async_with_pipes(NULL, (char **) argv, NULL, flags, NULL, + NULL, &pid, NULL, output, err, &gerr)) { + error("%s", gerr->message); + g_error_free(gerr); + return -EPERM; + } + + DBG("executing %s pid %d", argv[0], pid); + + return pid; +} + +static void *capability_open(const char *name, int oflag, mode_t mode, + void *context, size_t *size, int *err) +{ + struct capability_object *object = NULL; + char *buf; + const char *argv[2]; + + if (oflag != O_RDONLY) + goto fail; + + object = g_new0(struct capability_object, 1); + object->pid = -1; + object->output = -1; + object->err = -1; + + if (name[0] != '!') { + GError *gerr = NULL; + gboolean ret; + + ret = g_file_get_contents(name, &buf, NULL, &gerr); + if (ret == FALSE) { + error("%s", gerr->message); + g_error_free(gerr); + goto fail; + } + + object->buffer = g_string_new(buf); + + if (size) + *size = object->buffer->len; + + goto done; + } + + argv[0] = &name[1]; + argv[1] = NULL; + + object->pid = capability_exec(argv, &object->output, &object->err); + if (object->pid < 0) + goto fail; + + /* Watch cannot be removed while the process is still running */ + g_child_watch_add(object->pid, script_exited, object); + +done: + if (err) + *err = 0; + + return object; + +fail: + if (err) + *err = -EPERM; + + g_free(object); + return NULL; +} + +static GString *append_pcsuite_preamble(GString *object) +{ + return g_string_append(object, FL_TYPE_PCSUITE); +} + +static GString *append_folder_preamble(GString *object) +{ + return g_string_append(object, FL_TYPE); +} + +static GString *append_listing(GString *object, const char *name, + gboolean pcsuite, size_t *size, int *err) +{ + struct stat fstat, dstat; + struct dirent *ep; + DIR *dp; + gboolean root; + int ret; + + root = g_str_equal(name, obex_option_root_folder()); + + dp = opendir(name); + if (dp == NULL) { + if (err) + *err = -ENOENT; + goto failed; + } + + if (!root) + object = g_string_append(object, FL_PARENT_FOLDER_ELEMENT); + + ret = verify_path(name); + if (ret < 0) { + *err = ret; + goto failed; + } + + ret = stat(name, &dstat); + if (ret < 0) { + if (err) + *err = -errno; + goto failed; + } + + while ((ep = readdir(dp))) { + char *filename; + char *fullname; + char *line; + + if (ep->d_name[0] == '.') + continue; + + filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL); + if (filename == NULL) { + error("g_filename_to_utf8: invalid filename"); + continue; + } + + fullname = g_build_filename(name, ep->d_name, NULL); + + ret = stat(fullname, &fstat); + if (ret < 0) { + DBG("stat: %s(%d)", strerror(errno), errno); + g_free(filename); + g_free(fullname); + continue; + } + + g_free(fullname); + + line = file_stat_line(filename, &fstat, &dstat, root, FALSE); + if (line == NULL) { + g_free(filename); + continue; + } + + g_free(filename); + + object = g_string_append(object, line); + g_free(line); + } + + closedir(dp); + + object = g_string_append(object, FL_BODY_END); + if (size) + *size = object->len; + + if (err) + *err = 0; + + return object; + +failed: + if (dp) + closedir(dp); + + g_string_free(object, TRUE); + return NULL; +} + +static void *folder_open(const char *name, int oflag, mode_t mode, + void *context, size_t *size, int *err) +{ + GString *object; + + object = g_string_new(FL_VERSION); + object = append_folder_preamble(object); + object = g_string_append(object, FL_BODY_BEGIN); + + return append_listing(object, name, FALSE, size, err); +} + +static void *pcsuite_open(const char *name, int oflag, mode_t mode, + void *context, size_t *size, int *err) +{ + GString *object; + + object = g_string_new(FL_VERSION); + object = append_pcsuite_preamble(object); + object = g_string_append(object, FL_BODY_BEGIN); + + return append_listing(object, name, TRUE, size, err); +} + +static int string_free(void *object) +{ + GString *string = object; + + g_string_free(string, TRUE); + + return 0; +} + +ssize_t string_read(void *object, void *buf, size_t count) +{ + GString *string = object; + ssize_t len; + + if (string->len == 0) + return 0; + + len = MIN(string->len, count); + memcpy(buf, string->str, len); + g_string_erase(string, 0, len); + + return len; +} + +static ssize_t folder_read(void *object, void *buf, size_t count) +{ + return string_read(object, buf, count); +} + +static ssize_t capability_read(void *object, void *buf, size_t count) +{ + struct capability_object *obj = object; + + if (obj->buffer) + return string_read(obj->buffer, buf, count); + + if (obj->pid >= 0) + return -EAGAIN; + + return read(obj->output, buf, count); +} + +static int capability_close(void *object) +{ + struct capability_object *obj = object; + int err = 0; + + if (obj->pid < 0) + goto done; + + DBG("kill: pid %d", obj->pid); + err = kill(obj->pid, SIGTERM); + if (err < 0) { + err = -errno; + error("kill: %s (%d)", strerror(-err), -err); + goto done; + } + + obj->aborted = TRUE; + return 0; + +done: + if (obj->buffer != NULL) + g_string_free(obj->buffer, TRUE); + + g_free(obj); + + return err; +} + +static struct obex_mime_type_driver file = { + .open = filesystem_open, + .close = filesystem_close, + .read = filesystem_read, + .write = filesystem_write, + .remove = remove, + .move = filesystem_rename, + .copy = filesystem_copy, +}; + +static struct obex_mime_type_driver capability = { + .target = FTP_TARGET, + .target_size = FTP_TARGET_SIZE, + .mimetype = "x-obex/capability", + .open = capability_open, + .close = capability_close, + .read = capability_read, +}; + +static struct obex_mime_type_driver folder = { + .target = FTP_TARGET, + .target_size = FTP_TARGET_SIZE, + .mimetype = "x-obex/folder-listing", + .open = folder_open, + .close = string_free, + .read = folder_read, +}; + +static struct obex_mime_type_driver pcsuite = { + .target = FTP_TARGET, + .target_size = FTP_TARGET_SIZE, + .who = PCSUITE_WHO, + .who_size = PCSUITE_WHO_SIZE, + .mimetype = "x-obex/folder-listing", + .open = pcsuite_open, + .close = string_free, + .read = folder_read, +}; + +static int filesystem_init(void) +{ + int err; + + err = obex_mime_type_driver_register(&folder); + if (err < 0) + return err; + + err = obex_mime_type_driver_register(&capability); + if (err < 0) + return err; + + err = obex_mime_type_driver_register(&pcsuite); + if (err < 0) + return err; + + return obex_mime_type_driver_register(&file); +} + +static void filesystem_exit(void) +{ + obex_mime_type_driver_unregister(&folder); + obex_mime_type_driver_unregister(&capability); + obex_mime_type_driver_unregister(&file); +} + +OBEX_PLUGIN_DEFINE(filesystem, filesystem_init, filesystem_exit) diff --git a/obexd/plugins/filesystem.h b/obexd/plugins/filesystem.h new file mode 100644 index 0000000..f95773b --- /dev/null +++ b/obexd/plugins/filesystem.h @@ -0,0 +1,26 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +ssize_t string_read(void *object, void *buf, size_t count); +gboolean is_filename(const char *name); +int verify_path(const char *path); diff --git a/obexd/plugins/ftp.c b/obexd/plugins/ftp.c new file mode 100644 index 0000000..3ee18a6 --- /dev/null +++ b/obexd/plugins/ftp.c @@ -0,0 +1,532 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Nokia Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "obexd/src/obexd.h" +#include "obexd/src/plugin.h" +#include "obexd/src/log.h" +#include "obexd/src/obex.h" +#include "obexd/src/manager.h" +#include "obexd/src/mimetype.h" +#include "obexd/src/service.h" +#include "ftp.h" +#include "filesystem.h" + +#define LST_TYPE "x-obex/folder-listing" +#define CAP_TYPE "x-obex/capability" + +static const uint8_t FTP_TARGET[TARGET_SIZE] = { + 0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, + 0x98, 0x4E, 0x52, 0x54, 0x00, 0xDC, 0x9E, 0x09 }; + +struct ftp_session { + struct obex_session *os; + struct obex_transfer *transfer; + char *folder; +}; + +static void set_folder(struct ftp_session *ftp, const char *new_folder) +{ + DBG("%p folder %s", ftp, new_folder); + + g_free(ftp->folder); + + ftp->folder = new_folder ? g_strdup(new_folder) : NULL; +} + +static int get_by_type(struct ftp_session *ftp, const char *type) +{ + struct obex_session *os = ftp->os; + const char *capability = obex_option_capability(); + const char *name = obex_get_name(os); + char *path; + int err; + + DBG("%p name %s type %s", ftp, name, type); + + if (type == NULL && name == NULL) + return -EBADR; + + if (type != NULL && g_ascii_strcasecmp(type, CAP_TYPE) == 0) + return obex_get_stream_start(os, capability); + + if (name != NULL && !is_filename(name)) + return -EBADR; + + path = g_build_filename(ftp->folder, name, NULL); + err = obex_get_stream_start(os, path); + + g_free(path); + + return err; +} + +void *ftp_connect(struct obex_session *os, int *err) +{ + struct ftp_session *ftp; + const char *root_folder; + + DBG(""); + + root_folder = obex_option_root_folder(); + + manager_register_session(os); + + ftp = g_new0(struct ftp_session, 1); + set_folder(ftp, root_folder); + ftp->os = os; + + if (err) + *err = 0; + + ftp->transfer = manager_register_transfer(os); + + DBG("session %p created", ftp); + + return ftp; +} + +int ftp_get(struct obex_session *os, void *user_data) +{ + struct ftp_session *ftp = user_data; + const char *type = obex_get_type(os); + int ret; + + DBG("%p", ftp); + + if (ftp->folder == NULL) + return -ENOENT; + + ret = get_by_type(ftp, type); + if (ret < 0) + return ret; + + /* Only track progress of file transfer */ + if (type == NULL) + manager_emit_transfer_started(ftp->transfer); + + return 0; +} + +static int ftp_delete(struct ftp_session *ftp, const char *name) +{ + char *path; + int ret = 0; + + DBG("%p name %s", ftp, name); + + if (!(ftp->folder && name)) + return -EINVAL; + + path = g_build_filename(ftp->folder, name, NULL); + + if (obex_remove(ftp->os, path) < 0) + ret = -errno; + + g_free(path); + + return ret; +} + +int ftp_chkput(struct obex_session *os, void *user_data) +{ + struct ftp_session *ftp = user_data; + const char *name = obex_get_name(os); + char *path; + int ret; + + DBG("%p name %s", ftp, name); + + if (name == NULL) + return -EBADR; + + if (!is_filename(name)) + return -EBADR; + + if (obex_get_size(os) == OBJECT_SIZE_DELETE) + return 0; + + path = g_build_filename(ftp->folder, name, NULL); + + ret = obex_put_stream_start(os, path); + + if (ret == 0) + manager_emit_transfer_started(ftp->transfer); + + g_free(path); + + return ret; +} + +int ftp_put(struct obex_session *os, void *user_data) +{ + struct ftp_session *ftp = user_data; + const char *name = obex_get_name(os); + ssize_t size = obex_get_size(os); + + DBG("%p name %s size %zd", ftp, name, size); + + if (ftp->folder == NULL) + return -EPERM; + + if (name == NULL) + return -EBADR; + + if (!is_filename(name)) + return -EBADR; + + if (size == OBJECT_SIZE_DELETE) + return ftp_delete(ftp, name); + + return 0; +} + +int ftp_setpath(struct obex_session *os, void *user_data) +{ + struct ftp_session *ftp = user_data; + const char *root_folder, *name; + const uint8_t *nonhdr; + char *fullname; + struct stat dstat; + gboolean root; + int err; + + if (obex_get_non_header_data(os, &nonhdr) != 2) { + error("Set path failed: flag and constants not found!"); + return -EBADMSG; + } + + name = obex_get_name(os); + root_folder = obex_option_root_folder(); + root = g_str_equal(root_folder, ftp->folder); + + DBG("%p name %s", ftp, name); + + /* Check flag "Backup" */ + if ((nonhdr[0] & 0x01) == 0x01) { + DBG("Set to parent path"); + + if (root) + return -EPERM; + + fullname = g_path_get_dirname(ftp->folder); + set_folder(ftp, fullname); + g_free(fullname); + + DBG("Set to parent path: %s", ftp->folder); + + return 0; + } + + if (!name) { + DBG("Set path failed: name missing!"); + return -EINVAL; + } + + if (strlen(name) == 0) { + DBG("Set to root"); + set_folder(ftp, root_folder); + return 0; + } + + /* Check and set to name path */ + if (!is_filename(name)) { + error("Set path failed: name incorrect!"); + return -EPERM; + } + + fullname = g_build_filename(ftp->folder, name, NULL); + + DBG("Fullname: %s", fullname); + + err = verify_path(fullname); + if (err == -ENOENT) + goto not_found; + + if (err < 0) + goto done; + + err = stat(fullname, &dstat); + + if (err < 0) { + err = -errno; + + if (err == -ENOENT) + goto not_found; + + DBG("stat: %s(%d)", strerror(-err), -err); + + goto done; + } + + if (S_ISDIR(dstat.st_mode) && (dstat.st_mode & S_IRUSR) && + (dstat.st_mode & S_IXUSR)) { + set_folder(ftp, fullname); + goto done; + } + + err = -EPERM; + goto done; + +not_found: + if (nonhdr[0] != 0) { + err = -ENOENT; + goto done; + } + + if (mkdir(fullname, 0755) < 0) { + err = -errno; + DBG("mkdir: %s(%d)", strerror(-err), -err); + goto done; + } + + err = 0; + set_folder(ftp, fullname); + +done: + g_free(fullname); + return err; +} + +static gboolean is_valid_path(const char *path) +{ + char **elements, **cur; + int depth = 0; + + elements = g_strsplit(path, "/", 0); + + for (cur = elements; *cur != NULL; cur++) { + if (**cur == '\0' || strcmp(*cur, ".") == 0) + continue; + + if (strcmp(*cur, "..") == 0) { + depth--; + if (depth < 0) + break; + continue; + } + + depth++; + } + + g_strfreev(elements); + + if (depth < 0) + return FALSE; + + return TRUE; +} + +static char *ftp_build_filename(struct ftp_session *ftp, const char *destname) +{ + char *filename; + + /* DestName can either be relative or absolute (FTP style) */ + if (destname[0] == '/') + filename = g_build_filename(obex_option_root_folder(), + destname, NULL); + else + filename = g_build_filename(ftp->folder, destname, NULL); + + if (is_valid_path(filename + strlen(obex_option_root_folder()))) + return filename; + + g_free(filename); + + return NULL; +} + +static int ftp_copy(struct ftp_session *ftp, const char *name, + const char *destname) +{ + char *source, *destination, *destdir; + int ret; + + DBG("%p name %s destination %s", ftp, name, destname); + + if (ftp->folder == NULL) { + error("No folder set"); + return -ENOENT; + } + + if (name == NULL || destname == NULL) + return -EINVAL; + + destination = ftp_build_filename(ftp, destname); + + if (destination == NULL) + return -EBADR; + + destdir = g_path_get_dirname(destination); + ret = verify_path(destdir); + g_free(destdir); + + if (ret < 0) + return ret; + + source = g_build_filename(ftp->folder, name, NULL); + + ret = obex_copy(ftp->os, source, destination); + + g_free(source); + g_free(destination); + + return ret; +} + +static int ftp_move(struct ftp_session *ftp, const char *name, + const char *destname) +{ + char *source, *destination, *destdir; + int ret; + + DBG("%p name %s destname %s", ftp, name, destname); + + if (ftp->folder == NULL) { + error("No folder set"); + return -ENOENT; + } + + if (name == NULL || destname == NULL) + return -EINVAL; + + destination = ftp_build_filename(ftp, destname); + + if (destination == NULL) + return -EBADR; + + destdir = g_path_get_dirname(destination); + ret = verify_path(destdir); + g_free(destdir); + + if (ret < 0) + return ret; + + source = g_build_filename(ftp->folder, name, NULL); + + ret = obex_move(ftp->os, source, destination); + + g_free(source); + g_free(destination); + + return ret; +} + +int ftp_action(struct obex_session *os, void *user_data) +{ + struct ftp_session *ftp = user_data; + const char *name, *destname; + uint8_t action_id; + + name = obex_get_name(os); + if (name == NULL || !is_filename(name)) + return -EBADR; + + destname = obex_get_destname(os); + action_id = obex_get_action_id(os); + + DBG("%p action 0x%x", ftp, action_id); + + switch (action_id) { + case 0x00: /* Copy Object */ + return ftp_copy(ftp, name, destname); + case 0x01: /* Move/Rename Object */ + return ftp_move(ftp, name, destname); + default: + return -ENOSYS; + } +} + +void ftp_disconnect(struct obex_session *os, void *user_data) +{ + struct ftp_session *ftp = user_data; + + DBG("%p", ftp); + + manager_unregister_session(os); + + manager_unregister_transfer(ftp->transfer); + + g_free(ftp->folder); + g_free(ftp); +} + +static void ftp_progress(struct obex_session *os, void *user_data) +{ + struct ftp_session *ftp = user_data; + + manager_emit_transfer_progress(ftp->transfer); +} + +static void ftp_reset(struct obex_session *os, void *user_data) +{ + struct ftp_session *ftp = user_data; + + manager_emit_transfer_completed(ftp->transfer); +} + +static struct obex_service_driver ftp = { + .name = "File Transfer server", + .service = OBEX_FTP, + .target = FTP_TARGET, + .target_size = TARGET_SIZE, + .connect = ftp_connect, + .progress = ftp_progress, + .get = ftp_get, + .put = ftp_put, + .chkput = ftp_chkput, + .setpath = ftp_setpath, + .action = ftp_action, + .disconnect = ftp_disconnect, + .reset = ftp_reset +}; + +static int ftp_init(void) +{ + return obex_service_driver_register(&ftp); +} + +static void ftp_exit(void) +{ + obex_service_driver_unregister(&ftp); +} + +OBEX_PLUGIN_DEFINE(ftp, ftp_init, ftp_exit) diff --git a/obexd/plugins/ftp.h b/obexd/plugins/ftp.h new file mode 100644 index 0000000..f06de84 --- /dev/null +++ b/obexd/plugins/ftp.h @@ -0,0 +1,30 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void *ftp_connect(struct obex_session *os, int *err); +int ftp_get(struct obex_session *os, void *user_data); +int ftp_chkput(struct obex_session *os, void *user_data); +int ftp_put(struct obex_session *os, void *user_data); +int ftp_setpath(struct obex_session *os, void *user_data); +void ftp_disconnect(struct obex_session *os, void *user_data); +int ftp_action(struct obex_session *os, void *user_data); diff --git a/obexd/plugins/irmc.c b/obexd/plugins/irmc.c new file mode 100644 index 0000000..fcf5340 --- /dev/null +++ b/obexd/plugins/irmc.c @@ -0,0 +1,489 @@ +/* + * + * OBEX IrMC Sync Server + * + * Copyright (C) 2010 Marcel Mol + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "obexd/src/obexd.h" +#include "obexd/src/plugin.h" +#include "obexd/src/log.h" +#include "obexd/src/obex.h" +#include "obexd/src/service.h" +#include "obexd/src/manager.h" +#include "obexd/src/mimetype.h" +#include "phonebook.h" +#include "filesystem.h" + +struct aparam_header { + uint8_t tag; + uint8_t len; + uint8_t val[0]; +} __attribute__ ((packed)); + +#define DID_LEN 18 + +struct irmc_session { + struct obex_session *os; + struct apparam_field *params; + uint16_t entries; + GString *buffer; + char sn[DID_LEN]; + char did[DID_LEN]; + char manu[DID_LEN]; + char model[DID_LEN]; + void *request; +}; + +#define IRMC_TARGET_SIZE 9 + +static const guint8 IRMC_TARGET[IRMC_TARGET_SIZE] = { + 0x49, 0x52, 0x4d, 0x43, 0x2d, 0x53, 0x59, 0x4e, 0x43 }; + +/* FIXME: + * the IrMC specs state the first vcard should be the owner + * vcard. As there is no simple way to collect ownerdetails + * just create an empty vcard (which is allowed according to the + * specs). + */ +static const char *owner_vcard = + "BEGIN:VCARD\r\n" + "VERSION:2.1\r\n" + "N:\r\n" + "TEL:\r\n" + "X-IRMX-LUID:0\r\n" + "END:VCARD\r\n"; + +static void phonebook_size_result(const char *buffer, size_t bufsize, + int vcards, int missed, + gboolean lastpart, void *user_data) +{ + struct irmc_session *irmc = user_data; + + DBG("vcards %d", vcards); + + irmc->params->maxlistcount = vcards; + + if (irmc->request) { + phonebook_req_finalize(irmc->request); + irmc->request = NULL; + } +} + +static void query_result(const char *buffer, size_t bufsize, int vcards, + int missed, gboolean lastpart, void *user_data) +{ + struct irmc_session *irmc = user_data; + const char *s, *t; + + DBG("bufsize %zu vcards %d missed %d", bufsize, vcards, missed); + + if (irmc->request) { + phonebook_req_finalize(irmc->request); + irmc->request = NULL; + } + + /* first add a 'owner' vcard */ + if (!irmc->buffer) + irmc->buffer = g_string_new(owner_vcard); + else + irmc->buffer = g_string_append(irmc->buffer, owner_vcard); + + if (buffer == NULL) + goto done; + + /* loop around buffer and add X-IRMC-LUID attribs */ + s = buffer; + while ((t = strstr(s, "UID:")) != NULL) { + /* add up to UID: into buffer */ + irmc->buffer = g_string_append_len(irmc->buffer, s, t-s); + /* + * add UID: line into buffer + * Not sure if UID is still needed if X-IRMC-LUID is there + */ + s = t; + t = strstr(s, "\r\n"); + t += 2; + irmc->buffer = g_string_append_len(irmc->buffer, s, t-s); + /* add X-IRMC-LUID with same number as UID */ + irmc->buffer = g_string_append_len(irmc->buffer, + "X-IRMC-LUID:", 12); + s += 4; /* point to uid number */ + irmc->buffer = g_string_append_len(irmc->buffer, s, t-s); + s = t; + } + /* add remaining bit of buffer */ + irmc->buffer = g_string_append(irmc->buffer, s); + +done: + obex_object_set_io_flags(irmc, G_IO_IN, 0); +} + +static void *irmc_connect(struct obex_session *os, int *err) +{ + struct irmc_session *irmc; + struct apparam_field *param; + int ret; + + DBG(""); + + manager_register_session(os); + + irmc = g_new0(struct irmc_session, 1); + irmc->os = os; + + /* FIXME: + * Ideally get capabilities info here and use that to define + * IrMC DID and SN etc parameters. + * For now lets used hostname and some 'random' value + */ + gethostname(irmc->did, DID_LEN); + strncpy(irmc->sn, "12345", sizeof(irmc->sn) - 1); + strncpy(irmc->manu, "obex", sizeof(irmc->manu) - 1); + strncpy(irmc->model, "mymodel", sizeof(irmc->model) - 1); + + /* We need to know the number of contact/cal/nt entries + * somewhere so why not do it now. + */ + param = g_new0(struct apparam_field, 1); + param->maxlistcount = 0; /* to count the number of vcards... */ + param->filter = 0x200085; /* UID TEL N VERSION */ + irmc->params = param; + irmc->request = phonebook_pull(PB_CONTACTS, irmc->params, + phonebook_size_result, irmc, err); + ret = phonebook_pull_read(irmc->request); + if (err) + *err = ret; + + return irmc; +} + +static int irmc_get(struct obex_session *os, void *user_data) +{ + struct irmc_session *irmc = user_data; + const char *type = obex_get_type(os); + const char *name = obex_get_name(os); + char *path; + int ret; + + DBG("name %s type %s irmc %p", name, type ? type : "NA", irmc); + + path = g_strdup(name); + + ret = obex_get_stream_start(os, path); + + g_free(path); + + return ret; +} + +static void irmc_disconnect(struct obex_session *os, void *user_data) +{ + struct irmc_session *irmc = user_data; + + DBG(""); + + manager_unregister_session(os); + + if (irmc->params) { + if (irmc->params->searchval) + g_free(irmc->params->searchval); + g_free(irmc->params); + } + + if (irmc->buffer) + g_string_free(irmc->buffer, TRUE); + + g_free(irmc); +} + +static int irmc_chkput(struct obex_session *os, void *user_data) +{ + DBG(""); + /* Reject all PUTs */ + return -EBADR; +} + +static int irmc_open_devinfo(struct irmc_session *irmc) +{ + if (!irmc->buffer) + irmc->buffer = g_string_new(""); + + g_string_append_printf(irmc->buffer, + "MANU:%s\r\n" + "MOD:%s\r\n" + "SN:%s\r\n" + "IRMC-VERSION:1.1\r\n" + "PB-TYPE-TX:VCARD2.1\r\n" + "PB-TYPE-RX:NONE\r\n" + "CAL-TYPE-TX:NONE\r\n" + "CAL-TYPE-RX:NONE\r\n" + "MSG-TYPE-TX:NONE\r\n" + "MSG-TYPE-RX:NONE\r\n" + "NOTE-TYPE-TX:NONE\r\n" + "NOTE-TYPE-RX:NONE\r\n", + irmc->manu, irmc->model, irmc->sn); + + return 0; +} + +static int irmc_open_pb(struct irmc_session *irmc) +{ + int ret; + + /* how can we tell if the vcard count call already finished? */ + irmc->request = phonebook_pull(PB_CONTACTS, irmc->params, + query_result, irmc, &ret); + if (ret < 0) { + DBG("phonebook_pull failed..."); + return ret; + } + + ret = phonebook_pull_read(irmc->request); + if (ret < 0) { + DBG("phonebook_pull_read failed..."); + return ret; + } + + return 0; +} + +static int irmc_open_info(struct irmc_session *irmc) +{ + if (irmc->buffer == NULL) + irmc->buffer = g_string_new(""); + + g_string_printf(irmc->buffer, "Total-Records:%d\r\n" + "Maximum-Records:%d\r\n" + "IEL:2\r\n" + "DID:%s\r\n", + irmc->params->maxlistcount, + irmc->params->maxlistcount, irmc->did); + + return 0; +} + +static int irmc_open_cc(struct irmc_session *irmc) +{ + if (irmc->buffer == NULL) + irmc->buffer = g_string_new(""); + + g_string_printf(irmc->buffer, "%d\r\n", irmc->params->maxlistcount); + + return 0; +} + +static int irmc_open_cal(struct irmc_session *irmc) +{ + /* no suport yet. Just return an empty buffer. cal.vcs */ + DBG("unsupported, returning empty buffer"); + + if (!irmc->buffer) + irmc->buffer = g_string_new(""); + + return 0; +} + +static int irmc_open_nt(struct irmc_session *irmc) +{ + /* no suport yet. Just return an empty buffer. nt.vnt */ + DBG("unsupported, returning empty buffer"); + + if (!irmc->buffer) + irmc->buffer = g_string_new(""); + + return 0; +} + +static int irmc_open_luid(struct irmc_session *irmc) +{ + if (irmc->buffer == NULL) + irmc->buffer = g_string_new(""); + + DBG("changelog request, force whole book"); + g_string_printf(irmc->buffer, "SN:%s\r\n" + "DID:%s\r\n" + "Total-Records:%d\r\n" + "Maximum-Records:%d\r\n" + "*\r\n", + irmc->sn, irmc->did, + irmc->params->maxlistcount, + irmc->params->maxlistcount); + + return 0; +} + +static void *irmc_open(const char *name, int oflag, mode_t mode, void *context, + size_t *size, int *err) +{ + struct irmc_session *irmc = context; + int ret = 0; + char *path; + + DBG("name %s context %p", name, context); + + if (oflag != O_RDONLY) { + ret = -EPERM; + goto fail; + } + + if (name == NULL) { + ret = -EBADR; + goto fail; + } + + /* Always contains the absolute path */ + if (g_path_is_absolute(name)) + path = g_strdup(name); + else + path = g_build_filename("/", name, NULL); + + if (g_str_equal(path, PB_DEVINFO)) + ret = irmc_open_devinfo(irmc); + else if (g_str_equal(path, PB_CONTACTS)) + ret = irmc_open_pb(irmc); + else if (g_str_equal(path, PB_INFO_LOG)) + ret = irmc_open_info(irmc); + else if (g_str_equal(path, PB_CC_LOG)) + ret = irmc_open_cc(irmc); + else if (g_str_has_prefix(path, PB_CALENDAR_FOLDER)) + ret = irmc_open_cal(irmc); + else if (g_str_has_prefix(path, PB_NOTES_FOLDER)) + ret = irmc_open_nt(irmc); + else if (g_str_has_prefix(path, PB_LUID_FOLDER)) + ret = irmc_open_luid(irmc); + else + ret = -EBADR; + + g_free(path); + + if (ret == 0) + return irmc; + +fail: + if (err) + *err = ret; + + return NULL; +} + +static int irmc_close(void *object) +{ + struct irmc_session *irmc = object; + + DBG(""); + + if (irmc->buffer) { + g_string_free(irmc->buffer, TRUE); + irmc->buffer = NULL; + } + + if (irmc->request) { + phonebook_req_finalize(irmc->request); + irmc->request = NULL; + } + + return 0; +} + +static ssize_t irmc_read(void *object, void *buf, size_t count) +{ + struct irmc_session *irmc = object; + int len; + + DBG("buffer %p count %zu", irmc->buffer, count); + if (!irmc->buffer) + return -EAGAIN; + + len = string_read(irmc->buffer, buf, count); + DBG("returning %d bytes", len); + return len; +} + +static struct obex_mime_type_driver irmc_driver = { + .target = IRMC_TARGET, + .target_size = IRMC_TARGET_SIZE, + .open = irmc_open, + .close = irmc_close, + .read = irmc_read, +}; + +static struct obex_service_driver irmc = { + .name = "IRMC Sync server", + .service = OBEX_IRMC, + .target = IRMC_TARGET, + .target_size = IRMC_TARGET_SIZE, + .connect = irmc_connect, + .get = irmc_get, + .disconnect = irmc_disconnect, + .chkput = irmc_chkput +}; + +static int irmc_init(void) +{ + int err; + + DBG(""); + err = phonebook_init(); + if (err < 0) + return err; + + err = obex_mime_type_driver_register(&irmc_driver); + if (err < 0) + goto fail_mime_irmc; + + err = obex_service_driver_register(&irmc); + if (err < 0) + goto fail_irmc_reg; + + return 0; + +fail_irmc_reg: + obex_mime_type_driver_unregister(&irmc_driver); +fail_mime_irmc: + phonebook_exit(); + + return err; +} + +static void irmc_exit(void) +{ + DBG(""); + obex_service_driver_unregister(&irmc); + obex_mime_type_driver_unregister(&irmc_driver); + phonebook_exit(); +} + +OBEX_PLUGIN_DEFINE(irmc, irmc_init, irmc_exit) diff --git a/obexd/plugins/mas.c b/obexd/plugins/mas.c new file mode 100644 index 0000000..f73c3e9 --- /dev/null +++ b/obexd/plugins/mas.c @@ -0,0 +1,934 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2010-2011 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "gobex/gobex.h" +#include "gobex/gobex-apparam.h" + +#include "obexd/src/obexd.h" +#include "obexd/src/plugin.h" +#include "obexd/src/log.h" +#include "obexd/src/obex.h" +#include "obexd/src/service.h" +#include "obexd/src/mimetype.h" +#include "obexd/src/manager.h" +#include "obexd/src/map_ap.h" +#include "filesystem.h" +#include "messages.h" + +#define READ_STATUS_REQ 0 +#define DELETE_STATUS_REQ 1 + +#define XML_DECL "" + +/* Building blocks for x-obex/folder-listing */ +#define FL_DTD "" +#define FL_BODY_BEGIN "" +#define FL_BODY_EMPTY "" +#define FL_PARENT_FOLDER_ELEMENT "" +#define FL_FOLDER_ELEMENT "" +#define FL_BODY_END "" + +#define ML_BODY_BEGIN "" +#define ML_BODY_END "" + +struct mas_session { + struct mas_request *request; + void *backend_data; + gboolean finished; + gboolean nth_call; + GString *buffer; + GObexApparam *inparams; + GObexApparam *outparams; + gboolean ap_sent; + uint8_t notification_status; +}; + +static const uint8_t MAS_TARGET[TARGET_SIZE] = { + 0xbb, 0x58, 0x2b, 0x40, 0x42, 0x0c, 0x11, 0xdb, + 0xb0, 0xde, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 }; + +static int get_params(struct obex_session *os, struct mas_session *mas) +{ + const uint8_t *buffer; + ssize_t size; + + size = obex_get_apparam(os, &buffer); + if (size <= 0) + return 0; + + mas->inparams = g_obex_apparam_decode(buffer, size); + if (mas->inparams == NULL) { + DBG("Error when parsing parameters!"); + return -EBADR; + } + + return 0; +} + +static void reset_request(struct mas_session *mas) +{ + if (mas->buffer) { + g_string_free(mas->buffer, TRUE); + mas->buffer = NULL; + } + + if (mas->inparams) { + g_obex_apparam_free(mas->inparams); + mas->inparams = NULL; + } + + if (mas->outparams) { + g_obex_apparam_free(mas->outparams); + mas->outparams = NULL; + } + + mas->nth_call = FALSE; + mas->finished = FALSE; + mas->ap_sent = FALSE; +} + +static void mas_clean(struct mas_session *mas) +{ + reset_request(mas); + g_free(mas); +} + +static void *mas_connect(struct obex_session *os, int *err) +{ + struct mas_session *mas; + + DBG(""); + + mas = g_new0(struct mas_session, 1); + + *err = messages_connect(&mas->backend_data); + if (*err < 0) + goto failed; + + manager_register_session(os); + + return mas; + +failed: + g_free(mas); + + return NULL; +} + +static void mas_disconnect(struct obex_session *os, void *user_data) +{ + struct mas_session *mas = user_data; + + DBG(""); + + manager_unregister_session(os); + messages_disconnect(mas->backend_data); + + mas_clean(mas); +} + +static int mas_get(struct obex_session *os, void *user_data) +{ + struct mas_session *mas = user_data; + const char *type = obex_get_type(os); + const char *name = obex_get_name(os); + int ret; + + DBG("GET: name %s type %s mas %p", + name, type, mas); + + if (type == NULL) + return -EBADR; + + ret = get_params(os, mas); + if (ret < 0) + goto failed; + + ret = obex_get_stream_start(os, name); + if (ret < 0) + goto failed; + + return 0; + +failed: + reset_request(mas); + + return ret; +} + +static int mas_put(struct obex_session *os, void *user_data) +{ + struct mas_session *mas = user_data; + const char *type = obex_get_type(os); + const char *name = obex_get_name(os); + int ret; + + DBG("PUT: name %s type %s mas %p", name, type, mas); + + if (type == NULL) + return -EBADR; + + ret = get_params(os, mas); + if (ret < 0) + goto failed; + + ret = obex_put_stream_start(os, name); + if (ret < 0) + goto failed; + + return 0; + +failed: + reset_request(mas); + + return ret; +} + +/* FIXME: Preserve whitespaces */ +static void g_string_append_escaped_printf(GString *string, + const char *format, ...) +{ + va_list ap; + char *escaped; + + va_start(ap, format); + escaped = g_markup_vprintf_escaped(format, ap); + g_string_append(string, escaped); + g_free(escaped); + va_end(ap); +} + +static gchar *get_mse_timestamp(void) +{ + struct timeval time_val; + struct tm ltime; + gchar *local_ts; + char sign; + + if (gettimeofday(&time_val, NULL) < 0) + return NULL; + + if (!localtime_r(&time_val.tv_sec, <ime)) + return NULL; + + if (difftime(mktime(localtime(&time_val.tv_sec)), + mktime(gmtime(&time_val.tv_sec))) < 0) + sign = '+'; + else + sign = '-'; + + local_ts = g_strdup_printf("%04d%02d%02dT%02d%02d%02d%c%2ld%2ld", + ltime.tm_year + 1900, ltime.tm_mon + 1, + ltime.tm_mday, ltime.tm_hour, + ltime.tm_min, ltime.tm_sec, sign, + ltime.tm_gmtoff/3600, + (ltime.tm_gmtoff%3600)/60); + + return local_ts; +} + +static const char *yesorno(gboolean a) +{ + if (a) + return "yes"; + + return "no"; +} + +static void get_messages_listing_cb(void *session, int err, uint16_t size, + gboolean newmsg, + const struct messages_message *entry, + void *user_data) +{ + struct mas_session *mas = user_data; + uint16_t max = 1024; + gchar *mse_time; + + if (err < 0 && err != -EAGAIN) { + obex_object_set_io_flags(mas, G_IO_ERR, err); + return; + } + + if (mas->inparams) + g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, + &max); + + if (max == 0) { + if (!entry) + mas->finished = TRUE; + + goto proceed; + } + + if (!mas->nth_call) { + g_string_append(mas->buffer, ML_BODY_BEGIN); + mas->nth_call = TRUE; + } + + if (!entry) { + g_string_append(mas->buffer, ML_BODY_END); + mas->finished = TRUE; + + goto proceed; + } + + g_string_append(mas->buffer, "buffer, " handle=\"%s\"", + entry->handle); + + if (entry->mask & PMASK_SUBJECT) + g_string_append_escaped_printf(mas->buffer, " subject=\"%s\"", + entry->subject); + + if (entry->mask & PMASK_DATETIME) + g_string_append_escaped_printf(mas->buffer, " datetime=\"%s\"", + entry->datetime); + + if (entry->mask & PMASK_SENDER_NAME) + g_string_append_escaped_printf(mas->buffer, + " sender_name=\"%s\"", + entry->sender_name); + + if (entry->mask & PMASK_SENDER_ADDRESSING) + g_string_append_escaped_printf(mas->buffer, + " sender_addressing=\"%s\"", + entry->sender_addressing); + + if (entry->mask & PMASK_REPLYTO_ADDRESSING) + g_string_append_escaped_printf(mas->buffer, + " replyto_addressing=\"%s\"", + entry->replyto_addressing); + + if (entry->mask & PMASK_RECIPIENT_NAME) + g_string_append_escaped_printf(mas->buffer, + " recipient_name=\"%s\"", + entry->recipient_name); + + if (entry->mask & PMASK_RECIPIENT_ADDRESSING) + g_string_append_escaped_printf(mas->buffer, + " recipient_addressing=\"%s\"", + entry->recipient_addressing); + + if (entry->mask & PMASK_TYPE) + g_string_append_escaped_printf(mas->buffer, " type=\"%s\"", + entry->type); + + if (entry->mask & PMASK_RECEPTION_STATUS) + g_string_append_escaped_printf(mas->buffer, + " reception_status=\"%s\"", + entry->reception_status); + + if (entry->mask & PMASK_SIZE) + g_string_append_escaped_printf(mas->buffer, " size=\"%s\"", + entry->size); + + if (entry->mask & PMASK_ATTACHMENT_SIZE) + g_string_append_escaped_printf(mas->buffer, + " attachment_size=\"%s\"", + entry->attachment_size); + + if (entry->mask & PMASK_TEXT) + g_string_append_escaped_printf(mas->buffer, " text=\"%s\"", + yesorno(entry->text)); + + if (entry->mask & PMASK_READ) + g_string_append_escaped_printf(mas->buffer, " read=\"%s\"", + yesorno(entry->read)); + + if (entry->mask & PMASK_SENT) + g_string_append_escaped_printf(mas->buffer, " sent=\"%s\"", + yesorno(entry->sent)); + + if (entry->mask & PMASK_PROTECTED) + g_string_append_escaped_printf(mas->buffer, " protected=\"%s\"", + yesorno(entry->protect)); + + if (entry->mask & PMASK_PRIORITY) + g_string_append_escaped_printf(mas->buffer, " priority=\"%s\"", + yesorno(entry->priority)); + + g_string_append(mas->buffer, "/>\n"); + +proceed: + if (!entry) { + mas->outparams = g_obex_apparam_set_uint16(mas->outparams, + MAP_AP_MESSAGESLISTINGSIZE, + size); + mas->outparams = g_obex_apparam_set_uint8(mas->outparams, + MAP_AP_NEWMESSAGE, + newmsg ? 1 : 0); + /* Response to report the local time of MSE */ + mse_time = get_mse_timestamp(); + if (mse_time) { + g_obex_apparam_set_string(mas->outparams, + MAP_AP_MSETIME, mse_time); + g_free(mse_time); + } + } + + if (err != -EAGAIN) + obex_object_set_io_flags(mas, G_IO_IN, 0); +} + +static void get_message_cb(void *session, int err, gboolean fmore, + const char *chunk, void *user_data) +{ + struct mas_session *mas = user_data; + + DBG(""); + + if (err < 0 && err != -EAGAIN) { + obex_object_set_io_flags(mas, G_IO_ERR, err); + return; + } + + if (!chunk) { + mas->finished = TRUE; + goto proceed; + } + + g_string_append(mas->buffer, chunk); + +proceed: + if (err != -EAGAIN) + obex_object_set_io_flags(mas, G_IO_IN, 0); +} + +static void get_folder_listing_cb(void *session, int err, uint16_t size, + const char *name, void *user_data) +{ + struct mas_session *mas = user_data; + uint16_t max = 1024; + + if (err < 0 && err != -EAGAIN) { + obex_object_set_io_flags(mas, G_IO_ERR, err); + return; + } + + if (mas->inparams) + g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, + &max); + + if (max == 0) { + if (err != -EAGAIN) + mas->outparams = g_obex_apparam_set_uint16( + mas->outparams, + MAP_AP_FOLDERLISTINGSIZE, + size); + + if (!name) + mas->finished = TRUE; + + goto proceed; + } + + if (!mas->nth_call) { + g_string_append(mas->buffer, XML_DECL); + g_string_append(mas->buffer, FL_DTD); + if (!name) { + g_string_append(mas->buffer, FL_BODY_EMPTY); + mas->finished = TRUE; + goto proceed; + } + g_string_append(mas->buffer, FL_BODY_BEGIN); + mas->nth_call = TRUE; + } + + if (!name) { + g_string_append(mas->buffer, FL_BODY_END); + mas->finished = TRUE; + goto proceed; + } + + if (g_strcmp0(name, "..") == 0) + g_string_append(mas->buffer, FL_PARENT_FOLDER_ELEMENT); + else + g_string_append_escaped_printf(mas->buffer, FL_FOLDER_ELEMENT, + name); + +proceed: + if (err != -EAGAIN) + obex_object_set_io_flags(mas, G_IO_IN, err); +} + +static void set_status_cb(void *session, int err, void *user_data) +{ + struct mas_session *mas = user_data; + + DBG(""); + + mas->finished = TRUE; + + if (err < 0) + obex_object_set_io_flags(mas, G_IO_ERR, err); + else + obex_object_set_io_flags(mas, G_IO_OUT, 0); +} + +static int mas_setpath(struct obex_session *os, void *user_data) +{ + const char *name; + const uint8_t *nonhdr; + struct mas_session *mas = user_data; + + if (obex_get_non_header_data(os, &nonhdr) != 2) { + error("Set path failed: flag and constants not found!"); + return -EBADR; + } + + name = obex_get_name(os); + + DBG("SETPATH: name %s nonhdr 0x%x%x", name, nonhdr[0], nonhdr[1]); + + if ((nonhdr[0] & 0x02) != 0x02) { + DBG("Error: requested directory creation"); + return -EBADR; + } + + return messages_set_folder(mas->backend_data, name, nonhdr[0] & 0x01); +} + +static void *folder_listing_open(const char *name, int oflag, mode_t mode, + void *driver_data, size_t *size, int *err) +{ + struct mas_session *mas = driver_data; + /* 1024 is the default when there was no MaxListCount sent */ + uint16_t max = 1024; + uint16_t offset = 0; + + if (oflag != O_RDONLY) { + *err = -EBADR; + return NULL; + } + + DBG("name = %s", name); + + if (mas->inparams) { + g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, + &max); + g_obex_apparam_get_uint16(mas->inparams, MAP_AP_STARTOFFSET, + &offset); + } + + *err = messages_get_folder_listing(mas->backend_data, name, max, + offset, get_folder_listing_cb, mas); + + mas->buffer = g_string_new(""); + + if (*err < 0) + return NULL; + else + return mas; +} + +static void *msg_listing_open(const char *name, int oflag, mode_t mode, + void *driver_data, size_t *size, int *err) +{ + struct mas_session *mas = driver_data; + struct messages_filter filter = { 0, }; + /* 1024 is the default when there was no MaxListCount sent */ + uint16_t max = 1024; + uint16_t offset = 0; + /* If MAP client does not specify the subject length, + then subject_len = 0 and subject should be sent unaltered. */ + uint8_t subject_len = 0; + + DBG(""); + + if (oflag != O_RDONLY) { + *err = -EBADR; + return NULL; + } + + if (!mas->inparams) + goto done; + + g_obex_apparam_get_uint16(mas->inparams, MAP_AP_MAXLISTCOUNT, &max); + g_obex_apparam_get_uint16(mas->inparams, MAP_AP_STARTOFFSET, &offset); + g_obex_apparam_get_uint8(mas->inparams, MAP_AP_SUBJECTLENGTH, + &subject_len); + + g_obex_apparam_get_uint32(mas->inparams, MAP_AP_PARAMETERMASK, + &filter.parameter_mask); + g_obex_apparam_get_uint8(mas->inparams, MAP_AP_FILTERMESSAGETYPE, + &filter.type); + filter.period_begin = g_obex_apparam_get_string(mas->inparams, + MAP_AP_FILTERPERIODBEGIN); + filter.period_end = g_obex_apparam_get_string(mas->inparams, + MAP_AP_FILTERPERIODEND); + g_obex_apparam_get_uint8(mas->inparams, MAP_AP_FILTERREADSTATUS, + &filter.read_status); + filter.recipient = g_obex_apparam_get_string(mas->inparams, + MAP_AP_FILTERRECIPIENT); + filter.originator = g_obex_apparam_get_string(mas->inparams, + MAP_AP_FILTERORIGINATOR); + g_obex_apparam_get_uint8(mas->inparams, MAP_AP_FILTERPRIORITY, + &filter.priority); + +done: + *err = messages_get_messages_listing(mas->backend_data, name, max, + offset, subject_len, &filter, + get_messages_listing_cb, mas); + + mas->buffer = g_string_new(""); + + if (*err < 0) + return NULL; + else + return mas; +} + +static void *message_open(const char *name, int oflag, mode_t mode, + void *driver_data, size_t *size, int *err) +{ + struct mas_session *mas = driver_data; + + DBG(""); + + if (oflag != O_RDONLY) { + DBG("Message pushing unsupported"); + *err = -ENOSYS; + + return NULL; + } + + *err = messages_get_message(mas->backend_data, name, 0, + get_message_cb, mas); + + mas->buffer = g_string_new(""); + + if (*err < 0) + return NULL; + else + return mas; +} + +static void *message_update_open(const char *name, int oflag, mode_t mode, + void *driver_data, size_t *size, + int *err) +{ + struct mas_session *mas = driver_data; + + DBG(""); + + if (oflag == O_RDONLY) { + *err = -EBADR; + return NULL; + } + + *err = messages_update_inbox(mas->backend_data, set_status_cb, mas); + if (*err < 0) + return NULL; + else + return mas; +} + +static void *message_set_status_open(const char *name, int oflag, mode_t mode, + void *driver_data, size_t *size, + int *err) + +{ + struct mas_session *mas = driver_data; + uint8_t indicator; + uint8_t value; + + DBG(""); + + if (oflag == O_RDONLY) { + *err = -EBADR; + return NULL; + } + + if (!g_obex_apparam_get_uint8(mas->inparams, MAP_AP_STATUSINDICATOR, + &indicator)) { + *err = -EBADR; + return NULL; + } + + if (!g_obex_apparam_get_uint8(mas->inparams, MAP_AP_STATUSVALUE, + &value)) { + *err = -EBADR; + return NULL; + } + + if (indicator == READ_STATUS_REQ) + *err = messages_set_read(mas->backend_data, name, value, + set_status_cb, mas); + else if (indicator == DELETE_STATUS_REQ) + *err = messages_set_delete(mas->backend_data, name, value, + set_status_cb, mas); + else + *err = -EBADR; + + if (*err < 0) + return NULL; + + return mas; +} + +static ssize_t any_get_next_header(void *object, void *buf, size_t mtu, + uint8_t *hi) +{ + struct mas_session *mas = object; + + DBG(""); + + if (mas->buffer->len == 0 && !mas->finished) + return -EAGAIN; + + *hi = G_OBEX_HDR_APPARAM; + + if (mas->ap_sent) + return 0; + + mas->ap_sent = TRUE; + if (!mas->outparams) + return 0; + + return g_obex_apparam_encode(mas->outparams, buf, mtu); +} + +static void *any_open(const char *name, int oflag, mode_t mode, + void *driver_data, size_t *size, int *err) +{ + DBG(""); + + *err = -ENOSYS; + + return NULL; +} + +static ssize_t any_write(void *object, const void *buf, size_t count) +{ + DBG(""); + + return count; +} + +static ssize_t any_read(void *obj, void *buf, size_t count) +{ + struct mas_session *mas = obj; + ssize_t len; + + DBG(""); + + len = string_read(mas->buffer, buf, count); + + if (len == 0 && !mas->finished) + return -EAGAIN; + + return len; +} + +static int any_close(void *obj) +{ + struct mas_session *mas = obj; + + DBG(""); + + if (!mas->finished) + messages_abort(mas->backend_data); + + reset_request(mas); + + return 0; +} + +static void *notification_registration_open(const char *name, int oflag, + mode_t mode, void *driver_data, + size_t *size, int *err) +{ + struct mas_session *mas = driver_data; + uint8_t status; + + DBG(""); + + if (O_RDONLY) { + *err = -EBADR; + return NULL; + } + + if (!g_obex_apparam_get_uint8(mas->inparams, MAP_AP_NOTIFICATIONSTATUS, + &status)) { + *err = -EBADR; + return NULL; + } + + mas->notification_status = status; + mas->finished = TRUE; + *err = 0; + + return mas; +} + +static struct obex_service_driver mas = { + .name = "Message Access server", + .service = OBEX_MAS, + .target = MAS_TARGET, + .target_size = TARGET_SIZE, + .connect = mas_connect, + .get = mas_get, + .put = mas_put, + .setpath = mas_setpath, + .disconnect = mas_disconnect, +}; + +static struct obex_mime_type_driver mime_map = { + .target = MAS_TARGET, + .target_size = TARGET_SIZE, + .mimetype = NULL, + .open = any_open, + .close = any_close, + .read = any_read, + .write = any_write, +}; + +static struct obex_mime_type_driver mime_message = { + .target = MAS_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "x-bt/message", + .open = message_open, + .close = any_close, + .read = any_read, + .write = any_write, +}; + +static struct obex_mime_type_driver mime_folder_listing = { + .target = MAS_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "x-obex/folder-listing", + .get_next_header = any_get_next_header, + .open = folder_listing_open, + .close = any_close, + .read = any_read, + .write = any_write, +}; + +static struct obex_mime_type_driver mime_msg_listing = { + .target = MAS_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "x-bt/MAP-msg-listing", + .get_next_header = any_get_next_header, + .open = msg_listing_open, + .close = any_close, + .read = any_read, + .write = any_write, +}; + +static struct obex_mime_type_driver mime_notification_registration = { + .target = MAS_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "x-bt/MAP-NotificationRegistration", + .open = notification_registration_open, + .close = any_close, + .read = any_read, + .write = any_write, +}; + +static struct obex_mime_type_driver mime_message_status = { + .target = MAS_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "x-bt/messageStatus", + .open = message_set_status_open, + .close = any_close, + .read = any_read, + .write = any_write, +}; + +static struct obex_mime_type_driver mime_message_update = { + .target = MAS_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "x-bt/MAP-messageUpdate", + .open = message_update_open, + .close = any_close, + .read = any_read, + .write = any_write, +}; + +static struct obex_mime_type_driver *map_drivers[] = { + &mime_map, + &mime_message, + &mime_folder_listing, + &mime_msg_listing, + &mime_notification_registration, + &mime_message_status, + &mime_message_update, + NULL +}; + +static int mas_init(void) +{ + int err; + int i; + + err = messages_init(); + if (err < 0) + return err; + + for (i = 0; map_drivers[i] != NULL; ++i) { + err = obex_mime_type_driver_register(map_drivers[i]); + if (err < 0) + goto failed; + } + + err = obex_service_driver_register(&mas); + if (err < 0) + goto failed; + + return 0; + +failed: + for (--i; i >= 0; --i) + obex_mime_type_driver_unregister(map_drivers[i]); + + messages_exit(); + + return err; +} + +static void mas_exit(void) +{ + int i; + + obex_service_driver_unregister(&mas); + + for (i = 0; map_drivers[i] != NULL; ++i) + obex_mime_type_driver_unregister(map_drivers[i]); + + messages_exit(); +} + +OBEX_PLUGIN_DEFINE(mas, mas_init, mas_exit) diff --git a/obexd/plugins/messages-dummy.c b/obexd/plugins/messages-dummy.c new file mode 100644 index 0000000..3eca9ef --- /dev/null +++ b/obexd/plugins/messages-dummy.c @@ -0,0 +1,550 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2010-2011 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "obexd/src/log.h" + +#include "messages.h" + +#define MSG_LIST_XML "mlisting.xml" + +static char *root_folder = NULL; + +struct session { + char *cwd; + char *cwd_absolute; + void *request; +}; + +struct folder_listing_data { + struct session *session; + const char *name; + uint16_t max; + uint16_t offset; + messages_folder_listing_cb callback; + void *user_data; +}; + +struct message_listing_data { + struct session *session; + const char *name; + uint16_t max; + uint16_t offset; + uint8_t subject_len; + uint16_t size; + char *path; + FILE *fp; + const struct messages_filter *filter; + messages_get_messages_listing_cb callback; + void *user_data; +}; + +/* NOTE: Neither IrOBEX nor MAP specs says that folder listing needs to + * be sorted (in IrOBEX examples it is not). However existing implementations + * seem to follow the fig. 3-2 from MAP specification v1.0, and I've seen a + * test suite requiring folder listing to be in that order. + */ +static int folder_names_cmp(gconstpointer a, gconstpointer b, + gpointer user_data) +{ + static const char *order[] = { + "inbox", "outbox", "sent", "deleted", "draft", NULL + }; + struct session *session = user_data; + int ia, ib; + + if (g_strcmp0(session->cwd, "telecom/msg") == 0) { + for (ia = 0; order[ia]; ia++) { + if (g_strcmp0(a, order[ia]) == 0) + break; + } + for (ib = 0; order[ib]; ib++) { + if (g_strcmp0(b, order[ib]) == 0) + break; + } + if (ia != ib) + return ia - ib; + } + + return g_strcmp0(a, b); +} + +static char *get_next_subdir(DIR *dp, char *path) +{ + struct dirent *ep; + char *abs, *name; + + for (;;) { + if ((ep = readdir(dp)) == NULL) + return NULL; + + if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0) + continue; + + abs = g_build_filename(path, ep->d_name, NULL); + + if (g_file_test(abs, G_FILE_TEST_IS_DIR)) { + g_free(abs); + break; + } + + g_free(abs); + } + + name = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL); + + if (name == NULL) { + DBG("g_filename_to_utf8(): invalid filename"); + return NULL; + } + + return name; +} + +static ssize_t get_subdirs(struct folder_listing_data *fld, GSList **list) +{ + DIR *dp; + char *path, *name; + size_t n; + + path = g_build_filename(fld->session->cwd_absolute, fld->name, NULL); + dp = opendir(path); + + if (dp == NULL) { + int err = -errno; + + DBG("opendir(): %d, %s", -err, strerror(-err)); + g_free(path); + + return err; + } + + n = 0; + + while ((name = get_next_subdir(dp, path)) != NULL) { + n++; + if (fld->max > 0) + *list = g_slist_prepend(*list, name); + } + + closedir(dp); + g_free(path); + + *list = g_slist_sort_with_data(*list, folder_names_cmp, fld->session); + + return n; +} + +static void return_folder_listing(struct folder_listing_data *fld, GSList *list) +{ + struct session *session = fld->session; + GSList *cur; + uint16_t num = 0; + uint16_t offs = 0; + + for (cur = list; offs < fld->offset; offs++) { + cur = cur->next; + if (cur == NULL) + break; + } + + for (; cur != NULL && num < fld->max; cur = cur->next, num++) + fld->callback(session, -EAGAIN, 0, cur->data, fld->user_data); + + fld->callback(session, 0, 0, NULL, fld->user_data); +} + +static gboolean get_folder_listing(void *d) +{ + struct folder_listing_data *fld = d; + ssize_t n; + GSList *list = NULL; + + n = get_subdirs(fld, &list); + + if (n < 0) { + fld->callback(fld->session, n, 0, NULL, fld->user_data); + return FALSE; + } + + if (fld->max == 0) { + fld->callback(fld->session, 0, n, NULL, fld->user_data); + return FALSE; + } + + return_folder_listing(fld, list); + g_slist_free_full(list, g_free); + + return FALSE; +} + +int messages_init(void) +{ + char *tmp; + + if (root_folder) + return 0; + + tmp = getenv("MAP_ROOT"); + if (tmp) { + root_folder = g_strdup(tmp); + return 0; + } + + tmp = getenv("HOME"); + if (!tmp) + return -ENOENT; + + root_folder = g_build_filename(tmp, "map-messages", NULL); + + return 0; +} + +void messages_exit(void) +{ + g_free(root_folder); + root_folder = NULL; +} + +int messages_connect(void **s) +{ + struct session *session; + + session = g_new0(struct session, 1); + session->cwd = g_strdup(""); + session->cwd_absolute = g_strdup(root_folder); + + *s = session; + + return 0; +} + +void messages_disconnect(void *s) +{ + struct session *session = s; + + g_free(session->cwd); + g_free(session->cwd_absolute); + g_free(session); +} + +int messages_set_notification_registration(void *session, + void (*send_event)(void *session, + const struct messages_event *event, void *user_data), + void *user_data) +{ + return -ENOSYS; +} + +int messages_set_folder(void *s, const char *name, gboolean cdup) +{ + struct session *session = s; + char *newrel = NULL; + char *newabs; + char *tmp; + + if (name && (strchr(name, '/') || strcmp(name, "..") == 0)) + return -EBADR; + + if (cdup) { + if (session->cwd[0] == 0) + return -ENOENT; + + newrel = g_path_get_dirname(session->cwd); + + /* We use empty string for indication of the root directory */ + if (newrel[0] == '.' && newrel[1] == 0) + newrel[0] = 0; + } + + tmp = newrel; + if (!cdup && (!name || name[0] == 0)) + newrel = g_strdup(""); + else + newrel = g_build_filename(newrel ? newrel : session->cwd, name, + NULL); + g_free(tmp); + + newabs = g_build_filename(root_folder, newrel, NULL); + + if (!g_file_test(newabs, G_FILE_TEST_IS_DIR)) { + g_free(newrel); + g_free(newabs); + return -ENOENT; + } + + g_free(session->cwd); + session->cwd = newrel; + + g_free(session->cwd_absolute); + session->cwd_absolute = newabs; + + return 0; +} + +int messages_get_folder_listing(void *s, const char *name, uint16_t max, + uint16_t offset, + messages_folder_listing_cb callback, + void *user_data) +{ + struct session *session = s; + struct folder_listing_data *fld; + + fld = g_new0(struct folder_listing_data, 1); + fld->session = session; + fld->name = name; + fld->max = max; + fld->offset = offset; + fld->callback = callback; + fld->user_data = user_data; + + session->request = fld; + + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, get_folder_listing, + fld, g_free); + + return 0; +} + +static void max_msg_element(GMarkupParseContext *ctxt, const char *element, + const char **names, const char **values, + gpointer user_data, GError **gerr) +{ + struct message_listing_data *mld = user_data; + const char *key; + int i; + + for (i = 0, key = names[i]; key; key = names[++i]) { + if (g_strcmp0(names[i], "handle") == 0) { + mld->size++; + break; + } + } +} + +static void msg_element(GMarkupParseContext *ctxt, const char *element, + const char **names, const char **values, + gpointer user_data, GError **gerr) +{ + struct message_listing_data *mld = user_data; + struct messages_message *entry = NULL; + int i; + + entry = g_new0(struct messages_message, 1); + if (mld->filter->parameter_mask == 0) { + entry->mask = (entry->mask | PMASK_SUBJECT \ + | PMASK_DATETIME | PMASK_RECIPIENT_ADDRESSING \ + | PMASK_SENDER_ADDRESSING \ + | PMASK_ATTACHMENT_SIZE | PMASK_TYPE \ + | PMASK_RECEPTION_STATUS); + } else + entry->mask = mld->filter->parameter_mask; + + for (i = 0 ; names[i]; ++i) { + if (g_strcmp0(names[i], "handle") == 0) { + entry->handle = g_strdup(values[i]); + mld->size++; + continue; + } + if (g_strcmp0(names[i], "attachment_size") == 0) { + entry->attachment_size = g_strdup(values[i]); + continue; + } + if (g_strcmp0(names[i], "datetime") == 0) { + entry->datetime = g_strdup(values[i]); + continue; + } + if (g_strcmp0(names[i], "subject") == 0) { + entry->subject = g_strdup(values[i]); + continue; + } + if (g_strcmp0(names[i], "recipient_addressing") == 0) { + entry->recipient_addressing = g_strdup(values[i]); + continue; + } + if (g_strcmp0(names[i], "sender_addressing") == 0) { + entry->sender_addressing = g_strdup(values[i]); + continue; + } + if (g_strcmp0(names[i], "type") == 0) { + entry->type = g_strdup(values[i]); + continue; + } + if (g_strcmp0(names[i], "reception_status") == 0) + entry->reception_status = g_strdup(values[i]); + } + + if (mld->size > mld->offset) + mld->callback(mld->session, -EAGAIN, mld->size, 0, entry, mld->user_data); + + g_free(entry->reception_status); + g_free(entry->type); + g_free(entry->sender_addressing); + g_free(entry->subject); + g_free(entry->datetime); + g_free(entry->attachment_size); + g_free(entry->handle); + g_free(entry); +} + +static const GMarkupParser msg_parser = { + msg_element, + NULL, + NULL, + NULL, + NULL +}; + +static const GMarkupParser max_msg_parser = { + max_msg_element, + NULL, + NULL, + NULL, + NULL +}; + +static gboolean get_messages_listing(void *d) +{ + + struct message_listing_data *mld = d; + /* 1024 is the maximum size of the line which is calculated to be more + * sufficient*/ + char buffer[1024]; + GMarkupParseContext *ctxt; + size_t len; + + while (fgets(buffer, 1024, mld->fp)) { + len = strlen(buffer); + + if (mld->max == 0) { + ctxt = g_markup_parse_context_new(&max_msg_parser, 0, mld, NULL); + g_markup_parse_context_parse(ctxt, buffer, len, NULL); + g_markup_parse_context_free(ctxt); + } else { + ctxt = g_markup_parse_context_new(&msg_parser, 0, mld, NULL); + g_markup_parse_context_parse(ctxt, buffer, len, NULL); + g_markup_parse_context_free(ctxt); + } + } + + if (mld->max == 0) { + mld->callback(mld->session, 0, mld->size, 0, NULL, mld->user_data); + goto done; + } + + mld->callback(mld->session, 0, mld->size, 0, NULL, mld->user_data); + +done: + fclose(mld->fp); + return FALSE; +} + +int messages_get_messages_listing(void *session, const char *name, + uint16_t max, uint16_t offset, + uint8_t subject_len, + const struct messages_filter *filter, + messages_get_messages_listing_cb callback, + void *user_data) +{ + struct message_listing_data *mld; + struct session *s = session; + char *path; + + mld = g_new0(struct message_listing_data, 1); + mld->session = s; + mld->name = name; + mld->max = max; + mld->offset = offset; + mld->subject_len = subject_len; + mld->callback = callback; + mld->filter = filter; + mld->user_data = user_data; + + path = g_build_filename(s->cwd_absolute, MSG_LIST_XML, NULL); + mld->fp = fopen(path, "r"); + if (mld->fp == NULL) { + g_free(path); + messages_set_folder(s, mld->name, 0); + path = g_build_filename(s->cwd_absolute, MSG_LIST_XML, NULL); + mld->fp = fopen(path, "r"); + if (mld->fp == NULL) { + int err = -errno; + DBG("fopen(): %d, %s", -err, strerror(-err)); + g_free(path); + return -EBADR; + } + } + + + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, get_messages_listing, + mld, g_free); + g_free(path); + + return 0; +} + +int messages_get_message(void *session, const char *handle, + unsigned long flags, + messages_get_message_cb callback, + void *user_data) +{ + return -ENOSYS; +} + +int messages_update_inbox(void *session, messages_status_cb callback, + void *user_data) +{ + return -ENOSYS; +} + +int messages_set_read(void *session, const char *handle, uint8_t value, + messages_status_cb callback, void *user_data) +{ + return -ENOSYS; +} + +int messages_set_delete(void *session, const char *handle, uint8_t value, + messages_status_cb callback, void *user_data) +{ + return -ENOSYS; +} + +void messages_abort(void *s) +{ + struct session *session = s; + + if (session->request) { + g_idle_remove_by_data(session->request); + session->request = NULL; + } +} diff --git a/obexd/plugins/messages.h b/obexd/plugins/messages.h new file mode 100644 index 0000000..00a16b1 --- /dev/null +++ b/obexd/plugins/messages.h @@ -0,0 +1,309 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2010-2011 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +/* Those are used by backend to notify transport plugin which properties did it + * send. + */ +#define PMASK_SUBJECT 0x0001 +#define PMASK_DATETIME 0x0002 +#define PMASK_SENDER_NAME 0x0004 +#define PMASK_SENDER_ADDRESSING 0x0008 +#define PMASK_RECIPIENT_NAME 0x0010 +#define PMASK_RECIPIENT_ADDRESSING 0x0020 +#define PMASK_TYPE 0x0040 +#define PMASK_SIZE 0x0080 +#define PMASK_RECEPTION_STATUS 0x0100 +#define PMASK_TEXT 0x0200 +#define PMASK_ATTACHMENT_SIZE 0x0400 +#define PMASK_PRIORITY 0x0800 +#define PMASK_READ 0x1000 +#define PMASK_SENT 0x2000 +#define PMASK_PROTECTED 0x4000 +#define PMASK_REPLYTO_ADDRESSING 0x8000 + +/* This one is used in a response to GetMessagesListing. Use PMASK_* values to + * notify the plugin which members are actually set. Backend shall not omit + * properties required by MAP specification (subject, datetime, + * recipient_addressing, type, size, reception_status, attachment_size) unless + * ordered by PARAMETERMASK. Boolean values should be probably + * always sent (need checking). Handle is mandatory. Plugin will filter out any + * properties that were not wanted by MCE. + * + * Handle shall be set to hexadecimal representation with upper-case letters. No + * prefix shall be appended and without no zeros. This corresponds to PTS + * behaviour described in comments to the MAP specification. + * + * The rest of char * fields shall be set according to the MAP specification + * rules. + */ +struct messages_message { + uint32_t mask; + char *handle; + char *subject; + char *datetime; + char *sender_name; + char *sender_addressing; + char *replyto_addressing; + char *recipient_name; + char *recipient_addressing; + char *type; + char *reception_status; + char *size; + char *attachment_size; + gboolean text; + gboolean read; + gboolean sent; + gboolean protect; + gboolean priority; +}; + +/* Type of message event to be delivered to MNS server */ +enum messages_event_type { + MET_NEW_MESSAGE, + MET_DELIVERY_SUCCESS, + MET_SENDING_SUCCESS, + MET_DELIVERY_FAILURE, + MET_SENDING_FAILURE, + MET_MEMORY_FULL, + MET_MEMORY_AVAILABLE, + MET_MESSAGE_DELETED, + MET_MESSAGE_SHIFT +}; + +/* Data for sending MNS notification. Handle shall be formatted as described in + * messages_message. + */ +struct messages_event { + enum messages_event_type type; + uint8_t instance_id; + char *handle; + char *folder; + char *old_folder; + char *msg_type; +}; + +/* parameter_mask: |-ed PMASK_* values + * See MAP specification for the rest. + */ +struct messages_filter { + uint32_t parameter_mask; + uint8_t type; + const char *period_begin; + const char *period_end; + uint8_t read_status; + const char *recipient; + const char *originator; + uint8_t priority; +}; + +/* This is called once after server starts. + * + * Returns value less than zero if error. This will prevent MAP plugin from + * starting. + */ +int messages_init(void); + +/* This gets called right before server finishes + */ +void messages_exit(void); + +/* Starts a new MAP session. + * + * session: variable to store pointer to backend session data. This one shall be + * passed to all in-session calls. + * + * If session start succeeded, backend shall return 0. Otherwise the error value + * will be sent as a response to OBEX connect. + */ +int messages_connect(void **session); + +/* Closes a MAP session. + * + * This call should free buffer reserved by messages_connect. + */ +void messages_disconnect(void *session); + +/****************************************************************************** + * NOTE on callbacks. + * + * All functions requiring callbacks have to call them asynchronously. + * 'user_data' is for passing arbitrary user data. + * + * Functions for GetMessagesListing, GetFolder listing and GetMessage call their + * callbacks multiple times - one for each listing entry or message body chunk. + * To indicate the end of operation backend must call callback with the data + * pointer parameter set to NULL. + * + * If err == -EAGAIN the transport * plugin does not wake IO. + * + * Keep in mind that application parameters has to be send first. Therefore the + * first time err == 0 and thus sending is started, callback will use provided + * parameters (e.g. size in case of folder listing) to build applications + * parameters header used in response. In any other case those parameters will + * be ignored. + * + * If err != 0 && err != -EAGAIN, the operation is finished immediately and err + * value is used to set the error code in OBEX response. + ******************************************************************************/ + +/* Registers for messaging events notifications. + * + * session: Backend session. + * send_event: Function that will be called to indicate a new event. + * + * To unregister currently registered notifications, call this with send_event + * set to NULL. + */ +int messages_set_notification_registration(void *session, + void (*send_event)(void *session, + const struct messages_event *event, void *user_data), + void *user_data); + +/* Changes current directory. + * + * session: Backend session. + * name: Subdirectory to go to. If empty or null and cdup is false, go to the + * root directory. + * cdup: If true, go up one level first. + */ +int messages_set_folder(void *session, const char *name, gboolean cdup); + +/* Retrieves subdirectories listing from a current directory. + * + * session: Backend session. + * name: Optional subdirectory name (not strictly required by MAP). + * max: Maximum number of entries to retrieve. + * offset: Offset of the first entry. + * size: Total size of listing to be returned. + * + * Callback shall be called for every entry of the listing. 'name' is the + * subdirectory name. + */ +typedef void (*messages_folder_listing_cb)(void *session, int err, + uint16_t size, const char *name, void *user_data); + +int messages_get_folder_listing(void *session, const char *name, uint16_t max, + uint16_t offset, + messages_folder_listing_cb callback, + void *user_data); + +/* Retrieves messages listing from a current directory. + * + * session: Backend session. + * name: Optional subdirectory name. + * max: Maximum number of entries to retrieve. + * offset: Offset of the first entry. + * subject_len: Maximum string length of the "subject" parameter in the entries. + * filter: Filter to apply on returned message listing. + * size: Total size of listing to be returned. + * newmsg: Indicates presence of unread messages. + * + * Callback shall be called for every entry of the listing, giving message data + * in 'message'. + */ +typedef void (*messages_get_messages_listing_cb)(void *session, int err, + uint16_t size, gboolean newmsg, + const struct messages_message *message, + void *user_data); + +int messages_get_messages_listing(void *session, const char *name, + uint16_t max, uint16_t offset, + uint8_t subject_len, + const struct messages_filter *filter, + messages_get_messages_listing_cb callback, + void *user_data); + +#define MESSAGES_ATTACHMENT (1 << 0) +#define MESSAGES_UTF8 (1 << 1) +#define MESSAGES_FRACTION (1 << 2) +#define MESSAGES_NEXT (1 << 3) + +/* Retrieves bMessage object (see MAP specification, ch. 3.1.3) of a given + * message. + * + * session: Backend session. + * handle: Handle of the message to retrieve. + * flags: or-ed mask of following: + * MESSAGES_ATTACHMENT: Selects whether or not attachments (if any) are to + * be included. + * MESSAGES_UTF8: If true, convert message to utf-8. Otherwise use native + * encoding. + * MESSAGES_FRACTION: If true, deliver fractioned message. + * MESSAGES_NEXT: If fraction is true this indicates whether to retrieve + * first fraction + * or the next one. + * fmore: Indicates whether next fraction is available. + * chunk: chunk of bMessage body + * + * Callback allows for returning bMessage in chunks. + */ +typedef void (*messages_get_message_cb)(void *session, int err, gboolean fmore, + const char *chunk, void *user_data); + +int messages_get_message(void *session, const char *handle, + unsigned long flags, + messages_get_message_cb callback, + void *user_data); + +typedef void (*messages_status_cb)(void *session, int err, void *user_data); + +/* Informs Message Server to Update Inbox via network. + * + * session: Backend session. + * user_data: User data if any to be sent. + * Callback shall be called for every update inbox request received from MCE. + */ +int messages_update_inbox(void *session, messages_status_cb callback, + void *user_data); +/* Informs Message Server to modify read status of a given message. + * + * session: Backend session. + * handle: Unique identifier to the message. + * value: Indicates the new value of the read status for a given message. + * Callback shall be called for every read status update request + * recieved from MCE. + * user_data: User data if any to be sent. + */ +int messages_set_read(void *session, const char *handle, uint8_t value, + messages_status_cb callback, void *user_data); + +/* Informs Message Server to modify delete status of a given message. + * + * session: Backend session. + * handle: Unique identifier to the message. + * value: Indicates the new value of the delete status for a given message. + * Callback shall be called for every delete status update request + * recieved from MCE. + * user_data: User data if any to be sent. + */ +int messages_set_delete(void *session, const char *handle, uint8_t value, + messages_status_cb callback, void *user_data); + +/* Aborts currently pending request. + * + * session: Backend session. + */ +void messages_abort(void *session); diff --git a/obexd/plugins/opp.c b/obexd/plugins/opp.c new file mode 100644 index 0000000..5bb7667 --- /dev/null +++ b/obexd/plugins/opp.c @@ -0,0 +1,193 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Nokia Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include + +#include "obexd/src/obexd.h" +#include "obexd/src/plugin.h" +#include "obexd/src/obex.h" +#include "obexd/src/service.h" +#include "obexd/src/log.h" +#include "obexd/src/manager.h" +#include "filesystem.h" + +#define VCARD_TYPE "text/x-vcard" + +static void *opp_connect(struct obex_session *os, int *err) +{ + manager_register_session(os); + + if (err) + *err = 0; + + return manager_register_transfer(os); +} + +static void opp_progress(struct obex_session *os, void *user_data) +{ + manager_emit_transfer_progress(user_data); +} + +static int opp_chkput(struct obex_session *os, void *user_data) +{ + char *folder, *name, *path; + const char *t; + int err; + + if (obex_get_size(os) == OBJECT_SIZE_DELETE) + return -ENOSYS; + + t = obex_get_name(os); + if (t != NULL && !is_filename(t)) + return -EBADR; + + if (obex_option_auto_accept()) { + folder = g_strdup(obex_option_root_folder()); + name = g_strdup(obex_get_name(os)); + goto skip_auth; + } + + err = manager_request_authorization(user_data, &folder, &name); + if (err < 0) + return -EPERM; + + if (folder == NULL) + folder = g_strdup(obex_option_root_folder()); + + if (name == NULL) + name = g_strdup(obex_get_name(os)); + +skip_auth: + if (name == NULL || strlen(name) == 0) { + err = -EBADR; + goto failed; + } + + if (g_strcmp0(name, obex_get_name(os)) != 0) + obex_set_name(os, name); + + path = g_build_filename(folder, name, NULL); + + err = obex_put_stream_start(os, path); + + g_free(path); + + if (err < 0) + goto failed; + + manager_emit_transfer_started(user_data); + +failed: + g_free(folder); + g_free(name); + + return err; +} + +static int opp_put(struct obex_session *os, void *user_data) +{ + const char *name = obex_get_name(os); + const char *folder = obex_option_root_folder(); + + if (folder == NULL) + return -EPERM; + + if (name == NULL) + return -EBADR; + + return 0; +} + +static int opp_get(struct obex_session *os, void *user_data) +{ + const char *type; + char *folder, *path; + int err = 0; + + if (obex_get_name(os)) + return -EPERM; + + type = obex_get_type(os); + + if (type == NULL) + return -EPERM; + + folder = g_strdup(obex_option_root_folder()); + path = g_build_filename(folder, "/vcard.vcf", NULL); + + if (g_ascii_strcasecmp(type, VCARD_TYPE) == 0) { + if (obex_get_stream_start(os, path) < 0) + err = -ENOENT; + + } else + err = -EPERM; + + g_free(folder); + g_free(path); + return err; +} + +static void opp_disconnect(struct obex_session *os, void *user_data) +{ + manager_unregister_transfer(user_data); + manager_unregister_session(os); +} + +static void opp_reset(struct obex_session *os, void *user_data) +{ + manager_emit_transfer_completed(user_data); +} + +static struct obex_service_driver driver = { + .name = "Object Push server", + .service = OBEX_OPP, + .connect = opp_connect, + .progress = opp_progress, + .disconnect = opp_disconnect, + .get = opp_get, + .put = opp_put, + .chkput = opp_chkput, + .reset = opp_reset +}; + +static int opp_init(void) +{ + return obex_service_driver_register(&driver); +} + +static void opp_exit(void) +{ + obex_service_driver_unregister(&driver); +} + +OBEX_PLUGIN_DEFINE(opp, opp_init, opp_exit) diff --git a/obexd/plugins/pbap.c b/obexd/plugins/pbap.c new file mode 100644 index 0000000..d5a3046 --- /dev/null +++ b/obexd/plugins/pbap.c @@ -0,0 +1,1006 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2009-2010 Intel Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gobex/gobex.h" +#include "gobex/gobex-apparam.h" + +#include "obexd/src/obexd.h" +#include "obexd/src/plugin.h" +#include "obexd/src/log.h" +#include "obexd/src/obex.h" +#include "obexd/src/service.h" +#include "obexd/src/manager.h" +#include "obexd/src/mimetype.h" +#include "phonebook.h" +#include "filesystem.h" + +#define PHONEBOOK_TYPE "x-bt/phonebook" +#define VCARDLISTING_TYPE "x-bt/vcard-listing" +#define VCARDENTRY_TYPE "x-bt/vcard" + +#define ORDER_TAG 0x01 +#define SEARCHVALUE_TAG 0x02 +#define SEARCHATTRIB_TAG 0x03 +#define MAXLISTCOUNT_TAG 0x04 +#define LISTSTARTOFFSET_TAG 0x05 +#define FILTER_TAG 0x06 +#define FORMAT_TAG 0X07 +#define PHONEBOOKSIZE_TAG 0X08 +#define NEWMISSEDCALLS_TAG 0X09 + +struct cache { + gboolean valid; + uint32_t index; + GSList *entries; +}; + +struct cache_entry { + uint32_t handle; + char *id; + char *name; + char *sound; + char *tel; +}; + +struct pbap_session { + struct apparam_field *params; + char *folder; + uint32_t find_handle; + struct cache cache; + struct pbap_object *obj; +}; + +struct pbap_object { + GString *buffer; + GObexApparam *apparam; + gboolean firstpacket; + gboolean lastpart; + struct pbap_session *session; + void *request; +}; + +static const uint8_t PBAP_TARGET[TARGET_SIZE] = { + 0x79, 0x61, 0x35, 0xF0, 0xF0, 0xC5, 0x11, 0xD8, + 0x09, 0x66, 0x08, 0x00, 0x20, 0x0C, 0x9A, 0x66 }; + +typedef int (*cache_entry_find_f) (const struct cache_entry *entry, + const char *value); + +static void cache_entry_free(void *data) +{ + struct cache_entry *entry = data; + + g_free(entry->id); + g_free(entry->name); + g_free(entry->sound); + g_free(entry->tel); + g_free(entry); +} + +static gboolean entry_name_find(const struct cache_entry *entry, + const char *value) +{ + char *name; + gboolean ret; + + if (!entry->name) + return FALSE; + + if (strlen(value) == 0) + return TRUE; + + name = g_utf8_strdown(entry->name, -1); + ret = (g_strstr_len(name, -1, value) ? TRUE : FALSE); + g_free(name); + + return ret; +} + +static gboolean entry_sound_find(const struct cache_entry *entry, + const char *value) +{ + if (!entry->sound) + return FALSE; + + return (g_strstr_len(entry->sound, -1, value) ? TRUE : FALSE); +} + +static gboolean entry_tel_find(const struct cache_entry *entry, + const char *value) +{ + if (!entry->tel) + return FALSE; + + return (g_strstr_len(entry->tel, -1, value) ? TRUE : FALSE); +} + +static const char *cache_find(struct cache *cache, uint32_t handle) +{ + GSList *l; + + for (l = cache->entries; l; l = l->next) { + struct cache_entry *entry = l->data; + + if (entry->handle == handle) + return entry->id; + } + + return NULL; +} + +static void cache_clear(struct cache *cache) +{ + g_slist_free_full(cache->entries, cache_entry_free); + cache->entries = NULL; +} + +static void phonebook_size_result(const char *buffer, size_t bufsize, + int vcards, int missed, + gboolean lastpart, void *user_data) +{ + struct pbap_session *pbap = user_data; + uint16_t phonebooksize; + + if (pbap->obj->request) { + phonebook_req_finalize(pbap->obj->request); + pbap->obj->request = NULL; + } + + if (vcards < 0) + vcards = 0; + + DBG("vcards %d", vcards); + + phonebooksize = vcards; + + pbap->obj->apparam = g_obex_apparam_set_uint16(NULL, PHONEBOOKSIZE_TAG, + phonebooksize); + + pbap->obj->firstpacket = TRUE; + + if (missed > 0) { + DBG("missed %d", missed); + + pbap->obj->apparam = g_obex_apparam_set_uint16( + pbap->obj->apparam, + NEWMISSEDCALLS_TAG, + missed); + } + + obex_object_set_io_flags(pbap->obj, G_IO_IN, 0); +} + +static void query_result(const char *buffer, size_t bufsize, int vcards, + int missed, gboolean lastpart, void *user_data) +{ + struct pbap_session *pbap = user_data; + + DBG(""); + + if (pbap->obj->request && lastpart) { + phonebook_req_finalize(pbap->obj->request); + pbap->obj->request = NULL; + } + + pbap->obj->lastpart = lastpart; + + if (vcards < 0) { + obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT); + return; + } + + if (!pbap->obj->buffer) + pbap->obj->buffer = g_string_new_len(buffer, bufsize); + else + pbap->obj->buffer = g_string_append_len(pbap->obj->buffer, + buffer, bufsize); + + if (missed > 0) { + DBG("missed %d", missed); + + pbap->obj->firstpacket = TRUE; + + pbap->obj->apparam = g_obex_apparam_set_uint16( + pbap->obj->apparam, + NEWMISSEDCALLS_TAG, + missed); + } + + obex_object_set_io_flags(pbap->obj, G_IO_IN, 0); +} + +static void cache_entry_notify(const char *id, uint32_t handle, + const char *name, const char *sound, + const char *tel, void *user_data) +{ + struct pbap_session *pbap = user_data; + struct cache_entry *entry = g_new0(struct cache_entry, 1); + struct cache *cache = &pbap->cache; + + if (handle != PHONEBOOK_INVALID_HANDLE) + entry->handle = handle; + else + entry->handle = ++pbap->cache.index; + + entry->id = g_strdup(id); + entry->name = g_strdup(name); + entry->sound = g_strdup(sound); + entry->tel = g_strdup(tel); + + cache->entries = g_slist_append(cache->entries, entry); +} + +static int alpha_sort(gconstpointer a, gconstpointer b) +{ + const struct cache_entry *e1 = a; + const struct cache_entry *e2 = b; + + return g_strcmp0(e1->name, e2->name); +} + +static int indexed_sort(gconstpointer a, gconstpointer b) +{ + const struct cache_entry *e1 = a; + const struct cache_entry *e2 = b; + + return (e1->handle - e2->handle); +} + +static int phonetical_sort(gconstpointer a, gconstpointer b) +{ + const struct cache_entry *e1 = a; + const struct cache_entry *e2 = b; + + /* SOUND attribute is optional. Use Indexed sort if not present. */ + if (!e1->sound || !e2->sound) + return indexed_sort(a, b); + + return g_strcmp0(e1->sound, e2->sound); +} + +static GSList *sort_entries(GSList *l, uint8_t order, uint8_t search_attrib, + const char *value) +{ + GSList *sorted = NULL; + cache_entry_find_f find; + GCompareFunc sort; + char *searchval; + + /* + * Default sorter is "Indexed". Some backends doesn't inform the index, + * for this case a sequential internal index is assigned. + * 0x00 = indexed + * 0x01 = alphanumeric + * 0x02 = phonetic + */ + switch (order) { + case 0x01: + sort = alpha_sort; + break; + case 0x02: + sort = phonetical_sort; + break; + default: + sort = indexed_sort; + break; + } + + /* + * This implementation checks if the given field CONTAINS the + * search value(case insensitive). Name is the default field + * when the attribute is not provided. + */ + switch (search_attrib) { + /* Number */ + case 1: + find = entry_tel_find; + break; + /* Sound */ + case 2: + find = entry_sound_find; + break; + default: + find = entry_name_find; + break; + } + + searchval = value ? g_utf8_strdown(value, -1) : NULL; + for (; l; l = l->next) { + struct cache_entry *entry = l->data; + + if (searchval && !find(entry, (const char *) searchval)) + continue; + + sorted = g_slist_insert_sorted(sorted, entry, sort); + } + + g_free(searchval); + + return sorted; +} + +static int generate_response(void *user_data) +{ + struct pbap_session *pbap = user_data; + GSList *sorted; + GSList *l; + uint16_t max = pbap->params->maxlistcount; + + DBG(""); + + if (max == 0) { + /* Ignore all other parameter and return PhoneBookSize */ + uint16_t size = g_slist_length(pbap->cache.entries); + + pbap->obj->firstpacket = TRUE; + pbap->obj->apparam = g_obex_apparam_set_uint16( + pbap->obj->apparam, + PHONEBOOKSIZE_TAG, + size); + + return 0; + } + + /* + * Don't free the sorted list content: this list contains + * only the reference for the "real" cache entry. + */ + sorted = sort_entries(pbap->cache.entries, pbap->params->order, + pbap->params->searchattrib, + (const char *) pbap->params->searchval); + + /* Computing offset considering first entry of the phonebook */ + l = g_slist_nth(sorted, pbap->params->liststartoffset); + + pbap->obj->buffer = g_string_new(VCARD_LISTING_BEGIN); + for (; l && max; l = l->next, max--) { + const struct cache_entry *entry = l->data; + char *escaped_name = g_markup_escape_text(entry->name, -1); + + g_string_append_printf(pbap->obj->buffer, + VCARD_LISTING_ELEMENT, entry->handle, escaped_name); + + g_free(escaped_name); + } + + pbap->obj->buffer = g_string_append(pbap->obj->buffer, + VCARD_LISTING_END); + g_slist_free(sorted); + + return 0; +} + +static void cache_ready_notify(void *user_data) +{ + struct pbap_session *pbap = user_data; + + DBG(""); + + phonebook_req_finalize(pbap->obj->request); + pbap->obj->request = NULL; + + pbap->cache.valid = TRUE; + + generate_response(pbap); + obex_object_set_io_flags(pbap->obj, G_IO_IN, 0); +} + +static void cache_entry_done(void *user_data) +{ + struct pbap_session *pbap = user_data; + const char *id; + int ret; + + DBG(""); + + pbap->cache.valid = TRUE; + + id = cache_find(&pbap->cache, pbap->find_handle); + if (id == NULL) { + DBG("Entry %d not found on cache", pbap->find_handle); + obex_object_set_io_flags(pbap->obj, G_IO_ERR, -ENOENT); + return; + } + + phonebook_req_finalize(pbap->obj->request); + pbap->obj->request = phonebook_get_entry(pbap->folder, id, + pbap->params, query_result, pbap, &ret); + if (ret < 0) + obex_object_set_io_flags(pbap->obj, G_IO_ERR, ret); +} + +static struct apparam_field *parse_aparam(const uint8_t *buffer, uint32_t hlen) +{ + GObexApparam *apparam; + struct apparam_field *param; + + apparam = g_obex_apparam_decode(buffer, hlen); + if (apparam == NULL) + return NULL; + + param = g_new0(struct apparam_field, 1); + + /* + * As per spec when client doesn't include MAXLISTCOUNT_TAG then it + * should be assume as Maximum value in vcardlisting 65535 + */ + param->maxlistcount = UINT16_MAX; + + g_obex_apparam_get_uint8(apparam, ORDER_TAG, ¶m->order); + g_obex_apparam_get_uint8(apparam, SEARCHATTRIB_TAG, + ¶m->searchattrib); + g_obex_apparam_get_uint8(apparam, FORMAT_TAG, ¶m->format); + g_obex_apparam_get_uint16(apparam, MAXLISTCOUNT_TAG, + ¶m->maxlistcount); + g_obex_apparam_get_uint16(apparam, LISTSTARTOFFSET_TAG, + ¶m->liststartoffset); + g_obex_apparam_get_uint64(apparam, FILTER_TAG, ¶m->filter); + param->searchval = g_obex_apparam_get_string(apparam, SEARCHVALUE_TAG); + + DBG("o %x sa %x sv %s fil %" G_GINT64_MODIFIER "x for %x max %x off %x", + param->order, param->searchattrib, param->searchval, + param->filter, param->format, param->maxlistcount, + param->liststartoffset); + + g_obex_apparam_free(apparam); + + return param; +} + +static void *pbap_connect(struct obex_session *os, int *err) +{ + struct pbap_session *pbap; + + manager_register_session(os); + + pbap = g_new0(struct pbap_session, 1); + pbap->folder = g_strdup("/"); + pbap->find_handle = PHONEBOOK_INVALID_HANDLE; + + if (err) + *err = 0; + + return pbap; +} + +static int pbap_get(struct obex_session *os, void *user_data) +{ + struct pbap_session *pbap = user_data; + const char *type = obex_get_type(os); + const char *name = obex_get_name(os); + struct apparam_field *params; + const uint8_t *buffer; + char *path; + ssize_t rsize; + int ret; + + DBG("name %s type %s pbap %p", name, type, pbap); + + if (type == NULL) + return -EBADR; + + rsize = obex_get_apparam(os, &buffer); + if (rsize < 0) { + if (g_ascii_strcasecmp(type, VCARDENTRY_TYPE) != 0) + return -EBADR; + + rsize = 0; + } + + params = parse_aparam(buffer, rsize); + if (params == NULL) + return -EBADR; + + if (pbap->params) { + g_free(pbap->params->searchval); + g_free(pbap->params); + } + + pbap->params = params; + + if (g_ascii_strcasecmp(type, PHONEBOOK_TYPE) == 0) { + /* Always contains the absolute path */ + if (g_path_is_absolute(name)) + path = g_strdup(name); + else + path = g_build_filename("/", name, NULL); + + } else if (g_ascii_strcasecmp(type, VCARDLISTING_TYPE) == 0) { + /* Always relative */ + if (!name || strlen(name) == 0) { + /* Current folder */ + path = g_strdup(pbap->folder); + } else { + /* Current folder + relative path */ + path = g_build_filename(pbap->folder, name, NULL); + + /* clear cache */ + pbap->cache.valid = FALSE; + pbap->cache.index = 0; + cache_clear(&pbap->cache); + } + } else if (g_ascii_strcasecmp(type, VCARDENTRY_TYPE) == 0) { + /* File name only */ + path = g_strdup(name); + } else + return -EBADR; + + if (path == NULL) + return -EBADR; + + ret = obex_get_stream_start(os, path); + + g_free(path); + + return ret; +} + +static int pbap_setpath(struct obex_session *os, void *user_data) +{ + struct pbap_session *pbap = user_data; + const char *name; + const uint8_t *nonhdr; + char *fullname; + int err; + + if (obex_get_non_header_data(os, &nonhdr) != 2) { + error("Set path failed: flag and constants not found!"); + return -EBADMSG; + } + + name = obex_get_name(os); + + DBG("name %s folder %s nonhdr 0x%x%x", name, pbap->folder, + nonhdr[0], nonhdr[1]); + + fullname = phonebook_set_folder(pbap->folder, name, nonhdr[0], &err); + if (err < 0) + return err; + + g_free(pbap->folder); + pbap->folder = fullname; + + /* + * FIXME: Define a criteria to mark the cache as invalid + */ + pbap->cache.valid = FALSE; + pbap->cache.index = 0; + cache_clear(&pbap->cache); + + return 0; +} + +static void pbap_disconnect(struct obex_session *os, void *user_data) +{ + struct pbap_session *pbap = user_data; + + manager_unregister_session(os); + + if (pbap->obj) + pbap->obj->session = NULL; + + if (pbap->params) { + g_free(pbap->params->searchval); + g_free(pbap->params); + } + + cache_clear(&pbap->cache); + g_free(pbap->folder); + g_free(pbap); +} + +static int pbap_chkput(struct obex_session *os, void *user_data) +{ + /* Rejects all PUTs */ + return -EBADR; +} + +static struct obex_service_driver pbap = { + .name = "Phonebook Access server", + .service = OBEX_PBAP, + .target = PBAP_TARGET, + .target_size = TARGET_SIZE, + .connect = pbap_connect, + .get = pbap_get, + .setpath = pbap_setpath, + .disconnect = pbap_disconnect, + .chkput = pbap_chkput +}; + +static struct pbap_object *vobject_create(struct pbap_session *pbap, + void *request) +{ + struct pbap_object *obj; + + obj = g_new0(struct pbap_object, 1); + obj->session = pbap; + pbap->obj = obj; + obj->request = request; + + return obj; +} + +static void *vobject_pull_open(const char *name, int oflag, mode_t mode, + void *context, size_t *size, int *err) +{ + struct pbap_session *pbap = context; + phonebook_cb cb; + int ret; + void *request; + + DBG("name %s context %p maxlistcount %d", name, context, + pbap->params->maxlistcount); + + if (oflag != O_RDONLY) { + ret = -EPERM; + goto fail; + } + + if (name == NULL) { + ret = -EBADR; + goto fail; + } + + if (pbap->params->maxlistcount == 0) + cb = phonebook_size_result; + else + cb = query_result; + + request = phonebook_pull(name, pbap->params, cb, pbap, &ret); + + if (ret < 0) + goto fail; + + /* reading first part of results from backend */ + ret = phonebook_pull_read(request); + if (ret < 0) + goto fail; + + if (err) + *err = 0; + + return vobject_create(pbap, request); + +fail: + if (err) + *err = ret; + + return NULL; +} + +static int vobject_close(void *object) +{ + struct pbap_object *obj = object; + + DBG(""); + + if (obj->session) + obj->session->obj = NULL; + + if (obj->buffer) + g_string_free(obj->buffer, TRUE); + + if (obj->apparam) + g_obex_apparam_free(obj->apparam); + + if (obj->request) + phonebook_req_finalize(obj->request); + + g_free(obj); + + return 0; +} + +static void *vobject_list_open(const char *name, int oflag, mode_t mode, + void *context, size_t *size, int *err) +{ + struct pbap_session *pbap = context; + struct pbap_object *obj = NULL; + int ret; + void *request; + + if (name == NULL) { + ret = -EBADR; + goto fail; + } + + DBG("name %s context %p valid %d", name, context, pbap->cache.valid); + + if (oflag != O_RDONLY) { + ret = -EPERM; + goto fail; + } + + /* PullvCardListing always get the contacts from the cache */ + + if (pbap->cache.valid) { + obj = vobject_create(pbap, NULL); + ret = generate_response(pbap); + } else { + request = phonebook_create_cache(name, cache_entry_notify, + cache_ready_notify, pbap, &ret); + if (ret == 0) + obj = vobject_create(pbap, request); + } + if (ret < 0) + goto fail; + + if (err) + *err = 0; + + return obj; + +fail: + if (obj) + vobject_close(obj); + + if (err) + *err = ret; + + return NULL; +} + +static void *vobject_vcard_open(const char *name, int oflag, mode_t mode, + void *context, size_t *size, int *err) +{ + struct pbap_session *pbap = context; + const char *id; + uint32_t handle; + int ret; + void *request; + + DBG("name %s context %p valid %d", name, context, pbap->cache.valid); + + if (oflag != O_RDONLY) { + ret = -EPERM; + goto fail; + } + + if (name == NULL || sscanf(name, "%u.vcf", &handle) != 1) { + ret = -EBADR; + goto fail; + } + + if (pbap->cache.valid == FALSE) { + pbap->find_handle = handle; + request = phonebook_create_cache(pbap->folder, + cache_entry_notify, cache_entry_done, pbap, &ret); + goto done; + } + + id = cache_find(&pbap->cache, handle); + if (!id) { + ret = -ENOENT; + goto fail; + } + + request = phonebook_get_entry(pbap->folder, id, pbap->params, + query_result, pbap, &ret); + +done: + if (ret < 0) + goto fail; + + if (err) + *err = 0; + + return vobject_create(pbap, request); + +fail: + if (err) + *err = ret; + + return NULL; +} + +static ssize_t vobject_pull_get_next_header(void *object, void *buf, size_t mtu, + uint8_t *hi) +{ + struct pbap_object *obj = object; + + if (!obj->buffer && !obj->apparam) + return -EAGAIN; + + *hi = G_OBEX_HDR_APPARAM; + + if (obj->firstpacket) { + obj->firstpacket = FALSE; + + return g_obex_apparam_encode(obj->apparam, buf, mtu); + } + + return 0; +} + +static ssize_t vobject_pull_read(void *object, void *buf, size_t count) +{ + struct pbap_object *obj = object; + struct pbap_session *pbap = obj->session; + int len, ret; + + DBG("buffer %p maxlistcount %d", obj->buffer, + pbap->params->maxlistcount); + + if (!obj->buffer) { + if (pbap->params->maxlistcount == 0) + return -ENOSTR; + + return -EAGAIN; + } + + len = string_read(obj->buffer, buf, count); + if (len == 0 && !obj->lastpart) { + /* in case when buffer is empty and we know that more + * data is still available in backend, requesting new + * data part via phonebook_pull_read and returning + * -EAGAIN to suspend request for now */ + ret = phonebook_pull_read(obj->request); + if (ret) + return -EPERM; + + return -EAGAIN; + } + + return len; +} + +static ssize_t vobject_list_get_next_header(void *object, void *buf, size_t mtu, + uint8_t *hi) +{ + struct pbap_object *obj = object; + struct pbap_session *pbap = obj->session; + + /* Backend still busy reading contacts */ + if (!pbap->cache.valid) + return -EAGAIN; + + *hi = G_OBEX_HDR_APPARAM; + + if (obj->firstpacket) { + obj->firstpacket = FALSE; + return g_obex_apparam_encode(obj->apparam, buf, mtu); + } + + return 0; +} + +static ssize_t vobject_list_read(void *object, void *buf, size_t count) +{ + struct pbap_object *obj = object; + struct pbap_session *pbap = obj->session; + + DBG("valid %d maxlistcount %d", pbap->cache.valid, + pbap->params->maxlistcount); + + if (pbap->params->maxlistcount == 0) + return -ENOSTR; + + return string_read(obj->buffer, buf, count); +} + +static ssize_t vobject_vcard_read(void *object, void *buf, size_t count) +{ + struct pbap_object *obj = object; + + DBG("buffer %p", obj->buffer); + + if (!obj->buffer) + return -EAGAIN; + + return string_read(obj->buffer, buf, count); +} + +static struct obex_mime_type_driver mime_pull = { + .target = PBAP_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "x-bt/phonebook", + .open = vobject_pull_open, + .close = vobject_close, + .read = vobject_pull_read, + .get_next_header = vobject_pull_get_next_header, +}; + +static struct obex_mime_type_driver mime_list = { + .target = PBAP_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "x-bt/vcard-listing", + .open = vobject_list_open, + .close = vobject_close, + .read = vobject_list_read, + .get_next_header = vobject_list_get_next_header, +}; + +static struct obex_mime_type_driver mime_vcard = { + .target = PBAP_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "x-bt/vcard", + .open = vobject_vcard_open, + .close = vobject_close, + .read = vobject_vcard_read, +}; + +static int pbap_init(void) +{ + int err; + + err = phonebook_init(); + if (err < 0) + return err; + + err = obex_mime_type_driver_register(&mime_pull); + if (err < 0) + goto fail_mime_pull; + + err = obex_mime_type_driver_register(&mime_list); + if (err < 0) + goto fail_mime_list; + + err = obex_mime_type_driver_register(&mime_vcard); + if (err < 0) + goto fail_mime_vcard; + + err = obex_service_driver_register(&pbap); + if (err < 0) + goto fail_pbap_reg; + + return 0; + +fail_pbap_reg: + obex_mime_type_driver_unregister(&mime_vcard); +fail_mime_vcard: + obex_mime_type_driver_unregister(&mime_list); +fail_mime_list: + obex_mime_type_driver_unregister(&mime_pull); +fail_mime_pull: + phonebook_exit(); + + return err; +} + +static void pbap_exit(void) +{ + obex_service_driver_unregister(&pbap); + obex_mime_type_driver_unregister(&mime_pull); + obex_mime_type_driver_unregister(&mime_list); + obex_mime_type_driver_unregister(&mime_vcard); + phonebook_exit(); +} + +OBEX_PLUGIN_DEFINE(pbap, pbap_init, pbap_exit) diff --git a/obexd/plugins/pcsuite.c b/obexd/plugins/pcsuite.c new file mode 100644 index 0000000..43ab409 --- /dev/null +++ b/obexd/plugins/pcsuite.c @@ -0,0 +1,513 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Nokia Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" + +#include "obexd/src/obexd.h" +#include "obexd/src/plugin.h" +#include "obexd/src/log.h" +#include "obexd/src/obex.h" +#include "obexd/src/mimetype.h" +#include "obexd/src/service.h" +#include "ftp.h" + +#define PCSUITE_CHANNEL 24 +#define PCSUITE_WHO_SIZE 8 + +#define PCSUITE_RECORD " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +" + +#define BACKUP_BUS_NAME "com.nokia.backup.plugin" +#define BACKUP_PATH "/com/nokia/backup" +#define BACKUP_PLUGIN_INTERFACE "com.nokia.backup.plugin" +#define BACKUP_DBUS_TIMEOUT (1000 * 60 * 15) + +static const uint8_t FTP_TARGET[TARGET_SIZE] = { + 0xF9, 0xEC, 0x7B, 0xC4, 0x95, 0x3C, 0x11, 0xD2, + 0x98, 0x4E, 0x52, 0x54, 0x00, 0xDC, 0x9E, 0x09 }; + +static const uint8_t PCSUITE_WHO[PCSUITE_WHO_SIZE] = { + 'P', 'C', ' ', 'S', 'u', 'i', 't', 'e' }; + +struct pcsuite_session { + struct ftp_session *ftp; + char *lock_file; + int fd; +}; + +static void *pcsuite_connect(struct obex_session *os, int *err) +{ + struct pcsuite_session *pcsuite; + struct ftp_session *ftp; + int fd; + char *filename; + + DBG(""); + + ftp = ftp_connect(os, err); + if (ftp == NULL) + return NULL; + + filename = g_build_filename(g_get_home_dir(), ".pcsuite", NULL); + + fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd < 0 && errno != EEXIST) { + error("open(%s): %s(%d)", filename, strerror(errno), errno); + goto fail; + } + + /* Try to remove the file before retrying since it could be + that some process left/crash without removing it */ + if (fd < 0) { + if (remove(filename) < 0) { + error("remove(%s): %s(%d)", filename, strerror(errno), + errno); + goto fail; + } + + fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd < 0) { + error("open(%s): %s(%d)", filename, strerror(errno), + errno); + goto fail; + } + } + + DBG("%s created", filename); + + pcsuite = g_new0(struct pcsuite_session, 1); + pcsuite->ftp = ftp; + pcsuite->lock_file = filename; + pcsuite->fd = fd; + + DBG("session %p created", pcsuite); + + if (err) + *err = 0; + + return pcsuite; + +fail: + if (ftp) + ftp_disconnect(os, ftp); + if (err) + *err = -errno; + + g_free(filename); + + return NULL; +} + +static int pcsuite_get(struct obex_session *os, void *user_data) +{ + struct pcsuite_session *pcsuite = user_data; + + DBG("%p", pcsuite); + + return ftp_get(os, pcsuite->ftp); +} + +static int pcsuite_chkput(struct obex_session *os, void *user_data) +{ + struct pcsuite_session *pcsuite = user_data; + + DBG("%p", pcsuite); + + return ftp_chkput(os, pcsuite->ftp); +} + +static int pcsuite_put(struct obex_session *os, void *user_data) +{ + struct pcsuite_session *pcsuite = user_data; + + DBG("%p", pcsuite); + + return ftp_put(os, pcsuite->ftp); +} + +static int pcsuite_setpath(struct obex_session *os, void *user_data) +{ + struct pcsuite_session *pcsuite = user_data; + + DBG("%p", pcsuite); + + return ftp_setpath(os, pcsuite->ftp); +} + +static int pcsuite_action(struct obex_session *os, void *user_data) +{ + struct pcsuite_session *pcsuite = user_data; + + DBG("%p", pcsuite); + + return ftp_action(os, pcsuite->ftp); +} + +static void pcsuite_disconnect(struct obex_session *os, void *user_data) +{ + struct pcsuite_session *pcsuite = user_data; + + DBG("%p", pcsuite); + + if (pcsuite->fd >= 0) + close(pcsuite->fd); + + if (pcsuite->lock_file) { + remove(pcsuite->lock_file); + g_free(pcsuite->lock_file); + } + + if (pcsuite->ftp) + ftp_disconnect(os, pcsuite->ftp); + + g_free(pcsuite); +} + +static struct obex_service_driver pcsuite = { + .name = "Nokia OBEX PC Suite Services", + .service = OBEX_PCSUITE, + .channel = PCSUITE_CHANNEL, + .secure = TRUE, + .record = PCSUITE_RECORD, + .target = FTP_TARGET, + .target_size = TARGET_SIZE, + .who = PCSUITE_WHO, + .who_size = PCSUITE_WHO_SIZE, + .connect = pcsuite_connect, + .get = pcsuite_get, + .put = pcsuite_put, + .chkput = pcsuite_chkput, + .setpath = pcsuite_setpath, + .action = pcsuite_action, + .disconnect = pcsuite_disconnect +}; + +struct backup_object { + char *cmd; + int fd; + int oflag; + int error_code; + mode_t mode; + DBusPendingCall *pending_call; + DBusConnection *conn; +}; + +static void on_backup_dbus_notify(DBusPendingCall *pending_call, + void *user_data) +{ + struct backup_object *obj = user_data; + DBusMessage *reply; + const char *filename; + int error_code; + + DBG("Notification received for pending call - %s", obj->cmd); + + reply = dbus_pending_call_steal_reply(pending_call); + + if (reply && dbus_message_get_args(reply, NULL, DBUS_TYPE_INT32, + &error_code, DBUS_TYPE_STRING, + &filename, DBUS_TYPE_INVALID)) { + + obj->error_code = error_code; + + if (filename) { + DBG("Notification - file path = %s, error_code = %d", + filename, error_code); + if (error_code == 0) + obj->fd = open(filename,obj->oflag,obj->mode); + } + + } else + DBG("Notification timed out or connection got closed"); + + if (reply) + dbus_message_unref(reply); + + dbus_pending_call_unref(pending_call); + obj->pending_call = NULL; + dbus_connection_unref(obj->conn); + obj->conn = NULL; + + if (obj->fd >= 0) { + DBG("File opened, setting io flags, cmd = %s", + obj->cmd); + if (obj->oflag == O_RDONLY) + obex_object_set_io_flags(user_data, G_IO_IN, 0); + else + obex_object_set_io_flags(user_data, G_IO_OUT, 0); + } else { + DBG("File open error, setting io error, cmd = %s", + obj->cmd); + obex_object_set_io_flags(user_data, G_IO_ERR, -EPERM); + } +} + +static gboolean send_backup_dbus_message(const char *oper, + struct backup_object *obj, + size_t *size) +{ + DBusConnection *conn; + DBusMessage *msg; + DBusPendingCall *pending_call; + gboolean ret = FALSE; + dbus_uint32_t file_size; + + file_size = size ? *size : 0; + + conn = g_dbus_setup_bus(DBUS_BUS_SESSION, NULL, NULL); + + if (conn == NULL) + return FALSE; + + msg = dbus_message_new_method_call(BACKUP_BUS_NAME, BACKUP_PATH, + BACKUP_PLUGIN_INTERFACE, + "request"); + if (msg == NULL) { + dbus_connection_unref(conn); + return FALSE; + } + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &oper, + DBUS_TYPE_STRING, &obj->cmd, + DBUS_TYPE_INT32, &file_size, + DBUS_TYPE_INVALID); + + if (strcmp(oper, "open") == 0) { + ret = g_dbus_send_message_with_reply(conn, msg, &pending_call, + BACKUP_DBUS_TIMEOUT); + dbus_message_unref(msg); + if (ret) { + obj->conn = conn; + obj->pending_call = pending_call; + ret = dbus_pending_call_set_notify(pending_call, + on_backup_dbus_notify, + obj, NULL); + } else + dbus_connection_unref(conn); + } else { + g_dbus_send_message(conn, msg); + dbus_connection_unref(conn); + } + + return ret; +} + +static void *backup_open(const char *name, int oflag, mode_t mode, + void *context, size_t *size, int *err) +{ + struct backup_object *obj = g_new0(struct backup_object, 1); + + DBG("cmd = %s", name); + + obj->cmd = g_path_get_basename(name); + obj->oflag = oflag; + obj->mode = mode; + obj->fd = -1; + obj->pending_call = NULL; + obj->conn = NULL; + obj->error_code = 0; + + if (send_backup_dbus_message("open", obj, size) == FALSE) { + g_free(obj); + obj = NULL; + } + + if (err) + *err = 0; + + return obj; +} + +static int backup_close(void *object) +{ + struct backup_object *obj = object; + size_t size = 0; + + DBG("cmd = %s", obj->cmd); + + if (obj->fd != -1) + close(obj->fd); + + if (obj->pending_call) { + dbus_pending_call_cancel(obj->pending_call); + dbus_pending_call_unref(obj->pending_call); + dbus_connection_unref(obj->conn); + } + + send_backup_dbus_message("close", obj, &size); + + g_free(obj->cmd); + g_free(obj); + + return 0; +} + +static ssize_t backup_read(void *object, void *buf, size_t count) +{ + struct backup_object *obj = object; + ssize_t ret = 0; + + if (obj->pending_call) { + DBG("cmd = %s, IN WAITING STAGE", obj->cmd); + return -EAGAIN; + } + + if (obj->fd != -1) { + DBG("cmd = %s, READING DATA", obj->cmd); + ret = read(obj->fd, buf, count); + if (ret < 0) + ret = -errno; + } else { + DBG("cmd = %s, PERMANENT FAILURE", obj->cmd); + ret = obj->error_code ? -obj->error_code : -ENOENT; + } + + return ret; +} + +static ssize_t backup_write(void *object, const void *buf, size_t count) +{ + struct backup_object *obj = object; + ssize_t ret = 0; + + if (obj->pending_call) { + DBG("cmd = %s, IN WAITING STAGE", obj->cmd); + return -EAGAIN; + } + + if (obj->fd != -1) { + ret = write(obj->fd, buf, count); + + DBG("cmd = %s, WRITTING", obj->cmd); + + if (ret < 0) { + error("backup: cmd = %s", obj->cmd); + ret = -errno; + } + } else { + error("backup: cmd = %s", obj->cmd); + ret = obj->error_code ? -obj->error_code : -ENOENT; + } + + return ret; +} + +static int backup_flush(void *object) +{ + DBG("%p", object); + + return 0; +} + +static struct obex_mime_type_driver backup = { + .target = FTP_TARGET, + .target_size = TARGET_SIZE, + .mimetype = "application/vnd.nokia-backup", + .open = backup_open, + .close = backup_close, + .read = backup_read, + .write = backup_write, + .flush = backup_flush, +}; + +static int pcsuite_init(void) +{ + int err; + + err = obex_service_driver_register(&pcsuite); + if (err < 0) + return err; + + err = obex_mime_type_driver_register(&backup); + if (err < 0) + obex_service_driver_unregister(&pcsuite); + + return err; +} + +static void pcsuite_exit(void) +{ + obex_mime_type_driver_unregister(&backup); + obex_service_driver_unregister(&pcsuite); +} + +OBEX_PLUGIN_DEFINE(pcsuite, pcsuite_init, pcsuite_exit) diff --git a/obexd/plugins/phonebook-dummy.c b/obexd/plugins/phonebook-dummy.c new file mode 100644 index 0000000..9279ef2 --- /dev/null +++ b/obexd/plugins/phonebook-dummy.c @@ -0,0 +1,587 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2009-2010 Intel Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "obexd/src/log.h" +#include "phonebook.h" + +typedef void (*vcard_func_t) (const char *file, VObject *vo, void *user_data); + +struct dummy_data { + phonebook_cb cb; + void *user_data; + const struct apparam_field *apparams; + char *folder; + int fd; + guint id; +}; + +struct cache_query { + phonebook_entry_cb entry_cb; + phonebook_cache_ready_cb ready_cb; + void *user_data; + DIR *dp; +}; + +static char *root_folder = NULL; + +static void dummy_free(void *user_data) +{ + struct dummy_data *dummy = user_data; + + if (dummy->fd >= 0) + close(dummy->fd); + + g_free(dummy->folder); + g_free(dummy); +} + +static void query_free(void *user_data) +{ + struct cache_query *query = user_data; + + if (query->dp) + closedir(query->dp); + + g_free(query); +} + +int phonebook_init(void) +{ + if (root_folder) + return 0; + + /* FIXME: It should NOT be hard-coded */ + root_folder = g_build_filename(getenv("HOME"), "phonebook", NULL); + + return 0; +} + +void phonebook_exit(void) +{ + g_free(root_folder); + root_folder = NULL; +} + +static int handle_cmp(gconstpointer a, gconstpointer b) +{ + const char *f1 = a; + const char *f2 = b; + unsigned int i1, i2; + + if (sscanf(f1, "%u.vcf", &i1) != 1) + return -1; + + if (sscanf(f2, "%u.vcf", &i2) != 1) + return -1; + + return (i1 - i2); +} + +static int foreach_vcard(DIR *dp, vcard_func_t func, uint16_t offset, + uint16_t maxlistcount, void *user_data, uint16_t *count) +{ + struct dirent *ep; + GSList *sorted = NULL, *l; + VObject *v; + FILE *fp; + int err, fd, folderfd; + uint16_t n = 0; + + folderfd = dirfd(dp); + if (folderfd < 0) { + err = errno; + error("dirfd(): %s(%d)", strerror(err), err); + return -err; + } + + /* + * Sorting vcards by file name. versionsort is a GNU extension. + * The simple sorting function implemented on handle_cmp address + * vcards handle only(handle is always a number). This sort function + * doesn't address filename started by "0". + */ + while ((ep = readdir(dp))) { + char *filename; + + if (ep->d_name[0] == '.') + continue; + + filename = g_filename_to_utf8(ep->d_name, -1, NULL, NULL, NULL); + if (filename == NULL) { + error("g_filename_to_utf8: invalid filename"); + continue; + } + + if (!g_str_has_suffix(filename, ".vcf")) { + g_free(filename); + continue; + } + + sorted = g_slist_insert_sorted(sorted, filename, handle_cmp); + } + + /* + * Filtering only the requested vCards attributes. Offset + * shall be based on the first entry of the phonebook. + */ + for (l = g_slist_nth(sorted, offset); + l && n < maxlistcount; l = l->next) { + const char *filename = l->data; + + fd = openat(folderfd, filename, O_RDONLY); + if (fd < 0) { + err = errno; + error("openat(%s): %s(%d)", filename, strerror(err), err); + continue; + } + + fp = fdopen(fd, "r"); + v = Parse_MIME_FromFile(fp); + if (v != NULL) { + func(filename, v, user_data); + deleteVObject(v); + n++; + } + + close(fd); + } + + g_slist_free_full(sorted, g_free); + + if (count) + *count = n; + + return 0; +} + +static void entry_concat(const char *filename, VObject *v, void *user_data) +{ + GString *buffer = user_data; + char tmp[1024]; + int len; + + /* + * VObject API uses len for IN and OUT + * Written bytes is also returned in the len variable + */ + len = sizeof(tmp); + memset(tmp, 0, len); + + writeMemVObject(tmp, &len, v); + + /* FIXME: only the requested fields must be added */ + g_string_append_len(buffer, tmp, len); +} + +static gboolean read_dir(void *user_data) +{ + struct dummy_data *dummy = user_data; + GString *buffer; + DIR *dp; + uint16_t count = 0, max, offset; + + buffer = g_string_new(""); + + dp = opendir(dummy->folder); + if (dp == NULL) { + int err = errno; + DBG("opendir(): %s(%d)", strerror(err), err); + goto done; + } + + /* + * For PullPhoneBook function, the decision of returning the size + * or contacts is made in the PBAP core. When MaxListCount is ZERO, + * PCE wants to know the size of a given folder, PSE shall ignore all + * other applicattion parameters that may be present in the request. + */ + if (dummy->apparams->maxlistcount == 0) { + max = 0xffff; + offset = 0; + } else { + max = dummy->apparams->maxlistcount; + offset = dummy->apparams->liststartoffset; + } + + foreach_vcard(dp, entry_concat, offset, max, buffer, &count); + + closedir(dp); +done: + /* FIXME: Missing vCards fields filtering */ + dummy->cb(buffer->str, buffer->len, count, 0, TRUE, dummy->user_data); + + g_string_free(buffer, TRUE); + + return FALSE; +} + +static void entry_notify(const char *filename, VObject *v, void *user_data) +{ + struct cache_query *query = user_data; + VObject *property, *subproperty; + GString *name; + const char *tel; + long unsigned int handle; + + property = isAPropertyOf(v, VCNameProp); + if (!property) + return; + + if (sscanf(filename, "%lu.vcf", &handle) != 1) + return; + + if (handle > UINT32_MAX) + return; + + /* LastName; FirstName; MiddleName; Prefix; Suffix */ + + name = g_string_new(""); + subproperty = isAPropertyOf(property, VCFamilyNameProp); + if (subproperty) { + g_string_append(name, + fakeCString(vObjectUStringZValue(subproperty))); + } + + subproperty = isAPropertyOf(property, VCGivenNameProp); + if (subproperty) + g_string_append_printf(name, ";%s", + fakeCString(vObjectUStringZValue(subproperty))); + + subproperty = isAPropertyOf(property, VCAdditionalNamesProp); + if (subproperty) + g_string_append_printf(name, ";%s", + fakeCString(vObjectUStringZValue(subproperty))); + + subproperty = isAPropertyOf(property, VCNamePrefixesProp); + if (subproperty) + g_string_append_printf(name, ";%s", + fakeCString(vObjectUStringZValue(subproperty))); + + + subproperty = isAPropertyOf(property, VCNameSuffixesProp); + if (subproperty) + g_string_append_printf(name, ";%s", + fakeCString(vObjectUStringZValue(subproperty))); + + property = isAPropertyOf(v, VCTelephoneProp); + + tel = property ? fakeCString(vObjectUStringZValue(property)) : NULL; + + query->entry_cb(filename, handle, name->str, NULL, tel, + query->user_data); + g_string_free(name, TRUE); +} + +static gboolean create_cache(void *user_data) +{ + struct cache_query *query = user_data; + + /* + * MaxListCount and ListStartOffset shall not be used + * when creating the cache. All entries shall be fetched. + * PBAP core is responsible for consider these application + * parameters before reply the entries. + */ + foreach_vcard(query->dp, entry_notify, 0, 0xffff, query, NULL); + + query->ready_cb(query->user_data); + + return FALSE; +} + +static gboolean read_entry(void *user_data) +{ + struct dummy_data *dummy = user_data; + char buffer[1024]; + ssize_t count; + + memset(buffer, 0, sizeof(buffer)); + count = read(dummy->fd, buffer, sizeof(buffer)); + + if (count < 0) { + int err = errno; + error("read(): %s(%d)", strerror(err), err); + count = 0; + } + + /* FIXME: Missing vCards fields filtering */ + + dummy->cb(buffer, count, 1, 0, TRUE, dummy->user_data); + + return FALSE; +} + +static gboolean is_dir(const char *dir) +{ + struct stat st; + + if (stat(dir, &st) < 0) { + int err = errno; + error("stat(%s): %s (%d)", dir, strerror(err), err); + return FALSE; + } + + return S_ISDIR(st.st_mode); +} + +char *phonebook_set_folder(const char *current_folder, + const char *new_folder, uint8_t flags, int *err) +{ + gboolean root, child; + char *tmp1, *tmp2, *base, *absolute, *relative = NULL; + int len, ret = 0; + + root = (g_strcmp0("/", current_folder) == 0); + child = (new_folder && strlen(new_folder) != 0); + + switch (flags) { + case 0x02: + /* Go back to root */ + if (!child) { + relative = g_strdup("/"); + goto done; + } + + relative = g_build_filename(current_folder, new_folder, NULL); + break; + case 0x03: + /* Go up 1 level */ + if (root) { + /* Already root */ + ret = -EBADR; + goto done; + } + + /* + * Removing one level of the current folder. Current folder + * contains AT LEAST one level since it is not at root folder. + * Use glib utility functions to handle invalid chars in the + * folder path properly. + */ + tmp1 = g_path_get_basename(current_folder); + tmp2 = g_strrstr(current_folder, tmp1); + len = tmp2 - (current_folder + 1); + + g_free(tmp1); + + if (len == 0) + base = g_strdup("/"); + else + base = g_strndup(current_folder, len); + + /* Return: one level only */ + if (!child) { + relative = base; + goto done; + } + + relative = g_build_filename(base, new_folder, NULL); + g_free(base); + + break; + default: + ret = -EBADR; + break; + } + +done: + if (!relative) { + if (err) + *err = ret; + + return NULL; + } + + absolute = g_build_filename(root_folder, relative, NULL); + if (!is_dir(absolute)) { + g_free(relative); + relative = NULL; + ret = -ENOENT; + } + + g_free(absolute); + + if (err) + *err = ret; + + return relative; +} + +void phonebook_req_finalize(void *request) +{ + struct dummy_data *dummy = request; + + /* dummy_data will be cleaned when request will be finished via + * g_source_remove */ + if (dummy && dummy->id) + g_source_remove(dummy->id); +} + +void *phonebook_pull(const char *name, const struct apparam_field *params, + phonebook_cb cb, void *user_data, int *err) +{ + struct dummy_data *dummy; + char *filename, *folder; + + /* + * Main phonebook objects will be created dinamically based on the + * folder content. All vcards inside the given folder will be appended + * in the "virtual" main phonebook object. + */ + + filename = g_build_filename(root_folder, name, NULL); + + if (!g_str_has_suffix(filename, ".vcf")) { + g_free(filename); + if (err) + *err = -EBADR; + return NULL; + } + + folder = g_strndup(filename, strlen(filename) - 4); + g_free(filename); + if (!is_dir(folder)) { + g_free(folder); + if (err) + *err = -ENOENT; + return NULL; + } + + dummy = g_new0(struct dummy_data, 1); + dummy->cb = cb; + dummy->user_data = user_data; + dummy->apparams = params; + dummy->folder = folder; + dummy->fd = -1; + + if (err) + *err = 0; + + return dummy; +} + +int phonebook_pull_read(void *request) +{ + struct dummy_data *dummy = request; + + if (!dummy) + return -ENOENT; + + dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_dir, dummy, + dummy_free); + + return 0; +} + +void *phonebook_get_entry(const char *folder, const char *id, + const struct apparam_field *params, phonebook_cb cb, + void *user_data, int *err) +{ + struct dummy_data *dummy; + char *filename; + int fd; + + filename = g_build_filename(root_folder, folder, id, NULL); + + fd = open(filename, O_RDONLY); + + g_free(filename); + + if (fd < 0) { + DBG("open(): %s(%d)", strerror(errno), errno); + if (err) + *err = -ENOENT; + return NULL; + } + + dummy = g_new0(struct dummy_data, 1); + dummy->cb = cb; + dummy->user_data = user_data; + dummy->apparams = params; + dummy->fd = fd; + + dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, read_entry, dummy, + dummy_free); + + if (err) + *err = 0; + + return dummy; +} + +void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb, + phonebook_cache_ready_cb ready_cb, void *user_data, int *err) +{ + struct cache_query *query; + char *foldername; + DIR *dp; + struct dummy_data *dummy; + + foldername = g_build_filename(root_folder, name, NULL); + dp = opendir(foldername); + g_free(foldername); + + if (dp == NULL) { + DBG("opendir(): %s(%d)", strerror(errno), errno); + if (err) + *err = -ENOENT; + return NULL; + } + + query = g_new0(struct cache_query, 1); + query->entry_cb = entry_cb; + query->ready_cb = ready_cb; + query->user_data = user_data; + query->dp = dp; + + dummy = g_new0(struct dummy_data, 1); + + dummy->id = g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, create_cache, + query, query_free); + + if (err) + *err = 0; + + return dummy; +} diff --git a/obexd/plugins/phonebook.h b/obexd/plugins/phonebook.h new file mode 100644 index 0000000..70a9cb7 --- /dev/null +++ b/obexd/plugins/phonebook.h @@ -0,0 +1,166 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define EOL "\r\n" +#define VCARD_LISTING_BEGIN \ + "" EOL\ + "" EOL\ + "" EOL +#define VCARD_LISTING_ELEMENT "" EOL +#define VCARD_LISTING_END "" + +#define PB_TELECOM_FOLDER "/telecom" +#define PB_CONTACTS_FOLDER "/telecom/pb" +#define PB_CALENDAR_FOLDER "/telecom/cal" +#define PB_NOTES_FOLDER "/telecom/nt" +#define PB_CALLS_COMBINED_FOLDER "/telecom/cch" +#define PB_CALLS_INCOMING_FOLDER "/telecom/ich" +#define PB_CALLS_MISSED_FOLDER "/telecom/mch" +#define PB_CALLS_OUTGOING_FOLDER "/telecom/och" +#define PB_CALLS_SPEEDDIAL_FOLDER "/telecom/spd" +#define PB_CALLS_FAVORITE_FOLDER "/telecom/fav" +#define PB_LUID_FOLDER "/telecom/pb/luid" + +#define PB_CONTACTS "/telecom/pb.vcf" +#define PB_CALLS_COMBINED "/telecom/cch.vcf" +#define PB_CALLS_INCOMING "/telecom/ich.vcf" +#define PB_CALLS_MISSED "/telecom/mch.vcf" +#define PB_CALLS_OUTGOING "/telecom/och.vcf" +#define PB_CALLS_SPEEDDIAL "/telecom/spd.vcf" +#define PB_CALLS_FAVORITE "/telecom/fav.vcf" +#define PB_DEVINFO "/telecom/devinfo.txt" +#define PB_INFO_LOG "/telecom/pb/info.log" +#define PB_CC_LOG "/telecom/pb/luid/cc.log" + + +struct apparam_field { + /* list and pull attributes */ + uint16_t maxlistcount; + uint16_t liststartoffset; + + /* pull and vcard attributes */ + uint64_t filter; + uint8_t format; + + /* list attributes only */ + uint8_t order; + uint8_t searchattrib; + char *searchval; +}; + +/* + * Interface between the PBAP core and backends to retrieve + * all contacts that match the application parameters rules. + * Contacts will be returned in the vcard format. + */ +typedef void (*phonebook_cb) (const char *buffer, size_t bufsize, + int vcards, int missed, gboolean lastpart, void *user_data); + +/* + * Interface between the PBAP core and backends to + * append a new entry in the PBAP folder cache. + */ +#define PHONEBOOK_INVALID_HANDLE 0xffffffff +typedef void (*phonebook_entry_cb) (const char *id, uint32_t handle, + const char *name, const char *sound, + const char *tel, void *user_data); + +/* + * After notify all entries to PBAP core, the backend + * needs to notify that the operation has finished. + */ +typedef void (*phonebook_cache_ready_cb) (void *user_data); + + +int phonebook_init(void); +void phonebook_exit(void); + +/* + * Changes the current folder in the phonebook back-end. The PBAP core + * doesn't validate or restrict the possible values for the folders, + * allowing non-standard backends implementation which doesn't follow + * the PBAP virtual folder architecture. Validate the folder's name + * is responsibility of the back-ends. +*/ +char *phonebook_set_folder(const char *current_folder, + const char *new_folder, uint8_t flags, int *err); + +/* + * phonebook_pull should be used only to prepare pull request - prepared + * request data is returned by this function. Start of fetching data from + * back-end will be done only after calling phonebook_pull_read with this + * returned request given as a parameter. + * + * phonebook_req_finalize MUST always be used to free associated resources. + */ +void *phonebook_pull(const char *name, const struct apparam_field *params, + phonebook_cb cb, void *user_data, int *err); + +/* + * phonebook_pull_read should be used to start getting results from back-end. + * The back-end can return data as one response or can return it many parts. + * After obtaining one part, PBAP core need to call phonebook_pull_read with + * the same request again to get more results from back-end. + * The back-end MUST return only the content based on the application + * parameters requested by the client. + * + * Returns error code or 0 in case of success + */ +int phonebook_pull_read(void *request); + +/* + * Function used to retrieve a contact from the backend. Only contacts + * found in the cache are requested to the back-ends. The back-end MUST + * return only the content based on the application parameters requested + * by the client. + * + * Return value is a pointer to asynchronous request to phonebook back-end. + * phonebook_req_finalize MUST always be used to free associated resources. + */ +void *phonebook_get_entry(const char *folder, const char *id, + const struct apparam_field *params, + phonebook_cb cb, void *user_data, int *err); + +/* + * PBAP core will keep the contacts cache per folder. SetPhoneBook or + * PullvCardListing can invalidate the cache if the current folder changes. + * Cache will store only the necessary information required to reply to + * PullvCardListing request and verify if a given contact belongs to the + * source. + * + * Return value is a pointer to asynchronous request to phonebook back-end. + * phonebook_req_finalize MUST always be used to free associated resources. + */ +void *phonebook_create_cache(const char *name, phonebook_entry_cb entry_cb, + phonebook_cache_ready_cb ready_cb, void *user_data, int *err); + +/* + * Finalizes request to phonebook back-end and deallocates associated + * resources. Operation is canceled if not completed. This function MUST + * always be used after any of phonebook_pull, phonebook_get_entry, and + * phonebook_create_cache invoked. + * + * request is a pointer to asynchronous operation returned by phonebook_pull, + * phonebook_get_entry, and phonebook_create_cache. + */ +void phonebook_req_finalize(void *request); diff --git a/obexd/plugins/vcard.c b/obexd/plugins/vcard.c new file mode 100644 index 0000000..dc7c3b3 --- /dev/null +++ b/obexd/plugins/vcard.c @@ -0,0 +1,928 @@ +/* + * OBEX Server + * + * Copyright (C) 2008-2010 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 +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" + +#include "vcard.h" + +#define ADDR_FIELD_AMOUNT 7 +#define LEN_MAX 128 +#define TYPE_INTERNATIONAL 145 + +#define PHONEBOOK_FLAG_CACHED 0x1 + +#define FILTER_VERSION (1 << 0) +#define FILTER_FN (1 << 1) +#define FILTER_N (1 << 2) +#define FILTER_PHOTO (1 << 3) +#define FILTER_BDAY (1 << 4) +#define FILTER_ADR (1 << 5) +#define FILTER_LABEL (1 << 6) +#define FILTER_TEL (1 << 7) +#define FILTER_EMAIL (1 << 8) +#define FILTER_MAILER (1 << 9) +#define FILTER_TZ (1 << 10) +#define FILTER_GEO (1 << 11) +#define FILTER_TITLE (1 << 12) +#define FILTER_ROLE (1 << 13) +#define FILTER_LOGO (1 << 14) +#define FILTER_AGENT (1 << 15) +#define FILTER_ORG (1 << 16) +#define FILTER_NOTE (1 << 17) +#define FILTER_REV (1 << 18) +#define FILTER_SOUND (1 << 19) +#define FILTER_URL (1 << 20) +#define FILTER_UID (1 << 21) +#define FILTER_KEY (1 << 22) +#define FILTER_NICKNAME (1 << 23) +#define FILTER_CATEGORIES (1 << 24) +#define FILTER_PROID (1 << 25) +#define FILTER_CLASS (1 << 26) +#define FILTER_SORT_STRING (1 << 27) +#define FILTER_X_IRMC_CALL_DATETIME (1 << 28) + +#define FORMAT_VCARD21 0x00 +#define FORMAT_VCARD30 0x01 + +#define QP_LINE_LEN 75 +#define QP_CHAR_LEN 3 +#define QP_CR 0x0D +#define QP_LF 0x0A +#define QP_ESC 0x5C +#define QP_SOFT_LINE_BREAK "=" +#define QP_SELECT "\n!\"#$=@[\\]^`{|}~" +#define ASCII_LIMIT 0x7F + +/* according to RFC 2425, the output string may need folding */ +static void vcard_printf(GString *str, const char *fmt, ...) +{ + char buf[1024]; + va_list ap; + int len_temp, line_number, i; + unsigned int line_delimit = 75; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + line_number = strlen(buf) / line_delimit + 1; + + for (i = 0; i < line_number; i++) { + len_temp = MIN(line_delimit, strlen(buf) - line_delimit * i); + g_string_append_len(str, buf + line_delimit * i, len_temp); + if (i != line_number - 1) + g_string_append(str, "\r\n "); + } + + g_string_append(str, "\r\n"); +} + +/* According to RFC 2426, we need escape following characters: + * '\n', '\r', ';', ',', '\'. + */ +static void add_slash(char *dest, const char *src, int len_max, int len) +{ + int i, j; + + for (i = 0, j = 0; i < len && j + 1 < len_max; i++, j++) { + /* filling dest buffer - last field need to be reserved + * for '\0'*/ + switch (src[i]) { + case '\n': + if (j + 2 >= len_max) + /* not enough space in the buffer to put char + * preceded with escaping sequence (and '\0' in + * the end) */ + goto done; + + dest[j++] = '\\'; + dest[j] = 'n'; + break; + case '\r': + if (j + 2 >= len_max) + goto done; + + dest[j++] = '\\'; + dest[j] = 'r'; + break; + case '\\': + case ';': + case ',': + if (j + 2 >= len_max) + goto done; + + dest[j++] = '\\'; + /* fall through */ + default: + dest[j] = src[i]; + break; + } + } + +done: + dest[j] = 0; +} + +static void escape_semicolon(char *dest, const char *src, int len_max, int len) +{ + int i, j; + + for (i = 0, j = 0; i < len && j + 1 < len_max; i++, j++) { + if (src[i] == ';') { + if (j + 2 >= len_max) + break; + + dest[j++] = '\\'; + } + + dest[j] = src[i]; + } + + dest[j] = 0; +} + +static void set_escape(uint8_t format, char *dest, const char *src, + int len_max, int len) +{ + if (format == FORMAT_VCARD30) + add_slash(dest, src, len_max, len); + else if (format == FORMAT_VCARD21) + escape_semicolon(dest, src, len_max, len); +} + +static void get_escaped_fields(uint8_t format, char **fields, ...) +{ + va_list ap; + GString *line; + char *field; + char escaped[LEN_MAX]; + + va_start(ap, fields); + line = g_string_new(""); + + for (field = va_arg(ap, char *); field; ) { + set_escape(format, escaped, field, LEN_MAX, strlen(field)); + g_string_append(line, escaped); + + field = va_arg(ap, char *); + + if (field) + g_string_append(line, ";"); + } + + va_end(ap); + + *fields = g_string_free(line, FALSE); +} + +static gboolean set_qp_encoding(char c) +{ + unsigned char q = c; + + if (strchr(QP_SELECT, q) != NULL) + return TRUE; + + if (q < '!' || q > '~') + return TRUE; + + return FALSE; +} + +static void append_qp_break_line(GString *vcards, size_t *limit) +{ + /* Quoted Printable lines of text must be limited to less than 76 + * characters and terminated by Quoted Printable softline break + * sequence of "=" (if some more characters left) */ + g_string_append(vcards, QP_SOFT_LINE_BREAK); + g_string_append(vcards, "\r\n "); + *limit = QP_LINE_LEN - 1; +} + +static void append_qp_ascii(GString *vcards, size_t *limit, char c) +{ + if (*limit == 0) + append_qp_break_line(vcards, limit); + + g_string_append_c(vcards, c); + --*limit; +} + +static void append_qp_hex(GString *vcards, size_t *limit, char c) +{ + if (*limit < QP_CHAR_LEN) + append_qp_break_line(vcards, limit); + + g_string_append_printf(vcards, "=%2.2X", (unsigned char) c); + *limit -= QP_CHAR_LEN; +} + +static void append_qp_new_line(GString *vcards, size_t *limit) +{ + /* Multiple lines of text are separated with a Quoted Printable CRLF + * sequence of "=0D" followed by "=0A" followed by a Quoted Printable + * softline break sequence of "=" */ + append_qp_hex(vcards, limit, QP_CR); + append_qp_hex(vcards, limit, QP_LF); + append_qp_break_line(vcards, limit); +} + +static gboolean utf8_select(const char *field) +{ + const char *pos; + + if (g_utf8_validate(field, -1, NULL) == FALSE) + return FALSE; + + for (pos = field; *pos != '\0'; pos = g_utf8_next_char(pos)) { + /* Test for non-standard UTF-8 character (out of range + * standard ASCII set), composed of more than single byte + * and represented by 32-bit value greater than 0x7F */ + if (g_utf8_get_char(pos) > ASCII_LIMIT) + return TRUE; + } + + return FALSE; +} + +static void vcard_qp_print_encoded(GString *vcards, const char *desc, ...) +{ + const char *field, *charset = ""; + const char *encoding = ";ENCODING=QUOTED-PRINTABLE"; + size_t limit, param_len; + va_list ap; + + va_start(ap, desc); + + for (field = va_arg(ap, char *); field; field = va_arg(ap, char *)) { + if (utf8_select(field) == TRUE) { + charset = ";CHARSET=UTF-8"; + break; + } + } + + va_end(ap); + + vcard_printf(vcards, "%s%s%s:", desc, encoding, charset); + g_string_truncate(vcards, vcards->len - 2); + + param_len = strlen(desc) + strlen(encoding) + strlen(charset) + 1; + limit = QP_LINE_LEN - param_len; + + va_start(ap, desc); + + for (field = va_arg(ap, char *); field != NULL; ) { + size_t i, size = strlen(field); + + for (i = 0; i < size; ++i) { + if (set_qp_encoding(field[i])) { + if (field[i] == '\n') { + append_qp_new_line(vcards, &limit); + continue; + } + + append_qp_hex(vcards, &limit, field[i]); + } else { + /* According to vCard 2.1 spec. semicolons in + * property parameter value must be escaped */ + if (field[i] == ';') + append_qp_hex(vcards, &limit, QP_ESC); + + append_qp_ascii(vcards, &limit, field[i]); + } + } + + field = va_arg(ap, char *); + if (field) + append_qp_ascii(vcards, &limit, ';'); + } + + va_end(ap); + + g_string_append(vcards, "\r\n"); +} + +static gboolean select_qp_encoding(uint8_t format, ...) +{ + char *field; + va_list ap; + + if (format != FORMAT_VCARD21) + return FALSE; + + va_start(ap, format); + + for (field = va_arg(ap, char *); field; field = va_arg(ap, char *)) { + int i; + unsigned char c; + + if (strpbrk(field, QP_SELECT)) { + va_end(ap); + return TRUE; + } + + /* Quoted Printable encoding is selected if there is + * a character, which value is out of range standard + * ASCII set, since it may be a part of some + * non-standard character such as specified by UTF-8 */ + for (i = 0; (c = field[i]) != '\0'; ++i) { + if (c > ASCII_LIMIT) { + va_end(ap); + return TRUE; + } + } + } + + va_end(ap); + + return FALSE; +} + +static void vcard_printf_begin(GString *vcards, uint8_t format) +{ + vcard_printf(vcards, "BEGIN:VCARD"); + + if (format == FORMAT_VCARD30) + vcard_printf(vcards, "VERSION:3.0"); + else if (format == FORMAT_VCARD21) + vcard_printf(vcards, "VERSION:2.1"); +} + +/* check if there is at least one contact field with personal data present */ +static gboolean contact_fields_present(struct phonebook_contact * contact) +{ + if (contact->family && strlen(contact->family) > 0) + return TRUE; + + if (contact->given && strlen(contact->given) > 0) + return TRUE; + + if (contact->additional && strlen(contact->additional) > 0) + return TRUE; + + if (contact->prefix && strlen(contact->prefix) > 0) + return TRUE; + + if (contact->suffix && strlen(contact->suffix) > 0) + return TRUE; + + /* none of the personal data fields are present*/ + return FALSE; +} + +static void vcard_printf_name(GString *vcards, uint8_t format, + struct phonebook_contact *contact) +{ + char *fields; + + if (contact_fields_present(contact) == FALSE) { + /* If fields are empty, add only 'N:' as parameter. + * This is crucial for some devices (Nokia BH-903) which + * have problems with history listings and can't determine + * that a parameter is really empty if there are unnecessary + * characters after 'N:' (e.g. 'N:;;;;'). + * We need to add only'N:' param - without semicolons. + */ + vcard_printf(vcards, "N:"); + return; + } + + if (select_qp_encoding(format, contact->family, contact->given, + contact->additional, contact->prefix, + contact->suffix, NULL)) { + vcard_qp_print_encoded(vcards, "N", contact->family, + contact->given, contact->additional, + contact->prefix, contact->suffix, + NULL); + return; + } + + get_escaped_fields(format, &fields, contact->family, + contact->given, contact->additional, + contact->prefix, contact->suffix, + NULL); + + vcard_printf(vcards, "N:%s", fields); + + g_free(fields); +} + +static void vcard_printf_fullname(GString *vcards, uint8_t format, + const char *text) +{ + char field[LEN_MAX]; + + if (!text || strlen(text) == 0) { + vcard_printf(vcards, "FN:"); + return; + } + + if (select_qp_encoding(format, text, NULL)) { + vcard_qp_print_encoded(vcards, "FN", text, NULL); + return; + } + + set_escape(format, field, text, LEN_MAX, strlen(text)); + vcard_printf(vcards, "FN:%s", field); +} + +static void vcard_printf_number(GString *vcards, uint8_t format, + const char *number, int type, + enum phonebook_number_type category) +{ + const char *intl = "", *category_string = ""; + char buf[LEN_MAX], field[LEN_MAX]; + + /* TEL is a mandatory field, include even if empty */ + if (!number || !strlen(number) || !type) { + vcard_printf(vcards, "TEL:"); + return; + } + + switch (category) { + case TEL_TYPE_HOME: + if (format == FORMAT_VCARD21) + category_string = "HOME;VOICE"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=HOME;TYPE=VOICE"; + break; + case TEL_TYPE_MOBILE: + if (format == FORMAT_VCARD21) + category_string = "CELL;VOICE"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=CELL;TYPE=VOICE"; + break; + case TEL_TYPE_FAX: + if (format == FORMAT_VCARD21) + category_string = "FAX"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=FAX"; + break; + case TEL_TYPE_WORK: + if (format == FORMAT_VCARD21) + category_string = "WORK;VOICE"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=WORK;TYPE=VOICE"; + break; + case TEL_TYPE_OTHER: + if (format == FORMAT_VCARD21) + category_string = "OTHER;VOICE"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=OTHER;TYPE=VOICE"; + break; + } + + if ((type == TYPE_INTERNATIONAL) && (number[0] != '+')) + intl = "+"; + + snprintf(field, sizeof(field), "%s%s", intl, number); + + if (select_qp_encoding(format, number, NULL)) { + snprintf(buf, sizeof(buf), "TEL;%s", category_string); + vcard_qp_print_encoded(vcards, buf, field, NULL); + return; + } + + vcard_printf(vcards, "TEL;%s:%s", category_string, field); +} + +static void vcard_printf_tag(GString *vcards, uint8_t format, + const char *tag, const char *category, + const char *fld) +{ + int len; + char *separator = "", *type = ""; + char buf[LEN_MAX], field[LEN_MAX]; + + if (tag == NULL || strlen(tag) == 0) + return; + + if (fld == NULL || (len = strlen(fld)) == 0) { + vcard_printf(vcards, "%s:", tag); + return; + } + + if (category && strlen(category)) { + separator = ";"; + if (format == FORMAT_VCARD30) + type = "TYPE="; + } else { + category = ""; + } + + snprintf(buf, LEN_MAX, "%s%s%s%s", tag, separator, type, category); + + if (select_qp_encoding(format, fld, NULL)) { + vcard_qp_print_encoded(vcards, buf, fld, NULL); + return; + } + + set_escape(format, field, fld, LEN_MAX, len); + vcard_printf(vcards, "%s:%s", buf, field); +} + +static void vcard_printf_email(GString *vcards, uint8_t format, + const char *address, + enum phonebook_field_type category) +{ + const char *category_string = ""; + char buf[LEN_MAX], field[LEN_MAX]; + int len = 0; + + if (!address || !(len = strlen(address))) { + vcard_printf(vcards, "EMAIL:"); + return; + } + switch (category) { + case FIELD_TYPE_HOME: + if (format == FORMAT_VCARD21) + category_string = "INTERNET;HOME"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET;TYPE=HOME"; + break; + case FIELD_TYPE_WORK: + if (format == FORMAT_VCARD21) + category_string = "INTERNET;WORK"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET;TYPE=WORK"; + break; + case FIELD_TYPE_OTHER: + default: + if (format == FORMAT_VCARD21) + category_string = "INTERNET"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET;TYPE=OTHER"; + } + + if (select_qp_encoding(format, address, NULL)) { + snprintf(buf, sizeof(buf), "EMAIL;%s", category_string); + vcard_qp_print_encoded(vcards, buf, address, NULL); + return; + } + + set_escape(format, field, address, LEN_MAX, len); + vcard_printf(vcards, "EMAIL;%s:%s", category_string, field); +} + +static void vcard_printf_url(GString *vcards, uint8_t format, + const char *url, + enum phonebook_field_type category) +{ + const char *category_string = ""; + char buf[LEN_MAX], field[LEN_MAX]; + + if (!url || strlen(url) == 0) { + vcard_printf(vcards, "URL:"); + return; + } + + switch (category) { + case FIELD_TYPE_HOME: + if (format == FORMAT_VCARD21) + category_string = "INTERNET;HOME"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET;TYPE=HOME"; + break; + case FIELD_TYPE_WORK: + if (format == FORMAT_VCARD21) + category_string = "INTERNET;WORK"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET;TYPE=WORK"; + break; + case FIELD_TYPE_OTHER: + default: + if (format == FORMAT_VCARD21) + category_string = "INTERNET"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=INTERNET"; + break; + } + + if (select_qp_encoding(format, url, NULL)) { + snprintf(buf, sizeof(buf), "URL;%s", category_string); + vcard_qp_print_encoded(vcards, buf, url, NULL); + return; + } + + set_escape(format, field, url, LEN_MAX, strlen(url)); + vcard_printf(vcards, "URL;%s:%s", category_string, field); +} + +static gboolean org_fields_present(struct phonebook_contact *contact) +{ + if (contact->company && strlen(contact->company)) + return TRUE; + + if (contact->department && strlen(contact->department)) + return TRUE; + + return FALSE; +} + +static void vcard_printf_org(GString *vcards, uint8_t format, + struct phonebook_contact *contact) +{ + char *fields; + + if (org_fields_present(contact) == FALSE) + return; + + if (select_qp_encoding(format, contact->company, + contact->department, NULL)) { + vcard_qp_print_encoded(vcards, "ORG", contact->company, + contact->department, NULL); + return; + } + + get_escaped_fields(format, &fields, contact->company, + contact->department, NULL); + + vcard_printf(vcards, "ORG:%s", fields); + + g_free(fields); +} + +static void vcard_printf_address(GString *vcards, uint8_t format, + struct phonebook_addr *address) +{ + char *fields, field_esc[LEN_MAX]; + const char *category_string = ""; + char buf[LEN_MAX], *address_fields[ADDR_FIELD_AMOUNT]; + int i; + size_t len; + GSList *l; + + if (!address) { + vcard_printf(vcards, "ADR:"); + return; + } + + switch (address->type) { + case FIELD_TYPE_HOME: + if (format == FORMAT_VCARD21) + category_string = "HOME"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=HOME"; + break; + case FIELD_TYPE_WORK: + if (format == FORMAT_VCARD21) + category_string = "WORK"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=WORK"; + break; + default: + if (format == FORMAT_VCARD21) + category_string = "OTHER"; + else if (format == FORMAT_VCARD30) + category_string = "TYPE=OTHER"; + break; + } + + for (i = 0, l = address->fields; l; l = l->next) + address_fields[i++] = l->data; + + if (select_qp_encoding(format, address_fields[0], address_fields[1], + address_fields[2], address_fields[3], + address_fields[4], address_fields[5], + address_fields[6], NULL)) { + snprintf(buf, sizeof(buf), "ADR;%s", category_string); + vcard_qp_print_encoded(vcards, buf, + address_fields[0], address_fields[1], + address_fields[2], address_fields[3], + address_fields[4], address_fields[5], + address_fields[6], NULL); + return; + } + + /* allocate enough memory to insert address fields separated by ';' + * and terminated by '\0' */ + len = ADDR_FIELD_AMOUNT * LEN_MAX; + fields = g_malloc0(len); + + for (l = address->fields; l; l = l->next) { + char *field = l->data; + + if (field) { + set_escape(format, field_esc, field, LEN_MAX, + strlen(field)); + g_strlcat(fields, field_esc, len); + } + + if (l->next) + /* not adding ';' after last addr field */ + g_strlcat(fields, ";", len); + } + + vcard_printf(vcards,"ADR;%s:%s", category_string, fields); + + g_free(fields); +} + +static void vcard_printf_datetime(GString *vcards, uint8_t format, + struct phonebook_contact *contact) +{ + const char *type; + char buf[LEN_MAX]; + + switch (contact->calltype) { + case CALL_TYPE_MISSED: + type = "MISSED"; + break; + + case CALL_TYPE_INCOMING: + type = "RECEIVED"; + break; + + case CALL_TYPE_OUTGOING: + type = "DIALED"; + break; + + case CALL_TYPE_NOT_A_CALL: + default: + return; + } + + if (select_qp_encoding(format, contact->datetime, NULL)) { + snprintf(buf, sizeof(buf), "X-IRMC-CALL-DATETIME;%s", type); + vcard_qp_print_encoded(vcards, buf, contact->datetime, NULL); + return; + } + + vcard_printf(vcards, "X-IRMC-CALL-DATETIME;%s:%s", type, + contact->datetime); +} + +static void vcard_printf_end(GString *vcards) +{ + vcard_printf(vcards, "END:VCARD"); +} + +void phonebook_add_contact(GString *vcards, struct phonebook_contact *contact, + uint64_t filter, uint8_t format) +{ + if (format == FORMAT_VCARD30 && filter) + filter |= (FILTER_VERSION | FILTER_FN | FILTER_N | FILTER_TEL); + else if (format == FORMAT_VCARD21 && filter) + filter |= (FILTER_VERSION | FILTER_N | FILTER_TEL); + else + filter = (FILTER_VERSION | FILTER_UID | FILTER_N | FILTER_FN | + FILTER_TEL | FILTER_EMAIL | FILTER_ADR | + FILTER_BDAY | FILTER_NICKNAME | FILTER_URL | + FILTER_PHOTO | FILTER_ORG | FILTER_ROLE | + FILTER_TITLE | FILTER_X_IRMC_CALL_DATETIME); + + vcard_printf_begin(vcards, format); + + if (filter & FILTER_UID && *contact->uid) + vcard_printf_tag(vcards, format, "UID", NULL, contact->uid); + + if (filter & FILTER_N) + vcard_printf_name(vcards, format, contact); + + if (filter & FILTER_FN && (*contact->fullname || + format == FORMAT_VCARD30)) + vcard_printf_fullname(vcards, format, contact->fullname); + + if (filter & FILTER_TEL) { + GSList *l = contact->numbers; + + if (g_slist_length(l) == 0) + vcard_printf_number(vcards, format, NULL, 1, + TEL_TYPE_OTHER); + + for (; l; l = l->next) { + struct phonebook_field *number = l->data; + + vcard_printf_number(vcards, format, number->text, 1, + number->type); + } + } + + if (filter & FILTER_EMAIL) { + GSList *l = contact->emails; + + for (; l; l = l->next) { + struct phonebook_field *email = l->data; + vcard_printf_email(vcards, format, email->text, + email->type); + } + } + + if (filter & FILTER_ADR) { + GSList *l = contact->addresses; + + for (; l; l = l->next) { + struct phonebook_addr *addr = l->data; + vcard_printf_address(vcards, format, addr); + } + } + + if (filter & FILTER_BDAY && *contact->birthday) + vcard_printf_tag(vcards, format, "BDAY", NULL, + contact->birthday); + + if (filter & FILTER_NICKNAME && *contact->nickname) + vcard_printf_tag(vcards, format, "NICKNAME", NULL, + contact->nickname); + + if (filter & FILTER_URL) { + GSList *l = contact->urls; + + for (; l; l = l->next) { + struct phonebook_field *url = l->data; + vcard_printf_url(vcards, format, url->text, url->type); + } + } + + if (filter & FILTER_PHOTO && *contact->photo) + vcard_printf_tag(vcards, format, "PHOTO", NULL, + contact->photo); + + if (filter & FILTER_ORG) + vcard_printf_org(vcards, format, contact); + + if (filter & FILTER_ROLE && *contact->role) + vcard_printf_tag(vcards, format, "ROLE", NULL, contact->role); + + if (filter & FILTER_TITLE && *contact->title) + vcard_printf_tag(vcards, format, "TITLE", NULL, contact->title); + + if (filter & FILTER_X_IRMC_CALL_DATETIME) + vcard_printf_datetime(vcards, format, contact); + + vcard_printf_end(vcards); +} + +static void field_free(gpointer data) +{ + struct phonebook_field *field = data; + + g_free(field->text); + g_free(field); +} + +void phonebook_addr_free(gpointer addr) +{ + struct phonebook_addr *address = addr; + + g_slist_free_full(address->fields, g_free); + g_free(address); +} + +void phonebook_contact_free(struct phonebook_contact *contact) +{ + if (contact == NULL) + return; + + g_slist_free_full(contact->numbers, field_free); + g_slist_free_full(contact->emails, field_free); + g_slist_free_full(contact->addresses, phonebook_addr_free); + g_slist_free_full(contact->urls, field_free); + + g_free(contact->uid); + g_free(contact->fullname); + g_free(contact->given); + g_free(contact->family); + g_free(contact->additional); + g_free(contact->prefix); + g_free(contact->suffix); + g_free(contact->birthday); + g_free(contact->nickname); + g_free(contact->photo); + g_free(contact->company); + g_free(contact->department); + g_free(contact->role); + g_free(contact->title); + g_free(contact->datetime); + g_free(contact); +} diff --git a/obexd/plugins/vcard.h b/obexd/plugins/vcard.h new file mode 100644 index 0000000..22c3f68 --- /dev/null +++ b/obexd/plugins/vcard.h @@ -0,0 +1,81 @@ +/* + * OBEX Server + * + * Copyright (C) 2008-2010 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 + * + */ + +enum phonebook_number_type { + TEL_TYPE_HOME, + TEL_TYPE_MOBILE, + TEL_TYPE_FAX, + TEL_TYPE_WORK, + TEL_TYPE_OTHER, +}; + +enum phonebook_field_type { + FIELD_TYPE_HOME, + FIELD_TYPE_WORK, + FIELD_TYPE_OTHER, +}; + +enum phonebook_call_type { + CALL_TYPE_NOT_A_CALL, + CALL_TYPE_MISSED, + CALL_TYPE_INCOMING, + CALL_TYPE_OUTGOING, +}; + +struct phonebook_field { + char *text; + int type; +}; + +struct phonebook_addr { + GSList *fields; + int type; +}; + +struct phonebook_contact { + char *uid; + char *fullname; + char *given; + char *family; + char *additional; + GSList *numbers; + GSList *emails; + char *prefix; + char *suffix; + GSList *addresses; + char *birthday; + char *nickname; + GSList *urls; + char *photo; + char *company; + char *department; + char *role; + char *title; + char *datetime; + int calltype; +}; + +void phonebook_add_contact(GString *vcards, struct phonebook_contact *contact, + uint64_t filter, uint8_t format); + +void phonebook_contact_free(struct phonebook_contact *contact); + +void phonebook_addr_free(gpointer addr); diff --git a/obexd/src/genbuiltin b/obexd/src/genbuiltin new file mode 100755 index 0000000..39f7735 --- /dev/null +++ b/obexd/src/genbuiltin @@ -0,0 +1,17 @@ +#!/bin/sh + +for i in $* +do + echo "extern struct obex_plugin_desc __obex_builtin_$i;" +done + +echo +echo "static struct obex_plugin_desc *__obex_builtin[] = {" + +for i in $* +do + echo " &__obex_builtin_$i," +done + +echo " NULL" +echo "};" diff --git a/obexd/src/log.c b/obexd/src/log.c new file mode 100644 index 0000000..8e2ebaa --- /dev/null +++ b/obexd/src/log.c @@ -0,0 +1,137 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include + +#include "log.h" + +void info(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + vsyslog(LOG_INFO, format, ap); + + va_end(ap); +} + +void error(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + vsyslog(LOG_ERR, format, ap); + + va_end(ap); +} + +void obex_debug(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + + vsyslog(LOG_DEBUG, format, ap); + + va_end(ap); +} + +extern struct obex_debug_desc __start___debug[]; +extern struct obex_debug_desc __stop___debug[]; + +static char **enabled = NULL; + +static gboolean is_enabled(struct obex_debug_desc *desc) +{ + int i; + + if (enabled == NULL) + return 0; + + for (i = 0; enabled[i] != NULL; i++) { + if (desc->name != NULL && g_pattern_match_simple(enabled[i], + desc->name) == TRUE) + return 1; + if (desc->file != NULL && g_pattern_match_simple(enabled[i], + desc->file) == TRUE) + return 1; + } + + return 0; +} + +void __obex_log_enable_debug(void) +{ + struct obex_debug_desc *desc; + + for (desc = __start___debug; desc < __stop___debug; desc++) + desc->flags |= OBEX_DEBUG_FLAG_PRINT; +} + +void __obex_log_init(const char *debug, int detach) +{ + int option = LOG_NDELAY | LOG_PID; + struct obex_debug_desc *desc; + const char *name = NULL, *file = NULL; + + if (debug != NULL) + enabled = g_strsplit_set(debug, ":, ", 0); + + for (desc = __start___debug; desc < __stop___debug; desc++) { + if (file != NULL || name != NULL) { + if (g_strcmp0(desc->file, file) == 0) { + if (desc->name == NULL) + desc->name = name; + } else + file = NULL; + } + + if (is_enabled(desc)) + desc->flags |= OBEX_DEBUG_FLAG_PRINT; + } + + if (!detach) + option |= LOG_PERROR; + + openlog("obexd", option, LOG_DAEMON); + + info("OBEX daemon %s", VERSION); +} + +void __obex_log_cleanup(void) +{ + closelog(); + + g_strfreev(enabled); +} diff --git a/obexd/src/log.h b/obexd/src/log.h new file mode 100644 index 0000000..d9fb867 --- /dev/null +++ b/obexd/src/log.h @@ -0,0 +1,56 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void info(const char *format, ...) __attribute__((format(printf, 1, 2))); +void error(const char *format, ...) __attribute__((format(printf, 1, 2))); + +void obex_debug(const char *format, ...) __attribute__((format(printf, 1, 2))); + +void __obex_log_init(const char *debug, int detach); +void __obex_log_cleanup(void); +void __obex_log_enable_debug(void); + +struct obex_debug_desc { + const char *name; + const char *file; +#define OBEX_DEBUG_FLAG_DEFAULT (0) +#define OBEX_DEBUG_FLAG_PRINT (1 << 0) + unsigned int flags; +} __attribute__((aligned(8))); + +/** + * DBG: + * @fmt: format string + * @arg...: list of arguments + * + * Simple macro around debug() which also include the function + * name it is called in. + */ +#define DBG(fmt, arg...) do { \ + static struct obex_debug_desc __obex_debug_desc \ + __attribute__((used, section("__debug"), aligned(8))) = { \ + .file = __FILE__, .flags = OBEX_DEBUG_FLAG_DEFAULT, \ + }; \ + if (__obex_debug_desc.flags & OBEX_DEBUG_FLAG_PRINT) \ + obex_debug("%s:%s() " fmt, __FILE__, __func__ , ## arg); \ +} while (0) diff --git a/obexd/src/main.c b/obexd/src/main.c new file mode 100644 index 0000000..139f141 --- /dev/null +++ b/obexd/src/main.c @@ -0,0 +1,343 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gdbus/gdbus.h" + +#include "../client/manager.h" + +#include "log.h" +#include "obexd.h" +#include "server.h" + +#define DEFAULT_CAP_FILE CONFIGDIR "/capability.xml" + +static GMainLoop *main_loop = NULL; + +static gboolean signal_handler(GIOChannel *channel, GIOCondition cond, + gpointer user_data) +{ + static unsigned int __terminated = 0; + struct signalfd_siginfo si; + ssize_t result; + int fd; + + if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) + return FALSE; + + fd = g_io_channel_unix_get_fd(channel); + + result = read(fd, &si, sizeof(si)); + if (result != sizeof(si)) + return FALSE; + + switch (si.ssi_signo) { + case SIGINT: + case SIGTERM: + if (__terminated == 0) { + info("Terminating"); + g_main_loop_quit(main_loop); + } + + __terminated = 1; + break; + case SIGUSR2: + __obex_log_enable_debug(); + break; + } + + return TRUE; +} + +static guint setup_signalfd(void) +{ + GIOChannel *channel; + guint source; + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGUSR2); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + perror("Failed to set signal mask"); + return 0; + } + + fd = signalfd(-1, &mask, 0); + if (fd < 0) { + perror("Failed to create signal descriptor"); + return 0; + } + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + source = g_io_add_watch(channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + signal_handler, NULL); + + g_io_channel_unref(channel); + + return source; +} + +static gboolean option_detach = TRUE; +static char *option_debug = NULL; + +static char *option_root = NULL; +static char *option_root_setup = NULL; +static char *option_capability = NULL; +static char *option_plugin = NULL; +static char *option_noplugin = NULL; + +static gboolean option_autoaccept = FALSE; +static gboolean option_symlinks = FALSE; + +static gboolean parse_debug(const char *key, const char *value, + gpointer user_data, GError **error) +{ + if (value) + option_debug = g_strdup(value); + else + option_debug = g_strdup("*"); + + return TRUE; +} + +static GOptionEntry options[] = { + { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG, + G_OPTION_ARG_CALLBACK, parse_debug, + "Enable debug information output", "DEBUG" }, + { "plugin", 'p', 0, G_OPTION_ARG_STRING, &option_plugin, + "Specify plugins to load", "NAME,..." }, + { "noplugin", 'P', 0, G_OPTION_ARG_STRING, &option_noplugin, + "Specify plugins not to load", "NAME,..." }, + { "nodetach", 'n', G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, &option_detach, + "Run with logging in foreground" }, + { "root", 'r', 0, G_OPTION_ARG_STRING, &option_root, + "Specify root folder location. Both absolute " + "and relative can be used, but relative paths " + "are assumed to be relative to user $HOME " + "folder. Default $XDG_CACHE_HOME", "PATH" }, + { "root-setup", 'S', 0, G_OPTION_ARG_STRING, &option_root_setup, + "Root folder setup script", "SCRIPT" }, + { "symlinks", 'l', 0, G_OPTION_ARG_NONE, &option_symlinks, + "Allow symlinks leading outside of the root " + "folder" }, + { "capability", 'c', 0, G_OPTION_ARG_STRING, &option_capability, + "Specify capability file, use '!' mark for " + "scripts", "FILE" }, + { "auto-accept", 'a', 0, G_OPTION_ARG_NONE, &option_autoaccept, + "Automatically accept push requests" }, + { NULL }, +}; + +gboolean obex_option_auto_accept(void) +{ + return option_autoaccept; +} + +const char *obex_option_root_folder(void) +{ + return option_root; +} + +gboolean obex_option_symlinks(void) +{ + return option_symlinks; +} + +const char *obex_option_capability(void) +{ + return option_capability; +} + +static gboolean is_dir(const char *dir) +{ + struct stat st; + + if (stat(dir, &st) < 0) { + error("stat(%s): %s (%d)", dir, strerror(errno), errno); + return FALSE; + } + + return S_ISDIR(st.st_mode); +} + +static gboolean root_folder_setup(char *root, char *root_setup) +{ + int status; + char *argv[3] = { root_setup, root, NULL }; + + if (is_dir(root)) + return TRUE; + + if (root_setup == NULL) + return FALSE; + + DBG("Setting up %s using %s", root, root_setup); + + if (!g_spawn_sync(NULL, argv, NULL, 0, NULL, NULL, NULL, NULL, + &status, NULL)) { + error("Unable to execute %s", root_setup); + return FALSE; + } + + if (WEXITSTATUS(status) != EXIT_SUCCESS) { + error("%s exited with status %d", root_setup, + WEXITSTATUS(status)); + return FALSE; + } + + return is_dir(root); +} + +int main(int argc, char *argv[]) +{ + GOptionContext *context; + GError *err = NULL; + guint signal; + +#ifdef NEED_THREADS + if (g_thread_supported() == FALSE) + g_thread_init(NULL); +#endif + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) { + if (err != NULL) { + g_printerr("%s\n", err->message); + g_error_free(err); + } else + g_printerr("An unknown error occurred\n"); + exit(EXIT_FAILURE); + } + + g_option_context_free(context); + + __obex_log_init(option_debug, option_detach); + + DBG("Entering main loop"); + + main_loop = g_main_loop_new(NULL, FALSE); + + signal = setup_signalfd(); + +#ifdef NEED_THREADS + if (dbus_threads_init_default() == FALSE) { + fprintf(stderr, "Can't init usage of threads\n"); + exit(EXIT_FAILURE); + } +#endif + + if (manager_init() == FALSE) { + error("manager_init failed"); + exit(EXIT_FAILURE); + } + + if (option_root == NULL) { + option_root = g_build_filename(g_get_user_cache_dir(), "obexd", + NULL); + g_mkdir_with_parents(option_root, 0700); + } + + if (option_root[0] != '/') { + const char *home = getenv("HOME"); + if (home) { + char *old_root = option_root; + option_root = g_strdup_printf("%s/%s", home, old_root); + g_free(old_root); + } + } + + if (option_capability == NULL) + option_capability = g_strdup(DEFAULT_CAP_FILE); + + plugin_init(option_plugin, option_noplugin); + + if (obex_server_init() < 0) { + error("obex_server_init failed"); + exit(EXIT_FAILURE); + } + + if (!root_folder_setup(option_root, option_root_setup)) { + error("Unable to setup root folder %s", option_root); + exit(EXIT_FAILURE); + } + + if (client_manager_init() < 0) { + error("client_manager_init failed"); + exit(EXIT_FAILURE); + } + + g_main_loop_run(main_loop); + + g_source_remove(signal); + + client_manager_exit(); + + obex_server_exit(); + + plugin_cleanup(); + + manager_cleanup(); + + g_main_loop_unref(main_loop); + + g_free(option_capability); + g_free(option_root); + + __obex_log_cleanup(); + + return 0; +} diff --git a/obexd/src/manager.c b/obexd/src/manager.c new file mode 100644 index 0000000..a4af627 --- /dev/null +++ b/obexd/src/manager.c @@ -0,0 +1,808 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "gdbus/gdbus.h" +#include "gobex/gobex.h" + +#include "btio/btio.h" +#include "obexd.h" +#include "obex.h" +#include "obex-priv.h" +#include "server.h" +#include "manager.h" +#include "log.h" +#include "service.h" + +#define OBEX_BASE_PATH "/org/bluez/obex" +#define SESSION_BASE_PATH OBEX_BASE_PATH "/server" +#define OBEX_MANAGER_INTERFACE OBEXD_SERVICE ".AgentManager1" +#define ERROR_INTERFACE OBEXD_SERVICE ".Error" +#define TRANSFER_INTERFACE OBEXD_SERVICE ".Transfer1" +#define SESSION_INTERFACE OBEXD_SERVICE ".Session1" +#define AGENT_INTERFACE OBEXD_SERVICE ".Agent1" + +#define TIMEOUT 60*1000 /* Timeout for user response (miliseconds) */ + +struct agent { + char *bus_name; + char *path; + gboolean auth_pending; + char *new_name; + char *new_folder; + unsigned int watch_id; +}; + +enum { + TRANSFER_STATUS_QUEUED = 0, + TRANSFER_STATUS_ACTIVE, + TRANSFER_STATUS_COMPLETE, + TRANSFER_STATUS_ERROR +}; + +struct obex_transfer { + uint8_t status; + char *path; + struct obex_session *session; +}; + +static struct agent *agent = NULL; + +static DBusConnection *connection = NULL; + +static void agent_free(struct agent *agent) +{ + if (!agent) + return; + + g_free(agent->new_folder); + g_free(agent->new_name); + g_free(agent->bus_name); + g_free(agent->path); + g_free(agent); +} + +static inline DBusMessage *invalid_args(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); +} + +static inline DBusMessage *not_supported(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotSupported", + "Operation is not supported"); +} + +static inline DBusMessage *agent_already_exists(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".AlreadyExists", + "Agent already exists"); +} + +static inline DBusMessage *agent_does_not_exist(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".DoesNotExist", + "Agent does not exist"); +} + +static inline DBusMessage *not_authorized(DBusMessage *msg) +{ + return g_dbus_create_error(msg, + ERROR_INTERFACE ".NotAuthorized", + "Not authorized"); +} + +static void agent_disconnected(DBusConnection *conn, void *user_data) +{ + DBG("Agent exited"); + agent_free(agent); + agent = NULL; +} + +static DBusMessage *register_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path, *sender; + + if (agent) + return agent_already_exists(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + sender = dbus_message_get_sender(msg); + agent = g_new0(struct agent, 1); + agent->bus_name = g_strdup(sender); + agent->path = g_strdup(path); + + agent->watch_id = g_dbus_add_disconnect_watch(conn, sender, + agent_disconnected, NULL, NULL); + + DBG("Agent registered"); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_agent(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + const char *path, *sender; + + if (!agent) + return agent_does_not_exist(msg); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return invalid_args(msg); + + if (strcmp(agent->path, path) != 0) + return agent_does_not_exist(msg); + + sender = dbus_message_get_sender(msg); + if (strcmp(agent->bus_name, sender) != 0) + return not_authorized(msg); + + g_dbus_remove_watch(conn, agent->watch_id); + + agent_free(agent); + agent = NULL; + + DBG("Agent unregistered"); + + return dbus_message_new_method_return(msg); +} + +static gboolean get_source(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_session *os = data; + char *s; + + s = os->src; + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &s); + + return TRUE; +} + +static gboolean get_destination(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_session *os = data; + char *s; + + s = os->dst; + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &s); + + return TRUE; +} + +static gboolean session_target_exists(const GDBusPropertyTable *property, + void *data) +{ + struct obex_session *os = data; + + return os->service->target ? TRUE : FALSE; +} + +static char *target2str(const uint8_t *t) +{ + if (!t) + return NULL; + + return g_strdup_printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-" + "%02X%02X-%02X%02X%02X%02X%02X%02X", + t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], + t[8], t[9], t[10], t[11], t[12], t[13], t[14], + t[15]); +} + +static gboolean get_target(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_session *os = data; + char *uuid; + + uuid = target2str(os->service->target); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); + g_free(uuid); + + return TRUE; +} + +static gboolean get_root(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + const char *root; + + root = obex_option_root_folder(); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &root); + + return TRUE; +} + +static DBusMessage *transfer_cancel(DBusConnection *connection, + DBusMessage *msg, void *user_data) +{ + struct obex_transfer *transfer = user_data; + struct obex_session *os = transfer->session; + const char *sender; + + if (!agent) + return agent_does_not_exist(msg); + + if (!os) + return invalid_args(msg); + + sender = dbus_message_get_sender(msg); + if (strcmp(agent->bus_name, sender) != 0) + return not_authorized(msg); + + os->aborted = TRUE; + + return dbus_message_new_method_return(msg); +} + +static const char *status2str(uint8_t status) +{ + switch (status) { + case TRANSFER_STATUS_QUEUED: + return "queued"; + case TRANSFER_STATUS_ACTIVE: + return "active"; + case TRANSFER_STATUS_COMPLETE: + return "complete"; + case TRANSFER_STATUS_ERROR: + default: + return "error"; + } +} + +static gboolean transfer_get_status(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_transfer *transfer = data; + const char *status = status2str(transfer->status); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &status); + + return TRUE; +} + +static gboolean transfer_get_session(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + char *path; + + if (session == NULL) + return FALSE; + + path = g_strdup_printf("%s/session%u", SESSION_BASE_PATH, session->id); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + g_free(path); + + return TRUE; +} + +static gboolean transfer_name_exists(const GDBusPropertyTable *property, + void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + + return session->name != NULL; +} + +static gboolean transfer_get_name(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + + if (session->name == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->name); + + return TRUE; +} + +static gboolean transfer_type_exists(const GDBusPropertyTable *property, + void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + + return session->type != NULL; +} + +static gboolean transfer_get_type(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + + if (session->type == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->type); + + return TRUE; +} + +static gboolean transfer_size_exists(const GDBusPropertyTable *property, + void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + + return (session->size != OBJECT_SIZE_UNKNOWN && + session->size != OBJECT_SIZE_DELETE); +} + +static gboolean transfer_get_size(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + + if (session->size == OBJECT_SIZE_UNKNOWN || + session->size == OBJECT_SIZE_DELETE) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &session->size); + + return TRUE; +} + +static gboolean transfer_time_exists(const GDBusPropertyTable *property, + void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + + return session->time != 0; +} + +static gboolean transfer_get_time(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + dbus_uint64_t time_u64; + + if (session->size == 0) + return FALSE; + + time_u64 = session->time; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, &time_u64); + + return TRUE; +} + +static gboolean transfer_filename_exists(const GDBusPropertyTable *property, + void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + + return session->path != NULL; +} + +static gboolean transfer_get_filename(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + + if (session->path == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &session->path); + + return TRUE; +} + +static gboolean transfer_get_transferred(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct obex_transfer *transfer = data; + struct obex_session *session = transfer->session; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT64, + &session->offset); + + return TRUE; +} + +static const GDBusMethodTable manager_methods[] = { + { GDBUS_METHOD("RegisterAgent", + GDBUS_ARGS({ "agent", "o" }), NULL, register_agent) }, + { GDBUS_METHOD("UnregisterAgent", + GDBUS_ARGS({ "agent", "o" }), NULL, unregister_agent) }, + { } +}; + +static const GDBusMethodTable transfer_methods[] = { + { GDBUS_METHOD("Cancel", NULL, NULL, transfer_cancel) }, + { } +}; + +static const GDBusPropertyTable transfer_properties[] = { + { "Status", "s", transfer_get_status }, + { "Session", "o", transfer_get_session }, + { "Name", "s", transfer_get_name, NULL, transfer_name_exists }, + { "Type", "s", transfer_get_type, NULL, transfer_type_exists }, + { "Size", "t", transfer_get_size, NULL, transfer_size_exists }, + { "Time", "t", transfer_get_time, NULL, transfer_time_exists }, + { "Filename", "s", transfer_get_filename, NULL, + transfer_filename_exists }, + { "Transferred", "t", transfer_get_transferred }, + { } +}; + +static const GDBusPropertyTable session_properties[] = { + { "Source", "s", get_source }, + { "Destination", "s", get_destination }, + { "Target", "s", get_target, NULL, session_target_exists }, + { "Root", "s", get_root }, + { } +}; + +gboolean manager_init(void) +{ + DBusError err; + + DBG(""); + + dbus_error_init(&err); + + connection = g_dbus_setup_bus(DBUS_BUS_SESSION, OBEXD_SERVICE, &err); + if (connection == NULL) { + if (dbus_error_is_set(&err) == TRUE) { + fprintf(stderr, "%s\n", err.message); + dbus_error_free(&err); + } else + fprintf(stderr, "Can't register with session bus\n"); + return FALSE; + } + + g_dbus_attach_object_manager(connection); + + return g_dbus_register_interface(connection, OBEX_BASE_PATH, + OBEX_MANAGER_INTERFACE, + manager_methods, NULL, NULL, + NULL, NULL); +} + +void manager_cleanup(void) +{ + DBG(""); + + g_dbus_unregister_interface(connection, OBEX_BASE_PATH, + OBEX_MANAGER_INTERFACE); + + /* FIXME: Release agent? */ + + agent_free(agent); + + g_dbus_detach_object_manager(connection); + + dbus_connection_unref(connection); +} + +void manager_emit_transfer_property(struct obex_transfer *transfer, + char *name) +{ + if (!transfer->path) + return; + + g_dbus_emit_property_changed(connection, transfer->path, + TRANSFER_INTERFACE, name); +} + +void manager_emit_transfer_started(struct obex_transfer *transfer) +{ + transfer->status = TRANSFER_STATUS_ACTIVE; + + manager_emit_transfer_property(transfer, "Status"); +} + +static void emit_transfer_completed(struct obex_transfer *transfer, + gboolean success) +{ + if (transfer->path == NULL) + return; + + transfer->status = success ? TRANSFER_STATUS_COMPLETE : + TRANSFER_STATUS_ERROR; + + manager_emit_transfer_property(transfer, "Status"); +} + +static void transfer_free(struct obex_transfer *transfer) +{ + g_free(transfer->path); + g_free(transfer); +} + +struct obex_transfer *manager_register_transfer(struct obex_session *os) +{ + struct obex_transfer *transfer; + static unsigned int id = 0; + + transfer = g_new0(struct obex_transfer, 1); + transfer->path = g_strdup_printf("%s/session%u/transfer%u", + SESSION_BASE_PATH, os->id, id++); + transfer->session = os; + + if (!g_dbus_register_interface(connection, transfer->path, + TRANSFER_INTERFACE, + transfer_methods, NULL, + transfer_properties, transfer, NULL)) { + error("Cannot register Transfer interface."); + transfer_free(transfer); + return NULL; + } + + return transfer; +} + +void manager_unregister_transfer(struct obex_transfer *transfer) +{ + struct obex_session *os; + + if (transfer == NULL) + return; + + os = transfer->session; + + if (transfer->status == TRANSFER_STATUS_ACTIVE) + emit_transfer_completed(transfer, os->offset == os->size); + + g_dbus_unregister_interface(connection, transfer->path, + TRANSFER_INTERFACE); + + transfer_free(transfer); +} + +static void agent_cancel(void) +{ + DBusMessage *msg; + + if (agent == NULL) + return; + + msg = dbus_message_new_method_call(agent->bus_name, agent->path, + AGENT_INTERFACE, "Cancel"); + + g_dbus_send_message(connection, msg); +} + +static void agent_reply(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply = dbus_pending_call_steal_reply(call); + const char *name; + DBusError derr; + gboolean *got_reply = user_data; + + *got_reply = TRUE; + + /* Received a reply after the agent exited */ + if (!agent) + return; + + agent->auth_pending = FALSE; + + dbus_error_init(&derr); + if (dbus_set_error_from_message(&derr, reply)) { + error("Agent replied with an error: %s, %s", + derr.name, derr.message); + + if (dbus_error_has_name(&derr, DBUS_ERROR_NO_REPLY)) + agent_cancel(); + + dbus_error_free(&derr); + dbus_message_unref(reply); + return; + } + + if (dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + /* Splits folder and name */ + const char *slash = strrchr(name, '/'); + DBG("Agent replied with %s", name); + if (!slash) { + agent->new_name = g_strdup(name); + agent->new_folder = NULL; + } else { + agent->new_name = g_strdup(slash + 1); + agent->new_folder = g_strndup(name, slash - name); + } + } + + dbus_message_unref(reply); +} + +static gboolean auth_error(GIOChannel *io, GIOCondition cond, void *user_data) +{ + agent->auth_pending = FALSE; + + return FALSE; +} + +int manager_request_authorization(struct obex_transfer *transfer, + char **new_folder, char **new_name) +{ + struct obex_session *os = transfer->session; + DBusMessage *msg; + DBusPendingCall *call; + unsigned int watch; + gboolean got_reply; + + if (!agent) + return -1; + + if (agent->auth_pending) + return -EPERM; + + if (!new_folder || !new_name) + return -EINVAL; + + msg = dbus_message_new_method_call(agent->bus_name, agent->path, + AGENT_INTERFACE, + "AuthorizePush"); + + dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &transfer->path, + DBUS_TYPE_INVALID); + + if (!g_dbus_send_message_with_reply(connection, msg, &call, TIMEOUT)) { + dbus_message_unref(msg); + return -EPERM; + } + + dbus_message_unref(msg); + + agent->auth_pending = TRUE; + got_reply = FALSE; + + /* Catches errors before authorization response comes */ + watch = g_io_add_watch_full(os->io, G_PRIORITY_DEFAULT, + G_IO_HUP | G_IO_ERR | G_IO_NVAL, + auth_error, NULL, NULL); + + dbus_pending_call_set_notify(call, agent_reply, &got_reply, NULL); + + /* Workaround: process events while agent doesn't reply */ + while (agent && agent->auth_pending) + g_main_context_iteration(NULL, TRUE); + + g_source_remove(watch); + + if (!got_reply) { + dbus_pending_call_cancel(call); + agent_cancel(); + } + + dbus_pending_call_unref(call); + + if (!agent || !agent->new_name) + return -EPERM; + + *new_folder = agent->new_folder; + *new_name = agent->new_name; + agent->new_folder = NULL; + agent->new_name = NULL; + + return 0; +} + +static DBusMessage *session_get_capabilities(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + return not_supported(message); +} + +static const GDBusMethodTable session_methods[] = { + { GDBUS_ASYNC_METHOD("GetCapabilities", + NULL, GDBUS_ARGS({ "capabilities", "s" }), + session_get_capabilities) }, + { } +}; + +void manager_register_session(struct obex_session *os) +{ + char *path; + + path = g_strdup_printf("%s/session%u", SESSION_BASE_PATH, os->id); + + if (!g_dbus_register_interface(connection, path, + SESSION_INTERFACE, + session_methods, NULL, + session_properties, os, NULL)) + error("Cannot register Session interface."); + + g_free(path); +} + +void manager_unregister_session(struct obex_session *os) +{ + char *path; + + path = g_strdup_printf("%s/session%u", SESSION_BASE_PATH, os->id); + + g_dbus_unregister_interface(connection, path, SESSION_INTERFACE); + + g_free(path); +} + +void manager_emit_transfer_progress(struct obex_transfer *transfer) +{ + manager_emit_transfer_property(transfer, "Transferred"); +} + +void manager_emit_transfer_completed(struct obex_transfer *transfer) +{ + struct obex_session *session; + + if (transfer == NULL) + return; + + session = transfer->session; + + if (session == NULL || session->object == NULL) + return; + + emit_transfer_completed(transfer, !session->aborted); +} + +DBusConnection *manager_dbus_get_connection(void) +{ + if (connection == NULL) + return NULL; + + return dbus_connection_ref(connection); +} diff --git a/obexd/src/manager.h b/obexd/src/manager.h new file mode 100644 index 0000000..d709af5 --- /dev/null +++ b/obexd/src/manager.h @@ -0,0 +1,44 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#define OBEXD_SERVICE "org.bluez.obex" + +struct obex_session; +struct obex_transfer; + +void manager_register_session(struct obex_session *os); +void manager_unregister_session(struct obex_session *os); + +struct obex_transfer *manager_register_transfer(struct obex_session *os); +void manager_unregister_transfer(struct obex_transfer *transfer); +void manager_emit_transfer_property(struct obex_transfer *transfer, + char *name); +void manager_emit_transfer_started(struct obex_transfer *transfer); +void manager_emit_transfer_progress(struct obex_transfer *transfer); +void manager_emit_transfer_completed(struct obex_transfer *transfer); +int manager_request_authorization(struct obex_transfer *transfer, + char **new_folder, char **new_name); + +DBusConnection *manager_dbus_get_connection(void); diff --git a/obexd/src/map_ap.h b/obexd/src/map_ap.h new file mode 100644 index 0000000..da108fe --- /dev/null +++ b/obexd/src/map_ap.h @@ -0,0 +1,51 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2010-2011 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* List of OBEX application parameters tags as per MAP specification. */ +enum map_ap_tag { + MAP_AP_MAXLISTCOUNT = 0x01, /* uint16_t */ + MAP_AP_STARTOFFSET = 0x02, /* uint16_t */ + MAP_AP_FILTERMESSAGETYPE = 0x03, /* uint8_t */ + MAP_AP_FILTERPERIODBEGIN = 0x04, /* char * */ + MAP_AP_FILTERPERIODEND = 0x05, /* char * */ + MAP_AP_FILTERREADSTATUS = 0x06, /* uint8_t */ + MAP_AP_FILTERRECIPIENT = 0x07, /* char * */ + MAP_AP_FILTERORIGINATOR = 0x08, /* char * */ + MAP_AP_FILTERPRIORITY = 0x09, /* uint8_t */ + MAP_AP_ATTACHMENT = 0x0A, /* uint8_t */ + MAP_AP_TRANSPARENT = 0x0B, /* uint8_t */ + MAP_AP_RETRY = 0x0C, /* uint8_t */ + MAP_AP_NEWMESSAGE = 0x0D, /* uint8_t */ + MAP_AP_NOTIFICATIONSTATUS = 0x0E, /* uint8_t */ + MAP_AP_MASINSTANCEID = 0x0F, /* uint8_t */ + MAP_AP_PARAMETERMASK = 0x10, /* uint32_t */ + MAP_AP_FOLDERLISTINGSIZE = 0x11, /* uint16_t */ + MAP_AP_MESSAGESLISTINGSIZE = 0x12, /* uint16_t */ + MAP_AP_SUBJECTLENGTH = 0x13, /* uint8_t */ + MAP_AP_CHARSET = 0x14, /* uint8_t */ + MAP_AP_FRACTIONREQUEST = 0x15, /* uint8_t */ + MAP_AP_FRACTIONDELIVER = 0x16, /* uint8_t */ + MAP_AP_STATUSINDICATOR = 0x17, /* uint8_t */ + MAP_AP_STATUSVALUE = 0x18, /* uint8_t */ + MAP_AP_MSETIME = 0x19, /* char * */ +}; diff --git a/obexd/src/mimetype.c b/obexd/src/mimetype.c new file mode 100644 index 0000000..c8b040a --- /dev/null +++ b/obexd/src/mimetype.c @@ -0,0 +1,217 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#include "log.h" +#include "obex.h" +#include "mimetype.h" + +static GSList *drivers = NULL; + +static GSList *watches = NULL; + +struct io_watch { + void *object; + obex_object_io_func func; + void *user_data; +}; + +void obex_object_set_io_flags(void *object, int flags, int err) +{ + GSList *l; + + for (l = watches; l;) { + struct io_watch *watch = l->data; + + l = l->next; + + if (watch->object != object) + continue; + + if (watch->func(object, flags, err, watch->user_data) == TRUE) + continue; + + if (g_slist_find(watches, watch) == NULL) + continue; + + watches = g_slist_remove(watches, watch); + g_free(watch); + } +} + +static struct io_watch *find_io_watch(void *object) +{ + GSList *l; + + for (l = watches; l; l = l->next) { + struct io_watch *watch = l->data; + + if (watch->object == object) + return watch; + } + + return NULL; +} + +static void reset_io_watch(void *object) +{ + struct io_watch *watch; + + watch = find_io_watch(object); + if (watch == NULL) + return; + + watches = g_slist_remove(watches, watch); + g_free(watch); +} + +static int set_io_watch(void *object, obex_object_io_func func, + void *user_data) +{ + struct io_watch *watch; + + if (func == NULL) { + reset_io_watch(object); + return 0; + } + + watch = find_io_watch(object); + if (watch) + return -EPERM; + + watch = g_new0(struct io_watch, 1); + watch->object = object; + watch->func = func; + watch->user_data = user_data; + + watches = g_slist_append(watches, watch); + + return 0; +} + +static struct obex_mime_type_driver *find_driver(const uint8_t *target, + unsigned int target_size, + const char *mimetype, const uint8_t *who, + unsigned int who_size) +{ + GSList *l; + + for (l = drivers; l; l = l->next) { + struct obex_mime_type_driver *driver = l->data; + + if (memncmp0(target, target_size, driver->target, driver->target_size)) + continue; + + if (memncmp0(who, who_size, driver->who, driver->who_size)) + continue; + + if (mimetype == NULL || driver->mimetype == NULL) { + if (mimetype == driver->mimetype) + return driver; + else + continue; + } + + if (g_ascii_strcasecmp(mimetype, driver->mimetype) == 0) + return driver; + } + + return NULL; +} + +struct obex_mime_type_driver *obex_mime_type_driver_find(const uint8_t *target, + unsigned int target_size, + const char *mimetype, const uint8_t *who, + unsigned int who_size) +{ + struct obex_mime_type_driver *driver; + + driver = find_driver(target, target_size, mimetype, who, who_size); + if (driver == NULL) { + if (who != NULL) { + /* Fallback to non-who specific */ + driver = find_driver(target, target_size, mimetype, NULL, 0); + if (driver != NULL) + return driver; + } + + if (mimetype != NULL) + /* Fallback to target default */ + driver = find_driver(target, target_size, NULL, NULL, 0); + + if (driver == NULL) + /* Fallback to general default */ + driver = find_driver(NULL, 0, NULL, NULL, 0); + } + + return driver; +} + +int obex_mime_type_driver_register(struct obex_mime_type_driver *driver) +{ + if (!driver) { + error("Invalid driver"); + return -EINVAL; + } + + if (find_driver(driver->target, driver->target_size, driver->mimetype, + driver->who, driver->who_size)) { + error("Permission denied: %s could not be registered", + driver->mimetype); + return -EPERM; + } + + if (driver->set_io_watch == NULL) + driver->set_io_watch = set_io_watch; + + DBG("driver %p mimetype %s registered", driver, driver->mimetype); + + drivers = g_slist_append(drivers, driver); + + return 0; +} + +void obex_mime_type_driver_unregister(struct obex_mime_type_driver *driver) +{ + if (!g_slist_find(drivers, driver)) { + error("Unable to unregister: No such driver %p", driver); + return; + } + + DBG("driver %p mimetype %s unregistered", driver, driver->mimetype); + + drivers = g_slist_remove(drivers, driver); +} diff --git a/obexd/src/mimetype.h b/obexd/src/mimetype.h new file mode 100644 index 0000000..79529b8 --- /dev/null +++ b/obexd/src/mimetype.h @@ -0,0 +1,55 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +typedef gboolean (*obex_object_io_func) (void *object, int flags, int err, + void *user_data); + +struct obex_mime_type_driver { + const uint8_t *target; + unsigned int target_size; + const char *mimetype; + const uint8_t *who; + unsigned int who_size; + void *(*open) (const char *name, int oflag, mode_t mode, + void *driver_data, size_t *size, int *err); + int (*close) (void *object); + ssize_t (*get_next_header)(void *object, void *buf, size_t mtu, + uint8_t *hi); + ssize_t (*read) (void *object, void *buf, size_t count); + ssize_t (*write) (void *object, const void *buf, size_t count); + int (*flush) (void *object); + int (*copy) (const char *name, const char *destname); + int (*move) (const char *name, const char *destname); + int (*remove) (const char *name); + int (*set_io_watch) (void *object, obex_object_io_func func, + void *user_data); +}; + +int obex_mime_type_driver_register(struct obex_mime_type_driver *driver); +void obex_mime_type_driver_unregister(struct obex_mime_type_driver *driver); +struct obex_mime_type_driver *obex_mime_type_driver_find(const uint8_t *target, + unsigned int target_size, + const char *mimetype, const uint8_t *who, + unsigned int who_size); + +void obex_object_set_io_flags(void *object, int flags, int err); diff --git a/obexd/src/obex-priv.h b/obexd/src/obex-priv.h new file mode 100644 index 0000000..355a7f8 --- /dev/null +++ b/obexd/src/obex-priv.h @@ -0,0 +1,59 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Nokia Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct obex_session { + GIOChannel *io; + uint32_t id; + uint8_t cmd; + uint8_t action_id; + char *src; + char *dst; + char *name; + char *destname; + char *type; + char *path; + time_t time; + uint8_t *apparam; + size_t apparam_len; + const void *nonhdr; + size_t nonhdr_len; + guint get_rsp; + uint8_t *buf; + int64_t pending; + int64_t offset; + int64_t size; + void *object; + gboolean aborted; + int err; + struct obex_service_driver *service; + void *service_data; + struct obex_server *server; + gboolean checked; + GObex *obex; + struct obex_mime_type_driver *driver; + gboolean headers_sent; +}; + +int obex_session_start(GIOChannel *io, uint16_t tx_mtu, uint16_t rx_mtu, + gboolean stream, struct obex_server *server); diff --git a/obexd/src/obex.c b/obexd/src/obex.c new file mode 100644 index 0000000..2850d3a --- /dev/null +++ b/obexd/src/obex.c @@ -0,0 +1,1130 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Nokia Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gobex/gobex.h" + +#include "btio/btio.h" +#include "obexd.h" +#include "log.h" +#include "obex.h" +#include "obex-priv.h" +#include "server.h" +#include "manager.h" +#include "mimetype.h" +#include "service.h" +#include "transport.h" + +typedef struct { + uint8_t version; + uint8_t flags; + uint16_t mtu; +} __attribute__ ((packed)) obex_connect_hdr_t; + +struct auth_header { + uint8_t tag; + uint8_t len; + uint8_t val[0]; +} __attribute__ ((packed)); + +/* Possible commands */ +static struct { + int cmd; + const char *name; +} obex_command[] = { + { G_OBEX_OP_CONNECT, "CONNECT" }, + { G_OBEX_OP_DISCONNECT, "DISCONNECT" }, + { G_OBEX_OP_PUT, "PUT" }, + { G_OBEX_OP_GET, "GET" }, + { G_OBEX_OP_SETPATH, "SETPATH" }, + { G_OBEX_OP_SESSION, "SESSION" }, + { G_OBEX_OP_ABORT, "ABORT" }, + { G_OBEX_OP_ACTION, "ACTION" }, + { 0xFF, NULL }, +}; + +static gboolean handle_async_io(void *object, int flags, int err, + void *user_data); + +static void print_event(int cmd, uint8_t rsp) +{ + const char *cmdstr = NULL; + int i; + static int lastcmd; + + if (cmd < 0) + cmd = lastcmd; + else + lastcmd = cmd; + + for (i = 0; obex_command[i].cmd != 0xFF; i++) { + if (obex_command[i].cmd != cmd) + continue; + cmdstr = obex_command[i].name; + } + + obex_debug("%s(0x%x), %s(0x%x)", cmdstr, cmd, + g_obex_strerror(rsp), rsp); +} + +static void os_set_response(struct obex_session *os, int err) +{ + uint8_t rsp; + + rsp = g_obex_errno_to_rsp(err); + + print_event(-1, rsp); + + g_obex_send_rsp(os->obex, rsp, NULL, G_OBEX_HDR_INVALID); +} + +static void os_session_mark_aborted(struct obex_session *os) +{ + /* the session was already cancelled/aborted or size in unknown */ + if (os->aborted || os->size == OBJECT_SIZE_UNKNOWN) + return; + + os->aborted = (os->size != os->offset); +} + +static void os_reset_session(struct obex_session *os) +{ + os_session_mark_aborted(os); + + if (os->object) { + os->driver->set_io_watch(os->object, NULL, NULL); + os->driver->close(os->object); + if (os->aborted && os->cmd == G_OBEX_OP_PUT && os->path && + os->driver->remove) + os->driver->remove(os->path); + } + + if (os->service && os->service->reset) + os->service->reset(os, os->service_data); + + if (os->name) { + g_free(os->name); + os->name = NULL; + } + if (os->type) { + g_free(os->type); + os->type = NULL; + } + if (os->buf) { + g_free(os->buf); + os->buf = NULL; + } + if (os->path) { + g_free(os->path); + os->path = NULL; + } + if (os->apparam) { + g_free(os->apparam); + os->apparam = NULL; + os->apparam_len = 0; + } + + if (os->get_rsp > 0) { + g_obex_remove_request_function(os->obex, os->get_rsp); + os->get_rsp = 0; + } + + os->object = NULL; + os->driver = NULL; + os->aborted = FALSE; + os->pending = 0; + os->offset = 0; + os->size = OBJECT_SIZE_DELETE; + os->headers_sent = FALSE; + os->checked = FALSE; +} + +static void obex_session_free(struct obex_session *os) +{ + if (os->io) { + g_io_channel_shutdown(os->io, TRUE, NULL); + g_io_channel_unref(os->io); + } + + if (os->obex) + g_obex_unref(os->obex); + + g_free(os->src); + g_free(os->dst); + + g_free(os); +} + +/* From Imendio's GnomeVFS OBEX module (om-utils.c) */ +static time_t parse_iso8610(const char *val, int size) +{ + time_t time, tz_offset = 0; + struct tm tm; + char *date; + char tz; + int nr; + + memset(&tm, 0, sizeof(tm)); + /* According to spec the time doesn't have to be null terminated */ + date = g_strndup(val, size); + nr = sscanf(date, "%04u%02u%02uT%02u%02u%02u%c", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec, + &tz); + g_free(date); + if (nr < 6) { + /* Invalid time format */ + return -1; + } + + tm.tm_year -= 1900; /* Year since 1900 */ + tm.tm_mon--; /* Months since January, values 0-11 */ + tm.tm_isdst = -1; /* Daylight savings information not avail */ + +#if defined(HAVE_TM_GMTOFF) + tz_offset = tm.tm_gmtoff; +#elif defined(HAVE_TIMEZONE) + tz_offset = -timezone; + if (tm.tm_isdst > 0) + tz_offset += 3600; +#endif + + time = mktime(&tm); + if (nr == 7) { + /* + * Date/Time was in localtime (to remote device) + * already. Since we don't know anything about the + * timezone on that one we won't try to apply UTC offset + */ + time += tz_offset; + } + + return time; +} + +static void parse_service(struct obex_session *os, GObexPacket *req) +{ + GObexHeader *hdr; + const guint8 *target = NULL, *who = NULL; + gsize target_size = 0, who_size = 0; + + hdr = g_obex_packet_get_header(req, G_OBEX_HDR_WHO); + if (hdr == NULL) + goto target; + + g_obex_header_get_bytes(hdr, &who, &who_size); + +target: + hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TARGET); + if (hdr == NULL) + goto probe; + + g_obex_header_get_bytes(hdr, &target, &target_size); + +probe: + os->service = obex_service_driver_find(os->server->drivers, + target, target_size, + who, who_size); +} + +static void cmd_connect(GObex *obex, GObexPacket *req, void *user_data) +{ + struct obex_session *os = user_data; + GObexPacket *rsp; + GObexHeader *hdr; + int err; + + DBG(""); + + print_event(G_OBEX_OP_CONNECT, -1); + + parse_service(os, req); + + if (os->service == NULL || os->service->connect == NULL) { + error("Connect attempt to a non-supported target"); + os_set_response(os, -EPERM); + return; + } + + DBG("Selected driver: %s", os->service->name); + + os->service_data = os->service->connect(os, &err); + if (err < 0) { + os_set_response(os, err); + return; + } + + os->cmd = G_OBEX_OP_CONNECT; + + rsp = g_obex_packet_new(G_OBEX_RSP_SUCCESS, TRUE, G_OBEX_HDR_INVALID); + + if (os->service->target) { + hdr = g_obex_header_new_bytes(G_OBEX_HDR_WHO, + os->service->target, + os->service->target_size); + g_obex_packet_add_header(rsp, hdr); + } + + g_obex_send(obex, rsp, NULL); + + print_event(-1, 0); +} + +static void cmd_disconnect(GObex *obex, GObexPacket *req, void *user_data) +{ + struct obex_session *os = user_data; + + DBG("session %p", os); + + print_event(G_OBEX_OP_DISCONNECT, -1); + + os->cmd = G_OBEX_OP_DISCONNECT; + + os_set_response(os, 0); +} + +static ssize_t driver_write(struct obex_session *os) +{ + ssize_t len = 0; + + while (os->pending > 0) { + ssize_t w; + + w = os->driver->write(os->object, os->buf + len, os->pending); + if (w < 0) { + error("write(): %s (%zd)", strerror(-w), -w); + if (w == -EINTR) + continue; + else if (w == -EINVAL) + memmove(os->buf, os->buf + len, os->pending); + + return w; + } + + len += w; + os->offset += w; + os->pending -= w; + } + + DBG("%zd written", len); + + if (os->service->progress != NULL) + os->service->progress(os, os->service_data); + + return len; +} + +static gssize driver_read(struct obex_session *os, void *buf, gsize size) +{ + gssize len; + + if (os->object == NULL) + return -EIO; + + if (os->service->progress != NULL) + os->service->progress(os, os->service_data); + + len = os->driver->read(os->object, buf, size); + if (len < 0) { + error("read(): %s (%zd)", strerror(-len), -len); + if (len == -ENOSTR) + return 0; + if (len == -EAGAIN) + os->driver->set_io_watch(os->object, handle_async_io, + os); + } + + os->offset += len; + + DBG("%zd read", len); + + return len; +} + +static gssize send_data(void *buf, gsize size, gpointer user_data) +{ + struct obex_session *os = user_data; + + DBG("name=%s type=%s file=%p size=%zu", os->name, os->type, os->object, + size); + + if (os->aborted) + return os->err < 0 ? os->err : -EPERM; + + return driver_read(os, buf, size); +} + +static void transfer_complete(GObex *obex, GError *err, gpointer user_data) +{ + struct obex_session *os = user_data; + + DBG(""); + + if (err != NULL) { + error("transfer failed: %s\n", err->message); + goto reset; + } + + if (os->object && os->driver && os->driver->flush) { + if (os->driver->flush(os->object) == -EAGAIN) { + g_obex_suspend(os->obex); + os->driver->set_io_watch(os->object, handle_async_io, + os); + return; + } + } + +reset: + os_reset_session(os); +} + +static int driver_get_headers(struct obex_session *os) +{ + GObexPacket *rsp; + gssize len; + guint8 data[255]; + guint8 id; + GObexHeader *hdr; + + DBG("name=%s type=%s object=%p", os->name, os->type, os->object); + + if (os->aborted) + return os->err < 0 ? os->err : -EPERM; + + if (os->object == NULL) + return -EIO; + + if (os->headers_sent) + return 0; + + rsp = g_obex_packet_new(G_OBEX_RSP_CONTINUE, TRUE, G_OBEX_HDR_INVALID); + + if (os->driver->get_next_header == NULL) + goto done; + + while ((len = os->driver->get_next_header(os->object, &data, + sizeof(data), &id))) { + if (len < 0) { + error("get_next_header(): %s (%zd)", strerror(-len), + -len); + + g_obex_packet_free(rsp); + + if (len == -EAGAIN) + return len; + + g_free(os->buf); + os->buf = NULL; + + return len; + } + + hdr = g_obex_header_new_bytes(id, data, len); + g_obex_packet_add_header(rsp, hdr); + } + +done: + if (os->size != OBJECT_SIZE_UNKNOWN && os->size < UINT32_MAX) { + hdr = g_obex_header_new_uint32(G_OBEX_HDR_LENGTH, os->size); + g_obex_packet_add_header(rsp, hdr); + } + + g_obex_get_rsp_pkt(os->obex, rsp, send_data, transfer_complete, os, + NULL); + + os->headers_sent = TRUE; + + print_event(-1, G_OBEX_RSP_CONTINUE); + + return 0; +} + +static gboolean handle_async_io(void *object, int flags, int err, + void *user_data) +{ + struct obex_session *os = user_data; + + if (err < 0) + goto done; + + if (flags & G_IO_OUT) + err = driver_write(os); + if ((flags & G_IO_IN) && !os->headers_sent) + err = driver_get_headers(os); + + if (err == -EAGAIN) + return TRUE; + +done: + if (err < 0) { + os->err = err; + os->aborted = TRUE; + } + + g_obex_resume(os->obex); + + return FALSE; +} + +static gboolean recv_data(const void *buf, gsize size, gpointer user_data) +{ + struct obex_session *os = user_data; + ssize_t ret; + + DBG("name=%s type=%s file=%p size=%zu", os->name, os->type, os->object, + size); + + if (os->aborted) + return FALSE; + + /* workaround: client didn't send the object length */ + if (os->size == OBJECT_SIZE_DELETE) + os->size = OBJECT_SIZE_UNKNOWN; + + os->buf = g_realloc(os->buf, os->pending + size); + memcpy(os->buf + os->pending, buf, size); + os->pending += size; + + /* only write if both object and driver are valid */ + if (os->object == NULL || os->driver == NULL) { + DBG("Stored %" PRIu64 " bytes into temporary buffer", + os->pending); + return TRUE; + } + + ret = driver_write(os); + if (ret >= 0) + return TRUE; + + if (ret == -EAGAIN) { + g_obex_suspend(os->obex); + os->driver->set_io_watch(os->object, handle_async_io, os); + return TRUE; + } + + return FALSE; +} + +static void parse_type(struct obex_session *os, GObexPacket *req) +{ + GObexHeader *hdr; + const guint8 *type; + gsize len; + + g_free(os->type); + os->type = NULL; + + hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TYPE); + if (hdr == NULL) + goto probe; + + if (!g_obex_header_get_bytes(hdr, &type, &len)) + goto probe; + + /* Ensure null termination */ + if (type[len - 1] != '\0') + goto probe; + + os->type = g_strndup((const char *) type, len); + DBG("TYPE: %s", os->type); + +probe: + os->driver = obex_mime_type_driver_find(os->service->target, + os->service->target_size, + os->type, + os->service->who, + os->service->who_size); +} + +static void parse_name(struct obex_session *os, GObexPacket *req) +{ + GObexHeader *hdr; + const char *name; + + g_free(os->name); + os->name = NULL; + + hdr = g_obex_packet_get_header(req, G_OBEX_HDR_NAME); + if (hdr == NULL) + return; + + if (!g_obex_header_get_unicode(hdr, &name)) + return; + + os->name = g_strdup(name); + DBG("NAME: %s", os->name); +} + +static void parse_apparam(struct obex_session *os, GObexPacket *req) +{ + GObexHeader *hdr; + const guint8 *apparam; + gsize len; + + hdr = g_obex_packet_get_header(req, G_OBEX_HDR_APPARAM); + if (hdr == NULL) + return; + + if (!g_obex_header_get_bytes(hdr, &apparam, &len)) + return; + + os->apparam = g_memdup(apparam, len); + os->apparam_len = len; + DBG("APPARAM"); +} + +static void cmd_get(GObex *obex, GObexPacket *req, gpointer user_data) +{ + struct obex_session *os = user_data; + int err; + + DBG("session %p", os); + + print_event(G_OBEX_OP_GET, -1); + + if (os->service == NULL) { + os_set_response(os, -EPERM); + return; + } + + if (os->service->get == NULL) { + os_set_response(os, -ENOSYS); + return; + } + + os->headers_sent = FALSE; + + if (os->type) { + g_free(os->type); + os->type = NULL; + } + + parse_type(os, req); + + if (!os->driver) { + error("No driver found"); + os_set_response(os, -ENOSYS); + return; + } + + os->cmd = G_OBEX_OP_GET; + + parse_name(os, req); + + parse_apparam(os, req); + + err = os->service->get(os, os->service_data); + if (err == 0) + return; + + os_set_response(os, err); +} + +static void cmd_setpath(GObex *obex, GObexPacket *req, gpointer user_data) +{ + struct obex_session *os = user_data; + int err; + + DBG(""); + + print_event(G_OBEX_OP_SETPATH, -1); + + if (os->service == NULL) { + err = -EPERM; + goto done; + } + + if (os->service->setpath == NULL) { + err = -ENOSYS; + goto done; + } + + os->cmd = G_OBEX_OP_SETPATH; + + parse_name(os, req); + + os->nonhdr = g_obex_packet_get_data(req, &os->nonhdr_len); + + err = os->service->setpath(os, os->service_data); +done: + os_set_response(os, err); +} + +int obex_get_stream_start(struct obex_session *os, const char *filename) +{ + int err; + void *object; + size_t size = OBJECT_SIZE_UNKNOWN; + + object = os->driver->open(filename, O_RDONLY, 0, os->service_data, + &size, &err); + if (object == NULL) { + error("open(%s): %s (%d)", filename, strerror(-err), -err); + return err; + } + + os->object = object; + os->offset = 0; + os->size = size; + + err = driver_get_headers(os); + if (err != -EAGAIN) + return err; + + g_obex_suspend(os->obex); + os->driver->set_io_watch(os->object, handle_async_io, os); + return 0; +} + +int obex_put_stream_start(struct obex_session *os, const char *filename) +{ + int err; + + os->object = os->driver->open(filename, O_WRONLY | O_CREAT | O_TRUNC, + 0600, os->service_data, + os->size != OBJECT_SIZE_UNKNOWN ? + (size_t *) &os->size : NULL, &err); + if (os->object == NULL) { + error("open(%s): %s (%d)", filename, strerror(-err), -err); + return err; + } + + if (os->size != OBJECT_SIZE_DELETE && os->size != OBJECT_SIZE_UNKNOWN) + manager_emit_transfer_property(os->service_data, "Size"); + + os->path = g_strdup(filename); + + return 0; +} + +static void parse_length(struct obex_session *os, GObexPacket *req) +{ + GObexHeader *hdr; + guint32 size; + + hdr = g_obex_packet_get_header(req, G_OBEX_HDR_LENGTH); + if (hdr == NULL) + return; + + if (!g_obex_header_get_uint32(hdr, &size)) + return; + + os->size = size; + DBG("LENGTH: %" PRIu64, os->size); +} + +static void parse_time(struct obex_session *os, GObexPacket *req) +{ + GObexHeader *hdr; + const guint8 *time; + gsize len; + + hdr = g_obex_packet_get_header(req, G_OBEX_HDR_TIME); + if (hdr == NULL) + return; + + + if (!g_obex_header_get_bytes(hdr, &time, &len)) + return; + + os->time = parse_iso8610((const char *) time, len); + DBG("TIME: %s", ctime(&os->time)); +} + +static gboolean check_put(GObex *obex, GObexPacket *req, void *user_data) +{ + struct obex_session *os = user_data; + int ret; + + if (os->service->chkput == NULL) + goto done; + + ret = os->service->chkput(os, os->service_data); + switch (ret) { + case 0: + break; + case -EAGAIN: + g_obex_suspend(os->obex); + os->driver->set_io_watch(os->object, handle_async_io, os); + return TRUE; + default: + os_set_response(os, ret); + return FALSE; + } + + if (os->size == OBJECT_SIZE_DELETE || os->size == OBJECT_SIZE_UNKNOWN) + DBG("Got a PUT without a Length"); + +done: + os->checked = TRUE; + + return TRUE; +} + +static void cmd_put(GObex *obex, GObexPacket *req, gpointer user_data) +{ + struct obex_session *os = user_data; + int err; + + DBG(""); + + print_event(G_OBEX_OP_PUT, -1); + + if (os->service == NULL) { + os_set_response(os, -EPERM); + return; + } + + /* OPP session don't require CONNECT, in which case just call connect + * callback to register the transfer. + */ + if (!os->service_data && os->service->service == OBEX_OPP) { + os->service_data = os->service->connect(os, &err); + if (err < 0) { + os_set_response(os, err); + return; + } + } + + parse_type(os, req); + + if (os->driver == NULL) { + error("No driver found"); + os_set_response(os, -ENOSYS); + return; + } + + os->cmd = G_OBEX_OP_PUT; + + /* Set size to unknown if a body header exists */ + if (g_obex_packet_get_body(req)) + os->size = OBJECT_SIZE_UNKNOWN; + + parse_name(os, req); + parse_length(os, req); + parse_time(os, req); + parse_apparam(os, req); + + if (!os->checked) { + if (!check_put(obex, req, user_data)) + return; + } + + if (os->service->put == NULL) { + os_set_response(os, -ENOSYS); + return; + } + + err = os->service->put(os, os->service_data); + if (err == 0) { + g_obex_put_rsp(obex, req, recv_data, transfer_complete, os, + NULL, G_OBEX_HDR_INVALID); + print_event(G_OBEX_OP_PUT, G_OBEX_RSP_CONTINUE); + return; + } + + os_set_response(os, err); +} + +static void parse_destname(struct obex_session *os, GObexPacket *req) +{ + GObexHeader *hdr; + const char *destname; + + g_free(os->destname); + os->destname = NULL; + + hdr = g_obex_packet_get_header(req, G_OBEX_HDR_DESTNAME); + if (hdr == NULL) + return; + + if (!g_obex_header_get_unicode(hdr, &destname)) + return; + + os->destname = g_strdup(destname); + DBG("DESTNAME: %s", os->destname); +} + +static void parse_action(struct obex_session *os, GObexPacket *req) +{ + GObexHeader *hdr; + guint8 id; + + hdr = g_obex_packet_get_header(req, G_OBEX_HDR_ACTION); + if (hdr == NULL) + return; + + if (!g_obex_header_get_uint8(hdr, &id)) + return; + + os->action_id = id; + DBG("ACTION: 0x%02x", os->action_id); +} + +static void cmd_action(GObex *obex, GObexPacket *req, gpointer user_data) +{ + struct obex_session *os = user_data; + int err; + + DBG(""); + + print_event(G_OBEX_OP_ACTION, -1); + + if (os->service == NULL) { + err = -EPERM; + goto done; + } + + if (os->service->action == NULL) { + err = -ENOSYS; + goto done; + } + + os->cmd = G_OBEX_OP_ACTION; + + parse_name(os, req); + parse_destname(os, req); + parse_action(os, req); + + os->driver = obex_mime_type_driver_find(os->service->target, + os->service->target_size, + NULL, + os->service->who, + os->service->who_size); + if (os->driver == NULL) { + err = -ENOSYS; + goto done; + } + + err = os->service->action(os, os->service_data); +done: + os_set_response(os, err); +} + +static void cmd_abort(GObex *obex, GObexPacket *req, gpointer user_data) +{ + struct obex_session *os = user_data; + + DBG(""); + + print_event(G_OBEX_OP_ABORT, -1); + + os_reset_session(os); + + os_set_response(os, 0); +} + +static void obex_session_destroy(struct obex_session *os) +{ + DBG(""); + + os_reset_session(os); + + if (os->service && os->service->disconnect) + os->service->disconnect(os, os->service_data); + + obex_session_free(os); +} + +static void disconn_func(GObex *obex, GError *err, gpointer user_data) +{ + struct obex_session *os = user_data; + + error("disconnected: %s\n", err ? err->message : ""); + obex_session_destroy(os); +} + +int obex_session_start(GIOChannel *io, uint16_t tx_mtu, uint16_t rx_mtu, + gboolean stream, struct obex_server *server) +{ + struct obex_session *os; + GObex *obex; + GObexTransportType type; + static uint32_t id = 0; + + DBG(""); + + os = g_new0(struct obex_session, 1); + os->id = ++id; + + os->service = obex_service_driver_find(server->drivers, NULL, + 0, NULL, 0); + os->server = server; + os->size = OBJECT_SIZE_DELETE; + + type = stream ? G_OBEX_TRANSPORT_STREAM : G_OBEX_TRANSPORT_PACKET; + + obex = g_obex_new(io, type, rx_mtu, tx_mtu); + if (!obex) { + obex_session_free(os); + return -EIO; + } + + g_obex_set_disconnect_function(obex, disconn_func, os); + g_obex_add_request_function(obex, G_OBEX_OP_CONNECT, cmd_connect, os); + g_obex_add_request_function(obex, G_OBEX_OP_DISCONNECT, cmd_disconnect, + os); + g_obex_add_request_function(obex, G_OBEX_OP_PUT, cmd_put, os); + g_obex_add_request_function(obex, G_OBEX_OP_GET, cmd_get, os); + g_obex_add_request_function(obex, G_OBEX_OP_SETPATH, cmd_setpath, os); + g_obex_add_request_function(obex, G_OBEX_OP_ACTION, cmd_action, os); + g_obex_add_request_function(obex, G_OBEX_OP_ABORT, cmd_abort, os); + + os->obex = obex; + os->io = g_io_channel_ref(io); + + obex_getsockname(os, &os->src); + obex_getpeername(os, &os->dst); + + return 0; +} + +const char *obex_get_name(struct obex_session *os) +{ + return os->name; +} + +const char *obex_get_destname(struct obex_session *os) +{ + return os->destname; +} + +void obex_set_name(struct obex_session *os, const char *name) +{ + g_free(os->name); + os->name = g_strdup(name); + DBG("Name changed: %s", os->name); +} + +ssize_t obex_get_size(struct obex_session *os) +{ + return os->size; +} + +const char *obex_get_type(struct obex_session *os) +{ + return os->type; +} + +int obex_remove(struct obex_session *os, const char *path) +{ + if (os->driver == NULL) + return -ENOSYS; + + return os->driver->remove(path); +} + +int obex_copy(struct obex_session *os, const char *source, + const char *destination) +{ + if (os->driver == NULL || os->driver->copy == NULL) + return -ENOSYS; + + DBG("%s %s", source, destination); + + return os->driver->copy(source, destination); +} + +int obex_move(struct obex_session *os, const char *source, + const char *destination) +{ + if (os->driver == NULL || os->driver->move == NULL) + return -ENOSYS; + + DBG("%s %s", source, destination); + + return os->driver->move(source, destination); +} + +uint8_t obex_get_action_id(struct obex_session *os) +{ + return os->action_id; +} + +ssize_t obex_get_apparam(struct obex_session *os, const uint8_t **buffer) +{ + *buffer = os->apparam; + + return os->apparam_len; +} + +ssize_t obex_get_non_header_data(struct obex_session *os, + const uint8_t **data) +{ + *data = os->nonhdr; + + return os->nonhdr_len; +} + +int obex_getpeername(struct obex_session *os, char **name) +{ + struct obex_transport_driver *transport = os->server->transport; + + if (transport == NULL || transport->getpeername == NULL) + return -ENOTSUP; + + return transport->getpeername(os->io, name); +} + +int obex_getsockname(struct obex_session *os, char **name) +{ + struct obex_transport_driver *transport = os->server->transport; + + if (transport == NULL || transport->getsockname == NULL) + return -ENOTSUP; + + return transport->getsockname(os->io, name); +} + +int memncmp0(const void *a, size_t na, const void *b, size_t nb) +{ + if (na != nb) + return na - nb; + + if (a == NULL) + return -(a != b); + + if (b == NULL) + return a != b; + + return memcmp(a, b, na); +} diff --git a/obexd/src/obex.h b/obexd/src/obex.h new file mode 100644 index 0000000..67593f1 --- /dev/null +++ b/obexd/src/obex.h @@ -0,0 +1,54 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Nokia Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#define OBJECT_SIZE_UNKNOWN -1 +#define OBJECT_SIZE_DELETE -2 + +#define TARGET_SIZE 16 + +struct obex_session; + +int obex_get_stream_start(struct obex_session *os, const char *filename); +int obex_put_stream_start(struct obex_session *os, const char *filename); +const char *obex_get_name(struct obex_session *os); +const char *obex_get_destname(struct obex_session *os); +void obex_set_name(struct obex_session *os, const char *name); +ssize_t obex_get_size(struct obex_session *os); +const char *obex_get_type(struct obex_session *os); +int obex_remove(struct obex_session *os, const char *path); +int obex_copy(struct obex_session *os, const char *source, + const char *destination); +int obex_move(struct obex_session *os, const char *source, + const char *destination); +uint8_t obex_get_action_id(struct obex_session *os); +ssize_t obex_get_apparam(struct obex_session *os, const uint8_t **buffer); +ssize_t obex_get_non_header_data(struct obex_session *os, + const uint8_t **data); +int obex_getpeername(struct obex_session *os, char **name); +int obex_getsockname(struct obex_session *os, char **name); + +/* Just a thin wrapper around memcmp to deal with NULL values */ +int memncmp0(const void *a, size_t na, const void *b, size_t nb); diff --git a/obexd/src/obex.service.in b/obexd/src/obex.service.in new file mode 100644 index 0000000..fc0dce9 --- /dev/null +++ b/obexd/src/obex.service.in @@ -0,0 +1,10 @@ +[Unit] +Description=Bluetooth OBEX service + +[Service] +Type=dbus +BusName=org.bluez.obex +ExecStart=@pkglibexecdir@/obexd + +[Install] +Alias=dbus-org.bluez.obex.service diff --git a/obexd/src/obexd.h b/obexd/src/obexd.h new file mode 100644 index 0000000..42c3c4d --- /dev/null +++ b/obexd/src/obexd.h @@ -0,0 +1,43 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define OBEX_OPP (1 << 1) +#define OBEX_FTP (1 << 2) +#define OBEX_BIP (1 << 3) +#define OBEX_PBAP (1 << 4) +#define OBEX_IRMC (1 << 5) +#define OBEX_PCSUITE (1 << 6) +#define OBEX_SYNCEVOLUTION (1 << 7) +#define OBEX_MAS (1 << 8) +#define OBEX_MNS (1 << 9) + +gboolean plugin_init(const char *pattern, const char *exclude); +void plugin_cleanup(void); + +gboolean manager_init(void); +void manager_cleanup(void); + +gboolean obex_option_auto_accept(void); +const char *obex_option_root_folder(void); +gboolean obex_option_symlinks(void); +const char *obex_option_capability(void); diff --git a/obexd/src/org.bluez.obex.service b/obexd/src/org.bluez.obex.service new file mode 100644 index 0000000..a538088 --- /dev/null +++ b/obexd/src/org.bluez.obex.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=org.bluez.obex +Exec=/bin/false +SystemdService=dbus-org.bluez.obex.service diff --git a/obexd/src/plugin.c b/obexd/src/plugin.c new file mode 100644 index 0000000..a1962b9 --- /dev/null +++ b/obexd/src/plugin.c @@ -0,0 +1,210 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include + +#include "obexd.h" +#include "plugin.h" +#include "log.h" + +/* + * Plugins that are using libraries with threads and their own mainloop + * will crash on exit. This is a bug inside these libraries, but there is + * nothing much that can be done about it. One bad example is libebook. + */ +#ifdef NEED_THREADS +#define PLUGINFLAG (RTLD_NOW | RTLD_NODELETE) +#else +#define PLUGINFLAG (RTLD_NOW) +#endif + +static GSList *plugins = NULL; + +struct obex_plugin { + void *handle; + struct obex_plugin_desc *desc; +}; + +static gboolean add_plugin(void *handle, struct obex_plugin_desc *desc) +{ + struct obex_plugin *plugin; + + if (desc->init == NULL) + return FALSE; + + plugin = g_try_new0(struct obex_plugin, 1); + if (plugin == NULL) + return FALSE; + + plugin->handle = handle; + plugin->desc = desc; + + if (desc->init() < 0) { + g_free(plugin); + return FALSE; + } + + plugins = g_slist_append(plugins, plugin); + DBG("Plugin %s loaded", desc->name); + + return TRUE; +} + +static gboolean check_plugin(struct obex_plugin_desc *desc, + char **patterns, char **excludes) +{ + if (excludes) { + for (; *excludes; excludes++) + if (g_pattern_match_simple(*excludes, desc->name)) + break; + if (*excludes) { + info("Excluding %s", desc->name); + return FALSE; + } + } + + if (patterns) { + for (; *patterns; patterns++) + if (g_pattern_match_simple(*patterns, desc->name)) + break; + if (*patterns == NULL) { + info("Ignoring %s", desc->name); + return FALSE; + } + } + + return TRUE; +} + + +#include "builtin.h" + +gboolean plugin_init(const char *pattern, const char *exclude) +{ + char **patterns = NULL; + char **excludes = NULL; + GDir *dir; + const char *file; + unsigned int i; + + if (strlen(PLUGINDIR) == 0) + return FALSE; + + if (pattern) + patterns = g_strsplit_set(pattern, ":, ", -1); + + if (exclude) + excludes = g_strsplit_set(exclude, ":, ", -1); + + DBG("Loading builtin plugins"); + + for (i = 0; __obex_builtin[i]; i++) { + if (check_plugin(__obex_builtin[i], + patterns, excludes) == FALSE) + continue; + + add_plugin(NULL, __obex_builtin[i]); + } + + DBG("Loading plugins %s", PLUGINDIR); + + dir = g_dir_open(PLUGINDIR, 0, NULL); + if (!dir) { + g_strfreev(patterns); + g_strfreev(excludes); + return FALSE; + } + + while ((file = g_dir_read_name(dir)) != NULL) { + struct obex_plugin_desc *desc; + void *handle; + char *filename; + + if (g_str_has_prefix(file, "lib") == TRUE || + g_str_has_suffix(file, ".so") == FALSE) + continue; + + filename = g_build_filename(PLUGINDIR, file, NULL); + + handle = dlopen(filename, PLUGINFLAG); + if (handle == NULL) { + error("Can't load plugin %s: %s", filename, + dlerror()); + g_free(filename); + continue; + } + + g_free(filename); + + desc = dlsym(handle, "obex_plugin_desc"); + if (desc == NULL) { + error("Can't load plugin description: %s", dlerror()); + dlclose(handle); + continue; + } + + if (check_plugin(desc, patterns, excludes) == FALSE) { + dlclose(handle); + continue; + } + + if (add_plugin(handle, desc) == FALSE) + dlclose(handle); + } + + g_dir_close(dir); + g_strfreev(patterns); + g_strfreev(excludes); + + return TRUE; +} + +void plugin_cleanup(void) +{ + GSList *list; + + DBG("Cleanup plugins"); + + for (list = plugins; list; list = list->next) { + struct obex_plugin *plugin = list->data; + + if (plugin->desc->exit) + plugin->desc->exit(); + + if (plugin->handle != NULL) + dlclose(plugin->handle); + + g_free(plugin); + } + + g_slist_free(plugins); +} diff --git a/obexd/src/plugin.h b/obexd/src/plugin.h new file mode 100644 index 0000000..13d7769 --- /dev/null +++ b/obexd/src/plugin.h @@ -0,0 +1,42 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct obex_plugin_desc { + const char *name; + int (*init) (void); + void (*exit) (void); +}; + +#ifdef OBEX_PLUGIN_BUILTIN +#define OBEX_PLUGIN_DEFINE(name, init, exit) \ + struct obex_plugin_desc __obex_builtin_ ## name = { \ + #name, init, exit \ + }; +#else +#define OBEX_PLUGIN_DEFINE(name,init,exit) \ + extern struct obex_plugin_desc obex_plugin_desc \ + __attribute__ ((visibility("default"))); \ + struct obex_plugin_desc obex_plugin_desc = { \ + #name, init, exit \ + }; +#endif diff --git a/obexd/src/server.c b/obexd/src/server.c new file mode 100644 index 0000000..db85423 --- /dev/null +++ b/obexd/src/server.c @@ -0,0 +1,128 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Nokia Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gobex/gobex.h" + +#include "log.h" +#include "obex.h" +#include "obex-priv.h" +#include "server.h" +#include "service.h" +#include "transport.h" + +static GSList *servers = NULL; + +static void init_server(uint16_t service, GSList *transports) +{ + GSList *l; + + for (l = transports; l; l = l->next) { + struct obex_transport_driver *transport = l->data; + struct obex_server *server; + int err; + + if (transport->service != 0 && + (transport->service & service) == FALSE) + continue; + + server = g_new0(struct obex_server, 1); + server->transport = transport; + server->drivers = obex_service_driver_list(service); + + server->transport_data = transport->start(server, &err); + if (server->transport_data == NULL) { + DBG("Unable to start %s transport: %s (%d)", + transport->name, strerror(err), err); + g_free(server); + continue; + } + + servers = g_slist_prepend(servers, server); + } +} + +int obex_server_init(void) +{ + GSList *drivers; + GSList *transports; + GSList *l; + + drivers = obex_service_driver_list(0); + if (drivers == NULL) { + DBG("No service driver registered"); + return -EINVAL; + } + + transports = obex_transport_driver_list(); + if (transports == NULL) { + DBG("No transport driver registered"); + return -EINVAL; + } + + for (l = drivers; l; l = l->next) { + struct obex_service_driver *driver = l->data; + + init_server(driver->service, transports); + } + + return 0; +} + +void obex_server_exit(void) +{ + GSList *l; + + for (l = servers; l; l = l->next) { + struct obex_server *server = l->data; + + server->transport->stop(server->transport_data); + g_slist_free(server->drivers); + g_free(server); + } + + g_slist_free(servers); + + return; +} + +int obex_server_new_connection(struct obex_server *server, GIOChannel *io, + uint16_t tx_mtu, uint16_t rx_mtu, + gboolean stream) +{ + return obex_session_start(io, tx_mtu, rx_mtu, stream, server); +} diff --git a/obexd/src/server.h b/obexd/src/server.h new file mode 100644 index 0000000..278c35f --- /dev/null +++ b/obexd/src/server.h @@ -0,0 +1,37 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Nokia Corporation + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct obex_server { + struct obex_transport_driver *transport; + void *transport_data; + GSList *drivers; +}; + +int obex_server_init(void); + +void obex_server_exit(void); + +int obex_server_new_connection(struct obex_server *server, GIOChannel *io, + uint16_t tx_mtu, uint16_t rx_mtu, + gboolean stream); diff --git a/obexd/src/service.c b/obexd/src/service.c new file mode 100644 index 0000000..c088535 --- /dev/null +++ b/obexd/src/service.c @@ -0,0 +1,132 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include "obex.h" +#include "service.h" +#include "log.h" + +static GSList *drivers = NULL; + +struct obex_service_driver *obex_service_driver_find(GSList *drivers, + const uint8_t *target, unsigned int target_size, + const uint8_t *who, unsigned int who_size) +{ + GSList *l; + + for (l = drivers; l; l = l->next) { + struct obex_service_driver *driver = l->data; + + /* who is optional, so only check for it if not NULL */ + if (who != NULL && memncmp0(who, who_size, driver->who, + driver->who_size)) + continue; + + if (memncmp0(target, target_size, driver->target, + driver->target_size) == 0) + return driver; + } + + return NULL; +} + +GSList *obex_service_driver_list(uint16_t services) +{ + GSList *l; + GSList *list = NULL; + + if (services == 0) + return drivers; + + for (l = drivers; l && services; l = l->next) { + struct obex_service_driver *driver = l->data; + + if (driver->service & services) { + list = g_slist_append(list, driver); + services &= ~driver->service; + } + } + + return list; +} + +static struct obex_service_driver *find_driver(uint16_t service) +{ + GSList *l; + + for (l = drivers; l; l = l->next) { + struct obex_service_driver *driver = l->data; + + if (driver->service == service) + return driver; + } + + return NULL; +} + +int obex_service_driver_register(struct obex_service_driver *driver) +{ + if (!driver) { + error("Invalid driver"); + return -EINVAL; + } + + if (find_driver(driver->service)) { + error("Permission denied: service %s already registered", + driver->name); + return -EPERM; + } + + DBG("driver %p service %s registered", driver, driver->name); + + /* Drivers that support who has priority */ + if (driver->who) + drivers = g_slist_prepend(drivers, driver); + else + drivers = g_slist_append(drivers, driver); + + return 0; +} + +void obex_service_driver_unregister(struct obex_service_driver *driver) +{ + if (!g_slist_find(drivers, driver)) { + error("Unable to unregister: No such driver %p", driver); + return; + } + + DBG("driver %p service %s unregistered", driver, driver->name); + + drivers = g_slist_remove(drivers, driver); +} diff --git a/obexd/src/service.h b/obexd/src/service.h new file mode 100644 index 0000000..5d9d325 --- /dev/null +++ b/obexd/src/service.h @@ -0,0 +1,53 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define OBEX_PORT_RANDOM UINT16_MAX + +struct obex_service_driver { + const char *name; + uint16_t service; + uint8_t channel; + uint16_t port; + gboolean secure; + const uint8_t *target; + unsigned int target_size; + const uint8_t *who; + unsigned int who_size; + const char *record; + void *(*connect) (struct obex_session *os, int *err); + void (*progress) (struct obex_session *os, void *user_data); + int (*get) (struct obex_session *os, void *user_data); + int (*put) (struct obex_session *os, void *user_data); + int (*chkput) (struct obex_session *os, void *user_data); + int (*setpath) (struct obex_session *os, void *user_data); + int (*action) (struct obex_session *os, void *user_data); + void (*disconnect) (struct obex_session *os, void *user_data); + void (*reset) (struct obex_session *os, void *user_data); +}; + +int obex_service_driver_register(struct obex_service_driver *driver); +void obex_service_driver_unregister(struct obex_service_driver *driver); +GSList *obex_service_driver_list(uint16_t services); +struct obex_service_driver *obex_service_driver_find(GSList *drivers, + const uint8_t *target, unsigned int target_size, + const uint8_t *who, unsigned int who_size); diff --git a/obexd/src/transport.c b/obexd/src/transport.c new file mode 100644 index 0000000..4984643 --- /dev/null +++ b/obexd/src/transport.c @@ -0,0 +1,93 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include "obex.h" +#include "server.h" +#include "transport.h" +#include "log.h" + +static GSList *drivers = NULL; + +static struct obex_transport_driver *obex_transport_driver_find( + const char *name) +{ + GSList *l; + + for (l = drivers; l; l = l->next) { + struct obex_transport_driver *driver = l->data; + + if (g_strcmp0(name, driver->name) == 0) + return driver; + } + + return NULL; +} + +GSList *obex_transport_driver_list(void) +{ + return drivers; +} + +int obex_transport_driver_register(struct obex_transport_driver *driver) +{ + if (!driver) { + error("Invalid driver"); + return -EINVAL; + } + + if (obex_transport_driver_find(driver->name) != NULL) { + error("Permission denied: transport %s already registered", + driver->name); + return -EPERM; + } + + DBG("driver %p transport %s registered", driver, driver->name); + + drivers = g_slist_prepend(drivers, driver); + + return 0; +} + +void obex_transport_driver_unregister(struct obex_transport_driver *driver) +{ + if (!g_slist_find(drivers, driver)) { + error("Unable to unregister: No such driver %p", driver); + return; + } + + DBG("driver %p transport %s unregistered", driver, driver->name); + + drivers = g_slist_remove(drivers, driver); +} diff --git a/obexd/src/transport.h b/obexd/src/transport.h new file mode 100644 index 0000000..97e10d0 --- /dev/null +++ b/obexd/src/transport.h @@ -0,0 +1,35 @@ +/* + * + * OBEX Server + * + * Copyright (C) 2007-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct obex_transport_driver { + const char *name; + uint16_t service; + void *(*start) (struct obex_server *server, int *err); + int (*getpeername) (GIOChannel *io, char **name); + int (*getsockname) (GIOChannel *io, char **name); + void (*stop) (void *data); +}; + +int obex_transport_driver_register(struct obex_transport_driver *driver); +void obex_transport_driver_unregister(struct obex_transport_driver *driver); +GSList *obex_transport_driver_list(void); diff --git a/peripheral/attach.c b/peripheral/attach.c new file mode 100644 index 0000000..1de02ac --- /dev/null +++ b/peripheral/attach.c @@ -0,0 +1,145 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "tools/hciattach.h" +#include "peripheral/attach.h" + +static const char *serial_dev = "/dev/ttyS1"; +static int serial_fd = -1; + +static int open_serial(const char *path) +{ + struct termios ti; + int fd, saved_ldisc, ldisc = N_HCI; + + fd = open(path, O_RDWR | O_NOCTTY); + if (fd < 0) { + perror("Failed to open serial port"); + return -1; + } + + if (tcflush(fd, TCIOFLUSH) < 0) { + perror("Failed to flush serial port"); + close(fd); + return -1; + } + + if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) { + perror("Failed get serial line discipline"); + close(fd); + return -1; + } + + /* Switch TTY to raw mode */ + memset(&ti, 0, sizeof(ti)); + cfmakeraw(&ti); + + ti.c_cflag |= (B115200 | CLOCAL | CREAD); + + /* Set flow control */ + ti.c_cflag |= CRTSCTS; + + if (tcsetattr(fd, TCSANOW, &ti) < 0) { + perror("Failed to set serial port settings"); + close(fd); + return -1; + } + + if (ioctl(fd, TIOCSETD, &ldisc) < 0) { + perror("Failed set serial line discipline"); + close(fd); + return -1; + } + + printf("Switched line discipline from %d to %d\n", saved_ldisc, ldisc); + + return fd; +} + +static int attach_proto(const char *path, unsigned int proto, + unsigned int flags) +{ + int fd, dev_id; + + fd = open_serial(path); + if (fd < 0) + return -1; + + if (ioctl(fd, HCIUARTSETFLAGS, flags) < 0) { + perror("Failed to set flags"); + close(fd); + return -1; + } + + if (ioctl(fd, HCIUARTSETPROTO, proto) < 0) { + perror("Failed to set protocol"); + close(fd); + return -1; + } + + dev_id = ioctl(fd, HCIUARTGETDEVICE); + if (dev_id < 0) { + perror("Failed to get device id"); + close(fd); + return -1; + } + + printf("Device index %d attached\n", dev_id); + + return fd; +} + +void attach_start(void) +{ + unsigned long flags; + + if (serial_fd >= 0) + return; + + printf("Attaching BR/EDR controller to %s\n", serial_dev); + + flags = (1 << HCI_UART_RESET_ON_INIT); + + serial_fd = attach_proto(serial_dev, HCI_UART_H4, flags); +} + +void attach_stop(void) +{ + if (serial_fd < 0) + return; + + close(serial_fd); + serial_fd = -1; +} diff --git a/peripheral/attach.h b/peripheral/attach.h new file mode 100644 index 0000000..f76e2fb --- /dev/null +++ b/peripheral/attach.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 + * + */ + +void attach_start(void); +void attach_stop(void); diff --git a/peripheral/efivars.c b/peripheral/efivars.c new file mode 100644 index 0000000..3cc213c --- /dev/null +++ b/peripheral/efivars.c @@ -0,0 +1,133 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "peripheral/efivars.h" + +#define SYSFS_EFIVARS "/sys/firmware/efi/efivars" + +typedef struct { + uint32_t data1; + uint16_t data2; + uint16_t data3; + uint8_t data4[8]; +} efi_guid_t; + +#define VENDOR_GUID \ + (efi_guid_t) { 0xd5f9d775, 0x1a09, 0x4e89, \ + { 0x96, 0xcf, 0x1d, 0x19, 0x55, 0x4d, 0xa6, 0x67 } } + +static void efivars_pathname(const char *name, char *pathname, size_t size) +{ + static efi_guid_t guid = VENDOR_GUID; + + snprintf(pathname, size - 1, + "%s/%s-%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + SYSFS_EFIVARS, name, guid.data1, guid.data2, guid.data3, + guid.data4[0], guid.data4[1], guid.data4[2], guid.data4[3], + guid.data4[4], guid.data4[5], guid.data4[6], guid.data4[7]); +} + +int efivars_read(const char *name, uint32_t *attributes, + void *data, size_t size) +{ + char pathname[PATH_MAX]; + struct iovec iov[2]; + uint32_t attr; + ssize_t len; + int fd; + + efivars_pathname(name, pathname, PATH_MAX); + + fd = open(pathname, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return -EIO; + + iov[0].iov_base = &attr; + iov[0].iov_len = sizeof(attr); + iov[1].iov_base = data; + iov[1].iov_len = size; + + len = readv(fd, iov, 2); + + close(fd); + + if (len < 0) + return -EIO; + + if (attributes) + *attributes = attr; + + return 0; +} + +int efivars_write(const char *name, uint32_t attributes, + const void *data, size_t size) +{ + char pathname[PATH_MAX]; + void *buf; + ssize_t written; + int fd; + + efivars_pathname(name, pathname, PATH_MAX); + + buf = malloc(size + sizeof(attributes)); + if (!buf) + return -ENOMEM; + + fd = open(pathname, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + free(buf); + return -EIO; + } + + memcpy(buf, &attributes, sizeof(attributes)); + memcpy(buf + sizeof(attributes), data, size); + + written = write(fd, buf, size + sizeof(attributes)); + + close(fd); + free(buf); + + if (written < 0) + return -EIO; + + return 0; +} diff --git a/peripheral/efivars.h b/peripheral/efivars.h new file mode 100644 index 0000000..430d143 --- /dev/null +++ b/peripheral/efivars.h @@ -0,0 +1,33 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 EFIVARS_NON_VOLATILE 0x00000001 +#define EFIVARS_BOOTSERVICE_ACCESS 0x00000002 +#define EFIVARS_RUNTIME_ACCESS 0x00000004 +#define EFIVARS_HARDWARE_ERROR_RECORD 0x00000008 +#define EFIVARS_AUTHENTICATED_WRITE_ACCESS 0x00000010 + +int efivars_read(const char *name, uint32_t *attributes, + void *data, size_t size); +int efivars_write(const char *name, uint32_t attributes, + const void *data, size_t size); diff --git a/peripheral/gap.c b/peripheral/gap.c new file mode 100644 index 0000000..52a7bed --- /dev/null +++ b/peripheral/gap.c @@ -0,0 +1,549 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 +#endif + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/mgmt.h" +#include "src/shared/util.h" +#include "src/shared/mgmt.h" +#include "peripheral/gatt.h" +#include "peripheral/gap.h" + +static struct mgmt *mgmt = NULL; +static uint16_t mgmt_index = MGMT_INDEX_NONE; + +static bool adv_features = false; +static bool adv_instances = false; +static bool require_connectable = true; + +static uint8_t static_addr[6] = { 0x90, 0x78, 0x56, 0x34, 0x12, 0xc0 }; +static uint8_t dev_name[260] = { 0x00, }; +static uint8_t dev_name_len = 0; + +void gap_set_static_address(uint8_t addr[6]) +{ + memcpy(static_addr, addr, sizeof(static_addr)); + + printf("Using static address %02x:%02x:%02x:%02x:%02x:%02x\n", + static_addr[5], static_addr[4], static_addr[3], + static_addr[2], static_addr[1], static_addr[0]); +} + +static void clear_long_term_keys(uint16_t index) +{ + struct mgmt_cp_load_long_term_keys cp; + + memset(&cp, 0, sizeof(cp)); + cp.key_count = cpu_to_le16(0); + + mgmt_send(mgmt, MGMT_OP_LOAD_LONG_TERM_KEYS, index, + sizeof(cp), &cp, NULL, NULL, NULL); +} + +static void clear_identity_resolving_keys(uint16_t index) +{ + struct mgmt_cp_load_irks cp; + + memset(&cp, 0, sizeof(cp)); + cp.irk_count = cpu_to_le16(0); + + mgmt_send(mgmt, MGMT_OP_LOAD_IRKS, index, + sizeof(cp), &cp, NULL, NULL, NULL); +} + +static void add_advertising(uint16_t index) +{ + const char ad[] = { 0x11, 0x15, + 0xd0, 0x00, 0x2d, 0x12, 0x1e, 0x4b, 0x0f, 0xa4, + 0x99, 0x4e, 0xce, 0xb5, 0x31, 0xf4, 0x05, 0x79 }; + struct mgmt_cp_add_advertising *cp; + void *buf; + + buf = malloc(sizeof(*cp) + sizeof(ad)); + if (!buf) + return; + + memset(buf, 0, sizeof(*cp) + sizeof(ad)); + cp = buf; + cp->instance = 0x01; + cp->flags = cpu_to_le32((1 << 0) | (1 << 1) | (1 << 4)); + cp->duration = cpu_to_le16(0); + cp->timeout = cpu_to_le16(0); + cp->adv_data_len = sizeof(ad); + cp->scan_rsp_len = 0; + memcpy(cp->data, ad, sizeof(ad)); + + mgmt_send(mgmt, MGMT_OP_ADD_ADVERTISING, index, + sizeof(*cp) + sizeof(ad), buf, NULL, NULL, NULL); + + free(buf); +} + +static void enable_advertising(uint16_t index) +{ + uint8_t val; + + val = require_connectable ? 0x01 : 0x00; + mgmt_send(mgmt, MGMT_OP_SET_CONNECTABLE, index, 1, &val, + NULL, NULL, NULL); + + val = 0x01; + mgmt_send(mgmt, MGMT_OP_SET_POWERED, index, 1, &val, + NULL, NULL, NULL); + + if (adv_instances) { + add_advertising(index); + return; + } + + val = require_connectable ? 0x01 : 0x02; + mgmt_send(mgmt, MGMT_OP_SET_ADVERTISING, index, 1, &val, + NULL, NULL, NULL); +} + +static void new_settings_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("New settings\n"); +} + +static void local_name_changed_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("Local name changed\n"); +} + +static void new_long_term_key_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("New long term key\n"); +} + +static void device_connected_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("Device connected\n"); +} + +static void device_disconnected_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("Device disconnected\n"); +} + +static void user_confirm_request_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("User confirm request\n"); +} + +static void user_passkey_request_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("User passkey request\n"); +} + +static void auth_failed_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("Authentication failed\n"); +} + +static void device_unpaired_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("Device unpaired\n"); +} + +static void passkey_notify_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("Passkey notification\n"); +} + +static void new_irk_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("New identify resolving key\n"); +} + +static void new_csrk_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("New connection signature resolving key\n"); +} + +static void new_conn_param_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("New connection parameter\n"); +} + +static void advertising_added_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("Advertising added\n"); +} + +static void advertising_removed_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("Advertising removed\n"); +} + +static void read_adv_features_complete(uint8_t status, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_adv_features *rp = param; + uint16_t index = PTR_TO_UINT(user_data); + uint32_t flags; + + flags = le32_to_cpu(rp->supported_flags); + + if (rp->max_instances > 0) { + adv_instances = true; + + if (flags & (1 << 0)) + require_connectable = false; + } else + require_connectable = false; + + enable_advertising(index); +} + +static void read_info_complete(uint8_t status, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_info *rp = param; + uint16_t index = PTR_TO_UINT(user_data); + uint32_t required_settings = MGMT_SETTING_LE | + MGMT_SETTING_STATIC_ADDRESS; + uint32_t supported_settings, current_settings; + uint8_t val; + + required_settings = MGMT_SETTING_LE; + + if (status) { + fprintf(stderr, "Reading info for index %u failed: %s\n", + index, mgmt_errstr(status)); + return; + } + + if (mgmt_index != MGMT_INDEX_NONE) + return; + + supported_settings = le32_to_cpu(rp->supported_settings); + current_settings = le32_to_cpu(rp->current_settings); + + if ((supported_settings & required_settings) != required_settings) + return; + + printf("Selecting index %u\n", index); + mgmt_index = index; + + mgmt_register(mgmt, MGMT_EV_NEW_SETTINGS, index, + new_settings_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_LOCAL_NAME_CHANGED, index, + local_name_changed_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_NEW_LONG_TERM_KEY, index, + new_long_term_key_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_DEVICE_CONNECTED, index, + device_connected_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_DEVICE_DISCONNECTED, index, + device_disconnected_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_USER_CONFIRM_REQUEST, index, + user_confirm_request_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_USER_PASSKEY_REQUEST, index, + user_passkey_request_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_AUTH_FAILED, index, + auth_failed_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_DEVICE_UNPAIRED, index, + device_unpaired_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_PASSKEY_NOTIFY, index, + passkey_notify_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_NEW_IRK, index, + new_irk_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_NEW_CSRK, index, + new_csrk_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_NEW_CONN_PARAM, index, + new_conn_param_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_ADVERTISING_ADDED, index, + advertising_added_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_ADVERTISING_REMOVED, index, + advertising_removed_event, NULL, NULL); + + dev_name_len = snprintf((char *) dev_name, 26, "BlueZ Peripheral"); + + if (current_settings & MGMT_SETTING_POWERED) { + val = 0x00; + mgmt_send(mgmt, MGMT_OP_SET_POWERED, index, 1, &val, + NULL, NULL, NULL); + } + + if (!(current_settings & MGMT_SETTING_LE)) { + val = 0x01; + mgmt_send(mgmt, MGMT_OP_SET_LE, index, 1, &val, + NULL, NULL, NULL); + } + + if (current_settings & MGMT_SETTING_BREDR) { + val = 0x00; + mgmt_send(mgmt, MGMT_OP_SET_BREDR, index, 1, &val, + NULL, NULL, NULL); + } + + if ((supported_settings & MGMT_SETTING_SECURE_CONN) && + !(current_settings & MGMT_SETTING_SECURE_CONN)) { + val = 0x01; + mgmt_send(mgmt, MGMT_OP_SET_SECURE_CONN, index, 1, &val, + NULL, NULL, NULL); + } + + if (current_settings & MGMT_SETTING_DEBUG_KEYS) { + val = 0x00; + mgmt_send(mgmt, MGMT_OP_SET_DEBUG_KEYS, index, 1, &val, + NULL, NULL, NULL); + } + + if (!(current_settings & MGMT_SETTING_BONDABLE)) { + val = 0x01; + mgmt_send(mgmt, MGMT_OP_SET_BONDABLE, index, 1, &val, + NULL, NULL, NULL); + } + + clear_long_term_keys(mgmt_index); + clear_identity_resolving_keys(mgmt_index); + + mgmt_send(mgmt, MGMT_OP_SET_STATIC_ADDRESS, index, + 6, static_addr, NULL, NULL, NULL); + + mgmt_send(mgmt, MGMT_OP_SET_LOCAL_NAME, index, + 260, dev_name, NULL, NULL, NULL); + + gatt_set_static_address(static_addr); + gatt_set_device_name(dev_name, dev_name_len); + gatt_server_start(); + + if (adv_features) + mgmt_send(mgmt, MGMT_OP_READ_ADV_FEATURES, index, 0, NULL, + read_adv_features_complete, + UINT_TO_PTR(index), NULL); + else + enable_advertising(index); +} + +static void read_index_list_complete(uint8_t status, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_index_list *rp = param; + uint16_t count; + int i; + + if (status) { + fprintf(stderr, "Reading index list failed: %s\n", + mgmt_errstr(status)); + return; + } + + count = le16_to_cpu(rp->num_controllers); + + printf("Index list: %u\n", count); + + for (i = 0; i < count; i++) { + uint16_t index = cpu_to_le16(rp->index[i]); + + mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, + read_info_complete, UINT_TO_PTR(index), NULL); + } +} + +static void index_added_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("Index added\n"); + + if (mgmt_index != MGMT_INDEX_NONE) + return; + + mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, + read_info_complete, UINT_TO_PTR(index), NULL); +} + +static void index_removed_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + printf("Index removed\n"); + + if (mgmt_index != index) + return; + + mgmt_index = MGMT_INDEX_NONE; +} + +static void read_ext_index_list_complete(uint8_t status, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_ext_index_list *rp = param; + uint16_t count; + int i; + + if (status) { + fprintf(stderr, "Reading extended index list failed: %s\n", + mgmt_errstr(status)); + return; + } + + count = le16_to_cpu(rp->num_controllers); + + printf("Extended index list: %u\n", count); + + for (i = 0; i < count; i++) { + uint16_t index = cpu_to_le16(rp->entry[i].index); + + if (rp->entry[i].type != 0x00) + continue; + + mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, + read_info_complete, UINT_TO_PTR(index), NULL); + } +} + +static void ext_index_added_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_ext_index_added *ev = param; + + printf("Extended index added: %u\n", ev->type); + + if (mgmt_index != MGMT_INDEX_NONE) + return; + + if (ev->type != 0x00) + return; + + mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, + read_info_complete, UINT_TO_PTR(index), NULL); +} + +static void ext_index_removed_event(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_ext_index_added *ev = param; + + printf("Extended index removed: %u\n", ev->type); + + if (mgmt_index != index) + return; + + if (ev->type != 0x00) + return; + + mgmt_index = MGMT_INDEX_NONE; +} + +static void read_commands_complete(uint8_t status, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_commands *rp = param; + uint16_t num_commands; + bool ext_index_list = false; + int i; + + if (status) { + fprintf(stderr, "Reading index list failed: %s\n", + mgmt_errstr(status)); + return; + } + + num_commands = le16_to_cpu(rp->num_commands); + + for (i = 0; i < num_commands; i++) { + uint16_t op = get_le16(rp->opcodes + 1); + + if (op == MGMT_OP_READ_EXT_INDEX_LIST) + ext_index_list = true; + else if (op == MGMT_OP_READ_ADV_FEATURES) + adv_features = true; + } + + if (ext_index_list) { + mgmt_register(mgmt, MGMT_EV_EXT_INDEX_ADDED, MGMT_INDEX_NONE, + ext_index_added_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_EXT_INDEX_REMOVED, MGMT_INDEX_NONE, + ext_index_removed_event, NULL, NULL); + + if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INDEX_LIST, + MGMT_INDEX_NONE, 0, NULL, + read_ext_index_list_complete, NULL, NULL)) { + fprintf(stderr, "Failed to read extended index list\n"); + return; + } + } else { + mgmt_register(mgmt, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE, + index_added_event, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE, + index_removed_event, NULL, NULL); + + if (!mgmt_send(mgmt, MGMT_OP_READ_INDEX_LIST, + MGMT_INDEX_NONE, 0, NULL, + read_index_list_complete, NULL, NULL)) { + fprintf(stderr, "Failed to read index list\n"); + return; + } + } +} + +void gap_start(void) +{ + mgmt = mgmt_new_default(); + if (!mgmt) { + fprintf(stderr, "Failed to open management socket\n"); + return; + } + + if (!mgmt_send(mgmt, MGMT_OP_READ_COMMANDS, + MGMT_INDEX_NONE, 0, NULL, + read_commands_complete, NULL, NULL)) { + fprintf(stderr, "Failed to read supported commands\n"); + return; + } +} + +void gap_stop(void) +{ + if (!mgmt) + return; + + gatt_server_stop(); + + mgmt_unref(mgmt); + mgmt = NULL; + + mgmt_index = MGMT_INDEX_NONE; +} diff --git a/peripheral/gap.h b/peripheral/gap.h new file mode 100644 index 0000000..6d67378 --- /dev/null +++ b/peripheral/gap.h @@ -0,0 +1,29 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 + +void gap_set_static_address(uint8_t addr[6]); + +void gap_start(void); +void gap_stop(void); diff --git a/peripheral/gatt.c b/peripheral/gatt.c new file mode 100644 index 0000000..08541c4 --- /dev/null +++ b/peripheral/gatt.c @@ -0,0 +1,318 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 +#endif + +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/l2cap.h" +#include "lib/uuid.h" +#include "src/shared/mainloop.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-server.h" +#include "src/shared/gatt-client.h" +#include "peripheral/gatt.h" + +#define ATT_CID 4 + +#define UUID_GAP 0x1800 + +struct gatt_conn { + struct bt_att *att; + struct bt_gatt_server *gatt; + struct bt_gatt_client *client; +}; + +static int att_fd = -1; +static struct queue *conn_list = NULL; +static struct gatt_db *gatt_db = NULL; +static struct gatt_db *gatt_cache = NULL; + +static uint8_t static_addr[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static uint8_t dev_name[20]; +static uint8_t dev_name_len = 0; + +void gatt_set_static_address(uint8_t addr[6]) +{ + memcpy(static_addr, addr, sizeof(static_addr)); +} + +void gatt_set_device_name(uint8_t name[20], uint8_t len) +{ + memcpy(dev_name, name, sizeof(dev_name)); + dev_name_len = len; +} + +static void gatt_conn_destroy(void *data) +{ + struct gatt_conn *conn = data; + + bt_gatt_client_unref(conn->client); + bt_gatt_server_unref(conn->gatt); + bt_att_unref(conn->att); + + free(conn); +} + +static void gatt_conn_disconnect(int err, void *user_data) +{ + struct gatt_conn *conn = user_data; + + printf("Device disconnected: %s\n", strerror(err)); + + queue_remove(conn_list, conn); + gatt_conn_destroy(conn); +} + +static void client_ready_callback(bool success, uint8_t att_ecode, + void *user_data) +{ + printf("GATT client discovery complete\n"); +} + +static void client_service_changed_callback(uint16_t start_handle, + uint16_t end_handle, + void *user_data) +{ + printf("GATT client service changed notification\n"); +} + +static struct gatt_conn *gatt_conn_new(int fd) +{ + struct gatt_conn *conn; + uint16_t mtu = 0; + + conn = new0(struct gatt_conn, 1); + if (!conn) + return NULL; + + conn->att = bt_att_new(fd, false); + if (!conn->att) { + fprintf(stderr, "Failed to initialze ATT transport layer\n"); + free(conn); + return NULL; + } + + bt_att_set_close_on_unref(conn->att, true); + bt_att_register_disconnect(conn->att, gatt_conn_disconnect, conn, NULL); + + bt_att_set_security(conn->att, BT_SECURITY_MEDIUM); + + conn->gatt = bt_gatt_server_new(gatt_db, conn->att, mtu, 0); + if (!conn->gatt) { + fprintf(stderr, "Failed to create GATT server\n"); + bt_att_unref(conn->att); + free(conn); + return NULL; + } + + conn->client = bt_gatt_client_new(gatt_cache, conn->att, mtu); + if (!conn->gatt) { + fprintf(stderr, "Failed to create GATT client\n"); + bt_gatt_server_unref(conn->gatt); + bt_att_unref(conn->att); + free(conn); + return NULL; + } + + bt_gatt_client_ready_register(conn->client, client_ready_callback, + conn, NULL); + bt_gatt_client_set_service_changed(conn->client, + client_service_changed_callback, conn, NULL); + + return conn; +} + +static void att_conn_callback(int fd, uint32_t events, void *user_data) +{ + struct gatt_conn *conn; + struct sockaddr_l2 addr; + socklen_t addrlen; + int new_fd; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_remove_fd(fd); + return; + } + + memset(&addr, 0, sizeof(addr)); + addrlen = sizeof(addr); + + new_fd = accept(att_fd, (struct sockaddr *) &addr, &addrlen); + if (new_fd < 0) { + fprintf(stderr, "Failed to accept new ATT connection: %m\n"); + return; + } + + conn = gatt_conn_new(new_fd); + if (!conn) { + fprintf(stderr, "Failed to create GATT connection\n"); + close(new_fd); + return; + } + + if (!queue_push_tail(conn_list, conn)) { + fprintf(stderr, "Failed to add GATT connection\n"); + gatt_conn_destroy(conn); + close(new_fd); + } + + printf("New device connected\n"); +} + +static void gap_device_name_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t error; + const uint8_t *value; + size_t len; + + if (offset > dev_name_len) { + error = BT_ATT_ERROR_INVALID_OFFSET; + value = NULL; + len = dev_name_len; + } else { + error = 0; + len = dev_name_len - offset; + value = len ? &dev_name[offset] : NULL; + } + + gatt_db_attribute_read_result(attrib, id, error, value, len); +} + +static void populate_gap_service(struct gatt_db *db) +{ + struct gatt_db_attribute *service; + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, UUID_GAP); + service = gatt_db_add_service(db, &uuid, true, 6); + + bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); + gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + gap_device_name_read, NULL, NULL); + + gatt_db_service_set_active(service, true); +} + +static void populate_devinfo_service(struct gatt_db *db) +{ + struct gatt_db_attribute *service; + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, 0x180a); + service = gatt_db_add_service(db, &uuid, true, 17); + + gatt_db_service_set_active(service, true); +} + +void gatt_server_start(void) +{ + struct sockaddr_l2 addr; + + if (att_fd >= 0) + return; + + att_fd = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_CLOEXEC, + BTPROTO_L2CAP); + if (att_fd < 0) { + fprintf(stderr, "Failed to create ATT server socket: %m\n"); + return; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + addr.l2_cid = htobs(ATT_CID); + memcpy(&addr.l2_bdaddr, static_addr, 6); + addr.l2_bdaddr_type = BDADDR_LE_RANDOM; + + if (bind(att_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "Failed to bind ATT server socket: %m\n"); + close(att_fd); + att_fd = -1; + return; + } + + if (listen(att_fd, 1) < 0) { + fprintf(stderr, "Failed to listen on ATT server socket: %m\n"); + close(att_fd); + att_fd = -1; + return; + } + + gatt_db = gatt_db_new(); + if (!gatt_db) { + close(att_fd); + att_fd = -1; + return; + } + + populate_gap_service(gatt_db); + populate_devinfo_service(gatt_db); + + gatt_cache = gatt_db_new(); + + conn_list = queue_new(); + if (!conn_list) { + gatt_db_unref(gatt_db); + gatt_db = NULL; + close(att_fd); + att_fd = -1; + return; + } + + mainloop_add_fd(att_fd, EPOLLIN, att_conn_callback, NULL, NULL); +} + +void gatt_server_stop(void) +{ + if (att_fd < 0) + return; + + mainloop_remove_fd(att_fd); + + queue_destroy(conn_list, gatt_conn_destroy); + + gatt_db_unref(gatt_cache); + gatt_cache = NULL; + + gatt_db_unref(gatt_db); + gatt_db = NULL; + + close(att_fd); + att_fd = -1; +} diff --git a/peripheral/gatt.h b/peripheral/gatt.h new file mode 100644 index 0000000..5b68f35 --- /dev/null +++ b/peripheral/gatt.h @@ -0,0 +1,30 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 + +void gatt_set_static_address(uint8_t addr[6]); +void gatt_set_device_name(uint8_t name[20], uint8_t len); + +void gatt_server_start(void); +void gatt_server_stop(void); diff --git a/peripheral/log.c b/peripheral/log.c new file mode 100644 index 0000000..d5834eb --- /dev/null +++ b/peripheral/log.c @@ -0,0 +1,57 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "peripheral/log.h" + +static int kmsg_fd = -1; + +void log_open(void) +{ + if (kmsg_fd >= 0) + return; + + kmsg_fd = open("/dev/kmsg", O_WRONLY | O_NOCTTY | O_CLOEXEC); + if (kmsg_fd < 0) { + fprintf(stderr, "Failed to open kernel logging: %m\n"); + return; + } +} + +void log_close(void) +{ + if (kmsg_fd < 0) + return; + + close(kmsg_fd); + kmsg_fd = -1; +} diff --git a/peripheral/log.h b/peripheral/log.h new file mode 100644 index 0000000..36619b3 --- /dev/null +++ b/peripheral/log.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 + * + */ + +void log_open(void); +void log_close(void); diff --git a/peripheral/main.c b/peripheral/main.c new file mode 100644 index 0000000..75427ab --- /dev/null +++ b/peripheral/main.c @@ -0,0 +1,240 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef WAIT_ANY +#define WAIT_ANY (-1) +#endif + +#include "src/shared/mainloop.h" +#include "peripheral/efivars.h" +#include "peripheral/attach.h" +#include "peripheral/gap.h" +#include "peripheral/log.h" + +static bool is_init = false; +static pid_t shell_pid = -1; + +static const struct { + const char *target; + const char *linkpath; +} dev_table[] = { + { "/proc/self/fd", "/dev/fd" }, + { "/proc/self/fd/0", "/dev/stdin" }, + { "/proc/self/fd/1", "/dev/stdout" }, + { "/proc/self/fd/2", "/dev/stderr" }, + { } +}; + +static const struct { + const char *fstype; + const char *target; + const char *options; + unsigned long flags; +} mount_table[] = { + { "sysfs", "/sys", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, + { "proc", "/proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, + { "devtmpfs", "/dev", NULL, MS_NOSUID|MS_STRICTATIME }, + { "efivarfs", "/sys/firmware/efi/efivars", + NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, + { "pstore", "/sys/fs/pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, + { } +}; + +static void prepare_filesystem(void) +{ + int i; + + if (!is_init) + return; + + for (i = 0; mount_table[i].fstype; i++) { + struct stat st; + + if (lstat(mount_table[i].target, &st) < 0) { + printf("Creating %s\n", mount_table[i].target); + mkdir(mount_table[i].target, 0755); + } + + printf("Mounting %s to %s\n", mount_table[i].fstype, + mount_table[i].target); + + if (mount(mount_table[i].fstype, + mount_table[i].target, + mount_table[i].fstype, + mount_table[i].flags, NULL) < 0) + perror("Failed to mount filesystem"); + } + + for (i = 0; dev_table[i].target; i++) { + printf("Linking %s to %s\n", dev_table[i].linkpath, + dev_table[i].target); + + if (symlink(dev_table[i].target, dev_table[i].linkpath) < 0) + perror("Failed to create device symlink"); + } +} + +static void run_shell(void) +{ + pid_t pid; + + printf("Starting shell\n"); + + pid = fork(); + if (pid < 0) { + perror("Failed to fork new process"); + return; + } + + if (pid == 0) { + char *prg_argv[] = { "/bin/sh", NULL }; + char *prg_envp[] = { NULL }; + + execve(prg_argv[0], prg_argv, prg_envp); + exit(0); + } + + printf("PID %d created\n", pid); + + shell_pid = pid; +} + +static void exit_shell(void) +{ + shell_pid = -1; + + if (!is_init) { + mainloop_quit(); + return; + } + + run_shell(); +} + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + case SIGCHLD: + while (1) { + pid_t pid; + int status; + + pid = waitpid(WAIT_ANY, &status, WNOHANG); + if (pid < 0 || pid == 0) + break; + + if (WIFEXITED(status)) { + printf("PID %d exited (status=%d)\n", + pid, WEXITSTATUS(status)); + + if (pid == shell_pid) + exit_shell(); + } else if (WIFSIGNALED(status)) { + printf("PID %d terminated (signal=%d)\n", + pid, WTERMSIG(status)); + + if (pid == shell_pid) + exit_shell(); + } + } + break; + } +} + +int main(int argc, char *argv[]) +{ + int exit_status; + + if (getpid() == 1 && getppid() == 0) + is_init = true; + + mainloop_init(); + + printf("Bluetooth periperhal ver %s\n", VERSION); + + prepare_filesystem(); + + if (is_init) { + uint8_t addr[6]; + + if (efivars_read("BluetoothStaticAddress", NULL, + addr, 6) < 0) { + printf("Generating new persistent static address\n"); + + addr[0] = rand(); + addr[1] = rand(); + addr[2] = rand(); + addr[3] = 0x34; + addr[4] = 0x12; + addr[5] = 0xc0; + + efivars_write("BluetoothStaticAddress", + EFIVARS_NON_VOLATILE | + EFIVARS_BOOTSERVICE_ACCESS | + EFIVARS_RUNTIME_ACCESS, + addr, 6); + } + + gap_set_static_address(addr); + + run_shell(); + } + + log_open(); + gap_start(); + + if (is_init) + attach_start(); + + exit_status = mainloop_run_with_signal(signal_callback, NULL); + + if (is_init) + attach_stop(); + + gap_stop(); + log_close(); + + return exit_status; +} diff --git a/plugins/autopair.c b/plugins/autopair.c new file mode 100644 index 0000000..043bd9b --- /dev/null +++ b/plugins/autopair.c @@ -0,0 +1,228 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Google Inc. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/log.h" +#include "src/storage.h" + +/* + * Plugin to handle automatic pairing of devices with reduced user + * interaction, including implementing the recommendation of the HID spec + * for keyboard devices. + * + * The plugin works by intercepting the PIN request for devices; if the + * device is a keyboard a random six-digit numeric PIN is generated and + * returned, flagged for displaying using DisplayPinCode. + * + */ + +static ssize_t autopair_pincb(struct btd_adapter *adapter, + struct btd_device *device, + char *pinbuf, bool *display, + unsigned int attempt) +{ + char addr[18]; + char pinstr[7]; + char name[25]; + uint32_t class; + + ba2str(device_get_address(device), addr); + + class = btd_device_get_class(device); + + device_get_name(device, name, sizeof(name)); + + DBG("device '%s' (%s) class: 0x%x vid/pid: 0x%X/0x%X", + name, addr, class, + btd_device_get_vendor (device), + btd_device_get_product (device)); + + /* The iCade shouldn't use random PINs like normal keyboards */ + if (name != NULL && strstr(name, "iCade") != NULL) + return 0; + + /* This is a class-based pincode guesser. Ignore devices with an + * unknown class. + */ + if (class == 0) + return 0; + + switch ((class & 0x1f00) >> 8) { + case 0x04: /* Audio/Video */ + switch ((class & 0xfc) >> 2) { + case 0x01: /* Wearable Headset Device */ + case 0x02: /* Hands-free Device */ + case 0x06: /* Headphones */ + case 0x07: /* Portable Audio */ + case 0x0a: /* HiFi Audio Device */ + { + const char *pincodes[] = { + "0000", + "1234", + "1111" + }; + const char *pincode; + + if (attempt > G_N_ELEMENTS(pincodes)) + return 0; + pincode = pincodes[attempt - 1]; + memcpy(pinbuf, pincode, strlen(pincode)); + return strlen(pincode); + } + } + break; + + case 0x05: /* Peripheral */ + switch ((class & 0xc0) >> 6) { + case 0x00: + switch ((class & 0x1e) >> 2) { + case 0x01: /* Joystick */ + case 0x02: /* Gamepad */ + case 0x03: /* Remote Control */ + if (attempt > 1) + return 0; + memcpy(pinbuf, "0000", 4); + return 4; + } + + break; + case 0x01: /* Keyboard */ + case 0x03: /* Combo keyboard/pointing device */ + /* For keyboards rejecting the first random code + * in less than 500ms, try a fixed code. */ + if (attempt > 1 && + device_bonding_last_duration(device) < 500) { + /* Don't try more than one dumb code */ + if (attempt > 2) + return 0; + /* Try "0000" as the code for the second + * attempt. */ + memcpy(pinbuf, "0000", 4); + return 4; + } + + /* Never try more than 3 random pincodes. */ + if (attempt >= 4) + return 0; + + snprintf(pinstr, sizeof(pinstr), "%06u", + rand() % 1000000); + *display = true; + memcpy(pinbuf, pinstr, 6); + return 6; + + case 0x02: /* Pointing device */ + if (attempt > 1) + return 0; + memcpy(pinbuf, "0000", 4); + return 4; + } + + break; + case 0x06: /* Imaging */ + if (class & 0x80) { /* Printer */ + if (attempt > 1) + return 0; + memcpy(pinbuf, "0000", 4); + return 4; + } + break; + } + + return 0; +} + + +static int autopair_probe(struct btd_adapter *adapter) +{ + btd_adapter_register_pin_cb(adapter, autopair_pincb); + + return 0; +} + +static void autopair_remove(struct btd_adapter *adapter) +{ + btd_adapter_unregister_pin_cb(adapter, autopair_pincb); +} + +static struct btd_adapter_driver autopair_driver = { + .name = "autopair", + .probe = autopair_probe, + .remove = autopair_remove, +}; + +static int autopair_init(void) +{ + /* Initialize the random seed from /dev/urandom */ + unsigned int seed; + int fd, err; + ssize_t n; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + err = -errno; + error("Failed to open /dev/urandom: %s (%d)", strerror(-err), + -err); + return err; + } + + n = read(fd, &seed, sizeof(seed)); + if (n < (ssize_t) sizeof(seed)) { + err = (n == -1) ? -errno : -EIO; + error("Failed to read %zu bytes from /dev/urandom: %s (%d)", + sizeof(seed), strerror(-err), -err); + close(fd); + return err; + } + + close(fd); + + srand(seed); + + return btd_register_adapter_driver(&autopair_driver); +} + +static void autopair_exit(void) +{ + btd_unregister_adapter_driver(&autopair_driver); +} + +BLUETOOTH_PLUGIN_DEFINE(autopair, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + autopair_init, autopair_exit) diff --git a/plugins/external-dummy.c b/plugins/external-dummy.c new file mode 100644 index 0000000..536ad06 --- /dev/null +++ b/plugins/external-dummy.c @@ -0,0 +1,41 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "src/plugin.h" +#include "src/log.h" + +static int dummy_init(void) +{ + DBG(""); + + return 0; +} + +static void dummy_exit(void) +{ + DBG(""); +} + +BLUETOOTH_PLUGIN_DEFINE(external_dummy, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_LOW, dummy_init, dummy_exit) diff --git a/plugins/hostname.c b/plugins/hostname.c new file mode 100644 index 0000000..4f9dfe6 --- /dev/null +++ b/plugins/hostname.c @@ -0,0 +1,328 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "gdbus/gdbus.h" + +#include "src/dbus-common.h" +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/log.h" + +/* http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm */ + +#define MAJOR_CLASS_MISCELLANEOUS 0x00 +#define MAJOR_CLASS_COMPUTER 0x01 + +#define MINOR_CLASS_UNCATEGORIZED 0x00 +#define MINOR_CLASS_DESKTOP 0x01 +#define MINOR_CLASS_SERVER 0x02 +#define MINOR_CLASS_LAPTOP 0x03 +#define MINOR_CLASS_HANDHELD 0x04 +#define MINOR_CLASS_PALM_SIZED 0x05 +#define MINOR_CLASS_WEARABLE 0x06 +#define MINOR_CLASS_TABLET 0x07 + +static uint8_t major_class = MAJOR_CLASS_MISCELLANEOUS; +static uint8_t minor_class = MINOR_CLASS_UNCATEGORIZED; + +static char *pretty_hostname = NULL; +static char *static_hostname = NULL; + +/* + * Fallback to static hostname only if empty pretty hostname was already + * received. + */ +static const char *get_hostname(void) +{ + if (pretty_hostname) { + if (g_str_equal(pretty_hostname, "") == FALSE) + return pretty_hostname; + + if (static_hostname && + g_str_equal(static_hostname, "") == FALSE) + return static_hostname; + } + + return NULL; +} + +static void update_name(struct btd_adapter *adapter, gpointer user_data) +{ + const char *hostname = get_hostname(); + + if (hostname == NULL) + return; + + if (btd_adapter_is_default(adapter)) { + DBG("name: %s", hostname); + + adapter_set_name(adapter, hostname); + } else { + uint16_t index = btd_adapter_get_index(adapter); + char *str; + + /* Avoid "some device #0" names, start at #1 */ + str = g_strdup_printf("%s #%u", hostname, index + 1); + + DBG("name: %s", str); + + adapter_set_name(adapter, str); + + g_free(str); + } +} + +static void update_class(struct btd_adapter *adapter, gpointer user_data) +{ + if (major_class == MAJOR_CLASS_MISCELLANEOUS) + return; + + DBG("major: 0x%02x minor: 0x%02x", major_class, minor_class); + + btd_adapter_set_class(adapter, major_class, minor_class); +} + +static const struct { + const char *chassis; + uint8_t major_class; + uint8_t minor_class; +} chassis_table[] = { + { "desktop", MAJOR_CLASS_COMPUTER, MINOR_CLASS_DESKTOP }, + { "server", MAJOR_CLASS_COMPUTER, MINOR_CLASS_SERVER }, + { "laptop", MAJOR_CLASS_COMPUTER, MINOR_CLASS_LAPTOP }, + { "handset", MAJOR_CLASS_COMPUTER, MINOR_CLASS_HANDHELD }, + { "tablet", MAJOR_CLASS_COMPUTER, MINOR_CLASS_TABLET }, + { } +}; + +static void property_changed(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + if (g_str_equal(name, "PrettyHostname") == TRUE) { + if (iter == NULL) { + g_dbus_proxy_refresh_property(proxy, name); + return; + } + + if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) { + const char *str; + + dbus_message_iter_get_basic(iter, &str); + + DBG("pretty hostname: %s", str); + + g_free(pretty_hostname); + pretty_hostname = g_strdup(str); + + adapter_foreach(update_name, NULL); + } + } else if (g_str_equal(name, "StaticHostname") == TRUE) { + if (iter == NULL) { + g_dbus_proxy_refresh_property(proxy, name); + return; + } + + if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) { + const char *str; + + dbus_message_iter_get_basic(iter, &str); + + DBG("static hostname: %s", str); + + g_free(static_hostname); + static_hostname = g_strdup(str); + + adapter_foreach(update_name, NULL); + } + } else if (g_str_equal(name, "Chassis") == TRUE) { + if (iter == NULL) { + g_dbus_proxy_refresh_property(proxy, name); + return; + } + + if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING) { + const char *str; + int i; + + dbus_message_iter_get_basic(iter, &str); + + DBG("chassis: %s", str); + + for (i = 0; chassis_table[i].chassis; i++) { + if (strcmp(chassis_table[i].chassis, str)) + continue; + + major_class = chassis_table[i].major_class; + minor_class = chassis_table[i].minor_class; + + adapter_foreach(update_class, NULL); + break; + } + } + } +} + +static int hostname_probe(struct btd_adapter *adapter) +{ + DBG(""); + + update_name(adapter, NULL); + update_class(adapter, NULL); + + return 0; +} + +static void hostname_remove(struct btd_adapter *adapter) +{ + DBG(""); +} + +static struct btd_adapter_driver hostname_driver = { + .name = "hostname", + .probe = hostname_probe, + .remove = hostname_remove, +}; + +static void read_dmi_fallback(void) +{ + char *contents; + int i, type; + const char *str; + + if (g_file_get_contents("/sys/class/dmi/id/chassis_type", + &contents, NULL, NULL) == FALSE) + return; + + type = atoi(contents); + if (type < 0 || type > 0x1D) + return; + + g_free(contents); + + /* from systemd hostname chassis list */ + switch (type) { + case 0x3: + case 0x4: + case 0x6: + case 0x7: + str = "desktop"; + break; + case 0x8: + case 0x9: + case 0xA: + case 0xE: + str = "laptop"; + break; + case 0xB: + str = "handset"; + break; + case 0x11: + case 0x1C: + str = "server"; + break; + default: + return; + } + + DBG("chassis: %s", str); + + for (i = 0; chassis_table[i].chassis; i++) { + if (!strcmp(chassis_table[i].chassis, str)) { + major_class = chassis_table[i].major_class; + minor_class = chassis_table[i].minor_class; + break; + } + } + + DBG("major: 0x%02x minor: 0x%02x", major_class, minor_class); +} + +static GDBusClient *hostname_client = NULL; +static GDBusProxy *hostname_proxy = NULL; + +static int hostname_init(void) +{ + DBusConnection *conn = btd_get_dbus_connection(); + int err; + + read_dmi_fallback(); + + hostname_client = g_dbus_client_new(conn, "org.freedesktop.hostname1", + "/org/freedesktop/hostname1"); + if (!hostname_client) + return -EIO; + + hostname_proxy = g_dbus_proxy_new(hostname_client, + "/org/freedesktop/hostname1", + "org.freedesktop.hostname1"); + if (!hostname_proxy) { + g_dbus_client_unref(hostname_client); + hostname_client = NULL; + return -EIO; + } + + g_dbus_proxy_set_property_watch(hostname_proxy, property_changed, NULL); + + err = btd_register_adapter_driver(&hostname_driver); + if (err < 0) { + g_dbus_proxy_unref(hostname_proxy); + hostname_proxy = NULL; + g_dbus_client_unref(hostname_client); + hostname_client = NULL; + } + + return err; +} + +static void hostname_exit(void) +{ + btd_unregister_adapter_driver(&hostname_driver); + + if (hostname_proxy) { + g_dbus_proxy_unref(hostname_proxy); + hostname_proxy = NULL; + } + + if (hostname_client) { + g_dbus_client_unref(hostname_client); + hostname_client = NULL; + } + + g_free(pretty_hostname); + g_free(static_hostname); +} + +BLUETOOTH_PLUGIN_DEFINE(hostname, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + hostname_init, hostname_exit) diff --git a/plugins/neard.c b/plugins/neard.c new file mode 100644 index 0000000..6ffcd6e --- /dev/null +++ b/plugins/neard.c @@ -0,0 +1,911 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-2013 Tieto Poland + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/sdp.h" + +#include "gdbus/gdbus.h" + +#include "src/plugin.h" +#include "src/log.h" +#include "src/dbus-common.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/eir.h" +#include "src/agent.h" +#include "src/hcid.h" + +#define NEARD_NAME "org.neard" +#define NEARD_PATH "/" +#define NEARD_MANAGER_INTERFACE "org.neard.Manager" +#define AGENT_INTERFACE "org.neard.HandoverAgent" +#define AGENT_PATH "/org/bluez/neard_handover_agent" +#define AGENT_CARRIER_TYPE "bluetooth" +#define ERROR_INTERFACE "org.neard.HandoverAgent.Error" + +static guint watcher_id = 0; +static char *neard_service = NULL; +static bool agent_register_postpone = false; + +/* For NFC mimetype limits max OOB EIR size */ +#define NFC_OOB_EIR_MAX UINT8_MAX + +enum cps { + CPS_ACTIVE, + CPS_INACTIVE, + CPS_ACTIVATING, + CPS_UNKNOWN, +}; + +struct oob_params { + bdaddr_t address; + uint32_t class; + char *name; + GSList *services; + uint8_t *hash; + uint8_t *randomizer; + uint8_t *pin; + int pin_len; + enum cps power_state; +}; + +static void free_oob_params(struct oob_params *params) +{ + g_slist_free_full(params->services, free); + g_free(params->name); + g_free(params->hash); + g_free(params->randomizer); + g_free(params->pin); +} + +static DBusMessage *error_reply(DBusMessage *msg, int error) +{ + const char *name; + + if (error == EINPROGRESS) + name = ERROR_INTERFACE ".InProgress"; + else + name = ERROR_INTERFACE ".Failed"; + + return g_dbus_create_error(msg, name , "%s", strerror(error)); +} + +static void register_agent(bool append_carrier); + +static void register_agent_cb(DBusPendingCall *call, void *user_data) +{ + DBusMessage *reply; + DBusError err; + static bool try_fallback = true; + + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + if (g_str_equal(DBUS_ERROR_UNKNOWN_METHOD, err.name) && + try_fallback) { + DBG("Register to neard failed, trying legacy way"); + + register_agent(false); + try_fallback = false; + } else { + error("neard manager replied with an error: %s, %s", + err.name, err.message); + + g_dbus_unregister_interface(btd_get_dbus_connection(), + AGENT_PATH, AGENT_INTERFACE); + try_fallback = true; + } + + dbus_error_free(&err); + dbus_message_unref(reply); + + return; + } + + dbus_message_unref(reply); + neard_service = g_strdup(dbus_message_get_sender(reply)); + + try_fallback = true; + + info("Registered as neard handover agent"); +} + +static void register_agent(bool append_carrier) +{ + DBusMessage *message; + DBusPendingCall *call; + const char *path = AGENT_PATH; + const char *carrier = AGENT_CARRIER_TYPE; + + message = dbus_message_new_method_call(NEARD_NAME, NEARD_PATH, + NEARD_MANAGER_INTERFACE, "RegisterHandoverAgent"); + if (!message) { + error("Couldn't allocate D-Bus message"); + return; + } + + dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + if (append_carrier) + dbus_message_append_args(message, DBUS_TYPE_STRING, &carrier, + DBUS_TYPE_INVALID); + + if (!g_dbus_send_message_with_reply(btd_get_dbus_connection(), + message, &call, -1)) { + dbus_message_unref(message); + error("D-Bus send failed"); + return; + } + + dbus_pending_call_set_notify(call, register_agent_cb, NULL, NULL); + dbus_pending_call_unref(call); + + dbus_message_unref(message); +} + +static void unregister_agent(void) +{ + DBusMessage *message; + const char *path = AGENT_PATH; + const char *carrier = AGENT_CARRIER_TYPE; + + g_free(neard_service); + neard_service = NULL; + + message = dbus_message_new_method_call(NEARD_NAME, NEARD_PATH, + NEARD_MANAGER_INTERFACE, "UnregisterHandoverAgent"); + + if (!message) { + error("Couldn't allocate D-Bus message"); + goto unregister; + } + + dbus_message_append_args(message, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + dbus_message_append_args(message, DBUS_TYPE_STRING, &carrier, + DBUS_TYPE_INVALID); + + if (!g_dbus_send_message(btd_get_dbus_connection(), message)) + error("D-Bus send failed"); + +unregister: + g_dbus_unregister_interface(btd_get_dbus_connection(), AGENT_PATH, + AGENT_INTERFACE); +} + +static void add_power_state(DBusMessageIter *dict, struct btd_adapter *adapter) +{ + const char *state; + + if (btd_adapter_get_powered(adapter) && + btd_adapter_get_connectable(adapter)) + state = "active"; + else + state = "inactive"; + + dict_append_entry(dict, "State", DBUS_TYPE_STRING, &state); +} + +static DBusMessage *create_request_oob_reply(struct btd_adapter *adapter, + const uint8_t *hash, + const uint8_t *randomizer, + DBusMessage *msg) +{ + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + uint8_t eir[NFC_OOB_EIR_MAX]; + uint8_t *peir = eir; + int len; + + len = eir_create_oob(btd_adapter_get_address(adapter), + btd_adapter_get_name(adapter), + btd_adapter_get_class(adapter), hash, + randomizer, main_opts.did_vendor, + main_opts.did_product, main_opts.did_version, + main_opts.did_source, + btd_adapter_get_services(adapter), eir); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + dict_append_array(&dict, "EIR", DBUS_TYPE_BYTE, &peir, len); + + add_power_state(&dict, adapter); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} + +static void read_local_complete(struct btd_adapter *adapter, + const uint8_t *hash, const uint8_t *randomizer, + void *user_data) +{ + DBusMessage *msg = user_data; + DBusMessage *reply; + + DBG(""); + + if (neard_service == NULL) { + dbus_message_unref(msg); + + if (agent_register_postpone) { + agent_register_postpone = false; + register_agent(true); + } + + return; + } + + if (hash && randomizer) + reply = create_request_oob_reply(adapter, hash, randomizer, + msg); + else + reply = error_reply(msg, EIO); + + dbus_message_unref(msg); + + if (!g_dbus_send_message(btd_get_dbus_connection(), reply)) + error("D-Bus send failed"); +} + +static void bonding_complete(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t status, + void *user_data) +{ + DBusMessage *msg = user_data; + DBusMessage *reply; + + DBG(""); + + if (neard_service == NULL) { + dbus_message_unref(msg); + + if (agent_register_postpone) { + agent_register_postpone = false; + register_agent(true); + } + + return; + } + + if (status) + reply = error_reply(msg, EIO); + else + reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + + dbus_message_unref(msg); + + if (!g_dbus_send_message(btd_get_dbus_connection(), reply)) + error("D-Bus send failed"); +} + +static int check_device(struct btd_device *device) +{ + if (!device) + return -ENOENT; + + /* If already paired */ + if (device_is_paired(device, BDADDR_BREDR)) { + DBG("already paired"); + return -EALREADY; + } + + /* Pairing in progress... */ + if (device_is_bonding(device, NULL)) { + DBG("pairing in progress"); + return -EINPROGRESS; + } + + return 0; +} + +static int process_eir(uint8_t *eir, size_t size, struct oob_params *remote) +{ + struct eir_data eir_data; + + DBG("size %zu", size); + + memset(&eir_data, 0, sizeof(eir_data)); + + if (eir_parse_oob(&eir_data, eir, size) < 0) + return -EINVAL; + + bacpy(&remote->address, &eir_data.addr); + + remote->class = eir_data.class; + + remote->name = eir_data.name; + eir_data.name = NULL; + + remote->services = eir_data.services; + eir_data.services = NULL; + + remote->hash = eir_data.hash; + eir_data.hash = NULL; + + remote->randomizer = eir_data.randomizer; + eir_data.randomizer = NULL; + + eir_data_free(&eir_data); + + return 0; +} + +/* + * This is (barely documented) Nokia extension format, most work was done by + * reverse engineering. + * + * Binary format varies among different devices, type depends on first byte + * 0x00 - BT address not reversed, 16 bytes authentication data (all zeros) + * 0x01 - BT address not reversed, 16 bytes authentication data (4 digit PIN, + * padded with zeros) + * 0x02 - BT address not reversed, 16 bytes authentication data (not sure if + * 16 digit PIN or link key?, Nokia refers to it as ' Public Key') + * 0x10 - BT address reversed, no authentication data + * 0x24 - BT address not reversed, 4 bytes authentication data (4 digit PIN) + * + * General structure: + * 1 byte - marker + * 6 bytes - BT address (reversed or not, depends on marker) + * 3 bytes - Class of Device + * 0, 4 or 16 bytes - authentication data, interpretation depends on marker + * 1 bytes - name length + * N bytes - name + */ + +static int process_nokia_long (void *data, size_t size, uint8_t marker, + struct oob_params *remote) +{ + struct { + bdaddr_t address; + uint8_t class[3]; + uint8_t authentication[16]; + uint8_t name_len; + uint8_t name[0]; + } __attribute__((packed)) *n = data; + + if (size != sizeof(*n) + n->name_len) + return -EINVAL; + + /* address is not reverted */ + baswap(&remote->address, &n->address); + + remote->class = n->class[0] | (n->class[1] << 8) | (n->class[2] << 16); + + if (n->name_len > 0) + remote->name = g_strndup((char *)n->name, n->name_len); + + if (marker == 0x01) { + remote->pin = g_memdup(n->authentication, 4); + remote->pin_len = 4; + } else if (marker == 0x02) { + remote->pin = g_memdup(n->authentication, 16); + remote->pin_len = 16; + } + + return 0; +} + +static int process_nokia_short (void *data, size_t size, + struct oob_params *remote) +{ + struct { + bdaddr_t address; + uint8_t class[3]; + uint8_t authentication[4]; + uint8_t name_len; + uint8_t name[0]; + } __attribute__((packed)) *n = data; + + if (size != sizeof(*n) + n->name_len) + return -EINVAL; + + /* address is not reverted */ + baswap(&remote->address, &n->address); + + remote->class = n->class[0] | (n->class[1] << 8) | (n->class[2] << 16); + + if (n->name_len > 0) + remote->name = g_strndup((char *)n->name, n->name_len); + + remote->pin = g_memdup(n->authentication, 4); + remote->pin_len = 4; + + return 0; +} + +static int process_nokia_extra_short (void *data, size_t size, + struct oob_params *remote) +{ + struct { + bdaddr_t address; + uint8_t class[3]; + uint8_t name_len; + uint8_t name[0]; + } __attribute__((packed)) *n = data; + + if (size != sizeof(*n) + n->name_len) + return -EINVAL; + + bacpy(&remote->address, &n->address); + + remote->class = n->class[0] | (n->class[1] << 8) | (n->class[2] << 16); + + if (n->name_len > 0) + remote->name = g_strndup((char *)n->name, n->name_len); + + return 0; +} + +static int process_nokia_com_bt(uint8_t *data, size_t size, + struct oob_params *remote) +{ + uint8_t marker; + + marker = *data++; + size--; + + DBG("marker: 0x%.2x size: %zu", marker, size); + + switch (marker) { + case 0x00: + case 0x01: + case 0x02: + return process_nokia_long(data, size, marker, remote); + case 0x10: + return process_nokia_extra_short(data, size, remote); + case 0x24: + return process_nokia_short(data, size, remote); + default: + warn("Not supported Nokia NFC extension (0x%.2x)", marker); + return -EPROTONOSUPPORT; + } +} + +static enum cps process_state(const char *state) +{ + if (strcasecmp(state, "active") == 0) + return CPS_ACTIVE; + + if (strcasecmp(state, "activating") == 0) + return CPS_ACTIVATING; + + if (strcasecmp(state, "inactive") == 0) + return CPS_INACTIVE; + + return CPS_UNKNOWN; +} + +static int process_message(DBusMessage *msg, struct oob_params *remote) +{ + DBusMessageIter iter; + DBusMessageIter dict; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + return -EINVAL; + + /* set CPS to unknown in case State was not provided */ + remote->power_state = CPS_UNKNOWN; + + dbus_message_iter_recurse(&iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter value; + DBusMessageIter entry; + const char *key; + + dbus_message_iter_recurse(&dict, &entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + goto error; + + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + dbus_message_iter_recurse(&entry, &value); + + if (strcasecmp(key, "EIR") == 0) { + DBusMessageIter array; + uint8_t *eir; + int size; + + /* nokia.com:bt and EIR should not be passed together */ + if (bacmp(&remote->address, BDADDR_ANY) != 0) + goto error; + + if (dbus_message_iter_get_arg_type(&value) != + DBUS_TYPE_ARRAY) + goto error; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, &eir, &size); + + if (process_eir(eir, size, remote) < 0) + goto error; + } else if (strcasecmp(key, "nokia.com:bt") == 0) { + DBusMessageIter array; + uint8_t *data; + int size; + + /* nokia.com:bt and EIR should not be passed together */ + if (bacmp(&remote->address, BDADDR_ANY) != 0) + goto error; + + if (dbus_message_iter_get_arg_type(&value) != + DBUS_TYPE_ARRAY) + goto error; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, &data, &size); + + if (process_nokia_com_bt(data, size, remote)) + goto error; + } else if (strcasecmp(key, "State") == 0) { + DBusMessageIter array; + const char *state; + + if (dbus_message_iter_get_arg_type(&value) != + DBUS_TYPE_STRING) + goto error; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_basic(&value, &state); + + remote->power_state = process_state(state); + if (remote->power_state == CPS_UNKNOWN) + goto error; + } + + dbus_message_iter_next(&dict); + } + + /* Check if 'State' was passed along with one of other fields */ + if (remote->power_state != CPS_UNKNOWN + && bacmp(&remote->address, BDADDR_ANY) == 0) + return -EINVAL; + + return 0; + +error: + if (bacmp(&remote->address, BDADDR_ANY) != 0) { + free_oob_params(remote); + memset(remote, 0, sizeof(*remote)); + } + + return -EINVAL; +} + +static int check_adapter(struct btd_adapter *adapter) +{ + if (!adapter) + return -ENOENT; + + if (btd_adapter_check_oob_handler(adapter)) + return -EINPROGRESS; + + if (!btd_adapter_ssp_enabled(adapter)) + return -ENOTSUP; + + return 0; +} + +static void store_params(struct btd_adapter *adapter, struct btd_device *device, + struct oob_params *params) +{ + if (params->class != 0) + device_set_class(device, params->class); + + if (params->name) { + device_store_cached_name(device, params->name); + btd_device_device_set_name(device, params->name); + } + + if (params->services) + device_add_eir_uuids(device, params->services); + + if (params->hash) { + btd_adapter_add_remote_oob_data(adapter, ¶ms->address, + params->hash, + params->randomizer); + } else if (params->pin_len) { + /* TODO + * Handle PIN, for now only discovery mode and 'common' PINs + * that might be provided by agent will work correctly. + */ + } +} + +static DBusMessage *push_oob(DBusConnection *conn, DBusMessage *msg, void *data) +{ + struct btd_adapter *adapter; + struct agent *agent; + struct oob_handler *handler; + struct oob_params remote; + struct btd_device *device; + uint8_t io_cap; + int err; + + if (neard_service == NULL || + !g_str_equal(neard_service, dbus_message_get_sender(msg))) + return error_reply(msg, EPERM); + + DBG(""); + + adapter = btd_adapter_get_default(); + + err = check_adapter(adapter); + if (err < 0) + return error_reply(msg, -err); + + if (!btd_adapter_get_powered(adapter)) + return error_reply(msg, ENONET); + + agent = adapter_get_agent(adapter); + if (!agent) + return error_reply(msg, ENONET); + + io_cap = agent_get_io_capability(agent); + agent_unref(agent); + + memset(&remote, 0, sizeof(remote)); + + err = process_message(msg, &remote); + if (err < 0) + return error_reply(msg, -err); + + if (bacmp(&remote.address, BDADDR_ANY) == 0) { + free_oob_params(&remote); + + return error_reply(msg, EINVAL); + } + + device = btd_adapter_get_device(adapter, &remote.address, + BDADDR_BREDR); + + err = check_device(device); + if (err < 0) { + free_oob_params(&remote); + + /* already paired, reply immediately */ + if (err == -EALREADY) + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + + return error_reply(msg, -err); + } + + if (!btd_adapter_get_pairable(adapter)) { + free_oob_params(&remote); + + return error_reply(msg, ENONET); + } + + store_params(adapter, device, &remote); + + free_oob_params(&remote); + + err = adapter_create_bonding(adapter, device_get_address(device), + BDADDR_BREDR, io_cap); + if (err < 0) + return error_reply(msg, -err); + + handler = g_new0(struct oob_handler, 1); + handler->bonding_cb = bonding_complete; + bacpy(&handler->remote_addr, device_get_address(device)); + handler->user_data = dbus_message_ref(msg); + + btd_adapter_set_oob_handler(adapter, handler); + + return NULL; +} + +static DBusMessage *request_oob(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct btd_adapter *adapter; + struct oob_handler *handler; + struct oob_params remote; + struct btd_device *device; + int err; + + if (neard_service == NULL || + !g_str_equal(neard_service, dbus_message_get_sender(msg))) + return error_reply(msg, EPERM); + + DBG(""); + + adapter = btd_adapter_get_default(); + + err = check_adapter(adapter); + if (err < 0) + return error_reply(msg, -err); + + memset(&remote, 0, sizeof(remote)); + + err = process_message(msg, &remote); + if (err < 0) + return error_reply(msg, -err); + + if (bacmp(&remote.address, BDADDR_ANY) == 0) { + if (btd_adapter_get_powered(adapter)) + goto read_local; + + goto done; + } + + device = btd_adapter_get_device(adapter, &remote.address, BDADDR_BREDR); + + err = check_device(device); + if (err < 0) + goto done; + + if (!btd_adapter_get_pairable(adapter)) { + err = -ENONET; + goto done; + } + + store_params(adapter, device, &remote); + + if (remote.hash && btd_adapter_get_powered(adapter)) + goto read_local; +done: + free_oob_params(&remote); + + if (err < 0 && err != -EALREADY) + return error_reply(msg, -err); + + return create_request_oob_reply(adapter, NULL, NULL, msg); + +read_local: + free_oob_params(&remote); + + err = btd_adapter_read_local_oob_data(adapter); + if (err < 0) + return error_reply(msg, -err); + + handler = g_new0(struct oob_handler, 1); + handler->read_local_cb = read_local_complete; + handler->user_data = dbus_message_ref(msg); + + btd_adapter_set_oob_handler(adapter, handler); + + return NULL; +} + +static DBusMessage *release(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + if (neard_service == NULL || + !g_str_equal(neard_service, dbus_message_get_sender(msg))) + return error_reply(msg, EPERM); + + DBG(""); + + g_free(neard_service); + neard_service = NULL; + + g_dbus_unregister_interface(conn, AGENT_PATH, AGENT_INTERFACE); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static const GDBusMethodTable neard_methods[] = { + { GDBUS_ASYNC_METHOD("RequestOOB", + GDBUS_ARGS({ "data", "a{sv}" }), + GDBUS_ARGS({ "data", "a{sv}" }), request_oob) }, + { GDBUS_ASYNC_METHOD("PushOOB", + GDBUS_ARGS({ "data", "a{sv}"}), NULL, push_oob) }, + { GDBUS_METHOD("Release", NULL, NULL, release) }, + { } +}; + +static void neard_appeared(DBusConnection *conn, void *user_data) +{ + struct btd_adapter *adapter; + + DBG(""); + + if (!g_dbus_register_interface(conn, AGENT_PATH, AGENT_INTERFACE, + neard_methods, + NULL, NULL, NULL, NULL)) { + error("neard interface init failed on path " AGENT_PATH); + return; + } + + /* + * If there is pending action ongoing when neard appeared, possibly + * due to neard crash or release before action was completed, postpone + * register until action is finished. + */ + adapter = btd_adapter_get_default(); + + if (adapter && btd_adapter_check_oob_handler(adapter)) + agent_register_postpone = true; + else + register_agent(true); +} + +static void neard_vanished(DBusConnection *conn, void *user_data) +{ + DBG(""); + + /* neard existed without unregistering agent */ + if (neard_service != NULL) { + g_free(neard_service); + neard_service = NULL; + + g_dbus_unregister_interface(conn, AGENT_PATH, AGENT_INTERFACE); + } +} + +static int neard_init(void) +{ + DBG("Setup neard plugin"); + + watcher_id = g_dbus_add_service_watch(btd_get_dbus_connection(), + NEARD_NAME, neard_appeared, + neard_vanished, NULL, NULL); + if (watcher_id == 0) + return -ENOMEM; + + return 0; +} + +static void neard_exit(void) +{ + DBG("Cleanup neard plugin"); + + g_dbus_remove_watch(btd_get_dbus_connection(), watcher_id); + watcher_id = 0; + + if (neard_service != NULL) + unregister_agent(); +} + +BLUETOOTH_PLUGIN_DEFINE(neard, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + neard_init, neard_exit) diff --git a/plugins/policy.c b/plugins/policy.c new file mode 100644 index 0000000..de51e58 --- /dev/null +++ b/plugins/policy.c @@ -0,0 +1,890 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013 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 +#endif + +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" +#include "lib/mgmt.h" + +#include "src/log.h" +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/service.h" +#include "src/profile.h" +#include "src/hcid.h" + +#define CONTROL_CONNECT_TIMEOUT 2 +#define SOURCE_RETRY_TIMEOUT 2 +#define SINK_RETRY_TIMEOUT SOURCE_RETRY_TIMEOUT +#define CT_RETRY_TIMEOUT 1 +#define TG_RETRY_TIMEOUT CT_RETRY_TIMEOUT +#define SOURCE_RETRIES 1 +#define SINK_RETRIES SOURCE_RETRIES +#define CT_RETRIES 1 +#define TG_RETRIES CT_RETRIES + +struct reconnect_data { + struct btd_device *dev; + bool reconnect; + GSList *services; + guint timer; + bool active; + unsigned int attempt; +}; + +static const char *default_reconnect[] = { + HSP_AG_UUID, HFP_AG_UUID, A2DP_SOURCE_UUID, NULL }; +static char **reconnect_uuids = NULL; + +static const size_t default_attempts = 7; +static size_t reconnect_attempts = 0; + +static const int default_intervals[] = { 1, 2, 4, 8, 16, 32, 64 }; +static int *reconnect_intervals = NULL; +static size_t reconnect_intervals_len = 0; + +static GSList *reconnects = NULL; + +static unsigned int service_id = 0; +static GSList *devices = NULL; + +static bool auto_enable = false; + +struct policy_data { + struct btd_device *dev; + + guint source_timer; + uint8_t source_retries; + guint sink_timer; + uint8_t sink_retries; + guint ct_timer; + uint8_t ct_retries; + guint tg_timer; + uint8_t tg_retries; +}; + +static struct reconnect_data *reconnect_find(struct btd_device *dev) +{ + GSList *l; + + for (l = reconnects; l; l = g_slist_next(l)) { + struct reconnect_data *reconnect = l->data; + + if (reconnect->dev == dev) + return reconnect; + } + + return NULL; +} + +static void policy_connect(struct policy_data *data, + struct btd_service *service) +{ + struct btd_profile *profile = btd_service_get_profile(service); + struct reconnect_data *reconnect; + + reconnect = reconnect_find(btd_service_get_device(service)); + if (reconnect && reconnect->active) + return; + + DBG("%s profile %s", device_get_path(data->dev), profile->name); + + btd_service_connect(service); +} + +static void policy_disconnect(struct policy_data *data, + struct btd_service *service) +{ + struct btd_profile *profile = btd_service_get_profile(service); + + DBG("%s profile %s", device_get_path(data->dev), profile->name); + + btd_service_disconnect(service); +} + +static gboolean policy_connect_ct(gpointer user_data) +{ + struct policy_data *data = user_data; + struct btd_service *service; + + data->ct_timer = 0; + data->ct_retries++; + + service = btd_device_get_service(data->dev, AVRCP_REMOTE_UUID); + if (service != NULL) + policy_connect(data, service); + + return FALSE; +} + +static void policy_set_ct_timer(struct policy_data *data, int timeout) +{ + if (data->ct_timer > 0) + g_source_remove(data->ct_timer); + + data->ct_timer = g_timeout_add_seconds(timeout, policy_connect_ct, + data); +} + +static struct policy_data *find_data(struct btd_device *dev) +{ + GSList *l; + + for (l = devices; l; l = l->next) { + struct policy_data *data = l->data; + + if (data->dev == dev) + return data; + } + + return NULL; +} + +static void policy_remove(void *user_data) +{ + struct policy_data *data = user_data; + + if (data->source_timer > 0) + g_source_remove(data->source_timer); + + if (data->sink_timer > 0) + g_source_remove(data->sink_timer); + + if (data->ct_timer > 0) + g_source_remove(data->ct_timer); + + if (data->tg_timer > 0) + g_source_remove(data->tg_timer); + + g_free(data); +} + +static struct policy_data *policy_get_data(struct btd_device *dev) +{ + struct policy_data *data; + + data = find_data(dev); + if (data != NULL) + return data; + + data = g_new0(struct policy_data, 1); + data->dev = dev; + + devices = g_slist_prepend(devices, data); + + return data; +} + +static gboolean policy_connect_sink(gpointer user_data) +{ + struct policy_data *data = user_data; + struct btd_service *service; + + data->sink_timer = 0; + data->sink_retries++; + + service = btd_device_get_service(data->dev, A2DP_SINK_UUID); + if (service != NULL) + policy_connect(data, service); + + return FALSE; +} + +static void policy_set_sink_timer(struct policy_data *data) +{ + if (data->sink_timer > 0) + g_source_remove(data->sink_timer); + + data->sink_timer = g_timeout_add_seconds(SINK_RETRY_TIMEOUT, + policy_connect_sink, + data); +} + +static void sink_cb(struct btd_service *service, btd_service_state_t old_state, + btd_service_state_t new_state) +{ + struct btd_device *dev = btd_service_get_device(service); + struct policy_data *data; + struct btd_service *controller; + + controller = btd_device_get_service(dev, AVRCP_REMOTE_UUID); + if (controller == NULL) + return; + + data = policy_get_data(dev); + + switch (new_state) { + case BTD_SERVICE_STATE_UNAVAILABLE: + if (data->sink_timer > 0) { + g_source_remove(data->sink_timer); + data->sink_timer = 0; + } + break; + case BTD_SERVICE_STATE_DISCONNECTED: + if (old_state == BTD_SERVICE_STATE_CONNECTING) { + int err = btd_service_get_error(service); + + if (err == -EAGAIN) { + if (data->sink_retries < SINK_RETRIES) + policy_set_sink_timer(data); + else + data->sink_retries = 0; + break; + } else if (data->sink_timer > 0) { + g_source_remove(data->sink_timer); + data->sink_timer = 0; + } + } + + if (data->ct_timer > 0) { + g_source_remove(data->ct_timer); + data->ct_timer = 0; + } else if (btd_service_get_state(controller) != + BTD_SERVICE_STATE_DISCONNECTED) + policy_disconnect(data, controller); + break; + case BTD_SERVICE_STATE_CONNECTING: + break; + case BTD_SERVICE_STATE_CONNECTED: + if (data->sink_timer > 0) { + g_source_remove(data->sink_timer); + data->sink_timer = 0; + } + + /* Check if service initiate the connection then proceed + * immediatelly otherwise set timer + */ + if (old_state == BTD_SERVICE_STATE_CONNECTING) + policy_connect(data, controller); + else if (btd_service_get_state(controller) != + BTD_SERVICE_STATE_CONNECTED) + policy_set_ct_timer(data, CONTROL_CONNECT_TIMEOUT); + break; + case BTD_SERVICE_STATE_DISCONNECTING: + break; + } +} + +static void hs_cb(struct btd_service *service, btd_service_state_t old_state, + btd_service_state_t new_state) +{ + struct btd_device *dev = btd_service_get_device(service); + struct policy_data *data; + struct btd_service *sink; + + /* If the device supports Sink set a timer to connect it as well */ + sink = btd_device_get_service(dev, A2DP_SINK_UUID); + if (sink == NULL) + return; + + data = policy_get_data(dev); + + switch (new_state) { + case BTD_SERVICE_STATE_UNAVAILABLE: + break; + case BTD_SERVICE_STATE_DISCONNECTED: + break; + case BTD_SERVICE_STATE_CONNECTING: + break; + case BTD_SERVICE_STATE_CONNECTED: + /* Check if service initiate the connection then proceed + * immediately otherwise set timer + */ + if (old_state == BTD_SERVICE_STATE_CONNECTING) + policy_connect(data, sink); + else if (btd_service_get_state(sink) != + BTD_SERVICE_STATE_CONNECTED) + policy_set_sink_timer(data); + break; + case BTD_SERVICE_STATE_DISCONNECTING: + break; + } +} + +static gboolean policy_connect_tg(gpointer user_data) +{ + struct policy_data *data = user_data; + struct btd_service *service; + + data->tg_timer = 0; + data->tg_retries++; + + service = btd_device_get_service(data->dev, AVRCP_TARGET_UUID); + if (service != NULL) + policy_connect(data, service); + + return FALSE; +} + +static void policy_set_tg_timer(struct policy_data *data, int timeout) +{ + if (data->tg_timer > 0) + g_source_remove(data->tg_timer); + + data->tg_timer = g_timeout_add_seconds(timeout, policy_connect_tg, + data); +} + +static gboolean policy_connect_source(gpointer user_data) +{ + struct policy_data *data = user_data; + struct btd_service *service; + + data->source_timer = 0; + data->source_retries++; + + service = btd_device_get_service(data->dev, A2DP_SOURCE_UUID); + if (service != NULL) + policy_connect(data, service); + + return FALSE; +} + +static void policy_set_source_timer(struct policy_data *data) +{ + if (data->source_timer > 0) + g_source_remove(data->source_timer); + + data->source_timer = g_timeout_add_seconds(SOURCE_RETRY_TIMEOUT, + policy_connect_source, + data); +} + +static void source_cb(struct btd_service *service, + btd_service_state_t old_state, + btd_service_state_t new_state) +{ + struct btd_device *dev = btd_service_get_device(service); + struct policy_data *data; + struct btd_service *target; + + target = btd_device_get_service(dev, AVRCP_TARGET_UUID); + if (target == NULL) + return; + + data = policy_get_data(dev); + + switch (new_state) { + case BTD_SERVICE_STATE_UNAVAILABLE: + if (data->source_timer > 0) { + g_source_remove(data->source_timer); + data->source_timer = 0; + } + break; + case BTD_SERVICE_STATE_DISCONNECTED: + if (old_state == BTD_SERVICE_STATE_CONNECTING) { + int err = btd_service_get_error(service); + + if (err == -EAGAIN) { + if (data->source_retries < SOURCE_RETRIES) + policy_set_source_timer(data); + else + data->source_retries = 0; + break; + } else if (data->source_timer > 0) { + g_source_remove(data->source_timer); + data->source_timer = 0; + } + } + + if (data->tg_timer > 0) { + g_source_remove(data->tg_timer); + data->tg_timer = 0; + } else if (btd_service_get_state(target) != + BTD_SERVICE_STATE_DISCONNECTED) + policy_disconnect(data, target); + break; + case BTD_SERVICE_STATE_CONNECTING: + break; + case BTD_SERVICE_STATE_CONNECTED: + if (data->source_timer > 0) { + g_source_remove(data->source_timer); + data->source_timer = 0; + } + + /* Check if service initiate the connection then proceed + * immediatelly otherwise set timer + */ + if (old_state == BTD_SERVICE_STATE_CONNECTING) + policy_connect(data, target); + else if (btd_service_get_state(target) != + BTD_SERVICE_STATE_CONNECTED) + policy_set_tg_timer(data, CONTROL_CONNECT_TIMEOUT); + break; + case BTD_SERVICE_STATE_DISCONNECTING: + break; + } +} + +static void controller_cb(struct btd_service *service, + btd_service_state_t old_state, + btd_service_state_t new_state) +{ + struct btd_device *dev = btd_service_get_device(service); + struct policy_data *data; + + data = find_data(dev); + if (data == NULL) + return; + + switch (new_state) { + case BTD_SERVICE_STATE_UNAVAILABLE: + if (data->ct_timer > 0) { + g_source_remove(data->ct_timer); + data->ct_timer = 0; + } + break; + case BTD_SERVICE_STATE_DISCONNECTED: + if (old_state == BTD_SERVICE_STATE_CONNECTING) { + int err = btd_service_get_error(service); + + if (err == -EAGAIN) { + if (data->ct_retries < CT_RETRIES) + policy_set_ct_timer(data, + CT_RETRY_TIMEOUT); + else + data->ct_retries = 0; + break; + } else if (data->ct_timer > 0) { + g_source_remove(data->ct_timer); + data->ct_timer = 0; + } + } else if (old_state == BTD_SERVICE_STATE_CONNECTED) { + data->ct_retries = 0; + } + break; + case BTD_SERVICE_STATE_CONNECTING: + break; + case BTD_SERVICE_STATE_CONNECTED: + if (data->ct_timer > 0) { + g_source_remove(data->ct_timer); + data->ct_timer = 0; + } + break; + case BTD_SERVICE_STATE_DISCONNECTING: + break; + } +} + +static void target_cb(struct btd_service *service, + btd_service_state_t old_state, + btd_service_state_t new_state) +{ + struct btd_device *dev = btd_service_get_device(service); + struct policy_data *data; + + data = find_data(dev); + if (data == NULL) + return; + + switch (new_state) { + case BTD_SERVICE_STATE_UNAVAILABLE: + if (data->tg_timer > 0) { + g_source_remove(data->tg_timer); + data->tg_timer = 0; + } + break; + case BTD_SERVICE_STATE_DISCONNECTED: + if (old_state == BTD_SERVICE_STATE_CONNECTING) { + int err = btd_service_get_error(service); + + if (err == -EAGAIN) { + if (data->tg_retries < TG_RETRIES) + policy_set_tg_timer(data, + TG_RETRY_TIMEOUT); + else + data->tg_retries = 0; + break; + } else if (data->tg_timer > 0) { + g_source_remove(data->tg_timer); + data->tg_timer = 0; + } + } else if (old_state == BTD_SERVICE_STATE_CONNECTED) { + data->tg_retries = 0; + } + break; + case BTD_SERVICE_STATE_CONNECTING: + break; + case BTD_SERVICE_STATE_CONNECTED: + if (data->tg_timer > 0) { + g_source_remove(data->tg_timer); + data->tg_timer = 0; + } + break; + case BTD_SERVICE_STATE_DISCONNECTING: + break; + } +} + +static void reconnect_reset(struct reconnect_data *reconnect) +{ + reconnect->attempt = 0; + reconnect->active = false; + + if (reconnect->timer > 0) { + g_source_remove(reconnect->timer); + reconnect->timer = 0; + } +} + +static bool reconnect_match(const char *uuid) +{ + char **str; + + if (!reconnect_uuids) + return false; + + for (str = reconnect_uuids; *str; str++) { + if (!bt_uuid_strcmp(uuid, *str)) + return true; + } + + return false; +} + +static struct reconnect_data *reconnect_add(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + struct reconnect_data *reconnect; + + reconnect = reconnect_find(dev); + if (!reconnect) { + reconnect = g_new0(struct reconnect_data, 1); + reconnect->dev = dev; + reconnects = g_slist_append(reconnects, reconnect); + } + + if (g_slist_find(reconnect->services, service)) + return reconnect; + + reconnect->services = g_slist_append(reconnect->services, + btd_service_ref(service)); + + return reconnect; +} + +static void reconnect_destroy(gpointer data) +{ + struct reconnect_data *reconnect = data; + + if (reconnect->timer > 0) + g_source_remove(reconnect->timer); + + g_slist_free_full(reconnect->services, + (GDestroyNotify) btd_service_unref); + g_free(reconnect); +} + +static void reconnect_remove(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + struct reconnect_data *reconnect; + GSList *l; + + reconnect = reconnect_find(dev); + if (!reconnect) + return; + + l = g_slist_find(reconnect->services, service); + if (!l) + return; + + reconnect->services = g_slist_delete_link(reconnect->services, l); + btd_service_unref(service); + + if (reconnect->services) + return; + + reconnects = g_slist_remove(reconnects, reconnect); + + if (reconnect->timer > 0) + g_source_remove(reconnect->timer); + + g_free(reconnect); +} + +static void service_cb(struct btd_service *service, + btd_service_state_t old_state, + btd_service_state_t new_state, + void *user_data) +{ + struct btd_profile *profile = btd_service_get_profile(service); + struct reconnect_data *reconnect; + + if (g_str_equal(profile->remote_uuid, A2DP_SINK_UUID)) + sink_cb(service, old_state, new_state); + else if (g_str_equal(profile->remote_uuid, A2DP_SOURCE_UUID)) + source_cb(service, old_state, new_state); + else if (g_str_equal(profile->remote_uuid, AVRCP_REMOTE_UUID)) + controller_cb(service, old_state, new_state); + else if (g_str_equal(profile->remote_uuid, AVRCP_TARGET_UUID)) + target_cb(service, old_state, new_state); + else if (g_str_equal(profile->remote_uuid, HFP_HS_UUID) || + g_str_equal(profile->remote_uuid, HSP_HS_UUID)) + hs_cb(service, old_state, new_state); + + /* + * Return if the reconnection feature is not enabled (all + * subsequent code in this function is about that). + */ + if (!reconnect_uuids || !reconnect_uuids[0]) + return; + + /* + * We're only interested in reconnecting profiles which have set + * auto_connect to true. + */ + if (!profile->auto_connect) + return; + + /* + * If the service went away remove it from the reconnection + * tracking. The function will remove the entire tracking data + * if this was the last service for the device. + */ + if (new_state == BTD_SERVICE_STATE_UNAVAILABLE) { + reconnect_remove(service); + return; + } + + if (new_state != BTD_SERVICE_STATE_CONNECTED) + return; + + /* + * Add an entry to track reconnections. The function will return + * an existing entry if there is one. + */ + reconnect = reconnect_add(service); + + reconnect->active = false; + + /* + * Should this device be reconnected? A matching UUID might not + * be the first profile that's connected so we might have an + * entry but with the reconnect flag set to false. + */ + if (!reconnect->reconnect) + reconnect->reconnect = reconnect_match(profile->remote_uuid); + + DBG("Added %s reconnect %u", profile->name, reconnect->reconnect); +} + +static gboolean reconnect_timeout(gpointer data) +{ + struct reconnect_data *reconnect = data; + int err; + + DBG("Reconnecting profiles"); + + /* Mark the GSource as invalid */ + reconnect->timer = 0; + + err = btd_device_connect_services(reconnect->dev, reconnect->services); + if (err < 0) { + error("Reconnecting services failed: %s (%d)", + strerror(-err), -err); + reconnect_reset(reconnect); + return FALSE; + } + + reconnect->attempt++; + + return FALSE; +} + +static void reconnect_set_timer(struct reconnect_data *reconnect) +{ + static int timeout = 0; + + reconnect->active = true; + + if (reconnect->attempt < reconnect_intervals_len) + timeout = reconnect_intervals[reconnect->attempt]; + + DBG("attempt %u/%zu %d seconds", reconnect->attempt + 1, + reconnect_attempts, timeout); + + reconnect->timer = g_timeout_add_seconds(timeout, reconnect_timeout, + reconnect); +} + +static void disconnect_cb(struct btd_device *dev, uint8_t reason) +{ + struct reconnect_data *reconnect; + + DBG("reason %u", reason); + + if (reason != MGMT_DEV_DISCONN_TIMEOUT) + return; + + reconnect = reconnect_find(dev); + if (!reconnect || !reconnect->reconnect) + return; + + reconnect_reset(reconnect); + + DBG("Device %s identified for auto-reconnection", + device_get_path(dev)); + + reconnect_set_timer(reconnect); +} + +static void conn_fail_cb(struct btd_device *dev, uint8_t status) +{ + struct reconnect_data *reconnect; + + DBG("status %u", status); + + reconnect = reconnect_find(dev); + if (!reconnect || !reconnect->reconnect) + return; + + if (!reconnect->active) + return; + + /* Give up if we were powered off */ + if (status == MGMT_STATUS_NOT_POWERED) { + reconnect_reset(reconnect); + return; + } + + /* Reset if ReconnectAttempts was reached */ + if (reconnect->attempt == reconnect_attempts) { + reconnect_reset(reconnect); + return; + } + + reconnect_set_timer(reconnect); +} + +static int policy_adapter_probe(struct btd_adapter *adapter) +{ + DBG(""); + + btd_adapter_restore_powered(adapter); + + return 0; +} + +static struct btd_adapter_driver policy_driver = { + .name = "policy", + .probe = policy_adapter_probe, +}; + +static int policy_init(void) +{ + GError *gerr = NULL; + GKeyFile *conf; + + service_id = btd_service_add_state_cb(service_cb, NULL); + + conf = btd_get_main_conf(); + if (!conf) { + reconnect_uuids = g_strdupv((char **) default_reconnect); + reconnect_attempts = default_attempts; + reconnect_intervals_len = sizeof(default_intervals) / + sizeof(*reconnect_intervals); + reconnect_intervals = g_memdup(default_intervals, + sizeof(default_intervals)); + goto done; + } + + g_key_file_set_list_separator(conf, ','); + + reconnect_uuids = g_key_file_get_string_list(conf, "Policy", + "ReconnectUUIDs", + NULL, &gerr); + if (gerr) { + g_clear_error(&gerr); + reconnect_uuids = g_strdupv((char **) default_reconnect); + } + + reconnect_attempts = g_key_file_get_integer(conf, "Policy", + "ReconnectAttempts", + &gerr); + if (gerr) { + g_clear_error(&gerr); + reconnect_attempts = default_attempts; + } + + reconnect_intervals = g_key_file_get_integer_list(conf, "Policy", + "ReconnectIntervals", + (size_t *) &reconnect_intervals_len, + &gerr); + if (gerr) { + g_clear_error(&gerr); + reconnect_intervals_len = sizeof(default_intervals) / + sizeof(*reconnect_intervals); + reconnect_intervals = g_memdup(default_intervals, + sizeof(default_intervals)); + } + + auto_enable = g_key_file_get_boolean(conf, "Policy", "AutoEnable", + NULL); + +done: + if (reconnect_uuids && reconnect_uuids[0] && reconnect_attempts) { + btd_add_disconnect_cb(disconnect_cb); + btd_add_conn_fail_cb(conn_fail_cb); + } + + if (auto_enable) + btd_register_adapter_driver(&policy_driver); + + return 0; +} + +static void policy_exit(void) +{ + btd_remove_disconnect_cb(disconnect_cb); + btd_remove_conn_fail_cb(conn_fail_cb); + + if (reconnect_uuids) + g_strfreev(reconnect_uuids); + + g_free(reconnect_intervals); + + g_slist_free_full(reconnects, reconnect_destroy); + + g_slist_free_full(devices, policy_remove); + + btd_service_remove_state_cb(service_id); + + if (auto_enable) + btd_unregister_adapter_driver(&policy_driver); +} + +BLUETOOTH_PLUGIN_DEFINE(policy, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + policy_init, policy_exit) diff --git a/plugins/sixaxis.c b/plugins/sixaxis.c new file mode 100644 index 0000000..939fed7 --- /dev/null +++ b/plugins/sixaxis.c @@ -0,0 +1,553 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009 Bastien Nocera + * Copyright (C) 2011 Antonio Ospite + * Copyright (C) 2013 Szymon Janc + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/adapter.h" +#include "src/device.h" +#include "src/agent.h" +#include "src/plugin.h" +#include "src/log.h" +#include "src/shared/util.h" +#include "profiles/input/sixaxis.h" + +struct authentication_closure { + guint auth_id; + char *sysfs_path; + struct btd_adapter *adapter; + struct btd_device *device; + int fd; + bdaddr_t bdaddr; /* device bdaddr */ + CablePairingType type; +}; + +struct authentication_destroy_closure { + struct authentication_closure *closure; + bool remove_device; +}; + +static struct udev *ctx = NULL; +static struct udev_monitor *monitor = NULL; +static guint watch_id = 0; +/* key = sysfs_path (const str), value = auth_closure */ +static GHashTable *pending_auths = NULL; + +#define SIXAXIS_HID_SDP_RECORD "3601920900000A000100000900013503191124090004"\ + "350D35061901000900113503190011090006350909656E09006A090100090009350"\ + "8350619112409010009000D350F350D350619010009001335031900110901002513"\ + "576972656C65737320436F6E74726F6C6C65720901012513576972656C657373204"\ + "36F6E74726F6C6C6572090102251B536F6E7920436F6D707574657220456E746572"\ + "7461696E6D656E74090200090100090201090100090202080009020308210902042"\ + "8010902052801090206359A35980822259405010904A101A1028501750895011500"\ + "26FF00810375019513150025013500450105091901291381027501950D0600FF810"\ + "3150026FF0005010901A10075089504350046FF0009300931093209358102C00501"\ + "75089527090181027508953009019102750895300901B102C0A1028502750895300"\ + "901B102C0A10285EE750895300901B102C0A10285EF750895300901B102C0C00902"\ + "07350835060904090901000902082800090209280109020A280109020B090100090"\ + "20C093E8009020D280009020E2800" + +/* Make sure to unset auth_id if already handled */ +static void auth_closure_destroy(struct authentication_closure *closure, + bool remove_device) +{ + if (closure->auth_id) + btd_cancel_authorization(closure->auth_id); + + if (remove_device) + btd_adapter_remove_device(closure->adapter, closure->device); + close(closure->fd); + g_free(closure->sysfs_path); + g_free(closure); +} + +static int sixaxis_get_device_bdaddr(int fd, bdaddr_t *bdaddr) +{ + uint8_t buf[18]; + int ret; + + memset(buf, 0, sizeof(buf)); + + buf[0] = 0xf2; + + ret = ioctl(fd, HIDIOCGFEATURE(sizeof(buf)), buf); + if (ret < 0) { + error("sixaxis: failed to read device address (%s)", + strerror(errno)); + return ret; + } + + baswap(bdaddr, (bdaddr_t *) (buf + 4)); + + return 0; +} + +static int ds4_get_device_bdaddr(int fd, bdaddr_t *bdaddr) +{ + uint8_t buf[7]; + int ret; + + memset(buf, 0, sizeof(buf)); + + buf[0] = 0x81; + + ret = ioctl(fd, HIDIOCGFEATURE(sizeof(buf)), buf); + if (ret < 0) { + error("sixaxis: failed to read DS4 device address (%s)", + strerror(errno)); + return ret; + } + + /* address is little-endian on DS4 */ + bacpy(bdaddr, (bdaddr_t*) (buf + 1)); + + return 0; +} + +static int get_device_bdaddr(int fd, bdaddr_t *bdaddr, CablePairingType type) +{ + if (type == CABLE_PAIRING_SIXAXIS) + return sixaxis_get_device_bdaddr(fd, bdaddr); + else if (type == CABLE_PAIRING_DS4) + return ds4_get_device_bdaddr(fd, bdaddr); + return -1; +} + +static int sixaxis_get_master_bdaddr(int fd, bdaddr_t *bdaddr) +{ + uint8_t buf[8]; + int ret; + + memset(buf, 0, sizeof(buf)); + + buf[0] = 0xf5; + + ret = ioctl(fd, HIDIOCGFEATURE(sizeof(buf)), buf); + if (ret < 0) { + error("sixaxis: failed to read master address (%s)", + strerror(errno)); + return ret; + } + + baswap(bdaddr, (bdaddr_t *) (buf + 2)); + + return 0; +} + +static int ds4_get_master_bdaddr(int fd, bdaddr_t *bdaddr) +{ + uint8_t buf[16]; + int ret; + + memset(buf, 0, sizeof(buf)); + + buf[0] = 0x12; + + ret = ioctl(fd, HIDIOCGFEATURE(sizeof(buf)), buf); + if (ret < 0) { + error("sixaxis: failed to read DS4 master address (%s)", + strerror(errno)); + return ret; + } + + /* address is little-endian on DS4 */ + bacpy(bdaddr, (bdaddr_t*) (buf + 10)); + + return 0; +} + +static int get_master_bdaddr(int fd, bdaddr_t *bdaddr, CablePairingType type) +{ + if (type == CABLE_PAIRING_SIXAXIS) + return sixaxis_get_master_bdaddr(fd, bdaddr); + else if (type == CABLE_PAIRING_DS4) + return ds4_get_master_bdaddr(fd, bdaddr); + return -1; +} + +static int sixaxis_set_master_bdaddr(int fd, const bdaddr_t *bdaddr) +{ + uint8_t buf[8]; + int ret; + + buf[0] = 0xf5; + buf[1] = 0x01; + + baswap((bdaddr_t *) (buf + 2), bdaddr); + + ret = ioctl(fd, HIDIOCSFEATURE(sizeof(buf)), buf); + if (ret < 0) + error("sixaxis: failed to write master address (%s)", + strerror(errno)); + + return ret; +} + +static int ds4_set_master_bdaddr(int fd, const bdaddr_t *bdaddr) +{ + uint8_t buf[23]; + int ret; + + buf[0] = 0x13; + bacpy((bdaddr_t*) (buf + 1), bdaddr); + /* TODO: we could put the key here but + there is no way to force a re-loading + of link keys to the kernel from here. */ + memset(buf + 7, 0, 16); + + ret = ioctl(fd, HIDIOCSFEATURE(sizeof(buf)), buf); + if (ret < 0) + error("sixaxis: failed to write DS4 master address (%s)", + strerror(errno)); + + return ret; +} + +static int set_master_bdaddr(int fd, const bdaddr_t *bdaddr, + CablePairingType type) +{ + if (type == CABLE_PAIRING_SIXAXIS) + return sixaxis_set_master_bdaddr(fd, bdaddr); + else if (type == CABLE_PAIRING_DS4) + return ds4_set_master_bdaddr(fd, bdaddr); + return -1; +} + +static bool is_auth_pending(struct authentication_closure *closure) +{ + GHashTableIter iter; + gpointer value; + + g_hash_table_iter_init(&iter, pending_auths); + while (g_hash_table_iter_next(&iter, NULL, &value)) { + struct authentication_closure *c = value; + if (c == closure) + return true; + } + return false; +} + +static gboolean auth_closure_destroy_idle(gpointer user_data) +{ + struct authentication_destroy_closure *destroy = user_data; + + auth_closure_destroy(destroy->closure, destroy->remove_device); + g_free(destroy); + + return false; +} + +static void agent_auth_cb(DBusError *derr, void *user_data) +{ + struct authentication_closure *closure = user_data; + struct authentication_destroy_closure *destroy; + char master_addr[18], adapter_addr[18], device_addr[18]; + bdaddr_t master_bdaddr; + const bdaddr_t *adapter_bdaddr; + bool remove_device = true; + + if (!is_auth_pending(closure)) + return; + + /* Don't try to remove this auth, we're handling it already */ + closure->auth_id = 0; + + if (derr != NULL) { + DBG("Agent replied negatively, removing temporary device"); + goto out; + } + + if (get_master_bdaddr(closure->fd, &master_bdaddr, closure->type) < 0) + goto out; + + adapter_bdaddr = btd_adapter_get_address(closure->adapter); + if (bacmp(adapter_bdaddr, &master_bdaddr)) { + if (set_master_bdaddr(closure->fd, adapter_bdaddr, + closure->type) < 0) + goto out; + } + + remove_device = false; + btd_device_set_trusted(closure->device, true); + btd_device_set_temporary(closure->device, false); + + if (closure->type == CABLE_PAIRING_SIXAXIS) + btd_device_set_record(closure->device, HID_UUID, + SIXAXIS_HID_SDP_RECORD); + + ba2str(&closure->bdaddr, device_addr); + ba2str(&master_bdaddr, master_addr); + ba2str(adapter_bdaddr, adapter_addr); + DBG("remote %s old_master %s new_master %s", + device_addr, master_addr, adapter_addr); + +out: + g_hash_table_steal(pending_auths, closure->sysfs_path); + + /* btd_adapter_remove_device() cannot be called in this + * callback or it would lead to a double-free in while + * trying to cancel the authentication that's being processed, + * so clean up in an idle */ + destroy = g_new0(struct authentication_destroy_closure, 1); + destroy->closure = closure; + destroy->remove_device = remove_device; + g_idle_add(auth_closure_destroy_idle, destroy); +} + +static bool setup_device(int fd, const char *sysfs_path, + const struct cable_pairing *cp, + struct btd_adapter *adapter) +{ + bdaddr_t device_bdaddr; + const bdaddr_t *adapter_bdaddr; + struct btd_device *device; + struct authentication_closure *closure; + + if (get_device_bdaddr(fd, &device_bdaddr, cp->type) < 0) + return false; + + /* This can happen if controller was plugged while already setup and + * connected eg. to charge up battery. */ + device = btd_adapter_find_device(adapter, &device_bdaddr, + BDADDR_BREDR); + if (device != NULL && + btd_device_is_connected(device) && + g_slist_find_custom(btd_device_get_uuids(device), HID_UUID, + (GCompareFunc)strcasecmp)) { + char device_addr[18]; + ba2str(&device_bdaddr, device_addr); + DBG("device %s already known, skipping", device_addr); + return false; + } + + device = btd_adapter_get_device(adapter, &device_bdaddr, BDADDR_BREDR); + + info("sixaxis: setting up new device"); + + btd_device_device_set_name(device, cp->name); + btd_device_set_pnpid(device, cp->source, cp->vid, cp->pid, cp->version); + btd_device_set_trusted(device, false); + btd_device_set_temporary(device, true); + + closure = g_new0(struct authentication_closure, 1); + if (!closure) { + btd_adapter_remove_device(adapter, device); + return false; + } + closure->adapter = adapter; + closure->device = device; + closure->sysfs_path = g_strdup(sysfs_path); + closure->fd = fd; + bacpy(&closure->bdaddr, &device_bdaddr); + closure->type = cp->type; + adapter_bdaddr = btd_adapter_get_address(adapter); + closure->auth_id = btd_request_authorization_cable_configured( + adapter_bdaddr, &device_bdaddr, + HID_UUID, agent_auth_cb, closure); + + if (closure->auth_id == 0) { + error("sixaxis: could not request cable authorization"); + auth_closure_destroy(closure, true); + return false; + } + + g_hash_table_insert(pending_auths, closure->sysfs_path, closure); + + return true; +} + +static const struct cable_pairing * +get_pairing_type_for_device(struct udev_device *udevice, uint16_t *bus, + char **sysfs_path) +{ + struct udev_device *hid_parent; + const char *hid_id; + const struct cable_pairing *cp; + uint16_t vid, pid; + + hid_parent = udev_device_get_parent_with_subsystem_devtype(udevice, + "hid", NULL); + if (!hid_parent) + return NULL; + + hid_id = udev_device_get_property_value(hid_parent, "HID_ID"); + + if (sscanf(hid_id, "%hx:%hx:%hx", bus, &vid, &pid) != 3) + return NULL; + + cp = get_pairing(vid, pid); + *sysfs_path = g_strdup(udev_device_get_syspath(udevice)); + + return cp; +} + +static void device_added(struct udev_device *udevice) +{ + struct btd_adapter *adapter; + uint16_t bus; + char *sysfs_path = NULL; + const struct cable_pairing *cp; + int fd; + + adapter = btd_adapter_get_default(); + if (!adapter) + return; + + cp = get_pairing_type_for_device(udevice, &bus, &sysfs_path); + if (!cp || (cp->type != CABLE_PAIRING_SIXAXIS && + cp->type != CABLE_PAIRING_DS4)) + return; + if (bus != BUS_USB) + return; + + info("sixaxis: compatible device connected: %s (%04X:%04X %s)", + cp->name, cp->vid, cp->pid, sysfs_path); + + fd = open(udev_device_get_devnode(udevice), O_RDWR); + if (fd < 0) { + g_free(sysfs_path); + return; + } + + /* Only close the fd if an authentication is not pending */ + if (!setup_device(fd, sysfs_path, cp, adapter)) + close(fd); + + g_free(sysfs_path); +} + +static void device_removed(struct udev_device *udevice) +{ + struct authentication_closure *closure; + const char *sysfs_path; + + sysfs_path = udev_device_get_syspath(udevice); + if (!sysfs_path) + return; + + closure = g_hash_table_lookup(pending_auths, sysfs_path); + if (!closure) + return; + + g_hash_table_steal(pending_auths, sysfs_path); + auth_closure_destroy(closure, true); +} + +static gboolean monitor_watch(GIOChannel *source, GIOCondition condition, + gpointer data) +{ + struct udev_device *udevice; + + udevice = udev_monitor_receive_device(monitor); + if (!udevice) + return TRUE; + + if (!g_strcmp0(udev_device_get_action(udevice), "add")) + device_added(udevice); + else if (!g_strcmp0(udev_device_get_action(udevice), "remove")) + device_removed(udevice); + + udev_device_unref(udevice); + + return TRUE; +} + +static int sixaxis_init(void) +{ + GIOChannel *channel; + + DBG(""); + + ctx = udev_new(); + if (!ctx) + return -EIO; + + monitor = udev_monitor_new_from_netlink(ctx, "udev"); + if (!monitor) { + udev_unref(ctx); + ctx = NULL; + + return -EIO; + } + + /* Listen for newly connected hidraw interfaces */ + udev_monitor_filter_add_match_subsystem_devtype(monitor, "hidraw", + NULL); + udev_monitor_enable_receiving(monitor); + + channel = g_io_channel_unix_new(udev_monitor_get_fd(monitor)); + watch_id = g_io_add_watch(channel, G_IO_IN, monitor_watch, NULL); + g_io_channel_unref(channel); + + pending_auths = g_hash_table_new(g_str_hash, + g_str_equal); + + return 0; +} + +static void sixaxis_exit(void) +{ + GHashTableIter iter; + gpointer value; + + DBG(""); + + g_hash_table_iter_init(&iter, pending_auths); + while (g_hash_table_iter_next(&iter, NULL, &value)) { + struct authentication_closure *closure = value; + auth_closure_destroy(closure, true); + } + g_hash_table_destroy(pending_auths); + pending_auths = NULL; + + g_source_remove(watch_id); + watch_id = 0; + + udev_monitor_unref(monitor); + monitor = NULL; + + udev_unref(ctx); + ctx = NULL; +} + +BLUETOOTH_PLUGIN_DEFINE(sixaxis, VERSION, BLUETOOTH_PLUGIN_PRIORITY_LOW, + sixaxis_init, sixaxis_exit) diff --git a/plugins/wiimote.c b/plugins/wiimote.c new file mode 100644 index 0000000..0ced275 --- /dev/null +++ b/plugins/wiimote.c @@ -0,0 +1,143 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 David Herrmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "bluetooth/bluetooth.h" +#include "bluetooth/sdp.h" + +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/log.h" +#include "src/storage.h" + +/* + * Nintendo Wii Remote devices require the bdaddr of the host as pin input for + * authentication. This plugin registers a pin-callback and forces this pin + * to be used for authentication. + * + * There are two ways to place the wiimote into discoverable mode. + * - Pressing the red-sync button on the back of the wiimote. This module + * supports pairing via this method. Auto-reconnect should be possible after + * the device was paired once. + * - Pressing the 1+2 buttons on the front of the wiimote. This module does + * not support this method since this method never enables auto-reconnect. + * Hence, pairing is not needed. Use it without pairing if you want. + * After connecting the wiimote you should immediately connect to the input + * service of the wiimote. If you don't, the wiimote will close the connection. + * The wiimote waits about 5 seconds until it turns off again. + * Auto-reconnect is only enabled when pairing with the wiimote via the red + * sync-button and then connecting to the input service. If you do not connect + * to the input service, then auto-reconnect is not enabled. + * If enabled, the wiimote connects to the host automatically when any button + * is pressed. + */ + +static uint16_t wii_ids[][2] = { + { 0x057e, 0x0306 }, /* 1st gen */ + { 0x054c, 0x0306 }, /* LEGO wiimote */ + { 0x057e, 0x0330 }, /* 2nd gen */ +}; + +static const char *wii_names[] = { + "Nintendo RVL-CNT-01", /* 1st gen */ + "Nintendo RVL-CNT-01-TR", /* 2nd gen */ + "Nintendo RVL-CNT-01-UC", /* Wii U Pro Controller */ + "Nintendo RVL-WBC-01", /* Balance Board */ +}; + +static ssize_t wii_pincb(struct btd_adapter *adapter, struct btd_device *device, + char *pinbuf, bool *display, + unsigned int attempt) +{ + uint16_t vendor, product; + char addr[18], name[25]; + unsigned int i; + + /* Only try the pin code once per device. If it's not correct then it's + * an unknown device. */ + if (attempt > 1) + return 0; + + ba2str(device_get_address(device), addr); + + vendor = btd_device_get_vendor(device); + product = btd_device_get_product(device); + + device_get_name(device, name, sizeof(name)); + + for (i = 0; i < G_N_ELEMENTS(wii_ids); ++i) { + if (vendor == wii_ids[i][0] && product == wii_ids[i][1]) + goto found; + } + + for (i = 0; i < G_N_ELEMENTS(wii_names); ++i) { + if (g_str_equal(name, wii_names[i])) + goto found; + } + + return 0; + +found: + DBG("Forcing fixed pin on detected wiimote %s", addr); + memcpy(pinbuf, btd_adapter_get_address(adapter), 6); + return 6; +} + +static int wii_probe(struct btd_adapter *adapter) +{ + btd_adapter_register_pin_cb(adapter, wii_pincb); + + return 0; +} + +static void wii_remove(struct btd_adapter *adapter) +{ + btd_adapter_unregister_pin_cb(adapter, wii_pincb); +} + +static struct btd_adapter_driver wii_driver = { + .name = "wiimote", + .probe = wii_probe, + .remove = wii_remove, +}; + +static int wii_init(void) +{ + return btd_register_adapter_driver(&wii_driver); +} + +static void wii_exit(void) +{ + btd_unregister_adapter_driver(&wii_driver); +} + +BLUETOOTH_PLUGIN_DEFINE(wiimote, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_LOW, wii_init, wii_exit) diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h new file mode 100644 index 0000000..93e9d35 --- /dev/null +++ b/profiles/audio/a2dp-codecs.h @@ -0,0 +1,435 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2018 Pali Rohár + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG12 0x01 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_ATRAC 0x04 +#define A2DP_CODEC_VENDOR 0xFF + +#define SBC_SAMPLING_FREQ_16000 (1 << 3) +#define SBC_SAMPLING_FREQ_32000 (1 << 2) +#define SBC_SAMPLING_FREQ_44100 (1 << 1) +#define SBC_SAMPLING_FREQ_48000 1 + +#define SBC_CHANNEL_MODE_MONO (1 << 3) +#define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define SBC_CHANNEL_MODE_STEREO (1 << 1) +#define SBC_CHANNEL_MODE_JOINT_STEREO 1 + +#define SBC_BLOCK_LENGTH_4 (1 << 3) +#define SBC_BLOCK_LENGTH_8 (1 << 2) +#define SBC_BLOCK_LENGTH_12 (1 << 1) +#define SBC_BLOCK_LENGTH_16 1 + +#define SBC_SUBBANDS_4 (1 << 1) +#define SBC_SUBBANDS_8 1 + +#define SBC_ALLOCATION_SNR (1 << 1) +#define SBC_ALLOCATION_LOUDNESS 1 + +#define SBC_MIN_BITPOOL 2 +#define SBC_MAX_BITPOOL 250 + +/* Other settings: + * Block length = 16 + * Allocation method = Loudness + * Subbands = 8 + */ +#define SBC_BITPOOL_MQ_MONO_44100 19 +#define SBC_BITPOOL_MQ_MONO_48000 18 +#define SBC_BITPOOL_MQ_JOINT_STEREO_44100 35 +#define SBC_BITPOOL_MQ_JOINT_STEREO_48000 33 +#define SBC_BITPOOL_HQ_MONO_44100 31 +#define SBC_BITPOOL_HQ_MONO_48000 29 +#define SBC_BITPOOL_HQ_JOINT_STEREO_44100 53 +#define SBC_BITPOOL_HQ_JOINT_STEREO_48000 51 + +#define MPEG_CHANNEL_MODE_MONO (1 << 3) +#define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define MPEG_CHANNEL_MODE_STEREO (1 << 1) +#define MPEG_CHANNEL_MODE_JOINT_STEREO 1 + +#define MPEG_LAYER_MP1 (1 << 2) +#define MPEG_LAYER_MP2 (1 << 1) +#define MPEG_LAYER_MP3 1 + +#define MPEG_SAMPLING_FREQ_16000 (1 << 5) +#define MPEG_SAMPLING_FREQ_22050 (1 << 4) +#define MPEG_SAMPLING_FREQ_24000 (1 << 3) +#define MPEG_SAMPLING_FREQ_32000 (1 << 2) +#define MPEG_SAMPLING_FREQ_44100 (1 << 1) +#define MPEG_SAMPLING_FREQ_48000 1 + +#define MPEG_BIT_RATE_INDEX_0 (1 << 0) +#define MPEG_BIT_RATE_INDEX_1 (1 << 1) +#define MPEG_BIT_RATE_INDEX_2 (1 << 2) +#define MPEG_BIT_RATE_INDEX_3 (1 << 3) +#define MPEG_BIT_RATE_INDEX_4 (1 << 4) +#define MPEG_BIT_RATE_INDEX_5 (1 << 5) +#define MPEG_BIT_RATE_INDEX_6 (1 << 6) +#define MPEG_BIT_RATE_INDEX_7 (1 << 7) +#define MPEG_BIT_RATE_INDEX_8 (1 << 8) +#define MPEG_BIT_RATE_INDEX_9 (1 << 9) +#define MPEG_BIT_RATE_INDEX_10 (1 << 10) +#define MPEG_BIT_RATE_INDEX_11 (1 << 11) +#define MPEG_BIT_RATE_INDEX_12 (1 << 12) +#define MPEG_BIT_RATE_INDEX_13 (1 << 13) +#define MPEG_BIT_RATE_INDEX_14 (1 << 14) + +#define MPEG_MP1_BIT_RATE_32000 MPEG_BIT_RATE_INDEX_1 +#define MPEG_MP1_BIT_RATE_64000 MPEG_BIT_RATE_INDEX_2 +#define MPEG_MP1_BIT_RATE_96000 MPEG_BIT_RATE_INDEX_3 +#define MPEG_MP1_BIT_RATE_128000 MPEG_BIT_RATE_INDEX_4 +#define MPEG_MP1_BIT_RATE_160000 MPEG_BIT_RATE_INDEX_5 +#define MPEG_MP1_BIT_RATE_192000 MPEG_BIT_RATE_INDEX_6 +#define MPEG_MP1_BIT_RATE_224000 MPEG_BIT_RATE_INDEX_7 +#define MPEG_MP1_BIT_RATE_256000 MPEG_BIT_RATE_INDEX_8 +#define MPEG_MP1_BIT_RATE_288000 MPEG_BIT_RATE_INDEX_9 +#define MPEG_MP1_BIT_RATE_320000 MPEG_BIT_RATE_INDEX_10 +#define MPEG_MP1_BIT_RATE_352000 MPEG_BIT_RATE_INDEX_11 +#define MPEG_MP1_BIT_RATE_384000 MPEG_BIT_RATE_INDEX_12 +#define MPEG_MP1_BIT_RATE_416000 MPEG_BIT_RATE_INDEX_13 +#define MPEG_MP1_BIT_RATE_448000 MPEG_BIT_RATE_INDEX_14 + +#define MPEG_MP2_BIT_RATE_32000 MPEG_BIT_RATE_INDEX_1 +#define MPEG_MP2_BIT_RATE_48000 MPEG_BIT_RATE_INDEX_2 +#define MPEG_MP2_BIT_RATE_56000 MPEG_BIT_RATE_INDEX_3 +#define MPEG_MP2_BIT_RATE_64000 MPEG_BIT_RATE_INDEX_4 +#define MPEG_MP2_BIT_RATE_80000 MPEG_BIT_RATE_INDEX_5 +#define MPEG_MP2_BIT_RATE_96000 MPEG_BIT_RATE_INDEX_6 +#define MPEG_MP2_BIT_RATE_112000 MPEG_BIT_RATE_INDEX_7 +#define MPEG_MP2_BIT_RATE_128000 MPEG_BIT_RATE_INDEX_8 +#define MPEG_MP2_BIT_RATE_160000 MPEG_BIT_RATE_INDEX_9 +#define MPEG_MP2_BIT_RATE_192000 MPEG_BIT_RATE_INDEX_10 +#define MPEG_MP2_BIT_RATE_224000 MPEG_BIT_RATE_INDEX_11 +#define MPEG_MP2_BIT_RATE_256000 MPEG_BIT_RATE_INDEX_12 +#define MPEG_MP2_BIT_RATE_320000 MPEG_BIT_RATE_INDEX_13 +#define MPEG_MP2_BIT_RATE_384000 MPEG_BIT_RATE_INDEX_14 + +#define MPEG_MP3_BIT_RATE_32000 MPEG_BIT_RATE_INDEX_1 +#define MPEG_MP3_BIT_RATE_40000 MPEG_BIT_RATE_INDEX_2 +#define MPEG_MP3_BIT_RATE_48000 MPEG_BIT_RATE_INDEX_3 +#define MPEG_MP3_BIT_RATE_56000 MPEG_BIT_RATE_INDEX_4 +#define MPEG_MP3_BIT_RATE_64000 MPEG_BIT_RATE_INDEX_5 +#define MPEG_MP3_BIT_RATE_80000 MPEG_BIT_RATE_INDEX_6 +#define MPEG_MP3_BIT_RATE_96000 MPEG_BIT_RATE_INDEX_7 +#define MPEG_MP3_BIT_RATE_112000 MPEG_BIT_RATE_INDEX_8 +#define MPEG_MP3_BIT_RATE_128000 MPEG_BIT_RATE_INDEX_9 +#define MPEG_MP3_BIT_RATE_160000 MPEG_BIT_RATE_INDEX_10 +#define MPEG_MP3_BIT_RATE_192000 MPEG_BIT_RATE_INDEX_11 +#define MPEG_MP3_BIT_RATE_224000 MPEG_BIT_RATE_INDEX_12 +#define MPEG_MP3_BIT_RATE_256000 MPEG_BIT_RATE_INDEX_13 +#define MPEG_MP3_BIT_RATE_320000 MPEG_BIT_RATE_INDEX_14 + +#define MPEG_BIT_RATE_FREE MPEG_BIT_RATE_INDEX_0 + +#define MPEG_GET_BITRATE(a) ((uint16_t)(a).bitrate1 << 8 | (a).bitrate2) +#define MPEG_SET_BITRATE(a, b) \ + do { \ + (a).bitrate1 = ((b) >> 8) & 0x7f; \ + (a).bitrate2 = (b) & 0xff; \ + } while (0) + +#define AAC_OBJECT_TYPE_MPEG2_AAC_LC 0x80 +#define AAC_OBJECT_TYPE_MPEG4_AAC_LC 0x40 +#define AAC_OBJECT_TYPE_MPEG4_AAC_LTP 0x20 +#define AAC_OBJECT_TYPE_MPEG4_AAC_SCA 0x10 + +#define AAC_SAMPLING_FREQ_8000 0x0800 +#define AAC_SAMPLING_FREQ_11025 0x0400 +#define AAC_SAMPLING_FREQ_12000 0x0200 +#define AAC_SAMPLING_FREQ_16000 0x0100 +#define AAC_SAMPLING_FREQ_22050 0x0080 +#define AAC_SAMPLING_FREQ_24000 0x0040 +#define AAC_SAMPLING_FREQ_32000 0x0020 +#define AAC_SAMPLING_FREQ_44100 0x0010 +#define AAC_SAMPLING_FREQ_48000 0x0008 +#define AAC_SAMPLING_FREQ_64000 0x0004 +#define AAC_SAMPLING_FREQ_88200 0x0002 +#define AAC_SAMPLING_FREQ_96000 0x0001 + +#define AAC_CHANNELS_1 0x02 +#define AAC_CHANNELS_2 0x01 + +#define AAC_GET_BITRATE(a) ((a).bitrate1 << 16 | \ + (a).bitrate2 << 8 | (a).bitrate3) +#define AAC_GET_FREQUENCY(a) ((a).frequency1 << 4 | (a).frequency2) + +#define AAC_SET_BITRATE(a, b) \ + do { \ + (a).bitrate1 = (b >> 16) & 0x7f; \ + (a).bitrate2 = (b >> 8) & 0xff; \ + (a).bitrate3 = b & 0xff; \ + } while (0) +#define AAC_SET_FREQUENCY(a, f) \ + do { \ + (a).frequency1 = (f >> 4) & 0xff; \ + (a).frequency2 = f & 0x0f; \ + } while (0) + +#define AAC_INIT_BITRATE(b) \ + .bitrate1 = (b >> 16) & 0x7f, \ + .bitrate2 = (b >> 8) & 0xff, \ + .bitrate3 = b & 0xff, +#define AAC_INIT_FREQUENCY(f) \ + .frequency1 = (f >> 4) & 0xff, \ + .frequency2 = f & 0x0f, + +#define APTX_VENDOR_ID 0x0000004f +#define APTX_CODEC_ID 0x0001 + +#define APTX_CHANNEL_MODE_MONO 0x01 +#define APTX_CHANNEL_MODE_STEREO 0x02 + +#define APTX_SAMPLING_FREQ_16000 0x08 +#define APTX_SAMPLING_FREQ_32000 0x04 +#define APTX_SAMPLING_FREQ_44100 0x02 +#define APTX_SAMPLING_FREQ_48000 0x01 + +#define FASTSTREAM_VENDOR_ID 0x0000000a +#define FASTSTREAM_CODEC_ID 0x0001 + +#define FASTSTREAM_DIRECTION_SINK 0x1 +#define FASTSTREAM_DIRECTION_SOURCE 0x2 + +#define FASTSTREAM_SINK_SAMPLING_FREQ_44100 0x2 +#define FASTSTREAM_SINK_SAMPLING_FREQ_48000 0x1 + +#define FASTSTREAM_SOURCE_SAMPLING_FREQ_16000 0x2 + +#define APTX_LL_VENDOR_ID 0x0000000a +#define APTX_LL_CODEC_ID 0x0002 + +/* Default parameters for aptX Low Latency encoder */ + +/* Target codec buffer level = 180 */ +#define APTX_LL_TARGET_LEVEL2 0xb4 +#define APTX_LL_TARGET_LEVEL1 0x00 + +/* Initial codec buffer level = 360 */ +#define APTX_LL_INITIAL_LEVEL2 0x68 +#define APTX_LL_INITIAL_LEVEL1 0x01 + +/* SRA max rate 0.005 * 10000 = 50 */ +#define APTX_LL_SRA_MAX_RATE 0x32 + +/* SRA averaging time = 1s */ +#define APTX_LL_SRA_AVG_TIME 0x01 + +/* Good working codec buffer level = 180 */ +#define APTX_LL_GOOD_WORKING_LEVEL2 0xB4 +#define APTX_LL_GOOD_WORKING_LEVEL1 0x00 + +#define APTX_HD_VENDOR_ID 0x000000D7 +#define APTX_HD_CODEC_ID 0x0024 + +#define LDAC_VENDOR_ID 0x0000012d +#define LDAC_CODEC_ID 0x00aa + +#define LDAC_SAMPLING_FREQ_44100 0x20 +#define LDAC_SAMPLING_FREQ_48000 0x10 +#define LDAC_SAMPLING_FREQ_88200 0x08 +#define LDAC_SAMPLING_FREQ_96000 0x04 +#define LDAC_SAMPLING_FREQ_176400 0x02 +#define LDAC_SAMPLING_FREQ_192000 0x01 + +#define LDAC_CHANNEL_MODE_MONO 0x04 +#define LDAC_CHANNEL_MODE_DUAL 0x02 +#define LDAC_CHANNEL_MODE_STEREO 0x01 + +typedef struct { + uint8_t vendor_id4; + uint8_t vendor_id3; + uint8_t vendor_id2; + uint8_t vendor_id1; + uint8_t codec_id2; + uint8_t codec_id1; +} __attribute__ ((packed)) a2dp_vendor_codec_t; + +#define A2DP_GET_VENDOR_ID(a) ( \ + (((uint32_t)(a).vendor_id4) << 0) | \ + (((uint32_t)(a).vendor_id3) << 8) | \ + (((uint32_t)(a).vendor_id2) << 16) | \ + (((uint32_t)(a).vendor_id1) << 24) \ + ) +#define A2DP_GET_CODEC_ID(a) ((a).codec_id2 | (((uint16_t)(a).codec_id1) << 8)) +#define A2DP_SET_VENDOR_ID_CODEC_ID(v, c) ((a2dp_vendor_codec_t){ \ + .vendor_id4 = (((v) >> 0) & 0xff), \ + .vendor_id3 = (((v) >> 8) & 0xff), \ + .vendor_id2 = (((v) >> 16) & 0xff), \ + .vendor_id1 = (((v) >> 24) & 0xff), \ + .codec_id2 = (((c) >> 0) & 0xff), \ + .codec_id1 = (((c) >> 8) & 0xff), \ + }) + +typedef struct { + uint8_t reserved; + uint8_t target_level2; + uint8_t target_level1; + uint8_t initial_level2; + uint8_t initial_level1; + uint8_t sra_max_rate; + uint8_t sra_avg_time; + uint8_t good_working_level2; + uint8_t good_working_level1; +} __attribute__ ((packed)) a2dp_aptx_ll_new_caps_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t frequency; + uint8_t channel_mode; +} __attribute__ ((packed)) a2dp_ldac_t; + +#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN + +typedef struct { + uint8_t channel_mode:4; + uint8_t frequency:4; + uint8_t allocation_method:2; + uint8_t subbands:2; + uint8_t block_length:4; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t channel_mode:4; + uint8_t crc:1; + uint8_t layer:3; + uint8_t frequency:6; + uint8_t mpf:1; + uint8_t rfa:1; + uint8_t bitrate1:7; + uint8_t vbr:1; + uint8_t bitrate2; +} __attribute__ ((packed)) a2dp_mpeg_t; + +typedef struct { + uint8_t object_type; + uint8_t frequency1; + uint8_t rfa:2; + uint8_t channels:2; + uint8_t frequency2:4; + uint8_t bitrate1:7; + uint8_t vbr:1; + uint8_t bitrate2; + uint8_t bitrate3; +} __attribute__ ((packed)) a2dp_aac_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t channel_mode:4; + uint8_t frequency:4; +} __attribute__ ((packed)) a2dp_aptx_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t direction; + uint8_t sink_frequency:4; + uint8_t source_frequency:4; +} __attribute__ ((packed)) a2dp_faststream_t; + +typedef struct { + a2dp_aptx_t aptx; + uint8_t bidirect_link:1; + uint8_t has_new_caps:1; + uint8_t reserved:6; + a2dp_aptx_ll_new_caps_t new_caps[0]; +} __attribute__ ((packed)) a2dp_aptx_ll_t; + +#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN + +typedef struct { + uint8_t frequency:4; + uint8_t channel_mode:4; + uint8_t block_length:4; + uint8_t subbands:2; + uint8_t allocation_method:2; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) a2dp_sbc_t; + +typedef struct { + uint8_t layer:3; + uint8_t crc:1; + uint8_t channel_mode:4; + uint8_t rfa:1; + uint8_t mpf:1; + uint8_t frequency:6; + uint8_t vbr:1; + uint8_t bitrate1:7; + uint8_t bitrate2; +} __attribute__ ((packed)) a2dp_mpeg_t; + +typedef struct { + uint8_t object_type; + uint8_t frequency1; + uint8_t frequency2:4; + uint8_t channels:2; + uint8_t rfa:2; + uint8_t vbr:1; + uint8_t bitrate1:7; + uint8_t bitrate2; + uint8_t bitrate3; +} __attribute__ ((packed)) a2dp_aac_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t frequency:4; + uint8_t channel_mode:4; +} __attribute__ ((packed)) a2dp_aptx_t; + +typedef struct { + a2dp_vendor_codec_t info; + uint8_t direction; + uint8_t source_frequency:4; + uint8_t sink_frequency:4; +} __attribute__ ((packed)) a2dp_faststream_t; + +typedef struct { + a2dp_aptx_t aptx; + uint8_t reserved:6; + uint8_t has_new_caps:1; + uint8_t bidirect_link:1; + a2dp_aptx_ll_new_caps_t new_caps[0]; +} __attribute__ ((packed)) a2dp_aptx_ll_t; + +#else +#error "Unknown byte order" +#endif + +typedef struct { + a2dp_aptx_t aptx; + uint8_t reserved0; + uint8_t reserved1; + uint8_t reserved2; + uint8_t reserved3; +} __attribute__ ((packed)) a2dp_aptx_hd_t; diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c new file mode 100644 index 0000000..e8262cd --- /dev/null +++ b/profiles/audio/a2dp.c @@ -0,0 +1,3335 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2011 BMW Car IT GmbH. All rights reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE + +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/dbus-common.h" +#include "src/error.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/log.h" +#include "src/sdpd.h" +#include "src/shared/queue.h" +#include "src/shared/util.h" + +#include "btio/btio.h" + +#include "avdtp.h" +#include "sink.h" +#include "source.h" +#include "a2dp.h" +#include "a2dp-codecs.h" +#include "media.h" + +/* The duration that streams without users are allowed to stay in + * STREAMING state. */ +#define SUSPEND_TIMEOUT 5 +#define RECONFIGURE_TIMEOUT 500 + +#define AVDTP_PSM 25 + +#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" + +struct a2dp_sep { + struct a2dp_server *server; + struct a2dp_endpoint *endpoint; + uint8_t type; + uint8_t codec; + struct avdtp_local_sep *lsep; + struct avdtp *session; + struct avdtp_stream *stream; + guint suspend_timer; + gboolean delay_reporting; + gboolean locked; + gboolean suspending; + gboolean starting; + void *user_data; + GDestroyNotify destroy; +}; + +struct a2dp_setup_cb { + struct a2dp_setup *setup; + a2dp_discover_cb_t discover_cb; + a2dp_select_cb_t select_cb; + a2dp_config_cb_t config_cb; + a2dp_stream_cb_t resume_cb; + a2dp_stream_cb_t suspend_cb; + guint source_id; + void *user_data; + unsigned int id; +}; + +struct a2dp_setup { + struct a2dp_channel *chan; + struct avdtp *session; + struct queue *eps; + struct a2dp_sep *sep; + struct a2dp_remote_sep *rsep; + struct avdtp_stream *stream; + struct avdtp_error *err; + avdtp_set_configuration_cb setconf_cb; + GSList *seps; + GSList *caps; + gboolean reconfigure; + gboolean start; + GSList *cb; + GIOChannel *io; + int ref; +}; + +struct a2dp_server { + struct btd_adapter *adapter; + GSList *sinks; + GSList *sources; + uint32_t source_record_id; + uint32_t sink_record_id; + gboolean sink_enabled; + gboolean source_enabled; + GIOChannel *io; + struct queue *seps; + struct queue *channels; +}; + +struct a2dp_remote_sep { + struct a2dp_channel *chan; + char *path; + struct avdtp_remote_sep *sep; +}; + +struct a2dp_last_used { + struct a2dp_sep *lsep; + struct a2dp_remote_sep *rsep; +}; + +struct a2dp_channel { + struct a2dp_server *server; + struct btd_device *device; + GIOChannel *io; + guint io_id; + unsigned int state_id; + unsigned int auth_id; + struct avdtp *session; + struct queue *seps; + struct a2dp_last_used *last_used; +}; + +static GSList *servers = NULL; +static GSList *setups = NULL; +static unsigned int cb_id = 0; + +static struct a2dp_setup *setup_ref(struct a2dp_setup *setup) +{ + setup->ref++; + + DBG("%p: ref=%d", setup, setup->ref); + + return setup; +} + +static bool match_by_session(const void *data, const void *user_data) +{ + const struct a2dp_channel *chan = data; + const struct avdtp *session = user_data; + + return chan->session == session; +} + +static struct a2dp_channel *find_channel(struct avdtp *session) +{ + GSList *l; + + for (l = servers; l; l = g_slist_next(l)) { + struct a2dp_server *server = l->data; + struct a2dp_channel *chan; + + chan = queue_find(server->channels, match_by_session, session); + if (chan) + return chan; + } + + return NULL; +} + +static struct a2dp_setup *setup_new(struct avdtp *session) +{ + struct a2dp_setup *setup; + struct a2dp_channel *chan; + + chan = find_channel(session); + if (!chan) + return NULL; + + setup = g_new0(struct a2dp_setup, 1); + setup->session = avdtp_ref(session); + setup->chan = find_channel(session); + setups = g_slist_append(setups, setup); + + return setup; +} + +static void setup_free(struct a2dp_setup *s) +{ + DBG("%p", s); + + if (s->io) { + g_io_channel_shutdown(s->io, TRUE, NULL); + g_io_channel_unref(s->io); + } + + queue_destroy(s->eps, NULL); + + setups = g_slist_remove(setups, s); + if (s->session) + avdtp_unref(s->session); + g_slist_free_full(s->cb, g_free); + g_slist_free_full(s->caps, g_free); + g_free(s); +} + +static void setup_unref(struct a2dp_setup *setup) +{ + setup->ref--; + + DBG("%p: ref=%d", setup, setup->ref); + + if (setup->ref > 0) + return; + + setup_free(setup); +} + +static struct a2dp_setup_cb *setup_cb_new(struct a2dp_setup *setup) +{ + struct a2dp_setup_cb *cb; + + cb = g_new0(struct a2dp_setup_cb, 1); + cb->setup = setup; + cb->id = ++cb_id; + + setup->cb = g_slist_append(setup->cb, cb); + return cb; +} + +static void setup_cb_free(struct a2dp_setup_cb *cb) +{ + struct a2dp_setup *setup = cb->setup; + + if (cb->source_id) + g_source_remove(cb->source_id); + + setup->cb = g_slist_remove(setup->cb, cb); + setup_unref(cb->setup); + g_free(cb); +} + +static void finalize_setup_errno(struct a2dp_setup *s, int err, + GSourceFunc cb1, ...) +{ + GSourceFunc finalize; + va_list args; + struct avdtp_error avdtp_err; + + if (err < 0) { + avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err); + s->err = &avdtp_err; + } + + va_start(args, cb1); + finalize = cb1; + setup_ref(s); + while (finalize != NULL) { + finalize(s); + finalize = va_arg(args, GSourceFunc); + } + setup_unref(s); + va_end(args); +} + +static int error_to_errno(struct avdtp_error *err) +{ + int perr; + + if (!err) + return 0; + + if (avdtp_error_category(err) != AVDTP_ERRNO) + return -EIO; + + perr = avdtp_error_posix_errno(err); + switch (perr) { + case EHOSTDOWN: + case ECONNABORTED: + return -perr; + default: + /* + * An unexpect error has occurred setup may be attempted again. + */ + return -EAGAIN; + } +} + +static gboolean finalize_config(gpointer data) +{ + struct a2dp_setup *s = data; + GSList *l; + struct avdtp_stream *stream = s->err ? NULL : s->stream; + + for (l = s->cb; l != NULL; ) { + struct a2dp_setup_cb *cb = l->data; + + l = l->next; + + if (!cb->config_cb) + continue; + + cb->config_cb(s->session, s->sep, stream, + error_to_errno(s->err), cb->user_data); + setup_cb_free(cb); + } + + return FALSE; +} + +static gboolean finalize_resume(gpointer data) +{ + struct a2dp_setup *s = data; + GSList *l; + + for (l = s->cb; l != NULL; ) { + struct a2dp_setup_cb *cb = l->data; + + l = l->next; + + if (!cb->resume_cb) + continue; + + cb->resume_cb(s->session, error_to_errno(s->err), + cb->user_data); + setup_cb_free(cb); + } + + return FALSE; +} + +static gboolean finalize_suspend(gpointer data) +{ + struct a2dp_setup *s = data; + GSList *l; + + for (l = s->cb; l != NULL; ) { + struct a2dp_setup_cb *cb = l->data; + + l = l->next; + + if (!cb->suspend_cb) + continue; + + cb->suspend_cb(s->session, error_to_errno(s->err), + cb->user_data); + setup_cb_free(cb); + } + + return FALSE; +} + +static void finalize_select(struct a2dp_setup *s) +{ + GSList *l; + + for (l = s->cb; l != NULL; ) { + struct a2dp_setup_cb *cb = l->data; + + l = l->next; + + if (!cb->select_cb) + continue; + + cb->select_cb(s->session, s->sep, s->caps, cb->user_data); + setup_cb_free(cb); + } +} + +static void finalize_discover(struct a2dp_setup *s) +{ + GSList *l; + + for (l = s->cb; l != NULL; ) { + struct a2dp_setup_cb *cb = l->data; + + l = l->next; + + if (!cb->discover_cb) + continue; + + cb->discover_cb(s->session, s->seps, error_to_errno(s->err), + cb->user_data); + setup_cb_free(cb); + } +} + +static struct a2dp_setup *find_setup_by_session(struct avdtp *session) +{ + GSList *l; + + for (l = setups; l != NULL; l = l->next) { + struct a2dp_setup *setup = l->data; + + if (setup->session == session) + return setup; + } + + return NULL; +} + +static struct a2dp_setup *a2dp_setup_get(struct avdtp *session) +{ + struct a2dp_setup *setup; + + setup = find_setup_by_session(session); + if (!setup) { + setup = setup_new(session); + if (!setup) + return NULL; + } + + return setup_ref(setup); +} + +static struct a2dp_setup *find_setup_by_stream(struct avdtp_stream *stream) +{ + GSList *l; + + for (l = setups; l != NULL; l = l->next) { + struct a2dp_setup *setup = l->data; + + if (setup->stream == stream) + return setup; + } + + return NULL; +} + +static void stream_state_changed(struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *sep = user_data; + + if (new_state == AVDTP_STATE_OPEN) { + struct a2dp_setup *setup; + int err; + + setup = find_setup_by_stream(stream); + if (!setup || !setup->start) + return; + + setup->start = FALSE; + + err = avdtp_start(setup->session, stream); + if (err < 0 && err != -EINPROGRESS) { + error("avdtp_start: %s (%d)", strerror(-err), -err); + finalize_setup_errno(setup, err, finalize_resume, + NULL); + return; + } + + sep->starting = TRUE; + + return; + } + + if (new_state != AVDTP_STATE_IDLE) + return; + + if (sep->suspend_timer) { + g_source_remove(sep->suspend_timer); + sep->suspend_timer = 0; + } + + if (sep->session) { + avdtp_unref(sep->session); + sep->session = NULL; + } + + sep->stream = NULL; + + if (sep->endpoint && sep->endpoint->clear_configuration) + sep->endpoint->clear_configuration(sep, sep->user_data); +} + +static gboolean auto_config(gpointer data) +{ + struct a2dp_setup *setup = data; + struct btd_device *dev = NULL; + struct btd_service *service; + + /* Check if configuration was aborted */ + if (setup->sep->stream == NULL) + return FALSE; + + if (setup->err != NULL) + goto done; + + dev = avdtp_get_device(setup->session); + + avdtp_stream_add_cb(setup->session, setup->stream, + stream_state_changed, setup->sep); + + if (setup->sep->type == AVDTP_SEP_TYPE_SOURCE) { + service = btd_device_get_service(dev, A2DP_SINK_UUID); + sink_new_stream(service, setup->session, setup->stream); + } else { + service = btd_device_get_service(dev, A2DP_SOURCE_UUID); + source_new_stream(service, setup->session, setup->stream); + } + +done: + if (setup->setconf_cb) + setup->setconf_cb(setup->session, setup->stream, setup->err); + + finalize_config(setup); + + if (setup->err) { + g_free(setup->err); + setup->err = NULL; + } + + setup_unref(setup); + + return FALSE; +} + +static void endpoint_setconf_cb(struct a2dp_setup *setup, gboolean ret) +{ + if (ret == FALSE) { + setup->err = g_new(struct avdtp_error, 1); + avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC, + AVDTP_UNSUPPORTED_CONFIGURATION); + } + + auto_config(setup); + setup_unref(setup); +} + +static gboolean endpoint_match_codec_ind(struct avdtp *session, + struct avdtp_media_codec_capability *codec, + void *user_data) +{ + struct a2dp_sep *sep = user_data; + a2dp_vendor_codec_t *remote_codec; + a2dp_vendor_codec_t *local_codec; + uint8_t *capabilities; + size_t length; + + if (codec->media_codec_type != A2DP_CODEC_VENDOR) + return TRUE; + + if (sep->endpoint == NULL) + return FALSE; + + length = sep->endpoint->get_capabilities(sep, &capabilities, + sep->user_data); + if (length < sizeof(a2dp_vendor_codec_t)) + return FALSE; + + local_codec = (a2dp_vendor_codec_t *) capabilities; + remote_codec = (a2dp_vendor_codec_t *) codec->data; + + if (A2DP_GET_VENDOR_ID(*remote_codec) != + A2DP_GET_VENDOR_ID(*local_codec)) + return FALSE; + + if (A2DP_GET_CODEC_ID(*remote_codec) != A2DP_GET_CODEC_ID(*local_codec)) + return FALSE; + + DBG("vendor 0x%08x codec 0x%04x", A2DP_GET_VENDOR_ID(*remote_codec), + A2DP_GET_CODEC_ID(*remote_codec)); + return TRUE; +} + +static void reverse_discover(struct avdtp *session, GSList *seps, int err, + void *user_data) +{ + DBG("err %d", err); +} + +static gboolean endpoint_setconf_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + GSList *caps, + avdtp_set_configuration_cb cb, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Set_Configuration_Ind", sep); + else + DBG("Source %p: Set_Configuration_Ind", sep); + + setup = a2dp_setup_get(session); + if (!session) + return FALSE; + + a2dp_sep->stream = stream; + setup->sep = a2dp_sep; + setup->stream = stream; + setup->setconf_cb = cb; + + for (; caps != NULL; caps = g_slist_next(caps)) { + struct avdtp_service_capability *cap = caps->data; + struct avdtp_media_codec_capability *codec; + gboolean ret; + + if (cap->category == AVDTP_DELAY_REPORTING && + !a2dp_sep->delay_reporting) { + setup->err = g_new(struct avdtp_error, 1); + avdtp_error_init(setup->err, AVDTP_DELAY_REPORTING, + AVDTP_UNSUPPORTED_CONFIGURATION); + goto done; + } + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + codec = (struct avdtp_media_codec_capability *) cap->data; + + if (codec->media_codec_type != a2dp_sep->codec) { + setup->err = g_new(struct avdtp_error, 1); + avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC, + AVDTP_UNSUPPORTED_CONFIGURATION); + goto done; + } + + ret = a2dp_sep->endpoint->set_configuration(a2dp_sep, + codec->data, + cap->length - sizeof(*codec), + setup_ref(setup), + endpoint_setconf_cb, + a2dp_sep->user_data); + if (ret == 0) { + /* Attempt to reverve discover if there are no remote + * SEPs. + */ + if (queue_isempty(setup->chan->seps)) + a2dp_discover(session, reverse_discover, NULL); + return TRUE; + } + + setup_unref(setup); + setup->err = g_new(struct avdtp_error, 1); + avdtp_error_init(setup->err, AVDTP_MEDIA_CODEC, + AVDTP_UNSUPPORTED_CONFIGURATION); + break; + } + +done: + g_idle_add(auto_config, setup); + return TRUE; +} + +static gboolean endpoint_getcap_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + gboolean get_all, GSList **caps, + uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct avdtp_service_capability *media_transport, *media_codec; + struct avdtp_media_codec_capability *codec_caps; + uint8_t *capabilities; + size_t length; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Get_Capability_Ind", sep); + else + DBG("Source %p: Get_Capability_Ind", sep); + + *caps = NULL; + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + length = a2dp_sep->endpoint->get_capabilities(a2dp_sep, &capabilities, + a2dp_sep->user_data); + + codec_caps = g_malloc0(sizeof(*codec_caps) + length); + codec_caps->media_type = AVDTP_MEDIA_TYPE_AUDIO; + codec_caps->media_codec_type = a2dp_sep->codec; + memcpy(codec_caps->data, capabilities, length); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, codec_caps, + sizeof(*codec_caps) + length); + + *caps = g_slist_append(*caps, media_codec); + g_free(codec_caps); + + if (get_all) { + struct avdtp_service_capability *delay_reporting; + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + *caps = g_slist_append(*caps, delay_reporting); + } + + return TRUE; +} + +static void endpoint_open_cb(struct a2dp_setup *setup, gboolean ret) +{ + int err; + + if (ret == FALSE) { + setup->stream = NULL; + finalize_setup_errno(setup, -EPERM, finalize_config, NULL); + goto done; + } + + err = avdtp_open(setup->session, setup->stream); + if (err == 0) + goto done; + + error("Error on avdtp_open %s (%d)", strerror(-err), -err); + setup->stream = NULL; + finalize_setup_errno(setup, err, finalize_config, NULL); +done: + setup_unref(setup); +} + +static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + struct btd_device *dev; + struct btd_service *service; + int ret; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Set_Configuration_Cfm", sep); + else + DBG("Source %p: Set_Configuration_Cfm", sep); + + setup = find_setup_by_session(session); + + if (err) { + if (setup) { + setup_ref(setup); + setup->err = err; + finalize_config(setup); + setup->err = NULL; + setup_unref(setup); + } + return; + } + + avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep); + a2dp_sep->stream = stream; + + if (!setup) + return; + + dev = avdtp_get_device(session); + + /* Notify D-Bus interface of the new stream */ + if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE) { + service = btd_device_get_service(dev, A2DP_SINK_UUID); + sink_new_stream(service, session, setup->stream); + } else { + service = btd_device_get_service(dev, A2DP_SOURCE_UUID); + source_new_stream(service, session, setup->stream); + } + + /* Notify Endpoint */ + if (a2dp_sep->endpoint) { + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + int err; + + service = avdtp_stream_get_codec(stream); + codec = (struct avdtp_media_codec_capability *) service->data; + + err = a2dp_sep->endpoint->set_configuration(a2dp_sep, + codec->data, service->length - + sizeof(*codec), + setup_ref(setup), + endpoint_open_cb, + a2dp_sep->user_data); + if (err == 0) + return; + + setup->stream = NULL; + finalize_setup_errno(setup, -EPERM, finalize_config, NULL); + setup_unref(setup); + return; + } + + ret = avdtp_open(session, stream); + if (ret < 0) { + error("Error on avdtp_open %s (%d)", strerror(-ret), -ret); + setup->stream = NULL; + finalize_setup_errno(setup, ret, finalize_config, NULL); + } +} + +static gboolean getconf_ind(struct avdtp *session, struct avdtp_local_sep *sep, + uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Get_Configuration_Ind", sep); + else + DBG("Source %p: Get_Configuration_Ind", sep); + return TRUE; +} + +static void getconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Set_Configuration_Cfm", sep); + else + DBG("Source %p: Set_Configuration_Cfm", sep); +} + +static bool match_remote_sep(const void *data, const void *user_data) +{ + const struct a2dp_remote_sep *sep = data; + const struct avdtp_remote_sep *rsep = user_data; + + return sep->sep == rsep; +} + +static void store_last_used(struct a2dp_channel *chan, uint8_t lseid, + uint8_t rseid) +{ + GKeyFile *key_file; + char filename[PATH_MAX]; + char dst_addr[18]; + char value[6]; + char *data; + size_t len = 0; + + ba2str(device_get_address(chan->device), dst_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", + btd_adapter_get_storage_dir(device_get_adapter(chan->device)), + dst_addr); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + sprintf(value, "%02hhx:%02hhx", lseid, rseid); + + g_key_file_set_string(key_file, "Endpoints", "LastUsed", value); + + data = g_key_file_to_data(key_file, &len, NULL); + g_file_set_contents(filename, data, len, NULL); + + g_free(data); + g_key_file_free(key_file); +} + +static void add_last_used(struct a2dp_channel *chan, struct a2dp_sep *lsep, + struct a2dp_remote_sep *rsep) +{ + if (!chan->last_used) + chan->last_used = new0(struct a2dp_last_used, 1); + + chan->last_used->lsep = lsep; + chan->last_used->rsep = rsep; +} + +static void update_last_used(struct a2dp_channel *chan, struct a2dp_sep *lsep, + struct avdtp_stream *stream) +{ + struct avdtp_remote_sep *rsep; + struct a2dp_remote_sep *sep; + + rsep = avdtp_stream_get_remote_sep(stream); + sep = queue_find(chan->seps, match_remote_sep, rsep); + if (!sep) { + error("Unable to find remote SEP"); + return; + } + + /* Check if already stored then skip */ + if (chan->last_used && (chan->last_used->lsep == lsep && + chan->last_used->rsep == sep)) + return; + + add_last_used(chan, lsep, sep); + + store_last_used(chan, avdtp_sep_get_seid(lsep->lsep), + avdtp_get_seid(rsep)); +} + +static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Open_Ind", sep); + else + DBG("Source %p: Open_Ind", sep); + + setup = a2dp_setup_get(session); + if (!setup) + return FALSE; + + setup->stream = stream; + + if (!err && setup->chan) + update_last_used(setup->chan, a2dp_sep, stream); + + if (setup->reconfigure) + setup->reconfigure = FALSE; + + finalize_config(setup); + + return TRUE; +} + +static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Open_Cfm", sep); + else + DBG("Source %p: Open_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (setup->reconfigure) + setup->reconfigure = FALSE; + + if (err) { + setup->stream = NULL; + setup->err = err; + if (setup->start) + finalize_resume(setup); + } else if (setup->chan) + update_last_used(setup->chan, a2dp_sep, stream); + + finalize_config(setup); + + return; +} + +static gboolean suspend_timeout(struct a2dp_sep *sep) +{ + if (avdtp_suspend(sep->session, sep->stream) == 0) + sep->suspending = TRUE; + + sep->suspend_timer = 0; + + avdtp_unref(sep->session); + sep->session = NULL; + + return FALSE; +} + +static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Start_Ind", sep); + else + DBG("Source %p: Start_Ind", sep); + + if (!a2dp_sep->locked) { + a2dp_sep->session = avdtp_ref(session); + a2dp_sep->suspend_timer = g_timeout_add_seconds(SUSPEND_TIMEOUT, + (GSourceFunc) suspend_timeout, + a2dp_sep); + } + + if (!a2dp_sep->starting) + return TRUE; + + a2dp_sep->starting = FALSE; + + setup = find_setup_by_session(session); + if (setup) + finalize_resume(setup); + + return TRUE; +} + +static void start_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Start_Cfm", sep); + else + DBG("Source %p: Start_Cfm", sep); + + a2dp_sep->starting = FALSE; + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (err) { + setup->stream = NULL; + setup->err = err; + } + + finalize_resume(setup); +} + +static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + gboolean start; + int start_err; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Suspend_Ind", sep); + else + DBG("Source %p: Suspend_Ind", sep); + + if (a2dp_sep->suspend_timer) { + g_source_remove(a2dp_sep->suspend_timer); + a2dp_sep->suspend_timer = 0; + avdtp_unref(a2dp_sep->session); + a2dp_sep->session = NULL; + } + + if (!a2dp_sep->suspending) + return TRUE; + + a2dp_sep->suspending = FALSE; + + setup = find_setup_by_session(session); + if (!setup) + return TRUE; + + start = setup->start; + setup->start = FALSE; + + finalize_suspend(setup); + + if (!start) + return TRUE; + + start_err = avdtp_start(session, a2dp_sep->stream); + if (start_err < 0 && start_err != -EINPROGRESS) { + error("avdtp_start: %s (%d)", strerror(-start_err), + -start_err); + finalize_setup_errno(setup, start_err, finalize_resume, NULL); + } + + return TRUE; +} + +static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + gboolean start; + int start_err; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Suspend_Cfm", sep); + else + DBG("Source %p: Suspend_Cfm", sep); + + a2dp_sep->suspending = FALSE; + + setup = find_setup_by_session(session); + if (!setup) + return; + + start = setup->start; + setup->start = FALSE; + + if (err) { + setup->stream = NULL; + setup->err = err; + } + + finalize_suspend(setup); + + if (!start) + return; + + if (err) { + finalize_resume(setup); + return; + } + + start_err = avdtp_start(session, a2dp_sep->stream); + if (start_err < 0 && start_err != -EINPROGRESS) { + error("avdtp_start: %s (%d)", strerror(-start_err), + -start_err); + finalize_setup_errno(setup, start_err, finalize_suspend, NULL); + } +} + +static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Close_Ind", sep); + else + DBG("Source %p: Close_Ind", sep); + + setup = find_setup_by_session(session); + if (!setup) + return TRUE; + + finalize_setup_errno(setup, -ECONNRESET, finalize_suspend, + finalize_resume, NULL); + + return TRUE; +} + +static struct a2dp_remote_sep *find_remote_sep(struct a2dp_channel *chan, + struct a2dp_sep *sep) +{ + struct avdtp_remote_sep *rsep; + + rsep = avdtp_find_remote_sep(chan->session, sep->lsep); + + return queue_find(chan->seps, match_remote_sep, rsep); +} + +static gboolean a2dp_reconfigure(gpointer data) +{ + struct a2dp_setup *setup = data; + struct a2dp_sep *sep = setup->sep; + int posix_err; + struct avdtp_media_codec_capability *rsep_codec; + struct avdtp_service_capability *cap; + + if (setup->rsep) { + cap = avdtp_get_codec(setup->rsep->sep); + rsep_codec = (struct avdtp_media_codec_capability *) cap->data; + } + + if (!setup->rsep || sep->codec != rsep_codec->media_codec_type) + setup->rsep = find_remote_sep(setup->chan, sep); + + posix_err = avdtp_set_configuration(setup->session, setup->rsep->sep, + sep->lsep, + setup->caps, + &setup->stream); + if (posix_err < 0) { + error("avdtp_set_configuration: %s", strerror(-posix_err)); + goto failed; + } + + return FALSE; + +failed: + finalize_setup_errno(setup, posix_err, finalize_config, NULL); + return FALSE; +} + +static struct a2dp_remote_sep *get_remote_sep(struct a2dp_channel *chan, + struct avdtp_stream *stream) +{ + struct avdtp_remote_sep *rsep; + + rsep = avdtp_stream_get_remote_sep(stream); + + return queue_find(chan->seps, match_remote_sep, rsep); +} + +static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Close_Cfm", sep); + else + DBG("Source %p: Close_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (err) { + setup->stream = NULL; + setup->err = err; + finalize_config(setup); + return; + } + + if (!setup->rsep) + setup->rsep = get_remote_sep(setup->chan, stream); + + if (setup->reconfigure) + g_timeout_add(RECONFIGURE_TIMEOUT, a2dp_reconfigure, setup); +} + +static void abort_ind(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Abort_Ind", sep); + else + DBG("Source %p: Abort_Ind", sep); + + a2dp_sep->stream = NULL; + + setup = find_setup_by_session(session); + if (!setup) + return; + + finalize_setup_errno(setup, -ECONNRESET, finalize_suspend, + finalize_resume, + finalize_config, NULL); + + return; +} + +static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: Abort_Cfm", sep); + else + DBG("Source %p: Abort_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (setup->reconfigure) { + g_timeout_add(RECONFIGURE_TIMEOUT, a2dp_reconfigure, setup); + return; + } + + setup_unref(setup); +} + +static gboolean reconf_ind(struct avdtp *session, struct avdtp_local_sep *sep, + uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: ReConfigure_Ind", sep); + else + DBG("Source %p: ReConfigure_Ind", sep); + + return TRUE; +} + +static gboolean endpoint_delayreport_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + uint8_t rseid, uint16_t delay, + uint8_t *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: DelayReport_Ind", sep); + else + DBG("Source %p: DelayReport_Ind", sep); + + if (a2dp_sep->endpoint == NULL || + a2dp_sep->endpoint->set_delay == NULL) + return FALSE; + + a2dp_sep->endpoint->set_delay(a2dp_sep, delay, a2dp_sep->user_data); + + return TRUE; +} + +static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: ReConfigure_Cfm", sep); + else + DBG("Source %p: ReConfigure_Cfm", sep); + + setup = find_setup_by_session(session); + if (!setup) + return; + + if (err) { + setup->stream = NULL; + setup->err = err; + } + + finalize_config(setup); +} + +static void delay_report_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data) +{ + struct a2dp_sep *a2dp_sep = user_data; + + if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) + DBG("Sink %p: DelayReport_Cfm", sep); + else + DBG("Source %p: DelayReport_Cfm", sep); +} + +static struct avdtp_sep_cfm cfm = { + .set_configuration = setconf_cfm, + .get_configuration = getconf_cfm, + .open = open_cfm, + .start = start_cfm, + .suspend = suspend_cfm, + .close = close_cfm, + .abort = abort_cfm, + .reconfigure = reconf_cfm, + .delay_report = delay_report_cfm, +}; + +static struct avdtp_sep_ind endpoint_ind = { + .match_codec = endpoint_match_codec_ind, + .get_capability = endpoint_getcap_ind, + .set_configuration = endpoint_setconf_ind, + .get_configuration = getconf_ind, + .open = open_ind, + .start = start_ind, + .suspend = suspend_ind, + .close = close_ind, + .abort = abort_ind, + .reconfigure = reconf_ind, + .delayreport = endpoint_delayreport_ind, +}; + +static sdp_record_t *a2dp_record(uint8_t type) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVDTP_UUID; + uint16_t a2dp_ver = 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(0, &root_uuid); + sdp_set_browse_groups(record, root); + + if (type == AVDTP_SEP_TYPE_SOURCE) + sdp_uuid16_create(&a2dp_uuid, AUDIO_SOURCE_SVCLASS_ID); + else + sdp_uuid16_create(&a2dp_uuid, AUDIO_SINK_SVCLASS_ID); + svclass_id = sdp_list_append(0, &a2dp_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID); + profile[0].version = a2dp_ver; + pfseq = sdp_list_append(0, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&avdtp_uuid, AVDTP_UUID); + proto[1] = sdp_list_append(0, &avdtp_uuid); + version = sdp_data_alloc(SDP_UINT16, &avdtp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + if (type == AVDTP_SEP_TYPE_SOURCE) + sdp_set_info_attr(record, "Audio Source", 0, 0); + else + sdp_set_info_attr(record, "Audio Sink", 0, 0); + + free(psm); + free(version); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static struct a2dp_server *find_server(GSList *list, struct btd_adapter *a) +{ + + for (; list; list = list->next) { + struct a2dp_server *server = list->data; + + if (server->adapter == a) + return server; + } + + return NULL; +} + +static void remote_sep_free(void *data) +{ + struct a2dp_remote_sep *sep = data; + + free(sep->path); + free(sep); +} + +static void remove_remote_sep(void *data) +{ + struct a2dp_remote_sep *sep = data; + + if (!sep->path) { + remote_sep_free(sep); + return; + } + + g_dbus_unregister_interface(btd_get_dbus_connection(), sep->path, + MEDIA_ENDPOINT_INTERFACE); +} + +static void channel_free(void *data) +{ + struct a2dp_channel *chan = data; + + if (chan->auth_id > 0) + btd_cancel_authorization(chan->auth_id); + + if (chan->io_id > 0) + g_source_remove(chan->io_id); + + if (chan->io) { + g_io_channel_shutdown(chan->io, TRUE, NULL); + g_io_channel_unref(chan->io); + } + + avdtp_remove_state_cb(chan->state_id); + + queue_destroy(chan->seps, remove_remote_sep); + free(chan->last_used); + g_free(chan); +} + +static void channel_remove(struct a2dp_channel *chan) +{ + struct a2dp_server *server = chan->server; + + DBG("chan %p", chan); + + queue_remove(server->channels, chan); + + channel_free(chan); +} + +static gboolean disconnect_cb(GIOChannel *io, GIOCondition cond, gpointer data) +{ + struct a2dp_channel *chan = data; + + DBG("chan %p", chan); + + chan->io_id = 0; + + channel_remove(chan); + + return FALSE; +} + +static void caps_add_codec(GSList **l, uint8_t codec, uint8_t *caps, + size_t size) +{ + struct avdtp_service_capability *media_transport, *media_codec; + struct avdtp_media_codec_capability *cap; + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *l = g_slist_append(*l, media_transport); + + cap = g_malloc0(sizeof(*cap) + size); + cap->media_type = AVDTP_MEDIA_TYPE_AUDIO; + cap->media_codec_type = codec; + memcpy(cap->data, caps, size); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap, + sizeof(*cap) + size); + + *l = g_slist_append(*l, media_codec); + g_free(cap); +} + +struct client { + const char *sender; + const char *path; +}; + +static int match_client(const void *data, const void *user_data) +{ + struct a2dp_sep *sep = (void *) data; + const struct a2dp_endpoint *endpoint = sep->endpoint; + const struct client *client = user_data; + + if (strcmp(client->sender, endpoint->get_name(sep, sep->user_data))) + return -1; + + return strcmp(client->path, endpoint->get_path(sep, sep->user_data)); +} + +static struct a2dp_sep *find_sep(struct a2dp_server *server, uint8_t type, + const char *sender, const char *path) +{ + GSList *l; + struct client client = { sender, path }; + + l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks; + + l = g_slist_find_custom(l, &client, match_client); + if (l) + return l->data; + + return NULL; +} + +static int parse_properties(DBusMessageIter *props, uint8_t **caps, int *size) +{ + while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "Capabilities") == 0) { + DBusMessageIter array; + + if (var != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, caps, size); + return 0; + } + + dbus_message_iter_next(props); + } + + return -EINVAL; +} + +static void reconfig_cb(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, int err, void *user_data) +{ + DBusMessage *msg = user_data; + + if (err) + g_dbus_send_message(btd_get_dbus_connection(), + btd_error_failed(msg, strerror(-err))); + else + g_dbus_send_reply(btd_get_dbus_connection(), msg, + DBUS_TYPE_INVALID); + + dbus_message_unref(msg); +} + +static int a2dp_reconfig(struct a2dp_channel *chan, const char *sender, + struct a2dp_sep *lsep, struct a2dp_remote_sep *rsep, + uint8_t *caps, int size, void *user_data) +{ + struct a2dp_setup *setup; + struct a2dp_setup_cb *cb_data; + GSList *l; + int err; + + setup = a2dp_setup_get(chan->session); + if (!setup) + return -ENOMEM; + + cb_data = setup_cb_new(setup); + cb_data->config_cb = reconfig_cb; + cb_data->user_data = user_data; + + setup->sep = lsep; + setup->rsep = rsep; + + g_slist_free_full(setup->caps, g_free); + setup->caps = NULL; + + caps_add_codec(&setup->caps, setup->sep->codec, caps, size); + + l = avdtp_get_type(rsep->sep) == AVDTP_SEP_TYPE_SINK ? + chan->server->sources : + chan->server->sinks; + + /* Check for existing stream and close it */ + for (; l; l = g_slist_next(l)) { + struct a2dp_sep *tmp = l->data; + + /* Attempt to reconfigure if a stream already exists */ + if (tmp->stream) { + /* Only allow switching sep from the same sender */ + if (strcmp(sender, tmp->endpoint->get_name(tmp, + tmp->user_data))) + return -EPERM; + + /* Check if stream is for the channel */ + if (!avdtp_has_stream(chan->session, tmp->stream)) + continue; + + err = avdtp_close(chan->session, tmp->stream, FALSE); + if (err < 0) { + err = avdtp_abort(chan->session, tmp->stream); + if (err < 0) { + error("avdtp_abort: %s", + strerror(-err)); + goto fail; + } + } + + setup->reconfigure = TRUE; + + return 0; + } + } + + err = avdtp_set_configuration(setup->session, setup->rsep->sep, + lsep->lsep, + setup->caps, + &setup->stream); + if (err < 0) { + error("avdtp_set_configuration: %s", strerror(-err)); + goto fail; + } + + return 0; + +fail: + setup_unref(setup); + return err; +} + +static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct a2dp_remote_sep *rsep = data; + struct a2dp_channel *chan = rsep->chan; + struct a2dp_sep *lsep = NULL; + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + DBusMessageIter args, props; + const char *sender, *path; + uint8_t *caps; + int err, size = 0; + + sender = dbus_message_get_sender(msg); + + dbus_message_iter_init(msg, &args); + + dbus_message_iter_get_basic(&args, &path); + dbus_message_iter_next(&args); + + lsep = find_sep(chan->server, avdtp_get_type(rsep->sep), sender, path); + if (!lsep) + return btd_error_invalid_args(msg); + + service = avdtp_get_codec(rsep->sep); + codec = (struct avdtp_media_codec_capability *) service->data; + + /* Check if codec really matches */ + if (!endpoint_match_codec_ind(chan->session, codec, lsep)) + return btd_error_invalid_args(msg); + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + return btd_error_invalid_args(msg); + + if (parse_properties(&props, &caps, &size) < 0) + return btd_error_invalid_args(msg); + + err = a2dp_reconfig(chan, sender, lsep, rsep, caps, size, + dbus_message_ref(msg)); + if (err < 0) { + dbus_message_unref(msg); + return btd_error_failed(msg, strerror(-err)); + } + + return NULL; +} + +static const GDBusMethodTable sep_methods[] = { + { GDBUS_ASYNC_METHOD("SetConfiguration", + GDBUS_ARGS({ "endpoint", "o" }, + { "properties", "a{sv}" } ), + NULL, set_configuration) }, + { }, +}; + +static gboolean get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct a2dp_remote_sep *sep = data; + const char *uuid; + + switch (avdtp_get_type(sep->sep)) { + case AVDTP_SEP_TYPE_SOURCE: + uuid = A2DP_SOURCE_UUID; + break; + case AVDTP_SEP_TYPE_SINK: + uuid = A2DP_SINK_UUID; + break; + default: + uuid = ""; + } + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); + + return TRUE; +} + +static gboolean get_codec(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct a2dp_remote_sep *sep = data; + struct avdtp_service_capability *cap = avdtp_get_codec(sep->sep); + struct avdtp_media_codec_capability *codec = (void *) cap->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, + &codec->media_codec_type); + + return TRUE; +} + +static gboolean get_capabilities(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct a2dp_remote_sep *sep = data; + struct avdtp_service_capability *service = avdtp_get_codec(sep->sep); + struct avdtp_media_codec_capability *codec = (void *) service->data; + uint8_t *caps = codec->data; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array); + + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &caps, + service->length - sizeof(*codec)); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean get_device(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct a2dp_remote_sep *sep = data; + const char *path; + + path = device_get_path(sep->chan->device); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + return TRUE; +} + +static const GDBusPropertyTable sep_properties[] = { + { "UUID", "s", get_uuid, NULL, NULL }, + { "Codec", "y", get_codec, NULL, NULL }, + { "Capabilities", "ay", get_capabilities, NULL, NULL }, + { "Device", "o", get_device, NULL, NULL }, + { } +}; + +static void register_remote_sep(void *data, void *user_data) +{ + struct avdtp_remote_sep *rsep = data; + struct a2dp_channel *chan = user_data; + struct a2dp_remote_sep *sep; + + sep = queue_find(chan->seps, match_remote_sep, rsep); + if (sep) + return; + + sep = new0(struct a2dp_remote_sep, 1); + sep->chan = chan; + sep->sep = rsep; + + if (asprintf(&sep->path, "%s/sep%d", + device_get_path(chan->device), + avdtp_get_seid(rsep)) < 0) { + error("Could not allocate path for remote sep %s/sep%d", + device_get_path(chan->device), + avdtp_get_seid(rsep)); + sep->path = NULL; + goto done; + } + + if (g_dbus_register_interface(btd_get_dbus_connection(), + sep->path, MEDIA_ENDPOINT_INTERFACE, + sep_methods, NULL, sep_properties, + sep, remote_sep_free) == FALSE) { + error("Could not register remote sep %s", sep->path); + free(sep->path); + sep->path = NULL; + goto done; + } + + DBG("Found remote SEP: %s", sep->path); + +done: + queue_push_tail(chan->seps, sep); +} + +static bool match_seid(const void *data, const void *user_data) +{ + const struct a2dp_remote_sep *sep = data; + const uint8_t *seid = user_data; + + return avdtp_get_seid(sep->sep) == *seid; +} + +static int match_sep(const void *data, const void *user_data) +{ + struct a2dp_sep *sep = (void *) data; + const uint8_t *seid = user_data; + + return *seid - avdtp_sep_get_seid(sep->lsep); +} + +static struct a2dp_sep *find_sep_by_seid(struct a2dp_server *server, + uint8_t seid) +{ + GSList *l; + + l = g_slist_find_custom(server->sources, &seid, match_sep); + if (l) + return l->data; + + l = g_slist_find_custom(server->sinks, &seid, match_sep); + if (l) + return l->data; + + return NULL; +} + +static void load_remote_sep(struct a2dp_channel *chan, GKeyFile *key_file, + char **seids) +{ + struct a2dp_sep *lsep; + struct a2dp_remote_sep *sep; + struct avdtp_remote_sep *rsep; + uint8_t lseid, rseid; + char *value; + + if (!seids) + return; + + for (; *seids; seids++) { + uint8_t type; + uint8_t codec; + GSList *l = NULL; + char caps[256]; + uint8_t data[128]; + int i, size; + + if (sscanf(*seids, "%02hhx", &rseid) != 1) + continue; + + value = g_key_file_get_string(key_file, "Endpoints", *seids, + NULL); + if (!value) + continue; + + if (sscanf(value, "%02hhx:%02hhx:%s", &type, &codec, + caps) != 3) { + warn("Unable to load Endpoint: seid %u", rseid); + g_free(value); + continue; + } + + for (i = 0, size = strlen(caps); i < size; i += 2) { + uint8_t *tmp = data + i / 2; + + if (sscanf(caps + i, "%02hhx", tmp) != 1) { + warn("Unable to load Endpoint: seid %u", rseid); + g_free(value); + break; + } + } + + g_free(value); + + if (i != size) + continue; + + caps_add_codec(&l, codec, data, size / 2); + + rsep = avdtp_register_remote_sep(chan->session, rseid, type, l); + if (!rsep) { + warn("Unable to register Endpoint: seid %u", rseid); + continue; + } + + register_remote_sep(rsep, chan); + } + + value = g_key_file_get_string(key_file, "Endpoints", "LastUsed", NULL); + if (!value) + return; + + if (sscanf(value, "%02hhx:%02hhx", &lseid, &rseid) != 2) { + warn("Unable to load LastUsed"); + g_free(value); + return; + } + + g_free(value); + + lsep = find_sep_by_seid(chan->server, lseid); + if (!lsep) { + warn("Unable to load LastUsed: lseid %u not found", lseid); + return; + } + + sep = queue_find(chan->seps, match_seid, &rseid); + if (!sep) { + warn("Unable to load LastUsed: rseid %u not found", rseid); + return; + } + + add_last_used(chan, lsep, sep); +} + +static void load_remote_seps(struct a2dp_channel *chan) +{ + struct btd_device *device = chan->device; + char filename[PATH_MAX]; + char dst_addr[18]; + char **keys; + GKeyFile *key_file; + + ba2str(device_get_address(device), dst_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", + btd_adapter_get_storage_dir(device_get_adapter(device)), + dst_addr); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + keys = g_key_file_get_keys(key_file, "Endpoints", NULL, NULL); + + load_remote_sep(chan, key_file, keys); + + g_strfreev(keys); + g_key_file_free(key_file); +} + +static void avdtp_state_cb(struct btd_device *dev, struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data) +{ + struct a2dp_channel *chan = user_data; + + switch (new_state) { + case AVDTP_SESSION_STATE_DISCONNECTED: + if (chan->session == session) + channel_remove(chan); + break; + case AVDTP_SESSION_STATE_CONNECTING: + break; + case AVDTP_SESSION_STATE_CONNECTED: + if (!chan->session) + chan->session = session; + load_remote_seps(chan); + break; + } +} + +static struct a2dp_channel *channel_new(struct a2dp_server *server, + struct btd_device *device, + GIOChannel *io) +{ + struct a2dp_channel *chan; + + chan = g_new0(struct a2dp_channel, 1); + chan->server = server; + chan->device = device; + chan->seps = queue_new(); + chan->state_id = avdtp_add_state_cb(device, avdtp_state_cb, chan); + + if (!queue_push_tail(server->channels, chan)) { + g_free(chan); + return NULL; + } + + if (!io) + return chan; + + chan->io = g_io_channel_ref(io); + chan->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) disconnect_cb, chan); + + return chan; +} + +static bool match_by_device(const void *data, const void *user_data) +{ + const struct a2dp_channel *chan = data; + const struct btd_device *device = user_data; + + return chan->device == device; +} + +struct avdtp *a2dp_avdtp_get(struct btd_device *device) +{ + struct a2dp_server *server; + struct a2dp_channel *chan; + const struct queue_entry *entry; + + server = find_server(servers, device_get_adapter(device)); + if (server == NULL) + return NULL; + + chan = queue_find(server->channels, match_by_device, device); + if (!chan) { + chan = channel_new(server, device, NULL); + if (!chan) + return NULL; + } + + if (chan->session) + return avdtp_ref(chan->session); + + /* Check if there is any SEP available */ + for (entry = queue_get_entries(server->seps); entry; + entry = entry->next) { + struct avdtp_local_sep *sep = entry->data; + + if (avdtp_sep_get_state(sep) == AVDTP_STATE_IDLE) + goto found; + } + + DBG("Unable to find any available SEP"); + + return NULL; + +found: + chan->session = avdtp_new(NULL, device, server->seps); + if (!chan->session) { + channel_remove(chan); + return NULL; + } + + return avdtp_ref(chan->session); +} + +static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) +{ + struct a2dp_channel *chan = user_data; + + if (err) { + error("%s", err->message); + goto fail; + } + + chan->session = avdtp_new(chan->io, chan->device, chan->server->seps); + if (!chan->session) { + error("Unable to create AVDTP session"); + goto fail; + } + + g_io_channel_unref(chan->io); + chan->io = NULL; + + g_source_remove(chan->io_id); + chan->io_id = 0; + + return; + +fail: + channel_remove(chan); +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct a2dp_channel *chan = user_data; + GError *err = NULL; + + chan->auth_id = 0; + + if (derr && dbus_error_is_set(derr)) { + error("Access denied: %s", derr->message); + goto fail; + } + + if (!bt_io_accept(chan->io, connect_cb, chan, NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + goto fail; + } + + return; + +fail: + channel_remove(chan); +} + +static void transport_cb(GIOChannel *io, GError *err, gpointer user_data) +{ + struct a2dp_setup *setup = user_data; + uint16_t omtu, imtu; + + if (err) { + error("%s", err->message); + goto drop; + } + + bt_io_get(io, &err, BT_IO_OPT_OMTU, &omtu, BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + goto drop; + } + + if (!avdtp_stream_set_transport(setup->stream, + g_io_channel_unix_get_fd(io), + imtu, omtu)) + goto drop; + + g_io_channel_set_close_on_unref(io, FALSE); + + g_io_channel_unref(setup->io); + setup->io = NULL; + + setup_unref(setup); + + return; + +drop: + setup_unref(setup); + g_io_channel_shutdown(io, TRUE, NULL); + +} +static void confirm_cb(GIOChannel *io, gpointer data) +{ + struct a2dp_server *server = data; + struct a2dp_channel *chan; + char address[18]; + bdaddr_t src, dst; + GError *err = NULL; + struct btd_device *device; + + bt_io_get(io, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, address, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + goto drop; + } + + DBG("AVDTP: incoming connect from %s", address); + + device = btd_adapter_find_device(adapter_find(&src), &dst, + BDADDR_BREDR); + if (!device) + goto drop; + + chan = queue_find(server->channels, match_by_device, device); + if (chan) { + struct a2dp_setup *setup; + + setup = find_setup_by_session(chan->session); + if (!setup || !setup->stream) + goto drop; + + if (setup->io) { + error("transport channel already exists"); + goto drop; + } + + if (!bt_io_accept(io, transport_cb, setup, NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + goto drop; + } + + /* + * Reference the channel so it can be shutdown properly + * stopping bt_io_accept from calling the callback with invalid + * setup pointer. + */ + setup->io = g_io_channel_ref(io); + + return; + } + + chan = channel_new(server, device, io); + if (!chan) + goto drop; + + chan->auth_id = btd_request_authorization(&src, &dst, + ADVANCED_AUDIO_UUID, + auth_cb, chan); + if (chan->auth_id == 0 && !chan->session) + channel_remove(chan); + + return; + +drop: + g_io_channel_shutdown(io, TRUE, NULL); +} + +static bool a2dp_server_listen(struct a2dp_server *server) +{ + GError *err = NULL; + + if (server->io) + return true; + + server->io = bt_io_listen(NULL, confirm_cb, server, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(server->adapter), + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, true, + BT_IO_OPT_INVALID); + if (server->io) + return true; + + error("%s", err->message); + g_error_free(err); + + return false; +} + +static struct a2dp_server *a2dp_server_register(struct btd_adapter *adapter) +{ + struct a2dp_server *server; + + server = g_new0(struct a2dp_server, 1); + + server->adapter = btd_adapter_ref(adapter); + server->seps = queue_new(); + server->channels = queue_new(); + + servers = g_slist_append(servers, server); + + return server; +} + +static void a2dp_unregister_sep(struct a2dp_sep *sep) +{ + struct a2dp_server *server = sep->server; + + if (sep->destroy) { + sep->destroy(sep->user_data); + sep->endpoint = NULL; + } + + avdtp_unregister_sep(server->seps, sep->lsep); + + g_free(sep); + + if (!queue_isempty(server->seps)) + return; + + if (server->io) { + g_io_channel_shutdown(server->io, TRUE, NULL); + g_io_channel_unref(server->io); + server->io = NULL; + } +} + +static void a2dp_server_unregister(struct a2dp_server *server) +{ + servers = g_slist_remove(servers, server); + queue_destroy(server->channels, channel_free); + queue_destroy(server->seps, NULL); + + if (server->io) { + g_io_channel_shutdown(server->io, TRUE, NULL); + g_io_channel_unref(server->io); + } + + btd_adapter_unref(server->adapter); + g_free(server); +} + +struct a2dp_sep *a2dp_add_sep(struct btd_adapter *adapter, uint8_t type, + uint8_t codec, gboolean delay_reporting, + struct a2dp_endpoint *endpoint, + void *user_data, GDestroyNotify destroy, + int *err) +{ + struct a2dp_server *server; + struct a2dp_sep *sep; + GSList **l; + uint32_t *record_id; + sdp_record_t *record; + + server = find_server(servers, adapter); + if (server == NULL) { + if (err) + *err = -EPROTONOSUPPORT; + return NULL; + } + + if (type == AVDTP_SEP_TYPE_SINK && !server->sink_enabled) { + if (err) + *err = -EPROTONOSUPPORT; + return NULL; + } + + if (type == AVDTP_SEP_TYPE_SOURCE && !server->source_enabled) { + if (err) + *err = -EPROTONOSUPPORT; + return NULL; + } + + sep = g_new0(struct a2dp_sep, 1); + + sep->lsep = avdtp_register_sep(server->seps, type, + AVDTP_MEDIA_TYPE_AUDIO, codec, + delay_reporting, &endpoint_ind, + &cfm, sep); + + if (sep->lsep == NULL) { + g_free(sep); + if (err) + *err = -EINVAL; + return NULL; + } + + sep->server = server; + sep->endpoint = endpoint; + sep->codec = codec; + sep->type = type; + sep->delay_reporting = delay_reporting; + sep->user_data = user_data; + sep->destroy = destroy; + + if (type == AVDTP_SEP_TYPE_SOURCE) { + l = &server->sources; + record_id = &server->source_record_id; + } else { + l = &server->sinks; + record_id = &server->sink_record_id; + } + + if (*record_id != 0) + goto add; + + record = a2dp_record(type); + if (!record) { + error("Unable to allocate new service record"); + a2dp_unregister_sep(sep); + if (err) + *err = -EINVAL; + return NULL; + } + + if (adapter_service_add(server->adapter, record) < 0) { + error("Unable to register A2DP service record"); + sdp_record_free(record); + a2dp_unregister_sep(sep); + if (err) + *err = -EINVAL; + return NULL; + } + + if (!a2dp_server_listen(server)) { + sdp_record_free(record); + a2dp_unregister_sep(sep); + if (err) + *err = -EINVAL; + return NULL; + } + + *record_id = record->handle; + +add: + *l = g_slist_append(*l, sep); + + if (err) + *err = 0; + return sep; +} + +void a2dp_remove_sep(struct a2dp_sep *sep) +{ + struct a2dp_server *server = sep->server; + + if (sep->type == AVDTP_SEP_TYPE_SOURCE) { + if (g_slist_find(server->sources, sep) == NULL) + return; + server->sources = g_slist_remove(server->sources, sep); + if (server->sources == NULL && server->source_record_id) { + adapter_service_remove(server->adapter, + server->source_record_id); + server->source_record_id = 0; + } + } else { + if (g_slist_find(server->sinks, sep) == NULL) + return; + server->sinks = g_slist_remove(server->sinks, sep); + if (server->sinks == NULL && server->sink_record_id) { + adapter_service_remove(server->adapter, + server->sink_record_id); + server->sink_record_id = 0; + } + } + + if (sep->locked) + return; + + a2dp_unregister_sep(sep); +} + +static void select_cb(struct a2dp_setup *setup, void *ret, int size) +{ + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + int err; + + if (size >= 0) { + caps_add_codec(&setup->caps, setup->sep->codec, ret, size); + goto done; + } + + setup->sep = queue_pop_head(setup->eps); + if (!setup->sep) { + error("Unable to select a valid configuration"); + goto done; + } + + setup->rsep = find_remote_sep(setup->chan, setup->sep); + service = avdtp_get_codec(setup->rsep->sep); + codec = (struct avdtp_media_codec_capability *) service->data; + + err = setup->sep->endpoint->select_configuration(setup->sep, + codec->data, + service->length - sizeof(*codec), + setup, + select_cb, setup->sep->user_data); + if (err == 0) + return; + +done: + finalize_select(setup); + setup_unref(setup); +} + +static struct queue *a2dp_find_eps(struct avdtp *session, GSList *list, + const char *sender) +{ + struct a2dp_channel *chan = find_channel(session); + struct queue *seps = NULL; + + for (; list; list = list->next) { + struct a2dp_sep *sep = list->data; + struct avdtp_remote_sep *rsep; + + /* Use sender's endpoint if available */ + if (sender) { + const char *name; + + if (sep->endpoint == NULL) + continue; + + name = sep->endpoint->get_name(sep, sep->user_data); + if (g_strcmp0(sender, name) != 0) + continue; + } + + rsep = avdtp_find_remote_sep(session, sep->lsep); + if (!rsep) + continue; + + if (!seps) + seps = queue_new(); + + /* Prepend last used so it is preferred over others */ + if (chan->last_used && (chan->last_used->lsep == sep && + chan->last_used->rsep->sep == rsep)) + queue_push_head(seps, sep); + else + queue_push_tail(seps, sep); + } + + return seps; +} + +static struct queue *a2dp_select_eps(struct avdtp *session, uint8_t type, + const char *sender) +{ + struct a2dp_server *server; + struct queue *seps; + GSList *l; + + server = find_server(servers, avdtp_get_adapter(session)); + if (!server) + return NULL; + + l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks; + + /* Check sender's seps first */ + seps = a2dp_find_eps(session, l, sender); + if (seps != NULL) + return seps; + + return a2dp_find_eps(session, l, NULL); +} + +static void store_remote_sep(void *data, void *user_data) +{ + struct a2dp_remote_sep *sep = data; + GKeyFile *key_file = (void *) user_data; + char seid[4], value[256]; + struct avdtp_service_capability *service = avdtp_get_codec(sep->sep); + struct avdtp_media_codec_capability *codec = (void *) service->data; + unsigned int i; + ssize_t offset; + + sprintf(seid, "%02hhx", avdtp_get_seid(sep->sep)); + + offset = sprintf(value, "%02hhx:%02hhx:", avdtp_get_type(sep->sep), + codec->media_codec_type); + + for (i = 0; i < service->length - sizeof(*codec); i++) + offset += sprintf(value + offset, "%02hhx", codec->data[i]); + + + g_key_file_set_string(key_file, "Endpoints", seid, value); +} + +static void store_remote_seps(struct a2dp_channel *chan) +{ + struct btd_device *device = chan->device; + char filename[PATH_MAX]; + char dst_addr[18]; + GKeyFile *key_file; + char *data; + gsize length = 0; + + if (queue_isempty(chan->seps)) + return; + + ba2str(device_get_address(device), dst_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", + btd_adapter_get_storage_dir(device_get_adapter(device)), + dst_addr); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + data = g_key_file_get_string(key_file, "Endpoints", "LastUsed", NULL); + + /* Remove current endpoints since it might have changed */ + g_key_file_remove_group(key_file, "Endpoints", NULL); + + queue_foreach(chan->seps, store_remote_sep, key_file); + + if (data) { + g_key_file_set_string(key_file, "Endpoints", "LastUsed", data); + g_free(data); + } + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, data, length, NULL); + + g_free(data); + g_key_file_free(key_file); +} + +static void discover_cb(struct avdtp *session, GSList *seps, + struct avdtp_error *err, void *user_data) +{ + struct a2dp_setup *setup = user_data; + + DBG("err %p", err); + + setup->seps = seps; + setup->err = err; + + if (!err) { + g_slist_foreach(seps, register_remote_sep, setup->chan); + store_remote_seps(setup->chan); + } + + finalize_discover(setup); +} + +unsigned int a2dp_discover(struct avdtp *session, a2dp_discover_cb_t cb, + void *user_data) +{ + struct a2dp_setup *setup; + struct a2dp_setup_cb *cb_data; + + setup = a2dp_setup_get(session); + if (!setup) + return 0; + + cb_data = setup_cb_new(setup); + cb_data->discover_cb = cb; + cb_data->user_data = user_data; + + if (avdtp_discover(session, discover_cb, setup) == 0) + return cb_data->id; + + setup_cb_free(cb_data); + return 0; +} + +unsigned int a2dp_select_capabilities(struct avdtp *session, + uint8_t type, const char *sender, + a2dp_select_cb_t cb, + void *user_data) +{ + struct a2dp_setup *setup; + struct a2dp_setup_cb *cb_data; + struct queue *eps; + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + int err; + + eps = a2dp_select_eps(session, type, sender); + if (!eps) { + error("Unable to select SEP"); + return 0; + } + + setup = a2dp_setup_get(session); + if (!setup) + return 0; + + cb_data = setup_cb_new(setup); + cb_data->select_cb = cb; + cb_data->user_data = user_data; + + setup->eps = eps; + setup->sep = queue_pop_head(eps); + setup->rsep = find_remote_sep(setup->chan, setup->sep); + + if (setup->rsep == NULL) { + error("Could not find remote sep"); + goto fail; + } + + service = avdtp_get_codec(setup->rsep->sep); + codec = (struct avdtp_media_codec_capability *) service->data; + + err = setup->sep->endpoint->select_configuration(setup->sep, + codec->data, + service->length - + sizeof(*codec), + setup_ref(setup), + select_cb, + setup->sep->user_data); + if (err == 0) + return cb_data->id; + + setup_unref(setup); + +fail: + setup_cb_free(cb_data); + return 0; + +} + +unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep, + a2dp_config_cb_t cb, GSList *caps, + void *user_data) +{ + struct a2dp_setup_cb *cb_data; + GSList *l; + struct a2dp_server *server; + struct a2dp_setup *setup; + struct a2dp_sep *tmp; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_cap = NULL; + int posix_err; + + server = find_server(servers, avdtp_get_adapter(session)); + if (!server) + return 0; + + for (l = caps; l != NULL; l = l->next) { + cap = l->data; + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + codec_cap = (void *) cap->data; + break; + } + + if (!codec_cap) + return 0; + + if (sep->codec != codec_cap->media_codec_type) + return 0; + + DBG("a2dp_config: selected SEP %p", sep->lsep); + + setup = a2dp_setup_get(session); + if (!setup) + return 0; + + cb_data = setup_cb_new(setup); + cb_data->config_cb = cb; + cb_data->user_data = user_data; + + setup->sep = sep; + setup->stream = sep->stream; + + /* Copy given caps if they are different than current caps */ + if (setup->caps != caps) { + g_slist_free_full(setup->caps, g_free); + setup->caps = g_slist_copy(caps); + } + + switch (avdtp_sep_get_state(sep->lsep)) { + case AVDTP_STATE_IDLE: + if (sep->type == AVDTP_SEP_TYPE_SOURCE) + l = server->sources; + else + l = server->sinks; + + for (; l != NULL; l = l->next) { + tmp = l->data; + if (avdtp_has_stream(session, tmp->stream)) + break; + } + + if (l != NULL) { + if (tmp->locked) + goto failed; + setup->reconfigure = TRUE; + if (avdtp_close(session, tmp->stream, FALSE) < 0) { + error("avdtp_close failed"); + goto failed; + } + break; + } + + setup->rsep = find_remote_sep(setup->chan, sep); + if (setup->rsep == NULL) { + error("No matching ACP and INT SEPs found"); + goto failed; + } + + posix_err = avdtp_set_configuration(session, setup->rsep->sep, + sep->lsep, caps, + &setup->stream); + if (posix_err < 0) { + error("avdtp_set_configuration: %s", + strerror(-posix_err)); + goto failed; + } + break; + case AVDTP_STATE_OPEN: + case AVDTP_STATE_STREAMING: + if (avdtp_stream_has_capabilities(setup->stream, caps)) { + DBG("Configuration match: resuming"); + cb_data->source_id = g_idle_add(finalize_config, + setup); + } else if (!setup->reconfigure) { + setup->reconfigure = TRUE; + if (avdtp_close(session, sep->stream, FALSE) < 0) { + error("avdtp_close failed"); + goto failed; + } + } + break; + case AVDTP_STATE_CONFIGURED: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + default: + error("SEP in bad state for requesting a new stream"); + goto failed; + } + + return cb_data->id; + +failed: + setup_cb_free(cb_data); + return 0; +} + +unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data) +{ + struct a2dp_setup_cb *cb_data; + struct a2dp_setup *setup; + + setup = a2dp_setup_get(session); + if (!setup) + return 0; + + cb_data = setup_cb_new(setup); + cb_data->resume_cb = cb; + cb_data->user_data = user_data; + + setup->sep = sep; + setup->stream = sep->stream; + + switch (avdtp_sep_get_state(sep->lsep)) { + case AVDTP_STATE_IDLE: + goto failed; + break; + case AVDTP_STATE_CONFIGURED: + setup->start = TRUE; + break; + case AVDTP_STATE_OPEN: + if (avdtp_start(session, sep->stream) < 0) { + error("avdtp_start failed"); + goto failed; + } + sep->starting = TRUE; + break; + case AVDTP_STATE_STREAMING: + if (!sep->suspending && sep->suspend_timer) { + g_source_remove(sep->suspend_timer); + sep->suspend_timer = 0; + avdtp_unref(sep->session); + sep->session = NULL; + } + if (sep->suspending) + setup->start = TRUE; + else + cb_data->source_id = g_idle_add(finalize_resume, + setup); + break; + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + default: + error("SEP in bad state for resume"); + goto failed; + } + + return cb_data->id; + +failed: + setup_cb_free(cb_data); + return 0; +} + +unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data) +{ + struct a2dp_setup_cb *cb_data; + struct a2dp_setup *setup; + + setup = a2dp_setup_get(session); + if (!setup) + return 0; + + cb_data = setup_cb_new(setup); + cb_data->suspend_cb = cb; + cb_data->user_data = user_data; + + setup->sep = sep; + setup->stream = sep->stream; + + switch (avdtp_sep_get_state(sep->lsep)) { + case AVDTP_STATE_IDLE: + error("a2dp_suspend: no stream to suspend"); + goto failed; + break; + case AVDTP_STATE_OPEN: + cb_data->source_id = g_idle_add(finalize_suspend, setup); + break; + case AVDTP_STATE_STREAMING: + if (avdtp_suspend(session, sep->stream) < 0) { + error("avdtp_suspend failed"); + goto failed; + } + sep->suspending = TRUE; + break; + case AVDTP_STATE_CONFIGURED: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + default: + error("SEP in bad state for suspend"); + goto failed; + } + + return cb_data->id; + +failed: + setup_cb_free(cb_data); + return 0; +} + +gboolean a2dp_cancel(unsigned int id) +{ + GSList *ls; + + for (ls = setups; ls != NULL; ls = g_slist_next(ls)) { + struct a2dp_setup *setup = ls->data; + GSList *l; + for (l = setup->cb; l != NULL; l = g_slist_next(l)) { + struct a2dp_setup_cb *cb = l->data; + + if (cb->id != id) + continue; + + setup_ref(setup); + setup_cb_free(cb); + + if (!setup->cb) { + DBG("aborting setup %p", setup); + if (!avdtp_abort(setup->session, setup->stream)) + return TRUE; + } + + setup_unref(setup); + return TRUE; + } + } + + return FALSE; +} + +gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session) +{ + if (sep->locked) + return FALSE; + + DBG("SEP %p locked", sep->lsep); + sep->locked = TRUE; + + return TRUE; +} + +gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session) +{ + struct a2dp_server *server = sep->server; + avdtp_state_t state; + GSList *l; + + state = avdtp_sep_get_state(sep->lsep); + + sep->locked = FALSE; + + DBG("SEP %p unlocked", sep->lsep); + + if (sep->type == AVDTP_SEP_TYPE_SOURCE) + l = server->sources; + else + l = server->sinks; + + /* Unregister sep if it was removed */ + if (g_slist_find(l, sep) == NULL) { + a2dp_unregister_sep(sep); + return TRUE; + } + + if (!sep->stream || state == AVDTP_STATE_IDLE) + return TRUE; + + switch (state) { + case AVDTP_STATE_OPEN: + /* Set timer here */ + break; + case AVDTP_STATE_STREAMING: + if (avdtp_suspend(session, sep->stream) == 0) + sep->suspending = TRUE; + break; + case AVDTP_STATE_IDLE: + case AVDTP_STATE_CONFIGURED: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + default: + break; + } + + return TRUE; +} + +struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep) +{ + return sep->stream; +} + +struct btd_device *a2dp_setup_get_device(struct a2dp_setup *setup) +{ + if (setup->session == NULL) + return NULL; + + return avdtp_get_device(setup->session); +} + +const char *a2dp_setup_remote_path(struct a2dp_setup *setup) +{ + if (setup->rsep) { + return setup->rsep->path; + } + + return NULL; +} + +static int a2dp_source_probe(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + + DBG("path %s", device_get_path(dev)); + + source_init(service); + + return 0; +} + +static void a2dp_source_remove(struct btd_service *service) +{ + source_unregister(service); +} + +static int a2dp_sink_probe(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + + DBG("path %s", device_get_path(dev)); + + return sink_init(service); +} + +static void a2dp_sink_remove(struct btd_service *service) +{ + sink_unregister(service); +} + +static int a2dp_source_connect(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + struct btd_adapter *adapter = device_get_adapter(dev); + struct a2dp_server *server; + const char *path = device_get_path(dev); + + DBG("path %s", path); + + server = find_server(servers, adapter); + if (!server || !server->sink_enabled) { + DBG("Unexpected error: cannot find server"); + return -EPROTONOSUPPORT; + } + + /* Return protocol not available if no record/endpoint exists */ + if (server->sink_record_id == 0) + return -ENOPROTOOPT; + + return source_connect(service); +} + +static int a2dp_source_disconnect(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + const char *path = device_get_path(dev); + + DBG("path %s", path); + + return source_disconnect(service); +} + +static int a2dp_sink_connect(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + struct btd_adapter *adapter = device_get_adapter(dev); + struct a2dp_server *server; + const char *path = device_get_path(dev); + + DBG("path %s", path); + + server = find_server(servers, adapter); + if (!server || !server->source_enabled) { + DBG("Unexpected error: cannot find server"); + return -EPROTONOSUPPORT; + } + + /* Return protocol not available if no record/endpoint exists */ + if (server->source_record_id == 0) + return -ENOPROTOOPT; + + return sink_connect(service); +} + +static int a2dp_sink_disconnect(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + const char *path = device_get_path(dev); + + DBG("path %s", path); + + return sink_disconnect(service); +} + +static int a2dp_source_server_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct a2dp_server *server; + + DBG("path %s", adapter_get_path(adapter)); + + server = find_server(servers, adapter); + if (server != NULL) + goto done; + + server = a2dp_server_register(adapter); + if (server == NULL) + return -EPROTONOSUPPORT; + +done: + server->source_enabled = TRUE; + + return 0; +} + +static void a2dp_source_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct a2dp_server *server; + + DBG("path %s", adapter_get_path(adapter)); + + server = find_server(servers, adapter); + if (!server) + return; + + g_slist_free_full(server->sources, + (GDestroyNotify) a2dp_unregister_sep); + + if (server->source_record_id) { + adapter_service_remove(server->adapter, + server->source_record_id); + server->source_record_id = 0; + } + + if (server->sink_record_id) + return; + + a2dp_server_unregister(server); +} + +static int a2dp_sink_server_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct a2dp_server *server; + + DBG("path %s", adapter_get_path(adapter)); + + server = find_server(servers, adapter); + if (server != NULL) + goto done; + + server = a2dp_server_register(adapter); + if (server == NULL) + return -EPROTONOSUPPORT; + +done: + server->sink_enabled = TRUE; + + return 0; +} + +static void a2dp_sink_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct a2dp_server *server; + + DBG("path %s", adapter_get_path(adapter)); + + server = find_server(servers, adapter); + if (!server) + return; + + g_slist_free_full(server->sinks, (GDestroyNotify) a2dp_unregister_sep); + + if (server->sink_record_id) { + adapter_service_remove(server->adapter, server->sink_record_id); + server->sink_record_id = 0; + } + + if (server->source_record_id) + return; + + a2dp_server_unregister(server); +} + +static int media_server_probe(struct btd_adapter *adapter) +{ + DBG("path %s", adapter_get_path(adapter)); + + return media_register(adapter); +} + +static void media_server_remove(struct btd_adapter *adapter) +{ + DBG("path %s", adapter_get_path(adapter)); + + media_unregister(adapter); +} + +static struct btd_profile a2dp_source_profile = { + .name = "a2dp-source", + .priority = BTD_PROFILE_PRIORITY_MEDIUM, + + .remote_uuid = A2DP_SOURCE_UUID, + .device_probe = a2dp_source_probe, + .device_remove = a2dp_source_remove, + + .auto_connect = true, + .connect = a2dp_source_connect, + .disconnect = a2dp_source_disconnect, + + .adapter_probe = a2dp_sink_server_probe, + .adapter_remove = a2dp_sink_server_remove, +}; + +static struct btd_profile a2dp_sink_profile = { + .name = "a2dp-sink", + .priority = BTD_PROFILE_PRIORITY_MEDIUM, + + .remote_uuid = A2DP_SINK_UUID, + .device_probe = a2dp_sink_probe, + .device_remove = a2dp_sink_remove, + + .auto_connect = true, + .connect = a2dp_sink_connect, + .disconnect = a2dp_sink_disconnect, + + .adapter_probe = a2dp_source_server_probe, + .adapter_remove = a2dp_source_server_remove, +}; + +static struct btd_adapter_driver media_driver = { + .name = "media", + .probe = media_server_probe, + .remove = media_server_remove, +}; + +static int a2dp_init(void) +{ + btd_register_adapter_driver(&media_driver); + btd_profile_register(&a2dp_source_profile); + btd_profile_register(&a2dp_sink_profile); + + return 0; +} + +static void a2dp_exit(void) +{ + btd_unregister_adapter_driver(&media_driver); + btd_profile_unregister(&a2dp_source_profile); + btd_profile_unregister(&a2dp_sink_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(a2dp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + a2dp_init, a2dp_exit) diff --git a/profiles/audio/a2dp.h b/profiles/audio/a2dp.h new file mode 100644 index 0000000..19466a4 --- /dev/null +++ b/profiles/audio/a2dp.h @@ -0,0 +1,95 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2011 BMW Car IT GmbH. All rights reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 a2dp_sep; +struct a2dp_setup; + +typedef void (*a2dp_endpoint_select_t) (struct a2dp_setup *setup, void *ret, + int size); +typedef void (*a2dp_endpoint_config_t) (struct a2dp_setup *setup, gboolean ret); + +struct a2dp_endpoint { + const char *(*get_name) (struct a2dp_sep *sep, void *user_data); + const char *(*get_path) (struct a2dp_sep *sep, void *user_data); + size_t (*get_capabilities) (struct a2dp_sep *sep, + uint8_t **capabilities, + void *user_data); + int (*select_configuration) (struct a2dp_sep *sep, + uint8_t *capabilities, + size_t length, + struct a2dp_setup *setup, + a2dp_endpoint_select_t cb, + void *user_data); + int (*set_configuration) (struct a2dp_sep *sep, + uint8_t *configuration, + size_t length, + struct a2dp_setup *setup, + a2dp_endpoint_config_t cb, + void *user_data); + void (*clear_configuration) (struct a2dp_sep *sep, void *user_data); + void (*set_delay) (struct a2dp_sep *sep, uint16_t delay, + void *user_data); +}; + +typedef void (*a2dp_discover_cb_t) (struct avdtp *session, GSList *seps, + int err, void *user_data); +typedef void (*a2dp_select_cb_t) (struct avdtp *session, + struct a2dp_sep *sep, GSList *caps, + void *user_data); +typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, int err, + void *user_data); +typedef void (*a2dp_stream_cb_t) (struct avdtp *session, int err, + void *user_data); + +struct a2dp_sep *a2dp_add_sep(struct btd_adapter *adapter, uint8_t type, + uint8_t codec, gboolean delay_reporting, + struct a2dp_endpoint *endpoint, + void *user_data, GDestroyNotify destroy, + int *err); +void a2dp_remove_sep(struct a2dp_sep *sep); + + +unsigned int a2dp_discover(struct avdtp *session, a2dp_discover_cb_t cb, + void *user_data); +unsigned int a2dp_select_capabilities(struct avdtp *session, + uint8_t type, const char *sender, + a2dp_select_cb_t cb, + void *user_data); +unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep, + a2dp_config_cb_t cb, GSList *caps, + void *user_data); +unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data); +unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data); +gboolean a2dp_cancel(unsigned int id); + +gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session); +gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session); +struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep); +struct btd_device *a2dp_setup_get_device(struct a2dp_setup *setup); +const char *a2dp_setup_remote_path(struct a2dp_setup *setup); +struct avdtp *a2dp_avdtp_get(struct btd_device *device); diff --git a/profiles/audio/avctp.c b/profiles/audio/avctp.c new file mode 100644 index 0000000..70a3e40 --- /dev/null +++ b/profiles/audio/avctp.c @@ -0,0 +1,2199 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/l2cap.h" +#include "lib/uuid.h" + +#include "btio/btio.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/log.h" +#include "src/error.h" +#include "src/uinput.h" + +#include "avctp.h" +#include "avrcp.h" + +/* AV/C Panel 1.23, page 76: + * command with the pressed value is valid for two seconds + */ +#define AVC_PRESS_TIMEOUT 2 + +#define CONTROL_TIMEOUT 10 +#define BROWSING_TIMEOUT 10 + +#define PASSTHROUGH_QUEUE 0 +#define CONTROL_QUEUE 1 + +#define QUIRK_NO_RELEASE 1 << 0 + +/* Message types */ +#define AVCTP_COMMAND 0 +#define AVCTP_RESPONSE 1 + +/* Packet types */ +#define AVCTP_PACKET_SINGLE 0 +#define AVCTP_PACKET_START 1 +#define AVCTP_PACKET_CONTINUE 2 +#define AVCTP_PACKET_END 3 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avctp_header { + uint8_t ipid:1; + uint8_t cr:1; + uint8_t packet_type:2; + uint8_t transaction:4; + uint16_t pid; +} __attribute__ ((packed)); +#define AVCTP_HEADER_LENGTH 3 + +struct avc_header { + uint8_t code:4; + uint8_t _hdr0:4; + uint8_t subunit_id:3; + uint8_t subunit_type:5; + uint8_t opcode; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avctp_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t cr:1; + uint8_t ipid:1; + uint16_t pid; +} __attribute__ ((packed)); +#define AVCTP_HEADER_LENGTH 3 + +struct avc_header { + uint8_t _hdr0:4; + uint8_t code:4; + uint8_t subunit_type:5; + uint8_t subunit_id:3; + uint8_t opcode; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +struct avctp_state_callback { + avctp_state_cb cb; + struct btd_device *dev; + unsigned int id; + void *user_data; +}; + +struct avctp_server { + struct btd_adapter *adapter; + GIOChannel *control_io; + GIOChannel *browsing_io; + GSList *sessions; +}; + +struct avctp_control_req { + struct avctp_pending_req *p; + uint8_t code; + uint8_t subunit; + uint8_t op; + uint8_t *operands; + uint16_t operand_count; + avctp_rsp_cb func; + void *user_data; +}; + +struct avctp_browsing_req { + struct avctp_pending_req *p; + uint8_t *operands; + uint16_t operand_count; + avctp_browsing_rsp_cb func; + void *user_data; +}; + +typedef int (*avctp_process_cb) (void *data); + +struct avctp_pending_req { + struct avctp_queue *queue; + uint8_t transaction; + guint timeout; + bool retry; + int err; + avctp_process_cb process; + void *data; + GDestroyNotify destroy; +}; + +struct avctp_queue { + struct avctp_channel *chan; + struct avctp_pending_req *p; + GQueue *queue; + guint process_id; +}; + +struct avctp_channel { + struct avctp *session; + GIOChannel *io; + uint8_t transaction; + guint watch; + uint16_t imtu; + uint16_t omtu; + uint8_t *buffer; + GSList *handlers; + GSList *queues; + GSList *processed; + GDestroyNotify destroy; +}; + +struct key_pressed { + uint16_t op; + guint timer; +}; + +struct avctp { + struct avctp_server *server; + struct btd_device *device; + + avctp_state_t state; + + int uinput; + + guint auth_id; + unsigned int passthrough_id; + unsigned int unit_id; + unsigned int subunit_id; + + struct avctp_channel *control; + struct avctp_channel *browsing; + + struct avctp_passthrough_handler *handler; + + uint8_t key_quirks[256]; + struct key_pressed key; + bool initiator; +}; + +struct avctp_passthrough_handler { + avctp_passthrough_cb cb; + void *user_data; + unsigned int id; +}; + +struct avctp_pdu_handler { + uint8_t opcode; + avctp_control_pdu_cb cb; + void *user_data; + unsigned int id; +}; + +struct avctp_browsing_pdu_handler { + avctp_browsing_pdu_cb cb; + void *user_data; + unsigned int id; + GDestroyNotify destroy; +}; + +static 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 GSList *callbacks = NULL; +static GSList *servers = NULL; + +static void auth_cb(DBusError *derr, void *user_data); +static gboolean process_queue(gpointer user_data); +static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code, + uint8_t subunit, uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data); + +static int send_event(int fd, uint16_t type, uint16_t code, int32_t value) +{ + struct uinput_event event; + + memset(&event, 0, sizeof(event)); + event.type = type; + event.code = code; + event.value = value; + + return write(fd, &event, sizeof(event)); +} + +static void send_key(int fd, uint16_t key, int pressed) +{ + if (fd < 0) + return; + + send_event(fd, EV_KEY, key, pressed); + send_event(fd, EV_SYN, SYN_REPORT, 0); +} + +static 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 void handle_press(struct avctp *session, uint16_t op) +{ + if (session->key.timer > 0) { + g_source_remove(session->key.timer); + + /* Only auto release if keys are different */ + if (session->key.op == op) + goto done; + + send_key(session->uinput, session->key.op, 0); + } + + session->key.op = op; + + send_key(session->uinput, op, 1); + +done: + session->key.timer = g_timeout_add_seconds(AVC_PRESS_TIMEOUT, + auto_release, session); +} + +static void handle_release(struct avctp *session, uint16_t op) +{ + if (session->key.timer > 0) { + g_source_remove(session->key.timer); + session->key.timer = 0; + } + + send_key(session->uinput, op, 0); +} + +static size_t handle_panel_passthrough(struct avctp *session, + uint8_t transaction, uint8_t *code, + uint8_t *subunit, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avctp_passthrough_handler *handler = session->handler; + const char *status; + int pressed, i; + + if (*code != AVC_CTYPE_CONTROL || *subunit != AVC_SUBUNIT_PANEL) { + *code = AVC_CTYPE_REJECTED; + return operand_count; + } + + if (operand_count == 0) + goto done; + + if (operands[0] & 0x80) { + status = "released"; + pressed = 0; + } else { + status = "pressed"; + pressed = 1; + } + + if (session->key.timer == 0 && handler != NULL) { + if (handler->cb(session, operands[0] & 0x7F, + pressed, handler->user_data)) + goto done; + } + + for (i = 0; key_map[i].name != NULL; i++) { + uint8_t key_quirks; + + if ((operands[0] & 0x7F) != key_map[i].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) + handle_press(session, key_map[i].uinput); + else + handle_release(session, key_map[i].uinput); + + break; + } + + if (key_map[i].name == NULL) { + DBG("AV/C: unknown button 0x%02X %s", + operands[0] & 0x7F, status); + *code = AVC_CTYPE_NOT_IMPLEMENTED; + return operand_count; + } + +done: + *code = AVC_CTYPE_ACCEPTED; + return operand_count; +} + +static size_t handle_unit_info(struct avctp *session, + uint8_t transaction, uint8_t *code, + uint8_t *subunit, uint8_t *operands, + size_t operand_count, void *user_data) +{ + if (*code != AVC_CTYPE_STATUS) { + *code = AVC_CTYPE_REJECTED; + return 0; + } + + *code = AVC_CTYPE_STABLE; + + /* The first operand should be 0x07 for the UNITINFO response. + * Neither AVRCP (section 22.1, page 117) nor AVC Digital + * Interface Command Set (section 9.2.1, page 45) specs + * explain this value but both use it */ + if (operand_count >= 1) + operands[0] = 0x07; + if (operand_count >= 2) + operands[1] = AVC_SUBUNIT_PANEL << 3; + + DBG("reply to AVC_OP_UNITINFO"); + + return operand_count; +} + +static size_t handle_subunit_info(struct avctp *session, + uint8_t transaction, uint8_t *code, + uint8_t *subunit, uint8_t *operands, + size_t operand_count, void *user_data) +{ + if (*code != AVC_CTYPE_STATUS) { + *code = AVC_CTYPE_REJECTED; + return 0; + } + + *code = AVC_CTYPE_STABLE; + + /* The first operand should be 0x07 for the UNITINFO response. + * Neither AVRCP (section 22.1, page 117) nor AVC Digital + * Interface Command Set (section 9.2.1, page 45) specs + * explain this value but both use it */ + if (operand_count >= 2) + operands[1] = AVC_SUBUNIT_PANEL << 3; + + DBG("reply to AVC_OP_SUBUNITINFO"); + + return operand_count; +} + +static struct avctp_pdu_handler *find_handler(GSList *list, uint8_t opcode) +{ + for (; list; list = list->next) { + struct avctp_pdu_handler *handler = list->data; + + if (handler->opcode == opcode) + return handler; + } + + return NULL; +} + +static void pending_destroy(gpointer data, gpointer user_data) +{ + struct avctp_pending_req *req = data; + + if (req->destroy) + req->destroy(req->data); + + if (req->timeout > 0) + g_source_remove(req->timeout); + + g_free(req); +} + +static void avctp_queue_destroy(void *data) +{ + struct avctp_queue *queue = data; + + if (queue->process_id > 0) + g_source_remove(queue->process_id); + + if (queue->p) + pending_destroy(queue->p, NULL); + + g_queue_foreach(queue->queue, pending_destroy, NULL); + g_queue_free(queue->queue); + g_free(queue); +} + +static void avctp_channel_destroy(struct avctp_channel *chan) +{ + g_io_channel_shutdown(chan->io, TRUE, NULL); + g_io_channel_unref(chan->io); + + if (chan->watch) + g_source_remove(chan->watch); + + if (chan->destroy) + chan->destroy(chan); + + g_free(chan->buffer); + g_slist_foreach(chan->processed, pending_destroy, NULL); + g_slist_free(chan->processed); + g_slist_free_full(chan->queues, avctp_queue_destroy); + g_slist_free_full(chan->handlers, g_free); + g_free(chan); +} + +static void avctp_disconnected(struct avctp *session) +{ + struct avctp_server *server; + + if (!session) + return; + + if (session->browsing) + avctp_channel_destroy(session->browsing); + + if (session->control) + avctp_channel_destroy(session->control); + + if (session->auth_id != 0) { + btd_cancel_authorization(session->auth_id); + session->auth_id = 0; + } + + if (session->key.timer > 0) + g_source_remove(session->key.timer); + + if (session->uinput >= 0) { + char address[18]; + + ba2str(device_get_address(session->device), address); + DBG("AVCTP: closing uinput for %s", address); + + ioctl(session->uinput, UI_DEV_DESTROY); + close(session->uinput); + session->uinput = -1; + } + + server = session->server; + server->sessions = g_slist_remove(server->sessions, session); + btd_device_unref(session->device); + g_free(session); +} + +static void avctp_set_state(struct avctp *session, avctp_state_t new_state, + int err) +{ + GSList *l; + avctp_state_t old_state = session->state; + + session->state = new_state; + + for (l = callbacks; l != NULL; l = l->next) { + struct avctp_state_callback *cb = l->data; + + if (cb->dev && cb->dev != session->device) + continue; + + cb->cb(session->device, old_state, new_state, err, + cb->user_data); + } + + switch (new_state) { + case AVCTP_STATE_DISCONNECTED: + DBG("AVCTP Disconnected"); + avctp_disconnected(session); + break; + case AVCTP_STATE_CONNECTING: + DBG("AVCTP Connecting"); + break; + case AVCTP_STATE_CONNECTED: + DBG("AVCTP Connected"); + break; + case AVCTP_STATE_BROWSING_CONNECTING: + DBG("AVCTP Browsing Connecting"); + break; + case AVCTP_STATE_BROWSING_CONNECTED: + DBG("AVCTP Browsing Connected"); + break; + default: + error("Invalid AVCTP state %d", new_state); + return; + } +} + +static uint8_t chan_get_transaction(struct avctp_channel *chan) +{ + GSList *l, *tmp; + uint8_t transaction; + + if (!chan->processed) + goto done; + + tmp = g_slist_copy(chan->processed); + + /* Find first unused transaction id */ + for (l = tmp; l; l = g_slist_next(l)) { + struct avctp_pending_req *req = l->data; + + if (req->transaction == chan->transaction) { + chan->transaction++; + chan->transaction %= 16; + tmp = g_slist_delete_link(tmp, l); + l = tmp; + } + } + + g_slist_free(tmp); + +done: + transaction = chan->transaction; + + chan->transaction++; + chan->transaction %= 16; + + return transaction; +} + +static int avctp_send(struct avctp_channel *control, uint8_t transaction, + uint8_t cr, uint8_t code, + uint8_t subunit, uint8_t opcode, + uint8_t *operands, size_t operand_count) +{ + struct avctp_header *avctp; + struct avc_header *avc; + struct msghdr msg; + struct iovec iov[2]; + int sk, err = 0; + + iov[0].iov_base = control->buffer; + iov[0].iov_len = sizeof(*avctp) + sizeof(*avc); + iov[1].iov_base = operands; + iov[1].iov_len = operand_count; + + if (control->omtu < (iov[0].iov_len + iov[1].iov_len)) + return -EOVERFLOW; + + sk = g_io_channel_unix_get_fd(control->io); + + memset(control->buffer, 0, iov[0].iov_len); + + avctp = (void *) control->buffer; + avc = (void *) avctp + sizeof(*avctp); + + if (transaction > 16) + transaction = chan_get_transaction(control); + + avctp->transaction = transaction; + avctp->packet_type = AVCTP_PACKET_SINGLE; + avctp->cr = cr; + avctp->pid = htons(AV_REMOTE_SVCLASS_ID); + + avc->code = code; + avc->subunit_type = subunit; + avc->opcode = opcode; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + if (sendmsg(sk, &msg, 0) < 0) + err = -errno; + + return err; +} + +static int avctp_browsing_send(struct avctp_queue *queue, + uint8_t transaction, uint8_t cr, + uint8_t *operands, size_t operand_count) +{ + struct avctp_channel *browsing = queue->chan; + struct avctp_header *avctp; + struct msghdr msg; + struct iovec iov[2]; + int sk, err = 0; + + iov[0].iov_base = browsing->buffer; + iov[0].iov_len = sizeof(*avctp); + iov[1].iov_base = operands; + iov[1].iov_len = operand_count; + + if (browsing->omtu < (iov[0].iov_len + iov[1].iov_len)) + return -EOVERFLOW; + + sk = g_io_channel_unix_get_fd(browsing->io); + + memset(browsing->buffer, 0, iov[0].iov_len); + + avctp = (void *) browsing->buffer; + + avctp->transaction = transaction; + avctp->packet_type = AVCTP_PACKET_SINGLE; + avctp->cr = cr; + avctp->pid = htons(AV_REMOTE_SVCLASS_ID); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + if (sendmsg(sk, &msg, 0) < 0) + err = -errno; + + return err; +} + +static void control_req_destroy(void *data) +{ + struct avctp_control_req *req = data; + struct avctp_pending_req *p = req->p; + struct avctp *session = p->queue->chan->session; + + if (p->err == 0 || req->func == NULL) + goto done; + + req->func(session, AVC_CTYPE_REJECTED, req->subunit, p->transaction, + NULL, 0, req->user_data); + +done: + g_free(req->operands); + g_free(req); +} + +static void browsing_req_destroy(void *data) +{ + struct avctp_browsing_req *req = data; + struct avctp_pending_req *p = req->p; + struct avctp *session = p->queue->chan->session; + + if (p->err == 0 || req->func == NULL) + goto done; + + req->func(session, NULL, 0, req->user_data); + +done: + g_free(req->operands); + g_free(req); +} + +static gboolean req_timeout(gpointer user_data) +{ + struct avctp_queue *queue = user_data; + struct avctp_pending_req *p = queue->p; + + DBG("transaction %u retry %s", p->transaction, p->retry ? "true" : + "false"); + + p->timeout = 0; + + if (p->retry) { + p->process(p->data); + return FALSE; + } + + p->err = -ETIMEDOUT; + + pending_destroy(p, NULL); + queue->p = NULL; + + if (queue->process_id == 0) + queue->process_id = g_idle_add(process_queue, queue); + + return FALSE; +} + +static int process_passthrough(void *data) +{ + struct avctp_control_req *req = data; + struct avctp_pending_req *p = req->p; + int ret; + + ret = avctp_send(p->queue->chan, p->transaction, AVCTP_COMMAND, + req->code, req->subunit, req->op, req->operands, + req->operand_count); + if (ret < 0) + return ret; + + p->timeout = g_timeout_add_seconds(AVC_PRESS_TIMEOUT, req_timeout, + p->queue); + + return 0; +} + +static int process_control(void *data) +{ + struct avctp_control_req *req = data; + struct avctp_pending_req *p = req->p; + int ret; + + ret = avctp_send(p->queue->chan, p->transaction, AVCTP_COMMAND, + req->code, req->subunit, req->op, req->operands, + req->operand_count); + if (ret < 0) + return ret; + + p->retry = !p->retry; + + p->timeout = g_timeout_add_seconds(CONTROL_TIMEOUT, req_timeout, + p->queue); + + return 0; +} + +static int process_browsing(void *data) +{ + struct avctp_browsing_req *req = data; + struct avctp_pending_req *p = req->p; + int ret; + + ret = avctp_browsing_send(p->queue, p->transaction, AVCTP_COMMAND, + req->operands, req->operand_count); + if (ret < 0) + return ret; + + p->timeout = g_timeout_add_seconds(BROWSING_TIMEOUT, req_timeout, + p->queue); + + return 0; +} + +static gboolean process_queue(void *user_data) +{ + struct avctp_queue *queue = user_data; + struct avctp_pending_req *p = queue->p; + + queue->process_id = 0; + + if (p != NULL) + return FALSE; + + while ((p = g_queue_pop_head(queue->queue))) { + + if (p->process(p->data) == 0) + break; + + pending_destroy(p, NULL); + } + + if (p == NULL) + return FALSE; + + queue->p = p; + + return FALSE; + +} + +static void control_response(struct avctp_channel *control, + struct avctp_header *avctp, + struct avc_header *avc, + uint8_t *operands, + size_t operand_count) +{ + struct avctp_pending_req *p; + struct avctp_control_req *req; + struct avctp_queue *queue; + GSList *l; + + if (avc->opcode == AVC_OP_PASSTHROUGH) + queue = g_slist_nth_data(control->queues, PASSTHROUGH_QUEUE); + else + queue = g_slist_nth_data(control->queues, CONTROL_QUEUE); + + p = queue->p; + + if (p && p->transaction == avctp->transaction) { + req = p->data; + if (req->op != avc->opcode) + goto done; + + control->processed = g_slist_prepend(control->processed, p); + + if (p->timeout > 0) { + g_source_remove(p->timeout); + p->timeout = 0; + } + + queue->p = NULL; + + if (queue->process_id == 0) + queue->process_id = g_idle_add(process_queue, queue); + } + +done: + for (l = control->processed; l; l = l->next) { + p = l->data; + req = p->data; + + if (p->transaction != avctp->transaction) + continue; + + if (req->op != avc->opcode) + continue; + + if (req->func && req->func(control->session, avc->code, + avc->subunit_type, p->transaction, + operands, operand_count, + req->user_data)) + return; + + control->processed = g_slist_remove(control->processed, p); + pending_destroy(p, NULL); + + return; + } +} + +static void browsing_response(struct avctp_channel *browsing, + struct avctp_header *avctp, + uint8_t *operands, + size_t operand_count) +{ + struct avctp_pending_req *p; + struct avctp_browsing_req *req; + struct avctp_queue *queue; + GSList *l; + + queue = g_slist_nth_data(browsing->queues, 0); + + p = queue->p; + + if (p && p->transaction == avctp->transaction) { + browsing->processed = g_slist_prepend(browsing->processed, p); + + if (p->timeout > 0) { + g_source_remove(p->timeout); + p->timeout = 0; + } + + queue->p = NULL; + + if (queue->process_id == 0) + queue->process_id = g_idle_add(process_queue, queue); + } + + for (l = browsing->processed; l; l = l->next) { + p = l->data; + req = p->data; + + if (p->transaction != avctp->transaction) + continue; + + if (req->func && req->func(browsing->session, operands, + operand_count, req->user_data)) + return; + + browsing->processed = g_slist_remove(browsing->processed, p); + pending_destroy(p, NULL); + + return; + } +} + +static gboolean session_browsing_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avctp *session = data; + struct avctp_channel *browsing = session->browsing; + uint8_t *buf = browsing->buffer; + uint8_t *operands; + struct avctp_header *avctp; + int sock, ret, packet_size, operand_count; + struct avctp_browsing_pdu_handler *handler; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) + goto failed; + + sock = g_io_channel_unix_get_fd(chan); + + ret = read(sock, buf, browsing->imtu); + if (ret <= 0) + goto failed; + + avctp = (struct avctp_header *) buf; + + if (avctp->packet_type != AVCTP_PACKET_SINGLE) + goto failed; + + operands = buf + AVCTP_HEADER_LENGTH; + ret -= AVCTP_HEADER_LENGTH; + operand_count = ret; + + if (avctp->cr == AVCTP_RESPONSE) { + browsing_response(browsing, avctp, operands, operand_count); + return TRUE; + } + + packet_size = AVCTP_HEADER_LENGTH; + avctp->cr = AVCTP_RESPONSE; + + handler = g_slist_nth_data(browsing->handlers, 0); + if (handler == NULL) { + DBG("handler not found"); + packet_size += avrcp_browsing_general_reject(operands); + goto send; + } + + packet_size += handler->cb(session, avctp->transaction, + operands, operand_count, + handler->user_data); + +send: + if (packet_size != 0) { + ret = write(sock, buf, packet_size); + if (ret != packet_size) + goto failed; + } + + return TRUE; + +failed: + DBG("AVCTP Browsing: disconnected"); + avctp_set_state(session, AVCTP_STATE_CONNECTED, 0); + + if (session->browsing) { + avctp_channel_destroy(session->browsing); + session->browsing = NULL; + } + + return FALSE; +} + +static gboolean session_cb(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + struct avctp *session = data; + struct avctp_channel *control = session->control; + uint8_t *buf = control->buffer; + uint8_t *operands, code, subunit; + struct avctp_header *avctp; + struct avc_header *avc; + int ret, packet_size, operand_count, sock; + struct avctp_pdu_handler *handler; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) + goto failed; + + sock = g_io_channel_unix_get_fd(chan); + + ret = read(sock, buf, control->imtu); + if (ret <= 0) + goto failed; + + if (ret < AVCTP_HEADER_LENGTH) { + error("Too small AVCTP packet"); + goto failed; + } + + avctp = (struct avctp_header *) buf; + + ret -= AVCTP_HEADER_LENGTH; + if (ret < AVC_HEADER_LENGTH) { + error("Too small AVC packet"); + goto failed; + } + + avc = (struct avc_header *) (buf + AVCTP_HEADER_LENGTH); + + ret -= AVC_HEADER_LENGTH; + + operands = (uint8_t *) avc + AVC_HEADER_LENGTH; + operand_count = ret; + + if (avctp->cr == AVCTP_RESPONSE) { + control_response(control, avctp, avc, operands, operand_count); + return TRUE; + } + + packet_size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH; + avctp->cr = AVCTP_RESPONSE; + + if (avctp->packet_type != AVCTP_PACKET_SINGLE) { + avc->code = AVC_CTYPE_NOT_IMPLEMENTED; + goto done; + } + + if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) { + avctp->ipid = 1; + packet_size = AVCTP_HEADER_LENGTH; + goto done; + } + + handler = find_handler(control->handlers, avc->opcode); + if (!handler) { + DBG("handler not found for 0x%02x", avc->opcode); + packet_size += avrcp_handle_vendor_reject(&code, operands); + avc->code = code; + goto done; + } + + code = avc->code; + subunit = avc->subunit_type; + + packet_size += handler->cb(session, avctp->transaction, &code, + &subunit, operands, operand_count, + handler->user_data); + + avc->code = code; + avc->subunit_type = subunit; + +done: + ret = write(sock, buf, packet_size); + if (ret != packet_size) + goto failed; + + return TRUE; + +failed: + DBG("AVCTP session %p got disconnected", session); + avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); + return FALSE; +} + +static int uinput_create(char *name) +{ + struct uinput_dev dev; + int fd, err, i; + + fd = open("/dev/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/input/uinput", O_RDWR); + if (fd < 0) { + fd = open("/dev/misc/uinput", O_RDWR); + if (fd < 0) { + err = -errno; + error("Can't open input device: %s (%d)", + strerror(-err), -err); + return err; + } + } + } + + memset(&dev, 0, sizeof(dev)); + if (name) + strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1); + + dev.id.bustype = BUS_BLUETOOTH; + dev.id.vendor = 0x0000; + dev.id.product = 0x0000; + dev.id.version = 0x0000; + + if (write(fd, &dev, sizeof(dev)) < 0) { + err = -errno; + error("Can't write device information: %s (%d)", + strerror(-err), -err); + close(fd); + 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; + } + + send_event(fd, EV_REP, REP_DELAY, 300); + + return fd; +} + +static void init_uinput(struct avctp *session) +{ + char address[18], name[248 + 1]; + + device_get_name(session->device, name, sizeof(name)); + if (g_str_equal(name, "Nokia CK-20W")) { + session->key_quirks[AVC_FORWARD] |= QUIRK_NO_RELEASE; + session->key_quirks[AVC_BACKWARD] |= QUIRK_NO_RELEASE; + session->key_quirks[AVC_PLAY] |= QUIRK_NO_RELEASE; + session->key_quirks[AVC_PAUSE] |= QUIRK_NO_RELEASE; + } + + ba2str(device_get_address(session->device), address); + session->uinput = uinput_create(address); + if (session->uinput < 0) + error("AVRCP: failed to init uinput for %s", address); + else + DBG("AVRCP: uinput initialized for %s", address); +} + +static struct avctp_queue *avctp_queue_create(struct avctp_channel *chan) +{ + struct avctp_queue *queue; + + queue = g_new0(struct avctp_queue, 1); + queue->chan = chan; + queue->queue = g_queue_new(); + + return queue; +} + +static struct avctp_channel *avctp_channel_create(struct avctp *session, + GIOChannel *io, + int queues, + GDestroyNotify destroy) +{ + struct avctp_channel *chan; + + chan = g_new0(struct avctp_channel, 1); + chan->session = session; + chan->io = g_io_channel_ref(io); + chan->destroy = destroy; + + while (queues--) { + struct avctp_queue *queue; + + queue = avctp_queue_create(chan); + chan->queues = g_slist_prepend(chan->queues, queue); + } + + return chan; +} + +static void handler_free(void *data) +{ + struct avctp_browsing_pdu_handler *handler = data; + + if (handler->destroy) + handler->destroy(handler->user_data); + + g_free(data); +} + +static void avctp_destroy_browsing(void *data) +{ + struct avctp_channel *chan = data; + + g_slist_free_full(chan->handlers, handler_free); + + chan->handlers = NULL; +} + +static void avctp_connect_browsing_cb(GIOChannel *chan, GError *err, + gpointer data) +{ + struct avctp *session = data; + struct avctp_channel *browsing = session->browsing; + struct avctp_queue *queue; + char address[18]; + uint16_t imtu, omtu; + GError *gerr = NULL; + + if (err) { + error("Browsing: %s", err->message); + goto fail; + } + + bt_io_get(chan, &gerr, + BT_IO_OPT_DEST, &address, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s", gerr->message); + g_io_channel_shutdown(chan, TRUE, NULL); + g_io_channel_unref(chan); + g_error_free(gerr); + goto fail; + } + + DBG("AVCTP Browsing: connected to %s", address); + + if (browsing == NULL) { + browsing = avctp_channel_create(session, chan, 1, + avctp_destroy_browsing); + session->browsing = browsing; + } + + browsing->imtu = imtu; + browsing->omtu = omtu; + browsing->buffer = g_malloc0(MAX(imtu, omtu)); + browsing->watch = g_io_add_watch(session->browsing->io, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) session_browsing_cb, session); + + avctp_set_state(session, AVCTP_STATE_BROWSING_CONNECTED, 0); + + /* Process any request that was pending the connection to complete */ + queue = g_slist_nth_data(browsing->queues, 0); + if (queue->process_id == 0 && !g_queue_is_empty(queue->queue)) + queue->process_id = g_idle_add(process_queue, queue); + + return; + +fail: + avctp_set_state(session, AVCTP_STATE_CONNECTED, 0); + + if (session->browsing) { + avctp_channel_destroy(session->browsing); + session->browsing = NULL; + } +} + +static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data) +{ + struct avctp *session = data; + char address[18]; + uint16_t imtu, omtu; + GError *gerr = NULL; + + if (err) { + avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); + error("%s", err->message); + return; + } + + bt_io_get(chan, &gerr, + BT_IO_OPT_DEST, &address, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_INVALID); + if (gerr) { + avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); + error("%s", gerr->message); + g_error_free(gerr); + return; + } + + DBG("AVCTP: connected to %s", address); + + if (session->control == NULL) + session->control = avctp_channel_create(session, chan, 2, NULL); + + session->control->imtu = imtu; + session->control->omtu = omtu; + session->control->buffer = g_malloc0(MAX(imtu, omtu)); + session->control->watch = g_io_add_watch(session->control->io, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) session_cb, session); + + session->passthrough_id = avctp_register_pdu_handler(session, + AVC_OP_PASSTHROUGH, + handle_panel_passthrough, + NULL); + session->unit_id = avctp_register_pdu_handler(session, + AVC_OP_UNITINFO, + handle_unit_info, + NULL); + session->subunit_id = avctp_register_pdu_handler(session, + AVC_OP_SUBUNITINFO, + handle_subunit_info, + NULL); + + init_uinput(session); + + avctp_set_state(session, AVCTP_STATE_CONNECTED, 0); +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct avctp *session = user_data; + GError *err = NULL; + + session->auth_id = 0; + + if (session->control->watch > 0) { + g_source_remove(session->control->watch); + session->control->watch = 0; + } + + if (derr && dbus_error_is_set(derr)) { + error("Access denied: %s", derr->message); + avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); + return; + } + + if (!bt_io_accept(session->control->io, avctp_connect_cb, session, + NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); + } +} + +static struct avctp_server *find_server(GSList *list, struct btd_adapter *a) +{ + for (; list; list = list->next) { + struct avctp_server *server = list->data; + + if (server->adapter == a) + return server; + } + + return NULL; +} + +static struct avctp *find_session(GSList *list, struct btd_device *device) +{ + for (; list != NULL; list = g_slist_next(list)) { + struct avctp *s = list->data; + + if (s->device == device) + return s; + } + + return NULL; +} + +static struct avctp *avctp_get_internal(struct btd_device *device) +{ + struct avctp_server *server; + struct avctp *session; + + server = find_server(servers, device_get_adapter(device)); + if (server == NULL) + return NULL; + + session = find_session(server->sessions, device); + if (session) + return session; + + session = g_new0(struct avctp, 1); + + session->server = server; + session->device = btd_device_ref(device); + session->state = AVCTP_STATE_DISCONNECTED; + session->uinput = -1; + + server->sessions = g_slist_append(server->sessions, session); + + return session; +} + +static void avctp_control_confirm(struct avctp *session, GIOChannel *chan, + struct btd_device *dev) +{ + const bdaddr_t *src; + const bdaddr_t *dst; + + if (session->control != NULL) { + error("Control: Refusing unexpected connect"); + g_io_channel_shutdown(chan, TRUE, NULL); + + /* + * Close AVCTP channel if remote tried connect + * at the same time + * AVRCP SPEC V1.5 4.1.1 Connection Establishment + */ + avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EAGAIN); + return; + } + + avctp_set_state(session, AVCTP_STATE_CONNECTING, 0); + session->control = avctp_channel_create(session, chan, 2, NULL); + + src = btd_adapter_get_address(device_get_adapter(dev)); + dst = device_get_address(dev); + + session->auth_id = btd_request_authorization(src, dst, + AVRCP_REMOTE_UUID, + auth_cb, session); + if (session->auth_id == 0) + goto drop; + + session->control->watch = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | + G_IO_NVAL, session_cb, session); + return; + +drop: + avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); +} + +static void avctp_browsing_confirm(struct avctp *session, GIOChannel *chan, + struct btd_device *dev) +{ + GError *err = NULL; + + if (session->control == NULL || session->browsing != NULL) { + error("Browsing: Refusing unexpected connect"); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + if (bt_io_accept(chan, avctp_connect_browsing_cb, session, NULL, + &err)) { + avctp_set_state(session, AVCTP_STATE_BROWSING_CONNECTING, 0); + session->browsing = avctp_channel_create(session, chan, 1, + avctp_destroy_browsing); + return; + } + + error("Browsing: %s", err->message); + g_error_free(err); + + return; +} + +static void avctp_confirm_cb(GIOChannel *chan, gpointer data) +{ + struct avctp *session; + char address[18]; + bdaddr_t src, dst; + GError *err = NULL; + uint16_t psm; + struct btd_device *device; + + bt_io_get(chan, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, address, + BT_IO_OPT_PSM, &psm, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + DBG("AVCTP: incoming connect from %s", address); + + device = btd_adapter_find_device(adapter_find(&src), &dst, + BDADDR_BREDR); + if (!device) + return; + + session = avctp_get_internal(device); + if (session == NULL) + return; + + if (btd_device_get_service(device, AVRCP_REMOTE_UUID) == NULL) + btd_device_add_uuid(device, AVRCP_REMOTE_UUID); + + if (btd_device_get_service(device, AVRCP_TARGET_UUID) == NULL) + btd_device_add_uuid(device, AVRCP_TARGET_UUID); + + switch (psm) { + case AVCTP_CONTROL_PSM: + avctp_control_confirm(session, chan, device); + break; + case AVCTP_BROWSING_PSM: + avctp_browsing_confirm(session, chan, device); + break; + } + + return; +} + +static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master, + uint8_t mode, uint16_t psm) +{ + GError *err = NULL; + GIOChannel *io; + + io = bt_io_listen(NULL, avctp_confirm_cb, NULL, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_MODE, mode, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + } + + return io; +} + +int avctp_register(struct btd_adapter *adapter, gboolean master) +{ + struct avctp_server *server; + const bdaddr_t *src = btd_adapter_get_address(adapter); + + server = g_new0(struct avctp_server, 1); + + server->control_io = avctp_server_socket(src, master, L2CAP_MODE_BASIC, + AVCTP_CONTROL_PSM); + if (!server->control_io) { + g_free(server); + return -1; + } + server->browsing_io = avctp_server_socket(src, master, L2CAP_MODE_ERTM, + AVCTP_BROWSING_PSM); + if (!server->browsing_io) { + if (server->control_io) { + g_io_channel_shutdown(server->control_io, TRUE, NULL); + g_io_channel_unref(server->control_io); + server->control_io = NULL; + } + g_free(server); + return -1; + } + + server->adapter = btd_adapter_ref(adapter); + + servers = g_slist_append(servers, server); + + return 0; +} + +void avctp_unregister(struct btd_adapter *adapter) +{ + struct avctp_server *server; + + server = find_server(servers, adapter); + if (!server) + return; + + while (server->sessions) + avctp_disconnected(server->sessions->data); + + servers = g_slist_remove(servers, server); + + g_io_channel_shutdown(server->browsing_io, TRUE, NULL); + g_io_channel_unref(server->browsing_io); + server->browsing_io = NULL; + + g_io_channel_shutdown(server->control_io, TRUE, NULL); + g_io_channel_unref(server->control_io); + btd_adapter_unref(server->adapter); + g_free(server); +} + +static struct avctp_pending_req *pending_create(struct avctp_queue *queue, + avctp_process_cb process, + void *data, + GDestroyNotify destroy) +{ + struct avctp_pending_req *p; + + p = g_new0(struct avctp_pending_req, 1); + p->queue = queue; + p->transaction = chan_get_transaction(queue->chan); + p->process = process; + p->data = data; + p->destroy = destroy; + + return p; +} + +static int avctp_send_req(struct avctp *session, uint8_t code, + uint8_t subunit, uint8_t opcode, + uint8_t *operands, size_t operand_count, + avctp_rsp_cb func, void *user_data) +{ + struct avctp_channel *control = session->control; + struct avctp_queue *queue; + struct avctp_pending_req *p; + struct avctp_control_req *req; + + if (control == NULL) + return -ENOTCONN; + + /* If the request set a callback send it directly */ + if (!func) + return avctp_send(session->control, -1, AVCTP_COMMAND, + code, subunit, opcode, operands, operand_count); + + req = g_new0(struct avctp_control_req, 1); + req->code = code; + req->subunit = subunit; + req->op = opcode; + req->func = func; + req->operands = g_memdup(operands, operand_count); + req->operand_count = operand_count; + req->user_data = user_data; + + if (opcode == AVC_OP_PASSTHROUGH) { + queue = g_slist_nth_data(control->queues, PASSTHROUGH_QUEUE); + p = pending_create(queue, process_passthrough, req, + control_req_destroy); + } else { + queue = g_slist_nth_data(control->queues, CONTROL_QUEUE); + p = pending_create(queue, process_control, req, + control_req_destroy); + } + + req->p = p; + + g_queue_push_tail(queue->queue, p); + + if (queue->process_id == 0) + queue->process_id = g_idle_add(process_queue, queue); + + return 0; +} + +int avctp_send_browsing_req(struct avctp *session, + uint8_t *operands, size_t operand_count, + avctp_browsing_rsp_cb func, void *user_data) +{ + struct avctp_channel *browsing = session->browsing; + struct avctp_queue *queue; + struct avctp_pending_req *p; + struct avctp_browsing_req *req; + + if (browsing == NULL) + return -ENOTCONN; + + req = g_new0(struct avctp_browsing_req, 1); + req->func = func; + req->operands = g_memdup(operands, operand_count); + req->operand_count = operand_count; + req->user_data = user_data; + + queue = g_slist_nth_data(browsing->queues, 0); + + p = pending_create(queue, process_browsing, req, browsing_req_destroy); + + req->p = p; + + g_queue_push_tail(queue->queue, p); + + /* Connection did not complete, delay process of the request */ + if (browsing->watch == 0) + return 0; + + if (queue->process_id == 0) + queue->process_id = g_idle_add(process_queue, queue); + + return 0; +} + +static const char *op2str(uint8_t op) +{ + int i; + + for (i = 0; key_map[i].name != NULL; i++) { + if ((op & 0x7F) == key_map[i].avc) + return key_map[i].name; + } + + return "UNKNOWN"; +} + +static int avctp_passthrough_press(struct avctp *session, uint8_t op) +{ + uint8_t operands[2]; + + DBG("%s", op2str(op)); + + /* Button pressed */ + operands[0] = op & 0x7f; + operands[1] = 0; + + return avctp_send_req(session, AVC_CTYPE_CONTROL, + AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH, + operands, sizeof(operands), + avctp_passthrough_rsp, NULL); +} + +static int avctp_passthrough_release(struct avctp *session, uint8_t op) +{ + uint8_t operands[2]; + + DBG("%s", op2str(op)); + + /* Button released */ + operands[0] = op | 0x80; + operands[1] = 0; + + return avctp_send_req(session, AVC_CTYPE_CONTROL, + AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH, + operands, sizeof(operands), + NULL, NULL); +} + +static gboolean repeat_timeout(gpointer user_data) +{ + struct avctp *session = user_data; + + avctp_passthrough_release(session, session->key.op); + avctp_passthrough_press(session, session->key.op); + + return TRUE; +} + +static void release_pressed(struct avctp *session) +{ + avctp_passthrough_release(session, session->key.op); + + 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) +{ + 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.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 transaction, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + if (code != AVC_CTYPE_ACCEPTED) + return FALSE; + + if (set_pressed(session, operands[0])) + return FALSE; + + avctp_passthrough_release(session, operands[0]); + + return FALSE; +} + +int avctp_send_passthrough(struct avctp *session, uint8_t op) +{ + /* Auto release if key pressed */ + if (session->key.timer > 0) + release_pressed(session); + + return avctp_passthrough_press(session, op); +} + +int avctp_send_vendordep(struct avctp *session, uint8_t transaction, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count) +{ + struct avctp_channel *control = session->control; + + if (control == NULL) + return -ENOTCONN; + + return avctp_send(control, transaction, AVCTP_RESPONSE, code, subunit, + AVC_OP_VENDORDEP, operands, operand_count); +} + +int avctp_send_vendordep_req(struct avctp *session, uint8_t code, + uint8_t subunit, uint8_t *operands, + size_t operand_count, + avctp_rsp_cb func, void *user_data) +{ + return avctp_send_req(session, code, subunit, AVC_OP_VENDORDEP, + operands, operand_count, + func, user_data); +} + +unsigned int avctp_add_state_cb(struct btd_device *dev, avctp_state_cb cb, + void *user_data) +{ + struct avctp_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct avctp_state_callback, 1); + state_cb->cb = cb; + state_cb->dev = dev; + state_cb->id = ++id; + state_cb->user_data = user_data; + + callbacks = g_slist_append(callbacks, state_cb); + + return state_cb->id; +} + +gboolean avctp_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = callbacks; l != NULL; l = l->next) { + struct avctp_state_callback *cb = l->data; + if (cb && cb->id == id) { + callbacks = g_slist_remove(callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} + +unsigned int avctp_register_passthrough_handler(struct avctp *session, + avctp_passthrough_cb cb, + void *user_data) +{ + struct avctp_channel *control = session->control; + struct avctp_passthrough_handler *handler; + static unsigned int id = 0; + + if (control == NULL || session->handler != NULL) + return 0; + + handler = g_new(struct avctp_passthrough_handler, 1); + handler->cb = cb; + handler->user_data = user_data; + handler->id = ++id; + + session->handler = handler; + + return handler->id; +} + +bool avctp_unregister_passthrough_handler(unsigned int id) +{ + GSList *l; + + for (l = servers; l; l = l->next) { + struct avctp_server *server = l->data; + GSList *s; + + for (s = server->sessions; s; s = s->next) { + struct avctp *session = s->data; + + if (session->handler == NULL) + continue; + + if (session->handler->id == id) { + g_free(session->handler); + session->handler = NULL; + return true; + } + } + } + + return false; +} + +unsigned int avctp_register_pdu_handler(struct avctp *session, uint8_t opcode, + avctp_control_pdu_cb cb, + void *user_data) +{ + struct avctp_channel *control = session->control; + struct avctp_pdu_handler *handler; + static unsigned int id = 0; + + if (control == NULL) + return 0; + + handler = find_handler(control->handlers, opcode); + if (handler) + return 0; + + handler = g_new(struct avctp_pdu_handler, 1); + handler->opcode = opcode; + handler->cb = cb; + handler->user_data = user_data; + handler->id = ++id; + + control->handlers = g_slist_append(control->handlers, handler); + + return handler->id; +} + +unsigned int avctp_register_browsing_pdu_handler(struct avctp *session, + avctp_browsing_pdu_cb cb, + void *user_data, + GDestroyNotify destroy) +{ + struct avctp_channel *browsing = session->browsing; + struct avctp_browsing_pdu_handler *handler; + static unsigned int id = 0; + + if (browsing == NULL) + return 0; + + if (browsing->handlers != NULL) + return 0; + + handler = g_new(struct avctp_browsing_pdu_handler, 1); + handler->cb = cb; + handler->user_data = user_data; + handler->id = ++id; + handler->destroy = destroy; + + browsing->handlers = g_slist_append(browsing->handlers, handler); + + return handler->id; +} + +gboolean avctp_unregister_pdu_handler(unsigned int id) +{ + GSList *l; + + for (l = servers; l; l = l->next) { + struct avctp_server *server = l->data; + GSList *s; + + for (s = server->sessions; s; s = s->next) { + struct avctp *session = s->data; + struct avctp_channel *control = session->control; + GSList *h; + + if (control == NULL) + continue; + + for (h = control->handlers; h; h = h->next) { + struct avctp_pdu_handler *handler = h->data; + + if (handler->id != id) + continue; + + control->handlers = g_slist_remove( + control->handlers, + handler); + g_free(handler); + return TRUE; + } + } + } + + return FALSE; +} + +gboolean avctp_unregister_browsing_pdu_handler(unsigned int id) +{ + GSList *l; + + for (l = servers; l; l = l->next) { + struct avctp_server *server = l->data; + GSList *s; + + for (s = server->sessions; s; s = s->next) { + struct avctp *session = s->data; + struct avctp_channel *browsing = session->browsing; + GSList *h; + + if (browsing == NULL) + continue; + + for (h = browsing->handlers; h; h = h->next) { + struct avctp_browsing_pdu_handler *handler = + h->data; + + if (handler->id != id) + continue; + + browsing->handlers = g_slist_remove( + browsing->handlers, + handler); + g_free(handler); + return TRUE; + } + } + } + + return FALSE; +} + +struct avctp *avctp_connect(struct btd_device *device) +{ + struct avctp *session; + GError *err = NULL; + GIOChannel *io; + const bdaddr_t *src; + + session = avctp_get_internal(device); + if (!session) + return NULL; + + if (session->state > AVCTP_STATE_DISCONNECTED) + return session; + + avctp_set_state(session, AVCTP_STATE_CONNECTING, 0); + + src = btd_adapter_get_address(session->server->adapter); + + io = bt_io_connect(avctp_connect_cb, session, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, + device_get_address(session->device), + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_PSM, AVCTP_CONTROL_PSM, + BT_IO_OPT_INVALID); + if (err) { + avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); + error("%s", err->message); + g_error_free(err); + return NULL; + } + + session->control = avctp_channel_create(session, io, 2, NULL); + session->initiator = true; + g_io_channel_unref(io); + + return session; +} + +int avctp_connect_browsing(struct avctp *session) +{ + const bdaddr_t *src; + GError *err = NULL; + GIOChannel *io; + + if (session->state != AVCTP_STATE_CONNECTED) + return -ENOTCONN; + + if (session->browsing != NULL) + return 0; + + avctp_set_state(session, AVCTP_STATE_BROWSING_CONNECTING, 0); + + src = btd_adapter_get_address(session->server->adapter); + + io = bt_io_connect(avctp_connect_browsing_cb, session, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, + device_get_address(session->device), + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_PSM, AVCTP_BROWSING_PSM, + BT_IO_OPT_MODE, L2CAP_MODE_ERTM, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + return -EIO; + } + + session->browsing = avctp_channel_create(session, io, 1, + avctp_destroy_browsing); + g_io_channel_unref(io); + + return 0; +} + +void avctp_disconnect(struct avctp *session) +{ + if (session->state == AVCTP_STATE_DISCONNECTED) + return; + + avctp_set_state(session, AVCTP_STATE_DISCONNECTED, -EIO); +} + +struct avctp *avctp_get(struct btd_device *device) +{ + return avctp_get_internal(device); +} + +bool avctp_is_initiator(struct avctp *session) +{ + return session->initiator; +} diff --git a/profiles/audio/avctp.h b/profiles/audio/avctp.h new file mode 100644 index 0000000..68a2735 --- /dev/null +++ b/profiles/audio/avctp.h @@ -0,0 +1,185 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define AVCTP_CONTROL_PSM 23 +#define AVCTP_BROWSING_PSM 27 + +#define AVC_MTU 512 +#define AVC_HEADER_LENGTH 3 + +/* ctype entries */ +#define AVC_CTYPE_CONTROL 0x0 +#define AVC_CTYPE_STATUS 0x1 +#define AVC_CTYPE_NOTIFY 0x3 +#define AVC_CTYPE_NOT_IMPLEMENTED 0x8 +#define AVC_CTYPE_ACCEPTED 0x9 +#define AVC_CTYPE_REJECTED 0xA +#define AVC_CTYPE_STABLE 0xC +#define AVC_CTYPE_CHANGED 0xD +#define AVC_CTYPE_INTERIM 0xF + +/* opcodes */ +#define AVC_OP_VENDORDEP 0x00 +#define AVC_OP_UNITINFO 0x30 +#define AVC_OP_SUBUNITINFO 0x31 +#define AVC_OP_PASSTHROUGH 0x7c + +/* subunits of interest */ +#define AVC_SUBUNIT_PANEL 0x09 + +/* operands in passthrough commands */ +#define AVC_SELECT 0x00 +#define AVC_UP 0x01 +#define AVC_DOWN 0x02 +#define AVC_LEFT 0x03 +#define AVC_RIGHT 0x04 +#define AVC_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 + +struct avctp; + +typedef enum { + AVCTP_STATE_DISCONNECTED = 0, + AVCTP_STATE_CONNECTING, + AVCTP_STATE_CONNECTED, + AVCTP_STATE_BROWSING_CONNECTING, + AVCTP_STATE_BROWSING_CONNECTED +} avctp_state_t; + +typedef void (*avctp_state_cb) (struct btd_device *dev, + avctp_state_t old_state, + avctp_state_t new_state, + int err, void *user_data); + +typedef bool (*avctp_passthrough_cb) (struct avctp *session, + uint8_t op, bool pressed, + void *user_data); +typedef size_t (*avctp_control_pdu_cb) (struct avctp *session, + uint8_t transaction, uint8_t *code, + uint8_t *subunit, uint8_t *operands, + size_t operand_count, void *user_data); +typedef gboolean (*avctp_rsp_cb) (struct avctp *session, uint8_t code, + uint8_t subunit, uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data); +typedef gboolean (*avctp_browsing_rsp_cb) (struct avctp *session, + uint8_t *operands, size_t operand_count, + void *user_data); +typedef size_t (*avctp_browsing_pdu_cb) (struct avctp *session, + uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data); + +unsigned int avctp_add_state_cb(struct btd_device *dev, avctp_state_cb cb, + void *user_data); +gboolean avctp_remove_state_cb(unsigned int id); + +int avctp_register(struct btd_adapter *adapter, gboolean master); +void avctp_unregister(struct btd_adapter *adapter); + +struct avctp *avctp_connect(struct btd_device *device); +struct avctp *avctp_get(struct btd_device *device); +bool avctp_is_initiator(struct avctp *session); +int avctp_connect_browsing(struct avctp *session); +void avctp_disconnect(struct avctp *session); + +unsigned int avctp_register_passthrough_handler(struct avctp *session, + avctp_passthrough_cb cb, + void *user_data); +bool avctp_unregister_passthrough_handler(unsigned int id); + +unsigned int avctp_register_pdu_handler(struct avctp *session, uint8_t opcode, + avctp_control_pdu_cb cb, + void *user_data); +gboolean avctp_unregister_pdu_handler(unsigned int id); + +unsigned int avctp_register_browsing_pdu_handler(struct avctp *session, + avctp_browsing_pdu_cb cb, + void *user_data, + GDestroyNotify destroy); +gboolean avctp_unregister_browsing_pdu_handler(unsigned int id); + +int avctp_send_passthrough(struct avctp *session, uint8_t op); +int avctp_send_vendordep(struct avctp *session, uint8_t transaction, + uint8_t code, uint8_t subunit, + uint8_t *operands, size_t operand_count); +int avctp_send_vendordep_req(struct avctp *session, uint8_t code, + uint8_t subunit, uint8_t *operands, + size_t operand_count, + avctp_rsp_cb func, void *user_data); +int avctp_send_browsing_req(struct avctp *session, + uint8_t *operands, size_t operand_count, + avctp_browsing_rsp_cb func, void *user_data); diff --git a/profiles/audio/avdtp.c b/profiles/audio/avdtp.c new file mode 100644 index 0000000..91b1e4b --- /dev/null +++ b/profiles/audio/avdtp.c @@ -0,0 +1,3764 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "btio/btio.h" +#include "src/log.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/adapter.h" +#include "src/device.h" + +#include "avdtp.h" +#include "sink.h" +#include "source.h" + +#define AVDTP_PSM 25 + +#define MAX_SEID 0x3E +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 SUSPEND_TIMEOUT 10 +#define ABORT_TIMEOUT 2 +#define DISCONNECT_TIMEOUT 1 +#define START_TIMEOUT 1 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avdtp_common_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; +} __attribute__ ((packed)); + +struct avdtp_single_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t signal_id:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct avdtp_start_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t no_of_packets; + uint8_t signal_id:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct avdtp_continue_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; +} __attribute__ ((packed)); + +struct seid_info { + uint8_t rfa0:1; + uint8_t inuse:1; + uint8_t seid:6; + uint8_t rfa2:3; + uint8_t type:1; + uint8_t media_type:4; +} __attribute__ ((packed)); + +struct seid { + uint8_t rfa0:2; + uint8_t seid:6; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avdtp_common_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; +} __attribute__ ((packed)); + +struct avdtp_single_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; + uint8_t rfa0:2; + uint8_t signal_id:6; +} __attribute__ ((packed)); + +struct avdtp_start_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; + uint8_t no_of_packets; + uint8_t rfa0:2; + uint8_t signal_id:6; +} __attribute__ ((packed)); + +struct avdtp_continue_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; +} __attribute__ ((packed)); + +struct seid_info { + uint8_t seid:6; + uint8_t inuse:1; + uint8_t rfa0:1; + uint8_t media_type:4; + uint8_t type:1; + uint8_t rfa2:3; +} __attribute__ ((packed)); + +struct seid { + uint8_t seid:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +/* packets */ + +struct discover_resp { + struct seid_info seps[0]; +} __attribute__ ((packed)); + +struct getcap_resp { + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct start_req { + 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; + gboolean delay_reporting; + GSList *caps; + struct avdtp_sep_ind *ind; + struct avdtp_sep_cfm *cfm; + void *user_data; +}; + +struct stream_callback { + avdtp_stream_state_cb cb; + void *user_data; + unsigned int id; +}; + +struct avdtp_state_callback { + avdtp_session_state_cb cb; + struct btd_device *dev; + unsigned int id; + void *user_data; +}; + +struct discover_callback { + unsigned int id; + avdtp_discover_cb_t cb; + void *user_data; +}; + +struct avdtp_stream { + GIOChannel *io; + uint16_t imtu; + uint16_t omtu; + struct avdtp *session; + struct avdtp_local_sep *lsep; + uint8_t rseid; + GSList *caps; + GSList *callbacks; + struct avdtp_service_capability *codec; + guint io_id; /* Transport GSource ID */ + 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; + + struct queue *lseps; + struct btd_device *device; + + avdtp_session_state_t state; + + GIOChannel *io; + guint io_id; + + GSList *seps; /* Elements of type struct avdtp_remote_sep * */ + + GSList *streams; /* Elements of type struct avdtp_stream * */ + + GSList *req_queue; /* Elements of type struct pending_req * */ + GSList *prio_queue; /* Same as req_queue but is processed before it */ + + struct avdtp_stream *pending_open; + + uint16_t imtu; + uint16_t omtu; + + struct in_buf in; + + char *buf; + + struct discover_callback *discover; + struct pending_req *req; + + guint dc_timer; + int dc_timeout; + + /* Attempt stream setup instead of disconnecting */ + gboolean stream_setup; +}; + +static GSList *state_callbacks = NULL; + +static int send_request(struct avdtp *session, gboolean priority, + struct avdtp_stream *stream, uint8_t signal_id, + void *buffer, size_t size); +static gboolean avdtp_parse_resp(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size); +static gboolean avdtp_parse_rej(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size); +static int process_queue(struct avdtp *session); +static void avdtp_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 ""; + } +} + +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 avdtp_set_state(struct avdtp *session, + avdtp_session_state_t new_state) +{ + GSList *l; + avdtp_session_state_t old_state = session->state; + + session->state = new_state; + + for (l = state_callbacks; l != NULL;) { + struct avdtp_state_callback *cb = l->data; + + l = g_slist_next(l); + + if (session->device != cb->dev) + continue; + + cb->cb(cb->dev, session, old_state, new_state, cb->user_data); + } +} + +static void stream_free(void *data) +{ + struct avdtp_stream *stream = data; + struct avdtp_remote_sep *rsep; + + stream->lsep->info.inuse = 0; + stream->lsep->stream = NULL; + + rsep = find_remote_sep(stream->session->seps, stream->rseid); + if (rsep) + rsep->stream = NULL; + + if (stream->timer) + g_source_remove(stream->timer); + + if (stream->io) + close_stream(stream); + + if (stream->io_id) + g_source_remove(stream->io_id); + + g_slist_free_full(stream->callbacks, g_free); + g_slist_free_full(stream->caps, g_free); + + g_free(stream); +} + +static gboolean transport_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avdtp_stream *stream = data; + struct avdtp_local_sep *sep = stream->lsep; + + if (stream->close_int && sep->cfm && sep->cfm->close) + sep->cfm->close(stream->session, sep, stream, NULL, + sep->user_data); + + if (!(cond & G_IO_NVAL)) + close_stream(stream); + + stream->io_id = 0; + + if (!stream->abort_int) + avdtp_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE); + + return FALSE; +} + +static int get_send_buffer_size(int sk) +{ + int size; + socklen_t optlen = sizeof(size); + + if (getsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, &optlen) < 0) { + int err = -errno; + error("getsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err), + -err); + return err; + } + + /* + * Doubled value is returned by getsockopt since kernel uses that + * space for its own purposes (see man 7 socket, bookkeeping overhead + * for SO_SNDBUF). + */ + return size / 2; +} + +static int set_send_buffer_size(int sk, int size) +{ + socklen_t optlen = sizeof(size); + + if (setsockopt(sk, SOL_SOCKET, SO_SNDBUF, &size, optlen) < 0) { + int err = -errno; + error("setsockopt(SO_SNDBUF) failed: %s (%d)", strerror(-err), + -err); + return err; + } + + return 0; +} + +static void handle_transport_connect(struct avdtp *session, GIOChannel *io, + uint16_t imtu, uint16_t omtu) +{ + struct avdtp_stream *stream = session->pending_open; + struct avdtp_local_sep *sep = stream->lsep; + int sk, buf_size, min_buf_size; + GError *err = NULL; + + session->pending_open = NULL; + + if (stream->timer) { + g_source_remove(stream->timer); + stream->timer = 0; + } + + if (io == NULL) { + if (!stream->open_acp && sep->cfm && sep->cfm->open) { + struct avdtp_error err; + avdtp_error_init(&err, AVDTP_ERRNO, EIO); + sep->cfm->open(session, sep, NULL, &err, + sep->user_data); + } + return; + } + + if (stream->io == NULL) + stream->io = g_io_channel_ref(io); + + stream->omtu = omtu; + stream->imtu = imtu; + + /* Apply special settings only if local SEP is of type SRC */ + if (sep->info.type != AVDTP_SEP_TYPE_SOURCE) + goto proceed; + + bt_io_set(stream->io, &err, BT_IO_OPT_FLUSHABLE, TRUE, + BT_IO_OPT_INVALID); + if (err != NULL) { + error("Enabling flushable packets failed: %s", err->message); + g_error_free(err); + } else + DBG("Flushable packets enabled"); + + sk = g_io_channel_unix_get_fd(stream->io); + buf_size = get_send_buffer_size(sk); + if (buf_size < 0) + goto proceed; + + DBG("sk %d, omtu %d, send buffer size %d", sk, omtu, buf_size); + min_buf_size = omtu * 2; + if (buf_size < min_buf_size) { + DBG("send buffer size to be increassed to %d", + min_buf_size); + set_send_buffer_size(sk, min_buf_size); + } + +proceed: + if (!stream->open_acp && sep->cfm && sep->cfm->open) + sep->cfm->open(session, sep, stream, NULL, sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); + + stream->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) transport_cb, stream); +} + +static int pending_req_cmp(gconstpointer a, gconstpointer b) +{ + const struct pending_req *req = a; + const struct avdtp_stream *stream = b; + + if (req->stream == stream) + return 0; + + return -1; +} + +static void cleanup_queue(struct avdtp *session, struct avdtp_stream *stream) +{ + GSList *l; + struct pending_req *req; + + while ((l = g_slist_find_custom(session->prio_queue, stream, + pending_req_cmp))) { + req = l->data; + pending_req_free(req); + session->prio_queue = g_slist_remove(session->prio_queue, req); + } + + while ((l = g_slist_find_custom(session->req_queue, stream, + pending_req_cmp))) { + req = l->data; + pending_req_free(req); + session->req_queue = g_slist_remove(session->req_queue, req); + } +} + +static void handle_unanswered_req(struct avdtp *session, + struct avdtp_stream *stream) +{ + struct pending_req *req; + struct avdtp_local_sep *lsep; + struct avdtp_error err; + + if (session->req->signal_id == AVDTP_ABORT) { + /* Avoid freeing the Abort request here */ + DBG("handle_unanswered_req: Abort req, returning"); + session->req->stream = NULL; + return; + } + + req = session->req; + session->req = NULL; + + avdtp_error_init(&err, AVDTP_ERRNO, EIO); + + lsep = stream->lsep; + + switch (req->signal_id) { + case AVDTP_RECONFIGURE: + error("No reply to Reconfigure request"); + if (lsep && lsep->cfm && lsep->cfm->reconfigure) + lsep->cfm->reconfigure(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_OPEN: + error("No reply to Open request"); + if (lsep && lsep->cfm && lsep->cfm->open) + lsep->cfm->open(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_START: + error("No reply to Start request"); + if (lsep && lsep->cfm && lsep->cfm->start) + lsep->cfm->start(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_SUSPEND: + error("No reply to Suspend request"); + if (lsep && lsep->cfm && lsep->cfm->suspend) + lsep->cfm->suspend(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_CLOSE: + error("No reply to Close request"); + if (lsep && lsep->cfm && lsep->cfm->close) + lsep->cfm->close(session, lsep, stream, &err, + lsep->user_data); + break; + case AVDTP_SET_CONFIGURATION: + error("No reply to SetConfiguration request"); + if (lsep && lsep->cfm && lsep->cfm->set_configuration) + lsep->cfm->set_configuration(session, lsep, stream, + &err, lsep->user_data); + } + + pending_req_free(req); +} + +static void avdtp_sep_set_state(struct avdtp *session, + struct avdtp_local_sep *sep, + avdtp_state_t state) +{ + struct avdtp_stream *stream = sep->stream; + avdtp_state_t old_state; + struct avdtp_error err, *err_ptr = NULL; + GSList *l; + + if (!stream) { + error("Error changing sep state: stream not available"); + return; + } + + if (sep->state == state) { + avdtp_error_init(&err, AVDTP_ERRNO, EIO); + DBG("stream state change failed: %s", avdtp_strerror(&err)); + err_ptr = &err; + } else { + err_ptr = NULL; + DBG("stream state changed: %s -> %s", + avdtp_statestr(sep->state), + avdtp_statestr(state)); + } + + old_state = sep->state; + sep->state = state; + + switch (state) { + case AVDTP_STATE_CONFIGURED: + if (sep->info.type == AVDTP_SEP_TYPE_SINK) + avdtp_delay_report(session, stream, stream->delay); + break; + case AVDTP_STATE_OPEN: + stream->starting = FALSE; + 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); + session->streams = g_slist_remove(session->streams, stream); + break; + default: + break; + } + + l = stream->callbacks; + while (l != NULL) { + struct stream_callback *cb = l->data; + l = g_slist_next(l); + cb->cb(stream, old_state, state, err_ptr, cb->user_data); + } + + if (state == AVDTP_STATE_IDLE) + stream_free(stream); +} + +static void finalize_discovery(struct avdtp *session, int err) +{ + struct discover_callback *discover = session->discover; + struct avdtp_error avdtp_err; + + if (!discover) + return; + + avdtp_error_init(&avdtp_err, AVDTP_ERRNO, err); + + if (discover->id > 0) + g_source_remove(discover->id); + + if (discover->cb) + discover->cb(session, session->seps, err ? &avdtp_err : NULL, + discover->user_data); + g_free(discover); + session->discover = NULL; +} + +static void release_stream(struct avdtp_stream *stream, struct avdtp *session) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->abort && + (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 remove_disconnect_timer(struct avdtp *session) +{ + g_source_remove(session->dc_timer); + session->dc_timer = 0; + session->stream_setup = FALSE; +} + +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->dc_timer) + remove_disconnect_timer(session); + + if (session->req) + pending_req_free(session->req); + + g_slist_free_full(session->req_queue, pending_req_free); + g_slist_free_full(session->prio_queue, pending_req_free); + g_slist_free_full(session->seps, sep_free); + + g_free(session->buf); + + btd_device_unref(session->device); + g_free(session); +} + +static void connection_lost(struct avdtp *session, int err) +{ + char address[18]; + + session = avdtp_ref(session); + + ba2str(device_get_address(session->device), address); + DBG("Disconnected from %s", address); + + g_slist_foreach(session->streams, (GFunc) release_stream, session); + session->streams = NULL; + + finalize_discovery(session, err); + + avdtp_set_state(session, AVDTP_SESSION_STATE_DISCONNECTED); + + avdtp_unref(session); +} + +static gboolean disconnect_timeout(gpointer user_data) +{ + struct avdtp *session = user_data; + struct btd_service *service; + gboolean stream_setup; + + session->dc_timer = 0; + + stream_setup = session->stream_setup; + session->stream_setup = FALSE; + + service = btd_device_get_service(session->device, A2DP_SINK_UUID); + if (service && stream_setup) { + sink_setup_stream(service, session); + return FALSE; + } + + service = btd_device_get_service(session->device, A2DP_SOURCE_UUID); + if (service && stream_setup) { + source_setup_stream(service, session); + return FALSE; + } + + connection_lost(session, ETIMEDOUT); + + return FALSE; +} + +static void set_disconnect_timer(struct avdtp *session) +{ + if (session->dc_timer) + remove_disconnect_timer(session); + + DBG("timeout %d", session->dc_timeout); + + if (!session->dc_timeout) + session->dc_timer = g_idle_add(disconnect_timeout, session); + else + session->dc_timer = g_timeout_add_seconds(session->dc_timeout, + disconnect_timeout, + session); +} + +void avdtp_unref(struct avdtp *session) +{ + if (!session) + return; + + session->ref--; + + DBG("%p: ref=%d", session, session->ref); + + if (session->ref > 0) + return; + + switch (session->state) { + case AVDTP_SESSION_STATE_CONNECTED: + set_disconnect_timer(session); + break; + case AVDTP_SESSION_STATE_CONNECTING: + connection_lost(session, ECONNABORTED); + break; + case AVDTP_SESSION_STATE_DISCONNECTED: + default: + avdtp_free(session); + break; + } +} + +struct avdtp *avdtp_ref(struct avdtp *session) +{ + session->ref++; + DBG("%p: ref=%d", session, session->ref); + if (session->dc_timer) + remove_disconnect_timer(session); + return session; +} + +static bool match_by_seid(const void *data, const void *user_data) +{ + const struct avdtp_local_sep *sep = data; + uint8_t seid = PTR_TO_UINT(user_data); + + return sep->info.seid == seid; +} + +static struct avdtp_local_sep *find_local_sep_by_seid(struct avdtp *session, + uint8_t seid) +{ + return queue_find(session->lseps, match_by_seid, INT_TO_PTR(seid)); +} + +struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session, + struct avdtp_local_sep *lsep) +{ + GSList *l; + + if (lsep->info.inuse) + return NULL; + + for (l = session->seps; l != NULL; l = g_slist_next(l)) { + struct avdtp_remote_sep *sep = l->data; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_data; + + /* Type must be different: source <-> sink */ + if (sep->type == lsep->info.type) + continue; + + if (sep->media_type != lsep->info.media_type) + continue; + + if (!sep->codec) + continue; + + cap = sep->codec; + codec_data = (void *) cap->data; + + if (codec_data->media_codec_type != lsep->codec) + continue; + + if (lsep->ind && lsep->ind->match_codec) + if (!lsep->ind->match_codec(session, codec_data, + lsep->user_data)) + continue; + + if (sep->stream == NULL) + return sep; + } + + return NULL; +} + +static GSList *caps_to_list(uint8_t *data, 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, get_all, &caps, + &err, sep->user_data)) + goto failed; + + for (l = caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (rsp_size + cap->length + 2 > sizeof(buf)) + break; + + memcpy(ptr, cap, cap->length + 2); + rsp_size += cap->length + 2; + ptr += cap->length + 2; + + g_free(cap); + } + + g_slist_free(caps); + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, cmd, + buf, rsp_size); + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, cmd, + &err, sizeof(err)); +} + +static void setconf_cb(struct avdtp *session, struct avdtp_stream *stream, + struct avdtp_error *err) +{ + struct conf_rej rej; + struct avdtp_local_sep *sep; + + if (err != NULL) { + rej.error = AVDTP_UNSUPPORTED_CONFIGURATION; + rej.category = err->err.error_code; + avdtp_send(session, session->in.transaction, + AVDTP_MSG_TYPE_REJECT, AVDTP_SET_CONFIGURATION, + &rej, sizeof(rej)); + stream_free(stream); + return; + } + + if (!avdtp_send(session, session->in.transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_SET_CONFIGURATION, NULL, 0)) { + stream_free(stream); + return; + } + + sep = stream->lsep; + sep->stream = stream; + sep->info.inuse = 1; + session->streams = g_slist_append(session->streams, stream); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); +} + +static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction, + struct setconf_req *req, unsigned int size) +{ + struct conf_rej rej; + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + uint8_t err, category = 0x00; + struct btd_service *service; + 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; + } + + switch (sep->info.type) { + case AVDTP_SEP_TYPE_SOURCE: + service = btd_device_get_service(session->device, + A2DP_SINK_UUID); + if (service == NULL) { + btd_device_add_uuid(session->device, A2DP_SINK_UUID); + service = btd_device_get_service(session->device, + A2DP_SINK_UUID); + if (service == NULL) { + error("Unable to get a audio sink object"); + err = AVDTP_BAD_STATE; + goto failed; + } + } + break; + case AVDTP_SEP_TYPE_SINK: + service = btd_device_get_service(session->device, + A2DP_SOURCE_UUID); + if (service == NULL) { + btd_device_add_uuid(session->device, A2DP_SOURCE_UUID); + service = btd_device_get_service(session->device, + A2DP_SOURCE_UUID); + if (service == NULL) { + error("Unable to get a audio source object"); + err = AVDTP_BAD_STATE; + goto failed; + } + } + break; + } + + stream = 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 != NULL; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (rsp_size + cap->length + 2 > (int) sizeof(buf)) + break; + + memcpy(ptr, cap, cap->length + 2); + rsp_size += cap->length + 2; + ptr += cap->length + 2; + } + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_GET_CONFIGURATION, buf, rsp_size); + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_GET_CONFIGURATION, &err, sizeof(err)); +} + +static gboolean avdtp_reconf_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, int size) +{ + struct conf_rej rej; + + rej.error = AVDTP_NOT_SUPPORTED_COMMAND; + rej.category = 0x00; + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_RECONFIGURE, &rej, sizeof(rej)); +} + +static void check_seid_collision(struct pending_req *req, uint8_t id) +{ + struct seid_req *seid = req->data; + + if (seid->acp_seid == id) + req->collided = TRUE; +} + +static void check_start_collision(struct pending_req *req, uint8_t id) +{ + struct start_req *start = req->data; + 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); + + session->dc_timeout = DISCONNECT_TIMEOUT; + + 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); + session->dc_timeout = DISCONNECT_TIMEOUT; + } + + return ret; +} + +static gboolean avdtp_secctl_cmd(struct avdtp *session, uint8_t transaction, + struct seid_req *req, int size) +{ + return avdtp_unknown_cmd(session, transaction, AVDTP_SECURITY_CONTROL); +} + +static gboolean avdtp_delayreport_cmd(struct avdtp *session, + uint8_t transaction, + struct delay_req *req, + unsigned int size) +{ + struct avdtp_local_sep *sep; + struct avdtp_stream *stream; + uint8_t err; + + if (size < sizeof(struct delay_req)) { + error("Too short delay report request"); + return FALSE; + } + + sep = find_local_sep_by_seid(session, req->acp_seid); + if (!sep || !sep->stream) { + err = AVDTP_BAD_ACP_SEID; + goto failed; + } + + stream = sep->stream; + + if (sep->state != AVDTP_STATE_CONFIGURED && + sep->state != AVDTP_STATE_STREAMING) { + err = AVDTP_BAD_STATE; + goto failed; + } + + stream->delay = ntohs(req->delay); + + if (sep->ind && sep->ind->delayreport) { + if (!sep->ind->delayreport(session, sep, stream->rseid, + stream->delay, &err, + sep->user_data)) + goto failed; + } + + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, + AVDTP_DELAY_REPORT, NULL, 0); + +failed: + return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, + AVDTP_DELAY_REPORT, &err, sizeof(err)); +} + +static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction, + uint8_t signal_id, void *buf, int size) +{ + switch (signal_id) { + case AVDTP_DISCOVER: + DBG("Received DISCOVER_CMD"); + return avdtp_discover_cmd(session, transaction, buf, size); + case AVDTP_GET_CAPABILITIES: + DBG("Received GET_CAPABILITIES_CMD"); + return avdtp_getcap_cmd(session, transaction, buf, size, + FALSE); + case AVDTP_GET_ALL_CAPABILITIES: + DBG("Received GET_ALL_CAPABILITIES_CMD"); + return avdtp_getcap_cmd(session, transaction, buf, size, TRUE); + case AVDTP_SET_CONFIGURATION: + DBG("Received SET_CONFIGURATION_CMD"); + return avdtp_setconf_cmd(session, transaction, buf, size); + case AVDTP_GET_CONFIGURATION: + DBG("Received GET_CONFIGURATION_CMD"); + return avdtp_getconf_cmd(session, transaction, buf, size); + case AVDTP_RECONFIGURE: + DBG("Received RECONFIGURE_CMD"); + return avdtp_reconf_cmd(session, transaction, buf, size); + case AVDTP_OPEN: + DBG("Received OPEN_CMD"); + return avdtp_open_cmd(session, transaction, buf, size); + case AVDTP_START: + DBG("Received START_CMD"); + return avdtp_start_cmd(session, transaction, buf, size); + case AVDTP_CLOSE: + DBG("Received CLOSE_CMD"); + return avdtp_close_cmd(session, transaction, buf, size); + case AVDTP_SUSPEND: + DBG("Received SUSPEND_CMD"); + return avdtp_suspend_cmd(session, transaction, buf, size); + case AVDTP_ABORT: + DBG("Received ABORT_CMD"); + return avdtp_abort_cmd(session, transaction, buf, size); + case AVDTP_SECURITY_CONTROL: + DBG("Received SECURITY_CONTROL_CMD"); + return avdtp_secctl_cmd(session, transaction, buf, size); + case AVDTP_DELAY_REPORT: + DBG("Received DELAY_REPORT_CMD"); + return avdtp_delayreport_cmd(session, transaction, buf, size); + default: + DBG("Received unknown request id %u", signal_id); + return avdtp_unknown_cmd(session, transaction, signal_id); + } +} + +enum avdtp_parse_result { PARSE_ERROR, PARSE_FRAGMENT, PARSE_SUCCESS }; + +static enum avdtp_parse_result avdtp_parse_data(struct avdtp *session, + void *buf, size_t size) +{ + struct avdtp_common_header *header = buf; + struct avdtp_single_header *single = (void *) session->buf; + struct avdtp_start_header *start = (void *) session->buf; + void *payload; + gsize payload_size; + + switch (header->packet_type) { + case AVDTP_PKT_TYPE_SINGLE: + if (size < sizeof(*single)) { + error("Received too small single packet (%zu bytes)", size); + return PARSE_ERROR; + } + if (session->in.active) { + error("SINGLE: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + + payload = session->buf + sizeof(*single); + payload_size = size - sizeof(*single); + + session->in.active = TRUE; + session->in.data_size = 0; + session->in.no_of_packets = 1; + session->in.transaction = header->transaction; + session->in.message_type = header->message_type; + session->in.signal_id = single->signal_id; + + break; + case AVDTP_PKT_TYPE_START: + if (size < sizeof(*start)) { + error("Received too small start packet (%zu bytes)", size); + return PARSE_ERROR; + } + if (session->in.active) { + error("START: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + + session->in.active = TRUE; + session->in.data_size = 0; + session->in.transaction = header->transaction; + session->in.message_type = header->message_type; + session->in.no_of_packets = start->no_of_packets; + session->in.signal_id = start->signal_id; + + payload = session->buf + sizeof(*start); + payload_size = size - sizeof(*start); + + break; + case AVDTP_PKT_TYPE_CONTINUE: + if (size < sizeof(struct avdtp_continue_header)) { + error("Received too small continue packet (%zu bytes)", + size); + return PARSE_ERROR; + } + if (!session->in.active) { + error("CONTINUE: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + if (session->in.transaction != header->transaction) { + error("Continue transaction id doesn't match"); + return PARSE_ERROR; + } + if (session->in.no_of_packets <= 1) { + error("Too few continue packets"); + return PARSE_ERROR; + } + + payload = session->buf + sizeof(struct avdtp_continue_header); + payload_size = size - sizeof(struct avdtp_continue_header); + + break; + case AVDTP_PKT_TYPE_END: + if (size < sizeof(struct avdtp_continue_header)) { + error("Received too small end packet (%zu bytes)", size); + return PARSE_ERROR; + } + if (!session->in.active) { + error("END: Invalid AVDTP packet fragmentation"); + return PARSE_ERROR; + } + if (session->in.transaction != header->transaction) { + error("End transaction id doesn't match"); + return PARSE_ERROR; + } + if (session->in.no_of_packets > 1) { + error("Got an end packet too early"); + return PARSE_ERROR; + } + + payload = session->buf + sizeof(struct avdtp_continue_header); + payload_size = size - sizeof(struct avdtp_continue_header); + + break; + default: + error("Invalid AVDTP packet type 0x%02X", header->packet_type); + return PARSE_ERROR; + } + + if (session->in.data_size + payload_size > + sizeof(session->in.buf)) { + error("Not enough incoming buffer space!"); + return PARSE_ERROR; + } + + memcpy(session->in.buf + session->in.data_size, payload, payload_size); + session->in.data_size += payload_size; + + if (session->in.no_of_packets > 1) { + session->in.no_of_packets--; + DBG("Received AVDTP fragment. %d to go", + session->in.no_of_packets); + return PARSE_FRAGMENT; + } + + session->in.active = FALSE; + + return PARSE_SUCCESS; +} + +static gboolean session_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct avdtp *session = data; + struct avdtp_common_header *header; + ssize_t size; + int fd; + + DBG(""); + + if (cond & G_IO_NVAL) + return FALSE; + + header = (void *) session->buf; + + if (cond & (G_IO_HUP | G_IO_ERR)) + goto failed; + + fd = g_io_channel_unix_get_fd(chan); + size = read(fd, session->buf, session->imtu); + if (size < 0) { + error("IO Channel read error"); + goto failed; + } + + if ((size_t) size < sizeof(struct avdtp_common_header)) { + error("Received too small packet (%zu bytes)", size); + goto failed; + } + + switch (avdtp_parse_data(session, session->buf, size)) { + case PARSE_ERROR: + goto failed; + case PARSE_FRAGMENT: + return TRUE; + case PARSE_SUCCESS: + break; + } + + if (session->in.message_type == AVDTP_MSG_TYPE_COMMAND) { + if (!avdtp_parse_cmd(session, session->in.transaction, + session->in.signal_id, + session->in.buf, + session->in.data_size)) { + error("Unable to handle command. Disconnecting"); + goto failed; + } + + if (session->req && session->req->collided) { + DBG("Collision detected"); + goto next; + } + + return TRUE; + } + + if (session->req == NULL) { + error("No pending request, ignoring message"); + return TRUE; + } + + if (header->transaction != session->req->transaction) { + error("Transaction label doesn't match"); + return TRUE; + } + + if (session->in.signal_id != session->req->signal_id) { + error("Response signal doesn't match"); + return TRUE; + } + + g_source_remove(session->req->timeout); + session->req->timeout = 0; + + switch (header->message_type) { + case AVDTP_MSG_TYPE_ACCEPT: + if (!avdtp_parse_resp(session, session->req->stream, + session->in.transaction, + session->in.signal_id, + session->in.buf, + session->in.data_size)) { + error("Unable to parse accept response"); + goto failed; + } + break; + case AVDTP_MSG_TYPE_REJECT: + if (!avdtp_parse_rej(session, session->req->stream, + session->in.transaction, + session->in.signal_id, + session->in.buf, + session->in.data_size)) { + error("Unable to parse reject response"); + goto failed; + } + break; + case AVDTP_MSG_TYPE_GEN_REJECT: + error("Received a General Reject message"); + break; + default: + error("Unknown message type 0x%02X", header->message_type); + break; + } + +next: + pending_req_free(session->req); + session->req = NULL; + + process_queue(session); + + return TRUE; + +failed: + connection_lost(session, EIO); + + return FALSE; +} + +static uint16_t get_version(struct avdtp *session) +{ + const sdp_record_t *rec; + sdp_list_t *protos; + sdp_data_t *proto_desc; + uint16_t ver = 0x0100; + + rec = btd_device_get_record(session->device, A2DP_SINK_UUID); + if (!rec) + rec = btd_device_get_record(session->device, A2DP_SOURCE_UUID); + + if (!rec) + return ver; + + if (sdp_get_access_protos(rec, &protos) < 0) + return ver; + + proto_desc = sdp_get_proto_desc(protos, AVDTP_UUID); + if (proto_desc && proto_desc->dtd == SDP_UINT16) + ver = proto_desc->val.uint16; + + sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL); + sdp_list_free(protos, NULL); + + return ver; +} + +static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct avdtp *session = user_data; + char address[18]; + int err_no = EIO; + + if (err) { + err_no = err->code; + error("%s", err->message); + goto failed; + } + + if (!session->io) + session->io = g_io_channel_ref(chan); + + bt_io_get(chan, &err, + BT_IO_OPT_OMTU, &session->omtu, + BT_IO_OPT_IMTU, &session->imtu, + BT_IO_OPT_INVALID); + if (err) { + err_no = err->code; + error("%s", err->message); + goto failed; + } + + ba2str(device_get_address(session->device), address); + DBG("AVDTP: connected %s channel to %s", + session->pending_open ? "transport" : "signaling", + address); + + if (session->state == AVDTP_SESSION_STATE_CONNECTING) { + DBG("AVDTP imtu=%u, omtu=%u", session->imtu, session->omtu); + + session->buf = g_malloc0(MAX(session->imtu, session->omtu)); + avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTED); + + if (session->io_id) + g_source_remove(session->io_id); + + /* This watch should be low priority since otherwise the + * connect callback might be dispatched before the session + * callback if the kernel wakes us up at the same time for + * them. This could happen if a headset is very quick in + * sending the Start command after connecting the stream + * transport channel. + */ + session->io_id = g_io_add_watch_full(chan, + G_PRIORITY_LOW, + G_IO_IN | G_IO_ERR | G_IO_HUP + | G_IO_NVAL, + (GIOFunc) session_cb, session, + NULL); + + if (session->stream_setup) + set_disconnect_timer(session); + } else if (session->pending_open) + handle_transport_connect(session, chan, session->imtu, + session->omtu); + else + goto failed; + + process_queue(session); + + return; + +failed: + if (session->pending_open) { + struct avdtp_stream *stream = session->pending_open; + + handle_transport_connect(session, NULL, 0, 0); + + if (avdtp_abort(session, stream) < 0) + avdtp_sep_set_state(session, stream->lsep, + AVDTP_STATE_IDLE); + } else + connection_lost(session, err_no); +} + +struct avdtp *avdtp_new(GIOChannel *chan, struct btd_device *device, + struct queue *lseps) +{ + struct avdtp *session; + + session = g_new0(struct avdtp, 1); + + session->device = btd_device_ref(device); + /* We don't use avdtp_set_state() here since this isn't a state change + * but just setting of the initial state */ + session->state = AVDTP_SESSION_STATE_DISCONNECTED; + session->lseps = lseps; + + session->version = get_version(session); + + if (!chan) + return session; + + avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING); + + btd_device_add_uuid(device, ADVANCED_AUDIO_UUID); + + session->io = g_io_channel_ref(chan); + session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) session_cb, session); + + /* This is so that avdtp_connect_cb will know to do the right thing + * with respect to the disconnect timer */ + session->stream_setup = TRUE; + + session->dc_timeout = DISCONNECT_TIMEOUT; + + avdtp_connect_cb(chan, NULL, session); + + return session; +} + +static GIOChannel *l2cap_connect(struct avdtp *session) +{ + GError *err = NULL; + GIOChannel *io; + const bdaddr_t *src; + + src = btd_adapter_get_address(device_get_adapter(session->device)); + + io = bt_io_connect(avdtp_connect_cb, session, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, + device_get_address(session->device), + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + return NULL; + } + + return io; +} + +static void queue_request(struct avdtp *session, struct pending_req *req, + gboolean priority) +{ + if (priority) + session->prio_queue = g_slist_append(session->prio_queue, req); + else + session->req_queue = g_slist_append(session->req_queue, req); +} + +static uint8_t req_get_seid(struct pending_req *req) +{ + if (req->signal_id == AVDTP_DISCOVER) + return 0; + + return ((struct seid_req *) (req->data))->acp_seid; +} + +static int cancel_request(struct avdtp *session, int err) +{ + struct pending_req *req; + struct seid_req sreq; + struct avdtp_local_sep *lsep; + struct avdtp_stream *stream; + uint8_t seid; + struct avdtp_error averr; + + req = session->req; + session->req = NULL; + + avdtp_error_init(&averr, AVDTP_ERRNO, err); + + seid = req_get_seid(req); + if (seid) + stream = find_stream_by_rseid(session, seid); + else + stream = NULL; + + if (stream) + lsep = stream->lsep; + else + lsep = NULL; + + switch (req->signal_id) { + case AVDTP_RECONFIGURE: + error("Reconfigure: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->reconfigure) + lsep->cfm->reconfigure(session, lsep, stream, &averr, + lsep->user_data); + break; + case AVDTP_OPEN: + error("Open: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->open) + lsep->cfm->open(session, lsep, stream, &averr, + lsep->user_data); + break; + case AVDTP_START: + error("Start: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->start) { + lsep->cfm->start(session, lsep, stream, &averr, + lsep->user_data); + if (stream) + stream->starting = FALSE; + } + break; + case AVDTP_SUSPEND: + error("Suspend: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->suspend) + lsep->cfm->suspend(session, lsep, stream, &averr, + lsep->user_data); + break; + case AVDTP_CLOSE: + error("Close: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->close) { + lsep->cfm->close(session, lsep, stream, &averr, + lsep->user_data); + if (stream) + stream->close_int = FALSE; + } + break; + case AVDTP_SET_CONFIGURATION: + error("SetConfiguration: %s (%d)", strerror(err), err); + if (lsep && lsep->cfm && lsep->cfm->set_configuration) + lsep->cfm->set_configuration(session, lsep, stream, + &averr, lsep->user_data); + break; + case AVDTP_DISCOVER: + error("Discover: %s (%d)", strerror(err), err); + goto failed; + case AVDTP_GET_CAPABILITIES: + error("GetCapabilities: %s (%d)", strerror(err), err); + goto failed; + case AVDTP_ABORT: + error("Abort: %s (%d)", strerror(err), err); + goto failed; + } + + if (!stream) + goto failed; + + memset(&sreq, 0, sizeof(sreq)); + sreq.acp_seid = seid; + + err = send_request(session, TRUE, stream, AVDTP_ABORT, &sreq, + sizeof(sreq)); + if (err < 0) { + error("Unable to send abort request"); + goto failed; + } + + stream->abort_int = TRUE; + + goto done; + +failed: + connection_lost(session, err); +done: + pending_req_free(req); + return err; +} + +static 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, timeout; + + if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) { + session->io = l2cap_connect(session); + if (!session->io) { + err = -EIO; + goto failed; + } + avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING); + } + + if (session->state < AVDTP_SESSION_STATE_CONNECTED || + session->req != NULL) { + queue_request(session, req, priority); + return 0; + } + + req->transaction = transaction++; + transaction %= 16; + + /* FIXME: Should we retry to send if the buffer + was not totally sent or in case of EINTR? */ + if (!avdtp_send(session, req->transaction, AVDTP_MSG_TYPE_COMMAND, + req->signal_id, req->data, req->data_size)) { + err = -EIO; + goto failed; + } + + session->req = req; + + switch (req->signal_id) { + case AVDTP_ABORT: + timeout = ABORT_TIMEOUT; + break; + case AVDTP_SUSPEND: + timeout = SUSPEND_TIMEOUT; + break; + default: + timeout = REQ_TIMEOUT; + } + + req->timeout = g_timeout_add_seconds(timeout, request_timeout, session); + return 0; + +failed: + g_free(req->data); + g_free(req); + return err; +} + +static int send_request(struct avdtp *session, gboolean priority, + struct avdtp_stream *stream, uint8_t signal_id, + void *buffer, size_t size) +{ + struct pending_req *req; + + if (stream && stream->abort_int && signal_id != AVDTP_ABORT) { + DBG("Unable to send requests while aborting"); + return -EINVAL; + } + + req = g_new0(struct pending_req, 1); + req->signal_id = signal_id; + req->data = g_malloc(size); + memcpy(req->data, buffer, size); + req->data_size = size; + req->stream = stream; + + return send_req(session, priority, req); +} + +static gboolean avdtp_discover_resp(struct avdtp *session, + struct discover_resp *resp, int size) +{ + int sep_count, i; + uint8_t getcap_cmd; + int ret = 0; + gboolean getcap_pending = FALSE; + + if (session->version >= 0x0103) + getcap_cmd = AVDTP_GET_ALL_CAPABILITIES; + else + getcap_cmd = AVDTP_GET_CAPABILITIES; + + sep_count = size / sizeof(struct seid_info); + + for (i = 0; i < sep_count; i++) { + struct avdtp_remote_sep *sep; + struct avdtp_stream *stream; + struct seid_req req; + + DBG("seid %d type %d media %d in use %d", + resp->seps[i].seid, resp->seps[i].type, + resp->seps[i].media_type, resp->seps[i].inuse); + + stream = find_stream_by_rseid(session, resp->seps[i].seid); + + sep = find_remote_sep(session->seps, resp->seps[i].seid); + if (sep && sep->type == resp->seps[i].type && + sep->media_type == resp->seps[i].media_type) + continue; + + if (resp->seps[i].inuse && !stream) + continue; + + sep = g_new0(struct avdtp_remote_sep, 1); + session->seps = g_slist_append(session->seps, sep); + + sep->stream = stream; + sep->seid = resp->seps[i].seid; + sep->type = resp->seps[i].type; + sep->media_type = resp->seps[i].media_type; + + memset(&req, 0, sizeof(req)); + req.acp_seid = sep->seid; + + ret = send_request(session, TRUE, NULL, getcap_cmd, + &req, sizeof(req)); + if (ret < 0) + break; + getcap_pending = TRUE; + } + + if (!getcap_pending) + finalize_discovery(session, -ret); + + return TRUE; +} + +static gboolean avdtp_get_capabilities_resp(struct avdtp *session, + struct getcap_resp *resp, + unsigned int size) +{ + struct avdtp_remote_sep *sep; + uint8_t seid; + + /* Check for minimum required packet size includes: + * 1. getcap resp header + * 2. media transport capability (2 bytes) + * 3. media codec capability type + length (2 bytes) + * 4. the actual media codec elements + * */ + if (size < (sizeof(struct getcap_resp) + 4 + + sizeof(struct avdtp_media_codec_capability))) { + error("Too short getcap resp packet"); + return FALSE; + } + + seid = ((struct seid_req *) session->req->data)->acp_seid; + + sep = find_remote_sep(session->seps, seid); + + DBG("seid %d type %d media %d", sep->seid, + sep->type, sep->media_type); + + if (sep->caps) { + g_slist_free_full(sep->caps, g_free); + sep->caps = NULL; + sep->codec = NULL; + sep->delay_reporting = FALSE; + } + + sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp), + &sep->codec, &sep->delay_reporting); + + return TRUE; +} + +static gboolean avdtp_set_configuration_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_single_header *resp, + int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->set_configuration) + sep->cfm->set_configuration(session, sep, stream, NULL, + sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED); + + return TRUE; +} + +static gboolean avdtp_reconfigure_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_single_header *resp, int size) +{ + return TRUE; +} + +static gboolean avdtp_open_resp(struct avdtp *session, struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + stream->io = l2cap_connect(session); + if (!stream->io) { + avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); + return FALSE; + } + + session->pending_open = stream; + + return TRUE; +} + +static gboolean avdtp_start_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->start) + sep->cfm->start(session, sep, stream, NULL, sep->user_data); + + /* We might be in STREAMING already if both sides send START_CMD at the + * same time and the one in SNK role doesn't reject it as it should */ + if (sep->state != AVDTP_STATE_STREAMING) + avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING); + + return TRUE; +} + +static gboolean avdtp_close_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING); + + close_stream(stream); + + return TRUE; +} + +static gboolean avdtp_suspend_resp(struct avdtp *session, + struct avdtp_stream *stream, + void *data, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN); + + if (sep->cfm && sep->cfm->suspend) + sep->cfm->suspend(session, sep, stream, NULL, sep->user_data); + + return TRUE; +} + +static gboolean avdtp_abort_resp(struct avdtp *session, + struct avdtp_stream *stream, + struct seid_rej *resp, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING); + + if (sep->cfm && sep->cfm->abort) + sep->cfm->abort(session, sep, stream, NULL, sep->user_data); + + avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE); + + return TRUE; +} + +static gboolean avdtp_delay_report_resp(struct avdtp *session, + struct avdtp_stream *stream, + void *data, int size) +{ + struct avdtp_local_sep *sep = stream->lsep; + + if (sep->cfm && sep->cfm->delay_report) + sep->cfm->delay_report(session, sep, stream, NULL, sep->user_data); + + return TRUE; +} + +static gboolean avdtp_parse_resp(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size) +{ + struct pending_req *next; + const char *get_all = ""; + + if (session->prio_queue) + next = session->prio_queue->data; + else if (session->req_queue) + next = session->req_queue->data; + else + next = NULL; + + switch (signal_id) { + case AVDTP_DISCOVER: + DBG("DISCOVER request succeeded"); + return avdtp_discover_resp(session, buf, size); + case AVDTP_GET_ALL_CAPABILITIES: + get_all = "ALL_"; + /* fall through */ + case AVDTP_GET_CAPABILITIES: + DBG("GET_%sCAPABILITIES request succeeded", get_all); + if (!avdtp_get_capabilities_resp(session, buf, size)) + return FALSE; + if (!(next && (next->signal_id == AVDTP_GET_CAPABILITIES || + next->signal_id == AVDTP_GET_ALL_CAPABILITIES))) + finalize_discovery(session, 0); + return TRUE; + } + + /* The remaining commands require an existing stream so bail out + * here if the stream got unexpectedly disconnected */ + if (!stream) { + DBG("AVDTP: stream was closed while waiting for reply"); + return TRUE; + } + + switch (signal_id) { + case AVDTP_SET_CONFIGURATION: + DBG("SET_CONFIGURATION request succeeded"); + return avdtp_set_configuration_resp(session, stream, + buf, size); + case AVDTP_RECONFIGURE: + DBG("RECONFIGURE request succeeded"); + return avdtp_reconfigure_resp(session, stream, buf, size); + case AVDTP_OPEN: + DBG("OPEN request succeeded"); + return avdtp_open_resp(session, stream, buf, size); + case AVDTP_SUSPEND: + DBG("SUSPEND request succeeded"); + return avdtp_suspend_resp(session, stream, buf, size); + case AVDTP_START: + DBG("START request succeeded"); + return avdtp_start_resp(session, stream, buf, size); + case AVDTP_CLOSE: + DBG("CLOSE request succeeded"); + return avdtp_close_resp(session, stream, buf, size); + case AVDTP_ABORT: + DBG("ABORT request succeeded"); + return avdtp_abort_resp(session, stream, buf, size); + case AVDTP_DELAY_REPORT: + DBG("DELAY_REPORT request succeeded"); + return avdtp_delay_report_resp(session, stream, buf, size); + } + + error("Unknown signal id in accept response: %u", signal_id); + return TRUE; +} + +static gboolean seid_rej_to_err(struct seid_rej *rej, unsigned int size, + struct avdtp_error *err) +{ + if (size < sizeof(struct seid_rej)) { + error("Too small packet for seid_rej"); + return FALSE; + } + + avdtp_error_init(err, 0x00, rej->error); + + return TRUE; +} + +static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size, + struct avdtp_error *err) +{ + if (size < sizeof(struct conf_rej)) { + error("Too small packet for conf_rej"); + return FALSE; + } + + avdtp_error_init(err, rej->category, rej->error); + + return TRUE; +} + +static gboolean stream_rej_to_err(struct stream_rej *rej, unsigned int size, + struct avdtp_error *err, + uint8_t *acp_seid) +{ + if (size < sizeof(struct stream_rej)) { + error("Too small packet for stream_rej"); + return FALSE; + } + + avdtp_error_init(err, 0x00, rej->error); + + if (acp_seid) + *acp_seid = rej->acp_seid; + + return TRUE; +} + +static gboolean avdtp_parse_rej(struct avdtp *session, + struct avdtp_stream *stream, + uint8_t transaction, uint8_t signal_id, + void *buf, int size) +{ + struct avdtp_error err; + uint8_t acp_seid; + struct avdtp_local_sep *sep = stream ? stream->lsep : NULL; + + switch (signal_id) { + case AVDTP_DISCOVER: + if (!seid_rej_to_err(buf, size, &err)) + return FALSE; + error("DISCOVER request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_GET_CAPABILITIES: + case AVDTP_GET_ALL_CAPABILITIES: + if (!seid_rej_to_err(buf, size, &err)) + return FALSE; + error("GET_CAPABILITIES request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + return TRUE; + case AVDTP_OPEN: + if (!seid_rej_to_err(buf, size, &err)) + return FALSE; + error("OPEN request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->open) + sep->cfm->open(session, sep, stream, &err, + sep->user_data); + return TRUE; + case AVDTP_SET_CONFIGURATION: + if (!conf_rej_to_err(buf, size, &err)) + return FALSE; + error("SET_CONFIGURATION request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->set_configuration) + sep->cfm->set_configuration(session, sep, stream, + &err, sep->user_data); + return TRUE; + case AVDTP_RECONFIGURE: + if (!conf_rej_to_err(buf, size, &err)) + return FALSE; + error("RECONFIGURE request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->reconfigure) + sep->cfm->reconfigure(session, sep, stream, &err, + sep->user_data); + return TRUE; + case AVDTP_START: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("START request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->start) { + sep->cfm->start(session, sep, stream, &err, + sep->user_data); + stream->starting = FALSE; + } + return TRUE; + case AVDTP_SUSPEND: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("SUSPEND request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->suspend) + sep->cfm->suspend(session, sep, stream, &err, + sep->user_data); + return TRUE; + case AVDTP_CLOSE: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("CLOSE request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->close) { + sep->cfm->close(session, sep, stream, &err, + sep->user_data); + stream->close_int = FALSE; + } + return TRUE; + case AVDTP_ABORT: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("ABORT request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->abort) + sep->cfm->abort(session, sep, stream, &err, + sep->user_data); + return FALSE; + case AVDTP_DELAY_REPORT: + if (!stream_rej_to_err(buf, size, &err, &acp_seid)) + return FALSE; + error("DELAY_REPORT request rejected: %s (%d)", + avdtp_strerror(&err), err.err.error_code); + if (sep && sep->cfm && sep->cfm->delay_report) + sep->cfm->delay_report(session, sep, stream, &err, + sep->user_data); + return TRUE; + default: + error("Unknown reject response signal id: %u", signal_id); + return TRUE; + } +} + +struct avdtp_service_capability *avdtp_stream_get_codec( + struct avdtp_stream *stream) +{ + GSList *l; + + for (l = stream->caps; l; l = l->next) { + struct avdtp_service_capability *cap = l->data; + + if (cap->category == AVDTP_MEDIA_CODEC) + return cap; + } + + return NULL; +} + +static gboolean avdtp_stream_has_capability(struct avdtp_stream *stream, + struct avdtp_service_capability *cap) +{ + GSList *l; + struct avdtp_service_capability *stream_cap; + + for (l = stream->caps; l; l = g_slist_next(l)) { + stream_cap = l->data; + + if (stream_cap->category != cap->category || + stream_cap->length != cap->length) + continue; + + if (memcmp(stream_cap->data, cap->data, cap->length) == 0) + return TRUE; + } + + return FALSE; +} + +gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, + GSList *caps) +{ + for (; caps; caps = g_slist_next(caps)) { + struct avdtp_service_capability *cap = caps->data; + + if (!avdtp_stream_has_capability(stream, cap)) + return FALSE; + } + + return TRUE; +} + +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; + + 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); +} + +uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep) +{ + return sep->seid; +} + +uint8_t avdtp_get_type(struct avdtp_remote_sep *sep) +{ + return sep->type; +} + +struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep) +{ + return sep->codec; +} + +struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + void *data, int length) +{ + struct avdtp_service_capability *cap; + + if (category < AVDTP_MEDIA_TRANSPORT || category > AVDTP_DELAY_REPORTING) + return NULL; + + cap = g_malloc(sizeof(struct avdtp_service_capability) + length); + cap->category = category; + cap->length = length; + memcpy(cap->data, data, length); + + return cap; +} + +struct avdtp_remote_sep *avdtp_register_remote_sep(struct avdtp *session, + uint8_t seid, + uint8_t type, + GSList *caps) +{ + struct avdtp_remote_sep *sep; + GSList *l; + + sep = find_remote_sep(session->seps, seid); + if (sep) + return sep; + + sep = g_new0(struct avdtp_remote_sep, 1); + session->seps = g_slist_append(session->seps, sep); + sep->seid = seid; + sep->type = type; + sep->media_type = AVDTP_MEDIA_TYPE_AUDIO; + sep->caps = caps; + + for (l = caps; l; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (cap->category == AVDTP_MEDIA_CODEC) + sep->codec = cap; + } + + DBG("seid %d type %d media %d", sep->seid, sep->type, sep->media_type); + + return sep; +} + +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; + + if (session->state < AVDTP_SESSION_STATE_CONNECTED) + return -EINVAL; + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + return send_request(session, FALSE, stream, AVDTP_GET_CONFIGURATION, + &req, sizeof(req)); +} + +static void copy_capabilities(gpointer data, gpointer user_data) +{ + struct avdtp_service_capability *src_cap = data; + struct avdtp_service_capability *dst_cap; + GSList **l = user_data; + + dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data, + src_cap->length); + + *l = g_slist_append(*l, dst_cap); +} + +int avdtp_set_configuration(struct avdtp *session, + struct avdtp_remote_sep *rsep, + struct avdtp_local_sep *lsep, + GSList *caps, + struct avdtp_stream **stream) +{ + struct setconf_req *req; + struct avdtp_stream *new_stream; + unsigned char *ptr; + int err, caps_len; + struct avdtp_service_capability *cap; + GSList *l; + + if (session->state != AVDTP_SESSION_STATE_CONNECTED) + return -ENOTCONN; + + if (!(lsep && rsep)) + return -EINVAL; + + DBG("%p: int_seid=%u, acp_seid=%u", session, + lsep->info.seid, rsep->seid); + + new_stream = g_new0(struct avdtp_stream, 1); + new_stream->session = session; + new_stream->lsep = lsep; + new_stream->rseid = rsep->seid; + + if (rsep->delay_reporting && lsep->delay_reporting) { + struct avdtp_service_capability *delay_reporting; + + delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING, + NULL, 0); + caps = g_slist_append(caps, delay_reporting); + new_stream->delay_reporting = TRUE; + } + + g_slist_foreach(caps, copy_capabilities, &new_stream->caps); + + /* Calculate total size of request */ + for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) { + cap = l->data; + caps_len += cap->length + 2; + } + + req = g_malloc0(sizeof(struct setconf_req) + caps_len); + + req->int_seid = lsep->info.seid; + req->acp_seid = rsep->seid; + + /* Copy the capabilities into the request */ + for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) { + cap = l->data; + memcpy(ptr, cap, cap->length + 2); + ptr += cap->length + 2; + } + + err = send_request(session, FALSE, new_stream, + AVDTP_SET_CONFIGURATION, req, + sizeof(struct setconf_req) + caps_len); + if (err < 0) + stream_free(new_stream); + else { + lsep->info.inuse = 1; + lsep->stream = new_stream; + rsep->stream = new_stream; + session->streams = g_slist_append(session->streams, new_stream); + if (stream) + *stream = new_stream; + } + + g_free(req); + + return err; +} + +int avdtp_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->lsep->state < AVDTP_STATE_OPEN) + return -EINVAL; + + if (stream->close_int == TRUE) { + error("avdtp_close: rejecting since close is already initiated"); + return -EINVAL; + } + + if (immediate && session->req && stream == session->req->stream) + return avdtp_abort(session, stream); + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + ret = send_request(session, FALSE, stream, AVDTP_CLOSE, + &req, sizeof(req)); + if (ret == 0) { + stream->close_int = TRUE; + session->dc_timeout = 0; + } + + return ret; +} + +int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream) +{ + struct seid_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->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 (!stream && session->discover) { + /* Don't call cb since it being aborted */ + session->discover->cb = NULL; + finalize_discovery(session, ECANCELED); + return -EALREADY; + } + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->lsep->state == AVDTP_STATE_ABORTING) + return -EINVAL; + + if (session->req && stream == session->req->stream) + return cancel_request(session, ECANCELED); + + memset(&req, 0, sizeof(req)); + req.acp_seid = stream->rseid; + + ret = send_request(session, TRUE, stream, AVDTP_ABORT, + &req, sizeof(req)); + if (ret == 0) { + stream->abort_int = TRUE; + session->dc_timeout = 0; + } + + return ret; +} + +int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream, + uint16_t delay) +{ + struct delay_req req; + + if (!g_slist_find(session->streams, stream)) + return -EINVAL; + + if (stream->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); + + if (!queue_push_tail(lseps, sep)) { + g_free(sep); + return NULL; + } + + return sep; +} + +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; +} + +uint8_t avdtp_sep_get_seid(struct avdtp_local_sep *sep) +{ + return sep->info.seid; +} + +struct btd_adapter *avdtp_get_adapter(struct avdtp *session) +{ + return device_get_adapter(session->device); +} + +struct btd_device *avdtp_get_device(struct avdtp *session) +{ + return session->device; +} + +gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream) +{ + return g_slist_find(session->streams, stream) ? TRUE : FALSE; +} + +unsigned int avdtp_add_state_cb(struct btd_device *dev, + avdtp_session_state_cb cb, void *user_data) +{ + struct avdtp_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct avdtp_state_callback, 1); + state_cb->cb = cb; + state_cb->dev = dev; + state_cb->id = ++id; + state_cb->user_data = user_data; + + state_callbacks = g_slist_append(state_callbacks, state_cb); + + return state_cb->id; +} + +gboolean avdtp_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = state_callbacks; l != NULL; l = l->next) { + struct avdtp_state_callback *cb = l->data; + if (cb && cb->id == id) { + state_callbacks = g_slist_remove(state_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/profiles/audio/avdtp.h b/profiles/audio/avdtp.h new file mode 100644 index 0000000..ad2cb9b --- /dev/null +++ b/profiles/audio/avdtp.h @@ -0,0 +1,312 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +typedef enum { + AVDTP_SESSION_STATE_DISCONNECTED, + AVDTP_SESSION_STATE_CONNECTING, + AVDTP_SESSION_STATE_CONNECTED +} avdtp_session_state_t; + +struct avdtp; +struct avdtp_server; +struct avdtp_stream; +struct avdtp_local_sep; +struct avdtp_remote_sep; +struct avdtp_error { + uint8_t category; + union { + uint8_t error_code; + int posix_errno; + } err; +}; + +/* SEP capability categories */ +#define AVDTP_MEDIA_TRANSPORT 0x01 +#define AVDTP_REPORTING 0x02 +#define AVDTP_RECOVERY 0x03 +#define AVDTP_CONTENT_PROTECTION 0x04 +#define AVDTP_HEADER_COMPRESSION 0x05 +#define AVDTP_MULTIPLEXING 0x06 +#define AVDTP_MEDIA_CODEC 0x07 +#define AVDTP_DELAY_REPORTING 0x08 +#define AVDTP_ERRNO 0xff + +/* AVDTP error definitions */ +#define AVDTP_BAD_HEADER_FORMAT 0x01 +#define AVDTP_BAD_LENGTH 0x11 +#define AVDTP_BAD_ACP_SEID 0x12 +#define AVDTP_SEP_IN_USE 0x13 +#define AVDTP_SEP_NOT_IN_USE 0x14 +#define AVDTP_BAD_SERV_CATEGORY 0x17 +#define AVDTP_BAD_PAYLOAD_FORMAT 0x18 +#define AVDTP_NOT_SUPPORTED_COMMAND 0x19 +#define AVDTP_INVALID_CAPABILITIES 0x1A +#define AVDTP_BAD_RECOVERY_TYPE 0x22 +#define AVDTP_BAD_MEDIA_TRANSPORT_FORMAT 0x23 +#define AVDTP_BAD_RECOVERY_FORMAT 0x25 +#define AVDTP_BAD_ROHC_FORMAT 0x26 +#define AVDTP_BAD_CP_FORMAT 0x27 +#define AVDTP_BAD_MULTIPLEXING_FORMAT 0x28 +#define AVDTP_UNSUPPORTED_CONFIGURATION 0x29 +#define AVDTP_BAD_STATE 0x31 + +/* SEP types definitions */ +#define AVDTP_SEP_TYPE_SOURCE 0x00 +#define AVDTP_SEP_TYPE_SINK 0x01 + +/* Media types definitions */ +#define AVDTP_MEDIA_TYPE_AUDIO 0x00 +#define AVDTP_MEDIA_TYPE_VIDEO 0x01 +#define AVDTP_MEDIA_TYPE_MULTIMEDIA 0x02 + +typedef enum { + AVDTP_STATE_IDLE, + AVDTP_STATE_CONFIGURED, + AVDTP_STATE_OPEN, + AVDTP_STATE_STREAMING, + AVDTP_STATE_CLOSING, + AVDTP_STATE_ABORTING, +} avdtp_state_t; + +struct avdtp_service_capability { + uint8_t category; + uint8_t length; + uint8_t data[0]; +} __attribute__ ((packed)); + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avdtp_media_codec_capability { + uint8_t rfa0:4; + uint8_t media_type:4; + uint8_t media_codec_type; + uint8_t data[0]; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avdtp_media_codec_capability { + uint8_t media_type:4; + uint8_t rfa0:4; + uint8_t media_codec_type; + uint8_t data[0]; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +typedef void (*avdtp_session_state_cb) (struct btd_device *dev, + struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data); + +typedef void (*avdtp_stream_state_cb) (struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data); + +typedef void (*avdtp_set_configuration_cb) (struct avdtp *session, + struct avdtp_stream *stream, + struct avdtp_error *err); + +/* Callbacks for when a reply is received to a command that we sent */ +struct avdtp_sep_cfm { + void (*set_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data); + void (*get_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data); + void (*open) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data); + void (*start) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data); + void (*suspend) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*close) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*abort) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*reconfigure) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); + void (*delay_report) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + struct avdtp_error *err, void *user_data); +}; + +/* Callbacks for indicating when we received a new command. The return value + * indicates whether the command should be rejected or accepted */ +struct avdtp_sep_ind { + gboolean (*match_codec) (struct avdtp *session, + struct avdtp_media_codec_capability *codec, + void *user_data); + gboolean (*get_capability) (struct avdtp *session, + struct avdtp_local_sep *sep, + gboolean get_all, + GSList **caps, uint8_t *err, + void *user_data); + gboolean (*set_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, + GSList *caps, + avdtp_set_configuration_cb cb, + void *user_data); + gboolean (*get_configuration) (struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t *err, void *user_data); + gboolean (*open) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*start) (struct avdtp *session, struct avdtp_local_sep *lsep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*suspend) (struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*close) (struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + void (*abort) (struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, uint8_t *err, + void *user_data); + gboolean (*reconfigure) (struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t *err, void *user_data); + gboolean (*delayreport) (struct avdtp *session, + struct avdtp_local_sep *lsep, + uint8_t rseid, uint16_t delay, + uint8_t *err, void *user_data); +}; + +typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps, + struct avdtp_error *err, void *user_data); + +void avdtp_unref(struct avdtp *session); +struct avdtp *avdtp_ref(struct avdtp *session); + +struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + void *data, int size); + +struct avdtp_remote_sep *avdtp_register_remote_sep(struct avdtp *session, + uint8_t seid, + uint8_t type, + GSList *caps); + +uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep); + +uint8_t avdtp_get_type(struct avdtp_remote_sep *sep); + +struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep); + +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); + +unsigned int avdtp_add_state_cb(struct btd_device *dev, + avdtp_session_state_cb cb, void *user_data); + +gboolean avdtp_remove_state_cb(unsigned int id); + +int avdtp_set_configuration(struct avdtp *session, + struct avdtp_remote_sep *rsep, + struct avdtp_local_sep *lsep, + GSList *caps, + struct avdtp_stream **stream); + +int avdtp_get_configuration(struct avdtp *session, + struct avdtp_stream *stream); + +int avdtp_open(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_start(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_close(struct avdtp *session, struct avdtp_stream *stream, + gboolean immediate); +int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream); +int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream, + uint16_t delay); + +struct avdtp_local_sep *avdtp_register_sep(struct queue *lseps, uint8_t type, + uint8_t media_type, + uint8_t codec_type, + gboolean delay_reporting, + struct avdtp_sep_ind *ind, + struct avdtp_sep_cfm *cfm, + void *user_data); + +/* Find a matching pair of local and remote SEP ID's */ +struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session, + struct avdtp_local_sep *lsep); + +int avdtp_unregister_sep(struct queue *lseps, struct avdtp_local_sep *sep); + +avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep); +uint8_t avdtp_sep_get_seid(struct avdtp_local_sep *sep); + +void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id); +const char *avdtp_strerror(struct avdtp_error *err); +uint8_t avdtp_error_category(struct avdtp_error *err); +int avdtp_error_error_code(struct avdtp_error *err); +int avdtp_error_posix_errno(struct avdtp_error *err); + +struct btd_adapter *avdtp_get_adapter(struct avdtp *session); +struct btd_device *avdtp_get_device(struct avdtp *session); +struct avdtp_server *avdtp_get_server(struct avdtp_local_sep *lsep); + +struct avdtp *avdtp_new(GIOChannel *chan, struct btd_device *device, + struct queue *lseps); diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c new file mode 100644 index 0000000..6b3f685 --- /dev/null +++ b/profiles/audio/avrcp.c @@ -0,0 +1,4591 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "bluetooth/bluetooth.h" +#include "bluetooth/sdp.h" +#include "bluetooth/sdp_lib.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/log.h" +#include "src/error.h" +#include "src/sdpd.h" +#include "src/dbus-common.h" +#include "src/shared/util.h" + +#include "avctp.h" +#include "avrcp.h" +#include "control.h" +#include "player.h" +#include "transport.h" + +/* Company IDs for vendor dependent commands */ +#define IEEEID_BTSIG 0x001958 + +/* Status codes */ +#define AVRCP_STATUS_INVALID_COMMAND 0x00 +#define AVRCP_STATUS_INVALID_PARAM 0x01 +#define AVRCP_STATUS_PARAM_NOT_FOUND 0x02 +#define AVRCP_STATUS_INTERNAL_ERROR 0x03 +#define AVRCP_STATUS_SUCCESS 0x04 +#define AVRCP_STATUS_UID_CHANGED 0x05 +#define AVRCP_STATUS_DOES_NOT_EXIST 0x09 +#define AVRCP_STATUS_OUT_OF_BOUNDS 0x0b +#define AVRCP_STATUS_INVALID_PLAYER_ID 0x11 +#define AVRCP_STATUS_PLAYER_NOT_BROWSABLE 0x12 +#define AVRCP_STATUS_NO_AVAILABLE_PLAYERS 0x15 +#define AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED 0x16 + +/* Packet types */ +#define AVRCP_PACKET_TYPE_SINGLE 0x00 +#define AVRCP_PACKET_TYPE_START 0x01 +#define AVRCP_PACKET_TYPE_CONTINUING 0x02 +#define AVRCP_PACKET_TYPE_END 0x03 + +/* PDU types for metadata transfer */ +#define AVRCP_GET_CAPABILITIES 0x10 +#define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11 +#define AVRCP_LIST_PLAYER_VALUES 0x12 +#define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13 +#define AVRCP_SET_PLAYER_VALUE 0x14 +#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT 0x15 +#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16 +#define AVRCP_DISPLAYABLE_CHARSET 0x17 +#define AVRCP_CT_BATTERY_STATUS 0x18 +#define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20 +#define AVRCP_GET_PLAY_STATUS 0x30 +#define AVRCP_REGISTER_NOTIFICATION 0x31 +#define AVRCP_REQUEST_CONTINUING 0x40 +#define AVRCP_ABORT_CONTINUING 0x41 +#define AVRCP_SET_ABSOLUTE_VOLUME 0x50 +#define AVRCP_SET_ADDRESSED_PLAYER 0x60 +#define AVRCP_SET_BROWSED_PLAYER 0x70 +#define AVRCP_GET_FOLDER_ITEMS 0x71 +#define AVRCP_CHANGE_PATH 0x72 +#define AVRCP_GET_ITEM_ATTRIBUTES 0x73 +#define AVRCP_PLAY_ITEM 0x74 +#define AVRCP_GET_TOTAL_NUMBER_OF_ITEMS 0x75 +#define AVRCP_SEARCH 0x80 +#define AVRCP_ADD_TO_NOW_PLAYING 0x90 +#define AVRCP_GENERAL_REJECT 0xA0 + +/* Capabilities for AVRCP_GET_CAPABILITIES pdu */ +#define CAP_COMPANY_ID 0x02 +#define CAP_EVENTS_SUPPORTED 0x03 + +#define AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH 5 +#define AVRCP_GET_CAPABILITIES_PARAM_LENGTH 1 + +#define AVRCP_FEATURE_CATEGORY_1 0x0001 +#define AVRCP_FEATURE_CATEGORY_2 0x0002 +#define AVRCP_FEATURE_CATEGORY_3 0x0004 +#define AVRCP_FEATURE_CATEGORY_4 0x0008 +#define AVRCP_FEATURE_PLAYER_SETTINGS 0x0010 +#define AVRCP_FEATURE_BROWSING 0x0040 + +#define AVRCP_BATTERY_STATUS_NORMAL 0 +#define AVRCP_BATTERY_STATUS_WARNING 1 +#define AVRCP_BATTERY_STATUS_CRITICAL 2 +#define AVRCP_BATTERY_STATUS_EXTERNAL 3 +#define AVRCP_BATTERY_STATUS_FULL_CHARGE 4 + +#define AVRCP_CHARSET_UTF8 106 + +#define AVRCP_BROWSING_TIMEOUT 1 +#define AVRCP_CT_VERSION 0x0106 +#define AVRCP_TG_VERSION 0x0105 + +#define AVRCP_SCOPE_MEDIA_PLAYER_LIST 0x00 +#define AVRCP_SCOPE_MEDIA_PLAYER_VFS 0x01 +#define AVRCP_SCOPE_SEARCH 0x02 +#define AVRCP_SCOPE_NOW_PLAYING 0x03 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avrcp_header { + uint8_t company_id[3]; + uint8_t pdu_id; + uint8_t packet_type:2; + uint8_t rsvd:6; + uint16_t params_len; + uint8_t params[0]; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 7 + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avrcp_header { + uint8_t company_id[3]; + uint8_t pdu_id; + uint8_t rsvd:6; + uint8_t packet_type:2; + uint16_t params_len; + uint8_t params[0]; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 7 + +#else +#error "Unknown byte order" +#endif + +#define AVRCP_MTU (AVC_MTU - AVC_HEADER_LENGTH) +#define AVRCP_PDU_MTU (AVRCP_MTU - AVRCP_HEADER_LENGTH) + +struct avrcp_browsing_header { + uint8_t pdu_id; + uint16_t param_len; + uint8_t params[0]; +} __attribute__ ((packed)); +#define AVRCP_BROWSING_HEADER_LENGTH 3 + +struct get_folder_items_rsp { + uint8_t status; + uint16_t uid_counter; + uint16_t num_items; + uint8_t data[0]; +} __attribute__ ((packed)); + +struct folder_item { + uint8_t type; + uint16_t len; + uint8_t data[0]; +} __attribute__ ((packed)); + +struct player_item { + uint16_t player_id; + uint8_t type; + uint32_t subtype; + uint8_t status; + uint8_t features[16]; + uint16_t charset; + uint16_t namelen; + char name[0]; +} __attribute__ ((packed)); + +struct avrcp_server { + struct btd_adapter *adapter; + uint32_t tg_record_id; + uint32_t ct_record_id; + GSList *players; + GSList *sessions; +}; + +struct pending_pdu { + uint8_t pdu_id; + GList *attr_ids; + uint16_t offset; +}; + +struct pending_list_items { + GSList *items; + uint32_t start; + uint32_t end; + uint64_t total; +}; + +struct avrcp_player { + struct avrcp_server *server; + GSList *sessions; + uint16_t id; + uint8_t scope; + uint64_t uid; + uint16_t uid_counter; + bool browsed; + bool addressed; + uint8_t *features; + char *path; + guint changed_id; + + struct pending_list_items *p; + char *change_path; + uint64_t change_uid; + + struct avrcp_player_cb *cb; + void *user_data; + GDestroyNotify destroy; +}; + +struct avrcp_data { + struct avrcp_player *player; + uint16_t version; + int features; + GSList *players; +}; + +struct avrcp { + struct avrcp_server *server; + struct avctp *conn; + struct btd_device *dev; + struct avrcp_data *target; + struct avrcp_data *controller; + + const struct passthrough_handler *passthrough_handlers; + const struct control_pdu_handler *control_handlers; + + unsigned int passthrough_id; + unsigned int control_id; + unsigned int browsing_id; + unsigned int browsing_timer; + uint16_t supported_events; + uint16_t registered_events; + uint8_t transaction; + uint8_t transaction_events[AVRCP_EVENT_LAST + 1]; + struct pending_pdu *pending_pdu; +}; + +struct passthrough_handler { + uint8_t op; + bool (*func) (struct avrcp *session); +}; + +struct control_pdu_handler { + uint8_t pdu_id; + uint8_t code; + uint8_t (*func) (struct avrcp *session, struct avrcp_header *pdu, + uint8_t transaction); +}; + +static GSList *servers = NULL; +static unsigned int avctp_id = 0; + +/* Default feature bit mask for media player as per avctp.c:key_map */ +static const uint8_t features[16] = { + 0xF8, 0xBF, 0xFF, 0xBF, 0x1F, + 0xFB, 0x3F, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 }; + +/* Company IDs supported by this device */ +static uint32_t company_ids[] = { + IEEEID_BTSIG, +}; + +static void avrcp_register_notification(struct avrcp *session, uint8_t event); + +static sdp_record_t *avrcp_ct_record(void) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *apseq1, *root; + uuid_t root_uuid, l2cap, avctp, avrct, avrctr; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *aproto1, *proto[2], *proto1[2]; + sdp_record_t *record; + sdp_data_t *psm[2], *version, *features; + uint16_t lp = AVCTP_CONTROL_PSM, ap = AVCTP_BROWSING_PSM; + uint16_t avctp_ver = 0x0103; + uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 | + AVRCP_FEATURE_CATEGORY_2 | + AVRCP_FEATURE_CATEGORY_3 | + AVRCP_FEATURE_CATEGORY_4 | + AVRCP_FEATURE_BROWSING); + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + /* Service Class ID List */ + sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); + svclass_id = sdp_list_append(NULL, &avrct); + sdp_uuid16_create(&avrctr, AV_REMOTE_CONTROLLER_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &avrctr); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap); + psm[0] = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm[0]); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto[1] = sdp_list_append(NULL, &avctp); + version = sdp_data_alloc(SDP_UINT16, &avctp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + /* Additional Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto1[0] = sdp_list_append(NULL, &l2cap); + psm[1] = sdp_data_alloc(SDP_UINT16, &ap); + proto1[0] = sdp_list_append(proto1[0], psm[1]); + apseq1 = sdp_list_append(NULL, proto1[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto1[1] = sdp_list_append(NULL, &avctp); + proto1[1] = sdp_list_append(proto1[1], version); + apseq1 = sdp_list_append(apseq1, proto1[1]); + + aproto1 = sdp_list_append(NULL, apseq1); + sdp_set_add_access_protos(record, aproto1); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = AVRCP_CT_VERSION; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(record, "AVRCP CT", NULL, NULL); + + free(psm[0]); + free(psm[1]); + free(version); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(proto1[0], NULL); + sdp_list_free(proto1[1], NULL); + sdp_list_free(aproto1, NULL); + sdp_list_free(apseq1, NULL); + sdp_list_free(pfseq, NULL); + sdp_list_free(aproto, NULL); + sdp_list_free(root, NULL); + sdp_list_free(svclass_id, NULL); + + return record; +} + +static sdp_record_t *avrcp_tg_record(void) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root, *apseq_browsing; + uuid_t root_uuid, l2cap, avctp, avrtg; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto_control, *proto_control[2]; + sdp_record_t *record; + sdp_data_t *psm_control, *version, *features, *psm_browsing; + sdp_list_t *aproto_browsing, *proto_browsing[2] = {0}; + uint16_t lp = AVCTP_CONTROL_PSM; + uint16_t lp_browsing = AVCTP_BROWSING_PSM; + uint16_t avctp_ver = 0x0103; + uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 | + AVRCP_FEATURE_CATEGORY_2 | + AVRCP_FEATURE_CATEGORY_3 | + AVRCP_FEATURE_CATEGORY_4 | + AVRCP_FEATURE_BROWSING | + AVRCP_FEATURE_PLAYER_SETTINGS ); + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + /* Service Class ID List */ + sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); + svclass_id = sdp_list_append(NULL, &avrtg); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto_control[0] = sdp_list_append(NULL, &l2cap); + psm_control = sdp_data_alloc(SDP_UINT16, &lp); + proto_control[0] = sdp_list_append(proto_control[0], psm_control); + apseq = sdp_list_append(NULL, proto_control[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto_control[1] = sdp_list_append(NULL, &avctp); + version = sdp_data_alloc(SDP_UINT16, &avctp_ver); + proto_control[1] = sdp_list_append(proto_control[1], version); + apseq = sdp_list_append(apseq, proto_control[1]); + + aproto_control = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto_control); + proto_browsing[0] = sdp_list_append(NULL, &l2cap); + psm_browsing = sdp_data_alloc(SDP_UINT16, &lp_browsing); + proto_browsing[0] = sdp_list_append(proto_browsing[0], psm_browsing); + apseq_browsing = sdp_list_append(NULL, proto_browsing[0]); + + proto_browsing[1] = sdp_list_append(NULL, &avctp); + proto_browsing[1] = sdp_list_append(proto_browsing[1], version); + apseq_browsing = sdp_list_append(apseq_browsing, proto_browsing[1]); + + aproto_browsing = sdp_list_append(NULL, apseq_browsing); + sdp_set_add_access_protos(record, aproto_browsing); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = AVRCP_TG_VERSION; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(record, "AVRCP TG", NULL, NULL); + + free(psm_browsing); + sdp_list_free(proto_browsing[0], NULL); + sdp_list_free(proto_browsing[1], NULL); + sdp_list_free(apseq_browsing, NULL); + sdp_list_free(aproto_browsing, NULL); + + free(psm_control); + free(version); + sdp_list_free(proto_control[0], NULL); + sdp_list_free(proto_control[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(aproto_control, NULL); + sdp_list_free(pfseq, NULL); + sdp_list_free(root, NULL); + sdp_list_free(svclass_id, NULL); + + return record; +} + +static unsigned int attr_get_max_val(uint8_t attr) +{ + switch (attr) { + case AVRCP_ATTRIBUTE_EQUALIZER: + return AVRCP_EQUALIZER_ON; + case AVRCP_ATTRIBUTE_REPEAT_MODE: + return AVRCP_REPEAT_MODE_GROUP; + case AVRCP_ATTRIBUTE_SHUFFLE: + return AVRCP_SHUFFLE_GROUP; + case AVRCP_ATTRIBUTE_SCAN: + return AVRCP_SCAN_GROUP; + } + + return 0; +} + +static const char *battery_status_to_str(uint8_t status) +{ + switch (status) { + case AVRCP_BATTERY_STATUS_NORMAL: + return "normal"; + case AVRCP_BATTERY_STATUS_WARNING: + return "warning"; + case AVRCP_BATTERY_STATUS_CRITICAL: + return "critical"; + case AVRCP_BATTERY_STATUS_EXTERNAL: + return "external"; + case AVRCP_BATTERY_STATUS_FULL_CHARGE: + return "fullcharge"; + } + + return NULL; +} + +/* + * get_company_id: + * + * Get three-byte Company_ID from incoming AVRCP message + */ +static uint32_t get_company_id(const uint8_t cid[3]) +{ + return cid[0] << 16 | cid[1] << 8 | cid[2]; +} + +/* + * set_company_id: + * + * Set three-byte Company_ID into outgoing AVRCP message + */ +static void set_company_id(uint8_t cid[3], uint32_t cid_in) +{ + cid[0] = (cid_in & 0xff0000) >> 16; + cid[1] = (cid_in & 0x00ff00) >> 8; + cid[2] = (cid_in & 0x0000ff); +} + +static const char *attr_to_str(uint8_t attr) +{ + switch (attr) { + case AVRCP_ATTRIBUTE_EQUALIZER: + return "Equalizer"; + case AVRCP_ATTRIBUTE_REPEAT_MODE: + return "Repeat"; + case AVRCP_ATTRIBUTE_SHUFFLE: + return "Shuffle"; + case AVRCP_ATTRIBUTE_SCAN: + return "Scan"; + } + + return NULL; +} + +static int attrval_to_val(uint8_t attr, const char *value) +{ + int ret; + + switch (attr) { + case AVRCP_ATTRIBUTE_EQUALIZER: + if (!strcmp(value, "off")) + ret = AVRCP_EQUALIZER_OFF; + else if (!strcmp(value, "on")) + ret = AVRCP_EQUALIZER_ON; + else + ret = -EINVAL; + + return ret; + case AVRCP_ATTRIBUTE_REPEAT_MODE: + if (!strcmp(value, "off")) + ret = AVRCP_REPEAT_MODE_OFF; + else if (!strcmp(value, "singletrack")) + ret = AVRCP_REPEAT_MODE_SINGLE; + else if (!strcmp(value, "alltracks")) + ret = AVRCP_REPEAT_MODE_ALL; + else if (!strcmp(value, "group")) + ret = AVRCP_REPEAT_MODE_GROUP; + else + ret = -EINVAL; + + return ret; + case AVRCP_ATTRIBUTE_SHUFFLE: + if (!strcmp(value, "off")) + ret = AVRCP_SHUFFLE_OFF; + else if (!strcmp(value, "alltracks")) + ret = AVRCP_SHUFFLE_ALL; + else if (!strcmp(value, "group")) + ret = AVRCP_SHUFFLE_GROUP; + else + ret = -EINVAL; + + return ret; + case AVRCP_ATTRIBUTE_SCAN: + if (!strcmp(value, "off")) + ret = AVRCP_SCAN_OFF; + else if (!strcmp(value, "alltracks")) + ret = AVRCP_SCAN_ALL; + else if (!strcmp(value, "group")) + ret = AVRCP_SCAN_GROUP; + else + ret = -EINVAL; + + return ret; + } + + return -EINVAL; +} + +static int attr_to_val(const char *str) +{ + if (!strcasecmp(str, "Equalizer")) + return AVRCP_ATTRIBUTE_EQUALIZER; + else if (!strcasecmp(str, "Repeat")) + return AVRCP_ATTRIBUTE_REPEAT_MODE; + else if (!strcasecmp(str, "Shuffle")) + return AVRCP_ATTRIBUTE_SHUFFLE; + else if (!strcasecmp(str, "Scan")) + return AVRCP_ATTRIBUTE_SCAN; + + return -EINVAL; +} + +static int player_get_setting(struct avrcp_player *player, uint8_t id) +{ + const char *key; + const char *value; + + if (player == NULL) + return -ENOENT; + + key = attr_to_str(id); + if (key == NULL) + return -EINVAL; + + value = player->cb->get_setting(key, player->user_data); + if (value == NULL) + return -EINVAL; + + return attrval_to_val(id, value); +} + +static int play_status_to_val(const char *status) +{ + if (!strcasecmp(status, "stopped")) + return AVRCP_PLAY_STATUS_STOPPED; + else if (!strcasecmp(status, "playing")) + return AVRCP_PLAY_STATUS_PLAYING; + else if (!strcasecmp(status, "paused")) + return AVRCP_PLAY_STATUS_PAUSED; + else if (!strcasecmp(status, "forward-seek")) + return AVRCP_PLAY_STATUS_FWD_SEEK; + else if (!strcasecmp(status, "reverse-seek")) + return AVRCP_PLAY_STATUS_REV_SEEK; + else if (!strcasecmp(status, "error")) + return AVRCP_PLAY_STATUS_ERROR; + + return -EINVAL; +} + +void avrcp_player_event(struct avrcp_player *player, uint8_t id, + const void *data) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + 9]; + struct avrcp_header *pdu = (void *) buf; + uint8_t code; + uint16_t size; + GSList *l; + int attr; + int val; + + if (player->sessions == NULL) + return; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + + pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; + + DBG("id=%u", id); + + if (id != AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED && player->changed_id) { + code = AVC_CTYPE_REJECTED; + size = 1; + pdu->params[0] = AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED; + goto done; + } + + code = AVC_CTYPE_CHANGED; + pdu->params[0] = id; + + switch (id) { + case AVRCP_EVENT_STATUS_CHANGED: + size = 2; + pdu->params[1] = play_status_to_val(data); + + break; + case AVRCP_EVENT_TRACK_CHANGED: + size = 9; + memcpy(&pdu->params[1], data, sizeof(uint64_t)); + + break; + case AVRCP_EVENT_TRACK_REACHED_END: + case AVRCP_EVENT_TRACK_REACHED_START: + size = 1; + break; + case AVRCP_EVENT_SETTINGS_CHANGED: + size = 2; + pdu->params[1] = 1; + + attr = attr_to_val(data); + if (attr < 0) + return; + + val = player_get_setting(player, attr); + if (val < 0) + return; + + pdu->params[size++] = attr; + pdu->params[size++] = val; + break; + case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: + size = 5; + memcpy(&pdu->params[1], &player->id, sizeof(uint16_t)); + memcpy(&pdu->params[3], &player->uid_counter, sizeof(uint16_t)); + break; + case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: + size = 1; + break; + default: + error("Unknown event %u", id); + return; + } + +done: + pdu->params_len = htons(size); + + for (l = player->sessions; l; l = l->next) { + struct avrcp *session = l->data; + int err; + + if (!(session->registered_events & (1 << id))) + continue; + + err = avctp_send_vendordep(session->conn, + session->transaction_events[id], + code, AVC_SUBUNIT_PANEL, + buf, size + AVRCP_HEADER_LENGTH); + + if (err < 0) + continue; + + /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */ + session->registered_events ^= 1 << id; + } + + return; +} + +static const char *metadata_to_str(uint32_t id) +{ + switch (id) { + case AVRCP_MEDIA_ATTRIBUTE_TITLE: + return "Title"; + case AVRCP_MEDIA_ATTRIBUTE_ARTIST: + return "Artist"; + case AVRCP_MEDIA_ATTRIBUTE_ALBUM: + return "Album"; + case AVRCP_MEDIA_ATTRIBUTE_GENRE: + return "Genre"; + case AVRCP_MEDIA_ATTRIBUTE_TRACK: + return "TrackNumber"; + case AVRCP_MEDIA_ATTRIBUTE_N_TRACKS: + return "NumberOfTracks"; + case AVRCP_MEDIA_ATTRIBUTE_DURATION: + return "Duration"; + } + + return NULL; +} + +static const char *player_get_metadata(struct avrcp_player *player, + uint32_t id) +{ + const char *key; + + key = metadata_to_str(id); + if (key == NULL) + return NULL; + + if (player != NULL) + return player->cb->get_metadata(key, player->user_data); + + if (id == AVRCP_MEDIA_ATTRIBUTE_TITLE) + return ""; + + return NULL; +} + +static uint16_t player_write_media_attribute(struct avrcp_player *player, + uint32_t id, uint8_t *buf, + uint16_t *pos, + uint16_t *offset) +{ + uint16_t len; + uint16_t attr_len; + const char *value = NULL; + + DBG("%u", id); + + value = player_get_metadata(player, id); + if (value == NULL) { + *offset = 0; + return 0; + } + + attr_len = strlen(value); + value = ((char *) value) + *offset; + len = attr_len - *offset; + + if (len > AVRCP_PDU_MTU - *pos) { + len = AVRCP_PDU_MTU - *pos; + *offset += len; + } else { + *offset = 0; + } + + memcpy(&buf[*pos], value, len); + *pos += len; + + return attr_len; +} + +static GList *player_fill_media_attribute(struct avrcp_player *player, + GList *attr_ids, uint8_t *buf, + uint16_t *pos, uint16_t *offset) +{ + struct media_attribute_header { + uint32_t id; + uint16_t charset; + uint16_t len; + } *hdr = NULL; + GList *l; + + for (l = attr_ids; l != NULL; l = g_list_delete_link(l, l)) { + uint32_t attr = GPOINTER_TO_UINT(l->data); + uint16_t attr_len; + + if (*offset == 0) { + if (*pos + sizeof(*hdr) >= AVRCP_PDU_MTU) + break; + + hdr = (void *) &buf[*pos]; + hdr->id = htonl(attr); + /* Always use UTF-8 */ + hdr->charset = htons(AVRCP_CHARSET_UTF8); + *pos += sizeof(*hdr); + } + + attr_len = player_write_media_attribute(player, attr, buf, + pos, offset); + + if (hdr != NULL) + hdr->len = htons(attr_len); + + if (*offset > 0) + break; + } + + return l; +} + +static struct pending_pdu *pending_pdu_new(uint8_t pdu_id, GList *attr_ids, + unsigned int offset) +{ + struct pending_pdu *pending = g_new(struct pending_pdu, 1); + + pending->pdu_id = pdu_id; + pending->attr_ids = attr_ids; + pending->offset = offset; + + return pending; +} + +static gboolean session_abort_pending_pdu(struct avrcp *session) +{ + if (session->pending_pdu == NULL) + return FALSE; + + g_list_free(session->pending_pdu->attr_ids); + g_free(session->pending_pdu); + session->pending_pdu = NULL; + + return TRUE; +} + +static const char *attrval_to_str(uint8_t attr, uint8_t value) +{ + switch (attr) { + case AVRCP_ATTRIBUTE_EQUALIZER: + switch (value) { + case AVRCP_EQUALIZER_ON: + return "on"; + case AVRCP_EQUALIZER_OFF: + return "off"; + } + + break; + case AVRCP_ATTRIBUTE_REPEAT_MODE: + switch (value) { + case AVRCP_REPEAT_MODE_OFF: + return "off"; + case AVRCP_REPEAT_MODE_SINGLE: + return "singletrack"; + case AVRCP_REPEAT_MODE_ALL: + return "alltracks"; + case AVRCP_REPEAT_MODE_GROUP: + return "group"; + } + + break; + /* Shuffle and scan have the same values */ + case AVRCP_ATTRIBUTE_SHUFFLE: + case AVRCP_ATTRIBUTE_SCAN: + switch (value) { + case AVRCP_SCAN_OFF: + return "off"; + case AVRCP_SCAN_ALL: + return "alltracks"; + case AVRCP_SCAN_GROUP: + return "group"; + } + + break; + } + + return NULL; +} + +static int player_set_setting(struct avrcp_player *player, uint8_t id, + uint8_t val) +{ + const char *key, *value; + + key = attr_to_str(id); + if (key == NULL) + return -EINVAL; + + value = attrval_to_str(id, val); + if (value == NULL) + return -EINVAL; + + if (player == NULL) + return -ENOENT; + + return player->cb->set_setting(key, value, player->user_data); +} + +static uint8_t avrcp_handle_get_capabilities(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + unsigned int i; + + if (len != 1) + goto err; + + DBG("id=%u", pdu->params[0]); + + switch (pdu->params[0]) { + case CAP_COMPANY_ID: + for (i = 0; i < G_N_ELEMENTS(company_ids); i++) { + set_company_id(&pdu->params[2 + i * 3], + company_ids[i]); + } + + pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids))); + pdu->params[1] = G_N_ELEMENTS(company_ids); + + return AVC_CTYPE_STABLE; + case CAP_EVENTS_SUPPORTED: + pdu->params[1] = 0; + for (i = 1; i <= AVRCP_EVENT_LAST; i++) { + if (session->supported_events & (1 << i)) { + pdu->params[1]++; + pdu->params[pdu->params[1] + 1] = i; + } + } + + pdu->params_len = htons(2 + pdu->params[1]); + return AVC_CTYPE_STABLE; + } + +err: + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + + return AVC_CTYPE_REJECTED; +} + +static struct avrcp_player *target_get_player(struct avrcp *session) +{ + if (!session->target) + return NULL; + + return session->target->player; +} + +static uint8_t avrcp_handle_list_player_attributes(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + struct avrcp_player *player = target_get_player(session); + uint16_t len = ntohs(pdu->params_len); + unsigned int i; + + if (len != 0) { + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; + } + + if (!player) + goto done; + + for (i = 1; i <= AVRCP_ATTRIBUTE_SCAN; i++) { + if (player_get_setting(player, i) < 0) + continue; + + len++; + pdu->params[len] = i; + } + +done: + pdu->params[0] = len; + pdu->params_len = htons(len + 1); + + return AVC_CTYPE_STABLE; +} + +static uint8_t avrcp_handle_list_player_values(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + struct avrcp_player *player = target_get_player(session); + uint16_t len = ntohs(pdu->params_len); + unsigned int i; + + if (len != 1) + goto err; + + if (player_get_setting(player, pdu->params[0]) < 0) + goto err; + + len = attr_get_max_val(pdu->params[0]); + + for (i = 1; i <= len; i++) + pdu->params[i] = i; + + pdu->params[0] = len; + pdu->params_len = htons(len + 1); + + return AVC_CTYPE_STABLE; + +err: + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint32_t str_to_metadata(const char *str) +{ + if (strcasecmp(str, "Title") == 0) + return AVRCP_MEDIA_ATTRIBUTE_TITLE; + else if (strcasecmp(str, "Artist") == 0) + return AVRCP_MEDIA_ATTRIBUTE_ARTIST; + else if (strcasecmp(str, "Album") == 0) + return AVRCP_MEDIA_ATTRIBUTE_ALBUM; + else if (strcasecmp(str, "Genre") == 0) + return AVRCP_MEDIA_ATTRIBUTE_GENRE; + else if (strcasecmp(str, "TrackNumber") == 0) + return AVRCP_MEDIA_ATTRIBUTE_TRACK; + else if (strcasecmp(str, "NumberOfTracks") == 0) + return AVRCP_MEDIA_ATTRIBUTE_N_TRACKS; + else if (strcasecmp(str, "Duration") == 0) + return AVRCP_MEDIA_ATTRIBUTE_DURATION; + + return 0; +} + +static GList *player_list_metadata(struct avrcp_player *player) +{ + GList *l, *attrs = NULL; + + if (player == NULL) + return g_list_prepend(NULL, + GUINT_TO_POINTER(AVRCP_MEDIA_ATTRIBUTE_TITLE)); + + l = player->cb->list_metadata(player->user_data); + for (; l; l = l->next) { + const char *key = l->data; + + attrs = g_list_append(attrs, + GUINT_TO_POINTER(str_to_metadata(key))); + } + + return attrs; +} + +static uint8_t avrcp_handle_get_element_attributes(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + struct avrcp_player *player = target_get_player(session); + uint16_t len = ntohs(pdu->params_len); + uint64_t identifier = get_le64(&pdu->params[0]); + uint16_t pos; + uint8_t nattr; + GList *attr_ids; + uint16_t offset; + + if (len < 9 || identifier != 0) + goto err; + + nattr = pdu->params[8]; + + if (len < nattr * sizeof(uint32_t) + 1) + goto err; + + if (!nattr) { + /* + * Return all available information, at least + * title must be returned if there's a track selected. + */ + attr_ids = player_list_metadata(player); + len = g_list_length(attr_ids); + } else { + unsigned int i; + for (i = 0, len = 0, attr_ids = NULL; i < nattr; i++) { + uint32_t id; + + id = get_be32(&pdu->params[9] + (i * sizeof(id))); + + /* Don't add invalid attributes */ + if (id == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || + id > AVRCP_MEDIA_ATTRIBUTE_LAST) + continue; + + len++; + attr_ids = g_list_prepend(attr_ids, + GUINT_TO_POINTER(id)); + } + + attr_ids = g_list_reverse(attr_ids); + } + + if (!len) + goto err; + + session_abort_pending_pdu(session); + pos = 1; + offset = 0; + attr_ids = player_fill_media_attribute(player, attr_ids, pdu->params, + &pos, &offset); + + if (attr_ids != NULL) { + session->pending_pdu = pending_pdu_new(pdu->pdu_id, attr_ids, + offset); + pdu->packet_type = AVRCP_PACKET_TYPE_START; + } + + pdu->params[0] = len; + pdu->params_len = htons(pos); + + return AVC_CTYPE_STABLE; +err: + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_get_current_player_value(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + struct avrcp_player *player = target_get_player(session); + uint16_t len = ntohs(pdu->params_len); + uint8_t *settings; + unsigned int i; + + if (len <= 1 || pdu->params[0] != len - 1) + goto err; + + /* + * Save a copy of requested settings because we can override them + * while responding + */ + settings = g_memdup(&pdu->params[1], pdu->params[0]); + len = 0; + + /* + * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs + * and send a response with the existent ones. Only if all IDs are + * non-existent we should send an error. + */ + for (i = 0; i < pdu->params[0]; i++) { + int val; + + if (settings[i] < AVRCP_ATTRIBUTE_EQUALIZER || + settings[i] > AVRCP_ATTRIBUTE_SCAN) { + DBG("Ignoring %u", settings[i]); + continue; + } + + val = player_get_setting(player, settings[i]); + if (val < 0) + continue; + + pdu->params[++len] = settings[i]; + pdu->params[++len] = val; + } + + g_free(settings); + + if (len) { + pdu->params[0] = len / 2; + pdu->params_len = htons(len + 1); + + return AVC_CTYPE_STABLE; + } + + error("No valid attributes in request"); + +err: + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_set_player_value(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + struct avrcp_player *player = target_get_player(session); + uint16_t len = ntohs(pdu->params_len); + unsigned int i; + uint8_t *param; + + if (len < 3 || len > 2 * pdu->params[0] + 1U || player == NULL) + goto err; + + /* + * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs + * and set the existent ones. Sec. 5.2.4 is not clear however how to + * indicate that a certain ID was not accepted. If at least one + * attribute is valid, we respond with no parameters. Otherwise an + * AVRCP_STATUS_INVALID_PARAM is sent. + */ + for (len = 0, i = 0, param = &pdu->params[1]; i < pdu->params[0]; + i++, param += 2) { + if (player_set_setting(player, param[0], param[1]) < 0) + continue; + + len++; + } + + if (len) { + pdu->params_len = 0; + + return AVC_CTYPE_ACCEPTED; + } + +err: + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_displayable_charset(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + + if (len < 3) { + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; + } + + /* + * We acknowledge the commands, but we always use UTF-8 for + * encoding since CT is obliged to support it. + */ + pdu->params_len = 0; + return AVC_CTYPE_STABLE; +} + +static uint8_t avrcp_handle_ct_battery_status(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + const char *valstr; + + if (len != 1) + goto err; + + valstr = battery_status_to_str(pdu->params[0]); + if (valstr == NULL) + goto err; + + pdu->params_len = 0; + + return AVC_CTYPE_STABLE; + +err: + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint32_t player_get_position(struct avrcp_player *player) +{ + if (player == NULL) + return 0; + + return player->cb->get_position(player->user_data); +} + +static uint32_t player_get_duration(struct avrcp_player *player) +{ + uint32_t num; + + if (player == NULL) + return UINT32_MAX; + + num = player->cb->get_duration(player->user_data); + if (num == 0) + return UINT32_MAX; + + return num; +} + +static uint8_t player_get_status(struct avrcp_player *player) +{ + const char *value; + + if (player == NULL) + return AVRCP_PLAY_STATUS_STOPPED; + + value = player->cb->get_status(player->user_data); + if (value == NULL) + return AVRCP_PLAY_STATUS_STOPPED; + + return play_status_to_val(value); +} + +static uint16_t player_get_id(struct avrcp_player *player) +{ + if (player == NULL) + return 0x0000; + + return player->id; +} + +static uint16_t player_get_uid_counter(struct avrcp_player *player) +{ + if (player == NULL) + return 0x0000; + + return player->uid_counter; +} + +static uint8_t avrcp_handle_get_play_status(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + struct avrcp_player *player = target_get_player(session); + uint16_t len = ntohs(pdu->params_len); + uint32_t position; + uint32_t duration; + + if (len != 0) { + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; + } + + position = player_get_position(player); + duration = player_get_duration(player); + + position = htonl(position); + duration = htonl(duration); + + memcpy(&pdu->params[0], &duration, 4); + memcpy(&pdu->params[4], &position, 4); + pdu->params[8] = player_get_status(player); + + pdu->params_len = htons(9); + + return AVC_CTYPE_STABLE; +} + +static uint64_t player_get_uid(struct avrcp_player *player) +{ + if (player == NULL) + return UINT64_MAX; + + return player->cb->get_uid(player->user_data); +} + +static GList *player_list_settings(struct avrcp_player *player) +{ + if (player == NULL) + return NULL; + + return player->cb->list_settings(player->user_data); +} + +static bool avrcp_handle_play(struct avrcp *session) +{ + struct avrcp_player *player = target_get_player(session); + + if (player == NULL) + return false; + + return player->cb->play(player->user_data); +} + +static bool avrcp_handle_stop(struct avrcp *session) +{ + struct avrcp_player *player = target_get_player(session); + + if (player == NULL) + return false; + + return player->cb->stop(player->user_data); +} + +static bool avrcp_handle_pause(struct avrcp *session) +{ + struct avrcp_player *player = target_get_player(session); + + if (player == NULL) + return false; + + return player->cb->pause(player->user_data); +} + +static bool avrcp_handle_next(struct avrcp *session) +{ + struct avrcp_player *player = target_get_player(session); + + if (player == NULL) + return false; + + return player->cb->next(player->user_data); +} + +static bool avrcp_handle_previous(struct avrcp *session) +{ + struct avrcp_player *player = target_get_player(session); + + if (player == NULL) + return false; + + return player->cb->previous(player->user_data); +} + +static const struct passthrough_handler passthrough_handlers[] = { + { AVC_PLAY, avrcp_handle_play }, + { AVC_STOP, avrcp_handle_stop }, + { AVC_PAUSE, avrcp_handle_pause }, + { AVC_FORWARD, avrcp_handle_next }, + { AVC_BACKWARD, avrcp_handle_previous }, + { }, +}; + +static bool handle_passthrough(struct avctp *conn, uint8_t op, bool pressed, + void *user_data) +{ + struct avrcp *session = user_data; + const struct passthrough_handler *handler; + + for (handler = session->passthrough_handlers; handler->func; + handler++) { + if (handler->op == op) + break; + } + + if (handler->func == NULL) + return false; + + /* Do not trigger handler on release */ + if (!pressed) + return true; + + return handler->func(session); +} + +static uint8_t avrcp_handle_register_notification(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + struct avrcp_player *player = target_get_player(session); + struct btd_device *dev = session->dev; + uint16_t len = ntohs(pdu->params_len); + uint64_t uid; + GList *settings; + + /* + * 1 byte for EventID, 4 bytes for Playback interval but the latest + * one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP + * 1.3 spec, section 5.4.2. + */ + if (len != 5) + goto err; + + /* Check if event is supported otherwise reject */ + if (!(session->supported_events & (1 << pdu->params[0]))) + goto err; + + switch (pdu->params[0]) { + case AVRCP_EVENT_STATUS_CHANGED: + len = 2; + pdu->params[1] = player_get_status(player); + + break; + case AVRCP_EVENT_TRACK_CHANGED: + len = 9; + uid = player_get_uid(player); + memcpy(&pdu->params[1], &uid, sizeof(uint64_t)); + + break; + case AVRCP_EVENT_TRACK_REACHED_END: + case AVRCP_EVENT_TRACK_REACHED_START: + len = 1; + break; + case AVRCP_EVENT_SETTINGS_CHANGED: + len = 1; + settings = player_list_settings(player); + + pdu->params[len++] = g_list_length(settings); + for (; settings; settings = settings->next) { + const char *key = settings->data; + int attr; + int val; + + attr = attr_to_val(key); + if (attr < 0) + continue; + + val = player_get_setting(player, attr); + if (val < 0) + continue; + + pdu->params[len++] = attr; + pdu->params[len++] = val; + } + + g_list_free(settings); + + break; + case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: + len = 5; + bt_put_be16(player_get_id(player), &pdu->params[1]); + bt_put_be16(player_get_uid_counter(player), &pdu->params[3]); + break; + case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: + len = 1; + break; + case AVRCP_EVENT_VOLUME_CHANGED: + pdu->params[1] = media_transport_get_device_volume(dev); + if (pdu->params[1] > 127) + goto err; + + len = 2; + + break; + default: + /* All other events are not supported yet */ + goto err; + } + + /* Register event and save the transaction used */ + session->registered_events |= (1 << pdu->params[0]); + session->transaction_events[pdu->params[0]] = transaction; + + pdu->params_len = htons(len); + + return AVC_CTYPE_INTERIM; + +err: + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_request_continuing(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + struct avrcp_player *player = target_get_player(session); + uint16_t len = ntohs(pdu->params_len); + struct pending_pdu *pending; + + if (len != 1 || session->pending_pdu == NULL) + goto err; + + pending = session->pending_pdu; + + if (pending->pdu_id != pdu->params[0]) + goto err; + + + len = 0; + pending->attr_ids = player_fill_media_attribute(player, + pending->attr_ids, + pdu->params, &len, + &pending->offset); + pdu->pdu_id = pending->pdu_id; + + if (pending->attr_ids == NULL) { + g_free(session->pending_pdu); + session->pending_pdu = NULL; + pdu->packet_type = AVRCP_PACKET_TYPE_END; + } else { + pdu->packet_type = AVRCP_PACKET_TYPE_CONTINUING; + } + + pdu->params_len = htons(len); + + return AVC_CTYPE_STABLE; +err: + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_abort_continuing(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + struct pending_pdu *pending; + + if (len != 1 || session->pending_pdu == NULL) + goto err; + + pending = session->pending_pdu; + + if (pending->pdu_id != pdu->params[0]) + goto err; + + session_abort_pending_pdu(session); + pdu->params_len = 0; + + return AVC_CTYPE_ACCEPTED; + +err: + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_set_absolute_volume(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + uint8_t volume; + + if (len != 1) + goto err; + + volume = pdu->params[0] & 0x7F; + + media_transport_update_device_volume(session->dev, volume); + + return AVC_CTYPE_ACCEPTED; + +err: + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static struct avrcp_player *find_tg_player(struct avrcp *session, uint16_t id) +{ + struct avrcp_server *server = session->server; + GSList *l; + + for (l = server->players; l; l = l->next) { + struct avrcp_player *player = l->data; + + if (player->id == id) + return player; + } + + return NULL; +} + +static gboolean notify_addressed_player_changed(gpointer user_data) +{ + struct avrcp_player *player = user_data; + uint8_t events[6] = { AVRCP_EVENT_STATUS_CHANGED, + AVRCP_EVENT_TRACK_CHANGED, + AVRCP_EVENT_TRACK_REACHED_START, + AVRCP_EVENT_TRACK_REACHED_END, + AVRCP_EVENT_SETTINGS_CHANGED, + AVRCP_EVENT_PLAYBACK_POS_CHANGED + }; + uint8_t i; + + avrcp_player_event(player, AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED, NULL); + + /* + * TG shall complete all player specific + * notifications with AV/C C-Type REJECTED + * with error code as Addressed Player Changed. + */ + for (i = 0; i < sizeof(events); i++) + avrcp_player_event(player, events[i], NULL); + + player->changed_id = 0; + + return FALSE; +} + +static uint8_t avrcp_handle_set_addressed_player(struct avrcp *session, + struct avrcp_header *pdu, + uint8_t transaction) +{ + struct avrcp_player *player; + uint16_t len = ntohs(pdu->params_len); + uint16_t player_id = 0; + uint8_t status; + + if (len < 1) { + status = AVRCP_STATUS_INVALID_PARAM; + goto err; + } + + player_id = bt_get_be16(&pdu->params[0]); + player = find_tg_player(session, player_id); + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + + if (player) { + player->addressed = true; + status = AVRCP_STATUS_SUCCESS; + pdu->params_len = htons(len); + pdu->params[0] = status; + } else { + status = AVRCP_STATUS_INVALID_PLAYER_ID; + goto err; + } + + /* Don't emit player changed immediately since PTS expect the + * response of SetAddressedPlayer before the event. + */ + player->changed_id = g_idle_add(notify_addressed_player_changed, + player); + + return AVC_CTYPE_ACCEPTED; + +err: + pdu->params_len = htons(sizeof(status)); + pdu->params[0] = status; + return AVC_CTYPE_REJECTED; +} + +static const struct control_pdu_handler control_handlers[] = { + { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS, + avrcp_handle_get_capabilities }, + { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS, + avrcp_handle_list_player_attributes }, + { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS, + avrcp_handle_list_player_values }, + { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS, + avrcp_handle_get_element_attributes }, + { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS, + avrcp_handle_get_current_player_value }, + { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL, + avrcp_handle_set_player_value }, + { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS, + NULL }, + { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS, + NULL }, + { AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS, + avrcp_handle_displayable_charset }, + { AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS, + avrcp_handle_ct_battery_status }, + { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS, + avrcp_handle_get_play_status }, + { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY, + avrcp_handle_register_notification }, + { AVRCP_SET_ABSOLUTE_VOLUME, AVC_CTYPE_CONTROL, + avrcp_handle_set_absolute_volume }, + { AVRCP_REQUEST_CONTINUING, AVC_CTYPE_CONTROL, + avrcp_handle_request_continuing }, + { AVRCP_ABORT_CONTINUING, AVC_CTYPE_CONTROL, + avrcp_handle_abort_continuing }, + { AVRCP_SET_ADDRESSED_PLAYER, AVC_CTYPE_CONTROL, + avrcp_handle_set_addressed_player }, + { }, +}; + +/* handle vendordep pdu inside an avctp packet */ +static size_t handle_vendordep_pdu(struct avctp *conn, uint8_t transaction, + uint8_t *code, uint8_t *subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + const struct control_pdu_handler *handler; + struct avrcp_header *pdu = (void *) operands; + uint32_t company_id = get_company_id(pdu->company_id); + + if (company_id != IEEEID_BTSIG) { + *code = AVC_CTYPE_NOT_IMPLEMENTED; + return 0; + } + + DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X", + pdu->pdu_id, company_id, ntohs(pdu->params_len)); + + pdu->packet_type = 0; + pdu->rsvd = 0; + + if (operand_count < AVRCP_HEADER_LENGTH) { + pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; + goto err_metadata; + } + + for (handler = session->control_handlers; handler->pdu_id; handler++) { + if (handler->pdu_id == pdu->pdu_id) + break; + } + + if (handler->pdu_id != pdu->pdu_id || handler->code != *code) { + pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; + goto err_metadata; + } + + if (!handler->func) { + pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; + goto err_metadata; + } + + *code = handler->func(session, pdu, transaction); + + if (*code != AVC_CTYPE_REJECTED && + pdu->pdu_id != AVRCP_GET_ELEMENT_ATTRIBUTES && + pdu->pdu_id != AVRCP_REQUEST_CONTINUING && + pdu->pdu_id != AVRCP_ABORT_CONTINUING) + session_abort_pending_pdu(session); + + return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); + +err_metadata: + pdu->params_len = htons(1); + *code = AVC_CTYPE_REJECTED; + + return AVRCP_HEADER_LENGTH + 1; +} + +static void avrcp_handle_media_player_list(struct avrcp *session, + struct avrcp_browsing_header *pdu, + uint32_t start_item, uint32_t end_item) +{ + struct avrcp_player *player = session->target->player; + struct get_folder_items_rsp *rsp; + const char *name = NULL; + GSList *l; + + rsp = (void *)pdu->params; + rsp->status = AVRCP_STATUS_SUCCESS; + rsp->uid_counter = htons(player_get_uid_counter(player)); + rsp->num_items = 0; + pdu->param_len = sizeof(*rsp); + + for (l = g_slist_nth(session->server->players, start_item); + l; l = g_slist_next(l)) { + struct avrcp_player *player = l->data; + struct folder_item *folder; + struct player_item *item; + uint16_t namelen; + + if (rsp->num_items == (end_item - start_item) + 1) + break; + + folder = (void *)&pdu->params[pdu->param_len]; + folder->type = 0x01; /* Media Player */ + + pdu->param_len += sizeof(*folder); + + item = (void *)folder->data; + item->player_id = htons(player->id); + item->type = 0x01; /* Audio */ + item->subtype = htonl(0x01); /* Audio Book */ + item->status = player_get_status(player); + /* Assign Default Feature Bit Mask */ + memcpy(&item->features, &features, sizeof(features)); + + item->charset = htons(AVRCP_CHARSET_UTF8); + + name = player->cb->get_name(player->user_data); + namelen = strlen(name); + item->namelen = htons(namelen); + memcpy(item->name, name, namelen); + + folder->len = htons(sizeof(*item) + namelen); + pdu->param_len += sizeof(*item) + namelen; + rsp->num_items++; + } + + /* If no player could be found respond with an error */ + if (!rsp->num_items) + goto failed; + + rsp->num_items = htons(rsp->num_items); + pdu->param_len = htons(pdu->param_len); + + return; + +failed: + pdu->params[0] = AVRCP_STATUS_OUT_OF_BOUNDS; + pdu->param_len = htons(1); +} + +static void avrcp_handle_get_folder_items(struct avrcp *session, + struct avrcp_browsing_header *pdu, + uint8_t transaction) +{ + uint32_t start_item = 0; + uint32_t end_item = 0; + uint8_t scope; + uint8_t status = AVRCP_STATUS_SUCCESS; + + if (ntohs(pdu->param_len) < 10) { + status = AVRCP_STATUS_INVALID_PARAM; + goto failed; + } + + scope = pdu->params[0]; + start_item = bt_get_be32(&pdu->params[1]); + end_item = bt_get_be32(&pdu->params[5]); + + DBG("scope 0x%02x start_item 0x%08x end_item 0x%08x", scope, + start_item, end_item); + + if (end_item < start_item) { + status = AVRCP_STATUS_INVALID_PARAM; + goto failed; + } + + switch (scope) { + case AVRCP_SCOPE_MEDIA_PLAYER_LIST: + avrcp_handle_media_player_list(session, pdu, + start_item, end_item); + break; + case AVRCP_SCOPE_MEDIA_PLAYER_VFS: + case AVRCP_SCOPE_SEARCH: + case AVRCP_SCOPE_NOW_PLAYING: + default: + status = AVRCP_STATUS_INVALID_PARAM; + goto failed; + } + + return; + +failed: + pdu->params[0] = status; + pdu->param_len = htons(1); +} + +static struct browsing_pdu_handler { + uint8_t pdu_id; + void (*func) (struct avrcp *session, struct avrcp_browsing_header *pdu, + uint8_t transaction); +} browsing_handlers[] = { + { AVRCP_GET_FOLDER_ITEMS, avrcp_handle_get_folder_items }, + { }, +}; + +size_t avrcp_browsing_general_reject(uint8_t *operands) +{ + struct avrcp_browsing_header *pdu = (void *) operands; + uint8_t status; + + pdu->pdu_id = AVRCP_GENERAL_REJECT; + status = AVRCP_STATUS_INVALID_COMMAND; + + pdu->param_len = htons(sizeof(status)); + memcpy(pdu->params, &status, (sizeof(status))); + return AVRCP_BROWSING_HEADER_LENGTH + sizeof(status); +} + +static size_t handle_browsing_pdu(struct avctp *conn, + uint8_t transaction, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avrcp *session = user_data; + struct browsing_pdu_handler *handler; + struct avrcp_browsing_header *pdu = (void *) operands; + + DBG("AVRCP Browsing PDU 0x%02X, len 0x%04X", pdu->pdu_id, + ntohs(pdu->param_len)); + + for (handler = browsing_handlers; handler->pdu_id; handler++) { + if (handler->pdu_id == pdu->pdu_id) + goto done; + } + + return avrcp_browsing_general_reject(operands); + +done: + session->transaction = transaction; + handler->func(session, pdu, transaction); + return AVRCP_BROWSING_HEADER_LENGTH + ntohs(pdu->param_len); +} + +size_t avrcp_handle_vendor_reject(uint8_t *code, uint8_t *operands) +{ + struct avrcp_header *pdu = (void *) operands; + uint32_t company_id = get_company_id(pdu->company_id); + + *code = AVC_CTYPE_REJECTED; + pdu->params_len = htons(1); + pdu->params[0] = AVRCP_STATUS_INTERNAL_ERROR; + + DBG("rejecting AVRCP PDU 0x%02X, company 0x%06X len 0x%04X", + pdu->pdu_id, company_id, ntohs(pdu->params_len)); + + return AVRCP_HEADER_LENGTH + 1; +} + +static struct avrcp_server *find_server(GSList *list, struct btd_adapter *a) +{ + for (; list; list = list->next) { + struct avrcp_server *server = list->data; + + if (server->adapter == a) + return server; + } + + return NULL; +} + +static const char *status_to_string(uint8_t status) +{ + switch (status) { + case AVRCP_PLAY_STATUS_STOPPED: + return "stopped"; + case AVRCP_PLAY_STATUS_PLAYING: + return "playing"; + case AVRCP_PLAY_STATUS_PAUSED: + return "paused"; + case AVRCP_PLAY_STATUS_FWD_SEEK: + return "forward-seek"; + case AVRCP_PLAY_STATUS_REV_SEEK: + return "reverse-seek"; + case AVRCP_PLAY_STATUS_ERROR: + return "error"; + default: + return NULL; + } +} + +static gboolean avrcp_get_play_status_rsp(struct avctp *conn, uint8_t code, + uint8_t subunit, uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + struct avrcp_header *pdu = (void *) operands; + uint32_t duration; + uint32_t position; + uint8_t status; + + if (pdu == NULL || code == AVC_CTYPE_REJECTED || + ntohs(pdu->params_len) != 9) + return FALSE; + + memcpy(&duration, pdu->params, sizeof(uint32_t)); + duration = ntohl(duration); + media_player_set_duration(mp, duration); + + memcpy(&position, pdu->params + 4, sizeof(uint32_t)); + position = ntohl(position); + media_player_set_position(mp, position); + + memcpy(&status, pdu->params + 8, sizeof(uint8_t)); + media_player_set_status(mp, status_to_string(status)); + + return FALSE; +} + +static void avrcp_get_play_status(struct avrcp *session) +{ + uint8_t buf[AVRCP_HEADER_LENGTH]; + struct avrcp_header *pdu = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_GET_PLAY_STATUS; + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS, + AVC_SUBUNIT_PANEL, buf, sizeof(buf), + avrcp_get_play_status_rsp, + session); +} + +static const char *status_to_str(uint8_t status) +{ + switch (status) { + case AVRCP_STATUS_INVALID_COMMAND: + return "Invalid Command"; + case AVRCP_STATUS_INVALID_PARAM: + return "Invalid Parameter"; + case AVRCP_STATUS_INTERNAL_ERROR: + return "Internal Error"; + case AVRCP_STATUS_SUCCESS: + return "Success"; + default: + return "Unknown"; + } +} + +static gboolean avrcp_player_value_rsp(struct avctp *conn, uint8_t code, + uint8_t subunit, uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + struct avrcp_header *pdu = (void *) operands; + uint8_t count; + int i; + + if (pdu == NULL) { + media_player_set_setting(mp, "Error", "Timeout"); + return FALSE; + } + + if (code == AVC_CTYPE_REJECTED) { + media_player_set_setting(mp, "Error", + status_to_str(pdu->params[0])); + return FALSE; + } + + count = pdu->params[0]; + + if (pdu->params_len < count * 2) + return FALSE; + + for (i = 1; count > 0; count--, i += 2) { + const char *key; + const char *value; + + key = attr_to_str(pdu->params[i]); + if (key == NULL) + continue; + + value = attrval_to_str(pdu->params[i], pdu->params[i + 1]); + if (value == NULL) + continue; + + media_player_set_setting(mp, key, value); + } + + return FALSE; +} + +static void avrcp_get_current_player_value(struct avrcp *session, + uint8_t *attrs, uint8_t count) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_ATTRIBUTE_LAST + 1]; + struct avrcp_header *pdu = (void *) buf; + uint16_t length = AVRCP_HEADER_LENGTH + count + 1; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_GET_CURRENT_PLAYER_VALUE; + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + pdu->params_len = htons(count + 1); + pdu->params[0] = count; + + memcpy(pdu->params + 1, attrs, count); + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS, + AVC_SUBUNIT_PANEL, buf, length, + avrcp_player_value_rsp, session); +} + +static gboolean avrcp_list_player_attributes_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t transaction, uint8_t *operands, + size_t operand_count, void *user_data) +{ + uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; + struct avrcp *session = user_data; + struct avrcp_header *pdu = (void *) operands; + uint8_t len, count = 0; + int i; + + if (code == AVC_CTYPE_REJECTED) + return FALSE; + + len = pdu->params[0]; + + if (ntohs(pdu->params_len) < count) { + error("Invalid parameters"); + return FALSE; + } + + for (i = 0; len > 0; len--, i++) { + /* Don't query invalid attributes */ + if (pdu->params[i + 1] == AVRCP_ATTRIBUTE_ILEGAL || + pdu->params[i + 1] > AVRCP_ATTRIBUTE_LAST) + continue; + + attrs[count++] = pdu->params[i + 1]; + } + + avrcp_get_current_player_value(session, attrs, count); + + return FALSE; +} + +static void avrcp_list_player_attributes(struct avrcp *session) +{ + uint8_t buf[AVRCP_HEADER_LENGTH]; + struct avrcp_header *pdu = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_LIST_PLAYER_ATTRIBUTES; + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS, + AVC_SUBUNIT_PANEL, buf, sizeof(buf), + avrcp_list_player_attributes_rsp, + session); +} + +static void avrcp_parse_attribute_list(struct avrcp_player *player, + uint8_t *operands, uint8_t count) +{ + struct media_player *mp = player->user_data; + struct media_item *item; + int i; + + item = media_player_set_playlist_item(mp, player->uid); + + for (i = 0; count > 0; count--) { + uint32_t id; + uint16_t charset, len; + + id = get_be32(&operands[i]); + i += sizeof(uint32_t); + + charset = get_be16(&operands[i]); + i += sizeof(uint16_t); + + len = get_be16(&operands[i]); + i += sizeof(uint16_t); + + if (charset == 106) { + const char *key = metadata_to_str(id); + + if (key != NULL) + media_player_set_metadata(mp, item, + metadata_to_str(id), + &operands[i], len); + } + + i += len; + } +} + +static gboolean avrcp_get_element_attributes_rsp(struct avctp *conn, + uint8_t code, uint8_t subunit, + uint8_t transaction, + uint8_t *operands, + size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->controller->player; + struct avrcp_header *pdu = (void *) operands; + uint8_t count; + + if (code == AVC_CTYPE_REJECTED) + return FALSE; + + count = pdu->params[0]; + + if (ntohs(pdu->params_len) - 1 < count * 8) { + error("Invalid parameters"); + return FALSE; + } + + avrcp_parse_attribute_list(player, &pdu->params[1], count); + + avrcp_get_play_status(session); + + return FALSE; +} + +static void avrcp_get_element_attributes(struct avrcp *session) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + 9]; + struct avrcp_header *pdu = (void *) buf; + uint16_t length; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_GET_ELEMENT_ATTRIBUTES; + pdu->params_len = htons(9); + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + + length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS, + AVC_SUBUNIT_PANEL, buf, length, + avrcp_get_element_attributes_rsp, + session); +} + +static const char *type_to_string(uint8_t type) +{ + switch (type & 0x0F) { + case 0x01: + return "Audio"; + case 0x02: + return "Video"; + case 0x03: + return "Audio, Video"; + case 0x04: + return "Audio Broadcasting"; + case 0x05: + return "Audio, Audio Broadcasting"; + case 0x06: + return "Video, Audio Broadcasting"; + case 0x07: + return "Audio, Video, Audio Broadcasting"; + case 0x08: + return "Video Broadcasting"; + case 0x09: + return "Audio, Video Broadcasting"; + case 0x0A: + return "Video, Video Broadcasting"; + case 0x0B: + return "Audio, Video, Video Broadcasting"; + case 0x0C: + return "Audio Broadcasting, Video Broadcasting"; + case 0x0D: + return "Audio, Audio Broadcasting, Video Broadcasting"; + case 0x0E: + return "Video, Audio Broadcasting, Video Broadcasting"; + case 0x0F: + return "Audio, Video, Audio Broadcasting, Video Broadcasting"; + } + + return "None"; +} + +static const char *subtype_to_string(uint32_t subtype) +{ + switch (subtype & 0x03) { + case 0x01: + return "Audio Book"; + case 0x02: + return "Podcast"; + case 0x03: + return "Audio Book, Podcast"; + } + + return "None"; +} + +static struct media_item *parse_media_element(struct avrcp *session, + uint8_t *operands, uint16_t len) +{ + struct avrcp_player *player; + struct media_player *mp; + struct media_item *item; + uint16_t namelen; + char name[255]; + uint64_t uid; + + if (len < 13) + return NULL; + + uid = get_be64(&operands[0]); + + namelen = MIN(get_be16(&operands[11]), sizeof(name) - 1); + if (namelen > 0) { + memcpy(name, &operands[13], namelen); + name[namelen] = '\0'; + } + + player = session->controller->player; + mp = player->user_data; + + item = media_player_create_item(mp, name, PLAYER_ITEM_TYPE_AUDIO, uid); + if (item == NULL) + return NULL; + + media_item_set_playable(item, true); + + return item; +} + +static struct media_item *parse_media_folder(struct avrcp *session, + uint8_t *operands, uint16_t len) +{ + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + struct media_item *item; + uint16_t namelen; + char name[255]; + uint64_t uid; + uint8_t type; + uint8_t playable; + + if (len < 12) + return NULL; + + uid = get_be64(&operands[0]); + type = operands[8]; + playable = operands[9]; + + namelen = MIN(get_be16(&operands[12]), sizeof(name) - 1); + if (namelen > 0) { + memcpy(name, &operands[14], namelen); + name[namelen] = '\0'; + } + + item = media_player_create_folder(mp, name, type, uid); + if (!item) + return NULL; + + media_item_set_playable(item, playable & 0x01); + + return item; +} + +static void avrcp_list_items(struct avrcp *session, uint32_t start, + uint32_t end); +static gboolean avrcp_list_items_rsp(struct avctp *conn, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avrcp_browsing_header *pdu = (void *) operands; + struct avrcp *session = user_data; + struct avrcp_player *player = session->controller->player; + struct pending_list_items *p = player->p; + uint16_t count; + uint64_t items; + size_t i; + int err = 0; + + if (pdu == NULL) { + err = -ETIMEDOUT; + goto done; + } + + /* AVRCP 1.5 - Page 76: + * If the TG receives a GetFolderItems command for an empty folder then + * the TG shall return the error (= Range Out of Bounds) in the status + * field of the GetFolderItems response. + */ + if (pdu->params[0] == AVRCP_STATUS_OUT_OF_BOUNDS) + goto done; + + if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 5) { + err = -EINVAL; + goto done; + } + + count = get_be16(&operands[6]); + if (count == 0) + goto done; + + for (i = 8; count && i + 3 < operand_count; count--) { + struct media_item *item; + uint8_t type; + uint16_t len; + + type = operands[i++]; + len = get_be16(&operands[i]); + i += 2; + + if (type != 0x03 && type != 0x02) { + i += len; + continue; + } + + if (i + len > operand_count) { + error("Invalid item length"); + break; + } + + if (type == 0x03) + item = parse_media_element(session, &operands[i], len); + else + item = parse_media_folder(session, &operands[i], len); + + if (item) + p->items = g_slist_append(p->items, item); + + i += len; + } + + items = g_slist_length(p->items); + + DBG("start %u end %u items %" PRIu64 " total %" PRIu64 "", p->start, + p->end, items, p->total); + + if (items < p->total) { + avrcp_list_items(session, p->start + items, p->end); + return FALSE; + } + +done: + media_player_list_complete(player->user_data, p->items, err); + + g_slist_free(p->items); + g_free(p); + player->p = NULL; + + return FALSE; +} + +static void avrcp_list_items(struct avrcp *session, uint32_t start, + uint32_t end) +{ + uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 10 + + AVRCP_MEDIA_ATTRIBUTE_LAST * sizeof(uint32_t)]; + struct avrcp_player *player = session->controller->player; + struct avrcp_browsing_header *pdu = (void *) buf; + uint16_t length = AVRCP_BROWSING_HEADER_LENGTH + 10; + uint32_t attribute; + + memset(buf, 0, sizeof(buf)); + + pdu->pdu_id = AVRCP_GET_FOLDER_ITEMS; + pdu->param_len = htons(10 + sizeof(uint32_t)); + + pdu->params[0] = player->scope; + + put_be32(start, &pdu->params[1]); + put_be32(end, &pdu->params[5]); + + pdu->params[9] = 1; + + /* Only the title (0x01) is mandatory. This can be extended to + * support AVRCP_MEDIA_ATTRIBUTE_* attributes */ + attribute = htonl(AVRCP_MEDIA_ATTRIBUTE_TITLE); + memcpy(&pdu->params[10], &attribute, sizeof(uint32_t)); + + length += sizeof(uint32_t); + + avctp_send_browsing_req(session->conn, buf, length, + avrcp_list_items_rsp, session); +} + +static gboolean avrcp_change_path_rsp(struct avctp *conn, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp_browsing_header *pdu = (void *) operands; + struct avrcp *session = user_data; + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + int ret; + + if (pdu == NULL) { + ret = -ETIMEDOUT; + goto done; + } + + if (pdu->params[0] != AVRCP_STATUS_SUCCESS) { + ret = -EINVAL; + goto done; + } + + ret = get_be32(&pdu->params[1]); + +done: + if (ret < 0) { + g_free(player->change_path); + player->change_path = NULL; + } else { + g_free(player->path); + player->path = player->change_path; + player->change_path = NULL; + } + + media_player_change_folder_complete(mp, player->path, + player->change_uid, ret); + + player->change_uid = 0; + + return FALSE; +} + +static gboolean avrcp_set_browsed_player_rsp(struct avctp *conn, + uint8_t *operands, + size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + struct avrcp_browsing_header *pdu = (void *) operands; + uint32_t items; + char **folders; + uint8_t depth, count; + size_t i; + + if (pdu == NULL || pdu->params[0] != AVRCP_STATUS_SUCCESS || + operand_count < 13) + return FALSE; + + player->uid_counter = get_be16(&pdu->params[1]); + player->browsed = true; + + items = get_be32(&pdu->params[3]); + + depth = pdu->params[9]; + + folders = g_new0(char *, depth + 2); + folders[0] = g_strdup("/Filesystem"); + + for (i = 10, count = 1; count - 1 < depth && i < operand_count; + count++) { + uint8_t len; + + len = pdu->params[i++]; + if (!len) + continue; + + if (i + len > operand_count) { + error("Invalid folder length"); + break; + } + + folders[count] = g_memdup(&pdu->params[i], len); + i += len; + } + + player->path = g_build_pathv("/", folders); + g_strfreev(folders); + + media_player_set_folder(mp, player->path, items); + + return FALSE; +} + +static void avrcp_set_browsed_player(struct avrcp *session, + struct avrcp_player *player) +{ + uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 2]; + struct avrcp_browsing_header *pdu = (void *) buf; + uint16_t id; + + memset(buf, 0, sizeof(buf)); + + pdu->pdu_id = AVRCP_SET_BROWSED_PLAYER; + id = htons(player->id); + memcpy(pdu->params, &id, 2); + pdu->param_len = htons(2); + + avctp_send_browsing_req(session->conn, buf, sizeof(buf), + avrcp_set_browsed_player_rsp, session); +} + +static gboolean avrcp_get_item_attributes_rsp(struct avctp *conn, + uint8_t *operands, + size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->controller->player; + struct avrcp_browsing_header *pdu = (void *) operands; + uint8_t count; + + if (pdu == NULL) { + avrcp_get_element_attributes(session); + return FALSE; + } + + if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 4) { + avrcp_get_element_attributes(session); + return FALSE; + } + + count = pdu->params[1]; + + if (ntohs(pdu->param_len) - 1 < count * 8) { + error("Invalid parameters"); + return FALSE; + } + + avrcp_parse_attribute_list(player, &pdu->params[2], count); + + avrcp_get_play_status(session); + + return FALSE; +} + +static void avrcp_get_item_attributes(struct avrcp *session, uint64_t uid) +{ + struct avrcp_player *player = session->controller->player; + uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 12]; + struct avrcp_browsing_header *pdu = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + pdu->pdu_id = AVRCP_GET_ITEM_ATTRIBUTES; + pdu->params[0] = 0x03; + put_be64(uid, &pdu->params[1]); + put_be16(player->uid_counter, &pdu->params[9]); + pdu->param_len = htons(12); + + avctp_send_browsing_req(session->conn, buf, sizeof(buf), + avrcp_get_item_attributes_rsp, session); +} + +static void avrcp_player_parse_features(struct avrcp_player *player, + uint8_t *features) +{ + struct media_player *mp = player->user_data; + + player->features = g_memdup(features, 16); + + if (features[7] & 0x08) { + media_player_set_browsable(mp, true); + media_player_create_folder(mp, "/Filesystem", + PLAYER_FOLDER_TYPE_MIXED, 0); + } + + if (features[7] & 0x10) + media_player_set_searchable(mp, true); + + if (features[8] & 0x02) { + media_player_create_folder(mp, "/NowPlaying", + PLAYER_FOLDER_TYPE_MIXED, 0); + media_player_set_playlist(mp, "/NowPlaying"); + } +} + +static void avrcp_set_player_value(struct avrcp *session, uint8_t attr, + uint8_t val) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + 3]; + struct avrcp_header *pdu = (void *) buf; + uint8_t length; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_SET_PLAYER_VALUE; + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + pdu->params[0] = 1; + pdu->params[1] = attr; + pdu->params[2] = val; + pdu->params_len = htons(3); + + length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, + AVC_SUBUNIT_PANEL, buf, length, + avrcp_player_value_rsp, session); +} + +static gboolean avrcp_set_addressed_player_rsp(struct avctp *conn, uint8_t code, + uint8_t subunit, uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = session->controller->player; + struct avrcp_header *pdu = (void *) operands; + + if (!pdu || code != AVC_CTYPE_ACCEPTED) + return FALSE; + + player->addressed = true; + + return FALSE; +} + +static void avrcp_set_addressed_player(struct avrcp *session, + struct avrcp_player *player) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + 2]; + struct avrcp_header *pdu = (void *) buf; + uint16_t id; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_SET_ADDRESSED_PLAYER; + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + id = htons(player->id); + memcpy(pdu->params, &id, 2); + pdu->params_len = htons(2); + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, + AVC_SUBUNIT_PANEL, buf, sizeof(buf), + avrcp_set_addressed_player_rsp, + session); +} + +static void set_addressed_player(struct avrcp *session, + struct avrcp_player *player) +{ + if (!player || !player->id || player->addressed || + session->controller->version < 0x0104) + return; + + /* Set player as addressed */ + avrcp_set_addressed_player(session, player); +} + +static void set_browsed_player(struct avrcp *session, + struct avrcp_player *player) +{ + if (!player || !player->id || player->browsed) + return; + + if (media_player_get_browsable(player->user_data)) + avrcp_set_browsed_player(session, player); +} + +static void set_ct_player(struct avrcp *session, struct avrcp_player *player) +{ + struct btd_service *service; + + if (session->controller->player == player) + goto done; + + session->controller->player = player; + service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID); + control_set_player(service, player ? + media_player_get_path(player->user_data) : NULL); + +done: + set_addressed_player(session, player); + set_browsed_player(session, player); +} + +static bool ct_set_setting(struct media_player *mp, const char *key, + const char *value, void *user_data) +{ + struct avrcp_player *player = user_data; + int attr; + int val; + struct avrcp *session; + + session = player->sessions->data; + if (session == NULL) + return false; + + if (session->controller->version < 0x0103) + return false; + + set_ct_player(session, player); + + attr = attr_to_val(key); + if (attr < 0) + return false; + + val = attrval_to_val(attr, value); + if (val < 0) + return false; + + avrcp_set_player_value(session, attr, val); + + return true; +} + +static int ct_press(struct avrcp_player *player, uint8_t op) +{ + int err; + struct avrcp *session; + + session = player->sessions->data; + if (session == NULL) + return -ENOTCONN; + + set_ct_player(session, player); + + err = avctp_send_passthrough(session->conn, op); + if (err < 0) + return err; + + return 0; +} + +static int ct_play(struct media_player *mp, void *user_data) +{ + struct avrcp_player *player = user_data; + + return ct_press(player, AVC_PLAY); +} + +static int ct_pause(struct media_player *mp, void *user_data) +{ + struct avrcp_player *player = user_data; + + return ct_press(player, AVC_PAUSE); +} + +static int ct_stop(struct media_player *mp, void *user_data) +{ + struct avrcp_player *player = user_data; + + return ct_press(player, AVC_STOP); +} + +static int ct_next(struct media_player *mp, void *user_data) +{ + struct avrcp_player *player = user_data; + + return ct_press(player, AVC_FORWARD); +} + +static int ct_previous(struct media_player *mp, void *user_data) +{ + struct avrcp_player *player = user_data; + + return ct_press(player, AVC_BACKWARD); +} + +static int ct_fast_forward(struct media_player *mp, void *user_data) +{ + struct avrcp_player *player = user_data; + + return ct_press(player, AVC_FAST_FORWARD); +} + +static int ct_rewind(struct media_player *mp, void *user_data) +{ + struct avrcp_player *player = user_data; + + return ct_press(player, AVC_REWIND); +} + +static int ct_list_items(struct media_player *mp, const char *name, + uint32_t start, uint32_t end, void *user_data) +{ + struct avrcp_player *player = user_data; + struct avrcp *session; + struct pending_list_items *p; + + if (player->p != NULL) + return -EBUSY; + + session = player->sessions->data; + + set_ct_player(session, player); + + if (g_str_has_prefix(name, "/NowPlaying")) + player->scope = 0x03; + else if (g_str_has_suffix(name, "/search")) + player->scope = 0x02; + else + player->scope = 0x01; + + avrcp_list_items(session, start, end); + + p = g_new0(struct pending_list_items, 1); + p->start = start; + p->end = end; + p->total = (uint64_t) (p->end - p->start) + 1; + player->p = p; + + return 0; +} + +static void avrcp_change_path(struct avrcp *session, uint8_t direction, + uint64_t uid) +{ + struct avrcp_player *player = session->controller->player; + uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 11]; + struct avrcp_browsing_header *pdu = (void *) buf; + + memset(buf, 0, sizeof(buf)); + put_be16(player->uid_counter, &pdu->params[0]); + pdu->params[2] = direction; + put_be64(uid, &pdu->params[3]); + pdu->pdu_id = AVRCP_CHANGE_PATH; + pdu->param_len = htons(11); + + avctp_send_browsing_req(session->conn, buf, sizeof(buf), + avrcp_change_path_rsp, session); +} + +static int ct_change_folder(struct media_player *mp, const char *path, + uint64_t uid, void *user_data) +{ + struct avrcp_player *player = user_data; + struct avrcp *session; + uint8_t direction; + + session = player->sessions->data; + set_ct_player(session, player); + player->change_path = g_strdup(path); + player->change_uid = uid; + + direction = g_str_has_prefix(path, player->path) ? 0x01 : 0x00; + + avrcp_change_path(session, direction, uid); + + return 0; +} + +static gboolean avrcp_search_rsp(struct avctp *conn, uint8_t *operands, + size_t operand_count, void *user_data) +{ + struct avrcp_browsing_header *pdu = (void *) operands; + struct avrcp *session = (void *) user_data; + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + int ret; + + if (pdu == NULL) { + ret = -ETIMEDOUT; + goto done; + } + + if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 7) { + ret = -EINVAL; + goto done; + } + + player->uid_counter = get_be16(&pdu->params[1]); + ret = get_be32(&pdu->params[3]); + +done: + media_player_search_complete(mp, ret); + + return FALSE; +} + +static void avrcp_search(struct avrcp *session, const char *string) +{ + uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 255]; + struct avrcp_browsing_header *pdu = (void *) buf; + uint16_t len, stringlen; + + memset(buf, 0, sizeof(buf)); + len = AVRCP_BROWSING_HEADER_LENGTH + 4; + stringlen = strnlen(string, sizeof(buf) - len); + len += stringlen; + + put_be16(AVRCP_CHARSET_UTF8, &pdu->params[0]); + put_be16(stringlen, &pdu->params[2]); + memcpy(&pdu->params[4], string, stringlen); + pdu->pdu_id = AVRCP_SEARCH; + pdu->param_len = htons(len - AVRCP_BROWSING_HEADER_LENGTH); + + avctp_send_browsing_req(session->conn, buf, len, avrcp_search_rsp, + session); +} + +static int ct_search(struct media_player *mp, const char *string, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct avrcp *session; + + session = player->sessions->data; + + set_ct_player(session, player); + avrcp_search(session, string); + + return 0; +} + +static gboolean avrcp_play_item_rsp(struct avctp *conn, uint8_t code, + uint8_t subunit, uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp_header *pdu = (void *) operands; + struct avrcp *session = (void *) user_data; + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + int ret = 0; + + if (pdu == NULL) { + ret = -ETIMEDOUT; + goto done; + } + + if (pdu->params[0] != AVRCP_STATUS_SUCCESS) { + switch (pdu->params[0]) { + case AVRCP_STATUS_UID_CHANGED: + case AVRCP_STATUS_DOES_NOT_EXIST: + ret = -ENOENT; + break; + default: + ret = -EINVAL; + break; + } + goto done; + } + +done: + media_player_play_item_complete(mp, ret); + + return FALSE; +} + +static void avrcp_play_item(struct avrcp *session, uint64_t uid) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + 11]; + struct avrcp_player *player = session->controller->player; + struct avrcp_header *pdu = (void *) buf; + uint16_t length; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_PLAY_ITEM; + pdu->params_len = htons(11); + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + + pdu->params[0] = player->scope; + put_be64(uid, &pdu->params[1]); + put_be16(player->uid_counter, &pdu->params[9]); + + length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, + AVC_SUBUNIT_PANEL, buf, length, + avrcp_play_item_rsp, session); +} + +static int ct_play_item(struct media_player *mp, const char *name, + uint64_t uid, void *user_data) +{ + struct avrcp_player *player = user_data; + struct avrcp *session; + + if (player->p != NULL) + return -EBUSY; + + session = player->sessions->data; + set_ct_player(session, player); + + if (g_strrstr(name, "/NowPlaying")) + player->scope = 0x03; + else if (g_strrstr(name, "/Search")) + player->scope = 0x02; + else + player->scope = 0x01; + + avrcp_play_item(session, uid); + + return 0; +} + +static void avrcp_add_to_nowplaying(struct avrcp *session, uint64_t uid) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + 11]; + struct avrcp_player *player = session->controller->player; + struct avrcp_header *pdu = (void *) buf; + uint16_t length; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_ADD_TO_NOW_PLAYING; + pdu->params_len = htons(11); + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + + pdu->params[0] = player->scope; + put_be64(uid, &pdu->params[1]); + put_be16(player->uid_counter, &pdu->params[9]); + + length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_CONTROL, + AVC_SUBUNIT_PANEL, buf, length, + NULL, session); +} + +static int ct_add_to_nowplaying(struct media_player *mp, const char *name, + uint64_t uid, void *user_data) +{ + struct avrcp_player *player = user_data; + struct avrcp *session; + + if (player->p != NULL) + return -EBUSY; + + session = player->sessions->data; + + if (g_strrstr(name, "/NowPlaying")) + player->scope = 0x03; + else + player->scope = 0x01; + + set_ct_player(session, player); + avrcp_add_to_nowplaying(session, uid); + + return 0; +} + +static gboolean avrcp_get_total_numberofitems_rsp(struct avctp *conn, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp_browsing_header *pdu = (void *) operands; + struct avrcp *session = user_data; + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + uint32_t num_of_items = 0; + + if (pdu == NULL) + return -ETIMEDOUT; + + if (pdu->params[0] != AVRCP_STATUS_SUCCESS || operand_count < 7) + return -EINVAL; + + if (pdu->params[0] == AVRCP_STATUS_OUT_OF_BOUNDS) + goto done; + + player->uid_counter = get_be16(&pdu->params[1]); + num_of_items = get_be32(&pdu->params[3]); + + if (!num_of_items) + return -EINVAL; + +done: + media_player_total_items_complete(mp, num_of_items); + return FALSE; +} + +static void avrcp_get_total_numberofitems(struct avrcp *session) +{ + uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 7]; + struct avrcp_player *player = session->controller->player; + struct avrcp_browsing_header *pdu = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + pdu->pdu_id = AVRCP_GET_TOTAL_NUMBER_OF_ITEMS; + pdu->param_len = htons(7 + sizeof(uint32_t)); + + pdu->params[0] = player->scope; + + avctp_send_browsing_req(session->conn, buf, sizeof(buf), + avrcp_get_total_numberofitems_rsp, session); +} + +static int ct_get_total_numberofitems(struct media_player *mp, const char *name, + void *user_data) +{ + struct avrcp_player *player = user_data; + struct avrcp *session; + + session = player->sessions->data; + set_ct_player(session, player); + + if (session->controller->version != 0x0106) { + error("version not supported"); + return -1; + } + + if (g_str_has_prefix(name, "/NowPlaying")) + player->scope = 0x03; + else if (g_str_has_suffix(name, "/search")) + player->scope = 0x02; + else + player->scope = 0x01; + + avrcp_get_total_numberofitems(session); + + return 0; +} + +static const struct media_player_callback ct_cbs = { + .set_setting = ct_set_setting, + .play = ct_play, + .pause = ct_pause, + .stop = ct_stop, + .next = ct_next, + .previous = ct_previous, + .fast_forward = ct_fast_forward, + .rewind = ct_rewind, + .list_items = ct_list_items, + .change_folder = ct_change_folder, + .search = ct_search, + .play_item = ct_play_item, + .add_to_nowplaying = ct_add_to_nowplaying, + .total_items = ct_get_total_numberofitems, +}; + +static struct avrcp_player *create_ct_player(struct avrcp *session, + uint16_t id) +{ + struct avrcp_player *player; + struct media_player *mp; + const char *path; + + player = g_new0(struct avrcp_player, 1); + player->id = id; + player->sessions = g_slist_prepend(player->sessions, session); + + path = device_get_path(session->dev); + + mp = media_player_controller_create(path, id); + if (mp == NULL) + return NULL; + + media_player_set_callbacks(mp, &ct_cbs, player); + player->user_data = mp; + player->destroy = (GDestroyNotify) media_player_destroy; + + if (session->controller->player == NULL) + set_ct_player(session, player); + + session->controller->players = g_slist_prepend( + session->controller->players, + player); + + return player; +} + +static struct avrcp_player *find_ct_player(struct avrcp *session, uint16_t id) +{ + GSList *l; + + for (l = session->controller->players; l; l = l->next) { + struct avrcp_player *player = l->data; + + if (player->id == 0) { + player->id = id; + return player; + } + + if (player->id == id) + return player; + } + + return NULL; +} + +static struct avrcp_player * +avrcp_parse_media_player_item(struct avrcp *session, uint8_t *operands, + uint16_t len) +{ + struct avrcp_player *player; + struct media_player *mp; + uint16_t id, namelen; + uint32_t subtype; + const char *curval, *strval; + char name[255]; + + if (len < 28) + return NULL; + + id = get_be16(&operands[0]); + + player = find_ct_player(session, id); + if (player == NULL) { + player = create_ct_player(session, id); + if (player == NULL) + return NULL; + } else if (player->features != NULL) + return player; + + mp = player->user_data; + + media_player_set_type(mp, type_to_string(operands[2])); + + subtype = get_be32(&operands[3]); + + media_player_set_subtype(mp, subtype_to_string(subtype)); + + curval = media_player_get_status(mp); + strval = status_to_string(operands[7]); + + if (g_strcmp0(curval, strval) != 0) { + media_player_set_status(mp, strval); + avrcp_get_play_status(session); + } + + avrcp_player_parse_features(player, &operands[8]); + + namelen = get_be16(&operands[26]); + if (namelen > 0 && namelen + 28 == len) { + namelen = MIN(namelen, sizeof(name) - 1); + memcpy(name, &operands[28], namelen); + name[namelen] = '\0'; + media_player_set_name(mp, name); + } + + if (player->addressed) + set_browsed_player(session, player); + + return player; +} + +static void player_destroy(gpointer data) +{ + struct avrcp_player *player = data; + + if (player->destroy) + player->destroy(player->user_data); + + if (player->changed_id > 0) + g_source_remove(player->changed_id); + + g_slist_free(player->sessions); + g_free(player->path); + g_free(player->change_path); + g_free(player->features); + g_free(player); +} + +static void player_remove(gpointer data) +{ + struct avrcp_player *player = data; + GSList *l; + + /* Don't remove reserved player */ + if (!player->id) + return; + + for (l = player->sessions; l; l = l->next) { + struct avrcp *session = l->data; + struct avrcp_data *controller = session->controller; + + controller->players = g_slist_remove(controller->players, + player); + + /* Check if current player is being removed */ + if (controller->player == player) + set_ct_player(session, g_slist_nth_data( + controller->players, 0)); + } + + player_destroy(player); +} + +static gboolean avrcp_get_media_player_list_rsp(struct avctp *conn, + uint8_t *operands, + size_t operand_count, + void *user_data) +{ + struct avrcp_browsing_header *pdu = (void *) operands; + struct avrcp *session = user_data; + uint16_t count; + size_t i; + GSList *removed; + + if (pdu == NULL || pdu->params[0] != AVRCP_STATUS_SUCCESS || + operand_count < 5) + return FALSE; + + removed = g_slist_copy(session->controller->players); + count = get_be16(&operands[6]); + + for (i = 8; count && i < operand_count; count--) { + struct avrcp_player *player; + uint8_t type; + uint16_t len; + + type = operands[i++]; + len = get_be16(&operands[i]); + i += 2; + + if (type != 0x01) { + i += len; + continue; + } + + if (i + len > operand_count) { + error("Invalid player item length"); + return FALSE; + } + + player = avrcp_parse_media_player_item(session, &operands[i], + len); + if (player) + removed = g_slist_remove(removed, player); + + i += len; + } + + g_slist_free_full(removed, player_remove); + + /* There should always be an active player */ + if (!session->controller->player) + create_ct_player(session, 0); + + return FALSE; +} + +static void avrcp_get_media_player_list(struct avrcp *session) +{ + uint8_t buf[AVRCP_BROWSING_HEADER_LENGTH + 10]; + struct avrcp_browsing_header *pdu = (void *) buf; + + memset(buf, 0, sizeof(buf)); + + pdu->pdu_id = AVRCP_GET_FOLDER_ITEMS; + put_be32(0, &pdu->params[1]); + put_be32(UINT32_MAX, &pdu->params[5]); + pdu->param_len = htons(10); + + avctp_send_browsing_req(session->conn, buf, sizeof(buf), + avrcp_get_media_player_list_rsp, session); +} + +static void avrcp_volume_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + struct avrcp_player *player = target_get_player(session); + uint8_t volume; + + if (!player) + return; + + volume = pdu->params[1] & 0x7F; + + player->cb->set_volume(volume, session->dev, player->user_data); +} + +static void avrcp_status_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + uint8_t value; + const char *curval, *strval; + + value = pdu->params[1]; + + curval = media_player_get_status(mp); + strval = status_to_string(value); + + if (g_strcmp0(curval, strval) == 0) + return; + + media_player_set_status(mp, strval); + avrcp_get_play_status(session); +} + +static void avrcp_track_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + if (session->browsing_id) { + struct avrcp_player *player = session->controller->player; + player->uid = get_be64(&pdu->params[1]); + avrcp_get_item_attributes(session, player->uid); + } else + avrcp_get_element_attributes(session); +} + +static void avrcp_playback_pos_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + uint32_t position; + + position = get_be32(&pdu->params[1]); + media_player_set_position(mp, position); +} + +static void avrcp_setting_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + struct avrcp_player *player = session->controller->player; + struct media_player *mp = player->user_data; + uint8_t count = pdu->params[1]; + int i; + + for (i = 2; count > 0; count--, i += 2) { + const char *key; + const char *value; + + key = attr_to_str(pdu->params[i]); + if (key == NULL) + continue; + + value = attrval_to_str(pdu->params[i], pdu->params[i + 1]); + if (value == NULL) + continue; + + media_player_set_setting(mp, key, value); + } +} + +static void avrcp_available_players_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + avrcp_get_media_player_list(session); +} + +static void avrcp_addressed_player_changed(struct avrcp *session, + struct avrcp_header *pdu) +{ + struct avrcp_player *player = session->controller->player; + uint16_t id = get_be16(&pdu->params[1]); + + if (player != NULL && player->id == id) + return; + + player = find_ct_player(session, id); + if (player == NULL) { + player = create_ct_player(session, id); + if (player == NULL) + return; + } + + player->addressed = true; + player->uid_counter = get_be16(&pdu->params[3]); + set_ct_player(session, player); + + if (player->features != NULL) + return; + + avrcp_get_media_player_list(session); +} + +static void avrcp_uids_changed(struct avrcp *session, struct avrcp_header *pdu) +{ + struct avrcp_player *player = session->controller->player; + + player->uid_counter = get_be16(&pdu->params[1]); +} + +static gboolean avrcp_handle_event(struct avctp *conn, uint8_t code, + uint8_t subunit, uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_header *pdu = (void *) operands; + uint8_t event; + + if (!pdu) + return FALSE; + + if ((code != AVC_CTYPE_INTERIM && code != AVC_CTYPE_CHANGED)) { + if (pdu->params[0] == AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED && + code == AVC_CTYPE_REJECTED) { + int i; + + /* Lookup event by transaction */ + for (i = 0; i <= AVRCP_EVENT_LAST; i++) { + if (session->transaction_events[i] == + transaction) { + event = i; + goto changed; + } + } + } + return FALSE; + } + + event = pdu->params[0]; + + if (code == AVC_CTYPE_CHANGED) { + goto changed; + } + + switch (event) { + case AVRCP_EVENT_VOLUME_CHANGED: + avrcp_volume_changed(session, pdu); + break; + case AVRCP_EVENT_STATUS_CHANGED: + avrcp_status_changed(session, pdu); + break; + case AVRCP_EVENT_TRACK_CHANGED: + avrcp_track_changed(session, pdu); + break; + case AVRCP_EVENT_PLAYBACK_POS_CHANGED: + avrcp_playback_pos_changed(session, pdu); + break; + case AVRCP_EVENT_SETTINGS_CHANGED: + avrcp_setting_changed(session, pdu); + break; + case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: + avrcp_available_players_changed(session, pdu); + break; + case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: + avrcp_addressed_player_changed(session, pdu); + break; + case AVRCP_EVENT_UIDS_CHANGED: + avrcp_uids_changed(session, pdu); + break; + } + + session->registered_events |= (1 << event); + session->transaction_events[event] = transaction; + + return TRUE; + +changed: + session->registered_events ^= (1 << event); + session->transaction_events[event] = 0; + avrcp_register_notification(session, event); + return FALSE; +} + +static void avrcp_register_notification(struct avrcp *session, uint8_t event) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH]; + struct avrcp_header *pdu = (void *) buf; + uint8_t length; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + pdu->params[0] = event; + + /* + * Set maximum interval possible for position changed as we only + * use it to resync. + */ + if (event == AVRCP_EVENT_PLAYBACK_POS_CHANGED) + bt_put_be32(UINT32_MAX / 1000, &pdu->params[1]); + + pdu->params_len = htons(AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH); + + length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_NOTIFY, + AVC_SUBUNIT_PANEL, buf, length, + avrcp_handle_event, session); +} + +static gboolean avrcp_get_capabilities_resp(struct avctp *conn, uint8_t code, + uint8_t subunit, uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_header *pdu = (void *) operands; + uint16_t events = 0; + uint8_t count; + + if (code == AVC_CTYPE_REJECTED || code == AVC_CTYPE_NOT_IMPLEMENTED || + pdu == NULL || pdu->params[0] != CAP_EVENTS_SUPPORTED) + return FALSE; + + /* Connect browsing if pending */ + if (session->browsing_timer > 0) { + g_source_remove(session->browsing_timer); + session->browsing_timer = 0; + avctp_connect_browsing(session->conn); + } + + count = pdu->params[1]; + + for (; count > 0; count--) { + uint8_t event = pdu->params[1 + count]; + + events |= (1 << event); + + switch (event) { + case AVRCP_EVENT_STATUS_CHANGED: + case AVRCP_EVENT_TRACK_CHANGED: + case AVRCP_EVENT_PLAYBACK_POS_CHANGED: + case AVRCP_EVENT_SETTINGS_CHANGED: + case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: + case AVRCP_EVENT_UIDS_CHANGED: + case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: + /* These events above requires a player */ + if (!session->controller || + !session->controller->player) + break; + /* fall through */ + case AVRCP_EVENT_VOLUME_CHANGED: + avrcp_register_notification(session, event); + break; + } + } + + if (!session->controller || !session->controller->player) + return FALSE; + + if (!(events & (1 << AVRCP_EVENT_SETTINGS_CHANGED))) + avrcp_list_player_attributes(session); + + if (!(events & (1 << AVRCP_EVENT_STATUS_CHANGED))) + avrcp_get_play_status(session); + + if (!(events & (1 << AVRCP_EVENT_STATUS_CHANGED))) + avrcp_get_element_attributes(session); + + return FALSE; +} + +static void avrcp_get_capabilities(struct avrcp *session) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_GET_CAPABILITIES_PARAM_LENGTH]; + struct avrcp_header *pdu = (void *) buf; + uint8_t length; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_GET_CAPABILITIES; + pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; + pdu->params[0] = CAP_EVENTS_SUPPORTED; + pdu->params_len = htons(AVRCP_GET_CAPABILITIES_PARAM_LENGTH); + + length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); + + avctp_send_vendordep_req(session->conn, AVC_CTYPE_STATUS, + AVC_SUBUNIT_PANEL, buf, length, + avrcp_get_capabilities_resp, + session); +} + +static struct avrcp *find_session(GSList *list, struct btd_device *dev) +{ + for (; list; list = list->next) { + struct avrcp *session = list->data; + + if (session->dev == dev) + return session; + } + + return NULL; +} + +static void destroy_browsing(void *data) +{ + struct avrcp *session = data; + + session->browsing_id = 0; +} + +static void session_init_browsing(struct avrcp *session) +{ + if (session->browsing_timer > 0) { + g_source_remove(session->browsing_timer); + session->browsing_timer = 0; + } + + session->browsing_id = avctp_register_browsing_pdu_handler( + session->conn, + handle_browsing_pdu, + session, + destroy_browsing); +} + +static struct avrcp_data *data_init(struct avrcp *session, const char *uuid) +{ + struct avrcp_data *data; + const sdp_record_t *rec; + sdp_list_t *list; + sdp_profile_desc_t *desc; + + data = g_new0(struct avrcp_data, 1); + + rec = btd_device_get_record(session->dev, uuid); + if (rec == NULL) + return data; + + if (sdp_get_profile_descs(rec, &list) == 0) { + desc = list->data; + data->version = desc->version; + } + + sdp_get_int_attr(rec, SDP_ATTR_SUPPORTED_FEATURES, &data->features); + sdp_list_free(list, free); + + return data; +} + +static gboolean connect_browsing(gpointer user_data) +{ + struct avrcp *session = user_data; + + session->browsing_timer = 0; + + avctp_connect_browsing(session->conn); + + return FALSE; +} + +static void avrcp_connect_browsing(struct avrcp *session) +{ + /* Immediately connect browsing channel if initiator otherwise delay + * it to avoid possible collisions + */ + if (avctp_is_initiator(session->conn)) { + avctp_connect_browsing(session->conn); + return; + } + + if (session->browsing_timer > 0) + return; + + session->browsing_timer = g_timeout_add_seconds(AVRCP_BROWSING_TIMEOUT, + connect_browsing, + session); +} + +static void target_init(struct avrcp *session) +{ + struct avrcp_server *server = session->server; + struct avrcp_data *target; + struct avrcp_player *player; + struct btd_service *service; + + if (session->target != NULL) + return; + + target = data_init(session, AVRCP_REMOTE_UUID); + session->target = target; + + DBG("%p version 0x%04x", target, target->version); + + service = btd_device_get_service(session->dev, AVRCP_REMOTE_UUID); + btd_service_connecting_complete(service, 0); + + player = g_slist_nth_data(server->players, 0); + if (player != NULL) { + target->player = player; + player->sessions = g_slist_prepend(player->sessions, session); + } + + session->supported_events |= (1 << AVRCP_EVENT_STATUS_CHANGED) | + (1 << AVRCP_EVENT_TRACK_CHANGED) | + (1 << AVRCP_EVENT_TRACK_REACHED_START) | + (1 << AVRCP_EVENT_TRACK_REACHED_END) | + (1 << AVRCP_EVENT_SETTINGS_CHANGED); + + if (target->version < 0x0104) + return; + + session->supported_events |= + (1 << AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED) | + (1 << AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED) | + (1 << AVRCP_EVENT_VOLUME_CHANGED); + + /* Only check capabilities if controller is not supported */ + if (session->controller == NULL) + avrcp_get_capabilities(session); + + if (!(target->features & AVRCP_FEATURE_BROWSING)) + return; + + avrcp_connect_browsing(session); +} + +static void controller_init(struct avrcp *session) +{ + struct avrcp_player *player; + struct btd_service *service; + struct avrcp_data *controller; + + if (session->controller != NULL) + return; + + controller = data_init(session, AVRCP_TARGET_UUID); + session->controller = controller; + + DBG("%p version 0x%04x", controller, controller->version); + + service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID); + btd_service_connecting_complete(service, 0); + + /* Only create player if category 1 is supported */ + if (controller->features & AVRCP_FEATURE_CATEGORY_1) { + player = create_ct_player(session, 0); + if (player == NULL) + return; + } + + if (controller->version < 0x0103) + return; + + avrcp_get_capabilities(session); + + if (controller->version < 0x0104) + return; + + if (!(controller->features & AVRCP_FEATURE_BROWSING)) + return; + + avrcp_connect_browsing(session); +} + +static void session_init_control(struct avrcp *session) +{ + session->passthrough_id = avctp_register_passthrough_handler( + session->conn, + handle_passthrough, + session); + session->passthrough_handlers = passthrough_handlers; + session->control_id = avctp_register_pdu_handler(session->conn, + AVC_OP_VENDORDEP, + handle_vendordep_pdu, + session); + session->control_handlers = control_handlers; + + if (btd_device_get_service(session->dev, AVRCP_TARGET_UUID) != NULL) + controller_init(session); + + if (btd_device_get_service(session->dev, AVRCP_REMOTE_UUID) != NULL) + target_init(session); +} + +static void controller_destroy(struct avrcp *session) +{ + struct avrcp_data *controller = session->controller; + + DBG("%p", controller); + + g_slist_free_full(controller->players, player_destroy); + + g_free(controller); +} + +static void target_destroy(struct avrcp *session) +{ + struct avrcp_data *target = session->target; + struct avrcp_player *player = target->player; + + DBG("%p", target); + + if (player != NULL) + player->sessions = g_slist_remove(player->sessions, session); + + g_free(target); +} + +static void session_destroy(struct avrcp *session, int err) +{ + struct avrcp_server *server = session->server; + struct btd_service *service; + + server->sessions = g_slist_remove(server->sessions, session); + + session_abort_pending_pdu(session); + + service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID); + if (service != NULL) { + if (session->control_id == 0) + btd_service_connecting_complete(service, err); + else + btd_service_disconnecting_complete(service, 0); + } + + service = btd_device_get_service(session->dev, AVRCP_REMOTE_UUID); + if (service != NULL) { + if (session->control_id == 0) + btd_service_connecting_complete(service, err); + else + btd_service_disconnecting_complete(service, 0); + } + + if (session->browsing_timer > 0) + g_source_remove(session->browsing_timer); + + if (session->controller != NULL) + controller_destroy(session); + + if (session->target != NULL) + target_destroy(session); + + if (session->passthrough_id > 0) + avctp_unregister_passthrough_handler(session->passthrough_id); + + if (session->control_id > 0) + avctp_unregister_pdu_handler(session->control_id); + + if (session->browsing_id > 0) + avctp_unregister_browsing_pdu_handler(session->browsing_id); + + g_free(session); +} + +static struct avrcp *session_create(struct avrcp_server *server, + struct btd_device *device) +{ + struct avrcp *session; + + session = g_new0(struct avrcp, 1); + session->server = server; + session->conn = avctp_connect(device); + session->dev = device; + + server->sessions = g_slist_append(server->sessions, session); + + return session; +} + +static void state_changed(struct btd_device *device, avctp_state_t old_state, + avctp_state_t new_state, int err, + void *user_data) +{ + struct avrcp_server *server; + struct avrcp *session; + + server = find_server(servers, device_get_adapter(device)); + if (!server) + return; + + session = find_session(server->sessions, device); + + switch (new_state) { + case AVCTP_STATE_DISCONNECTED: + if (session == NULL) + break; + + session_destroy(session, err); + + break; + case AVCTP_STATE_CONNECTING: + if (session != NULL) + break; + + session_create(server, device); + + break; + case AVCTP_STATE_CONNECTED: + if (session == NULL || session->control_id > 0) + break; + + session_init_control(session); + + break; + case AVCTP_STATE_BROWSING_CONNECTED: + if (session == NULL || session->browsing_id > 0) + break; + + session_init_browsing(session); + + break; + case AVCTP_STATE_BROWSING_CONNECTING: + default: + return; + } +} + +static struct avrcp_server *avrcp_server_register(struct btd_adapter *adapter) +{ + struct avrcp_server *server; + + if (avctp_register(adapter, TRUE) < 0) + return NULL; + + server = g_new0(struct avrcp_server, 1); + server->adapter = btd_adapter_ref(adapter); + + servers = g_slist_append(servers, server); + + if (!avctp_id) + avctp_id = avctp_add_state_cb(NULL, state_changed, NULL); + + return server; +} + +static void avrcp_server_unregister(struct avrcp_server *server) +{ + g_slist_free_full(server->sessions, g_free); + g_slist_free_full(server->players, player_destroy); + + servers = g_slist_remove(servers, server); + + avctp_unregister(server->adapter); + btd_adapter_unref(server->adapter); + g_free(server); + + if (servers) + return; + + if (avctp_id) { + avctp_remove_state_cb(avctp_id); + avctp_id = 0; + } +} + +struct avrcp_player *avrcp_register_player(struct btd_adapter *adapter, + struct avrcp_player_cb *cb, + void *user_data, + GDestroyNotify destroy) +{ + struct avrcp_server *server; + struct avrcp_player *player; + GSList *l; + static uint16_t id = 0; + + server = find_server(servers, adapter); + if (!server) + return NULL; + + player = g_new0(struct avrcp_player, 1); + player->id = ++id; + player->server = server; + player->cb = cb; + player->user_data = user_data; + player->destroy = destroy; + + server->players = g_slist_append(server->players, player); + + /* Assign player to session without current player */ + for (l = server->sessions; l; l = l->next) { + struct avrcp *session = l->data; + struct avrcp_data *target = session->target; + + if (target == NULL) + continue; + + if (target->player == NULL) { + target->player = player; + player->sessions = g_slist_append(player->sessions, + session); + } + } + + avrcp_player_event(player, + AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED, NULL); + + return player; +} + +void avrcp_unregister_player(struct avrcp_player *player) +{ + struct avrcp_server *server = player->server; + GSList *l; + + server->players = g_slist_remove(server->players, player); + + /* Remove player from sessions using it */ + for (l = player->sessions; l; l = l->next) { + struct avrcp *session = l->data; + struct avrcp_data *target = session->target; + + if (target == NULL) + continue; + + if (target->player == player) + target->player = g_slist_nth_data(server->players, 0); + } + + avrcp_player_event(player, + AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED, NULL); + + player_destroy(player); +} + +static gboolean avrcp_handle_set_volume(struct avctp *conn, uint8_t code, + uint8_t subunit, uint8_t transaction, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct avrcp *session = user_data; + struct avrcp_player *player = target_get_player(session); + struct avrcp_header *pdu = (void *) operands; + uint8_t volume; + + if (code == AVC_CTYPE_REJECTED || code == AVC_CTYPE_NOT_IMPLEMENTED || + pdu == NULL) + return FALSE; + + volume = pdu->params[0] & 0x7F; + + if (player != NULL) + player->cb->set_volume(volume, session->dev, player->user_data); + + return FALSE; +} + +static int avrcp_event(struct avrcp *session, uint8_t id, const void *data) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + 2]; + struct avrcp_header *pdu = (void *) buf; + uint8_t code; + uint16_t size; + int err; + + /* Verify that the event is registered */ + if (!(session->registered_events & (1 << id))) + return -ENOENT; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; + code = AVC_CTYPE_CHANGED; + pdu->params[0] = id; + + DBG("id=%u", id); + + switch (id) { + case AVRCP_EVENT_VOLUME_CHANGED: + size = 2; + memcpy(&pdu->params[1], data, sizeof(uint8_t)); + break; + default: + error("Unknown event %u", id); + return -EINVAL; + } + + pdu->params_len = htons(size); + + err = avctp_send_vendordep(session->conn, + session->transaction_events[id], + code, AVC_SUBUNIT_PANEL, + buf, size + AVRCP_HEADER_LENGTH); + if (err < 0) + return err; + + /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */ + session->registered_events ^= 1 << id; + + return err; +} + +int avrcp_set_volume(struct btd_device *dev, uint8_t volume, bool notify) +{ + struct avrcp_server *server; + struct avrcp *session; + uint8_t buf[AVRCP_HEADER_LENGTH + 1]; + struct avrcp_header *pdu = (void *) buf; + + server = find_server(servers, device_get_adapter(dev)); + if (server == NULL) + return -EINVAL; + + session = find_session(server->sessions, dev); + if (session == NULL) + return -ENOTCONN; + + if (notify) { + if (!session->target) + return -ENOTSUP; + return avrcp_event(session, AVRCP_EVENT_VOLUME_CHANGED, + &volume); + } + + if (!session->controller || session->controller->version < 0x0104) + return -ENOTSUP; + + memset(buf, 0, sizeof(buf)); + + set_company_id(pdu->company_id, IEEEID_BTSIG); + + pdu->pdu_id = AVRCP_SET_ABSOLUTE_VOLUME; + pdu->params[0] = volume; + pdu->params_len = htons(1); + + return avctp_send_vendordep_req(session->conn, + AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, + buf, sizeof(buf), + avrcp_handle_set_volume, session); +} + +static int avrcp_connect(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + const char *path = device_get_path(dev); + + DBG("path %s", path); + + return control_connect(service); +} + +static int avrcp_disconnect(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + const char *path = device_get_path(dev); + + DBG("path %s", path); + + return control_disconnect(service); +} + +static int avrcp_target_probe(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + + DBG("path %s", device_get_path(dev)); + + return control_init_target(service); +} + +static void avrcp_target_remove(struct btd_service *service) +{ + control_unregister(service); +} + +static void avrcp_target_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct avrcp_server *server; + + DBG("path %s", adapter_get_path(adapter)); + + server = find_server(servers, adapter); + if (!server) + return; + + if (server->tg_record_id != 0) { + adapter_service_remove(adapter, server->tg_record_id); + server->tg_record_id = 0; + } + + if (server->ct_record_id == 0) + avrcp_server_unregister(server); +} + +static int avrcp_target_server_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + sdp_record_t *record; + struct avrcp_server *server; + + DBG("path %s", adapter_get_path(adapter)); + + server = find_server(servers, adapter); + if (server != NULL) + goto done; + + server = avrcp_server_register(adapter); + if (server == NULL) + return -EPROTONOSUPPORT; + +done: + record = avrcp_tg_record(); + if (!record) { + error("Unable to allocate new service record"); + avrcp_target_server_remove(p, adapter); + return -1; + } + + if (adapter_service_add(adapter, record) < 0) { + error("Unable to register AVRCP target service record"); + avrcp_target_server_remove(p, adapter); + sdp_record_free(record); + return -1; + } + server->tg_record_id = record->handle; + + return 0; +} + +static struct btd_profile avrcp_target_profile = { + .name = "audio-avrcp-target", + + .remote_uuid = AVRCP_TARGET_UUID, + .device_probe = avrcp_target_probe, + .device_remove = avrcp_target_remove, + + .connect = avrcp_connect, + .disconnect = avrcp_disconnect, + + .adapter_probe = avrcp_target_server_probe, + .adapter_remove = avrcp_target_server_remove, +}; + +static int avrcp_controller_probe(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + + DBG("path %s", device_get_path(dev)); + + return control_init_remote(service); +} + +static void avrcp_controller_remove(struct btd_service *service) +{ + control_unregister(service); +} + +static void avrcp_controller_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct avrcp_server *server; + + DBG("path %s", adapter_get_path(adapter)); + + server = find_server(servers, adapter); + if (!server) + return; + + if (server->ct_record_id != 0) { + adapter_service_remove(adapter, server->ct_record_id); + server->ct_record_id = 0; + } + + if (server->tg_record_id == 0) + avrcp_server_unregister(server); +} + +static int avrcp_controller_server_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + sdp_record_t *record; + struct avrcp_server *server; + + DBG("path %s", adapter_get_path(adapter)); + + server = find_server(servers, adapter); + if (server != NULL) + goto done; + + server = avrcp_server_register(adapter); + if (server == NULL) + return -EPROTONOSUPPORT; + +done: + record = avrcp_ct_record(); + if (!record) { + error("Unable to allocate new service record"); + avrcp_controller_server_remove(p, adapter); + return -1; + } + + if (adapter_service_add(adapter, record) < 0) { + error("Unable to register AVRCP service record"); + avrcp_controller_server_remove(p, adapter); + sdp_record_free(record); + return -1; + } + server->ct_record_id = record->handle; + + return 0; +} + +static struct btd_profile avrcp_controller_profile = { + .name = "avrcp-controller", + + .remote_uuid = AVRCP_REMOTE_UUID, + .device_probe = avrcp_controller_probe, + .device_remove = avrcp_controller_remove, + + .connect = avrcp_connect, + .disconnect = avrcp_disconnect, + + .adapter_probe = avrcp_controller_server_probe, + .adapter_remove = avrcp_controller_server_remove, +}; + +static int avrcp_init(void) +{ + btd_profile_register(&avrcp_controller_profile); + btd_profile_register(&avrcp_target_profile); + + return 0; +} + +static void avrcp_exit(void) +{ + btd_profile_unregister(&avrcp_controller_profile); + btd_profile_unregister(&avrcp_target_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(avrcp, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + avrcp_init, avrcp_exit) diff --git a/profiles/audio/avrcp.h b/profiles/audio/avrcp.h new file mode 100644 index 0000000..86d310c --- /dev/null +++ b/profiles/audio/avrcp.h @@ -0,0 +1,119 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* 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 + +/* play status */ +#define AVRCP_PLAY_STATUS_STOPPED 0x00 +#define AVRCP_PLAY_STATUS_PLAYING 0x01 +#define AVRCP_PLAY_STATUS_PAUSED 0x02 +#define AVRCP_PLAY_STATUS_FWD_SEEK 0x03 +#define AVRCP_PLAY_STATUS_REV_SEEK 0x04 +#define AVRCP_PLAY_STATUS_ERROR 0xFF + +/* Notification events */ +#define AVRCP_EVENT_STATUS_CHANGED 0x01 +#define AVRCP_EVENT_TRACK_CHANGED 0x02 +#define AVRCP_EVENT_TRACK_REACHED_END 0x03 +#define AVRCP_EVENT_TRACK_REACHED_START 0x04 +#define AVRCP_EVENT_PLAYBACK_POS_CHANGED 0x05 +#define AVRCP_EVENT_SETTINGS_CHANGED 0x08 +#define AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED 0x0a +#define AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED 0x0b +#define AVRCP_EVENT_UIDS_CHANGED 0x0c +#define AVRCP_EVENT_VOLUME_CHANGED 0x0d +#define AVRCP_EVENT_LAST AVRCP_EVENT_VOLUME_CHANGED + +struct avrcp_player_cb { + GList *(*list_settings) (void *user_data); + const char *(*get_setting) (const char *key, void *user_data); + int (*set_setting) (const char *key, const char *value, + void *user_data); + uint64_t (*get_uid) (void *user_data); + const char *(*get_metadata) (const char *key, void *user_data); + GList *(*list_metadata) (void *user_data); + const char *(*get_status) (void *user_data); + uint32_t (*get_position) (void *user_data); + uint32_t (*get_duration) (void *user_data); + const char *(*get_name) (void *user_data); + void (*set_volume) (uint8_t volume, struct btd_device *dev, + void *user_data); + bool (*play) (void *user_data); + bool (*stop) (void *user_data); + bool (*pause) (void *user_data); + bool (*next) (void *user_data); + bool (*previous) (void *user_data); +}; + +int avrcp_set_volume(struct btd_device *dev, uint8_t volume, bool notify); + +struct avrcp_player *avrcp_register_player(struct btd_adapter *adapter, + struct avrcp_player_cb *cb, + void *user_data, + GDestroyNotify destroy); +void avrcp_unregister_player(struct avrcp_player *player); + +void avrcp_player_event(struct avrcp_player *player, uint8_t id, + const void *data); + + +size_t avrcp_handle_vendor_reject(uint8_t *code, uint8_t *operands); +size_t avrcp_browsing_general_reject(uint8_t *operands); diff --git a/profiles/audio/control.c b/profiles/audio/control.c new file mode 100644 index 0000000..4ab1f9b --- /dev/null +++ b/profiles/audio/control.c @@ -0,0 +1,393 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/log.h" +#include "src/error.h" +#include "src/sdpd.h" +#include "src/uuid-helper.h" +#include "src/dbus-common.h" + +#include "avctp.h" +#include "control.h" +#include "player.h" + +static GSList *devices = NULL; + +struct control { + struct btd_device *dev; + struct avctp *session; + struct btd_service *target; + struct btd_service *remote; + unsigned int avctp_id; + const char *player; +}; + +static void state_changed(struct btd_device *dev, avctp_state_t old_state, + avctp_state_t new_state, int err, + void *user_data) +{ + struct control *control = user_data; + DBusConnection *conn = btd_get_dbus_connection(); + const char *path = device_get_path(dev); + + switch (new_state) { + case AVCTP_STATE_DISCONNECTED: + control->session = NULL; + control->player = NULL; + + g_dbus_emit_property_changed(conn, path, + AUDIO_CONTROL_INTERFACE, "Connected"); + g_dbus_emit_property_changed(conn, path, + AUDIO_CONTROL_INTERFACE, "Player"); + + break; + case AVCTP_STATE_CONNECTING: + if (control->session) + break; + + control->session = avctp_get(dev); + + break; + case AVCTP_STATE_CONNECTED: + g_dbus_emit_property_changed(conn, path, + AUDIO_CONTROL_INTERFACE, "Connected"); + break; + case AVCTP_STATE_BROWSING_CONNECTING: + case AVCTP_STATE_BROWSING_CONNECTED: + default: + return; + } +} + +int control_connect(struct btd_service *service) +{ + struct control *control = btd_service_get_user_data(service); + + if (control->session) + return -EALREADY; + + control->session = avctp_connect(control->dev); + if (!control->session) + return -EIO; + + return 0; +} + +int control_disconnect(struct btd_service *service) +{ + struct control *control = btd_service_get_user_data(service); + + if (!control || !control->session) + return -ENOTCONN; + + avctp_disconnect(control->session); + + return 0; +} + +static DBusMessage *key_pressed(DBusConnection *conn, DBusMessage *msg, + uint8_t op, void *data) +{ + struct control *control = data; + int err; + + if (!control->session) + return btd_error_not_connected(msg); + + if (!control->target) + return btd_error_not_supported(msg); + + err = avctp_send_passthrough(control->session, op); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *control_volume_up(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return key_pressed(conn, msg, AVC_VOLUME_UP, data); +} + +static DBusMessage *control_volume_down(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return key_pressed(conn, msg, AVC_VOLUME_DOWN, data); +} + +static DBusMessage *control_play(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return key_pressed(conn, msg, AVC_PLAY, data); +} + +static DBusMessage *control_pause(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return key_pressed(conn, msg, AVC_PAUSE, data); +} + +static DBusMessage *control_stop(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return key_pressed(conn, msg, AVC_STOP, data); +} + +static DBusMessage *control_next(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return key_pressed(conn, msg, AVC_FORWARD, data); +} + +static DBusMessage *control_previous(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return key_pressed(conn, msg, AVC_BACKWARD, data); +} + +static DBusMessage *control_fast_forward(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return key_pressed(conn, msg, AVC_FAST_FORWARD, data); +} + +static DBusMessage *control_rewind(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + return key_pressed(conn, msg, AVC_REWIND, data); +} + +static gboolean control_property_get_connected( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct control *control = data; + dbus_bool_t value = (control->session != NULL); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); + + return TRUE; +} + +static gboolean control_player_exists(const GDBusPropertyTable *property, + void *data) +{ + struct control *control = data; + + return control->player != NULL; +} + +static gboolean control_get_player(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct control *control = data; + + if (!control->player) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &control->player); + + return TRUE; +} + +static const GDBusMethodTable control_methods[] = { + { GDBUS_DEPRECATED_METHOD("Play", NULL, NULL, control_play) }, + { GDBUS_DEPRECATED_METHOD("Pause", NULL, NULL, control_pause) }, + { GDBUS_DEPRECATED_METHOD("Stop", NULL, NULL, control_stop) }, + { GDBUS_DEPRECATED_METHOD("Next", NULL, NULL, control_next) }, + { GDBUS_DEPRECATED_METHOD("Previous", NULL, NULL, control_previous) }, + { GDBUS_DEPRECATED_METHOD("VolumeUp", NULL, NULL, control_volume_up) }, + { GDBUS_DEPRECATED_METHOD("VolumeDown", NULL, NULL, + control_volume_down) }, + { GDBUS_DEPRECATED_METHOD("FastForward", NULL, NULL, + control_fast_forward) }, + { GDBUS_DEPRECATED_METHOD("Rewind", NULL, NULL, control_rewind) }, + { } +}; + +static const GDBusPropertyTable control_properties[] = { + { "Connected", "b", control_property_get_connected }, + { "Player", "o", control_get_player, NULL, control_player_exists }, + { } +}; + +static void path_unregister(void *data) +{ + struct control *control = data; + + DBG("Unregistered interface %s on path %s", AUDIO_CONTROL_INTERFACE, + device_get_path(control->dev)); + + if (control->session) + avctp_disconnect(control->session); + + avctp_remove_state_cb(control->avctp_id); + + if (control->target) { + btd_service_set_user_data(control->target, NULL); + btd_service_unref(control->target); + } + + if (control->remote) { + btd_service_set_user_data(control->remote, NULL); + btd_service_unref(control->remote); + } + + devices = g_slist_remove(devices, control); + g_free(control); +} + +void control_unregister(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + + g_dbus_unregister_interface(btd_get_dbus_connection(), + device_get_path(dev), + AUDIO_CONTROL_INTERFACE); +} + +static struct control *find_control(struct btd_device *dev) +{ + GSList *l; + + for (l = devices; l; l = l->next) { + struct control *control = l->data; + + if (control->dev == dev) + return control; + } + + return NULL; +} + +static struct control *control_init(struct btd_service *service) +{ + struct control *control; + struct btd_device *dev = btd_service_get_device(service); + + control = find_control(dev); + if (control != NULL) + return control; + + control = g_new0(struct control, 1); + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + device_get_path(dev), + AUDIO_CONTROL_INTERFACE, + control_methods, NULL, + control_properties, control, + path_unregister)) { + g_free(control); + return NULL; + } + + DBG("Registered interface %s on path %s", AUDIO_CONTROL_INTERFACE, + device_get_path(dev)); + + control->dev = dev; + control->avctp_id = avctp_add_state_cb(dev, state_changed, control); + devices = g_slist_prepend(devices, control); + + return control; +} + +int control_init_target(struct btd_service *service) +{ + struct control *control; + + control = control_init(service); + if (control == NULL) + return -EINVAL; + + control->target = btd_service_ref(service); + + btd_service_set_user_data(service, control); + + return 0; +} + +int control_init_remote(struct btd_service *service) +{ + struct control *control; + + control = control_init(service); + if (control == NULL) + return -EINVAL; + + control->remote = btd_service_ref(service); + + btd_service_set_user_data(service, control); + + return 0; +} + +int control_set_player(struct btd_service *service, const char *path) +{ + struct control *control = btd_service_get_user_data(service); + + if (!control->session) + return -ENOTCONN; + + if (g_strcmp0(control->player, path) == 0) + return -EALREADY; + + control->player = path; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + device_get_path(control->dev), + AUDIO_CONTROL_INTERFACE, "Player"); + + return 0; +} diff --git a/profiles/audio/control.h b/profiles/audio/control.h new file mode 100644 index 0000000..aab2631 --- /dev/null +++ b/profiles/audio/control.h @@ -0,0 +1,36 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define AUDIO_CONTROL_INTERFACE "org.bluez.MediaControl1" + +struct btd_service; + +int control_init_target(struct btd_service *service); +int control_init_remote(struct btd_service *service); +void control_unregister(struct btd_service *service); + +int control_connect(struct btd_service *service); +int control_disconnect(struct btd_service *service); + +int control_set_player(struct btd_service *service, const char *path); diff --git a/profiles/audio/media.c b/profiles/audio/media.c new file mode 100644 index 0000000..993ecb3 --- /dev/null +++ b/profiles/audio/media.c @@ -0,0 +1,2434 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * Copyright (C) 2011 BMW Car IT GmbH. All rights reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/dbus-common.h" +#include "src/profile.h" + +#include "src/uuid-helper.h" +#include "src/log.h" +#include "src/error.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" + +#include "avdtp.h" +#include "media.h" +#include "transport.h" +#include "a2dp.h" +#include "avrcp.h" + +#define MEDIA_INTERFACE "org.bluez.Media1" +#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" +#define MEDIA_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player" + +#define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */ + +struct media_app { + struct media_adapter *adapter; + GDBusClient *client; + DBusMessage *reg; + char *sender; /* Application bus id */ + char *path; /* Application object path */ + struct queue *proxies; /* Application proxies */ + struct queue *endpoints; /* Application endpoints */ + struct queue *players; /* Application players */ + int err; +}; + +struct media_adapter { + struct btd_adapter *btd_adapter; + struct queue *apps; /* Application list */ + GSList *endpoints; /* Endpoints list */ + GSList *players; /* Players list */ +}; + +struct endpoint_request { + struct media_endpoint *endpoint; + DBusMessage *msg; + DBusPendingCall *call; + media_endpoint_cb_t cb; + GDestroyNotify destroy; + void *user_data; +}; + +struct media_endpoint { + struct a2dp_sep *sep; + char *sender; /* Endpoint DBus bus id */ + char *path; /* Endpoint object path */ + char *uuid; /* Endpoint property UUID */ + uint8_t codec; /* Endpoint codec */ + uint8_t *capabilities; /* Endpoint property capabilities */ + size_t size; /* Endpoint capabilities size */ + guint hs_watch; + guint ag_watch; + guint watch; + GSList *requests; + struct media_adapter *adapter; + GSList *transports; +}; + +struct media_player { + struct media_adapter *adapter; + struct avrcp_player *player; + char *sender; /* Player DBus bus id */ + char *path; /* Player object path */ + GHashTable *settings; /* Player settings */ + GHashTable *track; /* Player current track */ + guint watch; + guint properties_watch; + guint seek_watch; + char *status; + uint32_t position; + uint32_t duration; + uint8_t volume; + GTimer *timer; + bool play; + bool pause; + bool next; + bool previous; + bool control; + char *name; +}; + +static GSList *adapters = NULL; + +static void endpoint_request_free(struct endpoint_request *request) +{ + if (request->call) + dbus_pending_call_unref(request->call); + + if (request->destroy) + request->destroy(request->user_data); + + dbus_message_unref(request->msg); + g_free(request); +} + +static void media_endpoint_cancel(struct endpoint_request *request) +{ + struct media_endpoint *endpoint = request->endpoint; + + if (request->call) + dbus_pending_call_cancel(request->call); + + endpoint->requests = g_slist_remove(endpoint->requests, request); + + if (request->cb) + request->cb(endpoint, NULL, -1, request->user_data); + + endpoint_request_free(request); +} + +static void media_endpoint_cancel_all(struct media_endpoint *endpoint) +{ + while (endpoint->requests != NULL) + media_endpoint_cancel(endpoint->requests->data); +} + +static void media_endpoint_destroy(struct media_endpoint *endpoint) +{ + DBG("sender=%s path=%s", endpoint->sender, endpoint->path); + + media_endpoint_cancel_all(endpoint); + + g_slist_free_full(endpoint->transports, + (GDestroyNotify) media_transport_destroy); + + g_dbus_remove_watch(btd_get_dbus_connection(), endpoint->watch); + g_free(endpoint->capabilities); + g_free(endpoint->sender); + g_free(endpoint->path); + g_free(endpoint->uuid); + g_free(endpoint); +} + +static struct media_endpoint *media_adapter_find_endpoint( + struct media_adapter *adapter, + const char *sender, + const char *path, + const char *uuid) +{ + GSList *l; + + for (l = adapter->endpoints; l; l = l->next) { + struct media_endpoint *endpoint = l->data; + + if (sender && g_strcmp0(endpoint->sender, sender) != 0) + continue; + + if (path && g_strcmp0(endpoint->path, path) != 0) + continue; + + if (uuid && strcasecmp(endpoint->uuid, uuid) != 0) + continue; + + return endpoint; + } + + return NULL; +} + +static void media_endpoint_remove(void *data) +{ + struct media_endpoint *endpoint = data; + struct media_adapter *adapter = endpoint->adapter; + + if (endpoint->sep) { + a2dp_remove_sep(endpoint->sep); + return; + } + + info("Endpoint unregistered: sender=%s path=%s", endpoint->sender, + endpoint->path); + + adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint); + + if (media_adapter_find_endpoint(adapter, NULL, NULL, + endpoint->uuid) == NULL) + btd_profile_remove_custom_prop(endpoint->uuid, + "MediaEndpoints"); + + media_endpoint_destroy(endpoint); +} + +static void media_endpoint_exit(DBusConnection *connection, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + endpoint->watch = 0; + media_endpoint_remove(endpoint); +} + +static void clear_configuration(struct media_endpoint *endpoint, + struct media_transport *transport) +{ + DBusMessage *msg; + const char *path; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "ClearConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + goto done; + } + + path = media_transport_get_path(transport); + dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + g_dbus_send_message(btd_get_dbus_connection(), msg); +done: + endpoint->transports = g_slist_remove(endpoint->transports, transport); + media_transport_destroy(transport); +} + +static void clear_endpoint(struct media_endpoint *endpoint) +{ + media_endpoint_cancel_all(endpoint); + + while (endpoint->transports != NULL) + clear_configuration(endpoint, endpoint->transports->data); +} + +static void endpoint_reply(DBusPendingCall *call, void *user_data) +{ + struct endpoint_request *request = user_data; + struct media_endpoint *endpoint = request->endpoint; + DBusMessage *reply; + DBusError err; + gboolean value; + void *ret = NULL; + int size = -1; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + reply = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, reply)) { + error("Endpoint replied with an error: %s", + err.name); + + /* Clear endpoint configuration in case of NO_REPLY error */ + if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { + clear_endpoint(endpoint); + dbus_message_unref(reply); + dbus_error_free(&err); + return; + } + + dbus_error_free(&err); + goto done; + } + + if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE, + "SelectConfiguration")) { + DBusMessageIter args, array; + uint8_t *configuration; + + dbus_message_iter_init(reply, &args); + + dbus_message_iter_recurse(&args, &array); + + dbus_message_iter_get_fixed_array(&array, &configuration, &size); + + ret = configuration; + goto done; + } else if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) { + error("Wrong reply signature: %s", err.message); + dbus_error_free(&err); + goto done; + } + + size = 1; + value = TRUE; + ret = &value; + +done: + dbus_message_unref(reply); + + if (request->cb) + request->cb(endpoint, ret, size, request->user_data); + + endpoint->requests = g_slist_remove(endpoint->requests, request); + endpoint_request_free(request); +} + +static gboolean media_endpoint_async_call(DBusMessage *msg, + struct media_endpoint *endpoint, + media_endpoint_cb_t cb, + void *user_data, + GDestroyNotify destroy) +{ + struct endpoint_request *request; + + request = g_new0(struct endpoint_request, 1); + + /* Timeout should be less than avdtp request timeout (4 seconds) */ + if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), + msg, &request->call, + REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + g_free(request); + return FALSE; + } + + dbus_pending_call_set_notify(request->call, endpoint_reply, request, + NULL); + + request->endpoint = endpoint; + request->msg = msg; + request->cb = cb; + request->destroy = destroy; + request->user_data = user_data; + + endpoint->requests = g_slist_append(endpoint->requests, request); + + DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg), + dbus_message_get_destination(msg), + dbus_message_get_path(msg)); + + return TRUE; +} + +static gboolean select_configuration(struct media_endpoint *endpoint, + uint8_t *capabilities, + size_t length, + media_endpoint_cb_t cb, + void *user_data, + GDestroyNotify destroy) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "SelectConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return FALSE; + } + + dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, + &capabilities, length, + DBUS_TYPE_INVALID); + + return media_endpoint_async_call(msg, endpoint, cb, user_data, destroy); +} + +static int transport_device_cmp(gconstpointer data, gconstpointer user_data) +{ + struct media_transport *transport = (struct media_transport *) data; + const struct btd_device *device = user_data; + const struct btd_device *dev = media_transport_get_dev(transport); + + if (device == dev) + return 0; + + return -1; +} + +static struct media_transport *find_device_transport( + struct media_endpoint *endpoint, + struct btd_device *device) +{ + GSList *match; + + match = g_slist_find_custom(endpoint->transports, device, + transport_device_cmp); + if (match == NULL) + return NULL; + + return match->data; +} + +struct a2dp_config_data { + struct a2dp_setup *setup; + a2dp_endpoint_config_t cb; +}; + +static gboolean set_configuration(struct media_endpoint *endpoint, + uint8_t *configuration, size_t size, + media_endpoint_cb_t cb, + void *user_data, + GDestroyNotify destroy) +{ + struct a2dp_config_data *data = user_data; + struct btd_device *device = a2dp_setup_get_device(data->setup); + DBusConnection *conn = btd_get_dbus_connection(); + DBusMessage *msg; + const char *path; + DBusMessageIter iter; + struct media_transport *transport; + + transport = find_device_transport(endpoint, device); + + if (transport != NULL) + return FALSE; + + transport = media_transport_create(device, + a2dp_setup_remote_path(data->setup), + configuration, size, endpoint); + if (transport == NULL) + return FALSE; + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "SetConfiguration"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + media_transport_destroy(transport); + return FALSE; + } + + endpoint->transports = g_slist_append(endpoint->transports, transport); + + dbus_message_iter_init_append(msg, &iter); + + path = media_transport_get_path(transport); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); + + g_dbus_get_properties(conn, path, "org.bluez.MediaTransport1", &iter); + + return media_endpoint_async_call(msg, endpoint, cb, user_data, destroy); +} + +static void release_endpoint(struct media_endpoint *endpoint) +{ + DBusMessage *msg; + + DBG("sender=%s path=%s", endpoint->sender, endpoint->path); + + /* already exit */ + if (endpoint->watch == 0) + goto done; + + clear_endpoint(endpoint); + + msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, + MEDIA_ENDPOINT_INTERFACE, + "Release"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return; + } + + g_dbus_send_message(btd_get_dbus_connection(), msg); + +done: + media_endpoint_remove(endpoint); +} + +static const char *get_name(struct a2dp_sep *sep, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + return endpoint->sender; +} + +static const char *get_path(struct a2dp_sep *sep, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + return endpoint->path; +} + +static size_t get_capabilities(struct a2dp_sep *sep, uint8_t **capabilities, + void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + *capabilities = endpoint->capabilities; + return endpoint->size; +} + +struct a2dp_select_data { + struct a2dp_setup *setup; + a2dp_endpoint_select_t cb; +}; + +static void select_cb(struct media_endpoint *endpoint, void *ret, int size, + void *user_data) +{ + struct a2dp_select_data *data = user_data; + + data->cb(data->setup, ret, size); +} + +static int select_config(struct a2dp_sep *sep, uint8_t *capabilities, + size_t length, struct a2dp_setup *setup, + a2dp_endpoint_select_t cb, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + struct a2dp_select_data *data; + + data = g_new0(struct a2dp_select_data, 1); + data->setup = setup; + data->cb = cb; + + if (select_configuration(endpoint, capabilities, length, + select_cb, data, g_free) == TRUE) + return 0; + + g_free(data); + return -ENOMEM; +} + +static void config_cb(struct media_endpoint *endpoint, void *ret, int size, + void *user_data) +{ + struct a2dp_config_data *data = user_data; + gboolean *ret_value = ret; + + data->cb(data->setup, ret_value ? *ret_value : FALSE); +} + +static int set_config(struct a2dp_sep *sep, uint8_t *configuration, + size_t length, + struct a2dp_setup *setup, + a2dp_endpoint_config_t cb, + void *user_data) +{ + struct media_endpoint *endpoint = user_data; + struct a2dp_config_data *data; + + data = g_new0(struct a2dp_config_data, 1); + data->setup = setup; + data->cb = cb; + + if (set_configuration(endpoint, configuration, length, config_cb, data, + g_free) == TRUE) + return 0; + + g_free(data); + return -ENOMEM; +} + +static void clear_config(struct a2dp_sep *sep, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + clear_endpoint(endpoint); +} + +static void set_delay(struct a2dp_sep *sep, uint16_t delay, void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + if (endpoint->transports == NULL) + return; + + media_transport_update_delay(endpoint->transports->data, delay); +} + +static struct a2dp_endpoint a2dp_endpoint = { + .get_name = get_name, + .get_path = get_path, + .get_capabilities = get_capabilities, + .select_configuration = select_config, + .set_configuration = set_config, + .clear_configuration = clear_config, + .set_delay = set_delay +}; + +static void a2dp_destroy_endpoint(void *user_data) +{ + struct media_endpoint *endpoint = user_data; + + endpoint->sep = NULL; + release_endpoint(endpoint); +} + +static gboolean endpoint_init_a2dp_source(struct media_endpoint *endpoint, + gboolean delay_reporting, + int *err) +{ + endpoint->sep = a2dp_add_sep(endpoint->adapter->btd_adapter, + AVDTP_SEP_TYPE_SOURCE, endpoint->codec, + delay_reporting, &a2dp_endpoint, + endpoint, a2dp_destroy_endpoint, err); + if (endpoint->sep == NULL) + return FALSE; + + return TRUE; +} + +static gboolean endpoint_init_a2dp_sink(struct media_endpoint *endpoint, + gboolean delay_reporting, + int *err) +{ + endpoint->sep = a2dp_add_sep(endpoint->adapter->btd_adapter, + AVDTP_SEP_TYPE_SINK, endpoint->codec, + delay_reporting, &a2dp_endpoint, + endpoint, a2dp_destroy_endpoint, err); + if (endpoint->sep == NULL) + return FALSE; + + return TRUE; +} + +static struct media_adapter *find_adapter(struct btd_device *device) +{ + GSList *l; + + for (l = adapters; l; l = l->next) { + struct media_adapter *adapter = l->data; + + if (adapter->btd_adapter == device_get_adapter(device)) + return adapter; + } + + return NULL; +} + +static bool endpoint_properties_exists(const char *uuid, + struct btd_device *dev, + void *user_data) +{ + struct media_adapter *adapter; + + adapter = find_adapter(dev); + if (adapter == NULL) + return false; + + if (media_adapter_find_endpoint(adapter, NULL, NULL, uuid) == NULL) + return false; + + return true; +} + +static void append_endpoint(struct media_endpoint *endpoint, + DBusMessageIter *dict) +{ + DBusMessageIter entry, var, props; + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, + &endpoint->sender); + + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "a{sv}", + &var); + + dbus_message_iter_open_container(&var, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &props); + + dict_append_entry(&props, "Path", DBUS_TYPE_OBJECT_PATH, + &endpoint->path); + dict_append_entry(&props, "Codec", DBUS_TYPE_BYTE, &endpoint->codec); + dict_append_array(&props, "Capabilities", DBUS_TYPE_BYTE, + &endpoint->capabilities, endpoint->size); + + dbus_message_iter_close_container(&var, &props); + dbus_message_iter_close_container(&entry, &var); + dbus_message_iter_close_container(dict, &entry); +} + +static bool endpoint_properties_get(const char *uuid, + struct btd_device *dev, + DBusMessageIter *iter, + void *user_data) +{ + struct media_adapter *adapter; + DBusMessageIter dict; + GSList *l; + + adapter = find_adapter(dev); + if (adapter == NULL) + return false; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + for (l = adapter->endpoints; l; l = l->next) { + struct media_endpoint *endpoint = l->data; + + if (strcasecmp(endpoint->uuid, uuid) != 0) + continue; + + append_endpoint(endpoint, &dict); + } + + dbus_message_iter_close_container(iter, &dict); + + return true; +} + +static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter, + const char *sender, + const char *path, + const char *uuid, + gboolean delay_reporting, + uint8_t codec, + uint8_t *capabilities, + int size, + int *err) +{ + struct media_endpoint *endpoint; + gboolean succeeded; + + endpoint = g_new0(struct media_endpoint, 1); + endpoint->sender = g_strdup(sender); + endpoint->path = g_strdup(path); + endpoint->uuid = g_strdup(uuid); + endpoint->codec = codec; + + if (size > 0) { + endpoint->capabilities = g_new(uint8_t, size); + memcpy(endpoint->capabilities, capabilities, size); + endpoint->size = size; + } + + endpoint->adapter = adapter; + + if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) + succeeded = endpoint_init_a2dp_source(endpoint, + delay_reporting, err); + else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) + succeeded = endpoint_init_a2dp_sink(endpoint, + delay_reporting, err); + else if (strcasecmp(uuid, HFP_AG_UUID) == 0 || + strcasecmp(uuid, HSP_AG_UUID) == 0) + succeeded = TRUE; + else if (strcasecmp(uuid, HFP_HS_UUID) == 0 || + strcasecmp(uuid, HSP_HS_UUID) == 0) + succeeded = TRUE; + else { + succeeded = FALSE; + + if (err) + *err = -EINVAL; + } + + if (!succeeded) { + media_endpoint_destroy(endpoint); + return NULL; + } + + endpoint->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), + sender, media_endpoint_exit, + endpoint, NULL); + + if (media_adapter_find_endpoint(adapter, NULL, NULL, uuid) == NULL) { + btd_profile_add_custom_prop(uuid, "a{sv}", "MediaEndpoints", + endpoint_properties_exists, + endpoint_properties_get, + NULL); + } + + adapter->endpoints = g_slist_append(adapter->endpoints, endpoint); + info("Endpoint registered: sender=%s path=%s", sender, path); + + if (err) + *err = 0; + return endpoint; +} + +static int parse_properties(DBusMessageIter *props, const char **uuid, + gboolean *delay_reporting, uint8_t *codec, + uint8_t **capabilities, int *size) +{ + gboolean has_uuid = FALSE; + gboolean has_codec = FALSE; + + while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "UUID") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&value, uuid); + has_uuid = TRUE; + } else if (strcasecmp(key, "Codec") == 0) { + if (var != DBUS_TYPE_BYTE) + return -EINVAL; + dbus_message_iter_get_basic(&value, codec); + has_codec = TRUE; + } else if (strcasecmp(key, "DelayReporting") == 0) { + if (var != DBUS_TYPE_BOOLEAN) + return -EINVAL; + dbus_message_iter_get_basic(&value, delay_reporting); + } else if (strcasecmp(key, "Capabilities") == 0) { + DBusMessageIter array; + + if (var != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(&value, &array); + dbus_message_iter_get_fixed_array(&array, capabilities, + size); + } + + dbus_message_iter_next(props); + } + + return (has_uuid && has_codec) ? 0 : -EINVAL; +} + +static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + DBusMessageIter args, props; + const char *sender, *path, *uuid; + gboolean delay_reporting = FALSE; + uint8_t codec; + uint8_t *capabilities; + int size = 0; + int err; + + sender = dbus_message_get_sender(msg); + + dbus_message_iter_init(msg, &args); + + dbus_message_iter_get_basic(&args, &path); + dbus_message_iter_next(&args); + + if (media_adapter_find_endpoint(adapter, sender, path, NULL) != NULL) + return btd_error_already_exists(msg); + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + return btd_error_invalid_args(msg); + + if (parse_properties(&props, &uuid, &delay_reporting, &codec, + &capabilities, &size) < 0) + return btd_error_invalid_args(msg); + + if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting, + codec, capabilities, size, &err) == NULL) { + if (err == -EPROTONOSUPPORT) + return btd_error_not_supported(msg); + else + return btd_error_invalid_args(msg); + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + struct media_endpoint *endpoint; + const char *sender, *path; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + sender = dbus_message_get_sender(msg); + + endpoint = media_adapter_find_endpoint(adapter, sender, path, NULL); + if (endpoint == NULL) + return btd_error_does_not_exist(msg); + + media_endpoint_remove(endpoint); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static struct media_player *media_adapter_find_player( + struct media_adapter *adapter, + const char *sender, + const char *path) +{ + GSList *l; + + for (l = adapter->players; l; l = l->next) { + struct media_player *mp = l->data; + + if (sender && g_strcmp0(mp->sender, sender) != 0) + continue; + + if (path && g_strcmp0(mp->path, path) != 0) + continue; + + return mp; + } + + return NULL; +} + +static void release_player(struct media_player *mp) +{ + DBusMessage *msg; + + DBG("sender=%s path=%s", mp->sender, mp->path); + + msg = dbus_message_new_method_call(mp->sender, mp->path, + MEDIA_PLAYER_INTERFACE, + "Release"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return; + } + + g_dbus_send_message(btd_get_dbus_connection(), msg); +} + +static void media_player_free(gpointer data) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct media_player *mp = data; + struct media_adapter *adapter = mp->adapter; + + if (mp->player) { + adapter->players = g_slist_remove(adapter->players, mp); + release_player(mp); + } + + g_dbus_remove_watch(conn, mp->watch); + g_dbus_remove_watch(conn, mp->properties_watch); + g_dbus_remove_watch(conn, mp->seek_watch); + + if (mp->track) + g_hash_table_unref(mp->track); + + if (mp->settings) + g_hash_table_unref(mp->settings); + + g_timer_destroy(mp->timer); + g_free(mp->sender); + g_free(mp->path); + g_free(mp->status); + g_free(mp->name); + g_free(mp); +} + +static void media_player_destroy(struct media_player *mp) +{ + struct media_adapter *adapter = mp->adapter; + + DBG("sender=%s path=%s", mp->sender, mp->path); + + if (mp->player) { + struct avrcp_player *player = mp->player; + mp->player = NULL; + adapter->players = g_slist_remove(adapter->players, mp); + avrcp_unregister_player(player); + return; + } + + media_player_free(mp); +} + +static void media_player_remove(void *data) +{ + struct media_player *mp = data; + + info("Player unregistered: sender=%s path=%s", mp->sender, mp->path); + + media_player_destroy(mp); +} + +static GList *list_settings(void *user_data) +{ + struct media_player *mp = user_data; + + DBG(""); + + if (mp->settings == NULL) + return NULL; + + return g_hash_table_get_keys(mp->settings); +} + +static const char *get_setting(const char *key, void *user_data) +{ + struct media_player *mp = user_data; + + DBG("%s", key); + + return g_hash_table_lookup(mp->settings, key); +} + +static const char *get_player_name(void *user_data) +{ + struct media_player *mp = user_data; + + if (!mp->name) + return "Player"; + + return mp->name; +} + +static void set_shuffle_setting(DBusMessageIter *iter, const char *value) +{ + const char *key = "Shuffle"; + dbus_bool_t val; + DBusMessageIter var; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &key); + dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, + DBUS_TYPE_BOOLEAN_AS_STRING, + &var); + val = strcasecmp(value, "off") != 0; + dbus_message_iter_append_basic(&var, DBUS_TYPE_BOOLEAN, &val); + dbus_message_iter_close_container(iter, &var); +} + +static const char *repeat_to_loop_status(const char *value) +{ + if (strcasecmp(value, "off") == 0) + return "None"; + else if (strcasecmp(value, "singletrack") == 0) + return "Track"; + else if (strcasecmp(value, "alltracks") == 0) + return "Playlist"; + else if (strcasecmp(value, "group") == 0) + return "Playlist"; + + return NULL; +} + +static void set_repeat_setting(DBusMessageIter *iter, const char *value) +{ + const char *key = "LoopStatus"; + const char *val; + DBusMessageIter var; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &key); + dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, + &var); + val = repeat_to_loop_status(value); + dbus_message_iter_append_basic(&var, DBUS_TYPE_STRING, &val); + dbus_message_iter_close_container(iter, &var); +} + +static int set_setting(const char *key, const char *value, void *user_data) +{ + struct media_player *mp = user_data; + const char *iface = MEDIA_PLAYER_INTERFACE; + DBusMessage *msg; + DBusMessageIter iter; + const char *curval; + + DBG("%s = %s", key, value); + + curval = g_hash_table_lookup(mp->settings, key); + if (!curval) + return -EINVAL; + + if (strcasecmp(curval, value) == 0) + return 0; + + msg = dbus_message_new_method_call(mp->sender, mp->path, + DBUS_INTERFACE_PROPERTIES, "Set"); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &iface); + + if (strcasecmp(key, "Shuffle") == 0) + set_shuffle_setting(&iter, value); + else if (strcasecmp(key, "Repeat") == 0) + set_repeat_setting(&iter, value); + + g_dbus_send_message(btd_get_dbus_connection(), msg); + + return 0; +} + +static GList *list_metadata(void *user_data) +{ + struct media_player *mp = user_data; + + DBG(""); + + if (mp->track == NULL) + return NULL; + + return g_hash_table_get_keys(mp->track); +} + +static uint64_t get_uid(void *user_data) +{ + struct media_player *mp = user_data; + + DBG("%p", mp->track); + + if (mp->track == NULL) + return UINT64_MAX; + + return 0; +} + +static const char *get_metadata(const char *key, void *user_data) +{ + struct media_player *mp = user_data; + + DBG("%s", key); + + if (mp->track == NULL) + return NULL; + + return g_hash_table_lookup(mp->track, key); +} + +static const char *get_status(void *user_data) +{ + struct media_player *mp = user_data; + + return mp->status; +} + +static uint32_t get_position(void *user_data) +{ + struct media_player *mp = user_data; + double timedelta; + uint32_t sec, msec; + + if (mp->status == NULL || strcasecmp(mp->status, "Playing") != 0) + return mp->position; + + timedelta = g_timer_elapsed(mp->timer, NULL); + + sec = (uint32_t) timedelta; + msec = (uint32_t) ((timedelta - sec) * 1000); + + return mp->position + sec * 1000 + msec; +} + +static uint32_t get_duration(void *user_data) +{ + struct media_player *mp = user_data; + + return mp->duration; +} + +static void set_volume(uint8_t volume, struct btd_device *dev, void *user_data) +{ + struct media_player *mp = user_data; + GSList *l; + + if (mp->volume == volume) + return; + + mp->volume = volume; + + for (l = mp->adapter->endpoints; l; l = l->next) { + struct media_endpoint *endpoint = l->data; + struct media_transport *transport; + + /* Volume is A2DP only */ + if (endpoint->sep == NULL) + continue; + + transport = find_device_transport(endpoint, dev); + if (transport == NULL) + continue; + + media_transport_update_volume(transport, volume); + } +} + +static bool media_player_send(struct media_player *mp, const char *name) +{ + DBusMessage *msg; + + msg = dbus_message_new_method_call(mp->sender, mp->path, + MEDIA_PLAYER_INTERFACE, name); + if (msg == NULL) { + error("Couldn't allocate D-Bus message"); + return false; + } + + g_dbus_send_message(btd_get_dbus_connection(), msg); + + return true; +} + +static bool play(void *user_data) +{ + struct media_player *mp = user_data; + + DBG(""); + + if (!mp->play || !mp->control) + return false; + + return media_player_send(mp, "Play"); +} + +static bool stop(void *user_data) +{ + struct media_player *mp = user_data; + + DBG(""); + + if (!mp->control) + return false; + + return media_player_send(mp, "Stop"); +} + +static bool pause(void *user_data) +{ + struct media_player *mp = user_data; + + DBG(""); + + if (!mp->pause || !mp->control) + return false; + + return media_player_send(mp, "Pause"); +} + +static bool next(void *user_data) +{ + struct media_player *mp = user_data; + + DBG(""); + + if (!mp->next || !mp->control) + return false; + + return media_player_send(mp, "Next"); +} + +static bool previous(void *user_data) +{ + struct media_player *mp = user_data; + + DBG(""); + + if (!mp->previous || !mp->control) + return false; + + return media_player_send(mp, "Previous"); +} + +static struct avrcp_player_cb player_cb = { + .list_settings = list_settings, + .get_setting = get_setting, + .set_setting = set_setting, + .list_metadata = list_metadata, + .get_uid = get_uid, + .get_metadata = get_metadata, + .get_position = get_position, + .get_duration = get_duration, + .get_status = get_status, + .get_name = get_player_name, + .set_volume = set_volume, + .play = play, + .stop = stop, + .pause = pause, + .next = next, + .previous = previous, +}; + +static void media_player_exit(DBusConnection *connection, void *user_data) +{ + struct media_player *mp = user_data; + + mp->watch = 0; + media_player_remove(mp); +} + +static gboolean set_status(struct media_player *mp, DBusMessageIter *iter) +{ + const char *value; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(iter, &value); + DBG("Status=%s", value); + + if (g_strcmp0(mp->status, value) == 0) + return TRUE; + + mp->position = get_position(mp); + g_timer_start(mp->timer); + + g_free(mp->status); + mp->status = g_strdup(value); + + avrcp_player_event(mp->player, AVRCP_EVENT_STATUS_CHANGED, mp->status); + + return TRUE; +} + +static gboolean set_position(struct media_player *mp, DBusMessageIter *iter) +{ + uint64_t value; + const char *status; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INT64) + return FALSE; + + dbus_message_iter_get_basic(iter, &value); + + value /= 1000; + + if (value > get_position(mp)) + status = "forward-seek"; + else + status = "reverse-seek"; + + mp->position = value; + g_timer_start(mp->timer); + + DBG("Position=%u", mp->position); + + if (!mp->position) { + avrcp_player_event(mp->player, + AVRCP_EVENT_TRACK_REACHED_START, NULL); + return TRUE; + } + + /* + * If position is the maximum value allowed or greater than track's + * duration, we send a track-reached-end event. + */ + if (mp->position == UINT32_MAX || mp->position >= mp->duration) { + avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_REACHED_END, + NULL); + return TRUE; + } + + /* Send a status change to force resync the position */ + avrcp_player_event(mp->player, AVRCP_EVENT_STATUS_CHANGED, status); + + return TRUE; +} + +static void set_metadata(struct media_player *mp, const char *key, + const char *value) +{ + DBG("%s=%s", key, value); + g_hash_table_replace(mp->track, g_strdup(key), g_strdup(value)); +} + +static gboolean parse_string_metadata(struct media_player *mp, const char *key, + DBusMessageIter *iter) +{ + const char *value; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(iter, &value); + + set_metadata(mp, key, value); + + return TRUE; +} + +static gboolean parse_array_metadata(struct media_player *mp, const char *key, + DBusMessageIter *iter) +{ + DBusMessageIter array; + const char *value; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return FALSE; + + dbus_message_iter_recurse(iter, &array); + + if (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_INVALID) + return TRUE; + + if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(&array, &value); + + set_metadata(mp, key, value); + + return TRUE; +} + +static gboolean parse_int64_metadata(struct media_player *mp, const char *key, + DBusMessageIter *iter) +{ + uint64_t value; + char valstr[20]; + int type; + + type = dbus_message_iter_get_arg_type(iter); + if (type == DBUS_TYPE_UINT64) + warn("expected DBUS_TYPE_INT64 got DBUS_TYPE_UINT64"); + else if (type != DBUS_TYPE_INT64) + return FALSE; + + dbus_message_iter_get_basic(iter, &value); + + if (strcasecmp(key, "Duration") == 0) { + value /= 1000; + mp->duration = value; + } + + snprintf(valstr, 20, "%" PRIu64, value); + + set_metadata(mp, key, valstr); + + return TRUE; +} + +static gboolean parse_int32_metadata(struct media_player *mp, const char *key, + DBusMessageIter *iter) +{ + uint32_t value; + char valstr[20]; + int type; + + type = dbus_message_iter_get_arg_type(iter); + if (type == DBUS_TYPE_UINT32) + warn("expected DBUS_TYPE_INT32 got DBUS_TYPE_UINT32"); + else if (type != DBUS_TYPE_INT32) + return FALSE; + + dbus_message_iter_get_basic(iter, &value); + + snprintf(valstr, 20, "%u", value); + + set_metadata(mp, key, valstr); + + return TRUE; +} + +static gboolean parse_player_metadata(struct media_player *mp, + DBusMessageIter *iter) +{ + DBusMessageIter dict; + DBusMessageIter var; + int ctype; + gboolean title = FALSE; + uint64_t uid; + + ctype = dbus_message_iter_get_arg_type(iter); + if (ctype != DBUS_TYPE_ARRAY) + return FALSE; + + dbus_message_iter_recurse(iter, &dict); + + if (mp->track != NULL) + g_hash_table_unref(mp->track); + + mp->track = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + g_free); + + while ((ctype = dbus_message_iter_get_arg_type(&dict)) != + DBUS_TYPE_INVALID) { + DBusMessageIter entry; + const char *key; + + if (ctype != DBUS_TYPE_DICT_ENTRY) + return FALSE; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) + return FALSE; + + dbus_message_iter_recurse(&entry, &var); + + if (strcasecmp(key, "xesam:title") == 0) { + if (!parse_string_metadata(mp, "Title", &var)) + return FALSE; + title = TRUE; + } else if (strcasecmp(key, "xesam:artist") == 0) { + if (!parse_array_metadata(mp, "Artist", &var)) + return FALSE; + } else if (strcasecmp(key, "xesam:album") == 0) { + if (!parse_string_metadata(mp, "Album", &var)) + return FALSE; + } else if (strcasecmp(key, "xesam:genre") == 0) { + if (!parse_array_metadata(mp, "Genre", &var)) + return FALSE; + } else if (strcasecmp(key, "mpris:length") == 0) { + if (!parse_int64_metadata(mp, "Duration", &var)) + return FALSE; + } else if (strcasecmp(key, "xesam:trackNumber") == 0) { + if (!parse_int32_metadata(mp, "TrackNumber", &var)) + return FALSE; + } else + DBG("%s not supported, ignoring", key); + + dbus_message_iter_next(&dict); + } + + if (title == FALSE) + g_hash_table_insert(mp->track, g_strdup("Title"), + g_strdup("")); + + mp->position = 0; + g_timer_start(mp->timer); + uid = get_uid(mp); + + avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_CHANGED, &uid); + avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_REACHED_START, NULL); + + return TRUE; +} + +static gboolean set_property(struct media_player *mp, const char *key, + const char *value) +{ + const char *curval; + + curval = g_hash_table_lookup(mp->settings, key); + if (g_strcmp0(curval, value) == 0) + return TRUE; + + DBG("%s=%s", key, value); + + g_hash_table_replace(mp->settings, g_strdup(key), g_strdup(value)); + + avrcp_player_event(mp->player, AVRCP_EVENT_SETTINGS_CHANGED, key); + + return TRUE; +} + +static gboolean set_shuffle(struct media_player *mp, DBusMessageIter *iter) +{ + dbus_bool_t value; + const char *strvalue; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) + return FALSE; + + dbus_message_iter_get_basic(iter, &value); + + strvalue = value ? "alltracks" : "off"; + + return set_property(mp, "Shuffle", strvalue); +} + +static const char *loop_status_to_repeat(const char *value) +{ + if (strcasecmp(value, "None") == 0) + return "off"; + else if (strcasecmp(value, "Track") == 0) + return "singletrack"; + else if (strcasecmp(value, "Playlist") == 0) + return "alltracks"; + + return NULL; +} + +static gboolean set_repeat(struct media_player *mp, DBusMessageIter *iter) +{ + const char *value; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(iter, &value); + + value = loop_status_to_repeat(value); + if (value == NULL) + return FALSE; + + return set_property(mp, "Repeat", value); +} + +static gboolean set_flag(struct media_player *mp, DBusMessageIter *iter, + bool *var) +{ + dbus_bool_t value; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) + return FALSE; + + dbus_message_iter_get_basic(iter, &value); + + *var = value; + + return TRUE; +} + +static gboolean set_name(struct media_player *mp, DBusMessageIter *iter) +{ + const char *value; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(iter, &value); + + if (g_strcmp0(mp->name, value) == 0) + return TRUE; + + g_free(mp->name); + + mp->name = g_strdup(value); + + return TRUE; +} + +static gboolean set_player_property(struct media_player *mp, const char *key, + DBusMessageIter *entry) +{ + DBusMessageIter var; + + if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT) + return FALSE; + + dbus_message_iter_recurse(entry, &var); + + if (strcasecmp(key, "PlaybackStatus") == 0) + return set_status(mp, &var); + + if (strcasecmp(key, "Position") == 0) + return set_position(mp, &var); + + if (strcasecmp(key, "Metadata") == 0) + return parse_player_metadata(mp, &var); + + if (strcasecmp(key, "Shuffle") == 0) + return set_shuffle(mp, &var); + + if (strcasecmp(key, "LoopStatus") == 0) + return set_repeat(mp, &var); + + if (strcasecmp(key, "CanPlay") == 0) + return set_flag(mp, &var, &mp->play); + + if (strcasecmp(key, "CanPause") == 0) + return set_flag(mp, &var, &mp->pause); + + if (strcasecmp(key, "CanGoNext") == 0) + return set_flag(mp, &var, &mp->next); + + if (strcasecmp(key, "CanGoPrevious") == 0) + return set_flag(mp, &var, &mp->previous); + + if (strcasecmp(key, "CanControl") == 0) + return set_flag(mp, &var, &mp->control); + + if (strcasecmp(key, "Identity") == 0) + return set_name(mp, &var); + + DBG("%s not supported, ignoring", key); + + return TRUE; +} + +static gboolean parse_player_properties(struct media_player *mp, + DBusMessageIter *iter) +{ + DBusMessageIter dict; + int ctype; + + ctype = dbus_message_iter_get_arg_type(iter); + if (ctype != DBUS_TYPE_ARRAY) + return FALSE; + + dbus_message_iter_recurse(iter, &dict); + + while ((ctype = dbus_message_iter_get_arg_type(&dict)) != + DBUS_TYPE_INVALID) { + DBusMessageIter entry; + const char *key; + + if (ctype != DBUS_TYPE_DICT_ENTRY) + return FALSE; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + if (set_player_property(mp, key, &entry) == FALSE) + return FALSE; + + dbus_message_iter_next(&dict); + } + + return TRUE; +} + +static gboolean properties_changed(DBusConnection *connection, DBusMessage *msg, + void *user_data) +{ + struct media_player *mp = user_data; + DBusMessageIter iter; + + DBG("sender=%s path=%s", mp->sender, mp->path); + + dbus_message_iter_init(msg, &iter); + + dbus_message_iter_next(&iter); + + parse_player_properties(mp, &iter); + + return TRUE; +} + +static gboolean position_changed(DBusConnection *connection, DBusMessage *msg, + void *user_data) +{ + struct media_player *mp = user_data; + DBusMessageIter iter; + + DBG("sender=%s path=%s", mp->sender, mp->path); + + dbus_message_iter_init(msg, &iter); + + set_position(mp, &iter); + + return TRUE; +} + +static struct media_player *media_player_create(struct media_adapter *adapter, + const char *sender, + const char *path, + int *err) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct media_player *mp; + + mp = g_new0(struct media_player, 1); + mp->adapter = adapter; + mp->sender = g_strdup(sender); + mp->path = g_strdup(path); + mp->timer = g_timer_new(); + + mp->watch = g_dbus_add_disconnect_watch(conn, sender, + media_player_exit, mp, + NULL); + mp->properties_watch = g_dbus_add_properties_watch(conn, sender, + path, MEDIA_PLAYER_INTERFACE, + properties_changed, + mp, NULL); + mp->seek_watch = g_dbus_add_signal_watch(conn, sender, + path, MEDIA_PLAYER_INTERFACE, + "Seeked", position_changed, + mp, NULL); + mp->player = avrcp_register_player(adapter->btd_adapter, &player_cb, + mp, media_player_free); + if (!mp->player) { + if (err) + *err = -EPROTONOSUPPORT; + media_player_destroy(mp); + return NULL; + } + + mp->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + g_free); + + adapter->players = g_slist_append(adapter->players, mp); + + info("Player registered: sender=%s path=%s", sender, path); + + if (err) + *err = 0; + + return mp; +} + +static DBusMessage *register_player(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + struct media_player *mp; + DBusMessageIter args; + const char *sender, *path; + int err; + + sender = dbus_message_get_sender(msg); + + dbus_message_iter_init(msg, &args); + + dbus_message_iter_get_basic(&args, &path); + dbus_message_iter_next(&args); + + if (media_adapter_find_player(adapter, sender, path) != NULL) + return btd_error_already_exists(msg); + + mp = media_player_create(adapter, sender, path, &err); + if (mp == NULL) { + if (err == -EPROTONOSUPPORT) + return btd_error_not_supported(msg); + else + return btd_error_invalid_args(msg); + } + + if (parse_player_properties(mp, &args) == FALSE) { + media_player_destroy(mp); + return btd_error_invalid_args(msg); + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *unregister_player(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + struct media_player *player; + const char *sender, *path; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + sender = dbus_message_get_sender(msg); + + player = media_adapter_find_player(adapter, sender, path); + if (player == NULL) + return btd_error_does_not_exist(msg); + + media_player_remove(player); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static void app_free(void *data) +{ + struct media_app *app = data; + + queue_destroy(app->endpoints, media_endpoint_remove); + queue_destroy(app->players, media_player_remove); + + if (app->client) { + g_dbus_client_set_disconnect_watch(app->client, NULL, NULL); + g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, + NULL, NULL); + g_dbus_client_set_ready_watch(app->client, NULL, NULL); + g_dbus_client_unref(app->client); + } + + if (app->reg) + dbus_message_unref(app->reg); + + g_free(app->sender); + g_free(app->path); + + free(app); +} + +static void client_disconnect_cb(DBusConnection *conn, void *user_data) +{ + struct media_app *app = user_data; + struct media_adapter *adapter = app->adapter; + + DBG("Client disconnected"); + + if (queue_remove(adapter->apps, app)) + app_free(app); +} + +static void app_register_endpoint(void *data, void *user_data) +{ + struct media_app *app = user_data; + GDBusProxy *proxy = data; + const char *iface = g_dbus_proxy_get_interface(proxy); + const char *path = g_dbus_proxy_get_path(proxy); + const char *uuid; + gboolean delay_reporting = FALSE; + uint8_t codec; + uint8_t *capabilities = NULL; + int size = 0; + DBusMessageIter iter, array; + struct media_endpoint *endpoint; + + if (app->err) + return; + + if (strcmp(iface, MEDIA_ENDPOINT_INTERFACE)) + return; + + /* Parse properties */ + if (!g_dbus_proxy_get_property(proxy, "UUID", &iter)) + goto fail; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + goto fail; + + dbus_message_iter_get_basic(&iter, &uuid); + + if (!g_dbus_proxy_get_property(proxy, "Codec", &iter)) + goto fail; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_basic(&iter, &codec); + + /* DelayReporting and Capabilities are considered optional */ + if (g_dbus_proxy_get_property(proxy, "DelayReporting", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) + goto fail; + + dbus_message_iter_get_basic(&iter, &delay_reporting); + } + + if (g_dbus_proxy_get_property(proxy, "Capabilities", &iter)) { + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + goto fail; + + dbus_message_iter_recurse(&iter, &array); + dbus_message_iter_get_fixed_array(&array, &capabilities, &size); + } + + endpoint = media_endpoint_create(app->adapter, app->sender, path, uuid, + delay_reporting, codec, capabilities, + size, &app->err); + if (!endpoint) { + error("Unable to register endpoint %s:%s: %s", app->sender, + path, strerror(-app->err)); + return; + } + + queue_push_tail(app->endpoints, endpoint); + + return; + +fail: + app->err = -EINVAL; +} + +static void app_register_player(void *data, void *user_data) +{ + struct media_app *app = user_data; + GDBusProxy *proxy = data; + const char *iface = g_dbus_proxy_get_interface(proxy); + const char *path = g_dbus_proxy_get_path(proxy); + struct media_player *player; + DBusMessageIter iter; + + if (app->err) + return; + + if (strcmp(iface, MEDIA_PLAYER_INTERFACE)) + return; + + player = media_player_create(app->adapter, app->sender, path, + &app->err); + if (!player) + return; + + if (g_dbus_proxy_get_property(proxy, "PlaybackStatus", &iter)) { + if (!set_status(player, &iter)) + goto fail; + } + + if (g_dbus_proxy_get_property(proxy, "Position", &iter)) { + if (!set_position(player, &iter)) + goto fail; + } + + if (g_dbus_proxy_get_property(proxy, "Metadata", &iter)) { + if (!parse_player_metadata(player, &iter)) + goto fail; + } + + if (g_dbus_proxy_get_property(proxy, "Shuffle", &iter)) { + if (!set_shuffle(player, &iter)) + goto fail; + } + + if (g_dbus_proxy_get_property(proxy, "LoopStatus", &iter)) { + if (!set_repeat(player, &iter)) + goto fail; + } + + if (g_dbus_proxy_get_property(proxy, "CanPlay", &iter)) { + if (!set_flag(player, &iter, &player->play)) + goto fail; + } + + if (g_dbus_proxy_get_property(proxy, "CanPause", &iter)) { + if (!set_flag(player, &iter, &player->pause)) + goto fail; + } + + if (g_dbus_proxy_get_property(proxy, "CanGoNext", &iter)) { + if (!set_flag(player, &iter, &player->next)) + goto fail; + } + + if (g_dbus_proxy_get_property(proxy, "CanGoPrevious", &iter)) { + if (!set_flag(player, &iter, &player->previous)) + goto fail; + } + + if (g_dbus_proxy_get_property(proxy, "CanControl", &iter)) { + if (!set_flag(player, &iter, &player->control)) + goto fail; + } + + if (g_dbus_proxy_get_property(proxy, "Identity", &iter)) { + if (!set_name(player, &iter)) + goto fail; + } + + queue_push_tail(app->players, player); + + return; +fail: + app->err = -EINVAL; + error("Unable to register player %s:%s: %s", app->sender, path, + strerror(-app->err)); + media_player_destroy(player); +} + +static void remove_app(void *data) +{ + struct media_app *app = data; + + /* + * Set callback to NULL to avoid potential race condition + * when calling remove_app and GDBusClient unref. + */ + g_dbus_client_set_disconnect_watch(app->client, NULL, NULL); + + /* + * Set proxy handlers to NULL, so that this gets called only once when + * the first proxy that belongs to this service gets removed. + */ + g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, NULL, NULL); + + + queue_remove(app->adapter->apps, app); + + app_free(app); +} + +static void client_ready_cb(GDBusClient *client, void *user_data) +{ + struct media_app *app = user_data; + DBusMessage *reply; + bool fail = false; + + /* + * Process received objects + */ + if (queue_isempty(app->proxies)) { + error("No object received"); + fail = true; + reply = btd_error_failed(app->reg, "No object received"); + goto reply; + } + + queue_foreach(app->proxies, app_register_endpoint, app); + queue_foreach(app->proxies, app_register_player, app); + + if (app->err) { + if (app->err == -EPROTONOSUPPORT) + reply = btd_error_not_supported(app->reg); + else + reply = btd_error_invalid_args(app->reg); + goto reply; + } + + if ((queue_isempty(app->endpoints) && queue_isempty(app->players))) { + error("No valid external Media objects found"); + fail = true; + reply = btd_error_failed(app->reg, + "No valid media object found"); + goto reply; + } + + DBG("Media application registered: %s:%s", app->sender, app->path); + + reply = dbus_message_new_method_return(app->reg); + +reply: + g_dbus_send_message(btd_get_dbus_connection(), reply); + dbus_message_unref(app->reg); + app->reg = NULL; + + if (fail) + remove_app(app); +} + +static void proxy_added_cb(GDBusProxy *proxy, void *user_data) +{ + struct media_app *app = user_data; + const char *iface, *path; + + if (app->err) + return; + + queue_push_tail(app->proxies, proxy); + + iface = g_dbus_proxy_get_interface(proxy); + path = g_dbus_proxy_get_path(proxy); + + DBG("Proxy added: %s, iface: %s", path, iface); +} + +static bool match_endpoint_by_path(const void *a, const void *b) +{ + const struct media_endpoint *endpoint = a; + const char *path = b; + + return !strcmp(endpoint->path, path); +} + +static bool match_player_by_path(const void *a, const void *b) +{ + const struct media_player *player = a; + const char *path = b; + + return !strcmp(player->path, path); +} + +static void proxy_removed_cb(GDBusProxy *proxy, void *user_data) +{ + struct media_app *app = user_data; + struct media_endpoint *endpoint; + struct media_player *player; + const char *iface, *path; + + iface = g_dbus_proxy_get_interface(proxy); + path = g_dbus_proxy_get_path(proxy); + + if (!strcmp(iface, MEDIA_ENDPOINT_INTERFACE)) { + endpoint = queue_remove_if(app->endpoints, + match_endpoint_by_path, + (void *) path); + if (!endpoint) + return; + + if (!g_slist_find(app->adapter->endpoints, endpoint)) + return; + + DBG("Proxy removed - removing endpoint: %s", endpoint->path); + + media_endpoint_remove(endpoint); + } else if (!strcmp(iface, MEDIA_PLAYER_INTERFACE)) { + player = queue_remove_if(app->players, match_player_by_path, + (void *) path); + if (!player) + return; + + if (!g_slist_find(app->adapter->players, player)) + return; + + DBG("Proxy removed - removing player: %s", player->path); + + media_player_remove(player); + } +} + +static struct media_app *create_app(DBusConnection *conn, DBusMessage *msg, + const char *path) +{ + struct media_app *app; + const char *sender = dbus_message_get_sender(msg); + + if (!path || !g_str_has_prefix(path, "/")) + return NULL; + + app = new0(struct media_app, 1); + + app->client = g_dbus_client_new_full(conn, sender, path, path); + if (!app->client) + goto fail; + + app->sender = g_strdup(sender); + if (!app->sender) + goto fail; + + app->path = g_strdup(path); + if (!app->path) + goto fail; + + app->proxies = queue_new(); + app->endpoints = queue_new(); + app->players = queue_new(); + app->reg = dbus_message_ref(msg); + + g_dbus_client_set_disconnect_watch(app->client, client_disconnect_cb, + app); + g_dbus_client_set_proxy_handlers(app->client, proxy_added_cb, + proxy_removed_cb, NULL, app); + g_dbus_client_set_ready_watch(app->client, client_ready_cb, app); + + return app; + +fail: + app_free(app); + return NULL; +} + +struct match_data { + const char *path; + const char *sender; +}; + +static bool match_app(const void *a, const void *b) +{ + const struct media_app *app = a; + const struct match_data *data = b; + + return g_strcmp0(app->path, data->path) == 0 && + g_strcmp0(app->sender, data->sender) == 0; +} + +static DBusMessage *register_app(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct media_adapter *adapter = user_data; + const char *sender = dbus_message_get_sender(msg); + DBusMessageIter args; + const char *path; + struct media_app *app; + struct match_data match_data; + + if (!dbus_message_iter_init(msg, &args)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&args, &path); + + match_data.path = path; + match_data.sender = sender; + + if (queue_find(adapter->apps, match_app, &match_data)) + return btd_error_already_exists(msg); + + dbus_message_iter_next(&args); + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) + return btd_error_invalid_args(msg); + + app = create_app(conn, msg, path); + if (!app) + return btd_error_failed(msg, "Failed to register application"); + + DBG("Registering application: %s:%s", sender, path); + + app->adapter = adapter; + queue_push_tail(adapter->apps, app); + + return NULL; +} + +static DBusMessage *unregister_app(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct media_adapter *adapter = user_data; + const char *sender = dbus_message_get_sender(msg); + const char *path; + DBusMessageIter args; + struct media_app *app; + struct match_data match_data; + + if (!dbus_message_iter_init(msg, &args)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&args, &path); + + match_data.path = path; + match_data.sender = sender; + + app = queue_remove_if(adapter->apps, match_app, &match_data); + if (!app) + return btd_error_does_not_exist(msg); + + app_free(app); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable media_methods[] = { + { GDBUS_METHOD("RegisterEndpoint", + GDBUS_ARGS({ "endpoint", "o" }, { "properties", "a{sv}" }), + NULL, register_endpoint) }, + { GDBUS_METHOD("UnregisterEndpoint", + GDBUS_ARGS({ "endpoint", "o" }), NULL, unregister_endpoint) }, + { GDBUS_METHOD("RegisterPlayer", + GDBUS_ARGS({ "player", "o" }, { "properties", "a{sv}" }), + NULL, register_player) }, + { GDBUS_METHOD("UnregisterPlayer", + GDBUS_ARGS({ "player", "o" }), NULL, unregister_player) }, + { GDBUS_ASYNC_METHOD("RegisterApplication", + GDBUS_ARGS({ "application", "o" }, + { "options", "a{sv}" }), + NULL, register_app) }, + { GDBUS_ASYNC_METHOD("UnregisterApplication", + GDBUS_ARGS({ "application", "o" }), + NULL, unregister_app) }, + { }, +}; + +static void path_free(void *data) +{ + struct media_adapter *adapter = data; + + while (adapter->endpoints) + release_endpoint(adapter->endpoints->data); + + while (adapter->players) + media_player_destroy(adapter->players->data); + + adapters = g_slist_remove(adapters, adapter); + + btd_adapter_unref(adapter->btd_adapter); + g_free(adapter); +} + +int media_register(struct btd_adapter *btd_adapter) +{ + struct media_adapter *adapter; + + adapter = g_new0(struct media_adapter, 1); + adapter->btd_adapter = btd_adapter_ref(btd_adapter); + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + adapter_get_path(btd_adapter), + MEDIA_INTERFACE, + media_methods, NULL, NULL, + adapter, path_free)) { + error("D-Bus failed to register %s path", + adapter_get_path(btd_adapter)); + path_free(adapter); + return -1; + } + + adapters = g_slist_append(adapters, adapter); + + return 0; +} + +void media_unregister(struct btd_adapter *btd_adapter) +{ + GSList *l; + + for (l = adapters; l; l = l->next) { + struct media_adapter *adapter = l->data; + + if (adapter->btd_adapter == btd_adapter) { + g_dbus_unregister_interface(btd_get_dbus_connection(), + adapter_get_path(btd_adapter), + MEDIA_INTERFACE); + return; + } + } +} + +struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint) +{ + return endpoint->sep; +} + +const char *media_endpoint_get_uuid(struct media_endpoint *endpoint) +{ + return endpoint->uuid; +} + +uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint) +{ + return endpoint->codec; +} diff --git a/profiles/audio/media.h b/profiles/audio/media.h new file mode 100644 index 0000000..dd630d4 --- /dev/null +++ b/profiles/audio/media.h @@ -0,0 +1,35 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct media_endpoint; + +typedef void (*media_endpoint_cb_t) (struct media_endpoint *endpoint, + void *ret, int size, void *user_data); + +int media_register(struct btd_adapter *btd_adapter); +void media_unregister(struct btd_adapter *btd_adapter); + +struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint); +const char *media_endpoint_get_uuid(struct media_endpoint *endpoint); +uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint); diff --git a/profiles/audio/player.c b/profiles/audio/player.c new file mode 100644 index 0000000..09ee979 --- /dev/null +++ b/profiles/audio/player.c @@ -0,0 +1,1942 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * Copyright (C) 2012-2012 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gdbus/gdbus.h" + +#include "src/log.h" +#include "src/dbus-common.h" +#include "src/error.h" + +#include "player.h" + +#define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1" +#define MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1" +#define MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1" + +struct player_callback { + const struct media_player_callback *cbs; + void *user_data; +}; + +struct pending_req { + GDBusPendingPropertySet id; + const char *key; + const char *value; +}; + +struct media_item { + struct media_player *player; + char *path; /* Item object path */ + char *name; /* Item name */ + player_item_type_t type; /* Item type */ + player_folder_type_t folder_type; /* Folder type */ + bool playable; /* Item playable flag */ + uint64_t uid; /* Item uid */ + GHashTable *metadata; /* Item metadata */ +}; + +struct media_folder { + struct media_folder *parent; + struct media_item *item; /* Folder item */ + uint32_t number_of_items;/* Number of items */ + GSList *subfolders; + GSList *items; + DBusMessage *msg; +}; + +struct media_player { + char *device; /* Device path */ + char *name; /* Player name */ + char *type; /* Player type */ + char *subtype; /* Player subtype */ + bool browsable; /* Player browsing feature */ + bool searchable; /* Player searching feature */ + struct media_folder *scope; /* Player current scope */ + struct media_folder *folder; /* Player current folder */ + struct media_folder *search; /* Player search folder */ + struct media_folder *playlist; /* Player current playlist */ + char *path; /* Player object path */ + GHashTable *settings; /* Player settings */ + GHashTable *track; /* Player current track */ + char *status; + uint32_t position; + GTimer *progress; + guint process_id; + struct player_callback *cb; + GSList *pending; + GSList *folders; +}; + +static void append_track(void *key, void *value, void *user_data) +{ + DBusMessageIter *dict = user_data; + const char *strkey = key; + + if (strcasecmp(strkey, "Duration") == 0 || + strcasecmp(strkey, "TrackNumber") == 0 || + strcasecmp(strkey, "NumberOfTracks") == 0) { + uint32_t num = atoi(value); + dict_append_entry(dict, key, DBUS_TYPE_UINT32, &num); + } else if (strcasecmp(strkey, "Item") == 0) { + dict_append_entry(dict, key, DBUS_TYPE_OBJECT_PATH, &value); + } else { + dict_append_entry(dict, key, DBUS_TYPE_STRING, &value); + } +} + +static struct pending_req *find_pending(struct media_player *mp, + const char *key) +{ + GSList *l; + + for (l = mp->pending; l; l = l->next) { + struct pending_req *p = l->data; + + if (strcasecmp(key, p->key) == 0) + return p; + } + + return NULL; +} + +static struct pending_req *pending_new(GDBusPendingPropertySet id, + const char *key, const char *value) +{ + struct pending_req *p; + + p = g_new0(struct pending_req, 1); + p->id = id; + p->key = key; + p->value = value; + + return p; +} + +static uint32_t media_player_get_position(struct media_player *mp) +{ + double timedelta; + uint32_t sec, msec; + + if (g_strcmp0(mp->status, "playing") != 0 || + mp->position == UINT32_MAX) + return mp->position; + + timedelta = g_timer_elapsed(mp->progress, NULL); + + sec = (uint32_t) timedelta; + msec = (uint32_t) ((timedelta - sec) * 1000); + + return mp->position + sec * 1000 + msec; +} + +static gboolean get_position(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + uint32_t position; + + position = media_player_get_position(mp); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &position); + + return TRUE; +} + +static gboolean status_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_player *mp = data; + + return mp->status != NULL; +} + +static gboolean get_status(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + + if (mp->status == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->status); + + return TRUE; +} + +static gboolean setting_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_player *mp = data; + const char *value; + + value = g_hash_table_lookup(mp->settings, property->name); + + return value ? TRUE : FALSE; +} + +static gboolean get_setting(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + const char *value; + + value = g_hash_table_lookup(mp->settings, property->name); + if (value == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &value); + + return TRUE; +} + +static void player_set_setting(struct media_player *mp, + GDBusPendingPropertySet id, + const char *key, const char *value) +{ + struct player_callback *cb = mp->cb; + struct pending_req *p; + + if (cb == NULL || cb->cbs->set_setting == NULL) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".NotSupported", + "Operation is not supported"); + return; + } + + p = find_pending(mp, key); + if (p != NULL) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InProgress", + "Operation already in progress"); + return; + } + + if (!cb->cbs->set_setting(mp, key, value, cb->user_data)) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + p = pending_new(id, key, value); + + mp->pending = g_slist_append(mp->pending, p); +} + +static void set_setting(const GDBusPropertyTable *property, + DBusMessageIter *iter, GDBusPendingPropertySet id, + void *data) +{ + struct media_player *mp = data; + const char *value, *current; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(iter, &value); + + current = g_hash_table_lookup(mp->settings, property->name); + if (g_strcmp0(current, value) == 0) { + g_dbus_pending_property_success(id); + return; + } + + player_set_setting(mp, id, property->name, value); +} + +static gboolean track_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_player *mp = data; + + return g_hash_table_size(mp->track) != 0; +} + +static gboolean get_track(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + g_hash_table_foreach(mp->track, append_track, &dict); + + dbus_message_iter_close_container(iter, &dict); + + return TRUE; +} + +static gboolean get_device(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &mp->device); + + return TRUE; +} + +static gboolean name_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_player *mp = data; + + return mp->name != NULL; +} + +static gboolean get_name(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + + if (mp->name == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->name); + + return TRUE; +} + +static gboolean type_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_player *mp = data; + + return mp->type != NULL; +} + +static gboolean get_type(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + + if (mp->type == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->type); + + return TRUE; +} + +static gboolean subtype_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_player *mp = data; + + return mp->subtype != NULL; +} + +static gboolean get_subtype(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + + if (mp->subtype == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->subtype); + + return TRUE; +} + +static gboolean browsable_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_player *mp = data; + + return mp->folder != NULL; +} + +static gboolean get_browsable(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + dbus_bool_t value; + + if (mp->folder == NULL) + return FALSE; + + value = mp->browsable; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); + + return TRUE; +} + +static gboolean searchable_exists(const GDBusPropertyTable *property, + void *data) +{ + struct media_player *mp = data; + + return mp->folder != NULL; +} + +static gboolean get_searchable(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + dbus_bool_t value; + + if (mp->folder == NULL) + return FALSE; + + value = mp->searchable; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); + + return TRUE; +} + +static gboolean playlist_exists(const GDBusPropertyTable *property, + void *data) +{ + struct media_player *mp = data; + + return mp->playlist != NULL; +} + +static gboolean get_playlist(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + struct media_folder *playlist = mp->playlist; + + if (playlist == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &playlist->item->path); + + return TRUE; +} + +static DBusMessage *media_player_play(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->play == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->play(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *media_player_pause(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->pause == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->pause(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *media_player_stop(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->stop == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->stop(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *media_player_next(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->next == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->next(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *media_player_previous(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->previous == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->previous(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *media_player_fast_forward(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->fast_forward == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->fast_forward(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *media_player_rewind(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->rewind == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->rewind(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static void parse_folder_list(gpointer data, gpointer user_data) +{ + struct media_item *item = data; + DBusMessageIter *array = user_data; + DBusMessageIter entry; + + dbus_message_iter_open_container(array, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + + dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH, + &item->path); + + g_dbus_get_properties(btd_get_dbus_connection(), item->path, + MEDIA_ITEM_INTERFACE, &entry); + + dbus_message_iter_close_container(array, &entry); +} + +void media_player_list_complete(struct media_player *mp, GSList *items, + int err) +{ + struct media_folder *folder = mp->scope; + DBusMessage *reply; + DBusMessageIter iter, array; + + if (folder == NULL || folder->msg == NULL) + return; + + if (err < 0) { + reply = btd_error_failed(folder->msg, strerror(-err)); + goto done; + } + + reply = dbus_message_new_method_return(folder->msg); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_OBJECT_PATH_AS_STRING + DBUS_TYPE_ARRAY_AS_STRING + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &array); + + g_slist_foreach(items, parse_folder_list, &array); + dbus_message_iter_close_container(&iter, &array); + +done: + g_dbus_send_message(btd_get_dbus_connection(), reply); + dbus_message_unref(folder->msg); + folder->msg = NULL; +} + +static struct media_item * +media_player_create_subfolder(struct media_player *mp, const char *name, + uint64_t uid) +{ + struct media_folder *folder = mp->scope; + struct media_item *item; + char *path; + + path = g_strdup_printf("%s/%s", folder->item->name, name); + + DBG("%s", path); + + item = media_player_create_item(mp, path, PLAYER_ITEM_TYPE_FOLDER, + uid); + g_free(path); + + return item; +} + +void media_player_search_complete(struct media_player *mp, int ret) +{ + struct media_folder *folder = mp->scope; + struct media_folder *search = mp->search; + DBusMessage *reply; + + if (folder == NULL || folder->msg == NULL) + return; + + if (ret < 0) { + reply = btd_error_failed(folder->msg, strerror(-ret)); + goto done; + } + + if (search == NULL) { + search = g_new0(struct media_folder, 1); + search->item = media_player_create_subfolder(mp, "search", 0); + mp->search = search; + mp->folders = g_slist_prepend(mp->folders, search); + } + + search->number_of_items = ret; + + reply = g_dbus_create_reply(folder->msg, + DBUS_TYPE_OBJECT_PATH, &search->item->path, + DBUS_TYPE_INVALID); + +done: + g_dbus_send_message(btd_get_dbus_connection(), reply); + dbus_message_unref(folder->msg); + folder->msg = NULL; +} + +void media_player_total_items_complete(struct media_player *mp, + uint32_t num_of_items) +{ + struct media_folder *folder = mp->scope; + + if (folder == NULL || folder->msg == NULL) + return; + + if (folder->number_of_items != num_of_items) { + folder->number_of_items = num_of_items; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + mp->path, MEDIA_FOLDER_INTERFACE, + "NumberOfItems"); + } +} + +static const GDBusMethodTable media_player_methods[] = { + { GDBUS_METHOD("Play", NULL, NULL, media_player_play) }, + { GDBUS_METHOD("Pause", NULL, NULL, media_player_pause) }, + { GDBUS_METHOD("Stop", NULL, NULL, media_player_stop) }, + { GDBUS_METHOD("Next", NULL, NULL, media_player_next) }, + { GDBUS_METHOD("Previous", NULL, NULL, media_player_previous) }, + { GDBUS_METHOD("FastForward", NULL, NULL, media_player_fast_forward) }, + { GDBUS_METHOD("Rewind", NULL, NULL, media_player_rewind) }, + { } +}; + +static const GDBusSignalTable media_player_signals[] = { + { } +}; + +static const GDBusPropertyTable media_player_properties[] = { + { "Name", "s", get_name, NULL, name_exists }, + { "Type", "s", get_type, NULL, type_exists }, + { "Subtype", "s", get_subtype, NULL, subtype_exists }, + { "Position", "u", get_position, NULL, NULL }, + { "Status", "s", get_status, NULL, status_exists }, + { "Equalizer", "s", get_setting, set_setting, setting_exists }, + { "Repeat", "s", get_setting, set_setting, setting_exists }, + { "Shuffle", "s", get_setting, set_setting, setting_exists }, + { "Scan", "s", get_setting, set_setting, setting_exists }, + { "Track", "a{sv}", get_track, NULL, track_exists }, + { "Device", "o", get_device, NULL, NULL }, + { "Browsable", "b", get_browsable, NULL, browsable_exists }, + { "Searchable", "b", get_searchable, NULL, searchable_exists }, + { "Playlist", "o", get_playlist, NULL, playlist_exists }, + { } +}; + +static DBusMessage *media_folder_search(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_player *mp = data; + struct media_folder *folder = mp->scope; + struct player_callback *cb = mp->cb; + DBusMessageIter iter; + const char *string; + int err; + + dbus_message_iter_init(msg, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &string); + + if (!mp->searchable || folder != mp->folder || !cb->cbs->search) + return btd_error_not_supported(msg); + + if (folder->msg != NULL) + return btd_error_failed(msg, strerror(EINVAL)); + + err = cb->cbs->search(mp, string, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + folder->msg = dbus_message_ref(msg); + + return NULL; +} + +static int parse_filters(struct media_player *player, DBusMessageIter *iter, + uint32_t *start, uint32_t *end) +{ + struct media_folder *folder = player->scope; + DBusMessageIter dict; + int ctype; + + *start = 0; + *end = folder->number_of_items ? folder->number_of_items - 1 : + UINT32_MAX; + + ctype = dbus_message_iter_get_arg_type(iter); + if (ctype != DBUS_TYPE_ARRAY) + return FALSE; + + dbus_message_iter_recurse(iter, &dict); + + while ((ctype = dbus_message_iter_get_arg_type(&dict)) != + DBUS_TYPE_INVALID) { + DBusMessageIter entry, var; + const char *key; + + if (ctype != DBUS_TYPE_DICT_ENTRY) + return -EINVAL; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + return -EINVAL; + + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) + return -EINVAL; + + dbus_message_iter_recurse(&entry, &var); + + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_UINT32) + return -EINVAL; + + if (strcasecmp(key, "Start") == 0) + dbus_message_iter_get_basic(&var, start); + else if (strcasecmp(key, "End") == 0) + dbus_message_iter_get_basic(&var, end); + + dbus_message_iter_next(&dict); + } + + if (folder->number_of_items > 0 && *end > folder->number_of_items) + *end = folder->number_of_items; + + return 0; +} + +static DBusMessage *media_folder_list_items(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct media_player *mp = data; + struct media_folder *folder = mp->scope; + struct player_callback *cb = mp->cb; + DBusMessageIter iter; + uint32_t start, end; + int err; + + dbus_message_iter_init(msg, &iter); + + if (parse_filters(mp, &iter, &start, &end) < 0) + return btd_error_invalid_args(msg); + + if (cb->cbs->list_items == NULL) + return btd_error_not_supported(msg); + + if (folder->msg != NULL) + return btd_error_failed(msg, strerror(EBUSY)); + + err = cb->cbs->list_items(mp, folder->item->name, start, end, + cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + folder->msg = dbus_message_ref(msg); + + return NULL; +} + +static void media_item_free(struct media_item *item) +{ + if (item->metadata != NULL) + g_hash_table_unref(item->metadata); + + g_free(item->path); + g_free(item->name); + g_free(item); +} + +static void media_item_destroy(void *data) +{ + struct media_item *item = data; + + DBG("%s", item->path); + + g_dbus_unregister_interface(btd_get_dbus_connection(), item->path, + MEDIA_ITEM_INTERFACE); + + media_item_free(item); +} + +static void media_folder_destroy(void *data) +{ + struct media_folder *folder = data; + + g_slist_free_full(folder->subfolders, media_folder_destroy); + g_slist_free_full(folder->items, media_item_destroy); + + if (folder->msg != NULL) + dbus_message_unref(folder->msg); + + media_item_destroy(folder->item); + g_free(folder); +} + +static void media_player_change_scope(struct media_player *mp, + struct media_folder *folder) +{ + struct player_callback *cb = mp->cb; + int err; + + if (mp->scope == folder) + return; + + DBG("%s", folder->item->name); + + /* Skip setting current folder if folder is current playlist/search */ + if (folder == mp->playlist || folder == mp->search) + goto cleanup; + + mp->folder = folder; + + /* Skip item cleanup if scope is the current playlist */ + if (mp->scope == mp->playlist) + goto done; + +cleanup: + g_slist_free_full(mp->scope->items, media_item_destroy); + mp->scope->items = NULL; + + /* Destroy search folder if it exists and is not being set as scope */ + if (mp->search != NULL && folder != mp->search) { + mp->folders = g_slist_remove(mp->folders, mp->search); + media_folder_destroy(mp->search); + mp->search = NULL; + } + +done: + mp->scope = folder; + + if (cb->cbs->total_items) { + err = cb->cbs->total_items(mp, folder->item->name, + cb->user_data); + if (err < 0) + DBG("Failed to get total num of items"); + } else { + g_dbus_emit_property_changed(btd_get_dbus_connection(), + mp->path, MEDIA_FOLDER_INTERFACE, + "NumberOfItems"); + } + + g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, + MEDIA_FOLDER_INTERFACE, "Name"); +} + +static struct media_folder *find_folder(GSList *folders, const char *pattern) +{ + GSList *l; + + for (l = folders; l; l = l->next) { + struct media_folder *folder = l->data; + + if (g_str_equal(folder->item->name, pattern)) + return folder; + + if (g_str_equal(folder->item->path, pattern)) + return folder; + + folder = find_folder(folder->subfolders, pattern); + if (folder != NULL) + return folder; + } + + return NULL; +} + +static struct media_folder *media_player_find_folder(struct media_player *mp, + const char *pattern) +{ + return find_folder(mp->folders, pattern); +} + +static DBusMessage *media_folder_change_folder(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct media_player *mp = data; + struct media_folder *folder = mp->scope; + struct player_callback *cb = mp->cb; + const char *path; + int err; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + if (folder->msg != NULL) + return btd_error_failed(msg, strerror(EBUSY)); + + folder = media_player_find_folder(mp, path); + if (folder == NULL) + return btd_error_invalid_args(msg); + + if (mp->scope == folder) + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + + if (folder == mp->playlist || folder == mp->folder || + folder == mp->search) { + media_player_change_scope(mp, folder); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + } + + /* + * ChangePath can only navigate one level up/down so check if folder + * is direct child or parent of the current folder otherwise fail. + */ + if (!g_slist_find(mp->folder->subfolders, folder) && + !g_slist_find(folder->subfolders, mp->folder)) + return btd_error_invalid_args(msg); + + if (cb->cbs->change_folder == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->change_folder(mp, folder->item->name, folder->item->uid, + cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + mp->scope->msg = dbus_message_ref(msg); + + return NULL; +} + +static gboolean folder_name_exists(const GDBusPropertyTable *property, + void *data) +{ + struct media_player *mp = data; + struct media_folder *folder = mp->scope; + + if (folder == NULL || folder->item == NULL) + return FALSE; + + return folder->item->name != NULL; +} + +static gboolean get_folder_name(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + struct media_folder *folder = mp->scope; + + if (folder == NULL || folder->item == NULL) + return FALSE; + + DBG("%s", folder->item->name); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &folder->item->name); + + return TRUE; +} + +static gboolean items_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_player *mp = data; + + return mp->scope != NULL; +} + +static gboolean get_items(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_player *mp = data; + struct media_folder *folder = mp->scope; + + if (folder == NULL) + return FALSE; + + DBG("%u", folder->number_of_items); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, + &folder->number_of_items); + + return TRUE; +} + +static const GDBusMethodTable media_folder_methods[] = { + { GDBUS_ASYNC_METHOD("Search", + GDBUS_ARGS({ "string", "s" }, { "filter", "a{sv}" }), + GDBUS_ARGS({ "folder", "o" }), + media_folder_search) }, + { GDBUS_ASYNC_METHOD("ListItems", + GDBUS_ARGS({ "filter", "a{sv}" }), + GDBUS_ARGS({ "items", "a{oa{sv}}" }), + media_folder_list_items) }, + { GDBUS_ASYNC_METHOD("ChangeFolder", + GDBUS_ARGS({ "folder", "o" }), NULL, + media_folder_change_folder) }, + { } +}; + +static const GDBusPropertyTable media_folder_properties[] = { + { "Name", "s", get_folder_name, NULL, folder_name_exists }, + { "NumberOfItems", "u", get_items, NULL, items_exists }, + { } +}; + +static void media_player_set_scope(struct media_player *mp, + struct media_folder *folder) +{ + if (mp->scope == NULL) { + if (!g_dbus_register_interface(btd_get_dbus_connection(), + mp->path, MEDIA_FOLDER_INTERFACE, + media_folder_methods, + NULL, + media_folder_properties, mp, NULL)) { + error("D-Bus failed to register %s on %s path", + MEDIA_FOLDER_INTERFACE, mp->path); + return; + } + mp->scope = folder; + return; + } + + return media_player_change_scope(mp, folder); +} + +static struct media_folder * +media_player_find_folder_by_uid(struct media_player *mp, uint64_t uid) +{ + struct media_folder *folder = mp->scope; + struct media_folder *parent = folder->parent; + GSList *l; + + if (parent && parent->item->uid == uid) + return parent; + + for (l = folder->subfolders; l; l = l->next) { + struct media_folder *folder = l->data; + + if (folder->item->uid == uid) + return folder; + } + + return NULL; +} + +static void media_player_set_folder_by_uid(struct media_player *mp, + uint64_t uid, uint32_t number_of_items) +{ + struct media_folder *folder; + + DBG("uid %" PRIu64 " number of items %u", uid, number_of_items); + + folder = media_player_find_folder_by_uid(mp, uid); + if (folder == NULL) { + error("Unknown folder: %" PRIu64, uid); + return; + } + + folder->number_of_items = number_of_items; + + media_player_set_scope(mp, folder); +} + +void media_player_change_folder_complete(struct media_player *mp, + const char *path, uint64_t uid, + int ret) +{ + struct media_folder *folder = mp->scope; + DBusMessage *reply; + + if (folder == NULL || folder->msg == NULL) + return; + + if (ret < 0) { + reply = btd_error_failed(folder->msg, strerror(-ret)); + goto done; + } + + media_player_set_folder_by_uid(mp, uid, ret); + + reply = g_dbus_create_reply(folder->msg, DBUS_TYPE_INVALID); + +done: + g_dbus_send_message(btd_get_dbus_connection(), reply); + dbus_message_unref(folder->msg); + folder->msg = NULL; +} + +void media_player_destroy(struct media_player *mp) +{ + DBG("%s", mp->path); + + g_dbus_unregister_interface(btd_get_dbus_connection(), mp->path, + MEDIA_PLAYER_INTERFACE); + + if (mp->track) + g_hash_table_unref(mp->track); + + if (mp->settings) + g_hash_table_unref(mp->settings); + + if (mp->process_id > 0) + g_source_remove(mp->process_id); + + if (mp->scope) + g_dbus_unregister_interface(btd_get_dbus_connection(), + mp->path, + MEDIA_FOLDER_INTERFACE); + + g_slist_free_full(mp->pending, g_free); + g_slist_free_full(mp->folders, media_folder_destroy); + + g_timer_destroy(mp->progress); + g_free(mp->cb); + g_free(mp->status); + g_free(mp->path); + g_free(mp->device); + g_free(mp->subtype); + g_free(mp->type); + g_free(mp->name); + g_free(mp); +} + +struct media_player *media_player_controller_create(const char *path, + uint16_t id) +{ + struct media_player *mp; + + mp = g_new0(struct media_player, 1); + mp->device = g_strdup(path); + mp->path = g_strdup_printf("%s/player%u", path, id); + mp->settings = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + mp->track = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + mp->progress = g_timer_new(); + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + mp->path, MEDIA_PLAYER_INTERFACE, + media_player_methods, + media_player_signals, + media_player_properties, mp, NULL)) { + error("D-Bus failed to register %s path", mp->path); + media_player_destroy(mp); + return NULL; + } + + DBG("%s", mp->path); + + return mp; +} + +const char *media_player_get_path(struct media_player *mp) +{ + return mp->path; +} + +void media_player_set_duration(struct media_player *mp, uint32_t duration) +{ + char *value, *curval; + + DBG("%u", duration); + + /* Only update duration if track exists */ + if (g_hash_table_size(mp->track) == 0) + return; + + /* Ignore if duration is already set */ + curval = g_hash_table_lookup(mp->track, "Duration"); + if (curval != NULL) + return; + + value = g_strdup_printf("%u", duration); + + g_hash_table_replace(mp->track, g_strdup("Duration"), value); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + mp->path, MEDIA_PLAYER_INTERFACE, + "Track"); +} + +void media_player_set_position(struct media_player *mp, uint32_t position) +{ + DBG("%u", position); + + /* Only update duration if track exists */ + if (g_hash_table_size(mp->track) == 0) + return; + + mp->position = position; + g_timer_start(mp->progress); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, + MEDIA_PLAYER_INTERFACE, "Position"); +} + +void media_player_set_setting(struct media_player *mp, const char *key, + const char *value) +{ + char *curval; + struct pending_req *p; + + DBG("%s: %s", key, value); + + if (strcasecmp(key, "Error") == 0) { + p = g_slist_nth_data(mp->pending, 0); + if (p == NULL) + return; + + g_dbus_pending_property_error(p->id, ERROR_INTERFACE ".Failed", + value); + goto send; + } + + curval = g_hash_table_lookup(mp->settings, key); + if (g_strcmp0(curval, value) == 0) + goto done; + + g_hash_table_replace(mp->settings, g_strdup(key), g_strdup(value)); + g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, + MEDIA_PLAYER_INTERFACE, key); + +done: + p = find_pending(mp, key); + if (p == NULL) + return; + + if (strcasecmp(value, p->value) == 0) + g_dbus_pending_property_success(p->id); + else + g_dbus_pending_property_error(p->id, + ERROR_INTERFACE ".NotSupported", + "Operation is not supported"); + +send: + mp->pending = g_slist_remove(mp->pending, p); + g_free(p); + + return; +} + +const char *media_player_get_status(struct media_player *mp) +{ + return mp->status; +} + +void media_player_set_status(struct media_player *mp, const char *status) +{ + DBG("%s", status); + + if (g_strcmp0(mp->status, status) == 0) + return; + + g_free(mp->status); + mp->status = g_strdup(status); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, + MEDIA_PLAYER_INTERFACE, "Status"); + + mp->position = media_player_get_position(mp); + g_timer_start(mp->progress); +} + +static gboolean process_metadata_changed(void *user_data) +{ + struct media_player *mp = user_data; + const char *item; + + mp->process_id = 0; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + mp->path, MEDIA_PLAYER_INTERFACE, + "Track"); + + item = g_hash_table_lookup(mp->track, "Item"); + if (item == NULL) + return FALSE; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + item, MEDIA_ITEM_INTERFACE, + "Metadata"); + + return FALSE; +} + +void media_player_set_metadata(struct media_player *mp, + struct media_item *item, const char *key, + void *data, size_t len) +{ + char *value, *curval; + + value = g_strndup(data, len); + + DBG("%s: %s", key, value); + + curval = g_hash_table_lookup(mp->track, key); + if (g_strcmp0(curval, value) == 0) { + g_free(value); + return; + } + + if (mp->process_id == 0) { + g_hash_table_remove_all(mp->track); + mp->process_id = g_idle_add(process_metadata_changed, mp); + } + + g_hash_table_replace(mp->track, g_strdup(key), value); +} + +void media_player_set_type(struct media_player *mp, const char *type) +{ + if (g_strcmp0(mp->type, type) == 0) + return; + + DBG("%s", type); + + mp->type = g_strdup(type); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + mp->path, MEDIA_PLAYER_INTERFACE, + "Type"); +} + +void media_player_set_subtype(struct media_player *mp, const char *subtype) +{ + if (g_strcmp0(mp->subtype, subtype) == 0) + return; + + DBG("%s", subtype); + + mp->subtype = g_strdup(subtype); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + mp->path, MEDIA_PLAYER_INTERFACE, + "Subtype"); +} + +void media_player_set_name(struct media_player *mp, const char *name) +{ + if (g_strcmp0(mp->name, name) == 0) + return; + + DBG("%s", name); + + mp->name = g_strdup(name); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + mp->path, MEDIA_PLAYER_INTERFACE, + "Name"); +} + +void media_player_set_browsable(struct media_player *mp, bool enabled) +{ + if (mp->browsable == enabled) + return; + + DBG("%s", enabled ? "true" : "false"); + + mp->browsable = enabled; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + mp->path, MEDIA_PLAYER_INTERFACE, + "Browsable"); +} + +bool media_player_get_browsable(struct media_player *mp) +{ + return mp->browsable; +} + +void media_player_set_searchable(struct media_player *mp, bool enabled) +{ + if (mp->searchable == enabled) + return; + + DBG("%s", enabled ? "true" : "false"); + + mp->searchable = enabled; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + mp->path, MEDIA_PLAYER_INTERFACE, + "Searchable"); +} + +void media_player_set_folder(struct media_player *mp, const char *name, + uint32_t number_of_items) +{ + struct media_folder *folder; + + DBG("%s number of items %u", name, number_of_items); + + folder = media_player_find_folder(mp, name); + if (folder == NULL) { + error("Unknown folder: %s", name); + return; + } + + folder->number_of_items = number_of_items; + + media_player_set_scope(mp, folder); +} + +void media_player_set_playlist(struct media_player *mp, const char *name) +{ + struct media_folder *folder; + + DBG("%s", name); + + folder = media_player_find_folder(mp, name); + if (folder == NULL) { + error("Unknown folder: %s", name); + return; + } + + mp->playlist = folder; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path, + MEDIA_PLAYER_INTERFACE, "Playlist"); +} + +static struct media_item *media_folder_find_item(struct media_folder *folder, + uint64_t uid) +{ + GSList *l; + + if (uid == 0) + return NULL; + + for (l = folder->items; l; l = l->next) { + struct media_item *item = l->data; + + if (item->uid == uid) + return item; + } + + return NULL; +} + +static DBusMessage *media_item_play(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_item *item = data; + struct media_player *mp = item->player; + struct media_folder *folder = mp->scope; + struct player_callback *cb = mp->cb; + const char *path; + int err; + + if (!item->playable || !cb->cbs->play_item) + return btd_error_not_supported(msg); + + if (folder->msg) + return btd_error_failed(msg, strerror(EBUSY)); + + path = mp->search && folder == mp->search ? "/Search" : item->path; + + err = cb->cbs->play_item(mp, path, item->uid, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + folder->msg = dbus_message_ref(msg); + + return NULL; +} + +static DBusMessage *media_item_add_to_nowplaying(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct media_item *item = data; + struct media_player *mp = item->player; + struct player_callback *cb = mp->cb; + int err; + + if (!item->playable || !cb->cbs->play_item) + return btd_error_not_supported(msg); + + err = cb->cbs->add_to_nowplaying(mp, item->path, item->uid, + cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static gboolean get_player(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_item *item = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &item->player->path); + + return TRUE; +} + +static gboolean item_name_exists(const GDBusPropertyTable *property, + void *data) +{ + struct media_item *item = data; + + return item->name != NULL; +} + +static gboolean get_item_name(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_item *item = data; + + if (item->name == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &item->name); + + return TRUE; +} + +static const char *type_to_string(uint8_t type) +{ + switch (type) { + case PLAYER_ITEM_TYPE_AUDIO: + return "audio"; + case PLAYER_ITEM_TYPE_VIDEO: + return "video"; + case PLAYER_ITEM_TYPE_FOLDER: + return "folder"; + } + + return NULL; +} + +static gboolean get_item_type(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_item *item = data; + const char *string; + + string = type_to_string(item->type); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string); + + return TRUE; +} + +static gboolean get_playable(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_item *item = data; + dbus_bool_t value; + + value = item->playable; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); + + return TRUE; +} + +static const char *folder_type_to_string(uint8_t type) +{ + switch (type) { + case PLAYER_FOLDER_TYPE_MIXED: + return "mixed"; + case PLAYER_FOLDER_TYPE_TITLES: + return "titles"; + case PLAYER_FOLDER_TYPE_ALBUMS: + return "albums"; + case PLAYER_FOLDER_TYPE_ARTISTS: + return "artists"; + case PLAYER_FOLDER_TYPE_GENRES: + return "genres"; + case PLAYER_FOLDER_TYPE_PLAYLISTS: + return "playlists"; + case PLAYER_FOLDER_TYPE_YEARS: + return "years"; + } + + return NULL; +} + +static gboolean folder_type_exists(const GDBusPropertyTable *property, + void *data) +{ + struct media_item *item = data; + + return folder_type_to_string(item->folder_type) != NULL; +} + +static gboolean get_folder_type(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_item *item = data; + const char *string; + + string = folder_type_to_string(item->folder_type); + if (string == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string); + + return TRUE; +} + +static gboolean metadata_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_item *item = data; + + return item->metadata != NULL; +} + +static void append_metadata(void *key, void *value, void *user_data) +{ + DBusMessageIter *dict = user_data; + const char *strkey = key; + + if (strcasecmp(strkey, "Item") == 0) + return; + + if (strcasecmp(strkey, "Duration") == 0 || + strcasecmp(strkey, "TrackNumber") == 0 || + strcasecmp(strkey, "NumberOfTracks") == 0) { + uint32_t num = atoi(value); + dict_append_entry(dict, key, DBUS_TYPE_UINT32, &num); + } else { + dict_append_entry(dict, key, DBUS_TYPE_STRING, &value); + } +} + +static gboolean get_metadata(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_item *item = data; + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + if (g_hash_table_size(item->metadata) > 0) + g_hash_table_foreach(item->metadata, append_metadata, &dict); + else if (item->name != NULL) + dict_append_entry(&dict, "Title", DBUS_TYPE_STRING, + &item->name); + + dbus_message_iter_close_container(iter, &dict); + + return TRUE; +} + +static const GDBusMethodTable media_item_methods[] = { + { GDBUS_ASYNC_METHOD("Play", NULL, NULL, media_item_play) }, + { GDBUS_METHOD("AddtoNowPlaying", NULL, NULL, + media_item_add_to_nowplaying) }, + { } +}; + +static const GDBusPropertyTable media_item_properties[] = { + { "Player", "o", get_player, NULL, NULL }, + { "Name", "s", get_item_name, NULL, item_name_exists }, + { "Type", "s", get_item_type, NULL, NULL }, + { "FolderType", "s", get_folder_type, NULL, folder_type_exists }, + { "Playable", "b", get_playable, NULL, NULL }, + { "Metadata", "a{sv}", get_metadata, NULL, metadata_exists }, + { } +}; + +void media_player_play_item_complete(struct media_player *mp, int err) +{ + struct media_folder *folder = mp->scope; + DBusMessage *reply; + + if (folder == NULL || folder->msg == NULL) + return; + + if (err < 0) { + reply = btd_error_failed(folder->msg, strerror(-err)); + goto done; + } + + reply = g_dbus_create_reply(folder->msg, DBUS_TYPE_INVALID); + +done: + g_dbus_send_message(btd_get_dbus_connection(), reply); + dbus_message_unref(folder->msg); + folder->msg = NULL; +} + +void media_item_set_playable(struct media_item *item, bool value) +{ + if (item->playable == value) + return; + + item->playable = value; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), item->path, + MEDIA_ITEM_INTERFACE, "Playable"); +} + +static struct media_item *media_folder_create_item(struct media_player *mp, + struct media_folder *folder, + const char *name, + player_item_type_t type, + uint64_t uid) +{ + struct media_item *item; + const char *strtype; + + item = media_folder_find_item(folder, uid); + if (item != NULL) + return item; + + strtype = type_to_string(type); + if (strtype == NULL) + return NULL; + + DBG("%s type %s uid %" PRIu64 "", name, strtype, uid); + + item = g_new0(struct media_item, 1); + item->player = mp; + item->uid = uid; + + if (!uid && name[0] == '/') + item->path = g_strdup_printf("%s%s", mp->path, name); + else + item->path = g_strdup_printf("%s/item%" PRIu64 "", + folder->item->path, uid); + + item->name = g_strdup(name); + item->type = type; + item->folder_type = PLAYER_FOLDER_TYPE_INVALID; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + item->path, MEDIA_ITEM_INTERFACE, + media_item_methods, + NULL, + media_item_properties, item, NULL)) { + error("D-Bus failed to register %s on %s path", + MEDIA_ITEM_INTERFACE, item->path); + media_item_free(item); + return NULL; + } + + if (type != PLAYER_ITEM_TYPE_FOLDER) { + folder->items = g_slist_prepend(folder->items, item); + item->metadata = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + } + + DBG("%s", item->path); + + return item; +} + +struct media_item *media_player_create_item(struct media_player *mp, + const char *name, + player_item_type_t type, + uint64_t uid) +{ + return media_folder_create_item(mp, mp->scope, name, type, uid); +} + +struct media_item *media_player_create_folder(struct media_player *mp, + const char *name, + player_folder_type_t type, + uint64_t uid) +{ + struct media_folder *folder; + struct media_item *item; + + if (uid > 0) + folder = media_player_find_folder_by_uid(mp, uid); + else + folder = media_player_find_folder(mp, name); + + if (folder != NULL) + return folder->item; + + if (uid > 0) + item = media_player_create_subfolder(mp, name, uid); + else + item = media_player_create_item(mp, name, + PLAYER_ITEM_TYPE_FOLDER, uid); + + if (item == NULL) + return NULL; + + folder = g_new0(struct media_folder, 1); + folder->item = item; + + item->folder_type = type; + + if (mp->folder != NULL) + goto done; + + mp->folder = folder; + +done: + if (uid > 0) { + folder->parent = mp->folder; + mp->folder->subfolders = g_slist_prepend( + mp->folder->subfolders, + folder); + } else + mp->folders = g_slist_prepend(mp->folders, folder); + + return item; +} + +void media_player_set_callbacks(struct media_player *mp, + const struct media_player_callback *cbs, + void *user_data) +{ + struct player_callback *cb; + + if (mp->cb) + g_free(mp->cb); + + cb = g_new0(struct player_callback, 1); + cb->cbs = cbs; + cb->user_data = user_data; + + mp->cb = cb; +} + +struct media_item *media_player_set_playlist_item(struct media_player *mp, + uint64_t uid) +{ + struct media_folder *folder = mp->playlist; + struct media_item *item; + + DBG("%" PRIu64 "", uid); + + if (folder == NULL || uid == 0) + return NULL; + + item = media_folder_create_item(mp, folder, NULL, + PLAYER_ITEM_TYPE_AUDIO, uid); + if (item == NULL) + return NULL; + + media_item_set_playable(item, true); + + if (mp->track != item->metadata) { + g_hash_table_unref(mp->track); + mp->track = g_hash_table_ref(item->metadata); + } + + if (item == g_hash_table_lookup(mp->track, "Item")) + return item; + + if (mp->process_id == 0) { + g_hash_table_remove_all(mp->track); + mp->process_id = g_idle_add(process_metadata_changed, mp); + } + + g_hash_table_replace(mp->track, g_strdup("Item"), + g_strdup(item->path)); + + return item; +} diff --git a/profiles/audio/player.h b/profiles/audio/player.h new file mode 100644 index 0000000..536394c --- /dev/null +++ b/profiles/audio/player.h @@ -0,0 +1,118 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * Copyright (C) 2012-2012 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 + * + */ + +typedef enum { + PLAYER_ITEM_TYPE_AUDIO, + PLAYER_ITEM_TYPE_VIDEO, + PLAYER_ITEM_TYPE_FOLDER, + PLAYER_ITEM_TYPE_INVALID, +} player_item_type_t; + +typedef enum { + PLAYER_FOLDER_TYPE_MIXED, + PLAYER_FOLDER_TYPE_TITLES, + PLAYER_FOLDER_TYPE_ALBUMS, + PLAYER_FOLDER_TYPE_ARTISTS, + PLAYER_FOLDER_TYPE_GENRES, + PLAYER_FOLDER_TYPE_PLAYLISTS, + PLAYER_FOLDER_TYPE_YEARS, + PLAYER_FOLDER_TYPE_INVALID, +} player_folder_type_t; + +struct media_player; +struct media_item; + +struct media_player_callback { + bool (*set_setting) (struct media_player *mp, const char *key, + const char *value, void *user_data); + int (*play) (struct media_player *mp, void *user_data); + int (*pause) (struct media_player *mp, void *user_data); + int (*stop) (struct media_player *mp, void *user_data); + int (*next) (struct media_player *mp, void *user_data); + int (*previous) (struct media_player *mp, void *user_data); + int (*fast_forward) (struct media_player *mp, void *user_data); + int (*rewind) (struct media_player *mp, void *user_data); + int (*list_items) (struct media_player *mp, const char *name, + uint32_t start, uint32_t end, void *user_data); + int (*change_folder) (struct media_player *mp, const char *path, + uint64_t uid, void *user_data); + int (*search) (struct media_player *mp, const char *string, + void *user_data); + int (*play_item) (struct media_player *mp, const char *name, + uint64_t uid, void *user_data); + int (*add_to_nowplaying) (struct media_player *mp, const char *name, + uint64_t uid, void *user_data); + int (*total_items) (struct media_player *mp, const char *name, + void *user_data); +}; + +struct media_player *media_player_controller_create(const char *path, + uint16_t id); +const char *media_player_get_path(struct media_player *mp); +void media_player_destroy(struct media_player *mp); +void media_player_set_duration(struct media_player *mp, uint32_t duration); +void media_player_set_position(struct media_player *mp, uint32_t position); +void media_player_set_setting(struct media_player *mp, const char *key, + const char *value); +const char *media_player_get_status(struct media_player *mp); +void media_player_set_status(struct media_player *mp, const char *status); +void media_player_set_metadata(struct media_player *mp, + struct media_item *item, const char *key, + void *data, size_t len); +void media_player_set_type(struct media_player *mp, const char *type); +void media_player_set_subtype(struct media_player *mp, const char *subtype); +void media_player_set_name(struct media_player *mp, const char *name); +void media_player_set_browsable(struct media_player *mp, bool enabled); +bool media_player_get_browsable(struct media_player *mp); +void media_player_set_searchable(struct media_player *mp, bool enabled); +void media_player_set_folder(struct media_player *mp, const char *path, + uint32_t items); +void media_player_set_playlist(struct media_player *mp, const char *name); +struct media_item *media_player_set_playlist_item(struct media_player *mp, + uint64_t uid); + +struct media_item *media_player_create_folder(struct media_player *mp, + const char *name, + player_folder_type_t type, + uint64_t uid); +struct media_item *media_player_create_item(struct media_player *mp, + const char *name, + player_item_type_t type, + uint64_t uid); + +void media_player_play_item_complete(struct media_player *mp, int err); +void media_item_set_playable(struct media_item *item, bool value); +void media_player_list_complete(struct media_player *mp, GSList *items, + int err); +void media_player_change_folder_complete(struct media_player *player, + const char *path, uint64_t uid, + int ret); +void media_player_search_complete(struct media_player *mp, int ret); +void media_player_total_items_complete(struct media_player *mp, + uint32_t num_of_items); + +void media_player_set_callbacks(struct media_player *mp, + const struct media_player_callback *cbs, + void *user_data); diff --git a/profiles/audio/sink.c b/profiles/audio/sink.c new file mode 100644 index 0000000..9664405 --- /dev/null +++ b/profiles/audio/sink.c @@ -0,0 +1,449 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "gdbus/gdbus.h" + +#include "src/log.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/service.h" +#include "src/error.h" +#include "src/dbus-common.h" +#include "src/shared/queue.h" + +#include "avdtp.h" +#include "media.h" +#include "a2dp.h" +#include "sink.h" + +#define STREAM_SETUP_RETRY_TIMER 2 + +struct sink { + struct btd_service *service; + struct avdtp *session; + struct avdtp_stream *stream; + unsigned int cb_id; + avdtp_session_state_t session_state; + avdtp_state_t stream_state; + sink_state_t state; + unsigned int connect_id; + unsigned int disconnect_id; + unsigned int avdtp_callback_id; +}; + +struct sink_state_callback { + sink_state_cb cb; + struct btd_service *service; + void *user_data; + unsigned int id; +}; + +static GSList *sink_callbacks = NULL; + +static char *str_state[] = { + "SINK_STATE_DISCONNECTED", + "SINK_STATE_CONNECTING", + "SINK_STATE_CONNECTED", + "SINK_STATE_PLAYING", +}; + +static void sink_set_state(struct sink *sink, sink_state_t new_state) +{ + struct btd_service *service = sink->service; + struct btd_device *dev = btd_service_get_device(service); + sink_state_t old_state = sink->state; + GSList *l; + + sink->state = new_state; + + DBG("State changed %s: %s -> %s", device_get_path(dev), + str_state[old_state], str_state[new_state]); + + for (l = sink_callbacks; l != NULL; l = l->next) { + struct sink_state_callback *cb = l->data; + + if (cb->service != service) + continue; + + cb->cb(service, old_state, new_state, cb->user_data); + } + + if (new_state != SINK_STATE_DISCONNECTED) + return; + + if (sink->session) { + avdtp_unref(sink->session); + sink->session = NULL; + } +} + +static void avdtp_state_callback(struct btd_device *dev, + struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data) +{ + struct sink *sink = user_data; + + switch (new_state) { + case AVDTP_SESSION_STATE_DISCONNECTED: + sink_set_state(sink, SINK_STATE_DISCONNECTED); + break; + case AVDTP_SESSION_STATE_CONNECTING: + sink_set_state(sink, SINK_STATE_CONNECTING); + break; + case AVDTP_SESSION_STATE_CONNECTED: + break; + } + + sink->session_state = new_state; +} + +static void stream_state_changed(struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data) +{ + struct btd_service *service = user_data; + struct sink *sink = btd_service_get_user_data(service); + + if (err) + return; + + switch (new_state) { + case AVDTP_STATE_IDLE: + btd_service_disconnecting_complete(sink->service, 0); + + if (sink->disconnect_id > 0) { + a2dp_cancel(sink->disconnect_id); + sink->disconnect_id = 0; + } + + if (sink->session) { + avdtp_unref(sink->session); + sink->session = NULL; + } + sink->stream = NULL; + sink->cb_id = 0; + break; + case AVDTP_STATE_OPEN: + btd_service_connecting_complete(sink->service, 0); + sink_set_state(sink, SINK_STATE_CONNECTED); + break; + case AVDTP_STATE_STREAMING: + sink_set_state(sink, SINK_STATE_PLAYING); + break; + case AVDTP_STATE_CONFIGURED: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + default: + break; + } + + sink->stream_state = new_state; +} + +static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, int err, + void *user_data) +{ + struct sink *sink = user_data; + + sink->connect_id = 0; + + if (stream) + return; + + avdtp_unref(sink->session); + sink->session = NULL; + btd_service_connecting_complete(sink->service, err); +} + +static void select_complete(struct avdtp *session, struct a2dp_sep *sep, + GSList *caps, void *user_data) +{ + struct sink *sink = user_data; + int id; + + sink->connect_id = 0; + + id = a2dp_config(session, sep, stream_setup_complete, caps, sink); + if (id == 0) + goto failed; + + sink->connect_id = id; + return; + +failed: + btd_service_connecting_complete(sink->service, -EIO); + + avdtp_unref(sink->session); + sink->session = NULL; +} + +static void discovery_complete(struct avdtp *session, GSList *seps, int err, + void *user_data) +{ + struct sink *sink = user_data; + int id; + + sink->connect_id = 0; + + if (err) { + avdtp_unref(sink->session); + sink->session = NULL; + goto failed; + } + + DBG("Discovery complete"); + + id = a2dp_select_capabilities(sink->session, AVDTP_SEP_TYPE_SINK, NULL, + select_complete, sink); + if (id == 0) { + err = -EIO; + goto failed; + } + + sink->connect_id = id; + return; + +failed: + btd_service_connecting_complete(sink->service, err); + avdtp_unref(sink->session); + sink->session = NULL; +} + +gboolean sink_setup_stream(struct btd_service *service, struct avdtp *session) +{ + struct sink *sink = btd_service_get_user_data(service); + + if (sink->connect_id > 0 || sink->disconnect_id > 0) + return FALSE; + + if (!sink->session) { + if (session) + sink->session = avdtp_ref(session); + else + sink->session = a2dp_avdtp_get( + btd_service_get_device(service)); + + if (!sink->session) { + DBG("Unable to get a session"); + return FALSE; + } + } + + sink->connect_id = a2dp_discover(sink->session, discovery_complete, + sink); + if (sink->connect_id == 0) + return FALSE; + + return TRUE; +} + +int sink_connect(struct btd_service *service) +{ + struct sink *sink = btd_service_get_user_data(service); + + if (sink->connect_id > 0 || sink->disconnect_id > 0) + return -EBUSY; + + if (sink->state == SINK_STATE_CONNECTING) + return -EBUSY; + + if (sink->stream_state >= AVDTP_STATE_OPEN) + return -EALREADY; + + if (!sink_setup_stream(service, NULL)) { + DBG("Failed to create a stream"); + return -EIO; + } + + DBG("stream creation in progress"); + + return 0; +} + +static void sink_free(struct btd_service *service) +{ + struct sink *sink = btd_service_get_user_data(service); + + if (sink->cb_id) + avdtp_stream_remove_cb(sink->session, sink->stream, + sink->cb_id); + + if (sink->session) + avdtp_unref(sink->session); + + if (sink->connect_id > 0) { + btd_service_connecting_complete(sink->service, -ECANCELED); + a2dp_cancel(sink->connect_id); + sink->connect_id = 0; + } + + if (sink->disconnect_id > 0) { + btd_service_disconnecting_complete(sink->service, -ECANCELED); + a2dp_cancel(sink->disconnect_id); + sink->disconnect_id = 0; + } + + avdtp_remove_state_cb(sink->avdtp_callback_id); + btd_service_unref(sink->service); + + g_free(sink); +} + +void sink_unregister(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + + DBG("%s", device_get_path(dev)); + + sink_free(service); +} + +int sink_init(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + struct sink *sink; + + DBG("%s", device_get_path(dev)); + + sink = g_new0(struct sink, 1); + + sink->service = btd_service_ref(service); + + sink->avdtp_callback_id = avdtp_add_state_cb(dev, avdtp_state_callback, + sink); + + btd_service_set_user_data(service, sink); + + return 0; +} + +gboolean sink_is_active(struct btd_service *service) +{ + struct sink *sink = btd_service_get_user_data(service); + + if (sink->session) + return TRUE; + + return FALSE; +} + +gboolean sink_new_stream(struct btd_service *service, struct avdtp *session, + struct avdtp_stream *stream) +{ + struct sink *sink = btd_service_get_user_data(service); + + if (sink->stream) + return FALSE; + + if (!sink->session) + sink->session = avdtp_ref(session); + + sink->stream = stream; + + sink->cb_id = avdtp_stream_add_cb(session, stream, + stream_state_changed, service); + + return TRUE; +} + +int sink_disconnect(struct btd_service *service) +{ + struct sink *sink = btd_service_get_user_data(service); + + if (!sink->session) + return -ENOTCONN; + + /* cancel pending connect */ + if (sink->connect_id > 0) { + avdtp_unref(sink->session); + sink->session = NULL; + + a2dp_cancel(sink->connect_id); + sink->connect_id = 0; + btd_service_disconnecting_complete(sink->service, 0); + + return 0; + } + + /* disconnect already ongoing */ + if (sink->disconnect_id > 0) + return -EBUSY; + + if (!sink->stream) + return -ENOTCONN; + + return avdtp_close(sink->session, sink->stream, FALSE); +} + +unsigned int sink_add_state_cb(struct btd_service *service, sink_state_cb cb, + void *user_data) +{ + struct sink_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct sink_state_callback, 1); + state_cb->cb = cb; + state_cb->service = service; + state_cb->user_data = user_data; + state_cb->id = ++id; + + sink_callbacks = g_slist_append(sink_callbacks, state_cb); + + return state_cb->id; +} + +gboolean sink_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = sink_callbacks; l != NULL; l = l->next) { + struct sink_state_callback *cb = l->data; + if (cb && cb->id == id) { + sink_callbacks = g_slist_remove(sink_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/profiles/audio/sink.h b/profiles/audio/sink.h new file mode 100644 index 0000000..93c62a2 --- /dev/null +++ b/profiles/audio/sink.h @@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +typedef enum { + SINK_STATE_DISCONNECTED, + SINK_STATE_CONNECTING, + SINK_STATE_CONNECTED, + SINK_STATE_PLAYING, +} sink_state_t; + +typedef void (*sink_state_cb) (struct btd_service *service, + sink_state_t old_state, + sink_state_t new_state, + void *user_data); + +struct btd_service; + +unsigned int sink_add_state_cb(struct btd_service *service, sink_state_cb cb, + void *user_data); +gboolean sink_remove_state_cb(unsigned int id); + +int sink_init(struct btd_service *service); +void sink_unregister(struct btd_service *service); +gboolean sink_is_active(struct btd_service *service); +int sink_connect(struct btd_service *service); +gboolean sink_new_stream(struct btd_service *service, struct avdtp *session, + struct avdtp_stream *stream); +gboolean sink_setup_stream(struct btd_service *service, struct avdtp *session); +int sink_disconnect(struct btd_service *service); diff --git a/profiles/audio/source.c b/profiles/audio/source.c new file mode 100644 index 0000000..0ac20fe --- /dev/null +++ b/profiles/audio/source.c @@ -0,0 +1,441 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2009 Joao Paulo Rechi Vita + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "gdbus/gdbus.h" + +#include "src/log.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/service.h" +#include "src/error.h" +#include "src/dbus-common.h" +#include "src/shared/queue.h" + +#include "avdtp.h" +#include "media.h" +#include "a2dp.h" +#include "source.h" + +struct source { + struct btd_service *service; + struct avdtp *session; + struct avdtp_stream *stream; + unsigned int cb_id; + avdtp_session_state_t session_state; + avdtp_state_t stream_state; + source_state_t state; + unsigned int connect_id; + unsigned int disconnect_id; + unsigned int avdtp_callback_id; +}; + +struct source_state_callback { + source_state_cb cb; + struct btd_service *service; + void *user_data; + unsigned int id; +}; + +static GSList *source_callbacks = NULL; + +static char *str_state[] = { + "SOURCE_STATE_DISCONNECTED", + "SOURCE_STATE_CONNECTING", + "SOURCE_STATE_CONNECTED", + "SOURCE_STATE_PLAYING", +}; + +static void source_set_state(struct source *source, source_state_t new_state) +{ + struct btd_device *dev = btd_service_get_device(source->service); + source_state_t old_state = source->state; + GSList *l; + + source->state = new_state; + + DBG("State changed %s: %s -> %s", device_get_path(dev), + str_state[old_state], str_state[new_state]); + + for (l = source_callbacks; l != NULL; l = l->next) { + struct source_state_callback *cb = l->data; + + if (cb->service != source->service) + continue; + + cb->cb(source->service, old_state, new_state, cb->user_data); + } + + if (new_state != SOURCE_STATE_DISCONNECTED) + return; + + if (source->session) { + avdtp_unref(source->session); + source->session = NULL; + } +} + +static void avdtp_state_callback(struct btd_device *dev, struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, + void *user_data) +{ + struct source *source = user_data; + + switch (new_state) { + case AVDTP_SESSION_STATE_DISCONNECTED: + source_set_state(source, SOURCE_STATE_DISCONNECTED); + break; + case AVDTP_SESSION_STATE_CONNECTING: + source_set_state(source, SOURCE_STATE_CONNECTING); + break; + case AVDTP_SESSION_STATE_CONNECTED: + break; + } + + source->session_state = new_state; +} + +static void stream_state_changed(struct avdtp_stream *stream, + avdtp_state_t old_state, + avdtp_state_t new_state, + struct avdtp_error *err, + void *user_data) +{ + struct btd_service *service = user_data; + struct source *source = btd_service_get_user_data(service); + + if (err) + return; + + switch (new_state) { + case AVDTP_STATE_IDLE: + btd_service_disconnecting_complete(source->service, 0); + + if (source->disconnect_id > 0) { + a2dp_cancel(source->disconnect_id); + source->disconnect_id = 0; + } + + if (source->session) { + avdtp_unref(source->session); + source->session = NULL; + } + source->stream = NULL; + source->cb_id = 0; + break; + case AVDTP_STATE_OPEN: + btd_service_connecting_complete(source->service, 0); + source_set_state(source, SOURCE_STATE_CONNECTED); + break; + case AVDTP_STATE_STREAMING: + source_set_state(source, SOURCE_STATE_PLAYING); + break; + case AVDTP_STATE_CONFIGURED: + case AVDTP_STATE_CLOSING: + case AVDTP_STATE_ABORTING: + default: + break; + } + + source->stream_state = new_state; +} + +static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, int err, + void *user_data) +{ + struct source *source = user_data; + + source->connect_id = 0; + + if (stream) + return; + + avdtp_unref(source->session); + source->session = NULL; + btd_service_connecting_complete(source->service, err); +} + +static void select_complete(struct avdtp *session, struct a2dp_sep *sep, + GSList *caps, void *user_data) +{ + struct source *source = user_data; + int id; + + source->connect_id = 0; + + if (caps == NULL) + goto failed; + + id = a2dp_config(session, sep, stream_setup_complete, caps, source); + if (id == 0) + goto failed; + + source->connect_id = id; + return; + +failed: + btd_service_connecting_complete(source->service, -EIO); + + avdtp_unref(source->session); + source->session = NULL; +} + +static void discovery_complete(struct avdtp *session, GSList *seps, int err, + void *user_data) +{ + struct source *source = user_data; + int id; + + source->connect_id = 0; + + if (err) { + avdtp_unref(source->session); + source->session = NULL; + goto failed; + } + + DBG("Discovery complete"); + + id = a2dp_select_capabilities(source->session, AVDTP_SEP_TYPE_SOURCE, + NULL, select_complete, source); + if (id == 0) { + err = -EIO; + goto failed; + } + + source->connect_id = id; + return; + +failed: + btd_service_connecting_complete(source->service, err); + avdtp_unref(source->session); + source->session = NULL; +} + +gboolean source_setup_stream(struct btd_service *service, + struct avdtp *session) +{ + struct source *source = btd_service_get_user_data(service); + + if (source->connect_id > 0 || source->disconnect_id > 0) + return FALSE; + + if (!source->session) { + if (session) + source->session = avdtp_ref(session); + else + source->session = a2dp_avdtp_get( + btd_service_get_device(service)); + + if (!source->session) { + DBG("Unable to get a session"); + return FALSE; + } + } + + source->connect_id = a2dp_discover(source->session, discovery_complete, + source); + if (source->connect_id == 0) + return FALSE; + + return TRUE; +} + +int source_connect(struct btd_service *service) +{ + struct source *source = btd_service_get_user_data(service); + + if (source->connect_id > 0 || source->disconnect_id > 0) + return -EBUSY; + + if (source->state == SOURCE_STATE_CONNECTING) + return -EBUSY; + + if (source->stream_state >= AVDTP_STATE_OPEN) + return -EALREADY; + + if (!source_setup_stream(service, NULL)) { + DBG("Failed to create a stream"); + return -EIO; + } + + DBG("stream creation in progress"); + + return 0; +} + +static void source_free(struct btd_service *service) +{ + struct source *source = btd_service_get_user_data(service); + + if (source->cb_id) + avdtp_stream_remove_cb(source->session, source->stream, + source->cb_id); + + if (source->session) + avdtp_unref(source->session); + + if (source->connect_id > 0) { + btd_service_connecting_complete(source->service, -ECANCELED); + a2dp_cancel(source->connect_id); + source->connect_id = 0; + } + + if (source->disconnect_id > 0) { + btd_service_disconnecting_complete(source->service, -ECANCELED); + a2dp_cancel(source->disconnect_id); + source->disconnect_id = 0; + } + + avdtp_remove_state_cb(source->avdtp_callback_id); + btd_service_unref(source->service); + + g_free(source); +} + +void source_unregister(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + + DBG("%s", device_get_path(dev)); + + source_free(service); +} + +int source_init(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + struct source *source; + + DBG("%s", device_get_path(dev)); + + source = g_new0(struct source, 1); + + source->service = btd_service_ref(service); + + source->avdtp_callback_id = avdtp_add_state_cb(dev, + avdtp_state_callback, + source); + + btd_service_set_user_data(service, source); + + return 0; +} + +gboolean source_new_stream(struct btd_service *service, struct avdtp *session, + struct avdtp_stream *stream) +{ + struct source *source = btd_service_get_user_data(service); + + if (source->stream) + return FALSE; + + if (!source->session) + source->session = avdtp_ref(session); + + source->stream = stream; + + source->cb_id = avdtp_stream_add_cb(session, stream, + stream_state_changed, service); + + return TRUE; +} + +int source_disconnect(struct btd_service *service) +{ + struct source *source = btd_service_get_user_data(service); + + if (!source->session) + return -ENOTCONN; + + /* cancel pending connect */ + if (source->connect_id > 0) { + avdtp_unref(source->session); + source->session = NULL; + + a2dp_cancel(source->connect_id); + source->connect_id = 0; + btd_service_disconnecting_complete(source->service, 0); + + return 0; + } + + /* disconnect already ongoing */ + if (source->disconnect_id > 0) + return -EBUSY; + + if (!source->stream) + return -ENOTCONN; + + return avdtp_close(source->session, source->stream, FALSE); +} + +unsigned int source_add_state_cb(struct btd_service *service, + source_state_cb cb, void *user_data) +{ + struct source_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new(struct source_state_callback, 1); + state_cb->cb = cb; + state_cb->service = service; + state_cb->user_data = user_data; + state_cb->id = ++id; + + source_callbacks = g_slist_append(source_callbacks, state_cb); + + return state_cb->id; +} + +gboolean source_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = source_callbacks; l != NULL; l = l->next) { + struct source_state_callback *cb = l->data; + if (cb && cb->id == id) { + source_callbacks = g_slist_remove(source_callbacks, cb); + g_free(cb); + return TRUE; + } + } + + return FALSE; +} diff --git a/profiles/audio/source.h b/profiles/audio/source.h new file mode 100644 index 0000000..a014c68 --- /dev/null +++ b/profiles/audio/source.h @@ -0,0 +1,51 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2009 Joao Paulo Rechi Vita + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +typedef enum { + SOURCE_STATE_DISCONNECTED, + SOURCE_STATE_CONNECTING, + SOURCE_STATE_CONNECTED, + SOURCE_STATE_PLAYING, +} source_state_t; + +typedef void (*source_state_cb) (struct btd_service *service, + source_state_t old_state, + source_state_t new_state, + void *user_data); + +struct btd_service; + +unsigned int source_add_state_cb(struct btd_service *service, + source_state_cb cb, void *user_data); +gboolean source_remove_state_cb(unsigned int id); + +int source_init(struct btd_service *service); +void source_unregister(struct btd_service *service); +int source_connect(struct btd_service *service); +gboolean source_new_stream(struct btd_service *service, struct avdtp *session, + struct avdtp_stream *stream); +gboolean source_setup_stream(struct btd_service *service, + struct avdtp *session); +int source_disconnect(struct btd_service *service); diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c new file mode 100644 index 0000000..48fabba --- /dev/null +++ b/profiles/audio/transport.c @@ -0,0 +1,993 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/adapter.h" +#include "src/device.h" +#include "src/dbus-common.h" + +#include "src/log.h" +#include "src/error.h" +#include "src/shared/queue.h" + +#include "avdtp.h" +#include "media.h" +#include "transport.h" +#include "a2dp.h" +#include "sink.h" +#include "source.h" +#include "avrcp.h" + +#define MEDIA_TRANSPORT_INTERFACE "org.bluez.MediaTransport1" + +typedef enum { + TRANSPORT_STATE_IDLE, /* Not acquired and suspended */ + TRANSPORT_STATE_PENDING, /* Playing but not acquired */ + TRANSPORT_STATE_REQUESTING, /* Acquire in progress */ + TRANSPORT_STATE_ACTIVE, /* Acquired and playing */ + TRANSPORT_STATE_SUSPENDING, /* Release in progress */ +} transport_state_t; + +static char *str_state[] = { + "TRANSPORT_STATE_IDLE", + "TRANSPORT_STATE_PENDING", + "TRANSPORT_STATE_REQUESTING", + "TRANSPORT_STATE_ACTIVE", + "TRANSPORT_STATE_SUSPENDING", +}; + +struct media_request { + DBusMessage *msg; + guint id; +}; + +struct media_owner { + struct media_transport *transport; + struct media_request *pending; + char *name; + guint watch; +}; + +struct a2dp_transport { + struct avdtp *session; + uint16_t delay; + uint16_t volume; +}; + +struct media_transport { + char *path; /* Transport object path */ + struct btd_device *device; /* Transport device */ + const char *remote_endpoint; /* Transport remote SEP */ + struct media_endpoint *endpoint; /* Transport endpoint */ + struct media_owner *owner; /* Transport owner */ + uint8_t *configuration; /* Transport configuration */ + int size; /* Transport configuration size */ + int fd; /* Transport file descriptor */ + uint16_t imtu; /* Transport input mtu */ + uint16_t omtu; /* Transport output mtu */ + transport_state_t state; + guint hs_watch; + guint source_watch; + guint sink_watch; + guint (*resume) (struct media_transport *transport, + struct media_owner *owner); + guint (*suspend) (struct media_transport *transport, + struct media_owner *owner); + void (*cancel) (struct media_transport *transport, + guint id); + GDestroyNotify destroy; + void *data; +}; + +static GSList *transports = NULL; + +static const char *state2str(transport_state_t state) +{ + switch (state) { + case TRANSPORT_STATE_IDLE: + case TRANSPORT_STATE_REQUESTING: + return "idle"; + case TRANSPORT_STATE_PENDING: + return "pending"; + case TRANSPORT_STATE_ACTIVE: + case TRANSPORT_STATE_SUSPENDING: + return "active"; + } + + return NULL; +} + +static gboolean state_in_use(transport_state_t state) +{ + switch (state) { + case TRANSPORT_STATE_IDLE: + case TRANSPORT_STATE_PENDING: + return FALSE; + case TRANSPORT_STATE_REQUESTING: + case TRANSPORT_STATE_ACTIVE: + case TRANSPORT_STATE_SUSPENDING: + return TRUE; + } + + return FALSE; +} + +static void transport_set_state(struct media_transport *transport, + transport_state_t state) +{ + transport_state_t old_state = transport->state; + const char *str; + + if (old_state == state) + return; + + transport->state = state; + + DBG("State changed %s: %s -> %s", transport->path, str_state[old_state], + str_state[state]); + + str = state2str(state); + + if (g_strcmp0(str, state2str(old_state)) != 0) + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, + MEDIA_TRANSPORT_INTERFACE, + "State"); +} + +void media_transport_destroy(struct media_transport *transport) +{ + char *path; + + if (transport->sink_watch) + sink_remove_state_cb(transport->sink_watch); + + if (transport->source_watch) + source_remove_state_cb(transport->source_watch); + + path = g_strdup(transport->path); + g_dbus_unregister_interface(btd_get_dbus_connection(), path, + MEDIA_TRANSPORT_INTERFACE); + + g_free(path); +} + +static struct media_request *media_request_create(DBusMessage *msg, guint id) +{ + struct media_request *req; + + req = g_new0(struct media_request, 1); + req->msg = dbus_message_ref(msg); + req->id = id; + + DBG("Request created: method=%s id=%u", dbus_message_get_member(msg), + id); + + return req; +} + +static void media_request_reply(struct media_request *req, int err) +{ + DBusMessage *reply; + + DBG("Request %s Reply %s", dbus_message_get_member(req->msg), + strerror(err)); + + if (!err) + reply = g_dbus_create_reply(req->msg, DBUS_TYPE_INVALID); + else + reply = g_dbus_create_error(req->msg, + ERROR_INTERFACE ".Failed", + "%s", strerror(err)); + + g_dbus_send_message(btd_get_dbus_connection(), reply); +} + +static void media_owner_remove(struct media_owner *owner) +{ + struct media_transport *transport = owner->transport; + struct media_request *req = owner->pending; + + if (!req) + return; + + DBG("Owner %s Request %s", owner->name, + dbus_message_get_member(req->msg)); + + if (req->id) + transport->cancel(transport, req->id); + + owner->pending = NULL; + if (req->msg) + dbus_message_unref(req->msg); + + g_free(req); +} + +static void media_owner_free(struct media_owner *owner) +{ + DBG("Owner %s", owner->name); + + media_owner_remove(owner); + + g_free(owner->name); + g_free(owner); +} + +static void media_transport_remove_owner(struct media_transport *transport) +{ + struct media_owner *owner = transport->owner; + + DBG("Transport %s Owner %s", transport->path, owner->name); + + /* Reply if owner has a pending request */ + if (owner->pending) + media_request_reply(owner->pending, EIO); + + transport->owner = NULL; + + if (owner->watch) + g_dbus_remove_watch(btd_get_dbus_connection(), owner->watch); + + media_owner_free(owner); + + if (state_in_use(transport->state)) + transport->suspend(transport, NULL); +} + +static gboolean media_transport_set_fd(struct media_transport *transport, + int fd, uint16_t imtu, uint16_t omtu) +{ + if (transport->fd == fd) + return TRUE; + + transport->fd = fd; + transport->imtu = imtu; + transport->omtu = omtu; + + info("%s: fd(%d) ready", transport->path, fd); + + return TRUE; +} + +static void a2dp_resume_complete(struct avdtp *session, int err, + void *user_data) +{ + struct media_owner *owner = user_data; + struct media_request *req = owner->pending; + struct media_transport *transport = owner->transport; + struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint); + struct avdtp_stream *stream; + int fd; + uint16_t imtu, omtu; + gboolean ret; + + req->id = 0; + + if (err) + goto fail; + + stream = a2dp_sep_get_stream(sep); + if (stream == NULL) + goto fail; + + ret = avdtp_stream_get_transport(stream, &fd, &imtu, &omtu, NULL); + if (ret == FALSE) + goto fail; + + media_transport_set_fd(transport, fd, imtu, omtu); + + ret = g_dbus_send_reply(btd_get_dbus_connection(), req->msg, + DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_UINT16, &imtu, + DBUS_TYPE_UINT16, &omtu, + DBUS_TYPE_INVALID); + if (ret == FALSE) + goto fail; + + media_owner_remove(owner); + + transport_set_state(transport, TRANSPORT_STATE_ACTIVE); + + return; + +fail: + media_transport_remove_owner(transport); +} + +static guint resume_a2dp(struct media_transport *transport, + struct media_owner *owner) +{ + struct a2dp_transport *a2dp = transport->data; + struct media_endpoint *endpoint = transport->endpoint; + struct a2dp_sep *sep = media_endpoint_get_sep(endpoint); + guint id; + + if (a2dp->session == NULL) { + a2dp->session = a2dp_avdtp_get(transport->device); + if (a2dp->session == NULL) + return 0; + } + + if (state_in_use(transport->state)) + return a2dp_resume(a2dp->session, sep, a2dp_resume_complete, + owner); + + if (a2dp_sep_lock(sep, a2dp->session) == FALSE) + return 0; + + id = a2dp_resume(a2dp->session, sep, a2dp_resume_complete, owner); + + if (id == 0) { + a2dp_sep_unlock(sep, a2dp->session); + return 0; + } + + if (transport->state == TRANSPORT_STATE_IDLE) + transport_set_state(transport, TRANSPORT_STATE_REQUESTING); + + return id; +} + +static void a2dp_suspend_complete(struct avdtp *session, int err, + void *user_data) +{ + struct media_owner *owner = user_data; + struct media_transport *transport = owner->transport; + struct a2dp_transport *a2dp = transport->data; + struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint); + + /* Release always succeeds */ + if (owner->pending) { + owner->pending->id = 0; + media_request_reply(owner->pending, 0); + media_owner_remove(owner); + } + + a2dp_sep_unlock(sep, a2dp->session); + transport_set_state(transport, TRANSPORT_STATE_IDLE); + media_transport_remove_owner(transport); +} + +static guint suspend_a2dp(struct media_transport *transport, + struct media_owner *owner) +{ + struct a2dp_transport *a2dp = transport->data; + struct media_endpoint *endpoint = transport->endpoint; + struct a2dp_sep *sep = media_endpoint_get_sep(endpoint); + + if (owner != NULL) + return a2dp_suspend(a2dp->session, sep, a2dp_suspend_complete, + owner); + + transport_set_state(transport, TRANSPORT_STATE_IDLE); + a2dp_sep_unlock(sep, a2dp->session); + + return 0; +} + +static void cancel_a2dp(struct media_transport *transport, guint id) +{ + a2dp_cancel(id); +} + +static void media_owner_exit(DBusConnection *connection, void *user_data) +{ + struct media_owner *owner = user_data; + + owner->watch = 0; + + media_owner_remove(owner); + + media_transport_remove_owner(owner->transport); +} + +static void media_transport_set_owner(struct media_transport *transport, + struct media_owner *owner) +{ + DBG("Transport %s Owner %s", transport->path, owner->name); + transport->owner = owner; + owner->transport = transport; + owner->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), + owner->name, + media_owner_exit, + owner, NULL); +} + +static struct media_owner *media_owner_create(DBusMessage *msg) +{ + struct media_owner *owner; + + owner = g_new0(struct media_owner, 1); + owner->name = g_strdup(dbus_message_get_sender(msg)); + + DBG("Owner created: sender=%s", owner->name); + + return owner; +} + +static void media_owner_add(struct media_owner *owner, + struct media_request *req) +{ + DBG("Owner %s Request %s", owner->name, + dbus_message_get_member(req->msg)); + + owner->pending = req; +} + +static DBusMessage *acquire(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + struct media_owner *owner; + struct media_request *req; + guint id; + + if (transport->owner != NULL) + return btd_error_not_authorized(msg); + + if (transport->state >= TRANSPORT_STATE_REQUESTING) + return btd_error_not_authorized(msg); + + owner = media_owner_create(msg); + id = transport->resume(transport, owner); + if (id == 0) { + media_owner_free(owner); + return btd_error_not_authorized(msg); + } + + req = media_request_create(msg, id); + media_owner_add(owner, req); + media_transport_set_owner(transport, owner); + + return NULL; +} + +static DBusMessage *try_acquire(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + struct media_owner *owner; + struct media_request *req; + guint id; + + if (transport->owner != NULL) + return btd_error_not_authorized(msg); + + if (transport->state >= TRANSPORT_STATE_REQUESTING) + return btd_error_not_authorized(msg); + + if (transport->state != TRANSPORT_STATE_PENDING) + return btd_error_not_available(msg); + + owner = media_owner_create(msg); + id = transport->resume(transport, owner); + if (id == 0) { + media_owner_free(owner); + return btd_error_not_authorized(msg); + } + + req = media_request_create(msg, id); + media_owner_add(owner, req); + media_transport_set_owner(transport, owner); + + return NULL; +} + +static DBusMessage *release(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_transport *transport = data; + struct media_owner *owner = transport->owner; + const char *sender; + struct media_request *req; + guint id; + + sender = dbus_message_get_sender(msg); + + if (owner == NULL || g_strcmp0(owner->name, sender) != 0) + return btd_error_not_authorized(msg); + + if (owner->pending) { + const char *member; + + member = dbus_message_get_member(owner->pending->msg); + /* Cancel Acquire request if that exist */ + if (g_str_equal(member, "Acquire")) + media_owner_remove(owner); + else + return btd_error_in_progress(msg); + } + + transport_set_state(transport, TRANSPORT_STATE_SUSPENDING); + + id = transport->suspend(transport, owner); + if (id == 0) { + media_transport_remove_owner(transport); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + } + + req = media_request_create(msg, id); + media_owner_add(owner, req); + + return NULL; +} + +static gboolean get_device(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + const char *path = device_get_path(transport->device); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + return TRUE; +} + +static gboolean get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + const char *uuid = media_endpoint_get_uuid(transport->endpoint); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); + + return TRUE; +} + +static gboolean get_codec(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + uint8_t codec = media_endpoint_get_codec(transport->endpoint); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &codec); + + return TRUE; +} + +static gboolean get_configuration(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array); + + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &transport->configuration, + transport->size); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean get_state(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + const char *state = state2str(transport->state); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &state); + + return TRUE; +} + +static gboolean delay_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_transport *transport = data; + struct a2dp_transport *a2dp = transport->data; + + return a2dp->delay != 0; +} + +static gboolean get_delay(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct a2dp_transport *a2dp = transport->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &a2dp->delay); + + return TRUE; +} + +static gboolean volume_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_transport *transport = data; + struct a2dp_transport *a2dp = transport->data; + + return a2dp->volume <= 127; +} + +static gboolean get_volume(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + struct a2dp_transport *a2dp = transport->data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &a2dp->volume); + + return TRUE; +} + +static void set_volume(const GDBusPropertyTable *property, + DBusMessageIter *iter, GDBusPendingPropertySet id, + void *data) +{ + struct media_transport *transport = data; + struct a2dp_transport *a2dp = transport->data; + uint16_t volume; + bool notify; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(iter, &volume); + + if (volume > 127) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + g_dbus_pending_property_success(id); + + if (a2dp->volume == volume) + return; + + a2dp->volume = volume; + + notify = transport->source_watch ? true : false; + if (notify) + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, + MEDIA_TRANSPORT_INTERFACE, + "Volume"); + + avrcp_set_volume(transport->device, volume, notify); +} + +static gboolean endpoint_exists(const GDBusPropertyTable *property, void *data) +{ + struct media_transport *transport = data; + + return transport->remote_endpoint != NULL; +} + +static gboolean get_endpoint(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct media_transport *transport = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &transport->remote_endpoint); + + return TRUE; +} + +static const GDBusMethodTable transport_methods[] = { + { GDBUS_ASYNC_METHOD("Acquire", + NULL, + GDBUS_ARGS({ "fd", "h" }, { "mtu_r", "q" }, + { "mtu_w", "q" }), + acquire) }, + { GDBUS_ASYNC_METHOD("TryAcquire", + NULL, + GDBUS_ARGS({ "fd", "h" }, { "mtu_r", "q" }, + { "mtu_w", "q" }), + try_acquire) }, + { GDBUS_ASYNC_METHOD("Release", NULL, NULL, release) }, + { }, +}; + +static const GDBusPropertyTable transport_properties[] = { + { "Device", "o", get_device }, + { "UUID", "s", get_uuid }, + { "Codec", "y", get_codec }, + { "Configuration", "ay", get_configuration }, + { "State", "s", get_state }, + { "Delay", "q", get_delay, NULL, delay_exists }, + { "Volume", "q", get_volume, set_volume, volume_exists }, + { "Endpoint", "o", get_endpoint, NULL, endpoint_exists, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { } +}; + +static void destroy_a2dp(void *data) +{ + struct a2dp_transport *a2dp = data; + + if (a2dp->session) + avdtp_unref(a2dp->session); + + g_free(a2dp); +} + +static void media_transport_free(void *data) +{ + struct media_transport *transport = data; + + transports = g_slist_remove(transports, transport); + + if (transport->owner) + media_transport_remove_owner(transport); + + if (transport->destroy != NULL) + transport->destroy(transport->data); + + g_free(transport->configuration); + g_free(transport->path); + g_free(transport); +} + +static void transport_update_playing(struct media_transport *transport, + gboolean playing) +{ + DBG("%s State=%s Playing=%d", transport->path, + str_state[transport->state], playing); + + if (playing == FALSE) { + if (transport->state == TRANSPORT_STATE_PENDING) + transport_set_state(transport, TRANSPORT_STATE_IDLE); + else if (transport->state == TRANSPORT_STATE_ACTIVE) { + /* Remove owner */ + if (transport->owner != NULL) + media_transport_remove_owner(transport); + } + } else if (transport->state == TRANSPORT_STATE_IDLE) + transport_set_state(transport, TRANSPORT_STATE_PENDING); +} + +static void sink_state_changed(struct btd_service *service, + sink_state_t old_state, + sink_state_t new_state, + void *user_data) +{ + struct media_transport *transport = user_data; + + if (new_state == SINK_STATE_PLAYING) + transport_update_playing(transport, TRUE); + else + transport_update_playing(transport, FALSE); +} + +static void source_state_changed(struct btd_service *service, + source_state_t old_state, + source_state_t new_state, + void *user_data) +{ + struct media_transport *transport = user_data; + + if (new_state == SOURCE_STATE_PLAYING) + transport_update_playing(transport, TRUE); + else + transport_update_playing(transport, FALSE); +} + +static int media_transport_init_source(struct media_transport *transport) +{ + struct btd_service *service; + struct a2dp_transport *a2dp; + + service = btd_device_get_service(transport->device, A2DP_SINK_UUID); + if (service == NULL) + return -EINVAL; + + a2dp = g_new0(struct a2dp_transport, 1); + + transport->resume = resume_a2dp; + transport->suspend = suspend_a2dp; + transport->cancel = cancel_a2dp; + transport->data = a2dp; + transport->destroy = destroy_a2dp; + + a2dp->volume = -1; + transport->sink_watch = sink_add_state_cb(service, sink_state_changed, + transport); + + return 0; +} + +static int media_transport_init_sink(struct media_transport *transport) +{ + struct btd_service *service; + struct a2dp_transport *a2dp; + + service = btd_device_get_service(transport->device, A2DP_SOURCE_UUID); + if (service == NULL) + return -EINVAL; + + a2dp = g_new0(struct a2dp_transport, 1); + + transport->resume = resume_a2dp; + transport->suspend = suspend_a2dp; + transport->cancel = cancel_a2dp; + transport->data = a2dp; + transport->destroy = destroy_a2dp; + + a2dp->volume = 127; + transport->source_watch = source_add_state_cb(service, + source_state_changed, + transport); + + return 0; +} + +struct media_transport *media_transport_create(struct btd_device *device, + const char *remote_endpoint, + uint8_t *configuration, + size_t size, void *data) +{ + struct media_endpoint *endpoint = data; + struct media_transport *transport; + const char *uuid; + static int fd = 0; + + transport = g_new0(struct media_transport, 1); + transport->device = device; + transport->endpoint = endpoint; + transport->configuration = g_new(uint8_t, size); + memcpy(transport->configuration, configuration, size); + transport->size = size; + transport->remote_endpoint = remote_endpoint; + transport->path = g_strdup_printf("%s/fd%d", + remote_endpoint ? remote_endpoint : + device_get_path(device), fd++); + transport->fd = -1; + + uuid = media_endpoint_get_uuid(endpoint); + if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) { + if (media_transport_init_source(transport) < 0) + goto fail; + } else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) { + if (media_transport_init_sink(transport) < 0) + goto fail; + } else + goto fail; + + if (g_dbus_register_interface(btd_get_dbus_connection(), + transport->path, MEDIA_TRANSPORT_INTERFACE, + transport_methods, NULL, transport_properties, + transport, media_transport_free) == FALSE) { + error("Could not register transport %s", transport->path); + goto fail; + } + + transports = g_slist_append(transports, transport); + + return transport; + +fail: + media_transport_free(transport); + return NULL; +} + +const char *media_transport_get_path(struct media_transport *transport) +{ + return transport->path; +} + +void media_transport_update_delay(struct media_transport *transport, + uint16_t delay) +{ + struct a2dp_transport *a2dp = transport->data; + + /* Check if delay really changed */ + if (a2dp->delay == delay) + return; + + a2dp->delay = delay; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, + MEDIA_TRANSPORT_INTERFACE, "Delay"); +} + +struct btd_device *media_transport_get_dev(struct media_transport *transport) +{ + return transport->device; +} + +uint16_t media_transport_get_volume(struct media_transport *transport) +{ + struct a2dp_transport *a2dp = transport->data; + return a2dp->volume; +} + +void media_transport_update_volume(struct media_transport *transport, + uint8_t volume) +{ + struct a2dp_transport *a2dp = transport->data; + + /* Check if volume really changed */ + if (a2dp->volume == volume) + return; + + a2dp->volume = volume; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + transport->path, + MEDIA_TRANSPORT_INTERFACE, "Volume"); +} + +uint8_t media_transport_get_device_volume(struct btd_device *dev) +{ + GSList *l; + + if (dev == NULL) + return 128; + + for (l = transports; l; l = l->next) { + struct media_transport *transport = l->data; + if (transport->device != dev) + continue; + + /* Volume is A2DP only */ + if (media_endpoint_get_sep(transport->endpoint)) + return media_transport_get_volume(transport); + } + + return 0; +} + +void media_transport_update_device_volume(struct btd_device *dev, + uint8_t volume) +{ + GSList *l; + + if (dev == NULL) + return; + + for (l = transports; l; l = l->next) { + struct media_transport *transport = l->data; + if (transport->device != dev) + continue; + + /* Volume is A2DP only */ + if (media_endpoint_get_sep(transport->endpoint)) + media_transport_update_volume(transport, volume); + } +} diff --git a/profiles/audio/transport.h b/profiles/audio/transport.h new file mode 100644 index 0000000..ac542bf --- /dev/null +++ b/profiles/audio/transport.h @@ -0,0 +1,45 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2009 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct media_transport; + +struct media_transport *media_transport_create(struct btd_device *device, + const char *remote_endpoint, + uint8_t *configuration, + size_t size, void *data); + +void media_transport_destroy(struct media_transport *transport); +const char *media_transport_get_path(struct media_transport *transport); +struct btd_device *media_transport_get_dev(struct media_transport *transport); +uint16_t media_transport_get_volume(struct media_transport *transport); +void media_transport_update_delay(struct media_transport *transport, + uint16_t delay); +void media_transport_update_volume(struct media_transport *transport, + uint8_t volume); +void transport_get_properties(struct media_transport *transport, + DBusMessageIter *iter); + +uint8_t media_transport_get_device_volume(struct btd_device *dev); +void media_transport_update_device_volume(struct btd_device *dev, + uint8_t volume); diff --git a/profiles/battery/bas.c b/profiles/battery/bas.c new file mode 100644 index 0000000..de369fd --- /dev/null +++ b/profiles/battery/bas.c @@ -0,0 +1,340 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can rebastribute 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 bastributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "src/log.h" + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "src/shared/queue.h" + +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" + +#include "profiles/battery/bas.h" + +#define ATT_NOTIFICATION_HEADER_SIZE 3 +#define ATT_READ_RESPONSE_HEADER_SIZE 1 + +struct bt_bas { + int ref_count; + GAttrib *attrib; + struct gatt_primary *primary; + uint16_t handle; + uint16_t ccc_handle; + guint id; + struct queue *gatt_op; +}; + +struct gatt_request { + unsigned int id; + struct bt_bas *bas; + void *user_data; +}; + +static void destroy_gatt_req(struct gatt_request *req) +{ + queue_remove(req->bas->gatt_op, req); + bt_bas_unref(req->bas); + free(req); +} + +static void bas_free(struct bt_bas *bas) +{ + bt_bas_detach(bas); + + g_free(bas->primary); + queue_destroy(bas->gatt_op, (void *) destroy_gatt_req); + free(bas); +} + +struct bt_bas *bt_bas_new(void *primary) +{ + struct bt_bas *bas; + + bas = new0(struct bt_bas, 1); + bas->gatt_op = queue_new(); + + if (primary) + bas->primary = g_memdup(primary, sizeof(*bas->primary)); + + return bt_bas_ref(bas); +} + +struct bt_bas *bt_bas_ref(struct bt_bas *bas) +{ + if (!bas) + return NULL; + + __sync_fetch_and_add(&bas->ref_count, 1); + + return bas; +} + +void bt_bas_unref(struct bt_bas *bas) +{ + if (!bas) + return; + + if (__sync_sub_and_fetch(&bas->ref_count, 1)) + return; + + bas_free(bas); +} + +static struct gatt_request *create_request(struct bt_bas *bas, + void *user_data) +{ + struct gatt_request *req; + + req = new0(struct gatt_request, 1); + req->user_data = user_data; + req->bas = bt_bas_ref(bas); + + return req; +} + +static void set_and_store_gatt_req(struct bt_bas *bas, + struct gatt_request *req, + unsigned int id) +{ + req->id = id; + queue_push_head(bas->gatt_op, req); +} + +static void write_char(struct bt_bas *bas, GAttrib *attrib, uint16_t handle, + const uint8_t *value, size_t vlen, + GAttribResultFunc func, + gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(bas, user_data); + + id = gatt_write_char(attrib, handle, value, vlen, func, req); + + set_and_store_gatt_req(bas, req, id); +} + +static void read_char(struct bt_bas *bas, GAttrib *attrib, uint16_t handle, + GAttribResultFunc func, gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(bas, user_data); + + id = gatt_read_char(attrib, handle, func, req); + + set_and_store_gatt_req(bas, req, id); +} + +static void discover_char(struct bt_bas *bas, GAttrib *attrib, + uint16_t start, uint16_t end, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(bas, user_data); + + id = gatt_discover_char(attrib, start, end, uuid, func, req); + + set_and_store_gatt_req(bas, req, id); +} + +static void discover_desc(struct bt_bas *bas, GAttrib *attrib, + uint16_t start, uint16_t end, bt_uuid_t *uuid, + gatt_cb_t func, gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(bas, user_data); + + id = gatt_discover_desc(attrib, start, end, uuid, func, req); + set_and_store_gatt_req(bas, req, id); +} + +static void notification_cb(const guint8 *pdu, guint16 len, gpointer user_data) +{ + DBG("Battery Level at %u", pdu[ATT_NOTIFICATION_HEADER_SIZE]); +} + +static void read_value_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + DBG("Battery Level at %u", pdu[ATT_READ_RESPONSE_HEADER_SIZE]); +} + +static void ccc_written_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct gatt_request *req = user_data; + struct bt_bas *bas = req->user_data; + + destroy_gatt_req(req); + + if (status != 0) { + error("Write Scan Refresh CCC failed: %s", + att_ecode2str(status)); + return; + } + + DBG("Battery Level: notification enabled"); + + bas->id = g_attrib_register(bas->attrib, ATT_OP_HANDLE_NOTIFY, + bas->handle, notification_cb, bas, + NULL); +} + +static void write_ccc(struct bt_bas *bas, GAttrib *attrib, uint16_t handle, + void *user_data) +{ + uint8_t value[2]; + + put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); + + write_char(bas, attrib, handle, value, sizeof(value), ccc_written_cb, + user_data); +} + +static void ccc_read_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct gatt_request *req = user_data; + struct bt_bas *bas = req->user_data; + + destroy_gatt_req(req); + + if (status != 0) { + error("Error reading CCC value: %s", att_ecode2str(status)); + return; + } + + write_ccc(bas, bas->attrib, bas->ccc_handle, bas); +} + +static void discover_descriptor_cb(uint8_t status, GSList *descs, + void *user_data) +{ + struct gatt_request *req = user_data; + struct bt_bas *bas = req->user_data; + struct gatt_desc *desc; + + destroy_gatt_req(req); + + if (status != 0) { + error("Discover descriptors failed: %s", att_ecode2str(status)); + return; + } + + /* There will be only one descriptor on list and it will be CCC */ + desc = descs->data; + bas->ccc_handle = desc->handle; + + read_char(bas, bas->attrib, desc->handle, ccc_read_cb, bas); +} + +static void bas_discovered_cb(uint8_t status, GSList *chars, void *user_data) +{ + struct gatt_request *req = user_data; + struct bt_bas *bas = req->user_data; + struct gatt_char *chr; + uint16_t start, end; + bt_uuid_t uuid; + + destroy_gatt_req(req); + + if (status) { + error("Battery: %s", att_ecode2str(status)); + return; + } + + chr = chars->data; + bas->handle = chr->value_handle; + + DBG("Battery handle: 0x%04x", bas->handle); + + read_char(bas, bas->attrib, bas->handle, read_value_cb, bas); + + start = chr->value_handle + 1; + end = bas->primary->range.end; + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + + discover_desc(bas, bas->attrib, start, end, &uuid, + discover_descriptor_cb, bas); +} + +bool bt_bas_attach(struct bt_bas *bas, void *attrib) +{ + if (!bas || bas->attrib || !bas->primary) + return false; + + bas->attrib = g_attrib_ref(attrib); + + if (bas->handle > 0) + return true; + + discover_char(bas, bas->attrib, bas->primary->range.start, + bas->primary->range.end, NULL, + bas_discovered_cb, bas); + + return true; +} + +static void cancel_gatt_req(struct gatt_request *req) +{ + if (g_attrib_cancel(req->bas->attrib, req->id)) + destroy_gatt_req(req); +} + +void bt_bas_detach(struct bt_bas *bas) +{ + if (!bas || !bas->attrib) + return; + + if (bas->id > 0) { + g_attrib_unregister(bas->attrib, bas->id); + bas->id = 0; + } + + queue_foreach(bas->gatt_op, (void *) cancel_gatt_req, NULL); + g_attrib_unref(bas->attrib); + bas->attrib = NULL; +} diff --git a/profiles/battery/bas.h b/profiles/battery/bas.h new file mode 100644 index 0000000..3e175b5 --- /dev/null +++ b/profiles/battery/bas.h @@ -0,0 +1,32 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can rebastribute 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 bastributed in the hope that 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 bt_bas; + +struct bt_bas *bt_bas_new(void *primary); + +struct bt_bas *bt_bas_ref(struct bt_bas *bas); +void bt_bas_unref(struct bt_bas *bas); + +bool bt_bas_attach(struct bt_bas *bas, void *gatt); +void bt_bas_detach(struct bt_bas *bas); diff --git a/profiles/battery/battery.c b/profiles/battery/battery.c new file mode 100644 index 0000000..4da4355 --- /dev/null +++ b/profiles/battery/battery.c @@ -0,0 +1,370 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT + * Copyright (C) 2014 Google Inc. + * Copyright (C) 2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/dbus-common.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/log.h" +#include "attrib/att.h" + +#define BATTERY_INTERFACE "org.bluez.Battery1" + +#define BATT_UUID16 0x180f + +/* Generic Attribute/Access Service */ +struct batt { + char *path; /* D-Bus path of device */ + struct btd_device *device; + struct gatt_db *db; + struct bt_gatt_client *client; + struct gatt_db_attribute *attr; + + unsigned int batt_level_cb_id; + uint16_t batt_level_io_handle; + + uint8_t *initial_value; + uint8_t percentage; +}; + +static void batt_free(struct batt *batt) +{ + gatt_db_unref(batt->db); + bt_gatt_client_unref(batt->client); + btd_device_unref(batt->device); + g_free (batt->initial_value); + g_free(batt); +} + +static void batt_reset(struct batt *batt) +{ + batt->attr = NULL; + gatt_db_unref(batt->db); + batt->db = NULL; + bt_gatt_client_unref(batt->client); + batt->client = NULL; + g_free (batt->initial_value); + batt->initial_value = NULL; + if (batt->path) { + g_dbus_unregister_interface(btd_get_dbus_connection(), + batt->path, BATTERY_INTERFACE); + g_free(batt->path); + batt->path = NULL; + } +} + +static void parse_battery_level(struct batt *batt, + const uint8_t *value) +{ + uint8_t percentage; + + percentage = value[0]; + if (batt->percentage != percentage) { + batt->percentage = percentage; + DBG("Battery Level updated: %d%%", percentage); + g_dbus_emit_property_changed(btd_get_dbus_connection(), batt->path, + BATTERY_INTERFACE, "Percentage"); + } +} + +static void batt_io_value_cb(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct batt *batt = user_data; + + if (value_handle == batt->batt_level_io_handle) { + parse_battery_level(batt, value); + } else { + g_assert_not_reached(); + } +} + +static gboolean property_get_percentage( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct batt *batt = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &batt->percentage); + + return TRUE; +} + +static const GDBusPropertyTable battery_properties[] = { + { "Percentage", "y", property_get_percentage }, + { } +}; + +static void batt_io_ccc_written_cb(uint16_t att_ecode, void *user_data) +{ + struct batt *batt = user_data; + + if (att_ecode != 0) { + error("Battery Level: notifications not enabled %s", + att_ecode2str(att_ecode)); + return; + } + + if (g_dbus_register_interface(btd_get_dbus_connection(), + batt->path, BATTERY_INTERFACE, + NULL, NULL, + battery_properties, batt, + NULL) == FALSE) { + error("Unable to register %s interface for %s", + BATTERY_INTERFACE, batt->path); + batt_reset(batt); + return; + } + + parse_battery_level(batt, batt->initial_value); + g_free (batt->initial_value); + batt->initial_value = NULL; + + DBG("Battery Level: notification enabled"); +} + +static void read_initial_battery_level_cb(bool success, + uint8_t att_ecode, + const uint8_t *value, + uint16_t length, + void *user_data) +{ + struct batt *batt = user_data; + + if (!success) { + DBG("Reading battery level failed with ATT errror: %u", + att_ecode); + return; + } + + if (!length) + return; + + batt->initial_value = g_memdup(value, length); + + /* request notify */ + batt->batt_level_cb_id = + bt_gatt_client_register_notify(batt->client, + batt->batt_level_io_handle, + batt_io_ccc_written_cb, + batt_io_value_cb, + batt, + NULL); +} + +static void handle_battery_level(struct batt *batt, uint16_t value_handle) +{ + batt->batt_level_io_handle = value_handle; + + if (!bt_gatt_client_read_value(batt->client, batt->batt_level_io_handle, + read_initial_battery_level_cb, batt, NULL)) + DBG("Failed to send request to read battery level"); +} + +static bool uuid_cmp(uint16_t u16, const bt_uuid_t *uuid) +{ + bt_uuid_t lhs; + + bt_uuid16_create(&lhs, u16); + + return bt_uuid_cmp(&lhs, uuid) == 0; +} + +static void handle_characteristic(struct gatt_db_attribute *attr, + void *user_data) +{ + struct batt *batt = user_data; + uint16_t value_handle; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, + NULL, &uuid)) { + error("Failed to obtain characteristic data"); + return; + } + + if (uuid_cmp(GATT_CHARAC_BATTERY_LEVEL, &uuid)) { + handle_battery_level(batt, value_handle); + } else { + char uuid_str[MAX_LEN_UUID_STR]; + + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + DBG("Unsupported characteristic: %s", uuid_str); + } +} + +static void handle_batt_service(struct batt *batt) +{ + gatt_db_service_foreach_char(batt->attr, handle_characteristic, batt); +} + +static int batt_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct batt *batt = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("BATT profile probe (%s)", addr); + + /* Ignore, if we were probed for this device already */ + if (batt) { + error("Profile probed twice for the same device!"); + return -1; + } + + batt = g_new0(struct batt, 1); + if (!batt) + return -1; + + batt->percentage = -1; + batt->device = btd_device_ref(device); + btd_service_set_user_data(service, batt); + + return 0; +} + +static void batt_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct batt *batt; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("BATT profile remove (%s)", addr); + + batt = btd_service_get_user_data(service); + if (!batt) { + error("BATT service not handled by profile"); + return; + } + + batt_free(batt); +} + +static void foreach_batt_service(struct gatt_db_attribute *attr, void *user_data) +{ + struct batt *batt = user_data; + + if (batt->attr) { + error("More than one BATT service exists for this device"); + return; + } + + batt->attr = attr; + handle_batt_service(batt); +} + +static int batt_accept(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gatt_db *db = btd_device_get_gatt_db(device); + struct bt_gatt_client *client = btd_device_get_gatt_client(device); + struct batt *batt = btd_service_get_user_data(service); + char addr[18]; + bt_uuid_t batt_uuid; + + ba2str(device_get_address(device), addr); + DBG("BATT profile accept (%s)", addr); + + if (!batt) { + error("BATT service not handled by profile"); + return -1; + } + + batt->db = gatt_db_ref(db); + batt->client = bt_gatt_client_clone(client); + + /* Handle the BATT services */ + bt_uuid16_create(&batt_uuid, BATT_UUID16); + gatt_db_foreach_service(db, &batt_uuid, foreach_batt_service, batt); + + if (!batt->attr) { + error("BATT attribute not found"); + batt_reset(batt); + return -1; + } + + batt->path = g_strdup (device_get_path(device)); + + btd_service_connecting_complete(service, 0); + + return 0; +} + +static int batt_disconnect(struct btd_service *service) +{ + struct batt *batt = btd_service_get_user_data(service); + + batt_reset(batt); + + btd_service_disconnecting_complete(service, 0); + + return 0; +} + +static struct btd_profile batt_profile = { + .name = "batt-profile", + .remote_uuid = BATTERY_UUID, + .device_probe = batt_probe, + .device_remove = batt_remove, + .accept = batt_accept, + .disconnect = batt_disconnect, +}; + +static int batt_init(void) +{ + return btd_profile_register(&batt_profile); +} + +static void batt_exit(void) +{ + btd_profile_unregister(&batt_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(battery, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + batt_init, batt_exit) diff --git a/profiles/cups/cups.h b/profiles/cups/cups.h new file mode 100644 index 0000000..f4e0c01 --- /dev/null +++ b/profiles/cups/cups.h @@ -0,0 +1,38 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +enum { /**** Backend exit codes ****/ + CUPS_BACKEND_OK = 0, /* Job completed successfully */ + CUPS_BACKEND_FAILED = 1, /* Job failed, use error-policy */ + CUPS_BACKEND_AUTH_REQUIRED = 2, /* Job failed, authentication required */ + CUPS_BACKEND_HOLD = 3, /* Job failed, hold job */ + CUPS_BACKEND_STOP = 4, /* Job failed, stop queue */ + CUPS_BACKEND_CANCEL = 5, /* Job failed, cancel job */ + CUPS_BACKEND_RETRY = 6, /* Failure requires us to retry (BlueZ specific) */ +}; + +int sdp_search_spp(sdp_session_t *sdp, uint8_t *channel); +int sdp_search_hcrp(sdp_session_t *sdp, unsigned short *ctrl_psm, unsigned short *data_psm); + +int spp_print(bdaddr_t *src, bdaddr_t *dst, uint8_t channel, int fd, int copies, const char *cups_class); +int hcrp_print(bdaddr_t *src, bdaddr_t *dst, unsigned short ctrl_psm, unsigned short data_psm, int fd, int copies, const char *cups_class); diff --git a/profiles/cups/hcrp.c b/profiles/cups/hcrp.c new file mode 100644 index 0000000..edaa2cd --- /dev/null +++ b/profiles/cups/hcrp.c @@ -0,0 +1,367 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/l2cap.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "cups.h" + +#define HCRP_PDU_CREDIT_GRANT 0x0001 +#define HCRP_PDU_CREDIT_REQUEST 0x0002 +#define HCRP_PDU_GET_LPT_STATUS 0x0005 + +#define HCRP_STATUS_FEATURE_UNSUPPORTED 0x0000 +#define HCRP_STATUS_SUCCESS 0x0001 +#define HCRP_STATUS_CREDIT_SYNC_ERROR 0x0002 +#define HCRP_STATUS_GENERIC_FAILURE 0xffff + +struct hcrp_pdu_hdr { + uint16_t pid; + uint16_t tid; + uint16_t plen; +} __attribute__ ((packed)); +#define HCRP_PDU_HDR_SIZE 6 + +struct hcrp_credit_grant_cp { + uint32_t credit; +} __attribute__ ((packed)); +#define HCRP_CREDIT_GRANT_CP_SIZE 4 + +struct hcrp_credit_grant_rp { + uint16_t status; +} __attribute__ ((packed)); +#define HCRP_CREDIT_GRANT_RP_SIZE 2 + +struct hcrp_credit_request_rp { + uint16_t status; + uint32_t credit; +} __attribute__ ((packed)); +#define HCRP_CREDIT_REQUEST_RP_SIZE 6 + +struct hcrp_get_lpt_status_rp { + uint16_t status; + uint8_t lpt_status; +} __attribute__ ((packed)); +#define HCRP_GET_LPT_STATUS_RP_SIZE 3 + +static int hcrp_credit_grant(int sk, uint16_t tid, uint32_t credit) +{ + struct hcrp_pdu_hdr hdr; + struct hcrp_credit_grant_cp cp; + struct hcrp_credit_grant_rp rp; + unsigned char buf[128]; + int len; + + hdr.pid = htons(HCRP_PDU_CREDIT_GRANT); + hdr.tid = htons(tid); + hdr.plen = htons(HCRP_CREDIT_GRANT_CP_SIZE); + cp.credit = credit; + memcpy(buf, &hdr, HCRP_PDU_HDR_SIZE); + memcpy(buf + HCRP_PDU_HDR_SIZE, &cp, HCRP_CREDIT_GRANT_CP_SIZE); + len = write(sk, buf, HCRP_PDU_HDR_SIZE + HCRP_CREDIT_GRANT_CP_SIZE); + if (len < 0) + return len; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + return len; + + memcpy(&hdr, buf, HCRP_PDU_HDR_SIZE); + memcpy(&rp, buf + HCRP_PDU_HDR_SIZE, HCRP_CREDIT_GRANT_RP_SIZE); + + if (ntohs(rp.status) != HCRP_STATUS_SUCCESS) { + errno = EIO; + return -1; + } + + return 0; +} + +static int hcrp_credit_request(int sk, uint16_t tid, uint32_t *credit) +{ + struct hcrp_pdu_hdr hdr; + struct hcrp_credit_request_rp rp; + unsigned char buf[128]; + int len; + + hdr.pid = htons(HCRP_PDU_CREDIT_REQUEST); + hdr.tid = htons(tid); + hdr.plen = htons(0); + memcpy(buf, &hdr, HCRP_PDU_HDR_SIZE); + len = write(sk, buf, HCRP_PDU_HDR_SIZE); + if (len < 0) + return len; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + return len; + + memcpy(&hdr, buf, HCRP_PDU_HDR_SIZE); + memcpy(&rp, buf + HCRP_PDU_HDR_SIZE, HCRP_CREDIT_REQUEST_RP_SIZE); + + if (ntohs(rp.status) != HCRP_STATUS_SUCCESS) { + errno = EIO; + return -1; + } + + if (credit) + *credit = ntohl(rp.credit); + + return 0; +} + +static int hcrp_get_lpt_status(int sk, uint16_t tid, uint8_t *lpt_status) +{ + struct hcrp_pdu_hdr hdr; + struct hcrp_get_lpt_status_rp rp; + unsigned char buf[128]; + int len; + + hdr.pid = htons(HCRP_PDU_GET_LPT_STATUS); + hdr.tid = htons(tid); + hdr.plen = htons(0); + memcpy(buf, &hdr, HCRP_PDU_HDR_SIZE); + len = write(sk, buf, HCRP_PDU_HDR_SIZE); + if (len < 0) + return len; + + len = read(sk, buf, sizeof(buf)); + if (len < 0) + return len; + + memcpy(&hdr, buf, HCRP_PDU_HDR_SIZE); + memcpy(&rp, buf + HCRP_PDU_HDR_SIZE, HCRP_GET_LPT_STATUS_RP_SIZE); + + if (ntohs(rp.status) != HCRP_STATUS_SUCCESS) { + errno = EIO; + return -1; + } + + if (lpt_status) + *lpt_status = rp.lpt_status; + + return 0; +} + +static inline int hcrp_get_next_tid(int tid) +{ + if (tid > 0xf000) + return 0; + else + return tid + 1; +} + +int hcrp_print(bdaddr_t *src, bdaddr_t *dst, unsigned short ctrl_psm, unsigned short data_psm, int fd, int copies, const char *cups_class) +{ + struct sockaddr_l2 addr; + struct l2cap_options opts; + socklen_t size; + unsigned char buf[2048]; + int i, ctrl_sk, data_sk, count, len, timeout = 0; + unsigned int mtu; + uint8_t status; + uint16_t tid = 0; + uint32_t tmp, credit = 0; + + if ((ctrl_sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) { + perror("ERROR: Can't create socket"); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + + if (bind(ctrl_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("ERROR: Can't bind socket"); + close(ctrl_sk); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + addr.l2_psm = htobs(ctrl_psm); + + if (connect(ctrl_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("ERROR: Can't connect to device"); + close(ctrl_sk); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + if ((data_sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) { + perror("ERROR: Can't create socket"); + close(ctrl_sk); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + + if (bind(data_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("ERROR: Can't bind socket"); + close(data_sk); + close(ctrl_sk); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + addr.l2_psm = htobs(data_psm); + + if (connect(data_sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("ERROR: Can't connect to device"); + close(data_sk); + close(ctrl_sk); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + fputs("STATE: -connecting-to-device\n", stderr); + + memset(&opts, 0, sizeof(opts)); + size = sizeof(opts); + + if (getsockopt(data_sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &size) < 0) { + perror("ERROR: Can't get socket options"); + close(data_sk); + close(ctrl_sk); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + mtu = opts.omtu; + + /* Ignore SIGTERM signals if printing from stdin */ + if (fd == 0) { +#ifdef HAVE_SIGSET + sigset(SIGTERM, SIG_IGN); +#elif defined(HAVE_SIGACTION) + memset(&action, 0, sizeof(action)); + sigemptyset(&action.sa_mask); + action.sa_handler = SIG_IGN; + sigaction(SIGTERM, &action, NULL); +#else + signal(SIGTERM, SIG_IGN); +#endif /* HAVE_SIGSET */ + } + + tid = hcrp_get_next_tid(tid); + if (hcrp_credit_grant(ctrl_sk, tid, 0) < 0) { + fprintf(stderr, "ERROR: Can't grant initial credits\n"); + close(data_sk); + close(ctrl_sk); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + for (i = 0; i < copies; i++) { + + if (fd != 0) { + fprintf(stderr, "PAGE: 1 1\n"); + lseek(fd, 0, SEEK_SET); + } + + while (1) { + if (credit < mtu) { + tid = hcrp_get_next_tid(tid); + if (!hcrp_credit_request(ctrl_sk, tid, &tmp)) { + credit += tmp; + timeout = 0; + } + } + + if (!credit) { + if (timeout++ > 300) { + tid = hcrp_get_next_tid(tid); + if (!hcrp_get_lpt_status(ctrl_sk, tid, &status)) + fprintf(stderr, "ERROR: LPT status 0x%02x\n", status); + break; + } + + sleep(1); + continue; + } + + count = read(fd, buf, (credit > mtu) ? mtu : credit); + if (count <= 0) + break; + + len = write(data_sk, buf, count); + if (len < 0) { + perror("ERROR: Error writing to device"); + close(data_sk); + close(ctrl_sk); + return CUPS_BACKEND_FAILED; + } + + if (len != count) + fprintf(stderr, "ERROR: Can't send complete data\n"); + + credit -= len; + } + + } + + close(data_sk); + close(ctrl_sk); + + return CUPS_BACKEND_OK; +} diff --git a/profiles/cups/main.c b/profiles/cups/main.c new file mode 100644 index 0000000..1fac726 --- /dev/null +++ b/profiles/cups/main.c @@ -0,0 +1,880 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "gdbus/gdbus.h" + +#include "cups.h" + +struct cups_device { + char *bdaddr; + char *name; + char *id; +}; + +static GSList *device_list = NULL; +static GMainLoop *loop = NULL; +static DBusConnection *conn = NULL; +static gboolean doing_disco = FALSE; + +#define ATTRID_1284ID 0x0300 + +struct context_data { + gboolean found; + char *id; +}; + +static void element_start(GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + gpointer user_data, GError **err) +{ + struct context_data *ctx_data = user_data; + + if (!strcmp(element_name, "record")) + return; + + if (!strcmp(element_name, "attribute")) { + int i; + for (i = 0; attribute_names[i]; i++) { + if (strcmp(attribute_names[i], "id") != 0) + continue; + if (strtol(attribute_values[i], 0, 0) == ATTRID_1284ID) + ctx_data->found = TRUE; + break; + } + return; + } + + if (ctx_data->found && !strcmp(element_name, "text")) { + int i; + for (i = 0; attribute_names[i]; i++) { + if (!strcmp(attribute_names[i], "value")) { + ctx_data->id = g_strdup(attribute_values[i] + 2); + ctx_data->found = FALSE; + } + } + } +} + +static GMarkupParser parser = { + element_start, NULL, NULL, NULL, NULL +}; + +static char *sdp_xml_parse_record(const char *data) +{ + GMarkupParseContext *ctx; + struct context_data ctx_data; + int size; + + size = strlen(data); + ctx_data.found = FALSE; + ctx_data.id = NULL; + ctx = g_markup_parse_context_new(&parser, 0, &ctx_data, NULL); + + if (g_markup_parse_context_parse(ctx, data, size, NULL) == FALSE) { + g_markup_parse_context_free(ctx); + g_free(ctx_data.id); + return NULL; + } + + g_markup_parse_context_free(ctx); + + return ctx_data.id; +} + +static char *device_get_ieee1284_id(const char *adapter, const char *device) +{ + DBusMessage *message, *reply; + DBusMessageIter iter, reply_iter; + DBusMessageIter reply_iter_entry; + const char *hcr_print = "00001126-0000-1000-8000-00805f9b34fb"; + const char *xml; + char *id = NULL; + + /* Look for the service handle of the HCRP service */ + message = dbus_message_new_method_call("org.bluez", device, + "org.bluez.Device1", + "DiscoverServices"); + dbus_message_iter_init_append(message, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &hcr_print); + + reply = dbus_connection_send_with_reply_and_block(conn, + message, -1, NULL); + + dbus_message_unref(message); + + if (!reply) + return NULL; + + dbus_message_iter_init(reply, &reply_iter); + + if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_ARRAY) { + dbus_message_unref(reply); + return NULL; + } + + dbus_message_iter_recurse(&reply_iter, &reply_iter_entry); + + /* Hopefully we only get one handle, or take a punt */ + while (dbus_message_iter_get_arg_type(&reply_iter_entry) == + DBUS_TYPE_DICT_ENTRY) { + guint32 key; + DBusMessageIter dict_entry; + + dbus_message_iter_recurse(&reply_iter_entry, &dict_entry); + + /* Key ? */ + dbus_message_iter_get_basic(&dict_entry, &key); + if (!key) { + dbus_message_iter_next(&reply_iter_entry); + continue; + } + + /* Try to get the value */ + if (!dbus_message_iter_next(&dict_entry)) { + dbus_message_iter_next(&reply_iter_entry); + continue; + } + + dbus_message_iter_get_basic(&dict_entry, &xml); + + id = sdp_xml_parse_record(xml); + if (id != NULL) + break; + dbus_message_iter_next(&reply_iter_entry); + } + + dbus_message_unref(reply); + + return id; +} + +static void print_printer_details(const char *name, const char *bdaddr, + const char *id) +{ + char *uri, *escaped; + + escaped = g_strdelimit(g_strdup(name), "\"", '\''); + uri = g_strdup_printf("bluetooth://%c%c%c%c%c%c%c%c%c%c%c%c", + bdaddr[0], bdaddr[1], + bdaddr[3], bdaddr[4], + bdaddr[6], bdaddr[7], + bdaddr[9], bdaddr[10], + bdaddr[12], bdaddr[13], + bdaddr[15], bdaddr[16]); + printf("direct %s \"%s\" \"%s (Bluetooth)\"", uri, escaped, escaped); + if (id != NULL) + printf(" \"%s\"\n", id); + else + printf("\n"); + g_free(escaped); + g_free(uri); +} + +static void add_device_to_list(const char *name, const char *bdaddr, + const char *id) +{ + struct cups_device *device; + GSList *l; + + /* Look for the device in the list */ + for (l = device_list; l != NULL; l = l->next) { + device = (struct cups_device *) l->data; + + if (strcmp(device->bdaddr, bdaddr) == 0) { + if (device->name != name) { + g_free(device->name); + device->name = g_strdup(name); + } + g_free(device->id); + device->id = g_strdup(id); + return; + } + } + + /* Or add it to the list if it's not there */ + device = g_new0(struct cups_device, 1); + device->bdaddr = g_strdup(bdaddr); + device->name = g_strdup(name); + device->id = g_strdup(id); + + device_list = g_slist_prepend(device_list, device); + print_printer_details(device->name, device->bdaddr, device->id); +} + +static gboolean parse_device_properties(DBusMessageIter *reply_iter, + char **name, char **bdaddr) +{ + guint32 class = 0; + DBusMessageIter reply_iter_entry; + + if (dbus_message_iter_get_arg_type(reply_iter) != DBUS_TYPE_ARRAY) + return FALSE; + + dbus_message_iter_recurse(reply_iter, &reply_iter_entry); + + while (dbus_message_iter_get_arg_type(&reply_iter_entry) == + DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter dict_entry, iter_dict_val; + + dbus_message_iter_recurse(&reply_iter_entry, &dict_entry); + + /* Key == Class ? */ + dbus_message_iter_get_basic(&dict_entry, &key); + if (!key) { + dbus_message_iter_next(&reply_iter_entry); + continue; + } + + if (strcmp(key, "Class") != 0 && + strcmp(key, "Alias") != 0 && + strcmp(key, "Address") != 0) { + dbus_message_iter_next(&reply_iter_entry); + continue; + } + + /* Try to get the value */ + if (!dbus_message_iter_next(&dict_entry)) { + dbus_message_iter_next(&reply_iter_entry); + continue; + } + dbus_message_iter_recurse(&dict_entry, &iter_dict_val); + if (strcmp(key, "Class") == 0) { + dbus_message_iter_get_basic(&iter_dict_val, &class); + } else { + const char *value; + dbus_message_iter_get_basic(&iter_dict_val, &value); + if (strcmp(key, "Alias") == 0) { + *name = g_strdup(value); + } else if (bdaddr) { + *bdaddr = g_strdup(value); + } + } + dbus_message_iter_next(&reply_iter_entry); + } + + if (class == 0) + return FALSE; + if (((class & 0x1f00) >> 8) == 0x06 && (class & 0x80)) + return TRUE; + + return FALSE; +} + +static gboolean device_is_printer(const char *adapter, const char *device_path, char **name, char **bdaddr) +{ + DBusMessage *message, *reply; + DBusMessageIter reply_iter; + gboolean retval; + + message = dbus_message_new_method_call("org.bluez", device_path, + "org.bluez.Device1", + "GetProperties"); + + reply = dbus_connection_send_with_reply_and_block(conn, + message, -1, NULL); + + dbus_message_unref(message); + + if (!reply) + return FALSE; + + dbus_message_iter_init(reply, &reply_iter); + + retval = parse_device_properties(&reply_iter, name, bdaddr); + + dbus_message_unref(reply); + + return retval; +} + +static void remote_device_found(const char *adapter, const char *bdaddr, + const char *name) +{ + DBusMessage *message, *reply; + DBusMessageIter iter; + char *object_path = NULL; + char *id; + + assert(adapter != NULL); + + message = dbus_message_new_method_call("org.bluez", adapter, + "org.bluez.Adapter1", + "FindDevice"); + dbus_message_iter_init_append(message, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr); + + reply = dbus_connection_send_with_reply_and_block(conn, + message, -1, NULL); + + dbus_message_unref(message); + + if (!reply) { + message = dbus_message_new_method_call("org.bluez", adapter, + "org.bluez.Adapter1", + "CreateDevice"); + dbus_message_iter_init_append(message, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr); + + reply = dbus_connection_send_with_reply_and_block(conn, + message, -1, NULL); + + dbus_message_unref(message); + + if (!reply) + return; + } + + if (dbus_message_get_args(reply, NULL, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_INVALID) == FALSE) { + dbus_message_unref(reply); + return; + } + + id = device_get_ieee1284_id(adapter, object_path); + add_device_to_list(name, bdaddr, id); + g_free(id); + + dbus_message_unref(reply); +} + +static void discovery_completed(void) +{ + g_slist_free(device_list); + device_list = NULL; + + g_main_loop_quit(loop); +} + +static void remote_device_disappeared(const char *bdaddr) +{ + GSList *l; + + for (l = device_list; l != NULL; l = l->next) { + struct cups_device *device = l->data; + + if (strcmp(device->bdaddr, bdaddr) == 0) { + g_free(device->name); + g_free(device->bdaddr); + g_free(device); + device_list = g_slist_delete_link(device_list, l); + return; + } + } +} + +static gboolean list_known_printers(const char *adapter) +{ + DBusMessageIter reply_iter, iter_array; + DBusError error; + DBusMessage *message, *reply; + + message = dbus_message_new_method_call("org.bluez", adapter, + "org.bluez.Adapter1", + "ListDevices"); + if (message == NULL) + return FALSE; + + dbus_error_init(&error); + reply = dbus_connection_send_with_reply_and_block(conn, message, + -1, &error); + + dbus_message_unref(message); + + if (dbus_error_is_set(&error)) { + dbus_error_free(&error); + return FALSE; + } + + dbus_message_iter_init(reply, &reply_iter); + if (dbus_message_iter_get_arg_type(&reply_iter) != DBUS_TYPE_ARRAY) { + dbus_message_unref(reply); + return FALSE; + } + + dbus_message_iter_recurse(&reply_iter, &iter_array); + while (dbus_message_iter_get_arg_type(&iter_array) == + DBUS_TYPE_OBJECT_PATH) { + const char *object_path; + char *name = NULL; + char *bdaddr = NULL; + + dbus_message_iter_get_basic(&iter_array, &object_path); + if (device_is_printer(adapter, object_path, &name, &bdaddr)) { + char *id; + + id = device_get_ieee1284_id(adapter, object_path); + add_device_to_list(name, bdaddr, id); + g_free(id); + } + g_free(name); + g_free(bdaddr); + dbus_message_iter_next(&iter_array); + } + + dbus_message_unref(reply); + + return FALSE; +} + +static DBusHandlerResult filter_func(DBusConnection *connection, + DBusMessage *message, void *user_data) +{ + if (dbus_message_is_signal(message, "org.bluez.Adapter1", + "DeviceFound")) { + const char *adapter, *bdaddr; + char *name; + DBusMessageIter iter; + + dbus_message_iter_init(message, &iter); + dbus_message_iter_get_basic(&iter, &bdaddr); + dbus_message_iter_next(&iter); + + adapter = dbus_message_get_path(message); + if (parse_device_properties(&iter, &name, NULL)) + remote_device_found(adapter, bdaddr, name); + g_free (name); + } else if (dbus_message_is_signal(message, "org.bluez.Adapter1", + "DeviceDisappeared")) { + const char *bdaddr; + + dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &bdaddr, + DBUS_TYPE_INVALID); + remote_device_disappeared(bdaddr); + } else if (dbus_message_is_signal(message, "org.bluez.Adapter1", + "PropertyChanged")) { + DBusMessageIter iter, value_iter; + const char *name; + gboolean discovering; + + dbus_message_iter_init(message, &iter); + dbus_message_iter_get_basic(&iter, &name); + if (name == NULL || strcmp(name, "Discovering") != 0) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + dbus_message_iter_next(&iter); + dbus_message_iter_recurse(&iter, &value_iter); + dbus_message_iter_get_basic(&value_iter, &discovering); + + if (discovering == FALSE && doing_disco) { + doing_disco = FALSE; + discovery_completed(); + } + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static gboolean list_printers(void) +{ + /* 1. Connect to the bus + * 2. Get the manager + * 3. Get the default adapter + * 4. Get a list of devices + * 5. Get the class of each device + * 6. Print the details from each printer device + */ + DBusError error; + dbus_bool_t hcid_exists; + DBusMessage *reply, *message; + DBusMessageIter reply_iter; + char *adapter, *match; + + conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); + if (conn == NULL) + return TRUE; + + dbus_error_init(&error); + hcid_exists = dbus_bus_name_has_owner(conn, "org.bluez", &error); + if (dbus_error_is_set(&error)) { + dbus_error_free(&error); + return TRUE; + } + + if (!hcid_exists) + return TRUE; + + /* Get the default adapter */ + message = dbus_message_new_method_call("org.bluez", "/", + "org.bluez.Manager", + "DefaultAdapter"); + if (message == NULL) { + dbus_connection_unref(conn); + return FALSE; + } + + reply = dbus_connection_send_with_reply_and_block(conn, + message, -1, &error); + + dbus_message_unref(message); + + if (dbus_error_is_set(&error)) { + dbus_error_free(&error); + dbus_connection_unref(conn); + /* No adapter */ + return TRUE; + } + + dbus_message_iter_init(reply, &reply_iter); + if (dbus_message_iter_get_arg_type(&reply_iter) != + DBUS_TYPE_OBJECT_PATH) { + dbus_message_unref(reply); + dbus_connection_unref(conn); + return FALSE; + } + + dbus_message_iter_get_basic(&reply_iter, &adapter); + adapter = g_strdup(adapter); + dbus_message_unref(reply); + + if (!dbus_connection_add_filter(conn, filter_func, adapter, g_free)) { + g_free(adapter); + dbus_connection_unref(conn); + return FALSE; + } + +#define MATCH_FORMAT \ + "type='signal'," \ + "interface='org.bluez.Adapter1'," \ + "sender='org.bluez'," \ + "path='%s'" + + match = g_strdup_printf(MATCH_FORMAT, adapter); + dbus_bus_add_match(conn, match, &error); + g_free(match); + + /* Add the the recent devices */ + list_known_printers(adapter); + + doing_disco = TRUE; + message = dbus_message_new_method_call("org.bluez", adapter, + "org.bluez.Adapter1", + "StartDiscovery"); + + if (!dbus_connection_send_with_reply(conn, message, NULL, -1)) { + dbus_message_unref(message); + dbus_connection_unref(conn); + g_free(adapter); + return FALSE; + } + dbus_message_unref(message); + + loop = g_main_loop_new(NULL, TRUE); + g_main_loop_run(loop); + + g_free(adapter); + dbus_connection_unref(conn); + + return TRUE; +} + +static gboolean print_ieee1284(const char *bdaddr) +{ + DBusMessage *message, *reply, *adapter_reply; + DBusMessageIter iter; + char *object_path = NULL; + char *adapter; + char *id; + + adapter_reply = NULL; + + conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); + if (conn == NULL) + return FALSE; + + message = dbus_message_new_method_call("org.bluez", "/", + "org.bluez.Manager", + "DefaultAdapter"); + + adapter_reply = dbus_connection_send_with_reply_and_block(conn, + message, -1, NULL); + + dbus_message_unref(message); + + if (!adapter_reply) + return FALSE; + + if (dbus_message_get_args(adapter_reply, NULL, + DBUS_TYPE_OBJECT_PATH, &adapter, + DBUS_TYPE_INVALID) == FALSE) { + dbus_message_unref(adapter_reply); + return FALSE; + } + + message = dbus_message_new_method_call("org.bluez", adapter, + "org.bluez.Adapter1", + "FindDevice"); + dbus_message_iter_init_append(message, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &bdaddr); + + if (adapter_reply != NULL) + dbus_message_unref(adapter_reply); + + reply = dbus_connection_send_with_reply_and_block(conn, + message, -1, NULL); + + dbus_message_unref(message); + + if (!reply) { + message = dbus_message_new_method_call("org.bluez", adapter, + "org.bluez.Adapter1", + "CreateDevice"); + dbus_message_iter_init_append(message, &iter); + dbus_message_iter_append_basic(&iter, + DBUS_TYPE_STRING, &bdaddr); + + reply = dbus_connection_send_with_reply_and_block(conn, + message, -1, NULL); + + dbus_message_unref(message); + + if (!reply) + return FALSE; + } + + if (dbus_message_get_args(reply, NULL, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_INVALID) == FALSE) { + dbus_message_unref(reply); + return FALSE; + } + + id = device_get_ieee1284_id(adapter, object_path); + if (id == NULL) { + dbus_message_unref(reply); + return FALSE; + } + printf("%s", id); + g_free(id); + + dbus_message_unref(reply); + + return TRUE; +} + +/* + * Usage: printer-uri job-id user title copies options [file] + * + */ + +int main(int argc, char *argv[]) +{ + sdp_session_t *sdp; + bdaddr_t bdaddr; + unsigned short ctrl_psm, data_psm; + uint8_t channel, b[6]; + char *ptr, str[3], device[18], service[13]; + const char *uri, *cups_class; + int i, err, fd, copies, proto; + + /* Make sure status messages are not buffered */ + setbuf(stderr, NULL); + + /* Make sure output is not buffered */ + setbuf(stdout, NULL); + + /* Ignore SIGPIPE signals */ +#ifdef HAVE_SIGSET + sigset(SIGPIPE, SIG_IGN); +#elif defined(HAVE_SIGACTION) + memset(&action, 0, sizeof(action)); + action.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &action, NULL); +#else + signal(SIGPIPE, SIG_IGN); +#endif /* HAVE_SIGSET */ + + if (argc == 1) { + if (list_printers() == TRUE) + return CUPS_BACKEND_OK; + else + return CUPS_BACKEND_FAILED; + } else if (argc == 3 && strcmp(argv[1], "--get-deviceid") == 0) { + if (bachk(argv[2]) < 0) { + fprintf(stderr, "Invalid Bluetooth address '%s'\n", + argv[2]); + return CUPS_BACKEND_FAILED; + } + if (print_ieee1284(argv[2]) == FALSE) + return CUPS_BACKEND_FAILED; + return CUPS_BACKEND_OK; + } + + if (argc < 6 || argc > 7) { + fprintf(stderr, "Usage: bluetooth job-id user title copies" + " options [file]\n"); + fprintf(stderr, " bluetooth --get-deviceid [bdaddr]\n"); + return CUPS_BACKEND_FAILED; + } + + if (argc == 6) { + fd = 0; + copies = 1; + } else { + if ((fd = open(argv[6], O_RDONLY)) < 0) { + perror("ERROR: Unable to open print file"); + return CUPS_BACKEND_FAILED; + } + copies = atoi(argv[4]); + } + + uri = getenv("DEVICE_URI"); + if (!uri) + uri = argv[0]; + + if (strncasecmp(uri, "bluetooth://", 12)) { + fprintf(stderr, "ERROR: No device URI found\n"); + return CUPS_BACKEND_FAILED; + } + + ptr = argv[0] + 12; + for (i = 0; i < 6; i++) { + strncpy(str, ptr, 2); + b[i] = (uint8_t) strtol(str, NULL, 16); + ptr += 2; + } + sprintf(device, "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X", + b[0], b[1], b[2], b[3], b[4], b[5]); + + str2ba(device, &bdaddr); + + ptr = strchr(ptr, '/'); + if (ptr) { + strncpy(service, ptr + 1, 12); + + if (!strncasecmp(ptr + 1, "spp", 3)) + proto = 1; + else if (!strncasecmp(ptr + 1, "hcrp", 4)) + proto = 2; + else + proto = 0; + } else { + strcpy(service, "auto"); + proto = 0; + } + + cups_class = getenv("CLASS"); + + fprintf(stderr, + "DEBUG: %s device %s service %s fd %d copies %d class %s\n", + argv[0], device, service, fd, copies, + cups_class ? cups_class : "(none)"); + + fputs("STATE: +connecting-to-device\n", stderr); + +service_search: + sdp = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY); + if (!sdp) { + fprintf(stderr, "ERROR: Can't open Bluetooth connection\n"); + return CUPS_BACKEND_FAILED; + } + + switch (proto) { + case 1: + err = sdp_search_spp(sdp, &channel); + break; + case 2: + err = sdp_search_hcrp(sdp, &ctrl_psm, &data_psm); + break; + default: + proto = 2; + err = sdp_search_hcrp(sdp, &ctrl_psm, &data_psm); + if (err) { + proto = 1; + err = sdp_search_spp(sdp, &channel); + } + break; + } + + sdp_close(sdp); + + if (err) { + if (cups_class) { + fputs("INFO: Unable to contact printer, queuing on " + "next printer in class...\n", stderr); + sleep(5); + return CUPS_BACKEND_FAILED; + } + sleep(20); + fprintf(stderr, "ERROR: Can't get service information\n"); + goto service_search; + } + +connect: + switch (proto) { + case 1: + err = spp_print(BDADDR_ANY, &bdaddr, channel, + fd, copies, cups_class); + break; + case 2: + err = hcrp_print(BDADDR_ANY, &bdaddr, ctrl_psm, data_psm, + fd, copies, cups_class); + break; + default: + err = CUPS_BACKEND_FAILED; + fprintf(stderr, "ERROR: Unsupported protocol\n"); + break; + } + + if (err == CUPS_BACKEND_FAILED && cups_class) { + fputs("INFO: Unable to contact printer, queuing on " + "next printer in class...\n", stderr); + sleep(5); + return CUPS_BACKEND_FAILED; + } else if (err == CUPS_BACKEND_RETRY) { + sleep(20); + goto connect; + } + + if (fd != 0) + close(fd); + + if (!err) + fprintf(stderr, "INFO: Ready to print\n"); + + return err; +} diff --git a/profiles/cups/sdp.c b/profiles/cups/sdp.c new file mode 100644 index 0000000..de9cd4e --- /dev/null +++ b/profiles/cups/sdp.c @@ -0,0 +1,119 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "cups.h" + +int sdp_search_hcrp(sdp_session_t *sdp, unsigned short *ctrl_psm, unsigned short *data_psm) +{ + sdp_list_t *srch, *attrs, *rsp; + uuid_t svclass; + uint16_t attr1, attr2; + int err; + + if (!sdp) + return -1; + + sdp_uuid16_create(&svclass, HCR_PRINT_SVCLASS_ID); + srch = sdp_list_append(NULL, &svclass); + + attr1 = SDP_ATTR_PROTO_DESC_LIST; + attrs = sdp_list_append(NULL, &attr1); + attr2 = SDP_ATTR_ADD_PROTO_DESC_LIST; + attrs = sdp_list_append(attrs, &attr2); + + err = sdp_service_search_attr_req(sdp, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp); + if (err) + return -1; + + for (; rsp; rsp = rsp->next) { + sdp_record_t *rec = (sdp_record_t *) rsp->data; + sdp_list_t *protos; + + if (!sdp_get_access_protos(rec, &protos)) { + unsigned short psm = sdp_get_proto_port(protos, L2CAP_UUID); + if (psm > 0) { + *ctrl_psm = psm; + } + } + + if (!sdp_get_add_access_protos(rec, &protos)) { + unsigned short psm = sdp_get_proto_port(protos, L2CAP_UUID); + if (psm > 0 && *ctrl_psm > 0) { + *data_psm = psm; + return 0; + } + } + } + + return -1; +} + +int sdp_search_spp(sdp_session_t *sdp, uint8_t *channel) +{ + sdp_list_t *srch, *attrs, *rsp; + uuid_t svclass; + uint16_t attr; + int err; + + if (!sdp) + return -1; + + sdp_uuid16_create(&svclass, SERIAL_PORT_SVCLASS_ID); + srch = sdp_list_append(NULL, &svclass); + + attr = SDP_ATTR_PROTO_DESC_LIST; + attrs = sdp_list_append(NULL, &attr); + + err = sdp_service_search_attr_req(sdp, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp); + if (err) + return -1; + + for (; rsp; rsp = rsp->next) { + sdp_record_t *rec = (sdp_record_t *) rsp->data; + sdp_list_t *protos; + + if (!sdp_get_access_protos(rec, &protos)) { + uint8_t ch = sdp_get_proto_port(protos, RFCOMM_UUID); + if (ch > 0) { + *channel = ch; + return 0; + } + } + } + + return -1; +} diff --git a/profiles/cups/spp.c b/profiles/cups/spp.c new file mode 100644 index 0000000..2f1e270 --- /dev/null +++ b/profiles/cups/spp.c @@ -0,0 +1,118 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/rfcomm.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "cups.h" + +int spp_print(bdaddr_t *src, bdaddr_t *dst, uint8_t channel, int fd, int copies, const char *cups_class) +{ + struct sockaddr_rc addr; + unsigned char buf[2048]; + int i, sk, err, len; + + if ((sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) { + perror("ERROR: Can't create socket"); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, src); + addr.rc_channel = 0; + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("ERROR: Can't bind socket"); + close(sk); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, dst); + addr.rc_channel = channel; + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("ERROR: Can't connect to device"); + close(sk); + if (cups_class) + return CUPS_BACKEND_FAILED; + else + return CUPS_BACKEND_RETRY; + } + + fputs("STATE: -connecting-to-device\n", stderr); + + /* Ignore SIGTERM signals if printing from stdin */ + if (fd == 0) { +#ifdef HAVE_SIGSET + sigset(SIGTERM, SIG_IGN); +#elif defined(HAVE_SIGACTION) + memset(&action, 0, sizeof(action)); + sigemptyset(&action.sa_mask); + action.sa_handler = SIG_IGN; + sigaction(SIGTERM, &action, NULL); +#else + signal(SIGTERM, SIG_IGN); +#endif /* HAVE_SIGSET */ + } + + for (i = 0; i < copies; i++) { + + if (fd != 0) { + fprintf(stderr, "PAGE: 1 1\n"); + lseek(fd, 0, SEEK_SET); + } + + while ((len = read(fd, buf, sizeof(buf))) > 0) { + err = write(sk, buf, len); + if (err < 0) { + perror("ERROR: Error writing to device"); + close(sk); + return CUPS_BACKEND_FAILED; + } + } + + } + + close(sk); + + return CUPS_BACKEND_OK; +} diff --git a/profiles/deviceinfo/deviceinfo.c b/profiles/deviceinfo/deviceinfo.c new file mode 100644 index 0000000..fa94efe --- /dev/null +++ b/profiles/deviceinfo/deviceinfo.c @@ -0,0 +1,172 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Texas Instruments, Inc. + * Copyright (C) 2015 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "attrib/gattrib.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "attrib/att.h" +#include "src/log.h" + +#define PNP_ID_SIZE 7 + +static void read_pnpid_cb(bool success, uint8_t att_ecode, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct btd_device *device = user_data; + + if (!success) { + error("Error reading PNP_ID value: %s", + att_ecode2str(att_ecode)); + return; + } + + if (length < PNP_ID_SIZE) { + error("Error reading PNP_ID: Invalid pdu length received"); + return; + } + + btd_device_set_pnpid(device, value[0], get_le16(&value[1]), + get_le16(&value[3]), get_le16(&value[5])); +} + +static void handle_pnpid(struct btd_device *device, uint16_t value_handle) +{ + struct bt_gatt_client *client = btd_device_get_gatt_client(device); + + if (!bt_gatt_client_read_value(client, value_handle, + read_pnpid_cb, device, NULL)) + DBG("Failed to send request to read pnpid"); +} + +static void handle_characteristic(struct gatt_db_attribute *attr, + void *user_data) +{ + struct btd_device *device = user_data; + uint16_t value_handle; + bt_uuid_t uuid, pnpid_uuid; + + bt_string_to_uuid(&pnpid_uuid, PNPID_UUID); + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, + NULL, &uuid)) { + error("Failed to obtain characteristic data"); + return; + } + + if (bt_uuid_cmp(&pnpid_uuid, &uuid) == 0) + handle_pnpid(device, value_handle); + else { + char uuid_str[MAX_LEN_UUID_STR]; + + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + DBG("Unsupported characteristic: %s", uuid_str); + } +} + +static void foreach_deviceinfo_service(struct gatt_db_attribute *attr, + void *user_data) +{ + struct btd_device *device = user_data; + + gatt_db_service_foreach_char(attr, handle_characteristic, device); +} + +static int deviceinfo_probe(struct btd_service *service) +{ + return 0; +} + +static void deviceinfo_remove(struct btd_service *service) +{ +} + + +static int deviceinfo_accept(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gatt_db *db = btd_device_get_gatt_db(device); + char addr[18]; + bt_uuid_t deviceinfo_uuid; + + ba2str(device_get_address(device), addr); + DBG("deviceinfo profile accept (%s)", addr); + + /* Handle the device info service */ + bt_string_to_uuid(&deviceinfo_uuid, DEVICE_INFORMATION_UUID); + gatt_db_foreach_service(db, &deviceinfo_uuid, + foreach_deviceinfo_service, device); + + btd_service_connecting_complete(service, 0); + + return 0; +} + +static int deviceinfo_disconnect(struct btd_service *service) +{ + btd_service_disconnecting_complete(service, 0); + + return 0; +} + +static struct btd_profile deviceinfo_profile = { + .name = "deviceinfo", + .remote_uuid = DEVICE_INFORMATION_UUID, + .external = true, + .device_probe = deviceinfo_probe, + .device_remove = deviceinfo_remove, + .accept = deviceinfo_accept, + .disconnect = deviceinfo_disconnect, +}; + +static int deviceinfo_init(void) +{ + return btd_profile_register(&deviceinfo_profile); +} + +static void deviceinfo_exit(void) +{ + btd_profile_unregister(&deviceinfo_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(deviceinfo, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + deviceinfo_init, deviceinfo_exit) diff --git a/profiles/deviceinfo/dis.c b/profiles/deviceinfo/dis.c new file mode 100644 index 0000000..6126a77 --- /dev/null +++ b/profiles/deviceinfo/dis.c @@ -0,0 +1,353 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 +#endif + +#include +#include + +#include + +#include "src/log.h" + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" + +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" + +#include "profiles/deviceinfo/dis.h" + +#define DIS_UUID16 0x180a +#define PNP_ID_SIZE 7 + +struct bt_dis { + int ref_count; + uint16_t handle; + uint8_t source; + uint16_t vendor; + uint16_t product; + uint16_t version; + GAttrib *attrib; /* GATT connection */ + struct gatt_primary *primary; /* Primary details */ + bt_dis_notify notify; + void *notify_data; + struct queue *gatt_op; +}; + +struct characteristic { + struct gatt_char attr; /* Characteristic */ + struct bt_dis *d; /* deviceinfo where the char belongs */ +}; + +struct gatt_request { + unsigned int id; + struct bt_dis *dis; + void *user_data; +}; + +static void destroy_gatt_req(struct gatt_request *req) +{ + queue_remove(req->dis->gatt_op, req); + bt_dis_unref(req->dis); + free(req); +} + +static void dis_free(struct bt_dis *dis) +{ + bt_dis_detach(dis); + + g_free(dis->primary); + queue_destroy(dis->gatt_op, (void *) destroy_gatt_req); + g_free(dis); +} + +static void foreach_dis_char(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_dis *dis = user_data; + bt_uuid_t pnpid_uuid, uuid; + uint16_t value_handle; + + /* Ignore if there are multiple instances */ + if (dis->handle) + return; + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, NULL, &uuid)) + return; + + /* Find PNPID characteristic's value handle */ + bt_string_to_uuid(&pnpid_uuid, PNPID_UUID); + if (bt_uuid_cmp(&pnpid_uuid, &uuid) == 0) + dis->handle = value_handle; +} + +static void foreach_dis_service(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_dis *dis = user_data; + + /* Ignore if there are multiple instances */ + if (dis->handle) + return; + + gatt_db_service_foreach_char(attr, foreach_dis_char, dis); +} + +struct bt_dis *bt_dis_new(struct gatt_db *db) +{ + struct bt_dis *dis; + + dis = g_try_new0(struct bt_dis, 1); + if (!dis) + return NULL; + + dis->gatt_op = queue_new(); + + if (db) { + bt_uuid_t uuid; + + /* Handle the DIS service */ + bt_uuid16_create(&uuid, DIS_UUID16); + gatt_db_foreach_service(db, &uuid, foreach_dis_service, dis); + if (!dis->handle) { + dis_free(dis); + return NULL; + } + } + + return bt_dis_ref(dis); +} + +struct bt_dis *bt_dis_new_primary(void *primary) +{ + struct bt_dis *dis; + + dis = g_try_new0(struct bt_dis, 1); + if (!dis) + return NULL; + + dis->gatt_op = queue_new(); + + if (primary) + dis->primary = g_memdup(primary, sizeof(*dis->primary)); + + return bt_dis_ref(dis); +} + +struct bt_dis *bt_dis_ref(struct bt_dis *dis) +{ + if (!dis) + return NULL; + + __sync_fetch_and_add(&dis->ref_count, 1); + + return dis; +} + +void bt_dis_unref(struct bt_dis *dis) +{ + if (!dis) + return; + + if (__sync_sub_and_fetch(&dis->ref_count, 1)) + return; + + dis_free(dis); +} + +static struct gatt_request *create_request(struct bt_dis *dis, + void *user_data) +{ + struct gatt_request *req; + + req = new0(struct gatt_request, 1); + req->user_data = user_data; + req->dis = bt_dis_ref(dis); + + return req; +} + +static bool set_and_store_gatt_req(struct bt_dis *dis, + struct gatt_request *req, + unsigned int id) +{ + req->id = id; + return queue_push_head(dis->gatt_op, req); +} + +static void read_pnpid_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct gatt_request *req = user_data; + struct bt_dis *dis = req->user_data; + uint8_t value[PNP_ID_SIZE]; + ssize_t vlen; + + destroy_gatt_req(req); + + if (status != 0) { + error("Error reading PNP_ID value: %s", att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, len, value, sizeof(value)); + if (vlen < 0) { + error("Error reading PNP_ID: Protocol error"); + return; + } + + if (vlen < 7) { + error("Error reading PNP_ID: Invalid pdu length received"); + return; + } + + dis->source = value[0]; + dis->vendor = get_le16(&value[1]); + dis->product = get_le16(&value[3]); + dis->version = get_le16(&value[5]); + + DBG("source: 0x%02X vendor: 0x%04X product: 0x%04X version: 0x%04X", + dis->source, dis->vendor, dis->product, dis->version); + + if (dis->notify) + dis->notify(dis->source, dis->vendor, dis->product, + dis->version, dis->notify_data); +} + +static void read_char(struct bt_dis *dis, GAttrib *attrib, uint16_t handle, + GAttribResultFunc func, gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(dis, user_data); + + id = gatt_read_char(attrib, handle, func, req); + + if (set_and_store_gatt_req(dis, req, id)) + return; + + error("dis: Could not read characteristic"); + g_attrib_cancel(attrib, id); + free(req); +} + +static void discover_char(struct bt_dis *dis, GAttrib *attrib, + uint16_t start, uint16_t end, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(dis, user_data); + + id = gatt_discover_char(attrib, start, end, uuid, func, req); + + if (set_and_store_gatt_req(dis, req, id)) + return; + + error("dis: Could not send discover characteristic"); + g_attrib_cancel(attrib, id); + free(req); +} + +static void configure_deviceinfo_cb(uint8_t status, GSList *characteristics, + void *user_data) +{ + struct gatt_request *req = user_data; + struct bt_dis *d = req->user_data; + GSList *l; + + destroy_gatt_req(req); + + if (status != 0) { + error("Discover deviceinfo characteristics: %s", + att_ecode2str(status)); + return; + } + + for (l = characteristics; l; l = l->next) { + struct gatt_char *c = l->data; + + if (strcmp(c->uuid, PNPID_UUID) == 0) { + d->handle = c->value_handle; + read_char(d, d->attrib, d->handle, read_pnpid_cb, d); + break; + } + } +} + +bool bt_dis_attach(struct bt_dis *dis, void *attrib) +{ + struct gatt_primary *primary = dis->primary; + + if (dis->attrib) + return false; + + dis->attrib = g_attrib_ref(attrib); + + if (!dis->handle) + discover_char(dis, dis->attrib, primary->range.start, + primary->range.end, NULL, + configure_deviceinfo_cb, dis); + else + read_char(dis, attrib, dis->handle, read_pnpid_cb, dis); + + return true; +} + +static void cancel_gatt_req(struct gatt_request *req) +{ + if (g_attrib_cancel(req->dis->attrib, req->id)) + destroy_gatt_req(req); +} + +void bt_dis_detach(struct bt_dis *dis) +{ + if (!dis->attrib) + return; + + queue_foreach(dis->gatt_op, (void *) cancel_gatt_req, NULL); + g_attrib_unref(dis->attrib); + dis->attrib = NULL; +} + +bool bt_dis_set_notification(struct bt_dis *dis, bt_dis_notify func, + void *user_data) +{ + if (!dis) + return false; + + dis->notify = func; + dis->notify_data = user_data; + + return true; +} diff --git a/profiles/deviceinfo/dis.h b/profiles/deviceinfo/dis.h new file mode 100644 index 0000000..305ba1a --- /dev/null +++ b/profiles/deviceinfo/dis.h @@ -0,0 +1,40 @@ +/* + * + * 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 + * + */ + +struct bt_dis; + +struct bt_dis *bt_dis_new(struct gatt_db *db); +struct bt_dis *bt_dis_new_primary(void *primary); + +struct bt_dis *bt_dis_ref(struct bt_dis *dis); +void bt_dis_unref(struct bt_dis *dis); + +bool bt_dis_attach(struct bt_dis *dis, void *gatt); +void bt_dis_detach(struct bt_dis *dis); + +typedef void (*bt_dis_notify) (uint8_t source, uint16_t vendor, + uint16_t product, uint16_t version, + void *user_data); + +bool bt_dis_set_notification(struct bt_dis *dis, bt_dis_notify func, + void *user_data); diff --git a/profiles/gap/gas.c b/profiles/gap/gas.c new file mode 100644 index 0000000..dffa313 --- /dev/null +++ b/profiles/gap/gas.c @@ -0,0 +1,330 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT + * Copyright (C) 2014 Google 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. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/log.h" + +#define GAP_UUID16 0x1800 + +/* Generic Attribute/Access Service */ +struct gas { + struct btd_device *device; + struct gatt_db *db; + struct bt_gatt_client *client; + struct gatt_db_attribute *attr; +}; + +static void gas_free(struct gas *gas) +{ + gatt_db_unref(gas->db); + bt_gatt_client_unref(gas->client); + btd_device_unref(gas->device); + g_free(gas); +} + +static char *name2utf8(const uint8_t *name, uint16_t len) +{ + char utf8_name[HCI_MAX_NAME_LENGTH + 2]; + int i; + + if (g_utf8_validate((const char *) name, len, NULL)) + return g_strndup((char *) name, len); + + len = MIN(len, sizeof(utf8_name) - 1); + + memset(utf8_name, 0, sizeof(utf8_name)); + strncpy(utf8_name, (char *) name, len); + + /* Assume ASCII, and replace all non-ASCII with spaces */ + for (i = 0; utf8_name[i] != '\0'; i++) { + if (!isascii(utf8_name[i])) + utf8_name[i] = ' '; + } + + /* Remove leading and trailing whitespace characters */ + g_strstrip(utf8_name); + + return g_strdup(utf8_name); +} + +static void read_device_name_cb(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct gas *gas = user_data; + char *name; + + if (!success) { + DBG("Reading device name failed with ATT errror: %u", + att_ecode); + return; + } + + if (!length) + return; + + name = name2utf8(value, length); + + DBG("GAP Device Name: %s", name); + + btd_device_device_set_name(gas->device, name); + + g_free(name); +} + +static void handle_device_name(struct gas *gas, uint16_t value_handle) +{ + if (!bt_gatt_client_read_long_value(gas->client, value_handle, 0, + read_device_name_cb, gas, NULL)) + DBG("Failed to send request to read device name"); +} + +static void read_appearance_cb(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct gas *gas = user_data; + uint16_t appearance; + + if (!success) { + DBG("Reading appearance failed with ATT error: %u", att_ecode); + return; + } + + /* The appearance value is a 16-bit unsigned integer */ + if (length != 2) { + DBG("Malformed appearance value"); + return; + } + + appearance = get_le16(value); + + DBG("GAP Appearance: 0x%04x", appearance); + + device_set_appearance(gas->device, appearance); +} + +static void handle_appearance(struct gas *gas, uint16_t value_handle) +{ + if (!bt_gatt_client_read_value(gas->client, value_handle, + read_appearance_cb, gas, NULL)) + DBG("Failed to send request to read appearance"); +} + +static bool uuid_cmp(uint16_t u16, const bt_uuid_t *uuid) +{ + bt_uuid_t lhs; + + bt_uuid16_create(&lhs, u16); + + return bt_uuid_cmp(&lhs, uuid) == 0; +} + +static void handle_characteristic(struct gatt_db_attribute *attr, + void *user_data) +{ + struct gas *gas = user_data; + uint16_t value_handle; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, + NULL, &uuid)) { + error("Failed to obtain characteristic data"); + return; + } + + if (uuid_cmp(GATT_CHARAC_DEVICE_NAME, &uuid)) + handle_device_name(gas, value_handle); + else if (uuid_cmp(GATT_CHARAC_APPEARANCE, &uuid)) + handle_appearance(gas, value_handle); + else { + char uuid_str[MAX_LEN_UUID_STR]; + + /* TODO: Support peripheral privacy feature */ + + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + DBG("Unsupported characteristic: %s", uuid_str); + } +} + +static void handle_gap_service(struct gas *gas) +{ + gatt_db_service_foreach_char(gas->attr, handle_characteristic, gas); +} + +static int gap_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gas *gas = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("GAP profile probe (%s)", addr); + + /* Ignore, if we were probed for this device already */ + if (gas) { + error("Profile probed twice for the same device!"); + return -1; + } + + gas = g_new0(struct gas, 1); + if (!gas) + return -1; + + gas->device = btd_device_ref(device); + btd_service_set_user_data(service, gas); + + return 0; +} + +static void gap_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gas *gas; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("GAP profile remove (%s)", addr); + + gas = btd_service_get_user_data(service); + if (!gas) { + error("GAP service not handled by profile"); + return; + } + + gas_free(gas); +} + +static void foreach_gap_service(struct gatt_db_attribute *attr, void *user_data) +{ + struct gas *gas = user_data; + + if (gas->attr) { + error("More than one GAP service exists for this device"); + return; + } + + gas->attr = attr; + handle_gap_service(gas); +} + +static void gas_reset(struct gas *gas) +{ + gas->attr = NULL; + gatt_db_unref(gas->db); + gas->db = NULL; + bt_gatt_client_unref(gas->client); + gas->client = NULL; +} + +static int gap_accept(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gatt_db *db = btd_device_get_gatt_db(device); + struct bt_gatt_client *client = btd_device_get_gatt_client(device); + struct gas *gas = btd_service_get_user_data(service); + char addr[18]; + bt_uuid_t gap_uuid; + + ba2str(device_get_address(device), addr); + DBG("GAP profile accept (%s)", addr); + + if (!gas) { + error("GAP service not handled by profile"); + return -1; + } + + gas->db = gatt_db_ref(db); + gas->client = bt_gatt_client_clone(client); + + /* Handle the GAP services */ + bt_uuid16_create(&gap_uuid, GAP_UUID16); + gatt_db_foreach_service(db, &gap_uuid, foreach_gap_service, gas); + + if (!gas->attr) { + error("GAP attribute not found"); + gas_reset(gas); + return -1; + } + + btd_service_connecting_complete(service, 0); + + return 0; +} + +static int gap_disconnect(struct btd_service *service) +{ + struct gas *gas = btd_service_get_user_data(service); + + gas_reset(gas); + + btd_service_disconnecting_complete(service, 0); + + return 0; +} + +static struct btd_profile gap_profile = { + .name = "gap-profile", + .remote_uuid = GAP_UUID, + .device_probe = gap_probe, + .device_remove = gap_remove, + .accept = gap_accept, + .disconnect = gap_disconnect, +}; + +static int gap_init(void) +{ + return btd_profile_register(&gap_profile); +} + +static void gap_exit(void) +{ + btd_profile_unregister(&gap_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(gap, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + gap_init, gap_exit) diff --git a/profiles/health/hdp.c b/profiles/health/hdp.c new file mode 100644 index 0000000..5417383 --- /dev/null +++ b/profiles/health/hdp.c @@ -0,0 +1,2266 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/l2cap.h" +#include "lib/sdp.h" + +#include "gdbus/gdbus.h" + +#include "src/dbus-common.h" +#include "src/log.h" +#include "src/error.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/sdpd.h" +#include "btio/btio.h" + +#include "hdp_types.h" +#include "hdp_util.h" +#include "hdp.h" +#include "mcap.h" + +#define ECHO_TIMEOUT 1 /* second */ +#define HDP_ECHO_LEN 15 + +static GSList *applications = NULL; +static GSList *devices = NULL; +static uint8_t next_app_id = HDP_MDEP_INITIAL; + +static GSList *adapters; + +static struct hdp_device *create_health_device(struct btd_device *device); +static void free_echo_data(struct hdp_echo_data *edata); + +struct hdp_create_dc { + DBusMessage *msg; + struct hdp_application *app; + struct hdp_device *dev; + uint8_t config; + uint8_t mdep; + guint ref; + mcap_mdl_operation_cb cb; +}; + +struct hdp_tmp_dc_data { + DBusMessage *msg; + struct hdp_channel *hdp_chann; + guint ref; + mcap_mdl_operation_cb cb; +}; + +struct hdp_echo_data { + gboolean echo_done; /* Is a echo was already done */ + gpointer buf; /* echo packet sent */ + guint tid; /* echo timeout */ +}; + +static struct hdp_channel *hdp_channel_ref(struct hdp_channel *chan) +{ + if (chan == NULL) + return NULL; + + chan->ref++; + + DBG("(%p): ref=%d", chan, chan->ref); + return chan; +} + +static void free_health_channel(struct hdp_channel *chan) +{ + if (chan->mdep == HDP_MDEP_ECHO) { + free_echo_data(chan->edata); + chan->edata = NULL; + } + + mcap_mdl_unref(chan->mdl); + hdp_application_unref(chan->app); + health_device_unref(chan->dev); + g_free(chan->path); + g_free(chan); +} + +static void hdp_channel_unref(struct hdp_channel *chan) +{ + if (chan == NULL) + return; + + chan->ref --; + DBG("(%p): ref=%d", chan, chan->ref); + + if (chan->ref > 0) + return; + + free_health_channel(chan); +} + +static void free_hdp_create_dc(struct hdp_create_dc *dc_data) +{ + dbus_message_unref(dc_data->msg); + hdp_application_unref(dc_data->app); + health_device_unref(dc_data->dev); + + g_free(dc_data); +} + +static struct hdp_create_dc *hdp_create_data_ref(struct hdp_create_dc *dc_data) +{ + dc_data->ref++; + + DBG("(%p): ref=%d", dc_data, dc_data->ref); + + return dc_data; +} + +static void hdp_create_data_unref(struct hdp_create_dc *dc_data) +{ + dc_data->ref--; + + DBG("(%p): ref=%d", dc_data, dc_data->ref); + + if (dc_data->ref > 0) + return; + + free_hdp_create_dc(dc_data); +} + +static void free_hdp_conn_dc(struct hdp_tmp_dc_data *data) +{ + dbus_message_unref(data->msg); + hdp_channel_unref(data->hdp_chann); + + g_free(data); +} + +static struct hdp_tmp_dc_data *hdp_tmp_dc_data_ref(struct hdp_tmp_dc_data *data) +{ + data->ref++; + + DBG("hdp_conn_data_ref(%p): ref=%d", data, data->ref); + + return data; +} + +static void hdp_tmp_dc_data_unref(struct hdp_tmp_dc_data *data) +{ + data->ref--; + + DBG("hdp_conn_data_unref(%p): ref=%d", data, data->ref); + + if (data->ref > 0) + return; + + free_hdp_conn_dc(data); +} + +static int cmp_app_id(gconstpointer a, gconstpointer b) +{ + const struct hdp_application *app = a; + const uint8_t *id = b; + + return app->id - *id; +} + +static int cmp_adapter(gconstpointer a, gconstpointer b) +{ + const struct hdp_adapter *hdp_adapter = a; + const struct btd_adapter *adapter = b; + + if (hdp_adapter->btd_adapter == adapter) + return 0; + + return -1; +} + +static int cmp_device(gconstpointer a, gconstpointer b) +{ + const struct hdp_device *hdp_device = a; + const struct btd_device *device = b; + + if (hdp_device->dev == device) + return 0; + + return -1; +} + +static int cmp_dev_addr(gconstpointer a, gconstpointer dst) +{ + const struct hdp_device *device = a; + + return bacmp(device_get_address(device->dev), dst); +} + +static int cmp_dev_mcl(gconstpointer a, gconstpointer mcl) +{ + const struct hdp_device *device = a; + + if (mcl == device->mcl) + return 0; + return -1; +} + +static int cmp_chan_mdlid(gconstpointer a, gconstpointer b) +{ + const struct hdp_channel *chan = a; + const uint16_t *mdlid = b; + + return chan->mdlid - *mdlid; +} + +static int cmp_chan_path(gconstpointer a, gconstpointer b) +{ + const struct hdp_channel *chan = a; + const char *path = b; + + return g_ascii_strcasecmp(chan->path, path); +} + +static int cmp_chan_mdl(gconstpointer a, gconstpointer mdl) +{ + const struct hdp_channel *chan = a; + + if (chan->mdl == mdl) + return 0; + return -1; +} + +static uint8_t get_app_id(void) +{ + uint8_t id = next_app_id; + + do { + GSList *l = g_slist_find_custom(applications, &id, cmp_app_id); + + if (l == NULL) { + next_app_id = (id % HDP_MDEP_FINAL) + 1; + return id; + } else + id = (id % HDP_MDEP_FINAL) + 1; + } while (id != next_app_id); + + /* No more ids available */ + return 0; +} + +static int cmp_app(gconstpointer a, gconstpointer b) +{ + const struct hdp_application *app = a; + + return g_strcmp0(app->path, b); +} + +static gboolean set_app_path(struct hdp_application *app) +{ + app->id = get_app_id(); + if (app->id == 0) + return FALSE; + app->path = g_strdup_printf(MANAGER_PATH "/health_app_%d", app->id); + + return TRUE; +}; + +static void device_unref_mcl(struct hdp_device *hdp_device) +{ + if (hdp_device->mcl == NULL) + return; + + mcap_close_mcl(hdp_device->mcl, FALSE); + mcap_mcl_unref(hdp_device->mcl); + hdp_device->mcl = NULL; + hdp_device->mcl_conn = FALSE; +} + +static void free_health_device(struct hdp_device *device) +{ + if (device->dev != NULL) { + btd_device_unref(device->dev); + device->dev = NULL; + } + + device_unref_mcl(device); + + g_free(device); +} + +static void update_adapter_cb(void *data, void *user_data); + +static void remove_application(struct hdp_application *app) +{ + DBG("Application %s deleted", app->path); + hdp_application_unref(app); + + g_slist_foreach(adapters, update_adapter_cb, NULL); +} + +static void client_disconnected(DBusConnection *conn, void *user_data) +{ + struct hdp_application *app = user_data; + + DBG("Client disconnected from the bus, deleting hdp application"); + applications = g_slist_remove(applications, app); + + app->dbus_watcher = 0; /* Watcher shouldn't be freed in this case */ + remove_application(app); +} + +static DBusMessage *manager_create_application(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct hdp_application *app; + const char *name; + DBusMessageIter iter; + GError *err = NULL; + + dbus_message_iter_init(msg, &iter); + app = hdp_get_app_config(&iter, &err); + if (err != NULL) { + g_error_free(err); + return btd_error_invalid_args(msg); + } + + name = dbus_message_get_sender(msg); + if (name == NULL) { + hdp_application_unref(app); + return g_dbus_create_error(msg, + ERROR_INTERFACE ".HealthError", + "Can't get sender name"); + } + + if (!set_app_path(app)) { + hdp_application_unref(app); + return g_dbus_create_error(msg, + ERROR_INTERFACE ".HealthError", + "Can't get a valid id for the application"); + } + + app->oname = g_strdup(name); + + applications = g_slist_prepend(applications, app); + + app->dbus_watcher = + g_dbus_add_disconnect_watch(btd_get_dbus_connection(), + name, client_disconnected, app, NULL); + g_slist_foreach(adapters, update_adapter_cb, NULL); + + DBG("Health application created with id %s", app->path); + + return g_dbus_create_reply(msg, DBUS_TYPE_OBJECT_PATH, &app->path, + DBUS_TYPE_INVALID); +} + +static DBusMessage *manager_destroy_application(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *path; + struct hdp_application *app; + GSList *l; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + l = g_slist_find_custom(applications, path, cmp_app); + + if (l == NULL) + return g_dbus_create_error(msg, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call, " + "no such application"); + + app = l->data; + applications = g_slist_remove(applications, app); + + remove_application(app); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static void application_unref(void *data, void *user_data) +{ + hdp_application_unref(data); +} + +static void manager_path_unregister(gpointer data) +{ + g_slist_foreach(applications, application_unref, NULL); + + g_slist_free(applications); + applications = NULL; + + g_slist_foreach(adapters, update_adapter_cb, NULL); +} + +static const GDBusMethodTable health_manager_methods[] = { + { GDBUS_METHOD("CreateApplication", + GDBUS_ARGS({ "config", "a{sv}" }), + GDBUS_ARGS({ "application", "o" }), + manager_create_application) }, + { GDBUS_METHOD("DestroyApplication", + GDBUS_ARGS({ "application", "o" }), NULL, + manager_destroy_application) }, + { } +}; + +static gboolean channel_property_get_device(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct hdp_channel *chan = data; + const char *path = device_get_path(chan->dev->dev); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + return TRUE; +} + +static gboolean channel_property_get_application( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct hdp_channel *chan = data; + const char *path = chan->app->path; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + return TRUE; +} + +static gboolean channel_property_get_type(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct hdp_channel *chan = data; + const char *type; + + if (chan->config == HDP_RELIABLE_DC) + type = "reliable"; + else + type = "streaming"; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &type); + + return TRUE; +} + +static void hdp_tmp_dc_data_destroy(gpointer data) +{ + struct hdp_tmp_dc_data *hdp_conn = data; + + hdp_tmp_dc_data_unref(hdp_conn); +} + +static void abort_mdl_cb(GError *err, gpointer data) +{ + if (err != NULL) + error("Aborting error: %s", err->message); +} + +static void hdp_mdl_reconn_cb(struct mcap_mdl *mdl, GError *err, gpointer data) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct hdp_tmp_dc_data *dc_data = data; + DBusMessage *reply; + int fd; + + if (err != NULL) { + struct hdp_channel *chan = dc_data->hdp_chann; + GError *gerr = NULL; + + error("%s", err->message); + reply = g_dbus_create_error(dc_data->msg, + ERROR_INTERFACE ".HealthError", + "Cannot reconnect: %s", err->message); + g_dbus_send_message(conn, reply); + + /* Send abort request because remote side */ + /* is now in PENDING state */ + if (!mcap_mdl_abort(chan->mdl, abort_mdl_cb, NULL, NULL, + &gerr)) { + error("%s", gerr->message); + g_error_free(gerr); + } + return; + } + + fd = mcap_mdl_get_fd(dc_data->hdp_chann->mdl); + if (fd < 0) { + reply = g_dbus_create_error(dc_data->msg, + ERROR_INTERFACE ".HealthError", + "Cannot get file descriptor"); + g_dbus_send_message(conn, reply); + return; + } + + reply = g_dbus_create_reply(dc_data->msg, DBUS_TYPE_UNIX_FD, + &fd, DBUS_TYPE_INVALID); + g_dbus_send_message(conn, reply); + + g_dbus_emit_signal(conn, device_get_path(dc_data->hdp_chann->dev->dev), + HEALTH_DEVICE, "ChannelConnected", + DBUS_TYPE_OBJECT_PATH, &dc_data->hdp_chann->path, + DBUS_TYPE_INVALID); +} + +static void hdp_get_dcpsm_cb(uint16_t dcpsm, gpointer user_data, GError *err) +{ + struct hdp_tmp_dc_data *hdp_conn = user_data; + struct hdp_channel *hdp_chann = hdp_conn->hdp_chann; + GError *gerr = NULL; + uint8_t mode; + + if (err != NULL) { + hdp_conn->cb(hdp_chann->mdl, err, hdp_conn); + return; + } + + if (hdp_chann->config == HDP_RELIABLE_DC) + mode = L2CAP_MODE_ERTM; + else + mode = L2CAP_MODE_STREAMING; + + if (mcap_connect_mdl(hdp_chann->mdl, mode, dcpsm, hdp_conn->cb, + hdp_tmp_dc_data_ref(hdp_conn), + hdp_tmp_dc_data_destroy, &gerr)) + return; + + hdp_conn->cb(hdp_chann->mdl, err, hdp_conn); + g_error_free(gerr); + hdp_tmp_dc_data_unref(hdp_conn); +} + +static void device_reconnect_mdl_cb(struct mcap_mdl *mdl, GError *err, + gpointer data) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct hdp_tmp_dc_data *dc_data = data; + GError *gerr = NULL; + DBusMessage *reply; + + if (err != NULL) { + reply = g_dbus_create_error(dc_data->msg, + ERROR_INTERFACE ".HealthError", + "Cannot reconnect: %s", err->message); + g_dbus_send_message(conn, reply); + return; + } + + dc_data->cb = hdp_mdl_reconn_cb; + + if (hdp_get_dcpsm(dc_data->hdp_chann->dev, hdp_get_dcpsm_cb, + hdp_tmp_dc_data_ref(dc_data), + hdp_tmp_dc_data_destroy, &gerr)) + return; + + error("%s", gerr->message); + + reply = g_dbus_create_error(dc_data->msg, + ERROR_INTERFACE ".HealthError", + "Cannot reconnect: %s", gerr->message); + g_dbus_send_message(conn, reply); + hdp_tmp_dc_data_unref(dc_data); + g_error_free(gerr); + + /* Send abort request because remote side is now in PENDING state */ + if (!mcap_mdl_abort(mdl, abort_mdl_cb, NULL, NULL, &gerr)) { + error("%s", gerr->message); + g_error_free(gerr); + } +} + +static DBusMessage *channel_acquire_continue(struct hdp_tmp_dc_data *data, + GError *err) +{ + DBusMessage *reply; + GError *gerr = NULL; + int fd; + + if (err != NULL) { + return g_dbus_create_error(data->msg, + ERROR_INTERFACE ".HealthError", + "%s", err->message); + } + + fd = mcap_mdl_get_fd(data->hdp_chann->mdl); + if (fd >= 0) + return g_dbus_create_reply(data->msg, DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_INVALID); + + hdp_tmp_dc_data_ref(data); + if (mcap_reconnect_mdl(data->hdp_chann->mdl, device_reconnect_mdl_cb, + data, hdp_tmp_dc_data_destroy, &gerr)) + return NULL; + + reply = g_dbus_create_error(data->msg, ERROR_INTERFACE ".HealthError", + "Cannot reconnect: %s", gerr->message); + g_error_free(gerr); + hdp_tmp_dc_data_unref(data); + + return reply; +} + +static void channel_acquire_cb(gpointer data, GError *err) +{ + DBusMessage *reply; + + reply = channel_acquire_continue(data, err); + + if (reply != NULL) + g_dbus_send_message(btd_get_dbus_connection(), reply); +} + +static DBusMessage *channel_acquire(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct hdp_channel *chan = user_data; + struct hdp_tmp_dc_data *dc_data; + GError *gerr = NULL; + DBusMessage *reply; + + dc_data = g_new0(struct hdp_tmp_dc_data, 1); + dc_data->msg = dbus_message_ref(msg); + dc_data->hdp_chann = hdp_channel_ref(chan); + + if (chan->dev->mcl_conn) { + reply = channel_acquire_continue(hdp_tmp_dc_data_ref(dc_data), + NULL); + hdp_tmp_dc_data_unref(dc_data); + return reply; + } + + if (hdp_establish_mcl(chan->dev, channel_acquire_cb, + hdp_tmp_dc_data_ref(dc_data), + hdp_tmp_dc_data_destroy, &gerr)) + return NULL; + + reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError", + "%s", gerr->message); + hdp_tmp_dc_data_unref(dc_data); + g_error_free(gerr); + + return reply; +} + +static void close_mdl(struct hdp_channel *hdp_chann) +{ + int fd; + + fd = mcap_mdl_get_fd(hdp_chann->mdl); + if (fd < 0) + return; + + close(fd); +} + +static DBusMessage *channel_release(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct hdp_channel *hdp_chann = user_data; + + close_mdl(hdp_chann); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static void free_echo_data(struct hdp_echo_data *edata) +{ + if (edata == NULL) + return; + + if (edata->tid > 0) + g_source_remove(edata->tid); + + if (edata->buf != NULL) + g_free(edata->buf); + + + g_free(edata); +} + +static void health_channel_destroy(void *data) +{ + struct hdp_channel *hdp_chan = data; + struct hdp_device *dev = hdp_chan->dev; + + DBG("Destroy Health Channel %s", hdp_chan->path); + if (g_slist_find(dev->channels, hdp_chan) == NULL) + goto end; + + dev->channels = g_slist_remove(dev->channels, hdp_chan); + + if (hdp_chan->mdep != HDP_MDEP_ECHO) + g_dbus_emit_signal(btd_get_dbus_connection(), + device_get_path(dev->dev), + HEALTH_DEVICE, "ChannelDeleted", + DBUS_TYPE_OBJECT_PATH, &hdp_chan->path, + DBUS_TYPE_INVALID); + + if (hdp_chan == dev->fr) { + hdp_channel_unref(dev->fr); + dev->fr = NULL; + } + +end: + hdp_channel_unref(hdp_chan); +} + +static const GDBusMethodTable health_channels_methods[] = { + { GDBUS_ASYNC_METHOD("Acquire", + NULL, GDBUS_ARGS({ "fd", "h" }), + channel_acquire) }, + { GDBUS_METHOD("Release", NULL, NULL, channel_release) }, + { } +}; + +static const GDBusPropertyTable health_channels_properties[] = { + { "Device", "o", channel_property_get_device }, + { "Application", "o", channel_property_get_application }, + { "Type", "s", channel_property_get_type }, + { } +}; + +static struct hdp_channel *create_channel(struct hdp_device *dev, + uint8_t config, + struct mcap_mdl *mdl, + uint16_t mdlid, + struct hdp_application *app, + GError **err) +{ + struct hdp_channel *hdp_chann; + + if (dev == NULL) { + g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR, + "HDP device uninitialized"); + return NULL; + } + + hdp_chann = g_new0(struct hdp_channel, 1); + hdp_chann->config = config; + hdp_chann->dev = health_device_ref(dev); + hdp_chann->mdlid = mdlid; + + if (mdl != NULL) + hdp_chann->mdl = mcap_mdl_ref(mdl); + + if (app != NULL) { + hdp_chann->mdep = app->id; + hdp_chann->app = hdp_application_ref(app); + } else + hdp_chann->edata = g_new0(struct hdp_echo_data, 1); + + hdp_chann->path = g_strdup_printf("%s/chan%d", + device_get_path(hdp_chann->dev->dev), + hdp_chann->mdlid); + + dev->channels = g_slist_append(dev->channels, + hdp_channel_ref(hdp_chann)); + + if (hdp_chann->mdep == HDP_MDEP_ECHO) + return hdp_channel_ref(hdp_chann); + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + hdp_chann->path, HEALTH_CHANNEL, + health_channels_methods, NULL, + health_channels_properties, hdp_chann, + health_channel_destroy)) { + g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR, + "Can't register the channel interface"); + health_channel_destroy(hdp_chann); + return NULL; + } + + return hdp_channel_ref(hdp_chann); +} + +static void remove_channels(struct hdp_device *dev) +{ + struct hdp_channel *chan; + char *path; + + while (dev->channels != NULL) { + chan = dev->channels->data; + + path = g_strdup(chan->path); + if (!g_dbus_unregister_interface(btd_get_dbus_connection(), + path, HEALTH_CHANNEL)) + health_channel_destroy(chan); + g_free(path); + } +} + +static void close_device_con(struct hdp_device *dev, gboolean cache) +{ + if (dev->mcl == NULL) + return; + + mcap_close_mcl(dev->mcl, cache); + dev->mcl_conn = FALSE; + + if (cache) + return; + + device_unref_mcl(dev); + remove_channels(dev); + + if (!dev->sdp_present) { + const char *path; + + path = device_get_path(dev->dev); + g_dbus_unregister_interface(btd_get_dbus_connection(), + path, HEALTH_DEVICE); + } +} + +static int send_echo_data(int sock, const void *buf, uint32_t size) +{ + const uint8_t *buf_b = buf; + uint32_t sent = 0; + + while (sent < size) { + int n = write(sock, buf_b + sent, size - sent); + if (n < 0) + return -1; + sent += n; + } + + return 0; +} + +static gboolean serve_echo(GIOChannel *io_chan, GIOCondition cond, + gpointer data) +{ + struct hdp_channel *chan = data; + uint8_t buf[MCAP_DC_MTU]; + int fd, len; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + hdp_channel_unref(chan); + return FALSE; + } + + if (chan->edata->echo_done) + goto fail; + + chan->edata->echo_done = TRUE; + + fd = g_io_channel_unix_get_fd(io_chan); + + len = read(fd, buf, sizeof(buf)); + if (len < 0) + goto fail; + + if (send_echo_data(fd, buf, len) >= 0) + return TRUE; + +fail: + close_device_con(chan->dev, FALSE); + hdp_channel_unref(chan); + return FALSE; +} + +static gboolean check_channel_conf(struct hdp_channel *chan) +{ + GError *err = NULL; + GIOChannel *io; + uint8_t mode; + uint16_t imtu, omtu; + int fd; + + fd = mcap_mdl_get_fd(chan->mdl); + if (fd < 0) + return FALSE; + io = g_io_channel_unix_new(fd); + + if (!bt_io_get(io, &err, + BT_IO_OPT_MODE, &mode, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_INVALID)) { + error("Error: %s", err->message); + g_io_channel_unref(io); + g_error_free(err); + return FALSE; + } + + g_io_channel_unref(io); + + switch (chan->config) { + case HDP_RELIABLE_DC: + if (mode != L2CAP_MODE_ERTM) + return FALSE; + break; + case HDP_STREAMING_DC: + if (mode != L2CAP_MODE_STREAMING) + return FALSE; + break; + default: + error("Error: Connected with unknown configuration"); + return FALSE; + } + + DBG("MDL imtu %d omtu %d Channel imtu %d omtu %d", imtu, omtu, + chan->imtu, chan->omtu); + + if (chan->imtu == 0) + chan->imtu = imtu; + if (chan->omtu == 0) + chan->omtu = omtu; + + if (chan->imtu != imtu || chan->omtu != omtu) + return FALSE; + + return TRUE; +} + +static void hdp_mcap_mdl_connected_cb(struct mcap_mdl *mdl, void *data) +{ + struct hdp_device *dev = data; + struct hdp_channel *chan; + + DBG(""); + + if (dev->ndc == NULL) + return; + + chan = dev->ndc; + if (chan->mdl == NULL) + chan->mdl = mcap_mdl_ref(mdl); + + if (g_slist_find(dev->channels, chan) == NULL) + dev->channels = g_slist_prepend(dev->channels, + hdp_channel_ref(chan)); + + if (!check_channel_conf(chan)) { + close_mdl(chan); + goto end; + } + + if (chan->mdep == HDP_MDEP_ECHO) { + GIOChannel *io; + int fd; + + fd = mcap_mdl_get_fd(chan->mdl); + if (fd < 0) + goto end; + + chan->edata->echo_done = FALSE; + io = g_io_channel_unix_new(fd); + g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL | G_IO_IN, + serve_echo, hdp_channel_ref(chan)); + g_io_channel_unref(io); + goto end; + } + + g_dbus_emit_signal(btd_get_dbus_connection(), device_get_path(dev->dev), + HEALTH_DEVICE, "ChannelConnected", + DBUS_TYPE_OBJECT_PATH, &chan->path, + DBUS_TYPE_INVALID); + + if (dev->fr != NULL) + goto end; + + dev->fr = hdp_channel_ref(chan); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + device_get_path(dev->dev), HEALTH_DEVICE, + "MainChannel"); + +end: + hdp_channel_unref(dev->ndc); + dev->ndc = NULL; +} + +static void hdp_mcap_mdl_closed_cb(struct mcap_mdl *mdl, void *data) +{ + /* struct hdp_device *dev = data; */ + + DBG(""); + + /* Nothing to do */ +} + +static void hdp_mcap_mdl_deleted_cb(struct mcap_mdl *mdl, void *data) +{ + struct hdp_device *dev = data; + struct hdp_channel *chan; + char *path; + GSList *l; + + DBG(""); + + l = g_slist_find_custom(dev->channels, mdl, cmp_chan_mdl); + if (l == NULL) + return; + + chan = l->data; + + path = g_strdup(chan->path); + if (!g_dbus_unregister_interface(btd_get_dbus_connection(), + path, HEALTH_CHANNEL)) + health_channel_destroy(chan); + g_free(path); +} + +static void hdp_mcap_mdl_aborted_cb(struct mcap_mdl *mdl, void *data) +{ + struct hdp_device *dev = data; + + DBG(""); + + if (dev->ndc == NULL) + return; + + dev->ndc->mdl = mcap_mdl_ref(mdl); + + if (g_slist_find(dev->channels, dev->ndc) == NULL) + dev->channels = g_slist_prepend(dev->channels, + hdp_channel_ref(dev->ndc)); + + if (dev->ndc->mdep != HDP_MDEP_ECHO) + g_dbus_emit_signal(btd_get_dbus_connection(), + device_get_path(dev->dev), + HEALTH_DEVICE, "ChannelConnected", + DBUS_TYPE_OBJECT_PATH, &dev->ndc->path, + DBUS_TYPE_INVALID); + + hdp_channel_unref(dev->ndc); + dev->ndc = NULL; +} + +static uint8_t hdp2l2cap_mode(uint8_t hdp_mode) +{ + return hdp_mode == HDP_STREAMING_DC ? L2CAP_MODE_STREAMING : + L2CAP_MODE_ERTM; +} + +static uint8_t hdp_mcap_mdl_conn_req_cb(struct mcap_mcl *mcl, uint8_t mdepid, + uint16_t mdlid, uint8_t *conf, void *data) +{ + struct hdp_device *dev = data; + struct hdp_application *app; + GError *err = NULL; + GSList *l; + + DBG("Data channel request"); + + if (mdepid == HDP_MDEP_ECHO) { + switch (*conf) { + case HDP_NO_PREFERENCE_DC: + *conf = HDP_RELIABLE_DC; + break; + case HDP_RELIABLE_DC: + break; + case HDP_STREAMING_DC: + return MCAP_CONFIGURATION_REJECTED; + default: + /* Special case defined in HDP spec 3.4. When an invalid + * configuration is received we shall close the MCL when + * we are still processing the callback. */ + close_device_con(dev, FALSE); + return MCAP_CONFIGURATION_REJECTED; /* not processed */ + } + + if (!mcap_set_data_chan_mode(dev->hdp_adapter->mi, + L2CAP_MODE_ERTM, &err)) { + error("Error: %s", err->message); + g_error_free(err); + return MCAP_MDL_BUSY; + } + + dev->ndc = create_channel(dev, *conf, NULL, mdlid, NULL, NULL); + if (dev->ndc == NULL) + return MCAP_MDL_BUSY; + + return MCAP_SUCCESS; + } + + l = g_slist_find_custom(applications, &mdepid, cmp_app_id); + if (l == NULL) + return MCAP_INVALID_MDEP; + + app = l->data; + + /* Check if is the first dc if so, + * only reliable configuration is allowed */ + switch (*conf) { + case HDP_NO_PREFERENCE_DC: + if (app->role == HDP_SINK) + return MCAP_CONFIGURATION_REJECTED; + else if (dev->fr && app->chan_type_set) + *conf = app->chan_type; + else + *conf = HDP_RELIABLE_DC; + break; + case HDP_STREAMING_DC: + if (!dev->fr || app->role == HDP_SOURCE) + return MCAP_CONFIGURATION_REJECTED; + break; + case HDP_RELIABLE_DC: + if (app->role == HDP_SOURCE) + return MCAP_CONFIGURATION_REJECTED; + break; + default: + /* Special case defined in HDP spec 3.4. When an invalid + * configuration is received we shall close the MCL when + * we are still processing the callback. */ + close_device_con(dev, FALSE); + return MCAP_CONFIGURATION_REJECTED; /* not processed */ + } + + l = g_slist_find_custom(dev->channels, &mdlid, cmp_chan_mdlid); + if (l != NULL) { + struct hdp_channel *chan = l->data; + char *path; + + path = g_strdup(chan->path); + g_dbus_unregister_interface(btd_get_dbus_connection(), + path, HEALTH_CHANNEL); + g_free(path); + } + + if (!mcap_set_data_chan_mode(dev->hdp_adapter->mi, + hdp2l2cap_mode(*conf), &err)) { + error("Error: %s", err->message); + g_error_free(err); + return MCAP_MDL_BUSY; + } + + dev->ndc = create_channel(dev, *conf, NULL, mdlid, app, NULL); + if (dev->ndc == NULL) + return MCAP_MDL_BUSY; + + return MCAP_SUCCESS; +} + +static uint8_t hdp_mcap_mdl_reconn_req_cb(struct mcap_mdl *mdl, void *data) +{ + struct hdp_device *dev = data; + struct hdp_channel *chan; + GError *err = NULL; + GSList *l; + + l = g_slist_find_custom(dev->channels, mdl, cmp_chan_mdl); + if (l == NULL) + return MCAP_INVALID_MDL; + + chan = l->data; + + if (dev->fr == NULL && chan->config != HDP_RELIABLE_DC && + chan->mdep != HDP_MDEP_ECHO) + return MCAP_UNSPECIFIED_ERROR; + + if (!mcap_set_data_chan_mode(dev->hdp_adapter->mi, + hdp2l2cap_mode(chan->config), &err)) { + error("Error: %s", err->message); + g_error_free(err); + return MCAP_MDL_BUSY; + } + + dev->ndc = hdp_channel_ref(chan); + + return MCAP_SUCCESS; +} + +gboolean hdp_set_mcl_cb(struct hdp_device *device, GError **err) +{ + gboolean ret; + + if (device->mcl == NULL) + return FALSE; + + ret = mcap_mcl_set_cb(device->mcl, device, err, + MCAP_MDL_CB_CONNECTED, hdp_mcap_mdl_connected_cb, + MCAP_MDL_CB_CLOSED, hdp_mcap_mdl_closed_cb, + MCAP_MDL_CB_DELETED, hdp_mcap_mdl_deleted_cb, + MCAP_MDL_CB_ABORTED, hdp_mcap_mdl_aborted_cb, + MCAP_MDL_CB_REMOTE_CONN_REQ, hdp_mcap_mdl_conn_req_cb, + MCAP_MDL_CB_REMOTE_RECONN_REQ, hdp_mcap_mdl_reconn_req_cb, + MCAP_MDL_CB_INVALID); + if (ret) + return TRUE; + + error("Can't set mcl callbacks, closing mcl"); + close_device_con(device, TRUE); + + return FALSE; +} + +static void mcl_connected(struct mcap_mcl *mcl, gpointer data) +{ + struct hdp_device *hdp_device; + bdaddr_t addr; + GSList *l; + + mcap_mcl_get_addr(mcl, &addr); + l = g_slist_find_custom(devices, &addr, cmp_dev_addr); + if (l == NULL) { + struct hdp_adapter *hdp_adapter = data; + struct btd_device *device; + + device = btd_adapter_get_device(hdp_adapter->btd_adapter, + &addr, BDADDR_BREDR); + if (!device) + return; + hdp_device = create_health_device(device); + if (!hdp_device) + return; + devices = g_slist_append(devices, hdp_device); + } else + hdp_device = l->data; + + hdp_device->mcl = mcap_mcl_ref(mcl); + hdp_device->mcl_conn = TRUE; + + DBG("New mcl connected from %s", device_get_path(hdp_device->dev)); + + hdp_set_mcl_cb(hdp_device, NULL); +} + +static void mcl_reconnected(struct mcap_mcl *mcl, gpointer data) +{ + struct hdp_device *hdp_device; + GSList *l; + + l = g_slist_find_custom(devices, mcl, cmp_dev_mcl); + if (l == NULL) + return; + + hdp_device = l->data; + hdp_device->mcl_conn = TRUE; + + DBG("MCL reconnected %s", device_get_path(hdp_device->dev)); + + hdp_set_mcl_cb(hdp_device, NULL); +} + +static void mcl_disconnected(struct mcap_mcl *mcl, gpointer data) +{ + struct hdp_device *hdp_device; + GSList *l; + + l = g_slist_find_custom(devices, mcl, cmp_dev_mcl); + if (l == NULL) + return; + + hdp_device = l->data; + hdp_device->mcl_conn = FALSE; + + DBG("Mcl disconnected %s", device_get_path(hdp_device->dev)); +} + +static void mcl_uncached(struct mcap_mcl *mcl, gpointer data) +{ + struct hdp_device *hdp_device; + const char *path; + GSList *l; + + l = g_slist_find_custom(devices, mcl, cmp_dev_mcl); + if (l == NULL) + return; + + hdp_device = l->data; + device_unref_mcl(hdp_device); + + if (hdp_device->sdp_present) + return; + + /* Because remote device hasn't announced an HDP record */ + /* the Bluetooth daemon won't notify when the device shall */ + /* be removed. Then we have to remove the HealthDevice */ + /* interface manually */ + path = device_get_path(hdp_device->dev); + g_dbus_unregister_interface(btd_get_dbus_connection(), + path, HEALTH_DEVICE); + DBG("Mcl uncached %s", path); +} + +static void check_devices_mcl(void) +{ + struct hdp_device *dev; + GSList *l, *to_delete = NULL; + + for (l = devices; l; l = l->next) { + dev = l->data; + device_unref_mcl(dev); + + if (!dev->sdp_present) + to_delete = g_slist_append(to_delete, dev); + else + remove_channels(dev); + } + + for (l = to_delete; l; l = l->next) { + const char *path; + + path = device_get_path(dev->dev); + g_dbus_unregister_interface(btd_get_dbus_connection(), + path, HEALTH_DEVICE); + } + + g_slist_free(to_delete); +} + +static void release_adapter_instance(struct hdp_adapter *hdp_adapter) +{ + if (hdp_adapter->mi == NULL) + return; + + check_devices_mcl(); + mcap_release_instance(hdp_adapter->mi); + mcap_instance_unref(hdp_adapter->mi); + hdp_adapter->mi = NULL; +} + +static gboolean update_adapter(struct hdp_adapter *hdp_adapter) +{ + GError *err = NULL; + const bdaddr_t *src; + + if (applications == NULL) { + release_adapter_instance(hdp_adapter); + goto update; + } + + if (hdp_adapter->mi != NULL) + goto update; + + src = btd_adapter_get_address(hdp_adapter->btd_adapter); + + hdp_adapter->mi = mcap_create_instance(src, + BT_IO_SEC_MEDIUM, 0, 0, + mcl_connected, mcl_reconnected, + mcl_disconnected, mcl_uncached, + NULL, /* CSP is not used by now */ + hdp_adapter, &err); + if (hdp_adapter->mi == NULL) { + error("Error creating the MCAP instance: %s", err->message); + g_error_free(err); + return FALSE; + } + + hdp_adapter->ccpsm = mcap_get_ctrl_psm(hdp_adapter->mi, &err); + if (err != NULL) { + error("Error getting MCAP control PSM: %s", err->message); + goto fail; + } + + hdp_adapter->dcpsm = mcap_get_data_psm(hdp_adapter->mi, &err); + if (err != NULL) { + error("Error getting MCAP data PSM: %s", err->message); + goto fail; + } + +update: + if (hdp_update_sdp_record(hdp_adapter, applications)) + return TRUE; + error("Error updating the SDP record"); + +fail: + release_adapter_instance(hdp_adapter); + if (err != NULL) + g_error_free(err); + + return FALSE; +} + +static void update_adapter_cb(void *data, void *user_data) +{ + update_adapter(data); +} + +int hdp_adapter_register(struct btd_adapter *adapter) +{ + struct hdp_adapter *hdp_adapter; + + hdp_adapter = g_new0(struct hdp_adapter, 1); + hdp_adapter->btd_adapter = btd_adapter_ref(adapter); + + if(!update_adapter(hdp_adapter)) + goto fail; + + adapters = g_slist_append(adapters, hdp_adapter); + + return 0; + +fail: + btd_adapter_unref(hdp_adapter->btd_adapter); + g_free(hdp_adapter); + return -1; +} + +void hdp_adapter_unregister(struct btd_adapter *adapter) +{ + struct hdp_adapter *hdp_adapter; + GSList *l; + + l = g_slist_find_custom(adapters, adapter, cmp_adapter); + + if (l == NULL) + return; + + hdp_adapter = l->data; + adapters = g_slist_remove(adapters, hdp_adapter); + if (hdp_adapter->sdp_handler > 0) + adapter_service_remove(adapter, hdp_adapter->sdp_handler); + release_adapter_instance(hdp_adapter); + btd_adapter_unref(hdp_adapter->btd_adapter); + g_free(hdp_adapter); +} + +static void delete_echo_channel_cb(GError *err, gpointer chan) +{ + if (err != NULL && err->code != MCAP_INVALID_MDL) { + /* TODO: Decide if more action is required here */ + error("Error deleting echo channel: %s", err->message); + return; + } + + health_channel_destroy(chan); +} + +static void delete_echo_channel(struct hdp_channel *chan) +{ + GError *err = NULL; + + if (!chan->dev->mcl_conn) { + error("Echo channel cannot be deleted: mcl closed"); + return; + } + + if (mcap_delete_mdl(chan->mdl, delete_echo_channel_cb, + hdp_channel_ref(chan), + (GDestroyNotify) hdp_channel_unref, &err)) + return; + + hdp_channel_unref(chan); + error("Error deleting the echo channel: %s", err->message); + g_error_free(err); + + /* TODO: Decide if more action is required here */ +} + +static void abort_echo_channel_cb(GError *err, gpointer data) +{ + struct hdp_channel *chan = data; + + if (err != NULL && err->code != MCAP_ERROR_INVALID_OPERATION) { + error("Aborting error: %s", err->message); + if (err->code == MCAP_INVALID_MDL) { + /* MDL is removed from MCAP so we can */ + /* free the data channel without sending */ + /* a MD_DELETE_MDL_REQ */ + /* TODO review the above comment */ + /* hdp_channel_unref(chan); */ + } + return; + } + + delete_echo_channel(chan); +} + +static void destroy_create_dc_data(gpointer data) +{ + struct hdp_create_dc *dc_data = data; + + hdp_create_data_unref(dc_data); +} + +static void *generate_echo_packet(void) +{ + uint8_t *buf; + int i; + + buf = g_malloc(HDP_ECHO_LEN); + srand(time(NULL)); + + for(i = 0; i < HDP_ECHO_LEN; i++) + buf[i] = rand() % UINT8_MAX; + + return buf; +} + +static gboolean check_echo(GIOChannel *io_chan, GIOCondition cond, + gpointer data) +{ + struct hdp_tmp_dc_data *hdp_conn = data; + struct hdp_echo_data *edata = hdp_conn->hdp_chann->edata; + struct hdp_channel *chan = hdp_conn->hdp_chann; + uint8_t buf[MCAP_DC_MTU]; + DBusMessage *reply; + gboolean value; + int fd, len; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + value = FALSE; + goto end; + } + + fd = g_io_channel_unix_get_fd(io_chan); + + len = read(fd, buf, sizeof(buf)); + if (len != HDP_ECHO_LEN) { + value = FALSE; + goto end; + } + + value = (memcmp(buf, edata->buf, len) == 0); + +end: + reply = g_dbus_create_reply(hdp_conn->msg, DBUS_TYPE_BOOLEAN, &value, + DBUS_TYPE_INVALID); + g_dbus_send_message(btd_get_dbus_connection(), reply); + g_source_remove(edata->tid); + edata->tid = 0; + g_free(edata->buf); + edata->buf = NULL; + + if (!value) + close_device_con(chan->dev, FALSE); + else + delete_echo_channel(chan); + hdp_tmp_dc_data_unref(hdp_conn); + + return FALSE; +} + +static gboolean echo_timeout(gpointer data) +{ + struct hdp_channel *chan = data; + GIOChannel *io; + int fd; + + error("Error: Echo request timeout"); + chan->edata->tid = 0; + + fd = mcap_mdl_get_fd(chan->mdl); + if (fd < 0) + return FALSE; + + io = g_io_channel_unix_new(fd); + g_io_channel_shutdown(io, TRUE, NULL); + + return FALSE; +} + +static void hdp_echo_connect_cb(struct mcap_mdl *mdl, GError *err, + gpointer data) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct hdp_tmp_dc_data *hdp_conn = data; + struct hdp_echo_data *edata; + GError *gerr = NULL; + DBusMessage *reply; + GIOChannel *io; + int fd; + + if (err != NULL) { + reply = g_dbus_create_error(hdp_conn->msg, + ERROR_INTERFACE ".HealthError", + "%s", err->message); + g_dbus_send_message(conn, reply); + + /* Send abort request because remote */ + /* side is now in PENDING state. */ + if (!mcap_mdl_abort(hdp_conn->hdp_chann->mdl, + abort_echo_channel_cb, + hdp_channel_ref(hdp_conn->hdp_chann), + (GDestroyNotify) hdp_channel_unref, + &gerr)) { + error("%s", gerr->message); + g_error_free(gerr); + hdp_channel_unref(hdp_conn->hdp_chann); + } + return; + } + + fd = mcap_mdl_get_fd(hdp_conn->hdp_chann->mdl); + if (fd < 0) { + reply = g_dbus_create_error(hdp_conn->msg, + ERROR_INTERFACE ".HealthError", + "Can't write in echo channel"); + g_dbus_send_message(conn, reply); + delete_echo_channel(hdp_conn->hdp_chann); + return; + } + + edata = hdp_conn->hdp_chann->edata; + edata->buf = generate_echo_packet(); + send_echo_data(fd, edata->buf, HDP_ECHO_LEN); + + io = g_io_channel_unix_new(fd); + g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL | G_IO_IN, + check_echo, hdp_tmp_dc_data_ref(hdp_conn)); + + edata->tid = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + ECHO_TIMEOUT, echo_timeout, + hdp_channel_ref(hdp_conn->hdp_chann), + (GDestroyNotify) hdp_channel_unref); + + g_io_channel_unref(io); +} + +static void delete_mdl_cb(GError *err, gpointer data) +{ + if (err != NULL) + error("Deleting error: %s", err->message); +} + +static void abort_and_del_mdl_cb(GError *err, gpointer data) +{ + struct mcap_mdl *mdl = data; + GError *gerr = NULL; + + if (err != NULL) { + error("%s", err->message); + if (err->code == MCAP_INVALID_MDL) { + /* MDL is removed from MCAP so we don't */ + /* need to delete it. */ + return; + } + } + + if (!mcap_delete_mdl(mdl, delete_mdl_cb, NULL, NULL, &gerr)) { + error("%s", gerr->message); + g_error_free(gerr); + } +} + +static void abort_mdl_connection_cb(GError *err, gpointer data) +{ + struct hdp_tmp_dc_data *hdp_conn = data; + struct hdp_channel *hdp_chann = hdp_conn->hdp_chann; + + if (err != NULL) + error("Aborting error: %s", err->message); + + /* Connection operation has failed but we have to */ + /* notify the channel created at MCAP level */ + if (hdp_chann->mdep != HDP_MDEP_ECHO) + g_dbus_emit_signal(btd_get_dbus_connection(), + device_get_path(hdp_chann->dev->dev), + HEALTH_DEVICE, "ChannelConnected", + DBUS_TYPE_OBJECT_PATH, &hdp_chann->path, + DBUS_TYPE_INVALID); +} + +static void hdp_mdl_conn_cb(struct mcap_mdl *mdl, GError *err, gpointer data) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct hdp_tmp_dc_data *hdp_conn = data; + struct hdp_channel *hdp_chann = hdp_conn->hdp_chann; + struct hdp_device *dev = hdp_chann->dev; + DBusMessage *reply; + GError *gerr = NULL; + + if (err != NULL) { + error("%s", err->message); + reply = g_dbus_create_reply(hdp_conn->msg, + DBUS_TYPE_OBJECT_PATH, &hdp_chann->path, + DBUS_TYPE_INVALID); + g_dbus_send_message(conn, reply); + + /* Send abort request because remote side */ + /* is now in PENDING state */ + if (!mcap_mdl_abort(hdp_chann->mdl, abort_mdl_connection_cb, + hdp_tmp_dc_data_ref(hdp_conn), + hdp_tmp_dc_data_destroy, &gerr)) { + hdp_tmp_dc_data_unref(hdp_conn); + error("%s", gerr->message); + g_error_free(gerr); + } + return; + } + + reply = g_dbus_create_reply(hdp_conn->msg, + DBUS_TYPE_OBJECT_PATH, &hdp_chann->path, + DBUS_TYPE_INVALID); + g_dbus_send_message(conn, reply); + + g_dbus_emit_signal(conn, device_get_path(hdp_chann->dev->dev), + HEALTH_DEVICE, "ChannelConnected", + DBUS_TYPE_OBJECT_PATH, &hdp_chann->path, + DBUS_TYPE_INVALID); + + if (!check_channel_conf(hdp_chann)) { + close_mdl(hdp_chann); + return; + } + + if (dev->fr != NULL) + return; + + dev->fr = hdp_channel_ref(hdp_chann); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + device_get_path(dev->dev), HEALTH_DEVICE, + "MainChannel"); +} + +static void device_create_mdl_cb(struct mcap_mdl *mdl, uint8_t conf, + GError *err, gpointer data) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct hdp_create_dc *user_data = data; + struct hdp_tmp_dc_data *hdp_conn; + struct hdp_channel *hdp_chan; + GError *gerr = NULL; + DBusMessage *reply; + + if (err != NULL) { + reply = g_dbus_create_error(user_data->msg, + ERROR_INTERFACE ".HealthError", + "%s", err->message); + g_dbus_send_message(conn, reply); + return; + } + + if (user_data->mdep != HDP_MDEP_ECHO && + user_data->config == HDP_NO_PREFERENCE_DC) { + if (user_data->dev->fr == NULL && conf != HDP_RELIABLE_DC) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "Data channel aborted, first data " + "channel should be reliable"); + goto fail; + } else if (conf == HDP_NO_PREFERENCE_DC || + conf > HDP_STREAMING_DC) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "Data channel aborted, " + "configuration error"); + goto fail; + } + } + + hdp_chan = create_channel(user_data->dev, conf, mdl, + mcap_mdl_get_mdlid(mdl), + user_data->app, &gerr); + if (hdp_chan == NULL) + goto fail; + + hdp_conn = g_new0(struct hdp_tmp_dc_data, 1); + hdp_conn->msg = dbus_message_ref(user_data->msg); + hdp_conn->hdp_chann = hdp_chan; + hdp_conn->cb = user_data->cb; + hdp_chan->mdep = user_data->mdep; + + if (hdp_get_dcpsm(hdp_chan->dev, hdp_get_dcpsm_cb, + hdp_tmp_dc_data_ref(hdp_conn), + hdp_tmp_dc_data_destroy, &gerr)) + return; + + error("%s", gerr->message); + g_error_free(gerr); + + reply = g_dbus_create_reply(hdp_conn->msg, + DBUS_TYPE_OBJECT_PATH, &hdp_chan->path, + DBUS_TYPE_INVALID); + g_dbus_send_message(conn, reply); + hdp_tmp_dc_data_unref(hdp_conn); + + /* Send abort request because remote side is now in PENDING state */ + if (!mcap_mdl_abort(hdp_chan->mdl, abort_mdl_connection_cb, + hdp_tmp_dc_data_ref(hdp_conn), + hdp_tmp_dc_data_destroy, &gerr)) { + hdp_tmp_dc_data_unref(hdp_conn); + error("%s", gerr->message); + g_error_free(gerr); + } + + return; + +fail: + reply = g_dbus_create_error(user_data->msg, + ERROR_INTERFACE ".HealthError", + "%s", gerr->message); + g_dbus_send_message(conn, reply); + g_error_free(gerr); + + /* Send abort request because remote side is now in PENDING */ + /* state. Then we have to delete it because we couldn't */ + /* register the HealthChannel interface */ + if (!mcap_mdl_abort(mdl, abort_and_del_mdl_cb, mcap_mdl_ref(mdl), + (GDestroyNotify) mcap_mdl_unref, &gerr)) { + error("%s", gerr->message); + g_error_free(gerr); + mcap_mdl_unref(mdl); + } +} + +static void device_create_dc_cb(gpointer user_data, GError *err) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct hdp_create_dc *data = user_data; + DBusMessage *reply; + GError *gerr = NULL; + + if (err != NULL) { + reply = g_dbus_create_error(data->msg, + ERROR_INTERFACE ".HealthError", + "%s", err->message); + g_dbus_send_message(conn, reply); + return; + } + + if (data->dev->mcl == NULL) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "Mcl was closed"); + goto fail; + } + + hdp_create_data_ref(data); + + if (mcap_create_mdl(data->dev->mcl, data->mdep, data->config, + device_create_mdl_cb, data, + destroy_create_dc_data, &gerr)) + return; + hdp_create_data_unref(data); + +fail: + reply = g_dbus_create_error(data->msg, ERROR_INTERFACE ".HealthError", + "%s", gerr->message); + g_error_free(gerr); + g_dbus_send_message(conn, reply); +} + +static DBusMessage *device_echo(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct hdp_device *device = user_data; + struct hdp_create_dc *data; + DBusMessage *reply; + GError *err = NULL; + + data = g_new0(struct hdp_create_dc, 1); + data->dev = health_device_ref(device); + data->mdep = HDP_MDEP_ECHO; + data->config = HDP_RELIABLE_DC; + data->msg = dbus_message_ref(msg); + data->cb = hdp_echo_connect_cb; + hdp_create_data_ref(data); + + if (device->mcl_conn && device->mcl) { + if (mcap_create_mdl(device->mcl, data->mdep, data->config, + device_create_mdl_cb, data, + destroy_create_dc_data, &err)) + return NULL; + goto fail; + } + + if (hdp_establish_mcl(data->dev, device_create_dc_cb, + data, destroy_create_dc_data, &err)) + return NULL; + +fail: + reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError", + "%s", err->message); + g_error_free(err); + hdp_create_data_unref(data); + return reply; +} + +static void device_get_mdep_cb(uint8_t mdep, gpointer data, GError *err) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct hdp_create_dc *dc_data, *user_data = data; + DBusMessage *reply; + GError *gerr = NULL; + + if (err != NULL) { + reply = g_dbus_create_error(user_data->msg, + ERROR_INTERFACE ".HealthError", + "%s", err->message); + g_dbus_send_message(conn, reply); + return; + } + + dc_data = hdp_create_data_ref(user_data); + dc_data->mdep = mdep; + + if (user_data->dev->mcl_conn) { + device_create_dc_cb(dc_data, NULL); + hdp_create_data_unref(dc_data); + return; + } + + if (hdp_establish_mcl(dc_data->dev, device_create_dc_cb, + dc_data, destroy_create_dc_data, &gerr)) + return; + + reply = g_dbus_create_error(user_data->msg, + ERROR_INTERFACE ".HealthError", + "%s", gerr->message); + hdp_create_data_unref(dc_data); + g_error_free(gerr); + g_dbus_send_message(conn, reply); +} + +static DBusMessage *device_create_channel(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct hdp_device *device = user_data; + struct hdp_application *app; + struct hdp_create_dc *data; + char *app_path, *conf; + DBusMessage *reply; + GError *err = NULL; + uint8_t config; + GSList *l; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &app_path, + DBUS_TYPE_STRING, &conf, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + l = g_slist_find_custom(applications, app_path, cmp_app); + if (l == NULL) + return btd_error_invalid_args(msg); + + app = l->data; + + if (g_ascii_strcasecmp("reliable", conf) == 0) + config = HDP_RELIABLE_DC; + else if (g_ascii_strcasecmp("streaming", conf) == 0) + config = HDP_STREAMING_DC; + else if (g_ascii_strcasecmp("any", conf) == 0) + config = HDP_NO_PREFERENCE_DC; + else + return btd_error_invalid_args(msg); + + if (app->role == HDP_SINK && config != HDP_NO_PREFERENCE_DC) + return btd_error_invalid_args(msg); + + if (app->role == HDP_SOURCE && config == HDP_NO_PREFERENCE_DC) + return btd_error_invalid_args(msg); + + if (!device->fr && config == HDP_STREAMING_DC) + return btd_error_invalid_args(msg); + + data = g_new0(struct hdp_create_dc, 1); + data->dev = health_device_ref(device); + data->config = config; + data->app = hdp_application_ref(app); + data->msg = dbus_message_ref(msg); + data->cb = hdp_mdl_conn_cb; + + if (hdp_get_mdep(device, l->data, device_get_mdep_cb, + hdp_create_data_ref(data), + destroy_create_dc_data, &err)) + return NULL; + + reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError", + "%s", err->message); + g_error_free(err); + hdp_create_data_unref(data); + return reply; +} + +static void hdp_mdl_delete_cb(GError *err, gpointer data) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct hdp_tmp_dc_data *del_data = data; + DBusMessage *reply; + char *path; + + if (err != NULL && err->code != MCAP_INVALID_MDL) { + reply = g_dbus_create_error(del_data->msg, + ERROR_INTERFACE ".HealthError", + "%s", err->message); + g_dbus_send_message(conn, reply); + return; + } + + path = g_strdup(del_data->hdp_chann->path); + g_dbus_unregister_interface(conn, path, HEALTH_CHANNEL); + g_free(path); + + reply = g_dbus_create_reply(del_data->msg, DBUS_TYPE_INVALID); + g_dbus_send_message(conn, reply); +} + +static void hdp_continue_del_cb(gpointer user_data, GError *err) +{ + DBusConnection *conn = btd_get_dbus_connection(); + struct hdp_tmp_dc_data *del_data = user_data; + GError *gerr = NULL; + DBusMessage *reply; + + if (err != NULL) { + reply = g_dbus_create_error(del_data->msg, + ERROR_INTERFACE ".HealthError", + "%s", err->message); + g_dbus_send_message(conn, reply); + return; + } + + if (mcap_delete_mdl(del_data->hdp_chann->mdl, hdp_mdl_delete_cb, + hdp_tmp_dc_data_ref(del_data), + hdp_tmp_dc_data_destroy, &gerr)) + return; + + reply = g_dbus_create_error(del_data->msg, + ERROR_INTERFACE ".HealthError", + "%s", gerr->message); + hdp_tmp_dc_data_unref(del_data); + g_error_free(gerr); + g_dbus_send_message(conn, reply); +} + +static DBusMessage *device_destroy_channel(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct hdp_device *device = user_data; + struct hdp_tmp_dc_data *del_data; + struct hdp_channel *hdp_chan; + DBusMessage *reply; + GError *err = NULL; + char *path; + GSList *l; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)){ + return btd_error_invalid_args(msg); + } + + l = g_slist_find_custom(device->channels, path, cmp_chan_path); + if (l == NULL) + return btd_error_invalid_args(msg); + + hdp_chan = l->data; + del_data = g_new0(struct hdp_tmp_dc_data, 1); + del_data->msg = dbus_message_ref(msg); + del_data->hdp_chann = hdp_channel_ref(hdp_chan); + + if (device->mcl_conn) { + if (mcap_delete_mdl(hdp_chan->mdl, hdp_mdl_delete_cb, + hdp_tmp_dc_data_ref(del_data), + hdp_tmp_dc_data_destroy, &err)) + return NULL; + goto fail; + } + + if (hdp_establish_mcl(device, hdp_continue_del_cb, + hdp_tmp_dc_data_ref(del_data), + hdp_tmp_dc_data_destroy, &err)) + return NULL; + +fail: + reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError", + "%s", err->message); + hdp_tmp_dc_data_unref(del_data); + g_error_free(err); + return reply; +} + +static gboolean dev_property_exists_main_channel( + const GDBusPropertyTable *property, void *data) +{ + struct hdp_device *device = data; + return device->fr != NULL; +} + +static gboolean dev_property_get_main_channel( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct hdp_device *device = data; + + if (device->fr == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &device->fr->path); + + return TRUE; +} + +static void health_device_destroy(void *data) +{ + struct hdp_device *device = data; + + DBG("Unregistered interface %s on path %s", HEALTH_DEVICE, + device_get_path(device->dev)); + + remove_channels(device); + if (device->ndc != NULL) { + hdp_channel_unref(device->ndc); + device->ndc = NULL; + } + + devices = g_slist_remove(devices, device); + health_device_unref(device); +} + +static const GDBusMethodTable health_device_methods[] = { + { GDBUS_ASYNC_METHOD("Echo", + NULL, GDBUS_ARGS({ "value", "b" }), device_echo) }, + { GDBUS_ASYNC_METHOD("CreateChannel", + GDBUS_ARGS({ "application", "o" }, + { "configuration", "s" }), + GDBUS_ARGS({ "channel", "o" }), + device_create_channel) }, + { GDBUS_ASYNC_METHOD("DestroyChannel", + GDBUS_ARGS({ "channel", "o" }), NULL, + device_destroy_channel) }, + { } +}; + +static const GDBusSignalTable health_device_signals[] = { + { GDBUS_SIGNAL("ChannelConnected", + GDBUS_ARGS({ "channel", "o" })) }, + { GDBUS_SIGNAL("ChannelDeleted", + GDBUS_ARGS({ "channel", "o" })) }, + { } +}; + +static const GDBusPropertyTable health_device_properties[] = { + { "MainChannel", "o", dev_property_get_main_channel, NULL, + dev_property_exists_main_channel }, + { } +}; + +static struct hdp_device *create_health_device(struct btd_device *device) +{ + struct btd_adapter *adapter = device_get_adapter(device); + const char *path = device_get_path(device); + struct hdp_device *dev; + GSList *l; + + if (device == NULL) + return NULL; + + dev = g_new0(struct hdp_device, 1); + dev->dev = btd_device_ref(device); + health_device_ref(dev); + + l = g_slist_find_custom(adapters, adapter, cmp_adapter); + if (l == NULL) + goto fail; + + dev->hdp_adapter = l->data; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + path, HEALTH_DEVICE, + health_device_methods, + health_device_signals, + health_device_properties, + dev, health_device_destroy)) { + error("D-Bus failed to register %s interface", HEALTH_DEVICE); + goto fail; + } + + DBG("Registered interface %s on path %s", HEALTH_DEVICE, path); + return dev; + +fail: + health_device_unref(dev); + return NULL; +} + +int hdp_device_register(struct btd_device *device) +{ + struct hdp_device *hdev; + GSList *l; + + l = g_slist_find_custom(devices, device, cmp_device); + if (l != NULL) { + hdev = l->data; + hdev->sdp_present = TRUE; + return 0; + } + + hdev = create_health_device(device); + if (hdev == NULL) + return -1; + + hdev->sdp_present = TRUE; + + devices = g_slist_prepend(devices, hdev); + return 0; +} + +void hdp_device_unregister(struct btd_device *device) +{ + struct hdp_device *hdp_dev; + const char *path; + GSList *l; + + l = g_slist_find_custom(devices, device, cmp_device); + if (l == NULL) + return; + + hdp_dev = l->data; + path = device_get_path(hdp_dev->dev); + g_dbus_unregister_interface(btd_get_dbus_connection(), + path, HEALTH_DEVICE); +} + +int hdp_manager_start(void) +{ + DBG("Starting Health manager"); + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + MANAGER_PATH, HEALTH_MANAGER, + health_manager_methods, NULL, NULL, + NULL, manager_path_unregister)) { + error("D-Bus failed to register %s interface", HEALTH_MANAGER); + return -1; + } + + return 0; +} + +void hdp_manager_stop(void) +{ + g_dbus_unregister_interface(btd_get_dbus_connection(), + MANAGER_PATH, HEALTH_MANAGER); + + DBG("Stopped Health manager"); +} + +struct hdp_device *health_device_ref(struct hdp_device *hdp_dev) +{ + hdp_dev->ref++; + + DBG("(%p): ref=%d", hdp_dev, hdp_dev->ref); + + return hdp_dev; +} + +void health_device_unref(struct hdp_device *hdp_dev) +{ + hdp_dev->ref--; + + DBG("(%p): ref=%d", hdp_dev, hdp_dev->ref); + + if (hdp_dev->ref > 0) + return; + + free_health_device(hdp_dev); +} diff --git a/profiles/health/hdp.h b/profiles/health/hdp.h new file mode 100644 index 0000000..6e78b09 --- /dev/null +++ b/profiles/health/hdp.h @@ -0,0 +1,32 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int hdp_adapter_register(struct btd_adapter *btd_adapter); +void hdp_adapter_unregister(struct btd_adapter *btd_adapter); + +int hdp_device_register(struct btd_device *device); +void hdp_device_unregister(struct btd_device *device); + +int hdp_manager_start(void); +void hdp_manager_stop(void); + +gboolean hdp_set_mcl_cb(struct hdp_device *device, GError **err); diff --git a/profiles/health/hdp_main.c b/profiles/health/hdp_main.c new file mode 100644 index 0000000..2c4bbe2 --- /dev/null +++ b/profiles/health/hdp_main.c @@ -0,0 +1,45 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gdbus/gdbus.h" + +#include "src/plugin.h" +#include "hdp_manager.h" + +static int hdp_init(void) +{ + return hdp_manager_init(); +} + +static void hdp_exit(void) +{ + hdp_manager_exit(); +} + +BLUETOOTH_PLUGIN_DEFINE(health, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, hdp_init, hdp_exit) diff --git a/profiles/health/hdp_manager.c b/profiles/health/hdp_manager.c new file mode 100644 index 0000000..401adf6 --- /dev/null +++ b/profiles/health/hdp_manager.c @@ -0,0 +1,107 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "btio/btio.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/uuid-helper.h" +#include "src/log.h" + +#include "hdp_types.h" +#include "hdp_manager.h" +#include "hdp.h" + +static int hdp_adapter_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + return hdp_adapter_register(adapter); +} + +static void hdp_adapter_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + hdp_adapter_unregister(adapter); +} + +static int hdp_driver_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + + return hdp_device_register(device); +} + +static void hdp_driver_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + + hdp_device_unregister(device); +} + +static struct btd_profile hdp_source_profile = { + .name = "hdp-source", + .remote_uuid = HDP_SOURCE_UUID, + + .device_probe = hdp_driver_probe, + .device_remove = hdp_driver_remove, + + .adapter_probe = hdp_adapter_probe, + .adapter_remove = hdp_adapter_remove, +}; + +static struct btd_profile hdp_sink_profile = { + .name = "hdp-sink", + .remote_uuid = HDP_SINK_UUID, + + .device_probe = hdp_driver_probe, + .device_remove = hdp_driver_remove, +}; + +int hdp_manager_init(void) +{ + if (hdp_manager_start() < 0) + return -1; + + btd_profile_register(&hdp_source_profile); + btd_profile_register(&hdp_sink_profile); + + return 0; +} + +void hdp_manager_exit(void) +{ + btd_profile_unregister(&hdp_sink_profile); + btd_profile_unregister(&hdp_source_profile); + + hdp_manager_stop(); +} diff --git a/profiles/health/hdp_manager.h b/profiles/health/hdp_manager.h new file mode 100644 index 0000000..1cab4d0 --- /dev/null +++ b/profiles/health/hdp_manager.h @@ -0,0 +1,24 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int hdp_manager_init(void); +void hdp_manager_exit(void); diff --git a/profiles/health/hdp_types.h b/profiles/health/hdp_types.h new file mode 100644 index 0000000..b34b5e0 --- /dev/null +++ b/profiles/health/hdp_types.h @@ -0,0 +1,120 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __HDP_TYPES_H__ +#define __HDP_TYPES_H__ + +#define MANAGER_PATH "/org/bluez" + +#define HEALTH_MANAGER "org.bluez.HealthManager1" +#define HEALTH_DEVICE "org.bluez.HealthDevice1" +#define HEALTH_CHANNEL "org.bluez.HealthChannel1" + +#define HDP_VERSION 0x0100 + +#define HDP_SERVICE_NAME "Bluez HDP" +#define HDP_SERVICE_DSC "A Bluez health device profile implementation" +#define HDP_SERVICE_PROVIDER "Bluez" + +#define HDP_MDEP_ECHO 0x00 +#define HDP_MDEP_INITIAL 0x01 +#define HDP_MDEP_FINAL 0x7F + +#define HDP_ERROR g_quark_from_static_string("hdp-error-quark") + +#define HDP_NO_PREFERENCE_DC 0x00 +#define HDP_RELIABLE_DC 0x01 +#define HDP_STREAMING_DC 0x02 + +#define HDP_SINK_ROLE_AS_STRING "sink" +#define HDP_SOURCE_ROLE_AS_STRING "source" + +typedef enum { + HDP_SOURCE = 0x00, + HDP_SINK = 0x01 +} HdpRole; + +typedef enum { + HDP_DIC_PARSE_ERROR, + HDP_DIC_ENTRY_PARSE_ERROR, + HDP_CONNECTION_ERROR, + HDP_UNSPECIFIED_ERROR, + HDP_UNKNOWN_ERROR +} HdpError; + +enum data_specs { + DATA_EXCHANGE_SPEC_11073 = 0x01 +}; + +struct hdp_application { + char *path; /* The path of the application */ + uint16_t data_type; /* Data type handled for this application */ + gboolean data_type_set; /* Flag for dictionary parsing */ + uint8_t role; /* Role of this application */ + gboolean role_set; /* Flag for dictionary parsing */ + uint8_t chan_type; /* QoS preferred by source applications */ + gboolean chan_type_set; /* Flag for dictionary parsing */ + char *description; /* Options description for SDP record */ + uint8_t id; /* The identification is also the mdepid */ + char *oname; /* Name of the owner application */ + guint dbus_watcher; /* Watch for clients disconnection */ + int ref; /* Reference counter */ +}; + +struct hdp_adapter { + struct btd_adapter *btd_adapter; /* Bluetooth adapter */ + struct mcap_instance *mi; /* Mcap instance in */ + uint16_t ccpsm; /* Control channel psm */ + uint16_t dcpsm; /* Data channel psm */ + uint32_t sdp_handler; /* SDP record handler */ + uint32_t record_state; /* Service record state */ +}; + +struct hdp_device { + struct btd_device *dev; /* Device reference */ + struct hdp_adapter *hdp_adapter; /* hdp_adapater */ + struct mcap_mcl *mcl; /* The mcap control channel */ + gboolean mcl_conn; /* Mcl status */ + gboolean sdp_present; /* Has an sdp record */ + GSList *channels; /* Data Channel list */ + struct hdp_channel *ndc; /* Data channel being negotiated */ + struct hdp_channel *fr; /* First reliable data channel */ + int ref; /* Reference counting */ +}; + +struct hdp_echo_data; + +struct hdp_channel { + struct hdp_device *dev; /* Device where this channel belongs */ + struct hdp_application *app; /* Application */ + struct mcap_mdl *mdl; /* The data channel reference */ + char *path; /* The path of the channel */ + uint8_t config; /* Channel configuration */ + uint8_t mdep; /* Remote MDEP */ + uint16_t mdlid; /* Data channel Id */ + uint16_t imtu; /* Channel incoming MTU */ + uint16_t omtu; /* Channel outgoing MTU */ + struct hdp_echo_data *edata; /* private data used by echo channels */ + int ref; /* Reference counter */ +}; + +#endif /* __HDP_TYPES_H__ */ diff --git a/profiles/health/hdp_util.c b/profiles/health/hdp_util.c new file mode 100644 index 0000000..64debfc --- /dev/null +++ b/profiles/health/hdp_util.c @@ -0,0 +1,1207 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "btio/btio.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/sdpd.h" +#include "src/sdp-client.h" +#include "src/uuid-helper.h" +#include "src/log.h" +#include "src/dbus-common.h" + +#include "mcap.h" +#include "hdp_types.h" +#include "hdp.h" +#include "hdp_util.h" + +typedef gboolean (*parse_item_f)(DBusMessageIter *iter, gpointer user_data, + GError **err); + +struct dict_entry_func { + char *key; + parse_item_f func; +}; + +struct get_mdep_data { + struct hdp_application *app; + gpointer data; + hdp_continue_mdep_f func; + GDestroyNotify destroy; +}; + +struct conn_mcl_data { + int refs; + gpointer data; + hdp_continue_proc_f func; + GDestroyNotify destroy; + struct hdp_device *dev; +}; + +struct get_dcpsm_data { + gpointer data; + hdp_continue_dcpsm_f func; + GDestroyNotify destroy; +}; + +static gboolean parse_dict_entry(struct dict_entry_func dict_context[], + DBusMessageIter *iter, + GError **err, + gpointer user_data) +{ + DBusMessageIter entry; + char *key; + int ctype, i; + struct dict_entry_func df; + + dbus_message_iter_recurse(iter, &entry); + ctype = dbus_message_iter_get_arg_type(&entry); + if (ctype != DBUS_TYPE_STRING) { + g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, + "Dictionary entries should have a string as key"); + return FALSE; + } + + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + /* Find function and call it */ + for (i = 0, df = dict_context[0]; df.key; i++, df = dict_context[i]) { + if (g_ascii_strcasecmp(df.key, key) == 0) + return df.func(&entry, user_data, err); + } + + g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, + "No function found for parsing value for key %s", key); + return FALSE; +} + +static gboolean parse_dict(struct dict_entry_func dict_context[], + DBusMessageIter *iter, + GError **err, + gpointer user_data) +{ + int ctype; + DBusMessageIter dict; + + ctype = dbus_message_iter_get_arg_type(iter); + if (ctype != DBUS_TYPE_ARRAY) { + g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR, + "Dictionary should be an array"); + return FALSE; + } + + dbus_message_iter_recurse(iter, &dict); + while ((ctype = dbus_message_iter_get_arg_type(&dict)) != + DBUS_TYPE_INVALID) { + if (ctype != DBUS_TYPE_DICT_ENTRY) { + g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR, + "Dictionary array should " + "contain dict entries"); + return FALSE; + } + + /* Start parsing entry */ + if (!parse_dict_entry(dict_context, &dict, err, + user_data)) + return FALSE; + /* Finish entry parsing */ + + dbus_message_iter_next(&dict); + } + + return TRUE; +} + +static gboolean parse_data_type(DBusMessageIter *iter, gpointer data, + GError **err) +{ + struct hdp_application *app = data; + DBusMessageIter *value; + DBusMessageIter variant; + int ctype; + + ctype = dbus_message_iter_get_arg_type(iter); + value = iter; + if (ctype == DBUS_TYPE_VARIANT) { + /* Get value inside the variable */ + dbus_message_iter_recurse(iter, &variant); + ctype = dbus_message_iter_get_arg_type(&variant); + value = &variant; + } + + if (ctype != DBUS_TYPE_UINT16) { + g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, + "Final value for data type should be uint16"); + return FALSE; + } + + dbus_message_iter_get_basic(value, &app->data_type); + app->data_type_set = TRUE; + return TRUE; +} + +static gboolean parse_role(DBusMessageIter *iter, gpointer data, GError **err) +{ + struct hdp_application *app = data; + DBusMessageIter *string; + DBusMessageIter value; + int ctype; + const char *role; + + ctype = dbus_message_iter_get_arg_type(iter); + if (ctype == DBUS_TYPE_VARIANT) { + /* Get value inside the variable */ + dbus_message_iter_recurse(iter, &value); + ctype = dbus_message_iter_get_arg_type(&value); + string = &value; + } else { + string = iter; + } + + if (ctype != DBUS_TYPE_STRING) { + g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR, + "Value data spec should be variable or string"); + return FALSE; + } + + dbus_message_iter_get_basic(string, &role); + if (g_ascii_strcasecmp(role, HDP_SINK_ROLE_AS_STRING) == 0) { + app->role = HDP_SINK; + } else if (g_ascii_strcasecmp(role, HDP_SOURCE_ROLE_AS_STRING) == 0) { + app->role = HDP_SOURCE; + } else { + g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR, + "Role value should be \"source\" or \"sink\""); + return FALSE; + } + + app->role_set = TRUE; + + return TRUE; +} + +static gboolean parse_desc(DBusMessageIter *iter, gpointer data, GError **err) +{ + struct hdp_application *app = data; + DBusMessageIter *string; + DBusMessageIter variant; + int ctype; + const char *desc; + + ctype = dbus_message_iter_get_arg_type(iter); + if (ctype == DBUS_TYPE_VARIANT) { + /* Get value inside the variable */ + dbus_message_iter_recurse(iter, &variant); + ctype = dbus_message_iter_get_arg_type(&variant); + string = &variant; + } else { + string = iter; + } + + if (ctype != DBUS_TYPE_STRING) { + g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, + "Value data spec should be variable or string"); + return FALSE; + } + + dbus_message_iter_get_basic(string, &desc); + app->description = g_strdup(desc); + return TRUE; +} + +static gboolean parse_chan_type(DBusMessageIter *iter, gpointer data, + GError **err) +{ + struct hdp_application *app = data; + DBusMessageIter *value; + DBusMessageIter variant; + char *chan_type; + int ctype; + + ctype = dbus_message_iter_get_arg_type(iter); + value = iter; + if (ctype == DBUS_TYPE_VARIANT) { + /* Get value inside the variable */ + dbus_message_iter_recurse(iter, &variant); + ctype = dbus_message_iter_get_arg_type(&variant); + value = &variant; + } + + if (ctype != DBUS_TYPE_STRING) { + g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, + "Final value for channel type should be an string"); + return FALSE; + } + + dbus_message_iter_get_basic(value, &chan_type); + + if (g_ascii_strcasecmp("reliable", chan_type) == 0) + app->chan_type = HDP_RELIABLE_DC; + else if (g_ascii_strcasecmp("streaming", chan_type) == 0) + app->chan_type = HDP_STREAMING_DC; + else { + g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, + "Invalid value for data type"); + return FALSE; + } + + app->chan_type_set = TRUE; + + return TRUE; +} + +static struct dict_entry_func dict_parser[] = { + {"DataType", parse_data_type}, + {"Role", parse_role}, + {"Description", parse_desc}, + {"ChannelType", parse_chan_type}, + {NULL, NULL} +}; + +struct hdp_application *hdp_get_app_config(DBusMessageIter *iter, GError **err) +{ + struct hdp_application *app; + + app = g_new0(struct hdp_application, 1); + app->ref = 1; + if (!parse_dict(dict_parser, iter, err, app)) + goto fail; + if (!app->data_type_set || !app->role_set) { + g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR, + "Mandatory fields aren't set"); + goto fail; + } + return app; + +fail: + hdp_application_unref(app); + return NULL; +} + +static gboolean is_app_role(GSList *app_list, HdpRole role) +{ + GSList *l; + + for (l = app_list; l; l = l->next) { + struct hdp_application *app = l->data; + + if (app->role == role) + return TRUE; + } + + return FALSE; +} + +static gboolean set_sdp_services_uuid(sdp_record_t *record, HdpRole role) +{ + uuid_t svc_uuid_source, svc_uuid_sink; + sdp_list_t *svc_list = NULL; + + sdp_uuid16_create(&svc_uuid_sink, HDP_SINK_SVCLASS_ID); + sdp_uuid16_create(&svc_uuid_source, HDP_SOURCE_SVCLASS_ID); + + sdp_get_service_classes(record, &svc_list); + + if (role == HDP_SOURCE) { + if (!sdp_list_find(svc_list, &svc_uuid_source, sdp_uuid_cmp)) + svc_list = sdp_list_append(svc_list, &svc_uuid_source); + } else if (role == HDP_SINK) { + if (!sdp_list_find(svc_list, &svc_uuid_sink, sdp_uuid_cmp)) + svc_list = sdp_list_append(svc_list, &svc_uuid_sink); + } + + if (sdp_set_service_classes(record, svc_list) < 0) { + sdp_list_free(svc_list, NULL); + return FALSE; + } + + sdp_list_free(svc_list, NULL); + + return TRUE; +} + +static gboolean register_service_protocols(struct hdp_adapter *adapter, + sdp_record_t *sdp_record) +{ + gboolean ret; + uuid_t l2cap_uuid, mcap_c_uuid; + sdp_list_t *l2cap_list, *proto_list = NULL, *mcap_list = NULL; + sdp_list_t *access_proto_list = NULL; + sdp_data_t *psm = NULL, *mcap_ver = NULL; + uint16_t version = MCAP_VERSION; + + /* set l2cap information */ + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + l2cap_list = sdp_list_append(NULL, &l2cap_uuid); + if (l2cap_list == NULL) { + ret = FALSE; + goto end; + } + + psm = sdp_data_alloc(SDP_UINT16, &adapter->ccpsm); + if (psm == NULL) { + ret = FALSE; + goto end; + } + + if (sdp_list_append(l2cap_list, psm) == NULL) { + ret = FALSE; + goto end; + } + + proto_list = sdp_list_append(NULL, l2cap_list); + if (proto_list == NULL) { + ret = FALSE; + goto end; + } + + /* set mcap information */ + sdp_uuid16_create(&mcap_c_uuid, MCAP_CTRL_UUID); + mcap_list = sdp_list_append(NULL, &mcap_c_uuid); + if (mcap_list == NULL) { + ret = FALSE; + goto end; + } + + mcap_ver = sdp_data_alloc(SDP_UINT16, &version); + if (mcap_ver == NULL) { + ret = FALSE; + goto end; + } + + if (sdp_list_append(mcap_list, mcap_ver) == NULL) { + ret = FALSE; + goto end; + } + + if (sdp_list_append(proto_list, mcap_list) == NULL) { + ret = FALSE; + goto end; + } + + /* attach protocol information to service record */ + access_proto_list = sdp_list_append(NULL, proto_list); + if (access_proto_list == NULL) { + ret = FALSE; + goto end; + } + + ret = TRUE; + sdp_set_access_protos(sdp_record, access_proto_list); + +end: + if (l2cap_list != NULL) + sdp_list_free(l2cap_list, NULL); + if (mcap_list != NULL) + sdp_list_free(mcap_list, NULL); + if (proto_list != NULL) + sdp_list_free(proto_list, NULL); + if (access_proto_list != NULL) + sdp_list_free(access_proto_list, NULL); + if (psm != NULL) + sdp_data_free(psm); + if (mcap_ver != NULL) + sdp_data_free(mcap_ver); + + return ret; +} + +static gboolean register_service_profiles(sdp_record_t *sdp_record) +{ + gboolean ret; + sdp_list_t *profile_list; + sdp_profile_desc_t hdp_profile; + + /* set hdp information */ + sdp_uuid16_create(&hdp_profile.uuid, HDP_SVCLASS_ID); + hdp_profile.version = HDP_VERSION; + profile_list = sdp_list_append(NULL, &hdp_profile); + if (profile_list == NULL) + return FALSE; + + /* set profile descriptor list */ + if (sdp_set_profile_descs(sdp_record, profile_list) < 0) + ret = FALSE; + else + ret = TRUE; + + sdp_list_free(profile_list, NULL); + + return ret; +} + +static gboolean register_service_additional_protocols( + struct hdp_adapter *adapter, + sdp_record_t *sdp_record) +{ + gboolean ret = TRUE; + uuid_t l2cap_uuid, mcap_d_uuid; + sdp_list_t *l2cap_list, *proto_list = NULL, *mcap_list = NULL; + sdp_list_t *access_proto_list = NULL; + sdp_data_t *psm = NULL; + + /* set l2cap information */ + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + l2cap_list = sdp_list_append(NULL, &l2cap_uuid); + if (l2cap_list == NULL) { + ret = FALSE; + goto end; + } + + psm = sdp_data_alloc(SDP_UINT16, &adapter->dcpsm); + if (psm == NULL) { + ret = FALSE; + goto end; + } + + if (sdp_list_append(l2cap_list, psm) == NULL) { + ret = FALSE; + goto end; + } + + proto_list = sdp_list_append(NULL, l2cap_list); + if (proto_list == NULL) { + ret = FALSE; + goto end; + } + + /* set mcap information */ + sdp_uuid16_create(&mcap_d_uuid, MCAP_DATA_UUID); + mcap_list = sdp_list_append(NULL, &mcap_d_uuid); + if (mcap_list == NULL) { + ret = FALSE; + goto end; + } + + if (sdp_list_append(proto_list, mcap_list) == NULL) { + ret = FALSE; + goto end; + } + + /* attach protocol information to service record */ + access_proto_list = sdp_list_append(NULL, proto_list); + if (access_proto_list == NULL) { + ret = FALSE; + goto end; + } + + sdp_set_add_access_protos(sdp_record, access_proto_list); + +end: + if (l2cap_list != NULL) + sdp_list_free(l2cap_list, NULL); + if (mcap_list != NULL) + sdp_list_free(mcap_list, NULL); + if (proto_list != NULL) + sdp_list_free(proto_list, NULL); + if (access_proto_list != NULL) + sdp_list_free(access_proto_list, NULL); + if (psm != NULL) + sdp_data_free(psm); + + return ret; +} + +static sdp_list_t *app_to_sdplist(struct hdp_application *app) +{ + sdp_data_t *mdepid, + *dtype = NULL, + *role = NULL, + *desc = NULL; + sdp_list_t *f_list = NULL; + + mdepid = sdp_data_alloc(SDP_UINT8, &app->id); + if (mdepid == NULL) + return NULL; + + dtype = sdp_data_alloc(SDP_UINT16, &app->data_type); + if (dtype == NULL) + goto fail; + + role = sdp_data_alloc(SDP_UINT8, &app->role); + if (role == NULL) + goto fail; + + if (app->description != NULL) { + desc = sdp_data_alloc(SDP_TEXT_STR8, app->description); + if (desc == NULL) + goto fail; + } + + f_list = sdp_list_append(NULL, mdepid); + if (f_list == NULL) + goto fail; + + if (sdp_list_append(f_list, dtype) == NULL) + goto fail; + + if (sdp_list_append(f_list, role) == NULL) + goto fail; + + if (desc != NULL) + if (sdp_list_append(f_list, desc) == NULL) + goto fail; + + return f_list; + +fail: + if (f_list != NULL) + sdp_list_free(f_list, NULL); + if (mdepid != NULL) + sdp_data_free(mdepid); + if (dtype != NULL) + sdp_data_free(dtype); + if (role != NULL) + sdp_data_free(role); + if (desc != NULL) + sdp_data_free(desc); + + 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 gboolean register_features(struct hdp_application *app, + sdp_list_t **sup_features) +{ + sdp_list_t *hdp_feature; + + hdp_feature = app_to_sdplist(app); + if (hdp_feature == NULL) + goto fail; + + if (*sup_features == NULL) { + *sup_features = sdp_list_append(NULL, hdp_feature); + if (*sup_features == NULL) + goto fail; + } else if (sdp_list_append(*sup_features, hdp_feature) == NULL) { + goto fail; + } + + return TRUE; + +fail: + if (hdp_feature != NULL) + sdp_list_free(hdp_feature, (sdp_free_func_t)sdp_data_free); + if (*sup_features != NULL) + sdp_list_free(*sup_features, free_hdp_list); + return FALSE; +} + +static gboolean register_service_sup_features(GSList *app_list, + sdp_record_t *sdp_record) +{ + GSList *l; + sdp_list_t *sup_features = NULL; + + for (l = app_list; l; l = l->next) { + if (!register_features(l->data, &sup_features)) + return FALSE; + } + + if (sdp_set_supp_feat(sdp_record, sup_features) < 0) { + sdp_list_free(sup_features, free_hdp_list); + return FALSE; + } + + sdp_list_free(sup_features, free_hdp_list); + + return TRUE; +} + +static gboolean register_data_exchange_spec(sdp_record_t *record) +{ + sdp_data_t *spec; + uint8_t data_spec = DATA_EXCHANGE_SPEC_11073; + /* As by now 11073 is the only supported we set it by default */ + + spec = sdp_data_alloc(SDP_UINT8, &data_spec); + if (spec == NULL) + return FALSE; + + if (sdp_attr_add(record, SDP_ATTR_DATA_EXCHANGE_SPEC, spec) < 0) { + sdp_data_free(spec); + return FALSE; + } + + return TRUE; +} + +static gboolean register_mcap_features(sdp_record_t *sdp_record) +{ + sdp_data_t *mcap_proc; + uint8_t mcap_sup_proc = MCAP_SUP_PROC; + + mcap_proc = sdp_data_alloc(SDP_UINT8, &mcap_sup_proc); + if (mcap_proc == NULL) + return FALSE; + + if (sdp_attr_add(sdp_record, SDP_ATTR_MCAP_SUPPORTED_PROCEDURES, + mcap_proc) < 0) { + sdp_data_free(mcap_proc); + return FALSE; + } + + return TRUE; +} + +gboolean hdp_update_sdp_record(struct hdp_adapter *adapter, GSList *app_list) +{ + sdp_record_t *sdp_record; + + if (adapter->sdp_handler > 0) + adapter_service_remove(adapter->btd_adapter, + adapter->sdp_handler); + + if (app_list == NULL) { + adapter->sdp_handler = 0; + return TRUE; + } + + sdp_record = sdp_record_alloc(); + if (sdp_record == NULL) + return FALSE; + + if (adapter->sdp_handler > 0) + sdp_record->handle = adapter->sdp_handler; + else + sdp_record->handle = 0xffffffff; /* Set automatically */ + + if (is_app_role(app_list, HDP_SINK)) + set_sdp_services_uuid(sdp_record, HDP_SINK); + if (is_app_role(app_list, HDP_SOURCE)) + set_sdp_services_uuid(sdp_record, HDP_SOURCE); + + if (!register_service_protocols(adapter, sdp_record)) + goto fail; + if (!register_service_profiles(sdp_record)) + goto fail; + if (!register_service_additional_protocols(adapter, sdp_record)) + goto fail; + + sdp_set_info_attr(sdp_record, HDP_SERVICE_NAME, HDP_SERVICE_PROVIDER, + HDP_SERVICE_DSC); + if (!register_service_sup_features(app_list, sdp_record)) + goto fail; + if (!register_data_exchange_spec(sdp_record)) + goto fail; + + register_mcap_features(sdp_record); + + if (sdp_set_record_state(sdp_record, adapter->record_state++) < 0) + goto fail; + + if (adapter_service_add(adapter->btd_adapter, sdp_record) < 0) + goto fail; + adapter->sdp_handler = sdp_record->handle; + return TRUE; + +fail: + if (sdp_record != NULL) + sdp_record_free(sdp_record); + return FALSE; +} + +static gboolean check_role(uint8_t rec_role, uint8_t app_role) +{ + if ((rec_role == HDP_SINK && app_role == HDP_SOURCE) || + (rec_role == HDP_SOURCE && app_role == HDP_SINK)) + return TRUE; + + return FALSE; +} + +static gboolean get_mdep_from_rec(const sdp_record_t *rec, uint8_t role, + uint16_t d_type, uint8_t *mdep, char **desc) +{ + sdp_data_t *list, *feat; + + if (desc == NULL && mdep == NULL) + return TRUE; + + list = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES_LIST); + if (list == NULL || !SDP_IS_SEQ(list->dtd)) + return FALSE; + + for (feat = list->val.dataseq; feat; feat = feat->next) { + sdp_data_t *data_type, *mdepid, *role_t, *desc_t; + + if (!SDP_IS_SEQ(feat->dtd)) + continue; + + mdepid = feat->val.dataseq; + if (mdepid == NULL) + continue; + + data_type = mdepid->next; + if (data_type == NULL) + continue; + + role_t = data_type->next; + if (role_t == NULL) + continue; + + desc_t = role_t->next; + + if (data_type->dtd != SDP_UINT16 || mdepid->dtd != SDP_UINT8 || + role_t->dtd != SDP_UINT8) + continue; + + if (data_type->val.uint16 != d_type || + !check_role(role_t->val.uint8, role)) + continue; + + if (mdep != NULL) + *mdep = mdepid->val.uint8; + + if (desc != NULL && desc_t != NULL && + SDP_IS_TEXT_STR(desc_t->dtd)) + *desc = g_strdup(desc_t->val.str); + + return TRUE; + } + + return FALSE; +} + +static void get_mdep_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct get_mdep_data *mdep_data = user_data; + GError *gerr = NULL; + uint8_t mdep; + + if (err < 0 || recs == NULL) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "Error getting remote SDP records"); + mdep_data->func(0, mdep_data->data, gerr); + g_error_free(gerr); + return; + } + + if (!get_mdep_from_rec(recs->data, mdep_data->app->role, + mdep_data->app->data_type, &mdep, NULL)) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "No matching MDEP found"); + mdep_data->func(0, mdep_data->data, gerr); + g_error_free(gerr); + return; + } + + mdep_data->func(mdep, mdep_data->data, NULL); +} + +static void free_mdep_data(gpointer data) +{ + struct get_mdep_data *mdep_data = data; + + if (mdep_data->destroy) + mdep_data->destroy(mdep_data->data); + hdp_application_unref(mdep_data->app); + + g_free(mdep_data); +} + +gboolean hdp_get_mdep(struct hdp_device *device, struct hdp_application *app, + hdp_continue_mdep_f func, gpointer data, + GDestroyNotify destroy, GError **err) +{ + struct get_mdep_data *mdep_data; + const bdaddr_t *src; + const bdaddr_t *dst; + uuid_t uuid; + + src = btd_adapter_get_address(device_get_adapter(device->dev)); + dst = device_get_address(device->dev); + + mdep_data = g_new0(struct get_mdep_data, 1); + mdep_data->app = hdp_application_ref(app); + mdep_data->func = func; + mdep_data->data = data; + mdep_data->destroy = destroy; + + bt_string2uuid(&uuid, HDP_UUID); + if (bt_search_service(src, dst, &uuid, get_mdep_cb, mdep_data, + free_mdep_data, 0) < 0) { + g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR, + "Can't get remote SDP record"); + g_free(mdep_data); + return FALSE; + } + + return TRUE; +} + +static gboolean get_prot_desc_entry(sdp_data_t *entry, int type, guint16 *val) +{ + sdp_data_t *iter; + int proto; + + if (entry == NULL || !SDP_IS_SEQ(entry->dtd)) + return FALSE; + + iter = entry->val.dataseq; + if (!(iter->dtd & SDP_UUID_UNSPEC)) + return FALSE; + + proto = sdp_uuid_to_proto(&iter->val.uuid); + if (proto != type) + return FALSE; + + if (val == NULL) + return TRUE; + + iter = iter->next; + if (iter->dtd != SDP_UINT16) + return FALSE; + + *val = iter->val.uint16; + + return TRUE; +} + +static gboolean hdp_get_prot_desc_list(const sdp_record_t *rec, guint16 *psm, + guint16 *version) +{ + sdp_data_t *pdl, *p0, *p1; + + if (psm == NULL && version == NULL) + return TRUE; + + pdl = sdp_data_get(rec, SDP_ATTR_PROTO_DESC_LIST); + if (pdl == NULL || !SDP_IS_SEQ(pdl->dtd)) + return FALSE; + + p0 = pdl->val.dataseq; + if (!get_prot_desc_entry(p0, L2CAP_UUID, psm)) + return FALSE; + + p1 = p0->next; + if (!get_prot_desc_entry(p1, MCAP_CTRL_UUID, version)) + return FALSE; + + return TRUE; +} + +static gboolean hdp_get_add_prot_desc_list(const sdp_record_t *rec, + guint16 *psm) +{ + sdp_data_t *pdl, *p0, *p1; + + if (psm == NULL) + return TRUE; + + pdl = sdp_data_get(rec, SDP_ATTR_ADD_PROTO_DESC_LIST); + if (pdl == NULL || pdl->dtd != SDP_SEQ8) + return FALSE; + pdl = pdl->val.dataseq; + if (pdl->dtd != SDP_SEQ8) + return FALSE; + + p0 = pdl->val.dataseq; + + if (!get_prot_desc_entry(p0, L2CAP_UUID, psm)) + return FALSE; + p1 = p0->next; + if (!get_prot_desc_entry(p1, MCAP_DATA_UUID, NULL)) + return FALSE; + + return TRUE; +} + +static gboolean get_ccpsm(sdp_list_t *recs, uint16_t *ccpsm) +{ + sdp_list_t *l; + + for (l = recs; l; l = l->next) { + sdp_record_t *rec = l->data; + + if (hdp_get_prot_desc_list(rec, ccpsm, NULL)) + return TRUE; + } + + return FALSE; +} + +static gboolean get_dcpsm(sdp_list_t *recs, uint16_t *dcpsm) +{ + sdp_list_t *l; + + for (l = recs; l; l = l->next) { + sdp_record_t *rec = l->data; + + if (hdp_get_add_prot_desc_list(rec, dcpsm)) + return TRUE; + } + + return FALSE; +} + +static void con_mcl_data_unref(struct conn_mcl_data *conn_data) +{ + if (conn_data == NULL) + return; + + if (--conn_data->refs > 0) + return; + + if (conn_data->destroy) + conn_data->destroy(conn_data->data); + + health_device_unref(conn_data->dev); + g_free(conn_data); +} + +static void destroy_con_mcl_data(gpointer data) +{ + con_mcl_data_unref(data); +} + +static struct conn_mcl_data *con_mcl_data_ref(struct conn_mcl_data *conn_data) +{ + if (conn_data == NULL) + return NULL; + + conn_data->refs++; + return conn_data; +} + +static void create_mcl_cb(struct mcap_mcl *mcl, GError *err, gpointer data) +{ + struct conn_mcl_data *conn_data = data; + struct hdp_device *device = conn_data->dev; + GError *gerr = NULL; + + if (err != NULL) { + conn_data->func(conn_data->data, err); + return; + } + + if (device->mcl == NULL) + device->mcl = mcap_mcl_ref(mcl); + device->mcl_conn = TRUE; + + hdp_set_mcl_cb(device, &gerr); + + conn_data->func(conn_data->data, gerr); + if (gerr != NULL) + g_error_free(gerr); +} + +static void search_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct conn_mcl_data *conn_data = user_data; + GError *gerr = NULL; + uint16_t ccpsm; + + if (conn_data->dev->hdp_adapter->mi == NULL) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "Mcap instance released"); + goto fail; + } + + if (err < 0 || recs == NULL) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "Error getting remote SDP records"); + goto fail; + } + + if (!get_ccpsm(recs, &ccpsm)) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "Can't get remote PSM for control channel"); + goto fail; + } + + conn_data = con_mcl_data_ref(conn_data); + + if (!mcap_create_mcl(conn_data->dev->hdp_adapter->mi, + device_get_address(conn_data->dev->dev), + ccpsm, create_mcl_cb, conn_data, + destroy_con_mcl_data, &gerr)) { + con_mcl_data_unref(conn_data); + goto fail; + } + return; +fail: + conn_data->func(conn_data->data, gerr); + g_error_free(gerr); +} + +gboolean hdp_establish_mcl(struct hdp_device *device, + hdp_continue_proc_f func, + gpointer data, + GDestroyNotify destroy, + GError **err) +{ + struct conn_mcl_data *conn_data; + const bdaddr_t *src; + const bdaddr_t *dst; + uuid_t uuid; + + src = btd_adapter_get_address(device_get_adapter(device->dev)); + dst = device_get_address(device->dev); + + conn_data = g_new0(struct conn_mcl_data, 1); + conn_data->refs = 1; + conn_data->func = func; + conn_data->data = data; + conn_data->destroy = destroy; + conn_data->dev = health_device_ref(device); + + bt_string2uuid(&uuid, HDP_UUID); + if (bt_search_service(src, dst, &uuid, search_cb, conn_data, + destroy_con_mcl_data, 0) < 0) { + g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR, + "Can't get remote SDP record"); + g_free(conn_data); + return FALSE; + } + + return TRUE; +} + +static void get_dcpsm_cb(sdp_list_t *recs, int err, gpointer data) +{ + struct get_dcpsm_data *dcpsm_data = data; + GError *gerr = NULL; + uint16_t dcpsm; + + if (err < 0 || recs == NULL) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "Error getting remote SDP records"); + goto fail; + } + + if (!get_dcpsm(recs, &dcpsm)) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "Can't get remote PSM for data channel"); + goto fail; + } + + dcpsm_data->func(dcpsm, dcpsm_data->data, NULL); + return; + +fail: + dcpsm_data->func(0, dcpsm_data->data, gerr); + g_error_free(gerr); +} + +static void free_dcpsm_data(gpointer data) +{ + struct get_dcpsm_data *dcpsm_data = data; + + if (dcpsm_data == NULL) + return; + + if (dcpsm_data->destroy) + dcpsm_data->destroy(dcpsm_data->data); + + g_free(dcpsm_data); +} + +gboolean hdp_get_dcpsm(struct hdp_device *device, hdp_continue_dcpsm_f func, + gpointer data, + GDestroyNotify destroy, + GError **err) +{ + struct get_dcpsm_data *dcpsm_data; + const bdaddr_t *src; + const bdaddr_t *dst; + uuid_t uuid; + + src = btd_adapter_get_address(device_get_adapter(device->dev)); + dst = device_get_address(device->dev); + + dcpsm_data = g_new0(struct get_dcpsm_data, 1); + dcpsm_data->func = func; + dcpsm_data->data = data; + dcpsm_data->destroy = destroy; + + bt_string2uuid(&uuid, HDP_UUID); + if (bt_search_service(src, dst, &uuid, get_dcpsm_cb, dcpsm_data, + free_dcpsm_data, 0) < 0) { + g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR, + "Can't get remote SDP record"); + g_free(dcpsm_data); + return FALSE; + } + + return TRUE; +} + +static void hdp_free_application(struct hdp_application *app) +{ + if (app->dbus_watcher > 0) + g_dbus_remove_watch(btd_get_dbus_connection(), + app->dbus_watcher); + + g_free(app->oname); + g_free(app->description); + g_free(app->path); + g_free(app); +} + +struct hdp_application *hdp_application_ref(struct hdp_application *app) +{ + if (app == NULL) + return NULL; + + app->ref++; + + DBG("health_application_ref(%p): ref=%d", app, app->ref); + return app; +} + +void hdp_application_unref(struct hdp_application *app) +{ + if (app == NULL) + return; + + app->ref--; + + DBG("health_application_unref(%p): ref=%d", app, app->ref); + if (app->ref > 0) + return; + + hdp_free_application(app); +} diff --git a/profiles/health/hdp_util.h b/profiles/health/hdp_util.h new file mode 100644 index 0000000..35e1196 --- /dev/null +++ b/profiles/health/hdp_util.h @@ -0,0 +1,58 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __HDP_UTIL_H__ +#define __HDP_UTIL_H__ + +typedef void (*hdp_continue_mdep_f)(uint8_t mdep, gpointer user_data, + GError *err); +typedef void (*hdp_continue_dcpsm_f)(uint16_t dcpsm, gpointer user_data, + GError *err); +typedef void (*hdp_continue_proc_f)(gpointer user_data, GError *err); + +struct hdp_application *hdp_get_app_config(DBusMessageIter *iter, GError **err); +gboolean hdp_update_sdp_record(struct hdp_adapter *adapter, GSList *app_list); +gboolean hdp_get_mdep(struct hdp_device *device, struct hdp_application *app, + hdp_continue_mdep_f func, + gpointer data, GDestroyNotify destroy, + GError **err); + +gboolean hdp_establish_mcl(struct hdp_device *device, + hdp_continue_proc_f func, + gpointer data, + GDestroyNotify destroy, + GError **err); + +gboolean hdp_get_dcpsm(struct hdp_device *device, hdp_continue_dcpsm_f func, + gpointer data, + GDestroyNotify destroy, + GError **err); + + +struct hdp_application *hdp_application_ref(struct hdp_application *app); +void hdp_application_unref(struct hdp_application *app); + +struct hdp_device *health_device_ref(struct hdp_device *hdp_dev); +void health_device_unref(struct hdp_device *hdp_dev); + + +#endif /* __HDP_UTIL_H__ */ diff --git a/profiles/health/mcap.c b/profiles/health/mcap.c new file mode 100644 index 0000000..249a753 --- /dev/null +++ b/profiles/health/mcap.c @@ -0,0 +1,3194 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * Copyright (C) 2010 Signove + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "bluetooth/l2cap.h" +#include "btio/btio.h" +#include "src/log.h" + +#include "mcap.h" + +#define MCAP_BTCLOCK_HALF (MCAP_BTCLOCK_FIELD / 2) +#define CLK CLOCK_MONOTONIC + +#define MCAP_CSP_ERROR g_quark_from_static_string("mcap-csp-error-quark") +#define MAX_RETRIES 10 +#define SAMPLE_COUNT 20 + +#define RESPONSE_TIMER 6 /* seconds */ +#define MAX_CACHED 10 /* 10 devices */ + +#define MCAP_ERROR g_quark_from_static_string("mcap-error-quark") + +#define RELEASE_TIMER(__mcl) do { \ + if (__mcl->tid) { \ + g_source_remove(__mcl->tid); \ + __mcl->tid = 0; \ + } \ +} while(0) + +struct mcap_csp { + uint64_t base_tmstamp; /* CSP base timestamp */ + struct timespec base_time; /* CSP base time when timestamp set */ + guint local_caps; /* CSP-Master: have got remote caps */ + guint remote_caps; /* CSP-Slave: remote master got caps */ + guint rem_req_acc; /* CSP-Slave: accuracy required by master */ + guint ind_expected; /* CSP-Master: indication expected */ + uint8_t csp_req; /* CSP-Master: Request control flag */ + guint ind_timer; /* CSP-Slave: indication timer */ + guint set_timer; /* CSP-Slave: delayed set timer */ + void *set_data; /* CSP-Slave: delayed set data */ + void *csp_priv_data; /* CSP-Master: In-flight request data */ +}; + +struct mcap_sync_cap_cbdata { + mcap_sync_cap_cb cb; + gpointer user_data; +}; + +struct mcap_sync_set_cbdata { + mcap_sync_set_cb cb; + gpointer user_data; +}; + +struct csp_caps { + int ts_acc; /* timestamp accuracy */ + int ts_res; /* timestamp resolution */ + int latency; /* Read BT clock latency */ + int preempt_thresh; /* Preemption threshold for latency */ + int syncleadtime_ms; /* SyncLeadTime in ms */ +}; + +struct sync_set_data { + uint8_t update; + uint32_t sched_btclock; + uint64_t timestamp; + int ind_freq; + gboolean role; +}; + +struct connect_mcl { + struct mcap_mcl *mcl; /* MCL for this operation */ + mcap_mcl_connect_cb connect_cb; /* Connect callback */ + GDestroyNotify destroy; /* Destroy callback */ + gpointer user_data; /* Callback user data */ +}; + +typedef union { + mcap_mdl_operation_cb op; + mcap_mdl_operation_conf_cb op_conf; + mcap_mdl_notify_cb notify; +} mcap_cb_type; + +struct mcap_mdl_op_cb { + struct mcap_mdl *mdl; /* MDL for this operation */ + mcap_cb_type cb; /* Operation callback */ + GDestroyNotify destroy; /* Destroy callback */ + gpointer user_data; /* Callback user data */ +}; + +/* MCAP finite state machine functions */ +static void proc_req_connected(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l); +static void proc_req_pending(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l); +static void proc_req_active(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l); + +static void (*proc_req[])(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) = { + proc_req_connected, + proc_req_pending, + proc_req_active +}; + +static gboolean csp_caps_initialized = FALSE; +struct csp_caps _caps; + +static void mcap_cache_mcl(struct mcap_mcl *mcl); + +static void default_mdl_connected_cb(struct mcap_mdl *mdl, gpointer data) +{ + DBG("MCAP Unmanaged mdl connection"); +} + +static void default_mdl_closed_cb(struct mcap_mdl *mdl, gpointer data) +{ + DBG("MCAP Unmanaged mdl closed"); +} + +static void default_mdl_deleted_cb(struct mcap_mdl *mdl, gpointer data) +{ + DBG("MCAP Unmanaged mdl deleted"); +} + +static void default_mdl_aborted_cb(struct mcap_mdl *mdl, gpointer data) +{ + DBG("MCAP Unmanaged mdl aborted"); +} + +static uint8_t default_mdl_conn_req_cb(struct mcap_mcl *mcl, + uint8_t mdepid, uint16_t mdlid, + uint8_t *conf, gpointer data) +{ + DBG("MCAP mdl remote connection aborted"); + /* Due to this callback isn't managed this request won't be supported */ + return MCAP_REQUEST_NOT_SUPPORTED; +} + +static uint8_t default_mdl_reconn_req_cb(struct mcap_mdl *mdl, + gpointer data) +{ + DBG("MCAP mdl remote reconnection aborted"); + /* Due to this callback isn't managed this request won't be supported */ + return MCAP_REQUEST_NOT_SUPPORTED; +} + +static void set_default_cb(struct mcap_mcl *mcl) +{ + if (!mcl->cb) + mcl->cb = g_new0(struct mcap_mdl_cb, 1); + + mcl->cb->mdl_connected = default_mdl_connected_cb; + mcl->cb->mdl_closed = default_mdl_closed_cb; + mcl->cb->mdl_deleted = default_mdl_deleted_cb; + mcl->cb->mdl_aborted = default_mdl_aborted_cb; + mcl->cb->mdl_conn_req = default_mdl_conn_req_cb; + mcl->cb->mdl_reconn_req = default_mdl_reconn_req_cb; +} + +static char *error2str(uint8_t rc) +{ + switch (rc) { + case MCAP_SUCCESS: + return "Success"; + case MCAP_INVALID_OP_CODE: + return "Invalid Op Code"; + case MCAP_INVALID_PARAM_VALUE: + return "Invalid Parameter Value"; + case MCAP_INVALID_MDEP: + return "Invalid MDEP"; + case MCAP_MDEP_BUSY: + return "MDEP Busy"; + case MCAP_INVALID_MDL: + return "Invalid MDL"; + case MCAP_MDL_BUSY: + return "MDL Busy"; + case MCAP_INVALID_OPERATION: + return "Invalid Operation"; + case MCAP_RESOURCE_UNAVAILABLE: + return "Resource Unavailable"; + case MCAP_UNSPECIFIED_ERROR: + return "Unspecified Error"; + case MCAP_REQUEST_NOT_SUPPORTED: + return "Request Not Supported"; + case MCAP_CONFIGURATION_REJECTED: + return "Configuration Rejected"; + default: + return "Unknown Response Code"; + } +} + +static gboolean mcap_send_std_opcode(struct mcap_mcl *mcl, void *cmd, + uint32_t size, GError **err) +{ + if (mcl->state == MCL_IDLE) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED, + "MCL is not connected"); + return FALSE; + } + + if (mcl->req != MCL_AVAILABLE) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_RESOURCE_UNAVAILABLE, + "Pending request"); + return FALSE; + } + + if (!(mcl->ctrl & MCAP_CTRL_STD_OP)) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_REQUEST_NOT_SUPPORTED, + "Remote does not support standard opcodes"); + return FALSE; + } + + if (mcl->state == MCL_PENDING) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_OPERATION, + "Not Std Op. Codes can be sent in PENDING State"); + return FALSE; + } + + if (mcap_send_data(g_io_channel_unix_get_fd(mcl->cc), cmd, size) < 0) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED, + "Command can't be sent, write error"); + return FALSE; + } + + mcl->lcmd = cmd; + mcl->req = MCL_WAITING_RSP; + + return TRUE; +} + +static void update_mcl_state(struct mcap_mcl *mcl) +{ + GSList *l; + struct mcap_mdl *mdl; + + if (mcl->state == MCL_PENDING) + return; + + for (l = mcl->mdls; l; l = l->next) { + mdl = l->data; + + if (mdl->state == MDL_CONNECTED) { + mcl->state = MCL_ACTIVE; + return; + } + } + + mcl->state = MCL_CONNECTED; +} + +static void shutdown_mdl(struct mcap_mdl *mdl) +{ + mdl->state = MDL_CLOSED; + + if (mdl->wid) { + g_source_remove(mdl->wid); + mdl->wid = 0; + } + + if (mdl->dc) { + g_io_channel_shutdown(mdl->dc, TRUE, NULL); + g_io_channel_unref(mdl->dc); + mdl->dc = NULL; + } +} + +static void free_mdl(struct mcap_mdl *mdl) +{ + if (!mdl) + return; + + mcap_mcl_unref(mdl->mcl); + g_free(mdl); +} + +static int cmp_mdl_state(gconstpointer a, gconstpointer b) +{ + const struct mcap_mdl *mdl = a; + const MDLState *st = b; + + if (mdl->state == *st) + return 0; + else if (mdl->state < *st) + return -1; + else + return 1; +} + +static void free_mcap_mdl_op(struct mcap_mdl_op_cb *op) +{ + if (op->destroy) + op->destroy(op->user_data); + + if (op->mdl) + mcap_mdl_unref(op->mdl); + + g_free(op); +} + +static void free_mcl_priv_data(struct mcap_mcl *mcl) +{ + free_mcap_mdl_op(mcl->priv_data); + mcl->priv_data = NULL; +} + +static void mcap_notify_error(struct mcap_mcl *mcl, GError *err) +{ + struct mcap_mdl_op_cb *con = mcl->priv_data; + struct mcap_mdl *mdl; + MDLState st; + GSList *l; + + if (!con || !mcl->lcmd) + return; + + switch (mcl->lcmd[0]) { + case MCAP_MD_CREATE_MDL_REQ: + st = MDL_WAITING; + l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state); + mdl = l->data; + mcl->mdls = g_slist_remove(mcl->mdls, mdl); + mcap_mdl_unref(mdl); + update_mcl_state(mcl); + con->cb.op_conf(NULL, 0, err, con->user_data); + break; + case MCAP_MD_ABORT_MDL_REQ: + st = MDL_WAITING; + l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state); + shutdown_mdl(l->data); + update_mcl_state(mcl); + con->cb.notify(err, con->user_data); + break; + case MCAP_MD_DELETE_MDL_REQ: + for (l = mcl->mdls; l; l = l->next) { + mdl = l->data; + if (mdl->state == MDL_DELETING) + mdl->state = (mdl->dc) ? MDL_CONNECTED : + MDL_CLOSED; + } + update_mcl_state(mcl); + con->cb.notify(err, con->user_data); + break; + case MCAP_MD_RECONNECT_MDL_REQ: + st = MDL_WAITING; + l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state); + shutdown_mdl(l->data); + update_mcl_state(mcl); + con->cb.op(NULL, err, con->user_data); + break; + } + + free_mcl_priv_data(mcl); + g_free(mcl->lcmd); + mcl->lcmd = NULL; +} + +int mcap_send_data(int sock, const void *buf, uint32_t size) +{ + const uint8_t *buf_b = buf; + uint32_t sent = 0; + + while (sent < size) { + int n = write(sock, buf_b + sent, size - sent); + if (n < 0) + return -1; + sent += n; + } + + return 0; +} + +static int mcap_send_cmd(struct mcap_mcl *mcl, uint8_t oc, uint8_t rc, + uint16_t mdl, uint8_t *data, size_t len) +{ + mcap_rsp *cmd; + int sock, sent; + + if (mcl->cc == NULL) + return -1; + + sock = g_io_channel_unix_get_fd(mcl->cc); + + cmd = g_malloc(sizeof(mcap_rsp) + len); + cmd->op = oc; + cmd->rc = rc; + cmd->mdl = htons(mdl); + + if (data && len > 0) + memcpy(cmd->data, data, len); + + sent = mcap_send_data(sock, cmd, sizeof(mcap_rsp) + len); + g_free(cmd); + + return sent; +} + +static struct mcap_mdl *get_mdl(struct mcap_mcl *mcl, uint16_t mdlid) +{ + GSList *l; + struct mcap_mdl *mdl; + + for (l = mcl->mdls; l; l = l->next) { + mdl = l->data; + if (mdlid == mdl->mdlid) + return mdl; + } + + return NULL; +} + +static uint16_t generate_mdlid(struct mcap_mcl *mcl) +{ + uint16_t mdlid = mcl->next_mdl; + struct mcap_mdl *mdl; + + do { + mdl = get_mdl(mcl, mdlid); + if (!mdl) { + mcl->next_mdl = (mdlid % MCAP_MDLID_FINAL) + 1; + return mdlid; + } else + mdlid = (mdlid % MCAP_MDLID_FINAL) + 1; + } while (mdlid != mcl->next_mdl); + + /* No more mdlids availables */ + return 0; +} + +static mcap_md_req *create_req(uint8_t op, uint16_t mdl_id) +{ + mcap_md_req *req_cmd; + + req_cmd = g_new0(mcap_md_req, 1); + + req_cmd->op = op; + req_cmd->mdl = htons(mdl_id); + + return req_cmd; +} + +static mcap_md_create_mdl_req *create_mdl_req(uint16_t mdl_id, uint8_t mdep, + uint8_t conf) +{ + mcap_md_create_mdl_req *req_mdl; + + req_mdl = g_new0(mcap_md_create_mdl_req, 1); + + req_mdl->op = MCAP_MD_CREATE_MDL_REQ; + req_mdl->mdl = htons(mdl_id); + req_mdl->mdep = mdep; + req_mdl->conf = conf; + + return req_mdl; +} + +static int compare_mdl(gconstpointer a, gconstpointer b) +{ + const struct mcap_mdl *mdla = a; + const struct mcap_mdl *mdlb = b; + + if (mdla->mdlid == mdlb->mdlid) + return 0; + else if (mdla->mdlid < mdlb->mdlid) + return -1; + else + return 1; +} + +static gboolean wait_response_timer(gpointer data) +{ + struct mcap_mcl *mcl = data; + + GError *gerr = NULL; + + RELEASE_TIMER(mcl); + + g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_FAILED, + "Timeout waiting response"); + + mcap_notify_error(mcl, gerr); + + g_error_free(gerr); + mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data); + mcap_cache_mcl(mcl); + + return FALSE; +} + +gboolean mcap_create_mdl(struct mcap_mcl *mcl, + uint8_t mdepid, + uint8_t conf, + mcap_mdl_operation_conf_cb connect_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err) +{ + struct mcap_mdl *mdl; + struct mcap_mdl_op_cb *con; + mcap_md_create_mdl_req *cmd; + uint16_t id; + + id = generate_mdlid(mcl); + if (!id) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED, + "Not more mdlids available"); + return FALSE; + } + + mdl = g_new0(struct mcap_mdl, 1); + mdl->mcl = mcap_mcl_ref(mcl); + mdl->mdlid = id; + mdl->mdep_id = mdepid; + mdl->state = MDL_WAITING; + + con = g_new0(struct mcap_mdl_op_cb, 1); + con->mdl = mcap_mdl_ref(mdl); + con->cb.op_conf = connect_cb; + con->destroy = destroy; + con->user_data = user_data; + + cmd = create_mdl_req(id, mdepid, conf); + if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_create_mdl_req), + err)) { + mcap_mdl_unref(con->mdl); + g_free(con); + g_free(cmd); + return FALSE; + } + + mcl->state = MCL_ACTIVE; + mcl->priv_data = con; + + mcl->mdls = g_slist_insert_sorted(mcl->mdls, mcap_mdl_ref(mdl), + compare_mdl); + mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer, + mcl); + return TRUE; +} + +gboolean mcap_reconnect_mdl(struct mcap_mdl *mdl, + mcap_mdl_operation_cb reconnect_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err) +{ + struct mcap_mdl_op_cb *con; + struct mcap_mcl *mcl = mdl->mcl; + mcap_md_req *cmd; + + if (mdl->state != MDL_CLOSED) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED, + "MDL is not closed"); + return FALSE; + } + + cmd = create_req(MCAP_MD_RECONNECT_MDL_REQ, mdl->mdlid); + if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) { + g_free(cmd); + return FALSE; + } + + mdl->state = MDL_WAITING; + + con = g_new0(struct mcap_mdl_op_cb, 1); + con->mdl = mcap_mdl_ref(mdl); + con->cb.op = reconnect_cb; + con->destroy = destroy; + con->user_data = user_data; + + mcl->state = MCL_ACTIVE; + mcl->priv_data = con; + + mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer, + mcl); + return TRUE; +} + +static gboolean send_delete_req(struct mcap_mcl *mcl, + struct mcap_mdl_op_cb *con, + uint16_t mdlid, + GError **err) +{ + mcap_md_req *cmd; + + cmd = create_req(MCAP_MD_DELETE_MDL_REQ, mdlid); + if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) { + g_free(cmd); + return FALSE; + } + + mcl->priv_data = con; + + mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer, + mcl); + return TRUE; +} + +gboolean mcap_delete_all_mdls(struct mcap_mcl *mcl, + mcap_mdl_notify_cb delete_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err) +{ + GSList *l; + struct mcap_mdl *mdl; + struct mcap_mdl_op_cb *con; + + DBG("MCL in state: %d", mcl->state); + if (!mcl->mdls) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED, + "There are not MDLs created"); + return FALSE; + } + + for (l = mcl->mdls; l; l = l->next) { + mdl = l->data; + if (mdl->state != MDL_WAITING) + mdl->state = MDL_DELETING; + } + + con = g_new0(struct mcap_mdl_op_cb, 1); + con->mdl = NULL; + con->cb.notify = delete_cb; + con->destroy = destroy; + con->user_data = user_data; + + + if (!send_delete_req(mcl, con, MCAP_ALL_MDLIDS, err)) { + g_free(con); + return FALSE; + } + + return TRUE; +} + +gboolean mcap_delete_mdl(struct mcap_mdl *mdl, mcap_mdl_notify_cb delete_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err) +{ + struct mcap_mcl *mcl= mdl->mcl; + struct mcap_mdl_op_cb *con; + GSList *l; + + l = g_slist_find(mcl->mdls, mdl); + + if (!l) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_MDL, + "%s" , error2str(MCAP_INVALID_MDEP)); + return FALSE; + } + + if (mdl->state == MDL_WAITING) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED, + "Mdl is not created"); + return FALSE; + } + + mdl->state = MDL_DELETING; + + con = g_new0(struct mcap_mdl_op_cb, 1); + con->mdl = mcap_mdl_ref(mdl); + con->cb.notify = delete_cb; + con->destroy = destroy; + con->user_data = user_data; + + if (!send_delete_req(mcl, con, mdl->mdlid, err)) { + mcap_mdl_unref(con->mdl); + g_free(con); + return FALSE; + } + + return TRUE; +} + +gboolean mcap_mdl_abort(struct mcap_mdl *mdl, mcap_mdl_notify_cb abort_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err) +{ + struct mcap_mdl_op_cb *con; + struct mcap_mcl *mcl = mdl->mcl; + mcap_md_req *cmd; + + if (mdl->state != MDL_WAITING) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED, + "Mdl in invalid state"); + return FALSE; + } + + cmd = create_req(MCAP_MD_ABORT_MDL_REQ, mdl->mdlid); + if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) { + g_free(cmd); + return FALSE; + } + + con = g_new0(struct mcap_mdl_op_cb, 1); + con->mdl = mcap_mdl_ref(mdl); + con->cb.notify = abort_cb; + con->destroy = destroy; + con->user_data = user_data; + + mcl->priv_data = con; + mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer, + mcl); + return TRUE; +} + +static struct mcap_mcl *find_mcl(GSList *list, const bdaddr_t *addr) +{ + struct mcap_mcl *mcl; + + for (; list; list = list->next) { + mcl = list->data; + + if (!bacmp(&mcl->addr, addr)) + return mcl; + } + + return NULL; +} + +int mcap_mdl_get_fd(struct mcap_mdl *mdl) +{ + if (!mdl || mdl->state != MDL_CONNECTED) + return -ENOTCONN; + + return g_io_channel_unix_get_fd(mdl->dc); +} + +uint16_t mcap_mdl_get_mdlid(struct mcap_mdl *mdl) +{ + if (!mdl) + return MCAP_MDLID_RESERVED; + + return mdl->mdlid; +} + +static void shutdown_mdl_cb(void *data, void *user_data) +{ + shutdown_mdl(data); +} + +static void mdl_unref_cb(void *data, void *user_data) +{ + mcap_mdl_unref(data); +} + +static void close_mcl(struct mcap_mcl *mcl, gboolean cache_requested) +{ + gboolean save = ((!(mcl->ctrl & MCAP_CTRL_FREE)) && cache_requested); + + RELEASE_TIMER(mcl); + + if (mcl->cc) { + g_io_channel_shutdown(mcl->cc, TRUE, NULL); + g_io_channel_unref(mcl->cc); + mcl->cc = NULL; + } + + if (mcl->wid) { + g_source_remove(mcl->wid); + mcl->wid = 0; + } + + if (mcl->lcmd) { + g_free(mcl->lcmd); + mcl->lcmd = NULL; + } + + if (mcl->priv_data) + free_mcl_priv_data(mcl); + + g_slist_foreach(mcl->mdls, shutdown_mdl_cb, NULL); + + mcap_sync_stop(mcl); + + mcl->state = MCL_IDLE; + + if (save) + return; + + g_slist_foreach(mcl->mdls, mdl_unref_cb, NULL); + g_slist_free(mcl->mdls); + mcl->mdls = NULL; +} + +static void mcap_mcl_shutdown(struct mcap_mcl *mcl) +{ + close_mcl(mcl, TRUE); +} + +static void mcap_mcl_release(struct mcap_mcl *mcl) +{ + close_mcl(mcl, FALSE); +} + +static void mcap_cache_mcl(struct mcap_mcl *mcl) +{ + GSList *l; + struct mcap_mcl *last; + int len; + + if (mcl->ctrl & MCAP_CTRL_CACHED) + return; + + mcl->mi->mcls = g_slist_remove(mcl->mi->mcls, mcl); + + if (mcl->ctrl & MCAP_CTRL_NOCACHE) { + mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl); + mcap_mcl_release(mcl); + mcap_mcl_unref(mcl); + return; + } + + DBG("Caching MCL"); + + len = g_slist_length(mcl->mi->cached); + if (len == MAX_CACHED) { + /* Remove the latest cached mcl */ + l = g_slist_last(mcl->mi->cached); + last = l->data; + mcl->mi->cached = g_slist_remove(mcl->mi->cached, last); + last->ctrl &= ~MCAP_CTRL_CACHED; + if (last->ctrl & MCAP_CTRL_CONN) { + /* + * We have to release this MCL if connection is not + * successful + */ + last->ctrl |= MCAP_CTRL_FREE; + } else { + mcap_mcl_release(last); + last->mi->mcl_uncached_cb(last, last->mi->user_data); + } + mcap_mcl_unref(last); + } + + mcl->mi->cached = g_slist_prepend(mcl->mi->cached, mcl); + mcl->ctrl |= MCAP_CTRL_CACHED; + mcap_mcl_shutdown(mcl); +} + +static void mcap_uncache_mcl(struct mcap_mcl *mcl) +{ + if (!(mcl->ctrl & MCAP_CTRL_CACHED)) + return; + + DBG("Got MCL from cache"); + + mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl); + mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls, mcl); + mcl->ctrl &= ~MCAP_CTRL_CACHED; + mcl->ctrl &= ~MCAP_CTRL_FREE; +} + +void mcap_close_mcl(struct mcap_mcl *mcl, gboolean cache) +{ + if (!mcl) + return; + + if (mcl->ctrl & MCAP_CTRL_FREE) { + mcap_mcl_release(mcl); + return; + } + + if (!cache) + mcl->ctrl |= MCAP_CTRL_NOCACHE; + + if (mcl->cc) { + g_io_channel_shutdown(mcl->cc, TRUE, NULL); + g_io_channel_unref(mcl->cc); + mcl->cc = NULL; + mcl->state = MCL_IDLE; + } else if ((mcl->ctrl & MCAP_CTRL_CACHED) && + (mcl->ctrl & MCAP_CTRL_NOCACHE)) { + mcl->ctrl &= ~MCAP_CTRL_CACHED; + mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl); + mcap_mcl_release(mcl); + mcap_mcl_unref(mcl); + } +} + +struct mcap_mcl *mcap_mcl_ref(struct mcap_mcl *mcl) +{ + mcl->ref++; + + DBG("mcap_mcl_ref(%p): ref=%d", mcl, mcl->ref); + + return mcl; +} + +void mcap_mcl_unref(struct mcap_mcl *mcl) +{ + mcl->ref--; + + DBG("mcap_mcl_unref(%p): ref=%d", mcl, mcl->ref); + + if (mcl->ref > 0) + return; + + mcap_mcl_release(mcl); + mcap_instance_unref(mcl->mi); + g_free(mcl->cb); + g_free(mcl); +} + +static gboolean parse_set_opts(struct mcap_mdl_cb *mdl_cb, GError **err, + McapMclCb cb1, va_list args) +{ + McapMclCb cb = cb1; + struct mcap_mdl_cb *c; + + c = g_new0(struct mcap_mdl_cb, 1); + + while (cb != MCAP_MDL_CB_INVALID) { + switch (cb) { + case MCAP_MDL_CB_CONNECTED: + c->mdl_connected = va_arg(args, mcap_mdl_event_cb); + break; + case MCAP_MDL_CB_CLOSED: + c->mdl_closed = va_arg(args, mcap_mdl_event_cb); + break; + case MCAP_MDL_CB_DELETED: + c->mdl_deleted = va_arg(args, mcap_mdl_event_cb); + break; + case MCAP_MDL_CB_ABORTED: + c->mdl_aborted = va_arg(args, mcap_mdl_event_cb); + break; + case MCAP_MDL_CB_REMOTE_CONN_REQ: + c->mdl_conn_req = va_arg(args, + mcap_remote_mdl_conn_req_cb); + break; + case MCAP_MDL_CB_REMOTE_RECONN_REQ: + c->mdl_reconn_req = va_arg(args, + mcap_remote_mdl_reconn_req_cb); + break; + case MCAP_MDL_CB_INVALID: + default: + g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS, + "Unknown option %d", cb); + g_free(c); + return FALSE; + } + cb = va_arg(args, int); + } + + /* Set new callbacks */ + if (c->mdl_connected) + mdl_cb->mdl_connected = c->mdl_connected; + if (c->mdl_closed) + mdl_cb->mdl_closed = c->mdl_closed; + if (c->mdl_deleted) + mdl_cb->mdl_deleted = c->mdl_deleted; + if (c->mdl_aborted) + mdl_cb->mdl_aborted = c->mdl_aborted; + if (c->mdl_conn_req) + mdl_cb->mdl_conn_req = c->mdl_conn_req; + if (c->mdl_reconn_req) + mdl_cb->mdl_reconn_req = c->mdl_reconn_req; + + g_free(c); + + return TRUE; +} + +gboolean mcap_mcl_set_cb(struct mcap_mcl *mcl, gpointer user_data, + GError **gerr, McapMclCb cb1, ...) +{ + va_list args; + gboolean ret; + + va_start(args, cb1); + ret = parse_set_opts(mcl->cb, gerr, cb1, args); + va_end(args); + + if (!ret) + return FALSE; + + mcl->cb->user_data = user_data; + return TRUE; +} + +void mcap_mcl_get_addr(struct mcap_mcl *mcl, bdaddr_t *addr) +{ + bacpy(addr, &mcl->addr); +} + +static void mcap_del_mdl(gpointer elem, gpointer user_data) +{ + struct mcap_mdl *mdl = elem; + gboolean notify = *(gboolean *) user_data; + + if (notify) + mdl->mcl->cb->mdl_deleted(mdl, mdl->mcl->cb->user_data); + + shutdown_mdl(mdl); + mcap_mdl_unref(mdl); +} + +static gboolean check_cmd_req_length(struct mcap_mcl *mcl, void *cmd, + uint32_t rlen, uint32_t explen, uint8_t rspcod) +{ + mcap_md_req *req; + uint16_t mdl_id; + + if (rlen != explen) { + if (rlen >= sizeof(mcap_md_req)) { + req = cmd; + mdl_id = ntohs(req->mdl); + } else { + /* We can't get mdlid */ + mdl_id = MCAP_MDLID_RESERVED; + } + mcap_send_cmd(mcl, rspcod, MCAP_INVALID_PARAM_VALUE, mdl_id, + NULL, 0); + return FALSE; + } + return TRUE; +} + +static void process_md_create_mdl_req(struct mcap_mcl *mcl, void *cmd, + uint32_t len) +{ + mcap_md_create_mdl_req *req; + struct mcap_mdl *mdl; + uint16_t mdl_id; + uint8_t mdep_id; + uint8_t cfga, conf; + uint8_t rsp; + + if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_create_mdl_req), + MCAP_MD_CREATE_MDL_RSP)) + return; + + req = cmd; + mdl_id = ntohs(req->mdl); + if (mdl_id < MCAP_MDLID_INITIAL || mdl_id > MCAP_MDLID_FINAL) { + mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_INVALID_MDL, + mdl_id, NULL, 0); + return; + } + + mdep_id = req->mdep; + if (mdep_id > MCAP_MDEPID_FINAL) { + mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_INVALID_MDEP, + mdl_id, NULL, 0); + return; + } + + mdl = get_mdl(mcl, mdl_id); + if (mdl && (mdl->state == MDL_WAITING || mdl->state == MDL_DELETING )) { + /* + * Creation request arrives for a MDL that is being managed + * at current moment + */ + mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_MDL_BUSY, + mdl_id, NULL, 0); + return; + } + + cfga = conf = req->conf; + /* Callback to upper layer */ + rsp = mcl->cb->mdl_conn_req(mcl, mdep_id, mdl_id, &conf, + mcl->cb->user_data); + if (mcl->state == MCL_IDLE) { + /* MCL has been closed int the callback */ + return; + } + + if (cfga != 0 && cfga != conf) { + /* + * Remote device set default configuration but upper profile + * has changed it. Protocol Error: force closing the MCL by + * remote device using UNSPECIFIED_ERROR response + */ + mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, + MCAP_UNSPECIFIED_ERROR, mdl_id, NULL, 0); + return; + } + if (rsp != MCAP_SUCCESS) { + mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, rsp, mdl_id, + NULL, 0); + return; + } + + if (!mdl) { + mdl = g_new0(struct mcap_mdl, 1); + mdl->mcl = mcap_mcl_ref(mcl); + mdl->mdlid = mdl_id; + mcl->mdls = g_slist_insert_sorted(mcl->mdls, mcap_mdl_ref(mdl), + compare_mdl); + } else if (mdl->state == MDL_CONNECTED) { + /* + * MCAP specification says that we should close the MCL if + * it is open when we receive a MD_CREATE_MDL_REQ + */ + shutdown_mdl(mdl); + } + + mdl->mdep_id = mdep_id; + mdl->state = MDL_WAITING; + + mcl->state = MCL_PENDING; + mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_SUCCESS, mdl_id, + &conf, 1); +} + +static void process_md_reconnect_mdl_req(struct mcap_mcl *mcl, void *cmd, + uint32_t len) +{ + mcap_md_req *req; + struct mcap_mdl *mdl; + uint16_t mdl_id; + uint8_t rsp; + + if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req), + MCAP_MD_RECONNECT_MDL_RSP)) + return; + + req = cmd; + mdl_id = ntohs(req->mdl); + + mdl = get_mdl(mcl, mdl_id); + if (!mdl) { + mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_INVALID_MDL, + mdl_id, NULL, 0); + return; + } else if (mdl->state == MDL_WAITING || mdl->state == MDL_DELETING ) { + /* + * Creation request arrives for a MDL that is being managed + * at current moment + */ + mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_MDL_BUSY, + mdl_id, NULL, 0); + return; + } + + /* Callback to upper layer */ + rsp = mcl->cb->mdl_reconn_req(mdl, mcl->cb->user_data); + if (mcl->state == MCL_IDLE) + return; + + if (rsp != MCAP_SUCCESS) { + mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, rsp, mdl_id, + NULL, 0); + return; + } + + if (mdl->state == MDL_CONNECTED) + shutdown_mdl(mdl); + + mdl->state = MDL_WAITING; + mcl->state = MCL_PENDING; + mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_SUCCESS, mdl_id, + NULL, 0); +} + +static void process_md_abort_mdl_req(struct mcap_mcl *mcl, void *cmd, + uint32_t len) +{ + mcap_md_req *req; + GSList *l; + struct mcap_mdl *mdl, *abrt; + uint16_t mdl_id; + + if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req), + MCAP_MD_ABORT_MDL_RSP)) + return; + + req = cmd; + mdl_id = ntohs(req->mdl); + mcl->state = MCL_CONNECTED; + abrt = NULL; + for (l = mcl->mdls; l; l = l->next) { + mdl = l->data; + if (mdl_id == mdl->mdlid && mdl->state == MDL_WAITING) { + abrt = mdl; + if (mcl->state != MCL_CONNECTED) + break; + continue; + } + if (mdl->state == MDL_CONNECTED && mcl->state != MCL_ACTIVE) + mcl->state = MCL_ACTIVE; + + if (abrt && mcl->state == MCL_ACTIVE) + break; + } + + if (!abrt) { + mcap_send_cmd(mcl, MCAP_MD_ABORT_MDL_RSP, MCAP_INVALID_MDL, + mdl_id, NULL, 0); + return; + } + + mcl->cb->mdl_aborted(abrt, mcl->cb->user_data); + abrt->state = MDL_CLOSED; + mcap_send_cmd(mcl, MCAP_MD_ABORT_MDL_RSP, MCAP_SUCCESS, mdl_id, + NULL, 0); +} + +static void process_md_delete_mdl_req(struct mcap_mcl *mcl, void *cmd, + uint32_t len) +{ + mcap_md_req *req; + struct mcap_mdl *mdl, *aux; + uint16_t mdlid; + gboolean notify; + GSList *l; + + if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req), + MCAP_MD_DELETE_MDL_RSP)) + return; + + req = cmd; + mdlid = ntohs(req->mdl); + if (mdlid == MCAP_ALL_MDLIDS) { + notify = TRUE; + g_slist_foreach(mcl->mdls, mcap_del_mdl, ¬ify); + g_slist_free(mcl->mdls); + mcl->mdls = NULL; + mcl->state = MCL_CONNECTED; + goto resp; + } + + if (mdlid < MCAP_MDLID_INITIAL || mdlid > MCAP_MDLID_FINAL) { + mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_INVALID_MDL, + mdlid, NULL, 0); + return; + } + + for (l = mcl->mdls, mdl = NULL; l; l = l->next) { + aux = l->data; + if (aux->mdlid == mdlid) { + mdl = aux; + break; + } + } + + if (!mdl || mdl->state == MDL_WAITING) { + mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_INVALID_MDL, + mdlid, NULL, 0); + return; + } + + mcl->mdls = g_slist_remove(mcl->mdls, mdl); + update_mcl_state(mcl); + notify = TRUE; + mcap_del_mdl(mdl, ¬ify); + +resp: + mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_SUCCESS, mdlid, + NULL, 0); +} + +static void invalid_req_state(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + uint16_t mdlr; + + error("Invalid cmd received (op code = %d) in state %d", cmd[0], + mcl->state); + /* + * Get previously mdlid sent to generate an appropriate + * response if it is possible + */ + mdlr = len < sizeof(mcap_md_req) ? MCAP_MDLID_RESERVED : + ntohs(((mcap_md_req *) cmd)->mdl); + mcap_send_cmd(mcl, cmd[0]+1, MCAP_INVALID_OPERATION, mdlr, NULL, 0); +} + +/* Function used to process commands depending of MCL state */ +static void proc_req_connected(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + switch (cmd[0]) { + case MCAP_MD_CREATE_MDL_REQ: + process_md_create_mdl_req(mcl, cmd, len); + break; + case MCAP_MD_RECONNECT_MDL_REQ: + process_md_reconnect_mdl_req(mcl, cmd, len); + break; + case MCAP_MD_DELETE_MDL_REQ: + process_md_delete_mdl_req(mcl, cmd, len); + break; + default: + invalid_req_state(mcl, cmd, len); + } +} + +static void proc_req_pending(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + if (cmd[0] == MCAP_MD_ABORT_MDL_REQ) + process_md_abort_mdl_req(mcl, cmd, len); + else + invalid_req_state(mcl, cmd, len); +} + +static void proc_req_active(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + switch (cmd[0]) { + case MCAP_MD_CREATE_MDL_REQ: + process_md_create_mdl_req(mcl, cmd, len); + break; + case MCAP_MD_RECONNECT_MDL_REQ: + process_md_reconnect_mdl_req(mcl, cmd, len); + break; + case MCAP_MD_DELETE_MDL_REQ: + process_md_delete_mdl_req(mcl, cmd, len); + break; + default: + invalid_req_state(mcl, cmd, len); + } +} + +/* Function used to process replies */ +static gboolean check_err_rsp(struct mcap_mcl *mcl, mcap_rsp *rsp, + uint32_t rlen, uint32_t len, GError **gerr) +{ + mcap_md_req *cmdlast = (mcap_md_req *) mcl->lcmd; + int err = MCAP_ERROR_FAILED; + gboolean close = FALSE; + char *msg; + + if (rsp->op == MCAP_ERROR_RSP) { + msg = "MCAP_ERROR_RSP received"; + close = FALSE; + goto fail; + } + + /* Check if the response matches with the last request */ + if (rlen < sizeof(mcap_rsp) || (mcl->lcmd[0] + 1) != rsp->op) { + msg = "Protocol error"; + close = FALSE; + goto fail; + } + + if (rlen < len) { + msg = "Protocol error"; + close = FALSE; + goto fail; + } + + if (rsp->mdl != cmdlast->mdl) { + msg = "MDLID received doesn't match with MDLID sent"; + close = TRUE; + goto fail; + } + + if (rsp->rc == MCAP_REQUEST_NOT_SUPPORTED) { + msg = "Remote does not support opcodes"; + mcl->ctrl &= ~MCAP_CTRL_STD_OP; + goto fail; + } + + if (rsp->rc == MCAP_UNSPECIFIED_ERROR) { + msg = "Unspecified error"; + close = TRUE; + goto fail; + } + + if (rsp->rc != MCAP_SUCCESS) { + msg = error2str(rsp->rc); + err = rsp->rc; + goto fail; + } + + return FALSE; + +fail: + g_set_error(gerr, MCAP_ERROR, err, "%s", msg); + return close; +} + +static gboolean process_md_create_mdl_rsp(struct mcap_mcl *mcl, + mcap_rsp *rsp, uint32_t len) +{ + mcap_md_create_mdl_req *cmdlast = (mcap_md_create_mdl_req *) mcl->lcmd; + struct mcap_mdl_op_cb *conn = mcl->priv_data; + mcap_mdl_operation_conf_cb connect_cb = conn->cb.op_conf; + gpointer user_data = conn->user_data; + struct mcap_mdl *mdl = conn->mdl; + uint8_t conf = cmdlast->conf; + gboolean close; + GError *gerr = NULL; + + close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp) + 1, &gerr); + g_free(mcl->lcmd); + mcl->lcmd = NULL; + mcl->req = MCL_AVAILABLE; + + if (gerr) + goto fail; + + /* Check if preferences changed */ + if (conf != 0x00 && rsp->data[0] != conf) { + g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_FAILED, + "Configuration changed"); + close = TRUE; + goto fail; + } + + connect_cb(mdl, rsp->data[0], gerr, user_data); + return close; + +fail: + connect_cb(NULL, 0, gerr, user_data); + mcl->mdls = g_slist_remove(mcl->mdls, mdl); + mcap_mdl_unref(mdl); + g_error_free(gerr); + update_mcl_state(mcl); + return close; +} + +static gboolean process_md_reconnect_mdl_rsp(struct mcap_mcl *mcl, + mcap_rsp *rsp, uint32_t len) +{ + struct mcap_mdl_op_cb *reconn = mcl->priv_data; + mcap_mdl_operation_cb reconn_cb = reconn->cb.op; + gpointer user_data = reconn->user_data; + struct mcap_mdl *mdl = reconn->mdl; + GError *gerr = NULL; + gboolean close; + + close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr); + + g_free(mcl->lcmd); + mcl->lcmd = NULL; + mcl->req = MCL_AVAILABLE; + + reconn_cb(mdl, gerr, user_data); + if (!gerr) + return close; + + g_error_free(gerr); + shutdown_mdl(mdl); + update_mcl_state(mcl); + + if (rsp->rc != MCAP_INVALID_MDL) + return close; + + /* Remove cached mdlid */ + mcl->mdls = g_slist_remove(mcl->mdls, mdl); + mcl->cb->mdl_deleted(mdl, mcl->cb->user_data); + mcap_mdl_unref(mdl); + + return close; +} + +static gboolean process_md_abort_mdl_rsp(struct mcap_mcl *mcl, + mcap_rsp *rsp, uint32_t len) +{ + struct mcap_mdl_op_cb *abrt = mcl->priv_data; + mcap_mdl_notify_cb abrt_cb = abrt->cb.notify; + gpointer user_data = abrt->user_data; + struct mcap_mdl *mdl = abrt->mdl; + GError *gerr = NULL; + gboolean close; + + close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr); + + g_free(mcl->lcmd); + mcl->lcmd = NULL; + mcl->req = MCL_AVAILABLE; + + abrt_cb(gerr, user_data); + shutdown_mdl(mdl); + + if (len >= sizeof(mcap_rsp) && rsp->rc == MCAP_INVALID_MDL) { + mcl->mdls = g_slist_remove(mcl->mdls, mdl); + mcl->cb->mdl_deleted(mdl, mcl->cb->user_data); + mcap_mdl_unref(mdl); + } + + if (gerr) + g_error_free(gerr); + + update_mcl_state(mcl); + + return close; +} + +static void restore_mdl(gpointer elem, gpointer data) +{ + struct mcap_mdl *mdl = elem; + + if (mdl->state == MDL_DELETING) { + if (mdl->dc) + mdl->state = MDL_CONNECTED; + else + mdl->state = MDL_CLOSED; + } else if (mdl->state == MDL_CLOSED) + mdl->mcl->cb->mdl_closed(mdl, mdl->mcl->cb->user_data); +} + +static void check_mdl_del_err(struct mcap_mdl *mdl, mcap_rsp *rsp) +{ + if (rsp->rc != MCAP_ERROR_INVALID_MDL) { + restore_mdl(mdl, NULL); + return; + } + + /* MDL does not exist in remote side, we can delete it */ + mdl->mcl->mdls = g_slist_remove(mdl->mcl->mdls, mdl); + mcap_mdl_unref(mdl); +} + +static gboolean process_md_delete_mdl_rsp(struct mcap_mcl *mcl, mcap_rsp *rsp, + uint32_t len) +{ + struct mcap_mdl_op_cb *del = mcl->priv_data; + struct mcap_mdl *mdl = del->mdl; + mcap_mdl_notify_cb deleted_cb = del->cb.notify; + gpointer user_data = del->user_data; + mcap_md_req *cmdlast = (mcap_md_req *) mcl->lcmd; + uint16_t mdlid = ntohs(cmdlast->mdl); + GError *gerr = NULL; + gboolean close; + gboolean notify = FALSE; + + close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr); + + g_free(mcl->lcmd); + mcl->lcmd = NULL; + mcl->req = MCL_AVAILABLE; + + if (gerr) { + if (mdl) + check_mdl_del_err(mdl, rsp); + else + g_slist_foreach(mcl->mdls, restore_mdl, NULL); + deleted_cb(gerr, user_data); + g_error_free(gerr); + return close; + } + + if (mdlid == MCAP_ALL_MDLIDS) { + g_slist_foreach(mcl->mdls, mcap_del_mdl, ¬ify); + g_slist_free(mcl->mdls); + mcl->mdls = NULL; + mcl->state = MCL_CONNECTED; + } else { + mcl->mdls = g_slist_remove(mcl->mdls, mdl); + update_mcl_state(mcl); + mcap_del_mdl(mdl, ¬ify); + } + + deleted_cb(gerr, user_data); + + return close; +} + +static void post_process_rsp(struct mcap_mcl *mcl, struct mcap_mdl_op_cb *op) +{ + if (mcl->priv_data != op) { + /* + * Queued MCAP request in some callback. + * We should not delete the mcl private data + */ + free_mcap_mdl_op(op); + } else { + /* + * This is not a queued request. It's safe + * delete the mcl private data here. + */ + free_mcl_priv_data(mcl); + } +} + +static void proc_response(struct mcap_mcl *mcl, void *buf, uint32_t len) +{ + struct mcap_mdl_op_cb *op = mcl->priv_data; + mcap_rsp *rsp = buf; + gboolean close; + + RELEASE_TIMER(mcl); + + switch (mcl->lcmd[0] + 1) { + case MCAP_MD_CREATE_MDL_RSP: + close = process_md_create_mdl_rsp(mcl, rsp, len); + post_process_rsp(mcl, op); + break; + case MCAP_MD_RECONNECT_MDL_RSP: + close = process_md_reconnect_mdl_rsp(mcl, rsp, len); + post_process_rsp(mcl, op); + break; + case MCAP_MD_ABORT_MDL_RSP: + close = process_md_abort_mdl_rsp(mcl, rsp, len); + post_process_rsp(mcl, op); + break; + case MCAP_MD_DELETE_MDL_RSP: + close = process_md_delete_mdl_rsp(mcl, rsp, len); + post_process_rsp(mcl, op); + break; + default: + DBG("Unknown cmd response received (op code = %d)", rsp->op); + close = TRUE; + break; + } + + if (close) { + mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data); + mcap_cache_mcl(mcl); + } +} + +static void proc_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + GError *gerr = NULL; + + if (cmd[0] > MCAP_MD_SYNC_INFO_IND || + (cmd[0] > MCAP_MD_DELETE_MDL_RSP && + cmd[0] < MCAP_MD_SYNC_CAP_REQ)) { + error("Unknown cmd received (op code = %d)", cmd[0]); + mcap_send_cmd(mcl, MCAP_ERROR_RSP, MCAP_INVALID_OP_CODE, + MCAP_MDLID_RESERVED, NULL, 0); + return; + } + + if (cmd[0] >= MCAP_MD_SYNC_CAP_REQ && + cmd[0] <= MCAP_MD_SYNC_INFO_IND) { + proc_sync_cmd(mcl, cmd, len); + return; + } + + if (!(mcl->ctrl & MCAP_CTRL_STD_OP)) { + /* In case the remote device doesn't work correctly */ + error("Remote device does not support opcodes, cmd ignored"); + return; + } + + if (mcl->req == MCL_WAITING_RSP) { + if (cmd[0] & 0x01) { + /* Request arrived when a response is expected */ + if (mcl->role == MCL_INITIATOR) + /* ignore */ + return; + /* Initiator will ignore our last request */ + RELEASE_TIMER(mcl); + mcl->req = MCL_AVAILABLE; + g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_REQ_IGNORED, + "Initiator sent a request with more priority"); + mcap_notify_error(mcl, gerr); + proc_req[mcl->state](mcl, cmd, len); + return; + } + proc_response(mcl, cmd, len); + } else if (cmd[0] & 0x01) + proc_req[mcl->state](mcl, cmd, len); +} + +static gboolean mdl_event_cb(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + + struct mcap_mdl *mdl = data; + gboolean notify; + + DBG("Close MDL %d", mdl->mdlid); + + notify = (mdl->state == MDL_CONNECTED); + shutdown_mdl(mdl); + + update_mcl_state(mdl->mcl); + + if (notify) { + /*Callback to upper layer */ + mdl->mcl->cb->mdl_closed(mdl, mdl->mcl->cb->user_data); + } + + return FALSE; +} + +static void mcap_connect_mdl_cb(GIOChannel *chan, GError *conn_err, + gpointer data) +{ + struct mcap_mdl_op_cb *con = data; + struct mcap_mdl *mdl = con->mdl; + mcap_mdl_operation_cb cb = con->cb.op; + gpointer user_data = con->user_data; + + DBG("mdl connect callback"); + + if (conn_err) { + DBG("ERROR: mdl connect callback"); + mdl->state = MDL_CLOSED; + g_io_channel_unref(mdl->dc); + mdl->dc = NULL; + cb(mdl, conn_err, user_data); + return; + } + + mdl->state = MDL_CONNECTED; + mdl->wid = g_io_add_watch_full(mdl->dc, G_PRIORITY_DEFAULT, + G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) mdl_event_cb, + mcap_mdl_ref(mdl), + (GDestroyNotify) mcap_mdl_unref); + + cb(mdl, conn_err, user_data); +} + +gboolean mcap_connect_mdl(struct mcap_mdl *mdl, uint8_t mode, + uint16_t dcpsm, + mcap_mdl_operation_cb connect_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err) +{ + struct mcap_mdl_op_cb *con; + + if (mdl->state != MDL_WAITING) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_MDL, + "%s", error2str(MCAP_INVALID_MDL)); + return FALSE; + } + + if ((mode != L2CAP_MODE_ERTM) && (mode != L2CAP_MODE_STREAMING)) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS, + "Invalid MDL configuration"); + return FALSE; + } + + con = g_new0(struct mcap_mdl_op_cb, 1); + con->mdl = mcap_mdl_ref(mdl); + con->cb.op = connect_cb; + con->destroy = destroy; + con->user_data = user_data; + + mdl->dc = bt_io_connect(mcap_connect_mdl_cb, con, + (GDestroyNotify) free_mcap_mdl_op, err, + BT_IO_OPT_SOURCE_BDADDR, &mdl->mcl->mi->src, + BT_IO_OPT_DEST_BDADDR, &mdl->mcl->addr, + BT_IO_OPT_PSM, dcpsm, + BT_IO_OPT_MTU, MCAP_DC_MTU, + BT_IO_OPT_SEC_LEVEL, mdl->mcl->mi->sec, + BT_IO_OPT_MODE, mode, + BT_IO_OPT_INVALID); + if (!mdl->dc) { + DBG("MDL Connection error"); + mdl->state = MDL_CLOSED; + mcap_mdl_unref(con->mdl); + g_free(con); + return FALSE; + } + + return TRUE; +} + +static gboolean mcl_control_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + GError *gerr = NULL; + struct mcap_mcl *mcl = data; + int sk, len; + uint8_t buf[MCAP_CC_MTU]; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) + goto fail; + + sk = g_io_channel_unix_get_fd(chan); + len = read(sk, buf, sizeof(buf)); + if (len < 0) + goto fail; + + proc_cmd(mcl, buf, (uint32_t) len); + return TRUE; + +fail: + if (mcl->state != MCL_IDLE) { + if (mcl->req == MCL_WAITING_RSP) { + /* notify error in pending callback */ + g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_MCL_CLOSED, + "MCL closed"); + mcap_notify_error(mcl, gerr); + g_error_free(gerr); + } + mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data); + } + mcap_cache_mcl(mcl); + return FALSE; +} + +static void mcap_connect_mcl_cb(GIOChannel *chan, GError *conn_err, + gpointer user_data) +{ + char dstaddr[18]; + struct connect_mcl *con = user_data; + struct mcap_mcl *aux, *mcl = con->mcl; + mcap_mcl_connect_cb connect_cb = con->connect_cb; + gpointer data = con->user_data; + GError *gerr = NULL; + + mcl->ctrl &= ~MCAP_CTRL_CONN; + + if (conn_err) { + if (mcl->ctrl & MCAP_CTRL_FREE) { + mcap_mcl_release(mcl); + mcl->mi->mcl_uncached_cb(mcl, mcl->mi->user_data); + } + connect_cb(NULL, conn_err, data); + return; + } + + ba2str(&mcl->addr, dstaddr); + + aux = find_mcl(mcl->mi->mcls, &mcl->addr); + if (aux) { + /* Double MCL connection case */ + error("MCL error: Device %s is already connected", dstaddr); + g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS, + "MCL %s is already connected", dstaddr); + connect_cb(NULL, gerr, data); + g_error_free(gerr); + return; + } + + mcl->state = MCL_CONNECTED; + mcl->role = MCL_INITIATOR; + mcl->req = MCL_AVAILABLE; + mcl->ctrl |= MCAP_CTRL_STD_OP; + + mcap_sync_init(mcl); + + if (mcl->ctrl & MCAP_CTRL_CACHED) + mcap_uncache_mcl(mcl); + else { + mcl->ctrl &= ~MCAP_CTRL_FREE; + mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls, + mcap_mcl_ref(mcl)); + } + + mcl->wid = g_io_add_watch_full(mcl->cc, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) mcl_control_cb, + mcap_mcl_ref(mcl), + (GDestroyNotify) mcap_mcl_unref); + connect_cb(mcl, gerr, data); +} + +static void set_mdl_properties(GIOChannel *chan, struct mcap_mdl *mdl) +{ + struct mcap_mcl *mcl = mdl->mcl; + + mdl->state = MDL_CONNECTED; + mdl->dc = g_io_channel_ref(chan); + mdl->wid = g_io_add_watch_full(mdl->dc, G_PRIORITY_DEFAULT, + G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) mdl_event_cb, + mcap_mdl_ref(mdl), + (GDestroyNotify) mcap_mdl_unref); + + mcl->state = MCL_ACTIVE; + mcl->cb->mdl_connected(mdl, mcl->cb->user_data); +} + +static void mcl_io_destroy(gpointer data) +{ + struct connect_mcl *con = data; + + mcap_mcl_unref(con->mcl); + if (con->destroy) + con->destroy(con->user_data); + g_free(con); +} + +gboolean mcap_create_mcl(struct mcap_instance *mi, + const bdaddr_t *addr, + uint16_t ccpsm, + mcap_mcl_connect_cb connect_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err) +{ + struct mcap_mcl *mcl; + struct connect_mcl *con; + + mcl = find_mcl(mi->mcls, addr); + if (mcl) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS, + "MCL is already connected."); + return FALSE; + } + + mcl = find_mcl(mi->cached, addr); + if (!mcl) { + mcl = g_new0(struct mcap_mcl, 1); + mcl->mi = mcap_instance_ref(mi); + mcl->state = MCL_IDLE; + bacpy(&mcl->addr, addr); + set_default_cb(mcl); + mcl->next_mdl = (rand() % MCAP_MDLID_FINAL) + 1; + } + + mcl->ctrl |= MCAP_CTRL_CONN; + + con = g_new0(struct connect_mcl, 1); + con->mcl = mcap_mcl_ref(mcl); + con->connect_cb = connect_cb; + con->destroy = destroy; + con->user_data = user_data; + + mcl->cc = bt_io_connect(mcap_connect_mcl_cb, con, + mcl_io_destroy, err, + BT_IO_OPT_SOURCE_BDADDR, &mi->src, + BT_IO_OPT_DEST_BDADDR, addr, + BT_IO_OPT_PSM, ccpsm, + BT_IO_OPT_MTU, MCAP_CC_MTU, + BT_IO_OPT_SEC_LEVEL, mi->sec, + BT_IO_OPT_MODE, L2CAP_MODE_ERTM, + BT_IO_OPT_INVALID); + if (!mcl->cc) { + mcl->ctrl &= ~MCAP_CTRL_CONN; + if (mcl->ctrl & MCAP_CTRL_FREE) { + mcap_mcl_release(mcl); + mcl->mi->mcl_uncached_cb(mcl, mcl->mi->user_data); + } + mcap_mcl_unref(con->mcl); + g_free(con); + return FALSE; + } + + return TRUE; +} + +static void connect_dc_event_cb(GIOChannel *chan, GError *gerr, + gpointer user_data) +{ + struct mcap_instance *mi = user_data; + struct mcap_mcl *mcl; + struct mcap_mdl *mdl; + GError *err = NULL; + bdaddr_t dst; + GSList *l; + + if (gerr) + return; + + bt_io_get(chan, &err, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + goto drop; + } + + mcl = find_mcl(mi->mcls, &dst); + if (!mcl || mcl->state != MCL_PENDING) + goto drop; + + for (l = mcl->mdls; l; l = l->next) { + mdl = l->data; + if (mdl->state == MDL_WAITING) { + set_mdl_properties(chan, mdl); + return; + } + } + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static void set_mcl_conf(GIOChannel *chan, struct mcap_mcl *mcl) +{ + gboolean reconn; + + mcl->state = MCL_CONNECTED; + mcl->role = MCL_ACCEPTOR; + mcl->req = MCL_AVAILABLE; + mcl->cc = g_io_channel_ref(chan); + mcl->ctrl |= MCAP_CTRL_STD_OP; + + mcap_sync_init(mcl); + + reconn = (mcl->ctrl & MCAP_CTRL_CACHED); + if (reconn) + mcap_uncache_mcl(mcl); + else + mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls, + mcap_mcl_ref(mcl)); + + mcl->wid = g_io_add_watch_full(mcl->cc, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) mcl_control_cb, + mcap_mcl_ref(mcl), + (GDestroyNotify) mcap_mcl_unref); + + /* Callback to report new MCL */ + if (reconn) + mcl->mi->mcl_reconnected_cb(mcl, mcl->mi->user_data); + else + mcl->mi->mcl_connected_cb(mcl, mcl->mi->user_data); +} + +static void connect_mcl_event_cb(GIOChannel *chan, GError *gerr, + gpointer user_data) +{ + struct mcap_instance *mi = user_data; + struct mcap_mcl *mcl; + bdaddr_t dst; + char address[18], srcstr[18]; + GError *err = NULL; + + if (gerr) + return; + + bt_io_get(chan, &err, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, address, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + goto drop; + } + + ba2str(&mi->src, srcstr); + mcl = find_mcl(mi->mcls, &dst); + if (mcl) { + error("Control channel already created with %s on adapter %s", + address, srcstr); + goto drop; + } + + mcl = find_mcl(mi->cached, &dst); + if (!mcl) { + mcl = g_new0(struct mcap_mcl, 1); + mcl->mi = mcap_instance_ref(mi); + bacpy(&mcl->addr, &dst); + set_default_cb(mcl); + mcl->next_mdl = (rand() % MCAP_MDLID_FINAL) + 1; + } + + set_mcl_conf(chan, mcl); + + return; +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +struct mcap_instance *mcap_create_instance(const bdaddr_t *src, + BtIOSecLevel sec, + uint16_t ccpsm, + uint16_t dcpsm, + mcap_mcl_event_cb mcl_connected, + mcap_mcl_event_cb mcl_reconnected, + mcap_mcl_event_cb mcl_disconnected, + mcap_mcl_event_cb mcl_uncached, + mcap_info_ind_event_cb mcl_sync_info_ind, + gpointer user_data, + GError **gerr) +{ + struct mcap_instance *mi; + + if (sec < BT_IO_SEC_MEDIUM) { + g_set_error(gerr, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS, + "Security level can't be minor of %d", + BT_IO_SEC_MEDIUM); + return NULL; + } + + if (!(mcl_connected && mcl_reconnected && + mcl_disconnected && mcl_uncached)) { + g_set_error(gerr, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS, + "The callbacks can't be null"); + return NULL; + } + + mi = g_new0(struct mcap_instance, 1); + + bacpy(&mi->src, src); + + mi->sec = sec; + mi->mcl_connected_cb = mcl_connected; + mi->mcl_reconnected_cb = mcl_reconnected; + mi->mcl_disconnected_cb = mcl_disconnected; + mi->mcl_uncached_cb = mcl_uncached; + mi->mcl_sync_infoind_cb = mcl_sync_info_ind; + mi->user_data = user_data; + mi->csp_enabled = FALSE; + + /* Listen incoming connections in control channel */ + mi->ccio = bt_io_listen(connect_mcl_event_cb, NULL, mi, + NULL, gerr, + BT_IO_OPT_SOURCE_BDADDR, &mi->src, + BT_IO_OPT_PSM, ccpsm, + BT_IO_OPT_MTU, MCAP_CC_MTU, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_MODE, L2CAP_MODE_ERTM, + BT_IO_OPT_INVALID); + if (!mi->ccio) { + error("%s", (*gerr)->message); + g_free(mi); + return NULL; + } + + /* Listen incoming connections in data channels */ + mi->dcio = bt_io_listen(connect_dc_event_cb, NULL, mi, + NULL, gerr, + BT_IO_OPT_SOURCE_BDADDR, &mi->src, + BT_IO_OPT_PSM, dcpsm, + BT_IO_OPT_MTU, MCAP_DC_MTU, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_INVALID); + if (!mi->dcio) { + g_io_channel_shutdown(mi->ccio, TRUE, NULL); + g_io_channel_unref(mi->ccio); + mi->ccio = NULL; + error("%s", (*gerr)->message); + g_free(mi); + return NULL; + } + + /* Initialize random seed to generate mdlids for this instance */ + srand(time(NULL)); + + return mcap_instance_ref(mi); +} + +void mcap_release_instance(struct mcap_instance *mi) +{ + GSList *l; + + if (!mi) + return; + + if (mi->ccio) { + g_io_channel_shutdown(mi->ccio, TRUE, NULL); + g_io_channel_unref(mi->ccio); + mi->ccio = NULL; + } + + if (mi->dcio) { + g_io_channel_shutdown(mi->dcio, TRUE, NULL); + g_io_channel_unref(mi->dcio); + mi->dcio = NULL; + } + + for (l = mi->mcls; l; l = l->next) { + mcap_mcl_release(l->data); + mcap_mcl_unref(l->data); + } + + g_slist_free(mi->mcls); + mi->mcls = NULL; + + for (l = mi->cached; l; l = l->next) { + mcap_mcl_release(l->data); + mcap_mcl_unref(l->data); + } + + g_slist_free(mi->cached); + mi->cached = NULL; +} + +struct mcap_instance *mcap_instance_ref(struct mcap_instance *mi) +{ + mi->ref++; + + DBG("mcap_instance_ref(%p): ref=%d", mi, mi->ref); + + return mi; +} + +void mcap_instance_unref(struct mcap_instance *mi) +{ + mi->ref--; + + DBG("mcap_instance_unref(%p): ref=%d", mi, mi->ref); + + if (mi->ref > 0) + return; + + mcap_release_instance(mi); + g_free(mi); +} + +uint16_t mcap_get_ctrl_psm(struct mcap_instance *mi, GError **err) +{ + uint16_t lpsm; + + if (!(mi && mi->ccio)) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS, + "Invalid MCAP instance"); + return 0; + } + + if (!bt_io_get(mi->ccio, err, BT_IO_OPT_PSM, &lpsm, BT_IO_OPT_INVALID)) + return 0; + + return lpsm; +} + +uint16_t mcap_get_data_psm(struct mcap_instance *mi, GError **err) +{ + uint16_t lpsm; + + if (!(mi && mi->dcio)) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS, + "Invalid MCAP instance"); + return 0; + } + + if (!bt_io_get(mi->dcio, err, BT_IO_OPT_PSM, &lpsm, BT_IO_OPT_INVALID)) + return 0; + + return lpsm; +} + +gboolean mcap_set_data_chan_mode(struct mcap_instance *mi, uint8_t mode, + GError **err) +{ + if (!(mi && mi->dcio)) { + g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS, + "Invalid MCAP instance"); + return FALSE; + } + + return bt_io_set(mi->dcio, err, BT_IO_OPT_MODE, mode, + BT_IO_OPT_INVALID); +} + +struct mcap_mdl *mcap_mdl_ref(struct mcap_mdl *mdl) +{ + mdl->ref++; + + DBG("mcap_mdl_ref(%p): ref=%d", mdl, mdl->ref); + + return mdl; +} + +void mcap_mdl_unref(struct mcap_mdl *mdl) +{ + mdl->ref--; + + DBG("mcap_mdl_unref(%p): ref=%d", mdl, mdl->ref); + + if (mdl->ref > 0) + return; + + free_mdl(mdl); +} + + +static int send_sync_cmd(struct mcap_mcl *mcl, const void *buf, uint32_t size) +{ + int sock; + + if (mcl->cc == NULL) + return -1; + + sock = g_io_channel_unix_get_fd(mcl->cc); + return mcap_send_data(sock, buf, size); +} + +static int send_unsupported_cap_req(struct mcap_mcl *mcl) +{ + mcap_md_sync_cap_rsp *cmd; + int sent; + + cmd = g_new0(mcap_md_sync_cap_rsp, 1); + cmd->op = MCAP_MD_SYNC_CAP_RSP; + cmd->rc = MCAP_REQUEST_NOT_SUPPORTED; + + sent = send_sync_cmd(mcl, cmd, sizeof(*cmd)); + g_free(cmd); + + return sent; +} + +static int send_unsupported_set_req(struct mcap_mcl *mcl) +{ + mcap_md_sync_set_rsp *cmd; + int sent; + + cmd = g_new0(mcap_md_sync_set_rsp, 1); + cmd->op = MCAP_MD_SYNC_SET_RSP; + cmd->rc = MCAP_REQUEST_NOT_SUPPORTED; + + sent = send_sync_cmd(mcl, cmd, sizeof(*cmd)); + g_free(cmd); + + return sent; +} + +static void reset_tmstamp(struct mcap_csp *csp, struct timespec *base_time, + uint64_t new_tmstamp) +{ + csp->base_tmstamp = new_tmstamp; + if (base_time) + csp->base_time = *base_time; + else + clock_gettime(CLK, &csp->base_time); +} + +void mcap_sync_init(struct mcap_mcl *mcl) +{ + if (!mcl->mi->csp_enabled) { + mcl->csp = NULL; + return; + } + + mcl->csp = g_new0(struct mcap_csp, 1); + + mcl->csp->rem_req_acc = 10000; /* safe divisor */ + mcl->csp->set_data = NULL; + mcl->csp->csp_priv_data = NULL; + + reset_tmstamp(mcl->csp, NULL, 0); +} + +void mcap_sync_stop(struct mcap_mcl *mcl) +{ + if (!mcl->csp) + return; + + if (mcl->csp->ind_timer) + g_source_remove(mcl->csp->ind_timer); + + if (mcl->csp->set_timer) + g_source_remove(mcl->csp->set_timer); + + if (mcl->csp->set_data) + g_free(mcl->csp->set_data); + + if (mcl->csp->csp_priv_data) + g_free(mcl->csp->csp_priv_data); + + mcl->csp->ind_timer = 0; + mcl->csp->set_timer = 0; + mcl->csp->set_data = NULL; + mcl->csp->csp_priv_data = NULL; + + g_free(mcl->csp); + mcl->csp = NULL; +} + +static uint64_t time_us(struct timespec *tv) +{ + return tv->tv_sec * 1000000ll + tv->tv_nsec / 1000ll; +} + +static int64_t bt2us(int bt) +{ + return bt * 312.5; +} + +static int bt2ms(int bt) +{ + return bt * 312.5 / 1000; +} + +static int btoffset(uint32_t btclk1, uint32_t btclk2) +{ + int offset = btclk2 - btclk1; + + if (offset <= -MCAP_BTCLOCK_HALF) + offset += MCAP_BTCLOCK_FIELD; + else if (offset > MCAP_BTCLOCK_HALF) + offset -= MCAP_BTCLOCK_FIELD; + + return offset; +} + +static int btdiff(uint32_t btclk1, uint32_t btclk2) +{ + return btoffset(btclk1, btclk2); +} + +static gboolean valid_btclock(uint32_t btclk) +{ + return btclk <= MCAP_BTCLOCK_MAX; +} + +/* This call may fail; either deal with retry or use read_btclock_retry */ +static gboolean read_btclock(struct mcap_mcl *mcl, uint32_t *btclock, + uint16_t *btaccuracy) +{ + /* + * FIXME: btd_adapter_read_clock(...) always return FALSE, current + * code doesn't support CSP (Clock Synchronization Protocol). To avoid + * build dependancy on struct 'btd_adapter', removing this code. + */ + + return FALSE; +} + +static gboolean read_btclock_retry(struct mcap_mcl *mcl, uint32_t *btclock, + uint16_t *btaccuracy) +{ + int retries = 5; + + while (--retries >= 0) { + if (read_btclock(mcl, btclock, btaccuracy)) + return TRUE; + DBG("CSP: retrying to read bt clock..."); + } + + return FALSE; +} + +static gboolean get_btrole(struct mcap_mcl *mcl) +{ + int sock, flags; + socklen_t len; + + if (mcl->cc == NULL) + return -1; + + sock = g_io_channel_unix_get_fd(mcl->cc); + len = sizeof(flags); + + if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len)) + DBG("CSP: could not read role"); + + return flags & L2CAP_LM_MASTER; +} + +uint64_t mcap_get_timestamp(struct mcap_mcl *mcl, + struct timespec *given_time) +{ + struct timespec now; + uint64_t tmstamp; + + if (!mcl->csp) + return MCAP_TMSTAMP_DONTSET; + + if (given_time) + now = *given_time; + else + if (clock_gettime(CLK, &now) < 0) + return MCAP_TMSTAMP_DONTSET; + + tmstamp = time_us(&now) - time_us(&mcl->csp->base_time) + + mcl->csp->base_tmstamp; + + return tmstamp; +} + +uint32_t mcap_get_btclock(struct mcap_mcl *mcl) +{ + uint32_t btclock; + uint16_t accuracy; + + if (!mcl->csp) + return MCAP_BTCLOCK_IMMEDIATE; + + if (!read_btclock_retry(mcl, &btclock, &accuracy)) + btclock = 0xffffffff; + + return btclock; +} + +static gboolean initialize_caps(struct mcap_mcl *mcl) +{ + struct timespec t1, t2; + int latencies[SAMPLE_COUNT]; + int latency, avg, dev; + uint32_t btclock; + uint16_t btaccuracy; + int i; + int retries; + + clock_getres(CLK, &t1); + + _caps.ts_res = time_us(&t1); + if (_caps.ts_res < 1) + _caps.ts_res = 1; + + _caps.ts_acc = 20; /* ppm, estimated */ + + /* A little exercise before measuing latency */ + clock_gettime(CLK, &t1); + read_btclock_retry(mcl, &btclock, &btaccuracy); + + /* Read clock a number of times and measure latency */ + avg = 0; + i = 0; + retries = MAX_RETRIES; + while (i < SAMPLE_COUNT && retries > 0) { + clock_gettime(CLK, &t1); + if (!read_btclock(mcl, &btclock, &btaccuracy)) { + retries--; + continue; + } + clock_gettime(CLK, &t2); + + latency = time_us(&t2) - time_us(&t1); + latencies[i] = latency; + avg += latency; + i++; + } + + if (retries <= 0) + return FALSE; + + /* Calculate average and deviation */ + avg /= SAMPLE_COUNT; + dev = 0; + for (i = 0; i < SAMPLE_COUNT; ++i) + dev += abs(latencies[i] - avg); + dev /= SAMPLE_COUNT; + + /* Calculate corrected average, without 'freak' latencies */ + latency = 0; + for (i = 0; i < SAMPLE_COUNT; ++i) { + if (latencies[i] > (avg + dev * 6)) + latency += avg; + else + latency += latencies[i]; + } + latency /= SAMPLE_COUNT; + + _caps.latency = latency; + _caps.preempt_thresh = latency * 4; + _caps.syncleadtime_ms = latency * 50 / 1000; + + csp_caps_initialized = TRUE; + return TRUE; +} + +static struct csp_caps *caps(struct mcap_mcl *mcl) +{ + if (!csp_caps_initialized) + if (!initialize_caps(mcl)) { + /* Temporary failure in reading BT clock */ + return NULL; + } + + return &_caps; +} + +static int send_sync_cap_rsp(struct mcap_mcl *mcl, uint8_t rspcode, + uint8_t btclockres, uint16_t synclead, + uint16_t tmstampres, uint16_t tmstampacc) +{ + mcap_md_sync_cap_rsp *rsp; + int sent; + + rsp = g_new0(mcap_md_sync_cap_rsp, 1); + + rsp->op = MCAP_MD_SYNC_CAP_RSP; + rsp->rc = rspcode; + + rsp->btclock = btclockres; + rsp->sltime = htons(synclead); + rsp->timestnr = htons(tmstampres); + rsp->timestna = htons(tmstampacc); + + sent = send_sync_cmd(mcl, rsp, sizeof(*rsp)); + g_free(rsp); + + return sent; +} + +static void proc_sync_cap_req(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + mcap_md_sync_cap_req *req; + uint16_t required_accuracy; + uint16_t our_accuracy; + uint32_t btclock; + uint16_t btres; + + if (len != sizeof(mcap_md_sync_cap_req)) { + send_sync_cap_rsp(mcl, MCAP_INVALID_PARAM_VALUE, + 0, 0, 0, 0); + return; + } + + if (!caps(mcl)) { + send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE, + 0, 0, 0, 0); + return; + } + + req = (mcap_md_sync_cap_req *) cmd; + required_accuracy = ntohs(req->timest); + our_accuracy = caps(mcl)->ts_acc; + btres = 0; + + if (required_accuracy < our_accuracy || required_accuracy < 1) { + send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE, + 0, 0, 0, 0); + return; + } + + if (!read_btclock_retry(mcl, &btclock, &btres)) { + send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE, + 0, 0, 0, 0); + return; + } + + mcl->csp->remote_caps = 1; + mcl->csp->rem_req_acc = required_accuracy; + + send_sync_cap_rsp(mcl, MCAP_SUCCESS, btres, + caps(mcl)->syncleadtime_ms, + caps(mcl)->ts_res, our_accuracy); +} + +static int send_sync_set_rsp(struct mcap_mcl *mcl, uint8_t rspcode, + uint32_t btclock, uint64_t timestamp, + uint16_t tmstampres) +{ + mcap_md_sync_set_rsp *rsp; + int sent; + + rsp = g_new0(mcap_md_sync_set_rsp, 1); + + rsp->op = MCAP_MD_SYNC_SET_RSP; + rsp->rc = rspcode; + rsp->btclock = htonl(btclock); + rsp->timestst = hton64(timestamp); + rsp->timestsa = htons(tmstampres); + + sent = send_sync_cmd(mcl, rsp, sizeof(*rsp)); + g_free(rsp); + + return sent; +} + +static gboolean get_all_clocks(struct mcap_mcl *mcl, uint32_t *btclock, + struct timespec *base_time, + uint64_t *timestamp) +{ + int latency; + int retry = 5; + uint16_t btres; + struct timespec t0; + + if (!caps(mcl)) + return FALSE; + + latency = caps(mcl)->preempt_thresh + 1; + + while (latency > caps(mcl)->preempt_thresh && --retry >= 0) { + + if (clock_gettime(CLK, &t0) < 0) + return FALSE; + + if (!read_btclock(mcl, btclock, &btres)) + continue; + + if (clock_gettime(CLK, base_time) < 0) + return FALSE; + + /* + * Tries to detect preemption between clock_gettime + * and read_btclock by measuring transaction time + */ + latency = time_us(base_time) - time_us(&t0); + } + + if (retry < 0) + return FALSE; + + *timestamp = mcap_get_timestamp(mcl, base_time); + + return TRUE; +} + +static gboolean sync_send_indication(gpointer user_data) +{ + struct mcap_mcl *mcl; + mcap_md_sync_info_ind *cmd; + uint32_t btclock; + uint64_t tmstamp; + struct timespec base_time; + int sent; + + if (!user_data) + return FALSE; + + btclock = 0; + mcl = user_data; + + if (!caps(mcl)) + return FALSE; + + if (!get_all_clocks(mcl, &btclock, &base_time, &tmstamp)) + return FALSE; + + cmd = g_new0(mcap_md_sync_info_ind, 1); + + cmd->op = MCAP_MD_SYNC_INFO_IND; + cmd->btclock = htonl(btclock); + cmd->timestst = hton64(tmstamp); + cmd->timestsa = htons(caps(mcl)->latency); + + sent = send_sync_cmd(mcl, cmd, sizeof(*cmd)); + g_free(cmd); + + return !sent; +} + +static gboolean proc_sync_set_req_phase2(gpointer user_data) +{ + struct mcap_mcl *mcl; + struct sync_set_data *data; + uint8_t update; + uint32_t sched_btclock; + uint64_t new_tmstamp; + int ind_freq; + int role; + uint32_t btclock; + uint64_t tmstamp; + struct timespec base_time; + uint16_t tmstampacc; + gboolean reset; + int delay; + + if (!user_data) + return FALSE; + + mcl = user_data; + + if (!mcl->csp->set_data) + return FALSE; + + btclock = 0; + data = mcl->csp->set_data; + update = data->update; + sched_btclock = data->sched_btclock; + new_tmstamp = data->timestamp; + ind_freq = data->ind_freq; + role = data->role; + + if (!caps(mcl)) { + send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0); + return FALSE; + } + + if (!get_all_clocks(mcl, &btclock, &base_time, &tmstamp)) { + send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0); + return FALSE; + } + + if (get_btrole(mcl) != role) { + send_sync_set_rsp(mcl, MCAP_INVALID_OPERATION, 0, 0, 0); + return FALSE; + } + + reset = (new_tmstamp != MCAP_TMSTAMP_DONTSET); + + if (reset) { + if (sched_btclock != MCAP_BTCLOCK_IMMEDIATE) { + delay = bt2us(btdiff(sched_btclock, btclock)); + if (delay >= 0 || ((new_tmstamp - delay) > 0)) { + new_tmstamp += delay; + DBG("CSP: reset w/ delay %dus, compensated", + delay); + } else + DBG("CSP: reset w/ delay %dus, uncompensated", + delay); + } + + reset_tmstamp(mcl->csp, &base_time, new_tmstamp); + tmstamp = new_tmstamp; + } + + tmstampacc = caps(mcl)->latency + caps(mcl)->ts_acc; + + if (mcl->csp->ind_timer) { + g_source_remove(mcl->csp->ind_timer); + mcl->csp->ind_timer = 0; + } + + if (update) { + int when = ind_freq + caps(mcl)->syncleadtime_ms; + mcl->csp->ind_timer = g_timeout_add(when, + sync_send_indication, + mcl); + } + + send_sync_set_rsp(mcl, MCAP_SUCCESS, btclock, tmstamp, tmstampacc); + + /* First indication after set is immediate */ + if (update) + sync_send_indication(mcl); + + return FALSE; +} + +static void proc_sync_set_req(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + mcap_md_sync_set_req *req; + uint32_t sched_btclock, cur_btclock; + uint16_t btres; + uint8_t update; + uint64_t timestamp; + struct sync_set_data *set_data; + int phase2_delay, ind_freq, when; + + if (len != sizeof(mcap_md_sync_set_req)) { + send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0); + return; + } + + req = (mcap_md_sync_set_req *) cmd; + sched_btclock = ntohl(req->btclock); + update = req->timestui; + timestamp = ntoh64(req->timestst); + cur_btclock = 0; + + if (sched_btclock != MCAP_BTCLOCK_IMMEDIATE && + !valid_btclock(sched_btclock)) { + send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0); + return; + } + + if (update > 1) { + send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0); + return; + } + + if (!mcl->csp->remote_caps) { + /* Remote side did not ask our capabilities yet */ + send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0); + return; + } + + if (!caps(mcl)) { + send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0); + return; + } + + if (!read_btclock_retry(mcl, &cur_btclock, &btres)) { + send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0); + return; + } + + if (sched_btclock == MCAP_BTCLOCK_IMMEDIATE) + phase2_delay = 0; + else { + phase2_delay = btdiff(cur_btclock, sched_btclock); + + if (phase2_delay < 0) { + /* can not reset in the past tense */ + send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, + 0, 0, 0); + return; + } + + /* Convert to miliseconds */ + phase2_delay = bt2ms(phase2_delay); + + if (phase2_delay > 61*1000) { + /* More than 60 seconds in the future */ + send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, + 0, 0, 0); + return; + } else if (phase2_delay < caps(mcl)->latency / 1000) { + /* Too fast for us to do in time */ + send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, + 0, 0, 0); + return; + } + } + + if (update) { + /* + * Indication frequency: required accuracy divided by ours + * Converted to milisseconds + */ + ind_freq = (1000 * mcl->csp->rem_req_acc) / caps(mcl)->ts_acc; + + if (ind_freq < MAX(caps(mcl)->latency * 2 / 1000, 100)) { + /* Too frequent, we can't handle */ + send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, + 0, 0, 0); + return; + } + + DBG("CSP: indication every %dms", ind_freq); + } else + ind_freq = 0; + + if (mcl->csp->ind_timer) { + /* Old indications are no longer sent */ + g_source_remove(mcl->csp->ind_timer); + mcl->csp->ind_timer = 0; + } + + if (!mcl->csp->set_data) + mcl->csp->set_data = g_new0(struct sync_set_data, 1); + + set_data = (struct sync_set_data *) mcl->csp->set_data; + + set_data->update = update; + set_data->sched_btclock = sched_btclock; + set_data->timestamp = timestamp; + set_data->ind_freq = ind_freq; + set_data->role = get_btrole(mcl); + + /* + * TODO is there some way to schedule a call based directly on + * a BT clock value, instead of this estimation that uses + * the SO clock? + */ + + if (phase2_delay > 0) { + when = phase2_delay + caps(mcl)->syncleadtime_ms; + mcl->csp->set_timer = g_timeout_add(when, + proc_sync_set_req_phase2, + mcl); + } else + proc_sync_set_req_phase2(mcl); + + /* First indication is immediate */ + if (update) + sync_send_indication(mcl); +} + +static void proc_sync_cap_rsp(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + mcap_md_sync_cap_rsp *rsp; + uint8_t mcap_err; + uint8_t btclockres; + uint16_t synclead; + uint16_t tmstampres; + uint16_t tmstampacc; + struct mcap_sync_cap_cbdata *cbdata; + mcap_sync_cap_cb cb; + gpointer user_data; + + if (mcl->csp->csp_req != MCAP_MD_SYNC_CAP_REQ) { + DBG("CSP: got unexpected cap respose"); + return; + } + + if (!mcl->csp->csp_priv_data) { + DBG("CSP: no priv data for cap respose"); + return; + } + + cbdata = mcl->csp->csp_priv_data; + cb = cbdata->cb; + user_data = cbdata->user_data; + g_free(cbdata); + + mcl->csp->csp_priv_data = NULL; + mcl->csp->csp_req = 0; + + if (len != sizeof(mcap_md_sync_cap_rsp)) { + DBG("CSP: got corrupted cap respose"); + return; + } + + rsp = (mcap_md_sync_cap_rsp *) cmd; + mcap_err = rsp->rc; + btclockres = rsp->btclock; + synclead = ntohs(rsp->sltime); + tmstampres = ntohs(rsp->timestnr); + tmstampacc = ntohs(rsp->timestna); + + if (!mcap_err) + mcl->csp->local_caps = TRUE; + + cb(mcl, mcap_err, btclockres, synclead, tmstampres, tmstampacc, NULL, + user_data); +} + +static void proc_sync_set_rsp(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + mcap_md_sync_set_rsp *rsp; + uint8_t mcap_err; + uint32_t btclock; + uint64_t timestamp; + uint16_t accuracy; + struct mcap_sync_set_cbdata *cbdata; + mcap_sync_set_cb cb; + gpointer user_data; + + if (mcl->csp->csp_req != MCAP_MD_SYNC_SET_REQ) { + DBG("CSP: got unexpected set respose"); + return; + } + + if (!mcl->csp->csp_priv_data) { + DBG("CSP: no priv data for set respose"); + return; + } + + cbdata = mcl->csp->csp_priv_data; + cb = cbdata->cb; + user_data = cbdata->user_data; + g_free(cbdata); + + mcl->csp->csp_priv_data = NULL; + mcl->csp->csp_req = 0; + + if (len != sizeof(mcap_md_sync_set_rsp)) { + DBG("CSP: got corrupted set respose"); + return; + } + + rsp = (mcap_md_sync_set_rsp *) cmd; + mcap_err = rsp->rc; + btclock = ntohl(rsp->btclock); + timestamp = ntoh64(rsp->timestst); + accuracy = ntohs(rsp->timestsa); + + if (!mcap_err && !valid_btclock(btclock)) + mcap_err = MCAP_ERROR_INVALID_ARGS; + + cb(mcl, mcap_err, btclock, timestamp, accuracy, NULL, user_data); +} + +static void proc_sync_info_ind(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + mcap_md_sync_info_ind *req; + struct sync_info_ind_data data; + uint32_t btclock; + + if (!mcl->csp->ind_expected) { + DBG("CSP: received unexpected info indication"); + return; + } + + if (len != sizeof(mcap_md_sync_info_ind)) + return; + + req = (mcap_md_sync_info_ind *) cmd; + + btclock = ntohl(req->btclock); + + if (!valid_btclock(btclock)) + return; + + data.btclock = btclock; + data.timestamp = ntoh64(req->timestst); + data.accuracy = ntohs(req->timestsa); + + if (mcl->mi->mcl_sync_infoind_cb) + mcl->mi->mcl_sync_infoind_cb(mcl, &data); +} + +void proc_sync_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) +{ + if (!mcl->mi->csp_enabled || !mcl->csp) { + switch (cmd[0]) { + case MCAP_MD_SYNC_CAP_REQ: + send_unsupported_cap_req(mcl); + break; + case MCAP_MD_SYNC_SET_REQ: + send_unsupported_set_req(mcl); + break; + } + return; + } + + switch (cmd[0]) { + case MCAP_MD_SYNC_CAP_REQ: + proc_sync_cap_req(mcl, cmd, len); + break; + case MCAP_MD_SYNC_CAP_RSP: + proc_sync_cap_rsp(mcl, cmd, len); + break; + case MCAP_MD_SYNC_SET_REQ: + proc_sync_set_req(mcl, cmd, len); + break; + case MCAP_MD_SYNC_SET_RSP: + proc_sync_set_rsp(mcl, cmd, len); + break; + case MCAP_MD_SYNC_INFO_IND: + proc_sync_info_ind(mcl, cmd, len); + break; + } +} + +void mcap_sync_cap_req(struct mcap_mcl *mcl, uint16_t reqacc, + mcap_sync_cap_cb cb, gpointer user_data, + GError **err) +{ + struct mcap_sync_cap_cbdata *cbdata; + mcap_md_sync_cap_req *cmd; + + if (!mcl->mi->csp_enabled || !mcl->csp) { + g_set_error(err, + MCAP_CSP_ERROR, + MCAP_ERROR_RESOURCE_UNAVAILABLE, + "CSP not enabled for the instance"); + return; + } + + if (mcl->csp->csp_req) { + g_set_error(err, + MCAP_CSP_ERROR, + MCAP_ERROR_RESOURCE_UNAVAILABLE, + "Pending CSP request"); + return; + } + + mcl->csp->csp_req = MCAP_MD_SYNC_CAP_REQ; + cmd = g_new0(mcap_md_sync_cap_req, 1); + + cmd->op = MCAP_MD_SYNC_CAP_REQ; + cmd->timest = htons(reqacc); + + cbdata = g_new0(struct mcap_sync_cap_cbdata, 1); + cbdata->cb = cb; + cbdata->user_data = user_data; + mcl->csp->csp_priv_data = cbdata; + + send_sync_cmd(mcl, cmd, sizeof(*cmd)); + + g_free(cmd); +} + +void mcap_sync_set_req(struct mcap_mcl *mcl, uint8_t update, uint32_t btclock, + uint64_t timestamp, mcap_sync_set_cb cb, + gpointer user_data, GError **err) +{ + mcap_md_sync_set_req *cmd; + struct mcap_sync_set_cbdata *cbdata; + + if (!mcl->mi->csp_enabled || !mcl->csp) { + g_set_error(err, + MCAP_CSP_ERROR, + MCAP_ERROR_RESOURCE_UNAVAILABLE, + "CSP not enabled for the instance"); + return; + } + + if (!mcl->csp->local_caps) { + g_set_error(err, + MCAP_CSP_ERROR, + MCAP_ERROR_RESOURCE_UNAVAILABLE, + "Did not get CSP caps from slave yet"); + return; + } + + if (mcl->csp->csp_req) { + g_set_error(err, + MCAP_CSP_ERROR, + MCAP_ERROR_RESOURCE_UNAVAILABLE, + "Pending CSP request"); + return; + } + + mcl->csp->csp_req = MCAP_MD_SYNC_SET_REQ; + cmd = g_new0(mcap_md_sync_set_req, 1); + + cmd->op = MCAP_MD_SYNC_SET_REQ; + cmd->timestui = update; + cmd->btclock = htonl(btclock); + cmd->timestst = hton64(timestamp); + + mcl->csp->ind_expected = update; + + cbdata = g_new0(struct mcap_sync_set_cbdata, 1); + cbdata->cb = cb; + cbdata->user_data = user_data; + mcl->csp->csp_priv_data = cbdata; + + send_sync_cmd(mcl, cmd, sizeof(*cmd)); + + g_free(cmd); +} + +void mcap_enable_csp(struct mcap_instance *mi) +{ + mi->csp_enabled = TRUE; +} + +void mcap_disable_csp(struct mcap_instance *mi) +{ + mi->csp_enabled = FALSE; +} diff --git a/profiles/health/mcap.h b/profiles/health/mcap.h new file mode 100644 index 0000000..69873ca --- /dev/null +++ b/profiles/health/mcap.h @@ -0,0 +1,437 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * Copyright (C) 2010 Signove + * 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 + * + */ + +#define MCAP_VERSION 0x0100 /* current version 01.00 */ + +/* bytes to get MCAP Supported Procedures */ +#define MCAP_SUP_PROC 0x06 + +/* maximum transmission unit for channels */ +#define MCAP_CC_MTU 48 +#define MCAP_DC_MTU 65535 + +/* MCAP Standard Op Codes */ +#define MCAP_ERROR_RSP 0x00 +#define MCAP_MD_CREATE_MDL_REQ 0x01 +#define MCAP_MD_CREATE_MDL_RSP 0x02 +#define MCAP_MD_RECONNECT_MDL_REQ 0x03 +#define MCAP_MD_RECONNECT_MDL_RSP 0x04 +#define MCAP_MD_ABORT_MDL_REQ 0x05 +#define MCAP_MD_ABORT_MDL_RSP 0x06 +#define MCAP_MD_DELETE_MDL_REQ 0x07 +#define MCAP_MD_DELETE_MDL_RSP 0x08 + +/* MCAP Clock Sync Op Codes */ +#define MCAP_MD_SYNC_CAP_REQ 0x11 +#define MCAP_MD_SYNC_CAP_RSP 0x12 +#define MCAP_MD_SYNC_SET_REQ 0x13 +#define MCAP_MD_SYNC_SET_RSP 0x14 +#define MCAP_MD_SYNC_INFO_IND 0x15 + +/* MCAP Response codes */ +#define MCAP_SUCCESS 0x00 +#define MCAP_INVALID_OP_CODE 0x01 +#define MCAP_INVALID_PARAM_VALUE 0x02 +#define MCAP_INVALID_MDEP 0x03 +#define MCAP_MDEP_BUSY 0x04 +#define MCAP_INVALID_MDL 0x05 +#define MCAP_MDL_BUSY 0x06 +#define MCAP_INVALID_OPERATION 0x07 +#define MCAP_RESOURCE_UNAVAILABLE 0x08 +#define MCAP_UNSPECIFIED_ERROR 0x09 +#define MCAP_REQUEST_NOT_SUPPORTED 0x0A +#define MCAP_CONFIGURATION_REJECTED 0x0B + +/* MDL IDs */ +#define MCAP_MDLID_RESERVED 0x0000 +#define MCAP_MDLID_INITIAL 0x0001 +#define MCAP_MDLID_FINAL 0xFEFF +#define MCAP_ALL_MDLIDS 0xFFFF + +/* MDEP IDs */ +#define MCAP_MDEPID_INITIAL 0x00 +#define MCAP_MDEPID_FINAL 0x7F + +/* CSP special values */ +#define MCAP_BTCLOCK_IMMEDIATE 0xffffffffUL +#define MCAP_TMSTAMP_DONTSET 0xffffffffffffffffULL +#define MCAP_BTCLOCK_MAX 0x0fffffff +#define MCAP_BTCLOCK_FIELD (MCAP_BTCLOCK_MAX + 1) + +#define MCAP_CTRL_CACHED 0x01 /* MCL is cached */ +#define MCAP_CTRL_STD_OP 0x02 /* Support for standard op codes */ +#define MCAP_CTRL_SYNC_OP 0x04 /* Support for synchronization commands */ +#define MCAP_CTRL_CONN 0x08 /* MCL is in connecting process */ +#define MCAP_CTRL_FREE 0x10 /* MCL is marked as releasable */ +#define MCAP_CTRL_NOCACHE 0x20 /* MCL is marked as not cacheable */ + +/* + * MCAP Request Packet Format + */ + +typedef struct { + uint8_t op; + uint16_t mdl; + uint8_t mdep; + uint8_t conf; +} __attribute__ ((packed)) mcap_md_create_mdl_req; + +typedef struct { + uint8_t op; + uint16_t mdl; +} __attribute__ ((packed)) mcap_md_req; + +/* MCAP Response Packet Format */ + +typedef struct { + uint8_t op; + uint8_t rc; + uint16_t mdl; + uint8_t data[0]; +} __attribute__ ((packed)) mcap_rsp; + +/* MCAP Clock Synchronization Protocol */ + +typedef struct { + uint8_t op; + uint16_t timest; +} __attribute__ ((packed)) mcap_md_sync_cap_req; + +typedef struct { + uint8_t op; + uint8_t rc; +} __attribute__ ((packed)) mcap_md_sync_rsp; + +typedef struct { + uint8_t op; + uint8_t rc; + uint8_t btclock; + uint16_t sltime; + uint16_t timestnr; + uint16_t timestna; +} __attribute__ ((packed)) mcap_md_sync_cap_rsp; + +typedef struct { + uint8_t op; + uint8_t timestui; + uint32_t btclock; + uint64_t timestst; +} __attribute__ ((packed)) mcap_md_sync_set_req; + +typedef struct { + int8_t op; + uint8_t rc; + uint32_t btclock; + uint64_t timestst; + uint16_t timestsa; +} __attribute__ ((packed)) mcap_md_sync_set_rsp; + +typedef struct { + uint8_t op; + uint32_t btclock; + uint64_t timestst; + uint16_t timestsa; +} __attribute__ ((packed)) mcap_md_sync_info_ind; + +typedef enum { +/* MCAP Error Response Codes */ + MCAP_ERROR_INVALID_OP_CODE = 1, + MCAP_ERROR_INVALID_PARAM_VALUE, + MCAP_ERROR_INVALID_MDEP, + MCAP_ERROR_MDEP_BUSY, + MCAP_ERROR_INVALID_MDL, + MCAP_ERROR_MDL_BUSY, + MCAP_ERROR_INVALID_OPERATION, + MCAP_ERROR_RESOURCE_UNAVAILABLE, + MCAP_ERROR_UNSPECIFIED_ERROR, + MCAP_ERROR_REQUEST_NOT_SUPPORTED, + MCAP_ERROR_CONFIGURATION_REJECTED, +/* MCAP Internal Errors */ + MCAP_ERROR_INVALID_ARGS, + MCAP_ERROR_ALREADY_EXISTS, + MCAP_ERROR_REQ_IGNORED, + MCAP_ERROR_MCL_CLOSED, + MCAP_ERROR_FAILED +} McapError; + +typedef enum { + MCAP_MDL_CB_INVALID, + MCAP_MDL_CB_CONNECTED, /* mcap_mdl_event_cb */ + MCAP_MDL_CB_CLOSED, /* mcap_mdl_event_cb */ + MCAP_MDL_CB_DELETED, /* mcap_mdl_event_cb */ + MCAP_MDL_CB_ABORTED, /* mcap_mdl_event_cb */ + MCAP_MDL_CB_REMOTE_CONN_REQ, /* mcap_remote_mdl_conn_req_cb */ + MCAP_MDL_CB_REMOTE_RECONN_REQ /* mcap_remote_mdl_reconn_req_cb */ +} McapMclCb; + +typedef enum { + MCL_CONNECTED, + MCL_PENDING, + MCL_ACTIVE, + MCL_IDLE +} MCLState; + +typedef enum { + MCL_ACCEPTOR, + MCL_INITIATOR +} MCLRole; + +typedef enum { + MCL_AVAILABLE, + MCL_WAITING_RSP +} MCAPCtrl; + +typedef enum { + MDL_WAITING, + MDL_CONNECTED, + MDL_DELETING, + MDL_CLOSED +} MDLState; + +struct mcap_csp; +struct mcap_mdl_op_cb; +struct mcap_instance; +struct mcap_mcl; +struct mcap_mdl; +struct sync_info_ind_data; + +/************ Callbacks ************/ + +/* MDL callbacks */ + +typedef void (* mcap_mdl_event_cb) (struct mcap_mdl *mdl, gpointer data); +typedef void (* mcap_mdl_operation_conf_cb) (struct mcap_mdl *mdl, uint8_t conf, + GError *err, gpointer data); +typedef void (* mcap_mdl_operation_cb) (struct mcap_mdl *mdl, GError *err, + gpointer data); +typedef void (* mcap_mdl_notify_cb) (GError *err, gpointer data); + +/* Next function should return an MCAP appropriate response code */ +typedef uint8_t (* mcap_remote_mdl_conn_req_cb) (struct mcap_mcl *mcl, + uint8_t mdepid, uint16_t mdlid, + uint8_t *conf, gpointer data); +typedef uint8_t (* mcap_remote_mdl_reconn_req_cb) (struct mcap_mdl *mdl, + gpointer data); + +/* MCL callbacks */ + +typedef void (* mcap_mcl_event_cb) (struct mcap_mcl *mcl, gpointer data); +typedef void (* mcap_mcl_connect_cb) (struct mcap_mcl *mcl, GError *err, + gpointer data); + +/* CSP callbacks */ + +typedef void (* mcap_info_ind_event_cb) (struct mcap_mcl *mcl, + struct sync_info_ind_data *data); + +typedef void (* mcap_sync_cap_cb) (struct mcap_mcl *mcl, + uint8_t mcap_err, + uint8_t btclockres, + uint16_t synclead, + uint16_t tmstampres, + uint16_t tmstampacc, + GError *err, + gpointer data); + +typedef void (* mcap_sync_set_cb) (struct mcap_mcl *mcl, + uint8_t mcap_err, + uint32_t btclock, + uint64_t timestamp, + uint16_t accuracy, + GError *err, + gpointer data); + +struct mcap_mdl_cb { + mcap_mdl_event_cb mdl_connected; /* Remote device has created a MDL */ + mcap_mdl_event_cb mdl_closed; /* Remote device has closed a MDL */ + mcap_mdl_event_cb mdl_deleted; /* Remote device requested deleting a MDL */ + mcap_mdl_event_cb mdl_aborted; /* Remote device aborted the mdl creation */ + mcap_remote_mdl_conn_req_cb mdl_conn_req; /* Remote device requested creating a MDL */ + mcap_remote_mdl_reconn_req_cb mdl_reconn_req; /* Remote device requested reconnecting a MDL */ + gpointer user_data; /* User data */ +}; + +struct mcap_instance { + bdaddr_t src; /* Source address */ + GIOChannel *ccio; /* Control Channel IO */ + GIOChannel *dcio; /* Data Channel IO */ + GSList *mcls; /* MCAP instance list */ + GSList *cached; /* List with all cached MCLs (MAX_CACHED macro) */ + BtIOSecLevel sec; /* Security level */ + mcap_mcl_event_cb mcl_connected_cb; /* New MCL connected */ + mcap_mcl_event_cb mcl_reconnected_cb; /* Old MCL has been reconnected */ + mcap_mcl_event_cb mcl_disconnected_cb; /* MCL disconnected */ + mcap_mcl_event_cb mcl_uncached_cb; /* MCL has been removed from MCAP cache */ + mcap_info_ind_event_cb mcl_sync_infoind_cb; /* (CSP Master) Received info indication */ + gpointer user_data; /* Data to be provided in callbacks */ + int ref; /* Reference counter */ + + gboolean csp_enabled; /* CSP: functionality enabled */ +}; + +struct mcap_mcl { + struct mcap_instance *mi; /* MCAP instance where this MCL belongs */ + bdaddr_t addr; /* Device address */ + GIOChannel *cc; /* MCAP Control Channel IO */ + guint wid; /* MCL Watcher id */ + GSList *mdls; /* List of Data Channels shorted by mdlid */ + MCLState state; /* Current MCL State */ + MCLRole role; /* Initiator or acceptor of this MCL */ + MCAPCtrl req; /* Request control flag */ + struct mcap_mdl_op_cb *priv_data; /* Temporal data to manage responses */ + struct mcap_mdl_cb *cb; /* MDL callbacks */ + guint tid; /* Timer id for waiting for a response */ + uint8_t *lcmd; /* Last command sent */ + int ref; /* References counter */ + uint8_t ctrl; /* MCL control flag */ + uint16_t next_mdl; /* id used to create next MDL */ + struct mcap_csp *csp; /* CSP control structure */ +}; + +struct mcap_mdl { + struct mcap_mcl *mcl; /* MCL where this MDL belongs */ + GIOChannel *dc; /* MCAP Data Channel IO */ + guint wid; /* MDL Watcher id */ + uint16_t mdlid; /* MDL id */ + uint8_t mdep_id; /* MCAP Data End Point */ + MDLState state; /* MDL state */ + int ref; /* References counter */ +}; + +struct sync_info_ind_data { + uint32_t btclock; + uint64_t timestamp; + uint16_t accuracy; +}; + +/************ Operations ************/ + +/* MDL operations */ + +gboolean mcap_create_mdl(struct mcap_mcl *mcl, + uint8_t mdepid, + uint8_t conf, + mcap_mdl_operation_conf_cb connect_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err); +gboolean mcap_reconnect_mdl(struct mcap_mdl *mdl, + mcap_mdl_operation_cb reconnect_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err); +gboolean mcap_delete_all_mdls(struct mcap_mcl *mcl, + mcap_mdl_notify_cb delete_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err); +gboolean mcap_delete_mdl(struct mcap_mdl *mdl, + mcap_mdl_notify_cb delete_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err); +gboolean mcap_connect_mdl(struct mcap_mdl *mdl, + uint8_t mode, + uint16_t dcpsm, + mcap_mdl_operation_cb connect_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err); +gboolean mcap_mdl_abort(struct mcap_mdl *mdl, + mcap_mdl_notify_cb abort_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err); + +int mcap_mdl_get_fd(struct mcap_mdl *mdl); +uint16_t mcap_mdl_get_mdlid(struct mcap_mdl *mdl); +struct mcap_mdl *mcap_mdl_ref(struct mcap_mdl *mdl); +void mcap_mdl_unref(struct mcap_mdl *mdl); + +/* MCL operations */ + +gboolean mcap_create_mcl(struct mcap_instance *mi, + const bdaddr_t *addr, + uint16_t ccpsm, + mcap_mcl_connect_cb connect_cb, + gpointer user_data, + GDestroyNotify destroy, + GError **err); +void mcap_close_mcl(struct mcap_mcl *mcl, gboolean cache); +gboolean mcap_mcl_set_cb(struct mcap_mcl *mcl, gpointer user_data, + GError **gerr, McapMclCb cb1, ...); +void mcap_mcl_get_addr(struct mcap_mcl *mcl, bdaddr_t *addr); +struct mcap_mcl *mcap_mcl_ref(struct mcap_mcl *mcl); +void mcap_mcl_unref(struct mcap_mcl *mcl); + +/* CSP operations */ + +void mcap_enable_csp(struct mcap_instance *mi); +void mcap_disable_csp(struct mcap_instance *mi); +uint64_t mcap_get_timestamp(struct mcap_mcl *mcl, + struct timespec *given_time); +uint32_t mcap_get_btclock(struct mcap_mcl *mcl); + +void mcap_sync_cap_req(struct mcap_mcl *mcl, + uint16_t reqacc, + mcap_sync_cap_cb cb, + gpointer user_data, + GError **err); + +void mcap_sync_set_req(struct mcap_mcl *mcl, + uint8_t update, + uint32_t btclock, + uint64_t timestamp, + mcap_sync_set_cb cb, + gpointer user_data, + GError **err); + +/* MCAP main operations */ + +struct mcap_instance *mcap_create_instance(const bdaddr_t *src, + BtIOSecLevel sec, uint16_t ccpsm, + uint16_t dcpsm, + mcap_mcl_event_cb mcl_connected, + mcap_mcl_event_cb mcl_reconnected, + mcap_mcl_event_cb mcl_disconnected, + mcap_mcl_event_cb mcl_uncached, + mcap_info_ind_event_cb mcl_sync_info_ind, + gpointer user_data, + GError **gerr); +void mcap_release_instance(struct mcap_instance *mi); + +struct mcap_instance *mcap_instance_ref(struct mcap_instance *mi); +void mcap_instance_unref(struct mcap_instance *mi); + +uint16_t mcap_get_ctrl_psm(struct mcap_instance *mi, GError **err); +uint16_t mcap_get_data_psm(struct mcap_instance *mi, GError **err); + +gboolean mcap_set_data_chan_mode(struct mcap_instance *mi, uint8_t mode, + GError **err); + +int mcap_send_data(int sock, const void *buf, uint32_t size); + +void proc_sync_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len); +void mcap_sync_init(struct mcap_mcl *mcl); +void mcap_sync_stop(struct mcap_mcl *mcl); diff --git a/profiles/iap/main.c b/profiles/iap/main.c new file mode 100644 index 0000000..2fd34c0 --- /dev/null +++ b/profiles/iap/main.c @@ -0,0 +1,467 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" + +#define IAP_PATH "/org/bluez/iap" + +#define IAP_UUID "00000000-deca-fade-deca-deafdecacafe" + +#define IAP_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +static GMainLoop *main_loop; + +static guint iap_source = 0; + +static gboolean iap_handler(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + unsigned char buf[512]; + ssize_t len; + int fd; + + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + iap_source = 0; + return FALSE; + } + + fd = g_io_channel_unix_get_fd(channel); + + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + iap_source = 0; + return FALSE; + } + + return TRUE; +} + +static guint create_source(int fd, GIOFunc func) +{ + GIOChannel *channel; + guint source; + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + source = g_io_add_watch(channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, func, NULL); + + g_io_channel_unref(channel); + + return source; +} + +static void remove_source(const char *path) +{ + if (iap_source > 0) { + g_source_remove(iap_source); + iap_source = 0; + } +} + +static DBusMessage *release_profile(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + g_print("Profile released\n"); + + remove_source(IAP_PATH); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *new_connection(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *path, *device; + int fd; + + g_print("New connection\n"); + + path = dbus_message_get_path(msg); + + dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &device, + DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID); + + g_print(" from %s\n", path); + g_print(" for device %s with fd %d\n", device, fd); + + if (iap_source == 0) + iap_source = create_source(fd, iap_handler); + else + close(fd); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *request_disconnection(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + DBusMessageIter iter; + const char *path, *device; + + g_print("Request disconnection\n"); + + path = dbus_message_get_path(msg); + + dbus_message_iter_init(msg, &iter); + dbus_message_iter_get_basic(&iter, &device); + + g_print(" from %s\n", path); + g_print(" for device %s\n", device); + + remove_source(path); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *cancel_request(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *path; + + g_print("Request canceled\n"); + + path = dbus_message_get_path(msg); + + g_print(" from %s\n", path); + + remove_source(path); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable methods[] = { + { GDBUS_METHOD("Release", NULL, NULL, release_profile) }, + { GDBUS_METHOD("NewConnection", + GDBUS_ARGS({ "device", "o" }, + { "fd", "h"}, { "opts", "a{sv}"}), + NULL, new_connection) }, + { GDBUS_METHOD("RequestDisconnection", + GDBUS_ARGS({ "device", "o" }), + NULL, request_disconnection) }, + { GDBUS_METHOD("Cancel", NULL, NULL, cancel_request) }, + { } +}; + +static void register_profile_setup(DBusMessageIter *iter, void *user_data) +{ + const char *path = IAP_PATH; + const char *uuid = IAP_UUID; + DBusMessageIter dict, entry, value; + dbus_uint16_t channel; + char *record; + const char *str; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + str = "Role"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &value); + str = "server"; + dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &str); + dbus_message_iter_close_container(&entry, &value); + dbus_message_iter_close_container(&dict, &entry); + + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + str = "Channel"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_UINT16_AS_STRING, &value); + channel = 23; + dbus_message_iter_append_basic(&value, DBUS_TYPE_UINT16, &channel); + dbus_message_iter_close_container(&entry, &value); + dbus_message_iter_close_container(&dict, &entry); + + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, + NULL, &entry); + str = "ServiceRecord"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &str); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &value); + record = g_strdup_printf(IAP_RECORD, IAP_UUID, channel); + dbus_message_iter_append_basic(&value, DBUS_TYPE_STRING, &record); + g_free(record); + dbus_message_iter_close_container(&entry, &value); + dbus_message_iter_close_container(&dict, &entry); + + dbus_message_iter_close_container(iter, &dict); +} + +static void register_profile_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + g_print("Failed to register profile\n"); + return; + } + + g_print("Profile registered\n"); +} + +static void connect_handler(DBusConnection *connection, void *user_data) +{ + GDBusClient *client = user_data; + GDBusProxy *proxy; + + g_print("Bluetooth connected\n"); + + proxy = g_dbus_proxy_new(client, "/org/bluez", + "org.bluez.ProfileManager1"); + if (!proxy) + return; + + g_dbus_register_interface(connection, IAP_PATH, + "org.bluez.Profile1", + methods, NULL, NULL, NULL, NULL); + + g_dbus_proxy_method_call(proxy, "RegisterProfile", + register_profile_setup, + register_profile_reply, NULL, NULL); + + g_dbus_proxy_unref(proxy); +} + +static void disconnect_handler(DBusConnection *connection, void *user_data) +{ + g_print("Bluetooth disconnected\n"); + + g_dbus_unregister_interface(connection, IAP_PATH, + "org.bluez.Profile1"); +} + +static gboolean signal_handler(GIOChannel *channel, GIOCondition condition, + gpointer user_data) +{ + static unsigned int __terminated = 0; + struct signalfd_siginfo si; + ssize_t result; + int fd; + + if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) { + g_main_loop_quit(main_loop); + return FALSE; + } + + fd = g_io_channel_unix_get_fd(channel); + + result = read(fd, &si, sizeof(si)); + if (result != sizeof(si)) + return FALSE; + + switch (si.ssi_signo) { + case SIGINT: + case SIGTERM: + if (__terminated == 0) + g_main_loop_quit(main_loop); + + __terminated = 1; + break; + } + + return TRUE; +} + +static guint setup_signalfd(void) +{ + GIOChannel *channel; + guint source; + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + perror("Failed to set signal mask"); + return 0; + } + + fd = signalfd(-1, &mask, 0); + if (fd < 0) { + perror("Failed to create signal descriptor"); + return 0; + } + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + source = g_io_add_watch(channel, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + signal_handler, NULL); + + g_io_channel_unref(channel); + + return source; +} + +static gboolean option_version = FALSE; + +static GOptionEntry options[] = { + { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, + "Show version information and exit" }, + { NULL }, +}; + +int main(int argc, char *argv[]) +{ + GOptionContext *context; + GError *error = NULL; + DBusConnection *dbus_conn; + GDBusClient *client; + guint signal; + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + if (g_option_context_parse(context, &argc, &argv, &error) == FALSE) { + if (error != NULL) { + g_printerr("%s\n", error->message); + g_error_free(error); + } else + g_printerr("An unknown error occurred\n"); + exit(1); + } + + g_option_context_free(context); + + if (option_version == TRUE) { + g_print("%s\n", VERSION); + exit(0); + } + + main_loop = g_main_loop_new(NULL, FALSE); + dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); + + signal = setup_signalfd(); + + client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez"); + + g_dbus_client_set_connect_watch(client, connect_handler, client); + g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL); + + g_main_loop_run(main_loop); + + g_dbus_client_unref(client); + + g_source_remove(signal); + + dbus_connection_unref(dbus_conn); + g_main_loop_unref(main_loop); + + return 0; +} diff --git a/profiles/input/device.c b/profiles/input/device.c new file mode 100644 index 0000000..a711ef5 --- /dev/null +++ b/profiles/input/device.c @@ -0,0 +1,1501 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2014 Google Inc. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hidp.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "btio/btio.h" +#include "src/log.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/storage.h" +#include "src/dbus-common.h" +#include "src/error.h" +#include "src/sdp-client.h" +#include "src/shared/uhid.h" + +#include "device.h" +#include "hidp_defs.h" + +#define INPUT_INTERFACE "org.bluez.Input1" + +enum reconnect_mode_t { + RECONNECT_NONE = 0, + RECONNECT_DEVICE, + RECONNECT_HOST, + RECONNECT_ANY +}; + +struct input_device { + struct btd_service *service; + struct btd_device *device; + char *path; + bdaddr_t src; + bdaddr_t dst; + uint32_t handle; + GIOChannel *ctrl_io; + GIOChannel *intr_io; + guint ctrl_watch; + guint intr_watch; + guint sec_watch; + struct hidp_connadd_req *req; + bool disable_sdp; + enum reconnect_mode_t reconnect_mode; + guint reconnect_timer; + uint32_t reconnect_attempt; + struct bt_uhid *uhid; + bool uhid_created; + uint8_t report_req_pending; + guint report_req_timer; + uint32_t report_rsp_id; +}; + +static int idle_timeout = 0; +static bool uhid_enabled = false; + +void input_set_idle_timeout(int timeout) +{ + idle_timeout = timeout; +} + +void input_enable_userspace_hid(bool state) +{ + uhid_enabled = state; +} + +static void input_device_enter_reconnect_mode(struct input_device *idev); +static int connection_disconnect(struct input_device *idev, uint32_t flags); + +static void input_device_free(struct input_device *idev) +{ + bt_uhid_unref(idev->uhid); + btd_service_unref(idev->service); + btd_device_unref(idev->device); + g_free(idev->path); + + if (idev->ctrl_watch > 0) + g_source_remove(idev->ctrl_watch); + + if (idev->intr_watch > 0) + g_source_remove(idev->intr_watch); + + if (idev->sec_watch > 0) + g_source_remove(idev->sec_watch); + + if (idev->intr_io) + g_io_channel_unref(idev->intr_io); + + if (idev->ctrl_io) + g_io_channel_unref(idev->ctrl_io); + + if (idev->req) { + g_free(idev->req->rd_data); + g_free(idev->req); + } + + if (idev->reconnect_timer > 0) + g_source_remove(idev->reconnect_timer); + + if (idev->report_req_timer > 0) + g_source_remove(idev->report_req_timer); + + g_free(idev); +} + +static bool hidp_send_message(GIOChannel *chan, uint8_t hdr, + const uint8_t *data, size_t size) +{ + int fd; + ssize_t len; + uint8_t msg[size + 1]; + + if (!chan) { + error("BT socket not connected"); + return false; + } + + if (data == NULL) + size = 0; + + msg[0] = hdr; + if (size > 0) + memcpy(&msg[1], data, size); + ++size; + + fd = g_io_channel_unix_get_fd(chan); + + len = write(fd, msg, size); + if (len < 0) { + error("BT socket write error: %s (%d)", strerror(errno), errno); + return false; + } + + if ((size_t) len < size) { + error("BT socket write error: partial write (%zd of %zu bytes)", + len, size); + return false; + } + + return true; +} + +static bool hidp_send_ctrl_message(struct input_device *idev, uint8_t hdr, + const uint8_t *data, size_t size) +{ + return hidp_send_message(idev->ctrl_io, hdr, data, size); +} + +static bool hidp_send_intr_message(struct input_device *idev, uint8_t hdr, + const uint8_t *data, size_t size) +{ + return hidp_send_message(idev->intr_io, hdr, data, size); +} + +static bool uhid_send_feature_answer(struct input_device *idev, + const uint8_t *data, size_t size, + uint32_t id, uint16_t err) +{ + struct uhid_event ev; + int ret; + + if (data == NULL) + size = 0; + + if (size > sizeof(ev.u.feature_answer.data)) + size = sizeof(ev.u.feature_answer.data); + + if (!idev->uhid_created) { + DBG("HID report (%zu bytes) dropped", size); + return false; + } + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_FEATURE_ANSWER; + ev.u.feature_answer.id = id; + ev.u.feature_answer.err = err; + ev.u.feature_answer.size = size; + + if (size > 0) + memcpy(ev.u.feature_answer.data, data, size); + + ret = bt_uhid_send(idev->uhid, &ev); + if (ret < 0) { + error("bt_uhid_send: %s (%d)", strerror(-ret), -ret); + return false; + } + + DBG("HID report (%zu bytes)", size); + + return true; +} + +static bool uhid_send_input_report(struct input_device *idev, + const uint8_t *data, size_t size) +{ + struct uhid_event ev; + int err; + + if (data == NULL) + size = 0; + + if (size > sizeof(ev.u.input.data)) + size = sizeof(ev.u.input.data); + + if (!idev->uhid_created) { + DBG("HID report (%zu bytes) dropped", size); + return false; + } + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_INPUT; + ev.u.input.size = size; + + if (size > 0) + memcpy(ev.u.input.data, data, size); + + err = bt_uhid_send(idev->uhid, &ev); + if (err < 0) { + error("bt_uhid_send: %s (%d)", strerror(-err), -err); + return false; + } + + DBG("HID report (%zu bytes)", size); + + return true; +} + +static bool hidp_recv_intr_data(GIOChannel *chan, struct input_device *idev) +{ + int fd; + ssize_t len; + uint8_t hdr; + uint8_t data[UHID_DATA_MAX + 1]; + + fd = g_io_channel_unix_get_fd(chan); + + len = read(fd, data, sizeof(data)); + if (len < 0) { + error("BT socket read error: %s (%d)", strerror(errno), errno); + return false; + } + + if (len == 0) { + DBG("BT socket read returned 0 bytes"); + return true; + } + + hdr = data[0]; + if (hdr != (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) { + DBG("unsupported HIDP protocol header 0x%02x", hdr); + return true; + } + + if (len < 2) { + DBG("received empty HID report"); + return true; + } + + uhid_send_input_report(idev, data + 1, len - 1); + + return true; +} + +static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + struct input_device *idev = data; + char address[18]; + + if (cond & G_IO_IN) { + if (hidp_recv_intr_data(chan, idev) && (cond == G_IO_IN)) + return TRUE; + } + + ba2str(&idev->dst, address); + + DBG("Device %s disconnected", address); + + /* Checking for ctrl_watch avoids a double g_io_channel_shutdown since + * it's likely that ctrl_watch_cb has been queued for dispatching in + * this mainloop iteration */ + if ((cond & (G_IO_HUP | G_IO_ERR)) && idev->ctrl_watch) + g_io_channel_shutdown(chan, TRUE, NULL); + + idev->intr_watch = 0; + + if (idev->intr_io) { + g_io_channel_unref(idev->intr_io); + idev->intr_io = NULL; + } + + /* Close control channel */ + if (idev->ctrl_io && !(cond & G_IO_NVAL)) + g_io_channel_shutdown(idev->ctrl_io, TRUE, NULL); + + btd_service_disconnecting_complete(idev->service, 0); + + /* Enter the auto-reconnect mode if needed */ + input_device_enter_reconnect_mode(idev); + + return FALSE; +} + +static void hidp_recv_ctrl_handshake(struct input_device *idev, uint8_t param) +{ + bool pending_req_complete = false; + uint8_t pending_req_type; + + DBG(""); + + pending_req_type = idev->report_req_pending & HIDP_HEADER_TRANS_MASK; + + switch (param) { + case HIDP_HSHK_SUCCESSFUL: + if (pending_req_type == HIDP_TRANS_SET_REPORT) { + DBG("SET_REPORT successful"); + pending_req_complete = true; + } else + DBG("Spurious HIDP_HSHK_SUCCESSFUL"); + break; + + case HIDP_HSHK_NOT_READY: + case HIDP_HSHK_ERR_INVALID_REPORT_ID: + case HIDP_HSHK_ERR_UNSUPPORTED_REQUEST: + case HIDP_HSHK_ERR_INVALID_PARAMETER: + case HIDP_HSHK_ERR_UNKNOWN: + case HIDP_HSHK_ERR_FATAL: + if (pending_req_type == HIDP_TRANS_GET_REPORT) { + DBG("GET_REPORT failed (%u)", param); + uhid_send_feature_answer(idev, NULL, 0, + idev->report_rsp_id, EIO); + pending_req_complete = true; + } else if (pending_req_type == HIDP_TRANS_SET_REPORT) { + DBG("SET_REPORT failed (%u)", param); + pending_req_complete = true; + } else + DBG("Spurious HIDP_HSHK_ERR"); + + if (param == HIDP_HSHK_ERR_FATAL) + hidp_send_ctrl_message(idev, HIDP_TRANS_HID_CONTROL | + HIDP_CTRL_SOFT_RESET, NULL, 0); + break; + + default: + hidp_send_ctrl_message(idev, HIDP_TRANS_HANDSHAKE | + HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0); + break; + } + + if (pending_req_complete) { + idev->report_req_pending = 0; + if (idev->report_req_timer > 0) { + g_source_remove(idev->report_req_timer); + idev->report_req_timer = 0; + } + idev->report_rsp_id = 0; + } +} + +static void hidp_recv_ctrl_hid_control(struct input_device *idev, uint8_t param) +{ + DBG(""); + + if (param == HIDP_CTRL_VIRTUAL_CABLE_UNPLUG) + connection_disconnect(idev, 0); +} + +static void hidp_recv_ctrl_data(struct input_device *idev, uint8_t param, + const uint8_t *data, size_t size) +{ + uint8_t pending_req_type; + uint8_t pending_req_param; + + DBG(""); + + pending_req_type = idev->report_req_pending & HIDP_HEADER_TRANS_MASK; + if (pending_req_type != HIDP_TRANS_GET_REPORT) { + DBG("Spurious DATA on control channel"); + return; + } + + pending_req_param = idev->report_req_pending & HIDP_HEADER_PARAM_MASK; + if (pending_req_param != param) { + DBG("Received DATA RTYPE doesn't match pending request RTYPE"); + return; + } + + switch (param) { + case HIDP_DATA_RTYPE_FEATURE: + case HIDP_DATA_RTYPE_INPUT: + case HIDP_DATA_RTYPE_OUPUT: + uhid_send_feature_answer(idev, data + 1, size - 1, + idev->report_rsp_id, 0); + break; + + case HIDP_DATA_RTYPE_OTHER: + DBG("Received DATA_RTYPE_OTHER"); + break; + + default: + hidp_send_ctrl_message(idev, HIDP_TRANS_HANDSHAKE | + HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0); + break; + } + + idev->report_req_pending = 0; + if (idev->report_req_timer > 0) { + g_source_remove(idev->report_req_timer); + idev->report_req_timer = 0; + } + idev->report_rsp_id = 0; +} + +static bool hidp_recv_ctrl_message(GIOChannel *chan, struct input_device *idev) +{ + int fd; + ssize_t len; + uint8_t hdr, type, param; + uint8_t data[UHID_DATA_MAX + 1]; + + fd = g_io_channel_unix_get_fd(chan); + + len = read(fd, data, sizeof(data)); + if (len < 0) { + error("BT socket read error: %s (%d)", strerror(errno), errno); + return false; + } + + if (len == 0) { + DBG("BT socket read returned 0 bytes"); + return true; + } + + hdr = data[0]; + type = hdr & HIDP_HEADER_TRANS_MASK; + param = hdr & HIDP_HEADER_PARAM_MASK; + + switch (type) { + case HIDP_TRANS_HANDSHAKE: + hidp_recv_ctrl_handshake(idev, param); + break; + case HIDP_TRANS_HID_CONTROL: + hidp_recv_ctrl_hid_control(idev, param); + break; + case HIDP_TRANS_DATA: + hidp_recv_ctrl_data(idev, param, data, len); + break; + default: + error("unsupported HIDP control message"); + hidp_send_ctrl_message(idev, HIDP_TRANS_HANDSHAKE | + HIDP_HSHK_ERR_UNSUPPORTED_REQUEST, NULL, 0); + break; + } + + return true; +} + +static gboolean ctrl_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + struct input_device *idev = data; + char address[18]; + + if (cond & G_IO_IN) { + if (hidp_recv_ctrl_message(chan, idev) && (cond == G_IO_IN)) + return TRUE; + } + + ba2str(&idev->dst, address); + + DBG("Device %s disconnected", address); + + /* Checking for intr_watch avoids a double g_io_channel_shutdown since + * it's likely that intr_watch_cb has been queued for dispatching in + * this mainloop iteration */ + if ((cond & (G_IO_HUP | G_IO_ERR)) && idev->intr_watch) + g_io_channel_shutdown(chan, TRUE, NULL); + + idev->ctrl_watch = 0; + + if (idev->ctrl_io) { + g_io_channel_unref(idev->ctrl_io); + idev->ctrl_io = NULL; + } + + /* Close interrupt channel */ + if (idev->intr_io && !(cond & G_IO_NVAL)) + g_io_channel_shutdown(idev->intr_io, TRUE, NULL); + + return FALSE; +} + +#define REPORT_REQ_TIMEOUT 3 + +static gboolean hidp_report_req_timeout(gpointer data) +{ + struct input_device *idev = data; + uint8_t pending_req_type; + const char *req_type_str; + char address[18]; + + ba2str(&idev->dst, address); + pending_req_type = idev->report_req_pending & HIDP_HEADER_TRANS_MASK; + + switch (pending_req_type) { + case HIDP_TRANS_GET_REPORT: + req_type_str = "GET_REPORT"; + break; + case HIDP_TRANS_SET_REPORT: + req_type_str = "SET_REPORT"; + break; + default: + /* Should never happen */ + req_type_str = "OTHER_TRANS"; + break; + } + + DBG("Device %s HIDP %s request timed out", address, req_type_str); + + idev->report_req_pending = 0; + idev->report_req_timer = 0; + idev->report_rsp_id = 0; + + return FALSE; +} + +static void hidp_send_set_report(struct uhid_event *ev, void *user_data) +{ + struct input_device *idev = user_data; + uint8_t hdr; + bool sent; + + DBG(""); + + switch (ev->u.output.rtype) { + case UHID_FEATURE_REPORT: + /* Send SET_REPORT on control channel */ + if (idev->report_req_pending) { + DBG("Old GET_REPORT or SET_REPORT still pending"); + return; + } + + hdr = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE; + sent = hidp_send_ctrl_message(idev, hdr, ev->u.output.data, + ev->u.output.size); + if (sent) { + idev->report_req_pending = hdr; + idev->report_req_timer = + g_timeout_add_seconds(REPORT_REQ_TIMEOUT, + hidp_report_req_timeout, idev); + } + break; + case UHID_OUTPUT_REPORT: + /* Send DATA on interrupt channel */ + hdr = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT; + hidp_send_intr_message(idev, hdr, ev->u.output.data, + ev->u.output.size); + break; + default: + DBG("Unsupported HID report type %u", ev->u.output.rtype); + return; + } +} + +static void hidp_send_get_report(struct uhid_event *ev, void *user_data) +{ + struct input_device *idev = user_data; + uint8_t hdr; + bool sent; + + DBG(""); + + if (idev->report_req_pending) { + DBG("Old GET_REPORT or SET_REPORT still pending"); + uhid_send_feature_answer(idev, NULL, 0, ev->u.feature.id, + EBUSY); + return; + } + + /* Send GET_REPORT on control channel */ + switch (ev->u.feature.rtype) { + case UHID_FEATURE_REPORT: + hdr = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_FEATURE; + break; + case UHID_INPUT_REPORT: + hdr = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_INPUT; + break; + case UHID_OUTPUT_REPORT: + hdr = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_OUPUT; + break; + default: + DBG("Unsupported HID report type %u", ev->u.feature.rtype); + return; + } + + sent = hidp_send_ctrl_message(idev, hdr, &ev->u.feature.rnum, + sizeof(ev->u.feature.rnum)); + if (sent) { + idev->report_req_pending = hdr; + idev->report_req_timer = + g_timeout_add_seconds(REPORT_REQ_TIMEOUT, + hidp_report_req_timeout, idev); + idev->report_rsp_id = ev->u.feature.id; + } +} + +static void epox_endian_quirk(unsigned char *data, int size) +{ + /* USAGE_PAGE (Keyboard) 05 07 + * USAGE_MINIMUM (0) 19 00 + * USAGE_MAXIMUM (65280) 2A 00 FF <= must be FF 00 + * LOGICAL_MINIMUM (0) 15 00 + * LOGICAL_MAXIMUM (65280) 26 00 FF <= must be FF 00 + */ + unsigned char pattern[] = { 0x05, 0x07, 0x19, 0x00, 0x2a, 0x00, 0xff, + 0x15, 0x00, 0x26, 0x00, 0xff }; + unsigned int i; + + if (!data) + return; + + for (i = 0; i < size - sizeof(pattern); i++) { + if (!memcmp(data + i, pattern, sizeof(pattern))) { + data[i + 5] = 0xff; + data[i + 6] = 0x00; + data[i + 10] = 0xff; + data[i + 11] = 0x00; + } + } +} + +static int create_hid_dev_name(sdp_record_t *rec, struct hidp_connadd_req *req) +{ + char sdesc[sizeof(req->name) / 2]; + + if (sdp_get_service_desc(rec, sdesc, sizeof(sdesc)) == 0) { + char pname[sizeof(req->name) / 2]; + + if (sdp_get_provider_name(rec, pname, sizeof(pname)) == 0 && + strncmp(sdesc, pname, 5) != 0) + snprintf(req->name, sizeof(req->name), "%s %s", + pname, sdesc); + else + snprintf(req->name, sizeof(req->name), "%s", sdesc); + } else { + return sdp_get_service_name(rec, req->name, sizeof(req->name)); + } + + return 0; +} + +/* See HID profile specification v1.0, "7.11.6 HIDDescriptorList" for details + * on the attribute format. */ +static int extract_hid_desc_data(sdp_record_t *rec, + struct hidp_connadd_req *req) +{ + sdp_data_t *d; + + d = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST); + if (!d) + goto invalid_desc; + + if (!SDP_IS_SEQ(d->dtd)) + goto invalid_desc; + + /* First HIDDescriptor */ + d = d->val.dataseq; + if (!SDP_IS_SEQ(d->dtd)) + goto invalid_desc; + + /* ClassDescriptorType */ + d = d->val.dataseq; + if (d->dtd != SDP_UINT8) + goto invalid_desc; + + /* ClassDescriptorData */ + d = d->next; + if (!d || !SDP_IS_TEXT_STR(d->dtd)) + goto invalid_desc; + + req->rd_data = g_try_malloc0(d->unitSize); + if (req->rd_data) { + memcpy(req->rd_data, d->val.str, d->unitSize); + req->rd_size = d->unitSize; + epox_endian_quirk(req->rd_data, req->rd_size); + } + + return 0; + +invalid_desc: + error("Missing or invalid HIDDescriptorList SDP attribute"); + return -EINVAL; +} + +static int extract_hid_record(sdp_record_t *rec, struct hidp_connadd_req *req) +{ + sdp_data_t *pdlist; + uint8_t attr_val; + int err; + + err = create_hid_dev_name(rec, req); + if (err < 0) + DBG("No valid Service Name or Service Description found"); + + pdlist = sdp_data_get(rec, SDP_ATTR_HID_PARSER_VERSION); + req->parser = pdlist ? pdlist->val.uint16 : 0x0100; + + pdlist = sdp_data_get(rec, SDP_ATTR_HID_DEVICE_SUBCLASS); + req->subclass = pdlist ? pdlist->val.uint8 : 0; + + pdlist = sdp_data_get(rec, SDP_ATTR_HID_COUNTRY_CODE); + req->country = pdlist ? pdlist->val.uint8 : 0; + + pdlist = sdp_data_get(rec, SDP_ATTR_HID_VIRTUAL_CABLE); + attr_val = pdlist ? pdlist->val.uint8 : 0; + if (attr_val) + req->flags |= (1 << HIDP_VIRTUAL_CABLE_UNPLUG); + + pdlist = sdp_data_get(rec, SDP_ATTR_HID_BOOT_DEVICE); + attr_val = pdlist ? pdlist->val.uint8 : 0; + if (attr_val) + req->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE); + + err = extract_hid_desc_data(rec, req); + if (err < 0) + return err; + + return 0; +} + +static int ioctl_connadd(struct hidp_connadd_req *req) +{ + int ctl, err = 0; + + ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP); + if (ctl < 0) + return -errno; + + if (ioctl(ctl, HIDPCONNADD, req) < 0) + err = -errno; + + close(ctl); + + return err; +} + +static bool ioctl_is_connected(struct input_device *idev) +{ + struct hidp_conninfo ci; + int ctl; + + /* Standard HID */ + ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP); + if (ctl < 0) { + error("Can't open HIDP control socket"); + return false; + } + + memset(&ci, 0, sizeof(ci)); + bacpy(&ci.bdaddr, &idev->dst); + if (ioctl(ctl, HIDPGETCONNINFO, &ci) < 0) { + error("Can't get HIDP connection info"); + close(ctl); + return false; + } + + close(ctl); + + if (ci.state != BT_CONNECTED) + return false; + + return true; +} + +static int ioctl_disconnect(struct input_device *idev, uint32_t flags) +{ + struct hidp_conndel_req req; + struct hidp_conninfo ci; + int ctl, err = 0; + + ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HIDP); + if (ctl < 0) { + error("Can't open HIDP control socket"); + return -errno; + } + + memset(&ci, 0, sizeof(ci)); + bacpy(&ci.bdaddr, &idev->dst); + if ((ioctl(ctl, HIDPGETCONNINFO, &ci) < 0) || + (ci.state != BT_CONNECTED)) { + close(ctl); + return -ENOTCONN; + } + + memset(&req, 0, sizeof(req)); + bacpy(&req.bdaddr, &idev->dst); + req.flags = flags; + if (ioctl(ctl, HIDPCONNDEL, &req) < 0) { + err = -errno; + error("Can't delete the HID device: %s (%d)", + strerror(-err), -err); + } + + close(ctl); + + return err; +} + +static int uhid_connadd(struct input_device *idev, struct hidp_connadd_req *req) +{ + int err; + struct uhid_event ev; + + if (idev->uhid_created) + return 0; + + /* create uHID device */ + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + strncpy((char *) ev.u.create.name, req->name, sizeof(ev.u.create.name)); + ba2str(&idev->src, (char *) ev.u.create.phys); + ba2str(&idev->dst, (char *) ev.u.create.uniq); + ev.u.create.vendor = req->vendor; + ev.u.create.product = req->product; + ev.u.create.version = req->version; + ev.u.create.country = req->country; + ev.u.create.bus = BUS_BLUETOOTH; + ev.u.create.rd_data = req->rd_data; + ev.u.create.rd_size = req->rd_size; + + err = bt_uhid_send(idev->uhid, &ev); + if (err < 0) { + error("bt_uhid_send: %s", strerror(-err)); + return err; + } + + bt_uhid_register(idev->uhid, UHID_OUTPUT, hidp_send_set_report, idev); + bt_uhid_register(idev->uhid, UHID_FEATURE, hidp_send_get_report, idev); + + idev->uhid_created = true; + + return err; +} + +static gboolean encrypt_notify(GIOChannel *io, GIOCondition condition, + gpointer data) +{ + struct input_device *idev = data; + int err; + + DBG(""); + + if (idev->uhid) + err = uhid_connadd(idev, idev->req); + else + err = ioctl_connadd(idev->req); + + if (err < 0) { + error("ioctl_connadd(): %s (%d)", strerror(-err), -err); + + if (idev->ctrl_io) { + g_io_channel_shutdown(idev->ctrl_io, FALSE, NULL); + g_io_channel_unref(idev->ctrl_io); + idev->ctrl_io = NULL; + } + + if (idev->intr_io) { + g_io_channel_shutdown(idev->intr_io, FALSE, NULL); + g_io_channel_unref(idev->intr_io); + idev->intr_io = NULL; + } + } + + idev->sec_watch = 0; + + g_free(idev->req->rd_data); + g_free(idev->req); + idev->req = NULL; + + return FALSE; +} + +static int hidp_add_connection(struct input_device *idev) +{ + struct hidp_connadd_req *req; + sdp_record_t *rec; + char src_addr[18], dst_addr[18]; + char filename[PATH_MAX]; + GKeyFile *key_file; + char handle[11], *str; + GError *gerr = NULL; + int err; + + req = g_new0(struct hidp_connadd_req, 1); + req->ctrl_sock = g_io_channel_unix_get_fd(idev->ctrl_io); + req->intr_sock = g_io_channel_unix_get_fd(idev->intr_io); + req->flags = 0; + req->idle_to = idle_timeout; + + ba2str(&idev->src, src_addr); + ba2str(&idev->dst, dst_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", src_addr, + dst_addr); + sprintf(handle, "0x%8.8X", idev->handle); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + str = g_key_file_get_string(key_file, "ServiceRecords", handle, NULL); + g_key_file_free(key_file); + + if (!str) { + error("Rejected connection from unknown device %s", dst_addr); + err = -EPERM; + goto cleanup; + } + + rec = record_from_string(str); + g_free(str); + + err = extract_hid_record(rec, req); + sdp_record_free(rec); + if (err < 0) { + error("Could not parse HID SDP record: %s (%d)", strerror(-err), + -err); + goto cleanup; + } + + req->vendor = btd_device_get_vendor(idev->device); + req->product = btd_device_get_product(idev->device); + req->version = btd_device_get_version(idev->device); + + if (device_name_known(idev->device)) + device_get_name(idev->device, req->name, sizeof(req->name)); + + /* Encryption is mandatory for keyboards */ + if (req->subclass & 0x40) { + if (!bt_io_set(idev->intr_io, &gerr, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_INVALID)) { + error("btio: %s", gerr->message); + g_error_free(gerr); + err = -EFAULT; + goto cleanup; + } + + idev->req = req; + idev->sec_watch = g_io_add_watch(idev->intr_io, G_IO_OUT, + encrypt_notify, idev); + + return 0; + } + + if (idev->uhid) + err = uhid_connadd(idev, req); + else + err = ioctl_connadd(req); + +cleanup: + g_free(req->rd_data); + g_free(req); + + return err; +} + +static bool is_connected(struct input_device *idev) +{ + if (idev->uhid) + return (idev->intr_io != NULL && idev->ctrl_io != NULL); + else + return ioctl_is_connected(idev); +} + +static int connection_disconnect(struct input_device *idev, uint32_t flags) +{ + if (!is_connected(idev)) + return -ENOTCONN; + + /* Standard HID disconnect */ + if (idev->intr_io) + g_io_channel_shutdown(idev->intr_io, TRUE, NULL); + if (idev->ctrl_io) + g_io_channel_shutdown(idev->ctrl_io, TRUE, NULL); + + if (idev->uhid) + return 0; + else + return ioctl_disconnect(idev, flags); +} + +static int input_device_connected(struct input_device *idev) +{ + int err; + + if (idev->intr_io == NULL || idev->ctrl_io == NULL) + return -ENOTCONN; + + err = hidp_add_connection(idev); + if (err < 0) + return err; + + btd_service_connecting_complete(idev->service, 0); + + return 0; +} + +static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err, + gpointer user_data) +{ + struct input_device *idev = user_data; + GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; + int err; + + if (conn_err) { + err = -EIO; + goto failed; + } + + err = input_device_connected(idev); + if (err < 0) + goto failed; + + if (idev->uhid) + cond |= G_IO_IN; + + idev->intr_watch = g_io_add_watch(idev->intr_io, cond, intr_watch_cb, + idev); + + return; + +failed: + btd_service_connecting_complete(idev->service, err); + + /* So we guarantee the interrupt channel is closed before the + * control channel (if we only do unref GLib will close it only + * after returning control to the mainloop */ + if (!conn_err) + g_io_channel_shutdown(idev->intr_io, FALSE, NULL); + + g_io_channel_unref(idev->intr_io); + idev->intr_io = NULL; + + if (idev->ctrl_io) { + g_io_channel_unref(idev->ctrl_io); + idev->ctrl_io = NULL; + } +} + +static void control_connect_cb(GIOChannel *chan, GError *conn_err, + gpointer user_data) +{ + struct input_device *idev = user_data; + GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; + GIOChannel *io; + GError *err = NULL; + + if (conn_err) { + error("%s", conn_err->message); + goto failed; + } + + /* Connect to the HID interrupt channel */ + io = bt_io_connect(interrupt_connect_cb, idev, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &idev->src, + BT_IO_OPT_DEST_BDADDR, &idev->dst, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!io) { + error("%s", err->message); + g_error_free(err); + goto failed; + } + + idev->intr_io = io; + + if (idev->uhid) + cond |= G_IO_IN; + + idev->ctrl_watch = g_io_add_watch(idev->ctrl_io, cond, ctrl_watch_cb, + idev); + + return; + +failed: + btd_service_connecting_complete(idev->service, -EIO); + g_io_channel_unref(idev->ctrl_io); + idev->ctrl_io = NULL; +} + +static int dev_connect(struct input_device *idev) +{ + GError *err = NULL; + GIOChannel *io; + + if (idev->disable_sdp) + bt_clear_cached_session(&idev->src, &idev->dst); + + io = bt_io_connect(control_connect_cb, idev, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, &idev->src, + BT_IO_OPT_DEST_BDADDR, &idev->dst, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + idev->ctrl_io = io; + + if (err == NULL) + return 0; + + error("%s", err->message); + g_error_free(err); + + return -EIO; +} + +static gboolean input_device_auto_reconnect(gpointer user_data) +{ + struct input_device *idev = user_data; + + DBG("path=%s, attempt=%d", idev->path, idev->reconnect_attempt); + + /* Stop the recurrent reconnection attempts if the device is + * reconnected or is marked for removal. + */ + if (device_is_temporary(idev->device) || + btd_device_is_connected(idev->device)) + goto bail; + + /* Only attempt an auto-reconnect for at most 3 minutes (6 * 30s). */ + if (idev->reconnect_attempt >= 6) + goto bail; + + /* Check if the profile is already connected. */ + if (idev->ctrl_io) + goto bail; + + if (is_connected(idev)) + goto bail; + + idev->reconnect_attempt++; + dev_connect(idev); + + return TRUE; + +bail: + idev->reconnect_timer = 0; + return FALSE; +} + +static const char * const _reconnect_mode_str[] = { + "none", + "device", + "host", + "any" +}; + +static const char *reconnect_mode_to_string(const enum reconnect_mode_t mode) +{ + return _reconnect_mode_str[mode]; +} + +static void input_device_enter_reconnect_mode(struct input_device *idev) +{ + DBG("path=%s reconnect_mode=%s", idev->path, + reconnect_mode_to_string(idev->reconnect_mode)); + + /* Only attempt an auto-reconnect when the device is required to + * accept reconnections from the host. + */ + if (idev->reconnect_mode != RECONNECT_ANY && + idev->reconnect_mode != RECONNECT_HOST) + return; + + /* If the device is temporary we are not required to reconnect + * with the device. This is likely the case of a removing device. + */ + if (device_is_temporary(idev->device) || + btd_device_is_connected(idev->device)) + return; + + if (idev->reconnect_timer > 0) + g_source_remove(idev->reconnect_timer); + + DBG("registering auto-reconnect"); + idev->reconnect_attempt = 0; + idev->reconnect_timer = g_timeout_add_seconds(30, + input_device_auto_reconnect, idev); + +} + +int input_device_connect(struct btd_service *service) +{ + struct input_device *idev; + + DBG(""); + + idev = btd_service_get_user_data(service); + + if (idev->ctrl_io) + return -EBUSY; + + if (is_connected(idev)) + return -EALREADY; + + return dev_connect(idev); +} + +int input_device_disconnect(struct btd_service *service) +{ + struct input_device *idev; + int err, flags; + + DBG(""); + + idev = btd_service_get_user_data(service); + + flags = device_is_temporary(idev->device) ? + (1 << HIDP_VIRTUAL_CABLE_UNPLUG) : 0; + + err = connection_disconnect(idev, flags); + if (err < 0) + return err; + + return 0; +} + +static bool is_device_sdp_disable(const sdp_record_t *rec) +{ + sdp_data_t *data; + + data = sdp_data_get(rec, SDP_ATTR_HID_SDP_DISABLE); + + return data && data->val.uint8; +} + +static enum reconnect_mode_t hid_reconnection_mode(bool reconnect_initiate, + bool normally_connectable) +{ + if (!reconnect_initiate && !normally_connectable) + return RECONNECT_NONE; + else if (!reconnect_initiate && normally_connectable) + return RECONNECT_HOST; + else if (reconnect_initiate && !normally_connectable) + return RECONNECT_DEVICE; + else /* (reconnect_initiate && normally_connectable) */ + return RECONNECT_ANY; +} + +static void extract_hid_props(struct input_device *idev, + const sdp_record_t *rec) +{ + /* Extract HID connectability */ + bool reconnect_initiate, normally_connectable; + sdp_data_t *pdlist; + + /* HIDNormallyConnectable is optional and assumed FALSE + * if not present. */ + pdlist = sdp_data_get(rec, SDP_ATTR_HID_RECONNECT_INITIATE); + reconnect_initiate = pdlist ? pdlist->val.uint8 : TRUE; + + pdlist = sdp_data_get(rec, SDP_ATTR_HID_NORMALLY_CONNECTABLE); + normally_connectable = pdlist ? pdlist->val.uint8 : FALSE; + + /* Update local values */ + idev->reconnect_mode = + hid_reconnection_mode(reconnect_initiate, normally_connectable); +} + +static struct input_device *input_device_new(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct btd_profile *p = btd_service_get_profile(service); + const char *path = device_get_path(device); + const sdp_record_t *rec = btd_device_get_record(device, p->remote_uuid); + struct btd_adapter *adapter = device_get_adapter(device); + struct input_device *idev; + + if (!rec) + return NULL; + + idev = g_new0(struct input_device, 1); + bacpy(&idev->src, btd_adapter_get_address(adapter)); + bacpy(&idev->dst, device_get_address(device)); + idev->service = btd_service_ref(service); + idev->device = btd_device_ref(device); + idev->path = g_strdup(path); + idev->handle = rec->handle; + idev->disable_sdp = is_device_sdp_disable(rec); + + /* Initialize device properties */ + extract_hid_props(idev, rec); + + return idev; +} + +static gboolean property_get_reconnect_mode( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct input_device *idev = data; + const char *str_mode = reconnect_mode_to_string(idev->reconnect_mode); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str_mode); + + return TRUE; +} + +static const GDBusPropertyTable input_properties[] = { + { "ReconnectMode", "s", property_get_reconnect_mode }, + { } +}; + +int input_device_register(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + const char *path = device_get_path(device); + struct input_device *idev; + + DBG("%s", path); + + idev = input_device_new(service); + if (!idev) + return -EINVAL; + + if (uhid_enabled) { + idev->uhid = bt_uhid_new_default(); + if (!idev->uhid) { + error("bt_uhid_new_default: failed"); + input_device_free(idev); + return -EIO; + } + } + + if (g_dbus_register_interface(btd_get_dbus_connection(), + idev->path, INPUT_INTERFACE, + NULL, NULL, + input_properties, idev, + NULL) == FALSE) { + error("Unable to register %s interface", INPUT_INTERFACE); + input_device_free(idev); + return -EINVAL; + } + + btd_service_set_user_data(service, idev); + + return 0; +} + +static struct input_device *find_device(const bdaddr_t *src, + const bdaddr_t *dst) +{ + struct btd_device *device; + struct btd_service *service; + + device = btd_adapter_find_device(adapter_find(src), dst, BDADDR_BREDR); + if (device == NULL) + return NULL; + + service = btd_device_get_service(device, HID_UUID); + if (service == NULL) + return NULL; + + return btd_service_get_user_data(service); +} + +void input_device_unregister(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + const char *path = device_get_path(device); + struct input_device *idev = btd_service_get_user_data(service); + + DBG("%s", path); + + g_dbus_unregister_interface(btd_get_dbus_connection(), + idev->path, INPUT_INTERFACE); + + input_device_free(idev); +} + +static int input_device_connadd(struct input_device *idev) +{ + int err; + + err = input_device_connected(idev); + if (err == 0) + return 0; + + if (idev->ctrl_io) { + g_io_channel_shutdown(idev->ctrl_io, FALSE, NULL); + g_io_channel_unref(idev->ctrl_io); + idev->ctrl_io = NULL; + } + + if (idev->intr_io) { + g_io_channel_shutdown(idev->intr_io, FALSE, NULL); + g_io_channel_unref(idev->intr_io); + idev->intr_io = NULL; + } + + return err; +} + +bool input_device_exists(const bdaddr_t *src, const bdaddr_t *dst) +{ + if (find_device(src, dst)) + return true; + + return false; +} + +int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, + GIOChannel *io) +{ + struct input_device *idev = find_device(src, dst); + GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; + + DBG("idev %p psm %d", idev, psm); + + if (!idev) + return -ENOENT; + + if (uhid_enabled) + cond |= G_IO_IN; + + switch (psm) { + case L2CAP_PSM_HIDP_CTRL: + if (idev->ctrl_io) + return -EALREADY; + idev->ctrl_io = g_io_channel_ref(io); + idev->ctrl_watch = g_io_add_watch(idev->ctrl_io, cond, + ctrl_watch_cb, idev); + break; + case L2CAP_PSM_HIDP_INTR: + if (idev->intr_io) + return -EALREADY; + idev->intr_io = g_io_channel_ref(io); + idev->intr_watch = g_io_add_watch(idev->intr_io, cond, + intr_watch_cb, idev); + break; + } + + if (idev->intr_io && idev->ctrl_io) + input_device_connadd(idev); + + return 0; +} + +int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct input_device *idev = find_device(src, dst); + + if (!idev) + return -ENOENT; + + if (idev->intr_io) + g_io_channel_shutdown(idev->intr_io, TRUE, NULL); + + if (idev->ctrl_io) + g_io_channel_shutdown(idev->ctrl_io, TRUE, NULL); + + return 0; +} diff --git a/profiles/input/device.h b/profiles/input/device.h new file mode 100644 index 0000000..51a9aee --- /dev/null +++ b/profiles/input/device.h @@ -0,0 +1,42 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define L2CAP_PSM_HIDP_CTRL 0x11 +#define L2CAP_PSM_HIDP_INTR 0x13 + +struct input_device; +struct input_conn; + +void input_set_idle_timeout(int timeout); +void input_enable_userspace_hid(bool state); + +int input_device_register(struct btd_service *service); +void input_device_unregister(struct btd_service *service); + +bool input_device_exists(const bdaddr_t *src, const bdaddr_t *dst); +int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, + GIOChannel *io); +int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst); + +int input_device_connect(struct btd_service *service); +int input_device_disconnect(struct btd_service *service); diff --git a/profiles/input/hidp_defs.h b/profiles/input/hidp_defs.h new file mode 100644 index 0000000..5dc479a --- /dev/null +++ b/profiles/input/hidp_defs.h @@ -0,0 +1,79 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2014 Marcel Holtmann + * Copyright (C) 2014 Google 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 __HIDP_DEFS_H +#define __HIDP_DEFS_H + +/* HIDP header masks */ +#define HIDP_HEADER_TRANS_MASK 0xf0 +#define HIDP_HEADER_PARAM_MASK 0x0f + +/* HIDP transaction types */ +#define HIDP_TRANS_HANDSHAKE 0x00 +#define HIDP_TRANS_HID_CONTROL 0x10 +#define HIDP_TRANS_GET_REPORT 0x40 +#define HIDP_TRANS_SET_REPORT 0x50 +#define HIDP_TRANS_GET_PROTOCOL 0x60 +#define HIDP_TRANS_SET_PROTOCOL 0x70 +#define HIDP_TRANS_GET_IDLE 0x80 +#define HIDP_TRANS_SET_IDLE 0x90 +#define HIDP_TRANS_DATA 0xa0 +#define HIDP_TRANS_DATC 0xb0 + +/* HIDP handshake results */ +#define HIDP_HSHK_SUCCESSFUL 0x00 +#define HIDP_HSHK_NOT_READY 0x01 +#define HIDP_HSHK_ERR_INVALID_REPORT_ID 0x02 +#define HIDP_HSHK_ERR_UNSUPPORTED_REQUEST 0x03 +#define HIDP_HSHK_ERR_INVALID_PARAMETER 0x04 +#define HIDP_HSHK_ERR_UNKNOWN 0x0e +#define HIDP_HSHK_ERR_FATAL 0x0f + +/* HIDP control operation parameters */ +#define HIDP_CTRL_NOP 0x00 +#define HIDP_CTRL_HARD_RESET 0x01 +#define HIDP_CTRL_SOFT_RESET 0x02 +#define HIDP_CTRL_SUSPEND 0x03 +#define HIDP_CTRL_EXIT_SUSPEND 0x04 +#define HIDP_CTRL_VIRTUAL_CABLE_UNPLUG 0x05 + +/* HIDP data transaction headers */ +#define HIDP_DATA_RTYPE_MASK 0x03 +#define HIDP_DATA_RSRVD_MASK 0x0c +#define HIDP_DATA_RTYPE_OTHER 0x00 +#define HIDP_DATA_RTYPE_INPUT 0x01 +#define HIDP_DATA_RTYPE_OUPUT 0x02 +#define HIDP_DATA_RTYPE_FEATURE 0x03 + +/* HIDP protocol header parameters */ +#define HIDP_PROTO_BOOT 0x00 +#define HIDP_PROTO_REPORT 0x01 + +#define HIDP_VIRTUAL_CABLE_UNPLUG 0 +#define HIDP_BOOT_PROTOCOL_MODE 1 +#define HIDP_BLUETOOTH_VENDOR_ID 9 +#define HIDP_WAITING_FOR_RETURN 10 +#define HIDP_WAITING_FOR_SEND_ACK 11 + +#endif /* __HIDP_DEFS_H */ diff --git a/profiles/input/hog-lib.c b/profiles/input/hog-lib.c new file mode 100644 index 0000000..d9ed806 --- /dev/null +++ b/profiles/input/hog-lib.c @@ -0,0 +1,1705 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. + * Copyright (C) 2012 Marcel Holtmann + * Copyright (C) 2012 Nordic Semiconductor Inc. + * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "src/shared/uhid.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/log.h" + +#include "attrib/att.h" +#include "attrib/gattrib.h" +#include "attrib/gatt.h" + +#include "btio/btio.h" + +#include "profiles/scanparam/scpp.h" +#include "profiles/deviceinfo/dis.h" +#include "profiles/battery/bas.h" +#include "profiles/input/hog-lib.h" + +#define HOG_UUID "00001812-0000-1000-8000-00805f9b34fb" +#define HOG_UUID16 0x1812 + +#define HOG_INFO_UUID 0x2A4A +#define HOG_REPORT_MAP_UUID 0x2A4B +#define HOG_REPORT_UUID 0x2A4D +#define HOG_PROTO_MODE_UUID 0x2A4E +#define HOG_CONTROL_POINT_UUID 0x2A4C + +#define HOG_REPORT_TYPE_INPUT 1 +#define HOG_REPORT_TYPE_OUTPUT 2 +#define HOG_REPORT_TYPE_FEATURE 3 + +#define HOG_PROTO_MODE_BOOT 0 +#define HOG_PROTO_MODE_REPORT 1 + +#define HOG_REPORT_MAP_MAX_SIZE 512 +#define HID_INFO_SIZE 4 +#define ATT_NOTIFICATION_HEADER_SIZE 3 + +struct bt_hog { + int ref_count; + char *name; + uint16_t vendor; + uint16_t product; + uint16_t version; + struct gatt_db_attribute *attr; + struct gatt_primary *primary; + GAttrib *attrib; + GSList *reports; + struct bt_uhid *uhid; + int uhid_fd; + bool uhid_created; + gboolean has_report_id; + uint16_t bcdhid; + uint8_t bcountrycode; + uint16_t proto_mode_handle; + uint16_t ctrlpt_handle; + uint8_t flags; + unsigned int getrep_att; + uint16_t getrep_id; + unsigned int setrep_att; + uint16_t setrep_id; + struct bt_scpp *scpp; + struct bt_dis *dis; + struct queue *bas; + GSList *instances; + struct queue *gatt_op; +}; + +struct report { + struct bt_hog *hog; + uint8_t id; + uint8_t type; + uint16_t handle; + uint16_t value_handle; + uint8_t properties; + uint16_t ccc_handle; + guint notifyid; + uint16_t len; + uint8_t *value; +}; + +struct gatt_request { + unsigned int id; + struct bt_hog *hog; + void *user_data; +}; + +static struct gatt_request *create_request(struct bt_hog *hog, + void *user_data) +{ + struct gatt_request *req; + + req = new0(struct gatt_request, 1); + if (!req) + return NULL; + + req->user_data = user_data; + req->hog = bt_hog_ref(hog); + + return req; +} + +static bool set_and_store_gatt_req(struct bt_hog *hog, + struct gatt_request *req, + unsigned int id) +{ + req->id = id; + return queue_push_head(hog->gatt_op, req); +} + +static void destroy_gatt_req(struct gatt_request *req) +{ + queue_remove(req->hog->gatt_op, req); + bt_hog_unref(req->hog); + free(req); +} + +static void write_char(struct bt_hog *hog, GAttrib *attrib, uint16_t handle, + const uint8_t *value, size_t vlen, + GAttribResultFunc func, + gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(hog, user_data); + if (!req) + return; + + id = gatt_write_char(attrib, handle, value, vlen, func, req); + + if (set_and_store_gatt_req(hog, req, id)) + return; + + error("hog: Could not read char"); + g_attrib_cancel(attrib, id); + free(req); +} + +static void read_char(struct bt_hog *hog, GAttrib *attrib, uint16_t handle, + GAttribResultFunc func, gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + /* Ignore if not connected */ + if (!attrib) + return; + + req = create_request(hog, user_data); + if (!req) + return; + + id = gatt_read_char(attrib, handle, func, req); + + if (set_and_store_gatt_req(hog, req, id)) + return; + + error("hog: Could not read char"); + g_attrib_cancel(attrib, id); + free(req); +} + +static void discover_desc(struct bt_hog *hog, GAttrib *attrib, + uint16_t start, uint16_t end, gatt_cb_t func, + gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(hog, user_data); + if (!req) + return; + + id = gatt_discover_desc(attrib, start, end, NULL, func, req); + if (set_and_store_gatt_req(hog, req, id)) + return; + + error("hog: Could not discover descriptors"); + g_attrib_cancel(attrib, id); + free(req); +} + +static void discover_char(struct bt_hog *hog, GAttrib *attrib, + uint16_t start, uint16_t end, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(hog, user_data); + if (!req) + return; + + id = gatt_discover_char(attrib, start, end, uuid, func, req); + + if (set_and_store_gatt_req(hog, req, id)) + return; + + error("hog: Could not discover characteristic"); + g_attrib_cancel(attrib, id); + free(req); +} + +static void discover_primary(struct bt_hog *hog, GAttrib *attrib, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(hog, user_data); + if (!req) + return; + + id = gatt_discover_primary(attrib, uuid, func, req); + + if (set_and_store_gatt_req(hog, req, id)) + return; + + error("hog: Could not send discover primary"); + g_attrib_cancel(attrib, id); + free(req); +} + +static void find_included(struct bt_hog *hog, GAttrib *attrib, + uint16_t start, uint16_t end, + gatt_cb_t func, gpointer user_data) +{ + struct gatt_request *req; + unsigned int id; + + req = create_request(hog, user_data); + if (!req) + return; + + id = gatt_find_included(attrib, start, end, func, req); + + if (set_and_store_gatt_req(hog, req, id)) + return; + + error("Could not find included"); + g_attrib_cancel(attrib, id); + free(req); +} + +static void report_value_cb(const guint8 *pdu, guint16 len, gpointer user_data) +{ + struct report *report = user_data; + struct bt_hog *hog = report->hog; + struct uhid_event ev; + uint8_t *buf; + int err; + + if (len < ATT_NOTIFICATION_HEADER_SIZE) { + error("Malformed ATT notification"); + return; + } + + pdu += ATT_NOTIFICATION_HEADER_SIZE; + len -= ATT_NOTIFICATION_HEADER_SIZE; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_INPUT; + buf = ev.u.input.data; + + if (hog->has_report_id) { + buf[0] = report->id; + len = MIN(len, sizeof(ev.u.input.data) - 1); + memcpy(buf + 1, pdu, len); + ev.u.input.size = ++len; + } else { + len = MIN(len, sizeof(ev.u.input.data)); + memcpy(buf, pdu, len); + ev.u.input.size = len; + } + + err = bt_uhid_send(hog->uhid, &ev); + if (err < 0) { + error("bt_uhid_send: %s (%d)", strerror(-err), -err); + return; + } +} + +static void report_ccc_written_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct gatt_request *req = user_data; + struct report *report = req->user_data; + struct bt_hog *hog = report->hog; + + destroy_gatt_req(req); + + if (status != 0) { + error("Write report characteristic descriptor failed: %s", + att_ecode2str(status)); + return; + } + + report->notifyid = g_attrib_register(hog->attrib, + ATT_OP_HANDLE_NOTIFY, + report->value_handle, + report_value_cb, report, NULL); + + DBG("Report characteristic descriptor written: notifications enabled"); +} + +static void write_ccc(struct bt_hog *hog, GAttrib *attrib, uint16_t handle, + void *user_data) +{ + uint8_t value[2]; + + put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); + + write_char(hog, attrib, handle, value, sizeof(value), + report_ccc_written_cb, user_data); +} + +static void ccc_read_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct gatt_request *req = user_data; + struct report *report = req->user_data; + + destroy_gatt_req(req); + + if (status != 0) { + error("Error reading CCC value: %s", att_ecode2str(status)); + return; + } + + write_ccc(report->hog, report->hog->attrib, report->ccc_handle, report); +} + +static const char *type_to_string(uint8_t type) +{ + switch (type) { + case HOG_REPORT_TYPE_INPUT: + return "input"; + case HOG_REPORT_TYPE_OUTPUT: + return "output"; + case HOG_REPORT_TYPE_FEATURE: + return "feature"; + } + + return NULL; +} + +static void report_reference_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct gatt_request *req = user_data; + struct report *report = req->user_data; + + destroy_gatt_req(req); + + if (status != 0) { + error("Read Report Reference descriptor failed: %s", + att_ecode2str(status)); + return; + } + + if (plen != 3) { + error("Malformed ATT read response"); + return; + } + + report->id = pdu[1]; + report->type = pdu[2]; + + DBG("Report 0x%04x: id 0x%02x type %s", report->value_handle, + report->id, type_to_string(report->type)); + + /* Enable notifications only for Input Reports */ + if (report->type == HOG_REPORT_TYPE_INPUT) + read_char(report->hog, report->hog->attrib, report->ccc_handle, + ccc_read_cb, report); +} + +static void external_report_reference_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data); + +static void discover_external_cb(uint8_t status, GSList *descs, void *user_data) +{ + struct gatt_request *req = user_data; + struct bt_hog *hog = req->user_data; + + destroy_gatt_req(req); + + if (status != 0) { + error("Discover external descriptors failed: %s", + att_ecode2str(status)); + return; + } + + for ( ; descs; descs = descs->next) { + struct gatt_desc *desc = descs->data; + + read_char(hog, hog->attrib, desc->handle, + external_report_reference_cb, + hog); + } +} + +static void discover_external(struct bt_hog *hog, GAttrib *attrib, + uint16_t start, uint16_t end, + gpointer user_data) +{ + bt_uuid_t uuid; + + if (start > end) + return; + + bt_uuid16_create(&uuid, GATT_EXTERNAL_REPORT_REFERENCE); + + discover_desc(hog, attrib, start, end, discover_external_cb, + user_data); +} + +static void discover_report_cb(uint8_t status, GSList *descs, void *user_data) +{ + struct gatt_request *req = user_data; + struct report *report = req->user_data; + struct bt_hog *hog = report->hog; + + destroy_gatt_req(req); + + if (status != 0) { + error("Discover report descriptors failed: %s", + att_ecode2str(status)); + return; + } + + for ( ; descs; descs = descs->next) { + struct gatt_desc *desc = descs->data; + + switch (desc->uuid16) { + case GATT_CLIENT_CHARAC_CFG_UUID: + report->ccc_handle = desc->handle; + break; + case GATT_REPORT_REFERENCE: + read_char(hog, hog->attrib, desc->handle, + report_reference_cb, report); + break; + } + } +} + +static void discover_report(struct bt_hog *hog, GAttrib *attrib, + uint16_t start, uint16_t end, + gpointer user_data) +{ + if (start > end) + return; + + discover_desc(hog, attrib, start, end, discover_report_cb, user_data); +} + +static void report_read_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct gatt_request *req = user_data; + struct report *report = req->user_data; + + destroy_gatt_req(req); + + if (status != 0) { + error("Error reading Report value: %s", att_ecode2str(status)); + return; + } + + if (report->value) + g_free(report->value); + + report->value = g_memdup(pdu, len); + report->len = len; +} + +static int report_chrc_cmp(const void *data, const void *user_data) +{ + const struct report *report = data; + const struct gatt_char *decl = user_data; + + return report->handle - decl->handle; +} + +static struct report *report_new(struct bt_hog *hog, struct gatt_char *chr) +{ + struct report *report; + GSList *l; + + /* Skip if report already exists */ + l = g_slist_find_custom(hog->reports, chr, report_chrc_cmp); + if (l) + return l->data; + + report = g_new0(struct report, 1); + report->hog = hog; + report->handle = chr->handle; + report->value_handle = chr->value_handle; + report->properties = chr->properties; + hog->reports = g_slist_append(hog->reports, report); + + read_char(hog, hog->attrib, chr->value_handle, report_read_cb, report); + + return report; +} + +static void external_service_char_cb(uint8_t status, GSList *chars, + void *user_data) +{ + struct gatt_request *req = user_data; + struct bt_hog *hog = req->user_data; + struct gatt_primary *primary = hog->primary; + struct report *report; + GSList *l; + + destroy_gatt_req(req); + + if (status != 0) { + const char *str = att_ecode2str(status); + DBG("Discover external service characteristic failed: %s", str); + return; + } + + for (l = chars; l; l = g_slist_next(l)) { + struct gatt_char *chr, *next; + uint16_t start, end; + + chr = l->data; + next = l->next ? l->next->data : NULL; + + DBG("0x%04x UUID: %s properties: %02x", + chr->handle, chr->uuid, chr->properties); + + report = report_new(hog, chr); + start = chr->value_handle + 1; + end = (next ? next->handle - 1 : primary->range.end); + discover_report(hog, hog->attrib, start, end, report); + } +} + +static void external_report_reference_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct gatt_request *req = user_data; + struct bt_hog *hog = req->user_data; + uint16_t uuid16; + bt_uuid_t uuid; + + destroy_gatt_req(req); + + if (status != 0) { + error("Read External Report Reference descriptor failed: %s", + att_ecode2str(status)); + return; + } + + if (plen != 3) { + error("Malformed ATT read response"); + return; + } + + uuid16 = get_le16(&pdu[1]); + DBG("External report reference read, external report characteristic " + "UUID: 0x%04x", uuid16); + + /* Do not discover if is not a Report */ + if (uuid16 != HOG_REPORT_UUID) + return; + + bt_uuid16_create(&uuid, uuid16); + discover_char(hog, hog->attrib, 0x0001, 0xffff, &uuid, + external_service_char_cb, hog); +} + +static int report_cmp(gconstpointer a, gconstpointer b) +{ + const struct report *ra = a, *rb = b; + + /* sort by type first.. */ + if (ra->type != rb->type) + return ra->type - rb->type; + + /* skip id check in case of report id 0 */ + if (!rb->id) + return 0; + + /* ..then by id */ + return ra->id - rb->id; +} + +static struct report *find_report(struct bt_hog *hog, uint8_t type, uint8_t id) +{ + struct report cmp; + GSList *l; + + cmp.type = type; + cmp.id = hog->has_report_id ? id : 0; + + l = g_slist_find_custom(hog->reports, &cmp, report_cmp); + + return l ? l->data : NULL; +} + +static struct report *find_report_by_rtype(struct bt_hog *hog, uint8_t rtype, + uint8_t id) +{ + uint8_t type; + + switch (rtype) { + case UHID_FEATURE_REPORT: + type = HOG_REPORT_TYPE_FEATURE; + break; + case UHID_OUTPUT_REPORT: + type = HOG_REPORT_TYPE_OUTPUT; + break; + case UHID_INPUT_REPORT: + type = HOG_REPORT_TYPE_INPUT; + break; + default: + return NULL; + } + + return find_report(hog, type, id); +} + +static void output_written_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct gatt_request *req = user_data; + + destroy_gatt_req(req); + + if (status != 0) { + error("Write output report failed: %s", att_ecode2str(status)); + return; + } +} + +static void forward_report(struct uhid_event *ev, void *user_data) +{ + struct bt_hog *hog = user_data; + struct report *report; + void *data; + int size; + + report = find_report_by_rtype(hog, ev->u.output.rtype, + ev->u.output.data[0]); + if (!report) + return; + + data = ev->u.output.data; + size = ev->u.output.size; + if (hog->has_report_id && size > 0) { + data++; + --size; + } + + DBG("Sending report type %d ID %d to handle 0x%X", report->type, + report->id, report->value_handle); + + if (hog->attrib == NULL) + return; + + if (report->properties & GATT_CHR_PROP_WRITE) + write_char(hog, hog->attrib, report->value_handle, + data, size, output_written_cb, hog); + else if (report->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP) + gatt_write_cmd(hog->attrib, report->value_handle, + data, size, NULL, NULL); +} + +static void set_report_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct bt_hog *hog = user_data; + struct uhid_event rsp; + int err; + + hog->setrep_att = 0; + + memset(&rsp, 0, sizeof(rsp)); + rsp.type = UHID_SET_REPORT_REPLY; + rsp.u.set_report_reply.id = hog->setrep_id; + rsp.u.set_report_reply.err = status; + + if (status != 0) + error("Error setting Report value: %s", att_ecode2str(status)); + + err = bt_uhid_send(hog->uhid, &rsp); + if (err < 0) + error("bt_uhid_send: %s", strerror(-err)); +} + +static void set_report(struct uhid_event *ev, void *user_data) +{ + struct bt_hog *hog = user_data; + struct report *report; + void *data; + int size; + int err; + + /* uhid never sends reqs in parallel; if there's a req, it timed out */ + if (hog->setrep_att) { + g_attrib_cancel(hog->attrib, hog->setrep_att); + hog->setrep_att = 0; + } + + hog->setrep_id = ev->u.set_report.id; + + report = find_report_by_rtype(hog, ev->u.set_report.rtype, + ev->u.set_report.rnum); + if (!report) { + err = ENOTSUP; + goto fail; + } + + data = ev->u.set_report.data; + size = ev->u.set_report.size; + if (hog->has_report_id && size > 0) { + data++; + --size; + } + + DBG("Sending report type %d ID %d to handle 0x%X", report->type, + report->id, report->value_handle); + + if (hog->attrib == NULL) + return; + + hog->setrep_att = gatt_write_char(hog->attrib, + report->value_handle, + data, size, set_report_cb, + hog); + if (!hog->setrep_att) { + err = ENOMEM; + goto fail; + } + + return; +fail: + /* cancel the request on failure */ + set_report_cb(err, NULL, 0, hog); +} + +static void get_report_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct bt_hog *hog = user_data; + struct uhid_event rsp; + int err; + + hog->getrep_att = 0; + + memset(&rsp, 0, sizeof(rsp)); + rsp.type = UHID_GET_REPORT_REPLY; + rsp.u.get_report_reply.id = hog->getrep_id; + + if (status != 0) { + error("Error reading Report value: %s", att_ecode2str(status)); + goto exit; + } + + if (len == 0) { + error("Error reading Report, length %d", len); + status = EIO; + goto exit; + } + + if (pdu[0] != 0x0b) { + error("Error reading Report, invalid response: %02x", pdu[0]); + status = EPROTO; + goto exit; + } + + --len; + ++pdu; + if (hog->has_report_id && len > 0) { + --len; + ++pdu; + } + + rsp.u.get_report_reply.size = len; + memcpy(rsp.u.get_report_reply.data, pdu, len); + +exit: + rsp.u.get_report_reply.err = status; + err = bt_uhid_send(hog->uhid, &rsp); + if (err < 0) + error("bt_uhid_send: %s", strerror(-err)); +} + +static void get_report(struct uhid_event *ev, void *user_data) +{ + struct bt_hog *hog = user_data; + struct report *report; + guint8 err; + + /* uhid never sends reqs in parallel; if there's a req, it timed out */ + if (hog->getrep_att) { + g_attrib_cancel(hog->attrib, hog->getrep_att); + hog->getrep_att = 0; + } + + hog->getrep_id = ev->u.get_report.id; + + report = find_report_by_rtype(hog, ev->u.get_report.rtype, + ev->u.get_report.rnum); + if (!report) { + err = ENOTSUP; + goto fail; + } + + hog->getrep_att = gatt_read_char(hog->attrib, + report->value_handle, + get_report_cb, hog); + if (!hog->getrep_att) { + err = ENOMEM; + goto fail; + } + + return; + +fail: + /* cancel the request on failure */ + get_report_cb(err, NULL, 0, hog); +} + +static bool get_descriptor_item_info(uint8_t *buf, ssize_t blen, ssize_t *len, + bool *is_long) +{ + if (!blen) + return false; + + *is_long = (buf[0] == 0xfe); + + if (*is_long) { + if (blen < 3) + return false; + + /* + * long item: + * byte 0 -> 0xFE + * byte 1 -> data size + * byte 2 -> tag + * + data + */ + + *len = buf[1] + 3; + } else { + uint8_t b_size; + + /* + * short item: + * byte 0[1..0] -> data size (=0, 1, 2, 4) + * byte 0[3..2] -> type + * byte 0[7..4] -> tag + * + data + */ + + b_size = buf[0] & 0x03; + *len = (b_size ? 1 << (b_size - 1) : 0) + 1; + } + + /* item length should be no more than input buffer length */ + return *len <= blen; +} + +static char *item2string(char *str, uint8_t *buf, uint8_t len) +{ + char *p = str; + int i; + + /* + * Since long item tags are not defined except for vendor ones, we + * just ensure that short items are printed properly (up to 5 bytes). + */ + for (i = 0; i < 6 && i < len; i++) + p += sprintf(p, " %02x", buf[i]); + + /* + * If there are some data left, just add continuation mark to indicate + * this. + */ + if (i < len) + sprintf(p, " ..."); + + return str; +} + +static void report_map_read_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + struct gatt_request *req = user_data; + struct bt_hog *hog = req->user_data; + uint8_t value[HOG_REPORT_MAP_MAX_SIZE]; + struct uhid_event ev; + ssize_t vlen; + char itemstr[20]; /* 5x3 (data) + 4 (continuation) + 1 (null) */ + int i, err; + GError *gerr = NULL; + + destroy_gatt_req(req); + + DBG("HoG inspecting report map"); + + if (status != 0) { + error("Report Map read failed: %s", att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, plen, value, sizeof(value)); + if (vlen < 0) { + error("ATT protocol error"); + return; + } + + DBG("Report MAP:"); + for (i = 0; i < vlen;) { + ssize_t ilen = 0; + bool long_item = false; + + if (get_descriptor_item_info(&value[i], vlen - i, &ilen, + &long_item)) { + /* Report ID is short item with prefix 100001xx */ + if (!long_item && (value[i] & 0xfc) == 0x84) + hog->has_report_id = TRUE; + + DBG("\t%s", item2string(itemstr, &value[i], ilen)); + + i += ilen; + } else { + error("Report Map parsing failed at %d", i); + + /* Just print remaining items at once and break */ + DBG("\t%s", item2string(itemstr, &value[i], vlen - i)); + break; + } + } + + /* create uHID device */ + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + + bt_io_get(g_attrib_get_channel(hog->attrib), &gerr, + BT_IO_OPT_SOURCE, ev.u.create.phys, + BT_IO_OPT_DEST, ev.u.create.uniq, + BT_IO_OPT_INVALID); + if (gerr) { + error("Failed to connection details: %s", gerr->message); + g_error_free(gerr); + return; + } + + strncpy((char *) ev.u.create.name, hog->name, + sizeof(ev.u.create.name) - 1); + ev.u.create.vendor = hog->vendor; + ev.u.create.product = hog->product; + ev.u.create.version = hog->version; + ev.u.create.country = hog->bcountrycode; + ev.u.create.bus = BUS_BLUETOOTH; + ev.u.create.rd_data = value; + ev.u.create.rd_size = vlen; + + err = bt_uhid_send(hog->uhid, &ev); + if (err < 0) { + error("bt_uhid_send: %s", strerror(-err)); + return; + } + + bt_uhid_register(hog->uhid, UHID_OUTPUT, forward_report, hog); + bt_uhid_register(hog->uhid, UHID_GET_REPORT, get_report, hog); + bt_uhid_register(hog->uhid, UHID_SET_REPORT, set_report, hog); + + hog->uhid_created = true; + + DBG("HoG created uHID device"); +} + +static void info_read_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + struct gatt_request *req = user_data; + struct bt_hog *hog = req->user_data; + uint8_t value[HID_INFO_SIZE]; + ssize_t vlen; + + destroy_gatt_req(req); + + if (status != 0) { + error("HID Information read failed: %s", + att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, plen, value, sizeof(value)); + if (vlen != 4) { + error("ATT protocol error"); + return; + } + + hog->bcdhid = get_le16(&value[0]); + hog->bcountrycode = value[2]; + hog->flags = value[3]; + + DBG("bcdHID: 0x%04X bCountryCode: 0x%02X Flags: 0x%02X", + hog->bcdhid, hog->bcountrycode, hog->flags); +} + +static void proto_mode_read_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + struct gatt_request *req = user_data; + struct bt_hog *hog = req->user_data; + uint8_t value; + ssize_t vlen; + + destroy_gatt_req(req); + + if (status != 0) { + error("Protocol Mode characteristic read failed: %s", + att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, plen, &value, sizeof(value)); + if (vlen < 0) { + error("ATT protocol error"); + return; + } + + if (value == HOG_PROTO_MODE_BOOT) { + uint8_t nval = HOG_PROTO_MODE_REPORT; + + DBG("HoG is operating in Boot Procotol Mode"); + + gatt_write_cmd(hog->attrib, hog->proto_mode_handle, &nval, + sizeof(nval), NULL, NULL); + } else if (value == HOG_PROTO_MODE_REPORT) + DBG("HoG is operating in Report Protocol Mode"); +} + +static void char_discovered_cb(uint8_t status, GSList *chars, void *user_data) +{ + struct gatt_request *req = user_data; + struct bt_hog *hog = req->user_data; + struct gatt_primary *primary = hog->primary; + bt_uuid_t report_uuid, report_map_uuid, info_uuid; + bt_uuid_t proto_mode_uuid, ctrlpt_uuid; + struct report *report; + GSList *l; + uint16_t info_handle = 0, proto_mode_handle = 0; + + destroy_gatt_req(req); + + DBG("HoG inspecting characteristics"); + + if (status != 0) { + const char *str = att_ecode2str(status); + DBG("Discover all characteristics failed: %s", str); + return; + } + + bt_uuid16_create(&report_uuid, HOG_REPORT_UUID); + bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID); + bt_uuid16_create(&info_uuid, HOG_INFO_UUID); + bt_uuid16_create(&proto_mode_uuid, HOG_PROTO_MODE_UUID); + bt_uuid16_create(&ctrlpt_uuid, HOG_CONTROL_POINT_UUID); + + for (l = chars; l; l = g_slist_next(l)) { + struct gatt_char *chr, *next; + bt_uuid_t uuid; + uint16_t start, end; + + chr = l->data; + next = l->next ? l->next->data : NULL; + + DBG("0x%04x UUID: %s properties: %02x", + chr->handle, chr->uuid, chr->properties); + + bt_string_to_uuid(&uuid, chr->uuid); + + start = chr->value_handle + 1; + end = (next ? next->handle - 1 : primary->range.end); + + if (bt_uuid_cmp(&uuid, &report_uuid) == 0) { + report = report_new(hog, chr); + discover_report(hog, hog->attrib, start, end, report); + } else if (bt_uuid_cmp(&uuid, &report_map_uuid) == 0) { + DBG("HoG discovering report map"); + read_char(hog, hog->attrib, chr->value_handle, + report_map_read_cb, hog); + discover_external(hog, hog->attrib, start, end, hog); + } else if (bt_uuid_cmp(&uuid, &info_uuid) == 0) + info_handle = chr->value_handle; + else if (bt_uuid_cmp(&uuid, &proto_mode_uuid) == 0) + proto_mode_handle = chr->value_handle; + else if (bt_uuid_cmp(&uuid, &ctrlpt_uuid) == 0) + hog->ctrlpt_handle = chr->value_handle; + } + + if (proto_mode_handle) { + hog->proto_mode_handle = proto_mode_handle; + read_char(hog, hog->attrib, proto_mode_handle, + proto_mode_read_cb, hog); + } + + if (info_handle) + read_char(hog, hog->attrib, info_handle, info_read_cb, hog); +} + +static void report_free(void *data) +{ + struct report *report = data; + + g_free(report->value); + g_free(report); +} + +static void cancel_gatt_req(struct gatt_request *req) +{ + if (g_attrib_cancel(req->hog->attrib, req->id)) + destroy_gatt_req(req); +} + +static void hog_free(void *data) +{ + struct bt_hog *hog = data; + + bt_hog_detach(hog); + + queue_destroy(hog->bas, (void *) bt_bas_unref); + g_slist_free_full(hog->instances, hog_free); + + bt_scpp_unref(hog->scpp); + bt_dis_unref(hog->dis); + bt_uhid_unref(hog->uhid); + g_slist_free_full(hog->reports, report_free); + g_free(hog->name); + g_free(hog->primary); + queue_destroy(hog->gatt_op, (void *) destroy_gatt_req); + g_free(hog); +} + +struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor, + uint16_t product, uint16_t version, + struct gatt_db *db) +{ + return bt_hog_new(-1, name, vendor, product, version, db); +} + +static void foreach_hog_report(struct gatt_db_attribute *attr, void *user_data) +{ + struct report *report = user_data; + struct bt_hog *hog = report->hog; + const bt_uuid_t *uuid; + bt_uuid_t ref_uuid, ccc_uuid; + uint16_t handle; + + handle = gatt_db_attribute_get_handle(attr); + uuid = gatt_db_attribute_get_type(attr); + + bt_uuid16_create(&ref_uuid, GATT_REPORT_REFERENCE); + if (!bt_uuid_cmp(&ref_uuid, uuid)) { + read_char(hog, hog->attrib, handle, report_reference_cb, + report); + return; + } + + bt_uuid16_create(&ccc_uuid, GATT_CLIENT_CHARAC_CFG_UUID); + if (!bt_uuid_cmp(&ccc_uuid, uuid)) + report->ccc_handle = handle; +} + +static int report_attr_cmp(const void *data, const void *user_data) +{ + const struct report *report = data; + const struct gatt_db_attribute *attr = user_data; + + return report->handle - gatt_db_attribute_get_handle(attr); +} + +static struct report *report_add(struct bt_hog *hog, + struct gatt_db_attribute *attr) +{ + struct report *report; + GSList *l; + + /* Skip if report already exists */ + l = g_slist_find_custom(hog->reports, attr, report_attr_cmp); + if (l) + return l->data; + + report = g_new0(struct report, 1); + report->hog = hog; + + gatt_db_attribute_get_char_data(attr, &report->handle, + &report->value_handle, + &report->properties, + NULL, NULL); + + hog->reports = g_slist_append(hog->reports, report); + + read_char(hog, hog->attrib, report->value_handle, report_read_cb, + report); + + return report; +} + +static void foreach_hog_external(struct gatt_db_attribute *attr, + void *user_data) +{ + struct bt_hog *hog = user_data; + const bt_uuid_t *uuid; + bt_uuid_t ext_uuid; + uint16_t handle; + + handle = gatt_db_attribute_get_handle(attr); + uuid = gatt_db_attribute_get_type(attr); + + bt_uuid16_create(&ext_uuid, GATT_EXTERNAL_REPORT_REFERENCE); + if (!bt_uuid_cmp(&ext_uuid, uuid)) + read_char(hog, hog->attrib, handle, + external_report_reference_cb, hog); +} + +static void foreach_hog_chrc(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_hog *hog = user_data; + bt_uuid_t uuid, report_uuid, report_map_uuid, info_uuid; + bt_uuid_t proto_mode_uuid, ctrlpt_uuid; + uint16_t handle, value_handle; + + gatt_db_attribute_get_char_data(attr, &handle, &value_handle, NULL, + NULL, &uuid); + + bt_uuid16_create(&report_uuid, HOG_REPORT_UUID); + if (!bt_uuid_cmp(&report_uuid, &uuid)) { + struct report *report = report_add(hog, attr); + gatt_db_service_foreach_desc(attr, foreach_hog_report, report); + return; + } + + bt_uuid16_create(&report_map_uuid, HOG_REPORT_MAP_UUID); + if (!bt_uuid_cmp(&report_map_uuid, &uuid)) { + read_char(hog, hog->attrib, value_handle, report_map_read_cb, + hog); + gatt_db_service_foreach_desc(attr, foreach_hog_external, hog); + return; + } + + bt_uuid16_create(&info_uuid, HOG_INFO_UUID); + if (!bt_uuid_cmp(&info_uuid, &uuid)) { + read_char(hog, hog->attrib, value_handle, info_read_cb, hog); + return; + } + + bt_uuid16_create(&proto_mode_uuid, HOG_PROTO_MODE_UUID); + if (!bt_uuid_cmp(&proto_mode_uuid, &uuid)) { + hog->proto_mode_handle = value_handle; + read_char(hog, hog->attrib, value_handle, proto_mode_read_cb, + hog); + } + + bt_uuid16_create(&ctrlpt_uuid, HOG_CONTROL_POINT_UUID); + if (!bt_uuid_cmp(&ctrlpt_uuid, &uuid)) + hog->ctrlpt_handle = value_handle; +} + +static struct bt_hog *hog_new(int fd, const char *name, uint16_t vendor, + uint16_t product, uint16_t version, + struct gatt_db_attribute *attr) +{ + struct bt_hog *hog; + + hog = g_try_new0(struct bt_hog, 1); + if (!hog) + return NULL; + + hog->gatt_op = queue_new(); + hog->bas = queue_new(); + + if (fd < 0) + hog->uhid = bt_uhid_new_default(); + else + hog->uhid = bt_uhid_new(fd); + + hog->uhid_fd = fd; + + if (!hog->gatt_op || !hog->bas || !hog->uhid) { + hog_free(hog); + return NULL; + } + + hog->name = g_strdup(name); + hog->vendor = vendor; + hog->product = product; + hog->version = version; + hog->attr = attr; + + return hog; +} + +static void hog_attach_instace(struct bt_hog *hog, + struct gatt_db_attribute *attr) +{ + struct bt_hog *instance; + + if (!hog->attr) { + hog->attr = attr; + gatt_db_service_foreach_char(hog->attr, foreach_hog_chrc, hog); + return; + } + + instance = hog_new(hog->uhid_fd, hog->name, hog->vendor, + hog->product, hog->version, attr); + if (!instance) + return; + + hog->instances = g_slist_append(hog->instances, instance); +} + +static void foreach_hog_service(struct gatt_db_attribute *attr, void *user_data) +{ + struct bt_hog *hog = user_data; + + hog_attach_instace(hog, attr); +} + +static void dis_notify(uint8_t source, uint16_t vendor, uint16_t product, + uint16_t version, void *user_data) +{ + struct bt_hog *hog = user_data; + + hog->vendor = vendor; + hog->product = product; + hog->version = version; +} + +struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor, + uint16_t product, uint16_t version, + struct gatt_db *db) +{ + struct bt_hog *hog; + + hog = hog_new(fd, name, vendor, product, version, NULL); + if (!hog) + return NULL; + + if (db) { + bt_uuid_t uuid; + + /* Handle the HID services */ + bt_uuid16_create(&uuid, HOG_UUID16); + gatt_db_foreach_service(db, &uuid, foreach_hog_service, hog); + if (!hog->attr) { + hog_free(hog); + return NULL; + } + + /* Try creating a DIS instance in case pid/vid are not set */ + if (!vendor && !product) { + hog->dis = bt_dis_new(db); + bt_dis_set_notification(hog->dis, dis_notify, hog); + } + } + + return bt_hog_ref(hog); +} + +struct bt_hog *bt_hog_ref(struct bt_hog *hog) +{ + if (!hog) + return NULL; + + __sync_fetch_and_add(&hog->ref_count, 1); + + return hog; +} + +void bt_hog_unref(struct bt_hog *hog) +{ + if (!hog) + return; + + if (__sync_sub_and_fetch(&hog->ref_count, 1)) + return; + + hog_free(hog); +} + +static void find_included_cb(uint8_t status, GSList *services, void *user_data) +{ + struct gatt_request *req = user_data; + GSList *l; + + DBG(""); + + destroy_gatt_req(req); + + if (status) { + const char *str = att_ecode2str(status); + DBG("Find included failed: %s", str); + return; + } + + for (l = services; l; l = l->next) { + struct gatt_included *include = l->data; + + DBG("included: handle %x, uuid %s", + include->handle, include->uuid); + } +} + +static void hog_attach_scpp(struct bt_hog *hog, struct gatt_primary *primary) +{ + if (hog->scpp) { + bt_scpp_attach(hog->scpp, hog->attrib); + return; + } + + hog->scpp = bt_scpp_new(primary); + if (hog->scpp) + bt_scpp_attach(hog->scpp, hog->attrib); +} + +static void hog_attach_dis(struct bt_hog *hog, struct gatt_primary *primary) +{ + if (hog->dis) { + bt_dis_attach(hog->dis, hog->attrib); + return; + } + + hog->dis = bt_dis_new_primary(primary); + if (hog->dis) { + bt_dis_set_notification(hog->dis, dis_notify, hog); + bt_dis_attach(hog->dis, hog->attrib); + } +} + +static void hog_attach_bas(struct bt_hog *hog, struct gatt_primary *primary) +{ + struct bt_bas *instance; + + instance = bt_bas_new(primary); + + bt_bas_attach(instance, hog->attrib); + queue_push_head(hog->bas, instance); +} + +static void hog_attach_hog(struct bt_hog *hog, struct gatt_primary *primary) +{ + struct bt_hog *instance; + + if (!hog->primary) { + hog->primary = g_memdup(primary, sizeof(*primary)); + discover_char(hog, hog->attrib, primary->range.start, + primary->range.end, NULL, + char_discovered_cb, hog); + find_included(hog, hog->attrib, primary->range.start, + primary->range.end, find_included_cb, hog); + return; + } + + instance = bt_hog_new(hog->uhid_fd, hog->name, hog->vendor, + hog->product, hog->version, NULL); + if (!instance) + return; + + instance->primary = g_memdup(primary, sizeof(*primary)); + find_included(instance, hog->attrib, primary->range.start, + primary->range.end, find_included_cb, instance); + + bt_hog_attach(instance, hog->attrib); + hog->instances = g_slist_append(hog->instances, instance); +} + +static void primary_cb(uint8_t status, GSList *services, void *user_data) +{ + struct gatt_request *req = user_data; + struct bt_hog *hog = req->user_data; + struct gatt_primary *primary; + GSList *l; + + DBG(""); + + destroy_gatt_req(req); + + if (status) { + const char *str = att_ecode2str(status); + DBG("Discover primary failed: %s", str); + return; + } + + if (!services) { + DBG("No primary service found"); + return; + } + + for (l = services; l; l = l->next) { + primary = l->data; + + if (strcmp(primary->uuid, SCAN_PARAMETERS_UUID) == 0) { + hog_attach_scpp(hog, primary); + continue; + } + + if (strcmp(primary->uuid, DEVICE_INFORMATION_UUID) == 0) { + hog_attach_dis(hog, primary); + continue; + } + + if (strcmp(primary->uuid, BATTERY_UUID) == 0) { + hog_attach_bas(hog, primary); + continue; + } + + if (strcmp(primary->uuid, HOG_UUID) == 0) + hog_attach_hog(hog, primary); + } +} + +bool bt_hog_attach(struct bt_hog *hog, void *gatt) +{ + GSList *l; + + if (hog->attrib) + return false; + + hog->attrib = g_attrib_ref(gatt); + + if (!hog->attr && !hog->primary) { + discover_primary(hog, hog->attrib, NULL, primary_cb, hog); + return true; + } + + if (hog->scpp) + bt_scpp_attach(hog->scpp, gatt); + + if (hog->dis) + bt_dis_attach(hog->dis, gatt); + + queue_foreach(hog->bas, (void *) bt_bas_attach, gatt); + + for (l = hog->instances; l; l = l->next) { + struct bt_hog *instance = l->data; + + bt_hog_attach(instance, gatt); + } + + if (!hog->uhid_created) { + DBG("HoG discovering characteristics"); + if (hog->attr) + gatt_db_service_foreach_char(hog->attr, + foreach_hog_chrc, hog); + else + discover_char(hog, hog->attrib, + hog->primary->range.start, + hog->primary->range.end, NULL, + char_discovered_cb, hog); + return true; + } + + for (l = hog->reports; l; l = l->next) { + struct report *r = l->data; + + r->notifyid = g_attrib_register(hog->attrib, + ATT_OP_HANDLE_NOTIFY, + r->value_handle, + report_value_cb, r, NULL); + } + + return true; +} + +void bt_hog_detach(struct bt_hog *hog) +{ + GSList *l; + + if (!hog->attrib) + return; + + queue_foreach(hog->bas, (void *) bt_bas_detach, NULL); + + for (l = hog->instances; l; l = l->next) { + struct bt_hog *instance = l->data; + + bt_hog_detach(instance); + } + + for (l = hog->reports; l; l = l->next) { + struct report *r = l->data; + + if (r->notifyid > 0) { + g_attrib_unregister(hog->attrib, r->notifyid); + r->notifyid = 0; + } + } + + if (hog->scpp) + bt_scpp_detach(hog->scpp); + + if (hog->dis) + bt_dis_detach(hog->dis); + + queue_foreach(hog->gatt_op, (void *) cancel_gatt_req, NULL); + g_attrib_unref(hog->attrib); + hog->attrib = NULL; +} + +int bt_hog_set_control_point(struct bt_hog *hog, bool suspend) +{ + uint8_t value = suspend ? 0x00 : 0x01; + + if (hog->attrib == NULL) + return -ENOTCONN; + + if (hog->ctrlpt_handle == 0) + return -ENOTSUP; + + gatt_write_cmd(hog->attrib, hog->ctrlpt_handle, &value, + sizeof(value), NULL, NULL); + + return 0; +} + +int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type) +{ + struct report *report; + GSList *l; + + if (!hog) + return -EINVAL; + + if (!hog->attrib) + return -ENOTCONN; + + report = find_report(hog, type, 0); + if (!report) + return -ENOTSUP; + + DBG("hog: Write report, handle 0x%X", report->value_handle); + + if (report->properties & GATT_CHR_PROP_WRITE) + write_char(hog, hog->attrib, report->value_handle, + data, size, output_written_cb, hog); + + if (report->properties & GATT_CHR_PROP_WRITE_WITHOUT_RESP) + gatt_write_cmd(hog->attrib, report->value_handle, + data, size, NULL, NULL); + + for (l = hog->instances; l; l = l->next) { + struct bt_hog *instance = l->data; + + bt_hog_send_report(instance, data, size, type); + } + + return 0; +} diff --git a/profiles/input/hog-lib.h b/profiles/input/hog-lib.h new file mode 100644 index 0000000..415dc63 --- /dev/null +++ b/profiles/input/hog-lib.h @@ -0,0 +1,41 @@ +/* + * + * 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 + * + */ + +struct bt_hog; + +struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor, + uint16_t product, uint16_t version, + struct gatt_db *db); + +struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor, + uint16_t product, uint16_t version, + struct gatt_db *db); + +struct bt_hog *bt_hog_ref(struct bt_hog *hog); +void bt_hog_unref(struct bt_hog *hog); + +bool bt_hog_attach(struct bt_hog *hog, void *gatt); +void bt_hog_detach(struct bt_hog *hog); + +int bt_hog_set_control_point(struct bt_hog *hog, bool suspend); +int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type); diff --git a/profiles/input/hog.c b/profiles/input/hog.c new file mode 100644 index 0000000..83c017d --- /dev/null +++ b/profiles/input/hog.c @@ -0,0 +1,243 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Marcel Holtmann + * Copyright (C) 2012 Nordic Semiconductor Inc. + * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/log.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/shared/util.h" +#include "src/shared/uhid.h" +#include "src/shared/queue.h" +#include "src/plugin.h" + +#include "suspend.h" +#include "attrib/att.h" +#include "attrib/gattrib.h" +#include "attrib/gatt.h" +#include "hog-lib.h" + +#define HOG_UUID "00001812-0000-1000-8000-00805f9b34fb" + +struct hog_device { + struct btd_device *device; + struct bt_hog *hog; +}; + +static gboolean suspend_supported = FALSE; +static struct queue *devices = NULL; + +static void hog_device_accept(struct hog_device *dev, struct gatt_db *db) +{ + char name[248]; + uint16_t vendor, product, version; + + if (dev->hog) + return; + + if (device_name_known(dev->device)) + device_get_name(dev->device, name, sizeof(name)); + else + strcpy(name, "bluez-hog-device"); + + vendor = btd_device_get_vendor(dev->device); + product = btd_device_get_product(dev->device); + version = btd_device_get_version(dev->device); + + DBG("name=%s vendor=0x%X, product=0x%X, version=0x%X", name, vendor, + product, version); + + dev->hog = bt_hog_new_default(name, vendor, product, version, db); +} + +static struct hog_device *hog_device_new(struct btd_device *device) +{ + struct hog_device *dev; + + dev = new0(struct hog_device, 1); + dev->device = btd_device_ref(device); + + if (!devices) + devices = queue_new(); + + queue_push_tail(devices, dev); + + return dev; +} + +static void hog_device_free(void *data) +{ + struct hog_device *dev = data; + + queue_remove(devices, dev); + if (queue_isempty(devices)) { + queue_destroy(devices, NULL); + devices = NULL; + } + + btd_device_unref(dev->device); + bt_hog_unref(dev->hog); + free(dev); +} + +static void set_suspend(gpointer data, gpointer user_data) +{ + struct hog_device *dev = data; + gboolean suspend = GPOINTER_TO_INT(user_data); + + bt_hog_set_control_point(dev->hog, suspend); +} + +static void suspend_callback(void) +{ + gboolean suspend = TRUE; + + DBG("Suspending ..."); + + queue_foreach(devices, set_suspend, GINT_TO_POINTER(suspend)); +} + +static void resume_callback(void) +{ + gboolean suspend = FALSE; + + DBG("Resuming ..."); + + queue_foreach(devices, set_suspend, GINT_TO_POINTER(suspend)); +} + +static int hog_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + const char *path = device_get_path(device); + struct hog_device *dev; + + DBG("path %s", path); + + dev = hog_device_new(device); + if (!dev) + return -EINVAL; + + btd_service_set_user_data(service, dev); + return 0; +} + +static void hog_remove(struct btd_service *service) +{ + struct hog_device *dev = btd_service_get_user_data(service); + struct btd_device *device = btd_service_get_device(service); + const char *path = device_get_path(device); + + DBG("path %s", path); + + hog_device_free(dev); +} + +static int hog_accept(struct btd_service *service) +{ + struct hog_device *dev = btd_service_get_user_data(service); + struct btd_device *device = btd_service_get_device(service); + struct gatt_db *db = btd_device_get_gatt_db(device); + GAttrib *attrib = btd_device_get_attrib(device); + + if (!dev->hog) { + hog_device_accept(dev, db); + if (!dev->hog) + return -EINVAL; + } + + /* TODO: Replace GAttrib with bt_gatt_client */ + bt_hog_attach(dev->hog, attrib); + + btd_service_connecting_complete(service, 0); + + return 0; +} + +static int hog_disconnect(struct btd_service *service) +{ + struct hog_device *dev = btd_service_get_user_data(service); + + bt_hog_detach(dev->hog); + bt_hog_unref(dev->hog); + dev->hog = NULL; + + btd_service_disconnecting_complete(service, 0); + + return 0; +} + +static struct btd_profile hog_profile = { + .name = "input-hog", + .remote_uuid = HOG_UUID, + .device_probe = hog_probe, + .device_remove = hog_remove, + .accept = hog_accept, + .disconnect = hog_disconnect, + .auto_connect = true, +}; + +static int hog_init(void) +{ + int err; + + err = suspend_init(suspend_callback, resume_callback); + if (err < 0) + error("Loading suspend plugin failed: %s (%d)", strerror(-err), + -err); + else + suspend_supported = TRUE; + + return btd_profile_register(&hog_profile); +} + +static void hog_exit(void) +{ + if (suspend_supported) + suspend_exit(); + + btd_profile_unregister(&hog_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(hog, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + hog_init, hog_exit) diff --git a/profiles/input/input.conf b/profiles/input/input.conf new file mode 100644 index 0000000..3e1d65a --- /dev/null +++ b/profiles/input/input.conf @@ -0,0 +1,13 @@ +# Configuration file for the input service + +# This section contains options which are not specific to any +# particular interface +[General] + +# Set idle timeout (in minutes) before the connection will +# be disconnect (defaults to 0 for no timeout) +#IdleTimeout=30 + +# Enable HID protocol handling in userspace input profile +# Defaults to false (HIDP handled in HIDP kernel module) +#UserspaceHID=true diff --git a/profiles/input/manager.c b/profiles/input/manager.c new file mode 100644 index 0000000..1d31b06 --- /dev/null +++ b/profiles/input/manager.c @@ -0,0 +1,133 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "src/log.h" +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" + +#include "device.h" +#include "server.h" + +static int hid_server_probe(struct btd_profile *p, struct btd_adapter *adapter) +{ + return server_start(btd_adapter_get_address(adapter)); +} + +static void hid_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + server_stop(btd_adapter_get_address(adapter)); +} + +static struct btd_profile input_profile = { + .name = "input-hid", + .local_uuid = HID_UUID, + .remote_uuid = HID_UUID, + + .auto_connect = true, + .connect = input_device_connect, + .disconnect = input_device_disconnect, + + .device_probe = input_device_register, + .device_remove = input_device_unregister, + + .adapter_probe = hid_server_probe, + .adapter_remove = hid_server_remove, +}; + +static GKeyFile *load_config_file(const char *file) +{ + GKeyFile *keyfile; + GError *err = NULL; + + keyfile = g_key_file_new(); + + if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { + if (!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + error("Parsing %s failed: %s", file, err->message); + g_error_free(err); + g_key_file_free(keyfile); + return NULL; + } + + return keyfile; +} + +static int input_init(void) +{ + GKeyFile *config; + GError *err = NULL; + + config = load_config_file(CONFIGDIR "/input.conf"); + if (config) { + int idle_timeout; + gboolean uhid_enabled; + + idle_timeout = g_key_file_get_integer(config, "General", + "IdleTimeout", &err); + if (!err) { + DBG("input.conf: IdleTimeout=%d", idle_timeout); + input_set_idle_timeout(idle_timeout * 60); + } else + g_clear_error(&err); + + uhid_enabled = g_key_file_get_boolean(config, "General", + "UserspaceHID", &err); + if (!err) { + DBG("input.conf: UserspaceHID=%s", uhid_enabled ? + "true" : "false"); + input_enable_userspace_hid(uhid_enabled); + } else + g_clear_error(&err); + } + + btd_profile_register(&input_profile); + + if (config) + g_key_file_free(config); + + return 0; +} + +static void input_exit(void) +{ + btd_profile_unregister(&input_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(input, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + input_init, input_exit) diff --git a/profiles/input/server.c b/profiles/input/server.c new file mode 100644 index 0000000..f2c8c0f --- /dev/null +++ b/profiles/input/server.c @@ -0,0 +1,341 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/log.h" +#include "src/uuid-helper.h" +#include "btio/btio.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" + +#include "sixaxis.h" +#include "device.h" +#include "server.h" + +struct confirm_data { + bdaddr_t dst; + GIOChannel *io; +}; + +static GSList *servers = NULL; +struct input_server { + bdaddr_t src; + GIOChannel *ctrl; + GIOChannel *intr; + struct confirm_data *confirm; +}; + +static int server_cmp(gconstpointer s, gconstpointer user_data) +{ + const struct input_server *server = s; + const bdaddr_t *src = user_data; + + return bacmp(&server->src, src); +} + +struct sixaxis_data { + GIOChannel *chan; + uint16_t psm; +}; + +static void sixaxis_sdp_cb(struct btd_device *dev, int err, void *user_data) +{ + struct sixaxis_data *data = user_data; + const bdaddr_t *src; + + DBG("err %d (%s)", err, strerror(-err)); + + if (err < 0) + goto fail; + + src = btd_adapter_get_address(device_get_adapter(dev)); + + if (input_device_set_channel(src, device_get_address(dev), data->psm, + data->chan) < 0) + goto fail; + + g_io_channel_unref(data->chan); + g_free(data); + + return; + +fail: + g_io_channel_shutdown(data->chan, TRUE, NULL); + g_io_channel_unref(data->chan); + g_free(data); +} + +static void sixaxis_browse_sdp(const bdaddr_t *src, const bdaddr_t *dst, + GIOChannel *chan, uint16_t psm) +{ + struct btd_device *device; + struct sixaxis_data *data; + + device = btd_adapter_find_device(adapter_find(src), dst, BDADDR_BREDR); + if (!device) + return; + + data = g_new0(struct sixaxis_data, 1); + data->chan = g_io_channel_ref(chan); + data->psm = psm; + + if (psm == L2CAP_PSM_HIDP_CTRL) + device_discover_services(device); + + device_wait_for_svc_complete(device, sixaxis_sdp_cb, data); +} + +static bool dev_is_sixaxis(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct btd_device *device; + uint16_t vid, pid; + const struct cable_pairing *cp; + + device = btd_adapter_find_device(adapter_find(src), dst, BDADDR_BREDR); + if (!device) + return false; + + vid = btd_device_get_vendor(device); + pid = btd_device_get_product(device); + + cp = get_pairing(vid, pid); + if (cp && (cp->type == CABLE_PAIRING_SIXAXIS || + cp->type == CABLE_PAIRING_DS4)) + return true; + + return false; +} + +static void connect_event_cb(GIOChannel *chan, GError *err, gpointer data) +{ + uint16_t psm; + bdaddr_t src, dst; + char address[18]; + GError *gerr = NULL; + int ret; + + if (err) { + error("%s", err->message); + return; + } + + bt_io_get(chan, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_PSM, &psm, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + ba2str(&dst, address); + DBG("Incoming connection from %s on PSM %d", address, psm); + + ret = input_device_set_channel(&src, &dst, psm, chan); + if (ret == 0) + return; + + if (ret == -ENOENT && dev_is_sixaxis(&src, &dst)) { + sixaxis_browse_sdp(&src, &dst, chan, psm); + return; + } + + error("Refusing input device connect: %s (%d)", strerror(-ret), -ret); + + /* Send unplug virtual cable to unknown devices */ + if (ret == -ENOENT && psm == L2CAP_PSM_HIDP_CTRL) { + unsigned char unplug = 0x15; + int sk = g_io_channel_unix_get_fd(chan); + if (write(sk, &unplug, sizeof(unplug)) < 0) + error("Unable to send virtual cable unplug"); + } + + g_io_channel_shutdown(chan, TRUE, NULL); +} + +static void auth_callback(DBusError *derr, void *user_data) +{ + struct input_server *server = user_data; + struct confirm_data *confirm = server->confirm; + GError *err = NULL; + + if (derr) { + error("Access denied: %s", derr->message); + goto reject; + } + + if (!input_device_exists(&server->src, &confirm->dst) && + !dev_is_sixaxis(&server->src, &confirm->dst)) + return; + + if (!bt_io_accept(confirm->io, connect_event_cb, server, NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + goto reject; + } + + g_io_channel_unref(confirm->io); + g_free(server->confirm); + server->confirm = NULL; + + return; + +reject: + g_io_channel_shutdown(confirm->io, TRUE, NULL); + g_io_channel_unref(confirm->io); + server->confirm = NULL; + input_device_close_channels(&server->src, &confirm->dst); + g_free(confirm); +} + +static void confirm_event_cb(GIOChannel *chan, gpointer user_data) +{ + struct input_server *server = user_data; + bdaddr_t src, dst; + GError *err = NULL; + char addr[18]; + guint ret; + + DBG(""); + + bt_io_get(chan, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + g_io_channel_shutdown(chan, TRUE, NULL); + return; + } + + ba2str(&dst, addr); + + if (server->confirm) { + error("Refusing connection from %s: setup in progress", addr); + goto drop; + } + + if (!input_device_exists(&src, &dst) && !dev_is_sixaxis(&src, &dst)) { + error("Refusing connection from %s: unknown device", addr); + goto drop; + } + + server->confirm = g_new0(struct confirm_data, 1); + server->confirm->io = g_io_channel_ref(chan); + bacpy(&server->confirm->dst, &dst); + + ret = btd_request_authorization(&src, &dst, HID_UUID, + auth_callback, server); + if (ret != 0) + return; + + error("input: authorization for device %s failed", addr); + + g_io_channel_unref(server->confirm->io); + g_free(server->confirm); + server->confirm = NULL; + +drop: + input_device_close_channels(&src, &dst); + g_io_channel_shutdown(chan, TRUE, NULL); +} + +int server_start(const bdaddr_t *src) +{ + struct input_server *server; + GError *err = NULL; + + server = g_new0(struct input_server, 1); + bacpy(&server->src, src); + + server->ctrl = bt_io_listen(connect_event_cb, NULL, + server, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!server->ctrl) { + error("Failed to listen on control channel"); + g_error_free(err); + g_free(server); + return -1; + } + + server->intr = bt_io_listen(NULL, confirm_event_cb, + server, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!server->intr) { + error("Failed to listen on interrupt channel"); + g_io_channel_unref(server->ctrl); + g_error_free(err); + g_free(server); + return -1; + } + + servers = g_slist_append(servers, server); + + return 0; +} + +void server_stop(const bdaddr_t *src) +{ + struct input_server *server; + GSList *l; + + l = g_slist_find_custom(servers, src, server_cmp); + if (!l) + return; + + server = l->data; + + g_io_channel_shutdown(server->intr, TRUE, NULL); + g_io_channel_unref(server->intr); + + g_io_channel_shutdown(server->ctrl, TRUE, NULL); + g_io_channel_unref(server->ctrl); + + servers = g_slist_remove(servers, server); + g_free(server); +} diff --git a/profiles/input/server.h b/profiles/input/server.h new file mode 100644 index 0000000..74159bb --- /dev/null +++ b/profiles/input/server.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int server_start(const bdaddr_t *src); +void server_stop(const bdaddr_t *src); diff --git a/profiles/input/sixaxis.h b/profiles/input/sixaxis.h new file mode 100644 index 0000000..8e6f3cc --- /dev/null +++ b/profiles/input/sixaxis.h @@ -0,0 +1,95 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009,2017 Bastien Nocera + * Copyright (C) 2011 Antonio Ospite + * Copyright (C) 2013 Szymon Janc + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 _SIXAXIS_H_ +#define _SIXAXIS_H_ + +typedef enum { + CABLE_PAIRING_UNSUPPORTED = 0, + CABLE_PAIRING_SIXAXIS, + CABLE_PAIRING_DS4, +} CablePairingType; + +struct cable_pairing { + const char *name; + uint16_t source; + uint16_t vid; + uint16_t pid; + uint16_t version; + CablePairingType type; +}; + +static inline const struct cable_pairing * +get_pairing(uint16_t vid, uint16_t pid) +{ + static const struct cable_pairing devices[] = { + { + .name = "Sony PLAYSTATION(R)3 Controller", + .source = 0x0002, + .vid = 0x054c, + .pid = 0x0268, + .version = 0x0000, + .type = CABLE_PAIRING_SIXAXIS, + }, + { + .name = "Navigation Controller", + .source = 0x0002, + .vid = 0x054c, + .pid = 0x042f, + .version = 0x0000, + .type = CABLE_PAIRING_SIXAXIS, + }, + { + .name = "Wireless Controller", + .source = 0x0002, + .vid = 0x054c, + .pid = 0x05c4, + .version = 0x0001, + .type = CABLE_PAIRING_DS4, + }, + { + .name = "Wireless Controller", + .source = 0x0002, + .vid = 0x054c, + .pid = 0x09cc, + .version = 0x0001, + .type = CABLE_PAIRING_DS4, + }, + }; + guint i; + + for (i = 0; i < G_N_ELEMENTS(devices); i++) { + if (devices[i].vid != vid) + continue; + if (devices[i].pid != pid) + continue; + + return &devices[i]; + } + + return NULL; +} + +#endif /* _SIXAXIS_H_ */ diff --git a/profiles/input/suspend-dummy.c b/profiles/input/suspend-dummy.c new file mode 100644 index 0000000..542ae25 --- /dev/null +++ b/profiles/input/suspend-dummy.c @@ -0,0 +1,162 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Nordic Semiconductor Inc. + * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "src/log.h" +#include "suspend.h" + +#define HOG_SUSPEND_FIFO "/tmp/hogsuspend" + +static suspend_event suspend_cb = NULL; +static resume_event resume_cb = NULL; +static guint watch = 0; + +static int fifo_open(void); + +static gboolean read_fifo(GIOChannel *io, GIOCondition cond, gpointer user_data) +{ + char buffer[12]; + gsize offset, left, bread; + GIOStatus iostatus; + + if (cond & (G_IO_ERR | G_IO_HUP)) { + /* + * Both ends needs to be open simultaneously before proceeding + * any input or output operation. When the remote closes the + * channel, hup signal is received on this end. + */ + fifo_open(); + return FALSE; + } + + offset = 0; + left = sizeof(buffer) - 1; + memset(buffer, 0, sizeof(buffer)); + + do { + iostatus = g_io_channel_read_chars(io, &buffer[offset], left, + &bread, NULL); + + offset += bread; + left -= bread; + if (left == 0) + break; + } while (iostatus == G_IO_STATUS_NORMAL); + + if (g_ascii_strncasecmp("suspend", buffer, 7) == 0) + suspend_cb(); + else if (g_ascii_strncasecmp("resume", buffer, 6) == 0) + resume_cb(); + + return TRUE; +} + +static int fifo_open(void) +{ + GIOCondition condition = G_IO_IN | G_IO_ERR | G_IO_HUP; + GIOChannel *fifoio; + int fd; + + fd = open(HOG_SUSPEND_FIFO, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + int err = -errno; + error("Can't open FIFO (%s): %s(%d)", HOG_SUSPEND_FIFO, + strerror(-err), -err); + return err; + } + + fifoio = g_io_channel_unix_new(fd); + g_io_channel_set_close_on_unref(fifoio, TRUE); + + watch = g_io_add_watch(fifoio, condition, read_fifo, NULL); + + g_io_channel_unref(fifoio); + + return 0; +} + +int suspend_init(suspend_event suspend, resume_event resume) +{ + struct stat st; + int ret; + + DBG(""); + + suspend_cb = suspend; + resume_cb = resume; + + if (stat(HOG_SUSPEND_FIFO, &st) == 0) { + if (!S_ISFIFO(st.st_mode)) { + error("Unexpected non-FIFO %s file", HOG_SUSPEND_FIFO); + return -EIO; + } + + if (unlink(HOG_SUSPEND_FIFO) < 0) { + int err = -errno; + error("Failed to remove FIFO (%s): %s (%d)", + HOG_SUSPEND_FIFO, strerror(-err), -err); + return err; + } + } + + if (mkfifo(HOG_SUSPEND_FIFO, S_IRUSR | S_IWUSR) < 0) { + int err = -errno; + + error("Can't create FIFO (%s): %s (%d)", HOG_SUSPEND_FIFO, + strerror(-err), -err); + return err; + } + + DBG("Created suspend-dummy FIFO on %s", HOG_SUSPEND_FIFO); + + ret = fifo_open(); + if (ret < 0) + unlink(HOG_SUSPEND_FIFO); + + return ret; +} + +void suspend_exit(void) +{ + if (watch > 0) { + g_source_remove(watch); + watch = 0; + } + + unlink(HOG_SUSPEND_FIFO); +} diff --git a/profiles/input/suspend-none.c b/profiles/input/suspend-none.c new file mode 100644 index 0000000..c619bb1 --- /dev/null +++ b/profiles/input/suspend-none.c @@ -0,0 +1,42 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Nordic Semiconductor Inc. + * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "src/log.h" +#include "suspend.h" + +int suspend_init(suspend_event suspend, resume_event resume) +{ + DBG(""); + + return 0; +} + +void suspend_exit(void) +{ + DBG(""); +} diff --git a/profiles/input/suspend.h b/profiles/input/suspend.h new file mode 100644 index 0000000..bfee3cf --- /dev/null +++ b/profiles/input/suspend.h @@ -0,0 +1,29 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Nordic Semiconductor Inc. + * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +typedef void (*suspend_event) (void); +typedef void (*resume_event) (void); + +int suspend_init(suspend_event suspend, resume_event resume); +void suspend_exit(void); diff --git a/profiles/input/uhid_copy.h b/profiles/input/uhid_copy.h new file mode 100644 index 0000000..0ef73d4 --- /dev/null +++ b/profiles/input/uhid_copy.h @@ -0,0 +1,199 @@ +#ifndef __UHID_H_ +#define __UHID_H_ + +/* + * User-space I/O driver support for HID subsystem + * Copyright (c) 2012 David Herrmann + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* + * Public header for user-space communication. We try to keep every structure + * aligned but to be safe we also use __attribute__((__packed__)). Therefore, + * the communication should be ABI compatible even between architectures. + */ + +#include +#include +#include + +enum uhid_event_type { + __UHID_LEGACY_CREATE, + UHID_DESTROY, + UHID_START, + UHID_STOP, + UHID_OPEN, + UHID_CLOSE, + UHID_OUTPUT, + __UHID_LEGACY_OUTPUT_EV, + __UHID_LEGACY_INPUT, + UHID_GET_REPORT, + UHID_GET_REPORT_REPLY, + UHID_CREATE2, + UHID_INPUT2, + UHID_SET_REPORT, + UHID_SET_REPORT_REPLY, +}; + +struct uhid_create2_req { + __u8 name[128]; + __u8 phys[64]; + __u8 uniq[64]; + __u16 rd_size; + __u16 bus; + __u32 vendor; + __u32 product; + __u32 version; + __u32 country; + __u8 rd_data[HID_MAX_DESCRIPTOR_SIZE]; +} __attribute__((__packed__)); + +enum uhid_dev_flag { + UHID_DEV_NUMBERED_FEATURE_REPORTS = (1ULL << 0), + UHID_DEV_NUMBERED_OUTPUT_REPORTS = (1ULL << 1), + UHID_DEV_NUMBERED_INPUT_REPORTS = (1ULL << 2), +}; + +struct uhid_start_req { + __u64 dev_flags; +}; + +#define UHID_DATA_MAX 4096 + +enum uhid_report_type { + UHID_FEATURE_REPORT, + UHID_OUTPUT_REPORT, + UHID_INPUT_REPORT, +}; + +struct uhid_input2_req { + __u16 size; + __u8 data[UHID_DATA_MAX]; +} __attribute__((__packed__)); + +struct uhid_output_req { + __u8 data[UHID_DATA_MAX]; + __u16 size; + __u8 rtype; +} __attribute__((__packed__)); + +struct uhid_get_report_req { + __u32 id; + __u8 rnum; + __u8 rtype; +} __attribute__((__packed__)); + +struct uhid_get_report_reply_req { + __u32 id; + __u16 err; + __u16 size; + __u8 data[UHID_DATA_MAX]; +} __attribute__((__packed__)); + +struct uhid_set_report_req { + __u32 id; + __u8 rnum; + __u8 rtype; + __u16 size; + __u8 data[UHID_DATA_MAX]; +} __attribute__((__packed__)); + +struct uhid_set_report_reply_req { + __u32 id; + __u16 err; +} __attribute__((__packed__)); + +/* + * Compat Layer + * All these commands and requests are obsolete. You should avoid using them in + * new code. We support them for backwards-compatibility, but you might not get + * access to new feature in case you use them. + */ + +enum uhid_legacy_event_type { + UHID_CREATE = __UHID_LEGACY_CREATE, + UHID_OUTPUT_EV = __UHID_LEGACY_OUTPUT_EV, + UHID_INPUT = __UHID_LEGACY_INPUT, + UHID_FEATURE = UHID_GET_REPORT, + UHID_FEATURE_ANSWER = UHID_GET_REPORT_REPLY, +}; + +/* Obsolete! Use UHID_CREATE2. */ +struct uhid_create_req { + __u8 name[128]; + __u8 phys[64]; + __u8 uniq[64]; + __u8 *rd_data; + __u16 rd_size; + + __u16 bus; + __u32 vendor; + __u32 product; + __u32 version; + __u32 country; +} __attribute__((__packed__)); + +/* Obsolete! Use UHID_INPUT2. */ +struct uhid_input_req { + __u8 data[UHID_DATA_MAX]; + __u16 size; +} __attribute__((__packed__)); + +/* Obsolete! Kernel uses UHID_OUTPUT exclusively now. */ +struct uhid_output_ev_req { + __u16 type; + __u16 code; + __s32 value; +} __attribute__((__packed__)); + +/* Obsolete! Kernel uses ABI compatible UHID_GET_REPORT. */ +struct uhid_feature_req { + __u32 id; + __u8 rnum; + __u8 rtype; +} __attribute__((__packed__)); + +/* Obsolete! Use ABI compatible UHID_GET_REPORT_REPLY. */ +struct uhid_feature_answer_req { + __u32 id; + __u16 err; + __u16 size; + __u8 data[UHID_DATA_MAX]; +} __attribute__((__packed__)); + +/* + * UHID Events + * All UHID events from and to the kernel are encoded as "struct uhid_event". + * The "type" field contains a UHID_* type identifier. All payload depends on + * that type and can be accessed via ev->u.XYZ accordingly. + * If user-space writes short events, they're extended with 0s by the kernel. If + * the kernel writes short events, user-space shall extend them with 0s. + */ + +struct uhid_event { + __u32 type; + + union { + struct uhid_create_req create; + struct uhid_input_req input; + struct uhid_output_req output; + struct uhid_output_ev_req output_ev; + struct uhid_feature_req feature; + struct uhid_get_report_req get_report; + struct uhid_feature_answer_req feature_answer; + struct uhid_get_report_reply_req get_report_reply; + struct uhid_create2_req create2; + struct uhid_input2_req input2; + struct uhid_set_report_req set_report; + struct uhid_set_report_reply_req set_report_reply; + struct uhid_start_req start; + } u; +} __attribute__((__packed__)); + +#endif /* __UHID_H_ */ diff --git a/profiles/midi/libmidi.c b/profiles/midi/libmidi.c new file mode 100644 index 0000000..4b4df79 --- /dev/null +++ b/profiles/midi/libmidi.c @@ -0,0 +1,466 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015,2016 Felipe F. Tonello + * Copyright (C) 2016 ROLI Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * + */ + +#include + +/* Avoid linkage problem on unit-tests */ +#ifndef MIDI_TEST +#include "src/backtrace.h" +#define MIDI_ASSERT(_expr) btd_assert(_expr) +#else +#define MIDI_ASSERT(_expr) g_assert(_expr) +#endif +#include "libmidi.h" + +inline static void buffer_append_byte(struct midi_buffer *buffer, + const uint8_t byte) +{ + buffer->data[buffer->len++] = byte; +} + +inline static void buffer_append_data(struct midi_buffer *buffer, + const uint8_t *data, size_t size) +{ + memcpy(buffer->data + buffer->len, data, size); + buffer->len += size; +} + +inline static uint8_t buffer_reverse_get(struct midi_buffer *buffer, size_t i) +{ + MIDI_ASSERT(buffer->len > i); + return buffer->data[buffer->len - (i + 1)]; +} + +inline static void buffer_reverse_set(struct midi_buffer *buffer, size_t i, + const uint8_t byte) +{ + MIDI_ASSERT(buffer->len > i); + buffer->data[buffer->len - (i + 1)] = byte; +} + +inline static size_t parser_get_available_size(struct midi_write_parser *parser) +{ + return parser->stream_size - parser->midi_stream.len; +} + +inline static uint8_t sysex_get(const snd_seq_event_t *ev, size_t i) +{ + MIDI_ASSERT(ev->data.ext.len > i); + return ((uint8_t*)ev->data.ext.ptr)[i]; +} + +inline static void append_timestamp_high_maybe(struct midi_write_parser *parser) +{ + uint8_t timestamp_high = 0x80; + + if (midi_write_has_data(parser)) + return; + + parser->rtime = g_get_monotonic_time() / 1000; /* convert µs to ms */ + timestamp_high |= (parser->rtime & 0x1F80) >> 7; + /* set timestampHigh */ + buffer_append_byte(&parser->midi_stream, timestamp_high); +} + +inline static void append_timestamp_low(struct midi_write_parser *parser) +{ + const uint8_t timestamp_low = 0x80 | (parser->rtime & 0x7F); + buffer_append_byte(&parser->midi_stream, timestamp_low); +} + +int midi_write_init(struct midi_write_parser *parser, size_t buffer_size) +{ + int err; + + parser->rtime = 0; + parser->rstatus = SND_SEQ_EVENT_NONE; + parser->stream_size = buffer_size; + + parser->midi_stream.data = malloc(buffer_size); + if (!parser->midi_stream.data) + return -ENOMEM; + + parser->midi_stream.len = 0; + + err = snd_midi_event_new(buffer_size, &parser->midi_ev); + if (err < 0) + free(parser->midi_stream.data); + + return err; +} + +int midi_read_init(struct midi_read_parser *parser) +{ + int err; + + parser->rstatus = 0; + parser->rtime = -1; + parser->timestamp = 0; + parser->timestamp_low = 0; + parser->timestamp_high = 0; + + parser->sysex_stream.data = malloc(MIDI_SYSEX_MAX_SIZE); + if (!parser->sysex_stream.data) + return -ENOMEM; + + parser->sysex_stream.len = 0; + + err = snd_midi_event_new(MIDI_MSG_MAX_SIZE, &parser->midi_ev); + if (err < 0) + free(parser->sysex_stream.data); + + return err; +} + +/* Algorithm: + 1) check initial timestampLow: + if used_sysex == 0, then tsLow = 1, else tsLow = 0 + 2) calculate sysex size of current packet: + 2a) first check special case: + if midi->out_length - 1 (tsHigh) - tsLow == + sysex_length - used_sysex + size is: min(midi->out_length - 1 - tsLow, + sysex_length - used_sysex - 1) + 2b) else size is: min(midi->out_length - 1 - tsLow, + sysex_length - used_sysex) + 3) check if packet contains F7: fill respective tsLow byte +*/ +static void read_ev_sysex(struct midi_write_parser *parser, const snd_seq_event_t *ev, + midi_read_ev_cb write_cb, void *user_data) +{ + unsigned int used_sysex = 0; + + /* We need at least 2 bytes (timestampLow + F0) */ + if (parser_get_available_size(parser) < 2) { + /* send current message and start new one */ + write_cb(parser, user_data); + midi_write_reset(parser); + append_timestamp_high_maybe(parser); + } + + /* timestampLow on initial F0 */ + if (sysex_get(ev, 0) == 0xF0) + append_timestamp_low(parser); + + do { + unsigned int size_of_sysex; + + append_timestamp_high_maybe(parser); + + size_of_sysex = MIN(parser_get_available_size(parser), + ev->data.ext.len - used_sysex); + + if (parser_get_available_size(parser) == ev->data.ext.len - used_sysex) + size_of_sysex--; + + buffer_append_data(&parser->midi_stream, + ev->data.ext.ptr + used_sysex, + size_of_sysex); + used_sysex += size_of_sysex; + + if (parser_get_available_size(parser) <= 1 && + buffer_reverse_get(&parser->midi_stream, 0) != 0xF7) { + write_cb(parser, user_data); + midi_write_reset(parser); + } + } while (used_sysex < ev->data.ext.len); + + /* check for F7 and update respective timestampLow byte */ + if (midi_write_has_data(parser) && + buffer_reverse_get(&parser->midi_stream, 0) == 0xF7) { + /* remove 0xF7 from buffer, append timestamp and add 0xF7 back again */ + parser->midi_stream.len--; + append_timestamp_low(parser); + buffer_append_byte(&parser->midi_stream, 0xF7); + } +} + +static void read_ev_others(struct midi_write_parser *parser, const snd_seq_event_t *ev, + midi_read_ev_cb write_cb, void *user_data) +{ + int length; + + /* check for running status */ + if (parser->rstatus != ev->type) { + snd_midi_event_reset_decode(parser->midi_ev); + append_timestamp_low(parser); + } + + /* each midi message has timestampLow byte to follow */ + length = snd_midi_event_decode(parser->midi_ev, + parser->midi_stream.data + + parser->midi_stream.len, + parser_get_available_size(parser), + ev); + + if (length == -ENOMEM) { + /* remove previously added timestampLow */ + if (parser->rstatus != ev->type) + parser->midi_stream.len--; + write_cb(parser, user_data); + /* cleanup state for next packet */ + snd_midi_event_reset_decode(parser->midi_ev); + midi_write_reset(parser); + append_timestamp_high_maybe(parser); + append_timestamp_low(parser); + length = snd_midi_event_decode(parser->midi_ev, + parser->midi_stream.data + + parser->midi_stream.len, + parser_get_available_size(parser), + ev); + } + + if (length > 0) + parser->midi_stream.len += length; +} + +void midi_read_ev(struct midi_write_parser *parser, const snd_seq_event_t *ev, + midi_read_ev_cb write_cb, void *user_data) +{ + MIDI_ASSERT(write_cb); + + append_timestamp_high_maybe(parser); + + /* SysEx is special case: + SysEx has two timestampLow bytes, before F0 and F7 + */ + if (ev->type == SND_SEQ_EVENT_SYSEX) + read_ev_sysex(parser, ev, write_cb, user_data); + else + read_ev_others(parser, ev, write_cb, user_data); + + parser->rstatus = ev->type; + + if (parser_get_available_size(parser) == 0) { + write_cb(parser, user_data); + midi_write_reset(parser); + } +} + +static void update_ev_timestamp(struct midi_read_parser *parser, + snd_seq_event_t *ev, uint16_t ts_low) +{ + int delta_timestamp; + int delta_rtime; + int64_t rtime_current; + uint16_t timestamp; + + /* time_low overwflow results on time_high to increment by one */ + if (parser->timestamp_low > ts_low) + parser->timestamp_high++; + + timestamp = (parser->timestamp_high << 7) | parser->timestamp_low; + + rtime_current = g_get_monotonic_time() / 1000; /* convert µs to ms */ + delta_timestamp = timestamp - (int)parser->timestamp; + delta_rtime = rtime_current - parser->rtime; + + if (delta_rtime > MIDI_MAX_TIMESTAMP) + parser->rtime = rtime_current; + else { + + /* If delta_timestamp is way to big than delta_rtime, + this means that the device sent a message in the past, + so we have to compensate for this. */ + if (delta_timestamp > 7000 && delta_rtime < 1000) + delta_timestamp = 0; + + /* check if timestamp did overflow */ + if (delta_timestamp < 0) { + /* same timestamp in the past problem */ + if ((delta_timestamp + MIDI_MAX_TIMESTAMP) > 7000 && + delta_rtime < 1000) + delta_timestamp = 0; + else + delta_timestamp = delta_timestamp + MIDI_MAX_TIMESTAMP; + } + + parser->rtime += delta_timestamp; + } + + parser->timestamp += delta_timestamp; + if (parser->timestamp > MIDI_MAX_TIMESTAMP) + parser->timestamp %= MIDI_MAX_TIMESTAMP + 1; + + /* set event timestamp */ + /* TODO: update event timestamp here! */ +} + +static size_t handle_end_of_sysex(struct midi_read_parser *parser, + snd_seq_event_t *ev, + const uint8_t *data, + size_t sysex_length) +{ + uint8_t time_low; + + /* At this time, timestampLow is copied as the last byte, + instead of 0xF7 */ + buffer_append_data(&parser->sysex_stream, data, sysex_length); + + time_low = buffer_reverse_get(&parser->sysex_stream, 0) & 0x7F; + + /* Remove timestamp byte */ + buffer_reverse_set(&parser->sysex_stream, 0, 0xF7); + + /* Update event */ + update_ev_timestamp(parser, ev, time_low); + snd_seq_ev_set_sysex(ev, parser->sysex_stream.len, + parser->sysex_stream.data); + + return sysex_length + 1; /* +1 because of timestampLow */ +} + + + +size_t midi_read_raw(struct midi_read_parser *parser, const uint8_t *data, + size_t size, snd_seq_event_t *ev /* OUT */) +{ + size_t midi_size = 0; + size_t i = 0; + bool err = false; + + if (parser->timestamp_high == 0) + parser->timestamp_high = data[i++] & 0x3F; + + snd_midi_event_reset_encode(parser->midi_ev); + + /* timestamp byte */ + if (data[i] & 0x80) { + update_ev_timestamp(parser, ev, data[i] & 0x7F); + + /* check for wrong BLE-MIDI message size */ + if (++i == size) { + err = true; + goto _finish; + } + } + + /* cleanup sysex_stream if message is broken or is a new SysEx */ + if (data[i] >= 0x80 && data[i] != 0xF7 && parser->sysex_stream.len > 0) + parser->sysex_stream.len = 0; + + switch (data[i]) { + case 0xF8 ... 0XFF: + /* System Real-Time Messages */ + midi_size = 1; + break; + + /* System Common Messages */ + case 0xF0: /* SysEx Start */ { + uint8_t *pos; + + /* cleanup Running Status Message */ + parser->rstatus = 0; + + /* Avoid parsing if SysEx is contained in one BLE packet */ + if ((pos = memchr(data + i, 0xF7, size - i))) { + const size_t sysex_length = pos - (data + i); + midi_size = handle_end_of_sysex(parser, ev, data + i, + sysex_length); + } else { + buffer_append_data(&parser->sysex_stream, data + i, size - i); + err = true; /* Not an actual error, just incomplete message */ + midi_size = size - i; + } + + goto _finish; + } + + case 0xF1: + case 0xF3: + midi_size = 2; + break; + case 0xF2: + midi_size = 3; + break; + case 0xF4: + case 0xF5: /* Ignore */ + i++; + err = true; + goto _finish; + break; + case 0xF6: + midi_size = 1; + break; + case 0xF7: /* SysEx End */ + buffer_append_byte(&parser->sysex_stream, 0xF7); + snd_seq_ev_set_sysex(ev, parser->sysex_stream.len, + parser->sysex_stream.data); + + midi_size = 1; /* timestampLow was alredy processed */ + goto _finish; + + case 0x80 ... 0xEF: + /* + * Channel Voice Messages, Channel Mode Messages + * and Control Change Messages. + */ + parser->rstatus = data[i]; + midi_size = (data[i] >= 0xC0 && data[i] <= 0xDF) ? 2 : 3; + break; + + case 0x00 ... 0x7F: + + /* Check for SysEx messages */ + if (parser->sysex_stream.len > 0) { + uint8_t *pos; + + if ((pos = memchr(data + i, 0xF7, size - i))) { + const size_t sysex_length = pos - (data + i); + midi_size = handle_end_of_sysex(parser, ev, data + i, + sysex_length); + } else { + buffer_append_data(&parser->sysex_stream, data + i, size - i); + err = true; /* Not an actual error, just incomplete message */ + midi_size = size - i; + } + + goto _finish; + } + + /* Running State Message was not set */ + if (parser->rstatus == 0) { + midi_size = 1; + err = true; + goto _finish; + } + + snd_midi_event_encode_byte(parser->midi_ev, parser->rstatus, ev); + midi_size = (parser->rstatus >= 0xC0 && parser->rstatus <= 0xDF) ? 1 : 2; + break; + } + + if ((i + midi_size) > size) { + err = true; + goto _finish; + } + + snd_midi_event_encode(parser->midi_ev, data + i, midi_size, ev); + +_finish: + if (err) + ev->type = SND_SEQ_EVENT_NONE; + + return i + midi_size; +} diff --git a/profiles/midi/libmidi.h b/profiles/midi/libmidi.h new file mode 100644 index 0000000..9d94065 --- /dev/null +++ b/profiles/midi/libmidi.h @@ -0,0 +1,125 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015,2016 Felipe F. Tonello + * Copyright (C) 2016 ROLI Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public 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 LIBMIDI_H +#define LIBMIDI_H + +#include +#include +#include + +#define MIDI_UUID "03B80E5A-EDE8-4B33-A751-6CE34EC4C700" +#define MIDI_IO_UUID "7772E5DB-3868-4112-A1A9-F2669D106BF3" + +#define MIDI_MAX_TIMESTAMP 8191 +#define MIDI_MSG_MAX_SIZE 12 +#define MIDI_SYSEX_MAX_SIZE (4 * 1024) + +struct midi_buffer { + uint8_t *data; + size_t len; +}; + +/* MIDI I/O Write parser */ + +struct midi_write_parser { + int64_t rtime; /* last writer's real time */ + snd_seq_event_type_t rstatus; /* running status event type */ + struct midi_buffer midi_stream; /* MIDI I/O byte stream */ + size_t stream_size; /* what is the maximum size of the midi_stream array */ + snd_midi_event_t *midi_ev; /* midi<->seq event */ +}; + +int midi_write_init(struct midi_write_parser *parser, size_t buffer_size); + +static inline void midi_write_free(struct midi_write_parser *parser) +{ + free(parser->midi_stream.data); + snd_midi_event_free(parser->midi_ev); +} + +static inline void midi_write_reset(struct midi_write_parser *parser) +{ + parser->rstatus = SND_SEQ_EVENT_NONE; + parser->midi_stream.len = 0; +} + +static inline bool midi_write_has_data(const struct midi_write_parser *parser) +{ + return parser->midi_stream.len > 0; +} + +static inline const uint8_t * midi_write_data(const struct midi_write_parser *parser) +{ + return parser->midi_stream.data; +} + +static inline size_t midi_write_data_size(const struct midi_write_parser *parser) +{ + return parser->midi_stream.len; +} + +typedef void (*midi_read_ev_cb)(const struct midi_write_parser *parser, void *); + +/* It creates BLE-MIDI raw packets from the a sequencer event. If the packet + is full, then it calls write_cb and resets its internal state as many times + as necessary. + */ +void midi_read_ev(struct midi_write_parser *parser, const snd_seq_event_t *ev, + midi_read_ev_cb write_cb, void *user_data); + +/* MIDI I/O Read parser */ + +struct midi_read_parser { + uint8_t rstatus; /* running status byte */ + int64_t rtime; /* last reader's real time */ + int16_t timestamp; /* last MIDI-BLE timestamp */ + int8_t timestamp_low; /* MIDI-BLE timestampLow from the current packet */ + int8_t timestamp_high; /* MIDI-BLE timestampHigh from the current packet */ + struct midi_buffer sysex_stream; /* SysEx stream */ + snd_midi_event_t *midi_ev; /* midi<->seq event */ +}; + +int midi_read_init(struct midi_read_parser *parser); + +static inline void midi_read_free(struct midi_read_parser *parser) +{ + free(parser->sysex_stream.data); + snd_midi_event_free(parser->midi_ev); +} + +static inline void midi_read_reset(struct midi_read_parser *parser) +{ + parser->rstatus = 0; + parser->timestamp_low = 0; + parser->timestamp_high = 0; +} + +/* Parses raw BLE-MIDI messages and populates a sequencer event representing the + current MIDI message. It returns how much raw data was processed. + */ +size_t midi_read_raw(struct midi_read_parser *parser, const uint8_t *data, + size_t size, snd_seq_event_t *ev /* OUT */); + +#endif /* LIBMIDI_H */ diff --git a/profiles/midi/midi.c b/profiles/midi/midi.c new file mode 100644 index 0000000..1ee5cb1 --- /dev/null +++ b/profiles/midi/midi.c @@ -0,0 +1,487 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015,2016 Felipe F. Tonello + * Copyright (C) 2016 ROLI Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Information about this plugin: + * + * This plugin implements the MIDI over Bluetooth Low-Energy (BLE-MIDI) 1.0 + * specification as published by MMA in November/2015. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/shared/io.h" +#include "src/log.h" +#include "attrib/att.h" + +#include "libmidi.h" + +struct midi { + struct btd_device *dev; + struct gatt_db *db; + struct bt_gatt_client *client; + unsigned int io_cb_id; + struct io *io; + uint16_t midi_io_handle; + + /* ALSA handlers */ + snd_seq_t *seq_handle; + int seq_client_id; + int seq_port_id; + + /* MIDI parser*/ + struct midi_read_parser midi_in; + struct midi_write_parser midi_out; +}; + +static bool midi_write_cb(struct io *io, void *user_data) +{ + struct midi *midi = user_data; + int err; + + void foreach_cb(const struct midi_write_parser *parser, void *user_data) { + struct midi *midi = user_data; + bt_gatt_client_write_without_response(midi->client, + midi->midi_io_handle, + false, + midi_write_data(parser), + midi_write_data_size(parser)); + }; + + do { + snd_seq_event_t *event = NULL; + + err = snd_seq_event_input(midi->seq_handle, &event); + + if (err < 0 || !event) + break; + + midi_read_ev(&midi->midi_out, event, foreach_cb, midi); + + } while (err > 0); + + if (midi_write_has_data(&midi->midi_out)) + bt_gatt_client_write_without_response(midi->client, + midi->midi_io_handle, + false, + midi_write_data(&midi->midi_out), + midi_write_data_size(&midi->midi_out)); + + midi_write_reset(&midi->midi_out); + + return true; +} + +static void midi_io_value_cb(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct midi *midi = user_data; + snd_seq_event_t ev; + unsigned int i = 0; + + if (length < 3) { + warn("MIDI I/O: Wrong packet format: length is %u bytes but it should " + "be at least 3 bytes", length); + return; + } + + snd_seq_ev_clear(&ev); + snd_seq_ev_set_source(&ev, midi->seq_port_id); + snd_seq_ev_set_subs(&ev); + snd_seq_ev_set_direct(&ev); + + midi_read_reset(&midi->midi_in); + + while (i < length) { + size_t count = midi_read_raw(&midi->midi_in, value + i, length - i, &ev); + + if (count == 0) + goto _err; + + if (ev.type != SND_SEQ_EVENT_NONE) + snd_seq_event_output_direct(midi->seq_handle, &ev); + + i += count; + } + + return; + +_err: + error("Wrong BLE-MIDI message"); +} + +static void midi_io_ccc_written_cb(uint16_t att_ecode, void *user_data) +{ + if (att_ecode != 0) { + error("MIDI I/O: notifications not enabled %s", + att_ecode2str(att_ecode)); + return; + } + + DBG("MIDI I/O: notification enabled"); +} + +static void midi_io_initial_read_cb(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct midi *midi = user_data; + + if (!success) { + error("MIDI I/O: Failed to read initial request"); + return; + } + + /* request notify */ + midi->io_cb_id = + bt_gatt_client_register_notify(midi->client, + midi->midi_io_handle, + midi_io_ccc_written_cb, + midi_io_value_cb, + midi, + NULL); +} + +static void handle_midi_io(struct midi *midi, uint16_t value_handle) +{ + DBG("MIDI I/O handle: 0x%04x", value_handle); + + midi->midi_io_handle = value_handle; + + /* + * The BLE-MIDI 1.0 spec specifies that The Central shall attempt to + * read the MIDI I/O characteristic of the Peripheral right after + * estrablhishing a connection with the accessory. + */ + if (!bt_gatt_client_read_value(midi->client, + value_handle, + midi_io_initial_read_cb, + midi, + NULL)) + DBG("MIDI I/O: Failed to send request to read initial value"); +} + +static void handle_characteristic(struct gatt_db_attribute *attr, + void *user_data) +{ + struct midi *midi = user_data; + uint16_t value_handle; + bt_uuid_t uuid, midi_io_uuid; + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, + NULL, &uuid)) { + error("Failed to obtain characteristic data"); + return; + } + + bt_string_to_uuid(&midi_io_uuid, MIDI_IO_UUID); + + if (bt_uuid_cmp(&midi_io_uuid, &uuid) == 0) + handle_midi_io(midi, value_handle); + else { + char uuid_str[MAX_LEN_UUID_STR]; + + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + DBG("Unsupported characteristic: %s", uuid_str); + } +} + +static void foreach_midi_service(struct gatt_db_attribute *attr, + void *user_data) +{ + struct midi *midi = user_data; + + gatt_db_service_foreach_char(attr, handle_characteristic, midi); +} + +static int midi_device_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct midi *midi; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("MIDI GATT Driver profile probe (%s)", addr); + + /* Ignore, if we were probed for this device already */ + midi = btd_service_get_user_data(service); + if (midi) { + error("Profile probed twice for the same device!"); + return -EADDRINUSE; + } + + midi = g_new0(struct midi, 1); + if (!midi) + return -ENOMEM; + + midi->dev = btd_device_ref(device); + + btd_service_set_user_data(service, midi); + + return 0; +} + +static void midi_device_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct midi *midi; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("MIDI GATT Driver profile remove (%s)", addr); + + midi = btd_service_get_user_data(service); + if (!midi) { + error("MIDI Service not handled by profile"); + return; + } + + btd_device_unref(midi->dev); + g_free(midi); +} + +static int midi_accept(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gatt_db *db = btd_device_get_gatt_db(device); + struct bt_gatt_client *client = btd_device_get_gatt_client(device); + bt_uuid_t midi_uuid; + struct pollfd pfd; + struct midi *midi; + char addr[18]; + char device_name[MAX_NAME_LENGTH + 11]; /* 11 = " Bluetooth\0"*/ + int err; + snd_seq_client_info_t *info; + + ba2str(device_get_address(device), addr); + DBG("MIDI GATT Driver profile accept (%s)", addr); + + midi = btd_service_get_user_data(service); + if (!midi) { + error("MIDI Service not handled by profile"); + return -ENODEV; + } + + /* Port Name */ + memset(device_name, 0, sizeof(device_name)); + if (device_name_known(device)) + device_get_name(device, device_name, sizeof(device_name)); + else + strncpy(device_name, addr, sizeof(device_name)); + + /* ALSA Sequencer Client and Port Setup */ + err = snd_seq_open(&midi->seq_handle, "default", SND_SEQ_OPEN_DUPLEX, 0); + if (err < 0) { + error("Could not open ALSA Sequencer: %s (%d)", snd_strerror(err), err); + return err; + } + + err = snd_seq_nonblock(midi->seq_handle, SND_SEQ_NONBLOCK); + if (err < 0) { + error("Could not set nonblock mode: %s (%d)", snd_strerror(err), err); + goto _err_handle; + } + + err = snd_seq_set_client_name(midi->seq_handle, device_name); + if (err < 0) { + error("Could not configure ALSA client: %s (%d)", snd_strerror(err), err); + goto _err_handle; + } + + err = snd_seq_client_id(midi->seq_handle); + if (err < 0) { + error("Could retreive ALSA client: %s (%d)", snd_strerror(err), err); + goto _err_handle; + } + midi->seq_client_id = err; + + err = snd_seq_create_simple_port(midi->seq_handle, strcat(device_name, " Bluetooth"), + SND_SEQ_PORT_CAP_READ | + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_READ | + SND_SEQ_PORT_CAP_SUBS_WRITE, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_HARDWARE); + if (err < 0) { + error("Could not create ALSA port: %s (%d)", snd_strerror(err), err); + goto _err_handle; + } + midi->seq_port_id = err; + + snd_seq_client_info_alloca(&info); + err = snd_seq_get_client_info(midi->seq_handle, info); + if (err < 0) + goto _err_port; + + /* list of relevant sequencer events */ + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_NOTEOFF); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_NOTEON); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_KEYPRESS); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_CONTROLLER); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_PGMCHANGE); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_CHANPRESS); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_PITCHBEND); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_SYSEX); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_QFRAME); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_SONGPOS); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_SONGSEL); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_TUNE_REQUEST); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_CLOCK); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_START); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_CONTINUE); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_STOP); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_SENSING); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_RESET); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_CONTROL14); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_NONREGPARAM); + snd_seq_client_info_event_filter_add(info, SND_SEQ_EVENT_REGPARAM); + + err = snd_seq_set_client_info(midi->seq_handle, info); + if (err < 0) + goto _err_port; + + + /* Input file descriptors */ + snd_seq_poll_descriptors(midi->seq_handle, &pfd, 1, POLLIN); + + midi->io = io_new(pfd.fd); + if (!midi->io) { + error("Could not allocate I/O eventloop"); + goto _err_port; + } + + io_set_read_handler(midi->io, midi_write_cb, midi, NULL); + + midi->db = gatt_db_ref(db); + midi->client = bt_gatt_client_ref(client); + + err = midi_read_init(&midi->midi_in); + if (err < 0) { + error("Could not initialise MIDI input parser"); + goto _err_port; + } + + err = midi_write_init(&midi->midi_out, bt_gatt_client_get_mtu(midi->client) - 3); + if (err < 0) { + error("Could not initialise MIDI output parser"); + goto _err_midi; + } + + bt_string_to_uuid(&midi_uuid, MIDI_UUID); + gatt_db_foreach_service(db, &midi_uuid, foreach_midi_service, midi); + + btd_service_connecting_complete(service, 0); + + return 0; + +_err_midi: + midi_read_free(&midi->midi_in); + +_err_port: + snd_seq_delete_simple_port(midi->seq_handle, midi->seq_port_id); + +_err_handle: + snd_seq_close(midi->seq_handle); + midi->seq_handle = NULL; + + btd_service_connecting_complete(service, err); + + return err; +} + +static int midi_disconnect(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct midi *midi; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("MIDI GATT Driver profile disconnect (%s)", addr); + + midi = btd_service_get_user_data(service); + if (!midi) { + error("MIDI Service not handled by profile"); + return -ENODEV; + } + + midi_read_free(&midi->midi_in); + midi_write_free(&midi->midi_out); + io_destroy(midi->io); + snd_seq_delete_simple_port(midi->seq_handle, midi->seq_port_id); + midi->seq_port_id = 0; + snd_seq_close(midi->seq_handle); + midi->seq_handle = NULL; + + /* Clean-up any old client/db */ + bt_gatt_client_unregister_notify(midi->client, midi->io_cb_id); + bt_gatt_client_unref(midi->client); + gatt_db_unref(midi->db); + + btd_service_disconnecting_complete(service, 0); + + return 0; +} + +static struct btd_profile midi_profile = { + .name = "MIDI GATT Driver", + .remote_uuid = MIDI_UUID, + .priority = BTD_PROFILE_PRIORITY_HIGH, + .auto_connect = true, + + .device_probe = midi_device_probe, + .device_remove = midi_device_remove, + + .accept = midi_accept, + + .disconnect = midi_disconnect, +}; + +static int midi_init(void) +{ + return btd_profile_register(&midi_profile); +} + +static void midi_exit(void) +{ + btd_profile_unregister(&midi_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(midi, VERSION, BLUETOOTH_PLUGIN_PRIORITY_HIGH, + midi_init, midi_exit); diff --git a/profiles/network/bnep.c b/profiles/network/bnep.c new file mode 100644 index 0000000..d0ad9e4 --- /dev/null +++ b/profiles/network/bnep.c @@ -0,0 +1,735 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/l2cap.h" +#include "lib/bnep.h" +#include "lib/uuid.h" + +#include "src/log.h" +#include "src/shared/util.h" +#include "btio/btio.h" + +#include "bnep.h" + +#define CON_SETUP_RETRIES 3 +#define CON_SETUP_TO 9 + +static int ctl; + +struct __service_16 { + uint16_t dst; + uint16_t src; +} __attribute__ ((packed)); + +struct bnep { + GIOChannel *io; + uint16_t src; + uint16_t dst; + bdaddr_t dst_addr; + char iface[16]; + guint attempts; + guint setup_to; + guint watch; + bnep_connect_cb conn_cb; + void *conn_data; + bnep_disconnect_cb disconn_cb; + void *disconn_data; +}; + +int bnep_init(void) +{ + ctl = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_BNEP); + if (ctl < 0) { + int err = -errno; + + if (err == -EPROTONOSUPPORT) + warn("kernel lacks bnep-protocol support"); + else + error("bnep: Failed to open control socket: %s (%d)", + strerror(-err), -err); + + return err; + } + + return 0; +} + +int bnep_cleanup(void) +{ + close(ctl); + return 0; +} + +static int bnep_conndel(const bdaddr_t *dst) +{ + struct bnep_conndel_req req; + + memset(&req, 0, sizeof(req)); + baswap((bdaddr_t *)&req.dst, dst); + req.flags = 0; + if (ioctl(ctl, BNEPCONNDEL, &req) < 0) { + int err = -errno; + error("bnep: Failed to kill connection: %s (%d)", + strerror(-err), -err); + return err; + } + return 0; +} + +static int bnep_connadd(int sk, uint16_t role, char *dev) +{ + struct bnep_connadd_req req; + + memset(&req, 0, sizeof(req)); + strncpy(req.device, dev, 16); + req.device[15] = '\0'; + + req.sock = sk; + req.role = role; + req.flags = (1 << BNEP_SETUP_RESPONSE); + if (ioctl(ctl, BNEPCONNADD, &req) < 0) { + int err = -errno; + error("bnep: Failed to add device %s: %s(%d)", + dev, strerror(-err), -err); + return err; + } + + strncpy(dev, req.device, 16); + return 0; +} + +static uint32_t bnep_getsuppfeat(void) +{ + uint32_t feat; + + if (ioctl(ctl, BNEPGETSUPPFEAT, &feat) < 0) + feat = 0; + + DBG("supported features: 0x%x", feat); + + return feat; +} + +static int bnep_if_up(const char *devname) +{ + struct ifreq ifr; + int sk, err = 0; + + sk = socket(AF_INET, SOCK_DGRAM, 0); + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, devname, IF_NAMESIZE - 1); + + ifr.ifr_flags |= IFF_UP; + ifr.ifr_flags |= IFF_MULTICAST; + + if (ioctl(sk, SIOCSIFFLAGS, (void *) &ifr) < 0) { + err = -errno; + error("bnep: Could not bring up %s: %s(%d)", + devname, strerror(-err), -err); + } + + close(sk); + + return err; +} + +static int bnep_if_down(const char *devname) +{ + struct ifreq ifr; + int sk, err = 0; + + sk = socket(AF_INET, SOCK_DGRAM, 0); + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, devname, IF_NAMESIZE - 1); + + ifr.ifr_flags &= ~IFF_UP; + + /* Bring down the interface */ + if (ioctl(sk, SIOCSIFFLAGS, (void *) &ifr) < 0) { + err = -errno; + error("bnep: Could not bring down %s: %s(%d)", + devname, strerror(-err), -err); + } + + close(sk); + + return err; +} + +static gboolean bnep_watchdog_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct bnep *session = data; + + if (session->disconn_cb) + session->disconn_cb(session->disconn_data); + + return FALSE; +} + +static gboolean bnep_setup_cb(GIOChannel *chan, GIOCondition cond, + gpointer data) +{ + struct bnep *session = data; + struct bnep_control_rsp *rsp; + struct timeval timeo; + char pkt[BNEP_MTU]; + ssize_t r; + int sk; + + if (cond & G_IO_NVAL) + return FALSE; + + if (session->setup_to > 0) { + g_source_remove(session->setup_to); + session->setup_to = 0; + } + + if (cond & (G_IO_HUP | G_IO_ERR)) { + error("bnep: Hangup or error on l2cap server socket"); + goto failed; + } + + sk = g_io_channel_unix_get_fd(chan); + memset(pkt, 0, BNEP_MTU); + r = read(sk, pkt, sizeof(pkt) - 1); + if (r < 0) { + error("bnep: IO Channel read error"); + goto failed; + } + + if (r == 0) { + error("bnep: No packet received on l2cap socket"); + goto failed; + } + + errno = EPROTO; + + if ((size_t) r < sizeof(*rsp)) { + error("bnep: Packet received is not bnep type"); + goto failed; + } + + rsp = (void *) pkt; + if (rsp->type != BNEP_CONTROL) { + error("bnep: Packet received is not bnep type"); + goto failed; + } + + if (rsp->ctrl != BNEP_SETUP_CONN_RSP) + return TRUE; + + r = ntohs(rsp->resp); + if (r != BNEP_SUCCESS) { + error("bnep: failed"); + goto failed; + } + + memset(&timeo, 0, sizeof(timeo)); + timeo.tv_sec = 0; + setsockopt(sk, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo)); + + sk = g_io_channel_unix_get_fd(session->io); + if (bnep_connadd(sk, session->src, session->iface) < 0) + goto failed; + + if (bnep_if_up(session->iface) < 0) { + bnep_conndel(&session->dst_addr); + goto failed; + } + + session->watch = g_io_add_watch(session->io, + G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) bnep_watchdog_cb, session); + g_io_channel_unref(session->io); + session->io = NULL; + + session->conn_cb(session->iface, 0, session->conn_data); + + return FALSE; + +failed: + session->conn_cb(NULL, -EIO, session->conn_data); + + return FALSE; +} + +static int bnep_setup_conn_req(struct bnep *session) +{ + struct bnep_setup_conn_req *req; + struct __service_16 *s; + unsigned char pkt[BNEP_MTU]; + int fd; + + /* Send request */ + req = (void *) pkt; + req->type = BNEP_CONTROL; + req->ctrl = BNEP_SETUP_CONN_REQ; + req->uuid_size = 2; /* 16bit UUID */ + s = (void *) req->service; + s->src = htons(session->src); + s->dst = htons(session->dst); + + fd = g_io_channel_unix_get_fd(session->io); + if (write(fd, pkt, sizeof(*req) + sizeof(*s)) < 0) { + error("bnep: connection req send failed: %s", strerror(errno)); + return -errno; + } + + session->attempts++; + + return 0; +} + +static gboolean bnep_conn_req_to(gpointer user_data) +{ + struct bnep *session = user_data; + + if (session->attempts == CON_SETUP_RETRIES) { + error("bnep: Too many bnep connection attempts"); + } else { + error("bnep: connection setup TO, retrying..."); + if (bnep_setup_conn_req(session) == 0) + return TRUE; + } + + session->conn_cb(NULL, -ETIMEDOUT, session->conn_data); + + return FALSE; +} + +struct bnep *bnep_new(int sk, uint16_t local_role, uint16_t remote_role, + char *iface) +{ + struct bnep *session; + int dup_fd; + + dup_fd = dup(sk); + if (dup_fd < 0) + return NULL; + + session = g_new0(struct bnep, 1); + session->io = g_io_channel_unix_new(dup_fd); + session->src = local_role; + session->dst = remote_role; + strncpy(session->iface, iface, 16); + session->iface[15] = '\0'; + + g_io_channel_set_close_on_unref(session->io, TRUE); + session->watch = g_io_add_watch(session->io, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc) bnep_setup_cb, session); + + return session; +} + +void bnep_free(struct bnep *session) +{ + if (!session) + return; + + if (session->io) { + g_io_channel_shutdown(session->io, FALSE, NULL); + g_io_channel_unref(session->io); + session->io = NULL; + } + + if (session->watch > 0) { + g_source_remove(session->watch); + session->watch = 0; + } + + g_free(session); +} + +int bnep_connect(struct bnep *session, bnep_connect_cb conn_cb, + bnep_disconnect_cb disconn_cb, + void *conn_data, void *disconn_data) +{ + GError *gerr = NULL; + int err; + + if (!session || !conn_cb || !disconn_cb) + return -EINVAL; + + session->attempts = 0; + session->conn_cb = conn_cb; + session->disconn_cb = disconn_cb; + session->conn_data = conn_data; + session->disconn_data = disconn_data; + + bt_io_get(session->io, &gerr, BT_IO_OPT_DEST_BDADDR, &session->dst_addr, + BT_IO_OPT_INVALID); + if (gerr) { + error("bnep: connect failed: %s", gerr->message); + g_error_free(gerr); + return -EINVAL; + } + + err = bnep_setup_conn_req(session); + if (err < 0) + return err; + + session->setup_to = g_timeout_add_seconds(CON_SETUP_TO, + bnep_conn_req_to, session); + return 0; +} + +void bnep_disconnect(struct bnep *session) +{ + if (!session) + return; + + if (session->watch > 0) { + g_source_remove(session->watch); + session->watch = 0; + } + + if (session->io) { + g_io_channel_unref(session->io); + session->io = NULL; + } + + bnep_if_down(session->iface); + bnep_conndel(&session->dst_addr); +} + +static int bnep_add_to_bridge(const char *devname, const char *bridge) +{ + int ifindex; + struct ifreq ifr; + int sk, err = 0; + + if (!devname || !bridge) + return -EINVAL; + + ifindex = if_nametoindex(devname); + + sk = socket(AF_INET, SOCK_STREAM, 0); + if (sk < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, bridge, IFNAMSIZ - 1); + ifr.ifr_ifindex = ifindex; + + if (ioctl(sk, SIOCBRADDIF, &ifr) < 0) { + err = -errno; + error("bnep: Can't add %s to the bridge %s: %s(%d)", + devname, bridge, strerror(-err), -err); + } else { + info("bnep: bridge %s: interface %s added", bridge, devname); + } + + close(sk); + + return err; +} + +static int bnep_del_from_bridge(const char *devname, const char *bridge) +{ + int ifindex; + struct ifreq ifr; + int sk, err = 0; + + if (!devname || !bridge) + return -EINVAL; + + ifindex = if_nametoindex(devname); + + sk = socket(AF_INET, SOCK_STREAM, 0); + if (sk < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, bridge, IFNAMSIZ - 1); + ifr.ifr_ifindex = ifindex; + + if (ioctl(sk, SIOCBRDELIF, &ifr) < 0) { + err = -errno; + error("bnep: Can't delete %s from the bridge %s: %s(%d)", + devname, bridge, strerror(-err), -err); + } else { + info("bnep: bridge %s: interface %s removed", bridge, devname); + } + + close(sk); + + return err; +} + +static ssize_t bnep_send_ctrl_rsp(int sk, uint8_t ctrl, uint16_t resp) +{ + ssize_t sent; + + switch (ctrl) { + case BNEP_CMD_NOT_UNDERSTOOD: { + struct bnep_ctrl_cmd_not_understood_cmd rsp; + + rsp.type = BNEP_CONTROL; + rsp.ctrl = ctrl; + rsp.unkn_ctrl = (uint8_t) resp; + + sent = send(sk, &rsp, sizeof(rsp), 0); + break; + } + case BNEP_FILTER_MULT_ADDR_RSP: + case BNEP_FILTER_NET_TYPE_RSP: + case BNEP_SETUP_CONN_RSP: { + struct bnep_control_rsp rsp; + + rsp.type = BNEP_CONTROL; + rsp.ctrl = ctrl; + rsp.resp = htons(resp); + + sent = send(sk, &rsp, sizeof(rsp), 0); + break; + } + default: + error("bnep: wrong response type"); + sent = -1; + break; + } + + return sent; +} + +static uint16_t bnep_setup_decode(int sk, struct bnep_setup_conn_req *req, + uint16_t *dst) +{ + const uint8_t bt_base[] = { 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, + 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }; + uint16_t src; + uint8_t *dest, *source; + uint32_t val; + + if (((req->type != BNEP_CONTROL) && + (req->type != (BNEP_CONTROL | BNEP_EXT_HEADER))) || + req->ctrl != BNEP_SETUP_CONN_REQ) + return BNEP_CONN_NOT_ALLOWED; + + dest = req->service; + source = req->service + req->uuid_size; + + switch (req->uuid_size) { + case 2: /* UUID16 */ + *dst = get_be16(dest); + src = get_be16(source); + break; + case 16: /* UUID128 */ + /* Check that the bytes in the UUID, except the service ID + * itself, are correct. The service ID is checked in + * bnep_setup_chk(). */ + if (memcmp(&dest[4], bt_base, sizeof(bt_base)) != 0) + return BNEP_CONN_INVALID_DST; + if (memcmp(&source[4], bt_base, sizeof(bt_base)) != 0) + return BNEP_CONN_INVALID_SRC; + /* fall through */ + case 4: /* UUID32 */ + val = get_be32(dest); + if (val > 0xffff) + return BNEP_CONN_INVALID_DST; + + *dst = val; + + val = get_be32(source); + if (val > 0xffff) + return BNEP_CONN_INVALID_SRC; + + src = val; + break; + default: + return BNEP_CONN_INVALID_SVC; + } + + /* Allowed PAN Profile scenarios */ + switch (*dst) { + case BNEP_SVC_NAP: + case BNEP_SVC_GN: + if (src == BNEP_SVC_PANU) + return BNEP_SUCCESS; + return BNEP_CONN_INVALID_SRC; + case BNEP_SVC_PANU: + if (src == BNEP_SVC_PANU || src == BNEP_SVC_GN || + src == BNEP_SVC_NAP) + return BNEP_SUCCESS; + + return BNEP_CONN_INVALID_SRC; + } + + return BNEP_CONN_INVALID_DST; +} + +static int bnep_server_add_legacy(int sk, uint16_t dst, char *bridge, + char *iface, const bdaddr_t *addr, + uint8_t *setup_data, int len) +{ + int err, n; + uint16_t rsp; + + n = read(sk, setup_data, len); + if (n != len) { + err = -EIO; + rsp = BNEP_CONN_NOT_ALLOWED; + goto reply; + } + + err = bnep_connadd(sk, dst, iface); + if (err < 0) { + rsp = BNEP_CONN_NOT_ALLOWED; + goto reply; + } + + err = bnep_add_to_bridge(iface, bridge); + if (err < 0) { + bnep_conndel(addr); + rsp = BNEP_CONN_NOT_ALLOWED; + goto reply; + } + + err = bnep_if_up(iface); + if (err < 0) { + bnep_del_from_bridge(iface, bridge); + bnep_conndel(addr); + rsp = BNEP_CONN_NOT_ALLOWED; + goto reply; + } + + rsp = BNEP_SUCCESS; + +reply: + if (bnep_send_ctrl_rsp(sk, BNEP_SETUP_CONN_RSP, rsp) < 0) { + err = -errno; + error("bnep: send ctrl rsp error: %s (%d)", strerror(-err), + -err); + } + + return err; +} + +int bnep_server_add(int sk, char *bridge, char *iface, const bdaddr_t *addr, + uint8_t *setup_data, int len) +{ + int err; + uint32_t feat; + uint16_t rsp, dst; + struct bnep_setup_conn_req *req = (void *) setup_data; + + /* Highest known Control command ID + * is BNEP_FILTER_MULT_ADDR_RSP = 0x06 */ + if (req->type == BNEP_CONTROL && + req->ctrl > BNEP_FILTER_MULT_ADDR_RSP) { + error("bnep: cmd not understood"); + err = bnep_send_ctrl_rsp(sk, BNEP_CMD_NOT_UNDERSTOOD, + req->ctrl); + if (err < 0) + error("send not understood ctrl rsp error: %s (%d)", + strerror(errno), errno); + + return err; + } + + /* Processing BNEP_SETUP_CONNECTION_REQUEST_MSG */ + rsp = bnep_setup_decode(sk, req, &dst); + if (rsp != BNEP_SUCCESS) { + err = -rsp; + error("bnep: error while decoding setup connection request: %d", + rsp); + goto failed; + } + + feat = bnep_getsuppfeat(); + + /* + * Take out setup data if kernel doesn't support handling it, especially + * setup request. If kernel would have set session flags, they should + * be checked and handled respectively. + */ + if (!feat || !(feat & (1 << BNEP_SETUP_RESPONSE))) + return bnep_server_add_legacy(sk, dst, bridge, iface, addr, + setup_data, len); + + err = bnep_connadd(sk, dst, iface); + if (err < 0) { + rsp = BNEP_CONN_NOT_ALLOWED; + goto failed; + } + + err = bnep_add_to_bridge(iface, bridge); + if (err < 0) + goto failed_conn; + + err = bnep_if_up(iface); + if (err < 0) + goto failed_bridge; + + return 0; + +failed_bridge: + bnep_del_from_bridge(iface, bridge); + +failed_conn: + bnep_conndel(addr); + + return err; + +failed: + if (bnep_send_ctrl_rsp(sk, BNEP_SETUP_CONN_RSP, rsp) < 0) { + err = -errno; + error("bnep: send ctrl rsp error: %s (%d)", strerror(-err), + -err); + } + + return err; +} + +void bnep_server_delete(char *bridge, char *iface, const bdaddr_t *addr) +{ + if (!bridge || !iface || !addr) + return; + + bnep_del_from_bridge(iface, bridge); + bnep_if_down(iface); + bnep_conndel(addr); +} diff --git a/profiles/network/bnep.h b/profiles/network/bnep.h new file mode 100644 index 0000000..e9f4c1c --- /dev/null +++ b/profiles/network/bnep.h @@ -0,0 +1,42 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +struct bnep; + +int bnep_init(void); +int bnep_cleanup(void); + +struct bnep *bnep_new(int sk, uint16_t local_role, uint16_t remote_role, + char *iface); +void bnep_free(struct bnep *session); + +typedef void (*bnep_connect_cb) (char *iface, int err, void *data); +typedef void (*bnep_disconnect_cb) (void *data); +int bnep_connect(struct bnep *session, bnep_connect_cb conn_cb, + bnep_disconnect_cb disconn_cb, + void *conn_data, void *disconn_data); +void bnep_disconnect(struct bnep *session); + +int bnep_server_add(int sk, char *bridge, char *iface, const bdaddr_t *addr, + uint8_t *setup_data, int len); +void bnep_server_delete(char *bridge, char *iface, const bdaddr_t *addr); diff --git a/profiles/network/connection.c b/profiles/network/connection.c new file mode 100644 index 0000000..97b87d0 --- /dev/null +++ b/profiles/network/connection.c @@ -0,0 +1,590 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/bnep.h" +#include "lib/sdp.h" + +#include "gdbus/gdbus.h" + +#include "btio/btio.h" +#include "src/log.h" +#include "src/dbus-common.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/error.h" +#include "lib/uuid.h" + +#include "bnep.h" +#include "connection.h" + +#define NETWORK_PEER_INTERFACE "org.bluez.Network1" +#define BNEP_INTERFACE "bnep%d" + +typedef enum { + CONNECTED, + CONNECTING, + DISCONNECTED +} conn_state; + +struct network_peer { + struct btd_device *device; + GSList *connections; +}; + +struct network_conn { + struct btd_service *service; + char dev[16]; /* Interface name */ + uint16_t id; /* Role: Service Class Identifier */ + conn_state state; + GIOChannel *io; + guint dc_id; + struct network_peer *peer; + DBusMessage *connect; + struct bnep *session; +}; + +static GSList *peers = NULL; + +static uint16_t get_pan_srv_id(const char *svc) +{ + if (!strcasecmp(svc, "panu") || !strcasecmp(svc, PANU_UUID)) + return BNEP_SVC_PANU; + if (!strcasecmp(svc, "nap") || !strcasecmp(svc, NAP_UUID)) + return BNEP_SVC_NAP; + if (!strcasecmp(svc, "gn") || !strcasecmp(svc, GN_UUID)) + return BNEP_SVC_GN; + + return 0; +} + +static struct network_peer *find_peer(GSList *list, struct btd_device *device) +{ + for (; list; list = list->next) { + struct network_peer *peer = list->data; + + if (peer->device == device) + return peer; + } + + return NULL; +} + +static struct network_conn *find_connection_by_state(GSList *list, + conn_state state) +{ + for (; list; list = list->next) { + struct network_conn *nc = list->data; + + if (nc->state == state) + return nc; + } + + return NULL; +} + +static void bnep_disconn_cb(gpointer data) +{ + struct network_conn *nc = data; + DBusConnection *conn = btd_get_dbus_connection(); + const char *path = device_get_path(nc->peer->device); + + g_dbus_emit_property_changed(conn, path, + NETWORK_PEER_INTERFACE, "Connected"); + g_dbus_emit_property_changed(conn, path, + NETWORK_PEER_INTERFACE, "Interface"); + g_dbus_emit_property_changed(conn, path, + NETWORK_PEER_INTERFACE, "UUID"); + device_remove_disconnect_watch(nc->peer->device, nc->dc_id); + nc->dc_id = 0; + + btd_service_disconnecting_complete(nc->service, 0); + + info("%s disconnected", nc->dev); + + nc->state = DISCONNECTED; + memset(nc->dev, 0, sizeof(nc->dev)); + strncpy(nc->dev, BNEP_INTERFACE, 16); + nc->dev[15] = '\0'; + + bnep_free(nc->session); + nc->session = NULL; +} + +static void local_connect_cb(struct network_conn *nc, int err) +{ + DBusConnection *conn = btd_get_dbus_connection(); + const char *pdev = nc->dev; + + if (err < 0) { + DBusMessage *reply = btd_error_failed(nc->connect, + strerror(-err)); + g_dbus_send_message(conn, reply); + } else { + g_dbus_send_reply(conn, nc->connect, DBUS_TYPE_STRING, &pdev, + DBUS_TYPE_INVALID); + } + + dbus_message_unref(nc->connect); + nc->connect = NULL; +} + +static void cancel_connection(struct network_conn *nc, int err) +{ + btd_service_connecting_complete(nc->service, err); + + if (nc->connect) + local_connect_cb(nc, err); + + if (nc->io) { + g_io_channel_shutdown(nc->io, FALSE, NULL); + g_io_channel_unref(nc->io); + nc->io = NULL; + } + + if (nc->state == CONNECTED) + bnep_disconnect(nc->session); + + bnep_free(nc->session); + nc->session = NULL; + + nc->state = DISCONNECTED; +} + +static void connection_destroy(DBusConnection *conn, void *user_data) +{ + struct network_conn *nc = user_data; + + cancel_connection(nc, -EIO); +} + +static void disconnect_cb(struct btd_device *device, gboolean removal, + void *user_data) +{ + struct network_conn *nc = user_data; + + info("Network: disconnect %s", device_get_path(nc->peer->device)); + + connection_destroy(NULL, user_data); +} + +static void bnep_conn_cb(char *iface, int err, void *data) +{ + struct network_conn *nc = data; + const char *path; + DBusConnection *conn; + + DBG(""); + + if (err < 0) { + error("connect failed %s", strerror(-err)); + goto failed; + } + + info("%s connected", nc->dev); + + memcpy(nc->dev, iface, sizeof(nc->dev)); + btd_service_connecting_complete(nc->service, 0); + + if (nc->connect) + local_connect_cb(nc, 0); + + conn = btd_get_dbus_connection(); + path = device_get_path(nc->peer->device); + + g_dbus_emit_property_changed(conn, path, + NETWORK_PEER_INTERFACE, "Connected"); + g_dbus_emit_property_changed(conn, path, + NETWORK_PEER_INTERFACE, "Interface"); + g_dbus_emit_property_changed(conn, path, + NETWORK_PEER_INTERFACE, "UUID"); + + nc->state = CONNECTED; + nc->dc_id = device_add_disconnect_watch(nc->peer->device, disconnect_cb, + nc, NULL); + + return; + +failed: + cancel_connection(nc, -EIO); +} + +static void connect_cb(GIOChannel *chan, GError *err, gpointer data) +{ + struct network_conn *nc = data; + int sk, perr; + + if (err) { + error("%s", err->message); + goto failed; + } + + sk = g_io_channel_unix_get_fd(nc->io); + nc->session = bnep_new(sk, BNEP_SVC_PANU, nc->id, BNEP_INTERFACE); + if (!nc->session) + goto failed; + + perr = bnep_connect(nc->session, bnep_conn_cb, bnep_disconn_cb, nc, nc); + if (perr < 0) { + error("bnep connect(): %s (%d)", strerror(-perr), -perr); + goto failed; + } + + if (nc->io) { + g_io_channel_unref(nc->io); + nc->io = NULL; + } + + return; + +failed: + cancel_connection(nc, -EIO); +} + +static DBusMessage *local_connect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_peer *peer = data; + struct btd_service *service; + struct network_conn *nc; + const char *svc; + uint16_t id; + int err; + char uuid_str[MAX_LEN_UUID_STR]; + bt_uuid_t uuid16, uuid128; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &svc, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + id = get_pan_srv_id(svc); + bt_uuid16_create(&uuid16, id); + bt_uuid_to_uuid128(&uuid16, &uuid128); + + if (bt_uuid_to_string(&uuid128, uuid_str, MAX_LEN_UUID_STR) < 0) + return btd_error_invalid_args(msg); + + service = btd_device_get_service(peer->device, uuid_str); + if (service == NULL) + return btd_error_not_supported(msg); + + nc = btd_service_get_user_data(service); + + if (nc->connect != NULL) + return btd_error_busy(msg); + + err = connection_connect(nc->service); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + nc->connect = dbus_message_ref(msg); + + return NULL; +} + +/* Connect and initiate BNEP session */ +int connection_connect(struct btd_service *svc) +{ + struct network_conn *nc = btd_service_get_user_data(svc); + struct network_peer *peer = nc->peer; + uint16_t id = get_pan_srv_id(btd_service_get_profile(svc)->remote_uuid); + GError *err = NULL; + const bdaddr_t *src; + const bdaddr_t *dst; + + DBG("id %u", id); + + if (nc->state != DISCONNECTED) + return -EALREADY; + + src = btd_adapter_get_address(device_get_adapter(peer->device)); + dst = device_get_address(peer->device); + + nc->io = bt_io_connect(connect_cb, nc, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_PSM, BNEP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_OMTU, BNEP_MTU, + BT_IO_OPT_IMTU, BNEP_MTU, + BT_IO_OPT_INVALID); + if (!nc->io) + return -EIO; + + nc->state = CONNECTING; + + return 0; +} + +int connection_disconnect(struct btd_service *svc) +{ + struct network_conn *nc = btd_service_get_user_data(svc); + + if (nc->state == DISCONNECTED) + return 0; + + connection_destroy(NULL, nc); + + return 0; +} + +static DBusMessage *local_disconnect(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_peer *peer = data; + GSList *l; + + for (l = peer->connections; l; l = l->next) { + struct network_conn *nc = l->data; + int err; + + if (nc->state == DISCONNECTED) + continue; + + err = connection_disconnect(nc->service); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + } + + return btd_error_not_connected(msg); +} + +static gboolean +network_property_get_connected(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct network_peer *peer = data; + struct network_conn *nc; + dbus_bool_t connected; + + nc = find_connection_by_state(peer->connections, CONNECTED); + connected = nc != NULL ? TRUE : FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &connected); + + return TRUE; +} + +static gboolean network_property_exists(const GDBusPropertyTable *property, + void *data) +{ + struct network_peer *peer = data; + struct network_conn *nc; + + nc = find_connection_by_state(peer->connections, CONNECTED); + if (nc == NULL) + return FALSE; + + return TRUE; +} + +static gboolean +network_property_get_interface(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct network_peer *peer = data; + struct network_conn *nc; + const char *iface; + + nc = find_connection_by_state(peer->connections, CONNECTED); + if (nc == NULL) + return FALSE; + + iface = nc->dev; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &iface); + + return TRUE; +} + +static gboolean network_property_get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct network_peer *peer = data; + struct network_conn *nc; + char uuid_str[MAX_LEN_UUID_STR]; + const char *uuid = uuid_str; + bt_uuid_t uuid16, uuid128; + + nc = find_connection_by_state(peer->connections, CONNECTED); + if (nc == NULL) + return FALSE; + + bt_uuid16_create(&uuid16, nc->id); + bt_uuid_to_uuid128(&uuid16, &uuid128); + bt_uuid_to_string(&uuid128, uuid_str, MAX_LEN_UUID_STR); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); + + return TRUE; +} + +static void connection_free(void *data) +{ + struct network_conn *nc = data; + + if (nc->dc_id) + device_remove_disconnect_watch(nc->peer->device, nc->dc_id); + + connection_destroy(NULL, nc); + + if (nc->connect) + dbus_message_unref(nc->connect); + + btd_service_unref(nc->service); + g_free(nc); +} + +static void peer_free(struct network_peer *peer) +{ + g_slist_free_full(peer->connections, connection_free); + btd_device_unref(peer->device); + g_free(peer); +} + +static void path_unregister(void *data) +{ + struct network_peer *peer = data; + + DBG("Unregistered interface %s on path %s", + NETWORK_PEER_INTERFACE, device_get_path(peer->device)); + + peers = g_slist_remove(peers, peer); + peer_free(peer); +} + +static const GDBusMethodTable connection_methods[] = { + { GDBUS_ASYNC_METHOD("Connect", + GDBUS_ARGS({"uuid", "s"}), + GDBUS_ARGS({"interface", "s"}), + local_connect) }, + { GDBUS_METHOD("Disconnect", + NULL, NULL, local_disconnect) }, + { } +}; + +static const GDBusPropertyTable connection_properties[] = { + { "Connected", "b", network_property_get_connected }, + { "Interface", "s", network_property_get_interface, NULL, + network_property_exists }, + { "UUID", "s", network_property_get_uuid, NULL, + network_property_exists }, + { } +}; + +void connection_unregister(struct btd_service *svc) +{ + struct btd_device *device = btd_service_get_device(svc); + struct network_conn *conn = btd_service_get_user_data(svc); + struct network_peer *peer = conn->peer; + uint16_t id = get_pan_srv_id(btd_service_get_profile(svc)->remote_uuid); + + DBG("%s id %u", device_get_path(device), id); + + peer->connections = g_slist_remove(peer->connections, conn); + connection_free(conn); + + if (peer->connections != NULL) + return; + + g_dbus_unregister_interface(btd_get_dbus_connection(), + device_get_path(device), + NETWORK_PEER_INTERFACE); +} + +static struct network_peer *create_peer(struct btd_device *device) +{ + struct network_peer *peer; + const char *path; + + peer = g_new0(struct network_peer, 1); + peer->device = btd_device_ref(device); + + path = device_get_path(device); + + if (g_dbus_register_interface(btd_get_dbus_connection(), path, + NETWORK_PEER_INTERFACE, + connection_methods, + NULL, connection_properties, + peer, path_unregister) == FALSE) { + error("D-Bus failed to register %s interface", + NETWORK_PEER_INTERFACE); + peer_free(peer); + return NULL; + } + + DBG("Registered interface %s on path %s", + NETWORK_PEER_INTERFACE, path); + + return peer; +} + +int connection_register(struct btd_service *svc) +{ + struct btd_device *device = btd_service_get_device(svc); + struct network_peer *peer; + struct network_conn *nc; + uint16_t id = get_pan_srv_id(btd_service_get_profile(svc)->remote_uuid); + + DBG("%s id %u", device_get_path(device), id); + + peer = find_peer(peers, device); + if (!peer) { + peer = create_peer(device); + if (!peer) + return -1; + peers = g_slist_append(peers, peer); + } + + nc = g_new0(struct network_conn, 1); + nc->id = id; + nc->service = btd_service_ref(svc); + nc->state = DISCONNECTED; + nc->peer = peer; + + btd_service_set_user_data(svc, nc); + + DBG("id %u registered", id); + + peer->connections = g_slist_append(peer->connections, nc); + + return 0; +} diff --git a/profiles/network/connection.h b/profiles/network/connection.h new file mode 100644 index 0000000..4a8b43b --- /dev/null +++ b/profiles/network/connection.h @@ -0,0 +1,27 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int connection_register(struct btd_service *service); +void connection_unregister(struct btd_service *service); +int connection_connect(struct btd_service *service); +int connection_disconnect(struct btd_service *service); diff --git a/profiles/network/manager.c b/profiles/network/manager.c new file mode 100644 index 0000000..41377fb --- /dev/null +++ b/profiles/network/manager.c @@ -0,0 +1,210 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/bnep.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/log.h" +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" + +#include "bnep.h" +#include "connection.h" +#include "server.h" + +static gboolean conf_security = TRUE; + +static void read_config(const char *file) +{ + GKeyFile *keyfile; + GError *err = NULL; + + keyfile = g_key_file_new(); + + if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { + g_clear_error(&err); + goto done; + } + + conf_security = !g_key_file_get_boolean(keyfile, "General", + "DisableSecurity", &err); + if (err) { + DBG("%s: %s", file, err->message); + g_clear_error(&err); + } + +done: + g_key_file_free(keyfile); + + DBG("Config options: Security=%s", + conf_security ? "true" : "false"); +} + +static int panu_server_probe(struct btd_profile *p, struct btd_adapter *adapter) +{ + const char *path = adapter_get_path(adapter); + + DBG("path %s", path); + + return server_register(adapter, BNEP_SVC_PANU); +} + +static void panu_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + const char *path = adapter_get_path(adapter); + + DBG("path %s", path); + + server_unregister(adapter, BNEP_SVC_PANU); +} + +static int gn_server_probe(struct btd_profile *p, struct btd_adapter *adapter) +{ + const char *path = adapter_get_path(adapter); + + DBG("path %s", path); + + return server_register(adapter, BNEP_SVC_GN); +} + +static void gn_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + const char *path = adapter_get_path(adapter); + + DBG("path %s", path); + + server_unregister(adapter, BNEP_SVC_GN); +} + +static int nap_server_probe(struct btd_profile *p, struct btd_adapter *adapter) +{ + const char *path = adapter_get_path(adapter); + + DBG("path %s", path); + + return server_register(adapter, BNEP_SVC_NAP); +} + +static void nap_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + const char *path = adapter_get_path(adapter); + + DBG("path %s", path); + + server_unregister(adapter, BNEP_SVC_NAP); +} + +static struct btd_profile panu_profile = { + .name = "network-panu", + .local_uuid = NAP_UUID, + .remote_uuid = PANU_UUID, + .device_probe = connection_register, + .device_remove = connection_unregister, + .connect = connection_connect, + .disconnect = connection_disconnect, + .adapter_probe = panu_server_probe, + .adapter_remove = panu_server_remove, +}; + +static struct btd_profile gn_profile = { + .name = "network-gn", + .local_uuid = PANU_UUID, + .remote_uuid = GN_UUID, + .device_probe = connection_register, + .device_remove = connection_unregister, + .connect = connection_connect, + .disconnect = connection_disconnect, + .adapter_probe = gn_server_probe, + .adapter_remove = gn_server_remove, +}; + +static struct btd_profile nap_profile = { + .name = "network-nap", + .local_uuid = PANU_UUID, + .remote_uuid = NAP_UUID, + .device_probe = connection_register, + .device_remove = connection_unregister, + .connect = connection_connect, + .disconnect = connection_disconnect, + .adapter_probe = nap_server_probe, + .adapter_remove = nap_server_remove, +}; + +static int network_init(void) +{ + int err; + + read_config(CONFIGDIR "/network.conf"); + + err = bnep_init(); + if (err) { + if (err == -EPROTONOSUPPORT) + err = -ENOSYS; + return err; + } + + /* + * There is one socket to handle the incoming connections. NAP, + * GN and PANU servers share the same PSM. The initial BNEP message + * (setup connection request) contains the destination service + * field that defines which service the source is connecting to. + */ + + if (server_init(conf_security) < 0) + return -1; + + btd_profile_register(&panu_profile); + btd_profile_register(&gn_profile); + btd_profile_register(&nap_profile); + + return 0; +} + +static void network_exit(void) +{ + btd_profile_unregister(&panu_profile); + btd_profile_unregister(&gn_profile); + btd_profile_unregister(&nap_profile); + + bnep_cleanup(); +} + +BLUETOOTH_PLUGIN_DEFINE(network, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, network_init, network_exit) diff --git a/profiles/network/network.conf b/profiles/network/network.conf new file mode 100644 index 0000000..5f11639 --- /dev/null +++ b/profiles/network/network.conf @@ -0,0 +1,6 @@ +# Configuration file for the network service + +[General] + +# Disable link encryption: default=false +#DisableSecurity=true diff --git a/profiles/network/server.c b/profiles/network/server.c new file mode 100644 index 0000000..c462677 --- /dev/null +++ b/profiles/network/server.c @@ -0,0 +1,758 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/bnep.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "btio/btio.h" +#include "src/dbus-common.h" +#include "src/adapter.h" +#include "src/log.h" +#include "src/error.h" +#include "src/sdpd.h" +#include "src/shared/util.h" + +#include "bnep.h" +#include "server.h" + +#define NETWORK_SERVER_INTERFACE "org.bluez.NetworkServer1" +#define BNEP_INTERFACE "bnep%d" +#define SETUP_TIMEOUT 1 + +/* Pending Authorization */ +struct network_session { + bdaddr_t dst; /* Remote Bluetooth Address */ + char dev[16]; /* Interface name */ + GIOChannel *io; /* Pending connect channel */ + guint watch; /* BNEP socket watch */ +}; + +struct network_adapter { + struct btd_adapter *adapter; /* Adapter pointer */ + GIOChannel *io; /* Bnep socket */ + struct network_session *setup; /* Setup in progress */ + GSList *servers; /* Server register to adapter */ +}; + +/* Main server structure */ +struct network_server { + bdaddr_t src; /* Bluetooth Local Address */ + char *name; /* Server service name */ + char *bridge; /* Bridge name */ + uint32_t record_id; /* Service record id */ + uint16_t id; /* Service class identifier */ + GSList *sessions; /* Active connections */ + struct network_adapter *na; /* Adapter reference */ + guint watch_id; /* Client service watch */ +}; + +static GSList *adapters = NULL; +static gboolean security = TRUE; + +static struct network_adapter *find_adapter(GSList *list, + struct btd_adapter *adapter) +{ + for (; list; list = list->next) { + struct network_adapter *na = list->data; + + if (na->adapter == adapter) + return na; + } + + return NULL; +} + +static struct network_server *find_server(GSList *list, uint16_t id) +{ + for (; list; list = list->next) { + struct network_server *ns = list->data; + + if (ns->id == id) + return ns; + } + + return NULL; +} + +static struct network_server *find_server_by_uuid(GSList *list, + const char *uuid) +{ + bt_uuid_t srv_uuid, bnep_uuid; + + if (!bt_string_to_uuid(&srv_uuid, uuid)) { + for (; list; list = list->next) { + struct network_server *ns = list->data; + + bt_uuid16_create(&bnep_uuid, ns->id); + + /* UUID value compare */ + if (!bt_uuid_cmp(&srv_uuid, &bnep_uuid)) + return ns; + } + } else { + for (; list; list = list->next) { + struct network_server *ns = list->data; + + /* String value compare */ + switch (ns->id) { + case BNEP_SVC_PANU: + if (!strcasecmp(uuid, "panu")) + return ns; + break; + case BNEP_SVC_NAP: + if (!strcasecmp(uuid, "nap")) + return ns; + break; + case BNEP_SVC_GN: + if (!strcasecmp(uuid, "gn")) + return ns; + break; + } + } + } + + return NULL; +} + +static sdp_record_t *server_record_new(const char *name, uint16_t id) +{ + sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto; + uuid_t root_uuid, pan, l2cap, bnep; + sdp_profile_desc_t profile[1]; + sdp_list_t *proto[2]; + sdp_data_t *v, *p; + uint16_t psm = BNEP_PSM, version = 0x0100; + uint16_t security_desc = (security ? 0x0001 : 0x0000); + uint16_t net_access_type = 0xfffe; + uint32_t max_net_access_rate = 0; + const char *desc = "Network service"; + sdp_record_t *record; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + record->attrlist = NULL; + record->pattern = NULL; + + switch (id) { + case BNEP_SVC_NAP: + sdp_uuid16_create(&pan, NAP_SVCLASS_ID); + svclass = sdp_list_append(NULL, &pan); + sdp_set_service_classes(record, svclass); + + sdp_uuid16_create(&profile[0].uuid, NAP_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + sdp_set_info_attr(record, name, NULL, desc); + + sdp_attr_add_new(record, SDP_ATTR_NET_ACCESS_TYPE, + SDP_UINT16, &net_access_type); + sdp_attr_add_new(record, SDP_ATTR_MAX_NET_ACCESSRATE, + SDP_UINT32, &max_net_access_rate); + break; + case BNEP_SVC_GN: + sdp_uuid16_create(&pan, GN_SVCLASS_ID); + svclass = sdp_list_append(NULL, &pan); + sdp_set_service_classes(record, svclass); + + sdp_uuid16_create(&profile[0].uuid, GN_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + sdp_set_info_attr(record, name, NULL, desc); + break; + case BNEP_SVC_PANU: + sdp_uuid16_create(&pan, PANU_SVCLASS_ID); + svclass = sdp_list_append(NULL, &pan); + sdp_set_service_classes(record, svclass); + + sdp_uuid16_create(&profile[0].uuid, PANU_PROFILE_ID); + profile[0].version = 0x0100; + pfseq = sdp_list_append(NULL, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + sdp_set_info_attr(record, name, NULL, desc); + break; + default: + sdp_record_free(record); + return NULL; + } + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap); + p = sdp_data_alloc(SDP_UINT16, &psm); + proto[0] = sdp_list_append(proto[0], p); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&bnep, BNEP_UUID); + proto[1] = sdp_list_append(NULL, &bnep); + v = sdp_data_alloc(SDP_UINT16, &version); + proto[1] = sdp_list_append(proto[1], v); + + /* Supported protocols */ + { + uint16_t ptype[] = { + 0x0800, /* IPv4 */ + 0x0806, /* ARP */ + }; + sdp_data_t *head, *pseq; + int p; + + for (p = 0, head = NULL; p < 2; p++) { + sdp_data_t *data = sdp_data_alloc(SDP_UINT16, &ptype[p]); + if (head) + sdp_seq_append(head, data); + else + head = data; + } + pseq = sdp_data_alloc(SDP_SEQ16, head); + proto[1] = sdp_list_append(proto[1], pseq); + } + + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + sdp_add_lang_attr(record); + + sdp_attr_add_new(record, SDP_ATTR_SECURITY_DESC, + SDP_UINT16, &security_desc); + + sdp_data_free(p); + sdp_data_free(v); + sdp_list_free(apseq, NULL); + sdp_list_free(root, NULL); + sdp_list_free(aproto, NULL); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(svclass, NULL); + sdp_list_free(pfseq, NULL); + + return record; +} + +static void session_free(void *data) +{ + struct network_session *session = data; + + if (session->watch) + g_source_remove(session->watch); + + if (session->io) + g_io_channel_unref(session->io); + + g_free(session); +} + +static void setup_destroy(void *user_data) +{ + struct network_adapter *na = user_data; + struct network_session *setup = na->setup; + + if (!setup) + return; + + na->setup = NULL; + + session_free(setup); +} + +static gboolean bnep_setup(GIOChannel *chan, + GIOCondition cond, gpointer user_data) +{ + const uint8_t bt_base[] = { 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, + 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB }; + struct network_adapter *na = user_data; + struct network_server *ns; + uint8_t packet[BNEP_MTU]; + struct bnep_setup_conn_req *req = (void *) packet; + uint16_t dst_role = 0; + uint32_t val; + int n, sk; + char *bridge = NULL; + + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_ERR | G_IO_HUP)) { + error("Hangup or error on BNEP socket"); + return FALSE; + } + + sk = g_io_channel_unix_get_fd(chan); + + /* + * BNEP_SETUP_CONNECTION_REQUEST_MSG should be read and left in case + * of kernel setup connection msg handling. + */ + n = recv(sk, packet, sizeof(packet), MSG_PEEK); + if (n < 0) { + error("read(): %s(%d)", strerror(errno), errno); + return FALSE; + } + + /* + * Initial received data packet is BNEP_SETUP_CONNECTION_REQUEST_MSG + * minimal size of this frame is 3 octets: 1 byte of BNEP Type + + * 1 byte of BNEP Control Type + 1 byte of BNEP services UUID size. + */ + if (n < 3) { + error("To few setup connection request data received"); + return FALSE; + } + + switch (req->uuid_size) { + case 2: + dst_role = get_be16(req->service); + break; + case 16: + if (memcmp(&req->service[4], bt_base, sizeof(bt_base)) != 0) + break; + /* fall through */ + case 4: + val = get_be32(req->service); + if (val > 0xffff) + break; + + dst_role = val; + break; + default: + break; + } + + ns = find_server(na->servers, dst_role); + if (!ns || !ns->record_id || !ns->bridge) + error("Server error, bridge not initialized: (0x%x)", dst_role); + else + bridge = ns->bridge; + + strncpy(na->setup->dev, BNEP_INTERFACE, 16); + na->setup->dev[15] = '\0'; + + if (bnep_server_add(sk, bridge, na->setup->dev, &na->setup->dst, + packet, n) < 0) + error("BNEP server cannot be added"); + + na->setup = NULL; + + return FALSE; +} + +static void connect_event(GIOChannel *chan, GError *err, gpointer user_data) +{ + struct network_adapter *na = user_data; + + if (err) { + error("%s", err->message); + setup_destroy(na); + return; + } + + g_io_channel_set_close_on_unref(chan, TRUE); + + na->setup->watch = g_io_add_watch_full(chan, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + bnep_setup, na, setup_destroy); +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct network_adapter *na = user_data; + GError *err = NULL; + + if (derr) { + error("Access denied: %s", derr->message); + goto reject; + } + + if (!bt_io_accept(na->setup->io, connect_event, na, NULL, + &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + goto reject; + } + + return; + +reject: + g_io_channel_shutdown(na->setup->io, TRUE, NULL); + setup_destroy(na); +} + +static void confirm_event(GIOChannel *chan, gpointer user_data) +{ + struct network_adapter *na = user_data; + bdaddr_t src, dst; + char address[18]; + GError *err = NULL; + guint ret; + + bt_io_get(chan, &err, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, address, + BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + goto drop; + } + + DBG("BNEP: incoming connect from %s", address); + + if (na->setup) { + error("Refusing connect from %s: setup in progress", address); + goto drop; + } + + if (!na->servers) + goto drop; + + na->setup = g_new0(struct network_session, 1); + bacpy(&na->setup->dst, &dst); + na->setup->io = g_io_channel_ref(chan); + + ret = btd_request_authorization(&src, &dst, BNEP_SVC_UUID, + auth_cb, na); + if (ret == 0) { + error("Refusing connect from %s", address); + setup_destroy(na); + goto drop; + } + + return; + +drop: + g_io_channel_shutdown(chan, TRUE, NULL); +} + +int server_init(gboolean secure) +{ + security = secure; + + return 0; +} + +static uint32_t register_server_record(struct network_server *ns) +{ + sdp_record_t *record; + + record = server_record_new(ns->name, ns->id); + if (!record) { + error("Unable to allocate new service record"); + return 0; + } + + if (adapter_service_add(ns->na->adapter, record) < 0) { + error("Failed to register service record"); + sdp_record_free(record); + return 0; + } + + DBG("got record id 0x%x", record->handle); + + return record->handle; +} + +static void server_remove_sessions(struct network_server *ns) +{ + GSList *list; + + for (list = ns->sessions; list; list = list->next) { + struct network_session *session = list->data; + + if (*session->dev == '\0') + continue; + + bnep_server_delete(ns->bridge, session->dev, &session->dst); + } + + g_slist_free_full(ns->sessions, session_free); + + ns->sessions = NULL; +} + +static void server_disconnect(DBusConnection *conn, void *user_data) +{ + struct network_server *ns = user_data; + + server_remove_sessions(ns); + + ns->watch_id = 0; + + if (ns->record_id) { + adapter_service_remove(ns->na->adapter, ns->record_id); + ns->record_id = 0; + } + + g_free(ns->bridge); + ns->bridge = NULL; +} + +static DBusMessage *register_server(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_adapter *na = data; + struct network_server *ns; + DBusMessage *reply; + const char *uuid, *bridge; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &uuid, + DBUS_TYPE_STRING, &bridge, DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + ns = find_server_by_uuid(na->servers, uuid); + if (ns == NULL) + return btd_error_failed(msg, "Invalid UUID"); + + if (ns->record_id) + return btd_error_already_exists(msg); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + ns->record_id = register_server_record(ns); + if (!ns->record_id) + return btd_error_failed(msg, "SDP record registration failed"); + + g_free(ns->bridge); + ns->bridge = g_strdup(bridge); + + ns->watch_id = g_dbus_add_disconnect_watch(conn, + dbus_message_get_sender(msg), + server_disconnect, ns, NULL); + + return reply; +} + +static DBusMessage *unregister_server(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_adapter *na = data; + struct network_server *ns; + DBusMessage *reply; + const char *uuid; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &uuid, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + ns = find_server_by_uuid(na->servers, uuid); + if (!ns) + return btd_error_failed(msg, "Invalid UUID"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + g_dbus_remove_watch(conn, ns->watch_id); + + server_disconnect(conn, ns); + + return reply; +} + +static void adapter_free(struct network_adapter *na) +{ + if (na->io != NULL) { + g_io_channel_shutdown(na->io, TRUE, NULL); + g_io_channel_unref(na->io); + } + + setup_destroy(na); + btd_adapter_unref(na->adapter); + g_free(na); +} + +static void server_free(void *data) +{ + struct network_server *ns = data; + + if (!ns) + return; + + server_remove_sessions(ns); + + if (ns->record_id) + adapter_service_remove(ns->na->adapter, ns->record_id); + + g_dbus_remove_watch(btd_get_dbus_connection(), ns->watch_id); + g_free(ns->name); + g_free(ns->bridge); + + g_free(ns); +} + +static void path_unregister(void *data) +{ + struct network_adapter *na = data; + + DBG("Unregistered interface %s on path %s", + NETWORK_SERVER_INTERFACE, adapter_get_path(na->adapter)); + + g_slist_free_full(na->servers, server_free); + + adapters = g_slist_remove(adapters, na); + adapter_free(na); +} + +static const GDBusMethodTable server_methods[] = { + { GDBUS_METHOD("Register", + GDBUS_ARGS({ "uuid", "s" }, { "bridge", "s" }), NULL, + register_server) }, + { GDBUS_METHOD("Unregister", + GDBUS_ARGS({ "uuid", "s" }), NULL, + unregister_server) }, + { } +}; + +static struct network_adapter *create_adapter(struct btd_adapter *adapter) +{ + struct network_adapter *na; + GError *err = NULL; + + na = g_new0(struct network_adapter, 1); + na->adapter = btd_adapter_ref(adapter); + + na->io = bt_io_listen(NULL, confirm_event, na, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(adapter), + BT_IO_OPT_PSM, BNEP_PSM, + BT_IO_OPT_OMTU, BNEP_MTU, + BT_IO_OPT_IMTU, BNEP_MTU, + BT_IO_OPT_SEC_LEVEL, + security ? BT_IO_SEC_MEDIUM : BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!na->io) { + error("%s", err->message); + g_error_free(err); + adapter_free(na); + return NULL; + } + + return na; +} + +int server_register(struct btd_adapter *adapter, uint16_t id) +{ + struct network_adapter *na; + struct network_server *ns; + const char *path; + + na = find_adapter(adapters, adapter); + if (!na) { + na = create_adapter(adapter); + if (!na) + return -EINVAL; + adapters = g_slist_append(adapters, na); + } + + ns = find_server(na->servers, id); + if (ns) + return 0; + + ns = g_new0(struct network_server, 1); + + ns->name = g_strdup("Network service"); + + path = adapter_get_path(adapter); + + if (g_slist_length(na->servers) > 0) + goto done; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), path, + NETWORK_SERVER_INTERFACE, + server_methods, NULL, NULL, na, + path_unregister)) { + error("D-Bus failed to register %s interface", + NETWORK_SERVER_INTERFACE); + server_free(ns); + return -1; + } + + DBG("Registered interface %s on path %s", NETWORK_SERVER_INTERFACE, + path); + +done: + bacpy(&ns->src, btd_adapter_get_address(adapter)); + ns->id = id; + ns->na = na; + ns->record_id = 0; + na->servers = g_slist_append(na->servers, ns); + + return 0; +} + +int server_unregister(struct btd_adapter *adapter, uint16_t id) +{ + struct network_adapter *na; + struct network_server *ns; + + na = find_adapter(adapters, adapter); + if (!na) + return -EINVAL; + + ns = find_server(na->servers, id); + if (!ns) + return -EINVAL; + + na->servers = g_slist_remove(na->servers, ns); + server_free(ns); + + if (g_slist_length(na->servers) > 0) + return 0; + + g_dbus_unregister_interface(btd_get_dbus_connection(), + adapter_get_path(adapter), + NETWORK_SERVER_INTERFACE); + + return 0; +} diff --git a/profiles/network/server.h b/profiles/network/server.h new file mode 100644 index 0000000..a76e6f7 --- /dev/null +++ b/profiles/network/server.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int server_init(gboolean secure); +int server_register(struct btd_adapter *adapter, uint16_t id); +int server_unregister(struct btd_adapter *adapter, uint16_t id); diff --git a/profiles/sap/main.c b/profiles/sap/main.c new file mode 100644 index 0000000..cd707ff --- /dev/null +++ b/profiles/sap/main.c @@ -0,0 +1,43 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Instituto Nokia de Tecnologia - INdT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "gdbus/gdbus.h" + +#include "src/plugin.h" +#include "manager.h" + +static int sap_init(void) +{ + return sap_manager_init(); +} + +static void sap_exit(void) +{ + sap_manager_exit(); +} + +BLUETOOTH_PLUGIN_DEFINE(sap, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, sap_init, sap_exit) diff --git a/profiles/sap/manager.c b/profiles/sap/manager.c new file mode 100644 index 0000000..b622397 --- /dev/null +++ b/profiles/sap/manager.c @@ -0,0 +1,72 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Instituto Nokia de Tecnologia - INdT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "src/log.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" + +#include "manager.h" +#include "server.h" + +static int sap_server_probe(struct btd_profile *p, struct btd_adapter *adapter) +{ + DBG("path %s", adapter_get_path(adapter)); + + return sap_server_register(adapter); +} + +static void sap_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + const char *path = adapter_get_path(adapter); + + DBG("path %s", path); + + sap_server_unregister(path); +} + +static struct btd_profile sap_profile = { + .name = "sap-server", + .adapter_probe = sap_server_probe, + .adapter_remove = sap_server_remove, +}; + +int sap_manager_init(void) +{ + btd_profile_register(&sap_profile); + + return 0; +} + +void sap_manager_exit(void) +{ + btd_profile_unregister(&sap_profile); +} diff --git a/profiles/sap/manager.h b/profiles/sap/manager.h new file mode 100644 index 0000000..6601a03 --- /dev/null +++ b/profiles/sap/manager.h @@ -0,0 +1,22 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Instituto Nokia de Tecnologia - INdT + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +int sap_manager_init(void); +void sap_manager_exit(void); diff --git a/profiles/sap/sap-dummy.c b/profiles/sap/sap-dummy.c new file mode 100644 index 0000000..53463ca --- /dev/null +++ b/profiles/sap/sap-dummy.c @@ -0,0 +1,376 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 ST-Ericsson SA + * Copyright (C) 2011 Tieto Poland + * + * Author: Waldemar Rymarkiewicz + * for ST-Ericsson + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "gdbus/gdbus.h" + +#include "src/dbus-common.h" +#include "src/error.h" +#include "src/log.h" + +#include "sap.h" + +#define SAP_DUMMY_IFACE "org.bluez.SimAccessTest1" +#define SAP_DUMMY_PATH "/org/bluez/test" + +enum { + SIM_DISCONNECTED = 0x00, + SIM_CONNECTED = 0x01, + SIM_POWERED_OFF = 0x02, + SIM_MISSING = 0x03 +}; + +static unsigned int init_cnt = 0; + +static int sim_card_conn_status = SIM_DISCONNECTED; +static void *sap_data = NULL; /* SAP server private data. */ +static gboolean ongoing_call_status = FALSE; +static int max_msg_size_supported = 512; + +void sap_connect_req(void *sap_device, uint16_t maxmsgsize) +{ + DBG("status: %d", sim_card_conn_status); + + if (sim_card_conn_status != SIM_DISCONNECTED) { + sap_connect_rsp(sap_device, SAP_STATUS_CONNECTION_FAILED); + return; + } + + if (max_msg_size_supported > maxmsgsize) { + sap_connect_rsp(sap_device, SAP_STATUS_MAX_MSG_SIZE_TOO_SMALL); + return; + } + + if (max_msg_size_supported < maxmsgsize) { + sap_connect_rsp(sap_device, + SAP_STATUS_MAX_MSG_SIZE_NOT_SUPPORTED); + return; + } + + if (ongoing_call_status) { + sap_connect_rsp(sap_device, SAP_STATUS_OK_ONGOING_CALL); + return; + } + + sim_card_conn_status = SIM_CONNECTED; + sap_data = sap_device; + + sap_connect_rsp(sap_device, SAP_STATUS_OK); + sap_status_ind(sap_device, SAP_STATUS_CHANGE_CARD_RESET); +} + +void sap_disconnect_req(void *sap_device, uint8_t linkloss) +{ + sim_card_conn_status = SIM_DISCONNECTED; + sap_data = NULL; + ongoing_call_status = FALSE; + + DBG("status: %d", sim_card_conn_status); + + if (linkloss) + return; + + sap_disconnect_rsp(sap_device); +} + +void sap_transfer_apdu_req(void *sap_device, struct sap_parameter *param) +{ + char apdu[] = "APDU response!"; + + DBG("status: %d", sim_card_conn_status); + + switch (sim_card_conn_status) { + case SIM_MISSING: + sap_transfer_apdu_rsp(sap_device, + SAP_RESULT_ERROR_CARD_REMOVED, NULL, 0); + break; + case SIM_POWERED_OFF: + sap_transfer_apdu_rsp(sap_device, SAP_RESULT_ERROR_POWERED_OFF, + NULL, 0); + break; + case SIM_DISCONNECTED: + sap_transfer_apdu_rsp(sap_device, + SAP_RESULT_ERROR_NOT_ACCESSIBLE, NULL, 0); + break; + case SIM_CONNECTED: + sap_transfer_apdu_rsp(sap_device, SAP_RESULT_OK, + (uint8_t *)apdu, sizeof(apdu)); + break; + } +} + +void sap_transfer_atr_req(void *sap_device) +{ + char atr[] = "ATR response!"; + + DBG("status: %d", sim_card_conn_status); + + switch (sim_card_conn_status) { + case SIM_MISSING: + sap_transfer_atr_rsp(sap_device, SAP_RESULT_ERROR_CARD_REMOVED, + NULL, 0); + break; + case SIM_POWERED_OFF: + sap_transfer_atr_rsp(sap_device, SAP_RESULT_ERROR_POWERED_OFF, + NULL, 0); + break; + case SIM_DISCONNECTED: + sap_transfer_atr_rsp(sap_device, SAP_RESULT_ERROR_NO_REASON, + NULL, 0); + break; + case SIM_CONNECTED: + sap_transfer_atr_rsp(sap_device, SAP_RESULT_OK, + (uint8_t *)atr, sizeof(atr)); + break; + } +} + +void sap_power_sim_off_req(void *sap_device) +{ + DBG("status: %d", sim_card_conn_status); + + switch (sim_card_conn_status) { + case SIM_MISSING: + sap_power_sim_off_rsp(sap_device, + SAP_RESULT_ERROR_CARD_REMOVED); + break; + case SIM_POWERED_OFF: + sap_power_sim_off_rsp(sap_device, + SAP_RESULT_ERROR_POWERED_OFF); + break; + case SIM_DISCONNECTED: + sap_power_sim_off_rsp(sap_device, SAP_RESULT_ERROR_NO_REASON); + break; + case SIM_CONNECTED: + sap_power_sim_off_rsp(sap_device, SAP_RESULT_OK); + sim_card_conn_status = SIM_POWERED_OFF; + break; + } +} + +void sap_power_sim_on_req(void *sap_device) +{ + DBG("status: %d", sim_card_conn_status); + + switch (sim_card_conn_status) { + case SIM_MISSING: + sap_power_sim_on_rsp(sap_device, + SAP_RESULT_ERROR_CARD_REMOVED); + break; + case SIM_POWERED_OFF: + sap_power_sim_on_rsp(sap_device, SAP_RESULT_OK); + sim_card_conn_status = SIM_CONNECTED; + break; + case SIM_DISCONNECTED: + sap_power_sim_on_rsp(sap_device, + SAP_RESULT_ERROR_NOT_ACCESSIBLE); + break; + case SIM_CONNECTED: + sap_power_sim_on_rsp(sap_device, SAP_RESULT_ERROR_NO_REASON); + break; + } +} + +void sap_reset_sim_req(void *sap_device) +{ + DBG("status: %d", sim_card_conn_status); + + switch (sim_card_conn_status) { + case SIM_MISSING: + sap_reset_sim_rsp(sap_device, SAP_RESULT_ERROR_CARD_REMOVED); + break; + case SIM_POWERED_OFF: + sap_reset_sim_rsp(sap_device, SAP_RESULT_ERROR_POWERED_OFF); + break; + case SIM_DISCONNECTED: + sap_reset_sim_rsp(sap_device, SAP_RESULT_ERROR_NO_REASON); + break; + case SIM_CONNECTED: + sap_reset_sim_rsp(sap_device, SAP_RESULT_OK); + break; + } +} + +void sap_transfer_card_reader_status_req(void *sap_device) +{ + DBG("status: %d", sim_card_conn_status); + + if (sim_card_conn_status != SIM_CONNECTED) { + sap_transfer_card_reader_status_rsp(sap_device, + SAP_RESULT_ERROR_NO_REASON, 0xF1); + return; + } + + sap_transfer_card_reader_status_rsp(sap_device, SAP_RESULT_OK, 0xF1); +} + +void sap_set_transport_protocol_req(void *sap_device, + struct sap_parameter *param) +{ + sap_transport_protocol_rsp(sap_device, SAP_RESULT_NOT_SUPPORTED); +} + +static DBusMessage *ongoing_call(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_bool_t ongoing; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &ongoing, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + if (ongoing_call_status && !ongoing) { + /* An ongoing call has finished. Continue connection.*/ + sap_status_ind(sap_data, SAP_STATUS_CHANGE_CARD_RESET); + ongoing_call_status = FALSE; + } else if (!ongoing_call_status && ongoing) { + /* An ongoing call has started.*/ + ongoing_call_status = TRUE; + } + + DBG("OngoingCall status set to %d", ongoing_call_status); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *max_msg_size(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_uint32_t size; + + if (sim_card_conn_status == SIM_CONNECTED) + return btd_error_failed(msg, + "Can't change msg size when connected."); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &size, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + max_msg_size_supported = size; + + DBG("MaxMessageSize set to %d", max_msg_size_supported); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *disconnect_immediate(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + if (sim_card_conn_status == SIM_DISCONNECTED) + return btd_error_failed(msg, "Already disconnected."); + + sim_card_conn_status = SIM_DISCONNECTED; + sap_disconnect_ind(sap_data, SAP_DISCONNECTION_TYPE_IMMEDIATE); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *card_status(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + dbus_uint32_t status; + + DBG("status %d", sim_card_conn_status); + + if (sim_card_conn_status != SIM_CONNECTED) + return btd_error_failed(msg, + "Can't change msg size when not connected."); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &status, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + switch (status) { + case 0: /* card removed */ + sim_card_conn_status = SIM_MISSING; + sap_status_ind(sap_data, SAP_STATUS_CHANGE_CARD_REMOVED); + break; + + case 1: /* card inserted */ + if (sim_card_conn_status == SIM_MISSING) { + sim_card_conn_status = SIM_CONNECTED; + sap_status_ind(sap_data, + SAP_STATUS_CHANGE_CARD_INSERTED); + } + break; + + case 2: /* card not longer available*/ + sim_card_conn_status = SIM_POWERED_OFF; + sap_status_ind(sap_data, SAP_STATUS_CHANGE_CARD_NOT_ACCESSIBLE); + break; + + default: + return btd_error_failed(msg, + "Unknown card status. Use 0, 1 or 2."); + } + + DBG("Card status changed to %d", status); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable dummy_methods[] = { + { GDBUS_EXPERIMENTAL_METHOD("OngoingCall", + GDBUS_ARGS({ "ongoing", "b" }), NULL, + ongoing_call) }, + { GDBUS_EXPERIMENTAL_METHOD("MaxMessageSize", + GDBUS_ARGS({ "size", "u" }), NULL, + max_msg_size) }, + { GDBUS_EXPERIMENTAL_METHOD("DisconnectImmediate", NULL, NULL, + disconnect_immediate) }, + { GDBUS_EXPERIMENTAL_METHOD("CardStatus", + GDBUS_ARGS({ "status", "" }), NULL, + card_status) }, + { } +}; + +int sap_init(void) +{ + if (init_cnt++) + return 0; + + if (g_dbus_register_interface(btd_get_dbus_connection(), SAP_DUMMY_PATH, + SAP_DUMMY_IFACE, dummy_methods, NULL, NULL, + NULL, NULL) == FALSE) { + init_cnt--; + return -1; + } + + return 0; +} + +void sap_exit(void) +{ + if (--init_cnt) + return; + + g_dbus_unregister_interface(btd_get_dbus_connection(), + SAP_DUMMY_PATH, SAP_DUMMY_IFACE); +} diff --git a/profiles/sap/sap.h b/profiles/sap/sap.h new file mode 100644 index 0000000..16c333a --- /dev/null +++ b/profiles/sap/sap.h @@ -0,0 +1,180 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Instituto Nokia de Tecnologia - INdT + * Copyright (C) 2010 ST-Ericsson SA + * + * Author: Marek Skowron for ST-Ericsson. + * Author: Waldemar Rymarkiewicz + * for ST-Ericsson. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#ifdef SAP_DEBUG +#define SAP_VDBG(fmt, arg...) DBG(fmt, arg) +#else +#define SAP_VDBG(fmt...) +#endif + +#define SAP_VERSION 0x0101 + +/* Connection Status - SAP v1.1 section 5.2.2 */ +enum sap_status { + SAP_STATUS_OK = 0x00, + SAP_STATUS_CONNECTION_FAILED = 0x01, + SAP_STATUS_MAX_MSG_SIZE_NOT_SUPPORTED = 0x02, + SAP_STATUS_MAX_MSG_SIZE_TOO_SMALL = 0x03, + SAP_STATUS_OK_ONGOING_CALL = 0x04 +}; + +/* Disconnection Type - SAP v1.1 section 5.2.3 */ +enum sap_disconnection_type { + SAP_DISCONNECTION_TYPE_GRACEFUL = 0x00, + SAP_DISCONNECTION_TYPE_IMMEDIATE = 0x01 +}; + +/* Result codes - SAP v1.1 section 5.2.4 */ +enum sap_result { + SAP_RESULT_OK = 0x00, + SAP_RESULT_ERROR_NO_REASON = 0x01, + SAP_RESULT_ERROR_NOT_ACCESSIBLE = 0x02, + SAP_RESULT_ERROR_POWERED_OFF = 0x03, + SAP_RESULT_ERROR_CARD_REMOVED = 0x04, + SAP_RESULT_ERROR_POWERED_ON = 0x05, + SAP_RESULT_ERROR_NO_DATA = 0x06, + SAP_RESULT_NOT_SUPPORTED = 0x07 +}; + +/* Status Change - SAP v1.1 section 5.2.8 */ +enum sap_status_change { + SAP_STATUS_CHANGE_UNKNOWN_ERROR = 0x00, + SAP_STATUS_CHANGE_CARD_RESET = 0x01, + SAP_STATUS_CHANGE_CARD_NOT_ACCESSIBLE = 0x02, + SAP_STATUS_CHANGE_CARD_REMOVED = 0x03, + SAP_STATUS_CHANGE_CARD_INSERTED = 0x04, + SAP_STATUS_CHANGE_CARD_RECOVERED = 0x05 +}; + +/* Message format - SAP v1.1 section 5.1 */ +struct sap_parameter { + uint8_t id; + uint8_t reserved; + uint16_t len; + uint8_t val[0]; + /* + * Padding bytes 0-3 bytes + */ +} __attribute__((packed)); + +struct sap_message { + uint8_t id; + uint8_t nparam; + uint16_t reserved; + struct sap_parameter param[0]; +} __attribute__((packed)); + +#define SAP_BUF_SIZE 512 +#define SAP_MSG_HEADER_SIZE 4 + +enum sap_protocol { + SAP_CONNECT_REQ = 0x00, + SAP_CONNECT_RESP = 0x01, + SAP_DISCONNECT_REQ = 0x02, + SAP_DISCONNECT_RESP = 0x03, + SAP_DISCONNECT_IND = 0x04, + SAP_TRANSFER_APDU_REQ = 0x05, + SAP_TRANSFER_APDU_RESP = 0x06, + SAP_TRANSFER_ATR_REQ = 0x07, + SAP_TRANSFER_ATR_RESP = 0x08, + SAP_POWER_SIM_OFF_REQ = 0x09, + SAP_POWER_SIM_OFF_RESP = 0x0A, + SAP_POWER_SIM_ON_REQ = 0x0B, + SAP_POWER_SIM_ON_RESP = 0x0C, + SAP_RESET_SIM_REQ = 0x0D, + SAP_RESET_SIM_RESP = 0x0E, + SAP_TRANSFER_CARD_READER_STATUS_REQ = 0x0F, + SAP_TRANSFER_CARD_READER_STATUS_RESP = 0x10, + SAP_STATUS_IND = 0x11, + SAP_ERROR_RESP = 0x12, + SAP_SET_TRANSPORT_PROTOCOL_REQ = 0x13, + SAP_SET_TRANSPORT_PROTOCOL_RESP = 0x14 +}; + +/* Parameters Ids - SAP 1.1 section 5.2 */ +enum sap_param_id { + SAP_PARAM_ID_MAX_MSG_SIZE = 0x00, + SAP_PARAM_ID_CONN_STATUS = 0x01, + SAP_PARAM_ID_RESULT_CODE = 0x02, + SAP_PARAM_ID_DISCONNECT_IND = 0x03, + SAP_PARAM_ID_COMMAND_APDU = 0x04, + SAP_PARAM_ID_COMMAND_APDU7816 = 0x10, + SAP_PARAM_ID_RESPONSE_APDU = 0x05, + SAP_PARAM_ID_ATR = 0x06, + SAP_PARAM_ID_CARD_READER_STATUS = 0x07, + SAP_PARAM_ID_STATUS_CHANGE = 0x08, + SAP_PARAM_ID_TRANSPORT_PROTOCOL = 0x09 +}; + +#define SAP_PARAM_ID_MAX_MSG_SIZE_LEN 0x02 +#define SAP_PARAM_ID_CONN_STATUS_LEN 0x01 +#define SAP_PARAM_ID_RESULT_CODE_LEN 0x01 +#define SAP_PARAM_ID_DISCONNECT_IND_LEN 0x01 +#define SAP_PARAM_ID_CARD_READER_STATUS_LEN 0x01 +#define SAP_PARAM_ID_STATUS_CHANGE_LEN 0x01 +#define SAP_PARAM_ID_TRANSPORT_PROTO_LEN 0x01 + +/* Transport Protocol - SAP v1.1 section 5.2.9 */ +enum sap_transport_protocol { + SAP_TRANSPORT_PROTOCOL_T0 = 0x00, + SAP_TRANSPORT_PROTOCOL_T1 = 0x01 +}; + +/*SAP driver init and exit routines. Implemented by sap-*.c */ +int sap_init(void); +void sap_exit(void); + +/* SAP requests implemented by sap-*.c */ +void sap_connect_req(void *sap_device, uint16_t maxmsgsize); +void sap_disconnect_req(void *sap_device, uint8_t linkloss); +void sap_transfer_apdu_req(void *sap_device, struct sap_parameter *param); +void sap_transfer_atr_req(void *sap_device); +void sap_power_sim_off_req(void *sap_device); +void sap_power_sim_on_req(void *sap_device); +void sap_reset_sim_req(void *sap_device); +void sap_transfer_card_reader_status_req(void *sap_device); +void sap_set_transport_protocol_req(void *sap_device, + struct sap_parameter *param); + +/*SAP responses to SAP requests. Implemented by server.c */ +int sap_connect_rsp(void *sap_device, uint8_t status); +int sap_disconnect_rsp(void *sap_device); +int sap_transfer_apdu_rsp(void *sap_device, uint8_t result, + uint8_t *sap_apdu_resp, uint16_t length); +int sap_transfer_atr_rsp(void *sap_device, uint8_t result, + uint8_t *sap_atr, uint16_t length); +int sap_power_sim_off_rsp(void *sap_device, uint8_t result); +int sap_power_sim_on_rsp(void *sap_device, uint8_t result); +int sap_reset_sim_rsp(void *sap_device, uint8_t result); +int sap_transfer_card_reader_status_rsp(void *sap_device, uint8_t result, + uint8_t status); +int sap_transport_protocol_rsp(void *sap_device, uint8_t result); + +/* Event indication. Implemented by server.c*/ +int sap_status_ind(void *sap_device, uint8_t status_change); +int sap_disconnect_ind(void *sap_device, uint8_t disc_type); diff --git a/profiles/sap/server.c b/profiles/sap/server.c new file mode 100644 index 0000000..5de682a --- /dev/null +++ b/profiles/sap/server.c @@ -0,0 +1,1422 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Instituto Nokia de Tecnologia - INdT + * Copyright (C) 2010 ST-Ericsson SA + * Copyright (C) 2011 Tieto Poland + * + * Author: Marek Skowron for ST-Ericsson. + * Author: Waldemar Rymarkiewicz + * for ST-Ericsson. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "btio/btio.h" +#include "src/adapter.h" +#include "src/sdpd.h" +#include "src/log.h" +#include "src/error.h" +#include "src/dbus-common.h" +#include "src/shared/util.h" + +#include "sap.h" +#include "server.h" + +#define SAP_SERVER_INTERFACE "org.bluez.SimAccess1" +#define SAP_SERVER_CHANNEL 8 + +#define PADDING4(x) ((4 - ((x) & 0x03)) & 0x03) +#define PARAMETER_SIZE(x) (sizeof(struct sap_parameter) + x + PADDING4(x)) + +#define SAP_NO_REQ 0xFF +#define SAP_DISCONNECTION_TYPE_CLIENT 0xFF + +#define SAP_TIMER_GRACEFUL_DISCONNECT 30 +#define SAP_TIMER_NO_ACTIVITY 30 + +enum { + SAP_STATE_DISCONNECTED, + SAP_STATE_CONNECT_IN_PROGRESS, + SAP_STATE_CONNECT_MODEM_BUSY, + SAP_STATE_CONNECTED, + SAP_STATE_GRACEFUL_DISCONNECT, + SAP_STATE_IMMEDIATE_DISCONNECT, + SAP_STATE_CLIENT_DISCONNECT +}; + +struct sap_connection { + GIOChannel *io; + uint32_t state; + uint8_t processing_req; + guint timer_id; +}; + +struct sap_server { + struct btd_adapter *adapter; + uint32_t record_id; + GIOChannel *listen_io; + struct sap_connection *conn; +}; + +static void start_guard_timer(struct sap_server *server, guint interval); +static void stop_guard_timer(struct sap_server *server); +static gboolean guard_timeout(gpointer data); + +static size_t add_result_parameter(uint8_t result, + struct sap_parameter *param) +{ + param->id = SAP_PARAM_ID_RESULT_CODE; + param->len = htons(SAP_PARAM_ID_RESULT_CODE_LEN); + *param->val = result; + + return PARAMETER_SIZE(SAP_PARAM_ID_RESULT_CODE_LEN); +} + +static int is_power_sim_off_req_allowed(uint8_t processing_req) +{ + switch (processing_req) { + case SAP_NO_REQ: + case SAP_TRANSFER_APDU_REQ: + case SAP_TRANSFER_ATR_REQ: + case SAP_POWER_SIM_ON_REQ: + case SAP_RESET_SIM_REQ: + case SAP_TRANSFER_CARD_READER_STATUS_REQ: + return 1; + default: + return 0; + } +} + +static int is_reset_sim_req_allowed(uint8_t processing_req) +{ + switch (processing_req) { + case SAP_NO_REQ: + case SAP_TRANSFER_APDU_REQ: + case SAP_TRANSFER_ATR_REQ: + case SAP_TRANSFER_CARD_READER_STATUS_REQ: + return 1; + default: + return 0; + } +} + +static int check_msg(struct sap_message *msg) +{ + switch (msg->id) { + case SAP_CONNECT_REQ: + if (msg->nparam != 0x01) + return -EBADMSG; + + if (msg->param->id != SAP_PARAM_ID_MAX_MSG_SIZE) + return -EBADMSG; + + if (ntohs(msg->param->len) != SAP_PARAM_ID_MAX_MSG_SIZE_LEN) + return -EBADMSG; + + break; + + case SAP_TRANSFER_APDU_REQ: + if (msg->nparam != 0x01) + return -EBADMSG; + + if (msg->param->id != SAP_PARAM_ID_COMMAND_APDU) + if (msg->param->id != SAP_PARAM_ID_COMMAND_APDU7816) + return -EBADMSG; + + if (msg->param->len == 0x00) + return -EBADMSG; + + break; + + case SAP_SET_TRANSPORT_PROTOCOL_REQ: + if (msg->nparam != 0x01) + return -EBADMSG; + + if (msg->param->id != SAP_PARAM_ID_TRANSPORT_PROTOCOL) + return -EBADMSG; + + if (ntohs(msg->param->len) != SAP_PARAM_ID_TRANSPORT_PROTO_LEN) + return -EBADMSG; + + if (*msg->param->val != SAP_TRANSPORT_PROTOCOL_T0) + if (*msg->param->val != SAP_TRANSPORT_PROTOCOL_T1) + return -EBADMSG; + + break; + + case SAP_DISCONNECT_REQ: + case SAP_TRANSFER_ATR_REQ: + case SAP_POWER_SIM_OFF_REQ: + case SAP_POWER_SIM_ON_REQ: + case SAP_RESET_SIM_REQ: + case SAP_TRANSFER_CARD_READER_STATUS_REQ: + if (msg->nparam != 0x00) + return -EBADMSG; + + break; + } + + return 0; +} + +static sdp_record_t *create_sap_record(uint8_t channel) +{ + sdp_list_t *apseq, *aproto, *profiles, *proto[2], *root, *svclass_id; + uuid_t sap_uuid, gt_uuid, root_uuid, l2cap, rfcomm; + sdp_profile_desc_t profile; + sdp_record_t *record; + sdp_data_t *ch; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + sdp_list_free(root, NULL); + + sdp_uuid16_create(&sap_uuid, SAP_SVCLASS_ID); + svclass_id = sdp_list_append(NULL, &sap_uuid); + sdp_uuid16_create(>_uuid, GENERIC_TELEPHONY_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, >_uuid); + + sdp_set_service_classes(record, svclass_id); + sdp_list_free(svclass_id, NULL); + + sdp_uuid16_create(&profile.uuid, SAP_PROFILE_ID); + profile.version = SAP_VERSION; + profiles = sdp_list_append(NULL, &profile); + sdp_set_profile_descs(record, profiles); + sdp_list_free(profiles, NULL); + + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&rfcomm, RFCOMM_UUID); + proto[1] = sdp_list_append(NULL, &rfcomm); + ch = sdp_data_alloc(SDP_UINT8, &channel); + proto[1] = sdp_list_append(proto[1], ch); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "SIM Access Server", NULL, NULL); + + sdp_data_free(ch); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(aproto, NULL); + + return record; +} + +static int send_message(struct sap_connection *conn, void *buf, size_t size) +{ + size_t written = 0; + GError *gerr = NULL; + GIOStatus gstatus; + + SAP_VDBG("conn %p, size %zu", conn, size); + + gstatus = g_io_channel_write_chars(conn->io, buf, size, &written, + &gerr); + if (gstatus != G_IO_STATUS_NORMAL) { + if (gerr) + g_error_free(gerr); + + error("write error (0x%02x).", gstatus); + return -EIO; + } + + if (written != size) { + error("written %zu bytes out of %zu", written, size); + return -EIO; + } + + return written; +} + +static int disconnect_ind(struct sap_connection *conn, uint8_t disc_type) +{ + char buf[SAP_BUF_SIZE]; + struct sap_message *msg = (struct sap_message *) buf; + struct sap_parameter *param = (struct sap_parameter *) msg->param; + size_t size = sizeof(struct sap_message); + + DBG("data %p state %d disc_type 0x%02x", conn, conn->state, disc_type); + + memset(buf, 0, sizeof(buf)); + msg->id = SAP_DISCONNECT_IND; + msg->nparam = 0x01; + + /* Add disconnection type param. */ + param->id = SAP_PARAM_ID_DISCONNECT_IND; + param->len = htons(SAP_PARAM_ID_DISCONNECT_IND_LEN); + *param->val = disc_type; + size += PARAMETER_SIZE(SAP_PARAM_ID_DISCONNECT_IND_LEN); + + return send_message(conn, buf, size); +} + +static int sap_error_rsp(struct sap_connection *conn) +{ + struct sap_message msg; + + memset(&msg, 0, sizeof(msg)); + msg.id = SAP_ERROR_RESP; + + error("SAP error (state %d pr 0x%02x).", conn->state, + conn->processing_req); + + return send_message(conn, &msg, sizeof(msg)); +} + +static void connect_req(struct sap_server *server, + struct sap_parameter *param) +{ + struct sap_connection *conn = server->conn; + uint16_t maxmsgsize; + + DBG("conn %p state %d", conn, conn->state); + + if (!param) + goto error_rsp; + + if (conn->state != SAP_STATE_DISCONNECTED) + goto error_rsp; + + stop_guard_timer(server); + + maxmsgsize = get_be16(¶m->val); + + DBG("Connect MaxMsgSize: 0x%04x", maxmsgsize); + + conn->state = SAP_STATE_CONNECT_IN_PROGRESS; + + if (maxmsgsize <= SAP_BUF_SIZE) { + conn->processing_req = SAP_CONNECT_REQ; + sap_connect_req(server, maxmsgsize); + } else { + sap_connect_rsp(server, SAP_STATUS_MAX_MSG_SIZE_NOT_SUPPORTED); + } + + return; + +error_rsp: + sap_error_rsp(conn); +} + +static int disconnect_req(struct sap_server *server, uint8_t disc_type) +{ + struct sap_connection *conn = server->conn; + + DBG("conn %p state %d disc_type 0x%02x", conn, conn->state, disc_type); + + switch (disc_type) { + case SAP_DISCONNECTION_TYPE_GRACEFUL: + if (conn->state == SAP_STATE_DISCONNECTED || + conn->state == SAP_STATE_CONNECT_IN_PROGRESS || + conn->state == SAP_STATE_CONNECT_MODEM_BUSY) + return -EPERM; + + if (conn->state == SAP_STATE_CONNECTED) { + conn->state = SAP_STATE_GRACEFUL_DISCONNECT; + conn->processing_req = SAP_NO_REQ; + + disconnect_ind(conn, disc_type); + /* Timer will disconnect if client won't do.*/ + start_guard_timer(server, + SAP_TIMER_GRACEFUL_DISCONNECT); + } + + return 0; + + case SAP_DISCONNECTION_TYPE_IMMEDIATE: + if (conn->state == SAP_STATE_DISCONNECTED || + conn->state == SAP_STATE_CONNECT_IN_PROGRESS || + conn->state == SAP_STATE_CONNECT_MODEM_BUSY) + return -EPERM; + + if (conn->state == SAP_STATE_CONNECTED || + conn->state == SAP_STATE_GRACEFUL_DISCONNECT) { + conn->state = SAP_STATE_IMMEDIATE_DISCONNECT; + conn->processing_req = SAP_NO_REQ; + + stop_guard_timer(server); + disconnect_ind(conn, disc_type); + sap_disconnect_req(server, 0); + } + + return 0; + + case SAP_DISCONNECTION_TYPE_CLIENT: + if (conn->state != SAP_STATE_CONNECTED && + conn->state != SAP_STATE_GRACEFUL_DISCONNECT) { + sap_error_rsp(conn); + return -EPERM; + } + + conn->state = SAP_STATE_CLIENT_DISCONNECT; + conn->processing_req = SAP_NO_REQ; + + stop_guard_timer(server); + sap_disconnect_req(server, 0); + + return 0; + + default: + error("Unknown disconnection type (0x%02x).", disc_type); + return -EINVAL; + } +} + +static void transfer_apdu_req(struct sap_server *server, + struct sap_parameter *param) +{ + struct sap_connection *conn = server->conn; + + SAP_VDBG("conn %p state %d", conn, conn->state); + + if (!param) + goto error_rsp; + + param->len = ntohs(param->len); + + if (conn->state != SAP_STATE_CONNECTED && + conn->state != SAP_STATE_GRACEFUL_DISCONNECT) + goto error_rsp; + + if (conn->processing_req != SAP_NO_REQ) + goto error_rsp; + + conn->processing_req = SAP_TRANSFER_APDU_REQ; + sap_transfer_apdu_req(server, param); + + return; + +error_rsp: + sap_error_rsp(conn); +} + +static void transfer_atr_req(struct sap_server *server) +{ + struct sap_connection *conn = server->conn; + + DBG("conn %p state %d", conn, conn->state); + + if (conn->state != SAP_STATE_CONNECTED) + goto error_rsp; + + if (conn->processing_req != SAP_NO_REQ) + goto error_rsp; + + conn->processing_req = SAP_TRANSFER_ATR_REQ; + sap_transfer_atr_req(server); + + return; + +error_rsp: + sap_error_rsp(conn); +} + +static void power_sim_off_req(struct sap_server *server) +{ + struct sap_connection *conn = server->conn; + + DBG("conn %p state %d", conn, conn->state); + + if (conn->state != SAP_STATE_CONNECTED) + goto error_rsp; + + if (!is_power_sim_off_req_allowed(conn->processing_req)) + goto error_rsp; + + conn->processing_req = SAP_POWER_SIM_OFF_REQ; + sap_power_sim_off_req(server); + + return; + +error_rsp: + sap_error_rsp(conn); +} + +static void power_sim_on_req(struct sap_server *server) +{ + struct sap_connection *conn = server->conn; + + DBG("conn %p state %d", conn, conn->state); + + if (conn->state != SAP_STATE_CONNECTED) + goto error_rsp; + + if (conn->processing_req != SAP_NO_REQ) + goto error_rsp; + + conn->processing_req = SAP_POWER_SIM_ON_REQ; + sap_power_sim_on_req(server); + + return; + +error_rsp: + sap_error_rsp(conn); +} + +static void reset_sim_req(struct sap_server *server) +{ + struct sap_connection *conn = server->conn; + + DBG("conn %p state %d", conn, conn->state); + + if (conn->state != SAP_STATE_CONNECTED) + goto error_rsp; + + if (!is_reset_sim_req_allowed(conn->processing_req)) + goto error_rsp; + + conn->processing_req = SAP_RESET_SIM_REQ; + sap_reset_sim_req(server); + + return; + +error_rsp: + sap_error_rsp(conn); +} + +static void transfer_card_reader_status_req(struct sap_server *server) +{ + struct sap_connection *conn = server->conn; + + DBG("conn %p state %d", conn, conn->state); + + if (conn->state != SAP_STATE_CONNECTED) + goto error_rsp; + + if (conn->processing_req != SAP_NO_REQ) + goto error_rsp; + + conn->processing_req = SAP_TRANSFER_CARD_READER_STATUS_REQ; + sap_transfer_card_reader_status_req(server); + + return; + +error_rsp: + sap_error_rsp(conn); +} + +static void set_transport_protocol_req(struct sap_server *server, + struct sap_parameter *param) +{ + struct sap_connection *conn = server->conn; + + if (!param) + goto error_rsp; + + DBG("conn %p state %d param %p", conn, conn->state, param); + + if (conn->state != SAP_STATE_CONNECTED) + goto error_rsp; + + if (conn->processing_req != SAP_NO_REQ) + goto error_rsp; + + conn->processing_req = SAP_SET_TRANSPORT_PROTOCOL_REQ; + sap_set_transport_protocol_req(server, param); + + return; + +error_rsp: + sap_error_rsp(conn); +} + +static void start_guard_timer(struct sap_server *server, guint interval) +{ + struct sap_connection *conn = server->conn; + + if (!conn) + return; + + if (!conn->timer_id) + conn->timer_id = g_timeout_add_seconds(interval, guard_timeout, + server); + else + error("Timer is already active."); +} + +static void stop_guard_timer(struct sap_server *server) +{ + struct sap_connection *conn = server->conn; + + if (conn && conn->timer_id) { + g_source_remove(conn->timer_id); + conn->timer_id = 0; + } +} + +static gboolean guard_timeout(gpointer data) +{ + struct sap_server *server = data; + struct sap_connection *conn = server->conn; + + if (!conn) + return FALSE; + + DBG("conn %p state %d pr 0x%02x", conn, conn->state, + conn->processing_req); + + conn->timer_id = 0; + + switch (conn->state) { + case SAP_STATE_DISCONNECTED: + /* Client opened RFCOMM channel but didn't send CONNECT_REQ, + * in fixed time or client disconnected SAP connection but + * didn't closed RFCOMM channel in fixed time.*/ + if (conn->io) { + g_io_channel_shutdown(conn->io, TRUE, NULL); + g_io_channel_unref(conn->io); + conn->io = NULL; + } + break; + + case SAP_STATE_GRACEFUL_DISCONNECT: + /* Client didn't disconnect SAP connection in fixed time, + * so close SAP connection immediately. */ + disconnect_req(server, SAP_DISCONNECTION_TYPE_IMMEDIATE); + break; + + default: + error("Unexpected state (%d).", conn->state); + break; + } + + return FALSE; +} + +static void sap_set_connected(struct sap_server *server) +{ + server->conn->state = SAP_STATE_CONNECTED; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + adapter_get_path(server->adapter), + SAP_SERVER_INTERFACE, "Connected"); +} + +int sap_connect_rsp(void *sap_device, uint8_t status) +{ + struct sap_server *server = sap_device; + struct sap_connection *conn = server->conn; + char buf[SAP_BUF_SIZE]; + struct sap_message *msg = (struct sap_message *) buf; + struct sap_parameter *param = (struct sap_parameter *) msg->param; + size_t size = sizeof(struct sap_message); + + if (!conn) + return -EINVAL; + + DBG("state %d pr 0x%02x status 0x%02x", conn->state, + conn->processing_req, status); + + if (conn->state != SAP_STATE_CONNECT_IN_PROGRESS) + return -EPERM; + + memset(buf, 0, sizeof(buf)); + msg->id = SAP_CONNECT_RESP; + msg->nparam = 0x01; + + /* Add connection status */ + param->id = SAP_PARAM_ID_CONN_STATUS; + param->len = htons(SAP_PARAM_ID_CONN_STATUS_LEN); + *param->val = status; + size += PARAMETER_SIZE(SAP_PARAM_ID_CONN_STATUS_LEN); + + + switch (status) { + case SAP_STATUS_OK: + sap_set_connected(server); + break; + case SAP_STATUS_OK_ONGOING_CALL: + DBG("ongoing call. Wait for reset indication!"); + conn->state = SAP_STATE_CONNECT_MODEM_BUSY; + break; + case SAP_STATUS_MAX_MSG_SIZE_NOT_SUPPORTED: /* Add MaxMsgSize */ + msg->nparam++; + param = (struct sap_parameter *) &buf[size]; + param->id = SAP_PARAM_ID_MAX_MSG_SIZE; + param->len = htons(SAP_PARAM_ID_MAX_MSG_SIZE_LEN); + put_be16(SAP_BUF_SIZE, ¶m->val); + size += PARAMETER_SIZE(SAP_PARAM_ID_MAX_MSG_SIZE_LEN); + /* fall through */ + default: + conn->state = SAP_STATE_DISCONNECTED; + + /* Timer will shutdown channel if client doesn't send + * CONNECT_REQ or doesn't shutdown channel itself.*/ + start_guard_timer(server, SAP_TIMER_NO_ACTIVITY); + break; + } + + conn->processing_req = SAP_NO_REQ; + + return send_message(conn, buf, size); +} + +int sap_disconnect_rsp(void *sap_device) +{ + struct sap_server *server = sap_device; + struct sap_connection *conn = server->conn; + struct sap_message msg; + + if (!conn) + return -EINVAL; + + DBG("state %d pr 0x%02x", conn->state, conn->processing_req); + + switch (conn->state) { + case SAP_STATE_CLIENT_DISCONNECT: + memset(&msg, 0, sizeof(msg)); + msg.id = SAP_DISCONNECT_RESP; + + conn->state = SAP_STATE_DISCONNECTED; + conn->processing_req = SAP_NO_REQ; + + /* Timer will close channel if client doesn't do it.*/ + start_guard_timer(server, SAP_TIMER_NO_ACTIVITY); + + return send_message(conn, &msg, sizeof(msg)); + + case SAP_STATE_IMMEDIATE_DISCONNECT: + conn->state = SAP_STATE_DISCONNECTED; + conn->processing_req = SAP_NO_REQ; + + if (conn->io) { + g_io_channel_shutdown(conn->io, TRUE, NULL); + g_io_channel_unref(conn->io); + conn->io = NULL; + } + + return 0; + + default: + break; + } + + return 0; +} + +int sap_transfer_apdu_rsp(void *sap_device, uint8_t result, uint8_t *apdu, + uint16_t length) +{ + struct sap_server *server = sap_device; + struct sap_connection *conn = server->conn; + char buf[SAP_BUF_SIZE]; + struct sap_message *msg = (struct sap_message *) buf; + struct sap_parameter *param = (struct sap_parameter *) msg->param; + size_t size = sizeof(struct sap_message); + + if (!conn) + return -EINVAL; + + SAP_VDBG("state %d pr 0x%02x", conn->state, conn->processing_req); + + if (conn->processing_req != SAP_TRANSFER_APDU_REQ) + return 0; + + if (result == SAP_RESULT_OK && (!apdu || (apdu && length == 0x00))) + return -EINVAL; + + memset(buf, 0, sizeof(buf)); + msg->id = SAP_TRANSFER_APDU_RESP; + msg->nparam = 0x01; + size += add_result_parameter(result, param); + + /* Add APDU response. */ + if (result == SAP_RESULT_OK) { + msg->nparam++; + param = (struct sap_parameter *) &buf[size]; + param->id = SAP_PARAM_ID_RESPONSE_APDU; + param->len = htons(length); + + size += PARAMETER_SIZE(length); + + if (size > SAP_BUF_SIZE) + return -EOVERFLOW; + + memcpy(param->val, apdu, length); + } + + conn->processing_req = SAP_NO_REQ; + + return send_message(conn, buf, size); +} + +int sap_transfer_atr_rsp(void *sap_device, uint8_t result, uint8_t *atr, + uint16_t length) +{ + struct sap_server *server = sap_device; + struct sap_connection *conn = server->conn; + char buf[SAP_BUF_SIZE]; + struct sap_message *msg = (struct sap_message *) buf; + struct sap_parameter *param = (struct sap_parameter *) msg->param; + size_t size = sizeof(struct sap_message); + + if (!conn) + return -EINVAL; + + DBG("result 0x%02x state %d pr 0x%02x len %d", result, conn->state, + conn->processing_req, length); + + if (conn->processing_req != SAP_TRANSFER_ATR_REQ) + return 0; + + if (result == SAP_RESULT_OK && (!atr || (atr && length == 0x00))) + return -EINVAL; + + memset(buf, 0, sizeof(buf)); + msg->id = SAP_TRANSFER_ATR_RESP; + msg->nparam = 0x01; + size += add_result_parameter(result, param); + + /* Add ATR response */ + if (result == SAP_RESULT_OK) { + msg->nparam++; + param = (struct sap_parameter *) &buf[size]; + param->id = SAP_PARAM_ID_ATR; + param->len = htons(length); + size += PARAMETER_SIZE(length); + + if (size > SAP_BUF_SIZE) + return -EOVERFLOW; + + memcpy(param->val, atr, length); + } + + conn->processing_req = SAP_NO_REQ; + + return send_message(conn, buf, size); +} + +int sap_power_sim_off_rsp(void *sap_device, uint8_t result) +{ + struct sap_server *server = sap_device; + struct sap_connection *conn = server->conn; + char buf[SAP_BUF_SIZE]; + struct sap_message *msg = (struct sap_message *) buf; + size_t size = sizeof(struct sap_message); + + if (!conn) + return -EINVAL; + + DBG("state %d pr 0x%02x", conn->state, conn->processing_req); + + if (conn->processing_req != SAP_POWER_SIM_OFF_REQ) + return 0; + + memset(buf, 0, sizeof(buf)); + msg->id = SAP_POWER_SIM_OFF_RESP; + msg->nparam = 0x01; + size += add_result_parameter(result, msg->param); + + conn->processing_req = SAP_NO_REQ; + + return send_message(conn, buf, size); +} + +int sap_power_sim_on_rsp(void *sap_device, uint8_t result) +{ + struct sap_server *server = sap_device; + struct sap_connection *conn = server->conn; + char buf[SAP_BUF_SIZE]; + struct sap_message *msg = (struct sap_message *) buf; + size_t size = sizeof(struct sap_message); + + if (!conn) + return -EINVAL; + + DBG("state %d pr 0x%02x", conn->state, conn->processing_req); + + if (conn->processing_req != SAP_POWER_SIM_ON_REQ) + return 0; + + memset(buf, 0, sizeof(buf)); + msg->id = SAP_POWER_SIM_ON_RESP; + msg->nparam = 0x01; + size += add_result_parameter(result, msg->param); + + conn->processing_req = SAP_NO_REQ; + + return send_message(conn, buf, size); +} + +int sap_reset_sim_rsp(void *sap_device, uint8_t result) +{ + struct sap_server *server = sap_device; + struct sap_connection *conn = server->conn; + char buf[SAP_BUF_SIZE]; + struct sap_message *msg = (struct sap_message *) buf; + size_t size = sizeof(struct sap_message); + + if (!conn) + return -EINVAL; + + DBG("state %d pr 0x%02x result 0x%02x", conn->state, + conn->processing_req, result); + + if (conn->processing_req != SAP_RESET_SIM_REQ) + return 0; + + memset(buf, 0, sizeof(buf)); + msg->id = SAP_RESET_SIM_RESP; + msg->nparam = 0x01; + size += add_result_parameter(result, msg->param); + + conn->processing_req = SAP_NO_REQ; + + return send_message(conn, buf, size); +} + +int sap_transfer_card_reader_status_rsp(void *sap_device, uint8_t result, + uint8_t status) +{ + struct sap_server *server = sap_device; + struct sap_connection *conn = server->conn; + char buf[SAP_BUF_SIZE]; + struct sap_message *msg = (struct sap_message *) buf; + struct sap_parameter *param = (struct sap_parameter *) msg->param; + size_t size = sizeof(struct sap_message); + + if (!conn) + return -EINVAL; + + DBG("state %d pr 0x%02x result 0x%02x", conn->state, + conn->processing_req, result); + + if (conn->processing_req != SAP_TRANSFER_CARD_READER_STATUS_REQ) + return 0; + + memset(buf, 0, sizeof(buf)); + msg->id = SAP_TRANSFER_CARD_READER_STATUS_RESP; + msg->nparam = 0x01; + size += add_result_parameter(result, param); + + /* Add card reader status. */ + if (result == SAP_RESULT_OK) { + msg->nparam++; + param = (struct sap_parameter *) &buf[size]; + param->id = SAP_PARAM_ID_CARD_READER_STATUS; + param->len = htons(SAP_PARAM_ID_CARD_READER_STATUS_LEN); + *param->val = status; + size += PARAMETER_SIZE(SAP_PARAM_ID_CARD_READER_STATUS_LEN); + } + + conn->processing_req = SAP_NO_REQ; + + return send_message(conn, buf, size); +} + +int sap_transport_protocol_rsp(void *sap_device, uint8_t result) +{ + struct sap_server *server = sap_device; + struct sap_connection *conn = server->conn; + char buf[SAP_BUF_SIZE]; + struct sap_message *msg = (struct sap_message *) buf; + size_t size = sizeof(struct sap_message); + + if (!conn) + return -EINVAL; + + DBG("state %d pr 0x%02x result 0x%02x", conn->state, + conn->processing_req, result); + + if (conn->processing_req != SAP_SET_TRANSPORT_PROTOCOL_REQ) + return 0; + + memset(buf, 0, sizeof(buf)); + msg->id = SAP_SET_TRANSPORT_PROTOCOL_RESP; + msg->nparam = 0x01; + size += add_result_parameter(result, msg->param); + + conn->processing_req = SAP_NO_REQ; + + return send_message(conn, buf, size); +} + +int sap_status_ind(void *sap_device, uint8_t status_change) +{ + struct sap_server *server = sap_device; + struct sap_connection *conn = server->conn; + char buf[SAP_BUF_SIZE]; + struct sap_message *msg = (struct sap_message *) buf; + struct sap_parameter *param = (struct sap_parameter *) msg->param; + size_t size = sizeof(struct sap_message); + + if (!conn) + return -EINVAL; + + DBG("state %d pr 0x%02x sc 0x%02x", conn->state, conn->processing_req, + status_change); + + switch (conn->state) { + case SAP_STATE_CONNECT_MODEM_BUSY: + if (status_change != SAP_STATUS_CHANGE_CARD_RESET) + break; + + /* Change state to connected after ongoing call ended */ + sap_set_connected(server); + /* fall through */ + case SAP_STATE_CONNECTED: + case SAP_STATE_GRACEFUL_DISCONNECT: + memset(buf, 0, sizeof(buf)); + msg->id = SAP_STATUS_IND; + msg->nparam = 0x01; + + /* Add status change. */ + param->id = SAP_PARAM_ID_STATUS_CHANGE; + param->len = htons(SAP_PARAM_ID_STATUS_CHANGE_LEN); + *param->val = status_change; + size += PARAMETER_SIZE(SAP_PARAM_ID_STATUS_CHANGE_LEN); + + return send_message(conn, buf, size); + case SAP_STATE_DISCONNECTED: + case SAP_STATE_CONNECT_IN_PROGRESS: + case SAP_STATE_IMMEDIATE_DISCONNECT: + case SAP_STATE_CLIENT_DISCONNECT: + break; + } + + return 0; +} + +int sap_disconnect_ind(void *sap_device, uint8_t disc_type) +{ + return disconnect_req(sap_device, SAP_DISCONNECTION_TYPE_IMMEDIATE); +} + +static int handle_cmd(struct sap_server *server, void *buf, size_t size) +{ + struct sap_connection *conn = server->conn; + struct sap_message *msg = buf; + + if (!conn) + return -EINVAL; + + if (size < sizeof(struct sap_message)) + goto error_rsp; + + if (msg->nparam != 0 && size < (sizeof(struct sap_message) + + sizeof(struct sap_parameter) + 4)) + goto error_rsp; + + if (check_msg(msg) < 0) + goto error_rsp; + + switch (msg->id) { + case SAP_CONNECT_REQ: + connect_req(server, msg->param); + return 0; + case SAP_DISCONNECT_REQ: + disconnect_req(server, SAP_DISCONNECTION_TYPE_CLIENT); + return 0; + case SAP_TRANSFER_APDU_REQ: + transfer_apdu_req(server, msg->param); + return 0; + case SAP_TRANSFER_ATR_REQ: + transfer_atr_req(server); + return 0; + case SAP_POWER_SIM_OFF_REQ: + power_sim_off_req(server); + return 0; + case SAP_POWER_SIM_ON_REQ: + power_sim_on_req(server); + return 0; + case SAP_RESET_SIM_REQ: + reset_sim_req(server); + return 0; + case SAP_TRANSFER_CARD_READER_STATUS_REQ: + transfer_card_reader_status_req(server); + return 0; + case SAP_SET_TRANSPORT_PROTOCOL_REQ: + set_transport_protocol_req(server, msg->param); + return 0; + default: + DBG("Unknown SAP message id 0x%02x.", msg->id); + break; + } + +error_rsp: + DBG("Invalid SAP message format."); + sap_error_rsp(conn); + return -EBADMSG; +} + +static void sap_server_remove_conn(struct sap_server *server) +{ + struct sap_connection *conn = server->conn; + + DBG("conn %p", conn); + + if (!conn) + return; + + if (conn->io) { + g_io_channel_shutdown(conn->io, TRUE, NULL); + g_io_channel_unref(conn->io); + } + + g_free(conn); + server->conn = NULL; +} + +static gboolean sap_io_cb(GIOChannel *io, GIOCondition cond, gpointer data) +{ + char buf[SAP_BUF_SIZE]; + size_t bytes_read = 0; + GError *gerr = NULL; + GIOStatus gstatus; + + SAP_VDBG("conn %p io %p", conn, io); + + if (cond & G_IO_NVAL) { + DBG("ERR (G_IO_NVAL) on rfcomm socket."); + return FALSE; + } + + if (cond & G_IO_ERR) { + DBG("ERR (G_IO_ERR) on rfcomm socket."); + return FALSE; + } + + if (cond & G_IO_HUP) { + DBG("HUP on rfcomm socket."); + return FALSE; + } + + gstatus = g_io_channel_read_chars(io, buf, sizeof(buf) - 1, + &bytes_read, &gerr); + if (gstatus != G_IO_STATUS_NORMAL) { + if (gerr) + g_error_free(gerr); + + return TRUE; + } + + if (handle_cmd(data, buf, bytes_read) < 0) + error("SAP protocol processing failure."); + + return TRUE; +} + +static void sap_io_destroy(void *data) +{ + struct sap_server *server = data; + struct sap_connection *conn = server->conn; + + DBG("conn %p", conn); + + if (!conn || !conn->io) + return; + + stop_guard_timer(server); + + if (conn->state != SAP_STATE_CONNECT_IN_PROGRESS && + conn->state != SAP_STATE_CONNECT_MODEM_BUSY) + g_dbus_emit_property_changed(btd_get_dbus_connection(), + adapter_get_path(server->adapter), + SAP_SERVER_INTERFACE, + "Connected"); + + if (conn->state == SAP_STATE_CONNECT_IN_PROGRESS || + conn->state == SAP_STATE_CONNECT_MODEM_BUSY || + conn->state == SAP_STATE_CONNECTED || + conn->state == SAP_STATE_GRACEFUL_DISCONNECT) + sap_disconnect_req(server, 1); + + sap_server_remove_conn(server); +} + +static void sap_connect_cb(GIOChannel *io, GError *gerr, gpointer data) +{ + struct sap_server *server = data; + struct sap_connection *conn = server->conn; + + DBG("conn %p, io %p", conn, io); + + if (!conn) + return; + + /* Timer will shutdown the channel in case of lack of client + activity */ + start_guard_timer(server, SAP_TIMER_NO_ACTIVITY); + + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + sap_io_cb, server, sap_io_destroy); +} + +static void connect_auth_cb(DBusError *derr, void *data) +{ + struct sap_server *server = data; + struct sap_connection *conn = server->conn; + GError *gerr = NULL; + + DBG("conn %p", conn); + + if (!conn) + return; + + if (derr && dbus_error_is_set(derr)) { + error("Access has been denied (%s)", derr->message); + sap_server_remove_conn(server); + return; + } + + if (!bt_io_accept(conn->io, sap_connect_cb, server, NULL, &gerr)) { + error("bt_io_accept: %s", gerr->message); + g_error_free(gerr); + sap_server_remove_conn(server); + return; + } + + DBG("Access has been granted."); +} + +static void connect_confirm_cb(GIOChannel *io, gpointer data) +{ + struct sap_server *server = data; + struct sap_connection *conn = server->conn; + GError *gerr = NULL; + bdaddr_t src, dst; + char dstaddr[18]; + guint ret; + + DBG("conn %p io %p", conn, io); + + if (!io) + return; + + if (conn) { + DBG("Another SAP connection already exists."); + g_io_channel_shutdown(io, TRUE, NULL); + return; + } + + conn = g_try_new0(struct sap_connection, 1); + if (!conn) { + error("Can't allocate memory for incoming SAP connection."); + g_io_channel_shutdown(io, TRUE, NULL); + return; + } + + g_io_channel_set_encoding(io, NULL, NULL); + g_io_channel_set_buffered(io, FALSE); + + server->conn = conn; + conn->io = g_io_channel_ref(io); + conn->state = SAP_STATE_DISCONNECTED; + + bt_io_get(io, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_INVALID); + if (gerr) { + error("%s", gerr->message); + g_error_free(gerr); + sap_server_remove_conn(server); + return; + } + + ba2str(&dst, dstaddr); + + ret = btd_request_authorization(&src, &dst, SAP_UUID, connect_auth_cb, + server); + if (ret == 0) { + error("Authorization failure"); + sap_server_remove_conn(server); + return; + } + + DBG("Authorizing incoming SAP connection from %s", dstaddr); +} + +static DBusMessage *disconnect(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct sap_server *server = data; + + if (!server) + return btd_error_failed(msg, "Server internal error."); + + DBG("conn %p", server->conn); + + if (!server->conn) + return btd_error_failed(msg, "Client already disconnected"); + + if (disconnect_req(server, SAP_DISCONNECTION_TYPE_GRACEFUL) < 0) + return btd_error_failed(msg, "There is no active connection"); + + return dbus_message_new_method_return(msg); +} + +static gboolean server_property_get_connected( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct sap_server *server = data; + struct sap_connection *conn = server->conn; + dbus_bool_t connected; + + if (!conn) { + connected = FALSE; + goto append; + } + + connected = (conn->state == SAP_STATE_CONNECTED || + conn->state == SAP_STATE_GRACEFUL_DISCONNECT); + +append: + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &connected); + + return TRUE; +} + +static const GDBusMethodTable server_methods[] = { + { GDBUS_METHOD("Disconnect", NULL, NULL, disconnect) }, + { } +}; + +static const GDBusPropertyTable server_properties[] = { + { "Connected", "b", server_property_get_connected }, + { } +}; + +static void server_remove(struct sap_server *server) +{ + if (!server) + return; + + sap_server_remove_conn(server); + + adapter_service_remove(server->adapter, server->record_id); + + if (server->listen_io) { + g_io_channel_shutdown(server->listen_io, TRUE, NULL); + g_io_channel_unref(server->listen_io); + server->listen_io = NULL; + } + + btd_adapter_unref(server->adapter); + g_free(server); +} + +static void destroy_sap_interface(void *data) +{ + struct sap_server *server = data; + + DBG("Unregistered interface %s on path %s", SAP_SERVER_INTERFACE, + adapter_get_path(server->adapter)); + + server_remove(server); +} + +int sap_server_register(struct btd_adapter *adapter) +{ + sdp_record_t *record = NULL; + GError *gerr = NULL; + GIOChannel *io; + struct sap_server *server; + + if (sap_init() < 0) { + error("Sap driver initialization failed."); + return -1; + } + + record = create_sap_record(SAP_SERVER_CHANNEL); + if (!record) { + error("Creating SAP SDP record failed."); + goto sdp_err; + } + + if (adapter_service_add(adapter, record) < 0) { + error("Adding SAP SDP record to the SDP server failed."); + sdp_record_free(record); + goto sdp_err; + } + + server = g_new0(struct sap_server, 1); + server->adapter = btd_adapter_ref(adapter); + server->record_id = record->handle; + + io = bt_io_listen(NULL, connect_confirm_cb, server, + NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(adapter), + BT_IO_OPT_CHANNEL, SAP_SERVER_CHANNEL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_HIGH, + BT_IO_OPT_MASTER, TRUE, + BT_IO_OPT_INVALID); + if (!io) { + error("Can't listen at channel %d.", SAP_SERVER_CHANNEL); + g_error_free(gerr); + goto server_err; + } + server->listen_io = io; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + adapter_get_path(server->adapter), + SAP_SERVER_INTERFACE, + server_methods, NULL, + server_properties, server, + destroy_sap_interface)) { + error("D-Bus failed to register %s interface", + SAP_SERVER_INTERFACE); + goto server_err; + } + + DBG("server %p, listen socket 0x%02x", server, + g_io_channel_unix_get_fd(io)); + + return 0; + +server_err: + server_remove(server); +sdp_err: + sap_exit(); + + return -1; +} + +void sap_server_unregister(const char *path) +{ + g_dbus_unregister_interface(btd_get_dbus_connection(), + path, SAP_SERVER_INTERFACE); + + sap_exit(); +} diff --git a/profiles/sap/server.h b/profiles/sap/server.h new file mode 100644 index 0000000..4bf9296 --- /dev/null +++ b/profiles/sap/server.h @@ -0,0 +1,22 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 ST-Ericsson SA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +int sap_server_register(struct btd_adapter *adapter); +void sap_server_unregister(const char *path); diff --git a/profiles/scanparam/scan.c b/profiles/scanparam/scan.c new file mode 100644 index 0000000..9e8f577 --- /dev/null +++ b/profiles/scanparam/scan.c @@ -0,0 +1,287 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Nordic Semiconductor Inc. + * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/log.h" +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "attrib/att.h" + +#define SCAN_INTERVAL_WIN_UUID 0x2A4F +#define SCAN_REFRESH_UUID 0x2A31 + +#define SCAN_INTERVAL 0x0060 +#define SCAN_WINDOW 0x0030 +#define SERVER_REQUIRES_REFRESH 0x00 + +struct scan { + struct btd_device *device; + struct gatt_db *db; + struct bt_gatt_client *client; + struct gatt_db_attribute *attr; + uint16_t iwhandle; + guint refresh_cb_id; +}; + +static void scan_free(struct scan *scan) +{ + bt_gatt_client_unregister_notify(scan->client, scan->refresh_cb_id); + gatt_db_unref(scan->db); + bt_gatt_client_unref(scan->client); + btd_device_unref(scan->device); + g_free(scan); +} + +static void write_scan_params(struct scan *scan) +{ + uint8_t value[4]; + + put_le16(SCAN_INTERVAL, &value[0]); + put_le16(SCAN_WINDOW, &value[2]); + + bt_gatt_client_write_without_response(scan->client, scan->iwhandle, + false, value, sizeof(value)); +} + +static void refresh_value_cb(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct scan *scan = user_data; + + DBG("Server requires refresh: %d", value[3]); + + if (value[3] == SERVER_REQUIRES_REFRESH) + write_scan_params(scan); +} + +static void refresh_ccc_written_cb(uint16_t att_ecode, void *user_data) +{ + if (att_ecode != 0) { + error("Scan Refresh: notifications not enabled %s", + att_ecode2str(att_ecode)); + return; + } + + DBG("Scan Refresh: notification enabled"); +} + +static void handle_refresh(struct scan *scan, uint16_t value_handle) +{ + DBG("Scan Refresh handle: 0x%04x", value_handle); + + scan->refresh_cb_id = bt_gatt_client_register_notify(scan->client, + value_handle, refresh_ccc_written_cb, + refresh_value_cb, scan, NULL); +} + +static void handle_iwin(struct scan *scan, uint16_t value_handle) +{ + scan->iwhandle = value_handle; + + DBG("Scan Interval Window handle: 0x%04x", scan->iwhandle); + + write_scan_params(scan); +} + +static void handle_characteristic(struct gatt_db_attribute *attr, + void *user_data) +{ + struct scan *scan = user_data; + uint16_t value_handle; + bt_uuid_t uuid, scan_interval_wind_uuid, scan_refresh_uuid; + + if (!gatt_db_attribute_get_char_data(attr, NULL, &value_handle, NULL, + NULL, &uuid)) { + error("Failed to obtain characteristic data"); + return; + } + + bt_uuid16_create(&scan_interval_wind_uuid, SCAN_INTERVAL_WIN_UUID); + bt_uuid16_create(&scan_refresh_uuid, SCAN_REFRESH_UUID); + + if (bt_uuid_cmp(&scan_interval_wind_uuid, &uuid) == 0) + handle_iwin(scan, value_handle); + else if (bt_uuid_cmp(&scan_refresh_uuid, &uuid) == 0) + handle_refresh(scan, value_handle); + else { + char uuid_str[MAX_LEN_UUID_STR]; + + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + DBG("Unsupported characteristic: %s", uuid_str); + } +} + +static void foreach_scan_param_service(struct gatt_db_attribute *attr, + void *user_data) +{ + struct scan *scan = user_data; + + if (scan->attr) { + error("More than one scan params service exists for this " + "device"); + return; + } + + scan->attr = attr; + gatt_db_service_foreach_char(scan->attr, handle_characteristic, scan); +} + +static void scan_reset(struct scan *scan) +{ + scan->attr = NULL; + gatt_db_unref(scan->db); + scan->db = NULL; + bt_gatt_client_unref(scan->client); + scan->client = NULL; +} + +static int scan_param_accept(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gatt_db *db = btd_device_get_gatt_db(device); + struct bt_gatt_client *client = btd_device_get_gatt_client(device); + bt_uuid_t scan_parameters_uuid; + struct scan *scan = btd_service_get_user_data(service); + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("Scan Parameters Client Driver profile accept (%s)", addr); + + if (!scan) { + error("Scan Parameters service not handled by profile"); + return -1; + } + + scan->db = gatt_db_ref(db); + scan->client = bt_gatt_client_clone(client); + + bt_string_to_uuid(&scan_parameters_uuid, SCAN_PARAMETERS_UUID); + gatt_db_foreach_service(db, &scan_parameters_uuid, + foreach_scan_param_service, scan); + + if (!scan->attr) { + error("Scan Parameters attribute not found"); + scan_reset(scan); + return -1; + } + + btd_service_connecting_complete(service, 0); + + return 0; +} + +static int scan_param_disconnect(struct btd_service *service) +{ + struct scan *scan = btd_service_get_user_data(service); + + scan_reset(scan); + + btd_service_disconnecting_complete(service, 0); + + return 0; +} + +static void scan_param_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct scan *scan; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("GAP profile remove (%s)", addr); + + scan = btd_service_get_user_data(service); + if (!scan) { + error("GAP service not handled by profile"); + return; + } + + scan_free(scan); +} + +static int scan_param_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct scan *scan; + char addr[18]; + + ba2str(device_get_address(device), addr); + DBG("Scan Parameters Client Driver profile probe (%s)", addr); + + /* Ignore, if we were probed for this device already */ + scan = btd_service_get_user_data(service); + if (scan) { + error("Profile probed twice for the same service!"); + return -1; + } + + scan = g_new0(struct scan, 1); + if (!scan) + return -1; + + scan->device = btd_device_ref(device); + btd_service_set_user_data(service, scan); + return 0; +} + +static struct btd_profile scan_profile = { + .name = "Scan Parameters Client Driver", + .remote_uuid = SCAN_PARAMETERS_UUID, + .device_probe = scan_param_probe, + .device_remove = scan_param_remove, + .accept = scan_param_accept, + .disconnect = scan_param_disconnect, +}; + +static int scan_param_init(void) +{ + return btd_profile_register(&scan_profile); +} + +static void scan_param_exit(void) +{ + btd_profile_unregister(&scan_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(scanparam, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + scan_param_init, scan_param_exit) diff --git a/profiles/scanparam/scpp.c b/profiles/scanparam/scpp.c new file mode 100644 index 0000000..df65d2c --- /dev/null +++ b/profiles/scanparam/scpp.c @@ -0,0 +1,355 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Nordic Semiconductor Inc. + * Copyright (C) 2012 Instituto Nokia de Tecnologia - INdT + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "src/log.h" + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/shared/util.h" +#include "src/shared/queue.h" + +#include "attrib/att.h" +#include "attrib/gattrib.h" +#include "attrib/gatt.h" + +#include "profiles/scanparam/scpp.h" + +#define SCAN_INTERVAL_WIN_UUID 0x2A4F +#define SCAN_REFRESH_UUID 0x2A31 + +#define SCAN_INTERVAL 0x0060 +#define SCAN_WINDOW 0x0030 +#define SERVER_REQUIRES_REFRESH 0x00 + +struct bt_scpp { + int ref_count; + GAttrib *attrib; + struct gatt_primary *primary; + uint16_t interval; + uint16_t window; + uint16_t iwhandle; + uint16_t refresh_handle; + guint refresh_cb_id; + struct queue *gatt_op; +}; + +static void discover_char(struct bt_scpp *scpp, GAttrib *attrib, + uint16_t start, uint16_t end, + bt_uuid_t *uuid, gatt_cb_t func, + gpointer user_data) +{ + unsigned int id; + + id = gatt_discover_char(attrib, start, end, uuid, func, user_data); + + queue_push_head(scpp->gatt_op, UINT_TO_PTR(id)); +} + +static void discover_desc(struct bt_scpp *scpp, GAttrib *attrib, + uint16_t start, uint16_t end, bt_uuid_t *uuid, + gatt_cb_t func, gpointer user_data) +{ + unsigned int id; + + id = gatt_discover_desc(attrib, start, end, uuid, func, user_data); + + queue_push_head(scpp->gatt_op, UINT_TO_PTR(id)); +} + +static void write_char(struct bt_scpp *scan, GAttrib *attrib, uint16_t handle, + const uint8_t *value, size_t vlen, + GAttribResultFunc func, + gpointer user_data) +{ + unsigned int id; + + id = gatt_write_char(attrib, handle, value, vlen, func, user_data); + + queue_push_head(scan->gatt_op, UINT_TO_PTR(id)); +} + +static void scpp_free(struct bt_scpp *scan) +{ + bt_scpp_detach(scan); + + g_free(scan->primary); + queue_destroy(scan->gatt_op, NULL); /* cleared in bt_scpp_detach */ + g_free(scan); +} + +struct bt_scpp *bt_scpp_new(void *primary) +{ + struct bt_scpp *scan; + + scan = g_try_new0(struct bt_scpp, 1); + if (!scan) + return NULL; + + scan->interval = SCAN_INTERVAL; + scan->window = SCAN_WINDOW; + + scan->gatt_op = queue_new(); + + if (primary) + scan->primary = g_memdup(primary, sizeof(*scan->primary)); + + return bt_scpp_ref(scan); +} + +struct bt_scpp *bt_scpp_ref(struct bt_scpp *scan) +{ + if (!scan) + return NULL; + + __sync_fetch_and_add(&scan->ref_count, 1); + + return scan; +} + +void bt_scpp_unref(struct bt_scpp *scan) +{ + if (!scan) + return; + + if (__sync_sub_and_fetch(&scan->ref_count, 1)) + return; + + scpp_free(scan); +} + +static void write_scan_params(GAttrib *attrib, uint16_t handle, + uint16_t interval, uint16_t window) +{ + uint8_t value[4]; + + put_le16(interval, &value[0]); + put_le16(window, &value[2]); + + gatt_write_cmd(attrib, handle, value, sizeof(value), NULL, NULL); +} + +static void refresh_value_cb(const uint8_t *pdu, uint16_t len, + gpointer user_data) +{ + struct bt_scpp *scan = user_data; + + DBG("Server requires refresh: %d", pdu[3]); + + if (pdu[3] == SERVER_REQUIRES_REFRESH) + write_scan_params(scan->attrib, scan->iwhandle, scan->interval, + scan->window); +} + +static void ccc_written_cb(guint8 status, const guint8 *pdu, + guint16 plen, gpointer user_data) +{ + struct bt_scpp *scan = user_data; + + if (status != 0) { + error("Write Scan Refresh CCC failed: %s", + att_ecode2str(status)); + return; + } + + DBG("Scan Refresh: notification enabled"); + + scan->refresh_cb_id = g_attrib_register(scan->attrib, + ATT_OP_HANDLE_NOTIFY, scan->refresh_handle, + refresh_value_cb, scan, NULL); +} + +static void write_ccc(struct bt_scpp *scan, GAttrib *attrib, uint16_t handle, + void *user_data) +{ + uint8_t value[2]; + + put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); + + write_char(scan, attrib, handle, value, sizeof(value), ccc_written_cb, + user_data); +} + +static void discover_descriptor_cb(uint8_t status, GSList *descs, + void *user_data) +{ + struct bt_scpp *scan = user_data; + struct gatt_desc *desc; + + if (status != 0) { + error("Discover descriptors failed: %s", att_ecode2str(status)); + return; + } + + /* There will be only one descriptor on list and it will be CCC */ + desc = descs->data; + + write_ccc(scan, scan->attrib, desc->handle, scan); +} + +static void refresh_discovered_cb(uint8_t status, GSList *chars, + void *user_data) +{ + struct bt_scpp *scan = user_data; + struct gatt_char *chr; + uint16_t start, end; + bt_uuid_t uuid; + + if (status) { + error("Scan Refresh %s", att_ecode2str(status)); + return; + } + + if (!chars) { + DBG("Scan Refresh not supported"); + return; + } + + chr = chars->data; + + DBG("Scan Refresh handle: 0x%04x", chr->value_handle); + + start = chr->value_handle + 1; + end = scan->primary->range.end; + + if (start > end) + return; + + scan->refresh_handle = chr->value_handle; + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + + discover_desc(scan, scan->attrib, start, end, &uuid, + discover_descriptor_cb, user_data); +} + +static void iwin_discovered_cb(uint8_t status, GSList *chars, void *user_data) +{ + struct bt_scpp *scan = user_data; + struct gatt_char *chr; + + if (status) { + error("Discover Scan Interval Window: %s", + att_ecode2str(status)); + return; + } + + chr = chars->data; + scan->iwhandle = chr->value_handle; + + DBG("Scan Interval Window handle: 0x%04x", scan->iwhandle); + + write_scan_params(scan->attrib, scan->iwhandle, scan->interval, + scan->window); +} + +bool bt_scpp_attach(struct bt_scpp *scan, void *attrib) +{ + bt_uuid_t iwin_uuid, refresh_uuid; + + if (!scan || scan->attrib || !scan->primary) + return false; + + scan->attrib = g_attrib_ref(attrib); + + if (scan->iwhandle) + write_scan_params(scan->attrib, scan->iwhandle, scan->interval, + scan->window); + else { + bt_uuid16_create(&iwin_uuid, SCAN_INTERVAL_WIN_UUID); + discover_char(scan, scan->attrib, scan->primary->range.start, + scan->primary->range.end, &iwin_uuid, + iwin_discovered_cb, scan); + } + + if (scan->refresh_handle) + scan->refresh_cb_id = g_attrib_register(scan->attrib, + ATT_OP_HANDLE_NOTIFY, scan->refresh_handle, + refresh_value_cb, scan, NULL); + else { + bt_uuid16_create(&refresh_uuid, SCAN_REFRESH_UUID); + discover_char(scan, scan->attrib, scan->primary->range.start, + scan->primary->range.end, &refresh_uuid, + refresh_discovered_cb, scan); + } + + return true; +} + +static void cancel_gatt_req(void *data, void *user_data) +{ + unsigned int id = PTR_TO_UINT(data); + struct bt_scpp *scan = user_data; + + g_attrib_cancel(scan->attrib, id); +} + +void bt_scpp_detach(struct bt_scpp *scan) +{ + if (!scan || !scan->attrib) + return; + + if (scan->refresh_cb_id > 0) { + g_attrib_unregister(scan->attrib, scan->refresh_cb_id); + scan->refresh_cb_id = 0; + } + + queue_foreach(scan->gatt_op, cancel_gatt_req, scan); + g_attrib_unref(scan->attrib); + scan->attrib = NULL; +} + +bool bt_scpp_set_interval(struct bt_scpp *scan, uint16_t value) +{ + if (!scan) + return false; + + /* TODO: Check valid range */ + + scan->interval = value; + + return true; +} + +bool bt_scpp_set_window(struct bt_scpp *scan, uint16_t value) +{ + if (!scan) + return false; + + /* TODO: Check valid range */ + + scan->window = value; + + return true; +} diff --git a/profiles/scanparam/scpp.h b/profiles/scanparam/scpp.h new file mode 100644 index 0000000..048fb9f --- /dev/null +++ b/profiles/scanparam/scpp.h @@ -0,0 +1,35 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Intel Corporation. All rights reserved. + * + * + * This library is free software; you can rescpptribute 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 scpptributed in the hope that 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 bt_scpp; + +struct bt_scpp *bt_scpp_new(void *primary); + +struct bt_scpp *bt_scpp_ref(struct bt_scpp *scan); +void bt_scpp_unref(struct bt_scpp *scan); + +bool bt_scpp_attach(struct bt_scpp *scan, void *gatt); +void bt_scpp_detach(struct bt_scpp *scan); + +bool bt_scpp_set_interval(struct bt_scpp *scan, uint16_t value); +bool bt_scpp_set_window(struct bt_scpp *scan, uint16_t value); diff --git a/src/adapter.c b/src/adapter.c new file mode 100644 index 0000000..cef2561 --- /dev/null +++ b/src/adapter.c @@ -0,0 +1,9139 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "bluetooth/bluetooth.h" +#include "bluetooth/hci.h" +#include "bluetooth/hci_lib.h" +#include "bluetooth/sdp.h" +#include "bluetooth/sdp_lib.h" +#include "lib/uuid.h" +#include "lib/mgmt.h" + +#include "gdbus/gdbus.h" + +#include "log.h" +#include "textfile.h" + +#include "src/shared/mgmt.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" + +#include "btio/btio.h" +#include "hcid.h" +#include "sdpd.h" +#include "adapter.h" +#include "device.h" +#include "profile.h" +#include "dbus-common.h" +#include "error.h" +#include "uuid-helper.h" +#include "agent.h" +#include "storage.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "attrib-server.h" +#include "gatt-database.h" +#include "advertising.h" +#include "eir.h" + +#define ADAPTER_INTERFACE "org.bluez.Adapter1" + +#define MODE_OFF 0x00 +#define MODE_CONNECTABLE 0x01 +#define MODE_DISCOVERABLE 0x02 +#define MODE_UNKNOWN 0xff + +#define CONN_SCAN_TIMEOUT (3) +#define IDLE_DISCOV_TIMEOUT (5) +#define TEMP_DEV_TIMEOUT (3 * 60) +#define BONDING_TIMEOUT (2 * 60) + +#define SCAN_TYPE_BREDR (1 << BDADDR_BREDR) +#define SCAN_TYPE_LE ((1 << BDADDR_LE_PUBLIC) | (1 << BDADDR_LE_RANDOM)) +#define SCAN_TYPE_DUAL (SCAN_TYPE_BREDR | SCAN_TYPE_LE) + +#define HCI_RSSI_INVALID 127 +#define DISTANCE_VAL_INVALID 0x7FFF +#define PATHLOSS_MAX 137 + +static DBusConnection *dbus_conn = NULL; + +static bool kernel_conn_control = false; + +static GList *adapter_list = NULL; +static unsigned int adapter_remaining = 0; +static bool powering_down = false; + +static GSList *adapters = NULL; + +static struct mgmt *mgmt_master = NULL; + +static uint8_t mgmt_version = 0; +static uint8_t mgmt_revision = 0; + +static GSList *adapter_drivers = NULL; + +static GSList *disconnect_list = NULL; +static GSList *conn_fail_list = NULL; + +struct link_key_info { + bdaddr_t bdaddr; + unsigned char key[16]; + uint8_t type; + uint8_t pin_len; +}; + +struct smp_ltk_info { + bdaddr_t bdaddr; + uint8_t bdaddr_type; + uint8_t authenticated; + bool master; + uint8_t enc_size; + uint16_t ediv; + uint64_t rand; + uint8_t val[16]; +}; + +struct irk_info { + bdaddr_t bdaddr; + uint8_t bdaddr_type; + uint8_t val[16]; +}; + +struct conn_param { + bdaddr_t bdaddr; + uint8_t bdaddr_type; + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t timeout; +}; + +struct discovery_filter { + uint8_t type; + uint16_t pathloss; + int16_t rssi; + GSList *uuids; + bool duplicate; + bool discoverable; +}; + +struct watch_client { + struct btd_adapter *adapter; + DBusMessage *msg; + char *owner; + guint watch; + struct discovery_filter *discovery_filter; +}; + +struct service_auth { + guint id; + unsigned int svc_id; + service_auth_cb cb; + void *user_data; + const char *uuid; + struct btd_device *device; + struct btd_adapter *adapter; + struct agent *agent; /* NULL for queued auths */ +}; + +struct btd_adapter_pin_cb_iter { + GSList *it; /* current callback function */ + unsigned int attempt; /* numer of times it() was called */ + /* When the iterator reaches the end, it is NULL and attempt is 0 */ +}; + +struct btd_adapter { + int ref_count; + + uint16_t dev_id; + struct mgmt *mgmt; + + bdaddr_t bdaddr; /* controller Bluetooth address */ + uint8_t bdaddr_type; /* address type */ + uint32_t dev_class; /* controller class of device */ + char *name; /* controller device name */ + char *short_name; /* controller short name */ + uint32_t supported_settings; /* controller supported settings */ + uint32_t pending_settings; /* pending controller settings */ + uint32_t current_settings; /* current controller settings */ + + char *path; /* adapter object path */ + uint16_t manufacturer; /* adapter manufacturer */ + uint8_t major_class; /* configured major class */ + uint8_t minor_class; /* configured minor class */ + char *system_name; /* configured system name */ + char *modalias; /* device id (modalias) */ + bool stored_discoverable; /* stored discoverable mode */ + uint32_t discoverable_timeout; /* discoverable time(sec) */ + uint32_t pairable_timeout; /* pairable time(sec) */ + + char *current_alias; /* current adapter name alias */ + char *stored_alias; /* stored adapter name alias */ + + bool discovering; /* discovering property state */ + bool filtered_discovery; /* we are doing filtered discovery */ + bool no_scan_restart_delay; /* when this flag is set, restart scan + * without delay */ + uint8_t discovery_type; /* current active discovery type */ + uint8_t discovery_enable; /* discovery enabled/disabled */ + bool discovery_suspended; /* discovery has been suspended */ + bool discovery_discoverable; /* discoverable while discovering */ + GSList *discovery_list; /* list of discovery clients */ + GSList *set_filter_list; /* list of clients that specified + * filter, but don't scan yet + */ + /* current discovery filter, if any */ + struct mgmt_cp_start_service_discovery *current_discovery_filter; + + GSList *discovery_found; /* list of found devices */ + guint discovery_idle_timeout; /* timeout between discovery runs */ + guint passive_scan_timeout; /* timeout between passive scans */ + guint temp_devices_timeout; /* timeout for temporary devices */ + + guint pairable_timeout_id; /* pairable timeout id */ + guint auth_idle_id; /* Pending authorization dequeue */ + GQueue *auths; /* Ongoing and pending auths */ + bool pincode_requested; /* PIN requested during last bonding */ + GSList *connections; /* Connected devices */ + GSList *devices; /* Devices structure pointers */ + GSList *connect_list; /* Devices to connect when found */ + struct btd_device *connect_le; /* LE device waiting to be connected */ + sdp_list_t *services; /* Services associated to adapter */ + + struct btd_gatt_database *database; + struct btd_adv_manager *adv_manager; + + gboolean initialized; + + GSList *pin_callbacks; + GSList *msd_callbacks; + + GSList *drivers; + GSList *profiles; + + struct oob_handler *oob_handler; + + unsigned int load_ltks_id; + guint load_ltks_timeout; + + unsigned int confirm_name_id; + guint confirm_name_timeout; + + unsigned int pair_device_id; + guint pair_device_timeout; + + unsigned int db_id; /* Service event handler for GATT db */ + + bool is_default; /* true if adapter is default one */ +}; + +typedef enum { + ADAPTER_AUTHORIZE_DISCONNECTED = 0, + ADAPTER_AUTHORIZE_CHECK_CONNECTED +} adapter_authorize_type; + +static struct btd_adapter *btd_adapter_lookup(uint16_t index) +{ + GList *list; + + for (list = g_list_first(adapter_list); list; + list = g_list_next(list)) { + struct btd_adapter *adapter = list->data; + + if (adapter->dev_id == index) + return adapter; + } + + return NULL; +} + +struct btd_adapter *btd_adapter_get_default(void) +{ + GList *list; + + for (list = g_list_first(adapter_list); list; + list = g_list_next(list)) { + struct btd_adapter *adapter = list->data; + + if (adapter->is_default) + return adapter; + } + + return NULL; +} + +bool btd_adapter_is_default(struct btd_adapter *adapter) +{ + if (!adapter) + return false; + + return adapter->is_default; +} + +uint16_t btd_adapter_get_index(struct btd_adapter *adapter) +{ + if (!adapter) + return MGMT_INDEX_NONE; + + return adapter->dev_id; +} + +static gboolean process_auth_queue(gpointer user_data); + +static void dev_class_changed_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const struct mgmt_cod *rp = param; + uint32_t dev_class; + + if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, + "Wrong size of class of device changed parameters"); + return; + } + + dev_class = rp->val[0] | (rp->val[1] << 8) | (rp->val[2] << 16); + + if (dev_class == adapter->dev_class) + return; + + DBG("Class: 0x%06x", dev_class); + + adapter->dev_class = dev_class; + + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Class"); +} + +static void set_dev_class_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to set device class: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + /* + * The parameters are identical and also the task that is + * required in both cases. So it is safe to just call the + * event handling functions here. + */ + dev_class_changed_callback(adapter->dev_id, length, param, adapter); +} + +static void set_dev_class(struct btd_adapter *adapter) +{ + struct mgmt_cp_set_dev_class cp; + + /* + * If the controller does not support BR/EDR operation, + * there is no point in trying to set a major and minor + * class value. + * + * This is an optimization for Low Energy only controllers. + */ + if (!(adapter->supported_settings & MGMT_SETTING_BREDR)) + return; + + memset(&cp, 0, sizeof(cp)); + + /* + * Silly workaround for a really stupid kernel bug :( + * + * All current kernel versions assign the major and minor numbers + * straight to dev_class[0] and dev_class[1] without considering + * the proper bit shifting. + * + * To make this work, shift the value in userspace for now until + * we get a fixed kernel version. + */ + cp.major = adapter->major_class & 0x1f; + cp.minor = adapter->minor_class << 2; + + DBG("sending set device class command for index %u", adapter->dev_id); + + if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DEV_CLASS, + adapter->dev_id, sizeof(cp), &cp, + set_dev_class_complete, adapter, NULL) > 0) + return; + + btd_error(adapter->dev_id, + "Failed to set class of device for index %u", adapter->dev_id); +} + +void btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major, + uint8_t minor) +{ + if (adapter->major_class == major && adapter->minor_class == minor) + return; + + DBG("class: major %u minor %u", major, minor); + + adapter->major_class = major; + adapter->minor_class = minor; + + set_dev_class(adapter); +} + +static uint8_t get_mode(const char *mode) +{ + if (strcasecmp("off", mode) == 0) + return MODE_OFF; + else if (strcasecmp("connectable", mode) == 0) + return MODE_CONNECTABLE; + else if (strcasecmp("discoverable", mode) == 0) + return MODE_DISCOVERABLE; + else + return MODE_UNKNOWN; +} + +const char *btd_adapter_get_storage_dir(struct btd_adapter *adapter) +{ + static char dir[25]; + + if (adapter->bdaddr_type == BDADDR_LE_RANDOM) { + strcpy(dir, "static-"); + ba2str(&adapter->bdaddr, dir + 7); + } else { + ba2str(&adapter->bdaddr, dir); + } + + return dir; +} + +uint8_t btd_adapter_get_address_type(struct btd_adapter *adapter) +{ + return adapter->bdaddr_type; +} + +static void store_adapter_info(struct btd_adapter *adapter) +{ + GKeyFile *key_file; + char filename[PATH_MAX]; + char *str; + gsize length = 0; + gboolean discoverable; + + key_file = g_key_file_new(); + + if (adapter->pairable_timeout != main_opts.pairto) + g_key_file_set_integer(key_file, "General", "PairableTimeout", + adapter->pairable_timeout); + + if ((adapter->current_settings & MGMT_SETTING_DISCOVERABLE) && + !adapter->discoverable_timeout) + discoverable = TRUE; + else + discoverable = FALSE; + + g_key_file_set_boolean(key_file, "General", "Discoverable", + discoverable); + + if (adapter->discoverable_timeout != main_opts.discovto) + g_key_file_set_integer(key_file, "General", + "DiscoverableTimeout", + adapter->discoverable_timeout); + + if (adapter->stored_alias) + g_key_file_set_string(key_file, "General", "Alias", + adapter->stored_alias); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/settings", + btd_adapter_get_storage_dir(adapter)); + + create_file(filename, S_IRUSR | S_IWUSR); + + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, str, length, NULL); + g_free(str); + + g_key_file_free(key_file); +} + +static void trigger_pairable_timeout(struct btd_adapter *adapter); +static void adapter_start(struct btd_adapter *adapter); +static void adapter_stop(struct btd_adapter *adapter); +static void trigger_passive_scanning(struct btd_adapter *adapter); +static bool set_mode(struct btd_adapter *adapter, uint16_t opcode, + uint8_t mode); + +static void settings_changed(struct btd_adapter *adapter, uint32_t settings) +{ + uint32_t changed_mask; + + changed_mask = adapter->current_settings ^ settings; + + adapter->current_settings = settings; + adapter->pending_settings &= ~changed_mask; + + DBG("Changed settings: 0x%08x", changed_mask); + DBG("Pending settings: 0x%08x", adapter->pending_settings); + + if (changed_mask & MGMT_SETTING_POWERED) { + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Powered"); + + if (adapter->current_settings & MGMT_SETTING_POWERED) { + adapter_start(adapter); + } else { + adapter_stop(adapter); + + if (powering_down) { + adapter_remaining--; + + if (!adapter_remaining) + btd_exit(); + } + } + } + + if (changed_mask & MGMT_SETTING_LE) { + if ((adapter->current_settings & MGMT_SETTING_POWERED) && + (adapter->current_settings & MGMT_SETTING_LE)) + trigger_passive_scanning(adapter); + } + + if (changed_mask & MGMT_SETTING_DISCOVERABLE) { + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Discoverable"); + store_adapter_info(adapter); + btd_adv_manager_refresh(adapter->adv_manager); + } + + if (changed_mask & MGMT_SETTING_BONDABLE) { + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Pairable"); + + trigger_pairable_timeout(adapter); + } +} + +static void new_settings_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + uint32_t settings; + + if (length < sizeof(settings)) { + btd_error(adapter->dev_id, + "Wrong size of new settings parameters"); + return; + } + + settings = get_le32(param); + + if (settings == adapter->current_settings) + return; + + DBG("Settings: 0x%08x", settings); + + settings_changed(adapter, settings); +} + +static void set_mode_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, "Failed to set 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->dev_id, length, param, adapter); +} + +static bool set_mode(struct btd_adapter *adapter, uint16_t opcode, + uint8_t mode) +{ + struct mgmt_mode cp; + uint32_t setting = 0; + + memset(&cp, 0, sizeof(cp)); + cp.val = mode; + + switch (mode) { + case MGMT_OP_SET_POWERED: + setting = MGMT_SETTING_POWERED; + break; + case MGMT_OP_SET_CONNECTABLE: + setting = MGMT_SETTING_CONNECTABLE; + break; + case MGMT_OP_SET_FAST_CONNECTABLE: + setting = MGMT_SETTING_FAST_CONNECTABLE; + break; + case MGMT_OP_SET_DISCOVERABLE: + setting = MGMT_SETTING_DISCOVERABLE; + break; + case MGMT_OP_SET_BONDABLE: + setting = MGMT_SETTING_DISCOVERABLE; + break; + } + + adapter->pending_settings |= setting; + + DBG("sending set mode command for index %u", adapter->dev_id); + + if (mgmt_send(adapter->mgmt, opcode, + adapter->dev_id, sizeof(cp), &cp, + set_mode_complete, adapter, NULL) > 0) + return true; + + btd_error(adapter->dev_id, "Failed to set mode for index %u", + adapter->dev_id); + + return false; +} + +static bool set_discoverable(struct btd_adapter *adapter, uint8_t mode, + uint16_t timeout) +{ + struct mgmt_cp_set_discoverable cp; + + memset(&cp, 0, sizeof(cp)); + cp.val = mode; + cp.timeout = htobs(timeout); + + DBG("sending set mode command for index %u", adapter->dev_id); + + if (kernel_conn_control) { + if (mode) + set_mode(adapter, MGMT_OP_SET_CONNECTABLE, mode); + else + /* This also disables discoverable so we're done */ + return set_mode(adapter, MGMT_OP_SET_CONNECTABLE, + mode); + } + + if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DISCOVERABLE, + adapter->dev_id, sizeof(cp), &cp, + set_mode_complete, adapter, NULL) > 0) + return true; + + btd_error(adapter->dev_id, "Failed to set mode for index %u", + adapter->dev_id); + + return false; +} + +static gboolean pairable_timeout_handler(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + + adapter->pairable_timeout_id = 0; + + set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x00); + + return FALSE; +} + +static void trigger_pairable_timeout(struct btd_adapter *adapter) +{ + if (adapter->pairable_timeout_id > 0) { + g_source_remove(adapter->pairable_timeout_id); + adapter->pairable_timeout_id = 0; + } + + if (!(adapter->current_settings & MGMT_SETTING_BONDABLE)) + return; + + if (adapter->pairable_timeout > 0) + adapter->pairable_timeout_id = + g_timeout_add_seconds(adapter->pairable_timeout, + pairable_timeout_handler, adapter); +} + +static void local_name_changed_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const struct mgmt_cp_set_local_name *rp = param; + + if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, + "Wrong size of local name changed parameters"); + return; + } + + if (!g_strcmp0(adapter->short_name, (const char *) rp->short_name) && + !g_strcmp0(adapter->name, (const char *) rp->name)) + return; + + DBG("Name: %s", rp->name); + DBG("Short name: %s", rp->short_name); + + g_free(adapter->name); + adapter->name = g_strdup((const char *) rp->name); + + g_free(adapter->short_name); + adapter->short_name = g_strdup((const char *) rp->short_name); + + /* + * Changing the name (even manually via HCI) will update the + * current alias property. + * + * In case the name is empty, use the short name. + * + * There is a difference between the stored alias (which is + * configured by the user) and the current alias. The current + * alias is temporary for the lifetime of the daemon. + */ + if (adapter->name && adapter->name[0] != '\0') { + g_free(adapter->current_alias); + adapter->current_alias = g_strdup(adapter->name); + } else { + g_free(adapter->current_alias); + adapter->current_alias = g_strdup(adapter->short_name); + } + + DBG("Current alias: %s", adapter->current_alias); + + if (!adapter->current_alias) + return; + + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Alias"); + + attrib_gap_set(adapter, GATT_CHARAC_DEVICE_NAME, + (const uint8_t *) adapter->current_alias, + strlen(adapter->current_alias)); +} + +static void set_local_name_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to set local name: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + /* + * The parameters are identical and also the task that is + * required in both cases. So it is safe to just call the + * event handling functions here. + */ + local_name_changed_callback(adapter->dev_id, length, param, adapter); +} + +static int set_name(struct btd_adapter *adapter, const char *name) +{ + struct mgmt_cp_set_local_name cp; + char maxname[MAX_NAME_LENGTH]; + + memset(maxname, 0, sizeof(maxname)); + strncpy(maxname, name, MAX_NAME_LENGTH - 1); + + if (!g_utf8_validate(maxname, -1, NULL)) { + btd_error(adapter->dev_id, + "Name change failed: supplied name isn't valid UTF-8"); + return -EINVAL; + } + + memset(&cp, 0, sizeof(cp)); + strncpy((char *) cp.name, maxname, sizeof(cp.name) - 1); + + DBG("sending set local name command for index %u", adapter->dev_id); + + if (mgmt_send(adapter->mgmt, MGMT_OP_SET_LOCAL_NAME, + adapter->dev_id, sizeof(cp), &cp, + set_local_name_complete, adapter, NULL) > 0) + return 0; + + btd_error(adapter->dev_id, "Failed to set local name for index %u", + adapter->dev_id); + + return -EIO; +} + +int adapter_set_name(struct btd_adapter *adapter, const char *name) +{ + if (g_strcmp0(adapter->system_name, name) == 0) + return 0; + + DBG("name: %s", name); + + g_free(adapter->system_name); + adapter->system_name = g_strdup(name); + + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Name"); + + /* alias is preferred over system name */ + if (adapter->stored_alias) + return 0; + + DBG("alias: %s", name); + + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Alias"); + + return set_name(adapter, name); +} + +struct btd_device *btd_adapter_find_device(struct btd_adapter *adapter, + const bdaddr_t *dst, + uint8_t bdaddr_type) +{ + struct device_addr_type addr; + struct btd_device *device; + GSList *list; + + if (!adapter) + return NULL; + + bacpy(&addr.bdaddr, dst); + addr.bdaddr_type = bdaddr_type; + + list = g_slist_find_custom(adapter->devices, &addr, + device_addr_type_cmp); + if (!list) + return NULL; + + device = list->data; + + /* + * If we're looking up based on public address and the address + * was not previously used over this bearer we may need to + * update LE or BR/EDR support information. + */ + if (bdaddr_type == BDADDR_BREDR) + device_set_bredr_support(device); + else + device_set_le_support(device, bdaddr_type); + + return device; +} + +static void uuid_to_uuid128(uuid_t *uuid128, const uuid_t *uuid) +{ + if (uuid->type == SDP_UUID16) + sdp_uuid16_to_uuid128(uuid128, uuid); + else if (uuid->type == SDP_UUID32) + sdp_uuid32_to_uuid128(uuid128, uuid); + else + memcpy(uuid128, uuid, sizeof(*uuid)); +} + +static bool is_supported_uuid(const uuid_t *uuid) +{ + uuid_t tmp; + + /* mgmt versions from 1.3 onwards support all types of UUIDs */ + if (MGMT_VERSION(mgmt_version, mgmt_revision) >= MGMT_VERSION(1, 3)) + return true; + + uuid_to_uuid128(&tmp, uuid); + + if (!sdp_uuid128_to_uuid(&tmp)) + return false; + + if (tmp.type != SDP_UUID16) + return false; + + return true; +} + +static void add_uuid_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, "Failed to add UUID: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + /* + * The parameters are identical and also the task that is + * required in both cases. So it is safe to just call the + * event handling functions here. + */ + dev_class_changed_callback(adapter->dev_id, length, param, adapter); + + if (adapter->initialized) + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "UUIDs"); +} + +static int add_uuid(struct btd_adapter *adapter, uuid_t *uuid, uint8_t svc_hint) +{ + struct mgmt_cp_add_uuid cp; + uuid_t uuid128; + uint128_t uint128; + + if (!is_supported_uuid(uuid)) { + btd_warn(adapter->dev_id, + "Ignoring unsupported UUID for addition"); + return 0; + } + + uuid_to_uuid128(&uuid128, uuid); + + ntoh128((uint128_t *) uuid128.value.uuid128.data, &uint128); + htob128(&uint128, (uint128_t *) cp.uuid); + cp.svc_hint = svc_hint; + + DBG("sending add uuid command for index %u", adapter->dev_id); + + if (mgmt_send(adapter->mgmt, MGMT_OP_ADD_UUID, + adapter->dev_id, sizeof(cp), &cp, + add_uuid_complete, adapter, NULL) > 0) + return 0; + + btd_error(adapter->dev_id, "Failed to add UUID for index %u", + adapter->dev_id); + + return -EIO; +} + +static void remove_uuid_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, "Failed to remove UUID: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + /* + * The parameters are identical and also the task that is + * required in both cases. So it is safe to just call the + * event handling functions here. + */ + dev_class_changed_callback(adapter->dev_id, length, param, adapter); + + if (adapter->initialized) + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "UUIDs"); +} + +static int remove_uuid(struct btd_adapter *adapter, uuid_t *uuid) +{ + struct mgmt_cp_remove_uuid cp; + uuid_t uuid128; + uint128_t uint128; + + if (!is_supported_uuid(uuid)) { + btd_warn(adapter->dev_id, + "Ignoring unsupported UUID for removal"); + return 0; + } + + uuid_to_uuid128(&uuid128, uuid); + + ntoh128((uint128_t *) uuid128.value.uuid128.data, &uint128); + htob128(&uint128, (uint128_t *) cp.uuid); + + DBG("sending remove uuid command for index %u", adapter->dev_id); + + if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_UUID, + adapter->dev_id, sizeof(cp), &cp, + remove_uuid_complete, adapter, NULL) > 0) + return 0; + + btd_error(adapter->dev_id, "Failed to remove UUID for index %u", + adapter->dev_id); + + return -EIO; +} + +static void clear_uuids_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, "Failed to clear UUIDs: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + /* + * The parameters are identical and also the task that is + * required in both cases. So it is safe to just call the + * event handling functions here. + */ + dev_class_changed_callback(adapter->dev_id, length, param, adapter); +} + +static int clear_uuids(struct btd_adapter *adapter) +{ + struct mgmt_cp_remove_uuid cp; + + memset(&cp, 0, sizeof(cp)); + + DBG("sending clear uuids command for index %u", adapter->dev_id); + + if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_UUID, + adapter->dev_id, sizeof(cp), &cp, + clear_uuids_complete, adapter, NULL) > 0) + return 0; + + btd_error(adapter->dev_id, "Failed to clear UUIDs for index %u", + adapter->dev_id); + + return -EIO; +} + +static uint8_t get_uuid_mask(uuid_t *uuid) +{ + if (uuid->type != SDP_UUID16) + return 0; + + switch (uuid->value.uuid16) { + case DIALUP_NET_SVCLASS_ID: + case CIP_SVCLASS_ID: + return 0x42; /* Telephony & Networking */ + case IRMC_SYNC_SVCLASS_ID: + case OBEX_OBJPUSH_SVCLASS_ID: + case OBEX_FILETRANS_SVCLASS_ID: + case IRMC_SYNC_CMD_SVCLASS_ID: + case PBAP_PSE_SVCLASS_ID: + return 0x10; /* Object Transfer */ + case HEADSET_SVCLASS_ID: + case HANDSFREE_SVCLASS_ID: + return 0x20; /* Audio */ + case CORDLESS_TELEPHONY_SVCLASS_ID: + case INTERCOM_SVCLASS_ID: + case FAX_SVCLASS_ID: + case SAP_SVCLASS_ID: + /* + * Setting the telephony bit for the handsfree audio gateway + * role is not required by the HFP specification, but the + * Nokia 616 carkit is just plain broken! It will refuse + * pairing without this bit set. + */ + case HANDSFREE_AGW_SVCLASS_ID: + return 0x40; /* Telephony */ + case AUDIO_SOURCE_SVCLASS_ID: + case VIDEO_SOURCE_SVCLASS_ID: + return 0x08; /* Capturing */ + case AUDIO_SINK_SVCLASS_ID: + case VIDEO_SINK_SVCLASS_ID: + return 0x04; /* Rendering */ + case PANU_SVCLASS_ID: + case NAP_SVCLASS_ID: + case GN_SVCLASS_ID: + return 0x02; /* Networking */ + default: + return 0; + } +} + +static int uuid_cmp(const void *a, const void *b) +{ + const sdp_record_t *rec = a; + const uuid_t *uuid = b; + + return sdp_uuid_cmp(&rec->svclass, uuid); +} + +static void adapter_service_insert(struct btd_adapter *adapter, sdp_record_t *rec) +{ + sdp_list_t *browse_list = NULL; + uuid_t browse_uuid; + gboolean new_uuid; + + DBG("%s", adapter->path); + + /* skip record without a browse group */ + if (sdp_get_browse_groups(rec, &browse_list) < 0) { + DBG("skipping record without browse group"); + return; + } + + sdp_uuid16_create(&browse_uuid, PUBLIC_BROWSE_GROUP); + + /* skip record without public browse group */ + if (!sdp_list_find(browse_list, &browse_uuid, sdp_uuid_cmp)) + goto done; + + if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL) + new_uuid = TRUE; + else + new_uuid = FALSE; + + adapter->services = sdp_list_insert_sorted(adapter->services, rec, + record_sort); + + if (new_uuid) { + uint8_t svc_hint = get_uuid_mask(&rec->svclass); + add_uuid(adapter, &rec->svclass, svc_hint); + } + +done: + sdp_list_free(browse_list, free); +} + +int adapter_service_add(struct btd_adapter *adapter, sdp_record_t *rec) +{ + int ret; + + DBG("%s", adapter->path); + + ret = add_record_to_server(&adapter->bdaddr, rec); + if (ret < 0) + return ret; + + adapter_service_insert(adapter, rec); + + return 0; +} + +void adapter_service_remove(struct btd_adapter *adapter, uint32_t handle) +{ + sdp_record_t *rec = sdp_record_find(handle); + + DBG("%s", adapter->path); + + if (!rec) + return; + + adapter->services = sdp_list_remove(adapter->services, rec); + + if (sdp_list_find(adapter->services, &rec->svclass, uuid_cmp) == NULL) + remove_uuid(adapter, &rec->svclass); + + remove_record_from_server(rec->handle); +} + +static struct btd_device *adapter_create_device(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + uint8_t bdaddr_type) +{ + struct btd_device *device; + + device = device_create(adapter, bdaddr, bdaddr_type); + if (!device) + return NULL; + + adapter->devices = g_slist_append(adapter->devices, device); + + return device; +} + +static void service_auth_cancel(struct service_auth *auth) +{ + DBusError derr; + + if (auth->svc_id > 0) + device_remove_svc_complete_callback(auth->device, + auth->svc_id); + + dbus_error_init(&derr); + dbus_set_error_const(&derr, ERROR_INTERFACE ".Canceled", NULL); + + auth->cb(&derr, auth->user_data); + + dbus_error_free(&derr); + + if (auth->agent != NULL) { + agent_cancel(auth->agent); + agent_unref(auth->agent); + } + + g_free(auth); +} + +void btd_adapter_remove_device(struct btd_adapter *adapter, + struct btd_device *dev) +{ + GList *l; + + adapter->connect_list = g_slist_remove(adapter->connect_list, dev); + + adapter->devices = g_slist_remove(adapter->devices, dev); + + adapter->discovery_found = g_slist_remove(adapter->discovery_found, + dev); + + adapter->connections = g_slist_remove(adapter->connections, dev); + + if (adapter->connect_le == dev) + adapter->connect_le = NULL; + + l = adapter->auths->head; + while (l != NULL) { + struct service_auth *auth = l->data; + GList *next = g_list_next(l); + + if (auth->device != dev) { + l = next; + continue; + } + + g_queue_delete_link(adapter->auths, l); + l = next; + + service_auth_cancel(auth); + } + + device_remove(dev, TRUE); +} + +struct btd_device *btd_adapter_get_device(struct btd_adapter *adapter, + const bdaddr_t *addr, + uint8_t addr_type) +{ + struct btd_device *device; + + if (!adapter) + return NULL; + + device = btd_adapter_find_device(adapter, addr, addr_type); + if (device) + return device; + + return adapter_create_device(adapter, addr, addr_type); +} + +sdp_list_t *btd_adapter_get_services(struct btd_adapter *adapter) +{ + return adapter->services; +} + +static void passive_scanning_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const struct mgmt_cp_start_discovery *rp = param; + + DBG("status 0x%02x", status); + + if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, + "Wrong size of start scanning return parameters"); + return; + } + + if (status == MGMT_STATUS_SUCCESS) { + adapter->discovery_type = rp->type; + adapter->discovery_enable = 0x01; + } +} + +static gboolean passive_scanning_timeout(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + struct mgmt_cp_start_discovery cp; + + adapter->passive_scan_timeout = 0; + + cp.type = SCAN_TYPE_LE; + + mgmt_send(adapter->mgmt, MGMT_OP_START_DISCOVERY, + adapter->dev_id, sizeof(cp), &cp, + passive_scanning_complete, adapter, NULL); + + return FALSE; +} + +static void trigger_passive_scanning(struct btd_adapter *adapter) +{ + if (!(adapter->current_settings & MGMT_SETTING_LE)) + return; + + DBG(""); + + if (adapter->passive_scan_timeout > 0) { + g_source_remove(adapter->passive_scan_timeout); + adapter->passive_scan_timeout = 0; + } + + /* + * When the kernel background scanning is available, there is + * no need to start any discovery. The kernel will keep scanning + * as long as devices are in its auto-connection list. + */ + if (kernel_conn_control) + return; + + /* + * If any client is running a discovery right now, then do not + * even try to start passive scanning. + * + * The discovery procedure is using interleaved scanning and + * thus will discover Low Energy devices as well. + */ + if (adapter->discovery_list) + return; + + if (adapter->discovery_enable == 0x01) + return; + + /* + * In case the discovery is suspended (for example for an ongoing + * pairing attempt), then also do not start passive scanning. + */ + if (adapter->discovery_suspended) + return; + + /* + * If the list of connectable Low Energy devices is empty, + * then do not start passive scanning. + */ + if (!adapter->connect_list) + return; + + adapter->passive_scan_timeout = g_timeout_add_seconds(CONN_SCAN_TIMEOUT, + passive_scanning_timeout, adapter); +} + +static void stop_passive_scanning_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + struct btd_device *dev; + int err; + + DBG("status 0x%02x (%s)", status, mgmt_errstr(status)); + + dev = adapter->connect_le; + adapter->connect_le = NULL; + + /* + * When the kernel background scanning is available, there is + * no need to stop any discovery. The kernel will handle the + * auto-connection by itself. + */ + if (kernel_conn_control) + return; + + /* + * MGMT_STATUS_REJECTED may be returned from kernel because the passive + * scan timer had expired in kernel and passive scan was disabled just + * around the time we called stop_passive_scanning(). + */ + if (status != MGMT_STATUS_SUCCESS && status != MGMT_STATUS_REJECTED) { + btd_error(adapter->dev_id, "Stopping passive scanning failed: %s", + mgmt_errstr(status)); + return; + } + + adapter->discovery_type = 0x00; + adapter->discovery_enable = 0x00; + + if (!dev) { + DBG("Device removed while stopping passive scanning"); + trigger_passive_scanning(adapter); + return; + } + + err = device_connect_le(dev); + if (err < 0) { + btd_error(adapter->dev_id, "LE auto connection failed: %s (%d)", + strerror(-err), -err); + trigger_passive_scanning(adapter); + } +} + +static void stop_passive_scanning(struct btd_adapter *adapter) +{ + struct mgmt_cp_stop_discovery cp; + + DBG(""); + + /* If there are any normal discovery clients passive scanning + * wont be running */ + if (adapter->discovery_list) + return; + + if (adapter->discovery_enable == 0x00) + return; + + cp.type = adapter->discovery_type; + + mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, + adapter->dev_id, sizeof(cp), &cp, + stop_passive_scanning_complete, adapter, NULL); +} + +static void cancel_passive_scanning(struct btd_adapter *adapter) +{ + if (!(adapter->current_settings & MGMT_SETTING_LE)) + return; + + DBG(""); + + if (adapter->passive_scan_timeout > 0) { + g_source_remove(adapter->passive_scan_timeout); + adapter->passive_scan_timeout = 0; + } +} + +static uint8_t get_scan_type(struct btd_adapter *adapter) +{ + uint8_t type; + + if (adapter->current_settings & MGMT_SETTING_BREDR) + type = SCAN_TYPE_BREDR; + else + type = 0; + + if (adapter->current_settings & MGMT_SETTING_LE) + type |= SCAN_TYPE_LE; + + return type; +} + +static void free_discovery_filter(struct discovery_filter *discovery_filter) +{ + if (!discovery_filter) + return; + + g_slist_free_full(discovery_filter->uuids, free); + g_free(discovery_filter); +} + +static void trigger_start_discovery(struct btd_adapter *adapter, guint delay); + +static void start_discovery_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + struct watch_client *client; + const struct mgmt_cp_start_discovery *rp = param; + DBusMessage *reply; + + DBG("status 0x%02x", status); + + /* Is there are no clients the discovery must have been stopped while + * discovery command was pending. + */ + if (!adapter->discovery_list) { + struct mgmt_cp_stop_discovery cp; + + if (status != MGMT_STATUS_SUCCESS) + return; + + /* Stop discovering as there are no clients left */ + cp.type = rp->type; + mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL); + return; + } + + client = adapter->discovery_list->data; + + if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, + "Wrong size of start discovery return parameters"); + if (client->msg) + goto fail; + return; + } + + if (status == MGMT_STATUS_SUCCESS) { + adapter->discovery_type = rp->type; + adapter->discovery_enable = 0x01; + + if (adapter->current_discovery_filter) + adapter->filtered_discovery = true; + else + adapter->filtered_discovery = false; + + if (client->msg) { + g_dbus_send_reply(dbus_conn, client->msg, + DBUS_TYPE_INVALID); + dbus_message_unref(client->msg); + client->msg = NULL; + } + + if (adapter->discovering) + return; + + adapter->discovering = true; + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Discovering"); + return; + } + +fail: + /* Reply with an error if the first discovery has failed */ + if (client->msg) { + reply = btd_error_busy(client->msg); + g_dbus_send_message(dbus_conn, reply); + g_dbus_remove_watch(dbus_conn, client->watch); + return; + } + + /* + * In case the restart of the discovery failed, then just trigger + * it for the next idle timeout again. + */ + trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT * 2); +} + +static gboolean start_discovery_timeout(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + struct mgmt_cp_start_service_discovery *sd_cp; + uint8_t new_type; + + DBG(""); + + adapter->discovery_idle_timeout = 0; + + /* If we're doing filtered discovery, it must be quickly restarted */ + adapter->no_scan_restart_delay = !!adapter->current_discovery_filter; + + DBG("adapter->current_discovery_filter == %d", + !!adapter->current_discovery_filter); + + new_type = get_scan_type(adapter); + + if (adapter->discovery_enable == 0x01) { + struct mgmt_cp_stop_discovery cp; + + /* + * If we're asked to start regular discovery, and there is an + * already running regular discovery and it has the same type, + * then just keep it. + */ + if (!adapter->current_discovery_filter && + !adapter->filtered_discovery && + adapter->discovery_type == new_type) { + if (adapter->discovering) + return FALSE; + + adapter->discovering = true; + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Discovering"); + return FALSE; + } + + /* + * Otherwise the current discovery must be stopped. So + * queue up a stop discovery command. + * + * This can happen if a passive scanning for Low Energy + * devices is ongoing, or scan type is changed between + * regular and filtered, or filter was updated. + */ + cp.type = adapter->discovery_type; + mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL); + + /* Don't even bother to try to quickly start discovery + * just after stopping it, it would fail with status + * MGMT_BUSY. Instead discovering_callback will take + * care of that. + */ + return FALSE; + + } + + /* Regular discovery is required */ + if (!adapter->current_discovery_filter) { + struct mgmt_cp_start_discovery cp; + + cp.type = new_type; + mgmt_send(adapter->mgmt, MGMT_OP_START_DISCOVERY, + adapter->dev_id, sizeof(cp), &cp, + start_discovery_complete, adapter, NULL); + return FALSE; + } + + /* Filtered discovery is required */ + sd_cp = adapter->current_discovery_filter; + + DBG("sending MGMT_OP_START_SERVICE_DISCOVERY %d, %d, %d", + sd_cp->rssi, sd_cp->type, + btohs(sd_cp->uuid_count)); + + mgmt_send(adapter->mgmt, MGMT_OP_START_SERVICE_DISCOVERY, + adapter->dev_id, sizeof(*sd_cp) + + btohs(sd_cp->uuid_count) * 16, + sd_cp, start_discovery_complete, adapter, NULL); + + return FALSE; +} + +static void trigger_start_discovery(struct btd_adapter *adapter, guint delay) +{ + + DBG(""); + + cancel_passive_scanning(adapter); + + if (adapter->discovery_idle_timeout > 0) { + g_source_remove(adapter->discovery_idle_timeout); + adapter->discovery_idle_timeout = 0; + } + + /* + * If the controller got powered down in between, then ensure + * that we do not keep trying to restart discovery. + * + * This is safe-guard and should actually never trigger. + */ + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + return; + + adapter->discovery_idle_timeout = g_timeout_add_seconds(delay, + start_discovery_timeout, adapter); +} + +static void suspend_discovery_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + DBG("status 0x%02x", status); + + if (status == MGMT_STATUS_SUCCESS) { + adapter->discovery_type = 0x00; + adapter->discovery_enable = 0x00; + return; + } +} + +static void suspend_discovery(struct btd_adapter *adapter) +{ + struct mgmt_cp_stop_discovery cp; + + DBG(""); + + adapter->discovery_suspended = true; + + /* + * If there are no clients discovering right now, then there is + * also nothing to suspend. + */ + if (!adapter->discovery_list) + return; + + /* + * In case of being inside the idle phase, make sure to remove + * the timeout to not trigger a restart. + * + * The restart will be triggered when the discovery is resumed. + */ + if (adapter->discovery_idle_timeout > 0) { + g_source_remove(adapter->discovery_idle_timeout); + adapter->discovery_idle_timeout = 0; + } + + if (adapter->discovery_enable == 0x00) + return; + + cp.type = adapter->discovery_type; + + mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, + adapter->dev_id, sizeof(cp), &cp, + suspend_discovery_complete, adapter, NULL); +} + +static void resume_discovery(struct btd_adapter *adapter) +{ + DBG(""); + + adapter->discovery_suspended = false; + + /* + * If there are no clients discovering right now, then there is + * also nothing to resume. + */ + if (!adapter->discovery_list) + return; + + /* + * Treat a suspended discovery session the same as extra long + * idle time for a normal discovery. So just trigger the default + * restart procedure. + */ + trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT); +} + +static void discovering_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_discovering *ev = param; + struct btd_adapter *adapter = user_data; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small discovering event"); + return; + } + + DBG("hci%u type %u discovering %u method %d", adapter->dev_id, ev->type, + ev->discovering, adapter->filtered_discovery); + + if (adapter->discovery_enable == ev->discovering) + return; + + adapter->discovery_type = ev->type; + adapter->discovery_enable = ev->discovering; + + /* + * Check for existing discoveries triggered by client applications + * and ignore all others. + * + * If there are no clients, then it is good idea to trigger a + * passive scanning attempt. + */ + if (!adapter->discovery_list) { + if (!adapter->connect_le) + trigger_passive_scanning(adapter); + return; + } + + if (adapter->discovery_suspended) + return; + + switch (adapter->discovery_enable) { + case 0x00: + if (adapter->no_scan_restart_delay) + trigger_start_discovery(adapter, 0); + else + trigger_start_discovery(adapter, IDLE_DISCOV_TIMEOUT); + break; + + case 0x01: + if (adapter->discovery_idle_timeout > 0) { + g_source_remove(adapter->discovery_idle_timeout); + adapter->discovery_idle_timeout = 0; + } + + break; + } +} + +static void invalidate_rssi_and_tx_power(gpointer a) +{ + struct btd_device *dev = a; + + device_set_rssi(dev, 0); + device_set_tx_power(dev, 127); +} + +static gboolean remove_temp_devices(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + GSList *l, *next; + + DBG("%s", adapter->path); + + adapter->temp_devices_timeout = 0; + + for (l = adapter->devices; l != NULL; l = next) { + struct btd_device *dev = l->data; + + next = g_slist_next(l); + + if (device_is_temporary(dev) && !btd_device_is_connected(dev)) + btd_adapter_remove_device(adapter, dev); + } + + return FALSE; +} + +static void discovery_cleanup(struct btd_adapter *adapter) +{ + GSList *l, *next; + + adapter->discovery_type = 0x00; + + if (adapter->discovery_idle_timeout > 0) { + g_source_remove(adapter->discovery_idle_timeout); + adapter->discovery_idle_timeout = 0; + } + + if (adapter->temp_devices_timeout > 0) { + g_source_remove(adapter->temp_devices_timeout); + adapter->temp_devices_timeout = 0; + } + + g_slist_free_full(adapter->discovery_found, + invalidate_rssi_and_tx_power); + adapter->discovery_found = NULL; + + if (!adapter->devices) + return; + + for (l = adapter->devices; l != NULL; l = next) { + struct btd_device *dev = l->data; + + next = g_slist_next(l); + + if (device_is_temporary(dev) && !device_is_connectable(dev)) + btd_adapter_remove_device(adapter, dev); + } + + adapter->temp_devices_timeout = g_timeout_add_seconds(TEMP_DEV_TIMEOUT, + remove_temp_devices, adapter); +} + +static void discovery_free(void *user_data) +{ + struct watch_client *client = user_data; + + if (client->watch) + g_dbus_remove_watch(dbus_conn, client->watch); + + if (client->discovery_filter) { + free_discovery_filter(client->discovery_filter); + client->discovery_filter = NULL; + } + + if (client->msg) + dbus_message_unref(client->msg); + + g_free(client->owner); + g_free(client); +} + +static bool set_discovery_discoverable(struct btd_adapter *adapter, bool enable) +{ + if (adapter->discovery_discoverable == enable) + return true; + + /* Reset discoverable filter if already set */ + if (enable && (adapter->current_settings & MGMT_OP_SET_DISCOVERABLE)) + return true; + + adapter->discovery_discoverable = enable; + + return set_discoverable(adapter, enable, 0); +} + +static void discovery_remove(struct watch_client *client, bool exit) +{ + struct btd_adapter *adapter = client->adapter; + + DBG("owner %s", client->owner); + + adapter->set_filter_list = g_slist_remove(adapter->set_filter_list, + client); + + adapter->discovery_list = g_slist_remove(adapter->discovery_list, + client); + + if (!exit && client->discovery_filter) + adapter->set_filter_list = g_slist_prepend( + adapter->set_filter_list, client); + else + discovery_free(client); + + /* + * If there are other client discoveries in progress, then leave + * it active. If not, then make sure to stop the restart timeout. + */ + if (adapter->discovery_list) + return; + + discovery_cleanup(adapter); +} + +static void stop_discovery_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct watch_client *client = user_data; + struct btd_adapter *adapter = client->adapter; + DBusMessage *reply; + + DBG("status 0x%02x", status); + + if (status != MGMT_STATUS_SUCCESS) { + if (client->msg) { + reply = btd_error_busy(client->msg); + g_dbus_send_message(dbus_conn, reply); + } + goto done; + } + + if (client->msg) { + g_dbus_send_reply(dbus_conn, client->msg, DBUS_TYPE_INVALID); + dbus_message_unref(client->msg); + client->msg = NULL; + } + + adapter->discovery_type = 0x00; + adapter->discovery_enable = 0x00; + adapter->filtered_discovery = false; + adapter->no_scan_restart_delay = false; + adapter->discovering = false; + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Discovering"); + + trigger_passive_scanning(adapter); + +done: + discovery_remove(client, false); +} + +static int compare_sender(gconstpointer a, gconstpointer b) +{ + const struct watch_client *client = a; + const char *sender = b; + + return g_strcmp0(client->owner, sender); +} + +static gint g_strcmp(gconstpointer a, gconstpointer b) +{ + return strcmp(a, b); +} + +static void extract_unique_uuids(gpointer data, gpointer user_data) +{ + char *uuid_str = data; + GSList **uuids = user_data; + + if (!g_slist_find_custom(*uuids, uuid_str, g_strcmp)) + *uuids = g_slist_insert_sorted(*uuids, uuid_str, g_strcmp); +} + +/* + * This method merges all adapter filters into rssi, transport and uuids. + * Returns 1 if there was no filtered scan, 0 otherwise. + */ +static int merge_discovery_filters(struct btd_adapter *adapter, int *rssi, + uint8_t *transport, GSList **uuids) +{ + GSList *l; + bool empty_uuid = false; + bool has_regular_discovery = false; + bool has_filtered_discovery = false; + + for (l = adapter->discovery_list; l != NULL; l = g_slist_next(l)) { + struct watch_client *client = l->data; + struct discovery_filter *item = client->discovery_filter; + + if (!item) { + has_regular_discovery = true; + continue; + } + + has_filtered_discovery = true; + + *transport |= item->type; + + /* + * Rule for merging rssi and pathloss into rssi field of kernel + * filter is as follow: + * - if there's any client without proximity filter, then do no + * proximity filtering, + * - if all clients specified RSSI, then use lowest value, + * - if any client specified pathloss, then kernel filter should + * do no proximity, as kernel can't compute pathloss. We'll do + * filtering on our own. + */ + if (item->rssi == DISTANCE_VAL_INVALID) + *rssi = HCI_RSSI_INVALID; + else if (*rssi != HCI_RSSI_INVALID && *rssi >= item->rssi) + *rssi = item->rssi; + else if (item->pathloss != DISTANCE_VAL_INVALID) + *rssi = HCI_RSSI_INVALID; + + if (!g_slist_length(item->uuids)) + empty_uuid = true; + + g_slist_foreach(item->uuids, extract_unique_uuids, uuids); + } + + /* If no proximity filtering is set, disable it */ + if (*rssi == DISTANCE_VAL_INVALID) + *rssi = HCI_RSSI_INVALID; + + /* + * Empty_uuid variable determines wether there was any filter with no + * uuids. In this case someone might be looking for all devices in + * certain proximity, and we need to have empty uuids in kernel filter. + */ + if (empty_uuid) { + g_slist_free(*uuids); + *uuids = NULL; + } + + if (has_regular_discovery) { + if (!has_filtered_discovery) + return 1; + + /* + * It there is both regular and filtered scan running, then + * clear whole fitler to report all devices. + */ + *transport = get_scan_type(adapter); + *rssi = HCI_RSSI_INVALID; + g_slist_free(*uuids); + *uuids = NULL; + } + + return 0; +} + +static void populate_mgmt_filter_uuids(uint8_t (*mgmt_uuids)[16], GSList *uuids) +{ + GSList *l; + + for (l = uuids; l != NULL; l = g_slist_next(l)) { + bt_uuid_t uuid, u128; + uint128_t uint128; + + bt_string_to_uuid(&uuid, l->data); + bt_uuid_to_uuid128(&uuid, &u128); + + ntoh128((uint128_t *) u128.value.u128.data, &uint128); + htob128(&uint128, (uint128_t *) mgmt_uuids); + + mgmt_uuids++; + } +} + +/* + * This method merges all adapter filters into one that will be send to kernel. + * cp_ptr is set to null when regular non-filtered discovery is needed, + * otherwise it's pointing to filter. Returns 0 on succes, -1 on error + */ +static int discovery_filter_to_mgmt_cp(struct btd_adapter *adapter, + struct mgmt_cp_start_service_discovery **cp_ptr) +{ + GSList *uuids = NULL; + struct mgmt_cp_start_service_discovery *cp; + int rssi = DISTANCE_VAL_INVALID; + int uuid_count; + uint8_t discovery_type = 0; + + DBG(""); + + if (merge_discovery_filters(adapter, &rssi, &discovery_type, &uuids)) { + /* There are only regular scans, run just regular scan. */ + *cp_ptr = NULL; + return 0; + } + + uuid_count = g_slist_length(uuids); + + cp = g_try_malloc(sizeof(*cp) + 16*uuid_count); + *cp_ptr = cp; + if (!cp) { + g_slist_free(uuids); + return -1; + } + + cp->type = discovery_type; + cp->rssi = rssi; + cp->uuid_count = htobs(uuid_count); + populate_mgmt_filter_uuids(cp->uuids, uuids); + + g_slist_free(uuids); + return 0; +} + +static bool filters_equal(struct mgmt_cp_start_service_discovery *a, + struct mgmt_cp_start_service_discovery *b) { + if (!a && !b) + return true; + + if ((!a && b) || (a && !b)) + return false; + + if (a->type != b->type) + return false; + + if (a->rssi != b->rssi) + return false; + + /* + * When we create mgmt_cp_start_service_discovery structure inside + * discovery_filter_to_mgmt_cp, we always keep uuids sorted, and + * unique, so we're safe to compare uuid_count, and uuids like that. + */ + if (a->uuid_count != b->uuid_count) + return false; + + if (memcmp(a->uuids, b->uuids, 16 * a->uuid_count) != 0) + return false; + + return true; +} + +static int update_discovery_filter(struct btd_adapter *adapter) +{ + struct mgmt_cp_start_service_discovery *sd_cp; + GSList *l; + + + DBG(""); + + if (discovery_filter_to_mgmt_cp(adapter, &sd_cp)) { + btd_error(adapter->dev_id, + "discovery_filter_to_mgmt_cp returned error"); + return -ENOMEM; + } + + for (l = adapter->discovery_list; l; l = g_slist_next(l)) { + struct watch_client *client = l->data; + + if (!client->discovery_filter) + continue; + + if (client->discovery_filter->discoverable) + break; + } + + set_discovery_discoverable(adapter, l ? true : false); + + /* + * If filters are equal, then don't update scan, except for when + * starting discovery. + */ + if (filters_equal(adapter->current_discovery_filter, sd_cp) && + adapter->discovering != 0) { + DBG("filters were equal, deciding to not restart the scan."); + g_free(sd_cp); + return 0; + } + + g_free(adapter->current_discovery_filter); + adapter->current_discovery_filter = sd_cp; + + trigger_start_discovery(adapter, 0); + + return -EINPROGRESS; +} + +static int discovery_stop(struct watch_client *client, bool exit) +{ + struct btd_adapter *adapter = client->adapter; + struct mgmt_cp_stop_discovery cp; + + /* Check if there are more client discovering */ + if (g_slist_next(adapter->discovery_list)) { + discovery_remove(client, exit); + update_discovery_filter(adapter); + return 0; + } + + if (adapter->discovery_discoverable) + set_discovery_discoverable(adapter, false); + + /* + * In the idle phase of a discovery, there is no need to stop it + * and so it is enough to send out the signal and just return. + */ + if (adapter->discovery_enable == 0x00) { + discovery_remove(client, exit); + adapter->discovering = false; + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Discovering"); + + trigger_passive_scanning(adapter); + + return 0; + } + + cp.type = adapter->discovery_type; + + mgmt_send(adapter->mgmt, MGMT_OP_STOP_DISCOVERY, + adapter->dev_id, sizeof(cp), &cp, + stop_discovery_complete, client, NULL); + + return -EINPROGRESS; +} + +static void discovery_disconnect(DBusConnection *conn, void *user_data) +{ + struct watch_client *client = user_data; + + DBG("owner %s", client->owner); + + discovery_stop(client, true); +} + +/* + * Returns true if client was already discovering, false otherwise. *client + * will point to discovering client, or client that have pre-set his filter. + */ +static bool get_discovery_client(struct btd_adapter *adapter, + const char *owner, + struct watch_client **client) +{ + GSList *list = g_slist_find_custom(adapter->discovery_list, owner, + compare_sender); + if (list) { + *client = list->data; + return true; + } + + list = g_slist_find_custom(adapter->set_filter_list, owner, + compare_sender); + if (list) { + *client = list->data; + return false; + } + + *client = NULL; + return false; +} + +static DBusMessage *start_discovery(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const char *sender = dbus_message_get_sender(msg); + struct watch_client *client; + bool is_discovering; + int err; + + DBG("sender %s", sender); + + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + return btd_error_not_ready(msg); + + is_discovering = get_discovery_client(adapter, sender, &client); + + /* + * Every client can only start one discovery, if the client + * already started a discovery then return an error. + */ + if (is_discovering) + return btd_error_busy(msg); + + /* + * If there was pre-set filter, just reconnect it to discovery_list, + * and trigger scan. + */ + if (client) { + if (client->msg) + return btd_error_busy(msg); + + adapter->set_filter_list = g_slist_remove( + adapter->set_filter_list, client); + adapter->discovery_list = g_slist_prepend( + adapter->discovery_list, client); + + goto done; + } + + client = g_new0(struct watch_client, 1); + + client->adapter = adapter; + client->owner = g_strdup(sender); + client->discovery_filter = NULL; + client->watch = g_dbus_add_disconnect_watch(dbus_conn, sender, + discovery_disconnect, client, + NULL); + adapter->discovery_list = g_slist_prepend(adapter->discovery_list, + client); + +done: + /* + * Just trigger the discovery here. In case an already running + * discovery in idle phase exists, it will be restarted right + * away. + */ + err = update_discovery_filter(adapter); + if (!err) + return dbus_message_new_method_return(msg); + + /* If the discovery has to be started wait it complete to reply */ + if (err == -EINPROGRESS) { + client->msg = dbus_message_ref(msg); + return NULL; + } + + return btd_error_failed(msg, strerror(-err)); +} + +static bool parse_uuids(DBusMessageIter *value, struct discovery_filter *filter) +{ + DBusMessageIter arriter; + + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_ARRAY) + return false; + + dbus_message_iter_recurse(value, &arriter); + while (dbus_message_iter_get_arg_type(&arriter) != DBUS_TYPE_INVALID) { + bt_uuid_t uuid, u128; + char uuidstr[MAX_LEN_UUID_STR + 1]; + char *uuid_param; + + if (dbus_message_iter_get_arg_type(&arriter) != + DBUS_TYPE_STRING) + return false; + + dbus_message_iter_get_basic(&arriter, &uuid_param); + + if (bt_string_to_uuid(&uuid, uuid_param)) + return false; + + bt_uuid_to_uuid128(&uuid, &u128); + bt_uuid_to_string(&u128, uuidstr, sizeof(uuidstr)); + + filter->uuids = g_slist_prepend(filter->uuids, strdup(uuidstr)); + + dbus_message_iter_next(&arriter); + } + + return true; +} + +static bool parse_rssi(DBusMessageIter *value, struct discovery_filter *filter) +{ + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_INT16) + return false; + + dbus_message_iter_get_basic(value, &filter->rssi); + /* -127 <= RSSI <= +20 (spec V4.2 [Vol 2, Part E] 7.7.65.2) */ + if (filter->rssi > 20 || filter->rssi < -127) + return false; + + return true; +} + +static bool parse_pathloss(DBusMessageIter *value, + struct discovery_filter *filter) +{ + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_UINT16) + return false; + + dbus_message_iter_get_basic(value, &filter->pathloss); + /* pathloss filter must be smaller that PATHLOSS_MAX */ + if (filter->pathloss > PATHLOSS_MAX) + return false; + + return true; +} + +static bool parse_transport(DBusMessageIter *value, + struct discovery_filter *filter) +{ + char *transport_str; + + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRING) + return false; + + dbus_message_iter_get_basic(value, &transport_str); + + if (!strcmp(transport_str, "bredr")) + filter->type = SCAN_TYPE_BREDR; + else if (!strcmp(transport_str, "le")) + filter->type = SCAN_TYPE_LE; + else if (strcmp(transport_str, "auto")) + return false; + + return true; +} + +static bool parse_duplicate_data(DBusMessageIter *value, + struct discovery_filter *filter) +{ + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) + return false; + + dbus_message_iter_get_basic(value, &filter->duplicate); + + return true; +} + +static bool parse_discoverable(DBusMessageIter *value, + struct discovery_filter *filter) +{ + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) + return false; + + dbus_message_iter_get_basic(value, &filter->discoverable); + + return true; +} + +struct filter_parser { + const char *name; + bool (*func)(DBusMessageIter *iter, struct discovery_filter *filter); +} parsers[] = { + { "UUIDs", parse_uuids }, + { "RSSI", parse_rssi }, + { "Pathloss", parse_pathloss }, + { "Transport", parse_transport }, + { "DuplicateData", parse_duplicate_data }, + { "Discoverable", parse_discoverable }, + { } +}; + +static bool parse_discovery_filter_entry(char *key, DBusMessageIter *value, + struct discovery_filter *filter) +{ + struct filter_parser *parser; + + for (parser = parsers; parser && parser->name; parser++) { + if (!strcmp(parser->name, key)) + return parser->func(value, filter); + } + + DBG("Unknown key parameter: %s!\n", key); + return false; +} + +/* + * This method is responsible for parsing parameters to SetDiscoveryFilter. If + * filter in msg was empty, sets *filter to NULL. If whole parsing was + * successful, sets *filter to proper value. + * Returns false on any error, and true on success. + */ +static bool parse_discovery_filter_dict(struct btd_adapter *adapter, + struct discovery_filter **filter, + DBusMessage *msg) +{ + DBusMessageIter iter, subiter, dictiter, variantiter; + bool is_empty = true; + + *filter = g_try_malloc(sizeof(**filter)); + if (!*filter) + return false; + + (*filter)->uuids = NULL; + (*filter)->pathloss = DISTANCE_VAL_INVALID; + (*filter)->rssi = DISTANCE_VAL_INVALID; + (*filter)->type = get_scan_type(adapter); + (*filter)->duplicate = false; + (*filter)->discoverable = false; + + dbus_message_iter_init(msg, &iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) + goto invalid_args; + + dbus_message_iter_recurse(&iter, &subiter); + do { + int type = dbus_message_iter_get_arg_type(&subiter); + char *key; + + if (type == DBUS_TYPE_INVALID) + break; + + is_empty = false; + dbus_message_iter_recurse(&subiter, &dictiter); + + dbus_message_iter_get_basic(&dictiter, &key); + if (!dbus_message_iter_next(&dictiter)) + goto invalid_args; + + if (dbus_message_iter_get_arg_type(&dictiter) != + DBUS_TYPE_VARIANT) + goto invalid_args; + + dbus_message_iter_recurse(&dictiter, &variantiter); + + if (!parse_discovery_filter_entry(key, &variantiter, *filter)) + goto invalid_args; + + dbus_message_iter_next(&subiter); + } while (true); + + if (is_empty) { + g_free(*filter); + *filter = NULL; + return true; + } + + /* only pathlos or rssi can be set, never both */ + if ((*filter)->pathloss != DISTANCE_VAL_INVALID && + (*filter)->rssi != DISTANCE_VAL_INVALID) + goto invalid_args; + + DBG("filtered discovery params: transport: %d rssi: %d pathloss: %d " + " duplicate data: %s discoverable %s", (*filter)->type, + (*filter)->rssi, (*filter)->pathloss, + (*filter)->duplicate ? "true" : "false", + (*filter)->discoverable ? "true" : "false"); + + return true; + +invalid_args: + g_slist_free_full((*filter)->uuids, g_free); + g_free(*filter); + *filter = NULL; + return false; +} + +static DBusMessage *set_discovery_filter(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct btd_adapter *adapter = user_data; + struct watch_client *client; + struct discovery_filter *discovery_filter; + const char *sender = dbus_message_get_sender(msg); + bool is_discovering; + + DBG("sender %s", sender); + + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + return btd_error_not_ready(msg); + + if (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 8)) + return btd_error_not_supported(msg); + + /* parse parameters */ + if (!parse_discovery_filter_dict(adapter, &discovery_filter, msg)) + return btd_error_invalid_args(msg); + + is_discovering = get_discovery_client(adapter, sender, &client); + + if (client) { + free_discovery_filter(client->discovery_filter); + client->discovery_filter = discovery_filter; + + if (is_discovering) + update_discovery_filter(adapter); + + if (discovery_filter || is_discovering) + return dbus_message_new_method_return(msg); + + /* Removing pre-set filter */ + adapter->set_filter_list = g_slist_remove( + adapter->set_filter_list, + client); + discovery_free(client); + DBG("successfully cleared pre-set filter"); + } else if (discovery_filter) { + /* Client pre-setting his filter for first time */ + client = g_new0(struct watch_client, 1); + client->adapter = adapter; + client->owner = g_strdup(sender); + client->discovery_filter = discovery_filter; + client->watch = g_dbus_add_disconnect_watch(dbus_conn, sender, + discovery_disconnect, client, + NULL); + adapter->set_filter_list = g_slist_prepend( + adapter->set_filter_list, client); + + DBG("successfully pre-set filter"); + } + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *stop_discovery(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const char *sender = dbus_message_get_sender(msg); + struct watch_client *client; + GSList *list; + int err; + + DBG("sender %s", sender); + + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + return btd_error_not_ready(msg); + + list = g_slist_find_custom(adapter->discovery_list, sender, + compare_sender); + if (!list) + return btd_error_failed(msg, "No discovery started"); + + client = list->data; + + if (client->msg) + return btd_error_busy(msg); + + err = discovery_stop(client, false); + switch (err) { + case 0: + return dbus_message_new_method_return(msg); + case -EINPROGRESS: + client->msg = dbus_message_ref(msg); + return NULL; + default: + return btd_error_failed(msg, strerror(-err)); + } +} + +static gboolean property_get_address(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + char addr[18]; + const char *str = addr; + + ba2str(&adapter->bdaddr, addr); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); + + return TRUE; +} + +static gboolean property_get_address_type(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const char *str; + + if ((adapter->current_settings & MGMT_SETTING_LE) && + (adapter->bdaddr_type == BDADDR_LE_RANDOM)) + str = "random"; + else + str = "public"; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); + + return TRUE; +} + +static gboolean property_get_name(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const char *str = adapter->system_name ? : ""; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); + + return TRUE; +} + +static gboolean property_get_alias(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const char *str; + + if (adapter->current_alias) + str = adapter->current_alias; + else if (adapter->stored_alias) + str = adapter->stored_alias; + else + str = adapter->system_name ? : ""; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); + + return TRUE; +} + +static void property_set_alias(const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const char *name; + int ret; + + dbus_message_iter_get_basic(iter, &name); + + if (g_str_equal(name, "") == TRUE) { + if (adapter->stored_alias == NULL) { + /* no alias set, nothing to restore */ + g_dbus_pending_property_success(id); + return; + } + + /* restore to system name */ + ret = set_name(adapter, adapter->system_name); + } else { + if (g_strcmp0(adapter->stored_alias, name) == 0) { + /* alias already set, nothing to do */ + g_dbus_pending_property_success(id); + return; + } + + /* set to alias */ + ret = set_name(adapter, name); + } + + if (ret >= 0) { + g_free(adapter->stored_alias); + + if (g_str_equal(name, "") == TRUE) + adapter->stored_alias = NULL; + else + adapter->stored_alias = g_strdup(name); + + store_adapter_info(adapter); + + g_dbus_pending_property_success(id); + return; + } + + if (ret == -EINVAL) + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + else + g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", + strerror(-ret)); +} + +static gboolean property_get_class(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + dbus_uint32_t val = adapter->dev_class; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &val); + + return TRUE; +} + +static gboolean property_get_mode(struct btd_adapter *adapter, + uint32_t setting, DBusMessageIter *iter) +{ + dbus_bool_t enable; + + enable = (adapter->current_settings & setting) ? TRUE : FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &enable); + + return TRUE; +} + +struct property_set_data { + struct btd_adapter *adapter; + GDBusPendingPropertySet id; +}; + +static void property_set_mode_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct property_set_data *data = user_data; + struct btd_adapter *adapter = data->adapter; + + DBG("%s (0x%02x)", mgmt_errstr(status), status); + + if (status != MGMT_STATUS_SUCCESS) { + const char *dbus_err; + + btd_error(adapter->dev_id, "Failed to set mode: %s (0x%02x)", + mgmt_errstr(status), status); + + if (status == MGMT_STATUS_RFKILLED) + dbus_err = ERROR_INTERFACE ".Blocked"; + else + dbus_err = ERROR_INTERFACE ".Failed"; + + g_dbus_pending_property_error(data->id, dbus_err, + mgmt_errstr(status)); + return; + } + + g_dbus_pending_property_success(data->id); + + /* + * The parameters are identical and also the task that is + * required in both cases. So it is safe to just call the + * event handling functions here. + */ + new_settings_callback(adapter->dev_id, length, param, adapter); +} + +static void clear_discoverable(struct btd_adapter *adapter) +{ + if (!kernel_conn_control) + return; + + if (!(adapter->current_settings & MGMT_SETTING_DISCOVERABLE)) + return; + + /* If no timeout is set do nothing as both connectable and discoverable + * flags are persistent on power toggle. + */ + if (!adapter->discoverable_timeout) + return; + + /* If timeout was set kernel clears discoverable on its own when + * powering off controller. This would leave connectable flag set + * after power on. + * + * With kernel control clearing connectable clear also discoverable + * flag so we need to clear connectable. + */ + set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x00); +} + +static void property_set_mode(struct btd_adapter *adapter, uint32_t setting, + DBusMessageIter *value, + GDBusPendingPropertySet id) +{ + struct property_set_data *data; + struct mgmt_cp_set_discoverable cp; + void *param; + dbus_bool_t enable, current_enable; + uint16_t opcode, len; + uint8_t mode; + + dbus_message_iter_get_basic(value, &enable); + + if (adapter->current_settings & setting) + current_enable = TRUE; + else + current_enable = FALSE; + + if (enable == current_enable || adapter->pending_settings & setting) { + g_dbus_pending_property_success(id); + return; + } + + mode = (enable == TRUE) ? 0x01 : 0x00; + + adapter->pending_settings |= setting; + + switch (setting) { + case MGMT_SETTING_POWERED: + opcode = MGMT_OP_SET_POWERED; + param = &mode; + len = sizeof(mode); + + if (!mode) + clear_discoverable(adapter); + + break; + case MGMT_SETTING_DISCOVERABLE: + if (kernel_conn_control) { + if (mode) { + set_mode(adapter, MGMT_OP_SET_CONNECTABLE, + mode); + } else { + opcode = MGMT_OP_SET_CONNECTABLE; + param = &mode; + len = sizeof(mode); + break; + } + } + + memset(&cp, 0, sizeof(cp)); + cp.val = mode; + if (cp.val) + cp.timeout = htobs(adapter->discoverable_timeout); + + opcode = MGMT_OP_SET_DISCOVERABLE; + param = &cp; + len = sizeof(cp); + break; + case MGMT_SETTING_BONDABLE: + opcode = MGMT_OP_SET_BONDABLE; + param = &mode; + len = sizeof(mode); + break; + default: + goto failed; + } + + DBG("sending %s command for index %u", mgmt_opstr(opcode), + adapter->dev_id); + + data = g_try_new0(struct property_set_data, 1); + if (!data) + goto failed; + + data->adapter = adapter; + data->id = id; + + if (mgmt_send(adapter->mgmt, opcode, adapter->dev_id, len, param, + property_set_mode_complete, data, g_free) > 0) + return; + + g_free(data); + +failed: + btd_error(adapter->dev_id, "Failed to set mode for index %u", + adapter->dev_id); + + g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", NULL); +} + +static gboolean property_get_powered(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + return property_get_mode(adapter, MGMT_SETTING_POWERED, iter); +} + +static void property_set_powered(const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (powering_down) { + g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", + "Powering down"); + return; + } + + property_set_mode(adapter, MGMT_SETTING_POWERED, iter, id); +} + +static gboolean property_get_discoverable(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + return property_get_mode(adapter, MGMT_SETTING_DISCOVERABLE, iter); +} + +static void property_set_discoverable(const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (adapter->discoverable_timeout > 0 && + !(adapter->current_settings & MGMT_SETTING_POWERED)) { + g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", + "Not Powered"); + return; + } + + /* Reset discovery_discoverable as Discoverable takes precedence */ + adapter->discovery_discoverable = false; + + property_set_mode(adapter, MGMT_SETTING_DISCOVERABLE, iter, id); +} + +static gboolean property_get_discoverable_timeout( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + dbus_uint32_t value = adapter->discoverable_timeout; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &value); + + return TRUE; +} + +static void property_set_discoverable_timeout( + const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *user_data) +{ + struct btd_adapter *adapter = user_data; + bool enabled; + dbus_uint32_t value; + + dbus_message_iter_get_basic(iter, &value); + + adapter->discoverable_timeout = value; + + g_dbus_pending_property_success(id); + + store_adapter_info(adapter); + + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "DiscoverableTimeout"); + + if (adapter->pending_settings & MGMT_SETTING_DISCOVERABLE) { + if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) + enabled = false; + else + enabled = true; + } else { + if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) + enabled = true; + else + enabled = false; + } + + if (enabled) + set_discoverable(adapter, 0x01, adapter->discoverable_timeout); +} + +static gboolean property_get_pairable(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + return property_get_mode(adapter, MGMT_SETTING_BONDABLE, iter); +} + +static void property_set_pairable(const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + property_set_mode(adapter, MGMT_SETTING_BONDABLE, iter, id); +} + +static gboolean property_get_pairable_timeout( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + dbus_uint32_t value = adapter->pairable_timeout; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &value); + + return TRUE; +} + +static void property_set_pairable_timeout(const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *user_data) +{ + struct btd_adapter *adapter = user_data; + dbus_uint32_t value; + + dbus_message_iter_get_basic(iter, &value); + + adapter->pairable_timeout = value; + + g_dbus_pending_property_success(id); + + store_adapter_info(adapter); + + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "PairableTimeout"); + + trigger_pairable_timeout(adapter); +} + +static gboolean property_get_discovering(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + dbus_bool_t discovering = adapter->discovering; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &discovering); + + return TRUE; +} + +static void add_gatt_uuid(struct gatt_db_attribute *attrib, void *user_data) +{ + GHashTable *uuids = user_data; + bt_uuid_t uuid, u128; + char uuidstr[MAX_LEN_UUID_STR + 1]; + + if (!gatt_db_service_get_active(attrib)) + return; + + if (!gatt_db_attribute_get_service_uuid(attrib, &uuid)) + return; + + bt_uuid_to_uuid128(&uuid, &u128); + bt_uuid_to_string(&u128, uuidstr, sizeof(uuidstr)); + + g_hash_table_add(uuids, strdup(uuidstr)); +} + +static void iter_append_uuid(gpointer key, gpointer value, gpointer user_data) +{ + DBusMessageIter *iter = user_data; + const char *uuid = key; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); +} + +static gboolean property_get_uuids(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + DBusMessageIter entry; + sdp_list_t *l; + struct gatt_db *db; + GHashTable *uuids; + + uuids = g_hash_table_new_full(g_str_hash, g_str_equal, free, NULL); + if (!uuids) + return FALSE; + + /* SDP records */ + for (l = adapter->services; l != NULL; l = l->next) { + sdp_record_t *rec = l->data; + char *uuid; + + uuid = bt_uuid2string(&rec->svclass); + if (uuid == NULL) + continue; + + g_hash_table_add(uuids, uuid); + } + + /* GATT services */ + db = btd_gatt_database_get_db(adapter->database); + if (db) + gatt_db_foreach_service(db, NULL, add_gatt_uuid, uuids); + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + g_hash_table_foreach(uuids, iter_append_uuid, &entry); + dbus_message_iter_close_container(iter, &entry); + + g_hash_table_destroy(uuids); + + return TRUE; +} + +static gboolean property_exists_modalias(const GDBusPropertyTable *property, + void *user_data) +{ + struct btd_adapter *adapter = user_data; + + return adapter->modalias ? TRUE : FALSE; +} + +static gboolean property_get_modalias(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const char *str = adapter->modalias ? : ""; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); + + return TRUE; +} + +static int device_path_cmp(gconstpointer a, gconstpointer b) +{ + const struct btd_device *device = a; + const char *path = b; + const char *dev_path = device_get_path(device); + + return strcasecmp(dev_path, path); +} + +static DBusMessage *remove_device(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct btd_adapter *adapter = user_data; + struct btd_device *device; + const char *path; + GSList *list; + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + list = g_slist_find_custom(adapter->devices, path, device_path_cmp); + if (!list) + return btd_error_does_not_exist(msg); + + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + return btd_error_not_ready(msg); + + device = list->data; + + btd_device_set_temporary(device, true); + + if (!btd_device_is_connected(device)) { + btd_adapter_remove_device(adapter, device); + return dbus_message_new_method_return(msg); + } + + device_request_disconnect(device, msg); + + return NULL; +} + +static DBusMessage *get_discovery_filters(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + DBusMessage *reply; + DBusMessageIter iter, array; + struct filter_parser *parser; + + reply = dbus_message_new_method_return(msg); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array); + + for (parser = parsers; parser && parser->name; parser++) { + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, + &parser->name); + } + + dbus_message_iter_close_container(&iter, &array); + + return reply; +} + +struct device_connect_data { + struct btd_adapter *adapter; + bdaddr_t dst; + uint8_t dst_type; + DBusMessage *msg; +}; + +static void device_browse_cb(struct btd_device *dev, int err, void *user_data) +{ + DBG("err %d (%s)", err, strerror(-err)); + + if (!err) + btd_device_connect_services(dev, NULL); +} + +static void device_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) +{ + struct device_connect_data *data = user_data; + struct btd_adapter *adapter = data->adapter; + struct btd_device *device; + const char *path; + + DBG("%s", gerr ? gerr->message : ""); + + if (gerr) + goto failed; + + /* object might already exist due to mgmt socket event */ + device = btd_adapter_get_device(adapter, &data->dst, data->dst_type); + if (!device) + goto failed; + + path = device_get_path(device); + + g_dbus_send_reply(dbus_conn, data->msg, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + /* continue with service discovery and connection */ + btd_device_set_temporary(device, false); + device_update_last_seen(device, data->dst_type); + + if (data->dst_type != BDADDR_BREDR){ + g_io_channel_set_close_on_unref(io, FALSE); + device_attach_att(device, io); + } + + device_discover_services(device); + device_wait_for_svc_complete(device, device_browse_cb, NULL); + + g_io_channel_unref(io); + dbus_message_unref(data->msg); + free(data); + return; + +failed: + g_dbus_send_error(dbus_conn, data->msg, "org.bluez.Failed", NULL); + g_io_channel_unref(io); + dbus_message_unref(data->msg); + free(data); +} + +static void device_connect(struct btd_adapter *adapter, const bdaddr_t *dst, + uint8_t dst_type, DBusMessage *msg) +{ + struct device_connect_data *data; + GIOChannel *io; + + data = new0(struct device_connect_data, 1); + data->adapter = adapter; + bacpy(&data->dst, dst); + data->dst_type = dst_type; + data->msg = dbus_message_ref(msg); + + if (dst_type == BDADDR_BREDR) + io = bt_io_connect(device_connect_cb, data, NULL, NULL, + BT_IO_OPT_SOURCE_BDADDR, &adapter->bdaddr, + BT_IO_OPT_SOURCE_TYPE, BDADDR_BREDR, + BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_DEST_TYPE, BDADDR_BREDR, + BT_IO_OPT_PSM, SDP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + else + io = bt_io_connect(device_connect_cb, data, NULL, NULL, + BT_IO_OPT_SOURCE_BDADDR, &adapter->bdaddr, + BT_IO_OPT_SOURCE_TYPE, adapter->bdaddr_type, + BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_DEST_TYPE, dst_type, + BT_IO_OPT_CID, ATT_CID, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + + if (!io) { + g_dbus_send_message(dbus_conn, + btd_error_failed(msg, "Connect failed")); + dbus_message_unref(data->msg); + free(data); + } +} + +static DBusMessage *connect_device(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct btd_adapter *adapter = user_data; + DBusMessageIter iter, subiter, dictiter, value; + uint8_t addr_type = BDADDR_BREDR; + bdaddr_t addr = *BDADDR_ANY; + + DBG("sender %s", dbus_message_get_sender(msg)); + + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + return btd_error_not_ready(msg); + + dbus_message_iter_init(msg, &iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) + return btd_error_invalid_args(msg); + + dbus_message_iter_recurse(&iter, &subiter); + while (true) { + int type = dbus_message_iter_get_arg_type(&subiter); + char *key; + char *str; + + if (type == DBUS_TYPE_INVALID) + break; + + dbus_message_iter_recurse(&subiter, &dictiter); + + dbus_message_iter_get_basic(&dictiter, &key); + if (!dbus_message_iter_next(&dictiter)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&dictiter) != + DBUS_TYPE_VARIANT) + return btd_error_invalid_args(msg); + + dbus_message_iter_recurse(&dictiter, &value); + + if (!strcmp(key, "Address")) { + if (dbus_message_iter_get_arg_type(&value) != + DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&value, &str); + + if (str2ba(str, &addr) < 0 ) + return btd_error_invalid_args(msg); + } else if (!strcmp(key, "AddressType")) { + if (dbus_message_iter_get_arg_type(&value) != + DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&value, &str); + + + if (!strcmp(str, "public")) + addr_type = BDADDR_LE_PUBLIC; + else if (!strcmp(str, "random")) + addr_type = BDADDR_LE_RANDOM; + else + return btd_error_invalid_args(msg); + } else { + return btd_error_invalid_args(msg); + } + + dbus_message_iter_next(&subiter); + } + + if (!bacmp(&addr, BDADDR_ANY)) + return btd_error_invalid_args(msg); + + if (btd_adapter_find_device(adapter, &addr, addr_type)) + return btd_error_already_exists(msg); + + device_connect(adapter, &addr, addr_type, msg); + return NULL; +} + +static const GDBusMethodTable adapter_methods[] = { + { GDBUS_ASYNC_METHOD("StartDiscovery", NULL, NULL, start_discovery) }, + { GDBUS_METHOD("SetDiscoveryFilter", + GDBUS_ARGS({ "properties", "a{sv}" }), NULL, + set_discovery_filter) }, + { GDBUS_ASYNC_METHOD("StopDiscovery", NULL, NULL, stop_discovery) }, + { GDBUS_ASYNC_METHOD("RemoveDevice", + GDBUS_ARGS({ "device", "o" }), NULL, remove_device) }, + { GDBUS_METHOD("GetDiscoveryFilters", NULL, + GDBUS_ARGS({ "filters", "as" }), + get_discovery_filters) }, + { GDBUS_EXPERIMENTAL_ASYNC_METHOD("ConnectDevice", + GDBUS_ARGS({ "properties", "a{sv}" }), NULL, + connect_device) }, + { } +}; + +static const GDBusPropertyTable adapter_properties[] = { + { "Address", "s", property_get_address }, + { "AddressType", "s", property_get_address_type }, + { "Name", "s", property_get_name }, + { "Alias", "s", property_get_alias, property_set_alias }, + { "Class", "u", property_get_class }, + { "Powered", "b", property_get_powered, property_set_powered }, + { "Discoverable", "b", property_get_discoverable, + property_set_discoverable }, + { "DiscoverableTimeout", "u", property_get_discoverable_timeout, + property_set_discoverable_timeout }, + { "Pairable", "b", property_get_pairable, property_set_pairable }, + { "PairableTimeout", "u", property_get_pairable_timeout, + property_set_pairable_timeout }, + { "Discovering", "b", property_get_discovering }, + { "UUIDs", "as", property_get_uuids }, + { "Modalias", "s", property_get_modalias, NULL, + property_exists_modalias }, + { } +}; + +static int str2buf(const char *str, uint8_t *buf, size_t blen) +{ + int i, dlen; + + if (str == NULL) + return -EINVAL; + + memset(buf, 0, blen); + + dlen = MIN((strlen(str) / 2), blen); + + for (i = 0; i < dlen; i++) + sscanf(str + (i * 2), "%02hhX", &buf[i]); + + return 0; +} + +static struct link_key_info *get_key_info(GKeyFile *key_file, const char *peer) +{ + struct link_key_info *info = NULL; + char *str; + + str = g_key_file_get_string(key_file, "LinkKey", "Key", NULL); + if (!str || strlen(str) < 32) + goto failed; + + info = g_new0(struct link_key_info, 1); + + str2ba(peer, &info->bdaddr); + + if (!strncmp(str, "0x", 2)) + str2buf(&str[2], info->key, sizeof(info->key)); + else + str2buf(&str[0], info->key, sizeof(info->key)); + + info->type = g_key_file_get_integer(key_file, "LinkKey", "Type", NULL); + info->pin_len = g_key_file_get_integer(key_file, "LinkKey", "PINLength", + NULL); + +failed: + g_free(str); + + return info; +} + +static struct smp_ltk_info *get_ltk(GKeyFile *key_file, const char *peer, + uint8_t peer_type, const char *group) +{ + struct smp_ltk_info *ltk = NULL; + GError *gerr = NULL; + bool master; + char *key; + char *rand = NULL; + + key = g_key_file_get_string(key_file, group, "Key", NULL); + if (!key || strlen(key) < 32) + goto failed; + + rand = g_key_file_get_string(key_file, group, "Rand", NULL); + if (!rand) + goto failed; + + ltk = g_new0(struct smp_ltk_info, 1); + + /* Default to assuming a master key */ + ltk->master = true; + + str2ba(peer, <k->bdaddr); + ltk->bdaddr_type = peer_type; + + /* + * Long term keys should respond to an identity address which can + * either be a public address or a random static address. Keys + * stored for resolvable random and unresolvable random addresses + * are ignored. + * + * This is an extra sanity check for older kernel versions or older + * daemons that might have been instructed to store long term keys + * for these temporary addresses. + */ + if (ltk->bdaddr_type == BDADDR_LE_RANDOM && + (ltk->bdaddr.b[5] & 0xc0) != 0xc0) { + g_free(ltk); + ltk = NULL; + goto failed; + } + + if (!strncmp(key, "0x", 2)) + str2buf(&key[2], ltk->val, sizeof(ltk->val)); + else + str2buf(&key[0], ltk->val, sizeof(ltk->val)); + + if (!strncmp(rand, "0x", 2)) { + uint64_t rand_le; + str2buf(&rand[2], (uint8_t *) &rand_le, sizeof(rand_le)); + ltk->rand = le64_to_cpu(rand_le); + } else { + sscanf(rand, "%" PRIu64, <k->rand); + } + + ltk->authenticated = g_key_file_get_integer(key_file, group, + "Authenticated", NULL); + ltk->enc_size = g_key_file_get_integer(key_file, group, "EncSize", + NULL); + ltk->ediv = g_key_file_get_integer(key_file, group, "EDiv", NULL); + + master = g_key_file_get_boolean(key_file, group, "Master", &gerr); + if (gerr) + g_error_free(gerr); + else + ltk->master = master; + +failed: + g_free(key); + g_free(rand); + + return ltk; +} + +static struct smp_ltk_info *get_ltk_info(GKeyFile *key_file, const char *peer, + uint8_t bdaddr_type) +{ + DBG("%s", peer); + + return get_ltk(key_file, peer, bdaddr_type, "LongTermKey"); +} + +static struct smp_ltk_info *get_slave_ltk_info(GKeyFile *key_file, + const char *peer, + uint8_t bdaddr_type) +{ + struct smp_ltk_info *ltk; + + DBG("%s", peer); + + ltk = get_ltk(key_file, peer, bdaddr_type, "SlaveLongTermKey"); + if (ltk) + ltk->master = false; + + return ltk; +} + +static struct irk_info *get_irk_info(GKeyFile *key_file, const char *peer, + uint8_t bdaddr_type) +{ + struct irk_info *irk = NULL; + char *str; + + str = g_key_file_get_string(key_file, "IdentityResolvingKey", "Key", NULL); + if (!str || strlen(str) < 32) + goto failed; + + irk = g_new0(struct irk_info, 1); + + str2ba(peer, &irk->bdaddr); + irk->bdaddr_type = bdaddr_type; + + if (!strncmp(str, "0x", 2)) + str2buf(&str[2], irk->val, sizeof(irk->val)); + else + str2buf(&str[0], irk->val, sizeof(irk->val)); + +failed: + g_free(str); + + return irk; +} + +static struct conn_param *get_conn_param(GKeyFile *key_file, const char *peer, + uint8_t bdaddr_type) +{ + struct conn_param *param; + + if (!g_key_file_has_group(key_file, "ConnectionParameters")) + return NULL; + + param = g_new0(struct conn_param, 1); + + param->min_interval = g_key_file_get_integer(key_file, + "ConnectionParameters", + "MinInterval", NULL); + param->max_interval = g_key_file_get_integer(key_file, + "ConnectionParameters", + "MaxInterval", NULL); + param->latency = g_key_file_get_integer(key_file, + "ConnectionParameters", + "Latency", NULL); + param->timeout = g_key_file_get_integer(key_file, + "ConnectionParameters", + "Timeout", NULL); + str2ba(peer, ¶m->bdaddr); + param->bdaddr_type = bdaddr_type; + + return param; +} + +static int generate_and_write_irk(uint8_t *irk, GKeyFile *key_file, + const char *filename) +{ + struct bt_crypto *crypto; + char str_irk_out[33]; + gsize length = 0; + char *str; + int i; + + crypto = bt_crypto_new(); + if (!crypto) { + error("Failed to open crypto"); + return -1; + } + + if (!bt_crypto_random_bytes(crypto, irk, 16)) { + error("Failed to generate IRK"); + bt_crypto_unref(crypto); + return -1; + } + + bt_crypto_unref(crypto); + + for (i = 0; i < 16; i++) + sprintf(str_irk_out + (i * 2), "%02x", irk[i]); + + str_irk_out[32] = '\0'; + info("Generated IRK successfully"); + + g_key_file_set_string(key_file, "General", "IdentityResolvingKey", + str_irk_out); + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, str, length, NULL); + g_free(str); + DBG("Generated IRK written to file"); + return 0; +} + +static int load_irk(struct btd_adapter *adapter, uint8_t *irk) +{ + char filename[PATH_MAX]; + GKeyFile *key_file; + char *str_irk; + int ret; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/identity", + btd_adapter_get_storage_dir(adapter)); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + str_irk = g_key_file_get_string(key_file, "General", + "IdentityResolvingKey", NULL); + if (!str_irk) { + info("No IRK stored"); + ret = generate_and_write_irk(irk, key_file, filename); + g_key_file_free(key_file); + return ret; + } + + g_key_file_free(key_file); + + if (strlen(str_irk) != 32 || str2buf(str_irk, irk, 16)) { + /* TODO re-create new IRK here? */ + error("Invalid IRK format, disabling privacy"); + g_free(str_irk); + return -1; + } + + g_free(str_irk); + DBG("Successfully read IRK from file"); + return 0; +} + +static void set_privacy_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, "Failed to set privacy: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + DBG("Successfuly set privacy for index %u", adapter->dev_id); +} + +static int set_privacy(struct btd_adapter *adapter, uint8_t privacy) +{ + struct mgmt_cp_set_privacy cp; + + memset(&cp, 0, sizeof(cp)); + + if (privacy) { + uint8_t irk[16]; + + if (load_irk(adapter, irk) == 0) { + cp.privacy = privacy; + memcpy(cp.irk, irk, 16); + } + } + + DBG("sending set privacy command for index %u", adapter->dev_id); + DBG("setting privacy mode 0x%02x for index %u", cp.privacy, + adapter->dev_id); + + if (mgmt_send(adapter->mgmt, MGMT_OP_SET_PRIVACY, + adapter->dev_id, sizeof(cp), &cp, + set_privacy_complete, adapter, NULL) > 0) + return 0; + + btd_error(adapter->dev_id, "Failed to set privacy for index %u", + adapter->dev_id); + + return -1; +} + +static void load_link_keys_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to load link keys for hci%u: %s (0x%02x)", + adapter->dev_id, mgmt_errstr(status), status); + return; + } + + DBG("link keys loaded for hci%u", adapter->dev_id); +} + +static void load_link_keys(struct btd_adapter *adapter, GSList *keys, + bool debug_keys) +{ + struct mgmt_cp_load_link_keys *cp; + struct mgmt_link_key_info *key; + size_t key_count, cp_size; + unsigned int id; + GSList *l; + + /* + * If the controller does not support BR/EDR operation, + * there is no point in trying to load the link keys into + * the kernel. + * + * This is an optimization for Low Energy only controllers. + */ + if (!(adapter->supported_settings & MGMT_SETTING_BREDR)) + return; + + key_count = g_slist_length(keys); + + DBG("hci%u keys %zu debug_keys %d", adapter->dev_id, key_count, + debug_keys); + + cp_size = sizeof(*cp) + (key_count * sizeof(*key)); + + cp = g_try_malloc0(cp_size); + if (cp == NULL) { + btd_error(adapter->dev_id, "No memory for link keys for hci%u", + adapter->dev_id); + return; + } + + /* + * Even if the list of stored keys is empty, it is important to + * load an empty list into the kernel. That way it is ensured + * that no old keys from a previous daemon are present. + * + * In addition it is also the only way to toggle the different + * behavior for debug keys. + */ + cp->debug_keys = debug_keys; + cp->key_count = htobs(key_count); + + for (l = keys, key = cp->keys; l != NULL; l = g_slist_next(l), key++) { + struct link_key_info *info = l->data; + + bacpy(&key->addr.bdaddr, &info->bdaddr); + key->addr.type = BDADDR_BREDR; + key->type = info->type; + memcpy(key->val, info->key, 16); + key->pin_len = info->pin_len; + } + + id = mgmt_send(adapter->mgmt, MGMT_OP_LOAD_LINK_KEYS, + adapter->dev_id, cp_size, cp, + load_link_keys_complete, adapter, NULL); + + g_free(cp); + + if (id == 0) + btd_error(adapter->dev_id, "Failed to load link keys for hci%u", + adapter->dev_id); +} + +static gboolean load_ltks_timeout(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + + btd_error(adapter->dev_id, "Loading LTKs timed out for hci%u", + adapter->dev_id); + + adapter->load_ltks_timeout = 0; + + mgmt_cancel(adapter->mgmt, adapter->load_ltks_id); + adapter->load_ltks_id = 0; + + return FALSE; +} + +static void load_ltks_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to load LTKs for hci%u: %s (0x%02x)", + adapter->dev_id, mgmt_errstr(status), status); + } + + adapter->load_ltks_id = 0; + + g_source_remove(adapter->load_ltks_timeout); + adapter->load_ltks_timeout = 0; + + DBG("LTKs loaded for hci%u", adapter->dev_id); +} + +static void load_ltks(struct btd_adapter *adapter, GSList *keys) +{ + struct mgmt_cp_load_long_term_keys *cp; + struct mgmt_ltk_info *key; + size_t key_count, cp_size; + GSList *l; + + /* + * If the controller does not support Low Energy operation, + * there is no point in trying to load the long term keys + * into the kernel. + * + * While there is no harm in loading keys into the kernel, + * this is an optimization to avoid a confusing warning + * message when the loading of the keys timed out due to + * a kernel bug (see comment below). + */ + if (!(adapter->supported_settings & MGMT_SETTING_LE)) + return; + + key_count = g_slist_length(keys); + + DBG("hci%u keys %zu", adapter->dev_id, key_count); + + cp_size = sizeof(*cp) + (key_count * sizeof(*key)); + + cp = g_try_malloc0(cp_size); + if (cp == NULL) { + btd_error(adapter->dev_id, "No memory for LTKs for hci%u", + adapter->dev_id); + return; + } + + /* + * Even if the list of stored keys is empty, it is important to + * load an empty list into the kernel. That way it is ensured + * that no old keys from a previous daemon are present. + */ + cp->key_count = htobs(key_count); + + for (l = keys, key = cp->keys; l != NULL; l = g_slist_next(l), key++) { + struct smp_ltk_info *info = l->data; + + bacpy(&key->addr.bdaddr, &info->bdaddr); + key->addr.type = info->bdaddr_type; + memcpy(key->val, info->val, sizeof(info->val)); + key->rand = cpu_to_le64(info->rand); + key->ediv = cpu_to_le16(info->ediv); + key->type = info->authenticated; + key->master = info->master; + key->enc_size = info->enc_size; + } + + adapter->load_ltks_id = mgmt_send(adapter->mgmt, + MGMT_OP_LOAD_LONG_TERM_KEYS, + adapter->dev_id, cp_size, cp, + load_ltks_complete, adapter, NULL); + + g_free(cp); + + if (adapter->load_ltks_id == 0) { + btd_error(adapter->dev_id, "Failed to load LTKs for hci%u", + adapter->dev_id); + return; + } + + /* + * This timeout handling is needed since the kernel is stupid + * and forgets to send a command complete response. However in + * case of failures it does send a command status. + */ + adapter->load_ltks_timeout = g_timeout_add_seconds(2, + load_ltks_timeout, adapter); +} + +static void load_irks_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status == MGMT_STATUS_UNKNOWN_COMMAND) { + btd_info(adapter->dev_id, + "Load IRKs failed: Kernel doesn't support LE Privacy"); + return; + } + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to load IRKs for hci%u: %s (0x%02x)", + adapter->dev_id, mgmt_errstr(status), status); + return; + } + + DBG("IRKs loaded for hci%u", adapter->dev_id); +} + +static void load_irks(struct btd_adapter *adapter, GSList *irks) +{ + struct mgmt_cp_load_irks *cp; + struct mgmt_irk_info *irk; + size_t irk_count, cp_size; + unsigned int id; + GSList *l; + + /* + * If the controller does not support LE Privacy operation, + * there is no support for loading identity resolving keys + * into the kernel. + */ + if (!(adapter->supported_settings & MGMT_SETTING_PRIVACY)) + return; + + irk_count = g_slist_length(irks); + + DBG("hci%u irks %zu", adapter->dev_id, irk_count); + + cp_size = sizeof(*cp) + (irk_count * sizeof(*irk)); + + cp = g_try_malloc0(cp_size); + if (cp == NULL) { + btd_error(adapter->dev_id, "No memory for IRKs for hci%u", + adapter->dev_id); + return; + } + + /* + * Even if the list of stored keys is empty, it is important to + * load an empty list into the kernel. That way we tell the + * kernel that we are able to handle New IRK events. + */ + cp->irk_count = htobs(irk_count); + + for (l = irks, irk = cp->irks; l != NULL; l = g_slist_next(l), irk++) { + struct irk_info *info = l->data; + + bacpy(&irk->addr.bdaddr, &info->bdaddr); + irk->addr.type = info->bdaddr_type; + memcpy(irk->val, info->val, sizeof(irk->val)); + } + + id = mgmt_send(adapter->mgmt, MGMT_OP_LOAD_IRKS, adapter->dev_id, + cp_size, cp, load_irks_complete, adapter, NULL); + + g_free(cp); + + if (id == 0) + btd_error(adapter->dev_id, "Failed to IRKs for hci%u", + adapter->dev_id); +} + +static void load_conn_params_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "hci%u Load Connection Parameters failed: %s (0x%02x)", + adapter->dev_id, mgmt_errstr(status), status); + return; + } + + DBG("Connection Parameters loaded for hci%u", adapter->dev_id); +} + +static void load_conn_params(struct btd_adapter *adapter, GSList *params) +{ + struct mgmt_cp_load_conn_param *cp; + struct mgmt_conn_param *param; + size_t param_count, cp_size; + unsigned int id; + GSList *l; + + /* + * If the controller does not support Low Energy operation, + * there is no point in trying to load the connection + * parameters into the kernel. + */ + if (!(adapter->supported_settings & MGMT_SETTING_LE)) + return; + + param_count = g_slist_length(params); + + DBG("hci%u conn params %zu", adapter->dev_id, param_count); + + cp_size = sizeof(*cp) + (param_count * sizeof(*param)); + + cp = g_try_malloc0(cp_size); + if (cp == NULL) { + btd_error(adapter->dev_id, + "Failed to allocate memory for connection parameters"); + return; + } + + cp->param_count = htobs(param_count); + + for (l = params, param = cp->params; l; l = g_slist_next(l), param++) { + struct conn_param *info = l->data; + + bacpy(¶m->addr.bdaddr, &info->bdaddr); + param->addr.type = info->bdaddr_type; + param->min_interval = htobs(info->min_interval); + param->max_interval = htobs(info->max_interval); + param->latency = htobs(info->latency); + param->timeout = htobs(info->timeout); + } + + id = mgmt_send(adapter->mgmt, MGMT_OP_LOAD_CONN_PARAM, adapter->dev_id, + cp_size, cp, load_conn_params_complete, adapter, NULL); + + g_free(cp); + + if (id == 0) + btd_error(adapter->dev_id, "Load connection parameters failed"); +} + +static uint8_t get_le_addr_type(GKeyFile *keyfile) +{ + uint8_t addr_type; + char *type; + + type = g_key_file_get_string(keyfile, "General", "AddressType", NULL); + if (!type) + return BDADDR_LE_PUBLIC; + + if (g_str_equal(type, "public")) + addr_type = BDADDR_LE_PUBLIC; + else if (g_str_equal(type, "static")) + addr_type = BDADDR_LE_RANDOM; + else + addr_type = BDADDR_LE_PUBLIC; + + g_free(type); + + return addr_type; +} + +static void probe_devices(void *user_data) +{ + struct btd_device *device = user_data; + + device_probe_profiles(device, btd_device_get_uuids(device)); +} + +static void load_devices(struct btd_adapter *adapter) +{ + char dirname[PATH_MAX]; + GSList *keys = NULL; + GSList *ltks = NULL; + GSList *irks = NULL; + GSList *params = NULL; + GSList *added_devices = NULL; + DIR *dir; + struct dirent *entry; + + snprintf(dirname, PATH_MAX, STORAGEDIR "/%s", + btd_adapter_get_storage_dir(adapter)); + + dir = opendir(dirname); + if (!dir) { + btd_error(adapter->dev_id, + "Unable to open adapter storage directory: %s", + dirname); + return; + } + + while ((entry = readdir(dir)) != NULL) { + struct btd_device *device; + char filename[PATH_MAX]; + GKeyFile *key_file; + struct link_key_info *key_info; + struct smp_ltk_info *ltk_info; + struct smp_ltk_info *slave_ltk_info; + GSList *list; + struct irk_info *irk_info; + struct conn_param *param; + uint8_t bdaddr_type; + + if (entry->d_type == DT_UNKNOWN) + entry->d_type = util_get_dt(dirname, entry->d_name); + + if (entry->d_type != DT_DIR || bachk(entry->d_name) < 0) + continue; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + btd_adapter_get_storage_dir(adapter), + entry->d_name); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + key_info = get_key_info(key_file, entry->d_name); + if (key_info) + keys = g_slist_append(keys, key_info); + + bdaddr_type = get_le_addr_type(key_file); + + ltk_info = get_ltk_info(key_file, entry->d_name, bdaddr_type); + if (ltk_info) + ltks = g_slist_append(ltks, ltk_info); + + slave_ltk_info = get_slave_ltk_info(key_file, entry->d_name, + bdaddr_type); + if (slave_ltk_info) + ltks = g_slist_append(ltks, slave_ltk_info); + + irk_info = get_irk_info(key_file, entry->d_name, bdaddr_type); + if (irk_info) + irks = g_slist_append(irks, irk_info); + + param = get_conn_param(key_file, entry->d_name, bdaddr_type); + if (param) + params = g_slist_append(params, param); + + list = g_slist_find_custom(adapter->devices, entry->d_name, + device_address_cmp); + if (list) { + device = list->data; + goto device_exist; + } + + device = device_create_from_storage(adapter, entry->d_name, + key_file); + if (!device) + goto free; + + btd_device_set_temporary(device, false); + adapter->devices = g_slist_append(adapter->devices, device); + + /* TODO: register services from pre-loaded list of primaries */ + + added_devices = g_slist_append(added_devices, device); + +device_exist: + if (key_info) { + device_set_paired(device, BDADDR_BREDR); + device_set_bonded(device, BDADDR_BREDR); + } + + if (ltk_info || slave_ltk_info) { + device_set_paired(device, bdaddr_type); + device_set_bonded(device, bdaddr_type); + + if (ltk_info) + device_set_ltk_enc_size(device, + ltk_info->enc_size); + else if (slave_ltk_info) + device_set_ltk_enc_size(device, + slave_ltk_info->enc_size); + } + +free: + g_key_file_free(key_file); + } + + closedir(dir); + + load_link_keys(adapter, keys, main_opts.debug_keys); + g_slist_free_full(keys, g_free); + + load_ltks(adapter, ltks); + g_slist_free_full(ltks, g_free); + load_irks(adapter, irks); + g_slist_free_full(irks, g_free); + load_conn_params(adapter, params); + g_slist_free_full(params, g_free); + + g_slist_free_full(added_devices, probe_devices); +} + +int btd_adapter_block_address(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type) +{ + struct mgmt_cp_block_device cp; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("hci%u %s", adapter->dev_id, addr); + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = bdaddr_type; + + if (mgmt_send(adapter->mgmt, MGMT_OP_BLOCK_DEVICE, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) + return 0; + + return -EIO; +} + +int btd_adapter_unblock_address(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type) +{ + struct mgmt_cp_unblock_device cp; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("hci%u %s", adapter->dev_id, addr); + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = bdaddr_type; + + if (mgmt_send(adapter->mgmt, MGMT_OP_UNBLOCK_DEVICE, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) + return 0; + + return -EIO; +} + +static int clear_blocked(struct btd_adapter *adapter) +{ + return btd_adapter_unblock_address(adapter, BDADDR_ANY, 0); +} + +static void probe_driver(struct btd_adapter *adapter, gpointer user_data) +{ + struct btd_adapter_driver *driver = user_data; + int err; + + if (driver->probe == NULL) + return; + + err = driver->probe(adapter); + if (err < 0) { + btd_error(adapter->dev_id, "%s: %s (%d)", driver->name, + strerror(-err), -err); + return; + } + + adapter->drivers = g_slist_prepend(adapter->drivers, driver); +} + +static void load_drivers(struct btd_adapter *adapter) +{ + GSList *l; + + for (l = adapter_drivers; l; l = l->next) + probe_driver(adapter, l->data); +} + +static void probe_profile(struct btd_profile *profile, void *data) +{ + struct btd_adapter *adapter = data; + int err; + + if (profile->adapter_probe == NULL) + return; + + err = profile->adapter_probe(profile, adapter); + if (err < 0) { + btd_error(adapter->dev_id, "%s: %s (%d)", profile->name, + strerror(-err), -err); + return; + } + + adapter->profiles = g_slist_prepend(adapter->profiles, profile); +} + +void adapter_add_profile(struct btd_adapter *adapter, gpointer p) +{ + struct btd_profile *profile = p; + + if (!adapter->initialized) + return; + + probe_profile(profile, adapter); + + g_slist_foreach(adapter->devices, device_probe_profile, profile); +} + +void adapter_remove_profile(struct btd_adapter *adapter, gpointer p) +{ + struct btd_profile *profile = p; + + if (!adapter->initialized) + return; + + if (profile->device_remove) + g_slist_foreach(adapter->devices, device_remove_profile, p); + + adapter->profiles = g_slist_remove(adapter->profiles, profile); + + if (profile->adapter_remove) + profile->adapter_remove(profile, adapter); +} + +static void adapter_add_connection(struct btd_adapter *adapter, + struct btd_device *device, + uint8_t bdaddr_type) +{ + device_add_connection(device, bdaddr_type); + + if (g_slist_find(adapter->connections, device)) { + btd_error(adapter->dev_id, + "Device is already marked as connected"); + return; + } + + adapter->connections = g_slist_append(adapter->connections, device); +} + +static void get_connections_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const struct mgmt_rp_get_connections *rp = param; + uint16_t i, conn_count; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to get connections: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, + "Wrong size of get connections response"); + return; + } + + conn_count = btohs(rp->conn_count); + + DBG("Connection count: %d", conn_count); + + if (conn_count * sizeof(struct mgmt_addr_info) + + sizeof(*rp) != length) { + btd_error(adapter->dev_id, + "Incorrect packet size for get connections response"); + return; + } + + for (i = 0; i < conn_count; i++) { + const struct mgmt_addr_info *addr = &rp->addr[i]; + struct btd_device *device; + char address[18]; + + ba2str(&addr->bdaddr, address); + DBG("Adding existing connection to %s", address); + + device = btd_adapter_get_device(adapter, &addr->bdaddr, + addr->type); + if (device) + adapter_add_connection(adapter, device, addr->type); + } +} + +static void load_connections(struct btd_adapter *adapter) +{ + DBG("sending get connections command for index %u", adapter->dev_id); + + if (mgmt_send(adapter->mgmt, MGMT_OP_GET_CONNECTIONS, + adapter->dev_id, 0, NULL, + get_connections_complete, adapter, NULL) > 0) + return; + + btd_error(adapter->dev_id, "Failed to get connections for index %u", + adapter->dev_id); +} + +bool btd_adapter_get_pairable(struct btd_adapter *adapter) +{ + if (adapter->current_settings & MGMT_SETTING_BONDABLE) + return true; + + return false; +} + +bool btd_adapter_get_powered(struct btd_adapter *adapter) +{ + if (adapter->current_settings & MGMT_SETTING_POWERED) + return true; + + return false; +} + +bool btd_adapter_get_connectable(struct btd_adapter *adapter) +{ + if (adapter->current_settings & MGMT_SETTING_CONNECTABLE) + return true; + + return false; +} + +bool btd_adapter_get_discoverable(struct btd_adapter *adapter) +{ + if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) + return true; + + return false; +} + +bool btd_adapter_get_bredr(struct btd_adapter *adapter) +{ + if (adapter->current_settings & MGMT_SETTING_BREDR) + return true; + + return false; +} + +struct btd_gatt_database *btd_adapter_get_database(struct btd_adapter *adapter) +{ + if (!adapter) + return NULL; + + return adapter->database; +} + +uint32_t btd_adapter_get_class(struct btd_adapter *adapter) +{ + return adapter->dev_class; +} + +const char *btd_adapter_get_name(struct btd_adapter *adapter) +{ + if (adapter->stored_alias) + return adapter->stored_alias; + + if (adapter->system_name) + return adapter->system_name; + + return NULL; +} + +int adapter_connect_list_add(struct btd_adapter *adapter, + struct btd_device *device) +{ + /* + * If the adapter->connect_le device is getting added back to + * the connect list it probably means that the connect attempt + * failed and hence we should clear this pointer + */ + if (device == adapter->connect_le) + adapter->connect_le = NULL; + + /* + * If kernel background scanning is supported then the + * adapter_auto_connect_add() function is used to maintain what to + * connect. + */ + if (kernel_conn_control) + return 0; + + if (g_slist_find(adapter->connect_list, device)) { + DBG("ignoring already added device %s", + device_get_path(device)); + goto done; + } + + if (!(adapter->supported_settings & MGMT_SETTING_LE)) { + btd_error(adapter->dev_id, + "Can't add %s to non-LE capable adapter connect list", + device_get_path(device)); + return -ENOTSUP; + } + + adapter->connect_list = g_slist_append(adapter->connect_list, device); + DBG("%s added to %s's connect_list", device_get_path(device), + adapter->system_name); + +done: + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + return 0; + + trigger_passive_scanning(adapter); + + return 0; +} + +void adapter_connect_list_remove(struct btd_adapter *adapter, + struct btd_device *device) +{ + /* + * If the adapter->connect_le device is being removed from the + * connect list it means the connection was successful and hence + * the pointer should be cleared + */ + if (device == adapter->connect_le) + adapter->connect_le = NULL; + + if (kernel_conn_control) + return; + + if (!g_slist_find(adapter->connect_list, device)) { + DBG("device %s is not on the list, ignoring", + device_get_path(device)); + return; + } + + adapter->connect_list = g_slist_remove(adapter->connect_list, device); + DBG("%s removed from %s's connect_list", device_get_path(device), + adapter->system_name); + + if (!adapter->connect_list) { + stop_passive_scanning(adapter); + return; + } + + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + return; + + trigger_passive_scanning(adapter); +} + +static void add_whitelist_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_add_device *rp = param; + struct btd_adapter *adapter = user_data; + struct btd_device *dev; + char addr[18]; + + if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, + "Too small Add Device complete event"); + return; + } + + ba2str(&rp->addr.bdaddr, addr); + + dev = btd_adapter_find_device(adapter, &rp->addr.bdaddr, + rp->addr.type); + if (!dev) { + btd_error(adapter->dev_id, + "Add Device complete for unknown device %s", addr); + return; + } + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to add device %s: %s (0x%02x)", + addr, mgmt_errstr(status), status); + return; + } + + DBG("%s added to kernel whitelist", addr); +} + +void adapter_whitelist_add(struct btd_adapter *adapter, struct btd_device *dev) +{ + struct mgmt_cp_add_device cp; + + if (!kernel_conn_control) + return; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, device_get_address(dev)); + cp.addr.type = BDADDR_BREDR; + cp.action = 0x01; + + mgmt_send(adapter->mgmt, MGMT_OP_ADD_DEVICE, + adapter->dev_id, sizeof(cp), &cp, + add_whitelist_complete, adapter, NULL); +} + +static void remove_whitelist_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_remove_device *rp = param; + char addr[18]; + + if (length < sizeof(*rp)) { + error("Too small Remove Device complete event"); + return; + } + + ba2str(&rp->addr.bdaddr, addr); + + if (status != MGMT_STATUS_SUCCESS) { + error("Failed to remove device %s: %s (0x%02x)", + addr, mgmt_errstr(status), status); + return; + } + + DBG("%s removed from kernel whitelist", addr); +} + +void adapter_whitelist_remove(struct btd_adapter *adapter, struct btd_device *dev) +{ + struct mgmt_cp_remove_device cp; + + if (!kernel_conn_control) + return; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, device_get_address(dev)); + cp.addr.type = BDADDR_BREDR; + + mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_DEVICE, + adapter->dev_id, sizeof(cp), &cp, + remove_whitelist_complete, adapter, NULL); +} + +static void add_device_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_add_device *rp = param; + struct btd_adapter *adapter = user_data; + struct btd_device *dev; + char addr[18]; + + if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, + "Too small Add Device complete event"); + return; + } + + ba2str(&rp->addr.bdaddr, addr); + + dev = btd_adapter_find_device(adapter, &rp->addr.bdaddr, + rp->addr.type); + if (!dev) { + btd_error(adapter->dev_id, + "Add Device complete for unknown device %s", addr); + return; + } + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to add device %s (%u): %s (0x%02x)", + addr, rp->addr.type, mgmt_errstr(status), status); + adapter->connect_list = g_slist_remove(adapter->connect_list, + dev); + return; + } + + DBG("%s (%u) added to kernel connect list", addr, rp->addr.type); +} + +void adapter_auto_connect_add(struct btd_adapter *adapter, + struct btd_device *device) +{ + struct mgmt_cp_add_device cp; + const bdaddr_t *bdaddr; + uint8_t bdaddr_type; + unsigned int id; + + if (!kernel_conn_control) + return; + + if (g_slist_find(adapter->connect_list, device)) { + DBG("ignoring already added device %s", + device_get_path(device)); + return; + } + + bdaddr = device_get_address(device); + bdaddr_type = btd_device_get_bdaddr_type(device); + + if (bdaddr_type == BDADDR_BREDR) { + DBG("auto-connection feature is not avaiable for BR/EDR"); + return; + } + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = bdaddr_type; + cp.action = 0x02; + + id = mgmt_send(adapter->mgmt, MGMT_OP_ADD_DEVICE, + adapter->dev_id, sizeof(cp), &cp, add_device_complete, + adapter, NULL); + if (id == 0) + return; + + adapter->connect_list = g_slist_append(adapter->connect_list, device); +} + +static void remove_device_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_remove_device *rp = param; + char addr[18]; + + if (length < sizeof(*rp)) { + error("Too small Remove Device complete event"); + return; + } + + ba2str(&rp->addr.bdaddr, addr); + + if (status != MGMT_STATUS_SUCCESS) { + error("Failed to remove device %s (%u): %s (0x%02x)", + addr, rp->addr.type, mgmt_errstr(status), status); + return; + } + + DBG("%s (%u) removed from kernel connect list", addr, rp->addr.type); +} + +void adapter_auto_connect_remove(struct btd_adapter *adapter, + struct btd_device *device) +{ + struct mgmt_cp_remove_device cp; + const bdaddr_t *bdaddr; + uint8_t bdaddr_type; + unsigned int id; + + if (!kernel_conn_control) + return; + + if (!g_slist_find(adapter->connect_list, device)) { + DBG("ignoring not added device %s", device_get_path(device)); + return; + } + + bdaddr = device_get_address(device); + bdaddr_type = btd_device_get_bdaddr_type(device); + + if (bdaddr_type == BDADDR_BREDR) { + DBG("auto-connection feature is not avaiable for BR/EDR"); + return; + } + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = bdaddr_type; + + id = mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_DEVICE, + adapter->dev_id, sizeof(cp), &cp, + remove_device_complete, adapter, NULL); + if (id == 0) + return; + + adapter->connect_list = g_slist_remove(adapter->connect_list, device); +} + +static void adapter_start(struct btd_adapter *adapter) +{ + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Powered"); + + DBG("adapter %s has been enabled", adapter->path); + + trigger_passive_scanning(adapter); +} + +static void reply_pending_requests(struct btd_adapter *adapter) +{ + GSList *l; + + if (!adapter) + return; + + /* pending bonding */ + for (l = adapter->devices; l; l = l->next) { + struct btd_device *device = l->data; + + if (device_is_bonding(device, NULL)) + device_bonding_failed(device, + HCI_OE_USER_ENDED_CONNECTION); + } +} + +static void remove_driver(gpointer data, gpointer user_data) +{ + struct btd_adapter_driver *driver = data; + struct btd_adapter *adapter = user_data; + + if (driver->remove) + driver->remove(adapter); +} + +static void remove_profile(gpointer data, gpointer user_data) +{ + struct btd_profile *profile = data; + struct btd_adapter *adapter = user_data; + + if (profile->adapter_remove) + profile->adapter_remove(profile, adapter); +} + +static void unload_drivers(struct btd_adapter *adapter) +{ + g_slist_foreach(adapter->drivers, remove_driver, adapter); + g_slist_free(adapter->drivers); + adapter->drivers = NULL; + + g_slist_foreach(adapter->profiles, remove_profile, adapter); + g_slist_free(adapter->profiles); + adapter->profiles = NULL; +} + +static void free_service_auth(gpointer data, gpointer user_data) +{ + struct service_auth *auth = data; + + g_free(auth); +} + +static void adapter_free(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + + DBG("%p", adapter); + + if (adapter->pairable_timeout_id > 0) { + g_source_remove(adapter->pairable_timeout_id); + adapter->pairable_timeout_id = 0; + } + + if (adapter->passive_scan_timeout > 0) { + g_source_remove(adapter->passive_scan_timeout); + adapter->passive_scan_timeout = 0; + } + + if (adapter->load_ltks_timeout > 0) + g_source_remove(adapter->load_ltks_timeout); + + if (adapter->confirm_name_timeout > 0) + g_source_remove(adapter->confirm_name_timeout); + + if (adapter->pair_device_timeout > 0) + g_source_remove(adapter->pair_device_timeout); + + if (adapter->auth_idle_id) + g_source_remove(adapter->auth_idle_id); + + g_queue_foreach(adapter->auths, free_service_auth, NULL); + g_queue_free(adapter->auths); + + /* + * Unregister all handlers for this specific index since + * the adapter bound to them is no longer valid. + * + * This also avoids having multiple instances of the same + * handler in case indexes got removed and re-added. + */ + mgmt_unregister_index(adapter->mgmt, adapter->dev_id); + + /* + * Cancel all pending commands for this specific index + * since the adapter bound to them is no longer valid. + */ + mgmt_cancel_index(adapter->mgmt, adapter->dev_id); + + mgmt_unref(adapter->mgmt); + + sdp_list_free(adapter->services, NULL); + + g_slist_free(adapter->connections); + + g_free(adapter->path); + g_free(adapter->name); + g_free(adapter->short_name); + g_free(adapter->system_name); + g_free(adapter->stored_alias); + g_free(adapter->current_alias); + free(adapter->modalias); + g_free(adapter); +} + +struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter) +{ + __sync_fetch_and_add(&adapter->ref_count, 1); + + return adapter; +} + +void btd_adapter_unref(struct btd_adapter *adapter) +{ + if (__sync_sub_and_fetch(&adapter->ref_count, 1)) + return; + + if (!adapter->path) { + DBG("Freeing adapter %u", adapter->dev_id); + + adapter_free(adapter); + return; + } + + DBG("Freeing adapter %s", adapter->path); + + g_dbus_unregister_interface(dbus_conn, adapter->path, + ADAPTER_INTERFACE); +} + +static void convert_names_entry(char *key, char *value, void *user_data) +{ + char *address = user_data; + char *str = key; + char filename[PATH_MAX]; + GKeyFile *key_file; + char *data; + gsize length = 0; + + if (strchr(key, '#')) + str[17] = '\0'; + + if (bachk(str) != 0) + return; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", address, str); + create_file(filename, S_IRUSR | S_IWUSR); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + g_key_file_set_string(key_file, "General", "Name", value); + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, data, length, NULL); + g_free(data); + + g_key_file_free(key_file); +} + +struct device_converter { + char *address; + void (*cb)(GKeyFile *key_file, void *value); + gboolean force; +}; + +static void set_device_type(GKeyFile *key_file, char type) +{ + char *techno; + char *addr_type = NULL; + char *str; + + switch (type) { + case BDADDR_BREDR: + techno = "BR/EDR"; + break; + case BDADDR_LE_PUBLIC: + techno = "LE"; + addr_type = "public"; + break; + case BDADDR_LE_RANDOM: + techno = "LE"; + addr_type = "static"; + break; + default: + return; + } + + str = g_key_file_get_string(key_file, "General", + "SupportedTechnologies", NULL); + if (!str) + g_key_file_set_string(key_file, "General", + "SupportedTechnologies", techno); + else if (!strstr(str, techno)) + g_key_file_set_string(key_file, "General", + "SupportedTechnologies", "BR/EDR;LE"); + + g_free(str); + + if (addr_type) + g_key_file_set_string(key_file, "General", "AddressType", + addr_type); +} + +static void convert_aliases_entry(GKeyFile *key_file, void *value) +{ + g_key_file_set_string(key_file, "General", "Alias", value); +} + +static void convert_trusts_entry(GKeyFile *key_file, void *value) +{ + g_key_file_set_boolean(key_file, "General", "Trusted", TRUE); +} + +static void convert_classes_entry(GKeyFile *key_file, void *value) +{ + g_key_file_set_string(key_file, "General", "Class", value); +} + +static void convert_blocked_entry(GKeyFile *key_file, void *value) +{ + g_key_file_set_boolean(key_file, "General", "Blocked", TRUE); +} + +static void convert_did_entry(GKeyFile *key_file, void *value) +{ + char *vendor_str, *product_str, *version_str; + uint16_t val; + + vendor_str = strchr(value, ' '); + if (!vendor_str) + return; + + *(vendor_str++) = 0; + + if (g_str_equal(value, "FFFF")) + return; + + product_str = strchr(vendor_str, ' '); + if (!product_str) + return; + + *(product_str++) = 0; + + version_str = strchr(product_str, ' '); + if (!version_str) + return; + + *(version_str++) = 0; + + val = (uint16_t) strtol(value, NULL, 16); + g_key_file_set_integer(key_file, "DeviceID", "Source", val); + + val = (uint16_t) strtol(vendor_str, NULL, 16); + g_key_file_set_integer(key_file, "DeviceID", "Vendor", val); + + val = (uint16_t) strtol(product_str, NULL, 16); + g_key_file_set_integer(key_file, "DeviceID", "Product", val); + + val = (uint16_t) strtol(version_str, NULL, 16); + g_key_file_set_integer(key_file, "DeviceID", "Version", val); +} + +static void convert_linkkey_entry(GKeyFile *key_file, void *value) +{ + char *type_str, *length_str, *str; + int val; + + type_str = strchr(value, ' '); + if (!type_str) + return; + + *(type_str++) = 0; + + length_str = strchr(type_str, ' '); + if (!length_str) + return; + + *(length_str++) = 0; + + str = g_strconcat("0x", value, NULL); + g_key_file_set_string(key_file, "LinkKey", "Key", str); + g_free(str); + + val = strtol(type_str, NULL, 16); + g_key_file_set_integer(key_file, "LinkKey", "Type", val); + + val = strtol(length_str, NULL, 16); + g_key_file_set_integer(key_file, "LinkKey", "PINLength", val); +} + +static void convert_ltk_entry(GKeyFile *key_file, void *value) +{ + char *auth_str, *rand_str, *str; + int i, ret; + unsigned char auth, master, enc_size; + unsigned short ediv; + + auth_str = strchr(value, ' '); + if (!auth_str) + return; + + *(auth_str++) = 0; + + for (i = 0, rand_str = auth_str; i < 4; i++) { + rand_str = strchr(rand_str, ' '); + if (!rand_str || rand_str[1] == '\0') + return; + + rand_str++; + } + + ret = sscanf(auth_str, " %hhd %hhd %hhd %hd", &auth, &master, + &enc_size, &ediv); + if (ret < 4) + return; + + str = g_strconcat("0x", value, NULL); + g_key_file_set_string(key_file, "LongTermKey", "Key", str); + g_free(str); + + g_key_file_set_integer(key_file, "LongTermKey", "Authenticated", auth); + g_key_file_set_integer(key_file, "LongTermKey", "Master", master); + g_key_file_set_integer(key_file, "LongTermKey", "EncSize", enc_size); + g_key_file_set_integer(key_file, "LongTermKey", "EDiv", ediv); + + str = g_strconcat("0x", rand_str, NULL); + g_key_file_set_string(key_file, "LongTermKey", "Rand", str); + g_free(str); +} + +static void convert_profiles_entry(GKeyFile *key_file, void *value) +{ + g_strdelimit(value, " ", ';'); + g_key_file_set_string(key_file, "General", "Services", value); +} + +static void convert_appearances_entry(GKeyFile *key_file, void *value) +{ + g_key_file_set_string(key_file, "General", "Appearance", value); +} + +static void convert_entry(char *key, char *value, void *user_data) +{ + struct device_converter *converter = user_data; + char type = BDADDR_BREDR; + char filename[PATH_MAX]; + GKeyFile *key_file; + char *data; + gsize length = 0; + + if (strchr(key, '#')) { + key[17] = '\0'; + type = key[18] - '0'; + } + + if (bachk(key) != 0) + return; + + if (converter->force == FALSE) { + struct stat st; + int err; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", + converter->address, key); + + err = stat(filename, &st); + if (err || !S_ISDIR(st.st_mode)) + return; + } + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + converter->address, key); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + set_device_type(key_file, type); + + converter->cb(key_file, value); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + + g_free(data); + + g_key_file_free(key_file); +} + +static void convert_file(char *file, char *address, + void (*cb)(GKeyFile *key_file, void *value), + gboolean force) +{ + char filename[PATH_MAX]; + struct device_converter converter; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", address, file); + + converter.address = address; + converter.cb = cb; + converter.force = force; + + textfile_foreach(filename, convert_entry, &converter); +} + +static gboolean record_has_uuid(const sdp_record_t *rec, + const char *profile_uuid) +{ + sdp_list_t *pat; + + for (pat = rec->pattern; pat != NULL; pat = pat->next) { + char *uuid; + int ret; + + uuid = bt_uuid2string(pat->data); + if (!uuid) + continue; + + ret = strcasecmp(uuid, profile_uuid); + + free(uuid); + + if (ret == 0) + return TRUE; + } + + return FALSE; +} + +static void store_attribute_uuid(GKeyFile *key_file, uint16_t start, + uint16_t end, char *att_uuid, + uuid_t uuid) +{ + char handle[6], uuid_str[33]; + int i; + + switch (uuid.type) { + case SDP_UUID16: + sprintf(uuid_str, "%4.4X", uuid.value.uuid16); + break; + case SDP_UUID32: + sprintf(uuid_str, "%8.8X", uuid.value.uuid32); + break; + case SDP_UUID128: + for (i = 0; i < 16; i++) + sprintf(uuid_str + (i * 2), "%2.2X", + uuid.value.uuid128.data[i]); + break; + default: + uuid_str[0] = '\0'; + } + + sprintf(handle, "%hu", start); + g_key_file_set_string(key_file, handle, "UUID", att_uuid); + g_key_file_set_string(key_file, handle, "Value", uuid_str); + g_key_file_set_integer(key_file, handle, "EndGroupHandle", end); +} + +static void store_sdp_record(char *local, char *peer, int handle, char *value) +{ + char filename[PATH_MAX]; + GKeyFile *key_file; + char handle_str[11]; + char *data; + gsize length = 0; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", local, peer); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + sprintf(handle_str, "0x%8.8X", handle); + g_key_file_set_string(key_file, "ServiceRecords", handle_str, value); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + + g_free(data); + + g_key_file_free(key_file); +} + +static void convert_sdp_entry(char *key, char *value, void *user_data) +{ + char *src_addr = user_data; + char dst_addr[18]; + char type = BDADDR_BREDR; + int handle, ret; + char filename[PATH_MAX]; + GKeyFile *key_file; + struct stat st; + sdp_record_t *rec; + uuid_t uuid; + char *att_uuid, *prim_uuid; + uint16_t start = 0, end = 0, psm = 0; + int err; + char *data; + gsize length = 0; + + ret = sscanf(key, "%17s#%hhu#%08X", dst_addr, &type, &handle); + if (ret < 3) { + ret = sscanf(key, "%17s#%08X", dst_addr, &handle); + if (ret < 2) + return; + } + + if (bachk(dst_addr) != 0) + return; + + /* Check if the device directory has been created as records should + * only be converted for known devices */ + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", src_addr, dst_addr); + + err = stat(filename, &st); + if (err || !S_ISDIR(st.st_mode)) + return; + + /* store device records in cache */ + store_sdp_record(src_addr, dst_addr, handle, value); + + /* Retrieve device record and check if there is an + * attribute entry in it */ + sdp_uuid16_create(&uuid, ATT_UUID); + att_uuid = bt_uuid2string(&uuid); + + sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + prim_uuid = bt_uuid2string(&uuid); + + rec = record_from_string(value); + + if (record_has_uuid(rec, att_uuid)) + goto failed; + + /* TODO: Do this through btd_gatt_database */ + if (!gatt_parse_record(rec, &uuid, &psm, &start, &end)) + goto failed; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/attributes", src_addr, + dst_addr); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + store_attribute_uuid(key_file, start, end, prim_uuid, uuid); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + + g_free(data); + g_key_file_free(key_file); + +failed: + sdp_record_free(rec); + free(prim_uuid); + free(att_uuid); +} + +static void convert_primaries_entry(char *key, char *value, void *user_data) +{ + char *address = user_data; + int device_type = -1; + uuid_t uuid; + char **services, **service, *prim_uuid; + char filename[PATH_MAX]; + GKeyFile *key_file; + int ret; + uint16_t start, end; + char uuid_str[MAX_LEN_UUID_STR + 1]; + char *data; + gsize length = 0; + + if (strchr(key, '#')) { + key[17] = '\0'; + device_type = key[18] - '0'; + } + + if (bachk(key) != 0) + return; + + services = g_strsplit(value, " ", 0); + if (services == NULL) + return; + + sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + prim_uuid = bt_uuid2string(&uuid); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/attributes", address, + key); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + for (service = services; *service; service++) { + ret = sscanf(*service, "%04hX#%04hX#%s", &start, &end, + uuid_str); + if (ret < 3) + continue; + + bt_string2uuid(&uuid, uuid_str); + sdp_uuid128_to_uuid(&uuid); + + store_attribute_uuid(key_file, start, end, prim_uuid, uuid); + } + + g_strfreev(services); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length == 0) + goto end; + + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + + if (device_type < 0) + goto end; + + g_free(data); + g_key_file_free(key_file); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", address, key); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + set_device_type(key_file, device_type); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + +end: + g_free(data); + free(prim_uuid); + g_key_file_free(key_file); +} + +static void convert_ccc_entry(char *key, char *value, void *user_data) +{ + char *src_addr = user_data; + char dst_addr[18]; + char type = BDADDR_BREDR; + uint16_t handle; + int ret, err; + char filename[PATH_MAX]; + GKeyFile *key_file; + struct stat st; + char group[6]; + char *data; + gsize length = 0; + + ret = sscanf(key, "%17s#%hhu#%04hX", dst_addr, &type, &handle); + if (ret < 3) + return; + + if (bachk(dst_addr) != 0) + return; + + /* Check if the device directory has been created as records should + * only be converted for known devices */ + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", src_addr, dst_addr); + + err = stat(filename, &st); + if (err || !S_ISDIR(st.st_mode)) + return; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/ccc", src_addr, + dst_addr); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + sprintf(group, "%hu", handle); + g_key_file_set_string(key_file, group, "Value", value); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + + g_free(data); + g_key_file_free(key_file); +} + +static void convert_gatt_entry(char *key, char *value, void *user_data) +{ + char *src_addr = user_data; + char dst_addr[18]; + char type = BDADDR_BREDR; + uint16_t handle; + int ret, err; + char filename[PATH_MAX]; + GKeyFile *key_file; + struct stat st; + char group[6]; + char *data; + gsize length = 0; + + ret = sscanf(key, "%17s#%hhu#%04hX", dst_addr, &type, &handle); + if (ret < 3) + return; + + if (bachk(dst_addr) != 0) + return; + + /* Check if the device directory has been created as records should + * only be converted for known devices */ + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", src_addr, dst_addr); + + err = stat(filename, &st); + if (err || !S_ISDIR(st.st_mode)) + return; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/gatt", src_addr, + dst_addr); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + sprintf(group, "%hu", handle); + g_key_file_set_string(key_file, group, "Value", value); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + + g_free(data); + g_key_file_free(key_file); +} + +static void convert_proximity_entry(char *key, char *value, void *user_data) +{ + char *src_addr = user_data; + char *alert; + char filename[PATH_MAX]; + GKeyFile *key_file; + struct stat st; + int err; + char *data; + gsize length = 0; + + if (!strchr(key, '#')) + return; + + key[17] = '\0'; + alert = &key[18]; + + if (bachk(key) != 0) + return; + + /* Check if the device directory has been created as records should + * only be converted for known devices */ + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", src_addr, key); + + err = stat(filename, &st); + if (err || !S_ISDIR(st.st_mode)) + return; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/proximity", src_addr, + key); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + g_key_file_set_string(key_file, alert, "Level", value); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + + g_free(data); + g_key_file_free(key_file); +} + +static void convert_device_storage(struct btd_adapter *adapter) +{ + char filename[PATH_MAX]; + char address[18]; + + ba2str(&adapter->bdaddr, address); + + /* Convert device's name cache */ + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/names", address); + textfile_foreach(filename, convert_names_entry, address); + + /* Convert aliases */ + convert_file("aliases", address, convert_aliases_entry, TRUE); + + /* Convert trusts */ + convert_file("trusts", address, convert_trusts_entry, TRUE); + + /* Convert blocked */ + convert_file("blocked", address, convert_blocked_entry, TRUE); + + /* Convert profiles */ + convert_file("profiles", address, convert_profiles_entry, TRUE); + + /* Convert primaries */ + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/primaries", address); + textfile_foreach(filename, convert_primaries_entry, address); + + /* Convert linkkeys */ + convert_file("linkkeys", address, convert_linkkey_entry, TRUE); + + /* Convert longtermkeys */ + convert_file("longtermkeys", address, convert_ltk_entry, TRUE); + + /* Convert classes */ + convert_file("classes", address, convert_classes_entry, FALSE); + + /* Convert device ids */ + convert_file("did", address, convert_did_entry, FALSE); + + /* Convert sdp */ + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/sdp", address); + textfile_foreach(filename, convert_sdp_entry, address); + + /* Convert ccc */ + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/ccc", address); + textfile_foreach(filename, convert_ccc_entry, address); + + /* Convert appearances */ + convert_file("appearances", address, convert_appearances_entry, FALSE); + + /* Convert gatt */ + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/gatt", address); + textfile_foreach(filename, convert_gatt_entry, address); + + /* Convert proximity */ + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/proximity", address); + textfile_foreach(filename, convert_proximity_entry, address); +} + +static void convert_config(struct btd_adapter *adapter, const char *filename, + GKeyFile *key_file) +{ + char address[18]; + char str[MAX_NAME_LENGTH + 1]; + char config_path[PATH_MAX]; + int timeout; + uint8_t mode; + char *data; + gsize length = 0; + + ba2str(&adapter->bdaddr, address); + snprintf(config_path, PATH_MAX, STORAGEDIR "/%s/config", address); + + if (read_pairable_timeout(address, &timeout) == 0) + g_key_file_set_integer(key_file, "General", + "PairableTimeout", timeout); + + if (read_discoverable_timeout(address, &timeout) == 0) + g_key_file_set_integer(key_file, "General", + "DiscoverableTimeout", timeout); + + if (read_on_mode(address, str, sizeof(str)) == 0) { + mode = get_mode(str); + g_key_file_set_boolean(key_file, "General", "Discoverable", + mode == MODE_DISCOVERABLE); + } + + if (read_local_name(&adapter->bdaddr, str) == 0) + g_key_file_set_string(key_file, "General", "Alias", str); + + create_file(filename, S_IRUSR | S_IWUSR); + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, data, length, NULL); + g_free(data); +} + +static void fix_storage(struct btd_adapter *adapter) +{ + char filename[PATH_MAX]; + char address[18]; + char *converted; + + ba2str(&adapter->bdaddr, address); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/config", address); + converted = textfile_get(filename, "converted"); + if (!converted) + return; + + free(converted); + + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/names", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/aliases", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/trusts", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/blocked", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/profiles", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/primaries", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/linkkeys", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/longtermkeys", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/classes", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/did", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/sdp", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/ccc", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/appearances", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/gatt", address); + textfile_del(filename, "converted"); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/proximity", address); + textfile_del(filename, "converted"); +} + +static void load_config(struct btd_adapter *adapter) +{ + GKeyFile *key_file; + char filename[PATH_MAX]; + struct stat st; + GError *gerr = NULL; + + key_file = g_key_file_new(); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/settings", + btd_adapter_get_storage_dir(adapter)); + + if (stat(filename, &st) < 0) { + convert_config(adapter, filename, key_file); + convert_device_storage(adapter); + } + + g_key_file_load_from_file(key_file, filename, 0, NULL); + + /* Get alias */ + adapter->stored_alias = g_key_file_get_string(key_file, "General", + "Alias", NULL); + if (!adapter->stored_alias) { + /* fallback */ + adapter->stored_alias = g_key_file_get_string(key_file, + "General", "Name", NULL); + } + + /* Get pairable timeout */ + adapter->pairable_timeout = g_key_file_get_integer(key_file, "General", + "PairableTimeout", &gerr); + if (gerr) { + adapter->pairable_timeout = main_opts.pairto; + g_error_free(gerr); + gerr = NULL; + } + + /* Get discoverable mode */ + adapter->stored_discoverable = g_key_file_get_boolean(key_file, + "General", "Discoverable", &gerr); + if (gerr) { + adapter->stored_discoverable = false; + g_error_free(gerr); + gerr = NULL; + } + + /* Get discoverable timeout */ + adapter->discoverable_timeout = g_key_file_get_integer(key_file, + "General", "DiscoverableTimeout", &gerr); + if (gerr) { + adapter->discoverable_timeout = main_opts.discovto; + g_error_free(gerr); + gerr = NULL; + } + + g_key_file_free(key_file); +} + +static struct btd_adapter *btd_adapter_new(uint16_t index) +{ + struct btd_adapter *adapter; + + adapter = g_try_new0(struct btd_adapter, 1); + if (!adapter) + return NULL; + + adapter->dev_id = index; + adapter->mgmt = mgmt_ref(mgmt_master); + adapter->pincode_requested = false; + + /* + * Setup default configuration values. These are either adapter + * defaults or from a system wide configuration file. + * + * Some value might be overwritten later on by adapter specific + * configuration. This is to make sure that sane defaults are + * always present. + */ + adapter->system_name = g_strdup(main_opts.name); + adapter->major_class = (main_opts.class & 0x001f00) >> 8; + adapter->minor_class = (main_opts.class & 0x0000fc) >> 2; + adapter->modalias = bt_modalias(main_opts.did_source, + main_opts.did_vendor, + main_opts.did_product, + main_opts.did_version); + adapter->discoverable_timeout = main_opts.discovto; + adapter->pairable_timeout = main_opts.pairto; + + DBG("System name: %s", adapter->system_name); + DBG("Major class: %u", adapter->major_class); + DBG("Minor class: %u", adapter->minor_class); + DBG("Modalias: %s", adapter->modalias); + DBG("Discoverable timeout: %u seconds", adapter->discoverable_timeout); + DBG("Pairable timeout: %u seconds", adapter->pairable_timeout); + + adapter->auths = g_queue_new(); + + return btd_adapter_ref(adapter); +} + +static void adapter_remove(struct btd_adapter *adapter) +{ + GSList *l; + struct gatt_db *db; + + DBG("Removing adapter %s", adapter->path); + + g_slist_free(adapter->connect_list); + adapter->connect_list = NULL; + + for (l = adapter->devices; l; l = l->next) + device_remove(l->data, FALSE); + + g_slist_free(adapter->devices); + adapter->devices = NULL; + + discovery_cleanup(adapter); + + unload_drivers(adapter); + + db = btd_gatt_database_get_db(adapter->database); + gatt_db_unregister(db, adapter->db_id); + adapter->db_id = 0; + + btd_gatt_database_destroy(adapter->database); + adapter->database = NULL; + + btd_adv_manager_destroy(adapter->adv_manager); + adapter->adv_manager = NULL; + + g_slist_free(adapter->pin_callbacks); + adapter->pin_callbacks = NULL; + + g_slist_free(adapter->msd_callbacks); + adapter->msd_callbacks = NULL; +} + +const char *adapter_get_path(struct btd_adapter *adapter) +{ + if (!adapter) + return NULL; + + return adapter->path; +} + +const bdaddr_t *btd_adapter_get_address(struct btd_adapter *adapter) +{ + return &adapter->bdaddr; +} + +static gboolean confirm_name_timeout(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + + btd_error(adapter->dev_id, "Confirm name timed out for hci%u", + adapter->dev_id); + + adapter->confirm_name_timeout = 0; + + mgmt_cancel(adapter->mgmt, adapter->confirm_name_id); + adapter->confirm_name_id = 0; + + return FALSE; +} + +static void confirm_name_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to confirm name for hci%u: %s (0x%02x)", + adapter->dev_id, mgmt_errstr(status), status); + } + + adapter->confirm_name_id = 0; + + g_source_remove(adapter->confirm_name_timeout); + adapter->confirm_name_timeout = 0; + + DBG("Confirm name complete for hci%u", adapter->dev_id); +} + +static void confirm_name(struct btd_adapter *adapter, const bdaddr_t *bdaddr, + uint8_t bdaddr_type, bool name_known) +{ + struct mgmt_cp_confirm_name cp; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("hci%d bdaddr %s name_known %u", adapter->dev_id, addr, + name_known); + + /* + * If the kernel does not answer the confirm name command with + * a command complete or command status in time, this might + * race against another device found event that also requires + * to confirm the name. If there is a pending command, just + * cancel it to be safe here. + */ + if (adapter->confirm_name_id > 0) { + btd_warn(adapter->dev_id, + "Found pending confirm name for hci%u", + adapter->dev_id); + mgmt_cancel(adapter->mgmt, adapter->confirm_name_id); + } + + if (adapter->confirm_name_timeout > 0) { + g_source_remove(adapter->confirm_name_timeout); + adapter->confirm_name_timeout = 0; + } + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = bdaddr_type; + cp.name_known = name_known; + + adapter->confirm_name_id = mgmt_reply(adapter->mgmt, + MGMT_OP_CONFIRM_NAME, + adapter->dev_id, sizeof(cp), &cp, + confirm_name_complete, adapter, NULL); + + if (adapter->confirm_name_id == 0) { + btd_error(adapter->dev_id, "Failed to confirm name for hci%u", + adapter->dev_id); + return; + } + + /* + * This timeout handling is needed since the kernel is stupid + * and forgets to send a command complete response. However in + * case of failures it does send a command status. + */ + adapter->confirm_name_timeout = g_timeout_add_seconds(2, + confirm_name_timeout, adapter); +} + +static void adapter_msd_notify(struct btd_adapter *adapter, + struct btd_device *dev, + GSList *msd_list) +{ + GSList *cb_l, *cb_next; + GSList *msd_l, *msd_next; + + for (cb_l = adapter->msd_callbacks; cb_l != NULL; cb_l = cb_next) { + btd_msd_cb_t cb = cb_l->data; + + cb_next = g_slist_next(cb_l); + + for (msd_l = msd_list; msd_l != NULL; msd_l = msd_next) { + const struct eir_msd *msd = msd_l->data; + + msd_next = g_slist_next(msd_l); + + cb(adapter, dev, msd->company, msd->data, + msd->data_len); + } + } +} + +static bool is_filter_match(GSList *discovery_filter, struct eir_data *eir_data, + int8_t rssi) +{ + GSList *l, *m; + bool got_match = false; + + for (l = discovery_filter; l != NULL && got_match != true; + l = g_slist_next(l)) { + struct watch_client *client = l->data; + struct discovery_filter *item = client->discovery_filter; + + /* + * If one of currently running scans is regular scan, then + * return all devices as matches + */ + if (!item) { + got_match = true; + continue; + } + + /* if someone started discovery with empty uuids, he wants all + * devices in given proximity. + */ + if (!item->uuids) + got_match = true; + else { + for (m = item->uuids; m != NULL && got_match != true; + m = g_slist_next(m)) { + /* m->data contains string representation of + * uuid. + */ + if (g_slist_find_custom(eir_data->services, + m->data, + g_strcmp) != NULL) + got_match = true; + } + } + + if (got_match) { + /* we have service match, check proximity */ + if (item->rssi == DISTANCE_VAL_INVALID || + item->rssi <= rssi || + item->pathloss == DISTANCE_VAL_INVALID || + (eir_data->tx_power != 127 && + eir_data->tx_power - rssi <= item->pathloss)) + return true; + + got_match = false; + } + } + + return got_match; +} + +static void filter_duplicate_data(void *data, void *user_data) +{ + struct watch_client *client = data; + bool *duplicate = user_data; + + if (*duplicate || !client->discovery_filter) + return; + + *duplicate = client->discovery_filter->duplicate; +} + +static void update_found_devices(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + uint8_t bdaddr_type, int8_t rssi, + bool confirm, bool legacy, + bool not_connectable, + const uint8_t *data, uint8_t data_len) +{ + struct btd_device *dev; + struct eir_data eir_data; + bool name_known, discoverable; + char addr[18]; + bool duplicate = false; + + memset(&eir_data, 0, sizeof(eir_data)); + eir_parse(&eir_data, data, data_len); + + if (bdaddr_type == BDADDR_BREDR || adapter->filtered_discovery) + discoverable = true; + else + discoverable = eir_data.flags & (EIR_LIM_DISC | EIR_GEN_DISC); + + ba2str(bdaddr, addr); + + dev = btd_adapter_find_device(adapter, bdaddr, bdaddr_type); + if (!dev) { + /* + * If no client has requested discovery or the device is + * not marked as discoverable, then do not create new + * device objects. + */ + if (!adapter->discovery_list || !discoverable) { + eir_data_free(&eir_data); + return; + } + + dev = adapter_create_device(adapter, bdaddr, bdaddr_type); + } + + if (!dev) { + btd_error(adapter->dev_id, + "Unable to create object for found device %s", addr); + eir_data_free(&eir_data); + return; + } + + device_update_last_seen(dev, bdaddr_type); + + /* + * FIXME: We need to check for non-zero flags first because + * older kernels send separate adv_ind and scan_rsp. Newer + * kernels send them merged, so once we know which mgmt version + * supports this we can make the non-zero check conditional. + */ + if (bdaddr_type != BDADDR_BREDR && eir_data.flags && + !(eir_data.flags & EIR_BREDR_UNSUP)) { + device_set_bredr_support(dev); + /* Update last seen for BR/EDR in case its flag is set */ + device_update_last_seen(dev, BDADDR_BREDR); + } + + if (eir_data.name != NULL && eir_data.name_complete) + device_store_cached_name(dev, eir_data.name); + + /* + * Only skip devices that are not connected, are temporary and there + * is no active discovery session ongoing. + */ + if (!btd_device_is_connected(dev) && (device_is_temporary(dev) && + !adapter->discovery_list)) { + eir_data_free(&eir_data); + return; + } + + if (adapter->filtered_discovery && + !is_filter_match(adapter->discovery_list, &eir_data, rssi)) { + eir_data_free(&eir_data); + return; + } + + device_set_legacy(dev, legacy); + + if (adapter->filtered_discovery) + device_set_rssi_with_delta(dev, rssi, 0); + else + device_set_rssi(dev, rssi); + + if (eir_data.tx_power != 127) + device_set_tx_power(dev, eir_data.tx_power); + + if (eir_data.appearance != 0) + device_set_appearance(dev, eir_data.appearance); + + /* Report an unknown name to the kernel even if there is a short name + * known, but still update the name with the known short name. */ + name_known = device_name_known(dev); + + if (eir_data.name && (eir_data.name_complete || !name_known)) + btd_device_device_set_name(dev, eir_data.name); + + if (eir_data.class != 0) + device_set_class(dev, eir_data.class); + + if (eir_data.did_source || eir_data.did_vendor || + eir_data.did_product || eir_data.did_version) + btd_device_set_pnpid(dev, eir_data.did_source, + eir_data.did_vendor, + eir_data.did_product, + eir_data.did_version); + + device_add_eir_uuids(dev, eir_data.services); + + if (adapter->discovery_list) + g_slist_foreach(adapter->discovery_list, filter_duplicate_data, + &duplicate); + + if (eir_data.msd_list) { + device_set_manufacturer_data(dev, eir_data.msd_list, duplicate); + adapter_msd_notify(adapter, dev, eir_data.msd_list); + } + + if (eir_data.sd_list) + device_set_service_data(dev, eir_data.sd_list, duplicate); + + if (eir_data.data_list) + device_set_data(dev, eir_data.data_list, duplicate); + + if (bdaddr_type != BDADDR_BREDR) + device_set_flags(dev, eir_data.flags); + + eir_data_free(&eir_data); + + /* + * Only if at least one client has requested discovery, maintain + * list of found devices and name confirming for legacy devices. + * Otherwise, this is an event from passive discovery and we + * should check if the device needs connecting to. + */ + if (!adapter->discovery_list) + goto connect_le; + + if (g_slist_find(adapter->discovery_found, dev)) + return; + + if (confirm) + confirm_name(adapter, bdaddr, bdaddr_type, name_known); + + adapter->discovery_found = g_slist_prepend(adapter->discovery_found, + dev); + + return; + +connect_le: + /* Ignore non-connectable events */ + if (not_connectable) + return; + + /* + * If we're in the process of stopping passive scanning and + * connecting another (or maybe even the same) LE device just + * ignore this one. + */ + if (adapter->connect_le) + return; + + /* + * If kernel background scan is used then the kernel is + * responsible for connecting. + */ + if (kernel_conn_control) + return; + + /* + * If this is an LE device that's not connected and part of the + * connect_list stop passive scanning so that a connection + * attempt to it can be made + */ + if (bdaddr_type != BDADDR_BREDR && !btd_device_is_connected(dev) && + g_slist_find(adapter->connect_list, dev)) { + adapter->connect_le = dev; + stop_passive_scanning(adapter); + } +} + +static void device_found_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_device_found *ev = param; + struct btd_adapter *adapter = user_data; + const uint8_t *eir; + uint16_t eir_len; + uint32_t flags; + bool confirm_name; + bool legacy; + char addr[18]; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, + "Too short device found event (%u bytes)", length); + return; + } + + eir_len = btohs(ev->eir_len); + if (length != sizeof(*ev) + eir_len) { + btd_error(adapter->dev_id, + "Device found event size mismatch (%u != %zu)", + length, sizeof(*ev) + eir_len); + return; + } + + if (eir_len == 0) + eir = NULL; + else + eir = ev->eir; + + flags = btohl(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); + legacy = (flags & MGMT_DEV_FOUND_LEGACY_PAIRING); + + update_found_devices(adapter, &ev->addr.bdaddr, ev->addr.type, + ev->rssi, confirm_name, legacy, + flags & MGMT_DEV_FOUND_NOT_CONNECTABLE, + eir, eir_len); +} + +struct agent *adapter_get_agent(struct btd_adapter *adapter) +{ + return agent_get(NULL); +} + +static void adapter_remove_connection(struct btd_adapter *adapter, + struct btd_device *device, + uint8_t bdaddr_type) +{ + DBG(""); + + if (!g_slist_find(adapter->connections, device)) { + btd_error(adapter->dev_id, "No matching connection for device"); + return; + } + + device_remove_connection(device, bdaddr_type); + + if (device_is_authenticating(device)) + device_cancel_authentication(device, TRUE); + + /* If another bearer is still connected */ + if (btd_device_is_connected(device)) + return; + + adapter->connections = g_slist_remove(adapter->connections, device); + + if (device_is_temporary(device) && !device_is_retrying(device)) { + const char *path = device_get_path(device); + + DBG("Removing temporary device %s", path); + btd_adapter_remove_device(adapter, device); + } +} + +static void adapter_stop(struct btd_adapter *adapter) +{ + /* check pending requests */ + reply_pending_requests(adapter); + + cancel_passive_scanning(adapter); + + g_slist_free_full(adapter->set_filter_list, discovery_free); + adapter->set_filter_list = NULL; + + g_slist_free_full(adapter->discovery_list, discovery_free); + adapter->discovery_list = NULL; + + discovery_cleanup(adapter); + + adapter->filtered_discovery = false; + adapter->no_scan_restart_delay = false; + g_free(adapter->current_discovery_filter); + adapter->current_discovery_filter = NULL; + + adapter->discovering = false; + + while (adapter->connections) { + struct btd_device *device = adapter->connections->data; + uint8_t addr_type = btd_device_get_bdaddr_type(device); + + adapter_remove_connection(adapter, device, BDADDR_BREDR); + if (addr_type != BDADDR_BREDR) + adapter_remove_connection(adapter, device, addr_type); + } + + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Discovering"); + + if (adapter->dev_class) { + /* the kernel should reset the class of device when powering + * down, but it does not. So force it here ... */ + adapter->dev_class = 0; + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Class"); + } + + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Powered"); + + DBG("adapter %s has been disabled", adapter->path); +} + +int btd_register_adapter_driver(struct btd_adapter_driver *driver) +{ + adapter_drivers = g_slist_append(adapter_drivers, driver); + + if (driver->probe == NULL) + return 0; + + adapter_foreach(probe_driver, driver); + + return 0; +} + +static void unload_driver(struct btd_adapter *adapter, gpointer data) +{ + struct btd_adapter_driver *driver = data; + + if (driver->remove) + driver->remove(adapter); + + adapter->drivers = g_slist_remove(adapter->drivers, data); +} + +void btd_unregister_adapter_driver(struct btd_adapter_driver *driver) +{ + adapter_drivers = g_slist_remove(adapter_drivers, driver); + + adapter_foreach(unload_driver, driver); +} + +static void agent_auth_cb(struct agent *agent, DBusError *derr, + void *user_data) +{ + struct btd_adapter *adapter = user_data; + struct service_auth *auth = g_queue_pop_head(adapter->auths); + + if (!auth) { + DBG("No pending authorization"); + return; + } + + auth->cb(derr, auth->user_data); + + if (auth->agent) + agent_unref(auth->agent); + + g_free(auth); + + /* Stop processing if queue is empty */ + if (g_queue_is_empty(adapter->auths)) { + if (adapter->auth_idle_id > 0) + g_source_remove(adapter->auth_idle_id); + return; + } + + if (adapter->auth_idle_id > 0) + return; + + adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter); +} + +static gboolean process_auth_queue(gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + DBusError err; + + adapter->auth_idle_id = 0; + + dbus_error_init(&err); + dbus_set_error_const(&err, ERROR_INTERFACE ".Rejected", NULL); + + while (!g_queue_is_empty(adapter->auths)) { + struct service_auth *auth = adapter->auths->head->data; + struct btd_device *device = auth->device; + const char *dev_path; + + /* Wait services to be resolved before asking authorization */ + if (auth->svc_id > 0) + return FALSE; + + if (device_is_trusted(device) == TRUE) { + auth->cb(NULL, auth->user_data); + goto next; + } + + /* If agent is set authorization is already ongoing */ + if (auth->agent) + return FALSE; + + auth->agent = agent_get(NULL); + if (auth->agent == NULL) { + btd_warn(adapter->dev_id, + "Authentication attempt without agent"); + auth->cb(&err, auth->user_data); + goto next; + } + + dev_path = device_get_path(device); + + if (agent_authorize_service(auth->agent, dev_path, auth->uuid, + agent_auth_cb, adapter, NULL) < 0) { + auth->cb(&err, auth->user_data); + goto next; + } + + break; + +next: + if (auth->agent) + agent_unref(auth->agent); + + g_free(auth); + + g_queue_pop_head(adapter->auths); + } + + dbus_error_free(&err); + + return FALSE; +} + +static void svc_complete(struct btd_device *dev, int err, void *user_data) +{ + struct service_auth *auth = user_data; + struct btd_adapter *adapter = auth->adapter; + + auth->svc_id = 0; + + if (adapter->auth_idle_id != 0) + return; + + adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter); +} + +static int adapter_authorize(struct btd_adapter *adapter, const bdaddr_t *dst, + const char *uuid, + adapter_authorize_type check_for_connection, + service_auth_cb cb, void *user_data) +{ + struct service_auth *auth; + struct btd_device *device; + static guint id = 0; + + device = btd_adapter_find_device(adapter, dst, BDADDR_BREDR); + if (!device) + return 0; + + if (device_is_disconnecting(device)) { + DBG("Authorization request while disconnecting"); + return 0; + } + + /* Device connected? */ + if (check_for_connection && !g_slist_find(adapter->connections, device)) + btd_error(adapter->dev_id, + "Authorization request for non-connected device!?"); + + auth = g_try_new0(struct service_auth, 1); + if (!auth) + return 0; + + auth->cb = cb; + auth->user_data = user_data; + auth->uuid = uuid; + auth->device = device; + auth->adapter = adapter; + auth->id = ++id; + if (check_for_connection) + auth->svc_id = device_wait_for_svc_complete(device, svc_complete, auth); + else { + if (adapter->auth_idle_id == 0) + adapter->auth_idle_id = g_idle_add(process_auth_queue, adapter); + } + + g_queue_push_tail(adapter->auths, auth); + + return auth->id; +} + +guint btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst, + const char *uuid, service_auth_cb cb, + void *user_data) +{ + struct btd_adapter *adapter; + GSList *l; + + if (bacmp(src, BDADDR_ANY) != 0) { + adapter = adapter_find(src); + if (!adapter) + return 0; + + return adapter_authorize(adapter, dst, uuid, + ADAPTER_AUTHORIZE_CHECK_CONNECTED, cb, user_data); + } + + for (l = adapters; l != NULL; l = g_slist_next(l)) { + guint id; + + adapter = l->data; + + id = adapter_authorize(adapter, dst, uuid, + ADAPTER_AUTHORIZE_CHECK_CONNECTED, cb, user_data); + if (id != 0) + return id; + } + + return 0; +} + +guint btd_request_authorization_cable_configured(const bdaddr_t *src, const bdaddr_t *dst, + const char *uuid, service_auth_cb cb, + void *user_data) +{ + struct btd_adapter *adapter; + + if (bacmp(src, BDADDR_ANY) == 0) + return 0; + + adapter = adapter_find(src); + if (!adapter) + return 0; + + return adapter_authorize(adapter, dst, uuid, + ADAPTER_AUTHORIZE_DISCONNECTED, cb, user_data); +} + +static struct service_auth *find_authorization(guint id) +{ + GSList *l; + GList *l2; + + for (l = adapters; l != NULL; l = g_slist_next(l)) { + struct btd_adapter *adapter = l->data; + + for (l2 = adapter->auths->head; l2 != NULL; l2 = l2->next) { + struct service_auth *auth = l2->data; + + if (auth->id == id) + return auth; + } + } + + return NULL; +} + +int btd_cancel_authorization(guint id) +{ + struct service_auth *auth; + + auth = find_authorization(id); + if (auth == NULL) + return -EPERM; + + if (auth->svc_id > 0) + device_remove_svc_complete_callback(auth->device, + auth->svc_id); + + g_queue_remove(auth->adapter->auths, auth); + + if (auth->agent) { + agent_cancel(auth->agent); + agent_unref(auth->agent); + } + + g_free(auth); + + return 0; +} + +int btd_adapter_restore_powered(struct btd_adapter *adapter) +{ + if (adapter->current_settings & MGMT_SETTING_POWERED) + return 0; + + set_mode(adapter, MGMT_OP_SET_POWERED, 0x01); + + return 0; +} + +void btd_adapter_register_pin_cb(struct btd_adapter *adapter, + btd_adapter_pin_cb_t cb) +{ + adapter->pin_callbacks = g_slist_prepend(adapter->pin_callbacks, cb); +} + +void btd_adapter_unregister_pin_cb(struct btd_adapter *adapter, + btd_adapter_pin_cb_t cb) +{ + adapter->pin_callbacks = g_slist_remove(adapter->pin_callbacks, cb); +} + +void btd_adapter_unregister_msd_cb(struct btd_adapter *adapter, + btd_msd_cb_t cb) +{ + adapter->msd_callbacks = g_slist_remove(adapter->msd_callbacks, cb); +} + +void btd_adapter_register_msd_cb(struct btd_adapter *adapter, + btd_msd_cb_t cb) +{ + adapter->msd_callbacks = g_slist_prepend(adapter->msd_callbacks, cb); +} + +int btd_adapter_set_fast_connectable(struct btd_adapter *adapter, + gboolean enable) +{ + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + return -EINVAL; + + set_mode(adapter, MGMT_OP_SET_FAST_CONNECTABLE, enable ? 0x01 : 0x00); + + return 0; +} + +int btd_adapter_read_clock(struct btd_adapter *adapter, const bdaddr_t *bdaddr, + int which, int timeout, uint32_t *clock, + uint16_t *accuracy) +{ + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + return -EINVAL; + + return -ENOSYS; +} + +int btd_adapter_remove_bonding(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type) +{ + struct mgmt_cp_unpair_device cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = bdaddr_type; + cp.disconnect = 1; + + if (mgmt_send(adapter->mgmt, MGMT_OP_UNPAIR_DEVICE, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) + return 0; + + return -EIO; +} + +static void pincode_reply_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_device *device = user_data; + + /* If the MGMT_OP_PIN_CODE_REPLY command is acknowledged, move the + * starting time to that point. This give a better sense of time + * evaluating the pincode. */ + device_bonding_restart_timer(device); +} + +int btd_adapter_pincode_reply(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + const char *pin, size_t pin_len) +{ + struct btd_device *device; + unsigned int id; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("hci%u addr %s pinlen %zu", adapter->dev_id, addr, pin_len); + + if (pin == NULL) { + struct mgmt_cp_pin_code_neg_reply cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = BDADDR_BREDR; + + id = mgmt_reply(adapter->mgmt, MGMT_OP_PIN_CODE_NEG_REPLY, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL); + } else { + struct mgmt_cp_pin_code_reply cp; + + if (pin_len > 16) + return -EINVAL; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = BDADDR_BREDR; + cp.pin_len = pin_len; + memcpy(cp.pin_code, pin, pin_len); + + /* Since a pincode was requested, update the starting time to + * the point where the pincode is provided. */ + device = btd_adapter_find_device(adapter, bdaddr, BDADDR_BREDR); + device_bonding_restart_timer(device); + + id = mgmt_reply(adapter->mgmt, MGMT_OP_PIN_CODE_REPLY, + adapter->dev_id, sizeof(cp), &cp, + pincode_reply_complete, device, NULL); + } + + if (id == 0) + return -EIO; + + return 0; +} + +int btd_adapter_confirm_reply(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type, + gboolean success) +{ + struct mgmt_cp_user_confirm_reply cp; + uint16_t opcode; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("hci%u addr %s success %d", adapter->dev_id, addr, success); + + if (success) + opcode = MGMT_OP_USER_CONFIRM_REPLY; + else + opcode = MGMT_OP_USER_CONFIRM_NEG_REPLY; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = bdaddr_type; + + if (mgmt_reply(adapter->mgmt, opcode, adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) + return 0; + + return -EIO; +} + +static void user_confirm_request_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_user_confirm_request *ev = param; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + char addr[18]; + int err; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, + "Too small user confirm request event"); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + DBG("hci%u %s confirm_hint %u", adapter->dev_id, addr, + ev->confirm_hint); + device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, + ev->addr.type); + if (!device) { + btd_error(adapter->dev_id, + "Unable to get device object for %s", addr); + return; + } + + err = device_confirm_passkey(device, ev->addr.type, btohl(ev->value), + ev->confirm_hint); + if (err < 0) { + btd_error(adapter->dev_id, + "device_confirm_passkey: %s", strerror(-err)); + btd_adapter_confirm_reply(adapter, &ev->addr.bdaddr, + ev->addr.type, FALSE); + } +} + +int btd_adapter_passkey_reply(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type, + uint32_t passkey) +{ + unsigned int id; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("hci%u addr %s passkey %06u", adapter->dev_id, addr, passkey); + + if (passkey == INVALID_PASSKEY) { + struct mgmt_cp_user_passkey_neg_reply cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = bdaddr_type; + + id = mgmt_reply(adapter->mgmt, MGMT_OP_USER_PASSKEY_NEG_REPLY, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL); + } else { + struct mgmt_cp_user_passkey_reply cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = bdaddr_type; + cp.passkey = htobl(passkey); + + id = mgmt_reply(adapter->mgmt, MGMT_OP_USER_PASSKEY_REPLY, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL); + } + + if (id == 0) + return -EIO; + + return 0; +} + +static void user_passkey_request_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_user_passkey_request *ev = param; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + char addr[18]; + int err; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small passkey request event"); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + DBG("hci%u %s", index, addr); + + device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, + ev->addr.type); + if (!device) { + btd_error(adapter->dev_id, + "Unable to get device object for %s", addr); + return; + } + + err = device_request_passkey(device, ev->addr.type); + if (err < 0) { + btd_error(adapter->dev_id, + "device_request_passkey: %s", strerror(-err)); + btd_adapter_passkey_reply(adapter, &ev->addr.bdaddr, + ev->addr.type, INVALID_PASSKEY); + } +} + +static void user_passkey_notify_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_passkey_notify *ev = param; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + uint32_t passkey; + char addr[18]; + int err; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small passkey notify event"); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + DBG("hci%u %s", index, addr); + + device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, + ev->addr.type); + if (!device) { + btd_error(adapter->dev_id, + "Unable to get device object for %s", addr); + return; + } + + passkey = get_le32(&ev->passkey); + + DBG("passkey %06u entered %u", passkey, ev->entered); + + err = device_notify_passkey(device, ev->addr.type, passkey, + ev->entered); + if (err < 0) + btd_error(adapter->dev_id, + "device_notify_passkey: %s", strerror(-err)); +} + +struct btd_adapter_pin_cb_iter *btd_adapter_pin_cb_iter_new( + struct btd_adapter *adapter) +{ + struct btd_adapter_pin_cb_iter *iter = + g_new0(struct btd_adapter_pin_cb_iter, 1); + + iter->it = adapter->pin_callbacks; + iter->attempt = 1; + + return iter; +} + +void btd_adapter_pin_cb_iter_free(struct btd_adapter_pin_cb_iter *iter) +{ + g_free(iter); +} + +bool btd_adapter_pin_cb_iter_end(struct btd_adapter_pin_cb_iter *iter) +{ + return iter->it == NULL && iter->attempt == 0; +} + +static ssize_t btd_adapter_pin_cb_iter_next( + struct btd_adapter_pin_cb_iter *iter, + struct btd_adapter *adapter, + struct btd_device *device, + char *pin_buf, bool *display) +{ + btd_adapter_pin_cb_t cb; + ssize_t ret; + + while (iter->it != NULL) { + cb = iter->it->data; + ret = cb(adapter, device, pin_buf, display, iter->attempt); + iter->attempt++; + if (ret > 0) + return ret; + iter->attempt = 1; + iter->it = g_slist_next(iter->it); + } + iter->attempt = 0; + + return 0; +} + +static void pin_code_request_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_pin_code_request *ev = param; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + bool display = false; + char pin[17]; + ssize_t pinlen; + char addr[18]; + int err; + struct btd_adapter_pin_cb_iter *iter; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small PIN code request event"); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + + DBG("hci%u %s", adapter->dev_id, addr); + + device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, + ev->addr.type); + if (!device) { + btd_error(adapter->dev_id, + "Unable to get device object for %s", addr); + return; + } + + /* Flag the request of a pincode to allow a bonding retry. */ + adapter->pincode_requested = true; + + memset(pin, 0, sizeof(pin)); + + iter = device_bonding_iter(device); + if (iter == NULL) + pinlen = 0; + else + pinlen = btd_adapter_pin_cb_iter_next(iter, adapter, device, + pin, &display); + + if (pinlen > 0 && (!ev->secure || pinlen == 16)) { + if (display && device_is_bonding(device, NULL)) { + err = device_notify_pincode(device, ev->secure, pin); + if (err < 0) { + btd_error(adapter->dev_id, + "device_notify_pin: %s", + strerror(-err)); + btd_adapter_pincode_reply(adapter, + &ev->addr.bdaddr, + NULL, 0); + } + } else { + btd_adapter_pincode_reply(adapter, &ev->addr.bdaddr, + pin, pinlen); + } + return; + } + + err = device_request_pincode(device, ev->secure); + if (err < 0) { + btd_error(adapter->dev_id, "device_request_pin: %s", + strerror(-err)); + btd_adapter_pincode_reply(adapter, &ev->addr.bdaddr, NULL, 0); + } +} + +int adapter_cancel_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, + uint8_t addr_type) +{ + struct mgmt_addr_info cp; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("hci%u bdaddr %s type %u", adapter->dev_id, addr, addr_type); + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + cp.type = addr_type; + + if (mgmt_reply(adapter->mgmt, MGMT_OP_CANCEL_PAIR_DEVICE, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) + return 0; + + return -EIO; +} + +static void check_oob_bonding_complete(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t status) +{ + if (!adapter->oob_handler || !adapter->oob_handler->bonding_cb) + return; + + if (bacmp(bdaddr, &adapter->oob_handler->remote_addr) != 0) + return; + + adapter->oob_handler->bonding_cb(adapter, bdaddr, status, + adapter->oob_handler->user_data); + + g_free(adapter->oob_handler); + adapter->oob_handler = NULL; +} + +static void bonding_complete(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + uint8_t addr_type, uint8_t status) +{ + struct btd_device *device; + + if (status == 0) + device = btd_adapter_get_device(adapter, bdaddr, addr_type); + else + device = btd_adapter_find_device(adapter, bdaddr, addr_type); + + if (device != NULL) + device_bonding_complete(device, addr_type, status); + + resume_discovery(adapter); + + check_oob_bonding_complete(adapter, bdaddr, status); +} + +/* bonding_attempt_complete() handles the end of a "bonding attempt" checking if + * it should begin a new attempt or complete the bonding. + */ +static void bonding_attempt_complete(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + uint8_t addr_type, uint8_t status) +{ + struct btd_device *device; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("hci%u bdaddr %s type %u status 0x%x", adapter->dev_id, addr, + addr_type, status); + + if (status == 0) + device = btd_adapter_get_device(adapter, bdaddr, addr_type); + else + device = btd_adapter_find_device(adapter, bdaddr, addr_type); + + if (status == MGMT_STATUS_AUTH_FAILED && adapter->pincode_requested) { + /* On faliure, issue a bonding_retry if possible. */ + if (device != NULL) { + if (device_bonding_attempt_retry(device) == 0) + return; + } + } + + /* Ignore disconnects during retry. */ + if (status == MGMT_STATUS_DISCONNECTED && + device && device_is_retrying(device)) + return; + + /* In any other case, finish the bonding. */ + bonding_complete(adapter, bdaddr, addr_type, status); +} + +struct pair_device_data { + struct btd_adapter *adapter; + bdaddr_t bdaddr; + uint8_t addr_type; +}; + +static void free_pair_device_data(void *user_data) +{ + struct pair_device_data *data = user_data; + + g_free(data); +} + +static gboolean pair_device_timeout(gpointer user_data) +{ + struct pair_device_data *data = user_data; + struct btd_adapter *adapter = data->adapter; + + btd_error(adapter->dev_id, "Pair device timed out for hci%u", + adapter->dev_id); + + adapter->pair_device_timeout = 0; + + adapter_cancel_bonding(adapter, &data->bdaddr, data->addr_type); + + return FALSE; +} + +static void pair_device_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_pair_device *rp = param; + struct pair_device_data *data = user_data; + struct btd_adapter *adapter = data->adapter; + + DBG("%s (0x%02x)", mgmt_errstr(status), status); + + adapter->pair_device_id = 0; + + if (adapter->pair_device_timeout > 0) { + g_source_remove(adapter->pair_device_timeout); + adapter->pair_device_timeout = 0; + } + + /* Workaround for a kernel bug + * + * Broken kernels may reply to device pairing command with command + * status instead of command complete event e.g. if adapter was not + * powered. + */ + if (status != MGMT_STATUS_SUCCESS && length < sizeof(*rp)) { + btd_error(adapter->dev_id, "Pair device failed: %s (0x%02x)", + mgmt_errstr(status), status); + + bonding_attempt_complete(adapter, &data->bdaddr, + data->addr_type, status); + return; + } + + if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, "Too small pair device response"); + return; + } + + bonding_attempt_complete(adapter, &rp->addr.bdaddr, rp->addr.type, + status); +} + +int adapter_create_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, + uint8_t addr_type, uint8_t io_cap) +{ + if (adapter->pair_device_id > 0) { + btd_error(adapter->dev_id, + "Unable pair since another pairing is in progress"); + return -EBUSY; + } + + suspend_discovery(adapter); + + return adapter_bonding_attempt(adapter, bdaddr, addr_type, io_cap); +} + +/* Starts a new bonding attempt in a fresh new bonding_req or a retried one. */ +int adapter_bonding_attempt(struct btd_adapter *adapter, const bdaddr_t *bdaddr, + uint8_t addr_type, uint8_t io_cap) +{ + struct mgmt_cp_pair_device cp; + char addr[18]; + struct pair_device_data *data; + unsigned int id; + + ba2str(bdaddr, addr); + DBG("hci%u bdaddr %s type %d io_cap 0x%02x", + adapter->dev_id, addr, addr_type, io_cap); + + /* Reset the pincode_requested flag for a new bonding attempt. */ + adapter->pincode_requested = false; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = addr_type; + cp.io_cap = io_cap; + + data = g_new0(struct pair_device_data, 1); + data->adapter = adapter; + bacpy(&data->bdaddr, bdaddr); + data->addr_type = addr_type; + + id = mgmt_send(adapter->mgmt, MGMT_OP_PAIR_DEVICE, + adapter->dev_id, sizeof(cp), &cp, + pair_device_complete, data, + free_pair_device_data); + + if (id == 0) { + btd_error(adapter->dev_id, "Failed to pair %s for hci%u", + addr, adapter->dev_id); + free_pair_device_data(data); + return -EIO; + } + + adapter->pair_device_id = id; + + /* Due to a bug in the kernel it is possible that a LE pairing + * request never times out. Therefore, add a timer to clean up + * if no response arrives + */ + adapter->pair_device_timeout = g_timeout_add_seconds(BONDING_TIMEOUT, + pair_device_timeout, data); + + return 0; +} + +static void disconnect_notify(struct btd_device *dev, uint8_t reason) +{ + GSList *l; + + for (l = disconnect_list; l; l = g_slist_next(l)) { + btd_disconnect_cb disconnect_cb = l->data; + disconnect_cb(dev, reason); + } +} + +static void dev_disconnected(struct btd_adapter *adapter, + const struct mgmt_addr_info *addr, + uint8_t reason) +{ + struct btd_device *device; + char dst[18]; + + ba2str(&addr->bdaddr, dst); + + DBG("Device %s disconnected, reason %u", dst, reason); + + device = btd_adapter_find_device(adapter, &addr->bdaddr, addr->type); + if (device) { + adapter_remove_connection(adapter, device, addr->type); + disconnect_notify(device, reason); + } + + bonding_attempt_complete(adapter, &addr->bdaddr, addr->type, + MGMT_STATUS_DISCONNECTED); +} + +void btd_add_disconnect_cb(btd_disconnect_cb func) +{ + disconnect_list = g_slist_append(disconnect_list, func); +} + +void btd_remove_disconnect_cb(btd_disconnect_cb func) +{ + disconnect_list = g_slist_remove(disconnect_list, func); +} + +static void disconnect_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_disconnect *rp = param; + struct btd_adapter *adapter = user_data; + + if (status == MGMT_STATUS_NOT_CONNECTED) { + btd_warn(adapter->dev_id, + "Disconnecting failed: already disconnected"); + } else if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to disconnect device: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, + "Too small device disconnect response"); + return; + } + + dev_disconnected(adapter, &rp->addr, MGMT_DEV_DISCONN_LOCAL_HOST); +} + +int btd_adapter_disconnect_device(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + uint8_t bdaddr_type) + +{ + struct mgmt_cp_disconnect cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + cp.addr.type = bdaddr_type; + + if (mgmt_send(adapter->mgmt, MGMT_OP_DISCONNECT, + adapter->dev_id, sizeof(cp), &cp, + disconnect_complete, adapter, NULL) > 0) + return 0; + + return -EIO; +} + +static void auth_failed_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_auth_failed *ev = param; + struct btd_adapter *adapter = user_data; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small auth failed mgmt event"); + return; + } + + bonding_attempt_complete(adapter, &ev->addr.bdaddr, ev->addr.type, + ev->status); +} + +static void store_link_key(struct btd_adapter *adapter, + struct btd_device *device, const uint8_t *key, + uint8_t type, uint8_t pin_length) +{ + char device_addr[18]; + char filename[PATH_MAX]; + GKeyFile *key_file; + gsize length = 0; + char key_str[33]; + char *str; + int i; + + ba2str(device_get_address(device), device_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + btd_adapter_get_storage_dir(adapter), device_addr); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + for (i = 0; i < 16; i++) + sprintf(key_str + (i * 2), "%2.2X", key[i]); + + g_key_file_set_string(key_file, "LinkKey", "Key", key_str); + + g_key_file_set_integer(key_file, "LinkKey", "Type", type); + g_key_file_set_integer(key_file, "LinkKey", "PINLength", pin_length); + + create_file(filename, S_IRUSR | S_IWUSR); + + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, str, length, NULL); + g_free(str); + + g_key_file_free(key_file); +} + +static void new_link_key_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_new_link_key *ev = param; + const struct mgmt_addr_info *addr = &ev->key.addr; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + char dst[18]; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small new link key event"); + return; + } + + ba2str(&addr->bdaddr, dst); + + DBG("hci%u new key for %s type %u pin_len %u store_hint %u", + adapter->dev_id, dst, ev->key.type, ev->key.pin_len, + ev->store_hint); + + if (ev->key.pin_len > 16) { + btd_error(adapter->dev_id, + "Invalid PIN length (%u) in new_key event", + ev->key.pin_len); + return; + } + + device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); + if (!device) { + btd_error(adapter->dev_id, + "Unable to get device object for %s", dst); + return; + } + + if (ev->store_hint) { + const struct mgmt_link_key_info *key = &ev->key; + + store_link_key(adapter, device, key->val, key->type, + key->pin_len); + + device_set_bonded(device, BDADDR_BREDR); + } + + bonding_complete(adapter, &addr->bdaddr, addr->type, 0); +} + +static void store_longtermkey(struct btd_adapter *adapter, const bdaddr_t *peer, + uint8_t bdaddr_type, const unsigned char *key, + uint8_t master, uint8_t authenticated, + uint8_t enc_size, uint16_t ediv, + uint64_t rand) +{ + const char *group = master ? "LongTermKey" : "SlaveLongTermKey"; + char device_addr[18]; + char filename[PATH_MAX]; + GKeyFile *key_file; + char key_str[33]; + gsize length = 0; + char *str; + int i; + + if (master != 0x00 && master != 0x01) { + error("Unsupported LTK type %u", master); + return; + } + + ba2str(peer, device_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + btd_adapter_get_storage_dir(adapter), device_addr); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + /* Old files may contain this so remove it in case it exists */ + g_key_file_remove_key(key_file, "LongTermKey", "Master", NULL); + + for (i = 0; i < 16; i++) + sprintf(key_str + (i * 2), "%2.2X", key[i]); + + g_key_file_set_string(key_file, group, "Key", key_str); + + g_key_file_set_integer(key_file, group, "Authenticated", + authenticated); + g_key_file_set_integer(key_file, group, "EncSize", enc_size); + + g_key_file_set_integer(key_file, group, "EDiv", ediv); + g_key_file_set_uint64(key_file, group, "Rand", rand); + + create_file(filename, S_IRUSR | S_IWUSR); + + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, str, length, NULL); + g_free(str); + + g_key_file_free(key_file); +} + +static void new_long_term_key_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_new_long_term_key *ev = param; + const struct mgmt_addr_info *addr = &ev->key.addr; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + bool persistent; + char dst[18]; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small long term key event"); + return; + } + + ba2str(&addr->bdaddr, dst); + + DBG("hci%u new LTK for %s type %u enc_size %u", + adapter->dev_id, dst, ev->key.type, ev->key.enc_size); + + device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); + if (!device) { + btd_error(adapter->dev_id, + "Unable to get device object for %s", dst); + return; + } + + /* + * Some older kernel versions set store_hint for long term keys + * from resolvable and unresolvable random addresses, but there + * is no point in storing these. Next time around the device + * address will be invalid. + * + * So only for identity addresses (public and static random) use + * the store_hint as an indication if the long term key should + * be persistently stored. + * + */ + if (addr->type == BDADDR_LE_RANDOM && + (addr->bdaddr.b[5] & 0xc0) != 0xc0) + persistent = false; + else + persistent = !!ev->store_hint; + + if (persistent) { + const struct mgmt_ltk_info *key = &ev->key; + uint16_t ediv; + uint64_t rand; + + ediv = le16_to_cpu(key->ediv); + rand = le64_to_cpu(key->rand); + + store_longtermkey(adapter, &key->addr.bdaddr, + key->addr.type, key->val, key->master, + key->type, key->enc_size, ediv, rand); + + device_set_bonded(device, addr->type); + } + + device_set_ltk_enc_size(device, ev->key.enc_size); + + bonding_complete(adapter, &addr->bdaddr, addr->type, 0); +} + +static void store_csrk(struct btd_adapter *adapter, const bdaddr_t *peer, + uint8_t bdaddr_type, const unsigned char *key, + uint32_t counter, uint8_t type) +{ + const char *group; + char device_addr[18]; + char filename[PATH_MAX]; + GKeyFile *key_file; + char key_str[33]; + gsize length = 0; + gboolean auth; + char *str; + int i; + + switch (type) { + case 0x00: + group = "LocalSignatureKey"; + auth = FALSE; + break; + case 0x01: + group = "RemoteSignatureKey"; + auth = FALSE; + break; + case 0x02: + group = "LocalSignatureKey"; + auth = TRUE; + break; + case 0x03: + group = "RemoteSignatureKey"; + auth = TRUE; + break; + default: + warn("Unsupported CSRK type %u", type); + return; + } + + ba2str(peer, device_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + btd_adapter_get_storage_dir(adapter), device_addr); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + for (i = 0; i < 16; i++) + sprintf(key_str + (i * 2), "%2.2X", key[i]); + + g_key_file_set_string(key_file, group, "Key", key_str); + g_key_file_set_integer(key_file, group, "Counter", counter); + g_key_file_set_boolean(key_file, group, "Authenticated", auth); + + create_file(filename, S_IRUSR | S_IWUSR); + + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, str, length, NULL); + g_free(str); + + 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; + const struct mgmt_addr_info *addr = &ev->key.addr; + const struct mgmt_csrk_info *key = &ev->key; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + char dst[18]; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small CSRK event"); + return; + } + + ba2str(&addr->bdaddr, dst); + + DBG("hci%u new CSRK for %s type %u", adapter->dev_id, dst, + ev->key.type); + + device = btd_adapter_get_device(adapter, &addr->bdaddr, addr->type); + if (!device) { + btd_error(adapter->dev_id, + "Unable to get device object for %s", dst); + return; + } + + if (!ev->store_hint) + return; + + store_csrk(adapter, &key->addr.bdaddr, key->addr.type, key->val, 0, + key->type); + + btd_device_set_temporary(device, false); +} + +static void store_irk(struct btd_adapter *adapter, const bdaddr_t *peer, + uint8_t bdaddr_type, const unsigned char *key) +{ + char device_addr[18]; + char filename[PATH_MAX]; + GKeyFile *key_file; + char *store_data; + char str[33]; + size_t length = 0; + int i; + + ba2str(peer, device_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + btd_adapter_get_storage_dir(adapter), device_addr); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + for (i = 0; i < 16; i++) + sprintf(str + (i * 2), "%2.2X", key[i]); + + g_key_file_set_string(key_file, "IdentityResolvingKey", "Key", str); + + create_file(filename, S_IRUSR | S_IWUSR); + + store_data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, store_data, length, NULL); + g_free(store_data); + + g_key_file_free(key_file); +} + +static void new_irk_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_new_irk *ev = param; + const struct mgmt_addr_info *addr = &ev->key.addr; + const struct mgmt_irk_info *irk = &ev->key; + struct btd_adapter *adapter = user_data; + struct btd_device *device, *duplicate; + bool persistent; + char dst[18], rpa[18]; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small New IRK event"); + return; + } + + ba2str(&addr->bdaddr, dst); + ba2str(&ev->rpa, rpa); + + DBG("hci%u new IRK for %s RPA %s", adapter->dev_id, dst, rpa); + + if (bacmp(&ev->rpa, BDADDR_ANY)) { + device = btd_adapter_get_device(adapter, &ev->rpa, + BDADDR_LE_RANDOM); + duplicate = btd_adapter_find_device(adapter, &addr->bdaddr, + addr->type); + if (duplicate == device) + duplicate = NULL; + } else { + device = btd_adapter_get_device(adapter, &addr->bdaddr, + addr->type); + duplicate = NULL; + } + + if (!device) { + btd_error(adapter->dev_id, + "Unable to get device object for %s", dst); + return; + } + + device_update_addr(device, &addr->bdaddr, addr->type); + + if (duplicate) + device_merge_duplicate(device, duplicate); + + persistent = !!ev->store_hint; + if (!persistent) + return; + + store_irk(adapter, &addr->bdaddr, addr->type, irk->val); + + btd_device_set_temporary(device, false); +} + +static void store_conn_param(struct btd_adapter *adapter, const bdaddr_t *peer, + uint8_t bdaddr_type, uint16_t min_interval, + uint16_t max_interval, uint16_t latency, + uint16_t timeout) +{ + char device_addr[18]; + char filename[PATH_MAX]; + GKeyFile *key_file; + char *store_data; + size_t length = 0; + + ba2str(peer, device_addr); + + DBG(""); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + btd_adapter_get_storage_dir(adapter), device_addr); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + g_key_file_set_integer(key_file, "ConnectionParameters", + "MinInterval", min_interval); + g_key_file_set_integer(key_file, "ConnectionParameters", + "MaxInterval", max_interval); + g_key_file_set_integer(key_file, "ConnectionParameters", + "Latency", latency); + g_key_file_set_integer(key_file, "ConnectionParameters", + "Timeout", timeout); + + create_file(filename, S_IRUSR | S_IWUSR); + + store_data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, store_data, length, NULL); + g_free(store_data); + + g_key_file_free(key_file); +} + +static void new_conn_param(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_new_conn_param *ev = param; + struct btd_adapter *adapter = user_data; + uint16_t min, max, latency, timeout; + struct btd_device *dev; + char dst[18]; + + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, + "Too small New Connection Parameter event"); + return; + } + + ba2str(&ev->addr.bdaddr, dst); + + min = btohs(ev->min_interval); + max = btohs(ev->max_interval); + latency = btohs(ev->latency); + timeout = btohs(ev->timeout); + + DBG("hci%u %s (%u) min 0x%04x max 0x%04x latency 0x%04x timeout 0x%04x", + adapter->dev_id, dst, ev->addr.type, min, max, latency, timeout); + + dev = btd_adapter_get_device(adapter, &ev->addr.bdaddr, ev->addr.type); + if (!dev) { + btd_error(adapter->dev_id, + "Unable to get device object for %s", dst); + return; + } + + if (!ev->store_hint) + return; + + store_conn_param(adapter, &ev->addr.bdaddr, ev->addr.type, + ev->min_interval, ev->max_interval, + ev->latency, ev->timeout); +} + +int adapter_set_io_capability(struct btd_adapter *adapter, uint8_t io_cap) +{ + struct mgmt_cp_set_io_capability cp; + + if (!main_opts.pairable) { + if (io_cap == IO_CAPABILITY_INVALID) { + if (adapter->current_settings & MGMT_SETTING_BONDABLE) + set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x00); + + return 0; + } + + if (!(adapter->current_settings & MGMT_SETTING_BONDABLE)) + set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x01); + } else if (io_cap == IO_CAPABILITY_INVALID) + io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; + + memset(&cp, 0, sizeof(cp)); + cp.io_capability = io_cap; + + if (mgmt_send(adapter->mgmt, MGMT_OP_SET_IO_CAPABILITY, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) + return 0; + + return -EIO; +} + +int btd_adapter_add_remote_oob_data(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + uint8_t *hash, uint8_t *randomizer) +{ + struct mgmt_cp_add_remote_oob_data cp; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("hci%d bdaddr %s", adapter->dev_id, addr); + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + memcpy(cp.hash192, hash, 16); + + if (randomizer) + memcpy(cp.rand192, randomizer, 16); + + if (mgmt_send(adapter->mgmt, MGMT_OP_ADD_REMOTE_OOB_DATA, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) + return 0; + + return -EIO; +} + +int btd_adapter_remove_remote_oob_data(struct btd_adapter *adapter, + const bdaddr_t *bdaddr) +{ + struct mgmt_cp_remove_remote_oob_data cp; + char addr[18]; + + ba2str(bdaddr, addr); + DBG("hci%d bdaddr %s", adapter->dev_id, addr); + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.addr.bdaddr, bdaddr); + + if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_REMOTE_OOB_DATA, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) + return 0; + + return -EIO; +} + +bool btd_adapter_ssp_enabled(struct btd_adapter *adapter) +{ + if (adapter->current_settings & MGMT_SETTING_SSP) + return true; + + return false; +} + +void btd_adapter_set_oob_handler(struct btd_adapter *adapter, + struct oob_handler *handler) +{ + adapter->oob_handler = handler; +} + +gboolean btd_adapter_check_oob_handler(struct btd_adapter *adapter) +{ + return adapter->oob_handler != NULL; +} + +static void read_local_oob_data_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_local_oob_data *rp = param; + struct btd_adapter *adapter = user_data; + const uint8_t *hash, *randomizer; + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Read local OOB data failed: %s (0x%02x)", + mgmt_errstr(status), status); + hash = NULL; + randomizer = NULL; + } else if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, + "Too small read local OOB data response"); + return; + } else { + hash = rp->hash192; + randomizer = rp->rand192; + } + + if (!adapter->oob_handler || !adapter->oob_handler->read_local_cb) + return; + + adapter->oob_handler->read_local_cb(adapter, hash, randomizer, + adapter->oob_handler->user_data); + + g_free(adapter->oob_handler); + adapter->oob_handler = NULL; +} + +int btd_adapter_read_local_oob_data(struct btd_adapter *adapter) +{ + DBG("hci%u", adapter->dev_id); + + if (mgmt_send(adapter->mgmt, MGMT_OP_READ_LOCAL_OOB_DATA, + adapter->dev_id, 0, NULL, read_local_oob_data_complete, + adapter, NULL) > 0) + return 0; + + return -EIO; +} + +void btd_adapter_for_each_device(struct btd_adapter *adapter, + void (*cb)(struct btd_device *device, void *data), + void *data) +{ + g_slist_foreach(adapter->devices, (GFunc) cb, data); +} + +static int adapter_cmp(gconstpointer a, gconstpointer b) +{ + struct btd_adapter *adapter = (struct btd_adapter *) a; + const bdaddr_t *bdaddr = b; + + return bacmp(&adapter->bdaddr, bdaddr); +} + +static int adapter_id_cmp(gconstpointer a, gconstpointer b) +{ + struct btd_adapter *adapter = (struct btd_adapter *) a; + uint16_t id = GPOINTER_TO_UINT(b); + + return adapter->dev_id == id ? 0 : -1; +} + +struct btd_adapter *adapter_find(const bdaddr_t *sba) +{ + GSList *match; + + match = g_slist_find_custom(adapters, sba, adapter_cmp); + if (!match) + return NULL; + + return match->data; +} + +struct btd_adapter *adapter_find_by_id(int id) +{ + GSList *match; + + match = g_slist_find_custom(adapters, GINT_TO_POINTER(id), + adapter_id_cmp); + if (!match) + return NULL; + + return match->data; +} + +void adapter_foreach(adapter_cb func, gpointer user_data) +{ + g_slist_foreach(adapters, (GFunc) func, user_data); +} + +static int set_did(struct btd_adapter *adapter, uint16_t vendor, + uint16_t product, uint16_t version, uint16_t source) +{ + struct mgmt_cp_set_device_id cp; + + DBG("hci%u source %x vendor %x product %x version %x", + adapter->dev_id, source, vendor, product, version); + + memset(&cp, 0, sizeof(cp)); + + cp.source = htobs(source); + cp.vendor = htobs(vendor); + cp.product = htobs(product); + cp.version = htobs(version); + + if (mgmt_send(adapter->mgmt, MGMT_OP_SET_DEVICE_ID, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) + return 0; + + return -EIO; +} + +static void services_modified(struct gatt_db_attribute *attrib, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "UUIDs"); +} + +static int adapter_register(struct btd_adapter *adapter) +{ + struct agent *agent; + struct gatt_db *db; + + if (powering_down) + return -EBUSY; + + adapter->path = g_strdup_printf("/org/bluez/hci%d", adapter->dev_id); + + if (!g_dbus_register_interface(dbus_conn, + adapter->path, ADAPTER_INTERFACE, + adapter_methods, NULL, + adapter_properties, adapter, + adapter_free)) { + btd_error(adapter->dev_id, + "Adapter interface init failed on path %s", + adapter->path); + g_free(adapter->path); + adapter->path = NULL; + return -EINVAL; + } + + if (adapters == NULL) + adapter->is_default = true; + + adapters = g_slist_append(adapters, adapter); + + agent = agent_get(NULL); + if (agent) { + uint8_t io_cap = agent_get_io_capability(agent); + adapter_set_io_capability(adapter, io_cap); + agent_unref(agent); + } + + /* Don't start GATT database and advertising managers on + * non-LE controllers. + */ + if (!(adapter->supported_settings & MGMT_SETTING_LE) || + main_opts.mode == BT_MODE_BREDR) + goto load; + + adapter->database = btd_gatt_database_new(adapter); + if (!adapter->database) { + btd_error(adapter->dev_id, + "Failed to create GATT database for adapter"); + adapters = g_slist_remove(adapters, adapter); + return -EINVAL; + } + + adapter->adv_manager = btd_adv_manager_new(adapter, adapter->mgmt); + + db = btd_gatt_database_get_db(adapter->database); + adapter->db_id = gatt_db_register(db, services_modified, + services_modified, + adapter, NULL); + +load: + load_config(adapter); + fix_storage(adapter); + load_drivers(adapter); + btd_profile_foreach(probe_profile, adapter); + clear_blocked(adapter); + load_devices(adapter); + + /* restore Service Changed CCC value for bonded devices */ + btd_gatt_database_restore_svc_chng_ccc(adapter->database); + + /* retrieve the active connections: address the scenario where + * the are active connections before the daemon've started */ + if (adapter->current_settings & MGMT_SETTING_POWERED) + load_connections(adapter); + + adapter->initialized = TRUE; + + if (main_opts.did_source) { + /* DeviceID record is added by sdpd-server before any other + * record is registered. */ + adapter_service_insert(adapter, sdp_record_find(0x10000)); + set_did(adapter, main_opts.did_vendor, main_opts.did_product, + main_opts.did_version, main_opts.did_source); + } + + DBG("Adapter %s registered", adapter->path); + + return 0; +} + +static int adapter_unregister(struct btd_adapter *adapter) +{ + DBG("Unregister path: %s", adapter->path); + + adapters = g_slist_remove(adapters, adapter); + + if (adapter->is_default && adapters != NULL) { + struct btd_adapter *new_default; + + new_default = adapter_find_by_id(hci_get_route(NULL)); + if (new_default == NULL) + new_default = adapters->data; + + new_default->is_default = true; + } + + adapter_list = g_list_remove(adapter_list, adapter); + + adapter_remove(adapter); + btd_adapter_unref(adapter); + + return 0; +} + +static void disconnected_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_device_disconnected *ev = param; + struct btd_adapter *adapter = user_data; + uint8_t reason; + + if (length < sizeof(struct mgmt_addr_info)) { + btd_error(adapter->dev_id, + "Too small device disconnected event"); + return; + } + + if (length < sizeof(*ev)) + reason = MGMT_DEV_DISCONN_UNKNOWN; + else + reason = ev->reason; + + dev_disconnected(adapter, &ev->addr, reason); +} + +static void connected_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_device_connected *ev = param; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + struct eir_data eir_data; + uint16_t eir_len; + char addr[18]; + bool name_known; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small device connected event"); + return; + } + + eir_len = btohs(ev->eir_len); + if (length < sizeof(*ev) + eir_len) { + btd_error(adapter->dev_id, "Too small device connected event"); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + + DBG("hci%u device %s connected eir_len %u", index, addr, eir_len); + + device = btd_adapter_get_device(adapter, &ev->addr.bdaddr, + ev->addr.type); + if (!device) { + btd_error(adapter->dev_id, + "Unable to get device object for %s", addr); + return; + } + + memset(&eir_data, 0, sizeof(eir_data)); + if (eir_len > 0) + eir_parse(&eir_data, ev->eir, eir_len); + + if (eir_data.class != 0) + device_set_class(device, eir_data.class); + + adapter_add_connection(adapter, device, ev->addr.type); + + name_known = device_name_known(device); + + if (eir_data.name && (eir_data.name_complete || !name_known)) { + device_store_cached_name(device, eir_data.name); + btd_device_device_set_name(device, eir_data.name); + } + + if (eir_data.msd_list) + adapter_msd_notify(adapter, device, eir_data.msd_list); + + eir_data_free(&eir_data); +} + +static void device_blocked_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_device_blocked *ev = param; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + char addr[18]; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small device blocked event"); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + DBG("hci%u %s blocked", index, addr); + + device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, + ev->addr.type); + if (device) + device_block(device, TRUE); +} + +static void device_unblocked_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_device_unblocked *ev = param; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + char addr[18]; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small device unblocked event"); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + DBG("hci%u %s unblocked", index, addr); + + device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, + ev->addr.type); + if (device) + device_unblock(device, FALSE, TRUE); +} + +static void conn_fail_notify(struct btd_device *dev, uint8_t status) +{ + GSList *l; + + for (l = conn_fail_list; l; l = g_slist_next(l)) { + btd_conn_fail_cb conn_fail_cb = l->data; + conn_fail_cb(dev, status); + } +} + +void btd_add_conn_fail_cb(btd_conn_fail_cb func) +{ + conn_fail_list = g_slist_append(conn_fail_list, func); +} + +void btd_remove_conn_fail_cb(btd_conn_fail_cb func) +{ + conn_fail_list = g_slist_remove(conn_fail_list, func); +} + +static void connect_failed_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_connect_failed *ev = param; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + char addr[18]; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small connect failed event"); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + + DBG("hci%u %s status %u", index, addr, ev->status); + + device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, + ev->addr.type); + if (device) { + conn_fail_notify(device, ev->status); + + /* If the device is in a bonding process cancel any auth request + * sent to the agent before proceeding, but keep the bonding + * request structure. */ + if (device_is_bonding(device, NULL)) + device_cancel_authentication(device, FALSE); + } + + /* In the case of security mode 3 devices */ + bonding_attempt_complete(adapter, &ev->addr.bdaddr, ev->addr.type, + ev->status); + + /* If the device is scheduled to retry the bonding wait until the retry + * happens. In other case, proceed with cancel the bondig. + */ + if (device && device_is_bonding(device, NULL) + && !device_is_retrying(device)) { + device_cancel_authentication(device, TRUE); + device_bonding_failed(device, ev->status); + } + + /* In the case the bonding was canceled or did exists, remove the device + * when it is temporary. */ + if (device && !device_is_bonding(device, NULL) + && device_is_temporary(device)) + btd_adapter_remove_device(adapter, device); +} + +static void remove_keys(struct btd_adapter *adapter, + struct btd_device *device, uint8_t type) +{ + char device_addr[18]; + char filename[PATH_MAX]; + GKeyFile *key_file; + gsize length = 0; + char *str; + + ba2str(device_get_address(device), device_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + btd_adapter_get_storage_dir(adapter), device_addr); + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + if (type == BDADDR_BREDR) { + g_key_file_remove_group(key_file, "LinkKey", NULL); + } else { + g_key_file_remove_group(key_file, "LongTermKey", NULL); + g_key_file_remove_group(key_file, "LocalSignatureKey", NULL); + g_key_file_remove_group(key_file, "RemoteSignatureKey", NULL); + g_key_file_remove_group(key_file, "IdentityResolvingKey", NULL); + } + + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, str, length, NULL); + g_free(str); + + g_key_file_free(key_file); +} + +static void unpaired_callback(uint16_t index, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_ev_device_unpaired *ev = param; + struct btd_adapter *adapter = user_data; + struct btd_device *device; + char addr[18]; + + if (length < sizeof(*ev)) { + btd_error(adapter->dev_id, "Too small device unpaired event"); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + + DBG("hci%u addr %s", index, addr); + + device = btd_adapter_find_device(adapter, &ev->addr.bdaddr, + ev->addr.type); + if (!device) { + btd_warn(adapter->dev_id, + "No device object for unpaired device %s", addr); + return; + } + + remove_keys(adapter, device, ev->addr.type); + device_set_unpaired(device, ev->addr.type); +} + +static void clear_devices_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + if (status != MGMT_STATUS_SUCCESS) { + error("Failed to clear devices: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } +} + +static int clear_devices(struct btd_adapter *adapter) +{ + struct mgmt_cp_remove_device cp; + + if (!kernel_conn_control) + return 0; + + memset(&cp, 0, sizeof(cp)); + + DBG("sending clear devices command for index %u", adapter->dev_id); + + if (mgmt_send(adapter->mgmt, MGMT_OP_REMOVE_DEVICE, + adapter->dev_id, sizeof(cp), &cp, + clear_devices_complete, adapter, NULL) > 0) + return 0; + + btd_error(adapter->dev_id, "Failed to clear devices for index %u", + adapter->dev_id); + + return -EIO; +} + +static bool get_static_addr(struct btd_adapter *adapter) +{ + struct bt_crypto *crypto; + GKeyFile *file; + char **addrs; + char mfg[7]; + char *str; + bool ret; + gsize len, i; + + snprintf(mfg, sizeof(mfg), "0x%04x", adapter->manufacturer); + + file = g_key_file_new(); + g_key_file_load_from_file(file, STORAGEDIR "/addresses", 0, NULL); + addrs = g_key_file_get_string_list(file, "Static", mfg, &len, NULL); + if (addrs) { + for (i = 0; i < len; i++) { + bdaddr_t addr; + + str2ba(addrs[i], &addr); + if (adapter_find(&addr)) + continue; + + /* Usable address found in list */ + bacpy(&adapter->bdaddr, &addr); + adapter->bdaddr_type = BDADDR_LE_RANDOM; + ret = true; + goto done; + } + + len++; + addrs = g_renew(char *, addrs, len + 1); + } else { + len = 1; + addrs = g_new(char *, len + 1); + } + + /* Initialize slot for new address */ + addrs[len - 1] = g_malloc(18); + addrs[len] = NULL; + + crypto = bt_crypto_new(); + if (!crypto) { + error("Failed to open crypto"); + ret = false; + goto done; + } + + ret = bt_crypto_random_bytes(crypto, &adapter->bdaddr, + sizeof(adapter->bdaddr)); + if (!ret) { + error("Failed to generate static address"); + bt_crypto_unref(crypto); + goto done; + } + + bt_crypto_unref(crypto); + + adapter->bdaddr.b[5] |= 0xc0; + adapter->bdaddr_type = BDADDR_LE_RANDOM; + + ba2str(&adapter->bdaddr, addrs[len - 1]); + + g_key_file_set_string_list(file, "Static", mfg, + (const char **)addrs, len); + + str = g_key_file_to_data(file, &len, NULL); + g_file_set_contents(STORAGEDIR "/addresses", str, len, NULL); + g_free(str); + + ret = true; + +done: + g_key_file_free(file); + g_strfreev(addrs); + + return ret; +} + +static bool set_static_addr(struct btd_adapter *adapter) +{ + struct mgmt_cp_set_static_address cp; + + /* dual-mode adapters must have a public address */ + if (adapter->supported_settings & MGMT_SETTING_BREDR) + return false; + + if (!(adapter->supported_settings & MGMT_SETTING_LE)) + return false; + + DBG("Setting static address"); + + if (!get_static_addr(adapter)) + return false; + + bacpy(&cp.bdaddr, &adapter->bdaddr); + if (mgmt_send(adapter->mgmt, MGMT_OP_SET_STATIC_ADDRESS, + adapter->dev_id, sizeof(cp), &cp, + NULL, NULL, NULL) > 0) { + return true; + } + + return false; +} + +static void read_info_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adapter *adapter = user_data; + const struct mgmt_rp_read_info *rp = param; + uint32_t missing_settings; + int err; + + DBG("index %u status 0x%02x", adapter->dev_id, status); + + if (status != MGMT_STATUS_SUCCESS) { + btd_error(adapter->dev_id, + "Failed to read info for index %u: %s (0x%02x)", + adapter->dev_id, mgmt_errstr(status), status); + goto failed; + } + + if (length < sizeof(*rp)) { + btd_error(adapter->dev_id, + "Too small read info complete response"); + goto failed; + } + + /* + * Store controller information for class of device, device + * name, short name and settings. + * + * During the lifetime of the controller these will be updated by + * events and the information is required to keep the current + * state of the controller. + */ + adapter->dev_class = rp->dev_class[0] | (rp->dev_class[1] << 8) | + (rp->dev_class[2] << 16); + adapter->name = g_strdup((const char *) rp->name); + adapter->short_name = g_strdup((const char *) rp->short_name); + + adapter->manufacturer = btohs(rp->manufacturer); + + adapter->supported_settings = btohl(rp->supported_settings); + adapter->current_settings = btohl(rp->current_settings); + + clear_uuids(adapter); + clear_devices(adapter); + + if (bacmp(&rp->bdaddr, BDADDR_ANY) == 0) { + if (!set_static_addr(adapter)) { + btd_error(adapter->dev_id, + "No Bluetooth address for index %u", + adapter->dev_id); + goto failed; + } + } else { + bacpy(&adapter->bdaddr, &rp->bdaddr); + if (!(adapter->supported_settings & MGMT_SETTING_LE)) + adapter->bdaddr_type = BDADDR_BREDR; + else + adapter->bdaddr_type = BDADDR_LE_PUBLIC; + } + + missing_settings = adapter->current_settings ^ + adapter->supported_settings; + + switch (main_opts.mode) { + case BT_MODE_DUAL: + if (missing_settings & MGMT_SETTING_SSP) + set_mode(adapter, MGMT_OP_SET_SSP, 0x01); + if (missing_settings & MGMT_SETTING_LE) + set_mode(adapter, MGMT_OP_SET_LE, 0x01); + if (missing_settings & MGMT_SETTING_BREDR) + set_mode(adapter, MGMT_OP_SET_BREDR, 0x01); + break; + case BT_MODE_BREDR: + if (!(adapter->supported_settings & MGMT_SETTING_BREDR)) { + btd_error(adapter->dev_id, + "Ignoring adapter withouth BR/EDR support"); + goto failed; + } + + if (missing_settings & MGMT_SETTING_SSP) + set_mode(adapter, MGMT_OP_SET_SSP, 0x01); + if (missing_settings & MGMT_SETTING_BREDR) + set_mode(adapter, MGMT_OP_SET_BREDR, 0x01); + if (adapter->current_settings & MGMT_SETTING_LE) + set_mode(adapter, MGMT_OP_SET_LE, 0x00); + break; + case BT_MODE_LE: + if (!(adapter->supported_settings & MGMT_SETTING_LE)) { + btd_error(adapter->dev_id, + "Ignoring adapter withouth LE support"); + goto failed; + } + + if (missing_settings & MGMT_SETTING_LE) + set_mode(adapter, MGMT_OP_SET_LE, 0x01); + if (adapter->current_settings & MGMT_SETTING_BREDR) + set_mode(adapter, MGMT_OP_SET_BREDR, 0x00); + break; + } + + if (missing_settings & MGMT_SETTING_SECURE_CONN) + set_mode(adapter, MGMT_OP_SET_SECURE_CONN, 0x01); + + if (adapter->supported_settings & MGMT_SETTING_PRIVACY) + set_privacy(adapter, main_opts.privacy); + + if (main_opts.fast_conn && + (missing_settings & MGMT_SETTING_FAST_CONNECTABLE)) + set_mode(adapter, MGMT_OP_SET_FAST_CONNECTABLE, 0x01); + + err = adapter_register(adapter); + if (err < 0) { + btd_error(adapter->dev_id, "Unable to register new adapter"); + goto failed; + } + + /* + * Register all event notification handlers for controller. + * + * The handlers are registered after a succcesful read of the + * controller info. From now on they can track updates and + * notifications. + */ + mgmt_register(adapter->mgmt, MGMT_EV_NEW_SETTINGS, adapter->dev_id, + new_settings_callback, adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_CLASS_OF_DEV_CHANGED, + adapter->dev_id, + dev_class_changed_callback, + adapter, NULL); + mgmt_register(adapter->mgmt, MGMT_EV_LOCAL_NAME_CHANGED, + adapter->dev_id, + local_name_changed_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_DISCOVERING, + adapter->dev_id, + discovering_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_FOUND, + adapter->dev_id, + device_found_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_DISCONNECTED, + adapter->dev_id, + disconnected_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_CONNECTED, + adapter->dev_id, + connected_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_CONNECT_FAILED, + adapter->dev_id, + connect_failed_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_UNPAIRED, + adapter->dev_id, + unpaired_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_AUTH_FAILED, + adapter->dev_id, + auth_failed_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_NEW_LINK_KEY, + adapter->dev_id, + new_link_key_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_NEW_LONG_TERM_KEY, + adapter->dev_id, + new_long_term_key_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_NEW_CSRK, + adapter->dev_id, + new_csrk_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_NEW_IRK, + adapter->dev_id, + new_irk_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_NEW_CONN_PARAM, + adapter->dev_id, + new_conn_param, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_BLOCKED, + adapter->dev_id, + device_blocked_callback, + adapter, NULL); + mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_UNBLOCKED, + adapter->dev_id, + device_unblocked_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_PIN_CODE_REQUEST, + adapter->dev_id, + pin_code_request_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_USER_CONFIRM_REQUEST, + adapter->dev_id, + user_confirm_request_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_USER_PASSKEY_REQUEST, + adapter->dev_id, + user_passkey_request_callback, + adapter, NULL); + + mgmt_register(adapter->mgmt, MGMT_EV_PASSKEY_NOTIFY, + adapter->dev_id, + user_passkey_notify_callback, + adapter, NULL); + + set_dev_class(adapter); + + set_name(adapter, btd_adapter_get_name(adapter)); + + if (main_opts.pairable && + !(adapter->current_settings & MGMT_SETTING_BONDABLE)) + set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x01); + + if (!kernel_conn_control) + set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x01); + else if (adapter->current_settings & MGMT_SETTING_CONNECTABLE) + set_mode(adapter, MGMT_OP_SET_CONNECTABLE, 0x00); + + if (adapter->stored_discoverable && !adapter->discoverable_timeout) + set_discoverable(adapter, 0x01, 0); + + if (adapter->current_settings & MGMT_SETTING_POWERED) + adapter_start(adapter); + + return; + +failed: + /* + * Remove adapter from list in case of a failure. + * + * Leaving an adapter structure around for a controller that can + * not be initilized makes no sense at the moment. + * + * This is a simplification to avoid constant checks if the + * adapter is ready to do anything. + */ + adapter_list = g_list_remove(adapter_list, adapter); + + btd_adapter_unref(adapter); +} + +static void index_added(uint16_t index, uint16_t length, const void *param, + void *user_data) +{ + struct btd_adapter *adapter; + + DBG("index %u", index); + + adapter = btd_adapter_lookup(index); + if (adapter) { + btd_warn(adapter->dev_id, + "Ignoring index added for an already existing adapter"); + return; + } + + adapter = btd_adapter_new(index); + if (!adapter) { + btd_error(index, + "Unable to create new adapter for index %u", index); + return; + } + + /* + * Protect against potential two executions of read controller info. + * + * In case the start of the daemon and the action of adding a new + * controller coincide this function might be called twice. + * + * To avoid the double execution of reading the controller info, + * add the adapter already to the list. If an adapter is already + * present, the second notification will cause a warning. If the + * command fails the adapter is removed from the list again. + */ + adapter_list = g_list_append(adapter_list, adapter); + + DBG("sending read info command for index %u", index); + + if (mgmt_send(mgmt_master, MGMT_OP_READ_INFO, index, 0, NULL, + read_info_complete, adapter, NULL) > 0) + return; + + btd_error(adapter->dev_id, + "Failed to read controller info for index %u", index); + + adapter_list = g_list_remove(adapter_list, adapter); + + btd_adapter_unref(adapter); +} + +static void index_removed(uint16_t index, uint16_t length, const void *param, + void *user_data) +{ + struct btd_adapter *adapter; + + DBG("index %u", index); + + adapter = btd_adapter_lookup(index); + if (!adapter) { + warn("Ignoring index removal for a non-existent adapter"); + return; + } + + adapter_unregister(adapter); +} + +static void read_index_list_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_index_list *rp = param; + uint16_t num; + int i; + + if (status != MGMT_STATUS_SUCCESS) { + error("Failed to read index list: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + if (length < sizeof(*rp)) { + error("Wrong size of read index list response"); + return; + } + + num = btohs(rp->num_controllers); + + DBG("Number of controllers: %d", num); + + if (num * sizeof(uint16_t) + sizeof(*rp) != length) { + error("Incorrect packet size for index list response"); + return; + } + + for (i = 0; i < num; i++) { + uint16_t index; + + index = btohs(rp->index[i]); + + DBG("Found index %u", index); + + /* + * Pretend to be index added event notification. + * + * It is safe to just trigger the procedure for index + * added notification. It does check against itself. + */ + index_added(index, 0, NULL, NULL); + } +} + +static void read_commands_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_commands *rp = param; + uint16_t num_commands, num_events; + size_t expected_len; + int i; + + if (status != MGMT_STATUS_SUCCESS) { + error("Failed to read supported commands: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + if (length < sizeof(*rp)) { + error("Wrong size of read commands response"); + return; + } + + num_commands = btohs(rp->num_commands); + num_events = btohs(rp->num_events); + + DBG("Number of commands: %d", num_commands); + DBG("Number of events: %d", num_events); + + expected_len = sizeof(*rp) + num_commands * sizeof(uint16_t) + + num_events * sizeof(uint16_t); + + if (length < expected_len) { + error("Too small reply for supported commands: (%u != %zu)", + length, expected_len); + return; + } + + for (i = 0; i < num_commands; i++) { + uint16_t op = get_le16(rp->opcodes + i); + + if (op == MGMT_OP_ADD_DEVICE) { + DBG("enabling kernel-side connection control"); + kernel_conn_control = true; + } + } +} + +static void read_version_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_version *rp = param; + + if (status != MGMT_STATUS_SUCCESS) { + error("Failed to read version information: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + if (length < sizeof(*rp)) { + error("Wrong size of read version response"); + return; + } + + mgmt_version = rp->version; + mgmt_revision = btohs(rp->revision); + + info("Bluetooth management interface %u.%u initialized", + mgmt_version, mgmt_revision); + + if (mgmt_version < 1) { + error("Version 1.0 or later of management interface required"); + abort(); + } + + DBG("sending read supported commands command"); + + /* + * It is irrelevant if this command succeeds or fails. In case of + * failure safe settings are assumed. + */ + mgmt_send(mgmt_master, MGMT_OP_READ_COMMANDS, + MGMT_INDEX_NONE, 0, NULL, + read_commands_complete, NULL, NULL); + + mgmt_register(mgmt_master, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE, + index_added, NULL, NULL); + mgmt_register(mgmt_master, MGMT_EV_INDEX_REMOVED, MGMT_INDEX_NONE, + index_removed, NULL, NULL); + + DBG("sending read index list command"); + + if (mgmt_send(mgmt_master, MGMT_OP_READ_INDEX_LIST, + MGMT_INDEX_NONE, 0, NULL, + read_index_list_complete, NULL, NULL) > 0) + return; + + error("Failed to read controller index list"); +} + +static void mgmt_debug(const char *str, void *user_data) +{ + const char *prefix = user_data; + + info("%s%s", prefix, str); +} + +int adapter_init(void) +{ + dbus_conn = btd_get_dbus_connection(); + + mgmt_master = mgmt_new_default(); + if (!mgmt_master) { + error("Failed to access management interface"); + return -EIO; + } + + if (getenv("MGMT_DEBUG")) + mgmt_set_debug(mgmt_master, mgmt_debug, "mgmt: ", NULL); + + DBG("sending read version command"); + + if (mgmt_send(mgmt_master, MGMT_OP_READ_VERSION, + MGMT_INDEX_NONE, 0, NULL, + read_version_complete, NULL, NULL) > 0) + return 0; + + error("Failed to read management version information"); + + return -EIO; +} + +void adapter_cleanup(void) +{ + g_list_free(adapter_list); + + while (adapters) { + struct btd_adapter *adapter = adapters->data; + + adapter_remove(adapter); + adapters = g_slist_remove(adapters, adapter); + btd_adapter_unref(adapter); + } + + /* + * In case there is another reference active, clear out + * registered handlers for index added and index removed. + * + * This is just an extra precaution to be safe, and in + * reality should not make a difference. + */ + mgmt_unregister_index(mgmt_master, MGMT_INDEX_NONE); + + /* + * In case there is another reference active, cancel + * all pending global commands. + * + * This is just an extra precaution to avoid callbacks + * that potentially then could leak memory or access + * an invalid structure. + */ + mgmt_cancel_index(mgmt_master, MGMT_INDEX_NONE); + + mgmt_unref(mgmt_master); + mgmt_master = NULL; + + dbus_conn = NULL; +} + +void adapter_shutdown(void) +{ + GList *list; + + DBG(""); + + powering_down = true; + + for (list = g_list_first(adapter_list); list; + list = g_list_next(list)) { + struct btd_adapter *adapter = list->data; + + if (!(adapter->current_settings & MGMT_SETTING_POWERED)) + continue; + + clear_discoverable(adapter); + set_mode(adapter, MGMT_OP_SET_POWERED, 0x00); + + adapter_remaining++; + } + + if (!adapter_remaining) + btd_exit(); +} + +/* + * Check if workaround for broken ATT server socket behavior is needed + * where we need to connect an ATT client socket before pairing to get + * early access to the ATT channel. + */ +bool btd_le_connect_before_pairing(void) +{ + if (MGMT_VERSION(mgmt_version, mgmt_revision) < MGMT_VERSION(1, 4)) + return true; + + return false; +} diff --git a/src/adapter.h b/src/adapter.h new file mode 100644 index 0000000..d0a5253 --- /dev/null +++ b/src/adapter.h @@ -0,0 +1,234 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include + +#define MAX_NAME_LENGTH 248 + +/* Invalid SSP passkey value used to indicate negative replies */ +#define INVALID_PASSKEY 0xffffffff + +struct btd_adapter; +struct btd_device; + +struct btd_adapter *btd_adapter_get_default(void); +bool btd_adapter_is_default(struct btd_adapter *adapter); +uint16_t btd_adapter_get_index(struct btd_adapter *adapter); + +typedef void (*adapter_cb) (struct btd_adapter *adapter, gpointer user_data); + +typedef void (*oob_read_local_cb_t) (struct btd_adapter *adapter, + const uint8_t *hash, + const uint8_t *randomizer, + void *user_data); +typedef void (*oob_bonding_cb_t) (struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t status, + void *user_data); + +struct oob_handler { + oob_read_local_cb_t read_local_cb; + oob_bonding_cb_t bonding_cb; + bdaddr_t remote_addr; + void *user_data; +}; + +int adapter_init(void); +void adapter_cleanup(void); +void adapter_shutdown(void); + +typedef void (*btd_disconnect_cb) (struct btd_device *device, uint8_t reason); +void btd_add_disconnect_cb(btd_disconnect_cb func); +void btd_remove_disconnect_cb(btd_disconnect_cb func); + +typedef void (*btd_conn_fail_cb) (struct btd_device *device, uint8_t status); +void btd_add_conn_fail_cb(btd_conn_fail_cb func); +void btd_remove_conn_fail_cb(btd_conn_fail_cb func); + +struct btd_adapter *adapter_find(const bdaddr_t *sba); +struct btd_adapter *adapter_find_by_id(int id); +void adapter_foreach(adapter_cb func, gpointer user_data); + +bool btd_adapter_get_pairable(struct btd_adapter *adapter); +bool btd_adapter_get_powered(struct btd_adapter *adapter); +bool btd_adapter_get_connectable(struct btd_adapter *adapter); +bool btd_adapter_get_discoverable(struct btd_adapter *adapter); +bool btd_adapter_get_bredr(struct btd_adapter *adapter); + +struct btd_gatt_database *btd_adapter_get_database(struct btd_adapter *adapter); + +uint32_t btd_adapter_get_class(struct btd_adapter *adapter); +const char *btd_adapter_get_name(struct btd_adapter *adapter); +void btd_adapter_remove_device(struct btd_adapter *adapter, + struct btd_device *dev); +struct btd_device *btd_adapter_get_device(struct btd_adapter *adapter, + const bdaddr_t *addr, + uint8_t addr_type); +sdp_list_t *btd_adapter_get_services(struct btd_adapter *adapter); + +struct btd_device *btd_adapter_find_device(struct btd_adapter *adapter, + const bdaddr_t *dst, + uint8_t dst_type); + +const char *adapter_get_path(struct btd_adapter *adapter); +const bdaddr_t *btd_adapter_get_address(struct btd_adapter *adapter); +uint8_t btd_adapter_get_address_type(struct btd_adapter *adapter); +const char *btd_adapter_get_storage_dir(struct btd_adapter *adapter); +int adapter_set_name(struct btd_adapter *adapter, const char *name); + +int adapter_service_add(struct btd_adapter *adapter, sdp_record_t *rec); +void adapter_service_remove(struct btd_adapter *adapter, uint32_t handle); + +struct agent *adapter_get_agent(struct btd_adapter *adapter); + +struct btd_adapter *btd_adapter_ref(struct btd_adapter *adapter); +void btd_adapter_unref(struct btd_adapter *adapter); + +void btd_adapter_set_class(struct btd_adapter *adapter, uint8_t major, + uint8_t minor); + +struct btd_adapter_driver { + const char *name; + int (*probe) (struct btd_adapter *adapter); + void (*remove) (struct btd_adapter *adapter); +}; + +typedef void (*service_auth_cb) (DBusError *derr, void *user_data); + +void adapter_add_profile(struct btd_adapter *adapter, gpointer p); +void adapter_remove_profile(struct btd_adapter *adapter, gpointer p); +int btd_register_adapter_driver(struct btd_adapter_driver *driver); +void btd_unregister_adapter_driver(struct btd_adapter_driver *driver); +guint btd_request_authorization(const bdaddr_t *src, const bdaddr_t *dst, + const char *uuid, service_auth_cb cb, void *user_data); +guint btd_request_authorization_cable_configured(const bdaddr_t *src, const bdaddr_t *dst, + const char *uuid, service_auth_cb cb, void *user_data); +int btd_cancel_authorization(guint id); + +int btd_adapter_restore_powered(struct btd_adapter *adapter); + +typedef ssize_t (*btd_adapter_pin_cb_t) (struct btd_adapter *adapter, + struct btd_device *dev, char *out, bool *display, + unsigned int attempt); +void btd_adapter_register_pin_cb(struct btd_adapter *adapter, + btd_adapter_pin_cb_t cb); +void btd_adapter_unregister_pin_cb(struct btd_adapter *adapter, + btd_adapter_pin_cb_t cb); + +struct btd_adapter_pin_cb_iter *btd_adapter_pin_cb_iter_new( + struct btd_adapter *adapter); +void btd_adapter_pin_cb_iter_free(struct btd_adapter_pin_cb_iter *iter); +bool btd_adapter_pin_cb_iter_end(struct btd_adapter_pin_cb_iter *iter); + +typedef void (*btd_msd_cb_t) (struct btd_adapter *adapter, + struct btd_device *dev, + uint16_t company, + const uint8_t *data, + uint8_t data_len); +void btd_adapter_register_msd_cb(struct btd_adapter *adapter, + btd_msd_cb_t cb); +void btd_adapter_unregister_msd_cb(struct btd_adapter *adapter, + btd_msd_cb_t cb); + +/* If TRUE, enables fast connectabe, i.e. reduces page scan interval and changes + * type. If FALSE, disables fast connectable, i.e. sets page scan interval and + * type to default values. Valid for both connectable and discoverable modes. */ +int btd_adapter_set_fast_connectable(struct btd_adapter *adapter, + gboolean enable); + +int btd_adapter_read_clock(struct btd_adapter *adapter, const bdaddr_t *bdaddr, + int which, int timeout, uint32_t *clock, + uint16_t *accuracy); + +int btd_adapter_block_address(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type); +int btd_adapter_unblock_address(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type); + +int btd_adapter_disconnect_device(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + uint8_t bdaddr_type); + +int btd_adapter_remove_bonding(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type); + +int btd_adapter_pincode_reply(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + const char *pin, size_t pin_len); +int btd_adapter_confirm_reply(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type, + gboolean success); +int btd_adapter_passkey_reply(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type, + uint32_t passkey); + +int adapter_create_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, + uint8_t addr_type, uint8_t io_cap); + +int adapter_bonding_attempt(struct btd_adapter *adapter, const bdaddr_t *bdaddr, + uint8_t addr_type, uint8_t io_cap); + +int adapter_cancel_bonding(struct btd_adapter *adapter, const bdaddr_t *bdaddr, + uint8_t addr_type); + +int adapter_set_io_capability(struct btd_adapter *adapter, uint8_t io_cap); + +int btd_adapter_read_local_oob_data(struct btd_adapter *adapter); + +int btd_adapter_add_remote_oob_data(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, + uint8_t *hash, uint8_t *randomizer); + +int btd_adapter_remove_remote_oob_data(struct btd_adapter *adapter, + const bdaddr_t *bdaddr); + +int btd_adapter_gatt_server_start(struct btd_adapter *adapter); +void btd_adapter_gatt_server_stop(struct btd_adapter *adapter); + +bool btd_adapter_ssp_enabled(struct btd_adapter *adapter); + +int adapter_connect_list_add(struct btd_adapter *adapter, + struct btd_device *device); +void adapter_connect_list_remove(struct btd_adapter *adapter, + struct btd_device *device); +void adapter_auto_connect_add(struct btd_adapter *adapter, + struct btd_device *device); +void adapter_auto_connect_remove(struct btd_adapter *adapter, + struct btd_device *device); +void adapter_whitelist_add(struct btd_adapter *adapter, + struct btd_device *dev); +void adapter_whitelist_remove(struct btd_adapter *adapter, + struct btd_device *dev); + +void btd_adapter_set_oob_handler(struct btd_adapter *adapter, + struct oob_handler *handler); +gboolean btd_adapter_check_oob_handler(struct btd_adapter *adapter); + +void btd_adapter_for_each_device(struct btd_adapter *adapter, + void (*cb)(struct btd_device *device, void *data), + void *data); + +bool btd_le_connect_before_pairing(void); + diff --git a/src/advertising.c b/src/advertising.c new file mode 100644 index 0000000..3ed1376 --- /dev/null +++ b/src/advertising.c @@ -0,0 +1,1461 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Google 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/mgmt.h" +#include "lib/sdp.h" + +#include "adapter.h" +#include "dbus-common.h" +#include "error.h" +#include "log.h" +#include "eir.h" +#include "src/shared/ad.h" +#include "src/shared/mgmt.h" +#include "src/shared/queue.h" +#include "src/shared/util.h" +#include "advertising.h" + +#define LE_ADVERTISING_MGR_IFACE "org.bluez.LEAdvertisingManager1" +#define LE_ADVERTISEMENT_IFACE "org.bluez.LEAdvertisement1" + +struct btd_adv_manager { + struct btd_adapter *adapter; + struct queue *clients; + struct mgmt *mgmt; + uint16_t mgmt_index; + uint8_t max_adv_len; + uint8_t max_scan_rsp_len; + uint8_t max_ads; + uint32_t supported_flags; + unsigned int instance_bitmap; +}; + +#define AD_TYPE_BROADCAST 0 +#define AD_TYPE_PERIPHERAL 1 + +struct btd_adv_client { + struct btd_adv_manager *manager; + char *owner; + char *path; + char *name; + uint16_t appearance; + uint16_t duration; + uint16_t timeout; + uint16_t discoverable_to; + unsigned int to_id; + unsigned int disc_to_id; + GDBusClient *client; + GDBusProxy *proxy; + DBusMessage *reg; + uint8_t type; /* Advertising type */ + uint32_t flags; + struct bt_ad *data; + struct bt_ad *scan; + uint8_t instance; +}; + +struct dbus_obj_match { + const char *owner; + const char *path; +}; + +static bool match_client(const void *a, const void *b) +{ + const struct btd_adv_client *client = a; + const struct dbus_obj_match *match = b; + + if (match->owner && g_strcmp0(client->owner, match->owner)) + return false; + + if (match->path && g_strcmp0(client->path, match->path)) + return false; + + return true; +} + +static void client_free(void *data) +{ + struct btd_adv_client *client = data; + + if (client->to_id > 0) + g_source_remove(client->to_id); + + if (client->disc_to_id > 0) + g_source_remove(client->disc_to_id); + + if (client->client) { + g_dbus_client_set_disconnect_watch(client->client, NULL, NULL); + g_dbus_client_unref(client->client); + } + + if (client->instance) + util_clear_uid(&client->manager->instance_bitmap, + client->instance); + + bt_ad_unref(client->data); + bt_ad_unref(client->scan); + + g_dbus_proxy_unref(client->proxy); + + if (client->owner) + g_free(client->owner); + + if (client->path) + g_free(client->path); + + free(client->name); + free(client); +} + +static gboolean client_free_idle_cb(void *data) +{ + client_free(data); + + return FALSE; +} + +static void client_release(void *data) +{ + struct btd_adv_client *client = data; + + DBG("Releasing advertisement %s, %s", client->owner, client->path); + + g_dbus_proxy_method_call(client->proxy, "Release", NULL, NULL, NULL, + NULL); +} + +static void client_destroy(void *data) +{ + client_release(data); + client_free(data); +} + +static void remove_advertising(struct btd_adv_manager *manager, + uint8_t instance) +{ + struct mgmt_cp_remove_advertising cp; + + if (instance) + DBG("instance %u", instance); + else + DBG("all instances"); + + cp.instance = instance; + + mgmt_send(manager->mgmt, MGMT_OP_REMOVE_ADVERTISING, + manager->mgmt_index, sizeof(cp), &cp, NULL, NULL, NULL); +} + +static void client_remove(void *data) +{ + struct btd_adv_client *client = data; + struct mgmt_cp_remove_advertising cp; + + g_dbus_client_set_disconnect_watch(client->client, NULL, NULL); + + cp.instance = client->instance; + + mgmt_send(client->manager->mgmt, MGMT_OP_REMOVE_ADVERTISING, + client->manager->mgmt_index, sizeof(cp), &cp, + NULL, NULL, NULL); + + queue_remove(client->manager->clients, client); + + g_idle_add(client_free_idle_cb, client); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + adapter_get_path(client->manager->adapter), + LE_ADVERTISING_MGR_IFACE, "SupportedInstances"); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + adapter_get_path(client->manager->adapter), + LE_ADVERTISING_MGR_IFACE, "ActiveInstances"); +} + +static void client_disconnect_cb(DBusConnection *conn, void *user_data) +{ + DBG("Client disconnected"); + + client_remove(user_data); +} + +static bool parse_type(DBusMessageIter *iter, struct btd_adv_client *client) +{ + const char *msg_type; + + if (!iter) + return true; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return false; + + dbus_message_iter_get_basic(iter, &msg_type); + + if (!g_strcmp0(msg_type, "broadcast")) { + client->type = AD_TYPE_BROADCAST; + return true; + } + + if (!g_strcmp0(msg_type, "peripheral")) { + client->type = AD_TYPE_PERIPHERAL; + return true; + } + + return false; +} + +static bool parse_service_uuids(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + DBusMessageIter ariter; + + if (!iter) { + bt_ad_clear_service_uuid(client->data); + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return false; + + dbus_message_iter_recurse(iter, &ariter); + + bt_ad_clear_service_uuid(client->data); + + while (dbus_message_iter_get_arg_type(&ariter) == DBUS_TYPE_STRING) { + const char *uuid_str; + bt_uuid_t uuid; + + dbus_message_iter_get_basic(&ariter, &uuid_str); + + DBG("Adding ServiceUUID: %s", uuid_str); + + if (bt_string_to_uuid(&uuid, uuid_str) < 0) + goto fail; + + if (!bt_ad_add_service_uuid(client->data, &uuid)) + goto fail; + + dbus_message_iter_next(&ariter); + } + + return true; + +fail: + bt_ad_clear_service_uuid(client->data); + return false; +} + +static bool parse_solicit_uuids(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + DBusMessageIter ariter; + + if (!iter) { + bt_ad_clear_solicit_uuid(client->data); + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return false; + + dbus_message_iter_recurse(iter, &ariter); + + bt_ad_clear_solicit_uuid(client->data); + + while (dbus_message_iter_get_arg_type(&ariter) == DBUS_TYPE_STRING) { + const char *uuid_str; + bt_uuid_t uuid; + + dbus_message_iter_get_basic(&ariter, &uuid_str); + + DBG("Adding SolicitUUID: %s", uuid_str); + + if (bt_string_to_uuid(&uuid, uuid_str) < 0) + goto fail; + + if (!bt_ad_add_solicit_uuid(client->data, &uuid)) + goto fail; + + dbus_message_iter_next(&ariter); + } + + return true; + +fail: + bt_ad_clear_solicit_uuid(client->data); + return false; +} + +static bool parse_manufacturer_data(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + DBusMessageIter entries; + + if (!iter) { + bt_ad_clear_manufacturer_data(client->data); + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return false; + + dbus_message_iter_recurse(iter, &entries); + + bt_ad_clear_manufacturer_data(client->data); + + while (dbus_message_iter_get_arg_type(&entries) + == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter value, entry, array; + uint16_t manuf_id; + uint8_t *manuf_data; + int len; + + dbus_message_iter_recurse(&entries, &entry); + dbus_message_iter_get_basic(&entry, &manuf_id); + + dbus_message_iter_next(&entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) + goto fail; + + dbus_message_iter_recurse(&entry, &value); + + if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) + goto fail; + + dbus_message_iter_recurse(&value, &array); + + if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_fixed_array(&array, &manuf_data, &len); + + DBG("Adding ManufacturerData for %04x", manuf_id); + + if (!bt_ad_add_manufacturer_data(client->data, manuf_id, + manuf_data, len)) + goto fail; + + dbus_message_iter_next(&entries); + } + + return true; + +fail: + bt_ad_clear_manufacturer_data(client->data); + return false; +} + +static bool parse_service_data(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + DBusMessageIter entries; + + if (!iter) { + bt_ad_clear_service_data(client->data); + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return false; + + dbus_message_iter_recurse(iter, &entries); + + bt_ad_clear_service_data(client->data); + + while (dbus_message_iter_get_arg_type(&entries) + == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter value, entry, array; + const char *uuid_str; + bt_uuid_t uuid; + uint8_t *service_data; + int len; + + dbus_message_iter_recurse(&entries, &entry); + dbus_message_iter_get_basic(&entry, &uuid_str); + + if (bt_string_to_uuid(&uuid, uuid_str) < 0) + goto fail; + + dbus_message_iter_next(&entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) + goto fail; + + dbus_message_iter_recurse(&entry, &value); + + if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) + goto fail; + + dbus_message_iter_recurse(&value, &array); + + if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_fixed_array(&array, &service_data, &len); + + DBG("Adding ServiceData for %s", uuid_str); + + if (!bt_ad_add_service_data(client->data, &uuid, service_data, + len)) + goto fail; + + dbus_message_iter_next(&entries); + } + + return true; + +fail: + bt_ad_clear_service_data(client->data); + return false; +} + +static struct adv_include { + uint8_t flag; + const char *name; +} includes[] = { + { MGMT_ADV_FLAG_TX_POWER, "tx-power" }, + { MGMT_ADV_FLAG_APPEARANCE, "appearance" }, + { MGMT_ADV_FLAG_LOCAL_NAME, "local-name" }, + { }, +}; + +static bool parse_includes(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + DBusMessageIter entries; + + if (!iter) { + client->flags = 0; + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return false; + + dbus_message_iter_recurse(iter, &entries); + + /* Reset flags before parsing */ + client->flags = 0; + + while (dbus_message_iter_get_arg_type(&entries) == DBUS_TYPE_STRING) { + const char *str; + struct adv_include *inc; + + dbus_message_iter_get_basic(&entries, &str); + + for (inc = includes; inc && inc->name; inc++) { + if (strcmp(str, inc->name)) + continue; + + if (!(client->manager->supported_flags & inc->flag)) + continue; + + DBG("Including Feature: %s", str); + + client->flags |= inc->flag; + } + + dbus_message_iter_next(&entries); + } + + return true; +} + +static bool parse_local_name(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + const char *name; + + if (!iter) { + free(client->name); + client->name = NULL; + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return false; + + if (client->flags & MGMT_ADV_FLAG_LOCAL_NAME) { + error("Local name already included"); + return false; + } + + dbus_message_iter_get_basic(iter, &name); + + free(client->name); + client->name = strdup(name); + + return true; +} + +static bool parse_appearance(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + if (!iter) { + client->appearance = 0; + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) + return false; + + if (client->flags & MGMT_ADV_FLAG_APPEARANCE) { + error("Appearance already included"); + return false; + } + + dbus_message_iter_get_basic(iter, &client->appearance); + + return true; +} + +static bool parse_duration(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + if (!iter) { + client->duration = 0; + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) + return false; + + dbus_message_iter_get_basic(iter, &client->duration); + + return true; +} + +static gboolean client_timeout(void *user_data) +{ + struct btd_adv_client *client = user_data; + + DBG(""); + + client->to_id = 0; + + client_release(client); + client_remove(client); + + return FALSE; +} + +static bool parse_timeout(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + if (!iter) { + client->timeout = 0; + g_source_remove(client->to_id); + client->to_id = 0; + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) + return false; + + dbus_message_iter_get_basic(iter, &client->timeout); + + if (client->to_id) + g_source_remove(client->to_id); + + if (client->timeout > 0) + client->to_id = g_timeout_add_seconds(client->timeout, + client_timeout, client); + + return true; +} + +static bool parse_data(DBusMessageIter *iter, struct btd_adv_client *client) +{ + DBusMessageIter entries; + + if (!iter) { + bt_ad_clear_data(client->data); + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return false; + + dbus_message_iter_recurse(iter, &entries); + + bt_ad_clear_data(client->data); + + while (dbus_message_iter_get_arg_type(&entries) + == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter value, entry, array; + uint8_t type; + uint8_t *data; + int len; + + dbus_message_iter_recurse(&entries, &entry); + dbus_message_iter_get_basic(&entry, &type); + + dbus_message_iter_next(&entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) + goto fail; + + dbus_message_iter_recurse(&entry, &value); + + if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_ARRAY) + goto fail; + + dbus_message_iter_recurse(&value, &array); + + if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_BYTE) + goto fail; + + dbus_message_iter_get_fixed_array(&array, &data, &len); + + DBG("Adding Data for type 0x%02x len %u", type, len); + + if (!bt_ad_add_data(client->data, type, data, len)) + goto fail; + + dbus_message_iter_next(&entries); + } + + return true; + +fail: + bt_ad_clear_data(client->data); + return false; +} + +static bool set_flags(struct btd_adv_client *client, uint8_t flags) +{ + if (!flags) { + bt_ad_clear_flags(client->data); + return true; + } + + /* Set BR/EDR Not Supported for LE only */ + if (!btd_adapter_get_bredr(client->manager->adapter)) + flags |= 0x04; + + if (!bt_ad_add_flags(client->data, &flags, 1)) + return false; + + return true; +} + +static bool parse_discoverable(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + uint8_t flags; + dbus_bool_t discoverable; + + if (!iter) { + bt_ad_clear_flags(client->data); + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) + return false; + + dbus_message_iter_get_basic(iter, &discoverable); + + if (discoverable) + flags = 0x02; + else + flags = 0x00; + + if (!set_flags(client , flags)) + goto fail; + + DBG("Adding Flags 0x%02x", flags); + + return true; + +fail: + bt_ad_clear_flags(client->data); + return false; +} + +static size_t calc_max_adv_len(struct btd_adv_client *client, uint32_t flags) +{ + size_t max = client->manager->max_adv_len; + + /* + * Flags which reduce the amount of space available for advertising. + * See doc/mgmt-api.txt + */ + if (flags & MGMT_ADV_FLAG_TX_POWER) + max -= 3; + + if (flags & (MGMT_ADV_FLAG_DISCOV | MGMT_ADV_FLAG_LIMITED_DISCOV | + MGMT_ADV_FLAG_MANAGED_FLAGS)) + max -= 3; + + if (flags & MGMT_ADV_FLAG_APPEARANCE) + max -= 4; + + return max; +} + +static uint8_t *generate_adv_data(struct btd_adv_client *client, + uint32_t *flags, size_t *len) +{ + if ((*flags & MGMT_ADV_FLAG_APPEARANCE) || + client->appearance != UINT16_MAX) { + uint16_t appearance; + + appearance = client->appearance; + if (appearance == UINT16_MAX) + /* TODO: Get the appearance from the adaptor once + * supported. + */ + appearance = 0x000; + + bt_ad_add_appearance(client->data, appearance); + } + + return bt_ad_generate(client->data, len); +} + +static uint8_t *generate_scan_rsp(struct btd_adv_client *client, + uint32_t *flags, size_t *len) +{ + struct btd_adv_manager *manager = client->manager; + const char *name; + + if (!(*flags & MGMT_ADV_FLAG_LOCAL_NAME) && !client->name) { + *len = 0; + return NULL; + } + + *flags &= ~MGMT_ADV_FLAG_LOCAL_NAME; + + name = client->name; + if (!name) + name = btd_adapter_get_name(manager->adapter); + + bt_ad_add_name(client->scan, name); + + return bt_ad_generate(client->scan, len); +} + +static int refresh_adv(struct btd_adv_client *client, mgmt_request_func_t func) +{ + struct mgmt_cp_add_advertising *cp; + uint8_t param_len; + uint8_t *adv_data; + size_t adv_data_len; + uint8_t *scan_rsp; + size_t scan_rsp_len = -1; + uint32_t flags = 0; + + DBG("Refreshing advertisement: %s", client->path); + + if (client->type == AD_TYPE_PERIPHERAL) { + flags = MGMT_ADV_FLAG_CONNECTABLE; + + if (btd_adapter_get_discoverable(client->manager->adapter) && + !(bt_ad_has_flags(client->data))) + flags |= MGMT_ADV_FLAG_DISCOV; + } + + flags |= client->flags; + + adv_data = generate_adv_data(client, &flags, &adv_data_len); + if (!adv_data || (adv_data_len > calc_max_adv_len(client, flags))) { + error("Advertising data too long or couldn't be generated."); + return -EINVAL; + } + + scan_rsp = generate_scan_rsp(client, &flags, &scan_rsp_len); + if (!scan_rsp && scan_rsp_len) { + error("Scan data couldn't be generated."); + free(adv_data); + return -EINVAL; + } + + param_len = sizeof(struct mgmt_cp_add_advertising) + adv_data_len + + scan_rsp_len; + + cp = malloc0(param_len); + if (!cp) { + error("Couldn't allocate for MGMT!"); + free(adv_data); + free(scan_rsp); + return -ENOMEM; + } + + cp->flags = htobl(flags); + cp->instance = client->instance; + cp->duration = client->duration; + cp->adv_data_len = adv_data_len; + cp->scan_rsp_len = scan_rsp_len; + memcpy(cp->data, adv_data, adv_data_len); + memcpy(cp->data + adv_data_len, scan_rsp, scan_rsp_len); + + free(adv_data); + free(scan_rsp); + + if (!mgmt_send(client->manager->mgmt, MGMT_OP_ADD_ADVERTISING, + client->manager->mgmt_index, param_len, cp, + func, client, NULL)) { + error("Failed to add Advertising Data"); + free(cp); + return -EINVAL; + } + + free(cp); + + return 0; +} + +static gboolean client_discoverable_timeout(void *user_data) +{ + struct btd_adv_client *client = user_data; + + DBG(""); + + client->disc_to_id = 0; + + bt_ad_clear_flags(client->data); + + refresh_adv(client, NULL); + + return FALSE; +} + +static bool parse_discoverable_timeout(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + if (!iter) { + client->discoverable_to = 0; + g_source_remove(client->disc_to_id); + client->disc_to_id = 0; + return true; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) + return false; + + dbus_message_iter_get_basic(iter, &client->discoverable_to); + + if (client->disc_to_id) + g_source_remove(client->disc_to_id); + + client->disc_to_id = g_timeout_add_seconds(client->discoverable_to, + client_discoverable_timeout, + client); + + return true; +} + +static struct adv_secondary { + int flag; + const char *name; +} secondary[] = { + { MGMT_ADV_FLAG_SEC_1M, "1M" }, + { MGMT_ADV_FLAG_SEC_2M, "2M" }, + { MGMT_ADV_FLAG_SEC_CODED, "Coded" }, + { }, +}; + +static bool parse_secondary(DBusMessageIter *iter, + struct btd_adv_client *client) +{ + const char *str; + struct adv_secondary *sec; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) + return false; + + /* Reset secondary channels before parsing */ + client->flags &= 0xfe00; + + dbus_message_iter_get_basic(iter, &str); + + for (sec = secondary; sec && sec->name; sec++) { + if (strcmp(str, sec->name)) + continue; + + if (!(client->manager->supported_flags & sec->flag)) + return false; + + DBG("Secondary Channel: %s", str); + + client->flags |= sec->flag; + + return true; + } + + return false; +} + +static struct adv_parser { + const char *name; + bool (*func)(DBusMessageIter *iter, struct btd_adv_client *client); +} parsers[] = { + { "Type", parse_type }, + { "ServiceUUIDs", parse_service_uuids }, + { "SolicitUUIDs", parse_solicit_uuids }, + { "ManufacturerData", parse_manufacturer_data }, + { "ServiceData", parse_service_data }, + { "Includes", parse_includes }, + { "LocalName", parse_local_name }, + { "Appearance", parse_appearance }, + { "Duration", parse_duration }, + { "Timeout", parse_timeout }, + { "Data", parse_data }, + { "Discoverable", parse_discoverable }, + { "DiscoverableTimeout", parse_discoverable_timeout }, + { "SecondaryChannel", parse_secondary }, + { }, +}; + +static void properties_changed(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + struct btd_adv_client *client = user_data; + struct adv_parser *parser; + + for (parser = parsers; parser && parser->name; parser++) { + if (strcmp(parser->name, name)) + continue; + + if (parser->func(iter, client)) { + refresh_adv(client, NULL); + break; + } + } +} + +static void add_client_complete(struct btd_adv_client *client, uint8_t status) +{ + DBusMessage *reply; + + if (status) { + error("Failed to add advertisement: %s (0x%02x)", + mgmt_errstr(status), status); + reply = btd_error_failed(client->reg, + "Failed to register advertisement"); + queue_remove(client->manager->clients, client); + g_idle_add(client_free_idle_cb, client); + + } else + reply = dbus_message_new_method_return(client->reg); + + g_dbus_send_message(btd_get_dbus_connection(), reply); + dbus_message_unref(client->reg); + client->reg = NULL; +} + +static void add_adv_callback(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adv_client *client = user_data; + const struct mgmt_rp_add_advertising *rp = param; + + if (status) + goto done; + + if (!param || length < sizeof(*rp)) { + status = MGMT_STATUS_FAILED; + goto done; + } + + client->instance = rp->instance; + + g_dbus_client_set_disconnect_watch(client->client, client_disconnect_cb, + client); + DBG("Advertisement registered: %s", client->path); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + adapter_get_path(client->manager->adapter), + LE_ADVERTISING_MGR_IFACE, "SupportedInstances"); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + adapter_get_path(client->manager->adapter), + LE_ADVERTISING_MGR_IFACE, "ActiveInstances"); + + g_dbus_proxy_set_property_watch(client->proxy, properties_changed, + client); + +done: + add_client_complete(client, status); +} + +static DBusMessage *parse_advertisement(struct btd_adv_client *client) +{ + struct adv_parser *parser; + int err; + + for (parser = parsers; parser && parser->name; parser++) { + DBusMessageIter iter; + + if (!g_dbus_proxy_get_property(client->proxy, parser->name, + &iter)) + continue; + + if (!parser->func(&iter, client)) { + error("Error parsing %s property", parser->name); + goto fail; + } + } + + if (bt_ad_has_flags(client->data)) { + /* BLUETOOTH SPECIFICATION Version 5.0 | Vol 3, Part C + * page 2042: + * A device in the broadcast mode shall not set the + * ‘LE General Discoverable Mode’ flag or the + * ‘LE Limited Discoverable Mode’ flag in the Flags AD Type + * as defined in [Core Specification Supplement], Part A, + * Section 1.3. + */ + if (client->type == AD_TYPE_BROADCAST) { + error("Broadcast cannot set flags"); + goto fail; + } + + /* Set Limited Discoverable if DiscoverableTimeout is set */ + if (client->disc_to_id && !set_flags(client, 0x01)) { + error("Failed to set Limited Discoverable Flag"); + goto fail; + } + } else if (client->disc_to_id) { + /* Ignore DiscoverableTimeout if not discoverable */ + g_source_remove(client->disc_to_id); + client->disc_to_id = 0; + client->discoverable_to = 0; + } + + if (client->timeout && client->timeout < client->discoverable_to) { + /* DiscoverableTimeout must not be bigger than Timeout */ + error("DiscoverableTimeout > Timeout"); + goto fail; + } + + err = refresh_adv(client, add_adv_callback); + if (!err) + return NULL; + +fail: + return btd_error_failed(client->reg, "Failed to parse advertisement."); +} + +static void client_proxy_added(GDBusProxy *proxy, void *data) +{ + struct btd_adv_client *client = data; + DBusMessage *reply; + const char *interface; + + interface = g_dbus_proxy_get_interface(proxy); + if (g_str_equal(interface, LE_ADVERTISEMENT_IFACE) == FALSE) + return; + + reply = parse_advertisement(client); + if (!reply) + return; + + /* Failed to publish for some reason, remove. */ + queue_remove(client->manager->clients, client); + + g_idle_add(client_free_idle_cb, client); + + g_dbus_send_message(btd_get_dbus_connection(), reply); + + dbus_message_unref(client->reg); + client->reg = NULL; +} + +static struct btd_adv_client *client_create(struct btd_adv_manager *manager, + DBusConnection *conn, + DBusMessage *msg, const char *path) +{ + struct btd_adv_client *client; + const char *sender = dbus_message_get_sender(msg); + + if (!path || !g_str_has_prefix(path, "/")) + return NULL; + + client = new0(struct btd_adv_client, 1); + client->client = g_dbus_client_new_full(conn, sender, path, path); + if (!client->client) + goto fail; + + client->owner = g_strdup(sender); + if (!client->owner) + goto fail; + + client->path = g_strdup(path); + if (!client->path) + goto fail; + + DBG("Adding proxy for %s", path); + client->proxy = g_dbus_proxy_new(client->client, path, + LE_ADVERTISEMENT_IFACE); + if (!client->proxy) + goto fail; + + g_dbus_client_set_proxy_handlers(client->client, client_proxy_added, + NULL, NULL, client); + + client->reg = dbus_message_ref(msg); + + client->data = bt_ad_new(); + if (!client->data) + goto fail; + + client->scan = bt_ad_new(); + if (!client->scan) + goto fail; + + client->manager = manager; + client->appearance = UINT16_MAX; + + return client; + +fail: + client_free(client); + return NULL; +} + +static DBusMessage *register_advertisement(DBusConnection *conn, + DBusMessage *msg, + void *user_data) +{ + struct btd_adv_manager *manager = user_data; + DBusMessageIter args; + struct btd_adv_client *client; + struct dbus_obj_match match; + + DBG("RegisterAdvertisement"); + + if (!dbus_message_iter_init(msg, &args)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&args, &match.path); + + match.owner = dbus_message_get_sender(msg); + + if (queue_find(manager->clients, match_client, &match)) + return btd_error_already_exists(msg); + + dbus_message_iter_next(&args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) + return btd_error_invalid_args(msg); + + client = client_create(manager, conn, msg, match.path); + if (!client) + return btd_error_failed(msg, + "Failed to register advertisement"); + + client->instance = util_get_uid(&manager->instance_bitmap, + manager->max_ads); + if (!client->instance) { + client_free(client); + return btd_error_not_permitted(msg, + "Maximum advertisements reached"); + } + + DBG("Registered advertisement at path %s", match.path); + + queue_push_tail(manager->clients, client); + + return NULL; +} + +static DBusMessage *unregister_advertisement(DBusConnection *conn, + DBusMessage *msg, + void *user_data) +{ + struct btd_adv_manager *manager = user_data; + DBusMessageIter args; + struct btd_adv_client *client; + struct dbus_obj_match match; + + DBG("UnregisterAdvertisement"); + + if (!dbus_message_iter_init(msg, &args)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&args, &match.path); + + match.owner = dbus_message_get_sender(msg); + + client = queue_find(manager->clients, match_client, &match); + if (!client) + return btd_error_does_not_exist(msg); + + client_remove(client); + + return dbus_message_new_method_return(msg); +} + +static gboolean get_instances(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_adv_manager *manager = data; + uint8_t instances; + + instances = manager->max_ads - queue_length(manager->clients); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &instances); + + return TRUE; +} + +static gboolean get_active_instances(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_adv_manager *manager = data; + uint8_t instances; + + instances = queue_length(manager->clients); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, &instances); + + return TRUE; +} + +static void append_include(struct btd_adv_manager *manager, + DBusMessageIter *iter) +{ + struct adv_include *inc; + + for (inc = includes; inc && inc->name; inc++) { + if (manager->supported_flags & inc->flag) + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &inc->name); + } +} + +static gboolean get_supported_includes(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_adv_manager *manager = data; + DBusMessageIter entry; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + + append_include(manager, &entry); + + dbus_message_iter_close_container(iter, &entry); + + return TRUE; +} + +static void append_secondary(struct btd_adv_manager *manager, + DBusMessageIter *iter) +{ + struct adv_secondary *sec; + + for (sec = secondary; sec && sec->name; sec++) { + if (manager->supported_flags & sec->flag) + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &sec->name); + } +} + +static gboolean secondary_exits(const GDBusPropertyTable *property, void *data) +{ + struct btd_adv_manager *manager = data; + + /* 1M PHY shall always be supported if ext_adv is supported */ + return manager->supported_flags & MGMT_ADV_FLAG_SEC_1M; +} + +static gboolean get_supported_secondary(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_adv_manager *manager = data; + DBusMessageIter entry; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + + append_secondary(manager, &entry); + + dbus_message_iter_close_container(iter, &entry); + + return TRUE; +} + +static const GDBusPropertyTable properties[] = { + { "ActiveInstances", "y", get_active_instances, NULL, NULL }, + { "SupportedInstances", "y", get_instances, NULL, NULL }, + { "SupportedIncludes", "as", get_supported_includes, NULL, NULL }, + { "SupportedSecondaryChannels", "as", get_supported_secondary, NULL, + secondary_exits }, + { } +}; + +static const GDBusMethodTable methods[] = { + { GDBUS_ASYNC_METHOD("RegisterAdvertisement", + GDBUS_ARGS({ "advertisement", "o" }, + { "options", "a{sv}" }), + NULL, register_advertisement) }, + { GDBUS_ASYNC_METHOD("UnregisterAdvertisement", + GDBUS_ARGS({ "service", "o" }), + NULL, + unregister_advertisement) }, + { } +}; + +static void manager_destroy(void *user_data) +{ + struct btd_adv_manager *manager = user_data; + + queue_destroy(manager->clients, client_destroy); + + mgmt_unref(manager->mgmt); + + free(manager); +} + +static void read_adv_features_callback(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct btd_adv_manager *manager = user_data; + const struct mgmt_rp_read_adv_features *feat = param; + + if (status || !param) { + error("Failed to read advertising features: %s (0x%02x)", + mgmt_errstr(status), status); + return; + } + + if (length < sizeof(*feat)) { + error("Wrong size of read adv features response"); + return; + } + + manager->max_adv_len = feat->max_adv_data_len; + manager->max_scan_rsp_len = feat->max_scan_rsp_len; + manager->max_ads = feat->max_instances; + manager->supported_flags |= feat->supported_flags; + + if (manager->max_ads == 0) + return; + + /* Reset existing instances */ + if (feat->num_instances) + remove_advertising(manager, 0); +} + +static struct btd_adv_manager *manager_create(struct btd_adapter *adapter, + struct mgmt *mgmt) +{ + struct btd_adv_manager *manager; + + manager = new0(struct btd_adv_manager, 1); + manager->adapter = adapter; + + manager->mgmt = mgmt_ref(mgmt); + + if (!manager->mgmt) { + error("Failed to access management interface"); + free(manager); + return NULL; + } + + manager->mgmt_index = btd_adapter_get_index(adapter); + manager->clients = queue_new(); + manager->supported_flags = MGMT_ADV_FLAG_LOCAL_NAME; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + adapter_get_path(manager->adapter), + LE_ADVERTISING_MGR_IFACE, methods, + NULL, properties, manager, NULL)) { + error("Failed to register " LE_ADVERTISING_MGR_IFACE); + goto fail; + } + + if (!mgmt_send(manager->mgmt, MGMT_OP_READ_ADV_FEATURES, + manager->mgmt_index, 0, NULL, + read_adv_features_callback, manager, NULL)) { + error("Failed to read advertising features"); + goto fail; + } + + return manager; + +fail: + manager_destroy(manager); + return NULL; +} + +struct btd_adv_manager *btd_adv_manager_new(struct btd_adapter *adapter, + struct mgmt *mgmt) +{ + struct btd_adv_manager *manager; + + if (!adapter || !mgmt) + return NULL; + + manager = manager_create(adapter, mgmt); + if (!manager) + return NULL; + + DBG("LE Advertising Manager created for adapter: %s", + adapter_get_path(adapter)); + + return manager; +} + +void btd_adv_manager_destroy(struct btd_adv_manager *manager) +{ + if (!manager) + return; + + g_dbus_unregister_interface(btd_get_dbus_connection(), + adapter_get_path(manager->adapter), + LE_ADVERTISING_MGR_IFACE); + + manager_destroy(manager); +} + +static void manager_refresh(void *data, void *user_data) +{ + refresh_adv(data, user_data); +} + +void btd_adv_manager_refresh(struct btd_adv_manager *manager) +{ + if (!manager) + return; + + queue_foreach(manager->clients, manager_refresh, NULL); +} diff --git a/src/advertising.h b/src/advertising.h new file mode 100644 index 0000000..50d8667 --- /dev/null +++ b/src/advertising.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Google 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. + * + */ + +struct btd_adapter; +struct btd_adv_manager; + +struct btd_adv_manager *btd_adv_manager_new(struct btd_adapter *adapter, + struct mgmt *mgmt); +void btd_adv_manager_destroy(struct btd_adv_manager *manager); +void btd_adv_manager_refresh(struct btd_adv_manager *manager); diff --git a/src/agent.c b/src/agent.c new file mode 100644 index 0000000..183e2f1 --- /dev/null +++ b/src/agent.c @@ -0,0 +1,1053 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "gdbus/gdbus.h" + +#include "log.h" +#include "error.h" +#include "hcid.h" +#include "dbus-common.h" +#include "adapter.h" +#include "device.h" +#include "agent.h" +#include "shared/queue.h" + +#define REQUEST_TIMEOUT (60 * 1000) /* 60 seconds */ +#define AGENT_INTERFACE "org.bluez.Agent1" + +static GHashTable *agent_list; +struct queue *default_agents = NULL; + +typedef enum { + AGENT_REQUEST_PASSKEY, + AGENT_REQUEST_CONFIRMATION, + AGENT_REQUEST_AUTHORIZATION, + AGENT_REQUEST_PINCODE, + AGENT_REQUEST_AUTHORIZE_SERVICE, + AGENT_REQUEST_DISPLAY_PINCODE, +} agent_request_type_t; + +struct agent { + int ref; + char *owner; + char *path; + uint8_t capability; + struct agent_request *request; + guint watch; +}; + +struct agent_request { + agent_request_type_t type; + struct agent *agent; + DBusMessage *msg; + DBusPendingCall *call; + void *cb; + void *user_data; + GDestroyNotify destroy; +}; + +static void agent_release(struct agent *agent) +{ + DBusMessage *message; + + DBG("Releasing agent %s, %s", agent->owner, agent->path); + + if (agent->request) + agent_cancel(agent); + + message = dbus_message_new_method_call(agent->owner, agent->path, + AGENT_INTERFACE, "Release"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return; + } + + g_dbus_send_message(btd_get_dbus_connection(), message); +} + +static int send_cancel_request(struct agent_request *req) +{ + DBusMessage *message; + + DBG("Sending Cancel request to %s, %s", req->agent->owner, + req->agent->path); + + message = dbus_message_new_method_call(req->agent->owner, req->agent->path, + AGENT_INTERFACE, "Cancel"); + if (message == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + g_dbus_send_message(btd_get_dbus_connection(), message); + + return 0; +} + +static void agent_request_free(struct agent_request *req, gboolean destroy) +{ + if (req->msg) + dbus_message_unref(req->msg); + if (req->call) + dbus_pending_call_unref(req->call); + if (req->agent && req->agent->request) + req->agent->request = NULL; + if (destroy && req->destroy) + req->destroy(req->user_data); + g_free(req); +} + +static void set_io_cap(struct btd_adapter *adapter, gpointer user_data) +{ + struct agent *agent = user_data; + uint8_t io_cap; + + if (agent) + io_cap = agent->capability; + else + io_cap = IO_CAPABILITY_INVALID; + + adapter_set_io_capability(adapter, io_cap); +} + +static bool add_default_agent(struct agent *agent) +{ + if (queue_peek_head(default_agents) == agent) + return true; + + queue_remove(default_agents, agent); + + if (!queue_push_head(default_agents, agent)) + return false; + + DBG("Default agent set to %s %s", agent->owner, agent->path); + + adapter_foreach(set_io_cap, agent); + + return true; +} + +static void remove_default_agent(struct agent *agent) +{ + if (queue_peek_head(default_agents) != agent) { + queue_remove(default_agents, agent); + return; + } + + queue_remove(default_agents, agent); + + agent = queue_peek_head(default_agents); + if (agent) + DBG("Default agent set to %s %s", agent->owner, agent->path); + else + DBG("Default agent cleared"); + + adapter_foreach(set_io_cap, agent); +} + +static void agent_disconnect(DBusConnection *conn, void *user_data) +{ + struct agent *agent = user_data; + + DBG("Agent %s disconnected", agent->owner); + + if (agent->watch > 0) { + g_dbus_remove_watch(conn, agent->watch); + agent->watch = 0; + } + + remove_default_agent(agent); + + g_hash_table_remove(agent_list, agent->owner); +} + +struct agent *agent_ref(struct agent *agent) +{ + agent->ref++; + + DBG("%p: ref=%d", agent, agent->ref); + + return agent; +} + +void agent_unref(struct agent *agent) +{ + agent->ref--; + + DBG("%p: ref=%d", agent, agent->ref); + + if (agent->ref > 0) + return; + + if (agent->request) { + DBusError err; + agent_pincode_cb pincode_cb; + agent_passkey_cb passkey_cb; + agent_cb cb; + + dbus_error_init(&err); + dbus_set_error_const(&err, ERROR_INTERFACE ".Failed", + "Canceled"); + + switch (agent->request->type) { + case AGENT_REQUEST_PINCODE: + pincode_cb = agent->request->cb; + pincode_cb(agent, &err, NULL, agent->request->user_data); + break; + case AGENT_REQUEST_PASSKEY: + passkey_cb = agent->request->cb; + passkey_cb(agent, &err, 0, agent->request->user_data); + break; + case AGENT_REQUEST_CONFIRMATION: + case AGENT_REQUEST_AUTHORIZATION: + case AGENT_REQUEST_AUTHORIZE_SERVICE: + case AGENT_REQUEST_DISPLAY_PINCODE: + default: + cb = agent->request->cb; + cb(agent, &err, agent->request->user_data); + } + + dbus_error_free(&err); + + agent_cancel(agent); + } + + g_free(agent->owner); + g_free(agent->path); + + g_free(agent); +} + +struct agent *agent_get(const char *owner) +{ + struct agent *agent; + + if (owner) { + agent = g_hash_table_lookup(agent_list, owner); + if (agent) + return agent_ref(agent); + } + + if (!queue_isempty(default_agents)) + return agent_ref(queue_peek_head(default_agents)); + + return NULL; +} + +static struct agent *agent_create( const char *name, const char *path, + uint8_t capability) +{ + struct agent *agent; + + agent = g_new0(struct agent, 1); + + agent->owner = g_strdup(name); + agent->path = g_strdup(path); + agent->capability = capability; + + agent->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), + name, agent_disconnect, + agent, NULL); + + if (queue_isempty(default_agents)) + add_default_agent(agent); + else + queue_push_tail(default_agents, agent); + + return agent_ref(agent); +} + +static struct agent_request *agent_request_new(struct agent *agent, + agent_request_type_t type, + void *cb, + void *user_data, + GDestroyNotify destroy) +{ + struct agent_request *req; + + req = g_new0(struct agent_request, 1); + + req->agent = agent; + req->type = type; + req->cb = cb; + req->user_data = user_data; + req->destroy = destroy; + + return req; +} + +int agent_cancel(struct agent *agent) +{ + if (!agent->request) + return -EINVAL; + + if (agent->request->call) { + dbus_pending_call_cancel(agent->request->call); + send_cancel_request(agent->request); + } + + agent_request_free(agent->request, TRUE); + agent->request = NULL; + + return 0; +} + +static void simple_agent_reply(DBusPendingCall *call, void *user_data) +{ + struct agent_request *req = user_data; + struct agent *agent = req->agent; + DBusMessage *message; + DBusError err; + agent_cb cb = req->cb; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + /* Protect from the callback freeing the agent */ + agent_ref(agent); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + DBG("agent error reply: %s, %s", err.name, err.message); + + cb(agent, &err, req->user_data); + + if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { + error("Timed out waiting for reply from agent"); + agent_cancel(agent); + dbus_message_unref(message); + dbus_error_free(&err); + agent_unref(agent); + return; + } + + dbus_error_free(&err); + goto done; + } + + if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) { + error("Wrong reply signature: %s", err.message); + cb(agent, &err, req->user_data); + dbus_error_free(&err); + goto done; + } + + cb(agent, NULL, req->user_data); +done: + dbus_message_unref(message); + + agent->request = NULL; + agent_request_free(req, TRUE); + agent_unref(agent); +} + +static int agent_call_authorize_service(struct agent_request *req, + const char *device_path, + const char *uuid) +{ + struct agent *agent = req->agent; + + req->msg = dbus_message_new_method_call(agent->owner, agent->path, + AGENT_INTERFACE, "AuthorizeService"); + if (!req->msg) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, + DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_STRING, &uuid, + DBUS_TYPE_INVALID); + + if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), + req->msg, &req->call, + REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); + return 0; +} + +int agent_authorize_service(struct agent *agent, const char *path, + const char *uuid, agent_cb cb, + void *user_data, GDestroyNotify destroy) +{ + struct agent_request *req; + int err; + + if (agent->request) + return -EBUSY; + + req = agent_request_new(agent, AGENT_REQUEST_AUTHORIZE_SERVICE, cb, + user_data, destroy); + + err = agent_call_authorize_service(req, path, uuid); + if (err < 0) { + agent_request_free(req, FALSE); + return -ENOMEM; + } + + agent->request = req; + + DBG("authorize service request was sent for %s", path); + + return 0; +} + +static void pincode_reply(DBusPendingCall *call, void *user_data) +{ + struct agent_request *req = user_data; + struct agent *agent = req->agent; + agent_pincode_cb cb = req->cb; + DBusMessage *message; + DBusError err; + size_t len; + char *pin; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + /* Protect from the callback freeing the agent */ + agent_ref(agent); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + error("Agent %s replied with an error: %s, %s", + agent->path, err.name, err.message); + + cb(agent, &err, NULL, req->user_data); + dbus_error_free(&err); + goto done; + } + + if (!dbus_message_get_args(message, &err, + DBUS_TYPE_STRING, &pin, + DBUS_TYPE_INVALID)) { + error("Wrong passkey reply signature: %s", err.message); + cb(agent, &err, NULL, req->user_data); + dbus_error_free(&err); + goto done; + } + + len = strlen(pin); + + if (len > 16 || len < 1) { + error("Invalid PIN length (%zu) from agent", len); + dbus_set_error_const(&err, ERROR_INTERFACE ".InvalidArgs", + "Invalid passkey length"); + cb(agent, &err, NULL, req->user_data); + dbus_error_free(&err); + goto done; + } + + cb(agent, NULL, pin, req->user_data); + +done: + if (message) + dbus_message_unref(message); + + dbus_pending_call_cancel(req->call); + agent->request = NULL; + agent_request_free(req, TRUE); + agent_unref(agent); +} + +static int pincode_request_new(struct agent_request *req, const char *device_path, + dbus_bool_t secure) +{ + struct agent *agent = req->agent; + + /* TODO: Add a new method or a new param to Agent interface to request + secure pin. */ + + req->msg = dbus_message_new_method_call(agent->owner, agent->path, + AGENT_INTERFACE, "RequestPinCode"); + if (req->msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_INVALID); + + if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, pincode_reply, req, NULL); + return 0; +} + +int agent_request_pincode(struct agent *agent, struct btd_device *device, + agent_pincode_cb cb, gboolean secure, + void *user_data, GDestroyNotify destroy) +{ + struct agent_request *req; + const char *dev_path = device_get_path(device); + int err; + + if (agent->request) + return -EBUSY; + + req = agent_request_new(agent, AGENT_REQUEST_PINCODE, cb, + user_data, destroy); + + err = pincode_request_new(req, dev_path, secure); + if (err < 0) + goto failed; + + agent->request = req; + + return 0; + +failed: + agent_request_free(req, FALSE); + return err; +} + +static void passkey_reply(DBusPendingCall *call, void *user_data) +{ + struct agent_request *req = user_data; + struct agent *agent = req->agent; + agent_passkey_cb cb = req->cb; + DBusMessage *message; + DBusError err; + uint32_t passkey; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + error("Agent replied with an error: %s, %s", + err.name, err.message); + cb(agent, &err, 0, req->user_data); + dbus_error_free(&err); + goto done; + } + + if (!dbus_message_get_args(message, &err, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_INVALID)) { + error("Wrong passkey reply signature: %s", err.message); + cb(agent, &err, 0, req->user_data); + dbus_error_free(&err); + goto done; + } + + cb(agent, NULL, passkey, req->user_data); + +done: + if (message) + dbus_message_unref(message); + + dbus_pending_call_cancel(req->call); + agent->request = NULL; + agent_request_free(req, TRUE); +} + +static int passkey_request_new(struct agent_request *req, + const char *device_path) +{ + struct agent *agent = req->agent; + + req->msg = dbus_message_new_method_call(agent->owner, agent->path, + AGENT_INTERFACE, "RequestPasskey"); + if (req->msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_INVALID); + + if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, passkey_reply, req, NULL); + return 0; +} + +int agent_request_passkey(struct agent *agent, struct btd_device *device, + agent_passkey_cb cb, void *user_data, + GDestroyNotify destroy) +{ + struct agent_request *req; + const char *dev_path = device_get_path(device); + int err; + + if (agent->request) + return -EBUSY; + + DBG("Calling Agent.RequestPasskey: name=%s, path=%s", + agent->owner, agent->path); + + req = agent_request_new(agent, AGENT_REQUEST_PASSKEY, cb, + user_data, destroy); + + err = passkey_request_new(req, dev_path); + if (err < 0) + goto failed; + + agent->request = req; + + return 0; + +failed: + agent_request_free(req, FALSE); + return err; +} + +static int confirmation_request_new(struct agent_request *req, + const char *device_path, + uint32_t passkey) +{ + struct agent *agent = req->agent; + + req->msg = dbus_message_new_method_call(agent->owner, agent->path, + AGENT_INTERFACE, "RequestConfirmation"); + if (req->msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, + DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_INVALID); + + if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); + + return 0; +} + +int agent_request_confirmation(struct agent *agent, struct btd_device *device, + uint32_t passkey, agent_cb cb, + void *user_data, GDestroyNotify destroy) +{ + struct agent_request *req; + const char *dev_path = device_get_path(device); + int err; + + if (agent->request) + return -EBUSY; + + DBG("Calling Agent.RequestConfirmation: name=%s, path=%s, passkey=%06u", + agent->owner, agent->path, passkey); + + req = agent_request_new(agent, AGENT_REQUEST_CONFIRMATION, cb, + user_data, destroy); + + err = confirmation_request_new(req, dev_path, passkey); + if (err < 0) + goto failed; + + agent->request = req; + + return 0; + +failed: + agent_request_free(req, FALSE); + return err; +} + +static int authorization_request_new(struct agent_request *req, + const char *device_path) +{ + struct agent *agent = req->agent; + + req->msg = dbus_message_new_method_call(agent->owner, agent->path, + AGENT_INTERFACE, "RequestAuthorization"); + if (req->msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, + DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_INVALID); + + if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, simple_agent_reply, req, NULL); + + return 0; +} + +int agent_request_authorization(struct agent *agent, struct btd_device *device, + agent_cb cb, void *user_data, + GDestroyNotify destroy) +{ + struct agent_request *req; + const char *dev_path = device_get_path(device); + int err; + + if (agent->request) + return -EBUSY; + + DBG("Calling Agent.RequestAuthorization: name=%s, path=%s", + agent->owner, agent->path); + + req = agent_request_new(agent, AGENT_REQUEST_AUTHORIZATION, cb, + user_data, destroy); + + err = authorization_request_new(req, dev_path); + if (err < 0) + goto failed; + + agent->request = req; + + return 0; + +failed: + agent_request_free(req, FALSE); + return err; +} + +int agent_display_passkey(struct agent *agent, struct btd_device *device, + uint32_t passkey, uint16_t entered) +{ + DBusMessage *message; + const char *dev_path = device_get_path(device); + + message = dbus_message_new_method_call(agent->owner, agent->path, + AGENT_INTERFACE, "DisplayPasskey"); + if (!message) { + error("Couldn't allocate D-Bus message"); + return -1; + } + + dbus_message_append_args(message, + DBUS_TYPE_OBJECT_PATH, &dev_path, + DBUS_TYPE_UINT32, &passkey, + DBUS_TYPE_UINT16, &entered, + DBUS_TYPE_INVALID); + + if (!g_dbus_send_message(btd_get_dbus_connection(), message)) { + error("D-Bus send failed"); + return -1; + } + + return 0; +} + +static void display_pincode_reply(DBusPendingCall *call, void *user_data) +{ + struct agent_request *req = user_data; + struct agent *agent = req->agent; + DBusMessage *message; + DBusError err; + agent_cb cb = req->cb; + + /* clear agent->request early; our callback will likely try + * another request */ + agent->request = NULL; + + /* steal_reply will always return non-NULL since the callback + * is only called after a reply has been received */ + message = dbus_pending_call_steal_reply(call); + + dbus_error_init(&err); + if (dbus_set_error_from_message(&err, message)) { + error("Agent replied with an error: %s, %s", + err.name, err.message); + + cb(agent, &err, req->user_data); + + if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { + agent_cancel(agent); + dbus_message_unref(message); + dbus_error_free(&err); + return; + } + + dbus_error_free(&err); + goto done; + } + + if (!dbus_message_get_args(message, &err, DBUS_TYPE_INVALID)) { + error("Wrong reply signature: %s", err.message); + cb(agent, &err, req->user_data); + dbus_error_free(&err); + goto done; + } + + cb(agent, NULL, req->user_data); +done: + dbus_message_unref(message); + + agent_request_free(req, TRUE); +} + +static int display_pincode_request_new(struct agent_request *req, + const char *device_path, + const char *pincode) +{ + struct agent *agent = req->agent; + + req->msg = dbus_message_new_method_call(agent->owner, agent->path, + AGENT_INTERFACE, "DisplayPinCode"); + if (req->msg == NULL) { + error("Couldn't allocate D-Bus message"); + return -ENOMEM; + } + + dbus_message_append_args(req->msg, + DBUS_TYPE_OBJECT_PATH, &device_path, + DBUS_TYPE_STRING, &pincode, + DBUS_TYPE_INVALID); + + if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), req->msg, + &req->call, REQUEST_TIMEOUT) == FALSE) { + error("D-Bus send failed"); + return -EIO; + } + + dbus_pending_call_set_notify(req->call, display_pincode_reply, + req, NULL); + + return 0; +} + +int agent_display_pincode(struct agent *agent, struct btd_device *device, + const char *pincode, agent_cb cb, + void *user_data, GDestroyNotify destroy) +{ + struct agent_request *req; + const char *dev_path = device_get_path(device); + int err; + + if (agent->request) + return -EBUSY; + + DBG("Calling Agent.DisplayPinCode: name=%s, path=%s, pincode=%s", + agent->owner, agent->path, pincode); + + req = agent_request_new(agent, AGENT_REQUEST_DISPLAY_PINCODE, cb, + user_data, destroy); + + err = display_pincode_request_new(req, dev_path, pincode); + if (err < 0) + goto failed; + + agent->request = req; + + return 0; + +failed: + agent_request_free(req, FALSE); + return err; +} + +uint8_t agent_get_io_capability(struct agent *agent) +{ + return agent->capability; +} + +static void agent_destroy(gpointer data) +{ + struct agent *agent = data; + + DBG("agent %s", agent->owner); + + if (agent->watch > 0) { + g_dbus_remove_watch(btd_get_dbus_connection(), agent->watch); + agent->watch = 0; + agent_release(agent); + } + + remove_default_agent(agent); + + agent_unref(agent); +} + +static uint8_t parse_io_capability(const char *capability) +{ + if (g_str_equal(capability, "")) + return IO_CAPABILITY_KEYBOARDDISPLAY; + if (g_str_equal(capability, "DisplayOnly")) + return IO_CAPABILITY_DISPLAYONLY; + if (g_str_equal(capability, "DisplayYesNo")) + return IO_CAPABILITY_DISPLAYYESNO; + if (g_str_equal(capability, "KeyboardOnly")) + return IO_CAPABILITY_KEYBOARDONLY; + if (g_str_equal(capability, "NoInputNoOutput")) + return IO_CAPABILITY_NOINPUTNOOUTPUT; + if (g_str_equal(capability, "KeyboardDisplay")) + return IO_CAPABILITY_KEYBOARDDISPLAY; + return IO_CAPABILITY_INVALID; +} + +static DBusMessage *register_agent(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct agent *agent; + const char *sender, *path, *capability; + uint8_t cap; + + sender = dbus_message_get_sender(msg); + + agent = g_hash_table_lookup(agent_list, sender); + if (agent) + return btd_error_already_exists(msg); + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_STRING, &capability, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + cap = parse_io_capability(capability); + if (cap == IO_CAPABILITY_INVALID) + return btd_error_invalid_args(msg); + + agent = agent_create(sender, path, cap); + if (!agent) + return btd_error_invalid_args(msg); + + DBG("agent %s", agent->owner); + + g_hash_table_replace(agent_list, agent->owner, agent); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_agent(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct agent *agent; + const char *sender, *path; + + sender = dbus_message_get_sender(msg); + + agent = g_hash_table_lookup(agent_list, sender); + if (!agent) + return btd_error_does_not_exist(msg); + + DBG("agent %s", agent->owner); + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + if (g_str_equal(path, agent->path) == FALSE) + return btd_error_does_not_exist(msg); + + agent_disconnect(conn, agent); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *request_default(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct agent *agent; + const char *sender, *path; + + sender = dbus_message_get_sender(msg); + + agent = g_hash_table_lookup(agent_list, sender); + if (!agent) + return btd_error_does_not_exist(msg); + + if (dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID) == FALSE) + return btd_error_invalid_args(msg); + + if (g_str_equal(path, agent->path) == FALSE) + return btd_error_does_not_exist(msg); + + if (!add_default_agent(agent)) + return btd_error_failed(msg, "Failed to set as default"); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable methods[] = { + { GDBUS_METHOD("RegisterAgent", + GDBUS_ARGS({ "agent", "o"}, { "capability", "s" }), + NULL, register_agent) }, + { GDBUS_METHOD("UnregisterAgent", GDBUS_ARGS({ "agent", "o" }), + NULL, unregister_agent) }, + { GDBUS_METHOD("RequestDefaultAgent", GDBUS_ARGS({ "agent", "o" }), + NULL, request_default ) }, + { } +}; + +void btd_agent_init(void) +{ + agent_list = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, agent_destroy); + + default_agents = queue_new(); + + g_dbus_register_interface(btd_get_dbus_connection(), + "/org/bluez", "org.bluez.AgentManager1", + methods, NULL, NULL, NULL, NULL); +} + +void btd_agent_cleanup(void) +{ + g_dbus_unregister_interface(btd_get_dbus_connection(), + "/org/bluez", "org.bluez.AgentManager1"); + + g_hash_table_destroy(agent_list); + queue_destroy(default_agents, NULL); +} diff --git a/src/agent.h b/src/agent.h new file mode 100644 index 0000000..f14d143 --- /dev/null +++ b/src/agent.h @@ -0,0 +1,80 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define IO_CAPABILITY_DISPLAYONLY 0x00 +#define IO_CAPABILITY_DISPLAYYESNO 0x01 +#define IO_CAPABILITY_KEYBOARDONLY 0x02 +#define IO_CAPABILITY_NOINPUTNOOUTPUT 0x03 +#define IO_CAPABILITY_KEYBOARDDISPLAY 0x04 +#define IO_CAPABILITY_INVALID 0xFF + +struct agent; + +typedef void (*agent_cb) (struct agent *agent, DBusError *err, + void *user_data); + +typedef void (*agent_pincode_cb) (struct agent *agent, DBusError *err, + const char *pincode, void *user_data); + +typedef void (*agent_passkey_cb) (struct agent *agent, DBusError *err, + uint32_t passkey, void *user_data); + +struct agent *agent_ref(struct agent *agent); +void agent_unref(struct agent *agent); + +struct agent *agent_get(const char *owner); + +int agent_authorize_service(struct agent *agent, const char *path, + const char *uuid, agent_cb cb, + void *user_data, GDestroyNotify destroy); + +int agent_request_pincode(struct agent *agent, struct btd_device *device, + agent_pincode_cb cb, gboolean secure, + void *user_data, GDestroyNotify destroy); + +int agent_request_passkey(struct agent *agent, struct btd_device *device, + agent_passkey_cb cb, void *user_data, + GDestroyNotify destroy); + +int agent_request_confirmation(struct agent *agent, struct btd_device *device, + uint32_t passkey, agent_cb cb, + void *user_data, GDestroyNotify destroy); + +int agent_request_authorization(struct agent *agent, struct btd_device *device, + agent_cb cb, void *user_data, + GDestroyNotify destroy); + +int agent_display_passkey(struct agent *agent, struct btd_device *device, + uint32_t passkey, uint16_t entered); + +int agent_display_pincode(struct agent *agent, struct btd_device *device, + const char *pincode, agent_cb cb, + void *user_data, GDestroyNotify destroy); + +int agent_cancel(struct agent *agent); + +uint8_t agent_get_io_capability(struct agent *agent); + +void btd_agent_init(void); +void btd_agent_cleanup(void); diff --git a/src/attrib-server.c b/src/attrib-server.c new file mode 100644 index 0000000..7c15a4e --- /dev/null +++ b/src/attrib-server.c @@ -0,0 +1,1654 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "btio/btio.h" +#include "log.h" +#include "backtrace.h" +#include "adapter.h" +#include "device.h" +#include "src/shared/util.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "attrib/att-database.h" +#include "textfile.h" +#include "storage.h" + +#include "attrib-server.h" + +static GSList *servers = NULL; + +struct gatt_server { + struct btd_adapter *adapter; + GIOChannel *l2cap_io; + GIOChannel *le_io; + uint32_t gatt_sdp_handle; + uint32_t gap_sdp_handle; + GList *database; + GSList *clients; + uint16_t name_handle; + uint16_t appearance_handle; +}; + +struct gatt_channel { + GAttrib *attrib; + guint mtu; + gboolean le; + guint id; + gboolean encrypted; + struct gatt_server *server; + guint cleanup_id; + struct btd_device *device; +}; + +struct group_elem { + uint16_t handle; + uint16_t end; + uint8_t *data; + uint16_t len; +}; + +static bt_uuid_t prim_uuid = { + .type = BT_UUID16, + .value.u16 = GATT_PRIM_SVC_UUID +}; +static bt_uuid_t snd_uuid = { + .type = BT_UUID16, + .value.u16 = GATT_SND_SVC_UUID +}; +static bt_uuid_t ccc_uuid = { + .type = BT_UUID16, + .value.u16 = GATT_CLIENT_CHARAC_CFG_UUID +}; + +static void attrib_free(void *data) +{ + struct attribute *a = data; + + g_free(a->data); + g_free(a); +} + +static void channel_free(struct gatt_channel *channel) +{ + + if (channel->cleanup_id) + g_source_remove(channel->cleanup_id); + + if (channel->device) + btd_device_unref(channel->device); + + g_attrib_unref(channel->attrib); + g_free(channel); +} + +static void gatt_server_free(struct gatt_server *server) +{ + g_list_free_full(server->database, attrib_free); + + if (server->l2cap_io != NULL) { + g_io_channel_shutdown(server->l2cap_io, FALSE, NULL); + g_io_channel_unref(server->l2cap_io); + } + + if (server->le_io != NULL) { + g_io_channel_shutdown(server->le_io, FALSE, NULL); + g_io_channel_unref(server->le_io); + } + + g_slist_free_full(server->clients, (GDestroyNotify) channel_free); + + if (server->gatt_sdp_handle > 0) + adapter_service_remove(server->adapter, + server->gatt_sdp_handle); + + if (server->gap_sdp_handle > 0) + adapter_service_remove(server->adapter, server->gap_sdp_handle); + + if (server->adapter != NULL) + btd_adapter_unref(server->adapter); + + g_free(server); +} + +static int adapter_cmp_addr(gconstpointer a, gconstpointer b) +{ + const struct gatt_server *server = a; + const bdaddr_t *bdaddr = b; + + return bacmp(btd_adapter_get_address(server->adapter), bdaddr); +} + +static int adapter_cmp(gconstpointer a, gconstpointer b) +{ + const struct gatt_server *server = a; + const struct btd_adapter *adapter = b; + + if (server->adapter == adapter) + return 0; + + return -1; +} + +static struct gatt_server *find_gatt_server(const bdaddr_t *bdaddr) +{ + GSList *l; + + l = g_slist_find_custom(servers, bdaddr, adapter_cmp_addr); + if (l == NULL) { + char addr[18]; + + ba2str(bdaddr, addr); + error("No GATT server found in %s", addr); + return NULL; + } + + return l->data; +} + +static sdp_record_t *server_record_new(uuid_t *uuid, uint16_t start, uint16_t end) +{ + sdp_list_t *svclass_id, *apseq, *proto[2], *root, *aproto; + uuid_t root_uuid, proto_uuid, l2cap; + sdp_record_t *record; + sdp_data_t *psm, *sh, *eh; + uint16_t lp = ATT_PSM; + + if (uuid == NULL) + return NULL; + + if (start > end) + return NULL; + + record = sdp_record_alloc(); + if (record == NULL) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + sdp_list_free(root, NULL); + + svclass_id = sdp_list_append(NULL, uuid); + sdp_set_service_classes(record, svclass_id); + sdp_list_free(svclass_id, NULL); + + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&proto_uuid, ATT_UUID); + proto[1] = sdp_list_append(NULL, &proto_uuid); + sh = sdp_data_alloc(SDP_UINT16, &start); + proto[1] = sdp_list_append(proto[1], sh); + eh = sdp_data_alloc(SDP_UINT16, &end); + proto[1] = sdp_list_append(proto[1], eh); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + sdp_data_free(psm); + sdp_data_free(sh); + sdp_data_free(eh); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(aproto, NULL); + + return record; +} + +static int handle_cmp(gconstpointer a, gconstpointer b) +{ + const struct attribute *attrib = a; + uint16_t handle = GPOINTER_TO_UINT(b); + + return attrib->handle - handle; +} + +static int attribute_cmp(gconstpointer a1, gconstpointer a2) +{ + const struct attribute *attrib1 = a1; + const struct attribute *attrib2 = a2; + + return attrib1->handle - attrib2->handle; +} + +static struct attribute *find_svc_range(struct gatt_server *server, + uint16_t start, uint16_t *end) +{ + struct attribute *attrib; + guint h = start; + GList *l; + + if (end == NULL) + return NULL; + + l = g_list_find_custom(server->database, GUINT_TO_POINTER(h), + handle_cmp); + if (!l) + return NULL; + + attrib = l->data; + + if (bt_uuid_cmp(&attrib->uuid, &prim_uuid) != 0 && + bt_uuid_cmp(&attrib->uuid, &snd_uuid) != 0) + return NULL; + + *end = start; + + for (l = l->next; l; l = l->next) { + struct attribute *a = l->data; + + if (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 || + bt_uuid_cmp(&a->uuid, &snd_uuid) == 0) + break; + + *end = a->handle; + } + + return attrib; +} + +static uint32_t attrib_create_sdp_new(struct gatt_server *server, + uint16_t handle, const char *name) +{ + sdp_record_t *record; + struct attribute *a; + uint16_t end = 0; + uuid_t svc, gap_uuid; + + a = find_svc_range(server, handle, &end); + + if (a == NULL) + return 0; + + if (a->len == 2) + sdp_uuid16_create(&svc, get_le16(a->data)); + else if (a->len == 16) { + uint8_t be128[16]; + + /* Converting from LE to BE */ + bswap_128(a->data, be128); + sdp_uuid128_create(&svc, be128); + } else + return 0; + + record = server_record_new(&svc, handle, end); + if (record == NULL) + return 0; + + if (name != NULL) + sdp_set_info_attr(record, name, "BlueZ", NULL); + + sdp_uuid16_create(&gap_uuid, GENERIC_ACCESS_PROFILE_ID); + if (sdp_uuid_cmp(&svc, &gap_uuid) == 0) { + sdp_set_url_attr(record, "http://www.bluez.org/", + "http://www.bluez.org/", + "http://www.bluez.org/"); + } + + if (adapter_service_add(server->adapter, record) == 0) + return record->handle; + + sdp_record_free(record); + return 0; +} + +static struct attribute *attrib_db_add_new(struct gatt_server *server, + uint16_t handle, bt_uuid_t *uuid, + int read_req, int write_req, + const uint8_t *value, size_t len) +{ + struct attribute *a; + guint h = handle; + + DBG("handle=0x%04x", handle); + + if (g_list_find_custom(server->database, GUINT_TO_POINTER(h), + handle_cmp)) + return NULL; + + a = g_new0(struct attribute, 1); + a->len = len; + a->data = g_memdup(value, len); + a->handle = handle; + a->uuid = *uuid; + a->read_req = read_req; + a->write_req = write_req; + + server->database = g_list_insert_sorted(server->database, a, + attribute_cmp); + + return a; +} + +static bool g_attrib_is_encrypted(GAttrib *attrib) +{ + BtIOSecLevel sec_level; + GIOChannel *io = g_attrib_get_channel(attrib); + + if (!bt_io_get(io, NULL, BT_IO_OPT_SEC_LEVEL, &sec_level, + BT_IO_OPT_INVALID)) + return FALSE; + + return sec_level > BT_IO_SEC_LOW; +} + +static uint8_t att_check_reqs(struct gatt_channel *channel, uint8_t opcode, + int reqs) +{ + /* FIXME: currently, it is assumed an encrypted link is enough for + * authentication. This will allow to enable the SMP negotiation once + * it is on upstream kernel. High security level should be mapped + * to authentication and medium to encryption permission. */ + if (!channel->encrypted) + channel->encrypted = g_attrib_is_encrypted(channel->attrib); + if (reqs == ATT_AUTHENTICATION && !channel->encrypted) + return ATT_ECODE_AUTHENTICATION; + else if (reqs == ATT_AUTHORIZATION) + return ATT_ECODE_AUTHORIZATION; + + switch (opcode) { + case ATT_OP_READ_BY_GROUP_REQ: + case ATT_OP_READ_BY_TYPE_REQ: + case ATT_OP_READ_REQ: + case ATT_OP_READ_BLOB_REQ: + case ATT_OP_READ_MULTI_REQ: + if (reqs == ATT_NOT_PERMITTED) + return ATT_ECODE_READ_NOT_PERM; + break; + case ATT_OP_PREP_WRITE_REQ: + case ATT_OP_WRITE_REQ: + case ATT_OP_WRITE_CMD: + if (reqs == ATT_NOT_PERMITTED) + return ATT_ECODE_WRITE_NOT_PERM; + break; + } + + return 0; +} + +static uint16_t read_by_group(struct gatt_channel *channel, uint16_t start, + uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, size_t len) +{ + struct att_data_list *adl; + struct attribute *a; + struct group_elem *cur, *old = NULL; + GSList *l, *groups; + GList *dl, *database; + uint16_t length, last_handle, last_size = 0; + uint8_t status; + int i; + + if (start > end || start == 0x0000) + return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, start, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + /* + * Only <> and <> grouping + * types may be used in the Read By Group Type Request. + */ + + if (bt_uuid_cmp(uuid, &prim_uuid) != 0 && + bt_uuid_cmp(uuid, &snd_uuid) != 0) + return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, 0x0000, + ATT_ECODE_UNSUPP_GRP_TYPE, pdu, len); + + last_handle = end; + database = channel->server->database; + for (dl = database, groups = NULL, cur = NULL; dl; dl = dl->next) { + + a = dl->data; + + if (a->handle < start) + continue; + + if (a->handle >= end) + break; + + /* The old group ends when a new one starts */ + if (old && (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 || + bt_uuid_cmp(&a->uuid, &snd_uuid) == 0)) { + old->end = last_handle; + old = NULL; + } + + if (bt_uuid_cmp(&a->uuid, uuid) != 0) { + /* Still inside a service, update its last handle */ + if (old) + last_handle = a->handle; + continue; + } + + if (last_size && (last_size != a->len)) + break; + + status = att_check_reqs(channel, ATT_OP_READ_BY_GROUP_REQ, + a->read_req); + + if (status == 0x00 && a->read_cb) + status = a->read_cb(a, channel->device, + a->cb_user_data); + + if (status) { + g_slist_free_full(groups, g_free); + return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, + a->handle, status, pdu, len); + } + + cur = g_new0(struct group_elem, 1); + cur->handle = a->handle; + cur->data = a->data; + cur->len = a->len; + + /* Attribute Grouping Type found */ + groups = g_slist_append(groups, cur); + + last_size = a->len; + old = cur; + last_handle = cur->handle; + } + + if (groups == NULL) + return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, start, + ATT_ECODE_ATTR_NOT_FOUND, pdu, len); + + if (dl == NULL) + cur->end = a->handle; + else + cur->end = last_handle; + + length = g_slist_length(groups); + + adl = att_data_list_alloc(length, last_size + 4); + if (adl == NULL) { + g_slist_free_full(groups, g_free); + return enc_error_resp(ATT_OP_READ_BY_GROUP_REQ, start, + ATT_ECODE_UNLIKELY, pdu, len); + } + + for (i = 0, l = groups; l; l = l->next, i++) { + uint8_t *value; + + cur = l->data; + + value = (void *) adl->data[i]; + + put_le16(cur->handle, value); + put_le16(cur->end, &value[2]); + /* Attribute Value */ + memcpy(&value[4], cur->data, cur->len); + } + + length = enc_read_by_grp_resp(adl, pdu, len); + + att_data_list_free(adl); + g_slist_free_full(groups, g_free); + + return length; +} + +static uint16_t read_by_type(struct gatt_channel *channel, uint16_t start, + uint16_t end, bt_uuid_t *uuid, + uint8_t *pdu, size_t len) +{ + struct att_data_list *adl; + GSList *l, *types; + GList *dl, *database; + struct attribute *a; + uint16_t num, length; + uint8_t status; + int i; + + if (start > end || start == 0x0000) + return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, start, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + database = channel->server->database; + for (dl = database, length = 0, types = NULL; dl; dl = dl->next) { + + a = dl->data; + + if (a->handle < start) + continue; + + if (a->handle > end) + break; + + if (bt_uuid_cmp(&a->uuid, uuid) != 0) + continue; + + status = att_check_reqs(channel, ATT_OP_READ_BY_TYPE_REQ, + a->read_req); + + if (status == 0x00 && a->read_cb) + status = a->read_cb(a, channel->device, + a->cb_user_data); + + if (status) { + g_slist_free(types); + return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, + a->handle, status, pdu, len); + } + + /* All elements must have the same length */ + if (length == 0) + length = a->len; + else if (a->len != length) + break; + + types = g_slist_append(types, a); + } + + if (types == NULL) + return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, start, + ATT_ECODE_ATTR_NOT_FOUND, pdu, len); + + num = g_slist_length(types); + + /* Handle length plus attribute value length */ + length += 2; + + adl = att_data_list_alloc(num, length); + if (adl == NULL) { + g_slist_free(types); + return enc_error_resp(ATT_OP_READ_BY_TYPE_REQ, start, + ATT_ECODE_UNLIKELY, pdu, len); + } + + for (i = 0, l = types; l; i++, l = l->next) { + uint8_t *value; + + a = l->data; + + value = (void *) adl->data[i]; + + put_le16(a->handle, value); + + /* Attribute Value */ + memcpy(&value[2], a->data, a->len); + } + + length = enc_read_by_type_resp(adl, pdu, len); + + att_data_list_free(adl); + g_slist_free(types); + + return length; +} + +static uint16_t find_info(struct gatt_channel *channel, uint16_t start, + uint16_t end, uint8_t *pdu, size_t len) +{ + struct attribute *a; + struct att_data_list *adl; + GSList *l, *info; + GList *dl, *database; + uint8_t format, last_type = BT_UUID_UNSPEC; + uint16_t length, num; + int i; + + if (start > end || start == 0x0000) + return enc_error_resp(ATT_OP_FIND_INFO_REQ, start, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + database = channel->server->database; + for (dl = database, info = NULL, num = 0; dl; dl = dl->next) { + a = dl->data; + + if (a->handle < start) + continue; + + if (a->handle > end) + break; + + if (last_type == BT_UUID_UNSPEC) + last_type = a->uuid.type; + + if (a->uuid.type != last_type) + break; + + info = g_slist_append(info, a); + num++; + + last_type = a->uuid.type; + } + + if (info == NULL) + return enc_error_resp(ATT_OP_FIND_INFO_REQ, start, + ATT_ECODE_ATTR_NOT_FOUND, pdu, len); + + if (last_type == BT_UUID16) { + length = 2; + format = 0x01; + } else if (last_type == BT_UUID128) { + length = 16; + format = 0x02; + } else { + g_slist_free(info); + return 0; + } + + adl = att_data_list_alloc(num, length + 2); + if (adl == NULL) { + g_slist_free(info); + return enc_error_resp(ATT_OP_FIND_INFO_REQ, start, + ATT_ECODE_UNLIKELY, pdu, len); + } + + for (i = 0, l = info; l; i++, l = l->next) { + uint8_t *value; + + a = l->data; + + value = (void *) adl->data[i]; + + put_le16(a->handle, value); + + /* Attribute Value */ + bt_uuid_to_le(&a->uuid, &value[2]); + } + + length = enc_find_info_resp(format, adl, pdu, len); + + att_data_list_free(adl); + g_slist_free(info); + + return length; +} + +static uint16_t find_by_type(struct gatt_channel *channel, uint16_t start, + uint16_t end, bt_uuid_t *uuid, + const uint8_t *value, size_t vlen, + uint8_t *opdu, size_t mtu) +{ + struct attribute *a; + struct att_range *range; + GSList *matches; + GList *dl, *database; + uint16_t len; + + if (start > end || start == 0x0000) + return enc_error_resp(ATT_OP_FIND_BY_TYPE_REQ, start, + ATT_ECODE_INVALID_HANDLE, opdu, mtu); + + /* Searching first requested handle number */ + database = channel->server->database; + for (dl = database, matches = NULL, range = NULL; dl; dl = dl->next) { + a = dl->data; + + if (a->handle < start) + continue; + + if (a->handle > end) + break; + + /* Primary service? Attribute value matches? */ + if ((bt_uuid_cmp(&a->uuid, uuid) == 0) && (a->len == vlen) && + (memcmp(a->data, value, vlen) == 0)) { + + range = g_new0(struct att_range, 1); + range->start = a->handle; + /* It is allowed to have end group handle the same as + * start handle, for groups with only one attribute. */ + range->end = a->handle; + + matches = g_slist_append(matches, range); + } else if (range) { + /* Update the last found handle or reset the pointer + * to track that a new group started: Primary or + * Secondary service. */ + if (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 || + bt_uuid_cmp(&a->uuid, &snd_uuid) == 0) + range = NULL; + else + range->end = a->handle; + } + } + + if (matches == NULL) + return enc_error_resp(ATT_OP_FIND_BY_TYPE_REQ, start, + ATT_ECODE_ATTR_NOT_FOUND, opdu, mtu); + + len = enc_find_by_type_resp(matches, opdu, mtu); + + g_slist_free_full(matches, g_free); + + return len; +} + +static int read_device_ccc(struct btd_device *device, uint16_t handle, + uint16_t *value) +{ + char *filename; + GKeyFile *key_file; + char group[6]; + char *str; + unsigned int config; + int err = 0; + + filename = btd_device_get_storage_path(device, "ccc"); + if (!filename) { + warn("Unable to get ccc storage path for device"); + return -ENOENT; + } + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + sprintf(group, "%hu", handle); + + str = g_key_file_get_string(key_file, group, "Value", NULL); + if (!str || sscanf(str, "%04X", &config) != 1) + err = -ENOENT; + else + *value = config; + + g_free(str); + g_free(filename); + g_key_file_free(key_file); + + return err; +} + +static uint16_t read_value(struct gatt_channel *channel, uint16_t handle, + uint8_t *pdu, size_t len) +{ + struct attribute *a; + uint8_t status; + GList *l; + uint16_t cccval; + guint h = handle; + + l = g_list_find_custom(channel->server->database, + GUINT_TO_POINTER(h), handle_cmp); + if (!l) + return enc_error_resp(ATT_OP_READ_REQ, handle, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + a = l->data; + + if (bt_uuid_cmp(&ccc_uuid, &a->uuid) == 0 && + read_device_ccc(channel->device, handle, &cccval) == 0) { + uint8_t config[2]; + + put_le16(cccval, config); + return enc_read_resp(config, sizeof(config), pdu, len); + } + + status = att_check_reqs(channel, ATT_OP_READ_REQ, a->read_req); + + if (status == 0x00 && a->read_cb) + status = a->read_cb(a, channel->device, a->cb_user_data); + + if (status) + return enc_error_resp(ATT_OP_READ_REQ, handle, status, pdu, + len); + + return enc_read_resp(a->data, a->len, pdu, len); +} + +static uint16_t read_blob(struct gatt_channel *channel, uint16_t handle, + uint16_t offset, uint8_t *pdu, size_t len) +{ + struct attribute *a; + uint8_t status; + GList *l; + uint16_t cccval; + guint h = handle; + + l = g_list_find_custom(channel->server->database, + GUINT_TO_POINTER(h), handle_cmp); + if (!l) + return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + a = l->data; + + if (a->len < offset) + return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle, + ATT_ECODE_INVALID_OFFSET, pdu, len); + + if (bt_uuid_cmp(&ccc_uuid, &a->uuid) == 0 && + read_device_ccc(channel->device, handle, &cccval) == 0) { + uint8_t config[2]; + + put_le16(cccval, config); + return enc_read_blob_resp(config, sizeof(config), offset, + pdu, len); + } + + status = att_check_reqs(channel, ATT_OP_READ_BLOB_REQ, a->read_req); + + if (status == 0x00 && a->read_cb) + status = a->read_cb(a, channel->device, a->cb_user_data); + + if (status) + return enc_error_resp(ATT_OP_READ_BLOB_REQ, handle, status, + pdu, len); + + return enc_read_blob_resp(a->data, a->len, offset, pdu, len); +} + +static uint16_t write_value(struct gatt_channel *channel, uint16_t handle, + const uint8_t *value, size_t vlen, + uint8_t *pdu, size_t len) +{ + struct attribute *a; + uint8_t status; + GList *l; + guint h = handle; + + l = g_list_find_custom(channel->server->database, + GUINT_TO_POINTER(h), handle_cmp); + if (!l) + return enc_error_resp(ATT_OP_WRITE_REQ, handle, + ATT_ECODE_INVALID_HANDLE, pdu, len); + + a = l->data; + + status = att_check_reqs(channel, ATT_OP_WRITE_REQ, a->write_req); + if (status) + return enc_error_resp(ATT_OP_WRITE_REQ, handle, status, pdu, + len); + + if (bt_uuid_cmp(&ccc_uuid, &a->uuid) != 0) { + + attrib_db_update(channel->server->adapter, handle, NULL, + value, vlen, NULL); + + if (a->write_cb) { + status = a->write_cb(a, channel->device, + a->cb_user_data); + if (status) + return enc_error_resp(ATT_OP_WRITE_REQ, handle, + status, pdu, len); + } + } else { + uint16_t cccval = get_le16(value); + char *filename; + GKeyFile *key_file; + char group[6], value[5]; + char *data; + gsize length = 0; + + filename = btd_device_get_storage_path(channel->device, "ccc"); + if (!filename) { + warn("Unable to get ccc storage path for device"); + return enc_error_resp(ATT_OP_WRITE_REQ, handle, + ATT_ECODE_WRITE_NOT_PERM, + pdu, len); + } + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + sprintf(group, "%hu", handle); + sprintf(value, "%hX", cccval); + g_key_file_set_string(key_file, group, "Value", value); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + + g_free(data); + g_free(filename); + g_key_file_free(key_file); + } + + return enc_write_resp(pdu); +} + +static uint16_t mtu_exchange(struct gatt_channel *channel, uint16_t mtu, + uint8_t *pdu, size_t len) +{ + GError *gerr = NULL; + GIOChannel *io; + uint16_t imtu; + + if (mtu < ATT_DEFAULT_LE_MTU) + return enc_error_resp(ATT_OP_MTU_REQ, 0, + ATT_ECODE_REQ_NOT_SUPP, pdu, len); + + io = g_attrib_get_channel(channel->attrib); + + bt_io_get(io, &gerr, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_INVALID); + if (gerr) { + error("bt_io_get: %s", gerr->message); + g_error_free(gerr); + return enc_error_resp(ATT_OP_MTU_REQ, 0, ATT_ECODE_UNLIKELY, + pdu, len); + } + + channel->mtu = MIN(mtu, imtu); + g_attrib_set_mtu(channel->attrib, channel->mtu); + + return enc_mtu_resp(imtu, pdu, len); +} + +static void channel_remove(struct gatt_channel *channel) +{ + channel->server->clients = g_slist_remove(channel->server->clients, + channel); + channel_free(channel); +} + +static gboolean channel_watch_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + channel_remove(user_data); + + return FALSE; +} + +static void channel_handler(const uint8_t *ipdu, uint16_t len, + gpointer user_data) +{ + struct gatt_channel *channel = user_data; + uint8_t opdu[channel->mtu]; + uint16_t length, start, end, mtu, offset; + bt_uuid_t uuid; + uint8_t status = 0; + size_t vlen; + uint8_t *value = g_attrib_get_buffer(channel->attrib, &vlen); + + DBG("op 0x%02x", ipdu[0]); + + if (len > vlen) { + error("Too much data on ATT socket"); + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + switch (ipdu[0]) { + case ATT_OP_READ_BY_GROUP_REQ: + length = dec_read_by_grp_req(ipdu, len, &start, &end, &uuid); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = read_by_group(channel, start, end, &uuid, opdu, + channel->mtu); + break; + case ATT_OP_READ_BY_TYPE_REQ: + length = dec_read_by_type_req(ipdu, len, &start, &end, &uuid); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = read_by_type(channel, start, end, &uuid, opdu, + channel->mtu); + break; + case ATT_OP_READ_REQ: + length = dec_read_req(ipdu, len, &start); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = read_value(channel, start, opdu, channel->mtu); + break; + case ATT_OP_READ_BLOB_REQ: + length = dec_read_blob_req(ipdu, len, &start, &offset); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = read_blob(channel, start, offset, opdu, channel->mtu); + break; + case ATT_OP_MTU_REQ: + if (!channel->le) { + status = ATT_ECODE_REQ_NOT_SUPP; + goto done; + } + + length = dec_mtu_req(ipdu, len, &mtu); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = mtu_exchange(channel, mtu, opdu, channel->mtu); + break; + case ATT_OP_FIND_INFO_REQ: + length = dec_find_info_req(ipdu, len, &start, &end); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = find_info(channel, start, end, opdu, channel->mtu); + break; + case ATT_OP_WRITE_REQ: + length = dec_write_req(ipdu, len, &start, value, &vlen); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = write_value(channel, start, value, vlen, opdu, + channel->mtu); + break; + case ATT_OP_WRITE_CMD: + length = dec_write_cmd(ipdu, len, &start, value, &vlen); + if (length > 0) + write_value(channel, start, value, vlen, opdu, + channel->mtu); + return; + case ATT_OP_FIND_BY_TYPE_REQ: + length = dec_find_by_type_req(ipdu, len, &start, &end, + &uuid, value, &vlen); + if (length == 0) { + status = ATT_ECODE_INVALID_PDU; + goto done; + } + + length = find_by_type(channel, start, end, &uuid, value, vlen, + opdu, channel->mtu); + break; + case ATT_OP_HANDLE_CNF: + return; + case ATT_OP_HANDLE_IND: + case ATT_OP_HANDLE_NOTIFY: + /* The attribute client is already handling these */ + return; + case ATT_OP_READ_MULTI_REQ: + case ATT_OP_PREP_WRITE_REQ: + case ATT_OP_EXEC_WRITE_REQ: + default: + DBG("Unsupported request 0x%02x", ipdu[0]); + status = ATT_ECODE_REQ_NOT_SUPP; + goto done; + } + + if (length == 0) + status = ATT_ECODE_IO; + +done: + if (status) + length = enc_error_resp(ipdu[0], 0x0000, status, opdu, + channel->mtu); + + g_attrib_send(channel->attrib, 0, opdu, length, NULL, NULL, NULL); +} + +GAttrib *attrib_from_device(struct btd_device *device) +{ + struct btd_adapter *adapter = device_get_adapter(device); + struct gatt_server *server; + GSList *l; + + l = g_slist_find_custom(servers, adapter, adapter_cmp); + if (!l) + return NULL; + + server = l->data; + + for (l = server->clients; l; l = l->next) { + struct gatt_channel *channel = l->data; + + if (channel->device == device) + return g_attrib_ref(channel->attrib); + } + + return NULL; +} + +guint attrib_channel_attach(GAttrib *attrib) +{ + struct gatt_server *server; + struct btd_device *device; + struct gatt_channel *channel; + bdaddr_t src, dst; + GIOChannel *io; + GError *gerr = NULL; + uint8_t bdaddr_type; + uint16_t cid; + guint mtu = 0; + + io = g_attrib_get_channel(attrib); + + bt_io_get(io, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST_TYPE, &bdaddr_type, + BT_IO_OPT_CID, &cid, + BT_IO_OPT_IMTU, &mtu, + BT_IO_OPT_INVALID); + if (gerr) { + error("bt_io_get: %s", gerr->message); + g_error_free(gerr); + return 0; + } + + server = find_gatt_server(&src); + if (server == NULL) + return 0; + + channel = g_new0(struct gatt_channel, 1); + channel->server = server; + + device = btd_adapter_find_device(server->adapter, &dst, bdaddr_type); + if (device == NULL) { + error("Device object not found for attrib server"); + g_free(channel); + return 0; + } + + if (!device_is_bonded(device, bdaddr_type)) { + char *filename; + + filename = btd_device_get_storage_path(device, "ccc"); + if (filename) { + unlink(filename); + g_free(filename); + } + } + + if (cid != ATT_CID) { + channel->le = FALSE; + channel->mtu = mtu; + } else { + channel->le = TRUE; + channel->mtu = ATT_DEFAULT_LE_MTU; + } + + channel->attrib = g_attrib_ref(attrib); + channel->id = g_attrib_register(channel->attrib, GATTRIB_ALL_REQS, + GATTRIB_ALL_HANDLES, channel_handler, channel, NULL); + + channel->cleanup_id = g_io_add_watch(io, G_IO_HUP, channel_watch_cb, + channel); + + channel->device = btd_device_ref(device); + + server->clients = g_slist_append(server->clients, channel); + + return channel->id; +} + +static struct gatt_channel *find_channel(guint id) +{ + GSList *l; + + for (l = servers; l; l = g_slist_next(l)) { + struct gatt_server *server = l->data; + GSList *c; + + for (c = server->clients; c; c = g_slist_next(c)) { + struct gatt_channel *channel = c->data; + + if (channel->id == id) + return channel; + } + } + + return NULL; +} + +gboolean attrib_channel_detach(GAttrib *attrib, guint id) +{ + struct gatt_channel *channel; + + channel = find_channel(id); + if (channel == NULL) + return FALSE; + + g_attrib_unregister(channel->attrib, channel->id); + channel_remove(channel); + + return TRUE; +} + +static void connect_event(GIOChannel *io, GError *gerr, void *user_data) +{ + struct btd_adapter *adapter; + struct btd_device *device; + uint8_t dst_type; + bdaddr_t src, dst; + + DBG(""); + + if (gerr) { + error("%s", gerr->message); + return; + } + + bt_io_get(io, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST_TYPE, &dst_type, + BT_IO_OPT_INVALID); + if (gerr) { + error("bt_io_get: %s", gerr->message); + g_error_free(gerr); + return; + } + + adapter = adapter_find(&src); + if (!adapter) + return; + + device = btd_adapter_get_device(adapter, &dst, dst_type); + if (!device) + return; + + device_attach_att(device, io); +} + +static gboolean register_core_services(struct gatt_server *server) +{ + uint8_t atval[256]; + bt_uuid_t uuid; + uint16_t appearance = 0x0000; + + /* GAP service: primary service definition */ + bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + put_le16(GENERIC_ACCESS_PROFILE_ID, &atval[0]); + attrib_db_add_new(server, 0x0001, &uuid, ATT_NONE, ATT_NOT_PERMITTED, + atval, 2); + + /* GAP service: device name characteristic */ + server->name_handle = 0x0006; + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = GATT_CHR_PROP_READ; + put_le16(server->name_handle, &atval[1]); + put_le16(GATT_CHARAC_DEVICE_NAME, &atval[3]); + attrib_db_add_new(server, 0x0004, &uuid, ATT_NONE, ATT_NOT_PERMITTED, + atval, 5); + + /* GAP service: device name attribute */ + bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); + attrib_db_add_new(server, server->name_handle, &uuid, ATT_NONE, + ATT_NOT_PERMITTED, NULL, 0); + + /* GAP service: device appearance characteristic */ + server->appearance_handle = 0x0008; + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = GATT_CHR_PROP_READ; + put_le16(server->appearance_handle, &atval[1]); + put_le16(GATT_CHARAC_APPEARANCE, &atval[3]); + attrib_db_add_new(server, 0x0007, &uuid, ATT_NONE, ATT_NOT_PERMITTED, + atval, 5); + + /* GAP service: device appearance attribute */ + bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE); + put_le16(appearance, &atval[0]); + attrib_db_add_new(server, server->appearance_handle, &uuid, ATT_NONE, + ATT_NOT_PERMITTED, atval, 2); + server->gap_sdp_handle = attrib_create_sdp_new(server, 0x0001, + "Generic Access Profile"); + if (server->gap_sdp_handle == 0) { + error("Failed to register GAP service record"); + return FALSE; + } + + /* GATT service: primary service definition */ + bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + put_le16(GENERIC_ATTRIB_PROFILE_ID, &atval[0]); + attrib_db_add_new(server, 0x0010, &uuid, ATT_NONE, ATT_NOT_PERMITTED, + atval, 2); + + server->gatt_sdp_handle = attrib_create_sdp_new(server, 0x0010, + "Generic Attribute Profile"); + if (server->gatt_sdp_handle == 0) { + error("Failed to register GATT service record"); + return FALSE; + } + + return TRUE; +} + +int btd_adapter_gatt_server_start(struct btd_adapter *adapter) +{ + struct gatt_server *server; + GError *gerr = NULL; + const bdaddr_t *addr; + + DBG("Start GATT server in hci%d", btd_adapter_get_index(adapter)); + + server = g_new0(struct gatt_server, 1); + server->adapter = btd_adapter_ref(adapter); + + addr = btd_adapter_get_address(server->adapter); + + /* BR/EDR socket */ + server->l2cap_io = bt_io_listen(connect_event, NULL, NULL, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, addr, + BT_IO_OPT_PSM, ATT_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + + if (server->l2cap_io == NULL) { + error("%s", gerr->message); + g_error_free(gerr); + gatt_server_free(server); + return -1; + } + + if (!register_core_services(server)) { + gatt_server_free(server); + return -1; + } + + /* LE socket */ + server->le_io = bt_io_listen(connect_event, NULL, + &server->le_io, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, addr, + BT_IO_OPT_SOURCE_TYPE, + btd_adapter_get_address_type(adapter), + BT_IO_OPT_CID, ATT_CID, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + + if (server->le_io == NULL) { + error("%s", gerr->message); + g_error_free(gerr); + /* Doesn't have LE support, continue */ + } + + servers = g_slist_prepend(servers, server); + return 0; +} + +void btd_adapter_gatt_server_stop(struct btd_adapter *adapter) +{ + struct gatt_server *server; + GSList *l; + + l = g_slist_find_custom(servers, adapter, adapter_cmp); + if (l == NULL) + return; + + DBG("Stop GATT server in hci%d", btd_adapter_get_index(adapter)); + + server = l->data; + servers = g_slist_remove(servers, server); + gatt_server_free(server); +} + +uint32_t attrib_create_sdp(struct btd_adapter *adapter, uint16_t handle, + const char *name) +{ + GSList *l; + + l = g_slist_find_custom(servers, adapter, adapter_cmp); + if (l == NULL) + return 0; + + return attrib_create_sdp_new(l->data, handle, name); +} + +void attrib_free_sdp(struct btd_adapter *adapter, uint32_t sdp_handle) +{ + adapter_service_remove(adapter, sdp_handle); +} + +static uint16_t find_uuid16_avail(struct btd_adapter *adapter, uint16_t nitems) +{ + struct gatt_server *server; + uint16_t handle; + GSList *l; + GList *dl; + + l = g_slist_find_custom(servers, adapter, adapter_cmp); + if (l == NULL) + return 0; + + server = l->data; + if (server->database == NULL) + return 0x0001; + + for (dl = server->database, handle = 0x0001; dl; dl = dl->next) { + struct attribute *a = dl->data; + + if ((bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 || + bt_uuid_cmp(&a->uuid, &snd_uuid) == 0) && + a->handle - handle >= nitems) + /* Note: the range above excludes the current handle */ + return handle; + + if (a->len == 16 && (bt_uuid_cmp(&a->uuid, &prim_uuid) == 0 || + bt_uuid_cmp(&a->uuid, &snd_uuid) == 0)) { + /* 128 bit UUID service definition */ + return 0; + } + + if (a->handle == 0xffff) + return 0; + + handle = a->handle + 1; + } + + if (0xffff - handle + 1 >= nitems) + return handle; + + return 0; +} + +static uint16_t find_uuid128_avail(struct btd_adapter *adapter, uint16_t nitems) +{ + uint16_t handle = 0, end = 0xffff; + struct gatt_server *server; + GList *dl; + GSList *l; + + l = g_slist_find_custom(servers, adapter, adapter_cmp); + if (l == NULL) + return 0; + + server = l->data; + if (server->database == NULL) + return 0xffff - nitems + 1; + + for (dl = g_list_last(server->database); dl; dl = dl->prev) { + struct attribute *a = dl->data; + + if (handle == 0) + handle = a->handle; + + if (bt_uuid_cmp(&a->uuid, &prim_uuid) != 0 && + bt_uuid_cmp(&a->uuid, &snd_uuid) != 0) + continue; + + if (end - handle >= nitems) + return end - nitems + 1; + + if (a->len == 2) { + /* 16 bit UUID service definition */ + return 0; + } + + if (a->handle == 0x0001) + return 0; + + end = a->handle - 1; + handle = 0; + } + + if (end - 0x0001 >= nitems) + return end - nitems + 1; + + return 0; +} + +uint16_t attrib_db_find_avail(struct btd_adapter *adapter, bt_uuid_t *svc_uuid, + uint16_t nitems) +{ + btd_assert(nitems > 0); + + if (svc_uuid->type == BT_UUID16) + return find_uuid16_avail(adapter, nitems); + else if (svc_uuid->type == BT_UUID128) + return find_uuid128_avail(adapter, nitems); + else { + char uuidstr[MAX_LEN_UUID_STR]; + + bt_uuid_to_string(svc_uuid, uuidstr, MAX_LEN_UUID_STR); + error("Service uuid: %s is neither a 16-bit nor a 128-bit uuid", + uuidstr); + return 0; + } +} + +struct attribute *attrib_db_add(struct btd_adapter *adapter, uint16_t handle, + bt_uuid_t *uuid, int read_req, + int write_req, const uint8_t *value, + size_t len) +{ + GSList *l; + + l = g_slist_find_custom(servers, adapter, adapter_cmp); + if (l == NULL) + return NULL; + + return attrib_db_add_new(l->data, handle, uuid, read_req, write_req, + value, len); +} + +int attrib_db_update(struct btd_adapter *adapter, uint16_t handle, + bt_uuid_t *uuid, const uint8_t *value, + size_t len, struct attribute **attr) +{ + struct gatt_server *server; + struct attribute *a; + GSList *l; + GList *dl; + guint h = handle; + + l = g_slist_find_custom(servers, adapter, adapter_cmp); + if (l == NULL) + return -ENOENT; + + server = l->data; + + DBG("handle=0x%04x", handle); + + dl = g_list_find_custom(server->database, GUINT_TO_POINTER(h), + handle_cmp); + if (dl == NULL) + return -ENOENT; + + a = dl->data; + + a->data = g_try_realloc(a->data, len); + if (len && a->data == NULL) + return -ENOMEM; + + a->len = len; + memcpy(a->data, value, len); + + if (uuid != NULL) + a->uuid = *uuid; + + if (attr) + *attr = a; + + return 0; +} + +int attrib_db_del(struct btd_adapter *adapter, uint16_t handle) +{ + struct gatt_server *server; + struct attribute *a; + GSList *l; + GList *dl; + guint h = handle; + + l = g_slist_find_custom(servers, adapter, adapter_cmp); + if (l == NULL) + return -ENOENT; + + server = l->data; + + DBG("handle=0x%04x", handle); + + dl = g_list_find_custom(server->database, GUINT_TO_POINTER(h), + handle_cmp); + if (dl == NULL) + return -ENOENT; + + a = dl->data; + server->database = g_list_remove(server->database, a); + g_free(a->data); + g_free(a); + + return 0; +} + +int attrib_gap_set(struct btd_adapter *adapter, uint16_t uuid, + const uint8_t *value, size_t len) +{ + struct gatt_server *server; + uint16_t handle; + GSList *l; + + l = g_slist_find_custom(servers, adapter, adapter_cmp); + if (l == NULL) + return -ENOENT; + + server = l->data; + + /* FIXME: Missing Privacy and Reconnection Address */ + + switch (uuid) { + case GATT_CHARAC_DEVICE_NAME: + handle = server->name_handle; + break; + case GATT_CHARAC_APPEARANCE: + handle = server->appearance_handle; + break; + default: + return -ENOSYS; + } + + return attrib_db_update(adapter, handle, NULL, value, len, NULL); +} diff --git a/src/attrib-server.h b/src/attrib-server.h new file mode 100644 index 0000000..063cb66 --- /dev/null +++ b/src/attrib-server.h @@ -0,0 +1,42 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +uint16_t attrib_db_find_avail(struct btd_adapter *adapter, bt_uuid_t *svc_uuid, + uint16_t nitems); +struct attribute *attrib_db_add(struct btd_adapter *adapter, uint16_t handle, + bt_uuid_t *uuid, int read_req, + int write_req, const uint8_t *value, + size_t len); +int attrib_db_update(struct btd_adapter *adapter, uint16_t handle, + bt_uuid_t *uuid, const uint8_t *value, + size_t len, struct attribute **attr); +int attrib_db_del(struct btd_adapter *adapter, uint16_t handle); +int attrib_gap_set(struct btd_adapter *adapter, uint16_t uuid, + const uint8_t *value, size_t len); +uint32_t attrib_create_sdp(struct btd_adapter *adapter, uint16_t handle, + const char *name); +void attrib_free_sdp(struct btd_adapter *adapter, uint32_t sdp_handle); +GAttrib *attrib_from_device(struct btd_device *device); +guint attrib_channel_attach(GAttrib *attrib); +gboolean attrib_channel_detach(GAttrib *attrib, guint id); diff --git a/src/backtrace.c b/src/backtrace.c new file mode 100644 index 0000000..c438733 --- /dev/null +++ b/src/backtrace.c @@ -0,0 +1,135 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#ifdef HAVE_BACKTRACE_SUPPORT +#include +#include +#endif + +#include "src/log.h" +#include "src/backtrace.h" + +void btd_backtrace_init(void) +{ +#ifdef HAVE_BACKTRACE_SUPPORT + void *frames[1]; + + /* + * initialize the backtracer, since the ctor calls dlopen(), which + * calls malloc(), which isn't signal-safe. + */ + backtrace(frames, 1); +#endif +} + +void btd_backtrace(uint16_t index) +{ +#ifdef HAVE_BACKTRACE_SUPPORT + char *debuginfo_path = NULL; + const Dwfl_Callbacks callbacks = { + .find_debuginfo = dwfl_standard_find_debuginfo, + .find_elf = dwfl_linux_proc_find_elf, + .debuginfo_path = &debuginfo_path, + }; + Dwfl *dwfl; + void *frames[48]; + int n, n_ptrs; + + dwfl = dwfl_begin(&callbacks); + + if (dwfl_linux_proc_report(dwfl, getpid())) + goto done; + + dwfl_report_end(dwfl, NULL, NULL); + + n_ptrs = backtrace(frames, 48); + if (n_ptrs < 1) + goto done; + + btd_error(index, "++++++++ backtrace ++++++++"); + + for (n = 1; n < n_ptrs; n++) { + GElf_Addr addr = (uintptr_t) frames[n]; + GElf_Sym sym; + GElf_Word shndx; + Dwfl_Module *module = dwfl_addrmodule(dwfl, addr); + Dwfl_Line *line; + const char *name, *modname; + + if (!module) { + btd_error(index, "#%-2u ?? [%#" PRIx64 "]", n, addr); + continue; + } + + name = dwfl_module_addrsym(module, addr, &sym, &shndx); + if (!name) { + modname = dwfl_module_info(module, NULL, NULL, NULL, + NULL, NULL, NULL, NULL); + btd_error(index, "#%-2u ?? (%s) [%#" PRIx64 "]", + n, modname, addr); + continue; + } + + line = dwfl_module_getsrc(module, addr); + if (line) { + int lineno; + const char *src = dwfl_lineinfo(line, NULL, &lineno, + NULL, NULL, NULL); + + if (src) { + btd_error(index, "#%-2u %s+%#" PRIx64 " " + "(%s:%d) [%#" PRIx64 "]", + n, name, addr - sym.st_value, + src, lineno, addr); + continue; + } + } + + modname = dwfl_module_info(module, NULL, NULL, NULL, + NULL, NULL, NULL, NULL); + btd_error(index, "#%-2u %s+%#" PRIx64 " (%s) [%#" PRIx64 "]", + n, name, addr - sym.st_value, + modname, addr); + } + + btd_error(index, "+++++++++++++++++++++++++++"); + +done: + dwfl_end(dwfl); +#endif +} + +void btd_assertion_message_expr(const char *file, int line, + const char *func, const char *expr) +{ + btd_error(0xffff, "Assertion failed: (%s) %s:%d in %s", + expr, file, line, func); + btd_backtrace(0xffff); +} diff --git a/src/backtrace.h b/src/backtrace.h new file mode 100644 index 0000000..b3eef6d --- /dev/null +++ b/src/backtrace.h @@ -0,0 +1,35 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +void btd_backtrace_init(void); +void btd_backtrace(uint16_t index); + +void btd_assertion_message_expr(const char *file, int line, + const char *func, const char *expr); + +#define btd_assert(expr) do { \ + if (expr) ; else \ + btd_assertion_message_expr(__FILE__, __LINE__, __func__, #expr); \ + } while (0) diff --git a/src/bluetooth.conf b/src/bluetooth.conf new file mode 100644 index 0000000..8a1e258 --- /dev/null +++ b/src/bluetooth.conf @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/bluetooth.service.in b/src/bluetooth.service.in new file mode 100644 index 0000000..f9faaa4 --- /dev/null +++ b/src/bluetooth.service.in @@ -0,0 +1,20 @@ +[Unit] +Description=Bluetooth service +Documentation=man:bluetoothd(8) +ConditionPathIsDirectory=/sys/class/bluetooth + +[Service] +Type=dbus +BusName=org.bluez +ExecStart=@pkglibexecdir@/bluetoothd +NotifyAccess=main +#WatchdogSec=10 +#Restart=on-failure +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE +LimitNPROC=1 +ProtectHome=true +ProtectSystem=full + +[Install] +WantedBy=bluetooth.target +Alias=dbus-org.bluez.service diff --git a/src/bluetooth.ver b/src/bluetooth.ver new file mode 100644 index 0000000..214fa8a --- /dev/null +++ b/src/bluetooth.ver @@ -0,0 +1,12 @@ +{ + global: + btd_*; + g_dbus_*; + info; + error; + debug; + baswap; + ba2str; + local: + *; +}; diff --git a/src/bluetoothd.8.in b/src/bluetoothd.8.in new file mode 100644 index 0000000..d61dcc5 --- /dev/null +++ b/src/bluetoothd.8.in @@ -0,0 +1,63 @@ +.\" +.TH "BLUETOOTHD" "8" "March 2004" "Bluetooth daemon" "System management commands" +.SH "NAME" +bluetoothd \- Bluetooth daemon + +.SH "SYNOPSIS" +.B bluetoothd [--version] | [--help] + +.B bluetoothd [--nodetach] [--compat] [--experimental] [--debug=] [--plugin=] [--noplugin=] + +.SH "DESCRIPTION" +This manual page documents briefly the +.B bluetoothd +daemon, which manages all the Bluetooth devices. +.B bluetoothd +can also provide a number of services via the D-Bus message bus +system. +.SH "OPTIONS" +.TP +.B -v, --version +Print bluetoothd version and exit. +.TP +.B -h, --help +Print bluetoothd options and exit. +.TP +.B -n, --nodetach +Enable logging in foreground. Directs log output to the controlling terminal \ +in addition to syslog. +.TP +.B -f, --configfile +Specifies an explicit config file path instead of relying on the default path \ +(@CONFIGDIR@/main.conf) for the config file. +.TP +.B -d, --debug=::... +Sets how much information bluetoothd sends to the log destination (usually \ +syslog's "daemon" facility). If the file options are omitted, then debugging \ +information from all the source files are printed. If file options are \ +present, then only debug prints from that source file are printed. The option \ +can be a pattern containing "*" and "?" characters. + +Example: --debug=src/adapter.c:src/agent.c +.TP +.B -p, --plugin=,,.. +Load these plugins only. The option can be a pattern containing "*" and "?" \ +characters. +.TP +.B -P, --noplugin=,,.. +Never load these plugins. The option can be a pattern containing "*" and "?" \ +characters. +.TP +.B -C, --compat +Provide deprecated command line interfaces. +.TP +.B -E, --experimental +Enable experimental interfaces. Those interfaces are not guaranteed to be +compatible or present in future releases. +.SH "FILES" +.TP +.I @CONFIGDIR@/main.conf +Location of the global configuration file. + +.SH "AUTHOR" +This manual page was written by Marcel Holtmann, Philipp Matthias Hahn and Fredrik Noring. diff --git a/src/dbus-common.c b/src/dbus-common.c new file mode 100644 index 0000000..6e2097a --- /dev/null +++ b/src/dbus-common.c @@ -0,0 +1,163 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2005-2007 Johan Hedberg + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include +#include + +#include "gdbus/gdbus.h" + +#include "log.h" + +#include "dbus-common.h" + +static DBusConnection *connection = NULL; + +void dict_append_entry(DBusMessageIter *dict, + const char *key, int type, void *val) +{ + g_dbus_dict_append_entry(dict, key, type, val); +} + +void dict_append_array(DBusMessageIter *dict, const char *key, int type, + void *val, int n_elements) +{ + g_dbus_dict_append_array(dict, key, type, val, n_elements); +} + +void set_dbus_connection(DBusConnection *conn) +{ + connection = conn; +} + +DBusConnection *btd_get_dbus_connection(void) +{ + return connection; +} + +const char *class_to_icon(uint32_t class) +{ + switch ((class & 0x1f00) >> 8) { + case 0x01: + return "computer"; + case 0x02: + switch ((class & 0xfc) >> 2) { + case 0x01: + case 0x02: + case 0x03: + case 0x05: + return "phone"; + case 0x04: + return "modem"; + } + break; + case 0x03: + return "network-wireless"; + case 0x04: + switch ((class & 0xfc) >> 2) { + case 0x01: + case 0x02: + return "audio-card"; /* Headset */ + case 0x06: + return "audio-card"; /* Headphone */ + case 0x0b: /* VCR */ + case 0x0c: /* Video Camera */ + case 0x0d: /* Camcorder */ + return "camera-video"; + default: + return "audio-card"; /* Other audio device */ + } + break; + case 0x05: + switch ((class & 0xc0) >> 6) { + case 0x00: + switch ((class & 0x1e) >> 2) { + case 0x01: + case 0x02: + return "input-gaming"; + } + break; + case 0x01: + return "input-keyboard"; + case 0x02: + switch ((class & 0x1e) >> 2) { + case 0x05: + return "input-tablet"; + default: + return "input-mouse"; + } + } + break; + case 0x06: + if (class & 0x80) + return "printer"; + if (class & 0x20) + return "camera-photo"; + break; + } + + return NULL; +} + +const char *gap_appearance_to_icon(uint16_t appearance) +{ + switch ((appearance & 0xffc0) >> 6) { + case 0x00: + return "unknown"; + case 0x01: + return "phone"; + case 0x02: + return "computer"; + case 0x05: + return "video-display"; + case 0x0a: + return "multimedia-player"; + case 0x0b: + return "scanner"; + case 0x0f: /* HID Generic */ + switch (appearance & 0x3f) { + case 0x01: + return "input-keyboard"; + case 0x02: + return "input-mouse"; + case 0x03: + case 0x04: + return "input-gaming"; + case 0x05: + return "input-tablet"; + case 0x08: + return "scanner"; + } + break; + } + + return NULL; +} diff --git a/src/dbus-common.h b/src/dbus-common.h new file mode 100644 index 0000000..fbf4acd --- /dev/null +++ b/src/dbus-common.h @@ -0,0 +1,33 @@ +/* * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void dict_append_entry(DBusMessageIter *dict, + const char *key, int type, void *val); +void dict_append_array(DBusMessageIter *dict, const char *key, int type, + void *val, int n_elements); + +void set_dbus_connection(DBusConnection *conn); +DBusConnection *btd_get_dbus_connection(void); + +const char *class_to_icon(uint32_t class); +const char *gap_appearance_to_icon(uint16_t appearance); diff --git a/src/device.c b/src/device.c new file mode 100644 index 0000000..263f60a --- /dev/null +++ b/src/device.c @@ -0,0 +1,6551 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "log.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/shared/gatt-server.h" +#include "src/shared/ad.h" +#include "btio/btio.h" +#include "lib/mgmt.h" +#include "attrib/att.h" +#include "hcid.h" +#include "adapter.h" +#include "gatt-database.h" +#include "attrib/gattrib.h" +#include "device.h" +#include "gatt-client.h" +#include "profile.h" +#include "service.h" +#include "dbus-common.h" +#include "error.h" +#include "uuid-helper.h" +#include "sdp-client.h" +#include "attrib/gatt.h" +#include "agent.h" +#include "textfile.h" +#include "storage.h" +#include "attrib-server.h" +#include "eir.h" + +#define DISCONNECT_TIMER 2 +#define DISCOVERY_TIMER 1 +#define INVALID_FLAGS 0xff + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define RSSI_THRESHOLD 8 + +#define GATT_PRIM_SVC_UUID_STR "2800" +#define GATT_SND_SVC_UUID_STR "2801" +#define GATT_INCLUDE_UUID_STR "2802" +#define GATT_CHARAC_UUID_STR "2803" + +static DBusConnection *dbus_conn = NULL; +static unsigned service_state_cb_id; + +struct btd_disconnect_data { + guint id; + disconnect_watch watch; + void *user_data; + GDestroyNotify destroy; +}; + +struct bonding_req { + DBusMessage *msg; + guint listener_id; + struct btd_device *device; + uint8_t bdaddr_type; + struct agent *agent; + struct btd_adapter_pin_cb_iter *cb_iter; + uint8_t status; + guint retry_timer; + struct timespec attempt_start_time; + long last_attempt_duration_ms; +}; + +typedef enum { + AUTH_TYPE_PINCODE, + AUTH_TYPE_PASSKEY, + AUTH_TYPE_CONFIRM, + AUTH_TYPE_NOTIFY_PASSKEY, + AUTH_TYPE_NOTIFY_PINCODE, +} auth_type_t; + +struct authentication_req { + auth_type_t type; + struct agent *agent; + struct btd_device *device; + uint8_t addr_type; + uint32_t passkey; + char *pincode; + gboolean secure; +}; + +enum { + BROWSE_SDP, + BROWSE_GATT +}; + +struct browse_req { + DBusMessage *msg; + struct btd_device *device; + uint8_t type; + GSList *match_uuids; + GSList *profiles_added; + sdp_list_t *records; + int search_uuid; + int reconnect_attempt; + guint listener_id; + uint16_t sdp_flags; +}; + +struct included_search { + struct browse_req *req; + GSList *services; + GSList *current; +}; + +struct svc_callback { + unsigned int id; + guint idle_id; + struct btd_device *dev; + device_svc_cb_t func; + void *user_data; +}; + +/* Per-bearer (LE or BR/EDR) device state */ +struct bearer_state { + bool paired; + bool bonded; + bool connected; + bool svc_resolved; +}; + +struct csrk_info { + uint8_t key[16]; + uint32_t counter; +}; + +struct btd_device { + int ref_count; + + bdaddr_t conn_bdaddr; + uint8_t conn_bdaddr_type; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + char *path; + bool bredr; + bool le; + bool pending_paired; /* "Paired" waiting for SDP */ + bool svc_refreshed; + GSList *svc_callbacks; + GSList *eir_uuids; + struct bt_ad *ad; + uint8_t ad_flags[1]; + char name[MAX_NAME_LENGTH + 1]; + char *alias; + uint32_t class; + uint16_t vendor_src; + uint16_t vendor; + uint16_t product; + uint16_t version; + uint16_t appearance; + char *modalias; + struct btd_adapter *adapter; + GSList *uuids; + GSList *primaries; /* List of primary services */ + GSList *services; /* List of btd_service */ + GSList *pending; /* Pending services */ + GSList *watches; /* List of disconnect_data */ + bool temporary; + bool connectable; + guint disconn_timer; + guint discov_timer; + struct browse_req *browse; /* service discover request */ + struct bonding_req *bonding; + struct authentication_req *authr; /* authentication request */ + GSList *disconnects; /* disconnects message */ + DBusMessage *connect; /* connect message */ + DBusMessage *disconnect; /* disconnect message */ + GAttrib *attrib; + + struct bt_att *att; /* The new ATT transport */ + uint16_t att_mtu; /* The ATT MTU */ + unsigned int att_disconn_id; + + /* + * TODO: For now, device creates and owns the client-role gatt_db, but + * this needs to be persisted in a more central place so that proper + * attribute cache support can be built. + */ + struct gatt_db *db; /* GATT db cache */ + unsigned int db_id; + struct bt_gatt_client *client; /* GATT client instance */ + struct bt_gatt_server *server; /* GATT server instance */ + unsigned int gatt_ready_id; + + struct btd_gatt_client *client_dbus; + + struct bearer_state bredr_state; + struct bearer_state le_state; + + struct csrk_info *local_csrk; + struct csrk_info *remote_csrk; + uint8_t ltk_enc_size; + + sdp_list_t *tmp_records; + + time_t bredr_seen; + time_t le_seen; + + gboolean trusted; + gboolean blocked; + gboolean auto_connect; + gboolean disable_auto_connect; + gboolean general_connect; + + bool legacy; + int8_t rssi; + int8_t tx_power; + + GIOChannel *att_io; + guint store_id; +}; + +static const uint16_t uuid_list[] = { + L2CAP_UUID, + PNP_INFO_SVCLASS_ID, + PUBLIC_BROWSE_GROUP, + 0 +}; + +static int device_browse_gatt(struct btd_device *device, DBusMessage *msg); +static int device_browse_sdp(struct btd_device *device, DBusMessage *msg); + +static struct bearer_state *get_state(struct btd_device *dev, + uint8_t bdaddr_type) +{ + if (bdaddr_type == BDADDR_BREDR) + return &dev->bredr_state; + else + return &dev->le_state; +} + +static GSList *find_service_with_profile(GSList *list, struct btd_profile *p) +{ + GSList *l; + + for (l = list; l != NULL; l = g_slist_next(l)) { + struct btd_service *service = l->data; + + if (btd_service_get_profile(service) == p) + return l; + } + + return NULL; +} + +static GSList *find_service_with_state(GSList *list, + btd_service_state_t state) +{ + GSList *l; + + for (l = list; l != NULL; l = g_slist_next(l)) { + struct btd_service *service = l->data; + + if (btd_service_get_state(service) == state) + return l; + } + + return NULL; +} + +static GSList *find_service_with_uuid(GSList *list, char *uuid) +{ + GSList *l; + + for (l = list; l != NULL; l = g_slist_next(l)) { + struct btd_service *service = l->data; + struct btd_profile *profile = btd_service_get_profile(service); + + if (bt_uuid_strcmp(profile->remote_uuid, uuid) == 0) + return l; + } + + return NULL; +} + +static void update_technologies(GKeyFile *file, struct btd_device *dev) +{ + const char *list[2]; + size_t len = 0; + + if (dev->bredr) + list[len++] = "BR/EDR"; + + if (dev->le) { + const char *type; + + if (dev->bdaddr_type == BDADDR_LE_PUBLIC) + type = "public"; + else + type = "static"; + + g_key_file_set_string(file, "General", "AddressType", type); + + list[len++] = "LE"; + } + + g_key_file_set_string_list(file, "General", "SupportedTechnologies", + list, len); +} + +static void store_csrk(struct csrk_info *csrk, GKeyFile *key_file, + const char *group) +{ + char key[33]; + int i; + + for (i = 0; i < 16; i++) + sprintf(key + (i * 2), "%2.2X", csrk->key[i]); + + g_key_file_set_string(key_file, group, "Key", key); + g_key_file_set_integer(key_file, group, "Counter", csrk->counter); +} + +static gboolean store_device_info_cb(gpointer user_data) +{ + struct btd_device *device = user_data; + GKeyFile *key_file; + char filename[PATH_MAX]; + char device_addr[18]; + char *str; + char class[9]; + char **uuids = NULL; + gsize length = 0; + + device->store_id = 0; + + ba2str(&device->bdaddr, device_addr); + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + btd_adapter_get_storage_dir(device->adapter), + device_addr); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + g_key_file_set_string(key_file, "General", "Name", device->name); + + if (device->alias != NULL) + g_key_file_set_string(key_file, "General", "Alias", + device->alias); + else + g_key_file_remove_key(key_file, "General", "Alias", NULL); + + if (device->class) { + sprintf(class, "0x%6.6x", device->class); + g_key_file_set_string(key_file, "General", "Class", class); + } else { + g_key_file_remove_key(key_file, "General", "Class", NULL); + } + + if (device->appearance) { + sprintf(class, "0x%4.4x", device->appearance); + g_key_file_set_string(key_file, "General", "Appearance", class); + } else { + g_key_file_remove_key(key_file, "General", "Appearance", NULL); + } + + update_technologies(key_file, device); + + g_key_file_set_boolean(key_file, "General", "Trusted", + device->trusted); + + g_key_file_set_boolean(key_file, "General", "Blocked", + device->blocked); + + if (device->uuids) { + GSList *l; + int i; + + uuids = g_new0(char *, g_slist_length(device->uuids) + 1); + for (i = 0, l = device->uuids; l; l = g_slist_next(l), i++) + uuids[i] = l->data; + g_key_file_set_string_list(key_file, "General", "Services", + (const char **)uuids, i); + } else { + g_key_file_remove_key(key_file, "General", "Services", NULL); + } + + if (device->vendor_src) { + g_key_file_set_integer(key_file, "DeviceID", "Source", + device->vendor_src); + g_key_file_set_integer(key_file, "DeviceID", "Vendor", + device->vendor); + g_key_file_set_integer(key_file, "DeviceID", "Product", + device->product); + g_key_file_set_integer(key_file, "DeviceID", "Version", + device->version); + } else { + g_key_file_remove_group(key_file, "DeviceID", NULL); + } + + if (device->local_csrk) + store_csrk(device->local_csrk, key_file, "LocalSignatureKey"); + + if (device->remote_csrk) + store_csrk(device->remote_csrk, key_file, "RemoteSignatureKey"); + + create_file(filename, S_IRUSR | S_IWUSR); + + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, str, length, NULL); + g_free(str); + + g_key_file_free(key_file); + g_free(uuids); + + return FALSE; +} + +static bool device_address_is_private(struct btd_device *dev) +{ + if (dev->bdaddr_type != BDADDR_LE_RANDOM) + return false; + + switch (dev->bdaddr.b[5] >> 6) { + case 0x00: /* Private non-resolvable */ + case 0x01: /* Private resolvable */ + return true; + default: + return false; + } +} + +static void store_device_info(struct btd_device *device) +{ + if (device->temporary || device->store_id > 0) + return; + + if (device_address_is_private(device)) { + DBG("Can't store info for private addressed device %s", + device->path); + return; + } + + device->store_id = g_idle_add(store_device_info_cb, device); +} + +void device_store_cached_name(struct btd_device *dev, const char *name) +{ + char filename[PATH_MAX]; + char d_addr[18]; + GKeyFile *key_file; + char *data; + gsize length = 0; + + if (device_address_is_private(dev)) { + DBG("Can't store name for private addressed device %s", + dev->path); + return; + } + + ba2str(&dev->bdaddr, d_addr); + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", + btd_adapter_get_storage_dir(dev->adapter), d_addr); + create_file(filename, S_IRUSR | S_IWUSR); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + g_key_file_set_string(key_file, "General", "Name", name); + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, data, length, NULL); + g_free(data); + + g_key_file_free(key_file); +} + +static void browse_request_free(struct browse_req *req) +{ + struct btd_device *device = req->device; + + if (device->browse == req) + device->browse = NULL; + + if (req->listener_id) + g_dbus_remove_watch(dbus_conn, req->listener_id); + if (req->msg) + dbus_message_unref(req->msg); + g_slist_free_full(req->profiles_added, g_free); + if (req->records) + sdp_list_free(req->records, (sdp_free_func_t) sdp_record_free); + + g_free(req); +} + +static bool gatt_cache_is_enabled(struct btd_device *device) +{ + switch (main_opts.gatt_cache) { + case BT_GATT_CACHE_YES: + return device_is_paired(device, device->bdaddr_type); + case BT_GATT_CACHE_NO: + return false; + case BT_GATT_CACHE_ALWAYS: + default: + return true; + } +} + +static void gatt_cache_cleanup(struct btd_device *device) +{ + if (gatt_cache_is_enabled(device)) + return; + + gatt_db_clear(device->db); +} + +static void gatt_client_cleanup(struct btd_device *device) +{ + if (!device->client) + return; + + gatt_cache_cleanup(device); + bt_gatt_client_set_service_changed(device->client, NULL, NULL, NULL); + + if (device->gatt_ready_id > 0) { + bt_gatt_client_ready_unregister(device->client, + device->gatt_ready_id); + device->gatt_ready_id = 0; + } + + bt_gatt_client_unref(device->client); + device->client = NULL; +} + +static void gatt_server_cleanup(struct btd_device *device) +{ + if (!device->server) + return; + + btd_gatt_database_att_disconnected( + btd_adapter_get_database(device->adapter), device); + + bt_gatt_server_unref(device->server); + device->server = NULL; +} + +static void attio_cleanup(struct btd_device *device) +{ + if (device->att_disconn_id) + bt_att_unregister_disconnect(device->att, + device->att_disconn_id); + + if (device->att_io) { + g_io_channel_shutdown(device->att_io, FALSE, NULL); + g_io_channel_unref(device->att_io); + device->att_io = NULL; + } + + gatt_client_cleanup(device); + gatt_server_cleanup(device); + + if (device->att) { + bt_att_unref(device->att); + device->att = NULL; + } + + if (device->attrib) { + GAttrib *attrib = device->attrib; + + device->attrib = NULL; + g_attrib_cancel_all(attrib); + g_attrib_unref(attrib); + } +} + +static void browse_request_cancel(struct browse_req *req) +{ + struct btd_device *device = req->device; + struct btd_adapter *adapter = device->adapter; + + DBG(""); + + bt_cancel_discovery(btd_adapter_get_address(adapter), &device->bdaddr); + + attio_cleanup(device); + + browse_request_free(req); +} + +static void svc_dev_remove(gpointer user_data) +{ + struct svc_callback *cb = user_data; + + if (cb->idle_id > 0) + g_source_remove(cb->idle_id); + + cb->func(cb->dev, -ENODEV, cb->user_data); + + g_free(cb); +} + +static void device_free(gpointer user_data) +{ + struct btd_device *device = user_data; + + btd_gatt_client_destroy(device->client_dbus); + device->client_dbus = NULL; + + g_slist_free_full(device->uuids, g_free); + g_slist_free_full(device->primaries, g_free); + g_slist_free_full(device->svc_callbacks, svc_dev_remove); + + /* Reset callbacks since the device is going to be freed */ + gatt_db_unregister(device->db, device->db_id); + + attio_cleanup(device); + + gatt_db_unref(device->db); + + bt_ad_unref(device->ad); + + if (device->tmp_records) + sdp_list_free(device->tmp_records, + (sdp_free_func_t) sdp_record_free); + + if (device->disconn_timer) + g_source_remove(device->disconn_timer); + + if (device->discov_timer) + g_source_remove(device->discov_timer); + + if (device->connect) + dbus_message_unref(device->connect); + + if (device->disconnect) + dbus_message_unref(device->disconnect); + + DBG("%p", device); + + if (device->authr) { + if (device->authr->agent) + agent_unref(device->authr->agent); + g_free(device->authr->pincode); + g_free(device->authr); + } + + if (device->eir_uuids) + g_slist_free_full(device->eir_uuids, g_free); + + g_free(device->local_csrk); + g_free(device->remote_csrk); + g_free(device->path); + g_free(device->alias); + free(device->modalias); + g_free(device); +} + +bool device_is_paired(struct btd_device *device, uint8_t bdaddr_type) +{ + struct bearer_state *state = get_state(device, bdaddr_type); + + return state->paired; +} + +bool device_is_bonded(struct btd_device *device, uint8_t bdaddr_type) +{ + struct bearer_state *state = get_state(device, bdaddr_type); + + return state->bonded; +} + +gboolean device_is_trusted(struct btd_device *device) +{ + return device->trusted; +} + +static gboolean dev_property_get_address(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + char dstaddr[18]; + const char *ptr = dstaddr; + + ba2str(&device->bdaddr, dstaddr); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); + + return TRUE; +} + +static gboolean property_get_address_type(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct btd_device *device = user_data; + const char *str; + + if (device->le && device->bdaddr_type == BDADDR_LE_RANDOM) + str = "random"; + else + str = "public"; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str); + + return TRUE; +} + +static gboolean dev_property_get_name(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + const char *ptr = device->name; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); + + return TRUE; +} + +static gboolean dev_property_exists_name(const GDBusPropertyTable *property, + void *data) +{ + struct btd_device *dev = data; + + return device_name_known(dev); +} + +static gboolean dev_property_get_alias(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + char dstaddr[18]; + const char *ptr; + + /* Alias (fallback to name or address) */ + if (device->alias != NULL) + ptr = device->alias; + else if (strlen(device->name) > 0) { + ptr = device->name; + } else { + ba2str(&device->bdaddr, dstaddr); + g_strdelimit(dstaddr, ":", '-'); + ptr = dstaddr; + } + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); + + return TRUE; +} + +static void set_alias(GDBusPendingPropertySet id, const char *alias, + void *data) +{ + struct btd_device *device = data; + + /* No change */ + if ((device->alias == NULL && g_str_equal(alias, "")) || + g_strcmp0(device->alias, alias) == 0) { + g_dbus_pending_property_success(id); + return; + } + + g_free(device->alias); + device->alias = g_str_equal(alias, "") ? NULL : g_strdup(alias); + + store_device_info(device); + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Alias"); + + g_dbus_pending_property_success(id); +} + +static void dev_property_set_alias(const GDBusPropertyTable *property, + DBusMessageIter *value, + GDBusPendingPropertySet id, void *data) +{ + const char *alias; + + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_STRING) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(value, &alias); + + set_alias(id, alias, data); +} + +static gboolean dev_property_exists_class(const GDBusPropertyTable *property, + void *data) +{ + struct btd_device *device = data; + + return device->class != 0; +} + +static gboolean dev_property_get_class(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + + if (device->class == 0) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &device->class); + + return TRUE; +} + +static gboolean get_appearance(const GDBusPropertyTable *property, void *data, + uint16_t *appearance) +{ + struct btd_device *device = data; + + if (dev_property_exists_class(property, data)) + return FALSE; + + if (device->appearance) { + *appearance = device->appearance; + return TRUE; + } + + return FALSE; +} + +static gboolean dev_property_exists_appearance( + const GDBusPropertyTable *property, void *data) +{ + uint16_t appearance; + + return get_appearance(property, data, &appearance); +} + +static gboolean dev_property_get_appearance(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + uint16_t appearance; + + if (!get_appearance(property, data, &appearance)) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &appearance); + + return TRUE; +} + +static const char *get_icon(const GDBusPropertyTable *property, void *data) +{ + struct btd_device *device = data; + const char *icon = NULL; + uint16_t appearance; + + if (device->class != 0) + icon = class_to_icon(device->class); + else if (get_appearance(property, data, &appearance)) + icon = gap_appearance_to_icon(appearance); + + return icon; +} + +static gboolean dev_property_exists_icon( + const GDBusPropertyTable *property, void *data) +{ + return get_icon(property, data) != NULL; +} + +static gboolean dev_property_get_icon(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + const char *icon; + + icon = get_icon(property, data); + if (icon == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &icon); + + return TRUE; +} + +static gboolean dev_property_get_paired(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *dev = data; + dbus_bool_t val; + + if (dev->bredr_state.paired || dev->le_state.paired) + val = TRUE; + else + val = FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); + + return TRUE; +} + +static gboolean dev_property_get_legacy(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + dbus_bool_t val = device->legacy; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); + + return TRUE; +} + +static gboolean dev_property_get_rssi(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *dev = data; + dbus_int16_t val = dev->rssi; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_INT16, &val); + + return TRUE; +} + +static gboolean dev_property_exists_rssi(const GDBusPropertyTable *property, + void *data) +{ + struct btd_device *dev = data; + + if (dev->rssi == 0) + return FALSE; + + return TRUE; +} + +static gboolean dev_property_get_tx_power(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *dev = data; + dbus_int16_t val = dev->tx_power; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_INT16, &val); + + return TRUE; +} + +static gboolean dev_property_exists_tx_power(const GDBusPropertyTable *property, + void *data) +{ + struct btd_device *dev = data; + + if (dev->tx_power == 127) + return FALSE; + + return TRUE; +} + +static gboolean +dev_property_get_svc_resolved(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + gboolean val = device->svc_refreshed; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); + + return TRUE; +} + +static gboolean dev_property_flags_exist(const GDBusPropertyTable *property, + void *data) +{ + struct btd_device *device = data; + + return device->ad_flags[0] != INVALID_FLAGS; +} + +static gboolean +dev_property_get_flags(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + uint8_t *flags = device->ad_flags; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array); + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &flags, sizeof(device->ad_flags)); + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean dev_property_get_trusted(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + gboolean val = device_is_trusted(device); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); + + return TRUE; +} + +static void set_trust(GDBusPendingPropertySet id, gboolean value, void *data) +{ + struct btd_device *device = data; + + btd_device_set_trusted(device, value); + + g_dbus_pending_property_success(id); +} + +static void dev_property_set_trusted(const GDBusPropertyTable *property, + DBusMessageIter *value, + GDBusPendingPropertySet id, void *data) +{ + dbus_bool_t b; + + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(value, &b); + + set_trust(id, b, data); +} + +static gboolean dev_property_get_blocked(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, + &device->blocked); + + return TRUE; +} + +static void set_blocked(GDBusPendingPropertySet id, gboolean value, void *data) +{ + struct btd_device *device = data; + int err; + + if (value) + err = device_block(device, FALSE); + else + err = device_unblock(device, FALSE, FALSE); + + switch (-err) { + case 0: + g_dbus_pending_property_success(id); + break; + case EINVAL: + g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", + "Kernel lacks blacklist support"); + break; + default: + g_dbus_pending_property_error(id, ERROR_INTERFACE ".Failed", + strerror(-err)); + break; + } +} + + +static void dev_property_set_blocked(const GDBusPropertyTable *property, + DBusMessageIter *value, + GDBusPendingPropertySet id, void *data) +{ + dbus_bool_t b; + + if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(value, &b); + + set_blocked(id, b, data); +} + +static gboolean dev_property_get_connected(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *dev = data; + dbus_bool_t connected; + + if (dev->bredr_state.connected || dev->le_state.connected) + connected = TRUE; + else + connected = FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &connected); + + return TRUE; +} + +static gboolean dev_property_get_uuids(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *dev = data; + DBusMessageIter entry; + GSList *l; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + + if (dev->bredr_state.svc_resolved || dev->le_state.svc_resolved) + l = dev->uuids; + else if (dev->eir_uuids) + l = dev->eir_uuids; + else + l = dev->uuids; + + for (; l != NULL; l = l->next) + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, + &l->data); + + dbus_message_iter_close_container(iter, &entry); + + return TRUE; +} + +static gboolean dev_property_get_modalias(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + + if (!device->modalias) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &device->modalias); + + return TRUE; +} + +static gboolean dev_property_exists_modalias(const GDBusPropertyTable *property, + void *data) +{ + struct btd_device *device = data; + + return device->modalias ? TRUE : FALSE; +} + +static gboolean dev_property_get_adapter(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + const char *str = adapter_get_path(device->adapter); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str); + + return TRUE; +} + +static void append_manufacturer_data(void *data, void *user_data) +{ + struct bt_ad_manufacturer_data *md = data; + DBusMessageIter *dict = user_data; + + g_dbus_dict_append_basic_array(dict, + DBUS_TYPE_UINT16, &md->manufacturer_id, + DBUS_TYPE_BYTE, &md->data, md->len); +} + +static gboolean +dev_property_get_manufacturer_data(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_UINT16_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + bt_ad_foreach_manufacturer_data(device->ad, append_manufacturer_data, + &dict); + + dbus_message_iter_close_container(iter, &dict); + + return TRUE; +} + +static gboolean +dev_property_manufacturer_data_exist(const GDBusPropertyTable *property, + void *data) +{ + struct btd_device *device = data; + + return bt_ad_has_manufacturer_data(device->ad, NULL); +} + +static void append_service_data(void *data, void *user_data) +{ + struct bt_ad_service_data *sd = data; + DBusMessageIter *dict = user_data; + char uuid_str[MAX_LEN_UUID_STR]; + + bt_uuid_to_string(&sd->uuid, uuid_str, sizeof(uuid_str)); + + dict_append_array(dict, uuid_str, DBUS_TYPE_BYTE, &sd->data, sd->len); +} + +static gboolean +dev_property_get_service_data(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + bt_ad_foreach_service_data(device->ad, append_service_data, &dict); + + dbus_message_iter_close_container(iter, &dict); + + return TRUE; +} + +static gboolean +dev_property_service_data_exist(const GDBusPropertyTable *property, + void *data) +{ + struct btd_device *device = data; + + return bt_ad_has_service_data(device->ad, NULL); +} + +static void append_advertising_data(void *data, void *user_data) +{ + struct bt_ad_data *ad = data; + DBusMessageIter *dict = user_data; + + g_dbus_dict_append_basic_array(dict, + DBUS_TYPE_BYTE, &ad->type, + DBUS_TYPE_BYTE, &ad->data, ad->len); +} + +static gboolean +dev_property_get_advertising_data(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_BYTE_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + bt_ad_foreach_data(device->ad, append_advertising_data, &dict); + + dbus_message_iter_close_container(iter, &dict); + + return TRUE; +} + +static gboolean +dev_property_advertising_data_exist(const GDBusPropertyTable *property, + void *data) +{ + struct btd_device *device = data; + + return bt_ad_has_data(device->ad, NULL); +} + +static gboolean disconnect_all(gpointer user_data) +{ + struct btd_device *device = user_data; + + device->disconn_timer = 0; + + if (device->bredr_state.connected) + btd_adapter_disconnect_device(device->adapter, &device->bdaddr, + BDADDR_BREDR); + + if (device->le_state.connected) + btd_adapter_disconnect_device(device->adapter, &device->bdaddr, + device->bdaddr_type); + + return FALSE; +} + +int device_block(struct btd_device *device, gboolean update_only) +{ + int err = 0; + + if (device->blocked) + return 0; + + if (device->disconn_timer > 0) + g_source_remove(device->disconn_timer); + + disconnect_all(device); + + while (device->services != NULL) { + struct btd_service *service = device->services->data; + + device->services = g_slist_remove(device->services, service); + service_remove(service); + } + + if (!update_only) { + if (device->le) + err = btd_adapter_block_address(device->adapter, + &device->bdaddr, + device->bdaddr_type); + if (!err && device->bredr) + err = btd_adapter_block_address(device->adapter, + &device->bdaddr, + BDADDR_BREDR); + } + + if (err < 0) + return err; + + device->blocked = TRUE; + + store_device_info(device); + + btd_device_set_temporary(device, false); + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Blocked"); + + return 0; +} + +int device_unblock(struct btd_device *device, gboolean silent, + gboolean update_only) +{ + int err = 0; + + if (!device->blocked) + return 0; + + if (!update_only) { + if (device->le) + err = btd_adapter_unblock_address(device->adapter, + &device->bdaddr, + device->bdaddr_type); + if (!err && device->bredr) + err = btd_adapter_unblock_address(device->adapter, + &device->bdaddr, + BDADDR_BREDR); + } + + if (err < 0) + return err; + + device->blocked = FALSE; + + store_device_info(device); + + if (!silent) { + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Blocked"); + device_probe_profiles(device, device->uuids); + } + + return 0; +} + +static void browse_request_exit(DBusConnection *conn, void *user_data) +{ + struct browse_req *req = user_data; + + DBG("Requestor exited"); + + browse_request_cancel(req); +} + +static void bonding_request_cancel(struct bonding_req *bonding) +{ + struct btd_device *device = bonding->device; + struct btd_adapter *adapter = device->adapter; + + adapter_cancel_bonding(adapter, &device->bdaddr, device->bdaddr_type); +} + +static void dev_disconn_service(gpointer a, gpointer b) +{ + btd_service_disconnect(a); +} + +void device_request_disconnect(struct btd_device *device, DBusMessage *msg) +{ + if (device->bonding) + bonding_request_cancel(device->bonding); + + if (device->browse) + browse_request_cancel(device->browse); + + if (device->att_io) { + g_io_channel_shutdown(device->att_io, FALSE, NULL); + g_io_channel_unref(device->att_io); + device->att_io = NULL; + } + + if (device->connect) { + DBusMessage *reply = btd_error_failed(device->connect, + "Cancelled"); + g_dbus_send_message(dbus_conn, reply); + dbus_message_unref(device->connect); + device->connect = NULL; + } + + if (btd_device_is_connected(device) && msg) + device->disconnects = g_slist_append(device->disconnects, + dbus_message_ref(msg)); + + if (device->disconn_timer) + return; + + g_slist_foreach(device->services, dev_disconn_service, NULL); + + g_slist_free(device->pending); + device->pending = NULL; + + while (device->watches) { + struct btd_disconnect_data *data = device->watches->data; + + if (data->watch) + /* temporary is set if device is going to be removed */ + data->watch(device, device->temporary, + data->user_data); + + /* Check if the watch has been removed by callback function */ + if (!g_slist_find(device->watches, data)) + continue; + + device->watches = g_slist_remove(device->watches, data); + g_free(data); + } + + if (!btd_device_is_connected(device)) { + if (msg) + g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID); + return; + } + + device->disconn_timer = g_timeout_add_seconds(DISCONNECT_TIMER, + disconnect_all, + device); +} + +bool device_is_disconnecting(struct btd_device *device) +{ + return device->disconn_timer > 0; +} + +void device_set_ltk_enc_size(struct btd_device *device, uint8_t enc_size) +{ + device->ltk_enc_size = enc_size; + bt_att_set_enc_key_size(device->att, device->ltk_enc_size); +} + +static void device_set_auto_connect(struct btd_device *device, gboolean enable) +{ + char addr[18]; + + if (!device || !device->le) + return; + + ba2str(&device->bdaddr, addr); + + DBG("%s auto connect: %d", addr, enable); + + if (device->auto_connect == enable) + return; + + device->auto_connect = enable; + + /* Disabling auto connect */ + if (enable == FALSE) { + adapter_connect_list_remove(device->adapter, device); + adapter_auto_connect_remove(device->adapter, device); + return; + } + + /* Enabling auto connect */ + adapter_auto_connect_add(device->adapter, device); + + if (device->attrib) { + DBG("Already connected"); + return; + } + + adapter_connect_list_add(device->adapter, device); +} + +static DBusMessage *dev_disconnect(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct btd_device *device = user_data; + + /* + * If device is not trusted disable connections through passive + * scanning until Device1.Connect is called + */ + if (device->auto_connect && !device->trusted) { + device->disable_auto_connect = TRUE; + device_set_auto_connect(device, FALSE); + } + + device_request_disconnect(device, msg); + + return NULL; +} + +static int connect_next(struct btd_device *dev) +{ + struct btd_service *service; + int err = -ENOENT; + + while (dev->pending) { + service = dev->pending->data; + + err = btd_service_connect(service); + if (!err) + return 0; + + dev->pending = g_slist_delete_link(dev->pending, dev->pending); + } + + return err; +} + +static void device_profile_connected(struct btd_device *dev, + struct btd_profile *profile, int err) +{ + struct btd_service *pending; + GSList *l; + + DBG("%s %s (%d)", profile->name, strerror(-err), -err); + + if (!err) + btd_device_set_temporary(dev, false); + + if (dev->pending == NULL) + goto done; + + if (!btd_device_is_connected(dev)) { + switch (-err) { + case EHOSTDOWN: /* page timeout */ + case EHOSTUNREACH: /* adapter not powered */ + case ECONNABORTED: /* adapter powered down */ + goto done; + } + } + + + pending = dev->pending->data; + l = find_service_with_profile(dev->pending, profile); + if (l != NULL) + dev->pending = g_slist_delete_link(dev->pending, l); + + /* Only continue connecting the next profile if it matches the first + * pending, otherwise it will trigger another connect to the same + * profile + */ + if (profile != btd_service_get_profile(pending)) + return; + + if (connect_next(dev) == 0) + return; + +done: + g_slist_free(dev->pending); + dev->pending = NULL; + + if (!dev->connect) + return; + + if (!err && dbus_message_is_method_call(dev->connect, DEVICE_INTERFACE, + "Connect")) + dev->general_connect = TRUE; + + DBG("returning response to %s", dbus_message_get_sender(dev->connect)); + + l = find_service_with_state(dev->services, BTD_SERVICE_STATE_CONNECTED); + + if (err && l == NULL) { + /* Fallback to LE bearer if supported */ + if (err == -EHOSTDOWN && dev->le && !dev->le_state.connected) { + err = device_connect_le(dev); + if (err == 0) + return; + } + + g_dbus_send_message(dbus_conn, + btd_error_failed(dev->connect, strerror(-err))); + } else { + /* Start passive SDP discovery to update known services */ + if (dev->bredr && !dev->svc_refreshed) + device_browse_sdp(dev, NULL); + g_dbus_send_reply(dbus_conn, dev->connect, DBUS_TYPE_INVALID); + } + + dbus_message_unref(dev->connect); + dev->connect = NULL; +} + +void device_add_eir_uuids(struct btd_device *dev, GSList *uuids) +{ + GSList *l; + bool added = false; + + if (dev->bredr_state.svc_resolved || dev->le_state.svc_resolved) + return; + + for (l = uuids; l != NULL; l = l->next) { + const char *str = l->data; + if (g_slist_find_custom(dev->eir_uuids, str, bt_uuid_strcmp)) + continue; + added = true; + dev->eir_uuids = g_slist_append(dev->eir_uuids, g_strdup(str)); + } + + if (added) + g_dbus_emit_property_changed(dbus_conn, dev->path, + DEVICE_INTERFACE, "UUIDs"); +} + +static void add_manufacturer_data(void *data, void *user_data) +{ + struct eir_msd *msd = data; + struct btd_device *dev = user_data; + + if (!bt_ad_add_manufacturer_data(dev->ad, msd->company, msd->data, + msd->data_len)) + return; + + g_dbus_emit_property_changed(dbus_conn, dev->path, + DEVICE_INTERFACE, "ManufacturerData"); +} + +void device_set_manufacturer_data(struct btd_device *dev, GSList *list, + bool duplicate) +{ + if (duplicate) + bt_ad_clear_manufacturer_data(dev->ad); + + g_slist_foreach(list, add_manufacturer_data, dev); +} + +static void add_service_data(void *data, void *user_data) +{ + struct eir_sd *sd = data; + struct btd_device *dev = user_data; + bt_uuid_t uuid; + + if (bt_string_to_uuid(&uuid, sd->uuid) < 0) + return; + + if (!bt_ad_add_service_data(dev->ad, &uuid, sd->data, sd->data_len)) + return; + + g_dbus_emit_property_changed(dbus_conn, dev->path, + DEVICE_INTERFACE, "ServiceData"); +} + +void device_set_service_data(struct btd_device *dev, GSList *list, + bool duplicate) +{ + if (duplicate) + bt_ad_clear_service_data(dev->ad); + + g_slist_foreach(list, add_service_data, dev); +} + +static void add_data(void *data, void *user_data) +{ + struct eir_ad *ad = data; + struct btd_device *dev = user_data; + + if (!bt_ad_add_data(dev->ad, ad->type, ad->data, ad->len)) + return; + + if (ad->type == EIR_TRANSPORT_DISCOVERY) + g_dbus_emit_property_changed(dbus_conn, dev->path, + DEVICE_INTERFACE, + "AdvertisingData"); +} + +void device_set_data(struct btd_device *dev, GSList *list, + bool duplicate) +{ + if (duplicate) + bt_ad_clear_data(dev->ad); + + g_slist_foreach(list, add_data, dev); +} + +static struct btd_service *find_connectable_service(struct btd_device *dev, + const char *uuid) +{ + GSList *l; + + for (l = dev->services; l != NULL; l = g_slist_next(l)) { + struct btd_service *service = l->data; + struct btd_profile *p = btd_service_get_profile(service); + + if (!p->connect || !p->remote_uuid) + continue; + + if (strcasecmp(uuid, p->remote_uuid) == 0) + return service; + } + + return NULL; +} + +static int service_prio_cmp(gconstpointer a, gconstpointer b) +{ + struct btd_profile *p1 = btd_service_get_profile(a); + struct btd_profile *p2 = btd_service_get_profile(b); + + return p2->priority - p1->priority; +} + +static GSList *create_pending_list(struct btd_device *dev, const char *uuid) +{ + struct btd_service *service; + struct btd_profile *p; + GSList *l; + + if (uuid) { + service = find_connectable_service(dev, uuid); + if (service) + return g_slist_prepend(dev->pending, service); + + return dev->pending; + } + + for (l = dev->services; l != NULL; l = g_slist_next(l)) { + service = l->data; + p = btd_service_get_profile(service); + + if (!p->auto_connect) + continue; + + if (g_slist_find(dev->pending, service)) + continue; + + if (btd_service_get_state(service) != + BTD_SERVICE_STATE_DISCONNECTED) + continue; + + dev->pending = g_slist_insert_sorted(dev->pending, service, + service_prio_cmp); + } + + return dev->pending; +} + +int btd_device_connect_services(struct btd_device *dev, GSList *services) +{ + GSList *l; + + if (dev->pending || dev->connect || dev->browse) + return -EBUSY; + + if (!btd_adapter_get_powered(dev->adapter)) + return -ENETDOWN; + + if (!dev->bredr_state.svc_resolved) + return -ENOENT; + + if (services) { + for (l = services; l; l = g_slist_next(l)) { + struct btd_service *service = l->data; + + dev->pending = g_slist_append(dev->pending, service); + } + } else { + dev->pending = create_pending_list(dev, NULL); + } + + return connect_next(dev); +} + +static DBusMessage *connect_profiles(struct btd_device *dev, uint8_t bdaddr_type, + DBusMessage *msg, const char *uuid) +{ + struct bearer_state *state = get_state(dev, bdaddr_type); + int err; + + DBG("%s %s, client %s", dev->path, uuid ? uuid : "(all)", + dbus_message_get_sender(msg)); + + if (dev->pending || dev->connect || dev->browse) + return btd_error_in_progress(msg); + + if (!btd_adapter_get_powered(dev->adapter)) + return btd_error_not_ready(msg); + + btd_device_set_temporary(dev, false); + + if (!state->svc_resolved) + goto resolve_services; + + dev->pending = create_pending_list(dev, uuid); + if (!dev->pending) { + if (dev->svc_refreshed) { + if (find_service_with_state(dev->services, + BTD_SERVICE_STATE_CONNECTED)) + return dbus_message_new_method_return(msg); + else + return btd_error_not_available(msg); + } + + goto resolve_services; + } + + err = connect_next(dev); + if (err < 0) { + if (err == -EALREADY) + return dbus_message_new_method_return(msg); + return btd_error_failed(msg, strerror(-err)); + } + + dev->connect = dbus_message_ref(msg); + + return NULL; + +resolve_services: + DBG("Resolving services for %s", dev->path); + + if (bdaddr_type == BDADDR_BREDR) + err = device_browse_sdp(dev, msg); + else + err = device_browse_gatt(dev, msg); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + return NULL; +} + +#define NVAL_TIME ((time_t) -1) +#define SEEN_TRESHHOLD 300 + +static uint8_t select_conn_bearer(struct btd_device *dev) +{ + time_t bredr_last = NVAL_TIME, le_last = NVAL_TIME; + time_t current = time(NULL); + + /* Prefer bonded bearer in case only one is bonded */ + if (dev->bredr_state.bonded && !dev->le_state.bonded ) + return BDADDR_BREDR; + else if (!dev->bredr_state.bonded && dev->le_state.bonded) + return dev->bdaddr_type; + + /* If the address is random it can only be connected over LE */ + if (dev->bdaddr_type == BDADDR_LE_RANDOM) + return dev->bdaddr_type; + + if (dev->bredr_seen) { + bredr_last = current - dev->bredr_seen; + if (bredr_last > SEEN_TRESHHOLD) + bredr_last = NVAL_TIME; + } + + if (dev->le_seen) { + le_last = current - dev->le_seen; + if (le_last > SEEN_TRESHHOLD) + le_last = NVAL_TIME; + } + + if (le_last == NVAL_TIME && bredr_last == NVAL_TIME) + return dev->bdaddr_type; + + if (dev->bredr && (!dev->le || le_last == NVAL_TIME)) + return BDADDR_BREDR; + + if (dev->le && (!dev->bredr || bredr_last == NVAL_TIME)) + return dev->bdaddr_type; + + /* + * Prefer BR/EDR if time is the same since it might be from an + * advertisement with BR/EDR flag set. + */ + if (bredr_last <= le_last && btd_adapter_get_bredr(dev->adapter)) + return BDADDR_BREDR; + + return dev->bdaddr_type; +} + +static DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct btd_device *dev = user_data; + uint8_t bdaddr_type; + + if (dev->bredr_state.connected) { + /* + * Check if services have been resolved and there is at list + * one connected before switching to connect LE. + */ + if (dev->bredr_state.svc_resolved && + find_service_with_state(dev->services, + BTD_SERVICE_STATE_CONNECTED)) + bdaddr_type = dev->bdaddr_type; + else + bdaddr_type = BDADDR_BREDR; + } else if (dev->le_state.connected && dev->bredr) + bdaddr_type = BDADDR_BREDR; + else + bdaddr_type = select_conn_bearer(dev); + + if (bdaddr_type != BDADDR_BREDR) { + int err; + + if (dev->le_state.connected) + return dbus_message_new_method_return(msg); + + btd_device_set_temporary(dev, false); + + if (dev->disable_auto_connect) { + dev->disable_auto_connect = FALSE; + device_set_auto_connect(dev, TRUE); + } + + err = device_connect_le(dev); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + dev->connect = dbus_message_ref(msg); + + return NULL; + } + + return connect_profiles(dev, bdaddr_type, msg, NULL); +} + +static DBusMessage *connect_profile(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct btd_device *dev = user_data; + const char *pattern; + char *uuid; + DBusMessage *reply; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + uuid = bt_name2string(pattern); + reply = connect_profiles(dev, BDADDR_BREDR, msg, uuid); + free(uuid); + + return reply; +} + +static void device_profile_disconnected(struct btd_device *dev, + struct btd_profile *profile, int err) +{ + if (!dev->disconnect) + return; + + if (err) + g_dbus_send_message(dbus_conn, + btd_error_failed(dev->disconnect, + strerror(-err))); + else + g_dbus_send_reply(dbus_conn, dev->disconnect, + DBUS_TYPE_INVALID); + + dbus_message_unref(dev->disconnect); + dev->disconnect = NULL; +} + +static DBusMessage *disconnect_profile(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct btd_device *dev = user_data; + struct btd_service *service; + const char *pattern; + char *uuid; + int err; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &pattern, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + uuid = bt_name2string(pattern); + if (uuid == NULL) + return btd_error_invalid_args(msg); + + service = find_connectable_service(dev, uuid); + free(uuid); + + if (!service) + return btd_error_invalid_args(msg); + + if (dev->disconnect) + return btd_error_in_progress(msg); + + dev->disconnect = dbus_message_ref(msg); + + err = btd_service_disconnect(service); + if (err == 0) + return NULL; + + dbus_message_unref(dev->disconnect); + dev->disconnect = NULL; + + if (err == -ENOTSUP) + return btd_error_not_supported(msg); + + return btd_error_failed(msg, strerror(-err)); +} + +static void store_services(struct btd_device *device) +{ + char filename[PATH_MAX]; + char dst_addr[18]; + uuid_t uuid; + char *prim_uuid; + GKeyFile *key_file; + GSList *l; + char *data; + gsize length = 0; + + if (device_address_is_private(device)) { + DBG("Can't store services for private addressed device %s", + device->path); + return; + } + + sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + prim_uuid = bt_uuid2string(&uuid); + if (prim_uuid == NULL) + return; + + ba2str(&device->bdaddr, dst_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/attributes", + btd_adapter_get_storage_dir(device->adapter), + dst_addr); + key_file = g_key_file_new(); + + for (l = device->primaries; l; l = l->next) { + struct gatt_primary *primary = l->data; + char handle[6], uuid_str[33]; + int i; + + sprintf(handle, "%hu", primary->range.start); + + bt_string2uuid(&uuid, primary->uuid); + sdp_uuid128_to_uuid(&uuid); + + switch (uuid.type) { + case SDP_UUID16: + sprintf(uuid_str, "%4.4X", uuid.value.uuid16); + break; + case SDP_UUID32: + sprintf(uuid_str, "%8.8X", uuid.value.uuid32); + break; + case SDP_UUID128: + for (i = 0; i < 16; i++) + sprintf(uuid_str + (i * 2), "%2.2X", + uuid.value.uuid128.data[i]); + break; + default: + uuid_str[0] = '\0'; + } + + g_key_file_set_string(key_file, handle, "UUID", prim_uuid); + g_key_file_set_string(key_file, handle, "Value", uuid_str); + g_key_file_set_integer(key_file, handle, "EndGroupHandle", + primary->range.end); + } + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + + free(prim_uuid); + g_free(data); + g_key_file_free(key_file); +} + +struct gatt_saver { + struct btd_device *device; + uint16_t ext_props; + GKeyFile *key_file; +}; + +static void db_hash_read_value_cb(struct gatt_db_attribute *attrib, + int err, const uint8_t *value, + size_t length, void *user_data) +{ + const uint8_t **hash = user_data; + + if (err || (length != 16)) + return; + + *hash = value; +} + +static void store_desc(struct gatt_db_attribute *attr, void *user_data) +{ + struct gatt_saver *saver = user_data; + GKeyFile *key_file = saver->key_file; + char handle[6], value[100], uuid_str[MAX_LEN_UUID_STR]; + const bt_uuid_t *uuid; + bt_uuid_t ext_uuid; + uint16_t handle_num; + + handle_num = gatt_db_attribute_get_handle(attr); + sprintf(handle, "%04hx", handle_num); + + uuid = gatt_db_attribute_get_type(attr); + bt_uuid_to_string(uuid, uuid_str, sizeof(uuid_str)); + + bt_uuid16_create(&ext_uuid, GATT_CHARAC_EXT_PROPER_UUID); + if (!bt_uuid_cmp(uuid, &ext_uuid) && saver->ext_props) + sprintf(value, "%04hx:%s", saver->ext_props, uuid_str); + else + sprintf(value, "%s", uuid_str); + + g_key_file_set_string(key_file, "Attributes", handle, value); +} + +static void store_chrc(struct gatt_db_attribute *attr, void *user_data) +{ + struct gatt_saver *saver = user_data; + GKeyFile *key_file = saver->key_file; + char handle[6], value[100], uuid_str[MAX_LEN_UUID_STR]; + uint16_t handle_num, value_handle; + uint8_t properties; + bt_uuid_t uuid, hash_uuid; + + if (!gatt_db_attribute_get_char_data(attr, &handle_num, &value_handle, + &properties, &saver->ext_props, + &uuid)) { + warn("Error storing characteristic - can't get data"); + return; + } + + sprintf(handle, "%04hx", handle_num); + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + + /* Store Database Hash value if available */ + bt_uuid16_create(&hash_uuid, GATT_CHARAC_DB_HASH); + if (!bt_uuid_cmp(&uuid, &hash_uuid)) { + const uint8_t *hash = NULL; + + attr = gatt_db_get_attribute(saver->device->db, value_handle); + + gatt_db_attribute_read(attr, 0, BT_ATT_OP_READ_REQ, NULL, + db_hash_read_value_cb, &hash); + if (hash) + sprintf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hhx:" + "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" + "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx" + "%02hhx%02hhx:%s", value_handle, properties, + hash[0], hash[1], hash[2], hash[3], + hash[4], hash[5], hash[6], hash[7], + hash[8], hash[9], hash[10], hash[11], + hash[12], hash[13], hash[14], hash[15], + uuid_str); + else + sprintf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hhx:%s", + value_handle, properties, uuid_str); + + } else + sprintf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hhx:%s", + value_handle, properties, uuid_str); + + g_key_file_set_string(key_file, "Attributes", handle, value); + + gatt_db_service_foreach_desc(attr, store_desc, saver); +} + +static void store_incl(struct gatt_db_attribute *attr, void *user_data) +{ + struct gatt_saver *saver = user_data; + GKeyFile *key_file = saver->key_file; + struct gatt_db_attribute *service; + char handle[6], value[100], uuid_str[MAX_LEN_UUID_STR]; + uint16_t handle_num, start, end; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_incl_data(attr, &handle_num, &start, &end)) { + warn("Error storing included service - can't get data"); + return; + } + + service = gatt_db_get_attribute(saver->device->db, start); + if (!service) { + warn("Error storing included service - can't find it"); + return; + } + + sprintf(handle, "%04hx", handle_num); + + gatt_db_attribute_get_service_uuid(service, &uuid); + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + sprintf(value, GATT_INCLUDE_UUID_STR ":%04hx:%04hx:%s", start, + end, uuid_str); + + g_key_file_set_string(key_file, "Attributes", handle, value); +} + +static void store_service(struct gatt_db_attribute *attr, void *user_data) +{ + struct gatt_saver *saver = user_data; + GKeyFile *key_file = saver->key_file; + char uuid_str[MAX_LEN_UUID_STR], handle[6], value[256]; + uint16_t start, end; + bt_uuid_t uuid; + bool primary; + char *type; + + if (!gatt_db_attribute_get_service_data(attr, &start, &end, &primary, + &uuid)) { + warn("Error storing service - can't get data"); + return; + } + + sprintf(handle, "%04hx", start); + + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + + if (primary) + type = GATT_PRIM_SVC_UUID_STR; + else + type = GATT_SND_SVC_UUID_STR; + + sprintf(value, "%s:%04hx:%s", type, end, uuid_str); + + g_key_file_set_string(key_file, "Attributes", handle, value); + + gatt_db_service_foreach_incl(attr, store_incl, saver); + gatt_db_service_foreach_char(attr, store_chrc, saver); +} + +static void store_gatt_db(struct btd_device *device) +{ + char filename[PATH_MAX]; + char dst_addr[18]; + GKeyFile *key_file; + char *data; + gsize length = 0; + struct gatt_saver saver; + + if (device_address_is_private(device)) { + DBG("Can't store GATT db for private addressed device %s", + device->path); + return; + } + + if (!gatt_cache_is_enabled(device)) + return; + + ba2str(&device->bdaddr, dst_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", + btd_adapter_get_storage_dir(device->adapter), + dst_addr); + create_file(filename, S_IRUSR | S_IWUSR); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + /* Remove current attributes since it might have changed */ + g_key_file_remove_group(key_file, "Attributes", NULL); + + saver.key_file = key_file; + saver.device = device; + + gatt_db_foreach_service(device->db, NULL, store_service, &saver); + + data = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, data, length, NULL); + + g_free(data); + g_key_file_free(key_file); +} + + +static void browse_request_complete(struct browse_req *req, uint8_t type, + uint8_t bdaddr_type, int err) +{ + struct btd_device *dev = req->device; + DBusMessage *reply = NULL; + DBusMessage *msg; + + if (req->type != type) + return; + + if (!req->msg) + goto done; + + if (dbus_message_is_method_call(req->msg, DEVICE_INTERFACE, "Pair")) { + if (!device_is_paired(dev, bdaddr_type)) { + reply = btd_error_failed(req->msg, "Not paired"); + goto done; + } + + if (dev->pending_paired) { + g_dbus_emit_property_changed(dbus_conn, dev->path, + DEVICE_INTERFACE, "Paired"); + dev->pending_paired = false; + } + + /* Disregard browse errors in case of Pair */ + reply = g_dbus_create_reply(req->msg, DBUS_TYPE_INVALID); + goto done; + } + + if (err) { + /* Fallback to LE bearer if supported */ + if (err == -EHOSTDOWN && bdaddr_type == BDADDR_BREDR && + dev->le && !dev->le_state.connected) { + err = device_connect_le(dev); + if (err == 0) + goto done; + } + reply = btd_error_failed(req->msg, strerror(-err)); + goto done; + } + + /* if successfully resolved services we need to free browsing request + * before passing message back to connect functions, otherwise + * device->browse is set and "InProgress" error is returned instead + * of actually connecting services + */ + msg = dbus_message_ref(req->msg); + browse_request_free(req); + req = NULL; + + if (dbus_message_is_method_call(msg, DEVICE_INTERFACE, "Connect")) + reply = dev_connect(dbus_conn, msg, dev); + else if (dbus_message_is_method_call(msg, DEVICE_INTERFACE, + "ConnectProfile")) + reply = connect_profile(dbus_conn, msg, dev); + else + reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + + dbus_message_unref(msg); + +done: + if (reply) + g_dbus_send_message(dbus_conn, reply); + + if (req) + browse_request_free(req); +} + +static void device_set_svc_refreshed(struct btd_device *device, bool value) +{ + if (device->svc_refreshed == value) + return; + + device->svc_refreshed = value; + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "ServicesResolved"); +} + +static void device_svc_resolved(struct btd_device *dev, uint8_t browse_type, + uint8_t bdaddr_type, int err) +{ + struct bearer_state *state = get_state(dev, bdaddr_type); + struct browse_req *req = dev->browse; + + DBG("%s err %d", dev->path, err); + + state->svc_resolved = true; + + /* Disconnection notification can happen before this function + * gets called, so don't set svc_refreshed for a disconnected + * device. + */ + if (state->connected) + device_set_svc_refreshed(dev, true); + + g_slist_free_full(dev->eir_uuids, g_free); + dev->eir_uuids = NULL; + + if (dev->pending_paired) { + g_dbus_emit_property_changed(dbus_conn, dev->path, + DEVICE_INTERFACE, "Paired"); + dev->pending_paired = false; + } + + if (!dev->temporary) { + store_device_info(dev); + + if (bdaddr_type != BDADDR_BREDR && err == 0) + store_services(dev); + } + + if (req) + browse_request_complete(req, browse_type, bdaddr_type, err); + + while (dev->svc_callbacks) { + struct svc_callback *cb = dev->svc_callbacks->data; + + if (cb->idle_id > 0) + g_source_remove(cb->idle_id); + + cb->func(dev, err, cb->user_data); + + dev->svc_callbacks = g_slist_delete_link(dev->svc_callbacks, + dev->svc_callbacks); + g_free(cb); + } +} + +static struct bonding_req *bonding_request_new(DBusMessage *msg, + struct btd_device *device, + uint8_t bdaddr_type, + struct agent *agent) +{ + struct bonding_req *bonding; + char addr[18]; + + ba2str(&device->bdaddr, addr); + DBG("Requesting bonding for %s", addr); + + bonding = g_new0(struct bonding_req, 1); + + bonding->msg = dbus_message_ref(msg); + bonding->bdaddr_type = bdaddr_type; + + bonding->cb_iter = btd_adapter_pin_cb_iter_new(device->adapter); + + /* Marks the bonding start time for the first attempt on request + * construction. The following attempts will be updated on + * device_bonding_retry. */ + clock_gettime(CLOCK_MONOTONIC, &bonding->attempt_start_time); + + if (agent) + bonding->agent = agent_ref(agent); + + return bonding; +} + +void device_bonding_restart_timer(struct btd_device *device) +{ + if (!device || !device->bonding) + return; + + clock_gettime(CLOCK_MONOTONIC, &device->bonding->attempt_start_time); +} + +static void bonding_request_stop_timer(struct bonding_req *bonding) +{ + struct timespec current; + + clock_gettime(CLOCK_MONOTONIC, ¤t); + + /* Compute the time difference in ms. */ + bonding->last_attempt_duration_ms = + (current.tv_sec - bonding->attempt_start_time.tv_sec) * 1000L + + (current.tv_nsec - bonding->attempt_start_time.tv_nsec) + / 1000000L; +} + +/* Returns the duration of the last bonding attempt in milliseconds. The + * duration is measured starting from the latest of the following three + * events and finishing when the Command complete event is received for the + * authentication request: + * - MGMT_OP_PAIR_DEVICE is sent, + * - MGMT_OP_PIN_CODE_REPLY is sent and + * - Command complete event is received for the sent MGMT_OP_PIN_CODE_REPLY. + */ +long device_bonding_last_duration(struct btd_device *device) +{ + struct bonding_req *bonding = device->bonding; + + if (!bonding) + return 0; + + return bonding->last_attempt_duration_ms; +} + +static void create_bond_req_exit(DBusConnection *conn, void *user_data) +{ + struct btd_device *device = user_data; + char addr[18]; + + ba2str(&device->bdaddr, addr); + DBG("%s: requestor exited before bonding was completed", addr); + + if (device->authr) + device_cancel_authentication(device, FALSE); + + if (device->bonding) { + device->bonding->listener_id = 0; + device_request_disconnect(device, NULL); + } +} + +static void bonding_request_free(struct bonding_req *bonding) +{ + if (!bonding) + return; + + if (bonding->listener_id) + g_dbus_remove_watch(dbus_conn, bonding->listener_id); + + if (bonding->msg) + dbus_message_unref(bonding->msg); + + if (bonding->cb_iter) + g_free(bonding->cb_iter); + + if (bonding->agent) { + agent_cancel(bonding->agent); + agent_unref(bonding->agent); + bonding->agent = NULL; + } + + if (bonding->retry_timer) + g_source_remove(bonding->retry_timer); + + if (bonding->device) + bonding->device->bonding = NULL; + + g_free(bonding); +} + +static DBusMessage *pair_device(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct btd_device *device = data; + struct btd_adapter *adapter = device->adapter; + struct bearer_state *state; + uint8_t bdaddr_type; + const char *sender; + struct agent *agent; + struct bonding_req *bonding; + uint8_t io_cap; + int err; + + btd_device_set_temporary(device, false); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + if (device->bonding) + return btd_error_in_progress(msg); + + if (device->bredr_state.bonded) + bdaddr_type = device->bdaddr_type; + else if (device->le_state.bonded) + bdaddr_type = BDADDR_BREDR; + else + bdaddr_type = select_conn_bearer(device); + + state = get_state(device, bdaddr_type); + + if (state->bonded) + return btd_error_already_exists(msg); + + sender = dbus_message_get_sender(msg); + + agent = agent_get(sender); + if (agent) + io_cap = agent_get_io_capability(agent); + else + io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; + + bonding = bonding_request_new(msg, device, bdaddr_type, agent); + + if (agent) + agent_unref(agent); + + bonding->listener_id = g_dbus_add_disconnect_watch(dbus_conn, + sender, create_bond_req_exit, + device, NULL); + + device->bonding = bonding; + bonding->device = device; + + /* Due to a bug in the kernel we might loose out on ATT commands + * that arrive during the SMP procedure, so connect the ATT + * channel first and only then start pairing (there's code for + * this in the ATT connect callback) + */ + if (bdaddr_type != BDADDR_BREDR) { + if (!state->connected && btd_le_connect_before_pairing()) + err = device_connect_le(device); + else + err = adapter_create_bonding(adapter, &device->bdaddr, + device->bdaddr_type, + io_cap); + } else { + err = adapter_create_bonding(adapter, &device->bdaddr, + BDADDR_BREDR, io_cap); + } + + if (err < 0) { + bonding_request_free(device->bonding); + return btd_error_failed(msg, strerror(-err)); + } + + return NULL; +} + +static DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t status) +{ + switch (status) { + case MGMT_STATUS_SUCCESS: + return dbus_message_new_method_return(msg); + + case MGMT_STATUS_CONNECT_FAILED: + return dbus_message_new_error(msg, + ERROR_INTERFACE ".ConnectionAttemptFailed", + "Page Timeout"); + case MGMT_STATUS_TIMEOUT: + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationTimeout", + "Authentication Timeout"); + case MGMT_STATUS_BUSY: + case MGMT_STATUS_REJECTED: + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationRejected", + "Authentication Rejected"); + case MGMT_STATUS_CANCELLED: + case MGMT_STATUS_NO_RESOURCES: + case MGMT_STATUS_DISCONNECTED: + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationCanceled", + "Authentication Canceled"); + case MGMT_STATUS_ALREADY_PAIRED: + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AlreadyExists", + "Already Paired"); + default: + return dbus_message_new_error(msg, + ERROR_INTERFACE ".AuthenticationFailed", + "Authentication Failed"); + } +} + +static void device_cancel_bonding(struct btd_device *device, uint8_t status) +{ + struct bonding_req *bonding = device->bonding; + DBusMessage *reply; + char addr[18]; + + if (!bonding) + return; + + ba2str(&device->bdaddr, addr); + DBG("Canceling bonding request for %s", addr); + + if (device->authr) + device_cancel_authentication(device, FALSE); + + reply = new_authentication_return(bonding->msg, status); + g_dbus_send_message(dbus_conn, reply); + + bonding_request_cancel(bonding); + bonding_request_free(bonding); +} + +static DBusMessage *cancel_pairing(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct btd_device *device = data; + struct bonding_req *req = device->bonding; + + DBG(""); + + if (!req) + return btd_error_does_not_exist(msg); + + device_cancel_bonding(device, MGMT_STATUS_CANCELLED); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable device_methods[] = { + { GDBUS_ASYNC_METHOD("Disconnect", NULL, NULL, dev_disconnect) }, + { GDBUS_ASYNC_METHOD("Connect", NULL, NULL, dev_connect) }, + { GDBUS_ASYNC_METHOD("ConnectProfile", GDBUS_ARGS({ "UUID", "s" }), + NULL, connect_profile) }, + { GDBUS_ASYNC_METHOD("DisconnectProfile", GDBUS_ARGS({ "UUID", "s" }), + NULL, disconnect_profile) }, + { GDBUS_ASYNC_METHOD("Pair", NULL, NULL, pair_device) }, + { GDBUS_METHOD("CancelPairing", NULL, NULL, cancel_pairing) }, + { } +}; + +static const GDBusPropertyTable device_properties[] = { + { "Address", "s", dev_property_get_address }, + { "AddressType", "s", property_get_address_type }, + { "Name", "s", dev_property_get_name, NULL, dev_property_exists_name }, + { "Alias", "s", dev_property_get_alias, dev_property_set_alias }, + { "Class", "u", dev_property_get_class, NULL, + dev_property_exists_class }, + { "Appearance", "q", dev_property_get_appearance, NULL, + dev_property_exists_appearance }, + { "Icon", "s", dev_property_get_icon, NULL, + dev_property_exists_icon }, + { "Paired", "b", dev_property_get_paired }, + { "Trusted", "b", dev_property_get_trusted, dev_property_set_trusted }, + { "Blocked", "b", dev_property_get_blocked, dev_property_set_blocked }, + { "LegacyPairing", "b", dev_property_get_legacy }, + { "RSSI", "n", dev_property_get_rssi, NULL, dev_property_exists_rssi }, + { "Connected", "b", dev_property_get_connected }, + { "UUIDs", "as", dev_property_get_uuids }, + { "Modalias", "s", dev_property_get_modalias, NULL, + dev_property_exists_modalias }, + { "Adapter", "o", dev_property_get_adapter }, + { "ManufacturerData", "a{qv}", dev_property_get_manufacturer_data, + NULL, dev_property_manufacturer_data_exist }, + { "ServiceData", "a{sv}", dev_property_get_service_data, + NULL, dev_property_service_data_exist }, + { "TxPower", "n", dev_property_get_tx_power, NULL, + dev_property_exists_tx_power }, + { "ServicesResolved", "b", dev_property_get_svc_resolved, NULL, NULL }, + { "AdvertisingFlags", "ay", dev_property_get_flags, NULL, + dev_property_flags_exist, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL}, + { "AdvertisingData", "a{yv}", dev_property_get_advertising_data, + NULL, dev_property_advertising_data_exist, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { } +}; + +uint8_t btd_device_get_bdaddr_type(struct btd_device *dev) +{ + return dev->bdaddr_type; +} + +bool btd_device_is_connected(struct btd_device *dev) +{ + return dev->bredr_state.connected || dev->le_state.connected; +} + +void device_add_connection(struct btd_device *dev, uint8_t bdaddr_type) +{ + struct bearer_state *state = get_state(dev, bdaddr_type); + + device_update_last_seen(dev, bdaddr_type); + + if (state->connected) { + char addr[18]; + ba2str(&dev->bdaddr, addr); + error("Device %s is already connected", addr); + return; + } + + bacpy(&dev->conn_bdaddr, &dev->bdaddr); + dev->conn_bdaddr_type = dev->bdaddr_type; + + /* If this is the first connection over this bearer */ + if (bdaddr_type == BDADDR_BREDR) + device_set_bredr_support(dev); + else + device_set_le_support(dev, bdaddr_type); + + state->connected = true; + + if (dev->le_state.connected && dev->bredr_state.connected) + return; + + g_dbus_emit_property_changed(dbus_conn, dev->path, DEVICE_INTERFACE, + "Connected"); +} + +void device_remove_connection(struct btd_device *device, uint8_t bdaddr_type) +{ + struct bearer_state *state = get_state(device, bdaddr_type); + + if (!state->connected) + return; + + state->connected = false; + device->general_connect = FALSE; + + device_set_svc_refreshed(device, false); + + if (device->disconn_timer > 0) { + g_source_remove(device->disconn_timer); + device->disconn_timer = 0; + } + + while (device->disconnects) { + DBusMessage *msg = device->disconnects->data; + + g_dbus_send_reply(dbus_conn, msg, DBUS_TYPE_INVALID); + device->disconnects = g_slist_remove(device->disconnects, msg); + dbus_message_unref(msg); + } + + if (state->paired && !state->bonded) { + btd_adapter_remove_bonding(device->adapter, &device->bdaddr, + bdaddr_type); + + state->paired = false; + + /* report change only if both bearers are unpaired */ + if (!device->bredr_state.paired && !device->le_state.paired) + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, + "Paired"); + } + + if (device->bredr_state.connected || device->le_state.connected) + return; + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Connected"); +} + +guint device_add_disconnect_watch(struct btd_device *device, + disconnect_watch watch, void *user_data, + GDestroyNotify destroy) +{ + struct btd_disconnect_data *data; + static guint id = 0; + + data = g_new0(struct btd_disconnect_data, 1); + data->id = ++id; + data->watch = watch; + data->user_data = user_data; + data->destroy = destroy; + + device->watches = g_slist_append(device->watches, data); + + return data->id; +} + +void device_remove_disconnect_watch(struct btd_device *device, guint id) +{ + GSList *l; + + for (l = device->watches; l; l = l->next) { + struct btd_disconnect_data *data = l->data; + + if (data->id == id) { + device->watches = g_slist_remove(device->watches, + data); + if (data->destroy) + data->destroy(data->user_data); + g_free(data); + return; + } + } +} + +static char *load_cached_name(struct btd_device *device, const char *local, + const char *peer) +{ + char filename[PATH_MAX]; + GKeyFile *key_file; + char *str = NULL; + int len; + + if (device_address_is_private(device)) + return NULL; + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", local, peer); + + key_file = g_key_file_new(); + + if (!g_key_file_load_from_file(key_file, filename, 0, NULL)) + goto failed; + + str = g_key_file_get_string(key_file, "General", "Name", NULL); + if (str) { + len = strlen(str); + if (len > HCI_MAX_NAME_LENGTH) + str[HCI_MAX_NAME_LENGTH] = '\0'; + } + +failed: + g_key_file_free(key_file); + + return str; +} + +static struct csrk_info *load_csrk(GKeyFile *key_file, const char *group) +{ + struct csrk_info *csrk; + char *str; + int i; + + str = g_key_file_get_string(key_file, group, "Key", NULL); + if (!str) + return NULL; + + csrk = g_new0(struct csrk_info, 1); + + for (i = 0; i < 16; i++) { + if (sscanf(str + (i * 2), "%2hhx", &csrk->key[i]) != 1) + goto fail; + } + + /* + * In case of older storage this will return 0 which is fine since it + * didn't support signing at that point the counter should never have + * been used. + */ + csrk->counter = g_key_file_get_integer(key_file, group, "Counter", + NULL); + g_free(str); + + return csrk; + +fail: + g_free(str); + g_free(csrk); + return NULL; +} + +static void load_services(struct btd_device *device, char **uuids) +{ + char **uuid; + + for (uuid = uuids; *uuid; uuid++) { + if (g_slist_find_custom(device->uuids, *uuid, bt_uuid_strcmp)) + continue; + + device->uuids = g_slist_insert_sorted(device->uuids, + g_strdup(*uuid), + bt_uuid_strcmp); + } + + g_strfreev(uuids); +} + +static void convert_info(struct btd_device *device, GKeyFile *key_file) +{ + char filename[PATH_MAX]; + char adapter_addr[18]; + char device_addr[18]; + char **uuids; + char *str; + gsize length = 0; + + /* Load device profile list from legacy properties */ + uuids = g_key_file_get_string_list(key_file, "General", "SDPServices", + NULL, NULL); + if (uuids) + load_services(device, uuids); + + uuids = g_key_file_get_string_list(key_file, "General", "GATTServices", + NULL, NULL); + if (uuids) + load_services(device, uuids); + + if (!device->uuids) + return; + + /* Remove old entries so they are not loaded again */ + g_key_file_remove_key(key_file, "General", "SDPServices", NULL); + g_key_file_remove_key(key_file, "General", "GATTServices", NULL); + + ba2str(btd_adapter_get_address(device->adapter), adapter_addr); + ba2str(&device->bdaddr, device_addr); + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", adapter_addr, + device_addr); + + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, str, length, NULL); + g_free(str); + + store_device_info(device); +} + +static void load_info(struct btd_device *device, const char *local, + const char *peer, GKeyFile *key_file) +{ + char *str; + gboolean store_needed = FALSE; + gboolean blocked; + char **uuids; + int source, vendor, product, version; + char **techno, **t; + + /* Load device name from storage info file, if that fails fall back to + * the cache. + */ + str = g_key_file_get_string(key_file, "General", "Name", NULL); + if (str == NULL) { + str = load_cached_name(device, local, peer); + if (str) + store_needed = TRUE; + } + + if (str) { + strcpy(device->name, str); + g_free(str); + } + + /* Load alias */ + device->alias = g_key_file_get_string(key_file, "General", "Alias", + NULL); + + /* Load class */ + str = g_key_file_get_string(key_file, "General", "Class", NULL); + if (str) { + uint32_t class; + + if (sscanf(str, "%x", &class) == 1) + device->class = class; + g_free(str); + } + + /* Load appearance */ + str = g_key_file_get_string(key_file, "General", "Appearance", NULL); + if (str) { + device->appearance = strtol(str, NULL, 16); + g_free(str); + } + + /* Load device technology */ + techno = g_key_file_get_string_list(key_file, "General", + "SupportedTechnologies", NULL, NULL); + if (!techno) + goto next; + + for (t = techno; *t; t++) { + if (g_str_equal(*t, "BR/EDR")) + device->bredr = true; + else if (g_str_equal(*t, "LE")) + device->le = true; + else + error("Unknown device technology"); + } + + if (!device->le) { + device->bdaddr_type = BDADDR_BREDR; + } else { + str = g_key_file_get_string(key_file, "General", + "AddressType", NULL); + + if (str && g_str_equal(str, "public")) + device->bdaddr_type = BDADDR_LE_PUBLIC; + else if (str && g_str_equal(str, "static")) + device->bdaddr_type = BDADDR_LE_RANDOM; + else + error("Unknown LE device technology"); + + g_free(str); + + device->local_csrk = load_csrk(key_file, "LocalSignatureKey"); + device->remote_csrk = load_csrk(key_file, "RemoteSignatureKey"); + } + + g_strfreev(techno); + +next: + /* Load trust */ + device->trusted = g_key_file_get_boolean(key_file, "General", + "Trusted", NULL); + + /* Load device blocked */ + blocked = g_key_file_get_boolean(key_file, "General", "Blocked", NULL); + if (blocked) + device_block(device, FALSE); + + /* Load device profile list */ + uuids = g_key_file_get_string_list(key_file, "General", "Services", + NULL, NULL); + if (uuids) { + load_services(device, uuids); + + /* Discovered services restored from storage */ + device->bredr_state.svc_resolved = true; + } + + /* Load device id */ + source = g_key_file_get_integer(key_file, "DeviceID", "Source", NULL); + if (source) { + vendor = g_key_file_get_integer(key_file, "DeviceID", + "Vendor", NULL); + + product = g_key_file_get_integer(key_file, "DeviceID", + "Product", NULL); + + version = g_key_file_get_integer(key_file, "DeviceID", + "Version", NULL); + + btd_device_set_pnpid(device, source, vendor, product, version); + } + + if (store_needed) + store_device_info(device); +} + +static void load_att_info(struct btd_device *device, const char *local, + const char *peer) +{ + char filename[PATH_MAX]; + GKeyFile *key_file; + char *prim_uuid, *str; + char **groups, **handle, *service_uuid; + struct gatt_primary *prim; + uuid_t uuid; + char tmp[3]; + int i; + + sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + prim_uuid = bt_uuid2string(&uuid); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/attributes", local, + peer); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + groups = g_key_file_get_groups(key_file, NULL); + + for (handle = groups; *handle; handle++) { + gboolean uuid_ok; + int end; + + str = g_key_file_get_string(key_file, *handle, "UUID", NULL); + if (!str) + continue; + + uuid_ok = g_str_equal(str, prim_uuid); + g_free(str); + + if (!uuid_ok) + continue; + + str = g_key_file_get_string(key_file, *handle, "Value", NULL); + if (!str) + continue; + + end = g_key_file_get_integer(key_file, *handle, + "EndGroupHandle", NULL); + if (end == 0) { + g_free(str); + continue; + } + + prim = g_new0(struct gatt_primary, 1); + prim->range.start = atoi(*handle); + prim->range.end = end; + + switch (strlen(str)) { + case 4: + uuid.type = SDP_UUID16; + sscanf(str, "%04hx", &uuid.value.uuid16); + break; + case 8: + uuid.type = SDP_UUID32; + sscanf(str, "%08x", &uuid.value.uuid32); + break; + case 32: + uuid.type = SDP_UUID128; + memset(tmp, 0, sizeof(tmp)); + for (i = 0; i < 16; i++) { + memcpy(tmp, str + (i * 2), 2); + uuid.value.uuid128.data[i] = + (uint8_t) strtol(tmp, NULL, 16); + } + break; + default: + g_free(str); + g_free(prim); + continue; + } + + service_uuid = bt_uuid2string(&uuid); + memcpy(prim->uuid, service_uuid, MAX_LEN_UUID_STR); + free(service_uuid); + g_free(str); + + device->primaries = g_slist_append(device->primaries, prim); + } + + g_strfreev(groups); + g_key_file_free(key_file); + free(prim_uuid); +} + +static void device_register_primaries(struct btd_device *device, + GSList *prim_list, int psm) +{ + device->primaries = g_slist_concat(device->primaries, prim_list); +} + +static void add_primary(struct gatt_db_attribute *attr, void *user_data) +{ + GSList **new_services = user_data; + struct gatt_primary *prim; + bt_uuid_t uuid; + + prim = g_new0(struct gatt_primary, 1); + if (!prim) { + DBG("Failed to allocate gatt_primary structure"); + return; + } + + gatt_db_attribute_get_service_handles(attr, &prim->range.start, + &prim->range.end); + gatt_db_attribute_get_service_uuid(attr, &uuid); + bt_uuid_to_string(&uuid, prim->uuid, sizeof(prim->uuid)); + + *new_services = g_slist_append(*new_services, prim); +} + +static void load_desc_value(struct gatt_db_attribute *attrib, + int err, void *user_data) +{ + if (err) + warn("loading descriptor value to db failed"); +} + +static ssize_t str2val(const char *str, uint8_t *val, size_t len) +{ + const char *pos = str; + size_t i; + + for (i = 0; i < len; i++) { + if (sscanf(pos, "%2hhx", &val[i]) != 1) + break; + pos += 2; + } + + return i; +} + +static int load_desc(char *handle, char *value, + struct gatt_db_attribute *service) +{ + char uuid_str[MAX_LEN_UUID_STR]; + struct gatt_db_attribute *att; + uint16_t handle_int; + uint16_t val; + bt_uuid_t uuid, ext_uuid; + + if (sscanf(handle, "%04hx", &handle_int) != 1) + return -EIO; + + /* Check if there is any value stored, otherwise it is just the UUID */ + if (sscanf(value, "%04hx:%s", &val, uuid_str) != 2) { + if (sscanf(value, "%s", uuid_str) != 1) + return -EIO; + val = 0; + } + + DBG("loading descriptor handle: 0x%04x, value: 0x%04x, value uuid: %s", + handle_int, val, uuid_str); + + bt_string_to_uuid(&uuid, uuid_str); + bt_uuid16_create(&ext_uuid, GATT_CHARAC_EXT_PROPER_UUID); + + /* If it is CEP then it must contain the value */ + if (!bt_uuid_cmp(&uuid, &ext_uuid) && !val) { + warn("cannot load CEP descriptor without value"); + return -EIO; + } + + att = gatt_db_service_insert_descriptor(service, handle_int, &uuid, + 0, NULL, NULL, NULL); + if (!att || gatt_db_attribute_get_handle(att) != handle_int) { + warn("loading descriptor to db failed"); + return -EIO; + } + + if (val) { + if (!gatt_db_attribute_write(att, 0, (uint8_t *)&val, + sizeof(val), 0, NULL, + load_desc_value, NULL)) + return -EIO; + } + + return 0; +} + +static int load_chrc(char *handle, char *value, + struct gatt_db_attribute *service) +{ + uint16_t properties, value_handle, handle_int; + char uuid_str[MAX_LEN_UUID_STR]; + struct gatt_db_attribute *att; + char val_str[32]; + uint8_t val[16]; + size_t val_len; + bt_uuid_t uuid; + + if (sscanf(handle, "%04hx", &handle_int) != 1) + return -EIO; + + /* Check if there is any value stored */ + if (sscanf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hx:%32s:%s", + &value_handle, &properties, val_str, uuid_str) != 4) { + if (sscanf(value, GATT_CHARAC_UUID_STR ":%04hx:%02hx:%s", + &value_handle, &properties, uuid_str) != 3) + return -EIO; + val_len = 0; + } else + val_len = str2val(val_str, val, sizeof(val)); + + bt_string_to_uuid(&uuid, uuid_str); + + /* Log debug message. */ + DBG("loading characteristic handle: 0x%04x, value handle: 0x%04x," + " properties 0x%04x value: %s uuid: %s", + handle_int, value_handle, properties, + val_len ? val_str : "", uuid_str); + + att = gatt_db_service_insert_characteristic(service, value_handle, + &uuid, 0, properties, + NULL, NULL, NULL); + if (!att || gatt_db_attribute_get_handle(att) != value_handle) { + warn("loading characteristic to db failed"); + return -EIO; + } + + if (val_len) { + if (!gatt_db_attribute_write(att, 0, val, val_len, 0, NULL, + load_desc_value, NULL)) + return -EIO; + } + + return 0; +} + +static int load_incl(struct gatt_db *db, char *handle, char *value, + struct gatt_db_attribute *service) +{ + char uuid_str[MAX_LEN_UUID_STR]; + struct gatt_db_attribute *att; + uint16_t start, end; + + if (sscanf(handle, "%04hx", &start) != 1) + return -EIO; + + if (sscanf(value, GATT_INCLUDE_UUID_STR ":%04hx:%04hx:%s", &start, &end, + uuid_str) != 3) + return -EIO; + + /* Log debug message. */ + DBG("loading included service: 0x%04x, end: 0x%04x, uuid: %s", start, + end, uuid_str); + + att = gatt_db_get_attribute(db, start); + if (!att) { + warn("loading included service to db failed - no such service"); + return -EIO; + } + + att = gatt_db_service_add_included(service, att); + if (!att) { + warn("loading included service to db failed"); + return -EIO; + } + + return 0; +} + +static int load_service(struct gatt_db *db, char *handle, char *value) +{ + struct gatt_db_attribute *att; + uint16_t start, end; + char type[MAX_LEN_UUID_STR], uuid_str[MAX_LEN_UUID_STR]; + bt_uuid_t uuid; + bool primary; + + if (sscanf(handle, "%04hx", &start) != 1) + return -EIO; + + if (sscanf(value, "%[^:]:%04hx:%s", type, &end, uuid_str) != 3) + return -EIO; + + if (g_str_equal(type, GATT_PRIM_SVC_UUID_STR)) + primary = true; + else if (g_str_equal(type, GATT_SND_SVC_UUID_STR)) + primary = false; + else + return -EIO; + + bt_string_to_uuid(&uuid, uuid_str); + + /* Log debug message. */ + DBG("loading service: 0x%04x, end: 0x%04x, uuid: %s", + start, end, uuid_str); + + att = gatt_db_insert_service(db, start, &uuid, primary, + end - start + 1); + if (!att) { + error("Unable load service into db!"); + return -EIO; + } + + return 0; +} + +static int load_gatt_db_impl(GKeyFile *key_file, char **keys, + struct gatt_db *db) +{ + struct gatt_db_attribute *current_service; + char **handle, *value, type[MAX_LEN_UUID_STR]; + int ret; + + /* first load service definitions */ + for (handle = keys; *handle; handle++) { + value = g_key_file_get_string(key_file, "Attributes", *handle, + NULL); + + if (sscanf(value, "%[^:]:", type) != 1) { + warn("Missing Type in attribute definition"); + g_free(value); + return -EIO; + } + + if (g_str_equal(type, GATT_PRIM_SVC_UUID_STR) || + g_str_equal(type, GATT_SND_SVC_UUID_STR)) { + ret = load_service(db, *handle, value); + if (ret) { + g_free(value); + return ret; + } + } + + g_free(value); + } + + current_service = NULL; + /* then fill them with data*/ + for (handle = keys; *handle; handle++) { + value = g_key_file_get_string(key_file, "Attributes", *handle, + NULL); + + if (sscanf(value, "%[^:]:", type) != 1) { + warn("Missing Type in attribute definition"); + g_free(value); + return -EIO; + } + + if (g_str_equal(type, GATT_PRIM_SVC_UUID_STR) || + g_str_equal(type, GATT_SND_SVC_UUID_STR)) { + uint16_t tmp; + uint16_t start, end; + bool primary; + bt_uuid_t uuid; + char uuid_str[MAX_LEN_UUID_STR]; + + if (sscanf(*handle, "%04hx", &tmp) != 1) { + warn("Unable to parse attribute handle"); + g_free(value); + return -EIO; + } + + if (current_service) + gatt_db_service_set_active(current_service, + true); + + current_service = gatt_db_get_attribute(db, tmp); + + gatt_db_attribute_get_service_data(current_service, + &start, &end, + &primary, &uuid); + + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + } else if (g_str_equal(type, GATT_INCLUDE_UUID_STR)) { + ret = load_incl(db, *handle, value, current_service); + } else if (g_str_equal(type, GATT_CHARAC_UUID_STR)) { + ret = load_chrc(*handle, value, current_service); + } else { + ret = load_desc(*handle, value, current_service); + } + + g_free(value); + if (ret) { + gatt_db_clear(db); + return ret; + } + } + + if (current_service) + gatt_db_service_set_active(current_service, true); + + return 0; +} + +static void load_gatt_db(struct btd_device *device, const char *local, + const char *peer) +{ + char **keys, filename[PATH_MAX]; + GKeyFile *key_file; + + if (!gatt_cache_is_enabled(device)) + return; + + DBG("Restoring %s gatt database from file", peer); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", local, peer); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + keys = g_key_file_get_keys(key_file, "Attributes", NULL, NULL); + + if (!keys) { + warn("No cache for %s", peer); + g_key_file_free(key_file); + return; + } + + if (load_gatt_db_impl(key_file, keys, device->db)) + warn("Unable to load gatt db from file for %s", peer); + + g_strfreev(keys); + g_key_file_free(key_file); + + g_slist_free_full(device->primaries, g_free); + device->primaries = NULL; + gatt_db_foreach_service(device->db, NULL, add_primary, + &device->primaries); +} + +static void device_add_uuids(struct btd_device *device, GSList *uuids) +{ + GSList *l; + bool changed = false; + + for (l = uuids; l != NULL; l = g_slist_next(l)) { + GSList *match = g_slist_find_custom(device->uuids, l->data, + bt_uuid_strcmp); + if (match) + continue; + + changed = true; + device->uuids = g_slist_insert_sorted(device->uuids, + g_strdup(l->data), + bt_uuid_strcmp); + } + + if (changed) + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "UUIDs"); +} + +static bool device_match_profile(struct btd_device *device, + struct btd_profile *profile, + GSList *uuids) +{ + if (profile->remote_uuid == NULL) + return false; + + if (g_slist_find_custom(uuids, profile->remote_uuid, + bt_uuid_strcmp) == NULL) + return false; + + return true; +} + +static void add_gatt_service(struct gatt_db_attribute *attr, void *user_data) +{ + struct btd_device *device = user_data; + struct btd_service *service; + struct btd_profile *profile; + bt_uuid_t uuid; + char uuid_str[MAX_LEN_UUID_STR]; + GSList *l; + + gatt_db_attribute_get_service_uuid(attr, &uuid); + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + + /* Check if service was already probed */ + l = find_service_with_uuid(device->services, uuid_str); + if (l) + goto done; + + /* Add UUID and probe service */ + btd_device_add_uuid(device, uuid_str); + + /* Check if service was probed */ + l = find_service_with_uuid(device->services, uuid_str); + if (!l) + return; + +done: + /* Mark service as active to skip discovering it again */ + gatt_db_service_set_active(attr, true); + + service = l->data; + profile = btd_service_get_profile(service); + + /* Claim attributes of internal profiles */ + if (!profile->external) { + /* Mark the service as claimed by the existing profile. */ + gatt_db_service_set_claimed(attr, true); + } + + /* Notify driver about the new connection */ + service_accept(service); +} + +static void device_add_gatt_services(struct btd_device *device) +{ + char addr[18]; + + ba2str(&device->bdaddr, addr); + + if (device->blocked) { + DBG("Skipping profiles for blocked device %s", addr); + return; + } + + gatt_db_foreach_service(device->db, NULL, add_gatt_service, device); +} + +static void device_accept_gatt_profiles(struct btd_device *device) +{ + GSList *l; + + for (l = device->services; l != NULL; l = g_slist_next(l)) + service_accept(l->data); +} + +static void device_remove_gatt_service(struct btd_device *device, + struct gatt_db_attribute *attr) +{ + struct btd_service *service; + bt_uuid_t uuid; + char uuid_str[MAX_LEN_UUID_STR]; + GSList *l; + + gatt_db_attribute_get_service_uuid(attr, &uuid); + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + + l = find_service_with_uuid(device->services, uuid_str); + if (!l) + return; + + service = l->data; + device->services = g_slist_delete_link(device->services, l); + device->pending = g_slist_remove(device->pending, service); + service_remove(service); +} + +static gboolean gatt_services_changed(gpointer user_data) +{ + struct btd_device *device = user_data; + + store_gatt_db(device); + + return FALSE; +} + +static void gatt_service_added(struct gatt_db_attribute *attr, void *user_data) +{ + struct btd_device *device = user_data; + GSList *new_service = NULL; + uint16_t start, end; + + if (!bt_gatt_client_is_ready(device->client)) + return; + + gatt_db_attribute_get_service_data(attr, &start, &end, NULL, NULL); + + DBG("start: 0x%04x, end: 0x%04x", start, end); + + /* + * TODO: Remove the primaries list entirely once all profiles use + * shared/gatt. + */ + add_primary(attr, &new_service); + if (!new_service) + return; + + device_register_primaries(device, new_service, -1); + + add_gatt_service(attr, device); + + btd_gatt_client_service_added(device->client_dbus, attr); + + gatt_services_changed(device); +} + +static gint prim_attr_cmp(gconstpointer a, gconstpointer b) +{ + const struct gatt_primary *prim = a; + const struct gatt_db_attribute *attr = b; + uint16_t start, end; + + gatt_db_attribute_get_service_handles(attr, &start, &end); + + return !(prim->range.start == start && prim->range.end == end); +} + +static gint prim_uuid_cmp(gconstpointer a, gconstpointer b) +{ + const struct gatt_primary *prim = a; + const char *uuid = b; + + return bt_uuid_strcmp(prim->uuid, uuid); +} + +static void gatt_service_removed(struct gatt_db_attribute *attr, + void *user_data) +{ + struct btd_device *device = user_data; + GSList *l; + struct gatt_primary *prim; + uint16_t start, end; + + /* + * NOTE: shared/gatt-client clears the database in case of failure. This + * triggers the service_removed callback for all affected services. + * Hence, this function will be called in the following cases: + * + * 1. When a GATT service gets removed due to "Service Changed". + * + * 2. When a GATT service gets removed when the database get cleared + * upon disconnection with a non-bonded device. + * + * 3. When a GATT service gets removed when the database get cleared + * by shared/gatt-client when its initialization procedure fails, + * e.g. due to an ATT protocol error or an unexpected disconnect. + * In this case the gatt-client will not be ready. + */ + + gatt_db_attribute_get_service_handles(attr, &start, &end); + + DBG("start: 0x%04x, end: 0x%04x", start, end); + + /* Remove the corresponding gatt_primary */ + l = g_slist_find_custom(device->primaries, attr, prim_attr_cmp); + if (!l) + return; + + prim = l->data; + device->primaries = g_slist_delete_link(device->primaries, l); + + /* + * Remove the corresponding UUIDs entry and profile, only if this is + * the last service with this UUID. + */ + l = g_slist_find_custom(device->uuids, prim->uuid, bt_uuid_strcmp); + + if (l && !g_slist_find_custom(device->primaries, prim->uuid, + prim_uuid_cmp)) { + /* + * If this happend since the db was cleared for a non-bonded + * device, then don't remove the btd_service just yet. We do + * this so that we can avoid re-probing the profile if the same + * GATT service is found on the device on re-connection. + * However, if the device is marked as temporary, then we + * remove it anyway. + */ + if (device->client || device->temporary == TRUE) + device_remove_gatt_service(device, attr); + + g_free(l->data); + device->uuids = g_slist_delete_link(device->uuids, l); + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "UUIDs"); + } + + g_free(prim); + + store_device_info(device); + + btd_gatt_client_service_removed(device->client_dbus, attr); + + gatt_services_changed(device); +} + +static struct btd_device *device_new(struct btd_adapter *adapter, + const char *address) +{ + char *address_up; + struct btd_device *device; + const char *adapter_path = adapter_get_path(adapter); + + DBG("address %s", address); + + device = g_try_malloc0(sizeof(struct btd_device)); + if (device == NULL) + return NULL; + + device->tx_power = 127; + + device->db = gatt_db_new(); + if (!device->db) { + g_free(device); + return NULL; + } + + memset(device->ad_flags, INVALID_FLAGS, sizeof(device->ad_flags)); + + device->ad = bt_ad_new(); + if (!device->ad) { + device_free(device); + return NULL; + } + + address_up = g_ascii_strup(address, -1); + device->path = g_strdup_printf("%s/dev_%s", adapter_path, address_up); + g_strdelimit(device->path, ":", '_'); + g_free(address_up); + + str2ba(address, &device->bdaddr); + + device->client_dbus = btd_gatt_client_new(device); + if (!device->client_dbus) { + error("Failed to create btd_gatt_client"); + device_free(device); + return NULL; + } + + DBG("Creating device %s", device->path); + + if (g_dbus_register_interface(dbus_conn, + device->path, DEVICE_INTERFACE, + device_methods, NULL, + device_properties, device, + device_free) == FALSE) { + error("Unable to register device interface for %s", address); + device_free(device); + return NULL; + } + + device->adapter = adapter; + device->temporary = true; + + device->db_id = gatt_db_register(device->db, gatt_service_added, + gatt_service_removed, device, NULL); + + return btd_device_ref(device); +} + +struct btd_device *device_create_from_storage(struct btd_adapter *adapter, + const char *address, GKeyFile *key_file) +{ + struct btd_device *device; + const char *src_dir; + + DBG("address %s", address); + + device = device_new(adapter, address); + if (device == NULL) + return NULL; + + convert_info(device, key_file); + + src_dir = btd_adapter_get_storage_dir(adapter); + + load_info(device, src_dir, address, key_file); + load_att_info(device, src_dir, address); + + return device; +} + +struct btd_device *device_create(struct btd_adapter *adapter, + const bdaddr_t *bdaddr, uint8_t bdaddr_type) +{ + struct btd_device *device; + char dst[18]; + char *str; + + ba2str(bdaddr, dst); + DBG("dst %s", dst); + + device = device_new(adapter, dst); + if (device == NULL) + return NULL; + + device->bdaddr_type = bdaddr_type; + + if (bdaddr_type == BDADDR_BREDR) + device->bredr = true; + else + device->le = true; + + str = load_cached_name(device, btd_adapter_get_storage_dir(adapter), + dst); + if (str) { + strcpy(device->name, str); + g_free(str); + } + + return device; +} + +char *btd_device_get_storage_path(struct btd_device *device, + const char *filename) +{ + char dstaddr[18]; + + if (device_address_is_private(device)) { + warn("Refusing storage path for private addressed device %s", + device->path); + return NULL; + } + + ba2str(&device->bdaddr, dstaddr); + + if (!filename) + return g_strdup_printf(STORAGEDIR "/%s/%s", + btd_adapter_get_storage_dir(device->adapter), + dstaddr); + + return g_strdup_printf(STORAGEDIR "/%s/%s/%s", + btd_adapter_get_storage_dir(device->adapter), + dstaddr, filename); +} + +void btd_device_device_set_name(struct btd_device *device, const char *name) +{ + if (strncmp(name, device->name, MAX_NAME_LENGTH) == 0) + return; + + DBG("%s %s", device->path, name); + + strncpy(device->name, name, MAX_NAME_LENGTH); + + store_device_info(device); + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Name"); + + if (device->alias != NULL) + return; + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Alias"); +} + +void device_get_name(struct btd_device *device, char *name, size_t len) +{ + if (name != NULL && len > 0) { + strncpy(name, device->name, len - 1); + name[len - 1] = '\0'; + } +} + +bool device_name_known(struct btd_device *device) +{ + return device->name[0] != '\0'; +} + +void device_set_class(struct btd_device *device, uint32_t class) +{ + if (device->class == class) + return; + + DBG("%s 0x%06X", device->path, class); + + device->class = class; + + store_device_info(device); + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Class"); + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Icon"); +} + +void device_update_addr(struct btd_device *device, const bdaddr_t *bdaddr, + uint8_t bdaddr_type) +{ + if (!bacmp(bdaddr, &device->bdaddr) && + bdaddr_type == device->bdaddr_type) + return; + + /* Since this function is only used for LE SMP Identity + * Resolving purposes we can now assume LE is supported. + */ + device->le = true; + + bacpy(&device->bdaddr, bdaddr); + device->bdaddr_type = bdaddr_type; + + store_device_info(device); + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Address"); + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "AddressType"); +} + +void device_set_bredr_support(struct btd_device *device) +{ + if (device->bredr) + return; + + device->bredr = true; + store_device_info(device); +} + +void device_set_le_support(struct btd_device *device, uint8_t bdaddr_type) +{ + if (device->le) + return; + + device->le = true; + device->bdaddr_type = bdaddr_type; + + store_device_info(device); +} + +void device_update_last_seen(struct btd_device *device, uint8_t bdaddr_type) +{ + if (bdaddr_type == BDADDR_BREDR) + device->bredr_seen = time(NULL); + else + device->le_seen = time(NULL); +} + +/* It is possible that we have two device objects for the same device in + * case it has first been discovered over BR/EDR and has a private + * address when discovered over LE for the first time. In such a case we + * need to inherit critical values from the duplicate so that we don't + * ovewrite them when writing to storage. The next time bluetoothd + * starts the device will show up as a single instance. + */ +void device_merge_duplicate(struct btd_device *dev, struct btd_device *dup) +{ + GSList *l; + + DBG(""); + + dev->bredr = dup->bredr; + + dev->trusted = dup->trusted; + dev->blocked = dup->blocked; + + for (l = dup->uuids; l; l = g_slist_next(l)) + dev->uuids = g_slist_append(dev->uuids, g_strdup(l->data)); + + if (dev->name[0] == '\0') + strcpy(dev->name, dup->name); + + if (!dev->alias) + dev->alias = g_strdup(dup->alias); + + dev->class = dup->class; + + dev->vendor_src = dup->vendor_src; + dev->vendor = dup->vendor; + dev->product = dup->product; + dev->version = dup->version; +} + +uint32_t btd_device_get_class(struct btd_device *device) +{ + return device->class; +} + +uint16_t btd_device_get_vendor(struct btd_device *device) +{ + return device->vendor; +} + +uint16_t btd_device_get_vendor_src(struct btd_device *device) +{ + return device->vendor_src; +} + +uint16_t btd_device_get_product(struct btd_device *device) +{ + return device->product; +} + +uint16_t btd_device_get_version(struct btd_device *device) +{ + return device->version; +} + +static void delete_folder_tree(const char *dirname) +{ + DIR *dir; + struct dirent *entry; + char filename[PATH_MAX]; + + dir = opendir(dirname); + if (dir == NULL) + return; + + while ((entry = readdir(dir)) != NULL) { + if (g_str_equal(entry->d_name, ".") || + g_str_equal(entry->d_name, "..")) + continue; + + if (entry->d_type == DT_UNKNOWN) + entry->d_type = util_get_dt(dirname, entry->d_name); + + snprintf(filename, PATH_MAX, "%s/%s", dirname, entry->d_name); + + if (entry->d_type == DT_DIR) + delete_folder_tree(filename); + else + unlink(filename); + } + closedir(dir); + + rmdir(dirname); +} + +static void device_remove_stored(struct btd_device *device) +{ + char device_addr[18]; + char filename[PATH_MAX]; + GKeyFile *key_file; + char *data; + gsize length = 0; + + if (device->bredr_state.bonded) { + device->bredr_state.bonded = false; + btd_adapter_remove_bonding(device->adapter, &device->bdaddr, + BDADDR_BREDR); + } + + if (device->le_state.bonded) { + device->le_state.bonded = false; + btd_adapter_remove_bonding(device->adapter, &device->bdaddr, + device->bdaddr_type); + } + + device->bredr_state.paired = false; + device->le_state.paired = false; + + if (device->blocked) + device_unblock(device, TRUE, FALSE); + + ba2str(&device->bdaddr, device_addr); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s", + btd_adapter_get_storage_dir(device->adapter), + device_addr); + delete_folder_tree(filename); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", + btd_adapter_get_storage_dir(device->adapter), + device_addr); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + g_key_file_remove_group(key_file, "ServiceRecords", NULL); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + + g_free(data); + g_key_file_free(key_file); +} + +void device_remove(struct btd_device *device, gboolean remove_stored) +{ + DBG("Removing device %s", device->path); + + if (device->bonding) { + uint8_t status; + + if (device->bredr_state.connected) + status = MGMT_STATUS_DISCONNECTED; + else + status = MGMT_STATUS_CONNECT_FAILED; + + device_cancel_bonding(device, status); + } + + if (device->browse) + browse_request_cancel(device->browse); + + while (device->services != NULL) { + struct btd_service *service = device->services->data; + + device->services = g_slist_remove(device->services, service); + service_remove(service); + } + + g_slist_free(device->pending); + device->pending = NULL; + + if (btd_device_is_connected(device)) { + if (device->disconn_timer > 0) + g_source_remove(device->disconn_timer); + disconnect_all(device); + } + + if (device->store_id > 0) { + g_source_remove(device->store_id); + device->store_id = 0; + + if (!remove_stored) + store_device_info_cb(device); + } + + if (remove_stored) + device_remove_stored(device); + + btd_device_unref(device); +} + +int device_address_cmp(gconstpointer a, gconstpointer b) +{ + const struct btd_device *device = a; + const char *address = b; + char addr[18]; + + ba2str(&device->bdaddr, addr); + return strcasecmp(addr, address); +} + +int device_bdaddr_cmp(gconstpointer a, gconstpointer b) +{ + const struct btd_device *device = a; + const bdaddr_t *bdaddr = b; + + return bacmp(&device->bdaddr, bdaddr); +} + +static bool addr_is_public(uint8_t addr_type) +{ + if (addr_type == BDADDR_BREDR || addr_type == BDADDR_LE_PUBLIC) + return true; + + return false; +} + +int device_addr_type_cmp(gconstpointer a, gconstpointer b) +{ + const struct btd_device *dev = a; + const struct device_addr_type *addr = b; + int cmp; + + cmp = bacmp(&dev->bdaddr, &addr->bdaddr); + + /* + * Address matches and both old and new are public addresses + * (doesn't matter whether LE or BR/EDR, then consider this a + * match. + */ + if (!cmp && addr_is_public(addr->bdaddr_type) && + addr_is_public(dev->bdaddr_type)) + return 0; + + if (addr->bdaddr_type == BDADDR_BREDR) { + if (!dev->bredr) + return -1; + + return cmp; + } + + if (!dev->le) + return -1; + + if (addr->bdaddr_type != dev->bdaddr_type) { + if (addr->bdaddr_type == dev->conn_bdaddr_type) + return bacmp(&dev->conn_bdaddr, &addr->bdaddr); + return -1; + } + + return cmp; +} + +static gboolean record_has_uuid(const sdp_record_t *rec, + const char *profile_uuid) +{ + sdp_list_t *pat; + + for (pat = rec->pattern; pat != NULL; pat = pat->next) { + char *uuid; + int ret; + + uuid = bt_uuid2string(pat->data); + if (!uuid) + continue; + + ret = strcasecmp(uuid, profile_uuid); + + free(uuid); + + if (ret == 0) + return TRUE; + } + + return FALSE; +} + +GSList *btd_device_get_uuids(struct btd_device *device) +{ + return device->uuids; +} + +struct probe_data { + struct btd_device *dev; + GSList *uuids; +}; + +static struct btd_service *probe_service(struct btd_device *device, + struct btd_profile *profile, + GSList *uuids) +{ + GSList *l; + struct btd_service *service; + + if (profile->device_probe == NULL) + return NULL; + + if (!device_match_profile(device, profile, uuids)) + return NULL; + + l = find_service_with_profile(device->services, profile); + if (l) + return l->data; + + service = service_create(device, profile); + + if (service_probe(service)) { + btd_service_unref(service); + return NULL; + } + + /* Only set auto connect if profile has set the flag and can really + * accept connections. + */ + if (profile->auto_connect && profile->accept) + device_set_auto_connect(device, TRUE); + + return service; +} + +static void dev_probe(struct btd_profile *p, void *user_data) +{ + struct probe_data *d = user_data; + struct btd_service *service; + + service = probe_service(d->dev, p, d->uuids); + if (!service) + return; + + d->dev->services = g_slist_append(d->dev->services, service); +} + +void device_probe_profile(gpointer a, gpointer b) +{ + struct btd_device *device = a; + struct btd_profile *profile = b; + struct btd_service *service; + + service = probe_service(device, profile, device->uuids); + if (!service) + return; + + device->services = g_slist_append(device->services, service); + + if (!profile->auto_connect || !device->general_connect) + return; + + device->pending = g_slist_append(device->pending, service); + + if (g_slist_length(device->pending) == 1) + connect_next(device); +} + +void device_remove_profile(gpointer a, gpointer b) +{ + struct btd_device *device = a; + struct btd_profile *profile = b; + struct btd_service *service; + GSList *l; + + l = find_service_with_profile(device->services, profile); + if (l == NULL) + return; + + service = l->data; + device->services = g_slist_delete_link(device->services, l); + device->pending = g_slist_remove(device->pending, service); + service_remove(service); +} + +void device_probe_profiles(struct btd_device *device, GSList *uuids) +{ + struct probe_data d = { device, uuids }; + char addr[18]; + + ba2str(&device->bdaddr, addr); + + if (device->blocked) { + DBG("Skipping profiles for blocked device %s", addr); + goto add_uuids; + } + + DBG("Probing profiles for device %s", addr); + + btd_profile_foreach(dev_probe, &d); + +add_uuids: + device_add_uuids(device, uuids); +} + +static void store_sdp_record(GKeyFile *key_file, sdp_record_t *rec) +{ + char handle_str[11]; + sdp_buf_t buf; + int size, i; + char *str; + + sprintf(handle_str, "0x%8.8X", rec->handle); + + if (sdp_gen_record_pdu(rec, &buf) < 0) + return; + + size = buf.data_size; + + str = g_malloc0(size*2+1); + + for (i = 0; i < size; i++) + sprintf(str + (i * 2), "%02X", buf.data[i]); + + g_key_file_set_string(key_file, "ServiceRecords", handle_str, str); + + free(buf.data); + g_free(str); +} + +static void store_primaries_from_sdp_record(GKeyFile *key_file, + sdp_record_t *rec) +{ + uuid_t uuid; + char *att_uuid, *prim_uuid; + uint16_t start = 0, end = 0, psm = 0; + char handle[6], uuid_str[33]; + int i; + + sdp_uuid16_create(&uuid, ATT_UUID); + att_uuid = bt_uuid2string(&uuid); + + sdp_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + prim_uuid = bt_uuid2string(&uuid); + + if (!record_has_uuid(rec, att_uuid)) + goto done; + + if (!gatt_parse_record(rec, &uuid, &psm, &start, &end)) + goto done; + + sprintf(handle, "%hu", start); + switch (uuid.type) { + case SDP_UUID16: + sprintf(uuid_str, "%4.4X", uuid.value.uuid16); + break; + case SDP_UUID32: + sprintf(uuid_str, "%8.8X", uuid.value.uuid32); + break; + case SDP_UUID128: + for (i = 0; i < 16; i++) + sprintf(uuid_str + (i * 2), "%2.2X", + uuid.value.uuid128.data[i]); + break; + default: + uuid_str[0] = '\0'; + } + + g_key_file_set_string(key_file, handle, "UUID", prim_uuid); + g_key_file_set_string(key_file, handle, "Value", uuid_str); + g_key_file_set_integer(key_file, handle, "EndGroupHandle", end); + +done: + free(prim_uuid); + free(att_uuid); +} + +static int rec_cmp(const void *a, const void *b) +{ + const sdp_record_t *r1 = a; + const sdp_record_t *r2 = b; + + return r1->handle - r2->handle; +} + +static int update_record(struct browse_req *req, const char *uuid, + sdp_record_t *rec) +{ + GSList *l; + + /* Check for duplicates */ + if (sdp_list_find(req->records, rec, rec_cmp)) + return -EALREADY; + + /* Copy record */ + req->records = sdp_list_append(req->records, sdp_copy_record(rec)); + + /* Check if UUID is duplicated */ + l = g_slist_find_custom(req->device->uuids, uuid, bt_uuid_strcmp); + if (l == NULL) { + l = g_slist_find_custom(req->profiles_added, uuid, + bt_uuid_strcmp); + if (l != NULL) + return 0; + req->profiles_added = g_slist_append(req->profiles_added, + g_strdup(uuid)); + } + + return 0; +} + +static void update_bredr_services(struct browse_req *req, sdp_list_t *recs) +{ + struct btd_device *device = req->device; + sdp_list_t *seq; + char srcaddr[18], dstaddr[18]; + char sdp_file[PATH_MAX]; + char att_file[PATH_MAX]; + GKeyFile *sdp_key_file; + GKeyFile *att_key_file; + char *data; + gsize length = 0; + + ba2str(btd_adapter_get_address(device->adapter), srcaddr); + ba2str(&device->bdaddr, dstaddr); + + snprintf(sdp_file, PATH_MAX, STORAGEDIR "/%s/cache/%s", srcaddr, + dstaddr); + + sdp_key_file = g_key_file_new(); + g_key_file_load_from_file(sdp_key_file, sdp_file, 0, NULL); + + snprintf(att_file, PATH_MAX, STORAGEDIR "/%s/%s/attributes", srcaddr, + dstaddr); + + att_key_file = g_key_file_new(); + g_key_file_load_from_file(att_key_file, att_file, 0, NULL); + + for (seq = recs; seq; seq = seq->next) { + sdp_record_t *rec = (sdp_record_t *) seq->data; + sdp_list_t *svcclass = NULL; + char *profile_uuid; + + if (!rec) + break; + + if (sdp_get_service_classes(rec, &svcclass) < 0) + continue; + + /* Check for empty service classes list */ + if (svcclass == NULL) { + DBG("Skipping record with no service classes"); + continue; + } + + /* Extract the first element and skip the remainning */ + profile_uuid = bt_uuid2string(svcclass->data); + if (!profile_uuid) { + sdp_list_free(svcclass, free); + continue; + } + + if (bt_uuid_strcmp(profile_uuid, PNP_UUID) == 0) { + uint16_t source, vendor, product, version; + sdp_data_t *pdlist; + + pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID_SOURCE); + source = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, SDP_ATTR_VENDOR_ID); + vendor = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, SDP_ATTR_PRODUCT_ID); + product = pdlist ? pdlist->val.uint16 : 0x0000; + + pdlist = sdp_data_get(rec, SDP_ATTR_VERSION); + version = pdlist ? pdlist->val.uint16 : 0x0000; + + if (source || vendor || product || version) + btd_device_set_pnpid(device, source, vendor, + product, version); + } + + if (update_record(req, profile_uuid, rec) < 0) + goto next; + + if (sdp_key_file) + store_sdp_record(sdp_key_file, rec); + + if (att_key_file) + store_primaries_from_sdp_record(att_key_file, rec); + +next: + free(profile_uuid); + sdp_list_free(svcclass, free); + } + + if (sdp_key_file) { + data = g_key_file_to_data(sdp_key_file, &length, NULL); + if (length > 0) { + create_file(sdp_file, S_IRUSR | S_IWUSR); + g_file_set_contents(sdp_file, data, length, NULL); + } + + g_free(data); + g_key_file_free(sdp_key_file); + } + + if (att_key_file) { + data = g_key_file_to_data(att_key_file, &length, NULL); + if (length > 0) { + create_file(att_file, S_IRUSR | S_IWUSR); + g_file_set_contents(att_file, data, length, NULL); + } + + g_free(data); + g_key_file_free(att_key_file); + } +} + +static int primary_cmp(gconstpointer a, gconstpointer b) +{ + return memcmp(a, b, sizeof(struct gatt_primary)); +} + +static void update_gatt_uuids(struct browse_req *req, GSList *current, + GSList *found) +{ + GSList *l, *lmatch; + + /* Added Profiles */ + for (l = found; l; l = g_slist_next(l)) { + struct gatt_primary *prim = l->data; + + /* Entry found ? */ + lmatch = g_slist_find_custom(current, prim, primary_cmp); + if (lmatch) + continue; + + /* New entry */ + req->profiles_added = g_slist_append(req->profiles_added, + g_strdup(prim->uuid)); + + DBG("UUID Added: %s", prim->uuid); + } +} + +static GSList *device_services_from_record(struct btd_device *device, + GSList *profiles) +{ + GSList *l, *prim_list = NULL; + char *att_uuid; + uuid_t proto_uuid; + + sdp_uuid16_create(&proto_uuid, ATT_UUID); + att_uuid = bt_uuid2string(&proto_uuid); + + for (l = profiles; l; l = l->next) { + const char *profile_uuid = l->data; + const sdp_record_t *rec; + struct gatt_primary *prim; + uint16_t start = 0, end = 0, psm = 0; + uuid_t prim_uuid; + + rec = btd_device_get_record(device, profile_uuid); + if (!rec) + continue; + + if (!record_has_uuid(rec, att_uuid)) + continue; + + if (!gatt_parse_record(rec, &prim_uuid, &psm, &start, &end)) + continue; + + prim = g_new0(struct gatt_primary, 1); + prim->range.start = start; + prim->range.end = end; + sdp_uuid2strn(&prim_uuid, prim->uuid, sizeof(prim->uuid)); + + prim_list = g_slist_append(prim_list, prim); + } + + free(att_uuid); + + return prim_list; +} + +static void search_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct browse_req *req = user_data; + struct btd_device *device = req->device; + GSList *primaries; + char addr[18]; + + ba2str(&device->bdaddr, addr); + + if (err < 0) { + error("%s: error updating services: %s (%d)", + addr, strerror(-err), -err); + goto send_reply; + } + + update_bredr_services(req, recs); + + if (device->tmp_records) + sdp_list_free(device->tmp_records, + (sdp_free_func_t) sdp_record_free); + + device->tmp_records = req->records; + req->records = NULL; + + if (!req->profiles_added) { + DBG("%s: No service update", addr); + goto send_reply; + } + + primaries = device_services_from_record(device, req->profiles_added); + if (primaries) + device_register_primaries(device, primaries, ATT_PSM); + + /* + * TODO: The btd_service instances for GATT services need to be + * initialized with the service handles. Eventually this code should + * perform ATT protocol service discovery over the ATT PSM to obtain + * the full list of services and populate a client-role gatt_db over + * BR/EDR. + */ + device_probe_profiles(device, req->profiles_added); + + /* Propagate services changes */ + g_dbus_emit_property_changed(dbus_conn, req->device->path, + DEVICE_INTERFACE, "UUIDs"); + +send_reply: + device_svc_resolved(device, BROWSE_SDP, BDADDR_BREDR, err); +} + +static void browse_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct browse_req *req = user_data; + struct btd_device *device = req->device; + struct btd_adapter *adapter = device->adapter; + uuid_t uuid; + + /* If we have a valid response and req->search_uuid == 2, then L2CAP + * UUID & PNP searching was successful -- we are done */ + if (err < 0 || (req->search_uuid == 2 && req->records)) { + if (err == -ECONNRESET && req->reconnect_attempt < 1) { + req->search_uuid--; + req->reconnect_attempt++; + } else + goto done; + } + + update_bredr_services(req, recs); + + /* Search for mandatory uuids */ + if (uuid_list[req->search_uuid]) { + sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); + bt_search_service(btd_adapter_get_address(adapter), + &device->bdaddr, &uuid, + browse_cb, user_data, NULL, + req->sdp_flags); + return; + } + +done: + search_cb(recs, err, user_data); +} + +static bool device_get_auto_connect(struct btd_device *device) +{ + if (device->disable_auto_connect) + return false; + + return device->auto_connect; +} + +static void disconnect_gatt_service(gpointer data, gpointer user_data) +{ + struct btd_service *service = data; + struct btd_profile *profile = btd_service_get_profile(service); + + /* Ignore if profile cannot accept connections */ + if (!profile->accept) + return; + + btd_service_disconnect(service); +} + +static void att_disconnected_cb(int err, void *user_data) +{ + struct btd_device *device = user_data; + + DBG(""); + + if (device->browse) + goto done; + + DBG("%s (%d)", strerror(err), err); + + g_slist_foreach(device->services, disconnect_gatt_service, NULL); + + btd_gatt_client_disconnected(device->client_dbus); + + if (!device_get_auto_connect(device)) { + DBG("Automatic connection disabled"); + goto done; + } + + /* + * Keep scanning/re-connection active if disconnection reason + * is connection timeout, remote user terminated connection or local + * initiated disconnection. + */ + if (err == ETIMEDOUT || err == ECONNRESET || err == ECONNABORTED) + adapter_connect_list_add(device->adapter, device); + +done: + attio_cleanup(device); +} + +static void register_gatt_services(struct btd_device *device) +{ + struct browse_req *req = device->browse; + GSList *services = NULL; + + if (!bt_gatt_client_is_ready(device->client)) + return; + + /* + * TODO: Remove the primaries list entirely once all profiles use + * shared/gatt. + */ + gatt_db_foreach_service(device->db, NULL, add_primary, &services); + + btd_device_set_temporary(device, false); + + if (req) + update_gatt_uuids(req, device->primaries, services); + + g_slist_free_full(device->primaries, g_free); + device->primaries = NULL; + + device_register_primaries(device, services, -1); + + device_add_gatt_services(device); +} + +static void gatt_client_init(struct btd_device *device); + +static void gatt_client_ready_cb(bool success, uint8_t att_ecode, + void *user_data) +{ + struct btd_device *device = user_data; + + DBG("status: %s, error: %u", success ? "success" : "failed", att_ecode); + + if (!success) { + device_svc_resolved(device, BROWSE_GATT, device->bdaddr_type, + -EIO); + return; + } + + register_gatt_services(device); + + btd_gatt_client_ready(device->client_dbus); + + device_svc_resolved(device, BROWSE_GATT, device->bdaddr_type, 0); + + store_gatt_db(device); +} + +static void gatt_client_service_changed(uint16_t start_handle, + uint16_t end_handle, + void *user_data) +{ + DBG("start 0x%04x, end: 0x%04x", start_handle, end_handle); +} + +static void gatt_debug(const char *str, void *user_data) +{ + DBG("%s", str); +} + +static void gatt_client_init(struct btd_device *device) +{ + gatt_client_cleanup(device); + + if (!device->connect && !main_opts.reverse_discovery) { + DBG("Reverse service discovery disabled: skipping GATT client"); + return; + } + + device->client = bt_gatt_client_new(device->db, device->att, + device->att_mtu); + if (!device->client) { + DBG("Failed to initialize"); + return; + } + + bt_gatt_client_set_debug(device->client, gatt_debug, NULL, NULL); + + /* + * Notify notify existing service about the new connection so they can + * react to notifications while discovering services + */ + device_accept_gatt_profiles(device); + + device->gatt_ready_id = bt_gatt_client_ready_register(device->client, + gatt_client_ready_cb, + device, NULL); + if (!device->gatt_ready_id) { + DBG("Failed to register GATT ready callback"); + gatt_client_cleanup(device); + return; + } + + if (!bt_gatt_client_set_service_changed(device->client, + gatt_client_service_changed, + device, NULL)) { + DBG("Failed to set service changed handler"); + gatt_client_cleanup(device); + return; + } + + btd_gatt_client_connected(device->client_dbus); +} + +static void gatt_server_init(struct btd_device *device, + struct btd_gatt_database *database) +{ + struct gatt_db *db = btd_gatt_database_get_db(database); + + if (!db) { + error("No local GATT database exists for this adapter"); + return; + } + + gatt_server_cleanup(device); + + device->server = bt_gatt_server_new(db, device->att, device->att_mtu, + main_opts.key_size); + if (!device->server) { + error("Failed to initialize bt_gatt_server"); + return; + } + + bt_att_set_enc_key_size(device->att, device->ltk_enc_size); + bt_gatt_server_set_debug(device->server, gatt_debug, NULL, NULL); + + btd_gatt_database_server_connected(database, device->server); +} + +static bool local_counter(uint32_t *sign_cnt, void *user_data) +{ + struct btd_device *dev = user_data; + + if (!dev->local_csrk) + return false; + + *sign_cnt = dev->local_csrk->counter++; + + store_device_info(dev); + + return true; +} + +static bool remote_counter(uint32_t *sign_cnt, void *user_data) +{ + struct btd_device *dev = user_data; + + if (!dev->remote_csrk || *sign_cnt < dev->remote_csrk->counter) + return false; + + dev->remote_csrk->counter = *sign_cnt; + + store_device_info(dev); + + return true; +} + +bool device_attach_att(struct btd_device *dev, GIOChannel *io) +{ + GError *gerr = NULL; + GAttrib *attrib; + BtIOSecLevel sec_level; + uint16_t mtu; + uint16_t cid; + struct btd_gatt_database *database; + const bdaddr_t *dst; + char dstaddr[18]; + + bt_io_get(io, &gerr, BT_IO_OPT_SEC_LEVEL, &sec_level, + BT_IO_OPT_IMTU, &mtu, + BT_IO_OPT_CID, &cid, + BT_IO_OPT_INVALID); + + if (gerr) { + error("bt_io_get: %s", gerr->message); + g_error_free(gerr); + return false; + } + + if (sec_level == BT_IO_SEC_LOW && dev->le_state.paired) { + DBG("Elevating security level since LTK is available"); + + sec_level = BT_IO_SEC_MEDIUM; + bt_io_set(io, &gerr, BT_IO_OPT_SEC_LEVEL, sec_level, + BT_IO_OPT_INVALID); + if (gerr) { + error("bt_io_set: %s", gerr->message); + g_error_free(gerr); + return false; + } + } + + dev->att_mtu = MIN(mtu, main_opts.gatt_mtu); + attrib = g_attrib_new(io, + cid == ATT_CID ? BT_ATT_DEFAULT_LE_MTU : dev->att_mtu, + false); + if (!attrib) { + error("Unable to create new GAttrib instance"); + return false; + } + + dev->attrib = attrib; + dev->att = g_attrib_get_att(attrib); + + bt_att_ref(dev->att); + + dev->att_disconn_id = bt_att_register_disconnect(dev->att, + att_disconnected_cb, dev, NULL); + bt_att_set_close_on_unref(dev->att, true); + + if (dev->local_csrk) + bt_att_set_local_key(dev->att, dev->local_csrk->key, + local_counter, dev); + + if (dev->remote_csrk) + bt_att_set_remote_key(dev->att, dev->remote_csrk->key, + remote_counter, dev); + + database = btd_adapter_get_database(dev->adapter); + + dst = device_get_address(dev); + ba2str(dst, dstaddr); + + if (gatt_db_isempty(dev->db)) + load_gatt_db(dev, btd_adapter_get_storage_dir(dev->adapter), + dstaddr); + + gatt_client_init(dev); + gatt_server_init(dev, database); + + /* + * Remove the device from the connect_list and give the passive + * scanning another chance to be restarted in case there are + * other devices in the connect_list. + */ + adapter_connect_list_remove(dev->adapter, dev); + + return true; +} + +static void att_connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) +{ + struct btd_device *device = user_data; + DBusMessage *reply; + uint8_t io_cap; + int err = 0; + + g_io_channel_unref(device->att_io); + device->att_io = NULL; + + if (gerr) { + DBG("%s", gerr->message); + + if (g_error_matches(gerr, BT_IO_ERROR, ECONNABORTED)) + goto done; + + if (device_get_auto_connect(device)) { + DBG("Enabling automatic connections"); + adapter_connect_list_add(device->adapter, device); + } + + if (device->browse) + browse_request_complete(device->browse, + BROWSE_GATT, + device->bdaddr_type, + -ECONNABORTED); + + err = -ECONNABORTED; + goto done; + } + + if (!device_attach_att(device, io)) + goto done; + + if (!device->bonding) + goto done; + + if (device->bonding->agent) + io_cap = agent_get_io_capability(device->bonding->agent); + else + io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; + + err = adapter_create_bonding(device->adapter, &device->bdaddr, + device->bdaddr_type, io_cap); +done: + if (device->bonding && err < 0) { + reply = btd_error_failed(device->bonding->msg, strerror(-err)); + g_dbus_send_message(dbus_conn, reply); + bonding_request_cancel(device->bonding); + bonding_request_free(device->bonding); + } + + if (!err) + device_browse_gatt(device, NULL); + + if (device->connect) { + if (err < 0) + reply = btd_error_failed(device->connect, + strerror(-err)); + else + reply = dbus_message_new_method_return(device->connect); + + g_dbus_send_message(dbus_conn, reply); + dbus_message_unref(device->connect); + device->connect = NULL; + } +} + +int device_connect_le(struct btd_device *dev) +{ + struct btd_adapter *adapter = dev->adapter; + BtIOSecLevel sec_level; + GIOChannel *io; + GError *gerr = NULL; + char addr[18]; + + /* There is one connection attempt going on */ + if (dev->att_io) + return -EALREADY; + + ba2str(&dev->bdaddr, addr); + + DBG("Connection attempt to: %s", addr); + + if (dev->le_state.paired) + sec_level = BT_IO_SEC_MEDIUM; + else + sec_level = BT_IO_SEC_LOW; + + /* + * This connection will help us catch any PDUs that comes before + * pairing finishes + */ + io = bt_io_connect(att_connect_cb, dev, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(adapter), + BT_IO_OPT_SOURCE_TYPE, + btd_adapter_get_address_type(adapter), + BT_IO_OPT_DEST_BDADDR, &dev->bdaddr, + BT_IO_OPT_DEST_TYPE, dev->bdaddr_type, + BT_IO_OPT_CID, ATT_CID, + BT_IO_OPT_SEC_LEVEL, sec_level, + BT_IO_OPT_INVALID); + + if (io == NULL) { + if (dev->bonding) { + DBusMessage *reply = btd_error_failed( + dev->bonding->msg, gerr->message); + + g_dbus_send_message(dbus_conn, reply); + bonding_request_cancel(dev->bonding); + bonding_request_free(dev->bonding); + } + + error("ATT bt_io_connect(%s): %s", addr, gerr->message); + g_error_free(gerr); + return -EIO; + } + + /* Keep this, so we can cancel the connection */ + dev->att_io = io; + + return 0; +} + +static struct browse_req *browse_request_new(struct btd_device *device, + uint8_t type, + DBusMessage *msg) +{ + struct browse_req *req; + + if (device->browse) + return NULL; + + req = g_new0(struct browse_req, 1); + req->device = device; + req->type = type; + + device->browse = req; + + if (!msg) + return req; + + req->msg = dbus_message_ref(msg); + + /* + * Track the request owner to cancel it automatically if the owner + * exits + */ + req->listener_id = g_dbus_add_disconnect_watch(dbus_conn, + dbus_message_get_sender(msg), + browse_request_exit, + req, NULL); + + return req; +} + +static int device_browse_gatt(struct btd_device *device, DBusMessage *msg) +{ + struct btd_adapter *adapter = device->adapter; + struct browse_req *req; + + req = browse_request_new(device, BROWSE_GATT, msg); + if (!req) + return -EBUSY; + + if (device->client) { + /* + * If discovery has not yet completed, then wait for gatt-client + * to become ready. + */ + if (!bt_gatt_client_is_ready(device->client)) + return 0; + + /* + * Services have already been discovered, so signal this browse + * request as resolved. + */ + device_svc_resolved(device, BROWSE_GATT, device->bdaddr_type, + 0); + return 0; + } + + device->att_io = bt_io_connect(att_connect_cb, + device, NULL, NULL, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(adapter), + BT_IO_OPT_SOURCE_TYPE, + btd_adapter_get_address_type(adapter), + BT_IO_OPT_DEST_BDADDR, &device->bdaddr, + BT_IO_OPT_DEST_TYPE, device->bdaddr_type, + BT_IO_OPT_CID, ATT_CID, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + + if (device->att_io == NULL) { + browse_request_free(req); + return -EIO; + } + + return 0; +} + +static uint16_t get_sdp_flags(struct btd_device *device) +{ + uint16_t vid, pid; + + vid = btd_device_get_vendor(device); + pid = btd_device_get_product(device); + + /* Sony DualShock 4 is not respecting negotiated L2CAP MTU. This might + * results in SDP response being dropped by kernel. Workaround this by + * forcing SDP code to use bigger MTU while connecting. + */ + if (vid == 0x054c && pid == 0x05c4) + return SDP_LARGE_MTU; + + if (btd_adapter_ssp_enabled(device->adapter)) + return 0; + + /* if no EIR try matching Sony DualShock 4 with name and class */ + if (!strncmp(device->name, "Wireless Controller", MAX_NAME_LENGTH) && + device->class == 0x2508) + return SDP_LARGE_MTU; + + return 0; +} + +static int device_browse_sdp(struct btd_device *device, DBusMessage *msg) +{ + struct btd_adapter *adapter = device->adapter; + struct browse_req *req; + uuid_t uuid; + int err; + + req = browse_request_new(device, BROWSE_SDP, msg); + if (!req) + return -EBUSY; + + sdp_uuid16_create(&uuid, uuid_list[req->search_uuid++]); + + req->sdp_flags = get_sdp_flags(device); + + err = bt_search_service(btd_adapter_get_address(adapter), + &device->bdaddr, &uuid, browse_cb, req, NULL, + req->sdp_flags); + if (err < 0) { + browse_request_free(req); + return err; + } + + return err; +} + +int device_discover_services(struct btd_device *device) +{ + int err; + + if (device->bredr) + err = device_browse_sdp(device, NULL); + else + err = device_browse_gatt(device, NULL); + + if (err == 0 && device->discov_timer) { + g_source_remove(device->discov_timer); + device->discov_timer = 0; + } + + return err; +} + +struct btd_adapter *device_get_adapter(struct btd_device *device) +{ + if (!device) + return NULL; + + return device->adapter; +} + +const bdaddr_t *device_get_address(struct btd_device *device) +{ + return &device->bdaddr; +} +uint8_t device_get_le_address_type(struct btd_device *device) +{ + return device->bdaddr_type; +} + +const char *device_get_path(const struct btd_device *device) +{ + if (!device) + return NULL; + + return device->path; +} + +gboolean device_is_temporary(struct btd_device *device) +{ + return device->temporary; +} + +void btd_device_set_temporary(struct btd_device *device, bool temporary) +{ + if (!device) + return; + + if (device->temporary == temporary) + return; + + if (device_address_is_private(device)) + return; + + DBG("temporary %d", temporary); + + device->temporary = temporary; + + if (temporary) { + if (device->bredr) + adapter_whitelist_remove(device->adapter, device); + adapter_connect_list_remove(device->adapter, device); + return; + } + + if (device->bredr) + adapter_whitelist_add(device->adapter, device); + + store_device_info(device); + + /* attributes were not stored when resolved if device was temporary */ + if (device->bdaddr_type != BDADDR_BREDR && + device->le_state.svc_resolved && + g_slist_length(device->primaries) != 0) + store_services(device); +} + +void btd_device_set_trusted(struct btd_device *device, gboolean trusted) +{ + if (!device) + return; + + if (device->trusted == trusted) + return; + + DBG("trusted %d", trusted); + + device->trusted = trusted; + + store_device_info(device); + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Trusted"); +} + +void device_set_bonded(struct btd_device *device, uint8_t bdaddr_type) +{ + if (!device) + return; + + DBG(""); + + if (bdaddr_type == BDADDR_BREDR) + device->bredr_state.bonded = true; + else + device->le_state.bonded = true; + + btd_device_set_temporary(device, false); +} + +void device_set_legacy(struct btd_device *device, bool legacy) +{ + if (!device) + return; + + DBG("legacy %d", legacy); + + if (device->legacy == legacy) + return; + + device->legacy = legacy; + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "LegacyPairing"); +} + +void device_store_svc_chng_ccc(struct btd_device *device, uint8_t bdaddr_type, + uint16_t value) +{ + char filename[PATH_MAX]; + char device_addr[18]; + GKeyFile *key_file; + uint16_t old_value; + gsize length = 0; + char *str; + + ba2str(&device->bdaddr, device_addr); + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + btd_adapter_get_storage_dir(device->adapter), + device_addr); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + /* for bonded devices this is done on every connection so limit writes + * to storage if no change needed + */ + if (bdaddr_type == BDADDR_BREDR) { + old_value = g_key_file_get_integer(key_file, "ServiceChanged", + "CCC_BR/EDR", NULL); + if (old_value == value) + goto done; + + g_key_file_set_integer(key_file, "ServiceChanged", "CCC_BR/EDR", + value); + } else { + old_value = g_key_file_get_integer(key_file, "ServiceChanged", + "CCC_LE", NULL); + if (old_value == value) + goto done; + + g_key_file_set_integer(key_file, "ServiceChanged", "CCC_LE", + value); + } + + create_file(filename, S_IRUSR | S_IWUSR); + + str = g_key_file_to_data(key_file, &length, NULL); + g_file_set_contents(filename, str, length, NULL); + g_free(str); + +done: + g_key_file_free(key_file); +} +void device_load_svc_chng_ccc(struct btd_device *device, uint16_t *ccc_le, + uint16_t *ccc_bredr) +{ + char filename[PATH_MAX]; + char device_addr[18]; + GKeyFile *key_file; + + ba2str(&device->bdaddr, device_addr); + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/%s/info", + btd_adapter_get_storage_dir(device->adapter), + device_addr); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + /* + * If there is no "ServiceChanged" section we may be loading data from + * old version which did not persist Service Changed CCC values. Let's + * check if we are bonded and assume indications were enabled by peer + * in such case - it should have done this anyway. + */ + if (!g_key_file_has_group(key_file, "ServiceChanged")) { + if (ccc_le) + *ccc_le = device->le_state.bonded ? 0x0002 : 0x0000; + if (ccc_bredr) + *ccc_bredr = device->bredr_state.bonded ? + 0x0002 : 0x0000; + g_key_file_free(key_file); + return; + } + + if (ccc_le) + *ccc_le = g_key_file_get_integer(key_file, "ServiceChanged", + "CCC_LE", NULL); + + if (ccc_bredr) + *ccc_bredr = g_key_file_get_integer(key_file, "ServiceChanged", + "CCC_BR/EDR", NULL); + + g_key_file_free(key_file); +} + +void device_set_rssi_with_delta(struct btd_device *device, int8_t rssi, + int8_t delta_threshold) +{ + if (!device) + return; + + if (rssi == 0 || device->rssi == 0) { + if (device->rssi == rssi) + return; + + DBG("rssi %d", rssi); + + device->rssi = rssi; + } else { + int delta; + + if (device->rssi > rssi) + delta = device->rssi - rssi; + else + delta = rssi - device->rssi; + + /* only report changes of delta_threshold dBm or more */ + if (delta < delta_threshold) + return; + + DBG("rssi %d delta %d", rssi, delta); + + device->rssi = rssi; + } + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "RSSI"); +} + +void device_set_rssi(struct btd_device *device, int8_t rssi) +{ + device_set_rssi_with_delta(device, rssi, RSSI_THRESHOLD); +} + +void device_set_tx_power(struct btd_device *device, int8_t tx_power) +{ + if (!device) + return; + + if (device->tx_power == tx_power) + return; + + DBG("tx_power %d", tx_power); + + device->tx_power = tx_power; + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "TxPower"); +} + +void device_set_flags(struct btd_device *device, uint8_t flags) +{ + if (!device) + return; + + DBG("flags %d", flags); + + if (device->ad_flags[0] == flags) + return; + + device->ad_flags[0] = flags; + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "AdvertisingFlags"); +} + +bool device_is_connectable(struct btd_device *device) +{ + if (!device) + return false; + + if (device->bredr) + return true; + + /* Check if either Limited or General discoverable are set */ + return (device->ad_flags[0] & 0x03); +} + +static gboolean start_discovery(gpointer user_data) +{ + struct btd_device *device = user_data; + + if (device->bredr) + device_browse_sdp(device, NULL); + else + device_browse_gatt(device, NULL); + + device->discov_timer = 0; + + return FALSE; +} + +void device_set_paired(struct btd_device *dev, uint8_t bdaddr_type) +{ + struct bearer_state *state = get_state(dev, bdaddr_type); + + if (state->paired) + return; + + state->paired = true; + + /* If the other bearer state was already true we don't need to + * send any property signals. + */ + if (dev->bredr_state.paired == dev->le_state.paired) + return; + + if (!state->svc_resolved) { + dev->pending_paired = true; + return; + } + + g_dbus_emit_property_changed(dbus_conn, dev->path, + DEVICE_INTERFACE, "Paired"); +} + +void device_set_unpaired(struct btd_device *dev, uint8_t bdaddr_type) +{ + struct bearer_state *state = get_state(dev, bdaddr_type); + + if (!state->paired) + return; + + state->paired = false; + + /* + * If the other bearer state is still true we don't need to + * send any property signals or remove device. + */ + if (dev->bredr_state.paired != dev->le_state.paired) { + /* TODO disconnect only unpaired bearer */ + if (state->connected) + device_request_disconnect(dev, NULL); + + return; + } + + g_dbus_emit_property_changed(dbus_conn, dev->path, + DEVICE_INTERFACE, "Paired"); + + btd_device_set_temporary(dev, true); + + if (btd_device_is_connected(dev)) + device_request_disconnect(dev, NULL); + else + btd_adapter_remove_device(dev->adapter, dev); +} + +static void device_auth_req_free(struct btd_device *device) +{ + struct authentication_req *authr = device->authr; + + if (!authr) + return; + + if (authr->agent) + agent_unref(authr->agent); + + g_free(authr->pincode); + g_free(authr); + + device->authr = NULL; +} + +bool device_is_retrying(struct btd_device *device) +{ + struct bonding_req *bonding = device->bonding; + + return bonding && bonding->retry_timer > 0; +} + +void device_bonding_complete(struct btd_device *device, uint8_t bdaddr_type, + uint8_t status) +{ + struct bonding_req *bonding = device->bonding; + struct authentication_req *auth = device->authr; + struct bearer_state *state = get_state(device, bdaddr_type); + + DBG("bonding %p status 0x%02x", bonding, status); + + if (auth && auth->agent) + agent_cancel(auth->agent); + + if (status) { + device_cancel_authentication(device, TRUE); + device_bonding_failed(device, status); + return; + } + + device_auth_req_free(device); + + /* If we're already paired nothing more is needed */ + if (state->paired) + return; + + device_set_paired(device, bdaddr_type); + + /* If services are already resolved just reply to the pairing + * request + */ + if (state->svc_resolved && bonding) { + /* Attept to store services for this device failed because it + * was not paired. Now that we're paired retry. */ + store_gatt_db(device); + + g_dbus_send_reply(dbus_conn, bonding->msg, DBUS_TYPE_INVALID); + bonding_request_free(bonding); + return; + } + + /* If we were initiators start service discovery immediately. + * However if the other end was the initator wait a few seconds + * before SDP. This is due to potential IOP issues if the other + * end starts doing SDP at the same time as us */ + if (bonding) { + DBG("Proceeding with service discovery"); + /* If we are initiators remove any discovery timer and just + * start discovering services directly */ + if (device->discov_timer) { + g_source_remove(device->discov_timer); + device->discov_timer = 0; + } + + if (bdaddr_type == BDADDR_BREDR) + device_browse_sdp(device, bonding->msg); + else + device_browse_gatt(device, bonding->msg); + + bonding_request_free(bonding); + } else if (!state->svc_resolved) { + if (!device->browse && !device->discov_timer && + main_opts.reverse_discovery) { + /* If we are not initiators and there is no currently + * active discovery or discovery timer, set discovery + * timer */ + DBG("setting timer for reverse service discovery"); + device->discov_timer = g_timeout_add_seconds( + DISCOVERY_TIMER, + start_discovery, + device); + } + } +} + +static gboolean svc_idle_cb(gpointer user_data) +{ + struct svc_callback *cb = user_data; + struct btd_device *dev = cb->dev; + + dev->svc_callbacks = g_slist_remove(dev->svc_callbacks, cb); + + cb->func(cb->dev, 0, cb->user_data); + + g_free(cb); + + return FALSE; +} + +unsigned int device_wait_for_svc_complete(struct btd_device *dev, + device_svc_cb_t func, + void *user_data) +{ + /* This API is only used for BR/EDR (for now) */ + struct bearer_state *state = &dev->bredr_state; + static unsigned int id = 0; + struct svc_callback *cb; + + cb = g_new0(struct svc_callback, 1); + cb->func = func; + cb->user_data = user_data; + cb->dev = dev; + cb->id = ++id; + + dev->svc_callbacks = g_slist_prepend(dev->svc_callbacks, cb); + + if (state->svc_resolved || !main_opts.reverse_discovery) + cb->idle_id = g_idle_add(svc_idle_cb, cb); + else if (dev->discov_timer > 0) { + g_source_remove(dev->discov_timer); + dev->discov_timer = g_idle_add(start_discovery, dev); + } + + return cb->id; +} + +bool device_remove_svc_complete_callback(struct btd_device *dev, + unsigned int id) +{ + GSList *l; + + for (l = dev->svc_callbacks; l != NULL; l = g_slist_next(l)) { + struct svc_callback *cb = l->data; + + if (cb->id != id) + continue; + + if (cb->idle_id > 0) + g_source_remove(cb->idle_id); + + dev->svc_callbacks = g_slist_remove(dev->svc_callbacks, cb); + g_free(cb); + + return true; + } + + return false; +} + +gboolean device_is_bonding(struct btd_device *device, const char *sender) +{ + struct bonding_req *bonding = device->bonding; + + if (!device->bonding) + return FALSE; + + if (!sender) + return TRUE; + + return g_str_equal(sender, dbus_message_get_sender(bonding->msg)); +} + +static gboolean device_bonding_retry(gpointer data) +{ + struct btd_device *device = data; + struct btd_adapter *adapter = device_get_adapter(device); + struct bonding_req *bonding = device->bonding; + uint8_t io_cap; + int err; + + if (!bonding) + return FALSE; + + DBG("retrying bonding"); + bonding->retry_timer = 0; + + /* Restart the bonding timer to the begining of the pairing. If not + * pincode request/reply occurs during this retry, + * device_bonding_last_duration() will return a consistent value from + * this point. */ + device_bonding_restart_timer(device); + + if (bonding->agent) + io_cap = agent_get_io_capability(bonding->agent); + else + io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; + + err = adapter_bonding_attempt(adapter, &device->bdaddr, + device->bdaddr_type, io_cap); + if (err < 0) + device_bonding_complete(device, bonding->bdaddr_type, + bonding->status); + + return FALSE; +} + +int device_bonding_attempt_retry(struct btd_device *device) +{ + struct bonding_req *bonding = device->bonding; + + /* Ignore other failure events while retrying */ + if (device_is_retrying(device)) + return 0; + + if (!bonding) + return -EINVAL; + + /* Mark the end of a bonding attempt to compute the delta for the + * retry. */ + bonding_request_stop_timer(bonding); + + if (btd_adapter_pin_cb_iter_end(bonding->cb_iter)) + return -EINVAL; + + DBG("scheduling retry"); + bonding->retry_timer = g_timeout_add(3000, + device_bonding_retry, device); + return 0; +} + +void device_bonding_failed(struct btd_device *device, uint8_t status) +{ + struct bonding_req *bonding = device->bonding; + DBusMessage *reply; + + DBG("status %u", status); + + if (!bonding) + return; + + if (device->authr) + device_cancel_authentication(device, FALSE); + + reply = new_authentication_return(bonding->msg, status); + g_dbus_send_message(dbus_conn, reply); + + bonding_request_free(bonding); +} + +struct btd_adapter_pin_cb_iter *device_bonding_iter(struct btd_device *device) +{ + if (device->bonding == NULL) + return NULL; + + return device->bonding->cb_iter; +} + +static void pincode_cb(struct agent *agent, DBusError *err, const char *pin, + void *data) +{ + struct authentication_req *auth = data; + struct btd_device *device = auth->device; + + /* No need to reply anything if the authentication already failed */ + if (auth->agent == NULL) + return; + + btd_adapter_pincode_reply(device->adapter, &device->bdaddr, + pin, pin ? strlen(pin) : 0); + + agent_unref(device->authr->agent); + device->authr->agent = NULL; +} + +static void confirm_cb(struct agent *agent, DBusError *err, void *data) +{ + struct authentication_req *auth = data; + struct btd_device *device = auth->device; + + /* No need to reply anything if the authentication already failed */ + if (auth->agent == NULL) + return; + + btd_adapter_confirm_reply(device->adapter, &device->bdaddr, + auth->addr_type, + err ? FALSE : TRUE); + + agent_unref(device->authr->agent); + device->authr->agent = NULL; +} + +static void passkey_cb(struct agent *agent, DBusError *err, + uint32_t passkey, void *data) +{ + struct authentication_req *auth = data; + struct btd_device *device = auth->device; + + /* No need to reply anything if the authentication already failed */ + if (auth->agent == NULL) + return; + + if (err) + passkey = INVALID_PASSKEY; + + btd_adapter_passkey_reply(device->adapter, &device->bdaddr, + auth->addr_type, passkey); + + agent_unref(device->authr->agent); + device->authr->agent = NULL; +} + +static void display_pincode_cb(struct agent *agent, DBusError *err, void *data) +{ + struct authentication_req *auth = data; + struct btd_device *device = auth->device; + + pincode_cb(agent, err, auth->pincode, auth); + + g_free(device->authr->pincode); + device->authr->pincode = NULL; +} + +static struct authentication_req *new_auth(struct btd_device *device, + uint8_t addr_type, + auth_type_t type, + gboolean secure) +{ + struct authentication_req *auth; + struct agent *agent; + char addr[18]; + + ba2str(&device->bdaddr, addr); + DBG("Requesting agent authentication for %s", addr); + + if (device->authr) { + error("Authentication already requested for %s", addr); + return NULL; + } + + if (device->bonding && device->bonding->agent) + agent = agent_ref(device->bonding->agent); + else + agent = agent_get(NULL); + + if (!agent) { + error("No agent available for request type %d", type); + return NULL; + } + + auth = g_new0(struct authentication_req, 1); + auth->agent = agent; + auth->device = device; + auth->type = type; + auth->addr_type = addr_type; + auth->secure = secure; + device->authr = auth; + + return auth; +} + +int device_request_pincode(struct btd_device *device, gboolean secure) +{ + struct authentication_req *auth; + int err; + + auth = new_auth(device, BDADDR_BREDR, AUTH_TYPE_PINCODE, secure); + if (!auth) + return -EPERM; + + err = agent_request_pincode(auth->agent, device, pincode_cb, secure, + auth, NULL); + if (err < 0) { + error("Failed requesting authentication"); + device_auth_req_free(device); + } + + return err; +} + +int device_request_passkey(struct btd_device *device, uint8_t type) +{ + struct authentication_req *auth; + int err; + + auth = new_auth(device, type, AUTH_TYPE_PASSKEY, FALSE); + if (!auth) + return -EPERM; + + err = agent_request_passkey(auth->agent, device, passkey_cb, auth, + NULL); + if (err < 0) { + error("Failed requesting authentication"); + device_auth_req_free(device); + } + + return err; +} + +int device_confirm_passkey(struct btd_device *device, uint8_t type, + int32_t passkey, uint8_t confirm_hint) +{ + struct authentication_req *auth; + int err; + + auth = new_auth(device, type, AUTH_TYPE_CONFIRM, FALSE); + if (!auth) + return -EPERM; + + auth->passkey = passkey; + + if (confirm_hint) + err = agent_request_authorization(auth->agent, device, + confirm_cb, auth, NULL); + else + err = agent_request_confirmation(auth->agent, device, passkey, + confirm_cb, auth, NULL); + + if (err < 0) { + error("Failed requesting authentication"); + device_auth_req_free(device); + } + + return err; +} + +int device_notify_passkey(struct btd_device *device, uint8_t type, + uint32_t passkey, uint8_t entered) +{ + struct authentication_req *auth; + int err; + + if (device->authr) { + auth = device->authr; + if (auth->type != AUTH_TYPE_NOTIFY_PASSKEY) + return -EPERM; + } else { + auth = new_auth(device, type, AUTH_TYPE_NOTIFY_PASSKEY, FALSE); + if (!auth) + return -EPERM; + } + + err = agent_display_passkey(auth->agent, device, passkey, entered); + if (err < 0) { + error("Failed requesting authentication"); + device_auth_req_free(device); + } + + return err; +} + +int device_notify_pincode(struct btd_device *device, gboolean secure, + const char *pincode) +{ + struct authentication_req *auth; + int err; + + auth = new_auth(device, BDADDR_BREDR, AUTH_TYPE_NOTIFY_PINCODE, secure); + if (!auth) + return -EPERM; + + auth->pincode = g_strdup(pincode); + + err = agent_display_pincode(auth->agent, device, pincode, + display_pincode_cb, auth, NULL); + if (err < 0) { + error("Failed requesting authentication"); + device_auth_req_free(device); + } + + return err; +} + +static void cancel_authentication(struct authentication_req *auth) +{ + struct agent *agent; + DBusError err; + + if (!auth || !auth->agent) + return; + + agent = auth->agent; + auth->agent = NULL; + + dbus_error_init(&err); + dbus_set_error_const(&err, ERROR_INTERFACE ".Canceled", NULL); + + switch (auth->type) { + case AUTH_TYPE_PINCODE: + pincode_cb(agent, &err, NULL, auth); + break; + case AUTH_TYPE_CONFIRM: + confirm_cb(agent, &err, auth); + break; + case AUTH_TYPE_PASSKEY: + passkey_cb(agent, &err, 0, auth); + break; + case AUTH_TYPE_NOTIFY_PASSKEY: + /* User Notify doesn't require any reply */ + break; + case AUTH_TYPE_NOTIFY_PINCODE: + pincode_cb(agent, &err, NULL, auth); + break; + } + + dbus_error_free(&err); +} + +void device_cancel_authentication(struct btd_device *device, gboolean aborted) +{ + struct authentication_req *auth = device->authr; + char addr[18]; + + if (!auth) + return; + + ba2str(&device->bdaddr, addr); + DBG("Canceling authentication request for %s", addr); + + if (auth->agent) + agent_cancel(auth->agent); + + if (!aborted) + cancel_authentication(auth); + + device_auth_req_free(device); +} + +gboolean device_is_authenticating(struct btd_device *device) +{ + return (device->authr != NULL); +} + +struct gatt_primary *btd_device_get_primary(struct btd_device *device, + const char *uuid) +{ + GSList *match; + + match = g_slist_find_custom(device->primaries, uuid, bt_uuid_strcmp); + if (match) + return match->data; + + return NULL; +} + +GSList *btd_device_get_primaries(struct btd_device *device) +{ + return device->primaries; +} + +struct gatt_db *btd_device_get_gatt_db(struct btd_device *device) +{ + if (!device) + return NULL; + + return device->db; +} + +struct bt_gatt_client *btd_device_get_gatt_client(struct btd_device *device) +{ + if (!device) + return NULL; + + return device->client; +} + +void *btd_device_get_attrib(struct btd_device *device) +{ + if (!device) + return NULL; + + return device->attrib; +} + +struct bt_gatt_server *btd_device_get_gatt_server(struct btd_device *device) +{ + if (!device) + return NULL; + + return device->server; +} + +void btd_device_gatt_set_service_changed(struct btd_device *device, + uint16_t start, uint16_t end) +{ + /* + * TODO: Remove this function and handle service changed via + * gatt-client. + */ +} + +void btd_device_add_uuid(struct btd_device *device, const char *uuid) +{ + GSList *uuid_list; + char *new_uuid; + + if (g_slist_find_custom(device->uuids, uuid, bt_uuid_strcmp)) + return; + + new_uuid = g_strdup(uuid); + uuid_list = g_slist_append(NULL, new_uuid); + + device_probe_profiles(device, uuid_list); + + g_free(new_uuid); + g_slist_free(uuid_list); + + store_device_info(device); + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "UUIDs"); +} + +static sdp_list_t *read_device_records(struct btd_device *device) +{ + char local[18], peer[18]; + char filename[PATH_MAX]; + GKeyFile *key_file; + char **keys, **handle; + char *str; + sdp_list_t *recs = NULL; + sdp_record_t *rec; + + ba2str(btd_adapter_get_address(device->adapter), local); + ba2str(&device->bdaddr, peer); + + snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", local, peer); + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + keys = g_key_file_get_keys(key_file, "ServiceRecords", NULL, NULL); + + for (handle = keys; handle && *handle; handle++) { + str = g_key_file_get_string(key_file, "ServiceRecords", + *handle, NULL); + if (!str) + continue; + + rec = record_from_string(str); + recs = sdp_list_append(recs, rec); + g_free(str); + } + + g_strfreev(keys); + g_key_file_free(key_file); + + return recs; +} + +void btd_device_set_record(struct btd_device *device, const char *uuid, + const char *record) +{ + /* This API is only used for BR/EDR */ + struct bearer_state *state = &device->bredr_state; + struct browse_req *req; + sdp_list_t *recs = NULL; + sdp_record_t *rec; + + if (!record) + return; + + req = browse_request_new(device, BROWSE_SDP, NULL); + if (!req) + return; + + rec = record_from_string(record); + recs = sdp_list_append(recs, rec); + update_bredr_services(req, recs); + sdp_list_free(recs, NULL); + + device->svc_refreshed = true; + state->svc_resolved = true; + + device_probe_profiles(device, req->profiles_added); + + /* Propagate services changes */ + g_dbus_emit_property_changed(dbus_conn, req->device->path, + DEVICE_INTERFACE, "UUIDs"); + + device_svc_resolved(device, BROWSE_SDP, device->bdaddr_type, 0); +} + +const sdp_record_t *btd_device_get_record(struct btd_device *device, + const char *uuid) +{ + /* Load records from storage if there is nothing in cache */ + if (!device->tmp_records) { + device->tmp_records = read_device_records(device); + if (!device->tmp_records) + return NULL; + } + + return find_record_in_list(device->tmp_records, uuid); +} + +struct btd_device *btd_device_ref(struct btd_device *device) +{ + __sync_fetch_and_add(&device->ref_count, 1); + + return device; +} + +void btd_device_unref(struct btd_device *device) +{ + if (__sync_sub_and_fetch(&device->ref_count, 1)) + return; + + if (!device->path) { + error("freeing device without an object path"); + return; + } + + DBG("Freeing device %s", device->path); + + g_dbus_unregister_interface(dbus_conn, device->path, DEVICE_INTERFACE); +} + +int device_get_appearance(struct btd_device *device, uint16_t *value) +{ + if (device->appearance == 0) + return -1; + + if (value) + *value = device->appearance; + + return 0; +} + +void device_set_appearance(struct btd_device *device, uint16_t value) +{ + const char *icon = gap_appearance_to_icon(value); + + if (device->appearance == value) + return; + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Appearance"); + + if (icon) + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Icon"); + + device->appearance = value; + store_device_info(device); +} + +void btd_device_set_pnpid(struct btd_device *device, uint16_t source, + uint16_t vendor, uint16_t product, uint16_t version) +{ + if (device->vendor_src == source && device->version == version && + device->vendor == vendor && device->product == product) + return; + + device->vendor_src = source; + device->vendor = vendor; + device->product = product; + device->version = version; + + free(device->modalias); + device->modalias = bt_modalias(source, vendor, product, version); + + g_dbus_emit_property_changed(dbus_conn, device->path, + DEVICE_INTERFACE, "Modalias"); + + store_device_info(device); +} + +static void service_state_changed(struct btd_service *service, + btd_service_state_t old_state, + btd_service_state_t new_state, + void *user_data) +{ + struct btd_profile *profile = btd_service_get_profile(service); + struct btd_device *device = btd_service_get_device(service); + int err = btd_service_get_error(service); + + if (new_state == BTD_SERVICE_STATE_CONNECTING || + new_state == BTD_SERVICE_STATE_DISCONNECTING) + return; + + if (old_state == BTD_SERVICE_STATE_CONNECTING) + device_profile_connected(device, profile, err); + else if (old_state == BTD_SERVICE_STATE_DISCONNECTING) + device_profile_disconnected(device, profile, err); +} + +struct btd_service *btd_device_get_service(struct btd_device *dev, + const char *remote_uuid) +{ + GSList *l; + + for (l = dev->services; l != NULL; l = g_slist_next(l)) { + struct btd_service *service = l->data; + struct btd_profile *p = btd_service_get_profile(service); + + if (g_str_equal(p->remote_uuid, remote_uuid)) + return service; + } + + return NULL; +} + +void btd_device_init(void) +{ + dbus_conn = btd_get_dbus_connection(); + service_state_cb_id = btd_service_add_state_cb( + service_state_changed, NULL); +} + +void btd_device_cleanup(void) +{ + btd_service_remove_state_cb(service_state_cb_id); +} diff --git a/src/device.h b/src/device.h new file mode 100644 index 0000000..06b1004 --- /dev/null +++ b/src/device.h @@ -0,0 +1,180 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define DEVICE_INTERFACE "org.bluez.Device1" + +struct btd_device; + +struct btd_device *device_create(struct btd_adapter *adapter, + const bdaddr_t *address, uint8_t bdaddr_type); +struct btd_device *device_create_from_storage(struct btd_adapter *adapter, + const char *address, GKeyFile *key_file); +char *btd_device_get_storage_path(struct btd_device *device, + const char *filename); + +void btd_device_device_set_name(struct btd_device *device, const char *name); +void device_store_cached_name(struct btd_device *dev, const char *name); +void device_get_name(struct btd_device *device, char *name, size_t len); +bool device_name_known(struct btd_device *device); +void device_set_class(struct btd_device *device, uint32_t class); +void device_update_addr(struct btd_device *device, const bdaddr_t *bdaddr, + uint8_t bdaddr_type); +void device_set_bredr_support(struct btd_device *device); +void device_set_le_support(struct btd_device *device, uint8_t bdaddr_type); +void device_update_last_seen(struct btd_device *device, uint8_t bdaddr_type); +void device_merge_duplicate(struct btd_device *dev, struct btd_device *dup); +uint32_t btd_device_get_class(struct btd_device *device); +uint16_t btd_device_get_vendor(struct btd_device *device); +uint16_t btd_device_get_vendor_src(struct btd_device *device); +uint16_t btd_device_get_product(struct btd_device *device); +uint16_t btd_device_get_version(struct btd_device *device); +void device_remove(struct btd_device *device, gboolean remove_stored); +int device_address_cmp(gconstpointer a, gconstpointer b); +int device_bdaddr_cmp(gconstpointer a, gconstpointer b); + +/* Struct used by device_addr_type_cmp() */ +struct device_addr_type { + bdaddr_t bdaddr; + uint8_t bdaddr_type; +}; + +int device_addr_type_cmp(gconstpointer a, gconstpointer b); +GSList *btd_device_get_uuids(struct btd_device *device); +void device_probe_profiles(struct btd_device *device, GSList *profiles); + +void btd_device_set_record(struct btd_device *device, const char *uuid, + const char *record); +const sdp_record_t *btd_device_get_record(struct btd_device *device, + const char *uuid); +struct gatt_primary *btd_device_get_primary(struct btd_device *device, + const char *uuid); +GSList *btd_device_get_primaries(struct btd_device *device); +struct gatt_db *btd_device_get_gatt_db(struct btd_device *device); +struct bt_gatt_client *btd_device_get_gatt_client(struct btd_device *device); +struct bt_gatt_server *btd_device_get_gatt_server(struct btd_device *device); +void *btd_device_get_attrib(struct btd_device *device); +void btd_device_gatt_set_service_changed(struct btd_device *device, + uint16_t start, uint16_t end); +bool device_attach_att(struct btd_device *dev, GIOChannel *io); +void btd_device_add_uuid(struct btd_device *device, const char *uuid); +void device_add_eir_uuids(struct btd_device *dev, GSList *uuids); +void device_set_manufacturer_data(struct btd_device *dev, GSList *list, + bool duplicate); +void device_set_service_data(struct btd_device *dev, GSList *list, + bool duplicate); +void device_set_data(struct btd_device *dev, GSList *list, + bool duplicate); +void device_probe_profile(gpointer a, gpointer b); +void device_remove_profile(gpointer a, gpointer b); +struct btd_adapter *device_get_adapter(struct btd_device *device); +const bdaddr_t *device_get_address(struct btd_device *device); +uint8_t device_get_le_address_type(struct btd_device *device); +const char *device_get_path(const struct btd_device *device); +gboolean device_is_temporary(struct btd_device *device); +bool device_is_connectable(struct btd_device *device); +bool device_is_paired(struct btd_device *device, uint8_t bdaddr_type); +bool device_is_bonded(struct btd_device *device, uint8_t bdaddr_type); +gboolean device_is_trusted(struct btd_device *device); +void device_set_paired(struct btd_device *dev, uint8_t bdaddr_type); +void device_set_unpaired(struct btd_device *dev, uint8_t bdaddr_type); +void btd_device_set_temporary(struct btd_device *device, bool temporary); +void btd_device_set_trusted(struct btd_device *device, gboolean trusted); +void device_set_bonded(struct btd_device *device, uint8_t bdaddr_type); +void device_set_legacy(struct btd_device *device, bool legacy); +void device_set_rssi_with_delta(struct btd_device *device, int8_t rssi, + int8_t delta_threshold); +void device_set_rssi(struct btd_device *device, int8_t rssi); +void device_set_tx_power(struct btd_device *device, int8_t tx_power); +void device_set_flags(struct btd_device *device, uint8_t flags); +bool btd_device_is_connected(struct btd_device *dev); +uint8_t btd_device_get_bdaddr_type(struct btd_device *dev); +bool device_is_retrying(struct btd_device *device); +void device_bonding_complete(struct btd_device *device, uint8_t bdaddr_type, + uint8_t status); +gboolean device_is_bonding(struct btd_device *device, const char *sender); +void device_bonding_attempt_failed(struct btd_device *device, uint8_t status); +void device_bonding_failed(struct btd_device *device, uint8_t status); +struct btd_adapter_pin_cb_iter *device_bonding_iter(struct btd_device *device); +int device_bonding_attempt_retry(struct btd_device *device); +long device_bonding_last_duration(struct btd_device *device); +void device_bonding_restart_timer(struct btd_device *device); +int device_request_pincode(struct btd_device *device, gboolean secure); +int device_request_passkey(struct btd_device *device, uint8_t type); +int device_confirm_passkey(struct btd_device *device, uint8_t type, + int32_t passkey, uint8_t confirm_hint); +int device_notify_passkey(struct btd_device *device, uint8_t type, + uint32_t passkey, uint8_t entered); +int device_notify_pincode(struct btd_device *device, gboolean secure, + const char *pincode); +void device_cancel_authentication(struct btd_device *device, gboolean aborted); +gboolean device_is_authenticating(struct btd_device *device); +void device_add_connection(struct btd_device *dev, uint8_t bdaddr_type); +void device_remove_connection(struct btd_device *device, uint8_t bdaddr_type); +void device_request_disconnect(struct btd_device *device, DBusMessage *msg); +bool device_is_disconnecting(struct btd_device *device); +void device_set_ltk_enc_size(struct btd_device *device, uint8_t enc_size); + +void device_store_svc_chng_ccc(struct btd_device *device, uint8_t bdaddr_type, + uint16_t value); +void device_load_svc_chng_ccc(struct btd_device *device, uint16_t *ccc_le, + uint16_t *ccc_bredr); + +typedef void (*disconnect_watch) (struct btd_device *device, gboolean removal, + void *user_data); + +guint device_add_disconnect_watch(struct btd_device *device, + disconnect_watch watch, void *user_data, + GDestroyNotify destroy); +void device_remove_disconnect_watch(struct btd_device *device, guint id); +int device_get_appearance(struct btd_device *device, uint16_t *value); +void device_set_appearance(struct btd_device *device, uint16_t value); + +struct btd_device *btd_device_ref(struct btd_device *device); +void btd_device_unref(struct btd_device *device); + +int device_block(struct btd_device *device, gboolean update_only); +int device_unblock(struct btd_device *device, gboolean silent, + gboolean update_only); +void btd_device_set_pnpid(struct btd_device *device, uint16_t source, + uint16_t vendor, uint16_t product, uint16_t version); + +int device_connect_le(struct btd_device *dev); + +typedef void (*device_svc_cb_t) (struct btd_device *dev, int err, + void *user_data); + +unsigned int device_wait_for_svc_complete(struct btd_device *dev, + device_svc_cb_t func, + void *user_data); +bool device_remove_svc_complete_callback(struct btd_device *dev, + unsigned int id); + +struct btd_service *btd_device_get_service(struct btd_device *dev, + const char *remote_uuid); + +int device_discover_services(struct btd_device *device); +int btd_device_connect_services(struct btd_device *dev, GSList *services); + +void btd_device_init(void); +void btd_device_cleanup(void); diff --git a/src/eir.c b/src/eir.c new file mode 100644 index 0000000..fa52a2b --- /dev/null +++ b/src/eir.c @@ -0,0 +1,614 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/sdp.h" + +#include "src/shared/util.h" +#include "uuid-helper.h" +#include "eir.h" + +#define EIR_OOB_MIN (2 + 6) + +static void sd_free(void *data) +{ + struct eir_sd *sd = data; + + free(sd->uuid); + g_free(sd); +} + +static void data_free(void *data) +{ + struct eir_ad *ad = data; + + g_free(ad->data); + g_free(ad); +} + +void eir_data_free(struct eir_data *eir) +{ + g_slist_free_full(eir->services, free); + eir->services = NULL; + g_free(eir->name); + eir->name = NULL; + g_free(eir->hash); + eir->hash = NULL; + g_free(eir->randomizer); + eir->randomizer = NULL; + g_slist_free_full(eir->msd_list, g_free); + eir->msd_list = NULL; + g_slist_free_full(eir->sd_list, sd_free); + eir->sd_list = NULL; + g_slist_free_full(eir->data_list, data_free); + eir->data_list = NULL; +} + +static void eir_parse_uuid16(struct eir_data *eir, const void *data, + uint8_t len) +{ + const uint16_t *uuid16 = data; + uuid_t service; + char *uuid_str; + unsigned int i; + + service.type = SDP_UUID16; + for (i = 0; i < len / 2; i++, uuid16++) { + service.value.uuid16 = get_le16(uuid16); + + uuid_str = bt_uuid2string(&service); + if (!uuid_str) + continue; + eir->services = g_slist_append(eir->services, uuid_str); + } +} + +static void eir_parse_uuid32(struct eir_data *eir, const void *data, + uint8_t len) +{ + const uint32_t *uuid32 = data; + uuid_t service; + char *uuid_str; + unsigned int i; + + service.type = SDP_UUID32; + for (i = 0; i < len / 4; i++, uuid32++) { + service.value.uuid32 = get_le32(uuid32); + + uuid_str = bt_uuid2string(&service); + if (!uuid_str) + continue; + eir->services = g_slist_append(eir->services, uuid_str); + } +} + +static void eir_parse_uuid128(struct eir_data *eir, const uint8_t *data, + uint8_t len) +{ + const uint8_t *uuid_ptr = data; + uuid_t service; + char *uuid_str; + unsigned int i; + int k; + + service.type = SDP_UUID128; + for (i = 0; i < len / 16; i++) { + for (k = 0; k < 16; k++) + service.value.uuid128.data[k] = uuid_ptr[16 - k - 1]; + uuid_str = bt_uuid2string(&service); + if (!uuid_str) + continue; + eir->services = g_slist_append(eir->services, uuid_str); + uuid_ptr += 16; + } +} + +static char *name2utf8(const uint8_t *name, uint8_t len) +{ + char utf8_name[HCI_MAX_NAME_LENGTH + 2]; + int i; + + if (g_utf8_validate((const char *) name, len, NULL)) + return g_strndup((char *) name, len); + + memset(utf8_name, 0, sizeof(utf8_name)); + strncpy(utf8_name, (char *) name, len); + + /* Assume ASCII, and replace all non-ASCII with spaces */ + for (i = 0; utf8_name[i] != '\0'; i++) { + if (!isascii(utf8_name[i])) + utf8_name[i] = ' '; + } + + /* Remove leading and trailing whitespace characters */ + g_strstrip(utf8_name); + + return g_strdup(utf8_name); +} + +static void eir_parse_msd(struct eir_data *eir, const uint8_t *data, + uint8_t len) +{ + struct eir_msd *msd; + + if (len < 2 || len > 2 + sizeof(msd->data)) + return; + + msd = g_malloc(sizeof(*msd)); + msd->company = get_le16(data); + msd->data_len = len - 2; + memcpy(&msd->data, data + 2, msd->data_len); + + eir->msd_list = g_slist_append(eir->msd_list, msd); +} + +static void eir_parse_sd(struct eir_data *eir, uuid_t *service, + const uint8_t *data, uint8_t len) +{ + struct eir_sd *sd; + char *uuid; + + uuid = bt_uuid2string(service); + if (!uuid) + return; + + sd = g_malloc(sizeof(*sd)); + sd->uuid = uuid; + sd->data_len = len; + memcpy(&sd->data, data, sd->data_len); + + eir->sd_list = g_slist_append(eir->sd_list, sd); +} + +static void eir_parse_uuid16_data(struct eir_data *eir, const uint8_t *data, + uint8_t len) +{ + uuid_t service; + + if (len < 2 || len > EIR_SD_MAX_LEN) + return; + + service.type = SDP_UUID16; + service.value.uuid16 = get_le16(data); + eir_parse_sd(eir, &service, data + 2, len - 2); +} + +static void eir_parse_uuid32_data(struct eir_data *eir, const uint8_t *data, + uint8_t len) +{ + uuid_t service; + + if (len < 4 || len > EIR_SD_MAX_LEN) + return; + + service.type = SDP_UUID32; + service.value.uuid32 = get_le32(data); + eir_parse_sd(eir, &service, data + 4, len - 4); +} + +static void eir_parse_uuid128_data(struct eir_data *eir, const uint8_t *data, + uint8_t len) +{ + uuid_t service; + int k; + + if (len < 16 || len > EIR_SD_MAX_LEN) + return; + + service.type = SDP_UUID128; + + for (k = 0; k < 16; k++) + service.value.uuid128.data[k] = data[16 - k - 1]; + + eir_parse_sd(eir, &service, data + 16, len - 16); +} + +static void eir_parse_data(struct eir_data *eir, uint8_t type, + const uint8_t *data, uint8_t len) +{ + struct eir_ad *ad; + + ad = g_malloc(sizeof(*ad)); + ad->type = type; + ad->len = len; + ad->data = g_malloc(len); + memcpy(ad->data, data, len); + + eir->data_list = g_slist_append(eir->data_list, ad); +} + +void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len) +{ + uint16_t len = 0; + + eir->flags = 0; + eir->tx_power = 127; + + /* No EIR data to parse */ + if (eir_data == NULL) + return; + + while (len < eir_len - 1) { + uint8_t field_len = eir_data[0]; + const uint8_t *data; + uint8_t data_len; + + /* Check for the end of EIR */ + if (field_len == 0) + break; + + len += field_len + 1; + + /* Do not continue EIR Data parsing if got incorrect length */ + if (len > eir_len) + break; + + data = &eir_data[2]; + data_len = field_len - 1; + + switch (eir_data[1]) { + case EIR_UUID16_SOME: + case EIR_UUID16_ALL: + eir_parse_uuid16(eir, data, data_len); + break; + + case EIR_UUID32_SOME: + case EIR_UUID32_ALL: + eir_parse_uuid32(eir, data, data_len); + break; + + case EIR_UUID128_SOME: + case EIR_UUID128_ALL: + eir_parse_uuid128(eir, data, data_len); + break; + + case EIR_FLAGS: + if (data_len > 0) + eir->flags = *data; + break; + + case EIR_NAME_SHORT: + case EIR_NAME_COMPLETE: + /* Some vendors put a NUL byte terminator into + * the name */ + while (data_len > 0 && data[data_len - 1] == '\0') + data_len--; + + g_free(eir->name); + + eir->name = name2utf8(data, data_len); + eir->name_complete = eir_data[1] == EIR_NAME_COMPLETE; + break; + + case EIR_TX_POWER: + if (data_len < 1) + break; + eir->tx_power = (int8_t) data[0]; + break; + + case EIR_CLASS_OF_DEV: + if (data_len < 3) + break; + eir->class = data[0] | (data[1] << 8) | + (data[2] << 16); + break; + + case EIR_GAP_APPEARANCE: + if (data_len < 2) + break; + eir->appearance = get_le16(data); + break; + + case EIR_SSP_HASH: + if (data_len < 16) + break; + eir->hash = g_memdup(data, 16); + break; + + case EIR_SSP_RANDOMIZER: + if (data_len < 16) + break; + eir->randomizer = g_memdup(data, 16); + break; + + case EIR_DEVICE_ID: + if (data_len < 8) + break; + + eir->did_source = data[0] | (data[1] << 8); + eir->did_vendor = data[2] | (data[3] << 8); + eir->did_product = data[4] | (data[5] << 8); + eir->did_version = data[6] | (data[7] << 8); + break; + + case EIR_SVC_DATA16: + eir_parse_uuid16_data(eir, data, data_len); + break; + + case EIR_SVC_DATA32: + eir_parse_uuid32_data(eir, data, data_len); + break; + + case EIR_SVC_DATA128: + eir_parse_uuid128_data(eir, data, data_len); + break; + + case EIR_MANUFACTURER_DATA: + eir_parse_msd(eir, data, data_len); + break; + + default: + eir_parse_data(eir, eir_data[1], data, data_len); + break; + } + + eir_data += field_len + 1; + } +} + +int eir_parse_oob(struct eir_data *eir, uint8_t *eir_data, uint16_t eir_len) +{ + + if (eir_len < EIR_OOB_MIN) + return -1; + + if (eir_len != get_le16(eir_data)) + return -1; + + eir_data += sizeof(uint16_t); + eir_len -= sizeof(uint16_t); + + memcpy(&eir->addr, eir_data, sizeof(bdaddr_t)); + eir_data += sizeof(bdaddr_t); + eir_len -= sizeof(bdaddr_t); + + /* optional OOB EIR data */ + if (eir_len > 0) + eir_parse(eir, eir_data, eir_len); + + return 0; +} + +#define SIZEOF_UUID128 16 + +static void eir_generate_uuid128(sdp_list_t *list, uint8_t *ptr, + uint16_t *eir_len) +{ + int i, k, uuid_count = 0; + uint16_t len = *eir_len; + uint8_t *uuid128; + bool truncated = false; + + /* Store UUIDs in place, skip 2 bytes to write type and length later */ + uuid128 = ptr + 2; + + for (; list; list = list->next) { + sdp_record_t *rec = list->data; + uuid_t *uuid = &rec->svclass; + uint8_t *uuid128_data = uuid->value.uuid128.data; + + if (uuid->type != SDP_UUID128) + continue; + + /* Stop if not enough space to put next UUID128 */ + if ((len + 2 + SIZEOF_UUID128) > HCI_MAX_EIR_LENGTH) { + truncated = true; + break; + } + + /* Check for duplicates, EIR data is Little Endian */ + for (i = 0; i < uuid_count; i++) { + for (k = 0; k < SIZEOF_UUID128; k++) { + if (uuid128[i * SIZEOF_UUID128 + k] != + uuid128_data[SIZEOF_UUID128 - 1 - k]) + break; + } + if (k == SIZEOF_UUID128) + break; + } + + if (i < uuid_count) + continue; + + /* EIR data is Little Endian */ + for (k = 0; k < SIZEOF_UUID128; k++) + uuid128[uuid_count * SIZEOF_UUID128 + k] = + uuid128_data[SIZEOF_UUID128 - 1 - k]; + + len += SIZEOF_UUID128; + uuid_count++; + } + + if (uuid_count > 0 || truncated) { + /* EIR Data length */ + ptr[0] = (uuid_count * SIZEOF_UUID128) + 1; + /* EIR Data type */ + ptr[1] = truncated ? EIR_UUID128_SOME : EIR_UUID128_ALL; + len += 2; + *eir_len = len; + } +} + +int eir_create_oob(const bdaddr_t *addr, const char *name, uint32_t cod, + const uint8_t *hash, const uint8_t *randomizer, + uint16_t did_vendor, uint16_t did_product, + uint16_t did_version, uint16_t did_source, + sdp_list_t *uuids, uint8_t *data) +{ + sdp_list_t *l; + uint8_t *ptr = data; + uint16_t eir_optional_len = 0; + uint16_t eir_total_len; + uint16_t uuid16[HCI_MAX_EIR_LENGTH / 2]; + int i, uuid_count = 0; + bool truncated = false; + size_t name_len; + + eir_total_len = sizeof(uint16_t) + sizeof(bdaddr_t); + ptr += sizeof(uint16_t); + + memcpy(ptr, addr, sizeof(bdaddr_t)); + ptr += sizeof(bdaddr_t); + + if (cod > 0) { + uint8_t class[3]; + + class[0] = (uint8_t) cod; + class[1] = (uint8_t) (cod >> 8); + class[2] = (uint8_t) (cod >> 16); + + *ptr++ = 4; + *ptr++ = EIR_CLASS_OF_DEV; + + memcpy(ptr, class, sizeof(class)); + ptr += sizeof(class); + + eir_optional_len += sizeof(class) + 2; + } + + if (hash) { + *ptr++ = 17; + *ptr++ = EIR_SSP_HASH; + + memcpy(ptr, hash, 16); + ptr += 16; + + eir_optional_len += 16 + 2; + } + + if (randomizer) { + *ptr++ = 17; + *ptr++ = EIR_SSP_RANDOMIZER; + + memcpy(ptr, randomizer, 16); + ptr += 16; + + eir_optional_len += 16 + 2; + } + + name_len = strlen(name); + + if (name_len > 0) { + /* EIR Data type */ + if (name_len > 48) { + name_len = 48; + ptr[1] = EIR_NAME_SHORT; + } else + ptr[1] = EIR_NAME_COMPLETE; + + /* EIR Data length */ + ptr[0] = name_len + 1; + + memcpy(ptr + 2, name, name_len); + + eir_optional_len += (name_len + 2); + ptr += (name_len + 2); + } + + if (did_vendor != 0x0000) { + *ptr++ = 9; + *ptr++ = EIR_DEVICE_ID; + *ptr++ = (did_source & 0x00ff); + *ptr++ = (did_source & 0xff00) >> 8; + *ptr++ = (did_vendor & 0x00ff); + *ptr++ = (did_vendor & 0xff00) >> 8; + *ptr++ = (did_product & 0x00ff); + *ptr++ = (did_product & 0xff00) >> 8; + *ptr++ = (did_version & 0x00ff); + *ptr++ = (did_version & 0xff00) >> 8; + eir_optional_len += 10; + } + + /* Group all UUID16 types */ + for (l = uuids; l != NULL; l = l->next) { + sdp_record_t *rec = l->data; + uuid_t *uuid = &rec->svclass; + + if (uuid->type != SDP_UUID16) + continue; + + if (uuid->value.uuid16 < 0x1100) + continue; + + if (uuid->value.uuid16 == PNP_INFO_SVCLASS_ID) + continue; + + /* Stop if not enough space to put next UUID16 */ + if ((eir_optional_len + 2 + sizeof(uint16_t)) > + HCI_MAX_EIR_LENGTH) { + truncated = true; + break; + } + + /* Check for duplicates */ + for (i = 0; i < uuid_count; i++) + if (uuid16[i] == uuid->value.uuid16) + break; + + if (i < uuid_count) + continue; + + uuid16[uuid_count++] = uuid->value.uuid16; + eir_optional_len += sizeof(uint16_t); + } + + if (uuid_count > 0) { + /* EIR Data length */ + ptr[0] = (uuid_count * sizeof(uint16_t)) + 1; + /* EIR Data type */ + ptr[1] = truncated ? EIR_UUID16_SOME : EIR_UUID16_ALL; + + ptr += 2; + eir_optional_len += 2; + + for (i = 0; i < uuid_count; i++) { + *ptr++ = (uuid16[i] & 0x00ff); + *ptr++ = (uuid16[i] & 0xff00) >> 8; + } + } + + /* Group all UUID128 types */ + if (eir_optional_len <= HCI_MAX_EIR_LENGTH - 2) + eir_generate_uuid128(uuids, ptr, &eir_optional_len); + + eir_total_len += eir_optional_len; + + /* store total length */ + put_le16(eir_total_len, data); + + return eir_total_len; +} diff --git a/src/eir.h b/src/eir.h new file mode 100644 index 0000000..c868177 --- /dev/null +++ b/src/eir.h @@ -0,0 +1,112 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include "lib/sdp.h" + +#define EIR_FLAGS 0x01 /* flags */ +#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */ +#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */ +#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */ +#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */ +#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */ +#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */ +#define EIR_NAME_SHORT 0x08 /* shortened local name */ +#define EIR_NAME_COMPLETE 0x09 /* complete local name */ +#define EIR_TX_POWER 0x0A /* transmit power level */ +#define EIR_CLASS_OF_DEV 0x0D /* Class of Device */ +#define EIR_SSP_HASH 0x0E /* SSP Hash */ +#define EIR_SSP_RANDOMIZER 0x0F /* SSP Randomizer */ +#define EIR_DEVICE_ID 0x10 /* device ID */ +#define EIR_SOLICIT16 0x14 /* LE: Solicit UUIDs, 16-bit */ +#define EIR_SOLICIT128 0x15 /* LE: Solicit UUIDs, 128-bit */ +#define EIR_SVC_DATA16 0x16 /* LE: Service data, 16-bit UUID */ +#define EIR_PUB_TRGT_ADDR 0x17 /* LE: Public Target Address */ +#define EIR_RND_TRGT_ADDR 0x18 /* LE: Random Target Address */ +#define EIR_GAP_APPEARANCE 0x19 /* GAP appearance */ +#define EIR_SOLICIT32 0x1F /* LE: Solicit UUIDs, 32-bit */ +#define EIR_SVC_DATA32 0x20 /* LE: Service data, 32-bit UUID */ +#define EIR_SVC_DATA128 0x21 /* LE: Service data, 128-bit UUID */ +#define EIR_TRANSPORT_DISCOVERY 0x26 /* Transport Discovery Service */ +#define EIR_MANUFACTURER_DATA 0xFF /* Manufacturer Specific Data */ + +/* Flags Descriptions */ +#define EIR_LIM_DISC 0x01 /* LE Limited Discoverable Mode */ +#define EIR_GEN_DISC 0x02 /* LE General Discoverable Mode */ +#define EIR_BREDR_UNSUP 0x04 /* BR/EDR Not Supported */ +#define EIR_CONTROLLER 0x08 /* Simultaneous LE and BR/EDR to Same + Device Capable (Controller) */ +#define EIR_SIM_HOST 0x10 /* Simultaneous LE and BR/EDR to Same + Device Capable (Host) */ + +#define EIR_SD_MAX_LEN 238 /* 240 (EIR) - 2 (len) */ +#define EIR_MSD_MAX_LEN 236 /* 240 (EIR) - 2 (len & type) - 2 */ + +struct eir_msd { + uint16_t company; + uint8_t data[EIR_MSD_MAX_LEN]; + uint8_t data_len; +}; + +struct eir_sd { + char *uuid; + uint8_t data[EIR_SD_MAX_LEN]; + uint8_t data_len; +}; + +struct eir_ad { + uint8_t type; + uint8_t len; + void *data; +}; + +struct eir_data { + GSList *services; + unsigned int flags; + char *name; + uint32_t class; + uint16_t appearance; + bool name_complete; + int8_t tx_power; + uint8_t *hash; + uint8_t *randomizer; + bdaddr_t addr; + uint16_t did_vendor; + uint16_t did_product; + uint16_t did_version; + uint16_t did_source; + GSList *msd_list; + GSList *sd_list; + GSList *data_list; +}; + +void eir_data_free(struct eir_data *eir); +void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len); +int eir_parse_oob(struct eir_data *eir, uint8_t *eir_data, uint16_t eir_len); +int eir_create_oob(const bdaddr_t *addr, const char *name, uint32_t cod, + const uint8_t *hash, const uint8_t *randomizer, + uint16_t did_vendor, uint16_t did_product, + uint16_t did_version, uint16_t did_source, + sdp_list_t *uuids, uint8_t *data); diff --git a/src/error.c b/src/error.c new file mode 100644 index 0000000..8951707 --- /dev/null +++ b/src/error.c @@ -0,0 +1,128 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2007-2008 Fabien Chevalier + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gdbus/gdbus.h" + +#include "error.h" + +DBusMessage *btd_error_invalid_args(DBusMessage *msg) +{ + return btd_error_invalid_args_str(msg, + "Invalid arguments in method call"); +} + +DBusMessage *btd_error_invalid_args_str(DBusMessage *msg, const char *str) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments", + "%s", str); +} + +DBusMessage *btd_error_busy(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", + "Operation already in progress"); +} + +DBusMessage *btd_error_already_exists(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyExists", + "Already Exists"); +} + +DBusMessage *btd_error_not_supported(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotSupported", + "Operation is not supported"); +} + +DBusMessage *btd_error_not_connected(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotConnected", + "Not Connected"); +} + +DBusMessage *btd_error_already_connected(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".AlreadyConnected", + "Already Connected"); +} + +DBusMessage *btd_error_in_progress(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", + "In Progress"); +} + +DBusMessage *btd_error_not_available(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAvailable", + "Operation currently not available"); +} + +DBusMessage *btd_error_does_not_exist(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExist", + "Does Not Exist"); +} + +DBusMessage *btd_error_not_authorized(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotAuthorized", + "Operation Not Authorized"); +} + +DBusMessage *btd_error_not_permitted(DBusMessage *msg, const char *str) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotPermitted", + "%s", str); +} + +DBusMessage *btd_error_no_such_adapter(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NoSuchAdapter", + "No such adapter"); +} + +DBusMessage *btd_error_agent_not_available(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".AgentNotAvailable", + "Agent Not Available"); +} + +DBusMessage *btd_error_not_ready(DBusMessage *msg) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady", + "Resource Not Ready"); +} + +DBusMessage *btd_error_failed(DBusMessage *msg, const char *str) +{ + return g_dbus_create_error(msg, ERROR_INTERFACE + ".Failed", "%s", str); +} diff --git a/src/error.h b/src/error.h new file mode 100644 index 0000000..7c8cad0 --- /dev/null +++ b/src/error.h @@ -0,0 +1,45 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2007-2008 Fabien Chevalier + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#define ERROR_INTERFACE "org.bluez.Error" + +DBusMessage *btd_error_invalid_args(DBusMessage *msg); +DBusMessage *btd_error_invalid_args_str(DBusMessage *msg, const char *str); +DBusMessage *btd_error_busy(DBusMessage *msg); +DBusMessage *btd_error_already_exists(DBusMessage *msg); +DBusMessage *btd_error_not_supported(DBusMessage *msg); +DBusMessage *btd_error_not_connected(DBusMessage *msg); +DBusMessage *btd_error_already_connected(DBusMessage *msg); +DBusMessage *btd_error_not_available(DBusMessage *msg); +DBusMessage *btd_error_in_progress(DBusMessage *msg); +DBusMessage *btd_error_does_not_exist(DBusMessage *msg); +DBusMessage *btd_error_not_authorized(DBusMessage *msg); +DBusMessage *btd_error_not_permitted(DBusMessage *msg, const char *str); +DBusMessage *btd_error_no_such_adapter(DBusMessage *msg); +DBusMessage *btd_error_agent_not_available(DBusMessage *msg); +DBusMessage *btd_error_not_ready(DBusMessage *msg); +DBusMessage *btd_error_failed(DBusMessage *msg, const char *str); diff --git a/src/gatt-client.c b/src/gatt-client.c new file mode 100644 index 0000000..6bcdecf --- /dev/null +++ b/src/gatt-client.c @@ -0,0 +1,2289 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "log.h" +#include "error.h" +#include "adapter.h" +#include "device.h" +#include "src/shared/io.h" +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" +#include "src/shared/util.h" +#include "gatt-client.h" +#include "dbus-common.h" + +#ifndef NELEM +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) +#endif + +#define GATT_SERVICE_IFACE "org.bluez.GattService1" +#define GATT_CHARACTERISTIC_IFACE "org.bluez.GattCharacteristic1" +#define GATT_DESCRIPTOR_IFACE "org.bluez.GattDescriptor1" + +struct btd_gatt_client { + struct btd_device *device; + bool ready; + char devaddr[18]; + struct gatt_db *db; + struct bt_gatt_client *gatt; + + struct queue *services; + struct queue *all_notify_clients; + struct queue *ios; +}; + +struct service { + struct btd_gatt_client *client; + bool primary; + uint16_t start_handle; + uint16_t end_handle; + bt_uuid_t uuid; + char *path; + struct queue *chrcs; + struct queue *incl_services; +}; + +typedef bool (*async_dbus_op_complete_t)(void *data); + +struct async_dbus_op { + int ref_count; + unsigned int id; + struct queue *msgs; + void *data; + uint16_t offset; + async_dbus_op_complete_t complete; +}; + +struct sock_io { + DBusMessage *msg; + struct io *io; + void (*destroy)(void *data); + void *data; +}; + +struct characteristic { + struct service *service; + struct gatt_db_attribute *attr; + uint16_t handle; + uint16_t value_handle; + uint8_t props; + uint16_t ext_props; + uint16_t ext_props_handle; + bt_uuid_t uuid; + char *path; + + unsigned int ready_id; + struct sock_io *write_io; + struct sock_io *notify_io; + + struct async_dbus_op *read_op; + struct async_dbus_op *write_op; + + struct queue *descs; + + bool notifying; + struct queue *notify_clients; +}; + +struct descriptor { + struct characteristic *chrc; + struct gatt_db_attribute *attr; + uint16_t handle; + bt_uuid_t uuid; + char *path; + + struct async_dbus_op *read_op; + struct async_dbus_op *write_op; +}; + +static bool uuid_cmp(const bt_uuid_t *uuid, uint16_t u16) +{ + bt_uuid_t uuid16; + + bt_uuid16_create(&uuid16, u16); + + return bt_uuid_cmp(uuid, &uuid16) == 0; +} + +static gboolean descriptor_get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + char uuid[MAX_LEN_UUID_STR + 1]; + const char *ptr = uuid; + struct descriptor *desc = data; + + bt_uuid_to_string(&desc->uuid, uuid, sizeof(uuid)); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); + + return TRUE; +} + +static gboolean descriptor_get_characteristic( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct descriptor *desc = data; + const char *str = desc->chrc->path; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str); + + return TRUE; +} + +static void read_cb(struct gatt_db_attribute *attrib, int err, + const uint8_t *value, size_t length, + void *user_data) +{ + DBusMessageIter *array = user_data; + + if (err) + return; + + dbus_message_iter_append_fixed_array(array, DBUS_TYPE_BYTE, &value, + length); +} + +static gboolean descriptor_get_value(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct descriptor *desc = data; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array); + + gatt_db_attribute_read(desc->attr, 0, 0, NULL, read_cb, &array); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static void read_check_cb(struct gatt_db_attribute *attrib, int err, + const uint8_t *value, size_t length, + void *user_data) +{ + gboolean *ret = user_data; + + if (err) { + *ret = FALSE; + return; + } + + *ret = TRUE; +} + +static gboolean descriptor_value_exists(const GDBusPropertyTable *property, + void *data) +{ + struct descriptor *desc = data; + gboolean ret; + + gatt_db_attribute_read(desc->attr, 0, 0, NULL, read_check_cb, &ret); + + return ret; +} + +static int parse_value_arg(DBusMessageIter *iter, uint8_t **value, int *len) +{ + DBusMessageIter array; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(iter, &array); + dbus_message_iter_get_fixed_array(&array, value, len); + + return 0; +} + +static void async_dbus_op_free(void *data) +{ + struct async_dbus_op *op = data; + + queue_destroy(op->msgs, (void *)dbus_message_unref); + + free(op); +} + +static struct async_dbus_op *async_dbus_op_ref(struct async_dbus_op *op) +{ + __sync_fetch_and_add(&op->ref_count, 1); + + return op; +} + +static void async_dbus_op_unref(void *data) +{ + struct async_dbus_op *op = data; + + if (__sync_sub_and_fetch(&op->ref_count, 1)) + return; + + async_dbus_op_free(op); +} + +static void message_append_byte_array(DBusMessage *msg, const uint8_t *bytes, + size_t len) +{ + DBusMessageIter iter, array; + + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "y", &array); + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &bytes, + len); + dbus_message_iter_close_container(&iter, &array); +} + +static DBusMessage *create_gatt_dbus_error(DBusMessage *msg, uint8_t att_ecode) +{ + switch (att_ecode) { + case BT_ATT_ERROR_READ_NOT_PERMITTED: + return btd_error_not_permitted(msg, "Read not permitted"); + case BT_ATT_ERROR_WRITE_NOT_PERMITTED: + return btd_error_not_permitted(msg, "Write not permitted"); + case BT_ATT_ERROR_AUTHENTICATION: + case BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION: + case BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE: + return btd_error_not_permitted(msg, "Not paired"); + case BT_ATT_ERROR_INVALID_OFFSET: + return btd_error_invalid_args_str(msg, "Invalid offset"); + case BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN: + return btd_error_invalid_args_str(msg, "Invalid Length"); + case BT_ATT_ERROR_AUTHORIZATION: + return btd_error_not_authorized(msg); + case BT_ATT_ERROR_REQUEST_NOT_SUPPORTED: + return btd_error_not_supported(msg); + case 0: + return btd_error_failed(msg, "Operation failed"); + default: + return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", + "Operation failed with ATT error: 0x%02x", + att_ecode); + } + + return NULL; +} + +static void write_descriptor_cb(struct gatt_db_attribute *attr, int err, + void *user_data) +{ + struct descriptor *desc = user_data; + + if (err) + return; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), desc->path, + GATT_DESCRIPTOR_IFACE, "Value"); +} + +static void async_dbus_op_reply(struct async_dbus_op *op, int err, + const uint8_t *value, ssize_t length) +{ + const struct queue_entry *entry; + DBusMessage *reply; + + op->id = 0; + + for (entry = queue_get_entries(op->msgs); entry; entry = entry->next) { + DBusMessage *msg = entry->data; + + if (err) { + reply = err > 0 ? create_gatt_dbus_error(msg, err) : + btd_error_failed(msg, strerror(-err)); + goto send_reply; + } + + reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + if (!reply) { + error("Failed to allocate D-Bus message reply"); + return; + } + + if (length >= 0) + message_append_byte_array(reply, value, length); + +send_reply: + g_dbus_send_message(btd_get_dbus_connection(), reply); + } +} + +static void read_op_cb(struct gatt_db_attribute *attrib, int err, + const uint8_t *value, size_t length, + void *user_data) +{ + struct async_dbus_op *op = user_data; + + async_dbus_op_reply(op, err, value, length); +} + +static void desc_read_cb(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct async_dbus_op *op = user_data; + struct descriptor *desc = op->data; + + if (!success) + goto fail; + + if (!op->offset) + gatt_db_attribute_reset(desc->attr); + + if (!gatt_db_attribute_write(desc->attr, op->offset, value, length, 0, + NULL, write_descriptor_cb, desc)) { + error("Failed to store attribute"); + att_ecode = BT_ATT_ERROR_UNLIKELY; + goto fail; + } + + /* Read the stored data from db */ + if (!gatt_db_attribute_read(desc->attr, 0, 0, NULL, read_op_cb, op)) { + error("Failed to read database"); + att_ecode = BT_ATT_ERROR_UNLIKELY; + goto fail; + } + + desc->read_op = NULL; + + return; + +fail: + async_dbus_op_reply(op, att_ecode, NULL, 0); + desc->read_op = NULL; +} + +static int parse_options(DBusMessageIter *iter, uint16_t *offset, + const char **type) +{ + DBusMessageIter dict; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(&dict, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "offset") == 0) { + if (var != DBUS_TYPE_UINT16) + return -EINVAL; + dbus_message_iter_get_basic(&value, offset); + } + + if (type && strcasecmp(key, "type") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&value, type); + } + + dbus_message_iter_next(&dict); + } + + return 0; +} + +static struct async_dbus_op *async_dbus_op_new(DBusMessage *msg, void *data) +{ + struct async_dbus_op *op; + + op = new0(struct async_dbus_op, 1); + op->msgs = queue_new(); + queue_push_tail(op->msgs, dbus_message_ref(msg)); + op->data = data; + + return op; +} + +static struct async_dbus_op *read_value(struct bt_gatt_client *gatt, + DBusMessage *msg, uint16_t handle, + uint16_t offset, + bt_gatt_client_read_callback_t callback, + void *data) +{ + struct async_dbus_op *op; + + op = async_dbus_op_new(msg, data); + op->offset = offset; + + op->id = bt_gatt_client_read_long_value(gatt, handle, offset, callback, + async_dbus_op_ref(op), + async_dbus_op_unref); + if (op->id) + return op; + + async_dbus_op_free(op); + + return NULL; +} + +static DBusMessage *descriptor_read_value(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct descriptor *desc = user_data; + struct bt_gatt_client *gatt = desc->chrc->service->client->gatt; + DBusMessageIter iter; + uint16_t offset = 0; + + if (!gatt) + return btd_error_failed(msg, "Not connected"); + + dbus_message_iter_init(msg, &iter); + + if (parse_options(&iter, &offset, NULL)) + return btd_error_invalid_args(msg); + + if (desc->read_op) { + if (desc->read_op->offset != offset) + return btd_error_in_progress(msg); + queue_push_tail(desc->read_op->msgs, dbus_message_ref(msg)); + return NULL; + } + + desc->read_op = read_value(gatt, msg, desc->handle, offset, + desc_read_cb, desc); + if (!desc->read_op) + return btd_error_failed(msg, "Failed to send read request"); + + return NULL; +} + +static void write_result_cb(bool success, bool reliable_error, + uint8_t att_ecode, void *user_data) +{ + struct async_dbus_op *op = user_data; + int err = 0; + + if (op->complete && !op->complete(op->data)) { + err = -EFAULT; + goto done; + } + + if (!success) { + if (reliable_error) + err = -EFAULT; + else + err = att_ecode; + } + +done: + async_dbus_op_reply(op, err, NULL, -1); +} + +static void write_cb(bool success, uint8_t att_ecode, void *user_data) +{ + write_result_cb(success, false, att_ecode, user_data); +} + +static struct async_dbus_op *start_long_write(DBusMessage *msg, uint16_t handle, + struct bt_gatt_client *gatt, + bool reliable, const uint8_t *value, + size_t value_len, uint16_t offset, + void *data, + async_dbus_op_complete_t complete) +{ + struct async_dbus_op *op; + + op = async_dbus_op_new(msg, data); + op->complete = complete; + op->offset = offset; + + op->id = bt_gatt_client_write_long_value(gatt, reliable, handle, offset, + value, value_len, + write_result_cb, op, + async_dbus_op_free); + + if (!op->id) { + async_dbus_op_free(op); + return NULL; + } + + return op; +} + +static struct async_dbus_op *start_write_request(DBusMessage *msg, + uint16_t handle, + struct bt_gatt_client *gatt, + const uint8_t *value, size_t value_len, + void *data, + async_dbus_op_complete_t complete) +{ + struct async_dbus_op *op; + + op = async_dbus_op_new(msg, data); + op->complete = complete; + + op->id = bt_gatt_client_write_value(gatt, handle, value, value_len, + write_cb, op, + async_dbus_op_free); + if (!op->id) { + async_dbus_op_free(op); + return NULL; + } + + return op; +} + +static bool desc_write_complete(void *data) +{ + struct descriptor *desc = data; + + desc->write_op = NULL; + + /* + * The descriptor might have been unregistered during the read. Return + * failure. + */ + return !!desc->chrc; +} + +static DBusMessage *descriptor_write_value(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct descriptor *desc = user_data; + struct bt_gatt_client *gatt = desc->chrc->service->client->gatt; + DBusMessageIter iter; + uint8_t *value = NULL; + int value_len = 0; + uint16_t offset = 0; + + if (!gatt) + return btd_error_failed(msg, "Not connected"); + + if (desc->write_op) + return btd_error_in_progress(msg); + + dbus_message_iter_init(msg, &iter); + + if (parse_value_arg(&iter, &value, &value_len)) + return btd_error_invalid_args(msg); + + dbus_message_iter_next(&iter); + + if (parse_options(&iter, &offset, NULL)) + return btd_error_invalid_args(msg); + + /* + * Don't allow writing to Client Characteristic Configuration + * descriptors. We achieve this through the StartNotify and StopNotify + * methods on GattCharacteristic1. + */ + if (uuid_cmp(&desc->uuid, GATT_CLIENT_CHARAC_CFG_UUID)) + return btd_error_not_permitted(msg, "Write not permitted"); + + /* + * Based on the value length and the MTU, either use a write or a long + * write. + */ + if (value_len <= bt_gatt_client_get_mtu(gatt) - 3 && !offset) + desc->write_op = start_write_request(msg, desc->handle, + gatt, value, + value_len, desc, + desc_write_complete); + else + desc->write_op = start_long_write(msg, desc->handle, gatt, + false, value, + value_len, offset, desc, + desc_write_complete); + + if (!desc->write_op) + return btd_error_failed(msg, "Failed to initiate write"); + + return NULL; +} + +static const GDBusPropertyTable descriptor_properties[] = { + { "UUID", "s", descriptor_get_uuid }, + { "Characteristic", "o", descriptor_get_characteristic, }, + { "Value", "ay", descriptor_get_value, NULL, descriptor_value_exists }, + { } +}; + +static const GDBusMethodTable descriptor_methods[] = { + { GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({ "options", "a{sv}" }), + GDBUS_ARGS({ "value", "ay" }), + descriptor_read_value) }, + { GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({ "value", "ay" }, + { "options", "a{sv}" }), + NULL, + descriptor_write_value) }, + { } +}; + +static void descriptor_free(void *data) +{ + struct descriptor *desc = data; + + g_free(desc->path); + free(desc); +} + +static struct descriptor *descriptor_create(struct gatt_db_attribute *attr, + struct characteristic *chrc) +{ + struct descriptor *desc; + + desc = new0(struct descriptor, 1); + desc->chrc = chrc; + desc->attr = attr; + desc->handle = gatt_db_attribute_get_handle(attr); + + bt_uuid_to_uuid128(gatt_db_attribute_get_type(attr), &desc->uuid); + + desc->path = g_strdup_printf("%s/desc%04x", chrc->path, desc->handle); + + if (!g_dbus_register_interface(btd_get_dbus_connection(), desc->path, + GATT_DESCRIPTOR_IFACE, + descriptor_methods, NULL, + descriptor_properties, + desc, descriptor_free)) { + error("Unable to register GATT descriptor with handle 0x%04x", + desc->handle); + descriptor_free(desc); + + return NULL; + } + + DBG("Exported GATT characteristic descriptor: %s", desc->path); + + if (uuid_cmp(&desc->uuid, GATT_CHARAC_EXT_PROPER_UUID)) + chrc->ext_props_handle = desc->handle; + + return desc; +} + +static void unregister_descriptor(void *data) +{ + struct descriptor *desc = data; + struct bt_gatt_client *gatt = desc->chrc->service->client->gatt; + + DBG("Removing GATT descriptor: %s", desc->path); + + if (desc->read_op) + bt_gatt_client_cancel(gatt, desc->read_op->id); + + if (desc->write_op) + bt_gatt_client_cancel(gatt, desc->write_op->id); + + desc->chrc = NULL; + + g_dbus_unregister_interface(btd_get_dbus_connection(), desc->path, + GATT_DESCRIPTOR_IFACE); +} + +static gboolean characteristic_get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + char uuid[MAX_LEN_UUID_STR + 1]; + const char *ptr = uuid; + struct characteristic *chrc = data; + + bt_uuid_to_string(&chrc->uuid, uuid, sizeof(uuid)); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); + + return TRUE; +} + +static gboolean characteristic_get_service(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct characteristic *chrc = data; + const char *str = chrc->service->path; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str); + + return TRUE; +} + +static gboolean characteristic_get_value(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct characteristic *chrc = data; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array); + + gatt_db_attribute_read(chrc->attr, 0, 0, NULL, read_cb, &array); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean characteristic_value_exists(const GDBusPropertyTable *property, + void *data) +{ + struct characteristic *chrc = data; + gboolean ret; + + gatt_db_attribute_read(chrc->attr, 0, 0, NULL, read_check_cb, &ret); + + return ret; +} + +static gboolean characteristic_get_notifying(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct characteristic *chrc = data; + dbus_bool_t notifying = chrc->notifying ? TRUE : FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, ¬ifying); + + return TRUE; +} + +static gboolean +characteristic_notifying_exists(const GDBusPropertyTable *property, void *data) +{ + struct characteristic *chrc = data; + + return (chrc->props & BT_GATT_CHRC_PROP_NOTIFY || + chrc->props & BT_GATT_CHRC_PROP_INDICATE); +} + +struct chrc_prop_data { + uint8_t prop; + char *str; +}; + +static struct chrc_prop_data chrc_props[] = { + /* Default Properties */ + { BT_GATT_CHRC_PROP_BROADCAST, "broadcast" }, + { BT_GATT_CHRC_PROP_READ, "read" }, + { BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP, "write-without-response" }, + { BT_GATT_CHRC_PROP_WRITE, "write" }, + { BT_GATT_CHRC_PROP_NOTIFY, "notify" }, + { BT_GATT_CHRC_PROP_INDICATE, "indicate" }, + { BT_GATT_CHRC_PROP_AUTH, "authenticated-signed-writes" }, + { BT_GATT_CHRC_PROP_EXT_PROP, "extended-properties" } +}; + +static struct chrc_prop_data chrc_ext_props[] = { + /* Extended Properties */ + { BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE, "reliable-write" }, + { BT_GATT_CHRC_EXT_PROP_WRITABLE_AUX, "writable-auxiliaries" } +}; + +static gboolean characteristic_get_flags(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct characteristic *chrc = data; + DBusMessageIter array; + unsigned i; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "s", &array); + + for (i = 0; i < NELEM(chrc_props); i++) { + if (chrc->props & chrc_props[i].prop) + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, + &chrc_props[i].str); + } + + for (i = 0; i < NELEM(chrc_ext_props); i++) { + if (chrc->ext_props & chrc_ext_props[i].prop) + dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, + &chrc_ext_props[i].str); + } + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static gboolean +characteristic_get_write_acquired(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct characteristic *chrc = data; + dbus_bool_t locked = chrc->write_io ? TRUE : FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &locked); + + return TRUE; +} + +static gboolean +characteristic_write_acquired_exists(const GDBusPropertyTable *property, + void *data) +{ + struct characteristic *chrc = data; + + return (chrc->props & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP); +} + +static gboolean +characteristic_get_notify_acquired(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct characteristic *chrc = data; + dbus_bool_t locked = chrc->notify_io ? TRUE : FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &locked); + + return TRUE; +} + +static gboolean +characteristic_notify_acquired_exists(const GDBusPropertyTable *property, + void *data) +{ + struct characteristic *chrc = data; + + return (chrc->props & BT_GATT_CHRC_PROP_NOTIFY); +} + +static void write_characteristic_cb(struct gatt_db_attribute *attr, int err, + void *user_data) +{ + struct characteristic *chrc = user_data; + + if (err) + return; + + g_dbus_emit_property_changed_full(btd_get_dbus_connection(), + chrc->path, GATT_CHARACTERISTIC_IFACE, + "Value", G_DBUS_PROPERTY_CHANGED_FLAG_FLUSH); + +} + +static void chrc_read_cb(bool success, uint8_t att_ecode, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct async_dbus_op *op = user_data; + struct characteristic *chrc = op->data; + + if (!success) + goto fail; + + if (!op->offset) + gatt_db_attribute_reset(chrc->attr); + + if (!gatt_db_attribute_write(chrc->attr, op->offset, value, length, 0, + NULL, write_characteristic_cb, chrc)) { + error("Failed to store attribute"); + att_ecode = BT_ATT_ERROR_UNLIKELY; + goto fail; + } + + /* Read the stored data from db */ + if (!gatt_db_attribute_read(chrc->attr, 0, 0, NULL, read_op_cb, op)) { + error("Failed to read database"); + att_ecode = BT_ATT_ERROR_UNLIKELY; + goto fail; + } + + chrc->read_op = NULL; + + return; + +fail: + async_dbus_op_reply(op, att_ecode, NULL, 0); + chrc->read_op = NULL; +} + +static DBusMessage *characteristic_read_value(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct characteristic *chrc = user_data; + struct bt_gatt_client *gatt = chrc->service->client->gatt; + DBusMessageIter iter; + uint16_t offset = 0; + + if (!gatt) + return btd_error_failed(msg, "Not connected"); + + dbus_message_iter_init(msg, &iter); + + if (parse_options(&iter, &offset, NULL)) + return btd_error_invalid_args(msg); + + if (chrc->read_op) { + if (chrc->read_op->offset != offset) + return btd_error_in_progress(msg); + queue_push_tail(chrc->read_op->msgs, dbus_message_ref(msg)); + return NULL; + } + + chrc->read_op = read_value(gatt, msg, chrc->value_handle, offset, + chrc_read_cb, chrc); + if (!chrc->read_op) + return btd_error_failed(msg, "Failed to send read request"); + + return NULL; +} + +static bool chrc_write_complete(void *data) +{ + struct characteristic *chrc = data; + + chrc->write_op = NULL; + + /* + * The characteristic might have been unregistered during the read. + * Return failure. + */ + return !!chrc->service; +} + +static DBusMessage *characteristic_write_value(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct characteristic *chrc = user_data; + struct bt_gatt_client *gatt = chrc->service->client->gatt; + DBusMessageIter iter; + uint8_t *value = NULL; + int value_len = 0; + bool supported = false; + uint16_t offset = 0; + const char *type = NULL; + + if (!gatt) + return btd_error_failed(msg, "Not connected"); + + if (chrc->write_io) + return btd_error_not_permitted(msg, "Write acquired"); + + if (chrc->write_op) + return btd_error_in_progress(msg); + + dbus_message_iter_init(msg, &iter); + + if (parse_value_arg(&iter, &value, &value_len)) + return btd_error_invalid_args(msg); + + dbus_message_iter_next(&iter); + + if (parse_options(&iter, &offset, &type)) + return btd_error_invalid_args(msg); + + /* + * Decide which write to use based on characteristic properties. For now + * we don't perform signed writes since gatt-client doesn't support them + * and the user can always encrypt the through pairing. The procedure to + * use is determined based on the following priority: + * + * * "reliable-write" property set -> reliable long-write. + * * "write" property set -> write request. + * - If value is larger than MTU - 3: long-write + * * "write-without-response" property set -> write command. + */ + if ((!type && (chrc->ext_props & BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE)) + || (type && !strcasecmp(type, "reliable"))) { + supported = true; + chrc->write_op = start_long_write(msg, chrc->value_handle, gatt, + true, value, value_len, offset, + chrc, chrc_write_complete); + if (chrc->write_op) + return NULL; + } + + if ((!type && chrc->props & BT_GATT_CHRC_PROP_WRITE) || + (type && !strcasecmp(type, "request"))) { + uint16_t mtu; + + supported = true; + mtu = bt_gatt_client_get_mtu(gatt); + if (!mtu) + return btd_error_failed(msg, "No ATT transport"); + + if (value_len <= mtu - 3 && !offset) + chrc->write_op = start_write_request(msg, + chrc->value_handle, + gatt, value, value_len, + chrc, chrc_write_complete); + else + chrc->write_op = start_long_write(msg, + chrc->value_handle, gatt, + false, value, value_len, offset, + chrc, chrc_write_complete); + + if (chrc->write_op) + return NULL; + } + + if ((type && strcasecmp(type, "command")) || offset || + !(chrc->props & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP)) + goto fail; + + supported = true; + + if (bt_gatt_client_write_without_response(gatt, + chrc->value_handle, + chrc->props & BT_GATT_CHRC_PROP_AUTH, + value, value_len)) + return dbus_message_new_method_return(msg); + +fail: + if (supported) + return btd_error_failed(msg, "Failed to initiate write"); + + return btd_error_not_supported(msg); +} + +static bool sock_read(struct io *io, void *user_data) +{ + struct characteristic *chrc = user_data; + struct bt_gatt_client *gatt = chrc->service->client->gatt; + struct msghdr msg; + uint8_t buf[512]; + struct iovec iov; + int fd = io_get_fd(io); + ssize_t bytes_read; + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + bytes_read = recvmsg(fd, &msg, MSG_DONTWAIT); + if (bytes_read < 0) { + error("recvmsg: %s", strerror(errno)); + return false; + } + + if (!gatt || bytes_read == 0) + return false; + + bt_gatt_client_write_without_response(gatt, chrc->value_handle, + chrc->props & BT_GATT_CHRC_PROP_AUTH, + buf, bytes_read); + + return true; +} + +static void sock_io_destroy(struct sock_io *io) +{ + if (io->destroy) + io->destroy(io->data); + + if (io->msg) + dbus_message_unref(io->msg); + + io_destroy(io->io); + free(io); +} + +static void destroy_sock(struct characteristic *chrc, struct io *io) +{ + queue_remove(chrc->service->client->ios, io); + + if (chrc->write_io && io == chrc->write_io->io) { + sock_io_destroy(chrc->write_io); + chrc->write_io = NULL; + g_dbus_emit_property_changed(btd_get_dbus_connection(), + chrc->path, + GATT_CHARACTERISTIC_IFACE, + "WriteAcquired"); + } else if (chrc->notify_io) { + sock_io_destroy(chrc->notify_io); + chrc->notify_io = NULL; + g_dbus_emit_property_changed(btd_get_dbus_connection(), + chrc->path, + GATT_CHARACTERISTIC_IFACE, + "NotifyAcquired"); + } +} + +static bool sock_hup(struct io *io, void *user_data) +{ + struct characteristic *chrc = user_data; + + DBG("%s: io %p", chrc->path, io); + + destroy_sock(chrc, io); + + return false; +} + +static DBusMessage *create_sock(struct characteristic *chrc, DBusMessage *msg) +{ + struct bt_gatt_client *gatt = chrc->service->client->gatt; + int fds[2]; + struct io *io; + bool dir; + uint16_t mtu; + DBusMessage *reply; + + if (!gatt || !bt_gatt_client_is_ready(gatt)) + return btd_error_failed(msg, "Not connected"); + + if (socketpair(AF_LOCAL, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, + 0, fds) < 0) + return btd_error_failed(msg, strerror(errno)); + + dir = dbus_message_has_member(msg, "AcquireWrite"); + + io = io_new(fds[!dir]); + if (!io) { + close(fds[0]); + close(fds[1]); + return btd_error_failed(msg, strerror(EIO)); + } + + io_set_close_on_destroy(io, true); + + if (!io_set_read_handler(io, sock_read, chrc, NULL)) + goto fail; + + if (!io_set_disconnect_handler(io, sock_hup, chrc, NULL)) + goto fail; + + mtu = bt_gatt_client_get_mtu(gatt); + + reply = g_dbus_create_reply(msg, DBUS_TYPE_UNIX_FD, &fds[dir], + DBUS_TYPE_UINT16, &mtu, + DBUS_TYPE_INVALID); + + close(fds[dir]); + + if (dir) { + chrc->write_io->io = io; + g_dbus_emit_property_changed(btd_get_dbus_connection(), + chrc->path, + GATT_CHARACTERISTIC_IFACE, + "WriteAcquired"); + } else { + chrc->notify_io->io = io; + g_dbus_emit_property_changed(btd_get_dbus_connection(), + chrc->path, + GATT_CHARACTERISTIC_IFACE, + "NotifyAcquired"); + } + + queue_push_tail(chrc->service->client->ios, io); + + DBG("%s: sender %s io %p", dbus_message_get_member(msg), + dbus_message_get_sender(msg), io); + + return reply; + +fail: + io_destroy(io); + close(fds[dir]); + return btd_error_failed(msg, strerror(EIO)); +} + +static void characteristic_ready(bool success, uint8_t ecode, void *user_data) +{ + struct characteristic *chrc = user_data; + DBusMessage *reply; + + chrc->ready_id = 0; + + if (chrc->write_io && chrc->write_io->msg) { + reply = create_sock(chrc, chrc->write_io->msg); + + g_dbus_send_message(btd_get_dbus_connection(), reply); + + dbus_message_unref(chrc->write_io->msg); + chrc->write_io->msg = NULL; + } + + if (chrc->notify_io && chrc->notify_io->msg) { + reply = create_sock(chrc, chrc->notify_io->msg); + + g_dbus_send_message(btd_get_dbus_connection(), reply); + + dbus_message_unref(chrc->notify_io->msg); + chrc->notify_io->msg = NULL; + } +} + +static DBusMessage *characteristic_acquire_write(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct characteristic *chrc = user_data; + struct bt_gatt_client *gatt = chrc->service->client->gatt; + + if (!gatt) + return btd_error_failed(msg, "Not connected"); + + if (chrc->write_io) + return btd_error_not_permitted(msg, "Write acquired"); + + if (!(chrc->props & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP)) + return btd_error_not_supported(msg); + + chrc->write_io = new0(struct sock_io, 1); + + if (!bt_gatt_client_is_ready(gatt)) { + /* GATT not ready, wait until it becomes ready */ + if (!chrc->ready_id) + chrc->ready_id = bt_gatt_client_ready_register(gatt, + characteristic_ready, + chrc, NULL); + chrc->write_io->msg = dbus_message_ref(msg); + return NULL; + } + + return create_sock(chrc, msg); +} + +struct notify_client { + struct characteristic *chrc; + int ref_count; + char *owner; + guint watch; + unsigned int notify_id; +}; + +static void notify_client_free(struct notify_client *client) +{ + DBG("owner %s", client->owner); + + g_dbus_remove_watch(btd_get_dbus_connection(), client->watch); + bt_gatt_client_unregister_notify(client->chrc->service->client->gatt, + client->notify_id); + free(client->owner); + free(client); +} + +static void notify_client_unref(void *data) +{ + struct notify_client *client = data; + + DBG("owner %s", client->owner); + + if (__sync_sub_and_fetch(&client->ref_count, 1)) + return; + + notify_client_free(client); +} + +static struct notify_client *notify_client_ref(struct notify_client *client) +{ + DBG("owner %s", client->owner); + + __sync_fetch_and_add(&client->ref_count, 1); + + return client; +} + +static bool match_notifying(const void *a, const void *b) +{ + const struct notify_client *client = a; + + return !!client->notify_id; +} + +static void update_notifying(struct characteristic *chrc) +{ + if (!chrc->notifying) + return; + + if (queue_find(chrc->notify_clients, match_notifying, NULL)) + return; + + chrc->notifying = false; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), chrc->path, + GATT_CHARACTERISTIC_IFACE, + "Notifying"); +} + +static void notify_client_disconnect(DBusConnection *conn, void *user_data) +{ + struct notify_client *client = user_data; + struct characteristic *chrc = client->chrc; + + DBG("owner %s", client->owner); + + queue_remove(chrc->notify_clients, client); + queue_remove(chrc->service->client->all_notify_clients, client); + + update_notifying(chrc); + + notify_client_unref(client); +} + +static struct notify_client *notify_client_create(struct characteristic *chrc, + const char *owner) +{ + struct notify_client *client; + + client = new0(struct notify_client, 1); + client->chrc = chrc; + client->owner = strdup(owner); + if (!client->owner) { + free(client); + return NULL; + } + + client->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), + owner, notify_client_disconnect, + client, NULL); + if (!client->watch) { + free(client->owner); + free(client); + return NULL; + } + + return notify_client_ref(client); +} + +static bool match_notify_sender(const void *a, const void *b) +{ + const struct notify_client *client = a; + const char *sender = b; + + return strcmp(client->owner, sender) == 0; +} + +static void notify_cb(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct async_dbus_op *op = user_data; + struct notify_client *client = op->data; + struct characteristic *chrc = client->chrc; + + /* + * Even if the value didn't change, we want to send a PropertiesChanged + * signal so that we propagate the notification/indication to + * applications. + */ + gatt_db_attribute_reset(chrc->attr); + gatt_db_attribute_write(chrc->attr, 0, value, length, 0, NULL, + write_characteristic_cb, chrc); +} + +static void create_notify_reply(struct async_dbus_op *op, bool success, + uint8_t att_ecode) +{ + int err; + + if (success) + err = 0; + else + err = att_ecode ? att_ecode : -ENOENT; + + async_dbus_op_reply(op, err, NULL, -1); +} + +static void register_notify_cb(uint16_t att_ecode, void *user_data) +{ + struct async_dbus_op *op = user_data; + struct notify_client *client = op->data; + struct characteristic *chrc = client->chrc; + + if (att_ecode) { + queue_remove(chrc->notify_clients, client); + queue_remove(chrc->service->client->all_notify_clients, client); + notify_client_free(client); + + create_notify_reply(op, false, att_ecode); + + return; + } + + if (!chrc->notifying) { + chrc->notifying = true; + g_dbus_emit_property_changed(btd_get_dbus_connection(), + chrc->path, GATT_CHARACTERISTIC_IFACE, + "Notifying"); + } + + create_notify_reply(op, true, 0); +} + +static void notify_io_cb(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct msghdr msg; + struct iovec iov; + struct notify_client *client = user_data; + struct characteristic *chrc = client->chrc; + int err; + + /* Drop notification if the sock is not ready */ + if (!chrc->notify_io || !chrc->notify_io->io) + return; + + iov.iov_base = (void *) value; + iov.iov_len = length; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + err = sendmsg(io_get_fd(chrc->notify_io->io), &msg, MSG_NOSIGNAL); + if (err < 0) + error("sendmsg: %s", strerror(errno)); +} + +static void register_notify_io_cb(uint16_t att_ecode, void *user_data) +{ + struct notify_client *client = user_data; + struct characteristic *chrc = client->chrc; + struct bt_gatt_client *gatt = chrc->service->client->gatt; + + if (att_ecode) { + queue_remove(chrc->notify_clients, client); + notify_client_free(client); + return; + } + + if (!bt_gatt_client_is_ready(gatt)) { + if (!chrc->ready_id) + chrc->ready_id = bt_gatt_client_ready_register(gatt, + characteristic_ready, + chrc, NULL); + return; + } + + characteristic_ready(true, 0, chrc); +} + +static void notify_io_destroy(void *data) +{ + struct notify_client *client = data; + + if (queue_remove(client->chrc->notify_clients, client)) + notify_client_unref(client); +} + +static DBusMessage *characteristic_acquire_notify(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct characteristic *chrc = user_data; + struct bt_gatt_client *gatt = chrc->service->client->gatt; + const char *sender = dbus_message_get_sender(msg); + struct notify_client *client; + + if (!gatt) + return btd_error_failed(msg, "Not connected"); + + if (chrc->notify_io) + return btd_error_not_permitted(msg, "Notify acquired"); + + /* Each client can only have one active notify session. */ + if (!queue_isempty(chrc->notify_clients)) + return btd_error_in_progress(msg); + + if (!(chrc->props & BT_GATT_CHRC_PROP_NOTIFY)) + return btd_error_not_supported(msg); + + client = notify_client_create(chrc, sender); + if (!client) + return btd_error_failed(msg, "Failed allocate notify session"); + + client->notify_id = bt_gatt_client_register_notify(gatt, + chrc->value_handle, + register_notify_io_cb, + notify_io_cb, + client, NULL); + if (!client->notify_id) { + notify_client_unref(client); + return btd_error_failed(msg, "Failed to subscribe"); + } + + queue_push_tail(chrc->notify_clients, client); + + chrc->notify_io = new0(struct sock_io, 1); + chrc->notify_io->data = client; + chrc->notify_io->msg = dbus_message_ref(msg); + chrc->notify_io->destroy = notify_io_destroy; + + return NULL; +} + +static DBusMessage *characteristic_start_notify(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct characteristic *chrc = user_data; + struct bt_gatt_client *gatt = chrc->service->client->gatt; + const char *sender = dbus_message_get_sender(msg); + struct async_dbus_op *op; + struct notify_client *client; + + if (chrc->notify_io) + return btd_error_not_permitted(msg, "Notify acquired"); + + if (!(chrc->props & BT_GATT_CHRC_PROP_NOTIFY || + chrc->props & BT_GATT_CHRC_PROP_INDICATE)) + return btd_error_not_supported(msg); + + /* Each client can only have one active notify session. */ + client = queue_find(chrc->notify_clients, match_notify_sender, sender); + if (client) + return client->notify_id ? + g_dbus_create_reply(msg, DBUS_TYPE_INVALID) : + btd_error_in_progress(msg); + + client = notify_client_create(chrc, sender); + if (!client) + return btd_error_failed(msg, "Failed allocate notify session"); + + queue_push_tail(chrc->notify_clients, client); + queue_push_tail(chrc->service->client->all_notify_clients, client); + + /* + * If the device is currently not connected, return success. We will + * automatically try and register all clients when a GATT client becomes + * ready. + */ + if (!gatt) { + DBusMessage *reply; + + reply = g_dbus_create_reply(msg, DBUS_TYPE_INVALID); + if (reply) + return reply; + + /* + * Clean up and respond with an error instead of timing out to + * avoid any ambiguities. + */ + error("Failed to construct D-Bus message reply"); + goto fail; + } + + op = async_dbus_op_new(msg, client); + + client->notify_id = bt_gatt_client_register_notify(gatt, + chrc->value_handle, + register_notify_cb, notify_cb, + op, async_dbus_op_free); + if (client->notify_id) + return NULL; + + async_dbus_op_free(op); + +fail: + queue_remove(chrc->notify_clients, client); + queue_remove(chrc->service->client->all_notify_clients, client); + + /* Directly free the client */ + notify_client_free(client); + + return btd_error_failed(msg, "Failed to register notify session"); +} + +static DBusMessage *characteristic_stop_notify(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct characteristic *chrc = user_data; + struct bt_gatt_client *gatt = chrc->service->client->gatt; + const char *sender = dbus_message_get_sender(msg); + struct notify_client *client; + + if (chrc->notify_io) { + destroy_sock(chrc, chrc->notify_io->io); + return dbus_message_new_method_return(msg); + } + + client = queue_remove_if(chrc->notify_clients, match_notify_sender, + (void *) sender); + if (!client) + return btd_error_failed(msg, "No notify session started"); + + queue_remove(chrc->service->client->all_notify_clients, client); + bt_gatt_client_unregister_notify(gatt, client->notify_id); + update_notifying(chrc); + + notify_client_unref(client); + + return dbus_message_new_method_return(msg); +} + +static const GDBusPropertyTable characteristic_properties[] = { + { "UUID", "s", characteristic_get_uuid, NULL, NULL }, + { "Service", "o", characteristic_get_service, NULL, NULL }, + { "Value", "ay", characteristic_get_value, NULL, + characteristic_value_exists }, + { "Notifying", "b", characteristic_get_notifying, NULL, + characteristic_notifying_exists }, + { "Flags", "as", characteristic_get_flags, NULL, NULL }, + { "WriteAcquired", "b", characteristic_get_write_acquired, NULL, + characteristic_write_acquired_exists }, + { "NotifyAcquired", "b", characteristic_get_notify_acquired, NULL, + characteristic_notify_acquired_exists }, + { } +}; + +static const GDBusMethodTable characteristic_methods[] = { + { GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({ "options", "a{sv}" }), + GDBUS_ARGS({ "value", "ay" }), + characteristic_read_value) }, + { GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({ "value", "ay" }, + { "options", "a{sv}" }), + NULL, + characteristic_write_value) }, + { GDBUS_ASYNC_METHOD("AcquireWrite", + GDBUS_ARGS({ "options", "a{sv}" }), + GDBUS_ARGS({ "fd", "h" }, + { "mtu", "q" }), + characteristic_acquire_write) }, + { GDBUS_ASYNC_METHOD("AcquireNotify", + GDBUS_ARGS({ "options", "a{sv}" }), + GDBUS_ARGS({ "fd", "h" }, + { "mtu", "q" }), + characteristic_acquire_notify) }, + { GDBUS_ASYNC_METHOD("StartNotify", NULL, NULL, + characteristic_start_notify) }, + { GDBUS_METHOD("StopNotify", NULL, NULL, + characteristic_stop_notify) }, + { } +}; + +static void remove_client(void *data) +{ + struct notify_client *ntfy_client = data; + struct btd_gatt_client *client = ntfy_client->chrc->service->client; + + queue_remove(client->all_notify_clients, ntfy_client); + + notify_client_unref(ntfy_client); +} + +static void characteristic_free(void *data) +{ + struct characteristic *chrc = data; + + /* List should be empty here */ + queue_destroy(chrc->descs, NULL); + + if (chrc->write_io) { + queue_remove(chrc->service->client->ios, chrc->write_io->io); + sock_io_destroy(chrc->write_io); + } + + if (chrc->notify_io) { + queue_remove(chrc->service->client->ios, chrc->notify_io->io); + sock_io_destroy(chrc->notify_io); + } + + queue_destroy(chrc->notify_clients, remove_client); + + g_free(chrc->path); + free(chrc); +} + +static struct characteristic *characteristic_create( + struct gatt_db_attribute *attr, + struct service *service) +{ + struct characteristic *chrc; + bt_uuid_t uuid; + + chrc = new0(struct characteristic, 1); + chrc->descs = queue_new(); + chrc->notify_clients = queue_new(); + chrc->service = service; + + gatt_db_attribute_get_char_data(attr, &chrc->handle, + &chrc->value_handle, + &chrc->props, + &chrc->ext_props, + &uuid); + + chrc->attr = gatt_db_get_attribute(service->client->db, + chrc->value_handle); + if (!chrc->attr) { + error("Attribute 0x%04x not found", chrc->value_handle); + characteristic_free(chrc); + return NULL; + } + + bt_uuid_to_uuid128(&uuid, &chrc->uuid); + + chrc->path = g_strdup_printf("%s/char%04x", service->path, + chrc->handle); + + if (!g_dbus_register_interface(btd_get_dbus_connection(), chrc->path, + GATT_CHARACTERISTIC_IFACE, + characteristic_methods, NULL, + characteristic_properties, + chrc, characteristic_free)) { + error("Unable to register GATT characteristic with handle " + "0x%04x", chrc->handle); + characteristic_free(chrc); + + return NULL; + } + + DBG("Exported GATT characteristic: %s", chrc->path); + + return chrc; +} + +static void unregister_characteristic(void *data) +{ + struct characteristic *chrc = data; + struct bt_gatt_client *gatt = chrc->service->client->gatt; + + DBG("Removing GATT characteristic: %s", chrc->path); + + if (chrc->read_op) + bt_gatt_client_cancel(gatt, chrc->read_op->id); + + if (chrc->write_op) + bt_gatt_client_cancel(gatt, chrc->write_op->id); + + queue_remove_all(chrc->descs, NULL, NULL, unregister_descriptor); + + g_dbus_unregister_interface(btd_get_dbus_connection(), chrc->path, + GATT_CHARACTERISTIC_IFACE); +} + +static gboolean service_get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + char uuid[MAX_LEN_UUID_STR + 1]; + const char *ptr = uuid; + struct service *service = data; + + bt_uuid_to_string(&service->uuid, uuid, sizeof(uuid)); + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &ptr); + + return TRUE; +} + +static gboolean service_get_device(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct service *service = data; + const char *str = device_get_path(service->client->device); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &str); + + return TRUE; +} + +static gboolean service_get_primary(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct service *service = data; + dbus_bool_t primary; + + primary = service->primary ? TRUE : FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &primary); + + return TRUE; +} + +static void append_incl_service_path(void *data, void *user_data) +{ + struct service *incl_service = data; + DBusMessageIter *array = user_data; + + dbus_message_iter_append_basic(array, DBUS_TYPE_OBJECT_PATH, + &incl_service->path); +} + +static gboolean service_get_includes(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct service *service = data; + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{o}", &array); + + queue_foreach(service->incl_services, append_incl_service_path, &array); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; + +} + +static const GDBusPropertyTable service_properties[] = { + { "UUID", "s", service_get_uuid }, + { "Device", "o", service_get_device }, + { "Primary", "b", service_get_primary }, + { "Includes", "ao", service_get_includes }, + { } +}; + +static void service_free(void *data) +{ + struct service *service = data; + + queue_destroy(service->chrcs, NULL); /* List should be empty here */ + queue_destroy(service->incl_services, NULL); + g_free(service->path); + free(service); +} + +static struct service *service_create(struct gatt_db_attribute *attr, + struct btd_gatt_client *client) +{ + struct service *service; + const char *device_path = device_get_path(client->device); + bt_uuid_t uuid; + + service = new0(struct service, 1); + service->chrcs = queue_new(); + service->incl_services = queue_new(); + service->client = client; + + gatt_db_attribute_get_service_data(attr, &service->start_handle, + &service->end_handle, + &service->primary, + &uuid); + bt_uuid_to_uuid128(&uuid, &service->uuid); + + service->path = g_strdup_printf("%s/service%04x", device_path, + service->start_handle); + + if (!g_dbus_register_interface(btd_get_dbus_connection(), service->path, + GATT_SERVICE_IFACE, + NULL, NULL, + service_properties, + service, service_free)) { + error("Unable to register GATT service with handle 0x%04x for " + "device %s", + service->start_handle, + client->devaddr); + service_free(service); + + return NULL; + } + + DBG("Exported GATT service: %s", service->path); + + /* Set service active so we can skip discovering next time */ + gatt_db_service_set_active(attr, true); + + /* Mark the service as claimed since it going to be exported */ + gatt_db_service_set_claimed(attr, true); + + return service; +} + +static void on_service_removed(void *data, void *user_data) +{ + struct service *service = data; + struct service *removed_service = user_data; + + if (queue_remove(service->incl_services, removed_service)) + g_dbus_emit_property_changed(btd_get_dbus_connection(), + service->path, + GATT_SERVICE_IFACE, + "Includes"); +} + +static void unregister_service(void *data) +{ + struct service *service = data; + struct btd_gatt_client *client = service->client; + + DBG("Removing GATT service: %s", service->path); + + queue_remove_all(service->chrcs, NULL, NULL, unregister_characteristic); + queue_remove_all(service->incl_services, NULL, NULL, NULL); + + queue_foreach(client->services, on_service_removed, service); + + g_dbus_unregister_interface(btd_get_dbus_connection(), service->path, + GATT_SERVICE_IFACE); +} + +struct export_data { + void *root; + bool failed; +}; + +static void export_desc(struct gatt_db_attribute *attr, void *user_data) +{ + struct descriptor *desc; + struct export_data *data = user_data; + struct characteristic *charac = data->root; + + if (data->failed) + return; + + desc = descriptor_create(attr, charac); + if (!desc) { + data->failed = true; + return; + } + + queue_push_tail(charac->descs, desc); +} + +static bool create_descriptors(struct gatt_db_attribute *attr, + struct characteristic *charac) +{ + struct export_data data; + + data.root = charac; + data.failed = false; + + gatt_db_service_foreach_desc(attr, export_desc, &data); + + return !data.failed; +} + +static void export_char(struct gatt_db_attribute *attr, void *user_data) +{ + struct characteristic *charac; + struct export_data *data = user_data; + struct service *service = data->root; + + if (data->failed) + return; + + charac = characteristic_create(attr, service); + if (!charac) + goto fail; + + if (!create_descriptors(attr, charac)) { + unregister_characteristic(charac); + goto fail; + } + + queue_push_tail(service->chrcs, charac); + + return; + +fail: + data->failed = true; +} + +static bool create_characteristics(struct gatt_db_attribute *attr, + struct service *service) +{ + struct export_data data; + + data.root = service; + data.failed = false; + + gatt_db_service_foreach_char(attr, export_char, &data); + + return !data.failed; +} + +static void export_service(struct gatt_db_attribute *attr, void *user_data) +{ + struct btd_gatt_client *client = user_data; + struct service *service; + + if (gatt_db_service_get_claimed(attr)) + return; + + service = service_create(attr, client); + if (!service) + return; + + if (!create_characteristics(attr, service)) { + error("Exporting characteristics failed"); + unregister_service(service); + return; + } + + queue_push_tail(client->services, service); +} + +static bool match_service_handle(const void *a, const void *b) +{ + const struct service *service = a; + uint16_t start_handle = PTR_TO_UINT(b); + + return service->start_handle == start_handle; +} + +struct update_incl_data { + struct service *service; + bool changed; +}; + +static void update_included_service(struct gatt_db_attribute *attrib, + void *user_data) +{ + struct update_incl_data *update_data = user_data; + struct btd_gatt_client *client = update_data->service->client; + struct service *service = update_data->service; + struct service *incl_service; + uint16_t start_handle; + + gatt_db_attribute_get_incl_data(attrib, NULL, &start_handle, NULL); + + incl_service = queue_find(client->services, match_service_handle, + UINT_TO_PTR(start_handle)); + + if (!incl_service) + return; + + /* Check if service is already on list */ + if (queue_find(service->incl_services, NULL, incl_service)) + return; + + queue_push_tail(service->incl_services, incl_service); + update_data->changed = true; +} + +static void update_included_services(void *data, void *user_data) +{ + struct btd_gatt_client *client = user_data; + struct service *service = data; + struct gatt_db_attribute *attr; + struct update_incl_data inc_data = { + .changed = false, + .service = service, + }; + + attr = gatt_db_get_attribute(client->db, service->start_handle); + gatt_db_service_foreach_incl(attr, update_included_service, &inc_data); + + if (inc_data.changed) + g_dbus_emit_property_changed(btd_get_dbus_connection(), + service->path, + GATT_SERVICE_IFACE, + "Includes"); +} + +static void create_services(struct btd_gatt_client *client) +{ + DBG("Exporting objects for GATT services: %s", client->devaddr); + + gatt_db_foreach_service(client->db, NULL, export_service, client); + + queue_foreach(client->services, update_included_services, client); +} + +struct btd_gatt_client *btd_gatt_client_new(struct btd_device *device) +{ + struct btd_gatt_client *client; + struct gatt_db *db; + + if (!device) + return NULL; + + db = btd_device_get_gatt_db(device); + if (!db) + return NULL; + + client = new0(struct btd_gatt_client, 1); + client->services = queue_new(); + client->all_notify_clients = queue_new(); + client->ios = queue_new(); + client->device = device; + ba2str(device_get_address(device), client->devaddr); + + client->db = gatt_db_ref(db); + + return client; +} + +void btd_gatt_client_destroy(struct btd_gatt_client *client) +{ + if (!client) + return; + + queue_destroy(client->services, unregister_service); + queue_destroy(client->all_notify_clients, NULL); + queue_destroy(client->ios, NULL); + bt_gatt_client_unref(client->gatt); + gatt_db_unref(client->db); + free(client); +} + +static void register_notify(void *data, void *user_data) +{ + struct notify_client *notify_client = data; + struct btd_gatt_client *client = user_data; + struct async_dbus_op *op; + + DBG("Re-register subscribed notification client"); + + op = new0(struct async_dbus_op, 1); + op->data = notify_client; + + notify_client->notify_id = bt_gatt_client_register_notify(client->gatt, + notify_client->chrc->value_handle, + register_notify_cb, notify_cb, + op, async_dbus_op_free); + if (notify_client->notify_id) + return; + + async_dbus_op_free(op); + + DBG("Failed to re-register notification client"); + + queue_remove(notify_client->chrc->notify_clients, notify_client); + queue_remove(client->all_notify_clients, notify_client); + + notify_client_free(notify_client); +} + +void btd_gatt_client_ready(struct btd_gatt_client *client) +{ + if (!client) + return; + + if (!client->gatt) { + struct bt_gatt_client *gatt; + + gatt = btd_device_get_gatt_client(client->device); + client->gatt = bt_gatt_client_clone(gatt); + if (!client->gatt) { + error("GATT client not initialized"); + return; + } + } + + client->ready = true; + + DBG("GATT client ready"); + + create_services(client); +} + +void btd_gatt_client_connected(struct btd_gatt_client *client) +{ + struct bt_gatt_client *gatt; + + gatt = btd_device_get_gatt_client(client->device); + if (!gatt) { + error("GATT client not initialized"); + return; + } + + DBG("Device connected."); + + bt_gatt_client_unref(client->gatt); + client->gatt = bt_gatt_client_clone(gatt); + + /* + * Services have already been created before. Re-enable notifications + * for any pre-registered notification sessions. + */ + queue_foreach(client->all_notify_clients, register_notify, client); +} + +void btd_gatt_client_service_added(struct btd_gatt_client *client, + struct gatt_db_attribute *attrib) +{ + if (!client || !attrib || !client->ready) + return; + + export_service(attrib, client); + + queue_foreach(client->services, update_included_services, client); +} + +void btd_gatt_client_service_removed(struct btd_gatt_client *client, + struct gatt_db_attribute *attrib) +{ + uint16_t start_handle, end_handle; + + if (!client || !attrib || !client->ready) + return; + + gatt_db_attribute_get_service_handles(attrib, &start_handle, + &end_handle); + + DBG("GATT Services Removed - start: 0x%04x, end: 0x%04x", start_handle, + end_handle); + queue_remove_all(client->services, match_service_handle, + UINT_TO_PTR(start_handle), + unregister_service); +} + +static void clear_notify_id(void *data, void *user_data) +{ + struct notify_client *client = data; + + client->notify_id = 0; +} + +static void client_shutdown(void *data) +{ + io_shutdown(data); +} + +void btd_gatt_client_disconnected(struct btd_gatt_client *client) +{ + if (!client || !client->gatt) + return; + + DBG("Device disconnected. Cleaning up."); + + queue_remove_all(client->ios, NULL, NULL, client_shutdown); + + /* + * TODO: Once GATT over BR/EDR is properly supported, we should pass the + * correct bdaddr_type based on the transport over which GATT is being + * done. + */ + queue_foreach(client->all_notify_clients, clear_notify_id, NULL); + + bt_gatt_client_unref(client->gatt); + client->gatt = NULL; +} + +struct foreach_service_data { + btd_gatt_client_service_path_t func; + void *user_data; +}; + +static void client_service_foreach(void *data, void *user_data) +{ + struct service *service = data; + struct foreach_service_data *foreach_data = user_data; + + foreach_data->func(service->path, foreach_data->user_data); +} + +void btd_gatt_client_foreach_service(struct btd_gatt_client *client, + btd_gatt_client_service_path_t func, + void *user_data) +{ + struct foreach_service_data data; + + if (!client) + return; + + data.func = func; + data.user_data = user_data; + + queue_foreach(client->services, client_service_foreach, &data); +} diff --git a/src/gatt-client.h b/src/gatt-client.h new file mode 100644 index 0000000..92a9255 --- /dev/null +++ b/src/gatt-client.h @@ -0,0 +1,37 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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. + * + */ + +struct btd_gatt_client; + +struct btd_gatt_client *btd_gatt_client_new(struct btd_device *device); +void btd_gatt_client_destroy(struct btd_gatt_client *client); + +void btd_gatt_client_ready(struct btd_gatt_client *client); +void btd_gatt_client_connected(struct btd_gatt_client *client); +void btd_gatt_client_service_added(struct btd_gatt_client *client, + struct gatt_db_attribute *attrib); +void btd_gatt_client_service_removed(struct btd_gatt_client *client, + struct gatt_db_attribute *attrib); +void btd_gatt_client_disconnected(struct btd_gatt_client *client); + +typedef void (*btd_gatt_client_service_path_t)(const char *service_path, + void *user_data); +void btd_gatt_client_foreach_service(struct btd_gatt_client *client, + btd_gatt_client_service_path_t func, + void *user_data); diff --git a/src/gatt-database.c b/src/gatt-database.c new file mode 100644 index 0000000..2bae871 --- /dev/null +++ b/src/gatt-database.c @@ -0,0 +1,3712 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Google 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" +#include "btio/btio.h" +#include "gdbus/gdbus.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/io.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-server.h" +#include "log.h" +#include "error.h" +#include "hcid.h" +#include "adapter.h" +#include "device.h" +#include "gatt-database.h" +#include "dbus-common.h" +#include "profile.h" +#include "service.h" + +#ifndef ATT_CID +#define ATT_CID 4 +#endif + +#ifndef ATT_PSM +#define ATT_PSM 31 +#endif + +#define GATT_MANAGER_IFACE "org.bluez.GattManager1" +#define GATT_PROFILE_IFACE "org.bluez.GattProfile1" +#define GATT_SERVICE_IFACE "org.bluez.GattService1" +#define GATT_CHRC_IFACE "org.bluez.GattCharacteristic1" +#define GATT_DESC_IFACE "org.bluez.GattDescriptor1" + +#define UUID_GAP 0x1800 +#define UUID_GATT 0x1801 + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +struct gatt_record { + struct btd_gatt_database *database; + uint32_t handle; + struct gatt_db_attribute *attr; +}; + +struct btd_gatt_database { + struct btd_adapter *adapter; + struct gatt_db *db; + unsigned int db_id; + GIOChannel *le_io; + GIOChannel *l2cap_io; + struct queue *records; + struct queue *device_states; + struct queue *ccc_callbacks; + struct gatt_db_attribute *svc_chngd; + struct gatt_db_attribute *svc_chngd_ccc; + struct gatt_db_attribute *cli_feat; + struct gatt_db_attribute *db_hash; + struct queue *apps; + struct queue *profiles; +}; + +struct gatt_app { + struct btd_gatt_database *database; + char *owner; + char *path; + DBusMessage *reg; + GDBusClient *client; + bool failed; + struct queue *profiles; + struct queue *services; + struct queue *proxies; +}; + +struct external_service { + struct gatt_app *app; + char *path; /* Path to GattService1 */ + GDBusProxy *proxy; + struct gatt_db_attribute *attrib; + uint16_t attr_cnt; + struct queue *chrcs; + struct queue *descs; + struct queue *includes; +}; + +struct external_profile { + struct gatt_app *app; + GDBusProxy *proxy; + struct queue *profiles; /* btd_profile list */ +}; + +struct external_chrc { + struct external_service *service; + char *path; + GDBusProxy *proxy; + uint8_t props; + uint8_t ext_props; + uint32_t perm; + uint16_t mtu; + struct io *write_io; + struct io *notify_io; + struct gatt_db_attribute *attrib; + struct gatt_db_attribute *ccc; + struct queue *pending_reads; + struct queue *pending_writes; + unsigned int ntfy_cnt; + bool prep_authorized; + bool req_prep_authorization; +}; + +struct external_desc { + struct external_service *service; + char *chrc_path; + GDBusProxy *proxy; + uint32_t perm; + struct gatt_db_attribute *attrib; + bool handled; + struct queue *pending_reads; + struct queue *pending_writes; + bool prep_authorized; + bool req_prep_authorization; +}; + +struct pending_op { + struct btd_device *device; + unsigned int id; + uint16_t offset; + uint8_t link_type; + struct gatt_db_attribute *attrib; + struct queue *owner_queue; + struct iovec data; + bool is_characteristic; + bool prep_authorize; +}; + +struct notify { + struct btd_gatt_database *database; + uint16_t handle, ccc_handle; + uint8_t *value; + uint16_t len; + bt_gatt_server_conf_func_t conf; + void *user_data; +}; + +struct device_state { + struct btd_gatt_database *db; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + unsigned int disc_id; + uint8_t cli_feat[1]; + bool change_aware; + bool out_of_sync; + struct queue *ccc_states; + struct notify *pending; +}; + +typedef uint8_t (*btd_gatt_database_ccc_write_t) (struct pending_op *op, + void *user_data); +typedef void (*btd_gatt_database_destroy_t) (void *data); + +struct ccc_state { + uint16_t handle; + uint16_t value; +}; + +struct ccc_cb_data { + uint16_t handle; + btd_gatt_database_ccc_write_t callback; + btd_gatt_database_destroy_t destroy; + void *user_data; +}; + +struct device_info { + bdaddr_t bdaddr; + uint8_t bdaddr_type; +}; + +static void ccc_cb_free(void *data) +{ + struct ccc_cb_data *ccc_cb = data; + + if (ccc_cb->destroy) + ccc_cb->destroy(ccc_cb->user_data); + + free(ccc_cb); +} + +static bool ccc_cb_match_service(const void *data, const void *match_data) +{ + const struct ccc_cb_data *ccc_cb = data; + const struct gatt_db_attribute *attrib = match_data; + uint16_t start, end; + + if (!gatt_db_attribute_get_service_handles(attrib, &start, &end)) + return false; + + return ccc_cb->handle >= start && ccc_cb->handle <= end; +} + +static bool ccc_cb_match_handle(const void *data, const void *match_data) +{ + const struct ccc_cb_data *ccc_cb = data; + uint16_t handle = PTR_TO_UINT(match_data); + + return ccc_cb->handle == handle; +} + +static bool dev_state_match(const void *a, const void *b) +{ + const struct device_state *dev_state = a; + const struct device_info *dev_info = b; + + return bacmp(&dev_state->bdaddr, &dev_info->bdaddr) == 0 && + dev_state->bdaddr_type == dev_info->bdaddr_type; +} + +static struct device_state * +find_device_state(struct btd_gatt_database *database, const bdaddr_t *bdaddr, + uint8_t bdaddr_type) +{ + struct device_info dev_info; + + memset(&dev_info, 0, sizeof(dev_info)); + + bacpy(&dev_info.bdaddr, bdaddr); + dev_info.bdaddr_type = bdaddr_type; + + return queue_find(database->device_states, dev_state_match, &dev_info); +} + +static bool ccc_state_match(const void *a, const void *b) +{ + const struct ccc_state *ccc = a; + uint16_t handle = PTR_TO_UINT(b); + + return ccc->handle == handle; +} + +static struct ccc_state *find_ccc_state(struct device_state *dev_state, + uint16_t handle) +{ + return queue_find(dev_state->ccc_states, ccc_state_match, + UINT_TO_PTR(handle)); +} + +static struct device_state *device_state_create(struct btd_gatt_database *db, + const bdaddr_t *bdaddr, + uint8_t bdaddr_type) +{ + struct device_state *dev_state; + + dev_state = new0(struct device_state, 1); + dev_state->db = db; + dev_state->ccc_states = queue_new(); + bacpy(&dev_state->bdaddr, bdaddr); + dev_state->bdaddr_type = bdaddr_type; + + return dev_state; +} + +static void device_state_free(void *data) +{ + struct device_state *state = data; + + queue_destroy(state->ccc_states, free); + + if (state->pending) { + free(state->pending->value); + free(state->pending); + } + + free(state); +} + +static void clear_ccc_state(void *data, void *user_data) +{ + struct ccc_state *ccc = data; + struct btd_gatt_database *db = user_data; + struct ccc_cb_data *ccc_cb; + + if (!ccc->value) + return; + + ccc_cb = queue_find(db->ccc_callbacks, ccc_cb_match_handle, + UINT_TO_PTR(ccc->handle)); + if (!ccc_cb) + return; + + if (ccc_cb->callback) + ccc_cb->callback(NULL, ccc_cb->user_data); +} + +static void att_disconnected(int err, void *user_data) +{ + struct device_state *state = user_data; + struct btd_device *device; + + DBG(""); + + state->disc_id = 0; + state->out_of_sync = false; + + device = btd_adapter_find_device(state->db->adapter, &state->bdaddr, + state->bdaddr_type); + if (!device) + goto remove; + + if (device_is_bonded(device, state->bdaddr_type)) { + struct ccc_state *ccc; + uint16_t handle; + + handle = gatt_db_attribute_get_handle(state->db->svc_chngd_ccc); + + ccc = find_ccc_state(state, handle); + if (ccc) + device_store_svc_chng_ccc(device, state->bdaddr_type, + ccc->value); + + return; + } + +remove: + /* Remove device state if device no longer exists or is not paired */ + if (queue_remove(state->db->device_states, state)) { + queue_foreach(state->ccc_states, clear_ccc_state, state->db); + device_state_free(state); + } +} + +static bool get_dst_info(struct bt_att *att, bdaddr_t *dst, uint8_t *dst_type) +{ + GIOChannel *io = NULL; + GError *gerr = NULL; + int fd; + + fd = bt_att_get_fd(att); + if (fd < 0) + return false; + + io = g_io_channel_unix_new(fd); + if (!io) + return false; + + bt_io_get(io, &gerr, BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_DEST_TYPE, dst_type, + BT_IO_OPT_INVALID); + if (gerr) { + error("gatt: bt_io_get: %s", gerr->message); + g_error_free(gerr); + g_io_channel_unref(io); + return false; + } + + g_io_channel_unref(io); + return true; +} + +static struct device_state *get_device_state(struct btd_gatt_database *database, + struct bt_att *att) +{ + struct device_state *dev_state; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + + if (!get_dst_info(att, &bdaddr, &bdaddr_type)) + return NULL; + + /* + * Find and return a device state. If a matching state doesn't exist, + * then create a new one. + */ + dev_state = find_device_state(database, &bdaddr, bdaddr_type); + if (dev_state) + goto done; + + dev_state = device_state_create(database, &bdaddr, bdaddr_type); + + queue_push_tail(database->device_states, dev_state); + +done: + if (!dev_state->disc_id) + dev_state->disc_id = bt_att_register_disconnect(att, + att_disconnected, + dev_state, NULL); + + return dev_state; +} + +static struct ccc_state *get_ccc_state(struct btd_gatt_database *database, + struct bt_att *att, uint16_t handle) +{ + struct device_state *dev_state; + struct ccc_state *ccc; + + dev_state = get_device_state(database, att); + if (!dev_state) + return NULL; + + ccc = find_ccc_state(dev_state, handle); + if (ccc) + return ccc; + + ccc = new0(struct ccc_state, 1); + ccc->handle = handle; + queue_push_tail(dev_state->ccc_states, ccc); + + return ccc; +} + +static void cancel_pending_read(void *data) +{ + struct pending_op *op = data; + + gatt_db_attribute_read_result(op->attrib, op->id, + BT_ATT_ERROR_REQUEST_NOT_SUPPORTED, + NULL, 0); + op->owner_queue = NULL; +} + +static void cancel_pending_write(void *data) +{ + struct pending_op *op = data; + + gatt_db_attribute_write_result(op->attrib, op->id, + BT_ATT_ERROR_REQUEST_NOT_SUPPORTED); + op->owner_queue = NULL; +} + +static void chrc_free(void *data) +{ + struct external_chrc *chrc = data; + + io_destroy(chrc->write_io); + io_destroy(chrc->notify_io); + + queue_destroy(chrc->pending_reads, cancel_pending_read); + queue_destroy(chrc->pending_writes, cancel_pending_write); + + g_free(chrc->path); + + g_dbus_proxy_set_property_watch(chrc->proxy, NULL, NULL); + g_dbus_proxy_unref(chrc->proxy); + + free(chrc); +} + +static void desc_free(void *data) +{ + struct external_desc *desc = data; + + queue_destroy(desc->pending_reads, cancel_pending_read); + queue_destroy(desc->pending_writes, cancel_pending_write); + + g_dbus_proxy_unref(desc->proxy); + g_free(desc->chrc_path); + + free(desc); +} + +static void inc_free(void *data) +{ + struct external_desc *inc = data; + + free(inc); +} + +static void service_free(void *data) +{ + struct external_service *service = data; + + queue_destroy(service->chrcs, chrc_free); + queue_destroy(service->descs, desc_free); + queue_destroy(service->includes, inc_free); + + if (service->attrib) + gatt_db_remove_service(service->app->database->db, + service->attrib); + + if (service->app->client) + g_dbus_proxy_unref(service->proxy); + + g_free(service->path); + + free(service); +} + +static void profile_remove(void *data) +{ + struct btd_profile *p = data; + + DBG("Removed \"%s\"", p->name); + + adapter_foreach(adapter_remove_profile, p); + btd_profile_unregister(p); + + g_free((void *) p->name); + g_free((void *) p->remote_uuid); + + free(p); +} + +static void profile_release(struct external_profile *profile) +{ + DBG("Releasing \"%s\"", profile->app->owner); + + g_dbus_proxy_method_call(profile->proxy, "Release", NULL, NULL, NULL, + NULL); +} + +static void profile_free(void *data) +{ + struct external_profile *profile = data; + + queue_destroy(profile->profiles, profile_remove); + + profile_release(profile); + + g_dbus_proxy_unref(profile->proxy); + + free(profile); +} + +static void app_free(void *data) +{ + struct gatt_app *app = data; + + queue_destroy(app->profiles, profile_free); + queue_destroy(app->services, service_free); + queue_destroy(app->proxies, NULL); + + if (app->client) { + g_dbus_client_set_disconnect_watch(app->client, NULL, NULL); + g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, + NULL, NULL); + g_dbus_client_set_ready_watch(app->client, NULL, NULL); + g_dbus_client_unref(app->client); + } + + if (app->reg) + dbus_message_unref(app->reg); + + g_free(app->owner); + g_free(app->path); + + free(app); +} + +static void gatt_record_free(void *data) +{ + struct gatt_record *rec = data; + + adapter_service_remove(rec->database->adapter, rec->handle); + free(rec); +} + +static void gatt_database_free(void *data) +{ + struct btd_gatt_database *database = data; + + if (database->le_io) { + g_io_channel_shutdown(database->le_io, FALSE, NULL); + g_io_channel_unref(database->le_io); + } + + if (database->l2cap_io) { + g_io_channel_shutdown(database->l2cap_io, FALSE, NULL); + g_io_channel_unref(database->l2cap_io); + } + + /* TODO: Persistently store CCC states before freeing them */ + gatt_db_unregister(database->db, database->db_id); + + queue_destroy(database->records, gatt_record_free); + queue_destroy(database->device_states, device_state_free); + queue_destroy(database->apps, app_free); + queue_destroy(database->profiles, profile_free); + queue_destroy(database->ccc_callbacks, ccc_cb_free); + database->device_states = NULL; + database->ccc_callbacks = NULL; + + gatt_db_unref(database->db); + + btd_adapter_unref(database->adapter); + free(database); +} + +static void connect_cb(GIOChannel *io, GError *gerr, gpointer user_data) +{ + struct btd_adapter *adapter; + struct btd_device *device; + uint8_t dst_type; + bdaddr_t src, dst; + + if (gerr) { + error("%s", gerr->message); + return; + } + + bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST_TYPE, &dst_type, + BT_IO_OPT_INVALID); + if (gerr) { + error("bt_io_get: %s", gerr->message); + g_error_free(gerr); + return; + } + + DBG("New incoming %s ATT connection", dst_type == BDADDR_BREDR ? + "BR/EDR" : "LE"); + + adapter = adapter_find(&src); + if (!adapter) + return; + + device = btd_adapter_get_device(adapter, &dst, dst_type); + if (!device) + return; + + device_attach_att(device, io); +} + +static void gap_device_name_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + uint8_t error = 0; + size_t len = 0; + const uint8_t *value = NULL; + const char *device_name; + + DBG("GAP Device Name read request\n"); + + device_name = btd_adapter_get_name(database->adapter); + len = strlen(device_name); + + if (offset > len) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + len -= offset; + value = len ? (const uint8_t *) &device_name[offset] : NULL; + +done: + gatt_db_attribute_read_result(attrib, id, error, value, len); +} + +static void gap_appearance_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + uint8_t error = 0; + size_t len = 2; + const uint8_t *value = NULL; + uint8_t appearance[2]; + uint32_t dev_class; + + DBG("GAP Appearance read request\n"); + + dev_class = btd_adapter_get_class(database->adapter); + + if (offset > 2) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + appearance[0] = dev_class & 0x00ff; + appearance[1] = (dev_class >> 8) & 0x001f; + + len -= offset; + value = len ? &appearance[offset] : NULL; + +done: + gatt_db_attribute_read_result(attrib, id, error, value, len); +} + +static sdp_record_t *record_new(uuid_t *uuid, uint16_t start, uint16_t end) +{ + sdp_list_t *svclass_id, *apseq, *proto[2], *root, *aproto; + uuid_t root_uuid, proto_uuid, l2cap; + sdp_record_t *record; + sdp_data_t *psm, *sh, *eh; + uint16_t lp = ATT_PSM; + + if (uuid == NULL) + return NULL; + + if (start > end) + return NULL; + + record = sdp_record_alloc(); + if (record == NULL) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + sdp_list_free(root, NULL); + + svclass_id = sdp_list_append(NULL, uuid); + sdp_set_service_classes(record, svclass_id); + sdp_list_free(svclass_id, NULL); + + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(NULL, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(NULL, proto[0]); + + sdp_uuid16_create(&proto_uuid, ATT_UUID); + proto[1] = sdp_list_append(NULL, &proto_uuid); + sh = sdp_data_alloc(SDP_UINT16, &start); + proto[1] = sdp_list_append(proto[1], sh); + eh = sdp_data_alloc(SDP_UINT16, &end); + proto[1] = sdp_list_append(proto[1], eh); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(NULL, apseq); + sdp_set_access_protos(record, aproto); + + sdp_data_free(psm); + sdp_data_free(sh); + sdp_data_free(eh); + sdp_list_free(proto[0], NULL); + sdp_list_free(proto[1], NULL); + sdp_list_free(apseq, NULL); + sdp_list_free(aproto, NULL); + + return record; +} + +static void database_add_record(struct btd_gatt_database *database, + struct gatt_db_attribute *attr) +{ + struct gatt_record *rec; + sdp_record_t *record; + uint16_t start, end; + uuid_t svc, gap_uuid; + bt_uuid_t uuid; + const char *name = NULL; + char uuidstr[MAX_LEN_UUID_STR]; + + gatt_db_attribute_get_service_uuid(attr, &uuid); + + switch (uuid.type) { + case BT_UUID16: + name = bt_uuid16_to_str(uuid.value.u16); + sdp_uuid16_create(&svc, uuid.value.u16); + break; + case BT_UUID32: + name = bt_uuid32_to_str(uuid.value.u32); + sdp_uuid32_create(&svc, uuid.value.u32); + break; + case BT_UUID128: + bt_uuid_to_string(&uuid, uuidstr, sizeof(uuidstr)); + name = bt_uuidstr_to_str(uuidstr); + sdp_uuid128_create(&svc, (void *) &uuid.value.u128); + break; + case BT_UUID_UNSPEC: + return; + } + + gatt_db_attribute_get_service_handles(attr, &start, &end); + + record = record_new(&svc, start, end); + if (!record) + return; + + if (name != NULL) + sdp_set_info_attr(record, name, "BlueZ", NULL); + + sdp_uuid16_create(&gap_uuid, UUID_GAP); + if (sdp_uuid_cmp(&svc, &gap_uuid) == 0) { + sdp_set_url_attr(record, "http://www.bluez.org/", + "http://www.bluez.org/", + "http://www.bluez.org/"); + } + + if (adapter_service_add(database->adapter, record) < 0) { + sdp_record_free(record); + return; + } + + rec = new0(struct gatt_record, 1); + rec->database = database; + rec->handle = record->handle; + rec->attr = attr; + queue_push_tail(database->records, rec); +} + +static void populate_gap_service(struct btd_gatt_database *database) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service; + + /* Add the GAP service */ + bt_uuid16_create(&uuid, UUID_GAP); + service = gatt_db_add_service(database->db, &uuid, true, 5); + + /* + * Device Name characteristic. + */ + bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); + gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + gap_device_name_read_cb, + NULL, database); + + /* + * Device Appearance characteristic. + */ + bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE); + gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + gap_appearance_read_cb, + NULL, database); + + gatt_db_service_set_active(service, true); + + database_add_record(database, service); +} + +static void gatt_ccc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + struct ccc_state *ccc; + uint16_t handle; + uint8_t ecode = 0; + const uint8_t *value = NULL; + size_t len = 0; + + handle = gatt_db_attribute_get_handle(attrib); + + DBG("CCC read called for handle: 0x%04x", handle); + + if (offset) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + ccc = get_ccc_state(database, att, handle); + if (!ccc) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + len = sizeof(ccc->value); + value = (void *) &ccc->value; + +done: + gatt_db_attribute_read_result(attrib, id, ecode, value, len); +} + +static struct btd_device *att_get_device(struct bt_att *att) +{ + GIOChannel *io = NULL; + GError *gerr = NULL; + bdaddr_t src, dst; + uint8_t dst_type; + struct btd_adapter *adapter; + + io = g_io_channel_unix_new(bt_att_get_fd(att)); + if (!io) + return NULL; + + bt_io_get(io, &gerr, BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST_TYPE, &dst_type, + BT_IO_OPT_INVALID); + if (gerr) { + error("bt_io_get: %s", gerr->message); + g_error_free(gerr); + g_io_channel_unref(io); + return NULL; + } + + g_io_channel_unref(io); + + adapter = adapter_find(&src); + if (!adapter) { + error("Unable to find adapter object"); + return NULL; + } + + return btd_adapter_find_device(adapter, &dst, dst_type); +} + +static struct pending_op *pending_ccc_new(struct bt_att *att, + struct gatt_db_attribute *attrib, + uint16_t value, + uint8_t link_type) +{ + struct pending_op *op; + struct btd_device *device; + + device = att_get_device(att); + if (!device) { + error("Unable to find device object"); + return NULL; + } + + op = new0(struct pending_op, 1); + + op->data.iov_base = UINT_TO_PTR(value); + op->data.iov_len = sizeof(value); + + op->device = device; + op->attrib = attrib; + op->link_type = link_type; + + return op; +} + +static void pending_op_free(void *data) +{ + struct pending_op *op = data; + + if (op->owner_queue) + queue_remove(op->owner_queue, op); + + free(op); +} + +static void gatt_ccc_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + struct ccc_state *ccc; + struct ccc_cb_data *ccc_cb; + uint16_t handle, val; + uint8_t ecode = 0; + + handle = gatt_db_attribute_get_handle(attrib); + + DBG("CCC write called for handle: 0x%04x", handle); + + if (!value || len > 2) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset > 2) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + ccc = get_ccc_state(database, att, handle); + if (!ccc) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + ccc_cb = queue_find(database->ccc_callbacks, ccc_cb_match_handle, + UINT_TO_PTR(gatt_db_attribute_get_handle(attrib))); + if (!ccc_cb) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + if (len == 1) + val = *value; + else + val = get_le16(value); + + /* If value is identical, then just succeed */ + if (val == ccc->value) + goto done; + + if (ccc_cb->callback) { + struct pending_op *op; + + op = pending_ccc_new(att, attrib, get_le16(value), + bt_att_get_link_type(att)); + if (!op) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + ecode = ccc_cb->callback(op, ccc_cb->user_data); + if (ecode) + pending_op_free(op); + } + + if (!ecode) + ccc->value = val; + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +static struct gatt_db_attribute * +service_add_ccc(struct gatt_db_attribute *service, + struct btd_gatt_database *database, + btd_gatt_database_ccc_write_t write_callback, + void *user_data, + btd_gatt_database_destroy_t destroy) +{ + struct gatt_db_attribute *ccc; + struct ccc_cb_data *ccc_cb; + bt_uuid_t uuid; + + ccc_cb = new0(struct ccc_cb_data, 1); + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + ccc = gatt_db_service_add_descriptor(service, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + gatt_ccc_read_cb, gatt_ccc_write_cb, database); + if (!ccc) { + error("Failed to create CCC entry in database"); + free(ccc_cb); + return NULL; + } + + ccc_cb->handle = gatt_db_attribute_get_handle(ccc); + ccc_cb->callback = write_callback; + ccc_cb->destroy = destroy; + ccc_cb->user_data = user_data; + + queue_push_tail(database->ccc_callbacks, ccc_cb); + + return ccc; +} + +static void cli_feat_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + struct device_state *state; + uint8_t ecode = 0; + const uint8_t *value = NULL; + size_t len = 0; + + DBG("Client Features read"); + + state = get_device_state(database, att); + if (!state) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + len = sizeof(state->cli_feat) - offset; + value = len ? &state->cli_feat[offset] : NULL; + +done: + gatt_db_attribute_read_result(attrib, id, ecode, value, len); +} + +static void cli_feat_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + struct device_state *state; + uint8_t ecode = 0; + + DBG("Client Features write"); + + state = get_device_state(database, att); + if (!state) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + if (!value || !len) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + /* A client shall never clear a bit it has set. + * TODO: make it generic to any bits. + */ + if (state->cli_feat[0] & BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING && + !(value[0] & BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING)) { + ecode = BT_ATT_ERROR_VALUE_NOT_ALLOWED; + goto done; + } + + /* Shall we reallocate the feat array if bigger? */ + len = MIN(sizeof(state->cli_feat), len); + while (len) { + state->cli_feat[len - 1] |= value[len - 1]; + len--; + } + + state->cli_feat[0] &= BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING; + state->change_aware = true; + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +static void db_hash_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + const uint8_t *hash; + struct device_state *state; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + + DBG("Database Hash read"); + + hash = gatt_db_get_hash(database->db); + + gatt_db_attribute_read_result(attrib, id, 0, hash, 16); + + if (!get_dst_info(att, &bdaddr, &bdaddr_type)) + return; + + state = find_device_state(database, &bdaddr, bdaddr_type); + if (state) + state->change_aware = true; +} + +static void populate_gatt_service(struct btd_gatt_database *database) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service; + + /* Add the GATT service */ + bt_uuid16_create(&uuid, UUID_GATT); + service = gatt_db_add_service(database->db, &uuid, true, 8); + + bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED); + database->svc_chngd = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_INDICATE, + NULL, NULL, database); + + database->svc_chngd_ccc = service_add_ccc(service, database, NULL, NULL, + NULL); + + bt_uuid16_create(&uuid, GATT_CHARAC_CLI_FEAT); + database->cli_feat = gatt_db_service_add_characteristic(service, + &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_WRITE, + cli_feat_read_cb, cli_feat_write_cb, + database); + + bt_uuid16_create(&uuid, GATT_CHARAC_DB_HASH); + database->db_hash = gatt_db_service_add_characteristic(service, + &uuid, BT_ATT_PERM_READ, BT_GATT_CHRC_PROP_READ, + db_hash_read_cb, NULL, database); + + gatt_db_service_set_active(service, true); + + database_add_record(database, service); +} + + +static void register_core_services(struct btd_gatt_database *database) +{ + populate_gap_service(database); + populate_gatt_service(database); +} + +static void conf_cb(void *user_data) +{ + GDBusProxy *proxy = user_data; + DBG("GATT server received confirmation"); + + if (proxy != NULL) + { + g_dbus_proxy_method_call(proxy, "Confirm", NULL, NULL, NULL, NULL); + } +} + +static void service_changed_conf(void *user_data) +{ + struct device_state *state = user_data; + + DBG(""); + + if (!state) + return; + + state->change_aware = true; +} + +static void state_set_pending(struct device_state *state, struct notify *notify) +{ + uint16_t start, end, old_start, old_end; + + /* Cache this only for Service Changed */ + if (notify->conf != service_changed_conf) + return; + + if (state->pending) { + old_start = get_le16(state->pending->value); + old_end = get_le16(state->pending->value + 2); + + start = get_le16(notify->value); + end = get_le16(notify->value + 2); + + if (start < old_start) + put_le16(start, state->pending->value); + + if (end > old_end) + put_le16(end, state->pending->value + 2); + + return; + } + + /* Copy notify contents to pending */ + state->pending = new0(struct notify, 1); + memcpy(state->pending, notify, sizeof(*notify)); + state->pending->value = malloc(notify->len); + memcpy(state->pending->value, notify->value, notify->len); +} + +static void send_notification_to_device(void *data, void *user_data) +{ + struct device_state *device_state = data; + struct notify *notify = user_data; + struct ccc_state *ccc; + struct btd_device *device; + struct bt_gatt_server *server; + + if (notify->conf == service_changed_conf) { + if (device_state->cli_feat[0] & + BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING) { + device_state->change_aware = false; + notify->user_data = device_state; + } + } + + ccc = find_ccc_state(device_state, notify->ccc_handle); + if (!ccc) + return; + + if (!ccc->value || (notify->conf && !(ccc->value & 0x0002))) + return; + + device = btd_adapter_get_device(notify->database->adapter, + &device_state->bdaddr, + device_state->bdaddr_type); + if (!device) + goto remove; + + server = btd_device_get_gatt_server(device); + if (!server) { + if (!device_is_bonded(device, device_state->bdaddr_type)) + goto remove; + state_set_pending(device_state, notify); + return; + } + + /* + * TODO: If the device is not connected but bonded, send the + * notification/indication when it becomes connected. + */ + if (!notify->conf) { + DBG("GATT server sending notification"); + bt_gatt_server_send_notification(server, + notify->handle, notify->value, + notify->len); + return; + } + + DBG("GATT server sending indication"); + bt_gatt_server_send_indication(server, notify->handle, notify->value, + notify->len, notify->conf, + notify->user_data, NULL); + + return; + +remove: + /* Remove device state if device no longer exists or is not paired */ + if (queue_remove(notify->database->device_states, device_state)) { + queue_foreach(device_state->ccc_states, clear_ccc_state, + notify->database); + device_state_free(device_state); + } +} + +static void send_notification_to_devices(struct btd_gatt_database *database, + uint16_t handle, uint8_t *value, + uint16_t len, uint16_t ccc_handle, + bt_gatt_server_conf_func_t conf, + void *user_data) +{ + struct notify notify; + + memset(¬ify, 0, sizeof(notify)); + + notify.database = database; + notify.handle = handle; + notify.ccc_handle = ccc_handle; + notify.value = value; + notify.len = len; + notify.conf = conf; + notify.user_data = user_data; + + queue_foreach(database->device_states, send_notification_to_device, + ¬ify); +} + +static void send_service_changed(struct btd_gatt_database *database, + struct gatt_db_attribute *attrib) +{ + uint16_t start, end; + uint8_t value[4]; + uint16_t handle, ccc_handle; + + if (!gatt_db_attribute_get_service_handles(attrib, &start, &end)) { + error("Failed to obtain changed service handles"); + return; + } + + handle = gatt_db_attribute_get_handle(database->svc_chngd); + ccc_handle = gatt_db_attribute_get_handle(database->svc_chngd_ccc); + + if (!handle || !ccc_handle) { + error("Failed to obtain handles for \"Service Changed\"" + " characteristic"); + return; + } + + put_le16(start, value); + put_le16(end, value + 2); + + send_notification_to_devices(database, handle, value, sizeof(value), + ccc_handle, service_changed_conf, NULL); +} + +static void gatt_db_service_added(struct gatt_db_attribute *attrib, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + + DBG("GATT Service added to local database"); + + database_add_record(database, attrib); + + send_service_changed(database, attrib); +} + +static bool ccc_match_service(const void *data, const void *match_data) +{ + const struct ccc_state *ccc = data; + const struct gatt_db_attribute *attrib = match_data; + uint16_t start, end; + + if (!gatt_db_attribute_get_service_handles(attrib, &start, &end)) + return false; + + return ccc->handle >= start && ccc->handle <= end; +} + +static void remove_device_ccc(void *data, void *user_data) +{ + struct device_state *state = data; + + queue_remove_all(state->ccc_states, ccc_match_service, user_data, free); +} + +static bool match_gatt_record(const void *data, const void *user_data) +{ + const struct gatt_record *rec = data; + const struct gatt_db_attribute *attr = user_data; + + return (rec->attr == attr); +} + +static void gatt_db_service_removed(struct gatt_db_attribute *attrib, + void *user_data) +{ + struct btd_gatt_database *database = user_data; + struct gatt_record *rec; + + DBG("Local GATT service removed"); + + rec = queue_remove_if(database->records, match_gatt_record, attrib); + if (rec) + gatt_record_free(rec); + + send_service_changed(database, attrib); + + queue_foreach(database->device_states, remove_device_ccc, attrib); + queue_remove_all(database->ccc_callbacks, ccc_cb_match_service, attrib, + ccc_cb_free); +} + +struct svc_match_data { + const char *path; + const char *sender; +}; + +static bool match_app(const void *a, const void *b) +{ + const struct gatt_app *app = a; + const struct svc_match_data *data = b; + + return g_strcmp0(app->path, data->path) == 0 && + g_strcmp0(app->owner, data->sender) == 0; +} + +static gboolean app_free_idle_cb(void *data) +{ + app_free(data); + + return FALSE; +} + +static void client_disconnect_cb(DBusConnection *conn, void *user_data) +{ + struct gatt_app *app = user_data; + struct btd_gatt_database *database = app->database; + + DBG("Client disconnected"); + + if (queue_remove(database->apps, app)) + app_free(app); +} + +static void remove_app(void *data) +{ + struct gatt_app *app = data; + + /* + * Set callback to NULL to avoid potential race condition + * when calling remove_app and GDBusClient unref. + */ + g_dbus_client_set_disconnect_watch(app->client, NULL, NULL); + + /* + * Set proxy handlers to NULL, so that this gets called only once when + * the first proxy that belongs to this service gets removed. + */ + g_dbus_client_set_proxy_handlers(app->client, NULL, NULL, NULL, NULL); + + + queue_remove(app->database->apps, app); + + /* + * Do not run in the same loop, this may be a disconnect + * watch call and GDBusClient should not be destroyed. + */ + g_idle_add(app_free_idle_cb, app); +} + +static bool match_service_by_path(const void *a, const void *b) +{ + const struct external_service *service = a; + const char *path = b; + + return strcmp(service->path, path) == 0; +} + +static bool parse_path(GDBusProxy *proxy, const char *name, const char **path) +{ + DBusMessageIter iter; + + if (!g_dbus_proxy_get_property(proxy, name, &iter)) + return false; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) + return false; + + dbus_message_iter_get_basic(&iter, path); + + return true; +} + +static bool incr_attr_count(struct external_service *service, uint16_t incr) +{ + if (service->attr_cnt > UINT16_MAX - incr) + return false; + + service->attr_cnt += incr; + + return true; +} + +static bool parse_chrc_flags(DBusMessageIter *array, uint8_t *props, + uint8_t *ext_props, uint32_t *perm, + bool *req_prep_authorization) +{ + const char *flag; + + *props = *ext_props = 0; + + do { + if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_STRING) + return false; + + dbus_message_iter_get_basic(array, &flag); + + if (!strcmp("broadcast", flag)) + *props |= BT_GATT_CHRC_PROP_BROADCAST; + else if (!strcmp("read", flag)) { + *props |= BT_GATT_CHRC_PROP_READ; + *perm |= BT_ATT_PERM_READ; + } else if (!strcmp("write-without-response", flag)) { + *props |= BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP; + *perm |= BT_ATT_PERM_WRITE; + } else if (!strcmp("write", flag)) { + *props |= BT_GATT_CHRC_PROP_WRITE; + *perm |= BT_ATT_PERM_WRITE; + } else if (!strcmp("notify", flag)) { + *props |= BT_GATT_CHRC_PROP_NOTIFY; + } else if (!strcmp("indicate", flag)) { + *props |= BT_GATT_CHRC_PROP_INDICATE; + } else if (!strcmp("authenticated-signed-writes", flag)) { + *props |= BT_GATT_CHRC_PROP_AUTH; + *perm |= BT_ATT_PERM_WRITE; + } else if (!strcmp("extended-properties", flag)) { + *props |= BT_GATT_CHRC_PROP_EXT_PROP; + } else if (!strcmp("reliable-write", flag)) { + *ext_props |= BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE; + *perm |= BT_ATT_PERM_WRITE; + } else if (!strcmp("writable-auxiliaries", flag)) { + *ext_props |= BT_GATT_CHRC_EXT_PROP_WRITABLE_AUX; + } else if (!strcmp("encrypt-read", flag)) { + *props |= BT_GATT_CHRC_PROP_READ; + *ext_props |= BT_GATT_CHRC_EXT_PROP_ENC_READ; + *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT; + } else if (!strcmp("encrypt-write", flag)) { + *props |= BT_GATT_CHRC_PROP_WRITE; + *ext_props |= BT_GATT_CHRC_EXT_PROP_ENC_WRITE; + *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_ENCRYPT; + } else if (!strcmp("encrypt-authenticated-read", flag)) { + *props |= BT_GATT_CHRC_PROP_READ; + *ext_props |= BT_GATT_CHRC_EXT_PROP_AUTH_READ; + *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_AUTHEN; + } else if (!strcmp("encrypt-authenticated-write", flag)) { + *props |= BT_GATT_CHRC_PROP_WRITE; + *ext_props |= BT_GATT_CHRC_EXT_PROP_AUTH_WRITE; + *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_AUTHEN; + } else if (!strcmp("secure-read", flag)) { + *props |= BT_GATT_CHRC_PROP_READ; + *ext_props |= BT_GATT_CHRC_EXT_PROP_AUTH_READ; + *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_SECURE; + } else if (!strcmp("secure-write", flag)) { + *props |= BT_GATT_CHRC_PROP_WRITE; + *ext_props |= BT_GATT_CHRC_EXT_PROP_AUTH_WRITE; + *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_SECURE; + } else if (!strcmp("authorize", flag)) { + *req_prep_authorization = true; + } else { + error("Invalid characteristic flag: %s", flag); + return false; + } + } while (dbus_message_iter_next(array)); + + if (*ext_props) + *props |= BT_GATT_CHRC_PROP_EXT_PROP; + + return true; +} + +static bool parse_desc_flags(DBusMessageIter *array, uint32_t *perm, + bool *req_prep_authorization) +{ + const char *flag; + + *perm = 0; + + do { + if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_STRING) + return false; + + dbus_message_iter_get_basic(array, &flag); + + if (!strcmp("read", flag)) + *perm |= BT_ATT_PERM_READ; + else if (!strcmp("write", flag)) + *perm |= BT_ATT_PERM_WRITE; + else if (!strcmp("encrypt-read", flag)) + *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_ENCRYPT; + else if (!strcmp("encrypt-write", flag)) + *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_ENCRYPT; + else if (!strcmp("encrypt-authenticated-read", flag)) + *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_AUTHEN; + else if (!strcmp("encrypt-authenticated-write", flag)) + *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_AUTHEN; + else if (!strcmp("secure-read", flag)) + *perm |= BT_ATT_PERM_READ | BT_ATT_PERM_READ_SECURE; + else if (!strcmp("secure-write", flag)) + *perm |= BT_ATT_PERM_WRITE | BT_ATT_PERM_WRITE_SECURE; + else if (!strcmp("authorize", flag)) + *req_prep_authorization = true; + else { + error("Invalid descriptor flag: %s", flag); + return false; + } + } while (dbus_message_iter_next(array)); + + return true; +} + +static bool parse_flags(GDBusProxy *proxy, uint8_t *props, uint8_t *ext_props, + uint32_t *perm, bool *req_prep_authorization) +{ + DBusMessageIter iter, array; + const char *iface; + + if (!g_dbus_proxy_get_property(proxy, "Flags", &iter)) + return false; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + return false; + + dbus_message_iter_recurse(&iter, &array); + + iface = g_dbus_proxy_get_interface(proxy); + if (!strcmp(iface, GATT_DESC_IFACE)) + return parse_desc_flags(&array, perm, req_prep_authorization); + + return parse_chrc_flags(&array, props, ext_props, perm, + req_prep_authorization); +} + +static struct external_chrc *chrc_create(struct gatt_app *app, + GDBusProxy *proxy, + const char *path) +{ + struct external_service *service; + struct external_chrc *chrc; + const char *service_path; + + if (!parse_path(proxy, "Service", &service_path)) { + error("Failed to obtain service path for characteristic"); + return NULL; + } + + service = queue_find(app->services, match_service_by_path, + service_path); + if (!service) { + error("Unable to find service for characteristic: %s", path); + return NULL; + } + + chrc = new0(struct external_chrc, 1); + chrc->pending_reads = queue_new(); + chrc->pending_writes = queue_new(); + + chrc->path = g_strdup(path); + if (!chrc->path) + goto fail; + + chrc->service = service; + chrc->proxy = g_dbus_proxy_ref(proxy); + + /* + * Add 2 for the characteristic declaration and the value + * attribute. + */ + if (!incr_attr_count(chrc->service, 2)) { + error("Failed to increment attribute count"); + goto fail; + } + + /* + * Parse characteristic flags (i.e. properties) here since they + * are used to determine if any special descriptors should be + * created. + */ + if (!parse_flags(proxy, &chrc->props, &chrc->ext_props, &chrc->perm, + &chrc->req_prep_authorization)) { + error("Failed to parse characteristic properties"); + goto fail; + } + + if ((chrc->props & BT_GATT_CHRC_PROP_NOTIFY || + chrc->props & BT_GATT_CHRC_PROP_INDICATE) && + !incr_attr_count(chrc->service, 1)) { + error("Failed to increment attribute count for CCC"); + goto fail; + } + + if (chrc->ext_props && !incr_attr_count(chrc->service, 1)) { + error("Failed to increment attribute count for CEP"); + goto fail; + } + + queue_push_tail(chrc->service->chrcs, chrc); + + return chrc; + +fail: + chrc_free(chrc); + return NULL; +} + +static bool match_chrc(const void *a, const void *b) +{ + const struct external_chrc *chrc = a; + const char *path = b; + + return strcmp(chrc->path, path) == 0; +} + +static bool match_service_by_chrc(const void *a, const void *b) +{ + const struct external_service *service = a; + const char *path = b; + + return queue_find(service->chrcs, match_chrc, path); +} + +static struct external_desc *desc_create(struct gatt_app *app, + GDBusProxy *proxy) +{ + struct external_service *service; + struct external_desc *desc; + const char *chrc_path; + + if (!parse_path(proxy, "Characteristic", &chrc_path)) { + error("Failed to obtain characteristic path for descriptor"); + return NULL; + } + + service = queue_find(app->services, match_service_by_chrc, chrc_path); + if (!service) { + error("Unable to find service for characteristic: %s", + chrc_path); + return NULL; + } + + desc = new0(struct external_desc, 1); + desc->pending_reads = queue_new(); + desc->pending_writes = queue_new(); + + desc->chrc_path = g_strdup(chrc_path); + if (!desc->chrc_path) + goto fail; + + desc->service = service; + desc->proxy = g_dbus_proxy_ref(proxy); + + /* Add 1 for the descriptor attribute */ + if (!incr_attr_count(desc->service, 1)) { + error("Failed to increment attribute count"); + goto fail; + } + + /* + * Parse descriptors flags here since they are used to + * determine the permission the descriptor should have + */ + if (!parse_flags(proxy, NULL, NULL, &desc->perm, + &desc->req_prep_authorization)) { + error("Failed to parse characteristic properties"); + goto fail; + } + + queue_push_tail(desc->service->descs, desc); + + return desc; + +fail: + desc_free(desc); + return NULL; +} + +static bool check_service_path(GDBusProxy *proxy, + struct external_service *service) +{ + const char *service_path; + + if (!parse_path(proxy, "Service", &service_path)) + return false; + + return g_strcmp0(service_path, service->path) == 0; +} + +static struct external_service *create_service(struct gatt_app *app, + GDBusProxy *proxy, + const char *path) +{ + struct external_service *service; + + if (!path || !g_str_has_prefix(path, "/")) + return NULL; + + service = queue_find(app->services, match_service_by_path, path); + if (service) { + error("Duplicated service: %s", path); + return NULL; + } + + service = new0(struct external_service, 1); + + service->app = app; + + service->path = g_strdup(path); + if (!service->path) + goto fail; + + service->proxy = g_dbus_proxy_ref(proxy); + service->chrcs = queue_new(); + service->descs = queue_new(); + service->includes = queue_new(); + + /* Add 1 for the service declaration */ + if (!incr_attr_count(service, 1)) { + error("Failed to increment attribute count"); + goto fail; + } + + queue_push_tail(app->services, service); + + return service; + +fail: + service_free(service); + return NULL; +} + +static void proxy_added_cb(GDBusProxy *proxy, void *user_data) +{ + struct gatt_app *app = user_data; + const char *iface, *path; + + if (app->failed) + return; + + queue_push_tail(app->proxies, proxy); + + iface = g_dbus_proxy_get_interface(proxy); + path = g_dbus_proxy_get_path(proxy); + + DBG("Object received: %s, iface: %s", path, iface); +} + +static void proxy_removed_cb(GDBusProxy *proxy, void *user_data) +{ + struct gatt_app *app = user_data; + struct external_service *service; + const char *path; + + path = g_dbus_proxy_get_path(proxy); + + service = queue_remove_if(app->services, match_service_by_path, + (void *) path); + if (!service) + return; + + DBG("Proxy removed - removing service: %s", service->path); + + service_free(service); +} + +static bool parse_uuid(GDBusProxy *proxy, bt_uuid_t *uuid) +{ + DBusMessageIter iter; + bt_uuid_t tmp; + const char *uuidstr; + + if (!g_dbus_proxy_get_property(proxy, "UUID", &iter)) + return false; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return false; + + dbus_message_iter_get_basic(&iter, &uuidstr); + + if (bt_string_to_uuid(uuid, uuidstr) < 0) + return false; + + /* GAP & GATT services are created and managed by BlueZ */ + bt_uuid16_create(&tmp, UUID_GAP); + if (!bt_uuid_cmp(&tmp, uuid)) { + error("GAP service must be handled by BlueZ"); + return false; + } + + bt_uuid16_create(&tmp, UUID_GATT); + if (!bt_uuid_cmp(&tmp, uuid)) { + error("GATT service must be handled by BlueZ"); + return false; + } + + return true; +} + +static bool parse_includes(GDBusProxy *proxy, struct external_service *service) +{ + DBusMessageIter iter; + DBusMessageIter array; + char *obj; + + /* Includes property is optional */ + if (!g_dbus_proxy_get_property(proxy, "Includes", &iter)) + return true; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) + return false; + + dbus_message_iter_recurse(&iter, &array); + + do { + if (dbus_message_iter_get_arg_type(&array) != + DBUS_TYPE_OBJECT_PATH) + return false; + + dbus_message_iter_get_basic(&array, &obj); + + if (!queue_push_tail(service->includes, obj)) { + error("Failed to add Includes path in queue\n"); + return false; + + } + + incr_attr_count(service, 1); + } while (dbus_message_iter_next(&array)); + + return true; +} + +static bool parse_primary(GDBusProxy *proxy, bool *primary) +{ + DBusMessageIter iter; + dbus_bool_t val; + + if (!g_dbus_proxy_get_property(proxy, "Primary", &iter)) + return false; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN) + return false; + + dbus_message_iter_get_basic(&iter, &val); + + *primary = val; + + return true; +} + +static bool parse_handle(GDBusProxy *proxy, uint16_t *handle) +{ + DBusMessageIter iter; + + *handle = 0; + + /* Handle property is optional */ + if (!g_dbus_proxy_get_property(proxy, "Handle", &iter)) + return true; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT16) + return false; + + dbus_message_iter_get_basic(&iter, handle); + + return true; +} + +static uint8_t dbus_error_to_att_ecode(const char *error_name) +{ + + if (strcmp(error_name, "org.bluez.Error.Failed") == 0) + return 0x80; /* For now return this "application error" */ + + if (strcmp(error_name, "org.bluez.Error.NotSupported") == 0) + return BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; + + if (strcmp(error_name, "org.bluez.Error.NotAuthorized") == 0) + return BT_ATT_ERROR_AUTHORIZATION; + + if (strcmp(error_name, "org.bluez.Error.InvalidValueLength") == 0) + return BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + + if (strcmp(error_name, "org.bluez.Error.InvalidOffset") == 0) + return BT_ATT_ERROR_INVALID_OFFSET; + + if (strcmp(error_name, "org.bluez.Error.InProgress") == 0) + return BT_ERROR_ALREADY_IN_PROGRESS; + + return 0; +} + +static void read_reply_cb(DBusMessage *message, void *user_data) +{ + struct pending_op *op = user_data; + DBusError err; + DBusMessageIter iter, array; + uint8_t ecode = 0; + uint8_t *value = NULL; + int len = 0; + + if (!op->owner_queue) { + DBG("Pending read was canceled when object got removed"); + return; + } + + dbus_error_init(&err); + + if (dbus_set_error_from_message(&err, message) == TRUE) { + DBG("Failed to read value: %s: %s", err.name, err.message); + ecode = dbus_error_to_att_ecode(err.name); + ecode = ecode ? ecode : BT_ATT_ERROR_READ_NOT_PERMITTED; + dbus_error_free(&err); + goto done; + } + + dbus_message_iter_init(message, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + /* + * Return not supported for this, as the external app basically + * doesn't properly support reading from this characteristic. + */ + ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; + error("Invalid return value received for \"ReadValue\""); + goto done; + } + + dbus_message_iter_recurse(&iter, &array); + dbus_message_iter_get_fixed_array(&array, &value, &len); + + if (len < 0) { + ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; + value = NULL; + len = 0; + goto done; + } + + /* Truncate the value if it's too large */ + len = MIN(BT_ATT_MAX_VALUE_LEN, len); + value = len ? value : NULL; + +done: + gatt_db_attribute_read_result(op->attrib, op->id, ecode, value, len); +} + +static struct pending_op *pending_read_new(struct btd_device *device, + struct queue *owner_queue, + struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t link_type) +{ + struct pending_op *op; + + op = new0(struct pending_op, 1); + + op->owner_queue = owner_queue; + op->device = device; + op->attrib = attrib; + op->id = id; + op->offset = offset; + op->link_type = link_type; + queue_push_tail(owner_queue, op); + + return op; +} + +static void append_options(DBusMessageIter *iter, void *user_data) +{ + struct pending_op *op = user_data; + const char *path = device_get_path(op->device); + struct bt_gatt_server *server; + const char *link; + uint16_t mtu; + + switch (op->link_type) { + case BT_ATT_LINK_BREDR: + link = "BR/EDR"; + break; + case BT_ATT_LINK_LE: + link = "LE"; + break; + default: + link = NULL; + break; + } + + dict_append_entry(iter, "device", DBUS_TYPE_OBJECT_PATH, &path); + if (op->offset) + dict_append_entry(iter, "offset", DBUS_TYPE_UINT16, + &op->offset); + if (link) + dict_append_entry(iter, "link", DBUS_TYPE_STRING, &link); + if (op->prep_authorize) + dict_append_entry(iter, "prepare-authorize", DBUS_TYPE_BOOLEAN, + &op->prep_authorize); + + server = btd_device_get_gatt_server(op->device); + mtu = bt_gatt_server_get_mtu(server); + + dict_append_entry(iter, "mtu", DBUS_TYPE_UINT16, &mtu); +} + +static void read_setup_cb(DBusMessageIter *iter, void *user_data) +{ + struct pending_op *op = user_data; + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + append_options(&dict, op); + + dbus_message_iter_close_container(iter, &dict); +} + +static struct pending_op *send_read(struct btd_device *device, + struct gatt_db_attribute *attrib, + GDBusProxy *proxy, + struct queue *owner_queue, + unsigned int id, + uint16_t offset, + uint8_t link_type) +{ + struct pending_op *op; + + op = pending_read_new(device, owner_queue, attrib, id, offset, + link_type); + + if (g_dbus_proxy_method_call(proxy, "ReadValue", read_setup_cb, + read_reply_cb, op, pending_op_free) == TRUE) + return op; + + pending_op_free(op); + + return NULL; +} + +static void write_setup_cb(DBusMessageIter *iter, void *user_data) +{ + struct pending_op *op = user_data; + DBusMessageIter array, dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "y", &array); + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &op->data.iov_base, op->data.iov_len); + dbus_message_iter_close_container(iter, &array); + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + append_options(&dict, op); + + dbus_message_iter_close_container(iter, &dict); + + if (!op->owner_queue) { + gatt_db_attribute_write_result(op->attrib, op->id, 0); + pending_op_free(op); + } +} + +static void write_reply_cb(DBusMessage *message, void *user_data) +{ + struct pending_op *op = user_data; + struct external_chrc *chrc; + struct external_desc *desc; + DBusError err; + DBusMessageIter iter; + uint8_t ecode = 0; + + if (!op->owner_queue) { + DBG("Pending write was canceled when object got removed"); + return; + } + + dbus_error_init(&err); + + if (dbus_set_error_from_message(&err, message) == TRUE) { + DBG("Failed to write value: %s: %s", err.name, err.message); + ecode = dbus_error_to_att_ecode(err.name); + ecode = ecode ? ecode : BT_ATT_ERROR_WRITE_NOT_PERMITTED; + dbus_error_free(&err); + goto done; + } + + if (op->prep_authorize) { + if (op->is_characteristic) { + chrc = gatt_db_attribute_get_user_data(op->attrib); + chrc->prep_authorized = true; + } else { + desc = gatt_db_attribute_get_user_data(op->attrib); + desc->prep_authorized = true; + } + } + + dbus_message_iter_init(message, &iter); + if (dbus_message_iter_has_next(&iter)) { + /* + * Return not supported for this, as the external app basically + * doesn't properly support the "WriteValue" API. + */ + ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; + error("Invalid return value received for \"WriteValue\""); + } + +done: + gatt_db_attribute_write_result(op->attrib, op->id, ecode); +} + +static struct pending_op *pending_write_new(struct btd_device *device, + struct queue *owner_queue, + struct gatt_db_attribute *attrib, + unsigned int id, + const uint8_t *value, size_t len, + uint16_t offset, uint8_t link_type, + bool is_characteristic, + bool prep_authorize) +{ + struct pending_op *op; + + op = new0(struct pending_op, 1); + + op->data.iov_base = (uint8_t *) value; + op->data.iov_len = len; + + op->device = device; + op->owner_queue = owner_queue; + op->attrib = attrib; + op->id = id; + op->offset = offset; + op->link_type = link_type; + op->is_characteristic = is_characteristic; + op->prep_authorize = prep_authorize; + queue_push_tail(owner_queue, op); + + return op; +} + +static struct pending_op *send_write(struct btd_device *device, + struct gatt_db_attribute *attrib, + GDBusProxy *proxy, + struct queue *owner_queue, + unsigned int id, + const uint8_t *value, size_t len, + uint16_t offset, uint8_t link_type, + bool is_characteristic, + bool prep_authorize) +{ + struct pending_op *op; + + op = pending_write_new(device, owner_queue, attrib, id, value, len, + offset, link_type, is_characteristic, + prep_authorize); + + if (g_dbus_proxy_method_call(proxy, "WriteValue", write_setup_cb, + owner_queue ? write_reply_cb : NULL, + op, pending_op_free) == TRUE) + return op; + + pending_op_free(op); + + return NULL; +} + +static bool sock_hup(struct io *io, void *user_data) +{ + struct external_chrc *chrc = user_data; + + DBG("%p closed\n", io); + + if (io == chrc->write_io) + chrc->write_io = NULL; + else + chrc->notify_io = NULL; + + io_destroy(io); + + return false; +} + +static bool sock_io_read(struct io *io, void *user_data) +{ + struct external_chrc *chrc = user_data; + uint8_t buf[512]; + int fd = io_get_fd(io); + ssize_t bytes_read; + + bytes_read = read(fd, buf, sizeof(buf)); + if (bytes_read < 0) + return false; + + send_notification_to_devices(chrc->service->app->database, + gatt_db_attribute_get_handle(chrc->attrib), + buf, bytes_read, + gatt_db_attribute_get_handle(chrc->ccc), + chrc->props & BT_GATT_CHRC_PROP_INDICATE ? + conf_cb : NULL, chrc->proxy); + + return true; +} + +static struct io *sock_io_new(int fd, void *user_data) +{ + struct io *io; + + io = io_new(fd); + + io_set_close_on_destroy(io, true); + + io_set_read_handler(io, sock_io_read, user_data, NULL); + + io_set_disconnect_handler(io, sock_hup, user_data, NULL); + + return io; +} + +static int sock_io_send(struct io *io, const void *data, size_t len) +{ + struct msghdr msg; + struct iovec iov; + + iov.iov_base = (void *) data; + iov.iov_len = len; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + + return sendmsg(io_get_fd(io), &msg, MSG_NOSIGNAL); +} + +static void acquire_write_reply(DBusMessage *message, void *user_data) +{ + struct pending_op *op = user_data; + struct external_chrc *chrc; + DBusError err; + int fd; + uint16_t mtu; + + chrc = gatt_db_attribute_get_user_data(op->attrib); + dbus_error_init(&err); + + if (dbus_set_error_from_message(&err, message) == TRUE) { + error("Failed to acquire write: %s\n", err.name); + dbus_error_free(&err); + goto retry; + } + + if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_UINT16, &mtu, + DBUS_TYPE_INVALID) == false)) { + error("Invalid AcquireWrite response\n"); + goto retry; + } + + DBG("AcquireWrite success: fd %d MTU %u\n", fd, mtu); + + chrc->write_io = sock_io_new(fd, chrc); + + if (sock_io_send(chrc->write_io, op->data.iov_base, + op->data.iov_len) < 0) + goto retry; + + gatt_db_attribute_write_result(op->attrib, op->id, 0); + + return; + +retry: + send_write(op->device, op->attrib, chrc->proxy, NULL, op->id, + op->data.iov_base, op->data.iov_len, 0, + op->link_type, false, false); +} + +static void acquire_write_setup(DBusMessageIter *iter, void *user_data) +{ + struct pending_op *op = user_data; + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + append_options(&dict, op); + + dbus_message_iter_close_container(iter, &dict); +} + +static struct pending_op *acquire_write(struct external_chrc *chrc, + struct btd_device *device, + struct gatt_db_attribute *attrib, + unsigned int id, + const uint8_t *value, size_t len, + uint8_t link_type) +{ + struct pending_op *op; + + op = pending_write_new(device, NULL, attrib, id, value, len, 0, + link_type, false, false); + + if (g_dbus_proxy_method_call(chrc->proxy, "AcquireWrite", + acquire_write_setup, + acquire_write_reply, + op, pending_op_free)) + return op; + + pending_op_free(op); + + return NULL; +} + +static void acquire_notify_reply(DBusMessage *message, void *user_data) +{ + struct pending_op *op = user_data; + struct external_chrc *chrc = (void *) op->data.iov_base; + DBusError err; + int fd; + uint16_t mtu; + + dbus_error_init(&err); + + if (dbus_set_error_from_message(&err, message) == TRUE) { + error("Failed to acquire notify: %s\n", err.name); + dbus_error_free(&err); + goto retry; + } + + if ((dbus_message_get_args(message, NULL, DBUS_TYPE_UNIX_FD, &fd, + DBUS_TYPE_UINT16, &mtu, + DBUS_TYPE_INVALID) == false)) { + error("Invalid AcquirNotify response\n"); + goto retry; + } + + DBG("AcquireNotify success: fd %d MTU %u\n", fd, mtu); + + chrc->notify_io = sock_io_new(fd, chrc); + + __sync_fetch_and_add(&chrc->ntfy_cnt, 1); + + return; + +retry: + g_dbus_proxy_method_call(chrc->proxy, "StartNotify", NULL, NULL, + NULL, NULL); + + __sync_fetch_and_add(&chrc->ntfy_cnt, 1); +} + +static void acquire_notify_setup(DBusMessageIter *iter, void *user_data) +{ + DBusMessageIter dict; + struct pending_op *op = user_data; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + append_options(&dict, op); + + dbus_message_iter_close_container(iter, &dict); +} + +static uint8_t ccc_write_cb(struct pending_op *op, void *user_data) +{ + struct external_chrc *chrc = user_data; + DBusMessageIter iter; + uint16_t value; + + value = op ? PTR_TO_UINT(op->data.iov_base) : 0; + + DBG("External CCC write received with value: 0x%04x", value); + + /* Notifications/indications disabled */ + if (!value) { + if (!chrc->ntfy_cnt) + goto done; + + if (__sync_sub_and_fetch(&chrc->ntfy_cnt, 1)) + goto done; + + if (chrc->notify_io) { + io_destroy(chrc->notify_io); + chrc->notify_io = NULL; + goto done; + } + + /* + * Send request to stop notifying. This is best-effort + * operation, so simply ignore the return the value. + */ + g_dbus_proxy_method_call(chrc->proxy, "StopNotify", NULL, + NULL, NULL, NULL); + goto done; + } + + if (chrc->ntfy_cnt == UINT_MAX) + /* Maximum number of per-device CCC descriptors configured */ + return BT_ATT_ERROR_INSUFFICIENT_RESOURCES; + + /* Don't support undefined CCC values yet */ + if (value > 2 || + (value == 1 && !(chrc->props & BT_GATT_CHRC_PROP_NOTIFY)) || + (value == 2 && !(chrc->props & BT_GATT_CHRC_PROP_INDICATE))) + return BT_ERROR_CCC_IMPROPERLY_CONFIGURED; + + if (chrc->notify_io) { + __sync_fetch_and_add(&chrc->ntfy_cnt, 1); + goto done; + } + + /* Make use of AcquireNotify if supported */ + if (g_dbus_proxy_get_property(chrc->proxy, "NotifyAcquired", &iter)) { + op->data.iov_base = (void *) chrc; + op->data.iov_len = sizeof(chrc); + if (g_dbus_proxy_method_call(chrc->proxy, "AcquireNotify", + acquire_notify_setup, + acquire_notify_reply, + op, pending_op_free)) + return 0; + } + + /* + * Always call StartNotify for an incoming enable and ignore the return + * value for now. + */ + if (g_dbus_proxy_method_call(chrc->proxy, "StartNotify", NULL, NULL, + NULL, NULL) == FALSE) + return BT_ATT_ERROR_UNLIKELY; + + __sync_fetch_and_add(&chrc->ntfy_cnt, 1); + +done: + if (op) + pending_op_free(op); + + return 0; +} + +static void property_changed_cb(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + struct external_chrc *chrc = user_data; + DBusMessageIter array; + uint8_t *value = NULL; + int len = 0; + + if (strcmp(name, "Value")) + return; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) { + DBG("Malformed \"Value\" property received"); + return; + } + + dbus_message_iter_recurse(iter, &array); + dbus_message_iter_get_fixed_array(&array, &value, &len); + + if (len < 0) { + DBG("Malformed \"Value\" property received"); + return; + } + + /* Truncate the value if it's too large */ + len = MIN(BT_ATT_MAX_VALUE_LEN, len); + value = len ? value : NULL; + + send_notification_to_devices(chrc->service->app->database, + gatt_db_attribute_get_handle(chrc->attrib), + value, len, + gatt_db_attribute_get_handle(chrc->ccc), + chrc->props & BT_GATT_CHRC_PROP_INDICATE ? + conf_cb : NULL, proxy); +} + +static bool database_add_ccc(struct external_service *service, + struct external_chrc *chrc) +{ + if (!(chrc->props & BT_GATT_CHRC_PROP_NOTIFY) && + !(chrc->props & BT_GATT_CHRC_PROP_INDICATE)) + return true; + + chrc->ccc = service_add_ccc(service->attrib, service->app->database, + ccc_write_cb, chrc, NULL); + if (!chrc->ccc) { + error("Failed to create CCC entry for characteristic"); + return false; + } + + if (g_dbus_proxy_set_property_watch(chrc->proxy, property_changed_cb, + chrc) == FALSE) { + error("Failed to set up property watch for characteristic"); + return false; + } + + DBG("Created CCC entry for characteristic"); + + return true; +} + +static void cep_write_cb(struct gatt_db_attribute *attrib, int err, + void *user_data) +{ + if (err) + DBG("Failed to store CEP value in the database"); + else + DBG("Stored CEP value in the database"); +} + +static bool database_add_cep(struct external_service *service, + struct external_chrc *chrc) +{ + struct gatt_db_attribute *cep; + bt_uuid_t uuid; + uint8_t value[2]; + + if (!chrc->ext_props) + return true; + + bt_uuid16_create(&uuid, GATT_CHARAC_EXT_PROPER_UUID); + cep = gatt_db_service_add_descriptor(service->attrib, &uuid, + BT_ATT_PERM_READ, + NULL, NULL, NULL); + if (!cep) { + error("Failed to create CEP entry for characteristic"); + return false; + } + + memset(value, 0, sizeof(value)); + value[0] = chrc->ext_props; + + if (!gatt_db_attribute_write(cep, 0, value, sizeof(value), 0, NULL, + cep_write_cb, NULL)) { + DBG("Failed to store CEP value in the database"); + return false; + } + + DBG("Created CEP entry for characteristic"); + + return true; +} + +static void desc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct external_desc *desc = user_data; + struct btd_device *device; + + if (desc->attrib != attrib) { + error("Read callback called with incorrect attribute"); + goto fail; + } + + device = att_get_device(att); + if (!device) { + error("Unable to find device object"); + goto fail; + } + + if (send_read(device, attrib, desc->proxy, desc->pending_reads, id, + offset, bt_att_get_link_type(att))) + return; + +fail: + gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY, + NULL, 0); +} + +static void desc_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct external_desc *desc = user_data; + struct btd_device *device; + + if (desc->attrib != attrib) { + error("Read callback called with incorrect attribute"); + goto fail; + } + + device = att_get_device(att); + if (!device) { + error("Unable to find device object"); + goto fail; + } + + if (opcode == BT_ATT_OP_PREP_WRITE_REQ) { + if (!device_is_trusted(device) && !desc->prep_authorized && + desc->req_prep_authorization) + send_write(device, attrib, desc->proxy, + desc->pending_writes, id, value, len, + offset, bt_att_get_link_type(att), + false, true); + else + gatt_db_attribute_write_result(attrib, id, 0); + + return; + } + + if (opcode == BT_ATT_OP_EXEC_WRITE_REQ) + desc->prep_authorized = false; + + if (send_write(device, attrib, desc->proxy, desc->pending_writes, id, + value, len, offset, bt_att_get_link_type(att), false, + false)) + return; + +fail: + gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_UNLIKELY); +} + +static void write_handle(struct GDBusProxy *proxy, uint16_t handle) +{ + DBusMessageIter iter; + + /* Check if the attribute has the Handle property */ + if (!g_dbus_proxy_get_property(proxy, "Handle", &iter)) + return; + + g_dbus_proxy_set_property_basic(proxy, "Handle", DBUS_TYPE_UINT16, + &handle, NULL, NULL, NULL); +} + +static bool database_add_desc(struct external_service *service, + struct external_desc *desc) +{ + uint16_t handle; + bt_uuid_t uuid; + char str[MAX_LEN_UUID_STR]; + + if (!parse_handle(desc->proxy, &handle)) { + error("Failed to read \"Handle\" property of descriptor"); + return false; + } + + if (!parse_uuid(desc->proxy, &uuid)) { + error("Failed to read \"UUID\" property of descriptor"); + return false; + } + + desc->attrib = gatt_db_service_insert_descriptor(service->attrib, + handle, &uuid, + desc->perm, + desc_read_cb, + desc_write_cb, desc); + if (!desc->attrib) { + error("Failed to create descriptor entry in database"); + return false; + } + + desc->handled = true; + + if (!handle) { + handle = gatt_db_attribute_get_handle(desc->attrib); + write_handle(desc->proxy, handle); + } + + bt_uuid_to_string(&uuid, str, sizeof(str)); + + DBG("handle 0x%04x UUID %s", handle, str); + + return true; +} + +static void chrc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct external_chrc *chrc = user_data; + struct btd_device *device; + + if (chrc->attrib != attrib) { + error("Read callback called with incorrect attribute"); + goto fail; + } + + device = att_get_device(att); + if (!device) { + error("Unable to find device object"); + goto fail; + } + + if (send_read(device, attrib, chrc->proxy, chrc->pending_reads, id, + offset, bt_att_get_link_type(att))) + return; + +fail: + gatt_db_attribute_read_result(attrib, id, BT_ATT_ERROR_UNLIKELY, + NULL, 0); +} + +static void chrc_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct external_chrc *chrc = user_data; + struct btd_device *device; + struct queue *queue; + DBusMessageIter iter; + + if (chrc->attrib != attrib) { + error("Write callback called with incorrect attribute"); + goto fail; + } + + device = att_get_device(att); + if (!device) { + error("Unable to find device object"); + goto fail; + } + + if (!(chrc->props & BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP)) + queue = chrc->pending_writes; + else + queue = NULL; + + if (opcode == BT_ATT_OP_PREP_WRITE_REQ) { + if (!device_is_trusted(device) && !chrc->prep_authorized && + chrc->req_prep_authorization) + send_write(device, attrib, chrc->proxy, queue, + id, value, len, offset, + bt_att_get_link_type(att), true, true); + else + gatt_db_attribute_write_result(attrib, id, 0); + + return; + } + + if (opcode == BT_ATT_OP_EXEC_WRITE_REQ) + chrc->prep_authorized = false; + + if (chrc->write_io) { + if (sock_io_send(chrc->write_io, value, len) < 0) { + error("Unable to write: %s", strerror(errno)); + goto fail; + } + + gatt_db_attribute_write_result(attrib, id, 0); + return; + } + + if (g_dbus_proxy_get_property(chrc->proxy, "WriteAcquired", &iter)) { + if (acquire_write(chrc, device, attrib, id, value, len, + bt_att_get_link_type(att))) + return; + } + + if (send_write(device, attrib, chrc->proxy, queue, id, value, len, + offset, bt_att_get_link_type(att), false, false)) + return; + +fail: + gatt_db_attribute_write_result(attrib, id, BT_ATT_ERROR_UNLIKELY); +} + +static void include_services(void *data ,void *userdata) +{ + char *obj = data; + struct external_service *service = userdata; + struct gatt_db_attribute *attrib; + struct external_service *service_inc; + + DBG("path %s", obj); + + service_inc = queue_find(service->app->services, match_service_by_path, + obj); + if (!service_inc) { + error("include service not found\n"); + return; + } + + attrib = gatt_db_service_add_included(service->attrib, + service_inc->attrib); + if (!attrib) { + error("include service attributes failed\n"); + return; + } + + service->attrib = attrib; +} + +static void database_add_includes(struct external_service *service) +{ + queue_foreach(service->includes, include_services, service); +} + +static bool database_add_chrc(struct external_service *service, + struct external_chrc *chrc) +{ + uint16_t handle; + bt_uuid_t uuid; + char str[MAX_LEN_UUID_STR]; + const struct queue_entry *entry; + + if (!parse_handle(chrc->proxy, &handle)) { + error("Failed to read \"Handle\" property of characteristic"); + return false; + } + + if (!parse_uuid(chrc->proxy, &uuid)) { + error("Failed to read \"UUID\" property of characteristic"); + return false; + } + + if (!check_service_path(chrc->proxy, service)) { + error("Invalid service path for characteristic"); + return false; + } + + chrc->attrib = gatt_db_service_insert_characteristic(service->attrib, + handle, &uuid, chrc->perm, + chrc->props, chrc_read_cb, + chrc_write_cb, chrc); + if (!chrc->attrib) { + error("Failed to create characteristic entry in database"); + return false; + } + + if (!database_add_ccc(service, chrc)) + return false; + + if (!database_add_cep(service, chrc)) + return false; + + if (!handle) { + handle = gatt_db_attribute_get_handle(chrc->attrib); + write_handle(chrc->proxy, handle); + } + + bt_uuid_to_string(&uuid, str, sizeof(str)); + + DBG("handle 0x%04x UUID %s", handle, str); + + /* Handle the descriptors that belong to this characteristic. */ + for (entry = queue_get_entries(service->descs); entry; + entry = entry->next) { + struct external_desc *desc = entry->data; + + if (desc->handled || g_strcmp0(desc->chrc_path, chrc->path)) + continue; + + if (!database_add_desc(service, desc)) { + chrc->attrib = NULL; + error("Failed to create descriptor entry"); + return false; + } + } + + return true; +} + +static bool match_desc_unhandled(const void *a, const void *b) +{ + const struct external_desc *desc = a; + + return !desc->handled; +} + +static bool database_add_service(struct external_service *service) +{ + bt_uuid_t uuid; + bool primary; + uint16_t handle; + const struct queue_entry *entry; + char str[MAX_LEN_UUID_STR]; + + if (!parse_uuid(service->proxy, &uuid)) { + error("Failed to read \"UUID\" property of service"); + return false; + } + + if (!parse_primary(service->proxy, &primary)) { + error("Failed to read \"Primary\" property of service"); + return false; + } + + if (!parse_includes(service->proxy, service)) { + error("Failed to read \"Includes\" property of service"); + return false; + } + + if (!parse_handle(service->proxy, &handle)) { + error("Failed to read \"Handle\" property of service"); + return false; + } + + service->attrib = gatt_db_insert_service(service->app->database->db, + handle, &uuid, + primary, service->attr_cnt); + if (!service->attrib) + return false; + + if (!handle) { + handle = gatt_db_attribute_get_handle(service->attrib); + write_handle(service->proxy, handle); + } + + bt_uuid_to_string(&uuid, str, sizeof(str)); + + DBG("handle 0x%04x UUID %s", handle, str); + + database_add_includes(service); + + entry = queue_get_entries(service->chrcs); + while (entry) { + struct external_chrc *chrc = entry->data; + + if (!database_add_chrc(service, chrc)) { + error("Failed to add characteristic"); + goto fail; + } + + entry = entry->next; + } + + /* If there are any unhandled descriptors, return an error */ + if (queue_find(service->descs, match_desc_unhandled, NULL)) { + error("Found descriptor with no matching characteristic!"); + goto fail; + } + + gatt_db_service_set_active(service->attrib, true); + + return true; + +fail: + gatt_db_remove_service(service->app->database->db, service->attrib); + service->attrib = NULL; + + return false; +} + +static bool database_add_app(struct gatt_app *app) +{ + const struct queue_entry *entry; + + entry = queue_get_entries(app->services); + while (entry) { + if (!database_add_service(entry->data)) { + error("Failed to add service"); + return false; + } + + entry = entry->next; + } + + return true; +} + +static int profile_device_probe(struct btd_service *service) +{ + struct btd_profile *p = btd_service_get_profile(service); + + DBG("%s probed", p->name); + + return 0; +} + +static void profile_device_remove(struct btd_service *service) +{ + struct btd_profile *p = btd_service_get_profile(service); + + DBG("%s removed", p->name); +} + +static int profile_device_accept(struct btd_service *service) +{ + struct btd_profile *p = btd_service_get_profile(service); + + DBG("%s accept", p->name); + + return 0; +} + +static int profile_add(struct external_profile *profile, const char *uuid) +{ + struct btd_profile *p; + + p = new0(struct btd_profile, 1); + + /* Assign directly to avoid having extra fields */ + p->name = (const void *) g_strdup_printf("%s%s/%s", profile->app->owner, + g_dbus_proxy_get_path(profile->proxy), uuid); + if (!p->name) { + free(p); + return -ENOMEM; + } + + p->remote_uuid = (const void *) g_strdup(uuid); + if (!p->remote_uuid) { + g_free((void *) p->name); + free(p); + return -ENOMEM; + } + + p->device_probe = profile_device_probe; + p->device_remove = profile_device_remove; + p->accept = profile_device_accept; + p->auto_connect = true; + p->external = true; + + queue_push_tail(profile->profiles, p); + + DBG("Added \"%s\"", p->name); + + return 0; +} + +static void add_profile(void *data, void *user_data) +{ + struct btd_adapter *adapter = user_data; + + btd_profile_register(data); + adapter_add_profile(adapter, data); +} + +static struct external_profile *create_profile(struct gatt_app *app, + GDBusProxy *proxy, + const char *path) +{ + struct external_profile *profile; + DBusMessageIter iter, array; + + if (!path || !g_str_has_prefix(path, "/")) + return NULL; + + profile = new0(struct external_profile, 1); + + profile->app = app; + profile->proxy = g_dbus_proxy_ref(proxy); + profile->profiles = queue_new(); + + if (!g_dbus_proxy_get_property(proxy, "UUIDs", &iter)) { + DBG("UUIDs property not found"); + goto fail; + } + + dbus_message_iter_recurse(&iter, &array); + + while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) { + const char *uuid; + + dbus_message_iter_get_basic(&array, &uuid); + + if (profile_add(profile, uuid) < 0) + goto fail; + + dbus_message_iter_next(&array); + } + + if (queue_isempty(profile->profiles)) + goto fail; + + queue_foreach(profile->profiles, add_profile, app->database->adapter); + queue_push_tail(app->profiles, profile); + + return profile; + +fail: + profile_free(profile); + return NULL; +} + +static void register_profile(void *data, void *user_data) +{ + struct gatt_app *app = user_data; + GDBusProxy *proxy = data; + const char *iface = g_dbus_proxy_get_interface(proxy); + const char *path = g_dbus_proxy_get_path(proxy); + + if (app->failed) + return; + + if (g_strcmp0(iface, GATT_PROFILE_IFACE) == 0) { + struct external_profile *profile; + + profile = create_profile(app, proxy, path); + if (!profile) { + app->failed = true; + return; + } + } +} + +static void register_service(void *data, void *user_data) +{ + struct gatt_app *app = user_data; + GDBusProxy *proxy = data; + const char *iface = g_dbus_proxy_get_interface(proxy); + const char *path = g_dbus_proxy_get_path(proxy); + + if (app->failed) + return; + + if (g_strcmp0(iface, GATT_SERVICE_IFACE) == 0) { + struct external_service *service; + + service = create_service(app, proxy, path); + if (!service) { + app->failed = true; + return; + } + } +} + +static void register_characteristic(void *data, void *user_data) +{ + struct gatt_app *app = user_data; + GDBusProxy *proxy = data; + const char *iface = g_dbus_proxy_get_interface(proxy); + const char *path = g_dbus_proxy_get_path(proxy); + + if (app->failed) + return; + + iface = g_dbus_proxy_get_interface(proxy); + path = g_dbus_proxy_get_path(proxy); + + if (g_strcmp0(iface, GATT_CHRC_IFACE) == 0) { + struct external_chrc *chrc; + + chrc = chrc_create(app, proxy, path); + if (!chrc) { + app->failed = true; + return; + } + } +} + +static void register_descriptor(void *data, void *user_data) +{ + struct gatt_app *app = user_data; + GDBusProxy *proxy = data; + const char *iface = g_dbus_proxy_get_interface(proxy); + + if (app->failed) + return; + + if (g_strcmp0(iface, GATT_DESC_IFACE) == 0) { + struct external_desc *desc; + + desc = desc_create(app, proxy); + if (!desc) { + app->failed = true; + return; + } + } +} + +static void client_ready_cb(GDBusClient *client, void *user_data) +{ + struct gatt_app *app = user_data; + DBusMessage *reply; + bool fail = false; + + /* + * Process received objects + */ + if (queue_isempty(app->proxies)) { + error("No object received"); + fail = true; + reply = btd_error_failed(app->reg, + "No object received"); + goto reply; + } + + queue_foreach(app->proxies, register_profile, app); + queue_foreach(app->proxies, register_service, app); + queue_foreach(app->proxies, register_characteristic, app); + queue_foreach(app->proxies, register_descriptor, app); + + if ((queue_isempty(app->services) && queue_isempty(app->profiles)) || + app->failed) { + error("No valid external GATT objects found"); + fail = true; + reply = btd_error_failed(app->reg, + "No valid service object found"); + goto reply; + } + + if (!database_add_app(app)) { + error("Failed to create GATT service entry in local database"); + fail = true; + reply = btd_error_failed(app->reg, + "Failed to create entry in database"); + goto reply; + } + + DBG("GATT application registered: %s:%s", app->owner, app->path); + + reply = dbus_message_new_method_return(app->reg); + +reply: + g_dbus_send_message(btd_get_dbus_connection(), reply); + dbus_message_unref(app->reg); + app->reg = NULL; + + if (fail) + remove_app(app); +} + +static struct gatt_app *create_app(DBusConnection *conn, DBusMessage *msg, + const char *path) +{ + struct gatt_app *app; + const char *sender = dbus_message_get_sender(msg); + + if (!path || !g_str_has_prefix(path, "/")) + return NULL; + + app = new0(struct gatt_app, 1); + + app->client = g_dbus_client_new_full(conn, sender, path, path); + if (!app->client) + goto fail; + + app->owner = g_strdup(sender); + if (!app->owner) + goto fail; + + app->path = g_strdup(path); + if (!app->path) + goto fail; + + app->services = queue_new(); + app->profiles = queue_new(); + app->proxies = queue_new(); + app->reg = dbus_message_ref(msg); + + g_dbus_client_set_disconnect_watch(app->client, client_disconnect_cb, + app); + g_dbus_client_set_proxy_handlers(app->client, proxy_added_cb, + proxy_removed_cb, NULL, app); + g_dbus_client_set_ready_watch(app->client, client_ready_cb, app); + + return app; + +fail: + app_free(app); + return NULL; +} + +static DBusMessage *manager_register_app(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct btd_gatt_database *database = user_data; + const char *sender = dbus_message_get_sender(msg); + DBusMessageIter args; + const char *path; + struct gatt_app *app; + struct svc_match_data match_data; + + if (!dbus_message_iter_init(msg, &args)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&args, &path); + + match_data.path = path; + match_data.sender = sender; + + if (queue_find(database->apps, match_app, &match_data)) + return btd_error_already_exists(msg); + + dbus_message_iter_next(&args); + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) + return btd_error_invalid_args(msg); + + app = create_app(conn, msg, path); + if (!app) + return btd_error_failed(msg, "Failed to register application"); + + DBG("Registering application: %s:%s", sender, path); + + app->database = database; + queue_push_tail(database->apps, app); + + return NULL; +} + +static DBusMessage *manager_unregister_app(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct btd_gatt_database *database = user_data; + const char *sender = dbus_message_get_sender(msg); + const char *path; + DBusMessageIter args; + struct gatt_app *app; + struct svc_match_data match_data; + + if (!dbus_message_iter_init(msg, &args)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&args, &path); + + match_data.path = path; + match_data.sender = sender; + + app = queue_remove_if(database->apps, match_app, &match_data); + if (!app) + return btd_error_does_not_exist(msg); + + app_free(app); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable manager_methods[] = { + { GDBUS_ASYNC_METHOD("RegisterApplication", + GDBUS_ARGS({ "application", "o" }, + { "options", "a{sv}" }), + NULL, manager_register_app) }, + { GDBUS_ASYNC_METHOD("UnregisterApplication", + GDBUS_ARGS({ "application", "o" }), + NULL, manager_unregister_app) }, + { } +}; + +static uint8_t server_authorize(struct bt_att *att, uint8_t opcode, + uint16_t handle, void *user_data) +{ + struct btd_gatt_database *database = user_data; + struct device_state *state; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + + if (!get_dst_info(att, &bdaddr, &bdaddr_type)) + return 0; + + /* Skip if there is no device state */ + state = find_device_state(database, &bdaddr, bdaddr_type); + if (!state) + return 0; + + /* Skip if client doesn't support Robust Caching */ + if (!(state->cli_feat[0] & BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING)) + return 0; + + if (state->change_aware) + return 0; + + if (state->out_of_sync) { + state->out_of_sync = false; + state->change_aware = true; + return 0; + } + + state->out_of_sync = true; + + return BT_ATT_ERROR_DB_OUT_OF_SYNC; +} + +struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter) +{ + struct btd_gatt_database *database; + GError *gerr = NULL; + const bdaddr_t *addr; + + if (!adapter) + return NULL; + + database = new0(struct btd_gatt_database, 1); + database->adapter = btd_adapter_ref(adapter); + database->db = gatt_db_new(); + database->records = queue_new(); + database->device_states = queue_new(); + database->apps = queue_new(); + database->profiles = queue_new(); + database->ccc_callbacks = queue_new(); + + addr = btd_adapter_get_address(adapter); + database->le_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, addr, + BT_IO_OPT_SOURCE_TYPE, + btd_adapter_get_address_type(adapter), + BT_IO_OPT_CID, ATT_CID, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!database->le_io) { + error("Failed to start listening: %s", gerr->message); + g_error_free(gerr); + goto fail; + } + + /* BR/EDR socket */ + database->l2cap_io = bt_io_listen(connect_cb, NULL, NULL, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, addr, + BT_IO_OPT_PSM, ATT_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MTU, main_opts.gatt_mtu, + BT_IO_OPT_INVALID); + if (database->l2cap_io == NULL) { + error("Failed to start listening: %s", gerr->message); + g_error_free(gerr); + goto fail; + } + + if (g_dbus_register_interface(btd_get_dbus_connection(), + adapter_get_path(adapter), + GATT_MANAGER_IFACE, + manager_methods, NULL, NULL, + database, NULL)) + DBG("GATT Manager registered for adapter: %s", + adapter_get_path(adapter)); + + register_core_services(database); + + database->db_id = gatt_db_register(database->db, gatt_db_service_added, + gatt_db_service_removed, + database, NULL); + if (!database->db_id) + goto fail; + + return database; + +fail: + gatt_database_free(database); + + return NULL; +} + +void btd_gatt_database_destroy(struct btd_gatt_database *database) +{ + if (!database) + return; + + g_dbus_unregister_interface(btd_get_dbus_connection(), + adapter_get_path(database->adapter), + GATT_MANAGER_IFACE); + + gatt_database_free(database); +} + +struct gatt_db *btd_gatt_database_get_db(struct btd_gatt_database *database) +{ + if (!database) + return NULL; + + return database->db; +} + +void btd_gatt_database_server_connected(struct btd_gatt_database *database, + struct bt_gatt_server *server) +{ + struct bt_att *att = bt_gatt_server_get_att(server); + struct device_state *state; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + + if (!get_dst_info(att, &bdaddr, &bdaddr_type)) + return; + + bt_gatt_server_set_authorize(server, server_authorize, database); + + state = find_device_state(database, &bdaddr, bdaddr_type); + if (!state || !state->pending) + return; + + send_notification_to_device(state, state->pending); + + free(state->pending->value); + free(state->pending); + state->pending = NULL; +} + +void btd_gatt_database_att_disconnected(struct btd_gatt_database *database, + struct btd_device *device) +{ + struct bt_gatt_server *server = btd_device_get_gatt_server(device); + struct bt_att *att = bt_gatt_server_get_att(server); + struct device_state *state; + const bdaddr_t *addr; + uint8_t type; + + DBG(""); + + addr = device_get_address(device); + type = btd_device_get_bdaddr_type(device); + + state = find_device_state(database, addr, type); + if (!state) + return; + + if (state->disc_id) + bt_att_unregister_disconnect(att, state->disc_id); + + att_disconnected(0, state); +} + +static void restore_ccc(struct btd_gatt_database *database, + const bdaddr_t *addr, uint8_t addr_type, uint16_t value) +{ + struct device_state *dev_state; + struct ccc_state *ccc; + + dev_state = device_state_create(database, addr, addr_type); + queue_push_tail(database->device_states, dev_state); + + ccc = new0(struct ccc_state, 1); + ccc->handle = gatt_db_attribute_get_handle(database->svc_chngd_ccc); + ccc->value = value; + queue_push_tail(dev_state->ccc_states, ccc); +} + +static void restore_state(struct btd_device *device, void *data) +{ + struct btd_gatt_database *database = data; + uint16_t ccc_le, ccc_bredr; + + device_load_svc_chng_ccc(device, &ccc_le, &ccc_bredr); + + if (ccc_le) { + restore_ccc(database, device_get_address(device), + device_get_le_address_type(device), ccc_le); + + DBG("%s LE", device_get_path(device)); + } + + if (ccc_bredr) { + restore_ccc(database, device_get_address(device), + BDADDR_BREDR, ccc_bredr); + + DBG("%s BR/EDR", device_get_path(device)); + } +} + +void btd_gatt_database_restore_svc_chng_ccc(struct btd_gatt_database *database) +{ + uint8_t value[4]; + uint16_t handle, ccc_handle; + + if (!database) + return; + + handle = gatt_db_attribute_get_handle(database->svc_chngd); + ccc_handle = gatt_db_attribute_get_handle(database->svc_chngd_ccc); + + if (!handle || !ccc_handle) { + error("Failed to obtain handles for \"Service Changed\"" + " characteristic"); + return; + } + + /* restore states for bonded device that registered for Service Changed + * indication + */ + btd_adapter_for_each_device(database->adapter, restore_state, database); + + /* This needs to be updated (probably to 0x0001) if we ever change + * core services + * + * TODO we could also store this info (along with CCC value) and be able + * to send 0x0001-0xffff only once per device. + */ + put_le16(0x000a, value); + put_le16(0xffff, value + 2); + + send_notification_to_devices(database, handle, value, sizeof(value), + ccc_handle, service_changed_conf, NULL); +} diff --git a/src/gatt-database.h b/src/gatt-database.h new file mode 100644 index 0000000..154d324 --- /dev/null +++ b/src/gatt-database.h @@ -0,0 +1,31 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Google 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. + * + */ + +struct btd_gatt_database; + +struct btd_gatt_database *btd_gatt_database_new(struct btd_adapter *adapter); +void btd_gatt_database_destroy(struct btd_gatt_database *database); + +struct gatt_db *btd_gatt_database_get_db(struct btd_gatt_database *database); +void btd_gatt_database_att_disconnected(struct btd_gatt_database *database, + struct btd_device *device); +void btd_gatt_database_server_connected(struct btd_gatt_database *database, + struct bt_gatt_server *server); + +void btd_gatt_database_restore_svc_chng_ccc(struct btd_gatt_database *database); diff --git a/src/genbuiltin b/src/genbuiltin new file mode 100755 index 0000000..8b6f047 --- /dev/null +++ b/src/genbuiltin @@ -0,0 +1,17 @@ +#!/bin/sh + +for i in $* +do + echo "extern struct bluetooth_plugin_desc __bluetooth_builtin_$i;" +done + +echo +echo "static struct bluetooth_plugin_desc *__bluetooth_builtin[] = {" + +for i in $* +do + echo " &__bluetooth_builtin_$i," +done + +echo " NULL" +echo "};" diff --git a/src/hcid.h b/src/hcid.h new file mode 100644 index 0000000..adea85c --- /dev/null +++ b/src/hcid.h @@ -0,0 +1,73 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +typedef enum { + BT_MODE_DUAL, + BT_MODE_BREDR, + BT_MODE_LE, +} bt_mode_t; + +typedef enum { + BT_GATT_CACHE_ALWAYS, + BT_GATT_CACHE_YES, + BT_GATT_CACHE_NO, +} bt_gatt_cache_t; + +struct main_opts { + char *name; + uint32_t class; + gboolean pairable; + uint32_t pairto; + uint32_t discovto; + uint8_t privacy; + + gboolean reverse_discovery; + gboolean name_resolv; + gboolean debug_keys; + gboolean fast_conn; + + uint16_t did_source; + uint16_t did_vendor; + uint16_t did_product; + uint16_t did_version; + + bt_mode_t mode; + bt_gatt_cache_t gatt_cache; + uint16_t gatt_mtu; + + uint8_t key_size; +}; + +extern struct main_opts main_opts; + +gboolean plugin_init(const char *enable, const char *disable); +void plugin_cleanup(void); + +void rfkill_init(void); +void rfkill_exit(void); + +GKeyFile *btd_get_main_conf(void); + +void btd_exit(void); diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..11d26d5 --- /dev/null +++ b/src/log.c @@ -0,0 +1,228 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/util.h" +#include "src/shared/log.h" +#include "log.h" + +#define LOG_IDENT "bluetoothd" + +static void monitor_log(uint16_t index, int priority, + const char *format, va_list ap) +{ + bt_log_vprintf(index, LOG_IDENT, priority, format, ap); +} + +void error(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog(LOG_ERR, format, ap); + va_end(ap); + + va_start(ap, format); + monitor_log(HCI_DEV_NONE, LOG_ERR, format, ap); + va_end(ap); +} + +void warn(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog(LOG_WARNING, format, ap); + va_end(ap); + + va_start(ap, format); + monitor_log(HCI_DEV_NONE, LOG_WARNING, format, ap); + va_end(ap); +} + +void info(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog(LOG_INFO, format, ap); + va_end(ap); + + va_start(ap, format); + monitor_log(HCI_DEV_NONE, LOG_INFO, format, ap); + va_end(ap); +} + +void btd_log(uint16_t index, int priority, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog(priority, format, ap); + va_end(ap); + + va_start(ap, format); + monitor_log(index, priority, format, ap); + va_end(ap); +} + +void btd_error(uint16_t index, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog(LOG_ERR, format, ap); + va_end(ap); + + va_start(ap, format); + monitor_log(index, LOG_ERR, format, ap); + va_end(ap); +} + +void btd_warn(uint16_t index, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog(LOG_WARNING, format, ap); + va_end(ap); + + va_start(ap, format); + monitor_log(index, LOG_WARNING, format, ap); + va_end(ap); +} + +void btd_info(uint16_t index, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog(LOG_INFO, format, ap); + va_end(ap); + + va_start(ap, format); + monitor_log(index, LOG_INFO, format, ap); + va_end(ap); +} + +void btd_debug(uint16_t index, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vsyslog(LOG_DEBUG, format, ap); + va_end(ap); + + va_start(ap, format); + monitor_log(index, LOG_DEBUG, format, ap); + va_end(ap); +} + +extern struct btd_debug_desc __start___debug[]; +extern struct btd_debug_desc __stop___debug[]; + +static char **enabled = NULL; + +static gboolean is_enabled(struct btd_debug_desc *desc) +{ + int i; + + if (enabled == NULL) + return 0; + + for (i = 0; enabled[i] != NULL; i++) + if (desc->file != NULL && g_pattern_match_simple(enabled[i], + desc->file) == TRUE) + return 1; + + return 0; +} + +void __btd_enable_debug(struct btd_debug_desc *start, + struct btd_debug_desc *stop) +{ + struct btd_debug_desc *desc; + + if (start == NULL || stop == NULL) + return; + + for (desc = start; desc < stop; desc++) { + if (is_enabled(desc)) + desc->flags |= BTD_DEBUG_FLAG_PRINT; + } +} + +void __btd_toggle_debug(void) +{ + struct btd_debug_desc *desc; + + for (desc = __start___debug; desc < __stop___debug; desc++) + desc->flags |= BTD_DEBUG_FLAG_PRINT; +} + +void __btd_log_init(const char *debug, int detach) +{ + int option = LOG_NDELAY | LOG_PID; + + if (debug != NULL) + enabled = g_strsplit_set(debug, ":, ", 0); + + __btd_enable_debug(__start___debug, __stop___debug); + + bt_log_open(); + + if (!detach) + option |= LOG_PERROR; + + openlog(LOG_IDENT, option, LOG_DAEMON); + + info("Bluetooth daemon %s", VERSION); +} + +void __btd_log_cleanup(void) +{ + closelog(); + + bt_log_close(); + + g_strfreev(enabled); +} diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..0d243ce --- /dev/null +++ b/src/log.h @@ -0,0 +1,73 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +void error(const char *format, ...) __attribute__((format(printf, 1, 2))); +void warn(const char *format, ...) __attribute__((format(printf, 1, 2))); +void info(const char *format, ...) __attribute__((format(printf, 1, 2))); + +void btd_log(uint16_t index, int priority, const char *format, ...) + __attribute__((format(printf, 3, 4))); + +void btd_error(uint16_t index, const char *format, ...) + __attribute__((format(printf, 2, 3))); +void btd_warn(uint16_t index, const char *format, ...) + __attribute__((format(printf, 2, 3))); +void btd_info(uint16_t index, const char *format, ...) + __attribute__((format(printf, 2, 3))); +void btd_debug(uint16_t index, const char *format, ...) + __attribute__((format(printf, 2, 3))); + +void __btd_log_init(const char *debug, int detach); +void __btd_log_cleanup(void); +void __btd_toggle_debug(void); + +struct btd_debug_desc { + const char *file; +#define BTD_DEBUG_FLAG_DEFAULT (0) +#define BTD_DEBUG_FLAG_PRINT (1 << 0) + unsigned int flags; +} __attribute__((aligned(8))); + +void __btd_enable_debug(struct btd_debug_desc *start, + struct btd_debug_desc *stop); + +/** + * DBG: + * @fmt: format string + * @arg...: list of arguments + * + * Simple macro around btd_debug() which also include the function + * name it is called in. + */ +#define DBG_IDX(idx, fmt, arg...) do { \ + static struct btd_debug_desc __btd_debug_desc \ + __attribute__((used, section("__debug"), aligned(8))) = { \ + .file = __FILE__, .flags = BTD_DEBUG_FLAG_DEFAULT, \ + }; \ + if (__btd_debug_desc.flags & BTD_DEBUG_FLAG_PRINT) \ + btd_debug(idx, "%s:%s() " fmt, __FILE__, __func__ , ## arg); \ +} while (0) + +#define DBG(fmt, arg...) DBG_IDX(0xffff, fmt, ## arg) diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..1a6ab36 --- /dev/null +++ b/src/main.c @@ -0,0 +1,756 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "gdbus/gdbus.h" + +#include "log.h" +#include "backtrace.h" + +#include "shared/att-types.h" +#include "shared/mainloop.h" +#include "lib/uuid.h" +#include "hcid.h" +#include "sdpd.h" +#include "adapter.h" +#include "device.h" +#include "dbus-common.h" +#include "agent.h" +#include "profile.h" + +#define BLUEZ_NAME "org.bluez" + +#define DEFAULT_PAIRABLE_TIMEOUT 0 /* disabled */ +#define DEFAULT_DISCOVERABLE_TIMEOUT 180 /* 3 minutes */ + +#define SHUTDOWN_GRACE_SECONDS 10 + +struct main_opts main_opts; +static GKeyFile *main_conf; +static char *main_conf_file_path; + +static enum { + MPS_OFF, + MPS_SINGLE, + MPS_MULTIPLE, +} mps = MPS_OFF; + +static const char *supported_options[] = { + "Name", + "Class", + "DiscoverableTimeout", + "AlwaysPairable" + "PairableTimeout", + "DeviceID", + "ReverseServiceDiscovery", + "NameResolving", + "DebugKeys", + "ControllerMode", + "MultiProfile", + "FastConnectable", + "Privacy", + NULL +}; + +static const char *policy_options[] = { + "ReconnectUUIDs", + "ReconnectAttempts", + "ReconnectIntervals", + "AutoEnable", + NULL +}; + +static const char *gatt_options[] = { + "Cache", + "KeySize", + "ExchangeMTU", + NULL +}; + +static const struct group_table { + const char *name; + const char **options; +} valid_groups[] = { + { "General", supported_options }, + { "Policy", policy_options }, + { "GATT", gatt_options }, + { } +}; + + +GKeyFile *btd_get_main_conf(void) +{ + return main_conf; +} + +static GKeyFile *load_config(const char *file) +{ + GError *err = NULL; + GKeyFile *keyfile; + + keyfile = g_key_file_new(); + + g_key_file_set_list_separator(keyfile, ','); + + if (!g_key_file_load_from_file(keyfile, file, 0, &err)) { + if (!g_error_matches(err, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + error("Parsing %s failed: %s", file, err->message); + g_error_free(err); + g_key_file_free(keyfile); + return NULL; + } + + return keyfile; +} + +static void parse_did(const char *did) +{ + int result; + uint16_t vendor, product, version , source; + + /* version and source are optional */ + version = 0x0000; + source = 0x0002; + + result = sscanf(did, "bluetooth:%4hx:%4hx:%4hx", + &vendor, &product, &version); + if (result != EOF && result >= 2) { + source = 0x0001; + goto done; + } + + result = sscanf(did, "usb:%4hx:%4hx:%4hx", + &vendor, &product, &version); + if (result != EOF && result >= 2) + goto done; + + result = sscanf(did, "%4hx:%4hx:%4hx", &vendor, &product, &version); + if (result == EOF || result < 2) + return; + +done: + main_opts.did_source = source; + main_opts.did_vendor = vendor; + main_opts.did_product = product; + main_opts.did_version = version; +} + +static bt_gatt_cache_t parse_gatt_cache(const char *cache) +{ + if (!strcmp(cache, "always")) { + return BT_GATT_CACHE_ALWAYS; + } else if (!strcmp(cache, "yes")) { + return BT_GATT_CACHE_YES; + } else if (!strcmp(cache, "no")) { + return BT_GATT_CACHE_NO; + } else { + DBG("Invalid value for KeepCache=%s", cache); + return BT_GATT_CACHE_ALWAYS; + } +} + +static void check_options(GKeyFile *config, const char *group, + const char **options) +{ + char **keys; + int i; + + keys = g_key_file_get_keys(config, group, NULL, NULL); + + for (i = 0; keys != NULL && keys[i] != NULL; i++) { + bool found; + unsigned int j; + + found = false; + for (j = 0; options != NULL && options[j] != NULL; j++) { + if (g_str_equal(keys[i], options[j])) { + found = true; + break; + } + } + + if (!found) + warn("Unknown key %s for group %s in %s", + keys[i], group, main_conf_file_path); + } + + g_strfreev(keys); +} + +static void check_config(GKeyFile *config) +{ + char **keys; + int i; + const struct group_table *group; + + if (!config) + return; + + keys = g_key_file_get_groups(config, NULL); + + for (i = 0; keys != NULL && keys[i] != NULL; i++) { + bool match = false; + + for (group = valid_groups; group && group->name ; group++) { + if (g_str_equal(keys[i], group->name)) { + match = true; + break; + } + } + + if (!match) + warn("Unknown group %s in %s", keys[i], + main_conf_file_path); + } + + g_strfreev(keys); + + for (group = valid_groups; group && group->name; group++) + check_options(config, group->name, group->options); +} + +static int get_mode(const char *str) +{ + if (strcmp(str, "dual") == 0) + return BT_MODE_DUAL; + else if (strcmp(str, "bredr") == 0) + return BT_MODE_BREDR; + else if (strcmp(str, "le") == 0) + return BT_MODE_LE; + + error("Unknown controller mode \"%s\"", str); + + return BT_MODE_DUAL; +} + +static void parse_config(GKeyFile *config) +{ + GError *err = NULL; + char *str; + int val; + gboolean boolean; + + if (!config) + return; + + check_config(config); + + DBG("parsing %s", main_conf_file_path); + + val = g_key_file_get_integer(config, "General", + "DiscoverableTimeout", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("discovto=%d", val); + main_opts.discovto = val; + } + + boolean = g_key_file_get_boolean(config, "General", + "AlwaysPairable", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("pairable=%s", boolean ? "true" : "false"); + main_opts.pairable = boolean; + } + + val = g_key_file_get_integer(config, "General", + "PairableTimeout", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("pairto=%d", val); + main_opts.pairto = val; + } + + str = g_key_file_get_string(config, "General", "Privacy", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + main_opts.privacy = 0x00; + } else { + DBG("privacy=%s", str); + + if (!strcmp(str, "device")) + main_opts.privacy = 0x01; + else if (!strcmp(str, "off")) + main_opts.privacy = 0x00; + else { + DBG("Invalid privacy option: %s", str); + main_opts.privacy = 0x00; + } + + g_free(str); + } + + str = g_key_file_get_string(config, "General", "Name", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("name=%s", str); + g_free(main_opts.name); + main_opts.name = str; + } + + str = g_key_file_get_string(config, "General", "Class", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("class=%s", str); + main_opts.class = strtol(str, NULL, 16); + g_free(str); + } + + str = g_key_file_get_string(config, "General", "DeviceID", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("deviceid=%s", str); + parse_did(str); + g_free(str); + } + + boolean = g_key_file_get_boolean(config, "General", + "ReverseServiceDiscovery", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else + main_opts.reverse_discovery = boolean; + + boolean = g_key_file_get_boolean(config, "General", + "NameResolving", &err); + if (err) + g_clear_error(&err); + else + main_opts.name_resolv = boolean; + + boolean = g_key_file_get_boolean(config, "General", + "DebugKeys", &err); + if (err) + g_clear_error(&err); + else + main_opts.debug_keys = boolean; + + str = g_key_file_get_string(config, "General", "ControllerMode", &err); + if (err) { + g_clear_error(&err); + } else { + DBG("ControllerMode=%s", str); + main_opts.mode = get_mode(str); + g_free(str); + } + + str = g_key_file_get_string(config, "General", "MultiProfile", &err); + if (err) { + g_clear_error(&err); + } else { + DBG("MultiProfile=%s", str); + + if (!strcmp(str, "single")) + mps = MPS_SINGLE; + else if (!strcmp(str, "multiple")) + mps = MPS_MULTIPLE; + + g_free(str); + } + + boolean = g_key_file_get_boolean(config, "General", + "FastConnectable", &err); + if (err) + g_clear_error(&err); + else + main_opts.fast_conn = boolean; + + str = g_key_file_get_string(config, "GATT", "Cache", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + main_opts.gatt_cache = parse_gatt_cache(str); + g_free(str); + } + + val = g_key_file_get_integer(config, "GATT", "KeySize", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + DBG("KeySize=%d", val); + + if (val >=7 && val <= 16) + main_opts.key_size = val; + } + + val = g_key_file_get_integer(config, "GATT", "ExchangeMTU", &err); + if (err) { + DBG("%s", err->message); + g_clear_error(&err); + } else { + /* Ensure the mtu is within a valid range. */ + val = MIN(val, BT_ATT_MAX_LE_MTU); + val = MAX(val, BT_ATT_DEFAULT_LE_MTU); + DBG("ExchangeMTU=%d", val); + main_opts.gatt_mtu = val; + } +} + +static void init_defaults(void) +{ + uint8_t major, minor; + + /* Default HCId settings */ + memset(&main_opts, 0, sizeof(main_opts)); + main_opts.name = g_strdup_printf("BlueZ %s", VERSION); + main_opts.class = 0x000000; + main_opts.pairto = DEFAULT_PAIRABLE_TIMEOUT; + main_opts.discovto = DEFAULT_DISCOVERABLE_TIMEOUT; + main_opts.reverse_discovery = TRUE; + main_opts.name_resolv = TRUE; + main_opts.debug_keys = FALSE; + + if (sscanf(VERSION, "%hhu.%hhu", &major, &minor) != 2) + return; + + main_opts.did_source = 0x0002; /* USB */ + main_opts.did_vendor = 0x1d6b; /* Linux Foundation */ + main_opts.did_product = 0x0246; /* BlueZ */ + main_opts.did_version = (major << 8 | minor); + + main_opts.gatt_cache = BT_GATT_CACHE_ALWAYS; + main_opts.gatt_mtu = BT_ATT_MAX_LE_MTU; +} + +static void log_handler(const gchar *log_domain, GLogLevelFlags log_level, + const gchar *message, gpointer user_data) +{ + int priority; + + if (log_level & (G_LOG_LEVEL_ERROR | + G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)) + priority = 0x03; + else + priority = 0x06; + + btd_log(0xffff, priority, "GLib: %s", message); + btd_backtrace(0xffff); +} + +void btd_exit(void) +{ + mainloop_quit(); +} + +static gboolean quit_eventloop(gpointer user_data) +{ + btd_exit(); + return FALSE; +} + +static void signal_callback(int signum, void *user_data) +{ + static bool terminated = false; + + switch (signum) { + case SIGINT: + case SIGTERM: + if (!terminated) { + info("Terminating"); + g_timeout_add_seconds(SHUTDOWN_GRACE_SECONDS, + quit_eventloop, NULL); + + mainloop_sd_notify("STATUS=Powering down"); + adapter_shutdown(); + } + + terminated = true; + break; + case SIGUSR2: + __btd_toggle_debug(); + break; + } +} + +static char *option_debug = NULL; +static char *option_plugin = NULL; +static char *option_noplugin = NULL; +static char *option_configfile = NULL; +static gboolean option_compat = FALSE; +static gboolean option_detach = TRUE; +static gboolean option_version = FALSE; +static gboolean option_experimental = FALSE; + +static void free_options(void) +{ + g_free(option_debug); + option_debug = NULL; + + g_free(option_plugin); + option_plugin = NULL; + + g_free(option_noplugin); + option_noplugin = NULL; + + g_free(option_configfile); + option_configfile = NULL; +} + +static void disconnect_dbus(void) +{ + DBusConnection *conn = btd_get_dbus_connection(); + + if (!conn || !dbus_connection_get_is_connected(conn)) + return; + + g_dbus_detach_object_manager(conn); + set_dbus_connection(NULL); + + dbus_connection_unref(conn); +} + +static void disconnected_dbus(DBusConnection *conn, void *data) +{ + info("Disconnected from D-Bus. Exiting."); + mainloop_quit(); +} + +static int connect_dbus(void) +{ + DBusConnection *conn; + DBusError err; + + dbus_error_init(&err); + + conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, BLUEZ_NAME, &err); + if (!conn) { + if (dbus_error_is_set(&err)) { + g_printerr("D-Bus setup failed: %s\n", err.message); + dbus_error_free(&err); + return -EIO; + } + return -EALREADY; + } + + set_dbus_connection(conn); + + g_dbus_set_disconnect_function(conn, disconnected_dbus, NULL, NULL); + g_dbus_attach_object_manager(conn); + + return 0; +} + +static gboolean parse_debug(const char *key, const char *value, + gpointer user_data, GError **error) +{ + if (value) + option_debug = g_strdup(value); + else + option_debug = g_strdup("*"); + + return TRUE; +} + +static GOptionEntry options[] = { + { "debug", 'd', G_OPTION_FLAG_OPTIONAL_ARG, + G_OPTION_ARG_CALLBACK, parse_debug, + "Specify debug options to enable", "DEBUG" }, + { "plugin", 'p', 0, G_OPTION_ARG_STRING, &option_plugin, + "Specify plugins to load", "NAME,..," }, + { "noplugin", 'P', 0, G_OPTION_ARG_STRING, &option_noplugin, + "Specify plugins not to load", "NAME,..." }, + { "configfile", 'f', 0, G_OPTION_ARG_STRING, &option_configfile, + "Specify an explicit path to the config file", "FILE"}, + { "compat", 'C', 0, G_OPTION_ARG_NONE, &option_compat, + "Provide deprecated command line interfaces" }, + { "experimental", 'E', 0, G_OPTION_ARG_NONE, &option_experimental, + "Enable experimental interfaces" }, + { "nodetach", 'n', G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, &option_detach, + "Run with logging in foreground" }, + { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, + "Show version information and exit" }, + { NULL }, +}; + +int main(int argc, char *argv[]) +{ + GOptionContext *context; + GError *err = NULL; + uint16_t sdp_mtu = 0; + uint32_t sdp_flags = 0; + int gdbus_flags = 0; + + init_defaults(); + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) { + if (err != NULL) { + g_printerr("%s\n", err->message); + g_error_free(err); + } else + g_printerr("An unknown error occurred\n"); + exit(1); + } + + g_option_context_free(context); + + if (option_version == TRUE) { + printf("%s\n", VERSION); + exit(0); + } + + umask(0077); + + btd_backtrace_init(); + + mainloop_init(); + + __btd_log_init(option_debug, option_detach); + + g_log_set_handler("GLib", G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | + G_LOG_FLAG_RECURSION, + log_handler, NULL); + + mainloop_sd_notify("STATUS=Starting up"); + + if (option_configfile) + main_conf_file_path = option_configfile; + else + main_conf_file_path = CONFIGDIR "/main.conf"; + + main_conf = load_config(main_conf_file_path); + + parse_config(main_conf); + + if (connect_dbus() < 0) { + error("Unable to get on D-Bus"); + exit(1); + } + + if (option_experimental) + gdbus_flags = G_DBUS_FLAG_ENABLE_EXPERIMENTAL; + + g_dbus_set_flags(gdbus_flags); + + if (adapter_init() < 0) { + error("Adapter handling initialization failed"); + exit(1); + } + + btd_device_init(); + btd_agent_init(); + btd_profile_init(); + + if (main_opts.mode != BT_MODE_LE) { + if (option_compat == TRUE) + sdp_flags |= SDP_SERVER_COMPAT; + + start_sdp_server(sdp_mtu, sdp_flags); + + if (main_opts.did_source > 0) + register_device_id(main_opts.did_source, + main_opts.did_vendor, + main_opts.did_product, + main_opts.did_version); + } + + if (mps != MPS_OFF) + register_mps(mps == MPS_MULTIPLE); + + /* Loading plugins has to be done after D-Bus has been setup since + * the plugins might wanna expose some paths on the bus. However the + * best order of how to init various subsystems of the Bluetooth + * daemon needs to be re-worked. */ + plugin_init(option_plugin, option_noplugin); + + /* no need to keep parsed option in memory */ + free_options(); + + rfkill_init(); + + DBG("Entering main loop"); + + mainloop_sd_notify("STATUS=Running"); + mainloop_sd_notify("READY=1"); + + mainloop_run_with_signal(signal_callback, NULL); + + mainloop_sd_notify("STATUS=Quitting"); + + plugin_cleanup(); + + btd_profile_cleanup(); + btd_agent_cleanup(); + btd_device_cleanup(); + + adapter_cleanup(); + + rfkill_exit(); + + if (main_opts.mode != BT_MODE_LE) + stop_sdp_server(); + + if (main_conf) + g_key_file_free(main_conf); + + disconnect_dbus(); + + info("Exit"); + + __btd_log_cleanup(); + + return 0; +} diff --git a/src/main.conf b/src/main.conf new file mode 100644 index 0000000..40687a7 --- /dev/null +++ b/src/main.conf @@ -0,0 +1,119 @@ +[General] + +# Default adapter name +# Defaults to 'BlueZ X.YZ' +#Name = BlueZ + +# Default device class. Only the major and minor device class bits are +# considered. Defaults to '0x000000'. +#Class = 0x000100 + +# How long to stay in discoverable mode before going back to non-discoverable +# The value is in seconds. Default is 180, i.e. 3 minutes. +# 0 = disable timer, i.e. stay discoverable forever +#DiscoverableTimeout = 0 + +# Always allow pairing even if there are no agent registered +# Possible values: true, false +# Default: false +#AlwaysPairable = false + +# How long to stay in pairable mode before going back to non-discoverable +# The value is in seconds. Default is 0. +# 0 = disable timer, i.e. stay pairable forever +#PairableTimeout = 0 + +# Use vendor id source (assigner), vendor, product and version information for +# DID profile support. The values are separated by ":" and assigner, VID, PID +# and version. +# Possible vendor id source values: bluetooth, usb (defaults to usb) +#DeviceID = bluetooth:1234:5678:abcd + +# Do reverse service discovery for previously unknown devices that connect to +# us. For BR/EDR this option is really only needed for qualification since the +# BITE tester doesn't like us doing reverse SDP for some test cases, for LE +# this disables the GATT client functionally so it can be used in system which +# can only operate as peripheral. +# Defaults to 'true'. +#ReverseServiceDiscovery = true + +# Enable name resolving after inquiry. Set it to 'false' if you don't need +# remote devices name and want shorter discovery cycle. Defaults to 'true'. +#NameResolving = true + +# Enable runtime persistency of debug link keys. Default is false which +# makes debug link keys valid only for the duration of the connection +# that they were created for. +#DebugKeys = false + +# Restricts all controllers to the specified transport. Default value +# is "dual", i.e. both BR/EDR and LE enabled (when supported by the HW). +# Possible values: "dual", "bredr", "le" +#ControllerMode = dual + +# Enables Multi Profile Specification support. This allows to specify if +# system supports only Multiple Profiles Single Device (MPSD) configuration +# or both Multiple Profiles Single Device (MPSD) and Multiple Profiles Multiple +# Devices (MPMD) configurations. +# Possible values: "off", "single", "multiple" +#MultiProfile = off + +# Permanently enables the Fast Connectable setting for adapters that +# support it. When enabled other devices can connect faster to us, +# however the tradeoff is increased power consumptions. This feature +# will fully work only on kernel version 4.1 and newer. Defaults to +# 'false'. +#FastConnectable = false + +# Default privacy setting. +# Enables use of private address. +# Possible values: "off", "device", "network" +# "network" option not supported currently +# Defaults to "off" +# Privacy = off + +[GATT] +# GATT attribute cache. +# Possible values: +# always: Always cache attributes even for devices not paired, this is +# recommended as it is best for interoperability, with more consistent +# reconnection times and enables proper tracking of notifications for all +# devices. +# yes: Only cache attributes of paired devices. +# no: Never cache attributes +# Default: always +#Cache = always + +# Minimum required Encryption Key Size for accessing secured characteristics. +# Possible values: 0 and 7-16. 0 means don't care. +# Defaults to 0 +#KeySize = 0 + +# Exchange MTU size. +# Possible values: 23-517 +# Defaults to 517 +#ExchangeMTU = 517 + +[Policy] +# +# The ReconnectUUIDs defines the set of remote services that should try +# to be reconnected to in case of a link loss (link supervision +# timeout). The policy plugin should contain a sane set of values by +# default, but this list can be overridden here. By setting the list to +# empty the reconnection feature gets disabled. +#ReconnectUUIDs=00001112-0000-1000-8000-00805f9b34fb,0000111f-0000-1000-8000-00805f9b34fb,0000110a-0000-1000-8000-00805f9b34fb + +# ReconnectAttempts define the number of attempts to reconnect after a link +# lost. Setting the value to 0 disables reconnecting feature. +#ReconnectAttempts=7 + +# ReconnectIntervals define the set of intervals in seconds to use in between +# attempts. +# If the number of attempts defined in ReconnectAttempts is bigger than the +# set of intervals the last interval is repeated until the last attempt. +#ReconnectIntervals=1,2,4,8,16,32,64 + +# AutoEnable defines option to enable all controllers when they are found. +# This includes adapters present on start as well as adapters that are plugged +# in later on. Defaults to 'false'. +#AutoEnable=false diff --git a/src/org.bluez.service b/src/org.bluez.service new file mode 100644 index 0000000..dd7ae8f --- /dev/null +++ b/src/org.bluez.service @@ -0,0 +1,5 @@ +[D-BUS Service] +Name=org.bluez +Exec=/bin/false +User=root +SystemdService=dbus-org.bluez.service diff --git a/src/oui.c b/src/oui.c new file mode 100644 index 0000000..8059c0a --- /dev/null +++ b/src/oui.c @@ -0,0 +1,75 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include "lib/bluetooth.h" +#include "oui.h" + +#ifdef HAVE_UDEV_HWDB_NEW +#include + +char *batocomp(const bdaddr_t *ba) +{ + struct udev *udev; + struct udev_hwdb *hwdb; + struct udev_list_entry *head, *entry; + char modalias[11], *comp = NULL; + + sprintf(modalias, "OUI:%2.2X%2.2X%2.2X", ba->b[5], ba->b[4], ba->b[3]); + + udev = udev_new(); + if (!udev) + return NULL; + + hwdb = udev_hwdb_new(udev); + if (!hwdb) + goto done; + + head = udev_hwdb_get_properties_list_entry(hwdb, modalias, 0); + + udev_list_entry_foreach(entry, head) { + const char *name = udev_list_entry_get_name(entry); + + if (name && !strcmp(name, "ID_OUI_FROM_DATABASE")) { + comp = strdup(udev_list_entry_get_value(entry)); + break; + } + } + + hwdb = udev_hwdb_unref(hwdb); + +done: + udev = udev_unref(udev); + + return comp; +} +#else +char *batocomp(const bdaddr_t *ba) +{ + return NULL; +} +#endif diff --git a/src/oui.h b/src/oui.h new file mode 100644 index 0000000..2ddc27f --- /dev/null +++ b/src/oui.h @@ -0,0 +1,24 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +char *batocomp(const bdaddr_t *ba); diff --git a/src/plugin.c b/src/plugin.c new file mode 100644 index 0000000..39310a7 --- /dev/null +++ b/src/plugin.c @@ -0,0 +1,239 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" + +#include "btio/btio.h" +#include "src/plugin.h" +#include "src/log.h" +#include "src/hcid.h" + +static GSList *plugins = NULL; + +struct bluetooth_plugin { + void *handle; + gboolean active; + struct bluetooth_plugin_desc *desc; +}; + +static int compare_priority(gconstpointer a, gconstpointer b) +{ + const struct bluetooth_plugin *plugin1 = a; + const struct bluetooth_plugin *plugin2 = b; + + return plugin2->desc->priority - plugin1->desc->priority; +} + +static gboolean add_plugin(void *handle, struct bluetooth_plugin_desc *desc) +{ + struct bluetooth_plugin *plugin; + + if (desc->init == NULL) + return FALSE; + + if (g_str_equal(desc->version, VERSION) == FALSE) { + error("Version mismatch for %s", desc->name); + return FALSE; + } + + DBG("Loading %s plugin", desc->name); + + plugin = g_try_new0(struct bluetooth_plugin, 1); + if (plugin == NULL) + return FALSE; + + plugin->handle = handle; + plugin->active = FALSE; + plugin->desc = desc; + + __btd_enable_debug(desc->debug_start, desc->debug_stop); + + plugins = g_slist_insert_sorted(plugins, plugin, compare_priority); + + return TRUE; +} + +static gboolean enable_plugin(const char *name, char **cli_enable, + char **cli_disable) +{ + if (cli_disable) { + for (; *cli_disable; cli_disable++) + if (g_pattern_match_simple(*cli_disable, name)) + break; + if (*cli_disable) { + info("Excluding (cli) %s", name); + return FALSE; + } + } + + if (cli_enable) { + for (; *cli_enable; cli_enable++) + if (g_pattern_match_simple(*cli_enable, name)) + break; + if (!*cli_enable) { + info("Ignoring (cli) %s", name); + return FALSE; + } + } + + return TRUE; +} + +#include "src/builtin.h" + +gboolean plugin_init(const char *enable, const char *disable) +{ + GSList *list; + GDir *dir; + const char *file; + char **cli_disabled, **cli_enabled; + unsigned int i; + + /* Make a call to BtIO API so its symbols got resolved before the + * plugins are loaded. */ + bt_io_error_quark(); + + if (enable) + cli_enabled = g_strsplit_set(enable, ", ", -1); + else + cli_enabled = NULL; + + if (disable) + cli_disabled = g_strsplit_set(disable, ", ", -1); + else + cli_disabled = NULL; + + DBG("Loading builtin plugins"); + + for (i = 0; __bluetooth_builtin[i]; i++) { + if (!enable_plugin(__bluetooth_builtin[i]->name, cli_enabled, + cli_disabled)) + continue; + + add_plugin(NULL, __bluetooth_builtin[i]); + } + + if (strlen(PLUGINDIR) == 0) + goto start; + + DBG("Loading plugins %s", PLUGINDIR); + + dir = g_dir_open(PLUGINDIR, 0, NULL); + if (!dir) + goto start; + + while ((file = g_dir_read_name(dir)) != NULL) { + struct bluetooth_plugin_desc *desc; + void *handle; + char *filename; + + if (g_str_has_prefix(file, "lib") == TRUE || + g_str_has_suffix(file, ".so") == FALSE) + continue; + + filename = g_build_filename(PLUGINDIR, file, NULL); + + handle = dlopen(filename, RTLD_NOW); + if (handle == NULL) { + error("Can't load plugin %s: %s", filename, + dlerror()); + g_free(filename); + continue; + } + + g_free(filename); + + desc = dlsym(handle, "bluetooth_plugin_desc"); + if (desc == NULL) { + error("Can't load plugin description: %s", dlerror()); + dlclose(handle); + continue; + } + + if (!enable_plugin(desc->name, cli_enabled, cli_disabled)) { + dlclose(handle); + continue; + } + + if (add_plugin(handle, desc) == FALSE) + dlclose(handle); + } + + g_dir_close(dir); + +start: + for (list = plugins; list; list = list->next) { + struct bluetooth_plugin *plugin = list->data; + int err; + + err = plugin->desc->init(); + if (err < 0) { + if (err == -ENOSYS) + warn("System does not support %s plugin", + plugin->desc->name); + else + error("Failed to init %s plugin", + plugin->desc->name); + continue; + } + + plugin->active = TRUE; + } + + g_strfreev(cli_enabled); + g_strfreev(cli_disabled); + + return TRUE; +} + +void plugin_cleanup(void) +{ + GSList *list; + + DBG("Cleanup plugins"); + + for (list = plugins; list; list = list->next) { + struct bluetooth_plugin *plugin = list->data; + + if (plugin->active == TRUE && plugin->desc->exit) + plugin->desc->exit(); + + if (plugin->handle != NULL) + dlclose(plugin->handle); + + g_free(plugin); + } + + g_slist_free(plugins); +} diff --git a/src/plugin.h b/src/plugin.h new file mode 100644 index 0000000..89c7b85 --- /dev/null +++ b/src/plugin.h @@ -0,0 +1,54 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#define BLUETOOTH_PLUGIN_PRIORITY_LOW -100 +#define BLUETOOTH_PLUGIN_PRIORITY_DEFAULT 0 +#define BLUETOOTH_PLUGIN_PRIORITY_HIGH 100 + +struct bluetooth_plugin_desc { + const char *name; + const char *version; + int priority; + int (*init) (void); + void (*exit) (void); + void *debug_start; + void *debug_stop; +}; + +#ifdef BLUETOOTH_PLUGIN_BUILTIN +#define BLUETOOTH_PLUGIN_DEFINE(name, version, priority, init, exit) \ + struct bluetooth_plugin_desc __bluetooth_builtin_ ## name = { \ + #name, version, priority, init, exit \ + }; +#else +#define BLUETOOTH_PLUGIN_DEFINE(name, version, priority, init, exit) \ + extern struct btd_debug_desc __start___debug[] \ + __attribute__ ((weak, visibility("hidden"))); \ + extern struct btd_debug_desc __stop___debug[] \ + __attribute__ ((weak, visibility("hidden"))); \ + extern struct bluetooth_plugin_desc bluetooth_plugin_desc \ + __attribute__ ((visibility("default"))); \ + struct bluetooth_plugin_desc bluetooth_plugin_desc = { \ + #name, version, priority, init, exit, \ + __start___debug, __stop___debug \ + }; +#endif diff --git a/src/profile.c b/src/profile.c new file mode 100644 index 0000000..192fd02 --- /dev/null +++ b/src/profile.c @@ -0,0 +1,2529 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "btio/btio.h" +#include "sdpd.h" +#include "log.h" +#include "error.h" +#include "uuid-helper.h" +#include "dbus-common.h" +#include "sdp-client.h" +#include "sdp-xml.h" +#include "adapter.h" +#include "device.h" +#include "profile.h" +#include "service.h" + +#define DUN_DEFAULT_CHANNEL 1 +#define SPP_DEFAULT_CHANNEL 3 +#define HFP_HF_DEFAULT_CHANNEL 7 +#define OPP_DEFAULT_CHANNEL 9 +#define FTP_DEFAULT_CHANNEL 10 +#define BIP_DEFAULT_CHANNEL 11 +#define HSP_AG_DEFAULT_CHANNEL 12 +#define HFP_AG_DEFAULT_CHANNEL 13 +#define SYNC_DEFAULT_CHANNEL 14 +#define PBAP_DEFAULT_CHANNEL 15 +#define MAS_DEFAULT_CHANNEL 16 +#define MNS_DEFAULT_CHANNEL 17 + +#define BTD_PROFILE_PSM_AUTO -1 +#define BTD_PROFILE_CHAN_AUTO -1 + +#define HFP_HF_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define HFP_AG_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define HSP_AG_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define SPP_RECORD \ + " \ + \ + \ + \ + \ + %s \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define DUN_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define OPP_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define FTP_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define PCE_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define PSE_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define MAS_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define MNS_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define SYNC_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + +#define GENERIC_RECORD \ + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + %s \ + \ + %s \ + \ + \ + \ + \ + \ + \ + \ + %s \ + \ + \ + \ + " + +struct ext_io; + +struct ext_profile { + struct btd_profile p; + + char *name; + char *owner; + char *path; + char *uuid; + char *service; + char *role; + + char *record; + char *(*get_record)(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm); + + char *remote_uuid; + + guint id; + + BtIOMode mode; + BtIOSecLevel sec_level; + bool authorize; + + bool enable_client; + bool enable_server; + + int local_psm; + int local_chan; + + uint16_t remote_psm; + uint8_t remote_chan; + + uint16_t version; + uint16_t features; + + GSList *records; + GSList *servers; + GSList *conns; + + GSList *connects; +}; + +struct ext_io { + struct ext_profile *ext; + int proto; + GIOChannel *io; + guint io_id; + struct btd_adapter *adapter; + struct btd_device *device; + struct btd_service *service; + + bool resolving; + bool connected; + + uint16_t version; + uint16_t features; + + uint16_t psm; + uint8_t chan; + + guint auth_id; + DBusPendingCall *pending; +}; + +struct ext_record { + struct btd_adapter *adapter; + uint32_t handle; +}; + +struct btd_profile_custom_property { + char *uuid; + char *type; + char *name; + btd_profile_prop_exists exists; + btd_profile_prop_get get; + void *user_data; +}; + +static GSList *custom_props = NULL; + +static GSList *profiles = NULL; +static GSList *ext_profiles = NULL; + +void btd_profile_foreach(void (*func)(struct btd_profile *p, void *data), + void *data) +{ + GSList *l, *next; + + for (l = profiles; l != NULL; l = next) { + struct btd_profile *profile = l->data; + + next = g_slist_next(l); + + func(profile, data); + } + + for (l = ext_profiles; l != NULL; l = next) { + struct ext_profile *profile = l->data; + + next = g_slist_next(l); + + func(&profile->p, data); + } +} + +int btd_profile_register(struct btd_profile *profile) +{ + profiles = g_slist_append(profiles, profile); + return 0; +} + +void btd_profile_unregister(struct btd_profile *profile) +{ + profiles = g_slist_remove(profiles, profile); +} + +static struct ext_profile *find_ext_profile(const char *owner, + const char *path) +{ + GSList *l; + + for (l = ext_profiles; l != NULL; l = g_slist_next(l)) { + struct ext_profile *ext = l->data; + + if (g_strcmp0(ext->owner, owner)) + continue; + + if (!g_strcmp0(ext->path, path)) + return ext; + } + + return NULL; +} + +static void ext_io_destroy(gpointer p) +{ + struct ext_io *ext_io = p; + + if (ext_io->io_id > 0) + g_source_remove(ext_io->io_id); + + if (ext_io->io) { + g_io_channel_shutdown(ext_io->io, FALSE, NULL); + g_io_channel_unref(ext_io->io); + } + + if (ext_io->auth_id != 0) + btd_cancel_authorization(ext_io->auth_id); + + if (ext_io->pending) { + dbus_pending_call_cancel(ext_io->pending); + dbus_pending_call_unref(ext_io->pending); + } + + if (ext_io->resolving) + bt_cancel_discovery(btd_adapter_get_address(ext_io->adapter), + device_get_address(ext_io->device)); + + if (ext_io->adapter) + btd_adapter_unref(ext_io->adapter); + + if (ext_io->device) + btd_device_unref(ext_io->device); + + if (ext_io->service) + btd_service_unref(ext_io->service); + + g_free(ext_io); +} + +static gboolean ext_io_disconnected(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct ext_io *conn = user_data; + struct ext_profile *ext = conn->ext; + GError *gerr = NULL; + char addr[18]; + + if (cond & G_IO_NVAL) + return FALSE; + + bt_io_get(io, &gerr, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID); + if (gerr != NULL) { + error("Unable to get io data for %s: %s", + ext->name, gerr->message); + g_error_free(gerr); + goto drop; + } + + DBG("%s disconnected from %s", ext->name, addr); +drop: + if (conn->service) { + if (btd_service_get_state(conn->service) == + BTD_SERVICE_STATE_CONNECTING) + btd_service_connecting_complete(conn->service, -EIO); + else + btd_service_disconnecting_complete(conn->service, 0); + } + + ext->conns = g_slist_remove(ext->conns, conn); + ext_io_destroy(conn); + return FALSE; +} + +static void new_conn_reply(DBusPendingCall *call, void *user_data) +{ + struct ext_io *conn = user_data; + struct ext_profile *ext = conn->ext; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError err; + + dbus_error_init(&err); + dbus_set_error_from_message(&err, reply); + + dbus_message_unref(reply); + + dbus_pending_call_unref(conn->pending); + conn->pending = NULL; + + if (!dbus_error_is_set(&err)) { + if (conn->service) + btd_service_connecting_complete(conn->service, 0); + + conn->connected = true; + return; + } + + error("%s replied with an error: %s, %s", ext->name, + err.name, err.message); + + if (conn->service) + btd_service_connecting_complete(conn->service, -ECONNREFUSED); + + dbus_error_free(&err); + + ext->conns = g_slist_remove(ext->conns, conn); + ext_io_destroy(conn); +} + +static void disconn_reply(DBusPendingCall *call, void *user_data) +{ + struct ext_io *conn = user_data; + struct ext_profile *ext = conn->ext; + DBusMessage *reply = dbus_pending_call_steal_reply(call); + DBusError err; + + dbus_error_init(&err); + dbus_set_error_from_message(&err, reply); + + dbus_message_unref(reply); + + dbus_pending_call_unref(conn->pending); + conn->pending = NULL; + + if (!dbus_error_is_set(&err)) { + if (conn->service) + btd_service_disconnecting_complete(conn->service, 0); + + goto disconnect; + } + + error("%s replied with an error: %s, %s", ext->name, + err.name, err.message); + + if (conn->service) + btd_service_disconnecting_complete(conn->service, + -ECONNREFUSED); + + dbus_error_free(&err); + +disconnect: + ext->conns = g_slist_remove(ext->conns, conn); + ext_io_destroy(conn); +} + +struct prop_append_data { + DBusMessageIter *dict; + struct ext_io *io; +}; + +static void append_prop(gpointer a, gpointer b) +{ + struct btd_profile_custom_property *p = a; + struct prop_append_data *data = b; + DBusMessageIter entry, value, *dict = data->dict; + struct btd_device *dev = data->io->device; + struct ext_profile *ext = data->io->ext; + const char *uuid = ext->service ? ext->service : ext->uuid; + + if (strcasecmp(p->uuid, uuid) != 0) + return; + + if (p->exists && !p->exists(p->uuid, dev, p->user_data)) + return; + + dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &p->name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, p->type, + &value); + + p->get(p->uuid, dev, &value, p->user_data); + + dbus_message_iter_close_container(&entry, &value); + dbus_message_iter_close_container(dict, &entry); +} + +static uint16_t get_supported_features(const sdp_record_t *rec) +{ + sdp_data_t *data; + + data = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES); + if (!data || data->dtd != SDP_UINT16) + return 0; + + return data->val.uint16; +} + +static uint16_t get_profile_version(const sdp_record_t *rec) +{ + sdp_list_t *descs; + uint16_t version; + + if (sdp_get_profile_descs(rec, &descs) < 0) + return 0; + + if (descs && descs->data) { + sdp_profile_desc_t *desc = descs->data; + version = desc->version; + } else { + version = 0; + } + + sdp_list_free(descs, free); + + return version; +} + +static bool send_new_connection(struct ext_profile *ext, struct ext_io *conn) +{ + DBusMessage *msg; + DBusMessageIter iter, dict; + struct prop_append_data data = { &dict, conn }; + const char *remote_uuid = ext->remote_uuid; + const sdp_record_t *rec; + const char *path; + int fd; + + msg = dbus_message_new_method_call(ext->owner, ext->path, + "org.bluez.Profile1", + "NewConnection"); + if (!msg) { + error("Unable to create NewConnection call for %s", ext->name); + return false; + } + + if (remote_uuid) { + rec = btd_device_get_record(conn->device, remote_uuid); + if (rec) { + conn->features = get_supported_features(rec); + conn->version = get_profile_version(rec); + } + } + + dbus_message_iter_init_append(msg, &iter); + + path = device_get_path(conn->device); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); + + fd = g_io_channel_unix_get_fd(conn->io); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_UNIX_FD, &fd); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + + if (conn->version) + dict_append_entry(&dict, "Version", DBUS_TYPE_UINT16, + &conn->version); + + if (conn->features) + dict_append_entry(&dict, "Features", DBUS_TYPE_UINT16, + &conn->features); + + g_slist_foreach(custom_props, append_prop, &data); + + dbus_message_iter_close_container(&iter, &dict); + + if (!g_dbus_send_message_with_reply(btd_get_dbus_connection(), + msg, &conn->pending, -1)) { + error("%s: sending NewConnection failed", ext->name); + dbus_message_unref(msg); + return false; + } + + dbus_message_unref(msg); + + dbus_pending_call_set_notify(conn->pending, new_conn_reply, conn, NULL); + + return true; +} + +static void ext_connect(GIOChannel *io, GError *err, gpointer user_data) +{ + struct ext_io *conn = user_data; + struct ext_profile *ext = conn->ext; + GError *io_err = NULL; + char addr[18]; + + if (!bt_io_get(io, &io_err, + BT_IO_OPT_DEST, addr, + BT_IO_OPT_INVALID)) { + error("Unable to get connect data for %s: %s", ext->name, + io_err->message); + if (err) { + g_error_free(io_err); + io_err = NULL; + } else { + err = io_err; + } + goto drop; + } + + if (err != NULL) { + error("%s failed to connect to %s: %s", ext->name, addr, + err->message); + goto drop; + } + + DBG("%s connected to %s", ext->name, addr); + + if (conn->io_id == 0) { + GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; + conn->io_id = g_io_add_watch(io, cond, ext_io_disconnected, + conn); + } + + if (conn->service && service_set_connecting(conn->service) < 0) + goto drop; + + if (send_new_connection(ext, conn)) + return; + +drop: + if (conn->service) + btd_service_connecting_complete(conn->service, + err ? -err->code : -EIO); + + if (io_err) + g_error_free(io_err); + + ext->conns = g_slist_remove(ext->conns, conn); + ext_io_destroy(conn); +} + +static void ext_auth(DBusError *err, void *user_data) +{ + struct ext_io *conn = user_data; + struct ext_profile *ext = conn->ext; + GError *gerr = NULL; + char addr[18]; + + conn->auth_id = 0; + + bt_io_get(conn->io, &gerr, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID); + if (gerr != NULL) { + error("Unable to get connect data for %s: %s", + ext->name, gerr->message); + g_error_free(gerr); + goto drop; + } + + if (err && dbus_error_is_set(err)) { + error("%s rejected %s: %s", ext->name, addr, err->message); + goto drop; + } + + if (!bt_io_accept(conn->io, ext_connect, conn, NULL, &gerr)) { + error("bt_io_accept: %s", gerr->message); + g_error_free(gerr); + goto drop; + } + + DBG("%s authorized to connect to %s", addr, ext->name); + + return; + +drop: + ext->conns = g_slist_remove(ext->conns, conn); + ext_io_destroy(conn); +} + +static struct ext_io *create_conn(struct ext_io *server, GIOChannel *io, + bdaddr_t *src, bdaddr_t *dst) +{ + struct btd_device *device; + struct btd_service *service; + struct ext_io *conn; + GIOCondition cond; + char addr[18]; + + device = btd_adapter_find_device(server->adapter, dst, BDADDR_BREDR); + if (device == NULL) { + ba2str(dst, addr); + error("%s device %s not found", server->ext->name, addr); + return NULL; + } + + /* Do not add UUID if client role is not enabled */ + if (!server->ext->enable_client) { + service = NULL; + goto done; + } + + btd_device_add_uuid(device, server->ext->remote_uuid); + service = btd_device_get_service(device, server->ext->remote_uuid); + if (service == NULL) { + ba2str(dst, addr); + error("%s service not found for device %s", server->ext->name, + addr); + return NULL; + } + +done: + conn = g_new0(struct ext_io, 1); + conn->io = g_io_channel_ref(io); + conn->proto = server->proto; + conn->ext = server->ext; + conn->adapter = btd_adapter_ref(server->adapter); + conn->device = btd_device_ref(device); + + if (service) + conn->service = btd_service_ref(service); + + cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; + conn->io_id = g_io_add_watch(io, cond, ext_io_disconnected, conn); + + return conn; +} + +static void ext_confirm(GIOChannel *io, gpointer user_data) +{ + struct ext_io *server = user_data; + struct ext_profile *ext = server->ext; + const char *uuid = ext->service ? ext->service : ext->uuid; + struct ext_io *conn; + GError *gerr = NULL; + bdaddr_t src, dst; + char addr[18]; + + bt_io_get(io, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_DEST, addr, + BT_IO_OPT_INVALID); + if (gerr != NULL) { + error("%s failed to get connect data: %s", ext->name, + gerr->message); + g_error_free(gerr); + return; + } + + DBG("incoming connect from %s", addr); + + conn = create_conn(server, io, &src, &dst); + if (conn == NULL) + return; + + conn->auth_id = btd_request_authorization(&src, &dst, uuid, ext_auth, + conn); + if (conn->auth_id == 0) { + error("%s authorization failure", ext->name); + ext_io_destroy(conn); + return; + } + + ext->conns = g_slist_append(ext->conns, conn); + + DBG("%s authorizing connection from %s", ext->name, addr); +} + +static void ext_direct_connect(GIOChannel *io, GError *err, gpointer user_data) +{ + struct ext_io *server = user_data; + struct ext_profile *ext = server->ext; + GError *gerr = NULL; + struct ext_io *conn; + bdaddr_t src, dst; + + bt_io_get(io, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &src, + BT_IO_OPT_DEST_BDADDR, &dst, + BT_IO_OPT_INVALID); + if (gerr != NULL) { + error("%s failed to get connect data: %s", ext->name, + gerr->message); + g_error_free(gerr); + return; + } + + conn = create_conn(server, io, &src, &dst); + if (conn == NULL) + return; + + ext->conns = g_slist_append(ext->conns, conn); + + ext_connect(io, err, conn); +} + +static uint32_t ext_register_record(struct ext_profile *ext, + struct ext_io *l2cap, + struct ext_io *rfcomm, + struct btd_adapter *a) +{ + sdp_record_t *rec; + char *dyn_record = NULL; + const char *record = ext->record; + + if (!record && ext->get_record) { + dyn_record = ext->get_record(ext, l2cap, rfcomm); + record = dyn_record; + } + + if (!record) + return 0; + + rec = sdp_xml_parse_record(record, strlen(record)); + + g_free(dyn_record); + + if (!rec) { + error("Unable to parse record for %s", ext->name); + return 0; + } + + if (adapter_service_add(a, rec) < 0) { + error("Failed to register service record"); + sdp_record_free(rec); + return 0; + } + + return rec->handle; +} + +static uint32_t ext_start_servers(struct ext_profile *ext, + struct btd_adapter *adapter) +{ + struct ext_io *l2cap = NULL; + struct ext_io *rfcomm = NULL; + BtIOConfirm confirm; + BtIOConnect connect; + GError *err = NULL; + GIOChannel *io; + + if (ext->authorize) { + confirm = ext_confirm; + connect = NULL; + } else { + confirm = NULL; + connect = ext_direct_connect; + } + + if (ext->local_psm) { + uint16_t psm; + + if (ext->local_psm > 0) + psm = ext->local_psm; + else + psm = 0; + + l2cap = g_new0(struct ext_io, 1); + l2cap->ext = ext; + + io = bt_io_listen(connect, confirm, l2cap, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(adapter), + BT_IO_OPT_MODE, ext->mode, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_SEC_LEVEL, ext->sec_level, + BT_IO_OPT_INVALID); + if (err != NULL) { + error("L2CAP server failed for %s: %s", + ext->name, err->message); + g_free(l2cap); + l2cap = NULL; + g_clear_error(&err); + goto failed; + } else { + if (psm == 0) + bt_io_get(io, NULL, BT_IO_OPT_PSM, &psm, + BT_IO_OPT_INVALID); + l2cap->io = io; + l2cap->proto = BTPROTO_L2CAP; + l2cap->psm = psm; + l2cap->adapter = btd_adapter_ref(adapter); + ext->servers = g_slist_append(ext->servers, l2cap); + DBG("%s listening on PSM %u", ext->name, psm); + } + } + + if (ext->local_chan) { + uint8_t chan; + + if (ext->local_chan > 0) + chan = ext->local_chan; + else + chan = 0; + + rfcomm = g_new0(struct ext_io, 1); + rfcomm->ext = ext; + + io = bt_io_listen(connect, confirm, rfcomm, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(adapter), + BT_IO_OPT_CHANNEL, chan, + BT_IO_OPT_SEC_LEVEL, ext->sec_level, + BT_IO_OPT_INVALID); + if (err != NULL) { + error("RFCOMM server failed for %s: %s", + ext->name, err->message); + g_free(rfcomm); + g_clear_error(&err); + goto failed; + } else { + if (chan == 0) + bt_io_get(io, NULL, BT_IO_OPT_CHANNEL, &chan, + BT_IO_OPT_INVALID); + rfcomm->io = io; + rfcomm->proto = BTPROTO_RFCOMM; + rfcomm->chan = chan; + rfcomm->adapter = btd_adapter_ref(adapter); + ext->servers = g_slist_append(ext->servers, rfcomm); + DBG("%s listening on chan %u", ext->name, chan); + } + } + + return ext_register_record(ext, l2cap, rfcomm, adapter); + +failed: + if (l2cap) { + ext->servers = g_slist_remove(ext->servers, l2cap); + ext_io_destroy(l2cap); + } + + return 0; +} + +static struct ext_profile *find_ext(struct btd_profile *p) +{ + GSList *l; + + l = g_slist_find(ext_profiles, p); + if (!l) + return NULL; + + return l->data; +} + +static int ext_adapter_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct ext_profile *ext; + struct ext_record *rec; + uint32_t handle; + + ext = find_ext(p); + if (!ext) + return -ENOENT; + + DBG("\"%s\" probed", ext->name); + + handle = ext_start_servers(ext, adapter); + if (!handle) + return 0; + + rec = g_new0(struct ext_record, 1); + rec->adapter = btd_adapter_ref(adapter); + rec->handle = handle; + + ext->records = g_slist_append(ext->records, rec); + + return 0; +} + +static void ext_remove_records(struct ext_profile *ext, + struct btd_adapter *adapter) +{ + GSList *l, *next; + + for (l = ext->records; l != NULL; l = next) { + struct ext_record *r = l->data; + + next = g_slist_next(l); + + if (adapter && r->adapter != adapter) + continue; + + ext->records = g_slist_remove(ext->records, r); + + adapter_service_remove(adapter, r->handle); + btd_adapter_unref(r->adapter); + g_free(r); + } +} + +static void ext_adapter_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct ext_profile *ext; + GSList *l, *next; + + ext = find_ext(p); + if (!ext) + return; + + DBG("\"%s\" removed", ext->name); + + ext_remove_records(ext, adapter); + + for (l = ext->servers; l != NULL; l = next) { + struct ext_io *server = l->data; + + next = g_slist_next(l); + + if (server->adapter != adapter) + continue; + + ext->servers = g_slist_remove(ext->servers, server); + ext_io_destroy(server); + } +} + +static int ext_device_probe(struct btd_service *service) +{ + struct btd_profile *p = btd_service_get_profile(service); + struct ext_profile *ext; + + ext = find_ext(p); + if (!ext) + return -ENOENT; + + DBG("%s probed with UUID %s", ext->name, p->remote_uuid); + + return 0; +} + +static struct ext_io *find_connection(struct ext_profile *ext, + struct btd_device *dev) +{ + GSList *l; + + for (l = ext->conns; l != NULL; l = g_slist_next(l)) { + struct ext_io *conn = l->data; + + if (conn->device == dev) + return conn; + } + + return NULL; +} + +static void ext_device_remove(struct btd_service *service) +{ + struct btd_profile *p = btd_service_get_profile(service); + struct btd_device *dev = btd_service_get_device(service); + struct ext_profile *ext; + struct ext_io *conn; + + ext = find_ext(p); + if (!ext) + return; + + DBG("%s", ext->name); + + conn = find_connection(ext, dev); + if (conn) { + ext->conns = g_slist_remove(ext->conns, conn); + ext_io_destroy(conn); + } +} + +static int connect_io(struct ext_io *conn, const bdaddr_t *src, + const bdaddr_t *dst) +{ + struct ext_profile *ext = conn->ext; + GError *gerr = NULL; + GIOChannel *io; + + if (conn->psm) { + conn->proto = BTPROTO_L2CAP; + io = bt_io_connect(ext_connect, conn, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_SEC_LEVEL, ext->sec_level, + BT_IO_OPT_PSM, conn->psm, + BT_IO_OPT_INVALID); + } else { + conn->proto = BTPROTO_RFCOMM; + io = bt_io_connect(ext_connect, conn, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_SEC_LEVEL, ext->sec_level, + BT_IO_OPT_CHANNEL, conn->chan, + BT_IO_OPT_INVALID); + } + + if (gerr != NULL) { + error("Unable to connect %s: %s", ext->name, gerr->message); + g_error_free(gerr); + return -EIO; + } + + conn->io = io; + + return 0; +} + +static uint16_t get_goep_l2cap_psm(sdp_record_t *rec) +{ + sdp_data_t *data; + + data = sdp_data_get(rec, SDP_ATTR_GOEP_L2CAP_PSM); + if (!data) + return 0; + + if (data->dtd != SDP_UINT16) + return 0; + + /* PSM must be odd and lsb of upper byte must be 0 */ + if ((data->val.uint16 & 0x0101) != 0x0001) + return 0; + + return data->val.uint16; +} + +static void record_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct ext_io *conn = user_data; + struct ext_profile *ext = conn->ext; + sdp_list_t *r; + + conn->resolving = false; + + if (err < 0) { + error("Unable to get %s SDP record: %s", ext->name, + strerror(-err)); + goto failed; + } + + if (!recs || !recs->data) { + error("No SDP records found for %s", ext->name); + err = -ENOTSUP; + goto failed; + } + + for (r = recs; r != NULL; r = r->next) { + sdp_record_t *rec = r->data; + sdp_list_t *protos; + int port; + + if (sdp_get_access_protos(rec, &protos) < 0) { + error("Unable to get proto list from %s record", + ext->name); + err = -ENOTSUP; + goto failed; + } + + port = sdp_get_proto_port(protos, L2CAP_UUID); + if (port > 0) + conn->psm = port; + + port = sdp_get_proto_port(protos, RFCOMM_UUID); + if (port > 0) + conn->chan = port; + + if (conn->psm == 0 && sdp_get_proto_desc(protos, OBEX_UUID)) + conn->psm = get_goep_l2cap_psm(rec); + + conn->features = get_supported_features(rec); + conn->version = get_profile_version(rec); + + sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, + NULL); + sdp_list_free(protos, NULL); + + if (conn->chan || conn->psm) + break; + } + + if (!conn->chan && !conn->psm) { + error("Failed to find L2CAP PSM or RFCOMM channel for %s", + ext->name); + err = -ENOTSUP; + goto failed; + } + + err = connect_io(conn, btd_adapter_get_address(conn->adapter), + device_get_address(conn->device)); + if (err < 0) { + error("Connecting %s failed: %s", ext->name, strerror(-err)); + goto failed; + } + + return; + +failed: + if (conn->service) + btd_service_connecting_complete(conn->service, err); + + ext->conns = g_slist_remove(ext->conns, conn); + ext_io_destroy(conn); +} + +static int resolve_service(struct ext_io *conn, const bdaddr_t *src, + const bdaddr_t *dst) +{ + struct ext_profile *ext = conn->ext; + uuid_t uuid; + int err; + + bt_string2uuid(&uuid, ext->remote_uuid); + sdp_uuid128_to_uuid(&uuid); + + err = bt_search_service(src, dst, &uuid, record_cb, conn, NULL, 0); + if (err == 0) + conn->resolving = true; + + return err; +} + +static int ext_connect_dev(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + struct btd_profile *profile = btd_service_get_profile(service); + struct btd_adapter *adapter; + struct ext_io *conn; + struct ext_profile *ext; + int err; + + ext = find_ext(profile); + if (!ext) + return -ENOENT; + + conn = find_connection(ext, dev); + if (conn) + return -EALREADY; + + adapter = device_get_adapter(dev); + + conn = g_new0(struct ext_io, 1); + conn->ext = ext; + + if (ext->remote_psm || ext->remote_chan) { + conn->psm = ext->remote_psm; + conn->chan = ext->remote_chan; + err = connect_io(conn, btd_adapter_get_address(adapter), + device_get_address(dev)); + } else { + err = resolve_service(conn, btd_adapter_get_address(adapter), + device_get_address(dev)); + } + + if (err < 0) + goto failed; + + conn->adapter = btd_adapter_ref(adapter); + conn->device = btd_device_ref(dev); + conn->service = btd_service_ref(service); + + ext->conns = g_slist_append(ext->conns, conn); + + return 0; + +failed: + g_free(conn); + return err; +} + +static int send_disconn_req(struct ext_profile *ext, struct ext_io *conn) +{ + DBusMessage *msg; + const char *path; + + msg = dbus_message_new_method_call(ext->owner, ext->path, + "org.bluez.Profile1", + "RequestDisconnection"); + if (!msg) { + error("Unable to create RequestDisconnection call for %s", + ext->name); + return -ENOMEM; + } + + path = device_get_path(conn->device); + dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID); + + if (!g_dbus_send_message_with_reply(btd_get_dbus_connection(), + msg, &conn->pending, -1)) { + error("%s: sending RequestDisconnection failed", ext->name); + dbus_message_unref(msg); + return -EIO; + } + + dbus_message_unref(msg); + + dbus_pending_call_set_notify(conn->pending, disconn_reply, conn, NULL); + + return 0; +} + +static int ext_disconnect_dev(struct btd_service *service) +{ + struct btd_device *dev = btd_service_get_device(service); + struct btd_profile *profile = btd_service_get_profile(service); + struct ext_profile *ext; + struct ext_io *conn; + int err; + + ext = find_ext(profile); + if (!ext) + return -ENOENT; + + conn = find_connection(ext, dev); + if (!conn || !conn->connected) + return -ENOTCONN; + + if (conn->pending) + return -EBUSY; + + err = send_disconn_req(ext, conn); + if (err < 0) + return err; + + return 0; +} + +static char *get_hfp_hf_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + return g_strdup_printf(HFP_HF_RECORD, rfcomm->chan, ext->version, + ext->name, ext->features); +} + +static char *get_hfp_ag_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + return g_strdup_printf(HFP_AG_RECORD, rfcomm->chan, ext->version, + ext->name, ext->features); +} + +static char *get_hsp_ag_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + return g_strdup_printf(HSP_AG_RECORD, rfcomm->chan, ext->version, + ext->name); +} + +static char *get_spp_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + char *svc, *rec; + + if (ext->service) + svc = g_strdup_printf("", ext->service); + else + svc = g_strdup(""); + + rec = g_strdup_printf(SPP_RECORD, svc, rfcomm->chan, ext->version, + ext->name); + g_free(svc); + return rec; +} + +static char *get_dun_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + return g_strdup_printf(DUN_RECORD, rfcomm->chan, ext->version, + ext->name); +} + +static char *get_pce_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + return g_strdup_printf(PCE_RECORD, ext->version, ext->name); +} + +static char *get_pse_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + uint16_t psm = 0; + uint8_t chan = 0; + + if (l2cap) + psm = l2cap->psm; + if (rfcomm) + chan = rfcomm->chan; + + return g_strdup_printf(PSE_RECORD, chan, ext->version, ext->name, psm); +} + +static char *get_mas_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + uint16_t psm = 0; + uint8_t chan = 0; + + if (l2cap) + psm = l2cap->psm; + if (rfcomm) + chan = rfcomm->chan; + + return g_strdup_printf(MAS_RECORD, chan, ext->version, ext->name, psm); +} + +static char *get_mns_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + uint16_t psm = 0; + uint8_t chan = 0; + + if (l2cap) + psm = l2cap->psm; + if (rfcomm) + chan = rfcomm->chan; + + return g_strdup_printf(MNS_RECORD, chan, ext->version, ext->name, psm); +} + +static char *get_sync_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + return g_strdup_printf(SYNC_RECORD, rfcomm->chan, ext->version, + ext->name); +} + +static char *get_opp_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + uint16_t psm = 0; + uint8_t chan = 0; + + if (l2cap) + psm = l2cap->psm; + if (rfcomm) + chan = rfcomm->chan; + + return g_strdup_printf(OPP_RECORD, chan, ext->version, psm, ext->name); +} + +static char *get_ftp_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + uint16_t psm = 0; + uint8_t chan = 0; + + if (l2cap) + psm = l2cap->psm; + if (rfcomm) + chan = rfcomm->chan; + + return g_strdup_printf(FTP_RECORD, chan, ext->version, psm, ext->name); +} + +#define RFCOMM_SEQ " \ + \ + \ + " + +#define VERSION_ATTR \ + " \ + \ + \ + \ + \ + \ + \ + " + +static char *get_generic_record(struct ext_profile *ext, struct ext_io *l2cap, + struct ext_io *rfcomm) +{ + char uuid_str[MAX_LEN_UUID_STR], svc_str[MAX_LEN_UUID_STR], psm[30]; + char *rf_seq, *ver_attr, *rec; + uuid_t uuid; + + bt_string2uuid(&uuid, ext->uuid); + sdp_uuid2strn(&uuid, uuid_str, sizeof(uuid_str)); + + if (ext->service) { + bt_string2uuid(&uuid, ext->service); + sdp_uuid2strn(&uuid, svc_str, sizeof(svc_str)); + } else { + strncpy(svc_str, uuid_str, sizeof(svc_str)); + } + + if (l2cap) + snprintf(psm, sizeof(psm), "", + l2cap->psm); + else + psm[0] = '\0'; + + if (rfcomm) + rf_seq = g_strdup_printf(RFCOMM_SEQ, rfcomm->chan); + else + rf_seq = g_strdup(""); + + if (ext->version) + ver_attr = g_strdup_printf(VERSION_ATTR, uuid_str, + ext->version); + else + ver_attr = g_strdup(""); + + rec = g_strdup_printf(GENERIC_RECORD, svc_str, psm, rf_seq, ver_attr, + ext->name); + + g_free(rf_seq); + g_free(ver_attr); + + return rec; +} + +static struct default_settings { + const char *uuid; + const char *name; + int priority; + const char *remote_uuid; + int channel; + int psm; + BtIOMode mode; + BtIOSecLevel sec_level; + bool authorize; + bool auto_connect; + char * (*get_record)(struct ext_profile *ext, + struct ext_io *l2cap, + struct ext_io *rfcomm); + uint16_t version; + uint16_t features; +} defaults[] = { + { + .uuid = SPP_UUID, + .name = "Serial Port", + .channel = SPP_DEFAULT_CHANNEL, + .authorize = true, + .get_record = get_spp_record, + .version = 0x0102, + }, { + .uuid = DUN_GW_UUID, + .name = "Dial-Up Networking", + .channel = DUN_DEFAULT_CHANNEL, + .authorize = true, + .get_record = get_dun_record, + .version = 0x0102, + }, { + .uuid = HFP_HS_UUID, + .name = "Hands-Free unit", + .priority = BTD_PROFILE_PRIORITY_HIGH, + .remote_uuid = HFP_AG_UUID, + .channel = HFP_HF_DEFAULT_CHANNEL, + .authorize = true, + .auto_connect = true, + .get_record = get_hfp_hf_record, + .version = 0x0107, + }, { + .uuid = HFP_AG_UUID, + .name = "Hands-Free Voice gateway", + .priority = BTD_PROFILE_PRIORITY_HIGH, + .remote_uuid = HFP_HS_UUID, + .channel = HFP_AG_DEFAULT_CHANNEL, + .authorize = true, + .auto_connect = true, + .get_record = get_hfp_ag_record, + .version = 0x0107, + }, { + .uuid = HSP_AG_UUID, + .name = "Headset Voice gateway", + .priority = BTD_PROFILE_PRIORITY_HIGH, + .remote_uuid = HSP_HS_UUID, + .channel = HSP_AG_DEFAULT_CHANNEL, + .authorize = true, + .auto_connect = true, + .get_record = get_hsp_ag_record, + .version = 0x0102, + }, { + .uuid = OBEX_OPP_UUID, + .name = "Object Push", + .channel = OPP_DEFAULT_CHANNEL, + .psm = BTD_PROFILE_PSM_AUTO, + .mode = BT_IO_MODE_ERTM, + .sec_level = BT_IO_SEC_LOW, + .authorize = false, + .get_record = get_opp_record, + .version = 0x0102, + }, { + .uuid = OBEX_FTP_UUID, + .name = "File Transfer", + .channel = FTP_DEFAULT_CHANNEL, + .psm = BTD_PROFILE_PSM_AUTO, + .mode = BT_IO_MODE_ERTM, + .authorize = true, + .get_record = get_ftp_record, + .version = 0x0102, + }, { + .uuid = OBEX_SYNC_UUID, + .name = "Synchronization", + .channel = SYNC_DEFAULT_CHANNEL, + .authorize = true, + .get_record = get_sync_record, + .version = 0x0100, + }, { + .uuid = OBEX_PSE_UUID, + .name = "Phone Book Access", + .channel = PBAP_DEFAULT_CHANNEL, + .psm = BTD_PROFILE_PSM_AUTO, + .mode = BT_IO_MODE_ERTM, + .authorize = true, + .get_record = get_pse_record, + .version = 0x0101, + }, { + .uuid = OBEX_PCE_UUID, + .name = "Phone Book Access Client", + .remote_uuid = OBEX_PSE_UUID, + .authorize = true, + .get_record = get_pce_record, + .version = 0x0102, + }, { + .uuid = OBEX_MAS_UUID, + .name = "Message Access", + .channel = MAS_DEFAULT_CHANNEL, + .psm = BTD_PROFILE_PSM_AUTO, + .mode = BT_IO_MODE_ERTM, + .authorize = true, + .get_record = get_mas_record, + .version = 0x0100 + }, { + .uuid = OBEX_MNS_UUID, + .name = "Message Notification", + .channel = MNS_DEFAULT_CHANNEL, + .psm = BTD_PROFILE_PSM_AUTO, + .mode = BT_IO_MODE_ERTM, + .authorize = true, + .get_record = get_mns_record, + .version = 0x0102 + }, +}; + +static void ext_set_defaults(struct ext_profile *ext) +{ + unsigned int i; + + ext->mode = BT_IO_MODE_BASIC; + ext->sec_level = BT_IO_SEC_MEDIUM; + ext->authorize = true; + ext->enable_client = true; + ext->enable_server = true; + ext->remote_uuid = NULL; + + for (i = 0; i < G_N_ELEMENTS(defaults); i++) { + struct default_settings *settings = &defaults[i]; + const char *remote_uuid; + + if (strcasecmp(ext->uuid, settings->uuid) != 0) + continue; + + if (settings->remote_uuid) + remote_uuid = settings->remote_uuid; + else + remote_uuid = ext->uuid; + + ext->remote_uuid = g_strdup(remote_uuid); + + if (settings->channel) + ext->local_chan = settings->channel; + + if (settings->psm) + ext->local_psm = settings->psm; + + if (settings->sec_level) + ext->sec_level = settings->sec_level; + + if (settings->mode) + ext->mode = settings->mode; + + ext->authorize = settings->authorize; + + if (settings->auto_connect) + ext->p.auto_connect = true; + + if (settings->priority) + ext->p.priority = settings->priority; + + if (settings->get_record) + ext->get_record = settings->get_record; + + if (settings->version) + ext->version = settings->version; + + if (settings->features) + ext->features = settings->features; + + if (settings->name) + ext->name = g_strdup(settings->name); + } +} + +static int parse_ext_opt(struct ext_profile *ext, const char *key, + DBusMessageIter *value) +{ + int type = dbus_message_iter_get_arg_type(value); + const char *str; + uint16_t u16; + dbus_bool_t b; + + if (strcasecmp(key, "Name") == 0) { + if (type != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(value, &str); + g_free(ext->name); + ext->name = g_strdup(str); + } else if (strcasecmp(key, "AutoConnect") == 0) { + if (type != DBUS_TYPE_BOOLEAN) + return -EINVAL; + dbus_message_iter_get_basic(value, &b); + ext->p.auto_connect = b; + } else if (strcasecmp(key, "PSM") == 0) { + if (type != DBUS_TYPE_UINT16) + return -EINVAL; + dbus_message_iter_get_basic(value, &u16); + ext->local_psm = u16 ? u16 : BTD_PROFILE_PSM_AUTO; + } else if (strcasecmp(key, "Channel") == 0) { + if (type != DBUS_TYPE_UINT16) + return -EINVAL; + + dbus_message_iter_get_basic(value, &u16); + if (u16 > 31) + return -EINVAL; + ext->local_chan = u16 ? u16 : BTD_PROFILE_CHAN_AUTO; + } else if (strcasecmp(key, "RequireAuthentication") == 0) { + if (type != DBUS_TYPE_BOOLEAN) + return -EINVAL; + + dbus_message_iter_get_basic(value, &b); + if (b) + ext->sec_level = BT_IO_SEC_MEDIUM; + else + ext->sec_level = BT_IO_SEC_LOW; + } else if (strcasecmp(key, "RequireAuthorization") == 0) { + if (type != DBUS_TYPE_BOOLEAN) + return -EINVAL; + dbus_message_iter_get_basic(value, &b); + ext->authorize = b; + } else if (strcasecmp(key, "Role") == 0) { + if (type != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(value, &str); + g_free(ext->role); + ext->role = g_strdup(str); + + if (g_str_equal(ext->role, "client")) { + ext->enable_server = false; + ext->enable_client = true; + } else if (g_str_equal(ext->role, "server")) { + ext->enable_server = true; + ext->enable_client = false; + } + } else if (strcasecmp(key, "ServiceRecord") == 0) { + if (type != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(value, &str); + g_free(ext->record); + ext->record = g_strdup(str); + ext->enable_server = true; + } else if (strcasecmp(key, "Version") == 0) { + uint16_t ver; + + if (type != DBUS_TYPE_UINT16) + return -EINVAL; + + dbus_message_iter_get_basic(value, &ver); + ext->version = ver; + } else if (strcasecmp(key, "Features") == 0) { + uint16_t feat; + + if (type != DBUS_TYPE_UINT16) + return -EINVAL; + + dbus_message_iter_get_basic(value, &feat); + ext->features = feat; + } else if (strcasecmp(key, "Service") == 0) { + if (type != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(value, &str); + free(ext->service); + ext->service = bt_name2string(str); + } + + return 0; +} + +static void set_service(struct ext_profile *ext) +{ + if (strcasecmp(ext->uuid, HSP_HS_UUID) == 0) { + ext->service = strdup(ext->uuid); + } else if (strcasecmp(ext->uuid, HSP_AG_UUID) == 0) { + ext->service = ext->uuid; + ext->uuid = strdup(HSP_HS_UUID); + } else if (strcasecmp(ext->uuid, HFP_HS_UUID) == 0) { + ext->service = strdup(ext->uuid); + } else if (strcasecmp(ext->uuid, HFP_AG_UUID) == 0) { + ext->service = ext->uuid; + ext->uuid = strdup(HFP_HS_UUID); + } else if (strcasecmp(ext->uuid, OBEX_SYNC_UUID) == 0 || + strcasecmp(ext->uuid, OBEX_OPP_UUID) == 0 || + strcasecmp(ext->uuid, OBEX_FTP_UUID) == 0) { + ext->service = strdup(ext->uuid); + } else if (strcasecmp(ext->uuid, OBEX_PSE_UUID) == 0 || + strcasecmp(ext->uuid, OBEX_PCE_UUID) == 0) { + ext->service = ext->uuid; + ext->uuid = strdup(OBEX_PBAP_UUID); + } else if (strcasecmp(ext->uuid, OBEX_MAS_UUID) == 0 || + strcasecmp(ext->uuid, OBEX_MNS_UUID) == 0) { + ext->service = ext->uuid; + ext->uuid = strdup(OBEX_MAP_UUID); + } +} + +static struct ext_profile *create_ext(const char *owner, const char *path, + const char *uuid, + DBusMessageIter *opts) +{ + struct btd_profile *p; + struct ext_profile *ext; + + ext = g_new0(struct ext_profile, 1); + + ext->uuid = bt_name2string(uuid); + if (ext->uuid == NULL) { + g_free(ext); + return NULL; + } + + ext->owner = g_strdup(owner); + ext->path = g_strdup(path); + + ext_set_defaults(ext); + + while (dbus_message_iter_get_arg_type(opts) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter value, entry; + const char *key; + + dbus_message_iter_recurse(opts, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + if (parse_ext_opt(ext, key, &value) < 0) + error("Invalid value for profile option %s", key); + + dbus_message_iter_next(opts); + } + + if (!ext->service) + set_service(ext); + + if (ext->enable_server && !(ext->record || ext->get_record)) + ext->get_record = get_generic_record; + + if (!ext->name) + ext->name = g_strdup_printf("%s%s/%s", owner, path, uuid); + + if (!ext->remote_uuid) { + if (ext->service) + ext->remote_uuid = g_strdup(ext->service); + else + ext->remote_uuid = g_strdup(ext->uuid); + } + + p = &ext->p; + + p->name = ext->name; + p->local_uuid = ext->service ? ext->service : ext->uuid; + p->remote_uuid = ext->remote_uuid; + p->external = true; + + if (ext->enable_server) { + p->adapter_probe = ext_adapter_probe; + p->adapter_remove = ext_adapter_remove; + } + + if (ext->enable_client) { + p->device_probe = ext_device_probe; + p->device_remove = ext_device_remove; + p->connect = ext_connect_dev; + p->disconnect = ext_disconnect_dev; + } + + DBG("Created \"%s\"", ext->name); + + ext_profiles = g_slist_append(ext_profiles, ext); + + adapter_foreach(adapter_add_profile, &ext->p); + + return ext; +} + +static void remove_ext(struct ext_profile *ext) +{ + adapter_foreach(adapter_remove_profile, &ext->p); + + ext_profiles = g_slist_remove(ext_profiles, ext); + + DBG("Removed \"%s\"", ext->name); + + ext_remove_records(ext, NULL); + + g_slist_free_full(ext->servers, ext_io_destroy); + g_slist_free_full(ext->conns, ext_io_destroy); + + g_free(ext->remote_uuid); + g_free(ext->name); + g_free(ext->owner); + free(ext->uuid); + free(ext->service); + g_free(ext->role); + g_free(ext->path); + g_free(ext->record); + + g_free(ext); +} + +static void ext_exited(DBusConnection *conn, void *user_data) +{ + struct ext_profile *ext = user_data; + + DBG("\"%s\" exited", ext->name); + + remove_ext(ext); +} + +static DBusMessage *register_profile(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *path, *sender, *uuid; + DBusMessageIter args, opts; + struct ext_profile *ext; + + sender = dbus_message_get_sender(msg); + + DBG("sender %s", sender); + + dbus_message_iter_init(msg, &args); + + dbus_message_iter_get_basic(&args, &path); + dbus_message_iter_next(&args); + + ext = find_ext_profile(sender, path); + if (ext) + return btd_error_already_exists(msg); + + dbus_message_iter_get_basic(&args, &uuid); + dbus_message_iter_next(&args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY) + return btd_error_invalid_args(msg); + + dbus_message_iter_recurse(&args, &opts); + + ext = create_ext(sender, path, uuid, &opts); + if (!ext) + return btd_error_invalid_args(msg); + + ext->id = g_dbus_add_disconnect_watch(conn, sender, ext_exited, ext, + NULL); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_profile(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + const char *path, *sender; + struct ext_profile *ext; + + sender = dbus_message_get_sender(msg); + + DBG("sender %s", sender); + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + ext = find_ext_profile(sender, path); + if (!ext) + return btd_error_does_not_exist(msg); + + g_dbus_remove_watch(conn, ext->id); + remove_ext(ext); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable methods[] = { + { GDBUS_METHOD("RegisterProfile", + GDBUS_ARGS({ "profile", "o"}, { "UUID", "s" }, + { "options", "a{sv}" }), + NULL, register_profile) }, + { GDBUS_METHOD("UnregisterProfile", GDBUS_ARGS({ "profile", "o" }), + NULL, unregister_profile) }, + { } +}; + +static struct btd_profile_custom_property *find_custom_prop(const char *uuid, + const char *name) +{ + GSList *l; + + for (l = custom_props; l; l = l->next) { + struct btd_profile_custom_property *prop = l->data; + + if (strcasecmp(prop->uuid, uuid) != 0) + continue; + + if (g_strcmp0(prop->name, name) == 0) + return prop; + } + + return NULL; +} + +bool btd_profile_add_custom_prop(const char *uuid, const char *type, + const char *name, + btd_profile_prop_exists exists, + btd_profile_prop_get get, + void *user_data) +{ + struct btd_profile_custom_property *prop; + + prop = find_custom_prop(uuid, name); + if (prop != NULL) + return false; + + prop = g_new0(struct btd_profile_custom_property, 1); + + prop->uuid = strdup(uuid); + prop->type = g_strdup(type); + prop->name = g_strdup(name); + prop->exists = exists; + prop->get = get; + prop->user_data = user_data; + + custom_props = g_slist_append(custom_props, prop); + + return true; +} + +static void free_property(gpointer data) +{ + struct btd_profile_custom_property *p = data; + + g_free(p->uuid); + g_free(p->type); + g_free(p->name); + + g_free(p); +} + +bool btd_profile_remove_custom_prop(const char *uuid, const char *name) +{ + struct btd_profile_custom_property *prop; + + prop = find_custom_prop(uuid, name); + if (prop == NULL) + return false; + + custom_props = g_slist_remove(custom_props, prop); + free_property(prop); + + return false; +} + +void btd_profile_init(void) +{ + g_dbus_register_interface(btd_get_dbus_connection(), + "/org/bluez", "org.bluez.ProfileManager1", + methods, NULL, NULL, NULL, NULL); +} + +void btd_profile_cleanup(void) +{ + while (ext_profiles) { + struct ext_profile *ext = ext_profiles->data; + DBusConnection *conn = btd_get_dbus_connection(); + DBusMessage *msg; + + DBG("Releasing \"%s\"", ext->name); + + g_slist_free_full(ext->conns, ext_io_destroy); + ext->conns = NULL; + + msg = dbus_message_new_method_call(ext->owner, ext->path, + "org.bluez.Profile1", + "Release"); + if (msg) + g_dbus_send_message(conn, msg); + + g_dbus_remove_watch(conn, ext->id); + remove_ext(ext); + + } + + g_slist_free_full(custom_props, free_property); + custom_props = NULL; + + g_dbus_unregister_interface(btd_get_dbus_connection(), + "/org/bluez", "org.bluez.ProfileManager1"); +} diff --git a/src/profile.h b/src/profile.h new file mode 100644 index 0000000..4448a2a --- /dev/null +++ b/src/profile.h @@ -0,0 +1,77 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 + * + */ + +#define BTD_PROFILE_PRIORITY_LOW 0 +#define BTD_PROFILE_PRIORITY_MEDIUM 1 +#define BTD_PROFILE_PRIORITY_HIGH 2 + +struct btd_service; + +struct btd_profile { + const char *name; + int priority; + + const char *local_uuid; + const char *remote_uuid; + + bool auto_connect; + bool external; + + int (*device_probe) (struct btd_service *service); + void (*device_remove) (struct btd_service *service); + + int (*connect) (struct btd_service *service); + int (*disconnect) (struct btd_service *service); + + int (*accept) (struct btd_service *service); + + int (*adapter_probe) (struct btd_profile *p, + struct btd_adapter *adapter); + void (*adapter_remove) (struct btd_profile *p, + struct btd_adapter *adapter); +}; + +void btd_profile_foreach(void (*func)(struct btd_profile *p, void *data), + void *data); + +int btd_profile_register(struct btd_profile *profile); +void btd_profile_unregister(struct btd_profile *profile); + +typedef bool (*btd_profile_prop_exists)(const char *uuid, + struct btd_device *dev, + void *user_data); + +typedef bool (*btd_profile_prop_get)(const char *uuid, + struct btd_device *dev, + DBusMessageIter *iter, + void *user_data); + +bool btd_profile_add_custom_prop(const char *uuid, const char *type, + const char *name, + btd_profile_prop_exists exists, + btd_profile_prop_get get, + void *user_data); +bool btd_profile_remove_custom_prop(const char *uuid, const char *name); + +void btd_profile_init(void); +void btd_profile_cleanup(void); diff --git a/src/rfkill.c b/src/rfkill.c new file mode 100644 index 0000000..fb2d113 --- /dev/null +++ b/src/rfkill.c @@ -0,0 +1,175 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "log.h" +#include "adapter.h" +#include "hcid.h" + +enum rfkill_type { + RFKILL_TYPE_ALL = 0, + RFKILL_TYPE_WLAN, + RFKILL_TYPE_BLUETOOTH, + RFKILL_TYPE_UWB, + RFKILL_TYPE_WIMAX, + RFKILL_TYPE_WWAN, +}; + +enum rfkill_operation { + RFKILL_OP_ADD = 0, + RFKILL_OP_DEL, + RFKILL_OP_CHANGE, + RFKILL_OP_CHANGE_ALL, +}; + +struct rfkill_event { + uint32_t idx; + uint8_t type; + uint8_t op; + uint8_t soft; + uint8_t hard; +}; + +static gboolean rfkill_event(GIOChannel *chan, + GIOCondition cond, gpointer data) +{ + unsigned char buf[32]; + struct rfkill_event *event = (void *) buf; + struct btd_adapter *adapter; + char sysname[PATH_MAX]; + ssize_t len; + int fd, id; + + if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR)) + return FALSE; + + fd = g_io_channel_unix_get_fd(chan); + + memset(buf, 0, sizeof(buf)); + + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EAGAIN) + return TRUE; + return FALSE; + } + + if (len != sizeof(struct rfkill_event)) + return TRUE; + + DBG("RFKILL event idx %u type %u op %u soft %u hard %u", + event->idx, event->type, event->op, + event->soft, event->hard); + + if (event->soft || event->hard) + return TRUE; + + if (event->op != RFKILL_OP_CHANGE) + return TRUE; + + if (event->type != RFKILL_TYPE_BLUETOOTH && + event->type != RFKILL_TYPE_ALL) + return TRUE; + + snprintf(sysname, sizeof(sysname) - 1, + "/sys/class/rfkill/rfkill%u/name", event->idx); + + fd = open(sysname, O_RDONLY); + if (fd < 0) + return TRUE; + + memset(sysname, 0, sizeof(sysname)); + + if (read(fd, sysname, sizeof(sysname) - 1) < 4) { + close(fd); + return TRUE; + } + + close(fd); + + if (g_str_has_prefix(sysname, "hci") == FALSE) + return TRUE; + + id = atoi(sysname + 3); + if (id < 0) + return TRUE; + + adapter = adapter_find_by_id(id); + if (!adapter) + return TRUE; + + DBG("RFKILL unblock for hci%d", id); + + btd_adapter_restore_powered(adapter); + + return TRUE; +} + +static guint watch = 0; + +void rfkill_init(void) +{ + int fd; + GIOChannel *channel; + + fd = open("/dev/rfkill", O_RDWR); + if (fd < 0) { + error("Failed to open RFKILL control device"); + return; + } + + channel = g_io_channel_unix_new(fd); + g_io_channel_set_close_on_unref(channel, TRUE); + + watch = g_io_add_watch(channel, + G_IO_IN | G_IO_NVAL | G_IO_HUP | G_IO_ERR, + rfkill_event, NULL); + + g_io_channel_unref(channel); +} + +void rfkill_exit(void) +{ + if (watch == 0) + return; + + g_source_remove(watch); + watch = 0; +} diff --git a/src/sdp-client.c b/src/sdp-client.c new file mode 100644 index 0000000..413cf30 --- /dev/null +++ b/src/sdp-client.c @@ -0,0 +1,413 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "btio/btio.h" +#include "log.h" +#include "sdp-client.h" + +/* Number of seconds to keep a sdp_session_t in the cache */ +#define CACHE_TIMEOUT 2 + +struct cached_sdp_session { + bdaddr_t src; + bdaddr_t dst; + sdp_session_t *session; + guint timer; + guint io_id; +}; + +static GSList *cached_sdp_sessions = NULL; + +static void cleanup_cached_session(struct cached_sdp_session *cached) +{ + cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, cached); + sdp_close(cached->session); + g_free(cached); +} + +static gboolean cached_session_expired(gpointer user_data) +{ + struct cached_sdp_session *cached = user_data; + + g_source_remove(cached->io_id); + cleanup_cached_session(cached); + + return FALSE; +} + +static sdp_session_t *get_cached_sdp_session(const bdaddr_t *src, + const bdaddr_t *dst) +{ + GSList *l; + + for (l = cached_sdp_sessions; l != NULL; l = l->next) { + struct cached_sdp_session *c = l->data; + sdp_session_t *session; + + if (bacmp(&c->src, src) || bacmp(&c->dst, dst)) + continue; + + g_source_remove(c->timer); + g_source_remove(c->io_id); + + session = c->session; + + cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, c); + g_free(c); + + return session; + } + + return NULL; +} + +static gboolean disconnect_watch(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + struct cached_sdp_session *cached = user_data; + + g_source_remove(cached->timer); + cleanup_cached_session(cached); + + return FALSE; +} + +static void cache_sdp_session(bdaddr_t *src, bdaddr_t *dst, + sdp_session_t *session) +{ + struct cached_sdp_session *cached; + int sk; + GIOChannel *chan; + + cached = g_new0(struct cached_sdp_session, 1); + + bacpy(&cached->src, src); + bacpy(&cached->dst, dst); + + cached->session = session; + + cached_sdp_sessions = g_slist_append(cached_sdp_sessions, cached); + + cached->timer = g_timeout_add_seconds(CACHE_TIMEOUT, + cached_session_expired, + cached); + + /* Watch the connection state during cache timeout */ + sk = sdp_get_socket(session); + chan = g_io_channel_unix_new(sk); + + cached->io_id = g_io_add_watch(chan, G_IO_HUP | G_IO_ERR | G_IO_NVAL, + disconnect_watch, cached); + + g_io_channel_unref(chan); +} + +struct search_context { + bdaddr_t src; + bdaddr_t dst; + sdp_session_t *session; + bt_callback_t cb; + bt_destroy_t destroy; + gpointer user_data; + uuid_t uuid; + guint io_id; +}; + +static GSList *context_list = NULL; + +static void search_context_cleanup(struct search_context *ctxt) +{ + context_list = g_slist_remove(context_list, ctxt); + + if (ctxt->destroy) + ctxt->destroy(ctxt->user_data); + + g_free(ctxt); +} + +static void search_completed_cb(uint8_t type, uint16_t status, + uint8_t *rsp, size_t size, void *user_data) +{ + struct search_context *ctxt = user_data; + sdp_list_t *recs = NULL; + int scanned, seqlen = 0, bytesleft = size; + uint8_t dataType; + int err = 0; + + if (status || type != SDP_SVC_SEARCH_ATTR_RSP) { + err = -EPROTO; + goto done; + } + + scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen); + if (!scanned || !seqlen) + goto done; + + rsp += scanned; + bytesleft -= scanned; + do { + sdp_record_t *rec; + int recsize; + + recsize = 0; + rec = sdp_extract_pdu(rsp, bytesleft, &recsize); + if (!rec) + break; + + if (!recsize) { + sdp_record_free(rec); + break; + } + + scanned += recsize; + rsp += recsize; + bytesleft -= recsize; + + recs = sdp_list_append(recs, rec); + } while (scanned < (ssize_t) size && bytesleft > 0); + +done: + cache_sdp_session(&ctxt->src, &ctxt->dst, ctxt->session); + + if (ctxt->cb) + ctxt->cb(recs, err, ctxt->user_data); + + if (recs) + sdp_list_free(recs, (sdp_free_func_t) sdp_record_free); + + search_context_cleanup(ctxt); +} + +static gboolean search_process_cb(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + struct search_context *ctxt = user_data; + + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { + sdp_close(ctxt->session); + ctxt->session = NULL; + + if (ctxt->cb) + ctxt->cb(NULL, -EIO, ctxt->user_data); + + search_context_cleanup(ctxt); + return FALSE; + } + + /* If sdp_process fails it calls search_completed_cb */ + if (sdp_process(ctxt->session) < 0) + return FALSE; + + return TRUE; +} + +static gboolean connect_watch(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + struct search_context *ctxt = user_data; + sdp_list_t *search, *attrids; + uint32_t range = 0x0000ffff; + socklen_t len; + int sk, err, sk_err = 0; + + sk = g_io_channel_unix_get_fd(chan); + ctxt->io_id = 0; + + len = sizeof(sk_err); + if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &sk_err, &len) < 0) + err = -errno; + else + err = -sk_err; + + if (err != 0) + goto failed; + + if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) { + err = -EIO; + goto failed; + } + + search = sdp_list_append(NULL, &ctxt->uuid); + attrids = sdp_list_append(NULL, &range); + if (sdp_service_search_attr_async(ctxt->session, + search, SDP_ATTR_REQ_RANGE, attrids) < 0) { + sdp_list_free(attrids, NULL); + sdp_list_free(search, NULL); + err = -EIO; + goto failed; + } + + sdp_list_free(attrids, NULL); + sdp_list_free(search, NULL); + + /* Set callback responsible for update the internal SDP transaction */ + ctxt->io_id = g_io_add_watch(chan, + G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + search_process_cb, ctxt); + return FALSE; + +failed: + sdp_close(ctxt->session); + ctxt->session = NULL; + + if (ctxt->cb) + ctxt->cb(NULL, err, ctxt->user_data); + + search_context_cleanup(ctxt); + + return FALSE; +} + +static int create_search_context(struct search_context **ctxt, + const bdaddr_t *src, + const bdaddr_t *dst, + uuid_t *uuid, uint16_t flags) +{ + sdp_session_t *s; + GIOChannel *chan; + uint32_t prio = 1; + int sk; + + if (!ctxt) + return -EINVAL; + + s = get_cached_sdp_session(src, dst); + if (!s) + s = sdp_connect(src, dst, SDP_NON_BLOCKING | flags); + + if (!s) + return -errno; + + *ctxt = g_try_malloc0(sizeof(struct search_context)); + if (!*ctxt) { + sdp_close(s); + return -ENOMEM; + } + + bacpy(&(*ctxt)->src, src); + bacpy(&(*ctxt)->dst, dst); + (*ctxt)->session = s; + (*ctxt)->uuid = *uuid; + + sk = sdp_get_socket(s); + /* Set low priority for the SDP connection not to interfere with + * other potential traffic. + */ + if (setsockopt(sk, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)) < 0) + warn("Setting SDP priority failed: %s (%d)", + strerror(errno), errno); + + chan = g_io_channel_unix_new(sk); + (*ctxt)->io_id = g_io_add_watch(chan, + G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + connect_watch, *ctxt); + g_io_channel_unref(chan); + + return 0; +} + +int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst, + uuid_t *uuid, bt_callback_t cb, void *user_data, + bt_destroy_t destroy, uint16_t flags) +{ + struct search_context *ctxt = NULL; + int err; + + if (!cb) + return -EINVAL; + + err = create_search_context(&ctxt, src, dst, uuid, flags); + if (err < 0) + return err; + + ctxt->cb = cb; + ctxt->destroy = destroy; + ctxt->user_data = user_data; + + context_list = g_slist_append(context_list, ctxt); + + return 0; +} + +static int find_by_bdaddr(gconstpointer data, gconstpointer user_data) +{ + const struct search_context *ctxt = data, *search = user_data; + int ret; + + ret = bacmp(&ctxt->src, &search->src); + if (ret != 0) + return ret; + + return bacmp(&ctxt->dst, &search->dst); +} + +int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct search_context match, *ctxt; + GSList *l; + + memset(&match, 0, sizeof(match)); + bacpy(&match.src, src); + bacpy(&match.dst, dst); + + /* Ongoing SDP Discovery */ + l = g_slist_find_custom(context_list, &match, find_by_bdaddr); + if (l == NULL) + return -ENOENT; + + ctxt = l->data; + + if (!ctxt->session) + return -ENOTCONN; + + if (ctxt->io_id) + g_source_remove(ctxt->io_id); + + if (ctxt->session) + sdp_close(ctxt->session); + + search_context_cleanup(ctxt); + + return 0; +} + +void bt_clear_cached_session(const bdaddr_t *src, const bdaddr_t *dst) +{ + sdp_session_t *session; + + session = get_cached_sdp_session(src, dst); + if (session) + sdp_close(session); +} diff --git a/src/sdp-client.h b/src/sdp-client.h new file mode 100644 index 0000000..9aa5a4d --- /dev/null +++ b/src/sdp-client.h @@ -0,0 +1,31 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +typedef void (*bt_callback_t) (sdp_list_t *recs, int err, gpointer user_data); +typedef void (*bt_destroy_t) (gpointer user_data); + +int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst, + uuid_t *uuid, bt_callback_t cb, void *user_data, + bt_destroy_t destroy, uint16_t flags); +int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst); +void bt_clear_cached_session(const bdaddr_t *src, const bdaddr_t *dst); diff --git a/src/sdp-xml.c b/src/sdp-xml.c new file mode 100644 index 0000000..6f83173 --- /dev/null +++ b/src/sdp-xml.c @@ -0,0 +1,1015 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "sdp-xml.h" + +#define DBG(...) (void)(0) +#define error(...) (void)(0) + +#define SDP_XML_ENCODING_NORMAL 0 +#define SDP_XML_ENCODING_HEX 1 + +#define STRBUFSIZE 1024 +#define MAXINDENT 64 + +struct sdp_xml_data { + char *text; /* Pointer to the current buffer */ + int size; /* Size of the current buffer */ + sdp_data_t *data; /* The current item being built */ + struct sdp_xml_data *next; /* Next item on the stack */ + char type; /* 0 = Text or Hexadecimal */ + char *name; /* Name, optional in the dtd */ + /* TODO: What is it used for? */ +}; + +struct context_data { + sdp_record_t *record; + sdp_data_t attr_data; + struct sdp_xml_data *stack_head; + uint16_t attr_id; +}; + +static int compute_seq_size(sdp_data_t *data) +{ + int unit_size = data->unitSize; + sdp_data_t *seq = data->val.dataseq; + + for (; seq; seq = seq->next) + unit_size += seq->unitSize; + + return unit_size; +} + +#define DEFAULT_XML_DATA_SIZE 1024 + +static struct sdp_xml_data *sdp_xml_data_alloc(void) +{ + struct sdp_xml_data *elem; + + elem = malloc(sizeof(struct sdp_xml_data)); + if (!elem) + return NULL; + + memset(elem, 0, sizeof(struct sdp_xml_data)); + + /* Null terminate the text */ + elem->size = DEFAULT_XML_DATA_SIZE; + elem->text = malloc(DEFAULT_XML_DATA_SIZE); + if (!elem->text) { + free(elem); + return NULL; + } + elem->text[0] = '\0'; + + return elem; +} + +static struct sdp_xml_data *sdp_xml_data_expand(struct sdp_xml_data *elem) +{ + char *newbuf; + + newbuf = malloc(elem->size * 2); + if (!newbuf) + return NULL; + + memcpy(newbuf, elem->text, elem->size); + elem->size *= 2; + free(elem->text); + + elem->text = newbuf; + + return elem; +} + +static sdp_data_t *sdp_xml_parse_uuid128(const char *data) +{ + uint128_t val; + unsigned int i, j; + + char buf[3]; + + memset(&val, 0, sizeof(val)); + + buf[2] = '\0'; + + for (j = 0, i = 0; i < strlen(data);) { + if (data[i] == '-') { + i++; + continue; + } + + buf[0] = data[i]; + buf[1] = data[i + 1]; + + val.data[j++] = strtoul(buf, 0, 16); + i += 2; + } + + return sdp_data_alloc(SDP_UUID128, &val); +} + +static sdp_data_t *sdp_xml_parse_uuid(const char *data, sdp_record_t *record) +{ + sdp_data_t *ret; + char *endptr; + uint32_t val; + uint16_t val2; + int len; + + len = strlen(data); + + if (len == 36) { + ret = sdp_xml_parse_uuid128(data); + goto result; + } + + val = strtoll(data, &endptr, 16); + + /* Couldn't parse */ + if (*endptr != '\0') + return NULL; + + if (val > USHRT_MAX) { + ret = sdp_data_alloc(SDP_UUID32, &val); + goto result; + } + + val2 = val; + + ret = sdp_data_alloc(SDP_UUID16, &val2); + +result: + if (record && ret) + sdp_pattern_add_uuid(record, &ret->val.uuid); + + return ret; +} + +static sdp_data_t *sdp_xml_parse_int(const char *data, uint8_t dtd) +{ + char *endptr; + sdp_data_t *ret = NULL; + + switch (dtd) { + case SDP_BOOL: + { + uint8_t val = 0; + + if (!strcmp("true", data)) + val = 1; + else if (!strcmp("false", data)) + val = 0; + else + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_INT8: + { + int8_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_UINT8: + { + uint8_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_INT16: + { + int16_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_UINT16: + { + uint16_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_INT32: + { + int32_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_UINT32: + { + uint32_t val = strtoul(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_INT64: + { + int64_t val = strtoull(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_UINT64: + { + uint64_t val = strtoull(data, &endptr, 0); + + /* Failed to parse */ + if ((endptr != data) && (*endptr != '\0')) + return NULL; + + ret = sdp_data_alloc(dtd, &val); + break; + } + + case SDP_INT128: + case SDP_UINT128: + { + uint128_t val; + int i = 0; + char buf[3]; + + buf[2] = '\0'; + + for (; i < 32; i += 2) { + buf[0] = data[i]; + buf[1] = data[i + 1]; + + val.data[i >> 1] = strtoul(buf, 0, 16); + } + + ret = sdp_data_alloc(dtd, &val); + break; + } + + }; + + return ret; +} + +static char *sdp_xml_parse_string_decode(const char *data, char encoding, + uint32_t *length) +{ + int len = strlen(data); + char *text; + + if (encoding == SDP_XML_ENCODING_NORMAL) { + text = strdup(data); + *length = len; + } else { + char buf[3], *decoded; + int i; + + decoded = malloc((len >> 1) + 1); + if (!decoded) + return NULL; + + /* Ensure the string is a power of 2 */ + len = (len >> 1) << 1; + + buf[2] = '\0'; + + for (i = 0; i < len; i += 2) { + buf[0] = data[i]; + buf[1] = data[i + 1]; + + decoded[i >> 1] = strtoul(buf, 0, 16); + } + + decoded[len >> 1] = '\0'; + text = decoded; + *length = len >> 1; + } + + return text; +} + +static sdp_data_t *sdp_xml_parse_url(const char *data) +{ + uint8_t dtd = SDP_URL_STR8; + char *url; + uint32_t length; + sdp_data_t *ret; + + url = sdp_xml_parse_string_decode(data, + SDP_XML_ENCODING_NORMAL, &length); + if (!url) + return NULL; + + if (length > UCHAR_MAX) + dtd = SDP_URL_STR16; + + ret = sdp_data_alloc_with_length(dtd, url, length); + + free(url); + + return ret; +} + +static sdp_data_t *sdp_xml_parse_text(const char *data, char encoding) +{ + uint8_t dtd = SDP_TEXT_STR8; + char *text; + uint32_t length; + sdp_data_t *ret; + + text = sdp_xml_parse_string_decode(data, encoding, &length); + if (!text) + return NULL; + + if (length > UCHAR_MAX) + dtd = SDP_TEXT_STR16; + + ret = sdp_data_alloc_with_length(dtd, text, length); + + free(text); + + return ret; +} + +static sdp_data_t *sdp_xml_parse_nil(const char *data) +{ + return sdp_data_alloc(SDP_DATA_NIL, 0); +} + +static sdp_data_t *sdp_xml_parse_datatype(const char *el, + struct sdp_xml_data *elem, + sdp_record_t *record) +{ + const char *data = elem->text; + + if (!strcmp(el, "boolean")) + return sdp_xml_parse_int(data, SDP_BOOL); + else if (!strcmp(el, "uint8")) + return sdp_xml_parse_int(data, SDP_UINT8); + else if (!strcmp(el, "uint16")) + return sdp_xml_parse_int(data, SDP_UINT16); + else if (!strcmp(el, "uint32")) + return sdp_xml_parse_int(data, SDP_UINT32); + else if (!strcmp(el, "uint64")) + return sdp_xml_parse_int(data, SDP_UINT64); + else if (!strcmp(el, "uint128")) + return sdp_xml_parse_int(data, SDP_UINT128); + else if (!strcmp(el, "int8")) + return sdp_xml_parse_int(data, SDP_INT8); + else if (!strcmp(el, "int16")) + return sdp_xml_parse_int(data, SDP_INT16); + else if (!strcmp(el, "int32")) + return sdp_xml_parse_int(data, SDP_INT32); + else if (!strcmp(el, "int64")) + return sdp_xml_parse_int(data, SDP_INT64); + else if (!strcmp(el, "int128")) + return sdp_xml_parse_int(data, SDP_INT128); + else if (!strcmp(el, "uuid")) + return sdp_xml_parse_uuid(data, record); + else if (!strcmp(el, "url")) + return sdp_xml_parse_url(data); + else if (!strcmp(el, "text")) + return sdp_xml_parse_text(data, elem->type); + else if (!strcmp(el, "nil")) + return sdp_xml_parse_nil(data); + + return NULL; +} +static void element_start(GMarkupParseContext *context, + const char *element_name, const char **attribute_names, + const char **attribute_values, gpointer user_data, GError **err) +{ + struct context_data *ctx_data = user_data; + + if (!strcmp(element_name, "record")) + return; + + if (!strcmp(element_name, "attribute")) { + int i; + for (i = 0; attribute_names[i]; i++) { + if (!strcmp(attribute_names[i], "id")) { + ctx_data->attr_id = strtol(attribute_values[i], 0, 0); + break; + } + } + DBG("New attribute 0x%04x", ctx_data->attr_id); + return; + } + + if (ctx_data->stack_head) { + struct sdp_xml_data *newelem = sdp_xml_data_alloc(); + newelem->next = ctx_data->stack_head; + ctx_data->stack_head = newelem; + } else { + ctx_data->stack_head = sdp_xml_data_alloc(); + ctx_data->stack_head->next = NULL; + } + + if (!strcmp(element_name, "sequence")) + ctx_data->stack_head->data = sdp_data_alloc(SDP_SEQ8, NULL); + else if (!strcmp(element_name, "alternate")) + ctx_data->stack_head->data = sdp_data_alloc(SDP_ALT8, NULL); + else { + int i; + /* Parse value, name, encoding */ + for (i = 0; attribute_names[i]; i++) { + if (!strcmp(attribute_names[i], "value")) { + int curlen = strlen(ctx_data->stack_head->text); + int attrlen = strlen(attribute_values[i]); + + /* Ensure we're big enough */ + while ((curlen + 1 + attrlen) > ctx_data->stack_head->size) + sdp_xml_data_expand(ctx_data->stack_head); + + memcpy(ctx_data->stack_head->text + curlen, + attribute_values[i], attrlen); + ctx_data->stack_head->text[curlen + attrlen] = '\0'; + } + + if (!strcmp(attribute_names[i], "encoding")) { + if (!strcmp(attribute_values[i], "hex")) + ctx_data->stack_head->type = 1; + } + + if (!strcmp(attribute_names[i], "name")) + ctx_data->stack_head->name = strdup(attribute_values[i]); + } + + ctx_data->stack_head->data = sdp_xml_parse_datatype(element_name, + ctx_data->stack_head, ctx_data->record); + + if (ctx_data->stack_head->data == NULL) + error("Can't parse element %s", element_name); + } +} + +static void sdp_xml_data_free(struct sdp_xml_data *elem) +{ + if (elem->data) + sdp_data_free(elem->data); + + free(elem->name); + free(elem->text); + free(elem); +} + +static void element_end(GMarkupParseContext *context, + const char *element_name, gpointer user_data, GError **err) +{ + struct context_data *ctx_data = user_data; + struct sdp_xml_data *elem; + + if (!strcmp(element_name, "record")) + return; + + if (!strcmp(element_name, "attribute")) { + if (ctx_data->stack_head && ctx_data->stack_head->data) { + int ret = sdp_attr_add(ctx_data->record, ctx_data->attr_id, + ctx_data->stack_head->data); + if (ret == -1) + DBG("Could not add attribute 0x%04x", + ctx_data->attr_id); + + ctx_data->stack_head->data = NULL; + sdp_xml_data_free(ctx_data->stack_head); + ctx_data->stack_head = NULL; + } else { + DBG("No data for attribute 0x%04x", ctx_data->attr_id); + } + return; + } + + if (!ctx_data->stack_head || !ctx_data->stack_head->data) { + DBG("No data for %s", element_name); + return; + } + + if (!strcmp(element_name, "sequence")) { + ctx_data->stack_head->data->unitSize = compute_seq_size(ctx_data->stack_head->data); + + if (ctx_data->stack_head->data->unitSize > USHRT_MAX) { + ctx_data->stack_head->data->unitSize += sizeof(uint32_t); + ctx_data->stack_head->data->dtd = SDP_SEQ32; + } else if (ctx_data->stack_head->data->unitSize > UCHAR_MAX) { + ctx_data->stack_head->data->unitSize += sizeof(uint16_t); + ctx_data->stack_head->data->dtd = SDP_SEQ16; + } else { + ctx_data->stack_head->data->unitSize += sizeof(uint8_t); + } + } else if (!strcmp(element_name, "alternate")) { + ctx_data->stack_head->data->unitSize = compute_seq_size(ctx_data->stack_head->data); + + if (ctx_data->stack_head->data->unitSize > USHRT_MAX) { + ctx_data->stack_head->data->unitSize += sizeof(uint32_t); + ctx_data->stack_head->data->dtd = SDP_ALT32; + } else if (ctx_data->stack_head->data->unitSize > UCHAR_MAX) { + ctx_data->stack_head->data->unitSize += sizeof(uint16_t); + ctx_data->stack_head->data->dtd = SDP_ALT16; + } else { + ctx_data->stack_head->data->unitSize += sizeof(uint8_t); + } + } + + if (ctx_data->stack_head->next && ctx_data->stack_head->data && + ctx_data->stack_head->next->data) { + switch (ctx_data->stack_head->next->data->dtd) { + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + case SDP_ALT8: + case SDP_ALT16: + case SDP_ALT32: + ctx_data->stack_head->next->data->val.dataseq = + sdp_seq_append(ctx_data->stack_head->next->data->val.dataseq, + ctx_data->stack_head->data); + ctx_data->stack_head->data = NULL; + break; + } + + elem = ctx_data->stack_head; + ctx_data->stack_head = ctx_data->stack_head->next; + + sdp_xml_data_free(elem); + } +} + +static GMarkupParser parser = { + element_start, element_end, NULL, NULL, NULL +}; + +sdp_record_t *sdp_xml_parse_record(const char *data, int size) +{ + GMarkupParseContext *ctx; + struct context_data *ctx_data; + sdp_record_t *record; + + ctx_data = malloc(sizeof(*ctx_data)); + if (!ctx_data) + return NULL; + + record = sdp_record_alloc(); + if (!record) { + free(ctx_data); + return NULL; + } + + memset(ctx_data, 0, sizeof(*ctx_data)); + ctx_data->record = record; + + ctx = g_markup_parse_context_new(&parser, 0, ctx_data, NULL); + + if (g_markup_parse_context_parse(ctx, data, size, NULL) == FALSE) { + error("XML parsing error"); + g_markup_parse_context_free(ctx); + sdp_record_free(record); + free(ctx_data); + return NULL; + } + + g_markup_parse_context_free(ctx); + + free(ctx_data); + + return record; +} + + +static void convert_raw_data_to_xml(sdp_data_t *value, int indent_level, + void *data, void (*appender)(void *, const char *)) +{ + int i, hex; + char buf[STRBUFSIZE]; + char indent[MAXINDENT]; + + if (!value) + return; + + if (indent_level >= MAXINDENT) + indent_level = MAXINDENT - 2; + + for (i = 0; i < indent_level; i++) + indent[i] = '\t'; + + indent[i] = '\0'; + buf[STRBUFSIZE - 1] = '\0'; + + switch (value->dtd) { + case SDP_DATA_NIL: + appender(data, indent); + appender(data, "\n"); + break; + + case SDP_BOOL: + appender(data, indent); + appender(data, "val.uint8 ? "true" : "false"); + appender(data, "\" />\n"); + break; + + case SDP_UINT8: + appender(data, indent); + appender(data, "val.uint8); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UINT16: + appender(data, indent); + appender(data, "val.uint16); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UINT32: + appender(data, indent); + appender(data, "val.uint32); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UINT64: + appender(data, indent); + appender(data, "val.uint64); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UINT128: + appender(data, indent); + appender(data, "val.uint128.data[i]); + } + + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_INT8: + appender(data, indent); + appender(data, "val.int8); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_INT16: + appender(data, indent); + appender(data, "val.int16); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_INT32: + appender(data, indent); + appender(data, "val.int32); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_INT64: + appender(data, indent); + appender(data, "val.int64); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_INT128: + appender(data, indent); + appender(data, "val.int128.data[i]); + } + appender(data, buf); + + appender(data, "\" />\n"); + break; + + case SDP_UUID16: + appender(data, indent); + appender(data, "val.uuid.value.uuid16); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UUID32: + appender(data, indent); + appender(data, "val.uuid.value.uuid32); + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_UUID128: + appender(data, indent); + appender(data, "val.uuid.value. + uuid128.data[0], + (unsigned char) value->val.uuid.value. + uuid128.data[1], + (unsigned char) value->val.uuid.value. + uuid128.data[2], + (unsigned char) value->val.uuid.value. + uuid128.data[3], + (unsigned char) value->val.uuid.value. + uuid128.data[4], + (unsigned char) value->val.uuid.value. + uuid128.data[5], + (unsigned char) value->val.uuid.value. + uuid128.data[6], + (unsigned char) value->val.uuid.value. + uuid128.data[7], + (unsigned char) value->val.uuid.value. + uuid128.data[8], + (unsigned char) value->val.uuid.value. + uuid128.data[9], + (unsigned char) value->val.uuid.value. + uuid128.data[10], + (unsigned char) value->val.uuid.value. + uuid128.data[11], + (unsigned char) value->val.uuid.value. + uuid128.data[12], + (unsigned char) value->val.uuid.value. + uuid128.data[13], + (unsigned char) value->val.uuid.value. + uuid128.data[14], + (unsigned char) value->val.uuid.value. + uuid128.data[15]); + + appender(data, buf); + appender(data, "\" />\n"); + break; + + case SDP_TEXT_STR8: + case SDP_TEXT_STR16: + case SDP_TEXT_STR32: + { + int num_chars_to_escape = 0; + int length = value->unitSize - 1; + char *strBuf; + + hex = 0; + + for (i = 0; i < length; i++) { + if (!isprint(value->val.str[i]) && + value->val.str[i] != '\0') { + hex = 1; + break; + } + + /* XML is evil, must do this... */ + if ((value->val.str[i] == '<') || + (value->val.str[i] == '>') || + (value->val.str[i] == '"') || + (value->val.str[i] == '&')) + num_chars_to_escape++; + } + + appender(data, indent); + + appender(data, "unitSize-1) * 2 + 1)); + if (!strBuf) { + DBG("No memory to convert raw data to xml"); + return; + } + + /* Unit Size seems to include the size for dtd + It is thus off by 1 + This is safe for Normal strings, but not + hex encoded data */ + for (i = 0; i < (value->unitSize-1); i++) + sprintf(&strBuf[i*sizeof(char)*2], + "%02x", + (unsigned char) value->val.str[i]); + + strBuf[(value->unitSize-1) * 2] = '\0'; + } else { + int j; + /* escape the XML disallowed chars */ + strBuf = malloc(sizeof(char) * + (value->unitSize + 1 + num_chars_to_escape * 4)); + if (!strBuf) { + DBG("No memory to convert raw data to xml"); + return; + } + for (i = 0, j = 0; i < length; i++) { + if (value->val.str[i] == '&') { + strBuf[j++] = '&'; + strBuf[j++] = 'a'; + strBuf[j++] = 'm'; + strBuf[j++] = 'p'; + } else if (value->val.str[i] == '<') { + strBuf[j++] = '&'; + strBuf[j++] = 'l'; + strBuf[j++] = 't'; + } else if (value->val.str[i] == '>') { + strBuf[j++] = '&'; + strBuf[j++] = 'g'; + strBuf[j++] = 't'; + } else if (value->val.str[i] == '"') { + strBuf[j++] = '&'; + strBuf[j++] = 'q'; + strBuf[j++] = 'u'; + strBuf[j++] = 'o'; + strBuf[j++] = 't'; + } else if (value->val.str[i] == '\0') { + strBuf[j++] = ' '; + } else { + strBuf[j++] = value->val.str[i]; + } + } + + strBuf[j] = '\0'; + } + + appender(data, "value=\""); + appender(data, strBuf); + appender(data, "\" />\n"); + free(strBuf); + break; + } + + case SDP_URL_STR8: + case SDP_URL_STR16: + case SDP_URL_STR32: + { + char *strBuf; + + appender(data, indent); + appender(data, "val.str, value->unitSize - 1); + appender(data, strBuf); + free(strBuf); + appender(data, "\" />\n"); + break; + } + + case SDP_SEQ8: + case SDP_SEQ16: + case SDP_SEQ32: + appender(data, indent); + appender(data, "\n"); + + convert_raw_data_to_xml(value->val.dataseq, + indent_level + 1, data, appender); + + appender(data, indent); + appender(data, "\n"); + + break; + + case SDP_ALT8: + case SDP_ALT16: + case SDP_ALT32: + appender(data, indent); + + appender(data, "\n"); + + convert_raw_data_to_xml(value->val.dataseq, + indent_level + 1, data, appender); + appender(data, indent); + + appender(data, "\n"); + + break; + } + + convert_raw_data_to_xml(value->next, indent_level, data, appender); +} + +struct conversion_data { + void *data; + void (*appender)(void *data, const char *); +}; + +static void convert_raw_attr_to_xml_func(void *val, void *data) +{ + struct conversion_data *cd = data; + sdp_data_t *value = val; + char buf[STRBUFSIZE]; + + buf[STRBUFSIZE - 1] = '\0'; + snprintf(buf, STRBUFSIZE - 1, "\t\n", + value->attrId); + cd->appender(cd->data, buf); + + convert_raw_data_to_xml(value, 2, cd->data, cd->appender); + + cd->appender(cd->data, "\t\n"); +} + +/* + * Will convert the sdp record to XML. The appender and data can be used + * to control where to output the record (e.g. file or a data buffer). The + * appender will be called repeatedly with data and the character buffer + * (containing parts of the generated XML) to append. + */ +void convert_sdp_record_to_xml(sdp_record_t *rec, + void *data, void (*appender)(void *, const char *)) +{ + struct conversion_data cd; + + cd.data = data; + cd.appender = appender; + + if (rec && rec->attrlist) { + appender(data, "\n\n"); + appender(data, "\n"); + sdp_list_foreach(rec->attrlist, + convert_raw_attr_to_xml_func, &cd); + appender(data, "\n"); + } +} diff --git a/src/sdp-xml.h b/src/sdp-xml.h new file mode 100644 index 0000000..80a4f44 --- /dev/null +++ b/src/sdp-xml.h @@ -0,0 +1,27 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +void convert_sdp_record_to_xml(sdp_record_t *rec, + void *user_data, void (*append_func) (void *, const char *)); + +sdp_record_t *sdp_xml_parse_record(const char *data, int size); diff --git a/src/sdpd-database.c b/src/sdpd-database.c new file mode 100644 index 0000000..843b6d0 --- /dev/null +++ b/src/sdpd-database.c @@ -0,0 +1,305 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "sdpd.h" +#include "log.h" + +static sdp_list_t *service_db; +static sdp_list_t *access_db; + +typedef struct { + uint32_t handle; + bdaddr_t device; +} sdp_access_t; + +/* + * Ordering function called when inserting a service record. + * The service repository is a linked list in sorted order + * and the service record handle is the sort key + */ +int record_sort(const void *r1, const void *r2) +{ + const sdp_record_t *rec1 = r1; + const sdp_record_t *rec2 = r2; + + if (!rec1 || !rec2) { + error("NULL RECORD LIST FATAL"); + return -1; + } + + return rec1->handle - rec2->handle; +} + +static int access_sort(const void *r1, const void *r2) +{ + const sdp_access_t *rec1 = r1; + const sdp_access_t *rec2 = r2; + + if (!rec1 || !rec2) { + error("NULL RECORD LIST FATAL"); + return -1; + } + + return rec1->handle - rec2->handle; +} + +static void access_free(void *p) +{ + free(p); +} + +/* + * Reset the service repository by deleting its contents + */ +void sdp_svcdb_reset(void) +{ + sdp_list_free(service_db, (sdp_free_func_t) sdp_record_free); + service_db = NULL; + + sdp_list_free(access_db, access_free); + access_db = NULL; +} + +typedef struct _indexed { + int sock; + sdp_record_t *record; +} sdp_indexed_t; + +static sdp_list_t *socket_index; + +/* + * collect all services registered over this socket + */ +void sdp_svcdb_collect_all(int sock) +{ + sdp_list_t *p, *q; + + for (p = socket_index, q = 0; p; ) { + sdp_indexed_t *item = p->data; + if (item->sock == sock) { + sdp_list_t *next = p->next; + sdp_record_remove(item->record->handle); + sdp_record_free(item->record); + free(item); + if (q) + q->next = next; + else + socket_index = next; + free(p); + p = next; + } else if (item->sock > sock) + return; + else { + q = p; + p = p->next; + } + } +} + +void sdp_svcdb_collect(sdp_record_t *rec) +{ + sdp_list_t *p, *q; + + for (p = socket_index, q = 0; p; q = p, p = p->next) { + sdp_indexed_t *item = p->data; + if (rec == item->record) { + free(item); + if (q) + q->next = p->next; + else + socket_index = p->next; + free(p); + return; + } + } +} + +static int compare_indices(const void *i1, const void *i2) +{ + const sdp_indexed_t *s1 = i1; + const sdp_indexed_t *s2 = i2; + return s1->sock - s2->sock; +} + +void sdp_svcdb_set_collectable(sdp_record_t *record, int sock) +{ + sdp_indexed_t *item = malloc(sizeof(sdp_indexed_t)); + + if (!item) { + SDPDBG("No memory"); + return; + } + + item->sock = sock; + item->record = record; + socket_index = sdp_list_insert_sorted(socket_index, item, compare_indices); +} + +/* + * Add a service record to the repository + */ +void sdp_record_add(const bdaddr_t *device, sdp_record_t *rec) +{ + sdp_access_t *dev; + + SDPDBG("Adding rec : 0x%lx", (long) rec); + SDPDBG("with handle : 0x%x", rec->handle); + + service_db = sdp_list_insert_sorted(service_db, rec, record_sort); + + dev = malloc(sizeof(*dev)); + if (!dev) + return; + + bacpy(&dev->device, device); + dev->handle = rec->handle; + + access_db = sdp_list_insert_sorted(access_db, dev, access_sort); +} + +static sdp_list_t *record_locate(uint32_t handle) +{ + if (service_db) { + sdp_list_t *p; + sdp_record_t r; + + r.handle = handle; + p = sdp_list_find(service_db, &r, record_sort); + return p; + } + + SDPDBG("Could not find svcRec for : 0x%x", handle); + return NULL; +} + +static sdp_list_t *access_locate(uint32_t handle) +{ + if (access_db) { + sdp_list_t *p; + sdp_access_t a; + + a.handle = handle; + p = sdp_list_find(access_db, &a, access_sort); + return p; + } + + SDPDBG("Could not find access data for : 0x%x", handle); + return NULL; +} + +/* + * Given a service record handle, find the record associated with it. + */ +sdp_record_t *sdp_record_find(uint32_t handle) +{ + sdp_list_t *p = record_locate(handle); + + if (!p) { + SDPDBG("Couldn't find record for : 0x%x", handle); + return 0; + } + + return p->data; +} + +/* + * Given a service record handle, remove its record from the repository + */ +int sdp_record_remove(uint32_t handle) +{ + sdp_list_t *p = record_locate(handle); + sdp_record_t *r; + sdp_access_t *a; + + if (!p) { + error("Remove : Couldn't find record for : 0x%x", handle); + return -1; + } + + r = p->data; + if (r) + service_db = sdp_list_remove(service_db, r); + + p = access_locate(handle); + if (p == NULL || p->data == NULL) + return 0; + + a = p->data; + + access_db = sdp_list_remove(access_db, a); + access_free(a); + + return 0; +} + +/* + * Return a pointer to the linked list containing the records in sorted order + */ +sdp_list_t *sdp_get_record_list(void) +{ + return service_db; +} + +int sdp_check_access(uint32_t handle, bdaddr_t *device) +{ + sdp_list_t *p = access_locate(handle); + sdp_access_t *a; + + if (!p) + return 1; + + a = p->data; + if (!a) + return 1; + + if (bacmp(&a->device, device) && + bacmp(&a->device, BDADDR_ANY) && + bacmp(device, BDADDR_ANY)) + return 0; + + return 1; +} + +uint32_t sdp_next_handle(void) +{ + uint32_t handle = 0x10000; + + while (sdp_record_find(handle)) + handle++; + + return handle; +} diff --git a/src/sdpd-request.c b/src/sdpd-request.c new file mode 100644 index 0000000..deaed26 --- /dev/null +++ b/src/sdpd-request.c @@ -0,0 +1,1123 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/l2cap.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "src/shared/util.h" + +#include "sdpd.h" +#include "log.h" + +typedef struct { + uint32_t timestamp; + union { + uint16_t maxBytesSent; + uint16_t lastIndexSent; + } cStateValue; +} sdp_cont_state_t; + +#define SDP_CONT_STATE_SIZE (sizeof(uint8_t) + sizeof(sdp_cont_state_t)) + +#define MIN(x, y) ((x) < (y)) ? (x): (y) + +typedef struct _sdp_cstate_list sdp_cstate_list_t; + +struct _sdp_cstate_list { + sdp_cstate_list_t *next; + uint32_t timestamp; + sdp_buf_t buf; +}; + +static sdp_cstate_list_t *cstates; + +/* FIXME: should probably remove it when it's found */ +static sdp_buf_t *sdp_get_cached_rsp(sdp_cont_state_t *cstate) +{ + sdp_cstate_list_t *p; + + for (p = cstates; p; p = p->next) { + /* Check timestamp */ + if (p->timestamp != cstate->timestamp) + continue; + + /* Check if requesting more than available */ + if (cstate->cStateValue.maxBytesSent < p->buf.data_size) + return &p->buf; + } + + return 0; +} + +static uint32_t sdp_cstate_alloc_buf(sdp_buf_t *buf) +{ + sdp_cstate_list_t *cstate = malloc(sizeof(sdp_cstate_list_t)); + uint8_t *data = malloc(buf->data_size); + + memcpy(data, buf->data, buf->data_size); + memset((char *)cstate, 0, sizeof(sdp_cstate_list_t)); + cstate->buf.data = data; + cstate->buf.data_size = buf->data_size; + cstate->buf.buf_size = buf->data_size; + cstate->timestamp = sdp_get_time(); + cstate->next = cstates; + cstates = cstate; + return cstate->timestamp; +} + +/* Additional values for checking datatype (not in spec) */ +#define SDP_TYPE_UUID 0xfe +#define SDP_TYPE_ATTRID 0xff + +struct attrid { + uint8_t dtd; + union { + uint16_t uint16; + uint32_t uint32; + }; +}; + +/* + * Generic data element sequence extractor. Builds + * a list whose elements are those found in the + * sequence. The data type of elements found in the + * sequence is returned in the reference pDataType + */ +static int extract_des(uint8_t *buf, int len, sdp_list_t **svcReqSeq, uint8_t *pDataType, uint8_t expectedType) +{ + uint8_t seqType; + int scanned, data_size = 0; + short numberOfElements = 0; + int seqlen = 0; + sdp_list_t *pSeq = NULL; + uint8_t dataType; + int status = 0; + const uint8_t *p; + size_t bufsize; + + scanned = sdp_extract_seqtype(buf, len, &seqType, &data_size); + + SDPDBG("Seq type : %d", seqType); + if (!scanned || (seqType != SDP_SEQ8 && seqType != SDP_SEQ16)) { + error("Unknown seq type"); + return -1; + } + p = buf + scanned; + bufsize = len - scanned; + + SDPDBG("Data size : %d", data_size); + + for (;;) { + char *pElem = NULL; + int localSeqLength = 0; + uuid_t *puuid; + + if (bufsize < sizeof(uint8_t)) { + SDPDBG("->Unexpected end of buffer"); + goto failed; + } + + dataType = *p; + + SDPDBG("Data type: 0x%02x", dataType); + + if (expectedType == SDP_TYPE_UUID) { + if (dataType != SDP_UUID16 && dataType != SDP_UUID32 && dataType != SDP_UUID128) { + SDPDBG("->Unexpected Data type (expected UUID_ANY)"); + goto failed; + } + } else if (expectedType == SDP_TYPE_ATTRID && + (dataType != SDP_UINT16 && dataType != SDP_UINT32)) { + SDPDBG("->Unexpected Data type (expected 0x%02x or 0x%02x)", + SDP_UINT16, SDP_UINT32); + goto failed; + } else if (expectedType != SDP_TYPE_ATTRID && dataType != expectedType) { + SDPDBG("->Unexpected Data type (expected 0x%02x)", expectedType); + goto failed; + } + + switch (dataType) { + case SDP_UINT16: + p += sizeof(uint8_t); + seqlen += sizeof(uint8_t); + bufsize -= sizeof(uint8_t); + if (bufsize < sizeof(uint16_t)) { + SDPDBG("->Unexpected end of buffer"); + goto failed; + } + + if (expectedType == SDP_TYPE_ATTRID) { + struct attrid *aid; + aid = malloc(sizeof(struct attrid)); + aid->dtd = dataType; + aid->uint16 = get_be16(p); + pElem = (char *) aid; + } else { + uint16_t tmp; + + memcpy(&tmp, p, sizeof(tmp)); + + pElem = malloc(sizeof(uint16_t)); + put_be16(tmp, pElem); + } + p += sizeof(uint16_t); + seqlen += sizeof(uint16_t); + bufsize -= sizeof(uint16_t); + break; + case SDP_UINT32: + p += sizeof(uint8_t); + seqlen += sizeof(uint8_t); + bufsize -= sizeof(uint8_t); + if (bufsize < (int)sizeof(uint32_t)) { + SDPDBG("->Unexpected end of buffer"); + goto failed; + } + + if (expectedType == SDP_TYPE_ATTRID) { + struct attrid *aid; + aid = malloc(sizeof(struct attrid)); + aid->dtd = dataType; + aid->uint32 = get_be32(p); + + pElem = (char *) aid; + } else { + uint32_t tmp; + + memcpy(&tmp, p, sizeof(tmp)); + + pElem = malloc(sizeof(uint32_t)); + put_be32(tmp, pElem); + } + p += sizeof(uint32_t); + seqlen += sizeof(uint32_t); + bufsize -= sizeof(uint32_t); + break; + case SDP_UUID16: + case SDP_UUID32: + case SDP_UUID128: + puuid = malloc(sizeof(uuid_t)); + status = sdp_uuid_extract(p, bufsize, puuid, &localSeqLength); + if (status < 0) { + free(puuid); + goto failed; + } + + pElem = (char *) puuid; + seqlen += localSeqLength; + p += localSeqLength; + bufsize -= localSeqLength; + break; + default: + return -1; + } + if (status == 0) { + pSeq = sdp_list_append(pSeq, pElem); + numberOfElements++; + SDPDBG("No of elements : %d", numberOfElements); + + if (seqlen == data_size) + break; + else if (seqlen > data_size || seqlen > len) + goto failed; + } else + free(pElem); + } + *svcReqSeq = pSeq; + scanned += seqlen; + *pDataType = dataType; + return scanned; + +failed: + sdp_list_free(pSeq, free); + return -1; +} + +static int sdp_set_cstate_pdu(sdp_buf_t *buf, sdp_cont_state_t *cstate) +{ + uint8_t *pdata = buf->data + buf->data_size; + int length = 0; + + if (cstate) { + SDPDBG("Non null sdp_cstate_t id : 0x%x", cstate->timestamp); + *pdata = sizeof(sdp_cont_state_t); + pdata += sizeof(uint8_t); + length += sizeof(uint8_t); + memcpy(pdata, cstate, sizeof(sdp_cont_state_t)); + length += sizeof(sdp_cont_state_t); + } else { + /* set "null" continuation state */ + *pdata = 0; + length += sizeof(uint8_t); + } + buf->data_size += length; + return length; +} + +static int sdp_cstate_get(uint8_t *buffer, size_t len, + sdp_cont_state_t **cstate) +{ + uint8_t cStateSize = *buffer; + + SDPDBG("Continuation State size : %d", cStateSize); + + if (cStateSize == 0) { + *cstate = NULL; + return 0; + } + + buffer++; + len--; + + if (len < sizeof(sdp_cont_state_t)) + return -EINVAL; + + /* + * Check if continuation state exists, if yes attempt + * to get response remainder from cache, else send error + */ + + *cstate = malloc(sizeof(sdp_cont_state_t)); + if (!(*cstate)) + return -ENOMEM; + + memcpy(*cstate, buffer, sizeof(sdp_cont_state_t)); + + SDPDBG("Cstate TS : 0x%x", (*cstate)->timestamp); + SDPDBG("Bytes sent : %d", (*cstate)->cStateValue.maxBytesSent); + + return 0; +} + +/* + * The matching process is defined as "each and every UUID + * specified in the "search pattern" must be present in the + * "target pattern". Here "search pattern" is the set of UUIDs + * specified by the service discovery client and "target pattern" + * is the set of UUIDs present in a service record. + * + * Return 1 if each and every UUID in the search + * pattern exists in the target pattern, 0 if the + * match succeeds and -1 on error. + */ +static int sdp_match_uuid(sdp_list_t *search, sdp_list_t *pattern) +{ + /* + * The target is a sorted list, so we need not look + * at all elements to confirm existence of an element + * from the search pattern + */ + int patlen = sdp_list_len(pattern); + + if (patlen < sdp_list_len(search)) + return -1; + for (; search; search = search->next) { + uuid_t *uuid128; + void *data = search->data; + sdp_list_t *list; + if (data == NULL) + return -1; + + /* create 128-bit form of the search UUID */ + uuid128 = sdp_uuid_to_uuid128((uuid_t *)data); + list = sdp_list_find(pattern, uuid128, sdp_uuid128_cmp); + bt_free(uuid128); + if (!list) + return 0; + } + return 1; +} + +/* + * Service search request PDU. This method extracts the search pattern + * (a sequence of UUIDs) and calls the matching function + * to find matching services + */ +static int service_search_req(sdp_req_t *req, sdp_buf_t *buf) +{ + int status = 0, i, plen, mlen, mtu, scanned; + sdp_list_t *pattern = NULL; + uint16_t expected, actual, rsp_count = 0; + uint8_t dtd; + sdp_cont_state_t *cstate = NULL; + uint8_t *pCacheBuffer = NULL; + int handleSize = 0; + uint32_t cStateId = 0; + uint8_t *pTotalRecordCount, *pCurrentRecordCount; + uint8_t *pdata = req->buf + sizeof(sdp_pdu_hdr_t); + size_t data_left = req->len - sizeof(sdp_pdu_hdr_t); + + scanned = extract_des(pdata, data_left, &pattern, &dtd, SDP_TYPE_UUID); + + if (scanned == -1) { + status = SDP_INVALID_SYNTAX; + goto done; + } + pdata += scanned; + data_left -= scanned; + + plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen); + mlen = scanned + sizeof(uint16_t) + 1; + /* ensure we don't read past buffer */ + if (plen < mlen || plen != mlen + *(uint8_t *)(pdata+sizeof(uint16_t))) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + if (data_left < sizeof(uint16_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + expected = get_be16(pdata); + + SDPDBG("Expected count: %d", expected); + SDPDBG("Bytes scanned : %d", scanned); + + pdata += sizeof(uint16_t); + data_left -= sizeof(uint16_t); + + /* + * Check if continuation state exists, if yes attempt + * to get rsp remainder from cache, else send error + */ + if (sdp_cstate_get(pdata, data_left, &cstate) < 0) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + mtu = req->mtu - sizeof(sdp_pdu_hdr_t) - sizeof(uint16_t) - sizeof(uint16_t) - SDP_CONT_STATE_SIZE; + actual = MIN(expected, mtu >> 2); + + /* make space in the rsp buffer for total and current record counts */ + pdata = buf->data; + + /* total service record count = 0 */ + pTotalRecordCount = pdata; + put_be16(0, pdata); + pdata += sizeof(uint16_t); + buf->data_size += sizeof(uint16_t); + + /* current service record count = 0 */ + pCurrentRecordCount = pdata; + put_be16(0, pdata); + pdata += sizeof(uint16_t); + buf->data_size += sizeof(uint16_t); + + if (cstate == NULL) { + /* for every record in the DB, do a pattern search */ + sdp_list_t *list = sdp_get_record_list(); + + handleSize = 0; + for (; list && rsp_count < expected; list = list->next) { + sdp_record_t *rec = list->data; + + SDPDBG("Checking svcRec : 0x%x", rec->handle); + + if (sdp_match_uuid(pattern, rec->pattern) > 0 && + sdp_check_access(rec->handle, &req->device)) { + rsp_count++; + put_be32(rec->handle, pdata); + pdata += sizeof(uint32_t); + handleSize += sizeof(uint32_t); + } + } + + SDPDBG("Match count: %d", rsp_count); + + buf->data_size += handleSize; + put_be16(rsp_count, pTotalRecordCount); + put_be16(rsp_count, pCurrentRecordCount); + + if (rsp_count > actual) { + /* cache the rsp and generate a continuation state */ + cStateId = sdp_cstate_alloc_buf(buf); + /* + * subtract handleSize since we now send only + * a subset of handles + */ + buf->data_size -= handleSize; + } else { + /* NULL continuation state */ + sdp_set_cstate_pdu(buf, NULL); + } + } + + /* under both the conditions below, the rsp buffer is not built yet */ + if (cstate || cStateId > 0) { + short lastIndex = 0; + + if (cstate) { + /* + * Get the previous sdp_cont_state_t and obtain + * the cached rsp + */ + sdp_buf_t *pCache = sdp_get_cached_rsp(cstate); + if (pCache) { + pCacheBuffer = pCache->data; + /* get the rsp_count from the cached buffer */ + rsp_count = get_be16(pCacheBuffer); + + /* get index of the last sdp_record_t sent */ + lastIndex = cstate->cStateValue.lastIndexSent; + } else { + status = SDP_INVALID_CSTATE; + goto done; + } + } else { + pCacheBuffer = buf->data; + lastIndex = 0; + } + + /* + * Set the local buffer pointer to after the + * current record count and increment the cached + * buffer pointer to beyond the counters + */ + pdata = pCurrentRecordCount + sizeof(uint16_t); + + /* increment beyond the totalCount and the currentCount */ + pCacheBuffer += 2 * sizeof(uint16_t); + + if (cstate) { + handleSize = 0; + for (i = lastIndex; (i - lastIndex) < actual && i < rsp_count; i++) { + memcpy(pdata, pCacheBuffer + i * sizeof(uint32_t), sizeof(uint32_t)); + pdata += sizeof(uint32_t); + handleSize += sizeof(uint32_t); + } + } else { + handleSize = actual << 2; + i = actual; + } + + buf->data_size += handleSize; + put_be16(rsp_count, pTotalRecordCount); + put_be16(i - lastIndex, pCurrentRecordCount); + + if (i == rsp_count) { + /* set "null" continuationState */ + sdp_set_cstate_pdu(buf, NULL); + } else { + /* + * there's more: set lastIndexSent to + * the new value and move on + */ + sdp_cont_state_t newState; + + SDPDBG("Setting non-NULL sdp_cstate_t"); + + if (cstate) + memcpy(&newState, cstate, sizeof(sdp_cont_state_t)); + else { + memset(&newState, 0, sizeof(sdp_cont_state_t)); + newState.timestamp = cStateId; + } + newState.cStateValue.lastIndexSent = i; + sdp_set_cstate_pdu(buf, &newState); + } + } + +done: + free(cstate); + if (pattern) + sdp_list_free(pattern, free); + + return status; +} + +/* + * Extract attribute identifiers from the request PDU. + * Clients could request a subset of attributes (by id) + * from a service record, instead of the whole set. The + * requested identifiers are present in the PDU form of + * the request + */ +static int extract_attrs(sdp_record_t *rec, sdp_list_t *seq, sdp_buf_t *buf) +{ + sdp_buf_t pdu; + + if (!rec) + return SDP_INVALID_RECORD_HANDLE; + + if (seq == NULL) { + SDPDBG("Attribute sequence is NULL"); + return 0; + } + + SDPDBG("Entries in attr seq : %d", sdp_list_len(seq)); + + sdp_gen_record_pdu(rec, &pdu); + + for (; seq; seq = seq->next) { + struct attrid *aid = seq->data; + + SDPDBG("AttrDataType : %d", aid->dtd); + + if (aid->dtd == SDP_UINT16) { + uint16_t attr = aid->uint16; + sdp_data_t *a = sdp_data_get(rec, attr); + if (a) + sdp_append_to_pdu(buf, a); + } else if (aid->dtd == SDP_UINT32) { + uint32_t range = aid->uint32; + uint16_t attr; + uint16_t low = (0xffff0000 & range) >> 16; + uint16_t high = 0x0000ffff & range; + sdp_data_t *data; + + SDPDBG("attr range : 0x%x", range); + SDPDBG("Low id : 0x%x", low); + SDPDBG("High id : 0x%x", high); + + if (low == 0x0000 && high == 0xffff && pdu.data_size <= buf->buf_size) { + /* copy it */ + memcpy(buf->data, pdu.data, pdu.data_size); + buf->data_size = pdu.data_size; + break; + } + /* (else) sub-range of attributes */ + for (attr = low; attr < high; attr++) { + data = sdp_data_get(rec, attr); + if (data) + sdp_append_to_pdu(buf, data); + } + data = sdp_data_get(rec, high); + if (data) + sdp_append_to_pdu(buf, data); + } else { + error("Unexpected data type : 0x%x", aid->dtd); + error("Expect uint16_t or uint32_t"); + free(pdu.data); + return SDP_INVALID_SYNTAX; + } + } + + free(pdu.data); + + return 0; +} + +/* Build cstate response */ +static int sdp_cstate_rsp(sdp_cont_state_t *cstate, sdp_buf_t *buf, + uint16_t max) +{ + /* continuation State exists -> get from cache */ + sdp_buf_t *cache = sdp_get_cached_rsp(cstate); + uint16_t sent; + + if (!cache) + return 0; + + sent = MIN(max, cache->data_size - cstate->cStateValue.maxBytesSent); + memcpy(buf->data, cache->data + cstate->cStateValue.maxBytesSent, sent); + buf->data_size += sent; + cstate->cStateValue.maxBytesSent += sent; + + SDPDBG("Response size : %d sending now : %d bytes sent so far : %d", + cache->data_size, sent, cstate->cStateValue.maxBytesSent); + + if (cstate->cStateValue.maxBytesSent == cache->data_size) + return sdp_set_cstate_pdu(buf, NULL); + + return sdp_set_cstate_pdu(buf, cstate); +} + +/* + * A request for the attributes of a service record. + * First check if the service record (specified by + * service record handle) exists, then call the attribute + * streaming function + */ +static int service_attr_req(sdp_req_t *req, sdp_buf_t *buf) +{ + sdp_cont_state_t *cstate = NULL; + short cstate_size = 0; + sdp_list_t *seq = NULL; + uint8_t dtd = 0; + int scanned = 0; + unsigned int max_rsp_size; + int status = 0, plen, mlen; + uint8_t *pdata = req->buf + sizeof(sdp_pdu_hdr_t); + size_t data_left = req->len - sizeof(sdp_pdu_hdr_t); + uint32_t handle; + + if (data_left < sizeof(uint32_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + handle = get_be32(pdata); + + pdata += sizeof(uint32_t); + data_left -= sizeof(uint32_t); + + if (data_left < sizeof(uint16_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + max_rsp_size = get_be16(pdata); + + pdata += sizeof(uint16_t); + data_left -= sizeof(uint16_t); + + if (data_left < sizeof(sdp_pdu_hdr_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + /* extract the attribute list */ + scanned = extract_des(pdata, data_left, &seq, &dtd, SDP_TYPE_ATTRID); + if (scanned == -1) { + status = SDP_INVALID_SYNTAX; + goto done; + } + pdata += scanned; + data_left -= scanned; + + plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen); + mlen = scanned + sizeof(uint32_t) + sizeof(uint16_t) + 1; + /* ensure we don't read past buffer */ + if (plen < mlen || plen != mlen + *(uint8_t *)pdata) { + status = SDP_INVALID_PDU_SIZE; + goto done; + } + + /* + * if continuation state exists, attempt + * to get rsp remainder from cache, else send error + */ + if (sdp_cstate_get(pdata, data_left, &cstate) < 0) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + SDPDBG("SvcRecHandle : 0x%x", handle); + SDPDBG("max_rsp_size : %d", max_rsp_size); + + /* + * Check that max_rsp_size is within valid range + * a minimum size of 0x0007 has to be used for data field + */ + if (max_rsp_size < 0x0007) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + /* + * Calculate Attribute size according to MTU + * We can send only (MTU - sizeof(sdp_pdu_hdr_t) - sizeof(sdp_cont_state_t)) + */ + max_rsp_size = MIN(max_rsp_size, req->mtu - sizeof(sdp_pdu_hdr_t) - + sizeof(uint32_t) - SDP_CONT_STATE_SIZE - sizeof(uint16_t)); + + /* pull header for AttributeList byte count */ + buf->data += sizeof(uint16_t); + buf->buf_size -= sizeof(uint16_t); + + if (cstate) { + cstate_size = sdp_cstate_rsp(cstate, buf, max_rsp_size); + if (!cstate_size) { + status = SDP_INVALID_CSTATE; + error("NULL cache buffer and non-NULL continuation state"); + } + } else { + sdp_record_t *rec = sdp_record_find(handle); + status = extract_attrs(rec, seq, buf); + if (buf->data_size > max_rsp_size) { + sdp_cont_state_t newState; + + memset((char *)&newState, 0, sizeof(sdp_cont_state_t)); + newState.timestamp = sdp_cstate_alloc_buf(buf); + /* + * Reset the buffer size to the maximum expected and + * set the sdp_cont_state_t + */ + SDPDBG("Creating continuation state of size : %d", buf->data_size); + buf->data_size = max_rsp_size; + newState.cStateValue.maxBytesSent = max_rsp_size; + cstate_size = sdp_set_cstate_pdu(buf, &newState); + } else { + if (buf->data_size == 0) + sdp_append_to_buf(buf, NULL, 0); + cstate_size = sdp_set_cstate_pdu(buf, NULL); + } + } + + /* push header */ + buf->data -= sizeof(uint16_t); + buf->buf_size += sizeof(uint16_t); + +done: + free(cstate); + if (seq) + sdp_list_free(seq, free); + if (status) + return status; + + /* set attribute list byte count */ + put_be16(buf->data_size - cstate_size, buf->data); + buf->data_size += sizeof(uint16_t); + return 0; +} + +/* + * combined service search and attribute extraction + */ +static int service_search_attr_req(sdp_req_t *req, sdp_buf_t *buf) +{ + int status = 0, plen, totscanned; + uint8_t *pdata; + unsigned int max; + int scanned, rsp_count = 0; + sdp_list_t *pattern = NULL, *seq = NULL, *svcList; + sdp_cont_state_t *cstate = NULL; + short cstate_size = 0; + uint8_t dtd = 0; + sdp_buf_t tmpbuf; + size_t data_left; + + tmpbuf.data = NULL; + pdata = req->buf + sizeof(sdp_pdu_hdr_t); + data_left = req->len - sizeof(sdp_pdu_hdr_t); + scanned = extract_des(pdata, data_left, &pattern, &dtd, SDP_TYPE_UUID); + if (scanned == -1) { + status = SDP_INVALID_SYNTAX; + goto done; + } + totscanned = scanned; + + SDPDBG("Bytes scanned: %d", scanned); + + pdata += scanned; + data_left -= scanned; + + if (data_left < sizeof(uint16_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + max = get_be16(pdata); + + pdata += sizeof(uint16_t); + data_left -= sizeof(uint16_t); + + SDPDBG("Max Attr expected: %d", max); + + if (data_left < sizeof(sdp_pdu_hdr_t)) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + /* extract the attribute list */ + scanned = extract_des(pdata, data_left, &seq, &dtd, SDP_TYPE_ATTRID); + if (scanned == -1) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + pdata += scanned; + data_left -= scanned; + + totscanned += scanned + sizeof(uint16_t) + 1; + + plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen); + if (plen < totscanned || plen != totscanned + *(uint8_t *)pdata) { + status = SDP_INVALID_PDU_SIZE; + goto done; + } + + /* + * if continuation state exists attempt + * to get rsp remainder from cache, else send error + */ + if (sdp_cstate_get(pdata, data_left, &cstate) < 0) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + svcList = sdp_get_record_list(); + + tmpbuf.data = malloc(USHRT_MAX); + tmpbuf.data_size = 0; + tmpbuf.buf_size = USHRT_MAX; + memset(tmpbuf.data, 0, USHRT_MAX); + + /* + * Calculate Attribute size according to MTU + * We can send only (MTU - sizeof(sdp_pdu_hdr_t) - sizeof(sdp_cont_state_t)) + */ + max = MIN(max, req->mtu - sizeof(sdp_pdu_hdr_t) - SDP_CONT_STATE_SIZE - sizeof(uint16_t)); + + /* pull header for AttributeList byte count */ + buf->data += sizeof(uint16_t); + buf->buf_size -= sizeof(uint16_t); + + if (cstate == NULL) { + /* no continuation state -> create new response */ + sdp_list_t *p; + for (p = svcList; p; p = p->next) { + sdp_record_t *rec = p->data; + if (sdp_match_uuid(pattern, rec->pattern) > 0 && + sdp_check_access(rec->handle, &req->device)) { + rsp_count++; + status = extract_attrs(rec, seq, &tmpbuf); + + SDPDBG("Response count : %d", rsp_count); + SDPDBG("Local PDU size : %d", tmpbuf.data_size); + if (status) { + SDPDBG("Extract attr from record returns err"); + break; + } + if (buf->data_size + tmpbuf.data_size < buf->buf_size) { + /* to be sure no relocations */ + sdp_append_to_buf(buf, tmpbuf.data, tmpbuf.data_size); + tmpbuf.data_size = 0; + memset(tmpbuf.data, 0, USHRT_MAX); + } else { + error("Relocation needed"); + break; + } + SDPDBG("Net PDU size : %d", buf->data_size); + } + } + if (buf->data_size > max) { + sdp_cont_state_t newState; + + memset((char *)&newState, 0, sizeof(sdp_cont_state_t)); + newState.timestamp = sdp_cstate_alloc_buf(buf); + /* + * Reset the buffer size to the maximum expected and + * set the sdp_cont_state_t + */ + buf->data_size = max; + newState.cStateValue.maxBytesSent = max; + cstate_size = sdp_set_cstate_pdu(buf, &newState); + } else + cstate_size = sdp_set_cstate_pdu(buf, NULL); + } else { + cstate_size = sdp_cstate_rsp(cstate, buf, max); + if (!cstate_size) { + status = SDP_INVALID_CSTATE; + SDPDBG("Non-null continuation state, but null cache buffer"); + } + } + + if (!rsp_count && !cstate) { + /* found nothing */ + buf->data_size = 0; + sdp_append_to_buf(buf, tmpbuf.data, tmpbuf.data_size); + sdp_set_cstate_pdu(buf, NULL); + } + + /* push header */ + buf->data -= sizeof(uint16_t); + buf->buf_size += sizeof(uint16_t); + + if (!status) { + /* set attribute list byte count */ + put_be16(buf->data_size - cstate_size, buf->data); + buf->data_size += sizeof(uint16_t); + } + +done: + free(cstate); + free(tmpbuf.data); + if (pattern) + sdp_list_free(pattern, free); + if (seq) + sdp_list_free(seq, free); + return status; +} + +/* + * Top level request processor. Calls the appropriate processing + * function based on request type. Handles service registration + * client requests also. + */ +static void process_request(sdp_req_t *req) +{ + sdp_pdu_hdr_t *reqhdr = (sdp_pdu_hdr_t *)req->buf; + sdp_pdu_hdr_t *rsphdr; + sdp_buf_t rsp; + uint8_t *buf = malloc(USHRT_MAX); + int status = SDP_INVALID_SYNTAX; + + memset(buf, 0, USHRT_MAX); + rsp.data = buf + sizeof(sdp_pdu_hdr_t); + rsp.data_size = 0; + rsp.buf_size = USHRT_MAX - sizeof(sdp_pdu_hdr_t); + rsphdr = (sdp_pdu_hdr_t *)buf; + + if (ntohs(reqhdr->plen) != req->len - sizeof(sdp_pdu_hdr_t)) { + status = SDP_INVALID_PDU_SIZE; + goto send_rsp; + } + switch (reqhdr->pdu_id) { + case SDP_SVC_SEARCH_REQ: + SDPDBG("Got a svc srch req"); + status = service_search_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_SEARCH_RSP; + break; + case SDP_SVC_ATTR_REQ: + SDPDBG("Got a svc attr req"); + status = service_attr_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_ATTR_RSP; + break; + case SDP_SVC_SEARCH_ATTR_REQ: + SDPDBG("Got a svc srch attr req"); + status = service_search_attr_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_SEARCH_ATTR_RSP; + break; + /* Following requests are allowed only for local connections */ + case SDP_SVC_REGISTER_REQ: + SDPDBG("Service register request"); + if (req->local) { + status = service_register_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_REGISTER_RSP; + } + break; + case SDP_SVC_UPDATE_REQ: + SDPDBG("Service update request"); + if (req->local) { + status = service_update_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_UPDATE_RSP; + } + break; + case SDP_SVC_REMOVE_REQ: + SDPDBG("Service removal request"); + if (req->local) { + status = service_remove_req(req, &rsp); + rsphdr->pdu_id = SDP_SVC_REMOVE_RSP; + } + break; + default: + error("Unknown PDU ID : 0x%x received", reqhdr->pdu_id); + status = SDP_INVALID_SYNTAX; + break; + } + +send_rsp: + if (status) { + rsphdr->pdu_id = SDP_ERROR_RSP; + put_be16(status, rsp.data); + rsp.data_size = sizeof(uint16_t); + } + + SDPDBG("Sending rsp. status %d", status); + + rsphdr->tid = reqhdr->tid; + rsphdr->plen = htons(rsp.data_size); + + /* point back to the real buffer start and set the real rsp length */ + rsp.data_size += sizeof(sdp_pdu_hdr_t); + rsp.data = buf; + + /* stream the rsp PDU */ + if (send(req->sock, rsp.data, rsp.data_size, 0) < 0) + error("send: %s (%d)", strerror(errno), errno); + + SDPDBG("Bytes Sent : %d", rsp.data_size); + + free(rsp.data); + free(req->buf); +} + +void handle_internal_request(int sk, int mtu, void *data, int len) +{ + sdp_req_t req; + + bacpy(&req.device, BDADDR_ANY); + bacpy(&req.bdaddr, BDADDR_LOCAL); + req.local = 0; + req.sock = sk; + req.mtu = mtu; + req.flags = 0; + req.buf = data; + req.len = len; + + process_request(&req); +} + +void handle_request(int sk, uint8_t *data, int len) +{ + struct sockaddr_l2 sa; + socklen_t size; + sdp_req_t req; + + size = sizeof(sa); + if (getpeername(sk, (struct sockaddr *) &sa, &size) < 0) { + error("getpeername: %s", strerror(errno)); + return; + } + + if (sa.l2_family == AF_BLUETOOTH) { + struct l2cap_options lo; + + memset(&lo, 0, sizeof(lo)); + size = sizeof(lo); + + if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &lo, &size) < 0) { + error("getsockopt: %s", strerror(errno)); + return; + } + + bacpy(&req.bdaddr, &sa.l2_bdaddr); + req.mtu = lo.omtu; + req.local = 0; + memset(&sa, 0, sizeof(sa)); + size = sizeof(sa); + + if (getsockname(sk, (struct sockaddr *) &sa, &size) < 0) { + error("getsockname: %s", strerror(errno)); + return; + } + + bacpy(&req.device, &sa.l2_bdaddr); + } else { + bacpy(&req.device, BDADDR_ANY); + bacpy(&req.bdaddr, BDADDR_LOCAL); + req.mtu = 2048; + req.local = 1; + } + + req.sock = sk; + req.buf = data; + req.len = len; + + process_request(&req); +} diff --git a/src/sdpd-server.c b/src/sdpd-server.c new file mode 100644 index 0000000..ef35309 --- /dev/null +++ b/src/sdpd-server.c @@ -0,0 +1,278 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/l2cap.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "log.h" +#include "sdpd.h" + +static guint l2cap_id = 0, unix_id = 0; +static int l2cap_sock = -1, unix_sock = -1; + +/* + * SDP server initialization on startup includes creating the + * l2cap and unix sockets over which discovery and registration clients + * access us respectively + */ +static int init_server(uint16_t mtu, int master, int compat) +{ + struct l2cap_options opts; + struct sockaddr_l2 l2addr; + struct sockaddr_un unaddr; + socklen_t optlen; + + /* Register the public browse group root */ + register_public_browse_group(); + + /* Register the SDP server's service record */ + register_server_service(); + + /* Create L2CAP socket */ + l2cap_sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (l2cap_sock < 0) { + error("opening L2CAP socket: %s", strerror(errno)); + return -1; + } + + memset(&l2addr, 0, sizeof(l2addr)); + l2addr.l2_family = AF_BLUETOOTH; + bacpy(&l2addr.l2_bdaddr, BDADDR_ANY); + l2addr.l2_psm = htobs(SDP_PSM); + + if (bind(l2cap_sock, (struct sockaddr *) &l2addr, sizeof(l2addr)) < 0) { + error("binding L2CAP socket: %s", strerror(errno)); + return -1; + } + + if (master) { + int opt = L2CAP_LM_MASTER; + if (setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt)) < 0) { + error("setsockopt: %s", strerror(errno)); + return -1; + } + } + + if (mtu > 0) { + memset(&opts, 0, sizeof(opts)); + optlen = sizeof(opts); + + if (getsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, &optlen) < 0) { + error("getsockopt: %s", strerror(errno)); + return -1; + } + + opts.omtu = mtu; + opts.imtu = mtu; + + if (setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) { + error("setsockopt: %s", strerror(errno)); + return -1; + } + } + + if (listen(l2cap_sock, 5) < 0) { + error("listen: %s", strerror(errno)); + return -1; + } + + if (!compat) { + unix_sock = -1; + return 0; + } + + /* Create local Unix socket */ + unix_sock = socket(PF_UNIX, SOCK_STREAM, 0); + if (unix_sock < 0) { + error("opening UNIX socket: %s", strerror(errno)); + return -1; + } + + memset(&unaddr, 0, sizeof(unaddr)); + unaddr.sun_family = AF_UNIX; + strcpy(unaddr.sun_path, SDP_UNIX_PATH); + + unlink(unaddr.sun_path); + + if (bind(unix_sock, (struct sockaddr *) &unaddr, sizeof(unaddr)) < 0) { + error("binding UNIX socket: %s", strerror(errno)); + return -1; + } + + if (listen(unix_sock, 5) < 0) { + error("listen UNIX socket: %s", strerror(errno)); + return -1; + } + + chmod(SDP_UNIX_PATH, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + + return 0; +} + +static gboolean io_session_event(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + sdp_pdu_hdr_t hdr; + uint8_t *buf; + int sk, len, size; + + if (cond & G_IO_NVAL) + return FALSE; + + sk = g_io_channel_unix_get_fd(chan); + + if (cond & (G_IO_HUP | G_IO_ERR)) { + sdp_svcdb_collect_all(sk); + return FALSE; + } + + len = recv(sk, &hdr, sizeof(sdp_pdu_hdr_t), MSG_PEEK); + if (len < 0 || (unsigned int) len < sizeof(sdp_pdu_hdr_t)) { + sdp_svcdb_collect_all(sk); + return FALSE; + } + + size = sizeof(sdp_pdu_hdr_t) + ntohs(hdr.plen); + buf = malloc(size); + if (!buf) + return TRUE; + + len = recv(sk, buf, size, 0); + /* Check here only that the received message is not empty. + * Incorrect length of message should be processed later + * inside handle_request() in order to produce ErrorResponse. + */ + if (len <= 0) { + sdp_svcdb_collect_all(sk); + free(buf); + return FALSE; + } + + handle_request(sk, buf, len); + + return TRUE; +} + +static gboolean io_accept_event(GIOChannel *chan, GIOCondition cond, gpointer data) +{ + GIOChannel *io; + int nsk; + + if (cond & (G_IO_HUP | G_IO_ERR | G_IO_NVAL)) + return FALSE; + + if (data == &l2cap_sock) { + struct sockaddr_l2 addr; + socklen_t len = sizeof(addr); + + nsk = accept(l2cap_sock, (struct sockaddr *) &addr, &len); + } else if (data == &unix_sock) { + struct sockaddr_un addr; + socklen_t len = sizeof(addr); + + nsk = accept(unix_sock, (struct sockaddr *) &addr, &len); + } else + return FALSE; + + if (nsk < 0) { + error("Can't accept connection: %s", strerror(errno)); + return TRUE; + } + + io = g_io_channel_unix_new(nsk); + g_io_channel_set_close_on_unref(io, TRUE); + + g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + io_session_event, data); + + g_io_channel_unref(io); + + return TRUE; +} + +int start_sdp_server(uint16_t mtu, uint32_t flags) +{ + int compat = flags & SDP_SERVER_COMPAT; + int master = flags & SDP_SERVER_MASTER; + GIOChannel *io; + + info("Starting SDP server"); + + if (init_server(mtu, master, compat) < 0) { + error("Server initialization failed"); + return -1; + } + + io = g_io_channel_unix_new(l2cap_sock); + g_io_channel_set_close_on_unref(io, TRUE); + + l2cap_id = g_io_add_watch(io, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + io_accept_event, &l2cap_sock); + g_io_channel_unref(io); + + if (compat && unix_sock > fileno(stderr)) { + io = g_io_channel_unix_new(unix_sock); + g_io_channel_set_close_on_unref(io, TRUE); + + unix_id = g_io_add_watch(io, + G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + io_accept_event, &unix_sock); + g_io_channel_unref(io); + } + + return 0; +} + +void stop_sdp_server(void) +{ + info("Stopping SDP server"); + + sdp_svcdb_reset(); + + if (unix_id > 0) + g_source_remove(unix_id); + + if (l2cap_id > 0) + g_source_remove(l2cap_id); + + l2cap_id = unix_id = 0; + l2cap_sock = unix_sock = -1; +} diff --git a/src/sdpd-service.c b/src/sdpd-service.c new file mode 100644 index 0000000..c3ee3eb --- /dev/null +++ b/src/sdpd-service.c @@ -0,0 +1,953 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "src/shared/util.h" +#include "sdpd.h" +#include "log.h" + +#define MPSD_HFP_AG_A2DP_SRC_ANSWER_CALL_DURING_AUDIO (1ULL << 0) +#define MPSD_HFP_HF_A2DP_SNK_ANSWER_CALL_DURING_AUDIO (1ULL << 1) +#define MPSD_HFP_AG_A2DP_SRC_OUTGOING_CALL_DURING_AUDIO (1ULL << 2) +#define MPSD_HFP_HF_A2DP_SNK_OUTGOING_CALL_DURING_AUDIO (1ULL << 3) +#define MPSD_HFP_AG_A2DP_SRC_REJECT_CALL_DURING_AUDIO (1ULL << 4) +#define MPSD_HFP_HF_A2DP_SNK_SRC_REJECT_CALL_DURING_AUDIO (1ULL << 5) +#define MPSD_HFP_AG_A2DP_SRC_TERMINATE_CALL_DURING_AVP (1ULL << 6) +#define MPSD_HFP_HF_A2DP_SNK_TERMINATE_CALL_DURING_AVP (1ULL << 7) +#define MPSD_HFP_AG_A2DP_SRC_PRESS_PLAY_DURING_ACTIVE_CALL (1ULL << 8) +#define MPSD_HFP_HF_A2DP_SNK_PRESS_PLAY_DURING_ACTIVE_CALL (1ULL << 9) +#define MPSD_HFP_AG_A2DP_SRC_START_AUDIO_STREAM_AFTER_PLAY (1ULL << 10) +#define MPSD_HFP_HF_A2DP_SNK_START_AUDIO_STREAM_AFTER_PLAY (1ULL << 11) +#define MPSD_HFP_AG_A2DP_SRC_SUSPEND_AUDIO_STREAM_ON_PAUSE (1ULL << 12) +#define MPSD_HFP_HF_A2DP_SNK_SUSPEND_AUDIO_STREAM_ON_PAUSE (1ULL << 13) +#define MPSD_HFP_AG_DUN_GW_DATA_COMM_DURING_VOICE_CALL (1ULL << 14) +#define MPSD_HFP_HF_DUN_DT_DATA_COMM_DURING_VOICE_CALL (1ULL << 15) +#define MPSD_HFP_AG_DUN_GW_OUTGOING_CALL_DURING_DATA_COMM (1ULL << 16) +#define MPSD_HFP_HF_DUN_DT_OUTGOING_CALL_DURING_DATA_COMM (1ULL << 17) +#define MPSD_HFP_AG_DUN_GW_INCOMING_CALL_DURING_DATA_COMM (1ULL << 18) +#define MPSD_HFP_HF_DUN_DT_INCOMING_CALL_DURING_DATA_COMM (1ULL << 19) +#define MPSD_A2DP_SRC_DUN_GW_START_AUDIO_DURING_DATA_COMM (1ULL << 20) +#define MPSD_A2DP_SNK_DUN_DT_START_AUDIO_DURING_DATA_COMM (1ULL << 21) +#define MPSD_A2DP_SRC_DUN_GW_DATA_COMM_DURING_AUDIO_STREAM (1ULL << 22) +#define MPSD_A2DP_SNK_DUN_DT_DATA_COMM_DURING_AUDIO_STREAM (1ULL << 23) +#define MPSD_HFP_AG_DUN_GW_TERMINATE_CALL_DURING_DATA_COMM (1ULL << 24) +#define MPSD_HFP_HF_DUN_DT_TERMINATE_CALL_DURING_DATA_COMM (1ULL << 25) +#define MPSD_HFP_AG_PAN_NAP_DATA_COMM_DURING_VOICE_CALL (1ULL << 26) +#define MPSD_HFP_HF_PAN_PANU_DATA_COMM_DURING_VOICE_CALL (1ULL << 27) +#define MPSD_HFP_AG_PAN_NAP_OUTGOING_CALL_DURING_DATA_COMM (1ULL << 28) +#define MPSD_HFP_HF_PAN_PANU_OUTGOING_CALL_DURING_DATA_COMM (1ULL << 29) +#define MPSD_HFP_AG_PAN_NAP_INCOMING_CALL_DURING_DATA_COMM (1ULL << 30) +#define MPSD_HFP_HF_PAN_PANU_INCOMING_CALL_DURING_DATA_COMM (1ULL << 31) +#define MPSD_A2DP_SRC_PAN_NAP_START_AUDIO_DURING_DATA_COMM (1ULL << 32) +#define MPSD_A2DP_SNK_PAN_PANU_START_AUDIO_DURING_DATA_COMM (1ULL << 33) +#define MPSD_A2DP_SRC_PAN_NAP_DATA_COMM_DURING_AUDIO_STREAM (1ULL << 34) +#define MPSD_A2DP_SNK_PAN_PANU_DATA_COMM_DURING_AUDIO_STREAM (1ULL << 35) +#define MPSD_A2DP_SRC_PBAP_SRV_PB_DL_DURING_AUDIO_STREAM (1ULL << 36) +#define MPSD_A2DP_SNK_PBAP_CLI_PB_DL_DURING_AUDIO_STREAM (1ULL << 37) + +#define MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_ANSWER_CALL_DURING_AUDIO (1ULL << 0) +#define MPMD_A2DP_SRC_AVRCP_TG_ANSWER_CALL_DURING_AUDIO (1ULL << 1) +#define MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_OUTGOING_CALL_DURING_AUDIO (1ULL << 2) +#define MPMD_A2DP_SRC_AVRCP_TG_OUTGOING_CALL_DURING_AUDIO (1ULL << 3) +#define MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_REJECT_CALL_DURING_AUDIO (1ULL << 4) +#define MPMD_A2DP_SRC_AVRCP_TG_REJECT_CALL_DURING_AUDIO (1ULL << 5) +#define MPMD_HFP_AG_CALL_TERMINATION_DURING_AVP (1ULL << 6) +#define MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_TERMINATION_DURING_AVP (1ULL << 7) +#define MPMD_A2DP_SRC_AVRCP_TG_TERMINATION_DURING_AVP (1ULL << 8) +#define MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_PLAY_DURING_CALL (1ULL << 9) +#define MPMD_A2DP_SRC_AVRCP_TG_PRESS_PLAY_DURING_CALL (1ULL << 10) +#define MPMD_AVRCP_CT_NO_A2DP_SNK_START_AUDIO_AFTER_PLAY (1ULL << 11) +#define MPMD_A2DP_SRC_AVRCP_TG_START_AUDIO_AFTER_PLAY (1ULL << 12) +#define MPMD_AVRCP_CT_NO_A2DP_SNK_SUSPEND_AUDIO_AFTER_PAUSE (1ULL << 13) +#define MPMD_A2DP_SRC_AVRCP_TG_SUSPEND_AUDIO_AFTER_PAUSE (1ULL << 14) +#define MPMD_A2DP_SRC_AVRCP_TG_START_AUDIO_DURING_DATA_COMM (1ULL << 15) +#define MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_AUDIO_DURING_DATA_COMM (1ULL << 16) +#define MPMD_A2DP_SRC_AVRCP_TG_START_DATA_DURING_AUDIO (1ULL << 17) +#define MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_START_DATA_DURING_AUDIO (1ULL << 18) + +/* Note: in spec dependency bit position starts from 1 (bit 0 unused?) */ +#define MPS_DEPS_SNIFF_MODE_DURRING_STREAMING (1ULL << 1) +#define MPS_DEPS_GAVDP_REQUIREMENTS (1ULL << 2) +#define MPS_DEPS_DIS_CONNECTION_ORDER_BEHAVIOR (1ULL << 3) + +/* + * default MPS features are all disabled, will be updated if relevant service + * is (un)registered + */ +#define MPS_MPSD_DEFAULT_FEATURES 0 +#define MPS_MPMD_DEFAULT_FEATURES 0 + +/* + * Those defines bits for all features that depend on specific profile and role. + * If profile is not supported then all those bits should not be set in record + */ +#define MPS_MPSD_HFP_AG (MPSD_HFP_AG_A2DP_SRC_ANSWER_CALL_DURING_AUDIO | \ + MPSD_HFP_AG_A2DP_SRC_OUTGOING_CALL_DURING_AUDIO | \ + MPSD_HFP_AG_A2DP_SRC_REJECT_CALL_DURING_AUDIO | \ + MPSD_HFP_AG_A2DP_SRC_TERMINATE_CALL_DURING_AVP | \ + MPSD_HFP_AG_A2DP_SRC_PRESS_PLAY_DURING_ACTIVE_CALL | \ + MPSD_HFP_AG_A2DP_SRC_START_AUDIO_STREAM_AFTER_PLAY | \ + MPSD_HFP_AG_DUN_GW_DATA_COMM_DURING_VOICE_CALL | \ + MPSD_HFP_AG_A2DP_SRC_SUSPEND_AUDIO_STREAM_ON_PAUSE | \ + MPSD_HFP_AG_DUN_GW_OUTGOING_CALL_DURING_DATA_COMM | \ + MPSD_HFP_AG_DUN_GW_INCOMING_CALL_DURING_DATA_COMM | \ + MPSD_HFP_AG_DUN_GW_TERMINATE_CALL_DURING_DATA_COMM | \ + MPSD_HFP_AG_PAN_NAP_DATA_COMM_DURING_VOICE_CALL | \ + MPSD_HFP_AG_PAN_NAP_OUTGOING_CALL_DURING_DATA_COMM | \ + MPSD_HFP_AG_PAN_NAP_INCOMING_CALL_DURING_DATA_COMM) + +#define MPS_MPSD_HFP_HF (MPSD_HFP_HF_A2DP_SNK_ANSWER_CALL_DURING_AUDIO | \ + MPSD_HFP_HF_A2DP_SNK_OUTGOING_CALL_DURING_AUDIO | \ + MPSD_HFP_HF_A2DP_SNK_SRC_REJECT_CALL_DURING_AUDIO | \ + MPSD_HFP_HF_A2DP_SNK_TERMINATE_CALL_DURING_AVP | \ + MPSD_HFP_HF_A2DP_SNK_PRESS_PLAY_DURING_ACTIVE_CALL | \ + MPSD_HFP_HF_A2DP_SNK_START_AUDIO_STREAM_AFTER_PLAY | \ + MPSD_HFP_HF_A2DP_SNK_SUSPEND_AUDIO_STREAM_ON_PAUSE | \ + MPSD_HFP_HF_DUN_DT_DATA_COMM_DURING_VOICE_CALL | \ + MPSD_HFP_HF_DUN_DT_OUTGOING_CALL_DURING_DATA_COMM | \ + MPSD_HFP_HF_DUN_DT_INCOMING_CALL_DURING_DATA_COMM | \ + MPSD_HFP_HF_DUN_DT_TERMINATE_CALL_DURING_DATA_COMM | \ + MPSD_HFP_HF_PAN_PANU_DATA_COMM_DURING_VOICE_CALL | \ + MPSD_HFP_HF_PAN_PANU_OUTGOING_CALL_DURING_DATA_COMM | \ + MPSD_HFP_HF_PAN_PANU_INCOMING_CALL_DURING_DATA_COMM) + +#define MPS_MPSD_A2DP_SRC (MPSD_HFP_AG_A2DP_SRC_ANSWER_CALL_DURING_AUDIO | \ + MPSD_HFP_AG_A2DP_SRC_OUTGOING_CALL_DURING_AUDIO | \ + MPSD_HFP_AG_A2DP_SRC_REJECT_CALL_DURING_AUDIO | \ + MPSD_HFP_AG_A2DP_SRC_TERMINATE_CALL_DURING_AVP | \ + MPSD_HFP_AG_A2DP_SRC_PRESS_PLAY_DURING_ACTIVE_CALL | \ + MPSD_HFP_AG_A2DP_SRC_START_AUDIO_STREAM_AFTER_PLAY | \ + MPSD_HFP_AG_A2DP_SRC_SUSPEND_AUDIO_STREAM_ON_PAUSE | \ + MPSD_A2DP_SRC_DUN_GW_START_AUDIO_DURING_DATA_COMM | \ + MPSD_A2DP_SRC_DUN_GW_DATA_COMM_DURING_AUDIO_STREAM | \ + MPSD_A2DP_SRC_PAN_NAP_START_AUDIO_DURING_DATA_COMM | \ + MPSD_A2DP_SRC_PAN_NAP_DATA_COMM_DURING_AUDIO_STREAM | \ + MPSD_A2DP_SRC_PBAP_SRV_PB_DL_DURING_AUDIO_STREAM) + +#define MPS_MPSD_A2DP_SNK (MPSD_HFP_HF_A2DP_SNK_ANSWER_CALL_DURING_AUDIO | \ + MPSD_HFP_HF_A2DP_SNK_OUTGOING_CALL_DURING_AUDIO | \ + MPSD_HFP_HF_A2DP_SNK_SRC_REJECT_CALL_DURING_AUDIO | \ + MPSD_HFP_HF_A2DP_SNK_TERMINATE_CALL_DURING_AVP | \ + MPSD_HFP_HF_A2DP_SNK_PRESS_PLAY_DURING_ACTIVE_CALL | \ + MPSD_HFP_HF_A2DP_SNK_START_AUDIO_STREAM_AFTER_PLAY | \ + MPSD_HFP_HF_A2DP_SNK_SUSPEND_AUDIO_STREAM_ON_PAUSE | \ + MPSD_A2DP_SNK_DUN_DT_START_AUDIO_DURING_DATA_COMM | \ + MPSD_A2DP_SNK_DUN_DT_DATA_COMM_DURING_AUDIO_STREAM | \ + MPSD_A2DP_SNK_PAN_PANU_START_AUDIO_DURING_DATA_COMM | \ + MPSD_A2DP_SNK_PAN_PANU_DATA_COMM_DURING_AUDIO_STREAM | \ + MPSD_A2DP_SNK_PBAP_CLI_PB_DL_DURING_AUDIO_STREAM) + +#define MPS_MPSD_AVRCP_CT MPS_MPSD_A2DP_SNK + +#define MPS_MPSD_AVRCP_TG MPS_MPSD_A2DP_SRC + +#define MPS_MPSD_DUN_GW (MPSD_HFP_AG_DUN_GW_DATA_COMM_DURING_VOICE_CALL | \ + MPSD_HFP_AG_DUN_GW_OUTGOING_CALL_DURING_DATA_COMM | \ + MPSD_HFP_AG_DUN_GW_INCOMING_CALL_DURING_DATA_COMM | \ + MPSD_A2DP_SRC_DUN_GW_START_AUDIO_DURING_DATA_COMM | \ + MPSD_A2DP_SRC_DUN_GW_DATA_COMM_DURING_AUDIO_STREAM | \ + MPSD_HFP_AG_DUN_GW_TERMINATE_CALL_DURING_DATA_COMM) + +#define MPS_MPSD_DUN_DT (MPSD_HFP_HF_DUN_DT_DATA_COMM_DURING_VOICE_CALL | \ + MPSD_HFP_HF_DUN_DT_OUTGOING_CALL_DURING_DATA_COMM | \ + MPSD_HFP_HF_DUN_DT_INCOMING_CALL_DURING_DATA_COMM | \ + MPSD_A2DP_SNK_DUN_DT_START_AUDIO_DURING_DATA_COMM | \ + MPSD_A2DP_SNK_DUN_DT_DATA_COMM_DURING_AUDIO_STREAM | \ + MPSD_HFP_HF_DUN_DT_TERMINATE_CALL_DURING_DATA_COMM) + +#define MPS_MPSD_PAN_NAP (MPSD_HFP_AG_PAN_NAP_DATA_COMM_DURING_VOICE_CALL | \ + MPSD_HFP_AG_PAN_NAP_OUTGOING_CALL_DURING_DATA_COMM | \ + MPSD_HFP_AG_PAN_NAP_INCOMING_CALL_DURING_DATA_COMM | \ + MPSD_A2DP_SRC_PAN_NAP_START_AUDIO_DURING_DATA_COMM | \ + MPSD_A2DP_SRC_PAN_NAP_DATA_COMM_DURING_AUDIO_STREAM) + +#define MPS_MPSD_PAN_PANU (MPSD_HFP_HF_PAN_PANU_DATA_COMM_DURING_VOICE_CALL | \ + MPSD_HFP_HF_PAN_PANU_OUTGOING_CALL_DURING_DATA_COMM | \ + MPSD_HFP_HF_PAN_PANU_INCOMING_CALL_DURING_DATA_COMM | \ + MPSD_A2DP_SNK_PAN_PANU_START_AUDIO_DURING_DATA_COMM | \ + MPSD_A2DP_SNK_PAN_PANU_DATA_COMM_DURING_AUDIO_STREAM) + +#define MPS_MPSD_PBAP_SRC MPSD_A2DP_SRC_PBAP_SRV_PB_DL_DURING_AUDIO_STREAM + +#define MPS_MPSD_PBAP_CLI MPSD_A2DP_SNK_PBAP_CLI_PB_DL_DURING_AUDIO_STREAM + +#define MPS_MPSD_ALL (MPS_MPSD_HFP_AG | MPS_MPSD_HFP_HF | \ + MPS_MPSD_A2DP_SRC | MPS_MPSD_A2DP_SNK | \ + MPS_MPSD_AVRCP_CT | MPS_MPSD_AVRCP_TG | \ + MPS_MPSD_DUN_GW | MPS_MPSD_DUN_DT | \ + MPS_MPSD_PAN_NAP | MPS_MPSD_PAN_PANU | \ + MPS_MPSD_PBAP_SRC | MPS_MPSD_PBAP_CLI) + +#define MPS_MPMD_HFP_AG MPMD_HFP_AG_CALL_TERMINATION_DURING_AVP + +#define MPS_MPMD_HFP_HF ( \ + MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_ANSWER_CALL_DURING_AUDIO | \ + MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_OUTGOING_CALL_DURING_AUDIO | \ + MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_REJECT_CALL_DURING_AUDIO | \ + MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_TERMINATION_DURING_AVP | \ + MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_PLAY_DURING_CALL) + +#define MPS_MPMD_A2DP_SRC (MPMD_A2DP_SRC_AVRCP_TG_ANSWER_CALL_DURING_AUDIO | \ + MPMD_A2DP_SRC_AVRCP_TG_OUTGOING_CALL_DURING_AUDIO | \ + MPMD_A2DP_SRC_AVRCP_TG_REJECT_CALL_DURING_AUDIO | \ + MPMD_A2DP_SRC_AVRCP_TG_TERMINATION_DURING_AVP | \ + MPMD_A2DP_SRC_AVRCP_TG_PRESS_PLAY_DURING_CALL | \ + MPMD_A2DP_SRC_AVRCP_TG_START_AUDIO_AFTER_PLAY | \ + MPMD_A2DP_SRC_AVRCP_TG_SUSPEND_AUDIO_AFTER_PAUSE | \ + MPMD_A2DP_SRC_AVRCP_TG_START_AUDIO_DURING_DATA_COMM | \ + MPMD_A2DP_SRC_AVRCP_TG_START_DATA_DURING_AUDIO) + +#define MPS_MPMD_A2DP_SNK ( \ + MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_ANSWER_CALL_DURING_AUDIO | \ + MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_OUTGOING_CALL_DURING_AUDIO | \ + MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_REJECT_CALL_DURING_AUDIO | \ + MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_TERMINATION_DURING_AVP | \ + MPMD_HFP_HF_A2DP_SNK_AVRCP_CT_PLAY_DURING_CALL | \ + MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_AUDIO_DURING_DATA_COMM | \ + MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_START_DATA_DURING_AUDIO) + +#define MPS_MPMD_AVRCP_CT MPS_MPMD_A2DP_SNK + +/* should be set only if CT is supported but SNK is not supported */ +#define MPS_MPMD_AVRCP_CT_ONLY ( \ + MPMD_AVRCP_CT_NO_A2DP_SNK_START_AUDIO_AFTER_PLAY | \ + MPMD_AVRCP_CT_NO_A2DP_SNK_SUSPEND_AUDIO_AFTER_PAUSE) + +#define MPS_MPMD_AVRCP_TG MPS_MPMD_A2DP_SRC + +#define MPS_MPMD_DUN_DT ( \ + MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_AUDIO_DURING_DATA_COMM | \ + MPMD_A2DP_SNK_AVRCP_CT_DUN_DT_START_DATA_DURING_AUDIO) + +#define MPS_MPMD_ALL (MPS_MPMD_HFP_AG | MPS_MPMD_HFP_HF | MPS_MPMD_A2DP_SRC | \ + MPS_MPMD_A2DP_SNK | MPS_MPMD_AVRCP_CT | \ + MPS_MPMD_AVRCP_CT_ONLY | MPS_MPMD_AVRCP_TG | \ + MPS_MPMD_DUN_DT) + +/* Assume all dependencies are supported */ +#define MPS_DEFAULT_DEPS (MPS_DEPS_SNIFF_MODE_DURRING_STREAMING | \ + MPS_DEPS_GAVDP_REQUIREMENTS | \ + MPS_DEPS_DIS_CONNECTION_ORDER_BEHAVIOR) + +static sdp_record_t *server = NULL; +static uint32_t fixed_dbts = 0; + +/* + * List of version numbers supported by the SDP server. + * Add to this list when newer versions are supported. + */ +static sdp_version_t sdpVnumArray[1] = { + { 1, 0 } +}; +static const int sdpServerVnumEntries = 1; + +static uint32_t mps_handle = 0; +static bool mps_mpmd = false; + +/* + * A simple function which returns the time of day in + * seconds. Used for updating the service db state + * attribute of the service record of the SDP server + */ +uint32_t sdp_get_time(void) +{ + /* + * To handle failure in gettimeofday, so an old + * value is returned and service does not fail + */ + static struct timeval tm; + + gettimeofday(&tm, NULL); + return (uint32_t) tm.tv_sec; +} + +/* + * The service database state is an attribute of the service record + * of the SDP server itself. This attribute is guaranteed to + * change if any of the contents of the service repository + * changes. This function updates the timestamp of value of + * the svcDBState attribute + * Set the SDP server DB. Simply a timestamp which is the marker + * when the DB was modified. + */ +static void update_db_timestamp(void) +{ + if (fixed_dbts) { + sdp_data_t *d = sdp_data_alloc(SDP_UINT32, &fixed_dbts); + sdp_attr_replace(server, SDP_ATTR_SVCDB_STATE, d); + } else { + uint32_t dbts = sdp_get_time(); + sdp_data_t *d = sdp_data_alloc(SDP_UINT32, &dbts); + sdp_attr_replace(server, SDP_ATTR_SVCDB_STATE, d); + } +} + +void set_fixed_db_timestamp(uint32_t dbts) +{ + fixed_dbts = dbts; +} + +void register_public_browse_group(void) +{ + sdp_list_t *browselist; + uuid_t bgscid, pbgid; + sdp_data_t *sdpdata; + sdp_record_t *browse = sdp_record_alloc(); + + browse->handle = SDP_SERVER_RECORD_HANDLE + 1; + + sdp_record_add(BDADDR_ANY, browse); + sdpdata = sdp_data_alloc(SDP_UINT32, &browse->handle); + sdp_attr_add(browse, SDP_ATTR_RECORD_HANDLE, sdpdata); + + sdp_uuid16_create(&bgscid, BROWSE_GRP_DESC_SVCLASS_ID); + browselist = sdp_list_append(0, &bgscid); + sdp_set_service_classes(browse, browselist); + sdp_list_free(browselist, 0); + + sdp_uuid16_create(&pbgid, PUBLIC_BROWSE_GROUP); + sdp_attr_add_new(browse, SDP_ATTR_GROUP_ID, + SDP_UUID16, &pbgid.value.uuid16); +} + +/* + * The SDP server must present its own service record to + * the service repository. This can be accessed by service + * discovery clients. This method constructs a service record + * and stores it in the repository + */ +void register_server_service(void) +{ + sdp_list_t *classIDList; + uuid_t classID; + void **versions, **versionDTDs; + uint8_t dtd; + sdp_data_t *pData; + int i; + + server = sdp_record_alloc(); + server->pattern = NULL; + + /* Force the record to be SDP_SERVER_RECORD_HANDLE */ + server->handle = SDP_SERVER_RECORD_HANDLE; + + sdp_record_add(BDADDR_ANY, server); + sdp_attr_add(server, SDP_ATTR_RECORD_HANDLE, + sdp_data_alloc(SDP_UINT32, &server->handle)); + + sdp_uuid16_create(&classID, SDP_SERVER_SVCLASS_ID); + classIDList = sdp_list_append(0, &classID); + sdp_set_service_classes(server, classIDList); + sdp_list_free(classIDList, 0); + + /* + * Set the version numbers supported, these are passed as arguments + * to the server on command line. Now defaults to 1.0 + * Build the version number sequence first + */ + versions = malloc(sdpServerVnumEntries * sizeof(void *)); + versionDTDs = malloc(sdpServerVnumEntries * sizeof(void *)); + dtd = SDP_UINT16; + for (i = 0; i < sdpServerVnumEntries; i++) { + uint16_t *version = malloc(sizeof(uint16_t)); + *version = sdpVnumArray[i].major; + *version = (*version << 8); + *version |= sdpVnumArray[i].minor; + versions[i] = version; + versionDTDs[i] = &dtd; + } + pData = sdp_seq_alloc(versionDTDs, versions, sdpServerVnumEntries); + for (i = 0; i < sdpServerVnumEntries; i++) + free(versions[i]); + free(versions); + free(versionDTDs); + sdp_attr_add(server, SDP_ATTR_VERSION_NUM_LIST, pData); + + update_db_timestamp(); +} + +void register_device_id(uint16_t source, uint16_t vendor, + uint16_t product, uint16_t version) +{ + const uint16_t spec = 0x0103; + const uint8_t primary = 1; + sdp_list_t *class_list, *group_list, *profile_list; + uuid_t class_uuid, group_uuid; + sdp_data_t *sdp_data, *primary_data, *source_data; + sdp_data_t *spec_data, *vendor_data, *product_data, *version_data; + sdp_profile_desc_t profile; + sdp_record_t *record = sdp_record_alloc(); + + DBG("Adding device id record for %04x:%04x:%04x:%04x", + source, vendor, product, version); + + record->handle = sdp_next_handle(); + + sdp_record_add(BDADDR_ANY, record); + sdp_data = sdp_data_alloc(SDP_UINT32, &record->handle); + sdp_attr_add(record, SDP_ATTR_RECORD_HANDLE, sdp_data); + + sdp_uuid16_create(&class_uuid, PNP_INFO_SVCLASS_ID); + class_list = sdp_list_append(0, &class_uuid); + sdp_set_service_classes(record, class_list); + sdp_list_free(class_list, NULL); + + sdp_uuid16_create(&group_uuid, PUBLIC_BROWSE_GROUP); + group_list = sdp_list_append(NULL, &group_uuid); + sdp_set_browse_groups(record, group_list); + sdp_list_free(group_list, NULL); + + sdp_uuid16_create(&profile.uuid, PNP_INFO_PROFILE_ID); + profile.version = spec; + profile_list = sdp_list_append(NULL, &profile); + sdp_set_profile_descs(record, profile_list); + sdp_list_free(profile_list, NULL); + + spec_data = sdp_data_alloc(SDP_UINT16, &spec); + sdp_attr_add(record, 0x0200, spec_data); + + vendor_data = sdp_data_alloc(SDP_UINT16, &vendor); + sdp_attr_add(record, 0x0201, vendor_data); + + product_data = sdp_data_alloc(SDP_UINT16, &product); + sdp_attr_add(record, 0x0202, product_data); + + version_data = sdp_data_alloc(SDP_UINT16, &version); + sdp_attr_add(record, 0x0203, version_data); + + primary_data = sdp_data_alloc(SDP_BOOL, &primary); + sdp_attr_add(record, 0x0204, primary_data); + + source_data = sdp_data_alloc(SDP_UINT16, &source); + sdp_attr_add(record, 0x0205, source_data); + + update_db_timestamp(); +} + +static bool class_supported(uint16_t class) +{ + sdp_list_t *list; + uuid_t uuid; + + sdp_uuid16_create(&uuid, class); + + for (list = sdp_get_record_list(); list; list = list->next) { + sdp_record_t *rec = list->data; + + if (sdp_uuid_cmp(&rec->svclass, &uuid) == 0) + return true; + } + + return false; +} + +static uint64_t mps_mpsd_features(void) +{ + uint64_t feat = MPS_MPSD_ALL; + + if (!class_supported(HANDSFREE_AGW_SVCLASS_ID)) + feat &= ~MPS_MPSD_HFP_AG; + + if (!class_supported(HANDSFREE_SVCLASS_ID)) + feat &= ~MPS_MPSD_HFP_HF; + + if (!class_supported(AUDIO_SOURCE_SVCLASS_ID)) + feat &= ~MPS_MPSD_A2DP_SRC; + + if (!class_supported(AUDIO_SINK_SVCLASS_ID)) + feat &= ~MPS_MPSD_A2DP_SNK; + + if (!class_supported(AV_REMOTE_CONTROLLER_SVCLASS_ID)) + feat &= ~MPS_MPSD_AVRCP_CT; + + if (!class_supported(AV_REMOTE_TARGET_SVCLASS_ID)) + feat &= ~MPS_MPSD_AVRCP_TG; + + if (!class_supported(DIALUP_NET_SVCLASS_ID)) + feat &= ~MPS_MPSD_DUN_GW; + + /* TODO */ + feat &= ~MPS_MPSD_DUN_DT; + + if (!class_supported(NAP_SVCLASS_ID)) + feat &= ~MPS_MPSD_PAN_NAP; + + if (!class_supported(PANU_SVCLASS_ID)) + feat &= ~MPS_MPSD_PAN_PANU; + + if (!class_supported(PBAP_PSE_SVCLASS_ID)) + feat &= ~MPS_MPSD_PBAP_SRC; + + if (!class_supported(PBAP_PCE_SVCLASS_ID)) + feat &= ~MPS_MPSD_PBAP_CLI; + + return feat; +} + +static uint64_t mps_mpmd_features(void) +{ + uint64_t feat = MPS_MPMD_ALL; + + if (!class_supported(HANDSFREE_AGW_SVCLASS_ID)) + feat &= ~MPS_MPMD_HFP_AG; + + if (!class_supported(HANDSFREE_SVCLASS_ID)) + feat &= ~MPS_MPMD_HFP_HF; + + if (!class_supported(AUDIO_SOURCE_SVCLASS_ID)) + feat &= ~MPS_MPMD_A2DP_SRC; + + if (!class_supported(AUDIO_SINK_SVCLASS_ID)) + feat &= ~MPS_MPMD_A2DP_SNK; + else + feat &= ~MPS_MPMD_AVRCP_CT_ONLY; + + if (!class_supported(AV_REMOTE_CONTROLLER_SVCLASS_ID)) { + feat &= ~MPS_MPMD_AVRCP_CT; + feat &= ~MPS_MPMD_AVRCP_CT_ONLY; + } + + if (!class_supported(AV_REMOTE_TARGET_SVCLASS_ID)) + feat &= ~MPS_MPMD_AVRCP_TG; + + /* TODO */ + feat &= ~MPS_MPMD_DUN_DT; + + return feat; +} + +static sdp_record_t *mps_record(int mpmd) +{ + sdp_data_t *mpsd_features, *mpmd_features, *dependencies; + sdp_list_t *svclass_id, *pfseq, *root; + uuid_t root_uuid, svclass_uuid; + sdp_profile_desc_t profile; + sdp_record_t *record; + uint64_t mpsd_feat = MPS_MPSD_DEFAULT_FEATURES; + uint64_t mpmd_feat = MPS_MPMD_DEFAULT_FEATURES; + uint16_t deps = MPS_DEFAULT_DEPS; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(NULL, &root_uuid); + sdp_set_browse_groups(record, root); + sdp_list_free(root, NULL); + + sdp_uuid16_create(&svclass_uuid, MPS_SVCLASS_ID); + svclass_id = sdp_list_append(NULL, &svclass_uuid); + sdp_set_service_classes(record, svclass_id); + sdp_list_free(svclass_id, NULL); + + sdp_uuid16_create(&profile.uuid, MPS_PROFILE_ID); + profile.version = 0x0100; + pfseq = sdp_list_append(NULL, &profile); + sdp_set_profile_descs(record, pfseq); + sdp_list_free(pfseq, NULL); + + mpsd_features = sdp_data_alloc(SDP_UINT64, &mpsd_feat); + sdp_attr_add(record, SDP_ATTR_MPSD_SCENARIOS, mpsd_features); + + if (mpmd) { + mpmd_features = sdp_data_alloc(SDP_UINT64, &mpmd_feat); + sdp_attr_add(record, SDP_ATTR_MPMD_SCENARIOS, mpmd_features); + } + + dependencies = sdp_data_alloc(SDP_UINT16, &deps); + sdp_attr_add(record, SDP_ATTR_MPS_DEPENDENCIES, dependencies); + + sdp_set_info_attr(record, "Multi Profile", 0, 0); + + return record; +} + +void register_mps(bool mpmd) +{ + sdp_record_t *record; + + record = mps_record(mpmd); + if (!record) + return; + + if (add_record_to_server(BDADDR_ANY, record) < 0) { + sdp_record_free(record); + return; + } + + mps_handle = record->handle; + mps_mpmd = mpmd; +} + +static void update_mps(void) +{ + sdp_record_t *rec; + sdp_data_t *data; + uint64_t mpsd_feat, mpmd_feat; + + if (!mps_handle) + return; + + rec = sdp_record_find(mps_handle); + if (!rec) + return; + + mpsd_feat = mps_mpsd_features(); + data = sdp_data_alloc(SDP_UINT64, &mpsd_feat); + sdp_attr_replace(rec, SDP_ATTR_MPSD_SCENARIOS, data); + + if (mps_mpmd) { + mpmd_feat = mps_mpmd_features(); + data = sdp_data_alloc(SDP_UINT64, &mpmd_feat); + sdp_attr_replace(rec, SDP_ATTR_MPMD_SCENARIOS, data); + } +} + +int add_record_to_server(const bdaddr_t *src, sdp_record_t *rec) +{ + sdp_data_t *data; + sdp_list_t *pattern; + + if (rec->handle == 0xffffffff) { + rec->handle = sdp_next_handle(); + if (rec->handle < 0x10000) + return -ENOSPC; + } else { + if (sdp_record_find(rec->handle)) + return -EEXIST; + } + + DBG("Adding record with handle 0x%05x", rec->handle); + + sdp_record_add(src, rec); + + data = sdp_data_alloc(SDP_UINT32, &rec->handle); + sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, data); + + if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) { + uuid_t uuid; + sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); + sdp_pattern_add_uuid(rec, &uuid); + } + + for (pattern = rec->pattern; pattern; pattern = pattern->next) { + char uuid[32]; + + if (pattern->data == NULL) + continue; + + sdp_uuid2strn((uuid_t *) pattern->data, uuid, sizeof(uuid)); + DBG("Record pattern UUID %s", uuid); + } + + update_mps(); + update_db_timestamp(); + + return 0; +} + +int remove_record_from_server(uint32_t handle) +{ + sdp_record_t *rec; + + /* Refuse to remove the server's own record */ + if (handle == SDP_SERVER_RECORD_HANDLE) + return -EINVAL; + + DBG("Removing record with handle 0x%05x", handle); + + rec = sdp_record_find(handle); + if (!rec) + return -ENOENT; + + if (sdp_record_remove(handle) == 0) { + update_mps(); + update_db_timestamp(); + } + + sdp_record_free(rec); + + return 0; +} + +/* FIXME: refactor for server-side */ +static sdp_record_t *extract_pdu_server(bdaddr_t *device, uint8_t *p, + unsigned int bufsize, + uint32_t handleExpected, int *scanned) +{ + int extractStatus = -1, localExtractedLength = 0; + uint8_t dtd; + int seqlen = 0; + sdp_record_t *rec = NULL; + uint16_t attrId, lookAheadAttrId; + sdp_data_t *pAttr = NULL; + uint32_t handle = 0xffffffff; + + *scanned = sdp_extract_seqtype(p, bufsize, &dtd, &seqlen); + p += *scanned; + bufsize -= *scanned; + + if (bufsize < sizeof(uint8_t) + sizeof(uint8_t)) { + SDPDBG("Unexpected end of packet"); + return NULL; + } + + lookAheadAttrId = get_be16(p + sizeof(uint8_t)); + + SDPDBG("Look ahead attr id : %d", lookAheadAttrId); + + if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) { + if (bufsize < (sizeof(uint8_t) * 2) + + sizeof(uint16_t) + sizeof(uint32_t)) { + SDPDBG("Unexpected end of packet"); + return NULL; + } + handle = get_be32(p + sizeof(uint8_t) + sizeof(uint16_t) + + sizeof(uint8_t)); + SDPDBG("SvcRecHandle : 0x%x", handle); + rec = sdp_record_find(handle); + } else if (handleExpected != 0xffffffff) + rec = sdp_record_find(handleExpected); + + if (!rec) { + rec = sdp_record_alloc(); + rec->attrlist = NULL; + if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) { + rec->handle = handle; + sdp_record_add(device, rec); + } else if (handleExpected != 0xffffffff) { + rec->handle = handleExpected; + sdp_record_add(device, rec); + } + } else { + sdp_list_free(rec->attrlist, (sdp_free_func_t) sdp_data_free); + rec->attrlist = NULL; + } + + while (localExtractedLength < seqlen) { + int attrSize = sizeof(uint8_t); + int attrValueLength = 0; + + if (bufsize < attrSize + sizeof(uint16_t)) { + SDPDBG("Unexpected end of packet: Terminating extraction of attributes"); + break; + } + + SDPDBG("Extract PDU, sequenceLength: %d localExtractedLength: %d", + seqlen, localExtractedLength); + dtd = *(uint8_t *) p; + + attrId = get_be16(p + attrSize); + attrSize += sizeof(uint16_t); + + SDPDBG("DTD of attrId : %d Attr id : 0x%x", dtd, attrId); + + pAttr = sdp_extract_attr(p + attrSize, bufsize - attrSize, + &attrValueLength, rec); + + SDPDBG("Attr id : 0x%x attrValueLength : %d", attrId, attrValueLength); + + attrSize += attrValueLength; + if (pAttr == NULL) { + SDPDBG("Terminating extraction of attributes"); + break; + } + localExtractedLength += attrSize; + p += attrSize; + bufsize -= attrSize; + sdp_attr_replace(rec, attrId, pAttr); + extractStatus = 0; + SDPDBG("Extract PDU, seqLength: %d localExtractedLength: %d", + seqlen, localExtractedLength); + } + + if (extractStatus == 0) { + SDPDBG("Successful extracting of Svc Rec attributes"); +#ifdef SDP_DEBUG + sdp_print_service_attr(rec->attrlist); +#endif + *scanned += seqlen; + } + return rec; +} + +/* + * Add the newly created service record to the service repository + */ +int service_register_req(sdp_req_t *req, sdp_buf_t *rsp) +{ + int scanned = 0; + sdp_data_t *handle; + uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t); + int bufsize = req->len - sizeof(sdp_pdu_hdr_t); + sdp_record_t *rec; + + req->flags = *p++; + if (req->flags & SDP_DEVICE_RECORD) { + bacpy(&req->device, (bdaddr_t *) p); + p += sizeof(bdaddr_t); + bufsize -= sizeof(bdaddr_t); + } + + /* save image of PDU: we need it when clients request this attribute */ + rec = extract_pdu_server(&req->device, p, bufsize, 0xffffffff, &scanned); + if (!rec) + goto invalid; + + if (rec->handle == 0xffffffff) { + rec->handle = sdp_next_handle(); + if (rec->handle < 0x10000) { + sdp_record_free(rec); + goto invalid; + } + } else { + if (sdp_record_find(rec->handle)) { + /* extract_pdu_server will add the record handle + * if it is missing. So instead of failing, skip + * the record adding to avoid duplication. */ + goto success; + } + } + + sdp_record_add(&req->device, rec); + if (!(req->flags & SDP_RECORD_PERSIST)) + sdp_svcdb_set_collectable(rec, req->sock); + + handle = sdp_data_alloc(SDP_UINT32, &rec->handle); + sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, handle); + +success: + /* if the browse group descriptor is NULL, + * ensure that the record belongs to the ROOT group */ + if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) { + uuid_t uuid; + sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); + sdp_pattern_add_uuid(rec, &uuid); + } + + update_db_timestamp(); + + /* Build a rsp buffer */ + put_be32(rec->handle, rsp->data); + rsp->data_size = sizeof(uint32_t); + + return 0; + +invalid: + put_be16(SDP_INVALID_SYNTAX, rsp->data); + rsp->data_size = sizeof(uint16_t); + + return -1; +} + +/* + * Update a service record + */ +int service_update_req(sdp_req_t *req, sdp_buf_t *rsp) +{ + sdp_record_t *orec, *nrec; + int status = 0, scanned = 0; + uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t); + int bufsize = req->len - sizeof(sdp_pdu_hdr_t); + uint32_t handle = get_be32(p); + + SDPDBG("Svc Rec Handle: 0x%x", handle); + + p += sizeof(uint32_t); + bufsize -= sizeof(uint32_t); + + orec = sdp_record_find(handle); + + SDPDBG("SvcRecOld: %p", orec); + + if (!orec) { + status = SDP_INVALID_RECORD_HANDLE; + goto done; + } + + nrec = extract_pdu_server(BDADDR_ANY, p, bufsize, handle, &scanned); + if (!nrec) { + status = SDP_INVALID_SYNTAX; + goto done; + } + + assert(nrec == orec); + + update_db_timestamp(); + +done: + p = rsp->data; + put_be16(status, p); + rsp->data_size = sizeof(uint16_t); + return status; +} + +/* + * Remove a registered service record + */ +int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp) +{ + uint8_t *p = req->buf + sizeof(sdp_pdu_hdr_t); + uint32_t handle = get_be32(p); + sdp_record_t *rec; + int status = 0; + + /* extract service record handle */ + + rec = sdp_record_find(handle); + if (rec) { + sdp_svcdb_collect(rec); + status = sdp_record_remove(handle); + sdp_record_free(rec); + if (status == 0) + update_db_timestamp(); + } else { + status = SDP_INVALID_RECORD_HANDLE; + SDPDBG("Could not find record : 0x%x", handle); + } + + p = rsp->data; + put_be16(status, p); + rsp->data_size = sizeof(uint16_t); + + return status; +} diff --git a/src/sdpd.h b/src/sdpd.h new file mode 100644 index 0000000..49cd98a --- /dev/null +++ b/src/sdpd.h @@ -0,0 +1,81 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2001-2002 Nokia Corporation + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * Copyright (C) 2002-2003 Stephen Crane + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef SDP_DEBUG +#include +#define SDPDBG(fmt, arg...) syslog(LOG_DEBUG, "%s: " fmt "\n", __func__ , ## arg) +#else +#define SDPDBG(fmt...) +#endif + +typedef struct request { + bdaddr_t device; + bdaddr_t bdaddr; + int local; + int sock; + int mtu; + int flags; + uint8_t *buf; + int len; +} sdp_req_t; + +void handle_internal_request(int sk, int mtu, void *data, int len); +void handle_request(int sk, uint8_t *data, int len); + +void set_fixed_db_timestamp(uint32_t dbts); + +int service_register_req(sdp_req_t *req, sdp_buf_t *rsp); +int service_update_req(sdp_req_t *req, sdp_buf_t *rsp); +int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp); + +void register_public_browse_group(void); +void register_server_service(void); +void register_device_id(uint16_t source, uint16_t vendor, + uint16_t product, uint16_t version); +void register_mps(bool mpmd); + +int record_sort(const void *r1, const void *r2); +void sdp_svcdb_reset(void); +void sdp_svcdb_collect_all(int sock); +void sdp_svcdb_set_collectable(sdp_record_t *rec, int sock); +void sdp_svcdb_collect(sdp_record_t *rec); +sdp_record_t *sdp_record_find(uint32_t handle); +void sdp_record_add(const bdaddr_t *device, sdp_record_t *rec); +int sdp_record_remove(uint32_t handle); +sdp_list_t *sdp_get_record_list(void); +int sdp_check_access(uint32_t handle, bdaddr_t *device); +uint32_t sdp_next_handle(void); + +uint32_t sdp_get_time(void); + +#define SDP_SERVER_COMPAT (1 << 0) +#define SDP_SERVER_MASTER (1 << 1) + +int start_sdp_server(uint16_t mtu, uint32_t flags); +void stop_sdp_server(void); + +int add_record_to_server(const bdaddr_t *src, sdp_record_t *rec); +int remove_record_from_server(uint32_t handle); diff --git a/src/service.c b/src/service.c new file mode 100644 index 0000000..c14ee0a --- /dev/null +++ b/src/service.c @@ -0,0 +1,395 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-2013 BMW Car IT GmbH. 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" + +#include "log.h" +#include "backtrace.h" + +#include "adapter.h" +#include "device.h" +#include "profile.h" +#include "service.h" + +struct btd_service { + int ref; + struct btd_device *device; + struct btd_profile *profile; + void *user_data; + btd_service_state_t state; + int err; +}; + +struct service_state_callback { + btd_service_state_cb cb; + void *user_data; + unsigned int id; +}; + +static GSList *state_callbacks = NULL; + +static const char *state2str(btd_service_state_t state) +{ + switch (state) { + case BTD_SERVICE_STATE_UNAVAILABLE: + return "unavailable"; + case BTD_SERVICE_STATE_DISCONNECTED: + return "disconnected"; + case BTD_SERVICE_STATE_CONNECTING: + return "connecting"; + case BTD_SERVICE_STATE_CONNECTED: + return "connected"; + case BTD_SERVICE_STATE_DISCONNECTING: + return "disconnecting"; + } + + return NULL; +} + +static void change_state(struct btd_service *service, btd_service_state_t state, + int err) +{ + btd_service_state_t old = service->state; + char addr[18]; + GSList *l; + + if (state == old) + return; + + btd_assert(service->device != NULL); + btd_assert(service->profile != NULL); + + service->state = state; + service->err = err; + + ba2str(device_get_address(service->device), addr); + DBG("%p: device %s profile %s state changed: %s -> %s (%d)", service, + addr, service->profile->name, + state2str(old), state2str(state), err); + + for (l = state_callbacks; l != NULL; l = g_slist_next(l)) { + struct service_state_callback *cb = l->data; + + cb->cb(service, old, state, cb->user_data); + } +} + +struct btd_service *btd_service_ref(struct btd_service *service) +{ + service->ref++; + + DBG("%p: ref=%d", service, service->ref); + + return service; +} + +void btd_service_unref(struct btd_service *service) +{ + service->ref--; + + DBG("%p: ref=%d", service, service->ref); + + if (service->ref > 0) + return; + + g_free(service); +} + +struct btd_service *service_create(struct btd_device *device, + struct btd_profile *profile) +{ + struct btd_service *service; + + service = g_try_new0(struct btd_service, 1); + if (!service) { + error("service_create: failed to alloc memory"); + return NULL; + } + + service->ref = 1; + service->device = device; /* Weak ref */ + service->profile = profile; + service->state = BTD_SERVICE_STATE_UNAVAILABLE; + + return service; +} + +int service_probe(struct btd_service *service) +{ + char addr[18]; + int err; + + btd_assert(service->state == BTD_SERVICE_STATE_UNAVAILABLE); + + err = service->profile->device_probe(service); + if (err == 0) { + change_state(service, BTD_SERVICE_STATE_DISCONNECTED, 0); + return 0; + } + + ba2str(device_get_address(service->device), addr); + error("%s profile probe failed for %s", service->profile->name, addr); + + return err; +} + +void service_remove(struct btd_service *service) +{ + change_state(service, BTD_SERVICE_STATE_DISCONNECTED, -ECONNABORTED); + change_state(service, BTD_SERVICE_STATE_UNAVAILABLE, 0); + service->profile->device_remove(service); + service->device = NULL; + service->profile = NULL; + btd_service_unref(service); +} + +int service_accept(struct btd_service *service) +{ + char addr[18]; + int err; + + switch (service->state) { + case BTD_SERVICE_STATE_UNAVAILABLE: + return -EINVAL; + case BTD_SERVICE_STATE_DISCONNECTED: + break; + case BTD_SERVICE_STATE_CONNECTING: + case BTD_SERVICE_STATE_CONNECTED: + return 0; + case BTD_SERVICE_STATE_DISCONNECTING: + return -EBUSY; + } + + if (!service->profile->accept) + return -ENOSYS; + + err = service->profile->accept(service); + if (!err) + goto done; + + ba2str(device_get_address(service->device), addr); + error("%s profile accept failed for %s", service->profile->name, addr); + + return err; + +done: + if (service->state == BTD_SERVICE_STATE_DISCONNECTED) + change_state(service, BTD_SERVICE_STATE_CONNECTING, 0); + return 0; +} + +int service_set_connecting(struct btd_service *service) +{ + switch (service->state) { + case BTD_SERVICE_STATE_UNAVAILABLE: + return -EINVAL; + case BTD_SERVICE_STATE_DISCONNECTED: + break; + case BTD_SERVICE_STATE_CONNECTING: + case BTD_SERVICE_STATE_CONNECTED: + return 0; + case BTD_SERVICE_STATE_DISCONNECTING: + return -EBUSY; + } + + change_state(service, BTD_SERVICE_STATE_CONNECTING, 0); + + return 0; +} + +int btd_service_connect(struct btd_service *service) +{ + struct btd_profile *profile = service->profile; + char addr[18]; + int err; + + if (!profile->connect) + return -ENOTSUP; + + switch (service->state) { + case BTD_SERVICE_STATE_UNAVAILABLE: + return -EINVAL; + case BTD_SERVICE_STATE_DISCONNECTED: + break; + case BTD_SERVICE_STATE_CONNECTING: + return 0; + case BTD_SERVICE_STATE_CONNECTED: + return -EALREADY; + case BTD_SERVICE_STATE_DISCONNECTING: + return -EBUSY; + } + + err = profile->connect(service); + if (err == 0) { + change_state(service, BTD_SERVICE_STATE_CONNECTING, 0); + return 0; + } + + ba2str(device_get_address(service->device), addr); + error("%s profile connect failed for %s: %s", profile->name, addr, + strerror(-err)); + + return err; +} + +int btd_service_disconnect(struct btd_service *service) +{ + struct btd_profile *profile = service->profile; + char addr[18]; + int err; + + if (!profile->disconnect) + return -ENOTSUP; + + switch (service->state) { + case BTD_SERVICE_STATE_UNAVAILABLE: + return -EINVAL; + case BTD_SERVICE_STATE_DISCONNECTED: + case BTD_SERVICE_STATE_DISCONNECTING: + return -EALREADY; + case BTD_SERVICE_STATE_CONNECTING: + case BTD_SERVICE_STATE_CONNECTED: + break; + } + + change_state(service, BTD_SERVICE_STATE_DISCONNECTING, 0); + + err = profile->disconnect(service); + if (err == 0) + return 0; + + if (err == -ENOTCONN) { + btd_service_disconnecting_complete(service, 0); + return 0; + } + + ba2str(device_get_address(service->device), addr); + error("%s profile disconnect failed for %s: %s", profile->name, addr, + strerror(-err)); + + btd_service_disconnecting_complete(service, err); + + return err; +} + +struct btd_device *btd_service_get_device(const struct btd_service *service) +{ + return service->device; +} + +struct btd_profile *btd_service_get_profile(const struct btd_service *service) +{ + return service->profile; +} + +void btd_service_set_user_data(struct btd_service *service, void *user_data) +{ + service->user_data = user_data; +} + +void *btd_service_get_user_data(const struct btd_service *service) +{ + return service->user_data; +} + +btd_service_state_t btd_service_get_state(const struct btd_service *service) +{ + return service->state; +} + +int btd_service_get_error(const struct btd_service *service) +{ + return service->err; +} + +unsigned int btd_service_add_state_cb(btd_service_state_cb cb, void *user_data) +{ + struct service_state_callback *state_cb; + static unsigned int id = 0; + + state_cb = g_new0(struct service_state_callback, 1); + state_cb->cb = cb; + state_cb->user_data = user_data; + state_cb->id = ++id; + + state_callbacks = g_slist_append(state_callbacks, state_cb); + + return state_cb->id; +} + +bool btd_service_remove_state_cb(unsigned int id) +{ + GSList *l; + + for (l = state_callbacks; l != NULL; l = g_slist_next(l)) { + struct service_state_callback *cb = l->data; + + if (cb && cb->id == id) { + state_callbacks = g_slist_remove(state_callbacks, cb); + g_free(cb); + return true; + } + } + + return false; +} + +void btd_service_connecting_complete(struct btd_service *service, int err) +{ + if (service->state != BTD_SERVICE_STATE_DISCONNECTED && + service->state != BTD_SERVICE_STATE_CONNECTING) + return; + + if (err == 0) + change_state(service, BTD_SERVICE_STATE_CONNECTED, 0); + else + change_state(service, BTD_SERVICE_STATE_DISCONNECTED, err); +} + +void btd_service_disconnecting_complete(struct btd_service *service, int err) +{ + if (service->state != BTD_SERVICE_STATE_CONNECTED && + service->state != BTD_SERVICE_STATE_DISCONNECTING) + return; + + if (err == 0) + change_state(service, BTD_SERVICE_STATE_DISCONNECTED, 0); + else /* If disconnect fails, we assume it remains connected */ + change_state(service, BTD_SERVICE_STATE_CONNECTED, err); +} diff --git a/src/service.h b/src/service.h new file mode 100644 index 0000000..6f1edfb --- /dev/null +++ b/src/service.h @@ -0,0 +1,72 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-2013 BMW Car IT GmbH. 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 + * + */ + +typedef enum { + BTD_SERVICE_STATE_UNAVAILABLE, /* Not probed */ + BTD_SERVICE_STATE_DISCONNECTED, + BTD_SERVICE_STATE_CONNECTING, + BTD_SERVICE_STATE_CONNECTED, + BTD_SERVICE_STATE_DISCONNECTING, +} btd_service_state_t; + +struct btd_service; +struct btd_device; +struct btd_profile; + +typedef void (*btd_service_state_cb) (struct btd_service *service, + btd_service_state_t old_state, + btd_service_state_t new_state, + void *user_data); + +struct btd_service *btd_service_ref(struct btd_service *service); +void btd_service_unref(struct btd_service *service); + +/* Service management functions used by the core */ +struct btd_service *service_create(struct btd_device *device, + struct btd_profile *profile); + +int service_probe(struct btd_service *service); +void service_remove(struct btd_service *service); + +int service_accept(struct btd_service *service); +int service_set_connecting(struct btd_service *service); + +/* Connection control API */ +int btd_service_connect(struct btd_service *service); +int btd_service_disconnect(struct btd_service *service); + +/* Public member access */ +struct btd_device *btd_service_get_device(const struct btd_service *service); +struct btd_profile *btd_service_get_profile(const struct btd_service *service); +btd_service_state_t btd_service_get_state(const struct btd_service *service); +int btd_service_get_error(const struct btd_service *service); + +unsigned int btd_service_add_state_cb(btd_service_state_cb cb, + void *user_data); +bool btd_service_remove_state_cb(unsigned int id); + +/* Functions used by profile implementation */ +void btd_service_connecting_complete(struct btd_service *service, int err); +void btd_service_disconnecting_complete(struct btd_service *service, int err); +void btd_service_set_user_data(struct btd_service *service, void *user_data); +void *btd_service_get_user_data(const struct btd_service *service); diff --git a/src/shared/ad.c b/src/shared/ad.c new file mode 100644 index 0000000..8d27684 --- /dev/null +++ b/src/shared/ad.c @@ -0,0 +1,1011 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Google 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE + +#include "src/shared/ad.h" + +#include "src/eir.h" +#include "src/shared/queue.h" +#include "src/shared/util.h" + +#define MAX_ADV_DATA_LEN 31 + +struct bt_ad { + int ref_count; + char *name; + uint16_t appearance; + struct queue *service_uuids; + struct queue *manufacturer_data; + struct queue *solicit_uuids; + struct queue *service_data; + struct queue *data; +}; + +struct bt_ad *bt_ad_new(void) +{ + struct bt_ad *ad; + + ad = new0(struct bt_ad, 1); + ad->service_uuids = queue_new(); + ad->manufacturer_data = queue_new(); + ad->solicit_uuids = queue_new(); + ad->service_data = queue_new(); + ad->data = queue_new(); + ad->appearance = UINT16_MAX; + + return bt_ad_ref(ad); +} + +struct bt_ad *bt_ad_ref(struct bt_ad *ad) +{ + if (!ad) + return NULL; + + ad->ref_count++; + return ad; +} + +static void uuid_destroy(void *data) +{ + struct bt_ad_service_data *uuid_data = data; + + free(uuid_data->data); + free(uuid_data); +} + +static bool uuid_data_match(const void *data, const void *elem) +{ + const struct bt_ad_service_data *uuid_data = elem; + const bt_uuid_t *uuid = data; + + return !bt_uuid_cmp(&uuid_data->uuid, uuid); +} + +static void manuf_destroy(void *data) +{ + struct bt_ad_manufacturer_data *manuf = data; + + free(manuf->data); + free(manuf); +} + +static bool manuf_match(const void *data, const void *elem) +{ + const struct bt_ad_manufacturer_data *manuf = elem; + uint16_t manuf_id = PTR_TO_UINT(elem); + + return manuf->manufacturer_id == manuf_id; +} + +static void data_destroy(void *data) +{ + struct bt_ad_data *ad = data; + + free(ad->data); + free(ad); +} + +void bt_ad_unref(struct bt_ad *ad) +{ + if (!ad) + return; + + if (__sync_sub_and_fetch(&ad->ref_count, 1)) + return; + + queue_destroy(ad->service_uuids, free); + + queue_destroy(ad->manufacturer_data, manuf_destroy); + + queue_destroy(ad->solicit_uuids, free); + + queue_destroy(ad->service_data, uuid_destroy); + + queue_destroy(ad->data, data_destroy); + + free(ad->name); + + free(ad); +} + +static bool data_type_match(const void *data, const void *user_data) +{ + const struct bt_ad_data *a = data; + const uint8_t type = PTR_TO_UINT(user_data); + + return a->type == type; +} + +static bool ad_replace_data(struct bt_ad *ad, uint8_t type, void *data, + size_t len) +{ + struct bt_ad_data *new_data; + + new_data = queue_find(ad->data, data_type_match, UINT_TO_PTR(type)); + if (new_data) { + if (new_data->len == len && !memcmp(new_data->data, data, len)) + return false; + new_data->data = realloc(new_data->data, len); + memcpy(new_data->data, data, len); + new_data->len = len; + return true; + } + + new_data = new0(struct bt_ad_data, 1); + new_data->type = type; + new_data->data = malloc(len); + if (!new_data->data) { + free(new_data); + return false; + } + + memcpy(new_data->data, data, len); + new_data->len = len; + + if (queue_push_tail(ad->data, new_data)) + return true; + + data_destroy(new_data); + + return false; +} + +static size_t uuid_list_length(struct queue *uuid_queue) +{ + bool uuid16_included = false; + bool uuid32_included = false; + bool uuid128_included = false; + size_t length = 0; + const struct queue_entry *entry; + + entry = queue_get_entries(uuid_queue); + + while (entry) { + bt_uuid_t *uuid = entry->data; + + length += bt_uuid_len(uuid); + + if (uuid->type == BT_UUID16) + uuid16_included = true; + else if (uuid->type == BT_UUID32) + uuid32_included = true; + else + uuid128_included = true; + + entry = entry->next; + } + + if (uuid16_included) + length += 2; + + if (uuid32_included) + length += 2; + + if (uuid128_included) + length += 2; + + return length; +} + +static size_t mfg_data_length(struct queue *manuf_data) +{ + size_t length = 0; + const struct queue_entry *entry; + + entry = queue_get_entries(manuf_data); + + while (entry) { + struct bt_ad_manufacturer_data *data = entry->data; + + length += 2 + sizeof(uint16_t) + data->len; + + entry = entry->next; + } + + return length; +} + +static size_t uuid_data_length(struct queue *uuid_data) +{ + size_t length = 0; + const struct queue_entry *entry; + + entry = queue_get_entries(uuid_data); + + while (entry) { + struct bt_ad_service_data *data = entry->data; + + length += 2 + bt_uuid_len(&data->uuid) + data->len; + + entry = entry->next; + } + + return length; +} + +static size_t name_length(const char *name, size_t *pos) +{ + size_t len; + + if (!name) + return 0; + + len = 2 + strlen(name); + + if (len > MAX_ADV_DATA_LEN - *pos) + len = MAX_ADV_DATA_LEN - *pos; + + return len; +} + +static size_t data_length(struct queue *queue) +{ + size_t length = 0; + const struct queue_entry *entry; + + entry = queue_get_entries(queue); + + while (entry) { + struct bt_ad_data *data = entry->data; + + length += 1 + 1 + data->len; + + entry = entry->next; + } + + return length; +} + +static size_t calculate_length(struct bt_ad *ad) +{ + size_t length = 0; + + length += uuid_list_length(ad->service_uuids); + + length += uuid_list_length(ad->solicit_uuids); + + length += mfg_data_length(ad->manufacturer_data); + + length += uuid_data_length(ad->service_data); + + length += name_length(ad->name, &length); + + length += ad->appearance != UINT16_MAX ? 4 : 0; + + length += data_length(ad->data); + + return length; +} + +static void serialize_uuids(struct queue *uuids, uint8_t uuid_type, + uint8_t ad_type, uint8_t *buf, + uint8_t *pos) +{ + const struct queue_entry *entry = queue_get_entries(uuids); + bool added = false; + uint8_t length_pos = 0; + + while (entry) { + bt_uuid_t *uuid = entry->data; + + if (uuid->type == uuid_type) { + if (!added) { + length_pos = (*pos)++; + buf[(*pos)++] = ad_type; + added = true; + } + + if (uuid_type != BT_UUID32) + bt_uuid_to_le(uuid, buf + *pos); + else + bt_put_le32(uuid->value.u32, buf + *pos); + + *pos += bt_uuid_len(uuid); + } + + entry = entry->next; + } + + if (added) + buf[length_pos] = *pos - length_pos - 1; +} + +static void serialize_service_uuids(struct queue *uuids, uint8_t *buf, + uint8_t *pos) +{ + serialize_uuids(uuids, BT_UUID16, BT_AD_UUID16_ALL, buf, pos); + + serialize_uuids(uuids, BT_UUID32, BT_AD_UUID32_ALL, buf, pos); + + serialize_uuids(uuids, BT_UUID128, BT_AD_UUID128_ALL, buf, pos); +} + +static void serialize_solicit_uuids(struct queue *uuids, uint8_t *buf, + uint8_t *pos) +{ + serialize_uuids(uuids, BT_UUID16, BT_AD_SOLICIT16, buf, pos); + + serialize_uuids(uuids, BT_UUID32, BT_AD_SOLICIT32, buf, pos); + + serialize_uuids(uuids, BT_UUID128, BT_AD_SOLICIT128, buf, pos); +} + +static void serialize_manuf_data(struct queue *manuf_data, uint8_t *buf, + uint8_t *pos) +{ + const struct queue_entry *entry = queue_get_entries(manuf_data); + + while (entry) { + struct bt_ad_manufacturer_data *data = entry->data; + + buf[(*pos)++] = data->len + 2 + 1; + + buf[(*pos)++] = BT_AD_MANUFACTURER_DATA; + + bt_put_le16(data->manufacturer_id, buf + (*pos)); + + *pos += 2; + + memcpy(buf + *pos, data->data, data->len); + + *pos += data->len; + + entry = entry->next; + } +} + +static void serialize_service_data(struct queue *service_data, uint8_t *buf, + uint8_t *pos) +{ + const struct queue_entry *entry = queue_get_entries(service_data); + + while (entry) { + struct bt_ad_service_data *data = entry->data; + int uuid_len = bt_uuid_len(&data->uuid); + + buf[(*pos)++] = uuid_len + data->len + 1; + + switch (uuid_len) { + case 2: + buf[(*pos)++] = BT_AD_SERVICE_DATA16; + break; + case 4: + buf[(*pos)++] = BT_AD_SERVICE_DATA32; + break; + case 16: + buf[(*pos)++] = BT_AD_SERVICE_DATA128; + break; + } + + if (uuid_len != 4) + bt_uuid_to_le(&data->uuid, buf + *pos); + else + bt_put_le32(data->uuid.value.u32, buf + *pos); + + *pos += uuid_len; + + memcpy(buf + *pos, data->data, data->len); + + *pos += data->len; + + entry = entry->next; + } +} + +static void serialize_name(const char *name, uint8_t *buf, uint8_t *pos) +{ + int len; + uint8_t type = BT_AD_NAME_COMPLETE; + + if (!name) + return; + + len = strlen(name); + if (len > MAX_ADV_DATA_LEN - (*pos + 2)) { + type = BT_AD_NAME_SHORT; + len = MAX_ADV_DATA_LEN - (*pos + 2); + } + + buf[(*pos)++] = len + 1; + buf[(*pos)++] = type; + + memcpy(buf + *pos, name, len); + *pos += len; +} + +static void serialize_appearance(uint16_t value, uint8_t *buf, uint8_t *pos) +{ + if (value == UINT16_MAX) + return; + + buf[(*pos)++] = sizeof(value) + 1; + buf[(*pos)++] = BT_AD_GAP_APPEARANCE; + + bt_put_le16(value, buf + (*pos)); + *pos += 2; +} + +static void serialize_data(struct queue *queue, uint8_t *buf, uint8_t *pos) +{ + const struct queue_entry *entry = queue_get_entries(queue); + + while (entry) { + struct bt_ad_data *data = entry->data; + + buf[(*pos)++] = data->len + 1; + buf[(*pos)++] = data->type; + + memcpy(buf + *pos, data->data, data->len); + + *pos += data->len; + + entry = entry->next; + } +} + +uint8_t *bt_ad_generate(struct bt_ad *ad, size_t *length) +{ + uint8_t *adv_data; + uint8_t pos = 0; + + if (!ad) + return NULL; + + *length = calculate_length(ad); + + if (*length > MAX_ADV_DATA_LEN) + return NULL; + + adv_data = malloc0(*length); + if (!adv_data) + return NULL; + + serialize_service_uuids(ad->service_uuids, adv_data, &pos); + + serialize_solicit_uuids(ad->solicit_uuids, adv_data, &pos); + + serialize_manuf_data(ad->manufacturer_data, adv_data, &pos); + + serialize_service_data(ad->service_data, adv_data, &pos); + + serialize_name(ad->name, adv_data, &pos); + + serialize_appearance(ad->appearance, adv_data, &pos); + + serialize_data(ad->data, adv_data, &pos); + + return adv_data; +} + +static bool queue_add_uuid(struct queue *queue, const bt_uuid_t *uuid) +{ + bt_uuid_t *new_uuid; + + if (!queue) + return false; + + new_uuid = new0(bt_uuid_t, 1); + + *new_uuid = *uuid; + + if (queue_push_tail(queue, new_uuid)) + return true; + + free(new_uuid); + + return false; +} + +static bool uuid_match(const void *data, const void *elem) +{ + const bt_uuid_t *match_uuid = data; + const bt_uuid_t *uuid = elem; + + return bt_uuid_cmp(match_uuid, uuid); +} + +static bool queue_remove_uuid(struct queue *queue, bt_uuid_t *uuid) +{ + bt_uuid_t *removed; + + if (!queue || !uuid) + return false; + + removed = queue_remove_if(queue, uuid_match, uuid); + + if (removed) { + free(removed); + return true; + } + + return false; +} + +bool bt_ad_add_service_uuid(struct bt_ad *ad, const bt_uuid_t *uuid) +{ + if (!ad) + return false; + + return queue_add_uuid(ad->service_uuids, uuid); +} + +bool bt_ad_remove_service_uuid(struct bt_ad *ad, bt_uuid_t *uuid) +{ + if (!ad) + return false; + + return queue_remove_uuid(ad->service_uuids, uuid); +} + +void bt_ad_clear_service_uuid(struct bt_ad *ad) +{ + if (!ad) + return; + + queue_remove_all(ad->service_uuids, NULL, NULL, free); +} + +static bool manufacturer_id_data_match(const void *data, const void *user_data) +{ + const struct bt_ad_manufacturer_data *m = data; + uint16_t id = PTR_TO_UINT(user_data); + + return m->manufacturer_id == id; +} + +bool bt_ad_add_manufacturer_data(struct bt_ad *ad, uint16_t manufacturer_id, + void *data, size_t len) +{ + struct bt_ad_manufacturer_data *new_data; + + if (!ad) + return false; + + if (len > (MAX_ADV_DATA_LEN - 2 - sizeof(uint16_t))) + return false; + + new_data = queue_find(ad->manufacturer_data, manufacturer_id_data_match, + UINT_TO_PTR(manufacturer_id)); + if (new_data) { + if (new_data->len == len && !memcmp(new_data->data, data, len)) + return false; + new_data->data = realloc(new_data->data, len); + memcpy(new_data->data, data, len); + new_data->len = len; + return true; + } + + new_data = new0(struct bt_ad_manufacturer_data, 1); + new_data->manufacturer_id = manufacturer_id; + + new_data->data = malloc(len); + if (!new_data->data) { + free(new_data); + return false; + } + + memcpy(new_data->data, data, len); + + new_data->len = len; + + if (queue_push_tail(ad->manufacturer_data, new_data)) + return true; + + manuf_destroy(new_data); + + return false; +} + +static bool manufacturer_data_match(const void *data, const void *user_data) +{ + const struct bt_ad_manufacturer_data *m1 = data; + const struct bt_ad_manufacturer_data *m2 = user_data; + + if (m1->manufacturer_id != m2->manufacturer_id) + return false; + + if (m1->len != m2->len) + return false; + + return !memcmp(m1->data, m2->data, m1->len); +} + +bool bt_ad_has_manufacturer_data(struct bt_ad *ad, + const struct bt_ad_manufacturer_data *data) +{ + if (!ad) + return false; + + if (!data) + return !queue_isempty(ad->manufacturer_data); + + return queue_find(ad->manufacturer_data, manufacturer_data_match, data); +} + +void bt_ad_foreach_manufacturer_data(struct bt_ad *ad, bt_ad_func_t func, + void *user_data) +{ + if (!ad) + return; + + queue_foreach(ad->manufacturer_data, func, user_data); +} + +bool bt_ad_remove_manufacturer_data(struct bt_ad *ad, uint16_t manufacturer_id) +{ + struct bt_ad_manufacturer_data *data; + + if (!ad) + return false; + + data = queue_remove_if(ad->manufacturer_data, manuf_match, + UINT_TO_PTR(manufacturer_id)); + + if (!data) + return false; + + manuf_destroy(data); + + return true; +} + +void bt_ad_clear_manufacturer_data(struct bt_ad *ad) +{ + if (!ad) + return; + + queue_remove_all(ad->manufacturer_data, NULL, NULL, manuf_destroy); +} + +bool bt_ad_add_solicit_uuid(struct bt_ad *ad, const bt_uuid_t *uuid) +{ + if (!ad) + return false; + + return queue_add_uuid(ad->solicit_uuids, uuid); +} + +bool bt_ad_remove_solicit_uuid(struct bt_ad *ad, bt_uuid_t *uuid) +{ + if (!ad) + return false; + + return queue_remove_uuid(ad->solicit_uuids, uuid); +} + +void bt_ad_clear_solicit_uuid(struct bt_ad *ad) +{ + if (!ad) + return; + + queue_remove_all(ad->solicit_uuids, NULL, NULL, free); +} + + +static bool service_uuid_match(const void *data, const void *user_data) +{ + const struct bt_ad_service_data *s = data; + const bt_uuid_t *uuid = user_data; + + return !bt_uuid_cmp(&s->uuid, uuid); +} + +bool bt_ad_add_service_data(struct bt_ad *ad, const bt_uuid_t *uuid, void *data, + size_t len) +{ + struct bt_ad_service_data *new_data; + + if (!ad) + return false; + + if (len > (MAX_ADV_DATA_LEN - 2 - (size_t)bt_uuid_len(uuid))) + return false; + + new_data = queue_find(ad->service_data, service_uuid_match, uuid); + if (new_data) { + if (new_data->len == len && !memcmp(new_data->data, data, len)) + return false; + new_data->data = realloc(new_data->data, len); + memcpy(new_data->data, data, len); + new_data->len = len; + return true; + } + + new_data = new0(struct bt_ad_service_data, 1); + + new_data->uuid = *uuid; + + new_data->data = malloc(len); + if (!new_data->data) { + free(new_data); + return false; + } + + memcpy(new_data->data, data, len); + + new_data->len = len; + + if (queue_push_tail(ad->service_data, new_data)) + return true; + + uuid_destroy(new_data); + + return false; +} + +static bool service_data_match(const void *data, const void *user_data) +{ + const struct bt_ad_service_data *s1 = data; + const struct bt_ad_service_data *s2 = user_data; + + if (bt_uuid_cmp(&s1->uuid, &s2->uuid)) + return false; + + if (s1->len != s2->len) + return false; + + return !memcmp(s1->data, s2->data, s1->len); +} + +bool bt_ad_has_service_data(struct bt_ad *ad, + const struct bt_ad_service_data *data) +{ + if (!ad) + return false; + + if (!data) + return !queue_isempty(ad->service_data); + + return queue_find(ad->service_data, service_data_match, data); +} + +void bt_ad_foreach_service_data(struct bt_ad *ad, bt_ad_func_t func, + void *user_data) +{ + if (!ad) + return; + + queue_foreach(ad->service_data, func, user_data); +} + +bool bt_ad_remove_service_data(struct bt_ad *ad, bt_uuid_t *uuid) +{ + struct bt_ad_service_data *data; + + if (!ad) + return false; + + data = queue_remove_if(ad->service_data, uuid_data_match, uuid); + + if (!data) + return false; + + uuid_destroy(data); + + return true; +} + +void bt_ad_clear_service_data(struct bt_ad *ad) +{ + if (!ad) + return; + + queue_remove_all(ad->service_data, NULL, NULL, uuid_destroy); +} + +bool bt_ad_add_name(struct bt_ad *ad, const char *name) +{ + if (!ad) + return false; + + free(ad->name); + + ad->name = strdup(name); + + return true; +} + +void bt_ad_clear_name(struct bt_ad *ad) +{ + if (!ad) + return; + + free(ad->name); + ad->name = NULL; +} + +bool bt_ad_add_appearance(struct bt_ad *ad, uint16_t appearance) +{ + if (!ad) + return false; + + ad->appearance = appearance; + + return true; +} + +void bt_ad_clear_appearance(struct bt_ad *ad) +{ + if (!ad) + return; + + ad->appearance = UINT16_MAX; +} + +bool bt_ad_add_flags(struct bt_ad *ad, uint8_t *flags, size_t len) +{ + if (!ad) + return false; + + /* TODO: Add table to check other flags */ + if (len > 1 || flags[0] & 0xe0) + return false; + + return ad_replace_data(ad, BT_AD_FLAGS, flags, len); +} + +bool bt_ad_has_flags(struct bt_ad *ad) +{ + struct bt_ad_data *data; + + if (!ad) + return false; + + data = queue_find(ad->data, data_type_match, UINT_TO_PTR(BT_AD_FLAGS)); + if (!data) + return false; + + return true; +} + +void bt_ad_clear_flags(struct bt_ad *ad) +{ + if (!ad) + return; + + queue_remove_all(ad->data, data_type_match, UINT_TO_PTR(BT_AD_FLAGS), + data_destroy); +} + +static uint8_t type_blacklist[] = { + BT_AD_FLAGS, + BT_AD_UUID16_SOME, + BT_AD_UUID16_ALL, + BT_AD_UUID32_SOME, + BT_AD_UUID32_ALL, + BT_AD_UUID128_SOME, + BT_AD_UUID128_ALL, + BT_AD_NAME_SHORT, + BT_AD_NAME_COMPLETE, + BT_AD_TX_POWER, + BT_AD_CLASS_OF_DEV, + BT_AD_SSP_HASH, + BT_AD_SSP_RANDOMIZER, + BT_AD_DEVICE_ID, + BT_AD_SMP_TK, + BT_AD_SMP_OOB_FLAGS, + BT_AD_SLAVE_CONN_INTERVAL, + BT_AD_SOLICIT16, + BT_AD_SOLICIT128, + BT_AD_SERVICE_DATA16, + BT_AD_PUBLIC_ADDRESS, + BT_AD_RANDOM_ADDRESS, + BT_AD_GAP_APPEARANCE, + BT_AD_ADVERTISING_INTERVAL, + BT_AD_LE_DEVICE_ADDRESS, + BT_AD_LE_ROLE, + BT_AD_SSP_HASH_P256, + BT_AD_SSP_RANDOMIZER_P256, + BT_AD_SOLICIT32, + BT_AD_SERVICE_DATA32, + BT_AD_SERVICE_DATA128, + BT_AD_LE_SC_CONFIRM_VALUE, + BT_AD_LE_SC_RANDOM_VALUE, + BT_AD_LE_SUPPORTED_FEATURES, + BT_AD_CHANNEL_MAP_UPDATE_IND, + BT_AD_MESH_PROV, + BT_AD_MESH_DATA, + BT_AD_MESH_BEACON, + BT_AD_3D_INFO_DATA, + BT_AD_MANUFACTURER_DATA, +}; + +bool bt_ad_add_data(struct bt_ad *ad, uint8_t type, void *data, size_t len) +{ + size_t i; + + if (!ad) + return false; + + if (len > (MAX_ADV_DATA_LEN - 2)) + return false; + + for (i = 0; i < sizeof(type_blacklist); i++) { + if (type == type_blacklist[i]) + return false; + } + + return ad_replace_data(ad, type, data, len); +} + +static bool data_match(const void *data, const void *user_data) +{ + const struct bt_ad_data *d1 = data; + const struct bt_ad_data *d2 = user_data; + + if (d1->type != d2->type) + return false; + + if (d1->len != d2->len) + return false; + + return !memcmp(d1->data, d2->data, d1->len); +} + +bool bt_ad_has_data(struct bt_ad *ad, const struct bt_ad_data *data) +{ + if (!ad) + return false; + + if (!data) + return !queue_isempty(ad->data); + + return queue_find(ad->data, data_match, data); +} + +void bt_ad_foreach_data(struct bt_ad *ad, bt_ad_func_t func, void *user_data) +{ + if (!ad) + return; + + queue_foreach(ad->data, func, user_data); +} + +bool bt_ad_remove_data(struct bt_ad *ad, uint8_t type) +{ + struct bt_ad_data *data; + + if (!ad) + return false; + + data = queue_remove_if(ad->data, data_type_match, UINT_TO_PTR(type)); + if (!data) + return false; + + data_destroy(data); + + return true; +} + +void bt_ad_clear_data(struct bt_ad *ad) +{ + if (!ad) + return; + + queue_remove_all(ad->data, NULL, NULL, data_destroy); +} diff --git a/src/shared/ad.h b/src/shared/ad.h new file mode 100644 index 0000000..a31df0f --- /dev/null +++ b/src/shared/ad.h @@ -0,0 +1,164 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Google 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 + * + */ + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" + +#define BT_AD_FLAGS 0x01 +#define BT_AD_UUID16_SOME 0x02 +#define BT_AD_UUID16_ALL 0x03 +#define BT_AD_UUID32_SOME 0x04 +#define BT_AD_UUID32_ALL 0x05 +#define BT_AD_UUID128_SOME 0x06 +#define BT_AD_UUID128_ALL 0x07 +#define BT_AD_NAME_SHORT 0x08 +#define BT_AD_NAME_COMPLETE 0x09 +#define BT_AD_TX_POWER 0x0a +#define BT_AD_CLASS_OF_DEV 0x0d +#define BT_AD_SSP_HASH 0x0e +#define BT_AD_SSP_RANDOMIZER 0x0f +#define BT_AD_DEVICE_ID 0x10 +#define BT_AD_SMP_TK 0x10 +#define BT_AD_SMP_OOB_FLAGS 0x11 +#define BT_AD_SLAVE_CONN_INTERVAL 0x12 +#define BT_AD_SOLICIT16 0x14 +#define BT_AD_SOLICIT128 0x15 +#define BT_AD_SERVICE_DATA16 0x16 +#define BT_AD_PUBLIC_ADDRESS 0x17 +#define BT_AD_RANDOM_ADDRESS 0x18 +#define BT_AD_GAP_APPEARANCE 0x19 +#define BT_AD_ADVERTISING_INTERVAL 0x1a +#define BT_AD_LE_DEVICE_ADDRESS 0x1b +#define BT_AD_LE_ROLE 0x1c +#define BT_AD_SSP_HASH_P256 0x1d +#define BT_AD_SSP_RANDOMIZER_P256 0x1e +#define BT_AD_SOLICIT32 0x1f +#define BT_AD_SERVICE_DATA32 0x20 +#define BT_AD_SERVICE_DATA128 0x21 +#define BT_AD_LE_SC_CONFIRM_VALUE 0x22 +#define BT_AD_LE_SC_RANDOM_VALUE 0x23 +#define BT_AD_URI 0x24 +#define BT_AD_INDOOR_POSITIONING 0x25 +#define BT_AD_TRANSPORT_DISCOVERY 0x26 +#define BT_AD_LE_SUPPORTED_FEATURES 0x27 +#define BT_AD_CHANNEL_MAP_UPDATE_IND 0x28 +#define BT_AD_MESH_PROV 0x29 +#define BT_AD_MESH_DATA 0x2a +#define BT_AD_MESH_BEACON 0x2b +#define BT_AD_3D_INFO_DATA 0x3d +#define BT_AD_MANUFACTURER_DATA 0xff + +typedef void (*bt_ad_func_t)(void *data, void *user_data); + +struct bt_ad; + +struct bt_ad_manufacturer_data { + uint16_t manufacturer_id; + uint8_t *data; + size_t len; +}; + +struct bt_ad_service_data { + bt_uuid_t uuid; + size_t len; + void *data; +}; + +struct bt_ad_data { + uint8_t type; + uint8_t *data; + size_t len; +}; + +struct bt_ad *bt_ad_new(void); + +struct bt_ad *bt_ad_ref(struct bt_ad *ad); + +void bt_ad_unref(struct bt_ad *ad); + +uint8_t *bt_ad_generate(struct bt_ad *ad, size_t *length); + +bool bt_ad_add_service_uuid(struct bt_ad *ad, const bt_uuid_t *uuid); + +bool bt_ad_remove_service_uuid(struct bt_ad *ad, bt_uuid_t *uuid); + +void bt_ad_clear_service_uuid(struct bt_ad *ad); + +bool bt_ad_add_manufacturer_data(struct bt_ad *ad, uint16_t manufacturer_data, + void *data, size_t len); + +bool bt_ad_has_manufacturer_data(struct bt_ad *ad, + const struct bt_ad_manufacturer_data *data); + +void bt_ad_foreach_manufacturer_data(struct bt_ad *ad, bt_ad_func_t func, + void *user_data); + +bool bt_ad_remove_manufacturer_data(struct bt_ad *ad, uint16_t manufacturer_id); + +void bt_ad_clear_manufacturer_data(struct bt_ad *ad); + +bool bt_ad_add_solicit_uuid(struct bt_ad *ad, const bt_uuid_t *uuid); + +bool bt_ad_remove_solicit_uuid(struct bt_ad *ad, bt_uuid_t *uuid); + +void bt_ad_clear_solicit_uuid(struct bt_ad *ad); + +bool bt_ad_add_service_data(struct bt_ad *ad, const bt_uuid_t *uuid, void *data, + size_t len); + +bool bt_ad_has_service_data(struct bt_ad *ad, + const struct bt_ad_service_data *data); + +void bt_ad_foreach_service_data(struct bt_ad *ad, bt_ad_func_t func, + void *user_data); + +bool bt_ad_remove_service_data(struct bt_ad *ad, bt_uuid_t *uuid); + +void bt_ad_clear_service_data(struct bt_ad *ad); + +bool bt_ad_add_name(struct bt_ad *ad, const char *name); + +void bt_ad_clear_name(struct bt_ad *ad); + +bool bt_ad_add_appearance(struct bt_ad *ad, uint16_t appearance); + +void bt_ad_clear_appearance(struct bt_ad *ad); + +bool bt_ad_add_flags(struct bt_ad *ad, uint8_t *flags, size_t len); + +bool bt_ad_has_flags(struct bt_ad *ad); + +void bt_ad_clear_flags(struct bt_ad *ad); + +bool bt_ad_add_data(struct bt_ad *ad, uint8_t type, void *data, size_t len); + +bool bt_ad_has_data(struct bt_ad *ad, const struct bt_ad_data *data); + +void bt_ad_foreach_data(struct bt_ad *ad, bt_ad_func_t func, void *user_data); + +bool bt_ad_remove_data(struct bt_ad *ad, uint8_t type); + +void bt_ad_clear_data(struct bt_ad *ad); diff --git a/src/shared/att-types.h b/src/shared/att-types.h new file mode 100644 index 0000000..8a2658d --- /dev/null +++ b/src/shared/att-types.h @@ -0,0 +1,161 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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 + * + */ + +#include + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#define BT_ATT_SECURITY_AUTO 0 +#define BT_ATT_SECURITY_LOW 1 +#define BT_ATT_SECURITY_MEDIUM 2 +#define BT_ATT_SECURITY_HIGH 3 +#define BT_ATT_SECURITY_FIPS 4 + +#define BT_ATT_DEFAULT_LE_MTU 23 +#define BT_ATT_MAX_LE_MTU 517 +#define BT_ATT_MAX_VALUE_LEN 512 + +#define BT_ATT_LINK_BREDR 0x00 +#define BT_ATT_LINK_LE 0x01 +#define BT_ATT_LINK_LOCAL 0xff + +/* ATT protocol opcodes */ +#define BT_ATT_OP_ERROR_RSP 0x01 +#define BT_ATT_OP_MTU_REQ 0x02 +#define BT_ATT_OP_MTU_RSP 0x03 +#define BT_ATT_OP_FIND_INFO_REQ 0x04 +#define BT_ATT_OP_FIND_INFO_RSP 0x05 +#define BT_ATT_OP_FIND_BY_TYPE_REQ 0x06 +#define BT_ATT_OP_FIND_BY_TYPE_RSP 0x07 +#define BT_ATT_OP_READ_BY_TYPE_REQ 0x08 +#define BT_ATT_OP_READ_BY_TYPE_RSP 0x09 +#define BT_ATT_OP_READ_REQ 0x0a +#define BT_ATT_OP_READ_RSP 0x0b +#define BT_ATT_OP_READ_BLOB_REQ 0x0c +#define BT_ATT_OP_READ_BLOB_RSP 0x0d +#define BT_ATT_OP_READ_MULT_REQ 0x0e +#define BT_ATT_OP_READ_MULT_RSP 0x0f +#define BT_ATT_OP_READ_BY_GRP_TYPE_REQ 0x10 +#define BT_ATT_OP_READ_BY_GRP_TYPE_RSP 0x11 +#define BT_ATT_OP_WRITE_REQ 0x12 +#define BT_ATT_OP_WRITE_RSP 0x13 +#define BT_ATT_OP_WRITE_CMD 0x52 +#define BT_ATT_OP_SIGNED_WRITE_CMD 0xD2 +#define BT_ATT_OP_PREP_WRITE_REQ 0x16 +#define BT_ATT_OP_PREP_WRITE_RSP 0x17 +#define BT_ATT_OP_EXEC_WRITE_REQ 0x18 +#define BT_ATT_OP_EXEC_WRITE_RSP 0x19 +#define BT_ATT_OP_HANDLE_VAL_NOT 0x1B +#define BT_ATT_OP_HANDLE_VAL_IND 0x1D +#define BT_ATT_OP_HANDLE_VAL_CONF 0x1E + +/* Packed struct definitions for ATT protocol PDUs */ +/* TODO: Complete these definitions for all opcodes */ +struct bt_att_pdu_error_rsp { + uint8_t opcode; + uint16_t handle; + uint8_t ecode; +} __packed; + +/* Special opcode to receive all requests (legacy servers) */ +#define BT_ATT_ALL_REQUESTS 0x00 + +/* Error codes for Error response PDU */ +#define BT_ATT_ERROR_INVALID_HANDLE 0x01 +#define BT_ATT_ERROR_READ_NOT_PERMITTED 0x02 +#define BT_ATT_ERROR_WRITE_NOT_PERMITTED 0x03 +#define BT_ATT_ERROR_INVALID_PDU 0x04 +#define BT_ATT_ERROR_AUTHENTICATION 0x05 +#define BT_ATT_ERROR_REQUEST_NOT_SUPPORTED 0x06 +#define BT_ATT_ERROR_INVALID_OFFSET 0x07 +#define BT_ATT_ERROR_AUTHORIZATION 0x08 +#define BT_ATT_ERROR_PREPARE_QUEUE_FULL 0x09 +#define BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND 0x0A +#define BT_ATT_ERROR_ATTRIBUTE_NOT_LONG 0x0B +#define BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE 0x0C +#define BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN 0x0D +#define BT_ATT_ERROR_UNLIKELY 0x0E +#define BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION 0x0F +#define BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE 0x10 +#define BT_ATT_ERROR_INSUFFICIENT_RESOURCES 0x11 +#define BT_ATT_ERROR_DB_OUT_OF_SYNC 0x12 +#define BT_ATT_ERROR_VALUE_NOT_ALLOWED 0x13 + +/* + * Common Profile and Service Error Code descriptions (see Supplement to the + * Bluetooth Core Specification, sections 1.2 and 2). The error codes within + * 0xE0-0xFC are reserved for future use. The remaining 3 are defined as the + * following: + */ +#define BT_ERROR_CCC_IMPROPERLY_CONFIGURED 0xfd +#define BT_ERROR_ALREADY_IN_PROGRESS 0xfe +#define BT_ERROR_OUT_OF_RANGE 0xff + +/* + * ATT attribute permission bitfield values. Permissions are grouped as + * "Access", "Encryption", "Authentication", and "Authorization". A bitmask of + * permissions is a byte that encodes a combination of these. + */ +#define BT_ATT_PERM_READ 0x01 +#define BT_ATT_PERM_WRITE 0x02 +#define BT_ATT_PERM_READ_ENCRYPT 0x04 +#define BT_ATT_PERM_WRITE_ENCRYPT 0x08 +#define BT_ATT_PERM_ENCRYPT (BT_ATT_PERM_READ_ENCRYPT | \ + BT_ATT_PERM_WRITE_ENCRYPT) +#define BT_ATT_PERM_READ_AUTHEN 0x10 +#define BT_ATT_PERM_WRITE_AUTHEN 0x20 +#define BT_ATT_PERM_AUTHEN (BT_ATT_PERM_READ_AUTHEN | \ + BT_ATT_PERM_WRITE_AUTHEN) +#define BT_ATT_PERM_AUTHOR 0x40 +#define BT_ATT_PERM_NONE 0x80 +#define BT_ATT_PERM_READ_SECURE 0x0100 +#define BT_ATT_PERM_WRITE_SECURE 0x0200 +#define BT_ATT_PERM_SECURE (BT_ATT_PERM_READ_SECURE | \ + BT_ATT_PERM_WRITE_SECURE) + +/* GATT Characteristic Properties Bitfield values */ +#define BT_GATT_CHRC_PROP_BROADCAST 0x01 +#define BT_GATT_CHRC_PROP_READ 0x02 +#define BT_GATT_CHRC_PROP_WRITE_WITHOUT_RESP 0x04 +#define BT_GATT_CHRC_PROP_WRITE 0x08 +#define BT_GATT_CHRC_PROP_NOTIFY 0x10 +#define BT_GATT_CHRC_PROP_INDICATE 0x20 +#define BT_GATT_CHRC_PROP_AUTH 0x40 +#define BT_GATT_CHRC_PROP_EXT_PROP 0x80 + +/* GATT Characteristic Extended Properties Bitfield values */ +#define BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE 0x01 +#define BT_GATT_CHRC_EXT_PROP_WRITABLE_AUX 0x02 +#define BT_GATT_CHRC_EXT_PROP_ENC_READ 0x04 +#define BT_GATT_CHRC_EXT_PROP_ENC_WRITE 0x08 +#define BT_GATT_CHRC_EXT_PROP_ENC (BT_GATT_CHRC_EXT_PROP_ENC_READ | \ + BT_GATT_CHRC_EXT_PROP_ENC_WRITE) +#define BT_GATT_CHRC_EXT_PROP_AUTH_READ 0x10 +#define BT_GATT_CHRC_EXT_PROP_AUTH_WRITE 0x20 +#define BT_GATT_CHRC_EXT_PROP_AUTH (BT_GATT_CHRC_EXT_PROP_AUTH_READ | \ + BT_GATT_CHRC_EXT_PROP_AUTH_WRITE) + +/* GATT Characteristic Client Features Bitfield values */ +#define BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING 0x01 diff --git a/src/shared/att.c b/src/shared/att.c new file mode 100644 index 0000000..0ea6d55 --- /dev/null +++ b/src/shared/att.c @@ -0,0 +1,1570 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "src/shared/io.h" +#include "src/shared/queue.h" +#include "src/shared/util.h" +#include "src/shared/timeout.h" +#include "lib/bluetooth.h" +#include "lib/l2cap.h" +#include "lib/uuid.h" +#include "src/shared/att.h" +#include "src/shared/crypto.h" + +#define ATT_MIN_PDU_LEN 1 /* At least 1 byte for the opcode. */ +#define ATT_OP_CMD_MASK 0x40 +#define ATT_OP_SIGNED_MASK 0x80 +#define ATT_TIMEOUT_INTERVAL 30000 /* 30000 ms */ + +/* Length of signature in write signed packet */ +#define BT_ATT_SIGNATURE_LEN 12 + +struct att_send_op; + +struct bt_att { + int ref_count; + int fd; + struct io *io; + bool io_on_l2cap; + int io_sec_level; /* Only used for non-L2CAP */ + uint8_t enc_size; + + struct queue *req_queue; /* Queued ATT protocol requests */ + struct att_send_op *pending_req; + struct queue *ind_queue; /* Queued ATT protocol indications */ + struct att_send_op *pending_ind; + struct queue *write_queue; /* Queue of PDUs ready to send */ + bool writer_active; + + struct queue *notify_list; /* List of registered callbacks */ + struct queue *disconn_list; /* List of disconnect handlers */ + + bool in_req; /* There's a pending incoming request */ + + uint8_t *buf; + uint16_t mtu; + + unsigned int next_send_id; /* IDs for "send" ops */ + unsigned int next_reg_id; /* IDs for registered callbacks */ + + bt_att_timeout_func_t timeout_callback; + bt_att_destroy_func_t timeout_destroy; + void *timeout_data; + + bt_att_debug_func_t debug_callback; + bt_att_destroy_func_t debug_destroy; + void *debug_data; + + struct bt_crypto *crypto; + + struct sign_info *local_sign; + struct sign_info *remote_sign; +}; + +struct sign_info { + uint8_t key[16]; + bt_att_counter_func_t counter; + void *user_data; +}; + +enum att_op_type { + ATT_OP_TYPE_REQ, + ATT_OP_TYPE_RSP, + ATT_OP_TYPE_CMD, + ATT_OP_TYPE_IND, + ATT_OP_TYPE_NOT, + ATT_OP_TYPE_CONF, + ATT_OP_TYPE_UNKNOWN, +}; + +static const struct { + uint8_t opcode; + enum att_op_type type; +} att_opcode_type_table[] = { + { BT_ATT_OP_ERROR_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_MTU_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_MTU_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_FIND_INFO_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_FIND_INFO_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_FIND_BY_TYPE_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_FIND_BY_TYPE_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_READ_BY_TYPE_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_READ_BY_TYPE_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_READ_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_READ_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_READ_BLOB_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_READ_BLOB_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_READ_MULT_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_READ_MULT_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_READ_BY_GRP_TYPE_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_READ_BY_GRP_TYPE_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_WRITE_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_WRITE_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_WRITE_CMD, ATT_OP_TYPE_CMD }, + { BT_ATT_OP_SIGNED_WRITE_CMD, ATT_OP_TYPE_CMD }, + { BT_ATT_OP_PREP_WRITE_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_PREP_WRITE_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_EXEC_WRITE_REQ, ATT_OP_TYPE_REQ }, + { BT_ATT_OP_EXEC_WRITE_RSP, ATT_OP_TYPE_RSP }, + { BT_ATT_OP_HANDLE_VAL_NOT, ATT_OP_TYPE_NOT }, + { BT_ATT_OP_HANDLE_VAL_IND, ATT_OP_TYPE_IND }, + { BT_ATT_OP_HANDLE_VAL_CONF, ATT_OP_TYPE_CONF }, + { } +}; + +static enum att_op_type get_op_type(uint8_t opcode) +{ + int i; + + for (i = 0; att_opcode_type_table[i].opcode; i++) { + if (att_opcode_type_table[i].opcode == opcode) + return att_opcode_type_table[i].type; + } + + if (opcode & ATT_OP_CMD_MASK) + return ATT_OP_TYPE_CMD; + + return ATT_OP_TYPE_UNKNOWN; +} + +static const struct { + uint8_t req_opcode; + uint8_t rsp_opcode; +} att_req_rsp_mapping_table[] = { + { BT_ATT_OP_MTU_REQ, BT_ATT_OP_MTU_RSP }, + { BT_ATT_OP_FIND_INFO_REQ, BT_ATT_OP_FIND_INFO_RSP}, + { BT_ATT_OP_FIND_BY_TYPE_REQ, BT_ATT_OP_FIND_BY_TYPE_RSP }, + { BT_ATT_OP_READ_BY_TYPE_REQ, BT_ATT_OP_READ_BY_TYPE_RSP }, + { BT_ATT_OP_READ_REQ, BT_ATT_OP_READ_RSP }, + { BT_ATT_OP_READ_BLOB_REQ, BT_ATT_OP_READ_BLOB_RSP }, + { BT_ATT_OP_READ_MULT_REQ, BT_ATT_OP_READ_MULT_RSP }, + { BT_ATT_OP_READ_BY_GRP_TYPE_REQ, BT_ATT_OP_READ_BY_GRP_TYPE_RSP }, + { BT_ATT_OP_WRITE_REQ, BT_ATT_OP_WRITE_RSP }, + { BT_ATT_OP_PREP_WRITE_REQ, BT_ATT_OP_PREP_WRITE_RSP }, + { BT_ATT_OP_EXEC_WRITE_REQ, BT_ATT_OP_EXEC_WRITE_RSP }, + { } +}; + +static uint8_t get_req_opcode(uint8_t rsp_opcode) +{ + int i; + + for (i = 0; att_req_rsp_mapping_table[i].rsp_opcode; i++) { + if (att_req_rsp_mapping_table[i].rsp_opcode == rsp_opcode) + return att_req_rsp_mapping_table[i].req_opcode; + } + + return 0; +} + +struct att_send_op { + unsigned int id; + unsigned int timeout_id; + enum att_op_type type; + uint8_t opcode; + void *pdu; + uint16_t len; + bt_att_response_func_t callback; + bt_att_destroy_func_t destroy; + void *user_data; +}; + +static void destroy_att_send_op(void *data) +{ + struct att_send_op *op = data; + + if (op->timeout_id) + timeout_remove(op->timeout_id); + + if (op->destroy) + op->destroy(op->user_data); + + free(op->pdu); + free(op); +} + +static void cancel_att_send_op(struct att_send_op *op) +{ + if (op->destroy) + op->destroy(op->user_data); + + op->user_data = NULL; + op->callback = NULL; + op->destroy = NULL; +} + +struct att_notify { + unsigned int id; + uint16_t opcode; + bt_att_notify_func_t callback; + bt_att_destroy_func_t destroy; + void *user_data; +}; + +static void destroy_att_notify(void *data) +{ + struct att_notify *notify = data; + + if (notify->destroy) + notify->destroy(notify->user_data); + + free(notify); +} + +static bool match_notify_id(const void *a, const void *b) +{ + const struct att_notify *notify = a; + unsigned int id = PTR_TO_UINT(b); + + return notify->id == id; +} + +struct att_disconn { + unsigned int id; + bool removed; + bt_att_disconnect_func_t callback; + bt_att_destroy_func_t destroy; + void *user_data; +}; + +static void destroy_att_disconn(void *data) +{ + struct att_disconn *disconn = data; + + if (disconn->destroy) + disconn->destroy(disconn->user_data); + + free(disconn); +} + +static bool match_disconn_id(const void *a, const void *b) +{ + const struct att_disconn *disconn = a; + unsigned int id = PTR_TO_UINT(b); + + return disconn->id == id; +} + +static bool encode_pdu(struct bt_att *att, struct att_send_op *op, + const void *pdu, uint16_t length) +{ + uint16_t pdu_len = 1; + struct sign_info *sign = att->local_sign; + uint32_t sign_cnt; + + if (sign && (op->opcode & ATT_OP_SIGNED_MASK)) + pdu_len += BT_ATT_SIGNATURE_LEN; + + if (length && pdu) + pdu_len += length; + + if (pdu_len > att->mtu) + return false; + + op->len = pdu_len; + op->pdu = malloc(op->len); + if (!op->pdu) + return false; + + ((uint8_t *) op->pdu)[0] = op->opcode; + if (pdu_len > 1) + memcpy(op->pdu + 1, pdu, length); + + if (!sign || !(op->opcode & ATT_OP_SIGNED_MASK) || !att->crypto) + return true; + + if (!sign->counter(&sign_cnt, sign->user_data)) + goto fail; + + if ((bt_crypto_sign_att(att->crypto, sign->key, op->pdu, 1 + length, + sign_cnt, &((uint8_t *) op->pdu)[1 + length]))) + return true; + + util_debug(att->debug_callback, att->debug_data, + "ATT unable to generate signature"); + +fail: + free(op->pdu); + return false; +} + +static struct att_send_op *create_att_send_op(struct bt_att *att, + uint8_t opcode, + const void *pdu, + uint16_t length, + bt_att_response_func_t callback, + void *user_data, + bt_att_destroy_func_t destroy) +{ + struct att_send_op *op; + enum att_op_type type; + + if (length && !pdu) + return NULL; + + type = get_op_type(opcode); + if (type == ATT_OP_TYPE_UNKNOWN) + return NULL; + + /* If the opcode corresponds to an operation type that does not elicit a + * response from the remote end, then no callback should have been + * provided, since it will never be called. + */ + if (callback && type != ATT_OP_TYPE_REQ && type != ATT_OP_TYPE_IND) + return NULL; + + /* Similarly, if the operation does elicit a response then a callback + * must be provided. + */ + if (!callback && (type == ATT_OP_TYPE_REQ || type == ATT_OP_TYPE_IND)) + return NULL; + + op = new0(struct att_send_op, 1); + op->type = type; + op->opcode = opcode; + op->callback = callback; + op->destroy = destroy; + op->user_data = user_data; + + if (!encode_pdu(att, op, pdu, length)) { + free(op); + return NULL; + } + + return op; +} + +static struct att_send_op *pick_next_send_op(struct bt_att *att) +{ + struct att_send_op *op; + + /* See if any operations are already in the write queue */ + op = queue_pop_head(att->write_queue); + if (op) + return op; + + /* If there is no pending request, pick an operation from the + * request queue. + */ + if (!att->pending_req) { + op = queue_pop_head(att->req_queue); + if (op) + return op; + } + + /* There is either a request pending or no requests queued. If there is + * no pending indication, pick an operation from the indication queue. + */ + if (!att->pending_ind) { + op = queue_pop_head(att->ind_queue); + if (op) + return op; + } + + return NULL; +} + +struct timeout_data { + struct bt_att *att; + unsigned int id; +}; + +static bool timeout_cb(void *user_data) +{ + struct timeout_data *timeout = user_data; + struct bt_att *att = timeout->att; + struct att_send_op *op = NULL; + + if (att->pending_req && att->pending_req->id == timeout->id) { + op = att->pending_req; + att->pending_req = NULL; + } else if (att->pending_ind && att->pending_ind->id == timeout->id) { + op = att->pending_ind; + att->pending_ind = NULL; + } + + if (!op) + return false; + + util_debug(att->debug_callback, att->debug_data, + "Operation timed out: 0x%02x", op->opcode); + + if (att->timeout_callback) + att->timeout_callback(op->id, op->opcode, att->timeout_data); + + op->timeout_id = 0; + destroy_att_send_op(op); + + /* + * Directly terminate the connection as required by the ATT protocol. + * This should trigger an io disconnect event which will clean up the + * io and notify the upper layer. + */ + io_shutdown(att->io); + + return false; +} + +static void write_watch_destroy(void *user_data) +{ + struct bt_att *att = user_data; + + att->writer_active = false; +} + +static bool can_write_data(struct io *io, void *user_data) +{ + struct bt_att *att = user_data; + struct att_send_op *op; + struct timeout_data *timeout; + ssize_t ret; + struct iovec iov; + + op = pick_next_send_op(att); + if (!op) + return false; + + iov.iov_base = op->pdu; + iov.iov_len = op->len; + + ret = io_send(io, &iov, 1); + if (ret < 0) { + util_debug(att->debug_callback, att->debug_data, + "write failed: %s", strerror(-ret)); + if (op->callback) + op->callback(BT_ATT_OP_ERROR_RSP, NULL, 0, + op->user_data); + + destroy_att_send_op(op); + return true; + } + + util_debug(att->debug_callback, att->debug_data, + "ATT op 0x%02x", op->opcode); + + util_hexdump('<', op->pdu, ret, att->debug_callback, att->debug_data); + + /* Based on the operation type, set either the pending request or the + * pending indication. If it came from the write queue, then there is + * no need to keep it around. + */ + switch (op->type) { + case ATT_OP_TYPE_REQ: + att->pending_req = op; + break; + case ATT_OP_TYPE_IND: + att->pending_ind = op; + break; + case ATT_OP_TYPE_RSP: + /* Set in_req to false to indicate that no request is pending */ + att->in_req = false; + /* fall through */ + case ATT_OP_TYPE_CMD: + case ATT_OP_TYPE_NOT: + case ATT_OP_TYPE_CONF: + case ATT_OP_TYPE_UNKNOWN: + default: + destroy_att_send_op(op); + return true; + } + + timeout = new0(struct timeout_data, 1); + timeout->att = att; + timeout->id = op->id; + op->timeout_id = timeout_add(ATT_TIMEOUT_INTERVAL, timeout_cb, + timeout, free); + + /* Return true as there may be more operations ready to write. */ + return true; +} + +static void wakeup_writer(struct bt_att *att) +{ + if (att->writer_active) + return; + + /* Set the write handler only if there is anything that can be sent + * at all. + */ + if (queue_isempty(att->write_queue)) { + if ((att->pending_req || queue_isempty(att->req_queue)) && + (att->pending_ind || queue_isempty(att->ind_queue))) + return; + } + + if (!io_set_write_handler(att->io, can_write_data, att, + write_watch_destroy)) + return; + + att->writer_active = true; +} + +static void disconn_handler(void *data, void *user_data) +{ + struct att_disconn *disconn = data; + int err = PTR_TO_INT(user_data); + + if (disconn->removed) + return; + + if (disconn->callback) + disconn->callback(err, disconn->user_data); +} + +static void disc_att_send_op(void *data) +{ + struct att_send_op *op = data; + + if (op->callback) + op->callback(BT_ATT_OP_ERROR_RSP, NULL, 0, op->user_data); + + destroy_att_send_op(op); +} + +static bool disconnect_cb(struct io *io, void *user_data) +{ + struct bt_att *att = user_data; + int err; + socklen_t len; + + len = sizeof(err); + + if (getsockopt(att->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { + util_debug(att->debug_callback, att->debug_data, + "Failed to obtain disconnect error: %s", + strerror(errno)); + err = 0; + } + + util_debug(att->debug_callback, att->debug_data, + "Physical link disconnected: %s", + strerror(err)); + + io_destroy(att->io); + att->io = NULL; + att->fd = -1; + + /* Notify request callbacks */ + queue_remove_all(att->req_queue, NULL, NULL, disc_att_send_op); + queue_remove_all(att->ind_queue, NULL, NULL, disc_att_send_op); + queue_remove_all(att->write_queue, NULL, NULL, disc_att_send_op); + + if (att->pending_req) { + disc_att_send_op(att->pending_req); + att->pending_req = NULL; + } + + if (att->pending_ind) { + disc_att_send_op(att->pending_ind); + att->pending_ind = NULL; + } + + bt_att_ref(att); + + queue_foreach(att->disconn_list, disconn_handler, INT_TO_PTR(err)); + + bt_att_unregister_all(att); + bt_att_unref(att); + + return false; +} + +static bool change_security(struct bt_att *att, uint8_t ecode) +{ + int security; + + if (att->io_sec_level != BT_ATT_SECURITY_AUTO) + return false; + + security = bt_att_get_security(att, NULL); + + if (ecode == BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION && + security < BT_ATT_SECURITY_MEDIUM) { + security = BT_ATT_SECURITY_MEDIUM; + } else if (ecode == BT_ATT_ERROR_AUTHENTICATION) { + if (security < BT_ATT_SECURITY_MEDIUM) + security = BT_ATT_SECURITY_MEDIUM; + else if (security < BT_ATT_SECURITY_HIGH) + security = BT_ATT_SECURITY_HIGH; + else if (security < BT_ATT_SECURITY_FIPS) + security = BT_ATT_SECURITY_FIPS; + else + return false; + } else { + return false; + } + + return bt_att_set_security(att, security); +} + +static bool handle_error_rsp(struct bt_att *att, uint8_t *pdu, + ssize_t pdu_len, uint8_t *opcode) +{ + const struct bt_att_pdu_error_rsp *rsp; + struct att_send_op *op = att->pending_req; + + if (pdu_len != sizeof(*rsp)) { + *opcode = 0; + return false; + } + + rsp = (void *) pdu; + + *opcode = rsp->opcode; + + /* Attempt to change security */ + if (!change_security(att, rsp->ecode)) + return false; + + /* Remove timeout_id if outstanding */ + if (op->timeout_id) { + timeout_remove(op->timeout_id); + op->timeout_id = 0; + } + + util_debug(att->debug_callback, att->debug_data, + "Retrying operation %p", op); + + att->pending_req = NULL; + + /* Push operation back to request queue */ + return queue_push_head(att->req_queue, op); +} + +static void handle_rsp(struct bt_att *att, uint8_t opcode, uint8_t *pdu, + ssize_t pdu_len) +{ + struct att_send_op *op = att->pending_req; + uint8_t req_opcode; + uint8_t rsp_opcode; + uint8_t *rsp_pdu = NULL; + uint16_t rsp_pdu_len = 0; + + /* + * If no request is pending, then the response is unexpected. Disconnect + * the bearer. + */ + if (!op) { + util_debug(att->debug_callback, att->debug_data, + "Received unexpected ATT response"); + io_shutdown(att->io); + return; + } + + /* + * If the received response doesn't match the pending request, or if + * the request is malformed, end the current request with failure. + */ + if (opcode == BT_ATT_OP_ERROR_RSP) { + /* Return if error response cause a retry */ + if (handle_error_rsp(att, pdu, pdu_len, &req_opcode)) { + wakeup_writer(att); + return; + } + } else if (!(req_opcode = get_req_opcode(opcode))) + goto fail; + + if (req_opcode != op->opcode) + goto fail; + + rsp_opcode = opcode; + + if (pdu_len > 0) { + rsp_pdu = pdu; + rsp_pdu_len = pdu_len; + } + + goto done; + +fail: + util_debug(att->debug_callback, att->debug_data, + "Failed to handle response PDU; opcode: 0x%02x", opcode); + + rsp_opcode = BT_ATT_OP_ERROR_RSP; + +done: + if (op->callback) + op->callback(rsp_opcode, rsp_pdu, rsp_pdu_len, op->user_data); + + destroy_att_send_op(op); + att->pending_req = NULL; + + wakeup_writer(att); +} + +static void handle_conf(struct bt_att *att, uint8_t *pdu, ssize_t pdu_len) +{ + struct att_send_op *op = att->pending_ind; + + /* + * Disconnect the bearer if the confirmation is unexpected or the PDU is + * invalid. + */ + if (!op || pdu_len) { + util_debug(att->debug_callback, att->debug_data, + "Received unexpected/invalid ATT confirmation"); + io_shutdown(att->io); + return; + } + + if (op->callback) + op->callback(BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0, op->user_data); + + destroy_att_send_op(op); + att->pending_ind = NULL; + + wakeup_writer(att); +} + +struct notify_data { + uint8_t opcode; + uint8_t *pdu; + ssize_t pdu_len; + bool handler_found; +}; + +static bool opcode_match(uint8_t opcode, uint8_t test_opcode) +{ + enum att_op_type op_type = get_op_type(test_opcode); + + if (opcode == BT_ATT_ALL_REQUESTS && (op_type == ATT_OP_TYPE_REQ || + op_type == ATT_OP_TYPE_CMD)) + return true; + + return opcode == test_opcode; +} + +static void respond_not_supported(struct bt_att *att, uint8_t opcode) +{ + struct bt_att_pdu_error_rsp pdu; + + pdu.opcode = opcode; + pdu.handle = 0x0000; + pdu.ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; + + bt_att_send(att, BT_ATT_OP_ERROR_RSP, &pdu, sizeof(pdu), NULL, NULL, + NULL); +} + +static bool handle_signed(struct bt_att *att, uint8_t opcode, uint8_t *pdu, + ssize_t pdu_len) +{ + uint8_t *signature; + uint32_t sign_cnt; + struct sign_info *sign; + + /* Check if there is enough data for a signature */ + if (pdu_len < 2 + BT_ATT_SIGNATURE_LEN) + goto fail; + + sign = att->remote_sign; + if (!sign) + goto fail; + + signature = pdu + (pdu_len - BT_ATT_SIGNATURE_LEN); + sign_cnt = get_le32(signature); + + /* Validate counter */ + if (!sign->counter(&sign_cnt, sign->user_data)) + goto fail; + + /* Generate signature and verify it */ + if (!bt_crypto_sign_att(att->crypto, sign->key, pdu, + pdu_len - BT_ATT_SIGNATURE_LEN, sign_cnt, + signature)) + goto fail; + + return true; + +fail: + util_debug(att->debug_callback, att->debug_data, + "ATT failed to verify signature: 0x%02x", opcode); + + return false; +} + +static void handle_notify(struct bt_att *att, uint8_t opcode, uint8_t *pdu, + ssize_t pdu_len) +{ + const struct queue_entry *entry; + bool found; + + if ((opcode & ATT_OP_SIGNED_MASK) && !att->crypto) { + if (!handle_signed(att, opcode, pdu, pdu_len)) + return; + pdu_len -= BT_ATT_SIGNATURE_LEN; + } + + bt_att_ref(att); + + found = false; + entry = queue_get_entries(att->notify_list); + + while (entry) { + struct att_notify *notify = entry->data; + + entry = entry->next; + + if (!opcode_match(notify->opcode, opcode)) + continue; + + /* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G + * page 2370 + * + * 4.3.1 Exchange MTU + * + * This sub-procedure shall not be used on a BR/EDR physical + * link since the MTU size is negotiated using L2CAP channel + * configuration procedures. + */ + if (bt_att_get_link_type(att) == BT_ATT_LINK_BREDR) { + switch (opcode) { + case BT_ATT_OP_MTU_REQ: + goto not_supported; + } + } + + found = true; + + if (notify->callback) + notify->callback(opcode, pdu, pdu_len, + notify->user_data); + + /* callback could remove all entries from notify list */ + if (queue_isempty(att->notify_list)) + break; + } + +not_supported: + /* + * If this was not a command and no handler was registered for it, + * respond with "Not Supported" + */ + if (!found && get_op_type(opcode) != ATT_OP_TYPE_CMD) + respond_not_supported(att, opcode); + + bt_att_unref(att); +} + +static bool can_read_data(struct io *io, void *user_data) +{ + struct bt_att *att = user_data; + uint8_t opcode; + uint8_t *pdu; + ssize_t bytes_read; + + bytes_read = read(att->fd, att->buf, att->mtu); + if (bytes_read < 0) + return false; + + util_hexdump('>', att->buf, bytes_read, + att->debug_callback, att->debug_data); + + if (bytes_read < ATT_MIN_PDU_LEN) + return true; + + pdu = att->buf; + opcode = pdu[0]; + + bt_att_ref(att); + + /* Act on the received PDU based on the opcode type */ + switch (get_op_type(opcode)) { + case ATT_OP_TYPE_RSP: + util_debug(att->debug_callback, att->debug_data, + "ATT response received: 0x%02x", opcode); + handle_rsp(att, opcode, pdu + 1, bytes_read - 1); + break; + case ATT_OP_TYPE_CONF: + util_debug(att->debug_callback, att->debug_data, + "ATT confirmation received: 0x%02x", opcode); + handle_conf(att, pdu + 1, bytes_read - 1); + break; + case ATT_OP_TYPE_REQ: + /* + * If a request is currently pending, then the sequential + * protocol was violated. Disconnect the bearer, which will + * promptly notify the upper layer via disconnect handlers. + */ + if (att->in_req) { + util_debug(att->debug_callback, att->debug_data, + "Received request while another is " + "pending: 0x%02x", opcode); + io_shutdown(att->io); + bt_att_unref(att); + + return false; + } + + att->in_req = true; + /* fall through */ + case ATT_OP_TYPE_CMD: + case ATT_OP_TYPE_NOT: + case ATT_OP_TYPE_UNKNOWN: + case ATT_OP_TYPE_IND: + /* fall through */ + default: + /* For all other opcodes notify the upper layer of the PDU and + * let them act on it. + */ + util_debug(att->debug_callback, att->debug_data, + "ATT PDU received: 0x%02x", opcode); + handle_notify(att, opcode, pdu + 1, bytes_read - 1); + break; + } + + bt_att_unref(att); + + return true; +} + +static bool is_io_l2cap_based(int fd) +{ + int domain; + int proto; + int err; + socklen_t len; + + domain = 0; + len = sizeof(domain); + err = getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &domain, &len); + if (err < 0) + return false; + + if (domain != AF_BLUETOOTH) + return false; + + proto = 0; + len = sizeof(proto); + err = getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &proto, &len); + if (err < 0) + return false; + + return proto == BTPROTO_L2CAP; +} + +static void bt_att_free(struct bt_att *att) +{ + if (att->pending_req) + destroy_att_send_op(att->pending_req); + + if (att->pending_ind) + destroy_att_send_op(att->pending_ind); + + io_destroy(att->io); + bt_crypto_unref(att->crypto); + + queue_destroy(att->req_queue, NULL); + queue_destroy(att->ind_queue, NULL); + queue_destroy(att->write_queue, NULL); + queue_destroy(att->notify_list, NULL); + queue_destroy(att->disconn_list, NULL); + + if (att->timeout_destroy) + att->timeout_destroy(att->timeout_data); + + if (att->debug_destroy) + att->debug_destroy(att->debug_data); + + free(att->local_sign); + free(att->remote_sign); + + free(att->buf); + + free(att); +} + +static uint16_t get_l2cap_mtu(int fd) +{ + socklen_t len; + struct l2cap_options l2o; + + len = sizeof(l2o); + if (getsockopt(fd, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) + return 0; + + return l2o.omtu; +} + +struct bt_att *bt_att_new(int fd, bool ext_signed) +{ + struct bt_att *att; + + if (fd < 0) + return NULL; + + att = new0(struct bt_att, 1); + att->fd = fd; + + att->io = io_new(fd); + if (!att->io) + goto fail; + + /* crypto is optional, if not available leave it NULL */ + if (!ext_signed) + att->crypto = bt_crypto_new(); + + att->req_queue = queue_new(); + att->ind_queue = queue_new(); + att->write_queue = queue_new(); + att->notify_list = queue_new(); + att->disconn_list = queue_new(); + + if (!io_set_read_handler(att->io, can_read_data, att, NULL)) + goto fail; + + if (!io_set_disconnect_handler(att->io, disconnect_cb, att, NULL)) + goto fail; + + att->io_on_l2cap = is_io_l2cap_based(att->fd); + if (!att->io_on_l2cap) + att->io_sec_level = BT_ATT_SECURITY_LOW; + + if (bt_att_get_link_type(att) == BT_ATT_LINK_BREDR) + att->mtu = get_l2cap_mtu(att->fd); + else + att->mtu = BT_ATT_DEFAULT_LE_MTU; + + if (att->mtu < BT_ATT_DEFAULT_LE_MTU) + goto fail; + + att->buf = malloc(att->mtu); + if (!att->buf) + goto fail; + + return bt_att_ref(att); + +fail: + bt_att_free(att); + + return NULL; +} + +struct bt_att *bt_att_ref(struct bt_att *att) +{ + if (!att) + return NULL; + + __sync_fetch_and_add(&att->ref_count, 1); + + return att; +} + +void bt_att_unref(struct bt_att *att) +{ + if (!att) + return; + + if (__sync_sub_and_fetch(&att->ref_count, 1)) + return; + + bt_att_unregister_all(att); + bt_att_cancel_all(att); + + bt_att_free(att); +} + +bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close) +{ + if (!att || !att->io) + return false; + + return io_set_close_on_destroy(att->io, do_close); +} + +int bt_att_get_fd(struct bt_att *att) +{ + if (!att) + return -1; + + return att->fd; +} + +bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback, + void *user_data, bt_att_destroy_func_t destroy) +{ + if (!att) + return false; + + if (att->debug_destroy) + att->debug_destroy(att->debug_data); + + att->debug_callback = callback; + att->debug_destroy = destroy; + att->debug_data = user_data; + + return true; +} + +uint16_t bt_att_get_mtu(struct bt_att *att) +{ + if (!att) + return 0; + + return att->mtu; +} + +bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu) +{ + void *buf; + + if (!att) + return false; + + if (mtu < BT_ATT_DEFAULT_LE_MTU) + return false; + + buf = malloc(mtu); + if (!buf) + return false; + + free(att->buf); + + att->mtu = mtu; + att->buf = buf; + + return true; +} + +uint8_t bt_att_get_link_type(struct bt_att *att) +{ + struct sockaddr_l2 src; + socklen_t len; + + if (!att) + return -EINVAL; + + if (!att->io_on_l2cap) + return BT_ATT_LINK_LOCAL; + + len = sizeof(src); + memset(&src, 0, len); + if (getsockname(att->fd, (void *)&src, &len) < 0) + return -errno; + + if (src.l2_bdaddr_type == BDADDR_BREDR) + return BT_ATT_LINK_BREDR; + + return BT_ATT_LINK_LE; +} + +bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback, + void *user_data, + bt_att_destroy_func_t destroy) +{ + if (!att) + return false; + + if (att->timeout_destroy) + att->timeout_destroy(att->timeout_data); + + att->timeout_callback = callback; + att->timeout_destroy = destroy; + att->timeout_data = user_data; + + return true; +} + +unsigned int bt_att_register_disconnect(struct bt_att *att, + bt_att_disconnect_func_t callback, + void *user_data, + bt_att_destroy_func_t destroy) +{ + struct att_disconn *disconn; + + if (!att || !att->io) + return 0; + + disconn = new0(struct att_disconn, 1); + disconn->callback = callback; + disconn->destroy = destroy; + disconn->user_data = user_data; + + if (att->next_reg_id < 1) + att->next_reg_id = 1; + + disconn->id = att->next_reg_id++; + + if (!queue_push_tail(att->disconn_list, disconn)) { + free(disconn); + return 0; + } + + return disconn->id; +} + +bool bt_att_unregister_disconnect(struct bt_att *att, unsigned int id) +{ + struct att_disconn *disconn; + + if (!att || !id) + return false; + + /* Check if disconnect is running */ + if (!att->io) { + disconn = queue_find(att->disconn_list, match_disconn_id, + UINT_TO_PTR(id)); + if (!disconn) + return false; + + disconn->removed = true; + return true; + } + + disconn = queue_remove_if(att->disconn_list, match_disconn_id, + UINT_TO_PTR(id)); + if (!disconn) + return false; + + destroy_att_disconn(disconn); + return true; +} + +unsigned int bt_att_send(struct bt_att *att, uint8_t opcode, + const void *pdu, uint16_t length, + bt_att_response_func_t callback, void *user_data, + bt_att_destroy_func_t destroy) +{ + struct att_send_op *op; + bool result; + + if (!att || !att->io) + return 0; + + op = create_att_send_op(att, opcode, pdu, length, callback, user_data, + destroy); + if (!op) + return 0; + + if (att->next_send_id < 1) + att->next_send_id = 1; + + op->id = att->next_send_id++; + + /* Add the op to the correct queue based on its type */ + switch (op->type) { + case ATT_OP_TYPE_REQ: + result = queue_push_tail(att->req_queue, op); + break; + case ATT_OP_TYPE_IND: + result = queue_push_tail(att->ind_queue, op); + break; + case ATT_OP_TYPE_CMD: + case ATT_OP_TYPE_NOT: + case ATT_OP_TYPE_UNKNOWN: + case ATT_OP_TYPE_RSP: + case ATT_OP_TYPE_CONF: + default: + result = queue_push_tail(att->write_queue, op); + break; + } + + if (!result) { + free(op->pdu); + free(op); + return 0; + } + + wakeup_writer(att); + + return op->id; +} + +static bool match_op_id(const void *a, const void *b) +{ + const struct att_send_op *op = a; + unsigned int id = PTR_TO_UINT(b); + + return op->id == id; +} + +bool bt_att_cancel(struct bt_att *att, unsigned int id) +{ + struct att_send_op *op; + + if (!att || !id) + return false; + + if (att->pending_req && att->pending_req->id == id) { + /* Don't cancel the pending request; remove it's handlers */ + cancel_att_send_op(att->pending_req); + return true; + } + + if (att->pending_ind && att->pending_ind->id == id) { + /* Don't cancel the pending indication; remove it's handlers */ + cancel_att_send_op(att->pending_ind); + return true; + } + + op = queue_remove_if(att->req_queue, match_op_id, UINT_TO_PTR(id)); + if (op) + goto done; + + op = queue_remove_if(att->ind_queue, match_op_id, UINT_TO_PTR(id)); + if (op) + goto done; + + op = queue_remove_if(att->write_queue, match_op_id, UINT_TO_PTR(id)); + if (op) + goto done; + + if (!op) + return false; + +done: + destroy_att_send_op(op); + + wakeup_writer(att); + + return true; +} + +bool bt_att_cancel_all(struct bt_att *att) +{ + if (!att) + return false; + + queue_remove_all(att->req_queue, NULL, NULL, destroy_att_send_op); + queue_remove_all(att->ind_queue, NULL, NULL, destroy_att_send_op); + queue_remove_all(att->write_queue, NULL, NULL, destroy_att_send_op); + + if (att->pending_req) + /* Don't cancel the pending request; remove it's handlers */ + cancel_att_send_op(att->pending_req); + + if (att->pending_ind) + /* Don't cancel the pending request; remove it's handlers */ + cancel_att_send_op(att->pending_ind); + + return true; +} + +static uint8_t att_ecode_from_error(int err) +{ + /* + * If the error fits in a single byte, treat it as an ATT protocol + * error as is. Since "0" is not a valid ATT protocol error code, we map + * that to UNLIKELY below. + */ + if (err > 0 && err < UINT8_MAX) + return err; + + /* + * Since we allow UNIX errnos, map them to appropriate ATT protocol + * and "Common Profile and Service" error codes. + */ + switch (err) { + case -ENOENT: + return BT_ATT_ERROR_INVALID_HANDLE; + case -ENOMEM: + return BT_ATT_ERROR_INSUFFICIENT_RESOURCES; + case -EALREADY: + return BT_ERROR_ALREADY_IN_PROGRESS; + case -EOVERFLOW: + return BT_ERROR_OUT_OF_RANGE; + } + + return BT_ATT_ERROR_UNLIKELY; +} + +unsigned int bt_att_send_error_rsp(struct bt_att *att, uint8_t opcode, + uint16_t handle, int error) +{ + struct bt_att_pdu_error_rsp pdu; + uint8_t ecode; + + if (!att || !opcode) + return 0; + + ecode = att_ecode_from_error(error); + + memset(&pdu, 0, sizeof(pdu)); + + pdu.opcode = opcode; + put_le16(handle, &pdu.handle); + pdu.ecode = ecode; + + return bt_att_send(att, BT_ATT_OP_ERROR_RSP, &pdu, sizeof(pdu), + NULL, NULL, NULL); +} + +unsigned int bt_att_register(struct bt_att *att, uint8_t opcode, + bt_att_notify_func_t callback, + void *user_data, + bt_att_destroy_func_t destroy) +{ + struct att_notify *notify; + + if (!att || !callback || !att->io) + return 0; + + notify = new0(struct att_notify, 1); + notify->opcode = opcode; + notify->callback = callback; + notify->destroy = destroy; + notify->user_data = user_data; + + if (att->next_reg_id < 1) + att->next_reg_id = 1; + + notify->id = att->next_reg_id++; + + if (!queue_push_tail(att->notify_list, notify)) { + free(notify); + return 0; + } + + return notify->id; +} + +bool bt_att_unregister(struct bt_att *att, unsigned int id) +{ + struct att_notify *notify; + + if (!att || !id) + return false; + + notify = queue_remove_if(att->notify_list, match_notify_id, + UINT_TO_PTR(id)); + if (!notify) + return false; + + destroy_att_notify(notify); + return true; +} + +bool bt_att_unregister_all(struct bt_att *att) +{ + if (!att) + return false; + + queue_remove_all(att->notify_list, NULL, NULL, destroy_att_notify); + queue_remove_all(att->disconn_list, NULL, NULL, destroy_att_disconn); + + return true; +} + +int bt_att_get_security(struct bt_att *att, uint8_t *enc_size) +{ + struct bt_security sec; + socklen_t len; + + if (!att) + return -EINVAL; + + if (!att->io_on_l2cap) { + if (enc_size) + *enc_size = att->enc_size; + + return att->io_sec_level; + } + + memset(&sec, 0, sizeof(sec)); + len = sizeof(sec); + if (getsockopt(att->fd, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) < 0) + return -EIO; + + if (enc_size) + *enc_size = att->enc_size; + + return sec.level; +} + +bool bt_att_set_security(struct bt_att *att, int level) +{ + struct bt_security sec; + + if (!att || level < BT_ATT_SECURITY_AUTO || + level > BT_ATT_SECURITY_HIGH) + return false; + + if (!att->io_on_l2cap) { + att->io_sec_level = level; + return true; + } + + memset(&sec, 0, sizeof(sec)); + sec.level = level; + + if (setsockopt(att->fd, SOL_BLUETOOTH, BT_SECURITY, &sec, + sizeof(sec)) < 0) + return false; + + return true; +} + +void bt_att_set_enc_key_size(struct bt_att *att, uint8_t enc_size) +{ + if (!att) + return; + + att->enc_size = enc_size; +} + +static bool sign_set_key(struct sign_info **sign, uint8_t key[16], + bt_att_counter_func_t func, void *user_data) +{ + if (!(*sign)) + *sign = new0(struct sign_info, 1); + + (*sign)->counter = func; + (*sign)->user_data = user_data; + memcpy((*sign)->key, key, 16); + + return true; +} + +bool bt_att_set_local_key(struct bt_att *att, uint8_t sign_key[16], + bt_att_counter_func_t func, void *user_data) +{ + if (!att) + return false; + + return sign_set_key(&att->local_sign, sign_key, func, user_data); +} + +bool bt_att_set_remote_key(struct bt_att *att, uint8_t sign_key[16], + bt_att_counter_func_t func, void *user_data) +{ + if (!att) + return false; + + return sign_set_key(&att->remote_sign, sign_key, func, user_data); +} + +bool bt_att_has_crypto(struct bt_att *att) +{ + if (!att) + return false; + + return att->crypto ? true : false; +} diff --git a/src/shared/att.h b/src/shared/att.h new file mode 100644 index 0000000..49d9326 --- /dev/null +++ b/src/shared/att.h @@ -0,0 +1,95 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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 + * + */ + +#include +#include + +#include "src/shared/att-types.h" + +struct bt_att; + +struct bt_att *bt_att_new(int fd, bool ext_signed); + +struct bt_att *bt_att_ref(struct bt_att *att); +void bt_att_unref(struct bt_att *att); + +bool bt_att_set_close_on_unref(struct bt_att *att, bool do_close); + +int bt_att_get_fd(struct bt_att *att); + +typedef void (*bt_att_response_func_t)(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data); +typedef void (*bt_att_notify_func_t)(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data); +typedef void (*bt_att_destroy_func_t)(void *user_data); +typedef void (*bt_att_debug_func_t)(const char *str, void *user_data); +typedef void (*bt_att_timeout_func_t)(unsigned int id, uint8_t opcode, + void *user_data); +typedef void (*bt_att_disconnect_func_t)(int err, void *user_data); +typedef bool (*bt_att_counter_func_t)(uint32_t *sign_cnt, void *user_data); + +bool bt_att_set_debug(struct bt_att *att, bt_att_debug_func_t callback, + void *user_data, bt_att_destroy_func_t destroy); + +uint16_t bt_att_get_mtu(struct bt_att *att); +bool bt_att_set_mtu(struct bt_att *att, uint16_t mtu); +uint8_t bt_att_get_link_type(struct bt_att *att); + +bool bt_att_set_timeout_cb(struct bt_att *att, bt_att_timeout_func_t callback, + void *user_data, + bt_att_destroy_func_t destroy); + +unsigned int bt_att_send(struct bt_att *att, uint8_t opcode, + const void *pdu, uint16_t length, + bt_att_response_func_t callback, + void *user_data, + bt_att_destroy_func_t destroy); +bool bt_att_cancel(struct bt_att *att, unsigned int id); +bool bt_att_cancel_all(struct bt_att *att); + +unsigned int bt_att_send_error_rsp(struct bt_att *att, uint8_t opcode, + uint16_t handle, int error); + +unsigned int bt_att_register(struct bt_att *att, uint8_t opcode, + bt_att_notify_func_t callback, + void *user_data, + bt_att_destroy_func_t destroy); +bool bt_att_unregister(struct bt_att *att, unsigned int id); + +unsigned int bt_att_register_disconnect(struct bt_att *att, + bt_att_disconnect_func_t callback, + void *user_data, + bt_att_destroy_func_t destroy); +bool bt_att_unregister_disconnect(struct bt_att *att, unsigned int id); + +bool bt_att_unregister_all(struct bt_att *att); + +int bt_att_get_security(struct bt_att *att, uint8_t *enc_size); +bool bt_att_set_security(struct bt_att *att, int level); +void bt_att_set_enc_key_size(struct bt_att *att, uint8_t enc_size); + +bool bt_att_set_local_key(struct bt_att *att, uint8_t sign_key[16], + bt_att_counter_func_t func, void *user_data); +bool bt_att_set_remote_key(struct bt_att *att, uint8_t sign_key[16], + bt_att_counter_func_t func, void *user_data); +bool bt_att_has_crypto(struct bt_att *att); diff --git a/src/shared/btp.c b/src/shared/btp.c new file mode 100644 index 0000000..7eea241 --- /dev/null +++ b/src/shared/btp.c @@ -0,0 +1,387 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-2017 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 +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "src/shared/btp.h" + +#define BTP_MTU 512 + +struct btp_handler { + unsigned int id; + uint8_t service; + uint8_t opcode; + + btp_cmd_func_t callback; + void *user_data; + btp_destroy_func_t destroy; +}; + +struct btp { + struct l_io *io; + + struct l_queue *pending; + bool writer_active; + bool reader_active; + + struct l_queue *handlers; + unsigned int next_handler; + + uint8_t buf[BTP_MTU]; + + btp_disconnect_func_t disconnect_cb; + void *disconnect_cb_data; + btp_destroy_func_t disconnect_cb_data_destroy; +}; + + +static struct l_io *btp_connect(const char *path) +{ + struct sockaddr_un addr; + struct l_io *io; + int sk; + + sk = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (sk < 0) + return NULL; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(sk); + return NULL; + } + + io = l_io_new(sk); + if (!io) { + close(sk); + return NULL; + } + + l_io_set_close_on_destroy(io, true); + return io; +} + +static void disconnect_handler(struct l_io *io, void *user_data) +{ + struct btp *btp = user_data; + + btp->disconnect_cb(btp, btp->disconnect_cb_data); +} + +static void disconnect_handler_destroy(void *user_data) +{ + struct btp *btp = user_data; + + if (btp->disconnect_cb_data_destroy) + btp->disconnect_cb_data_destroy(btp->disconnect_cb_data); +} + +struct handler_match_data { + uint8_t service; + uint8_t opcode; +}; + +static bool handler_match(const void *a, const void *b) +{ + const struct btp_handler *handler = a; + const struct handler_match_data *match = b; + + return (handler->service == match->service) && + (handler->opcode == match->opcode); +} + +static bool can_read_data(struct l_io *io, void *user_data) +{ + struct handler_match_data match; + struct btp *btp = user_data; + struct btp_handler *handler; + struct btp_hdr *hdr; + ssize_t bytes_read; + uint16_t data_len; + + bytes_read = read(l_io_get_fd(btp->io), btp->buf, sizeof(btp->buf)); + if (bytes_read < 0) + return false; + + if ((size_t) bytes_read < sizeof(*hdr)) + return false; + + hdr = (void *)btp->buf; + + data_len = L_LE16_TO_CPU(hdr->data_len); + + if ((size_t) bytes_read < sizeof(*hdr) + data_len) + return false; + + match.service = hdr->service; + match.opcode = hdr->opcode; + + handler = l_queue_find(btp->handlers, handler_match, &match); + if (handler) { + handler->callback(hdr->index, hdr->data, data_len, + handler->user_data); + return false; + } + + /* keep reader active if we sent error reply */ + btp_send_error(btp, match.service, hdr->index, BTP_ERROR_UNKNOWN_CMD); + return true; +} + +static void read_watch_destroy(void *user_data) +{ + struct btp *btp = user_data; + + btp->reader_active = false; +} + +static void wakeup_reader(struct btp *btp) +{ + if (btp->reader_active) + return; + + btp->reader_active = l_io_set_read_handler(btp->io, can_read_data, btp, + read_watch_destroy); +} + +struct btp *btp_new(const char *path) +{ + struct btp *btp; + struct l_io *io; + + io = btp_connect(path); + if (!io) + return NULL; + + btp = l_new(struct btp, 1); + btp->pending = l_queue_new(); + btp->handlers = l_queue_new(); + btp->io = io; + btp->next_handler = 1; + + wakeup_reader(btp); + + return btp; +} + +struct pending_message { + size_t len; + void *data; + bool wakeup_read; +}; + +static void destroy_message(struct pending_message *msg) +{ + l_free(msg->data); + l_free(msg); +} + +void btp_cleanup(struct btp *btp) +{ + if (!btp) + return; + + l_io_destroy(btp->io); + l_queue_destroy(btp->pending, (l_queue_destroy_func_t)destroy_message); + l_queue_destroy(btp->handlers, (l_queue_destroy_func_t)l_free); + l_free(btp); +} + +bool btp_set_disconnect_handler(struct btp *btp, btp_disconnect_func_t callback, + void *user_data, btp_destroy_func_t destroy) +{ + if (callback) { + if (!l_io_set_disconnect_handler(btp->io, disconnect_handler, + btp, disconnect_handler_destroy)) + return false; + } else { + if (!l_io_set_disconnect_handler(btp->io, NULL, NULL, NULL)) + return false; + } + + btp->disconnect_cb = callback; + btp->disconnect_cb_data = user_data; + btp->disconnect_cb_data_destroy = destroy; + + return true; +} + +static bool can_write_data(struct l_io *io, void *user_data) +{ + struct btp *btp = user_data; + struct pending_message *msg; + + msg = l_queue_pop_head(btp->pending); + if (!msg) + return false; + + if (msg->wakeup_read) + wakeup_reader(btp); + + if (write(l_io_get_fd(btp->io), msg->data, msg->len) < 0) { + l_error("Failed to send BTP message"); + destroy_message(msg); + return false; + } + + destroy_message(msg); + + return !l_queue_isempty(btp->pending); +} + +static void write_watch_destroy(void *user_data) +{ + struct btp *btp = user_data; + + btp->writer_active = false; +} + +static void wakeup_writer(struct btp *btp) +{ + if (l_queue_isempty(btp->pending)) + return; + + if (btp->writer_active) + return; + + btp->writer_active = l_io_set_write_handler(btp->io, can_write_data, + btp, write_watch_destroy); +} + +bool btp_send_error(struct btp *btp, uint8_t service, uint8_t index, + uint8_t status) +{ + struct btp_error rsp; + + rsp.status = status; + + return btp_send(btp, service, BTP_OP_ERROR, index, sizeof(rsp), &rsp); +} + +bool btp_send(struct btp *btp, uint8_t service, uint8_t opcode, uint8_t index, + uint16_t length, const void *param) +{ + struct btp_hdr *hdr; + struct pending_message *msg; + size_t len; + + if (!btp) + return false; + + len = sizeof(*hdr) + length; + hdr = l_malloc(len); + if (!hdr) + return false; + + hdr->service = service; + hdr->opcode = opcode; + hdr->index = index; + hdr->data_len = L_CPU_TO_LE16(length); + if (length) + memcpy(hdr->data, param, length); + + msg = l_new(struct pending_message, 1); + msg->len = len; + msg->data = hdr; + msg->wakeup_read = opcode < 0x80; + + l_queue_push_tail(btp->pending, msg); + wakeup_writer(btp); + + return true; +} + +unsigned int btp_register(struct btp *btp, uint8_t service, uint8_t opcode, + btp_cmd_func_t callback, void *user_data, + btp_destroy_func_t destroy) +{ + struct btp_handler *handler; + + handler = l_new(struct btp_handler, 1); + + handler->id = btp->next_handler++; + handler->service = service; + handler->opcode = opcode; + handler->callback = callback; + handler->user_data = user_data; + handler->destroy = destroy; + + l_queue_push_tail(btp->handlers, handler); + + return handler->id; +} + +static bool handler_match_by_id(const void *a, const void *b) +{ + const struct btp_handler *handler = a; + unsigned int id = L_PTR_TO_UINT(b); + + return handler->id == id; +} + +bool btp_unregister(struct btp *btp, unsigned int id) +{ + struct btp_handler *handler; + + handler = l_queue_remove_if(btp->handlers, handler_match_by_id, + L_UINT_TO_PTR(id)); + if (!handler) + return false; + + if (handler->destroy) + handler->destroy(handler->user_data); + + l_free(handler); + + return true; +} + +static bool handler_remove_by_service(void *a, void *b) +{ + struct btp_handler *handler = a; + uint8_t service = L_PTR_TO_UINT(b); + + if (handler->service != service) + return false; + + if (handler->destroy) + handler->destroy(handler->user_data); + + l_free(handler); + return true; +} + +void btp_unregister_service(struct btp *btp, uint8_t service) +{ + l_queue_foreach_remove(btp->handlers, handler_remove_by_service, + L_UINT_TO_PTR(service)); +} diff --git a/src/shared/btp.h b/src/shared/btp.h new file mode 100644 index 0000000..f0ac3a1 --- /dev/null +++ b/src/shared/btp.h @@ -0,0 +1,320 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-2017 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 + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +#define BTP_INDEX_NON_CONTROLLER 0xff + +#define BTP_ERROR_FAIL 0x01 +#define BTP_ERROR_UNKNOWN_CMD 0x02 +#define BTP_ERROR_NOT_READY 0x03 +#define BTP_ERROR_INVALID_INDEX 0x04 + +#define BTP_CORE_SERVICE 0 +#define BTP_GAP_SERVICE 1 +#define BTP_GATT_SERVICE 2 +#define BTP_L2CAP_SERVICE 3 +#define BTP_MESH_NODE_SERVICE 4 + +struct btp_hdr { + uint8_t service; + uint8_t opcode; + uint8_t index; + uint16_t data_len; + uint8_t data[0]; +} __packed; + +struct btp_error { + uint8_t status; +} __packed; + +#define BTP_OP_ERROR 0x00 + +#define BTP_OP_CORE_READ_SUPPORTED_COMMANDS 0x01 + +#define BTP_OP_CORE_READ_SUPPORTED_SERVICES 0x02 + +#define BTP_OP_CORE_REGISTER 0x03 +struct btp_core_register_cp { + uint8_t service_id; +} __packed; + +#define BTP_OP_CORE_UNREGISTER 0x04 +struct btp_core_unregister_cp { + uint8_t service_id; +} __packed; + +#define BTP_EV_CORE_READY 0x80 + +#define BTP_OP_GAP_READ_SUPPORTED_COMMANDS 0x01 + +#define BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST 0x02 +struct btp_gap_read_index_rp { + uint8_t num; + uint8_t indexes[0]; +} __packed; + +#define BTP_GAP_SETTING_POWERED 0x00000001 +#define BTP_GAP_SETTING_CONNECTABLE 0x00000002 +#define BTP_GAP_SETTING_FAST_CONNECTABLE 0x00000004 +#define BTP_GAP_SETTING_DISCOVERABLE 0x00000008 +#define BTP_GAP_SETTING_BONDABLE 0x00000010 +#define BTP_GAP_SETTING_LL_SECURITY 0x00000020 +#define BTP_GAP_SETTING_SSP 0x00000040 +#define BTP_GAP_SETTING_BREDR 0x00000080 +#define BTP_GAP_SETTING_HS 0x00000100 +#define BTP_GAP_SETTING_LE 0x00000200 +#define BTP_GAP_SETTING_ADVERTISING 0x00000400 +#define BTP_GAP_SETTING_SC 0x00000800 +#define BTP_GAP_SETTING_DEBUG_KEYS 0x00001000 +#define BTP_GAP_SETTING_PRIVACY 0x00002000 +#define BTP_GAP_SETTING_CONTROLLER_CONF 0x00004000 +#define BTP_GAP_SETTING_STATIC_ADDRESS 0x00008000 + +#define BTP_OP_GAP_READ_COTROLLER_INFO 0x03 +struct btp_gap_read_info_rp { + bdaddr_t address; + uint32_t supported_settings; + uint32_t current_settings; + uint8_t cod[3]; + uint8_t name[249]; + uint8_t short_name[11]; +} __packed; + +#define BTP_OP_GAP_RESET 0x04 +struct btp_gap_reset_rp { + uint32_t current_settings; +} __packed; + +#define BTP_OP_GAP_SET_POWERED 0x05 +struct btp_gap_set_powered_cp { + uint8_t powered; +} __packed; + +struct btp_gap_set_powered_rp { + uint32_t current_settings; +} __packed; + +#define BTP_OP_GAP_SET_CONNECTABLE 0x06 +struct btp_gap_set_connectable_cp { + uint8_t connectable; +} __packed; + +struct btp_gap_set_connectable_rp { + uint32_t current_settings; +} __packed; + +#define BTP_OP_GAP_SET_FAST_CONNECTABLE 0x07 +struct btp_gap_set_fast_connectable_cp { + uint8_t fast_connectable; +} __packed; + +struct btp_gap_set_fast_connectable_rp { + uint32_t current_settings; +} __packed; + +#define BTP_OP_GAP_SET_DISCOVERABLE 0x08 +struct btp_gap_set_discoverable_cp { + uint8_t discoverable; +} __packed; + +struct btp_gap_set_discoverable_rp { + uint32_t current_settings; +} __packed; + +#define BTP_OP_GAP_SET_BONDABLE 0x09 +struct btp_gap_set_bondable_cp { + uint8_t bondable; +} __packed; + +struct btp_gap_set_bondable_rp { + uint32_t current_settings; +} __packed; + +#define BTP_OP_GAP_START_ADVERTISING 0x0a +struct btp_gap_start_adv_cp { + uint8_t adv_data_len; + uint8_t scan_rsp_len; + uint8_t data[0]; +} __packed; + +struct btp_gap_start_adv_rp { + uint32_t current_settings; +} __packed; + +#define BTP_OP_GAP_STOP_ADVERTISING 0x0b +struct btp_gap_stop_adv_rp { + uint32_t current_settings; +} __packed; + +#define BTP_GAP_DISCOVERY_FLAG_LE 0x01 +#define BTP_GAP_DISCOVERY_FLAG_BREDR 0x02 +#define BTP_GAP_DISCOVERY_FLAG_LIMITED 0x04 +#define BTP_GAP_DISCOVERY_FLAG_ACTIVE 0x08 +#define BTP_GAP_DISCOVERY_FLAG_OBSERVATION 0x10 + +#define BTP_OP_GAP_START_DISCOVERY 0x0c +struct btp_gap_start_discovery_cp { + uint8_t flags; +} __packed; + +#define BTP_OP_GAP_STOP_DISCOVERY 0x0d + +#define BTP_GAP_ADDR_PUBLIC 0x00 +#define BTP_GAP_ADDR_RANDOM 0x01 + +#define BTP_OP_GAP_CONNECT 0x0e +struct btp_gap_connect_cp { + uint8_t address_type; + bdaddr_t address; +} __packed; + +#define BTP_OP_GAP_DISCONNECT 0x0f +struct btp_gap_disconnect_cp { + uint8_t address_type; + bdaddr_t address; +} __packed; + +#define BTP_GAP_IOCAPA_DISPLAY_ONLY 0x00 +#define BTP_GAP_IOCAPA_DISPLAY_YESNO 0x01 +#define BTP_GAP_IOCAPA_KEYBOARD_ONLY 0x02 +#define BTP_GAP_IOCAPA_NO_INPUT_NO_OUTPUT 0x03 +#define BTP_GAP_IOCAPA_KEYBOARD_DISPLAY 0x04 + +#define BTP_OP_GAP_SET_IO_CAPA 0x10 +struct btp_gap_set_io_capa_cp { + uint8_t capa; +} __packed; + +#define BTP_OP_GAP_PAIR 0x11 +struct btp_gap_pair_cp { + uint8_t address_type; + bdaddr_t address; +} __packed; + +#define BTP_OP_GAP_UNPAIR 0x12 +struct btp_gap_unpair_cp { + uint8_t address_type; + bdaddr_t address; +} __packed; + +#define BTP_OP_GAP_PASSKEY_ENTRY_RSP 0x13 +struct btp_gap_passkey_entry_rsp_cp { + uint8_t address_type; + bdaddr_t address; + uint32_t passkey; +} __packed; + +#define BTP_OP_GAP_PASSKEY_CONFIRM_RSP 0x14 +struct btp_gap_passkey_confirm_rsp_cp { + uint8_t address_type; + bdaddr_t address; + uint8_t match; +} __packed; + +#define BTP_EV_GAP_NEW_SETTINGS 0x80 +struct btp_new_settings_ev { + uint32_t current_settings; +} __packed; + +#define BTP_EV_GAP_DEVICE_FOUND_FLAG_RSSI 0x01 +#define BTP_EV_GAP_DEVICE_FOUND_FLAG_AD 0x02 +#define BTP_EV_GAP_DEVICE_FOUND_FLAG_SR 0x04 + +#define BTP_EV_GAP_DEVICE_FOUND 0x81 +struct btp_device_found_ev { + uint8_t address_type; + bdaddr_t address; + int8_t rssi; + uint8_t flags; + uint16_t eir_len; + uint8_t eir[0]; +} __packed; + +#define BTP_EV_GAP_DEVICE_CONNECTED 0x82 +struct btp_gap_device_connected_ev { + uint8_t address_type; + bdaddr_t address; +} __packed; + +#define BTP_EV_GAP_DEVICE_DISCONNECTED 0x83 +struct btp_gap_device_disconnected_ev { + uint8_t address_type; + bdaddr_t address; +} __packed; + +#define BTP_EV_GAP_PASSKEY_DISPLAY 0x84 +struct btp_gap_passkey_display_ev { + uint8_t address_type; + bdaddr_t address; + uint32_t passkey; +} __packed; + +#define BTP_EV_GAP_PASSKEY_REQUEST 0x85 +struct btp_gap_passkey_req_ev { + uint8_t address_type; + bdaddr_t address; +} __packed; + +#define BTP_EV_GAP_PASSKEY_CONFIRM 0x86 +struct btp_gap_passkey_confirm_ev { + uint8_t address_type; + bdaddr_t address; + uint32_t passkey; +} __packed; + +#define BTP_EV_GAP_IDENTITY_RESOLVED 0x87 +struct btp_gap_identity_resolved_ev { + uint8_t address_type; + bdaddr_t address; + uint8_t identity_address_type; + bdaddr_t identity_address; +} __packed; + +struct btp; + +typedef void (*btp_destroy_func_t)(void *user_data); +typedef void (*btp_disconnect_func_t)(struct btp *btp, void *user_data); +typedef void (*btp_cmd_func_t)(uint8_t index, const void *param, + uint16_t length, void *user_data); + +struct btp *btp_new(const char *path); +void btp_cleanup(struct btp *btp); + +bool btp_set_disconnect_handler(struct btp *btp, btp_disconnect_func_t callback, + void *user_data, btp_destroy_func_t destroy); + +bool btp_send_error(struct btp *btp, uint8_t service, uint8_t index, + uint8_t status); +bool btp_send(struct btp *btp, uint8_t service, uint8_t opcode, uint8_t index, + uint16_t length, const void *param); + +unsigned int btp_register(struct btp *btp, uint8_t service, uint8_t opcode, + btp_cmd_func_t callback, void *user_data, + btp_destroy_func_t destroy); +bool btp_unregister(struct btp *btp, unsigned int id); +void btp_unregister_service(struct btp *btp, uint8_t service); diff --git a/src/shared/btsnoop.c b/src/shared/btsnoop.c new file mode 100644 index 0000000..f0d0747 --- /dev/null +++ b/src/shared/btsnoop.c @@ -0,0 +1,584 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "src/shared/btsnoop.h" + +struct btsnoop_hdr { + uint8_t id[8]; /* Identification Pattern */ + uint32_t version; /* Version Number = 1 */ + uint32_t type; /* Datalink Type */ +} __attribute__ ((packed)); +#define BTSNOOP_HDR_SIZE (sizeof(struct btsnoop_hdr)) + +struct btsnoop_pkt { + uint32_t size; /* Original Length */ + uint32_t len; /* Included Length */ + uint32_t flags; /* Packet Flags */ + uint32_t drops; /* Cumulative Drops */ + uint64_t ts; /* Timestamp microseconds */ + uint8_t data[0]; /* Packet Data */ +} __attribute__ ((packed)); +#define BTSNOOP_PKT_SIZE (sizeof(struct btsnoop_pkt)) + +static const uint8_t btsnoop_id[] = { 0x62, 0x74, 0x73, 0x6e, + 0x6f, 0x6f, 0x70, 0x00 }; + +static const uint32_t btsnoop_version = 1; + +struct pklg_pkt { + uint32_t len; + uint64_t ts; + uint8_t type; +} __attribute__ ((packed)); +#define PKLG_PKT_SIZE (sizeof(struct pklg_pkt)) + +struct btsnoop { + int ref_count; + int fd; + unsigned long flags; + uint32_t format; + uint16_t index; + bool aborted; + bool pklg_format; + bool pklg_v2; + const char *path; + size_t max_size; + size_t cur_size; + unsigned int max_count; + unsigned int cur_count; +}; + +struct btsnoop *btsnoop_open(const char *path, unsigned long flags) +{ + struct btsnoop *btsnoop; + struct btsnoop_hdr hdr; + ssize_t len; + + btsnoop = calloc(1, sizeof(*btsnoop)); + if (!btsnoop) + return NULL; + + btsnoop->fd = open(path, O_RDONLY | O_CLOEXEC); + if (btsnoop->fd < 0) { + free(btsnoop); + return NULL; + } + + btsnoop->flags = flags; + + len = read(btsnoop->fd, &hdr, BTSNOOP_HDR_SIZE); + if (len < 0 || len != BTSNOOP_HDR_SIZE) + goto failed; + + if (!memcmp(hdr.id, btsnoop_id, sizeof(btsnoop_id))) { + /* Check for BTSnoop version 1 format */ + if (be32toh(hdr.version) != btsnoop_version) + goto failed; + + btsnoop->format = be32toh(hdr.type); + btsnoop->index = 0xffff; + } else { + if (!(btsnoop->flags & BTSNOOP_FLAG_PKLG_SUPPORT)) + goto failed; + + /* Check for Apple Packet Logger format */ + if (hdr.id[0] != 0x00 || + (hdr.id[1] != 0x00 && hdr.id[1] != 0x01)) + goto failed; + + btsnoop->format = BTSNOOP_FORMAT_MONITOR; + btsnoop->index = 0xffff; + btsnoop->pklg_format = true; + btsnoop->pklg_v2 = (hdr.id[1] == 0x01); + + /* Apple Packet Logger format has no header */ + lseek(btsnoop->fd, 0, SEEK_SET); + } + + return btsnoop_ref(btsnoop); + +failed: + close(btsnoop->fd); + free(btsnoop); + + return NULL; +} + +struct btsnoop *btsnoop_create(const char *path, size_t max_size, + unsigned int max_count, uint32_t format) +{ + struct btsnoop *btsnoop; + struct btsnoop_hdr hdr; + const char *real_path; + char tmp[PATH_MAX]; + ssize_t written; + + if (!max_size && max_count) + return NULL; + + btsnoop = calloc(1, sizeof(*btsnoop)); + if (!btsnoop) + return NULL; + + /* If max file size is specified, always add counter to file path */ + if (max_size) { + snprintf(tmp, PATH_MAX, "%s.0", path); + real_path = tmp; + } else { + real_path = path; + } + + btsnoop->fd = open(real_path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (btsnoop->fd < 0) { + free(btsnoop); + return NULL; + } + + btsnoop->format = format; + btsnoop->index = 0xffff; + btsnoop->path = path; + btsnoop->max_count = max_count; + btsnoop->max_size = max_size; + + memcpy(hdr.id, btsnoop_id, sizeof(btsnoop_id)); + hdr.version = htobe32(btsnoop_version); + hdr.type = htobe32(btsnoop->format); + + written = write(btsnoop->fd, &hdr, BTSNOOP_HDR_SIZE); + if (written < 0) { + close(btsnoop->fd); + free(btsnoop); + return NULL; + } + + btsnoop->cur_size = BTSNOOP_HDR_SIZE; + + return btsnoop_ref(btsnoop); +} + +struct btsnoop *btsnoop_ref(struct btsnoop *btsnoop) +{ + if (!btsnoop) + return NULL; + + __sync_fetch_and_add(&btsnoop->ref_count, 1); + + return btsnoop; +} + +void btsnoop_unref(struct btsnoop *btsnoop) +{ + if (!btsnoop) + return; + + if (__sync_sub_and_fetch(&btsnoop->ref_count, 1)) + return; + + if (btsnoop->fd >= 0) + close(btsnoop->fd); + + free(btsnoop); +} + +uint32_t btsnoop_get_format(struct btsnoop *btsnoop) +{ + if (!btsnoop) + return BTSNOOP_FORMAT_INVALID; + + return btsnoop->format; +} + +static bool btsnoop_rotate(struct btsnoop *btsnoop) +{ + struct btsnoop_hdr hdr; + char path[PATH_MAX]; + ssize_t written; + + close(btsnoop->fd); + + /* Check if max number of log files has been reached */ + if (btsnoop->max_count && btsnoop->cur_count >= btsnoop->max_count) { + snprintf(path, PATH_MAX, "%s.%u", btsnoop->path, + btsnoop->cur_count - btsnoop->max_count); + unlink(path); + } + + snprintf(path, PATH_MAX,"%s.%u", btsnoop->path, btsnoop->cur_count); + btsnoop->cur_count++; + + btsnoop->fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (btsnoop->fd < 0) + return false; + + memcpy(hdr.id, btsnoop_id, sizeof(btsnoop_id)); + hdr.version = htobe32(btsnoop_version); + hdr.type = htobe32(btsnoop->format); + + written = write(btsnoop->fd, &hdr, BTSNOOP_HDR_SIZE); + if (written < 0) + return false; + + btsnoop->cur_size = BTSNOOP_HDR_SIZE; + + return true; +} + +bool btsnoop_write(struct btsnoop *btsnoop, struct timeval *tv, + uint32_t flags, uint32_t drops, const void *data, + uint16_t size) +{ + struct btsnoop_pkt pkt; + uint64_t ts; + ssize_t written; + + if (!btsnoop || !tv) + return false; + + if (btsnoop->max_size && btsnoop->max_size <= + btsnoop->cur_size + size + BTSNOOP_PKT_SIZE) + if (!btsnoop_rotate(btsnoop)) + return false; + + ts = (tv->tv_sec - 946684800ll) * 1000000ll + tv->tv_usec; + + pkt.size = htobe32(size); + pkt.len = htobe32(size); + pkt.flags = htobe32(flags); + pkt.drops = htobe32(drops); + pkt.ts = htobe64(ts + 0x00E03AB44A676000ll); + + written = write(btsnoop->fd, &pkt, BTSNOOP_PKT_SIZE); + if (written < 0) + return false; + + btsnoop->cur_size += BTSNOOP_PKT_SIZE; + + if (data && size > 0) { + written = write(btsnoop->fd, data, size); + if (written < 0) + return false; + } + + btsnoop->cur_size += size; + + return true; +} + +static uint32_t get_flags_from_opcode(uint16_t opcode) +{ + switch (opcode) { + case BTSNOOP_OPCODE_NEW_INDEX: + case BTSNOOP_OPCODE_DEL_INDEX: + break; + case BTSNOOP_OPCODE_COMMAND_PKT: + return 0x02; + case BTSNOOP_OPCODE_EVENT_PKT: + return 0x03; + case BTSNOOP_OPCODE_ACL_TX_PKT: + return 0x00; + case BTSNOOP_OPCODE_ACL_RX_PKT: + return 0x01; + case BTSNOOP_OPCODE_SCO_TX_PKT: + case BTSNOOP_OPCODE_SCO_RX_PKT: + break; + case BTSNOOP_OPCODE_OPEN_INDEX: + case BTSNOOP_OPCODE_CLOSE_INDEX: + break; + } + + return 0xff; +} + +bool btsnoop_write_hci(struct btsnoop *btsnoop, struct timeval *tv, + uint16_t index, uint16_t opcode, uint32_t drops, + const void *data, uint16_t size) +{ + uint32_t flags; + + if (!btsnoop) + return false; + + switch (btsnoop->format) { + case BTSNOOP_FORMAT_HCI: + if (btsnoop->index == 0xffff) + btsnoop->index = index; + + if (index != btsnoop->index) + return false; + + flags = get_flags_from_opcode(opcode); + if (flags == 0xff) + return false; + break; + + case BTSNOOP_FORMAT_MONITOR: + flags = (index << 16) | opcode; + break; + + default: + return false; + } + + return btsnoop_write(btsnoop, tv, flags, drops, data, size); +} + +bool btsnoop_write_phy(struct btsnoop *btsnoop, struct timeval *tv, + uint16_t frequency, const void *data, uint16_t size) +{ + uint32_t flags; + + if (!btsnoop) + return false; + + switch (btsnoop->format) { + case BTSNOOP_FORMAT_SIMULATOR: + flags = (1 << 16) | frequency; + break; + + default: + return false; + } + + return btsnoop_write(btsnoop, tv, flags, 0, data, size); +} + +static bool pklg_read_hci(struct btsnoop *btsnoop, struct timeval *tv, + uint16_t *index, uint16_t *opcode, + void *data, uint16_t *size) +{ + struct pklg_pkt pkt; + uint32_t toread; + uint64_t ts; + ssize_t len; + + len = read(btsnoop->fd, &pkt, PKLG_PKT_SIZE); + if (len == 0) + return false; + + if (len < 0 || len != PKLG_PKT_SIZE) { + btsnoop->aborted = true; + return false; + } + + if (btsnoop->pklg_v2) { + toread = le32toh(pkt.len) - (PKLG_PKT_SIZE - 4); + + ts = le64toh(pkt.ts); + tv->tv_sec = ts & 0xffffffff; + tv->tv_usec = ts >> 32; + } else { + toread = be32toh(pkt.len) - (PKLG_PKT_SIZE - 4); + + ts = be64toh(pkt.ts); + tv->tv_sec = ts >> 32; + tv->tv_usec = ts & 0xffffffff; + } + + if (toread > BTSNOOP_MAX_PACKET_SIZE) { + btsnoop->aborted = true; + return false; + } + + switch (pkt.type) { + case 0x00: + *index = 0x0000; + *opcode = BTSNOOP_OPCODE_COMMAND_PKT; + break; + case 0x01: + *index = 0x0000; + *opcode = BTSNOOP_OPCODE_EVENT_PKT; + break; + case 0x02: + *index = 0x0000; + *opcode = BTSNOOP_OPCODE_ACL_TX_PKT; + break; + case 0x03: + *index = 0x0000; + *opcode = BTSNOOP_OPCODE_ACL_RX_PKT; + break; + case 0x08: + *index = 0x0000; + *opcode = BTSNOOP_OPCODE_SCO_TX_PKT; + break; + case 0x09: + *index = 0x0000; + *opcode = BTSNOOP_OPCODE_SCO_RX_PKT; + break; + case 0x0b: + *index = 0x0000; + *opcode = BTSNOOP_OPCODE_VENDOR_DIAG; + break; + case 0xfc: + *index = 0xffff; + *opcode = BTSNOOP_OPCODE_SYSTEM_NOTE; + break; + default: + *index = 0xffff; + *opcode = 0xffff; + break; + } + + len = read(btsnoop->fd, data, toread); + if (len < 0) { + btsnoop->aborted = true; + return false; + } + + *size = toread; + + return true; +} + +static uint16_t get_opcode_from_flags(uint8_t type, uint32_t flags) +{ + switch (type) { + case 0x01: + return BTSNOOP_OPCODE_COMMAND_PKT; + case 0x02: + if (flags & 0x01) + return BTSNOOP_OPCODE_ACL_RX_PKT; + else + return BTSNOOP_OPCODE_ACL_TX_PKT; + case 0x03: + if (flags & 0x01) + return BTSNOOP_OPCODE_SCO_RX_PKT; + else + return BTSNOOP_OPCODE_SCO_TX_PKT; + case 0x04: + return BTSNOOP_OPCODE_EVENT_PKT; + case 0xff: + if (flags & 0x02) { + if (flags & 0x01) + return BTSNOOP_OPCODE_EVENT_PKT; + else + return BTSNOOP_OPCODE_COMMAND_PKT; + } else { + if (flags & 0x01) + return BTSNOOP_OPCODE_ACL_RX_PKT; + else + return BTSNOOP_OPCODE_ACL_TX_PKT; + } + break; + } + + return 0xffff; +} + +bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv, + uint16_t *index, uint16_t *opcode, + void *data, uint16_t *size) +{ + struct btsnoop_pkt pkt; + uint32_t toread, flags; + uint64_t ts; + uint8_t pkt_type; + ssize_t len; + + if (!btsnoop || btsnoop->aborted) + return false; + + if (btsnoop->pklg_format) + return pklg_read_hci(btsnoop, tv, index, opcode, data, size); + + len = read(btsnoop->fd, &pkt, BTSNOOP_PKT_SIZE); + if (len == 0) + return false; + + if (len < 0 || len != BTSNOOP_PKT_SIZE) { + btsnoop->aborted = true; + return false; + } + + toread = be32toh(pkt.size); + if (toread > BTSNOOP_MAX_PACKET_SIZE) { + btsnoop->aborted = true; + return false; + } + + flags = be32toh(pkt.flags); + + ts = be64toh(pkt.ts) - 0x00E03AB44A676000ll; + tv->tv_sec = (ts / 1000000ll) + 946684800ll; + tv->tv_usec = ts % 1000000ll; + + switch (btsnoop->format) { + case BTSNOOP_FORMAT_HCI: + *index = 0; + *opcode = get_opcode_from_flags(0xff, flags); + break; + + case BTSNOOP_FORMAT_UART: + len = read(btsnoop->fd, &pkt_type, 1); + if (len < 0) { + btsnoop->aborted = true; + return false; + } + toread--; + + *index = 0; + *opcode = get_opcode_from_flags(pkt_type, flags); + break; + + case BTSNOOP_FORMAT_MONITOR: + *index = flags >> 16; + *opcode = flags & 0xffff; + break; + + default: + btsnoop->aborted = true; + return false; + } + + len = read(btsnoop->fd, data, toread); + if (len < 0) { + btsnoop->aborted = true; + return false; + } + + *size = toread; + + return true; +} + +bool btsnoop_read_phy(struct btsnoop *btsnoop, struct timeval *tv, + uint16_t *frequency, void *data, uint16_t *size) +{ + return false; +} diff --git a/src/shared/btsnoop.h b/src/shared/btsnoop.h new file mode 100644 index 0000000..3043d33 --- /dev/null +++ b/src/shared/btsnoop.h @@ -0,0 +1,122 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include +#include + +#define BTSNOOP_FORMAT_INVALID 0 +#define BTSNOOP_FORMAT_HCI 1001 +#define BTSNOOP_FORMAT_UART 1002 +#define BTSNOOP_FORMAT_BCSP 1003 +#define BTSNOOP_FORMAT_3WIRE 1004 +#define BTSNOOP_FORMAT_MONITOR 2001 +#define BTSNOOP_FORMAT_SIMULATOR 2002 + +#define BTSNOOP_FLAG_PKLG_SUPPORT (1 << 0) + +#define BTSNOOP_OPCODE_NEW_INDEX 0 +#define BTSNOOP_OPCODE_DEL_INDEX 1 +#define BTSNOOP_OPCODE_COMMAND_PKT 2 +#define BTSNOOP_OPCODE_EVENT_PKT 3 +#define BTSNOOP_OPCODE_ACL_TX_PKT 4 +#define BTSNOOP_OPCODE_ACL_RX_PKT 5 +#define BTSNOOP_OPCODE_SCO_TX_PKT 6 +#define BTSNOOP_OPCODE_SCO_RX_PKT 7 +#define BTSNOOP_OPCODE_OPEN_INDEX 8 +#define BTSNOOP_OPCODE_CLOSE_INDEX 9 +#define BTSNOOP_OPCODE_INDEX_INFO 10 +#define BTSNOOP_OPCODE_VENDOR_DIAG 11 +#define BTSNOOP_OPCODE_SYSTEM_NOTE 12 +#define BTSNOOP_OPCODE_USER_LOGGING 13 +#define BTSNOOP_OPCODE_CTRL_OPEN 14 +#define BTSNOOP_OPCODE_CTRL_CLOSE 15 +#define BTSNOOP_OPCODE_CTRL_COMMAND 16 +#define BTSNOOP_OPCODE_CTRL_EVENT 17 + +#define BTSNOOP_MAX_PACKET_SIZE (1486 + 4) + +#define BTSNOOP_TYPE_PRIMARY 0 +#define BTSNOOP_TYPE_AMP 1 + +#define BTSNOOP_BUS_VIRTUAL 0 +#define BTSNOOP_BUS_USB 1 +#define BTSNOOP_BUS_PCCARD 2 +#define BTSNOOP_BUS_UART 3 +#define BTSNOOP_BUS_RS232 4 +#define BTSNOOP_BUS_PCI 5 +#define BTSNOOP_BUS_SDIO 6 +#define BTSNOOP_BUS_SPI 7 +#define BTSNOOP_BUS_I2C 8 +#define BTSNOOP_BUS_SMD 9 + +struct btsnoop_opcode_new_index { + uint8_t type; + uint8_t bus; + uint8_t bdaddr[6]; + char name[8]; +} __attribute__((packed)); + +struct btsnoop_opcode_index_info { + uint8_t bdaddr[6]; + uint16_t manufacturer; +} __attribute__((packed)); + +#define BTSNOOP_PRIORITY_EMERG 0 +#define BTSNOOP_PRIORITY_ALERT 1 +#define BTSNOOP_PRIORITY_CRIT 2 +#define BTSNOOP_PRIORITY_ERR 3 +#define BTSNOOP_PRIORITY_WARNING 4 +#define BTSNOOP_PRIORITY_NOTICE 5 +#define BTSNOOP_PRIORITY_INFO 6 +#define BTSNOOP_PRIORITY_DEBUG 7 + +struct btsnoop_opcode_user_logging { + uint8_t priority; + uint8_t ident_len; +} __attribute__((packed)); + +struct btsnoop; + +struct btsnoop *btsnoop_open(const char *path, unsigned long flags); +struct btsnoop *btsnoop_create(const char *path, size_t max_size, + unsigned int max_count, uint32_t format); + +struct btsnoop *btsnoop_ref(struct btsnoop *btsnoop); +void btsnoop_unref(struct btsnoop *btsnoop); + +uint32_t btsnoop_get_format(struct btsnoop *btsnoop); + +bool btsnoop_write(struct btsnoop *btsnoop, struct timeval *tv, uint32_t flags, + uint32_t drops, const void *data, uint16_t size); +bool btsnoop_write_hci(struct btsnoop *btsnoop, struct timeval *tv, + uint16_t index, uint16_t opcode, uint32_t drops, + const void *data, uint16_t size); +bool btsnoop_write_phy(struct btsnoop *btsnoop, struct timeval *tv, + uint16_t frequency, const void *data, uint16_t size); + +bool btsnoop_read_hci(struct btsnoop *btsnoop, struct timeval *tv, + uint16_t *index, uint16_t *opcode, + void *data, uint16_t *size); +bool btsnoop_read_phy(struct btsnoop *btsnoop, struct timeval *tv, + uint16_t *frequency, void *data, uint16_t *size); diff --git a/src/shared/crypto.c b/src/shared/crypto.c new file mode 100644 index 0000000..5c5e121 --- /dev/null +++ b/src/shared/crypto.c @@ -0,0 +1,721 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#include +#include +#include +#include + +#include "src/shared/util.h" +#include "src/shared/crypto.h" + +#ifndef HAVE_LINUX_IF_ALG_H +#ifndef HAVE_LINUX_TYPES_H +typedef uint8_t __u8; +typedef uint16_t __u16; +typedef uint32_t __u32; +#else +#include +#endif + +struct sockaddr_alg { + __u16 salg_family; + __u8 salg_type[14]; + __u32 salg_feat; + __u32 salg_mask; + __u8 salg_name[64]; +}; + +struct af_alg_iv { + __u32 ivlen; + __u8 iv[0]; +}; + +#define ALG_SET_KEY 1 +#define ALG_SET_IV 2 +#define ALG_SET_OP 3 + +#define ALG_OP_DECRYPT 0 +#define ALG_OP_ENCRYPT 1 + +#define PF_ALG 38 /* Algorithm sockets. */ +#define AF_ALG PF_ALG +#else +#include +#endif + +#ifndef SOL_ALG +#define SOL_ALG 279 +#endif + +/* Maximum message length that can be passed to aes_cmac */ +#define CMAC_MSG_MAX 80 + +struct bt_crypto { + int ref_count; + int ecb_aes; + int urandom; + int cmac_aes; +}; + +static int urandom_setup(void) +{ + int fd; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -1; + + return fd; +} + +static int ecb_aes_setup(void) +{ + struct sockaddr_alg salg; + int fd; + + fd = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + if (fd < 0) + return -1; + + memset(&salg, 0, sizeof(salg)); + salg.salg_family = AF_ALG; + strcpy((char *) salg.salg_type, "skcipher"); + strcpy((char *) salg.salg_name, "ecb(aes)"); + + if (bind(fd, (struct sockaddr *) &salg, sizeof(salg)) < 0) { + close(fd); + return -1; + } + + return fd; +} + +static int cmac_aes_setup(void) +{ + struct sockaddr_alg salg; + int fd; + + fd = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + if (fd < 0) + return -1; + + memset(&salg, 0, sizeof(salg)); + salg.salg_family = AF_ALG; + strcpy((char *) salg.salg_type, "hash"); + strcpy((char *) salg.salg_name, "cmac(aes)"); + + if (bind(fd, (struct sockaddr *) &salg, sizeof(salg)) < 0) { + close(fd); + return -1; + } + + return fd; +} + +struct bt_crypto *bt_crypto_new(void) +{ + struct bt_crypto *crypto; + + crypto = new0(struct bt_crypto, 1); + + crypto->ecb_aes = ecb_aes_setup(); + if (crypto->ecb_aes < 0) { + free(crypto); + return NULL; + } + + crypto->urandom = urandom_setup(); + if (crypto->urandom < 0) { + close(crypto->ecb_aes); + free(crypto); + return NULL; + } + + crypto->cmac_aes = cmac_aes_setup(); + if (crypto->cmac_aes < 0) { + close(crypto->urandom); + close(crypto->ecb_aes); + free(crypto); + return NULL; + } + + return bt_crypto_ref(crypto); +} + +struct bt_crypto *bt_crypto_ref(struct bt_crypto *crypto) +{ + if (!crypto) + return NULL; + + __sync_fetch_and_add(&crypto->ref_count, 1); + + return crypto; +} + +void bt_crypto_unref(struct bt_crypto *crypto) +{ + if (!crypto) + return; + + if (__sync_sub_and_fetch(&crypto->ref_count, 1)) + return; + + close(crypto->urandom); + close(crypto->ecb_aes); + close(crypto->cmac_aes); + + free(crypto); +} + +bool bt_crypto_random_bytes(struct bt_crypto *crypto, + void *buf, uint8_t num_bytes) +{ + ssize_t len; + + if (!crypto) + return false; + + len = read(crypto->urandom, buf, num_bytes); + if (len < num_bytes) + return false; + + return true; +} + +static int alg_new(int fd, const void *keyval, socklen_t keylen) +{ + if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, keyval, keylen) < 0) + return -1; + + /* FIXME: This should use accept4() with SOCK_CLOEXEC */ + return accept(fd, NULL, 0); +} + +static bool alg_encrypt(int fd, const void *inbuf, size_t inlen, + void *outbuf, size_t outlen) +{ + __u32 alg_op = ALG_OP_ENCRYPT; + char cbuf[CMSG_SPACE(sizeof(alg_op))]; + struct cmsghdr *cmsg; + struct msghdr msg; + struct iovec iov; + ssize_t len; + + memset(cbuf, 0, sizeof(cbuf)); + memset(&msg, 0, sizeof(msg)); + + msg.msg_control = cbuf; + msg.msg_controllen = sizeof(cbuf); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_ALG; + cmsg->cmsg_type = ALG_SET_OP; + cmsg->cmsg_len = CMSG_LEN(sizeof(alg_op)); + memcpy(CMSG_DATA(cmsg), &alg_op, sizeof(alg_op)); + + iov.iov_base = (void *) inbuf; + iov.iov_len = inlen; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + len = sendmsg(fd, &msg, 0); + if (len < 0) + return false; + + len = read(fd, outbuf, outlen); + if (len < 0) + return false; + + return true; +} + +static inline void swap_buf(const uint8_t *src, uint8_t *dst, uint16_t len) +{ + int i; + + for (i = 0; i < len; i++) + dst[len - 1 - i] = src[i]; +} + +bool bt_crypto_sign_att(struct bt_crypto *crypto, const uint8_t key[16], + const uint8_t *m, uint16_t m_len, + uint32_t sign_cnt, uint8_t signature[12]) +{ + int fd; + int len; + uint8_t tmp[16], out[16]; + uint16_t msg_len = m_len + sizeof(uint32_t); + uint8_t msg[msg_len]; + uint8_t msg_s[msg_len]; + + if (!crypto) + return false; + + memset(msg, 0, msg_len); + memcpy(msg, m, m_len); + + /* Add sign_counter to the message */ + put_le32(sign_cnt, msg + m_len); + + /* The most significant octet of key corresponds to key[0] */ + swap_buf(key, tmp, 16); + + fd = alg_new(crypto->cmac_aes, tmp, 16); + if (fd < 0) + return false; + + /* Swap msg before signing */ + swap_buf(msg, msg_s, msg_len); + + len = send(fd, msg_s, msg_len, 0); + if (len < 0) { + close(fd); + return false; + } + + len = read(fd, out, 16); + if (len < 0) { + close(fd); + return false; + } + + close(fd); + + /* + * As to BT spec. 4.1 Vol[3], Part C, chapter 10.4.1 sign counter should + * be placed in the signature + */ + put_be32(sign_cnt, out + 8); + + /* + * The most significant octet of hash corresponds to out[0] - swap it. + * Then truncate in most significant bit first order to a length of + * 12 octets + */ + swap_buf(out, tmp, 16); + memcpy(signature, tmp + 4, 12); + + return true; +} +/* + * Security function e + * + * Security function e generates 128-bit encryptedData from a 128-bit key + * and 128-bit plaintextData using the AES-128-bit block cypher: + * + * encryptedData = e(key, plaintextData) + * + * The most significant octet of key corresponds to key[0], the most + * significant octet of plaintextData corresponds to in[0] and the + * most significant octet of encryptedData corresponds to out[0]. + * + */ +bool bt_crypto_e(struct bt_crypto *crypto, const uint8_t key[16], + const uint8_t plaintext[16], uint8_t encrypted[16]) +{ + uint8_t tmp[16], in[16], out[16]; + int fd; + + if (!crypto) + return false; + + /* The most significant octet of key corresponds to key[0] */ + swap_buf(key, tmp, 16); + + fd = alg_new(crypto->ecb_aes, tmp, 16); + if (fd < 0) + return false; + + + /* Most significant octet of plaintextData corresponds to in[0] */ + swap_buf(plaintext, in, 16); + + if (!alg_encrypt(fd, in, 16, out, 16)) { + close(fd); + return false; + } + + /* Most significant octet of encryptedData corresponds to out[0] */ + swap_buf(out, encrypted, 16); + + close(fd); + + return true; +} + +/* + * Random Address Hash function ah + * + * The random address hash function ah is used to generate a hash value + * that is used in resolvable private addresses. + * + * The following are inputs to the random address hash function ah: + * + * k is 128 bits + * r is 24 bits + * padding is 104 bits + * + * r is concatenated with padding to generate r' which is used as the + * 128-bit input parameter plaintextData to security function e: + * + * r' = padding || r + * + * The least significant octet of r becomes the least significant octet + * of r’ and the most significant octet of padding becomes the most + * significant octet of r'. + * + * For example, if the 24-bit value r is 0x423456 then r' is + * 0x00000000000000000000000000423456. + * + * The output of the random address function ah is: + * + * ah(k, r) = e(k, r') mod 2^24 + * + * The output of the security function e is then truncated to 24 bits by + * taking the least significant 24 bits of the output of e as the result + * of ah. + */ +bool bt_crypto_ah(struct bt_crypto *crypto, const uint8_t k[16], + const uint8_t r[3], uint8_t hash[3]) +{ + uint8_t rp[16]; + uint8_t encrypted[16]; + + if (!crypto) + return false; + + /* r' = padding || r */ + memcpy(rp, r, 3); + memset(rp + 3, 0, 13); + + /* e(k, r') */ + if (!bt_crypto_e(crypto, k, rp, encrypted)) + return false; + + /* ah(k, r) = e(k, r') mod 2^24 */ + memcpy(hash, encrypted, 3); + + return true; +} + +typedef struct { + uint64_t a, b; +} u128; + +static inline void u128_xor(const uint8_t p[16], const uint8_t q[16], + uint8_t r[16]) +{ + u128 pp, qq, rr; + + memcpy(&pp, p, 16); + memcpy(&qq, q, 16); + + rr.a = pp.a ^ qq.a; + rr.b = pp.b ^ qq.b; + + memcpy(r, &rr, 16); +} + +/* + * Confirm value generation function c1 + * + * During the pairing process confirm values are exchanged. This confirm + * value generation function c1 is used to generate the confirm values. + * + * The following are inputs to the confirm value generation function c1: + * + * k is 128 bits + * r is 128 bits + * pres is 56 bits + * preq is 56 bits + * iat is 1 bit + * ia is 48 bits + * rat is 1 bit + * ra is 48 bits + * padding is 32 bits of 0 + * + * iat is concatenated with 7-bits of 0 to create iat' which is 8 bits + * in length. iat is the least significant bit of iat' + * + * rat is concatenated with 7-bits of 0 to create rat' which is 8 bits + * in length. rat is the least significant bit of rat' + * + * pres, preq, rat' and iat' are concatenated to generate p1 which is + * XORed with r and used as 128-bit input parameter plaintextData to + * security function e: + * + * p1 = pres || preq || rat' || iat' + * + * The octet of iat' becomes the least significant octet of p1 and the + * most significant octet of pres becomes the most significant octet of + * p1. + * + * ra is concatenated with ia and padding to generate p2 which is XORed + * with the result of the security function e using p1 as the input + * paremter plaintextData and is then used as the 128-bit input + * parameter plaintextData to security function e: + * + * p2 = padding || ia || ra + * + * The least significant octet of ra becomes the least significant octet + * of p2 and the most significant octet of padding becomes the most + * significant octet of p2. + * + * The output of the confirm value generation function c1 is: + * + * c1(k, r, preq, pres, iat, rat, ia, ra) = e(k, e(k, r XOR p1) XOR p2) + * + * The 128-bit output of the security function e is used as the result + * of confirm value generation function c1. + */ +bool bt_crypto_c1(struct bt_crypto *crypto, const uint8_t k[16], + const uint8_t r[16], const uint8_t pres[7], + const uint8_t preq[7], uint8_t iat, + const uint8_t ia[6], uint8_t rat, + const uint8_t ra[6], uint8_t res[16]) +{ + uint8_t p1[16], p2[16]; + + /* p1 = pres || preq || _rat || _iat */ + p1[0] = iat; + p1[1] = rat; + memcpy(p1 + 2, preq, 7); + memcpy(p1 + 9, pres, 7); + + /* p2 = padding || ia || ra */ + memcpy(p2, ra, 6); + memcpy(p2 + 6, ia, 6); + memset(p2 + 12, 0, 4); + + /* res = r XOR p1 */ + u128_xor(r, p1, res); + + /* res = e(k, res) */ + if (!bt_crypto_e(crypto, k, res, res)) + return false; + + /* res = res XOR p2 */ + u128_xor(res, p2, res); + + /* res = e(k, res) */ + return bt_crypto_e(crypto, k, res, res); +} + +/* + * Key generation function s1 + * + * The key generation function s1 is used to generate the STK during the + * pairing process. + * + * The following are inputs to the key generation function s1: + * + * k is 128 bits + * r1 is 128 bits + * r2 is 128 bits + * + * The most significant 64-bits of r1 are discarded to generate r1' and + * the most significant 64-bits of r2 are discarded to generate r2'. + * + * r1' is concatenated with r2' to generate r' which is used as the + * 128-bit input parameter plaintextData to security function e: + * + * r' = r1' || r2' + * + * The least significant octet of r2' becomes the least significant + * octet of r' and the most significant octet of r1' becomes the most + * significant octet of r'. + * + * The output of the key generation function s1 is: + * + * s1(k, r1, r2) = e(k, r') + * + * The 128-bit output of the security function e is used as the result + * of key generation function s1. + */ +bool bt_crypto_s1(struct bt_crypto *crypto, const uint8_t k[16], + const uint8_t r1[16], const uint8_t r2[16], + uint8_t res[16]) +{ + memcpy(res, r2, 8); + memcpy(res + 8, r1, 8); + + return bt_crypto_e(crypto, k, res, res); +} + +static bool aes_cmac(struct bt_crypto *crypto, const uint8_t key[16], + const uint8_t *msg, size_t msg_len, uint8_t res[16]) +{ + uint8_t key_msb[16], out[16], msg_msb[CMAC_MSG_MAX]; + ssize_t len; + int fd; + + if (msg_len > CMAC_MSG_MAX) + return false; + + swap_buf(key, key_msb, 16); + fd = alg_new(crypto->cmac_aes, key_msb, 16); + if (fd < 0) + return false; + + swap_buf(msg, msg_msb, msg_len); + len = send(fd, msg_msb, msg_len, 0); + if (len < 0) { + close(fd); + return false; + } + + len = read(fd, out, 16); + if (len < 0) { + close(fd); + return false; + } + + swap_buf(out, res, 16); + + close(fd); + + return true; +} + +bool bt_crypto_f4(struct bt_crypto *crypto, uint8_t u[32], uint8_t v[32], + uint8_t x[16], uint8_t z, uint8_t res[16]) +{ + uint8_t m[65]; + + if (!crypto) + return false; + + m[0] = z; + memcpy(&m[1], v, 32); + memcpy(&m[33], u, 32); + + return aes_cmac(crypto, x, m, sizeof(m), res); +} + +bool bt_crypto_f5(struct bt_crypto *crypto, uint8_t w[32], uint8_t n1[16], + uint8_t n2[16], uint8_t a1[7], uint8_t a2[7], + uint8_t mackey[16], uint8_t ltk[16]) +{ + uint8_t btle[4] = { 0x65, 0x6c, 0x74, 0x62 }; + uint8_t salt[16] = { 0xbe, 0x83, 0x60, 0x5a, 0xdb, 0x0b, 0x37, 0x60, + 0x38, 0xa5, 0xf5, 0xaa, 0x91, 0x83, 0x88, 0x6c }; + uint8_t length[2] = { 0x00, 0x01 }; + uint8_t m[53], t[16]; + + if (!aes_cmac(crypto, salt, w, 32, t)) + return false; + + memcpy(&m[0], length, 2); + memcpy(&m[2], a2, 7); + memcpy(&m[9], a1, 7); + memcpy(&m[16], n2, 16); + memcpy(&m[32], n1, 16); + memcpy(&m[48], btle, 4); + + m[52] = 0; /* Counter */ + if (!aes_cmac(crypto, t, m, sizeof(m), mackey)) + return false; + + m[52] = 1; /* Counter */ + return aes_cmac(crypto, t, m, sizeof(m), ltk); +} + +bool bt_crypto_f6(struct bt_crypto *crypto, uint8_t w[16], uint8_t n1[16], + uint8_t n2[16], uint8_t r[16], uint8_t io_cap[3], + uint8_t a1[7], uint8_t a2[7], uint8_t res[16]) +{ + uint8_t m[65]; + + memcpy(&m[0], a2, 7); + memcpy(&m[7], a1, 7); + memcpy(&m[14], io_cap, 3); + memcpy(&m[17], r, 16); + memcpy(&m[33], n2, 16); + memcpy(&m[49], n1, 16); + + return aes_cmac(crypto, w, m, sizeof(m), res); +} + +bool bt_crypto_g2(struct bt_crypto *crypto, uint8_t u[32], uint8_t v[32], + uint8_t x[16], uint8_t y[16], uint32_t *val) +{ + uint8_t m[80], tmp[16]; + + memcpy(&m[0], y, 16); + memcpy(&m[16], v, 32); + memcpy(&m[48], u, 32); + + if (!aes_cmac(crypto, x, m, sizeof(m), tmp)) + return false; + + *val = get_le32(tmp); + *val %= 1000000; + + return true; +} + +bool bt_crypto_h6(struct bt_crypto *crypto, const uint8_t w[16], + const uint8_t keyid[4], uint8_t res[16]) +{ + if (!aes_cmac(crypto, w, keyid, 4, res)) + return false; + + return true; +} + +bool bt_crypto_gatt_hash(struct bt_crypto *crypto, struct iovec *iov, + size_t iov_len, uint8_t res[16]) +{ + const uint8_t key[16] = {}; + ssize_t len; + int fd; + + if (!crypto) + return false; + + fd = alg_new(crypto->cmac_aes, key, 16); + if (fd < 0) + return false; + + len = writev(fd, iov, iov_len); + if (len < 0) { + close(fd); + return false; + } + + len = read(fd, res, 16); + if (len < 0) { + close(fd); + return false; + } + + close(fd); + + return true; +} diff --git a/src/shared/crypto.h b/src/shared/crypto.h new file mode 100644 index 0000000..c58d2e1 --- /dev/null +++ b/src/shared/crypto.h @@ -0,0 +1,66 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include +#include + +struct bt_crypto; + +struct bt_crypto *bt_crypto_new(void); + +struct bt_crypto *bt_crypto_ref(struct bt_crypto *crypto); +void bt_crypto_unref(struct bt_crypto *crypto); + +bool bt_crypto_random_bytes(struct bt_crypto *crypto, + void *buf, uint8_t num_bytes); + +bool bt_crypto_e(struct bt_crypto *crypto, const uint8_t key[16], + const uint8_t plaintext[16], uint8_t encrypted[16]); +bool bt_crypto_ah(struct bt_crypto *crypto, const uint8_t k[16], + const uint8_t r[3], uint8_t hash[3]); +bool bt_crypto_c1(struct bt_crypto *crypto, const uint8_t k[16], + const uint8_t r[16], const uint8_t pres[7], + const uint8_t preq[7], uint8_t iat, + const uint8_t ia[6], uint8_t rat, + const uint8_t ra[6], uint8_t res[16]); +bool bt_crypto_s1(struct bt_crypto *crypto, const uint8_t k[16], + const uint8_t r1[16], const uint8_t r2[16], + uint8_t res[16]); +bool bt_crypto_f4(struct bt_crypto *crypto, uint8_t u[32], uint8_t v[32], + uint8_t x[16], uint8_t z, uint8_t res[16]); +bool bt_crypto_f5(struct bt_crypto *crypto, uint8_t w[32], uint8_t n1[16], + uint8_t n2[16], uint8_t a1[7], uint8_t a2[7], + uint8_t mackey[16], uint8_t ltk[16]); +bool bt_crypto_f6(struct bt_crypto *crypto, uint8_t w[16], uint8_t n1[16], + uint8_t n2[16], uint8_t r[16], uint8_t io_cap[3], + uint8_t a1[7], uint8_t a2[7], uint8_t res[16]); +bool bt_crypto_g2(struct bt_crypto *crypto, uint8_t u[32], uint8_t v[32], + uint8_t x[16], uint8_t y[16], uint32_t *val); +bool bt_crypto_h6(struct bt_crypto *crypto, const uint8_t w[16], + const uint8_t keyid[4], uint8_t res[16]); +bool bt_crypto_sign_att(struct bt_crypto *crypto, const uint8_t key[16], + const uint8_t *m, uint16_t m_len, + uint32_t sign_cnt, uint8_t signature[12]); +bool bt_crypto_gatt_hash(struct bt_crypto *crypto, struct iovec *iov, + size_t iov_len, uint8_t res[16]); diff --git a/src/shared/ecc.c b/src/shared/ecc.c new file mode 100644 index 0000000..1b45e0d --- /dev/null +++ b/src/shared/ecc.c @@ -0,0 +1,947 @@ +/* + * Copyright (c) 2013, Kenneth MacKay + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "ecc.h" + +/* 256-bit curve */ +#define ECC_BYTES 32 + +/* Number of uint64_t's needed */ +#define NUM_ECC_DIGITS (ECC_BYTES / 8) + +struct ecc_point { + uint64_t x[NUM_ECC_DIGITS]; + uint64_t y[NUM_ECC_DIGITS]; +}; + +#define MAX_TRIES 16 + +typedef struct { + uint64_t m_low; + uint64_t m_high; +} uint128_t; + +#define CURVE_P_32 { 0xFFFFFFFFFFFFFFFFull, 0x00000000FFFFFFFFull, \ + 0x0000000000000000ull, 0xFFFFFFFF00000001ull } + +#define CURVE_G_32 { \ + { 0xF4A13945D898C296ull, 0x77037D812DEB33A0ull, \ + 0xF8BCE6E563A440F2ull, 0x6B17D1F2E12C4247ull }, \ + { 0xCBB6406837BF51F5ull, 0x2BCE33576B315ECEull, \ + 0x8EE7EB4A7C0F9E16ull, 0x4FE342E2FE1A7F9Bull } \ +} + +#define CURVE_N_32 { 0xF3B9CAC2FC632551ull, 0xBCE6FAADA7179E84ull, \ + 0xFFFFFFFFFFFFFFFFull, 0xFFFFFFFF00000000ull } + +#define CURVE_B_32 { 0x3BCE3C3E27D2604Bull, 0x651D06B0CC53B0F6ull, \ + 0xB3EBBD55769886BCull, 0x5AC635D8AA3A93E7ull } + +static uint64_t curve_p[NUM_ECC_DIGITS] = CURVE_P_32; +static struct ecc_point curve_g = CURVE_G_32; +static uint64_t curve_n[NUM_ECC_DIGITS] = CURVE_N_32; +static uint64_t curve_b[NUM_ECC_DIGITS] = CURVE_B_32; + +static bool get_random_number(uint64_t *vli) +{ + char *ptr = (char *) vli; + size_t left = ECC_BYTES; + int fd; + + fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); + if (fd < 0) { + fd = open("/dev/random", O_RDONLY | O_CLOEXEC); + if (fd < 0) + return false; + } + + while (left > 0) { + ssize_t ret; + + ret = read(fd, ptr, left); + if (ret <= 0) { + close(fd); + return false; + } + + left -= ret; + ptr += ret; + } + + close(fd); + + return true; +} + +static void vli_clear(uint64_t *vli) +{ + int i; + + for (i = 0; i < NUM_ECC_DIGITS; i++) + vli[i] = 0; +} + +/* Returns true if vli == 0, false otherwise. */ +static bool vli_is_zero(const uint64_t *vli) +{ + int i; + + for (i = 0; i < NUM_ECC_DIGITS; i++) { + if (vli[i]) + return false; + } + + return true; +} + +/* Returns nonzero if bit bit of vli is set. */ +static uint64_t vli_test_bit(const uint64_t *vli, unsigned int bit) +{ + return (vli[bit / 64] & ((uint64_t) 1 << (bit % 64))); +} + +/* Counts the number of 64-bit "digits" in vli. */ +static unsigned int vli_num_digits(const uint64_t *vli) +{ + int i; + + /* Search from the end until we find a non-zero digit. + * We do it in reverse because we expect that most digits will + * be nonzero. + */ + for (i = NUM_ECC_DIGITS - 1; i >= 0 && vli[i] == 0; i--); + + return (i + 1); +} + +/* Counts the number of bits required for vli. */ +static unsigned int vli_num_bits(const uint64_t *vli) +{ + unsigned int i, num_digits; + uint64_t digit; + + num_digits = vli_num_digits(vli); + if (num_digits == 0) + return 0; + + digit = vli[num_digits - 1]; + for (i = 0; digit; i++) + digit >>= 1; + + return ((num_digits - 1) * 64 + i); +} + +/* Sets dest = src. */ +static void vli_set(uint64_t *dest, const uint64_t *src) +{ + int i; + + for (i = 0; i < NUM_ECC_DIGITS; i++) + dest[i] = src[i]; +} + +/* Returns sign of left - right. */ +static int vli_cmp(const uint64_t *left, const uint64_t *right) +{ + int i; + + for (i = NUM_ECC_DIGITS - 1; i >= 0; i--) { + if (left[i] > right[i]) + return 1; + else if (left[i] < right[i]) + return -1; + } + + return 0; +} + +/* Constant-time comparison function - secure way to compare long integers */ +/* Returns one if left == right, zero otherwise. */ +static bool vli_equal(const uint64_t *left, const uint64_t *right) +{ + uint64_t diff = 0; + int i; + + for (i = NUM_ECC_DIGITS - 1; i >= 0; --i) + diff |= (left[i] ^ right[i]); + + return (diff == 0); +} + +/* Computes result = in << c, returning carry. Can modify in place + * (if result == in). 0 < shift < 64. + */ +static uint64_t vli_lshift(uint64_t *result, const uint64_t *in, + unsigned int shift) +{ + uint64_t carry = 0; + int i; + + for (i = 0; i < NUM_ECC_DIGITS; i++) { + uint64_t temp = in[i]; + + result[i] = (temp << shift) | carry; + carry = temp >> (64 - shift); + } + + return carry; +} + +/* Computes vli = vli >> 1. */ +static void vli_rshift1(uint64_t *vli) +{ + uint64_t *end = vli; + uint64_t carry = 0; + + vli += NUM_ECC_DIGITS; + + while (vli-- > end) { + uint64_t temp = *vli; + *vli = (temp >> 1) | carry; + carry = temp << 63; + } +} + +/* Computes result = left + right, returning carry. Can modify in place. */ +static uint64_t vli_add(uint64_t *result, const uint64_t *left, + const uint64_t *right) +{ + uint64_t carry = 0; + int i; + + for (i = 0; i < NUM_ECC_DIGITS; i++) { + uint64_t sum; + + sum = left[i] + right[i] + carry; + if (sum != left[i]) + carry = (sum < left[i]); + + result[i] = sum; + } + + return carry; +} + +/* Computes result = left - right, returning borrow. Can modify in place. */ +static uint64_t vli_sub(uint64_t *result, const uint64_t *left, + const uint64_t *right) +{ + uint64_t borrow = 0; + int i; + + for (i = 0; i < NUM_ECC_DIGITS; i++) { + uint64_t diff; + + diff = left[i] - right[i] - borrow; + if (diff != left[i]) + borrow = (diff > left[i]); + + result[i] = diff; + } + + return borrow; +} + +static uint128_t mul_64_64(uint64_t left, uint64_t right) +{ + uint64_t a0 = left & 0xffffffffull; + uint64_t a1 = left >> 32; + uint64_t b0 = right & 0xffffffffull; + uint64_t b1 = right >> 32; + uint64_t m0 = a0 * b0; + uint64_t m1 = a0 * b1; + uint64_t m2 = a1 * b0; + uint64_t m3 = a1 * b1; + uint128_t result; + + m2 += (m0 >> 32); + m2 += m1; + + /* Overflow */ + if (m2 < m1) + m3 += 0x100000000ull; + + result.m_low = (m0 & 0xffffffffull) | (m2 << 32); + result.m_high = m3 + (m2 >> 32); + + return result; +} + +static uint128_t add_128_128(uint128_t a, uint128_t b) +{ + uint128_t result; + + result.m_low = a.m_low + b.m_low; + result.m_high = a.m_high + b.m_high + (result.m_low < a.m_low); + + return result; +} + +static void vli_mult(uint64_t *result, const uint64_t *left, + const uint64_t *right) +{ + uint128_t r01 = { 0, 0 }; + uint64_t r2 = 0; + unsigned int i, k; + + /* Compute each digit of result in sequence, maintaining the + * carries. + */ + for (k = 0; k < NUM_ECC_DIGITS * 2 - 1; k++) { + unsigned int min; + + if (k < NUM_ECC_DIGITS) + min = 0; + else + min = (k + 1) - NUM_ECC_DIGITS; + + for (i = min; i <= k && i < NUM_ECC_DIGITS; i++) { + uint128_t product; + + product = mul_64_64(left[i], right[k - i]); + + r01 = add_128_128(r01, product); + r2 += (r01.m_high < product.m_high); + } + + result[k] = r01.m_low; + r01.m_low = r01.m_high; + r01.m_high = r2; + r2 = 0; + } + + result[NUM_ECC_DIGITS * 2 - 1] = r01.m_low; +} + +static void vli_square(uint64_t *result, const uint64_t *left) +{ + uint128_t r01 = { 0, 0 }; + uint64_t r2 = 0; + int i, k; + + for (k = 0; k < NUM_ECC_DIGITS * 2 - 1; k++) { + unsigned int min; + + if (k < NUM_ECC_DIGITS) + min = 0; + else + min = (k + 1) - NUM_ECC_DIGITS; + + for (i = min; i <= k && i <= k - i; i++) { + uint128_t product; + + product = mul_64_64(left[i], left[k - i]); + + if (i < k - i) { + r2 += product.m_high >> 63; + product.m_high = (product.m_high << 1) | + (product.m_low >> 63); + product.m_low <<= 1; + } + + r01 = add_128_128(r01, product); + r2 += (r01.m_high < product.m_high); + } + + result[k] = r01.m_low; + r01.m_low = r01.m_high; + r01.m_high = r2; + r2 = 0; + } + + result[NUM_ECC_DIGITS * 2 - 1] = r01.m_low; +} + +/* Computes result = (left + right) % mod. + * Assumes that left < mod and right < mod, result != mod. + */ +static void vli_mod_add(uint64_t *result, const uint64_t *left, + const uint64_t *right, const uint64_t *mod) +{ + uint64_t carry; + + carry = vli_add(result, left, right); + + /* result > mod (result = mod + remainder), so subtract mod to + * get remainder. + */ + if (carry || vli_cmp(result, mod) >= 0) + vli_sub(result, result, mod); +} + +/* Computes result = (left - right) % mod. + * Assumes that left < mod and right < mod, result != mod. + */ +static void vli_mod_sub(uint64_t *result, const uint64_t *left, + const uint64_t *right, const uint64_t *mod) +{ + uint64_t borrow = vli_sub(result, left, right); + + /* In this case, p_result == -diff == (max int) - diff. + * Since -x % d == d - x, we can get the correct result from + * result + mod (with overflow). + */ + if (borrow) + vli_add(result, result, mod); +} + +/* Computes result = product % curve_p + from http://www.nsa.gov/ia/_files/nist-routines.pdf */ +static void vli_mmod_fast(uint64_t *result, const uint64_t *product) +{ + uint64_t tmp[NUM_ECC_DIGITS]; + int carry; + + /* t */ + vli_set(result, product); + + /* s1 */ + tmp[0] = 0; + tmp[1] = product[5] & 0xffffffff00000000ull; + tmp[2] = product[6]; + tmp[3] = product[7]; + carry = vli_lshift(tmp, tmp, 1); + carry += vli_add(result, result, tmp); + + /* s2 */ + tmp[1] = product[6] << 32; + tmp[2] = (product[6] >> 32) | (product[7] << 32); + tmp[3] = product[7] >> 32; + carry += vli_lshift(tmp, tmp, 1); + carry += vli_add(result, result, tmp); + + /* s3 */ + tmp[0] = product[4]; + tmp[1] = product[5] & 0xffffffff; + tmp[2] = 0; + tmp[3] = product[7]; + carry += vli_add(result, result, tmp); + + /* s4 */ + tmp[0] = (product[4] >> 32) | (product[5] << 32); + tmp[1] = (product[5] >> 32) | (product[6] & 0xffffffff00000000ull); + tmp[2] = product[7]; + tmp[3] = (product[6] >> 32) | (product[4] << 32); + carry += vli_add(result, result, tmp); + + /* d1 */ + tmp[0] = (product[5] >> 32) | (product[6] << 32); + tmp[1] = (product[6] >> 32); + tmp[2] = 0; + tmp[3] = (product[4] & 0xffffffff) | (product[5] << 32); + carry -= vli_sub(result, result, tmp); + + /* d2 */ + tmp[0] = product[6]; + tmp[1] = product[7]; + tmp[2] = 0; + tmp[3] = (product[4] >> 32) | (product[5] & 0xffffffff00000000ull); + carry -= vli_sub(result, result, tmp); + + /* d3 */ + tmp[0] = (product[6] >> 32) | (product[7] << 32); + tmp[1] = (product[7] >> 32) | (product[4] << 32); + tmp[2] = (product[4] >> 32) | (product[5] << 32); + tmp[3] = (product[6] << 32); + carry -= vli_sub(result, result, tmp); + + /* d4 */ + tmp[0] = product[7]; + tmp[1] = product[4] & 0xffffffff00000000ull; + tmp[2] = product[5]; + tmp[3] = product[6] & 0xffffffff00000000ull; + carry -= vli_sub(result, result, tmp); + + if (carry < 0) { + do { + carry += vli_add(result, result, curve_p); + } while (carry < 0); + } else { + while (carry || vli_cmp(curve_p, result) != 1) + carry -= vli_sub(result, result, curve_p); + } +} + +/* Computes result = (left * right) % curve_p. */ +static void vli_mod_mult_fast(uint64_t *result, const uint64_t *left, + const uint64_t *right) +{ + uint64_t product[2 * NUM_ECC_DIGITS]; + + vli_mult(product, left, right); + vli_mmod_fast(result, product); +} + +/* Computes result = left^2 % curve_p. */ +static void vli_mod_square_fast(uint64_t *result, const uint64_t *left) +{ + uint64_t product[2 * NUM_ECC_DIGITS]; + + vli_square(product, left); + vli_mmod_fast(result, product); +} + +#define EVEN(vli) (!(vli[0] & 1)) +/* Computes result = (1 / p_input) % mod. All VLIs are the same size. + * See "From Euclid's GCD to Montgomery Multiplication to the Great Divide" + * https://labs.oracle.com/techrep/2001/smli_tr-2001-95.pdf + */ +static void vli_mod_inv(uint64_t *result, const uint64_t *input, + const uint64_t *mod) +{ + uint64_t a[NUM_ECC_DIGITS], b[NUM_ECC_DIGITS]; + uint64_t u[NUM_ECC_DIGITS], v[NUM_ECC_DIGITS]; + uint64_t carry; + int cmp_result; + + if (vli_is_zero(input)) { + vli_clear(result); + return; + } + + vli_set(a, input); + vli_set(b, mod); + vli_clear(u); + u[0] = 1; + vli_clear(v); + + while ((cmp_result = vli_cmp(a, b)) != 0) { + carry = 0; + + if (EVEN(a)) { + vli_rshift1(a); + + if (!EVEN(u)) + carry = vli_add(u, u, mod); + + vli_rshift1(u); + if (carry) + u[NUM_ECC_DIGITS - 1] |= 0x8000000000000000ull; + } else if (EVEN(b)) { + vli_rshift1(b); + + if (!EVEN(v)) + carry = vli_add(v, v, mod); + + vli_rshift1(v); + if (carry) + v[NUM_ECC_DIGITS - 1] |= 0x8000000000000000ull; + } else if (cmp_result > 0) { + vli_sub(a, a, b); + vli_rshift1(a); + + if (vli_cmp(u, v) < 0) + vli_add(u, u, mod); + + vli_sub(u, u, v); + if (!EVEN(u)) + carry = vli_add(u, u, mod); + + vli_rshift1(u); + if (carry) + u[NUM_ECC_DIGITS - 1] |= 0x8000000000000000ull; + } else { + vli_sub(b, b, a); + vli_rshift1(b); + + if (vli_cmp(v, u) < 0) + vli_add(v, v, mod); + + vli_sub(v, v, u); + if (!EVEN(v)) + carry = vli_add(v, v, mod); + + vli_rshift1(v); + if (carry) + v[NUM_ECC_DIGITS - 1] |= 0x8000000000000000ull; + } + } + + vli_set(result, u); +} + +/* ------ Point operations ------ */ + +/* Returns true if p_point is the point at infinity, false otherwise. */ +static bool ecc_point_is_zero(const struct ecc_point *point) +{ + return (vli_is_zero(point->x) && vli_is_zero(point->y)); +} + +/* Point multiplication algorithm using Montgomery's ladder with co-Z + * coordinates. From http://eprint.iacr.org/2011/338.pdf + */ + +/* Double in place */ +static void ecc_point_double_jacobian(uint64_t *x1, uint64_t *y1, uint64_t *z1) +{ + /* t1 = x, t2 = y, t3 = z */ + uint64_t t4[NUM_ECC_DIGITS]; + uint64_t t5[NUM_ECC_DIGITS]; + + if (vli_is_zero(z1)) + return; + + vli_mod_square_fast(t4, y1); /* t4 = y1^2 */ + vli_mod_mult_fast(t5, x1, t4); /* t5 = x1*y1^2 = A */ + vli_mod_square_fast(t4, t4); /* t4 = y1^4 */ + vli_mod_mult_fast(y1, y1, z1); /* t2 = y1*z1 = z3 */ + vli_mod_square_fast(z1, z1); /* t3 = z1^2 */ + + vli_mod_add(x1, x1, z1, curve_p); /* t1 = x1 + z1^2 */ + vli_mod_add(z1, z1, z1, curve_p); /* t3 = 2*z1^2 */ + vli_mod_sub(z1, x1, z1, curve_p); /* t3 = x1 - z1^2 */ + vli_mod_mult_fast(x1, x1, z1); /* t1 = x1^2 - z1^4 */ + + vli_mod_add(z1, x1, x1, curve_p); /* t3 = 2*(x1^2 - z1^4) */ + vli_mod_add(x1, x1, z1, curve_p); /* t1 = 3*(x1^2 - z1^4) */ + if (vli_test_bit(x1, 0)) { + uint64_t carry = vli_add(x1, x1, curve_p); + vli_rshift1(x1); + x1[NUM_ECC_DIGITS - 1] |= carry << 63; + } else { + vli_rshift1(x1); + } + /* t1 = 3/2*(x1^2 - z1^4) = B */ + + vli_mod_square_fast(z1, x1); /* t3 = B^2 */ + vli_mod_sub(z1, z1, t5, curve_p); /* t3 = B^2 - A */ + vli_mod_sub(z1, z1, t5, curve_p); /* t3 = B^2 - 2A = x3 */ + vli_mod_sub(t5, t5, z1, curve_p); /* t5 = A - x3 */ + vli_mod_mult_fast(x1, x1, t5); /* t1 = B * (A - x3) */ + vli_mod_sub(t4, x1, t4, curve_p); /* t4 = B * (A - x3) - y1^4 = y3 */ + + vli_set(x1, z1); + vli_set(z1, y1); + vli_set(y1, t4); +} + +/* Modify (x1, y1) => (x1 * z^2, y1 * z^3) */ +static void apply_z(uint64_t *x1, uint64_t *y1, uint64_t *z) +{ + uint64_t t1[NUM_ECC_DIGITS]; + + vli_mod_square_fast(t1, z); /* z^2 */ + vli_mod_mult_fast(x1, x1, t1); /* x1 * z^2 */ + vli_mod_mult_fast(t1, t1, z); /* z^3 */ + vli_mod_mult_fast(y1, y1, t1); /* y1 * z^3 */ +} + +/* P = (x1, y1) => 2P, (x2, y2) => P' */ +static void xycz_initial_double(uint64_t *x1, uint64_t *y1, uint64_t *x2, + uint64_t *y2, uint64_t *p_initial_z) +{ + uint64_t z[NUM_ECC_DIGITS]; + + vli_set(x2, x1); + vli_set(y2, y1); + + vli_clear(z); + z[0] = 1; + + if (p_initial_z) + vli_set(z, p_initial_z); + + apply_z(x1, y1, z); + + ecc_point_double_jacobian(x1, y1, z); + + apply_z(x2, y2, z); +} + +/* Input P = (x1, y1, Z), Q = (x2, y2, Z) + * Output P' = (x1', y1', Z3), P + Q = (x3, y3, Z3) + * or P => P', Q => P + Q + */ +static void xycz_add(uint64_t *x1, uint64_t *y1, uint64_t *x2, uint64_t *y2) +{ + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ + uint64_t t5[NUM_ECC_DIGITS]; + + vli_mod_sub(t5, x2, x1, curve_p); /* t5 = x2 - x1 */ + vli_mod_square_fast(t5, t5); /* t5 = (x2 - x1)^2 = A */ + vli_mod_mult_fast(x1, x1, t5); /* t1 = x1*A = B */ + vli_mod_mult_fast(x2, x2, t5); /* t3 = x2*A = C */ + vli_mod_sub(y2, y2, y1, curve_p); /* t4 = y2 - y1 */ + vli_mod_square_fast(t5, y2); /* t5 = (y2 - y1)^2 = D */ + + vli_mod_sub(t5, t5, x1, curve_p); /* t5 = D - B */ + vli_mod_sub(t5, t5, x2, curve_p); /* t5 = D - B - C = x3 */ + vli_mod_sub(x2, x2, x1, curve_p); /* t3 = C - B */ + vli_mod_mult_fast(y1, y1, x2); /* t2 = y1*(C - B) */ + vli_mod_sub(x2, x1, t5, curve_p); /* t3 = B - x3 */ + vli_mod_mult_fast(y2, y2, x2); /* t4 = (y2 - y1)*(B - x3) */ + vli_mod_sub(y2, y2, y1, curve_p); /* t4 = y3 */ + + vli_set(x2, t5); +} + +/* Input P = (x1, y1, Z), Q = (x2, y2, Z) + * Output P + Q = (x3, y3, Z3), P - Q = (x3', y3', Z3) + * or P => P - Q, Q => P + Q + */ +static void xycz_add_c(uint64_t *x1, uint64_t *y1, uint64_t *x2, uint64_t *y2) +{ + /* t1 = X1, t2 = Y1, t3 = X2, t4 = Y2 */ + uint64_t t5[NUM_ECC_DIGITS]; + uint64_t t6[NUM_ECC_DIGITS]; + uint64_t t7[NUM_ECC_DIGITS]; + + vli_mod_sub(t5, x2, x1, curve_p); /* t5 = x2 - x1 */ + vli_mod_square_fast(t5, t5); /* t5 = (x2 - x1)^2 = A */ + vli_mod_mult_fast(x1, x1, t5); /* t1 = x1*A = B */ + vli_mod_mult_fast(x2, x2, t5); /* t3 = x2*A = C */ + vli_mod_add(t5, y2, y1, curve_p); /* t4 = y2 + y1 */ + vli_mod_sub(y2, y2, y1, curve_p); /* t4 = y2 - y1 */ + + vli_mod_sub(t6, x2, x1, curve_p); /* t6 = C - B */ + vli_mod_mult_fast(y1, y1, t6); /* t2 = y1 * (C - B) */ + vli_mod_add(t6, x1, x2, curve_p); /* t6 = B + C */ + vli_mod_square_fast(x2, y2); /* t3 = (y2 - y1)^2 */ + vli_mod_sub(x2, x2, t6, curve_p); /* t3 = x3 */ + + vli_mod_sub(t7, x1, x2, curve_p); /* t7 = B - x3 */ + vli_mod_mult_fast(y2, y2, t7); /* t4 = (y2 - y1)*(B - x3) */ + vli_mod_sub(y2, y2, y1, curve_p); /* t4 = y3 */ + + vli_mod_square_fast(t7, t5); /* t7 = (y2 + y1)^2 = F */ + vli_mod_sub(t7, t7, t6, curve_p); /* t7 = x3' */ + vli_mod_sub(t6, t7, x1, curve_p); /* t6 = x3' - B */ + vli_mod_mult_fast(t6, t6, t5); /* t6 = (y2 + y1)*(x3' - B) */ + vli_mod_sub(y1, t6, y1, curve_p); /* t2 = y3' */ + + vli_set(x1, t7); +} + +static void ecc_point_mult(struct ecc_point *result, + const struct ecc_point *point, + uint64_t *scalar, uint64_t *initial_z, + int num_bits) +{ + /* R0 and R1 */ + uint64_t rx[2][NUM_ECC_DIGITS]; + uint64_t ry[2][NUM_ECC_DIGITS]; + uint64_t z[NUM_ECC_DIGITS]; + int i, nb; + + vli_set(rx[1], point->x); + vli_set(ry[1], point->y); + + xycz_initial_double(rx[1], ry[1], rx[0], ry[0], initial_z); + + for (i = num_bits - 2; i > 0; i--) { + nb = !vli_test_bit(scalar, i); + xycz_add_c(rx[1 - nb], ry[1 - nb], rx[nb], ry[nb]); + xycz_add(rx[nb], ry[nb], rx[1 - nb], ry[1 - nb]); + } + + nb = !vli_test_bit(scalar, 0); + xycz_add_c(rx[1 - nb], ry[1 - nb], rx[nb], ry[nb]); + + /* Find final 1/Z value. */ + vli_mod_sub(z, rx[1], rx[0], curve_p); /* X1 - X0 */ + vli_mod_mult_fast(z, z, ry[1 - nb]); /* Yb * (X1 - X0) */ + vli_mod_mult_fast(z, z, point->x); /* xP * Yb * (X1 - X0) */ + vli_mod_inv(z, z, curve_p); /* 1 / (xP * Yb * (X1 - X0)) */ + vli_mod_mult_fast(z, z, point->y); /* yP / (xP * Yb * (X1 - X0)) */ + vli_mod_mult_fast(z, z, rx[1 - nb]); /* Xb * yP / (xP * Yb * (X1 - X0)) */ + /* End 1/Z calculation */ + + xycz_add(rx[nb], ry[nb], rx[1 - nb], ry[1 - nb]); + + apply_z(rx[0], ry[0], z); + + vli_set(result->x, rx[0]); + vli_set(result->y, ry[0]); +} + +static bool ecc_valid_point(const struct ecc_point *point) +{ + uint64_t tmp1[NUM_ECC_DIGITS]; + uint64_t tmp2[NUM_ECC_DIGITS]; + uint64_t _3[NUM_ECC_DIGITS] = { 3 }; /* -a = 3 */ + + /* The point at infinity is invalid. */ + if (ecc_point_is_zero(point)) + return false; + + /* x and y must be smaller than p. */ + if (vli_cmp(curve_p, point->x) != 1 || + vli_cmp(curve_p, point->y) != 1) + return false; + + /* Computes result = y^2. */ + vli_mod_square_fast(tmp1, point->y); + + /* Computes result = x^3 + ax + b. result must not overlap x. */ + vli_mod_square_fast(tmp2, point->x); /* r = x^2 */ + vli_mod_sub(tmp2, tmp2, _3, curve_p); /* r = x^2 - 3 */ + vli_mod_mult_fast(tmp2, tmp2, point->x); /* r = x^3 - 3x */ + vli_mod_add(tmp2, tmp2, curve_b, curve_p); /* r = x^3 - 3x + b */ + + /* Make sure that y^2 == x^3 + ax + b */ + return vli_equal(tmp1, tmp2); +} + +/* Little endian byte-array to native conversion */ +static void ecc_bytes2native(const uint8_t bytes[ECC_BYTES], + uint64_t native[NUM_ECC_DIGITS]) +{ + int i; + + for (i = 0; i < NUM_ECC_DIGITS; i++) { + const uint8_t *digit = bytes + 8 * (NUM_ECC_DIGITS - 1 - i); + + native[NUM_ECC_DIGITS - 1 - i] = + ((uint64_t) digit[0] << 0) | + ((uint64_t) digit[1] << 8) | + ((uint64_t) digit[2] << 16) | + ((uint64_t) digit[3] << 24) | + ((uint64_t) digit[4] << 32) | + ((uint64_t) digit[5] << 40) | + ((uint64_t) digit[6] << 48) | + ((uint64_t) digit[7] << 56); + } +} + +/* Native to little endian byte-array conversion */ +static void ecc_native2bytes(const uint64_t native[NUM_ECC_DIGITS], + uint8_t bytes[ECC_BYTES]) +{ + int i; + + for (i = 0; i < NUM_ECC_DIGITS; i++) { + uint8_t *digit = bytes + 8 * (NUM_ECC_DIGITS - 1 - i); + + digit[0] = native[NUM_ECC_DIGITS - 1 - i] >> 0; + digit[1] = native[NUM_ECC_DIGITS - 1 - i] >> 8; + digit[2] = native[NUM_ECC_DIGITS - 1 - i] >> 16; + digit[3] = native[NUM_ECC_DIGITS - 1 - i] >> 24; + digit[4] = native[NUM_ECC_DIGITS - 1 - i] >> 32; + digit[5] = native[NUM_ECC_DIGITS - 1 - i] >> 40; + digit[6] = native[NUM_ECC_DIGITS - 1 - i] >> 48; + digit[7] = native[NUM_ECC_DIGITS - 1 - i] >> 56; + } +} + +bool ecc_make_public_key(const uint8_t private_key[32], uint8_t public_key[64]) +{ + struct ecc_point pk; + uint64_t priv[NUM_ECC_DIGITS]; + + ecc_bytes2native(private_key, priv); + + if (vli_is_zero(priv)) + return false; + + /* Make sure the private key is in the range [1, n-1]. */ + if (vli_cmp(curve_n, priv) != 1) + return false; + + ecc_point_mult(&pk, &curve_g, priv, NULL, vli_num_bits(priv)); + + if (ecc_point_is_zero(&pk)) + return false; + + ecc_native2bytes(pk.x, public_key); + ecc_native2bytes(pk.y, &public_key[32]); + + return true; +} + +bool ecc_make_key(uint8_t public_key[64], uint8_t private_key[32]) +{ + struct ecc_point pk; + uint64_t priv[NUM_ECC_DIGITS]; + unsigned int tries = 0; + + do { + if (!get_random_number(priv) || (tries++ >= MAX_TRIES)) + return false; + + if (vli_is_zero(priv)) + continue; + + /* Make sure the private key is in the range [1, n-1]. */ + if (vli_cmp(curve_n, priv) != 1) + continue; + + ecc_point_mult(&pk, &curve_g, priv, NULL, vli_num_bits(priv)); + } while (ecc_point_is_zero(&pk)); + + ecc_native2bytes(priv, private_key); + ecc_native2bytes(pk.x, public_key); + ecc_native2bytes(pk.y, &public_key[32]); + + return true; +} + +bool ecc_valid_public_key(const uint8_t public_key[64]) +{ + struct ecc_point pk; + + ecc_bytes2native(public_key, pk.x); + ecc_bytes2native(&public_key[32], pk.y); + + return ecc_valid_point(&pk); +} + +bool ecdh_shared_secret(const uint8_t public_key[64], + const uint8_t private_key[32], + uint8_t secret[32]) +{ + uint64_t priv[NUM_ECC_DIGITS]; + uint64_t rand[NUM_ECC_DIGITS]; + struct ecc_point product, pk; + + if (!get_random_number(rand)) + return false; + + ecc_bytes2native(public_key, pk.x); + ecc_bytes2native(&public_key[32], pk.y); + + if (!ecc_valid_point(&pk)) + return false; + + ecc_bytes2native(private_key, priv); + + ecc_point_mult(&product, &pk, priv, rand, vli_num_bits(priv)); + + ecc_native2bytes(product.x, secret); + + return !ecc_point_is_zero(&product); +} diff --git a/src/shared/ecc.h b/src/shared/ecc.h new file mode 100644 index 0000000..8c15e4e --- /dev/null +++ b/src/shared/ecc.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2013, Kenneth MacKay + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +/* Create a public key from a private key. + * + * Inputs: + * private_key - Your private key. + * + * Outputs: + * public_key - Will be filled in with the public key. + * + * Returns true if the public key was generated successfully, false + * if an error occurred. The keys are with the LSB first. + */ +bool ecc_make_public_key(const uint8_t private_key[32], uint8_t public_key[64]); + +/* Create a public/private key pair. + * + * Outputs: + * public_key - Will be filled in with the public key. + * private_key - Will be filled in with the private key. + * + * Returns true if the key pair was generated successfully, false + * if an error occurred. The keys are with the LSB first. + */ +bool ecc_make_key(uint8_t public_key[64], uint8_t private_key[32]); + +/* Check to see if a public key is valid. + * + * Inputs: + * public_key - The public key to check. + * + * Returns true if the public key is valid, false if it is invalid. +*/ +bool ecc_valid_public_key(const uint8_t public_key[64]); + +/* Compute a shared secret given your secret key and someone else's + * public key. + * Note: It is recommended that you hash the result of ecdh_shared_secret + * before using it for symmetric encryption or HMAC. + * + * Inputs: + * public_key - The public key of the remote party. + * private_key - Your private key. + * + * Outputs: + * secret - Will be filled in with the shared secret value. + * + * Returns true if the shared secret was generated successfully, false + * if an error occurred. Both input and output parameters are with the + * LSB first. + */ +bool ecdh_shared_secret(const uint8_t public_key[64], + const uint8_t private_key[32], + uint8_t secret[32]); diff --git a/src/shared/gap.c b/src/shared/gap.c new file mode 100644 index 0000000..0b8d073 --- /dev/null +++ b/src/shared/gap.c @@ -0,0 +1,275 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/mgmt.h" + +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/mgmt.h" +#include "src/shared/gap.h" + +#define FLAG_MGMT_CONN_CONTROL (0 << 1) + +struct bt_gap { + int ref_count; + uint16_t index; + struct mgmt *mgmt; + + uint8_t mgmt_version; + uint16_t mgmt_revision; + bool mgmt_ready; + + unsigned long flags; + + bt_gap_ready_func_t ready_handler; + bt_gap_destroy_func_t ready_destroy; + void *ready_data; + + uint8_t static_addr[6]; + uint8_t local_irk[16]; + struct queue *irk_list; +}; + +struct irk_entry { + uint8_t addr_type; + uint8_t addr[6]; + uint8_t key[16]; +}; + +static void irk_entry_free(void *data) +{ + struct irk_entry *irk = data; + + free(irk); +} + +static void ready_status(struct bt_gap *gap, bool status) +{ + gap->mgmt_ready = status; + + if (gap->ready_handler) + gap->ready_handler(status, gap->ready_data); +} + +static void read_commands_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct bt_gap *gap = user_data; + const struct mgmt_rp_read_commands *rp = param; + uint16_t num_commands, num_events; + size_t expected_len; + int i; + + if (status != MGMT_STATUS_SUCCESS) { + ready_status(gap, false); + return; + } + + if (length < sizeof(*rp)) { + ready_status(gap, false); + return; + } + + num_commands = le16_to_cpu(rp->num_commands); + num_events = le16_to_cpu(rp->num_events); + + expected_len = sizeof(*rp) + num_commands * sizeof(uint16_t) + + num_events * sizeof(uint16_t); + + if (length < expected_len) { + ready_status(gap, false); + return; + } + + for (i = 0; i < num_commands; i++) { + uint16_t op = get_le16(rp->opcodes + i); + + if (op == MGMT_OP_ADD_DEVICE) + gap->flags |= FLAG_MGMT_CONN_CONTROL; + } + + ready_status(gap, true); +} + +static void read_version_complete(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct bt_gap *gap = user_data; + const struct mgmt_rp_read_version *rp = param; + + if (status != MGMT_STATUS_SUCCESS) { + ready_status(gap, false); + return; + } + + if (length < sizeof(*rp)) { + ready_status(gap, false); + return; + } + + gap->mgmt_version = rp->version; + gap->mgmt_revision = le16_to_cpu(rp->revision); + + if (!mgmt_send(gap->mgmt, MGMT_OP_READ_COMMANDS, + MGMT_INDEX_NONE, 0, NULL, + read_commands_complete, gap, NULL)) { + ready_status(gap, false); + return; + } +} + +struct bt_gap *bt_gap_new_default(void) +{ + return bt_gap_new_index(0x0000); +} + +struct bt_gap *bt_gap_new_index(uint16_t index) +{ + struct bt_gap *gap; + + if (index == MGMT_INDEX_NONE) + return NULL; + + gap = new0(struct bt_gap, 1); + gap->index = index; + + gap->mgmt = mgmt_new_default(); + if (!gap->mgmt) { + free(gap); + return NULL; + } + + gap->irk_list = queue_new(); + gap->mgmt_ready = false; + + if (!mgmt_send(gap->mgmt, MGMT_OP_READ_VERSION, + MGMT_INDEX_NONE, 0, NULL, + read_version_complete, gap, NULL)) { + mgmt_unref(gap->mgmt); + return NULL; + } + + return bt_gap_ref(gap); +} + +struct bt_gap *bt_gap_ref(struct bt_gap *gap) +{ + if (!gap) + return NULL; + + __sync_fetch_and_add(&gap->ref_count, 1); + + return gap; +} + +void bt_gap_unref(struct bt_gap *gap) +{ + if (!gap) + return; + + if (__sync_sub_and_fetch(&gap->ref_count, 1)) + return; + + gap->mgmt_ready = false; + + mgmt_cancel_all(gap->mgmt); + mgmt_unregister_all(gap->mgmt); + + if (gap->ready_destroy) + gap->ready_destroy(gap->ready_data); + + queue_destroy(gap->irk_list, irk_entry_free); + + mgmt_unref(gap->mgmt); + + free(gap); +} + +bool bt_gap_set_ready_handler(struct bt_gap *gap, + bt_gap_ready_func_t handler, void *user_data, + bt_gap_destroy_func_t destroy) +{ + if (!gap) + return false; + + if (gap->ready_destroy) + gap->ready_destroy(gap->ready_data); + + gap->ready_handler = handler; + gap->ready_destroy = destroy; + gap->ready_data = user_data; + + return true; +} + +bool bt_gap_set_static_addr(struct bt_gap *gap, uint8_t addr[6]) +{ + if (!gap) + return false; + + memcpy(gap->static_addr, addr, 6); + + return true; +} + +bool bt_gap_set_local_irk(struct bt_gap *gap, uint8_t key[16]) +{ + if (!gap) + return false; + + memcpy(gap->local_irk, key, 16); + + return true; +} + +bool bt_gap_add_peer_irk(struct bt_gap *gap, uint8_t addr_type, + uint8_t addr[6], uint8_t key[16]) +{ + struct irk_entry *irk; + + if (!gap) + return false; + + if (addr_type > BT_GAP_ADDR_TYPE_LE_RANDOM) + return false; + + irk = new0(struct irk_entry, 1); + irk->addr_type = addr_type; + memcpy(irk->addr, addr, 6); + memcpy(irk->key, key, 16); + + if (!queue_push_tail(gap->irk_list, irk)) { + free(irk); + return false; + } + + return true; +} diff --git a/src/shared/gap.h b/src/shared/gap.h new file mode 100644 index 0000000..52c264a --- /dev/null +++ b/src/shared/gap.h @@ -0,0 +1,49 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include + +#define BT_GAP_ADDR_TYPE_BREDR 0x00 +#define BT_GAP_ADDR_TYPE_LE_PUBLIC 0x01 +#define BT_GAP_ADDR_TYPE_LE_RANDOM 0x02 + +struct bt_gap; + +struct bt_gap *bt_gap_new_default(void); +struct bt_gap *bt_gap_new_index(uint16_t index); + +struct bt_gap *bt_gap_ref(struct bt_gap *gap); +void bt_gap_unref(struct bt_gap *gap); + +typedef void (*bt_gap_destroy_func_t)(void *user_data); +typedef void (*bt_gap_ready_func_t)(bool success, void *user_data); + +bool bt_gap_set_ready_handler(struct bt_gap *gap, + bt_gap_ready_func_t handler, void *user_data, + bt_gap_destroy_func_t destroy); + +bool bt_gap_set_static_addr(struct bt_gap *gap, uint8_t addr[6]); +bool bt_gap_set_local_irk(struct bt_gap *gap, uint8_t key[16]); +bool bt_gap_add_peer_irk(struct bt_gap *gap, uint8_t addr_type, + uint8_t addr[6], uint8_t key[16]); diff --git a/src/shared/gatt-client.c b/src/shared/gatt-client.c new file mode 100644 index 0000000..29254cb --- /dev/null +++ b/src/shared/gatt-client.c @@ -0,0 +1,3483 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "src/shared/att.h" +#include "lib/bluetooth.h" +#include "lib/uuid.h" +#include "src/shared/gatt-helpers.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" + +#include +#include +#include + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define UUID_BYTES (BT_GATT_UUID_SIZE * sizeof(uint8_t)) + +#define GATT_SVC_UUID 0x1801 +#define SVC_CHNGD_UUID 0x2a05 + +struct ready_cb { + bt_gatt_client_callback_t callback; + bt_gatt_client_destroy_func_t destroy; + void *data; +}; + +struct bt_gatt_client { + struct bt_att *att; + int ref_count; + + struct bt_gatt_client *parent; + struct queue *clones; + + struct queue *ready_cbs; + + bt_gatt_client_service_changed_callback_t svc_chngd_callback; + bt_gatt_client_destroy_func_t svc_chngd_destroy; + void *svc_chngd_data; + + bt_gatt_client_debug_func_t debug_callback; + bt_gatt_client_destroy_func_t debug_destroy; + void *debug_data; + + struct gatt_db *db; + bool in_init; + bool ready; + + /* + * Queue of long write requests. An error during "prepare write" + * requests can result in a cancel through "execute write". To prevent + * cancelation of prepared writes to the wrong attribute and multiple + * requests to the same attribute that may result in a corrupted final + * value, we avoid interleaving prepared writes. + */ + struct queue *long_write_queue; + bool in_long_write; + + unsigned int reliable_write_session_id; + + /* List of registered disconnect/notification/indication callbacks */ + struct queue *notify_list; + struct queue *notify_chrcs; + int next_reg_id; + unsigned int disc_id, notify_id, ind_id; + + /* + * Handles of the GATT Service and the Service Changed characteristic + * value handle. These will have the value 0 if they are not present on + * the remote peripheral. + */ + unsigned int svc_chngd_ind_id; + bool svc_chngd_registered; + struct queue *svc_chngd_queue; /* Queued service changed events */ + bool in_svc_chngd; + + /* + * List of pending read/write operations. For operations that span + * across multiple PDUs, this list provides a mapping from an operation + * id to an ATT request id. + */ + struct queue *pending_requests; + unsigned int next_request_id; + + struct bt_gatt_request *discovery_req; + unsigned int mtu_req_id; +}; + +struct request { + struct bt_gatt_client *client; + bool long_write; + bool prep_write; + bool removed; + int ref_count; + unsigned int id; + unsigned int att_id; + void *data; + void (*destroy)(void *); +}; + +static struct request *request_ref(struct request *req) +{ + __sync_fetch_and_add(&req->ref_count, 1); + + return req; +} + +static struct request *request_create(struct bt_gatt_client *client) +{ + struct request *req; + + if (!client->att) + return NULL; + + req = new0(struct request, 1); + + if (client->next_request_id < 1) + client->next_request_id = 1; + + queue_push_tail(client->pending_requests, req); + req->client = client; + req->id = client->next_request_id++; + + return request_ref(req); +} + +static void request_unref(void *data) +{ + struct request *req = data; + + if (__sync_sub_and_fetch(&req->ref_count, 1)) + return; + + if (req->destroy) + req->destroy(req->data); + + if (!req->removed) + queue_remove(req->client->pending_requests, req); + + free(req); +} + +struct notify_chrc { + uint16_t value_handle; + uint16_t ccc_handle; + uint16_t properties; + int notify_count; /* Reference count of registered notify callbacks */ + + /* Pending calls to register_notify are queued here so that they can be + * processed after a write that modifies the CCC descriptor. + */ + struct queue *reg_notify_queue; + unsigned int ccc_write_id; +}; + +struct notify_data { + struct bt_gatt_client *client; + unsigned int id; + unsigned int att_id; + int ref_count; + struct notify_chrc *chrc; + bt_gatt_client_register_callback_t callback; + bt_gatt_client_notify_callback_t notify; + void *user_data; + bt_gatt_client_destroy_func_t destroy; +}; + +static struct notify_data *notify_data_ref(struct notify_data *notify_data) +{ + __sync_fetch_and_add(¬ify_data->ref_count, 1); + + return notify_data; +} + +static void notify_data_unref(void *data) +{ + struct notify_data *notify_data = data; + + if (__sync_sub_and_fetch(¬ify_data->ref_count, 1)) + return; + + if (notify_data->destroy) + notify_data->destroy(notify_data->user_data); + + free(notify_data); +} + +static void find_ccc(struct gatt_db_attribute *attr, void *user_data) +{ + struct gatt_db_attribute **ccc_ptr = user_data; + bt_uuid_t uuid; + + if (*ccc_ptr) + return; + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + + if (bt_uuid_cmp(&uuid, gatt_db_attribute_get_type(attr))) + return; + + *ccc_ptr = attr; +} + +static struct notify_chrc *notify_chrc_create(struct bt_gatt_client *client, + uint16_t value_handle) +{ + struct gatt_db_attribute *attr, *ccc; + struct notify_chrc *chrc; + bt_uuid_t uuid; + uint8_t properties; + + /* Check that chrc_value_handle belongs to a known characteristic */ + attr = gatt_db_get_attribute(client->db, value_handle - 1); + if (!attr) + return NULL; + + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + if (bt_uuid_cmp(&uuid, gatt_db_attribute_get_type(attr))) + return NULL; + + if (!gatt_db_attribute_get_char_data(attr, NULL, NULL, &properties, + NULL, NULL)) + return NULL; + + chrc = new0(struct notify_chrc, 1); + + chrc->reg_notify_queue = queue_new(); + if (!chrc->reg_notify_queue) { + free(chrc); + return NULL; + } + + /* + * Find the CCC characteristic. Some characteristics that allow + * notifications may not have a CCC descriptor. We treat these as + * automatically successful. + */ + ccc = NULL; + gatt_db_service_foreach_desc(attr, find_ccc, &ccc); + if (ccc) + chrc->ccc_handle = gatt_db_attribute_get_handle(ccc); + + chrc->value_handle = value_handle; + chrc->properties = properties; + + queue_push_tail(client->notify_chrcs, chrc); + + return chrc; +} + +static void notify_chrc_free(void *data) +{ + struct notify_chrc *chrc = data; + + queue_destroy(chrc->reg_notify_queue, notify_data_unref); + free(chrc); +} + +static bool match_notify_data_id(const void *a, const void *b) +{ + const struct notify_data *notify_data = a; + unsigned int id = PTR_TO_UINT(b); + + return notify_data->id == id; +} + +struct handle_range { + uint16_t start; + uint16_t end; +}; + +static void notify_data_cleanup(void *data) +{ + struct notify_data *notify_data = data; + + if (notify_data->att_id) + bt_att_cancel(notify_data->client->att, notify_data->att_id); + + notify_data_unref(notify_data); +} + +struct discovery_op; + +typedef void (*discovery_op_complete_func_t)(struct discovery_op *op, + bool success, + uint8_t att_ecode); +typedef void (*discovery_op_fail_func_t)(struct discovery_op *op); + +struct discovery_op { + struct bt_gatt_client *client; + struct queue *discov_ranges; + struct queue *pending_svcs; + struct queue *pending_chrcs; + struct queue *ext_prop_desc; + struct gatt_db_attribute *cur_svc; + struct gatt_db_attribute *hash; + bool success; + uint16_t start; + uint16_t end; + uint16_t last; + uint16_t svc_first; + uint16_t svc_last; + unsigned int db_id; + int ref_count; + discovery_op_complete_func_t complete_func; + discovery_op_fail_func_t failure_func; +}; + +static void discovery_op_free(struct discovery_op *op) +{ + if (op->db_id > 0) + gatt_db_unregister(op->client->db, op->db_id); + + queue_destroy(op->discov_ranges, free); + queue_destroy(op->pending_svcs, NULL); + queue_destroy(op->pending_chrcs, free); + queue_destroy(op->ext_prop_desc, NULL); + free(op); +} + +static bool read_db_hash(struct discovery_op *op); + +static void discovery_op_complete(struct discovery_op *op, bool success, + uint8_t err) +{ + const struct queue_entry *svc; + + op->success = success; + + /* Read database hash if discovery has been successful */ + if (success && read_db_hash(op)) + return; + + /* + * Unregister remove callback so it is not called when clearing unused + * range. + */ + gatt_db_unregister(op->client->db, op->db_id); + op->db_id = 0; + + /* Remove services pending */ + for (svc = queue_get_entries(op->pending_svcs); svc; svc = svc->next) { + struct gatt_db_attribute *attr = svc->data; + uint16_t start, end; + + gatt_db_attribute_get_service_data(attr, &start, &end, + NULL, NULL); + + util_debug(op->client->debug_callback, op->client->debug_data, + "service disappeared: start 0x%04x end 0x%04x", + start, end); + + gatt_db_remove_service(op->client->db, attr); + } + + /* Reset remaining range */ + if (op->last != UINT16_MAX) + gatt_db_clear_range(op->client->db, op->last + 1, UINT16_MAX); + + op->complete_func(op, success, err); +} + +static void discovery_load_services(struct gatt_db_attribute *attr, + void *user_data) +{ + struct discovery_op *op = user_data; + + queue_push_tail(op->pending_svcs, attr); +} + +static void discovery_service_changed(struct gatt_db_attribute *attr, + void *user_data) +{ + struct discovery_op *op = user_data; + + queue_remove(op->pending_svcs, attr); +} + +static struct discovery_op *discovery_op_create(struct bt_gatt_client *client, + uint16_t start, uint16_t end, + discovery_op_complete_func_t complete_func, + discovery_op_fail_func_t failure_func) +{ + struct discovery_op *op; + struct handle_range *range; + + op = new0(struct discovery_op, 1); + op->discov_ranges = queue_new(); + op->pending_svcs = queue_new(); + op->pending_chrcs = queue_new(); + op->ext_prop_desc = queue_new(); + op->client = client; + op->complete_func = complete_func; + op->failure_func = failure_func; + op->start = start; + op->end = end; + op->last = gatt_db_isempty(client->db) ? 0 : UINT16_MAX; + op->svc_first = UINT16_MAX; + op->svc_last = 0; + + /* Load existing services as pending */ + gatt_db_foreach_service_in_range(client->db, NULL, + discovery_load_services, op, + start, end); + + /* + * Services are only added when set active in which case they are no + * longer pending so it is safe to remove either way. + */ + op->db_id = gatt_db_register(client->db, discovery_service_changed, + discovery_service_changed, + op, NULL); + + range = new0(struct handle_range, 1); + range->start = start; + range->end = end; + queue_push_tail(op->discov_ranges, range); + + return op; +} + +static struct discovery_op *discovery_op_ref(struct discovery_op *op) +{ + __sync_fetch_and_add(&op->ref_count, 1); + + return op; +} + +static void discovery_op_unref(void *data) +{ + struct discovery_op *op = data; + + if (__sync_sub_and_fetch(&op->ref_count, 1)) + return; + + if (!op->success && op->failure_func) + op->failure_func(op); + + discovery_op_free(op); +} + +static void discovery_req_clear(struct bt_gatt_client *client) +{ + if (!client->discovery_req) + return; + + bt_gatt_request_unref(client->discovery_req); + client->discovery_req = NULL; +} + +static void discover_chrcs_cb(bool success, uint8_t att_ecode, + struct bt_gatt_result *result, + void *user_data); + +static void discover_incl_cb(bool success, uint8_t att_ecode, + struct bt_gatt_result *result, void *user_data) +{ + struct discovery_op *op = user_data; + struct bt_gatt_client *client = op->client; + struct bt_gatt_iter iter; + struct gatt_db_attribute *attr; + uint16_t handle, start, end; + uint128_t u128; + bt_uuid_t uuid; + char uuid_str[MAX_LEN_UUID_STR]; + unsigned int includes_count, i; + struct handle_range *range; + + discovery_req_clear(client); + + if (!success) { + if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) + goto next; + + goto failed; + } + + if (!result || !bt_gatt_iter_init(&iter, result)) + goto failed; + + includes_count = bt_gatt_result_included_count(result); + if (includes_count == 0) + goto failed; + + util_debug(client->debug_callback, client->debug_data, + "Included services found: %u", + includes_count); + + for (i = 0; i < includes_count; i++) { + if (!bt_gatt_iter_next_included_service(&iter, &handle, &start, + &end, u128.data)) + break; + + bt_uuid128_create(&uuid, u128); + + /* Log debug message */ + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + util_debug(client->debug_callback, client->debug_data, + "handle: 0x%04x, start: 0x%04x, end: 0x%04x," + "uuid: %s", handle, start, end, uuid_str); + + attr = gatt_db_get_attribute(client->db, start); + if (!attr) { + util_debug(client->debug_callback, client->debug_data, + "Unable to find attribute at 0x%04x", start); + goto failed; + } + + attr = gatt_db_insert_included(client->db, handle, attr); + if (!attr) { + util_debug(client->debug_callback, client->debug_data, + "Unable to add include attribute at 0x%04x", + handle); + goto failed; + } + + /* + * GATT requires that all include definitions precede + * characteristic declarations. Based on the order we're adding + * these entries, the correct handle must be assigned to the new + * attribute. + */ + if (gatt_db_attribute_get_handle(attr) != handle) { + util_debug(client->debug_callback, client->debug_data, + "Invalid attribute 0x%04x expect it at 0x%04x", + gatt_db_attribute_get_handle(attr), handle); + goto failed; + } + } + +next: + range = queue_pop_head(op->discov_ranges); + if (!range) + goto failed; + + client->discovery_req = bt_gatt_discover_characteristics(client->att, + range->start, + range->end, + discover_chrcs_cb, + discovery_op_ref(op), + discovery_op_unref); + free(range); + if (client->discovery_req) + return; + + util_debug(client->debug_callback, client->debug_data, + "Failed to start characteristic discovery"); + discovery_op_unref(op); +failed: + discovery_op_complete(op, false, att_ecode); +} + +struct chrc { + uint16_t start_handle; + uint16_t end_handle; + uint16_t value_handle; + uint8_t properties; + bt_uuid_t uuid; +}; + +static void discover_descs_cb(bool success, uint8_t att_ecode, + struct bt_gatt_result *result, + void *user_data); + +static bool discover_descs(struct discovery_op *op, bool *discovering) +{ + struct bt_gatt_client *client = op->client; + struct gatt_db_attribute *attr; + struct chrc *chrc_data; + uint16_t desc_start; + + *discovering = false; + + while ((chrc_data = queue_pop_head(op->pending_chrcs))) { + struct gatt_db_attribute *svc; + uint16_t start, end; + + /* Adjust current service */ + svc = gatt_db_get_service(client->db, chrc_data->value_handle); + if (op->cur_svc != svc) { + if (op->cur_svc) { + queue_remove(op->pending_svcs, op->cur_svc); + + /* Done with the current service */ + gatt_db_service_set_active(op->cur_svc, true); + } + + op->cur_svc = svc; + } + + attr = gatt_db_insert_characteristic(client->db, + chrc_data->value_handle, + &chrc_data->uuid, 0, + chrc_data->properties, + NULL, NULL, NULL); + + if (!attr) { + util_debug(client->debug_callback, client->debug_data, + "Failed to insert characteristic at 0x%04x", + chrc_data->value_handle); + goto failed; + } + + if (gatt_db_attribute_get_handle(attr) != + chrc_data->value_handle) + goto failed; + + gatt_db_attribute_get_service_handles(svc, &start, &end); + + /* + * Ajust end_handle in case the next chrc is not within the + * same service. + */ + if (chrc_data->end_handle > end) + chrc_data->end_handle = end; + + /* + * check for descriptors presence, before initializing the + * desc_handle and avoid integer overflow during desc_handle + * intialization. + */ + if (chrc_data->value_handle >= chrc_data->end_handle) { + free(chrc_data); + continue; + } + + desc_start = chrc_data->value_handle + 1; + + if (desc_start == chrc_data->end_handle && + (chrc_data->properties & BT_GATT_CHRC_PROP_NOTIFY || + chrc_data->properties & BT_GATT_CHRC_PROP_INDICATE)) { + bt_uuid_t ccc_uuid; + + /* If there is only one descriptor that must be the CCC + * in case either notify or indicate are supported. + */ + bt_uuid16_create(&ccc_uuid, + GATT_CLIENT_CHARAC_CFG_UUID); + attr = gatt_db_insert_descriptor(client->db, desc_start, + &ccc_uuid, 0, NULL, + NULL, NULL); + if (attr) { + free(chrc_data); + continue; + } + } + + /* Check if the start range is within characteristic range */ + if (desc_start > chrc_data->end_handle) { + free(chrc_data); + continue; + } + + client->discovery_req = bt_gatt_discover_descriptors( + client->att, desc_start, + chrc_data->end_handle, + discover_descs_cb, + discovery_op_ref(op), + discovery_op_unref); + if (client->discovery_req) { + *discovering = true; + goto done; + } + + util_debug(client->debug_callback, client->debug_data, + "Failed to start descriptor discovery"); + discovery_op_unref(op); + + goto failed; + } + +done: + free(chrc_data); + return true; + +failed: + free(chrc_data); + return false; +} + +static void ext_prop_write_cb(struct gatt_db_attribute *attrib, + int err, void *user_data) +{ + struct bt_gatt_client *client = user_data; + + util_debug(client->debug_callback, client->debug_data, + "Value set status: %d", err); +} + +static void ext_prop_read_cb(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data); + +static bool read_ext_prop_desc(struct discovery_op *op) +{ + struct bt_gatt_client *client = op->client; + uint16_t handle; + struct gatt_db_attribute *attr; + + attr = queue_peek_head(op->ext_prop_desc); + if (!attr) + return false; + + handle = gatt_db_attribute_get_handle(attr); + + if (!bt_gatt_client_read_value(client, handle, ext_prop_read_cb, + discovery_op_ref(op), + discovery_op_unref)) + return false; + + return true; +} + +static void ext_prop_read_cb(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct discovery_op *op = user_data; + struct bt_gatt_client *client = op->client; + bool discovering; + struct gatt_db_attribute *desc_attr = NULL; + + if (!success) + goto done; + + util_debug(client->debug_callback, client->debug_data, + "Ext. prop value: 0x%04x", (uint16_t)value[0]); + + desc_attr = queue_pop_head(op->ext_prop_desc); + if (!desc_attr) + goto failed; + + if (!gatt_db_attribute_write(desc_attr, 0, value, length, 0, NULL, + ext_prop_write_cb, client)) + goto failed; + + /* Any other descriptor to read? */ + if (read_ext_prop_desc(op)) + return; + + if (!discover_descs(op, &discovering)) + goto failed; + + if (discovering) + return; + + /* Done with the current service */ + gatt_db_service_set_active(op->cur_svc, true); + + goto done; + +failed: + success = false; + +done: + discovery_op_complete(op, success, att_ecode); +} + +static void discover_descs_cb(bool success, uint8_t att_ecode, + struct bt_gatt_result *result, + void *user_data) +{ + struct discovery_op *op = user_data; + struct bt_gatt_client *client = op->client; + struct bt_gatt_iter iter; + struct gatt_db_attribute *attr; + uint16_t handle; + uint128_t u128; + bt_uuid_t uuid; + char uuid_str[MAX_LEN_UUID_STR]; + unsigned int desc_count; + bool discovering; + bt_uuid_t ext_prop_uuid; + + discovery_req_clear(client); + + if (!success) { + if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) { + success = true; + goto next; + } + + goto done; + } + + if (!result || !bt_gatt_iter_init(&iter, result)) + goto failed; + + desc_count = bt_gatt_result_descriptor_count(result); + if (desc_count == 0) + goto failed; + + util_debug(client->debug_callback, client->debug_data, + "Descriptors found: %u", desc_count); + + bt_uuid16_create(&ext_prop_uuid, GATT_CHARAC_EXT_PROPER_UUID); + + while (bt_gatt_iter_next_descriptor(&iter, &handle, u128.data)) { + bt_uuid128_create(&uuid, u128); + + /* Log debug message */ + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + util_debug(client->debug_callback, client->debug_data, + "handle: 0x%04x, uuid: %s", + handle, uuid_str); + + attr = gatt_db_insert_descriptor(client->db, handle, + &uuid, 0, NULL, NULL, + NULL); + if (!attr) { + attr = gatt_db_get_attribute(client->db, handle); + if (attr && !bt_uuid_cmp(&uuid, + gatt_db_attribute_get_type(attr))) + continue; + + util_debug(client->debug_callback, client->debug_data, + "Failed to insert descriptor at 0x%04x", + handle); + goto failed; + } + + if (gatt_db_attribute_get_handle(attr) != handle) + goto failed; + + if (!bt_uuid_cmp(&ext_prop_uuid, &uuid)) + queue_push_tail(op->ext_prop_desc, attr); + } + + /* If we got extended prop descriptor, lets read it right away */ + if (read_ext_prop_desc(op)) + return; + +next: + if (!discover_descs(op, &discovering)) + goto failed; + + if (discovering) + return; + + /* Done with the current service */ + gatt_db_service_set_active(op->cur_svc, true); + + goto done; + +failed: + success = false; + +done: + discovery_op_complete(op, success, att_ecode); +} + +static void discover_chrcs_cb(bool success, uint8_t att_ecode, + struct bt_gatt_result *result, + void *user_data) +{ + struct discovery_op *op = user_data; + struct bt_gatt_client *client = op->client; + struct bt_gatt_iter iter; + struct chrc *chrc_data; + uint16_t start, end, value; + uint8_t properties; + uint128_t u128; + bt_uuid_t uuid; + char uuid_str[MAX_LEN_UUID_STR]; + unsigned int chrc_count; + bool discovering; + + discovery_req_clear(client); + + if (!success) { + if (att_ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) { + success = true; + goto next; + } + + goto done; + } + + if (!result || !bt_gatt_iter_init(&iter, result)) + goto failed; + + chrc_count = bt_gatt_result_characteristic_count(result); + util_debug(client->debug_callback, client->debug_data, + "Characteristics found: %u", chrc_count); + + if (chrc_count == 0) + goto failed; + + while (bt_gatt_iter_next_characteristic(&iter, &start, &end, &value, + &properties, u128.data)) { + bt_uuid128_create(&uuid, u128); + + /* Log debug message */ + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + util_debug(client->debug_callback, client->debug_data, + "start: 0x%04x, end: 0x%04x, value: 0x%04x, " + "props: 0x%02x, uuid: %s", + start, end, value, properties, uuid_str); + + chrc_data = new0(struct chrc, 1); + + chrc_data->start_handle = start; + chrc_data->end_handle = end; + chrc_data->value_handle = value; + chrc_data->properties = properties; + chrc_data->uuid = uuid; + + queue_push_tail(op->pending_chrcs, chrc_data); + } + +next: + /* + * Before attempting to process discovered characteristics make sure we + * discovered all missing ranges. + */ + if (queue_length(op->discov_ranges)) { + struct handle_range *range; + + range = queue_peek_head(op->discov_ranges); + if (!range) + goto failed; + + client->discovery_req = + bt_gatt_discover_included_services(client->att, + range->start, + range->end, + discover_incl_cb, + discovery_op_ref(op), + discovery_op_unref); + if (client->discovery_req) + return; + + util_debug(client->debug_callback, client->debug_data, + "Failed to start included services discovery"); + + discovery_op_unref(op); + + goto failed; + } + + /* + * Sequentially discover descriptors for each characteristic and insert + * the characteristics into the database as we proceed. + */ + if (!discover_descs(op, &discovering)) + goto failed; + + if (discovering) + return; + + /* Done with the current service */ + gatt_db_service_set_active(op->cur_svc, true); + + goto done; + +failed: + success = false; + +done: + discovery_op_complete(op, success, att_ecode); +} + +static bool match_handle_range(const void *data, const void *match_data) +{ + const struct handle_range *range = data; + const struct handle_range *match_range = match_data; + + return (match_range->start >= range->start) && + (match_range->start <= range->end); +} + +static void remove_discov_range(struct discovery_op *op, uint16_t start, + uint16_t end) +{ + struct handle_range match_range; + struct handle_range *range, *new_range; + + match_range.start = start; + match_range.end = end; + + range = queue_find(op->discov_ranges, match_handle_range, &match_range); + if (!range) + return; + + if ((range->start == start) && (range->end == end)) { + queue_remove(op->discov_ranges, range); + free(range); + } else if (range->start == start) + range->start = end + 1; + else if (range->end == end) + range->end = start - 1; + else { + new_range = new0(struct handle_range, 1); + new_range->start = end + 1; + new_range->end = range->end; + + queue_push_after(op->discov_ranges, range, new_range); + + range->end = start - 1; + } +} + +static void discovery_found_service(struct discovery_op *op, + struct gatt_db_attribute *attr, + uint16_t start, uint16_t end) +{ + /* Skip if service already active */ + if (!gatt_db_service_get_active(attr)) { + /* Skip if there are no attributes */ + if (end == start) + gatt_db_service_set_active(attr, true); + else + queue_push_tail(op->pending_svcs, attr); + + if (start < op->svc_first) + op->svc_first = start; + if (end > op->svc_last) + op->svc_last = end; + } else { + /* Remove from pending if active */ + queue_remove(op->pending_svcs, attr); + + remove_discov_range(op, start, end); + } + + /* Update last handle */ + if (end > op->last) + op->last = end; +} + +static void discover_secondary_cb(bool success, uint8_t att_ecode, + struct bt_gatt_result *result, + void *user_data) +{ + struct discovery_op *op = user_data; + struct bt_gatt_client *client = op->client; + struct bt_gatt_iter iter; + struct gatt_db_attribute *attr; + uint16_t start, end; + uint128_t u128; + bt_uuid_t uuid; + char uuid_str[MAX_LEN_UUID_STR]; + struct handle_range *range; + + discovery_req_clear(client); + + if (!success) { + switch (att_ecode) { + case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND: + case BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE: + success = true; + att_ecode = 0; + goto next; + default: + util_debug(client->debug_callback, client->debug_data, + "Secondary service discovery failed." + " ATT ECODE: 0x%02x", att_ecode); + goto done; + } + } + + if (!result || !bt_gatt_iter_init(&iter, result)) { + success = false; + goto done; + } + + util_debug(client->debug_callback, client->debug_data, + "Secondary services found: %u", + bt_gatt_result_service_count(result)); + + while (bt_gatt_iter_next_service(&iter, &start, &end, u128.data)) { + bt_uuid128_create(&uuid, u128); + + /* Log debug message */ + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + util_debug(client->debug_callback, client->debug_data, + "start: 0x%04x, end: 0x%04x, uuid: %s", + start, end, uuid_str); + + /* Store the service */ + attr = gatt_db_insert_service(client->db, start, &uuid, false, + end - start + 1); + if (!attr) { + gatt_db_clear_range(client->db, start, end); + attr = gatt_db_insert_service(client->db, start, &uuid, + false, end - start + 1); + if (!attr) { + util_debug(client->debug_callback, + client->debug_data, + "Failed to store service"); + success = false; + goto done; + } + /* Database has changed adjust last handle */ + op->last = end; + } + + /* Update pending list */ + discovery_found_service(op, attr, start, end); + } + +next: + if (queue_isempty(op->pending_svcs) || queue_isempty(op->discov_ranges)) + goto done; + + if (op->svc_first > 0x0001) + remove_discov_range(op, 1, op->svc_first - 1); + if (op->svc_last < 0xffff) + remove_discov_range(op, op->svc_last + 1, 0xffff); + + range = queue_peek_head(op->discov_ranges); + + client->discovery_req = bt_gatt_discover_included_services(client->att, + range->start, + range->end, + discover_incl_cb, + discovery_op_ref(op), + discovery_op_unref); + if (client->discovery_req) + return; + + util_debug(client->debug_callback, client->debug_data, + "Failed to start included services discovery"); + discovery_op_unref(op); + success = false; + +done: + discovery_op_complete(op, success, att_ecode); +} + +static void discover_primary_cb(bool success, uint8_t att_ecode, + struct bt_gatt_result *result, + void *user_data) +{ + struct discovery_op *op = user_data; + struct bt_gatt_client *client = op->client; + struct bt_gatt_iter iter; + struct gatt_db_attribute *attr; + uint16_t start, end; + uint128_t u128; + bt_uuid_t uuid; + char uuid_str[MAX_LEN_UUID_STR]; + + discovery_req_clear(client); + + if (!success) { + /* Reset error in case of not found */ + switch (att_ecode) { + case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND: + success = true; + att_ecode = 0; + goto secondary; + default: + util_debug(client->debug_callback, client->debug_data, + "Primary service discovery failed." + " ATT ECODE: 0x%02x", att_ecode); + goto done; + } + } + + if (!result || !bt_gatt_iter_init(&iter, result)) { + success = false; + goto done; + } + + util_debug(client->debug_callback, client->debug_data, + "Primary services found: %u", + bt_gatt_result_service_count(result)); + + while (bt_gatt_iter_next_service(&iter, &start, &end, u128.data)) { + bt_uuid128_create(&uuid, u128); + + /* Log debug message. */ + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + util_debug(client->debug_callback, client->debug_data, + "start: 0x%04x, end: 0x%04x, uuid: %s", + start, end, uuid_str); + + attr = gatt_db_insert_service(client->db, start, &uuid, true, + end - start + 1); + if (!attr) { + gatt_db_clear_range(client->db, start, end); + attr = gatt_db_insert_service(client->db, start, &uuid, + true, end - start + 1); + if (!attr) { + util_debug(client->debug_callback, + client->debug_data, + "Failed to store service"); + success = false; + goto done; + } + /* Database has changed adjust last handle */ + op->last = end; + } + + /* Update pending list */ + discovery_found_service(op, attr, start, end); + } + +secondary: + /* + * Version 4.2 [Vol 1, Part A] page 101: + * A secondary service is a service that provides auxiliary + * functionality of a device and is referenced from at least one + * primary service on the device. + */ + if (queue_isempty(op->pending_svcs)) + goto done; + + /* Discover secondary services */ + client->discovery_req = bt_gatt_discover_secondary_services(client->att, + NULL, op->start, op->end, + discover_secondary_cb, + discovery_op_ref(op), + discovery_op_unref); + if (client->discovery_req) + return; + + util_debug(client->debug_callback, client->debug_data, + "Failed to start secondary service discovery"); + discovery_op_unref(op); + success = false; + +done: + discovery_op_complete(op, success, att_ecode); +} + +static void ready_destroy(void *data) +{ + struct ready_cb *ready = data; + + if (ready->destroy) + ready->destroy(ready->data); + + free(ready); +} + +static void notify_client_ready(struct bt_gatt_client *client, bool success, + uint8_t att_ecode) +{ + const struct queue_entry *entry; + + if (client->ready) + return; + + bt_gatt_client_ref(client); + client->ready = success; + + for (entry = queue_get_entries(client->ready_cbs); entry; + entry = entry->next) { + struct ready_cb *ready = entry->data; + + ready->callback(success, att_ecode, ready->data); + } + + queue_remove_all(client->ready_cbs, NULL, NULL, ready_destroy); + + /* Notify clones */ + for (entry = queue_get_entries(client->clones); entry; + entry = entry->next) { + struct bt_gatt_client *clone = entry->data; + + notify_client_ready(clone, success, att_ecode); + } + + bt_gatt_client_unref(client); +} + +static void discover_all(struct discovery_op *op) +{ + struct bt_gatt_client *client = op->client; + + client->discovery_req = bt_gatt_discover_all_primary_services( + client->att, NULL, + discover_primary_cb, + discovery_op_ref(op), + discovery_op_unref); + if (client->discovery_req) + return; + + util_debug(client->debug_callback, client->debug_data, + "Failed to initiate primary service discovery"); + + client->in_init = false; + notify_client_ready(client, false, BT_ATT_ERROR_UNLIKELY); + + discovery_op_unref(op); +} + +static void db_hash_write_value_cb(struct gatt_db_attribute *attrib, + int err, void *user_data) +{ + struct bt_gatt_client *client = user_data; + + util_debug(client->debug_callback, client->debug_data, + "Value set status: %d", err); +} + +static void db_hash_read_value_cb(struct gatt_db_attribute *attrib, + int err, const uint8_t *value, + size_t length, void *user_data) +{ + const uint8_t **hash = user_data; + + if (err || (length != 16)) + return; + + *hash = value; +} + +static void db_hash_read_cb(bool success, uint8_t att_ecode, + struct bt_gatt_result *result, + void *user_data) +{ + struct discovery_op *op = user_data; + struct bt_gatt_client *client = op->client; + const uint8_t *hash = NULL, *value; + uint16_t len, handle; + struct bt_gatt_iter iter; + + if (!success) + goto discover; + + bt_gatt_iter_init(&iter, result); + bt_gatt_iter_next_read_by_type(&iter, &handle, &len, &value); + + util_debug(client->debug_callback, client->debug_data, + "DB Hash found: handle 0x%04x length 0x%04x", + handle, len); + + if (len != 16) + goto discover; + + /* Read stored value in the db */ + gatt_db_attribute_read(op->hash, 0, BT_ATT_OP_READ_REQ, NULL, + db_hash_read_value_cb, &hash); + + /* Check if the has has changed since last time */ + if (hash && !memcmp(hash, value, len)) { + util_debug(client->debug_callback, client->debug_data, + "DB Hash match: skipping discovery"); + queue_remove_all(op->pending_svcs, NULL, NULL, NULL); + discovery_op_complete(op, true, 0); + return; + } + + util_debug(client->debug_callback, client->debug_data, + "DB Hash value:"); + util_hexdump(' ', value, len, client->debug_callback, + client->debug_data); + + /* Store the new hash in the db */ + gatt_db_attribute_write(op->hash, 0, value, len, 0, NULL, + db_hash_write_value_cb, client); + +discover: + if (!op->success) { + discover_all(op); + return; + } + + discovery_op_complete(op, true, 0); +} + +static void get_first_attribute(struct gatt_db_attribute *attrib, + void *user_data) +{ + struct gatt_db_attribute **stored = user_data; + + if (*stored) + return; + + *stored = attrib; +} + +static bool read_db_hash(struct discovery_op *op) +{ + struct bt_gatt_client *client = op->client; + bt_uuid_t uuid; + + /* Check if hash was already read */ + if (op->hash) + return false; + + bt_uuid16_create(&uuid, GATT_CHARAC_DB_HASH); + gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, + get_first_attribute, &op->hash); + if (!op->hash) + return false; + + if (!bt_gatt_read_by_type(client->att, 0x0001, 0xffff, &uuid, + db_hash_read_cb, + discovery_op_ref(op), + discovery_op_unref)) { + discovery_op_unref(op); + return false; + } + + return true; +} + +static void exchange_mtu_cb(bool success, uint8_t att_ecode, void *user_data) +{ + struct discovery_op *op = user_data; + struct bt_gatt_client *client = op->client; + + op->success = success; + client->mtu_req_id = 0; + + if (!success) { + util_debug(client->debug_callback, client->debug_data, + "MTU Exchange failed. ATT ECODE: 0x%02x", + att_ecode); + + /* + * BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 546 + * If the Error Response is sent by the server with the Error + * Code set to RequestNot Supported , the Attribute Opcode is + * not supported and the default MTU shall be used. + */ + if (att_ecode == BT_ATT_ERROR_REQUEST_NOT_SUPPORTED) + goto discover; + + client->in_init = false; + notify_client_ready(client, success, att_ecode); + + return; + } + + util_debug(client->debug_callback, client->debug_data, + "MTU exchange complete, with MTU: %u", + bt_att_get_mtu(client->att)); + +discover: + if (read_db_hash(op)) { + op->success = false; + return; + } + + discover_all(op); +} + +struct service_changed_op { + struct bt_gatt_client *client; + uint16_t start_handle; + uint16_t end_handle; +}; + +static void process_service_changed(struct bt_gatt_client *client, + uint16_t start_handle, + uint16_t end_handle); +static void service_changed_cb(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data); + +static void complete_notify_request(void *data) +{ + struct notify_data *notify_data = data; + + notify_data->att_id = 0; + + if (notify_data->callback) + notify_data->callback(0, notify_data->user_data); +} + +static bool notify_data_write_ccc(struct notify_data *notify_data, bool enable, + bt_att_response_func_t callback) +{ + uint8_t pdu[4]; + unsigned int att_id; + + assert(notify_data->chrc->ccc_handle); + memset(pdu, 0, sizeof(pdu)); + put_le16(notify_data->chrc->ccc_handle, pdu); + + if (enable) { + /* Try to enable notifications and/or indications based on + * whatever the characteristic supports. + */ + if (notify_data->chrc->properties & BT_GATT_CHRC_PROP_NOTIFY) + pdu[2] = 0x01; + + if (notify_data->chrc->properties & BT_GATT_CHRC_PROP_INDICATE) + pdu[2] |= 0x02; + + if (!pdu[2]) + return false; + } + + att_id = bt_att_send(notify_data->client->att, BT_ATT_OP_WRITE_REQ, + pdu, sizeof(pdu), callback, + notify_data_ref(notify_data), + notify_data_unref); + notify_data->chrc->ccc_write_id = notify_data->att_id = att_id; + + return !!att_id; +} + +static uint8_t process_error(const void *pdu, uint16_t length) +{ + const struct bt_att_pdu_error_rsp *error_pdu; + + if (!pdu || length != sizeof(struct bt_att_pdu_error_rsp)) + return 0; + + error_pdu = pdu; + + return error_pdu->ecode; +} + +static void enable_ccc_callback(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct notify_data *notify_data = user_data; + uint8_t att_ecode; + + assert(notify_data->chrc->ccc_write_id); + + notify_data->chrc->ccc_write_id = 0; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + att_ecode = process_error(pdu, length); + + /* Failed to enable. Complete the current request and move on to + * the next one in the queue. If there was an error sending the + * write request, then just move on to the next queued entry. + */ + queue_remove(notify_data->client->notify_list, notify_data); + notify_data->callback(att_ecode, notify_data->user_data); + + while ((notify_data = queue_pop_head( + notify_data->chrc->reg_notify_queue))) { + + if (notify_data_write_ccc(notify_data, true, + enable_ccc_callback)) + return; + } + + return; + } + + /* Success! Report success for all remaining requests. */ + bt_gatt_client_ref(notify_data->client); + + complete_notify_request(notify_data); + queue_remove_all(notify_data->chrc->reg_notify_queue, NULL, NULL, + complete_notify_request); + + bt_gatt_client_unref(notify_data->client); +} + +static bool match_notify_chrc_value_handle(const void *a, const void *b) +{ + const struct notify_chrc *chrc = a; + uint16_t value_handle = PTR_TO_UINT(b); + + return chrc->value_handle == value_handle; +} + +static unsigned int register_notify(struct bt_gatt_client *client, + uint16_t handle, + bt_gatt_client_register_callback_t callback, + bt_gatt_client_notify_callback_t notify, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + struct notify_data *notify_data; + struct notify_chrc *chrc = NULL; + + /* Check if a characteristic ref count has been started already */ + chrc = queue_find(client->notify_chrcs, match_notify_chrc_value_handle, + UINT_TO_PTR(handle)); + + if (!chrc) { + /* + * Create an entry if the characteristic is known and has a CCC + * descriptor. + */ + chrc = notify_chrc_create(client, handle); + if (!chrc) + return 0; + } + + /* Fail if we've hit the maximum allowed notify sessions */ + if (chrc->notify_count == INT_MAX) + return 0; + + notify_data = new0(struct notify_data, 1); + notify_data->client = client; + notify_data->ref_count = 1; + notify_data->chrc = chrc; + notify_data->callback = callback; + notify_data->notify = notify; + notify_data->user_data = user_data; + notify_data->destroy = destroy; + + /* Add the handler to the bt_gatt_client's general list */ + queue_push_tail(client->notify_list, notify_data); + + /* Assign an ID to the handler. */ + if (client->next_reg_id < 1) + client->next_reg_id = 1; + + notify_data->id = client->next_reg_id++; + + /* Increment the per-characteristic ref count of notify handlers */ + __sync_fetch_and_add(¬ify_data->chrc->notify_count, 1); + + /* + * If a write to the CCC descriptor is in progress, then queue this + * request. + */ + if (chrc->ccc_write_id) { + queue_push_tail(chrc->reg_notify_queue, notify_data); + return notify_data->id; + } + + /* + * If the ref count > 1, then notifications are already enabled. + */ + if (chrc->notify_count > 1 || !chrc->ccc_handle) { + complete_notify_request(notify_data); + return notify_data->id; + } + + /* Write to the CCC descriptor */ + if (!notify_data_write_ccc(notify_data, true, enable_ccc_callback)) { + queue_remove(client->notify_list, notify_data); + free(notify_data); + return 0; + } + + return notify_data->id; +} + +static void service_changed_register_cb(uint16_t att_ecode, void *user_data) +{ + bool success; + struct bt_gatt_client *client = user_data; + + if (att_ecode) { + util_debug(client->debug_callback, client->debug_data, + "Failed to register handler for \"Service Changed\""); + success = false; + client->svc_chngd_ind_id = 0; + goto done; + } + + client->svc_chngd_registered = true; + success = true; + util_debug(client->debug_callback, client->debug_data, + "Registered handler for \"Service Changed\": %u", + client->svc_chngd_ind_id); + +done: + notify_client_ready(client, success, att_ecode); +} + +static bool register_service_changed(struct bt_gatt_client *client) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *attr = NULL; + + bt_uuid16_create(&uuid, SVC_CHNGD_UUID); + + if (client->svc_chngd_ind_id) + return true; + + gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, + get_first_attribute, &attr); + if (!attr) + return true; + + /* + * Register an indication handler for the "Service Changed" + * characteristic and report ready only if the handler is registered + * successfully. + */ + client->svc_chngd_ind_id = register_notify(client, + gatt_db_attribute_get_handle(attr), + service_changed_register_cb, + service_changed_cb, + client, NULL); + + return client->svc_chngd_ind_id ? true : false; +} + +static void service_changed_complete(struct discovery_op *op, bool success, + uint8_t att_ecode) +{ + struct bt_gatt_client *client = op->client; + struct service_changed_op *next_sc_op; + uint16_t start_handle = op->start; + uint16_t end_handle = op->end; + const struct queue_entry *entry; + + client->in_svc_chngd = false; + + if (!success && att_ecode != BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND) { + util_debug(client->debug_callback, client->debug_data, + "Failed to discover services within changed range - " + "error: 0x%02x", att_ecode); + + gatt_db_clear_range(client->db, start_handle, end_handle); + } + + /* Notify the upper layer of changed services */ + if (client->svc_chngd_callback) + client->svc_chngd_callback(start_handle, end_handle, + client->svc_chngd_data); + + /* Notify clones */ + for (entry = queue_get_entries(client->clones); entry; + entry = entry->next) { + struct bt_gatt_client *clone = entry->data; + + if (clone->svc_chngd_callback) + clone->svc_chngd_callback(start_handle, end_handle, + clone->svc_chngd_data); + } + + /* Process any queued events */ + next_sc_op = queue_pop_head(client->svc_chngd_queue); + if (next_sc_op) { + process_service_changed(client, next_sc_op->start_handle, + next_sc_op->end_handle); + free(next_sc_op); + return; + } + + if (register_service_changed(client)) + return; + + util_debug(client->debug_callback, client->debug_data, + "Failed to re-register handler for \"Service Changed\""); +} + +static void service_changed_failure(struct discovery_op *op) +{ + struct bt_gatt_client *client = op->client; + + gatt_db_clear_range(client->db, op->start, op->end); +} + +static void process_service_changed(struct bt_gatt_client *client, + uint16_t start_handle, + uint16_t end_handle) +{ + struct discovery_op *op; + + op = discovery_op_create(client, start_handle, end_handle, + service_changed_complete, + service_changed_failure); + if (!op) + goto fail; + + client->discovery_req = bt_gatt_discover_primary_services(client->att, + NULL, start_handle, end_handle, + discover_primary_cb, + discovery_op_ref(op), + discovery_op_unref); + if (client->discovery_req) { + client->in_svc_chngd = true; + return; + } + + discovery_op_free(op); + +fail: + util_debug(client->debug_callback, client->debug_data, + "Failed to initiate service discovery" + " after Service Changed"); +} + +static void service_changed_cb(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + struct bt_gatt_client *client = user_data; + struct service_changed_op *op; + uint16_t start, end; + + if (length != 4) + return; + + start = get_le16(value); + end = get_le16(value + 2); + + if (start > end) { + util_debug(client->debug_callback, client->debug_data, + "Service Changed received with invalid handles"); + return; + } + + util_debug(client->debug_callback, client->debug_data, + "Service Changed received - start: 0x%04x end: 0x%04x", + start, end); + + if (!client->in_svc_chngd) { + process_service_changed(client, start, end); + return; + } + + op = new0(struct service_changed_op, 1); + + op->start_handle = start; + op->end_handle = end; + + queue_push_tail(client->svc_chngd_queue, op); +} + +static void write_client_features(struct bt_gatt_client *client) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *attr = NULL; + uint16_t handle; + uint8_t value; + + bt_uuid16_create(&uuid, GATT_CHARAC_CLI_FEAT); + + gatt_db_find_by_type(client->db, 0x0001, 0xffff, &uuid, + get_first_attribute, &attr); + if (!attr) + return; + + handle = gatt_db_attribute_get_handle(attr); + value = BT_GATT_CHRC_CLI_FEAT_ROBUST_CACHING; + + bt_gatt_client_write_value(client, handle, &value, sizeof(value), NULL, + NULL, NULL); +} + +static void init_complete(struct discovery_op *op, bool success, + uint8_t att_ecode) +{ + struct bt_gatt_client *client = op->client; + + client->in_init = false; + + if (!success) + goto fail; + + write_client_features(client); + + if (register_service_changed(client)) + goto done; + + util_debug(client->debug_callback, client->debug_data, + "Failed to register handler for \"Service Changed\""); + success = false; + +fail: + util_debug(client->debug_callback, client->debug_data, + "Failed to initialize gatt-client"); + + op->success = false; + +done: + notify_client_ready(client, success, att_ecode); +} + +static bool gatt_client_init(struct bt_gatt_client *client, uint16_t mtu) +{ + struct discovery_op *op; + + if (client->in_init || client->ready) + return false; + + op = discovery_op_create(client, 0x0001, 0xffff, init_complete, NULL); + if (!op) + return false; + + /* + * BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 546: + * + * 4.3.1 Exchange MTU + * + * This sub-procedure shall not be used on a BR/EDR physical link since + * the MTU size is negotiated using L2CAP channel configuration + * procedures. + */ + if (bt_att_get_link_type(client->att) == BT_ATT_LINK_BREDR) + goto discover; + + /* Check if MTU needs to be send */ + mtu = MAX(BT_ATT_DEFAULT_LE_MTU, mtu); + if (mtu == BT_ATT_DEFAULT_LE_MTU) + goto discover; + + /* Configure the MTU */ + client->mtu_req_id = bt_gatt_exchange_mtu(client->att, mtu, + exchange_mtu_cb, + discovery_op_ref(op), + discovery_op_unref); + if (!client->mtu_req_id) { + discovery_op_free(op); + return false; + } + + client->in_init = true; + + return true; + +discover: + if (read_db_hash(op)) { + op->success = false; + goto done; + } + + client->discovery_req = bt_gatt_discover_all_primary_services( + client->att, NULL, + discover_primary_cb, + discovery_op_ref(op), + discovery_op_unref); + if (!client->discovery_req) { + discovery_op_free(op); + return false; + } + +done: + client->in_init = true; + return true; +} + +struct pdu_data { + const void *pdu; + uint16_t length; +}; + +static void disable_ccc_callback(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct notify_data *notify_data = user_data; + struct notify_data *next_data; + + assert(notify_data->chrc->ccc_write_id); + + notify_data->chrc->ccc_write_id = 0; + + /* This is a best effort procedure, so ignore errors and process any + * queued requests. + */ + while (1) { + next_data = queue_pop_head(notify_data->chrc->reg_notify_queue); + if (!next_data || notify_data_write_ccc(next_data, true, + enable_ccc_callback)) + return; + } +} + +static void complete_unregister_notify(void *data) +{ + struct notify_data *notify_data = data; + + /* + * If a procedure to enable the CCC is still pending, then cancel it and + * return. + */ + if (notify_data->att_id) { + bt_att_cancel(notify_data->client->att, notify_data->att_id); + notify_data->att_id = 0; + goto done; + } + + if (__sync_sub_and_fetch(¬ify_data->chrc->notify_count, 1) || + !notify_data->chrc->ccc_handle) + goto done; + + notify_data_write_ccc(notify_data, false, disable_ccc_callback); + +done: + notify_data_unref(notify_data); +} + +static void notify_handler(void *data, void *user_data) +{ + struct notify_data *notify_data = data; + struct pdu_data *pdu_data = user_data; + uint16_t value_handle; + const uint8_t *value = NULL; + + value_handle = get_le16(pdu_data->pdu); + + if (notify_data->chrc->value_handle != value_handle) + return; + + if (pdu_data->length > 2) + value = pdu_data->pdu + 2; + + /* + * Even if the notify data has a pending ATT request to write to the + * CCC, there is really no reason not to notify the handlers. + */ + if (notify_data->notify) + notify_data->notify(value_handle, value, pdu_data->length - 2, + notify_data->user_data); +} + +static void notify_cb(uint8_t opcode, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_client *client = user_data; + struct pdu_data pdu_data; + + bt_gatt_client_ref(client); + + memset(&pdu_data, 0, sizeof(pdu_data)); + pdu_data.pdu = pdu; + pdu_data.length = length; + + queue_foreach(client->notify_list, notify_handler, &pdu_data); + + if (opcode == BT_ATT_OP_HANDLE_VAL_IND && !client->parent) + bt_att_send(client->att, BT_ATT_OP_HANDLE_VAL_CONF, NULL, 0, + NULL, NULL, NULL); + + bt_gatt_client_unref(client); +} + +static void bt_gatt_client_free(struct bt_gatt_client *client) +{ + bt_gatt_client_cancel_all(client); + + queue_destroy(client->notify_list, notify_data_cleanup); + + queue_destroy(client->ready_cbs, ready_destroy); + + if (client->debug_destroy) + client->debug_destroy(client->debug_data); + + if (client->att) { + bt_att_unregister_disconnect(client->att, client->disc_id); + bt_att_unregister(client->att, client->notify_id); + bt_att_unregister(client->att, client->ind_id); + bt_att_unref(client->att); + } + + gatt_db_unref(client->db); + + queue_destroy(client->clones, NULL); + queue_destroy(client->svc_chngd_queue, free); + queue_destroy(client->long_write_queue, request_unref); + queue_destroy(client->notify_chrcs, notify_chrc_free); + queue_destroy(client->pending_requests, request_unref); + + if (client->parent) { + queue_remove(client->parent->clones, client); + bt_gatt_client_unref(client->parent); + } + + free(client); +} + +static void att_disconnect_cb(int err, void *user_data) +{ + struct bt_gatt_client *client = user_data; + bool in_init = client->in_init; + + client->disc_id = 0; + + bt_att_unref(client->att); + client->att = NULL; + + client->in_init = false; + client->ready = false; + + if (in_init) + notify_client_ready(client, false, 0); +} + +static struct bt_gatt_client *gatt_client_new(struct gatt_db *db, + struct bt_att *att) +{ + struct bt_gatt_client *client; + + client = new0(struct bt_gatt_client, 1); + client->disc_id = bt_att_register_disconnect(att, att_disconnect_cb, + client, NULL); + if (!client->disc_id) + goto fail; + + client->clones = queue_new(); + client->ready_cbs = queue_new(); + client->long_write_queue = queue_new(); + client->svc_chngd_queue = queue_new(); + client->notify_list = queue_new(); + client->notify_chrcs = queue_new(); + client->pending_requests = queue_new(); + + client->notify_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_NOT, + notify_cb, client, NULL); + if (!client->notify_id) + goto fail; + + client->ind_id = bt_att_register(att, BT_ATT_OP_HANDLE_VAL_IND, + notify_cb, client, NULL); + if (!client->ind_id) + goto fail; + + client->att = bt_att_ref(att); + client->db = gatt_db_ref(db); + + return client; + +fail: + bt_gatt_client_free(client); + return NULL; + +} + +struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db, + struct bt_att *att, + uint16_t mtu) +{ + struct bt_gatt_client *client; + + if (!att || !db) + return NULL; + + client = gatt_client_new(db, att); + if (!client) + return NULL; + + if (!gatt_client_init(client, mtu)) { + bt_gatt_client_free(client); + return NULL; + } + + return bt_gatt_client_ref(client); +} + +struct bt_gatt_client *bt_gatt_client_clone(struct bt_gatt_client *client) +{ + struct bt_gatt_client *clone; + + if (!client) + return NULL; + + clone = gatt_client_new(client->db, client->att); + if (!clone) + return NULL; + + queue_push_tail(client->clones, clone); + + /* + * Reference the parent since the clones depend on it to propagate + * service changed and ready callbacks. + */ + clone->parent = bt_gatt_client_ref(client); + clone->ready = client->ready; + + return bt_gatt_client_ref(clone); +} + +struct bt_gatt_client *bt_gatt_client_ref(struct bt_gatt_client *client) +{ + if (!client) + return NULL; + + __sync_fetch_and_add(&client->ref_count, 1); + + return client; +} + +void bt_gatt_client_unref(struct bt_gatt_client *client) +{ + if (!client) + return; + + if (__sync_sub_and_fetch(&client->ref_count, 1)) + return; + + bt_gatt_client_free(client); +} + +bool bt_gatt_client_is_ready(struct bt_gatt_client *client) +{ + return (client && client->ready); +} + +unsigned int bt_gatt_client_ready_register(struct bt_gatt_client *client, + bt_gatt_client_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + struct ready_cb *ready; + + if (!client) + return 0; + + ready = new0(struct ready_cb, 1); + ready->callback = callback; + ready->destroy = destroy; + ready->data = user_data; + + queue_push_tail(client->ready_cbs, ready); + + return PTR_TO_UINT(ready); +} + +bool bt_gatt_client_ready_unregister(struct bt_gatt_client *client, + unsigned int id) +{ + struct ready_cb *ready = UINT_TO_PTR(id); + + if (queue_remove(client->ready_cbs, ready)) { + ready_destroy(ready); + return true; + } + + return false; +} + +bool bt_gatt_client_set_service_changed(struct bt_gatt_client *client, + bt_gatt_client_service_changed_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + if (!client) + return false; + + if (client->svc_chngd_destroy) + client->svc_chngd_destroy(client->svc_chngd_data); + + client->svc_chngd_callback = callback; + client->svc_chngd_destroy = destroy; + client->svc_chngd_data = user_data; + + return true; +} + +bool bt_gatt_client_set_debug(struct bt_gatt_client *client, + bt_gatt_client_debug_func_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) { + if (!client) + return false; + + if (client->debug_destroy) + client->debug_destroy(client->debug_data); + + client->debug_callback = callback; + client->debug_destroy = destroy; + client->debug_data = user_data; + + return true; +} + +uint16_t bt_gatt_client_get_mtu(struct bt_gatt_client *client) +{ + if (!client || !client->att) + return 0; + + return bt_att_get_mtu(client->att); +} + +struct gatt_db *bt_gatt_client_get_db(struct bt_gatt_client *client) +{ + if (!client || !client->db) + return NULL; + + return client->db; +} + +static bool match_req_id(const void *a, const void *b) +{ + const struct request *req = a; + unsigned int id = PTR_TO_UINT(b); + + return req->id == id; +} + +static void cancel_long_write_cb(uint8_t opcode, const void *pdu, uint16_t len, + void *user_data) +{ + struct bt_gatt_client *client = user_data; + + if (queue_isempty(client->long_write_queue)) + client->in_long_write = false; +} + +static bool cancel_long_write_req(struct bt_gatt_client *client, + struct request *req) +{ + uint8_t pdu = 0x00; + + /* + * att_id == 0 means that request has been queued and no prepare write + * has been sent so far.Let's just remove if from the queue. + * Otherwise execute write needs to be send. + */ + if (!req->att_id) + return queue_remove(client->long_write_queue, req); + + return !!bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, + sizeof(pdu), + cancel_long_write_cb, + client, NULL); + +} + +static void cancel_prep_write_cb(uint8_t opcode, const void *pdu, uint16_t len, + void *user_data) +{ + struct request *req = user_data; + struct bt_gatt_client *client = req->client; + + client->reliable_write_session_id = 0; +} + +static bool cancel_prep_write_session(struct bt_gatt_client *client, + struct request *req) +{ + uint8_t pdu = 0x00; + + return !!bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, + sizeof(pdu), + cancel_prep_write_cb, + req, request_unref); +} + +static bool cancel_request(struct request *req) +{ + req->removed = true; + + if (req->long_write) + return cancel_long_write_req(req->client, req); + + if (req->prep_write) + return cancel_prep_write_session(req->client, req); + + return bt_att_cancel(req->client->att, req->att_id); +} + +bool bt_gatt_client_cancel(struct bt_gatt_client *client, unsigned int id) +{ + struct request *req; + + if (!client || !id || !client->att) + return false; + + req = queue_remove_if(client->pending_requests, match_req_id, + UINT_TO_PTR(id)); + if (!req) + return false; + + return cancel_request(req); +} + +static void cancel_pending(void *data) +{ + cancel_request(data); +} + +bool bt_gatt_client_cancel_all(struct bt_gatt_client *client) +{ + if (!client || !client->att) + return false; + + queue_remove_all(client->pending_requests, NULL, NULL, cancel_pending); + + if (client->discovery_req) { + bt_gatt_request_cancel(client->discovery_req); + bt_gatt_request_unref(client->discovery_req); + client->discovery_req = NULL; + } + + if (client->mtu_req_id) + bt_att_cancel(client->att, client->mtu_req_id); + + return true; +} + +struct read_op { + bt_gatt_client_read_callback_t callback; + void *user_data; + bt_gatt_client_destroy_func_t destroy; +}; + +static void destroy_read_op(void *data) +{ + struct read_op *op = data; + + if (op->destroy) + op->destroy(op->user_data); + + free(op); +} + +static void read_cb(uint8_t opcode, const void *pdu, uint16_t length, + void *user_data) +{ + struct request *req = user_data; + struct read_op *op = req->data; + bool success; + uint8_t att_ecode = 0; + const uint8_t *value = NULL; + uint16_t value_len = 0; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + if (opcode != BT_ATT_OP_READ_RSP || (!pdu && length)) { + success = false; + goto done; + } + + success = true; + value_len = length; + if (value_len) + value = pdu; + +done: + if (op->callback) + op->callback(success, att_ecode, value, length, op->user_data); +} + +unsigned int bt_gatt_client_read_value(struct bt_gatt_client *client, + uint16_t value_handle, + bt_gatt_client_read_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + struct request *req; + struct read_op *op; + uint8_t pdu[2]; + + if (!client) + return 0; + + op = new0(struct read_op, 1); + + req = request_create(client); + if (!req) { + free(op); + return 0; + } + + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + + req->data = op; + req->destroy = destroy_read_op; + + put_le16(value_handle, pdu); + + req->att_id = bt_att_send(client->att, BT_ATT_OP_READ_REQ, + pdu, sizeof(pdu), + read_cb, req, + request_unref); + if (!req->att_id) { + op->destroy = NULL; + request_unref(req); + return 0; + } + + return req->id; +} + +static void read_multiple_cb(uint8_t opcode, const void *pdu, uint16_t length, + void *user_data) +{ + struct request *req = user_data; + struct read_op *op = req->data; + uint8_t att_ecode; + bool success; + + if (opcode != BT_ATT_OP_READ_MULT_RSP || (!pdu && length)) { + success = false; + + if (opcode == BT_ATT_OP_ERROR_RSP) + att_ecode = process_error(pdu, length); + else + att_ecode = 0; + + pdu = NULL; + length = 0; + } else { + success = true; + att_ecode = 0; + } + + if (op->callback) + op->callback(success, att_ecode, pdu, length, op->user_data); +} + +unsigned int bt_gatt_client_read_multiple(struct bt_gatt_client *client, + uint16_t *handles, uint8_t num_handles, + bt_gatt_client_read_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + uint8_t pdu[num_handles * 2]; + struct request *req; + struct read_op *op; + int i; + + if (!client) + return 0; + + if (num_handles < 2) + return 0; + + if (num_handles * 2 > bt_att_get_mtu(client->att) - 1) + return 0; + + op = new0(struct read_op, 1); + + req = request_create(client); + if (!req) { + free(op); + return 0; + } + + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + + req->data = op; + req->destroy = destroy_read_op; + + for (i = 0; i < num_handles; i++) + put_le16(handles[i], pdu + (2 * i)); + + req->att_id = bt_att_send(client->att, BT_ATT_OP_READ_MULT_REQ, + pdu, sizeof(pdu), + read_multiple_cb, req, + request_unref); + if (!req->att_id) { + op->destroy = NULL; + request_unref(req); + return 0; + } + + return req->id; +} + +struct read_long_op { + struct bt_gatt_client *client; + int ref_count; + uint16_t value_handle; + uint16_t offset; + struct iovec iov; + bt_gatt_client_read_callback_t callback; + void *user_data; + bt_gatt_client_destroy_func_t destroy; +}; + +static void destroy_read_long_op(void *data) +{ + struct read_long_op *op = data; + + if (op->destroy) + op->destroy(op->user_data); + + free(op->iov.iov_base); + free(op); +} + +static bool append_chunk(struct read_long_op *op, const uint8_t *data, + uint16_t len) +{ + void *buf; + + /* Truncate if the data would exceed maximum length */ + if (op->offset + len > BT_ATT_MAX_VALUE_LEN) + len = BT_ATT_MAX_VALUE_LEN - op->offset; + + buf = realloc(op->iov.iov_base, op->iov.iov_len + len); + if (!buf) + return false; + + op->iov.iov_base = buf; + + memcpy(op->iov.iov_base + op->iov.iov_len, data, len); + + op->iov.iov_len += len; + op->offset += len; + + return true; +} + +static void read_long_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct request *req = user_data; + struct read_long_op *op = req->data; + bool success; + uint8_t att_ecode = 0; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + if ((!op->offset && opcode != BT_ATT_OP_READ_RSP) + || (op->offset && opcode != BT_ATT_OP_READ_BLOB_RSP) + || (!pdu && length)) { + success = false; + goto done; + } + + if (!length) + goto success; + + if (!append_chunk(op, pdu, length)) { + success = false; + goto done; + } + + if (op->offset >= BT_ATT_MAX_VALUE_LEN) + goto success; + + if (length >= bt_att_get_mtu(op->client->att) - 1) { + uint8_t pdu[4]; + + put_le16(op->value_handle, pdu); + put_le16(op->offset, pdu + 2); + + req->att_id = bt_att_send(op->client->att, + BT_ATT_OP_READ_BLOB_REQ, + pdu, sizeof(pdu), + read_long_cb, + request_ref(req), + request_unref); + if (req->att_id) + return; + + request_unref(req); + success = false; + goto done; + } + +success: + success = true; + +done: + if (op->callback) + op->callback(success, att_ecode, op->iov.iov_base, + op->iov.iov_len, op->user_data); +} + +unsigned int bt_gatt_client_read_long_value(struct bt_gatt_client *client, + uint16_t value_handle, uint16_t offset, + bt_gatt_client_read_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + struct request *req; + struct read_long_op *op; + uint8_t att_op; + uint8_t pdu[4]; + uint16_t pdu_len; + + if (!client) + return 0; + + op = new0(struct read_long_op, 1); + + req = request_create(client); + if (!req) { + free(op); + return 0; + } + + op->client = client; + op->value_handle = value_handle; + op->offset = offset; + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + + req->data = op; + req->destroy = destroy_read_long_op; + + put_le16(value_handle, pdu); + pdu_len = sizeof(value_handle); + + /* + * Core v4.2, part F, section 1.3.4.4.5: + * If the attribute value has a fixed length that is less than or equal + * to (ATT_MTU - 3) octets in length, then an Error Response can be sent + * with the error code «Attribute Not Long». + * + * To remove need for caller to handle "Attribute Not Long" error when + * reading characteristics with short values, use Read Request for + * reading first part of characteristics value instead of Read Blob + * Request. Both are allowed in this case. + */ + + if (op->offset) { + att_op = BT_ATT_OP_READ_BLOB_REQ; + pdu_len += sizeof(op->offset); + + put_le16(op->offset, pdu + 2); + } else { + att_op = BT_ATT_OP_READ_REQ; + } + + req->att_id = bt_att_send(client->att, att_op, pdu, pdu_len, + read_long_cb, req, request_unref); + + if (!req->att_id) { + op->destroy = NULL; + request_unref(req); + return 0; + } + + return req->id; +} + +unsigned int bt_gatt_client_write_without_response( + struct bt_gatt_client *client, + uint16_t value_handle, + bool signed_write, + const uint8_t *value, uint16_t length) { + uint8_t pdu[2 + length]; + struct request *req; + int security; + uint8_t op; + + if (!client) + return 0; + + req = request_create(client); + if (!req) + return 0; + + /* Only use signed write if unencrypted */ + if (signed_write) { + security = bt_att_get_security(client->att, NULL); + op = security > BT_SECURITY_LOW ? BT_ATT_OP_WRITE_CMD : + BT_ATT_OP_SIGNED_WRITE_CMD; + } else + op = BT_ATT_OP_WRITE_CMD; + + put_le16(value_handle, pdu); + memcpy(pdu + 2, value, length); + + req->att_id = bt_att_send(client->att, op, pdu, sizeof(pdu), NULL, req, + request_unref); + if (!req->att_id) { + request_unref(req); + return 0; + } + + return req->id; +} + +struct write_op { + struct bt_gatt_client *client; + bt_gatt_client_callback_t callback; + void *user_data; + bt_gatt_destroy_func_t destroy; +}; + +static void destroy_write_op(void *data) +{ + struct write_op *op = data; + + if (op->destroy) + op->destroy(op->user_data); + + free(op); +} + +static void write_cb(uint8_t opcode, const void *pdu, uint16_t length, + void *user_data) +{ + struct request *req = user_data; + struct write_op *op = req->data; + bool success = true; + uint8_t att_ecode = 0; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + if (opcode != BT_ATT_OP_WRITE_RSP || pdu || length) + success = false; + +done: + if (op->callback) + op->callback(success, att_ecode, op->user_data); +} + +unsigned int bt_gatt_client_write_value(struct bt_gatt_client *client, + uint16_t value_handle, + const uint8_t *value, uint16_t length, + bt_gatt_client_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + struct request *req; + struct write_op *op; + uint8_t pdu[2 + length]; + + if (!client) + return 0; + + op = new0(struct write_op, 1); + + req = request_create(client); + if (!req) { + free(op); + return 0; + } + + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + + req->data = op; + req->destroy = destroy_write_op; + + put_le16(value_handle, pdu); + memcpy(pdu + 2, value, length); + + req->att_id = bt_att_send(client->att, BT_ATT_OP_WRITE_REQ, + pdu, sizeof(pdu), + write_cb, req, + request_unref); + if (!req->att_id) { + op->destroy = NULL; + request_unref(req); + return 0; + } + + return req->id; +} + +struct long_write_op { + struct bt_gatt_client *client; + bool reliable; + bool success; + uint8_t att_ecode; + bool reliable_error; + uint16_t value_handle; + uint8_t *value; + uint16_t length; + uint16_t offset; + uint16_t index; + uint16_t cur_length; + bt_gatt_client_write_long_callback_t callback; + void *user_data; + bt_gatt_client_destroy_func_t destroy; +}; + +static void long_write_op_free(void *data) +{ + struct long_write_op *op = data; + + if (op->destroy) + op->destroy(op->user_data); + + free(op->value); + free(op); +} + +static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length, + void *user_data); +static void complete_write_long_op(struct request *req, bool success, + uint8_t att_ecode, bool reliable_error); + +static void handle_next_prep_write(struct request *req) +{ + struct long_write_op *op = req->data; + bool success = true; + uint8_t *pdu; + + pdu = malloc(op->cur_length + 4); + if (!pdu) { + success = false; + goto done; + } + + put_le16(op->value_handle, pdu); + put_le16(op->offset + op->index, pdu + 2); + memcpy(pdu + 4, op->value + op->index, op->cur_length); + + req->att_id = bt_att_send(op->client->att, BT_ATT_OP_PREP_WRITE_REQ, + pdu, op->cur_length + 4, + prepare_write_cb, + request_ref(req), + request_unref); + if (!req->att_id) { + request_unref(req); + success = false; + } + + free(pdu); + + /* If so far successful, then the operation should continue. + * Otherwise, there was an error and the procedure should be + * completed. + */ + if (success) + return; + +done: + complete_write_long_op(req, success, 0, false); +} + +static void start_next_long_write(struct bt_gatt_client *client) +{ + struct request *req; + + if (queue_isempty(client->long_write_queue)) { + client->in_long_write = false; + return; + } + + req = queue_pop_head(client->long_write_queue); + if (!req) + return; + + handle_next_prep_write(req); + + /* + * send_next_prep_write adds an extra ref. Unref here to clean up if + * necessary, since we also added a ref before pushing to the queue. + */ + request_unref(req); +} + +static void execute_write_cb(uint8_t opcode, const void *pdu, uint16_t length, + void *user_data) +{ + struct request *req = user_data; + struct long_write_op *op = req->data; + bool success = op->success; + uint8_t att_ecode = op->att_ecode; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + } else if (opcode != BT_ATT_OP_EXEC_WRITE_RSP || pdu || length) + success = false; + + bt_gatt_client_ref(op->client); + + if (op->callback) + op->callback(success, op->reliable_error, att_ecode, + op->user_data); + + start_next_long_write(op->client); + + bt_gatt_client_unref(op->client); +} + +static void complete_write_long_op(struct request *req, bool success, + uint8_t att_ecode, bool reliable_error) +{ + struct long_write_op *op = req->data; + uint8_t pdu; + + op->success = success; + op->att_ecode = att_ecode; + op->reliable_error = reliable_error; + + if (success) + pdu = 0x01; /* Write */ + else + pdu = 0x00; /* Cancel */ + + req->att_id = bt_att_send(op->client->att, BT_ATT_OP_EXEC_WRITE_REQ, + &pdu, sizeof(pdu), + execute_write_cb, + request_ref(req), + request_unref); + if (req->att_id) + return; + + request_unref(req); + success = false; + + bt_gatt_client_ref(op->client); + + if (op->callback) + op->callback(success, reliable_error, att_ecode, op->user_data); + + start_next_long_write(op->client); + + bt_gatt_client_unref(op->client); +} + +static void prepare_write_cb(uint8_t opcode, const void *pdu, uint16_t length, + void *user_data) +{ + struct request *req = user_data; + struct long_write_op *op = req->data; + bool success = true; + bool reliable_error = false; + uint8_t att_ecode = 0; + uint16_t next_index; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + if (opcode != BT_ATT_OP_PREP_WRITE_RSP) { + success = false; + goto done; + } + + if (op->reliable) { + if (!pdu || length != (op->cur_length + 4)) { + success = false; + reliable_error = true; + goto done; + } + + if (get_le16(pdu) != op->value_handle || + get_le16(pdu + 2) != (op->offset + op->index)) { + success = false; + reliable_error = true; + goto done; + } + + if (memcmp(pdu + 4, op->value + op->index, op->cur_length)) { + success = false; + reliable_error = true; + goto done; + } + } + + next_index = op->index + op->cur_length; + if (next_index == op->length) { + /* All bytes written */ + goto done; + } + + /* If the last written length was greater than or equal to what can fit + * inside a PDU, then there is more data to send. + */ + if (op->cur_length >= bt_att_get_mtu(op->client->att) - 5) { + op->index = next_index; + op->cur_length = MIN(op->length - op->index, + bt_att_get_mtu(op->client->att) - 5); + handle_next_prep_write(req); + return; + } + +done: + complete_write_long_op(req, success, att_ecode, reliable_error); +} + +unsigned int bt_gatt_client_write_long_value(struct bt_gatt_client *client, + bool reliable, + uint16_t value_handle, uint16_t offset, + const uint8_t *value, uint16_t length, + bt_gatt_client_write_long_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + struct request *req; + struct long_write_op *op; + uint8_t *pdu; + + if (!client) + return 0; + + if ((size_t)(length + offset) > UINT16_MAX) + return 0; + + /* Don't allow writing a 0-length value using this procedure. The + * upper-layer should use bt_gatt_write_value for that instead. + */ + if (!length || !value) + return 0; + + op = new0(struct long_write_op, 1); + op->value = malloc(length); + if (!op->value) { + free(op); + return 0; + } + + req = request_create(client); + if (!req) { + free(op->value); + free(op); + return 0; + } + + memcpy(op->value, value, length); + + op->client = client; + op->reliable = reliable; + op->value_handle = value_handle; + op->length = length; + op->offset = offset; + op->cur_length = MIN(length, bt_att_get_mtu(client->att) - 5); + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + + req->data = op; + req->destroy = long_write_op_free; + req->long_write = true; + + if (client->in_long_write || client->reliable_write_session_id > 0) { + queue_push_tail(client->long_write_queue, req); + return req->id; + } + + pdu = malloc(op->cur_length + 4); + if (!pdu) { + free(op->value); + free(op); + return 0; + } + + put_le16(value_handle, pdu); + put_le16(offset, pdu + 2); + memcpy(pdu + 4, op->value, op->cur_length); + + req->att_id = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ, + pdu, op->cur_length + 4, + prepare_write_cb, req, + request_unref); + free(pdu); + + if (!req->att_id) { + op->destroy = NULL; + request_unref(req); + return 0; + } + + client->in_long_write = true; + + return req->id; +} + +struct prep_write_op { + bt_gatt_client_write_long_callback_t callback; + void *user_data; + bt_gatt_destroy_func_t destroy; + uint8_t *pdu; + uint16_t pdu_len; +}; + +static void destroy_prep_write_op(void *data) +{ + struct prep_write_op *op = data; + + if (op->destroy) + op->destroy(op->user_data); + + free(op->pdu); + free(op); +} + +static void prep_write_cb(uint8_t opcode, const void *pdu, uint16_t length, + void *user_data) +{ + struct request *req = user_data; + struct prep_write_op *op = req->data; + bool success; + uint8_t att_ecode; + bool reliable_error; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + reliable_error = false; + att_ecode = process_error(pdu, length); + goto done; + } + + if (opcode != BT_ATT_OP_PREP_WRITE_RSP) { + success = false; + reliable_error = false; + att_ecode = 0; + goto done; + } + + if (!pdu || length != op->pdu_len || + memcmp(pdu, op->pdu, op->pdu_len)) { + success = false; + reliable_error = true; + att_ecode = 0; + goto done; + } + + success = true; + reliable_error = false; + att_ecode = 0; + +done: + if (op->callback) + op->callback(success, reliable_error, att_ecode, op->user_data); +} + +static struct request *get_reliable_request(struct bt_gatt_client *client, + unsigned int id) +{ + struct request *req; + struct prep_write_op *op; + + op = new0(struct prep_write_op, 1); + + /* Following prepare writes */ + if (id != 0) + req = queue_find(client->pending_requests, match_req_id, + UINT_TO_PTR(id)); + else + req = request_create(client); + + if (!req) { + free(op); + return NULL; + } + + req->data = op; + + return req; +} + +unsigned int bt_gatt_client_prepare_write(struct bt_gatt_client *client, + unsigned int id, uint16_t value_handle, + uint16_t offset, const uint8_t *value, + uint16_t length, + bt_gatt_client_write_long_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + struct request *req; + struct prep_write_op *op; + uint8_t pdu[4 + length]; + + if (!client) + return 0; + + if (client->in_long_write) + return 0; + + /* + * Make sure that client who owns reliable session continues with + * prepare writes or this is brand new reliable session (id == 0) + */ + if (id != client->reliable_write_session_id) { + util_debug(client->debug_callback, client->debug_data, + "There is other reliable write session ongoing %u", + client->reliable_write_session_id); + + return 0; + } + + req = get_reliable_request(client, id); + if (!req) + return 0; + + op = (struct prep_write_op *)req->data; + + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + + req->destroy = destroy_prep_write_op; + req->prep_write = true; + + put_le16(value_handle, pdu); + put_le16(offset, pdu + 2); + memcpy(pdu + 4, value, length); + + /* + * Before sending command we need to remember pdu as we need to validate + * it in the response. Store handle, offset and value. Therefore + * increase length by 4 (handle + offset) as we need it in couple places + * below + */ + length += 4; + + op->pdu = malloc(length); + if (!op->pdu) { + op->destroy = NULL; + request_unref(req); + return 0; + } + + memcpy(op->pdu, pdu, length); + op->pdu_len = length; + + /* + * Now we are ready to send command + * Note that request_unref will be done on write execute + */ + req->att_id = bt_att_send(client->att, BT_ATT_OP_PREP_WRITE_REQ, pdu, + sizeof(pdu), prep_write_cb, req, + NULL); + if (!req->att_id) { + op->destroy = NULL; + request_unref(req); + return 0; + } + + /* + * Store first request id for prepare write and treat it as a session id + * valid until write execute is done + */ + if (client->reliable_write_session_id == 0) + client->reliable_write_session_id = req->id; + + return client->reliable_write_session_id; +} + +static void exec_write_cb(uint8_t opcode, const void *pdu, uint16_t length, + void *user_data) +{ + struct request *req = user_data; + struct write_op *op = req->data; + bool success; + uint8_t att_ecode; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + if (opcode != BT_ATT_OP_EXEC_WRITE_RSP || pdu || length) { + success = false; + att_ecode = 0; + goto done; + } + + success = true; + att_ecode = 0; + +done: + if (op->callback) + op->callback(success, att_ecode, op->user_data); + + op->client->reliable_write_session_id = 0; + + start_next_long_write(op->client); +} + +unsigned int bt_gatt_client_write_execute(struct bt_gatt_client *client, + unsigned int id, + bt_gatt_client_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + struct request *req; + struct write_op *op; + uint8_t pdu; + + if (!client) + return 0; + + if (client->in_long_write) + return 0; + + if (client->reliable_write_session_id != id) + return 0; + + op = new0(struct write_op, 1); + + req = queue_find(client->pending_requests, match_req_id, + UINT_TO_PTR(id)); + if (!req) { + free(op); + return 0; + } + + op->client = client; + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + + pdu = 0x01; + + req->data = op; + req->destroy = destroy_write_op; + + req->att_id = bt_att_send(client->att, BT_ATT_OP_EXEC_WRITE_REQ, &pdu, + sizeof(pdu), exec_write_cb, + req, request_unref); + if (!req->att_id) { + op->destroy = NULL; + request_unref(req); + return 0; + } + + return id; +} + +unsigned int bt_gatt_client_register_notify(struct bt_gatt_client *client, + uint16_t chrc_value_handle, + bt_gatt_client_register_callback_t callback, + bt_gatt_client_notify_callback_t notify, + void *user_data, + bt_gatt_client_destroy_func_t destroy) +{ + if (!client || !client->db || !chrc_value_handle || !callback) + return 0; + + if (client->in_svc_chngd) + return 0; + + return register_notify(client, chrc_value_handle, callback, notify, + user_data, destroy); +} + +bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client, + unsigned int id) +{ + struct notify_data *notify_data; + + if (!client || !id) + return false; + + notify_data = queue_remove_if(client->notify_list, match_notify_data_id, + UINT_TO_PTR(id)); + if (!notify_data) + return false; + + /* Remove data if it has been queued */ + queue_remove(notify_data->chrc->reg_notify_queue, notify_data); + + /* Reset callbacks */ + notify_data->callback = NULL; + notify_data->notify = NULL; + + complete_unregister_notify(notify_data); + return true; +} + +bool bt_gatt_client_set_security(struct bt_gatt_client *client, int level) +{ + if (!client) + return false; + + return bt_att_set_security(client->att, level); +} + +int bt_gatt_client_get_security(struct bt_gatt_client *client) +{ + if (!client) + return -1; + + return bt_att_get_security(client->att, NULL); +} diff --git a/src/shared/gatt-client.h b/src/shared/gatt-client.h new file mode 100644 index 0000000..6d8bf80 --- /dev/null +++ b/src/shared/gatt-client.h @@ -0,0 +1,138 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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 + * + */ + +#include +#include +#include + +#define BT_GATT_UUID_SIZE 16 + +struct bt_gatt_client; + +struct bt_gatt_client *bt_gatt_client_new(struct gatt_db *db, + struct bt_att *att, + uint16_t mtu); +struct bt_gatt_client *bt_gatt_client_clone(struct bt_gatt_client *client); + +struct bt_gatt_client *bt_gatt_client_ref(struct bt_gatt_client *client); +void bt_gatt_client_unref(struct bt_gatt_client *client); + +typedef void (*bt_gatt_client_destroy_func_t)(void *user_data); +typedef void (*bt_gatt_client_callback_t)(bool success, uint8_t att_ecode, + void *user_data); +typedef void (*bt_gatt_client_debug_func_t)(const char *str, void *user_data); +typedef void (*bt_gatt_client_read_callback_t)(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data); +typedef void (*bt_gatt_client_write_long_callback_t)(bool success, + bool reliable_error, uint8_t att_ecode, + void *user_data); +typedef void (*bt_gatt_client_notify_callback_t)(uint16_t value_handle, + const uint8_t *value, uint16_t length, + void *user_data); +typedef void (*bt_gatt_client_register_callback_t)(uint16_t att_ecode, + void *user_data); +typedef void (*bt_gatt_client_service_changed_callback_t)(uint16_t start_handle, + uint16_t end_handle, + void *user_data); + +bool bt_gatt_client_is_ready(struct bt_gatt_client *client); +unsigned int bt_gatt_client_ready_register(struct bt_gatt_client *client, + bt_gatt_client_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); +bool bt_gatt_client_ready_unregister(struct bt_gatt_client *client, + unsigned int id); +bool bt_gatt_client_set_service_changed(struct bt_gatt_client *client, + bt_gatt_client_service_changed_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); +bool bt_gatt_client_set_debug(struct bt_gatt_client *client, + bt_gatt_client_debug_func_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); + +uint16_t bt_gatt_client_get_mtu(struct bt_gatt_client *client); +struct gatt_db *bt_gatt_client_get_db(struct bt_gatt_client *client); + +bool bt_gatt_client_cancel(struct bt_gatt_client *client, unsigned int id); +bool bt_gatt_client_cancel_all(struct bt_gatt_client *client); + +unsigned int bt_gatt_client_read_value(struct bt_gatt_client *client, + uint16_t value_handle, + bt_gatt_client_read_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); +unsigned int bt_gatt_client_read_long_value(struct bt_gatt_client *client, + uint16_t value_handle, uint16_t offset, + bt_gatt_client_read_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); +unsigned int bt_gatt_client_read_multiple(struct bt_gatt_client *client, + uint16_t *handles, uint8_t num_handles, + bt_gatt_client_read_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); + +unsigned int bt_gatt_client_write_without_response( + struct bt_gatt_client *client, + uint16_t value_handle, + bool signed_write, + const uint8_t *value, uint16_t length); +unsigned int bt_gatt_client_write_value(struct bt_gatt_client *client, + uint16_t value_handle, + const uint8_t *value, uint16_t length, + bt_gatt_client_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); +unsigned int bt_gatt_client_write_long_value(struct bt_gatt_client *client, + bool reliable, + uint16_t value_handle, uint16_t offset, + const uint8_t *value, uint16_t length, + bt_gatt_client_write_long_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); +unsigned int bt_gatt_client_prepare_write(struct bt_gatt_client *client, + unsigned int id, + uint16_t value_handle, uint16_t offset, + const uint8_t *value, uint16_t length, + bt_gatt_client_write_long_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); +unsigned int bt_gatt_client_write_execute(struct bt_gatt_client *client, + unsigned int id, + bt_gatt_client_callback_t callback, + void *user_data, + bt_gatt_client_destroy_func_t destroy); + +unsigned int bt_gatt_client_register_notify(struct bt_gatt_client *client, + uint16_t chrc_value_handle, + bt_gatt_client_register_callback_t callback, + bt_gatt_client_notify_callback_t notify, + void *user_data, + bt_gatt_client_destroy_func_t destroy); +bool bt_gatt_client_unregister_notify(struct bt_gatt_client *client, + unsigned int id); + +bool bt_gatt_client_set_security(struct bt_gatt_client *client, int level); +int bt_gatt_client_get_security(struct bt_gatt_client *client); diff --git a/src/shared/gatt-db.c b/src/shared/gatt-db.c new file mode 100644 index 0000000..b44f7b5 --- /dev/null +++ b/src/shared/gatt-db.c @@ -0,0 +1,1987 @@ +/* + * + * 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 +#endif + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/uuid.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/timeout.h" +#include "src/shared/att.h" +#include "src/shared/gatt-db.h" +#include "src/shared/crypto.h" + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#define MAX_CHAR_DECL_VALUE_LEN 19 +#define MAX_INCLUDED_VALUE_LEN 6 +#define ATTRIBUTE_TIMEOUT 5000 +#define HASH_UPDATE_TIMEOUT 100 + +static const bt_uuid_t primary_service_uuid = { .type = BT_UUID16, + .value.u16 = GATT_PRIM_SVC_UUID }; +static const bt_uuid_t secondary_service_uuid = { .type = BT_UUID16, + .value.u16 = GATT_SND_SVC_UUID }; +static const bt_uuid_t characteristic_uuid = { .type = BT_UUID16, + .value.u16 = GATT_CHARAC_UUID }; +static const bt_uuid_t included_service_uuid = { .type = BT_UUID16, + .value.u16 = GATT_INCLUDE_UUID }; +static const bt_uuid_t ext_desc_uuid = { .type = BT_UUID16, + .value.u16 = GATT_CHARAC_EXT_PROPER_UUID }; + +struct gatt_db { + int ref_count; + struct bt_crypto *crypto; + uint8_t hash[16]; + unsigned int hash_id; + uint16_t next_handle; + struct queue *services; + + struct queue *notify_list; + unsigned int next_notify_id; + + gatt_db_authorize_cb_t authorize; + void *authorize_data; +}; + +struct notify { + unsigned int id; + gatt_db_attribute_cb_t service_added; + gatt_db_attribute_cb_t service_removed; + gatt_db_authorize_cb_t authorize_cb; + gatt_db_destroy_func_t destroy; + void *user_data; +}; + +struct pending_read { + struct gatt_db_attribute *attrib; + unsigned int id; + unsigned int timeout_id; + gatt_db_attribute_read_t func; + void *user_data; +}; + +struct pending_write { + struct gatt_db_attribute *attrib; + unsigned int id; + unsigned int timeout_id; + gatt_db_attribute_write_t func; + void *user_data; +}; + +struct gatt_db_attribute { + struct gatt_db_service *service; + uint16_t handle; + bt_uuid_t uuid; + uint32_t permissions; + uint16_t value_len; + uint8_t *value; + + gatt_db_read_t read_func; + gatt_db_write_t write_func; + void *user_data; + + unsigned int read_id; + struct queue *pending_reads; + + unsigned int write_id; + struct queue *pending_writes; +}; + +struct gatt_db_service { + struct gatt_db *db; + bool active; + bool claimed; + uint16_t num_handles; + struct gatt_db_attribute **attributes; +}; + +static void set_attribute_data(struct gatt_db_attribute *attribute, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + uint32_t permissions, + void *user_data) +{ + attribute->permissions = permissions; + attribute->read_func = read_func; + attribute->write_func = write_func; + attribute->user_data = user_data; +} + +static void pending_read_result(struct pending_read *p, int err, + const uint8_t *data, size_t length) +{ + if (p->timeout_id > 0) + timeout_remove(p->timeout_id); + + p->func(p->attrib, err, data, length, p->user_data); + + free(p); +} + +static void pending_read_free(void *data) +{ + struct pending_read *p = data; + + pending_read_result(p, -ECANCELED, NULL, 0); +} + +static void pending_write_result(struct pending_write *p, int err) +{ + if (p->timeout_id > 0) + timeout_remove(p->timeout_id); + + p->func(p->attrib, err, p->user_data); + + free(p); +} + +static void pending_write_free(void *data) +{ + struct pending_write *p = data; + + pending_write_result(p, -ECANCELED); +} + +static void attribute_destroy(struct gatt_db_attribute *attribute) +{ + /* Attribute was not initialized by user */ + if (!attribute) + return; + + queue_destroy(attribute->pending_reads, pending_read_free); + queue_destroy(attribute->pending_writes, pending_write_free); + + free(attribute->value); + free(attribute); +} + +static struct gatt_db_attribute *new_attribute(struct gatt_db_service *service, + uint16_t handle, + const bt_uuid_t *type, + const uint8_t *val, + uint16_t len) +{ + struct gatt_db_attribute *attribute; + + attribute = new0(struct gatt_db_attribute, 1); + + attribute->service = service; + attribute->handle = handle; + attribute->uuid = *type; + attribute->value_len = len; + if (len) { + attribute->value = malloc0(len); + if (!attribute->value) + goto failed; + + memcpy(attribute->value, val, len); + } + + attribute->pending_reads = queue_new(); + attribute->pending_writes = queue_new(); + + return attribute; + +failed: + attribute_destroy(attribute); + return NULL; +} + +struct gatt_db *gatt_db_ref(struct gatt_db *db) +{ + if (!db) + return NULL; + + __sync_fetch_and_add(&db->ref_count, 1); + + return db; +} + +struct gatt_db *gatt_db_new(void) +{ + struct gatt_db *db; + + db = new0(struct gatt_db, 1); + db->crypto = bt_crypto_new(); + db->services = queue_new(); + db->notify_list = queue_new(); + db->next_handle = 0x0001; + + return gatt_db_ref(db); +} + +static void notify_destroy(void *data) +{ + struct notify *notify = data; + + if (notify->destroy) + notify->destroy(notify->user_data); + + free(notify); +} + +static bool match_notify_id(const void *a, const void *b) +{ + const struct notify *notify = a; + unsigned int id = PTR_TO_UINT(b); + + return notify->id == id; +} + +struct notify_data { + struct gatt_db_attribute *attr; + bool added; +}; + +static void handle_notify(void *data, void *user_data) +{ + struct notify *notify = data; + struct notify_data *notify_data = user_data; + + if (notify_data->added) + notify->service_added(notify_data->attr, notify->user_data); + else + notify->service_removed(notify_data->attr, notify->user_data); +} + +struct hash_data { + struct iovec *iov; + uint16_t i; +}; + +static void gen_hash_m(struct gatt_db_attribute *attr, void *user_data) +{ + struct hash_data *hash = user_data; + uint8_t *data; + size_t len; + + if (bt_uuid_len(&attr->uuid) != 2) + return; + + switch (attr->uuid.value.u16) { + case GATT_PRIM_SVC_UUID: + case GATT_SND_SVC_UUID: + case GATT_INCLUDE_UUID: + case GATT_CHARAC_UUID: + /* Allocate space for handle + type + value */ + len = 2 + 2 + attr->value_len; + data = malloc(2 + 2 + attr->value_len); + put_le16(attr->handle, data); + bt_uuid_to_le(&attr->uuid, data + 2); + memcpy(data + 4, attr->value, attr->value_len); + break; + case GATT_CHARAC_USER_DESC_UUID: + case GATT_CLIENT_CHARAC_CFG_UUID: + case GATT_SERVER_CHARAC_CFG_UUID: + case GATT_CHARAC_FMT_UUID: + case GATT_CHARAC_AGREG_FMT_UUID: + /* Allocate space for handle + type */ + len = 2 + 2; + data = malloc(2 + 2 + attr->value_len); + put_le16(attr->handle, data); + bt_uuid_to_le(&attr->uuid, data + 2); + break; + default: + return; + } + + hash->iov[hash->i].iov_base = data; + hash->iov[hash->i].iov_len = len; + + hash->i++; + + return; +} + +static void service_gen_hash_m(struct gatt_db_attribute *attr, void *user_data) +{ + gatt_db_service_foreach(attr, NULL, gen_hash_m, user_data); +} + +static bool db_hash_update(void *user_data) +{ + struct gatt_db *db = user_data; + struct hash_data hash; + uint16_t i; + + db->hash_id = 0; + + if (!db->next_handle) + return false; + + hash.iov = new0(struct iovec, db->next_handle); + hash.i = 0; + + gatt_db_foreach_service(db, NULL, service_gen_hash_m, &hash); + bt_crypto_gatt_hash(db->crypto, hash.iov, db->next_handle, db->hash); + + for (i = 0; i < hash.i; i++) + free(hash.iov[i].iov_base); + + free(hash.iov); + + return false; +} + +static void notify_service_changed(struct gatt_db *db, + struct gatt_db_service *service, + bool added) +{ + struct notify_data data; + + if (queue_isempty(db->notify_list)) + return; + + data.attr = service->attributes[0]; + data.added = added; + + gatt_db_ref(db); + + queue_foreach(db->notify_list, handle_notify, &data); + + /* Tigger hash update */ + if (!db->hash_id && db->crypto) + db->hash_id = timeout_add(HASH_UPDATE_TIMEOUT, db_hash_update, + db, NULL); + + gatt_db_unref(db); +} + +static void gatt_db_service_destroy(void *data) +{ + struct gatt_db_service *service = data; + int i; + + if (service->active) + notify_service_changed(service->db, service, false); + + for (i = 0; i < service->num_handles; i++) + attribute_destroy(service->attributes[i]); + + free(service->attributes); + free(service); +} + +static void gatt_db_destroy(struct gatt_db *db) +{ + if (!db) + return; + + bt_crypto_unref(db->crypto); + + /* + * Clear the notify list before clearing the services to prevent the + * latter from sending service_removed events. + */ + queue_destroy(db->notify_list, notify_destroy); + db->notify_list = NULL; + + if (db->hash_id) + timeout_remove(db->hash_id); + + queue_destroy(db->services, gatt_db_service_destroy); + free(db); +} + +void gatt_db_unref(struct gatt_db *db) +{ + if (!db) + return; + + if (__sync_sub_and_fetch(&db->ref_count, 1)) + return; + + gatt_db_destroy(db); +} + +bool gatt_db_isempty(struct gatt_db *db) +{ + if (!db) + return true; + + return queue_isempty(db->services); +} + +static int uuid_to_le(const bt_uuid_t *uuid, uint8_t *dst) +{ + bt_uuid_t uuid128; + + if (uuid->type == BT_UUID16) { + put_le16(uuid->value.u16, dst); + return bt_uuid_len(uuid); + } + + bt_uuid_to_uuid128(uuid, &uuid128); + bswap_128(&uuid128.value.u128, dst); + return bt_uuid_len(&uuid128); +} + +static bool le_to_uuid(const uint8_t *src, size_t len, bt_uuid_t *uuid) +{ + uint128_t u128; + + if (len == 2) { + bt_uuid16_create(uuid, get_le16(src)); + return true; + } + + if (len == 4) { + bt_uuid32_create(uuid, get_le32(src)); + return true; + } + + if (len != 16) + return false; + + bswap_128(src, &u128); + bt_uuid128_create(uuid, u128); + + return true; +} + +static struct gatt_db_service *gatt_db_service_create(const bt_uuid_t *uuid, + uint16_t handle, + bool primary, + uint16_t num_handles) +{ + struct gatt_db_service *service; + const bt_uuid_t *type; + uint8_t value[16]; + uint16_t len; + + if (num_handles < 1) + return NULL; + + service = new0(struct gatt_db_service, 1); + service->attributes = new0(struct gatt_db_attribute *, num_handles); + + if (primary) + type = &primary_service_uuid; + else + type = &secondary_service_uuid; + + len = uuid_to_le(uuid, value); + + service->attributes[0] = new_attribute(service, handle, type, value, + len); + if (!service->attributes[0]) { + gatt_db_service_destroy(service); + return NULL; + } + + set_attribute_data(service->attributes[0], NULL, NULL, BT_ATT_PERM_READ, NULL); + + return service; +} + + +bool gatt_db_remove_service(struct gatt_db *db, + struct gatt_db_attribute *attrib) +{ + struct gatt_db_service *service; + + if (!db || !attrib) + return false; + + service = attrib->service; + + queue_remove(db->services, service); + + gatt_db_service_destroy(service); + + return true; +} + +bool gatt_db_clear(struct gatt_db *db) +{ + return gatt_db_clear_range(db, 1, UINT16_MAX); +} + +static void gatt_db_service_get_handles(const struct gatt_db_service *service, + uint16_t *start_handle, + uint16_t *end_handle) +{ + if (start_handle) + *start_handle = service->attributes[0]->handle; + + if (end_handle) + *end_handle = service->attributes[0]->handle + + service->num_handles - 1; +} + +struct clear_range { + uint16_t start, end; +}; + +static bool match_range(const void *a, const void *b) +{ + const struct gatt_db_service *service = a; + const struct clear_range *range = b; + uint16_t svc_start, svc_end; + + gatt_db_service_get_handles(service, &svc_start, &svc_end); + + return svc_start <= range->end && svc_end >= range->start; +} + +bool gatt_db_clear_range(struct gatt_db *db, uint16_t start_handle, + uint16_t end_handle) +{ + struct clear_range range; + + if (!db || start_handle > end_handle) + return false; + + /* Check if it is a full clear */ + if (start_handle == 1 && end_handle == UINT16_MAX) { + queue_remove_all(db->services, NULL, NULL, + gatt_db_service_destroy); + goto done; + } + + range.start = start_handle; + range.end = end_handle; + + queue_remove_all(db->services, match_range, &range, + gatt_db_service_destroy); + +done: + if (gatt_db_isempty(db)) + db->next_handle = 0; + + return true; +} + +uint8_t *gatt_db_get_hash(struct gatt_db *db) +{ + uint8_t hash[16] = {}; + + if (!db || !db->crypto) + return NULL; + + /* Generate hash if if has not been generated yet */ + if (db->hash_id || !memcmp(db->hash, hash, 16)) { + timeout_remove(db->hash_id); + db_hash_update(db); + } + + return db->hash; +} + +static struct gatt_db_service *find_insert_loc(struct gatt_db *db, + uint16_t start, uint16_t end, + struct gatt_db_service **after) +{ + const struct queue_entry *services_entry; + struct gatt_db_service *service; + uint16_t cur_start, cur_end; + + *after = NULL; + + services_entry = queue_get_entries(db->services); + + while (services_entry) { + service = services_entry->data; + + gatt_db_service_get_handles(service, &cur_start, &cur_end); + + if (start >= cur_start && start <= cur_end) + return service; + + if (end >= cur_start && end <= cur_end) + return service; + + if (end < cur_start) + return NULL; + + *after = service; + services_entry = services_entry->next; + } + + return NULL; +} + +struct gatt_db_attribute *gatt_db_insert_service(struct gatt_db *db, + uint16_t handle, + const bt_uuid_t *uuid, + bool primary, + uint16_t num_handles) +{ + struct gatt_db_service *service, *after; + + after = NULL; + + if (!db) + return NULL; + + if (!handle) + handle = db->next_handle; + + if (num_handles < 1 || (handle + num_handles - 1) > UINT16_MAX) + return NULL; + + service = find_insert_loc(db, handle, handle + num_handles - 1, &after); + if (service) { + const bt_uuid_t *type; + bt_uuid_t value; + + if (primary) + type = &primary_service_uuid; + else + type = &secondary_service_uuid; + + gatt_db_attribute_get_service_uuid(service->attributes[0], + &value); + + /* Check if service match */ + if (!bt_uuid_cmp(&service->attributes[0]->uuid, type) && + !bt_uuid_cmp(&value, uuid) && + service->num_handles == num_handles && + service->attributes[0]->handle == handle) + return service->attributes[0]; + + return NULL; + } + + service = gatt_db_service_create(uuid, handle, primary, num_handles); + + if (!service) + return NULL; + + if (after) { + if (!queue_push_after(db->services, after, service)) + goto fail; + } else if (!queue_push_head(db->services, service)) { + goto fail; + } + + service->db = db; + service->attributes[0]->handle = handle; + service->num_handles = num_handles; + + /* Fast-forward next_handle if the new service was added to the end */ + db->next_handle = MAX(handle + num_handles, db->next_handle); + + return service->attributes[0]; + +fail: + gatt_db_service_destroy(service); + return NULL; +} + +struct gatt_db_attribute *gatt_db_add_service(struct gatt_db *db, + const bt_uuid_t *uuid, + bool primary, + uint16_t num_handles) +{ + return gatt_db_insert_service(db, 0, uuid, primary, num_handles); +} + +unsigned int gatt_db_register(struct gatt_db *db, + gatt_db_attribute_cb_t service_added, + gatt_db_attribute_cb_t service_removed, + void *user_data, + gatt_db_destroy_func_t destroy) +{ + struct notify *notify; + + if (!db || !(service_added || service_removed)) + return 0; + + notify = new0(struct notify, 1); + notify->service_added = service_added; + notify->service_removed = service_removed; + notify->destroy = destroy; + notify->user_data = user_data; + + if (db->next_notify_id < 1) + db->next_notify_id = 1; + + notify->id = db->next_notify_id++; + + if (!queue_push_tail(db->notify_list, notify)) { + free(notify); + return 0; + } + + return notify->id; +} + +bool gatt_db_unregister(struct gatt_db *db, unsigned int id) +{ + struct notify *notify; + + if (!db || !id) + return false; + + notify = queue_find(db->notify_list, match_notify_id, UINT_TO_PTR(id)); + if (!notify) + return false; + + queue_remove(db->notify_list, notify); + notify_destroy(notify); + + return true; +} + +bool gatt_db_set_authorize(struct gatt_db *db, gatt_db_authorize_cb_t cb, + void *user_data) +{ + if (!db) + return false; + + db->authorize = cb; + db->authorize_data = user_data; + + return true; +} + +static uint16_t get_attribute_index(struct gatt_db_service *service, + int end_offset) +{ + int i = 0; + + /* Here we look for first free attribute index with given offset */ + while (i < (service->num_handles - end_offset) && + service->attributes[i]) + i++; + + return i == (service->num_handles - end_offset) ? 0 : i; +} + +static uint16_t get_handle_at_index(struct gatt_db_service *service, + int index) +{ + return service->attributes[index]->handle; +} + +static struct gatt_db_attribute * +service_insert_characteristic(struct gatt_db_service *service, + uint16_t handle, + const bt_uuid_t *uuid, + uint32_t permissions, + uint8_t properties, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data) +{ + uint8_t value[MAX_CHAR_DECL_VALUE_LEN]; + uint16_t len = 0; + int i; + + /* Check if handle is in within service range */ + if (handle && handle <= service->attributes[0]->handle) + return NULL; + + /* + * It is not possible to allocate last handle for a Characteristic + * since it would not have space for its value: + * 3.3.2 Characteristic Value Declaration + * The Characteristic Value declaration contains the value of the + * characteristic. It is the first Attribute after the characteristic + * declaration. All characteristic definitions shall have a + * Characteristic Value declaration. + */ + if (handle == UINT16_MAX) + return NULL; + + i = get_attribute_index(service, 1); + if (!i) + return NULL; + + if (!handle) + handle = get_handle_at_index(service, i - 1) + 2; + + value[0] = properties; + len += sizeof(properties); + + /* We set handle of characteristic value, which will be added next */ + put_le16(handle, &value[1]); + len += sizeof(uint16_t); + len += uuid_to_le(uuid, &value[3]); + + service->attributes[i] = new_attribute(service, handle - 1, + &characteristic_uuid, + value, len); + if (!service->attributes[i]) + return NULL; + + set_attribute_data(service->attributes[i], NULL, NULL, BT_ATT_PERM_READ, NULL); + + i++; + + service->attributes[i] = new_attribute(service, handle, uuid, NULL, 0); + if (!service->attributes[i]) { + free(service->attributes[i - 1]); + return NULL; + } + + set_attribute_data(service->attributes[i], read_func, write_func, + permissions, user_data); + + return service->attributes[i]; +} + +struct gatt_db_attribute * +gatt_db_insert_characteristic(struct gatt_db *db, + uint16_t handle, + const bt_uuid_t *uuid, + uint32_t permissions, + uint8_t properties, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data) +{ + struct gatt_db_attribute *attrib; + + attrib = gatt_db_get_service(db, handle); + if (!attrib) + return NULL; + + return service_insert_characteristic(attrib->service, handle, uuid, + permissions, properties, + read_func, write_func, + user_data); +} + +struct gatt_db_attribute * +gatt_db_service_insert_characteristic(struct gatt_db_attribute *attrib, + uint16_t handle, + const bt_uuid_t *uuid, + uint32_t permissions, + uint8_t properties, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data) +{ + if (!attrib) + return NULL; + + return service_insert_characteristic(attrib->service, handle, uuid, + permissions, properties, + read_func, write_func, + user_data); +} + +struct gatt_db_attribute * +gatt_db_service_add_characteristic(struct gatt_db_attribute *attrib, + const bt_uuid_t *uuid, + uint32_t permissions, + uint8_t properties, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data) +{ + if (!attrib) + return NULL; + + return service_insert_characteristic(attrib->service, 0, uuid, + permissions, properties, + read_func, write_func, + user_data); +} + +static struct gatt_db_attribute * +service_insert_descriptor(struct gatt_db_service *service, + uint16_t handle, + const bt_uuid_t *uuid, + uint32_t permissions, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data) +{ + int i; + + i = get_attribute_index(service, 0); + if (!i) + return NULL; + + /* Check if handle is in within service range */ + if (handle && handle <= service->attributes[0]->handle) + return NULL; + + if (!handle) + handle = get_handle_at_index(service, i - 1) + 1; + + service->attributes[i] = new_attribute(service, handle, uuid, NULL, 0); + if (!service->attributes[i]) + return NULL; + + set_attribute_data(service->attributes[i], read_func, write_func, + permissions, user_data); + + return service->attributes[i]; +} + +struct gatt_db_attribute * +gatt_db_insert_descriptor(struct gatt_db *db, + uint16_t handle, + const bt_uuid_t *uuid, + uint32_t permissions, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data) +{ + struct gatt_db_attribute *attrib; + + attrib = gatt_db_get_service(db, handle); + if (!attrib) + return NULL; + + return service_insert_descriptor(attrib->service, handle, uuid, + permissions, read_func, write_func, + user_data); +} + +struct gatt_db_attribute * +gatt_db_service_insert_descriptor(struct gatt_db_attribute *attrib, + uint16_t handle, + const bt_uuid_t *uuid, + uint32_t permissions, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data) +{ + if (!attrib) + return NULL; + + return service_insert_descriptor(attrib->service, handle, uuid, + permissions, read_func, write_func, + user_data); +} + +struct gatt_db_attribute * +gatt_db_service_add_descriptor(struct gatt_db_attribute *attrib, + const bt_uuid_t *uuid, + uint32_t permissions, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data) +{ + if (!attrib) + return NULL; + + return service_insert_descriptor(attrib->service, 0, uuid, + permissions, read_func, write_func, + user_data); +} + +static struct gatt_db_attribute * +service_insert_included(struct gatt_db_service *service, uint16_t handle, + struct gatt_db_attribute *include) +{ + struct gatt_db_service *included; + uint8_t value[MAX_INCLUDED_VALUE_LEN]; + uint16_t included_handle, len = 0; + int index; + + included = include->service; + + /* Adjust include to point to the first attribute */ + if (include != included->attributes[0]) + include = included->attributes[0]; + + included_handle = include->handle; + + put_le16(included_handle, &value[len]); + len += sizeof(uint16_t); + + put_le16(included_handle + included->num_handles - 1, &value[len]); + len += sizeof(uint16_t); + + /* The Service UUID shall only be present when the UUID is a 16-bit + * Bluetooth UUID. Vol 2. Part G. 3.2 + */ + if (include->value_len == sizeof(uint16_t)) { + memcpy(&value[len], include->value, include->value_len); + len += include->value_len; + } + + index = get_attribute_index(service, 0); + if (!index) + return NULL; + + /* Check if handle is in within service range */ + if (handle && handle <= service->attributes[0]->handle) + return NULL; + + if (!handle) + handle = get_handle_at_index(service, index - 1) + 1; + + service->attributes[index] = new_attribute(service, handle, + &included_service_uuid, + value, len); + if (!service->attributes[index]) + return NULL; + + /* The Attribute Permissions shall be read only and not require + * authentication or authorization. Vol 2. Part G. 3.2 + * + * TODO handle permissions + */ + set_attribute_data(service->attributes[index], NULL, NULL, + BT_ATT_PERM_READ, NULL); + + return service->attributes[index]; +} + +struct gatt_db_attribute * +gatt_db_service_add_included(struct gatt_db_attribute *attrib, + struct gatt_db_attribute *include) +{ + if (!attrib || !include) + return NULL; + + return service_insert_included(attrib->service, 0, include); +} + +struct gatt_db_attribute * +gatt_db_service_insert_included(struct gatt_db_attribute *attrib, + uint16_t handle, + struct gatt_db_attribute *include) +{ + if (!attrib || !handle || !include) + return NULL; + + return service_insert_included(attrib->service, handle, include); +} + +struct gatt_db_attribute * +gatt_db_insert_included(struct gatt_db *db, uint16_t handle, + struct gatt_db_attribute *include) +{ + struct gatt_db_attribute *attrib; + + attrib = gatt_db_get_service(db, handle); + if (!attrib) + return NULL; + + return service_insert_included(attrib->service, handle, include); +} + +bool gatt_db_service_set_active(struct gatt_db_attribute *attrib, bool active) +{ + struct gatt_db_service *service; + + if (!attrib) + return false; + + service = attrib->service; + + if (service->active == active) + return true; + + service->active = active; + + notify_service_changed(service->db, service, active); + + return true; +} + +bool gatt_db_service_get_active(struct gatt_db_attribute *attrib) +{ + if (!attrib) + return false; + + return attrib->service->active; +} + +bool gatt_db_service_set_claimed(struct gatt_db_attribute *attrib, + bool claimed) +{ + if (!attrib) + return false; + + attrib->service->claimed = claimed; + + return true; +} + +bool gatt_db_service_get_claimed(struct gatt_db_attribute *attrib) +{ + if (!attrib) + return false; + + return attrib->service->claimed; +} + +static void read_by_group_type(struct gatt_db_attribute *attribute, + void *user_data) +{ + struct queue *queue = user_data; + + queue_push_tail(queue, attribute); +} + +void gatt_db_read_by_group_type(struct gatt_db *db, uint16_t start_handle, + uint16_t end_handle, + const bt_uuid_t type, + struct queue *queue) +{ + gatt_db_foreach_service_in_range(db, &type, read_by_group_type, queue, + start_handle, end_handle); +} + +struct find_by_type_value_data { + gatt_db_attribute_cb_t func; + void *user_data; + const void *value; + size_t value_len; + unsigned int num_of_res; +}; + +static void find_by_type(struct gatt_db_attribute *attribute, void *user_data) +{ + struct find_by_type_value_data *search_data = user_data; + + /* TODO: fix for read-callback based attributes */ + if (search_data->value) { + if (search_data->value_len != attribute->value_len) + return; + + if (memcmp(attribute->value, search_data->value, + search_data->value_len)) + return; + } + + search_data->num_of_res++; + search_data->func(attribute, search_data->user_data); +} + +unsigned int gatt_db_find_by_type(struct gatt_db *db, uint16_t start_handle, + uint16_t end_handle, + const bt_uuid_t *type, + gatt_db_attribute_cb_t func, + void *user_data) +{ + struct find_by_type_value_data data; + + memset(&data, 0, sizeof(data)); + + data.func = func; + data.user_data = user_data; + + gatt_db_foreach_in_range(db, type, find_by_type, &data, + start_handle, end_handle); + + return data.num_of_res; +} + +unsigned int gatt_db_find_by_type_value(struct gatt_db *db, + uint16_t start_handle, + uint16_t end_handle, + const bt_uuid_t *type, + const void *value, + size_t value_len, + gatt_db_attribute_cb_t func, + void *user_data) +{ + struct find_by_type_value_data data; + + data.func = func; + data.user_data = user_data; + data.value = value; + data.value_len = value_len; + + gatt_db_foreach_in_range(db, type, find_by_type, &data, + start_handle, end_handle); + + return data.num_of_res; +} + +static void read_by_type(struct gatt_db_attribute *attribute, void *user_data) +{ + struct queue *queue = user_data; + + queue_push_tail(queue, attribute); +} + +void gatt_db_read_by_type(struct gatt_db *db, uint16_t start_handle, + uint16_t end_handle, + const bt_uuid_t type, + struct queue *queue) +{ + gatt_db_foreach_in_range(db, &type, read_by_type, queue, + start_handle, end_handle); +} + + +static void find_information(struct gatt_db_attribute *attribute, + void *user_data) +{ + struct queue *queue = user_data; + + queue_push_tail(queue, attribute); +} + +void gatt_db_find_information(struct gatt_db *db, uint16_t start_handle, + uint16_t end_handle, + struct queue *queue) +{ + gatt_db_foreach_in_range(db, NULL, find_information, queue, + start_handle, end_handle); +} + +void gatt_db_foreach_service(struct gatt_db *db, const bt_uuid_t *uuid, + gatt_db_attribute_cb_t func, + void *user_data) +{ + gatt_db_foreach_service_in_range(db, uuid, func, user_data, 0x0001, + 0xffff); +} + +struct foreach_data { + gatt_db_attribute_cb_t func; + const bt_uuid_t *uuid; + void *user_data; + uint16_t start, end; + bool attr; +}; + +static void foreach_service_in_range(void *data, void *user_data) +{ + struct gatt_db_service *service = data; + struct gatt_db_attribute *attribute = service->attributes[0]; + struct foreach_data *foreach_data = user_data; + bt_uuid_t uuid; + + if (foreach_data->uuid) { + gatt_db_attribute_get_service_uuid(attribute, &uuid); + if (bt_uuid_cmp(&uuid, foreach_data->uuid)) { + /* Compare with attribute UUID in case it is a lookup + * by group type. + */ + if (bt_uuid_cmp(&attribute->uuid, foreach_data->uuid)) + return; + } + } + + foreach_data->func(service->attributes[0], foreach_data->user_data); +} + +static void foreach_in_range(void *data, void *user_data) +{ + struct gatt_db_service *service = data; + struct foreach_data *foreach_data = user_data; + uint16_t svc_start, svc_end; + int i; + + if (!service->active) + return; + + gatt_db_service_get_handles(service, &svc_start, &svc_end); + + /* Check if service is within requested range */ + if (svc_start > foreach_data->end || svc_end < foreach_data->start) + return; + + if (!foreach_data->attr) { + if (svc_start < foreach_data->start || + svc_start > foreach_data->end) + return; + return foreach_service_in_range(data, user_data); + } + + for (i = 0; i < service->num_handles; i++) { + struct gatt_db_attribute *attribute = service->attributes[i]; + + if (!attribute) + continue; + + if (attribute->handle < foreach_data->start) + continue; + + if (attribute->handle > foreach_data->end) + return; + + if (foreach_data->uuid && bt_uuid_cmp(foreach_data->uuid, + &attribute->uuid)) + continue; + + foreach_data->func(attribute, foreach_data->user_data); + } +} + +void gatt_db_foreach_service_in_range(struct gatt_db *db, + const bt_uuid_t *uuid, + gatt_db_attribute_cb_t func, + void *user_data, + uint16_t start_handle, + uint16_t end_handle) +{ + struct foreach_data data; + + if (!db || !func || start_handle > end_handle) + return; + + data.func = func; + data.uuid = uuid; + data.user_data = user_data; + data.start = start_handle; + data.end = end_handle; + data.attr = false; + + queue_foreach(db->services, foreach_in_range, &data); +} + +void gatt_db_foreach_in_range(struct gatt_db *db, const bt_uuid_t *uuid, + gatt_db_attribute_cb_t func, + void *user_data, + uint16_t start_handle, + uint16_t end_handle) +{ + struct foreach_data data; + + if (!db || !func || start_handle > end_handle) + return; + + data.func = func; + data.uuid = uuid; + data.user_data = user_data; + data.start = start_handle; + data.end = end_handle; + data.attr = true; + + queue_foreach(db->services, foreach_in_range, &data); +} + +void gatt_db_service_foreach(struct gatt_db_attribute *attrib, + const bt_uuid_t *uuid, + gatt_db_attribute_cb_t func, + void *user_data) +{ + struct gatt_db_service *service; + struct gatt_db_attribute *attr; + uint16_t i; + + if (!attrib || !func) + return; + + service = attrib->service; + + for (i = 0; i < service->num_handles; i++) { + attr = service->attributes[i]; + if (!attr) + continue; + + if (uuid && bt_uuid_cmp(uuid, &attr->uuid)) + continue; + + func(attr, user_data); + } +} + +void gatt_db_service_foreach_char(struct gatt_db_attribute *attrib, + gatt_db_attribute_cb_t func, + void *user_data) +{ + gatt_db_service_foreach(attrib, &characteristic_uuid, func, user_data); +} + +void gatt_db_service_foreach_desc(struct gatt_db_attribute *attrib, + gatt_db_attribute_cb_t func, + void *user_data) +{ + struct gatt_db_service *service; + struct gatt_db_attribute *attr; + uint16_t i; + + if (!attrib || !func) + return; + + /* Return if this attribute is not a characteristic declaration */ + if (bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) + return; + + service = attrib->service; + + /* Start from the attribute following the value handle */ + for (i = 0; i < service->num_handles; i++) { + if (service->attributes[i] == attrib) { + i += 2; + break; + } + } + + for (; i < service->num_handles; i++) { + attr = service->attributes[i]; + if (!attr) + continue; + + /* Return if we reached the end of this characteristic */ + if (!bt_uuid_cmp(&characteristic_uuid, &attr->uuid) || + !bt_uuid_cmp(&included_service_uuid, &attr->uuid)) + return; + + func(attr, user_data); + } +} + +void gatt_db_service_foreach_incl(struct gatt_db_attribute *attrib, + gatt_db_attribute_cb_t func, + void *user_data) +{ + gatt_db_service_foreach(attrib, &included_service_uuid, func, + user_data); +} + +static bool find_service_for_handle(const void *data, const void *user_data) +{ + const struct gatt_db_service *service = data; + uint16_t handle = PTR_TO_UINT(user_data); + uint16_t start, end; + + gatt_db_service_get_handles(service, &start, &end); + + return (start <= handle) && (handle <= end); +} + +struct gatt_db_attribute *gatt_db_get_service(struct gatt_db *db, + uint16_t handle) +{ + struct gatt_db_service *service; + + if (!db || !handle) + return NULL; + + service = queue_find(db->services, find_service_for_handle, + UINT_TO_PTR(handle)); + if (!service) + return NULL; + + return service->attributes[0]; +} + +struct gatt_db_attribute *gatt_db_get_attribute(struct gatt_db *db, + uint16_t handle) +{ + struct gatt_db_attribute *attrib; + struct gatt_db_service *service; + int i; + + attrib = gatt_db_get_service(db, handle); + if (!attrib) + return NULL; + + service = attrib->service; + + for (i = 0; i < service->num_handles; i++) { + if (!service->attributes[i]) + continue; + + if (service->attributes[i]->handle == handle) + return service->attributes[i]; + } + + return NULL; +} + +static bool find_service_with_uuid(const void *data, const void *user_data) +{ + const struct gatt_db_service *service = data; + const bt_uuid_t *uuid = user_data; + bt_uuid_t svc_uuid; + + gatt_db_attribute_get_service_uuid(service->attributes[0], &svc_uuid); + + return bt_uuid_cmp(uuid, &svc_uuid) == 0; +} + +struct gatt_db_attribute *gatt_db_get_service_with_uuid(struct gatt_db *db, + const bt_uuid_t *uuid) +{ + struct gatt_db_service *service; + + if (!db || !uuid) + return NULL; + + service = queue_find(db->services, find_service_with_uuid, uuid); + if (!service) + return NULL; + + return service->attributes[0]; +} + +const bt_uuid_t *gatt_db_attribute_get_type( + const struct gatt_db_attribute *attrib) +{ + if (!attrib) + return NULL; + + return &attrib->uuid; +} + +uint16_t gatt_db_attribute_get_handle(const struct gatt_db_attribute *attrib) +{ + if (!attrib) + return 0; + + return attrib->handle; +} + +bool gatt_db_attribute_get_service_uuid(const struct gatt_db_attribute *attrib, + bt_uuid_t *uuid) +{ + struct gatt_db_service *service; + + if (!attrib || !uuid) + return false; + + service = attrib->service; + + if (service->attributes[0]->value_len == sizeof(uint16_t)) { + uint16_t value; + + value = get_le16(service->attributes[0]->value); + bt_uuid16_create(uuid, value); + + return true; + } + + if (service->attributes[0]->value_len == sizeof(uint128_t)) { + uint128_t value; + + bswap_128(service->attributes[0]->value, &value); + bt_uuid128_create(uuid, value); + + return true; + } + + return false; +} + +bool gatt_db_attribute_get_service_handles( + const struct gatt_db_attribute *attrib, + uint16_t *start_handle, + uint16_t *end_handle) +{ + struct gatt_db_service *service; + + if (!attrib) + return false; + + service = attrib->service; + + gatt_db_service_get_handles(service, start_handle, end_handle); + + return true; +} + +bool gatt_db_attribute_get_service_data(const struct gatt_db_attribute *attrib, + uint16_t *start_handle, + uint16_t *end_handle, + bool *primary, + bt_uuid_t *uuid) +{ + struct gatt_db_service *service; + struct gatt_db_attribute *decl; + + if (!attrib) + return false; + + service = attrib->service; + decl = service->attributes[0]; + + gatt_db_service_get_handles(service, start_handle, end_handle); + + if (primary) + *primary = bt_uuid_cmp(&decl->uuid, &secondary_service_uuid); + + if (!uuid) + return true; + + /* + * The service declaration attribute value is the 16 or 128 bit service + * UUID. + */ + return le_to_uuid(decl->value, decl->value_len, uuid); +} + +static void read_ext_prop_value(struct gatt_db_attribute *attrib, + int err, const uint8_t *value, + size_t length, void *user_data) +{ + uint16_t *ext_prop = user_data; + + if (err || (length != sizeof(uint16_t))) + return; + + *ext_prop = (uint16_t) value[0]; +} + +static void read_ext_prop(struct gatt_db_attribute *attrib, + void *user_data) +{ + uint16_t *ext_prop = user_data; + + /* + * If ext_prop is set that means extended properties descriptor + * has been already found + */ + if (*ext_prop != 0) + return; + + if (bt_uuid_cmp(&ext_desc_uuid, &attrib->uuid)) + return; + + gatt_db_attribute_read(attrib, 0, BT_ATT_OP_READ_REQ, NULL, + read_ext_prop_value, ext_prop); +} + +static uint8_t get_char_extended_prop(const struct gatt_db_attribute *attrib) +{ + uint16_t ext_prop; + + if (!attrib) + return 0; + + if (bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) + return 0; + + /* Check properties first */ + if (!(attrib->value[0] & BT_GATT_CHRC_PROP_EXT_PROP)) + return 0; + + ext_prop = 0; + + /* + * Cast needed for foreach function. We do not change attrib during + * this call + */ + gatt_db_service_foreach_desc((struct gatt_db_attribute *) attrib, + read_ext_prop, &ext_prop); + + return ext_prop; +} + +bool gatt_db_attribute_get_char_data(const struct gatt_db_attribute *attrib, + uint16_t *handle, + uint16_t *value_handle, + uint8_t *properties, + uint16_t *ext_prop, + bt_uuid_t *uuid) +{ + if (!attrib) + return false; + + if (bt_uuid_cmp(&characteristic_uuid, &attrib->uuid)) + return false; + + /* + * Characteristic declaration value: + * 1 octet: Characteristic properties + * 2 octets: Characteristic value handle + * 2 or 16 octets: characteristic UUID + */ + if (!attrib->value || (attrib->value_len != 5 && + attrib->value_len != 19)) + return false; + + if (handle) + *handle = attrib->handle; + + if (properties) + *properties = attrib->value[0]; + + if (ext_prop) + *ext_prop = get_char_extended_prop(attrib); + + if (value_handle) + *value_handle = get_le16(attrib->value + 1); + + if (!uuid) + return true; + + return le_to_uuid(attrib->value + 3, attrib->value_len - 3, uuid); +} + +bool gatt_db_attribute_get_incl_data(const struct gatt_db_attribute *attrib, + uint16_t *handle, + uint16_t *start_handle, + uint16_t *end_handle) +{ + if (!attrib) + return false; + + if (bt_uuid_cmp(&included_service_uuid, &attrib->uuid)) + return false; + + /* + * Include definition value: + * 2 octets: start handle of included service + * 2 octets: end handle of included service + * optional 2 octets: 16-bit Bluetooth UUID + */ + if (!attrib->value || attrib->value_len < 4 || attrib->value_len > 6) + return false; + + /* + * We only return the handles since the UUID can be easily obtained + * from the corresponding attribute. + */ + if (handle) + *handle = attrib->handle; + + if (start_handle) + *start_handle = get_le16(attrib->value); + + if (end_handle) + *end_handle = get_le16(attrib->value + 2); + + return true; +} + +uint32_t +gatt_db_attribute_get_permissions(const struct gatt_db_attribute *attrib) +{ + if (!attrib) + return 0; + + return attrib->permissions; +} + +static bool read_timeout(void *user_data) +{ + struct pending_read *p = user_data; + + p->timeout_id = 0; + + queue_remove(p->attrib->pending_reads, p); + + pending_read_result(p, -ETIMEDOUT, NULL, 0); + + return false; +} + +static uint8_t attribute_authorize(struct gatt_db_attribute *attrib, + uint8_t opcode, struct bt_att *att) +{ + struct gatt_db *db = attrib->service->db; + + if (!db->authorize) + return 0; + + return db->authorize(attrib, opcode, att, db->authorize_data); +} + +bool gatt_db_attribute_read(struct gatt_db_attribute *attrib, uint16_t offset, + uint8_t opcode, struct bt_att *att, + gatt_db_attribute_read_t func, void *user_data) +{ + uint8_t *value; + + if (!attrib || !func) + return false; + + if (attrib->read_func) { + struct pending_read *p; + uint8_t err; + + err = attribute_authorize(attrib, opcode, att); + if (err) { + func(attrib, err, NULL, 0, user_data); + return true; + } + + p = new0(struct pending_read, 1); + p->attrib = attrib; + p->id = ++attrib->read_id; + p->timeout_id = timeout_add(ATTRIBUTE_TIMEOUT, read_timeout, + p, NULL); + p->func = func; + p->user_data = user_data; + + queue_push_tail(attrib->pending_reads, p); + + attrib->read_func(attrib, p->id, offset, opcode, att, + attrib->user_data); + return true; + } + + /* Check boundary if value is stored in the db */ + if (offset > attrib->value_len) { + func(attrib, BT_ATT_ERROR_INVALID_OFFSET, NULL, 0, user_data); + return true; + } + + /* Guard against invalid access if offset equals to value length */ + value = offset == attrib->value_len ? NULL : &attrib->value[offset]; + + func(attrib, 0, value, attrib->value_len - offset, user_data); + + return true; +} + +static bool find_pending(const void *a, const void *b) +{ + const struct pending_read *p = a; + unsigned int id = PTR_TO_UINT(b); + + return p->id == id; +} + +bool gatt_db_attribute_read_result(struct gatt_db_attribute *attrib, + unsigned int id, int err, + const uint8_t *value, size_t length) +{ + struct pending_read *p; + + if (!attrib || !id) + return false; + + p = queue_remove_if(attrib->pending_reads, find_pending, + UINT_TO_PTR(id)); + if (!p) + return false; + + pending_read_result(p, err, value, length); + + return true; +} + +static bool write_timeout(void *user_data) +{ + struct pending_write *p = user_data; + + p->timeout_id = 0; + + queue_remove(p->attrib->pending_writes, p); + + pending_write_result(p, -ETIMEDOUT); + + return false; +} + +bool gatt_db_attribute_write(struct gatt_db_attribute *attrib, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + gatt_db_attribute_write_t func, + void *user_data) +{ + if (!attrib || !func) + return false; + + if (attrib->write_func) { + struct pending_write *p; + uint8_t err; + + err = attribute_authorize(attrib, opcode, att); + if (err) { + func(attrib, err, user_data); + return true; + } + + p = new0(struct pending_write, 1); + p->attrib = attrib; + p->id = ++attrib->write_id; + p->timeout_id = timeout_add(ATTRIBUTE_TIMEOUT, write_timeout, + p, NULL); + p->func = func; + p->user_data = user_data; + + queue_push_tail(attrib->pending_writes, p); + + attrib->write_func(attrib, p->id, offset, value, len, opcode, + att, attrib->user_data); + return true; + } + + /* Nothing to write just skip */ + if (len == 0) + goto done; + + /* For values stored in db allocate on demand */ + if (!attrib->value || offset >= attrib->value_len || + len > (unsigned) (attrib->value_len - offset)) { + void *buf; + + buf = realloc(attrib->value, len + offset); + if (!buf) + return false; + + attrib->value = buf; + + /* Init data in the first allocation */ + if (!attrib->value_len) + memset(attrib->value, 0, offset); + + attrib->value_len = len + offset; + } + + memcpy(&attrib->value[offset], value, len); + +done: + func(attrib, 0, user_data); + + return true; +} + +bool gatt_db_attribute_write_result(struct gatt_db_attribute *attrib, + unsigned int id, int err) +{ + struct pending_write *p; + + if (!attrib || !id) + return false; + + p = queue_remove_if(attrib->pending_writes, find_pending, + UINT_TO_PTR(id)); + if (!p) + return false; + + pending_write_result(p, err); + + return true; +} + +bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib) +{ + if (!attrib) + return false; + + if (!attrib->value || !attrib->value_len) + return true; + + free(attrib->value); + attrib->value = NULL; + attrib->value_len = 0; + + return true; +} + +void *gatt_db_attribute_get_user_data(struct gatt_db_attribute *attrib) +{ + if (!attrib) + return NULL; + + return attrib->user_data; +} diff --git a/src/shared/gatt-db.h b/src/shared/gatt-db.h new file mode 100644 index 0000000..08f0374 --- /dev/null +++ b/src/shared/gatt-db.h @@ -0,0 +1,282 @@ +/* + * + * 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 + * + */ + +struct gatt_db; +struct gatt_db_attribute; + +struct gatt_db *gatt_db_new(void); + +struct gatt_db *gatt_db_ref(struct gatt_db *db); +void gatt_db_unref(struct gatt_db *db); + +bool gatt_db_isempty(struct gatt_db *db); + +struct gatt_db_attribute *gatt_db_add_service(struct gatt_db *db, + const bt_uuid_t *uuid, + bool primary, + uint16_t num_handles); + +bool gatt_db_remove_service(struct gatt_db *db, + struct gatt_db_attribute *attrib); +bool gatt_db_clear(struct gatt_db *db); +bool gatt_db_clear_range(struct gatt_db *db, uint16_t start_handle, + uint16_t end_handle); +uint8_t *gatt_db_get_hash(struct gatt_db *db); + +struct gatt_db_attribute *gatt_db_insert_service(struct gatt_db *db, + uint16_t handle, + const bt_uuid_t *uuid, + bool primary, + uint16_t num_handles); + +typedef void (*gatt_db_read_t) (struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data); + +typedef void (*gatt_db_write_t) (struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data); + +struct gatt_db_attribute * +gatt_db_service_add_characteristic(struct gatt_db_attribute *attrib, + const bt_uuid_t *uuid, + uint32_t permissions, + uint8_t properties, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data); +struct gatt_db_attribute * +gatt_db_service_insert_characteristic(struct gatt_db_attribute *attrib, + uint16_t handle, + const bt_uuid_t *uuid, + uint32_t permissions, + uint8_t properties, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data); + +struct gatt_db_attribute * +gatt_db_insert_characteristic(struct gatt_db *db, + uint16_t handle, + const bt_uuid_t *uuid, + uint32_t permissions, + uint8_t properties, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data); + +struct gatt_db_attribute * +gatt_db_insert_descriptor(struct gatt_db *db, + uint16_t handle, + const bt_uuid_t *uuid, + uint32_t permissions, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data); + +struct gatt_db_attribute * +gatt_db_service_add_descriptor(struct gatt_db_attribute *attrib, + const bt_uuid_t *uuid, + uint32_t permissions, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data); +struct gatt_db_attribute * +gatt_db_service_insert_descriptor(struct gatt_db_attribute *attrib, + uint16_t handle, + const bt_uuid_t *uuid, + uint32_t permissions, + gatt_db_read_t read_func, + gatt_db_write_t write_func, + void *user_data); + +struct gatt_db_attribute * +gatt_db_insert_included(struct gatt_db *db, uint16_t handle, + struct gatt_db_attribute *include); + +struct gatt_db_attribute * +gatt_db_service_add_included(struct gatt_db_attribute *attrib, + struct gatt_db_attribute *include); +struct gatt_db_attribute * +gatt_db_service_insert_included(struct gatt_db_attribute *attrib, + uint16_t handle, + struct gatt_db_attribute *include); + +bool gatt_db_service_set_active(struct gatt_db_attribute *attrib, bool active); +bool gatt_db_service_get_active(struct gatt_db_attribute *attrib); + +bool gatt_db_service_set_claimed(struct gatt_db_attribute *attrib, + bool claimed); +bool gatt_db_service_get_claimed(struct gatt_db_attribute *attrib); + +typedef void (*gatt_db_attribute_cb_t)(struct gatt_db_attribute *attrib, + void *user_data); + +void gatt_db_read_by_group_type(struct gatt_db *db, uint16_t start_handle, + uint16_t end_handle, + const bt_uuid_t type, + struct queue *queue); + +unsigned int gatt_db_find_by_type(struct gatt_db *db, uint16_t start_handle, + uint16_t end_handle, + const bt_uuid_t *type, + gatt_db_attribute_cb_t func, + void *user_data); + +unsigned int gatt_db_find_by_type_value(struct gatt_db *db, + uint16_t start_handle, + uint16_t end_handle, + const bt_uuid_t *type, + const void *value, + size_t value_len, + gatt_db_attribute_cb_t func, + void *user_data); + +void gatt_db_read_by_type(struct gatt_db *db, uint16_t start_handle, + uint16_t end_handle, + const bt_uuid_t type, + struct queue *queue); + +void gatt_db_find_information(struct gatt_db *db, uint16_t start_handle, + uint16_t end_handle, + struct queue *queue); + + +void gatt_db_foreach_service(struct gatt_db *db, const bt_uuid_t *uuid, + gatt_db_attribute_cb_t func, + void *user_data); +void gatt_db_foreach_in_range(struct gatt_db *db, const bt_uuid_t *uuid, + gatt_db_attribute_cb_t func, + void *user_data, + uint16_t start_handle, + uint16_t end_handle); + +void gatt_db_foreach_service_in_range(struct gatt_db *db, + const bt_uuid_t *uuid, + gatt_db_attribute_cb_t func, + void *user_data, + uint16_t start_handle, + uint16_t end_handle); + +void gatt_db_service_foreach(struct gatt_db_attribute *attrib, + const bt_uuid_t *uuid, + gatt_db_attribute_cb_t func, + void *user_data); +void gatt_db_service_foreach_char(struct gatt_db_attribute *attrib, + gatt_db_attribute_cb_t func, + void *user_data); +void gatt_db_service_foreach_desc(struct gatt_db_attribute *attrib, + gatt_db_attribute_cb_t func, + void *user_data); +void gatt_db_service_foreach_incl(struct gatt_db_attribute *attrib, + gatt_db_attribute_cb_t func, + void *user_data); + +typedef void (*gatt_db_destroy_func_t)(void *user_data); + +unsigned int gatt_db_register(struct gatt_db *db, + gatt_db_attribute_cb_t service_added, + gatt_db_attribute_cb_t service_removed, + void *user_data, + gatt_db_destroy_func_t destroy); +bool gatt_db_unregister(struct gatt_db *db, unsigned int id); + +typedef uint8_t (*gatt_db_authorize_cb_t)(struct gatt_db_attribute *attrib, + uint8_t opcode, struct bt_att *att, + void *user_data); +bool gatt_db_set_authorize(struct gatt_db *db, gatt_db_authorize_cb_t cb, + void *user_data); + +struct gatt_db_attribute *gatt_db_get_service(struct gatt_db *db, + uint16_t handle); + +struct gatt_db_attribute *gatt_db_get_attribute(struct gatt_db *db, + uint16_t handle); + +struct gatt_db_attribute *gatt_db_get_service_with_uuid(struct gatt_db *db, + const bt_uuid_t *uuid); + +const bt_uuid_t *gatt_db_attribute_get_type( + const struct gatt_db_attribute *attrib); + +uint16_t gatt_db_attribute_get_handle(const struct gatt_db_attribute *attrib); + +bool gatt_db_attribute_get_service_uuid(const struct gatt_db_attribute *attrib, + bt_uuid_t *uuid); + +bool gatt_db_attribute_get_service_handles( + const struct gatt_db_attribute *attrib, + uint16_t *start_handle, + uint16_t *end_handle); + +bool gatt_db_attribute_get_service_data(const struct gatt_db_attribute *attrib, + uint16_t *start_handle, + uint16_t *end_handle, + bool *primary, + bt_uuid_t *uuid); + +bool gatt_db_attribute_get_char_data(const struct gatt_db_attribute *attrib, + uint16_t *handle, + uint16_t *value_handle, + uint8_t *properties, + uint16_t *ext_prop, + bt_uuid_t *uuid); + +bool gatt_db_attribute_get_incl_data(const struct gatt_db_attribute *attrib, + uint16_t *handle, + uint16_t *start_handle, + uint16_t *end_handle); + +uint32_t +gatt_db_attribute_get_permissions(const struct gatt_db_attribute *attrib); + +typedef void (*gatt_db_attribute_read_t) (struct gatt_db_attribute *attrib, + int err, const uint8_t *value, + size_t length, void *user_data); + +bool gatt_db_attribute_read(struct gatt_db_attribute *attrib, uint16_t offset, + uint8_t opcode, struct bt_att *att, + gatt_db_attribute_read_t func, void *user_data); + +bool gatt_db_attribute_read_result(struct gatt_db_attribute *attrib, + unsigned int id, int err, + const uint8_t *value, size_t length); + +typedef void (*gatt_db_attribute_write_t) (struct gatt_db_attribute *attrib, + int err, void *user_data); + +bool gatt_db_attribute_write(struct gatt_db_attribute *attrib, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + gatt_db_attribute_write_t func, + void *user_data); + +bool gatt_db_attribute_write_result(struct gatt_db_attribute *attrib, + unsigned int id, int err); + +bool gatt_db_attribute_reset(struct gatt_db_attribute *attrib); + +void *gatt_db_attribute_get_user_data(struct gatt_db_attribute *attrib); diff --git a/src/shared/gatt-helpers.c b/src/shared/gatt-helpers.c new file mode 100644 index 0000000..6b39bb1 --- /dev/null +++ b/src/shared/gatt-helpers.c @@ -0,0 +1,1514 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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 + * + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "src/shared/queue.h" +#include "src/shared/att.h" +#include "lib/bluetooth.h" +#include "lib/uuid.h" +#include "src/shared/gatt-helpers.h" +#include "src/shared/util.h" + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +struct bt_gatt_result { + uint8_t opcode; + void *pdu; + uint16_t pdu_len; + uint16_t data_len; + + void *op; /* Discovery operation data */ + + struct bt_gatt_result *next; +}; + +static struct bt_gatt_result *result_create(uint8_t opcode, const void *pdu, + uint16_t pdu_len, + uint16_t data_len, + void *op) +{ + struct bt_gatt_result *result; + + result = new0(struct bt_gatt_result, 1); + result->pdu = malloc(pdu_len); + if (!result->pdu) { + free(result); + return NULL; + } + + result->opcode = opcode; + result->pdu_len = pdu_len; + result->data_len = data_len; + result->op = op; + + memcpy(result->pdu, pdu, pdu_len); + + return result; +} + +static void result_destroy(struct bt_gatt_result *result) +{ + struct bt_gatt_result *next; + + while (result) { + next = result->next; + + free(result->pdu); + free(result); + + result = next; + } +} + +static unsigned int result_element_count(struct bt_gatt_result *result) +{ + unsigned int count = 0; + struct bt_gatt_result *cur; + + cur = result; + + while (cur) { + count += cur->pdu_len / cur->data_len; + cur = cur->next; + } + + return count; +} + +unsigned int bt_gatt_result_service_count(struct bt_gatt_result *result) +{ + if (!result) + return 0; + + if (result->opcode != BT_ATT_OP_READ_BY_GRP_TYPE_RSP && + result->opcode != BT_ATT_OP_FIND_BY_TYPE_RSP) + return 0; + + return result_element_count(result); +} + +unsigned int bt_gatt_result_characteristic_count(struct bt_gatt_result *result) +{ + if (!result) + return 0; + + if (result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) + return 0; + + /* + * Data length contains 7 or 21 octets: + * 2 octets: Attribute handle + * 1 octet: Characteristic properties + * 2 octets: Characteristic value handle + * 2 or 16 octets: characteristic UUID + */ + if (result->data_len != 21 && result->data_len != 7) + return 0; + + return result_element_count(result); +} + +unsigned int bt_gatt_result_descriptor_count(struct bt_gatt_result *result) +{ + if (!result) + return 0; + + if (result->opcode != BT_ATT_OP_FIND_INFO_RSP) + return 0; + + return result_element_count(result); +} + +unsigned int bt_gatt_result_included_count(struct bt_gatt_result *result) +{ + struct bt_gatt_result *cur; + unsigned int count = 0; + + if (!result) + return 0; + + if (result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) + return 0; + + /* + * Data length can be of length 6 or 8 octets: + * 2 octets - include service handle + * 2 octets - start handle of included service + * 2 octets - end handle of included service + * 2 octets (optionally) - 16 bit Bluetooth UUID + */ + if (result->data_len != 6 && result->data_len != 8) + return 0; + + for (cur = result; cur; cur = cur->next) + if (cur->opcode == BT_ATT_OP_READ_BY_TYPE_RSP) + count += cur->pdu_len / cur->data_len; + + return count; +} + +bool bt_gatt_iter_init(struct bt_gatt_iter *iter, struct bt_gatt_result *result) +{ + if (!iter || !result) + return false; + + iter->result = result; + iter->pos = 0; + + return true; +} + +static const uint8_t bt_base_uuid[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB +}; + +static bool convert_uuid_le(const uint8_t *src, size_t len, uint8_t dst[16]) +{ + if (len == 16) { + bswap_128(src, dst); + return true; + } + + if (len != 2) + return false; + + memcpy(dst, bt_base_uuid, sizeof(bt_base_uuid)); + dst[2] = src[1]; + dst[3] = src[0]; + + return true; +} + +struct bt_gatt_request { + struct bt_att *att; + unsigned int id; + uint16_t start_handle; + uint16_t end_handle; + int ref_count; + bt_uuid_t uuid; + uint16_t service_type; + struct bt_gatt_result *result_head; + struct bt_gatt_result *result_tail; + bt_gatt_request_callback_t callback; + void *user_data; + bt_gatt_destroy_func_t destroy; +}; + +static struct bt_gatt_result *result_append(uint8_t opcode, const void *pdu, + uint16_t pdu_len, + uint16_t data_len, + struct bt_gatt_request *op) +{ + struct bt_gatt_result *result; + + result = result_create(opcode, pdu, pdu_len, data_len, op); + if (!result) + return NULL; + + if (!op->result_head) + op->result_head = op->result_tail = result; + else { + op->result_tail->next = result; + op->result_tail = result; + } + + return result; +} + +bool bt_gatt_iter_next_included_service(struct bt_gatt_iter *iter, + uint16_t *handle, uint16_t *start_handle, + uint16_t *end_handle, uint8_t uuid[16]) +{ + struct bt_gatt_result *read_result; + struct bt_gatt_request *op; + const void *pdu_ptr; + int i = 0; + + if (!iter || !iter->result || !handle || !start_handle || !end_handle + || !uuid) + return false; + + + if (iter->result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) + return false; + + /* UUID in discovery_op is set in read_by_type and service_discovery */ + op = iter->result->op; + if (op->uuid.type != BT_UUID_UNSPEC) + return false; + /* + * iter->result points to READ_BY_TYPE_RSP with data length containing: + * 2 octets - include service handle + * 2 octets - start handle of included service + * 2 octets - end handle of included service + * optional 2 octets - Bluetooth UUID + */ + if (iter->result->data_len != 8 && iter->result->data_len != 6) + return false; + + pdu_ptr = iter->result->pdu + iter->pos; + + /* This result contains 16 bit UUID */ + if (iter->result->data_len == 8) { + *handle = get_le16(pdu_ptr); + *start_handle = get_le16(pdu_ptr + 2); + *end_handle = get_le16(pdu_ptr + 4); + convert_uuid_le(pdu_ptr + 6, 2, uuid); + + iter->pos += iter->result->data_len; + + if (iter->pos == iter->result->pdu_len) { + iter->result = iter->result->next; + iter->pos = 0; + } + + return true; + } + + *handle = get_le16(pdu_ptr); + *start_handle = get_le16(pdu_ptr + 2); + *end_handle = get_le16(pdu_ptr + 4); + read_result = iter->result; + + /* + * Find READ_RSP with include service UUID. + * If number of current data set in READ_BY_TYPE_RSP is n, then we must + * go to n'th PDU next to current item->result + */ + for (read_result = read_result->next; read_result; i++) { + if (i >= (iter->pos / iter->result->data_len)) + break; + + read_result = read_result->next; + } + + if (!read_result) + return false; + + convert_uuid_le(read_result->pdu, read_result->data_len, uuid); + iter->pos += iter->result->data_len; + if (iter->pos == iter->result->pdu_len) { + iter->result = read_result->next; + iter->pos = 0; + } + + return true; +} + +bool bt_gatt_iter_next_service(struct bt_gatt_iter *iter, + uint16_t *start_handle, uint16_t *end_handle, + uint8_t uuid[16]) +{ + struct bt_gatt_request *op; + const void *pdu_ptr; + bt_uuid_t tmp; + + if (!iter || !iter->result || !start_handle || !end_handle || !uuid) + return false; + + op = iter->result->op; + pdu_ptr = iter->result->pdu + iter->pos; + + switch (iter->result->opcode) { + case BT_ATT_OP_READ_BY_GRP_TYPE_RSP: + *start_handle = get_le16(pdu_ptr); + *end_handle = get_le16(pdu_ptr + 2); + convert_uuid_le(pdu_ptr + 4, iter->result->data_len - 4, uuid); + break; + case BT_ATT_OP_FIND_BY_TYPE_RSP: + *start_handle = get_le16(pdu_ptr); + *end_handle = get_le16(pdu_ptr + 2); + + bt_uuid_to_uuid128(&op->uuid, &tmp); + memcpy(uuid, tmp.value.u128.data, 16); + break; + default: + return false; + } + + + iter->pos += iter->result->data_len; + if (iter->pos == iter->result->pdu_len) { + iter->result = iter->result->next; + iter->pos = 0; + } + + return true; +} + +bool bt_gatt_iter_next_characteristic(struct bt_gatt_iter *iter, + uint16_t *start_handle, uint16_t *end_handle, + uint16_t *value_handle, uint8_t *properties, + uint8_t uuid[16]) +{ + struct bt_gatt_request *op; + const void *pdu_ptr; + + if (!iter || !iter->result || !start_handle || !end_handle || + !value_handle || !properties || !uuid) + return false; + + if (iter->result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) + return false; + + /* UUID in discovery_op is set in read_by_type and service_discovery */ + op = iter->result->op; + if (op->uuid.type != BT_UUID_UNSPEC) + return false; + /* + * Data length contains 7 or 21 octets: + * 2 octets: Attribute handle + * 1 octet: Characteristic properties + * 2 octets: Characteristic value handle + * 2 or 16 octets: characteristic UUID + */ + if (iter->result->data_len != 21 && iter->result->data_len != 7) + return false; + + pdu_ptr = iter->result->pdu + iter->pos; + + *start_handle = get_le16(pdu_ptr); + *properties = ((uint8_t *) pdu_ptr)[2]; + *value_handle = get_le16(pdu_ptr + 3); + convert_uuid_le(pdu_ptr + 5, iter->result->data_len - 5, uuid); + + iter->pos += iter->result->data_len; + if (iter->pos == iter->result->pdu_len) { + iter->result = iter->result->next; + iter->pos = 0; + } + + if (!iter->result) { + *end_handle = op->end_handle; + return true; + } + + *end_handle = get_le16(iter->result->pdu + iter->pos) - 1; + + return true; +} + +bool bt_gatt_iter_next_descriptor(struct bt_gatt_iter *iter, uint16_t *handle, + uint8_t uuid[16]) +{ + const void *pdu_ptr; + + if (!iter || !iter->result || !handle || !uuid) + return false; + + if (iter->result->opcode != BT_ATT_OP_FIND_INFO_RSP) + return false; + + pdu_ptr = iter->result->pdu + iter->pos; + + *handle = get_le16(pdu_ptr); + convert_uuid_le(pdu_ptr + 2, iter->result->data_len - 2, uuid); + + iter->pos += iter->result->data_len; + if (iter->pos == iter->result->pdu_len) { + iter->result = iter->result->next; + iter->pos = 0; + } + + return true; +} + +bool bt_gatt_iter_next_read_by_type(struct bt_gatt_iter *iter, + uint16_t *handle, uint16_t *length, + const uint8_t **value) +{ + struct bt_gatt_request *op; + const void *pdu_ptr; + + if (!iter || !iter->result || !handle || !length || !value) + return false; + + if (iter->result->opcode != BT_ATT_OP_READ_BY_TYPE_RSP) + return false; + + /* + * Check if UUID is set, otherwise results can contain characteristic + * discovery service or included service discovery results + */ + op = iter->result->op; + if (op->uuid.type == BT_UUID_UNSPEC) + return false; + + pdu_ptr = iter->result->pdu + iter->pos; + + *handle = get_le16(pdu_ptr); + *length = iter->result->data_len - 2; + *value = pdu_ptr + 2; + + iter->pos += iter->result->data_len; + if (iter->pos == iter->result->pdu_len) { + iter->result = iter->result->next; + iter->pos = 0; + } + + return true; +} + +struct mtu_op { + struct bt_att *att; + uint16_t client_rx_mtu; + bt_gatt_result_callback_t callback; + void *user_data; + bt_gatt_destroy_func_t destroy; +}; + +static void destroy_mtu_op(void *user_data) +{ + struct mtu_op *op = user_data; + + if (op->destroy) + op->destroy(op->user_data); + + free(op); +} + +static uint8_t process_error(const void *pdu, uint16_t length) +{ + const struct bt_att_pdu_error_rsp *error_pdu; + + if (!pdu || length != sizeof(struct bt_att_pdu_error_rsp)) + return 0; + + error_pdu = pdu; + + return error_pdu->ecode; +} + +static void mtu_cb(uint8_t opcode, const void *pdu, uint16_t length, + void *user_data) +{ + struct mtu_op *op = user_data; + bool success = true; + uint8_t att_ecode = 0; + uint16_t server_rx_mtu; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + if (opcode != BT_ATT_OP_MTU_RSP || !pdu || length != 2) { + success = false; + goto done; + } + + server_rx_mtu = get_le16(pdu); + bt_att_set_mtu(op->att, MIN(op->client_rx_mtu, server_rx_mtu)); + +done: + if (op->callback) + op->callback(success, att_ecode, op->user_data); +} + +unsigned int bt_gatt_exchange_mtu(struct bt_att *att, uint16_t client_rx_mtu, + bt_gatt_result_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy) +{ + struct mtu_op *op; + uint8_t pdu[2]; + unsigned int id; + + if (!att || !client_rx_mtu) + return false; + + op = new0(struct mtu_op, 1); + op->att = att; + op->client_rx_mtu = client_rx_mtu; + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + + put_le16(client_rx_mtu, pdu); + + id = bt_att_send(att, BT_ATT_OP_MTU_REQ, pdu, sizeof(pdu), mtu_cb, op, + destroy_mtu_op); + if (!id) + free(op); + + return id; +} + +static inline int get_uuid_len(const bt_uuid_t *uuid) +{ + if (!uuid) + return 0; + + return (uuid->type == BT_UUID16) ? 2 : 16; +} + +struct bt_gatt_request *bt_gatt_request_ref(struct bt_gatt_request *req) +{ + if (!req) + return NULL; + + __sync_fetch_and_add(&req->ref_count, 1); + + return req; +} + +void bt_gatt_request_unref(struct bt_gatt_request *req) +{ + if (!req) + return; + + if (__sync_sub_and_fetch(&req->ref_count, 1)) + return; + + bt_gatt_request_cancel(req); + + if (req->destroy) + req->destroy(req->user_data); + + result_destroy(req->result_head); + + free(req); +} + +void bt_gatt_request_cancel(struct bt_gatt_request *req) +{ + if (!req) + return; + + if (!req->id) + return; + + bt_att_cancel(req->att, req->id); + req->id = 0; +} + +static void async_req_unref(void *data) +{ + struct bt_gatt_request *req = data; + + bt_gatt_request_unref(req); +} + +static void discovery_op_complete(struct bt_gatt_request *op, bool success, + uint8_t ecode) +{ + /* Reset success if there is some result to report */ + if (ecode == BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND && op->result_head) + success = true; + + if (op->callback) + op->callback(success, ecode, success ? op->result_head : NULL, + op->user_data); + + if (!op->id) + async_req_unref(op); + else + op->id = 0; + +} + +static void read_by_grp_type_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_request *op = user_data; + bool success; + uint8_t att_ecode = 0; + struct bt_gatt_result *cur_result; + size_t data_length; + size_t list_length; + uint16_t last_end; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + /* PDU must contain at least the following (sans opcode): + * - Attr Data Length (1 octet) + * - Attr Data List (at least 6 octets): + * -- 2 octets: Attribute handle + * -- 2 octets: End group handle + * -- 2 or 16 octets: service UUID + */ + if (opcode != BT_ATT_OP_READ_BY_GRP_TYPE_RSP || !pdu || length < 7) { + success = false; + goto done; + } + + data_length = ((uint8_t *) pdu)[0]; + list_length = length - 1; + + if ((data_length != 6 && data_length != 20) || + (list_length % data_length)) { + success = false; + goto done; + } + + /* PDU is correctly formatted. Get the last end handle to process the + * next request and store the PDU. + */ + cur_result = result_append(opcode, pdu + 1, list_length, data_length, + op); + if (!cur_result) { + success = false; + goto done; + } + + last_end = get_le16(pdu + length - data_length + 2); + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (last_end < op->start_handle) { + success = false; + goto done; + } + + op->start_handle = last_end + 1; + + if (last_end < op->end_handle) { + uint8_t pdu[6]; + + put_le16(op->start_handle, pdu); + put_le16(op->end_handle, pdu + 2); + put_le16(op->service_type, pdu + 4); + + op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_GRP_TYPE_REQ, + pdu, sizeof(pdu), + read_by_grp_type_cb, + bt_gatt_request_ref(op), + async_req_unref); + if (op->id) + return; + + success = false; + goto done; + } + + /* Some devices incorrectly return 0xffff as the end group handle when + * the read-by-group-type request is performed within a smaller range. + * Manually set the end group handle that we report in the result to the + * end handle in the original request. + */ + if (last_end == 0xffff && last_end != op->end_handle) + put_le16(op->end_handle, + cur_result->pdu + length - data_length + 1); + + success = true; + +done: + discovery_op_complete(op, success, att_ecode); +} + +static void find_by_type_val_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_request *op = user_data; + bool success; + uint8_t att_ecode = 0; + uint16_t last_end; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + /* PDU must contain 4 bytes and it must be a multiple of 4, where each + * 4 bytes contain the 16-bit attribute and group end handles. + */ + if (opcode != BT_ATT_OP_FIND_BY_TYPE_RSP || !pdu || !length || + length % 4) { + success = false; + goto done; + } + + if (!result_append(opcode, pdu, length, 4, op)) { + success = false; + goto done; + } + + /* + * Each data set contains: + * 2 octets with start handle + * 2 octets with end handle + * last_end is end handle of last data set + */ + last_end = get_le16(pdu + length - 2); + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (last_end < op->start_handle) { + success = false; + goto done; + } + + op->start_handle = last_end + 1; + + if (last_end < op->end_handle) { + uint8_t pdu[6 + get_uuid_len(&op->uuid)]; + + put_le16(op->start_handle, pdu); + put_le16(op->end_handle, pdu + 2); + put_le16(op->service_type, pdu + 4); + bt_uuid_to_le(&op->uuid, pdu + 6); + + op->id = bt_att_send(op->att, BT_ATT_OP_FIND_BY_TYPE_REQ, + pdu, sizeof(pdu), + find_by_type_val_cb, + bt_gatt_request_ref(op), + async_req_unref); + if (op->id) + return; + + success = false; + goto done; + } + + success = true; + +done: + discovery_op_complete(op, success, att_ecode); +} + +static struct bt_gatt_request *discover_services(struct bt_att *att, + bt_uuid_t *uuid, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy, + bool primary) +{ + struct bt_gatt_request *op; + + if (!att) + return NULL; + + op = new0(struct bt_gatt_request, 1); + op->att = att; + op->start_handle = start; + op->end_handle = end; + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + /* set service uuid to primary or secondary */ + op->service_type = primary ? GATT_PRIM_SVC_UUID : GATT_SND_SVC_UUID; + + /* If UUID is NULL, then discover all primary services */ + if (!uuid) { + uint8_t pdu[6]; + + put_le16(start, pdu); + put_le16(end, pdu + 2); + put_le16(op->service_type, pdu + 4); + + op->id = bt_att_send(att, BT_ATT_OP_READ_BY_GRP_TYPE_REQ, + pdu, sizeof(pdu), + read_by_grp_type_cb, + bt_gatt_request_ref(op), + async_req_unref); + } else { + uint8_t pdu[6 + get_uuid_len(uuid)]; + + if (uuid->type == BT_UUID_UNSPEC) { + free(op); + return NULL; + } + + /* Discover by UUID */ + op->uuid = *uuid; + + put_le16(start, pdu); + put_le16(end, pdu + 2); + put_le16(op->service_type, pdu + 4); + bt_uuid_to_le(&op->uuid, pdu + 6); + + op->id = bt_att_send(att, BT_ATT_OP_FIND_BY_TYPE_REQ, + pdu, sizeof(pdu), + find_by_type_val_cb, + bt_gatt_request_ref(op), + async_req_unref); + } + + if (!op->id) { + free(op); + return NULL; + } + + return bt_gatt_request_ref(op); +} + +struct bt_gatt_request *bt_gatt_discover_all_primary_services( + struct bt_att *att, bt_uuid_t *uuid, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy) +{ + return bt_gatt_discover_primary_services(att, uuid, 0x0001, 0xffff, + callback, user_data, + destroy); +} + +struct bt_gatt_request *bt_gatt_discover_primary_services( + struct bt_att *att, bt_uuid_t *uuid, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy) +{ + return discover_services(att, uuid, start, end, callback, user_data, + destroy, true); +} + +struct bt_gatt_request *bt_gatt_discover_secondary_services( + struct bt_att *att, bt_uuid_t *uuid, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy) +{ + return discover_services(att, uuid, start, end, callback, user_data, + destroy, false); +} + +struct read_incl_data { + struct bt_gatt_request *op; + struct bt_gatt_result *result; + int pos; + int ref_count; +}; + +static struct read_incl_data *new_read_included(struct bt_gatt_result *res) +{ + struct read_incl_data *data; + + data = new0(struct read_incl_data, 1); + data->op = bt_gatt_request_ref(res->op); + data->result = res; + + return data; +}; + +static struct read_incl_data *read_included_ref(struct read_incl_data *data) +{ + __sync_fetch_and_add(&data->ref_count, 1); + + return data; +} + +static void read_included_unref(void *data) +{ + struct read_incl_data *read_data = data; + + if (__sync_sub_and_fetch(&read_data->ref_count, 1)) + return; + + async_req_unref(read_data->op); + + free(read_data); +} + +static void discover_included_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data); + +static void read_included_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct read_incl_data *data = user_data; + struct bt_gatt_request *op = data->op; + uint8_t att_ecode = 0; + uint8_t read_pdu[2]; + bool success; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + if (opcode != BT_ATT_OP_READ_RSP || (!pdu && length)) { + success = false; + goto done; + } + + /* + * UUID should be in 128 bit format, as it couldn't be read in + * READ_BY_TYPE request + */ + if (length != 16) { + success = false; + goto done; + } + + if (!result_append(opcode, pdu, length, length, op)) { + success = false; + goto done; + } + + if (data->pos == data->result->pdu_len) { + uint16_t last_handle; + uint8_t pdu[6]; + + last_handle = get_le16(data->result->pdu + data->pos - + data->result->data_len); + if (last_handle == op->end_handle) { + success = true; + goto done; + } + + put_le16(last_handle + 1, pdu); + put_le16(op->end_handle, pdu + 2); + put_le16(GATT_INCLUDE_UUID, pdu + 4); + + op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, + pdu, sizeof(pdu), + discover_included_cb, + bt_gatt_request_ref(op), + async_req_unref); + if (op->id) + return; + + success = false; + goto done; + } + + memcpy(read_pdu, data->result->pdu + data->pos + 2, sizeof(uint16_t)); + + data->pos += data->result->data_len; + + if (bt_att_send(op->att, BT_ATT_OP_READ_REQ, read_pdu, sizeof(read_pdu), + read_included_cb, read_included_ref(data), + read_included_unref)) + return; + + read_included_unref(data); + success = false; + +done: + discovery_op_complete(op, success, att_ecode); +} + +static void read_included(struct read_incl_data *data) +{ + struct bt_gatt_request *op = data->op; + uint8_t pdu[2]; + + memcpy(pdu, data->result->pdu + 2, sizeof(uint16_t)); + + data->pos += data->result->data_len; + + if (bt_att_send(op->att, BT_ATT_OP_READ_REQ, pdu, sizeof(pdu), + read_included_cb, + read_included_ref(data), + read_included_unref)) + return; + + if (op->callback) + op->callback(false, 0, NULL, data->op->user_data); + + read_included_unref(data); +} + +static void discover_included_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_request *op = user_data; + struct bt_gatt_result *cur_result; + uint8_t att_ecode = 0; + uint16_t last_handle; + size_t data_length; + bool success; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + att_ecode = process_error(pdu, length); + success = false; + goto failed; + } + + if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || !pdu || length < 6) { + success = false; + goto failed; + } + + data_length = ((const uint8_t *) pdu)[0]; + + /* + * Check if PDU contains data sets with length declared in the beginning + * of frame and if this length is correct. + * Data set length may be 6 or 8 octets: + * 2 octets - include service handle + * 2 octets - start handle of included service + * 2 octets - end handle of included service + * optional 2 octets - Bluetooth UUID of included service + */ + if ((data_length != 8 && data_length != 6) || + (length - 1) % data_length) { + success = false; + goto failed; + } + + cur_result = result_append(opcode, pdu + 1, length - 1, data_length, + op); + if (!cur_result) { + success = false; + goto failed; + } + + if (data_length == 6) { + struct read_incl_data *data; + + data = new_read_included(cur_result); + if (!data) { + success = false; + goto failed; + } + + read_included(data); + return; + } + + last_handle = get_le16(pdu + length - data_length); + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (last_handle < op->start_handle) { + success = false; + goto failed; + } + + op->start_handle = last_handle + 1; + if (last_handle != op->end_handle) { + uint8_t pdu[6]; + + put_le16(op->start_handle, pdu); + put_le16(op->end_handle, pdu + 2); + put_le16(GATT_INCLUDE_UUID, pdu + 4); + + op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, + pdu, sizeof(pdu), + discover_included_cb, + bt_gatt_request_ref(op), + async_req_unref); + if (op->id) + return; + + success = false; + goto failed; + } + + success = true; + +failed: + discovery_op_complete(op, success, att_ecode); +} + +struct bt_gatt_request *bt_gatt_discover_included_services(struct bt_att *att, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy) +{ + struct bt_gatt_request *op; + uint8_t pdu[6]; + + if (!att) + return false; + + op = new0(struct bt_gatt_request, 1); + op->att = att; + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + op->start_handle = start; + op->end_handle = end; + + put_le16(start, pdu); + put_le16(end, pdu + 2); + put_le16(GATT_INCLUDE_UUID, pdu + 4); + + op->id = bt_att_send(att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), + discover_included_cb, bt_gatt_request_ref(op), + async_req_unref); + if (!op->id) { + free(op); + return NULL; + } + + return bt_gatt_request_ref(op); +} + +static void discover_chrcs_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_request *op = user_data; + bool success; + uint8_t att_ecode = 0; + size_t data_length; + uint16_t last_handle; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + /* PDU must contain at least the following (sans opcode): + * - Attr Data Length (1 octet) + * - Attr Data List (at least 7 octets): + * -- 2 octets: Attribute handle + * -- 1 octet: Characteristic properties + * -- 2 octets: Characteristic value handle + * -- 2 or 16 octets: characteristic UUID + */ + if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || !pdu || length < 8) { + success = false; + goto done; + } + + data_length = ((uint8_t *) pdu)[0]; + + if ((data_length != 7 && data_length != 21) || + ((length - 1) % data_length)) { + success = false; + goto done; + } + + if (!result_append(opcode, pdu + 1, length - 1, + data_length, op)) { + success = false; + goto done; + } + last_handle = get_le16(pdu + length - data_length); + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (last_handle < op->start_handle) { + success = false; + goto done; + } + + op->start_handle = last_handle + 1; + + if (last_handle != op->end_handle) { + uint8_t pdu[6]; + + put_le16(op->start_handle, pdu); + put_le16(op->end_handle, pdu + 2); + put_le16(GATT_CHARAC_UUID, pdu + 4); + + op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, + pdu, sizeof(pdu), + discover_chrcs_cb, + bt_gatt_request_ref(op), + async_req_unref); + if (op->id) + return; + + success = false; + goto done; + } + + success = true; + +done: + discovery_op_complete(op, success, att_ecode); +} + +struct bt_gatt_request *bt_gatt_discover_characteristics(struct bt_att *att, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy) +{ + struct bt_gatt_request *op; + uint8_t pdu[6]; + + if (!att) + return false; + + op = new0(struct bt_gatt_request, 1); + op->att = att; + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + op->start_handle = start; + op->end_handle = end; + + put_le16(start, pdu); + put_le16(end, pdu + 2); + put_le16(GATT_CHARAC_UUID, pdu + 4); + + op->id = bt_att_send(att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), + discover_chrcs_cb, bt_gatt_request_ref(op), + async_req_unref); + if (!op->id) { + free(op); + return NULL; + } + + return bt_gatt_request_ref(op); +} + +static void read_by_type_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_request *op = user_data; + bool success; + uint8_t att_ecode = 0; + size_t data_length; + uint16_t last_handle; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + att_ecode = process_error(pdu, length); + success = false; + goto done; + } + + if (opcode != BT_ATT_OP_READ_BY_TYPE_RSP || !pdu) { + success = false; + att_ecode = 0; + goto done; + } + + data_length = ((uint8_t *) pdu)[0]; + if (((length - 1) % data_length)) { + success = false; + att_ecode = 0; + goto done; + } + + if (!result_append(opcode, pdu + 1, length - 1, data_length, op)) { + success = false; + att_ecode = 0; + goto done; + } + + last_handle = get_le16(pdu + length - data_length); + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (last_handle < op->start_handle) { + success = false; + goto done; + } + + op->start_handle = last_handle + 1; + + if (last_handle != op->end_handle) { + uint8_t pdu[4 + get_uuid_len(&op->uuid)]; + + put_le16(op->start_handle, pdu); + put_le16(op->end_handle, pdu + 2); + bt_uuid_to_le(&op->uuid, pdu + 4); + + op->id = bt_att_send(op->att, BT_ATT_OP_READ_BY_TYPE_REQ, + pdu, sizeof(pdu), + read_by_type_cb, + bt_gatt_request_ref(op), + async_req_unref); + if (op->id) + return; + + success = false; + goto done; + } + + success = true; + +done: + discovery_op_complete(op, success, att_ecode); +} + +bool bt_gatt_read_by_type(struct bt_att *att, uint16_t start, uint16_t end, + const bt_uuid_t *uuid, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy) +{ + struct bt_gatt_request *op; + uint8_t pdu[4 + get_uuid_len(uuid)]; + + if (!att || !uuid || uuid->type == BT_UUID_UNSPEC) + return false; + + op = new0(struct bt_gatt_request, 1); + op->att = att; + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + op->start_handle = start; + op->end_handle = end; + op->uuid = *uuid; + + put_le16(start, pdu); + put_le16(end, pdu + 2); + bt_uuid_to_le(uuid, pdu + 4); + + op->id = bt_att_send(att, BT_ATT_OP_READ_BY_TYPE_REQ, pdu, sizeof(pdu), + read_by_type_cb, + bt_gatt_request_ref(op), + async_req_unref); + if (op->id) + return true; + + free(op); + return false; +} + +static void discover_descs_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_request *op = user_data; + bool success; + uint8_t att_ecode = 0; + uint8_t format; + uint16_t last_handle; + size_t data_length; + + if (opcode == BT_ATT_OP_ERROR_RSP) { + success = false; + att_ecode = process_error(pdu, length); + goto done; + } + + /* The PDU should contain the following data (sans opcode): + * - Format (1 octet) + * - Attr Data List (at least 4 octets): + * -- 2 octets: Attribute handle + * -- 2 or 16 octets: UUID. + */ + if (opcode != BT_ATT_OP_FIND_INFO_RSP || !pdu || length < 5) { + success = false; + goto done; + } + + format = ((uint8_t *) pdu)[0]; + + if (format == 0x01) + data_length = 4; + else if (format == 0x02) + data_length = 18; + else { + success = false; + goto done; + } + + if ((length - 1) % data_length) { + success = false; + goto done; + } + + if (!result_append(opcode, pdu + 1, length - 1, data_length, op)) { + success = false; + goto done; + } + + last_handle = get_le16(pdu + length - data_length); + + /* + * If last handle is lower from previous start handle then it is smth + * wrong. Let's stop search, otherwise we might enter infinite loop. + */ + if (last_handle < op->start_handle) { + success = false; + goto done; + } + + op->start_handle = last_handle + 1; + + if (last_handle != op->end_handle) { + uint8_t pdu[4]; + + put_le16(op->start_handle, pdu); + put_le16(op->end_handle, pdu + 2); + + op->id = bt_att_send(op->att, BT_ATT_OP_FIND_INFO_REQ, + pdu, sizeof(pdu), + discover_descs_cb, + bt_gatt_request_ref(op), + async_req_unref); + if (op->id) + return; + + success = false; + goto done; + } + + success = true; + +done: + discovery_op_complete(op, success, att_ecode); +} + +struct bt_gatt_request *bt_gatt_discover_descriptors(struct bt_att *att, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy) +{ + struct bt_gatt_request *op; + uint8_t pdu[4]; + + if (!att) + return false; + + op = new0(struct bt_gatt_request, 1); + op->att = att; + op->callback = callback; + op->user_data = user_data; + op->destroy = destroy; + op->start_handle = start; + op->end_handle = end; + + put_le16(start, pdu); + put_le16(end, pdu + 2); + + op->id = bt_att_send(att, BT_ATT_OP_FIND_INFO_REQ, pdu, sizeof(pdu), + discover_descs_cb, + bt_gatt_request_ref(op), + async_req_unref); + if (!op->id) { + free(op); + return NULL; + } + + return bt_gatt_request_ref(op); +} diff --git a/src/shared/gatt-helpers.h b/src/shared/gatt-helpers.h new file mode 100644 index 0000000..dd9dd1c --- /dev/null +++ b/src/shared/gatt-helpers.h @@ -0,0 +1,116 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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 + * + */ + +/* This file defines helpers for performing client-side procedures defined by + * the Generic Attribute Profile. + */ + +#include +#include + +struct bt_gatt_result; + +struct bt_gatt_iter { + struct bt_gatt_result *result; + uint16_t pos; +}; + +unsigned int bt_gatt_result_service_count(struct bt_gatt_result *result); +unsigned int bt_gatt_result_characteristic_count(struct bt_gatt_result *result); +unsigned int bt_gatt_result_descriptor_count(struct bt_gatt_result *result); +unsigned int bt_gatt_result_included_count(struct bt_gatt_result *result); + +bool bt_gatt_iter_init(struct bt_gatt_iter *iter, struct bt_gatt_result *result); +bool bt_gatt_iter_next_service(struct bt_gatt_iter *iter, + uint16_t *start_handle, uint16_t *end_handle, + uint8_t uuid[16]); +bool bt_gatt_iter_next_characteristic(struct bt_gatt_iter *iter, + uint16_t *start_handle, uint16_t *end_handle, + uint16_t *value_handle, uint8_t *properties, + uint8_t uuid[16]); +bool bt_gatt_iter_next_descriptor(struct bt_gatt_iter *iter, uint16_t *handle, + uint8_t uuid[16]); +bool bt_gatt_iter_next_included_service(struct bt_gatt_iter *iter, + uint16_t *handle, uint16_t *start_handle, + uint16_t *end_handle, uint8_t uuid[16]); +bool bt_gatt_iter_next_read_by_type(struct bt_gatt_iter *iter, + uint16_t *handle, uint16_t *length, + const uint8_t **value); + +typedef void (*bt_gatt_destroy_func_t)(void *user_data); + +typedef void (*bt_gatt_result_callback_t)(bool success, uint8_t att_ecode, + void *user_data); +typedef void (*bt_gatt_request_callback_t)(bool success, uint8_t att_ecode, + struct bt_gatt_result *result, + void *user_data); + +struct bt_gatt_request; + +struct bt_gatt_request *bt_gatt_request_ref(struct bt_gatt_request *req); +void bt_gatt_request_unref(struct bt_gatt_request *req); +void bt_gatt_request_cancel(struct bt_gatt_request *req); + +unsigned int bt_gatt_exchange_mtu(struct bt_att *att, uint16_t client_rx_mtu, + bt_gatt_result_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy); + +struct bt_gatt_request *bt_gatt_discover_all_primary_services( + struct bt_att *att, bt_uuid_t *uuid, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy); +struct bt_gatt_request *bt_gatt_discover_primary_services( + struct bt_att *att, bt_uuid_t *uuid, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy); +struct bt_gatt_request *bt_gatt_discover_secondary_services( + struct bt_att *att, bt_uuid_t *uuid, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy); +struct bt_gatt_request *bt_gatt_discover_included_services(struct bt_att *att, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy); +struct bt_gatt_request *bt_gatt_discover_characteristics(struct bt_att *att, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy); +struct bt_gatt_request *bt_gatt_discover_descriptors(struct bt_att *att, + uint16_t start, uint16_t end, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy); + +bool bt_gatt_read_by_type(struct bt_att *att, uint16_t start, uint16_t end, + const bt_uuid_t *uuid, + bt_gatt_request_callback_t callback, + void *user_data, + bt_gatt_destroy_func_t destroy); diff --git a/src/shared/gatt-server.c b/src/shared/gatt-server.c new file mode 100644 index 0000000..0d9bb07 --- /dev/null +++ b/src/shared/gatt-server.c @@ -0,0 +1,1761 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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 + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "src/shared/att.h" +#include "lib/bluetooth.h" +#include "lib/uuid.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-server.h" +#include "src/shared/gatt-helpers.h" +#include "src/shared/util.h" + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +/* + * TODO: This is an arbitrary limit. Come up with something reasonable or + * perhaps an API to set this value if there is a use case for it. + */ +#define DEFAULT_MAX_PREP_QUEUE_LEN 30 + +struct async_read_op { + struct bt_gatt_server *server; + uint8_t opcode; + bool done; + uint8_t *pdu; + size_t pdu_len; + size_t value_len; + struct queue *db_data; +}; + +struct async_write_op { + struct bt_gatt_server *server; + uint8_t opcode; +}; + +struct prep_write_data { + struct bt_gatt_server *server; + uint8_t *value; + uint16_t handle; + uint16_t offset; + uint16_t length; + + bool reliable_supported; +}; + +static void prep_write_data_destroy(void *user_data) +{ + struct prep_write_data *data = user_data; + + free(data->value); + free(data); +} + +struct bt_gatt_server { + struct gatt_db *db; + struct bt_att *att; + int ref_count; + uint16_t mtu; + + unsigned int mtu_id; + unsigned int read_by_grp_type_id; + unsigned int read_by_type_id; + unsigned int find_info_id; + unsigned int find_by_type_value_id; + unsigned int write_id; + unsigned int write_cmd_id; + unsigned int read_id; + unsigned int read_blob_id; + unsigned int read_multiple_id; + unsigned int prep_write_id; + unsigned int exec_write_id; + + uint8_t min_enc_size; + + struct queue *prep_queue; + unsigned int max_prep_queue_len; + + struct async_read_op *pending_read_op; + struct async_write_op *pending_write_op; + + bt_gatt_server_debug_func_t debug_callback; + bt_gatt_server_destroy_func_t debug_destroy; + void *debug_data; + + bt_gatt_server_authorize_cb_t authorize; + void *authorize_data; +}; + +static void bt_gatt_server_free(struct bt_gatt_server *server) +{ + if (server->debug_destroy) + server->debug_destroy(server->debug_data); + + bt_att_unregister(server->att, server->mtu_id); + bt_att_unregister(server->att, server->read_by_grp_type_id); + bt_att_unregister(server->att, server->read_by_type_id); + bt_att_unregister(server->att, server->find_info_id); + bt_att_unregister(server->att, server->find_by_type_value_id); + bt_att_unregister(server->att, server->write_id); + bt_att_unregister(server->att, server->write_cmd_id); + bt_att_unregister(server->att, server->read_id); + bt_att_unregister(server->att, server->read_blob_id); + bt_att_unregister(server->att, server->read_multiple_id); + bt_att_unregister(server->att, server->prep_write_id); + bt_att_unregister(server->att, server->exec_write_id); + + if (server->pending_read_op) + server->pending_read_op->server = NULL; + + if (server->pending_write_op) + server->pending_write_op->server = NULL; + + queue_destroy(server->prep_queue, prep_write_data_destroy); + + gatt_db_unref(server->db); + bt_att_unref(server->att); + free(server); +} + +static bool get_uuid_le(const uint8_t *uuid, size_t len, bt_uuid_t *out_uuid) +{ + uint128_t u128; + + switch (len) { + case 2: + bt_uuid16_create(out_uuid, get_le16(uuid)); + return true; + case 16: + bswap_128(uuid, &u128.data); + bt_uuid128_create(out_uuid, u128); + return true; + default: + return false; + } + + return false; +} + +static void attribute_read_cb(struct gatt_db_attribute *attrib, int err, + const uint8_t *value, size_t length, + void *user_data) +{ + struct iovec *iov = user_data; + + iov->iov_base = (void *) value; + iov->iov_len = length; +} + +static bool encode_read_by_grp_type_rsp(struct gatt_db *db, struct queue *q, + struct bt_att *att, + uint16_t mtu, uint8_t *pdu, + uint16_t *len) +{ + int iter = 0; + uint16_t start_handle, end_handle; + struct iovec value; + uint8_t data_val_len; + + *len = 0; + + while (queue_peek_head(q)) { + struct gatt_db_attribute *attrib = queue_pop_head(q); + + value.iov_base = NULL; + value.iov_len = 0; + + /* + * This should never be deferred to the read callback for + * primary/secondary service declarations. + */ + if (!gatt_db_attribute_read(attrib, 0, + BT_ATT_OP_READ_BY_GRP_TYPE_REQ, + att, attribute_read_cb, + &value) || !value.iov_len) + return false; + + /* + * Use the first attribute to determine the length of each + * attribute data unit. Stop the list when a different attribute + * value is seen. + */ + if (iter == 0) { + data_val_len = MIN(MIN((unsigned)mtu - 6, 251), + value.iov_len); + pdu[0] = data_val_len + 4; + iter++; + } else if (value.iov_len != data_val_len) + break; + + /* Stop if this unit would surpass the MTU */ + if (iter + data_val_len + 4 > mtu - 1) + break; + + gatt_db_attribute_get_service_handles(attrib, &start_handle, + &end_handle); + + put_le16(start_handle, pdu + iter); + put_le16(end_handle, pdu + iter + 2); + memcpy(pdu + iter + 4, value.iov_base, data_val_len); + + iter += data_val_len + 4; + } + + *len = iter; + + return true; +} + +static void read_by_grp_type_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + uint16_t start, end; + bt_uuid_t type; + bt_uuid_t prim, snd; + uint16_t mtu = bt_att_get_mtu(server->att); + uint8_t rsp_pdu[mtu]; + uint16_t rsp_len; + uint8_t ecode = 0; + uint16_t ehandle = 0; + struct queue *q = NULL; + + if (length != 6 && length != 20) { + ecode = BT_ATT_ERROR_INVALID_PDU; + goto error; + } + + q = queue_new(); + + start = get_le16(pdu); + end = get_le16(pdu + 2); + get_uuid_le(pdu + 4, length - 4, &type); + + util_debug(server->debug_callback, server->debug_data, + "Read By Grp Type - start: 0x%04x end: 0x%04x", + start, end); + + if (!start || !end) { + ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + ehandle = start; + + if (start > end) { + ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + /* + * GATT defines that only the <> and + * <> group types can be used for the + * "Read By Group Type" request (Core v4.1, Vol 3, sec 2.5.3). Return an + * error if any other group type is given. + */ + bt_uuid16_create(&prim, GATT_PRIM_SVC_UUID); + bt_uuid16_create(&snd, GATT_SND_SVC_UUID); + if (bt_uuid_cmp(&type, &prim) && bt_uuid_cmp(&type, &snd)) { + ecode = BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE; + goto error; + } + + gatt_db_read_by_group_type(server->db, start, end, type, q); + + if (queue_isempty(q)) { + ecode = BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND; + goto error; + } + + if (!encode_read_by_grp_type_rsp(server->db, q, server->att, mtu, + rsp_pdu, &rsp_len)) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto error; + } + + queue_destroy(q, NULL); + + bt_att_send(server->att, BT_ATT_OP_READ_BY_GRP_TYPE_RSP, + rsp_pdu, rsp_len, + NULL, NULL, NULL); + + return; + +error: + queue_destroy(q, NULL); + bt_att_send_error_rsp(server->att, opcode, ehandle, ecode); +} + +static void async_read_op_destroy(struct async_read_op *op) +{ + if (op->server) + op->server->pending_read_op = NULL; + + queue_destroy(op->db_data, NULL); + free(op->pdu); + free(op); +} + +static void process_read_by_type(struct async_read_op *op); + +static void read_by_type_read_complete_cb(struct gatt_db_attribute *attr, + int err, const uint8_t *value, + size_t len, void *user_data) +{ + struct async_read_op *op = user_data; + struct bt_gatt_server *server = op->server; + uint16_t mtu; + uint16_t handle; + + if (!server) { + async_read_op_destroy(op); + return; + } + + mtu = bt_att_get_mtu(server->att); + handle = gatt_db_attribute_get_handle(attr); + + /* Terminate the operation if there was an error */ + if (err) { + bt_att_send_error_rsp(server->att, BT_ATT_OP_READ_BY_TYPE_REQ, + handle, err); + async_read_op_destroy(op); + return; + } + + if (op->pdu_len == 0) { + op->value_len = MIN(MIN((unsigned) mtu - 4, 253), len); + op->pdu[0] = op->value_len + 2; + op->pdu_len++; + } else if (len != op->value_len) { + op->done = true; + goto done; + } + + /* Stop if this would surpass the MTU */ + if (op->pdu_len + op->value_len + 2 > (unsigned) mtu - 1) { + op->done = true; + goto done; + } + + /* Encode the current value */ + put_le16(handle, op->pdu + op->pdu_len); + memcpy(op->pdu + op->pdu_len + 2, value, op->value_len); + + op->pdu_len += op->value_len + 2; + + if (op->pdu_len == (unsigned) mtu - 1) + op->done = true; + +done: + process_read_by_type(op); +} + +static bool check_min_key_size(uint8_t min_size, uint8_t size) +{ + if (!min_size || !size) + return true; + + return min_size <= size; +} + +static uint8_t check_permissions(struct bt_gatt_server *server, + struct gatt_db_attribute *attr, uint32_t mask) +{ + uint8_t enc_size; + uint32_t perm; + int security; + + perm = gatt_db_attribute_get_permissions(attr); + + if (perm && mask & BT_ATT_PERM_READ && !(perm & BT_ATT_PERM_READ)) + return BT_ATT_ERROR_READ_NOT_PERMITTED; + + if (perm && mask & BT_ATT_PERM_WRITE && !(perm & BT_ATT_PERM_WRITE)) + return BT_ATT_ERROR_WRITE_NOT_PERMITTED; + + perm &= mask; + if (!perm) + return 0; + + security = bt_att_get_security(server->att, &enc_size); + if (security < 0) + return BT_ATT_ERROR_UNLIKELY; + + if (perm & BT_ATT_PERM_SECURE) { + if (security < BT_ATT_SECURITY_FIPS) + return BT_ATT_ERROR_AUTHENTICATION; + + if (!check_min_key_size(server->min_enc_size, enc_size)) + return BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE; + } + + if (perm & BT_ATT_PERM_AUTHEN) { + if (security < BT_ATT_SECURITY_HIGH) + return BT_ATT_ERROR_AUTHENTICATION; + + if (!check_min_key_size(server->min_enc_size, enc_size)) + return BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE; + } + + if (perm & BT_ATT_PERM_ENCRYPT) { + if (security < BT_ATT_SECURITY_MEDIUM) + return BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION; + + if (!check_min_key_size(server->min_enc_size, enc_size)) + return BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE; + } + + return 0; +} + +static void process_read_by_type(struct async_read_op *op) +{ + struct bt_gatt_server *server = op->server; + uint8_t ecode; + struct gatt_db_attribute *attr; + + attr = queue_pop_head(op->db_data); + + if (op->done || !attr) { + bt_att_send(server->att, BT_ATT_OP_READ_BY_TYPE_RSP, op->pdu, + op->pdu_len, + NULL, NULL, + NULL); + async_read_op_destroy(op); + return; + } + + ecode = check_permissions(server, attr, BT_ATT_PERM_READ | + BT_ATT_PERM_READ_AUTHEN | + BT_ATT_PERM_READ_ENCRYPT); + if (ecode) + goto error; + + if (gatt_db_attribute_read(attr, 0, op->opcode, server->att, + read_by_type_read_complete_cb, op)) + return; + + ecode = BT_ATT_ERROR_UNLIKELY; + +error: + bt_att_send_error_rsp(server->att, BT_ATT_OP_READ_BY_TYPE_REQ, + gatt_db_attribute_get_handle(attr), ecode); + async_read_op_destroy(op); +} + +static void read_by_type_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + uint16_t start, end; + bt_uuid_t type; + uint16_t ehandle = 0; + uint8_t ecode; + struct queue *q = NULL; + struct async_read_op *op; + + if (length != 6 && length != 20) { + ecode = BT_ATT_ERROR_INVALID_PDU; + goto error; + } + + q = queue_new(); + + start = get_le16(pdu); + end = get_le16(pdu + 2); + get_uuid_le(pdu + 4, length - 4, &type); + + util_debug(server->debug_callback, server->debug_data, + "Read By Type - start: 0x%04x end: 0x%04x", + start, end); + + if (!start || !end) { + ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + ehandle = start; + + if (start > end) { + ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + gatt_db_read_by_type(server->db, start, end, type, q); + + if (queue_isempty(q)) { + ecode = BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND; + goto error; + } + + if (server->pending_read_op) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto error; + } + + op = new0(struct async_read_op, 1); + op->pdu = malloc(bt_att_get_mtu(server->att)); + if (!op->pdu) { + free(op); + ecode = BT_ATT_ERROR_INSUFFICIENT_RESOURCES; + goto error; + } + + op->opcode = opcode; + op->server = server; + op->db_data = q; + server->pending_read_op = op; + + process_read_by_type(op); + + return; + +error: + bt_att_send_error_rsp(server->att, opcode, ehandle, ecode); + queue_destroy(q, NULL); +} + +static bool encode_find_info_rsp(struct gatt_db *db, struct queue *q, + uint16_t mtu, + uint8_t *pdu, uint16_t *len) +{ + uint16_t handle; + struct gatt_db_attribute *attr; + const bt_uuid_t *type; + int uuid_len, cur_uuid_len; + int iter = 0; + + *len = 0; + + while (queue_peek_head(q)) { + attr = queue_pop_head(q); + handle = gatt_db_attribute_get_handle(attr); + type = gatt_db_attribute_get_type(attr); + if (!handle || !type) + return false; + + cur_uuid_len = bt_uuid_len(type); + + if (iter == 0) { + switch (cur_uuid_len) { + case 2: + uuid_len = 2; + pdu[0] = 0x01; + break; + case 4: + case 16: + uuid_len = 16; + pdu[0] = 0x02; + break; + default: + return false; + } + + iter++; + } else if (cur_uuid_len != uuid_len) + break; + + if (iter + uuid_len + 2 > mtu - 1) + break; + + put_le16(handle, pdu + iter); + bt_uuid_to_le(type, pdu + iter + 2); + + iter += uuid_len + 2; + } + + *len = iter; + + return true; +} + +static void find_info_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + uint16_t start, end; + uint16_t mtu = bt_att_get_mtu(server->att); + uint8_t rsp_pdu[mtu]; + uint16_t rsp_len; + uint8_t ecode = 0; + uint16_t ehandle = 0; + struct queue *q = NULL; + + if (length != 4) { + ecode = BT_ATT_ERROR_INVALID_PDU; + goto error; + } + + q = queue_new(); + + start = get_le16(pdu); + end = get_le16(pdu + 2); + + util_debug(server->debug_callback, server->debug_data, + "Find Info - start: 0x%04x end: 0x%04x", + start, end); + + if (!start || !end) { + ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + ehandle = start; + + if (start > end) { + ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + gatt_db_find_information(server->db, start, end, q); + + if (queue_isempty(q)) { + ecode = BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND; + goto error; + } + + if (!encode_find_info_rsp(server->db, q, mtu, rsp_pdu, &rsp_len)) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto error; + } + + bt_att_send(server->att, BT_ATT_OP_FIND_INFO_RSP, rsp_pdu, rsp_len, + NULL, NULL, NULL); + queue_destroy(q, NULL); + + return; + +error: + bt_att_send_error_rsp(server->att, opcode, ehandle, ecode); + queue_destroy(q, NULL); + +} + +struct find_by_type_val_data { + uint8_t *pdu; + uint16_t len; + uint16_t mtu; + uint8_t ecode; +}; + +static void find_by_type_val_att_cb(struct gatt_db_attribute *attrib, + void *user_data) +{ + uint16_t handle, end_handle; + struct find_by_type_val_data *data = user_data; + + if (data->ecode) + return; + + if (data->len + 4 > data->mtu - 1) + return; + + /* + * This OP is only valid for Primary Service per the spec + * page 562, so this should work. + */ + gatt_db_attribute_get_service_data(attrib, &handle, &end_handle, NULL, + NULL); + + if (!handle || !end_handle) { + data->ecode = BT_ATT_ERROR_UNLIKELY; + return; + } + + put_le16(handle, data->pdu + data->len); + put_le16(end_handle, data->pdu + data->len + 2); + + data->len += 4; +} + +static void find_by_type_val_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + uint16_t start, end, uuid16; + struct find_by_type_val_data data; + uint16_t mtu = bt_att_get_mtu(server->att); + uint8_t rsp_pdu[mtu]; + uint16_t ehandle = 0; + bt_uuid_t uuid; + + if (length < 6) { + data.ecode = BT_ATT_ERROR_INVALID_PDU; + goto error; + } + + data.pdu = rsp_pdu; + data.len = 0; + data.mtu = mtu; + data.ecode = 0; + + start = get_le16(pdu); + end = get_le16(pdu + 2); + uuid16 = get_le16(pdu + 4); + + util_debug(server->debug_callback, server->debug_data, + "Find By Type Value - start: 0x%04x end: 0x%04x uuid: 0x%04x", + start, end, uuid16); + ehandle = start; + if (start > end) { + data.ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + bt_uuid16_create(&uuid, uuid16); + gatt_db_find_by_type_value(server->db, start, end, &uuid, pdu + 6, + length - 6, + find_by_type_val_att_cb, + &data); + + if (!data.len) + data.ecode = BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND; + + if (data.ecode) + goto error; + + bt_att_send(server->att, BT_ATT_OP_FIND_BY_TYPE_RSP, data.pdu, + data.len, NULL, NULL, NULL); + + return; + +error: + bt_att_send_error_rsp(server->att, opcode, ehandle, data.ecode); +} + +static void async_write_op_destroy(struct async_write_op *op) +{ + if (op->server) + op->server->pending_write_op = NULL; + + free(op); +} + +static void write_complete_cb(struct gatt_db_attribute *attr, int err, + void *user_data) +{ + struct async_write_op *op = user_data; + struct bt_gatt_server *server = op->server; + uint16_t handle; + + if (!server || op->opcode == BT_ATT_OP_WRITE_CMD) { + async_write_op_destroy(op); + return; + } + + handle = gatt_db_attribute_get_handle(attr); + + if (err) + bt_att_send_error_rsp(server->att, op->opcode, handle, err); + else + bt_att_send(server->att, BT_ATT_OP_WRITE_RSP, NULL, 0, + NULL, NULL, NULL); + + async_write_op_destroy(op); +} + +static uint8_t authorize_req(struct bt_gatt_server *server, + uint8_t opcode, uint16_t handle) +{ + if (!server->authorize) + return 0; + + return server->authorize(server->att, opcode, handle, + server->authorize_data); +} + +static void write_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + struct gatt_db_attribute *attr; + uint16_t handle = 0; + struct async_write_op *op = NULL; + uint8_t ecode; + + if (length < 2) { + ecode = BT_ATT_ERROR_INVALID_PDU; + goto error; + } + + ecode = authorize_req(server, opcode, handle); + if (ecode) + goto error; + + handle = get_le16(pdu); + attr = gatt_db_get_attribute(server->db, handle); + if (!attr) { + ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + util_debug(server->debug_callback, server->debug_data, + "Write %s - handle: 0x%04x", + (opcode == BT_ATT_OP_WRITE_REQ) ? "Req" : "Cmd", + handle); + + ecode = check_permissions(server, attr, BT_ATT_PERM_WRITE | + BT_ATT_PERM_WRITE_AUTHEN | + BT_ATT_PERM_WRITE_ENCRYPT); + if (ecode) + goto error; + + if (server->pending_write_op) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto error; + } + + op = new0(struct async_write_op, 1); + op->server = server; + op->opcode = opcode; + server->pending_write_op = op; + + if (gatt_db_attribute_write(attr, 0, pdu + 2, length - 2, opcode, + server->att, + write_complete_cb, op)) + return; + + async_write_op_destroy(op); + + ecode = BT_ATT_ERROR_UNLIKELY; + +error: + if (opcode == BT_ATT_OP_WRITE_CMD) + return; + + bt_att_send_error_rsp(server->att, opcode, handle, ecode); +} + +static uint8_t get_read_rsp_opcode(uint8_t opcode) +{ + + switch (opcode) { + case BT_ATT_OP_READ_REQ: + return BT_ATT_OP_READ_RSP; + case BT_ATT_OP_READ_BLOB_REQ: + return BT_ATT_OP_READ_BLOB_RSP; + default: + /* + * Should never happen + * + * TODO: It would be nice to have a debug-mode assert macro + * for development builds. This way bugs could be easily catched + * during development and there would be self documenting code + * that wouldn't be crash release builds. + */ + return 0; + } + + return 0; +} + +static void read_complete_cb(struct gatt_db_attribute *attr, int err, + const uint8_t *value, size_t len, + void *user_data) +{ + struct async_read_op *op = user_data; + struct bt_gatt_server *server = op->server; + uint8_t rsp_opcode; + uint16_t mtu; + uint16_t handle; + + if (!server) { + async_read_op_destroy(op); + return; + } + + mtu = bt_att_get_mtu(server->att); + handle = gatt_db_attribute_get_handle(attr); + + if (err) { + bt_att_send_error_rsp(server->att, op->opcode, handle, err); + async_read_op_destroy(op); + return; + } + + rsp_opcode = get_read_rsp_opcode(op->opcode); + + bt_att_send(server->att, rsp_opcode, len ? value : NULL, + MIN((unsigned) mtu - 1, len), + NULL, NULL, NULL); + async_read_op_destroy(op); +} + +static void handle_read_req(struct bt_gatt_server *server, uint8_t opcode, + uint16_t handle, + uint16_t offset) +{ + struct gatt_db_attribute *attr; + uint8_t ecode; + struct async_read_op *op = NULL; + + ecode = authorize_req(server, opcode, handle); + if (ecode) + goto error; + + attr = gatt_db_get_attribute(server->db, handle); + if (!attr) { + ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + util_debug(server->debug_callback, server->debug_data, + "Read %sReq - handle: 0x%04x", + opcode == BT_ATT_OP_READ_BLOB_REQ ? "Blob " : "", + handle); + + ecode = check_permissions(server, attr, BT_ATT_PERM_READ | + BT_ATT_PERM_READ_AUTHEN | + BT_ATT_PERM_READ_ENCRYPT); + if (ecode) + goto error; + + if (server->pending_read_op) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto error; + } + + op = new0(struct async_read_op, 1); + op->opcode = opcode; + op->server = server; + server->pending_read_op = op; + + if (gatt_db_attribute_read(attr, offset, opcode, server->att, + read_complete_cb, op)) + return; + + ecode = BT_ATT_ERROR_UNLIKELY; + +error: + if (op) + async_read_op_destroy(op); + + bt_att_send_error_rsp(server->att, opcode, handle, ecode); +} + +static void read_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + uint16_t handle; + + if (length != 2) { + bt_att_send_error_rsp(server->att, opcode, 0, + BT_ATT_ERROR_INVALID_PDU); + return; + } + + handle = get_le16(pdu); + + handle_read_req(server, opcode, handle, 0); +} + +static void read_blob_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + uint16_t handle, offset; + + if (length != 4) { + bt_att_send_error_rsp(server->att, opcode, 0, + BT_ATT_ERROR_INVALID_PDU); + return; + } + + handle = get_le16(pdu); + offset = get_le16(pdu + 2); + + handle_read_req(server, opcode, handle, offset); +} + +struct read_multiple_resp_data { + struct bt_gatt_server *server; + uint16_t *handles; + size_t cur_handle; + size_t num_handles; + uint8_t *rsp_data; + size_t length; + size_t mtu; +}; + +static void read_multiple_resp_data_free(struct read_multiple_resp_data *data) +{ + free(data->handles); + free(data->rsp_data); + free(data); +} + +static void read_multiple_complete_cb(struct gatt_db_attribute *attr, int err, + const uint8_t *value, size_t len, + void *user_data) +{ + struct read_multiple_resp_data *data = user_data; + struct gatt_db_attribute *next_attr; + uint16_t handle = gatt_db_attribute_get_handle(attr); + uint8_t ecode; + + if (err != 0) { + bt_att_send_error_rsp(data->server->att, + BT_ATT_OP_READ_MULT_REQ, handle, err); + read_multiple_resp_data_free(data); + return; + } + + ecode = check_permissions(data->server, attr, BT_ATT_PERM_READ | + BT_ATT_PERM_READ_AUTHEN | + BT_ATT_PERM_READ_ENCRYPT); + if (ecode) { + bt_att_send_error_rsp(data->server->att, + BT_ATT_OP_READ_MULT_REQ, handle, ecode); + read_multiple_resp_data_free(data); + return; + } + + len = MIN(len, data->mtu - data->length - 1); + + memcpy(data->rsp_data + data->length, value, len); + data->length += len; + + data->cur_handle++; + + if ((data->length >= data->mtu - 1) || + (data->cur_handle == data->num_handles)) { + bt_att_send(data->server->att, BT_ATT_OP_READ_MULT_RSP, + data->rsp_data, data->length, NULL, NULL, NULL); + read_multiple_resp_data_free(data); + return; + } + + util_debug(data->server->debug_callback, data->server->debug_data, + "Read Multiple Req - #%zu of %zu: 0x%04x", + data->cur_handle + 1, data->num_handles, + data->handles[data->cur_handle]); + + next_attr = gatt_db_get_attribute(data->server->db, + data->handles[data->cur_handle]); + + if (!next_attr) { + bt_att_send_error_rsp(data->server->att, + BT_ATT_OP_READ_MULT_REQ, + data->handles[data->cur_handle], + BT_ATT_ERROR_INVALID_HANDLE); + read_multiple_resp_data_free(data); + return; + } + + if (!gatt_db_attribute_read(next_attr, 0, BT_ATT_OP_READ_MULT_REQ, + data->server->att, + read_multiple_complete_cb, data)) { + bt_att_send_error_rsp(data->server->att, + BT_ATT_OP_READ_MULT_REQ, + data->handles[data->cur_handle], + BT_ATT_ERROR_UNLIKELY); + read_multiple_resp_data_free(data); + } +} + +static void read_multiple_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + struct gatt_db_attribute *attr; + struct read_multiple_resp_data *data = NULL; + uint8_t ecode = BT_ATT_ERROR_UNLIKELY; + size_t i = 0; + + if (length < 4) { + ecode = BT_ATT_ERROR_INVALID_PDU; + goto error; + } + + data = new0(struct read_multiple_resp_data, 1); + data->handles = NULL; + data->rsp_data = NULL; + data->server = server; + data->num_handles = length / 2; + data->cur_handle = 0; + data->mtu = bt_att_get_mtu(server->att); + data->length = 0; + data->rsp_data = malloc(data->mtu - 1); + + if (!data->rsp_data) + goto error; + + data->handles = new0(uint16_t, data->num_handles); + + for (i = 0; i < data->num_handles; i++) + data->handles[i] = get_le16(pdu + i * 2); + + util_debug(server->debug_callback, server->debug_data, + "Read Multiple Req - %zu handles, 1st: 0x%04x", + data->num_handles, data->handles[0]); + + attr = gatt_db_get_attribute(server->db, data->handles[0]); + + if (!attr) { + ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + if (gatt_db_attribute_read(attr, 0, opcode, server->att, + read_multiple_complete_cb, data)) + return; + +error: + if (data) + read_multiple_resp_data_free(data); + + bt_att_send_error_rsp(server->att, opcode, 0, ecode); +} + +static bool append_prep_data(struct prep_write_data *prep_data, uint16_t handle, + uint16_t length, uint8_t *value) +{ + uint8_t *val; + uint16_t len; + + if (!length) + return true; + + len = prep_data->length + length; + + val = realloc(prep_data->value, len); + if (!val) + return false; + + memcpy(val + prep_data->length, value, length); + + prep_data->value = val; + prep_data->length = len; + + return true; +} + +static bool is_reliable_write_supported(const struct bt_gatt_server *server, + uint16_t handle) +{ + struct gatt_db_attribute *attr; + uint16_t ext_prop; + + attr = gatt_db_get_attribute(server->db, handle); + if (!attr) + return false; + + if (!gatt_db_attribute_get_char_data(attr, NULL, NULL, NULL, &ext_prop, + NULL)) + return false; + + return (ext_prop & BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE); +} + +static bool prep_data_new(struct bt_gatt_server *server, + uint16_t handle, uint16_t offset, + uint16_t length, uint8_t *value) +{ + struct prep_write_data *prep_data; + + prep_data = new0(struct prep_write_data, 1); + + if (!append_prep_data(prep_data, handle, length, value)) { + prep_write_data_destroy(prep_data); + return false; + } + + prep_data->server = server; + prep_data->handle = handle; + prep_data->offset = offset; + + /* + * Handle is the value handle. We need characteristic declaration + * handle which in BlueZ is handle_value -1 + */ + prep_data->reliable_supported = is_reliable_write_supported(server, + handle - 1); + + queue_push_tail(server->prep_queue, prep_data); + + return true; +} + +static bool store_prep_data(struct bt_gatt_server *server, + uint16_t handle, uint16_t offset, + uint16_t length, uint8_t *value) +{ + struct prep_write_data *prep_data = NULL; + + /* + * Now lets check if prep write is a continuation of long write + * If so do aggregation of data + */ + prep_data = queue_peek_tail(server->prep_queue); + if (prep_data && (prep_data->handle == handle) && + (offset == (prep_data->length + prep_data->offset))) + return append_prep_data(prep_data, handle, length, value); + + return prep_data_new(server, handle, offset, length, value); +} + +struct prep_write_complete_data { + void *pdu; + uint16_t length; + struct bt_gatt_server *server; +}; + +static void prep_write_complete_cb(struct gatt_db_attribute *attr, int err, + void *user_data) +{ + struct prep_write_complete_data *pwcd = user_data; + uint16_t handle = 0; + uint16_t offset; + + handle = get_le16(pwcd->pdu); + + if (err) { + bt_att_send_error_rsp(pwcd->server->att, + BT_ATT_OP_PREP_WRITE_REQ, handle, err); + free(pwcd->pdu); + free(pwcd); + + return; + } + + offset = get_le16(pwcd->pdu + 2); + + if (!store_prep_data(pwcd->server, handle, offset, pwcd->length - 4, + &((uint8_t *) pwcd->pdu)[4])) + bt_att_send_error_rsp(pwcd->server->att, + BT_ATT_OP_PREP_WRITE_RSP, handle, + BT_ATT_ERROR_INSUFFICIENT_RESOURCES); + + bt_att_send(pwcd->server->att, BT_ATT_OP_PREP_WRITE_RSP, pwcd->pdu, + pwcd->length, NULL, NULL, NULL); + + free(pwcd->pdu); + free(pwcd); +} + +static void prep_write_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + uint16_t handle = 0; + uint16_t offset; + struct gatt_db_attribute *attr; + struct prep_write_complete_data *pwcd; + uint8_t ecode, status; + + if (length < 4) { + ecode = BT_ATT_ERROR_INVALID_PDU; + goto error; + } + + if (queue_length(server->prep_queue) >= server->max_prep_queue_len) { + ecode = BT_ATT_ERROR_PREPARE_QUEUE_FULL; + goto error; + } + + handle = get_le16(pdu); + offset = get_le16(pdu + 2); + + attr = gatt_db_get_attribute(server->db, handle); + if (!attr) { + ecode = BT_ATT_ERROR_INVALID_HANDLE; + goto error; + } + + util_debug(server->debug_callback, server->debug_data, + "Prep Write Req - handle: 0x%04x", handle); + + ecode = check_permissions(server, attr, BT_ATT_PERM_WRITE | + BT_ATT_PERM_WRITE_AUTHEN | + BT_ATT_PERM_WRITE_ENCRYPT); + if (ecode) + goto error; + + pwcd = new0(struct prep_write_complete_data, 1); + pwcd->pdu = malloc(length); + memcpy(pwcd->pdu, pdu, length); + pwcd->length = length; + pwcd->server = server; + + status = gatt_db_attribute_write(attr, offset, NULL, 0, + BT_ATT_OP_PREP_WRITE_REQ, + server->att, + prep_write_complete_cb, pwcd); + + if (status) + return; + + ecode = BT_ATT_ERROR_UNLIKELY; + +error: + bt_att_send_error_rsp(server->att, opcode, handle, ecode); +} + +static void exec_next_prep_write(struct bt_gatt_server *server, + uint16_t ehandle, int err); + +static void exec_write_complete_cb(struct gatt_db_attribute *attr, int err, + void *user_data) +{ + struct bt_gatt_server *server = user_data; + uint16_t handle = gatt_db_attribute_get_handle(attr); + + exec_next_prep_write(server, handle, err); +} + +static void exec_next_prep_write(struct bt_gatt_server *server, + uint16_t ehandle, int err) +{ + struct prep_write_data *next = NULL; + struct gatt_db_attribute *attr; + bool status; + + if (err) + goto error; + + next = queue_pop_head(server->prep_queue); + if (!next) { + bt_att_send(server->att, BT_ATT_OP_EXEC_WRITE_RSP, NULL, 0, + NULL, NULL, NULL); + return; + } + + attr = gatt_db_get_attribute(server->db, next->handle); + if (!attr) { + err = BT_ATT_ERROR_UNLIKELY; + goto error; + } + + status = gatt_db_attribute_write(attr, next->offset, + next->value, next->length, + BT_ATT_OP_EXEC_WRITE_REQ, + server->att, + exec_write_complete_cb, server); + + prep_write_data_destroy(next); + + if (status) + return; + + err = BT_ATT_ERROR_UNLIKELY; + +error: + queue_remove_all(server->prep_queue, NULL, NULL, + prep_write_data_destroy); + + bt_att_send_error_rsp(server->att, BT_ATT_OP_EXEC_WRITE_REQ, + ehandle, err); +} + +static bool find_no_reliable_characteristic(const void *data, + const void *match_data) +{ + const struct prep_write_data *prep_data = data; + + return !prep_data->reliable_supported; +} + +static void exec_write_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + uint8_t flags; + uint8_t ecode; + bool write; + uint16_t ehandle = 0; + + if (length != 1) { + ecode = BT_ATT_ERROR_INVALID_PDU; + goto error; + } + + flags = ((uint8_t *) pdu)[0]; + + util_debug(server->debug_callback, server->debug_data, + "Exec Write Req - flags: 0x%02x", flags); + + if (flags == 0x00) + write = false; + else if (flags == 0x01) + write = true; + else { + ecode = BT_ATT_ERROR_INVALID_PDU; + goto error; + } + + if (!write) { + queue_remove_all(server->prep_queue, NULL, NULL, + prep_write_data_destroy); + bt_att_send(server->att, BT_ATT_OP_EXEC_WRITE_RSP, NULL, 0, + NULL, NULL, NULL); + return; + } + + /* If there is more than one prep request, we are in reliable session */ + if (queue_length(server->prep_queue) > 1) { + struct prep_write_data *prep_data; + + prep_data = queue_find(server->prep_queue, + find_no_reliable_characteristic, NULL); + if (prep_data) { + ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; + ehandle = prep_data->handle; + goto error; + } + } + + exec_next_prep_write(server, 0, 0); + + return; + +error: + queue_remove_all(server->prep_queue, NULL, NULL, + prep_write_data_destroy); + bt_att_send_error_rsp(server->att, opcode, ehandle, ecode); +} + +static void exchange_mtu_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct bt_gatt_server *server = user_data; + uint16_t client_rx_mtu; + uint16_t final_mtu; + uint8_t rsp_pdu[2]; + + if (length != 2) { + bt_att_send_error_rsp(server->att, opcode, 0, + BT_ATT_ERROR_INVALID_PDU); + return; + } + + client_rx_mtu = get_le16(pdu); + final_mtu = MAX(MIN(client_rx_mtu, server->mtu), BT_ATT_DEFAULT_LE_MTU); + + /* Respond with the server MTU */ + put_le16(server->mtu, rsp_pdu); + bt_att_send(server->att, BT_ATT_OP_MTU_RSP, rsp_pdu, 2, NULL, NULL, + NULL); + + /* Set MTU to be the minimum */ + server->mtu = final_mtu; + bt_att_set_mtu(server->att, final_mtu); + + util_debug(server->debug_callback, server->debug_data, + "MTU exchange complete, with MTU: %u", final_mtu); +} + +static bool gatt_server_register_att_handlers(struct bt_gatt_server *server) +{ + /* Exchange MTU */ + server->mtu_id = bt_att_register(server->att, BT_ATT_OP_MTU_REQ, + exchange_mtu_cb, + server, NULL); + if (!server->mtu_id) + return false; + + /* Read By Group Type */ + server->read_by_grp_type_id = bt_att_register(server->att, + BT_ATT_OP_READ_BY_GRP_TYPE_REQ, + read_by_grp_type_cb, + server, NULL); + if (!server->read_by_grp_type_id) + return false; + + /* Read By Type */ + server->read_by_type_id = bt_att_register(server->att, + BT_ATT_OP_READ_BY_TYPE_REQ, + read_by_type_cb, + server, NULL); + if (!server->read_by_type_id) + return false; + + /* Find Information */ + server->find_info_id = bt_att_register(server->att, + BT_ATT_OP_FIND_INFO_REQ, + find_info_cb, + server, NULL); + if (!server->find_info_id) + return false; + + /* Find By Type Value */ + server->find_by_type_value_id = bt_att_register(server->att, + BT_ATT_OP_FIND_BY_TYPE_REQ, + find_by_type_val_cb, + server, NULL); + + if (!server->find_by_type_value_id) + return false; + + /* Write Request */ + server->write_id = bt_att_register(server->att, BT_ATT_OP_WRITE_REQ, + write_cb, + server, NULL); + if (!server->write_id) + return false; + + /* Write Command */ + server->write_cmd_id = bt_att_register(server->att, BT_ATT_OP_WRITE_CMD, + write_cb, + server, NULL); + if (!server->write_cmd_id) + return false; + + /* Read Request */ + server->read_id = bt_att_register(server->att, BT_ATT_OP_READ_REQ, + read_cb, + server, NULL); + if (!server->read_id) + return false; + + /* Read Blob Request */ + server->read_blob_id = bt_att_register(server->att, + BT_ATT_OP_READ_BLOB_REQ, + read_blob_cb, + server, NULL); + if (!server->read_blob_id) + return false; + + /* Read Multiple Request */ + server->read_multiple_id = bt_att_register(server->att, + BT_ATT_OP_READ_MULT_REQ, + read_multiple_cb, + server, NULL); + + if (!server->read_multiple_id) + return false; + + /* Prepare Write Request */ + server->prep_write_id = bt_att_register(server->att, + BT_ATT_OP_PREP_WRITE_REQ, + prep_write_cb, server, NULL); + if (!server->prep_write_id) + return false; + + /* Execute Write Request */ + server->exec_write_id = bt_att_register(server->att, + BT_ATT_OP_EXEC_WRITE_REQ, + exec_write_cb, server, NULL); + if (!server->exec_write_id) + return NULL; + + return true; +} + +struct bt_gatt_server *bt_gatt_server_new(struct gatt_db *db, + struct bt_att *att, uint16_t mtu, + uint8_t min_enc_size) +{ + struct bt_gatt_server *server; + + if (!att || !db) + return NULL; + + server = new0(struct bt_gatt_server, 1); + server->db = gatt_db_ref(db); + server->att = bt_att_ref(att); + server->mtu = MAX(mtu, BT_ATT_DEFAULT_LE_MTU); + server->max_prep_queue_len = DEFAULT_MAX_PREP_QUEUE_LEN; + server->prep_queue = queue_new(); + server->min_enc_size = min_enc_size; + + if (!gatt_server_register_att_handlers(server)) { + bt_gatt_server_free(server); + return NULL; + } + + return bt_gatt_server_ref(server); +} + +uint16_t bt_gatt_server_get_mtu(struct bt_gatt_server *server) +{ + if (!server || !server->att) + return 0; + + return bt_att_get_mtu(server->att); +} + +struct bt_att *bt_gatt_server_get_att(struct bt_gatt_server *server) +{ + if (!server) + return NULL; + + return server->att; +} + +struct bt_gatt_server *bt_gatt_server_ref(struct bt_gatt_server *server) +{ + if (!server) + return NULL; + + __sync_fetch_and_add(&server->ref_count, 1); + + return server; +} + +void bt_gatt_server_unref(struct bt_gatt_server *server) +{ + if (!server) + return; + + if (__sync_sub_and_fetch(&server->ref_count, 1)) + return; + + bt_gatt_server_free(server); +} + +bool bt_gatt_server_set_debug(struct bt_gatt_server *server, + bt_gatt_server_debug_func_t callback, + void *user_data, + bt_gatt_server_destroy_func_t destroy) +{ + if (!server) + return false; + + if (server->debug_destroy) + server->debug_destroy(server->debug_data); + + server->debug_callback = callback; + server->debug_destroy = destroy; + server->debug_data = user_data; + + return true; +} + +bool bt_gatt_server_send_notification(struct bt_gatt_server *server, + uint16_t handle, const uint8_t *value, + uint16_t length) +{ + uint16_t pdu_len; + uint8_t *pdu; + bool result; + + if (!server || (length && !value)) + return false; + + pdu_len = MIN(bt_att_get_mtu(server->att) - 1, length + 2); + pdu = malloc(pdu_len); + if (!pdu) + return false; + + put_le16(handle, pdu); + memcpy(pdu + 2, value, pdu_len - 2); + + result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_VAL_NOT, pdu, + pdu_len, NULL, NULL, NULL); + free(pdu); + + return result; +} + +struct ind_data { + bt_gatt_server_conf_func_t callback; + bt_gatt_server_destroy_func_t destroy; + void *user_data; +}; + +static void destroy_ind_data(void *user_data) +{ + struct ind_data *data = user_data; + + if (data->destroy) + data->destroy(data->user_data); + + free(data); +} + +static void conf_cb(uint8_t opcode, const void *pdu, + uint16_t length, void *user_data) +{ + struct ind_data *data = user_data; + + if (data->callback) + data->callback(data->user_data); +} + +bool bt_gatt_server_send_indication(struct bt_gatt_server *server, + uint16_t handle, const uint8_t *value, + uint16_t length, + bt_gatt_server_conf_func_t callback, + void *user_data, + bt_gatt_server_destroy_func_t destroy) +{ + uint16_t pdu_len; + uint8_t *pdu; + struct ind_data *data; + bool result; + + if (!server || (length && !value)) + return false; + + pdu_len = MIN(bt_att_get_mtu(server->att) - 1, length + 2); + pdu = malloc(pdu_len); + if (!pdu) + return false; + + data = new0(struct ind_data, 1); + + data->callback = callback; + data->destroy = destroy; + data->user_data = user_data; + + put_le16(handle, pdu); + memcpy(pdu + 2, value, pdu_len - 2); + + result = !!bt_att_send(server->att, BT_ATT_OP_HANDLE_VAL_IND, pdu, + pdu_len, conf_cb, + data, destroy_ind_data); + if (!result) + destroy_ind_data(data); + + free(pdu); + + return result; +} + +bool bt_gatt_server_set_authorize(struct bt_gatt_server *server, + bt_gatt_server_authorize_cb_t cb, + void *user_data) +{ + if (!server) + return false; + + server->authorize = cb; + server->authorize_data = user_data; + + return true; +} diff --git a/src/shared/gatt-server.h b/src/shared/gatt-server.h new file mode 100644 index 0000000..c3d83f2 --- /dev/null +++ b/src/shared/gatt-server.h @@ -0,0 +1,62 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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 + * + */ + +#include + +struct bt_gatt_server; + +struct bt_gatt_server *bt_gatt_server_new(struct gatt_db *db, + struct bt_att *att, uint16_t mtu, + uint8_t min_enc_size); +uint16_t bt_gatt_server_get_mtu(struct bt_gatt_server *server); +struct bt_att *bt_gatt_server_get_att(struct bt_gatt_server *server); + +struct bt_gatt_server *bt_gatt_server_ref(struct bt_gatt_server *server); +void bt_gatt_server_unref(struct bt_gatt_server *server); + +typedef void (*bt_gatt_server_destroy_func_t)(void *user_data); +typedef void (*bt_gatt_server_debug_func_t)(const char *str, void *user_data); +typedef void (*bt_gatt_server_conf_func_t)(void *user_data); + +bool bt_gatt_server_set_debug(struct bt_gatt_server *server, + bt_gatt_server_debug_func_t callback, + void *user_data, + bt_gatt_server_destroy_func_t destroy); + +typedef uint8_t (*bt_gatt_server_authorize_cb_t)(struct bt_att *att, + uint8_t opcode, uint16_t handle, + void *user_data); +bool bt_gatt_server_set_authorize(struct bt_gatt_server *server, + bt_gatt_server_authorize_cb_t cb, + void *user_data); + +bool bt_gatt_server_send_notification(struct bt_gatt_server *server, + uint16_t handle, const uint8_t *value, + uint16_t length); + +bool bt_gatt_server_send_indication(struct bt_gatt_server *server, + uint16_t handle, const uint8_t *value, + uint16_t length, + bt_gatt_server_conf_func_t callback, + void *user_data, + bt_gatt_server_destroy_func_t destroy); diff --git a/src/shared/hci-crypto.c b/src/shared/hci-crypto.c new file mode 100644 index 0000000..f750747 --- /dev/null +++ b/src/shared/hci-crypto.c @@ -0,0 +1,172 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "monitor/bt.h" +#include "src/shared/util.h" +#include "src/shared/hci.h" +#include "src/shared/hci-crypto.h" + +struct crypto_data { + uint8_t size; + bt_hci_crypto_func_t callback; + void *user_data; +}; + +static void le_encrypt_callback(const void *response, uint8_t size, + void *user_data) +{ + struct crypto_data *data = user_data; + const struct bt_hci_rsp_le_encrypt *rsp = response; + + if (rsp->status) { + data->callback(NULL, 0, data->user_data); + return; + } + + data->callback(rsp->data, data->size, data->user_data); +} + +static bool le_encrypt(struct bt_hci *hci, uint8_t size, + const uint8_t key[16], const uint8_t plaintext[16], + bt_hci_crypto_func_t callback, void *user_data) +{ + struct crypto_data *data; + struct bt_hci_cmd_le_encrypt cmd; + + if (!callback || !size || size > 16) + return false; + + memcpy(cmd.key, key, 16); + memcpy(cmd.plaintext, plaintext, 16); + + data = new0(struct crypto_data, 1); + data->size = size; + data->callback = callback; + data->user_data = user_data; + + if (!bt_hci_send(hci, BT_HCI_CMD_LE_ENCRYPT, &cmd, sizeof(cmd), + le_encrypt_callback, data, free)) { + free(data); + return false; + } + + return true; +} + +static void prand_callback(const void *response, uint8_t size, + void *user_data) +{ + struct crypto_data *data = user_data; + const struct bt_hci_rsp_le_rand *rsp = response; + uint8_t prand[3]; + + if (rsp->status) { + data->callback(NULL, 0, data->user_data); + return; + } + + prand[0] = (rsp->number & 0xff0000) >> 16; + prand[1] = (rsp->number & 0x00ff00) >> 8; + prand[2] = (rsp->number & 0x00003f) | 0x40; + + data->callback(prand, 3, data->user_data); +} + +bool bt_hci_crypto_prand(struct bt_hci *hci, + bt_hci_crypto_func_t callback, void *user_data) +{ + struct crypto_data *data; + + if (!callback) + return false; + + data = new0(struct crypto_data, 1); + data->callback = callback; + data->user_data = user_data; + + if (!bt_hci_send(hci, BT_HCI_CMD_LE_RAND, NULL, 0, + prand_callback, data, free)) { + free(data); + return false; + } + + return true; +} + +bool bt_hci_crypto_e(struct bt_hci *hci, + const uint8_t key[16], const uint8_t plaintext[16], + bt_hci_crypto_func_t callback, void *user_data) +{ + return le_encrypt(hci, 16, key, plaintext, callback, user_data); +} + +bool bt_hci_crypto_d1(struct bt_hci *hci, + const uint8_t k[16], uint16_t d, uint16_t r, + bt_hci_crypto_func_t callback, void *user_data) +{ + uint8_t dp[16]; + + /* d' = padding || r || d */ + dp[0] = d & 0xff; + dp[1] = d >> 8; + dp[2] = r & 0xff; + dp[3] = r >> 8; + memset(dp + 4, 0, 12); + + /* d1(k, d, r) = e(k, d') */ + return le_encrypt(hci, 16, k, dp, callback, user_data); +} + +bool bt_hci_crypto_dm(struct bt_hci *hci, + const uint8_t k[16], const uint8_t r[8], + bt_hci_crypto_func_t callback, void *user_data) +{ + uint8_t rp[16]; + + /* r' = padding || r */ + memcpy(rp, r, 8); + memset(rp + 8, 0, 8); + + /* dm(k, r) = e(k, r') mod 2^16 */ + return le_encrypt(hci, 8, k, rp, callback, user_data); +} + +bool bt_hci_crypto_ah(struct bt_hci *hci, + const uint8_t k[16], const uint8_t r[3], + bt_hci_crypto_func_t callback, void *user_data) +{ + uint8_t rp[16]; + + /* r' = padding || r */ + memcpy(rp, r, 3); + memset(rp + 3, 0, 13); + + /* ah(k, r) = e(k, r') mod 2^24 */ + return le_encrypt(hci, 3, k, rp, callback, user_data); +} diff --git a/src/shared/hci-crypto.h b/src/shared/hci-crypto.h new file mode 100644 index 0000000..b090c24 --- /dev/null +++ b/src/shared/hci-crypto.h @@ -0,0 +1,45 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013-2014 Intel Corporation + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +struct bt_hci; + +typedef void (*bt_hci_crypto_func_t)(const void *data, uint8_t size, + void *user_data); + +bool bt_hci_crypto_prand(struct bt_hci *hci, + bt_hci_crypto_func_t callback, void *user_data); +bool bt_hci_crypto_e(struct bt_hci *hci, + const uint8_t key[16], const uint8_t plaintext[16], + bt_hci_crypto_func_t callback, void *user_data); +bool bt_hci_crypto_d1(struct bt_hci *hci, + const uint8_t k[16], uint16_t d, uint16_t r, + bt_hci_crypto_func_t callback, void *user_data); +bool bt_hci_crypto_dm(struct bt_hci *hci, + const uint8_t k[16], const uint8_t r[8], + bt_hci_crypto_func_t callback, void *user_data); +bool bt_hci_crypto_ah(struct bt_hci *hci, + const uint8_t k[16], const uint8_t r[3], + bt_hci_crypto_func_t callback, void *user_data); diff --git a/src/shared/hci.c b/src/shared/hci.c new file mode 100644 index 0000000..bfee4ab --- /dev/null +++ b/src/shared/hci.c @@ -0,0 +1,586 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#include +#include +#include +#include +#include + +#include "monitor/bt.h" +#include "src/shared/mainloop.h" +#include "src/shared/io.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/hci.h" + +#define BTPROTO_HCI 1 +struct sockaddr_hci { + sa_family_t hci_family; + unsigned short hci_dev; + unsigned short hci_channel; +}; +#define HCI_CHANNEL_RAW 0 +#define HCI_CHANNEL_USER 1 + +#define SOL_HCI 0 +#define HCI_FILTER 2 +struct hci_filter { + uint32_t type_mask; + uint32_t event_mask[2]; + uint16_t opcode; +}; + +struct bt_hci { + int ref_count; + struct io *io; + bool is_stream; + bool writer_active; + uint8_t num_cmds; + unsigned int next_cmd_id; + unsigned int next_evt_id; + struct queue *cmd_queue; + struct queue *rsp_queue; + struct queue *evt_list; +}; + +struct cmd { + unsigned int id; + uint16_t opcode; + void *data; + uint8_t size; + bt_hci_callback_func_t callback; + bt_hci_destroy_func_t destroy; + void *user_data; +}; + +struct evt { + unsigned int id; + uint8_t event; + bt_hci_callback_func_t callback; + bt_hci_destroy_func_t destroy; + void *user_data; +}; + +static void cmd_free(void *data) +{ + struct cmd *cmd = data; + + if (cmd->destroy) + cmd->destroy(cmd->user_data); + + free(cmd->data); + free(cmd); +} + +static void evt_free(void *data) +{ + struct evt *evt = data; + + if (evt->destroy) + evt->destroy(evt->user_data); + + free(evt); +} + +static void send_command(struct bt_hci *hci, uint16_t opcode, + void *data, uint8_t size) +{ + uint8_t type = BT_H4_CMD_PKT; + struct bt_hci_cmd_hdr hdr; + struct iovec iov[3]; + int iovcnt; + + if (hci->num_cmds < 1) + return; + + hdr.opcode = cpu_to_le16(opcode); + hdr.plen = size; + + iov[0].iov_base = &type; + iov[0].iov_len = 1; + iov[1].iov_base = &hdr; + iov[1].iov_len = sizeof(hdr); + + if (size > 0) { + iov[2].iov_base = data; + iov[2].iov_len = size; + iovcnt = 3; + } else + iovcnt = 2; + + if (io_send(hci->io, iov, iovcnt) < 0) + return; + + hci->num_cmds--; +} + +static bool io_write_callback(struct io *io, void *user_data) +{ + struct bt_hci *hci = user_data; + struct cmd *cmd; + + cmd = queue_pop_head(hci->cmd_queue); + if (cmd) { + send_command(hci, cmd->opcode, cmd->data, cmd->size); + queue_push_tail(hci->rsp_queue, cmd); + } + + hci->writer_active = false; + + return false; +} + +static void wakeup_writer(struct bt_hci *hci) +{ + if (hci->writer_active) + return; + + if (hci->num_cmds < 1) + return; + + if (queue_isempty(hci->cmd_queue)) + return; + + if (!io_set_write_handler(hci->io, io_write_callback, hci, NULL)) + return; + + hci->writer_active = true; +} + +static bool match_cmd_opcode(const void *a, const void *b) +{ + const struct cmd *cmd = a; + uint16_t opcode = PTR_TO_UINT(b); + + return cmd->opcode == opcode; +} + +static void process_response(struct bt_hci *hci, uint16_t opcode, + const void *data, size_t size) +{ + struct cmd *cmd; + + if (opcode == BT_HCI_CMD_NOP) + goto done; + + cmd = queue_remove_if(hci->rsp_queue, match_cmd_opcode, + UINT_TO_PTR(opcode)); + if (!cmd) + return; + + if (cmd->callback) + cmd->callback(data, size, cmd->user_data); + + cmd_free(cmd); + +done: + wakeup_writer(hci); +} + +static void process_notify(void *data, void *user_data) +{ + struct bt_hci_evt_hdr *hdr = user_data; + struct evt *evt = data; + + if (evt->event == hdr->evt) + evt->callback(user_data + sizeof(struct bt_hci_evt_hdr), + hdr->plen, evt->user_data); +} + +static void process_event(struct bt_hci *hci, const void *data, size_t size) +{ + const struct bt_hci_evt_hdr *hdr = data; + const struct bt_hci_evt_cmd_complete *cc; + const struct bt_hci_evt_cmd_status *cs; + + if (size < sizeof(struct bt_hci_evt_hdr)) + return; + + data += sizeof(struct bt_hci_evt_hdr); + size -= sizeof(struct bt_hci_evt_hdr); + + if (hdr->plen != size) + return; + + switch (hdr->evt) { + case BT_HCI_EVT_CMD_COMPLETE: + if (size < sizeof(*cc)) + return; + cc = data; + hci->num_cmds = cc->ncmd; + process_response(hci, le16_to_cpu(cc->opcode), + data + sizeof(*cc), + size - sizeof(*cc)); + break; + + case BT_HCI_EVT_CMD_STATUS: + if (size < sizeof(*cs)) + return; + cs = data; + hci->num_cmds = cs->ncmd; + process_response(hci, le16_to_cpu(cs->opcode), &cs->status, 1); + break; + + default: + queue_foreach(hci->evt_list, process_notify, (void *) hdr); + break; + } +} + +static bool io_read_callback(struct io *io, void *user_data) +{ + struct bt_hci *hci = user_data; + uint8_t buf[512]; + ssize_t len; + int fd; + + fd = io_get_fd(hci->io); + if (fd < 0) + return false; + + if (hci->is_stream) + return false; + + len = read(fd, buf, sizeof(buf)); + if (len < 0) + return false; + + if (len < 1) + return true; + + switch (buf[0]) { + case BT_H4_EVT_PKT: + process_event(hci, buf + 1, len - 1); + break; + } + + return true; +} + +static struct bt_hci *create_hci(int fd) +{ + struct bt_hci *hci; + + if (fd < 0) + return NULL; + + hci = new0(struct bt_hci, 1); + hci->io = io_new(fd); + if (!hci->io) { + free(hci); + return NULL; + } + + hci->is_stream = true; + hci->writer_active = false; + hci->num_cmds = 1; + hci->next_cmd_id = 1; + hci->next_evt_id = 1; + + hci->cmd_queue = queue_new(); + hci->rsp_queue = queue_new(); + hci->evt_list = queue_new(); + + if (!io_set_read_handler(hci->io, io_read_callback, hci, NULL)) { + queue_destroy(hci->evt_list, NULL); + queue_destroy(hci->rsp_queue, NULL); + queue_destroy(hci->cmd_queue, NULL); + io_destroy(hci->io); + free(hci); + return NULL; + } + + return bt_hci_ref(hci); +} + +struct bt_hci *bt_hci_new(int fd) +{ + struct bt_hci *hci; + + hci = create_hci(fd); + if (!hci) + return NULL; + + return hci; +} + +static int create_socket(uint16_t index, uint16_t channel) +{ + struct sockaddr_hci addr; + int fd; + + fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, + BTPROTO_HCI); + if (fd < 0) + return -1; + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = index; + addr.hci_channel = channel; + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(fd); + return -1; + } + + return fd; +} + +struct bt_hci *bt_hci_new_user_channel(uint16_t index) +{ + struct bt_hci *hci; + int fd; + + fd = create_socket(index, HCI_CHANNEL_USER); + if (fd < 0) + return NULL; + + hci = create_hci(fd); + if (!hci) { + close(fd); + return NULL; + } + + hci->is_stream = false; + + bt_hci_set_close_on_unref(hci, true); + + return hci; +} + +struct bt_hci *bt_hci_new_raw_device(uint16_t index) +{ + struct bt_hci *hci; + struct hci_filter flt; + int fd; + + fd = create_socket(index, HCI_CHANNEL_RAW); + if (fd < 0) + return NULL; + + memset(&flt, 0, sizeof(flt)); + flt.type_mask = 1 << BT_H4_EVT_PKT; + flt.event_mask[0] = 0xffffffff; + flt.event_mask[1] = 0xffffffff; + + if (setsockopt(fd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + close(fd); + return NULL; + } + + hci = create_hci(fd); + if (!hci) { + close(fd); + return NULL; + } + + hci->is_stream = false; + + bt_hci_set_close_on_unref(hci, true); + + return hci; +} + +struct bt_hci *bt_hci_ref(struct bt_hci *hci) +{ + if (!hci) + return NULL; + + __sync_fetch_and_add(&hci->ref_count, 1); + + return hci; +} + +void bt_hci_unref(struct bt_hci *hci) +{ + if (!hci) + return; + + if (__sync_sub_and_fetch(&hci->ref_count, 1)) + return; + + queue_destroy(hci->evt_list, evt_free); + queue_destroy(hci->cmd_queue, cmd_free); + queue_destroy(hci->rsp_queue, cmd_free); + + io_destroy(hci->io); + + free(hci); +} + +bool bt_hci_set_close_on_unref(struct bt_hci *hci, bool do_close) +{ + if (!hci) + return false; + + return io_set_close_on_destroy(hci->io, do_close); +} + +unsigned int bt_hci_send(struct bt_hci *hci, uint16_t opcode, + const void *data, uint8_t size, + bt_hci_callback_func_t callback, + void *user_data, bt_hci_destroy_func_t destroy) +{ + struct cmd *cmd; + + if (!hci) + return 0; + + cmd = new0(struct cmd, 1); + cmd->opcode = opcode; + cmd->size = size; + + if (cmd->size > 0) { + cmd->data = malloc(cmd->size); + if (!cmd->data) { + free(cmd); + return 0; + } + + memcpy(cmd->data, data, cmd->size); + } + + if (hci->next_cmd_id < 1) + hci->next_cmd_id = 1; + + cmd->id = hci->next_cmd_id++; + + cmd->callback = callback; + cmd->destroy = destroy; + cmd->user_data = user_data; + + if (!queue_push_tail(hci->cmd_queue, cmd)) { + free(cmd->data); + free(cmd); + return 0; + } + + wakeup_writer(hci); + + return cmd->id; +} + +static bool match_cmd_id(const void *a, const void *b) +{ + const struct cmd *cmd = a; + unsigned int id = PTR_TO_UINT(b); + + return cmd->id == id; +} + +bool bt_hci_cancel(struct bt_hci *hci, unsigned int id) +{ + struct cmd *cmd; + + if (!hci || !id) + return false; + + cmd = queue_remove_if(hci->cmd_queue, match_cmd_id, UINT_TO_PTR(id)); + if (!cmd) { + cmd = queue_remove_if(hci->rsp_queue, match_cmd_id, + UINT_TO_PTR(id)); + if (!cmd) + return false; + } + + cmd_free(cmd); + + wakeup_writer(hci); + + return true; +} + +bool bt_hci_flush(struct bt_hci *hci) +{ + if (!hci) + return false; + + if (hci->writer_active) { + io_set_write_handler(hci->io, NULL, NULL, NULL); + hci->writer_active = false; + } + + queue_remove_all(hci->cmd_queue, NULL, NULL, cmd_free); + queue_remove_all(hci->rsp_queue, NULL, NULL, cmd_free); + + return true; +} + +unsigned int bt_hci_register(struct bt_hci *hci, uint8_t event, + bt_hci_callback_func_t callback, + void *user_data, bt_hci_destroy_func_t destroy) +{ + struct evt *evt; + + if (!hci) + return 0; + + evt = new0(struct evt, 1); + evt->event = event; + + if (hci->next_evt_id < 1) + hci->next_evt_id = 1; + + evt->id = hci->next_evt_id++; + + evt->callback = callback; + evt->destroy = destroy; + evt->user_data = user_data; + + if (!queue_push_tail(hci->evt_list, evt)) { + free(evt); + return 0; + } + + return evt->id; +} + +static bool match_evt_id(const void *a, const void *b) +{ + const struct evt *evt = a; + unsigned int id = PTR_TO_UINT(b); + + return evt->id == id; +} + +bool bt_hci_unregister(struct bt_hci *hci, unsigned int id) +{ + struct evt *evt; + + if (!hci || !id) + return false; + + evt = queue_remove_if(hci->evt_list, match_evt_id, UINT_TO_PTR(id)); + if (!evt) + return false; + + evt_free(evt); + + return true; +} diff --git a/src/shared/hci.h b/src/shared/hci.h new file mode 100644 index 0000000..dba0f11 --- /dev/null +++ b/src/shared/hci.h @@ -0,0 +1,53 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include + +typedef void (*bt_hci_destroy_func_t)(void *user_data); + +struct bt_hci; + +struct bt_hci *bt_hci_new(int fd); +struct bt_hci *bt_hci_new_user_channel(uint16_t index); +struct bt_hci *bt_hci_new_raw_device(uint16_t index); + +struct bt_hci *bt_hci_ref(struct bt_hci *hci); +void bt_hci_unref(struct bt_hci *hci); + +bool bt_hci_set_close_on_unref(struct bt_hci *hci, bool do_close); + +typedef void (*bt_hci_callback_func_t)(const void *data, uint8_t size, + void *user_data); + +unsigned int bt_hci_send(struct bt_hci *hci, uint16_t opcode, + const void *data, uint8_t size, + bt_hci_callback_func_t callback, + void *user_data, bt_hci_destroy_func_t destroy); +bool bt_hci_cancel(struct bt_hci *hci, unsigned int id); +bool bt_hci_flush(struct bt_hci *hci); + +unsigned int bt_hci_register(struct bt_hci *hci, uint8_t event, + bt_hci_callback_func_t callback, + void *user_data, bt_hci_destroy_func_t destroy); +bool bt_hci_unregister(struct bt_hci *hci, unsigned int id); diff --git a/src/shared/hfp.c b/src/shared/hfp.c new file mode 100644 index 0000000..f4747b4 --- /dev/null +++ b/src/shared/hfp.c @@ -0,0 +1,1542 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "src/shared/util.h" +#include "src/shared/ringbuf.h" +#include "src/shared/queue.h" +#include "src/shared/io.h" +#include "src/shared/hfp.h" + +struct hfp_gw { + int ref_count; + int fd; + bool close_on_unref; + struct io *io; + struct ringbuf *read_buf; + struct ringbuf *write_buf; + struct queue *cmd_handlers; + bool writer_active; + bool result_pending; + hfp_command_func_t command_callback; + hfp_destroy_func_t command_destroy; + void *command_data; + hfp_debug_func_t debug_callback; + hfp_destroy_func_t debug_destroy; + void *debug_data; + + hfp_disconnect_func_t disconnect_callback; + hfp_destroy_func_t disconnect_destroy; + void *disconnect_data; + + bool in_disconnect; + bool destroyed; +}; + +struct hfp_hf { + int ref_count; + int fd; + bool close_on_unref; + struct io *io; + struct ringbuf *read_buf; + struct ringbuf *write_buf; + + bool writer_active; + struct queue *cmd_queue; + + struct queue *event_handlers; + + hfp_debug_func_t debug_callback; + hfp_destroy_func_t debug_destroy; + void *debug_data; + + hfp_disconnect_func_t disconnect_callback; + hfp_destroy_func_t disconnect_destroy; + void *disconnect_data; + + bool in_disconnect; + bool destroyed; +}; + +struct cmd_handler { + char *prefix; + void *user_data; + hfp_destroy_func_t destroy; + hfp_result_func_t callback; +}; + +struct hfp_context { + const char *data; + unsigned int offset; +}; + +struct cmd_response { + hfp_response_func_t resp_cb; + struct hfp_context *response; + char *resp_data; + void *user_data; +}; + +struct event_handler { + char *prefix; + void *user_data; + hfp_destroy_func_t destroy; + hfp_hf_result_func_t callback; +}; + +static void destroy_cmd_handler(void *data) +{ + struct cmd_handler *handler = data; + + if (handler->destroy) + handler->destroy(handler->user_data); + + free(handler->prefix); + + free(handler); +} + +static bool match_handler_prefix(const void *a, const void *b) +{ + const struct cmd_handler *handler = a; + const char *prefix = b; + + if (strcmp(handler->prefix, prefix) != 0) + return false; + + return true; +} + +static void write_watch_destroy(void *user_data) +{ + struct hfp_gw *hfp = user_data; + + hfp->writer_active = false; +} + +static bool can_write_data(struct io *io, void *user_data) +{ + struct hfp_gw *hfp = user_data; + ssize_t bytes_written; + + bytes_written = ringbuf_write(hfp->write_buf, hfp->fd); + if (bytes_written < 0) + return false; + + if (ringbuf_len(hfp->write_buf) > 0) + return true; + + return false; +} + +static void wakeup_writer(struct hfp_gw *hfp) +{ + if (hfp->writer_active) + return; + + if (!ringbuf_len(hfp->write_buf)) + return; + + if (!io_set_write_handler(hfp->io, can_write_data, + hfp, write_watch_destroy)) + return; + + hfp->writer_active = true; +} + +static void skip_whitespace(struct hfp_context *context) +{ + while (context->data[context->offset] == ' ') + context->offset++; +} + +static void handle_unknown_at_command(struct hfp_gw *hfp, + const char *data) +{ + if (hfp->command_callback) { + hfp->result_pending = true; + hfp->command_callback(data, hfp->command_data); + } else { + hfp_gw_send_result(hfp, HFP_RESULT_ERROR); + } +} + +static bool handle_at_command(struct hfp_gw *hfp, const char *data) +{ + struct cmd_handler *handler; + const char *separators = ";?=\0"; + struct hfp_context context; + enum hfp_gw_cmd_type type; + char lookup_prefix[18]; + uint8_t pref_len = 0; + const char *prefix; + int i; + + context.offset = 0; + context.data = data; + + skip_whitespace(&context); + + if (strlen(data + context.offset) < 3) + return false; + + if (strncmp(data + context.offset, "AT", 2)) + if (strncmp(data + context.offset, "at", 2)) + return false; + + context.offset += 2; + prefix = data + context.offset; + + if (isalpha(prefix[0])) { + lookup_prefix[pref_len++] = toupper(prefix[0]); + } else { + pref_len = strcspn(prefix, separators); + if (pref_len > 17 || pref_len < 2) + return false; + + for (i = 0; i < pref_len; i++) + lookup_prefix[i] = toupper(prefix[i]); + } + + lookup_prefix[pref_len] = '\0'; + context.offset += pref_len; + + if (lookup_prefix[0] == 'D') { + type = HFP_GW_CMD_TYPE_SET; + goto done; + } + + if (data[context.offset] == '=') { + context.offset++; + if (data[context.offset] == '?') { + context.offset++; + type = HFP_GW_CMD_TYPE_TEST; + } else { + type = HFP_GW_CMD_TYPE_SET; + } + goto done; + } + + if (data[context.offset] == '?') { + context.offset++; + type = HFP_GW_CMD_TYPE_READ; + goto done; + } + + type = HFP_GW_CMD_TYPE_COMMAND; + +done: + + handler = queue_find(hfp->cmd_handlers, match_handler_prefix, + lookup_prefix); + if (!handler) { + handle_unknown_at_command(hfp, data); + return true; + } + + hfp->result_pending = true; + handler->callback(&context, type, handler->user_data); + + return true; +} + +static void next_field(struct hfp_context *context) +{ + if (context->data[context->offset] == ',') + context->offset++; +} + +bool hfp_context_get_number_default(struct hfp_context *context, + unsigned int *val, + unsigned int default_val) +{ + skip_whitespace(context); + + if (context->data[context->offset] == ',') { + if (val) + *val = default_val; + + context->offset++; + return true; + } + + return hfp_context_get_number(context, val); +} + +bool hfp_context_get_number(struct hfp_context *context, + unsigned int *val) +{ + unsigned int i; + int tmp = 0; + + skip_whitespace(context); + + i = context->offset; + + while (context->data[i] >= '0' && context->data[i] <= '9') + tmp = tmp * 10 + context->data[i++] - '0'; + + if (i == context->offset) + return false; + + if (val) + *val = tmp; + context->offset = i; + + skip_whitespace(context); + next_field(context); + + return true; +} + +bool hfp_context_open_container(struct hfp_context *context) +{ + skip_whitespace(context); + + /* The list shall be preceded by a left parenthesis "(") */ + if (context->data[context->offset] != '(') + return false; + + context->offset++; + + return true; +} + +bool hfp_context_close_container(struct hfp_context *context) +{ + skip_whitespace(context); + + /* The list shall be followed by a right parenthesis (")" V250 5.7.3.1*/ + if (context->data[context->offset] != ')') + return false; + + context->offset++; + + next_field(context); + + return true; +} + +bool hfp_context_get_string(struct hfp_context *context, char *buf, + uint8_t len) +{ + int i = 0; + const char *data = context->data; + unsigned int offset; + + skip_whitespace(context); + + if (data[context->offset] != '"') + return false; + + offset = context->offset; + offset++; + + while (data[offset] != '\0' && data[offset] != '"') { + if (i == len) + return false; + + buf[i++] = data[offset]; + offset++; + } + + if (i == len) + return false; + + buf[i] = '\0'; + + if (data[offset] == '"') + offset++; + else + return false; + + context->offset = offset; + + skip_whitespace(context); + next_field(context); + + return true; +} + +bool hfp_context_get_unquoted_string(struct hfp_context *context, + char *buf, uint8_t len) +{ + const char *data = context->data; + unsigned int offset; + int i = 0; + char c; + + skip_whitespace(context); + + c = data[context->offset]; + if (c == '"' || c == ')' || c == '(') + return false; + + offset = context->offset; + + while (data[offset] != '\0' && data[offset] != ',' && + data[offset] != ')') { + if (i == len) + return false; + + buf[i++] = data[offset]; + offset++; + } + + if (i == len) + return false; + + buf[i] = '\0'; + + context->offset = offset; + + next_field(context); + + return true; +} + +bool hfp_context_has_next(struct hfp_context *context) +{ + return context->data[context->offset] != '\0'; +} + +void hfp_context_skip_field(struct hfp_context *context) +{ + const char *data = context->data; + unsigned int offset = context->offset; + + while (data[offset] != '\0' && data[offset] != ',') + offset++; + + context->offset = offset; + next_field(context); +} + +bool hfp_context_get_range(struct hfp_context *context, uint32_t *min, + uint32_t *max) +{ + uint32_t l, h; + uint32_t start; + + start = context->offset; + + if (!hfp_context_get_number(context, &l)) + goto failed; + + if (context->data[context->offset] != '-') + goto failed; + + context->offset++; + + if (!hfp_context_get_number(context, &h)) + goto failed; + + *min = l; + *max = h; + + next_field(context); + + return true; + +failed: + context->offset = start; + return false; +} + +static void process_input(struct hfp_gw *hfp) +{ + char *str, *ptr; + size_t len, count; + bool free_ptr = false; + bool read_again; + + do { + str = ringbuf_peek(hfp->read_buf, 0, &len); + if (!str) + return; + + ptr = memchr(str, '\r', len); + if (!ptr) { + char *str2; + size_t len2; + + /* + * If there is no more data in ringbuffer, + * it's just an incomplete command. + */ + if (len == ringbuf_len(hfp->read_buf)) + return; + + str2 = ringbuf_peek(hfp->read_buf, len, &len2); + if (!str2) + return; + + ptr = memchr(str2, '\r', len2); + if (!ptr) + return; + + *ptr = '\0'; + + count = len2 + len; + ptr = malloc(count); + if (!ptr) + return; + + memcpy(ptr, str, len); + memcpy(ptr + len, str2, len2); + + free_ptr = true; + str = ptr; + } else { + count = ptr - str; + *ptr = '\0'; + } + + if (!handle_at_command(hfp, str)) + /* + * Command is not handled that means that was some + * trash. Let's skip that and keep reading from ring + * buffer. + */ + read_again = true; + else + /* + * Command has been handled. If we are waiting for a + * result from upper layer, we can stop reading. If we + * already reply i.e. ERROR on unknown command, then we + * can keep reading ring buffer. Actually ring buffer + * should be empty but lets just look there. + */ + read_again = !hfp->result_pending; + + ringbuf_drain(hfp->read_buf, count + 1); + + if (free_ptr) + free(ptr); + + } while (read_again); +} + +static void read_watch_destroy(void *user_data) +{ +} + +static bool can_read_data(struct io *io, void *user_data) +{ + struct hfp_gw *hfp = user_data; + ssize_t bytes_read; + + bytes_read = ringbuf_read(hfp->read_buf, hfp->fd); + if (bytes_read < 0) + return false; + + if (hfp->result_pending) + return true; + + process_input(hfp); + + return true; +} + +struct hfp_gw *hfp_gw_new(int fd) +{ + struct hfp_gw *hfp; + + if (fd < 0) + return NULL; + + hfp = new0(struct hfp_gw, 1); + hfp->fd = fd; + hfp->close_on_unref = false; + + hfp->read_buf = ringbuf_new(4096); + if (!hfp->read_buf) { + free(hfp); + return NULL; + } + + hfp->write_buf = ringbuf_new(4096); + if (!hfp->write_buf) { + ringbuf_free(hfp->read_buf); + free(hfp); + return NULL; + } + + hfp->io = io_new(fd); + if (!hfp->io) { + ringbuf_free(hfp->write_buf); + ringbuf_free(hfp->read_buf); + free(hfp); + return NULL; + } + + hfp->cmd_handlers = queue_new(); + + if (!io_set_read_handler(hfp->io, can_read_data, hfp, + read_watch_destroy)) { + queue_destroy(hfp->cmd_handlers, destroy_cmd_handler); + io_destroy(hfp->io); + ringbuf_free(hfp->write_buf); + ringbuf_free(hfp->read_buf); + free(hfp); + return NULL; + } + + hfp->writer_active = false; + hfp->result_pending = false; + + return hfp_gw_ref(hfp); +} + +struct hfp_gw *hfp_gw_ref(struct hfp_gw *hfp) +{ + if (!hfp) + return NULL; + + __sync_fetch_and_add(&hfp->ref_count, 1); + + return hfp; +} + +void hfp_gw_unref(struct hfp_gw *hfp) +{ + if (!hfp) + return; + + if (__sync_sub_and_fetch(&hfp->ref_count, 1)) + return; + + hfp_gw_set_command_handler(hfp, NULL, NULL, NULL); + + io_set_write_handler(hfp->io, NULL, NULL, NULL); + io_set_read_handler(hfp->io, NULL, NULL, NULL); + io_set_disconnect_handler(hfp->io, NULL, NULL, NULL); + + io_destroy(hfp->io); + hfp->io = NULL; + + if (hfp->close_on_unref) + close(hfp->fd); + + hfp_gw_set_debug(hfp, NULL, NULL, NULL); + + ringbuf_free(hfp->read_buf); + hfp->read_buf = NULL; + + ringbuf_free(hfp->write_buf); + hfp->write_buf = NULL; + + queue_destroy(hfp->cmd_handlers, destroy_cmd_handler); + hfp->cmd_handlers = NULL; + + if (!hfp->in_disconnect) { + free(hfp); + return; + } + + hfp->destroyed = true; +} + +static void read_tracing(const void *buf, size_t count, void *user_data) +{ + struct hfp_gw *hfp = user_data; + + util_hexdump('>', buf, count, hfp->debug_callback, hfp->debug_data); +} + +static void write_tracing(const void *buf, size_t count, void *user_data) +{ + struct hfp_gw *hfp = user_data; + + util_hexdump('<', buf, count, hfp->debug_callback, hfp->debug_data); +} + +bool hfp_gw_set_debug(struct hfp_gw *hfp, hfp_debug_func_t callback, + void *user_data, hfp_destroy_func_t destroy) +{ + if (!hfp) + return false; + + if (hfp->debug_destroy) + hfp->debug_destroy(hfp->debug_data); + + hfp->debug_callback = callback; + hfp->debug_destroy = destroy; + hfp->debug_data = user_data; + + if (hfp->debug_callback) { + ringbuf_set_input_tracing(hfp->read_buf, read_tracing, hfp); + ringbuf_set_input_tracing(hfp->write_buf, write_tracing, hfp); + } else { + ringbuf_set_input_tracing(hfp->read_buf, NULL, NULL); + ringbuf_set_input_tracing(hfp->write_buf, NULL, NULL); + } + + return true; +} + +bool hfp_gw_set_close_on_unref(struct hfp_gw *hfp, bool do_close) +{ + if (!hfp) + return false; + + hfp->close_on_unref = do_close; + + return true; +} + +bool hfp_gw_send_result(struct hfp_gw *hfp, enum hfp_result result) +{ + const char *str; + + if (!hfp) + return false; + + switch (result) { + case HFP_RESULT_OK: + str = "OK"; + break; + case HFP_RESULT_ERROR: + str = "ERROR"; + break; + case HFP_RESULT_RING: + case HFP_RESULT_NO_CARRIER: + case HFP_RESULT_BUSY: + case HFP_RESULT_NO_ANSWER: + case HFP_RESULT_DELAYED: + case HFP_RESULT_BLACKLISTED: + case HFP_RESULT_CME_ERROR: + case HFP_RESULT_NO_DIALTONE: + case HFP_RESULT_CONNECT: + default: + return false; + } + + if (ringbuf_printf(hfp->write_buf, "\r\n%s\r\n", str) < 0) + return false; + + wakeup_writer(hfp); + + /* + * There might be already something to read in the ring buffer. + * If so, let's read it. + */ + if (hfp->result_pending) { + hfp->result_pending = false; + process_input(hfp); + } + + return true; +} + +bool hfp_gw_send_error(struct hfp_gw *hfp, enum hfp_error error) +{ + if (!hfp) + return false; + + if (ringbuf_printf(hfp->write_buf, "\r\n+CME ERROR: %u\r\n", error) < 0) + return false; + + wakeup_writer(hfp); + + /* + * There might be already something to read in the ring buffer. + * If so, let's read it. + */ + if (hfp->result_pending) { + hfp->result_pending = false; + process_input(hfp); + } + + return true; +} + +bool hfp_gw_send_info(struct hfp_gw *hfp, const char *format, ...) +{ + va_list ap; + char *fmt; + int len; + + if (!hfp || !format) + return false; + + if (asprintf(&fmt, "\r\n%s\r\n", format) < 0) + return false; + + va_start(ap, format); + len = ringbuf_vprintf(hfp->write_buf, fmt, ap); + va_end(ap); + + free(fmt); + + if (len < 0) + return false; + + if (hfp->result_pending) + return true; + + wakeup_writer(hfp); + + return true; +} + +bool hfp_gw_set_command_handler(struct hfp_gw *hfp, + hfp_command_func_t callback, + void *user_data, hfp_destroy_func_t destroy) +{ + if (!hfp) + return false; + + if (hfp->command_destroy) + hfp->command_destroy(hfp->command_data); + + hfp->command_callback = callback; + hfp->command_destroy = destroy; + hfp->command_data = user_data; + + return true; +} + +bool hfp_gw_register(struct hfp_gw *hfp, hfp_result_func_t callback, + const char *prefix, + void *user_data, + hfp_destroy_func_t destroy) +{ + struct cmd_handler *handler; + + handler = new0(struct cmd_handler, 1); + handler->callback = callback; + handler->user_data = user_data; + + handler->prefix = strdup(prefix); + if (!handler->prefix) { + free(handler); + return false; + } + + if (queue_find(hfp->cmd_handlers, match_handler_prefix, + handler->prefix)) { + destroy_cmd_handler(handler); + return false; + } + + handler->destroy = destroy; + + return queue_push_tail(hfp->cmd_handlers, handler); +} + +bool hfp_gw_unregister(struct hfp_gw *hfp, const char *prefix) +{ + struct cmd_handler *handler; + char *lookup_prefix; + + lookup_prefix = strdup(prefix); + if (!lookup_prefix) + return false; + + handler = queue_remove_if(hfp->cmd_handlers, match_handler_prefix, + lookup_prefix); + free(lookup_prefix); + + if (!handler) + return false; + + destroy_cmd_handler(handler); + + return true; +} + +static void disconnect_watch_destroy(void *user_data) +{ + struct hfp_gw *hfp = user_data; + + if (hfp->disconnect_destroy) + hfp->disconnect_destroy(hfp->disconnect_data); + + if (hfp->destroyed) + free(hfp); +} + +static bool io_disconnected(struct io *io, void *user_data) +{ + struct hfp_gw *hfp = user_data; + + hfp->in_disconnect = true; + + if (hfp->disconnect_callback) + hfp->disconnect_callback(hfp->disconnect_data); + + hfp->in_disconnect = false; + + return false; +} + +bool hfp_gw_set_disconnect_handler(struct hfp_gw *hfp, + hfp_disconnect_func_t callback, + void *user_data, + hfp_destroy_func_t destroy) +{ + if (!hfp) + return false; + + if (hfp->disconnect_destroy) + hfp->disconnect_destroy(hfp->disconnect_data); + + if (!io_set_disconnect_handler(hfp->io, io_disconnected, hfp, + disconnect_watch_destroy)) { + hfp->disconnect_callback = NULL; + hfp->disconnect_destroy = NULL; + hfp->disconnect_data = NULL; + return false; + } + + hfp->disconnect_callback = callback; + hfp->disconnect_destroy = destroy; + hfp->disconnect_data = user_data; + + return true; +} + +bool hfp_gw_disconnect(struct hfp_gw *hfp) +{ + if (!hfp) + return false; + + return io_shutdown(hfp->io); +} + +static bool match_handler_event_prefix(const void *a, const void *b) +{ + const struct event_handler *handler = a; + const char *prefix = b; + + if (strcmp(handler->prefix, prefix) != 0) + return false; + + return true; +} + +static void destroy_event_handler(void *data) +{ + struct event_handler *handler = data; + + if (handler->destroy) + handler->destroy(handler->user_data); + + free(handler->prefix); + + free(handler); +} + +static bool hf_can_write_data(struct io *io, void *user_data) +{ + struct hfp_hf *hfp = user_data; + ssize_t bytes_written; + + bytes_written = ringbuf_write(hfp->write_buf, hfp->fd); + if (bytes_written < 0) + return false; + + if (ringbuf_len(hfp->write_buf) > 0) + return true; + + return false; +} + +static void hf_write_watch_destroy(void *user_data) +{ + struct hfp_hf *hfp = user_data; + + hfp->writer_active = false; +} + +static void hf_skip_whitespace(struct hfp_context *context) +{ + while (context->data[context->offset] == ' ') + context->offset++; +} + +static bool is_response(const char *prefix, enum hfp_result *result, + enum hfp_error *cme_err, + struct hfp_context *context) +{ + if (strcmp(prefix, "OK") == 0) { + *result = HFP_RESULT_OK; + /* + * Set cme_err to 0 as this is not valid when result is not + * CME ERROR + */ + *cme_err = 0; + return true; + } + + if (strcmp(prefix, "ERROR") == 0) { + *result = HFP_RESULT_ERROR; + *cme_err = 0; + return true; + } + + if (strcmp(prefix, "NO CARRIER") == 0) { + *result = HFP_RESULT_NO_CARRIER; + *cme_err = 0; + return true; + } + + if (strcmp(prefix, "NO ANSWER") == 0) { + *result = HFP_RESULT_NO_ANSWER; + *cme_err = 0; + return true; + } + + if (strcmp(prefix, "BUSY") == 0) { + *result = HFP_RESULT_BUSY; + *cme_err = 0; + return true; + } + + if (strcmp(prefix, "DELAYED") == 0) { + *result = HFP_RESULT_DELAYED; + *cme_err = 0; + return true; + } + + if (strcmp(prefix, "BLACKLISTED") == 0) { + *result = HFP_RESULT_BLACKLISTED; + *cme_err = 0; + return true; + } + + if (strcmp(prefix, "+CME ERROR") == 0) { + uint32_t val; + + *result = HFP_RESULT_CME_ERROR; + + if (hfp_context_get_number(context, &val) && + val <= HFP_ERROR_NETWORK_NOT_ALLOWED) + *cme_err = val; + else + *cme_err = HFP_ERROR_AG_FAILURE; + + return true; + } + + return false; +} + +static void hf_wakeup_writer(struct hfp_hf *hfp) +{ + if (hfp->writer_active) + return; + + if (!ringbuf_len(hfp->write_buf)) + return; + + if (!io_set_write_handler(hfp->io, hf_can_write_data, + hfp, hf_write_watch_destroy)) + return; + + hfp->writer_active = true; +} + +static void hf_call_prefix_handler(struct hfp_hf *hfp, const char *data) +{ + struct event_handler *handler; + const char *separators = ";:\0"; + struct hfp_context context; + enum hfp_result result; + enum hfp_error cme_err; + char lookup_prefix[18] = {}; + uint8_t pref_len = 0; + const char *prefix; + int i; + + context.offset = 0; + context.data = data; + + hf_skip_whitespace(&context); + + if (strlen(data + context.offset) < 2) + return; + + prefix = data + context.offset; + + pref_len = strcspn(prefix, separators); + if (pref_len > 17 || pref_len < 2) + return; + + for (i = 0; i < pref_len; i++) + lookup_prefix[i] = toupper(prefix[i]); + + lookup_prefix[pref_len] = '\0'; + context.offset += pref_len + 1; + + if (is_response(lookup_prefix, &result, &cme_err, &context)) { + struct cmd_response *cmd; + + cmd = queue_peek_head(hfp->cmd_queue); + if (!cmd) + return; + + cmd->resp_cb(result, cme_err, cmd->user_data); + + queue_remove(hfp->cmd_queue, cmd); + free(cmd); + + hf_wakeup_writer(hfp); + return; + } + + handler = queue_find(hfp->event_handlers, match_handler_event_prefix, + lookup_prefix); + if (!handler) + return; + + handler->callback(&context, handler->user_data); +} + +static char *find_cr_lf(char *str, size_t len) +{ + char *ptr; + size_t count, offset; + + offset = 0; + + ptr = memchr(str, '\r', len); + while (ptr) { + /* + * Check if there is more data after '\r'. If so check for + * '\n' + */ + count = ptr - str; + if ((count < (len - 1)) && *(ptr + 1) == '\n') + return ptr; + + /* There is only '\r'? Let's try to find next one */ + offset += count + 1; + + if (offset >= len) + return NULL; + + ptr = memchr(str + offset, '\r', len - offset); + } + + return NULL; +} + +static void hf_process_input(struct hfp_hf *hfp) +{ + char *str, *ptr, *str2, *tmp; + size_t len, count, offset, len2; + bool free_tmp = false; + + str = ringbuf_peek(hfp->read_buf, 0, &len); + if (!str) + return; + + offset = 0; + + ptr = find_cr_lf(str, len); + while (ptr) { + count = ptr - (str + offset); + if (count == 0) { + /* 2 is for */ + offset += 2; + } else { + *ptr = '\0'; + hf_call_prefix_handler(hfp, str + offset); + offset += count + 2; + } + + ptr = find_cr_lf(str + offset, len - offset); + } + + /* + * Just check if there is no wrapped data in ring buffer. + * Should not happen too often + */ + if (len == ringbuf_len(hfp->read_buf)) + goto done; + + str2 = ringbuf_peek(hfp->read_buf, len, &len2); + if (!str2) + goto done; + + ptr = find_cr_lf(str2, len2); + if (!ptr) { + /* Might happen that we wrap between \r and \n */ + ptr = memchr(str2, '\n', len2); + if (!ptr) + goto done; + } + + count = ptr - str2; + + if (count) { + *ptr = '\0'; + + tmp = malloc(len + count); + if (!tmp) + goto done; + + /* "str" here is not a string so we need to use memcpy */ + memcpy(tmp, str, len); + memcpy(tmp + len, str2, count); + + free_tmp = true; + } else { + str[len-1] = '\0'; + tmp = str; + } + + hf_call_prefix_handler(hfp, tmp); + offset += count; + +done: + ringbuf_drain(hfp->read_buf, offset); + + if (free_tmp) + free(tmp); +} + +static bool hf_can_read_data(struct io *io, void *user_data) +{ + struct hfp_hf *hfp = user_data; + ssize_t bytes_read; + + bytes_read = ringbuf_read(hfp->read_buf, hfp->fd); + if (bytes_read < 0) + return false; + + hf_process_input(hfp); + + return true; +} + +struct hfp_hf *hfp_hf_new(int fd) +{ + struct hfp_hf *hfp; + + if (fd < 0) + return NULL; + + hfp = new0(struct hfp_hf, 1); + hfp->fd = fd; + hfp->close_on_unref = false; + + hfp->read_buf = ringbuf_new(4096); + if (!hfp->read_buf) { + free(hfp); + return NULL; + } + + hfp->write_buf = ringbuf_new(4096); + if (!hfp->write_buf) { + ringbuf_free(hfp->read_buf); + free(hfp); + return NULL; + } + + hfp->io = io_new(fd); + if (!hfp->io) { + ringbuf_free(hfp->write_buf); + ringbuf_free(hfp->read_buf); + free(hfp); + return NULL; + } + + hfp->event_handlers = queue_new(); + hfp->cmd_queue = queue_new(); + hfp->writer_active = false; + + if (!io_set_read_handler(hfp->io, hf_can_read_data, hfp, + read_watch_destroy)) { + queue_destroy(hfp->event_handlers, + destroy_event_handler); + io_destroy(hfp->io); + ringbuf_free(hfp->write_buf); + ringbuf_free(hfp->read_buf); + free(hfp); + return NULL; + } + + return hfp_hf_ref(hfp); +} + +struct hfp_hf *hfp_hf_ref(struct hfp_hf *hfp) +{ + if (!hfp) + return NULL; + + __sync_fetch_and_add(&hfp->ref_count, 1); + + return hfp; +} + +void hfp_hf_unref(struct hfp_hf *hfp) +{ + if (!hfp) + return; + + if (__sync_sub_and_fetch(&hfp->ref_count, 1)) + return; + + io_set_write_handler(hfp->io, NULL, NULL, NULL); + io_set_read_handler(hfp->io, NULL, NULL, NULL); + io_set_disconnect_handler(hfp->io, NULL, NULL, NULL); + + io_destroy(hfp->io); + hfp->io = NULL; + + if (hfp->close_on_unref) + close(hfp->fd); + + hfp_hf_set_debug(hfp, NULL, NULL, NULL); + + ringbuf_free(hfp->read_buf); + hfp->read_buf = NULL; + + ringbuf_free(hfp->write_buf); + hfp->write_buf = NULL; + + queue_destroy(hfp->event_handlers, destroy_event_handler); + hfp->event_handlers = NULL; + + queue_destroy(hfp->cmd_queue, free); + hfp->cmd_queue = NULL; + + if (!hfp->in_disconnect) { + free(hfp); + return; + } + + hfp->destroyed = true; +} + +static void hf_read_tracing(const void *buf, size_t count, + void *user_data) +{ + struct hfp_hf *hfp = user_data; + + util_hexdump('>', buf, count, hfp->debug_callback, hfp->debug_data); +} + +static void hf_write_tracing(const void *buf, size_t count, + void *user_data) +{ + struct hfp_hf *hfp = user_data; + + util_hexdump('<', buf, count, hfp->debug_callback, hfp->debug_data); +} + +bool hfp_hf_set_debug(struct hfp_hf *hfp, hfp_debug_func_t callback, + void *user_data, hfp_destroy_func_t destroy) +{ + if (!hfp) + return false; + + if (hfp->debug_destroy) + hfp->debug_destroy(hfp->debug_data); + + hfp->debug_callback = callback; + hfp->debug_destroy = destroy; + hfp->debug_data = user_data; + + if (hfp->debug_callback) { + ringbuf_set_input_tracing(hfp->read_buf, hf_read_tracing, hfp); + ringbuf_set_input_tracing(hfp->write_buf, hf_write_tracing, + hfp); + } else { + ringbuf_set_input_tracing(hfp->read_buf, NULL, NULL); + ringbuf_set_input_tracing(hfp->write_buf, NULL, NULL); + } + + return true; +} + +bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close) +{ + if (!hfp) + return false; + + hfp->close_on_unref = do_close; + + return true; +} + +bool hfp_hf_send_command(struct hfp_hf *hfp, hfp_response_func_t resp_cb, + void *user_data, const char *format, ...) +{ + va_list ap; + char *fmt; + int len; + struct cmd_response *cmd; + + if (!hfp || !format || !resp_cb) + return false; + + if (asprintf(&fmt, "%s\r", format) < 0) + return false; + + cmd = new0(struct cmd_response, 1); + + va_start(ap, format); + len = ringbuf_vprintf(hfp->write_buf, fmt, ap); + va_end(ap); + + free(fmt); + + if (len < 0) { + free(cmd); + return false; + } + + cmd->resp_cb = resp_cb; + cmd->user_data = user_data; + + if (!queue_push_tail(hfp->cmd_queue, cmd)) { + ringbuf_drain(hfp->write_buf, len); + free(cmd); + return false; + } + + hf_wakeup_writer(hfp); + + return true; +} + +bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback, + const char *prefix, + void *user_data, + hfp_destroy_func_t destroy) +{ + struct event_handler *handler; + + if (!callback) + return false; + + handler = new0(struct event_handler, 1); + handler->callback = callback; + handler->user_data = user_data; + + handler->prefix = strdup(prefix); + if (!handler->prefix) { + free(handler); + return false; + } + + if (queue_find(hfp->event_handlers, match_handler_event_prefix, + handler->prefix)) { + destroy_event_handler(handler); + return false; + } + + handler->destroy = destroy; + + return queue_push_tail(hfp->event_handlers, handler); +} + +bool hfp_hf_unregister(struct hfp_hf *hfp, const char *prefix) +{ + struct cmd_handler *handler; + + /* Cast to void as queue_remove needs that */ + handler = queue_remove_if(hfp->event_handlers, + match_handler_event_prefix, + (void *) prefix); + + if (!handler) + return false; + + destroy_event_handler(handler); + + return true; +} + +static void hf_disconnect_watch_destroy(void *user_data) +{ + struct hfp_hf *hfp = user_data; + + if (hfp->disconnect_destroy) + hfp->disconnect_destroy(hfp->disconnect_data); + + if (hfp->destroyed) + free(hfp); +} + +static bool hf_io_disconnected(struct io *io, void *user_data) +{ + struct hfp_hf *hfp = user_data; + + hfp->in_disconnect = true; + + if (hfp->disconnect_callback) + hfp->disconnect_callback(hfp->disconnect_data); + + hfp->in_disconnect = false; + + return false; +} + +bool hfp_hf_set_disconnect_handler(struct hfp_hf *hfp, + hfp_disconnect_func_t callback, + void *user_data, + hfp_destroy_func_t destroy) +{ + if (!hfp) + return false; + + if (hfp->disconnect_destroy) + hfp->disconnect_destroy(hfp->disconnect_data); + + if (!io_set_disconnect_handler(hfp->io, hf_io_disconnected, hfp, + hf_disconnect_watch_destroy)) { + hfp->disconnect_callback = NULL; + hfp->disconnect_destroy = NULL; + hfp->disconnect_data = NULL; + return false; + } + + hfp->disconnect_callback = callback; + hfp->disconnect_destroy = destroy; + hfp->disconnect_data = user_data; + + return true; +} + +bool hfp_hf_disconnect(struct hfp_hf *hfp) +{ + if (!hfp) + return false; + + return io_shutdown(hfp->io); +} diff --git a/src/shared/hfp.h b/src/shared/hfp.h new file mode 100644 index 0000000..2eb7838 --- /dev/null +++ b/src/shared/hfp.h @@ -0,0 +1,161 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include + +enum hfp_result { + HFP_RESULT_OK = 0, + HFP_RESULT_CONNECT = 1, + HFP_RESULT_RING = 2, + HFP_RESULT_NO_CARRIER = 3, + HFP_RESULT_ERROR = 4, + HFP_RESULT_NO_DIALTONE = 6, + HFP_RESULT_BUSY = 7, + HFP_RESULT_NO_ANSWER = 8, + HFP_RESULT_DELAYED = 9, + HFP_RESULT_BLACKLISTED = 10, + HFP_RESULT_CME_ERROR = 11, +}; + +enum hfp_error { + HFP_ERROR_AG_FAILURE = 0, + HFP_ERROR_NO_CONNECTION_TO_PHONE = 1, + HFP_ERROR_OPERATION_NOT_ALLOWED = 3, + HFP_ERROR_OPERATION_NOT_SUPPORTED = 4, + HFP_ERROR_PH_SIM_PIN_REQUIRED = 5, + HFP_ERROR_SIM_NOT_INSERTED = 10, + HFP_ERROR_SIM_PIN_REQUIRED = 11, + HFP_ERROR_SIM_PUK_REQUIRED = 12, + HFP_ERROR_SIM_FAILURE = 13, + HFP_ERROR_SIM_BUSY = 14, + HFP_ERROR_INCORRECT_PASSWORD = 16, + HFP_ERROR_SIM_PIN2_REQUIRED = 17, + HFP_ERROR_SIM_PUK2_REQUIRED = 18, + HFP_ERROR_MEMORY_FULL = 20, + HFP_ERROR_INVALID_INDEX = 21, + HFP_ERROR_MEMORY_FAILURE = 23, + HFP_ERROR_TEXT_STRING_TOO_LONG = 24, + HFP_ERROR_INVALID_CHARS_IN_TEXT_STRING = 25, + HFP_ERROR_DIAL_STRING_TO_LONG = 26, + HFP_ERROR_INVALID_CHARS_IN_DIAL_STRING = 27, + HFP_ERROR_NO_NETWORK_SERVICE = 30, + HFP_ERROR_NETWORK_TIMEOUT = 31, + HFP_ERROR_NETWORK_NOT_ALLOWED = 32, +}; + +enum hfp_gw_cmd_type { + HFP_GW_CMD_TYPE_READ, + HFP_GW_CMD_TYPE_SET, + HFP_GW_CMD_TYPE_TEST, + HFP_GW_CMD_TYPE_COMMAND +}; + +struct hfp_context; + +typedef void (*hfp_result_func_t)(struct hfp_context *context, + enum hfp_gw_cmd_type type, void *user_data); + +typedef void (*hfp_destroy_func_t)(void *user_data); +typedef void (*hfp_debug_func_t)(const char *str, void *user_data); + +typedef void (*hfp_command_func_t)(const char *command, void *user_data); +typedef void (*hfp_disconnect_func_t)(void *user_data); + +struct hfp_gw; + +struct hfp_gw *hfp_gw_new(int fd); + +struct hfp_gw *hfp_gw_ref(struct hfp_gw *hfp); +void hfp_gw_unref(struct hfp_gw *hfp); + +bool hfp_gw_set_debug(struct hfp_gw *hfp, hfp_debug_func_t callback, + void *user_data, hfp_destroy_func_t destroy); + +bool hfp_gw_set_close_on_unref(struct hfp_gw *hfp, bool do_close); +bool hfp_gw_set_permissive_syntax(struct hfp_gw *hfp, bool permissive); + +bool hfp_gw_send_result(struct hfp_gw *hfp, enum hfp_result result); +bool hfp_gw_send_error(struct hfp_gw *hfp, enum hfp_error error); +bool hfp_gw_send_info(struct hfp_gw *hfp, const char *format, ...) + __attribute__((format(printf, 2, 3))); + +bool hfp_gw_set_command_handler(struct hfp_gw *hfp, + hfp_command_func_t callback, + void *user_data, hfp_destroy_func_t destroy); + +bool hfp_gw_set_disconnect_handler(struct hfp_gw *hfp, + hfp_disconnect_func_t callback, + void *user_data, + hfp_destroy_func_t destroy); + +bool hfp_gw_disconnect(struct hfp_gw *hfp); + +bool hfp_gw_register(struct hfp_gw *hfp, hfp_result_func_t callback, + const char *prefix, + void *user_data, + hfp_destroy_func_t destroy); +bool hfp_gw_unregister(struct hfp_gw *hfp, const char *prefix); + +bool hfp_context_get_number(struct hfp_context *context, + unsigned int *val); +bool hfp_context_get_number_default(struct hfp_context *context, + unsigned int *val, + unsigned int default_val); +bool hfp_context_open_container(struct hfp_context *context); +bool hfp_context_close_container(struct hfp_context *context); +bool hfp_context_get_string(struct hfp_context *context, char *buf, + uint8_t len); +bool hfp_context_get_unquoted_string(struct hfp_context *context, + char *buf, uint8_t len); +bool hfp_context_get_range(struct hfp_context *context, unsigned int *min, + unsigned int *max); +bool hfp_context_has_next(struct hfp_context *context); +void hfp_context_skip_field(struct hfp_context *context); + +typedef void (*hfp_hf_result_func_t)(struct hfp_context *context, + void *user_data); + +typedef void (*hfp_response_func_t)(enum hfp_result result, + enum hfp_error cme_err, + void *user_data); + +struct hfp_hf; + +struct hfp_hf *hfp_hf_new(int fd); + +struct hfp_hf *hfp_hf_ref(struct hfp_hf *hfp); +void hfp_hf_unref(struct hfp_hf *hfp); +bool hfp_hf_set_debug(struct hfp_hf *hfp, hfp_debug_func_t callback, + void *user_data, hfp_destroy_func_t destroy); +bool hfp_hf_set_close_on_unref(struct hfp_hf *hfp, bool do_close); +bool hfp_hf_set_disconnect_handler(struct hfp_hf *hfp, + hfp_disconnect_func_t callback, + void *user_data, + hfp_destroy_func_t destroy); +bool hfp_hf_disconnect(struct hfp_hf *hfp); +bool hfp_hf_register(struct hfp_hf *hfp, hfp_hf_result_func_t callback, + const char *prefix, void *user_data, + hfp_destroy_func_t destroy); +bool hfp_hf_unregister(struct hfp_hf *hfp, const char *prefix); +bool hfp_hf_send_command(struct hfp_hf *hfp, hfp_response_func_t resp_cb, + void *user_data, const char *format, ...); diff --git a/src/shared/io-ell.c b/src/shared/io-ell.c new file mode 100644 index 0000000..6748982 --- /dev/null +++ b/src/shared/io-ell.c @@ -0,0 +1,154 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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 +#endif + +#include +#include +#include + +#include + +#include "src/shared/io.h" + +struct io { + struct l_io *l_io; +}; + +struct io *io_new(int fd) +{ + struct io *io; + struct l_io *l_io; + + if (fd < 0) + return NULL; + + io = l_new(struct io, 1); + if (!io) + return NULL; + + l_io = l_io_new(fd); + if (!l_io) { + l_free(io); + return NULL; + } + + io->l_io = l_io; + + return io; +} + +void io_destroy(struct io *io) +{ + if (!io) + return; + + if (io->l_io) + l_io_destroy(io->l_io); + + l_free(io); +} + +int io_get_fd(struct io *io) +{ + if (!io || !io->l_io) + return -ENOTCONN; + + return l_io_get_fd(io->l_io); +} + +bool io_set_close_on_destroy(struct io *io, bool do_close) +{ + if (!io || !io->l_io) + return false; + + return l_io_set_close_on_destroy(io->l_io, do_close); +} + +bool io_set_read_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy) +{ + if (!io || !io->l_io) + return false; + + return l_io_set_read_handler(io->l_io, (l_io_read_cb_t) callback, + user_data, destroy); +} + +bool io_set_write_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy) +{ + if (!io || !io->l_io) + return false; + + return l_io_set_write_handler(io->l_io, (l_io_write_cb_t) callback, + user_data, destroy); +} + +bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy) +{ + if (!io || !io->l_io) + return false; + + return l_io_set_disconnect_handler(io->l_io, (void *) callback, + user_data, destroy); +} + +ssize_t io_send(struct io *io, const struct iovec *iov, int iovcnt) +{ + ssize_t ret; + int fd; + + if (!io || !io->l_io) + return -ENOTCONN; + + fd = l_io_get_fd(io->l_io); + if (fd < 0) + return -ENOTCONN; + + do { + ret = writev(fd, iov, iovcnt); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) + return -errno; + + return ret; +} + +bool io_shutdown(struct io *io) +{ + int fd; + + if (!io || !io->l_io) + return false; + + fd = l_io_get_fd(io->l_io); + if (fd < 0) + return false; + + return shutdown(fd, SHUT_RDWR) == 0; +} diff --git a/src/shared/io-glib.c b/src/shared/io-glib.c new file mode 100644 index 0000000..d62de4e --- /dev/null +++ b/src/shared/io-glib.c @@ -0,0 +1,293 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#include + +#include + +#include "src/shared/io.h" + +struct io_watch { + struct io *io; + guint id; + io_callback_func_t callback; + io_destroy_func_t destroy; + void *user_data; +}; + +struct io { + int ref_count; + GIOChannel *channel; + struct io_watch *read_watch; + struct io_watch *write_watch; + struct io_watch *disconnect_watch; +}; + +static struct io *io_ref(struct io *io) +{ + if (!io) + return NULL; + + __sync_fetch_and_add(&io->ref_count, 1); + + return io; +} + +static void io_unref(struct io *io) +{ + if (!io) + return; + + if (__sync_sub_and_fetch(&io->ref_count, 1)) + return; + + g_free(io); +} + +struct io *io_new(int fd) +{ + struct io *io; + + if (fd < 0) + return NULL; + + io = g_try_new0(struct io, 1); + if (!io) + return NULL; + + io->channel = g_io_channel_unix_new(fd); + + g_io_channel_set_encoding(io->channel, NULL, NULL); + g_io_channel_set_buffered(io->channel, FALSE); + + g_io_channel_set_close_on_unref(io->channel, FALSE); + + return io_ref(io); +} + +static void watch_destroy(void *user_data) +{ + struct io_watch *watch = user_data; + struct io *io = watch->io; + + if (watch == io->read_watch) + io->read_watch = NULL; + else if (watch == io->write_watch) + io->write_watch = NULL; + else if (watch == io->disconnect_watch) + io->disconnect_watch = NULL; + + if (watch->destroy) + watch->destroy(watch->user_data); + + io_unref(watch->io); + g_free(watch); +} + +void io_destroy(struct io *io) +{ + if (!io) + return; + + if (io->read_watch) { + g_source_remove(io->read_watch->id); + io->read_watch = NULL; + } + + if (io->write_watch) { + g_source_remove(io->write_watch->id); + io->write_watch = NULL; + } + + if (io->disconnect_watch) { + g_source_remove(io->disconnect_watch->id); + io->disconnect_watch = NULL; + } + + g_io_channel_unref(io->channel); + io->channel = NULL; + + io_unref(io); +} + +int io_get_fd(struct io *io) +{ + if (!io) + return -ENOTCONN; + + return g_io_channel_unix_get_fd(io->channel); +} + +bool io_set_close_on_destroy(struct io *io, bool do_close) +{ + if (!io) + return false; + + if (do_close) + g_io_channel_set_close_on_unref(io->channel, TRUE); + else + g_io_channel_set_close_on_unref(io->channel, FALSE); + + return true; +} + +static gboolean watch_callback(GIOChannel *channel, GIOCondition cond, + gpointer user_data) +{ + struct io_watch *watch = user_data; + bool result, destroy; + + destroy = watch == watch->io->disconnect_watch; + + if (!destroy && (cond & (G_IO_ERR | G_IO_NVAL))) + return FALSE; + + if (watch->callback) + result = watch->callback(watch->io, watch->user_data); + else + result = false; + + return result ? TRUE : FALSE; +} + +static struct io_watch *watch_new(struct io *io, GIOCondition cond, + io_callback_func_t callback, void *user_data, + io_destroy_func_t destroy) +{ + struct io_watch *watch; + int prio; + + watch = g_try_new0(struct io_watch, 1); + if (!watch) + return NULL; + + watch->io = io_ref(io); + watch->callback = callback; + watch->destroy = destroy; + watch->user_data = user_data; + + prio = cond == G_IO_HUP ? G_PRIORITY_DEFAULT_IDLE : G_PRIORITY_DEFAULT; + + watch->id = g_io_add_watch_full(io->channel, prio, + cond | G_IO_ERR | G_IO_NVAL, + watch_callback, watch, + watch_destroy); + if (watch->id == 0) { + watch_destroy(watch); + return NULL; + } + + return watch; +} + +static bool io_set_handler(struct io *io, GIOCondition cond, + io_callback_func_t callback, void *user_data, + io_destroy_func_t destroy) +{ + struct io_watch **watch; + + if (!io) + return false; + + switch (cond) { + case G_IO_IN: + watch = &io->read_watch; + break; + case G_IO_OUT: + watch = &io->write_watch; + break; + case G_IO_HUP: + watch = &io->disconnect_watch; + break; + case G_IO_PRI: + case G_IO_ERR: + case G_IO_NVAL: + default: + return false; + } + + if (*watch) { + g_source_remove((*watch)->id); + *watch = NULL; + } + + if (!callback) + return true; + + *watch = watch_new(io, cond, callback, user_data, destroy); + if (!*watch) + return false; + + return true; +} + +bool io_set_read_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy) +{ + return io_set_handler(io, G_IO_IN, callback, user_data, destroy); +} + +bool io_set_write_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy) +{ + return io_set_handler(io, G_IO_OUT, callback, user_data, destroy); +} + +bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy) +{ + return io_set_handler(io, G_IO_HUP, callback, user_data, destroy); +} + +ssize_t io_send(struct io *io, const struct iovec *iov, int iovcnt) +{ + int fd; + ssize_t ret; + + if (!io || !io->channel) + return -ENOTCONN; + + fd = io_get_fd(io); + + do { + ret = writev(fd, iov, iovcnt); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) + return -errno; + + return ret; +} + +bool io_shutdown(struct io *io) +{ + if (!io || !io->channel) + return false; + + return g_io_channel_shutdown(io->channel, TRUE, NULL) + == G_IO_STATUS_NORMAL; +} diff --git a/src/shared/io-mainloop.c b/src/shared/io-mainloop.c new file mode 100644 index 0000000..2306c34 --- /dev/null +++ b/src/shared/io-mainloop.c @@ -0,0 +1,324 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#include +#include +#include + +#include "src/shared/mainloop.h" +#include "src/shared/util.h" +#include "src/shared/io.h" + +struct io { + int ref_count; + int fd; + uint32_t events; + bool close_on_destroy; + io_callback_func_t read_callback; + io_destroy_func_t read_destroy; + void *read_data; + io_callback_func_t write_callback; + io_destroy_func_t write_destroy; + void *write_data; + io_callback_func_t disconnect_callback; + io_destroy_func_t disconnect_destroy; + void *disconnect_data; +}; + +static struct io *io_ref(struct io *io) +{ + if (!io) + return NULL; + + __sync_fetch_and_add(&io->ref_count, 1); + + return io; +} + +static void io_unref(struct io *io) +{ + if (!io) + return; + + if (__sync_sub_and_fetch(&io->ref_count, 1)) + return; + + free(io); +} + +static void io_cleanup(void *user_data) +{ + struct io *io = user_data; + + if (io->write_destroy) + io->write_destroy(io->write_data); + + if (io->read_destroy) + io->read_destroy(io->read_data); + + if (io->disconnect_destroy) + io->disconnect_destroy(io->disconnect_data); + + if (io->close_on_destroy) + close(io->fd); + + io->fd = -1; +} + +static void io_callback(int fd, uint32_t events, void *user_data) +{ + struct io *io = user_data; + + io_ref(io); + + if ((events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))) { + io->read_callback = NULL; + io->write_callback = NULL; + + if (!io->disconnect_callback) { + mainloop_remove_fd(io->fd); + io_unref(io); + return; + } + + if (!io->disconnect_callback(io, io->disconnect_data)) { + if (io->disconnect_destroy) + io->disconnect_destroy(io->disconnect_data); + + io->disconnect_callback = NULL; + io->disconnect_destroy = NULL; + io->disconnect_data = NULL; + + io->events &= ~EPOLLRDHUP; + + mainloop_modify_fd(io->fd, io->events); + } + } + + if ((events & EPOLLIN) && io->read_callback) { + if (!io->read_callback(io, io->read_data)) { + if (io->read_destroy) + io->read_destroy(io->read_data); + + io->read_callback = NULL; + io->read_destroy = NULL; + io->read_data = NULL; + + io->events &= ~EPOLLIN; + + mainloop_modify_fd(io->fd, io->events); + } + } + + if ((events & EPOLLOUT) && io->write_callback) { + if (!io->write_callback(io, io->write_data)) { + if (io->write_destroy) + io->write_destroy(io->write_data); + + io->write_callback = NULL; + io->write_destroy = NULL; + io->write_data = NULL; + + io->events &= ~EPOLLOUT; + + mainloop_modify_fd(io->fd, io->events); + } + } + + io_unref(io); +} + +struct io *io_new(int fd) +{ + struct io *io; + + if (fd < 0) + return NULL; + + io = new0(struct io, 1); + io->fd = fd; + io->events = 0; + io->close_on_destroy = false; + + if (mainloop_add_fd(io->fd, io->events, io_callback, + io, io_cleanup) < 0) { + free(io); + return NULL; + } + + return io_ref(io); +} + +void io_destroy(struct io *io) +{ + if (!io) + return; + + io->read_callback = NULL; + io->write_callback = NULL; + io->disconnect_callback = NULL; + + mainloop_remove_fd(io->fd); + + io_unref(io); +} + +int io_get_fd(struct io *io) +{ + if (!io) + return -ENOTCONN; + + return io->fd; +} + +bool io_set_close_on_destroy(struct io *io, bool do_close) +{ + if (!io) + return false; + + io->close_on_destroy = do_close; + + return true; +} + +bool io_set_read_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy) +{ + uint32_t events; + + if (!io || io->fd < 0) + return false; + + if (io->read_destroy) + io->read_destroy(io->read_data); + + if (callback) + events = io->events | EPOLLIN; + else + events = io->events & ~EPOLLIN; + + io->read_callback = callback; + io->read_destroy = destroy; + io->read_data = user_data; + + if (events == io->events) + return true; + + if (mainloop_modify_fd(io->fd, events) < 0) + return false; + + io->events = events; + + return true; +} + +bool io_set_write_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy) +{ + uint32_t events; + + if (!io || io->fd < 0) + return false; + + if (io->write_destroy) + io->write_destroy(io->write_data); + + if (callback) + events = io->events | EPOLLOUT; + else + events = io->events & ~EPOLLOUT; + + io->write_callback = callback; + io->write_destroy = destroy; + io->write_data = user_data; + + if (events == io->events) + return true; + + if (mainloop_modify_fd(io->fd, events) < 0) + return false; + + io->events = events; + + return true; +} + +bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy) +{ + uint32_t events; + + if (!io || io->fd < 0) + return false; + + if (io->disconnect_destroy) + io->disconnect_destroy(io->disconnect_data); + + if (callback) + events = io->events | EPOLLRDHUP; + else + events = io->events & ~EPOLLRDHUP; + + io->disconnect_callback = callback; + io->disconnect_destroy = destroy; + io->disconnect_data = user_data; + + if (events == io->events) + return true; + + if (mainloop_modify_fd(io->fd, events) < 0) + return false; + + io->events = events; + + return true; +} + +ssize_t io_send(struct io *io, const struct iovec *iov, int iovcnt) +{ + ssize_t ret; + + if (!io || io->fd < 0) + return -ENOTCONN; + + do { + ret = writev(io->fd, iov, iovcnt); + } while (ret < 0 && errno == EINTR); + + if (ret < 0) + return -errno; + + return ret; +} + +bool io_shutdown(struct io *io) +{ + if (!io || io->fd < 0) + return false; + + return shutdown(io->fd, SHUT_RDWR) == 0; +} diff --git a/src/shared/io.h b/src/shared/io.h new file mode 100644 index 0000000..8bc1111 --- /dev/null +++ b/src/shared/io.h @@ -0,0 +1,47 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include + +typedef void (*io_destroy_func_t)(void *data); + +struct io; + +struct io *io_new(int fd); +void io_destroy(struct io *io); + +int io_get_fd(struct io *io); +bool io_set_close_on_destroy(struct io *io, bool do_close); + +ssize_t io_send(struct io *io, const struct iovec *iov, int iovcnt); +bool io_shutdown(struct io *io); + +typedef bool (*io_callback_func_t)(struct io *io, void *user_data); + +bool io_set_read_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy); +bool io_set_write_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy); +bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback, + void *user_data, io_destroy_func_t destroy); diff --git a/src/shared/log.c b/src/shared/log.c new file mode 100644 index 0000000..7c8b712 --- /dev/null +++ b/src/shared/log.c @@ -0,0 +1,192 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/util.h" +#include "src/shared/log.h" + +struct log_hdr { + uint16_t opcode; + uint16_t index; + uint16_t len; + uint8_t priority; + uint8_t ident_len; +} __attribute__((packed)); + +struct log_l2cap_hdr { + uint16_t cid; + uint16_t psm; +} __attribute__((packed)); + +static int log_fd = -1; + +int bt_log_sendmsg(uint16_t index, const char *label, int level, + struct iovec *io, size_t io_len) +{ + struct log_hdr hdr; + struct msghdr msg; + struct iovec iov[5]; + size_t i; + int err; + + if (io_len > 3) + return -EMSGSIZE; + + log_fd = bt_log_open(); + if (log_fd < 0) + return log_fd; + + hdr.opcode = cpu_to_le16(0x0000); + hdr.index = cpu_to_le16(index); + hdr.ident_len = strlen(label) + 1; + hdr.len = cpu_to_le16(2 + hdr.ident_len); + hdr.priority = level; + + iov[0].iov_base = &hdr; + iov[0].iov_len = sizeof(hdr); + + iov[1].iov_base = (void *) label; + iov[1].iov_len = hdr.ident_len; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + + for (i = 0; i < io_len; i++) { + iov[i + 2] = io[i]; + hdr.len += io[i].iov_len; + msg.msg_iovlen++; + } + + err = sendmsg(log_fd, &msg, 0); + if (err < 0) { + err = -errno; + close(log_fd); + log_fd = -1; + } + + return err; +} + +int bt_log_open(void) +{ + struct sockaddr_hci addr; + int fd; + static int err; + + if (err < 0) + return err; + + if (log_fd >= 0) + return log_fd; + + fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (fd < 0) { + err = -errno; + return -errno; + } + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = HCI_DEV_NONE; + addr.hci_channel = HCI_CHANNEL_LOGGING; + + err = bind(fd, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0) { + err = -errno; + close(fd); + return err; + } + + log_fd = fd; + + return fd; +} + +int bt_log_vprintf(uint16_t index, const char *label, int level, + const char *format, va_list ap) +{ + struct iovec iov; + char *str; + int len; + + len = vasprintf(&str, format, ap); + if (len < 0) + return errno; + + len = strlen(str); + + /* Replace new line since btmon already adds it */ + if (len > 1 && str[len - 1] == '\n') { + str[len - 1] = '\0'; + len--; + } + + iov.iov_base = str; + iov.iov_len = len + 1; + + len = bt_log_sendmsg(index, label, level, &iov, 1); + + free(str); + + return len; +} + +int bt_log_printf(uint16_t index, const char *label, int level, + const char *format, ...) +{ + va_list ap; + int err; + + va_start(ap, format); + err = bt_log_vprintf(index, label, level, format, ap); + va_end(ap); + + return err; +} + +void bt_log_close(void) +{ + if (log_fd < 0) + return; + + close(log_fd); + log_fd = -1; +} diff --git a/src/shared/log.h b/src/shared/log.h new file mode 100644 index 0000000..c72ab73 --- /dev/null +++ b/src/shared/log.h @@ -0,0 +1,31 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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 + * + */ + +int bt_log_open(void); +int bt_log_sendmsg(uint16_t index, const char *label, int level, + struct iovec *io, size_t io_len); +int bt_log_vprintf(uint16_t index, const char *label, int level, + const char *format, va_list ap); +int bt_log_printf(uint16_t index, const char *label, int level, + const char *format, ...); +void bt_log_close(void); diff --git a/src/shared/mainloop-ell.c b/src/shared/mainloop-ell.c new file mode 100644 index 0000000..63e254a --- /dev/null +++ b/src/shared/mainloop-ell.c @@ -0,0 +1,126 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2019 Intel Corporation + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include + +#include "mainloop.h" + +static bool is_initialized; +static int exit_status; +static mainloop_signal_func sig_func; + +static void l_sig_func(uint32_t signo, void *user_data) +{ + if (sig_func) + sig_func(signo, user_data); +} + +void mainloop_init(void) +{ + is_initialized = l_main_init(); +} + +void mainloop_quit(void) +{ + l_main_quit(); +} + +void mainloop_exit_success(void) +{ + exit_status = EXIT_SUCCESS; + l_main_quit(); +} + +void mainloop_exit_failure(void) +{ + exit_status = EXIT_FAILURE; + l_main_quit(); +} + +int mainloop_run(void) +{ + if (!is_initialized) + return -EINVAL; + + l_main_run(); + + is_initialized = false; + sig_func = NULL; + + return exit_status; +} + +int mainloop_run_with_signal(mainloop_signal_func func, void *user_data) +{ + if (!is_initialized || !func) + return -EINVAL; + + /* Workaround for sign discrepancy in ell and bluez */ + sig_func = func; + + return l_main_run_with_signal(l_sig_func, user_data); +} + +int mainloop_add_fd(int fd, uint32_t events, mainloop_event_func callback, + void *user_data, mainloop_destroy_func destroy) +{ + return -ENOSYS; +} + +int mainloop_modify_fd(int fd, uint32_t events) +{ + return -ENOSYS; +} + +int mainloop_remove_fd(int fd) +{ + return -ENOSYS; +} + +int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback, + void *user_data, mainloop_destroy_func destroy) +{ + return -ENOSYS; +} + +int mainloop_modify_timeout(int fd, unsigned int msec) +{ + return -ENOSYS; +} + +int mainloop_remove_timeout(int id) +{ + return -ENOSYS; +} + +int mainloop_set_signal(sigset_t *mask, mainloop_signal_func callback, + void *user_data, mainloop_destroy_func destroy) +{ + return -ENOSYS; +} diff --git a/src/shared/mainloop-glib.c b/src/shared/mainloop-glib.c new file mode 100644 index 0000000..2508a1f --- /dev/null +++ b/src/shared/mainloop-glib.c @@ -0,0 +1,125 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 Intel Corporation + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "mainloop.h" +#include "mainloop-notify.h" +#include "io.h" + +static GMainLoop *main_loop; +static int exit_status; + +void mainloop_init(void) +{ + main_loop = g_main_loop_new(NULL, FALSE); + mainloop_notify_init(); +} + +void mainloop_quit(void) +{ + if (!main_loop) + return; + + g_main_loop_quit(main_loop); + + mainloop_sd_notify("STOPPING=1"); +} + +void mainloop_exit_success(void) +{ + exit_status = EXIT_SUCCESS; + mainloop_quit(); +} + +void mainloop_exit_failure(void) +{ + exit_status = EXIT_FAILURE; + mainloop_quit(); +} + +int mainloop_run(void) +{ + if (!main_loop) + return -EINVAL; + + g_main_loop_run(main_loop); + + g_main_loop_unref(main_loop); + main_loop = NULL; + + mainloop_notify_exit(); + + return exit_status; +} + +int mainloop_add_fd(int fd, uint32_t events, mainloop_event_func callback, + void *user_data, mainloop_destroy_func destroy) +{ + return -ENOSYS; +} + +int mainloop_modify_fd(int fd, uint32_t events) +{ + return -ENOSYS; +} + +int mainloop_remove_fd(int fd) +{ + return -ENOSYS; +} + +int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback, + void *user_data, mainloop_destroy_func destroy) +{ + return -ENOSYS; +} + +int mainloop_modify_timeout(int fd, unsigned int msec) +{ + return -ENOSYS; +} + +int mainloop_remove_timeout(int id) +{ + return -ENOSYS; +} + +int mainloop_set_signal(sigset_t *mask, mainloop_signal_func callback, + void *user_data, mainloop_destroy_func destroy) +{ + return -ENOSYS; +} diff --git a/src/shared/mainloop-notify.c b/src/shared/mainloop-notify.c new file mode 100644 index 0000000..89a8800 --- /dev/null +++ b/src/shared/mainloop-notify.c @@ -0,0 +1,207 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mainloop.h" +#include "mainloop-notify.h" +#include "timeout.h" +#include "util.h" +#include "io.h" + +#define WATCHDOG_TRIGGER_FREQ 2 + +static int notify_fd = -1; + +static unsigned int watchdog; + +struct signal_data { + struct io *io; + mainloop_signal_func func; + void *user_data; +}; + +static struct signal_data *signal_data; + +static bool watchdog_callback(void *user_data) +{ + mainloop_sd_notify("WATCHDOG=1"); + + return true; +} + +void mainloop_notify_init(void) +{ + const char *sock; + struct sockaddr_un addr; + const char *watchdog_usec; + int msec; + + sock = getenv("NOTIFY_SOCKET"); + if (!sock) + return; + + /* check for abstract socket or absolute path */ + if (sock[0] != '@' && sock[0] != '/') + return; + + notify_fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (notify_fd < 0) + return; + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sock, sizeof(addr.sun_path) - 1); + + if (addr.sun_path[0] == '@') + addr.sun_path[0] = '\0'; + + if (bind(notify_fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(notify_fd); + notify_fd = -1; + return; + } + + watchdog_usec = getenv("WATCHDOG_USEC"); + if (!watchdog_usec) + return; + + msec = atoi(watchdog_usec) / 1000; + if (msec < 0) + return; + + watchdog = timeout_add(msec / WATCHDOG_TRIGGER_FREQ, + watchdog_callback, NULL, NULL); +} + +void mainloop_notify_exit(void) +{ + if (notify_fd > 0) { + close(notify_fd); + notify_fd = -1; + } + + timeout_remove(watchdog); +} + +int mainloop_sd_notify(const char *state) +{ + int err; + + if (notify_fd <= 0) + return -ENOTCONN; + + err = send(notify_fd, state, strlen(state), MSG_NOSIGNAL); + if (err < 0) + return -errno; + + return err; +} + +static bool signal_read(struct io *io, void *user_data) +{ + struct signal_data *data = user_data; + struct signalfd_siginfo si; + ssize_t result; + int fd; + + fd = io_get_fd(io); + + result = read(fd, &si, sizeof(si)); + if (result != sizeof(si)) + return false; + + if (data && data->func) + data->func(si.ssi_signo, data->user_data); + + return true; +} + +static struct io *setup_signalfd(void *user_data) +{ + struct io *io; + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGUSR2); + sigaddset(&mask, SIGCHLD); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) + return NULL; + + fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (fd < 0) + return NULL; + + io = io_new(fd); + + io_set_close_on_destroy(io, true); + io_set_read_handler(io, signal_read, user_data, free); + + return io; +} + +int mainloop_run_with_signal(mainloop_signal_func func, void *user_data) +{ + struct signal_data *data; + struct io *io; + int ret; + + if (!func) + return -EINVAL; + + data = new0(struct signal_data, 1); + data->func = func; + data->user_data = user_data; + + io = setup_signalfd(data); + if (!io) { + free(data); + return -errno; + } + + ret = mainloop_run(); + + io_destroy(io); + free(signal_data); + + return ret; +} diff --git a/src/shared/mainloop-notify.h b/src/shared/mainloop-notify.h new file mode 100644 index 0000000..721b5fb --- /dev/null +++ b/src/shared/mainloop-notify.h @@ -0,0 +1,25 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2018 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 + * + */ + +void mainloop_notify_init(void); +void mainloop_notify_exit(void); diff --git a/src/shared/mainloop.c b/src/shared/mainloop.c new file mode 100644 index 0000000..8a8e2c0 --- /dev/null +++ b/src/shared/mainloop.c @@ -0,0 +1,334 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mainloop.h" +#include "mainloop-notify.h" + +#define MAX_EPOLL_EVENTS 10 + +static int epoll_fd; +static int epoll_terminate; +static int exit_status = EXIT_SUCCESS; + +struct mainloop_data { + int fd; + uint32_t events; + mainloop_event_func callback; + mainloop_destroy_func destroy; + void *user_data; +}; + +#define MAX_MAINLOOP_ENTRIES 128 + +static struct mainloop_data *mainloop_list[MAX_MAINLOOP_ENTRIES]; + +struct timeout_data { + int fd; + mainloop_timeout_func callback; + mainloop_destroy_func destroy; + void *user_data; +}; + +void mainloop_init(void) +{ + unsigned int i; + + epoll_fd = epoll_create1(EPOLL_CLOEXEC); + + for (i = 0; i < MAX_MAINLOOP_ENTRIES; i++) + mainloop_list[i] = NULL; + + epoll_terminate = 0; + + mainloop_notify_init(); +} + +void mainloop_quit(void) +{ + epoll_terminate = 1; + + mainloop_sd_notify("STOPPING=1"); +} + +void mainloop_exit_success(void) +{ + exit_status = EXIT_SUCCESS; + epoll_terminate = 1; +} + +void mainloop_exit_failure(void) +{ + exit_status = EXIT_FAILURE; + epoll_terminate = 1; +} + +int mainloop_run(void) +{ + unsigned int i; + + while (!epoll_terminate) { + struct epoll_event events[MAX_EPOLL_EVENTS]; + int n, nfds; + + nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS, -1); + if (nfds < 0) + continue; + + for (n = 0; n < nfds; n++) { + struct mainloop_data *data = events[n].data.ptr; + + data->callback(data->fd, events[n].events, + data->user_data); + } + } + + for (i = 0; i < MAX_MAINLOOP_ENTRIES; i++) { + struct mainloop_data *data = mainloop_list[i]; + + mainloop_list[i] = NULL; + + if (data) { + epoll_ctl(epoll_fd, EPOLL_CTL_DEL, data->fd, NULL); + + if (data->destroy) + data->destroy(data->user_data); + + free(data); + } + } + + close(epoll_fd); + epoll_fd = 0; + + mainloop_notify_exit(); + + return exit_status; +} + +int mainloop_add_fd(int fd, uint32_t events, mainloop_event_func callback, + void *user_data, mainloop_destroy_func destroy) +{ + struct mainloop_data *data; + struct epoll_event ev; + int err; + + if (fd < 0 || fd > MAX_MAINLOOP_ENTRIES - 1 || !callback) + return -EINVAL; + + data = malloc(sizeof(*data)); + if (!data) + return -ENOMEM; + + memset(data, 0, sizeof(*data)); + data->fd = fd; + data->events = events; + data->callback = callback; + data->destroy = destroy; + data->user_data = user_data; + + memset(&ev, 0, sizeof(ev)); + ev.events = events; + ev.data.ptr = data; + + err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, data->fd, &ev); + if (err < 0) { + free(data); + return err; + } + + mainloop_list[fd] = data; + + return 0; +} + +int mainloop_modify_fd(int fd, uint32_t events) +{ + struct mainloop_data *data; + struct epoll_event ev; + int err; + + if (fd < 0 || fd > MAX_MAINLOOP_ENTRIES - 1) + return -EINVAL; + + data = mainloop_list[fd]; + if (!data) + return -ENXIO; + + memset(&ev, 0, sizeof(ev)); + ev.events = events; + ev.data.ptr = data; + + err = epoll_ctl(epoll_fd, EPOLL_CTL_MOD, data->fd, &ev); + if (err < 0) + return err; + + data->events = events; + + return 0; +} + +int mainloop_remove_fd(int fd) +{ + struct mainloop_data *data; + int err; + + if (fd < 0 || fd > MAX_MAINLOOP_ENTRIES - 1) + return -EINVAL; + + data = mainloop_list[fd]; + if (!data) + return -ENXIO; + + mainloop_list[fd] = NULL; + + err = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, data->fd, NULL); + + if (data->destroy) + data->destroy(data->user_data); + + free(data); + + return err; +} + +static void timeout_destroy(void *user_data) +{ + struct timeout_data *data = user_data; + + close(data->fd); + data->fd = -1; + + if (data->destroy) + data->destroy(data->user_data); + + free(data); +} + +static void timeout_callback(int fd, uint32_t events, void *user_data) +{ + struct timeout_data *data = user_data; + uint64_t expired; + ssize_t result; + + if (events & (EPOLLERR | EPOLLHUP)) + return; + + result = read(data->fd, &expired, sizeof(expired)); + if (result != sizeof(expired)) + return; + + if (data->callback) + data->callback(data->fd, data->user_data); +} + +static inline int timeout_set(int fd, unsigned int msec) +{ + struct itimerspec itimer; + unsigned int sec = msec / 1000; + + memset(&itimer, 0, sizeof(itimer)); + itimer.it_interval.tv_sec = 0; + itimer.it_interval.tv_nsec = 0; + itimer.it_value.tv_sec = sec; + itimer.it_value.tv_nsec = (msec - (sec * 1000)) * 1000 * 1000; + + return timerfd_settime(fd, 0, &itimer, NULL); +} + +int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback, + void *user_data, mainloop_destroy_func destroy) +{ + struct timeout_data *data; + + if (!callback) + return -EINVAL; + + data = malloc(sizeof(*data)); + if (!data) + return -ENOMEM; + + memset(data, 0, sizeof(*data)); + data->callback = callback; + data->destroy = destroy; + data->user_data = user_data; + + data->fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); + if (data->fd < 0) { + free(data); + return -EIO; + } + + if (msec > 0) { + if (timeout_set(data->fd, msec) < 0) { + close(data->fd); + free(data); + return -EIO; + } + } + + if (mainloop_add_fd(data->fd, EPOLLIN | EPOLLONESHOT, + timeout_callback, data, timeout_destroy) < 0) { + close(data->fd); + free(data); + return -EIO; + } + + return data->fd; +} + +int mainloop_modify_timeout(int id, unsigned int msec) +{ + if (msec > 0) { + if (timeout_set(id, msec) < 0) + return -EIO; + } + + if (mainloop_modify_fd(id, EPOLLIN | EPOLLONESHOT) < 0) + return -EIO; + + return 0; +} + +int mainloop_remove_timeout(int id) +{ + return mainloop_remove_fd(id); +} diff --git a/src/shared/mainloop.h b/src/shared/mainloop.h new file mode 100644 index 0000000..1ede627 --- /dev/null +++ b/src/shared/mainloop.h @@ -0,0 +1,53 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +typedef void (*mainloop_destroy_func) (void *user_data); + +typedef void (*mainloop_event_func) (int fd, uint32_t events, void *user_data); +typedef void (*mainloop_timeout_func) (int id, void *user_data); +typedef void (*mainloop_signal_func) (int signum, void *user_data); + +void mainloop_init(void); +void mainloop_quit(void); +void mainloop_exit_success(void); +void mainloop_exit_failure(void); +int mainloop_run(void); +int mainloop_run_with_signal(mainloop_signal_func func, void *user_data); + +int mainloop_add_fd(int fd, uint32_t events, mainloop_event_func callback, + void *user_data, mainloop_destroy_func destroy); +int mainloop_modify_fd(int fd, uint32_t events); +int mainloop_remove_fd(int fd); + +int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback, + void *user_data, mainloop_destroy_func destroy); +int mainloop_modify_timeout(int fd, unsigned int msec); +int mainloop_remove_timeout(int id); + +int mainloop_set_signal(sigset_t *mask, mainloop_signal_func callback, + void *user_data, mainloop_destroy_func destroy); +int mainloop_sd_notify(const char *state); diff --git a/src/shared/mgmt.c b/src/shared/mgmt.c new file mode 100644 index 0000000..277e361 --- /dev/null +++ b/src/shared/mgmt.c @@ -0,0 +1,801 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/mgmt.h" +#include "lib/hci.h" + +#include "src/shared/io.h" +#include "src/shared/queue.h" +#include "src/shared/util.h" +#include "src/shared/mgmt.h" + +struct mgmt { + int ref_count; + int fd; + bool close_on_unref; + struct io *io; + bool writer_active; + struct queue *request_queue; + struct queue *reply_queue; + struct queue *pending_list; + struct queue *notify_list; + unsigned int next_request_id; + unsigned int next_notify_id; + bool need_notify_cleanup; + bool in_notify; + void *buf; + uint16_t len; + mgmt_debug_func_t debug_callback; + mgmt_destroy_func_t debug_destroy; + void *debug_data; +}; + +struct mgmt_request { + unsigned int id; + uint16_t opcode; + uint16_t index; + void *buf; + uint16_t len; + mgmt_request_func_t callback; + mgmt_destroy_func_t destroy; + void *user_data; +}; + +struct mgmt_notify { + unsigned int id; + uint16_t event; + uint16_t index; + bool removed; + mgmt_notify_func_t callback; + mgmt_destroy_func_t destroy; + void *user_data; +}; + +static void destroy_request(void *data) +{ + struct mgmt_request *request = data; + + if (request->destroy) + request->destroy(request->user_data); + + free(request->buf); + free(request); +} + +static bool match_request_id(const void *a, const void *b) +{ + const struct mgmt_request *request = a; + unsigned int id = PTR_TO_UINT(b); + + return request->id == id; +} + +static bool match_request_index(const void *a, const void *b) +{ + const struct mgmt_request *request = a; + uint16_t index = PTR_TO_UINT(b); + + return request->index == index; +} + +static void destroy_notify(void *data) +{ + struct mgmt_notify *notify = data; + + if (notify->destroy) + notify->destroy(notify->user_data); + + free(notify); +} + +static bool match_notify_id(const void *a, const void *b) +{ + const struct mgmt_notify *notify = a; + unsigned int id = PTR_TO_UINT(b); + + return notify->id == id; +} + +static bool match_notify_index(const void *a, const void *b) +{ + const struct mgmt_notify *notify = a; + uint16_t index = PTR_TO_UINT(b); + + return notify->index == index; +} + +static bool match_notify_removed(const void *a, const void *b) +{ + const struct mgmt_notify *notify = a; + + return notify->removed; +} + +static void mark_notify_removed(void *data , void *user_data) +{ + struct mgmt_notify *notify = data; + uint16_t index = PTR_TO_UINT(user_data); + + if (notify->index == index || index == MGMT_INDEX_NONE) + notify->removed = true; +} + +static void write_watch_destroy(void *user_data) +{ + struct mgmt *mgmt = user_data; + + mgmt->writer_active = false; +} + +static bool send_request(struct mgmt *mgmt, struct mgmt_request *request) +{ + struct iovec iov; + ssize_t ret; + + iov.iov_base = request->buf; + iov.iov_len = request->len; + + ret = io_send(mgmt->io, &iov, 1); + if (ret < 0) { + util_debug(mgmt->debug_callback, mgmt->debug_data, + "write failed: %s", strerror(-ret)); + if (request->callback) + request->callback(MGMT_STATUS_FAILED, 0, NULL, + request->user_data); + destroy_request(request); + return false; + } + + util_debug(mgmt->debug_callback, mgmt->debug_data, + "[0x%04x] command 0x%04x", + request->index, request->opcode); + + util_hexdump('<', request->buf, ret, mgmt->debug_callback, + mgmt->debug_data); + + queue_push_tail(mgmt->pending_list, request); + + return true; +} + +static bool can_write_data(struct io *io, void *user_data) +{ + struct mgmt *mgmt = user_data; + struct mgmt_request *request; + bool can_write; + + request = queue_pop_head(mgmt->reply_queue); + if (!request) { + /* only reply commands can jump the queue */ + if (!queue_isempty(mgmt->pending_list)) + return false; + + request = queue_pop_head(mgmt->request_queue); + if (!request) + return false; + + can_write = false; + } else { + /* allow multiple replies to jump the queue */ + can_write = !queue_isempty(mgmt->reply_queue); + } + + if (!send_request(mgmt, request)) + return true; + + return can_write; +} + +static void wakeup_writer(struct mgmt *mgmt) +{ + if (!queue_isempty(mgmt->pending_list)) { + /* only queued reply commands trigger wakeup */ + if (queue_isempty(mgmt->reply_queue)) + return; + } + + if (mgmt->writer_active) + return; + + mgmt->writer_active = true; + + io_set_write_handler(mgmt->io, can_write_data, mgmt, + write_watch_destroy); +} + +struct opcode_index { + uint16_t opcode; + uint16_t index; +}; + +static bool match_request_opcode_index(const void *a, const void *b) +{ + const struct mgmt_request *request = a; + const struct opcode_index *match = b; + + return request->opcode == match->opcode && + request->index == match->index; +} + +static void request_complete(struct mgmt *mgmt, uint8_t status, + uint16_t opcode, uint16_t index, + uint16_t length, const void *param) +{ + struct opcode_index match = { .opcode = opcode, .index = index }; + struct mgmt_request *request; + + request = queue_remove_if(mgmt->pending_list, + match_request_opcode_index, &match); + if (request) { + if (request->callback) + request->callback(status, length, param, + request->user_data); + + destroy_request(request); + } + + wakeup_writer(mgmt); +} + +struct event_index { + uint16_t event; + uint16_t index; + uint16_t length; + const void *param; +}; + +static void notify_handler(void *data, void *user_data) +{ + struct mgmt_notify *notify = data; + struct event_index *match = user_data; + + if (notify->removed) + return; + + if (notify->event != match->event) + return; + + if (notify->index != match->index && notify->index != MGMT_INDEX_NONE) + return; + + if (notify->callback) + notify->callback(match->index, match->length, match->param, + notify->user_data); +} + +static void process_notify(struct mgmt *mgmt, uint16_t event, uint16_t index, + uint16_t length, const void *param) +{ + struct event_index match = { .event = event, .index = index, + .length = length, .param = param }; + + mgmt->in_notify = true; + + queue_foreach(mgmt->notify_list, notify_handler, &match); + + mgmt->in_notify = false; + + if (mgmt->need_notify_cleanup) { + queue_remove_all(mgmt->notify_list, match_notify_removed, + NULL, destroy_notify); + mgmt->need_notify_cleanup = false; + } +} + +static bool can_read_data(struct io *io, void *user_data) +{ + struct mgmt *mgmt = user_data; + struct mgmt_hdr *hdr; + struct mgmt_ev_cmd_complete *cc; + struct mgmt_ev_cmd_status *cs; + ssize_t bytes_read; + uint16_t opcode, event, index, length; + + bytes_read = read(mgmt->fd, mgmt->buf, mgmt->len); + if (bytes_read < 0) + return false; + + util_hexdump('>', mgmt->buf, bytes_read, + mgmt->debug_callback, mgmt->debug_data); + + if (bytes_read < MGMT_HDR_SIZE) + return true; + + hdr = mgmt->buf; + event = btohs(hdr->opcode); + index = btohs(hdr->index); + length = btohs(hdr->len); + + if (bytes_read < length + MGMT_HDR_SIZE) + return true; + + mgmt_ref(mgmt); + + switch (event) { + case MGMT_EV_CMD_COMPLETE: + cc = mgmt->buf + MGMT_HDR_SIZE; + opcode = btohs(cc->opcode); + + util_debug(mgmt->debug_callback, mgmt->debug_data, + "[0x%04x] command 0x%04x complete: 0x%02x", + index, opcode, cc->status); + + request_complete(mgmt, cc->status, opcode, index, length - 3, + mgmt->buf + MGMT_HDR_SIZE + 3); + break; + case MGMT_EV_CMD_STATUS: + cs = mgmt->buf + MGMT_HDR_SIZE; + opcode = btohs(cs->opcode); + + util_debug(mgmt->debug_callback, mgmt->debug_data, + "[0x%04x] command 0x%02x status: 0x%02x", + index, opcode, cs->status); + + request_complete(mgmt, cs->status, opcode, index, 0, NULL); + break; + default: + util_debug(mgmt->debug_callback, mgmt->debug_data, + "[0x%04x] event 0x%04x", index, event); + + process_notify(mgmt, event, index, length, + mgmt->buf + MGMT_HDR_SIZE); + break; + } + + mgmt_unref(mgmt); + + return true; +} + +struct mgmt *mgmt_new(int fd) +{ + struct mgmt *mgmt; + + if (fd < 0) + return NULL; + + mgmt = new0(struct mgmt, 1); + mgmt->fd = fd; + mgmt->close_on_unref = false; + + mgmt->len = 512; + mgmt->buf = malloc(mgmt->len); + if (!mgmt->buf) { + free(mgmt); + return NULL; + } + + mgmt->io = io_new(fd); + if (!mgmt->io) { + free(mgmt->buf); + free(mgmt); + return NULL; + } + + mgmt->request_queue = queue_new(); + mgmt->reply_queue = queue_new(); + mgmt->pending_list = queue_new(); + mgmt->notify_list = queue_new(); + + if (!io_set_read_handler(mgmt->io, can_read_data, mgmt, NULL)) { + queue_destroy(mgmt->notify_list, NULL); + queue_destroy(mgmt->pending_list, NULL); + queue_destroy(mgmt->reply_queue, NULL); + queue_destroy(mgmt->request_queue, NULL); + io_destroy(mgmt->io); + free(mgmt->buf); + free(mgmt); + return NULL; + } + + mgmt->writer_active = false; + + return mgmt_ref(mgmt); +} + +struct mgmt *mgmt_new_default(void) +{ + struct mgmt *mgmt; + union { + struct sockaddr common; + struct sockaddr_hci hci; + } addr; + int fd; + + fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, + BTPROTO_HCI); + if (fd < 0) + return NULL; + + memset(&addr, 0, sizeof(addr)); + addr.hci.hci_family = AF_BLUETOOTH; + addr.hci.hci_dev = HCI_DEV_NONE; + addr.hci.hci_channel = HCI_CHANNEL_CONTROL; + + if (bind(fd, &addr.common, sizeof(addr.hci)) < 0) { + close(fd); + return NULL; + } + + mgmt = mgmt_new(fd); + if (!mgmt) { + close(fd); + return NULL; + } + + mgmt->close_on_unref = true; + + return mgmt; +} + +struct mgmt *mgmt_ref(struct mgmt *mgmt) +{ + if (!mgmt) + return NULL; + + __sync_fetch_and_add(&mgmt->ref_count, 1); + + return mgmt; +} + +void mgmt_unref(struct mgmt *mgmt) +{ + if (!mgmt) + return; + + if (__sync_sub_and_fetch(&mgmt->ref_count, 1)) + return; + + mgmt_unregister_all(mgmt); + mgmt_cancel_all(mgmt); + + queue_destroy(mgmt->reply_queue, NULL); + queue_destroy(mgmt->request_queue, NULL); + + io_set_write_handler(mgmt->io, NULL, NULL, NULL); + io_set_read_handler(mgmt->io, NULL, NULL, NULL); + + io_destroy(mgmt->io); + mgmt->io = NULL; + + if (mgmt->close_on_unref) + close(mgmt->fd); + + if (mgmt->debug_destroy) + mgmt->debug_destroy(mgmt->debug_data); + + free(mgmt->buf); + mgmt->buf = NULL; + + if (!mgmt->in_notify) { + queue_destroy(mgmt->notify_list, NULL); + queue_destroy(mgmt->pending_list, NULL); + free(mgmt); + return; + } +} + +bool mgmt_set_debug(struct mgmt *mgmt, mgmt_debug_func_t callback, + void *user_data, mgmt_destroy_func_t destroy) +{ + if (!mgmt) + return false; + + if (mgmt->debug_destroy) + mgmt->debug_destroy(mgmt->debug_data); + + mgmt->debug_callback = callback; + mgmt->debug_destroy = destroy; + mgmt->debug_data = user_data; + + return true; +} + +bool mgmt_set_close_on_unref(struct mgmt *mgmt, bool do_close) +{ + if (!mgmt) + return false; + + mgmt->close_on_unref = do_close; + + return true; +} + +static struct mgmt_request *create_request(uint16_t opcode, uint16_t index, + uint16_t length, const void *param, + mgmt_request_func_t callback, + void *user_data, mgmt_destroy_func_t destroy) +{ + struct mgmt_request *request; + struct mgmt_hdr *hdr; + + if (!opcode) + return NULL; + + if (length > 0 && !param) + return NULL; + + request = new0(struct mgmt_request, 1); + request->len = length + MGMT_HDR_SIZE; + request->buf = malloc(request->len); + if (!request->buf) { + free(request); + return NULL; + } + + if (length > 0) + memcpy(request->buf + MGMT_HDR_SIZE, param, length); + + hdr = request->buf; + hdr->opcode = htobs(opcode); + hdr->index = htobs(index); + hdr->len = htobs(length); + + request->opcode = opcode; + request->index = index; + + request->callback = callback; + request->destroy = destroy; + request->user_data = user_data; + + return request; +} + +unsigned int mgmt_send(struct mgmt *mgmt, uint16_t opcode, uint16_t index, + uint16_t length, const void *param, + mgmt_request_func_t callback, + void *user_data, mgmt_destroy_func_t destroy) +{ + struct mgmt_request *request; + + if (!mgmt) + return 0; + + request = create_request(opcode, index, length, param, + callback, user_data, destroy); + if (!request) + return 0; + + if (mgmt->next_request_id < 1) + mgmt->next_request_id = 1; + + request->id = mgmt->next_request_id++; + + if (!queue_push_tail(mgmt->request_queue, request)) { + free(request->buf); + free(request); + return 0; + } + + wakeup_writer(mgmt); + + return request->id; +} + +unsigned int mgmt_send_nowait(struct mgmt *mgmt, uint16_t opcode, uint16_t index, + uint16_t length, const void *param, + mgmt_request_func_t callback, + void *user_data, mgmt_destroy_func_t destroy) +{ + struct mgmt_request *request; + + if (!mgmt) + return 0; + + request = create_request(opcode, index, length, param, + callback, user_data, destroy); + if (!request) + return 0; + + if (mgmt->next_request_id < 1) + mgmt->next_request_id = 1; + + request->id = mgmt->next_request_id++; + + if (!send_request(mgmt, request)) + return 0; + + return request->id; +} + +unsigned int mgmt_reply(struct mgmt *mgmt, uint16_t opcode, uint16_t index, + uint16_t length, const void *param, + mgmt_request_func_t callback, + void *user_data, mgmt_destroy_func_t destroy) +{ + struct mgmt_request *request; + + if (!mgmt) + return 0; + + request = create_request(opcode, index, length, param, + callback, user_data, destroy); + if (!request) + return 0; + + if (mgmt->next_request_id < 1) + mgmt->next_request_id = 1; + + request->id = mgmt->next_request_id++; + + if (!queue_push_tail(mgmt->reply_queue, request)) { + free(request->buf); + free(request); + return 0; + } + + wakeup_writer(mgmt); + + return request->id; +} + +bool mgmt_cancel(struct mgmt *mgmt, unsigned int id) +{ + struct mgmt_request *request; + + if (!mgmt || !id) + return false; + + request = queue_remove_if(mgmt->request_queue, match_request_id, + UINT_TO_PTR(id)); + if (request) + goto done; + + request = queue_remove_if(mgmt->reply_queue, match_request_id, + UINT_TO_PTR(id)); + if (request) + goto done; + + request = queue_remove_if(mgmt->pending_list, match_request_id, + UINT_TO_PTR(id)); + if (!request) + return false; + +done: + destroy_request(request); + + wakeup_writer(mgmt); + + return true; +} + +bool mgmt_cancel_index(struct mgmt *mgmt, uint16_t index) +{ + if (!mgmt) + return false; + + queue_remove_all(mgmt->request_queue, match_request_index, + UINT_TO_PTR(index), destroy_request); + queue_remove_all(mgmt->reply_queue, match_request_index, + UINT_TO_PTR(index), destroy_request); + queue_remove_all(mgmt->pending_list, match_request_index, + UINT_TO_PTR(index), destroy_request); + + return true; +} + +bool mgmt_cancel_all(struct mgmt *mgmt) +{ + if (!mgmt) + return false; + + queue_remove_all(mgmt->pending_list, NULL, NULL, destroy_request); + queue_remove_all(mgmt->reply_queue, NULL, NULL, destroy_request); + queue_remove_all(mgmt->request_queue, NULL, NULL, destroy_request); + + return true; +} + +unsigned int mgmt_register(struct mgmt *mgmt, uint16_t event, uint16_t index, + mgmt_notify_func_t callback, + void *user_data, mgmt_destroy_func_t destroy) +{ + struct mgmt_notify *notify; + + if (!mgmt || !event) + return 0; + + notify = new0(struct mgmt_notify, 1); + notify->event = event; + notify->index = index; + + notify->callback = callback; + notify->destroy = destroy; + notify->user_data = user_data; + + if (mgmt->next_notify_id < 1) + mgmt->next_notify_id = 1; + + notify->id = mgmt->next_notify_id++; + + if (!queue_push_tail(mgmt->notify_list, notify)) { + free(notify); + return 0; + } + + return notify->id; +} + +bool mgmt_unregister(struct mgmt *mgmt, unsigned int id) +{ + struct mgmt_notify *notify; + + if (!mgmt || !id) + return false; + + notify = queue_remove_if(mgmt->notify_list, match_notify_id, + UINT_TO_PTR(id)); + if (!notify) + return false; + + if (!mgmt->in_notify) { + destroy_notify(notify); + return true; + } + + notify->removed = true; + mgmt->need_notify_cleanup = true; + + return true; +} + +bool mgmt_unregister_index(struct mgmt *mgmt, uint16_t index) +{ + if (!mgmt) + return false; + + if (mgmt->in_notify) { + queue_foreach(mgmt->notify_list, mark_notify_removed, + UINT_TO_PTR(index)); + mgmt->need_notify_cleanup = true; + } else + queue_remove_all(mgmt->notify_list, match_notify_index, + UINT_TO_PTR(index), destroy_notify); + + return true; +} + +bool mgmt_unregister_all(struct mgmt *mgmt) +{ + if (!mgmt) + return false; + + if (mgmt->in_notify) { + queue_foreach(mgmt->notify_list, mark_notify_removed, + UINT_TO_PTR(MGMT_INDEX_NONE)); + mgmt->need_notify_cleanup = true; + } else + queue_remove_all(mgmt->notify_list, NULL, NULL, destroy_notify); + + return true; +} diff --git a/src/shared/mgmt.h b/src/shared/mgmt.h new file mode 100644 index 0000000..7caeb38 --- /dev/null +++ b/src/shared/mgmt.h @@ -0,0 +1,73 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include + +#define MGMT_VERSION(v, r) (((v) << 16) + (r)) + +typedef void (*mgmt_destroy_func_t)(void *user_data); + +struct mgmt; + +struct mgmt *mgmt_new(int fd); +struct mgmt *mgmt_new_default(void); + +struct mgmt *mgmt_ref(struct mgmt *mgmt); +void mgmt_unref(struct mgmt *mgmt); + +typedef void (*mgmt_debug_func_t)(const char *str, void *user_data); + +bool mgmt_set_debug(struct mgmt *mgmt, mgmt_debug_func_t callback, + void *user_data, mgmt_destroy_func_t destroy); + +bool mgmt_set_close_on_unref(struct mgmt *mgmt, bool do_close); + +typedef void (*mgmt_request_func_t)(uint8_t status, uint16_t length, + const void *param, void *user_data); + +unsigned int mgmt_send(struct mgmt *mgmt, uint16_t opcode, uint16_t index, + uint16_t length, const void *param, + mgmt_request_func_t callback, + void *user_data, mgmt_destroy_func_t destroy); +unsigned int mgmt_send_nowait(struct mgmt *mgmt, uint16_t opcode, uint16_t index, + uint16_t length, const void *param, + mgmt_request_func_t callback, + void *user_data, mgmt_destroy_func_t destroy); +unsigned int mgmt_reply(struct mgmt *mgmt, uint16_t opcode, uint16_t index, + uint16_t length, const void *param, + mgmt_request_func_t callback, + void *user_data, mgmt_destroy_func_t destroy); +bool mgmt_cancel(struct mgmt *mgmt, unsigned int id); +bool mgmt_cancel_index(struct mgmt *mgmt, uint16_t index); +bool mgmt_cancel_all(struct mgmt *mgmt); + +typedef void (*mgmt_notify_func_t)(uint16_t index, uint16_t length, + const void *param, void *user_data); + +unsigned int mgmt_register(struct mgmt *mgmt, uint16_t event, uint16_t index, + mgmt_notify_func_t callback, + void *user_data, mgmt_destroy_func_t destroy); +bool mgmt_unregister(struct mgmt *mgmt, unsigned int id); +bool mgmt_unregister_index(struct mgmt *mgmt, uint16_t index); +bool mgmt_unregister_all(struct mgmt *mgmt); diff --git a/src/shared/pcap.c b/src/shared/pcap.c new file mode 100644 index 0000000..0d887e2 --- /dev/null +++ b/src/shared/pcap.c @@ -0,0 +1,234 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include + +#include "src/shared/util.h" +#include "src/shared/pcap.h" + +struct pcap_hdr { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +} __attribute__ ((packed)); +#define PCAP_HDR_SIZE (sizeof(struct pcap_hdr)) + +struct pcap_pkt { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +} __attribute__ ((packed)); +#define PCAP_PKT_SIZE (sizeof(struct pcap_pkt)) + +struct pcap_ppi { + uint8_t version; /* version, currently 0 */ + uint8_t flags; /* flags */ + uint16_t len; /* length of entire message */ + uint32_t dlt; /* data link type */ +} __attribute__ ((packed)); +#define PCAP_PPI_SIZE (sizeof(struct pcap_ppi)) + +struct pcap { + int ref_count; + int fd; + uint32_t type; + uint32_t snaplen; +}; + +struct pcap *pcap_open(const char *path) +{ + struct pcap *pcap; + struct pcap_hdr hdr; + ssize_t len; + + pcap = calloc(1, sizeof(*pcap)); + if (!pcap) + return NULL; + + pcap->fd = open(path, O_RDONLY | O_CLOEXEC); + if (pcap->fd < 0) { + free(pcap); + return NULL; + } + + len = read(pcap->fd, &hdr, PCAP_HDR_SIZE); + if (len < 0 || len != PCAP_HDR_SIZE) + goto failed; + + if (hdr.magic_number != 0xa1b2c3d4) + goto failed; + + if (hdr.version_major != 2 || hdr.version_minor != 4) + goto failed; + + pcap->snaplen = hdr.snaplen; + pcap->type = hdr.network; + + return pcap_ref(pcap); + +failed: + close(pcap->fd); + free(pcap); + + return NULL; +} + +struct pcap *pcap_ref(struct pcap *pcap) +{ + if (!pcap) + return NULL; + + __sync_fetch_and_add(&pcap->ref_count, 1); + + return pcap; +} + +void pcap_unref(struct pcap *pcap) +{ + if (!pcap) + return; + + if (__sync_sub_and_fetch(&pcap->ref_count, 1)) + return; + + if (pcap->fd >= 0) + close(pcap->fd); + + free(pcap); +} + +uint32_t pcap_get_type(struct pcap *pcap) +{ + if (!pcap) + return PCAP_TYPE_INVALID; + + return pcap->type; +} + +uint32_t pcap_get_snaplen(struct pcap *pcap) +{ + if (!pcap) + return 0; + + return pcap->snaplen; +} + +bool pcap_read(struct pcap *pcap, struct timeval *tv, + void *data, uint32_t size, uint32_t *len) +{ + struct pcap_pkt pkt; + uint32_t toread; + ssize_t bytes_read; + + if (!pcap) + return false; + + bytes_read = read(pcap->fd, &pkt, PCAP_PKT_SIZE); + if (bytes_read != PCAP_PKT_SIZE) + return false; + + if (pkt.incl_len > size) + toread = size; + else + toread = pkt.incl_len; + + bytes_read = read(pcap->fd, data, toread); + if (bytes_read < 0) + return false; + + if (tv) { + tv->tv_sec = pkt.ts_sec; + tv->tv_usec = pkt.ts_usec; + } + + if (len) + *len = toread; + + return true; +} + +bool pcap_read_ppi(struct pcap *pcap, struct timeval *tv, uint32_t *type, + void *data, uint32_t size, + uint32_t *offset, uint32_t *len) +{ + struct pcap_pkt pkt; + struct pcap_ppi ppi; + uint16_t pph_len; + uint32_t toread; + ssize_t bytes_read; + + if (!pcap) + return false; + + bytes_read = read(pcap->fd, &pkt, PCAP_PKT_SIZE); + if (bytes_read != PCAP_PKT_SIZE) + return false; + + if (pkt.incl_len > size) + toread = size; + else + toread = pkt.incl_len; + + bytes_read = read(pcap->fd, &ppi, PCAP_PPI_SIZE); + if (bytes_read != PCAP_PPI_SIZE) + return false; + + if (ppi.flags) + return false; + + pph_len = le16_to_cpu(ppi.len); + if (pph_len < PCAP_PPI_SIZE) + return false; + + bytes_read = read(pcap->fd, data, toread - PCAP_PPI_SIZE); + if (bytes_read < 0) + return false; + + if (tv) { + tv->tv_sec = pkt.ts_sec; + tv->tv_usec = pkt.ts_usec; + } + + if (type) + *type = le32_to_cpu(ppi.dlt); + + if (offset) + *offset = pph_len - PCAP_PPI_SIZE; + + if (len) + *len = toread - pph_len; + + return true; +} diff --git a/src/shared/pcap.h b/src/shared/pcap.h new file mode 100644 index 0000000..b47de62 --- /dev/null +++ b/src/shared/pcap.h @@ -0,0 +1,47 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include +#include + +#define PCAP_TYPE_INVALID 0 +#define PCAP_TYPE_USER0 147 +#define PCAP_TYPE_PPI 192 +#define PCAP_TYPE_BLUETOOTH_LE_LL 251 + +struct pcap; + +struct pcap *pcap_open(const char *path); + +struct pcap *pcap_ref(struct pcap *pcap); +void pcap_unref(struct pcap *pcap); + +uint32_t pcap_get_type(struct pcap *pcap); +uint32_t pcap_get_snaplen(struct pcap *pcap); + +bool pcap_read(struct pcap *pcap, struct timeval *tv, + void *data, uint32_t size, uint32_t *len); +bool pcap_read_ppi(struct pcap *pcap, struct timeval *tv, uint32_t *type, + void *data, uint32_t size, + uint32_t *offset, uint32_t *len); diff --git a/src/shared/queue.c b/src/shared/queue.c new file mode 100644 index 0000000..60df111 --- /dev/null +++ b/src/shared/queue.c @@ -0,0 +1,386 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#include "src/shared/util.h" +#include "src/shared/queue.h" + +struct queue { + int ref_count; + struct queue_entry *head; + struct queue_entry *tail; + unsigned int entries; +}; + +static struct queue *queue_ref(struct queue *queue) +{ + if (!queue) + return NULL; + + __sync_fetch_and_add(&queue->ref_count, 1); + + return queue; +} + +static void queue_unref(struct queue *queue) +{ + if (__sync_sub_and_fetch(&queue->ref_count, 1)) + return; + + free(queue); +} + +struct queue *queue_new(void) +{ + struct queue *queue; + + queue = new0(struct queue, 1); + queue->head = NULL; + queue->tail = NULL; + queue->entries = 0; + + return queue_ref(queue); +} + +void queue_destroy(struct queue *queue, queue_destroy_func_t destroy) +{ + if (!queue) + return; + + queue_remove_all(queue, NULL, NULL, destroy); + + queue_unref(queue); +} + +static struct queue_entry *queue_entry_new(void *data) +{ + struct queue_entry *entry; + + entry = new0(struct queue_entry, 1); + entry->data = data; + + return entry; +} + +bool queue_push_tail(struct queue *queue, void *data) +{ + struct queue_entry *entry; + + if (!queue) + return false; + + entry = queue_entry_new(data); + + if (queue->tail) + queue->tail->next = entry; + + queue->tail = entry; + + if (!queue->head) + queue->head = entry; + + queue->entries++; + + return true; +} + +bool queue_push_head(struct queue *queue, void *data) +{ + struct queue_entry *entry; + + if (!queue) + return false; + + entry = queue_entry_new(data); + + entry->next = queue->head; + + queue->head = entry; + + if (!queue->tail) + queue->tail = entry; + + queue->entries++; + + return true; +} + +bool queue_push_after(struct queue *queue, void *entry, void *data) +{ + struct queue_entry *qentry, *tmp, *new_entry; + + qentry = NULL; + + if (!queue) + return false; + + for (tmp = queue->head; tmp; tmp = tmp->next) { + if (tmp->data == entry) { + qentry = tmp; + break; + } + } + + if (!qentry) + return false; + + new_entry = queue_entry_new(data); + + new_entry->next = qentry->next; + + if (!qentry->next) + queue->tail = new_entry; + + qentry->next = new_entry; + queue->entries++; + + return true; +} + +void *queue_pop_head(struct queue *queue) +{ + struct queue_entry *entry; + void *data; + + if (!queue || !queue->head) + return NULL; + + entry = queue->head; + + if (!queue->head->next) { + queue->head = NULL; + queue->tail = NULL; + } else + queue->head = queue->head->next; + + data = entry->data; + + free(entry); + queue->entries--; + + return data; +} + +void *queue_peek_head(struct queue *queue) +{ + if (!queue || !queue->head) + return NULL; + + return queue->head->data; +} + +void *queue_peek_tail(struct queue *queue) +{ + if (!queue || !queue->tail) + return NULL; + + return queue->tail->data; +} + +void queue_foreach(struct queue *queue, queue_foreach_func_t function, + void *user_data) +{ + struct queue_entry *entry; + + if (!queue || !function) + return; + + entry = queue->head; + if (!entry) + return; + + queue_ref(queue); + while (entry && queue->head && queue->ref_count > 1) { + struct queue_entry *next; + + next = entry->next; + function(entry->data, user_data); + entry = next; + } + queue_unref(queue); +} + +static bool direct_match(const void *a, const void *b) +{ + return a == b; +} + +void *queue_find(struct queue *queue, queue_match_func_t function, + const void *match_data) +{ + struct queue_entry *entry; + + if (!queue) + return NULL; + + if (!function) + function = direct_match; + + for (entry = queue->head; entry; entry = entry->next) + if (function(entry->data, match_data)) + return entry->data; + + return NULL; +} + +bool queue_remove(struct queue *queue, void *data) +{ + struct queue_entry *entry, *prev; + + if (!queue) + return false; + + for (entry = queue->head, prev = NULL; entry; + prev = entry, entry = entry->next) { + if (entry->data != data) + continue; + + if (prev) + prev->next = entry->next; + else + queue->head = entry->next; + + if (!entry->next) + queue->tail = prev; + + free(entry); + queue->entries--; + + return true; + } + + return false; +} + +void *queue_remove_if(struct queue *queue, queue_match_func_t function, + void *user_data) +{ + struct queue_entry *entry, *prev = NULL; + + if (!queue) + return NULL; + + if (!function) + function = direct_match; + + entry = queue->head; + + while (entry) { + if (function(entry->data, user_data)) { + void *data; + + if (prev) + prev->next = entry->next; + else + queue->head = entry->next; + + if (!entry->next) + queue->tail = prev; + + data = entry->data; + + free(entry); + queue->entries--; + + return data; + } else { + prev = entry; + entry = entry->next; + } + } + + return NULL; +} + +unsigned int queue_remove_all(struct queue *queue, queue_match_func_t function, + void *user_data, queue_destroy_func_t destroy) +{ + struct queue_entry *entry; + unsigned int count = 0; + + if (!queue) + return 0; + + entry = queue->head; + + if (function) { + while (entry) { + void *data; + unsigned int entries = queue->entries; + + data = queue_remove_if(queue, function, user_data); + if (entries == queue->entries) + break; + + if (destroy) + destroy(data); + + count++; + } + } else { + queue->head = NULL; + queue->tail = NULL; + queue->entries = 0; + + while (entry) { + struct queue_entry *tmp = entry; + + entry = entry->next; + + if (destroy) + destroy(tmp->data); + + free(tmp); + count++; + } + } + + return count; +} + +const struct queue_entry *queue_get_entries(struct queue *queue) +{ + if (!queue) + return NULL; + + return queue->head; +} + +unsigned int queue_length(struct queue *queue) +{ + if (!queue) + return 0; + + return queue->entries; +} + +bool queue_isempty(struct queue *queue) +{ + if (!queue) + return true; + + return queue->entries == 0; +} diff --git a/src/shared/queue.h b/src/shared/queue.h new file mode 100644 index 0000000..8cd817c --- /dev/null +++ b/src/shared/queue.h @@ -0,0 +1,64 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include + +typedef void (*queue_destroy_func_t)(void *data); + +struct queue; + +struct queue_entry { + void *data; + struct queue_entry *next; +}; + +struct queue *queue_new(void); +void queue_destroy(struct queue *queue, queue_destroy_func_t destroy); + +bool queue_push_tail(struct queue *queue, void *data); +bool queue_push_head(struct queue *queue, void *data); +bool queue_push_after(struct queue *queue, void *entry, void *data); +void *queue_pop_head(struct queue *queue); +void *queue_peek_head(struct queue *queue); +void *queue_peek_tail(struct queue *queue); + +typedef void (*queue_foreach_func_t)(void *data, void *user_data); + +void queue_foreach(struct queue *queue, queue_foreach_func_t function, + void *user_data); + +typedef bool (*queue_match_func_t)(const void *data, const void *match_data); + +void *queue_find(struct queue *queue, queue_match_func_t function, + const void *match_data); + +bool queue_remove(struct queue *queue, void *data); +void *queue_remove_if(struct queue *queue, queue_match_func_t function, + void *user_data); +unsigned int queue_remove_all(struct queue *queue, queue_match_func_t function, + void *user_data, queue_destroy_func_t destroy); + +const struct queue_entry *queue_get_entries(struct queue *queue); + +unsigned int queue_length(struct queue *queue); +bool queue_isempty(struct queue *queue); diff --git a/src/shared/ringbuf.c b/src/shared/ringbuf.c new file mode 100644 index 0000000..8cf0b5b --- /dev/null +++ b/src/shared/ringbuf.c @@ -0,0 +1,310 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "src/shared/util.h" +#include "src/shared/ringbuf.h" + +#ifndef MIN +#define MIN(x,y) ((x)<(y)?(x):(y)) +#endif + +struct ringbuf { + void *buffer; + size_t size; + size_t in; + size_t out; + ringbuf_tracing_func_t in_tracing; + void *in_data; +}; + +#define RINGBUF_RESET 0 + +/* Find last (most siginificant) set bit */ +static inline unsigned int fls(unsigned int x) +{ + return x ? sizeof(x) * 8 - __builtin_clz(x) : 0; +} + +/* Round up to nearest power of two */ +static inline unsigned int align_power2(unsigned int u) +{ + return 1 << fls(u - 1); +} + +struct ringbuf *ringbuf_new(size_t size) +{ + struct ringbuf *ringbuf; + size_t real_size; + + if (size < 2 || size > UINT_MAX) + return NULL; + + /* Find the next power of two for size */ + real_size = align_power2(size); + + ringbuf = new0(struct ringbuf, 1); + ringbuf->buffer = malloc(real_size); + if (!ringbuf->buffer) { + free(ringbuf); + return NULL; + } + + ringbuf->size = real_size; + ringbuf->in = RINGBUF_RESET; + ringbuf->out = RINGBUF_RESET; + + return ringbuf; +} + +void ringbuf_free(struct ringbuf *ringbuf) +{ + if (!ringbuf) + return; + + free(ringbuf->buffer); + free(ringbuf); +} + +bool ringbuf_set_input_tracing(struct ringbuf *ringbuf, + ringbuf_tracing_func_t callback, void *user_data) +{ + if (!ringbuf) + return false; + + ringbuf->in_tracing = callback; + ringbuf->in_data = user_data; + + return true; +} + +size_t ringbuf_capacity(struct ringbuf *ringbuf) +{ + if (!ringbuf) + return 0; + + return ringbuf->size; +} + +size_t ringbuf_len(struct ringbuf *ringbuf) +{ + if (!ringbuf) + return 0; + + return ringbuf->in - ringbuf->out; +} + +size_t ringbuf_drain(struct ringbuf *ringbuf, size_t count) +{ + size_t len; + + if (!ringbuf) + return 0; + + len = MIN(count, ringbuf->in - ringbuf->out); + if (!len) + return 0; + + ringbuf->out += len; + + if (ringbuf->out == ringbuf->in) { + ringbuf->in = RINGBUF_RESET; + ringbuf->out = RINGBUF_RESET; + } + + return len; +} + +void *ringbuf_peek(struct ringbuf *ringbuf, size_t offset, size_t *len_nowrap) +{ + if (!ringbuf) + return NULL; + + offset = (ringbuf->out + offset) & (ringbuf->size - 1); + + if (len_nowrap) { + size_t len = ringbuf->in - ringbuf->out; + *len_nowrap = MIN(len, ringbuf->size - offset); + } + + return ringbuf->buffer + offset; +} + +ssize_t ringbuf_write(struct ringbuf *ringbuf, int fd) +{ + size_t len, offset, end; + struct iovec iov[2]; + ssize_t consumed; + + if (!ringbuf || fd < 0) + return -1; + + /* Determine how much data is available */ + len = ringbuf->in - ringbuf->out; + if (!len) + return 0; + + /* Grab data from buffer starting at offset until the end */ + offset = ringbuf->out & (ringbuf->size - 1); + end = MIN(len, ringbuf->size - offset); + + iov[0].iov_base = ringbuf->buffer + offset; + iov[0].iov_len = end; + + /* Use second vector for remainder from the beginning */ + iov[1].iov_base = ringbuf->buffer; + iov[1].iov_len = len - end; + + consumed = writev(fd, iov, 2); + if (consumed < 0) + return -1; + + ringbuf->out += consumed; + + if (ringbuf->out == ringbuf->in) { + ringbuf->in = RINGBUF_RESET; + ringbuf->out = RINGBUF_RESET; + } + + return consumed; +} + +size_t ringbuf_avail(struct ringbuf *ringbuf) +{ + if (!ringbuf) + return 0; + + return ringbuf->size - ringbuf->in + ringbuf->out; +} + +int ringbuf_printf(struct ringbuf *ringbuf, const char *format, ...) +{ + va_list ap; + int len; + + va_start(ap, format); + len = ringbuf_vprintf(ringbuf, format, ap); + va_end(ap); + + return len; +} + +int ringbuf_vprintf(struct ringbuf *ringbuf, const char *format, va_list ap) +{ + size_t avail, offset, end; + char *str; + int len; + + if (!ringbuf || !format) + return -1; + + /* Determine maximum length available for string */ + avail = ringbuf->size - ringbuf->in + ringbuf->out; + if (!avail) + return -1; + + len = vasprintf(&str, format, ap); + if (len < 0) + return -1; + + if ((size_t) len > avail) { + free(str); + return -1; + } + + /* Determine possible length of string before wrapping */ + offset = ringbuf->in & (ringbuf->size - 1); + end = MIN((size_t) len, ringbuf->size - offset); + memcpy(ringbuf->buffer + offset, str, end); + + if (ringbuf->in_tracing) + ringbuf->in_tracing(ringbuf->buffer + offset, end, + ringbuf->in_data); + + if (len - end > 0) { + /* Put the remainder of string at the beginning */ + memcpy(ringbuf->buffer, str + end, len - end); + + if (ringbuf->in_tracing) + ringbuf->in_tracing(ringbuf->buffer, len - end, + ringbuf->in_data); + } + + free(str); + + ringbuf->in += len; + + return len; +} + +ssize_t ringbuf_read(struct ringbuf *ringbuf, int fd) +{ + size_t avail, offset, end; + struct iovec iov[2]; + ssize_t consumed; + + if (!ringbuf || fd < 0) + return -1; + + /* Determine how much can actually be consumed */ + avail = ringbuf->size - ringbuf->in + ringbuf->out; + if (!avail) + return -1; + + /* Determine how much to consume before wrapping */ + offset = ringbuf->in & (ringbuf->size - 1); + end = MIN(avail, ringbuf->size - offset); + + iov[0].iov_base = ringbuf->buffer + offset; + iov[0].iov_len = end; + + /* Now put the remainder into the second vector */ + iov[1].iov_base = ringbuf->buffer; + iov[1].iov_len = avail - end; + + consumed = readv(fd, iov, 2); + if (consumed < 0) + return -1; + + if (ringbuf->in_tracing) { + size_t len = MIN((size_t) consumed, end); + ringbuf->in_tracing(ringbuf->buffer + offset, len, + ringbuf->in_data); + if (consumed - len > 0) + ringbuf->in_tracing(ringbuf->buffer, consumed - len, + ringbuf->in_data); + } + + ringbuf->in += consumed; + + return consumed; +} diff --git a/src/shared/ringbuf.h b/src/shared/ringbuf.h new file mode 100644 index 0000000..adf471a --- /dev/null +++ b/src/shared/ringbuf.h @@ -0,0 +1,50 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include +#include + +typedef void (*ringbuf_tracing_func_t)(const void *buf, size_t count, + void *user_data); + +struct ringbuf; + +struct ringbuf *ringbuf_new(size_t size); +void ringbuf_free(struct ringbuf *ringbuf); + +bool ringbuf_set_input_tracing(struct ringbuf *ringbuf, + ringbuf_tracing_func_t callback, void *user_data); + +size_t ringbuf_capacity(struct ringbuf *ringbuf); + +size_t ringbuf_len(struct ringbuf *ringbuf); +size_t ringbuf_drain(struct ringbuf *ringbuf, size_t count); +void *ringbuf_peek(struct ringbuf *ringbuf, size_t offset, size_t *len_nowrap); +ssize_t ringbuf_write(struct ringbuf *ringbuf, int fd); + +size_t ringbuf_avail(struct ringbuf *ringbuf); +int ringbuf_printf(struct ringbuf *ringbuf, const char *format, ...) + __attribute__((format(printf, 2, 3))); +int ringbuf_vprintf(struct ringbuf *ringbuf, const char *format, va_list ap); +ssize_t ringbuf_read(struct ringbuf *ringbuf, int fd); diff --git a/src/shared/shell.c b/src/shared/shell.c new file mode 100644 index 0000000..cfdcc76 --- /dev/null +++ b/src/shared/shell.c @@ -0,0 +1,1356 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2017 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "src/shared/mainloop.h" +#include "src/shared/timeout.h" +#include "src/shared/io.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/shell.h" +#include "src/shared/log.h" + +#define CMD_LENGTH 48 +#define print_text(color, fmt, args...) \ + printf(color fmt COLOR_OFF "\n", ## args) +#define print_menu(cmd, args, desc) \ + printf(COLOR_HIGHLIGHT "%s %-*s " COLOR_OFF "%s\n", \ + cmd, (int)(CMD_LENGTH - strlen(cmd)), args, desc) +#define print_submenu(cmd, desc) \ + printf(COLOR_BLUE "%s %-*s " COLOR_OFF "%s\n", \ + cmd, (int)(CMD_LENGTH - strlen(cmd)), "", desc) + +struct bt_shell_env { + char *name; + void *value; +}; + +static char *cmplt = "help"; + +struct bt_shell_prompt_input { + char *str; + bt_shell_prompt_input_func func; + void *user_data; +}; + +static struct { + bool init; + char *name; + char history[256]; + int argc; + char **argv; + bool mode; + bool zsh; + bool monitor; + int timeout; + struct io *input; + + bool saved_prompt; + bt_shell_prompt_input_func saved_func; + void *saved_user_data; + + struct queue *prompts; + + const struct bt_shell_menu *menu; + const struct bt_shell_menu *main; + struct queue *submenus; + const struct bt_shell_menu_entry *exec; + + struct queue *envs; +} data; + +static void shell_print_menu(void); +static void shell_print_menu_zsh_complete(void); + +static void cmd_version(int argc, char *argv[]) +{ + bt_shell_printf("Version %s\n", VERSION); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_quit(int argc, char *argv[]) +{ + mainloop_quit(); +} + +static void print_cmds(void) +{ + const struct bt_shell_menu_entry *entry; + const struct queue_entry *submenu; + + if (!data.menu) + return; + + printf("Commands:\n"); + + for (entry = data.menu->entries; entry->cmd; entry++) { + printf("\t%s%s\t%s\n", entry->cmd, + strlen(entry->cmd) < 8 ? "\t" : "", entry->desc); + } + + for (submenu = queue_get_entries(data.submenus); submenu; + submenu = submenu->next) { + struct bt_shell_menu *menu = submenu->data; + + printf("\n\t%s.:\n", menu->name); + + for (entry = menu->entries; entry->cmd; entry++) { + printf("\t\t%s%s\t%s\n", entry->cmd, + strlen(entry->cmd) < 8 ? "\t" : "", + entry->desc); + } + } +} + +static void cmd_help(int argc, char *argv[]) +{ + if (argv[0] == cmplt) + print_cmds(); + else + shell_print_menu(); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static const struct bt_shell_menu *find_menu(const char *name, size_t len) +{ + const struct queue_entry *entry; + + for (entry = queue_get_entries(data.submenus); entry; + entry = entry->next) { + struct bt_shell_menu *menu = entry->data; + + if (!strncmp(menu->name, name, len)) + return menu; + } + + return NULL; +} + +static char *menu_generator(const char *text, int state) +{ + static unsigned int index, len; + static struct queue_entry *entry; + + if (!state) { + index = 0; + len = strlen(text); + entry = (void *) queue_get_entries(data.submenus); + } + + for (; entry; entry = entry->next) { + struct bt_shell_menu *menu = entry->data; + + index++; + + if (!strncmp(menu->name, text, len)) { + entry = entry->next; + return strdup(menu->name); + } + } + + return NULL; +} + +static void cmd_menu(int argc, char *argv[]) +{ + const struct bt_shell_menu *menu; + + if (argc < 2 || !strlen(argv[1])) { + bt_shell_printf("Missing name argument\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + menu = find_menu(argv[1], strlen(argv[1])); + if (!menu) { + bt_shell_printf("Unable find menu with name: %s\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_set_menu(menu); + + shell_print_menu(); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static bool cmd_menu_exists(const struct bt_shell_menu *menu) +{ + /* Skip menu command if not on main menu or if there are no + * submenus. + */ + if (menu != data.main || queue_isempty(data.submenus)) + return false; + + return true; +} + +static void cmd_back(int argc, char *argv[]) +{ + if (data.menu == data.main) { + bt_shell_printf("Already on main menu\n"); + return; + } + + bt_shell_set_menu(data.main); + + shell_print_menu(); +} + +static bool cmd_back_exists(const struct bt_shell_menu *menu) +{ + /* Skip back command if on main menu */ + if (menu == data.main) + return false; + + return true; +} + +static void cmd_export(int argc, char *argv[]) +{ + const struct queue_entry *entry; + + for (entry = queue_get_entries(data.envs); entry; entry = entry->next) { + struct bt_shell_env *env = entry->data; + + print_text(COLOR_HIGHLIGHT, "%s=%p", env->name, env->value); + } +} + +static const struct bt_shell_menu_entry default_menu[] = { + { "back", NULL, cmd_back, "Return to main menu", NULL, + NULL, cmd_back_exists }, + { "menu", "", cmd_menu, "Select submenu", + menu_generator, NULL, + cmd_menu_exists}, + { "version", NULL, cmd_version, "Display version" }, + { "quit", NULL, cmd_quit, "Quit program" }, + { "exit", NULL, cmd_quit, "Quit program" }, + { "help", NULL, cmd_help, + "Display help about this program" }, + { "export", NULL, cmd_export, + "Print environment variables" }, + { } +}; + +static void shell_print_help(void) +{ + print_text(COLOR_HIGHLIGHT, + "\n" + "Use \"help\" for a list of available commands in a menu.\n" + "Use \"menu \" if you want to enter any submenu.\n" + "Use \"back\" if you want to return to menu main."); +} + +static void shell_print_menu(void) +{ + const struct bt_shell_menu_entry *entry; + const struct queue_entry *submenu; + + if (!data.menu) + return; + + if (data.zsh) { + shell_print_menu_zsh_complete(); + return; + } + + print_text(COLOR_HIGHLIGHT, "Menu %s:", data.menu->name); + print_text(COLOR_HIGHLIGHT, "Available commands:"); + print_text(COLOR_HIGHLIGHT, "-------------------"); + + if (data.menu == data.main) { + for (submenu = queue_get_entries(data.submenus); submenu; + submenu = submenu->next) { + struct bt_shell_menu *menu = submenu->data; + + print_submenu(menu->name, menu->desc ? menu->desc : + "Submenu"); + } + } + + for (entry = data.menu->entries; entry->cmd; entry++) { + print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); + } + + for (entry = default_menu; entry->cmd; entry++) { + if (entry->exists && !entry->exists(data.menu)) + continue; + + print_menu(entry->cmd, entry->arg ? : "", entry->desc ? : ""); + } +} + +static void shell_print_menu_zsh_complete(void) +{ + const struct bt_shell_menu_entry *entry; + + for (entry = data.menu->entries; entry->cmd; entry++) + printf("%s:%s\n", entry->cmd, entry->desc ? : ""); + + for (entry = default_menu; entry->cmd; entry++) { + if (entry->exists && !entry->exists(data.menu)) + continue; + + printf("%s:%s\n", entry->cmd, entry->desc ? : ""); + } +} + +static int parse_args(char *arg, wordexp_t *w, char *del, int flags) +{ + char *str; + + str = strdelimit(arg, del, '"'); + + if (wordexp(str, w, flags)) { + free(str); + return -EINVAL; + } + + /* If argument ends with ... set we_offs bypass strict checks */ + if (w->we_wordc && !strsuffix(w->we_wordv[w->we_wordc -1], "...")) + w->we_offs = 1; + + free(str); + + return 0; +} + +static int cmd_exec(const struct bt_shell_menu_entry *entry, + int argc, char *argv[]) +{ + wordexp_t w; + size_t len; + char *man, *opt; + int flags = WRDE_NOCMD; + bool optargs = false; + + if (!entry->arg || entry->arg[0] == '\0') { + if (argc > 1) { + print_text(COLOR_HIGHLIGHT, "Too many arguments"); + return -EINVAL; + } + goto exec; + } + + /* Find last mandatory arguments */ + man = strrchr(entry->arg, '>'); + if (!man) { + opt = strdup(entry->arg); + goto optional; + } + + len = man - entry->arg; + if (entry->arg[0] == '<') + man = strndup(entry->arg, len + 1); + else { + /* Find where mandatory arguments start */ + opt = strrchr(entry->arg, '<'); + /* Skip if mandatory arguments are not in the right format */ + if (!opt || opt > man) { + opt = strdup(entry->arg); + goto optional; + } + man = strndup(opt, man - opt + 1); + optargs = true; + } + + if (parse_args(man, &w, "<>", flags) < 0) { + print_text(COLOR_HIGHLIGHT, + "Unable to parse mandatory command arguments: %s", man ); + free(man); + return -EINVAL; + } + + free(man); + + /* Check if there are enough arguments */ + if ((unsigned) argc - 1 < w.we_wordc) { + print_text(COLOR_HIGHLIGHT, "Missing %s argument", + w.we_wordv[argc - 1]); + goto fail; + } + + flags |= WRDE_APPEND; + opt = strdup(entry->arg + len + 1); + +optional: + if (parse_args(opt, &w, "[]", flags) < 0) { + print_text(COLOR_HIGHLIGHT, + "Unable to parse optional command arguments: %s", opt); + free(opt); + return -EINVAL; + } + + free(opt); + + /* Check if there are too many arguments */ + if (!optargs && ((unsigned int) argc - 1 > w.we_wordc && !w.we_offs)) { + print_text(COLOR_HIGHLIGHT, "Too many arguments: %d > %zu", + argc - 1, w.we_wordc); + goto fail; + } + + w.we_offs = 0; + wordfree(&w); + +exec: + data.exec = entry; + + if (entry->func) + entry->func(argc, argv); + + data.exec = NULL; + + return 0; + +fail: + w.we_offs = 0; + wordfree(&w); + return -EINVAL; +} + +static int menu_exec(const struct bt_shell_menu_entry *entry, + int argc, char *argv[]) +{ + for (; entry->cmd; entry++) { + if (strcmp(argv[0], entry->cmd)) + continue; + + /* Skip menu command if not on main menu */ + if (data.menu != data.main && !strcmp(entry->cmd, "menu")) + continue; + + /* Skip back command if on main menu */ + if (data.menu == data.main && !strcmp(entry->cmd, "back")) + continue; + + return cmd_exec(entry, argc, argv); + } + + return -ENOENT; +} + +static int submenu_exec(int argc, char *argv[]) +{ + char *name; + int len, tlen; + const struct bt_shell_menu *submenu; + + if (data.menu != data.main) + return -ENOENT; + + name = strchr(argv[0], '.'); + if (!name) + return -ENOENT; + + tlen = strlen(argv[0]); + len = name - argv[0]; + name[0] = '\0'; + + submenu = find_menu(argv[0], strlen(argv[0])); + if (!submenu) + return -ENOENT; + + /* Replace submenu.command with command */ + memmove(argv[0], argv[0] + len + 1, tlen - len - 1); + memset(argv[0] + tlen - len - 1, 0, len + 1); + + return menu_exec(submenu->entries, argc, argv); +} + +static int shell_exec(int argc, char *argv[]) +{ + int err; + + if (!data.menu || !argv[0]) + return -EINVAL; + + err = menu_exec(default_menu, argc, argv); + if (err == -ENOENT) { + err = menu_exec(data.menu->entries, argc, argv); + if (err == -ENOENT) { + err = submenu_exec(argc, argv); + if (err == -ENOENT) { + print_text(COLOR_HIGHLIGHT, + "Invalid command in menu %s: %s", + data.menu->name , argv[0]); + shell_print_help(); + } + } + } + + return err; +} + +void bt_shell_printf(const char *fmt, ...) +{ + va_list args; + bool save_input; + char *saved_line; + int saved_point; + + if (!data.input) + return; + + if (data.mode) { + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + return; + } + + save_input = !RL_ISSTATE(RL_STATE_DONE); + + if (save_input) { + saved_point = rl_point; + saved_line = rl_copy_text(0, rl_end); + if (!data.saved_prompt) + rl_save_prompt(); + rl_replace_line("", 0); + rl_redisplay(); + } + + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + + if (data.monitor) { + va_start(args, fmt); + bt_log_vprintf(0xffff, data.name, LOG_INFO, fmt, args); + va_end(args); + } + + if (save_input) { + if (!data.saved_prompt) + rl_restore_prompt(); + rl_replace_line(saved_line, 0); + rl_point = saved_point; + rl_forced_update_display(); + free(saved_line); + } +} + +static void print_string(const char *str, void *user_data) +{ + bt_shell_printf("%s\n", str); +} + +void bt_shell_hexdump(const unsigned char *buf, size_t len) +{ + util_hexdump(' ', buf, len, print_string, NULL); +} + +void bt_shell_usage() +{ + if (!data.exec) + return; + + bt_shell_printf("Usage: %s %s\n", data.exec->cmd, + data.exec->arg ? data.exec->arg : ""); +} + +static void prompt_input(const char *str, bt_shell_prompt_input_func func, + void *user_data) +{ + data.saved_prompt = true; + data.saved_func = func; + data.saved_user_data = user_data; + + rl_save_prompt(); + bt_shell_set_prompt(str); +} + +void bt_shell_prompt_input(const char *label, const char *msg, + bt_shell_prompt_input_func func, void *user_data) +{ + char *str; + + if (!data.init || data.mode) + return; + + if (data.saved_prompt) { + struct bt_shell_prompt_input *prompt; + + prompt = new0(struct bt_shell_prompt_input, 1); + + if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label, + msg) < 0) { + free(prompt); + return; + } + + prompt->func = func; + prompt->user_data = user_data; + + queue_push_tail(data.prompts, prompt); + + return; + } + + if (asprintf(&str, COLOR_HIGHLIGHT "[%s] %s " COLOR_OFF, label, + msg) < 0) + return; + + prompt_input(str, func, user_data); + + free(str); +} + +static void prompt_free(void *data) +{ + struct bt_shell_prompt_input *prompt = data; + + free(prompt->str); + free(prompt); +} + +int bt_shell_release_prompt(const char *input) +{ + struct bt_shell_prompt_input *prompt; + bt_shell_prompt_input_func func; + void *user_data; + + if (!data.saved_prompt) + return -1; + + data.saved_prompt = false; + + rl_restore_prompt(); + + func = data.saved_func; + user_data = data.saved_user_data; + + prompt = queue_pop_head(data.prompts); + if (prompt) + data.saved_prompt = true; + + data.saved_func = NULL; + data.saved_user_data = NULL; + + func(input, user_data); + + if (prompt) { + prompt_input(prompt->str, prompt->func, prompt->user_data); + prompt_free(prompt); + } + + return 0; +} + +static void rl_handler(char *input) +{ + wordexp_t w; + + if (!input) { + rl_insert_text("quit"); + rl_redisplay(); + rl_crlf(); + mainloop_quit(); + return; + } + + if (!strlen(input)) + goto done; + + if (!bt_shell_release_prompt(input)) + goto done; + + if (history_search(input, -1)) + add_history(input); + + if (data.monitor) + bt_log_printf(0xffff, data.name, LOG_INFO, "%s", input); + + if (wordexp(input, &w, WRDE_NOCMD)) + goto done; + + if (w.we_wordc == 0) { + wordfree(&w); + goto done; + } + + shell_exec(w.we_wordc, w.we_wordv); + wordfree(&w); +done: + free(input); +} + +static char *find_cmd(const char *text, + const struct bt_shell_menu_entry *entry, int *index) +{ + const struct bt_shell_menu_entry *tmp; + int len; + + len = strlen(text); + + while ((tmp = &entry[*index])) { + (*index)++; + + if (!tmp->cmd) + break; + + if (tmp->exists && !tmp->exists(data.menu)) + continue; + + if (!strncmp(tmp->cmd, text, len)) + return strdup(tmp->cmd); + } + + return NULL; +} + +static char *cmd_generator(const char *text, int state) +{ + static int index; + static bool default_menu_enabled, submenu_enabled; + static const struct bt_shell_menu *menu; + char *cmd; + + if (!state) { + index = 0; + menu = NULL; + default_menu_enabled = true; + submenu_enabled = false; + } + + if (default_menu_enabled) { + cmd = find_cmd(text, default_menu, &index); + if (cmd) { + return cmd; + } else { + index = 0; + menu = data.menu; + default_menu_enabled = false; + } + } + + if (!submenu_enabled) { + cmd = find_cmd(text, menu->entries, &index); + if (cmd || menu != data.main) + return cmd; + + cmd = strrchr(text, '.'); + if (!cmd) + return NULL; + + menu = find_menu(text, cmd - text); + if (!menu) + return NULL; + + index = 0; + submenu_enabled = true; + } + + cmd = find_cmd(text + strlen(menu->name) + 1, menu->entries, &index); + if (cmd) { + int err; + char *tmp; + + err = asprintf(&tmp, "%s.%s", menu->name, cmd); + + free(cmd); + + if (err < 0) + return NULL; + + cmd = tmp; + } + + return cmd; +} + +static wordexp_t args; + +static char *arg_generator(const char *text, int state) +{ + static unsigned int index, len; + const char *arg; + + if (!state) { + index = 0; + len = strlen(text); + } + + while (index < args.we_wordc) { + arg = args.we_wordv[index]; + index++; + + if (!strncmp(arg, text, len)) + return strdup(arg); + } + + return NULL; +} + +static char **args_completion(const struct bt_shell_menu_entry *entry, int argc, + const char *text) +{ + char **matches = NULL; + char *str; + int index; + + index = text[0] == '\0' ? argc - 1 : argc - 2; + if (index < 0) + return NULL; + + if (!entry->arg) + goto end; + + str = strdup(entry->arg); + + if (parse_args(str, &args, "<>[]", WRDE_NOCMD)) + goto done; + + /* Check if argument is valid */ + if ((unsigned) index > args.we_wordc - 1) + goto done; + + /* Check if there are multiple values */ + if (!strrchr(entry->arg, '/')) + goto done; + + free(str); + + /* Split values separated by / */ + str = strdelimit(args.we_wordv[index], "/", ' '); + + args.we_offs = 0; + wordfree(&args); + + if (wordexp(str, &args, WRDE_NOCMD)) + goto done; + + rl_completion_display_matches_hook = NULL; + matches = rl_completion_matches(text, arg_generator); + +done: + free(str); +end: + if (!matches && text[0] == '\0') + bt_shell_printf("Usage: %s %s\n", entry->cmd, + entry->arg ? entry->arg : ""); + + args.we_offs = 0; + wordfree(&args); + return matches; +} + +static char **menu_completion(const struct bt_shell_menu_entry *entry, + const char *text, int argc, char *input_cmd) +{ + char **matches = NULL; + + for (; entry->cmd; entry++) { + if (strcmp(entry->cmd, input_cmd)) + continue; + + if (!entry->gen) { + matches = args_completion(entry, argc, text); + break; + } + + rl_completion_display_matches_hook = entry->disp; + matches = rl_completion_matches(text, entry->gen); + break; + } + + return matches; +} + +static char **shell_completion(const char *text, int start, int end) +{ + char **matches = NULL; + + if (!data.menu) + return NULL; + + if (start > 0) { + wordexp_t w; + + if (wordexp(rl_line_buffer, &w, WRDE_NOCMD)) + return NULL; + + matches = menu_completion(default_menu, text, w.we_wordc, + w.we_wordv[0]); + if (!matches) + matches = menu_completion(data.menu->entries, text, + w.we_wordc, + w.we_wordv[0]); + + wordfree(&w); + } else { + rl_completion_display_matches_hook = NULL; + matches = rl_completion_matches(text, cmd_generator); + } + + if (!matches) + rl_attempted_completion_over = 1; + + return matches; +} + +static bool io_hup(struct io *io, void *user_data) +{ + mainloop_quit(); + + return false; +} + +static void signal_callback(int signum, void *user_data) +{ + static bool terminated = false; + + switch (signum) { + case SIGINT: + if (data.input && !data.mode) { + rl_replace_line("", 0); + rl_crlf(); + rl_on_new_line(); + rl_redisplay(); + return; + } + + /* + * If input was not yet setup up that means signal was received + * while daemon was not yet running. Since user is not able + * to terminate client by CTRL-D or typing exit treat this as + * exit and fall through. + */ + + /* fall through */ + case SIGTERM: + if (!terminated) { + if (!data.mode) { + rl_replace_line("", 0); + rl_crlf(); + } + mainloop_quit(); + } + + terminated = true; + break; + } +} + +static void rl_init_history(void) +{ + const char *name; + char *dir; + + memset(data.history, 0, sizeof(data.history)); + + name = strrchr(data.name, '/'); + if (!name) + name = data.name; + else + name++; + + dir = getenv("XDG_CACHE_HOME"); + if (dir) { + snprintf(data.history, sizeof(data.history), "%s/.%s_history", + dir, name); + goto done; + } + + dir = getenv("HOME"); + if (dir) { + snprintf(data.history, sizeof(data.history), + "%s/.cache/.%s_history", dir, name); + goto done; + } + + dir = getenv("PWD"); + if (dir) { + snprintf(data.history, sizeof(data.history), "%s/.%s_history", + dir, name); + goto done; + } + + return; + +done: + read_history(data.history); + using_history(); + bt_shell_set_env("HISTORY", data.history); +} + +static void rl_init(void) +{ + if (data.mode) + return; + + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name = data.name; + + setlinebuf(stdout); + rl_attempted_completion_function = shell_completion; + + rl_erase_empty_line = 1; + rl_callback_handler_install(NULL, rl_handler); + + rl_init_history(); +} + +static const struct option main_options[] = { + { "version", no_argument, 0, 'v' }, + { "help", no_argument, 0, 'h' }, + { "timeout", required_argument, 0, 't' }, + { "monitor", no_argument, 0, 'm' }, + { "zsh-complete", no_argument, 0, 'z' }, +}; + +static void usage(int argc, char **argv, const struct bt_shell_opt *opt) +{ + unsigned int i; + + printf("%s ver %s\n", data.name, VERSION); + printf("Usage:\n" + "\t%s [--options] [commands]\n", data.name); + + printf("Options:\n"); + + for (i = 0; opt && opt->options[i].name; i++) + printf("\t--%s \t%s\n", opt->options[i].name, opt->help[i]); + + printf("\t--monitor \tEnable monitor output\n" + "\t--timeout \tTimeout in seconds for non-interactive mode\n" + "\t--version \tDisplay version\n" + "\t--help \t\tDisplay help\n"); +} + +void bt_shell_init(int argc, char **argv, const struct bt_shell_opt *opt) +{ + int c, index = -1; + struct option options[256]; + char optstr[256]; + size_t offset; + + offset = sizeof(main_options) / sizeof(struct option); + + memcpy(options, main_options, sizeof(struct option) * offset); + + if (opt) { + memcpy(options + offset, opt->options, + sizeof(struct option) * opt->optno); + snprintf(optstr, sizeof(optstr), "+mhvt:%s", opt->optstr); + } else + snprintf(optstr, sizeof(optstr), "+mhvt:"); + + data.name = strrchr(argv[0], '/'); + if (!data.name) + data.name = strdup(argv[0]); + else + data.name = strdup(++data.name); + + while ((c = getopt_long(argc, argv, optstr, options, &index)) != -1) { + switch (c) { + case 'v': + printf("%s: %s\n", data.name, VERSION); + exit(EXIT_SUCCESS); + return; + case 'h': + usage(argc, argv, opt); + data.argc = 1; + data.argv = &cmplt; + data.mode = 1; + goto done; + case 't': + data.timeout = atoi(optarg); + break; + case 'z': + data.zsh = 1; + break; + case 'm': + data.monitor = true; + if (bt_log_open() < 0) { + data.monitor = false; + printf("Unable to open logging channel\n"); + } + break; + default: + if (index < 0) { + for (index = 0; options[index].val; index++) { + if (c == options[index].val) + break; + } + } + + if (c != opt->options[index - offset].val) { + usage(argc, argv, opt); + exit(EXIT_SUCCESS); + return; + } + + *opt->optarg[index - offset] = optarg; + } + + index = -1; + } + + bt_shell_set_env("SHELL", data.name); + + data.argc = argc - optind; + data.argv = argv + optind; + optind = 0; + data.mode = (data.argc > 0); + +done: + if (data.mode) + bt_shell_set_env("NON_INTERACTIVE", &data.mode); + + mainloop_init(); + + rl_init(); + + data.init = true; + data.prompts = queue_new(); +} + +static void rl_cleanup(void) +{ + if (data.mode) + return; + + if (data.history[0] != '\0') + write_history(data.history); + + rl_message(""); + rl_callback_handler_remove(); +} + +static void env_destroy(void *data) +{ + struct bt_shell_env *env = data; + + free(env->name); + free(env); +} + +int bt_shell_run(void) +{ + int status; + + status = mainloop_run_with_signal(signal_callback, NULL); + + bt_shell_cleanup(); + + return status; +} + +void bt_shell_cleanup(void) +{ + bt_shell_release_prompt(""); + bt_shell_detach(); + + if (data.envs) { + queue_destroy(data.envs, env_destroy); + data.envs = NULL; + } + + if (data.monitor) + bt_log_close(); + + rl_cleanup(); + + queue_destroy(data.prompts, prompt_free); + data.prompts = NULL; + + data.init = false; + free(data.name); +} + +void bt_shell_quit(int status) +{ + if (status == EXIT_SUCCESS) + mainloop_exit_success(); + else + mainloop_exit_failure(); +} + +void bt_shell_noninteractive_quit(int status) +{ + if (!data.mode || data.timeout) + return; + + bt_shell_quit(status); +} + +bool bt_shell_set_menu(const struct bt_shell_menu *menu) +{ + if (!menu) + return false; + + data.menu = menu; + + if (!data.main) + data.main = menu; + + return true; +} + +bool bt_shell_add_submenu(const struct bt_shell_menu *menu) +{ + if (!menu) + return false; + + if (!data.submenus) + data.submenus = queue_new(); + + queue_push_tail(data.submenus, (void *) menu); + + return true; +} + +void bt_shell_set_prompt(const char *string) +{ + if (!data.init || data.mode) + return; + + rl_set_prompt(string); + rl_redisplay(); +} + +static bool input_read(struct io *io, void *user_data) +{ + rl_callback_read_char(); + return true; +} + +static bool shell_quit(void *data) +{ + mainloop_quit(); + + return false; +} + +bool bt_shell_attach(int fd) +{ + struct io *io; + + /* TODO: Allow more than one input? */ + if (data.input) + return false; + + io = io_new(fd); + + if (!data.mode) + io_set_read_handler(io, input_read, NULL, NULL); + + io_set_disconnect_handler(io, io_hup, NULL, NULL); + + data.input = io; + + if (data.mode) { + if (shell_exec(data.argc, data.argv) < 0) { + bt_shell_noninteractive_quit(EXIT_FAILURE); + return true; + } + + if (data.timeout) + timeout_add(data.timeout * 1000, shell_quit, NULL, + NULL); + } + + return true; +} + +bool bt_shell_detach(void) +{ + if (!data.input) + return false; + + io_destroy(data.input); + data.input = NULL; + + return true; +} + +static bool match_env(const void *data, const void *user_data) +{ + const struct bt_shell_env *env = data; + const char *name = user_data; + + return !strcmp(env->name, name); +} + +void bt_shell_set_env(const char *name, void *value) +{ + struct bt_shell_env *env; + + if (!data.envs) { + if (!value) + return; + data.envs = queue_new(); + goto done; + } + + env = queue_remove_if(data.envs, match_env, (void *) name); + if (env) + env_destroy(env); + + /* Don't create an env if value is not set */ + if (!value) + return; + +done: + env = new0(struct bt_shell_env, 1); + env->name = strdup(name); + env->value = value; + + queue_push_tail(data.envs, env); +} + +void *bt_shell_get_env(const char *name) +{ + const struct bt_shell_env *env; + + if (!data.envs) + return NULL; + + env = queue_find(data.envs, match_env, name); + if (!env) + return NULL; + + return env->value; +} diff --git a/src/shared/shell.h b/src/shared/shell.h new file mode 100644 index 0000000..e14d583 --- /dev/null +++ b/src/shared/shell.h @@ -0,0 +1,97 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2017 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 + +#define COLOR_OFF "\001\x1B[0m\002" +#define COLOR_RED "\001\x1B[0;91m\002" +#define COLOR_GREEN "\001\x1B[0;92m\002" +#define COLOR_YELLOW "\001\x1B[0;93m\002" +#define COLOR_BLUE "\001\x1B[0;94m\002" +#define COLOR_BOLDGRAY "\001\x1B[1;30m\002" +#define COLOR_BOLDWHITE "\001\x1B[1;37m\002" +#define COLOR_HIGHLIGHT "\001\x1B[1;39m\002" + +struct bt_shell_menu; + +typedef void (*bt_shell_menu_cb_t)(int argc, char *argv[]); +typedef char * (*bt_shell_menu_gen_t)(const char *text, int state); +typedef void (*bt_shell_menu_disp_t) (char **matches, int num_matches, + int max_length); +typedef void (*bt_shell_prompt_input_func) (const char *input, void *user_data); +typedef bool (*bt_shell_menu_exists_t) (const struct bt_shell_menu *menu); + +struct bt_shell_menu_entry { + const char *cmd; + const char *arg; + bt_shell_menu_cb_t func; + const char *desc; + bt_shell_menu_gen_t gen; + bt_shell_menu_disp_t disp; + bt_shell_menu_exists_t exists; +}; + +struct bt_shell_menu { + const char *name; + const char *desc; + const struct bt_shell_menu_entry entries[]; +}; + +struct bt_shell_opt { + const struct option *options; + size_t optno; + const char *optstr; + const char ***optarg; + const char **help; +}; + +void bt_shell_init(int argc, char **argv, const struct bt_shell_opt *opt); + +int bt_shell_run(void); + +void bt_shell_quit(int status); +void bt_shell_noninteractive_quit(int status); + +bool bt_shell_set_menu(const struct bt_shell_menu *menu); + +bool bt_shell_add_submenu(const struct bt_shell_menu *menu); + +bool bt_shell_remove_submenu(const struct bt_shell_menu *menu); + +void bt_shell_set_prompt(const char *string); + +void bt_shell_printf(const char *fmt, + ...) __attribute__((format(printf, 1, 2))); +void bt_shell_hexdump(const unsigned char *buf, size_t len); +void bt_shell_usage(void); + +void bt_shell_prompt_input(const char *label, const char *msg, + bt_shell_prompt_input_func func, void *user_data); +int bt_shell_release_prompt(const char *input); + +bool bt_shell_attach(int fd); +bool bt_shell_detach(void); + +void bt_shell_set_env(const char *name, void *value); +void *bt_shell_get_env(const char *name); + +void bt_shell_cleanup(void); diff --git a/src/shared/tester.c b/src/shared/tester.c new file mode 100644 index 0000000..62e5c1a --- /dev/null +++ b/src/shared/tester.c @@ -0,0 +1,880 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include "src/shared/mainloop.h" +#include "src/shared/util.h" +#include "src/shared/tester.h" +#include "src/shared/log.h" + +#define COLOR_OFF "\x1B[0m" +#define COLOR_BLACK "\x1B[0;30m" +#define COLOR_RED "\x1B[0;31m" +#define COLOR_GREEN "\x1B[0;32m" +#define COLOR_YELLOW "\x1B[0;33m" +#define COLOR_BLUE "\x1B[0;34m" +#define COLOR_MAGENTA "\x1B[0;35m" +#define COLOR_CYAN "\x1B[0;36m" +#define COLOR_WHITE "\x1B[0;37m" +#define COLOR_HIGHLIGHT "\x1B[1;39m" + +#define print_text(color, fmt, args...) \ + tester_log(color fmt COLOR_OFF, ## args) + +#define print_summary(label, color, value, fmt, args...) \ + tester_log("%-52s " color "%-10s" COLOR_OFF fmt, \ + label, value, ## args) + +#define print_progress(name, color, fmt, args...) \ + tester_log(COLOR_HIGHLIGHT "%s" COLOR_OFF " - " \ + color fmt COLOR_OFF, name, ## args) + +enum test_result { + TEST_RESULT_NOT_RUN, + TEST_RESULT_PASSED, + TEST_RESULT_FAILED, + TEST_RESULT_TIMED_OUT, +}; + +enum test_stage { + TEST_STAGE_INVALID, + TEST_STAGE_PRE_SETUP, + TEST_STAGE_SETUP, + TEST_STAGE_RUN, + TEST_STAGE_TEARDOWN, + TEST_STAGE_POST_TEARDOWN, +}; + +struct test_case { + char *name; + enum test_result result; + enum test_stage stage; + const void *test_data; + tester_data_func_t pre_setup_func; + tester_data_func_t setup_func; + tester_data_func_t test_func; + tester_data_func_t teardown_func; + tester_data_func_t post_teardown_func; + gdouble start_time; + gdouble end_time; + unsigned int timeout; + unsigned int timeout_id; + unsigned int teardown_id; + tester_destroy_func_t destroy; + void *user_data; +}; + +static char *tester_name; + +static GList *test_list; +static GList *test_current; +static GTimer *test_timer; + +static gboolean option_version = FALSE; +static gboolean option_quiet = FALSE; +static gboolean option_debug = FALSE; +static gboolean option_monitor = FALSE; +static gboolean option_list = FALSE; +static const char *option_prefix = NULL; + +struct monitor_hdr { + uint16_t opcode; + uint16_t index; + uint16_t len; + uint8_t priority; + uint8_t ident_len; +} __attribute__((packed)); + +struct monitor_l2cap_hdr { + uint16_t cid; + uint16_t psm; +} __attribute__((packed)); + +static void test_destroy(gpointer data) +{ + struct test_case *test = data; + + if (test->timeout_id > 0) + g_source_remove(test->timeout_id); + + if (test->teardown_id > 0) + g_source_remove(test->teardown_id); + + if (test->destroy) + test->destroy(test->user_data); + + free(test->name); + free(test); +} + +static void tester_vprintf(const char *format, va_list ap) +{ + if (tester_use_quiet()) + return; + + printf(" %s", COLOR_WHITE); + vprintf(format, ap); + printf("%s\n", COLOR_OFF); +} + +static void tester_log(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + vprintf(format, ap); + printf("\n"); + va_end(ap); + + va_start(ap, format); + bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_INFO, format, ap); + va_end(ap); +} + +void tester_print(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + tester_vprintf(format, ap); + va_end(ap); + + va_start(ap, format); + bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_INFO, format, ap); + va_end(ap); +} + +void tester_debug(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + tester_vprintf(format, ap); + va_end(ap); + + va_start(ap, format); + bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_DEBUG, format, ap); + va_end(ap); +} + +void tester_warn(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + tester_vprintf(format, ap); + va_end(ap); + + va_start(ap, format); + bt_log_vprintf(HCI_DEV_NONE, tester_name, LOG_WARNING, format, ap); + va_end(ap); +} + +static void monitor_debug(const char *str, void *user_data) +{ + const char *label = user_data; + + tester_debug("%s: %s", label, str); +} + +static void monitor_log(char dir, uint16_t cid, uint16_t psm, const void *data, + size_t len) +{ + struct iovec iov[3]; + struct monitor_l2cap_hdr hdr; + uint8_t term = 0x00; + char label[16]; + + if (snprintf(label, sizeof(label), "%c %s", dir, tester_name) < 0) + return; + + hdr.cid = cpu_to_le16(cid); + hdr.psm = cpu_to_le16(psm); + + iov[0].iov_base = &hdr; + iov[0].iov_len = sizeof(hdr); + + iov[1].iov_base = (void *) data; + iov[1].iov_len = len; + + /* Kernel won't forward if data is no NULL terminated */ + iov[2].iov_base = &term; + iov[2].iov_len = sizeof(term); + + bt_log_sendmsg(HCI_DEV_NONE, label, LOG_INFO, iov, 3); +} + +void tester_monitor(char dir, uint16_t cid, uint16_t psm, const void *data, + size_t len) +{ + monitor_log(dir, cid, psm, data, len); + + if (!tester_use_debug()) + return; + + util_hexdump(dir, data, len, monitor_debug, (void *) tester_name); +} + +static void default_pre_setup(const void *test_data) +{ + tester_pre_setup_complete(); +} + +static void default_setup(const void *test_data) +{ + tester_setup_complete(); +} + +static void default_teardown(const void *test_data) +{ + tester_teardown_complete(); +} + +static void default_post_teardown(const void *test_data) +{ + tester_post_teardown_complete(); +} + +void tester_add_full(const char *name, const void *test_data, + tester_data_func_t pre_setup_func, + tester_data_func_t setup_func, + tester_data_func_t test_func, + tester_data_func_t teardown_func, + tester_data_func_t post_teardown_func, + unsigned int timeout, + void *user_data, tester_destroy_func_t destroy) +{ + struct test_case *test; + + if (!test_func) + return; + + if (option_prefix && !g_str_has_prefix(name, option_prefix)) { + if (destroy) + destroy(user_data); + return; + } + + if (option_list) { + tester_log("%s", name); + if (destroy) + destroy(user_data); + return; + } + + test = new0(struct test_case, 1); + test->name = strdup(name); + test->result = TEST_RESULT_NOT_RUN; + test->stage = TEST_STAGE_INVALID; + + test->test_data = test_data; + + if (pre_setup_func) + test->pre_setup_func = pre_setup_func; + else + test->pre_setup_func = default_pre_setup; + + if (setup_func) + test->setup_func = setup_func; + else + test->setup_func = default_setup; + + test->test_func = test_func; + + if (teardown_func) + test->teardown_func = teardown_func; + else + test->teardown_func = default_teardown; + + if (post_teardown_func) + test->post_teardown_func = post_teardown_func; + else + test->post_teardown_func = default_post_teardown; + + test->timeout = timeout; + + test->destroy = destroy; + test->user_data = user_data; + + test_list = g_list_append(test_list, test); +} + +void tester_add(const char *name, const void *test_data, + tester_data_func_t setup_func, + tester_data_func_t test_func, + tester_data_func_t teardown_func) +{ + tester_add_full(name, test_data, NULL, setup_func, test_func, + teardown_func, NULL, 0, NULL, NULL); +} + +void *tester_get_data(void) +{ + struct test_case *test; + + if (!test_current) + return NULL; + + test = test_current->data; + + return test->user_data; +} + +static int tester_summarize(void) +{ + unsigned int not_run = 0, passed = 0, failed = 0; + gdouble execution_time; + GList *list; + + tester_log(""); + print_text(COLOR_HIGHLIGHT, ""); + print_text(COLOR_HIGHLIGHT, "Test Summary"); + print_text(COLOR_HIGHLIGHT, "------------"); + + for (list = g_list_first(test_list); list; list = g_list_next(list)) { + struct test_case *test = list->data; + gdouble exec_time; + + exec_time = test->end_time - test->start_time; + + switch (test->result) { + case TEST_RESULT_NOT_RUN: + print_summary(test->name, COLOR_YELLOW, "Not Run", ""); + not_run++; + break; + case TEST_RESULT_PASSED: + print_summary(test->name, COLOR_GREEN, "Passed", + "%8.3f seconds", exec_time); + passed++; + break; + case TEST_RESULT_FAILED: + print_summary(test->name, COLOR_RED, "Failed", + "%8.3f seconds", exec_time); + failed++; + break; + case TEST_RESULT_TIMED_OUT: + print_summary(test->name, COLOR_RED, "Timed out", + "%8.3f seconds", exec_time); + failed++; + break; + } + } + + tester_log("Total: %d, " + COLOR_GREEN "Passed: %d (%.1f%%)" COLOR_OFF ", " + COLOR_RED "Failed: %d" COLOR_OFF ", " + COLOR_YELLOW "Not Run: %d" COLOR_OFF, + not_run + passed + failed, passed, + (not_run + passed + failed) ? + (float) passed * 100 / (not_run + passed + failed) : 0, + failed, not_run); + + execution_time = g_timer_elapsed(test_timer, NULL); + tester_log("Overall execution time: %.3g seconds", execution_time); + + return failed; +} + +static gboolean teardown_callback(gpointer user_data) +{ + struct test_case *test = user_data; + + test->teardown_id = 0; + test->stage = TEST_STAGE_TEARDOWN; + + print_progress(test->name, COLOR_MAGENTA, "teardown"); + test->teardown_func(test->test_data); + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_DO_ADDED_LEAK_CHECK; +#endif + + return FALSE; +} + +static gboolean test_timeout(gpointer user_data) +{ + struct test_case *test = user_data; + + test->timeout_id = 0; + + if (!test_current) + return FALSE; + + test->result = TEST_RESULT_TIMED_OUT; + print_progress(test->name, COLOR_RED, "test timed out"); + + g_idle_add(teardown_callback, test); + + return FALSE; +} + +static void next_test_case(void) +{ + struct test_case *test; + + if (test_current) + test_current = g_list_next(test_current); + else + test_current = test_list; + + if (!test_current) { + g_timer_stop(test_timer); + + mainloop_quit(); + return; + } + + test = test_current->data; + + tester_log(""); + print_progress(test->name, COLOR_BLACK, "init"); + + test->start_time = g_timer_elapsed(test_timer, NULL); + + if (test->timeout > 0) + test->timeout_id = g_timeout_add_seconds(test->timeout, + test_timeout, test); + + test->stage = TEST_STAGE_PRE_SETUP; + + test->pre_setup_func(test->test_data); +} + +static gboolean setup_callback(gpointer user_data) +{ + struct test_case *test = user_data; + + test->stage = TEST_STAGE_SETUP; + + print_progress(test->name, COLOR_BLUE, "setup"); + test->setup_func(test->test_data); + + return FALSE; +} + +static gboolean run_callback(gpointer user_data) +{ + struct test_case *test = user_data; + + test->stage = TEST_STAGE_RUN; + + print_progress(test->name, COLOR_BLACK, "run"); + test->test_func(test->test_data); + + return FALSE; +} + +static gboolean done_callback(gpointer user_data) +{ + struct test_case *test = user_data; + + test->end_time = g_timer_elapsed(test_timer, NULL); + + print_progress(test->name, COLOR_BLACK, "done"); + next_test_case(); + + return FALSE; +} + +void tester_pre_setup_complete(void) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_PRE_SETUP) + return; + + g_idle_add(setup_callback, test); +} + +void tester_pre_setup_failed(void) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_PRE_SETUP) + return; + + print_progress(test->name, COLOR_RED, "pre setup failed"); + + g_idle_add(done_callback, test); +} + +void tester_setup_complete(void) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_SETUP) + return; + + print_progress(test->name, COLOR_BLUE, "setup complete"); + + g_idle_add(run_callback, test); +} + +void tester_setup_failed(void) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_SETUP) + return; + + test->stage = TEST_STAGE_POST_TEARDOWN; + + if (test->timeout_id > 0) { + g_source_remove(test->timeout_id); + test->timeout_id = 0; + } + + print_progress(test->name, COLOR_RED, "setup failed"); + print_progress(test->name, COLOR_MAGENTA, "teardown"); + + test->post_teardown_func(test->test_data); +} + +static void test_result(enum test_result result) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_RUN) + return; + + if (test->timeout_id > 0) { + g_source_remove(test->timeout_id); + test->timeout_id = 0; + } + + test->result = result; + switch (result) { + case TEST_RESULT_PASSED: + print_progress(test->name, COLOR_GREEN, "test passed"); + break; + case TEST_RESULT_FAILED: + print_progress(test->name, COLOR_RED, "test failed"); + break; + case TEST_RESULT_NOT_RUN: + print_progress(test->name, COLOR_YELLOW, "test not run"); + break; + case TEST_RESULT_TIMED_OUT: + print_progress(test->name, COLOR_RED, "test timed out"); + break; + } + + if (test->teardown_id > 0) + return; + + test->teardown_id = g_idle_add(teardown_callback, test); +} + +void tester_test_passed(void) +{ + test_result(TEST_RESULT_PASSED); +} + +void tester_test_failed(void) +{ + test_result(TEST_RESULT_FAILED); +} + +void tester_test_abort(void) +{ + test_result(TEST_RESULT_NOT_RUN); +} + +void tester_teardown_complete(void) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_TEARDOWN) + return; + + test->stage = TEST_STAGE_POST_TEARDOWN; + + test->post_teardown_func(test->test_data); +} + +void tester_teardown_failed(void) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_TEARDOWN) + return; + + test->stage = TEST_STAGE_POST_TEARDOWN; + + tester_post_teardown_failed(); +} + +void tester_post_teardown_complete(void) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_POST_TEARDOWN) + return; + + print_progress(test->name, COLOR_MAGENTA, "teardown complete"); + + g_idle_add(done_callback, test); +} + +void tester_post_teardown_failed(void) +{ + struct test_case *test; + + if (!test_current) + return; + + test = test_current->data; + + if (test->stage != TEST_STAGE_POST_TEARDOWN) + return; + + print_progress(test->name, COLOR_RED, "teardown failed"); + + g_idle_add(done_callback, test); +} + +static gboolean start_tester(gpointer user_data) +{ + test_timer = g_timer_new(); + + next_test_case(); + + return FALSE; +} + +struct wait_data { + unsigned int seconds; + struct test_case *test; + tester_wait_func_t func; + void *user_data; +}; + +static gboolean wait_callback(gpointer user_data) +{ + struct wait_data *wait = user_data; + struct test_case *test = wait->test; + + wait->seconds--; + + if (wait->seconds > 0) { + print_progress(test->name, COLOR_BLACK, "%u seconds left", + wait->seconds); + return TRUE; + } + + print_progress(test->name, COLOR_BLACK, "waiting done"); + + wait->func(wait->user_data); + + free(wait); + + return FALSE; +} + +void tester_wait(unsigned int seconds, tester_wait_func_t func, + void *user_data) +{ + struct test_case *test; + struct wait_data *wait; + + if (!func || seconds < 1) + return; + + if (!test_current) + return; + + test = test_current->data; + + wait = new0(struct wait_data, 1); + wait->seconds = seconds; + wait->test = test; + wait->func = func; + wait->user_data = user_data; + + g_timeout_add(1000, wait_callback, wait); + + print_progress(test->name, COLOR_BLACK, "waiting %u seconds", seconds); +} + +static void signal_callback(int signum, void *user_data) +{ + static bool terminated = false; + + switch (signum) { + case SIGINT: + case SIGTERM: + if (!terminated) + mainloop_quit(); + + terminated = true; + break; + } +} + +bool tester_use_quiet(void) +{ + return option_quiet == TRUE ? true : false; +} + +bool tester_use_debug(void) +{ + return option_debug == TRUE ? true : false; +} + +static GOptionEntry options[] = { + { "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, + "Show version information and exit" }, + { "quiet", 'q', 0, G_OPTION_ARG_NONE, &option_quiet, + "Run tests without logging" }, + { "debug", 'd', 0, G_OPTION_ARG_NONE, &option_debug, + "Run tests with debug output" }, + { "monitor", 'm', 0, G_OPTION_ARG_NONE, &option_monitor, + "Enable monitor output" }, + { "list", 'l', 0, G_OPTION_ARG_NONE, &option_list, + "Only list the tests to be run" }, + { "prefix", 'p', 0, G_OPTION_ARG_STRING, &option_prefix, + "Run tests matching provided prefix" }, + { NULL }, +}; + +void tester_init(int *argc, char ***argv) +{ + GOptionContext *context; + GError *error = NULL; + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + if (g_option_context_parse(context, argc, argv, &error) == FALSE) { + if (error != NULL) { + g_printerr("%s\n", error->message); + g_error_free(error); + } else + g_printerr("An unknown error occurred\n"); + exit(1); + } + + g_option_context_free(context); + + if (option_version == TRUE) { + g_print("%s\n", VERSION); + exit(EXIT_SUCCESS); + } + + mainloop_init(); + + tester_name = strrchr(*argv[0], '/'); + if (!tester_name) + tester_name = strdup(*argv[0]); + else + tester_name = strdup(++tester_name); + + test_list = NULL; + test_current = NULL; +} + +int tester_run(void) +{ + int ret; + + if (option_list) { + mainloop_quit(); + return EXIT_SUCCESS; + } + + g_idle_add(start_tester, NULL); + + mainloop_run_with_signal(signal_callback, NULL); + + ret = tester_summarize(); + + g_list_free_full(test_list, test_destroy); + + if (option_monitor) + bt_log_close(); + + return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/shared/tester.h b/src/shared/tester.h new file mode 100644 index 0000000..96e8dc9 --- /dev/null +++ b/src/shared/tester.h @@ -0,0 +1,81 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include +#include + +void tester_init(int *argc, char ***argv); +int tester_run(void); + +bool tester_use_quiet(void); +bool tester_use_debug(void); + +void tester_print(const char *format, ...) + __attribute__((format(printf, 1, 2))); +void tester_warn(const char *format, ...) + __attribute__((format(printf, 1, 2))); +void tester_debug(const char *format, ...) + __attribute__((format(printf, 1, 2))); +void tester_monitor(char dir, uint16_t cid, uint16_t psm, const void *data, + size_t len); + +typedef void (*tester_destroy_func_t)(void *user_data); +typedef void (*tester_data_func_t)(const void *test_data); + +void tester_add_full(const char *name, const void *test_data, + tester_data_func_t pre_setup_func, + tester_data_func_t setup_func, + tester_data_func_t test_func, + tester_data_func_t teardown_func, + tester_data_func_t post_teardown_func, + unsigned int timeout, + void *user_data, tester_destroy_func_t destroy); + +void tester_add(const char *name, const void *test_data, + tester_data_func_t setup_func, + tester_data_func_t test_func, + tester_data_func_t teardown_func); + +void *tester_get_data(void); + +void tester_pre_setup_complete(void); +void tester_pre_setup_failed(void); + +void tester_setup_complete(void); +void tester_setup_failed(void); + +void tester_test_passed(void); +void tester_test_failed(void); +void tester_test_abort(void); + +void tester_teardown_complete(void); +void tester_teardown_failed(void); + +void tester_post_teardown_complete(void); +void tester_post_teardown_failed(void); + +typedef void (*tester_wait_func_t)(void *user_data); + +void tester_wait(unsigned int seconds, tester_wait_func_t func, + void *user_data); diff --git a/src/shared/timeout-ell.c b/src/shared/timeout-ell.c new file mode 100644 index 0000000..8419d46 --- /dev/null +++ b/src/shared/timeout-ell.c @@ -0,0 +1,72 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2019 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. + * + */ + +#include + +#include "timeout.h" + +struct timeout_data { + timeout_func_t func; + timeout_destroy_func_t destroy; + unsigned int timeout; + void *user_data; +}; + +static void timeout_callback(struct l_timeout *timeout, void *user_data) +{ + struct timeout_data *data = user_data; + + if (data->func) + data->func(data->user_data); + + l_timeout_modify(timeout, data->timeout); +} + +static void timeout_destroy(void *user_data) +{ + struct timeout_data *data = user_data; + + if (data->destroy) + data->destroy(data->user_data); + + l_free(data); +} + +unsigned int timeout_add(unsigned int timeout, timeout_func_t func, + void *user_data, timeout_destroy_func_t destroy) +{ + struct timeout_data *data; + uint32_t id; + + data = l_new(struct timeout_data, 1); + + data->func = func; + data->destroy = destroy; + data->user_data = user_data; + data->timeout = timeout; + + id = L_PTR_TO_UINT(l_timeout_create(timeout, timeout_callback, + user_data, timeout_destroy)); + return id; +} + +void timeout_remove(unsigned int id) +{ + l_timeout_remove(L_UINT_TO_PTR(id)); +} diff --git a/src/shared/timeout-glib.c b/src/shared/timeout-glib.c new file mode 100644 index 0000000..fd71ca4 --- /dev/null +++ b/src/shared/timeout-glib.c @@ -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. + * + */ + +#include "timeout.h" + +#include + +struct timeout_data { + timeout_func_t func; + timeout_destroy_func_t destroy; + void *user_data; +}; + +static gboolean timeout_callback(gpointer user_data) +{ + struct timeout_data *data = user_data; + + if (data->func(data->user_data)) + return TRUE; + + return FALSE; +} + +static void timeout_destroy(gpointer user_data) +{ + struct timeout_data *data = user_data; + + if (data->destroy) + data->destroy(data->user_data); + + g_free(data); +} + +unsigned int timeout_add(unsigned int timeout, timeout_func_t func, + void *user_data, timeout_destroy_func_t destroy) +{ + struct timeout_data *data; + guint id; + + data = g_try_new0(struct timeout_data, 1); + if (!data) + return 0; + + data->func = func; + data->destroy = destroy; + data->user_data = user_data; + + id = g_timeout_add_full(G_PRIORITY_DEFAULT, timeout, timeout_callback, + data, timeout_destroy); + if (!id) + g_free(data); + + return id; +} + +void timeout_remove(unsigned int id) +{ + GSource *source; + + if (!id) + return; + + source = g_main_context_find_source_by_id(NULL, id); + if (source) + g_source_destroy(source); +} diff --git a/src/shared/timeout-mainloop.c b/src/shared/timeout-mainloop.c new file mode 100644 index 0000000..971124a --- /dev/null +++ b/src/shared/timeout-mainloop.c @@ -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. + * + */ + +#include + +#include "mainloop.h" +#include "util.h" +#include "timeout.h" + +struct timeout_data { + int id; + timeout_func_t func; + timeout_destroy_func_t destroy; + unsigned int timeout; + void *user_data; +}; + +static void timeout_callback(int id, void *user_data) +{ + struct timeout_data *data = user_data; + + if (data->func(data->user_data) && + !mainloop_modify_timeout(data->id, data->timeout)) + return; + + mainloop_remove_timeout(data->id); +} + +static void timeout_destroy(void *user_data) +{ + struct timeout_data *data = user_data; + + if (data->destroy) + data->destroy(data->user_data); + + free(data); +} + +unsigned int timeout_add(unsigned int timeout, timeout_func_t func, + void *user_data, timeout_destroy_func_t destroy) +{ + struct timeout_data *data; + + data = new0(struct timeout_data, 1); + data->func = func; + data->user_data = user_data; + data->timeout = timeout; + data->destroy = destroy; + + data->id = mainloop_add_timeout(timeout, timeout_callback, data, + timeout_destroy); + if (data->id < 0) { + free(data); + return 0; + } + + return (unsigned int) data->id; +} + +void timeout_remove(unsigned int id) +{ + if (!id) + return; + + mainloop_remove_timeout((int) id); +} diff --git a/src/shared/timeout.h b/src/shared/timeout.h new file mode 100644 index 0000000..4930ce1 --- /dev/null +++ b/src/shared/timeout.h @@ -0,0 +1,27 @@ +/* + * + * 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. + * + */ + +#include + +typedef bool (*timeout_func_t)(void *user_data); +typedef void (*timeout_destroy_func_t)(void *user_data); + +unsigned int timeout_add(unsigned int timeout, timeout_func_t func, + void *user_data, timeout_destroy_func_t destroy); +void timeout_remove(unsigned int id); diff --git a/src/shared/tty.h b/src/shared/tty.h new file mode 100644 index 0000000..66ec09f --- /dev/null +++ b/src/shared/tty.h @@ -0,0 +1,80 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2016 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 + +static inline unsigned int tty_get_speed(int speed) +{ + switch (speed) { + case 9600: + return B9600; + case 19200: + return B19200; + case 38400: + return B38400; + case 57600: + return B57600; + case 115200: + return B115200; + case 230400: + return B230400; + case 460800: + return B460800; + case 500000: + return B500000; + case 576000: + return B576000; + case 921600: + return B921600; + case 1000000: + return B1000000; + case 1152000: + return B1152000; + case 1500000: + return B1500000; + case 2000000: + return B2000000; +#ifdef B2500000 + case 2500000: + return B2500000; +#endif +#ifdef B3000000 + case 3000000: + return B3000000; +#endif +#ifdef B3500000 + case 3500000: + return B3500000; +#endif +#ifdef B3710000 + case 3710000: + return B3710000; +#endif +#ifdef B4000000 + case 4000000: + return B4000000; +#endif + } + + return 0; +} diff --git a/src/shared/uhid.c b/src/shared/uhid.c new file mode 100644 index 0000000..685f902 --- /dev/null +++ b/src/shared/uhid.c @@ -0,0 +1,239 @@ +/* + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "src/shared/io.h" +#include "src/shared/util.h" +#include "src/shared/queue.h" +#include "src/shared/uhid.h" + +#define UHID_DEVICE_FILE "/dev/uhid" + +struct bt_uhid { + int ref_count; + struct io *io; + unsigned int notify_id; + struct queue *notify_list; +}; + +struct uhid_notify { + unsigned int id; + uint32_t event; + bt_uhid_callback_t func; + void *user_data; +}; + +static void uhid_free(struct bt_uhid *uhid) +{ + if (uhid->io) + io_destroy(uhid->io); + + if (uhid->notify_list) + queue_destroy(uhid->notify_list, free); + + free(uhid); +} + +static void notify_handler(void *data, void *user_data) +{ + struct uhid_notify *notify = data; + struct uhid_event *ev = user_data; + + if (notify->event != ev->type) + return; + + if (notify->func) + notify->func(ev, notify->user_data); +} + +static bool uhid_read_handler(struct io *io, void *user_data) +{ + struct bt_uhid *uhid = user_data; + int fd; + ssize_t len; + struct uhid_event ev; + + fd = io_get_fd(io); + if (fd < 0) + return false; + + memset(&ev, 0, sizeof(ev)); + + len = read(fd, &ev, sizeof(ev)); + if (len < 0) + return false; + + if ((size_t) len < sizeof(ev.type)) + return false; + + queue_foreach(uhid->notify_list, notify_handler, &ev); + + return true; +} + +struct bt_uhid *bt_uhid_new_default(void) +{ + struct bt_uhid *uhid; + int fd; + + fd = open(UHID_DEVICE_FILE, O_RDWR | O_CLOEXEC); + if (fd < 0) + return NULL; + + uhid = bt_uhid_new(fd); + if (!uhid) { + close(fd); + return NULL; + } + + io_set_close_on_destroy(uhid->io, true); + + return uhid; +} + +struct bt_uhid *bt_uhid_new(int fd) +{ + struct bt_uhid *uhid; + + uhid = new0(struct bt_uhid, 1); + uhid->io = io_new(fd); + if (!uhid->io) + goto failed; + + uhid->notify_list = queue_new(); + + if (!io_set_read_handler(uhid->io, uhid_read_handler, uhid, NULL)) + goto failed; + + return bt_uhid_ref(uhid); + +failed: + uhid_free(uhid); + return NULL; +} + +struct bt_uhid *bt_uhid_ref(struct bt_uhid *uhid) +{ + if (!uhid) + return NULL; + + __sync_fetch_and_add(&uhid->ref_count, 1); + + return uhid; +} + +void bt_uhid_unref(struct bt_uhid *uhid) +{ + if (!uhid) + return; + + if (__sync_sub_and_fetch(&uhid->ref_count, 1)) + return; + + uhid_free(uhid); +} + +bool bt_uhid_set_close_on_unref(struct bt_uhid *uhid, bool do_close) +{ + if (!uhid || !uhid->io) + return false; + + io_set_close_on_destroy(uhid->io, do_close); + + return true; +} + +unsigned int bt_uhid_register(struct bt_uhid *uhid, uint32_t event, + bt_uhid_callback_t func, void *user_data) +{ + struct uhid_notify *notify; + + if (!uhid) + return 0; + + notify = new0(struct uhid_notify, 1); + notify->id = uhid->notify_id++; + notify->event = event; + notify->func = func; + notify->user_data = user_data; + + if (!queue_push_tail(uhid->notify_list, notify)) { + free(notify); + return 0; + } + + return notify->id; +} + +static bool match_notify_id(const void *a, const void *b) +{ + const struct uhid_notify *notify = a; + unsigned int id = PTR_TO_UINT(b); + + return notify->id == id; +} + +bool bt_uhid_unregister(struct bt_uhid *uhid, unsigned int id) +{ + struct uhid_notify *notify; + + if (!uhid || !id) + return false; + + notify = queue_remove_if(uhid->notify_list, match_notify_id, + UINT_TO_PTR(id)); + if (!notify) + return false; + + free(notify); + return true; +} + +int bt_uhid_send(struct bt_uhid *uhid, const struct uhid_event *ev) +{ + ssize_t len; + struct iovec iov; + + if (!uhid->io) + return -ENOTCONN; + + iov.iov_base = (void *) ev; + iov.iov_len = sizeof(*ev); + + len = io_send(uhid->io, &iov, 1); + if (len < 0) + return -errno; + + /* uHID kernel driver does not handle partial writes */ + return len != sizeof(*ev) ? -EIO : 0; +} diff --git a/src/shared/uhid.h b/src/shared/uhid.h new file mode 100644 index 0000000..459a249 --- /dev/null +++ b/src/shared/uhid.h @@ -0,0 +1,44 @@ +/* + * + * 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 + * + */ + +#include +#include + +#include "profiles/input/uhid_copy.h" + +struct bt_uhid; + +struct bt_uhid *bt_uhid_new_default(void); +struct bt_uhid *bt_uhid_new(int fd); + +struct bt_uhid *bt_uhid_ref(struct bt_uhid *uhid); +void bt_uhid_unref(struct bt_uhid *uhid); + +bool bt_uhid_set_close_on_unref(struct bt_uhid *uhid, bool do_close); + +typedef void (*bt_uhid_callback_t)(struct uhid_event *ev, void *user_data); +unsigned int bt_uhid_register(struct bt_uhid *uhid, uint32_t event, + bt_uhid_callback_t func, void *user_data); +bool bt_uhid_unregister(struct bt_uhid *uhid, unsigned int id); + +int bt_uhid_send(struct bt_uhid *uhid, const struct uhid_event *ev); diff --git a/src/shared/util.c b/src/shared/util.c new file mode 100644 index 0000000..330a072 --- /dev/null +++ b/src/shared/util.c @@ -0,0 +1,1030 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "src/shared/util.h" + +void *btd_malloc(size_t size) +{ + if (__builtin_expect(!!size, 1)) { + void *ptr; + + ptr = malloc(size); + if (ptr) + return ptr; + + fprintf(stderr, "failed to allocate %zu bytes\n", size); + abort(); + } + + return NULL; +} + +void util_debug(util_debug_func_t function, void *user_data, + const char *format, ...) +{ + char str[78]; + va_list ap; + + if (!function || !format) + return; + + va_start(ap, format); + vsnprintf(str, sizeof(str), format, ap); + va_end(ap); + + function(str, user_data); +} + +void util_hexdump(const char dir, const unsigned char *buf, size_t len, + util_debug_func_t function, void *user_data) +{ + static const char hexdigits[] = "0123456789abcdef"; + char str[68]; + size_t i; + + if (!function || !len) + return; + + str[0] = dir; + + for (i = 0; i < len; i++) { + str[((i % 16) * 3) + 1] = ' '; + str[((i % 16) * 3) + 2] = hexdigits[buf[i] >> 4]; + str[((i % 16) * 3) + 3] = hexdigits[buf[i] & 0xf]; + str[(i % 16) + 51] = isprint(buf[i]) ? buf[i] : '.'; + + if ((i + 1) % 16 == 0) { + str[49] = ' '; + str[50] = ' '; + str[67] = '\0'; + function(str, user_data); + str[0] = ' '; + } + } + + if (i % 16 > 0) { + size_t j; + for (j = (i % 16); j < 16; j++) { + str[(j * 3) + 1] = ' '; + str[(j * 3) + 2] = ' '; + str[(j * 3) + 3] = ' '; + str[j + 51] = ' '; + } + str[49] = ' '; + str[50] = ' '; + str[67] = '\0'; + function(str, user_data); + } +} + +/* Helper for getting the dirent type in case readdir returns DT_UNKNOWN */ +unsigned char util_get_dt(const char *parent, const char *name) +{ + char filename[PATH_MAX]; + struct stat st; + + snprintf(filename, PATH_MAX, "%s/%s", parent, name); + if (lstat(filename, &st) == 0 && S_ISDIR(st.st_mode)) + return DT_DIR; + + return DT_UNKNOWN; +} + +/* Helpers for bitfield operations */ + +/* Find unique id in range from 1 to max but no bigger then + * sizeof(int) * 8. ffs() is used since it is POSIX standard + */ +uint8_t util_get_uid(unsigned int *bitmap, uint8_t max) +{ + uint8_t id; + + id = ffs(~*bitmap); + + if (!id || id > max) + return 0; + + *bitmap |= 1 << (id - 1); + + return id; +} + +/* Clear id bit in bitmap */ +void util_clear_uid(unsigned int *bitmap, uint8_t id) +{ + if (!id) + return; + + *bitmap &= ~(1 << (id - 1)); +} + +static const struct { + uint16_t uuid; + const char *str; +} uuid16_table[] = { + { 0x0001, "SDP" }, + { 0x0003, "RFCOMM" }, + { 0x0005, "TCS-BIN" }, + { 0x0007, "ATT" }, + { 0x0008, "OBEX" }, + { 0x000f, "BNEP" }, + { 0x0010, "UPNP" }, + { 0x0011, "HIDP" }, + { 0x0012, "Hardcopy Control Channel" }, + { 0x0014, "Hardcopy Data Channel" }, + { 0x0016, "Hardcopy Notification" }, + { 0x0017, "AVCTP" }, + { 0x0019, "AVDTP" }, + { 0x001b, "CMTP" }, + { 0x001e, "MCAP Control Channel" }, + { 0x001f, "MCAP Data Channel" }, + { 0x0100, "L2CAP" }, + /* 0x0101 to 0x0fff undefined */ + { 0x1000, "Service Discovery Server Service Class" }, + { 0x1001, "Browse Group Descriptor Service Class" }, + { 0x1002, "Public Browse Root" }, + /* 0x1003 to 0x1100 undefined */ + { 0x1101, "Serial Port" }, + { 0x1102, "LAN Access Using PPP" }, + { 0x1103, "Dialup Networking" }, + { 0x1104, "IrMC Sync" }, + { 0x1105, "OBEX Object Push" }, + { 0x1106, "OBEX File Transfer" }, + { 0x1107, "IrMC Sync Command" }, + { 0x1108, "Headset" }, + { 0x1109, "Cordless Telephony" }, + { 0x110a, "Audio Source" }, + { 0x110b, "Audio Sink" }, + { 0x110c, "A/V Remote Control Target" }, + { 0x110d, "Advanced Audio Distribution" }, + { 0x110e, "A/V Remote Control" }, + { 0x110f, "A/V Remote Control Controller" }, + { 0x1110, "Intercom" }, + { 0x1111, "Fax" }, + { 0x1112, "Headset AG" }, + { 0x1113, "WAP" }, + { 0x1114, "WAP Client" }, + { 0x1115, "PANU" }, + { 0x1116, "NAP" }, + { 0x1117, "GN" }, + { 0x1118, "Direct Printing" }, + { 0x1119, "Reference Printing" }, + { 0x111a, "Basic Imaging Profile" }, + { 0x111b, "Imaging Responder" }, + { 0x111c, "Imaging Automatic Archive" }, + { 0x111d, "Imaging Referenced Objects" }, + { 0x111e, "Handsfree" }, + { 0x111f, "Handsfree Audio Gateway" }, + { 0x1120, "Direct Printing Refrence Objects Service" }, + { 0x1121, "Reflected UI" }, + { 0x1122, "Basic Printing" }, + { 0x1123, "Printing Status" }, + { 0x1124, "Human Interface Device Service" }, + { 0x1125, "Hardcopy Cable Replacement" }, + { 0x1126, "HCR Print" }, + { 0x1127, "HCR Scan" }, + { 0x1128, "Common ISDN Access" }, + /* 0x1129 and 0x112a undefined */ + { 0x112d, "SIM Access" }, + { 0x112e, "Phonebook Access Client" }, + { 0x112f, "Phonebook Access Server" }, + { 0x1130, "Phonebook Access" }, + { 0x1131, "Headset HS" }, + { 0x1132, "Message Access Server" }, + { 0x1133, "Message Notification Server" }, + { 0x1134, "Message Access Profile" }, + { 0x1135, "GNSS" }, + { 0x1136, "GNSS Server" }, + { 0x1137, "3D Display" }, + { 0x1138, "3D Glasses" }, + { 0x1139, "3D Synchronization" }, + { 0x113a, "MPS Profile" }, + { 0x113b, "MPS Service" }, + /* 0x113c to 0x11ff undefined */ + { 0x1200, "PnP Information" }, + { 0x1201, "Generic Networking" }, + { 0x1202, "Generic File Transfer" }, + { 0x1203, "Generic Audio" }, + { 0x1204, "Generic Telephony" }, + { 0x1205, "UPNP Service" }, + { 0x1206, "UPNP IP Service" }, + { 0x1300, "UPNP IP PAN" }, + { 0x1301, "UPNP IP LAP" }, + { 0x1302, "UPNP IP L2CAP" }, + { 0x1303, "Video Source" }, + { 0x1304, "Video Sink" }, + { 0x1305, "Video Distribution" }, + /* 0x1306 to 0x13ff undefined */ + { 0x1400, "HDP" }, + { 0x1401, "HDP Source" }, + { 0x1402, "HDP Sink" }, + /* 0x1403 to 0x17ff undefined */ + { 0x1800, "Generic Access Profile" }, + { 0x1801, "Generic Attribute Profile" }, + { 0x1802, "Immediate Alert" }, + { 0x1803, "Link Loss" }, + { 0x1804, "Tx Power" }, + { 0x1805, "Current Time Service" }, + { 0x1806, "Reference Time Update Service" }, + { 0x1807, "Next DST Change Service" }, + { 0x1808, "Glucose" }, + { 0x1809, "Health Thermometer" }, + { 0x180a, "Device Information" }, + /* 0x180b and 0x180c undefined */ + { 0x180d, "Heart Rate" }, + { 0x180e, "Phone Alert Status Service" }, + { 0x180f, "Battery Service" }, + { 0x1810, "Blood Pressure" }, + { 0x1811, "Alert Notification Service" }, + { 0x1812, "Human Interface Device" }, + { 0x1813, "Scan Parameters" }, + { 0x1814, "Running Speed and Cadence" }, + { 0x1815, "Automation IO" }, + { 0x1816, "Cycling Speed and Cadence" }, + /* 0x1817 undefined */ + { 0x1818, "Cycling Power" }, + { 0x1819, "Location and Navigation" }, + { 0x181a, "Environmental Sensing" }, + { 0x181b, "Body Composition" }, + { 0x181c, "User Data" }, + { 0x181d, "Weight Scale" }, + { 0x181e, "Bond Management" }, + { 0x181f, "Continuous Glucose Monitoring" }, + { 0x1820, "Internet Protocol Support" }, + { 0x1821, "Indoor Positioning" }, + { 0x1822, "Pulse Oximeter" }, + { 0x1823, "HTTP Proxy" }, + { 0x1824, "Transport Discovery" }, + { 0x1825, "Object Transfer" }, + { 0x1826, "Fitness Machine" }, + { 0x1827, "Mesh Provisioning" }, + { 0x1828, "Mesh Proxy" }, + /* 0x1829 to 0x27ff undefined */ + { 0x2800, "Primary Service" }, + { 0x2801, "Secondary Service" }, + { 0x2802, "Include" }, + { 0x2803, "Characteristic" }, + /* 0x2804 to 0x28ff undefined */ + { 0x2900, "Characteristic Extended Properties" }, + { 0x2901, "Characteristic User Description" }, + { 0x2902, "Client Characteristic Configuration" }, + { 0x2903, "Server Characteristic Configuration" }, + { 0x2904, "Characteristic Format" }, + { 0x2905, "Characteristic Aggregate Formate" }, + { 0x2906, "Valid Range" }, + { 0x2907, "External Report Reference" }, + { 0x2908, "Report Reference" }, + { 0x2909, "Number of Digitals" }, + { 0x290a, "Value Trigger Setting" }, + { 0x290b, "Environmental Sensing Configuration" }, + { 0x290c, "Environmental Sensing Measurement" }, + { 0x290d, "Environmental Sensing Trigger Setting" }, + { 0x290e, "Time Trigger Setting" }, + /* 0x290f to 0x29ff undefined */ + { 0x2a00, "Device Name" }, + { 0x2a01, "Appearance" }, + { 0x2a02, "Peripheral Privacy Flag" }, + { 0x2a03, "Reconnection Address" }, + { 0x2a04, "Peripheral Preferred Connection Parameters" }, + { 0x2a05, "Service Changed" }, + { 0x2a06, "Alert Level" }, + { 0x2a07, "Tx Power Level" }, + { 0x2a08, "Date Time" }, + { 0x2a09, "Day of Week" }, + { 0x2a0a, "Day Date Time" }, + /* 0x2a0b undefined */ + { 0x2a0c, "Exact Time 256" }, + { 0x2a0d, "DST Offset" }, + { 0x2a0e, "Time Zone" }, + { 0x2a0f, "Local Time Information" }, + /* 0x2a10 undefined */ + { 0x2a11, "Time with DST" }, + { 0x2a12, "Time Accuracy" }, + { 0x2a13, "Time Source" }, + { 0x2a14, "Reference Time Information" }, + /* 0x2a15 undefined */ + { 0x2a16, "Time Update Control Point" }, + { 0x2a17, "Time Update State" }, + { 0x2a18, "Glucose Measurement" }, + { 0x2a19, "Battery Level" }, + /* 0x2a1a and 0x2a1b undefined */ + { 0x2a1c, "Temperature Measurement" }, + { 0x2a1d, "Temperature Type" }, + { 0x2a1e, "Intermediate Temperature" }, + /* 0x2a1f and 0x2a20 undefined */ + { 0x2a21, "Measurement Interval" }, + { 0x2a22, "Boot Keyboard Input Report" }, + { 0x2a23, "System ID" }, + { 0x2a24, "Model Number String" }, + { 0x2a25, "Serial Number String" }, + { 0x2a26, "Firmware Revision String" }, + { 0x2a27, "Hardware Revision String" }, + { 0x2a28, "Software Revision String" }, + { 0x2a29, "Manufacturer Name String" }, + { 0x2a2a, "IEEE 11073-20601 Regulatory Cert. Data List" }, + { 0x2a2b, "Current Time" }, + { 0x2a2c, "Magnetic Declination" }, + /* 0x2a2d to 0x2a30 undefined */ + { 0x2a31, "Scan Refresh" }, + { 0x2a32, "Boot Keyboard Output Report" }, + { 0x2a33, "Boot Mouse Input Report" }, + { 0x2a34, "Glucose Measurement Context" }, + { 0x2a35, "Blood Pressure Measurement" }, + { 0x2a36, "Intermediate Cuff Pressure" }, + { 0x2a37, "Heart Rate Measurement" }, + { 0x2a38, "Body Sensor Location" }, + { 0x2a39, "Heart Rate Control Point" }, + /* 0x2a3a to 0x2a3e undefined */ + { 0x2a3f, "Alert Status" }, + { 0x2a40, "Ringer Control Point" }, + { 0x2a41, "Ringer Setting" }, + { 0x2a42, "Alert Category ID Bit Mask" }, + { 0x2a43, "Alert Category ID" }, + { 0x2a44, "Alert Notification Control Point" }, + { 0x2a45, "Unread Alert Status" }, + { 0x2a46, "New Alert" }, + { 0x2a47, "Supported New Alert Category" }, + { 0x2a48, "Supported Unread Alert Category" }, + { 0x2a49, "Blood Pressure Feature" }, + { 0x2a4a, "HID Information" }, + { 0x2a4b, "Report Map" }, + { 0x2a4c, "HID Control Point" }, + { 0x2a4d, "Report" }, + { 0x2a4e, "Protocol Mode" }, + { 0x2a4f, "Scan Interval Window" }, + { 0x2a50, "PnP ID" }, + { 0x2a51, "Glucose Feature" }, + { 0x2a52, "Record Access Control Point" }, + { 0x2a53, "RSC Measurement" }, + { 0x2a54, "RSC Feature" }, + { 0x2a55, "SC Control Point" }, + { 0x2a56, "Digital" }, + /* 0x2a57 undefined */ + { 0x2a58, "Analog" }, + /* 0x2a59 undefined */ + { 0x2a5a, "Aggregate" }, + { 0x2a5b, "CSC Measurement" }, + { 0x2a5c, "CSC Feature" }, + { 0x2a5d, "Sensor Location" }, + /* 0x2a5e to 0x2a62 undefined */ + { 0x2a63, "Cycling Power Measurement" }, + { 0x2a64, "Cycling Power Vector" }, + { 0x2a65, "Cycling Power Feature" }, + { 0x2a66, "Cycling Power Control Point" }, + { 0x2a67, "Location and Speed" }, + { 0x2a68, "Navigation" }, + { 0x2a69, "Position Quality" }, + { 0x2a6a, "LN Feature" }, + { 0x2a6b, "LN Control Point" }, + { 0x2a6c, "Elevation" }, + { 0x2a6d, "Pressure" }, + { 0x2a6e, "Temperature" }, + { 0x2a6f, "Humidity" }, + { 0x2a70, "True Wind Speed" }, + { 0x2a71, "True Wind Direction" }, + { 0x2a72, "Apparent Wind Speed" }, + { 0x2a73, "Apparent Wind Direction" }, + { 0x2a74, "Gust Factor" }, + { 0x2a75, "Pollen Concentration" }, + { 0x2a76, "UV Index" }, + { 0x2a77, "Irradiance" }, + { 0x2a78, "Rainfall" }, + { 0x2a79, "Wind Chill" }, + { 0x2a7a, "Heat Index" }, + { 0x2a7b, "Dew Point" }, + { 0x2a7c, "Trend" }, + { 0x2a7d, "Descriptor Value Changed" }, + { 0x2a7e, "Aerobic Heart Rate Lower Limit" }, + { 0x2a7f, "Aerobic Threshold" }, + { 0x2a80, "Age" }, + { 0x2a81, "Anaerobic Heart Rate Lower Limit" }, + { 0x2a82, "Anaerobic Heart Rate Upper Limit" }, + { 0x2a83, "Anaerobic Threshold" }, + { 0x2a84, "Aerobic Heart Rate Upper Limit" }, + { 0x2a85, "Date of Birth" }, + { 0x2a86, "Date of Threshold Assessment" }, + { 0x2a87, "Email Address" }, + { 0x2a88, "Fat Burn Heart Rate Lower Limit" }, + { 0x2a89, "Fat Burn Heart Rate Upper Limit" }, + { 0x2a8a, "First Name" }, + { 0x2a8b, "Five Zone Heart Rate Limits" }, + { 0x2a8c, "Gender" }, + { 0x2a8d, "Heart Rate Max" }, + { 0x2a8e, "Height" }, + { 0x2a8f, "Hip Circumference" }, + { 0x2a90, "Last Name" }, + { 0x2a91, "Maximum Recommended Heart Rate" }, + { 0x2a92, "Resting Heart Rate" }, + { 0x2a93, "Sport Type for Aerobic/Anaerobic Thresholds" }, + { 0x2a94, "Three Zone Heart Rate Limits" }, + { 0x2a95, "Two Zone Heart Rate Limit" }, + { 0x2a96, "VO2 Max" }, + { 0x2a97, "Waist Circumference" }, + { 0x2a98, "Weight" }, + { 0x2a99, "Database Change Increment" }, + { 0x2a9a, "User Index" }, + { 0x2a9b, "Body Composition Feature" }, + { 0x2a9c, "Body Composition Measurement" }, + { 0x2a9d, "Weight Measurement" }, + { 0x2a9e, "Weight Scale Feature" }, + { 0x2a9f, "User Control Point" }, + { 0x2aa0, "Magnetic Flux Density - 2D" }, + { 0x2aa1, "Magnetic Flux Density - 3D" }, + { 0x2aa2, "Language" }, + { 0x2aa3, "Barometric Pressure Trend" }, + { 0x2aa4, "Bond Management Control Point" }, + { 0x2aa5, "Bond Management Feature" }, + { 0x2aa6, "Central Address Resolution" }, + { 0x2aa7, "CGM Measurement" }, + { 0x2aa8, "CGM Feature" }, + { 0x2aa9, "CGM Status" }, + { 0x2aaa, "CGM Session Start Time" }, + { 0x2aab, "CGM Session Run Time" }, + { 0x2aac, "CGM Specific Ops Control Point" }, + { 0x2aad, "Indoor Positioning Configuration" }, + { 0x2aae, "Latitude" }, + { 0x2aaf, "Longitude" }, + { 0x2ab0, "Local North Coordinate" }, + { 0x2ab1, "Local East Coordinate" }, + { 0x2ab2, "Floor Number" }, + { 0x2ab3, "Altitude" }, + { 0x2ab4, "Uncertainty" }, + { 0x2ab5, "Location Name" }, + { 0x2ab6, "URI" }, + { 0x2ab7, "HTTP Headers" }, + { 0x2ab8, "HTTP Status Code" }, + { 0x2ab9, "HTTP Entity Body" }, + { 0x2aba, "HTTP Control Point" }, + { 0x2abb, "HTTPS Security" }, + { 0x2abc, "TDS Control Point" }, + { 0x2abd, "OTS Feature" }, + { 0x2abe, "Object Name" }, + { 0x2abf, "Object Type" }, + { 0x2ac0, "Object Size" }, + { 0x2ac1, "Object First-Created" }, + { 0x2ac2, "Object Last-Modified" }, + { 0x2ac3, "Object ID" }, + { 0x2ac4, "Object Properties" }, + { 0x2ac5, "Object Action Control Point" }, + { 0x2ac6, "Object List Control Point" }, + { 0x2ac7, "Object List Filter" }, + { 0x2ac8, "Object Changed" }, + { 0x2ac9, "Resolvable Private Address Only" }, + /* 0x2aca and 0x2acb undefined */ + { 0x2acc, "Fitness Machine Feature" }, + { 0x2acd, "Treadmill Data" }, + { 0x2ace, "Cross Trainer Data" }, + { 0x2acf, "Step Climber Data" }, + { 0x2ad0, "Stair Climber Data" }, + { 0x2ad1, "Rower Data" }, + { 0x2ad2, "Indoor Bike Data" }, + { 0x2ad3, "Training Status" }, + { 0x2ad4, "Supported Speed Range" }, + { 0x2ad5, "Supported Inclination Range" }, + { 0x2ad6, "Supported Resistance Level Range" }, + { 0x2ad7, "Supported Heart Rate Range" }, + { 0x2ad8, "Supported Power Range" }, + { 0x2ad9, "Fitness Machine Control Point" }, + { 0x2ada, "Fitness Machine Status" }, + { 0x2adb, "Mesh Provisioning Data In" }, + { 0x2adc, "Mesh Provisioning Data Out" }, + { 0x2add, "Mesh Proxy Data In" }, + { 0x2ade, "Mesh Proxy Data Out" }, + { 0x2b29, "Client Supported Features" }, + { 0x2b2A, "Database Hash" }, + /* vendor defined */ + { 0xfeff, "GN Netcom" }, + { 0xfefe, "GN ReSound A/S" }, + { 0xfefd, "Gimbal, Inc." }, + { 0xfefc, "Gimbal, Inc." }, + { 0xfefb, "Stollmann E+V GmbH" }, + { 0xfefa, "PayPal, Inc." }, + { 0xfef9, "PayPal, Inc." }, + { 0xfef8, "Aplix Corporation" }, + { 0xfef7, "Aplix Corporation" }, + { 0xfef6, "Wicentric, Inc." }, + { 0xfef5, "Dialog Semiconductor GmbH" }, + { 0xfef4, "Google" }, + { 0xfef3, "Google" }, + { 0xfef2, "CSR" }, + { 0xfef1, "CSR" }, + { 0xfef0, "Intel" }, + { 0xfeef, "Polar Electro Oy" }, + { 0xfeee, "Polar Electro Oy" }, + { 0xfeed, "Tile, Inc." }, + { 0xfeec, "Tile, Inc." }, + { 0xfeeb, "Swirl Networks, Inc." }, + { 0xfeea, "Swirl Networks, Inc." }, + { 0xfee9, "Quintic Corp." }, + { 0xfee8, "Quintic Corp." }, + { 0xfee7, "Tencent Holdings Limited" }, + { 0xfee6, "Seed Labs, Inc." }, + { 0xfee5, "Nordic Semiconductor ASA" }, + { 0xfee4, "Nordic Semiconductor ASA" }, + { 0xfee3, "Anki, Inc." }, + { 0xfee2, "Anki, Inc." }, + { 0xfee1, "Anhui Huami Information Technology Co." }, + { 0xfee0, "Anhui Huami Information Technology Co." }, + { 0xfedf, "Design SHIFT" }, + { 0xfede, "Coin, Inc." }, + { 0xfedd, "Jawbone" }, + { 0xfedc, "Jawbone" }, + { 0xfedb, "Perka, Inc." }, + { 0xfeda, "ISSC Technologies Corporation" }, + { 0xfed9, "Pebble Technology Corporation" }, + { 0xfed8, "Google" }, + { 0xfed7, "Broadcom Corporation" }, + { 0xfed6, "Broadcom Corporation" }, + { 0xfed5, "Plantronics Inc." }, + { 0xfed4, "Apple, Inc." }, + { 0xfed3, "Apple, Inc." }, + { 0xfed2, "Apple, Inc." }, + { 0xfed1, "Apple, Inc." }, + { 0xfed0, "Apple, Inc." }, + { 0xfecf, "Apple, Inc." }, + { 0xfece, "Apple, Inc." }, + { 0xfecd, "Apple, Inc." }, + { 0xfecc, "Apple, Inc." }, + { 0xfecb, "Apple, Inc." }, + { 0xfeca, "Apple, Inc." }, + { 0xfec9, "Apple, Inc." }, + { 0xfec8, "Apple, Inc." }, + { 0xfec7, "Apple, Inc." }, + { 0xfec6, "Kocomojo, LLC" }, + { 0xfec5, "Realtek Semiconductor Corp." }, + { 0xfec4, "PLUS Location Systems" }, + { 0xfec3, "360fly, Inc." }, + { 0xfec2, "Blue Spark Technologies, Inc." }, + { 0xfec1, "KDDI Corporation" }, + { 0xfec0, "KDDI Corporation" }, + { 0xfebf, "Nod, Inc." }, + { 0xfebe, "Bose Corporation" }, + { 0xfebd, "Clover Network, Inc." }, + { 0xfebc, "Dexcom, Inc." }, + { 0xfebb, "adafruit industries" }, + { 0xfeba, "Tencent Holdings Limited" }, + { 0xfeb9, "LG Electronics" }, + { 0xfeb8, "Facebook, Inc." }, + { 0xfeb7, "Facebook, Inc." }, + { 0xfeb6, "Vencer Co, Ltd" }, + { 0xfeb5, "WiSilica Inc." }, + { 0xfeb4, "WiSilica Inc." }, + { 0xfeb3, "Taobao" }, + { 0xfeb2, "Microsoft Corporation" }, + { 0xfeb1, "Electronics Tomorrow Limited" }, + { 0xfeb0, "Nest Labs Inc." }, + { 0xfeaf, "Nest Labs Inc." }, + { 0xfeae, "Nokia Corporation" }, + { 0xfead, "Nokia Corporation" }, + { 0xfeac, "Nokia Corporation" }, + { 0xfeab, "Nokia Corporation" }, + { 0xfeaa, "Google" }, + { 0xfea9, "Savant Systems LLC" }, + { 0xfea8, "Savant Systems LLC" }, + { 0xfea7, "UTC Fire and Security" }, + { 0xfea6, "GoPro, Inc." }, + { 0xfea5, "GoPro, Inc." }, + { 0xfea4, "Paxton Access Ltd" }, + { 0xfea3, "ITT Industries" }, + { 0xfea2, "Intrepid Control Systems, Inc." }, + { 0xfea1, "Intrepid Control Systems, Inc." }, + { 0xfea0, "Google" }, + { 0xfe9f, "Google" }, + { 0xfe9e, "Dialog Semiconductor B.V." }, + { 0xfe9d, "Mobiquity Networks Inc" }, + { 0xfe9c, "GSI Laboratories, Inc." }, + { 0xfe9b, "Samsara Networks, Inc" }, + { 0xfe9a, "Estimote" }, + { 0xfe99, "Currant, Inc." }, + { 0xfe98, "Currant, Inc." }, + { 0xfe97, "Tesla Motor Inc." }, + { 0xfe96, "Tesla Motor Inc." }, + { 0xfe95, "Xiaomi Inc." }, + { 0xfe94, "OttoQ Inc." }, + { 0xfe93, "OttoQ Inc." }, + { 0xfe92, "Jarden Safety & Security" }, + { 0xfe91, "Shanghai Imilab Technology Co.,Ltd" }, + { 0xfe90, "JUMA" }, + { 0xfe8f, "CSR" }, + { 0xfe8e, "ARM Ltd" }, + { 0xfe8d, "Interaxon Inc." }, + { 0xfe8c, "TRON Forum" }, + { 0xfe8b, "Apple, Inc." }, + { 0xfe8a, "Apple, Inc." }, + { 0xfe89, "B&O Play A/S" }, + { 0xfe88, "SALTO SYSTEMS S.L." }, + { 0xfe87, "Qingdao Yeelink Information Technology Co., Ltd. ( 青岛亿联客信息技术有限公司 )" }, + { 0xfe86, "HUAWEI Technologies Co., Ltd. ( 华为技术有限公司 )" }, + { 0xfe85, "RF Digital Corp" }, + { 0xfe84, "RF Digital Corp" }, + { 0xfe83, "Blue Bite" }, + { 0xfe82, "Medtronic Inc." }, + { 0xfe81, "Medtronic Inc." }, + { 0xfe80, "Doppler Lab" }, + { 0xfe7f, "Doppler Lab" }, + { 0xfe7e, "Awear Solutions Ltd" }, + { 0xfe7d, "Aterica Health Inc." }, + { 0xfe7c, "Stollmann E+V GmbH" }, + { 0xfe7b, "Orion Labs, Inc." }, + { 0xfe7a, "Bragi GmbH" }, + { 0xfe79, "Zebra Technologies" }, + { 0xfe78, "Hewlett-Packard Company" }, + { 0xfe77, "Hewlett-Packard Company" }, + { 0xfe76, "TangoMe" }, + { 0xfe75, "TangoMe" }, + { 0xfe74, "unwire" }, + { 0xfe73, "St. Jude Medical, Inc." }, + { 0xfe72, "St. Jude Medical, Inc." }, + { 0xfe71, "Plume Design Inc" }, + { 0xfe70, "Beijing Jingdong Century Trading Co., Ltd." }, + { 0xfe6f, "LINE Corporation" }, + { 0xfe6e, "The University of Tokyo" }, + { 0xfe6d, "The University of Tokyo" }, + { 0xfe6c, "TASER International, Inc." }, + { 0xfe6b, "TASER International, Inc." }, + { 0xfe6a, "Kontakt Micro-Location Sp. z o.o." }, + { 0xfe69, "Qualcomm Life Inc" }, + { 0xfe68, "Qualcomm Life Inc" }, + { 0xfe67, "Lab Sensor Solutions" }, + { 0xfe66, "Intel Corporation" }, + { 0xfe65, "CHIPOLO d.o.o." }, + { 0xfe64, "Siemens AG" }, + { 0xfe63, "Connected Yard, Inc." }, + { 0xfe62, "Indagem Tech LLC" }, + { 0xfe61, "Logitech International SA" }, + { 0xfe60, "Lierda Science & Technology Group Co., Ltd." }, + { 0xfe5F, "Eyefi, Inc." }, + { 0xfe5E, "Plastc Corporation" }, + { 0xfe5D, "Grundfos A/S" }, + { 0xfe5C, "million hunters GmbH" }, + { 0xfe5B, "GT-tronics HK Ltd" }, + { 0xfe5A, "Chronologics Corporation" }, + { 0xfe59, "Nordic Semiconductor ASA" }, + { 0xfe58, "Nordic Semiconductor ASA" }, + { 0xfe57, "Dotted Labs" }, + { 0xfe56, "Google Inc." }, + { 0xfe55, "Google Inc." }, + { 0xfe54, "Motiv, Inc." }, + { 0xfe53, "3M" }, + { 0xfe52, "SetPoint Medical" }, + { 0xfe51, "SRAM" }, + { 0xfe50, "Google Inc." }, + { 0xfe4F, "Molekule, Inc." }, + { 0xfe4E, "NTT docomo" }, + { 0xfe4D, "Casambi Technologies Oy" }, + { 0xfe4C, "Volkswagen AG" }, + { 0xfe4B, "Koninklijke Philips N.V." }, + { 0xfe4A, "OMRON HEALTHCARE Co., Ltd." }, + { 0xfe49, "SenionLab AB" }, + { 0xfe48, "General Motors" }, + { 0xfe47, "General Motors" }, + { 0xfe46, "B&O Play A/S" }, + { 0xfe45, "Snapchat Inc" }, + { 0xfe44, "SK Telecom" }, + { 0xfe43, "Andreas Stihl AG & Co. KG" }, + { 0xfe42, "Nets A/S" }, + { 0xfe41, "Inugo Systems Limited" }, + { 0xfe40, "Inugo Systems Limited" }, + { 0xfe3F, "Friday Labs Limited" }, + { 0xfe3E, "BD Medical" }, + { 0xfe3D, "BD Medical" }, + { 0xfe3C, "Alibaba" }, + { 0xfe3B, "Dolby Laboratories" }, + { 0xfe3A, "TTS Tooltechnic Systems AG & Co. KG" }, + { 0xfe39, "TTS Tooltechnic Systems AG & Co. KG" }, + { 0xfe38, "Spaceek LTD" }, + { 0xfe37, "Spaceek LTD" }, + { 0xfe36, "HUAWEI Technologies Co., Ltd" }, + { 0xfe35, "HUAWEI Technologies Co., Ltd" }, + { 0xfe34, "SmallLoop LLC" }, + { 0xfe33, "CHIPOLO d.o.o." }, + { 0xfe32, "Pro-Mark, Inc." }, + { 0xfe31, "Volkswagen AG" }, + { 0xfe30, "Volkswagen AG" }, + { 0xfe2F, "CRESCO Wireless, Inc" }, + { 0xfe2E, "ERi,Inc." }, + { 0xfe2D, "SMART INNOVATION Co.,Ltd" }, + { 0xfe2C, "Google Inc." }, + { 0xfe2B, "ITT Industries" }, + { 0xfe2A, "DaisyWorks, Inc." }, + { 0xfe29, "Gibson Innovations" }, + { 0xfe28, "Ayla Network" }, + { 0xfe27, "Google Inc." }, + { 0xfe26, "Google Inc." }, + { 0xfe25, "Apple, Inc." }, + { 0xfe24, "August Home Inc" }, + { 0xfe23, "Zoll Medical Corporation" }, + { 0xfe22, "Zoll Medical Corporation" }, + { 0xfe21, "Bose Corporation" }, + { 0xfe20, "Emerson" }, + { 0xfe1F, "Garmin International, Inc." }, + { 0xfe1E, "Smart Innovations Co., Ltd" }, + { 0xfe1D, "Illuminati Instrument Corporation" }, + { 0xfe1C, "NetMedia, Inc." }, + /* SDO defined */ + { 0xfffc, "AirFuel Alliance" }, + { 0xfffe, "Alliance for Wireless Power (A4WP)" }, + { 0xfffd, "Fast IDentity Online Alliance (FIDO)" }, + { } +}; + +static const struct { + const char *uuid; + const char *str; +} uuid128_table[] = { + { "a3c87500-8ed3-4bdf-8a39-a01bebede295", + "Eddystone Configuration Service" }, + { "a3c87501-8ed3-4bdf-8a39-a01bebede295", "Capabilities" }, + { "a3c87502-8ed3-4bdf-8a39-a01bebede295", "Active Slot" }, + { "a3c87503-8ed3-4bdf-8a39-a01bebede295", + "Advertising Interval" }, + { "a3c87504-8ed3-4bdf-8a39-a01bebede295", "Radio Tx Power" }, + { "a3c87505-8ed3-4bdf-8a39-a01bebede295", + "(Advanced) Advertised Tx Power" }, + { "a3c87506-8ed3-4bdf-8a39-a01bebede295", "Lock State" }, + { "a3c87507-8ed3-4bdf-8a39-a01bebede295", "Unlock" }, + { "a3c87508-8ed3-4bdf-8a39-a01bebede295", "Public ECDH Key" }, + { "a3c87509-8ed3-4bdf-8a39-a01bebede295", "EID Identity Key" }, + { "a3c8750a-8ed3-4bdf-8a39-a01bebede295", "ADV Slot Data" }, + { "a3c8750b-8ed3-4bdf-8a39-a01bebede295", + "(Advanced) Factory reset" }, + { "a3c8750c-8ed3-4bdf-8a39-a01bebede295", + "(Advanced) Remain Connectable" }, + /* BBC micro:bit Bluetooth Profiles */ + { "e95d0753-251d-470a-a062-fa1922dfa9a8", + "MicroBit Accelerometer Service" }, + { "e95dca4b-251d-470a-a062-fa1922dfa9a8", + "MicroBit Accelerometer Data" }, + { "e95dfb24-251d-470a-a062-fa1922dfa9a8", + "MicroBit Accelerometer Period" }, + { "e95df2d8-251d-470a-a062-fa1922dfa9a8", + "MicroBit Magnetometer Service" }, + { "e95dfb11-251d-470a-a062-fa1922dfa9a8", + "MicroBit Magnetometer Data" }, + { "e95d386c-251d-470a-a062-fa1922dfa9a8", + "MicroBit Magnetometer Period" }, + { "e95d9715-251d-470a-a062-fa1922dfa9a8", + "MicroBit Magnetometer Bearing" }, + { "e95d9882-251d-470a-a062-fa1922dfa9a8", + "MicroBit Button Service" }, + { "e95dda90-251d-470a-a062-fa1922dfa9a8", + "MicroBit Button A State" }, + { "e95dda91-251d-470a-a062-fa1922dfa9a8", + "MicroBit Button B State" }, + { "e95d127b-251d-470a-a062-fa1922dfa9a8", + "MicroBit IO PIN Service" }, + { "e95d8d00-251d-470a-a062-fa1922dfa9a8", "MicroBit PIN Data" }, + { "e95d5899-251d-470a-a062-fa1922dfa9a8", + "MicroBit PIN AD Configuration" }, + { "e95dd822-251d-470a-a062-fa1922dfa9a8", "MicroBit PWM Control" }, + { "e95dd91d-251d-470a-a062-fa1922dfa9a8", "MicroBit LED Service" }, + { "e95d7b77-251d-470a-a062-fa1922dfa9a8", "MicroBit LED Matrix state" }, + { "e95d93ee-251d-470a-a062-fa1922dfa9a8", "MicroBit LED Text" }, + { "e95d0d2d-251d-470a-a062-fa1922dfa9a8", "MicroBit Scrolling Delay" }, + { "e95d93af-251d-470a-a062-fa1922dfa9a8", "MicroBit Event Service" }, + { "e95db84c-251d-470a-a062-fa1922dfa9a8", "MicroBit Requirements" }, + { "e95d9775-251d-470a-a062-fa1922dfa9a8", "MicroBit Event Data" }, + { "e95d23c4-251d-470a-a062-fa1922dfa9a8", + "MicroBit Client Requirements" }, + { "e95d5404-251d-470a-a062-fa1922dfa9a8", "MicroBit Client Events" }, + { "e95d93b0-251d-470a-a062-fa1922dfa9a8", + "MicroBit DFU Control Service" }, + { "e95d93b1-251d-470a-a062-fa1922dfa9a8", "MicroBit DFU Control" }, + { "e95d6100-251d-470a-a062-fa1922dfa9a8", + "MicroBit Temperature Service" }, + { "e95d1b25-251d-470a-a062-fa1922dfa9a8", + "MicroBit Temperature Period" }, + /* Nordic UART Port Emulation */ + { "6e400001-b5a3-f393-e0a9-e50e24dcca9e", "Nordic UART Service" }, + { "6e400002-b5a3-f393-e0a9-e50e24dcca9e", "Nordic UART TX" }, + { "6e400003-b5a3-f393-e0a9-e50e24dcca9e", "Nordic UART RX" }, + { } +}; + +const char *bt_uuid16_to_str(uint16_t uuid) +{ + int i; + + for (i = 0; uuid16_table[i].str; i++) { + if (uuid16_table[i].uuid == uuid) + return uuid16_table[i].str; + } + + return "Unknown"; +} + +const char *bt_uuid32_to_str(uint32_t uuid) +{ + if ((uuid & 0xffff0000) == 0x0000) + return bt_uuid16_to_str(uuid & 0x0000ffff); + + return "Unknown"; +} + +const char *bt_uuidstr_to_str(const char *uuid) +{ + uint32_t val; + size_t len; + int i; + + if (!uuid) + return NULL; + + len = strlen(uuid); + + if (len < 36) { + char *endptr = NULL; + + val = strtol(uuid, &endptr, 0); + if (!endptr || *endptr != '\0') + return NULL; + + if (val > UINT16_MAX) + return bt_uuid32_to_str(val); + + return bt_uuid16_to_str(val); + } + + if (len != 36) + return NULL; + + for (i = 0; uuid128_table[i].str; i++) { + if (strcasecmp(uuid128_table[i].uuid, uuid) == 0) + return uuid128_table[i].str; + } + + if (strncasecmp(uuid + 8, "-0000-1000-8000-00805f9b34fb", 28)) + return "Vendor specific"; + + if (sscanf(uuid, "%08x-0000-1000-8000-00805f9b34fb", &val) != 1) + return NULL; + + return bt_uuid32_to_str(val); +} + +static const struct { + uint16_t val; + bool generic; + const char *str; +} appearance_table[] = { + { 0, true, "Unknown" }, + { 64, true, "Phone" }, + { 128, true, "Computer" }, + { 192, true, "Watch" }, + { 193, false, "Sports Watch" }, + { 256, true, "Clock" }, + { 320, true, "Display" }, + { 384, true, "Remote Control" }, + { 448, true, "Eye-glasses" }, + { 512, true, "Tag" }, + { 576, true, "Keyring" }, + { 640, true, "Media Player" }, + { 704, true, "Barcode Scanner" }, + { 768, true, "Thermometer" }, + { 769, false, "Thermometer: Ear" }, + { 832, true, "Heart Rate Sensor" }, + { 833, false, "Heart Rate Belt" }, + { 896, true, "Blood Pressure" }, + { 897, false, "Blood Pressure: Arm" }, + { 898, false, "Blood Pressure: Wrist" }, + { 960, true, "Human Interface Device" }, + { 961, false, "Keyboard" }, + { 962, false, "Mouse" }, + { 963, false, "Joystick" }, + { 964, false, "Gamepad" }, + { 965, false, "Digitizer Tablet" }, + { 966, false, "Card Reader" }, + { 967, false, "Digital Pen" }, + { 968, false, "Barcode Scanner" }, + { 1024, true, "Glucose Meter" }, + { 1088, true, "Running Walking Sensor" }, + { 1089, false, "Running Walking Sensor: In-Shoe" }, + { 1090, false, "Running Walking Sensor: On-Shoe" }, + { 1091, false, "Running Walking Sensor: On-Hip" }, + { 1152, true, "Cycling" }, + { 1153, false, "Cycling: Cycling Computer" }, + { 1154, false, "Cycling: Speed Sensor" }, + { 1155, false, "Cycling: Cadence Sensor" }, + { 1156, false, "Cycling: Power Sensor" }, + { 1157, false, "Cycling: Speed and Cadence Sensor" }, + { 1216, true, "Undefined" }, + + { 3136, true, "Pulse Oximeter" }, + { 3137, false, "Pulse Oximeter: Fingertip" }, + { 3138, false, "Pulse Oximeter: Wrist Worn" }, + { 3200, true, "Weight Scale" }, + { 3264, true, "Undefined" }, + + { 5184, true, "Outdoor Sports Activity" }, + { 5185, false, "Location Display Device" }, + { 5186, false, "Location and Navigation Display Device" }, + { 5187, false, "Location Pod" }, + { 5188, false, "Location and Navigation Pod" }, + { 5248, true, "Undefined" }, + { } +}; + +const char *bt_appear_to_str(uint16_t appearance) +{ + const char *str = NULL; + int i, type = 0; + + for (i = 0; appearance_table[i].str; i++) { + if (appearance_table[i].generic) { + if (appearance < appearance_table[i].val) + break; + type = i; + } + + if (appearance_table[i].val == appearance) { + str = appearance_table[i].str; + break; + } + } + + if (!str) + str = appearance_table[type].str; + + return str; +} + +char *strdelimit(char *str, char *del, char c) +{ + char *dup; + + if (!str) + return NULL; + + dup = strdup(str); + if (dup[0] == '\0') + return dup; + + while (del[0] != '\0') { + char *rep = dup; + + while ((rep = strchr(rep, del[0]))) + rep[0] = c; + + del++; + } + + return dup; +} + +int strsuffix(const char *str, const char *suffix) +{ + int len; + int suffix_len; + + if (!str || !suffix) + return -1; + + if (str[0] == '\0' && suffix[0] != '\0') + return -1; + + if (suffix[0] == '\0' && str[0] != '\0') + return -1; + + len = strlen(str); + suffix_len = strlen(suffix); + if (len < suffix_len) + return -1; + + return strncmp(str + len - suffix_len, suffix, suffix_len); +} diff --git a/src/shared/util.h b/src/shared/util.h new file mode 100644 index 0000000..604dc3b --- /dev/null +++ b/src/shared/util.h @@ -0,0 +1,187 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 + * + */ + +#include +#include +#include +#include +#include + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define le16_to_cpu(val) (val) +#define le32_to_cpu(val) (val) +#define le64_to_cpu(val) (val) +#define cpu_to_le16(val) (val) +#define cpu_to_le32(val) (val) +#define cpu_to_le64(val) (val) +#define be16_to_cpu(val) bswap_16(val) +#define be32_to_cpu(val) bswap_32(val) +#define be64_to_cpu(val) bswap_64(val) +#define cpu_to_be16(val) bswap_16(val) +#define cpu_to_be32(val) bswap_32(val) +#define cpu_to_be64(val) bswap_64(val) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define le16_to_cpu(val) bswap_16(val) +#define le32_to_cpu(val) bswap_32(val) +#define le64_to_cpu(val) bswap_64(val) +#define cpu_to_le16(val) bswap_16(val) +#define cpu_to_le32(val) bswap_32(val) +#define cpu_to_le64(val) bswap_64(val) +#define be16_to_cpu(val) (val) +#define be32_to_cpu(val) (val) +#define be64_to_cpu(val) (val) +#define cpu_to_be16(val) (val) +#define cpu_to_be32(val) (val) +#define cpu_to_be64(val) (val) +#else +#error "Unknown byte order" +#endif + +#define get_unaligned(ptr) \ +__extension__ ({ \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v; \ +}) + +#define put_unaligned(val, ptr) \ +do { \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v = (val); \ +} while (0) + +#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) +#define UINT_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define PTR_TO_INT(p) ((int) ((intptr_t) (p))) +#define INT_TO_PTR(u) ((void *) ((intptr_t) (u))) + +#define new0(type, count) \ + (type *) (__extension__ ({ \ + size_t __n = (size_t) (count); \ + size_t __s = sizeof(type); \ + void *__p; \ + __p = btd_malloc(__n * __s); \ + memset(__p, 0, __n * __s); \ + __p; \ + })) + +#define newa(t, n) ((t*) alloca(sizeof(t)*(n))) +#define malloc0(n) (calloc((n), 1)) + +char *strdelimit(char *str, char *del, char c); +int strsuffix(const char *str, const char *suffix); + +void *btd_malloc(size_t size); + +typedef void (*util_debug_func_t)(const char *str, void *user_data); + +void util_debug(util_debug_func_t function, void *user_data, + const char *format, ...) + __attribute__((format(printf, 3, 4))); + +void util_hexdump(const char dir, const unsigned char *buf, size_t len, + util_debug_func_t function, void *user_data); + +unsigned char util_get_dt(const char *parent, const char *name); + +uint8_t util_get_uid(unsigned int *bitmap, uint8_t max); +void util_clear_uid(unsigned int *bitmap, uint8_t id); + +const char *bt_uuid16_to_str(uint16_t uuid); +const char *bt_uuid32_to_str(uint32_t uuid); +const char *bt_uuidstr_to_str(const char *uuid); +const char *bt_appear_to_str(uint16_t appearance); + +static inline int8_t get_s8(const void *ptr) +{ + return *((int8_t *) ptr); +} + +static inline uint8_t get_u8(const void *ptr) +{ + return *((uint8_t *) ptr); +} + +static inline uint16_t get_le16(const void *ptr) +{ + return le16_to_cpu(get_unaligned((const uint16_t *) ptr)); +} + +static inline uint16_t get_be16(const void *ptr) +{ + return be16_to_cpu(get_unaligned((const uint16_t *) ptr)); +} + +static inline uint32_t get_le32(const void *ptr) +{ + return le32_to_cpu(get_unaligned((const uint32_t *) ptr)); +} + +static inline uint32_t get_be32(const void *ptr) +{ + return be32_to_cpu(get_unaligned((const uint32_t *) ptr)); +} + +static inline uint64_t get_le64(const void *ptr) +{ + return le64_to_cpu(get_unaligned((const uint64_t *) ptr)); +} + +static inline uint64_t get_be64(const void *ptr) +{ + return be64_to_cpu(get_unaligned((const uint64_t *) ptr)); +} + +static inline void put_le16(uint16_t val, void *dst) +{ + put_unaligned(cpu_to_le16(val), (uint16_t *) dst); +} + +static inline void put_be16(uint16_t val, const void *ptr) +{ + put_unaligned(cpu_to_be16(val), (uint16_t *) ptr); +} + +static inline void put_le32(uint32_t val, void *dst) +{ + put_unaligned(cpu_to_le32(val), (uint32_t *) dst); +} + +static inline void put_be32(uint32_t val, void *dst) +{ + put_unaligned(cpu_to_be32(val), (uint32_t *) dst); +} + +static inline void put_le64(uint64_t val, void *dst) +{ + put_unaligned(cpu_to_le64(val), (uint64_t *) dst); +} + +static inline void put_be64(uint64_t val, void *dst) +{ + put_unaligned(cpu_to_be64(val), (uint64_t *) dst); +} diff --git a/src/storage.c b/src/storage.c new file mode 100644 index 0000000..8cbb5b2 --- /dev/null +++ b/src/storage.c @@ -0,0 +1,199 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/uuid.h" + +#include "textfile.h" +#include "uuid-helper.h" +#include "storage.h" + +/* When all services should trust a remote device */ +#define GLOBAL_TRUST "[all]" + +struct match { + GSList *keys; + char *pattern; +}; + +static inline int create_filename(char *buf, size_t size, + const bdaddr_t *bdaddr, const char *name) +{ + char addr[18]; + + ba2str(bdaddr, addr); + + return create_name(buf, size, STORAGEDIR, addr, name); +} + +int read_discoverable_timeout(const char *src, int *timeout) +{ + char filename[PATH_MAX], *str; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); + + str = textfile_get(filename, "discovto"); + if (!str) + return -ENOENT; + + if (sscanf(str, "%d", timeout) != 1) { + free(str); + return -ENOENT; + } + + free(str); + + return 0; +} + +int read_pairable_timeout(const char *src, int *timeout) +{ + char filename[PATH_MAX], *str; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); + + str = textfile_get(filename, "pairto"); + if (!str) + return -ENOENT; + + if (sscanf(str, "%d", timeout) != 1) { + free(str); + return -ENOENT; + } + + free(str); + + return 0; +} + +int read_on_mode(const char *src, char *mode, int length) +{ + char filename[PATH_MAX], *str; + + create_name(filename, PATH_MAX, STORAGEDIR, src, "config"); + + str = textfile_get(filename, "onmode"); + if (!str) + return -ENOENT; + + strncpy(mode, str, length); + mode[length - 1] = '\0'; + + free(str); + + return 0; +} + +int read_local_name(const bdaddr_t *bdaddr, char *name) +{ + char filename[PATH_MAX], *str; + int len; + + create_filename(filename, PATH_MAX, bdaddr, "config"); + + str = textfile_get(filename, "name"); + if (!str) + return -ENOENT; + + len = strlen(str); + if (len > HCI_MAX_NAME_LENGTH) + str[HCI_MAX_NAME_LENGTH] = '\0'; + strcpy(name, str); + + free(str); + + return 0; +} + +sdp_record_t *record_from_string(const char *str) +{ + sdp_record_t *rec; + int size, i, len; + uint8_t *pdata; + char tmp[3]; + + size = strlen(str)/2; + pdata = g_malloc0(size); + + tmp[2] = 0; + for (i = 0; i < size; i++) { + memcpy(tmp, str + (i * 2), 2); + pdata[i] = (uint8_t) strtol(tmp, NULL, 16); + } + + rec = sdp_extract_pdu(pdata, size, &len); + g_free(pdata); + + return rec; +} + +sdp_record_t *find_record_in_list(sdp_list_t *recs, const char *uuid) +{ + sdp_list_t *seq; + + for (seq = recs; seq; seq = seq->next) { + sdp_record_t *rec = (sdp_record_t *) seq->data; + sdp_list_t *svcclass = NULL; + char *uuid_str; + + if (sdp_get_service_classes(rec, &svcclass) < 0) + continue; + + /* Extract the uuid */ + uuid_str = bt_uuid2string(svcclass->data); + if (!uuid_str) { + sdp_list_free(svcclass, free); + continue; + } + + if (!strcasecmp(uuid_str, uuid)) { + sdp_list_free(svcclass, free); + free(uuid_str); + return rec; + } + + sdp_list_free(svcclass, free); + free(uuid_str); + } + return NULL; +} diff --git a/src/storage.h b/src/storage.h new file mode 100644 index 0000000..1c0ad57 --- /dev/null +++ b/src/storage.h @@ -0,0 +1,29 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int read_discoverable_timeout(const char *src, int *timeout); +int read_pairable_timeout(const char *src, int *timeout); +int read_on_mode(const char *src, char *mode, int length); +int read_local_name(const bdaddr_t *bdaddr, char *name); +sdp_record_t *record_from_string(const char *str); +sdp_record_t *find_record_in_list(sdp_list_t *recs, const char *uuid); diff --git a/src/textfile.c b/src/textfile.c new file mode 100644 index 0000000..371651b --- /dev/null +++ b/src/textfile.c @@ -0,0 +1,476 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "textfile.h" + +static int create_dirs(const char *filename, const mode_t mode) +{ + struct stat st; + char dir[PATH_MAX + 1], *prev, *next; + int err; + + err = stat(filename, &st); + if (!err && S_ISREG(st.st_mode)) + return 0; + + memset(dir, 0, PATH_MAX + 1); + strcat(dir, "/"); + + prev = strchr(filename, '/'); + + while (prev) { + next = strchr(prev + 1, '/'); + if (!next) + break; + + if (next - prev == 1) { + prev = next; + continue; + } + + strncat(dir, prev + 1, next - prev); + mkdir(dir, mode); + + prev = next; + } + + return 0; +} + +int create_file(const char *filename, const mode_t mode) +{ + int fd; + + create_dirs(filename, S_IRUSR | S_IWUSR | S_IXUSR); + + fd = open(filename, O_RDWR | O_CREAT, mode); + if (fd < 0) + return fd; + + close(fd); + + return 0; +} + +int create_name(char *buf, size_t size, const char *path, const char *address, const char *name) +{ + return snprintf(buf, size, "%s/%s/%s", path, address, name); +} + +static inline char *find_key(char *map, size_t size, const char *key, size_t len, int icase) +{ + char *ptr = map; + size_t ptrlen = size; + + while (ptrlen > len + 1) { + int cmp = (icase) ? strncasecmp(ptr, key, len) : strncmp(ptr, key, len); + if (cmp == 0) { + if (ptr == map && *(ptr + len) == ' ') + return ptr; + + if ((*(ptr - 1) == '\r' || *(ptr - 1) == '\n') && + *(ptr + len) == ' ') + return ptr; + } + + if (icase) { + char *p1 = memchr(ptr + 1, tolower(*key), ptrlen - 1); + char *p2 = memchr(ptr + 1, toupper(*key), ptrlen - 1); + + if (!p1) + ptr = p2; + else if (!p2) + ptr = p1; + else + ptr = (p1 < p2) ? p1 : p2; + } else + ptr = memchr(ptr + 1, *key, ptrlen - 1); + + if (!ptr) + return NULL; + + ptrlen = size - (ptr - map); + } + + return NULL; +} + +static inline int write_key_value(int fd, const char *key, const char *value) +{ + char *str; + size_t size; + int err = 0; + + size = strlen(key) + strlen(value) + 2; + + str = malloc(size + 1); + if (!str) + return ENOMEM; + + sprintf(str, "%s %s\n", key, value); + + if (write(fd, str, size) < 0) + err = -errno; + + free(str); + + return err; +} + +static char *strnpbrk(const char *s, ssize_t len, const char *accept) +{ + const char *p = s; + const char *end; + + end = s + len - 1; + + while (p <= end && *p) { + const char *a = accept; + + while (*a) { + if (*p == *a) + return (char *) p; + a++; + } + + p++; + } + + return NULL; +} + +static int write_key(const char *pathname, const char *key, const char *value, int icase) +{ + struct stat st; + char *map, *off, *end, *str; + off_t size; + size_t base; + int fd, len, err = 0; + + fd = open(pathname, O_RDWR); + if (fd < 0) + return -errno; + + if (flock(fd, LOCK_EX) < 0) { + err = -errno; + goto close; + } + + if (fstat(fd, &st) < 0) { + err = -errno; + goto unlock; + } + + size = st.st_size; + + if (!size) { + if (value) { + lseek(fd, size, SEEK_SET); + err = write_key_value(fd, key, value); + } + goto unlock; + } + + map = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_LOCKED, fd, 0); + if (!map || map == MAP_FAILED) { + err = -errno; + goto unlock; + } + + len = strlen(key); + off = find_key(map, size, key, len, icase); + if (!off) { + munmap(map, size); + if (value) { + lseek(fd, size, SEEK_SET); + err = write_key_value(fd, key, value); + } + goto unlock; + } + + base = off - map; + + end = strnpbrk(off, size, "\r\n"); + if (!end) { + err = -EILSEQ; + goto unmap; + } + + if (value && ((ssize_t) strlen(value) == end - off - len - 1) && + !strncmp(off + len + 1, value, end - off - len - 1)) + goto unmap; + + len = strspn(end, "\r\n"); + end += len; + + len = size - (end - map); + if (!len) { + munmap(map, size); + if (ftruncate(fd, base) < 0) { + err = -errno; + goto unlock; + } + lseek(fd, base, SEEK_SET); + if (value) + err = write_key_value(fd, key, value); + + goto unlock; + } + + if (len < 0 || len > size) { + err = -EILSEQ; + goto unmap; + } + + str = malloc(len); + if (!str) { + err = -errno; + goto unmap; + } + + memcpy(str, end, len); + + munmap(map, size); + if (ftruncate(fd, base) < 0) { + err = -errno; + free(str); + goto unlock; + } + lseek(fd, base, SEEK_SET); + if (value) + err = write_key_value(fd, key, value); + + if (write(fd, str, len) < 0) + err = -errno; + + free(str); + + goto unlock; + +unmap: + munmap(map, size); + +unlock: + flock(fd, LOCK_UN); + +close: + fdatasync(fd); + + close(fd); + errno = -err; + + return err; +} + +static char *read_key(const char *pathname, const char *key, int icase) +{ + struct stat st; + char *map, *off, *end, *str = NULL; + off_t size; size_t len; + int fd, err = 0; + + fd = open(pathname, O_RDONLY); + if (fd < 0) + return NULL; + + if (flock(fd, LOCK_SH) < 0) { + err = -errno; + goto close; + } + + if (fstat(fd, &st) < 0) { + err = -errno; + goto unlock; + } + + size = st.st_size; + + map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + err = -errno; + goto unlock; + } + + len = strlen(key); + off = find_key(map, size, key, len, icase); + if (!off) { + err = -EILSEQ; + goto unmap; + } + + end = strnpbrk(off, size - (off - map), "\r\n"); + if (!end) { + err = -EILSEQ; + goto unmap; + } + + str = malloc(end - off - len); + if (!str) { + err = -EILSEQ; + goto unmap; + } + + memset(str, 0, end - off - len); + strncpy(str, off + len + 1, end - off - len - 1); + +unmap: + munmap(map, size); + +unlock: + flock(fd, LOCK_UN); + +close: + close(fd); + errno = -err; + + return str; +} + +int textfile_put(const char *pathname, const char *key, const char *value) +{ + return write_key(pathname, key, value, 0); +} + +int textfile_del(const char *pathname, const char *key) +{ + return write_key(pathname, key, NULL, 0); +} + +char *textfile_get(const char *pathname, const char *key) +{ + return read_key(pathname, key, 0); +} + +int textfile_foreach(const char *pathname, textfile_cb func, void *data) +{ + struct stat st; + char *map, *off, *end, *key, *value; + off_t size; size_t len; + int fd, err = 0; + + fd = open(pathname, O_RDONLY); + if (fd < 0) + return -errno; + + if (flock(fd, LOCK_SH) < 0) { + err = -errno; + goto close; + } + + if (fstat(fd, &st) < 0) { + err = -errno; + goto unlock; + } + + size = st.st_size; + + map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + err = -errno; + goto unlock; + } + + off = map; + + while (size - (off - map) > 0) { + end = strnpbrk(off, size - (off - map), " "); + if (!end) { + err = -EILSEQ; + break; + } + + len = end - off; + + key = malloc(len + 1); + if (!key) { + err = -errno; + break; + } + + memset(key, 0, len + 1); + memcpy(key, off, len); + + off = end + 1; + + if (size - (off - map) < 0) { + err = -EILSEQ; + free(key); + break; + } + + end = strnpbrk(off, size - (off - map), "\r\n"); + if (!end) { + err = -EILSEQ; + free(key); + break; + } + + len = end - off; + + value = malloc(len + 1); + if (!value) { + err = -errno; + free(key); + break; + } + + memset(value, 0, len + 1); + memcpy(value, off, len); + + func(key, value, data); + + free(key); + free(value); + + off = end + 1; + } + + munmap(map, size); + +unlock: + flock(fd, LOCK_UN); + +close: + close(fd); + errno = -err; + + return 0; +} diff --git a/src/textfile.h b/src/textfile.h new file mode 100644 index 0000000..f01629e --- /dev/null +++ b/src/textfile.h @@ -0,0 +1,34 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +int create_file(const char *filename, const mode_t mode); +int create_name(char *buf, size_t size, const char *path, + const char *address, const char *name); + +int textfile_put(const char *pathname, const char *key, const char *value); +int textfile_del(const char *pathname, const char *key); +char *textfile_get(const char *pathname, const char *key); + +typedef void (*textfile_cb) (char *key, char *value, void *data); + +int textfile_foreach(const char *pathname, textfile_cb func, void *data); diff --git a/src/uinput.h b/src/uinput.h new file mode 100644 index 0000000..20e0941 --- /dev/null +++ b/src/uinput.h @@ -0,0 +1,724 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __UINPUT_H +#define __UINPUT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/* Events */ + +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_REL 0x02 +#define EV_ABS 0x03 +#define EV_MSC 0x04 +#define EV_LED 0x11 +#define EV_SND 0x12 +#define EV_REP 0x14 +#define EV_FF 0x15 +#define EV_PWR 0x16 +#define EV_FF_STATUS 0x17 +#define EV_MAX 0x1f + +/* Synchronization events */ + +#define SYN_REPORT 0 +#define SYN_CONFIG 1 + +/* + * Keys and buttons + * + * Most of the keys/buttons are modelled after USB HUT 1.12 + * (see http://www.usb.org/developers/hidpage). + * Abbreviations in the comments: + * AC - Application Control + * AL - Application Launch Button + * SC - System Control + */ + +#define KEY_RESERVED 0 +#define KEY_ESC 1 +#define KEY_1 2 +#define KEY_2 3 +#define KEY_3 4 +#define KEY_4 5 +#define KEY_5 6 +#define KEY_6 7 +#define KEY_7 8 +#define KEY_8 9 +#define KEY_9 10 +#define KEY_0 11 +#define KEY_MINUS 12 +#define KEY_EQUAL 13 +#define KEY_BACKSPACE 14 +#define KEY_TAB 15 +#define KEY_Q 16 +#define KEY_W 17 +#define KEY_E 18 +#define KEY_R 19 +#define KEY_T 20 +#define KEY_Y 21 +#define KEY_U 22 +#define KEY_I 23 +#define KEY_O 24 +#define KEY_P 25 +#define KEY_LEFTBRACE 26 +#define KEY_RIGHTBRACE 27 +#define KEY_ENTER 28 +#define KEY_LEFTCTRL 29 +#define KEY_A 30 +#define KEY_S 31 +#define KEY_D 32 +#define KEY_F 33 +#define KEY_G 34 +#define KEY_H 35 +#define KEY_J 36 +#define KEY_K 37 +#define KEY_L 38 +#define KEY_SEMICOLON 39 +#define KEY_APOSTROPHE 40 +#define KEY_GRAVE 41 +#define KEY_LEFTSHIFT 42 +#define KEY_BACKSLASH 43 +#define KEY_Z 44 +#define KEY_X 45 +#define KEY_C 46 +#define KEY_V 47 +#define KEY_B 48 +#define KEY_N 49 +#define KEY_M 50 +#define KEY_COMMA 51 +#define KEY_DOT 52 +#define KEY_SLASH 53 +#define KEY_RIGHTSHIFT 54 +#define KEY_KPASTERISK 55 +#define KEY_LEFTALT 56 +#define KEY_SPACE 57 +#define KEY_CAPSLOCK 58 +#define KEY_F1 59 +#define KEY_F2 60 +#define KEY_F3 61 +#define KEY_F4 62 +#define KEY_F5 63 +#define KEY_F6 64 +#define KEY_F7 65 +#define KEY_F8 66 +#define KEY_F9 67 +#define KEY_F10 68 +#define KEY_NUMLOCK 69 +#define KEY_SCROLLLOCK 70 +#define KEY_KP7 71 +#define KEY_KP8 72 +#define KEY_KP9 73 +#define KEY_KPMINUS 74 +#define KEY_KP4 75 +#define KEY_KP5 76 +#define KEY_KP6 77 +#define KEY_KPPLUS 78 +#define KEY_KP1 79 +#define KEY_KP2 80 +#define KEY_KP3 81 +#define KEY_KP0 82 +#define KEY_KPDOT 83 + +#define KEY_ZENKAKUHANKAKU 85 +#define KEY_102ND 86 +#define KEY_F11 87 +#define KEY_F12 88 +#define KEY_RO 89 +#define KEY_KATAKANA 90 +#define KEY_HIRAGANA 91 +#define KEY_HENKAN 92 +#define KEY_KATAKANAHIRAGANA 93 +#define KEY_MUHENKAN 94 +#define KEY_KPJPCOMMA 95 +#define KEY_KPENTER 96 +#define KEY_RIGHTCTRL 97 +#define KEY_KPSLASH 98 +#define KEY_SYSRQ 99 +#define KEY_RIGHTALT 100 +#define KEY_LINEFEED 101 +#define KEY_HOME 102 +#define KEY_UP 103 +#define KEY_PAGEUP 104 +#define KEY_LEFT 105 +#define KEY_RIGHT 106 +#define KEY_END 107 +#define KEY_DOWN 108 +#define KEY_PAGEDOWN 109 +#define KEY_INSERT 110 +#define KEY_DELETE 111 +#define KEY_MACRO 112 +#define KEY_MUTE 113 +#define KEY_VOLUMEDOWN 114 +#define KEY_VOLUMEUP 115 +#define KEY_POWER 116 /* SC System Power Down */ +#define KEY_KPEQUAL 117 +#define KEY_KPPLUSMINUS 118 +#define KEY_PAUSE 119 + +#define KEY_KPCOMMA 121 +#define KEY_HANGEUL 122 +#define KEY_HANGUEL KEY_HANGEUL +#define KEY_HANJA 123 +#define KEY_YEN 124 +#define KEY_LEFTMETA 125 +#define KEY_RIGHTMETA 126 +#define KEY_COMPOSE 127 + +#define KEY_STOP 128 /* AC Stop */ +#define KEY_AGAIN 129 +#define KEY_PROPS 130 /* AC Properties */ +#define KEY_UNDO 131 /* AC Undo */ +#define KEY_FRONT 132 +#define KEY_COPY 133 /* AC Copy */ +#define KEY_OPEN 134 /* AC Open */ +#define KEY_PASTE 135 /* AC Paste */ +#define KEY_FIND 136 /* AC Search */ +#define KEY_CUT 137 /* AC Cut */ +#define KEY_HELP 138 /* AL Integrated Help Center */ +#define KEY_MENU 139 /* Menu (show menu) */ +#define KEY_CALC 140 /* AL Calculator */ +#define KEY_SETUP 141 +#define KEY_SLEEP 142 /* SC System Sleep */ +#define KEY_WAKEUP 143 /* System Wake Up */ +#define KEY_FILE 144 /* AL Local Machine Browser */ +#define KEY_SENDFILE 145 +#define KEY_DELETEFILE 146 +#define KEY_XFER 147 +#define KEY_PROG1 148 +#define KEY_PROG2 149 +#define KEY_WWW 150 /* AL Internet Browser */ +#define KEY_MSDOS 151 +#define KEY_COFFEE 152 /* AL Terminal Lock/Screensaver */ +#define KEY_SCREENLOCK KEY_COFFEE +#define KEY_DIRECTION 153 +#define KEY_CYCLEWINDOWS 154 +#define KEY_MAIL 155 +#define KEY_BOOKMARKS 156 /* AC Bookmarks */ +#define KEY_COMPUTER 157 +#define KEY_BACK 158 /* AC Back */ +#define KEY_FORWARD 159 /* AC Forward */ +#define KEY_CLOSECD 160 +#define KEY_EJECTCD 161 +#define KEY_EJECTCLOSECD 162 +#define KEY_NEXTSONG 163 +#define KEY_PLAYPAUSE 164 +#define KEY_PREVIOUSSONG 165 +#define KEY_STOPCD 166 +#define KEY_RECORD 167 +#define KEY_REWIND 168 +#define KEY_PHONE 169 /* Media Select Telephone */ +#define KEY_ISO 170 +#define KEY_CONFIG 171 /* AL Consumer Control Configuration */ +#define KEY_HOMEPAGE 172 /* AC Home */ +#define KEY_REFRESH 173 /* AC Refresh */ +#define KEY_EXIT 174 /* AC Exit */ +#define KEY_MOVE 175 +#define KEY_EDIT 176 +#define KEY_SCROLLUP 177 +#define KEY_SCROLLDOWN 178 +#define KEY_KPLEFTPAREN 179 +#define KEY_KPRIGHTPAREN 180 +#define KEY_NEW 181 /* AC New */ +#define KEY_REDO 182 /* AC Redo/Repeat */ + +#define KEY_F13 183 +#define KEY_F14 184 +#define KEY_F15 185 +#define KEY_F16 186 +#define KEY_F17 187 +#define KEY_F18 188 +#define KEY_F19 189 +#define KEY_F20 190 +#define KEY_F21 191 +#define KEY_F22 192 +#define KEY_F23 193 +#define KEY_F24 194 + +#define KEY_PLAYCD 200 +#define KEY_PAUSECD 201 +#define KEY_PROG3 202 +#define KEY_PROG4 203 +#define KEY_SUSPEND 205 +#define KEY_CLOSE 206 /* AC Close */ +#define KEY_PLAY 207 +#define KEY_FASTFORWARD 208 +#define KEY_BASSBOOST 209 +#define KEY_PRINT 210 /* AC Print */ +#define KEY_HP 211 +#define KEY_CAMERA 212 +#define KEY_SOUND 213 +#define KEY_QUESTION 214 +#define KEY_EMAIL 215 +#define KEY_CHAT 216 +#define KEY_SEARCH 217 +#define KEY_CONNECT 218 +#define KEY_FINANCE 219 /* AL Checkbook/Finance */ +#define KEY_SPORT 220 +#define KEY_SHOP 221 +#define KEY_ALTERASE 222 +#define KEY_CANCEL 223 /* AC Cancel */ +#define KEY_BRIGHTNESSDOWN 224 +#define KEY_BRIGHTNESSUP 225 +#define KEY_MEDIA 226 + +#define KEY_SWITCHVIDEOMODE 227 /* Cycle between available video + outputs (Monitor/LCD/TV-out/etc) */ +#define KEY_KBDILLUMTOGGLE 228 +#define KEY_KBDILLUMDOWN 229 +#define KEY_KBDILLUMUP 230 + +#define KEY_SEND 231 /* AC Send */ +#define KEY_REPLY 232 /* AC Reply */ +#define KEY_FORWARDMAIL 233 /* AC Forward Msg */ +#define KEY_SAVE 234 /* AC Save */ +#define KEY_DOCUMENTS 235 + +#define KEY_BATTERY 236 + +#define KEY_BLUETOOTH 237 +#define KEY_WLAN 238 +#define KEY_UWB 239 + +#define KEY_UNKNOWN 240 + +#define KEY_VIDEO_NEXT 241 /* drive next video source */ +#define KEY_VIDEO_PREV 242 /* drive previous video source */ +#define KEY_BRIGHTNESS_CYCLE 243 /* brightness up, after max is min */ +#define KEY_BRIGHTNESS_ZERO 244 /* brightness off, use ambient */ +#define KEY_DISPLAY_OFF 245 /* display device to off state */ + +#define KEY_WIMAX 246 + +/* Range 248 - 255 is reserved for special needs of AT keyboard driver */ + +#define BTN_MISC 0x100 +#define BTN_0 0x100 +#define BTN_1 0x101 +#define BTN_2 0x102 +#define BTN_3 0x103 +#define BTN_4 0x104 +#define BTN_5 0x105 +#define BTN_6 0x106 +#define BTN_7 0x107 +#define BTN_8 0x108 +#define BTN_9 0x109 + +#define BTN_MOUSE 0x110 +#define BTN_LEFT 0x110 +#define BTN_RIGHT 0x111 +#define BTN_MIDDLE 0x112 +#define BTN_SIDE 0x113 +#define BTN_EXTRA 0x114 +#define BTN_FORWARD 0x115 +#define BTN_BACK 0x116 +#define BTN_TASK 0x117 + +#define BTN_JOYSTICK 0x120 +#define BTN_TRIGGER 0x120 +#define BTN_THUMB 0x121 +#define BTN_THUMB2 0x122 +#define BTN_TOP 0x123 +#define BTN_TOP2 0x124 +#define BTN_PINKIE 0x125 +#define BTN_BASE 0x126 +#define BTN_BASE2 0x127 +#define BTN_BASE3 0x128 +#define BTN_BASE4 0x129 +#define BTN_BASE5 0x12a +#define BTN_BASE6 0x12b +#define BTN_DEAD 0x12f + +#define BTN_GAMEPAD 0x130 +#define BTN_A 0x130 +#define BTN_B 0x131 +#define BTN_C 0x132 +#define BTN_X 0x133 +#define BTN_Y 0x134 +#define BTN_Z 0x135 +#define BTN_TL 0x136 +#define BTN_TR 0x137 +#define BTN_TL2 0x138 +#define BTN_TR2 0x139 +#define BTN_SELECT 0x13a +#define BTN_START 0x13b +#define BTN_MODE 0x13c +#define BTN_THUMBL 0x13d +#define BTN_THUMBR 0x13e + +#define BTN_DIGI 0x140 +#define BTN_TOOL_PEN 0x140 +#define BTN_TOOL_RUBBER 0x141 +#define BTN_TOOL_BRUSH 0x142 +#define BTN_TOOL_PENCIL 0x143 +#define BTN_TOOL_AIRBRUSH 0x144 +#define BTN_TOOL_FINGER 0x145 +#define BTN_TOOL_MOUSE 0x146 +#define BTN_TOOL_LENS 0x147 +#define BTN_TOUCH 0x14a +#define BTN_STYLUS 0x14b +#define BTN_STYLUS2 0x14c +#define BTN_TOOL_DOUBLETAP 0x14d +#define BTN_TOOL_TRIPLETAP 0x14e + +#define BTN_WHEEL 0x150 +#define BTN_GEAR_DOWN 0x150 +#define BTN_GEAR_UP 0x151 + +#define KEY_OK 0x160 +#define KEY_SELECT 0x161 +#define KEY_GOTO 0x162 +#define KEY_CLEAR 0x163 +#define KEY_POWER2 0x164 +#define KEY_OPTION 0x165 +#define KEY_INFO 0x166 /* AL OEM Features/Tips/Tutorial */ +#define KEY_TIME 0x167 +#define KEY_VENDOR 0x168 +#define KEY_ARCHIVE 0x169 +#define KEY_PROGRAM 0x16a /* Media Select Program Guide */ +#define KEY_CHANNEL 0x16b +#define KEY_FAVORITES 0x16c +#define KEY_EPG 0x16d +#define KEY_PVR 0x16e /* Media Select Home */ +#define KEY_MHP 0x16f +#define KEY_LANGUAGE 0x170 +#define KEY_TITLE 0x171 +#define KEY_SUBTITLE 0x172 +#define KEY_ANGLE 0x173 +#define KEY_ZOOM 0x174 +#define KEY_MODE 0x175 +#define KEY_KEYBOARD 0x176 +#define KEY_SCREEN 0x177 +#define KEY_PC 0x178 /* Media Select Computer */ +#define KEY_TV 0x179 /* Media Select TV */ +#define KEY_TV2 0x17a /* Media Select Cable */ +#define KEY_VCR 0x17b /* Media Select VCR */ +#define KEY_VCR2 0x17c /* VCR Plus */ +#define KEY_SAT 0x17d /* Media Select Satellite */ +#define KEY_SAT2 0x17e +#define KEY_CD 0x17f /* Media Select CD */ +#define KEY_TAPE 0x180 /* Media Select Tape */ +#define KEY_RADIO 0x181 +#define KEY_TUNER 0x182 /* Media Select Tuner */ +#define KEY_PLAYER 0x183 +#define KEY_TEXT 0x184 +#define KEY_DVD 0x185 /* Media Select DVD */ +#define KEY_AUX 0x186 +#define KEY_MP3 0x187 +#define KEY_AUDIO 0x188 +#define KEY_VIDEO 0x189 +#define KEY_DIRECTORY 0x18a +#define KEY_LIST 0x18b +#define KEY_MEMO 0x18c /* Media Select Messages */ +#define KEY_CALENDAR 0x18d +#define KEY_RED 0x18e +#define KEY_GREEN 0x18f +#define KEY_YELLOW 0x190 +#define KEY_BLUE 0x191 +#define KEY_CHANNELUP 0x192 /* Channel Increment */ +#define KEY_CHANNELDOWN 0x193 /* Channel Decrement */ +#define KEY_FIRST 0x194 +#define KEY_LAST 0x195 /* Recall Last */ +#define KEY_AB 0x196 +#define KEY_NEXT 0x197 +#define KEY_RESTART 0x198 +#define KEY_SLOW 0x199 +#define KEY_SHUFFLE 0x19a +#define KEY_BREAK 0x19b +#define KEY_PREVIOUS 0x19c +#define KEY_DIGITS 0x19d +#define KEY_TEEN 0x19e +#define KEY_TWEN 0x19f +#define KEY_VIDEOPHONE 0x1a0 /* Media Select Video Phone */ +#define KEY_GAMES 0x1a1 /* Media Select Games */ +#define KEY_ZOOMIN 0x1a2 /* AC Zoom In */ +#define KEY_ZOOMOUT 0x1a3 /* AC Zoom Out */ +#define KEY_ZOOMRESET 0x1a4 /* AC Zoom */ +#define KEY_WORDPROCESSOR 0x1a5 /* AL Word Processor */ +#define KEY_EDITOR 0x1a6 /* AL Text Editor */ +#define KEY_SPREADSHEET 0x1a7 /* AL Spreadsheet */ +#define KEY_GRAPHICSEDITOR 0x1a8 /* AL Graphics Editor */ +#define KEY_PRESENTATION 0x1a9 /* AL Presentation App */ +#define KEY_DATABASE 0x1aa /* AL Database App */ +#define KEY_NEWS 0x1ab /* AL Newsreader */ +#define KEY_VOICEMAIL 0x1ac /* AL Voicemail */ +#define KEY_ADDRESSBOOK 0x1ad /* AL Contacts/Address Book */ +#define KEY_MESSENGER 0x1ae /* AL Instant Messaging */ +#define KEY_DISPLAYTOGGLE 0x1af /* Turn display (LCD) on and off */ +#define KEY_SPELLCHECK 0x1b0 /* AL Spell Check */ +#define KEY_LOGOFF 0x1b1 /* AL Logoff */ + +#define KEY_DOLLAR 0x1b2 +#define KEY_EURO 0x1b3 + +#define KEY_FRAMEBACK 0x1b4 /* Consumer - transport controls */ +#define KEY_FRAMEFORWARD 0x1b5 +#define KEY_CONTEXT_MENU 0x1b6 /* GenDesc - system context menu */ +#define KEY_MEDIA_REPEAT 0x1b7 /* Consumer - transport control */ + +#define KEY_DEL_EOL 0x1c0 +#define KEY_DEL_EOS 0x1c1 +#define KEY_INS_LINE 0x1c2 +#define KEY_DEL_LINE 0x1c3 + +#define KEY_FN 0x1d0 +#define KEY_FN_ESC 0x1d1 +#define KEY_FN_F1 0x1d2 +#define KEY_FN_F2 0x1d3 +#define KEY_FN_F3 0x1d4 +#define KEY_FN_F4 0x1d5 +#define KEY_FN_F5 0x1d6 +#define KEY_FN_F6 0x1d7 +#define KEY_FN_F7 0x1d8 +#define KEY_FN_F8 0x1d9 +#define KEY_FN_F9 0x1da +#define KEY_FN_F10 0x1db +#define KEY_FN_F11 0x1dc +#define KEY_FN_F12 0x1dd +#define KEY_FN_1 0x1de +#define KEY_FN_2 0x1df +#define KEY_FN_D 0x1e0 +#define KEY_FN_E 0x1e1 +#define KEY_FN_F 0x1e2 +#define KEY_FN_S 0x1e3 +#define KEY_FN_B 0x1e4 + +#define KEY_BRL_DOT1 0x1f1 +#define KEY_BRL_DOT2 0x1f2 +#define KEY_BRL_DOT3 0x1f3 +#define KEY_BRL_DOT4 0x1f4 +#define KEY_BRL_DOT5 0x1f5 +#define KEY_BRL_DOT6 0x1f6 +#define KEY_BRL_DOT7 0x1f7 +#define KEY_BRL_DOT8 0x1f8 +#define KEY_BRL_DOT9 0x1f9 +#define KEY_BRL_DOT10 0x1fa + +/* We avoid low common keys in module aliases so they don't get huge. */ +#define KEY_MIN_INTERESTING KEY_MUTE +#define KEY_MAX 0x1ff +#define KEY_CNT (KEY_MAX+1) + +/* + * Relative axes + */ + +#define REL_X 0x00 +#define REL_Y 0x01 +#define REL_Z 0x02 +#define REL_RX 0x03 +#define REL_RY 0x04 +#define REL_RZ 0x05 +#define REL_HWHEEL 0x06 +#define REL_DIAL 0x07 +#define REL_WHEEL 0x08 +#define REL_MISC 0x09 +#define REL_MAX 0x0f +#define REL_CNT (REL_MAX+1) + +/* + * Absolute axes + */ + +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_Z 0x02 +#define ABS_RX 0x03 +#define ABS_RY 0x04 +#define ABS_RZ 0x05 +#define ABS_THROTTLE 0x06 +#define ABS_RUDDER 0x07 +#define ABS_WHEEL 0x08 +#define ABS_GAS 0x09 +#define ABS_BRAKE 0x0a +#define ABS_HAT0X 0x10 +#define ABS_HAT0Y 0x11 +#define ABS_HAT1X 0x12 +#define ABS_HAT1Y 0x13 +#define ABS_HAT2X 0x14 +#define ABS_HAT2Y 0x15 +#define ABS_HAT3X 0x16 +#define ABS_HAT3Y 0x17 +#define ABS_PRESSURE 0x18 +#define ABS_DISTANCE 0x19 +#define ABS_TILT_X 0x1a +#define ABS_TILT_Y 0x1b +#define ABS_TOOL_WIDTH 0x1c +#define ABS_VOLUME 0x20 +#define ABS_MISC 0x28 +#define ABS_MAX 0x3f +#define ABS_CNT (ABS_MAX+1) + +/* + * Switch events + */ + +#define SW_LID 0x00 /* set = lid shut */ +#define SW_TABLET_MODE 0x01 /* set = tablet mode */ +#define SW_HEADPHONE_INSERT 0x02 /* set = inserted */ +#define SW_RFKILL_ALL 0x03 /* rfkill master switch, type "any" + set = radio enabled */ +#define SW_RADIO SW_RFKILL_ALL /* deprecated */ +#define SW_MICROPHONE_INSERT 0x04 /* set = inserted */ +#define SW_DOCK 0x05 /* set = plugged into dock */ +#define SW_MAX 0x0f +#define SW_CNT (SW_MAX+1) + +/* + * Misc events + */ + +#define MSC_SERIAL 0x00 +#define MSC_PULSELED 0x01 +#define MSC_GESTURE 0x02 +#define MSC_RAW 0x03 +#define MSC_SCAN 0x04 +#define MSC_MAX 0x07 +#define MSC_CNT (MSC_MAX+1) + +/* + * LEDs + */ + +#define LED_NUML 0x00 +#define LED_CAPSL 0x01 +#define LED_SCROLLL 0x02 +#define LED_COMPOSE 0x03 +#define LED_KANA 0x04 +#define LED_SLEEP 0x05 +#define LED_SUSPEND 0x06 +#define LED_MUTE 0x07 +#define LED_MISC 0x08 +#define LED_MAIL 0x09 +#define LED_CHARGING 0x0a +#define LED_MAX 0x0f +#define LED_CNT (LED_MAX+1) + +/* + * Autorepeat values + */ + +#define REP_DELAY 0x00 +#define REP_PERIOD 0x01 +#define REP_MAX 0x01 + +/* + * Sounds + */ + +#define SND_CLICK 0x00 +#define SND_BELL 0x01 +#define SND_TONE 0x02 +#define SND_MAX 0x07 +#define SND_CNT (SND_MAX+1) + +/* + * IDs. + */ + +#define ID_BUS 0 +#define ID_VENDOR 1 +#define ID_PRODUCT 2 +#define ID_VERSION 3 + +#define BUS_PCI 0x01 +#define BUS_ISAPNP 0x02 +#define BUS_USB 0x03 +#define BUS_HIL 0x04 +#define BUS_BLUETOOTH 0x05 +#define BUS_VIRTUAL 0x06 + +#define BUS_ISA 0x10 +#define BUS_I8042 0x11 +#define BUS_XTKBD 0x12 +#define BUS_RS232 0x13 +#define BUS_GAMEPORT 0x14 +#define BUS_PARPORT 0x15 +#define BUS_AMIGA 0x16 +#define BUS_ADB 0x17 +#define BUS_I2C 0x18 +#define BUS_HOST 0x19 +#define BUS_GSC 0x1A +#define BUS_ATARI 0x1B + +/* User input interface */ + +#define UINPUT_IOCTL_BASE 'U' + +#define UI_DEV_CREATE _IO(UINPUT_IOCTL_BASE, 1) +#define UI_DEV_DESTROY _IO(UINPUT_IOCTL_BASE, 2) + +#define UI_SET_EVBIT _IOW(UINPUT_IOCTL_BASE, 100, int) +#define UI_SET_KEYBIT _IOW(UINPUT_IOCTL_BASE, 101, int) +#define UI_SET_RELBIT _IOW(UINPUT_IOCTL_BASE, 102, int) +#define UI_SET_ABSBIT _IOW(UINPUT_IOCTL_BASE, 103, int) +#define UI_SET_MSCBIT _IOW(UINPUT_IOCTL_BASE, 104, int) +#define UI_SET_LEDBIT _IOW(UINPUT_IOCTL_BASE, 105, int) +#define UI_SET_SNDBIT _IOW(UINPUT_IOCTL_BASE, 106, int) +#define UI_SET_FFBIT _IOW(UINPUT_IOCTL_BASE, 107, int) +#define UI_SET_PHYS _IOW(UINPUT_IOCTL_BASE, 108, char*) +#define UI_SET_SWBIT _IOW(UINPUT_IOCTL_BASE, 109, int) + +#ifndef NBITS +#define NBITS(x) ((((x) - 1) / (sizeof(long) * 8)) + 1) +#endif + +#define UINPUT_MAX_NAME_SIZE 80 + +struct uinput_id { + uint16_t bustype; + uint16_t vendor; + uint16_t product; + uint16_t version; +}; + +struct uinput_dev { + char name[UINPUT_MAX_NAME_SIZE]; + struct uinput_id id; + int ff_effects_max; + int absmax[ABS_MAX + 1]; + int absmin[ABS_MAX + 1]; + int absfuzz[ABS_MAX + 1]; + int absflat[ABS_MAX + 1]; +}; + +struct uinput_event { + struct timeval time; + uint16_t type; + uint16_t code; + int32_t value; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __UINPUT_H */ diff --git a/src/uuid-helper.c b/src/uuid-helper.c new file mode 100644 index 0000000..2c897d8 --- /dev/null +++ b/src/uuid-helper.c @@ -0,0 +1,248 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "uuid-helper.h" + +char *bt_modalias(uint16_t source, uint16_t vendor, + uint16_t product, uint16_t version) +{ + char *str; + int err; + + switch (source) { + case 0x0001: + err = asprintf(&str, "%s:v%04Xp%04Xd%04X", + "bluetooth", vendor, product, version); + break; + case 0x0002: + err = asprintf(&str, "%s:v%04Xp%04Xd%04X", + "usb", vendor, product, version); + break; + default: + return NULL; + } + + if (err < 0) + return NULL; + + return str; +} + +char *bt_uuid2string(uuid_t *uuid) +{ + char *str; + uuid_t uuid128; + unsigned int data0; + unsigned short data1; + unsigned short data2; + unsigned short data3; + unsigned int data4; + unsigned short data5; + int err; + + if (!uuid) + return NULL; + + switch (uuid->type) { + case SDP_UUID16: + sdp_uuid16_to_uuid128(&uuid128, uuid); + break; + case SDP_UUID32: + sdp_uuid32_to_uuid128(&uuid128, uuid); + break; + case SDP_UUID128: + memcpy(&uuid128, uuid, sizeof(uuid_t)); + break; + default: + /* Type of UUID unknown */ + return NULL; + } + + memcpy(&data0, &uuid128.value.uuid128.data[0], 4); + memcpy(&data1, &uuid128.value.uuid128.data[4], 2); + memcpy(&data2, &uuid128.value.uuid128.data[6], 2); + memcpy(&data3, &uuid128.value.uuid128.data[8], 2); + memcpy(&data4, &uuid128.value.uuid128.data[10], 4); + memcpy(&data5, &uuid128.value.uuid128.data[14], 2); + + err = asprintf(&str, "%.8x-%.4x-%.4x-%.4x-%.8x%.4x", + ntohl(data0), ntohs(data1), + ntohs(data2), ntohs(data3), + ntohl(data4), ntohs(data5)); + if (err < 0) + return NULL; + + return str; +} + +static struct { + const char *name; + uint16_t class; +} bt_services[] = { + { "pbap", PBAP_SVCLASS_ID }, + { "sap", SAP_SVCLASS_ID }, + { "ftp", OBEX_FILETRANS_SVCLASS_ID }, + { "bpp", BASIC_PRINTING_SVCLASS_ID }, + { "bip", IMAGING_SVCLASS_ID }, + { "synch", IRMC_SYNC_SVCLASS_ID }, + { "dun", DIALUP_NET_SVCLASS_ID }, + { "opp", OBEX_OBJPUSH_SVCLASS_ID }, + { "fax", FAX_SVCLASS_ID }, + { "spp", SERIAL_PORT_SVCLASS_ID }, + { "hsp", HEADSET_SVCLASS_ID }, + { "hsp-hs", HEADSET_SVCLASS_ID }, + { "hsp-ag", HEADSET_AGW_SVCLASS_ID }, + { "hfp", HANDSFREE_SVCLASS_ID }, + { "hfp-hf", HANDSFREE_SVCLASS_ID }, + { "hfp-ag", HANDSFREE_AGW_SVCLASS_ID }, + { "pbap-pce", PBAP_PCE_SVCLASS_ID }, + { "pbap-pse", PBAP_PSE_SVCLASS_ID }, + { "map-mse", MAP_MSE_SVCLASS_ID }, + { "map-mas", MAP_MSE_SVCLASS_ID }, + { "map-mce", MAP_MCE_SVCLASS_ID }, + { "map-mns", MAP_MCE_SVCLASS_ID }, + { "gnss", GNSS_SERVER_SVCLASS_ID }, + { } +}; + +static uint16_t name2class(const char *pattern) +{ + int i; + + for (i = 0; bt_services[i].name; i++) { + if (strcasecmp(bt_services[i].name, pattern) == 0) + return bt_services[i].class; + } + + return 0; +} + +static inline bool is_uuid128(const char *string) +{ + return (strlen(string) == 36 && + string[8] == '-' && + string[13] == '-' && + string[18] == '-' && + string[23] == '-'); +} + +static int string2uuid16(uuid_t *uuid, const char *string) +{ + int length = strlen(string); + char *endptr = NULL; + uint16_t u16; + + if (length != 4 && length != 6) + return -EINVAL; + + u16 = strtol(string, &endptr, 16); + if (endptr && *endptr == '\0') { + sdp_uuid16_create(uuid, u16); + return 0; + } + + return -EINVAL; +} + +char *bt_name2string(const char *pattern) +{ + uuid_t uuid; + uint16_t uuid16; + int i; + + /* UUID 128 string format */ + if (is_uuid128(pattern)) + return strdup(pattern); + + /* Friendly service name format */ + uuid16 = name2class(pattern); + if (uuid16) + goto proceed; + + /* HEX format */ + uuid16 = strtol(pattern, NULL, 16); + for (i = 0; bt_services[i].class; i++) { + if (bt_services[i].class == uuid16) + goto proceed; + } + + return NULL; + +proceed: + sdp_uuid16_create(&uuid, uuid16); + + return bt_uuid2string(&uuid); +} + +int bt_string2uuid(uuid_t *uuid, const char *string) +{ + uint32_t data0, data4; + uint16_t data1, data2, data3, data5; + + if (is_uuid128(string) && + sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx", + &data0, &data1, &data2, &data3, &data4, &data5) == 6) { + uint8_t val[16]; + + data0 = htonl(data0); + data1 = htons(data1); + data2 = htons(data2); + data3 = htons(data3); + data4 = htonl(data4); + data5 = htons(data5); + + memcpy(&val[0], &data0, 4); + memcpy(&val[4], &data1, 2); + memcpy(&val[6], &data2, 2); + memcpy(&val[8], &data3, 2); + memcpy(&val[10], &data4, 4); + memcpy(&val[14], &data5, 2); + + sdp_uuid128_create(uuid, val); + + return 0; + } else { + uint16_t class = name2class(string); + if (class) { + sdp_uuid16_create(uuid, class); + return 0; + } + + return string2uuid16(uuid, string); + } +} diff --git a/src/uuid-helper.h b/src/uuid-helper.h new file mode 100644 index 0000000..c0d7f9e --- /dev/null +++ b/src/uuid-helper.h @@ -0,0 +1,28 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +char *bt_modalias(uint16_t source, uint16_t vendor, + uint16_t product, uint16_t version); +char *bt_uuid2string(uuid_t *uuid); +char *bt_name2string(const char *string); +int bt_string2uuid(uuid_t *uuid, const char *string); diff --git a/test-driver b/test-driver new file mode 100755 index 0000000..b8521a4 --- /dev/null +++ b/test-driver @@ -0,0 +1,148 @@ +#! /bin/sh +# test-driver - basic testsuite driver script. + +scriptversion=2018-03-07.03; # UTC + +# Copyright (C) 2011-2018 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +usage_error () +{ + echo "$0: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat <$log_file 2>&1 +estatus=$? + +if test $enable_hard_errors = no && test $estatus -eq 99; then + tweaked_estatus=1 +else + tweaked_estatus=$estatus +fi + +case $tweaked_estatus:$expect_failure in + 0:yes) col=$red res=XPASS recheck=yes gcopy=yes;; + 0:*) col=$grn res=PASS recheck=no gcopy=no;; + 77:*) col=$blu res=SKIP recheck=no gcopy=yes;; + 99:*) col=$mgn res=ERROR recheck=yes gcopy=yes;; + *:yes) col=$lgn res=XFAIL recheck=no gcopy=yes;; + *:*) col=$red res=FAIL recheck=yes gcopy=yes;; +esac + +# Report the test outcome and exit status in the logs, so that one can +# know whether the test passed or failed simply by looking at the '.log' +# file, without the need of also peaking into the corresponding '.trs' +# file (automake bug#11814). +echo "$res $test_name (exit status: $estatus)" >>$log_file + +# Report outcome to console. +echo "${col}${res}${std}: $test_name" + +# Register the test result, and other relevant metadata. +echo ":test-result: $res" > $trs_file +echo ":global-test-result: $res" >> $trs_file +echo ":recheck: $recheck" >> $trs_file +echo ":copy-in-global-log: $gcopy" >> $trs_file + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/test/agent.py b/test/agent.py new file mode 100755 index 0000000..778dbe0 --- /dev/null +++ b/test/agent.py @@ -0,0 +1,48 @@ +#!/usr/bin/python3 + +import sys +import dbus +import dbus.service + +try: + from termcolor import colored, cprint + set_green = lambda x: colored(x, 'green', attrs=['bold']) + set_cyan = lambda x: colored(x, 'cyan', attrs=['bold']) +except ImportError: + set_green = lambda x: x + set_cyan = lambda x: x + +AGENT_IFACE = 'org.bluez.mesh.ProvisionAgent1' +AGENT_PATH = "/mesh/test/agent" + +bus = None + +class Agent(dbus.service.Object): + def __init__(self, bus): + self.path = AGENT_PATH + self.bus = bus + dbus.service.Object.__init__(self, bus, self.path) + + def get_properties(self): + caps = [] + oob = [] + caps.append('out-numeric') + oob.append('other') + return { + AGENT_IFACE: { + 'Capabilities': dbus.Array(caps, 's'), + 'OutOfBandInfo': dbus.Array(oob, 's') + } + } + + def get_path(self): + return dbus.ObjectPath(self.path) + + @dbus.service.method(AGENT_IFACE, in_signature="", out_signature="") + def Cancel(self): + print("Cancel") + + @dbus.service.method(AGENT_IFACE, in_signature="su", out_signature="") + def DisplayNumeric(self, type, value): + print(set_cyan('DisplayNumeric ('), type, + set_cyan(') number ='), set_green(value)) diff --git a/test/bluezutils.py b/test/bluezutils.py new file mode 100644 index 0000000..cd89640 --- /dev/null +++ b/test/bluezutils.py @@ -0,0 +1,47 @@ +import dbus + +SERVICE_NAME = "org.bluez" +ADAPTER_INTERFACE = SERVICE_NAME + ".Adapter1" +DEVICE_INTERFACE = SERVICE_NAME + ".Device1" + +def get_managed_objects(): + bus = dbus.SystemBus() + manager = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.freedesktop.DBus.ObjectManager") + return manager.GetManagedObjects() + +def find_adapter(pattern=None): + return find_adapter_in_objects(get_managed_objects(), pattern) + +def find_adapter_in_objects(objects, pattern=None): + bus = dbus.SystemBus() + for path, ifaces in objects.items(): + adapter = ifaces.get(ADAPTER_INTERFACE) + if adapter is None: + continue + if not pattern or pattern == adapter["Address"] or \ + path.endswith(pattern): + obj = bus.get_object(SERVICE_NAME, path) + return dbus.Interface(obj, ADAPTER_INTERFACE) + raise Exception("Bluetooth adapter not found") + +def find_device(device_address, adapter_pattern=None): + return find_device_in_objects(get_managed_objects(), device_address, + adapter_pattern) + +def find_device_in_objects(objects, device_address, adapter_pattern=None): + bus = dbus.SystemBus() + path_prefix = "" + if adapter_pattern: + adapter = find_adapter_in_objects(objects, adapter_pattern) + path_prefix = adapter.object_path + for path, ifaces in objects.items(): + device = ifaces.get(DEVICE_INTERFACE) + if device is None: + continue + if (device["Address"] == device_address and + path.startswith(path_prefix)): + obj = bus.get_object(SERVICE_NAME, path) + return dbus.Interface(obj, DEVICE_INTERFACE) + + raise Exception("Bluetooth device not found") diff --git a/test/dbusdef.py b/test/dbusdef.py new file mode 100644 index 0000000..f1cd35a --- /dev/null +++ b/test/dbusdef.py @@ -0,0 +1,15 @@ +import dbus +import bluezutils + +bus = dbus.SystemBus() + + +dummy = dbus.Interface(bus.get_object('org.bluez', '/'), 'org.freedesktop.DBus.Introspectable') + +#print dummy.Introspect() + + +try: + adapter = bluezutils.find_adapter() +except: + pass diff --git a/test/example-advertisement b/test/example-advertisement new file mode 100755 index 0000000..88a27ab --- /dev/null +++ b/test/example-advertisement @@ -0,0 +1,226 @@ +#!/usr/bin/python + +from __future__ import print_function + +import argparse +import dbus +import dbus.exceptions +import dbus.mainloop.glib +import dbus.service +import time +import threading + +try: + from gi.repository import GObject # python3 +except ImportError: + import gobject as GObject # python2 + +mainloop = None + +BLUEZ_SERVICE_NAME = 'org.bluez' +LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1' +DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager' +DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties' + +LE_ADVERTISEMENT_IFACE = 'org.bluez.LEAdvertisement1' + + +class InvalidArgsException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs' + + +class NotSupportedException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.bluez.Error.NotSupported' + + +class NotPermittedException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.bluez.Error.NotPermitted' + + +class InvalidValueLengthException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.bluez.Error.InvalidValueLength' + + +class FailedException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.bluez.Error.Failed' + + +class Advertisement(dbus.service.Object): + PATH_BASE = '/org/bluez/example/advertisement' + + def __init__(self, bus, index, advertising_type): + self.path = self.PATH_BASE + str(index) + self.bus = bus + self.ad_type = advertising_type + self.service_uuids = None + self.manufacturer_data = None + self.solicit_uuids = None + self.service_data = None + self.local_name = None + self.include_tx_power = None + self.data = None + dbus.service.Object.__init__(self, bus, self.path) + + def get_properties(self): + properties = dict() + properties['Type'] = self.ad_type + if self.service_uuids is not None: + properties['ServiceUUIDs'] = dbus.Array(self.service_uuids, + signature='s') + if self.solicit_uuids is not None: + properties['SolicitUUIDs'] = dbus.Array(self.solicit_uuids, + signature='s') + if self.manufacturer_data is not None: + properties['ManufacturerData'] = dbus.Dictionary( + self.manufacturer_data, signature='qv') + if self.service_data is not None: + properties['ServiceData'] = dbus.Dictionary(self.service_data, + signature='sv') + if self.local_name is not None: + properties['LocalName'] = dbus.String(self.local_name) + if self.include_tx_power is not None: + properties['IncludeTxPower'] = dbus.Boolean(self.include_tx_power) + + if self.data is not None: + properties['Data'] = dbus.Dictionary( + self.data, signature='yv') + return {LE_ADVERTISEMENT_IFACE: properties} + + def get_path(self): + return dbus.ObjectPath(self.path) + + def add_service_uuid(self, uuid): + if not self.service_uuids: + self.service_uuids = [] + self.service_uuids.append(uuid) + + def add_solicit_uuid(self, uuid): + if not self.solicit_uuids: + self.solicit_uuids = [] + self.solicit_uuids.append(uuid) + + def add_manufacturer_data(self, manuf_code, data): + if not self.manufacturer_data: + self.manufacturer_data = dbus.Dictionary({}, signature='qv') + self.manufacturer_data[manuf_code] = dbus.Array(data, signature='y') + + def add_service_data(self, uuid, data): + if not self.service_data: + self.service_data = dbus.Dictionary({}, signature='sv') + self.service_data[uuid] = dbus.Array(data, signature='y') + + def add_local_name(self, name): + if not self.local_name: + self.local_name = "" + self.local_name = dbus.String(name) + + def add_data(self, ad_type, data): + if not self.data: + self.data = dbus.Dictionary({}, signature='yv') + self.data[ad_type] = dbus.Array(data, signature='y') + + @dbus.service.method(DBUS_PROP_IFACE, + in_signature='s', + out_signature='a{sv}') + def GetAll(self, interface): + print('GetAll') + if interface != LE_ADVERTISEMENT_IFACE: + raise InvalidArgsException() + print('returning props') + return self.get_properties()[LE_ADVERTISEMENT_IFACE] + + @dbus.service.method(LE_ADVERTISEMENT_IFACE, + in_signature='', + out_signature='') + def Release(self): + print('%s: Released!' % self.path) + + +class TestAdvertisement(Advertisement): + + def __init__(self, bus, index): + Advertisement.__init__(self, bus, index, 'peripheral') + self.add_service_uuid('180D') + self.add_service_uuid('180F') + self.add_manufacturer_data(0xffff, [0x00, 0x01, 0x02, 0x03, 0x04]) + self.add_service_data('9999', [0x00, 0x01, 0x02, 0x03, 0x04]) + self.add_local_name('TestAdvertisement') + self.include_tx_power = True + self.add_data(0x26, [0x01, 0x01, 0x00]) + + +def register_ad_cb(): + print('Advertisement registered') + + +def register_ad_error_cb(error): + print('Failed to register advertisement: ' + str(error)) + mainloop.quit() + + +def find_adapter(bus): + remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'), + DBUS_OM_IFACE) + objects = remote_om.GetManagedObjects() + + for o, props in objects.items(): + if LE_ADVERTISING_MANAGER_IFACE in props: + return o + + return None + + +def shutdown(timeout): + print('Advertising for {} seconds...'.format(timeout)) + time.sleep(timeout) + mainloop.quit() + + +def main(timeout=0): + global mainloop + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + adapter = find_adapter(bus) + if not adapter: + print('LEAdvertisingManager1 interface not found') + return + + adapter_props = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter), + "org.freedesktop.DBus.Properties") + + adapter_props.Set("org.bluez.Adapter1", "Powered", dbus.Boolean(1)) + + ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter), + LE_ADVERTISING_MANAGER_IFACE) + + test_advertisement = TestAdvertisement(bus, 0) + + mainloop = GObject.MainLoop() + + ad_manager.RegisterAdvertisement(test_advertisement.get_path(), {}, + reply_handler=register_ad_cb, + error_handler=register_ad_error_cb) + + if timeout > 0: + threading.Thread(target=shutdown, args=(timeout,)).start() + else: + print('Advertising forever...') + + mainloop.run() # blocks until mainloop.quit() is called + + ad_manager.UnregisterAdvertisement(test_advertisement) + print('Advertisement unregistered') + dbus.service.Object.remove_from_connection(test_advertisement) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--timeout', default=0, type=int, help="advertise " + + "for this many seconds then stop, 0=run forever " + + "(default: 0)") + args = parser.parse_args() + + main(args.timeout) diff --git a/test/example-gatt-client b/test/example-gatt-client new file mode 100755 index 0000000..b4bbaa9 --- /dev/null +++ b/test/example-gatt-client @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 + +import dbus +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject +import sys + +from dbus.mainloop.glib import DBusGMainLoop + +bus = None +mainloop = None + +BLUEZ_SERVICE_NAME = 'org.bluez' +DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager' +DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties' + +GATT_SERVICE_IFACE = 'org.bluez.GattService1' +GATT_CHRC_IFACE = 'org.bluez.GattCharacteristic1' + +HR_SVC_UUID = '0000180d-0000-1000-8000-00805f9b34fb' +HR_MSRMT_UUID = '00002a37-0000-1000-8000-00805f9b34fb' +BODY_SNSR_LOC_UUID = '00002a38-0000-1000-8000-00805f9b34fb' +HR_CTRL_PT_UUID = '00002a39-0000-1000-8000-00805f9b34fb' + +# The objects that we interact with. +hr_service = None +hr_msrmt_chrc = None +body_snsr_loc_chrc = None +hr_ctrl_pt_chrc = None + + +def generic_error_cb(error): + print('D-Bus call failed: ' + str(error)) + mainloop.quit() + + +def body_sensor_val_to_str(val): + if val == 0: + return 'Other' + if val == 1: + return 'Chest' + if val == 2: + return 'Wrist' + if val == 3: + return 'Finger' + if val == 4: + return 'Hand' + if val == 5: + return 'Ear Lobe' + if val == 6: + return 'Foot' + + return 'Reserved value' + + +def sensor_contact_val_to_str(val): + if val == 0 or val == 1: + return 'not supported' + if val == 2: + return 'no contact detected' + if val == 3: + return 'contact detected' + + return 'invalid value' + + +def body_sensor_val_cb(value): + if len(value) != 1: + print('Invalid body sensor location value: ' + repr(value)) + return + + print('Body sensor location value: ' + body_sensor_val_to_str(value[0])) + + +def hr_msrmt_start_notify_cb(): + print('HR Measurement notifications enabled') + + +def hr_msrmt_changed_cb(iface, changed_props, invalidated_props): + if iface != GATT_CHRC_IFACE: + return + + if not len(changed_props): + return + + value = changed_props.get('Value', None) + if not value: + return + + print('New HR Measurement') + + flags = value[0] + value_format = flags & 0x01 + sc_status = (flags >> 1) & 0x03 + ee_status = flags & 0x08 + + if value_format == 0x00: + hr_msrmt = value[1] + next_ind = 2 + else: + hr_msrmt = value[1] | (value[2] << 8) + next_ind = 3 + + print('\tHR: ' + str(int(hr_msrmt))) + print('\tSensor Contact status: ' + + sensor_contact_val_to_str(sc_status)) + + if ee_status: + print('\tEnergy Expended: ' + str(int(value[next_ind]))) + + +def start_client(): + # Read the Body Sensor Location value and print it asynchronously. + body_snsr_loc_chrc[0].ReadValue({}, reply_handler=body_sensor_val_cb, + error_handler=generic_error_cb, + dbus_interface=GATT_CHRC_IFACE) + + # Listen to PropertiesChanged signals from the Heart Measurement + # Characteristic. + hr_msrmt_prop_iface = dbus.Interface(hr_msrmt_chrc[0], DBUS_PROP_IFACE) + hr_msrmt_prop_iface.connect_to_signal("PropertiesChanged", + hr_msrmt_changed_cb) + + # Subscribe to Heart Rate Measurement notifications. + hr_msrmt_chrc[0].StartNotify(reply_handler=hr_msrmt_start_notify_cb, + error_handler=generic_error_cb, + dbus_interface=GATT_CHRC_IFACE) + + +def process_chrc(chrc_path): + chrc = bus.get_object(BLUEZ_SERVICE_NAME, chrc_path) + chrc_props = chrc.GetAll(GATT_CHRC_IFACE, + dbus_interface=DBUS_PROP_IFACE) + + uuid = chrc_props['UUID'] + + if uuid == HR_MSRMT_UUID: + global hr_msrmt_chrc + hr_msrmt_chrc = (chrc, chrc_props) + elif uuid == BODY_SNSR_LOC_UUID: + global body_snsr_loc_chrc + body_snsr_loc_chrc = (chrc, chrc_props) + elif uuid == HR_CTRL_PT_UUID: + global hr_ctrl_pt_chrc + hr_ctrl_pt_chrc = (chrc, chrc_props) + else: + print('Unrecognized characteristic: ' + uuid) + + return True + + +def process_hr_service(service_path, chrc_paths): + service = bus.get_object(BLUEZ_SERVICE_NAME, service_path) + service_props = service.GetAll(GATT_SERVICE_IFACE, + dbus_interface=DBUS_PROP_IFACE) + + uuid = service_props['UUID'] + + if uuid != HR_SVC_UUID: + return False + + print('Heart Rate Service found: ' + service_path) + + # Process the characteristics. + for chrc_path in chrc_paths: + process_chrc(chrc_path) + + global hr_service + hr_service = (service, service_props, service_path) + + return True + + +def interfaces_removed_cb(object_path, interfaces): + if not hr_service: + return + + if object_path == hr_service[2]: + print('Service was removed') + mainloop.quit() + + +def main(): + # Set up the main loop. + DBusGMainLoop(set_as_default=True) + global bus + bus = dbus.SystemBus() + global mainloop + mainloop = GObject.MainLoop() + + om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'), DBUS_OM_IFACE) + om.connect_to_signal('InterfacesRemoved', interfaces_removed_cb) + + print('Getting objects...') + objects = om.GetManagedObjects() + chrcs = [] + + # List characteristics found + for path, interfaces in objects.items(): + if GATT_CHRC_IFACE not in interfaces.keys(): + continue + chrcs.append(path) + + # List sevices found + for path, interfaces in objects.items(): + if GATT_SERVICE_IFACE not in interfaces.keys(): + continue + + chrc_paths = [d for d in chrcs if d.startswith(path + "/")] + + if process_hr_service(path, chrc_paths): + break + + if not hr_service: + print('No Heart Rate Service found') + sys.exit(1) + + start_client() + + mainloop.run() + + +if __name__ == '__main__': + main() diff --git a/test/example-gatt-server b/test/example-gatt-server new file mode 100755 index 0000000..689e86f --- /dev/null +++ b/test/example-gatt-server @@ -0,0 +1,662 @@ +#!/usr/bin/env python3 + +import dbus +import dbus.exceptions +import dbus.mainloop.glib +import dbus.service + +import array +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject +import sys + +from random import randint + +mainloop = None + +BLUEZ_SERVICE_NAME = 'org.bluez' +GATT_MANAGER_IFACE = 'org.bluez.GattManager1' +DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager' +DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties' + +GATT_SERVICE_IFACE = 'org.bluez.GattService1' +GATT_CHRC_IFACE = 'org.bluez.GattCharacteristic1' +GATT_DESC_IFACE = 'org.bluez.GattDescriptor1' + +class InvalidArgsException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs' + +class NotSupportedException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.bluez.Error.NotSupported' + +class NotPermittedException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.bluez.Error.NotPermitted' + +class InvalidValueLengthException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.bluez.Error.InvalidValueLength' + +class FailedException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.bluez.Error.Failed' + + +class Application(dbus.service.Object): + """ + org.bluez.GattApplication1 interface implementation + """ + def __init__(self, bus): + self.path = '/' + self.services = [] + dbus.service.Object.__init__(self, bus, self.path) + self.add_service(HeartRateService(bus, 0)) + self.add_service(BatteryService(bus, 1)) + self.add_service(TestService(bus, 2)) + + def get_path(self): + return dbus.ObjectPath(self.path) + + def add_service(self, service): + self.services.append(service) + + @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}') + def GetManagedObjects(self): + response = {} + print('GetManagedObjects') + + for service in self.services: + response[service.get_path()] = service.get_properties() + chrcs = service.get_characteristics() + for chrc in chrcs: + response[chrc.get_path()] = chrc.get_properties() + descs = chrc.get_descriptors() + for desc in descs: + response[desc.get_path()] = desc.get_properties() + + return response + + +class Service(dbus.service.Object): + """ + org.bluez.GattService1 interface implementation + """ + PATH_BASE = '/org/bluez/example/service' + + def __init__(self, bus, index, uuid, primary): + self.path = self.PATH_BASE + str(index) + self.bus = bus + self.uuid = uuid + self.primary = primary + self.characteristics = [] + dbus.service.Object.__init__(self, bus, self.path) + + def get_properties(self): + return { + GATT_SERVICE_IFACE: { + 'UUID': self.uuid, + 'Primary': self.primary, + 'Characteristics': dbus.Array( + self.get_characteristic_paths(), + signature='o') + } + } + + def get_path(self): + return dbus.ObjectPath(self.path) + + def add_characteristic(self, characteristic): + self.characteristics.append(characteristic) + + def get_characteristic_paths(self): + result = [] + for chrc in self.characteristics: + result.append(chrc.get_path()) + return result + + def get_characteristics(self): + return self.characteristics + + @dbus.service.method(DBUS_PROP_IFACE, + in_signature='s', + out_signature='a{sv}') + def GetAll(self, interface): + if interface != GATT_SERVICE_IFACE: + raise InvalidArgsException() + + return self.get_properties()[GATT_SERVICE_IFACE] + + +class Characteristic(dbus.service.Object): + """ + org.bluez.GattCharacteristic1 interface implementation + """ + def __init__(self, bus, index, uuid, flags, service): + self.path = service.path + '/char' + str(index) + self.bus = bus + self.uuid = uuid + self.service = service + self.flags = flags + self.descriptors = [] + dbus.service.Object.__init__(self, bus, self.path) + + def get_properties(self): + return { + GATT_CHRC_IFACE: { + 'Service': self.service.get_path(), + 'UUID': self.uuid, + 'Flags': self.flags, + 'Descriptors': dbus.Array( + self.get_descriptor_paths(), + signature='o') + } + } + + def get_path(self): + return dbus.ObjectPath(self.path) + + def add_descriptor(self, descriptor): + self.descriptors.append(descriptor) + + def get_descriptor_paths(self): + result = [] + for desc in self.descriptors: + result.append(desc.get_path()) + return result + + def get_descriptors(self): + return self.descriptors + + @dbus.service.method(DBUS_PROP_IFACE, + in_signature='s', + out_signature='a{sv}') + def GetAll(self, interface): + if interface != GATT_CHRC_IFACE: + raise InvalidArgsException() + + return self.get_properties()[GATT_CHRC_IFACE] + + @dbus.service.method(GATT_CHRC_IFACE, + in_signature='a{sv}', + out_signature='ay') + def ReadValue(self, options): + print('Default ReadValue called, returning error') + raise NotSupportedException() + + @dbus.service.method(GATT_CHRC_IFACE, in_signature='aya{sv}') + def WriteValue(self, value, options): + print('Default WriteValue called, returning error') + raise NotSupportedException() + + @dbus.service.method(GATT_CHRC_IFACE) + def StartNotify(self): + print('Default StartNotify called, returning error') + raise NotSupportedException() + + @dbus.service.method(GATT_CHRC_IFACE) + def StopNotify(self): + print('Default StopNotify called, returning error') + raise NotSupportedException() + + @dbus.service.signal(DBUS_PROP_IFACE, + signature='sa{sv}as') + def PropertiesChanged(self, interface, changed, invalidated): + pass + + +class Descriptor(dbus.service.Object): + """ + org.bluez.GattDescriptor1 interface implementation + """ + def __init__(self, bus, index, uuid, flags, characteristic): + self.path = characteristic.path + '/desc' + str(index) + self.bus = bus + self.uuid = uuid + self.flags = flags + self.chrc = characteristic + dbus.service.Object.__init__(self, bus, self.path) + + def get_properties(self): + return { + GATT_DESC_IFACE: { + 'Characteristic': self.chrc.get_path(), + 'UUID': self.uuid, + 'Flags': self.flags, + } + } + + def get_path(self): + return dbus.ObjectPath(self.path) + + @dbus.service.method(DBUS_PROP_IFACE, + in_signature='s', + out_signature='a{sv}') + def GetAll(self, interface): + if interface != GATT_DESC_IFACE: + raise InvalidArgsException() + + return self.get_properties()[GATT_DESC_IFACE] + + @dbus.service.method(GATT_DESC_IFACE, + in_signature='a{sv}', + out_signature='ay') + def ReadValue(self, options): + print ('Default ReadValue called, returning error') + raise NotSupportedException() + + @dbus.service.method(GATT_DESC_IFACE, in_signature='aya{sv}') + def WriteValue(self, value, options): + print('Default WriteValue called, returning error') + raise NotSupportedException() + + +class HeartRateService(Service): + """ + Fake Heart Rate Service that simulates a fake heart beat and control point + behavior. + + """ + HR_UUID = '0000180d-0000-1000-8000-00805f9b34fb' + + def __init__(self, bus, index): + Service.__init__(self, bus, index, self.HR_UUID, True) + self.add_characteristic(HeartRateMeasurementChrc(bus, 0, self)) + self.add_characteristic(BodySensorLocationChrc(bus, 1, self)) + self.add_characteristic(HeartRateControlPointChrc(bus, 2, self)) + self.energy_expended = 0 + + +class HeartRateMeasurementChrc(Characteristic): + HR_MSRMT_UUID = '00002a37-0000-1000-8000-00805f9b34fb' + + def __init__(self, bus, index, service): + Characteristic.__init__( + self, bus, index, + self.HR_MSRMT_UUID, + ['notify'], + service) + self.notifying = False + self.hr_ee_count = 0 + + def hr_msrmt_cb(self): + value = [] + value.append(dbus.Byte(0x06)) + + value.append(dbus.Byte(randint(90, 130))) + + if self.hr_ee_count % 10 == 0: + value[0] = dbus.Byte(value[0] | 0x08) + value.append(dbus.Byte(self.service.energy_expended & 0xff)) + value.append(dbus.Byte((self.service.energy_expended >> 8) & 0xff)) + + self.service.energy_expended = \ + min(0xffff, self.service.energy_expended + 1) + self.hr_ee_count += 1 + + print('Updating value: ' + repr(value)) + + self.PropertiesChanged(GATT_CHRC_IFACE, { 'Value': value }, []) + + return self.notifying + + def _update_hr_msrmt_simulation(self): + print('Update HR Measurement Simulation') + + if not self.notifying: + return + + GObject.timeout_add(1000, self.hr_msrmt_cb) + + def StartNotify(self): + if self.notifying: + print('Already notifying, nothing to do') + return + + self.notifying = True + self._update_hr_msrmt_simulation() + + def StopNotify(self): + if not self.notifying: + print('Not notifying, nothing to do') + return + + self.notifying = False + self._update_hr_msrmt_simulation() + + +class BodySensorLocationChrc(Characteristic): + BODY_SNSR_LOC_UUID = '00002a38-0000-1000-8000-00805f9b34fb' + + def __init__(self, bus, index, service): + Characteristic.__init__( + self, bus, index, + self.BODY_SNSR_LOC_UUID, + ['read'], + service) + + def ReadValue(self, options): + # Return 'Chest' as the sensor location. + return [ 0x01 ] + +class HeartRateControlPointChrc(Characteristic): + HR_CTRL_PT_UUID = '00002a39-0000-1000-8000-00805f9b34fb' + + def __init__(self, bus, index, service): + Characteristic.__init__( + self, bus, index, + self.HR_CTRL_PT_UUID, + ['write'], + service) + + def WriteValue(self, value, options): + print('Heart Rate Control Point WriteValue called') + + if len(value) != 1: + raise InvalidValueLengthException() + + byte = value[0] + print('Control Point value: ' + repr(byte)) + + if byte != 1: + raise FailedException("0x80") + + print('Energy Expended field reset!') + self.service.energy_expended = 0 + + +class BatteryService(Service): + """ + Fake Battery service that emulates a draining battery. + + """ + BATTERY_UUID = '180f' + + def __init__(self, bus, index): + Service.__init__(self, bus, index, self.BATTERY_UUID, True) + self.add_characteristic(BatteryLevelCharacteristic(bus, 0, self)) + + +class BatteryLevelCharacteristic(Characteristic): + """ + Fake Battery Level characteristic. The battery level is drained by 2 points + every 5 seconds. + + """ + BATTERY_LVL_UUID = '2a19' + + def __init__(self, bus, index, service): + Characteristic.__init__( + self, bus, index, + self.BATTERY_LVL_UUID, + ['read', 'notify'], + service) + self.notifying = False + self.battery_lvl = 100 + GObject.timeout_add(5000, self.drain_battery) + + def notify_battery_level(self): + if not self.notifying: + return + self.PropertiesChanged( + GATT_CHRC_IFACE, + { 'Value': [dbus.Byte(self.battery_lvl)] }, []) + + def drain_battery(self): + if not self.notifying: + return True + if self.battery_lvl > 0: + self.battery_lvl -= 2 + if self.battery_lvl < 0: + self.battery_lvl = 0 + print('Battery Level drained: ' + repr(self.battery_lvl)) + self.notify_battery_level() + return True + + def ReadValue(self, options): + print('Battery Level read: ' + repr(self.battery_lvl)) + return [dbus.Byte(self.battery_lvl)] + + def StartNotify(self): + if self.notifying: + print('Already notifying, nothing to do') + return + + self.notifying = True + self.notify_battery_level() + + def StopNotify(self): + if not self.notifying: + print('Not notifying, nothing to do') + return + + self.notifying = False + + +class TestService(Service): + """ + Dummy test service that provides characteristics and descriptors that + exercise various API functionality. + + """ + TEST_SVC_UUID = '12345678-1234-5678-1234-56789abcdef0' + + def __init__(self, bus, index): + Service.__init__(self, bus, index, self.TEST_SVC_UUID, True) + self.add_characteristic(TestCharacteristic(bus, 0, self)) + self.add_characteristic(TestEncryptCharacteristic(bus, 1, self)) + self.add_characteristic(TestSecureCharacteristic(bus, 2, self)) + +class TestCharacteristic(Characteristic): + """ + Dummy test characteristic. Allows writing arbitrary bytes to its value, and + contains "extended properties", as well as a test descriptor. + + """ + TEST_CHRC_UUID = '12345678-1234-5678-1234-56789abcdef1' + + def __init__(self, bus, index, service): + Characteristic.__init__( + self, bus, index, + self.TEST_CHRC_UUID, + ['read', 'write', 'writable-auxiliaries'], + service) + self.value = [] + self.add_descriptor(TestDescriptor(bus, 0, self)) + self.add_descriptor( + CharacteristicUserDescriptionDescriptor(bus, 1, self)) + + def ReadValue(self, options): + print('TestCharacteristic Read: ' + repr(self.value)) + return self.value + + def WriteValue(self, value, options): + print('TestCharacteristic Write: ' + repr(value)) + self.value = value + + +class TestDescriptor(Descriptor): + """ + Dummy test descriptor. Returns a static value. + + """ + TEST_DESC_UUID = '12345678-1234-5678-1234-56789abcdef2' + + def __init__(self, bus, index, characteristic): + Descriptor.__init__( + self, bus, index, + self.TEST_DESC_UUID, + ['read', 'write'], + characteristic) + + def ReadValue(self, options): + return [ + dbus.Byte('T'), dbus.Byte('e'), dbus.Byte('s'), dbus.Byte('t') + ] + + +class CharacteristicUserDescriptionDescriptor(Descriptor): + """ + Writable CUD descriptor. + + """ + CUD_UUID = '2901' + + def __init__(self, bus, index, characteristic): + self.writable = 'writable-auxiliaries' in characteristic.flags + self.value = array.array('B', b'This is a characteristic for testing') + self.value = self.value.tolist() + Descriptor.__init__( + self, bus, index, + self.CUD_UUID, + ['read', 'write'], + characteristic) + + def ReadValue(self, options): + return self.value + + def WriteValue(self, value, options): + if not self.writable: + raise NotPermittedException() + self.value = value + +class TestEncryptCharacteristic(Characteristic): + """ + Dummy test characteristic requiring encryption. + + """ + TEST_CHRC_UUID = '12345678-1234-5678-1234-56789abcdef3' + + def __init__(self, bus, index, service): + Characteristic.__init__( + self, bus, index, + self.TEST_CHRC_UUID, + ['encrypt-read', 'encrypt-write'], + service) + self.value = [] + self.add_descriptor(TestEncryptDescriptor(bus, 2, self)) + self.add_descriptor( + CharacteristicUserDescriptionDescriptor(bus, 3, self)) + + def ReadValue(self, options): + print('TestEncryptCharacteristic Read: ' + repr(self.value)) + return self.value + + def WriteValue(self, value, options): + print('TestEncryptCharacteristic Write: ' + repr(value)) + self.value = value + +class TestEncryptDescriptor(Descriptor): + """ + Dummy test descriptor requiring encryption. Returns a static value. + + """ + TEST_DESC_UUID = '12345678-1234-5678-1234-56789abcdef4' + + def __init__(self, bus, index, characteristic): + Descriptor.__init__( + self, bus, index, + self.TEST_DESC_UUID, + ['encrypt-read', 'encrypt-write'], + characteristic) + + def ReadValue(self, options): + return [ + dbus.Byte('T'), dbus.Byte('e'), dbus.Byte('s'), dbus.Byte('t') + ] + + +class TestSecureCharacteristic(Characteristic): + """ + Dummy test characteristic requiring secure connection. + + """ + TEST_CHRC_UUID = '12345678-1234-5678-1234-56789abcdef5' + + def __init__(self, bus, index, service): + Characteristic.__init__( + self, bus, index, + self.TEST_CHRC_UUID, + ['secure-read', 'secure-write'], + service) + self.value = [] + self.add_descriptor(TestSecureDescriptor(bus, 2, self)) + self.add_descriptor( + CharacteristicUserDescriptionDescriptor(bus, 3, self)) + + def ReadValue(self, options): + print('TestSecureCharacteristic Read: ' + repr(self.value)) + return self.value + + def WriteValue(self, value, options): + print('TestSecureCharacteristic Write: ' + repr(value)) + self.value = value + + +class TestSecureDescriptor(Descriptor): + """ + Dummy test descriptor requiring secure connection. Returns a static value. + + """ + TEST_DESC_UUID = '12345678-1234-5678-1234-56789abcdef6' + + def __init__(self, bus, index, characteristic): + Descriptor.__init__( + self, bus, index, + self.TEST_DESC_UUID, + ['secure-read', 'secure-write'], + characteristic) + + def ReadValue(self, options): + return [ + dbus.Byte('T'), dbus.Byte('e'), dbus.Byte('s'), dbus.Byte('t') + ] + +def register_app_cb(): + print('GATT application registered') + + +def register_app_error_cb(error): + print('Failed to register application: ' + str(error)) + mainloop.quit() + + +def find_adapter(bus): + remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'), + DBUS_OM_IFACE) + objects = remote_om.GetManagedObjects() + + for o, props in objects.items(): + if GATT_MANAGER_IFACE in props.keys(): + return o + + return None + +def main(): + global mainloop + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + adapter = find_adapter(bus) + if not adapter: + print('GattManager1 interface not found') + return + + service_manager = dbus.Interface( + bus.get_object(BLUEZ_SERVICE_NAME, adapter), + GATT_MANAGER_IFACE) + + app = Application(bus) + + mainloop = GObject.MainLoop() + + print('Registering GATT application...') + + service_manager.RegisterApplication(app.get_path(), {}, + reply_handler=register_app_cb, + error_handler=register_app_error_cb) + + mainloop.run() + +if __name__ == '__main__': + main() diff --git a/test/ftp-client b/test/ftp-client new file mode 100755 index 0000000..4540602 --- /dev/null +++ b/test/ftp-client @@ -0,0 +1,180 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser +import os.path +import sys +import dbus +import dbus.service +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject + +BUS_NAME='org.bluez.obex' +PATH = '/org/bluez/obex' +CLIENT_INTERFACE='org.bluez.obex.Client1' +SESSION_INTERFACE='org.bluez.obex.Session1' +FILE_TRASNFER_INTERFACE='org.bluez.obex.FileTransfer1' +TRANSFER_INTERFACE='org.bluez.obex.Transfer1' + +def parse_options(): + parser.add_option("-d", "--device", dest="device", + help="Device to connect", metavar="DEVICE") + parser.add_option("-c", "--chdir", dest="new_dir", + help="Change current directory to DIR", metavar="DIR") + parser.add_option("-l", "--list", action="store_true", dest="list_dir", + help="List the current directory") + parser.add_option("-g", "--get", dest="get_file", + help="Get FILE", metavar="FILE") + parser.add_option("-p", "--put", dest="put_file", + help="Put FILE", metavar="FILE") + parser.add_option("-y", "--copy", dest="copy_file", + help="Copy FILE", metavar="FILE") + parser.add_option("-m", "--move", dest="move_file", + help="Move FILE", metavar="FILE") + parser.add_option("-n", "--destname", dest="dest_file", + help="Destination FILE", metavar="FILE") + parser.add_option("-r", "--remove", dest="remove_file", + help="Remove FILE", metavar="FILE") + parser.add_option("-v", "--verbose", action="store_true", + dest="verbose") + + return parser.parse_args() + +class FtpClient: + def __init__(self, session_path, verbose=False): + self.transferred = 0 + self.transfer_path = None + self.transfer_size = 0 + self.verbose = verbose + bus = dbus.SessionBus() + obj = bus.get_object(BUS_NAME, session_path) + self.session = dbus.Interface(obj, SESSION_INTERFACE) + self.ftp = dbus.Interface(obj, FILE_TRASNFER_INTERFACE) + bus.add_signal_receiver(self.properties_changed, + dbus_interface="org.freedesktop.DBus.Properties", + signal_name="PropertiesChanged", + path_keyword="path") + + def create_transfer_reply(self, path, properties): + self.transfer_path = path + self.transfer_size = properties["Size"] + if self.verbose: + print("Transfer created: %s" % path) + + def generic_reply(self): + if self.verbose: + print("Operation succeeded") + + def error(self, err): + print(err) + mainloop.quit() + + def properties_changed(self, interface, properties, invalidated, path): + if path != self.transfer_path: + return + + if "Status" in properties and \ + (properties['Status'] == 'complete' or \ + properties['Status'] == 'error'): + if self.verbose: + print("Transfer %s" % properties['Status']) + mainloop.quit() + return + + if "Transferred" not in properties: + return + + value = properties["Transferred"] + speed = (value - self.transferred) / 1000 + print("Transfer progress %d/%d at %d kBps" % (value, + self.transfer_size, + speed)) + self.transferred = value + + def change_folder(self, new_dir): + for node in new_dir.split("/"): + self.ftp.ChangeFolder(node) + + def list_folder(self): + for i in self.ftp.ListFolder(): + if i["Type"] == "folder": + print("%s/" % (i["Name"])) + else: + print("%s" % (i["Name"])) + + def put_file(self, filename): + self.ftp.PutFile(os.path.abspath(filename), + os.path.basename(filename), + reply_handler=self.create_transfer_reply, + error_handler=self.error) + + def get_file(self, filename): + self.ftp.GetFile(os.path.abspath(filename), + os.path.basename(filename), + reply_handler=self.create_transfer_reply, + error_handler=self.error) + + def remove_file(self, filename): + self.ftp.Delete(filename, + reply_handler=self.generic_reply, + error_handler=self.error) + + def move_file(self, filename, destname): + self.ftp.MoveFile(filename, destname, + reply_handler=self.generic_reply, + error_handler=self.error) + + def copy_file(self, filename, destname): + self.ftp.CopyFile(filename, destname, + reply_handler=self.generic_reply, + error_handler=self.error) + +if __name__ == '__main__': + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + parser = OptionParser() + + (options, args) = parse_options() + + if not options.device: + parser.print_help() + sys.exit(0) + + bus = dbus.SessionBus() + mainloop = GObject.MainLoop() + + client = dbus.Interface(bus.get_object(BUS_NAME, PATH,), + CLIENT_INTERFACE) + + print("Creating Session") + path = client.CreateSession(options.device, { "Target": "ftp" }) + + ftp_client = FtpClient(path, options.verbose) + + if options.new_dir: + ftp_client.change_folder(options.new_dir) + + if options.list_dir: + ftp_client.list_folder() + + if options.get_file: + ftp_client.get_file(options.get_file) + + if options.put_file: + ftp_client.put_file(options.put_file) + + if options.move_file: + ftp_client.move_file(options.move_file, options.dest_file) + + if options.copy_file: + ftp_client.copy_file(options.copy_file, options.dest_file) + + if options.remove_file: + ftp_client.remove_file(options.remove_file) + + mainloop.run() diff --git a/test/list-devices b/test/list-devices new file mode 100755 index 0000000..0aac217 --- /dev/null +++ b/test/list-devices @@ -0,0 +1,76 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +import dbus + +bus = dbus.SystemBus() + +manager = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.freedesktop.DBus.ObjectManager") + +def extract_objects(object_list): + list = "" + for object in object_list: + val = str(object) + list = list + val[val.rfind("/") + 1:] + " " + return list + +def extract_uuids(uuid_list): + list = "" + for uuid in uuid_list: + if (uuid.endswith("-0000-1000-8000-00805f9b34fb")): + if (uuid.startswith("0000")): + val = "0x" + uuid[4:8] + else: + val = "0x" + uuid[0:8] + else: + val = str(uuid) + list = list + val + " " + return list + +objects = manager.GetManagedObjects() + +all_devices = (str(path) for path, interfaces in objects.iteritems() if + "org.bluez.Device1" in interfaces.keys()) + +for path, interfaces in objects.iteritems(): + if "org.bluez.Adapter1" not in interfaces.keys(): + continue + + print("[ " + path + " ]") + + properties = interfaces["org.bluez.Adapter1"] + for key in properties.keys(): + value = properties[key] + if (key == "UUIDs"): + list = extract_uuids(value) + print(" %s = %s" % (key, list)) + else: + print(" %s = %s" % (key, value)) + + device_list = [d for d in all_devices if d.startswith(path + "/")] + + for dev_path in device_list: + print(" [ " + dev_path + " ]") + + dev = objects[dev_path] + properties = dev["org.bluez.Device1"] + + for key in properties.keys(): + value = properties[key] + if (key == "UUIDs"): + list = extract_uuids(value) + print(" %s = %s" % (key, list)) + elif (key == "Class"): + print(" %s = 0x%06x" % (key, value)) + elif (key == "Vendor"): + print(" %s = 0x%04x" % (key, value)) + elif (key == "Product"): + print(" %s = 0x%04x" % (key, value)) + elif (key == "Version"): + print(" %s = 0x%04x" % (key, value)) + else: + print(" %s = %s" % (key, value)) + + print("") diff --git a/test/map-client b/test/map-client new file mode 100755 index 0000000..b9695da --- /dev/null +++ b/test/map-client @@ -0,0 +1,232 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser +import os +from pprint import pformat +import sys +import dbus +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject + +BUS_NAME='org.bluez.obex' +PATH = '/org/bluez/obex' +CLIENT_INTERFACE = 'org.bluez.obex.Client1' +SESSION_INTERFACE = 'org.bluez.obex.Session1' +MESSAGE_ACCESS_INTERFACE = 'org.bluez.obex.MessageAccess1' +MESSAGE_INTERFACE = 'org.bluez.obex.Message1' +TRANSFER_INTERFACE = 'org.bluez.obex.Transfer1' + +def unwrap(x): + """Hack to unwrap D-Bus values, so that they're easier to read when + printed. Taken from d-feet """ + + if isinstance(x, list): + return map(unwrap, x) + + if isinstance(x, tuple): + return tuple(map(unwrap, x)) + + if isinstance(x, dict): + return dict([(unwrap(k), unwrap(v)) for k, v in x.iteritems()]) + + for t in [unicode, str, long, int, float, bool]: + if isinstance(x, t): + return t(x) + + return x + +def parse_options(): + parser.add_option("-d", "--device", dest="device", + help="Device to connect", metavar="DEVICE") + parser.add_option("-c", "--chdir", dest="new_dir", + help="Change current directory to DIR", metavar="DIR") + parser.add_option("-l", "--lsdir", action="store_true", dest="ls_dir", + help="List folders in current directory") + parser.add_option("-v", "--verbose", action="store_true", dest="verbose") + parser.add_option("-L", "--lsmsg", action="store", dest="ls_msg", + help="List messages in supplied CWD subdir") + parser.add_option("-g", "--get", action="store", dest="get_msg", + help="Get message contents") + parser.add_option("-p", "--push", action="store", dest="push_msg", + help="Push message") + parser.add_option("--get-properties", action="store", dest="get_msg_properties", + help="Get message properties") + parser.add_option("--mark-read", action="store", dest="mark_msg_read", + help="Marks the messages as read") + parser.add_option("--mark-unread", action="store", dest="mark_msg_unread", + help="Marks the messages as unread") + parser.add_option("--mark-deleted", action="store", dest="mark_msg_deleted", + help="Deletes the message from the folder") + parser.add_option("--mark-undeleted", action="store", dest="mark_msg_undeleted", + help="Undeletes the message") + parser.add_option("-u", "--update-inbox", action="store_true", dest="update_inbox", + help="Checks for new mails") + + return parser.parse_args() + +def set_folder(session, new_dir): + session.SetFolder(new_dir) + +class MapClient: + def __init__(self, session_path, verbose=False): + self.progress = 0 + self.transfer_path = None + self.props = dict() + self.verbose = verbose + self.path = session_path + bus = dbus.SessionBus() + obj = bus.get_object(BUS_NAME, session_path) + self.session = dbus.Interface(obj, SESSION_INTERFACE) + self.map = dbus.Interface(obj, MESSAGE_ACCESS_INTERFACE) + bus.add_signal_receiver(self.properties_changed, + dbus_interface="org.freedesktop.DBus.Properties", + signal_name="PropertiesChanged", + path_keyword="path") + + def create_transfer_reply(self, path, properties): + self.transfer_path = path + self.props[path] = properties + if self.verbose: + print("Transfer created: %s (file %s)" % (path, + properties["Filename"])) + + def generic_reply(self): + if self.verbose: + print("Operation succeeded") + + def error(self, err): + print(err) + mainloop.quit() + + def transfer_complete(self, path): + if self.verbose: + print("Transfer finished") + properties = self.props.get(path) + if properties == None: + return + f = open(properties["Filename"], "r") + os.remove(properties["Filename"]) + print(f.readlines()) + + def transfer_error(self, path): + print("Transfer %s error" % path) + mainloop.quit() + + def properties_changed(self, interface, properties, invalidated, path): + req = self.props.get(path) + if req == None: + return + + if properties['Status'] == 'complete': + self.transfer_complete(path) + return + + if properties['Status'] == 'error': + self.transfer_error(path) + return + + def set_folder(self, new_dir): + self.map.SetFolder(new_dir) + + def list_folders(self): + for i in self.map.ListFolders(dict()): + print("%s/" % (i["Name"])) + + def list_messages(self, folder): + ret = self.map.ListMessages(folder, dict()) + print(pformat(unwrap(ret))) + + def get_message(self, handle): + self.map.ListMessages("", dict()) + path = self.path + "/message" + handle + obj = bus.get_object(BUS_NAME, path) + msg = dbus.Interface(obj, MESSAGE_INTERFACE) + msg.Get("", True, reply_handler=self.create_transfer_reply, + error_handler=self.error) + + def push_message(self, filename): + self.map.PushMessage(filename, "telecom/msg/outbox", dict(), + reply_handler=self.create_transfer_reply, + error_handler=self.error) + + def get_message_properties(self, handle): + self.map.ListMessages("", dict()) + path = self.path + "/message" + handle + obj = bus.get_object(BUS_NAME, path) + msg = dbus.Interface(obj, "org.freedesktop.DBus.Properties") + ret = msg.GetAll(MESSAGE_INTERFACE) + print(pformat(unwrap(ret))) + + def set_message_property(self, handle, prop, flag): + self.map.ListMessages("", dict()) + path = self.path + "/message" + handle + obj = bus.get_object(BUS_NAME, path) + msg = dbus.Interface(obj, MESSAGE_INTERFACE) + msg.SetProperty (prop, flag); + + def update_inbox(self): + self.map.UpdateInbox() + + +if __name__ == '__main__': + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + parser = OptionParser() + + (options, args) = parse_options() + + if not options.device: + parser.print_help() + exit(0) + + bus = dbus.SessionBus() + mainloop = GObject.MainLoop() + + client = dbus.Interface(bus.get_object(BUS_NAME, PATH), + CLIENT_INTERFACE) + + print("Creating Session") + path = client.CreateSession(options.device, { "Target": "map" }) + + map_client = MapClient(path, options.verbose) + + if options.new_dir: + map_client.set_folder(options.new_dir) + + if options.ls_dir: + map_client.list_folders() + + if options.ls_msg is not None: + map_client.list_messages(options.ls_msg) + + if options.get_msg is not None: + map_client.get_message(options.get_msg) + + if options.push_msg is not None: + map_client.push_message(options.push_msg) + + if options.get_msg_properties is not None: + map_client.get_message_properties(options.get_msg_properties) + + if options.mark_msg_read is not None: + map_client.set_message_property(options.mark_msg_read, "Read", True) + + if options.mark_msg_unread is not None: + map_client.set_message_property(options.mark_msg_unread, "Read", False) + + if options.mark_msg_deleted is not None: + map_client.set_message_property(options.mark_msg_deleted, "Deleted", True) + + if options.mark_msg_undeleted is not None: + map_client.set_message_property(options.mark_msg_undeleted, "Deleted", False) + + if options.update_inbox: + map_client.update_inbox() + + mainloop.run() diff --git a/test/monitor-bluetooth b/test/monitor-bluetooth new file mode 100755 index 0000000..d9b5472 --- /dev/null +++ b/test/monitor-bluetooth @@ -0,0 +1,54 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +import dbus +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject + +relevant_ifaces = [ "org.bluez.Adapter1", "org.bluez.Device1" ] + +def property_changed(interface, changed, invalidated, path): + iface = interface[interface.rfind(".") + 1:] + for name, value in changed.iteritems(): + val = str(value) + print("{%s.PropertyChanged} [%s] %s = %s" % (iface, path, name, + val)) + +def interfaces_added(path, interfaces): + for iface, props in interfaces.iteritems(): + if not(iface in relevant_ifaces): + continue + print("{Added %s} [%s]" % (iface, path)) + for name, value in props.iteritems(): + print(" %s = %s" % (name, value)) + +def interfaces_removed(path, interfaces): + for iface in interfaces: + if not(iface in relevant_ifaces): + continue + print("{Removed %s} [%s]" % (iface, path)) + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + bus.add_signal_receiver(property_changed, bus_name="org.bluez", + dbus_interface="org.freedesktop.DBus.Properties", + signal_name="PropertiesChanged", + path_keyword="path") + + bus.add_signal_receiver(interfaces_added, bus_name="org.bluez", + dbus_interface="org.freedesktop.DBus.ObjectManager", + signal_name="InterfacesAdded") + + bus.add_signal_receiver(interfaces_removed, bus_name="org.bluez", + dbus_interface="org.freedesktop.DBus.ObjectManager", + signal_name="InterfacesRemoved") + + mainloop = GObject.MainLoop() + mainloop.run() diff --git a/test/opp-client b/test/opp-client new file mode 100755 index 0000000..62d5b84 --- /dev/null +++ b/test/opp-client @@ -0,0 +1,119 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser +import os.path +import sys +import dbus +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject + +BUS_NAME='org.bluez.obex' +PATH = '/org/bluez/obex' +CLIENT_INTERFACE='org.bluez.obex.Client1' +SESSION_INTERFACE='org.bluez.obex.Session1' +OBJECT_PUSH_INTERFACE='org.bluez.obex.ObjectPush1' +TRANSFER_INTERFACE='org.bluez.obex.Transfer1' + +def parse_options(): + parser.add_option("-d", "--device", dest="device", + help="Device to connect", metavar="DEVICE") + parser.add_option("-p", "--pull", dest="pull_to_file", + help="Pull vcard and store in FILE", metavar="FILE") + parser.add_option("-s", "--send", dest="send_file", + help="Send FILE", metavar="FILE") + parser.add_option("-v", "--verbose", action="store_true", + dest="verbose") + + return parser.parse_args() + +class OppClient: + def __init__(self, session_path, verbose=False): + self.transferred = 0 + self.transfer_path = None + self.verbose = verbose + bus = dbus.SessionBus() + obj = bus.get_object(BUS_NAME, session_path) + self.session = dbus.Interface(obj, SESSION_INTERFACE) + self.opp = dbus.Interface(obj, OBJECT_PUSH_INTERFACE) + bus.add_signal_receiver(self.properties_changed, + dbus_interface="org.freedesktop.DBus.Properties", + signal_name="PropertiesChanged", + path_keyword="path") + + def create_transfer_reply(self, path, properties): + self.transfer_path = path + self.transfer_size = properties["Size"] + if self.verbose: + print("Transfer created: %s" % path) + + def error(self, err): + print(err) + mainloop.quit() + + def properties_changed(self, interface, properties, invalidated, path): + if path != self.transfer_path: + return + + if "Status" in properties and \ + (properties["Status"] == "complete" or \ + properties["Status"] == "error"): + if self.verbose: + print("Transfer %s" % properties["Status"]) + mainloop.quit() + return + + if "Transferred" not in properties: + return + + value = properties["Transferred"] + speed = (value - self.transferred) / 1000 + print("Transfer progress %d/%d at %d kBps" % (value, + self.transfer_size, + speed)) + self.transferred = value + + def pull_business_card(self, filename): + self.opp.PullBusinessCard(os.path.abspath(filename), + reply_handler=self.create_transfer_reply, + error_handler=self.error) + + def send_file(self, filename): + self.opp.SendFile(os.path.abspath(filename), + reply_handler=self.create_transfer_reply, + error_handler=self.error) + +if __name__ == '__main__': + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + parser = OptionParser() + + (options, args) = parse_options() + + if not options.device: + parser.print_help() + sys.exit(0) + + bus = dbus.SessionBus() + mainloop = GObject.MainLoop() + + client = dbus.Interface(bus.get_object(BUS_NAME, PATH), + CLIENT_INTERFACE) + + print("Creating Session") + path = client.CreateSession(options.device, { "Target": "OPP" }) + + opp_client = OppClient(path, options.verbose) + + if options.pull_to_file: + opp_client.pull_business_card(options.pull_to_file) + + if options.send_file: + opp_client.send_file(options.send_file) + + mainloop.run() diff --git a/test/pbap-client b/test/pbap-client new file mode 100755 index 0000000..16a786b --- /dev/null +++ b/test/pbap-client @@ -0,0 +1,175 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +import os +import sys +import dbus +import dbus.service +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject + +BUS_NAME='org.bluez.obex' +PATH = '/org/bluez/obex' +CLIENT_INTERFACE = 'org.bluez.obex.Client1' +SESSION_INTERFACE = 'org.bluez.obex.Session1' +PHONEBOOK_ACCESS_INTERFACE = 'org.bluez.obex.PhonebookAccess1' +TRANSFER_INTERFACE = 'org.bluez.obex.Transfer1' + +class Transfer: + def __init__(self, callback_func): + self.callback_func = callback_func + self.path = None + self.filename = None + +class PbapClient: + def __init__(self, session_path): + self.transfers = 0 + self.props = dict() + self.flush_func = None + bus = dbus.SessionBus() + obj = bus.get_object(BUS_NAME, session_path) + self.session = dbus.Interface(obj, SESSION_INTERFACE) + self.pbap = dbus.Interface(obj, PHONEBOOK_ACCESS_INTERFACE) + bus.add_signal_receiver(self.properties_changed, + dbus_interface="org.freedesktop.DBus.Properties", + signal_name="PropertiesChanged", + path_keyword="path") + + def register(self, path, properties, transfer): + transfer.path = path + transfer.filename = properties["Filename"] + self.props[path] = transfer + print("Transfer created: %s (file %s)" % (path, + transfer.filename)) + + def error(self, err): + print(err) + mainloop.quit() + + def transfer_complete(self, path): + req = self.props.get(path) + if req == None: + return + self.transfers -= 1 + print("Transfer %s complete" % path) + try: + f = open(req.filename, "r") + os.remove(req.filename) + lines = f.readlines() + del self.props[path] + req.callback_func(lines) + except: + pass + + if (len(self.props) == 0) and (self.transfers == 0): + if self.flush_func != None: + f = self.flush_func + self.flush_func = None + f() + + def transfer_error(self, path): + print("Transfer %s error" % path) + mainloop.quit() + + def properties_changed(self, interface, properties, invalidated, path): + req = self.props.get(path) + if req == None: + return + + if properties['Status'] == 'complete': + self.transfer_complete(path) + return + + if properties['Status'] == 'error': + self.transfer_error(path) + return + + def pull(self, vcard, params, func): + req = Transfer(func) + self.pbap.Pull(vcard, "", params, + reply_handler=lambda o, p: self.register(o, p, req), + error_handler=self.error) + self.transfers += 1 + + def pull_all(self, params, func): + req = Transfer(func) + self.pbap.PullAll("", params, + reply_handler=lambda o, p: self.register(o, p, req), + error_handler=self.error) + self.transfers += 1 + + def flush_transfers(self, func): + if (len(self.props) == 0) and (self.transfers == 0): + return + self.flush_func = func + + def interface(self): + return self.pbap + +if __name__ == '__main__': + + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SessionBus() + mainloop = GObject.MainLoop() + + client = dbus.Interface(bus.get_object(BUS_NAME, PATH), + CLIENT_INTERFACE) + + if (len(sys.argv) < 2): + print("Usage: %s " % (sys.argv[0])) + sys.exit(1) + + print("Creating Session") + session_path = client.CreateSession(sys.argv[1], { "Target": "PBAP" }) + + pbap_client = PbapClient(session_path) + + def process_result(lines, header): + if header != None: + print(header) + for line in lines: + print(line), + print + + def test_paths(paths): + if len(paths) == 0: + print + print("FINISHED") + mainloop.quit() + return + + path = paths[0] + + print("\n--- Select Phonebook %s ---\n" % (path)) + pbap_client.interface().Select("int", path) + + print("\n--- GetSize ---\n") + ret = pbap_client.interface().GetSize() + print("Size = %d\n" % (ret)) + + print("\n--- List vCard ---\n") + try: + ret = pbap_client.interface().List(dbus.Dictionary()) + except: + ret = [] + + params = dbus.Dictionary({ "Format" : "vcard30", + "Fields" : ["PHOTO"] }) + for item in ret: + print("%s : %s" % (item[0], item[1])) + pbap_client.pull(item[0], params, + lambda x: process_result(x, None)) + + pbap_client.pull_all(params, lambda x: process_result(x, + "\n--- PullAll ---\n")) + + pbap_client.flush_transfers(lambda: test_paths(paths[1:])) + + test_paths(["PB", "ICH", "OCH", "MCH", "CCH", "SPD", "FAV"]) + + mainloop.run() diff --git a/test/sap_client.py b/test/sap_client.py new file mode 100644 index 0000000..413424c --- /dev/null +++ b/test/sap_client.py @@ -0,0 +1,943 @@ +""" Copyright (C) 2010-2011 ST-Ericsson SA """ + +""" Author: Szymon Janc for ST-Ericsson. """ + +""" This program is free software; you can redistribute it and/or modify """ +""" it under the terms of the GNU General Public License as published by """ +""" the Free Software Foundation; either version 2 of the License, or """ +""" (at your option) any later version. """ + +""" This program is distributed in the hope that it will be useful, """ +""" but WITHOUT ANY WARRANTY; without even the implied warranty of """ +""" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the """ +""" GNU General Public License for more details. """ + +""" You should have received a copy of the GNU General Public License """ +""" along with this program; if not, write to the Free Software """ +""" Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ + +from array import array +from bluetooth import * +import time +import re + +class SAPParam: + """ SAP Parameter Class """ + + MaxMsgSize = 0x00 + ConnectionStatus = 0x01 + ResultCode = 0x02 + DisconnectionType = 0x03 + CommandAPDU = 0x04 + ResponseAPDU = 0x05 + ATR = 0x06 + CardReaderStatus = 0x07 + StatusChange = 0x08 + TransportProtocol = 0x09 + CommandAPDU7816 = 0x10 + + def __init__(self, name, id, value = None): + self.name = name + self.id = id + self.value = value + + def _padding(self, buf): + pad = array('B') + while ( (len(buf) + len(pad)) % 4 ) != 0: + pad.append(0) + return pad + + def _basicCheck(self, buf): + if len(buf) < 4 or (len(buf) % 4) != 0 or buf[1] != 0: + return (-1, -1) + if buf[0] != self.id: + return (-1, -1) + plen = buf[2] * 256 + buf[3] + 4 + if plen > len(buf): + return (-1, -1) + pad = plen + while (pad % 4) != 0: + if buf[pad] != 0: + return (-1, -1) + pad+=1 + return (plen, pad) + + def getID(self): + return self.id + + def getValue(self): + return self.value + + def getContent(self): + return "%s(id=0x%.2X), value=%s \n" % (self.name, self.id, self.value) + + def serialize(self): + a = array('B', '\00\00\00\00') + a[0] = self.id + a[1] = 0 # reserved + a[2] = 0 # length + a[3] = 1 # length + a.append(self.value) + a.extend(self._padding(a)) + return a + + def deserialize(self, buf): + p = self._basicCheck(buf) + if p[0] == -1: + return -1 + self.id = buf[0] + self.value = buf[4] + return p[1] + + +class SAPParam_MaxMsgSize(SAPParam): + """MaxMsgSize Param """ + + def __init__(self, value = None): + SAPParam.__init__(self,"MaxMsgSize", SAPParam.MaxMsgSize, value) + self.__validate() + + def __validate(self): + if self.value > 0xFFFF: + self.value = 0xFFFF + + def serialize(self): + a = array('B', '\00\00\00\00') + a[0] = self.id + a[3] = 2 + a.append(self.value / 256) + a.append(self.value % 256) + a.extend(self._padding(a)) + return a + + def deserialize(self, buf): + p = self._basicCheck(buf) + if p[0] == -1 : + return -1 + self.value = buf[4] * 256 + buf[5] + return p[1] + +class SAPParam_CommandAPDU(SAPParam): + def __init__(self, value = None): + if value is None: + SAPParam.__init__(self, "CommandAPDU", SAPParam.CommandAPDU, array('B')) + else: + SAPParam.__init__(self, "CommandAPDU", SAPParam.CommandAPDU, array('B', value)) + + def serialize(self): + a = array('B', '\00\00\00\00') + a[0] = self.id + plen = len(self.value) + a[2] = plen / 256 + a[3] = plen % 256 + a.extend(self.value) + a.extend(self._padding(a)) + return a + + def deserialize(self, buf): + p = self._basicCheck(buf) + if p[0] == -1: + return -1 + self.value = buf[4:p[0]] + return p[1] + +class SAPParam_ResponseAPDU(SAPParam_CommandAPDU): + """ResponseAPDU Param """ + + def __init__(self, value = None): + if value is None: + SAPParam.__init__(self, "ResponseAPDU", SAPParam.ResponseAPDU, array('B')) + else: + SAPParam.__init__(self, "ResponseAPDU", SAPParam.ResponseAPDU, array('B', value)) + +class SAPParam_ATR(SAPParam_CommandAPDU): + """ATR Param """ + + def __init__(self, value = None): + if value is None: + SAPParam.__init__(self, "ATR", SAPParam.ATR, array('B')) + else: + SAPParam.__init__(self, "ATR", SAPParam.ATR, array('B', value)) + +class SAPParam_CommandAPDU7816(SAPParam_CommandAPDU): + """Command APDU7816 Param.""" + + def __init__(self, value = None): + if value is None: + SAPParam.__init__(self, "CommandAPDU7816", SAPParam.CommandAPDU7816, array('B')) + else: + SAPParam.__init__(self, "CommandAPDU7816", SAPParam.CommandAPDU7816, array('B', value)) + + +class SAPParam_ConnectionStatus(SAPParam): + """Connection status Param.""" + + def __init__(self, value = None): + SAPParam.__init__(self,"ConnectionStatus", SAPParam.ConnectionStatus, value) + self.__validate() + + def __validate(self): + if self.value is not None and self.value not in (0x00, 0x01, 0x02, 0x03, 0x04): + print "Warning. ConnectionStatus value in reserved range (0x%x)" % self.value + + def deserialize(self, buf): + ret = SAPParam.deserialize(self, buf) + if ret == -1: + return -1 + self.__validate() + return ret + +class SAPParam_ResultCode(SAPParam): + """ Result Code Param """ + + def __init__(self, value = None): + SAPParam.__init__(self,"ResultCode", SAPParam.ResultCode, value) + self.__validate() + + def __validate(self): + if self.value is not None and self.value not in (0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07): + print "Warning. ResultCode value in reserved range (0x%x)" % self.value + + def deserialize(self, buf): + ret = SAPParam.deserialize(self, buf) + if ret == -1: + return -1 + self.__validate() + return ret + +class SAPParam_DisconnectionType(SAPParam): + """Disconnection Type Param.""" + + def __init__(self, value = None): + SAPParam.__init__(self,"DisconnectionType", SAPParam.DisconnectionType, value) + self.__validate() + + def __validate(self): + if self.value is not None and self.value not in (0x00, 0x01): + print "Warning. DisconnectionType value in reserved range (0x%x)" % self.value + + def deserialize(self, buf): + ret = SAPParam.deserialize(self, buf) + if ret == -1: + return -1 + self.__validate() + return ret + +class SAPParam_CardReaderStatus(SAPParam_CommandAPDU): + """Card reader Status Param.""" + + def __init__(self, value = None): + if value is None: + SAPParam.__init__(self, "CardReaderStatus", SAPParam.CardReaderStatus, array('B')) + else: + SAPParam.__init__(self, "CardReaderStatus", SAPParam.CardReaderStatus, array('B', value)) + +class SAPParam_StatusChange(SAPParam): + """Status Change Param """ + + def __init__(self, value = None): + SAPParam.__init__(self,"StatusChange", SAPParam.StatusChange, value) + + def __validate(self): + if self.value is not None and self.value not in (0x00, 0x01, 0x02, 0x03, 0x04, 0x05): + print "Warning. StatusChange value in reserved range (0x%x)" % self.value + + def deserialize(self, buf): + ret = SAPParam.deserialize(self, buf) + if ret == -1: + return -1 + self.__validate() + return ret + +class SAPParam_TransportProtocol(SAPParam): + """Transport Protocol Param """ + + def __init__(self, value = None): + SAPParam.__init__(self,"TransportProtocol", SAPParam.TransportProtocol, value) + self.__validate() + + def __validate(self): + if self.value is not None and self.value not in (0x00, 0x01): + print "Warning. TransportProtoco value in reserved range (0x%x)" % self.value + + def deserialize(self, buf): + ret = SAPParam.deserialize(self, buf) + if ret == -1: + return -1 + self.__validate() + return ret + +class SAPMessage: + + CONNECT_REQ = 0x00 + CONNECT_RESP = 0x01 + DISCONNECT_REQ = 0x02 + DISCONNECT_RESP =0x03 + DISCONNECT_IND = 0x04 + TRANSFER_APDU_REQ = 0x05 + TRANSFER_APDU_RESP = 0x06 + TRANSFER_ATR_REQ = 0x07 + TRANSFER_ATR_RESP = 0x08 + POWER_SIM_OFF_REQ = 0x09 + POWER_SIM_OFF_RESP = 0x0A + POWER_SIM_ON_REQ = 0x0B + POWER_SIM_ON_RESP = 0x0C + RESET_SIM_REQ = 0x0D + RESET_SIM_RESP = 0x0E + TRANSFER_CARD_READER_STATUS_REQ = 0x0F + TRANSFER_CARD_READER_STATUS_RESP = 0x10 + STATUS_IND = 0x11 + ERROR_RESP = 0x12 + SET_TRANSPORT_PROTOCOL_REQ = 0x13 + SET_TRANSPORT_PROTOCOL_RESP = 0x14 + + def __init__(self, name, id): + self.name = name + self.id = id + self.params = [] + self.buf = array('B') + + def _basicCheck(self, buf): + if len(buf) < 4 or (len(buf) % 4) != 0 : + return False + + if buf[0] != self.id: + return False + + return True + + def getID(self): + return self.id + + def getContent(self): + s = "%s(id=0x%.2X) " % (self.name, self.id) + if len( self.buf): s = s + "[%s]" % re.sub("(.{2})", "0x\\1 " , self.buf.tostring().encode("hex").upper(), re.DOTALL) + s = s + "\n\t" + for p in self.params: + s = s + "\t" + p.getContent() + return s + + def getParams(self): + return self.params + + def addParam(self, param): + self.params.append(param) + + def serialize(self): + ret = array('B', '\00\00\00\00') + ret[0] = self.id + ret[1] = len(self.params) + ret[2] = 0 # reserved + ret[3] = 0 # reserved + for p in self.params: + ret.extend(p.serialize()) + + self.buf = ret + return ret + + def deserialize(self, buf): + self.buf = buf + return len(buf) == 4 and buf[1] == 0 and self._basicCheck(buf) + + +class SAPMessage_CONNECT_REQ(SAPMessage): + def __init__(self, MaxMsgSize = None): + SAPMessage.__init__(self,"CONNECT_REQ", SAPMessage.CONNECT_REQ) + if MaxMsgSize is not None: + self.addParam(SAPParam_MaxMsgSize(MaxMsgSize)) + + def _validate(self): + if len(self.params) == 1: + if self.params[0].getID() == SAPParam.MaxMsgSize: + return True + return False + + def deserialize(self, buf): + self.buf = buf + self.params[:] = [] + if SAPMessage._basicCheck(self, buf): + p = SAPParam_MaxMsgSize() + if p.deserialize(buf[4:]) == len(buf[4:]): + self.addParam(p) + return self._validate() + + return False + +class SAPMessage_CONNECT_RESP(SAPMessage): + def __init__(self, ConnectionStatus = None, MaxMsgSize = None): + SAPMessage.__init__(self,"CONNECT_RESP", SAPMessage.CONNECT_RESP) + if ConnectionStatus is not None: + self.addParam(SAPParam_ConnectionStatus(ConnectionStatus)) + if MaxMsgSize is not None: + self.addParam(SAPParam_MaxMsgSize(MaxMsgSize)) + + def _validate(self): + if len(self.params) > 0: + if self.params[0] .getID() == SAPParam.ConnectionStatus: + if self.params[0].getValue() == 0x02: + if len(self.params) == 2: + return True + else: + if len(self.params) == 1: + return True + return False + + def deserialize(self, buf): + self.buf = buf + self.params[:] = [] + + if SAPMessage._basicCheck(self, buf): + p = SAPParam_ConnectionStatus() + r = p.deserialize(buf[4:]) + if r != -1: + self.addParam(p) + if buf[1] == 2: + p = SAPParam_MaxMsgSize() + r = p.deserialize(buf[4+r:]) + if r != -1: + self.addParam(p) + + return self._validate() + + return False + +class SAPMessage_DISCONNECT_REQ(SAPMessage): + def __init__(self): + SAPMessage.__init__(self,"DISCONNECT_REQ", SAPMessage.DISCONNECT_REQ) + +class SAPMessage_DISCONNECT_RESP(SAPMessage): + def __init__(self): + SAPMessage.__init__(self,"DISCONNECT_RESP", SAPMessage.DISCONNECT_RESP) + +class SAPMessage_DISCONNECT_IND(SAPMessage): + def __init__(self, Type = None): + SAPMessage.__init__(self,"DISCONNECT_IND", SAPMessage.DISCONNECT_IND) + if Type is not None: + self.addParam(SAPParam_DisconnectionType(Type)) + + def _validate(self): + if len(self.params) == 1: + if self.params[0].getID() == SAPParam.DisconnectionType: + return True + return False + + def deserialize(self, buf): + self.buf = buf + self.params[:] = [] + if SAPMessage._basicCheck(self, buf): + p = SAPParam_DisconnectionType() + if p.deserialize(buf[4:]) == len(buf[4:]): + self.addParam(p) + return self._validate() + + return False + + +class SAPMessage_TRANSFER_APDU_REQ(SAPMessage): + def __init__(self, APDU = None, T = False): + SAPMessage.__init__(self,"TRANSFER_APDU_REQ", SAPMessage.TRANSFER_APDU_REQ) + if APDU is not None: + if T : + self.addParam(SAPParam_CommandAPDU(APDU)) + else: + self.addParam(SAPParam_CommandAPDU7816(APDU)) + + def _validate(self): + if len(self.params) == 1: + if self.params[0].getID() == SAPParam.CommandAPDU or self.params[0].getID() == SAPParam.CommandAPDU7816: + return True + return False + + def deserialize(self, buf): + self.buf = buf + self.params[:] = [] + if SAPMessage._basicCheck(self, buf): + + p = SAPParam_CommandAPDU() + p2 = SAPParam_CommandAPDU7816() + if p.deserialize(buf[4:]) == len(buf[4:]): + self.addParam(p) + return self._validate() + elif p2.deserialize(buf[4:]) == len(buf[4:]): + self.addParam(p2) + return self._validate() + + return False + +class SAPMessage_TRANSFER_APDU_RESP(SAPMessage): + def __init__(self, ResultCode = None, Response = None): + SAPMessage.__init__(self,"TRANSFER_APDU_RESP", SAPMessage.TRANSFER_APDU_RESP) + if ResultCode is not None: + self.addParam(SAPParam_ResultCode(ResultCode)) + if Response is not None: + self.addParam(SAPParam_ResponseAPDU(Response)) + + def _validate(self): + if len(self.params) > 0: + if self.params[0] .getID() == SAPParam.ResultCode: + if self.params[0].getValue() == 0x00: + if len(self.params) == 2: + return True + else: + if len(self.params) == 1: + return True + return False + + def deserialize(self, buf): + self.buf = buf + self.params[:] = [] + + if SAPMessage._basicCheck(self, buf): + p = SAPParam_ResultCode() + r = p.deserialize(buf[4:]) + if r != -1: + self.addParam(p) + if buf[1] == 2: + p = SAPParam_ResponseAPDU() + r = p.deserialize(buf[4+r:]) + if r != -1: + self.addParam(p) + + return self._validate() + + return False + +class SAPMessage_TRANSFER_ATR_REQ(SAPMessage): + def __init__(self): + SAPMessage.__init__(self,"TRANSFER_ATR_REQ", SAPMessage.TRANSFER_ATR_REQ) + +class SAPMessage_TRANSFER_ATR_RESP(SAPMessage): + def __init__(self, ResultCode = None, ATR = None): + SAPMessage.__init__(self,"TRANSFER_ATR_RESP", SAPMessage.TRANSFER_ATR_RESP) + if ResultCode is not None: + self.addParam(SAPParam_ResultCode(ResultCode)) + if ATR is not None: + self.addParam(SAPParam_ATR(ATR)) + + def _validate(self): + if len(self.params) > 0: + if self.params[0] .getID() == SAPParam.ResultCode: + if self.params[0].getValue() == 0x00: + if len(self.params) == 2: + return True + else: + if len(self.params) == 1: + return True + return False + + def deserialize(self, buf): + self.buf = buf + self.params[:] = [] + + if SAPMessage._basicCheck(self, buf): + + p = SAPParam_ResultCode() + r = p.deserialize(buf[4:]) + + if r != -1: + + self.addParam(p) + if buf[1] == 2: + + p = SAPParam_ATR() + r = p.deserialize(buf[4+r:]) + if r != -1: + self.addParam(p) + + return self._validate() + + return False + +class SAPMessage_POWER_SIM_OFF_REQ(SAPMessage): + def __init__(self): + SAPMessage.__init__(self,"POWER_SIM_OFF_REQ", SAPMessage.POWER_SIM_OFF_REQ) + +class SAPMessage_POWER_SIM_OFF_RESP(SAPMessage): + def __init__(self, ResultCode = None): + SAPMessage.__init__(self,"POWER_SIM_OFF_RESP", SAPMessage.POWER_SIM_OFF_RESP) + if ResultCode is not None: + self.addParam(SAPParam_ResultCode(ResultCode)) + + def _validate(self): + if len(self.params) == 1: + if self.params[0].getID() == SAPParam.ResultCode: + return True + return False + + def deserialize(self, buf): + self.buf = buf + self.params[:] = [] + if SAPMessage._basicCheck(self, buf): + p = SAPParam_ResultCode() + if p.deserialize(buf[4:]) == len(buf[4:]): + self.addParam(p) + return self._validate() + + return False + +class SAPMessage_POWER_SIM_ON_REQ(SAPMessage): + def __init__(self): + SAPMessage.__init__(self,"POWER_SIM_ON_REQ", SAPMessage.POWER_SIM_ON_REQ) + +class SAPMessage_POWER_SIM_ON_RESP(SAPMessage_POWER_SIM_OFF_RESP): + def __init__(self, ResultCode = None): + SAPMessage.__init__(self,"POWER_SIM_ON_RESP", SAPMessage.POWER_SIM_ON_RESP) + if ResultCode is not None: + self.addParam(SAPParam_ResultCode(ResultCode)) + +class SAPMessage_RESET_SIM_REQ(SAPMessage): + def __init__(self): + SAPMessage.__init__(self,"RESET_SIM_REQ", SAPMessage.RESET_SIM_REQ) + +class SAPMessage_RESET_SIM_RESP(SAPMessage_POWER_SIM_OFF_RESP): + def __init__(self, ResultCode = None): + SAPMessage.__init__(self,"RESET_SIM_RESP", SAPMessage.RESET_SIM_RESP) + if ResultCode is not None: + self.addParam(SAPParam_ResultCode(ResultCode)) + +class SAPMessage_STATUS_IND(SAPMessage): + def __init__(self, StatusChange = None): + SAPMessage.__init__(self,"STATUS_IND", SAPMessage.STATUS_IND) + if StatusChange is not None: + self.addParam(SAPParam_StatusChange(StatusChange)) + + def _validate(self): + if len(self.params) == 1: + if self.params[0].getID() == SAPParam.StatusChange: + return True + return False + + def deserialize(self, buf): + self.buf = buf + self.params[:] = [] + if SAPMessage._basicCheck(self, buf): + p = SAPParam_StatusChange() + if p.deserialize(buf[4:]) == len(buf[4:]): + self.addParam(p) + return self._validate() + + return False + +class SAPMessage_TRANSFER_CARD_READER_STATUS_REQ(SAPMessage): + def __init__(self): + SAPMessage.__init__(self,"TRANSFER_CARD_READER_STATUS_REQ", SAPMessage.TRANSFER_CARD_READER_STATUS_REQ) + +class SAPMessage_TRANSFER_CARD_READER_STATUS_RESP(SAPMessage): + def __init__(self, ResultCode = None, Status = None): + SAPMessage.__init__(self,"TRANSFER_CARD_READER_STATUS_RESP", SAPMessage.TRANSFER_CARD_READER_STATUS_RESP) + if ResultCode is not None: + self.addParam(SAPParam_ResultCode(ResultCode)) + if Status is not None: + self.addParam(SAPParam_CardReaderStatus(Status)) + + def _validate(self): + if len(self.params) > 0: + if self.params[0] .getID() == SAPParam.ResultCode: + if self.params[0].getValue() == 0x00: + if len(self.params) == 2: + return True + else: + if len(self.params) == 1: + return True + return False + + def deserialize(self, buf): + self.buf = buf + self.params[:] = [] + + if SAPMessage._basicCheck(self, buf): + p = SAPParam_ResultCode() + r = p.deserialize(buf[4:]) + if r != -1: + self.addParam(p) + if buf[1] == 2: + p = SAPParam_CardReaderStatus() + r = p.deserialize(buf[4+r:]) + if r != -1: + self.addParam(p) + + return self._validate() + + return False + +class SAPMessage_ERROR_RESP(SAPMessage): + def __init__(self): + SAPMessage.__init__(self,"ERROR_RESP", SAPMessage.ERROR_RESP) + + +class SAPMessage_SET_TRANSPORT_PROTOCOL_REQ(SAPMessage): + def __init__(self, protocol = None): + SAPMessage.__init__(self,"SET_TRANSPORT_PROTOCOL_REQ", SAPMessage.SET_TRANSPORT_PROTOCOL_REQ) + if protocol is not None: + self.addParam(SAPParam_TransportProtocol(protocol)) + + def _validate(self): + if len(self.params) == 1: + if self.params[0].getID() == SAPParam.TransportProtocol: + return True + return False + + def deserialize(self, buf): + self.buf = buf + self.params[:] = [] + if SAPMessage._basicCheck(self, buf): + p = SAPParam_TransportProtocol() + if p.deserialize(buf[4:]) == len(buf[4:]): + self.addParam(p) + return self._validate() + + return False + +class SAPMessage_SET_TRANSPORT_PROTOCOL_RESP(SAPMessage_POWER_SIM_OFF_RESP): + def __init__(self, ResultCode = None): + SAPMessage.__init__(self,"SET_TRANSPORT_PROTOCOL_RESP", SAPMessage.SET_TRANSPORT_PROTOCOL_RESP) + if ResultCode is not None: + self.addParam(SAPParam_ResultCode(ResultCode)) + + +class SAPClient: + + CONNECTED = 1 + DISCONNECTED = 0 + + uuid = "0000112D-0000-1000-8000-00805F9B34FB" + bufsize = 1024 + timeout = 20 + state = DISCONNECTED + + def __init__(self, host = None, port = None): + self.sock = None + + if host is None or is_valid_address(host): + self.host = host + else: + raise BluetoothError ("%s is not a valid BT address." % host) + self.host = None + return + + if port is None: + self.__discover() + else: + self.port = port + + self.__connectRFCOMM() + + def __del__(self): + self.__disconnectRFCOMM() + + def __disconnectRFCOMM(self): + if self.sock is not None: + self.sock.close() + self.state = self.DISCONNECTED + + def __discover(self): + service_matches = find_service(self.uuid, self.host) + + if len(service_matches) == 0: + raise BluetoothError ("No SAP service found") + return + + first_match = service_matches[0] + self.port = first_match["port"] + self.host = first_match["host"] + + print "SAP Service found on %s(%s)" % first_match["name"] % self.host + + def __connectRFCOMM(self): + self.sock=BluetoothSocket( RFCOMM ) + self.sock.connect((self.host, self.port)) + self.sock.settimeout(self.timeout) + self.state = self.CONNECTED + + def __sendMsg(self, msg): + if isinstance(msg, SAPMessage): + s = msg.serialize() + print "\tTX: " + msg.getContent() + return self.sock.send(s.tostring()) + + def __rcvMsg(self, msg): + if isinstance(msg, SAPMessage): + print "\tRX Wait: %s(id = 0x%.2x)" % (msg.name, msg.id) + data = self.sock.recv(self.bufsize) + if data: + if msg.deserialize(array('B',data)): + print "\tRX: len(%d) %s" % (len(data), msg.getContent()) + return msg + else: + print "msg: %s" % array('B',data) + raise BluetoothError ("Message deserialization failed.") + else: + raise BluetoothError ("Timeout. No data received.") + + def connect(self): + self.__connectRFCOMM() + + def disconnect(self): + self.__disconnectRFCOMM() + + def isConnected(self): + return self.state + + def proc_connect(self): + try: + self.__sendMsg(SAPMessage_CONNECT_REQ(self.bufsize)) + params = self.__rcvMsg(SAPMessage_CONNECT_RESP()).getParams() + + if params[0].getValue() in (0x00, 0x04): + pass + elif params[0].getValue() == 0x02: + self.bufsize = params[1].getValue() + + self.__sendMsg(SAPMessage_CONNECT_REQ(self.bufsize)) + params = self.__rcvMsg(SAPMessage_CONNECT_RESP()).getParams() + + if params[0].getValue() not in (0x00, 0x04): + return False + else: + return False + + params = self.__rcvMsg(SAPMessage_STATUS_IND()).getParams() + if params[0].getValue() == 0x00: + return False + elif params[0].getValue() == 0x01: + """OK, Card reset""" + return self.proc_transferATR() + elif params[0].getValue() == 0x02: + """T0 not supported""" + if self.proc_transferATR(): + return self.proc_setTransportProtocol(1) + else: + return False + else: + return False + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_disconnectByClient(self, timeout=0): + try: + self.__sendMsg(SAPMessage_DISCONNECT_REQ()) + self.__rcvMsg(SAPMessage_DISCONNECT_RESP()) + time.sleep(timeout) # let srv to close rfcomm + self.__disconnectRFCOMM() + return True + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_disconnectByServer(self, timeout=0): + try: + params = self.__rcvMsg(SAPMessage_DISCONNECT_IND()).getParams() + + """graceful""" + if params[0].getValue() == 0x00: + if not self.proc_transferAPDU(): + return False + + return self.proc_disconnectByClient(timeout) + + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_transferAPDU(self, apdu = "Sample APDU command"): + try: + self.__sendMsg(SAPMessage_TRANSFER_APDU_REQ(apdu)) + params = self.__rcvMsg(SAPMessage_TRANSFER_APDU_RESP()).getParams() + return True + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_transferATR(self): + try: + self.__sendMsg(SAPMessage_TRANSFER_ATR_REQ()) + params = self.__rcvMsg(SAPMessage_TRANSFER_ATR_RESP()).getParams() + return True + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_powerSimOff(self): + try: + self.__sendMsg(SAPMessage_POWER_SIM_OFF_REQ()) + params = self.__rcvMsg(SAPMessage_POWER_SIM_OFF_RESP()).getParams() + return True + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_powerSimOn(self): + try: + self.__sendMsg(SAPMessage_POWER_SIM_ON_REQ()) + params = self.__rcvMsg(SAPMessage_POWER_SIM_ON_RESP()).getParams() + if params[0].getValue() == 0x00: + return self.proc_transferATR() + + return True + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_resetSim(self): + try: + self.__sendMsg(SAPMessage_RESET_SIM_REQ()) + params = self.__rcvMsg(SAPMessage_RESET_SIM_RESP()).getParams() + if params[0].getValue() == 0x00: + return self.proc_transferATR() + + return True + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_reportStatus(self): + try: + params = self.__rcvMsg(SAPMessage_STATUS_IND()).getParams() + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_transferCardReaderStatus(self): + try: + self.__sendMsg(SAPMessage_TRANSFER_CARD_READER_STATUS_REQ()) + params = self.__rcvMsg(SAPMessage_TRANSFER_CARD_READER_STATUS_RESP()).getParams() + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_errorResponse(self): + try: + """ send malformed message, no mandatory maxmsgsize parameter""" + self.__sendMsg(SAPMessage_CONNECT_REQ()) + + params = self.__rcvMsg(SAPMessage_ERROR_RESP()).getParams() + except BluetoothError , e: + print "Error. " +str(e) + return False + + def proc_setTransportProtocol(self, protocol = 0): + try: + self.__sendMsg(SAPMessage_SET_TRANSPORT_PROTOCOL_REQ(protocol)) + params = self.__rcvMsg(SAPMessage_SET_TRANSPORT_PROTOCOL_RESP()).getParams() + + if params[0].getValue() == 0x00: + params = self.__rcvMsg(SAPMessage_STATUS_IND()).getParams() + if params[0].getValue() in (0x01, 0x02): + return self.proc_transferATR() + else: + return True + """return False ???""" + elif params[0].getValue == 0x07: + """not supported""" + return True + """return False ???""" + else: + return False + + except BluetoothError , e: + print "Error. " +str(e) + return False + +if __name__ == "__main__": + pass diff --git a/test/service-did.xml b/test/service-did.xml new file mode 100644 index 0000000..52eb68c --- /dev/null +++ b/test/service-did.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/service-ftp.xml b/test/service-ftp.xml new file mode 100644 index 0000000..1bda885 --- /dev/null +++ b/test/service-ftp.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/service-opp.xml b/test/service-opp.xml new file mode 100644 index 0000000..351b4a4 --- /dev/null +++ b/test/service-opp.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/service-record.dtd b/test/service-record.dtd new file mode 100644 index 0000000..f53be5d --- /dev/null +++ b/test/service-record.dtd @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/service-spp.xml b/test/service-spp.xml new file mode 100644 index 0000000..2b156c3 --- /dev/null +++ b/test/service-spp.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/simple-agent b/test/simple-agent new file mode 100755 index 0000000..a69299a --- /dev/null +++ b/test/simple-agent @@ -0,0 +1,183 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser +import sys +import dbus +import dbus.service +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject +import bluezutils + +BUS_NAME = 'org.bluez' +AGENT_INTERFACE = 'org.bluez.Agent1' +AGENT_PATH = "/test/agent" + +bus = None +device_obj = None +dev_path = None + +def ask(prompt): + try: + return raw_input(prompt) + except: + return input(prompt) + +def set_trusted(path): + props = dbus.Interface(bus.get_object("org.bluez", path), + "org.freedesktop.DBus.Properties") + props.Set("org.bluez.Device1", "Trusted", True) + +def dev_connect(path): + dev = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Device1") + dev.Connect() + +class Rejected(dbus.DBusException): + _dbus_error_name = "org.bluez.Error.Rejected" + +class Agent(dbus.service.Object): + exit_on_release = True + + def set_exit_on_release(self, exit_on_release): + self.exit_on_release = exit_on_release + + @dbus.service.method(AGENT_INTERFACE, + in_signature="", out_signature="") + def Release(self): + print("Release") + if self.exit_on_release: + mainloop.quit() + + @dbus.service.method(AGENT_INTERFACE, + in_signature="os", out_signature="") + def AuthorizeService(self, device, uuid): + print("AuthorizeService (%s, %s)" % (device, uuid)) + authorize = ask("Authorize connection (yes/no): ") + if (authorize == "yes"): + return + raise Rejected("Connection rejected by user") + + @dbus.service.method(AGENT_INTERFACE, + in_signature="o", out_signature="s") + def RequestPinCode(self, device): + print("RequestPinCode (%s)" % (device)) + set_trusted(device) + return ask("Enter PIN Code: ") + + @dbus.service.method(AGENT_INTERFACE, + in_signature="o", out_signature="u") + def RequestPasskey(self, device): + print("RequestPasskey (%s)" % (device)) + set_trusted(device) + passkey = ask("Enter passkey: ") + return dbus.UInt32(passkey) + + @dbus.service.method(AGENT_INTERFACE, + in_signature="ouq", out_signature="") + def DisplayPasskey(self, device, passkey, entered): + print("DisplayPasskey (%s, %06u entered %u)" % + (device, passkey, entered)) + + @dbus.service.method(AGENT_INTERFACE, + in_signature="os", out_signature="") + def DisplayPinCode(self, device, pincode): + print("DisplayPinCode (%s, %s)" % (device, pincode)) + + @dbus.service.method(AGENT_INTERFACE, + in_signature="ou", out_signature="") + def RequestConfirmation(self, device, passkey): + print("RequestConfirmation (%s, %06d)" % (device, passkey)) + confirm = ask("Confirm passkey (yes/no): ") + if (confirm == "yes"): + set_trusted(device) + return + raise Rejected("Passkey doesn't match") + + @dbus.service.method(AGENT_INTERFACE, + in_signature="o", out_signature="") + def RequestAuthorization(self, device): + print("RequestAuthorization (%s)" % (device)) + auth = ask("Authorize? (yes/no): ") + if (auth == "yes"): + return + raise Rejected("Pairing rejected") + + @dbus.service.method(AGENT_INTERFACE, + in_signature="", out_signature="") + def Cancel(self): + print("Cancel") + +def pair_reply(): + print("Device paired") + set_trusted(dev_path) + dev_connect(dev_path) + mainloop.quit() + +def pair_error(error): + err_name = error.get_dbus_name() + if err_name == "org.freedesktop.DBus.Error.NoReply" and device_obj: + print("Timed out. Cancelling pairing") + device_obj.CancelPairing() + else: + print("Creating device failed: %s" % (error)) + + + mainloop.quit() + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + capability = "KeyboardDisplay" + + parser = OptionParser() + parser.add_option("-i", "--adapter", action="store", + type="string", + dest="adapter_pattern", + default=None) + parser.add_option("-c", "--capability", action="store", + type="string", dest="capability") + parser.add_option("-t", "--timeout", action="store", + type="int", dest="timeout", + default=60000) + (options, args) = parser.parse_args() + if options.capability: + capability = options.capability + + path = "/test/agent" + agent = Agent(bus, path) + + mainloop = GObject.MainLoop() + + obj = bus.get_object(BUS_NAME, "/org/bluez"); + manager = dbus.Interface(obj, "org.bluez.AgentManager1") + manager.RegisterAgent(path, capability) + + print("Agent registered") + + # Fix-up old style invocation (BlueZ 4) + if len(args) > 0 and args[0].startswith("hci"): + options.adapter_pattern = args[0] + del args[:1] + + if len(args) > 0: + device = bluezutils.find_device(args[0], + options.adapter_pattern) + dev_path = device.object_path + agent.set_exit_on_release(False) + device.Pair(reply_handler=pair_reply, error_handler=pair_error, + timeout=60000) + device_obj = device + else: + manager.RequestDefaultAgent(path) + + mainloop.run() + + #adapter.UnregisterAgent(path) + #print("Agent unregistered") diff --git a/test/simple-endpoint b/test/simple-endpoint new file mode 100755 index 0000000..78fb5fd --- /dev/null +++ b/test/simple-endpoint @@ -0,0 +1,138 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +import sys +import dbus +import dbus.service +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject +import bluezutils + +A2DP_SOURCE_UUID = "0000110A-0000-1000-8000-00805F9B34FB" +A2DP_SINK_UUID = "0000110B-0000-1000-8000-00805F9B34FB" +HFP_AG_UUID = "0000111F-0000-1000-8000-00805F9B34FB" +HFP_HF_UUID = "0000111E-0000-1000-8000-00805F9B34FB" +HSP_AG_UUID = "00001112-0000-1000-8000-00805F9B34FB" + +SBC_CODEC = dbus.Byte(0x00) +#Channel Modes: Mono DualChannel Stereo JointStereo +#Frequencies: 16Khz 32Khz 44.1Khz 48Khz +#Subbands: 4 8 +#Blocks: 4 8 12 16 +#Bitpool Range: 2-64 +SBC_CAPABILITIES = dbus.Array([dbus.Byte(0xff), dbus.Byte(0xff), dbus.Byte(2), dbus.Byte(64)]) +# JointStereo 44.1Khz Subbands: Blocks: 16 Bitpool Range: 2-32 +SBC_CONFIGURATION = dbus.Array([dbus.Byte(0x21), dbus.Byte(0x15), dbus.Byte(2), dbus.Byte(32)]) + +MP3_CODEC = dbus.Byte(0x01) +#Channel Modes: Mono DualChannel Stereo JointStereo +#Frequencies: 32Khz 44.1Khz 48Khz +#CRC: YES +#Layer: 3 +#Bit Rate: All except Free format +#VBR: Yes +#Payload Format: RFC-2250 +MP3_CAPABILITIES = dbus.Array([dbus.Byte(0x3f), dbus.Byte(0x07), dbus.Byte(0xff), dbus.Byte(0xfe)]) +# JointStereo 44.1Khz Layer: 3 Bit Rate: VBR Format: RFC-2250 +MP3_CONFIGURATION = dbus.Array([dbus.Byte(0x21), dbus.Byte(0x02), dbus.Byte(0x00), dbus.Byte(0x80)]) + +PCM_CODEC = dbus.Byte(0x00) +PCM_CONFIGURATION = dbus.Array([], signature="ay") + +CVSD_CODEC = dbus.Byte(0x01) + +class Rejected(dbus.DBusException): + _dbus_error_name = "org.bluez.Error.Rejected" + +class Endpoint(dbus.service.Object): + exit_on_release = True + configuration = SBC_CONFIGURATION + + def set_exit_on_release(self, exit_on_release): + self.exit_on_release = exit_on_release + + def default_configuration(self, configuration): + self.configuration = configuration + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="", out_signature="") + def Release(self): + print("Release") + if self.exit_on_release: + mainloop.quit() + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="o", out_signature="") + def ClearConfiguration(self, transport): + print("ClearConfiguration (%s)" % (transport)) + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="oay", out_signature="") + def SetConfiguration(self, transport, config): + print("SetConfiguration (%s, %s)" % (transport, config)) + return + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="ay", out_signature="ay") + def SelectConfiguration(self, caps): + print("SelectConfiguration (%s)" % (caps)) + return self.configuration + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + if len(sys.argv) > 1: + path = bluezutils.find_adapter(sys.argv[1]).object_path + else: + path = bluezutils.find_adapter().object_path + + media = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Media1") + + path = "/test/endpoint" + endpoint = Endpoint(bus, path) + mainloop = GObject.MainLoop() + + properties = dbus.Dictionary({ "UUID" : A2DP_SOURCE_UUID, + "Codec" : SBC_CODEC, + "DelayReporting" : True, + "Capabilities" : SBC_CAPABILITIES }) + + if len(sys.argv) > 2: + if sys.argv[2] == "sbcsink": + properties = dbus.Dictionary({ "UUID" : A2DP_SINK_UUID, + "Codec" : SBC_CODEC, + "DelayReporting" : True, + "Capabilities" : SBC_CAPABILITIES }) + if sys.argv[2] == "mp3source": + properties = dbus.Dictionary({ "UUID" : A2DP_SOURCE_UUID, + "Codec" : MP3_CODEC, + "Capabilities" : MP3_CAPABILITIES }) + endpoint.default_configuration(MP3_CONFIGURATION) + if sys.argv[2] == "mp3sink": + properties = dbus.Dictionary({ "UUID" : A2DP_SINK_UUID, + "Codec" : MP3_CODEC, + "Capabilities" : MP3_CAPABILITIES }) + endpoint.default_configuration(MP3_CONFIGURATION) + if sys.argv[2] == "hfpag" or sys.argv[2] == "hspag": + properties = dbus.Dictionary({ "UUID" : HFP_AG_UUID, + "Codec" : PCM_CODEC, + "Capabilities" : PCM_CONFIGURATION }) + endpoint.default_configuration(dbus.Array([])) + if sys.argv[2] == "hfphf": + properties = dbus.Dictionary({ "UUID" : HFP_HF_UUID, + "Codec" : CVSD_CODEC, + "Capabilities" : PCM_CONFIGURATION }) + endpoint.default_configuration(dbus.Array([])) + + print(properties) + + media.RegisterEndpoint(path, properties) + + mainloop.run() diff --git a/test/simple-player b/test/simple-player new file mode 100755 index 0000000..02754c2 --- /dev/null +++ b/test/simple-player @@ -0,0 +1,156 @@ +#!/usr/bin/python + +from __future__ import print_function + +import os +import sys +import dbus +import dbus.service +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject +import bluezutils + +class Player(dbus.service.Object): + properties = None + metadata = None + + def set_object(self, obj = None): + if obj != None: + bus = dbus.SystemBus() + mp = dbus.Interface(bus.get_object("org.bluez", obj), + "org.bluez.MediaPlayer1") + prop = dbus.Interface(bus.get_object("org.bluez", obj), + "org.freedesktop.DBus.Properties") + + self.properties = prop.GetAll("org.bluez.MediaPlayer1") + + bus.add_signal_receiver(self.properties_changed, + path = obj, + dbus_interface = "org.freedesktop.DBus.Properties", + signal_name = "PropertiesChanged") + else: + track = dbus.Dictionary({ + "xesam:title" : "Title", + "xesam:artist" : ["Artist"], + "xesam:album" : "Album", + "xesam:genre" : ["Genre"], + "xesam:trackNumber" : dbus.Int32(1), + "mpris:length" : dbus.Int64(10000) }, + signature="sv") + + self.properties = dbus.Dictionary({ + "PlaybackStatus" : "playing", + "Identity" : "SimplePlayer", + "LoopStatus" : "None", + "Rate" : dbus.Double(1.0), + "Shuffle" : dbus.Boolean(False), + "Metadata" : track, + "Volume" : dbus.Double(1.0), + "Position" : dbus.Int64(0), + "MinimumRate" : dbus.Double(1.0), + "MaximumRate" : dbus.Double(1.0), + "CanGoNext" : dbus.Boolean(False), + "CanGoPrevious" : dbus.Boolean(False), + "CanPlay" : dbus.Boolean(False), + "CanSeek" : dbus.Boolean(False), + "CanControl" : dbus.Boolean(False), + }, + signature="sv") + + handler = InputHandler(self) + GObject.io_add_watch(sys.stdin, GObject.IO_IN, + handler.handle) + + @dbus.service.method("org.freedesktop.DBus.Properties", + in_signature="ssv", out_signature="") + def Set(self, interface, key, value): + print("Set (%s, %s)" % (key, value), file=sys.stderr) + return + + @dbus.service.signal("org.freedesktop.DBus.Properties", + signature="sa{sv}as") + def PropertiesChanged(self, interface, properties, + invalidated = dbus.Array()): + """PropertiesChanged(interface, properties, invalidated) + + Send a PropertiesChanged signal. 'properties' is a dictionary + containing string parameters as specified in doc/media-api.txt. + """ + pass + + def help(self, func): + help(self.__class__.__dict__[func]) + + def properties_changed(self, interface, properties, invalidated): + print("properties_changed(%s, %s)" % (properties, invalidated)) + + self.PropertiesChanged(interface, properties, invalidated) + +class InputHandler: + commands = { 'PropertiesChanged': '(interface, properties)', + 'help': '(cmd)' } + def __init__(self, player): + self.player = player + print('\n\nAvailable commands:') + for cmd in self.commands: + print('\t', cmd, self.commands[cmd], sep='') + + print("\nUse python syntax to pass arguments to available methods.\n" \ + "E.g.: PropertiesChanged({'Metadata' : {'Title': 'My title', \ + 'Album': 'my album' }})") + self.prompt() + + def prompt(self): + print('\n>>> ', end='') + sys.stdout.flush() + + def handle(self, fd, condition): + s = os.read(fd.fileno(), 1024).strip() + try: + cmd = s[:s.find('(')] + if not cmd in self.commands: + print("Unknown command ", cmd) + except ValueError: + print("Malformed command") + return True + + try: + exec "self.player.%s" % s + except Exception as e: + print(e) + pass + self.prompt() + return True + + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + if len(sys.argv) > 1: + path = bluezutils.find_adapter(sys.argv[1]).object_path + else: + path = bluezutils.find_adapter().object_path + + media = dbus.Interface(bus.get_object("org.bluez", path), + "org.bluez.Media1") + + path = "/test/player" + player = Player(bus, path) + mainloop = GObject.MainLoop() + + if len(sys.argv) > 2: + player.set_object(sys.argv[2]) + else: + player.set_object() + + print('Register media player with:\n\tProperties: %s' \ + % (player.properties)) + + media.RegisterPlayer(dbus.ObjectPath(path), player.properties) + + mainloop.run() diff --git a/test/test-adapter b/test/test-adapter new file mode 100755 index 0000000..959a437 --- /dev/null +++ b/test/test-adapter @@ -0,0 +1,145 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser, make_option +import sys +import time +import dbus +import bluezutils + +bus = dbus.SystemBus() + +option_list = [ + make_option("-i", "--device", action="store", + type="string", dest="dev_id"), + ] +parser = OptionParser(option_list=option_list) + +(options, args) = parser.parse_args() + +adapter_path = bluezutils.find_adapter(options.dev_id).object_path +adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path), + "org.freedesktop.DBus.Properties") + +if (len(args) < 1): + print("Usage: %s " % (sys.argv[0])) + print("") + print(" address") + print(" list") + print(" name") + print(" alias [alias]") + print(" powered [on/off]") + print(" pairable [on/off]") + print(" pairabletimeout [timeout]") + print(" discoverable [on/off]") + print(" discoverabletimeout [timeout]") + print(" discovering") + sys.exit(1) + +if (args[0] == "address"): + addr = adapter.Get("org.bluez.Adapter1", "Address") + print(addr) + sys.exit(0) + +if (args[0] == "name"): + name = adapter.Get("org.bluez.Adapter1", "Name") + print(name) + sys.exit(0) + +if (args[0] == "alias"): + if (len(args) < 2): + alias = adapter.Get("org.bluez.Adapter1", "Alias") + print(alias) + else: + adapter.Set("org.bluez.Adapter1", "Alias", args[1]) + sys.exit(0) + +if (args[0] == "list"): + if (len(args) < 2): + om = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.freedesktop.DBus.ObjectManager") + objects = om.GetManagedObjects() + for path, interfaces in objects.iteritems(): + if "org.bluez.Adapter1" not in interfaces: + continue + + print(" [ %s ]" % (path)) + + props = interfaces["org.bluez.Adapter1"] + + for (key, value) in props.items(): + if (key == "Class"): + print(" %s = 0x%06x" % (key, value)) + else: + print(" %s = %s" % (key, value)) + print() + sys.exit(0) + +if (args[0] == "powered"): + if (len(args) < 2): + powered = adapter.Get("org.bluez.Adapter1", "Powered") + print(powered) + else: + if (args[1] == "on"): + value = dbus.Boolean(1) + elif (args[1] == "off"): + value = dbus.Boolean(0) + else: + value = dbus.Boolean(args[1]) + adapter.Set("org.bluez.Adapter1", "Powered", value) + sys.exit(0) + +if (args[0] == "pairable"): + if (len(args) < 2): + pairable = adapter.Get("org.bluez.Adapter1", "Pairable") + print(pairable) + else: + if (args[1] == "on"): + value = dbus.Boolean(1) + elif (args[1] == "off"): + value = dbus.Boolean(0) + else: + value = dbus.Boolean(args[1]) + adapter.Set("org.bluez.Adapter1", "Pairable", value) + sys.exit(0) + +if (args[0] == "pairabletimeout"): + if (len(args) < 2): + pt = adapter.Get("org.bluez.Adapter1", "PairableTimeout") + print(pt) + else: + timeout = dbus.UInt32(args[1]) + adapter.Set("org.bluez.Adapter1", "PairableTimeout", timeout) + sys.exit(0) + +if (args[0] == "discoverable"): + if (len(args) < 2): + discoverable = adapter.Get("org.bluez.Adapter1", "Discoverable") + print(discoverable) + else: + if (args[1] == "on"): + value = dbus.Boolean(1) + elif (args[1] == "off"): + value = dbus.Boolean(0) + else: + value = dbus.Boolean(args[1]) + adapter.Set("org.bluez.Adapter1", "Discoverable", value) + sys.exit(0) + +if (args[0] == "discoverabletimeout"): + if (len(args) < 2): + dt = adapter.Get("org.bluez.Adapter1", "DiscoverableTimeout") + print(dt) + else: + to = dbus.UInt32(args[1]) + adapter.Set("org.bluez.Adapter1", "DiscoverableTimeout", to) + sys.exit(0) + +if (args[0] == "discovering"): + discovering = adapter.Get("org.bluez.Adapter1", "Discovering") + print(discovering) + sys.exit(0) + +print("Unknown command") +sys.exit(1) diff --git a/test/test-device b/test/test-device new file mode 100755 index 0000000..b490d53 --- /dev/null +++ b/test/test-device @@ -0,0 +1,202 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser, make_option +import re +import sys +import dbus +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject +import bluezutils + +dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) +bus = dbus.SystemBus() +mainloop = GObject.MainLoop() + +option_list = [ + make_option("-i", "--device", action="store", + type="string", dest="dev_id"), + ] +parser = OptionParser(option_list=option_list) + +(options, args) = parser.parse_args() + +if (len(args) < 1): + print("Usage: %s " % (sys.argv[0])) + print("") + print(" list") + print(" create
") + print(" remove ") + print(" connect
[profile]") + print(" disconnect
[profile]") + print(" class
") + print(" name
") + print(" alias
[alias]") + print(" trusted
[yes/no]") + print(" blocked
[yes/no]") + sys.exit(1) + +if (args[0] == "list"): + adapter = bluezutils.find_adapter(options.dev_id) + adapter_path = adapter.object_path + + om = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.freedesktop.DBus.ObjectManager") + objects = om.GetManagedObjects() + + for path, interfaces in objects.iteritems(): + if "org.bluez.Device1" not in interfaces: + continue + properties = interfaces["org.bluez.Device1"] + if properties["Adapter"] != adapter_path: + continue; + print("%s %s" % (properties["Address"], properties["Alias"])) + + sys.exit(0) + +def create_device_reply(device): + print("New device (%s)" % device) + mainloop.quit() + sys.exit(0) + +def create_device_error(error): + print("Creating device failed: %s" % error) + mainloop.quit() + sys.exit(1) + +if (args[0] == "create"): + if (len(args) < 2): + print("Need address parameter") + else: + adapter = bluezutils.find_adapter(options.dev_id) + adapter.CreateDevice(args[1], + reply_handler=create_device_reply, + error_handler=create_device_error) + mainloop.run() + +if (args[0] == "remove"): + if (len(args) < 2): + print("Need address or object path parameter") + else: + managed_objects = bluezutils.get_managed_objects() + adapter = bluezutils.find_adapter_in_objects(managed_objects, + options.dev_id) + try: + dev = bluezutils.find_device_in_objects(managed_objects, + args[1], + options.dev_id) + path = dev.object_path + except: + path = args[1] + adapter.RemoveDevice(path) + sys.exit(0) + +if (args[0] == "connect"): + if (len(args) < 2): + print("Need address parameter") + else: + device = bluezutils.find_device(args[1], options.dev_id) + if (len(args) > 2): + device.ConnectProfile(args[2]) + else: + device.Connect() + sys.exit(0) + +if (args[0] == "disconnect"): + if (len(args) < 2): + print("Need address parameter") + else: + device = bluezutils.find_device(args[1], options.dev_id) + if (len(args) > 2): + device.DisconnectProfile(args[2]) + else: + device.Disconnect() + sys.exit(0) + +if (args[0] == "class"): + if (len(args) < 2): + print("Need address parameter") + else: + device = bluezutils.find_device(args[1], options.dev_id) + path = device.object_path + props = dbus.Interface(bus.get_object("org.bluez", path), + "org.freedesktop.DBus.Properties") + cls = props.Get("org.bluez.Device1", "Class") + print("0x%06x" % cls) + sys.exit(0) + +if (args[0] == "name"): + if (len(args) < 2): + print("Need address parameter") + else: + device = bluezutils.find_device(args[1], options.dev_id) + path = device.object_path + props = dbus.Interface(bus.get_object("org.bluez", path), + "org.freedesktop.DBus.Properties") + name = props.Get("org.bluez.Device1", "Name") + print(name) + sys.exit(0) + +if (args[0] == "alias"): + if (len(args) < 2): + print("Need address parameter") + else: + device = bluezutils.find_device(args[1], options.dev_id) + path = device.object_path + props = dbus.Interface(bus.get_object("org.bluez", path), + "org.freedesktop.DBus.Properties") + if (len(args) < 3): + alias = props.Get("org.bluez.Device1", "Alias") + print(alias) + else: + props.Set("org.bluez.Device1", "Alias", args[2]) + sys.exit(0) + +if (args[0] == "trusted"): + if (len(args) < 2): + print("Need address parameter") + else: + device = bluezutils.find_device(args[1], options.dev_id) + path = device.object_path + props = dbus.Interface(bus.get_object("org.bluez", path), + "org.freedesktop.DBus.Properties") + if (len(args) < 3): + trusted = props.Get("org.bluez.Device1", "Trusted") + print(trusted) + else: + if (args[2] == "yes"): + value = dbus.Boolean(1) + elif (args[2] == "no"): + value = dbus.Boolean(0) + else: + value = dbus.Boolean(args[2]) + props.Set("org.bluez.Device1", "Trusted", value) + sys.exit(0) + +if (args[0] == "blocked"): + if (len(args) < 2): + print("Need address parameter") + else: + device = bluezutils.find_device(args[1], options.dev_id) + path = device.object_path + props = dbus.Interface(bus.get_object("org.bluez", path), + "org.freedesktop.DBus.Properties") + if (len(args) < 3): + blocked = props.Get("org.bluez.Device1", "Blocked") + print(blocked) + else: + if (args[2] == "yes"): + value = dbus.Boolean(1) + elif (args[2] == "no"): + value = dbus.Boolean(0) + else: + value = dbus.Boolean(args[2]) + props.Set("org.bluez.Device1", "Blocked", value) + sys.exit(0) + +print("Unknown command") +sys.exit(1) diff --git a/test/test-discovery b/test/test-discovery new file mode 100755 index 0000000..cea7768 --- /dev/null +++ b/test/test-discovery @@ -0,0 +1,182 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser, make_option +import dbus +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject +import bluezutils + +compact = False +devices = {} + +def print_compact(address, properties): + name = "" + address = "" + + for key, value in properties.iteritems(): + if type(value) is dbus.String: + value = unicode(value).encode('ascii', 'replace') + if (key == "Name"): + name = value + elif (key == "Address"): + address = value + + if "Logged" in properties: + flag = "*" + else: + flag = " " + + print("%s%s %s" % (flag, address, name)) + + properties["Logged"] = True + +def print_normal(address, properties): + print("[ " + address + " ]") + + for key in properties.keys(): + value = properties[key] + if type(value) is dbus.String: + value = unicode(value).encode('ascii', 'replace') + if (key == "Class"): + print(" %s = 0x%06x" % (key, value)) + else: + print(" %s = %s" % (key, value)) + + print() + + properties["Logged"] = True + +def skip_dev(old_dev, new_dev): + if not "Logged" in old_dev: + return False + if "Name" in old_dev: + return True + if not "Name" in new_dev: + return True + return False + +def interfaces_added(path, interfaces): + properties = interfaces["org.bluez.Device1"] + if not properties: + return + + if path in devices: + dev = devices[path] + + if compact and skip_dev(dev, properties): + return + devices[path] = dict(devices[path].items() + properties.items()) + else: + devices[path] = properties + + if "Address" in devices[path]: + address = properties["Address"] + else: + address = "" + + if compact: + print_compact(address, devices[path]) + else: + print_normal(address, devices[path]) + +def properties_changed(interface, changed, invalidated, path): + if interface != "org.bluez.Device1": + return + + if path in devices: + dev = devices[path] + + if compact and skip_dev(dev, changed): + return + devices[path] = dict(devices[path].items() + changed.items()) + else: + devices[path] = changed + + if "Address" in devices[path]: + address = devices[path]["Address"] + else: + address = "" + + if compact: + print_compact(address, devices[path]) + else: + print_normal(address, devices[path]) + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + option_list = [ + make_option("-i", "--device", action="store", + type="string", dest="dev_id"), + make_option("-u", "--uuids", action="store", + type="string", dest="uuids", + help="Filtered service UUIDs [uuid1,uuid2,...]"), + make_option("-r", "--rssi", action="store", + type="int", dest="rssi", + help="RSSI threshold value"), + make_option("-p", "--pathloss", action="store", + type="int", dest="pathloss", + help="Pathloss threshold value"), + make_option("-t", "--transport", action="store", + type="string", dest="transport", + help="Type of scan to run (le/bredr/auto)"), + make_option("-c", "--compact", + action="store_true", dest="compact"), + ] + parser = OptionParser(option_list=option_list) + + (options, args) = parser.parse_args() + + adapter = bluezutils.find_adapter(options.dev_id) + + if options.compact: + compact = True; + + bus.add_signal_receiver(interfaces_added, + dbus_interface = "org.freedesktop.DBus.ObjectManager", + signal_name = "InterfacesAdded") + + bus.add_signal_receiver(properties_changed, + dbus_interface = "org.freedesktop.DBus.Properties", + signal_name = "PropertiesChanged", + arg0 = "org.bluez.Device1", + path_keyword = "path") + + om = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.freedesktop.DBus.ObjectManager") + objects = om.GetManagedObjects() + for path, interfaces in objects.iteritems(): + if "org.bluez.Device1" in interfaces: + devices[path] = interfaces["org.bluez.Device1"] + + scan_filter = dict() + + if options.uuids: + uuids = [] + uuid_list = options.uuids.split(',') + for uuid in uuid_list: + uuids.append(uuid) + + scan_filter.update({ "UUIDs": uuids }) + + if options.rssi: + scan_filter.update({ "RSSI": dbus.Int16(options.rssi) }) + + if options.pathloss: + scan_filter.update({ "Pathloss": dbus.UInt16(options.pathloss) }) + + if options.transport: + scan_filter.update({ "Transport": options.transport }) + + adapter.SetDiscoveryFilter(scan_filter) + adapter.StartDiscovery() + + mainloop = GObject.MainLoop() + mainloop.run() diff --git a/test/test-gatt-profile b/test/test-gatt-profile new file mode 100755 index 0000000..995a659 --- /dev/null +++ b/test/test-gatt-profile @@ -0,0 +1,130 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser, make_option +import os +import sys +import uuid +import dbus +import dbus.service +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject +import bluezutils + +BLUEZ_SERVICE_NAME = 'org.bluez' +GATT_MANAGER_IFACE = 'org.bluez.GattManager1' +DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager' +DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties' + +GATT_PROFILE_IFACE = 'org.bluez.GattProfile1' + + +class InvalidArgsException(dbus.exceptions.DBusException): + _dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs' + + +class Application(dbus.service.Object): + def __init__(self, bus): + self.path = '/' + self.profiles = [] + dbus.service.Object.__init__(self, bus, self.path) + + def get_path(self): + return dbus.ObjectPath(self.path) + + def add_profile(self, profile): + self.profiles.append(profile) + + @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}') + def GetManagedObjects(self): + response = {} + print('GetManagedObjects') + + for profile in self.profiles: + response[profile.get_path()] = profile.get_properties() + + return response + + +class Profile(dbus.service.Object): + PATH_BASE = '/org/bluez/example/profile' + + def __init__(self, bus, uuids): + self.path = self.PATH_BASE + self.bus = bus + self.uuids = uuids + dbus.service.Object.__init__(self, bus, self.path) + + def get_properties(self): + return { + GATT_PROFILE_IFACE: { + 'UUIDs': self.uuids, + } + } + + def get_path(self): + return dbus.ObjectPath(self.path) + + @dbus.service.method(GATT_PROFILE_IFACE, + in_signature="", + out_signature="") + def Release(self): + print("Release") + mainloop.quit() + + @dbus.service.method(DBUS_PROP_IFACE, + in_signature='s', + out_signature='a{sv}') + def GetAll(self, interface): + if interface != GATT_PROFILE_IFACE: + raise InvalidArgsException() + + return self.get_properties[GATT_PROFILE_IFACE] + + +def register_app_cb(): + print('GATT application registered') + + +def register_app_error_cb(error): + print('Failed to register application: ' + str(error)) + mainloop.quit() + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + path = bluezutils.find_adapter().object_path + + manager = dbus.Interface(bus.get_object("org.bluez", path), + GATT_MANAGER_IFACE) + + option_list = [make_option("-u", "--uuid", action="store", + type="string", dest="uuid", + default=None), + ] + + opts = dbus.Dictionary({}, signature='sv') + + parser = OptionParser(option_list=option_list) + + (options, args) = parser.parse_args() + + mainloop = GObject.MainLoop() + + if not options.uuid: + options.uuid = str(uuid.uuid4()) + + app = Application(bus) + profile = Profile(bus, [options.uuid]) + app.add_profile(profile) + manager.RegisterApplication(app.get_path(), {}, + reply_handler=register_app_cb, + error_handler=register_app_error_cb) + + mainloop.run() diff --git a/test/test-health b/test/test-health new file mode 100755 index 0000000..24afa79 --- /dev/null +++ b/test/test-health @@ -0,0 +1,242 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals +# -*- coding: utf-8 -*- + +import sys +import dbus +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject + +BUS_NAME = 'org.bluez' +PATH = '/org/bluez' +ADAPTER_INTERFACE = 'org.bluez.Adapter1' +HEALTH_MANAGER_INTERFACE = 'org.bluez.HealthManager1' +HEALTH_DEVICE_INTERFACE = 'org.bluez.HealthDevice1' + +DBusGMainLoop(set_as_default=True) +loop = GObject.MainLoop() + +bus = dbus.SystemBus() + +def sig_received(*args, **kwargs): + if "member" not in kwargs: + return + if "path" not in kwargs: + return; + sig_name = kwargs["member"] + path = kwargs["path"] + print(sig_name) + print(path) + if sig_name == "PropertyChanged": + k, v = args + print(k) + print(v) + else: + ob = args[0] + print(ob) + + +def enter_mainloop(): + bus.add_signal_receiver(sig_received, bus_name=BUS_NAME, + dbus_interface=HEALTH_DEVICE_INTERFACE, + path_keyword="path", + member_keyword="member", + interface_keyword="interface") + + try: + print("Entering main lopp, push Ctrl+C for finish") + + mainloop = GObject.MainLoop() + mainloop.run() + except KeyboardInterrupt: + pass + finally: + print("Exiting, bye") + +hdp_manager = dbus.Interface(bus.get_object(BUS_NAME, PATH), + HEALTH_MANAGER_INTERFACE) + +role = None +while role == None: + print("Select 1. source or 2. sink: ",) + try: + sel = int(sys.stdin.readline()) + if sel == 1: + role = "source" + elif sel == 2: + role = "sink" + else: + raise ValueError + except (TypeError, ValueError): + print("Wrong selection, try again: ",) + except KeyboardInterrupt: + sys.exit() + +dtype = None +while dtype == None: + print("Select a data type: ",) + try: + sel = int(sys.stdin.readline()) + if (sel < 0) or (sel > 65535): + raise ValueError + dtype = sel; + except (TypeError, ValueError): + print("Wrong selection, try again: ",) + except KeyboardInterrupt: + sys.exit() + +pref = None +if role == "source": + while pref == None: + try: + print("Select a preferred data channel type 1.",) + print("reliable 2. streaming: ",) + sel = int(sys.stdin.readline()) + if sel == 1: + pref = "reliable" + elif sel == 2: + pref = "streaming" + else: + raise ValueError + + except (TypeError, ValueError): + print("Wrong selection, try again") + except KeyboardInterrupt: + sys.exit() + + app_path = hdp_manager.CreateApplication({ + "DataType": dbus.types.UInt16(dtype), + "Role": role, + "Description": "Test Source", + "ChannelType": pref}) +else: + app_path = hdp_manager.CreateApplication({ + "DataType": dbus.types.UInt16(dtype), + "Description": "Test sink", + "Role": role}) + +print("New application created:", app_path) + +con = None +while con == None: + try: + print("Connect to a remote device (y/n)? ",) + sel = sys.stdin.readline() + if sel in ("y\n", "yes\n", "Y\n", "YES\n"): + con = True + elif sel in ("n\n", "no\n", "N\n", "NO\n"): + con = False + else: + print("Wrong selection, try again.") + except KeyboardInterrupt: + sys.exit() + +if not con: + enter_mainloop() + sys.exit() + +manager = dbus.Interface(bus.get_object(BUS_NAME, "/"), + "org.freedesktop.DBus.ObjectManager") + +objects = manager.GetManagedObjects() +adapters = [] + +for path, ifaces in objects.iteritems(): + if ifaces.has_key(ADAPTER_INTERFACE): + adapters.append(path) + +i = 1 +for ad in adapters: + print("%d. %s" % (i, ad)) + i = i + 1 + +print("Select an adapter: ",) +select = None +while select == None: + try: + pos = int(sys.stdin.readline()) - 1 + if pos < 0: + raise TypeError + select = adapters[pos] + except (TypeError, IndexError, ValueError): + print("Wrong selection, try again: ",) + except KeyboardInterrupt: + sys.exit() + +adapter = dbus.Interface(bus.get_object(BUS_NAME, select), ADAPTER_INTERFACE) + +devices = [] +for path, interfaces in objects.iteritems(): + if "org.bluez.Device1" not in interfaces: + continue + properties = interfaces["org.bluez.Device1"] + if properties["Adapter"] != select: + continue; + + if HEALTH_DEVICE_INTERFACE not in interfaces: + continue + devices.append(path) + +if len(devices) == 0: + print("No devices available") + sys.exit() + +i = 1 +for dev in devices: + print("%d. %s" % (i, dev)) + i = i + 1 + +print("Select a device: ",) +select = None +while select == None: + try: + pos = int(sys.stdin.readline()) - 1 + if pos < 0: + raise TypeError + select = devices[pos] + except (TypeError, IndexError, ValueError): + print("Wrong selection, try again: ",) + except KeyboardInterrupt: + sys.exit() + +device = dbus.Interface(bus.get_object(BUS_NAME, select), + HEALTH_DEVICE_INTERFACE) + +echo = None +while echo == None: + try: + print("Perform an echo (y/n)? ",) + sel = sys.stdin.readline() + if sel in ("y\n", "yes\n", "Y\n", "YES\n"): + echo = True + elif sel in ("n\n", "no\n", "N\n", "NO\n"): + echo = False + else: + print("Wrong selection, try again.") + except KeyboardInterrupt: + sys.exit() + +if echo: + if device.Echo(): + print("Echo was ok") + else: + print("Echo war wrong, exiting") + sys.exit() + +print("Connecting to device %s" % (select)) + +if role == "source": + chan = device.CreateChannel(app_path, "reliable") +else: + chan = device.CreateChannel(app_path, "any") + +print(chan) + +enter_mainloop() + +hdp_manager.DestroyApplication(app_path) diff --git a/test/test-health-sink b/test/test-health-sink new file mode 100755 index 0000000..37e630a --- /dev/null +++ b/test/test-health-sink @@ -0,0 +1,113 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals +# -*- coding: utf-8 -*- + +import sys +import dbus +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject + +BUS_NAME = 'org.bluez' +PATH = '/org/bluez' +ADAPTER_INTERFACE = 'org.bluez.Adapter1' +HEALTH_MANAGER_INTERFACE = 'org.bluez.HealthManager1' +HEALTH_DEVICE_INTERFACE = 'org.bluez.HealthDevice1' + +DBusGMainLoop(set_as_default=True) +loop = GObject.MainLoop() + +bus = dbus.SystemBus() + +type = 4103 +if len(sys.argv) > 1: + type = int(sys.argv[1]) + +hdp_manager = dbus.Interface(bus.get_object(BUS_NAME, PATH), + HEALTH_MANAGER_INTERFACE) +app_path = hdp_manager.CreateApplication({"DataType": dbus.types.UInt16(type), + "Role": "sink"}) + +print(app_path) + +manager = dbus.Interface(bus.get_object(BUS_NAME, "/"), + "org.freedesktop.DBus.ObjectManager") + +objects = manager.GetManagedObjects() +adapters = [] + +for path, ifaces in objects.iteritems(): + if ifaces.has_key(ADAPTER_INTERFACE): + adapters.append(path) + +i = 1 +for ad in adapters: + print("%d. %s" % (i, ad)) + i = i + 1 + +print("Select an adapter: ",) +select = None +while select == None: + try: + pos = int(sys.stdin.readline()) - 1 + if pos < 0: + raise TypeError + select = adapters[pos] + except (TypeError, IndexError, ValueError): + print("Wrong selection, try again: ",) + except KeyboardInterrupt: + sys.exit() + +adapter = dbus.Interface(bus.get_object(BUS_NAME, select), + ADAPTER_INTERFACE) + +devices = [] +for path, interfaces in objects.iteritems(): + if "org.bluez.Device1" not in interfaces: + continue + properties = interfaces["org.bluez.Device1"] + if properties["Adapter"] != select: + continue; + + if HEALTH_DEVICE_INTERFACE not in interfaces: + continue + devices.append(path) + +if len(devices) == 0: + print("No devices available") + sys.exit() + +i = 1 +for dev in devices: + print("%d. %s" % (i, dev)) + i = i + 1 + +print("Select a device: ",) +select = None +while select == None: + try: + pos = int(sys.stdin.readline()) - 1 + if pos < 0: + raise TypeError + select = devices[pos] + except (TypeError, IndexError, ValueError): + print("Wrong selection, try again: ",) + except KeyboardInterrupt: + sys.exit() + +print("Connecting to %s" % (select)) +device = dbus.Interface(bus.get_object(BUS_NAME, select), + HEALTH_DEVICE_INTERFACE) + +chan = device.CreateChannel(app_path, "Any") + +print(chan) + +print("Push Enter for finishing") +sys.stdin.readline() + +hdp_manager.DestroyApplication(app_path) diff --git a/test/test-hfp b/test/test-hfp new file mode 100755 index 0000000..a806043 --- /dev/null +++ b/test/test-hfp @@ -0,0 +1,248 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser, make_option +import os +from socket import SOCK_SEQPACKET, socket +import sys +import dbus +import dbus.service +import dbus.mainloop.glib +import glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject + +mainloop = None +audio_supported = True + +try: + from socket import AF_BLUETOOTH, BTPROTO_SCO +except: + print("WARNING: python compiled without Bluetooth support" + " - audio will not be available") + audio_supported = False + +BUF_SIZE = 1024 + +BDADDR_ANY = '00:00:00:00:00:00' + +HF_NREC = 0x0001 +HF_3WAY = 0x0002 +HF_CLI = 0x0004 +HF_VOICE_RECOGNITION = 0x0008 +HF_REMOTE_VOL = 0x0010 +HF_ENHANCED_STATUS = 0x0020 +HF_ENHANCED_CONTROL = 0x0040 +HF_CODEC_NEGOTIATION = 0x0080 + +AG_3WAY = 0x0001 +AG_NREC = 0x0002 +AG_VOICE_RECOGNITION = 0x0004 +AG_INBAND_RING = 0x0008 +AG_VOICE_TAG = 0x0010 +AG_REJECT_CALL = 0x0020 +AG_ENHANCED_STATUS = 0x0040 +AG_ENHANCED_CONTROL = 0x0080 +AG_EXTENDED_RESULT = 0x0100 +AG_CODEC_NEGOTIATION = 0x0200 + +HF_FEATURES = (HF_3WAY | HF_CLI | HF_VOICE_RECOGNITION | + HF_REMOTE_VOL | HF_ENHANCED_STATUS | + HF_ENHANCED_CONTROL | HF_CODEC_NEGOTIATION) + +AVAIL_CODECS = "1,2" + +class HfpConnection: + slc_complete = False + fd = None + io_id = 0 + version = 0 + features = 0 + pending = None + + def disconnect(self): + if (self.fd >= 0): + os.close(self.fd) + self.fd = -1 + glib.source_remove(self.io_id) + self.io_id = 0 + + def slc_completed(self): + print("SLC establisment complete") + self.slc_complete = True + + def slc_next_cmd(self, cmd): + if not cmd: + self.send_cmd("AT+BRSF=%u" % (HF_FEATURES)) + elif (cmd.startswith("AT+BRSF")): + if (self.features & AG_CODEC_NEGOTIATION and + HF_FEATURES & HF_CODEC_NEGOTIATION): + self.send_cmd("AT+BAC=%s" % (AVAIL_CODECS)) + else: + self.send_cmd("AT+CIND=?") + elif (cmd.startswith("AT+BAC")): + self.send_cmd("AT+CIND=?") + elif (cmd.startswith("AT+CIND=?")): + self.send_cmd("AT+CIND?") + elif (cmd.startswith("AT+CIND?")): + self.send_cmd("AT+CMER=3,0,0,1") + elif (cmd.startswith("AT+CMER=")): + if (HF_FEATURES & HF_3WAY and self.features & AG_3WAY): + self.send_cmd("AT+CHLD=?") + else: + self.slc_completed() + elif (cmd.startswith("AT+CHLD=?")): + self.slc_completed() + else: + print("Unknown SLC command completed: %s" % (cmd)) + + def io_cb(self, fd, cond): + buf = os.read(fd, BUF_SIZE) + buf = buf.strip() + + print("Received: %s" % (buf)) + + if (buf == "OK" or buf == "ERROR"): + cmd = self.pending + self.pending = None + + if (not self.slc_complete): + self.slc_next_cmd(cmd) + + return True + + parts = buf.split(':') + + if (parts[0] == "+BRSF"): + self.features = int(parts[1]) + + return True + + def send_cmd(self, cmd): + if (self.pending): + print("ERROR: Another command is pending") + return + + print("Sending: %s" % (cmd)) + + os.write(self.fd, cmd + "\r\n") + self.pending = cmd + + def __init__(self, fd, version, features): + self.fd = fd + self.version = version + self.features = features + + print("Version 0x%04x Features 0x%04x" % (version, features)) + + self.io_id = glib.io_add_watch(fd, glib.IO_IN, self.io_cb) + + self.slc_next_cmd(None) + +class HfpProfile(dbus.service.Object): + sco_socket = None + io_id = 0 + conns = {} + + def sco_cb(self, sock, cond): + (sco, peer) = sock.accept() + print("New SCO connection from %s" % (peer)) + + def init_sco(self, sock): + self.sco_socket = sock + self.io_id = glib.io_add_watch(sock, glib.IO_IN, self.sco_cb) + + def __init__(self, bus, path, sco): + dbus.service.Object.__init__(self, bus, path) + + if sco: + self.init_sco(sco) + + @dbus.service.method("org.bluez.Profile1", + in_signature="", out_signature="") + def Release(self): + print("Release") + mainloop.quit() + + @dbus.service.method("org.bluez.Profile1", + in_signature="", out_signature="") + def Cancel(self): + print("Cancel") + + @dbus.service.method("org.bluez.Profile1", + in_signature="o", out_signature="") + def RequestDisconnection(self, path): + conn = self.conns.pop(path) + conn.disconnect() + + @dbus.service.method("org.bluez.Profile1", + in_signature="oha{sv}", out_signature="") + def NewConnection(self, path, fd, properties): + fd = fd.take() + version = 0x0105 + features = 0 + print("NewConnection(%s, %d)" % (path, fd)) + for key in properties.keys(): + if key == "Version": + version = properties[key] + elif key == "Features": + features = properties[key] + + conn = HfpConnection(fd, version, features) + + self.conns[path] = conn + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + manager = dbus.Interface(bus.get_object("org.bluez", + "/org/bluez"), "org.bluez.ProfileManager1") + + option_list = [ + make_option("-p", "--path", action="store", + type="string", dest="path", + default="/bluez/test/hfp"), + make_option("-n", "--name", action="store", + type="string", dest="name", + default=None), + make_option("-C", "--channel", action="store", + type="int", dest="channel", + default=None), + ] + + parser = OptionParser(option_list=option_list) + + (options, args) = parser.parse_args() + + mainloop = GObject.MainLoop() + + opts = { + "Version" : dbus.UInt16(0x0106), + "Features" : dbus.UInt16(HF_FEATURES), + } + + if (options.name): + opts["Name"] = options.name + + if (options.channel is not None): + opts["Channel"] = dbus.UInt16(options.channel) + + if audio_supported: + sco = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO) + sco.bind(BDADDR_ANY) + sco.listen(1) + else: + sco = None + + profile = HfpProfile(bus, options.path, sco) + + manager.RegisterProfile(options.path, "hfp-hf", opts) + + print("Profile registered - waiting for connections") + + mainloop.run() diff --git a/test/test-manager b/test/test-manager new file mode 100755 index 0000000..4f5994f --- /dev/null +++ b/test/test-manager @@ -0,0 +1,41 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +import dbus +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject +import bluezutils + +def interfaces_added(path, interfaces): + if interfaces.get("org.bluez.Adapter1") != None: + print("Adapter with path %s added" % (path)) + +def interfaces_removed(path, interfaces): + if "org.bluez.Adapter1" in interfaces: + print("Adapter with path %s removed" % (path)) + +if __name__ == "__main__": + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + bus.add_signal_receiver(interfaces_added, bus_name="org.bluez", + dbus_interface="org.freedesktop.DBus.ObjectManager", + signal_name="InterfacesAdded") + + bus.add_signal_receiver(interfaces_removed, bus_name="org.bluez", + dbus_interface="org.freedesktop.DBus.ObjectManager", + signal_name="InterfacesRemoved") + + try: + path = bluezutils.find_adapter().object_path + print("Adapter found at path %s" % (path)) + except: + print("No adapter found") + + mainloop = GObject.MainLoop() + mainloop.run() diff --git a/test/test-mesh b/test/test-mesh new file mode 100755 index 0000000..3c5ded7 --- /dev/null +++ b/test/test-mesh @@ -0,0 +1,1124 @@ +#!/usr/bin/env python3 + +################################################################### +# +# This is a unified test sample for BT Mesh +# +# To run the test: +# test-mesh [token] +# +# 'token' is an optional argument. It must be a 16-digit +# hexadecimal number. The token must be associated with +# an existing node. The token is generated and assigned +# to a node as a result of successful provisioning (see +# explanation of "join" option). +# When the token is set, the menu operations "attach" +# and "remove" may be performed on a node specified +# by this token. +# +# The test imitates a device with 2 elements: +# element 0: OnOff Server model +# Sample Vendor model +# element 1: OnOff Client model +# +# The main menu: +# token +# create +# scan +# add +# join +# attach +# remove +# dest +# uuid +# app-index +# client-menu +# exit +# +# The main menu options explained: +# token +# Set the unique node token. +# The token can be set from command line arguments as +# well. +# +# create +# Creates a new mesh network, with its first local +# node. The test generates device UUID to store for +# the initial node, and the daemon will create all +# the other parameters including Unicast address 0x0001 +# for the new nodes primary element. +# In case of successful creation, the application +# automatically attaches as a node to the daemon. A node +# 'token' is returned to the application and is used +# for the runtime of the test, and may be used in future +# attach requests. +# +# scan +# Scan for unprovisioned devices +# +# add +# Adds a remote node to a mesh network that we have provisioning +# authorization to. The test prompts for a remote devices +# UUID, and supplies an Agent that will handle the interaction, +# and provide the provisioning data which will complete to +# process. +# +# join +# Request provisioning of a device to become a node +# on a mesh network. The test generates device UUID +# which is displayed and will need to be provided to +# an outside entity that acts as a Provisioner. Also, +# during the provisioning process, an agent that is +# part of the test, will request (or will be requested) +# to perform a specified operation, e.g., a number will +# be displayed and this number will need to be entered +# on the Provisioner's side. +# In case of successful provisioning, the application +# automatically attaches as a node to the daemon. A node +# 'token' is returned to the application and is used +# for the runtime of the test. +# +# attach +# Attach the application to bluetoothd-daemon as a node. +# For the call to be successful, the valid node token must +# be already set, either from command arguments or by +# executing "set token" operation or automatically after +# successfully executing "join" or "create" operation in +# the same test run. +# +# remove +# Permanently removes any node configuration from daemon +# and persistent storage. After this operation, the node +# is permanently forgotten by the daemon and the associated +# node token is no longer valid. +# +# dest +# Set destination address to send messages: 4 hex digits +# +# app-index +# Set AppKey index to indicate which application key to use +# to encode outgoing messages: up to 3 hex digits +# +# vendor-send +# Allows to send an arbitrary endor message. +# The destination is set based on previously executed "dest" +# command (if not set, the outbound message will fail). +# User is prompted to enter hex bytearray payload. +# The message is originated from the vendor model registered +# on element 0. For the command to succeed, the AppKey index +# that is set by executing "app-key" must correspond to the +# application key to which the Sample Vendor model is bound. +# +# client-menu +# Enter On/Off client submenu. +# +# quit +# Exits the test. +# +################################################################### +import sys +import struct +import fcntl +import os +import numpy +import random +import dbus +import dbus.service +import dbus.exceptions + +from threading import Timer +import time + +try: + from gi.repository import GLib +except ImportError: + import glib as GLib +from dbus.mainloop.glib import DBusGMainLoop + +try: + from termcolor import colored, cprint + set_error = lambda x: colored('!' + x, 'red', attrs=['bold']) + set_cyan = lambda x: colored(x, 'cyan', attrs=['bold']) + set_green = lambda x: colored(x, 'green', attrs=['bold']) + set_yellow = lambda x: colored(x, 'yellow', attrs=['bold']) +except ImportError: + print('!!! Install termcolor module for better experience !!!') + set_error = lambda x: x + set_cyan = lambda x: x + set_green = lambda x: x + set_yellow = lambda x: x + +# Provisioning agent +try: + import agent +except ImportError: + agent = None + +MESH_SERVICE_NAME = 'org.bluez.mesh' +DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties' +DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager' + +MESH_MGR_IFACE = 'org.bluez.mesh.Management1' +MESH_NETWORK_IFACE = 'org.bluez.mesh.Network1' +MESH_NODE_IFACE = 'org.bluez.mesh.Node1' +MESH_APPLICATION_IFACE = 'org.bluez.mesh.Application1' +MESH_PROV_IFACE = 'org.bluez.mesh.Provisioner1' +MESH_ELEMENT_IFACE = 'org.bluez.mesh.Element1' + +APP_COMPANY_ID = 0x05f1 +APP_PRODUCT_ID = 0x0001 +APP_VERSION_ID = 0x0001 + +VENDOR_ID_NONE = 0xffff + +app = None +bus = None +mainloop = None +node = None +node_mgr = None +mesh_net = None + +dst_addr = 0x0000 +app_idx = 0 + +# Node token housekeeping +token = None +have_token = False +attached = False + +# Remote device UUID +have_uuid = False +remote_uuid = None + +# Menu housekeeping +MAIN_MENU = 0 +ON_OFF_CLIENT_MENU = 1 + +INPUT_NONE = 0 +INPUT_TOKEN = 1 +INPUT_DEST_ADDRESS = 2 +INPUT_APP_KEY_INDEX = 3 +INPUT_MESSAGE_PAYLOAD = 4 +INPUT_UUID = 5 + +menus = [] +current_menu = None + +user_input = 0 +input_error = False + +def raise_error(str_value): + global input_error + + input_error = True + print(set_error(str_value)) + +def clear_error(): + global input_error + input_error = False + +def is_error(): + return input_error + +def app_exit(): + global mainloop + global app + + for el in app.elements: + for model in el.models: + if model.timer != None: + model.timer.cancel() + mainloop.quit() + +def set_token(str_value): + global token + global have_token + + if len(str_value) != 16: + raise_error('Expected 16 digits') + return + + try: + input_number = int(str_value, 16) + except ValueError: + raise_error('Not a valid hexadecimal number') + return + + token = numpy.uint64(input_number) + have_token = True + +def set_uuid(str_value): + global remote_uuid + global have_uuid + + if len(str_value) != 32: + raise_error('Expected 32 digits') + return + + remote_uuid = bytearray.fromhex(str_value) + have_uuid = True + +def array_to_string(b_array): + str_value = "" + for b in b_array: + str_value += "%02x" % b + return str_value + +def generic_error_cb(error): + print(set_error('D-Bus call failed: ') + str(error)) + +def generic_reply_cb(): + return + +def attach_app_error_cb(error): + print(set_error('Failed to register application: ') + str(error)) + +def attach(token): + print('Attach mesh node to bluetooth-meshd daemon') + + mesh_net.Attach(app.get_path(), token, + reply_handler=attach_app_cb, + error_handler=attach_app_error_cb) + +def scan_cb(): + print('Scan procedure started') + +def scan_error_cb(reason): + print('Scan procedure failed ', reason) + +def add_cb(): + print('AddNode procedure started') + +def add_error_cb(reason): + print('AddNode procedure failed ', reason) + +def join_cb(): + print('Join procedure started') + +def join_error_cb(reason): + print('Join procedure failed: ', reason) + +def create_cb(value): + global token + global have_token + global attach + + print(set_yellow('Created mesh network with token ') + + set_green(format(value, '16x'))) + + token = value + have_token = True + if attached == False: + attach(token) + +def create_error_cb(reason): + print('Create procedure failed: ', reason) + +def remove_node_cb(): + global attached + global have_token + + print(set_yellow('Node removed')) + attached = False + have_token = False + +def unwrap(item): + if isinstance(item, dbus.Boolean): + return bool(item) + if isinstance(item, (dbus.UInt16, dbus.Int16, dbus.UInt32, dbus.Int32, + dbus.UInt64, dbus.Int64)): + return int(item) + if isinstance(item, dbus.Byte): + return bytes([int(item)]) + if isinstance(item, dbus.String): + return item + if isinstance(item, (dbus.Array, list, tuple)): + return [unwrap(x) for x in item] + if isinstance(item, (dbus.Dictionary, dict)): + return dict([(unwrap(x), unwrap(y)) for x, y in item.items()]) + + print(set_error('Dictionary item not handled: ') + type(item)) + + return item + +def attach_app_cb(node_path, dict_array): + global attached + + attached = True + + print(set_yellow('Mesh app registered: ') + set_green(node_path)) + + obj = bus.get_object(MESH_SERVICE_NAME, node_path) + + global node_mgr + node_mgr = dbus.Interface(obj, MESH_MGR_IFACE) + + global node + node = dbus.Interface(obj, MESH_NODE_IFACE) + + els = unwrap(dict_array) + + for el in els: + idx = struct.unpack('b', el[0])[0] + + models = el[1] + element = app.get_element(idx) + element.set_model_config(models) + +def interfaces_removed_cb(object_path, interfaces): + print('Removed') + if not mesh_net: + return + + print(object_path) + if object_path == mesh_net[2]: + print('Service was removed') + app_exit() + +def print_state(state): + print('State is ', end='') + if state == 0: + print('OFF') + elif state == 1: + print('ON') + else: + print('UNKNOWN') +class PubTimer(): + def __init__(self): + self.seconds = None + self.func = None + self.thread = None + self.busy = False + + def _timeout_cb(self): + self.func() + self.busy = True + self._schedule_timer() + self.busy =False + + def _schedule_timer(self): + self.thread = Timer(self.seconds, self._timeout_cb) + self.thread.start() + + def start(self, seconds, func): + self.func = func + self.seconds = seconds + if not self.busy: + self._schedule_timer() + + def cancel(self): + if self.thread is not None: + self.thread.cancel() + self.thread = None + +class Application(dbus.service.Object): + + def __init__(self, bus): + self.path = '/example' + self.agent = None + self.elements = [] + dbus.service.Object.__init__(self, bus, self.path) + + def set_agent(self, agent): + self.agent = agent + + def get_path(self): + return dbus.ObjectPath(self.path) + + def add_element(self, element): + self.elements.append(element) + + def get_element(self, idx): + for ele in self.elements: + if ele.get_index() == idx: + return ele + + def get_properties(self): + return { + MESH_APPLICATION_IFACE: { + 'CompanyID': dbus.UInt16(APP_COMPANY_ID), + 'ProductID': dbus.UInt16(APP_PRODUCT_ID), + 'VersionID': dbus.UInt16(APP_VERSION_ID) + }, + MESH_PROV_IFACE: { + } + } + + @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}') + def GetManagedObjects(self): + response = {} + response[self.path] = self.get_properties() + response[self.agent.get_path()] = self.agent.get_properties() + for element in self.elements: + response[element.get_path()] = element.get_properties() + return response + + @dbus.service.method(MESH_APPLICATION_IFACE, + in_signature="t", out_signature="") + def JoinComplete(self, value): + global token + global have_token + global attach + + print(set_yellow('Joined mesh network with token ') + + set_green(format(value, '16x'))) + + token = value + have_token = True + if attached == False: + attach(token) + + @dbus.service.method(MESH_APPLICATION_IFACE, + in_signature="s", out_signature="") + def JoinFailed(self, value): + print(set_error('JoinFailed '), value) + + @dbus.service.method(MESH_PROV_IFACE, + in_signature="nay", out_signature="") + def ScanResult(self, rssi, uuid): + uuid_str = array_to_string(uuid) + print(set_yellow('ScanResult RSSI ') + + set_green(format(rssi, 'd')) + + ' ' + uuid_str) + + @dbus.service.method(MESH_PROV_IFACE, + in_signature="y", out_signature="qq") + def RequestProvData(self, count): + print('RequestProvData for Ele_Cnt ' + + set_green(format(count, 'd'))) + return dbus.Struct((dbus.UInt16(0), dbus.UInt16(678))) + + @dbus.service.method(MESH_PROV_IFACE, + in_signature="ayqy", out_signature="") + def AddNodeComplete(self, uuid, unicast, count): + uuid_str = array_to_string(uuid) + print(set_yellow('AddNodeComplete of node ') + + set_green(format(unicast, '04x')) + + ' uuid ' + uuid_str) + + @dbus.service.method(MESH_PROV_IFACE, + in_signature="ays", out_signature="") + + def AddNodeFailed(self, uuid, value): + print(set_error('AddNodeFailed '), value) + + +class Element(dbus.service.Object): + PATH_BASE = '/example/ele' + + def __init__(self, bus, index): + self.path = self.PATH_BASE + format(index, '02x') + self.models = [] + self.bus = bus + self.index = index + dbus.service.Object.__init__(self, bus, self.path) + + def _get_sig_models(self): + ids = [] + for model in self.models: + id = model.get_id() + vendor = model.get_vendor() + if vendor == VENDOR_ID_NONE: + ids.append(id) + return ids + + def _get_v_models(self): + ids = [] + for model in self.models: + id = model.get_id() + v = model.get_vendor() + if v != VENDOR_ID_NONE: + vendor_id = (v, id) + ids.append(vendor_id) + return ids + + def get_properties(self): + vendor_models = self._get_v_models() + sig_models = self._get_sig_models() + + props = {'Index' : dbus.Byte(self.index)} + props['Models'] = dbus.Array(sig_models, signature='q') + props['VendorModels'] = dbus.Array(vendor_models, + signature='(qq)') + #print(props) + return { MESH_ELEMENT_IFACE: props } + + def add_model(self, model): + model.set_path(self.path) + self.models.append(model) + + def get_index(self): + return self.index + + def set_model_config(self, configs): + for config in configs: + mod_id = config[0] + self.UpdateModelConfiguration(mod_id, config[1]) + + @dbus.service.method(MESH_ELEMENT_IFACE, + in_signature="qqbay", out_signature="") + def MessageReceived(self, source, key, is_sub, data): + print('Message Received on Element ', end='') + print(self.index) + for model in self.models: + model.process_message(source, key, data) + + @dbus.service.method(MESH_ELEMENT_IFACE, + in_signature="qa{sv}", out_signature="") + + def UpdateModelConfiguration(self, model_id, config): + print(('Update Model Config '), end='') + print(format(model_id, '04x')) + for model in self.models: + if model_id == model.get_id(): + model.set_config(config) + return + + @dbus.service.method(MESH_ELEMENT_IFACE, + in_signature="", out_signature="") + + def get_path(self): + return dbus.ObjectPath(self.path) + +class Model(): + def __init__(self, model_id): + self.cmd_ops = [] + self.model_id = model_id + self.vendor = VENDOR_ID_NONE + self.bindings = [] + self.pub_period = 0 + self.pub_id = 0 + self.path = None + self.timer = None + + def set_path(self, path): + self.path = path + + def get_id(self): + return self.model_id + + def get_vendor(self): + return self.vendor + + def process_message(self, source, key, data): + return + + def set_publication(self, period): + self.pub_period = period + + def send_publication(self, data): + print('Send publication ', end='') + print(data) + node.Publish(self.path, self.model_id, data, + reply_handler=generic_reply_cb, + error_handler=generic_error_cb) + + def send_message(self, dest, key, data): + node.Send(self.path, dest, key, data, + reply_handler=generic_reply_cb, + error_handler=generic_error_cb) + + def set_config(self, config): + if 'Bindings' in config: + self.bindings = config.get('Bindings') + print('Bindings: ', end='') + print(self.bindings) + if 'PublicationPeriod' in config: + self.set_publication(config.get('PublicationPeriod')) + print('Model publication period ', end='') + print(self.pub_period, end='') + print(' ms') + + def print_bindings(self): + print(set_cyan('Model'), set_cyan('%03x' % self.model_id), + set_cyan('is bound to: ')) + + if len(self.bindings) == 0: + print(set_cyan('** None **')) + return + + for b in self.bindings: + print(set_green('%03x' % b) + ' ') + +######################## +# On Off Server Model +######################## +class OnOffServer(Model): + def __init__(self, model_id): + Model.__init__(self, model_id) + self.cmd_ops = { 0x8201, # get + 0x8202, # set + 0x8203, # set unacknowledged + 0x8204 } # status + + print("OnOff Server ") + self.state = 0 + print_state(self.state) + self.timer = PubTimer() + + def process_message(self, source, key, data): + datalen = len(data) + + if datalen != 2 and datalen != 3: + # The opcode is not recognized by this model + return + + if datalen == 2: + op_tuple=struct.unpack(' 1: + for cmd in cmds: + print(set_cyan(cmd + '?')) + return + + self.menu.get(cmds[0]).func() + +class MenuHandler(object): + def __init__(self, callback): + self.cb = callback + flags = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL) + flags |= os.O_NONBLOCK + fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, flags) + sys.stdin.flush() + GLib.io_add_watch(sys.stdin, GLib.IO_IN, self.input_callback) + + def input_callback(self, fd, condition): + chunk = fd.read() + buffer = '' + for char in chunk: + buffer += char + if char == '\n': + self.cb(buffer) + + return True + +def process_input(input_str): + str_value = input_str.strip() + + # Allow entering empty lines for better output visibility + if len(str_value) == 0: + return + + current_menu.process_cmd(str_value) + +def switch_menu(level): + global current_menu + + if level >= len(menus): + return + + current_menu = menus[level] + current_menu.show() + +######################## +# Main menu class +######################## +class MainMenu(Menu): + def __init__(self): + menu_items = { + 'token': MenuItem(' - set node ID (token)', + self.__cmd_set_token), + 'create': MenuItem(' - create mesh network', + self.__cmd_create), + 'scan': MenuItem(' - scan for near unprovisioned devs', + self.__cmd_scan), + 'add': MenuItem(' - add device to mesh network', + self.__cmd_add), + 'join': MenuItem(' - join mesh network', + self.__cmd_join), + 'attach': MenuItem(' - attach mesh node', + self.__cmd_attach), + 'remove': MenuItem(' - delete node', + self.__cmd_remove), + 'dest': MenuItem(' - set destination address', + self.__cmd_set_dest), + 'uuid': MenuItem(' - set remote uuid', + self.__cmd_set_uuid), + 'app-index': MenuItem(' - set AppKey index', + self.__cmd_set_app_idx), + 'vendor-send': MenuItem(' - send raw vendor message', + self.__cmd_vendor_msg), + 'client-menu': MenuItem(' - On/Off client menu', + self.__cmd_client_menu), + 'quit': MenuItem(' - exit the test', app_exit) + } + + Menu.__init__(self, 'Main Menu', menu_items) + + def __cmd_client_menu(self): + if attached != True: + print(set_error('Disallowed: node is not attached')) + return + switch_menu(ON_OFF_CLIENT_MENU) + + def __cmd_set_token(self): + global user_input + + if have_token == True: + print('Token already set') + return + + user_input = INPUT_TOKEN + print(set_cyan('Enter 16-digit hex node ID:')) + + def __cmd_set_dest(self): + global user_input + + user_input = INPUT_DEST_ADDRESS + print(set_cyan('Enter 4-digit hex destination address:')) + + def __cmd_set_uuid(self): + global user_input + + user_input = INPUT_UUID + print(set_cyan('Enter 32-digit hex remote UUID:')) + + def __cmd_set_app_idx(self): + global user_input + + user_input = INPUT_APP_KEY_INDEX; + print(set_cyan('Enter app key index (up to 3 digit hex):')) + + def __cmd_vendor_msg(self): + global user_input + + user_input = INPUT_MESSAGE_PAYLOAD; + print(set_cyan('Enter message payload (hex):')) + + def __cmd_create(self): + if agent == None: + print(set_error('Provisioning agent not found')) + return + + uuid = bytearray.fromhex("0a0102030405060708090A0B0C0D0E0F") + random.shuffle(uuid) + uuid_str = array_to_string(uuid) + + print(set_yellow('Creating with UUID ') + set_green(uuid_str)) + mesh_net.CreateNetwork(app.get_path(), uuid, + reply_handler=create_cb, + error_handler=create_error_cb) + + def __cmd_join(self): + if agent == None: + print(set_error('Provisioning agent not found')) + return + + uuid = bytearray.fromhex("0a0102030405060708090A0B0C0D0E0F") + random.shuffle(uuid) + uuid_str = array_to_string(uuid) + caps = ["out-numeric"] + oob = ["other"] + + print(set_yellow('Joining with UUID ') + set_green(uuid_str)) + mesh_net.Join(app.get_path(), uuid, + reply_handler=join_cb, + error_handler=join_error_cb) + + def __cmd_scan(self): + + print(set_yellow('Scanning...')) + node_mgr.UnprovisionedScan(0, reply_handler=add_cb, + error_handler=add_error_cb) + + def __cmd_add(self): + global user_input + if agent == None: + print(set_error('Provisioning agent not found')) + return + + uuid_str = array_to_string(remote_uuid) + caps = ["in-numeric"] + oob = ["other"] + + print(set_yellow('Adding dev UUID ') + set_green(uuid_str)) + node_mgr.AddNode(remote_uuid, reply_handler=add_cb, + error_handler=add_error_cb) + + def __cmd_attach(self): + if have_token == False: + print(set_error('Token is not set')) + self.show() + return + + attach(token) + + def __cmd_remove(self): + if have_token == False: + print(set_error('Token is not set')) + self.show() + return + + print('Removing mesh node') + mesh_net.Leave(token, reply_handler=remove_node_cb, + error_handler=generic_error_cb) + + def __send_vendor_msg(self, str_value): + try: + msg_data = bytearray.fromhex(str_value) + except ValueError: + raise_error('Not a valid hexadecimal input') + return + + print(set_yellow('Send data: ' + set_green(str_value))) + app.elements[0].models[1].send_message(dst_addr, app_idx, + msg_data) + + def process_cmd(self, str_value): + global user_input + global dst_addr + global app_idx + + if user_input == INPUT_TOKEN: + set_token(str_value) + elif user_input == INPUT_UUID: + set_uuid(str_value) + elif user_input == INPUT_DEST_ADDRESS: + res = set_value(str_value, 4, 4) + if is_error() != True: + dst_addr = res + print(set_yellow("Destination address: ") + + set_green(format(dst_addr, '04x'))) + elif user_input == INPUT_APP_KEY_INDEX: + res = set_value(str_value, 1, 3) + if is_error() != True: + app_idx = res + print(set_yellow("Application index: ") + + set_green(format(app_idx, '03x'))) + elif user_input == INPUT_MESSAGE_PAYLOAD: + self.__send_vendor_msg(str_value) + + if user_input != INPUT_NONE: + user_input = INPUT_NONE + if is_error() != True: + return + + Menu.process_cmd(self, str_value) + +############################## +# On/Off Client menu class +############################## +class ClientMenu(Menu): + def __init__(self): + menu_items = { + 'get-state': MenuItem(' - get server state', + self.__cmd_get_state), + 'off': MenuItem(' - set state OFF', + self.__cmd_set_state_off), + 'on': MenuItem(' - set state ON', + self.__cmd_set_state_on), + 'back': MenuItem(' - back to main menu', + self.__cmd_main_menu), + 'quit': MenuItem(' - exit the test', app_exit) + } + + Menu.__init__(self, 'On/Off Clien Menu', menu_items) + + def __cmd_main_menu(self): + switch_menu(MAIN_MENU) + + def __cmd_get_state(self): + app.elements[1].models[0].get_state(dst_addr, app_idx) + + def __cmd_set_state_off(self): + app.elements[1].models[0].set_state(dst_addr, app_idx, 0) + + def __cmd_set_state_on(self): + app.elements[1].models[0].set_state(dst_addr, app_idx, 1) + + +def set_value(str_value, min, max): + + if len(str_value) > max or len(str_value) < min: + raise_error('Bad input length %d' % len(str_value)) + return -1 + + try: + value = int(str_value, 16) + except ValueError: + raise_error('Not a valid hexadecimal number') + return -1 + + return value + + +######################## +# Main entry +######################## +def main(): + + DBusGMainLoop(set_as_default=True) + + global bus + bus = dbus.SystemBus() + global mainloop + global app + global mesh_net + global menu + global current_menu + + if len(sys.argv) > 1 : + set_token(sys.argv[1]) + + mesh_net = dbus.Interface(bus.get_object(MESH_SERVICE_NAME, + "/org/bluez/mesh"), + MESH_NETWORK_IFACE) + + mesh_net.connect_to_signal('InterfacesRemoved', interfaces_removed_cb) + + app = Application(bus) + + # Provisioning agent + if agent != None: + app.set_agent(agent.Agent(bus)) + + first_ele = Element(bus, 0x00) + second_ele = Element(bus, 0x01) + + print(set_yellow('Register OnOff Server model on element 0')) + first_ele.add_model(OnOffServer(0x1000)) + + print(set_yellow('Register Vendor model on element 0')) + first_ele.add_model(SampleVendor(0x0001)) + + print(set_yellow('Register OnOff Client model on element 1')) + second_ele.add_model(OnOffClient(0x1001)) + + app.add_element(first_ele) + app.add_element(second_ele) + + mainloop = GLib.MainLoop() + + menus.append(MainMenu()) + menus.append(ClientMenu()) + switch_menu(MAIN_MENU) + + event_catcher = MenuHandler(process_input); + mainloop.run() + +if __name__ == '__main__': + main() diff --git a/test/test-nap b/test/test-nap new file mode 100755 index 0000000..ab67a75 --- /dev/null +++ b/test/test-nap @@ -0,0 +1,47 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser, make_option +import sys +import time +import dbus +import bluezutils +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject + +bus = dbus.SystemBus() + +option_list = [ + make_option("-i", "--device", action="store", + type="string", dest="dev_id"), + ] +parser = OptionParser(option_list=option_list) + +(options, args) = parser.parse_args() + +adapter_path = bluezutils.find_adapter(options.dev_id).object_path +server = dbus.Interface(bus.get_object("org.bluez", adapter_path), + "org.bluez.NetworkServer1") + +service = "nap" + +if (len(args) < 1): + bridge = "tether" +else: + bridge = args[0] + +dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + +mainloop = GObject.MainLoop() + +server.Register(service, bridge) + +print("Server for %s registered for %s" % (service, bridge)) + +print("Press CTRL-C to disconnect") + +mainloop.run() diff --git a/test/test-network b/test/test-network new file mode 100755 index 0000000..6f09486 --- /dev/null +++ b/test/test-network @@ -0,0 +1,54 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser, make_option +import sys +import time +import dbus +import bluezutils + +bus = dbus.SystemBus() + +manager = dbus.Interface(bus.get_object("org.bluez", "/"), + "org.bluez.Manager") + +option_list = [ + make_option("-i", "--device", action="store", + type="string", dest="dev_id"), + ] +parser = OptionParser(option_list=option_list) + +(options, args) = parser.parse_args() + +if (len(args) < 1): + print("Usage: %s
[service]" % (sys.argv[0])) + sys.exit(1) + +# Fix-up in case of "connect" invocation that other scripts use +if args[0] == "connect": + del args[:1] + +if (len(args) < 2): + service = "panu" +else: + service = args[1] + +device = bluezutils.find_device(args[0], options.dev_id) + +network = dbus.Interface(bus.get_object("org.bluez", device.object_path), + "org.bluez.Network1") + +iface = network.Connect(service) + +print("Connected to %s service %s, interface %s" % (args[0], service, iface)) + +print("Press CTRL-C to disconnect") + +try: + time.sleep(1000) + print("Terminating connection") +except: + pass + +network.Disconnect() diff --git a/test/test-profile b/test/test-profile new file mode 100755 index 0000000..2791580 --- /dev/null +++ b/test/test-profile @@ -0,0 +1,127 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from optparse import OptionParser, make_option +import os +import sys +import uuid +import dbus +import dbus.service +import dbus.mainloop.glib +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject + +class Profile(dbus.service.Object): + fd = -1 + + @dbus.service.method("org.bluez.Profile1", + in_signature="", out_signature="") + def Release(self): + print("Release") + mainloop.quit() + + @dbus.service.method("org.bluez.Profile1", + in_signature="", out_signature="") + def Cancel(self): + print("Cancel") + + @dbus.service.method("org.bluez.Profile1", + in_signature="oha{sv}", out_signature="") + def NewConnection(self, path, fd, properties): + self.fd = fd.take() + print("NewConnection(%s, %d)" % (path, self.fd)) + for key in properties.keys(): + if key == "Version" or key == "Features": + print(" %s = 0x%04x" % (key, properties[key])) + else: + print(" %s = %s" % (key, properties[key])) + + @dbus.service.method("org.bluez.Profile1", + in_signature="o", out_signature="") + def RequestDisconnection(self, path): + print("RequestDisconnection(%s)" % (path)) + + if (self.fd > 0): + os.close(self.fd) + self.fd = -1 + +if __name__ == '__main__': + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + + bus = dbus.SystemBus() + + manager = dbus.Interface(bus.get_object("org.bluez", + "/org/bluez"), "org.bluez.ProfileManager1") + + option_list = [ + make_option("-u", "--uuid", action="store", + type="string", dest="uuid", + default=None), + make_option("-p", "--path", action="store", + type="string", dest="path", + default="/foo/bar/profile"), + make_option("-n", "--name", action="store", + type="string", dest="name", + default=None), + make_option("-s", "--server", + action="store_const", + const="server", dest="role"), + make_option("-c", "--client", + action="store_const", + const="client", dest="role"), + make_option("-a", "--auto-connect", + action="store_true", + dest="auto_connect", default=False), + make_option("-P", "--PSM", action="store", + type="int", dest="psm", + default=None), + make_option("-C", "--channel", action="store", + type="int", dest="channel", + default=None), + make_option("-r", "--record", action="store", + type="string", dest="record", + default=None), + make_option("-S", "--service", action="store", + type="string", dest="service", + default=None), + ] + + parser = OptionParser(option_list=option_list) + + (options, args) = parser.parse_args() + + profile = Profile(bus, options.path) + + mainloop = GObject.MainLoop() + + opts = { + "AutoConnect" : options.auto_connect, + } + + if (options.name): + opts["Name"] = options.name + + if (options.role): + opts["Role"] = options.role + + if (options.psm is not None): + opts["PSM"] = dbus.UInt16(options.psm) + + if (options.channel is not None): + opts["Channel"] = dbus.UInt16(options.channel) + + if (options.record): + opts["ServiceRecord"] = options.record + + if (options.service): + opts["Service"] = options.service + + if not options.uuid: + options.uuid = str(uuid.uuid4()) + + manager.RegisterProfile(options.path, options.uuid, opts) + + mainloop.run() diff --git a/test/test-sap-server b/test/test-sap-server new file mode 100755 index 0000000..ff178af --- /dev/null +++ b/test/test-sap-server @@ -0,0 +1,151 @@ +#!/usr/bin/python + +from __future__ import absolute_import, print_function, unicode_literals + +from sap_client import * +import time +import sys + +def connect_disconnect_by_client(sap): + + print("[Test] Connect - Disconnect by client \n") + + try: + if not sap.isConnected(): + sap.connect() + + if sap.proc_connect(): + if sap.proc_disconnectByClient(): + print("OK") + return 0 + + print("NOT OK") + return 1 + + except BluetoothError as e: + print("Error " + str(e)) + + +def connect_disconnect_by_server_gracefully(sap, timeout=0): + + print("[Test] Connect - Disconnect by server with timer \n") + + try: + if not sap.isConnected(): + sap.connect() + + if sap.proc_connect(): + if sap.proc_disconnectByServer(timeout): + print("OK") + return 0 + + print("NOT OK") + return 1 + + except BluetoothError as e: + print("Error " + str(e)) + + +def connect_txAPDU_disconnect_by_client(sap): + + print("[Test] Connect - TX APDU - Disconnect by client \n") + + try: + if not sap.isConnected(): + sap.connect() + + if sap.proc_connect(): + if not sap.proc_transferAPDU(): + print("NOT OK 1") + return 1 + + if not sap.proc_transferAPDU(): + print("NOT OK 2") + return 1 + + if not sap.proc_transferAPDU(): + print("NOT OK 3") + return 1 + + if not sap.proc_transferAPDU(): + print("NOT OK 4") + return 1 + + if sap.proc_disconnectByClient(): + print("OK") + return 0 + + print("NOT OK") + return 1 + + except BluetoothError as e: + print("Error " + str(e)) + +def connect_rfcomm_only_and_wait_for_close_by_server(sap): + + print("[Test] Connect rfcomm only - Disconnect by server timeout \n") + + if not sap.isConnected(): + sap.connect() + + time.sleep(40) + print("OK") + +def power_sim_off_on(sap): + + print("[Test] Powe sim off \n") + + try: + if not sap.isConnected(): + sap.connect() + + if sap.proc_connect(): + if not sap.proc_resetSim(): + print("NOT OK") + return 1 + + if not sap.proc_powerSimOff(): + print("NOT OK") + return 1 + + if not sap.proc_powerSimOn(): + print("NOT OK") + return 1 + + if sap.proc_disconnectByClient(): + print("OK") + return 0 + + print("NOT OK") + return 1 + + except BluetoothError as e: + print("Error " + str(e)) + + +if __name__ == "__main__": + + host = None # server bd_addr + port = 8 # sap server port + + if (len(sys.argv) < 2): + print("Usage: %s
[port]" % (sys.argv[0])) + sys.exit(1) + + host = sys.argv[1] + + if (len(sys.argv) == 3): + port = sys.argv[2] + + try: + s = SAPClient(host, port) + except BluetoothError as e: + print("Error: " + str(e)) + sys.exit(1) + + connect_disconnect_by_client(s) + connect_disconnect_by_server_gracefully(s) + connect_disconnect_by_server_gracefully(s, 40) # wait 40 sec for srv to close rfcomm sock + connect_rfcomm_only_and_wait_for_close_by_server(s) + connect_txAPDU_disconnect_by_client(s) + power_sim_off_on(s) diff --git a/tools/3dsp.c b/tools/3dsp.c new file mode 100644 index 0000000..77a70c0 --- /dev/null +++ b/tools/3dsp.c @@ -0,0 +1,655 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 +#endif + +#include +#include +#include +#include +#include + +#include "monitor/bt.h" +#include "src/shared/mainloop.h" +#include "src/shared/timeout.h" +#include "src/shared/util.h" +#include "src/shared/hci.h" + +#define LT_ADDR 0x01 +#define PKT_TYPE 0x0008 /* 0x0008 = EDR + DM1, 0xff1e = BR only */ +#define SERVICE_DATA 0x00 + +struct broadcast_message { + uint32_t frame_sync_instant; + uint16_t bluetooth_clock_phase; + uint16_t left_open_offset; + uint16_t left_close_offset; + uint16_t right_open_offset; + uint16_t right_close_offset; + uint16_t frame_sync_period; + uint8_t frame_sync_period_fraction; +} __attribute__ ((packed)); + +struct brcm_evt_sync_train_received { + uint8_t status; + uint8_t bdaddr[6]; + uint32_t offset; + uint8_t map[10]; + uint8_t service_data; + uint8_t lt_addr; + uint32_t instant; + uint16_t interval; +} __attribute__ ((packed)); + +static struct bt_hci *hci_dev; + +static bool reset_on_init = false; +static bool reset_on_shutdown = false; + +static bool shutdown_timeout(void *user_data) +{ + mainloop_quit(); + + return false; +} + +static void shutdown_complete(const void *data, uint8_t size, void *user_data) +{ + unsigned int id = PTR_TO_UINT(user_data); + + timeout_remove(id); + mainloop_quit(); +} + +static void shutdown_device(void) +{ + unsigned int id; + + bt_hci_flush(hci_dev); + + if (reset_on_shutdown) { + id = timeout_add(5000, shutdown_timeout, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, + shutdown_complete, UINT_TO_PTR(id), NULL); + } else + mainloop_quit(); +} + +static void inquiry_started(const void *data, uint8_t size, void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + printf("Failed to search for 3D display\n"); + shutdown_device(); + return; + } + + printf("Searching for 3D display\n"); +} + +static void start_inquiry(void) +{ + struct bt_hci_cmd_inquiry cmd; + + cmd.lap[0] = 0x33; + cmd.lap[1] = 0x8b; + cmd.lap[2] = 0x9e; + cmd.length = 0x08; + cmd.num_resp = 0x00; + + bt_hci_send(hci_dev, BT_HCI_CMD_INQUIRY, &cmd, sizeof(cmd), + inquiry_started, NULL, NULL); +} + +static void set_slave_broadcast_receive(const void *data, uint8_t size, + void *user_data) +{ + printf("Slave broadcast receiption enabled\n"); +} + +static void sync_train_received(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_evt_sync_train_received *evt = data; + struct bt_hci_cmd_set_slave_broadcast_receive cmd; + + if (evt->status) { + printf("Failed to synchronize with 3D display\n"); + start_inquiry(); + return; + } + + if (evt->lt_addr != LT_ADDR) { + printf("Ignoring synchronization for non 3D display\n"); + return; + } + + cmd.enable = 0x01; + memcpy(cmd.bdaddr, evt->bdaddr, 6); + cmd.lt_addr = evt->lt_addr; + cmd.interval = evt->interval; + cmd.offset = evt->offset; + cmd.instant = evt->instant; + cmd.timeout = cpu_to_le16(0xfffe); + cmd.accuracy = 250; + cmd.skip = 20; + cmd.pkt_type = cpu_to_le16(PKT_TYPE); + memcpy(cmd.map, evt->map, 10); + + bt_hci_send(hci_dev, BT_HCI_CMD_SET_SLAVE_BROADCAST_RECEIVE, + &cmd, sizeof(cmd), + set_slave_broadcast_receive, NULL, NULL); +} + +static void brcm_sync_train_received(const void *data, uint8_t size, + void *user_data) +{ + const struct brcm_evt_sync_train_received *evt = data; + struct bt_hci_cmd_set_slave_broadcast_receive cmd; + + if (evt->status) { + printf("Failed to synchronize with 3D display\n"); + start_inquiry(); + return; + } + + if (evt->lt_addr != LT_ADDR) { + printf("Ignoring synchronization for non 3D display\n"); + return; + } + + cmd.enable = 0x01; + memcpy(cmd.bdaddr, evt->bdaddr, 6); + cmd.lt_addr = evt->lt_addr; + cmd.interval = evt->interval; + cmd.offset = evt->offset; + cmd.instant = evt->instant; + cmd.timeout = cpu_to_le16(0xfffe); + cmd.accuracy = 250; + cmd.skip = 20; + cmd.pkt_type = cpu_to_le16(PKT_TYPE); + memcpy(cmd.map, evt->map, 10); + + bt_hci_send(hci_dev, BT_HCI_CMD_SET_SLAVE_BROADCAST_RECEIVE, + &cmd, sizeof(cmd), + set_slave_broadcast_receive, NULL, NULL); +} + +static void truncated_page_complete(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_evt_truncated_page_complete *evt = data; + struct bt_hci_cmd_receive_sync_train cmd; + + if (evt->status) { + printf("Failed to contact 3D display\n"); + shutdown_device(); + return; + } + + printf("Attempt to synchronize with 3D display\n"); + + memcpy(cmd.bdaddr, evt->bdaddr, 6); + cmd.timeout = cpu_to_le16(0x4000); + cmd.window = cpu_to_le16(0x0100); + cmd.interval = cpu_to_le16(0x0080); + + bt_hci_send(hci_dev, BT_HCI_CMD_RECEIVE_SYNC_TRAIN, &cmd, sizeof(cmd), + NULL, NULL, NULL); +} + +static void slave_broadcast_timeout(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_evt_slave_broadcast_timeout *evt = data; + struct bt_hci_cmd_receive_sync_train cmd; + + printf("Re-synchronizing with 3D display\n"); + + memcpy(cmd.bdaddr, evt->bdaddr, 6); + cmd.timeout = cpu_to_le16(0x4000); + cmd.window = cpu_to_le16(0x0100); + cmd.interval = cpu_to_le16(0x0080); + + bt_hci_send(hci_dev, BT_HCI_CMD_RECEIVE_SYNC_TRAIN, &cmd, sizeof(cmd), + NULL, NULL, NULL); +} + +static void slave_broadcast_receive(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_evt_slave_broadcast_receive *evt = data; + struct bt_hci_cmd_read_clock cmd; + + if (evt->status != 0x00) + return; + + if (le32_to_cpu(evt->clock) != 0x00000000) + return; + + cmd.handle = cpu_to_le16(0x0000); + cmd.type = 0x00; + + bt_hci_send(hci_dev, BT_HCI_CMD_READ_CLOCK, &cmd, sizeof(cmd), + NULL, NULL, NULL); +} + +static void ext_inquiry_result(const void *data, uint8_t size, void *user_data) +{ + const struct bt_hci_evt_ext_inquiry_result *evt = data; + + if (evt->dev_class[0] != 0x3c || evt->dev_class[1] != 0x04 + || evt->dev_class[2] != 0x08) + return; + + if (evt->data[0]) { + struct bt_hci_cmd_truncated_page cmd; + + printf("Found 3D display\n"); + + bt_hci_send(hci_dev, BT_HCI_CMD_INQUIRY_CANCEL, NULL, 0, + NULL, NULL, NULL); + + memcpy(cmd.bdaddr, evt->bdaddr, 6); + cmd.pscan_rep_mode = evt->pscan_rep_mode; + cmd.clock_offset = evt->clock_offset; + + bt_hci_send(hci_dev, BT_HCI_CMD_TRUNCATED_PAGE, + &cmd, sizeof(cmd), NULL, NULL, NULL); + } +} + +static void inquiry_complete(const void *data, uint8_t size, void *user_data) +{ + printf("No 3D display found\n"); + + start_inquiry(); +} + +static void read_local_version(const void *data, uint8_t size, void *user_data) +{ + const struct bt_hci_rsp_read_local_version *rsp = data; + + if (rsp->status) { + printf("Failed to read local version information\n"); + shutdown_device(); + return; + } + + if (rsp->manufacturer == 15) { + printf("Enabling receiver workaround for Broadcom\n"); + + bt_hci_register(hci_dev, BT_HCI_EVT_SYNC_TRAIN_RECEIVED, + brcm_sync_train_received, NULL, NULL); + } else { + bt_hci_register(hci_dev, BT_HCI_EVT_SYNC_TRAIN_RECEIVED, + sync_train_received, NULL, NULL); + } +} + +static void start_glasses(void) +{ + uint8_t evtmask1[] = { 0x03, 0xe0, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00 }; + uint8_t evtmask2[] = { 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00 }; + uint8_t inqmode = 0x02; + + if (reset_on_init) { + bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, + NULL, NULL, NULL); + bt_hci_send(hci_dev, BT_HCI_CMD_SET_EVENT_MASK, evtmask1, 8, + NULL, NULL, NULL); + } + + bt_hci_send(hci_dev, BT_HCI_CMD_READ_LOCAL_VERSION, NULL, 0, + read_local_version, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_SET_EVENT_MASK_PAGE2, evtmask2, 8, + NULL, NULL, NULL); + bt_hci_send(hci_dev, BT_HCI_CMD_WRITE_INQUIRY_MODE, &inqmode, 1, + NULL, NULL, NULL); + + bt_hci_register(hci_dev, BT_HCI_EVT_INQUIRY_COMPLETE, + inquiry_complete, NULL, NULL); + bt_hci_register(hci_dev, BT_HCI_EVT_EXT_INQUIRY_RESULT, + ext_inquiry_result, NULL, NULL); + + bt_hci_register(hci_dev, BT_HCI_EVT_TRUNCATED_PAGE_COMPLETE, + truncated_page_complete, NULL, NULL); + bt_hci_register(hci_dev, BT_HCI_EVT_SLAVE_BROADCAST_TIMEOUT, + slave_broadcast_timeout, NULL, NULL); + bt_hci_register(hci_dev, BT_HCI_EVT_SLAVE_BROADCAST_RECEIVE, + slave_broadcast_receive, NULL, NULL); + + start_inquiry(); +} + +static bool sync_train_active = false; + +static void sync_train_complete(const void *data, uint8_t size, + void *user_data) +{ + sync_train_active = false; +} + +static void start_sync_train(void) +{ + struct bt_hci_cmd_write_sync_train_params cmd; + + if (sync_train_active) + return; + + printf("Starting new synchronization train\n"); + + cmd.min_interval = cpu_to_le16(0x0050); + cmd.max_interval = cpu_to_le16(0x00a0); + cmd.timeout = cpu_to_le32(0x0002ee00); /* 120 sec */ + cmd.service_data = SERVICE_DATA; + + bt_hci_send(hci_dev, BT_HCI_CMD_WRITE_SYNC_TRAIN_PARAMS, + &cmd, sizeof(cmd), NULL, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_START_SYNC_TRAIN, NULL, 0, + NULL, NULL, NULL); + + sync_train_active = true; +} + +static void conn_request(const void *data, uint8_t size, void *user_data) +{ + const struct bt_hci_evt_conn_request *evt = data; + struct bt_hci_cmd_accept_conn_request cmd; + + printf("Incoming connection from 3D glasses\n"); + + memcpy(cmd.bdaddr, evt->bdaddr, 6); + cmd.role = 0x00; + + bt_hci_send(hci_dev, BT_HCI_CMD_ACCEPT_CONN_REQUEST, &cmd, sizeof(cmd), + NULL, NULL, NULL); + + start_sync_train(); +} + +static void slave_page_response_timeout(const void *data, uint8_t size, + void *user_data) +{ + printf("Incoming truncated page received\n"); + + start_sync_train(); +} + +static void slave_broadcast_channel_map_change(const void *data, uint8_t size, + void *user_data) +{ + printf("Broadcast channel map changed\n"); + + start_sync_train(); +} + +static void inquiry_resp_tx_power(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_read_inquiry_resp_tx_power *rsp = data; + struct bt_hci_cmd_write_ext_inquiry_response cmd; + uint8_t inqdata[] = { 0x03, 0x3d, 0x03, 0x43, 0x02, 0x0a, 0x00, 0x00 }; + uint8_t devclass[] = { 0x3c, 0x04, 0x08 }; + uint8_t scanmode = 0x03; + + inqdata[6] = (uint8_t) rsp->level; + + cmd.fec = 0x00; + memset(cmd.data, 0, sizeof(cmd.data)); + memcpy(cmd.data, inqdata, sizeof(inqdata)); + + bt_hci_send(hci_dev, BT_HCI_CMD_WRITE_EXT_INQUIRY_RESPONSE, + &cmd, sizeof(cmd), NULL, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_WRITE_CLASS_OF_DEV, devclass, 3, + NULL, NULL, NULL); + bt_hci_send(hci_dev, BT_HCI_CMD_WRITE_SCAN_ENABLE, &scanmode, 1, + NULL, NULL, NULL); +} + +static void read_clock(const void *data, uint8_t size, void *user_data) +{ + const struct bt_hci_rsp_read_clock *rsp = data; + struct broadcast_message msg; + uint8_t bcastdata[sizeof(msg) + 3] = { LT_ADDR, 0x03, 0x11, }; + + if (rsp->status) { + printf("Failed to read local clock information\n"); + shutdown_device(); + return; + } + + msg.frame_sync_instant = rsp->clock; + msg.bluetooth_clock_phase = rsp->accuracy; + msg.left_open_offset = cpu_to_le16(50); + msg.left_close_offset = cpu_to_le16(300); + msg.right_open_offset = cpu_to_le16(350); + msg.right_close_offset = cpu_to_le16(600); + msg.frame_sync_period = cpu_to_le16(650); + msg.frame_sync_period_fraction = 0; + memcpy(bcastdata + 3, &msg, sizeof(msg)); + + bt_hci_send(hci_dev, BT_HCI_CMD_SET_SLAVE_BROADCAST_DATA, + bcastdata, sizeof(bcastdata), NULL, NULL, NULL); +} + +static void set_slave_broadcast(const void *data, uint8_t size, void *user_data) +{ + const struct bt_hci_rsp_set_slave_broadcast *rsp = data; + struct bt_hci_cmd_read_clock cmd; + + if (rsp->status) { + printf("Failed to set slave broadcast transmission\n"); + shutdown_device(); + return; + } + + cmd.handle = cpu_to_le16(0x0000); + cmd.type = 0x00; + + bt_hci_send(hci_dev, BT_HCI_CMD_READ_CLOCK, &cmd, sizeof(cmd), + read_clock, NULL, NULL); +} + +static void start_display(void) +{ + struct bt_hci_cmd_set_slave_broadcast cmd; + uint8_t evtmask1[] = { 0x1c, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + uint8_t evtmask2[] = { 0x00, 0xc0, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00 }; + uint8_t sspmode = 0x01; + uint8_t ltaddr = LT_ADDR; + + if (reset_on_init) { + bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, + NULL, NULL, NULL); + bt_hci_send(hci_dev, BT_HCI_CMD_SET_EVENT_MASK, evtmask1, 8, + NULL, NULL, NULL); + } + + bt_hci_send(hci_dev, BT_HCI_CMD_SET_EVENT_MASK_PAGE2, evtmask2, 8, + NULL, NULL, NULL); + bt_hci_send(hci_dev, BT_HCI_CMD_WRITE_SIMPLE_PAIRING_MODE, &sspmode, 1, + NULL, NULL, NULL); + bt_hci_send(hci_dev, BT_HCI_CMD_SET_RESERVED_LT_ADDR, <addr, 1, + NULL, NULL, NULL); + bt_hci_send(hci_dev, BT_HCI_CMD_READ_SYNC_TRAIN_PARAMS, NULL, 0, + NULL, NULL, NULL); + + bt_hci_register(hci_dev, BT_HCI_EVT_CONN_REQUEST, + conn_request, NULL, NULL); + + bt_hci_register(hci_dev, BT_HCI_EVT_SLAVE_PAGE_RESPONSE_TIMEOUT, + slave_page_response_timeout, NULL, NULL); + bt_hci_register(hci_dev, BT_HCI_EVT_SLAVE_BROADCAST_CHANNEL_MAP_CHANGE, + slave_broadcast_channel_map_change, NULL, NULL); + bt_hci_register(hci_dev, BT_HCI_EVT_SYNC_TRAIN_COMPLETE, + sync_train_complete, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_READ_INQUIRY_RESP_TX_POWER, NULL, 0, + inquiry_resp_tx_power, NULL, NULL); + + cmd.enable = 0x01; + cmd.lt_addr = LT_ADDR; + cmd.lpo_allowed = 0x01; + cmd.pkt_type = cpu_to_le16(PKT_TYPE); + cmd.min_interval = cpu_to_le16(0x0050); /* 50 ms */ + cmd.max_interval = cpu_to_le16(0x00a0); /* 100 ms */ + cmd.timeout = cpu_to_le16(0xfffe); + + bt_hci_send(hci_dev, BT_HCI_CMD_SET_SLAVE_BROADCAST, &cmd, sizeof(cmd), + set_slave_broadcast, NULL, NULL); +} + +static void signal_callback(int signum, void *user_data) +{ + static bool terminated = false; + + switch (signum) { + case SIGINT: + case SIGTERM: + if (!terminated) { + shutdown_device(); + terminated = true; + } + break; + } +} + +static void usage(void) +{ + printf("3dsp - 3D Synchronization Profile testing\n" + "Usage:\n"); + printf("\t3dsp [options]\n"); + printf("options:\n" + "\t-D, --display Use display role\n" + "\t-G, --glasses Use glasses role\n" + "\t-i, --index Use specified controller\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "display", no_argument, NULL, 'D' }, + { "glasses", no_argument, NULL, 'G' }, + { "index", required_argument, NULL, 'i' }, + { "raw", no_argument, NULL, 'r' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + bool display_role = false, glasses_role = false; + uint16_t index = 0; + const char *str; + bool use_raw = false; + int exit_status; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "DGi:rvh", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'D': + display_role = true; + break; + case 'G': + glasses_role = true; + break; + case 'i': + if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3)) + str = optarg + 3; + else + str = optarg; + if (!isdigit(*str)) { + usage(); + return EXIT_FAILURE; + } + index = atoi(str); + break; + case 'r': + use_raw = true; + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind > 0) { + fprintf(stderr, "Invalid command line parameters\n"); + return EXIT_FAILURE; + } + + if (display_role == glasses_role) { + fprintf(stderr, "Specify either display or glasses role\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + printf("3D Synchronization Profile testing ver %s\n", VERSION); + + if (use_raw) { + hci_dev = bt_hci_new_raw_device(index); + if (!hci_dev) { + fprintf(stderr, "Failed to open HCI raw device\n"); + return EXIT_FAILURE; + } + } else { + hci_dev = bt_hci_new_user_channel(index); + if (!hci_dev) { + fprintf(stderr, "Failed to open HCI user channel\n"); + return EXIT_FAILURE; + } + + reset_on_init = true; + reset_on_shutdown = true; + } + + if (display_role) + start_display(); + else if (glasses_role) + start_glasses(); + + exit_status = mainloop_run_with_signal(signal_callback, NULL); + + bt_hci_unref(hci_dev); + + return exit_status; +} diff --git a/tools/advtest.c b/tools/advtest.c new file mode 100644 index 0000000..050b570 --- /dev/null +++ b/tools/advtest.c @@ -0,0 +1,430 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "lib/bluetooth.h" +#include "lib/mgmt.h" + +#include "monitor/bt.h" +#include "src/shared/mainloop.h" +#include "src/shared/util.h" +#include "src/shared/mgmt.h" +#include "src/shared/hci.h" +#include "src/shared/crypto.h" + +#define PEER_ADDR_TYPE 0x00 +#define PEER_ADDR "\x00\x00\x00\x00\x00\x00" + +#define ADV_IRK "\x69\x30\xde\xc3\x8f\x84\x74\x14" \ + "\xe1\x23\x99\xc1\xca\x9a\xc3\x31" +#define SCAN_IRK "\xfa\x73\x09\x11\x3f\x03\x37\x0f" \ + "\xf4\xf9\x93\x1e\xf9\xa3\x63\xa6" + +static struct mgmt *mgmt; +static uint16_t index1 = MGMT_INDEX_NONE; +static uint16_t index2 = MGMT_INDEX_NONE; + +static struct bt_crypto *crypto; +static struct bt_hci *adv_dev; +static struct bt_hci *scan_dev; + +static void print_rpa(const uint8_t addr[6]) +{ + printf(" Address: %02x:%02x:%02x:%02x:%02x:%02x\n", + addr[5], addr[4], addr[3], + addr[2], addr[1], addr[0]); + printf(" Random: %02x%02x%02x\n", addr[3], addr[4], addr[5]); + printf(" Hash: %02x%02x%02x\n", addr[0], addr[1], addr[2]); +} + +static void scan_le_adv_report(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_evt_le_adv_report *evt = data; + + if (evt->addr_type == 0x01 && (evt->addr[5] & 0xc0) == 0x40) { + uint8_t hash[3], irk[16]; + + memcpy(irk, ADV_IRK, 16); + bt_crypto_ah(crypto, irk, evt->addr + 3, hash); + + if (!memcmp(evt->addr, hash, 3)) { + printf("Received advertising report\n"); + print_rpa(evt->addr); + + memcpy(irk, ADV_IRK, 16); + bt_crypto_ah(crypto, irk, evt->addr + 3, hash); + + printf(" -> Computed hash: %02x%02x%02x\n", + hash[0], hash[1], hash[2]); + + mainloop_quit(); + } + } +} + +static void scan_le_meta_event(const void *data, uint8_t size, + void *user_data) +{ + uint8_t evt_code = ((const uint8_t *) data)[0]; + + switch (evt_code) { + case BT_HCI_EVT_LE_ADV_REPORT: + scan_le_adv_report(data + 1, size - 1, user_data); + break; + } +} + +static void scan_enable_callback(const void *data, uint8_t size, + void *user_data) +{ +} + +static void adv_enable_callback(const void *data, uint8_t size, + void *user_data) +{ + struct bt_hci_cmd_le_set_scan_parameters cmd4; + struct bt_hci_cmd_le_set_scan_enable cmd5; + + cmd4.type = 0x00; /* Passive scanning */ + cmd4.interval = cpu_to_le16(0x0010); + cmd4.window = cpu_to_le16(0x0010); + cmd4.own_addr_type = 0x00; /* Use public address */ + cmd4.filter_policy = 0x00; + + bt_hci_send(scan_dev, BT_HCI_CMD_LE_SET_SCAN_PARAMETERS, + &cmd4, sizeof(cmd4), NULL, NULL, NULL); + + cmd5.enable = 0x01; + cmd5.filter_dup = 0x01; + + bt_hci_send(scan_dev, BT_HCI_CMD_LE_SET_SCAN_ENABLE, + &cmd5, sizeof(cmd5), + scan_enable_callback, NULL, NULL); +} + +static void adv_le_evtmask_callback(const void *data, uint8_t size, + void *user_data) +{ + struct bt_hci_cmd_le_set_resolv_timeout cmd0; + struct bt_hci_cmd_le_add_to_resolv_list cmd1; + struct bt_hci_cmd_le_set_resolv_enable cmd2; + struct bt_hci_cmd_le_set_random_address cmd3; + struct bt_hci_cmd_le_set_adv_parameters cmd4; + struct bt_hci_cmd_le_set_adv_enable cmd5; + + cmd0.timeout = cpu_to_le16(0x0384); + + bt_hci_send(adv_dev, BT_HCI_CMD_LE_SET_RESOLV_TIMEOUT, + &cmd0, sizeof(cmd0), NULL, NULL, NULL); + + cmd1.addr_type = PEER_ADDR_TYPE; + memcpy(cmd1.addr, PEER_ADDR, 6); + memset(cmd1.peer_irk, 0, 16); + memcpy(cmd1.local_irk, ADV_IRK, 16); + + bt_hci_send(adv_dev, BT_HCI_CMD_LE_ADD_TO_RESOLV_LIST, + &cmd1, sizeof(cmd1), NULL, NULL, NULL); + + cmd2.enable = 0x01; + + bt_hci_send(adv_dev, BT_HCI_CMD_LE_SET_RESOLV_ENABLE, + &cmd2, sizeof(cmd2), NULL, NULL, NULL); + + bt_crypto_random_bytes(crypto, cmd3.addr + 3, 3); + cmd3.addr[5] &= 0x3f; /* Clear two most significant bits */ + cmd3.addr[5] |= 0x40; /* Set second most significant bit */ + bt_crypto_ah(crypto, cmd1.local_irk, cmd3.addr + 3, cmd3.addr); + + bt_hci_send(adv_dev, BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, + &cmd3, sizeof(cmd3), NULL, NULL, NULL); + + printf("Setting advertising address\n"); + print_rpa(cmd3.addr); + + cmd4.min_interval = cpu_to_le16(0x0800); + cmd4.max_interval = cpu_to_le16(0x0800); + cmd4.type = 0x03; /* Non-connectable advertising */ + cmd4.own_addr_type = 0x03; /* Local IRK, random address fallback */ + cmd4.direct_addr_type = PEER_ADDR_TYPE; + memcpy(cmd4.direct_addr, PEER_ADDR, 6); + cmd4.channel_map = 0x07; + cmd4.filter_policy = 0x00; + + bt_hci_send(adv_dev, BT_HCI_CMD_LE_SET_ADV_PARAMETERS, + &cmd4, sizeof(cmd4), NULL, NULL, NULL); + + cmd5.enable = 0x01; + + bt_hci_send(adv_dev, BT_HCI_CMD_LE_SET_ADV_ENABLE, + &cmd5, sizeof(cmd5), + adv_enable_callback, NULL, NULL); +} + +static void adv_le_features_callback(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_le_read_local_features *rsp = data; + uint8_t evtmask[] = { 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + if (rsp->status) { + fprintf(stderr, "Failed to read local LE features\n"); + mainloop_exit_failure(); + return; + } + + bt_hci_send(adv_dev, BT_HCI_CMD_LE_SET_EVENT_MASK, evtmask, 8, + adv_le_evtmask_callback, NULL, NULL); +} + +static void adv_features_callback(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_read_local_features *rsp = data; + uint8_t evtmask[] = { 0x90, 0xe8, 0x04, 0x02, 0x00, 0x80, 0x00, 0x20 }; + + if (rsp->status) { + fprintf(stderr, "Failed to read local features\n"); + mainloop_exit_failure(); + return; + } + + if (!(rsp->features[4] & 0x40)) { + fprintf(stderr, "Controller without Low Energy support\n"); + mainloop_exit_failure(); + return; + } + + bt_hci_send(adv_dev, BT_HCI_CMD_SET_EVENT_MASK, evtmask, 8, + NULL, NULL, NULL); + + bt_hci_send(adv_dev, BT_HCI_CMD_LE_READ_LOCAL_FEATURES, NULL, 0, + adv_le_features_callback, NULL, NULL); +} + +static void scan_le_evtmask_callback(const void *data, uint8_t size, + void *user_data) +{ + bt_hci_send(adv_dev, BT_HCI_CMD_RESET, NULL, 0, NULL, NULL, NULL); + + bt_hci_send(adv_dev, BT_HCI_CMD_READ_LOCAL_FEATURES, NULL, 0, + adv_features_callback, NULL, NULL); +} + +static void scan_le_features_callback(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_le_read_local_features *rsp = data; + uint8_t evtmask[] = { 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + if (rsp->status) { + fprintf(stderr, "Failed to read local LE features\n"); + mainloop_exit_failure(); + return; + } + + bt_hci_send(adv_dev, BT_HCI_CMD_LE_SET_EVENT_MASK, evtmask, 8, + scan_le_evtmask_callback, NULL, NULL); +} + +static void scan_features_callback(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_read_local_features *rsp = data; + uint8_t evtmask[] = { 0x90, 0xe8, 0x04, 0x02, 0x00, 0x80, 0x00, 0x20 }; + + if (rsp->status) { + fprintf(stderr, "Failed to read local features\n"); + mainloop_exit_failure(); + return; + } + + if (!(rsp->features[4] & 0x40)) { + fprintf(stderr, "Controller without Low Energy support\n"); + mainloop_exit_failure(); + return; + } + + bt_hci_send(scan_dev, BT_HCI_CMD_SET_EVENT_MASK, evtmask, 8, + NULL, NULL, NULL); + + bt_hci_send(scan_dev, BT_HCI_CMD_LE_READ_LOCAL_FEATURES, NULL, 0, + scan_le_features_callback, NULL, NULL); +} + +static void read_index_list(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_index_list *rp = param; + uint16_t count; + int i; + + if (status) { + fprintf(stderr, "Reading index list failed: %s\n", + mgmt_errstr(status)); + mainloop_exit_failure(); + return; + } + + count = le16_to_cpu(rp->num_controllers); + + if (count < 2) { + fprintf(stderr, "At least 2 controllers are required\n"); + mainloop_exit_failure(); + return; + } + + for (i = 0; i < count; i++) { + uint16_t index = cpu_to_le16(rp->index[i]); + + if (index < index1) + index1 = index; + } + + for (i = 0; i < count; i++) { + uint16_t index = cpu_to_le16(rp->index[i]); + + if (index < index2 && index > index1) + index2 = index; + } + + printf("Selecting index %u for advertiser\n", index1); + printf("Selecting index %u for scanner\n", index2); + + crypto = bt_crypto_new(); + if (!crypto) { + fprintf(stderr, "Failed to open crypto interface\n"); + mainloop_exit_failure(); + return; + } + + adv_dev = bt_hci_new_user_channel(index1); + if (!adv_dev) { + fprintf(stderr, "Failed to open HCI for advertiser\n"); + mainloop_exit_failure(); + return; + } + + scan_dev = bt_hci_new_user_channel(index2); + if (!scan_dev) { + fprintf(stderr, "Failed to open HCI for scanner\n"); + mainloop_exit_failure(); + return; + } + + bt_hci_register(scan_dev, BT_HCI_EVT_LE_META_EVENT, + scan_le_meta_event, NULL, NULL); + + bt_hci_send(scan_dev, BT_HCI_CMD_RESET, NULL, 0, NULL, NULL, NULL); + + bt_hci_send(scan_dev, BT_HCI_CMD_READ_LOCAL_FEATURES, NULL, 0, + scan_features_callback, NULL, NULL); +} + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + } +} + +static void usage(void) +{ + printf("advtest - Advertising testing\n" + "Usage:\n"); + printf("\tadvtest [options]\n"); + printf("options:\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc ,char *argv[]) +{ + int exit_status; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "vh", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind > 0) { + fprintf(stderr, "Invalid command line parameters\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + mgmt = mgmt_new_default(); + if (!mgmt) { + fprintf(stderr, "Failed to open management socket\n"); + return EXIT_FAILURE; + } + + if (!mgmt_send(mgmt, MGMT_OP_READ_INDEX_LIST, + MGMT_INDEX_NONE, 0, NULL, + read_index_list, NULL, NULL)) { + fprintf(stderr, "Failed to read index list\n"); + exit_status = EXIT_FAILURE; + goto done; + } + + exit_status = mainloop_run_with_signal(signal_callback, NULL); + + bt_hci_unref(adv_dev); + bt_hci_unref(scan_dev); + + bt_crypto_unref(crypto); + +done: + mgmt_unref(mgmt); + + return exit_status; +} diff --git a/tools/amptest.c b/tools/amptest.c new file mode 100644 index 0000000..5574707 --- /dev/null +++ b/tools/amptest.c @@ -0,0 +1,668 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +static int activate_amp_controller(int dev_id) +{ + struct hci_dev_info di; + struct hci_filter flt; + int fd; + + printf("hci%d: Activating controller\n", dev_id); + + fd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (fd < 0) { + perror("Failed to open raw HCI socket"); + return -1; + } + + di.dev_id = dev_id; + + if (ioctl(fd, HCIGETDEVINFO, (void *) &di) < 0) { + perror("Failed to get HCI device info"); + close(fd); + return -1; + } + + if (!hci_test_bit(HCI_UP, &di.flags)) { + if (ioctl(fd, HCIDEVUP, dev_id) < 0) { + if (errno != EALREADY) { + perror("Failed to bring up HCI device"); + close(fd); + return -1; + } + } + } + + close(fd); + + fd = hci_open_dev(dev_id); + if (fd < 0) { + perror("Failed to open HCI device"); + return -1; + } + + hci_filter_clear(&flt); + hci_filter_set_ptype(HCI_EVENT_PKT, &flt); + hci_filter_set_event(EVT_CHANNEL_SELECTED, &flt); + hci_filter_set_event(EVT_PHYSICAL_LINK_COMPLETE, &flt); + hci_filter_set_event(EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE, &flt); + + if (setsockopt(fd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + perror("Failed to setup HCI device filter"); + close(fd); + return -1; + } + + return fd; +} + +static bool read_local_amp_info(int dev_id, uint16_t *max_assoc_len) +{ + read_local_amp_info_rp rp; + struct hci_request rq; + int fd; + + printf("hci%d: Reading local AMP information\n", dev_id); + + fd = hci_open_dev(dev_id); + if (fd < 0) { + perror("Failed to open HCI device"); + return false; + } + + memset(&rp, 0, sizeof(rp)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_STATUS_PARAM; + rq.ocf = OCF_READ_LOCAL_AMP_INFO; + rq.rparam = &rp; + rq.rlen = READ_LOCAL_AMP_INFO_RP_SIZE; + + if (hci_send_req(fd, &rq, 1000) < 0) { + perror("Failed sending HCI request"); + hci_close_dev(fd); + return false; + } + + if (rp.status) { + fprintf(stderr, "Failed HCI command: 0x%02x\n", rp.status); + hci_close_dev(fd); + return false; + } + + printf("\tAMP status: 0x%02x\n", rp.amp_status); + printf("\tController type: 0x%02x\n", rp.controller_type); + printf("\tMax ASSOC length: %d\n", btohs(rp.max_amp_assoc_length)); + + *max_assoc_len = btohs(rp.max_amp_assoc_length); + + hci_close_dev(fd); + + return true; +} + +static bool read_local_amp_assoc(int dev_id, uint8_t phy_handle, + uint16_t max_assoc_len, + uint8_t *assoc_data, + uint16_t *assoc_len) +{ + read_local_amp_assoc_cp cp; + read_local_amp_assoc_rp rp; + struct hci_request rq; + int fd; + + printf("hci%d: Reading local AMP association\n", dev_id); + + fd = hci_open_dev(dev_id); + if (fd < 0) { + perror("Failed to open HCI device"); + return false; + } + + memset(&cp, 0, sizeof(cp)); + cp.handle = phy_handle; + cp.length_so_far = htobs(0); + cp.assoc_length = htobs(max_assoc_len); + memset(&rp, 0, sizeof(rp)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_STATUS_PARAM; + rq.ocf = OCF_READ_LOCAL_AMP_ASSOC; + rq.cparam = &cp; + rq.clen = READ_LOCAL_AMP_ASSOC_CP_SIZE; + rq.rparam = &rp; + rq.rlen = READ_LOCAL_AMP_ASSOC_RP_SIZE; + + if (hci_send_req(fd, &rq, 1000) < 0) { + perror("Failed sending HCI request"); + hci_close_dev(fd); + return false; + } + + if (rp.status) { + fprintf(stderr, "Failed HCI command: 0x%02x\n", rp.status); + hci_close_dev(fd); + return false; + } + + printf("\tRemain ASSOC length: %d\n", btohs(rp.length)); + + *assoc_len = btohs(rp.length); + memcpy(assoc_data, rp.fragment, *assoc_len); + + hci_close_dev(fd); + + return true; +} + +static bool write_remote_amp_assoc(int dev_id, uint8_t phy_handle, + uint8_t *assoc_data, + uint16_t assoc_len) +{ + write_remote_amp_assoc_cp cp; + write_remote_amp_assoc_rp rp; + struct hci_request rq; + int fd; + + printf("hci%d: Writing remote AMP association\n", dev_id); + + fd = hci_open_dev(dev_id); + if (fd < 0) { + perror("Failed to open HCI device"); + return false; + } + + memset(&cp, 0, sizeof(cp)); + cp.handle = phy_handle; + cp.length_so_far = htobs(0); + cp.remaining_length = htobs(assoc_len); + memcpy(cp.fragment, assoc_data, assoc_len); + memset(&rp, 0, sizeof(rp)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_STATUS_PARAM; + rq.ocf = OCF_WRITE_REMOTE_AMP_ASSOC; + rq.cparam = &cp; + rq.clen = 5 + assoc_len; + rq.rparam = &rp; + rq.rlen = WRITE_REMOTE_AMP_ASSOC_RP_SIZE; + + if (hci_send_req(fd, &rq, 1000) < 0) { + perror("Failed sending HCI request"); + hci_close_dev(fd); + return false; + } + + if (rp.status) { + fprintf(stderr, "Failed HCI command: 0x%02x\n", rp.status); + hci_close_dev(fd); + return false; + } + + hci_close_dev(fd); + + return true; +} + +static bool channel_selected_event(int dev_id, int fd, uint8_t phy_handle) +{ + printf("hci%d: Waiting for channel selected event\n", dev_id); + + while (1) { + uint8_t buf[HCI_MAX_EVENT_SIZE]; + hci_event_hdr *hdr; + struct pollfd p; + int n, len; + + p.fd = fd; + p.events = POLLIN; + + n = poll(&p, 1, 10000); + if (n < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + perror("Failed to poll HCI device"); + return false; + } + + if (n == 0) { + fprintf(stderr, "Failure to receive event\n"); + return false; + } + + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + perror("Failed to read from HCI device"); + return false; + } + + hdr = (void *) (buf + 1); + + if (hdr->evt == EVT_CHANNEL_SELECTED) + break; + } + + return true; +} + +static bool create_physical_link(int dev_id, uint8_t phy_handle) +{ + create_physical_link_cp cp; + evt_cmd_status evt; + struct hci_request rq; + int i, fd; + + printf("hci%d: Creating physical link\n", dev_id); + + fd = hci_open_dev(dev_id); + if (fd < 0) { + perror("Failed to open HCI device"); + return false; + } + + memset(&cp, 0, sizeof(cp)); + cp.handle = phy_handle; + cp.key_length = 32; + cp.key_type = 0x03; + for (i = 0; i < cp.key_length; i++) + cp.key[i] = 0x23; + memset(&evt, 0, sizeof(evt)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_CREATE_PHYSICAL_LINK; + rq.event = EVT_CMD_STATUS; + rq.cparam = &cp; + rq.clen = CREATE_PHYSICAL_LINK_CP_SIZE; + rq.rparam = &evt; + rq.rlen = EVT_CMD_STATUS_SIZE; + + if (hci_send_req(fd, &rq, 1000) < 0) { + perror("Failed sending HCI request"); + hci_close_dev(fd); + return false; + } + + if (evt.status) { + fprintf(stderr, "Failed HCI command: 0x%02x\n", evt.status); + hci_close_dev(fd); + return false; + } + + hci_close_dev(fd); + + return true; +} + +static bool accept_physical_link(int dev_id, uint8_t phy_handle) +{ + accept_physical_link_cp cp; + evt_cmd_status evt; + struct hci_request rq; + int i, fd; + + printf("hci%d: Accepting physical link\n", dev_id); + + fd = hci_open_dev(dev_id); + if (fd < 0) { + perror("Failed to open HCI device"); + return false; + } + + memset(&cp, 0, sizeof(cp)); + cp.handle = phy_handle; + cp.key_length = 32; + cp.key_type = 0x03; + for (i = 0; i < cp.key_length; i++) + cp.key[i] = 0x23; + memset(&evt, 0, sizeof(evt)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_ACCEPT_PHYSICAL_LINK; + rq.event = EVT_CMD_STATUS; + rq.cparam = &cp; + rq.clen = ACCEPT_PHYSICAL_LINK_CP_SIZE; + rq.rparam = &evt; + rq.rlen = EVT_CMD_STATUS_SIZE; + + if (hci_send_req(fd, &rq, 1000) < 0) { + perror("Failed sending HCI request"); + hci_close_dev(fd); + return false; + } + + if (evt.status) { + fprintf(stderr, "Failed HCI command: 0x%02x\n", evt.status); + hci_close_dev(fd); + return false; + } + + hci_close_dev(fd); + + return true; +} + +static bool disconnect_physical_link(int dev_id, uint8_t phy_handle, + uint8_t reason) +{ + disconnect_physical_link_cp cp; + evt_cmd_status evt; + struct hci_request rq; + int fd; + + printf("hci%d: Disconnecting physical link\n", dev_id); + + fd = hci_open_dev(dev_id); + if (fd < 0) { + perror("Failed to open HCI device"); + return false; + } + + memset(&cp, 0, sizeof(cp)); + cp.handle = phy_handle; + cp.reason = reason; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_DISCONNECT_PHYSICAL_LINK; + rq.event = EVT_CMD_STATUS; + rq.cparam = &cp; + rq.clen = DISCONNECT_PHYSICAL_LINK_CP_SIZE; + rq.rparam = &evt; + rq.rlen = EVT_CMD_STATUS_SIZE; + + if (hci_send_req(fd, &rq, 1000) < 0) { + perror("Failed sending HCI request"); + hci_close_dev(fd); + return false; + } + + if (evt.status) { + fprintf(stderr, "Failed HCI command: 0x%02x\n", evt.status); + hci_close_dev(fd); + return false; + } + + hci_close_dev(fd); + + return true; +} + +static bool physical_link_complete_event(int dev_id, int fd, + uint8_t phy_handle) +{ + printf("hci%d: Waiting for physical link complete event\n", dev_id); + + while (1) { + uint8_t buf[HCI_MAX_EVENT_SIZE]; + hci_event_hdr *hdr; + int len; + + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + perror("Failed to read from HCI device"); + return false; + } + + hdr = (void *) (buf + 1); + + if (hdr->evt == EVT_PHYSICAL_LINK_COMPLETE) + break; + } + + return true; +} + +static bool disconnect_physical_link_complete_event(int dev_id, int fd, + uint8_t phy_handle) +{ + printf("hci%d: Waiting for physical link disconnect event\n", dev_id); + + while (1) { + uint8_t buf[HCI_MAX_EVENT_SIZE]; + hci_event_hdr *hdr; + int len; + + len = read(fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + perror("Failed to read from HCI device"); + return false; + } + + hdr = (void *) (buf + 1); + + if (hdr->evt == EVT_DISCONNECT_PHYSICAL_LINK_COMPLETE) + break; + } + + return true; +} + +static int amp1_dev_id = -1; +static int amp2_dev_id = -1; + +static bool find_amp_controller(void) +{ + struct hci_dev_list_req *dl; + struct hci_dev_req *dr; + int fd, i; + bool result; + + fd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (fd < 0) { + perror("Failed to open raw HCI socket"); + return false; + } + + dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)); + if (!dl) { + perror("Failed allocate HCI device request memory"); + close(fd); + return false; + } + + dl->dev_num = HCI_MAX_DEV; + dr = dl->dev_req; + + if (ioctl(fd, HCIGETDEVLIST, (void *) dl) < 0) { + perror("Failed to get HCI device list"); + result = false; + goto done; + } + + for (i = 0; i< dl->dev_num; i++) { + struct hci_dev_info di; + + di.dev_id = (dr + i)->dev_id; + + if (ioctl(fd, HCIGETDEVINFO, (void *) &di) < 0) + continue; + + if (((di.type & 0x30) >> 4) != HCI_AMP) + continue; + + if (amp1_dev_id < 0) + amp1_dev_id = di.dev_id; + else if (amp2_dev_id < 0) { + if (di.dev_id < amp1_dev_id) { + amp2_dev_id = amp1_dev_id; + amp1_dev_id = di.dev_id; + } else + amp2_dev_id = di.dev_id; + } + } + + result = true; + +done: + free(dl); + close(fd); + return result; +} + +int main(int argc ,char *argv[]) +{ + int amp1_event_fd, amp2_event_fd; + uint16_t amp1_max_assoc_len, amp2_max_assoc_len; + uint8_t *amp1_assoc_data, *amp2_assoc_data; + uint16_t amp1_assoc_len, amp2_assoc_len; + uint8_t amp1_phy_handle, amp2_phy_handle; + + if (!find_amp_controller()) + return EXIT_FAILURE; + + if (amp1_dev_id < 0 || amp2_dev_id < 0) { + fprintf(stderr, "Two AMP controllers are required\n"); + return EXIT_FAILURE; + } + + printf("hci%d: AMP initiator\n", amp1_dev_id); + printf("hci%d: AMP acceptor\n", amp2_dev_id); + + amp1_event_fd = activate_amp_controller(amp1_dev_id); + if (amp1_event_fd < 0) + return EXIT_FAILURE; + + amp2_event_fd = activate_amp_controller(amp2_dev_id); + if (amp2_event_fd < 0) { + hci_close_dev(amp1_event_fd); + return EXIT_FAILURE; + } + + if (!read_local_amp_info(amp1_dev_id, &1_max_assoc_len)) + return EXIT_FAILURE; + + amp1_assoc_data = alloca(amp1_max_assoc_len); + + printf("--> AMP_Get_Info_Request (Amp_ID B)\n"); + + if (!read_local_amp_info(amp2_dev_id, &2_max_assoc_len)) + return EXIT_FAILURE; + + amp2_assoc_data = alloca(amp2_max_assoc_len); + + printf("<-- AMP_Get_Info_Response (Amp_ID B, Status)\n"); + + printf("--> AMP_Get_AMP_Assoc_Request (Amp_ID B)\n"); + + if (!read_local_amp_assoc(amp2_dev_id, 0x00, amp2_max_assoc_len, + amp2_assoc_data, &2_assoc_len)) + return EXIT_FAILURE; + + printf("<-- AMP_Get_AMP_Assoc_Response (Amp_ID B, AMP_Assoc B)\n"); + + amp1_phy_handle = 0x04; + + if (!create_physical_link(amp1_dev_id, amp1_phy_handle)) + return EXIT_FAILURE; + + if (!write_remote_amp_assoc(amp1_dev_id, amp1_phy_handle, + amp2_assoc_data, amp2_assoc_len)) + return EXIT_FAILURE; + + printf("hci%d: Signal MAC to scan\n", amp1_dev_id); + + printf("hci%d: Signal MAC to start\n", amp1_dev_id); + + if (!channel_selected_event(amp1_dev_id, amp1_event_fd, + amp1_phy_handle)) + return EXIT_FAILURE; + + if (!read_local_amp_assoc(amp1_dev_id, amp1_phy_handle, + amp1_max_assoc_len, + amp1_assoc_data, &1_assoc_len)) + return EXIT_FAILURE; + + printf("--> AMP_Create_Physical_Link_Request (Remote-Amp-ID B, AMP_Assoc A)\n"); + + amp2_phy_handle = 0x05; + + if (!accept_physical_link(amp2_dev_id, amp2_phy_handle)) + return EXIT_FAILURE; + + if (!write_remote_amp_assoc(amp2_dev_id, amp2_phy_handle, + amp1_assoc_data, amp1_assoc_len)) + return EXIT_FAILURE; + + printf("hci%d: Signal MAC to start\n", amp2_dev_id); + + printf("<-- AMP_Create_Physical_Link_Response (Local-Amp-ID B, Status)\n"); + + if (!physical_link_complete_event(amp2_dev_id, amp2_event_fd, + amp2_phy_handle)) + return EXIT_FAILURE; + + if (!physical_link_complete_event(amp1_dev_id, amp1_event_fd, + amp1_phy_handle)) + return EXIT_FAILURE; + + /* physical link established */ + + if (!disconnect_physical_link(amp1_dev_id, amp1_phy_handle, 0x13)) + return EXIT_FAILURE; + + if (!disconnect_physical_link_complete_event(amp1_dev_id, + amp1_event_fd, + amp1_phy_handle)) + return EXIT_FAILURE; + + if (!disconnect_physical_link_complete_event(amp2_dev_id, + amp2_event_fd, + amp2_phy_handle)) + return EXIT_FAILURE; + + hci_close_dev(amp2_event_fd); + hci_close_dev(amp1_event_fd); + + return EXIT_SUCCESS; +} diff --git a/tools/avinfo.c b/tools/avinfo.c new file mode 100644 index 0000000..e45b509 --- /dev/null +++ b/tools/avinfo.c @@ -0,0 +1,967 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2018 Pali Rohár + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/l2cap.h" + +#include "profiles/audio/a2dp-codecs.h" + +#define AVDTP_PSM 25 + +/* Commands */ +#define AVDTP_DISCOVER 0x01 +#define AVDTP_GET_CAPABILITIES 0x02 + +#define AVDTP_PKT_TYPE_SINGLE 0x00 + +#define AVDTP_MSG_TYPE_COMMAND 0x00 + +/* SEP capability categories */ +#define AVDTP_MEDIA_TRANSPORT 0x01 +#define AVDTP_REPORTING 0x02 +#define AVDTP_RECOVERY 0x03 +#define AVDTP_CONTENT_PROTECTION 0x04 +#define AVDTP_HEADER_COMPRESSION 0x05 +#define AVDTP_MULTIPLEXING 0x06 +#define AVDTP_MEDIA_CODEC 0x07 + +/* SEP types definitions */ +#define AVDTP_SEP_TYPE_SOURCE 0x00 +#define AVDTP_SEP_TYPE_SINK 0x01 + +/* Media types definitions */ +#define AVDTP_MEDIA_TYPE_AUDIO 0x00 +#define AVDTP_MEDIA_TYPE_VIDEO 0x01 +#define AVDTP_MEDIA_TYPE_MULTIMEDIA 0x02 + +/* Content Protection types definitions */ +#define AVDTP_CONTENT_PROTECTION_TYPE_DTCP 0x0001 +#define AVDTP_CONTENT_PROTECTION_TYPE_SCMS_T 0x0002 + +struct avdtp_service_capability { + uint8_t category; + uint8_t length; + uint8_t data[0]; +} __attribute__ ((packed)); + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avdtp_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t signal_id:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct seid_info { + uint8_t rfa0:1; + uint8_t inuse:1; + uint8_t seid:6; + uint8_t rfa2:3; + uint8_t type:1; + uint8_t media_type:4; +} __attribute__ ((packed)); + +struct seid_req { + struct avdtp_header header; + uint8_t rfa0:2; + uint8_t acp_seid:6; +} __attribute__ ((packed)); + +struct avdtp_media_codec_capability { + uint8_t rfa0:4; + uint8_t media_type:4; + uint8_t media_codec_type; + uint8_t data[0]; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avdtp_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; + uint8_t rfa0:2; + uint8_t signal_id:6; +} __attribute__ ((packed)); + +struct seid_info { + uint8_t seid:6; + uint8_t inuse:1; + uint8_t rfa0:1; + uint8_t media_type:4; + uint8_t type:1; + uint8_t rfa2:3; +} __attribute__ ((packed)); + +struct seid_req { + struct avdtp_header header; + uint8_t acp_seid:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct avdtp_media_codec_capability { + uint8_t media_type:4; + uint8_t rfa0:4; + uint8_t media_codec_type; + uint8_t data[0]; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +struct discover_resp { + struct avdtp_header header; + struct seid_info seps[0]; +} __attribute__ ((packed)); + +struct getcap_resp { + struct avdtp_header header; + uint8_t caps[0]; +} __attribute__ ((packed)); + +struct avdtp_content_protection_capability { + uint16_t content_protection_type; + uint8_t data[0]; +} __attribute__ ((packed)); + +static void print_aptx_common(a2dp_aptx_t *aptx) +{ + printf("\n\t\t\tFrequencies: "); + if (aptx->frequency & APTX_SAMPLING_FREQ_16000) + printf("16kHz "); + if (aptx->frequency & APTX_SAMPLING_FREQ_32000) + printf("32kHz "); + if (aptx->frequency & APTX_SAMPLING_FREQ_44100) + printf("44.1kHz "); + if (aptx->frequency & APTX_SAMPLING_FREQ_48000) + printf("48kHz "); + + printf("\n\t\t\tChannel modes: "); + if (aptx->channel_mode & APTX_CHANNEL_MODE_MONO) + printf("Mono "); + if (aptx->channel_mode & APTX_CHANNEL_MODE_STEREO) + printf("Stereo "); +} + +static void print_aptx(a2dp_aptx_t *aptx, uint8_t size) +{ + printf("\t\tVendor Specific Value (aptX)"); + + if (size < sizeof(*aptx)) { + printf(" (broken)\n"); + return; + } + + print_aptx_common(aptx); + + printf("\n"); +} + +static void print_faststream(a2dp_faststream_t *faststream, uint8_t size) +{ + printf("\t\tVendor Specific Value (FastStream)"); + + if (size < sizeof(*faststream)) { + printf(" (broken)\n"); + return; + } + + printf("\n\t\t\tDirections: "); + if (faststream->direction & FASTSTREAM_DIRECTION_SINK) + printf("sink "); + if (faststream->direction & FASTSTREAM_DIRECTION_SOURCE) + printf("source "); + + if (faststream->direction & FASTSTREAM_DIRECTION_SINK) { + printf("\n\t\t\tSink Frequencies: "); + if (faststream->sink_frequency & + FASTSTREAM_SINK_SAMPLING_FREQ_44100) + printf("44.1kHz "); + if (faststream->sink_frequency & + FASTSTREAM_SINK_SAMPLING_FREQ_48000) + printf("48kHz "); + } + + if (faststream->direction & FASTSTREAM_DIRECTION_SOURCE) { + printf("\n\t\t\tSource Frequencies: "); + if (faststream->source_frequency & + FASTSTREAM_SOURCE_SAMPLING_FREQ_16000) + printf("16kHz "); + } + + printf("\n"); +} + +static void print_aptx_ll(a2dp_aptx_ll_t *aptx_ll, uint8_t size) +{ + a2dp_aptx_ll_new_caps_t *aptx_ll_new; + + printf("\t\tVendor Specific Value (aptX Low Latency)"); + + if (size < sizeof(*aptx_ll)) { + printf(" (broken)\n"); + return; + } + + print_aptx_common(&aptx_ll->aptx); + + printf("\n\t\tBidirectional link: %s", + aptx_ll->bidirect_link ? "Yes" : "No"); + + aptx_ll_new = &aptx_ll->new_caps[0]; + if (aptx_ll->has_new_caps && + size >= sizeof(*aptx_ll) + sizeof(*aptx_ll_new)) { + printf("\n\t\tTarget codec buffer level: %u", + (unsigned int)aptx_ll_new->target_level2 | + ((unsigned int)(aptx_ll_new->target_level1) << 8)); + printf("\n\t\tInitial codec buffer level: %u", + (unsigned int)aptx_ll_new->initial_level2 | + ((unsigned int)(aptx_ll_new->initial_level1) << 8)); + printf("\n\t\tSRA max rate: %g", + aptx_ll_new->sra_max_rate / 10000.0); + printf("\n\t\tSRA averaging time: %us", + (unsigned int)aptx_ll_new->sra_avg_time); + printf("\n\t\tGood working codec buffer level: %u", + (unsigned int)aptx_ll_new->good_working_level2 | + ((unsigned int)(aptx_ll_new->good_working_level1) << 8) + ); + } + + printf("\n"); +} + +static void print_aptx_hd(a2dp_aptx_hd_t *aptx_hd, uint8_t size) +{ + printf("\t\tVendor Specific Value (aptX HD)"); + + if (size < sizeof(*aptx_hd)) { + printf(" (broken)\n"); + return; + } + + print_aptx_common(&aptx_hd->aptx); + + printf("\n"); +} + +static void print_ldac(a2dp_ldac_t *ldac, uint8_t size) +{ + printf("\t\tVendor Specific Value (LDAC)"); + + if (size < sizeof(*ldac)) { + printf(" (broken)\n"); + return; + } + + printf("\n\t\t\tFrequencies: "); + if (ldac->frequency & LDAC_SAMPLING_FREQ_44100) + printf("44.1kHz "); + if (ldac->frequency & LDAC_SAMPLING_FREQ_48000) + printf("48kHz "); + if (ldac->frequency & LDAC_SAMPLING_FREQ_88200) + printf("88.2kHz "); + if (ldac->frequency & LDAC_SAMPLING_FREQ_96000) + printf("96kHz "); + if (ldac->frequency & LDAC_SAMPLING_FREQ_176400) + printf("176.4kHz "); + if (ldac->frequency & LDAC_SAMPLING_FREQ_192000) + printf("192kHz "); + + printf("\n\t\t\tChannel modes: "); + if (ldac->channel_mode & LDAC_CHANNEL_MODE_MONO) + printf("Mono "); + if (ldac->channel_mode & LDAC_CHANNEL_MODE_DUAL) + printf("Dual "); + if (ldac->channel_mode & LDAC_CHANNEL_MODE_STEREO) + printf("Stereo "); + + printf("\n"); +} + +static void print_vendor(a2dp_vendor_codec_t *vendor, uint8_t size) +{ + uint32_t vendor_id; + uint16_t codec_id; + int i; + + if (size < sizeof(*vendor)) { + printf("\tMedia Codec: Vendor Specific A2DP Codec (broken)"); + return; + } + + vendor_id = A2DP_GET_VENDOR_ID(*vendor); + codec_id = A2DP_GET_CODEC_ID(*vendor); + + printf("\tMedia Codec: Vendor Specific A2DP Codec"); + + printf("\n\t\tVendor ID 0x%08x", vendor_id); + + printf("\n\t\tVendor Specific Codec ID 0x%04x", codec_id); + + printf("\n\t\tVendor Specific Data:"); + for (i = 6; i < size; ++i) + printf(" 0x%.02x", ((unsigned char *)vendor)[i]); + printf("\n"); + + if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) + print_aptx((void *) vendor, size); + else if (vendor_id == FASTSTREAM_VENDOR_ID && + codec_id == FASTSTREAM_CODEC_ID) + print_faststream((void *) vendor, size); + else if (vendor_id == APTX_LL_VENDOR_ID && codec_id == APTX_LL_CODEC_ID) + print_aptx_ll((void *) vendor, size); + else if (vendor_id == APTX_HD_VENDOR_ID && codec_id == APTX_HD_CODEC_ID) + print_aptx_hd((void *) vendor, size); + else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID) + print_ldac((void *) vendor, size); +} + +static void print_mpeg24(a2dp_aac_t *aac, uint8_t size) +{ + unsigned int freq, bitrate; + + if (size < sizeof(*aac)) { + printf("\tMedia Codec: MPEG24 (broken)\n"); + return; + } + + freq = AAC_GET_FREQUENCY(*aac); + bitrate = AAC_GET_BITRATE(*aac); + + printf("\tMedia Codec: MPEG24\n\t\tObject Types: "); + + if (aac->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC) + printf("MPEG-2 AAC LC "); + if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC) + printf("MPEG-4 AAC LC "); + if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LTP) + printf("MPEG-4 AAC LTP "); + if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_SCA) + printf("MPEG-4 AAC scalable "); + + printf("\n\t\tFrequencies: "); + if (freq & AAC_SAMPLING_FREQ_8000) + printf("8kHz "); + if (freq & AAC_SAMPLING_FREQ_11025) + printf("11.025kHz "); + if (freq & AAC_SAMPLING_FREQ_12000) + printf("12kHz "); + if (freq & AAC_SAMPLING_FREQ_16000) + printf("16kHz "); + if (freq & AAC_SAMPLING_FREQ_22050) + printf("22.05kHz "); + if (freq & AAC_SAMPLING_FREQ_24000) + printf("24kHz "); + if (freq & AAC_SAMPLING_FREQ_32000) + printf("32kHz "); + if (freq & AAC_SAMPLING_FREQ_44100) + printf("44.1kHz "); + if (freq & AAC_SAMPLING_FREQ_48000) + printf("48kHz "); + if (freq & AAC_SAMPLING_FREQ_64000) + printf("64kHz "); + if (freq & AAC_SAMPLING_FREQ_88200) + printf("88.2kHz "); + if (freq & AAC_SAMPLING_FREQ_96000) + printf("96kHz "); + + printf("\n\t\tChannels: "); + if (aac->channels & AAC_CHANNELS_1) + printf("1 "); + if (aac->channels & AAC_CHANNELS_2) + printf("2 "); + + printf("\n\t\tBitrate: %u", bitrate); + + printf("\n\t\tVBR: %s", aac->vbr ? "Yes\n" : "No\n"); +} + +static void print_mpeg12(a2dp_mpeg_t *mpeg, uint8_t size) +{ + uint16_t bitrate; + + if (size < sizeof(*mpeg)) { + printf("\tMedia Codec: MPEG12 (broken)\n"); + return; + } + + bitrate = MPEG_GET_BITRATE(*mpeg); + + printf("\tMedia Codec: MPEG12\n\t\tChannel Modes: "); + + if (mpeg->channel_mode & MPEG_CHANNEL_MODE_MONO) + printf("Mono "); + if (mpeg->channel_mode & MPEG_CHANNEL_MODE_DUAL_CHANNEL) + printf("DualChannel "); + if (mpeg->channel_mode & MPEG_CHANNEL_MODE_STEREO) + printf("Stereo "); + if (mpeg->channel_mode & MPEG_CHANNEL_MODE_JOINT_STEREO) + printf("JointStereo"); + + printf("\n\t\tFrequencies: "); + if (mpeg->frequency & MPEG_SAMPLING_FREQ_16000) + printf("16Khz "); + if (mpeg->frequency & MPEG_SAMPLING_FREQ_22050) + printf("22.05Khz "); + if (mpeg->frequency & MPEG_SAMPLING_FREQ_24000) + printf("24Khz "); + if (mpeg->frequency & MPEG_SAMPLING_FREQ_32000) + printf("32Khz "); + if (mpeg->frequency & MPEG_SAMPLING_FREQ_44100) + printf("44.1Khz "); + if (mpeg->frequency & MPEG_SAMPLING_FREQ_48000) + printf("48Khz "); + + printf("\n\t\tCRC: %s", mpeg->crc ? "Yes" : "No"); + + printf("\n\t\tLayer: "); + if (mpeg->layer & MPEG_LAYER_MP1) + printf("1 "); + if (mpeg->layer & MPEG_LAYER_MP2) + printf("2 "); + if (mpeg->layer & MPEG_LAYER_MP3) + printf("3 "); + + if (bitrate & MPEG_BIT_RATE_FREE) { + printf("\n\t\tBit Rate: Free format"); + } else { + if (mpeg->layer & MPEG_LAYER_MP1) { + printf("\n\t\tLayer 1 Bit Rate: "); + if (bitrate & MPEG_MP1_BIT_RATE_32000) + printf("32kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_64000) + printf("64kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_96000) + printf("96kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_128000) + printf("128kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_160000) + printf("160kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_192000) + printf("192kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_224000) + printf("224kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_256000) + printf("256kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_320000) + printf("320kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_352000) + printf("352kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_384000) + printf("384kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_416000) + printf("416kbps "); + if (bitrate & MPEG_MP1_BIT_RATE_448000) + printf("448kbps "); + } + + if (mpeg->layer & MPEG_LAYER_MP2) { + printf("\n\t\tLayer 2 Bit Rate: "); + if (bitrate & MPEG_MP2_BIT_RATE_32000) + printf("32kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_48000) + printf("48kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_56000) + printf("56kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_64000) + printf("64kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_80000) + printf("80kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_96000) + printf("96kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_112000) + printf("112kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_128000) + printf("128kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_160000) + printf("160kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_192000) + printf("192kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_224000) + printf("224kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_256000) + printf("256kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_320000) + printf("320kbps "); + if (bitrate & MPEG_MP2_BIT_RATE_384000) + printf("384kbps "); + } + + if (mpeg->layer & MPEG_LAYER_MP3) { + printf("\n\t\tLayer 3 Bit Rate: "); + if (bitrate & MPEG_MP3_BIT_RATE_32000) + printf("32kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_40000) + printf("40kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_48000) + printf("48kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_56000) + printf("56kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_64000) + printf("64kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_80000) + printf("80kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_96000) + printf("96kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_112000) + printf("112kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_128000) + printf("128kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_160000) + printf("160kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_192000) + printf("192kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_224000) + printf("224kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_256000) + printf("256kbps "); + if (bitrate & MPEG_MP3_BIT_RATE_320000) + printf("320kbps "); + } + } + + printf("\n\t\tVBR: %s", mpeg->vbr ? "Yes" : "No"); + + printf("\n\t\tPayload Format: "); + if (mpeg->mpf) + printf("RFC-2250 RFC-3119\n"); + else + printf("RFC-2250\n"); +} + +static void print_sbc(a2dp_sbc_t *sbc, uint8_t size) +{ + if (size < sizeof(*sbc)) { + printf("\tMedia Codec: SBC (broken)\n"); + return; + } + + printf("\tMedia Codec: SBC\n\t\tChannel Modes: "); + + if (sbc->channel_mode & SBC_CHANNEL_MODE_MONO) + printf("Mono "); + if (sbc->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) + printf("DualChannel "); + if (sbc->channel_mode & SBC_CHANNEL_MODE_STEREO) + printf("Stereo "); + if (sbc->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) + printf("JointStereo"); + + printf("\n\t\tFrequencies: "); + if (sbc->frequency & SBC_SAMPLING_FREQ_16000) + printf("16Khz "); + if (sbc->frequency & SBC_SAMPLING_FREQ_32000) + printf("32Khz "); + if (sbc->frequency & SBC_SAMPLING_FREQ_44100) + printf("44.1Khz "); + if (sbc->frequency & SBC_SAMPLING_FREQ_48000) + printf("48Khz "); + + printf("\n\t\tSubbands: "); + if (sbc->allocation_method & SBC_SUBBANDS_4) + printf("4 "); + if (sbc->allocation_method & SBC_SUBBANDS_8) + printf("8"); + + printf("\n\t\tBlocks: "); + if (sbc->block_length & SBC_BLOCK_LENGTH_4) + printf("4 "); + if (sbc->block_length & SBC_BLOCK_LENGTH_8) + printf("8 "); + if (sbc->block_length & SBC_BLOCK_LENGTH_12) + printf("12 "); + if (sbc->block_length & SBC_BLOCK_LENGTH_16) + printf("16 "); + + printf("\n\t\tBitpool Range: %d-%d\n", + sbc->min_bitpool, sbc->max_bitpool); +} + +static void print_media_codec( + struct avdtp_media_codec_capability *cap, + uint8_t size) +{ + int i; + + if (size < sizeof(*cap)) { + printf("\tMedia Codec: Unknown (broken)\n"); + return; + } + + switch (cap->media_codec_type) { + case A2DP_CODEC_SBC: + print_sbc((void *) cap->data, size - 2); + break; + case A2DP_CODEC_MPEG12: + print_mpeg12((void *) cap->data, size - 2); + break; + case A2DP_CODEC_MPEG24: + print_mpeg24((void *) cap->data, size - 2); + break; + case A2DP_CODEC_VENDOR: + print_vendor((void *) cap->data, size - 2); + break; + default: + printf("\tMedia Codec: Unknown\n"); + printf("\t\tCodec Data:"); + for (i = 0; i < size - 2; ++i) + printf(" 0x%.02x", ((unsigned char *)cap->data)[i]); + printf("\n"); + } +} + +static void print_content_protection( + struct avdtp_content_protection_capability *cap, + uint8_t size) +{ + printf("\tContent Protection: "); + + if (size < sizeof(*cap)) { + printf("Unknown (broken)\n"); + return; + } + + switch (btohs(cap->content_protection_type)) { + case AVDTP_CONTENT_PROTECTION_TYPE_DTCP: + printf("DTCP"); + break; + case AVDTP_CONTENT_PROTECTION_TYPE_SCMS_T: + printf("SCMS-T"); + break; + default: + printf("Unknown"); + } + + printf("\n"); +} + +static void print_caps(void *data, int size) +{ + int processed; + int i; + + for (processed = 0; processed + 2 < size;) { + struct avdtp_service_capability *cap; + + cap = data; + + if (processed + 2 + cap->length > size) { + printf("Invalid capability data in getcap resp\n"); + break; + } + + switch (cap->category) { + case AVDTP_MEDIA_TRANSPORT: + case AVDTP_REPORTING: + case AVDTP_RECOVERY: + case AVDTP_MULTIPLEXING: + /* FIXME: Add proper functions */ + break; + default: + printf("\tUnknown category: %d\n", cap->category); + printf("\t\tData:"); + for (i = 0; i < cap->length; ++i) + printf(" 0x%.02x", + ((unsigned char *)cap->data)[i]); + printf("\n"); + break; + case AVDTP_MEDIA_CODEC: + print_media_codec((void *) cap->data, cap->length); + break; + case AVDTP_CONTENT_PROTECTION: + print_content_protection((void *) cap->data, + cap->length); + break; + } + + processed += 2 + cap->length; + data += 2 + cap->length; + } +} + +static void init_request(struct avdtp_header *header, int request_id) +{ + static int transaction = 0; + + header->packet_type = AVDTP_PKT_TYPE_SINGLE; + header->message_type = AVDTP_MSG_TYPE_COMMAND; + header->transaction = transaction; + header->signal_id = request_id; + + /* clear rfa bits */ + header->rfa0 = 0; + + transaction = (transaction + 1) % 16; +} + +static ssize_t avdtp_send(int sk, void *data, int len) +{ + ssize_t ret; + + ret = send(sk, data, len, 0); + + if (ret < 0) + ret = -errno; + else if (ret != len) + ret = -EIO; + + if (ret < 0) { + printf("Unable to send message: %s (%zd)\n", + strerror(-ret), -ret); + return ret; + } + + return ret; +} + +static ssize_t avdtp_receive(int sk, void *data, int len) +{ + ssize_t ret; + + ret = recv(sk, data, len, 0); + + if (ret < 0) { + printf("Unable to receive message: %s (%d)\n", + strerror(errno), errno); + return -errno; + } + + return ret; +} + +static ssize_t avdtp_get_caps(int sk, int seid) +{ + struct seid_req req; + char buffer[1024]; + struct getcap_resp *caps = (void *) buffer; + ssize_t ret; + + memset(&req, 0, sizeof(req)); + init_request(&req.header, AVDTP_GET_CAPABILITIES); + req.acp_seid = seid; + + ret = avdtp_send(sk, &req, sizeof(req)); + if (ret < 0) + return ret; + + memset(&buffer, 0, sizeof(buffer)); + ret = avdtp_receive(sk, caps, sizeof(buffer)); + if (ret < 0) + return ret; + + if ((size_t) ret < (sizeof(struct getcap_resp) + 4 + + sizeof(struct avdtp_media_codec_capability))) { + printf("Invalid capabilities\n"); + return -1; + } + + print_caps(caps->caps, ret - sizeof(struct getcap_resp)); + + return 0; +} + +static ssize_t avdtp_discover(int sk) +{ + struct avdtp_header req; + char buffer[256]; + struct discover_resp *discover = (void *) buffer; + int seps, i; + ssize_t ret; + + memset(&req, 0, sizeof(req)); + init_request(&req, AVDTP_DISCOVER); + + ret = avdtp_send(sk, &req, sizeof(req)); + if (ret < 0) + return ret; + + memset(&buffer, 0, sizeof(buffer)); + ret = avdtp_receive(sk, discover, sizeof(buffer)); + if (ret < 0) + return ret; + + seps = (ret - sizeof(struct avdtp_header)) / sizeof(struct seid_info); + for (i = 0; i < seps; i++) { + const char *type, *media; + + switch (discover->seps[i].type) { + case AVDTP_SEP_TYPE_SOURCE: + type = "Source"; + break; + case AVDTP_SEP_TYPE_SINK: + type = "Sink"; + break; + default: + type = "Invalid"; + } + + switch (discover->seps[i].media_type) { + case AVDTP_MEDIA_TYPE_AUDIO: + media = "Audio"; + break; + case AVDTP_MEDIA_TYPE_VIDEO: + media = "Video"; + break; + case AVDTP_MEDIA_TYPE_MULTIMEDIA: + media = "Multimedia"; + break; + default: + media = "Invalid"; + } + + printf("Stream End-Point #%d: %s %s %s\n", + discover->seps[i].seid, media, type, + discover->seps[i].inuse ? "*" : ""); + + avdtp_get_caps(sk, discover->seps[i].seid); + } + + return 0; +} + +static int l2cap_connect(bdaddr_t *src, bdaddr_t *dst) +{ + struct sockaddr_l2 l2a; + int sk; + + memset(&l2a, 0, sizeof(l2a)); + l2a.l2_family = AF_BLUETOOTH; + bacpy(&l2a.l2_bdaddr, src); + + sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sk < 0) { + printf("Cannot create L2CAP socket. %s(%d)\n", strerror(errno), + errno); + return -errno; + } + + if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) { + printf("Bind failed. %s (%d)\n", strerror(errno), errno); + close(sk); + return -errno; + } + + memset(&l2a, 0, sizeof(l2a)); + l2a.l2_family = AF_BLUETOOTH; + bacpy(&l2a.l2_bdaddr, dst); + l2a.l2_psm = htobs(AVDTP_PSM); + + if (connect(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) { + printf("Connect failed. %s(%d)\n", strerror(errno), errno); + close(sk); + return -errno; + } + + return sk; +} + +static void usage(void) +{ + printf("avinfo - Audio/Video Info Tool ver %s\n", VERSION); + printf("Usage:\n" + "\tavinfo [options] \n"); + printf("Options:\n" + "\t-h\t\tDisplay help\n" + "\t-i\t\tSpecify source interface\n"); +} + +static struct option main_options[] = { + { "help", 0, 0, 'h' }, + { "device", 1, 0, 'i' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + bdaddr_t src, dst; + int opt, sk, dev_id; + + if (argc < 2) { + usage(); + exit(0); + } + + bacpy(&src, BDADDR_ANY); + dev_id = hci_get_route(&src); + if ((dev_id < 0) || (hci_devba(dev_id, &src) < 0)) { + printf("Cannot find any local adapter\n"); + exit(-1); + } + + while ((opt = getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) { + switch (opt) { + case 'i': + if (!strncmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &src); + else + str2ba(optarg, &src); + break; + + case 'h': + default: + usage(); + exit(0); + } + } + + printf("Connecting ... \n"); + + if (bachk(argv[optind]) < 0) { + printf("Invalid argument\n"); + exit(1); + } + + str2ba(argv[optind], &dst); + sk = l2cap_connect(&src, &dst); + if (sk < 0) + exit(1); + + if (avdtp_discover(sk) < 0) + exit(1); + + return 0; +} diff --git a/tools/avtest.c b/tools/avtest.c new file mode 100644 index 0000000..59fb1da --- /dev/null +++ b/tools/avtest.c @@ -0,0 +1,869 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2007-2010 Marcel Holtmann + * Copyright (C) 2009-2010 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/l2cap.h" +#include "lib/sdp.h" + +#define AVDTP_PKT_TYPE_SINGLE 0x00 +#define AVDTP_PKT_TYPE_START 0x01 +#define AVDTP_PKT_TYPE_CONTINUE 0x02 +#define AVDTP_PKT_TYPE_END 0x03 + +#define AVDTP_MSG_TYPE_COMMAND 0x00 +#define AVDTP_MSG_TYPE_GEN_REJECT 0x01 +#define AVDTP_MSG_TYPE_ACCEPT 0x02 +#define AVDTP_MSG_TYPE_REJECT 0x03 + +#define AVDTP_DISCOVER 0x01 +#define AVDTP_GET_CAPABILITIES 0x02 +#define AVDTP_SET_CONFIGURATION 0x03 +#define AVDTP_GET_CONFIGURATION 0x04 +#define AVDTP_RECONFIGURE 0x05 +#define AVDTP_OPEN 0x06 +#define AVDTP_START 0x07 +#define AVDTP_CLOSE 0x08 +#define AVDTP_SUSPEND 0x09 +#define AVDTP_ABORT 0x0A + +#define AVDTP_SEP_TYPE_SOURCE 0x00 +#define AVDTP_SEP_TYPE_SINK 0x01 + +#define AVDTP_MEDIA_TYPE_AUDIO 0x00 +#define AVDTP_MEDIA_TYPE_VIDEO 0x01 +#define AVDTP_MEDIA_TYPE_MULTIMEDIA 0x02 + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avdtp_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t signal_id:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct seid_info { + uint8_t rfa0:1; + uint8_t inuse:1; + uint8_t seid:6; + uint8_t rfa2:3; + uint8_t type:1; + uint8_t media_type:4; +} __attribute__ ((packed)); + +struct avdtp_start_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t no_of_packets; + uint8_t signal_id:6; + uint8_t rfa0:2; +} __attribute__ ((packed)); + +struct avdtp_continue_header { + uint8_t message_type:2; + uint8_t packet_type:2; + uint8_t transaction:4; +} __attribute__ ((packed)); + +struct avctp_header { + uint8_t ipid:1; + uint8_t cr:1; + uint8_t packet_type:2; + uint8_t transaction:4; + uint16_t pid; +} __attribute__ ((packed)); +#define AVCTP_HEADER_LENGTH 3 + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avdtp_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; + uint8_t rfa0:2; + uint8_t signal_id:6; +} __attribute__ ((packed)); + +struct seid_info { + uint8_t seid:6; + uint8_t inuse:1; + uint8_t rfa0:1; + uint8_t media_type:4; + uint8_t type:1; + uint8_t rfa2:3; +} __attribute__ ((packed)); + +struct avdtp_start_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; + uint8_t no_of_packets; + uint8_t rfa0:2; + uint8_t signal_id:6; +} __attribute__ ((packed)); + +struct avdtp_continue_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t message_type:2; +} __attribute__ ((packed)); + +struct avctp_header { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t cr:1; + uint8_t ipid:1; + uint16_t pid; +} __attribute__ ((packed)); +#define AVCTP_HEADER_LENGTH 3 + +#else +#error "Unknown byte order" +#endif + +#define AVCTP_COMMAND 0 +#define AVCTP_RESPONSE 1 + +#define AVCTP_PACKET_SINGLE 0 + +static const unsigned char media_transport[] = { + 0x01, /* Media transport category */ + 0x00, + 0x07, /* Media codec category */ + 0x06, + 0x00, /* Media type audio */ + 0x00, /* Codec SBC */ + 0x22, /* 44.1 kHz, stereo */ + 0x15, /* 16 blocks, 8 subbands */ + 0x02, + 0x33, +}; + +static int media_sock = -1; + +static void dump_avctp_header(struct avctp_header *hdr) +{ + printf("TL %d PT %d CR %d IPID %d PID 0x%04x\n", hdr->transaction, + hdr->packet_type, hdr->cr, hdr->ipid, ntohs(hdr->pid)); +} + +static void dump_avdtp_header(struct avdtp_header *hdr) +{ + printf("TL %d PT %d MT %d SI %d\n", hdr->transaction, + hdr->packet_type, hdr->message_type, hdr->signal_id); +} + +static void dump_buffer(const unsigned char *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) + printf("%02x ", buf[i]); + printf("\n"); +} + +static void process_avdtp(int srv_sk, int sk, unsigned char reject, + int fragment) +{ + unsigned char buf[672]; + ssize_t len; + + while (1) { + struct avdtp_header *hdr = (void *) buf; + + len = read(sk, buf, sizeof(buf)); + if (len <= 0) { + perror("Read failed"); + break; + } + + dump_buffer(buf, len); + dump_avdtp_header(hdr); + + if (hdr->packet_type != AVDTP_PKT_TYPE_SINGLE) { + fprintf(stderr, "Only single packets are supported\n"); + break; + } + + if (hdr->message_type != AVDTP_MSG_TYPE_COMMAND) { + fprintf(stderr, "Ignoring non-command messages\n"); + continue; + } + + switch (hdr->signal_id) { + case AVDTP_DISCOVER: + if (reject == AVDTP_DISCOVER) { + hdr->message_type = AVDTP_MSG_TYPE_REJECT; + buf[2] = 0x29; /* Unsupported configuration */ + printf("Rejecting discover command\n"); + len = write(sk, buf, 3); + } else { + struct seid_info *sei = (void *) (buf + 2); + hdr->message_type = AVDTP_MSG_TYPE_ACCEPT; + buf[2] = 0x00; + buf[3] = 0x00; + sei->seid = 0x01; + sei->type = AVDTP_SEP_TYPE_SINK; + sei->media_type = AVDTP_MEDIA_TYPE_AUDIO; + printf("Accepting discover command\n"); + len = write(sk, buf, 4); + } + break; + + case AVDTP_GET_CAPABILITIES: + if (reject == AVDTP_GET_CAPABILITIES) { + hdr->message_type = AVDTP_MSG_TYPE_REJECT; + buf[2] = 0x29; /* Unsupported configuration */ + printf("Rejecting get capabilties command\n"); + len = write(sk, buf, 3); + } else if (fragment) { + struct avdtp_start_header *start = (void *) buf; + + printf("Sending fragmented reply to getcap\n"); + + hdr->message_type = AVDTP_MSG_TYPE_ACCEPT; + + /* Start packet */ + hdr->packet_type = AVDTP_PKT_TYPE_START; + start->signal_id = AVDTP_GET_CAPABILITIES; + start->no_of_packets = 3; + memcpy(&buf[3], media_transport, + sizeof(media_transport)); + len = write(sk, buf, + 3 + sizeof(media_transport)); + + /* Continue packet */ + hdr->packet_type = AVDTP_PKT_TYPE_CONTINUE; + memcpy(&buf[1], media_transport, + sizeof(media_transport)); + len = write(sk, buf, + 1 + sizeof(media_transport)); + + /* End packet */ + hdr->packet_type = AVDTP_PKT_TYPE_END; + memcpy(&buf[1], media_transport, + sizeof(media_transport)); + len = write(sk, buf, + 1 + sizeof(media_transport)); + } else { + hdr->message_type = AVDTP_MSG_TYPE_ACCEPT; + memcpy(&buf[2], media_transport, + sizeof(media_transport)); + printf("Accepting get capabilities command\n"); + len = write(sk, buf, + 2 + sizeof(media_transport)); + } + break; + + case AVDTP_SET_CONFIGURATION: + if (reject == AVDTP_SET_CONFIGURATION) { + hdr->message_type = AVDTP_MSG_TYPE_REJECT; + buf[2] = buf[4]; + buf[3] = 0x13; /* SEP In Use */ + printf("Rejecting set configuration command\n"); + len = write(sk, buf, 4); + } else { + hdr->message_type = AVDTP_MSG_TYPE_ACCEPT; + printf("Accepting set configuration command\n"); + len = write(sk, buf, 2); + } + break; + + case AVDTP_GET_CONFIGURATION: + if (reject == AVDTP_GET_CONFIGURATION) { + hdr->message_type = AVDTP_MSG_TYPE_REJECT; + buf[2] = 0x12; /* Bad ACP SEID */ + printf("Rejecting get configuration command\n"); + len = write(sk, buf, 3); + } else { + hdr->message_type = AVDTP_MSG_TYPE_ACCEPT; + printf("Accepting get configuration command\n"); + len = write(sk, buf, 2); + } + break; + + case AVDTP_OPEN: + if (reject == AVDTP_OPEN) { + hdr->message_type = AVDTP_MSG_TYPE_REJECT; + buf[2] = 0x31; /* Bad State */ + printf("Rejecting open command\n"); + len = write(sk, buf, 3); + } else { + struct sockaddr_l2 addr; + socklen_t optlen; + + hdr->message_type = AVDTP_MSG_TYPE_ACCEPT; + printf("Accepting open command\n"); + len = write(sk, buf, 2); + + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + + media_sock = accept(srv_sk, + (struct sockaddr *) &addr, + &optlen); + if (media_sock < 0) { + perror("Accept failed"); + break; + } + } + break; + + case AVDTP_START: + if (reject == AVDTP_ABORT) + printf("Ignoring start to cause abort"); + else if (reject == AVDTP_START) { + hdr->message_type = AVDTP_MSG_TYPE_REJECT; + buf[3] = 0x31; /* Bad State */ + printf("Rejecting start command\n"); + len = write(sk, buf, 4); + } else { + hdr->message_type = AVDTP_MSG_TYPE_ACCEPT; + printf("Accepting start command\n"); + len = write(sk, buf, 2); + } + break; + + case AVDTP_CLOSE: + if (reject == AVDTP_CLOSE) { + hdr->message_type = AVDTP_MSG_TYPE_REJECT; + buf[2] = 0x31; /* Bad State */ + printf("Rejecting close command\n"); + len = write(sk, buf, 3); + } else { + hdr->message_type = AVDTP_MSG_TYPE_ACCEPT; + printf("Accepting close command\n"); + len = write(sk, buf, 2); + if (media_sock >= 0) { + close(media_sock); + media_sock = -1; + } + } + break; + + case AVDTP_SUSPEND: + if (reject == AVDTP_SUSPEND) { + hdr->message_type = AVDTP_MSG_TYPE_REJECT; + buf[3] = 0x31; /* Bad State */ + printf("Rejecting suspend command\n"); + len = write(sk, buf, 4); + } else { + hdr->message_type = AVDTP_MSG_TYPE_ACCEPT; + printf("Accepting suspend command\n"); + len = write(sk, buf, 2); + } + break; + + case AVDTP_ABORT: + hdr->message_type = AVDTP_MSG_TYPE_ACCEPT; + printf("Accepting abort command\n"); + len = write(sk, buf, 2); + if (media_sock >= 0) { + close(media_sock); + media_sock = -1; + } + break; + + default: + buf[1] = 0x00; + printf("Unknown command\n"); + len = write(sk, buf, 2); + break; + } + } +} + +static void process_avctp(int sk, int reject) +{ + unsigned char buf[672]; + ssize_t len; + + while (1) { + struct avctp_header *hdr = (void *) buf; + + len = read(sk, buf, sizeof(buf)); + if (len <= 0) { + perror("Read failed"); + break; + } + + dump_buffer(buf, len); + + if (len >= AVCTP_HEADER_LENGTH) + dump_avctp_header(hdr); + } +} + +static int set_minimum_mtu(int sk) +{ + struct l2cap_options l2o; + socklen_t optlen; + + memset(&l2o, 0, sizeof(l2o)); + optlen = sizeof(l2o); + + if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &optlen) < 0) { + perror("getsockopt"); + return -1; + } + + l2o.imtu = 48; + l2o.omtu = 48; + + if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o, sizeof(l2o)) < 0) { + perror("setsockopt"); + return -1; + } + + return 0; +} + +static void do_listen(const bdaddr_t *src, unsigned char reject, int fragment) +{ + struct sockaddr_l2 addr; + socklen_t optlen; + int sk, nsk; + + sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sk < 0) { + perror("Can't create socket"); + return; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + addr.l2_psm = htobs(25); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Can't bind socket"); + goto error; + } + + if (fragment) + set_minimum_mtu(sk); + + if (listen(sk, 10)) { + perror("Can't listen on the socket"); + goto error; + } + + while (1) { + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + + nsk = accept(sk, (struct sockaddr *) &addr, &optlen); + if (nsk < 0) { + perror("Accept failed"); + continue; + } + + process_avdtp(sk, nsk, reject, fragment); + + if (media_sock >= 0) { + close(media_sock); + media_sock = -1; + } + + close(nsk); + } + +error: + close(sk); +} + +static int do_connect(const bdaddr_t *src, const bdaddr_t *dst, int avctp, + int fragment) +{ + struct sockaddr_l2 addr; + int sk, err; + + sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sk < 0) { + perror("Can't create socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Can't bind socket"); + goto error; + } + + if (fragment) + set_minimum_mtu(sk); + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + addr.l2_psm = htobs(avctp ? 23 : 25); + + err = connect(sk, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0) { + perror("Unable to connect"); + goto error; + } + + return sk; + +error: + close(sk); + return -1; +} + +static void do_avdtp_send(int sk, const bdaddr_t *src, const bdaddr_t *dst, + unsigned char cmd, int invalid, int preconf) +{ + unsigned char buf[672]; + struct avdtp_header *hdr = (void *) buf; + ssize_t len; + + memset(buf, 0, sizeof(buf)); + + switch (cmd) { + case AVDTP_DISCOVER: + if (invalid) + hdr->message_type = 0x01; + else + hdr->message_type = AVDTP_MSG_TYPE_COMMAND; + hdr->packet_type = AVDTP_PKT_TYPE_SINGLE; + hdr->signal_id = AVDTP_DISCOVER; + len = write(sk, buf, 2); + break; + + case AVDTP_GET_CAPABILITIES: + hdr->message_type = AVDTP_MSG_TYPE_COMMAND; + hdr->packet_type = AVDTP_PKT_TYPE_SINGLE; + hdr->signal_id = AVDTP_GET_CAPABILITIES; + buf[2] = 1 << 2; /* SEID 1 */ + len = write(sk, buf, invalid ? 2 : 3); + break; + + case AVDTP_SET_CONFIGURATION: + if (preconf) + do_avdtp_send(sk, src, dst, cmd, 0, 0); + hdr->message_type = AVDTP_MSG_TYPE_COMMAND; + hdr->packet_type = AVDTP_PKT_TYPE_SINGLE; + hdr->signal_id = AVDTP_SET_CONFIGURATION; + buf[2] = 1 << 2; /* ACP SEID */ + buf[3] = 1 << 2; /* INT SEID */ + memcpy(&buf[4], media_transport, sizeof(media_transport)); + if (invalid) + buf[5] = 0x01; /* LOSC != 0 */ + len = write(sk, buf, 4 + sizeof(media_transport)); + break; + + case AVDTP_GET_CONFIGURATION: + if (preconf) + do_avdtp_send(sk, src, dst, AVDTP_SET_CONFIGURATION, 0, 0); + hdr->message_type = AVDTP_MSG_TYPE_COMMAND; + hdr->packet_type = AVDTP_PKT_TYPE_SINGLE; + hdr->signal_id = AVDTP_GET_CONFIGURATION; + if (invalid) + buf[2] = 13 << 2; /* Invalid ACP SEID */ + else + buf[2] = 1 << 2; /* Valid ACP SEID */ + len = write(sk, buf, 3); + break; + + case AVDTP_OPEN: + if (preconf) + do_avdtp_send(sk, src, dst, AVDTP_SET_CONFIGURATION, 0, 0); + hdr->message_type = AVDTP_MSG_TYPE_COMMAND; + hdr->packet_type = AVDTP_PKT_TYPE_SINGLE; + hdr->signal_id = AVDTP_OPEN; + buf[2] = 1 << 2; /* ACP SEID */ + len = write(sk, buf, 3); + break; + + case AVDTP_START: + if (preconf) + do_avdtp_send(sk, src, dst, AVDTP_SET_CONFIGURATION, 0, 0); + if (!invalid) + do_avdtp_send(sk, src, dst, AVDTP_OPEN, 0, 0); + hdr->message_type = AVDTP_MSG_TYPE_COMMAND; + hdr->packet_type = AVDTP_PKT_TYPE_SINGLE; + hdr->signal_id = AVDTP_START; + buf[2] = 1 << 2; /* ACP SEID */ + len = write(sk, buf, 3); + break; + + case AVDTP_CLOSE: + if (preconf) { + do_avdtp_send(sk, src, dst, AVDTP_SET_CONFIGURATION, 0, 0); + do_avdtp_send(sk, src, dst, AVDTP_OPEN, 0, 0); + } + hdr->message_type = AVDTP_MSG_TYPE_COMMAND; + hdr->packet_type = AVDTP_PKT_TYPE_SINGLE; + hdr->signal_id = AVDTP_CLOSE; + if (invalid) + buf[2] = 13 << 2; /* Invalid ACP SEID */ + else + buf[2] = 1 << 2; /* Valid ACP SEID */ + len = write(sk, buf, 3); + break; + + case AVDTP_SUSPEND: + if (invalid) + do_avdtp_send(sk, src, dst, AVDTP_OPEN, 0, preconf); + else + do_avdtp_send(sk, src, dst, AVDTP_START, 0, preconf); + hdr->message_type = AVDTP_MSG_TYPE_COMMAND; + hdr->packet_type = AVDTP_PKT_TYPE_SINGLE; + hdr->signal_id = AVDTP_SUSPEND; + buf[2] = 1 << 2; /* ACP SEID */ + len = write(sk, buf, 3); + break; + + case AVDTP_ABORT: + do_avdtp_send(sk, src, dst, AVDTP_OPEN, 0, 1); + hdr->message_type = AVDTP_MSG_TYPE_COMMAND; + hdr->packet_type = AVDTP_PKT_TYPE_SINGLE; + hdr->signal_id = AVDTP_ABORT; + buf[2] = 1 << 2; /* ACP SEID */ + len = write(sk, buf, 3); + break; + + default: + hdr->message_type = AVDTP_MSG_TYPE_COMMAND; + hdr->packet_type = AVDTP_PKT_TYPE_SINGLE; + hdr->signal_id = cmd; + len = write(sk, buf, 2); + break; + } + + do { + len = read(sk, buf, sizeof(buf)); + + dump_buffer(buf, len); + dump_avdtp_header(hdr); + } while (len < 2 || (hdr->message_type != AVDTP_MSG_TYPE_ACCEPT && + hdr->message_type != AVDTP_MSG_TYPE_REJECT && + hdr->message_type != AVDTP_MSG_TYPE_GEN_REJECT)); + + if (cmd == AVDTP_OPEN && len >= 2 && + hdr->message_type == AVDTP_MSG_TYPE_ACCEPT) + media_sock = do_connect(src, dst, 0, 0); +} + +static void do_avctp_send(int sk, int invalid) +{ + unsigned char buf[672]; + struct avctp_header *hdr = (void *) buf; + unsigned char play_pressed[] = { 0x00, 0x48, 0x7c, 0x44, 0x00 }; + ssize_t len; + + memset(buf, 0, sizeof(buf)); + + hdr->packet_type = AVCTP_PACKET_SINGLE; + hdr->cr = AVCTP_COMMAND; + if (invalid) + hdr->pid = 0xffff; + else + hdr->pid = htons(AV_REMOTE_SVCLASS_ID); + + memcpy(&buf[AVCTP_HEADER_LENGTH], play_pressed, sizeof(play_pressed)); + + len = write(sk, buf, AVCTP_HEADER_LENGTH + sizeof(play_pressed)); + + len = read(sk, buf, sizeof(buf)); + + dump_buffer(buf, len); + if (len >= AVCTP_HEADER_LENGTH) + dump_avctp_header(hdr); +} + +static void usage(void) +{ + printf("avtest - Audio/Video testing ver %s\n", VERSION); + printf("Usage:\n" + "\tavtest [options] [remote address]\n"); + printf("Options:\n" + "\t--device HCI device\n" + "\t--reject Reject command\n" + "\t--send Send command\n" + "\t--preconf Configure stream before actual command\n" + "\t--wait Wait N seconds before exiting\n" + "\t--fragment Use minimum MTU and fragmented messages\n" + "\t--invalid Send invalid command\n"); +} + +static struct option main_options[] = { + { "help", 0, 0, 'h' }, + { "device", 1, 0, 'i' }, + { "reject", 1, 0, 'r' }, + { "send", 1, 0, 's' }, + { "invalid", 1, 0, 'f' }, + { "preconf", 0, 0, 'c' }, + { "fragment", 0, 0, 'F' }, + { "avctp", 0, 0, 'C' }, + { "wait", 1, 0, 'w' }, + { 0, 0, 0, 0 } +}; + +static unsigned char parse_cmd(const char *arg) +{ + if (!strncmp(arg, "discov", 6)) + return AVDTP_DISCOVER; + else if (!strncmp(arg, "capa", 4)) + return AVDTP_GET_CAPABILITIES; + else if (!strncmp(arg, "getcapa", 7)) + return AVDTP_GET_CAPABILITIES; + else if (!strncmp(arg, "setconf", 7)) + return AVDTP_SET_CONFIGURATION; + else if (!strncmp(arg, "getconf", 7)) + return AVDTP_GET_CONFIGURATION; + else if (!strncmp(arg, "open", 4)) + return AVDTP_OPEN; + else if (!strncmp(arg, "start", 5)) + return AVDTP_START; + else if (!strncmp(arg, "close", 5)) + return AVDTP_CLOSE; + else if (!strncmp(arg, "suspend", 7)) + return AVDTP_SUSPEND; + else if (!strncmp(arg, "abort", 7)) + return AVDTP_ABORT; + else + return atoi(arg); +} + +enum { + MODE_NONE, MODE_REJECT, MODE_SEND, +}; + +int main(int argc, char *argv[]) +{ + unsigned char cmd = 0x00; + bdaddr_t src, dst; + int opt, mode = MODE_NONE, sk, invalid = 0, preconf = 0, fragment = 0; + int avctp = 0, wait_before_exit = 0; + + bacpy(&src, BDADDR_ANY); + bacpy(&dst, BDADDR_ANY); + + while ((opt = getopt_long(argc, argv, "+i:r:s:f:hcFCw:", + main_options, NULL)) != EOF) { + switch (opt) { + case 'i': + if (!strncmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &src); + else + str2ba(optarg, &src); + break; + + case 'r': + mode = MODE_REJECT; + cmd = parse_cmd(optarg); + break; + + case 'f': + invalid = 1; + /* fall through */ + + case 's': + mode = MODE_SEND; + cmd = parse_cmd(optarg); + break; + + case 'c': + preconf = 1; + break; + + case 'F': + fragment = 1; + break; + + case 'C': + avctp = 1; + break; + + case 'w': + wait_before_exit = atoi(optarg); + break; + + case 'h': + default: + usage(); + exit(0); + } + } + + if (argv[optind]) + str2ba(argv[optind], &dst); + + if (avctp) { + avctp = mode; + mode = MODE_SEND; + } + + switch (mode) { + case MODE_REJECT: + do_listen(&src, cmd, fragment); + break; + case MODE_SEND: + sk = do_connect(&src, &dst, avctp, fragment); + if (sk < 0) + exit(1); + if (avctp) { + if (avctp == MODE_SEND) + do_avctp_send(sk, invalid); + else + process_avctp(sk, cmd); + } else + do_avdtp_send(sk, &src, &dst, cmd, invalid, preconf); + if (wait_before_exit) { + printf("Waiting %d seconds before exiting\n", wait_before_exit); + sleep(wait_before_exit); + } + if (media_sock >= 0) + close(media_sock); + close(sk); + break; + default: + fprintf(stderr, "No operating mode specified!\n"); + exit(1); + } + + return 0; +} diff --git a/tools/bccmd.1 b/tools/bccmd.1 new file mode 100644 index 0000000..26c83a6 --- /dev/null +++ b/tools/bccmd.1 @@ -0,0 +1,130 @@ +.TH BCCMD 1 "Jun 20 2006" BlueZ "Linux System Administration" +.SH NAME +bccmd \- Utility for the CSR BCCMD interface +.SH SYNOPSIS +.B bccmd +.br +.B bccmd [-t ] [-d ] [] +.br +.B bccmd [-h --help] +.br +.SH DESCRIPTION +.B +bccmd +issues BlueCore commands to +.B +Cambridge Silicon Radio +devices. If run without the argument, a short help page will be displayed. +.SH OPTIONS +.TP +.BI -t\ +Specify the communication transport. Valid options are: +.RS +.TP +.BI HCI +Local device with Host Controller Interface (default). +.TP +.BI USB +Direct USB connection. +.TP +.BI BCSP +Blue Core Serial Protocol. +.TP +.BI H4 +H4 serial protocol. +.TP +.BI 3WIRE +3WIRE protocol (not implemented). +.SH +.TP +.BI -d\ +Specify a particular device to operate on. If not specified, default is the first available HCI device +or /dev/ttyS0 for serial transports. +.SH COMMANDS +.TP +.BI builddef +Get build definitions +.TP +.BI keylen\ +Get current crypt key length +.TP +.BI clock +Get local Bluetooth clock +.TP +.BI rand +Get random number +.TP +.BI chiprev +Get chip revision +.TP +.BI buildname +Get the full build name +.TP +.BI panicarg +Get panic code argument +.TP +.BI faultarg +Get fault code argument +.TP +.BI coldreset +Perform cold reset +.TP +.BI warmreset +Perform warm reset +.TP +.BI disabletx +Disable TX on the device +.TP +.BI enabletx +Enable TX on the device +.TP +.BI singlechan\ +Lock radio on specific channel +.TP +.BI hoppingon +Revert to channel hopping +.TP +.BI rttxdata1\ \ +TXData1 radio test +.TP +.BI radiotest\ \ \ +Run radio tests, tests 4, 6 and 7 are transmit tests +.TP +.BI memtypes +Get memory types +.TP +.BI psget\ [-r]\ [-s\ ]\ +Get value for PS key. +-r sends a warm reset afterwards +.TP +.BI psset\ [-r]\ [-s\ ]\ \ +Set value for PS key. +-r sends a warm reset afterwards +.TP +.BI psclr\ [-r]\ [-s\ ]\ +Clear value for PS key. +-r sends a warm reset afterwards +.TP +.BI pslist\ [-r]\ [-s\ ] +List all PS keys. +-r sends a warm reset afterwards +.TP +.BI psread\ [-r]\ [-s\ ] +Read all PS keys. +-r sends a warm reset afterwards +.TP +.BI psload\ [-r]\ [-s\ ]\ +Load all PS keys from PSR file. +-r sends a warm reset afterwards +.TP +.BI pscheck\ [-r]\ [-s\ ]\ +Check syntax of PSR file. +-r sends a warm reset afterwards +.SH KEYS +bdaddr country devclass keymin keymax features commands version +remver hciextn mapsco baudrate hostintf anafreq anaftrim usbvid +usbpid dfupid bootmode +.SH AUTHORS +Written by Marcel Holtmann , +man page by Adam Laurie +.PP diff --git a/tools/bccmd.c b/tools/bccmd.c new file mode 100644 index 0000000..2c215ac --- /dev/null +++ b/tools/bccmd.c @@ -0,0 +1,1248 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "src/shared/tty.h" + +#include "csr.h" + +#define CSR_TRANSPORT_UNKNOWN 0 +#define CSR_TRANSPORT_HCI 1 +#define CSR_TRANSPORT_USB 2 +#define CSR_TRANSPORT_BCSP 3 +#define CSR_TRANSPORT_H4 4 +#define CSR_TRANSPORT_3WIRE 5 + +#define CSR_STORES_PSI (0x0001) +#define CSR_STORES_PSF (0x0002) +#define CSR_STORES_PSROM (0x0004) +#define CSR_STORES_PSRAM (0x0008) +#define CSR_STORES_DEFAULT (CSR_STORES_PSI | CSR_STORES_PSF) + +#define CSR_TYPE_NULL 0 +#define CSR_TYPE_COMPLEX 1 +#define CSR_TYPE_UINT8 2 +#define CSR_TYPE_UINT16 3 +#define CSR_TYPE_UINT32 4 + +#define CSR_TYPE_ARRAY CSR_TYPE_COMPLEX +#define CSR_TYPE_BDADDR CSR_TYPE_COMPLEX + +static inline int transport_open(int transport, char *device, speed_t bcsp_rate) +{ + switch (transport) { + case CSR_TRANSPORT_HCI: + return csr_open_hci(device); + case CSR_TRANSPORT_USB: + return csr_open_usb(device); + case CSR_TRANSPORT_BCSP: + return csr_open_bcsp(device, bcsp_rate); + case CSR_TRANSPORT_H4: + return csr_open_h4(device); + case CSR_TRANSPORT_3WIRE: + return csr_open_3wire(device); + default: + fprintf(stderr, "Unsupported transport\n"); + return -1; + } +} + +static inline int transport_read(int transport, uint16_t varid, uint8_t *value, uint16_t length) +{ + switch (transport) { + case CSR_TRANSPORT_HCI: + return csr_read_hci(varid, value, length); + case CSR_TRANSPORT_USB: + return csr_read_usb(varid, value, length); + case CSR_TRANSPORT_BCSP: + return csr_read_bcsp(varid, value, length); + case CSR_TRANSPORT_H4: + return csr_read_h4(varid, value, length); + case CSR_TRANSPORT_3WIRE: + return csr_read_3wire(varid, value, length); + default: + errno = EOPNOTSUPP; + return -1; + } +} + +static inline int transport_write(int transport, uint16_t varid, uint8_t *value, uint16_t length) +{ + switch (transport) { + case CSR_TRANSPORT_HCI: + return csr_write_hci(varid, value, length); + case CSR_TRANSPORT_USB: + return csr_write_usb(varid, value, length); + case CSR_TRANSPORT_BCSP: + return csr_write_bcsp(varid, value, length); + case CSR_TRANSPORT_H4: + return csr_write_h4(varid, value, length); + case CSR_TRANSPORT_3WIRE: + return csr_write_3wire(varid, value, length); + default: + errno = EOPNOTSUPP; + return -1; + } +} + +static inline void transport_close(int transport) +{ + switch (transport) { + case CSR_TRANSPORT_HCI: + csr_close_hci(); + break; + case CSR_TRANSPORT_USB: + csr_close_usb(); + break; + case CSR_TRANSPORT_BCSP: + csr_close_bcsp(); + break; + case CSR_TRANSPORT_H4: + csr_close_h4(); + break; + case CSR_TRANSPORT_3WIRE: + csr_close_3wire(); + break; + } +} + +static struct { + uint16_t pskey; + int type; + int size; + char *str; +} storage[] = { + { CSR_PSKEY_BDADDR, CSR_TYPE_BDADDR, 8, "bdaddr" }, + { CSR_PSKEY_COUNTRYCODE, CSR_TYPE_UINT16, 0, "country" }, + { CSR_PSKEY_CLASSOFDEVICE, CSR_TYPE_UINT32, 0, "devclass" }, + { CSR_PSKEY_ENC_KEY_LMIN, CSR_TYPE_UINT16, 0, "keymin" }, + { CSR_PSKEY_ENC_KEY_LMAX, CSR_TYPE_UINT16, 0, "keymax" }, + { CSR_PSKEY_LOCAL_SUPPORTED_FEATURES, CSR_TYPE_ARRAY, 8, "features" }, + { CSR_PSKEY_LOCAL_SUPPORTED_COMMANDS, CSR_TYPE_ARRAY, 18, "commands" }, + { CSR_PSKEY_HCI_LMP_LOCAL_VERSION, CSR_TYPE_UINT16, 0, "version" }, + { CSR_PSKEY_LMP_REMOTE_VERSION, CSR_TYPE_UINT8, 0, "remver" }, + { CSR_PSKEY_HOSTIO_USE_HCI_EXTN, CSR_TYPE_UINT16, 0, "hciextn" }, + { CSR_PSKEY_HOSTIO_MAP_SCO_PCM, CSR_TYPE_UINT16, 0, "mapsco" }, + { CSR_PSKEY_UART_BAUDRATE, CSR_TYPE_UINT16, 0, "baudrate" }, + { CSR_PSKEY_HOST_INTERFACE, CSR_TYPE_UINT16, 0, "hostintf" }, + { CSR_PSKEY_ANA_FREQ, CSR_TYPE_UINT16, 0, "anafreq" }, + { CSR_PSKEY_ANA_FTRIM, CSR_TYPE_UINT16, 0, "anaftrim" }, + { CSR_PSKEY_USB_VENDOR_ID, CSR_TYPE_UINT16, 0, "usbvid" }, + { CSR_PSKEY_USB_PRODUCT_ID, CSR_TYPE_UINT16, 0, "usbpid" }, + { CSR_PSKEY_USB_DFU_PRODUCT_ID, CSR_TYPE_UINT16, 0, "dfupid" }, + { CSR_PSKEY_INITIAL_BOOTMODE, CSR_TYPE_UINT16, 0, "bootmode" }, + { 0x0000 }, +}; + +static char *storestostr(uint16_t stores) +{ + switch (stores) { + case 0x0000: + return "Default"; + case 0x0001: + return "psi"; + case 0x0002: + return "psf"; + case 0x0004: + return "psrom"; + case 0x0008: + return "psram"; + default: + return "Unknown"; + } +} + +static char *memorytostr(uint16_t type) +{ + switch (type) { + case 0x0000: + return "Flash memory"; + case 0x0001: + return "EEPROM"; + case 0x0002: + return "RAM (transient)"; + case 0x0003: + return "ROM (or \"read-only\" flash memory)"; + default: + return "Unknown"; + } +} + +#define OPT_RANGE(min, max) \ + if (argc < (min)) { errno = EINVAL; return -1; } \ + if (argc > (max)) { errno = E2BIG; return -1; } + +static struct option help_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static int opt_help(int argc, char *argv[], int *help) +{ + int opt; + + while ((opt=getopt_long(argc, argv, "+h", help_options, NULL)) != EOF) { + switch (opt) { + case 'h': + if (help) + *help = 1; + break; + } + } + + return optind; +} + +#define OPT_HELP(range, help) \ + opt_help(argc, argv, (help)); \ + argc -= optind; argv += optind; optind = 0; \ + OPT_RANGE((range), (range)) + +static int cmd_builddef(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t def = 0x0000, nextdef = 0x0000; + int err = 0; + + OPT_HELP(0, NULL); + + printf("Build definitions:\n"); + + while (1) { + memset(array, 0, sizeof(array)); + array[0] = def & 0xff; + array[1] = def >> 8; + + err = transport_read(transport, CSR_VARID_GET_NEXT_BUILDDEF, array, 8); + if (err < 0) + break; + + nextdef = array[2] | (array[3] << 8); + + if (nextdef == 0x0000) + break; + + def = nextdef; + + printf("0x%04x - %s\n", def, csr_builddeftostr(def)); + } + + return err; +} + +static int cmd_keylen(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t handle, keylen; + int err; + + OPT_HELP(1, NULL); + + handle = atoi(argv[0]); + + memset(array, 0, sizeof(array)); + array[0] = handle & 0xff; + array[1] = handle >> 8; + + err = transport_read(transport, CSR_VARID_CRYPT_KEY_LENGTH, array, 8); + if (err < 0) + return -1; + + keylen = array[2] | (array[3] << 8); + + printf("Crypt key length: %d bit\n", keylen * 8); + + return 0; +} + +static int cmd_clock(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint32_t clock; + int err; + + OPT_HELP(0, NULL); + + memset(array, 0, sizeof(array)); + + err = transport_read(transport, CSR_VARID_BT_CLOCK, array, 8); + if (err < 0) + return -1; + + clock = array[2] | (array[3] << 8) | (array[0] << 16) | (array[1] << 24); + + printf("Bluetooth clock: 0x%04x (%d)\n", clock, clock); + + return 0; +} + +static int cmd_rand(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t rand; + int err; + + OPT_HELP(0, NULL); + + memset(array, 0, sizeof(array)); + + err = transport_read(transport, CSR_VARID_RAND, array, 8); + if (err < 0) + return -1; + + rand = array[0] | (array[1] << 8); + + printf("Random number: 0x%02x (%d)\n", rand, rand); + + return 0; +} + +static int cmd_chiprev(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t rev; + char *str; + int err; + + OPT_HELP(0, NULL); + + memset(array, 0, sizeof(array)); + + err = transport_read(transport, CSR_VARID_CHIPREV, array, 8); + if (err < 0) + return -1; + + rev = array[0] | (array[1] << 8); + + switch (rev) { + case 0x64: + str = "BC1 ES"; + break; + case 0x65: + str = "BC1"; + break; + case 0x89: + str = "BC2-External A"; + break; + case 0x8a: + str = "BC2-External B"; + break; + case 0x28: + str = "BC2-ROM"; + break; + case 0x43: + str = "BC3-Multimedia"; + break; + case 0x15: + str = "BC3-ROM"; + break; + case 0xe2: + str = "BC3-Flash"; + break; + case 0x26: + str = "BC4-External"; + break; + case 0x30: + str = "BC4-ROM"; + break; + default: + str = "NA"; + break; + } + + printf("Chip revision: 0x%04x (%s)\n", rev, str); + + return 0; +} + +static int cmd_buildname(int transport, int argc, char *argv[]) +{ + uint8_t array[131]; + char name[64]; + unsigned int i; + int err; + + OPT_HELP(0, NULL); + + memset(array, 0, sizeof(array)); + + err = transport_read(transport, CSR_VARID_READ_BUILD_NAME, array, 128); + if (err < 0) + return -1; + + for (i = 0; i < sizeof(name); i++) + name[i] = array[(i * 2) + 4]; + + printf("Build name: %s\n", name); + + return 0; +} + +static int cmd_panicarg(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t error; + int err; + + OPT_HELP(0, NULL); + + memset(array, 0, sizeof(array)); + + err = transport_read(transport, CSR_VARID_PANIC_ARG, array, 8); + if (err < 0) + return -1; + + error = array[0] | (array[1] << 8); + + printf("Panic code: 0x%02x (%s)\n", error, + error < 0x100 ? "valid" : "invalid"); + + return 0; +} + +static int cmd_faultarg(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t error; + int err; + + OPT_HELP(0, NULL); + + memset(array, 0, sizeof(array)); + + err = transport_read(transport, CSR_VARID_FAULT_ARG, array, 8); + if (err < 0) + return -1; + + error = array[0] | (array[1] << 8); + + printf("Fault code: 0x%02x (%s)\n", error, + error < 0x100 ? "valid" : "invalid"); + + return 0; +} + +static int cmd_coldreset(int transport, int argc, char *argv[]) +{ + return transport_write(transport, CSR_VARID_COLD_RESET, NULL, 0); +} + +static int cmd_warmreset(int transport, int argc, char *argv[]) +{ + return transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0); +} + +static int cmd_disabletx(int transport, int argc, char *argv[]) +{ + return transport_write(transport, CSR_VARID_DISABLE_TX, NULL, 0); +} + +static int cmd_enabletx(int transport, int argc, char *argv[]) +{ + return transport_write(transport, CSR_VARID_ENABLE_TX, NULL, 0); +} + +static int cmd_singlechan(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t channel; + + OPT_HELP(1, NULL); + + channel = atoi(argv[0]); + + if (channel > 2401 && channel < 2481) + channel -= 2402; + + if (channel > 78) { + errno = EINVAL; + return -1; + } + + memset(array, 0, sizeof(array)); + array[0] = channel & 0xff; + array[1] = channel >> 8; + + return transport_write(transport, CSR_VARID_SINGLE_CHAN, array, 8); +} + +static int cmd_hoppingon(int transport, int argc, char *argv[]) +{ + return transport_write(transport, CSR_VARID_HOPPING_ON, NULL, 0); +} + +static int cmd_rttxdata1(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t freq, level; + + OPT_HELP(2, NULL); + + freq = atoi(argv[0]); + + if (!strncasecmp(argv[1], "0x", 2)) + level = strtol(argv[1], NULL, 16); + else + level = atoi(argv[1]); + + memset(array, 0, sizeof(array)); + array[0] = 0x04; + array[1] = 0x00; + array[2] = freq & 0xff; + array[3] = freq >> 8; + array[4] = level & 0xff; + array[5] = level >> 8; + + return transport_write(transport, CSR_VARID_RADIOTEST, array, 8); +} + +static int cmd_radiotest(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t freq, level, test; + + OPT_HELP(3, NULL); + + freq = atoi(argv[0]); + + if (!strncasecmp(argv[1], "0x", 2)) + level = strtol(argv[1], NULL, 16); + else + level = atoi(argv[1]); + + test = atoi(argv[2]); + + memset(array, 0, sizeof(array)); + array[0] = test & 0xff; + array[1] = test >> 8; + array[2] = freq & 0xff; + array[3] = freq >> 8; + array[4] = level & 0xff; + array[5] = level >> 8; + + return transport_write(transport, CSR_VARID_RADIOTEST, array, 8); +} + +static int cmd_memtypes(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t type, stores[4] = { 0x0001, 0x0002, 0x0004, 0x0008 }; + int i, err; + + OPT_HELP(0, NULL); + + for (i = 0; i < 4; i++) { + memset(array, 0, sizeof(array)); + array[0] = stores[i] & 0xff; + array[1] = stores[i] >> 8; + + err = transport_read(transport, CSR_VARID_PS_MEMORY_TYPE, array, 8); + if (err < 0) + continue; + + type = array[2] + (array[3] << 8); + + printf("%s (0x%04x) = %s (%d)\n", storestostr(stores[i]), + stores[i], memorytostr(type), type); + } + + return 0; +} + +static struct option pskey_options[] = { + { "stores", 1, 0, 's' }, + { "reset", 0, 0, 'r' }, + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static int opt_pskey(int argc, char *argv[], uint16_t *stores, int *reset, int *help) +{ + int opt; + + while ((opt=getopt_long(argc, argv, "+s:rh", pskey_options, NULL)) != EOF) { + switch (opt) { + case 's': + if (!stores) + break; + if (!strcasecmp(optarg, "default")) + *stores = 0x0000; + else if (!strcasecmp(optarg, "implementation")) + *stores = 0x0001; + else if (!strcasecmp(optarg, "factory")) + *stores = 0x0002; + else if (!strcasecmp(optarg, "rom")) + *stores = 0x0004; + else if (!strcasecmp(optarg, "ram")) + *stores = 0x0008; + else if (!strcasecmp(optarg, "psi")) + *stores = 0x0001; + else if (!strcasecmp(optarg, "psf")) + *stores = 0x0002; + else if (!strcasecmp(optarg, "psrom")) + *stores = 0x0004; + else if (!strcasecmp(optarg, "psram")) + *stores = 0x0008; + else if (!strncasecmp(optarg, "0x", 2)) + *stores = strtol(optarg, NULL, 16); + else + *stores = atoi(optarg); + break; + + case 'r': + if (reset) + *reset = 1; + break; + + case 'h': + if (help) + *help = 1; + break; + } + } + + return optind; +} + +#define OPT_PSKEY(min, max, stores, reset, help) \ + opt_pskey(argc, argv, (stores), (reset), (help)); \ + argc -= optind; argv += optind; optind = 0; \ + OPT_RANGE((min), (max)) + +static int cmd_psget(int transport, int argc, char *argv[]) +{ + uint8_t array[128]; + uint16_t pskey, length, value, stores = CSR_STORES_DEFAULT; + uint32_t val32; + int i, err, reset = 0; + + memset(array, 0, sizeof(array)); + + OPT_PSKEY(1, 1, &stores, &reset, NULL); + + if (strncasecmp(argv[0], "0x", 2)) { + pskey = atoi(argv[0]); + + for (i = 0; storage[i].pskey; i++) { + if (strcasecmp(storage[i].str, argv[0])) + continue; + + pskey = storage[i].pskey; + break; + } + } else + pskey = strtol(argv[0] + 2, NULL, 16); + + memset(array, 0, sizeof(array)); + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = stores & 0xff; + array[3] = stores >> 8; + + err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8); + if (err < 0) + return err; + + length = array[2] + (array[3] << 8); + if (length + 6 > (int) sizeof(array) / 2) + return -EIO; + + memset(array, 0, sizeof(array)); + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = length & 0xff; + array[3] = length >> 8; + array[4] = stores & 0xff; + array[5] = stores >> 8; + + err = transport_read(transport, CSR_VARID_PS, array, (length + 3) * 2); + if (err < 0) + return err; + + switch (length) { + case 1: + value = array[6] | (array[7] << 8); + printf("%s: 0x%04x (%d)\n", csr_pskeytostr(pskey), value, value); + break; + + case 2: + val32 = array[8] | (array[9] << 8) | (array[6] << 16) | (array[7] << 24); + printf("%s: 0x%08x (%d)\n", csr_pskeytostr(pskey), val32, val32); + break; + + default: + printf("%s:", csr_pskeytostr(pskey)); + for (i = 0; i < length; i++) + printf(" 0x%02x%02x", array[(i * 2) + 6], array[(i * 2) + 7]); + printf("\n"); + break; + } + + if (reset) + transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0); + + return err; +} + +static int cmd_psset(int transport, int argc, char *argv[]) +{ + uint8_t array[128]; + uint16_t pskey, length, value, stores = CSR_STORES_PSRAM; + uint32_t val32; + int i, err, reset = 0; + + memset(array, 0, sizeof(array)); + + OPT_PSKEY(2, 81, &stores, &reset, NULL); + + if (strncasecmp(argv[0], "0x", 2)) { + pskey = atoi(argv[0]); + + for (i = 0; storage[i].pskey; i++) { + if (strcasecmp(storage[i].str, argv[0])) + continue; + + pskey = storage[i].pskey; + break; + } + } else + pskey = strtol(argv[0] + 2, NULL, 16); + + memset(array, 0, sizeof(array)); + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = stores & 0xff; + array[3] = stores >> 8; + + err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8); + if (err < 0) + return err; + + length = array[2] + (array[3] << 8); + if (length + 6 > (int) sizeof(array) / 2) + return -EIO; + + memset(array, 0, sizeof(array)); + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = length & 0xff; + array[3] = length >> 8; + array[4] = stores & 0xff; + array[5] = stores >> 8; + + argc--; + argv++; + + switch (length) { + case 1: + if (argc != 1) { + errno = E2BIG; + return -1; + } + + if (!strncasecmp(argv[0], "0x", 2)) + value = strtol(argv[0] + 2, NULL, 16); + else + value = atoi(argv[0]); + + array[6] = value & 0xff; + array[7] = value >> 8; + break; + + case 2: + if (argc != 1) { + errno = E2BIG; + return -1; + } + + if (!strncasecmp(argv[0], "0x", 2)) + val32 = strtol(argv[0] + 2, NULL, 16); + else + val32 = atoi(argv[0]); + + array[6] = (val32 & 0xff0000) >> 16; + array[7] = val32 >> 24; + array[8] = val32 & 0xff; + array[9] = (val32 & 0xff00) >> 8; + break; + + default: + if (argc != length * 2) { + errno = EINVAL; + return -1; + } + + for (i = 0; i < length * 2; i++) + if (!strncasecmp(argv[0], "0x", 2)) + array[i + 6] = strtol(argv[i] + 2, NULL, 16); + else + array[i + 6] = atoi(argv[i]); + break; + } + + err = transport_write(transport, CSR_VARID_PS, array, (length + 3) * 2); + if (err < 0) + return err; + + if (reset) + transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0); + + return err; +} + +static int cmd_psclr(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t pskey, stores = CSR_STORES_PSRAM; + int i, err, reset = 0; + + OPT_PSKEY(1, 1, &stores, &reset, NULL); + + if (strncasecmp(argv[0], "0x", 2)) { + pskey = atoi(argv[0]); + + for (i = 0; storage[i].pskey; i++) { + if (strcasecmp(storage[i].str, argv[0])) + continue; + + pskey = storage[i].pskey; + break; + } + } else + pskey = strtol(argv[0] + 2, NULL, 16); + + memset(array, 0, sizeof(array)); + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = stores & 0xff; + array[3] = stores >> 8; + + err = transport_write(transport, CSR_VARID_PS_CLR_STORES, array, 8); + if (err < 0) + return err; + + if (reset) + transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0); + + return err; +} + +static int cmd_pslist(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t pskey = 0x0000, length, stores = CSR_STORES_DEFAULT; + int err, reset = 0; + + OPT_PSKEY(0, 0, &stores, &reset, NULL); + + while (1) { + memset(array, 0, sizeof(array)); + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = stores & 0xff; + array[3] = stores >> 8; + + err = transport_read(transport, CSR_VARID_PS_NEXT, array, 8); + if (err < 0) + break; + + pskey = array[4] + (array[5] << 8); + if (pskey == 0x0000) + break; + + memset(array, 0, sizeof(array)); + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = stores & 0xff; + array[3] = stores >> 8; + + err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8); + if (err < 0) + continue; + + length = array[2] + (array[3] << 8); + + printf("0x%04x - %s (%d bytes)\n", pskey, + csr_pskeytostr(pskey), length * 2); + } + + if (reset) + transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0); + + return 0; +} + +static int cmd_psread(int transport, int argc, char *argv[]) +{ + uint8_t array[256]; + uint16_t pskey = 0x0000, length, stores = CSR_STORES_DEFAULT; + char *str, val[7]; + int i, err, reset = 0; + + OPT_PSKEY(0, 0, &stores, &reset, NULL); + + while (1) { + memset(array, 0, sizeof(array)); + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = stores & 0xff; + array[3] = stores >> 8; + + err = transport_read(transport, CSR_VARID_PS_NEXT, array, 8); + if (err < 0) + break; + + pskey = array[4] + (array[5] << 8); + if (pskey == 0x0000) + break; + + memset(array, 0, sizeof(array)); + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = stores & 0xff; + array[3] = stores >> 8; + + err = transport_read(transport, CSR_VARID_PS_SIZE, array, 8); + if (err < 0) + continue; + + length = array[2] + (array[3] << 8); + if (length + 6 > (int) sizeof(array) / 2) + continue; + + memset(array, 0, sizeof(array)); + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = length & 0xff; + array[3] = length >> 8; + array[4] = stores & 0xff; + array[5] = stores >> 8; + + err = transport_read(transport, CSR_VARID_PS, array, (length + 3) * 2); + if (err < 0) + continue; + + str = csr_pskeytoval(pskey); + if (!strcasecmp(str, "UNKNOWN")) { + sprintf(val, "0x%04x", pskey); + str = NULL; + } + + printf("// %s%s\n&%04x =", str ? "PSKEY_" : "", + str ? str : val, pskey); + for (i = 0; i < length; i++) + printf(" %02x%02x", array[(i * 2) + 7], array[(i * 2) + 6]); + printf("\n"); + } + + if (reset) + transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0); + + return 0; +} + +static int cmd_psload(int transport, int argc, char *argv[]) +{ + uint8_t array[256]; + uint16_t pskey, length, size, stores = CSR_STORES_PSRAM; + char *str, val[7]; + int err, reset = 0; + + OPT_PSKEY(1, 1, &stores, &reset, NULL); + + psr_read(argv[0]); + + memset(array, 0, sizeof(array)); + size = sizeof(array) - 6; + + while (psr_get(&pskey, array + 6, &size) == 0) { + str = csr_pskeytoval(pskey); + if (!strcasecmp(str, "UNKNOWN")) { + sprintf(val, "0x%04x", pskey); + str = NULL; + } + + printf("Loading %s%s ... ", str ? "PSKEY_" : "", + str ? str : val); + fflush(stdout); + + length = size / 2; + + array[0] = pskey & 0xff; + array[1] = pskey >> 8; + array[2] = length & 0xff; + array[3] = length >> 8; + array[4] = stores & 0xff; + array[5] = stores >> 8; + + err = transport_write(transport, CSR_VARID_PS, array, size + 6); + + printf("%s\n", err < 0 ? "failed" : "done"); + + memset(array, 0, sizeof(array)); + size = sizeof(array) - 6; + } + + if (reset) + transport_write(transport, CSR_VARID_WARM_RESET, NULL, 0); + + return 0; +} + +static int cmd_pscheck(int transport, int argc, char *argv[]) +{ + uint8_t array[256]; + uint16_t pskey, size; + int i; + + OPT_HELP(1, NULL); + + psr_read(argv[0]); + + while (psr_get(&pskey, array, &size) == 0) { + printf("0x%04x =", pskey); + for (i = 0; i < size; i++) + printf(" 0x%02x", array[i]); + printf("\n"); + } + + return 0; +} + +static int cmd_adc(int transport, int argc, char *argv[]) +{ + uint8_t array[8]; + uint16_t mux, value; + int err; + + OPT_HELP(1, NULL); + + if (!strncasecmp(argv[0], "0x", 2)) + mux = strtol(argv[0], NULL, 16); + else + mux = atoi(argv[0]); + + /* Request an ADC read from a particular mux'ed input */ + memset(array, 0, sizeof(array)); + array[0] = mux & 0xff; + array[1] = mux >> 8; + + err = transport_write(transport, CSR_VARID_ADC, array, 2); + if (err < 0) { + errno = -err; + return -1; + } + + /* have to wait a short while, then read result */ + usleep(50000); + err = transport_read(transport, CSR_VARID_ADC_RES, array, 8); + if (err < 0) { + errno = -err; + return -1; + } + + mux = array[0] | (array[1] << 8); + value = array[4] | (array[5] << 8); + + printf("ADC value from Mux 0x%02x : 0x%04x (%s)\n", mux, value, + array[2] == 1 ? "valid" : "invalid"); + + return 0; +} + +static struct { + char *str; + int (*func)(int transport, int argc, char *argv[]); + char *arg; + char *doc; +} commands[] = { + { "builddef", cmd_builddef, "", "Get build definitions" }, + { "keylen", cmd_keylen, "", "Get current crypt key length" }, + { "clock", cmd_clock, "", "Get local Bluetooth clock" }, + { "rand", cmd_rand, "", "Get random number" }, + { "chiprev", cmd_chiprev, "", "Get chip revision" }, + { "buildname", cmd_buildname, "", "Get the full build name" }, + { "panicarg", cmd_panicarg, "", "Get panic code argument" }, + { "faultarg", cmd_faultarg, "", "Get fault code argument" }, + { "coldreset", cmd_coldreset, "", "Perform cold reset" }, + { "warmreset", cmd_warmreset, "", "Perform warm reset" }, + { "disabletx", cmd_disabletx, "", "Disable TX on the device" }, + { "enabletx", cmd_enabletx, "", "Enable TX on the device" }, + { "singlechan",cmd_singlechan,"", "Lock radio on specific channel" }, + { "hoppingon", cmd_hoppingon, "", "Revert to channel hopping" }, + { "rttxdata1", cmd_rttxdata1, " ", "TXData1 radio test" }, + { "radiotest", cmd_radiotest, " ", "Run radio tests" }, + { "memtypes", cmd_memtypes, NULL, "Get memory types" }, + { "psget", cmd_psget, "", "Get value for PS key" }, + { "psset", cmd_psset, " ", "Set value for PS key" }, + { "psclr", cmd_psclr, "", "Clear value for PS key" }, + { "pslist", cmd_pslist, NULL, "List all PS keys" }, + { "psread", cmd_psread, NULL, "Read all PS keys" }, + { "psload", cmd_psload, "", "Load all PS keys from PSR file" }, + { "pscheck", cmd_pscheck, "", "Check PSR file" }, + { "adc", cmd_adc, "", "Read ADC value of input" }, + { NULL } +}; + +static void usage(void) +{ + int i, pos = 0; + + printf("bccmd - Utility for the CSR BCCMD interface\n\n"); + printf("Usage:\n" + "\tbccmd [options] \n\n"); + + printf("Options:\n" + "\t-t Select the transport\n" + "\t-d Select the device\n" + "\t-b Select the bcsp transfer rate\n" + "\t-h, --help Display help\n" + "\n"); + + printf("Transports:\n" + "\tHCI USB BCSP H4 3WIRE\n\n"); + + printf("Commands:\n"); + for (i = 0; commands[i].str; i++) + printf("\t%-10s %-20s\t%s\n", commands[i].str, + commands[i].arg ? commands[i].arg : " ", + commands[i].doc); + printf("\n"); + + printf("Keys:\n\t"); + for (i = 0; storage[i].pskey; i++) { + printf("%s ", storage[i].str); + pos += strlen(storage[i].str) + 1; + if (pos > 60) { + printf("\n\t"); + pos = 0; + } + } + printf("\n"); +} + +static struct option main_options[] = { + { "transport", 1, 0, 't' }, + { "device", 1, 0, 'd' }, + { "bcsprate", 1, 0, 'b'}, + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + char *device = NULL; + int i, err, opt, transport = CSR_TRANSPORT_HCI; + speed_t bcsp_rate = B38400; + + while ((opt=getopt_long(argc, argv, "+t:d:i:b:h", main_options, NULL)) != EOF) { + switch (opt) { + case 't': + if (!strcasecmp(optarg, "hci")) + transport = CSR_TRANSPORT_HCI; + else if (!strcasecmp(optarg, "usb")) + transport = CSR_TRANSPORT_USB; + else if (!strcasecmp(optarg, "bcsp")) + transport = CSR_TRANSPORT_BCSP; + else if (!strcasecmp(optarg, "h4")) + transport = CSR_TRANSPORT_H4; + else if (!strcasecmp(optarg, "h5")) + transport = CSR_TRANSPORT_3WIRE; + else if (!strcasecmp(optarg, "3wire")) + transport = CSR_TRANSPORT_3WIRE; + else if (!strcasecmp(optarg, "twutl")) + transport = CSR_TRANSPORT_3WIRE; + else + transport = CSR_TRANSPORT_UNKNOWN; + break; + + case 'd': + case 'i': + device = strdup(optarg); + break; + case 'b': + bcsp_rate = tty_get_speed(atoi(optarg)); + if (!bcsp_rate) { + printf("Unknown BCSP baud rate specified, defaulting to 38400bps\n"); + bcsp_rate = B38400; + } + break; + case 'h': + default: + usage(); + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + usage(); + exit(1); + } + + if (transport_open(transport, device, bcsp_rate) < 0) + exit(1); + + free(device); + + for (i = 0; commands[i].str; i++) { + if (strcasecmp(commands[i].str, argv[0])) + continue; + + err = commands[i].func(transport, argc, argv); + + transport_close(transport); + + if (err < 0) { + fprintf(stderr, "Can't execute command: %s (%d)\n", + strerror(errno), errno); + exit(1); + } + + exit(0); + } + + fprintf(stderr, "Unsupported command\n"); + + transport_close(transport); + + exit(1); +} diff --git a/tools/bcmfw.c b/tools/bcmfw.c new file mode 100644 index 0000000..80d8e71 --- /dev/null +++ b/tools/bcmfw.c @@ -0,0 +1,165 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-2013 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void print_cmd(uint16_t opcode, const uint8_t *buf, uint8_t plen) +{ + switch (opcode) { + case 0xfc4c: + printf(" Write_RAM [address=0x%08x]", + buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24); + break; + case 0xfc4e: + printf(" Launch_RAM [address=0x%08x]", + buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24); + break; + } +} + +static void analyze_memory(const uint8_t *buf, size_t len) +{ + const uint8_t *ptr = buf; + + while (len >= 3) { + uint16_t opcode = ptr[0] | ptr[1] << 8; + uint8_t plen = ptr[2]; + + ptr += 3; + len -= 3; + + if (len < plen) { + fprintf(stderr, "Corrupted file\n"); + break; + } + + printf("opcode=0x%04x plen=%-3u", opcode, plen); + print_cmd(opcode, ptr + 3, plen); + printf("\n"); + + ptr += plen; + len -= plen; + } +} + +static void analyze_file(const char *pathname) +{ + struct stat st; + void *map; + int fd; + + printf("Analyzing %s\n", pathname); + + fd = open(pathname, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + perror("Failed to open file"); + return; + } + + if (fstat(fd, &st) < 0) { + fprintf(stderr, "Failed get file size\n"); + close(fd); + return; + } + + if (st.st_size == 0) { + fprintf(stderr, "Empty file\n"); + close(fd); + return; + } + + map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + fprintf(stderr, "Failed to map file\n"); + close(fd); + return; + } + + analyze_memory(map, st.st_size); + + munmap(map, st.st_size); + close(fd); +} + +static void usage(void) +{ + printf("Broadcom Bluetooth firmware analyzer\n" + "Usage:\n"); + printf("\tbcmfw [options] \n"); + printf("Options:\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + int i; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "vh", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind < 1) { + fprintf(stderr, "No input firmware files provided\n"); + return EXIT_FAILURE; + } + + for (i = optind; i < argc; i++) + analyze_file(argv[i]); + + return EXIT_SUCCESS; +} diff --git a/tools/bdaddr.1 b/tools/bdaddr.1 new file mode 100644 index 0000000..efb77d2 --- /dev/null +++ b/tools/bdaddr.1 @@ -0,0 +1,68 @@ +.TH BDADDR 1 "Sep 27 2005" BlueZ "Linux System Administration" +.SH NAME +bdaddr \- Utility for changing the Bluetooth device address +.SH SYNOPSIS +.B bdaddr +.br +.B bdaddr -h +.br +.B bdaddr [-i ] [-r] [-t] [new bdaddr] + +.SH DESCRIPTION +.LP +.B +bdaddr +is used to query or set the local Bluetooth device address (BD_ADDR). If run +with no arguments, +.B +bdaddr +prints the chip manufacturer's name, and the current BD_ADDR. If the IEEE OUI +index file "oui.txt" is installed on the system, the BD_ADDR owner will be +displayed. If the optional [new bdaddr] argument is given, the device will be +reprogrammed with that address. This can either be permanent or temporary, as +specified by the -t flag. In both cases, the device must be reset before the +new address will become active. This can be done with a 'soft' reset by +specifying the -r flag, or a 'hard' reset by removing and replugging the +device. A 'hard' reset will cause the address to revert to the current +non-volatile value. +.PP +.B +bdaddr +uses manufacturer specific commands to set the address, and is therefore +device specific. For this reason, not all devices are supported, and not all +options are supported on all devices. +Current supported manufacturers are: +.B Ericsson, Cambridge Silicon Radio (CSR), Texas Instruments (TI), Zeevo +and +.B ST Microelectronics (ST) + +.SH OPTIONS +.TP +.BI -h +Gives a list of possible commands. +.TP +.BI -i\ +Specify a particular device to operate on. If not specified, default is the +first available device. +.TP +.BI -r +Reset device and make new BD_ADDR active. +.B +CSR +devices only. +.TP +.BI -t +Temporary change. Do not write to non-volatile memory. +.B +CSR +devices only. +.SH FILES +.TP +.I +/usr/share/misc/oui.txt +IEEE Organizationally Unique Identifier master file. +Manually update from: http://standards.ieee.org/regauth/oui/oui.txt +.SH AUTHORS +Written by Marcel Holtmann , +man page by Adam Laurie +.PP diff --git a/tools/bdaddr.c b/tools/bdaddr.c new file mode 100644 index 0000000..952e990 --- /dev/null +++ b/tools/bdaddr.c @@ -0,0 +1,479 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "src/oui.h" + +static int transient = 0; + +static int generic_reset_device(int dd) +{ + bdaddr_t bdaddr; + int err; + + err = hci_send_cmd(dd, 0x03, 0x0003, 0, NULL); + if (err < 0) + return err; + + return hci_read_bd_addr(dd, &bdaddr, 10000); +} + +#define OCF_ERICSSON_WRITE_BD_ADDR 0x000d +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) ericsson_write_bd_addr_cp; + +static int ericsson_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + struct hci_request rq; + ericsson_write_bd_addr_cp cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_ERICSSON_WRITE_BD_ADDR; + rq.cparam = &cp; + rq.clen = sizeof(cp); + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +#define OCF_ERICSSON_STORE_IN_FLASH 0x0022 +typedef struct { + uint8_t user_id; + uint8_t flash_length; + uint8_t flash_data[253]; +} __attribute__ ((packed)) ericsson_store_in_flash_cp; + +static int ericsson_store_in_flash(int dd, uint8_t user_id, uint8_t flash_length, uint8_t *flash_data) +{ + struct hci_request rq; + ericsson_store_in_flash_cp cp; + + memset(&cp, 0, sizeof(cp)); + cp.user_id = user_id; + cp.flash_length = flash_length; + if (flash_length > 0) + memcpy(cp.flash_data, flash_data, flash_length); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_ERICSSON_STORE_IN_FLASH; + rq.cparam = &cp; + rq.clen = sizeof(cp); + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +static int csr_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + unsigned char cmd[] = { 0x02, 0x00, 0x0c, 0x00, 0x11, 0x47, 0x03, 0x70, + 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + if (transient) + cmd[14] = 0x08; + + cmd[16] = bdaddr->b[2]; + cmd[17] = 0x00; + cmd[18] = bdaddr->b[0]; + cmd[19] = bdaddr->b[1]; + cmd[20] = bdaddr->b[3]; + cmd[21] = 0x00; + cmd[22] = bdaddr->b[4]; + cmd[23] = bdaddr->b[5]; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + if (rp[0] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[9] + (rp[10] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + return 0; +} + +static int csr_reset_device(int dd) +{ + unsigned char cmd[] = { 0x02, 0x00, 0x09, 0x00, + 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + if (transient) + cmd[6] = 0x02; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + return 0; +} + +#define OCF_TI_WRITE_BD_ADDR 0x0006 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) ti_write_bd_addr_cp; + +static int ti_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + struct hci_request rq; + ti_write_bd_addr_cp cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_TI_WRITE_BD_ADDR; + rq.cparam = &cp; + rq.clen = sizeof(cp); + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +#define OCF_BCM_WRITE_BD_ADDR 0x0001 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) bcm_write_bd_addr_cp; + +static int bcm_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + struct hci_request rq; + bcm_write_bd_addr_cp cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_BCM_WRITE_BD_ADDR; + rq.cparam = &cp; + rq.clen = sizeof(cp); + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +#define OCF_ZEEVO_WRITE_BD_ADDR 0x0001 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) zeevo_write_bd_addr_cp; + +static int zeevo_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + struct hci_request rq; + zeevo_write_bd_addr_cp cp; + + memset(&cp, 0, sizeof(cp)); + bacpy(&cp.bdaddr, bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = OCF_ZEEVO_WRITE_BD_ADDR; + rq.cparam = &cp; + rq.clen = sizeof(cp); + rq.rparam = NULL; + rq.rlen = 0; + + if (hci_send_req(dd, &rq, 1000) < 0) + return -1; + + return 0; +} + +#define OCF_MRVL_WRITE_BD_ADDR 0x0022 +typedef struct { + uint8_t parameter_id; + uint8_t bdaddr_len; + bdaddr_t bdaddr; +} __attribute__ ((packed)) mrvl_write_bd_addr_cp; + +static int mrvl_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + mrvl_write_bd_addr_cp cp; + + memset(&cp, 0, sizeof(cp)); + cp.parameter_id = 0xFE; + cp.bdaddr_len = 6; + bacpy(&cp.bdaddr, bdaddr); + + if (hci_send_cmd(dd, OGF_VENDOR_CMD, OCF_MRVL_WRITE_BD_ADDR, + sizeof(cp), &cp) < 0) + return -1; + + sleep(1); + return 0; +} + +static int st_write_bd_addr(int dd, bdaddr_t *bdaddr) +{ + return ericsson_store_in_flash(dd, 0xfe, 6, (uint8_t *) bdaddr); +} + +static struct { + uint16_t compid; + int (*write_bd_addr)(int dd, bdaddr_t *bdaddr); + int (*reset_device)(int dd); +} vendor[] = { + { 0, ericsson_write_bd_addr, NULL }, + { 10, csr_write_bd_addr, csr_reset_device }, + { 13, ti_write_bd_addr, NULL }, + { 15, bcm_write_bd_addr, generic_reset_device }, + { 18, zeevo_write_bd_addr, NULL }, + { 48, st_write_bd_addr, generic_reset_device }, + { 57, ericsson_write_bd_addr, generic_reset_device }, + { 72, mrvl_write_bd_addr, generic_reset_device }, + { 65535, NULL, NULL }, +}; + +static void usage(void) +{ + printf("bdaddr - Utility for changing the Bluetooth device address\n\n"); + printf("Usage:\n" + "\tbdaddr [-i ] [-r] [-t] [new bdaddr]\n"); +} + +static struct option main_options[] = { + { "device", 1, 0, 'i' }, + { "reset", 0, 0, 'r' }, + { "transient", 0, 0, 't' }, + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + struct hci_dev_info di; + struct hci_version ver; + bdaddr_t bdaddr; + char addr[18], *comp; + int i, dd, opt, dev = 0, reset = 0; + + bacpy(&bdaddr, BDADDR_ANY); + + while ((opt=getopt_long(argc, argv, "+i:rth", main_options, NULL)) != -1) { + switch (opt) { + case 'i': + dev = hci_devid(optarg); + if (dev < 0) { + perror("Invalid device"); + exit(1); + } + break; + + case 'r': + reset = 1; + break; + + case 't': + transient = 1; + break; + + case 'h': + default: + usage(); + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + dd = hci_open_dev(dev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + dev, strerror(errno), errno); + exit(1); + } + + if (hci_devinfo(dev, &di) < 0) { + fprintf(stderr, "Can't get device info for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + exit(1); + } + + if (hci_read_local_version(dd, &ver, 1000) < 0) { + fprintf(stderr, "Can't read version info for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + exit(1); + } + + if (!bacmp(&di.bdaddr, BDADDR_ANY)) { + if (hci_read_bd_addr(dd, &bdaddr, 1000) < 0) { + fprintf(stderr, "Can't read address for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + exit(1); + } + } else + bacpy(&bdaddr, &di.bdaddr); + + printf("Manufacturer: %s (%d)\n", + bt_compidtostr(ver.manufacturer), ver.manufacturer); + + comp = batocomp(&bdaddr); + + ba2str(&bdaddr, addr); + printf("Device address: %s", addr); + + if (comp) { + printf(" (%s)\n", comp); + free(comp); + } else + printf("\n"); + + if (argc < 1) { + hci_close_dev(dd); + exit(0); + } + + str2ba(argv[0], &bdaddr); + if (!bacmp(&bdaddr, BDADDR_ANY)) { + hci_close_dev(dd); + exit(0); + } + + for (i = 0; vendor[i].compid != 65535; i++) + if (ver.manufacturer == vendor[i].compid) { + comp = batocomp(&bdaddr); + + ba2str(&bdaddr, addr); + printf("New BD address: %s", addr); + + if (comp) { + printf(" (%s)\n\n", comp); + free(comp); + } else + printf("\n\n"); + + + if (vendor[i].write_bd_addr(dd, &bdaddr) < 0) { + fprintf(stderr, "Can't write new address\n"); + hci_close_dev(dd); + exit(1); + } + + printf("Address changed - "); + + if (reset && vendor[i].reset_device) { + if (vendor[i].reset_device(dd) < 0) { + printf("Reset device manually\n"); + } else { + ioctl(dd, HCIDEVRESET, dev); + printf("Device reset successfully\n"); + } + } else { + printf("Reset device now\n"); + } + + //ioctl(dd, HCIDEVRESET, dev); + //ioctl(dd, HCIDEVDOWN, dev); + //ioctl(dd, HCIDEVUP, dev); + + hci_close_dev(dd); + exit(0); + } + + hci_close_dev(dd); + + printf("\n"); + fprintf(stderr, "Unsupported manufacturer\n"); + + exit(1); +} diff --git a/tools/bluemoon.c b/tools/bluemoon.c new file mode 100644 index 0000000..8005411 --- /dev/null +++ b/tools/bluemoon.c @@ -0,0 +1,1031 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "monitor/bt.h" +#include "src/shared/mainloop.h" +#include "src/shared/util.h" +#include "src/shared/hci.h" + +#define CMD_RESET 0xfc01 +struct cmd_reset { + uint8_t reset_type; + uint8_t patch_enable; + uint8_t otp_ddc_reload; + uint8_t boot_option; + uint32_t boot_addr; +} __attribute__ ((packed)); + +#define CMD_NO_OPERATION 0xfc02 + +#define CMD_READ_VERSION 0xfc05 +struct rsp_read_version { + uint8_t status; + uint8_t hw_platform; + uint8_t hw_variant; + uint8_t hw_revision; + uint8_t fw_variant; + uint8_t fw_revision; + uint8_t fw_build_nn; + uint8_t fw_build_cw; + uint8_t fw_build_yy; + uint8_t fw_patch; +} __attribute__ ((packed)); + +#define CMD_READ_BOOT_PARAMS 0xfc0d +struct rsp_read_boot_params { + uint8_t status; + uint8_t otp_format; + uint8_t otp_content; + uint8_t otp_patch; + uint16_t dev_revid; + uint8_t secure_boot; + uint8_t key_from_hdr; + uint8_t key_type; + uint8_t otp_lock; + uint8_t api_lock; + uint8_t debug_lock; + uint8_t otp_bdaddr[6]; + uint8_t min_fw_build_nn; + uint8_t min_fw_build_cw; + uint8_t min_fw_build_yy; + uint8_t limited_cce; + uint8_t unlocked_state; +} __attribute__ ((packed)); + +#define CMD_WRITE_BOOT_PARAMS 0xfc0e +struct cmd_write_boot_params { + uint32_t boot_addr; + uint8_t fw_build_nn; + uint8_t fw_build_cw; + uint8_t fw_build_yy; +} __attribute__ ((packed)); + +#define CMD_MANUFACTURER_MODE 0xfc11 +struct cmd_manufacturer_mode { + uint8_t mode_switch; + uint8_t reset; +} __attribute__ ((packed)); + +#define CMD_WRITE_BD_DATA 0xfc2f +struct cmd_write_bd_data { + uint8_t bdaddr[6]; + uint8_t reserved1[6]; + uint8_t features[8]; + uint8_t le_features; + uint8_t reserved2[32]; + uint8_t lmp_version; + uint8_t reserved3[26]; +} __attribute__ ((packed)); + +#define CMD_READ_BD_DATA 0xfc30 +struct rsp_read_bd_data { + uint8_t status; + uint8_t bdaddr[6]; + uint8_t reserved1[6]; + uint8_t features[8]; + uint8_t le_features; + uint8_t reserved2[32]; + uint8_t lmp_version; + uint8_t reserved3[26]; +} __attribute__ ((packed)); + +#define CMD_WRITE_BD_ADDRESS 0xfc31 +struct cmd_write_bd_address { + uint8_t bdaddr[6]; +} __attribute__ ((packed)); + +#define CMD_ACT_DEACT_TRACES 0xfc43 +struct cmd_act_deact_traces { + uint8_t tx_trace; + uint8_t tx_arq; + uint8_t rx_trace; +} __attribute__ ((packed)); + +#define CMD_TRIGGER_EXCEPTION 0xfc4d +struct cmd_trigger_exception { + uint8_t type; +} __attribute__ ((packed)); + +#define CMD_MEMORY_WRITE 0xfc8e + +static struct bt_hci *hci_dev; +static uint16_t hci_index = 0; + +#define FIRMWARE_BASE_PATH "/lib/firmware" + +static bool set_bdaddr = false; +static const char *set_bdaddr_value = NULL; +static bool get_bddata = false; +static bool load_firmware = false; +static const char *load_firmware_value = NULL; +static uint8_t *firmware_data = NULL; +static size_t firmware_size = 0; +static size_t firmware_offset = 0; +static bool check_firmware = false; +static const char *check_firmware_value = NULL; +uint8_t manufacturer_mode_reset = 0x00; +static bool use_manufacturer_mode = false; +static bool set_traces = false; +static bool set_exception = false; +static bool reset_on_exit = false; +static bool cold_boot = false; + +static void reset_complete(const void *data, uint8_t size, void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + fprintf(stderr, "Failed to reset (0x%02x)\n", status); + mainloop_quit(); + return; + } + + mainloop_quit(); +} + +static void cold_boot_complete(const void *data, uint8_t size, void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + fprintf(stderr, "Failed to cold boot (0x%02x)\n", status); + mainloop_quit(); + return; + } + + if (reset_on_exit) { + bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, + reset_complete, NULL, NULL); + return; + } + + mainloop_quit(); +} + +static void leave_manufacturer_mode_complete(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + fprintf(stderr, "Failed to leave manufacturer mode (0x%02x)\n", + status); + mainloop_quit(); + return; + } + + if (reset_on_exit) { + bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, + reset_complete, NULL, NULL); + return; + } + + mainloop_quit(); +} + +static void shutdown_device(void) +{ + bt_hci_flush(hci_dev); + + free(firmware_data); + + if (use_manufacturer_mode) { + struct cmd_manufacturer_mode cmd; + + cmd.mode_switch = 0x00; + cmd.reset = manufacturer_mode_reset; + + bt_hci_send(hci_dev, CMD_MANUFACTURER_MODE, &cmd, sizeof(cmd), + leave_manufacturer_mode_complete, NULL, NULL); + return; + } + + if (reset_on_exit) { + bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, + reset_complete, NULL, NULL); + return; + } + + mainloop_quit(); +} + +static void write_bd_address_complete(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + fprintf(stderr, "Failed to write address (0x%02x)\n", status); + mainloop_quit(); + return; + } + + shutdown_device(); +} + +static void read_bd_addr_complete(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_read_bd_addr *rsp = data; + struct cmd_write_bd_address cmd; + + if (rsp->status) { + fprintf(stderr, "Failed to read address (0x%02x)\n", + rsp->status); + mainloop_quit(); + shutdown_device(); + return; + } + + if (set_bdaddr_value) { + fprintf(stderr, "Setting address is not supported\n"); + mainloop_quit(); + return; + } + + printf("Controller Address\n"); + printf("\tOld BD_ADDR: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X\n", + rsp->bdaddr[5], rsp->bdaddr[4], + rsp->bdaddr[3], rsp->bdaddr[2], + rsp->bdaddr[1], rsp->bdaddr[0]); + + memcpy(cmd.bdaddr, rsp->bdaddr, 6); + cmd.bdaddr[0] = (hci_index & 0xff); + + printf("\tNew BD_ADDR: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X\n", + cmd.bdaddr[5], cmd.bdaddr[4], + cmd.bdaddr[3], cmd.bdaddr[2], + cmd.bdaddr[1], cmd.bdaddr[0]); + + bt_hci_send(hci_dev, CMD_WRITE_BD_ADDRESS, &cmd, sizeof(cmd), + write_bd_address_complete, NULL, NULL); +} + +static void act_deact_traces_complete(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + fprintf(stderr, "Failed to activate traces (0x%02x)\n", status); + shutdown_device(); + return; + } + + shutdown_device(); +} + +static void act_deact_traces(void) +{ + struct cmd_act_deact_traces cmd; + + cmd.tx_trace = 0x03; + cmd.tx_arq = 0x03; + cmd.rx_trace = 0x03; + + bt_hci_send(hci_dev, CMD_ACT_DEACT_TRACES, &cmd, sizeof(cmd), + act_deact_traces_complete, NULL, NULL); +} + +static void trigger_exception(void) +{ + struct cmd_trigger_exception cmd; + + cmd.type = 0x00; + + bt_hci_send(hci_dev, CMD_TRIGGER_EXCEPTION, &cmd, sizeof(cmd), + NULL, NULL, NULL); + + shutdown_device(); +} + +static void write_bd_data_complete(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + fprintf(stderr, "Failed to write data (0x%02x)\n", status); + shutdown_device(); + return; + } + + if (set_traces) { + act_deact_traces(); + return; + } + + shutdown_device(); +} + +static void read_bd_data_complete(const void *data, uint8_t size, + void *user_data) +{ + const struct rsp_read_bd_data *rsp = data; + + if (rsp->status) { + fprintf(stderr, "Failed to read data (0x%02x)\n", rsp->status); + shutdown_device(); + return; + } + + printf("Controller Data\n"); + printf("\tBD_ADDR: %2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X\n", + rsp->bdaddr[5], rsp->bdaddr[4], + rsp->bdaddr[3], rsp->bdaddr[2], + rsp->bdaddr[1], rsp->bdaddr[0]); + + printf("\tLMP Version: %u\n", rsp->lmp_version); + printf("\tLMP Features: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x" + " 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", + rsp->features[0], rsp->features[1], + rsp->features[2], rsp->features[3], + rsp->features[4], rsp->features[5], + rsp->features[6], rsp->features[7]); + printf("\tLE Features: 0x%2.2x\n", rsp->le_features); + + if (set_bdaddr) { + struct cmd_write_bd_data cmd; + + memcpy(cmd.bdaddr, rsp->bdaddr, 6); + cmd.bdaddr[0] = (hci_index & 0xff); + cmd.lmp_version = 0x07; + memcpy(cmd.features, rsp->features, 8); + cmd.le_features = rsp->le_features; + cmd.le_features |= 0x1e; + memcpy(cmd.reserved1, rsp->reserved1, sizeof(cmd.reserved1)); + memcpy(cmd.reserved2, rsp->reserved2, sizeof(cmd.reserved2)); + memcpy(cmd.reserved3, rsp->reserved3, sizeof(cmd.reserved3)); + + bt_hci_send(hci_dev, CMD_WRITE_BD_DATA, &cmd, sizeof(cmd), + write_bd_data_complete, NULL, NULL); + return; + } + + shutdown_device(); +} + +static void firmware_command_complete(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + fprintf(stderr, "Failed to load firmware (0x%02x)\n", status); + manufacturer_mode_reset = 0x01; + shutdown_device(); + return; + } + + if (firmware_offset >= firmware_size) { + printf("Activating firmware\n"); + manufacturer_mode_reset = 0x02; + shutdown_device(); + return; + } + + if (firmware_data[firmware_offset] == 0x01) { + uint16_t opcode; + uint8_t dlen; + + opcode = firmware_data[firmware_offset + 2] << 8 | + firmware_data[firmware_offset + 1]; + dlen = firmware_data[firmware_offset + 3]; + + bt_hci_send(hci_dev, opcode, firmware_data + + firmware_offset + 4, dlen, + firmware_command_complete, NULL, NULL); + + firmware_offset += dlen + 4; + + if (firmware_data[firmware_offset] == 0x02) { + dlen = firmware_data[firmware_offset + 2]; + firmware_offset += dlen + 3; + } + } else { + fprintf(stderr, "Invalid packet in firmware\n"); + manufacturer_mode_reset = 0x01; + shutdown_device(); + } + +} + +static void enter_manufacturer_mode_complete(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + fprintf(stderr, "Failed to enter manufacturer mode (0x%02x)\n", + status); + mainloop_quit(); + return; + } + + if (load_firmware) { + uint8_t status = BT_HCI_ERR_SUCCESS; + firmware_command_complete(&status, sizeof(status), NULL); + return; + } + + if (get_bddata || set_bdaddr) { + bt_hci_send(hci_dev, CMD_READ_BD_DATA, NULL, 0, + read_bd_data_complete, NULL, NULL); + return; + } + + if (set_traces) { + act_deact_traces(); + return; + } + + if (set_exception) { + trigger_exception(); + return; + } + + shutdown_device(); +} + +static void request_firmware(const char *path) +{ + unsigned int cmd_num = 0; + unsigned int evt_num = 0; + struct stat st; + ssize_t len; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Failed to open firmware %s\n", path); + shutdown_device(); + return; + } + + if (fstat(fd, &st) < 0) { + fprintf(stderr, "Failed to get firmware size\n"); + close(fd); + shutdown_device(); + return; + } + + firmware_data = malloc(st.st_size); + if (!firmware_data) { + fprintf(stderr, "Failed to allocate firmware buffer\n"); + close(fd); + shutdown_device(); + return; + } + + len = read(fd, firmware_data, st.st_size); + if (len < 0) { + fprintf(stderr, "Failed to read firmware file\n"); + close(fd); + shutdown_device(); + return; + } + + close(fd); + + if (len < st.st_size) { + fprintf(stderr, "Firmware size does not match buffer\n"); + shutdown_device(); + return; + } + + firmware_size = len; + + if (firmware_data[0] == 0xff) + firmware_offset = 1; + + while (firmware_offset < firmware_size) { + uint16_t opcode; + uint8_t evt, dlen; + + switch (firmware_data[firmware_offset]) { + case 0x01: + opcode = firmware_data[firmware_offset + 2] << 8 | + firmware_data[firmware_offset + 1]; + dlen = firmware_data[firmware_offset + 3]; + + if (opcode != CMD_MEMORY_WRITE) + printf("Unexpected opcode 0x%02x\n", opcode); + + firmware_offset += dlen + 4; + cmd_num++; + break; + + case 0x02: + evt = firmware_data[firmware_offset + 1]; + dlen = firmware_data[firmware_offset + 2]; + + if (evt != BT_HCI_EVT_CMD_COMPLETE) + printf("Unexpected event 0x%02x\n", evt); + + firmware_offset += dlen + 3; + evt_num++; + break; + + default: + fprintf(stderr, "Invalid firmware file\n"); + shutdown_device(); + return; + } + } + + printf("Firmware with %u commands and %u events\n", cmd_num, evt_num); + + if (firmware_data[0] == 0xff) + firmware_offset = 1; +} + +static void read_boot_params_complete(const void *data, uint8_t size, + void *user_data) +{ + const struct rsp_read_boot_params *rsp = data; + + if (rsp->status) { + fprintf(stderr, "Failed to read boot params (0x%02x)\n", + rsp->status); + mainloop_quit(); + return; + } + + if (size != sizeof(*rsp)) { + fprintf(stderr, "Size mismatch for read boot params\n"); + mainloop_quit(); + return; + } + + printf("Secure Boot Parameters\n"); + printf("\tOTP Format Version:\t%u\n", rsp->otp_format); + printf("\tOTP Content Version:\t%u\n", rsp->otp_content); + printf("\tOTP ROM Patch Version:\t%u\n", rsp->otp_patch); + printf("\tDevice Revision ID:\t%u\n", le16_to_cpu(rsp->dev_revid)); + printf("\tSecure Boot Enable:\t%u\n", rsp->secure_boot); + printf("\tTake Key From Header:\t%u\n", rsp->key_from_hdr); + printf("\tRSA Key Type:\t\t%u\n", rsp->key_type); + printf("\tOTP Lock:\t\t%u\n", rsp->otp_lock); + printf("\tAPI Lock:\t\t%u\n", rsp->api_lock); + printf("\tDebug Lock:\t\t%u\n", rsp->debug_lock); + printf("\tMin FW Build Number:\t%u-%u.%u\n", rsp->min_fw_build_nn, + rsp->min_fw_build_cw, 2000 + rsp->min_fw_build_yy); + printf("\tLimited CCE to ISSC:\t%u\n", rsp->limited_cce); + printf("\tUnlocked State:\t\t%u\n", rsp->unlocked_state); + + mainloop_quit(); +} + +static const struct { + uint8_t val; + const char *str; +} hw_variant_table[] = { + { 0x06, "iBT 1.1 (XG223)" }, + { 0x07, "iBT 2.0 (WP)" }, + { 0x08, "iBT 2.5 (StP)" }, + { 0x09, "iBT 1.5 (AG610)" }, + { 0x0a, "iBT 2.1 (AG620)" }, + { 0x0b, "iBT 3.0 (LnP)" }, + { 0x0c, "iBT 3.0 (WsP)" }, + { 0x12, "iBT 3.5 (ThP)" }, + { } +}; + +static const struct { + uint8_t val; + const char *str; +} fw_variant_table[] = { + { 0x01, "iBT 1.0 - iBT 2.5" }, + { 0x06, "iBT Bootloader" }, + { 0x23, "iBT 3.x Bluetooth FW" }, + { } +}; + +static void read_version_complete(const void *data, uint8_t size, + void *user_data) +{ + const struct rsp_read_version *rsp = data; + const char *str; + int i; + + if (rsp->status) { + fprintf(stderr, "Failed to read version (0x%02x)\n", + rsp->status); + mainloop_quit(); + return; + } + + if (size != sizeof(*rsp)) { + fprintf(stderr, "Size mismatch for read version response\n"); + mainloop_quit(); + return; + } + + if (cold_boot) { + struct cmd_reset cmd; + + cmd.reset_type = 0x01; + cmd.patch_enable = 0x00; + cmd.otp_ddc_reload = 0x01; + cmd.boot_option = 0x00; + cmd.boot_addr = cpu_to_le32(0x00000000); + + bt_hci_send(hci_dev, CMD_RESET, &cmd, sizeof(cmd), + cold_boot_complete, NULL, NULL); + return; + } + + if (load_firmware) { + if (load_firmware_value) { + printf("Firmware: %s\n", load_firmware_value); + request_firmware(load_firmware_value); + } else { + char fw_name[PATH_MAX]; + + snprintf(fw_name, sizeof(fw_name), + "%s/%s/ibt-hw-%x.%x.%x-fw-%x.%x.%x.%x.%x.bseq", + FIRMWARE_BASE_PATH, "intel", + rsp->hw_platform, rsp->hw_variant, + rsp->hw_revision, rsp->fw_variant, + rsp->fw_revision, rsp->fw_build_nn, + rsp->fw_build_cw, rsp->fw_build_yy); + + printf("Firmware: %s\n", fw_name); + printf("Patch level: %d\n", rsp->fw_patch); + request_firmware(fw_name); + } + } + + if (use_manufacturer_mode) { + struct cmd_manufacturer_mode cmd; + + cmd.mode_switch = 0x01; + cmd.reset = 0x00; + + bt_hci_send(hci_dev, CMD_MANUFACTURER_MODE, &cmd, sizeof(cmd), + enter_manufacturer_mode_complete, NULL, NULL); + return; + } + + if (set_bdaddr) { + bt_hci_send(hci_dev, BT_HCI_CMD_READ_BD_ADDR, NULL, 0, + read_bd_addr_complete, NULL, NULL); + return; + } + + printf("Controller Version Information\n"); + printf("\tHardware Platform:\t%u\n", rsp->hw_platform); + + str = "Reserved"; + + for (i = 0; hw_variant_table[i].str; i++) { + if (hw_variant_table[i].val == rsp->hw_variant) { + str = hw_variant_table[i].str; + break; + } + } + + printf("\tHardware Variant:\t%s (0x%02x)\n", str, rsp->hw_variant); + printf("\tHardware Revision:\t%u.%u\n", rsp->hw_revision >> 4, + rsp->hw_revision & 0x0f); + + str = "Reserved"; + + for (i = 0; fw_variant_table[i].str; i++) { + if (fw_variant_table[i].val == rsp->fw_variant) { + str = fw_variant_table[i].str; + break; + } + } + + printf("\tFirmware Variant:\t%s (0x%02x)\n", str, rsp->fw_variant); + printf("\tFirmware Revision:\t%u.%u\n", rsp->fw_revision >> 4, + rsp->fw_revision & 0x0f); + printf("\tFirmware Build Number:\t%u-%u.%u\n", rsp->fw_build_nn, + rsp->fw_build_cw, 2000 + rsp->fw_build_yy); + printf("\tFirmware Patch Number:\t%u\n", rsp->fw_patch); + + if (rsp->hw_variant == 0x0b && rsp->fw_variant == 0x06) { + bt_hci_send(hci_dev, CMD_READ_BOOT_PARAMS, NULL, 0, + read_boot_params_complete, NULL, NULL); + return; + } + + mainloop_quit(); +} + +struct css_hdr { + uint32_t module_type; + uint32_t header_len; + uint32_t header_version; + uint32_t module_id; + uint32_t module_vendor; + uint32_t date; + uint32_t size; + uint32_t key_size; + uint32_t modulus_size; + uint32_t exponent_size; + uint8_t reserved[88]; +} __attribute__ ((packed)); + +static void analyze_firmware(const char *path) +{ + unsigned int cmd_num = 0; + struct css_hdr *css; + struct stat st; + ssize_t len; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Failed to open firmware %s\n", path); + return; + } + + if (fstat(fd, &st) < 0) { + fprintf(stderr, "Failed to get firmware size\n"); + close(fd); + return; + } + + firmware_data = malloc(st.st_size); + if (!firmware_data) { + fprintf(stderr, "Failed to allocate firmware buffer\n"); + close(fd); + return; + } + + len = read(fd, firmware_data, st.st_size); + if (len < 0) { + fprintf(stderr, "Failed to read firmware file\n"); + close(fd); + goto done; + } + + close(fd); + + if (len != st.st_size) { + fprintf(stderr, "Failed to read complete firmware file\n"); + goto done; + } + + if ((size_t) len < sizeof(*css)) { + fprintf(stderr, "Firmware file is too short\n"); + goto done; + } + + css = (void *) firmware_data; + + printf("Module type:\t%u\n", le32_to_cpu(css->module_type)); + printf("Header len:\t%u DWORDs / %u bytes\n", + le32_to_cpu(css->header_len), + le32_to_cpu(css->header_len) * 4); + printf("Header version:\t%u.%u\n", + le32_to_cpu(css->header_version) >> 16, + le32_to_cpu(css->header_version) & 0xffff); + printf("Module ID:\t%u\n", le32_to_cpu(css->module_id)); + printf("Module vendor:\t%u\n", le32_to_cpu(css->module_vendor)); + printf("Date:\t\t%u\n", le32_to_cpu(css->date)); + printf("Size:\t\t%u DWORDs / %u bytes\n", le32_to_cpu(css->size), + le32_to_cpu(css->size) * 4); + printf("Key size:\t%u DWORDs / %u bytes\n", + le32_to_cpu(css->key_size), + le32_to_cpu(css->key_size) * 4); + printf("Modulus size:\t%u DWORDs / %u bytes\n", + le32_to_cpu(css->modulus_size), + le32_to_cpu(css->modulus_size) * 4); + printf("Exponent size:\t%u DWORDs / %u bytes\n", + le32_to_cpu(css->exponent_size), + le32_to_cpu(css->exponent_size) * 4); + printf("\n"); + + + if ((size_t) len != le32_to_cpu(css->size) * 4) { + fprintf(stderr, "CSS.size does not match file length\n"); + goto done; + } + + if (le32_to_cpu(css->header_len) != (sizeof(*css) / 4) + + le32_to_cpu(css->key_size) + + le32_to_cpu(css->modulus_size) + + le32_to_cpu(css->exponent_size)) { + fprintf(stderr, "CSS.headerLen does not match data sizes\n"); + goto done; + } + + firmware_size = le32_to_cpu(css->size) * 4; + firmware_offset = le32_to_cpu(css->header_len) * 4; + + while (firmware_offset < firmware_size) { + uint16_t opcode; + uint8_t dlen; + + opcode = get_le16(firmware_data + firmware_offset); + dlen = firmware_data[firmware_offset + 2]; + + switch (opcode) { + case CMD_NO_OPERATION: + case CMD_WRITE_BOOT_PARAMS: + case CMD_MEMORY_WRITE: + break; + default: + printf("Unexpected opcode 0x%02x\n", opcode); + break; + } + + firmware_offset += dlen + 3; + cmd_num++; + } + + printf("Firmware with %u commands\n", cmd_num); + +done: + free(firmware_data); +} + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + } +} + +static void usage(void) +{ + printf("bluemoon - Bluemoon configuration utility\n" + "Usage:\n"); + printf("\tbluemoon [options]\n"); + printf("Options:\n" + "\t-A, --bdaddr [addr] Set Bluetooth address\n" + "\t-F, --firmware [file] Load firmware\n" + "\t-C, --check Check firmware image\n" + "\t-R, --reset Reset controller\n" + "\t-B, --coldboot Cold boot controller\n" + "\t-E, --exception Trigger exception\n" + "\t-i, --index Use specified controller\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "bdaddr", optional_argument, NULL, 'A' }, + { "bddata", no_argument, NULL, 'D' }, + { "firmware", optional_argument, NULL, 'F' }, + { "check", required_argument, NULL, 'C' }, + { "traces", no_argument, NULL, 'T' }, + { "reset", no_argument, NULL, 'R' }, + { "coldboot", no_argument, NULL, 'B' }, + { "exception",no_argument, NULL, 'E' }, + { "index", required_argument, NULL, 'i' }, + { "raw", no_argument, NULL, 'r' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + const char *str; + bool use_raw = false; + int exit_status; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "A::DF::C:TRBEi:rvh", + main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'A': + if (optarg) + set_bdaddr_value = optarg; + set_bdaddr = true; + break; + case 'D': + use_manufacturer_mode = true; + get_bddata = true; + break; + case 'F': + use_manufacturer_mode = true; + if (optarg) + load_firmware_value = optarg; + load_firmware = true; + break; + case 'C': + check_firmware_value = optarg; + check_firmware = true; + break; + case 'E': + use_manufacturer_mode = true; + set_exception = true; + break; + case 'T': + use_manufacturer_mode = true; + set_traces = true; + break; + case 'R': + reset_on_exit = true; + break; + case 'B': + cold_boot = true; + break; + case 'i': + if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3)) + str = optarg + 3; + else + str = optarg; + if (!isdigit(*str)) { + usage(); + return EXIT_FAILURE; + } + hci_index = atoi(str); + break; + case 'r': + use_raw = true; + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind > 0) { + fprintf(stderr, "Invalid command line parameters\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + printf("Bluemoon configuration utility ver %s\n", VERSION); + + if (check_firmware) { + analyze_firmware(check_firmware_value); + return EXIT_SUCCESS; + } + + if (use_raw) { + hci_dev = bt_hci_new_raw_device(hci_index); + if (!hci_dev) { + fprintf(stderr, "Failed to open HCI raw device\n"); + return EXIT_FAILURE; + } + } else { + hci_dev = bt_hci_new_user_channel(hci_index); + if (!hci_dev) { + fprintf(stderr, "Failed to open HCI user channel\n"); + return EXIT_FAILURE; + } + } + + bt_hci_send(hci_dev, CMD_READ_VERSION, NULL, 0, + read_version_complete, NULL, NULL); + + exit_status = mainloop_run_with_signal(signal_callback, NULL); + + bt_hci_unref(hci_dev); + + return exit_status; +} diff --git a/tools/bluetooth-logger.service.in b/tools/bluetooth-logger.service.in new file mode 100644 index 0000000..0090027 --- /dev/null +++ b/tools/bluetooth-logger.service.in @@ -0,0 +1,17 @@ +[Unit] +Description=Bluetooth monitor logger +ConditionPathIsDirectory=/sys/class/bluetooth + +[Service] +Type=simple +ExecStart=@pkglibexecdir@/btmon-logger -p -b /var/log/bluetooth/hci.log +NotifyAccess=main +CapabilityBoundingSet=CAP_NET_RAW +LimitNPROC=1 +ProtectHome=true +ProtectSystem=full +PrivateTmp=true +PrivateDevices=true + +[Install] +WantedBy=bluetooth.target diff --git a/tools/bluetooth-player.c b/tools/bluetooth-player.c new file mode 100644 index 0000000..c1005c9 --- /dev/null +++ b/tools/bluetooth-player.c @@ -0,0 +1,1146 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include + +#include "gdbus/gdbus.h" +#include "src/shared/shell.h" + +/* String display constants */ +#define COLORED_NEW COLOR_GREEN "NEW" COLOR_OFF +#define COLORED_CHG COLOR_YELLOW "CHG" COLOR_OFF +#define COLORED_DEL COLOR_RED "DEL" COLOR_OFF + +#define PROMPT_ON COLOR_BLUE "[bluetooth]" COLOR_OFF "# " +#define PROMPT_OFF "[bluetooth]# " + +#define BLUEZ_MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1" +#define BLUEZ_MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1" +#define BLUEZ_MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1" + +static DBusConnection *dbus_conn; +static GDBusProxy *default_player; +static GList *players = NULL; +static GList *folders = NULL; +static GList *items = NULL; + +static void connect_handler(DBusConnection *connection, void *user_data) +{ + bt_shell_attach(fileno(stdin)); + bt_shell_set_prompt(PROMPT_ON); +} + +static void disconnect_handler(DBusConnection *connection, void *user_data) +{ + bt_shell_detach(); + bt_shell_set_prompt(PROMPT_OFF); +} + +static bool check_default_player(void) +{ + if (!default_player) { + bt_shell_printf("No default player available\n"); + return FALSE; + } + + return TRUE; +} + +static char *generic_generator(const char *text, int state, GList *source) +{ + static int index = 0; + + if (!state) { + index = 0; + } + + return g_dbus_proxy_path_lookup(source, &index, text); +} + +static char *player_generator(const char *text, int state) +{ + return generic_generator(text, state, players); +} + +static char *item_generator(const char *text, int state) +{ + return generic_generator(text, state, items); +} + +static void play_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to play: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Play successful\n"); + + return bt_shell_noninteractive_quit(EXIT_FAILURE); +} + +static void cmd_play(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + if (argc > 1) { + proxy = g_dbus_proxy_lookup(items, NULL, argv[1], + BLUEZ_MEDIA_ITEM_INTERFACE); + if (proxy == NULL) { + bt_shell_printf("Item %s not available\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } else { + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + proxy = default_player; + } + + if (g_dbus_proxy_method_call(proxy, "Play", NULL, play_reply, + NULL, NULL) == FALSE) { + bt_shell_printf("Failed to play\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to play %s\n", argv[1] ? : ""); +} + +static void pause_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to pause: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Pause successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_pause(int argc, char *argv[]) +{ + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (g_dbus_proxy_method_call(default_player, "Pause", NULL, + pause_reply, NULL, NULL) == FALSE) { + bt_shell_printf("Failed to play\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to pause\n"); +} + +static void stop_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to stop: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Stop successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_stop(int argc, char *argv[]) +{ + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (g_dbus_proxy_method_call(default_player, "Stop", NULL, stop_reply, + NULL, NULL) == FALSE) { + bt_shell_printf("Failed to stop\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to stop\n"); +} + +static void next_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to jump to next: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Next successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_next(int argc, char *argv[]) +{ + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (g_dbus_proxy_method_call(default_player, "Next", NULL, next_reply, + NULL, NULL) == FALSE) { + bt_shell_printf("Failed to jump to next\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to jump to next\n"); +} + +static void previous_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to jump to previous: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Previous successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_previous(int argc, char *argv[]) +{ + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (g_dbus_proxy_method_call(default_player, "Previous", NULL, + previous_reply, NULL, NULL) == FALSE) { + bt_shell_printf("Failed to jump to previous\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to jump to previous\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void fast_forward_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to fast forward: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("FastForward successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_fast_forward(int argc, char *argv[]) +{ + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (g_dbus_proxy_method_call(default_player, "FastForward", NULL, + fast_forward_reply, NULL, NULL) == FALSE) { + bt_shell_printf("Failed to jump to previous\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Fast forward playback\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void rewind_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to rewind: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Rewind successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_rewind(int argc, char *argv[]) +{ + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (g_dbus_proxy_method_call(default_player, "Rewind", NULL, + rewind_reply, NULL, NULL) == FALSE) { + bt_shell_printf("Failed to rewind\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Rewind playback\n"); +} + +static void generic_callback(const DBusError *error, void *user_data) +{ + char *str = user_data; + + if (dbus_error_is_set(error)) { + bt_shell_printf("Failed to set %s: %s\n", str, error->name); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } else { + bt_shell_printf("Changing %s succeeded\n", str); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } +} + +static void cmd_equalizer(int argc, char *argv[]) +{ + char *value; + DBusMessageIter iter; + + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (!g_dbus_proxy_get_property(default_player, "Equalizer", &iter)) { + bt_shell_printf("Operation not supported\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + value = g_strdup(argv[1]); + + if (g_dbus_proxy_set_property_basic(default_player, "Equalizer", + DBUS_TYPE_STRING, &value, + generic_callback, value, + g_free) == FALSE) { + bt_shell_printf("Failed to setting equalizer\n"); + g_free(value); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to set equalizer\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_repeat(int argc, char *argv[]) +{ + char *value; + DBusMessageIter iter; + + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (!g_dbus_proxy_get_property(default_player, "Repeat", &iter)) { + bt_shell_printf("Operation not supported\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + value = g_strdup(argv[1]); + + if (g_dbus_proxy_set_property_basic(default_player, "Repeat", + DBUS_TYPE_STRING, &value, + generic_callback, value, + g_free) == FALSE) { + bt_shell_printf("Failed to set repeat\n"); + g_free(value); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to set repeat\n"); +} + +static void cmd_shuffle(int argc, char *argv[]) +{ + char *value; + DBusMessageIter iter; + + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (!g_dbus_proxy_get_property(default_player, "Shuffle", &iter)) { + bt_shell_printf("Operation not supported\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + value = g_strdup(argv[1]); + + if (g_dbus_proxy_set_property_basic(default_player, "Shuffle", + DBUS_TYPE_STRING, &value, + generic_callback, value, + g_free) == FALSE) { + bt_shell_printf("Failed to set shuffle\n"); + g_free(value); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to set shuffle\n"); +} + +static void cmd_scan(int argc, char *argv[]) +{ + char *value; + DBusMessageIter iter; + + if (!check_default_player()) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (!g_dbus_proxy_get_property(default_player, "Shuffle", &iter)) { + bt_shell_printf("Operation not supported\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + value = g_strdup(argv[1]); + + if (g_dbus_proxy_set_property_basic(default_player, "Shuffle", + DBUS_TYPE_STRING, &value, + generic_callback, value, + g_free) == FALSE) { + bt_shell_printf("Failed to set scan\n"); + g_free(value); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to set scan\n"); +} + +static char *proxy_description(GDBusProxy *proxy, const char *title, + const char *description) +{ + const char *path; + + path = g_dbus_proxy_get_path(proxy); + + return g_strdup_printf("%s%s%s%s %s ", + description ? "[" : "", + description ? : "", + description ? "] " : "", + title, path); +} + +static void print_player(GDBusProxy *proxy, const char *description) +{ + char *str; + + str = proxy_description(proxy, "Player", description); + + bt_shell_printf("%s%s\n", str, + default_player == proxy ? "[default]" : ""); + + g_free(str); +} + +static void cmd_list(int argc, char *arg[]) +{ + GList *l; + + for (l = players; l; l = g_list_next(l)) { + GDBusProxy *proxy = l->data; + print_player(proxy, NULL); + } + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void print_iter(const char *label, const char *name, + DBusMessageIter *iter) +{ + dbus_bool_t valbool; + dbus_uint32_t valu32; + dbus_uint16_t valu16; + dbus_int16_t vals16; + const char *valstr; + DBusMessageIter subiter; + + if (iter == NULL) { + bt_shell_printf("%s%s is nil\n", label, name); + return; + } + + switch (dbus_message_iter_get_arg_type(iter)) { + case DBUS_TYPE_INVALID: + bt_shell_printf("%s%s is invalid\n", label, name); + break; + case DBUS_TYPE_STRING: + case DBUS_TYPE_OBJECT_PATH: + dbus_message_iter_get_basic(iter, &valstr); + bt_shell_printf("%s%s: %s\n", label, name, valstr); + break; + case DBUS_TYPE_BOOLEAN: + dbus_message_iter_get_basic(iter, &valbool); + bt_shell_printf("%s%s: %s\n", label, name, + valbool == TRUE ? "yes" : "no"); + break; + case DBUS_TYPE_UINT32: + dbus_message_iter_get_basic(iter, &valu32); + bt_shell_printf("%s%s: 0x%06x\n", label, name, valu32); + break; + case DBUS_TYPE_UINT16: + dbus_message_iter_get_basic(iter, &valu16); + bt_shell_printf("%s%s: 0x%04x\n", label, name, valu16); + break; + case DBUS_TYPE_INT16: + dbus_message_iter_get_basic(iter, &vals16); + bt_shell_printf("%s%s: %d\n", label, name, vals16); + break; + case DBUS_TYPE_VARIANT: + dbus_message_iter_recurse(iter, &subiter); + print_iter(label, name, &subiter); + break; + case DBUS_TYPE_ARRAY: + dbus_message_iter_recurse(iter, &subiter); + while (dbus_message_iter_get_arg_type(&subiter) != + DBUS_TYPE_INVALID) { + print_iter(label, name, &subiter); + dbus_message_iter_next(&subiter); + } + break; + case DBUS_TYPE_DICT_ENTRY: + dbus_message_iter_recurse(iter, &subiter); + dbus_message_iter_get_basic(&subiter, &valstr); + dbus_message_iter_next(&subiter); + print_iter(label, valstr, &subiter); + break; + default: + bt_shell_printf("%s%s has unsupported type\n", label, name); + break; + } +} + +static void print_property(GDBusProxy *proxy, const char *name) +{ + DBusMessageIter iter; + + if (g_dbus_proxy_get_property(proxy, name, &iter) == FALSE) + return; + + print_iter("\t", name, &iter); +} + +static void cmd_show_item(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + proxy = g_dbus_proxy_lookup(items, NULL, argv[1], + BLUEZ_MEDIA_ITEM_INTERFACE); + if (!proxy) { + bt_shell_printf("Item %s not available\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + bt_shell_printf("Item %s\n", g_dbus_proxy_get_path(proxy)); + + print_property(proxy, "Player"); + print_property(proxy, "Name"); + print_property(proxy, "Type"); + print_property(proxy, "FolderType"); + print_property(proxy, "Playable"); + print_property(proxy, "Metadata"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_show(int argc, char *argv[]) +{ + GDBusProxy *proxy; + GDBusProxy *folder; + GDBusProxy *item; + DBusMessageIter iter; + const char *path; + + if (argc < 2) { + if (check_default_player() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + proxy = default_player; + } else { + proxy = g_dbus_proxy_lookup(players, NULL, argv[1], + BLUEZ_MEDIA_PLAYER_INTERFACE); + if (!proxy) { + bt_shell_printf("Player %s not available\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + bt_shell_printf("Player %s\n", g_dbus_proxy_get_path(proxy)); + + print_property(proxy, "Name"); + print_property(proxy, "Repeat"); + print_property(proxy, "Equalizer"); + print_property(proxy, "Shuffle"); + print_property(proxy, "Scan"); + print_property(proxy, "Status"); + print_property(proxy, "Position"); + print_property(proxy, "Track"); + + folder = g_dbus_proxy_lookup(folders, NULL, + g_dbus_proxy_get_path(proxy), + BLUEZ_MEDIA_FOLDER_INTERFACE); + if (folder == NULL) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + bt_shell_printf("Folder %s\n", g_dbus_proxy_get_path(proxy)); + + print_property(folder, "Name"); + print_property(folder, "NumberOfItems"); + + if (!g_dbus_proxy_get_property(proxy, "Playlist", &iter)) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + dbus_message_iter_get_basic(&iter, &path); + + item = g_dbus_proxy_lookup(items, NULL, path, + BLUEZ_MEDIA_ITEM_INTERFACE); + if (item == NULL) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + bt_shell_printf("Playlist %s\n", path); + + print_property(item, "Name"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_select(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + proxy = g_dbus_proxy_lookup(players, NULL, argv[1], + BLUEZ_MEDIA_PLAYER_INTERFACE); + if (proxy == NULL) { + bt_shell_printf("Player %s not available\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (default_player == proxy) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + default_player = proxy, + print_player(proxy, NULL); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void change_folder_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to change folder: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("ChangeFolder successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void change_folder_setup(DBusMessageIter *iter, void *user_data) +{ + const char *path = user_data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); +} + +static void cmd_change_folder(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + if (dbus_validate_path(argv[1], NULL) == FALSE) { + bt_shell_printf("Not a valid path\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (check_default_player() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + proxy = g_dbus_proxy_lookup(folders, NULL, + g_dbus_proxy_get_path(default_player), + BLUEZ_MEDIA_FOLDER_INTERFACE); + if (proxy == NULL) { + bt_shell_printf("Operation not supported\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (g_dbus_proxy_method_call(proxy, "ChangeFolder", change_folder_setup, + change_folder_reply, argv[1], NULL) == FALSE) { + bt_shell_printf("Failed to change current folder\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to change folder\n"); +} + +struct list_items_args { + int start; + int end; +}; + +static void list_items_setup(DBusMessageIter *iter, void *user_data) +{ + struct list_items_args *args = user_data; + DBusMessageIter dict; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + if (args->start < 0) + goto done; + + g_dbus_dict_append_entry(&dict, "Start", + DBUS_TYPE_UINT32, &args->start); + + if (args->end < 0) + goto done; + + g_dbus_dict_append_entry(&dict, "End", DBUS_TYPE_UINT32, &args->end); + +done: + dbus_message_iter_close_container(iter, &dict); +} + +static void list_items_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to list items: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("ListItems successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_list_items(int argc, char *argv[]) +{ + GDBusProxy *proxy; + struct list_items_args *args; + + if (check_default_player() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + proxy = g_dbus_proxy_lookup(folders, NULL, + g_dbus_proxy_get_path(default_player), + BLUEZ_MEDIA_FOLDER_INTERFACE); + if (proxy == NULL) { + bt_shell_printf("Operation not supported\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + args = g_new0(struct list_items_args, 1); + args->start = -1; + args->end = -1; + + if (argc < 2) + goto done; + + errno = 0; + args->start = strtol(argv[1], NULL, 10); + if (errno != 0) { + bt_shell_printf("%s(%d)\n", strerror(errno), errno); + g_free(args); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (argc < 3) + goto done; + + errno = 0; + args->end = strtol(argv[2], NULL, 10); + if (errno != 0) { + bt_shell_printf("%s(%d)\n", strerror(errno), errno); + g_free(args); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + +done: + if (g_dbus_proxy_method_call(proxy, "ListItems", list_items_setup, + list_items_reply, args, g_free) == FALSE) { + bt_shell_printf("Failed to change current folder\n"); + g_free(args); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to list items\n"); +} + +static void search_setup(DBusMessageIter *iter, void *user_data) +{ + char *string = user_data; + DBusMessageIter dict; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string); + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING + DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, + &dict); + + dbus_message_iter_close_container(iter, &dict); +} + +static void search_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to search: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Search successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_search(int argc, char *argv[]) +{ + GDBusProxy *proxy; + char *string; + + if (check_default_player() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + proxy = g_dbus_proxy_lookup(folders, NULL, + g_dbus_proxy_get_path(default_player), + BLUEZ_MEDIA_FOLDER_INTERFACE); + if (proxy == NULL) { + bt_shell_printf("Operation not supported\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + string = g_strdup(argv[1]); + + if (g_dbus_proxy_method_call(proxy, "Search", search_setup, + search_reply, string, g_free) == FALSE) { + bt_shell_printf("Failed to search\n"); + g_free(string); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to search\n"); +} + +static void add_to_nowplaying_reply(DBusMessage *message, void *user_data) +{ + DBusError error; + + dbus_error_init(&error); + + if (dbus_set_error_from_message(&error, message) == TRUE) { + bt_shell_printf("Failed to queue: %s\n", error.name); + dbus_error_free(&error); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("AddToNowPlaying successful\n"); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_queue(int argc, char *argv[]) +{ + GDBusProxy *proxy; + + proxy = g_dbus_proxy_lookup(items, NULL, argv[1], + BLUEZ_MEDIA_ITEM_INTERFACE); + if (proxy == NULL) { + bt_shell_printf("Item %s not available\n", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (g_dbus_proxy_method_call(proxy, "AddtoNowPlaying", NULL, + add_to_nowplaying_reply, NULL, + NULL) == FALSE) { + bt_shell_printf("Failed to play\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bt_shell_printf("Attempting to queue %s\n", argv[1]); +} + +static const struct bt_shell_menu main_menu = { + .name = "main", + .entries = { + { "list", NULL, cmd_list, "List available players" }, + { "show", "[player]", cmd_show, "Player information", + player_generator}, + { "select", "", cmd_select, "Select default player", + player_generator}, + { "play", "[item]", cmd_play, "Start playback", + item_generator}, + { "pause", NULL, cmd_pause, "Pause playback" }, + { "stop", NULL, cmd_stop, "Stop playback" }, + { "next", NULL, cmd_next, "Jump to next item" }, + { "previous", NULL, cmd_previous, "Jump to previous item" }, + { "fast-forward", NULL, cmd_fast_forward, + "Fast forward playback" }, + { "rewind", NULL, cmd_rewind, "Rewind playback" }, + { "equalizer", "", cmd_equalizer, + "Enable/Disable equalizer"}, + { "repeat", "", cmd_repeat, + "Set repeat mode"}, + { "shuffle", "", cmd_shuffle, + "Set shuffle mode"}, + { "scan", "", cmd_scan, + "Set scan mode"}, + { "change-folder", "", cmd_change_folder, + "Change current folder", + item_generator}, + { "list-items", "[start] [end]", cmd_list_items, + "List items of current folder" }, + { "search", "", cmd_search, + "Search items containing string" }, + { "queue", "", cmd_queue, "Add item to playlist queue", + item_generator}, + { "show-item", "", cmd_show_item, "Show item information", + item_generator}, + {} }, +}; + +static void player_added(GDBusProxy *proxy) +{ + players = g_list_append(players, proxy); + + if (default_player == NULL) + default_player = proxy; + + print_player(proxy, COLORED_NEW); +} + +static void print_folder(GDBusProxy *proxy, const char *description) +{ + const char *path; + + path = g_dbus_proxy_get_path(proxy); + + bt_shell_printf("%s%s%sFolder %s\n", description ? "[" : "", + description ? : "", + description ? "] " : "", + path); +} + +static void folder_added(GDBusProxy *proxy) +{ + folders = g_list_append(folders, proxy); + + print_folder(proxy, COLORED_NEW); +} + +static void print_item(GDBusProxy *proxy, const char *description) +{ + const char *path, *name; + DBusMessageIter iter; + + path = g_dbus_proxy_get_path(proxy); + + if (g_dbus_proxy_get_property(proxy, "Name", &iter)) + dbus_message_iter_get_basic(&iter, &name); + else + name = ""; + + bt_shell_printf("%s%s%sItem %s %s\n", description ? "[" : "", + description ? : "", + description ? "] " : "", + path, name); +} + +static void item_added(GDBusProxy *proxy) +{ + items = g_list_append(items, proxy); + + print_item(proxy, COLORED_NEW); +} + +static void proxy_added(GDBusProxy *proxy, void *user_data) +{ + const char *interface; + + interface = g_dbus_proxy_get_interface(proxy); + + if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE)) + player_added(proxy); + else if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE)) + folder_added(proxy); + else if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE)) + item_added(proxy); +} + +static void player_removed(GDBusProxy *proxy) +{ + print_player(proxy, COLORED_DEL); + + if (default_player == proxy) + default_player = NULL; + + players = g_list_remove(players, proxy); +} + +static void folder_removed(GDBusProxy *proxy) +{ + folders = g_list_remove(folders, proxy); + + print_folder(proxy, COLORED_DEL); +} + +static void item_removed(GDBusProxy *proxy) +{ + items = g_list_remove(items, proxy); + + print_item(proxy, COLORED_DEL); +} + +static void proxy_removed(GDBusProxy *proxy, void *user_data) +{ + const char *interface; + + interface = g_dbus_proxy_get_interface(proxy); + + if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE)) + player_removed(proxy); + if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE)) + folder_removed(proxy); + if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE)) + item_removed(proxy); +} + +static void player_property_changed(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter) +{ + char *str; + + str = proxy_description(proxy, "Player", COLORED_CHG); + print_iter(str, name, iter); + g_free(str); +} + +static void folder_property_changed(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter) +{ + char *str; + + str = proxy_description(proxy, "Folder", COLORED_CHG); + print_iter(str, name, iter); + g_free(str); +} + +static void item_property_changed(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter) +{ + char *str; + + str = proxy_description(proxy, "Item", COLORED_CHG); + print_iter(str, name, iter); + g_free(str); +} + +static void property_changed(GDBusProxy *proxy, const char *name, + DBusMessageIter *iter, void *user_data) +{ + const char *interface; + + interface = g_dbus_proxy_get_interface(proxy); + + if (!strcmp(interface, BLUEZ_MEDIA_PLAYER_INTERFACE)) + player_property_changed(proxy, name, iter); + else if (!strcmp(interface, BLUEZ_MEDIA_FOLDER_INTERFACE)) + folder_property_changed(proxy, name, iter); + else if (!strcmp(interface, BLUEZ_MEDIA_ITEM_INTERFACE)) + item_property_changed(proxy, name, iter); +} + +int main(int argc, char *argv[]) +{ + GDBusClient *client; + int status; + + bt_shell_init(argc, argv, NULL); + bt_shell_set_menu(&main_menu); + bt_shell_set_prompt(PROMPT_OFF); + + dbus_conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); + + client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez"); + + g_dbus_client_set_connect_watch(client, connect_handler, NULL); + g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL); + + g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed, + property_changed, NULL); + + status = bt_shell_run(); + + g_dbus_client_unref(client); + + dbus_connection_unref(dbus_conn); + + return status; +} diff --git a/tools/bnep-tester.c b/tools/bnep-tester.c new file mode 100644 index 0000000..ec4ad26 --- /dev/null +++ b/tools/bnep-tester.c @@ -0,0 +1,309 @@ +/* + * + * 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 +#endif + +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/bnep.h" +#include "lib/mgmt.h" + +#include "monitor/bt.h" +#include "emulator/bthost.h" +#include "emulator/hciemu.h" + +#include "src/shared/tester.h" +#include "src/shared/mgmt.h" + +struct test_data { + struct mgmt *mgmt; + uint16_t mgmt_index; + struct hciemu *hciemu; + enum hciemu_type hciemu_type; + const void *test_data; + unsigned int io_id; + uint16_t conn_handle; +}; + +struct rfcomm_client_data { + uint8_t server_channel; + uint8_t client_channel; + int expected_connect_err; + const uint8_t *send_data; + const uint8_t *read_data; + uint16_t data_len; +}; + +struct rfcomm_server_data { + uint8_t server_channel; + uint8_t client_channel; + bool expected_status; + const uint8_t *send_data; + const uint8_t *read_data; + uint16_t data_len; +}; + +static void mgmt_debug(const char *str, void *user_data) +{ + const char *prefix = user_data; + + tester_print("%s%s", prefix, str); +} + +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(); + } + + tester_print("New hciemu instance created"); +} + +static void test_pre_setup(const void *test_data) +{ + struct test_data *data = tester_get_data(); + + data->mgmt = mgmt_new_default(); + if (!data->mgmt) { + tester_warn("Failed to setup management interface"); + tester_pre_setup_failed(); + return; + } + + if (tester_use_debug()) + mgmt_set_debug(data->mgmt, mgmt_debug, "mgmt: ", NULL); + + mgmt_send(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 *test_data) +{ + struct test_data *data = tester_get_data(); + + if (data->io_id > 0) { + g_source_remove(data->io_id); + data->io_id = 0; + } + + hciemu_unref(data->hciemu); + data->hciemu = NULL; +} + +static void test_data_free(void *test_data) +{ + struct test_data *data = test_data; + + free(data); +} + +static void client_connectable_complete(uint16_t opcode, uint8_t status, + const void *param, uint8_t len, + void *user_data) +{ + switch (opcode) { + case BT_HCI_CMD_WRITE_SCAN_ENABLE: + break; + default: + return; + } + + tester_print("Client set connectable status 0x%02x", status); + + if (status) + tester_setup_failed(); + else + tester_setup_complete(); +} + +static void setup_powered_client_callback(uint8_t status, uint16_t length, + const void *param, void *user_data) +{ + struct test_data *data = tester_get_data(); + struct bthost *bthost; + + if (status != MGMT_STATUS_SUCCESS) { + tester_setup_failed(); + return; + } + + tester_print("Controller powered on"); + + bthost = hciemu_client_get_host(data->hciemu); + bthost_set_cmd_complete_cb(bthost, client_connectable_complete, data); + bthost_write_scan_enable(bthost, 0x03); +} + +static void setup_powered_client(const void *test_data) +{ + struct test_data *data = tester_get_data(); + unsigned char param[] = { 0x01 }; + + tester_print("Powering on controller"); + + mgmt_send(data->mgmt, MGMT_OP_SET_POWERED, data->mgmt_index, + sizeof(param), param, setup_powered_client_callback, + NULL, NULL); +} + +static void test_basic(const void *test_data) +{ + int sk; + + sk = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_BNEP); + if (sk < 0) { + tester_warn("Can't create socket: %s (%d)", strerror(errno), + errno); + tester_test_failed(); + return; + } + + close(sk); + + tester_test_passed(); +} + +#define test_bnep(name, data, setup, func) \ + do { \ + struct test_data *user; \ + user = malloc(sizeof(struct test_data)); \ + if (!user) \ + break; \ + user->hciemu_type = HCIEMU_TYPE_BREDR; \ + user->test_data = data; \ + user->io_id = 0; \ + tester_add_full(name, data, \ + test_pre_setup, setup, func, NULL, \ + test_post_teardown, 2, user, test_data_free); \ + } while (0) + +int main(int argc, char *argv[]) +{ + tester_init(&argc, &argv); + + test_bnep("Basic BNEP Socket - Success", NULL, + setup_powered_client, test_basic); + + return tester_run(); +} diff --git a/tools/bneptest.c b/tools/bneptest.c new file mode 100644 index 0000000..8bc7596 --- /dev/null +++ b/tools/bneptest.c @@ -0,0 +1,707 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "src/log.h" +#include "src/shared/util.h" +#include "btio/btio.h" +#include "lib/bnep.h" +#include "profiles/network/bnep.h" + +enum { + MODE_LISTEN, + MODE_CONNECT, +}; + +static GMainLoop *mloop; +static GIOChannel *bnep_io; +static struct bnep *session; + +static int mode; +static bool no_close_after_disconn; +static int send_frame_timeout; + +static bdaddr_t src_addr, dst_addr; +static char iface[16]; +static char bridge[16]; +static bool send_ctrl_msg_type_set = false; +static uint8_t ctrl_msg_type = 0x00; +static bool send_bnep_msg_type_set = false; +static uint8_t bnep_msg_type = 0x00; +static int ctrl_msg_retransmition_nb = 0; +static int bnep_msg_retransmission_nb = 0; +static uint16_t local_role = BNEP_SVC_PANU; +static uint16_t remote_role = BNEP_SVC_NAP; +static uint16_t ntw_proto_down_range = 0x0000; +static uint16_t ntw_proto_up_range = 0xdc05; +static uint16_t ntw_proto_type = 0x0000; +static uint8_t mcast_addr_down_range[6]; +static uint8_t mcast_addr_up_range[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; +static uint8_t src_hw_addr[6]; +static uint8_t dst_hw_addr[6]; +static uint8_t general_frame_payload[] = "abcdef0123456789_bnep_test_data"; + +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, bridge, IFNAMSIZ); + ifr.ifr_data = (char *) args; + + if (ioctl(sk, SIOCDEVPRIVATE, &ifr) < 0) { + error("setting forward delay failed: %d (%s)", + errno, strerror(errno)); + return -1; + } + + return 0; +} + +static int nap_create_bridge(void) +{ + int sk, err; + + sk = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sk < 0) + return -EOPNOTSUPP; + + if (ioctl(sk, SIOCBRADDBR, bridge) < 0) { + if (errno != EEXIST) { + close(sk); + return -EOPNOTSUPP; + } + } + + err = set_forward_delay(sk); + if (err < 0) { + printf("failed to set forward delay\n"); + ioctl(sk, SIOCBRDELBR, bridge); + } + + close(sk); + + return err; +} + +static int cleanup(void) +{ + bnep_cleanup(); + + if (mode == MODE_LISTEN) + bnep_server_delete(bridge, iface, &dst_addr); + + if (bnep_io) { + g_io_channel_shutdown(bnep_io, TRUE, NULL); + g_io_channel_unref(bnep_io); + bnep_io = NULL; + } + + return 0; +} + +static gboolean bnep_watchdog_cb(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + printf("%s\n", __func__); + + if (no_close_after_disconn) + return FALSE; + + /* Cleanup since it's called when disconnected l2cap */ + if (cleanup() < 0) { + printf("cleanup went wrong...\n"); + return FALSE; + } + + g_main_loop_quit(mloop); + return FALSE; +} + +static ssize_t send_compressed_frame(int sk, uint8_t type) +{ + uint8_t frame[100]; + + printf("%s\n", __func__); + + if (send_frame_timeout > 0) { + printf("waiting %d seconds before sending msg\n", + send_frame_timeout); + sleep(send_frame_timeout); + } + + frame[0] = type; + memcpy(&frame[1], dst_hw_addr, sizeof(dst_hw_addr)); + memcpy(&frame[7], src_hw_addr, sizeof(src_hw_addr)); + frame[13] = ntw_proto_type & 0xff; + frame[14] = (ntw_proto_type >> 8); + memcpy(&frame[15], general_frame_payload, + sizeof(general_frame_payload)); + + /* TODO - set frame payload by user */ + return send(sk, frame, 15 + sizeof(general_frame_payload), 0); +} + +static ssize_t send_general_frame(int sk) +{ + uint8_t frame[100]; + + printf("%s\n", __func__); + + if (send_frame_timeout > 0) { + printf("waiting %d seconds before sending msg\n", + send_frame_timeout); + sleep(send_frame_timeout); + } + + frame[0] = BNEP_GENERAL; + memcpy(&frame[1], dst_hw_addr, sizeof(dst_hw_addr)); + memcpy(&frame[7], src_hw_addr, sizeof(src_hw_addr)); + frame[13] = ntw_proto_type & 0xff; + frame[14] = (ntw_proto_type >> 8); + memcpy(&frame[15], general_frame_payload, + sizeof(general_frame_payload)); + + /* TODO - set frame payload by user */ + return send(sk, frame, 15 + sizeof(general_frame_payload), 0); +} + +static ssize_t send_ctrl_frame(int sk) +{ + /* + * Max buff size = type(1byte) + ctrl(1byte) + len(2byte) + + * mcast_addr_down(6byte) + mcast_addr_up(6byte) + */ + uint8_t buff[16]; + struct bnep_set_filter_req *frame = (void *) buff; + int err; + + printf("%s\n", __func__); + + if (send_frame_timeout > 0) { + printf("waiting %d seconds before sending msg\n", + send_frame_timeout); + sleep(send_frame_timeout); + } + + switch (ctrl_msg_type) { + case BNEP_FILTER_NET_TYPE_SET: + frame->type = BNEP_CONTROL; + frame->ctrl = ctrl_msg_type; + frame->len = htons(sizeof(ntw_proto_down_range) + + sizeof(ntw_proto_up_range)); + memcpy(frame->list, &ntw_proto_down_range, + sizeof(ntw_proto_down_range)); + memcpy(frame->list + sizeof(ntw_proto_down_range), + &ntw_proto_up_range, sizeof(ntw_proto_up_range)); + + err = send(sk, frame, sizeof(*frame) + + sizeof(ntw_proto_down_range) + + sizeof(ntw_proto_up_range), 0); + break; + case BNEP_FILTER_MULT_ADDR_SET: + frame->type = BNEP_CONTROL; + frame->ctrl = ctrl_msg_type; + frame->len = htons(sizeof(mcast_addr_down_range) + + sizeof(mcast_addr_up_range)); + memcpy(frame->list, mcast_addr_down_range, + sizeof(mcast_addr_down_range)); + memcpy(frame->list + sizeof(mcast_addr_down_range), + mcast_addr_up_range, sizeof(mcast_addr_up_range)); + + err = send(sk, frame, sizeof(*frame) + + sizeof(mcast_addr_down_range) + + sizeof(mcast_addr_up_range), 0); + break; + default: + err = -1; + break; + } + + return err; +} + +static int send_bnep_frame(int sk) +{ + int err; + + switch (bnep_msg_type) { + case BNEP_GENERAL: + err = send_general_frame(sk); + break; + case BNEP_COMPRESSED: + err = send_compressed_frame(sk, BNEP_COMPRESSED); + break; + case BNEP_COMPRESSED_SRC_ONLY: + err = send_compressed_frame(sk, + BNEP_COMPRESSED_SRC_ONLY); + break; + case BNEP_COMPRESSED_DST_ONLY: + err = send_compressed_frame(sk, + BNEP_COMPRESSED_DST_ONLY); + break; + default: + printf("wrong bnep_msg_type 0x%02x\n", bnep_msg_type); + err = -EINVAL; + break; + } + + return err; +} + +static void handle_bnep_msg_send(int sk) +{ + if (send_ctrl_msg_type_set) { + do { + if (send_ctrl_frame(sk) < 0) + printf("sending ctrl frame error: %s (%d)\n", + strerror(errno), errno); + } while (ctrl_msg_retransmition_nb--); + } + + if (send_bnep_msg_type_set) { + do { + if (send_bnep_frame(sk) < 0) + printf("sending bnep frame error: %s (%d)\n", + strerror(errno), errno); + } while (bnep_msg_retransmission_nb--); + } +} + +static gboolean setup_bnep_cb(GIOChannel *chan, GIOCondition cond, + gpointer user_data) +{ + uint8_t packet[BNEP_MTU]; + int sk, n, err; + + printf("%s\n", __func__); + + 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); + + /* Reading BNEP_SETUP_CONNECTION_REQUEST_MSG */ + n = recv(sk, packet, sizeof(packet), MSG_PEEK); + if (n < 0) { + error("read(): %s(%d)", strerror(errno), errno); + return FALSE; + } + + err = nap_create_bridge(); + if (err < 0) { + error("failed to create bridge: %s (%d)", strerror(-err), err); + return FALSE; + } + + if (bnep_server_add(sk, (err < 0) ? NULL : bridge, iface, &dst_addr, + packet, n) < 0) { + printf("server_connadd failed\n"); + cleanup(); + return FALSE; + } + + g_io_add_watch(chan, G_IO_HUP | G_IO_ERR | G_IO_NVAL, bnep_watchdog_cb, + NULL); + + handle_bnep_msg_send(sk); + + g_io_channel_unref(bnep_io); + bnep_io = NULL; + + return FALSE; +} + +static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + printf("%s\n", __func__); + + if (err) { + error("%s", err->message); + return; + } + + g_io_add_watch(chan, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + setup_bnep_cb, NULL); +} + +static void connected_client_cb(char *iface, int err, void *data) +{ + int sk = PTR_TO_INT(data); + + printf("%s\n", __func__); + + handle_bnep_msg_send(sk); +} + +static void disconnected_client_cb(void *data) +{ + printf("%s\n", __func__); + + if (no_close_after_disconn) + return; + + /* Cleanup since it's called when disconnected l2cap */ + if (cleanup() < 0) { + printf("cleanup went wrong...\n"); + return; + } + + g_main_loop_quit(mloop); +} + +static void connect_client_cb(GIOChannel *chan, GError *err, gpointer user_data) +{ + int perr; + int sk; + + sk = g_io_channel_unix_get_fd(bnep_io); + + session = bnep_new(sk, local_role, remote_role, bridge); + if (!session) { + printf("cannot create bnep session\n"); + return; + } + + perr = bnep_connect(session, connected_client_cb, + disconnected_client_cb, INT_TO_PTR(sk), NULL); + if (perr < 0) + printf("cannot initiate bnep connection\n"); +} + +static void confirm_cb(GIOChannel *chan, gpointer data) +{ + GError *err = NULL; + char address[18]; + + printf("%s\n", __func__); + + bt_io_get(chan, &err, BT_IO_OPT_DEST_BDADDR, &dst_addr, BT_IO_OPT_DEST, + address, BT_IO_OPT_INVALID); + if (err) { + error("%s", err->message); + g_error_free(err); + return; + } + + printf("incoming connection from: %s\n", address); + + bnep_io = g_io_channel_ref(chan); + g_io_channel_set_close_on_unref(bnep_io, TRUE); + + if (!bt_io_accept(bnep_io, connect_cb, NULL, NULL, &err)) { + error("bt_io_accept: %s", err->message); + g_error_free(err); + g_io_channel_unref(bnep_io); + } +} + +static int bnep_server_listen(void) +{ + GError *gerr = NULL; + + printf("%s\n", __func__); + + bnep_io = bt_io_listen(NULL, confirm_cb, NULL, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &src_addr, + BT_IO_OPT_PSM, BNEP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_OMTU, BNEP_MTU, + BT_IO_OPT_IMTU, BNEP_MTU, + BT_IO_OPT_INVALID); + if (!bnep_io) { + printf("can't start server listening: err %s\n", gerr->message); + g_error_free(gerr); + return -1; + } + + return 0; +} + +static int bnep_client_connect(void) +{ + GError *gerr = NULL; + char bdastr[18]; + + printf("%s\n", __func__); + + ba2str(&dst_addr, bdastr); + printf("connecting %s\n", bdastr); + + bnep_io = bt_io_connect(connect_client_cb, NULL, NULL, &gerr, + BT_IO_OPT_SOURCE_BDADDR, &src_addr, + BT_IO_OPT_DEST_BDADDR, &dst_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 (!bnep_io) { + printf("cannot connect: err %s\n", gerr->message); + g_error_free(gerr); + return -1; + } + + return 0; +} + +static void exit_handler(int sig) +{ + printf("got sig = %d, cleaning up...\n", sig); + + if (cleanup() < 0) + printf("cleanup failure...\n"); + else + printf("cleanup successful - exit\n"); + + exit(0); +} + +static void usage(void) +{ + printf("bneptest - BNEP testing ver %s\n", VERSION); + printf("Usage:\n" + "\tbneptest [-i] -b -n " + " [send_ctrl_cmd] [options]\n" + "\t-i hci dev number , def. 0\n" + "\t-b bridge name \n" + "\t-n interface name \n"); + printf("Connect Mode:\n" + "\t-c connect \n" + "\t-r remote role <16 bit svc value>\n" + "\t-l local role <16 bit svc valu>\n"); + printf("Listen Mode:\n" + "\t-s start server listening\n"); + printf("Send control command:\n" + "\t-t send message type , def. 0\n" + "\t-e start network protocol type range <16 bit val>, def. 0\n" + "\t-d end network protocol type range <16 bit val>, def. 1500\n" + "\t-g start multicast addr range , def. 0\n" + "\t-j end multicast addr range , def. f\n" + "\t-y number of ctrl frame retransmission , def. 0\n" + "\t-u number of bnep frame retransmission , def. 0\n"); + printf("Send bnep generic frame:\n" + "\t-w send bnep generic frame , def. 0\n" + "\t-k set src mac addr , def. 0\n" + "\t-f set dst mac addr , def. 0\n"); + printf("Options:\n" + "\t-T send message timeout after setup \n" + "\t-N don't close bneptest after disconnect\n"); +} + +static struct option main_options[] = { + { "device", 1, 0, 'i' }, + { "listen", 0, 0, 's' }, + { "connect", 1, 0, 'c' }, + { "snd_ctrl_msg_type", 1, 0, 't' }, + { "snd_bnep_msg_type", 1, 0, 'w' }, + { "src_hw_addr", 1, 0, 'k' }, + { "dst_hw_addr", 1, 0, 'f' }, + { "send_timeout", 1, 0, 'T' }, + { "ntw_proto_down_range", 1, 0, 'd' }, + { "ntw_proto_up_range", 1, 0, 'e' }, + { "mcast_addr_down_range", 1, 0, 'g' }, + { "mcast_addr_up_range", 1, 0, 'j' }, + { "local_role", 1, 0, 'l' }, + { "remote_role", 1, 0, 'r' }, + { "bridge name", 1, 0, 'b' }, + { "iface name", 1, 0, 'n' }, + { "no_close", 0, 0, 'N' }, + { "retrans_ctrl_nb", 0, 0, 'y' }, + { "retrans_bnep_nb", 0, 0, 'u' }, + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + int opt, i; + int err; + bool is_set_b_name = false, is_set_i_name = false; + + DBG(""); + + signal(SIGINT, exit_handler); + + hci_devba(0, &src_addr); + bacpy(&src_addr, BDADDR_ANY); + + mloop = g_main_loop_new(NULL, FALSE); + if (!mloop) { + printf("cannot create main loop\n"); + + exit(1); + } + + while ((opt = getopt_long(argc, argv, + "+i:c:b:n:t:T:d:e:g:j:k:f:w:l:r:y:u:Nsh", + main_options, NULL)) != EOF) { + switch (opt) { + case 'i': + if (!strncmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &src_addr); + else + str2ba(optarg, &src_addr); + break; + case 's': + mode = MODE_LISTEN; + break; + case 'c': + str2ba(optarg, &dst_addr); + mode = MODE_CONNECT; + break; + case 't': + send_ctrl_msg_type_set = true; + ctrl_msg_type = atoi(optarg); + break; + case 'w': + send_bnep_msg_type_set = true; + bnep_msg_type = atoi(optarg); + break; + case 'k': + for (i = 0; i <= 5; i++, optarg += 3) + src_hw_addr[i] = strtol(optarg, NULL, 16); + break; + case 'f': + for (i = 0; i <= 5; i++, optarg += 3) + dst_hw_addr[i] = strtol(optarg, NULL, 16); + break; + case 'T': + send_frame_timeout = atoi(optarg); + break; + case 'd': + ntw_proto_down_range = htons(atoi(optarg)); + break; + case 'e': + ntw_proto_up_range = htons(atoi(optarg)); + break; + case 'g': + for (i = 5; i >= 0; i--, optarg += 3) + mcast_addr_down_range[i] = + strtol(optarg, NULL, 16); + break; + case 'j': + for (i = 5; i >= 0; i--, optarg += 3) + mcast_addr_up_range[i] = + strtol(optarg, NULL, 16); + break; + case 'l': + local_role = atoi(optarg); + break; + case 'r': + remote_role = atoi(optarg); + break; + case 'b': + strncpy(bridge, optarg, 16); + bridge[15] = '\0'; + is_set_b_name = true; + break; + case 'n': + strncpy(iface, optarg, 14); + strcat(iface, "\%d"); + iface[15] = '\0'; + is_set_i_name = true; + break; + case 'N': + no_close_after_disconn = true; + break; + case 'y': + ctrl_msg_retransmition_nb = atoi(optarg); + break; + case 'u': + bnep_msg_retransmission_nb = atoi(optarg); + break; + case 'h': + default: + usage(); + exit(0); + } + } + + if (!is_set_b_name || !is_set_i_name) { + printf("bridge, interface name must be set!\n"); + exit(1); + } + + switch (mode) { + case MODE_CONNECT: + err = bnep_init(); + if (err < 0) { + printf("cannot initialize bnep\n"); + exit(1); + } + err = bnep_client_connect(); + if (err < 0) + exit(1); + + break; + case MODE_LISTEN: + err = bnep_init(); + if (err < 0) { + printf("cannot initialize bnep\n"); + exit(1); + } + err = bnep_server_listen(); + if (err < 0) + exit(1); + + break; + default: + printf("connect/listen mode not set, exit...\n"); + exit(1); + } + + g_main_loop_run(mloop); + + printf("Done\n"); + + g_main_loop_unref(mloop); + + return 0; +} diff --git a/tools/btattach.1 b/tools/btattach.1 new file mode 100644 index 0000000..ffd653d --- /dev/null +++ b/tools/btattach.1 @@ -0,0 +1,53 @@ +.TH "btattach" "1" "November 2015" "BlueZ" "Linux System Administration" +.SH NAME +btattach \- attach serial devices to BlueZ stack + +.SH SYNOPSIS +.B btattach +.RB [\| \-B +.IR device \|] +.RB [\| \-A +.IR device \|] +.RB [\| \-P +.IR protocol \|] +.RB [\| \-R \|] + +.SH DESCRIPTION +.LP +btattach is used to attach a serial UART to the Bluetooth stack as a +transport interface. + +.SH OPTIONS +.TP +.BI \-B " device" , " " \--bredr " device" +Attach a BR/EDR controller. +.TP +.BI \-A " device" , " " \--amp " device" +Attach an AMP controller. +.TP +.BI \-P " protocol" , " " \--protocol " protocol" +Specify the protocol type for talking to the device. +Supported values are: +.RS +.IP \(bu 2 +.B h4 +.IP \(bu 2 +.B bcsp +.IP \(bu 2 +.B 3wire +.IP \(bu 2 +.B h4ds +.IP \(bu 2 +.B ll +.IP \(bu 2 +.B ath3k +.IP \(bu 2 +.B intel +.IP \(bu 2 +.B bcm +.IP \(bu 2 +.B qca +.RE +.TP +.B \-R +Set the device into raw mode (the kernel and bluetoothd will ignore it). diff --git a/tools/btattach.c b/tools/btattach.c new file mode 100644 index 0000000..56315d0 --- /dev/null +++ b/tools/btattach.c @@ -0,0 +1,348 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "hciattach.h" +#include "monitor/bt.h" +#include "src/shared/mainloop.h" +#include "src/shared/timeout.h" +#include "src/shared/util.h" +#include "src/shared/tty.h" +#include "src/shared/hci.h" + +static int open_serial(const char *path, unsigned int speed, bool flowctl) +{ + struct termios ti; + int fd, saved_ldisc, ldisc = N_HCI; + + fd = open(path, O_RDWR | O_NOCTTY); + if (fd < 0) { + perror("Failed to open serial port"); + return -1; + } + + if (tcflush(fd, TCIOFLUSH) < 0) { + perror("Failed to flush serial port"); + close(fd); + return -1; + } + + if (ioctl(fd, TIOCGETD, &saved_ldisc) < 0) { + perror("Failed get serial line discipline"); + close(fd); + return -1; + } + + /* Switch TTY to raw mode */ + memset(&ti, 0, sizeof(ti)); + cfmakeraw(&ti); + + ti.c_cflag |= (speed | CLOCAL | CREAD); + + if (flowctl) { + /* Set flow control */ + ti.c_cflag |= CRTSCTS; + } + + if (tcsetattr(fd, TCSANOW, &ti) < 0) { + perror("Failed to set serial port settings"); + close(fd); + return -1; + } + + if (ioctl(fd, TIOCSETD, &ldisc) < 0) { + perror("Failed set serial line discipline"); + close(fd); + return -1; + } + + printf("Switched line discipline from %d to %d\n", saved_ldisc, ldisc); + + return fd; +} + +static void local_version_callback(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_read_local_version *rsp = data; + + printf("Manufacturer: %u\n", le16_to_cpu(rsp->manufacturer)); +} + +static int attach_proto(const char *path, unsigned int proto, + unsigned int speed, bool flowctl, unsigned int flags) +{ + int fd, dev_id; + + fd = open_serial(path, speed, flowctl); + if (fd < 0) + return -1; + + if (ioctl(fd, HCIUARTSETFLAGS, flags) < 0) { + perror("Failed to set flags"); + close(fd); + return -1; + } + + if (ioctl(fd, HCIUARTSETPROTO, proto) < 0) { + perror("Failed to set protocol"); + close(fd); + return -1; + } + + dev_id = ioctl(fd, HCIUARTGETDEVICE); + if (dev_id < 0) { + perror("Failed to get device id"); + close(fd); + return -1; + } + + printf("Device index %d attached\n", dev_id); + + if (flags & (1 << HCI_UART_RAW_DEVICE)) { + unsigned int attempts = 6; + struct bt_hci *hci; + + while (attempts-- > 0) { + hci = bt_hci_new_user_channel(dev_id); + if (hci) + break; + + usleep(250 * 1000); + } + + if (!hci) { + fprintf(stderr, "Failed to open HCI user channel\n"); + close(fd); + return -1; + } + + bt_hci_send(hci, BT_HCI_CMD_READ_LOCAL_VERSION, NULL, 0, + local_version_callback, hci, + (bt_hci_destroy_func_t) bt_hci_unref); + } + + return fd; +} + +static void uart_callback(int fd, uint32_t events, void *user_data) +{ + printf("UART callback handling\n"); +} + +static void signal_callback(int signum, void *user_data) +{ + static bool terminated = false; + + switch (signum) { + case SIGINT: + case SIGTERM: + if (!terminated) { + mainloop_quit(); + terminated = true; + } + break; + } +} +static void usage(void) +{ + printf("btattach - Bluetooth serial utility\n" + "Usage:\n"); + printf("\tbtattach [options]\n"); + printf("options:\n" + "\t-B, --bredr Attach Primary controller\n" + "\t-A, --amp Attach AMP controller\n" + "\t-P, --protocol Specify protocol type\n" + "\t-S, --speed Specify which baudrate to use\n" + "\t-N, --noflowctl Disable flow control\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "bredr", required_argument, NULL, 'B' }, + { "amp", required_argument, NULL, 'A' }, + { "protocol", required_argument, NULL, 'P' }, + { "speed", required_argument, NULL, 'S' }, + { "noflowctl",no_argument, NULL, 'N' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +static const struct { + const char *name; + unsigned int id; +} proto_table[] = { + { "h4", HCI_UART_H4 }, + { "bcsp", HCI_UART_BCSP }, + { "3wire", HCI_UART_3WIRE }, + { "h4ds", HCI_UART_H4DS }, + { "ll", HCI_UART_LL }, + { "ath3k", HCI_UART_ATH3K }, + { "intel", HCI_UART_INTEL }, + { "bcm", HCI_UART_BCM }, + { "qca", HCI_UART_QCA }, + { "ag6xx", HCI_UART_AG6XX }, + { "nokia", HCI_UART_NOKIA }, + { "mrvl", HCI_UART_MRVL }, + { } +}; + +int main(int argc, char *argv[]) +{ + const char *bredr_path = NULL, *amp_path = NULL, *proto = NULL; + bool flowctl = true, raw_device = false; + int exit_status, count = 0, proto_id = HCI_UART_H4; + unsigned int speed = B115200; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "B:A:P:S:NRvh", + main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'B': + bredr_path = optarg; + break; + case 'A': + amp_path = optarg; + break; + case 'P': + proto = optarg; + break; + case 'S': + speed = tty_get_speed(atoi(optarg)); + if (!speed) { + fprintf(stderr, "Invalid speed: %s\n", optarg); + return EXIT_FAILURE; + } + break; + case 'N': + flowctl = false; + break; + case 'R': + raw_device = true; + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind > 0) { + fprintf(stderr, "Invalid command line parameters\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + if (proto) { + unsigned int i; + + for (i = 0; proto_table[i].name; i++) { + if (!strcmp(proto_table[i].name, proto)) { + proto_id = proto_table[i].id; + break; + } + } + + if (!proto_table[i].name) { + fprintf(stderr, "Invalid protocol\n"); + return EXIT_FAILURE; + } + } + + if (bredr_path) { + unsigned long flags; + int fd; + + printf("Attaching Primary controller to %s\n", bredr_path); + + flags = (1 << HCI_UART_RESET_ON_INIT); + + if (raw_device) + flags = (1 << HCI_UART_RAW_DEVICE); + + fd = attach_proto(bredr_path, proto_id, speed, flowctl, flags); + if (fd >= 0) { + mainloop_add_fd(fd, 0, uart_callback, NULL, NULL); + count++; + } + } + + if (amp_path) { + unsigned long flags; + int fd; + + printf("Attaching AMP controller to %s\n", amp_path); + + flags = (1 << HCI_UART_RESET_ON_INIT) | + (1 << HCI_UART_CREATE_AMP); + + if (raw_device) + flags = (1 << HCI_UART_RAW_DEVICE); + + fd = attach_proto(amp_path, proto_id, speed, flowctl, flags); + if (fd >= 0) { + mainloop_add_fd(fd, 0, uart_callback, NULL, NULL); + count++; + } + } + + if (count < 1) { + fprintf(stderr, "No controller attached\n"); + return EXIT_FAILURE; + } + + exit_status = mainloop_run_with_signal(signal_callback, NULL); + + return exit_status; +} diff --git a/tools/btconfig.c b/tools/btconfig.c new file mode 100644 index 0000000..c1ef583 --- /dev/null +++ b/tools/btconfig.c @@ -0,0 +1,124 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "src/shared/mainloop.h" +#include "src/shared/util.h" +#include "src/shared/mgmt.h" + +static struct mgmt *mgmt = NULL; + +static void mgmt_debug(const char *str, void *user_data) +{ + const char *prefix = user_data; + + printf("%s%s\n", prefix, str); +} + +static void signal_callback(int signum, void *user_data) +{ + static bool terminated = false; + + switch (signum) { + case SIGINT: + case SIGTERM: + if (!terminated) { + mainloop_quit(); + terminated = true; + } + break; + } +} +static void usage(void) +{ + printf("btconfig - Bluetooth configuration utility\n" + "Usage:\n"); + printf("\tbtconfig [options]\n"); + printf("options:\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + int exit_status; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "vh", + main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind > 0) { + fprintf(stderr, "Invalid command line parameters\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + mgmt = mgmt_new_default(); + if (!mgmt) { + fprintf(stderr, "Unable to open mgmt_socket\n"); + return EXIT_FAILURE; + } + + if (getenv("MGMT_DEBUG")) + mgmt_set_debug(mgmt, mgmt_debug, "mgmt: ", NULL); + + exit_status = mainloop_run_with_signal(signal_callback, NULL); + + mgmt_cancel_all(mgmt); + mgmt_unregister_all(mgmt); + + mgmt_unref(mgmt); + + return exit_status; +} diff --git a/tools/btgatt-client.c b/tools/btgatt-client.c new file mode 100644 index 0000000..7df6597 --- /dev/null +++ b/tools/btgatt-client.c @@ -0,0 +1,1651 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google Inc. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/l2cap.h" +#include "lib/uuid.h" + +#include "src/shared/mainloop.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-client.h" + +#define ATT_CID 4 + +#define PRLOG(...) \ + printf(__VA_ARGS__); print_prompt(); + +#define COLOR_OFF "\x1B[0m" +#define COLOR_RED "\x1B[0;91m" +#define COLOR_GREEN "\x1B[0;92m" +#define COLOR_YELLOW "\x1B[0;93m" +#define COLOR_BLUE "\x1B[0;94m" +#define COLOR_MAGENTA "\x1B[0;95m" +#define COLOR_BOLDGRAY "\x1B[1;30m" +#define COLOR_BOLDWHITE "\x1B[1;37m" + +static bool verbose = false; + +struct client { + int fd; + struct bt_att *att; + struct gatt_db *db; + struct bt_gatt_client *gatt; + + unsigned int reliable_session_id; +}; + +static void print_prompt(void) +{ + printf(COLOR_BLUE "[GATT client]" COLOR_OFF "# "); + fflush(stdout); +} + +static const char *ecode_to_string(uint8_t ecode) +{ + switch (ecode) { + case BT_ATT_ERROR_INVALID_HANDLE: + return "Invalid Handle"; + case BT_ATT_ERROR_READ_NOT_PERMITTED: + return "Read Not Permitted"; + case BT_ATT_ERROR_WRITE_NOT_PERMITTED: + return "Write Not Permitted"; + case BT_ATT_ERROR_INVALID_PDU: + return "Invalid PDU"; + case BT_ATT_ERROR_AUTHENTICATION: + return "Authentication Required"; + case BT_ATT_ERROR_REQUEST_NOT_SUPPORTED: + return "Request Not Supported"; + case BT_ATT_ERROR_INVALID_OFFSET: + return "Invalid Offset"; + case BT_ATT_ERROR_AUTHORIZATION: + return "Authorization Required"; + case BT_ATT_ERROR_PREPARE_QUEUE_FULL: + return "Prepare Write Queue Full"; + case BT_ATT_ERROR_ATTRIBUTE_NOT_FOUND: + return "Attribute Not Found"; + case BT_ATT_ERROR_ATTRIBUTE_NOT_LONG: + return "Attribute Not Long"; + case BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION_KEY_SIZE: + return "Insuficient Encryption Key Size"; + case BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN: + return "Invalid Attribute value len"; + case BT_ATT_ERROR_UNLIKELY: + return "Unlikely Error"; + case BT_ATT_ERROR_INSUFFICIENT_ENCRYPTION: + return "Insufficient Encryption"; + case BT_ATT_ERROR_UNSUPPORTED_GROUP_TYPE: + return "Group type Not Supported"; + case BT_ATT_ERROR_INSUFFICIENT_RESOURCES: + return "Insufficient Resources"; + case BT_ERROR_CCC_IMPROPERLY_CONFIGURED: + return "CCC Improperly Configured"; + case BT_ERROR_ALREADY_IN_PROGRESS: + return "Procedure Already in Progress"; + case BT_ERROR_OUT_OF_RANGE: + return "Out of Range"; + default: + return "Unknown error type"; + } +} + +static void att_disconnect_cb(int err, void *user_data) +{ + printf("Device disconnected: %s\n", strerror(err)); + + mainloop_quit(); +} + +static void att_debug_cb(const char *str, void *user_data) +{ + const char *prefix = user_data; + + PRLOG(COLOR_BOLDGRAY "%s" COLOR_BOLDWHITE "%s\n" COLOR_OFF, prefix, str); +} + +static void gatt_debug_cb(const char *str, void *user_data) +{ + const char *prefix = user_data; + + PRLOG(COLOR_GREEN "%s%s\n" COLOR_OFF, prefix, str); +} + +static void ready_cb(bool success, uint8_t att_ecode, void *user_data); +static void service_changed_cb(uint16_t start_handle, uint16_t end_handle, + void *user_data); + +static void log_service_event(struct gatt_db_attribute *attr, const char *str) +{ + char uuid_str[MAX_LEN_UUID_STR]; + bt_uuid_t uuid; + uint16_t start, end; + + gatt_db_attribute_get_service_uuid(attr, &uuid); + bt_uuid_to_string(&uuid, uuid_str, sizeof(uuid_str)); + + gatt_db_attribute_get_service_handles(attr, &start, &end); + + PRLOG("%s - UUID: %s start: 0x%04x end: 0x%04x\n", str, uuid_str, + start, end); +} + +static void service_added_cb(struct gatt_db_attribute *attr, void *user_data) +{ + log_service_event(attr, "Service Added"); +} + +static void service_removed_cb(struct gatt_db_attribute *attr, void *user_data) +{ + log_service_event(attr, "Service Removed"); +} + +static struct client *client_create(int fd, uint16_t mtu) +{ + struct client *cli; + + cli = new0(struct client, 1); + if (!cli) { + fprintf(stderr, "Failed to allocate memory for client\n"); + return NULL; + } + + cli->att = bt_att_new(fd, false); + if (!cli->att) { + fprintf(stderr, "Failed to initialze ATT transport layer\n"); + bt_att_unref(cli->att); + free(cli); + return NULL; + } + + if (!bt_att_set_close_on_unref(cli->att, true)) { + fprintf(stderr, "Failed to set up ATT transport layer\n"); + bt_att_unref(cli->att); + free(cli); + return NULL; + } + + if (!bt_att_register_disconnect(cli->att, att_disconnect_cb, NULL, + NULL)) { + fprintf(stderr, "Failed to set ATT disconnect handler\n"); + bt_att_unref(cli->att); + free(cli); + return NULL; + } + + cli->fd = fd; + cli->db = gatt_db_new(); + if (!cli->db) { + fprintf(stderr, "Failed to create GATT database\n"); + bt_att_unref(cli->att); + free(cli); + return NULL; + } + + cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu); + if (!cli->gatt) { + fprintf(stderr, "Failed to create GATT client\n"); + gatt_db_unref(cli->db); + bt_att_unref(cli->att); + free(cli); + return NULL; + } + + gatt_db_register(cli->db, service_added_cb, service_removed_cb, + NULL, NULL); + + if (verbose) { + bt_att_set_debug(cli->att, att_debug_cb, "att: ", NULL); + bt_gatt_client_set_debug(cli->gatt, gatt_debug_cb, "gatt: ", + NULL); + } + + bt_gatt_client_ready_register(cli->gatt, ready_cb, cli, NULL); + bt_gatt_client_set_service_changed(cli->gatt, service_changed_cb, cli, + NULL); + + /* bt_gatt_client already holds a reference */ + gatt_db_unref(cli->db); + + return cli; +} + +static void client_destroy(struct client *cli) +{ + bt_gatt_client_unref(cli->gatt); + bt_att_unref(cli->att); + free(cli); +} + +static void print_uuid(const bt_uuid_t *uuid) +{ + char uuid_str[MAX_LEN_UUID_STR]; + bt_uuid_t uuid128; + + bt_uuid_to_uuid128(uuid, &uuid128); + bt_uuid_to_string(&uuid128, uuid_str, sizeof(uuid_str)); + + printf("%s\n", uuid_str); +} + +static void print_incl(struct gatt_db_attribute *attr, void *user_data) +{ + struct client *cli = user_data; + uint16_t handle, start, end; + struct gatt_db_attribute *service; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_incl_data(attr, &handle, &start, &end)) + return; + + service = gatt_db_get_attribute(cli->db, start); + if (!service) + return; + + gatt_db_attribute_get_service_uuid(service, &uuid); + + printf("\t " COLOR_GREEN "include" COLOR_OFF " - handle: " + "0x%04x, - start: 0x%04x, end: 0x%04x," + "uuid: ", handle, start, end); + print_uuid(&uuid); +} + +static void print_desc(struct gatt_db_attribute *attr, void *user_data) +{ + printf("\t\t " COLOR_MAGENTA "descr" COLOR_OFF + " - handle: 0x%04x, uuid: ", + gatt_db_attribute_get_handle(attr)); + print_uuid(gatt_db_attribute_get_type(attr)); +} + +static void print_chrc(struct gatt_db_attribute *attr, void *user_data) +{ + uint16_t handle, value_handle; + uint8_t properties; + uint16_t ext_prop; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_char_data(attr, &handle, + &value_handle, + &properties, + &ext_prop, + &uuid)) + return; + + printf("\t " COLOR_YELLOW "charac" COLOR_OFF + " - start: 0x%04x, value: 0x%04x, " + "props: 0x%02x, ext_props: 0x%04x, uuid: ", + handle, value_handle, properties, ext_prop); + print_uuid(&uuid); + + gatt_db_service_foreach_desc(attr, print_desc, NULL); +} + +static void print_service(struct gatt_db_attribute *attr, void *user_data) +{ + struct client *cli = user_data; + uint16_t start, end; + bool primary; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_service_data(attr, &start, &end, &primary, + &uuid)) + return; + + printf(COLOR_RED "service" COLOR_OFF " - start: 0x%04x, " + "end: 0x%04x, type: %s, uuid: ", + start, end, primary ? "primary" : "secondary"); + print_uuid(&uuid); + + gatt_db_service_foreach_incl(attr, print_incl, cli); + gatt_db_service_foreach_char(attr, print_chrc, NULL); + + printf("\n"); +} + +static void print_services(struct client *cli) +{ + printf("\n"); + + gatt_db_foreach_service(cli->db, NULL, print_service, cli); +} + +static void print_services_by_uuid(struct client *cli, const bt_uuid_t *uuid) +{ + printf("\n"); + + gatt_db_foreach_service(cli->db, uuid, print_service, cli); +} + +static void print_services_by_handle(struct client *cli, uint16_t handle) +{ + printf("\n"); + + /* TODO: Filter by handle */ + gatt_db_foreach_service(cli->db, NULL, print_service, cli); +} + +static void ready_cb(bool success, uint8_t att_ecode, void *user_data) +{ + struct client *cli = user_data; + + if (!success) { + PRLOG("GATT discovery procedures failed - error code: 0x%02x\n", + att_ecode); + return; + } + + PRLOG("GATT discovery procedures complete\n"); + + print_services(cli); + print_prompt(); +} + +static void service_changed_cb(uint16_t start_handle, uint16_t end_handle, + void *user_data) +{ + struct client *cli = user_data; + + printf("\nService Changed handled - start: 0x%04x end: 0x%04x\n", + start_handle, end_handle); + + gatt_db_foreach_service_in_range(cli->db, NULL, print_service, cli, + start_handle, end_handle); + print_prompt(); +} + +static void services_usage(void) +{ + printf("Usage: services [options]\nOptions:\n" + "\t -u, --uuid \tService UUID\n" + "\t -a, --handle \tService start handle\n" + "\t -h, --help\t\tShow help message\n" + "e.g.:\n" + "\tservices\n\tservices -u 0x180d\n\tservices -a 0x0009\n"); +} + +static bool parse_args(char *str, int expected_argc, char **argv, int *argc) +{ + char **ap; + + for (ap = argv; (*ap = strsep(&str, " \t")) != NULL;) { + if (**ap == '\0') + continue; + + (*argc)++; + ap++; + + if (*argc > expected_argc) + return false; + } + + return true; +} + +static void cmd_services(struct client *cli, char *cmd_str) +{ + char *argv[3]; + int argc = 0; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, 2, argv, &argc)) { + services_usage(); + return; + } + + if (!argc) { + print_services(cli); + return; + } + + if (argc != 2) { + services_usage(); + return; + } + + if (!strcmp(argv[0], "-u") || !strcmp(argv[0], "--uuid")) { + bt_uuid_t tmp, uuid; + + if (bt_string_to_uuid(&tmp, argv[1]) < 0) { + printf("Invalid UUID: %s\n", argv[1]); + return; + } + + bt_uuid_to_uuid128(&tmp, &uuid); + + print_services_by_uuid(cli, &uuid); + } else if (!strcmp(argv[0], "-a") || !strcmp(argv[0], "--handle")) { + uint16_t handle; + char *endptr = NULL; + + handle = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0') { + printf("Invalid start handle: %s\n", argv[1]); + return; + } + + print_services_by_handle(cli, handle); + } else + services_usage(); +} + +static void read_multiple_usage(void) +{ + printf("Usage: read-multiple ...\n"); +} + +static void read_multiple_cb(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + int i; + + if (!success) { + PRLOG("\nRead multiple request failed: 0x%02x\n", att_ecode); + return; + } + + printf("\nRead multiple value (%u bytes):", length); + + for (i = 0; i < length; i++) + printf("%02x ", value[i]); + + PRLOG("\n"); +} + +static void cmd_read_multiple(struct client *cli, char *cmd_str) +{ + int argc = 0; + uint16_t *value; + char *argv[512]; + int i; + char *endptr = NULL; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, sizeof(argv), argv, &argc) || argc < 2) { + read_multiple_usage(); + return; + } + + value = malloc(sizeof(uint16_t) * argc); + if (!value) { + printf("Failed to construct value\n"); + return; + } + + for (i = 0; i < argc; i++) { + value[i] = strtol(argv[i], &endptr, 0); + if (endptr == argv[i] || *endptr != '\0' || !value[i]) { + printf("Invalid value byte: %s\n", argv[i]); + free(value); + return; + } + } + + if (!bt_gatt_client_read_multiple(cli->gatt, value, argc, + read_multiple_cb, NULL, NULL)) + printf("Failed to initiate read multiple procedure\n"); + + free(value); +} + +static void read_value_usage(void) +{ + printf("Usage: read-value \n"); +} + +static void read_cb(bool success, uint8_t att_ecode, const uint8_t *value, + uint16_t length, void *user_data) +{ + int i; + + if (!success) { + PRLOG("\nRead request failed: %s (0x%02x)\n", + ecode_to_string(att_ecode), att_ecode); + return; + } + + printf("\nRead value"); + + if (length == 0) { + PRLOG(": 0 bytes\n"); + return; + } + + printf(" (%u bytes): ", length); + + for (i = 0; i < length; i++) + printf("%02x ", value[i]); + + PRLOG("\n"); +} + +static void cmd_read_value(struct client *cli, char *cmd_str) +{ + char *argv[2]; + int argc = 0; + uint16_t handle; + char *endptr = NULL; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, 1, argv, &argc) || argc != 1) { + read_value_usage(); + return; + } + + handle = strtol(argv[0], &endptr, 0); + if (!endptr || *endptr != '\0' || !handle) { + printf("Invalid value handle: %s\n", argv[0]); + return; + } + + if (!bt_gatt_client_read_value(cli->gatt, handle, read_cb, + NULL, NULL)) + printf("Failed to initiate read value procedure\n"); +} + +static void read_long_value_usage(void) +{ + printf("Usage: read-long-value \n"); +} + +static void cmd_read_long_value(struct client *cli, char *cmd_str) +{ + char *argv[3]; + int argc = 0; + uint16_t handle; + uint16_t offset; + char *endptr = NULL; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, 2, argv, &argc) || argc != 2) { + read_long_value_usage(); + return; + } + + handle = strtol(argv[0], &endptr, 0); + if (!endptr || *endptr != '\0' || !handle) { + printf("Invalid value handle: %s\n", argv[0]); + return; + } + + endptr = NULL; + offset = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0') { + printf("Invalid offset: %s\n", argv[1]); + return; + } + + if (!bt_gatt_client_read_long_value(cli->gatt, handle, offset, read_cb, + NULL, NULL)) + printf("Failed to initiate read long value procedure\n"); +} + +static void write_value_usage(void) +{ + printf("Usage: write-value [options] \n" + "Options:\n" + "\t-w, --without-response\tWrite without response\n" + "\t-s, --signed-write\tSigned write command\n" + "e.g.:\n" + "\twrite-value 0x0001 00 01 00\n"); +} + +static struct option write_value_options[] = { + { "without-response", 0, 0, 'w' }, + { "signed-write", 0, 0, 's' }, + { } +}; + +static void write_cb(bool success, uint8_t att_ecode, void *user_data) +{ + if (success) { + PRLOG("\nWrite successful\n"); + } else { + PRLOG("\nWrite failed: %s (0x%02x)\n", + ecode_to_string(att_ecode), att_ecode); + } +} + +static void cmd_write_value(struct client *cli, char *cmd_str) +{ + int opt, i, val; + char *argvbuf[516]; + char **argv = argvbuf; + int argc = 1; + uint16_t handle; + char *endptr = NULL; + int length; + uint8_t *value = NULL; + bool without_response = false; + bool signed_write = false; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, 514, argv + 1, &argc)) { + printf("Too many arguments\n"); + write_value_usage(); + return; + } + + optind = 0; + argv[0] = "write-value"; + while ((opt = getopt_long(argc, argv, "+ws", write_value_options, + NULL)) != -1) { + switch (opt) { + case 'w': + without_response = true; + break; + case 's': + signed_write = true; + break; + default: + write_value_usage(); + return; + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + write_value_usage(); + return; + } + + handle = strtol(argv[0], &endptr, 0); + if (!endptr || *endptr != '\0' || !handle) { + printf("Invalid handle: %s\n", argv[0]); + return; + } + + length = argc - 1; + + if (length > 0) { + if (length > UINT16_MAX) { + printf("Write value too long\n"); + return; + } + + value = malloc(length); + if (!value) { + printf("Failed to construct write value\n"); + return; + } + + for (i = 1; i < argc; i++) { + val = strtol(argv[i], &endptr, 0); + if (endptr == argv[i] || *endptr != '\0' + || errno == ERANGE || val < 0 || val > 255) { + printf("Invalid value byte: %s\n", + argv[i]); + goto done; + } + value[i-1] = val; + } + } + + if (without_response) { + if (!bt_gatt_client_write_without_response(cli->gatt, handle, + signed_write, value, length)) { + printf("Failed to initiate write without response " + "procedure\n"); + goto done; + } + + printf("Write command sent\n"); + goto done; + } + + if (!bt_gatt_client_write_value(cli->gatt, handle, value, length, + write_cb, + NULL, NULL)) + printf("Failed to initiate write procedure\n"); + +done: + free(value); +} + +static void write_long_value_usage(void) +{ + printf("Usage: write-long-value [options] " + "\n" + "Options:\n" + "\t-r, --reliable-write\tReliable write\n" + "e.g.:\n" + "\twrite-long-value 0x0001 0 00 01 00\n"); +} + +static struct option write_long_value_options[] = { + { "reliable-write", 0, 0, 'r' }, + { } +}; + +static void write_long_cb(bool success, bool reliable_error, uint8_t att_ecode, + void *user_data) +{ + if (success) { + PRLOG("Write successful\n"); + } else if (reliable_error) { + PRLOG("Reliable write not verified\n"); + } else { + PRLOG("\nWrite failed: %s (0x%02x)\n", + ecode_to_string(att_ecode), att_ecode); + } +} + +static void cmd_write_long_value(struct client *cli, char *cmd_str) +{ + int opt, i, val; + char *argvbuf[516]; + char **argv = argvbuf; + int argc = 1; + uint16_t handle; + uint16_t offset; + char *endptr = NULL; + int length; + uint8_t *value = NULL; + bool reliable_writes = false; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, 514, argv + 1, &argc)) { + printf("Too many arguments\n"); + write_value_usage(); + return; + } + + optind = 0; + argv[0] = "write-long-value"; + while ((opt = getopt_long(argc, argv, "+r", write_long_value_options, + NULL)) != -1) { + switch (opt) { + case 'r': + reliable_writes = true; + break; + default: + write_long_value_usage(); + return; + } + } + + argc -= optind; + argv += optind; + + if (argc < 2) { + write_long_value_usage(); + return; + } + + handle = strtol(argv[0], &endptr, 0); + if (!endptr || *endptr != '\0' || !handle) { + printf("Invalid handle: %s\n", argv[0]); + return; + } + + endptr = NULL; + offset = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0' || errno == ERANGE) { + printf("Invalid offset: %s\n", argv[1]); + return; + } + + length = argc - 2; + + if (length > 0) { + if (length > UINT16_MAX) { + printf("Write value too long\n"); + return; + } + + value = malloc(length); + if (!value) { + printf("Failed to construct write value\n"); + return; + } + + for (i = 2; i < argc; i++) { + val = strtol(argv[i], &endptr, 0); + if (endptr == argv[i] || *endptr != '\0' + || errno == ERANGE || val < 0 || val > 255) { + printf("Invalid value byte: %s\n", + argv[i]); + free(value); + return; + } + value[i-2] = val; + } + } + + if (!bt_gatt_client_write_long_value(cli->gatt, reliable_writes, handle, + offset, value, length, + write_long_cb, + NULL, NULL)) + printf("Failed to initiate long write procedure\n"); + + free(value); +} + +static void write_prepare_usage(void) +{ + printf("Usage: write-prepare [options] " + "\n" + "Options:\n" + "\t-s, --session-id\tSession id\n" + "e.g.:\n" + "\twrite-prepare -s 1 0x0001 00 01 00\n"); +} + +static struct option write_prepare_options[] = { + { "session-id", 1, 0, 's' }, + { } +}; + +static void cmd_write_prepare(struct client *cli, char *cmd_str) +{ + int opt, i, val; + char *argvbuf[516]; + char **argv = argvbuf; + int argc = 0; + unsigned int id = 0; + uint16_t handle; + uint16_t offset; + char *endptr = NULL; + unsigned int length; + uint8_t *value = NULL; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, 514, argv + 1, &argc)) { + printf("Too many arguments\n"); + write_value_usage(); + return; + } + + /* Add command name for getopt_long */ + argc++; + argv[0] = "write-prepare"; + + optind = 0; + while ((opt = getopt_long(argc, argv , "s:", write_prepare_options, + NULL)) != -1) { + switch (opt) { + case 's': + if (!optarg) { + write_prepare_usage(); + return; + } + + id = atoi(optarg); + + break; + default: + write_prepare_usage(); + return; + } + } + + argc -= optind; + argv += optind; + + if (argc < 3) { + write_prepare_usage(); + return; + } + + if (cli->reliable_session_id != id) { + printf("Session id != Ongoing session id (%u!=%u)\n", id, + cli->reliable_session_id); + return; + } + + handle = strtol(argv[0], &endptr, 0); + if (!endptr || *endptr != '\0' || !handle) { + printf("Invalid handle: %s\n", argv[0]); + return; + } + + endptr = NULL; + offset = strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0' || errno == ERANGE) { + printf("Invalid offset: %s\n", argv[1]); + return; + } + + /* + * First two arguments are handle and offset. What remains is the value + * length + */ + length = argc - 2; + + if (length == 0) + goto done; + + if (length > UINT16_MAX) { + printf("Write value too long\n"); + return; + } + + value = malloc(length); + if (!value) { + printf("Failed to allocate memory for value\n"); + return; + } + + for (i = 2; i < argc; i++) { + val = strtol(argv[i], &endptr, 0); + if (endptr == argv[i] || *endptr != '\0' || errno == ERANGE + || val < 0 || val > 255) { + printf("Invalid value byte: %s\n", argv[i]); + free(value); + return; + } + value[i-2] = val; + } + +done: + cli->reliable_session_id = + bt_gatt_client_prepare_write(cli->gatt, id, + handle, offset, + value, length, + write_long_cb, NULL, + NULL); + if (!cli->reliable_session_id) + printf("Failed to proceed prepare write\n"); + else + printf("Prepare write success.\n" + "Session id: %d to be used on next write\n", + cli->reliable_session_id); + + free(value); +} + +static void write_execute_usage(void) +{ + printf("Usage: write-execute \n" + "e.g.:\n" + "\twrite-execute 1 0\n"); +} + +static void cmd_write_execute(struct client *cli, char *cmd_str) +{ + char *argvbuf[516]; + char **argv = argvbuf; + int argc = 0; + char *endptr = NULL; + unsigned int session_id; + bool execute; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, 514, argv, &argc)) { + printf("Too many arguments\n"); + write_value_usage(); + return; + } + + if (argc < 2) { + write_execute_usage(); + return; + } + + session_id = strtol(argv[0], &endptr, 0); + if (!endptr || *endptr != '\0') { + printf("Invalid session id: %s\n", argv[0]); + return; + } + + if (session_id != cli->reliable_session_id) { + printf("Invalid session id: %u != %u\n", session_id, + cli->reliable_session_id); + return; + } + + execute = !!strtol(argv[1], &endptr, 0); + if (!endptr || *endptr != '\0') { + printf("Invalid execute: %s\n", argv[1]); + return; + } + + if (execute) { + if (!bt_gatt_client_write_execute(cli->gatt, session_id, + write_cb, NULL, NULL)) + printf("Failed to proceed write execute\n"); + } else { + bt_gatt_client_cancel(cli->gatt, session_id); + } + + cli->reliable_session_id = 0; +} + +static void register_notify_usage(void) +{ + printf("Usage: register-notify \n"); +} + +static void notify_cb(uint16_t value_handle, const uint8_t *value, + uint16_t length, void *user_data) +{ + int i; + + printf("\n\tHandle Value Not/Ind: 0x%04x - ", value_handle); + + if (length == 0) { + PRLOG("(0 bytes)\n"); + return; + } + + printf("(%u bytes): ", length); + + for (i = 0; i < length; i++) + printf("%02x ", value[i]); + + PRLOG("\n"); +} + +static void register_notify_cb(uint16_t att_ecode, void *user_data) +{ + if (att_ecode) { + PRLOG("Failed to register notify handler " + "- error code: 0x%02x\n", att_ecode); + return; + } + + PRLOG("Registered notify handler!\n"); +} + +static void cmd_register_notify(struct client *cli, char *cmd_str) +{ + char *argv[2]; + int argc = 0; + uint16_t value_handle; + unsigned int id; + char *endptr = NULL; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, 1, argv, &argc) || argc != 1) { + register_notify_usage(); + return; + } + + value_handle = strtol(argv[0], &endptr, 0); + if (!endptr || *endptr != '\0' || !value_handle) { + printf("Invalid value handle: %s\n", argv[0]); + return; + } + + id = bt_gatt_client_register_notify(cli->gatt, value_handle, + register_notify_cb, + notify_cb, NULL, NULL); + if (!id) { + printf("Failed to register notify handler\n"); + return; + } + + printf("Registering notify handler with id: %u\n", id); +} + +static void unregister_notify_usage(void) +{ + printf("Usage: unregister-notify \n"); +} + +static void cmd_unregister_notify(struct client *cli, char *cmd_str) +{ + char *argv[2]; + int argc = 0; + unsigned int id; + char *endptr = NULL; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, 1, argv, &argc) || argc != 1) { + unregister_notify_usage(); + return; + } + + id = strtol(argv[0], &endptr, 0); + if (!endptr || *endptr != '\0' || !id) { + printf("Invalid notify id: %s\n", argv[0]); + return; + } + + if (!bt_gatt_client_unregister_notify(cli->gatt, id)) { + printf("Failed to unregister notify handler with id: %u\n", id); + return; + } + + printf("Unregistered notify handler with id: %u\n", id); +} + +static void set_security_usage(void) +{ + printf("Usage: set-security \n" + "level: 1-3\n" + "e.g.:\n" + "\tset-security 2\n"); +} + +static void cmd_set_security(struct client *cli, char *cmd_str) +{ + char *argv[2]; + int argc = 0; + char *endptr = NULL; + int level; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + if (!parse_args(cmd_str, 1, argv, &argc)) { + printf("Too many arguments\n"); + set_security_usage(); + return; + } + + if (argc < 1) { + set_security_usage(); + return; + } + + level = strtol(argv[0], &endptr, 0); + if (!endptr || *endptr != '\0' || level < 1 || level > 3) { + printf("Invalid level: %s\n", argv[0]); + return; + } + + if (!bt_gatt_client_set_security(cli->gatt, level)) + printf("Could not set sec level\n"); + else + printf("Setting security level %d success\n", level); +} + +static void cmd_get_security(struct client *cli, char *cmd_str) +{ + int level; + + if (!bt_gatt_client_is_ready(cli->gatt)) { + printf("GATT client not initialized\n"); + return; + } + + level = bt_gatt_client_get_security(cli->gatt); + if (level < 0) + printf("Could not set sec level\n"); + else + printf("Security level: %u\n", level); +} + +static bool convert_sign_key(char *optarg, uint8_t key[16]) +{ + int i; + + if (strlen(optarg) != 32) { + printf("sign-key length is invalid\n"); + return false; + } + + for (i = 0; i < 16; i++) { + if (sscanf(optarg + (i * 2), "%2hhx", &key[i]) != 1) + return false; + } + + return true; +} + +static void set_sign_key_usage(void) +{ + printf("Usage: set-sign-key [options]\nOptions:\n" + "\t -c, --sign-key \tCSRK\n" + "e.g.:\n" + "\tset-sign-key -c D8515948451FEA320DC05A2E88308188\n"); +} + +static bool local_counter(uint32_t *sign_cnt, void *user_data) +{ + static uint32_t cnt = 0; + + *sign_cnt = cnt++; + + return true; +} + +static void cmd_set_sign_key(struct client *cli, char *cmd_str) +{ + char *argv[3]; + int argc = 0; + uint8_t key[16]; + + memset(key, 0, 16); + + if (!parse_args(cmd_str, 2, argv, &argc)) { + set_sign_key_usage(); + return; + } + + if (argc != 2) { + set_sign_key_usage(); + return; + } + + if (!strcmp(argv[0], "-c") || !strcmp(argv[0], "--sign-key")) { + if (convert_sign_key(argv[1], key)) + bt_att_set_local_key(cli->att, key, local_counter, cli); + } else + set_sign_key_usage(); +} + +static void cmd_help(struct client *cli, char *cmd_str); + +typedef void (*command_func_t)(struct client *cli, char *cmd_str); + +static struct { + char *cmd; + command_func_t func; + char *doc; +} command[] = { + { "help", cmd_help, "\tDisplay help message" }, + { "services", cmd_services, "\tShow discovered services" }, + { "read-value", cmd_read_value, + "\tRead a characteristic or descriptor value" }, + { "read-long-value", cmd_read_long_value, + "\tRead a long characteristic or desctriptor value" }, + { "read-multiple", cmd_read_multiple, "\tRead Multiple" }, + { "write-value", cmd_write_value, + "\tWrite a characteristic or descriptor value" }, + { "write-long-value", cmd_write_long_value, + "Write long characteristic or descriptor value" }, + { "write-prepare", cmd_write_prepare, + "\tWrite prepare characteristic or descriptor value" }, + { "write-execute", cmd_write_execute, + "\tExecute already prepared write" }, + { "register-notify", cmd_register_notify, + "\tSubscribe to not/ind from a characteristic" }, + { "unregister-notify", cmd_unregister_notify, + "Unregister a not/ind session"}, + { "set-security", cmd_set_security, + "\tSet security level on le connection"}, + { "get-security", cmd_get_security, + "\tGet security level on le connection"}, + { "set-sign-key", cmd_set_sign_key, + "\tSet signing key for signed write command"}, + { } +}; + +static void cmd_help(struct client *cli, char *cmd_str) +{ + int i; + + printf("Commands:\n"); + for (i = 0; command[i].cmd; i++) + printf("\t%-15s\t%s\n", command[i].cmd, command[i].doc); +} + +static void prompt_read_cb(int fd, uint32_t events, void *user_data) +{ + ssize_t read; + size_t len = 0; + char *line = NULL; + char *cmd = NULL, *args; + struct client *cli = user_data; + int i; + + if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { + mainloop_quit(); + return; + } + + if ((read = getline(&line, &len, stdin)) == -1) + return; + + if (read <= 1) { + cmd_help(cli, NULL); + print_prompt(); + return; + } + + line[read-1] = '\0'; + args = line; + + while ((cmd = strsep(&args, " \t"))) + if (*cmd != '\0') + break; + + if (!cmd) + goto failed; + + for (i = 0; command[i].cmd; i++) { + if (strcmp(command[i].cmd, cmd) == 0) + break; + } + + if (command[i].cmd) + command[i].func(cli, args); + else + fprintf(stderr, "Unknown command: %s\n", line); + +failed: + print_prompt(); + + free(line); +} + +static void signal_cb(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + default: + break; + } +} + +static int l2cap_le_att_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type, + int sec) +{ + int sock; + struct sockaddr_l2 srcaddr, dstaddr; + struct bt_security btsec; + + if (verbose) { + char srcaddr_str[18], dstaddr_str[18]; + + ba2str(src, srcaddr_str); + ba2str(dst, dstaddr_str); + + printf("btgatt-client: Opening L2CAP LE connection on ATT " + "channel:\n\t src: %s\n\tdest: %s\n", + srcaddr_str, dstaddr_str); + } + + sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sock < 0) { + perror("Failed to create L2CAP socket"); + return -1; + } + + /* Set up source address */ + memset(&srcaddr, 0, sizeof(srcaddr)); + srcaddr.l2_family = AF_BLUETOOTH; + srcaddr.l2_cid = htobs(ATT_CID); + srcaddr.l2_bdaddr_type = 0; + bacpy(&srcaddr.l2_bdaddr, src); + + if (bind(sock, (struct sockaddr *)&srcaddr, sizeof(srcaddr)) < 0) { + perror("Failed to bind L2CAP socket"); + close(sock); + return -1; + } + + /* Set the security level */ + memset(&btsec, 0, sizeof(btsec)); + btsec.level = sec; + if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &btsec, + sizeof(btsec)) != 0) { + fprintf(stderr, "Failed to set L2CAP security level\n"); + close(sock); + return -1; + } + + /* Set up destination address */ + memset(&dstaddr, 0, sizeof(dstaddr)); + dstaddr.l2_family = AF_BLUETOOTH; + dstaddr.l2_cid = htobs(ATT_CID); + dstaddr.l2_bdaddr_type = dst_type; + bacpy(&dstaddr.l2_bdaddr, dst); + + printf("Connecting to device..."); + fflush(stdout); + + if (connect(sock, (struct sockaddr *) &dstaddr, sizeof(dstaddr)) < 0) { + perror(" Failed to connect"); + close(sock); + return -1; + } + + printf(" Done\n"); + + return sock; +} + +static void usage(void) +{ + printf("btgatt-client\n"); + printf("Usage:\n\tbtgatt-client [options]\n"); + + printf("Options:\n" + "\t-i, --index \t\tSpecify adapter index, e.g. hci0\n" + "\t-d, --dest \t\tSpecify the destination address\n" + "\t-t, --type [random|public] \tSpecify the LE address type\n" + "\t-m, --mtu \t\tThe ATT MTU to use\n" + "\t-s, --security-level \tSet security level (low|" + "medium|high)\n" + "\t-v, --verbose\t\t\tEnable extra logging\n" + "\t-h, --help\t\t\tDisplay help\n"); +} + +static struct option main_options[] = { + { "index", 1, 0, 'i' }, + { "dest", 1, 0, 'd' }, + { "type", 1, 0, 't' }, + { "mtu", 1, 0, 'm' }, + { "security-level", 1, 0, 's' }, + { "verbose", 0, 0, 'v' }, + { "help", 0, 0, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + int opt; + int sec = BT_SECURITY_LOW; + uint16_t mtu = 0; + uint8_t dst_type = BDADDR_LE_PUBLIC; + bool dst_addr_given = false; + bdaddr_t src_addr, dst_addr; + int dev_id = -1; + int fd; + struct client *cli; + + while ((opt = getopt_long(argc, argv, "+hvs:m:t:d:i:", + main_options, NULL)) != -1) { + switch (opt) { + case 'h': + usage(); + return EXIT_SUCCESS; + case 'v': + verbose = true; + break; + case 's': + if (strcmp(optarg, "low") == 0) + sec = BT_SECURITY_LOW; + else if (strcmp(optarg, "medium") == 0) + sec = BT_SECURITY_MEDIUM; + else if (strcmp(optarg, "high") == 0) + sec = BT_SECURITY_HIGH; + else { + fprintf(stderr, "Invalid security level\n"); + return EXIT_FAILURE; + } + break; + case 'm': { + int arg; + + arg = atoi(optarg); + if (arg <= 0) { + fprintf(stderr, "Invalid MTU: %d\n", arg); + return EXIT_FAILURE; + } + + if (arg > UINT16_MAX) { + fprintf(stderr, "MTU too large: %d\n", arg); + return EXIT_FAILURE; + } + + mtu = (uint16_t)arg; + break; + } + case 't': + if (strcmp(optarg, "random") == 0) + dst_type = BDADDR_LE_RANDOM; + else if (strcmp(optarg, "public") == 0) + dst_type = BDADDR_LE_PUBLIC; + else { + fprintf(stderr, + "Allowed types: random, public\n"); + return EXIT_FAILURE; + } + break; + case 'd': + if (str2ba(optarg, &dst_addr) < 0) { + fprintf(stderr, "Invalid remote address: %s\n", + optarg); + return EXIT_FAILURE; + } + + dst_addr_given = true; + break; + + case 'i': + dev_id = hci_devid(optarg); + if (dev_id < 0) { + perror("Invalid adapter"); + return EXIT_FAILURE; + } + + break; + default: + fprintf(stderr, "Invalid option: %c\n", opt); + return EXIT_FAILURE; + } + } + + if (!argc) { + usage(); + return EXIT_SUCCESS; + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc) { + usage(); + return EXIT_SUCCESS; + } + + if (dev_id == -1) + bacpy(&src_addr, BDADDR_ANY); + else if (hci_devba(dev_id, &src_addr) < 0) { + perror("Adapter not available"); + return EXIT_FAILURE; + } + + if (!dst_addr_given) { + fprintf(stderr, "Destination address required!\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + fd = l2cap_le_att_connect(&src_addr, &dst_addr, dst_type, sec); + if (fd < 0) + return EXIT_FAILURE; + + cli = client_create(fd, mtu); + if (!cli) { + close(fd); + return EXIT_FAILURE; + } + + if (mainloop_add_fd(fileno(stdin), + EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR, + prompt_read_cb, cli, NULL) < 0) { + fprintf(stderr, "Failed to initialize console\n"); + return EXIT_FAILURE; + } + + print_prompt(); + + mainloop_run_with_signal(signal_cb, NULL); + + printf("\n\nShutting down...\n"); + + client_destroy(cli); + + return EXIT_SUCCESS; +} diff --git a/tools/btgatt-server.c b/tools/btgatt-server.c new file mode 100644 index 0000000..d9d96e6 --- /dev/null +++ b/tools/btgatt-server.c @@ -0,0 +1,1262 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Google 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/l2cap.h" +#include "lib/uuid.h" + +#include "src/shared/mainloop.h" +#include "src/shared/util.h" +#include "src/shared/att.h" +#include "src/shared/queue.h" +#include "src/shared/timeout.h" +#include "src/shared/gatt-db.h" +#include "src/shared/gatt-server.h" + +#define UUID_GAP 0x1800 +#define UUID_GATT 0x1801 +#define UUID_HEART_RATE 0x180d +#define UUID_HEART_RATE_MSRMT 0x2a37 +#define UUID_HEART_RATE_BODY 0x2a38 +#define UUID_HEART_RATE_CTRL 0x2a39 + +#define ATT_CID 4 + +#define PRLOG(...) \ + do { \ + printf(__VA_ARGS__); \ + print_prompt(); \ + } while (0) + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define COLOR_OFF "\x1B[0m" +#define COLOR_RED "\x1B[0;91m" +#define COLOR_GREEN "\x1B[0;92m" +#define COLOR_YELLOW "\x1B[0;93m" +#define COLOR_BLUE "\x1B[0;94m" +#define COLOR_MAGENTA "\x1B[0;95m" +#define COLOR_BOLDGRAY "\x1B[1;30m" +#define COLOR_BOLDWHITE "\x1B[1;37m" + +static const char test_device_name[] = "Very Long Test Device Name For Testing " + "ATT Protocol Operations On GATT Server"; +static bool verbose = false; + +struct server { + int fd; + struct bt_att *att; + struct gatt_db *db; + struct bt_gatt_server *gatt; + + uint8_t *device_name; + size_t name_len; + + uint16_t gatt_svc_chngd_handle; + bool svc_chngd_enabled; + + uint16_t hr_handle; + uint16_t hr_msrmt_handle; + uint16_t hr_energy_expended; + bool hr_visible; + bool hr_msrmt_enabled; + int hr_ee_count; + unsigned int hr_timeout_id; +}; + +static void print_prompt(void) +{ + printf(COLOR_BLUE "[GATT server]" COLOR_OFF "# "); + fflush(stdout); +} + +static void att_disconnect_cb(int err, void *user_data) +{ + printf("Device disconnected: %s\n", strerror(err)); + + mainloop_quit(); +} + +static void att_debug_cb(const char *str, void *user_data) +{ + const char *prefix = user_data; + + PRLOG(COLOR_BOLDGRAY "%s" COLOR_BOLDWHITE "%s\n" COLOR_OFF, prefix, + str); +} + +static void gatt_debug_cb(const char *str, void *user_data) +{ + const char *prefix = user_data; + + PRLOG(COLOR_GREEN "%s%s\n" COLOR_OFF, prefix, str); +} + +static void gap_device_name_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t error = 0; + size_t len = 0; + const uint8_t *value = NULL; + + PRLOG("GAP Device Name Read called\n"); + + len = server->name_len; + + if (offset > len) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + len -= offset; + value = len ? &server->device_name[offset] : NULL; + +done: + gatt_db_attribute_read_result(attrib, id, error, value, len); +} + +static void gap_device_name_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 server *server = user_data; + uint8_t error = 0; + + PRLOG("GAP Device Name Write called\n"); + + /* If the value is being completely truncated, clean up and return */ + if (!(offset + len)) { + free(server->device_name); + server->device_name = NULL; + server->name_len = 0; + goto done; + } + + /* Implement this as a variable length attribute value. */ + if (offset > server->name_len) { + error = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (offset + len != server->name_len) { + uint8_t *name; + + name = realloc(server->device_name, offset + len); + if (!name) { + error = BT_ATT_ERROR_INSUFFICIENT_RESOURCES; + goto done; + } + + server->device_name = name; + server->name_len = offset + len; + } + + if (value) + memcpy(server->device_name + offset, value, len); + +done: + gatt_db_attribute_write_result(attrib, id, error); +} + +static void gap_device_name_ext_prop_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t value[2]; + + PRLOG("Device Name Extended Properties Read called\n"); + + value[0] = BT_GATT_CHRC_EXT_PROP_RELIABLE_WRITE; + value[1] = 0; + + gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value)); +} + +static void gatt_service_changed_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + PRLOG("Service Changed Read called\n"); + + gatt_db_attribute_read_result(attrib, id, 0, NULL, 0); +} + +static void gatt_svc_chngd_ccc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t value[2]; + + PRLOG("Service Changed CCC Read called\n"); + + value[0] = server->svc_chngd_enabled ? 0x02 : 0x00; + value[1] = 0x00; + + gatt_db_attribute_read_result(attrib, id, 0, value, sizeof(value)); +} + +static void gatt_svc_chngd_ccc_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t ecode = 0; + + PRLOG("Service Changed CCC Write called\n"); + + if (!value || len != 2) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (value[0] == 0x00) + server->svc_chngd_enabled = false; + else if (value[0] == 0x02) + server->svc_chngd_enabled = true; + else + ecode = 0x80; + + PRLOG("Service Changed Enabled: %s\n", + server->svc_chngd_enabled ? "true" : "false"); + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +static void hr_msrmt_ccc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t value[2]; + + value[0] = server->hr_msrmt_enabled ? 0x01 : 0x00; + value[1] = 0x00; + + gatt_db_attribute_read_result(attrib, id, 0, value, 2); +} + +static bool hr_msrmt_cb(void *user_data) +{ + struct server *server = user_data; + bool expended_present = !(server->hr_ee_count % 10); + uint16_t len = 2; + uint8_t pdu[4]; + uint32_t cur_ee; + + pdu[0] = 0x06; + pdu[1] = 90 + (rand() % 40); + + if (expended_present) { + pdu[0] |= 0x08; + put_le16(server->hr_energy_expended, pdu + 2); + len += 2; + } + + bt_gatt_server_send_notification(server->gatt, + server->hr_msrmt_handle, + pdu, len); + + + cur_ee = server->hr_energy_expended; + server->hr_energy_expended = MIN(UINT16_MAX, cur_ee + 10); + server->hr_ee_count++; + + return true; +} + +static void update_hr_msrmt_simulation(struct server *server) +{ + if (!server->hr_msrmt_enabled || !server->hr_visible) { + timeout_remove(server->hr_timeout_id); + return; + } + + server->hr_timeout_id = timeout_add(1000, hr_msrmt_cb, server, NULL); +} + +static void hr_msrmt_ccc_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t ecode = 0; + + if (!value || len != 2) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (value[0] == 0x00) + server->hr_msrmt_enabled = false; + else if (value[0] == 0x01) { + if (server->hr_msrmt_enabled) { + PRLOG("HR Measurement Already Enabled\n"); + goto done; + } + + server->hr_msrmt_enabled = true; + } else + ecode = 0x80; + + PRLOG("HR: Measurement Enabled: %s\n", + server->hr_msrmt_enabled ? "true" : "false"); + + update_hr_msrmt_simulation(server); + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +static void hr_control_point_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct server *server = user_data; + uint8_t ecode = 0; + + if (!value || len != 1) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (value[0] == 1) { + PRLOG("HR: Energy Expended value reset\n"); + server->hr_energy_expended = 0; + } + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +static void confirm_write(struct gatt_db_attribute *attr, int err, + void *user_data) +{ + if (!err) + return; + + fprintf(stderr, "Error caching attribute %p - err: %d\n", attr, err); + exit(1); +} + +static void populate_gap_service(struct server *server) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service, *tmp; + uint16_t appearance; + + /* Add the GAP service */ + bt_uuid16_create(&uuid, UUID_GAP); + service = gatt_db_add_service(server->db, &uuid, true, 6); + + /* + * Device Name characteristic. Make the value dynamically read and + * written via callbacks. + */ + bt_uuid16_create(&uuid, GATT_CHARAC_DEVICE_NAME); + gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_READ | + BT_GATT_CHRC_PROP_EXT_PROP, + gap_device_name_read_cb, + gap_device_name_write_cb, + server); + + bt_uuid16_create(&uuid, GATT_CHARAC_EXT_PROPER_UUID); + gatt_db_service_add_descriptor(service, &uuid, BT_ATT_PERM_READ, + gap_device_name_ext_prop_read_cb, + NULL, server); + + /* + * Appearance characteristic. Reads and writes should obtain the value + * from the database. + */ + bt_uuid16_create(&uuid, GATT_CHARAC_APPEARANCE); + tmp = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + NULL, NULL, server); + + /* + * Write the appearance value to the database, since we're not using a + * callback. + */ + put_le16(128, &appearance); + gatt_db_attribute_write(tmp, 0, (void *) &appearance, + sizeof(appearance), + BT_ATT_OP_WRITE_REQ, + NULL, confirm_write, + NULL); + + gatt_db_service_set_active(service, true); +} + +static void populate_gatt_service(struct server *server) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service, *svc_chngd; + + /* Add the GATT service */ + bt_uuid16_create(&uuid, UUID_GATT); + service = gatt_db_add_service(server->db, &uuid, true, 4); + + bt_uuid16_create(&uuid, GATT_CHARAC_SERVICE_CHANGED); + svc_chngd = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_INDICATE, + gatt_service_changed_cb, + NULL, server); + server->gatt_svc_chngd_handle = gatt_db_attribute_get_handle(svc_chngd); + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + gatt_db_service_add_descriptor(service, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + gatt_svc_chngd_ccc_read_cb, + gatt_svc_chngd_ccc_write_cb, server); + + gatt_db_service_set_active(service, true); +} + +static void populate_hr_service(struct server *server) +{ + bt_uuid_t uuid; + struct gatt_db_attribute *service, *hr_msrmt, *body; + uint8_t body_loc = 1; /* "Chest" */ + + /* Add Heart Rate Service */ + bt_uuid16_create(&uuid, UUID_HEART_RATE); + service = gatt_db_add_service(server->db, &uuid, true, 8); + server->hr_handle = gatt_db_attribute_get_handle(service); + + /* HR Measurement Characteristic */ + bt_uuid16_create(&uuid, UUID_HEART_RATE_MSRMT); + hr_msrmt = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_NONE, + BT_GATT_CHRC_PROP_NOTIFY, + NULL, NULL, NULL); + server->hr_msrmt_handle = gatt_db_attribute_get_handle(hr_msrmt); + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + gatt_db_service_add_descriptor(service, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + hr_msrmt_ccc_read_cb, + hr_msrmt_ccc_write_cb, server); + + /* + * Body Sensor Location Characteristic. Make reads obtain the value from + * the database. + */ + bt_uuid16_create(&uuid, UUID_HEART_RATE_BODY); + body = gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_READ, + BT_GATT_CHRC_PROP_READ, + NULL, NULL, server); + gatt_db_attribute_write(body, 0, (void *) &body_loc, sizeof(body_loc), + BT_ATT_OP_WRITE_REQ, + NULL, confirm_write, + NULL); + + /* HR Control Point Characteristic */ + bt_uuid16_create(&uuid, UUID_HEART_RATE_CTRL); + gatt_db_service_add_characteristic(service, &uuid, + BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_WRITE, + NULL, hr_control_point_write_cb, + server); + + if (server->hr_visible) + gatt_db_service_set_active(service, true); +} + +static void populate_db(struct server *server) +{ + populate_gap_service(server); + populate_gatt_service(server); + populate_hr_service(server); +} + +static struct server *server_create(int fd, uint16_t mtu, bool hr_visible) +{ + struct server *server; + size_t name_len = strlen(test_device_name); + + server = new0(struct server, 1); + if (!server) { + fprintf(stderr, "Failed to allocate memory for server\n"); + return NULL; + } + + server->att = bt_att_new(fd, false); + if (!server->att) { + fprintf(stderr, "Failed to initialze ATT transport layer\n"); + goto fail; + } + + if (!bt_att_set_close_on_unref(server->att, true)) { + fprintf(stderr, "Failed to set up ATT transport layer\n"); + goto fail; + } + + if (!bt_att_register_disconnect(server->att, att_disconnect_cb, NULL, + NULL)) { + fprintf(stderr, "Failed to set ATT disconnect handler\n"); + goto fail; + } + + server->name_len = name_len + 1; + server->device_name = malloc(name_len + 1); + if (!server->device_name) { + fprintf(stderr, "Failed to allocate memory for device name\n"); + goto fail; + } + + memcpy(server->device_name, test_device_name, name_len); + server->device_name[name_len] = '\0'; + + server->fd = fd; + server->db = gatt_db_new(); + if (!server->db) { + fprintf(stderr, "Failed to create GATT database\n"); + goto fail; + } + + server->gatt = bt_gatt_server_new(server->db, server->att, mtu, 0); + if (!server->gatt) { + fprintf(stderr, "Failed to create GATT server\n"); + goto fail; + } + + server->hr_visible = hr_visible; + + if (verbose) { + bt_att_set_debug(server->att, att_debug_cb, "att: ", NULL); + bt_gatt_server_set_debug(server->gatt, gatt_debug_cb, + "server: ", NULL); + } + + /* Random seed for generating fake Heart Rate measurements */ + srand(time(NULL)); + + /* bt_gatt_server already holds a reference */ + populate_db(server); + + return server; + +fail: + gatt_db_unref(server->db); + free(server->device_name); + bt_att_unref(server->att); + free(server); + + return NULL; +} + +static void server_destroy(struct server *server) +{ + timeout_remove(server->hr_timeout_id); + bt_gatt_server_unref(server->gatt); + gatt_db_unref(server->db); +} + +static void usage(void) +{ + printf("btgatt-server\n"); + printf("Usage:\n\tbtgatt-server [options]\n"); + + printf("Options:\n" + "\t-i, --index \t\tSpecify adapter index, e.g. hci0\n" + "\t-m, --mtu \t\t\tThe ATT MTU to use\n" + "\t-s, --security-level \tSet security level (low|" + "medium|high)\n" + "\t-t, --type [random|public] \t The source address type\n" + "\t-v, --verbose\t\t\tEnable extra logging\n" + "\t-r, --heart-rate\t\tEnable Heart Rate service\n" + "\t-h, --help\t\t\tDisplay help\n"); +} + +static struct option main_options[] = { + { "index", 1, 0, 'i' }, + { "mtu", 1, 0, 'm' }, + { "security-level", 1, 0, 's' }, + { "type", 1, 0, 't' }, + { "verbose", 0, 0, 'v' }, + { "heart-rate", 0, 0, 'r' }, + { "help", 0, 0, 'h' }, + { } +}; + +static int l2cap_le_att_listen_and_accept(bdaddr_t *src, int sec, + uint8_t src_type) +{ + int sk, nsk; + struct sockaddr_l2 srcaddr, addr; + socklen_t optlen; + struct bt_security btsec; + char ba[18]; + + sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sk < 0) { + perror("Failed to create L2CAP socket"); + return -1; + } + + /* Set up source address */ + memset(&srcaddr, 0, sizeof(srcaddr)); + srcaddr.l2_family = AF_BLUETOOTH; + srcaddr.l2_cid = htobs(ATT_CID); + srcaddr.l2_bdaddr_type = src_type; + bacpy(&srcaddr.l2_bdaddr, src); + + if (bind(sk, (struct sockaddr *) &srcaddr, sizeof(srcaddr)) < 0) { + perror("Failed to bind L2CAP socket"); + goto fail; + } + + /* Set the security level */ + memset(&btsec, 0, sizeof(btsec)); + btsec.level = sec; + if (setsockopt(sk, SOL_BLUETOOTH, BT_SECURITY, &btsec, + sizeof(btsec)) != 0) { + fprintf(stderr, "Failed to set L2CAP security level\n"); + goto fail; + } + + if (listen(sk, 10) < 0) { + perror("Listening on socket failed"); + goto fail; + } + + printf("Started listening on ATT channel. Waiting for connections\n"); + + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + nsk = accept(sk, (struct sockaddr *) &addr, &optlen); + if (nsk < 0) { + perror("Accept failed"); + goto fail; + } + + ba2str(&addr.l2_bdaddr, ba); + printf("Connect from %s\n", ba); + close(sk); + + return nsk; + +fail: + close(sk); + return -1; +} + +static void notify_usage(void) +{ + printf("Usage: notify [options] \n" + "Options:\n" + "\t -i, --indicate\tSend indication\n" + "e.g.:\n" + "\tnotify 0x0001 00 01 00\n"); +} + +static struct option notify_options[] = { + { "indicate", 0, 0, 'i' }, + { } +}; + +static bool parse_args(char *str, int expected_argc, char **argv, int *argc) +{ + char **ap; + + for (ap = argv; (*ap = strsep(&str, " \t")) != NULL;) { + if (**ap == '\0') + continue; + + (*argc)++; + ap++; + + if (*argc > expected_argc) + return false; + } + + return true; +} + +static void conf_cb(void *user_data) +{ + PRLOG("Received confirmation\n"); +} + +static void cmd_notify(struct server *server, char *cmd_str) +{ + int opt, i; + char *argvbuf[516]; + char **argv = argvbuf; + int argc = 1; + uint16_t handle; + char *endptr = NULL; + int length; + uint8_t *value = NULL; + bool indicate = false; + + if (!parse_args(cmd_str, 514, argv + 1, &argc)) { + printf("Too many arguments\n"); + notify_usage(); + return; + } + + optind = 0; + argv[0] = "notify"; + while ((opt = getopt_long(argc, argv, "+i", notify_options, + NULL)) != -1) { + switch (opt) { + case 'i': + indicate = true; + break; + default: + notify_usage(); + return; + } + } + + argc -= optind; + argv += optind; + + if (argc < 1) { + notify_usage(); + return; + } + + handle = strtol(argv[0], &endptr, 16); + if (!endptr || *endptr != '\0' || !handle) { + printf("Invalid handle: %s\n", argv[0]); + return; + } + + length = argc - 1; + + if (length > 0) { + if (length > UINT16_MAX) { + printf("Value too long\n"); + return; + } + + value = malloc(length); + if (!value) { + printf("Failed to construct value\n"); + return; + } + + for (i = 1; i < argc; i++) { + if (strlen(argv[i]) != 2) { + printf("Invalid value byte: %s\n", + argv[i]); + goto done; + } + + value[i-1] = strtol(argv[i], &endptr, 16); + if (endptr == argv[i] || *endptr != '\0' + || errno == ERANGE) { + printf("Invalid value byte: %s\n", + argv[i]); + goto done; + } + } + } + + if (indicate) { + if (!bt_gatt_server_send_indication(server->gatt, handle, + value, length, + conf_cb, NULL, NULL)) + printf("Failed to initiate indication\n"); + } else if (!bt_gatt_server_send_notification(server->gatt, handle, + value, length)) + printf("Failed to initiate notification\n"); + +done: + free(value); +} + +static void heart_rate_usage(void) +{ + printf("Usage: heart-rate on|off\n"); +} + +static void cmd_heart_rate(struct server *server, char *cmd_str) +{ + bool enable; + uint8_t pdu[4]; + struct gatt_db_attribute *attr; + + if (!cmd_str) { + heart_rate_usage(); + return; + } + + if (strcmp(cmd_str, "on") == 0) + enable = true; + else if (strcmp(cmd_str, "off") == 0) + enable = false; + else { + heart_rate_usage(); + return; + } + + if (enable == server->hr_visible) { + printf("Heart Rate Service already %s\n", + enable ? "visible" : "hidden"); + return; + } + + server->hr_visible = enable; + attr = gatt_db_get_attribute(server->db, server->hr_handle); + gatt_db_service_set_active(attr, server->hr_visible); + update_hr_msrmt_simulation(server); + + if (!server->svc_chngd_enabled) + return; + + put_le16(server->hr_handle, pdu); + put_le16(server->hr_handle + 7, pdu + 2); + + server->hr_msrmt_enabled = false; + update_hr_msrmt_simulation(server); + + bt_gatt_server_send_indication(server->gatt, + server->gatt_svc_chngd_handle, + pdu, 4, conf_cb, NULL, NULL); +} + +static void print_uuid(const bt_uuid_t *uuid) +{ + char uuid_str[MAX_LEN_UUID_STR]; + bt_uuid_t uuid128; + + bt_uuid_to_uuid128(uuid, &uuid128); + bt_uuid_to_string(&uuid128, uuid_str, sizeof(uuid_str)); + + printf("%s\n", uuid_str); +} + +static void print_incl(struct gatt_db_attribute *attr, void *user_data) +{ + struct server *server = user_data; + uint16_t handle, start, end; + struct gatt_db_attribute *service; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_incl_data(attr, &handle, &start, &end)) + return; + + service = gatt_db_get_attribute(server->db, start); + if (!service) + return; + + gatt_db_attribute_get_service_uuid(service, &uuid); + + printf("\t " COLOR_GREEN "include" COLOR_OFF " - handle: " + "0x%04x, - start: 0x%04x, end: 0x%04x," + "uuid: ", handle, start, end); + print_uuid(&uuid); +} + +static void print_desc(struct gatt_db_attribute *attr, void *user_data) +{ + printf("\t\t " COLOR_MAGENTA "descr" COLOR_OFF + " - handle: 0x%04x, uuid: ", + gatt_db_attribute_get_handle(attr)); + print_uuid(gatt_db_attribute_get_type(attr)); +} + +static void print_chrc(struct gatt_db_attribute *attr, void *user_data) +{ + uint16_t handle, value_handle; + uint8_t properties; + uint16_t ext_prop; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_char_data(attr, &handle, + &value_handle, + &properties, + &ext_prop, + &uuid)) + return; + + printf("\t " COLOR_YELLOW "charac" COLOR_OFF + " - start: 0x%04x, value: 0x%04x, " + "props: 0x%02x, ext_prop: 0x%04x, uuid: ", + handle, value_handle, properties, ext_prop); + print_uuid(&uuid); + + gatt_db_service_foreach_desc(attr, print_desc, NULL); +} + +static void print_service(struct gatt_db_attribute *attr, void *user_data) +{ + struct server *server = user_data; + uint16_t start, end; + bool primary; + bt_uuid_t uuid; + + if (!gatt_db_attribute_get_service_data(attr, &start, &end, &primary, + &uuid)) + return; + + printf(COLOR_RED "service" COLOR_OFF " - start: 0x%04x, " + "end: 0x%04x, type: %s, uuid: ", + start, end, primary ? "primary" : "secondary"); + print_uuid(&uuid); + + gatt_db_service_foreach_incl(attr, print_incl, server); + gatt_db_service_foreach_char(attr, print_chrc, NULL); + + printf("\n"); +} + +static void cmd_services(struct server *server, char *cmd_str) +{ + gatt_db_foreach_service(server->db, NULL, print_service, server); +} + +static bool convert_sign_key(char *optarg, uint8_t key[16]) +{ + int i; + + if (strlen(optarg) != 32) { + printf("sign-key length is invalid\n"); + return false; + } + + for (i = 0; i < 16; i++) { + if (sscanf(optarg + (i * 2), "%2hhx", &key[i]) != 1) + return false; + } + + return true; +} + +static void set_sign_key_usage(void) +{ + printf("Usage: set-sign-key [options]\nOptions:\n" + "\t -c, --sign-key \tRemote CSRK\n" + "e.g.:\n" + "\tset-sign-key -c D8515948451FEA320DC05A2E88308188\n"); +} + +static bool remote_counter(uint32_t *sign_cnt, void *user_data) +{ + static uint32_t cnt = 0; + + if (*sign_cnt < cnt) + return false; + + cnt = *sign_cnt; + + return true; +} + +static void cmd_set_sign_key(struct server *server, char *cmd_str) +{ + char *argv[3]; + int argc = 0; + uint8_t key[16]; + + memset(key, 0, 16); + + if (!parse_args(cmd_str, 2, argv, &argc)) { + set_sign_key_usage(); + return; + } + + if (argc != 2) { + set_sign_key_usage(); + return; + } + + if (!strcmp(argv[0], "-c") || !strcmp(argv[0], "--sign-key")) { + if (convert_sign_key(argv[1], key)) + bt_att_set_remote_key(server->att, key, remote_counter, + server); + } else + set_sign_key_usage(); +} + +static void cmd_help(struct server *server, char *cmd_str); + +typedef void (*command_func_t)(struct server *server, char *cmd_str); + +static struct { + char *cmd; + command_func_t func; + char *doc; +} command[] = { + { "help", cmd_help, "\tDisplay help message" }, + { "notify", cmd_notify, "\tSend handle-value notification" }, + { "heart-rate", cmd_heart_rate, "\tHide/Unhide Heart Rate Service" }, + { "services", cmd_services, "\tEnumerate all services" }, + { "set-sign-key", cmd_set_sign_key, + "\tSet remote signing key for signed write command"}, + { } +}; + +static void cmd_help(struct server *server, char *cmd_str) +{ + int i; + + printf("Commands:\n"); + for (i = 0; command[i].cmd; i++) + printf("\t%-15s\t%s\n", command[i].cmd, command[i].doc); +} + +static void prompt_read_cb(int fd, uint32_t events, void *user_data) +{ + ssize_t read; + size_t len = 0; + char *line = NULL; + char *cmd = NULL, *args; + struct server *server = user_data; + int i; + + if (events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) { + mainloop_quit(); + return; + } + + read = getline(&line, &len, stdin); + if (read < 0) + return; + + if (read <= 1) { + cmd_help(server, NULL); + print_prompt(); + return; + } + + line[read-1] = '\0'; + args = line; + + while ((cmd = strsep(&args, " \t"))) + if (*cmd != '\0') + break; + + if (!cmd) + goto failed; + + for (i = 0; command[i].cmd; i++) { + if (strcmp(command[i].cmd, cmd) == 0) + break; + } + + if (command[i].cmd) + command[i].func(server, args); + else + fprintf(stderr, "Unknown command: %s\n", line); + +failed: + print_prompt(); + + free(line); +} + +static void signal_cb(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + default: + break; + } +} + +int main(int argc, char *argv[]) +{ + int opt; + bdaddr_t src_addr; + int dev_id = -1; + int fd; + int sec = BT_SECURITY_LOW; + uint8_t src_type = BDADDR_LE_PUBLIC; + uint16_t mtu = 0; + bool hr_visible = false; + struct server *server; + + while ((opt = getopt_long(argc, argv, "+hvrs:t:m:i:", + main_options, NULL)) != -1) { + switch (opt) { + case 'h': + usage(); + return EXIT_SUCCESS; + case 'v': + verbose = true; + break; + case 'r': + hr_visible = true; + break; + case 's': + if (strcmp(optarg, "low") == 0) + sec = BT_SECURITY_LOW; + else if (strcmp(optarg, "medium") == 0) + sec = BT_SECURITY_MEDIUM; + else if (strcmp(optarg, "high") == 0) + sec = BT_SECURITY_HIGH; + else { + fprintf(stderr, "Invalid security level\n"); + return EXIT_FAILURE; + } + break; + case 't': + if (strcmp(optarg, "random") == 0) + src_type = BDADDR_LE_RANDOM; + else if (strcmp(optarg, "public") == 0) + src_type = BDADDR_LE_PUBLIC; + else { + fprintf(stderr, + "Allowed types: random, public\n"); + return EXIT_FAILURE; + } + break; + case 'm': { + int arg; + + arg = atoi(optarg); + if (arg <= 0) { + fprintf(stderr, "Invalid MTU: %d\n", arg); + return EXIT_FAILURE; + } + + if (arg > UINT16_MAX) { + fprintf(stderr, "MTU too large: %d\n", arg); + return EXIT_FAILURE; + } + + mtu = (uint16_t) arg; + break; + } + case 'i': + dev_id = hci_devid(optarg); + if (dev_id < 0) { + perror("Invalid adapter"); + return EXIT_FAILURE; + } + + break; + default: + fprintf(stderr, "Invalid option: %c\n", opt); + return EXIT_FAILURE; + } + } + + argc -= optind; + argv -= optind; + optind = 0; + + if (argc) { + usage(); + return EXIT_SUCCESS; + } + + if (dev_id == -1) + bacpy(&src_addr, BDADDR_ANY); + else if (hci_devba(dev_id, &src_addr) < 0) { + perror("Adapter not available"); + return EXIT_FAILURE; + } + + fd = l2cap_le_att_listen_and_accept(&src_addr, sec, src_type); + if (fd < 0) { + fprintf(stderr, "Failed to accept L2CAP ATT connection\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + server = server_create(fd, mtu, hr_visible); + if (!server) { + close(fd); + return EXIT_FAILURE; + } + + if (mainloop_add_fd(fileno(stdin), + EPOLLIN | EPOLLRDHUP | EPOLLHUP | EPOLLERR, + prompt_read_cb, server, NULL) < 0) { + fprintf(stderr, "Failed to initialize console\n"); + server_destroy(server); + + return EXIT_FAILURE; + } + + printf("Running GATT server\n"); + + print_prompt(); + + mainloop_run_with_signal(signal_cb, NULL); + + printf("\n\nShutting down...\n"); + + server_destroy(server); + + return EXIT_SUCCESS; +} diff --git a/tools/btinfo.c b/tools/btinfo.c new file mode 100644 index 0000000..5e60973 --- /dev/null +++ b/tools/btinfo.c @@ -0,0 +1,358 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "monitor/bt.h" +#include "src/shared/mainloop.h" +#include "src/shared/timeout.h" +#include "src/shared/util.h" +#include "src/shared/hci.h" + +#define BTPROTO_HCI 1 + +struct hci_dev_stats { + uint32_t err_rx; + uint32_t err_tx; + uint32_t cmd_tx; + uint32_t evt_rx; + uint32_t acl_tx; + uint32_t acl_rx; + uint32_t sco_tx; + uint32_t sco_rx; + uint32_t byte_rx; + uint32_t byte_tx; +}; + +struct hci_dev_info { + uint16_t dev_id; + char name[8]; + uint8_t bdaddr[6]; + uint32_t flags; + uint8_t type; + uint8_t features[8]; + uint32_t pkt_type; + uint32_t link_policy; + uint32_t link_mode; + uint16_t acl_mtu; + uint16_t acl_pkts; + uint16_t sco_mtu; + uint16_t sco_pkts; + struct hci_dev_stats stat; +}; + +#define HCIDEVUP _IOW('H', 201, int) +#define HCIDEVDOWN _IOW('H', 202, int) +#define HCIGETDEVINFO _IOR('H', 211, int) + +#define HCI_UP (1 << 0) + +#define HCI_PRIMARY 0x00 +#define HCI_AMP 0x01 + +static struct hci_dev_info hci_info; +static uint8_t hci_type; +static struct bt_hci *hci_dev; + +static bool reset_on_init = false; +static bool reset_on_shutdown = false; + +static bool shutdown_timeout(void *user_data) +{ + mainloop_quit(); + + return false; +} + +static void shutdown_complete(const void *data, uint8_t size, void *user_data) +{ + unsigned int id = PTR_TO_UINT(user_data); + + timeout_remove(id); + mainloop_quit(); +} + +static void shutdown_device(void) +{ + unsigned int id; + + bt_hci_flush(hci_dev); + + if (reset_on_shutdown) { + id = timeout_add(5000, shutdown_timeout, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, + shutdown_complete, UINT_TO_PTR(id), NULL); + } else + mainloop_quit(); +} + +static void local_version_callback(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_read_local_version *rsp = data; + + printf("HCI version: %u\n", rsp->hci_ver); + printf("HCI revision: %u\n", le16_to_cpu(rsp->hci_rev)); + + switch (hci_type) { + case HCI_PRIMARY: + printf("LMP version: %u\n", rsp->lmp_ver); + printf("LMP subversion: %u\n", le16_to_cpu(rsp->lmp_subver)); + break; + case HCI_AMP: + printf("PAL version: %u\n", rsp->lmp_ver); + printf("PAL subversion: %u\n", le16_to_cpu(rsp->lmp_subver)); + break; + } + + printf("Manufacturer: %u\n", le16_to_cpu(rsp->manufacturer)); +} + +static void local_commands_callback(const void *data, uint8_t size, + void *user_data) +{ + shutdown_device(); +} + +static void local_features_callback(const void *data, uint8_t size, + void *user_data) +{ + bt_hci_send(hci_dev, BT_HCI_CMD_READ_LOCAL_COMMANDS, NULL, 0, + local_commands_callback, NULL, NULL); +} + +static bool cmd_local(int argc, char *argv[]) +{ + if (reset_on_init) + bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, + NULL, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_READ_LOCAL_VERSION, NULL, 0, + local_version_callback, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_READ_LOCAL_FEATURES, NULL, 0, + local_features_callback, NULL, NULL); + + return true; +} + +typedef bool (*cmd_func_t)(int argc, char *argv[]); + +static const struct { + const char *name; + cmd_func_t func; + const char *help; +} cmd_table[] = { + { "local", cmd_local, "Print local controller details" }, + { } +}; + +static void signal_callback(int signum, void *user_data) +{ + static bool terminated = false; + + switch (signum) { + case SIGINT: + case SIGTERM: + if (!terminated) { + shutdown_device(); + terminated = true; + } + break; + } +} + +static void usage(void) +{ + int i; + + printf("btinfo - Bluetooth device testing tool\n" + "Usage:\n"); + printf("\tbtinfo [options] \n"); + printf("options:\n" + "\t-i, --index Use specified controller\n" + "\t-h, --help Show help options\n"); + printf("commands:\n"); + for (i = 0; cmd_table[i].name; i++) + printf("\t%-25s%s\n", cmd_table[i].name, cmd_table[i].help); +} + +static const struct option main_options[] = { + { "index", required_argument, NULL, 'i' }, + { "reset", no_argument, NULL, 'r' }, + { "raw", no_argument, NULL, 'R' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + cmd_func_t func = NULL; + uint16_t index = 0; + const char *str; + bool use_raw = false; + bool power_down = false; + int fd, i, exit_status; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "i:rRvh", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'i': + if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3)) + str = optarg + 3; + else + str = optarg; + if (!isdigit(*str)) { + usage(); + return EXIT_FAILURE; + } + index = atoi(str); + break; + case 'r': + reset_on_init = true; + break; + case 'R': + use_raw = true; + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind < 1) { + fprintf(stderr, "Missing command argument\n"); + return EXIT_FAILURE; + } + + for (i = 0; cmd_table[i].name; i++) { + if (!strcmp(cmd_table[i].name, argv[optind])) { + func = cmd_table[i].func; + break; + } + } + + if (!func) { + fprintf(stderr, "Unsupported command specified\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + printf("Bluetooth information utility ver %s\n", VERSION); + + fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (fd < 0) { + perror("Failed to open HCI raw socket"); + return EXIT_FAILURE; + } + + memset(&hci_info, 0, sizeof(hci_info)); + hci_info.dev_id = index; + + if (ioctl(fd, HCIGETDEVINFO, (void *) &hci_info) < 0) { + perror("Failed to get HCI device information"); + close(fd); + return EXIT_FAILURE; + } + + if (use_raw && !(hci_info.flags & HCI_UP)) { + printf("Powering on controller\n"); + + if (ioctl(fd, HCIDEVUP, hci_info.dev_id) < 0) { + perror("Failed to power on controller"); + close(fd); + return EXIT_FAILURE; + } + + power_down = true; + } + + close(fd); + + hci_type = (hci_info.type & 0x30) >> 4; + + if (use_raw) { + hci_dev = bt_hci_new_raw_device(index); + if (!hci_dev) { + fprintf(stderr, "Failed to open HCI raw device\n"); + return EXIT_FAILURE; + } + } else { + hci_dev = bt_hci_new_user_channel(index); + if (!hci_dev) { + fprintf(stderr, "Failed to open HCI user channel\n"); + return EXIT_FAILURE; + } + + reset_on_init = true; + reset_on_shutdown = true; + } + + if (!func(argc - optind - 1, argv + optind + 1)) { + bt_hci_unref(hci_dev); + return EXIT_FAILURE; + } + + exit_status = mainloop_run_with_signal(signal_callback, NULL); + + bt_hci_unref(hci_dev); + + if (use_raw && power_down) { + fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (fd >= 0) { + printf("Powering down controller\n"); + + if (ioctl(fd, HCIDEVDOWN, hci_info.dev_id) < 0) + perror("Failed to power down controller"); + + close(fd); + } + } + + return exit_status; +} diff --git a/tools/btiotest.c b/tools/btiotest.c new file mode 100644 index 0000000..6c778e3 --- /dev/null +++ b/tools/btiotest.c @@ -0,0 +1,663 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009-2010 Marcel Holtmann + * Copyright (C) 2009-2010 Nokia Corporation + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" + +#include "btio/btio.h" + +#define DEFAULT_ACCEPT_TIMEOUT 2 +static int opt_update_sec = 0; + +struct io_data { + guint ref; + GIOChannel *io; + int reject; + int disconn; + int accept; + int voice; +}; + +static void io_data_unref(struct io_data *data) +{ + data->ref--; + + if (data->ref) + return; + + if (data->io) + g_io_channel_unref(data->io); + + g_free(data); +} + +static struct io_data *io_data_ref(struct io_data *data) +{ + data->ref++; + return data; +} + +static struct io_data *io_data_new(GIOChannel *io, int reject, int disconn, + int accept) +{ + struct io_data *data; + + data = g_new0(struct io_data, 1); + data->io = io; + data->reject = reject; + data->disconn = disconn; + data->accept = accept; + + return io_data_ref(data); +} + +static gboolean io_watch(GIOChannel *io, GIOCondition cond, gpointer user_data) +{ + printf("Disconnected\n"); + return FALSE; +} + +static gboolean disconn_timeout(gpointer user_data) +{ + struct io_data *data = user_data; + + printf("Disconnecting\n"); + + g_io_channel_shutdown(data->io, TRUE, NULL); + + return FALSE; +} + +static void update_sec_level(struct io_data *data) +{ + GError *err = NULL; + int sec_level; + + if (!bt_io_get(data->io, &err, BT_IO_OPT_SEC_LEVEL, &sec_level, + BT_IO_OPT_INVALID)) { + printf("bt_io_get(OPT_SEC_LEVEL): %s\n", err->message); + g_clear_error(&err); + return; + } + + printf("sec_level=%d\n", sec_level); + + if (opt_update_sec == sec_level) + return; + + if (!bt_io_set(data->io, &err, BT_IO_OPT_SEC_LEVEL, opt_update_sec, + BT_IO_OPT_INVALID)) { + printf("bt_io_set(OPT_SEC_LEVEL): %s\n", err->message); + g_clear_error(&err); + } +} + +static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) +{ + struct io_data *data = user_data; + GIOCondition cond; + char addr[18]; + uint16_t handle, omtu, imtu; + uint8_t cls[3], key_size; + + if (err) { + printf("Connecting failed: %s\n", err->message); + return; + } + + if (!bt_io_get(io, &err, + BT_IO_OPT_DEST, addr, + BT_IO_OPT_HANDLE, &handle, + BT_IO_OPT_CLASS, cls, + BT_IO_OPT_INVALID)) { + printf("Unable to get destination address: %s\n", + err->message); + g_clear_error(&err); + strcpy(addr, "(unknown)"); + } + + printf("Successfully connected to %s. handle=%u, class=%02x%02x%02x\n", + addr, handle, cls[0], cls[1], cls[2]); + + if (!bt_io_get(io, &err, BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_INVALID)) { + printf("Unable to get MTU sizes: %s\n", err->message); + g_clear_error(&err); + } else + printf("imtu=%u, omtu=%u\n", imtu, omtu); + + if (!bt_io_get(io, &err, BT_IO_OPT_KEY_SIZE, &key_size, + BT_IO_OPT_INVALID)) { + printf("Unable to get Key size: %s\n", err->message); + g_clear_error(&err); + } else + printf("key_size=%u\n", key_size); + + if (data->disconn == 0) { + g_io_channel_shutdown(io, TRUE, NULL); + printf("Disconnected\n"); + return; + } + + if (data->io == NULL) + data->io = g_io_channel_ref(io); + + if (data->disconn > 0) { + io_data_ref(data); + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, data->disconn, + disconn_timeout, data, + (GDestroyNotify) io_data_unref); + } + + + io_data_ref(data); + + if (opt_update_sec > 0) + update_sec_level(data); + + cond = G_IO_NVAL | G_IO_HUP | G_IO_ERR; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, io_watch, data, + (GDestroyNotify) io_data_unref); +} + +static gboolean confirm_timeout(gpointer user_data) +{ + struct io_data *data = user_data; + + if (data->reject >= 0) { + printf("Rejecting connection\n"); + g_io_channel_shutdown(data->io, TRUE, NULL); + return FALSE; + } + + printf("Accepting connection\n"); + + io_data_ref(data); + + if (opt_update_sec > 0) + update_sec_level(data); + + if (!bt_io_accept(data->io, connect_cb, data, + (GDestroyNotify) io_data_unref, NULL)) { + printf("bt_io_accept() failed\n"); + io_data_unref(data); + } + + return FALSE; +} + +static void confirm_cb(GIOChannel *io, gpointer user_data) +{ + char addr[18]; + struct io_data *data = user_data; + GError *err = NULL; + + if (!bt_io_get(io, &err, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID)) { + printf("bt_io_get(OPT_DEST): %s\n", err->message); + g_clear_error(&err); + } else + printf("Got confirmation request for %s\n", addr); + + if (data->accept < 0 && data->reject < 0) + return; + + if (data->reject == 0) { + printf("Rejecting connection\n"); + g_io_channel_shutdown(io, TRUE, NULL); + return; + } + + if (data->voice) { + if (!bt_io_set(io, &err, BT_IO_OPT_VOICE, data->voice, + BT_IO_OPT_INVALID)) { + printf("bt_io_set(OPT_VOICE): %s\n", err->message); + g_clear_error(&err); + } + } + + data->io = g_io_channel_ref(io); + io_data_ref(data); + + if (data->accept == 0) { + if (!bt_io_accept(io, connect_cb, data, + (GDestroyNotify) io_data_unref, + &err)) { + printf("bt_io_accept() failed: %s\n", err->message); + g_clear_error(&err); + io_data_unref(data); + return; + } + } else { + int seconds = (data->reject > 0) ? + data->reject : data->accept; + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, seconds, + confirm_timeout, data, + (GDestroyNotify) io_data_unref); + } +} + +static void l2cap_connect(const char *src, const char *dst, uint8_t addr_type, + uint16_t psm, uint16_t cid, int disconn, + int sec, int prio) +{ + struct io_data *data; + GError *err = NULL; + uint8_t src_type; + + printf("Connecting to %s L2CAP PSM %u\n", dst, psm); + + data = io_data_new(NULL, -1, disconn, -1); + + if (addr_type != BDADDR_BREDR) + src_type = BDADDR_LE_PUBLIC; + else + src_type = BDADDR_BREDR; + + if (src) + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_SOURCE_TYPE, src_type, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_DEST_TYPE, addr_type, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_CID, cid, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_PRIORITY, prio, + BT_IO_OPT_INVALID); + else + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE_TYPE, src_type, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_DEST_TYPE, addr_type, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_CID, cid, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_PRIORITY, prio, + BT_IO_OPT_INVALID); + + if (!data->io) { + printf("Connecting to %s failed: %s\n", dst, err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } +} + +static void l2cap_listen(const char *src, uint8_t addr_type, uint16_t psm, + uint16_t cid, int defer, int reject, + int disconn, int accept, int sec, + gboolean master) +{ + struct io_data *data; + BtIOConnect conn; + BtIOConfirm cfm; + GIOChannel *l2_srv; + GError *err = NULL; + + if (defer) { + conn = NULL; + cfm = confirm_cb; + } else { + conn = connect_cb; + cfm = NULL; + } + + if (cid) + printf("Listening on L2CAP CID 0x%04x (%u)\n", cid, cid); + else + printf("Listening on L2CAP PSM 0x%04x (%u)\n", psm, psm); + + + data = io_data_new(NULL, reject, disconn, accept); + + if (src) + l2_srv = bt_io_listen(conn, cfm, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_SOURCE_TYPE, addr_type, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_CID, cid, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + else + l2_srv = bt_io_listen(conn, cfm, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE_TYPE, addr_type, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_CID, cid, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + + if (!l2_srv) { + printf("Listening failed: %s\n", err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } + + g_io_channel_unref(l2_srv); +} + +static void rfcomm_connect(const char *src, const char *dst, uint8_t ch, + int disconn, int sec) +{ + struct io_data *data; + GError *err = NULL; + + printf("Connecting to %s RFCOMM channel %u\n", dst, ch); + + data = io_data_new(NULL, -1, disconn, -1); + + if (src) + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_INVALID); + else + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_INVALID); + + if (!data->io) { + printf("Connecting to %s failed: %s\n", dst, err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } +} + +static void rfcomm_listen(const char *src, uint8_t ch, gboolean defer, + int reject, int disconn, int accept, + int sec, gboolean master) +{ + struct io_data *data; + BtIOConnect conn; + BtIOConfirm cfm; + GIOChannel *rc_srv; + GError *err = NULL; + + if (defer) { + conn = NULL; + cfm = confirm_cb; + } else { + conn = connect_cb; + cfm = NULL; + } + + data = io_data_new(NULL, reject, disconn, accept); + + if (src) + rc_srv = bt_io_listen(conn, cfm, + data, (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + else + rc_srv = bt_io_listen(conn, cfm, + data, (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + + if (!rc_srv) { + printf("Listening failed: %s\n", err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } + + bt_io_get(rc_srv, &err, BT_IO_OPT_CHANNEL, &ch, BT_IO_OPT_INVALID); + + printf("Listening on RFCOMM channel %u\n", ch); + + g_io_channel_unref(rc_srv); +} + +static void sco_connect(const char *src, const char *dst, int disconn, + int voice) +{ + struct io_data *data; + GError *err = NULL; + + printf("Connecting SCO to %s\n", dst); + + data = io_data_new(NULL, -1, disconn, -1); + + if (src) + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_VOICE, voice, + BT_IO_OPT_INVALID); + else + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_VOICE, voice, + BT_IO_OPT_INVALID); + + if (!data->io) { + printf("Connecting to %s failed: %s\n", dst, err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } +} + +static void sco_listen(const char *src, gboolean defer, int reject, + int disconn, int accept, int voice) +{ + struct io_data *data; + BtIOConnect conn; + BtIOConfirm cfm; + GIOChannel *sco_srv; + GError *err = NULL; + + printf("Listening for SCO connections\n"); + + if (defer) { + conn = NULL; + cfm = confirm_cb; + } else { + conn = connect_cb; + cfm = NULL; + } + + data = io_data_new(NULL, reject, disconn, accept); + + data->voice = voice; + + if (src) + sco_srv = bt_io_listen(conn, cfm, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_VOICE, voice, + BT_IO_OPT_INVALID); + else + sco_srv = bt_io_listen(conn, cfm, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_VOICE, voice, + BT_IO_OPT_INVALID); + + if (!sco_srv) { + printf("Listening failed: %s\n", err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } + + g_io_channel_unref(sco_srv); +} + +static int opt_channel = -1; +static int opt_psm = 0; +static gboolean opt_sco = FALSE; +static gboolean opt_defer = FALSE; +static gint opt_voice = 0; +static char *opt_dev = NULL; +static int opt_reject = -1; +static int opt_disconn = -1; +static int opt_accept = DEFAULT_ACCEPT_TIMEOUT; +static int opt_sec = 0; +static gboolean opt_master = FALSE; +static int opt_priority = 0; +static int opt_cid = 0; +static guint8 opt_addr_type = 0; + +static GMainLoop *main_loop; + +static GOptionEntry options[] = { + { "channel", 'c', 0, G_OPTION_ARG_INT, &opt_channel, + "RFCOMM channel" }, + { "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm, + "L2CAP PSM" }, + { "cid", 'j', 0, G_OPTION_ARG_INT, &opt_cid, + "L2CAP CID" }, + { "addr-type", 't', 0, G_OPTION_ARG_INT, &opt_addr_type, + "Address type " + "(0 BR/EDR 1 LE Public 2 LE Random" }, + { "sco", 's', 0, G_OPTION_ARG_NONE, &opt_sco, + "Use SCO" }, + { "defer", 'd', 0, G_OPTION_ARG_NONE, &opt_defer, + "Use DEFER_SETUP for incoming connections" }, + { "voice", 'V', 0, G_OPTION_ARG_INT, &opt_voice, + "Voice setting " + "(0x0060 CVSD, 0x0003 Transparent)" }, + { "sec-level", 'S', 0, G_OPTION_ARG_INT, &opt_sec, + "Security level" }, + { "update-sec-level", 'U', 0, G_OPTION_ARG_INT, &opt_update_sec, + "Update security level" }, + { "dev", 'i', 0, G_OPTION_ARG_STRING, &opt_dev, + "Which HCI device to use" }, + { "reject", 'r', 0, G_OPTION_ARG_INT, &opt_reject, + "Reject connection after N seconds" }, + { "disconnect", 'D', 0, G_OPTION_ARG_INT, &opt_disconn, + "Disconnect connection after N seconds" }, + { "accept", 'a', 0, G_OPTION_ARG_INT, &opt_accept, + "Accept connection after N seconds" }, + { "master", 'm', 0, G_OPTION_ARG_NONE, &opt_master, + "Master role switch (incoming connections)" }, + { "priority", 'P', 0, G_OPTION_ARG_INT, &opt_priority, + "Transmission priority: Setting a priority " + "outside the range 0 to 6 requires the" + "CAP_NET_ADMIN capability." }, + { NULL }, +}; + +static void sig_term(int sig) +{ + g_main_loop_quit(main_loop); +} + +int main(int argc, char *argv[]) +{ + GOptionContext *context; + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + if (!g_option_context_parse(context, &argc, &argv, NULL)) + exit(EXIT_FAILURE); + + g_option_context_free(context); + + printf("accept=%d reject=%d discon=%d defer=%d sec=%d update_sec=%d" + " prio=%d voice=0x%04x\n", opt_accept, opt_reject, opt_disconn, + opt_defer, opt_sec, opt_update_sec, opt_priority, opt_voice); + + if (opt_psm || opt_cid) { + if (argc > 1) + l2cap_connect(opt_dev, argv[1], opt_addr_type, + opt_psm, opt_cid, opt_disconn, + opt_sec, opt_priority); + else + l2cap_listen(opt_dev, opt_addr_type, opt_psm, opt_cid, + opt_defer, opt_reject, opt_disconn, + opt_accept, opt_sec, opt_master); + } + + if (opt_channel != -1) { + if (argc > 1) + rfcomm_connect(opt_dev, argv[1], opt_channel, + opt_disconn, opt_sec); + else + rfcomm_listen(opt_dev, opt_channel, opt_defer, + opt_reject, opt_disconn, opt_accept, + opt_sec, opt_master); + } + + if (opt_sco) { + if (argc > 1) + sco_connect(opt_dev, argv[1], opt_disconn, opt_voice); + else + sco_listen(opt_dev, opt_defer, opt_reject, + opt_disconn, opt_accept, opt_voice); + } + + signal(SIGTERM, sig_term); + signal(SIGINT, sig_term); + + main_loop = g_main_loop_new(NULL, FALSE); + + g_main_loop_run(main_loop); + + g_main_loop_unref(main_loop); + + printf("Exiting\n"); + + exit(EXIT_SUCCESS); +} diff --git a/tools/btmgmt.c b/tools/btmgmt.c new file mode 100644 index 0000000..f631de4 --- /dev/null +++ b/tools/btmgmt.c @@ -0,0 +1,4583 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" + +#include "src/uuid-helper.h" +#include "lib/mgmt.h" + +#include "src/shared/mainloop.h" +#include "src/shared/io.h" +#include "src/shared/util.h" +#include "src/shared/mgmt.h" +#include "src/shared/shell.h" + +#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) + +static struct mgmt *mgmt = NULL; +static uint16_t mgmt_index = MGMT_INDEX_NONE; + +static bool discovery = false; +static bool resolve_names = true; + +static struct { + uint16_t index; + uint16_t req; + struct mgmt_addr_info addr; +} prompt = { + .index = MGMT_INDEX_NONE, +}; + + +static int pending_index = 0; + +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#define PROMPT_ON COLOR_BLUE "[mgmt]" COLOR_OFF "# " + +static void set_index(const char *arg) +{ + if (!arg || !strcmp(arg, "none") || !strcmp(arg, "any") || + !strcmp(arg, "all")) + mgmt_index = MGMT_INDEX_NONE; + else if(strlen(arg) > 3 && !strncasecmp(arg, "hci", 3)) + mgmt_index = atoi(&arg[3]); + else + mgmt_index = atoi(arg); +} + +static void update_prompt(uint16_t index) +{ + char str[32]; + + if (index == MGMT_INDEX_NONE) + snprintf(str, sizeof(str), "%s# ", + COLOR_BLUE "[mgmt]" COLOR_OFF); + else + snprintf(str, sizeof(str), + COLOR_BLUE "[hci%u]" COLOR_OFF "# ", index); + + bt_shell_set_prompt(str); +} + +#define print(fmt, arg...) do { \ + bt_shell_printf(fmt "\n", ## arg); \ +} while (0) + +#define error(fmt, arg...) do { \ + bt_shell_printf(COLOR_RED fmt "\n" COLOR_OFF, ## arg); \ +} while (0) + +static size_t hex2bin(const char *hexstr, uint8_t *buf, size_t buflen) +{ + size_t i, len; + + len = MIN((strlen(hexstr) / 2), buflen); + memset(buf, 0, len); + + for (i = 0; i < len; i++) + sscanf(hexstr + (i * 2), "%02hhX", &buf[i]); + + return len; +} + +static size_t bin2hex(const uint8_t *buf, size_t buflen, char *str, + size_t strlen) +{ + size_t i; + + for (i = 0; i < buflen && i < (strlen / 2); i++) + sprintf(str + (i * 2), "%02x", buf[i]); + + return i; +} + +static void print_eir(const uint8_t *eir, uint16_t eir_len) +{ + uint16_t parsed = 0; + char str[33]; + + while (parsed < eir_len - 1) { + uint8_t field_len = eir[0]; + + if (field_len == 0) + break; + + parsed += field_len + 1; + + if (parsed > eir_len) + break; + + switch (eir[1]) { + case 0x01: + print("Flags: 0x%02x", eir[2]); + break; + case 0x0d: + print("Class of Device: 0x%02x%02x%02x", + eir[4], eir[3], eir[2]); + break; + case 0x0e: + bin2hex(eir + 2, 16, str, sizeof(str)); + print("SSP Hash C-192: %s", str); + break; + case 0x0f: + bin2hex(eir + 2, 16, str, sizeof(str)); + print("SSP Rand R-192: %s", str); + break; + case 0x1b: + ba2str((bdaddr_t *) (eir + 2), str); + print("LE Device Address: %s (%s)", str, + eir[8] ? "random" : "public"); + break; + case 0x1c: + print("LE Role: 0x%02x", eir[2]); + break; + case 0x1d: + bin2hex(eir + 2, 16, str, sizeof(str)); + print("SSP Hash C-256: %s", str); + break; + case 0x1e: + bin2hex(eir + 2, 16, str, sizeof(str)); + print("SSP Rand R-256: %s", str); + break; + case 0x22: + bin2hex(eir + 2, 16, str, sizeof(str)); + print("LE SC Confirmation Value: %s", str); + break; + case 0x23: + bin2hex(eir + 2, 16, str, sizeof(str)); + print("LE SC Random Value: %s", str); + break; + default: + print("Type %u: %u byte%s", eir[1], field_len - 1, + (field_len - 1) == 1 ? "" : "s"); + break; + } + + eir += field_len + 1; + } +} + +static bool load_identity(const char *path, struct mgmt_irk_info *irk) +{ + char *addr, *key; + unsigned int type; + int n; + FILE *fp; + + fp = fopen(path, "r"); + if (!fp) { + error("Failed to open identity file: %s", strerror(errno)); + return false; + } + + n = fscanf(fp, "%m[0-9a-f:] (type %u) %m[0-9a-f]", &addr, &type, &key); + + fclose(fp); + + if (n != 3) + return false; + + str2ba(addr, &irk->addr.bdaddr); + hex2bin(key, irk->val, sizeof(irk->val)); + + free(addr); + free(key); + + switch (type) { + case 0: + irk->addr.type = BDADDR_LE_PUBLIC; + break; + case 1: + irk->addr.type = BDADDR_LE_RANDOM; + break; + default: + error("Invalid address type %u", type); + return false; + } + + return true; +} + +static void controller_error(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_ev_controller_error *ev = param; + + if (len < sizeof(*ev)) { + error("Too short (%u bytes) controller error event", len); + return; + } + + print("hci%u error 0x%02x", index, ev->error_code); +} + +static void index_added(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + print("hci%u added", index); +} + +static void index_removed(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + print("hci%u removed", index); +} + +static void unconf_index_added(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + print("hci%u added (unconfigured)", index); +} + +static void unconf_index_removed(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + print("hci%u removed (unconfigured)", index); +} + +static void ext_index_added(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_ev_ext_index_added *ev = param; + + print("hci%u added (type %u bus %u)", index, ev->type, ev->bus); +} + +static void ext_index_removed(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_ev_ext_index_removed *ev = param; + + print("hci%u removed (type %u bus %u)", index, ev->type, ev->bus); +} + +static const char *options_str[] = { + "external", + "public-address", +}; + +static const char *options2str(uint32_t options) +{ + static char str[256]; + unsigned i; + int off; + + off = 0; + str[0] = '\0'; + + for (i = 0; i < NELEM(options_str); i++) { + if ((options & (1 << i)) != 0) + off += snprintf(str + off, sizeof(str) - off, "%s ", + options_str[i]); + } + + return str; +} + +static void new_config_options(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + const uint32_t *ev = param; + + if (len < sizeof(*ev)) { + error("Too short new_config_options event (%u)", len); + return; + } + + print("hci%u new_config_options: %s", index, options2str(get_le32(ev))); +} + +static const char *settings_str[] = { + "powered", + "connectable", + "fast-connectable", + "discoverable", + "bondable", + "link-security", + "ssp", + "br/edr", + "hs", + "le", + "advertising", + "secure-conn", + "debug-keys", + "privacy", + "configuration", + "static-addr", +}; + +static const char *settings2str(uint32_t settings) +{ + static char str[256]; + unsigned i; + int off; + + off = 0; + str[0] = '\0'; + + for (i = 0; i < NELEM(settings_str); i++) { + if ((settings & (1 << i)) != 0) + off += snprintf(str + off, sizeof(str) - off, "%s ", + settings_str[i]); + } + + return str; +} + +static void new_settings(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + const uint32_t *ev = param; + + if (len < sizeof(*ev)) { + error("Too short new_settings event (%u)", len); + return; + } + + print("hci%u new_settings: %s", index, settings2str(get_le32(ev))); +} + +static void discovering(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_discovering *ev = param; + + if (len < sizeof(*ev)) { + error("Too short (%u bytes) discovering event", len); + return; + } + + print("hci%u type %u discovering %s", index, ev->type, + ev->discovering ? "on" : "off"); + + if (ev->discovering == 0 && discovery) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void new_link_key(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_new_link_key *ev = param; + char addr[18]; + + if (len != sizeof(*ev)) { + error("Invalid new_link_key length (%u bytes)", len); + return; + } + + ba2str(&ev->key.addr.bdaddr, addr); + print("hci%u new_link_key %s type 0x%02x pin_len %d store_hint %u", + index, addr, ev->key.type, ev->key.pin_len, ev->store_hint); +} + +static const char *typestr(uint8_t type) +{ + static const char *str[] = { "BR/EDR", "LE Public", "LE Random" }; + + if (type <= BDADDR_LE_RANDOM) + return str[type]; + + return "(unknown)"; +} + +static void connected(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_device_connected *ev = param; + uint16_t eir_len; + char addr[18]; + + if (len < sizeof(*ev)) { + error("Invalid connected event length (%u bytes)", len); + return; + } + + eir_len = get_le16(&ev->eir_len); + if (len != sizeof(*ev) + eir_len) { + error("Invalid connected event length (%u != eir_len %u)", + len, eir_len); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + print("hci%u %s type %s connected eir_len %u", index, addr, + typestr(ev->addr.type), eir_len); +} + +static void disconnected(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_device_disconnected *ev = param; + char addr[18]; + uint8_t reason; + + if (len < sizeof(struct mgmt_addr_info)) { + error("Invalid disconnected event length (%u bytes)", len); + return; + } + + if (len < sizeof(*ev)) + reason = MGMT_DEV_DISCONN_UNKNOWN; + else + reason = ev->reason; + + ba2str(&ev->addr.bdaddr, addr); + print("hci%u %s type %s disconnected with reason %u", + index, addr, typestr(ev->addr.type), reason); +} + +static void conn_failed(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_connect_failed *ev = param; + char addr[18]; + + if (len != sizeof(*ev)) { + error("Invalid connect_failed event length (%u bytes)", len); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + print("hci%u %s type %s connect failed (status 0x%02x, %s)", + index, addr, typestr(ev->addr.type), ev->status, + mgmt_errstr(ev->status)); +} + +static void auth_failed(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_auth_failed *ev = param; + char addr[18]; + + if (len != sizeof(*ev)) { + error("Invalid auth_failed event length (%u bytes)", len); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + print("hci%u %s auth failed with status 0x%02x (%s)", + index, addr, ev->status, mgmt_errstr(ev->status)); +} + +static void class_of_dev_changed(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_ev_class_of_dev_changed *ev = param; + + if (len != sizeof(*ev)) { + error("Invalid class_of_dev_changed length (%u bytes)", len); + return; + } + + print("hci%u class of device changed: 0x%02x%02x%02x", index, + ev->dev_class[2], ev->dev_class[1], ev->dev_class[0]); +} + +static void local_name_changed(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_local_name_changed *ev = param; + + if (len != sizeof(*ev)) { + error("Invalid local_name_changed length (%u bytes)", len); + return; + } + + print("hci%u name changed: %s", index, ev->name); +} + +static void confirm_name_rsp(uint8_t status, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_rp_confirm_name *rp = param; + char addr[18]; + + if (len == 0 && status != 0) { + error("confirm_name failed with status 0x%02x (%s)", status, + mgmt_errstr(status)); + return; + } + + if (len != sizeof(*rp)) { + error("confirm_name rsp length %u instead of %zu", + len, sizeof(*rp)); + return; + } + + ba2str(&rp->addr.bdaddr, addr); + + if (status != 0) + error("confirm_name for %s failed: 0x%02x (%s)", + addr, status, mgmt_errstr(status)); + else + print("confirm_name succeeded for %s", addr); +} + +static char *eir_get_name(const uint8_t *eir, uint16_t eir_len) +{ + uint8_t parsed = 0; + + if (eir_len < 2) + return NULL; + + while (parsed < eir_len - 1) { + uint8_t field_len = eir[0]; + + if (field_len == 0) + break; + + parsed += field_len + 1; + + if (parsed > eir_len) + break; + + /* Check for short of complete name */ + if (eir[1] == 0x09 || eir[1] == 0x08) + return strndup((char *) &eir[2], field_len - 1); + + eir += field_len + 1; + } + + return NULL; +} + +static unsigned int eir_get_flags(const uint8_t *eir, uint16_t eir_len) +{ + uint8_t parsed = 0; + + if (eir_len < 2) + return 0; + + while (parsed < eir_len - 1) { + uint8_t field_len = eir[0]; + + if (field_len == 0) + break; + + parsed += field_len + 1; + + if (parsed > eir_len) + break; + + /* Check for flags */ + if (eir[1] == 0x01) + return eir[2]; + + eir += field_len + 1; + } + + return 0; +} + +static void device_found(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_device_found *ev = param; + struct mgmt *mgmt = user_data; + uint16_t eir_len; + uint32_t flags; + + if (len < sizeof(*ev)) { + error("Too short device_found length (%u bytes)", len); + return; + } + + flags = btohl(ev->flags); + + eir_len = get_le16(&ev->eir_len); + if (len != sizeof(*ev) + eir_len) { + error("dev_found: expected %zu bytes, got %u bytes", + sizeof(*ev) + eir_len, len); + return; + } + + if (discovery) { + char addr[18], *name; + + ba2str(&ev->addr.bdaddr, addr); + print("hci%u dev_found: %s type %s rssi %d " + "flags 0x%04x ", index, addr, + typestr(ev->addr.type), ev->rssi, flags); + + if (ev->addr.type != BDADDR_BREDR) + print("AD flags 0x%02x ", + eir_get_flags(ev->eir, eir_len)); + + name = eir_get_name(ev->eir, eir_len); + if (name) + print("name %s", name); + else + print("eir_len %u", eir_len); + + free(name); + } + + if (discovery && (flags & MGMT_DEV_FOUND_CONFIRM_NAME)) { + struct mgmt_cp_confirm_name cp; + + memset(&cp, 0, sizeof(cp)); + memcpy(&cp.addr, &ev->addr, sizeof(cp.addr)); + if (resolve_names) + cp.name_known = 0; + else + cp.name_known = 1; + + mgmt_reply(mgmt, MGMT_OP_CONFIRM_NAME, index, sizeof(cp), &cp, + confirm_name_rsp, NULL, NULL); + } +} + +static void pin_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) { + error("PIN Code reply failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("PIN Reply successful"); +} + +static int mgmt_pin_reply(uint16_t index, const struct mgmt_addr_info *addr, + const char *pin, size_t len) +{ + struct mgmt_cp_pin_code_reply cp; + + memset(&cp, 0, sizeof(cp)); + memcpy(&cp.addr, addr, sizeof(cp.addr)); + cp.pin_len = len; + memcpy(cp.pin_code, pin, len); + + return mgmt_reply(mgmt, MGMT_OP_PIN_CODE_REPLY, index, + sizeof(cp), &cp, pin_rsp, NULL, NULL); +} + +static void pin_neg_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) { + error("PIN Neg reply failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("PIN Negative Reply successful"); +} + +static int mgmt_pin_neg_reply(uint16_t index, const struct mgmt_addr_info *addr) +{ + struct mgmt_cp_pin_code_neg_reply cp; + + memset(&cp, 0, sizeof(cp)); + memcpy(&cp.addr, addr, sizeof(cp.addr)); + + return mgmt_reply(mgmt, MGMT_OP_PIN_CODE_NEG_REPLY, index, + sizeof(cp), &cp, pin_neg_rsp, NULL, NULL); +} + +static void confirm_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) { + error("User Confirm reply failed. status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("User Confirm Reply successful"); +} + +static int mgmt_confirm_reply(uint16_t index, const struct mgmt_addr_info *addr) +{ + struct mgmt_cp_user_confirm_reply cp; + + memset(&cp, 0, sizeof(cp)); + memcpy(&cp.addr, addr, sizeof(*addr)); + + return mgmt_reply(mgmt, MGMT_OP_USER_CONFIRM_REPLY, index, + sizeof(cp), &cp, confirm_rsp, NULL, NULL); +} + +static void confirm_neg_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) { + error("Confirm Neg reply failed. status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("User Confirm Negative Reply successful"); +} + +static int mgmt_confirm_neg_reply(uint16_t index, + const struct mgmt_addr_info *addr) +{ + struct mgmt_cp_user_confirm_reply cp; + + memset(&cp, 0, sizeof(cp)); + memcpy(&cp.addr, addr, sizeof(*addr)); + + return mgmt_reply(mgmt, MGMT_OP_USER_CONFIRM_NEG_REPLY, index, + sizeof(cp), &cp, confirm_neg_rsp, NULL, NULL); +} + +static void passkey_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) { + error("User Passkey reply failed. status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("User Passkey Reply successful"); +} + +static int mgmt_passkey_reply(uint16_t index, const struct mgmt_addr_info *addr, + uint32_t passkey) +{ + struct mgmt_cp_user_passkey_reply cp; + + memset(&cp, 0, sizeof(cp)); + memcpy(&cp.addr, addr, sizeof(*addr)); + put_le32(passkey, &cp.passkey); + + return mgmt_reply(mgmt, MGMT_OP_USER_PASSKEY_REPLY, index, + sizeof(cp), &cp, passkey_rsp, NULL, NULL); +} + +static void passkey_neg_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) { + error("Passkey Neg reply failed. status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("User Passkey Negative Reply successful"); +} + +static int mgmt_passkey_neg_reply(uint16_t index, + const struct mgmt_addr_info *addr) +{ + struct mgmt_cp_user_passkey_reply cp; + + memset(&cp, 0, sizeof(cp)); + memcpy(&cp.addr, addr, sizeof(*addr)); + + return mgmt_reply(mgmt, MGMT_OP_USER_PASSKEY_NEG_REPLY, index, + sizeof(cp), &cp, passkey_neg_rsp, NULL, NULL); +} + +static void prompt_input(const char *input, void *user_data) +{ + size_t len; + + len = strlen(input); + + switch (prompt.req) { + case MGMT_EV_PIN_CODE_REQUEST: + if (len) + mgmt_pin_reply(prompt.index, &prompt.addr, input, len); + else + mgmt_pin_neg_reply(prompt.index, &prompt.addr); + break; + case MGMT_EV_USER_PASSKEY_REQUEST: + if (strlen(input) > 0) + mgmt_passkey_reply(prompt.index, &prompt.addr, + atoi(input)); + else + mgmt_passkey_neg_reply(prompt.index, + &prompt.addr); + break; + case MGMT_EV_USER_CONFIRM_REQUEST: + if (input[0] == 'y' || input[0] == 'Y') + mgmt_confirm_reply(prompt.index, &prompt.addr); + else + mgmt_confirm_neg_reply(prompt.index, &prompt.addr); + break; + } +} + +static void ask(uint16_t index, uint16_t req, const struct mgmt_addr_info *addr, + const char *fmt, ...) +{ + char msg[256]; + va_list ap; + int off; + + prompt.index = index; + prompt.req = req; + memcpy(&prompt.addr, addr, sizeof(*addr)); + + va_start(ap, fmt); + off = vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + snprintf(msg + off, sizeof(msg) - off, " %s ", + COLOR_BOLDGRAY ">>" COLOR_OFF); + + bt_shell_prompt_input("", msg, prompt_input, NULL); +} + +static void request_pin(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_pin_code_request *ev = param; + char addr[18]; + + if (len != sizeof(*ev)) { + error("Invalid pin_code request length (%u bytes)", len); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + print("hci%u %s request PIN", index, addr); + + ask(index, MGMT_EV_PIN_CODE_REQUEST, &ev->addr, + "PIN Request (press enter to reject)"); +} + +static void user_confirm(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_user_confirm_request *ev = param; + uint32_t val; + char addr[18]; + + if (len != sizeof(*ev)) { + error("Invalid user_confirm request length (%u)", len); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + val = get_le32(&ev->value); + + print("hci%u %s User Confirm %06u hint %u", index, addr, + val, ev->confirm_hint); + + if (ev->confirm_hint) + ask(index, MGMT_EV_USER_CONFIRM_REQUEST, &ev->addr, + "Accept pairing with %s (yes/no)", addr); + else + ask(index, MGMT_EV_USER_CONFIRM_REQUEST, &ev->addr, + "Confirm value %06u for %s (yes/no)", val, addr); +} + +static void request_passkey(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_user_passkey_request *ev = param; + char addr[18]; + + if (len != sizeof(*ev)) { + error("Invalid passkey request length (%u bytes)", len); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + print("hci%u %s request passkey", index, addr); + + ask(index, MGMT_EV_USER_PASSKEY_REQUEST, &ev->addr, + "Passkey Request (press enter to reject)"); +} + +static void passkey_notify(uint16_t index, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_ev_passkey_notify *ev = param; + char addr[18]; + + if (len != sizeof(*ev)) { + error("Invalid passkey request length (%u bytes)", len); + return; + } + + ba2str(&ev->addr.bdaddr, addr); + print("hci%u %s request passkey", index, addr); + + print("Passkey Notify: %06u (entered %u)", get_le32(&ev->passkey), + ev->entered); +} + +static void local_oob_data_updated(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_ev_local_oob_data_updated *ev = param; + uint16_t eir_len; + + if (len < sizeof(*ev)) { + error("Too small (%u bytes) local_oob_updated event", len); + return; + } + + eir_len = le16_to_cpu(ev->eir_len); + if (len != sizeof(*ev) + eir_len) { + error("local_oob_updated: expected %zu bytes, got %u bytes", + sizeof(*ev) + eir_len, len); + return; + } + + print("hci%u oob data updated: type %u len %u", index, + ev->type, eir_len); +} + +static void advertising_added(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_ev_advertising_added *ev = param; + + if (len < sizeof(*ev)) { + error("Too small (%u bytes) advertising_added event", len); + return; + } + + print("hci%u advertising_added: instance %u", index, ev->instance); +} + +static void advertising_removed(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_ev_advertising_removed *ev = param; + + if (len < sizeof(*ev)) { + error("Too small (%u bytes) advertising_removed event", len); + return; + } + + print("hci%u advertising_removed: instance %u", index, ev->instance); +} + +static void version_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_version *rp = param; + + if (status != 0) { + error("Reading mgmt version failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + goto done; + } + + if (len < sizeof(*rp)) { + error("Too small version reply (%u bytes)", len); + goto done; + } + + print("MGMT Version %u, revision %u", rp->version, + get_le16(&rp->revision)); + +done: + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_version(int argc, char **argv) +{ + if (mgmt_send(mgmt, MGMT_OP_READ_VERSION, MGMT_INDEX_NONE, + 0, NULL, version_rsp, NULL, NULL) == 0) { + error("Unable to send read_version cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void commands_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_commands *rp = param; + uint16_t num_commands, num_events; + size_t expected_len; + int i; + + if (status != 0) { + error("Read Supported Commands failed: status 0x%02x (%s)", + status, mgmt_errstr(status)); + goto done; + } + + if (len < sizeof(*rp)) { + error("Too small commands reply (%u bytes)", len); + goto done; + } + + num_commands = get_le16(&rp->num_commands); + num_events = get_le16(&rp->num_events); + + expected_len = sizeof(*rp) + num_commands * sizeof(uint16_t) + + num_events * sizeof(uint16_t); + + if (len < expected_len) { + error("Too small commands reply (%u != %zu)", + len, expected_len); + goto done; + } + + print("%u commands:", num_commands); + for (i = 0; i < num_commands; i++) { + uint16_t op = get_le16(rp->opcodes + i); + print("\t%s (0x%04x)", mgmt_opstr(op), op); + } + + print("%u events:", num_events); + for (i = 0; i < num_events; i++) { + uint16_t ev = get_le16(rp->opcodes + num_commands + i); + print("\t%s (0x%04x)", mgmt_evstr(ev), ev); + } + +done: + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_commands(int argc, + char **argv) +{ + if (mgmt_send(mgmt, MGMT_OP_READ_COMMANDS, MGMT_INDEX_NONE, + 0, NULL, commands_rsp, NULL, NULL) == 0) { + error("Unable to send read_commands cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void config_info_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_config_info *rp = param; + uint16_t index = PTR_TO_UINT(user_data); + uint32_t supported_options, missing_options; + + if (status != 0) { + error("Reading hci%u config failed with status 0x%02x (%s)", + index, status, mgmt_errstr(status)); + goto done; + } + + if (len < sizeof(*rp)) { + error("Too small info reply (%u bytes)", len); + goto done; + } + + print("hci%u:\tUnconfigured controller", index); + + print("\tmanufacturer %u", le16_to_cpu(rp->manufacturer)); + + supported_options = le32_to_cpu(rp->supported_options); + print("\tsupported options: %s", options2str(supported_options)); + + missing_options = le32_to_cpu(rp->missing_options); + print("\tmissing options: %s", options2str(missing_options)); + +done: + pending_index--; + + if (pending_index > 0) + return; + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void unconf_index_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_unconf_index_list *rp = param; + uint16_t count; + unsigned int i; + + if (status != 0) { + error("Reading index list failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp)) { + error("Too small index list reply (%u bytes)", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + count = le16_to_cpu(rp->num_controllers); + + if (len < sizeof(*rp) + count * sizeof(uint16_t)) { + error("Index count (%u) doesn't match reply length (%u)", + count, len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("Unconfigured index list with %u item%s", + count, count != 1 ? "s" : ""); + + for (i = 0; i < count; i++) { + uint16_t index = le16_to_cpu(rp->index[i]); + + if (!mgmt_send(mgmt, MGMT_OP_READ_CONFIG_INFO, index, 0, NULL, + config_info_rsp, UINT_TO_PTR(index), NULL)) { + error("Unable to send read_config_info cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + pending_index++; + } + + if (!count) + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_config(int argc, char **argv) +{ + if (mgmt_index == MGMT_INDEX_NONE) { + if (!mgmt_send(mgmt, MGMT_OP_READ_UNCONF_INDEX_LIST, + MGMT_INDEX_NONE, 0, NULL, + unconf_index_rsp, mgmt, NULL)) { + error("Unable to send unconf_index_list cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + return; + } + + if (!mgmt_send(mgmt, MGMT_OP_READ_CONFIG_INFO, mgmt_index, 0, NULL, + config_info_rsp, UINT_TO_PTR(index), NULL)) { + error("Unable to send read_config_info cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void config_options_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_config_info *rp = param; + uint16_t index = PTR_TO_UINT(user_data); + uint32_t supported_options, missing_options; + + if (status != 0) { + error("Reading hci%u config failed with status 0x%02x (%s)", + index, status, mgmt_errstr(status)); + goto done; + } + + if (len < sizeof(*rp)) { + error("Too small info reply (%u bytes)", len); + goto done; + } + + print("hci%u:\tConfiguration options", index); + + supported_options = le32_to_cpu(rp->supported_options); + print("\tsupported options: %s", options2str(supported_options)); + + missing_options = le32_to_cpu(rp->missing_options); + print("\tmissing options: %s", options2str(missing_options)); + +done: + pending_index--; + + if (pending_index > 0) + return; + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void info_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_info *rp = param; + uint16_t index = PTR_TO_UINT(user_data); + uint32_t supported_settings, current_settings; + char addr[18]; + + if (status != 0) { + error("Reading hci%u info failed with status 0x%02x (%s)", + index, status, mgmt_errstr(status)); + goto done; + } + + if (len < sizeof(*rp)) { + error("Too small info reply (%u bytes)", len); + goto done; + } + + print("hci%u:\tPrimary controller", index); + + ba2str(&rp->bdaddr, addr); + print("\taddr %s version %u manufacturer %u class 0x%02x%02x%02x", + addr, rp->version, le16_to_cpu(rp->manufacturer), + rp->dev_class[2], rp->dev_class[1], rp->dev_class[0]); + + supported_settings = le32_to_cpu(rp->supported_settings); + print("\tsupported settings: %s", settings2str(supported_settings)); + + current_settings = le32_to_cpu(rp->current_settings); + print("\tcurrent settings: %s", settings2str(current_settings)); + + print("\tname %s", rp->name); + print("\tshort name %s", rp->short_name); + + if (supported_settings & MGMT_SETTING_CONFIGURATION) { + if (!mgmt_send(mgmt, MGMT_OP_READ_CONFIG_INFO, + index, 0, NULL, config_options_rsp, + UINT_TO_PTR(index), NULL)) { + error("Unable to send read_config cmd"); + goto done; + } + return; + } + +done: + pending_index--; + + if (pending_index > 0) + return; + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void ext_info_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_ext_info *rp = param; + uint16_t index = PTR_TO_UINT(user_data); + uint32_t supported_settings, current_settings; + char addr[18]; + + if (status != 0) { + error("Reading hci%u info failed with status 0x%02x (%s)", + index, status, mgmt_errstr(status)); + goto done; + } + + if (len < sizeof(*rp)) { + error("Too small info reply (%u bytes)", len); + goto done; + } + + print("hci%u:\tPrimary controller", index); + + ba2str(&rp->bdaddr, addr); + print("\taddr %s version %u manufacturer %u", + addr, rp->version, le16_to_cpu(rp->manufacturer)); + + supported_settings = le32_to_cpu(rp->supported_settings); + print("\tsupported settings: %s", settings2str(supported_settings)); + + current_settings = le32_to_cpu(rp->current_settings); + print("\tcurrent settings: %s", settings2str(current_settings)); + + if (supported_settings & MGMT_SETTING_CONFIGURATION) { + if (!mgmt_send(mgmt, MGMT_OP_READ_CONFIG_INFO, + index, 0, NULL, config_options_rsp, + UINT_TO_PTR(index), NULL)) { + error("Unable to send read_config cmd"); + goto done; + } + return; + } + +done: + pending_index--; + + if (pending_index > 0) + return; + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void index_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_index_list *rp = param; + struct mgmt *mgmt = user_data; + uint16_t count; + unsigned int i; + + if (status != 0) { + error("Reading index list failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp)) { + error("Too small index list reply (%u bytes)", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + count = le16_to_cpu(rp->num_controllers); + + if (len < sizeof(*rp) + count * sizeof(uint16_t)) { + error("Index count (%u) doesn't match reply length (%u)", + count, len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("Index list with %u item%s", count, count != 1 ? "s" : ""); + + for (i = 0; i < count; i++) { + uint16_t index = le16_to_cpu(rp->index[i]); + + if (!mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, + info_rsp, UINT_TO_PTR(index), NULL)) { + error("Unable to send read_info cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + pending_index++; + } + + if (!count) + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_info(int argc, char **argv) +{ + if (mgmt_index == MGMT_INDEX_NONE) { + if (!mgmt_send(mgmt, MGMT_OP_READ_INDEX_LIST, + MGMT_INDEX_NONE, 0, NULL, + index_rsp, mgmt, NULL)) { + error("Unable to send index_list cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + return; + } + + if (!mgmt_send(mgmt, MGMT_OP_READ_INFO, mgmt_index, 0, NULL, info_rsp, + UINT_TO_PTR(mgmt_index), NULL)) { + error("Unable to send read_info cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void ext_index_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_ext_index_list *rp = param; + uint16_t count, index_filter = PTR_TO_UINT(user_data); + unsigned int i; + + if (status != 0) { + error("Reading ext index list failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp)) { + error("Too small ext index list reply (%u bytes)", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + count = get_le16(&rp->num_controllers); + + if (len < sizeof(*rp) + count * (sizeof(uint16_t) + sizeof(uint8_t))) { + error("Index count (%u) doesn't match reply length (%u)", + count, len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("Extended index list with %u item%s", + count, count != 1 ? "s" : ""); + + for (i = 0; i < count; i++) { + uint16_t index = le16_to_cpu(rp->entry[i].index); + char *busstr = hci_bustostr(rp->entry[i].bus); + + if (index_filter != MGMT_INDEX_NONE && index_filter != index) + continue; + + switch (rp->entry[i].type) { + case 0x00: + print("Primary controller (hci%u,%s)", index, busstr); + if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INFO, + index, 0, NULL, ext_info_rsp, + UINT_TO_PTR(index), NULL)) { + error("Unable to send read_ext_info cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + pending_index++; + break; + case 0x01: + print("Unconfigured controller (hci%u,%s)", + index, busstr); + if (!mgmt_send(mgmt, MGMT_OP_READ_CONFIG_INFO, + index, 0, NULL, config_info_rsp, + UINT_TO_PTR(index), NULL)) { + error("Unable to send read_config cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + pending_index++; + break; + case 0x02: + print("AMP controller (hci%u,%s)", index, busstr); + break; + default: + print("Type %u controller (hci%u,%s)", + rp->entry[i].type, index, busstr); + break; + } + } + + print(""); + + if (!count) + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_extinfo( + int argc, char **argv) +{ + if (!mgmt_send(mgmt, MGMT_OP_READ_EXT_INDEX_LIST, + MGMT_INDEX_NONE, 0, NULL, + ext_index_rsp, UINT_TO_PTR(index), NULL)) { + error("Unable to send ext_index_list cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void auto_power_enable_rsp(uint8_t status, uint16_t len, + const void *param, void *user_data) +{ + uint16_t index = PTR_TO_UINT(user_data); + + print("Successfully enabled controller with index %u", index); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void auto_power_info_rsp(uint8_t status, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_info *rp = param; + uint16_t index = PTR_TO_UINT(user_data); + uint32_t supported_settings, current_settings, missing_settings; + uint8_t val = 0x01; + + if (status) { + error("Reading info failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + supported_settings = le32_to_cpu(rp->supported_settings); + current_settings = le32_to_cpu(rp->current_settings); + missing_settings = current_settings ^ supported_settings; + + if (missing_settings & MGMT_SETTING_BREDR) + mgmt_send(mgmt, MGMT_OP_SET_BREDR, index, sizeof(val), &val, + NULL, NULL, NULL); + + if (missing_settings & MGMT_SETTING_SSP) + mgmt_send(mgmt, MGMT_OP_SET_SSP, index, sizeof(val), &val, + NULL, NULL, NULL); + + if (missing_settings & MGMT_SETTING_LE) + mgmt_send(mgmt, MGMT_OP_SET_LE, index, sizeof(val), &val, + NULL, NULL, NULL); + + if (missing_settings & MGMT_SETTING_SECURE_CONN) + mgmt_send(mgmt, MGMT_OP_SET_SECURE_CONN, index, + sizeof(val), &val, + NULL, NULL, NULL); + + if (missing_settings & MGMT_SETTING_BONDABLE) + mgmt_send(mgmt, MGMT_OP_SET_BONDABLE, index, sizeof(val), &val, + NULL, NULL, NULL); + + if (current_settings & MGMT_SETTING_POWERED) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + if (!mgmt_send(mgmt, MGMT_OP_SET_POWERED, index, sizeof(val), &val, + auto_power_enable_rsp, + UINT_TO_PTR(index), NULL)) { + error("Unable to send set powerd cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void auto_power_index_evt(uint16_t index, uint16_t len, + const void *param, void *user_data) +{ + uint16_t index_filter = PTR_TO_UINT(user_data); + + if (index != index_filter) + return; + + print("New controller with index %u", index); + + if (!mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, + auto_power_info_rsp, + UINT_TO_PTR(index), NULL)) { + error("Unable to send read info cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void auto_power_index_rsp(uint8_t status, uint16_t len, + const void *param, void *user_data) +{ + const struct mgmt_rp_read_index_list *rp = param; + uint16_t index = PTR_TO_UINT(user_data); + uint16_t i, count; + bool found = false; + + if (status) { + error("Reading index list failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + count = le16_to_cpu(rp->num_controllers); + for (i = 0; i < count; i++) { + if (le16_to_cpu(rp->index[i]) == index) + found = true; + } + + if (!found) { + print("Waiting for index %u to appear", index); + + mgmt_register(mgmt, MGMT_EV_INDEX_ADDED, index, + auto_power_index_evt, + UINT_TO_PTR(index), NULL); + return; + } + + print("Found controller with index %u", index); + + if (!mgmt_send(mgmt, MGMT_OP_READ_INFO, index, 0, NULL, + auto_power_info_rsp, + UINT_TO_PTR(index), NULL)) { + error("Unable to send read info cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_auto_power(int argc, char **argv) +{ + int index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (!mgmt_send(mgmt, MGMT_OP_READ_INDEX_LIST, MGMT_INDEX_NONE, 0, NULL, + auto_power_index_rsp, + UINT_TO_PTR(index), NULL)) { + error("Unable to send read index list cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +/* Wrapper to get the index and opcode to the response callback */ +struct command_data { + uint16_t id; + uint16_t op; + void (*callback) (uint16_t id, uint16_t op, uint8_t status, + uint16_t len, const void *param); +}; + +static void cmd_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + struct command_data *data = user_data; + + data->callback(data->op, data->id, status, len, param); +} + +static unsigned int send_cmd(struct mgmt *mgmt, uint16_t op, uint16_t id, + uint16_t len, const void *param, + void (*cb)(uint16_t id, uint16_t op, + uint8_t status, uint16_t len, + const void *param)) +{ + struct command_data *data; + unsigned int send_id; + + data = new0(struct command_data, 1); + if (!data) + return 0; + + data->id = id; + data->op = op; + data->callback = cb; + + send_id = mgmt_send(mgmt, op, id, len, param, cmd_rsp, data, free); + if (send_id == 0) + free(data); + + return send_id; +} + +static void setting_rsp(uint16_t op, uint16_t id, uint8_t status, uint16_t len, + const void *param) +{ + const uint32_t *rp = param; + + if (status != 0) { + error("%s for hci%u failed with status 0x%02x (%s)", + mgmt_opstr(op), id, status, mgmt_errstr(status)); + goto done; + } + + if (len < sizeof(*rp)) { + error("Too small %s response (%u bytes)", + mgmt_opstr(op), len); + goto done; + } + + print("hci%u %s complete, settings: %s", id, mgmt_opstr(op), + settings2str(get_le32(rp))); + +done: + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static bool parse_setting(int argc, char **argv, uint8_t *val) +{ + if (strcasecmp(argv[1], "on") == 0 || strcasecmp(argv[1], "yes") == 0) + *val = 1; + else if (strcasecmp(argv[1], "off") == 0) + *val = 0; + else + *val = atoi(argv[1]); + return true; +} + +static void cmd_setting(uint16_t op, int argc, char **argv) +{ + int index; + uint8_t val; + + if (parse_setting(argc, argv, &val) == false) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (send_cmd(mgmt, op, index, sizeof(val), &val, setting_rsp) == 0) { + error("Unable to send %s cmd", mgmt_opstr(op)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_power(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_POWERED, argc, argv); +} + +static void cmd_discov(int argc, char **argv) +{ + struct mgmt_cp_set_discoverable cp; + uint16_t index; + + memset(&cp, 0, sizeof(cp)); + + if (strcasecmp(argv[1], "on") == 0 || strcasecmp(argv[1], "yes") == 0) + cp.val = 1; + else if (strcasecmp(argv[1], "off") == 0) + cp.val = 0; + else if (strcasecmp(argv[1], "limited") == 0) + cp.val = 2; + else + cp.val = atoi(argv[1]); + + if (argc > 2) + cp.timeout = htobs(atoi(argv[2])); + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (send_cmd(mgmt, MGMT_OP_SET_DISCOVERABLE, index, sizeof(cp), &cp, + setting_rsp) == 0) { + error("Unable to send set_discoverable cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_connectable(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_CONNECTABLE, argc, argv); +} + +static void cmd_fast_conn(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_FAST_CONNECTABLE, argc, argv); +} + +static void cmd_bondable(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_BONDABLE, argc, argv); +} + +static void cmd_linksec(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_LINK_SECURITY, argc, argv); +} + +static void cmd_ssp(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_SSP, argc, argv); +} + +static void cmd_sc(int argc, char **argv) +{ + uint8_t val; + uint16_t index; + + if (strcasecmp(argv[1], "on") == 0 || strcasecmp(argv[1], "yes") == 0) + val = 1; + else if (strcasecmp(argv[1], "off") == 0) + val = 0; + else if (strcasecmp(argv[1], "only") == 0) + val = 2; + else + val = atoi(argv[1]); + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (send_cmd(mgmt, MGMT_OP_SET_SECURE_CONN, index, + sizeof(val), &val, setting_rsp) == 0) { + error("Unable to send set_secure_conn cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_hs(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_HS, argc, argv); +} + +static void cmd_le(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_LE, argc, argv); +} + +static void cmd_advertising(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_ADVERTISING, argc, argv); +} + +static void cmd_bredr(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_BREDR, argc, argv); +} + +static void cmd_privacy(int argc, char **argv) +{ + struct mgmt_cp_set_privacy cp; + uint16_t index; + + if (parse_setting(argc, argv, &cp.privacy) == false) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (argc > 2) { + if (hex2bin(argv[2], cp.irk, + sizeof(cp.irk)) != sizeof(cp.irk)) { + error("Invalid key format"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } else { + int fd; + + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + error("open(/dev/urandom): %s", strerror(errno)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (read(fd, cp.irk, sizeof(cp.irk)) != sizeof(cp.irk)) { + error("Reading from urandom failed"); + close(fd); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + close(fd); + } + + if (send_cmd(mgmt, MGMT_OP_SET_PRIVACY, index, sizeof(cp), &cp, + setting_rsp) == 0) { + error("Unable to send Set Privacy command"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void class_rsp(uint16_t op, uint16_t id, uint8_t status, uint16_t len, + const void *param) +{ + const struct mgmt_ev_class_of_dev_changed *rp = param; + + if (len == 0 && status != 0) { + error("%s failed, status 0x%02x (%s)", + mgmt_opstr(op), status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len != sizeof(*rp)) { + error("Unexpected %s len %u", mgmt_opstr(op), len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("%s succeeded. Class 0x%02x%02x%02x", mgmt_opstr(op), + rp->dev_class[2], rp->dev_class[1], rp->dev_class[0]); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_class(int argc, char **argv) +{ + uint8_t class[2]; + uint16_t index; + + class[0] = atoi(argv[1]); + class[1] = atoi(argv[2]); + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (send_cmd(mgmt, MGMT_OP_SET_DEV_CLASS, index, sizeof(class), class, + class_rsp) == 0) { + error("Unable to send set_dev_class cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void disconnect_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_disconnect *rp = param; + char addr[18]; + + if (len == 0 && status != 0) { + error("Disconnect failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len != sizeof(*rp)) { + error("Invalid disconnect response length (%u)", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ba2str(&rp->addr.bdaddr, addr); + + if (status == 0) + print("%s disconnected", addr); + else + error("Disconnecting %s failed with status 0x%02x (%s)", + addr, status, mgmt_errstr(status)); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct option disconnect_options[] = { + { "help", 0, 0, 'h' }, + { "type", 1, 0, 't' }, + { 0, 0, 0, 0 } +}; + +static void cmd_disconnect(int argc, char **argv) +{ + struct mgmt_cp_disconnect cp; + uint8_t type = BDADDR_BREDR; + int opt; + uint16_t index; + + while ((opt = getopt_long(argc, argv, "+t:h", disconnect_options, + NULL)) != -1) { + switch (opt) { + case 't': + type = strtol(optarg, NULL, 0); + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + str2ba(argv[0], &cp.addr.bdaddr); + cp.addr.type = type; + + if (mgmt_send(mgmt, MGMT_OP_DISCONNECT, index, sizeof(cp), &cp, + disconnect_rsp, NULL, NULL) == 0) { + error("Unable to send disconnect cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void con_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_get_connections *rp = param; + uint16_t count, i; + + if (len < sizeof(*rp)) { + error("Too small (%u bytes) get_connections rsp", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + count = get_le16(&rp->conn_count); + if (len != sizeof(*rp) + count * sizeof(struct mgmt_addr_info)) { + error("Invalid get_connections length (count=%u, len=%u)", + count, len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + for (i = 0; i < count; i++) { + char addr[18]; + + ba2str(&rp->addr[i].bdaddr, addr); + + print("%s type %s", addr, typestr(rp->addr[i].type)); + } + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_con(int argc, char **argv) +{ + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (mgmt_send(mgmt, MGMT_OP_GET_CONNECTIONS, index, 0, NULL, + con_rsp, NULL, NULL) == 0) { + error("Unable to send get_connections cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void find_service_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) { + error("Start Service Discovery failed: status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("Service discovery started"); + discovery = true; +} + +static struct option find_service_options[] = { + { "help", no_argument, 0, 'h' }, + { "le-only", no_argument, 0, 'l' }, + { "bredr-only", no_argument, 0, 'b' }, + { "uuid", required_argument, 0, 'u' }, + { "rssi", required_argument, 0, 'r' }, + { 0, 0, 0, 0 } +}; + +static void uuid_to_uuid128(uuid_t *uuid128, const uuid_t *uuid) +{ + if (uuid->type == SDP_UUID16) + sdp_uuid16_to_uuid128(uuid128, uuid); + else if (uuid->type == SDP_UUID32) + sdp_uuid32_to_uuid128(uuid128, uuid); + else + memcpy(uuid128, uuid, sizeof(*uuid)); +} + +#define MAX_UUIDS 4 + +static void cmd_find_service(int argc, char **argv) +{ + struct mgmt_cp_start_service_discovery *cp; + uint8_t buf[sizeof(*cp) + 16 * MAX_UUIDS]; + uuid_t uuid; + uint128_t uint128; + uuid_t uuid128; + uint8_t type = SCAN_TYPE_DUAL; + int8_t rssi; + uint16_t count; + int opt; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + rssi = 127; + count = 0; + + while ((opt = getopt_long(argc, argv, "+lbu:r:h", + find_service_options, NULL)) != -1) { + switch (opt) { + case 'l': + type &= ~SCAN_TYPE_BREDR; + type |= SCAN_TYPE_LE; + break; + case 'b': + type |= SCAN_TYPE_BREDR; + type &= ~SCAN_TYPE_LE; + break; + case 'u': + if (count == MAX_UUIDS) { + print("Max %u UUIDs supported", MAX_UUIDS); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (bt_string2uuid(&uuid, optarg) < 0) { + print("Invalid UUID: %s", optarg); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + cp = (void *) buf; + uuid_to_uuid128(&uuid128, &uuid); + ntoh128((uint128_t *) uuid128.value.uuid128.data, + &uint128); + htob128(&uint128, (uint128_t *) cp->uuids[count++]); + break; + case 'r': + rssi = atoi(optarg); + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + cp = (void *) buf; + cp->type = type; + cp->rssi = rssi; + cp->uuid_count = cpu_to_le16(count); + + if (mgmt_send(mgmt, MGMT_OP_START_SERVICE_DISCOVERY, index, + sizeof(*cp) + count * 16, cp, + find_service_rsp, NULL, NULL) == 0) { + error("Unable to send start_service_discovery cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void find_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) { + error("Unable to start discovery. status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("Discovery started"); + discovery = true; +} + +static struct option find_options[] = { + { "help", 0, 0, 'h' }, + { "le-only", 1, 0, 'l' }, + { "bredr-only", 1, 0, 'b' }, + { "limited", 1, 0, 'L' }, + { 0, 0, 0, 0 } +}; + +static void cmd_find(int argc, char **argv) +{ + struct mgmt_cp_start_discovery cp; + uint8_t op = MGMT_OP_START_DISCOVERY; + uint8_t type = SCAN_TYPE_DUAL; + int opt; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + while ((opt = getopt_long(argc, argv, "+lbLh", find_options, + NULL)) != -1) { + switch (opt) { + case 'l': + type &= ~SCAN_TYPE_BREDR; + type |= SCAN_TYPE_LE; + break; + case 'b': + type |= SCAN_TYPE_BREDR; + type &= ~SCAN_TYPE_LE; + break; + case 'L': + op = MGMT_OP_START_LIMITED_DISCOVERY; + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + memset(&cp, 0, sizeof(cp)); + cp.type = type; + + if (mgmt_send(mgmt, op, index, sizeof(cp), &cp, find_rsp, + NULL, NULL) == 0) { + error("Unable to send start_discovery cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void stop_find_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) { + error("Stop Discovery failed: status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + + print("Discovery stopped"); + discovery = false; + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct option stop_find_options[] = { + { "help", 0, 0, 'h' }, + { "le-only", 1, 0, 'l' }, + { "bredr-only", 1, 0, 'b' }, + { 0, 0, 0, 0 } +}; + +static void cmd_stop_find(int argc, char **argv) +{ + struct mgmt_cp_stop_discovery cp; + uint8_t type = SCAN_TYPE_DUAL; + int opt; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + while ((opt = getopt_long(argc, argv, "+lbh", stop_find_options, + NULL)) != -1) { + switch (opt) { + case 'l': + type &= ~SCAN_TYPE_BREDR; + type |= SCAN_TYPE_LE; + break; + case 'b': + type |= SCAN_TYPE_BREDR; + type &= ~SCAN_TYPE_LE; + break; + case 'h': + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + memset(&cp, 0, sizeof(cp)); + cp.type = type; + + if (mgmt_send(mgmt, MGMT_OP_STOP_DISCOVERY, index, sizeof(cp), &cp, + stop_find_rsp, NULL, NULL) == 0) { + error("Unable to send stop_discovery cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void name_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Unable to set local name with status 0x%02x (%s)", + status, mgmt_errstr(status)); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_name(int argc, char **argv) +{ + struct mgmt_cp_set_local_name cp; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + strncpy((char *) cp.name, argv[1], HCI_MAX_NAME_LENGTH); + if (argc > 2) + strncpy((char *) cp.short_name, argv[2], + MGMT_MAX_SHORT_NAME_LENGTH - 1); + + if (mgmt_send(mgmt, MGMT_OP_SET_LOCAL_NAME, index, sizeof(cp), &cp, + name_rsp, NULL, NULL) == 0) { + error("Unable to send set_name cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void pair_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_pair_device *rp = param; + char addr[18]; + + if (len == 0 && status != 0) { + error("Pairing failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len != sizeof(*rp)) { + error("Unexpected pair_rsp len %u", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ba2str(&rp->addr.bdaddr, addr); + + if (status) + error("Pairing with %s (%s) failed. status 0x%02x (%s)", + addr, typestr(rp->addr.type), status, + mgmt_errstr(status)); + else + print("Paired with %s (%s)", addr, typestr(rp->addr.type)); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct option pair_options[] = { + { "help", 0, 0, 'h' }, + { "capability", 1, 0, 'c' }, + { "type", 1, 0, 't' }, + { 0, 0, 0, 0 } +}; + +static void cmd_pair(int argc, char **argv) +{ + struct mgmt_cp_pair_device cp; + uint8_t cap = 0x01; + uint8_t type = BDADDR_BREDR; + char addr[18]; + int opt; + uint16_t index; + + while ((opt = getopt_long(argc, argv, "+c:t:h", pair_options, + NULL)) != -1) { + switch (opt) { + case 'c': + cap = strtol(optarg, NULL, 0); + break; + case 't': + type = strtol(optarg, NULL, 0); + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + bt_shell_usage(); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + str2ba(argv[0], &cp.addr.bdaddr); + cp.addr.type = type; + cp.io_cap = cap; + + ba2str(&cp.addr.bdaddr, addr); + print("Pairing with %s (%s)", addr, typestr(cp.addr.type)); + + if (mgmt_send(mgmt, MGMT_OP_PAIR_DEVICE, index, sizeof(cp), &cp, + pair_rsp, NULL, NULL) == 0) { + error("Unable to send pair_device cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cancel_pair_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_addr_info *rp = param; + char addr[18]; + + if (len == 0 && status != 0) { + error("Cancel Pairing failed with 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len != sizeof(*rp)) { + error("Unexpected cancel_pair_rsp len %u", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ba2str(&rp->bdaddr, addr); + + if (status) + error("Cancel Pairing with %s (%s) failed. 0x%02x (%s)", + addr, typestr(rp->type), status, + mgmt_errstr(status)); + else + print("Pairing Cancelled with %s", addr); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct option cancel_pair_options[] = { + { "help", 0, 0, 'h' }, + { "type", 1, 0, 't' }, + { 0, 0, 0, 0 } +}; + +static void cmd_cancel_pair(int argc, char **argv) +{ + struct mgmt_addr_info cp; + uint8_t type = BDADDR_BREDR; + int opt; + uint16_t index; + + while ((opt = getopt_long(argc, argv, "+t:h", cancel_pair_options, + NULL)) != -1) { + switch (opt) { + case 't': + type = strtol(optarg, NULL, 0); + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + bt_shell_usage(); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + str2ba(argv[0], &cp.bdaddr); + cp.type = type; + + if (mgmt_reply(mgmt, MGMT_OP_CANCEL_PAIR_DEVICE, index, sizeof(cp), &cp, + cancel_pair_rsp, NULL, NULL) == 0) { + error("Unable to send cancel_pair_device cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void unpair_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_unpair_device *rp = param; + char addr[18]; + + if (len == 0 && status != 0) { + error("Unpair device failed. status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len != sizeof(*rp)) { + error("Unexpected unpair_device_rsp len %u", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ba2str(&rp->addr.bdaddr, addr); + + if (status) + error("Unpairing %s failed. status 0x%02x (%s)", + addr, status, mgmt_errstr(status)); + else + print("%s unpaired", addr); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct option unpair_options[] = { + { "help", 0, 0, 'h' }, + { "type", 1, 0, 't' }, + { 0, 0, 0, 0 } +}; + +static void cmd_unpair(int argc, char **argv) +{ + struct mgmt_cp_unpair_device cp; + uint8_t type = BDADDR_BREDR; + int opt; + uint16_t index = mgmt_index; + + while ((opt = getopt_long(argc, argv, "+t:h", unpair_options, + NULL)) != -1) { + switch (opt) { + case 't': + type = strtol(optarg, NULL, 0); + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + bt_shell_usage(); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + str2ba(argv[0], &cp.addr.bdaddr); + cp.addr.type = type; + cp.disconnect = 1; + + if (mgmt_send(mgmt, MGMT_OP_UNPAIR_DEVICE, index, sizeof(cp), &cp, + unpair_rsp, NULL, NULL) == 0) { + error("Unable to send unpair_device cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void keys_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Load keys failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + else + print("Keys successfully loaded"); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_keys(int argc, char **argv) +{ + struct mgmt_cp_load_link_keys cp; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + + if (mgmt_send(mgmt, MGMT_OP_LOAD_LINK_KEYS, index, sizeof(cp), &cp, + keys_rsp, NULL, NULL) == 0) { + error("Unable to send load_keys cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void ltks_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Load keys failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + else + print("Long term keys successfully loaded"); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_ltks(int argc, char **argv) +{ + struct mgmt_cp_load_long_term_keys cp; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + + if (mgmt_send(mgmt, MGMT_OP_LOAD_LONG_TERM_KEYS, index, sizeof(cp), &cp, + ltks_rsp, NULL, NULL) == 0) { + error("Unable to send load_ltks cmd"); + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } +} + +static void irks_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Load IRKs failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + else + print("Identity Resolving Keys successfully loaded"); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct option irks_options[] = { + { "help", 0, 0, 'h' }, + { "local", 1, 0, 'l' }, + { "file", 1, 0, 'f' }, + { 0, 0, 0, 0 } +}; + +#define MAX_IRKS 4 + +static void cmd_irks(int argc, char **argv) +{ + struct mgmt_cp_load_irks *cp; + uint8_t buf[sizeof(*cp) + 23 * MAX_IRKS]; + uint16_t count, local_index; + char path[PATH_MAX]; + int opt; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + cp = (void *) buf; + count = 0; + + while ((opt = getopt_long(argc, argv, "+l:f:h", + irks_options, NULL)) != -1) { + switch (opt) { + case 'l': + if (count >= MAX_IRKS) { + error("Number of IRKs exceeded"); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + if (strlen(optarg) > 3 && + strncasecmp(optarg, "hci", 3) == 0) + local_index = atoi(optarg + 3); + else + local_index = atoi(optarg); + snprintf(path, sizeof(path), + "/sys/kernel/debug/bluetooth/hci%u/identity", + local_index); + if (!load_identity(path, &cp->irks[count])) { + error("Unable to load identity"); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + count++; + break; + case 'f': + if (count >= MAX_IRKS) { + error("Number of IRKs exceeded"); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + if (!load_identity(optarg, &cp->irks[count])) { + error("Unable to load identities"); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + count++; + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + cp->irk_count = cpu_to_le16(count); + + if (mgmt_send(mgmt, MGMT_OP_LOAD_IRKS, index, + sizeof(*cp) + count * 23, cp, + irks_rsp, NULL, NULL) == 0) { + error("Unable to send load_irks cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void block_rsp(uint16_t op, uint16_t id, uint8_t status, uint16_t len, + const void *param) +{ + const struct mgmt_addr_info *rp = param; + char addr[18]; + + if (len == 0 && status != 0) { + error("%s failed, status 0x%02x (%s)", + mgmt_opstr(op), status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len != sizeof(*rp)) { + error("Unexpected %s len %u", mgmt_opstr(op), len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ba2str(&rp->bdaddr, addr); + + if (status) + error("%s %s (%s) failed. status 0x%02x (%s)", + mgmt_opstr(op), addr, typestr(rp->type), + status, mgmt_errstr(status)); + else + print("%s %s succeeded", mgmt_opstr(op), addr); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct option block_options[] = { + { "help", 0, 0, 'h' }, + { "type", 1, 0, 't' }, + { 0, 0, 0, 0 } +}; + +static void cmd_block(int argc, char **argv) +{ + struct mgmt_cp_block_device cp; + uint8_t type = BDADDR_BREDR; + int opt; + uint16_t index; + + while ((opt = getopt_long(argc, argv, "+t:h", block_options, + NULL)) != -1) { + switch (opt) { + case 't': + type = strtol(optarg, NULL, 0); + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + bt_shell_usage(); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + str2ba(argv[0], &cp.addr.bdaddr); + cp.addr.type = type; + + if (send_cmd(mgmt, MGMT_OP_BLOCK_DEVICE, index, sizeof(cp), &cp, + block_rsp) == 0) { + error("Unable to send block_device cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_unblock(int argc, char **argv) +{ + struct mgmt_cp_unblock_device cp; + uint8_t type = BDADDR_BREDR; + int opt; + uint16_t index; + + while ((opt = getopt_long(argc, argv, "+t:h", block_options, + NULL)) != -1) { + switch (opt) { + case 't': + type = strtol(optarg, NULL, 0); + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + bt_shell_usage(); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + str2ba(argv[0], &cp.addr.bdaddr); + cp.addr.type = type; + + if (send_cmd(mgmt, MGMT_OP_UNBLOCK_DEVICE, index, sizeof(cp), &cp, + block_rsp) == 0) { + error("Unable to send unblock_device cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_add_uuid(int argc, char **argv) +{ + struct mgmt_cp_add_uuid cp; + uint128_t uint128; + uuid_t uuid, uuid128; + uint16_t index; + + if (argc < 3) { + print("UUID and service hint needed"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (bt_string2uuid(&uuid, argv[1]) < 0) { + print("Invalid UUID: %s", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + memset(&cp, 0, sizeof(cp)); + + uuid_to_uuid128(&uuid128, &uuid); + ntoh128((uint128_t *) uuid128.value.uuid128.data, &uint128); + htob128(&uint128, (uint128_t *) cp.uuid); + + cp.svc_hint = atoi(argv[2]); + + if (send_cmd(mgmt, MGMT_OP_ADD_UUID, index, sizeof(cp), &cp, + class_rsp) == 0) { + error("Unable to send add_uuid cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_remove_uuid(int argc, char **argv) +{ + struct mgmt_cp_remove_uuid cp; + uint128_t uint128; + uuid_t uuid, uuid128; + uint16_t index; + + if (argc < 2) { + print("UUID needed"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (bt_string2uuid(&uuid, argv[1]) < 0) { + print("Invalid UUID: %s", argv[1]); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + memset(&cp, 0, sizeof(cp)); + + uuid_to_uuid128(&uuid128, &uuid); + ntoh128((uint128_t *) uuid128.value.uuid128.data, &uint128); + htob128(&uint128, (uint128_t *) cp.uuid); + + if (send_cmd(mgmt, MGMT_OP_REMOVE_UUID, index, sizeof(cp), &cp, + class_rsp) == 0) { + error("Unable to send remove_uuid cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_clr_uuids(int argc, char **argv) +{ + char *uuid_any = "00000000-0000-0000-0000-000000000000"; + char *rm_argv[] = { "rm-uuid", uuid_any, NULL }; + + cmd_remove_uuid(2, rm_argv); +} + +static void local_oob_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_local_oob_data *rp = param; + char str[33]; + + if (status != 0) { + error("Read Local OOB Data failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp)) { + error("Too small (%u bytes) read_local_oob rsp", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + bin2hex(rp->hash192, 16, str, sizeof(str)); + print("Hash C from P-192: %s", str); + + bin2hex(rp->rand192, 16, str, sizeof(str)); + print("Randomizer R with P-192: %s", str); + + if (len < sizeof(*rp)) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + + bin2hex(rp->hash256, 16, str, sizeof(str)); + print("Hash C from P-256: %s", str); + + bin2hex(rp->rand256, 16, str, sizeof(str)); + print("Randomizer R with P-256: %s", str); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_local_oob(int argc, char **argv) +{ + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (mgmt_send(mgmt, MGMT_OP_READ_LOCAL_OOB_DATA, index, 0, NULL, + local_oob_rsp, NULL, NULL) == 0) { + error("Unable to send read_local_oob cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void remote_oob_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_addr_info *rp = param; + char addr[18]; + + if (status != 0) { + error("Add Remote OOB Data failed: 0x%02x (%s)", + status, mgmt_errstr(status)); + return; + } + + if (len < sizeof(*rp)) { + error("Too small (%u bytes) add_remote_oob rsp", len); + return; + } + + ba2str(&rp->bdaddr, addr); + print("Remote OOB data added for %s (%u)", addr, rp->type); +} + +static struct option remote_oob_opt[] = { + { "help", 0, 0, '?' }, + { "type", 1, 0, 't' }, + { 0, 0, 0, 0 } +}; + +static void cmd_remote_oob(int argc, char **argv) +{ + struct mgmt_cp_add_remote_oob_data cp; + int opt; + uint16_t index; + + memset(&cp, 0, sizeof(cp)); + cp.addr.type = BDADDR_BREDR; + + while ((opt = getopt_long(argc, argv, "+t:r:R:h:H:", + remote_oob_opt, NULL)) != -1) { + switch (opt) { + case 't': + cp.addr.type = strtol(optarg, NULL, 0); + break; + case 'r': + hex2bin(optarg, cp.rand192, 16); + break; + case 'h': + hex2bin(optarg, cp.hash192, 16); + break; + case 'R': + hex2bin(optarg, cp.rand256, 16); + break; + case 'H': + hex2bin(optarg, cp.hash256, 16); + break; + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + bt_shell_usage(); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + str2ba(argv[0], &cp.addr.bdaddr); + + print("Adding OOB data for %s (%s)", argv[0], typestr(cp.addr.type)); + + if (mgmt_send(mgmt, MGMT_OP_ADD_REMOTE_OOB_DATA, index, + sizeof(cp), &cp, remote_oob_rsp, + NULL, NULL) == 0) { + error("Unable to send add_remote_oob cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void did_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Set Device ID failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + else + print("Device ID successfully set"); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_did(int argc, char **argv) +{ + struct mgmt_cp_set_device_id cp; + uint16_t vendor, product, version , source; + int result; + uint16_t index; + + result = sscanf(argv[1], "bluetooth:%4hx:%4hx:%4hx", &vendor, &product, + &version); + if (result == 3) { + source = 0x0001; + goto done; + } + + result = sscanf(argv[1], "usb:%4hx:%4hx:%4hx", &vendor, &product, + &version); + if (result == 3) { + source = 0x0002; + goto done; + } + + return; +done: + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + cp.source = htobs(source); + cp.vendor = htobs(vendor); + cp.product = htobs(product); + cp.version = htobs(version); + + if (mgmt_send(mgmt, MGMT_OP_SET_DEVICE_ID, index, sizeof(cp), &cp, + did_rsp, NULL, NULL) == 0) { + error("Unable to send set_device_id cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void static_addr_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Set static address failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + else + print("Static address successfully set"); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_static_addr(int argc, char **argv) +{ + struct mgmt_cp_set_static_address cp; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + str2ba(argv[1], &cp.bdaddr); + + if (mgmt_send(mgmt, MGMT_OP_SET_STATIC_ADDRESS, index, sizeof(cp), &cp, + static_addr_rsp, NULL, NULL) == 0) { + error("Unable to send set_static_address cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void options_rsp(uint16_t op, uint16_t id, uint8_t status, + uint16_t len, const void *param) +{ + const uint32_t *rp = param; + + if (status != 0) { + error("%s for hci%u failed with status 0x%02x (%s)", + mgmt_opstr(op), id, status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp)) { + error("Too small %s response (%u bytes)", + mgmt_opstr(op), len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("hci%u %s complete, options: %s", id, mgmt_opstr(op), + options2str(get_le32(rp))); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_public_addr(int argc, char **argv) +{ + struct mgmt_cp_set_public_address cp; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + str2ba(argv[1], &cp.bdaddr); + + if (send_cmd(mgmt, MGMT_OP_SET_PUBLIC_ADDRESS, index, sizeof(cp), &cp, + options_rsp) == 0) { + error("Unable to send Set Public Address cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_ext_config(int argc, char **argv) +{ + struct mgmt_cp_set_external_config cp; + uint16_t index; + + if (parse_setting(argc, argv, &cp.config) == false) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (send_cmd(mgmt, MGMT_OP_SET_EXTERNAL_CONFIG, index, sizeof(cp), &cp, + options_rsp) == 0) { + error("Unable to send Set External Config cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_debug_keys(int argc, char **argv) +{ + cmd_setting(MGMT_OP_SET_DEBUG_KEYS, argc, argv); +} + +static void conn_info_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_get_conn_info *rp = param; char addr[18]; + + if (len == 0 && status != 0) { + error("Get Conn Info failed, status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp)) { + error("Unexpected Get Conn Info len %u", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + ba2str(&rp->addr.bdaddr, addr); + + if (status) { + error("Get Conn Info for %s (%s) failed. status 0x%02x (%s)", + addr, typestr(rp->addr.type), + status, mgmt_errstr(status)); + } else { + print("Connection Information for %s (%s)", + addr, typestr(rp->addr.type)); + print("\tRSSI %d\tTX power %d\tmaximum TX power %d", + rp->rssi, rp->tx_power, rp->max_tx_power); + } + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct option conn_info_options[] = { + { "help", 0, 0, 'h' }, + { "type", 1, 0, 't' }, + { 0, 0, 0, 0 } +}; + +static void cmd_conn_info(int argc, char **argv) +{ + struct mgmt_cp_get_conn_info cp; + uint8_t type = BDADDR_BREDR; + int opt; + uint16_t index; + + while ((opt = getopt_long(argc, argv, "+t:h", conn_info_options, + NULL)) != -1) { + switch (opt) { + case 't': + type = strtol(optarg, NULL, 0); + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + bt_shell_usage(); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + str2ba(argv[0], &cp.addr.bdaddr); + cp.addr.type = type; + + if (mgmt_send(mgmt, MGMT_OP_GET_CONN_INFO, index, sizeof(cp), &cp, + conn_info_rsp, NULL, NULL) == 0) { + error("Unable to send get_conn_info cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void io_cap_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Could not set IO Capability with status 0x%02x (%s)", + status, mgmt_errstr(status)); + else + print("IO Capabilities successfully set"); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_io_cap(int argc, char **argv) +{ + struct mgmt_cp_set_io_capability cp; + uint8_t cap; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + cap = strtol(argv[1], NULL, 0); + memset(&cp, 0, sizeof(cp)); + cp.io_capability = cap; + + if (mgmt_send(mgmt, MGMT_OP_SET_IO_CAPABILITY, index, sizeof(cp), &cp, + io_cap_rsp, NULL, NULL) == 0) { + error("Unable to send set-io-cap cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void scan_params_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Set scan parameters failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + else + print("Scan parameters successfully set"); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_scan_params(int argc, char **argv) +{ + struct mgmt_cp_set_scan_params cp; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + cp.interval = strtol(argv[1], NULL, 0); + cp.window = strtol(argv[2], NULL, 0); + + if (mgmt_send(mgmt, MGMT_OP_SET_SCAN_PARAMS, index, sizeof(cp), &cp, + scan_params_rsp, NULL, NULL) == 0) { + error("Unable to send set_scan_params cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void clock_info_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_get_clock_info *rp = param; + + if (len < sizeof(*rp)) { + error("Unexpected Get Clock Info len %u", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (status) { + error("Get Clock Info failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("Local Clock: %u", le32_to_cpu(rp->local_clock)); + print("Piconet Clock: %u", le32_to_cpu(rp->piconet_clock)); + print("Accurary: %u", le16_to_cpu(rp->accuracy)); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_clock_info(int argc, char **argv) +{ + struct mgmt_cp_get_clock_info cp; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + + if (argc > 1) + str2ba(argv[1], &cp.addr.bdaddr); + + if (mgmt_send(mgmt, MGMT_OP_GET_CLOCK_INFO, index, sizeof(cp), &cp, + clock_info_rsp, NULL, NULL) == 0) { + error("Unable to send get_clock_info cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void add_device_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Add device failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct option add_device_options[] = { + { "help", 0, 0, 'h' }, + { "action", 1, 0, 'a' }, + { "type", 1, 0, 't' }, + { 0, 0, 0, 0 } +}; + +static void cmd_add_device(int argc, char **argv) +{ + struct mgmt_cp_add_device cp; + uint8_t action = 0x00; + uint8_t type = BDADDR_BREDR; + char addr[18]; + int opt; + uint16_t index; + + while ((opt = getopt_long(argc, argv, "+a:t:h", add_device_options, + NULL)) != -1) { + switch (opt) { + case 'a': + action = strtol(optarg, NULL, 0); + break; + case 't': + type = strtol(optarg, NULL, 0); + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + bt_shell_usage(); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + str2ba(argv[0], &cp.addr.bdaddr); + cp.addr.type = type; + cp.action = action; + + ba2str(&cp.addr.bdaddr, addr); + print("Adding device with %s (%s)", addr, typestr(cp.addr.type)); + + if (mgmt_send(mgmt, MGMT_OP_ADD_DEVICE, index, sizeof(cp), &cp, + add_device_rsp, NULL, NULL) == 0) { + error("Unable to send add device command"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void remove_device_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Remove device failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static struct option del_device_options[] = { + { "help", 0, 0, 'h' }, + { "type", 1, 0, 't' }, + { 0, 0, 0, 0 } +}; + +static void cmd_del_device(int argc, char **argv) +{ + struct mgmt_cp_remove_device cp; + uint8_t type = BDADDR_BREDR; + char addr[18]; + int opt; + uint16_t index; + + while ((opt = getopt_long(argc, argv, "+t:h", del_device_options, + NULL)) != -1) { + switch (opt) { + case 't': + type = strtol(optarg, NULL, 0); + break; + case 'h': + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + default: + bt_shell_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + bt_shell_usage(); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + str2ba(argv[0], &cp.addr.bdaddr); + cp.addr.type = type; + + ba2str(&cp.addr.bdaddr, addr); + print("Removing device with %s (%s)", addr, typestr(cp.addr.type)); + + if (mgmt_send(mgmt, MGMT_OP_REMOVE_DEVICE, index, sizeof(cp), &cp, + remove_device_rsp, NULL, NULL) == 0) { + error("Unable to send remove device command"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_clr_devices(int argc, char **argv) +{ + char *bdaddr_any = "00:00:00:00:00:00"; + char *rm_argv[] = { "del-device", bdaddr_any, NULL }; + + cmd_del_device(2, rm_argv); +} + +static void local_oob_ext_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_local_oob_ext_data *rp = param; + uint16_t eir_len; + + if (status != 0) { + error("Read Local OOB Ext Data failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp)) { + error("Too small (%u bytes) read_local_oob_ext rsp", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + eir_len = le16_to_cpu(rp->eir_len); + if (len != sizeof(*rp) + eir_len) { + error("local_oob_ext: expected %zu bytes, got %u bytes", + sizeof(*rp) + eir_len, len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print_eir(rp->eir, eir_len); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_bredr_oob(int argc, char **argv) +{ + struct mgmt_cp_read_local_oob_ext_data cp; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + cp.type = SCAN_TYPE_BREDR; + + if (!mgmt_send(mgmt, MGMT_OP_READ_LOCAL_OOB_EXT_DATA, + index, sizeof(cp), &cp, + local_oob_ext_rsp, NULL, NULL)) { + error("Unable to send read_local_oob_ext cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_le_oob(int argc, char **argv) +{ + struct mgmt_cp_read_local_oob_ext_data cp; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + cp.type = SCAN_TYPE_LE; + + if (!mgmt_send(mgmt, MGMT_OP_READ_LOCAL_OOB_EXT_DATA, + index, sizeof(cp), &cp, + local_oob_ext_rsp, NULL, NULL)) { + error("Unable to send read_local_oob_ext cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static const char *adv_flags_str[] = { + "connectable", + "general-discoverable", + "limited-discoverable", + "managed-flags", + "tx-power", + "scan-rsp-appearance", + "scan-rsp-local-name", + "Secondary-channel-1M", + "Secondary-channel-2M", + "Secondary-channel-CODED", +}; + +static const char *adv_flags2str(uint32_t flags) +{ + static char str[256]; + unsigned i; + int off; + + off = 0; + str[0] = '\0'; + + for (i = 0; i < NELEM(adv_flags_str); i++) { + if ((flags & (1 << i)) != 0) + off += snprintf(str + off, sizeof(str) - off, "%s ", + adv_flags_str[i]); + } + + return str; +} + +static void adv_features_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_read_adv_features *rp = param; + uint32_t supported_flags; + + if (status != 0) { + error("Reading adv features failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp)) { + error("Too small adv features reply (%u bytes)", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp) + rp->num_instances * sizeof(uint8_t)) { + error("Instances count (%u) doesn't match reply length (%u)", + rp->num_instances, len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + supported_flags = le32_to_cpu(rp->supported_flags); + print("Supported flags: %s", adv_flags2str(supported_flags)); + print("Max advertising data len: %u", rp->max_adv_data_len); + print("Max scan response data len: %u", rp->max_scan_rsp_len); + print("Max instances: %u", rp->max_instances); + + print("Instances list with %u item%s", rp->num_instances, + rp->num_instances != 1 ? "s" : ""); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_advinfo(int argc, char **argv) +{ + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (!mgmt_send(mgmt, MGMT_OP_READ_ADV_FEATURES, index, 0, NULL, + adv_features_rsp, NULL, NULL)) { + error("Unable to send advertising features command"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void adv_size_info_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_get_adv_size_info *rp = param; + uint32_t flags; + + if (status != 0) { + error("Reading adv size info failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp)) { + error("Too small adv size info reply (%u bytes)", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + flags = le32_to_cpu(rp->flags); + print("Instance: %u", rp->instance); + print("Flags: %s", adv_flags2str(flags)); + print("Max advertising data len: %u", rp->max_adv_data_len); + print("Max scan response data len: %u", rp->max_scan_rsp_len); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void advsize_usage(void) +{ + bt_shell_usage(); + print("Options:\n" + "\t -c, --connectable \"connectable\" flag\n" + "\t -g, --general-discov \"general-discoverable\" flag\n" + "\t -l, --limited-discov \"limited-discoverable\" flag\n" + "\t -m, --managed-flags \"managed-flags\" flag\n" + "\t -p, --tx-power \"tx-power\" flag\n" + "\t -a, --appearance \"appearance\" flag\n" + "\t -n, --local-name \"local-name\" flag"); +} + +static struct option advsize_options[] = { + { "help", 0, 0, 'h' }, + { "connectable", 0, 0, 'c' }, + { "general-discov", 0, 0, 'g' }, + { "limited-discov", 0, 0, 'l' }, + { "managed-flags", 0, 0, 'm' }, + { "tx-power", 0, 0, 'p' }, + { "appearance", 0, 0, 'a' }, + { "local-name", 0, 0, 'n' }, + { 0, 0, 0, 0} +}; + +static void cmd_advsize(int argc, char **argv) +{ + struct mgmt_cp_get_adv_size_info cp; + uint8_t instance; + uint32_t flags = 0; + int opt; + uint16_t index; + + while ((opt = getopt_long(argc, argv, "+cglmphna", + advsize_options, NULL)) != -1) { + switch (opt) { + case 'c': + flags |= MGMT_ADV_FLAG_CONNECTABLE; + break; + case 'g': + flags |= MGMT_ADV_FLAG_DISCOV; + break; + case 'l': + flags |= MGMT_ADV_FLAG_LIMITED_DISCOV; + break; + case 'm': + flags |= MGMT_ADV_FLAG_MANAGED_FLAGS; + break; + case 'p': + flags |= MGMT_ADV_FLAG_TX_POWER; + break; + case 'a': + flags |= MGMT_ADV_FLAG_APPEARANCE; + break; + case 'n': + flags |= MGMT_ADV_FLAG_LOCAL_NAME; + break; + default: + advsize_usage(); + optind = 0; + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc != 1) { + advsize_usage(); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + instance = strtol(argv[0], NULL, 0); + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + + cp.instance = instance; + cp.flags = cpu_to_le32(flags); + + if (!mgmt_send(mgmt, MGMT_OP_GET_ADV_SIZE_INFO, index, sizeof(cp), &cp, + adv_size_info_rsp, NULL, NULL)) { + error("Unable to send advertising size info command"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void add_adv_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_add_advertising *rp = param; + + if (status != 0) { + error("Add Advertising failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len != sizeof(*rp)) { + error("Invalid Add Advertising response length (%u)", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("Instance added: %u", rp->instance); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void add_adv_usage(void) +{ + bt_shell_usage(); + print("Options:\n" + "\t -u, --uuid Service UUID\n" + "\t -d, --adv-data Advertising Data bytes\n" + "\t -s, --scan-rsp Scan Response Data bytes\n" + "\t -t, --timeout Timeout in seconds\n" + "\t -D, --duration Duration in seconds\n" + "\t -P, --phy Phy type, Specify 1M/2M/CODED\n" + "\t -c, --connectable \"connectable\" flag\n" + "\t -g, --general-discov \"general-discoverable\" flag\n" + "\t -l, --limited-discov \"limited-discoverable\" flag\n" + "\t -n, --scan-rsp-local-name \"local-name\" flag\n" + "\t -a, --scan-rsp-appearance \"appearance\" flag\n" + "\t -m, --managed-flags \"managed-flags\" flag\n" + "\t -p, --tx-power \"tx-power\" flag\n" + "e.g.:\n" + "\tadd-adv -u 180d -u 180f -d 080954657374204C45 1"); +} + +static struct option add_adv_options[] = { + { "help", 0, 0, 'h' }, + { "uuid", 1, 0, 'u' }, + { "adv-data", 1, 0, 'd' }, + { "scan-rsp", 1, 0, 's' }, + { "timeout", 1, 0, 't' }, + { "duration", 1, 0, 'D' }, + { "phy", 1, 0, 'P' }, + { "connectable", 0, 0, 'c' }, + { "general-discov", 0, 0, 'g' }, + { "limited-discov", 0, 0, 'l' }, + { "managed-flags", 0, 0, 'm' }, + { "tx-power", 0, 0, 'p' }, + { 0, 0, 0, 0} +}; + +static bool parse_bytes(char *optarg, uint8_t **bytes, size_t *len) +{ + unsigned i; + + if (!optarg) { + add_adv_usage(); + return false; + } + + *len = strlen(optarg); + + if (*len % 2) { + error("Malformed data"); + return false; + } + + *len /= 2; + if (*len > UINT8_MAX) { + error("Data too long"); + return false; + } + + *bytes = malloc(*len); + if (!*bytes) { + error("Failed to allocate memory"); + return false; + } + + for (i = 0; i < *len; i++) { + if (sscanf(optarg + (i * 2), "%2hhx", *bytes + i) != 1) { + error("Invalid data"); + free(*bytes); + *bytes = NULL; + return false; + } + } + + return true; +} + +#define MAX_AD_UUID_BYTES 32 + +static void cmd_add_adv(int argc, char **argv) +{ + struct mgmt_cp_add_advertising *cp = NULL; + int opt; + uint8_t *adv_data = NULL, *scan_rsp = NULL; + size_t adv_len = 0, scan_rsp_len = 0; + size_t cp_len; + uint8_t uuids[MAX_AD_UUID_BYTES]; + size_t uuid_bytes = 0; + uint8_t uuid_type = 0; + uint16_t timeout = 0, duration = 0; + uint8_t instance; + uuid_t uuid; + bool success = false; + bool quit = true; + uint32_t flags = 0; + uint16_t index; + + while ((opt = getopt_long(argc, argv, "+u:d:s:t:D:P:cglmphna", + add_adv_options, NULL)) != -1) { + switch (opt) { + case 'u': + if (bt_string2uuid(&uuid, optarg) < 0) { + print("Invalid UUID: %s", optarg); + goto done; + } + + if (uuid_type && uuid_type != uuid.type) { + print("UUID types must be consistent"); + goto done; + } + + if (uuid.type == SDP_UUID16) { + if (uuid_bytes + 2 >= MAX_AD_UUID_BYTES) { + print("Too many UUIDs"); + goto done; + } + + put_le16(uuid.value.uuid16, uuids + uuid_bytes); + uuid_bytes += 2; + } else if (uuid.type == SDP_UUID128) { + if (uuid_bytes + 16 >= MAX_AD_UUID_BYTES) { + print("Too many UUIDs"); + goto done; + } + + bswap_128(uuid.value.uuid128.data, + uuids + uuid_bytes); + uuid_bytes += 16; + } else { + printf("Unsupported UUID type"); + goto done; + } + + if (!uuid_type) + uuid_type = uuid.type; + + break; + case 'd': + if (adv_len) { + print("Only one adv-data option allowed"); + goto done; + } + + if (!parse_bytes(optarg, &adv_data, &adv_len)) + goto done; + break; + case 's': + if (scan_rsp_len) { + print("Only one scan-rsp option allowed"); + goto done; + } + + if (!parse_bytes(optarg, &scan_rsp, &scan_rsp_len)) + goto done; + break; + case 't': + timeout = strtol(optarg, NULL, 0); + break; + case 'D': + duration = strtol(optarg, NULL, 0); + break; + case 'c': + flags |= MGMT_ADV_FLAG_CONNECTABLE; + break; + case 'g': + flags |= MGMT_ADV_FLAG_DISCOV; + break; + case 'l': + flags |= MGMT_ADV_FLAG_LIMITED_DISCOV; + break; + case 'm': + flags |= MGMT_ADV_FLAG_MANAGED_FLAGS; + break; + case 'p': + flags |= MGMT_ADV_FLAG_TX_POWER; + break; + case 'n': + flags |= MGMT_ADV_FLAG_LOCAL_NAME; + break; + case 'a': + flags |= MGMT_ADV_FLAG_APPEARANCE; + break; + case 'P': + if (strcasecmp(optarg, "1M") == 0) + flags |= MGMT_ADV_FLAG_SEC_1M; + else if (strcasecmp(optarg, "2M") == 0) + flags |= MGMT_ADV_FLAG_SEC_2M; + else if (strcasecmp(optarg, "CODED") == 0) + flags |= MGMT_ADV_FLAG_SEC_CODED; + else + goto done; + break; + case 'h': + success = true; + /* fall through */ + default: + add_adv_usage(); + optind = 0; + goto done; + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc != 1) { + add_adv_usage(); + goto done; + } + + if (uuid_bytes) + uuid_bytes += 2; + + instance = strtol(argv[0], NULL, 0); + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + cp_len = sizeof(*cp) + uuid_bytes + adv_len + scan_rsp_len; + cp = malloc0(cp_len); + if (!cp) + goto done; + + cp->instance = instance; + put_le32(flags, &cp->flags); + put_le16(timeout, &cp->timeout); + put_le16(duration, &cp->duration); + cp->adv_data_len = adv_len + uuid_bytes; + cp->scan_rsp_len = scan_rsp_len; + + if (uuid_bytes) { + cp->data[0] = uuid_bytes - 1; + cp->data[1] = uuid_type == SDP_UUID16 ? 0x03 : 0x07; + memcpy(cp->data + 2, uuids, uuid_bytes - 2); + } + + memcpy(cp->data + uuid_bytes, adv_data, adv_len); + memcpy(cp->data + uuid_bytes + adv_len, scan_rsp, scan_rsp_len); + + if (!mgmt_send(mgmt, MGMT_OP_ADD_ADVERTISING, index, cp_len, cp, + add_adv_rsp, NULL, NULL)) { + error("Unable to send \"Add Advertising\" command"); + goto done; + } + + quit = false; + +done: + free(adv_data); + free(scan_rsp); + free(cp); + + if (quit) + bt_shell_noninteractive_quit(success ? EXIT_SUCCESS : EXIT_FAILURE); +} + +static void rm_adv_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_remove_advertising *rp = param; + + if (status != 0) { + error("Remove Advertising failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len != sizeof(*rp)) { + error("Invalid Remove Advertising response length (%u)", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("Instance removed: %u", rp->instance); + + return bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_rm_adv(int argc, char **argv) +{ + struct mgmt_cp_remove_advertising cp; + uint8_t instance; + uint16_t index; + + instance = strtol(argv[1], NULL, 0); + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + memset(&cp, 0, sizeof(cp)); + + cp.instance = instance; + + if (!mgmt_send(mgmt, MGMT_OP_REMOVE_ADVERTISING, index, sizeof(cp), &cp, + rm_adv_rsp, NULL, NULL)) { + error("Unable to send \"Remove Advertising\" command"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void cmd_clr_adv(int argc, char **argv) +{ + char *all_instances = "0"; + char *rm_argv[] = { "rm-adv", all_instances, NULL }; + + cmd_rm_adv(2, rm_argv); +} + +static void appearance_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) + error("Could not set Appearance with status 0x%02x (%s)", + status, mgmt_errstr(status)); + else + print("Appearance successfully set"); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_appearance(int argc, char **argv) +{ + struct mgmt_cp_set_appearance cp; + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + cp.appearance = cpu_to_le16(strtol(argv[1], NULL, 0)); + + if (mgmt_send(mgmt, MGMT_OP_SET_APPEARANCE, index, sizeof(cp), &cp, + appearance_rsp, NULL, NULL) == 0) { + error("Unable to send appearance cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static const char *phys_str[] = { + "BR1M1SLOT", + "BR1M3SLOT", + "BR1M5SLOT", + "EDR2M1SLOT", + "EDR2M3SLOT", + "EDR2M5SLOT", + "EDR3M1SLOT", + "EDR3M3SLOT", + "EDR3M5SLOT", + "LE1MTX", + "LE1MRX", + "LE2MTX", + "LE2MRX", + "LECODEDTX", + "LECODEDRX", +}; + +static const char *phys2str(uint32_t phys) +{ + static char str[256]; + unsigned int i; + int off; + + off = 0; + str[0] = '\0'; + + for (i = 0; i < NELEM(phys_str); i++) { + if ((phys & (1 << i)) != 0) + off += snprintf(str + off, sizeof(str) - off, "%s ", + phys_str[i]); + } + + return str; +} + +static bool str2phy(const char *phy_str, uint32_t *phy_val) +{ + unsigned int i; + + for (i = 0; i < NELEM(phys_str); i++) { + if (strcasecmp(phys_str[i], phy_str) == 0) { + *phy_val = (1 << i); + return true; + } + } + + return false; +} + +static void get_phy_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + const struct mgmt_rp_get_phy_confguration *rp = param; + uint32_t supported_phys, selected_phys, configurable_phys; + + if (status != 0) { + error("Get PHY Configuration failed with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + if (len < sizeof(*rp)) { + error("Too small get-phy reply (%u bytes)", len); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + supported_phys = get_le32(&rp->supported_phys); + configurable_phys = get_le32(&rp->configurable_phys); + selected_phys = get_le32(&rp->selected_phys); + + print("Supported phys: %s", phys2str(supported_phys)); + print("Configurable phys: %s", phys2str(configurable_phys)); + print("Selected phys: %s", phys2str(selected_phys)); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void get_phy(void) +{ + uint16_t index; + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (mgmt_send(mgmt, MGMT_OP_GET_PHY_CONFIGURATION, index, 0, NULL, + get_phy_rsp, NULL, NULL) == 0) { + error("Unable to send Get PHY cmd"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void set_phy_rsp(uint8_t status, uint16_t len, const void *param, + void *user_data) +{ + if (status != 0) { + error("Could not set PHY Configuration with status 0x%02x (%s)", + status, mgmt_errstr(status)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + + print("PHY Configuration successfully set"); + + bt_shell_noninteractive_quit(EXIT_SUCCESS); +} + +static void cmd_phy(int argc, char **argv) +{ + struct mgmt_cp_set_phy_confguration cp; + int i; + uint32_t phys = 0; + uint16_t index; + + if (argc < 2) + return get_phy(); + + for (i = 1; i < argc; i++) { + uint32_t phy_val; + + if (str2phy(argv[i], &phy_val)) + phys |= phy_val; + } + + cp.selected_phys = cpu_to_le32(phys); + + index = mgmt_index; + if (index == MGMT_INDEX_NONE) + index = 0; + + if (mgmt_send(mgmt, MGMT_OP_SET_PHY_CONFIGURATION, index, sizeof(cp), + &cp, set_phy_rsp, NULL, NULL) == 0) { + error("Unable to send %s cmd", + mgmt_opstr(MGMT_OP_GET_PHY_CONFIGURATION)); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +} + +static void register_mgmt_callbacks(struct mgmt *mgmt, uint16_t index) +{ + mgmt_register(mgmt, MGMT_EV_CONTROLLER_ERROR, index, controller_error, + NULL, NULL); + mgmt_register(mgmt, MGMT_EV_INDEX_ADDED, index, index_added, + NULL, NULL); + mgmt_register(mgmt, MGMT_EV_INDEX_REMOVED, index, index_removed, + NULL, NULL); + mgmt_register(mgmt, MGMT_EV_NEW_SETTINGS, index, new_settings, + NULL, NULL); + mgmt_register(mgmt, MGMT_EV_DISCOVERING, index, discovering, + NULL, NULL); + mgmt_register(mgmt, MGMT_EV_NEW_LINK_KEY, index, new_link_key, + NULL, NULL); + mgmt_register(mgmt, MGMT_EV_DEVICE_CONNECTED, index, connected, + NULL, NULL); + mgmt_register(mgmt, MGMT_EV_DEVICE_DISCONNECTED, index, disconnected, + NULL, NULL); + mgmt_register(mgmt, MGMT_EV_CONNECT_FAILED, index, conn_failed, + NULL, NULL); + mgmt_register(mgmt, MGMT_EV_AUTH_FAILED, index, auth_failed, + NULL, NULL); + mgmt_register(mgmt, MGMT_EV_CLASS_OF_DEV_CHANGED, index, + class_of_dev_changed, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_LOCAL_NAME_CHANGED, index, + local_name_changed, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_DEVICE_FOUND, index, device_found, + mgmt, NULL); + mgmt_register(mgmt, MGMT_EV_PIN_CODE_REQUEST, index, request_pin, + mgmt, NULL); + mgmt_register(mgmt, MGMT_EV_USER_CONFIRM_REQUEST, index, user_confirm, + mgmt, NULL); + mgmt_register(mgmt, MGMT_EV_USER_PASSKEY_REQUEST, index, + request_passkey, mgmt, NULL); + mgmt_register(mgmt, MGMT_EV_PASSKEY_NOTIFY, index, + passkey_notify, mgmt, NULL); + mgmt_register(mgmt, MGMT_EV_UNCONF_INDEX_ADDED, index, + unconf_index_added, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_UNCONF_INDEX_REMOVED, index, + unconf_index_removed, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_NEW_CONFIG_OPTIONS, index, + new_config_options, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_EXT_INDEX_ADDED, index, + ext_index_added, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_EXT_INDEX_REMOVED, index, + ext_index_removed, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_LOCAL_OOB_DATA_UPDATED, index, + local_oob_data_updated, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_ADVERTISING_ADDED, index, + advertising_added, NULL, NULL); + mgmt_register(mgmt, MGMT_EV_ADVERTISING_REMOVED, index, + advertising_removed, NULL, NULL); +} + +static void cmd_select(int argc, char **argv) +{ + mgmt_cancel_all(mgmt); + mgmt_unregister_all(mgmt); + + set_index(argv[1]); + + register_mgmt_callbacks(mgmt, mgmt_index); + + print("Selected index %u", mgmt_index); + + update_prompt(mgmt_index); +} + +static const struct bt_shell_menu main_menu = { + .name = "main", + .entries = { + { "select", "", + cmd_select, "Select a different index" }, + { "version", NULL, + cmd_version, "Get the MGMT Version" }, + { "commands", NULL, + cmd_commands, "List supported commands" }, + { "config", NULL, + cmd_config, "Show configuration info" }, + { "info", NULL, + cmd_info, "Show controller info" }, + { "extinfo", NULL, + cmd_extinfo, "Show extended controller info" }, + { "auto-power", NULL, + cmd_auto_power, "Power all available features" }, + { "power", "", + cmd_power, "Toggle powered state" }, + { "discov", " [timeout]", + cmd_discov, "Toggle discoverable state" }, + { "connectable", "", + cmd_connectable, "Toggle connectable state" }, + { "fast-conn", "", + cmd_fast_conn, "Toggle fast connectable state" }, + { "bondable", "", + cmd_bondable, "Toggle bondable state" }, + { "pairable", "", + cmd_bondable, "Toggle bondable state" }, + { "linksec", "", + cmd_linksec, "Toggle link level security" }, + { "ssp", "", + cmd_ssp, "Toggle SSP mode" }, + { "sc", "", + cmd_sc, "Toogle SC support" }, + { "hs", "", + cmd_hs, "Toggle HS support" }, + { "le", "", + cmd_le, "Toggle LE support" }, + { "advertising", "", + cmd_advertising, "Toggle LE advertising", }, + { "bredr", "", + cmd_bredr, "Toggle BR/EDR support", }, + { "privacy", "", + cmd_privacy, "Toggle privacy support" }, + { "class", " ", + cmd_class, "Set device major/minor class" }, + { "disconnect", "[-t type] ", + cmd_disconnect, "Disconnect device" }, + { "con", NULL, + cmd_con, "List connections" }, + { "find", "[-l|-b] [-L]", + cmd_find, "Discover nearby devices" }, + { "find-service", "[-u UUID] [-r RSSI_Threshold] [-l|-b]", + cmd_find_service, "Discover nearby service" }, + { "stop-find", "[-l|-b]", + cmd_stop_find, "Stop discovery" }, + { "name", " [shortname]", + cmd_name, "Set local name" }, + { "pair", "[-c cap] [-t type] ", + cmd_pair, "Pair with a remote device" }, + { "cancelpair", "[-t type] ", + cmd_cancel_pair, "Cancel pairing" }, + { "unpair", "[-t type] ", + cmd_unpair, "Unpair device" }, + { "keys", NULL, + cmd_keys, "Load Link Keys" }, + { "ltks", NULL, + cmd_ltks, "Load Long Term Keys" }, + { "irks", "[--local ] [--file ]", + cmd_irks, "Load Identity Resolving Keys" }, + { "block", "[-t type] ", + cmd_block, "Block Device" }, + { "unblock", "[-t type] ", + cmd_unblock, "Unblock Device" }, + { "add-uuid", " ", + cmd_add_uuid, "Add UUID" }, + { "rm-uuid", "", + cmd_remove_uuid, "Remove UUID" }, + { "clr-uuids", NULL, + cmd_clr_uuids, "Clear UUIDs" }, + { "local-oob", NULL, + cmd_local_oob, "Local OOB data" }, + { "remote-oob", "[-t ] [-r ] " + "[-h ] [-R ] " + "[-H ] ", + cmd_remote_oob, "Remote OOB data" }, + { "did", ":::", + cmd_did, "Set Device ID" }, + { "static-addr", "
", + cmd_static_addr, "Set static address" }, + { "public-addr", "
", + cmd_public_addr, "Set public address" }, + { "ext-config", "", + cmd_ext_config, "External configuration" }, + { "debug-keys", "", + cmd_debug_keys, "Toogle debug keys" }, + { "conn-info", "[-t type] ", + cmd_conn_info, "Get connection information" }, + { "io-cap", "", + cmd_io_cap, "Set IO Capability" }, + { "scan-params", " ", + cmd_scan_params, "Set Scan Parameters" }, + { "get-clock", "[address]", + cmd_clock_info, "Get Clock Information" }, + { "add-device", "[-a action] [-t type]
", + cmd_add_device, "Add Device" }, + { "del-device", "[-t type]
", + cmd_del_device, "Remove Device" }, + { "clr-devices", NULL, + cmd_clr_devices, "Clear Devices" }, + { "bredr-oob", NULL, + cmd_bredr_oob, "Local OOB data (BR/EDR)" }, + { "le-oob", NULL, + cmd_le_oob, "Local OOB data (LE)" }, + { "advinfo", NULL, + cmd_advinfo, "Show advertising features" }, + { "advsize", "[options] ", + cmd_advsize, "Show advertising size info" }, + { "add-adv", "[options] ", + cmd_add_adv, "Add advertising instance" }, + { "rm-adv", "", + cmd_rm_adv, "Remove advertising instance" }, + { "clr-adv", NULL, + cmd_clr_adv, "Clear advertising instances" }, + { "appearance", "", + cmd_appearance, "Set appearance" }, + { "phy", "[LE1MTX] [LE1MRX] [LE2MTX] [LE2MRX] " + "[LECODEDTX] [LECODEDRX] " + "[BR1M1SLOT] [BR1M3SLOT] [BR1M5SLOT]" + "[EDR2M1SLOT] [EDR2M3SLOT] [EDR2M5SLOT]" + "[EDR3M1SLOT] [EDR3M3SLOT] [EDR3M5SLOT]", + cmd_phy, "Get/Set PHY Configuration" }, + {} }, +}; + +static void mgmt_debug(const char *str, void *user_data) +{ + const char *prefix = user_data; + + print("%s%s", prefix, str); +} + +static const char *index_option; + +static struct option main_options[] = { + { "index", 1, 0, 'i' }, + { 0, 0, 0, 0 } +}; + +static const char **optargs[] = { + &index_option +}; + +static const char *help[] = { + "Specify adapter index\n" +}; + +static const struct bt_shell_opt opt = { + .options = main_options, + .optno = sizeof(main_options) / sizeof(struct option), + .optstr = "i:V", + .optarg = optargs, + .help = help, +}; + +int main(int argc, char *argv[]) +{ + int status; + + bt_shell_init(argc, argv, &opt); + bt_shell_set_menu(&main_menu); + + mgmt = mgmt_new_default(); + if (!mgmt) { + fprintf(stderr, "Unable to open mgmt_socket\n"); + return EXIT_FAILURE; + } + + if (getenv("MGMT_DEBUG")) + mgmt_set_debug(mgmt, mgmt_debug, "mgmt: ", NULL); + + if (index_option) + set_index(index_option); + + register_mgmt_callbacks(mgmt, mgmt_index); + + bt_shell_attach(fileno(stdin)); + update_prompt(mgmt_index); + status = bt_shell_run(); + + mgmt_cancel_all(mgmt); + mgmt_unregister_all(mgmt); + mgmt_unref(mgmt); + + return status; +} diff --git a/tools/btmon-logger.c b/tools/btmon-logger.c new file mode 100644 index 0000000..9c23d3c --- /dev/null +++ b/tools/btmon-logger.c @@ -0,0 +1,380 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2017-2018 Codecoup + * Copyright (C) 2011-2014 Intel Corporation + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" + +#include "src/shared/util.h" +#include "src/shared/mainloop.h" +#include "src/shared/btsnoop.h" + +#define MONITOR_INDEX_NONE 0xffff + +struct monitor_hdr { + uint16_t opcode; + uint16_t index; + uint16_t len; +} __attribute__ ((packed)); + +static struct btsnoop *btsnoop_file = NULL; + +static void data_callback(int fd, uint32_t events, void *user_data) +{ + uint8_t buf[BTSNOOP_MAX_PACKET_SIZE]; + unsigned char control[64]; + struct monitor_hdr hdr; + struct msghdr msg; + struct iovec iov[2]; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_exit_failure(); + return; + } + + iov[0].iov_base = &hdr; + iov[0].iov_len = sizeof(hdr); + iov[1].iov_base = buf; + iov[1].iov_len = sizeof(buf); + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 2; + msg.msg_control = control; + msg.msg_controllen = sizeof(control); + + while (1) { + struct cmsghdr *cmsg; + struct timeval *tv = NULL; + struct timeval ctv; + uint16_t opcode, index, pktlen; + ssize_t len; + + len = recvmsg(fd, &msg, MSG_DONTWAIT); + if (len < 0) + break; + + if (len < (ssize_t) sizeof(hdr)) + 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 = le16_to_cpu(hdr.opcode); + index = le16_to_cpu(hdr.index); + pktlen = le16_to_cpu(hdr.len); + + btsnoop_write_hci(btsnoop_file, tv, index, opcode, 0, buf, + pktlen); + } +} + +static bool open_monitor_channel(void) +{ + struct sockaddr_hci addr; + int fd, opt = 1; + + fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (fd < 0) { + perror("Failed to open monitor channel"); + return false; + } + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = HCI_DEV_NONE; + addr.hci_channel = HCI_CHANNEL_MONITOR; + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind monitor channel"); + close(fd); + return false; + } + + if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &opt, sizeof(opt)) < 0) { + perror("Failed to enable timestamps"); + close(fd); + return false; + } + + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &opt, sizeof(opt)) < 0) { + perror("Failed to enable credentials"); + close(fd); + return false; + } + + mainloop_add_fd(fd, EPOLLIN, data_callback, NULL, NULL); + + return true; +} + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + } +} + +extern int capget(struct __user_cap_header_struct *header, + struct __user_cap_data_struct *data); +extern int capset(struct __user_cap_header_struct *header, + const struct __user_cap_data_struct *data); + +static void drop_capabilities(void) +{ + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap; + unsigned int mask; + int err; + + header.version = _LINUX_CAPABILITY_VERSION_3; + header.pid = 0; + + err = capget(&header, &cap); + if (err) { + perror("Unable to get current capabilities"); + return; + } + + /* not needed anymore since monitor socket is already open */ + mask = ~CAP_TO_MASK(CAP_NET_RAW); + + cap.effective &= mask; + cap.permitted &= mask; + cap.inheritable &= mask; + + err = capset(&header, &cap); + if (err) + perror("Failed to set capabilities"); +} + +static void usage(void) +{ + printf("btmon-logger - Bluetooth monitor\n" + "Usage:\n"); + printf("\tbtmon-logger [options]\n"); + printf("options:\n" + "\t-b, --basename Save traces in specified path\n" + "\t-p, --parents Create basename parent directories\n" + "\t-l, --limit Limit traces file size (rotate)\n" + "\t-c, --count Limit number of rotated files\n" + "\t-v, --version Show version\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "basename", required_argument, NULL, 'b' }, + { "parents", no_argument, NULL, 'p' }, + { "limit", required_argument, NULL, 'l' }, + { "count", required_argument, NULL, 'c' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +static int create_dir(const char *filename) +{ + char *dirc; + char *dir; + char *p; + int err = 0; + + /* get base directory */ + dirc = strdup(filename); + dir = dirname(dirc); + + p = dir; + + /* preserve leading / if present */ + if (*p == '/') + p++; + + /* create any intermediate directories */ + p = strchrnul(p, '/'); + while (*p) { + /* cut directory path */ + *p = '\0'; + + if (mkdir(dir, 0700) < 0 && errno != EEXIST) { + err = errno; + goto done; + } + + /* restore directory path */ + *p = '/'; + p = strchrnul(p + 1, '/'); + } + + /* create leaf directory */ + if (mkdir(dir, 0700) < 0 && errno != EEXIST) + err = errno; + +done: + free(dirc); + + if (err) + printf("Failed to create parent directories for %s\n", + filename); + + return err; +} + +int main(int argc, char *argv[]) +{ + const char *path = "hci.log"; + unsigned long max_count = 0; + size_t size_limit = 0; + bool parents = false; + int exit_status; + char *endptr; + + mainloop_init(); + + mainloop_sd_notify("STATUS=Starting up"); + + while (true) { + int opt; + + opt = getopt_long(argc, argv, "b:l:c:vhp", main_options, + NULL); + if (opt < 0) + break; + + switch (opt) { + case 'b': + path = optarg; + if (strlen(path) > PATH_MAX) { + fprintf(stderr, "Too long path\n"); + return EXIT_FAILURE; + } + break; + case 'l': + size_limit = strtoul(optarg, &endptr, 10); + + if (size_limit == ULONG_MAX) { + fprintf(stderr, "Invalid limit\n"); + return EXIT_FAILURE; + } + + if (*endptr != '\0') { + if (*endptr == 'K' || *endptr == 'k') { + size_limit *= 1024; + } else if (*endptr == 'M' || *endptr == 'm') { + size_limit *= 1024 * 1024; + } else { + fprintf(stderr, "Invalid limit\n"); + return EXIT_FAILURE; + } + } + + /* limit this to reasonable size */ + if (size_limit < 4096) { + fprintf(stderr, "Too small limit value\n"); + return EXIT_FAILURE; + } + break; + case 'c': + max_count = strtoul(optarg, &endptr, 10); + break; + case 'p': + if (getppid() != 1) { + fprintf(stderr, "Parents option allowed only " + "when running as a service\n"); + return EXIT_FAILURE; + } + + parents = true; + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind > 0) { + fprintf(stderr, "Invalid command line parameters\n"); + return EXIT_FAILURE; + } + + if (!open_monitor_channel()) + return EXIT_FAILURE; + + if (parents && create_dir(path) < 0) + return EXIT_FAILURE; + + btsnoop_file = btsnoop_create(path, size_limit, max_count, + BTSNOOP_FORMAT_MONITOR); + if (!btsnoop_file) + return EXIT_FAILURE; + + drop_capabilities(); + + printf("Bluetooth monitor logger ver %s\n", VERSION); + + mainloop_sd_notify("STATUS=Running"); + mainloop_sd_notify("READY=1"); + + exit_status = mainloop_run_with_signal(signal_callback, NULL); + + mainloop_sd_notify("STATUS=Quitting"); + + btsnoop_unref(btsnoop_file); + + return exit_status; +} diff --git a/tools/btpclient.c b/tools/btpclient.c new file mode 100644 index 0000000..f9c6930 --- /dev/null +++ b/tools/btpclient.c @@ -0,0 +1,3221 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2017 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 +#endif + +#include +#include +#include +#include +#include +#include + +#include + +#include "lib/bluetooth.h" +#include "src/shared/btp.h" + +#define AD_PATH "/org/bluez/advertising" +#define AG_PATH "/org/bluez/agent" +#define AD_IFACE "org.bluez.LEAdvertisement1" +#define AG_IFACE "org.bluez.Agent1" + +/* List of assigned numbers for advetising data and scan response */ +#define AD_TYPE_FLAGS 0x01 +#define AD_TYPE_INCOMPLETE_UUID16_SERVICE_LIST 0x02 +#define AD_TYPE_SHORT_NAME 0x08 +#define AD_TYPE_TX_POWER 0x0a +#define AD_TYPE_SOLICIT_UUID16_SERVICE_LIST 0x14 +#define AD_TYPE_SERVICE_DATA_UUID16 0x16 +#define AD_TYPE_APPEARANCE 0x19 +#define AD_TYPE_MANUFACTURER_DATA 0xff + +static void register_gap_service(void); + +static struct l_dbus *dbus; + +struct btp_adapter { + struct l_dbus_proxy *proxy; + struct l_dbus_proxy *ad_proxy; + uint8_t index; + uint32_t supported_settings; + uint32_t current_settings; + uint32_t default_settings; + struct l_queue *devices; +}; + +struct btp_device { + struct l_dbus_proxy *proxy; + uint8_t address_type; + bdaddr_t address; +}; + +static struct l_queue *adapters; +static char *socket_path; +static struct btp *btp; + +static bool gap_service_registered; + +struct ad_data { + uint8_t data[25]; + uint8_t len; +}; + +struct service_data { + char *uuid; + struct ad_data data; +}; + +struct manufacturer_data { + uint16_t id; + struct ad_data data; +}; + +static struct ad { + bool registered; + char *type; + char *local_name; + uint16_t local_appearance; + uint16_t duration; + uint16_t timeout; + struct l_queue *uuids; + struct l_queue *services; + struct l_queue *manufacturers; + struct l_queue *solicits; + bool tx_power; + bool name; + bool appearance; +} ad; + +static struct btp_agent { + bool registered; + struct l_dbus_proxy *proxy; + struct l_dbus_message *pending_req; +} ag; + +static char *dupuuid2str(const uint8_t *uuid, uint8_t len) +{ + switch (len) { + case 16: + return l_strdup_printf("%hhx%hhx", uuid[0], uuid[1]); + case 128: + return l_strdup_printf("%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx%hhx" + "%hhx%hhx%hhx%hhx%hhx%hhx%hhx", uuid[0], + uuid[1], uuid[2], uuid[3], uuid[4], + uuid[5], uuid[6], uuid[6], uuid[8], + uuid[7], uuid[10], uuid[11], uuid[12], + uuid[13], uuid[14], uuid[15]); + default: + return NULL; + } +} + +static bool match_dev_addr_type(const char *addr_type_str, uint8_t addr_type) +{ + if (addr_type == BTP_GAP_ADDR_PUBLIC && strcmp(addr_type_str, "public")) + return false; + + if (addr_type == BTP_GAP_ADDR_RANDOM && strcmp(addr_type_str, "random")) + return false; + + return true; +} + +static struct btp_adapter *find_adapter_by_proxy(struct l_dbus_proxy *proxy) +{ + const struct l_queue_entry *entry; + + for (entry = l_queue_get_entries(adapters); entry; + entry = entry->next) { + struct btp_adapter *adapter = entry->data; + + if (adapter->proxy == proxy) + return adapter; + } + + return NULL; +} + +static struct btp_adapter *find_adapter_by_index(uint8_t index) +{ + const struct l_queue_entry *entry; + + for (entry = l_queue_get_entries(adapters); entry; + entry = entry->next) { + struct btp_adapter *adapter = entry->data; + + if (adapter->index == index) + return adapter; + } + + return NULL; +} + +static struct btp_adapter *find_adapter_by_path(const char *path) +{ + const struct l_queue_entry *entry; + + for (entry = l_queue_get_entries(adapters); entry; + entry = entry->next) { + struct btp_adapter *adapter = entry->data; + + if (!strcmp(l_dbus_proxy_get_path(adapter->proxy), path)) + return adapter; + } + + return NULL; +} + +static struct btp_device *find_device_by_address(struct btp_adapter *adapter, + const bdaddr_t *addr, + uint8_t addr_type) +{ + const struct l_queue_entry *entry; + const char *str; + char addr_str[18]; + + if (!ba2str(addr, addr_str)) + return NULL; + + for (entry = l_queue_get_entries(adapter->devices); entry; + entry = entry->next) { + struct btp_device *device = entry->data; + + l_dbus_proxy_get_property(device->proxy, "Address", "s", &str); + if (strcmp(str, addr_str)) + continue; + + l_dbus_proxy_get_property(device->proxy, "AddressType", "s", + &str); + if (match_dev_addr_type(str, addr_type)) + return device; + } + + return NULL; +} + +static bool match_device_paths(const void *device, const void *path) +{ + const struct btp_device *dev = device; + + return !strcmp(l_dbus_proxy_get_path(dev->proxy), path); +} + +static struct btp_device *find_device_by_path(const char *path) +{ + const struct l_queue_entry *entry; + struct btp_device *device; + + for (entry = l_queue_get_entries(adapters); entry; + entry = entry->next) { + struct btp_adapter *adapter = entry->data; + + device = l_queue_find(adapter->devices, match_device_paths, + path); + if (device) + return device; + } + + return NULL; +} + +static bool match_adapter_dev_proxy(const void *device, const void *proxy) +{ + const struct btp_device *d = device; + + return d->proxy == proxy; +} + +static bool match_adapter_dev(const void *device_a, const void *device_b) +{ + return device_a == device_b; +} + +static struct btp_adapter *find_adapter_by_device(struct btp_device *device) +{ + const struct l_queue_entry *entry; + + for (entry = l_queue_get_entries(adapters); entry; + entry = entry->next) { + struct btp_adapter *adapter = entry->data; + + if (l_queue_find(adapter->devices, match_adapter_dev, device)) + return adapter; + } + + return NULL; +} + +static struct btp_device *find_device_by_proxy(struct l_dbus_proxy *proxy) +{ + const struct l_queue_entry *entry; + struct btp_device *device; + + for (entry = l_queue_get_entries(adapters); entry; + entry = entry->next) { + struct btp_adapter *adapter = entry->data; + + device = l_queue_find(adapter->devices, match_adapter_dev_proxy, + proxy); + + if (device) + return device; + } + + return NULL; +} + +static void btp_gap_read_commands(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + uint16_t commands = 0; + + if (index != BTP_INDEX_NON_CONTROLLER) { + btp_send_error(btp, BTP_GAP_SERVICE, index, + BTP_ERROR_INVALID_INDEX); + return; + } + + commands |= (1 << BTP_OP_GAP_READ_SUPPORTED_COMMANDS); + commands |= (1 << BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST); + commands |= (1 << BTP_OP_GAP_READ_COTROLLER_INFO); + commands |= (1 << BTP_OP_GAP_RESET); + commands |= (1 << BTP_OP_GAP_SET_POWERED); + commands |= (1 << BTP_OP_GAP_SET_CONNECTABLE); + commands |= (1 << BTP_OP_GAP_SET_DISCOVERABLE); + commands |= (1 << BTP_OP_GAP_SET_BONDABLE); + commands |= (1 << BTP_OP_GAP_START_ADVERTISING); + commands |= (1 << BTP_OP_GAP_STOP_ADVERTISING); + commands |= (1 << BTP_OP_GAP_START_DISCOVERY); + commands |= (1 << BTP_OP_GAP_STOP_DISCOVERY); + commands |= (1 << BTP_OP_GAP_CONNECT); + commands |= (1 << BTP_OP_GAP_DISCONNECT); + commands |= (1 << BTP_OP_GAP_SET_IO_CAPA); + commands |= (1 << BTP_OP_GAP_PAIR); + commands |= (1 << BTP_OP_GAP_UNPAIR); + + commands = L_CPU_TO_LE16(commands); + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_SUPPORTED_COMMANDS, + BTP_INDEX_NON_CONTROLLER, sizeof(commands), &commands); +} + +static void btp_gap_read_controller_index(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + const struct l_queue_entry *entry; + struct btp_gap_read_index_rp *rp; + uint8_t cnt; + int i; + + if (index != BTP_INDEX_NON_CONTROLLER) { + btp_send_error(btp, BTP_GAP_SERVICE, index, + BTP_ERROR_INVALID_INDEX); + return; + } + + cnt = l_queue_length(adapters); + + rp = l_malloc(sizeof(*rp) + cnt); + + rp->num = cnt; + + for (i = 0, entry = l_queue_get_entries(adapters); entry; + i++, entry = entry->next) { + struct btp_adapter *adapter = entry->data; + + rp->indexes[i] = adapter->index; + } + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST, + BTP_INDEX_NON_CONTROLLER, sizeof(*rp) + cnt, rp); +} + +static void btp_gap_read_info(uint8_t index, const void *param, uint16_t length, + void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + struct btp_gap_read_info_rp rp; + const char *str; + uint8_t status = BTP_ERROR_FAIL; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + memset(&rp, 0, sizeof(rp)); + + if (!l_dbus_proxy_get_property(adapter->proxy, "Address", "s", &str)) + goto failed; + + if (str2ba(str, &rp.address) < 0) + goto failed; + + if (!l_dbus_proxy_get_property(adapter->proxy, "Name", "s", &str)) { + goto failed; + } + + snprintf((char *)rp.name, sizeof(rp.name), "%s", str); + snprintf((char *)rp.short_name, sizeof(rp.short_name), "%s", str); + rp.supported_settings = L_CPU_TO_LE32(adapter->supported_settings); + rp.current_settings = L_CPU_TO_LE32(adapter->current_settings); + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_COTROLLER_INFO, index, + sizeof(rp), &rp); + + return; +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void remove_device_setup(struct l_dbus_message *message, + void *user_data) +{ + struct btp_device *device = user_data; + + l_dbus_message_set_arguments(message, "o", + l_dbus_proxy_get_path(device->proxy)); +} + +static void remove_device_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct btp_device *device = user_data; + struct btp_adapter *adapter = find_adapter_by_proxy(proxy); + + if (!adapter) + return; + + if (l_dbus_message_is_error(result)) { + const char *name; + + l_dbus_message_get_error(result, &name, NULL); + + l_error("Failed to remove device %s (%s)", + l_dbus_proxy_get_path(device->proxy), + name); + return; + } + + l_queue_remove(adapter->devices, device); +} + +static void unreg_advertising_setup(struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_message_builder *builder; + + builder = l_dbus_message_builder_new(message); + l_dbus_message_builder_append_basic(builder, 'o', AD_PATH); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void ad_cleanup_service(void *service) +{ + struct service_data *s = service; + + l_free(s->uuid); + l_free(s); +} + +static void ad_cleanup(void) +{ + l_free(ad.local_name); + l_queue_destroy(ad.uuids, l_free); + l_queue_destroy(ad.services, ad_cleanup_service); + l_queue_destroy(ad.manufacturers, l_free); + l_queue_destroy(ad.solicits, l_free); + + memset(&ad, 0, sizeof(ad)); +} + +static void unreg_advertising_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + const char *path = l_dbus_proxy_get_path(proxy); + struct btp_adapter *adapter = find_adapter_by_path(path); + + if (!adapter) + return; + + if (l_dbus_message_is_error(result)) { + const char *name; + + l_dbus_message_get_error(result, &name, NULL); + + l_error("Failed to stop advertising %s (%s)", + l_dbus_proxy_get_path(proxy), name); + return; + } + + if (!l_dbus_object_remove_interface(dbus, AD_PATH, AD_IFACE)) + l_info("Unable to remove ad instance"); + if (!l_dbus_object_remove_interface(dbus, AD_PATH, + L_DBUS_INTERFACE_PROPERTIES)) + l_info("Unable to remove propety instance"); + if (!l_dbus_unregister_interface(dbus, AD_IFACE)) + l_info("Unable to unregister ad interface"); + + ad_cleanup(); +} + +static void unreg_agent_setup(struct l_dbus_message *message, void *user_data) +{ + struct l_dbus_message_builder *builder; + + builder = l_dbus_message_builder_new(message); + + l_dbus_message_builder_append_basic(builder, 'o', AG_PATH); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void reset_unreg_agent_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + if (l_dbus_message_is_error(result)) { + const char *name; + + l_dbus_message_get_error(result, &name, NULL); + + l_error("Failed to unregister agent %s (%s)", + l_dbus_proxy_get_path(proxy), name); + return; + } + + if (!l_dbus_object_remove_interface(dbus, AG_PATH, + L_DBUS_INTERFACE_PROPERTIES)) + l_info("Unable to remove propety instance"); + if (!l_dbus_object_remove_interface(dbus, AG_PATH, AG_IFACE)) + l_info("Unable to remove agent instance"); + if (!l_dbus_unregister_interface(dbus, AG_IFACE)) + l_info("Unable to unregister agent interface"); + + ag.registered = false; +} + +static void btp_gap_reset(uint8_t index, const void *param, uint16_t length, + void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct l_queue_entry *entry; + uint8_t status; + bool prop; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + /* Adapter needs to be powered to be able to remove devices */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop) { + status = BTP_ERROR_FAIL; + goto failed; + } + + for (entry = l_queue_get_entries(adapter->devices); entry; + entry = entry->next) { + struct btp_device *device = entry->data; + + l_dbus_proxy_method_call(adapter->proxy, "RemoveDevice", + remove_device_setup, + remove_device_reply, device, + NULL); + } + + if (adapter->ad_proxy && ad.registered) + if (!l_dbus_proxy_method_call(adapter->ad_proxy, + "UnregisterAdvertisement", + unreg_advertising_setup, + unreg_advertising_reply, + NULL, NULL)) { + status = BTP_ERROR_FAIL; + goto failed; + } + + if (ag.proxy && ag.registered) + if (!l_dbus_proxy_method_call(ag.proxy, "UnregisterAgent", + unreg_agent_setup, + reset_unreg_agent_reply, + NULL, NULL)) { + status = BTP_ERROR_FAIL; + goto failed; + } + + adapter->current_settings = adapter->default_settings; + + /* TODO for we assume all went well */ + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_RESET, index, 0, NULL); + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +struct set_setting_data { + struct btp_adapter *adapter; + uint8_t opcode; + uint32_t setting; + bool value; +}; + +static void set_setting_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, void *user_data) +{ + struct set_setting_data *data = user_data; + struct btp_adapter *adapter = data->adapter; + uint32_t settings; + + if (l_dbus_message_is_error(result)) { + btp_send_error(btp, BTP_GAP_SERVICE, data->adapter->index, + BTP_ERROR_FAIL); + return; + } + + if (data->value) + adapter->current_settings |= data->setting; + else + adapter->current_settings &= ~data->setting; + + settings = L_CPU_TO_LE32(adapter->current_settings); + + btp_send(btp, BTP_GAP_SERVICE, data->opcode, adapter->index, + sizeof(settings), &settings); +} + +static void btp_gap_set_powered(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_set_powered_cp *cp = param; + uint8_t status = BTP_ERROR_FAIL; + struct set_setting_data *data; + + if (length < sizeof(*cp)) + goto failed; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + data = l_new(struct set_setting_data, 1); + data->adapter = adapter; + data->opcode = BTP_OP_GAP_SET_POWERED; + data->setting = BTP_GAP_SETTING_POWERED; + data->value = cp->powered; + + if (l_dbus_proxy_set_property(adapter->proxy, set_setting_reply, + data, l_free, "Powered", "b", + data->value)) + return; + + l_free(data); + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void update_current_settings(struct btp_adapter *adapter, + uint32_t new_settings) +{ + struct btp_new_settings_ev ev; + + adapter->current_settings = new_settings; + + ev.current_settings = L_CPU_TO_LE32(adapter->current_settings); + + btp_send(btp, BTP_GAP_SERVICE, BTP_EV_GAP_NEW_SETTINGS, adapter->index, + sizeof(ev), &ev); +} + +static void btp_gap_set_connectable(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_set_connectable_cp *cp = param; + uint8_t status = BTP_ERROR_FAIL; + uint32_t new_settings; + + if (length < sizeof(*cp)) + goto failed; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + new_settings = adapter->current_settings; + + if (cp->connectable) + new_settings |= BTP_GAP_SETTING_CONNECTABLE; + else + new_settings &= ~BTP_GAP_SETTING_CONNECTABLE; + + update_current_settings(adapter, new_settings); + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_SET_CONNECTABLE, index, + sizeof(new_settings), &new_settings); + + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void btp_gap_set_discoverable(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_set_discoverable_cp *cp = param; + uint8_t status = BTP_ERROR_FAIL; + struct set_setting_data *data; + + if (length < sizeof(*cp)) + goto failed; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + data = l_new(struct set_setting_data, 1); + data->adapter = adapter; + data->opcode = BTP_OP_GAP_SET_DISCOVERABLE; + data->setting = BTP_GAP_SETTING_DISCOVERABLE; + data->value = cp->discoverable; + + if (l_dbus_proxy_set_property(adapter->proxy, set_setting_reply, + data, l_free, "Discoverable", "b", + data->value)) + return; + + l_free(data); + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void btp_gap_set_bondable(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_set_bondable_cp *cp = param; + uint8_t status = BTP_ERROR_FAIL; + struct set_setting_data *data; + + if (length < sizeof(*cp)) + goto failed; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + data = l_new(struct set_setting_data, 1); + data->adapter = adapter; + data->opcode = BTP_OP_GAP_SET_BONDABLE; + data->setting = BTP_GAP_SETTING_BONDABLE; + data->value = cp->bondable; + + if (l_dbus_proxy_set_property(adapter->proxy, set_setting_reply, + data, l_free, "Pairable", "b", + data->value)) + return; + + l_free(data); + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void ad_init(void) +{ + ad.uuids = l_queue_new(); + ad.services = l_queue_new(); + ad.manufacturers = l_queue_new(); + ad.solicits = l_queue_new(); + + ad.local_appearance = UINT16_MAX; +} + +static struct l_dbus_message *ad_release_call(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_message *reply; + + l_dbus_unregister_object(dbus, AD_PATH); + l_dbus_unregister_interface(dbus, AD_IFACE); + + reply = l_dbus_message_new_method_return(message); + l_dbus_message_set_arguments(reply, ""); + + ad_cleanup(); + + return reply; +} + +static bool ad_type_getter(struct l_dbus *dbus, struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + l_dbus_message_builder_append_basic(builder, 's', ad.type); + + return true; +} + +static bool ad_serviceuuids_getter(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct l_queue_entry *entry; + + if (l_queue_isempty(ad.uuids)) + return false; + + l_dbus_message_builder_enter_array(builder, "s"); + + for (entry = l_queue_get_entries(ad.uuids); entry; entry = entry->next) + l_dbus_message_builder_append_basic(builder, 's', entry->data); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool ad_servicedata_getter(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct l_queue_entry *entry; + size_t i; + + if (l_queue_isempty(ad.services)) + return false; + + l_dbus_message_builder_enter_array(builder, "{sv}"); + + for (entry = l_queue_get_entries(ad.services); entry; + entry = entry->next) { + struct service_data *sd = entry->data; + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', sd->uuid); + l_dbus_message_builder_enter_variant(builder, "ay"); + l_dbus_message_builder_enter_array(builder, "y"); + + for (i = 0; i < sd->data.len; i++) + l_dbus_message_builder_append_basic(builder, 'y', + &(sd->data.data[i])); + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); + } + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool ad_manufacturerdata_getter(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct l_queue_entry *entry; + size_t i; + + if (l_queue_isempty(ad.manufacturers)) + return false; + + l_dbus_message_builder_enter_array(builder, "{qv}"); + + for (entry = l_queue_get_entries(ad.manufacturers); entry; + entry = entry->next) { + struct manufacturer_data *md = entry->data; + + l_dbus_message_builder_enter_dict(builder, "qv"); + l_dbus_message_builder_append_basic(builder, 'q', &md->id); + l_dbus_message_builder_enter_variant(builder, "ay"); + l_dbus_message_builder_enter_array(builder, "y"); + + for (i = 0; i < md->data.len; i++) + l_dbus_message_builder_append_basic(builder, 'y', + &(md->data.data[i])); + + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); + } + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool ad_solicituuids_getter(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + const struct l_queue_entry *entry; + + if (l_queue_isempty(ad.solicits)) + return false; + + l_dbus_message_builder_enter_array(builder, "s"); + + for (entry = l_queue_get_entries(ad.solicits); entry; + entry = entry->next) + l_dbus_message_builder_append_basic(builder, 's', entry->data); + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool ad_includes_getter(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + l_dbus_message_builder_enter_array(builder, "s"); + + if (!(ad.tx_power || ad.name || ad.appearance)) + return false; + + if (ad.tx_power) { + const char *str = "tx-power"; + + l_dbus_message_builder_append_basic(builder, 's', str); + } + + if (ad.name) { + const char *str = "local-name"; + + l_dbus_message_builder_append_basic(builder, 's', str); + } + + if (ad.appearance) { + const char *str = "appearance"; + + l_dbus_message_builder_append_basic(builder, 's', str); + } + + l_dbus_message_builder_leave_array(builder); + + return true; +} + +static bool ad_localname_getter(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + if (!ad.local_name) + return false; + + l_dbus_message_builder_append_basic(builder, 's', ad.local_name); + + return true; +} + +static bool ad_appearance_getter(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + if (!ad.local_appearance) + return false; + + l_dbus_message_builder_append_basic(builder, 'q', &ad.local_appearance); + + return true; +} + +static bool ad_duration_getter(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + if (!ad.duration) + return false; + + l_dbus_message_builder_append_basic(builder, 'q', &ad.duration); + + return true; +} + +static bool ad_timeout_getter(struct l_dbus *dbus, + struct l_dbus_message *message, + struct l_dbus_message_builder *builder, + void *user_data) +{ + if (!ad.timeout) + return false; + + l_dbus_message_builder_append_basic(builder, 'q', &ad.timeout); + + return true; +} + +static void setup_ad_interface(struct l_dbus_interface *interface) +{ + l_dbus_interface_method(interface, "Release", + L_DBUS_METHOD_FLAG_NOREPLY, + ad_release_call, "", ""); + l_dbus_interface_property(interface, "Type", 0, "s", ad_type_getter, + NULL); + l_dbus_interface_property(interface, "ServiceUUIDs", 0, "as", + ad_serviceuuids_getter, NULL); + l_dbus_interface_property(interface, "ServiceData", 0, "a{sv}", + ad_servicedata_getter, NULL); + l_dbus_interface_property(interface, "ManufacturerData", 0, + "a{qv}", ad_manufacturerdata_getter, + NULL); + l_dbus_interface_property(interface, "SolicitUUIDs", 0, "as", + ad_solicituuids_getter, NULL); + l_dbus_interface_property(interface, "Includes", 0, "as", + ad_includes_getter, NULL); + l_dbus_interface_property(interface, "LocalName", 0, "s", + ad_localname_getter, NULL); + l_dbus_interface_property(interface, "Appearance", 0, "q", + ad_appearance_getter, NULL); + l_dbus_interface_property(interface, "Duration", 0, "q", + ad_duration_getter, NULL); + l_dbus_interface_property(interface, "Timeout", 0, "q", + ad_timeout_getter, NULL); +} + +static void start_advertising_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + const char *path = l_dbus_proxy_get_path(proxy); + struct btp_adapter *adapter = find_adapter_by_path(path); + uint32_t new_settings; + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + return; + } + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to start advertising (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter->index, + BTP_ERROR_FAIL); + return; + } + + new_settings = adapter->current_settings; + new_settings |= BTP_GAP_SETTING_ADVERTISING; + update_current_settings(adapter, new_settings); + + ad.registered = true; + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_START_ADVERTISING, + adapter->index, sizeof(new_settings), + &new_settings); +} + +static void create_advertising_data(uint8_t adv_data_len, const uint8_t *data) +{ + const uint8_t *ad_data; + uint8_t ad_type, ad_len; + uint8_t remaining_data_len = adv_data_len; + + while (remaining_data_len) { + ad_type = data[adv_data_len - remaining_data_len]; + ad_len = data[adv_data_len - remaining_data_len + 1]; + ad_data = &data[adv_data_len - remaining_data_len + 2]; + + switch (ad_type) { + case AD_TYPE_INCOMPLETE_UUID16_SERVICE_LIST: + { + char *uuid = dupuuid2str(ad_data, 16); + + l_queue_push_tail(ad.uuids, uuid); + + break; + } + case AD_TYPE_SHORT_NAME: + ad.local_name = malloc(ad_len + 1); + memcpy(ad.local_name, ad_data, ad_len); + ad.local_name[ad_len] = '\0'; + + break; + case AD_TYPE_TX_POWER: + ad.tx_power = true; + + /* XXX Value is ommited cause, stack fills it */ + + break; + case AD_TYPE_SERVICE_DATA_UUID16: + { + struct service_data *sd; + + sd = l_new(struct service_data, 1); + sd->uuid = dupuuid2str(ad_data, 16); + sd->data.len = ad_len - 2; + memcpy(sd->data.data, ad_data + 2, sd->data.len); + + l_queue_push_tail(ad.services, sd); + + break; + } + case AD_TYPE_APPEARANCE: + memcpy(&ad.local_appearance, ad_data, ad_len); + + break; + case AD_TYPE_MANUFACTURER_DATA: + { + struct manufacturer_data *md; + + md = l_new(struct manufacturer_data, 1); + /* The first 2 octets contain the Company Identifier + * Code followed by additional manufacturer specific + * data. + */ + memcpy(&md->id, ad_data, 2); + md->data.len = ad_len - 2; + memcpy(md->data.data, ad_data + 2, md->data.len); + + l_queue_push_tail(ad.manufacturers, md); + + break; + } + case AD_TYPE_SOLICIT_UUID16_SERVICE_LIST: + { + char *uuid = dupuuid2str(ad_data, 16); + + l_queue_push_tail(ad.solicits, uuid); + + break; + } + default: + l_info("Unsupported advertising data type"); + + break; + } + /* Advertising entity data len + advertising entity header + * (type, len) + */ + remaining_data_len -= ad_len + 2; + } +} + +static void create_scan_response(uint8_t scan_rsp_len, const uint8_t *data) +{ + /* TODO */ +} + +static void start_advertising_setup(struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_message_builder *builder; + + builder = l_dbus_message_builder_new(message); + l_dbus_message_builder_append_basic(builder, 'o', AD_PATH); + l_dbus_message_builder_enter_array(builder, "{sv}"); + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_leave_dict(builder); + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void btp_gap_start_advertising(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_start_adv_cp *cp = param; + uint8_t status = BTP_ERROR_FAIL; + bool prop; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + /* Adapter needs to be powered to be able to advertise */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop || ad.registered) + goto failed; + + if (!l_dbus_register_interface(dbus, AD_IFACE, setup_ad_interface, NULL, + false)) { + l_info("Unable to register ad interface"); + goto failed; + } + + if (!l_dbus_object_add_interface(dbus, AD_PATH, AD_IFACE, NULL)) { + l_info("Unable to instantiate ad interface"); + + if (!l_dbus_unregister_interface(dbus, AD_IFACE)) + l_info("Unable to unregister ad interface"); + + goto failed; + } + + if (!l_dbus_object_add_interface(dbus, AD_PATH, + L_DBUS_INTERFACE_PROPERTIES, + NULL)) { + l_info("Unable to instantiate the properties interface"); + + if (!l_dbus_object_remove_interface(dbus, AD_PATH, AD_IFACE)) + l_info("Unable to remove ad instance"); + if (!l_dbus_unregister_interface(dbus, AD_IFACE)) + l_info("Unable to unregister ad interface"); + + goto failed; + } + + ad_init(); + + if (adapter->current_settings & BTP_GAP_SETTING_CONNECTABLE) + ad.type = "peripheral"; + else + ad.type = "broadcast"; + + if (cp->adv_data_len > 0) + create_advertising_data(cp->adv_data_len, cp->data); + if (cp->scan_rsp_len > 0) + create_scan_response(cp->scan_rsp_len, + cp->data + cp->scan_rsp_len); + + if (!l_dbus_proxy_method_call(adapter->ad_proxy, + "RegisterAdvertisement", + start_advertising_setup, + start_advertising_reply, + NULL, NULL)) { + if (!l_dbus_object_remove_interface(dbus, AD_PATH, AD_IFACE)) + l_info("Unable to remove ad instance"); + if (!l_dbus_unregister_interface(dbus, AD_IFACE)) + l_info("Unable to unregister ad interface"); + + goto failed; + } + + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void stop_advertising_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + const char *path = l_dbus_proxy_get_path(proxy); + struct btp_adapter *adapter = find_adapter_by_path(path); + uint32_t new_settings; + + if (!adapter) + return; + + if (l_dbus_message_is_error(result)) { + const char *name; + + l_dbus_message_get_error(result, &name, NULL); + + l_error("Failed to stop advertising %s (%s)", + l_dbus_proxy_get_path(proxy), name); + return; + } + + if (!l_dbus_object_remove_interface(dbus, AD_PATH, AD_IFACE)) + l_info("Unable to remove ad instance"); + if (!l_dbus_object_remove_interface(dbus, AD_PATH, + L_DBUS_INTERFACE_PROPERTIES)) + l_info("Unable to remove propety instance"); + if (!l_dbus_unregister_interface(dbus, AD_IFACE)) + l_info("Unable to unregister ad interface"); + + new_settings = adapter->current_settings; + new_settings &= ~BTP_GAP_SETTING_ADVERTISING; + update_current_settings(adapter, new_settings); + + ad_cleanup(); + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_STOP_ADVERTISING, + adapter->index, sizeof(new_settings), + &new_settings); +} + +static void btp_gap_stop_advertising(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + uint8_t status = BTP_ERROR_FAIL; + bool prop; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop || !adapter->ad_proxy || !ad.registered) + goto failed; + + if (!l_dbus_proxy_method_call(adapter->ad_proxy, + "UnregisterAdvertisement", + unreg_advertising_setup, + stop_advertising_reply, + NULL, NULL)) + goto failed; + + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void start_discovery_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_proxy(proxy); + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + return; + } + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to start discovery (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter->index, + BTP_ERROR_FAIL); + return; + } + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_START_DISCOVERY, + adapter->index, 0, NULL); +} + +static void set_discovery_filter_setup(struct l_dbus_message *message, + void *user_data) +{ + uint8_t flags = L_PTR_TO_UINT(user_data); + struct l_dbus_message_builder *builder; + + if (!(flags & (BTP_GAP_DISCOVERY_FLAG_LE | + BTP_GAP_DISCOVERY_FLAG_BREDR))) { + l_info("Failed to start discovery - no transport set"); + return; + } + + builder = l_dbus_message_builder_new(message); + + l_dbus_message_builder_enter_array(builder, "{sv}"); + l_dbus_message_builder_enter_dict(builder, "sv"); + + /* Be in observer mode or in general mode (default in Bluez) */ + if (flags & BTP_GAP_DISCOVERY_FLAG_OBSERVATION) { + l_dbus_message_builder_append_basic(builder, 's', "Transport"); + l_dbus_message_builder_enter_variant(builder, "s"); + + if (flags & (BTP_GAP_DISCOVERY_FLAG_LE | + BTP_GAP_DISCOVERY_FLAG_BREDR)) + l_dbus_message_builder_append_basic(builder, 's', + "auto"); + else if (flags & BTP_GAP_DISCOVERY_FLAG_LE) + l_dbus_message_builder_append_basic(builder, 's', "le"); + else if (flags & BTP_GAP_DISCOVERY_FLAG_BREDR) + l_dbus_message_builder_append_basic(builder, 's', + "bredr"); + + l_dbus_message_builder_leave_variant(builder); + } + + l_dbus_message_builder_leave_dict(builder); + l_dbus_message_builder_leave_array(builder); + + /* TODO add passive, limited discovery */ + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void set_discovery_filter_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_proxy(proxy); + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + return; + } + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to set discovery filter (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter->index, + BTP_ERROR_FAIL); + return; + } + + l_dbus_proxy_method_call(adapter->proxy, "StartDiscovery", NULL, + start_discovery_reply, NULL, NULL); +} + +static void btp_gap_start_discovery(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_start_discovery_cp *cp = param; + bool prop; + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, index, + BTP_ERROR_INVALID_INDEX); + return; + } + + /* Adapter needs to be powered to start discovery */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop) { + btp_send_error(btp, BTP_GAP_SERVICE, index, BTP_ERROR_FAIL); + return; + } + + l_dbus_proxy_method_call(adapter->proxy, "SetDiscoveryFilter", + set_discovery_filter_setup, + set_discovery_filter_reply, + L_UINT_TO_PTR(cp->flags), NULL); +} + +static void clear_discovery_filter_setup(struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_message_builder *builder; + + builder = l_dbus_message_builder_new(message); + + /* Clear discovery filter setup */ + l_dbus_message_builder_enter_array(builder, "{sv}"); + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_leave_dict(builder); + l_dbus_message_builder_leave_array(builder); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void clear_discovery_filter_reaply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_proxy(proxy); + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + return; + } + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to set discovery filter (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter->index, + BTP_ERROR_FAIL); + return; + } + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_STOP_DISCOVERY, + adapter->index, 0, NULL); +} + +static void stop_discovery_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_proxy(proxy); + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + return; + } + + if (l_dbus_message_is_error(result)) { + const char *name; + + l_dbus_message_get_error(result, &name, NULL); + l_error("Failed to stop discovery (%s)", name); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter->index, + BTP_ERROR_FAIL); + return; + } + + l_dbus_proxy_method_call(adapter->proxy, "SetDiscoveryFilter", + clear_discovery_filter_setup, + clear_discovery_filter_reaply, + NULL, NULL); +} + +static void btp_gap_stop_discovery(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + bool prop; + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, index, + BTP_ERROR_INVALID_INDEX); + return; + } + + /* Adapter needs to be powered to be able to remove devices */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop) { + btp_send_error(btp, BTP_GAP_SERVICE, index, BTP_ERROR_FAIL); + return; + } + + l_dbus_proxy_method_call(adapter->proxy, "StopDiscovery", NULL, + stop_discovery_reply, NULL, NULL); +} + +static void connect_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, void *user_data) +{ + uint8_t adapter_index = L_PTR_TO_UINT(user_data); + struct btp_adapter *adapter = find_adapter_by_index(adapter_index); + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + return; + } + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to connect (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter_index, + BTP_ERROR_FAIL); + return; + } + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_CONNECT, adapter_index, 0, + NULL); +} + +struct connect_device_data { + bdaddr_t addr; + uint8_t addr_type; +}; + +static void connect_device_destroy(void *connect_device_data) +{ + l_free(connect_device_data); +} + +static void connect_device_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_proxy(proxy); + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + return; + } + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to connect device (%s), %s", name, desc); + + return; + } +} + +static void connect_device_setup(struct l_dbus_message *message, + void *user_data) +{ + struct connect_device_data *cdd = user_data; + struct l_dbus_message_builder *builder; + char str_addr[18]; + + ba2str(&cdd->addr, str_addr); + + builder = l_dbus_message_builder_new(message); + + l_dbus_message_builder_enter_array(builder, "{sv}"); + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', "Address"); + l_dbus_message_builder_enter_variant(builder, "s"); + l_dbus_message_builder_append_basic(builder, 's', str_addr); + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); + + l_dbus_message_builder_enter_dict(builder, "sv"); + l_dbus_message_builder_append_basic(builder, 's', "AddressType"); + l_dbus_message_builder_enter_variant(builder, "s"); + if (cdd->addr_type == BTP_GAP_ADDR_RANDOM) + l_dbus_message_builder_append_basic(builder, 's', "random"); + else + l_dbus_message_builder_append_basic(builder, 's', "public"); + l_dbus_message_builder_leave_variant(builder); + l_dbus_message_builder_leave_dict(builder); + + l_dbus_message_builder_leave_array(builder); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void btp_gap_connect(uint8_t index, const void *param, uint16_t length, + void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_connect_cp *cp = param; + struct btp_device *device; + struct connect_device_data *cdd; + bool prop; + uint8_t status = BTP_ERROR_FAIL; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + /* Adapter needs to be powered to be able to connect */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop) + goto failed; + + device = find_device_by_address(adapter, &cp->address, + cp->address_type); + + if (!device) { + cdd = l_new(struct connect_device_data, 1); + memcpy(&cdd->addr, &cp->address, sizeof(cdd->addr)); + cdd->addr_type = cp->address_type; + + l_dbus_proxy_method_call(adapter->proxy, "ConnectDevice", + connect_device_setup, + connect_device_reply, + cdd, + connect_device_destroy); + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_CONNECT, + adapter->index, 0, NULL); + return; + } + + l_dbus_proxy_method_call(device->proxy, "Connect", NULL, connect_reply, + L_UINT_TO_PTR(adapter->index), NULL); + + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void disconnect_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, void *user_data) +{ + uint8_t adapter_index = L_PTR_TO_UINT(user_data); + struct btp_adapter *adapter = find_adapter_by_index(adapter_index); + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + return; + } + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to disconnect (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter_index, + BTP_ERROR_FAIL); + return; + } + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_DISCONNECT, adapter_index, 0, + NULL); +} + +static void btp_gap_disconnect(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_disconnect_cp *cp = param; + uint8_t status = BTP_ERROR_FAIL; + struct btp_device *device; + bool prop; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + /* Adapter needs to be powered to be able to connect */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop) + goto failed; + + device = find_device_by_address(adapter, &cp->address, + cp->address_type); + + if (!device) + goto failed; + + l_dbus_proxy_method_call(device->proxy, "Disconnect", NULL, + disconnect_reply, + L_UINT_TO_PTR(adapter->index), NULL); + + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static struct l_dbus_message *ag_release_call(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_message *reply; + + reply = l_dbus_message_new_method_return(message); + l_dbus_message_set_arguments(reply, ""); + + return reply; +} + +static struct l_dbus_message *ag_request_passkey_call(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct btp_gap_passkey_req_ev ev; + struct btp_device *device; + struct btp_adapter *adapter; + const char *path, *str_addr, *str_addr_type; + + l_dbus_message_get_arguments(message, "o", &path); + + device = find_device_by_path(path); + + if (!l_dbus_proxy_get_property(device->proxy, "Address", "s", &str_addr) + || !l_dbus_proxy_get_property(device->proxy, "AddressType", "s", + &str_addr_type)) { + l_info("Cannot get device properties"); + + return NULL; + } + + ev.address_type = strcmp(str_addr_type, "public") ? + BTP_GAP_ADDR_RANDOM : + BTP_GAP_ADDR_PUBLIC; + if (!str2ba(str_addr, &ev.address)) + return NULL; + + adapter = find_adapter_by_device(device); + + ag.pending_req = l_dbus_message_ref(message); + + btp_send(btp, BTP_GAP_SERVICE, BTP_EV_GAP_PASSKEY_REQUEST, + adapter->index, sizeof(ev), &ev); + + return NULL; +} + +static struct l_dbus_message *ag_display_passkey_call(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct btp_gap_passkey_display_ev ev; + struct btp_device *device; + struct btp_adapter *adapter; + struct l_dbus_message *reply; + const char *path, *str_addr, *str_addr_type; + uint32_t passkey; + uint16_t entered; + + reply = l_dbus_message_new_method_return(message); + l_dbus_message_set_arguments(reply, ""); + + l_dbus_message_get_arguments(message, "ouq", &path, &passkey, &entered); + + device = find_device_by_path(path); + + if (!l_dbus_proxy_get_property(device->proxy, "Address", "s", &str_addr) + || !l_dbus_proxy_get_property(device->proxy, "AddressType", "s", + &str_addr_type)) { + l_info("Cannot get device properties"); + + return reply; + } + + ev.passkey = L_CPU_TO_LE32(passkey); + ev.address_type = strcmp(str_addr_type, "public") ? + BTP_GAP_ADDR_RANDOM : + BTP_GAP_ADDR_PUBLIC; + if (str2ba(str_addr, &ev.address) < 0) { + l_info("Incorrect device addres"); + + return reply; + } + + adapter = find_adapter_by_device(device); + + btp_send(btp, BTP_GAP_SERVICE, BTP_EV_GAP_PASSKEY_DISPLAY, + adapter->index, sizeof(ev), &ev); + + return reply; +} + +static struct l_dbus_message *ag_request_confirmation_call(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct btp_gap_passkey_confirm_ev ev; + struct btp_device *device; + struct btp_adapter *adapter; + const char *path, *str_addr, *str_addr_type; + uint32_t passkey; + + l_dbus_message_get_arguments(message, "ou", &path, &passkey); + + device = find_device_by_path(path); + + if (!l_dbus_proxy_get_property(device->proxy, "Address", "s", &str_addr) + || !l_dbus_proxy_get_property(device->proxy, "AddressType", "s", + &str_addr_type)) { + l_info("Cannot get device properties"); + + return NULL; + } + + ev.passkey = L_CPU_TO_LE32(passkey); + ev.address_type = strcmp(str_addr_type, "public") ? + BTP_GAP_ADDR_RANDOM : + BTP_GAP_ADDR_PUBLIC; + if (str2ba(str_addr, &ev.address) < 0) { + l_info("Incorrect device address"); + + return NULL; + } + + adapter = find_adapter_by_device(device); + + ag.pending_req = l_dbus_message_ref(message); + + btp_send(btp, BTP_GAP_SERVICE, BTP_EV_GAP_PASSKEY_CONFIRM, + adapter->index, sizeof(ev), &ev); + + return NULL; +} + +static struct l_dbus_message *ag_request_authorization_call(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_message *reply; + + reply = l_dbus_message_new_method_return(message); + l_dbus_message_set_arguments(reply, ""); + + return reply; +} + +static struct l_dbus_message *ag_authorize_service_call(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_message *reply; + + reply = l_dbus_message_new_method_return(message); + l_dbus_message_set_arguments(reply, ""); + + return reply; +} + +static struct l_dbus_message *ag_cancel_call(struct l_dbus *dbus, + struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_message *reply; + + reply = l_dbus_message_new_method_return(message); + l_dbus_message_set_arguments(reply, ""); + + return reply; +} + +static void setup_ag_interface(struct l_dbus_interface *iface) +{ + l_dbus_interface_method(iface, "Release", 0, ag_release_call, "", ""); + l_dbus_interface_method(iface, "RequestPasskey", 0, + ag_request_passkey_call, "u", "o", + "passkey", "device"); + l_dbus_interface_method(iface, "DisplayPasskey", 0, + ag_display_passkey_call, "", "ouq", + "device", "passkey", "entered"); + l_dbus_interface_method(iface, "RequestConfirmation", 0, + ag_request_confirmation_call, "", "ou", + "device", "passkey"); + l_dbus_interface_method(iface, "RequestAuthorization", 0, + ag_request_authorization_call, "", "o", + "device"); + l_dbus_interface_method(iface, "AuthorizeService", 0, + ag_authorize_service_call, "", "os", + "device", "uuid"); + l_dbus_interface_method(iface, "Cancel", 0, ag_cancel_call, "", ""); +} + +struct set_io_capabilities_data { + uint8_t capa; + struct btp_adapter *adapter; +}; + +static void set_io_capabilities_setup(struct l_dbus_message *message, + void *user_data) +{ + struct set_io_capabilities_data *sicd = user_data; + struct l_dbus_message_builder *builder; + char *capa_str; + + builder = l_dbus_message_builder_new(message); + + l_dbus_message_builder_append_basic(builder, 'o', AG_PATH); + + switch (sicd->capa) { + case BTP_GAP_IOCAPA_DISPLAY_ONLY: + capa_str = "DisplayOnly"; + break; + case BTP_GAP_IOCAPA_DISPLAY_YESNO: + capa_str = "DisplayYesNo"; + break; + case BTP_GAP_IOCAPA_KEYBOARD_ONLY: + capa_str = "KeyboardOnly"; + break; + case BTP_GAP_IOCAPA_KEYBOARD_DISPLAY: + capa_str = "KeyboardDisplay"; + break; + case BTP_GAP_IOCAPA_NO_INPUT_NO_OUTPUT: + default: + capa_str = "NoInputNoOutput"; + break; + } + + l_dbus_message_builder_append_basic(builder, 's', capa_str); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void reg_def_req_default_agent_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + if (!l_dbus_object_remove_interface(dbus, AG_PATH, AG_IFACE)) + l_info("Unable to remove agent instance"); + if (!l_dbus_unregister_interface(dbus, AG_IFACE)) + l_info("Unable to unregister agent interface"); + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to request default agent (%s), %s", name, desc); + + btp_send_error(btp, BTP_CORE_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + return; + } + + register_gap_service(); + gap_service_registered = true; + + ag.registered = true; + + btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_REGISTER, + BTP_INDEX_NON_CONTROLLER, 0, NULL); +} + +static void set_io_req_default_agent_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct btp_adapter *adapter = user_data; + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + goto failed; + } + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to set io capabilities (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter->index, + BTP_ERROR_FAIL); + goto failed; + } + + ag.registered = true; + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_SET_IO_CAPA, + adapter->index, 0, NULL); + + return; + +failed: + if (!l_dbus_object_remove_interface(dbus, AG_PATH, AG_IFACE)) + l_info("Unable to remove agent instance"); + if (!l_dbus_unregister_interface(dbus, AG_IFACE)) + l_info("Unable to unregister agent interface"); +} + +static void request_default_agent_setup(struct l_dbus_message *message, + void *user_data) +{ + struct l_dbus_message_builder *builder; + + builder = l_dbus_message_builder_new(message); + + l_dbus_message_builder_append_basic(builder, 'o', AG_PATH); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void set_io_capabilities_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct set_io_capabilities_data *sicd = user_data; + + if (!sicd->adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + goto failed; + } + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to set io capabilities (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, sicd->adapter->index, + BTP_ERROR_FAIL); + goto failed; + } + + if (l_dbus_proxy_method_call(ag.proxy, "RequestDefaultAgent", + request_default_agent_setup, + set_io_req_default_agent_reply, + sicd->adapter, NULL)) + return; + +failed: + if (!l_dbus_object_remove_interface(dbus, AG_PATH, AG_IFACE)) + l_info("Unable to remove agent instance"); + if (!l_dbus_unregister_interface(dbus, AG_IFACE)) + l_info("Unable to unregister agent interface"); +} + +static void register_default_agent_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + const char *name, *desc; + + if (l_dbus_message_is_error(result)) { + if (!l_dbus_object_remove_interface(dbus, AG_PATH, AG_IFACE)) + l_info("Unable to remove agent instance"); + if (!l_dbus_unregister_interface(dbus, AG_IFACE)) + l_info("Unable to unregister agent interface"); + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to register default agent (%s), %s", name, + desc); + return; + } + + if (!l_dbus_proxy_method_call(ag.proxy, "RequestDefaultAgent", + request_default_agent_setup, + reg_def_req_default_agent_reply, + NULL, NULL)) { + if (!l_dbus_object_remove_interface(dbus, AG_PATH, AG_IFACE)) + l_info("Unable to remove agent instance"); + if (!l_dbus_unregister_interface(dbus, AG_IFACE)) + l_info("Unable to unregister agent interface"); + } +} + +static void set_io_capabilities_destroy(void *user_data) +{ + l_free(user_data); +} + +static bool register_default_agent(struct btp_adapter *adapter, uint8_t capa, + l_dbus_client_proxy_result_func_t set_io_cb) +{ + struct set_io_capabilities_data *data; + + if (!l_dbus_register_interface(dbus, AG_IFACE, setup_ag_interface, NULL, + false)) { + l_info("Unable to register agent interface"); + return false; + } + + if (!l_dbus_object_add_interface(dbus, AG_PATH, AG_IFACE, NULL)) { + l_info("Unable to instantiate agent interface"); + + if (!l_dbus_unregister_interface(dbus, AG_IFACE)) + l_info("Unable to unregister agent interface"); + + return false; + } + + if (!l_dbus_object_add_interface(dbus, AG_PATH, + L_DBUS_INTERFACE_PROPERTIES, + NULL)) { + l_info("Unable to instantiate the ag properties interface"); + + if (!l_dbus_object_remove_interface(dbus, AG_PATH, AG_IFACE)) + l_info("Unable to remove agent instance"); + if (!l_dbus_unregister_interface(dbus, AG_IFACE)) + l_info("Unable to unregister agent interface"); + + return false; + } + + data = l_new(struct set_io_capabilities_data, 1); + data->adapter = adapter; + data->capa = capa; + + if (!l_dbus_proxy_method_call(ag.proxy, "RegisterAgent", + set_io_capabilities_setup, set_io_cb, + data, set_io_capabilities_destroy)) { + if (!l_dbus_object_remove_interface(dbus, AG_PATH, AG_IFACE)) + l_info("Unable to remove agent instance"); + if (!l_dbus_unregister_interface(dbus, AG_IFACE)) + l_info("Unable to unregister agent interface"); + + return false; + } + + return true; +} + +struct rereg_unreg_agent_data { + struct btp_adapter *adapter; + l_dbus_client_proxy_result_func_t cb; + uint8_t capa; +}; + +static void rereg_unreg_agent_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, + void *user_data) +{ + struct rereg_unreg_agent_data *ruad = user_data; + + if (l_dbus_message_is_error(result)) { + const char *name; + + l_dbus_message_get_error(result, &name, NULL); + + l_error("Failed to unregister agent %s (%s)", + l_dbus_proxy_get_path(proxy), name); + return; + } + + if (!l_dbus_object_remove_interface(dbus, AG_PATH, + L_DBUS_INTERFACE_PROPERTIES)) + l_info("Unable to remove propety instance"); + if (!l_dbus_object_remove_interface(dbus, AG_PATH, AG_IFACE)) + l_info("Unable to remove agent instance"); + if (!l_dbus_unregister_interface(dbus, AG_IFACE)) + l_info("Unable to unregister agent interface"); + + ag.registered = false; + + if (!register_default_agent(ruad->adapter, ruad->capa, ruad->cb)) + btp_send_error(btp, BTP_GAP_SERVICE, ruad->adapter->index, + BTP_ERROR_FAIL); +} + +static void rereg_unreg_agent_destroy(void *rereg_unreg_agent_data) +{ + l_free(rereg_unreg_agent_data); +} + +static void btp_gap_set_io_capabilities(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_set_io_capa_cp *cp = param; + uint8_t status = BTP_ERROR_FAIL; + struct rereg_unreg_agent_data *data; + bool prop; + + switch (cp->capa) { + case BTP_GAP_IOCAPA_DISPLAY_ONLY: + case BTP_GAP_IOCAPA_DISPLAY_YESNO: + case BTP_GAP_IOCAPA_KEYBOARD_ONLY: + case BTP_GAP_IOCAPA_NO_INPUT_NO_OUTPUT: + case BTP_GAP_IOCAPA_KEYBOARD_DISPLAY: + break; + default: + l_error("Wrong iocapa given!"); + + goto failed; + } + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + /* Adapter needs to be powered to be able to set io cap */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop) + goto failed; + + if (ag.registered) { + data = l_new(struct rereg_unreg_agent_data, 1); + data->adapter = adapter; + data->capa = cp->capa; + data->cb = set_io_capabilities_reply; + + if (!l_dbus_proxy_method_call(ag.proxy, "UnregisterAgent", + unreg_agent_setup, + rereg_unreg_agent_reply, data, + rereg_unreg_agent_destroy)) + goto failed; + + return; + } + + if (!register_default_agent(adapter, cp->capa, + set_io_capabilities_reply)) + goto failed; + + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void pair_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, void *user_data) +{ + uint8_t adapter_index = L_PTR_TO_UINT(user_data); + struct btp_adapter *adapter = find_adapter_by_index(adapter_index); + + if (!adapter) + return; + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to pair (%s), %s", name, desc); + + return; + } +} + +static void btp_gap_pair(uint8_t index, const void *param, uint16_t length, + void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_pair_cp *cp = param; + uint8_t status = BTP_ERROR_FAIL; + struct btp_device *device; + bool prop; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + /* Adapter needs to be powered to be able to pair */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop) + goto failed; + + device = find_device_by_address(adapter, &cp->address, + cp->address_type); + + if (!device) + goto failed; + + /* This command is asynchronous, send reply immediatelly to not block + * pairing process eg. passkey request. + */ + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_PAIR, adapter->index, 0, + NULL); + + l_dbus_proxy_method_call(device->proxy, "Pair", NULL, pair_reply, + L_UINT_TO_PTR(adapter->index), NULL); + + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void unpair_reply(struct l_dbus_proxy *proxy, + struct l_dbus_message *result, void *user_data) +{ + struct btp_device *device = user_data; + struct btp_adapter *adapter = find_adapter_by_device(device); + + if (!adapter) { + btp_send_error(btp, BTP_GAP_SERVICE, BTP_INDEX_NON_CONTROLLER, + BTP_ERROR_FAIL); + return; + } + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to unpair (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter->index, + BTP_ERROR_FAIL); + return; + } + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_UNPAIR, adapter->index, 0, + NULL); +} + +static void unpair_setup(struct l_dbus_message *message, void *user_data) +{ + struct btp_device *device = user_data; + const char *path = l_dbus_proxy_get_path(device->proxy); + struct l_dbus_message_builder *builder; + + builder = l_dbus_message_builder_new(message); + + l_dbus_message_builder_append_basic(builder, 'o', path); + + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); +} + +static void btp_gap_unpair(uint8_t index, const void *param, uint16_t length, + void *user_data) +{ + struct btp_adapter *adapter = find_adapter_by_index(index); + const struct btp_gap_pair_cp *cp = param; + uint8_t status = BTP_ERROR_FAIL; + struct btp_device *device; + bool prop; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + /* Adapter needs to be powered to be able to unpair */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop) + goto failed; + + device = find_device_by_address(adapter, &cp->address, + cp->address_type); + + if (!device) + goto failed; + + /* There is no direct unpair method, removing device will clear pairing + * information. + */ + l_dbus_proxy_method_call(adapter->proxy, "RemoveDevice", unpair_setup, + unpair_reply, device, NULL); + + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void passkey_entry_rsp_reply(struct l_dbus_message *result, + void *user_data) +{ + struct btp_adapter *adapter = user_data; + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to reply with passkey (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter->index, + BTP_ERROR_FAIL); + return; + } + + l_dbus_message_unref(ag.pending_req); + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_PASSKEY_ENTRY_RSP, + adapter->index, 0, NULL); +} + +static void btp_gap_passkey_entry_rsp(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + const struct btp_gap_passkey_entry_rsp_cp *cp = param; + struct btp_adapter *adapter = find_adapter_by_index(index); + struct l_dbus_message_builder *builder; + uint8_t status = BTP_ERROR_FAIL; + uint32_t passkey = L_CPU_TO_LE32(cp->passkey); + bool prop; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + /* Adapter needs to be powered to be able to response with passkey */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop || !ag.pending_req) + goto failed; + + builder = l_dbus_message_builder_new(ag.pending_req); + l_dbus_message_builder_append_basic(builder, 'u', &passkey); + l_dbus_message_builder_finalize(builder); + l_dbus_message_builder_destroy(builder); + + l_dbus_send_with_reply(dbus, ag.pending_req, passkey_entry_rsp_reply, + adapter, NULL); + + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void passkey_confirm_rsp_reply(struct l_dbus_message *result, + void *user_data) +{ + struct btp_adapter *adapter = user_data; + + if (l_dbus_message_is_error(result)) { + const char *name, *desc; + + l_dbus_message_get_error(result, &name, &desc); + l_error("Failed to confirm passkey (%s), %s", name, desc); + + btp_send_error(btp, BTP_GAP_SERVICE, adapter->index, + BTP_ERROR_FAIL); + return; + } + + l_dbus_message_unref(ag.pending_req); + + btp_send(btp, BTP_GAP_SERVICE, BTP_OP_GAP_PASSKEY_CONFIRM_RSP, + adapter->index, 0, NULL); +} + +static void btp_gap_confirm_entry_rsp(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + const struct btp_gap_passkey_confirm_rsp_cp *cp = param; + struct btp_adapter *adapter = find_adapter_by_index(index); + struct l_dbus_message *reply; + uint8_t status = BTP_ERROR_FAIL; + bool prop; + + if (!adapter) { + status = BTP_ERROR_INVALID_INDEX; + goto failed; + } + + /* Adapter needs to be powered to be able to confirm passkey */ + if (!l_dbus_proxy_get_property(adapter->proxy, "Powered", "b", &prop) || + !prop || !ag.pending_req) + goto failed; + + if (cp->match) { + reply = l_dbus_message_new_method_return(ag.pending_req); + l_dbus_message_set_arguments(reply, ""); + } else { + reply = l_dbus_message_new_error(ag.pending_req, + "org.bluez.Error.Rejected", + "Passkey missmatch"); + } + + l_dbus_send_with_reply(dbus, ag.pending_req, passkey_confirm_rsp_reply, + adapter, NULL); + + return; + +failed: + btp_send_error(btp, BTP_GAP_SERVICE, index, status); +} + +static void btp_gap_device_found_ev(struct l_dbus_proxy *proxy) +{ + struct btp_device *device = find_device_by_proxy(proxy); + struct btp_adapter *adapter = find_adapter_by_device(device); + struct btp_device_found_ev ev; + struct btp_gap_device_connected_ev ev_conn; + const char *str, *addr_str; + int16_t rssi; + uint8_t address_type; + bool connected; + + if (!l_dbus_proxy_get_property(proxy, "Address", "s", &addr_str) || + str2ba(addr_str, &ev.address) < 0) + return; + + if (!l_dbus_proxy_get_property(proxy, "AddressType", "s", &str)) + return; + + address_type = strcmp(str, "public") ? BTP_GAP_ADDR_RANDOM : + BTP_GAP_ADDR_PUBLIC; + ev.address_type = address_type; + + if (!l_dbus_proxy_get_property(proxy, "RSSI", "n", &rssi)) + ev.rssi = 0x81; + else + ev.rssi = rssi; + + /* TODO Temporary set all flags */ + ev.flags = (BTP_EV_GAP_DEVICE_FOUND_FLAG_RSSI | + BTP_EV_GAP_DEVICE_FOUND_FLAG_AD | + BTP_EV_GAP_DEVICE_FOUND_FLAG_SR); + + /* TODO Add eir to device found event */ + ev.eir_len = 0; + + btp_send(btp, BTP_GAP_SERVICE, BTP_EV_GAP_DEVICE_FOUND, adapter->index, + sizeof(ev) + ev.eir_len, &ev); + + if (l_dbus_proxy_get_property(proxy, "Connected", "b", &connected) && + connected) { + ev_conn.address_type = address_type; + str2ba(addr_str, &ev_conn.address); + + btp_send(btp, BTP_GAP_SERVICE, BTP_EV_GAP_DEVICE_CONNECTED, + adapter->index, sizeof(ev_conn), &ev_conn); + } +} + +static void btp_gap_device_connection_ev(struct l_dbus_proxy *proxy, + bool connected) +{ + struct btp_adapter *adapter; + struct btp_device *device; + const char *str_addr, *str_addr_type; + uint8_t address_type; + + device = find_device_by_proxy(proxy); + adapter = find_adapter_by_device(device); + + if (!device || !adapter) + return; + + if (!l_dbus_proxy_get_property(proxy, "Address", "s", &str_addr)) + return; + + if (!l_dbus_proxy_get_property(proxy, "AddressType", "s", + &str_addr_type)) + return; + + address_type = strcmp(str_addr_type, "public") ? BTP_GAP_ADDR_RANDOM : + BTP_GAP_ADDR_PUBLIC; + + if (connected) { + struct btp_gap_device_connected_ev ev; + + str2ba(str_addr, &ev.address); + ev.address_type = address_type; + + btp_send(btp, BTP_GAP_SERVICE, BTP_EV_GAP_DEVICE_CONNECTED, + adapter->index, sizeof(ev), &ev); + } else { + struct btp_gap_device_disconnected_ev ev; + + str2ba(str_addr, &ev.address); + ev.address_type = address_type; + + btp_send(btp, BTP_GAP_SERVICE, BTP_EV_GAP_DEVICE_DISCONNECTED, + adapter->index, sizeof(ev), &ev); + } +} + +static void btp_identity_resolved_ev(struct l_dbus_proxy *proxy) +{ + struct btp_device *dev = find_device_by_proxy(proxy); + struct btp_adapter *adapter = find_adapter_by_device(dev); + struct btp_gap_identity_resolved_ev ev; + char *str_addr, *str_addr_type; + uint8_t identity_address_type; + + if (!l_dbus_proxy_get_property(proxy, "Address", "s", &str_addr)) + return; + + if (!l_dbus_proxy_get_property(proxy, "AddressType", "s", + &str_addr_type)) + return; + + identity_address_type = strcmp(str_addr_type, "public") ? + BTP_GAP_ADDR_RANDOM : BTP_GAP_ADDR_PUBLIC; + + str2ba(str_addr, &ev.identity_address); + ev.identity_address_type = identity_address_type; + + memcpy(&ev.address, &dev->address, sizeof(ev.address)); + ev.address_type = dev->address_type; + + btp_send(btp, BTP_GAP_SERVICE, BTP_EV_GAP_IDENTITY_RESOLVED, + adapter->index, sizeof(ev), &ev); +} + +static void register_gap_service(void) +{ + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_SUPPORTED_COMMANDS, + btp_gap_read_commands, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, + BTP_OP_GAP_READ_CONTROLLER_INDEX_LIST, + btp_gap_read_controller_index, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_READ_COTROLLER_INFO, + btp_gap_read_info, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_RESET, + btp_gap_reset, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_SET_POWERED, + btp_gap_set_powered, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_SET_CONNECTABLE, + btp_gap_set_connectable, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_SET_DISCOVERABLE, + btp_gap_set_discoverable, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_SET_BONDABLE, + btp_gap_set_bondable, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_START_ADVERTISING, + btp_gap_start_advertising, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_STOP_ADVERTISING, + btp_gap_stop_advertising, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_START_DISCOVERY, + btp_gap_start_discovery, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_STOP_DISCOVERY, + btp_gap_stop_discovery, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_CONNECT, btp_gap_connect, + NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_DISCONNECT, + btp_gap_disconnect, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_SET_IO_CAPA, + btp_gap_set_io_capabilities, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_PAIR, btp_gap_pair, NULL, + NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_UNPAIR, btp_gap_unpair, + NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_PASSKEY_ENTRY_RSP, + btp_gap_passkey_entry_rsp, NULL, NULL); + + btp_register(btp, BTP_GAP_SERVICE, BTP_OP_GAP_PASSKEY_CONFIRM_RSP, + btp_gap_confirm_entry_rsp, NULL, NULL); +} + +static void btp_core_read_commands(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + uint8_t commands = 0; + + if (index != BTP_INDEX_NON_CONTROLLER) { + btp_send_error(btp, BTP_CORE_SERVICE, index, + BTP_ERROR_INVALID_INDEX); + return; + } + + commands |= (1 << BTP_OP_CORE_READ_SUPPORTED_COMMANDS); + commands |= (1 << BTP_OP_CORE_READ_SUPPORTED_SERVICES); + commands |= (1 << BTP_OP_CORE_REGISTER); + commands |= (1 << BTP_OP_CORE_UNREGISTER); + + btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_READ_SUPPORTED_COMMANDS, + BTP_INDEX_NON_CONTROLLER, sizeof(commands), &commands); +} + +static void btp_core_read_services(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + uint8_t services = 0; + + if (index != BTP_INDEX_NON_CONTROLLER) { + btp_send_error(btp, BTP_CORE_SERVICE, index, + BTP_ERROR_INVALID_INDEX); + return; + } + + services |= (1 << BTP_CORE_SERVICE); + services |= (1 << BTP_GAP_SERVICE); + + btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_READ_SUPPORTED_SERVICES, + BTP_INDEX_NON_CONTROLLER, sizeof(services), &services); +} + +static void btp_core_register(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + const struct btp_core_register_cp *cp = param; + + if (length < sizeof(*cp)) + goto failed; + + if (index != BTP_INDEX_NON_CONTROLLER) { + btp_send_error(btp, BTP_CORE_SERVICE, index, + BTP_ERROR_INVALID_INDEX); + return; + } + + switch (cp->service_id) { + case BTP_GAP_SERVICE: + if (gap_service_registered) + goto failed; + + if (!register_default_agent(NULL, + BTP_GAP_IOCAPA_NO_INPUT_NO_OUTPUT, + register_default_agent_reply)) + goto failed; + + return; + case BTP_GATT_SERVICE: + case BTP_L2CAP_SERVICE: + case BTP_MESH_NODE_SERVICE: + case BTP_CORE_SERVICE: + default: + goto failed; + } + + btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_REGISTER, + BTP_INDEX_NON_CONTROLLER, 0, NULL); + return; + +failed: + btp_send_error(btp, BTP_CORE_SERVICE, index, BTP_ERROR_FAIL); +} + +static void btp_core_unregister(uint8_t index, const void *param, + uint16_t length, void *user_data) +{ + const struct btp_core_unregister_cp *cp = param; + + if (length < sizeof(*cp)) + goto failed; + + if (index != BTP_INDEX_NON_CONTROLLER) { + btp_send_error(btp, BTP_CORE_SERVICE, index, + BTP_ERROR_INVALID_INDEX); + return; + } + + switch (cp->service_id) { + case BTP_GAP_SERVICE: + if (!gap_service_registered) + goto failed; + + btp_unregister_service(btp, BTP_GAP_SERVICE); + gap_service_registered = false; + break; + case BTP_GATT_SERVICE: + case BTP_L2CAP_SERVICE: + case BTP_MESH_NODE_SERVICE: + case BTP_CORE_SERVICE: + default: + goto failed; + } + + btp_send(btp, BTP_CORE_SERVICE, BTP_OP_CORE_UNREGISTER, + BTP_INDEX_NON_CONTROLLER, 0, NULL); + return; + +failed: + btp_send_error(btp, BTP_CORE_SERVICE, index, BTP_ERROR_FAIL); +} + +static void register_core_service(void) +{ + btp_register(btp, BTP_CORE_SERVICE, + BTP_OP_CORE_READ_SUPPORTED_COMMANDS, + btp_core_read_commands, NULL, NULL); + + btp_register(btp, BTP_CORE_SERVICE, + BTP_OP_CORE_READ_SUPPORTED_SERVICES, + btp_core_read_services, NULL, NULL); + + btp_register(btp, BTP_CORE_SERVICE, BTP_OP_CORE_REGISTER, + btp_core_register, NULL, NULL); + + btp_register(btp, BTP_CORE_SERVICE, BTP_OP_CORE_UNREGISTER, + btp_core_unregister, NULL, NULL); +} + +static void signal_handler(uint32_t signo, void *user_data) +{ + switch (signo) { + case SIGINT: + case SIGTERM: + l_info("Terminating"); + l_main_quit(); + break; + } +} + +static void btp_device_free(struct btp_device *device) +{ + l_free(device); +} + +static void btp_adapter_free(struct btp_adapter *adapter) +{ + l_queue_destroy(adapter->devices, + (l_queue_destroy_func_t)btp_device_free); + l_free(adapter); +} + +static void extract_settings(struct l_dbus_proxy *proxy, uint32_t *current, + uint32_t *supported) +{ + bool prop; + + *supported = 0; + *current = 0; + + /* TODO not all info is available via D-Bus API */ + *supported |= BTP_GAP_SETTING_POWERED; + *supported |= BTP_GAP_SETTING_CONNECTABLE; + *supported |= BTP_GAP_SETTING_DISCOVERABLE; + *supported |= BTP_GAP_SETTING_BONDABLE; + *supported |= BTP_GAP_SETTING_SSP; + *supported |= BTP_GAP_SETTING_BREDR; + *supported |= BTP_GAP_SETTING_LE; + *supported |= BTP_GAP_SETTING_ADVERTISING; + *supported |= BTP_GAP_SETTING_SC; + *supported |= BTP_GAP_SETTING_PRIVACY; + /* *supported |= BTP_GAP_SETTING_STATIC_ADDRESS; */ + + /* TODO not all info is availbe via D-Bus API so some are assumed to be + * enabled by bluetoothd or simply hardcoded until API is extended + */ + *current |= BTP_GAP_SETTING_CONNECTABLE; + *current |= BTP_GAP_SETTING_SSP; + *current |= BTP_GAP_SETTING_BREDR; + *current |= BTP_GAP_SETTING_LE; + *current |= BTP_GAP_SETTING_PRIVACY; + *current |= BTP_GAP_SETTING_SC; + /* *supported |= BTP_GAP_SETTING_STATIC_ADDRESS; */ + + if (l_dbus_proxy_get_property(proxy, "Powered", "b", &prop) && prop) + *current |= BTP_GAP_SETTING_POWERED; + + if (l_dbus_proxy_get_property(proxy, "Discoverable", "b", &prop) && + prop) + *current |= BTP_GAP_SETTING_DISCOVERABLE; + + if (l_dbus_proxy_get_property(proxy, "Pairable", "b", &prop) && prop) + *current |= BTP_GAP_SETTING_BONDABLE; +} + +static void proxy_added(struct l_dbus_proxy *proxy, void *user_data) +{ + const char *interface = l_dbus_proxy_get_interface(proxy); + const char *path = l_dbus_proxy_get_path(proxy); + + l_info("Proxy added: %s (%s)", interface, path); + + if (!strcmp(interface, "org.bluez.Adapter1")) { + struct btp_adapter *adapter; + + adapter = l_new(struct btp_adapter, 1); + adapter->proxy = proxy; + adapter->index = l_queue_length(adapters); + adapter->devices = l_queue_new(); + + extract_settings(proxy, &adapter->current_settings, + &adapter->supported_settings); + + adapter->default_settings = adapter->current_settings; + + l_queue_push_tail(adapters, adapter); + return; + } + + if (!strcmp(interface, "org.bluez.Device1")) { + struct btp_adapter *adapter; + struct btp_device *device; + char *str, *str_addr, *str_addr_type; + + if (!l_dbus_proxy_get_property(proxy, "Adapter", "o", &str)) + return; + + adapter = find_adapter_by_path(str); + if (!adapter) + return; + + device = l_new(struct btp_device, 1); + device->proxy = proxy; + + l_queue_push_tail(adapter->devices, device); + + btp_gap_device_found_ev(proxy); + + if (!l_dbus_proxy_get_property(proxy, "Address", "s", + &str_addr)) + return; + + if (!l_dbus_proxy_get_property(proxy, "AddressType", "s", + &str_addr_type)) + return; + + device->address_type = strcmp(str_addr_type, "public") ? + BTP_GAP_ADDR_RANDOM : + BTP_GAP_ADDR_PUBLIC; + if (!str2ba(str_addr, &device->address)) + return; + + return; + } + + if (!strcmp(interface, "org.bluez.LEAdvertisingManager1")) { + struct btp_adapter *adapter; + + adapter = find_adapter_by_path(path); + if (!adapter) + return; + + adapter->ad_proxy = proxy; + + return; + } + + if (!strcmp(interface, "org.bluez.AgentManager1")) { + ag.proxy = proxy; + + return; + } +} + +static bool device_match_by_proxy(const void *a, const void *b) +{ + const struct btp_device *device = a; + const struct l_dbus_proxy *proxy = b; + + return device->proxy == proxy; +} + +static void proxy_removed(struct l_dbus_proxy *proxy, void *user_data) +{ + const char *interface = l_dbus_proxy_get_interface(proxy); + const char *path = l_dbus_proxy_get_path(proxy); + + l_info("Proxy removed: %s (%s)", interface, path); + + if (!strcmp(interface, "org.bluez.Adapter1")) { + l_info("Adapter removed, terminating."); + l_main_quit(); + return; + } + + if (!strcmp(interface, "org.bluez.Device1")) { + struct btp_adapter *adapter; + char *str; + + if (!l_dbus_proxy_get_property(proxy, "Adapter", "o", &str)) + return; + + adapter = find_adapter_by_path(str); + if (!adapter) + return; + + l_queue_remove_if(adapter->devices, device_match_by_proxy, + proxy); + + return; + } +} + +static void property_changed(struct l_dbus_proxy *proxy, const char *name, + struct l_dbus_message *msg, void *user_data) +{ + const char *interface = l_dbus_proxy_get_interface(proxy); + const char *path = l_dbus_proxy_get_path(proxy); + + l_info("property_changed %s %s %s", name, path, interface); + + if (!strcmp(interface, "org.bluez.Adapter1")) { + struct btp_adapter *adapter = find_adapter_by_proxy(proxy); + uint32_t new_settings; + + if (!adapter) + return; + + new_settings = adapter->current_settings; + + if (!strcmp(name, "Powered")) { + bool prop; + + if (!l_dbus_message_get_arguments(msg, "b", &prop)) + return; + + if (prop) + new_settings |= BTP_GAP_SETTING_POWERED; + else + new_settings &= ~BTP_GAP_SETTING_POWERED; + } else if (!strcmp(name, "Discoverable")) { + bool prop; + + if (!l_dbus_message_get_arguments(msg, "b", &prop)) + return; + + if (prop) + new_settings |= BTP_GAP_SETTING_DISCOVERABLE; + else + new_settings &= ~BTP_GAP_SETTING_DISCOVERABLE; + } + + if (!strcmp(name, "Pairable")) { + bool prop; + + if (!l_dbus_message_get_arguments(msg, "b", &prop)) + return; + + if (prop) + new_settings |= BTP_GAP_SETTING_BONDABLE; + else + new_settings &= ~BTP_GAP_SETTING_BONDABLE; + } + + if (new_settings != adapter->current_settings) + update_current_settings(adapter, new_settings); + + return; + } else if (!strcmp(interface, "org.bluez.Device1")) { + if (!strcmp(name, "RSSI")) { + int16_t rssi; + + if (!l_dbus_message_get_arguments(msg, "n", &rssi)) + return; + + btp_gap_device_found_ev(proxy); + } else if (!strcmp(name, "Connected")) { + bool prop; + + if (!l_dbus_message_get_arguments(msg, "b", &prop)) + return; + + btp_gap_device_connection_ev(proxy, prop); + } else if (!strcmp(name, "AddressType")) { + /* Addres property change came first along with address + * type. + */ + btp_identity_resolved_ev(proxy); + } + } +} + +static void client_connected(struct l_dbus *dbus, void *user_data) +{ + l_info("D-Bus client connected"); +} + +static void client_disconnected(struct l_dbus *dbus, void *user_data) +{ + l_info("D-Bus client disconnected, terminated"); + l_main_quit(); +} + +static void btp_disconnect_handler(struct btp *btp, void *user_data) +{ + l_info("btp disconnected"); + l_main_quit(); +} + +static void client_ready(struct l_dbus_client *client, void *user_data) +{ + l_info("D-Bus client ready, connecting BTP"); + + btp = btp_new(socket_path); + if (!btp) { + l_error("Failed to connect BTP, terminating"); + l_main_quit(); + return; + } + + btp_set_disconnect_handler(btp, btp_disconnect_handler, NULL, NULL); + + register_core_service(); + + btp_send(btp, BTP_CORE_SERVICE, BTP_EV_CORE_READY, + BTP_INDEX_NON_CONTROLLER, 0, NULL); +} + +static void ready_callback(void *user_data) +{ + if (!l_dbus_object_manager_enable(dbus, "/")) + l_info("Unable to register the ObjectManager"); +} + +static void usage(void) +{ + l_info("btpclient - Bluetooth tester"); + l_info("Usage:"); + l_info("\tbtpclient [options]"); + l_info("options:\n" + "\t-s, --socket Socket to use for BTP\n" + "\t-q, --quiet Don't emit any logs\n" + "\t-v, --version Show version\n" + "\t-h, --help Show help options"); +} + +static const struct option options[] = { + { "socket", 1, 0, 's' }, + { "quiet", 0, 0, 'q' }, + { "version", 0, 0, 'v' }, + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + struct l_dbus_client *client; + int opt; + + l_log_set_stderr(); + + while ((opt = getopt_long(argc, argv, "+hs:vq", options, NULL)) != -1) { + switch (opt) { + case 's': + socket_path = l_strdup(optarg); + break; + case 'q': + l_log_set_null(); + break; + case 'd': + break; + case 'v': + l_info("%s", VERSION); + return EXIT_SUCCESS; + case 'h': + default: + usage(); + return EXIT_SUCCESS; + } + } + + if (!socket_path) { + l_info("Socket option is required"); + l_info("Type --help for usage"); + return EXIT_FAILURE; + } + + if (!l_main_init()) + return EXIT_FAILURE; + + + adapters = l_queue_new(); + + dbus = l_dbus_new_default(L_DBUS_SYSTEM_BUS); + l_dbus_set_ready_handler(dbus, ready_callback, NULL, NULL); + client = l_dbus_client_new(dbus, "org.bluez", "/org/bluez"); + + l_dbus_client_set_connect_handler(client, client_connected, NULL, NULL); + l_dbus_client_set_disconnect_handler(client, client_disconnected, NULL, + NULL); + + l_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed, + property_changed, NULL, NULL); + + l_dbus_client_set_ready_handler(client, client_ready, NULL, NULL); + + l_main_run_with_signal(signal_handler, NULL); + + l_dbus_client_destroy(client); + l_dbus_destroy(dbus); + btp_cleanup(btp); + + l_queue_destroy(adapters, (l_queue_destroy_func_t)btp_adapter_free); + + l_free(socket_path); + + l_main_exit(); + + return EXIT_SUCCESS; +} diff --git a/tools/btproxy.c b/tools/btproxy.c new file mode 100644 index 0000000..fb67a57 --- /dev/null +++ b/tools/btproxy.c @@ -0,0 +1,928 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "src/shared/util.h" +#include "src/shared/mainloop.h" +#include "src/shared/ecc.h" +#include "monitor/bt.h" + +#define HCI_PRIMARY 0x00 +#define HCI_AMP 0x01 + +#define BTPROTO_HCI 1 +struct sockaddr_hci { + sa_family_t hci_family; + unsigned short hci_dev; + unsigned short hci_channel; +}; +#define HCI_CHANNEL_USER 1 + +static uint16_t hci_index = 0; +static bool client_active = false; +static bool debug_enabled = false; +static bool emulate_ecc = false; +static bool skip_first_zero = false; + +static void hexdump_print(const char *str, void *user_data) +{ + printf("%s%s\n", (char *) user_data, str); +} + +struct proxy { + /* Receive commands, ACL and SCO data */ + int host_fd; + uint8_t host_buf[4096]; + uint16_t host_len; + bool host_shutdown; + bool host_skip_first_zero; + + /* Receive events, ACL and SCO data */ + int dev_fd; + uint8_t dev_buf[4096]; + uint16_t dev_len; + bool dev_shutdown; + + /* ECC emulation */ + uint8_t event_mask[8]; + uint8_t local_sk256[32]; +}; + +static bool write_packet(int fd, const void *data, size_t size, + void *user_data) +{ + while (size > 0) { + ssize_t written; + + written = write(fd, data, size); + if (written < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + return false; + } + + if (debug_enabled) + util_hexdump('<', data, written, hexdump_print, + user_data); + + data += written; + size -= written; + } + + return true; +} + +static void host_write_packet(struct proxy *proxy, void *buf, uint16_t len) +{ + if (!write_packet(proxy->dev_fd, buf, len, "D: ")) { + fprintf(stderr, "Write to device descriptor failed\n"); + mainloop_remove_fd(proxy->dev_fd); + } +} + +static void dev_write_packet(struct proxy *proxy, void *buf, uint16_t len) +{ + if (!write_packet(proxy->host_fd, buf, len, "H: ")) { + fprintf(stderr, "Write to host descriptor failed\n"); + mainloop_remove_fd(proxy->host_fd); + } +} + +static void cmd_status(struct proxy *proxy, uint8_t status, uint16_t opcode) +{ + size_t buf_size = 1 + sizeof(struct bt_hci_evt_hdr) + + sizeof(struct bt_hci_evt_cmd_status); + void *buf = alloca(buf_size); + struct bt_hci_evt_hdr *hdr = buf + 1; + struct bt_hci_evt_cmd_status *cs = buf + 1 + sizeof(*hdr); + + *((uint8_t *) buf) = BT_H4_EVT_PKT; + + hdr->evt = BT_HCI_EVT_CMD_STATUS; + hdr->plen = sizeof(*cs); + + cs->status = status; + cs->ncmd = 0x01; + cs->opcode = cpu_to_le16(opcode); + + dev_write_packet(proxy, buf, buf_size); +} + +static void le_meta_event(struct proxy *proxy, uint8_t event, + void *data, uint8_t len) +{ + size_t buf_size = 1 + sizeof(struct bt_hci_evt_hdr) + 1 + len; + void *buf = alloca(buf_size); + struct bt_hci_evt_hdr *hdr = buf + 1; + + *((uint8_t *) buf) = BT_H4_EVT_PKT; + + hdr->evt = BT_HCI_EVT_LE_META_EVENT; + hdr->plen = 1 + len; + + *((uint8_t *) (buf + 1 + sizeof(*hdr))) = event; + + if (len > 0) + memcpy(buf + 1 + sizeof(*hdr) + 1, data, len); + + dev_write_packet(proxy, buf, buf_size); +} + +static void host_emulate_ecc(struct proxy *proxy, void *buf, uint16_t len) +{ + uint8_t pkt_type = *((uint8_t *) buf); + struct bt_hci_cmd_hdr *hdr = buf + 1; + struct bt_hci_cmd_le_set_event_mask *lsem; + struct bt_hci_cmd_le_generate_dhkey *lgd; + struct bt_hci_evt_le_read_local_pk256_complete lrlpkc; + struct bt_hci_evt_le_generate_dhkey_complete lgdc; + + if (pkt_type != BT_H4_CMD_PKT) { + host_write_packet(proxy, buf, len); + return; + } + + switch (le16_to_cpu(hdr->opcode)) { + case BT_HCI_CMD_LE_SET_EVENT_MASK: + lsem = buf + 1 + sizeof(*hdr); + memcpy(proxy->event_mask, lsem->mask, 8); + + lsem->mask[0] &= ~0x80; /* P-256 Public Key Complete */ + lsem->mask[1] &= ~0x01; /* Generate DHKey Complete */ + + host_write_packet(proxy, buf, len); + break; + + case BT_HCI_CMD_LE_READ_LOCAL_PK256: + if (!ecc_make_key(lrlpkc.local_pk256, proxy->local_sk256)) { + cmd_status(proxy, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LE_READ_LOCAL_PK256); + break; + } + cmd_status(proxy, BT_HCI_ERR_SUCCESS, + BT_HCI_CMD_LE_READ_LOCAL_PK256); + + if (!(proxy->event_mask[0] & 0x80)) + break; + + lrlpkc.status = BT_HCI_ERR_SUCCESS; + le_meta_event(proxy, BT_HCI_EVT_LE_READ_LOCAL_PK256_COMPLETE, + &lrlpkc, sizeof(lrlpkc)); + break; + + case BT_HCI_CMD_LE_GENERATE_DHKEY: + lgd = buf + 1 + sizeof(*hdr); + if (!ecdh_shared_secret(lgd->remote_pk256, proxy->local_sk256, + lgdc.dhkey)) { + cmd_status(proxy, BT_HCI_ERR_COMMAND_DISALLOWED, + BT_HCI_CMD_LE_GENERATE_DHKEY); + break; + } + cmd_status(proxy, BT_HCI_ERR_SUCCESS, + BT_HCI_CMD_LE_GENERATE_DHKEY); + + if (!(proxy->event_mask[1] & 0x01)) + break; + + lgdc.status = BT_HCI_ERR_SUCCESS; + le_meta_event(proxy, BT_HCI_EVT_LE_GENERATE_DHKEY_COMPLETE, + &lgdc, sizeof(lgdc)); + break; + + default: + host_write_packet(proxy, buf, len); + break; + } +} + +static void dev_emulate_ecc(struct proxy *proxy, void *buf, uint16_t len) +{ + uint8_t pkt_type = *((uint8_t *) buf); + struct bt_hci_evt_hdr *hdr = buf + 1; + struct bt_hci_evt_cmd_complete *cc; + struct bt_hci_rsp_read_local_commands *rlc; + + if (pkt_type != BT_H4_EVT_PKT) { + dev_write_packet(proxy, buf, len); + return; + } + + switch (hdr->evt) { + case BT_HCI_EVT_CMD_COMPLETE: + cc = buf + 1 + sizeof(*hdr); + + switch (le16_to_cpu(cc->opcode)) { + case BT_HCI_CMD_READ_LOCAL_COMMANDS: + rlc = buf + 1 + sizeof(*hdr) + sizeof(*cc); + rlc->commands[34] |= 0x02; /* P-256 Public Key */ + rlc->commands[34] |= 0x04; /* Generate DHKey */ + break; + } + + dev_write_packet(proxy, buf, len); + break; + + default: + dev_write_packet(proxy, buf, len); + break; + } +} + +static void host_read_destroy(void *user_data) +{ + struct proxy *proxy = user_data; + + printf("Closing host descriptor\n"); + + if (proxy->host_shutdown) + shutdown(proxy->host_fd, SHUT_RDWR); + + close(proxy->host_fd); + proxy->host_fd = -1; + + if (proxy->dev_fd < 0) { + client_active = false; + free(proxy); + } else + mainloop_remove_fd(proxy->dev_fd); +} + +static void host_read_callback(int fd, uint32_t events, void *user_data) +{ + struct proxy *proxy = user_data; + struct bt_hci_cmd_hdr *cmd_hdr; + struct bt_hci_acl_hdr *acl_hdr; + struct bt_hci_sco_hdr *sco_hdr; + ssize_t len; + uint16_t pktlen; + + if (events & (EPOLLERR | EPOLLHUP)) { + fprintf(stderr, "Error from host descriptor\n"); + mainloop_remove_fd(proxy->host_fd); + return; + } + + if (events & EPOLLRDHUP) { + fprintf(stderr, "Remote hangup of host descriptor\n"); + mainloop_remove_fd(proxy->host_fd); + return; + } + + len = read(proxy->host_fd, proxy->host_buf + proxy->host_len, + sizeof(proxy->host_buf) - proxy->host_len); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return; + + fprintf(stderr, "Read from host descriptor failed\n"); + mainloop_remove_fd(proxy->host_fd); + return; + } + + if (debug_enabled) + util_hexdump('>', proxy->host_buf + proxy->host_len, len, + hexdump_print, "H: "); + + if (proxy->host_skip_first_zero && len > 0) { + proxy->host_skip_first_zero = false; + if (proxy->host_buf[proxy->host_len] == '\0') { + printf("Skipping initial zero byte\n"); + len--; + memmove(proxy->host_buf + proxy->host_len, + proxy->host_buf + proxy->host_len + 1, len); + } + } + + proxy->host_len += len; + +process_packet: + if (proxy->host_len < 1) + return; + + switch (proxy->host_buf[0]) { + case BT_H4_CMD_PKT: + if (proxy->host_len < 1 + sizeof(*cmd_hdr)) + return; + + cmd_hdr = (void *) (proxy->host_buf + 1); + pktlen = 1 + sizeof(*cmd_hdr) + cmd_hdr->plen; + break; + case BT_H4_ACL_PKT: + if (proxy->host_len < 1 + sizeof(*acl_hdr)) + return; + + acl_hdr = (void *) (proxy->host_buf + 1); + pktlen = 1 + sizeof(*acl_hdr) + cpu_to_le16(acl_hdr->dlen); + break; + case BT_H4_SCO_PKT: + if (proxy->host_len < 1 + sizeof(*sco_hdr)) + return; + + sco_hdr = (void *) (proxy->host_buf + 1); + pktlen = 1 + sizeof(*sco_hdr) + sco_hdr->dlen; + break; + case 0xff: + /* Notification packet from /dev/vhci - ignore */ + proxy->host_len = 0; + return; + default: + fprintf(stderr, "Received unknown host packet type 0x%02x\n", + proxy->host_buf[0]); + mainloop_remove_fd(proxy->host_fd); + return; + } + + if (proxy->host_len < pktlen) + return; + + if (emulate_ecc) + host_emulate_ecc(proxy, proxy->host_buf, pktlen); + else + host_write_packet(proxy, proxy->host_buf, pktlen); + + if (proxy->host_len > pktlen) { + memmove(proxy->host_buf, proxy->host_buf + pktlen, + proxy->host_len - pktlen); + proxy->host_len -= pktlen; + goto process_packet; + } + + proxy->host_len = 0; +} + +static void dev_read_destroy(void *user_data) +{ + struct proxy *proxy = user_data; + + printf("Closing device descriptor\n"); + + if (proxy->dev_shutdown) + shutdown(proxy->dev_fd, SHUT_RDWR); + + close(proxy->dev_fd); + proxy->dev_fd = -1; + + if (proxy->host_fd < 0) { + client_active = false; + free(proxy); + } else + mainloop_remove_fd(proxy->host_fd); +} + +static void dev_read_callback(int fd, uint32_t events, void *user_data) +{ + struct proxy *proxy = user_data; + struct bt_hci_evt_hdr *evt_hdr; + struct bt_hci_acl_hdr *acl_hdr; + struct bt_hci_sco_hdr *sco_hdr; + ssize_t len; + uint16_t pktlen; + + if (events & (EPOLLERR | EPOLLHUP)) { + fprintf(stderr, "Error from device descriptor\n"); + mainloop_remove_fd(proxy->dev_fd); + return; + } + + if (events & EPOLLRDHUP) { + fprintf(stderr, "Remote hangup of device descriptor\n"); + mainloop_remove_fd(proxy->host_fd); + return; + } + + len = read(proxy->dev_fd, proxy->dev_buf + proxy->dev_len, + sizeof(proxy->dev_buf) - proxy->dev_len); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return; + + fprintf(stderr, "Read from device descriptor failed\n"); + mainloop_remove_fd(proxy->dev_fd); + return; + } + + if (debug_enabled) + util_hexdump('>', proxy->dev_buf + proxy->dev_len, len, + hexdump_print, "D: "); + + proxy->dev_len += len; + +process_packet: + if (proxy->dev_len < 1) + return; + + switch (proxy->dev_buf[0]) { + case BT_H4_EVT_PKT: + if (proxy->dev_len < 1 + sizeof(*evt_hdr)) + return; + + evt_hdr = (void *) (proxy->dev_buf + 1); + pktlen = 1 + sizeof(*evt_hdr) + evt_hdr->plen; + break; + case BT_H4_ACL_PKT: + if (proxy->dev_len < 1 + sizeof(*acl_hdr)) + return; + + acl_hdr = (void *) (proxy->dev_buf + 1); + pktlen = 1 + sizeof(*acl_hdr) + cpu_to_le16(acl_hdr->dlen); + break; + case BT_H4_SCO_PKT: + if (proxy->dev_len < 1 + sizeof(*sco_hdr)) + return; + + sco_hdr = (void *) (proxy->dev_buf + 1); + pktlen = 1 + sizeof(*sco_hdr) + sco_hdr->dlen; + break; + default: + fprintf(stderr, "Received unknown device packet type 0x%02x\n", + proxy->dev_buf[0]); + mainloop_remove_fd(proxy->dev_fd); + return; + } + + if (proxy->dev_len < pktlen) + return; + + if (emulate_ecc) + dev_emulate_ecc(proxy, proxy->dev_buf, pktlen); + else + dev_write_packet(proxy, proxy->dev_buf, pktlen); + + if (proxy->dev_len > pktlen) { + memmove(proxy->dev_buf, proxy->dev_buf + pktlen, + proxy->dev_len - pktlen); + proxy->dev_len -= pktlen; + goto process_packet; + } + + proxy->dev_len = 0; +} + +static bool setup_proxy(int host_fd, bool host_shutdown, + int dev_fd, bool dev_shutdown) +{ + struct proxy *proxy; + + proxy = new0(struct proxy, 1); + if (!proxy) + return false; + + if (emulate_ecc) + printf("Enabling ECC emulation\n"); + + proxy->host_fd = host_fd; + proxy->host_shutdown = host_shutdown; + proxy->host_skip_first_zero = skip_first_zero; + + proxy->dev_fd = dev_fd; + proxy->dev_shutdown = dev_shutdown; + + mainloop_add_fd(proxy->host_fd, EPOLLIN | EPOLLRDHUP, + host_read_callback, proxy, host_read_destroy); + + mainloop_add_fd(proxy->dev_fd, EPOLLIN | EPOLLRDHUP, + dev_read_callback, proxy, dev_read_destroy); + + return true; +} + +static int open_channel(uint16_t index) +{ + struct sockaddr_hci addr; + int fd; + + printf("Opening user channel for hci%u\n", hci_index); + + fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); + if (fd < 0) { + perror("Failed to open Bluetooth socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = index; + addr.hci_channel = HCI_CHANNEL_USER; + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(fd); + perror("Failed to bind Bluetooth socket"); + return -1; + } + + return fd; +} + +static void server_callback(int fd, uint32_t events, void *user_data) +{ + union { + struct sockaddr common; + struct sockaddr_un sun; + struct sockaddr_in sin; + } addr; + socklen_t len; + int host_fd, dev_fd; + + if (events & (EPOLLERR | EPOLLHUP)) { + mainloop_quit(); + return; + } + + memset(&addr, 0, sizeof(addr)); + len = sizeof(addr); + + if (getsockname(fd, &addr.common, &len) < 0) { + perror("Failed to get socket name"); + return; + } + + host_fd = accept(fd, &addr.common, &len); + if (host_fd < 0) { + perror("Failed to accept client socket"); + return; + } + + if (client_active) { + fprintf(stderr, "Active client already present\n"); + close(host_fd); + return; + } + + dev_fd = open_channel(hci_index); + if (dev_fd < 0) { + close(host_fd); + return; + } + + printf("New client connected\n"); + + if (!setup_proxy(host_fd, true, dev_fd, false)) { + close(dev_fd); + close(host_fd); + return; + } + + client_active = true; +} + +static int open_unix(const char *path) +{ + struct sockaddr_un addr; + size_t len; + int fd; + + len = strlen(path); + if (len > sizeof(addr.sun_path) - 1) { + fprintf(stderr, "Path too long\n"); + return -1; + } + + unlink(path); + + fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + perror("Failed to open Unix server socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind Unix server socket"); + close(fd); + return -1; + } + + if (listen(fd, 1) < 0) { + perror("Failed to listen Unix server socket"); + close(fd); + return -1; + } + + if (chmod(path, 0666) < 0) + perror("Failed to change mode"); + + return fd; +} + +static int open_tcp(const char *address, unsigned int port) +{ + struct sockaddr_in addr; + int fd, opt = 1; + + fd = socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + perror("Failed to open TCP server socket"); + return -1; + } + + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(address); + addr.sin_port = htons(port); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind TCP server socket"); + close(fd); + return -1; + } + + if (listen(fd, 1) < 0) { + perror("Failed to listen TCP server socket"); + close(fd); + return -1; + } + + return fd; +} + +static int connect_tcp(const char *address, unsigned int port) +{ + struct sockaddr_in addr; + int fd; + + fd = socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + perror("Failed to open TCP client socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(address); + addr.sin_port = htons(port); + + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to connect TCP client socket"); + close(fd); + return -1; + } + + return fd; +} + +static int open_vhci(uint8_t type) +{ + uint8_t create_req[2] = { 0xff, type }; + ssize_t written; + int fd; + + fd = open("/dev/vhci", O_RDWR | O_CLOEXEC); + if (fd < 0) { + perror("Failed to open /dev/vhci device"); + return -1; + } + + written = write(fd, create_req, sizeof(create_req)); + if (written < 0) { + perror("Failed to set device type"); + close(fd); + return -1; + } + + return fd; +} + +static void signal_callback(int signum, void *user_data) +{ + switch (signum) { + case SIGINT: + case SIGTERM: + mainloop_quit(); + break; + } +} + +static void usage(void) +{ + printf("btproxy - Bluetooth controller proxy\n" + "Usage:\n"); + printf("\tbtproxy [options]\n"); + printf("Options:\n" + "\t-c, --connect
Connect to server\n" + "\t-l, --listen [address] Use TCP server\n" + "\t-u, --unix [path] Use Unix server\n" + "\t-p, --port Use specified TCP port\n" + "\t-i, --index Use specified controller\n" + "\t-a, --amp Create AMP controller\n" + "\t-e, --ecc Emulate ECC support\n" + "\t-d, --debug Enable debugging output\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "redirect", no_argument, NULL, 'r' }, + { "connect", required_argument, NULL, 'c' }, + { "listen", optional_argument, NULL, 'l' }, + { "unix", optional_argument, NULL, 'u' }, + { "port", required_argument, NULL, 'p' }, + { "index", required_argument, NULL, 'i' }, + { "amp", no_argument, NULL, 'a' }, + { "ecc", no_argument, NULL, 'e' }, + { "debug", no_argument, NULL, 'd' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + const char *connect_address = NULL; + const char *server_address = NULL; + const char *unix_path = NULL; + unsigned short tcp_port = 0xb1ee; /* 45550 */ + bool use_redirect = false; + uint8_t type = HCI_PRIMARY; + const char *str; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "rc:l::u::p:i:aezdvh", + main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'r': + use_redirect = true; + break; + case 'c': + connect_address = optarg; + break; + case 'l': + if (optarg) + server_address = optarg; + else + server_address = "0.0.0.0"; + break; + case 'u': + if (optarg) { + struct sockaddr_un addr; + + unix_path = optarg; + if (strlen(unix_path) > + sizeof(addr.sun_path) - 1) { + fprintf(stderr, "Path too long\n"); + return EXIT_FAILURE; + } + } else + unix_path = "/tmp/bt-server-bredr"; + break; + case 'p': + tcp_port = atoi(optarg); + break; + case 'i': + if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3)) + str = optarg + 3; + else + str = optarg; + if (!isdigit(*str)) { + usage(); + return EXIT_FAILURE; + } + hci_index = atoi(str); + break; + case 'a': + type = HCI_AMP; + break; + case 'e': + emulate_ecc = true; + break; + case 'z': + skip_first_zero = true; + break; + case 'd': + debug_enabled = true; + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind > 0) { + fprintf(stderr, "Invalid command line parameters\n"); + return EXIT_FAILURE; + } + + if (unix_path && (server_address || use_redirect)) { + fprintf(stderr, "Invalid to specify TCP and Unix servers\n"); + return EXIT_FAILURE; + } + + if (connect_address && (unix_path || server_address || use_redirect)) { + fprintf(stderr, "Invalid to specify client and server mode\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + if (connect_address || use_redirect) { + int host_fd, dev_fd; + + if (use_redirect) { + printf("Creating local redirect\n"); + + dev_fd = open_channel(hci_index); + } else { + printf("Connecting to %s:%u\n", connect_address, + tcp_port); + + dev_fd = connect_tcp(connect_address, tcp_port); + } + + if (dev_fd < 0) + return EXIT_FAILURE; + + printf("Opening virtual device\n"); + + host_fd = open_vhci(type); + if (host_fd < 0) { + close(dev_fd); + return EXIT_FAILURE; + } + + if (!setup_proxy(host_fd, false, dev_fd, true)) { + close(dev_fd); + close(host_fd); + return EXIT_FAILURE; + } + } else { + int server_fd; + + if (unix_path) { + printf("Listening on %s\n", unix_path); + + server_fd = open_unix(unix_path); + } else if (server_address) { + printf("Listening on %s:%u\n", server_address, + tcp_port); + + server_fd = open_tcp(server_address, tcp_port); + } else { + fprintf(stderr, "Missing emulator device\n"); + return EXIT_FAILURE; + } + + if (server_fd < 0) + return EXIT_FAILURE; + + mainloop_add_fd(server_fd, EPOLLIN, server_callback, + NULL, NULL); + } + + return mainloop_run_with_signal(signal_callback, NULL); +} diff --git a/tools/btsnoop.c b/tools/btsnoop.c new file mode 100644 index 0000000..9f30437 --- /dev/null +++ b/tools/btsnoop.c @@ -0,0 +1,610 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "src/shared/btsnoop.h" + +struct btsnoop_hdr { + uint8_t id[8]; /* Identification Pattern */ + uint32_t version; /* Version Number = 1 */ + uint32_t type; /* Datalink Type */ +} __attribute__ ((packed)); +#define BTSNOOP_HDR_SIZE (sizeof(struct btsnoop_hdr)) + +struct btsnoop_pkt { + uint32_t size; /* Original Length */ + uint32_t len; /* Included Length */ + uint32_t flags; /* Packet Flags */ + uint32_t drops; /* Cumulative Drops */ + uint64_t ts; /* Timestamp microseconds */ + uint8_t data[0]; /* Packet Data */ +} __attribute__ ((packed)); +#define BTSNOOP_PKT_SIZE (sizeof(struct btsnoop_pkt)) + +static const uint8_t btsnoop_id[] = { 0x62, 0x74, 0x73, 0x6e, + 0x6f, 0x6f, 0x70, 0x00 }; + +static const uint32_t btsnoop_version = 1; + +static int create_btsnoop(const char *path) +{ + struct btsnoop_hdr hdr; + ssize_t written; + int fd; + + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + perror("failed to output file"); + return -1; + } + + memcpy(hdr.id, btsnoop_id, sizeof(btsnoop_id)); + hdr.version = htobe32(btsnoop_version); + hdr.type = htobe32(2001); + + written = write(fd, &hdr, BTSNOOP_HDR_SIZE); + if (written < 0) { + perror("failed to write output header"); + close(fd); + return -1; + } + + return fd; +} + +static int open_btsnoop(const char *path, uint32_t *type) +{ + struct btsnoop_hdr hdr; + ssize_t len; + int fd; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + perror("failed to open input file"); + return -1; + } + + len = read(fd, &hdr, BTSNOOP_HDR_SIZE); + if (len < 0 || len != BTSNOOP_HDR_SIZE) { + perror("failed to read input header"); + close(fd); + return -1; + } + + if (memcmp(hdr.id, btsnoop_id, sizeof(btsnoop_id))) { + fprintf(stderr, "not a valid btsnoop header\n"); + close(fd); + return -1; + } + + if (be32toh(hdr.version) != btsnoop_version) { + fprintf(stderr, "invalid btsnoop version\n"); + close(fd); + return -1; + } + + if (type) + *type = be32toh(hdr.type); + + return fd; +} + +#define MAX_MERGE 8 + +static void command_merge(const char *output, int argc, char *argv[]) +{ + struct btsnoop_pkt input_pkt[MAX_MERGE]; + unsigned char buf[2048]; + int output_fd, input_fd[MAX_MERGE], num_input = 0; + int i, select_input; + ssize_t len, written; + uint32_t toread, flags; + uint16_t index, opcode; + + if (argc > MAX_MERGE) { + fprintf(stderr, "only up to %d files allowed\n", MAX_MERGE); + return; + } + + for (i = 0; i < argc; i++) { + uint32_t type; + int fd; + + fd = open_btsnoop(argv[i], &type); + if (fd < 0) + break; + + if (type != 1002) { + fprintf(stderr, "unsupported link data type %u\n", + type); + close(fd); + break; + } + + input_fd[num_input++] = fd; + } + + if (num_input != argc) { + fprintf(stderr, "failed to open all input files\n"); + goto close_input; + } + + output_fd = create_btsnoop(output); + if (output_fd < 0) + goto close_input; + + for (i = 0; i < num_input; i++) { + len = read(input_fd[i], &input_pkt[i], BTSNOOP_PKT_SIZE); + if (len < 0 || len != BTSNOOP_PKT_SIZE) { + close(input_fd[i]); + input_fd[i] = -1; + } + } + +next_packet: + select_input = -1; + + for (i = 0; i < num_input; i++) { + uint64_t ts; + + if (input_fd[i] < 0) + continue; + + if (select_input < 0) { + select_input = i; + continue; + } + + ts = be64toh(input_pkt[i].ts); + + if (ts < be64toh(input_pkt[select_input].ts)) + select_input = i; + } + + if (select_input < 0) + goto close_output; + + toread = be32toh(input_pkt[select_input].size); + flags = be32toh(input_pkt[select_input].flags); + + len = read(input_fd[select_input], buf, toread); + if (len < 0 || len != (ssize_t) toread) { + close(input_fd[select_input]); + input_fd[select_input] = -1; + goto next_packet; + } + + written = htobe32(toread - 1); + input_pkt[select_input].size = written; + input_pkt[select_input].len = written; + + switch (buf[0]) { + case 0x01: + opcode = BTSNOOP_OPCODE_COMMAND_PKT; + break; + case 0x02: + if (flags & 0x01) + opcode = BTSNOOP_OPCODE_ACL_RX_PKT; + else + opcode = BTSNOOP_OPCODE_ACL_TX_PKT; + break; + case 0x03: + if (flags & 0x01) + opcode = BTSNOOP_OPCODE_SCO_RX_PKT; + else + opcode = BTSNOOP_OPCODE_SCO_TX_PKT; + break; + case 0x04: + opcode = BTSNOOP_OPCODE_EVENT_PKT; + break; + default: + goto skip_write; + } + + index = select_input; + input_pkt[select_input].flags = htobe32((index << 16) | opcode); + + written = write(output_fd, &input_pkt[select_input], BTSNOOP_PKT_SIZE); + if (written != BTSNOOP_PKT_SIZE) { + fprintf(stderr, "write of packet header failed\n"); + goto close_output; + } + + written = write(output_fd, buf + 1, toread - 1); + if (written != (ssize_t) toread - 1) { + fprintf(stderr, "write of packet data failed\n"); + goto close_output; + } + +skip_write: + len = read(input_fd[select_input], + &input_pkt[select_input], BTSNOOP_PKT_SIZE); + if (len < 0 || len != BTSNOOP_PKT_SIZE) { + close(input_fd[select_input]); + input_fd[select_input] = -1; + } + + goto next_packet; + +close_output: + close(output_fd); + +close_input: + for (i = 0; i < num_input; i++) + close(input_fd[i]); +} + +static void command_extract_eir(const char *input) +{ + struct btsnoop_pkt pkt; + unsigned char buf[2048]; + ssize_t len; + uint32_t type, toread, flags; + uint16_t opcode; + int fd, count = 0; + + fd = open_btsnoop(input, &type); + if (fd < 0) + return; + + if (type != 2001) { + fprintf(stderr, "unsupported link data type %u\n", type); + close(fd); + return; + } + +next_packet: + len = read(fd, &pkt, BTSNOOP_PKT_SIZE); + if (len < 0 || len != BTSNOOP_PKT_SIZE) + goto close_input; + + toread = be32toh(pkt.size); + flags = be32toh(pkt.flags); + + opcode = flags & 0x00ff; + + len = read(fd, buf, toread); + if (len < 0 || len != (ssize_t) toread) { + fprintf(stderr, "failed to read packet data\n"); + goto close_input; + } + + switch (opcode) { + case BTSNOOP_OPCODE_EVENT_PKT: + /* extended inquiry result event */ + if (buf[0] == 0x2f) { + uint8_t *eir_ptr, eir_len, i; + + eir_len = buf[1] - 15; + eir_ptr = buf + 17; + + if (eir_len < 1 || eir_len > 240) + break; + + printf("\t[Extended Inquiry Data with %u bytes]\n", + eir_len); + printf("\t\t"); + for (i = 0; i < eir_len; i++) { + printf("0x%02x", eir_ptr[i]); + if (((i + 1) % 8) == 0) { + if (i < eir_len - 1) + printf(",\n\t\t"); + } else { + if (i < eir_len - 1) + printf(", "); + } + } + printf("\n"); + + count++; + } + break; + } + + goto next_packet; + +close_input: + close(fd); +} + +static void command_extract_ad(const char *input) +{ + struct btsnoop_pkt pkt; + unsigned char buf[2048]; + ssize_t len; + uint32_t type, toread, flags; + uint16_t opcode; + int fd, count = 0; + + fd = open_btsnoop(input, &type); + if (fd < 0) + return; + + if (type != 2001) { + fprintf(stderr, "unsupported link data type %u\n", type); + close(fd); + return; + } + +next_packet: + len = read(fd, &pkt, BTSNOOP_PKT_SIZE); + if (len < 0 || len != BTSNOOP_PKT_SIZE) + goto close_input; + + toread = be32toh(pkt.size); + flags = be32toh(pkt.flags); + + opcode = flags & 0x00ff; + + len = read(fd, buf, toread); + if (len < 0 || len != (ssize_t) toread) { + fprintf(stderr, "failed to read packet data\n"); + goto close_input; + } + + switch (opcode) { + case BTSNOOP_OPCODE_EVENT_PKT: + /* advertising report */ + if (buf[0] == 0x3e && buf[2] == 0x02) { + uint8_t *ad_ptr, ad_len, i; + + ad_len = buf[12]; + ad_ptr = buf + 13; + + if (ad_len < 1 || ad_len > 40) + break; + + printf("\t[Advertising Data with %u bytes]\n", ad_len); + printf("\t\t"); + for (i = 0; i < ad_len; i++) { + printf("0x%02x", ad_ptr[i]); + if (((i + 1) % 8) == 0) { + if (i < ad_len - 1) + printf(",\n\t\t"); + } else { + if (i < ad_len - 1) + printf(", "); + } + } + printf("\n"); + + count++; + } + break; + } + + goto next_packet; + +close_input: + close(fd); +} +static const uint8_t conn_complete[] = { 0x04, 0x03, 0x0B, 0x00 }; +static const uint8_t disc_complete[] = { 0x04, 0x05, 0x04, 0x00 }; + +static void command_extract_sdp(const char *input) +{ + struct btsnoop_pkt pkt; + unsigned char buf[2048]; + ssize_t len; + uint32_t type, toread; + uint16_t current_cid = 0x0000; + uint8_t pdu_buf[512]; + uint16_t pdu_len = 0; + bool pdu_first = false; + int fd, count = 0; + + fd = open_btsnoop(input, &type); + if (fd < 0) + return; + + if (type != 1002) { + fprintf(stderr, "unsupported link data type %u\n", type); + close(fd); + return; + } + +next_packet: + len = read(fd, &pkt, BTSNOOP_PKT_SIZE); + if (len < 0 || len != BTSNOOP_PKT_SIZE) + goto close_input; + + toread = be32toh(pkt.size); + + len = read(fd, buf, toread); + if (len < 0 || len != (ssize_t) toread) { + fprintf(stderr, "failed to read packet data\n"); + goto close_input; + } + + if (buf[0] == 0x02) { + uint8_t acl_flags; + + /* first 4 bytes are handle and data len */ + acl_flags = buf[2] >> 4; + + /* use only packet with ACL start flag */ + if (acl_flags & 0x02) { + if (current_cid == 0x0040 && pdu_len > 0) { + int i; + if (!pdu_first) + printf(",\n"); + printf("\t\traw_pdu("); + for (i = 0; i < pdu_len; i++) { + printf("0x%02x", pdu_buf[i]); + if (((i + 1) % 8) == 0) { + if (i < pdu_len - 1) + printf(",\n\t\t\t"); + } else { + if (i < pdu_len - 1) + printf(", "); + } + } + printf(")"); + pdu_first = false; + } + + /* next 4 bytes are data len and cid */ + current_cid = buf[8] << 8 | buf[7]; + memcpy(pdu_buf, buf + 9, len - 9); + pdu_len = len - 9; + } else if (acl_flags & 0x01) { + memcpy(pdu_buf + pdu_len, buf + 5, len - 5); + pdu_len += len - 5; + } + } + + if ((size_t) len > sizeof(conn_complete)) { + if (memcmp(buf, conn_complete, sizeof(conn_complete)) == 0) { + printf("\tdefine_test(\"/test/%u\",\n", ++count); + pdu_first = true; + } + } + + if ((size_t) len > sizeof(disc_complete)) { + if (memcmp(buf, disc_complete, sizeof(disc_complete)) == 0) { + printf(");\n"); + } + } + + goto next_packet; + +close_input: + close(fd); +} + +static void usage(void) +{ + printf("btsnoop trace file handling tool\n" + "Usage:\n"); + printf("\tbtsnoop [files]\n"); + printf("commands:\n" + "\t-m, --merge Merge multiple btsnoop files\n" + "\t-e, --extract Extract data from btsnoop file\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "merge", required_argument, NULL, 'm' }, + { "extract", required_argument, NULL, 'e' }, + { "type", required_argument, NULL, 't' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +enum { INVALID, MERGE, EXTRACT }; + +int main(int argc, char *argv[]) +{ + const char *output_path = NULL; + const char *input_path = NULL; + const char *type = NULL; + unsigned short command = INVALID; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "m:e:t:vh", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'm': + command = MERGE; + output_path = optarg; + break; + case 'e': + command = EXTRACT; + input_path = optarg; + break; + case 't': + type = optarg; + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + switch (command) { + case MERGE: + if (argc - optind < 1) { + fprintf(stderr, "input files required\n"); + return EXIT_FAILURE; + } + + command_merge(output_path, argc - optind, argv + optind); + break; + + case EXTRACT: + if (argc - optind > 0) { + fprintf(stderr, "extra arguments not allowed\n"); + return EXIT_FAILURE; + } + + if (!type) { + fprintf(stderr, "no extract type specified\n"); + return EXIT_FAILURE; + } + + if (!strcasecmp(type, "eir")) + command_extract_eir(input_path); + else if (!strcasecmp(type, "ad")) + command_extract_ad(input_path); + else if (!strcasecmp(type, "sdp")) + command_extract_sdp(input_path); + else + fprintf(stderr, "extract type not supported\n"); + break; + + default: + usage(); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/tools/check-selftest.c b/tools/check-selftest.c new file mode 100644 index 0000000..0de6362 --- /dev/null +++ b/tools/check-selftest.c @@ -0,0 +1,72 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +static void check_result(const char *name, const char *pathname) +{ + FILE *fp; + int i; + + for (i = 0; i < 50; i++) { + struct stat st; + + if (!stat(pathname, &st)) { + printf("Found %s selftest result\n", name); + break; + } + + usleep(25 * 1000); + } + + fp = fopen(pathname, "re"); + if (fp) { + char result[32], *ptr; + + ptr = fgets(result, sizeof(result), fp); + fclose(fp); + + ptr = strpbrk(result, "\r\n"); + if (ptr) + *ptr = '\0'; + + printf("%s: %s\n", name, result); + } +} + +int main(int argc, char *argv[]) +{ + check_result("ECDH", "/sys/kernel/debug/bluetooth/selftest_ecdh"); + check_result("SMP", "/sys/kernel/debug/bluetooth/selftest_smp"); + + return 0; +} diff --git a/tools/ciptool.1 b/tools/ciptool.1 new file mode 100644 index 0000000..65d903d --- /dev/null +++ b/tools/ciptool.1 @@ -0,0 +1,68 @@ +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.\" +.\" +.TH CIPTOOL 1 "JUNE 6, 2003" "" "" + +.SH NAME +ciptool \- Bluetooth Common ISDN Access Profile (CIP) +.SH SYNOPSIS +.BR "ciptool +[ +.I options +] < +.I command +> +.SH DESCRIPTION +.B ciptool +is used to set up, maintain, and inspect the CIP configuration +of the Bluetooth subsystem in the Linux kernel. +.SH OPTIONS +.TP +.BI -h +Gives a list of possible commands. +.TP +.BI -i " | " +The command is applied to device +.I +hciX +, which must be the name or the address of an installed Bluetooth +device. If not specified, the command will be use the first +available Bluetooth device. +.SH COMMANDS +.TP +.BI show +Display information about the connected devices. +.TP +.BI search +Search for Bluetooth devices and connect to first one that +offers CIP support. +.TP +.BI connect " [psm]" +Connect the local device to the remote Bluetooth device on the +specified PSM number. If no PSM is specified, it will use the +SDP to retrieve it from the remote device. +.TP +.BI release " [bdaddr]" +Release a connection to the specific device. If no address is +given and only one device is connected this will be released. +.TP +.BI loopback " [psm]" +Create a connection to the remote device for Bluetooth testing. +This command will not provide a CAPI controller, because it is +only for testing the CAPI Message Transport Protocol. +.SH AUTHOR +Written by Marcel Holtmann . +.br diff --git a/tools/ciptool.c b/tools/ciptool.c new file mode 100644 index 0000000..b898ae8 --- /dev/null +++ b/tools/ciptool.c @@ -0,0 +1,494 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/l2cap.h" +#include "lib/sdp.h" +#include "lib/sdp_lib.h" +#include "lib/cmtp.h" + +static volatile sig_atomic_t __io_canceled = 0; + +static void sig_hup(int sig) +{ + return; +} + +static void sig_term(int sig) +{ + __io_canceled = 1; +} + +static char *cmtp_state[] = { + "unknown", + "connected", + "open", + "bound", + "listening", + "connecting", + "connecting", + "config", + "disconnecting", + "closed" +}; + +static char *cmtp_flagstostr(uint32_t flags) +{ + static char str[100] = ""; + + strcat(str, "["); + + if (flags & (1 << CMTP_LOOPBACK)) + strcat(str, "loopback"); + + strcat(str, "]"); + + return str; +} + +static int get_psm(bdaddr_t *src, bdaddr_t *dst, unsigned short *psm) +{ + sdp_session_t *s; + sdp_list_t *srch, *attrs, *rsp; + uuid_t svclass; + uint16_t attr; + int err; + + if (!(s = sdp_connect(src, dst, 0))) + return -1; + + sdp_uuid16_create(&svclass, CIP_SVCLASS_ID); + srch = sdp_list_append(NULL, &svclass); + + attr = SDP_ATTR_PROTO_DESC_LIST; + attrs = sdp_list_append(NULL, &attr); + + err = sdp_service_search_attr_req(s, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp); + + sdp_close(s); + + if (err) + return 0; + + for (; rsp; rsp = rsp->next) { + sdp_record_t *rec = (sdp_record_t *) rsp->data; + sdp_list_t *protos; + + if (!sdp_get_access_protos(rec, &protos)) { + unsigned short p = sdp_get_proto_port(protos, L2CAP_UUID); + if (p > 0) { + *psm = p; + return 1; + } + } + } + + return 0; +} + +static int do_connect(int ctl, int dev_id, bdaddr_t *src, bdaddr_t *dst, unsigned short psm, uint32_t flags) +{ + struct cmtp_connadd_req req; + struct hci_dev_info di; + struct sockaddr_l2 addr; + struct l2cap_options opts; + socklen_t size; + int sk; + + hci_devinfo(dev_id, &di); + if (!(di.link_policy & HCI_LP_RSWITCH)) { + printf("Local device is not accepting role switch\n"); + } + + if ((sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) { + perror("Can't create L2CAP socket"); + exit(1); + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + + if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("Can't bind L2CAP socket"); + close(sk); + exit(1); + } + + memset(&opts, 0, sizeof(opts)); + size = sizeof(opts); + + if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &size) < 0) { + perror("Can't get L2CAP options"); + close(sk); + exit(1); + } + + opts.imtu = CMTP_DEFAULT_MTU; + opts.omtu = CMTP_DEFAULT_MTU; + opts.flush_to = 0xffff; + + if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) { + perror("Can't set L2CAP options"); + close(sk); + exit(1); + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + addr.l2_psm = htobs(psm); + + if (connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("Can't connect L2CAP socket"); + close(sk); + exit(1); + } + + req.sock = sk; + req.flags = flags; + + if (ioctl(ctl, CMTPCONNADD, &req) < 0) { + perror("Can't create connection"); + exit(1); + } + + return sk; +} + +static void cmd_show(int ctl, bdaddr_t *bdaddr, int argc, char **argv) +{ + struct cmtp_connlist_req req; + struct cmtp_conninfo ci[16]; + char addr[18]; + unsigned int i; + + req.cnum = 16; + req.ci = ci; + + if (ioctl(ctl, CMTPGETCONNLIST, &req) < 0) { + perror("Can't get connection list"); + exit(1); + } + + for (i = 0; i < req.cnum; i++) { + ba2str(&ci[i].bdaddr, addr); + printf("%d %s %s %s\n", ci[i].num, addr, + cmtp_state[ci[i].state], + ci[i].flags ? cmtp_flagstostr(ci[i].flags) : ""); + } +} + +static void cmd_search(int ctl, bdaddr_t *bdaddr, int argc, char **argv) +{ + inquiry_info *info = NULL; + bdaddr_t src, dst; + unsigned short psm; + int i, dev_id, num_rsp, length, flags; + char addr[18]; + uint8_t class[3]; + + ba2str(bdaddr, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) { + dev_id = hci_get_route(NULL); + hci_devba(dev_id, &src); + } else + bacpy(&src, bdaddr); + + length = 8; /* ~10 seconds */ + num_rsp = 0; + flags = 0; + + printf("Searching ...\n"); + + num_rsp = hci_inquiry(dev_id, length, num_rsp, NULL, &info, flags); + + for (i = 0; i < num_rsp; i++) { + memcpy(class, (info+i)->dev_class, 3); + if ((class[1] == 2) && ((class[0] / 4) == 5)) { + bacpy(&dst, &(info+i)->bdaddr); + ba2str(&dst, addr); + + printf("\tChecking service for %s\n", addr); + if (!get_psm(&src, &dst, &psm)) + continue; + + bt_free(info); + + printf("\tConnecting to device %s\n", addr); + do_connect(ctl, dev_id, &src, &dst, psm, 0); + return; + } + } + + bt_free(info); + fprintf(stderr, "\tNo devices in range or visible\n"); + exit(1); +} + +static void cmd_create(int ctl, bdaddr_t *bdaddr, int argc, char **argv) +{ + bdaddr_t src, dst; + unsigned short psm; + int dev_id; + char addr[18]; + + if (argc < 2) + return; + + str2ba(argv[1], &dst); + + ba2str(bdaddr, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) { + dev_id = hci_get_route(&dst); + hci_devba(dev_id, &src); + } else + bacpy(&src, bdaddr); + + if (argc < 3) { + if (!get_psm(&src, &dst, &psm)) + psm = 4099; + } else + psm = atoi(argv[2]); + + do_connect(ctl, dev_id, &src, &dst, psm, 0); +} + +static void cmd_release(int ctl, bdaddr_t *bdaddr, int argc, char **argv) +{ + struct cmtp_conndel_req req; + struct cmtp_connlist_req cl; + struct cmtp_conninfo ci[16]; + + if (argc < 2) { + cl.cnum = 16; + cl.ci = ci; + + if (ioctl(ctl, CMTPGETCONNLIST, &cl) < 0) { + perror("Can't get connection list"); + exit(1); + } + + if (cl.cnum == 0) + return; + + if (cl.cnum != 1) { + fprintf(stderr, "You have to specifiy the device address.\n"); + exit(1); + } + + bacpy(&req.bdaddr, &ci[0].bdaddr); + } else + str2ba(argv[1], &req.bdaddr); + + if (ioctl(ctl, CMTPCONNDEL, &req) < 0) { + perror("Can't release connection"); + exit(1); + } +} + +static void cmd_loopback(int ctl, bdaddr_t *bdaddr, int argc, char **argv) +{ + struct cmtp_conndel_req req; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + bdaddr_t src, dst; + unsigned short psm; + int dev_id, sk; + char addr[18]; + + if (argc < 2) + return; + + str2ba(argv[1], &dst); + + ba2str(bdaddr, addr); + dev_id = hci_devid(addr); + if (dev_id < 0) { + dev_id = hci_get_route(&dst); + hci_devba(dev_id, &src); + } else + bacpy(&src, bdaddr); + + ba2str(&dst, addr); + printf("Connecting to %s in loopback mode\n", addr); + + if (argc < 3) { + if (!get_psm(&src, &dst, &psm)) + psm = 4099; + } else + psm = atoi(argv[2]); + + sk = do_connect(ctl, dev_id, &src, &dst, psm, (1 << CMTP_LOOPBACK)); + + printf("Press CTRL-C for hangup\n"); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + p.fd = sk; + p.events = POLLERR | POLLHUP; + + while (!__io_canceled) { + p.revents = 0; + if (ppoll(&p, 1, NULL, &sigs) > 0) + break; + } + + bacpy(&req.bdaddr, &dst); + ioctl(ctl, CMTPCONNDEL, &req); +} + +static struct { + char *cmd; + char *alt; + void (*func)(int ctl, bdaddr_t *bdaddr, int argc, char **argv); + char *opt; + char *doc; +} command[] = { + { "show", "list", cmd_show, 0, "Show remote connections" }, + { "search", "scan", cmd_search, 0, "Search for a remote device" }, + { "connect", "create", cmd_create, "", "Connect a remote device" }, + { "release", "disconnect", cmd_release, "[bdaddr]", "Disconnect the remote device" }, + { "loopback", "test", cmd_loopback, "", "Loopback test of a device" }, + { NULL, NULL, NULL, 0, 0 } +}; + +static void usage(void) +{ + int i; + + printf("ciptool - Bluetooth Common ISDN Access Profile (CIP)\n\n"); + + printf("Usage:\n" + "\tciptool [options] [command]\n" + "\n"); + + printf("Options:\n" + "\t-i [hciX|bdaddr] Local HCI device or BD Address\n" + "\t-h, --help Display help\n" + "\n"); + + printf("Commands:\n"); + for (i = 0; command[i].cmd; i++) + printf("\t%-8s %-10s\t%s\n", command[i].cmd, + command[i].opt ? command[i].opt : " ", + command[i].doc); + printf("\n"); +} + +static struct option main_options[] = { + { "help", 0, 0, 'h' }, + { "device", 1, 0, 'i' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + bdaddr_t bdaddr; + int i, opt, ctl; + + bacpy(&bdaddr, BDADDR_ANY); + + while ((opt = getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) { + switch(opt) { + case 'i': + if (!strncmp(optarg, "hci", 3)) + hci_devba(atoi(optarg + 3), &bdaddr); + else + str2ba(optarg, &bdaddr); + break; + case 'h': + usage(); + exit(0); + default: + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + if (argc < 1) { + usage(); + return 0; + } + + if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_CMTP)) < 0 ) { + perror("Can't open CMTP control socket"); + exit(1); + } + + for (i = 0; command[i].cmd; i++) { + if (strncmp(command[i].cmd, argv[0], 4) && strncmp(command[i].alt, argv[0], 4)) + continue; + command[i].func(ctl, &bdaddr, argc, argv); + close(ctl); + exit(0); + } + + usage(); + + close(ctl); + + return 0; +} diff --git a/tools/cltest.c b/tools/cltest.c new file mode 100644 index 0000000..44a17a8 --- /dev/null +++ b/tools/cltest.c @@ -0,0 +1,278 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/l2cap.h" + +#include "src/shared/mainloop.h" + +static bool send_message(const bdaddr_t *src, const bdaddr_t *dst, + uint16_t psm) +{ + const unsigned char buf[] = { 0x42, 0x23 }; + struct sockaddr_l2 addr; + ssize_t len; + int fd; + + fd = socket(PF_BLUETOOTH, SOCK_DGRAM | SOCK_CLOEXEC, BTPROTO_L2CAP); + if (fd < 0) { + perror("Failed to create transmitter socket"); + return false; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind transmitter socket"); + close(fd); + return false; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + addr.l2_psm = htobs(psm); + + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to connect transmitter socket"); + close(fd); + return false; + } + + len = send(fd, buf, sizeof(buf), 0); + if (len < 0) { + perror("Failed to send message"); + close(fd); + return false; + } + + return true; +} + +static void receiver_callback(int fd, uint32_t events, void *user_data) +{ + unsigned char buf[512]; + struct sockaddr_l2 addr; + socklen_t addrlen = sizeof(addr); + char str[18]; + ssize_t len, i; + + if (events & (EPOLLERR | EPOLLHUP)) { + close(fd); + mainloop_remove_fd(fd); + return; + } + + len = recvfrom(fd, buf, sizeof(buf), 0, + (struct sockaddr *) &addr, &addrlen); + if (len < 0) { + perror("Failed to receive data"); + return; + } + + if (addrlen > 0) { + ba2str(&addr.l2_bdaddr, str); + printf("RX Address: %s PSM: %d CID: %d\n", str, + btohs(addr.l2_psm), btohs(addr.l2_cid)); + } + + printf("RX Data:"); + for (i = 0; i < len; i++) + printf(" 0x%02x", buf[i]); + printf("\n"); +} + +static bool create_receiver(const bdaddr_t *bdaddr, uint16_t psm) +{ + struct sockaddr_l2 addr; + int fd; + + fd = socket(PF_BLUETOOTH, SOCK_DGRAM | SOCK_CLOEXEC, BTPROTO_L2CAP); + if (fd < 0) { + perror("Failed to create receiver socket"); + return false; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, bdaddr); + addr.l2_psm = htobs(psm); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Failed to bind receiver socket"); + close(fd); + return false; + } + + mainloop_add_fd(fd, EPOLLIN, receiver_callback, NULL, NULL); + + return true; +} + +static bool activate_controller(int fd, struct hci_dev_info *di) +{ + if (!hci_test_bit(HCI_UP, &di->flags)) { + char addr[18]; + + ba2str(&di->bdaddr, addr); + printf("Activating controller %s\n", addr); + + if (ioctl(fd, HCIDEVUP, di->dev_id) < 0) { + if (errno != EALREADY) { + perror("Failed to bring up HCI device"); + return false; + } + } + } + return true; +} + +static bool enable_connections(int fd, struct hci_dev_info *di) +{ + if (!hci_test_bit(HCI_PSCAN, &di->flags)) { + struct hci_dev_req dr; + char addr[18]; + + ba2str(&di->bdaddr, addr); + printf("Enabling connections on %s\n", addr); + + dr.dev_id = di->dev_id; + dr.dev_opt = SCAN_PAGE; + + if (ioctl(fd, HCISETSCAN, (unsigned long) &dr) < 0) { + perror("Failed to enable connections"); + return false; + } + } + return true; +} + +static bdaddr_t bdaddr_src; +static bdaddr_t bdaddr_dst; + +static bool find_controllers(void) +{ + struct hci_dev_list_req *dl; + struct hci_dev_req *dr; + bool result; + int fd, i; + + fd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (fd < 0) { + perror("Failed to open raw HCI socket"); + return false; + } + + dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + sizeof(uint16_t)); + if (!dl) { + perror("Failed allocate HCI device request memory"); + close(fd); + return false; + } + + dl->dev_num = HCI_MAX_DEV; + dr = dl->dev_req; + + if (ioctl(fd, HCIGETDEVLIST, (void *) dl) < 0) { + perror("Failed to get HCI device list"); + result = false; + goto done; + } + + result = true; + + for (i = 0; i< dl->dev_num && result; i++) { + struct hci_dev_info di; + + di.dev_id = (dr + i)->dev_id; + + if (ioctl(fd, HCIGETDEVINFO, (void *) &di) < 0) + continue; + + if (((di.type & 0x30) >> 4) != HCI_PRIMARY) + continue; + + if (!bacmp(&bdaddr_src, BDADDR_ANY)) { + bacpy(&bdaddr_src, &di.bdaddr); + result = activate_controller(fd, &di); + } else if (!bacmp(&bdaddr_dst, BDADDR_ANY)) { + bacpy(&bdaddr_dst, &di.bdaddr); + result = activate_controller(fd, &di); + if (result) + result = enable_connections(fd, &di); + } + } + +done: + free(dl); + close(fd); + return result; +} + +int main(int argc ,char *argv[]) +{ + char addr_src[18], addr_dst[18]; + + bacpy(&bdaddr_src, BDADDR_ANY); + bacpy(&bdaddr_dst, BDADDR_ANY); + + if (!find_controllers()) + return EXIT_FAILURE; + + if (!bacmp(&bdaddr_src, BDADDR_ANY) || + !bacmp(&bdaddr_dst, BDADDR_ANY)) { + fprintf(stderr, "Two controllers are required\n"); + return EXIT_FAILURE; + } + + ba2str(&bdaddr_src, addr_src); + ba2str(&bdaddr_dst, addr_dst); + + printf("%s -> %s\n", addr_src, addr_dst); + + mainloop_init(); + + create_receiver(&bdaddr_dst, 0x0021); + send_message(&bdaddr_src, &bdaddr_dst, 0x0021); + + return mainloop_run(); +} diff --git a/tools/create-image.c b/tools/create-image.c new file mode 100644 index 0000000..ca9d011 --- /dev/null +++ b/tools/create-image.c @@ -0,0 +1,206 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012-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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The "new" ASCII format uses 8-byte hexadecimal fields for all numbers and + * separates device numbers into separate fields for major and minor numbers. + * + * struct cpio_newc_header { + * char c_magic[6]; + * char c_ino[8]; + * char c_mode[8]; + * char c_uid[8]; + * char c_gid[8]; + * char c_nlink[8]; + * char c_mtime[8]; + * char c_filesize[8]; + * char c_devmajor[8]; + * char c_devminor[8]; + * char c_rdevmajor[8]; + * char c_rdevminor[8]; + * char c_namesize[8]; + * char c_check[8]; + * }; + * + */ + +#define HDR_FMT "%s%08X%08X%08X%08X%08X%08X%08jX%08X%08X%08X%08X%08X%08X%s" + +#define HDR_MAGIC "070701" + +static unsigned int ino_cnt = 721; + +#define REG_EXE S_IFREG | \ + S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH + +static const struct { + const char *source; + const char *target; + mode_t mode; +} file_list[] = { + { "tools/test-runner", "init", REG_EXE }, + { } +}; + +static void write_block(FILE *fp, const char *pathname, unsigned int ino, + mode_t mode, const char *name) +{ + int i, pad, namelen = strlen(name); + struct stat st; + void *map; + int fd; + + if (!pathname) { + fd = -1; + map = NULL; + st.st_size = 0; + goto done; + } + + fd = open(pathname, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + fd = -1; + map = NULL; + st.st_size = 0; + goto done; + } + + if (fstat(fd, &st) < 0) { + close(fd); + fd = -1; + map = NULL; + st.st_size = 0; + goto done; + } + + map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + close(fd); + fd = -1; + map = NULL; + st.st_size = 0; + } + +done: + fprintf(fp, HDR_FMT, HDR_MAGIC, ino, mode, 0, 0, 1, 0, + (uintmax_t) st.st_size, 0, 0, 0, 0, namelen + 1, 0, name); + + pad = 4 - ((110 + namelen) % 4); + for (i = 0; i < pad; i++) + fputc(0, fp); + + if (st.st_size > 0) { + fwrite(map, st.st_size, 1, fp); + + pad = 3 - ((st.st_size + 3) % 4); + for (i = 0; i < pad; i++) + fputc(0, fp); + + munmap(map, st.st_size); + close(fd); + } +} + +static void usage(void) +{ + printf("create-image - CPIO image creation utility\n" + "Usage:\n"); + printf("\tcreate-image [options]\n"); + printf("Options:\n" + "\t-o, --output Output CPIO image\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "output", required_argument, NULL, 'o' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + const char *output_pathname = NULL; + FILE *fp; + int i; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "o:vh", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'o': + output_pathname = optarg; + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind > 0) { + fprintf(stderr, "Invalid command line parameters\n"); + return EXIT_FAILURE; + } + + if (!output_pathname) { + fprintf(stderr, "Failed to specify output file\n"); + return EXIT_FAILURE; + } + + fp = fopen(output_pathname, "we"); + + for (i = 0; file_list[i].source; i++) + write_block(fp, file_list[i].source, ino_cnt++, + file_list[i].mode, file_list[i].target); + + write_block(fp, NULL, ino_cnt++, 0, "TRAILER!!!"); + + fclose(fp); + + return EXIT_SUCCESS; +} diff --git a/tools/csr.c b/tools/csr.c new file mode 100644 index 0000000..61bdaa0 --- /dev/null +++ b/tools/csr.c @@ -0,0 +1,2856 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "csr.h" + +struct psr_data { + uint16_t pskey; + uint8_t *value; + uint8_t size; + struct psr_data *next; +}; + +static struct psr_data *head = NULL, *tail = NULL; + +static struct { + uint16_t id; + char *str; +} csr_map[] = { + { 66, "HCI 9.8" }, + { 97, "HCI 10.3" }, + { 101, "HCI 10.5" }, + { 111, "HCI 11.0" }, + { 112, "HCI 11.1" }, + { 114, "HCI 11.2" }, + { 115, "HCI 11.3" }, + { 117, "HCI 12.0" }, + { 119, "HCI 12.1" }, + { 133, "HCI 12.2" }, + { 134, "HCI 12.3" }, + { 162, "HCI 12.4" }, + { 165, "HCI 12.5" }, + { 169, "HCI 12.6" }, + { 188, "HCI 12.7" }, + { 218, "HCI 12.8" }, + { 283, "HCI 12.9" }, + { 203, "HCI 13.2" }, + { 204, "HCI 13.2" }, + { 210, "HCI 13.3" }, + { 211, "HCI 13.3" }, + { 213, "HCI 13.4" }, + { 214, "HCI 13.4" }, + { 225, "HCI 13.5" }, + { 226, "HCI 13.5" }, + { 237, "HCI 13.6" }, + { 238, "HCI 13.6" }, + { 242, "HCI 14.0" }, + { 243, "HCI 14.0" }, + { 244, "HCI 14.0" }, + { 245, "HCI 14.0" }, + { 254, "HCI 13.7" }, + { 255, "HCI 13.7" }, + { 264, "HCI 14.1" }, + { 265, "HCI 14.1" }, + { 267, "HCI 14.2" }, + { 268, "HCI 14.2" }, + { 272, "HCI 14.3" }, + { 273, "HCI 14.3" }, + { 274, "HCI 13.8" }, + { 275, "HCI 13.8" }, + { 286, "HCI 13.9" }, + { 287, "HCI 13.9" }, + { 309, "HCI 13.10" }, + { 310, "HCI 13.10" }, + { 313, "HCI 14.4" }, + { 314, "HCI 14.4" }, + { 323, "HCI 14.5" }, + { 324, "HCI 14.5" }, + { 336, "HCI 14.6" }, + { 337, "HCI 14.6" }, + { 351, "HCI 13.11" }, + { 352, "HCI 13.11" }, + { 362, "HCI 15.0" }, + { 363, "HCI 15.0" }, + { 364, "HCI 15.0" }, + { 365, "HCI 15.0" }, + { 373, "HCI 14.7" }, + { 374, "HCI 14.7" }, + { 379, "HCI 15.1" }, + { 380, "HCI 15.1" }, + { 381, "HCI 15.1" }, + { 382, "HCI 15.1" }, + { 392, "HCI 15.2" }, + { 393, "HCI 15.2" }, + { 394, "HCI 15.2" }, + { 395, "HCI 15.2" }, + { 436, "HCI 16.0" }, + { 437, "HCI 16.0" }, + { 438, "HCI 16.0" }, + { 439, "HCI 16.0" }, + { 443, "HCI 15.3" }, + { 444, "HCI 15.3" }, + { 465, "HCI 16.1" }, + { 466, "HCI 16.1" }, + { 467, "HCI 16.1" }, + { 468, "HCI 16.1" }, + { 487, "HCI 14.8" }, + { 488, "HCI 14.8" }, + { 492, "HCI 16.2" }, + { 493, "HCI 16.2" }, + { 495, "HCI 16.2" }, + { 496, "HCI 16.2" }, + { 502, "HCI 16.1.1" }, + { 503, "HCI 16.1.1" }, + { 504, "HCI 16.1.1" }, + { 505, "HCI 16.1.1" }, + { 506, "HCI 16.1.2" }, + { 507, "HCI 16.1.2" }, + { 508, "HCI 16.1.2" }, + { 509, "HCI 16.1.2" }, + { 516, "HCI 16.3" }, + { 517, "HCI 16.3" }, + { 518, "HCI 16.3" }, + { 519, "HCI 16.3" }, + { 523, "HCI 16.4" }, + { 524, "HCI 16.4" }, + { 525, "HCI 16.4" }, + { 526, "HCI 16.4" }, + { 553, "HCI 15.3" }, + { 554, "HCI 15.3" }, + { 562, "HCI 16.5" }, + { 563, "HCI 16.5" }, + { 564, "HCI 16.5" }, + { 565, "HCI 16.5" }, + { 593, "HCI 17.0" }, + { 594, "HCI 17.0" }, + { 595, "HCI 17.0" }, + { 599, "HCI 17.0" }, + { 600, "HCI 17.0" }, + { 608, "HCI 13.10.1" }, + { 609, "HCI 13.10.1" }, + { 613, "HCI 17.1" }, + { 614, "HCI 17.1" }, + { 615, "HCI 17.1" }, + { 616, "HCI 17.1" }, + { 618, "HCI 17.1" }, + { 624, "HCI 17.2" }, + { 625, "HCI 17.2" }, + { 626, "HCI 17.2" }, + { 627, "HCI 17.2" }, + { 637, "HCI 16.6" }, + { 638, "HCI 16.6" }, + { 639, "HCI 16.6" }, + { 640, "HCI 16.6" }, + { 642, "HCI 13.10.2" }, + { 643, "HCI 13.10.2" }, + { 644, "HCI 13.10.3" }, + { 645, "HCI 13.10.3" }, + { 668, "HCI 13.10.4" }, + { 669, "HCI 13.10.4" }, + { 681, "HCI 16.7" }, + { 682, "HCI 16.7" }, + { 683, "HCI 16.7" }, + { 684, "HCI 16.7" }, + { 704, "HCI 16.8" }, + { 718, "HCI 16.4.1" }, + { 719, "HCI 16.4.1" }, + { 720, "HCI 16.4.1" }, + { 721, "HCI 16.4.1" }, + { 722, "HCI 16.7.1" }, + { 723, "HCI 16.7.1" }, + { 724, "HCI 16.7.1" }, + { 725, "HCI 16.7.1" }, + { 731, "HCI 16.7.2" }, + { 732, "HCI 16.7.2" }, + { 733, "HCI 16.7.2" }, + { 734, "HCI 16.7.2" }, + { 735, "HCI 16.4.2" }, + { 736, "HCI 16.4.2" }, + { 737, "HCI 16.4.2" }, + { 738, "HCI 16.4.2" }, + { 750, "HCI 16.7.3" }, + { 751, "HCI 16.7.3" }, + { 752, "HCI 16.7.3" }, + { 753, "HCI 16.7.3" }, + { 760, "HCI 16.7.4" }, + { 761, "HCI 16.7.4" }, + { 762, "HCI 16.7.4" }, + { 763, "HCI 16.7.4" }, + { 770, "HCI 16.9" }, + { 771, "HCI 16.9" }, + { 772, "HCI 16.9" }, + { 773, "HCI 16.9" }, + { 774, "HCI 17.3" }, + { 775, "HCI 17.3" }, + { 776, "HCI 17.3" }, + { 777, "HCI 17.3" }, + { 781, "HCI 16.7.5" }, + { 786, "HCI 16.10" }, + { 787, "HCI 16.10" }, + { 788, "HCI 16.10" }, + { 789, "HCI 16.10" }, + { 791, "HCI 16.4.3" }, + { 792, "HCI 16.4.3" }, + { 793, "HCI 16.4.3" }, + { 794, "HCI 16.4.3" }, + { 798, "HCI 16.11" }, + { 799, "HCI 16.11" }, + { 800, "HCI 16.11" }, + { 801, "HCI 16.11" }, + { 806, "HCI 16.7.5" }, + { 807, "HCI 16.12" }, + { 808, "HCI 16.12" }, + { 809, "HCI 16.12" }, + { 810, "HCI 16.12" }, + { 817, "HCI 16.13" }, + { 818, "HCI 16.13" }, + { 819, "HCI 16.13" }, + { 820, "HCI 16.13" }, + { 823, "HCI 13.10.5" }, + { 824, "HCI 13.10.5" }, + { 826, "HCI 16.14" }, + { 827, "HCI 16.14" }, + { 828, "HCI 16.14" }, + { 829, "HCI 16.14" }, + { 843, "HCI 17.3.1" }, + { 856, "HCI 17.3.2" }, + { 857, "HCI 17.3.2" }, + { 858, "HCI 17.3.2" }, + { 1120, "HCI 17.11" }, + { 1168, "HCI 18.1" }, + { 1169, "HCI 18.1" }, + { 1241, "HCI 18.x" }, + { 1298, "HCI 18.2" }, + { 1354, "HCI 18.2" }, + { 1392, "HCI 18.2" }, + { 1393, "HCI 18.2" }, + { 1501, "HCI 18.2" }, + { 1503, "HCI 18.2" }, + { 1504, "HCI 18.2" }, + { 1505, "HCI 18.2" }, + { 1506, "HCI 18.2" }, + { 1520, "HCI 18.2" }, + { 1586, "HCI 18.2" }, + { 1591, "HCI 18.2" }, + { 1592, "HCI 18.2" }, + { 1593, "HCI 18.2.1" }, + { 1733, "HCI 18.3" }, + { 1734, "HCI 18.3" }, + { 1735, "HCI 18.3" }, + { 1737, "HCI 18.3" }, + { 1915, "HCI 19.2" }, + { 1916, "HCI 19.2" }, + { 1958, "HCI 19.2" }, + { 1981, "Unified 20a" }, + { 1982, "Unified 20a" }, + { 1989, "HCI 18.4" }, + { 2062, "Unified 20a1" }, + { 2063, "Unified 20a1" }, + { 2067, "Unified 18f" }, + { 2068, "Unified 18f" }, + { 2243, "Unified 18e" }, + { 2244, "Unified 18e" }, + { 2258, "Unified 20d" }, + { 2259, "Unified 20d" }, + { 2361, "Unified 20e" }, + { 2362, "Unified 20e" }, + { 2386, "Unified 21a" }, + { 2387, "Unified 21a" }, + { 2423, "Unified 21a" }, + { 2424, "Unified 21a" }, + { 2623, "Unified 21c" }, + { 2624, "Unified 21c" }, + { 2625, "Unified 21c" }, + { 2626, "Unified 21c" }, + { 2627, "Unified 21c" }, + { 2628, "Unified 21c" }, + { 2629, "Unified 21c" }, + { 2630, "Unified 21c" }, + { 2631, "Unified 21c" }, + { 2632, "Unified 21c" }, + { 2633, "Unified 21c" }, + { 2634, "Unified 21c" }, + { 2635, "Unified 21c" }, + { 2636, "Unified 21c" }, + { 2649, "Unified 21c" }, + { 2650, "Unified 21c" }, + { 2651, "Unified 21c" }, + { 2652, "Unified 21c" }, + { 2653, "Unified 21c" }, + { 2654, "Unified 21c" }, + { 2655, "Unified 21c" }, + { 2656, "Unified 21c" }, + { 2658, "Unified 21c" }, + { 3057, "Unified 21d" }, + { 3058, "Unified 21d" }, + { 3059, "Unified 21d" }, + { 3060, "Unified 21d" }, + { 3062, "Unified 21d" }, + { 3063, "Unified 21d" }, + { 3064, "Unified 21d" }, + { 3164, "Unified 21e" }, + { 3413, "Unified 21f" }, + { 3414, "Unified 21f" }, + { 3415, "Unified 21f" }, + { 3424, "Unified 21f" }, + { 3454, "Unified 21f" }, + { 3684, "Unified 21f" }, + { 3764, "Unified 21f" }, + { 4276, "Unified 22b" }, + { 4277, "Unified 22b" }, + { 4279, "Unified 22b" }, + { 4281, "Unified 22b" }, + { 4282, "Unified 22b" }, + { 4283, "Unified 22b" }, + { 4284, "Unified 22b" }, + { 4285, "Unified 22b" }, + { 4289, "Unified 22b" }, + { 4290, "Unified 22b" }, + { 4291, "Unified 22b" }, + { 4292, "Unified 22b" }, + { 4293, "Unified 22b" }, + { 4294, "Unified 22b" }, + { 4295, "Unified 22b" }, + { 4363, "Unified 22c" }, + { 4373, "Unified 22c" }, + { 4374, "Unified 22c" }, + { 4532, "Unified 22d" }, + { 4533, "Unified 22d" }, + { 4698, "Unified 23c" }, + { 4839, "Unified 23c" }, + { 4841, "Unified 23c" }, + { 4866, "Unified 23c" }, + { 4867, "Unified 23c" }, + { 4868, "Unified 23c" }, + { 4869, "Unified 23c" }, + { 4870, "Unified 23c" }, + { 4871, "Unified 23c" }, + { 4872, "Unified 23c" }, + { 4874, "Unified 23c" }, + { 4875, "Unified 23c" }, + { 4876, "Unified 23c" }, + { 4877, "Unified 23c" }, + { 2526, "Marcel 1 (2005-09-26)" }, + { 2543, "Marcel 2 (2005-09-28)" }, + { 2622, "Marcel 3 (2005-10-27)" }, + { 3326, "Marcel 4 (2006-06-16)" }, + { 3612, "Marcel 5 (2006-10-24)" }, + { 4509, "Marcel 6 (2007-06-11)" }, + { 5417, "Marcel 7 (2008-08-26)" }, + { 195, "Sniff 1 (2001-11-27)" }, + { 220, "Sniff 2 (2002-01-03)" }, + { 269, "Sniff 3 (2002-02-22)" }, + { 270, "Sniff 4 (2002-02-26)" }, + { 284, "Sniff 5 (2002-03-12)" }, + { 292, "Sniff 6 (2002-03-20)" }, + { 305, "Sniff 7 (2002-04-12)" }, + { 306, "Sniff 8 (2002-04-12)" }, + { 343, "Sniff 9 (2002-05-02)" }, + { 346, "Sniff 10 (2002-05-03)" }, + { 355, "Sniff 11 (2002-05-16)" }, + { 256, "Sniff 11 (2002-05-16)" }, + { 390, "Sniff 12 (2002-06-26)" }, + { 450, "Sniff 13 (2002-08-16)" }, + { 451, "Sniff 13 (2002-08-16)" }, + { 533, "Sniff 14 (2002-10-11)" }, + { 580, "Sniff 15 (2002-11-14)" }, + { 623, "Sniff 16 (2002-12-12)" }, + { 678, "Sniff 17 (2003-01-29)" }, + { 847, "Sniff 18 (2003-04-17)" }, + { 876, "Sniff 19 (2003-06-10)" }, + { 997, "Sniff 22 (2003-09-05)" }, + { 1027, "Sniff 23 (2003-10-03)" }, + { 1029, "Sniff 24 (2003-10-03)" }, + { 1112, "Sniff 25 (2003-12-03)" }, + { 1113, "Sniff 25 (2003-12-03)" }, + { 1133, "Sniff 26 (2003-12-18)" }, + { 1134, "Sniff 26 (2003-12-18)" }, + { 1223, "Sniff 27 (2004-03-08)" }, + { 1224, "Sniff 27 (2004-03-08)" }, + { 1319, "Sniff 31 (2004-04-22)" }, + { 1320, "Sniff 31 (2004-04-22)" }, + { 1427, "Sniff 34 (2004-06-16)" }, + { 1508, "Sniff 35 (2004-07-19)" }, + { 1509, "Sniff 35 (2004-07-19)" }, + { 1587, "Sniff 36 (2004-08-18)" }, + { 1588, "Sniff 36 (2004-08-18)" }, + { 1641, "Sniff 37 (2004-09-16)" }, + { 1642, "Sniff 37 (2004-09-16)" }, + { 1699, "Sniff 38 (2004-10-07)" }, + { 1700, "Sniff 38 (2004-10-07)" }, + { 1752, "Sniff 39 (2004-11-02)" }, + { 1753, "Sniff 39 (2004-11-02)" }, + { 1759, "Sniff 40 (2004-11-03)" }, + { 1760, "Sniff 40 (2004-11-03)" }, + { 1761, "Sniff 40 (2004-11-03)" }, + { 2009, "Sniff 41 (2005-04-06)" }, + { 2010, "Sniff 41 (2005-04-06)" }, + { 2011, "Sniff 41 (2005-04-06)" }, + { 2016, "Sniff 42 (2005-04-11)" }, + { 2017, "Sniff 42 (2005-04-11)" }, + { 2018, "Sniff 42 (2005-04-11)" }, + { 2023, "Sniff 43 (2005-04-14)" }, + { 2024, "Sniff 43 (2005-04-14)" }, + { 2025, "Sniff 43 (2005-04-14)" }, + { 2032, "Sniff 44 (2005-04-18)" }, + { 2033, "Sniff 44 (2005-04-18)" }, + { 2034, "Sniff 44 (2005-04-18)" }, + { 2288, "Sniff 45 (2005-07-08)" }, + { 2289, "Sniff 45 (2005-07-08)" }, + { 2290, "Sniff 45 (2005-07-08)" }, + { 2388, "Sniff 46 (2005-08-17)" }, + { 2389, "Sniff 46 (2005-08-17)" }, + { 2390, "Sniff 46 (2005-08-17)" }, + { 2869, "Sniff 47 (2006-02-15)" }, + { 2870, "Sniff 47 (2006-02-15)" }, + { 2871, "Sniff 47 (2006-02-15)" }, + { 3214, "Sniff 48 (2006-05-16)" }, + { 3215, "Sniff 48 (2006-05-16)" }, + { 3216, "Sniff 48 (2006-05-16)" }, + { 3356, "Sniff 49 (2006-07-17)" }, + { 3529, "Sniff 50 (2006-09-21)" }, + { 3546, "Sniff 51 (2006-09-29)" }, + { 3683, "Sniff 52 (2006-11-03)" }, + { 0, } +}; + +char *csr_builddeftostr(uint16_t def) +{ + switch (def) { + case 0x0000: + return "NONE"; + case 0x0001: + return "CHIP_BASE_BC01"; + case 0x0002: + return "CHIP_BASE_BC02"; + case 0x0003: + return "CHIP_BC01B"; + case 0x0004: + return "CHIP_BC02_EXTERNAL"; + case 0x0005: + return "BUILD_HCI"; + case 0x0006: + return "BUILD_RFCOMM"; + case 0x0007: + return "BT_VER_1_1"; + case 0x0008: + return "TRANSPORT_ALL"; + case 0x0009: + return "TRANSPORT_BCSP"; + case 0x000a: + return "TRANSPORT_H4"; + case 0x000b: + return "TRANSPORT_USB"; + case 0x000c: + return "MAX_CRYPT_KEY_LEN_56"; + case 0x000d: + return "MAX_CRYPT_KEY_LEN_128"; + case 0x000e: + return "TRANSPORT_USER"; + case 0x000f: + return "CHIP_BC02_KATO"; + case 0x0010: + return "TRANSPORT_NONE"; + case 0x0012: + return "REQUIRE_8MBIT"; + case 0x0013: + return "RADIOTEST"; + case 0x0014: + return "RADIOTEST_LITE"; + case 0x0015: + return "INSTALL_FLASH"; + case 0x0016: + return "INSTALL_EEPROM"; + case 0x0017: + return "INSTALL_COMBO_DOT11"; + case 0x0018: + return "LOWPOWER_TX"; + case 0x0019: + return "TRANSPORT_TWUTL"; + case 0x001a: + return "COMPILER_GCC"; + case 0x001b: + return "CHIP_BC02_CLOUSEAU"; + case 0x001c: + return "CHIP_BC02_TOULOUSE"; + case 0x001d: + return "CHIP_BASE_BC3"; + case 0x001e: + return "CHIP_BC3_NICKNACK"; + case 0x001f: + return "CHIP_BC3_KALIMBA"; + case 0x0020: + return "INSTALL_HCI_MODULE"; + case 0x0021: + return "INSTALL_L2CAP_MODULE"; + case 0x0022: + return "INSTALL_DM_MODULE"; + case 0x0023: + return "INSTALL_SDP_MODULE"; + case 0x0024: + return "INSTALL_RFCOMM_MODULE"; + case 0x0025: + return "INSTALL_HIDIO_MODULE"; + case 0x0026: + return "INSTALL_PAN_MODULE"; + case 0x0027: + return "INSTALL_IPV4_MODULE"; + case 0x0028: + return "INSTALL_IPV6_MODULE"; + case 0x0029: + return "INSTALL_TCP_MODULE"; + case 0x002a: + return "BT_VER_1_2"; + case 0x002b: + return "INSTALL_UDP_MODULE"; + case 0x002c: + return "REQUIRE_0_WAIT_STATES"; + case 0x002d: + return "CHIP_BC3_PADDYWACK"; + case 0x002e: + return "CHIP_BC4_COYOTE"; + case 0x002f: + return "CHIP_BC4_ODDJOB"; + case 0x0030: + return "TRANSPORT_H4DS"; + case 0x0031: + return "CHIP_BASE_BC4"; + default: + return "UNKNOWN"; + } +} + +char *csr_buildidtostr(uint16_t id) +{ + static char str[12]; + int i; + + for (i = 0; csr_map[i].id; i++) + if (csr_map[i].id == id) + return csr_map[i].str; + + snprintf(str, sizeof(str), "Build %d", id); + return str; +} + +char *csr_chipvertostr(uint16_t ver, uint16_t rev) +{ + switch (ver) { + case 0x00: + return "BlueCore01a"; + case 0x01: + switch (rev) { + case 0x64: + return "BlueCore01b (ES)"; + case 0x65: + default: + return "BlueCore01b"; + } + case 0x02: + switch (rev) { + case 0x89: + return "BlueCore02-External (ES2)"; + case 0x8a: + return "BlueCore02-External"; + case 0x28: + return "BlueCore02-ROM/Audio/Flash"; + default: + return "BlueCore02"; + } + case 0x03: + switch (rev) { + case 0x43: + return "BlueCore3-MM"; + case 0x15: + return "BlueCore3-ROM"; + case 0xe2: + return "BlueCore3-Flash"; + case 0x26: + return "BlueCore4-External"; + case 0x30: + return "BlueCore4-ROM"; + default: + return "BlueCore3 or BlueCore4"; + } + default: + return "Unknown"; + } +} + +char *csr_pskeytostr(uint16_t pskey) +{ + switch (pskey) { + case CSR_PSKEY_BDADDR: + return "Bluetooth address"; + case CSR_PSKEY_COUNTRYCODE: + return "Country code"; + case CSR_PSKEY_CLASSOFDEVICE: + return "Class of device"; + case CSR_PSKEY_DEVICE_DRIFT: + return "Device drift"; + case CSR_PSKEY_DEVICE_JITTER: + return "Device jitter"; + case CSR_PSKEY_MAX_ACLS: + return "Maximum ACL links"; + case CSR_PSKEY_MAX_SCOS: + return "Maximum SCO links"; + case CSR_PSKEY_MAX_REMOTE_MASTERS: + return "Maximum remote masters"; + case CSR_PSKEY_ENABLE_MASTERY_WITH_SLAVERY: + return "Support master and slave roles simultaneously"; + case CSR_PSKEY_H_HC_FC_MAX_ACL_PKT_LEN: + return "Maximum HCI ACL packet length"; + case CSR_PSKEY_H_HC_FC_MAX_SCO_PKT_LEN: + return "Maximum HCI SCO packet length"; + case CSR_PSKEY_H_HC_FC_MAX_ACL_PKTS: + return "Maximum number of HCI ACL packets"; + case CSR_PSKEY_H_HC_FC_MAX_SCO_PKTS: + return "Maximum number of HCI SCO packets"; + case CSR_PSKEY_LC_FC_BUFFER_LOW_WATER_MARK: + return "Flow control low water mark"; + case CSR_PSKEY_LC_MAX_TX_POWER: + return "Maximum transmit power"; + case CSR_PSKEY_TX_GAIN_RAMP: + return "Transmit gain ramp rate"; + case CSR_PSKEY_LC_POWER_TABLE: + return "Radio power table"; + case CSR_PSKEY_LC_PEER_POWER_PERIOD: + return "Peer transmit power control interval"; + case CSR_PSKEY_LC_FC_POOLS_LOW_WATER_MARK: + return "Flow control pool low water mark"; + case CSR_PSKEY_LC_DEFAULT_TX_POWER: + return "Default transmit power"; + case CSR_PSKEY_LC_RSSI_GOLDEN_RANGE: + return "RSSI at bottom of golden receive range"; + case CSR_PSKEY_LC_COMBO_DISABLE_PIO_MASK: + return "Combo: PIO lines and logic to disable transmit"; + case CSR_PSKEY_LC_COMBO_PRIORITY_PIO_MASK: + return "Combo: priority activity PIO lines and logic"; + case CSR_PSKEY_LC_COMBO_DOT11_CHANNEL_PIO_BASE: + return "Combo: 802.11b channel number base PIO line"; + case CSR_PSKEY_LC_COMBO_DOT11_BLOCK_CHANNELS: + return "Combo: channels to block either side of 802.11b"; + case CSR_PSKEY_LC_MAX_TX_POWER_NO_RSSI: + return "Maximum transmit power when peer has no RSSI"; + case CSR_PSKEY_LC_CONNECTION_RX_WINDOW: + return "Receive window size during connections"; + case CSR_PSKEY_LC_COMBO_DOT11_TX_PROTECTION_MODE: + return "Combo: which TX packets shall we protect"; + case CSR_PSKEY_LC_ENHANCED_POWER_TABLE: + return "Radio power table"; + case CSR_PSKEY_LC_WIDEBAND_RSSI_CONFIG: + return "RSSI configuration for use with wideband RSSI"; + case CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_LEAD: + return "Combo: How much notice will we give the Combo Card"; + case CSR_PSKEY_BT_CLOCK_INIT: + return "Initial value of Bluetooth clock"; + case CSR_PSKEY_TX_MR_MOD_DELAY: + return "TX Mod delay"; + case CSR_PSKEY_RX_MR_SYNC_TIMING: + return "RX MR Sync Timing"; + case CSR_PSKEY_RX_MR_SYNC_CONFIG: + return "RX MR Sync Configuration"; + case CSR_PSKEY_LC_LOST_SYNC_SLOTS: + return "Time in ms for lost sync in low power modes"; + case CSR_PSKEY_RX_MR_SAMP_CONFIG: + return "RX MR Sync Configuration"; + case CSR_PSKEY_AGC_HYST_LEVELS: + return "AGC hysteresis levels"; + case CSR_PSKEY_RX_LEVEL_LOW_SIGNAL: + return "ANA_RX_LVL at low signal strengths"; + case CSR_PSKEY_AGC_IQ_LVL_VALUES: + return "ANA_IQ_LVL values for AGC algorithmn"; + case CSR_PSKEY_MR_FTRIM_OFFSET_12DB: + return "ANA_RX_FTRIM offset when using 12 dB IF atten "; + case CSR_PSKEY_MR_FTRIM_OFFSET_6DB: + return "ANA_RX_FTRIM offset when using 6 dB IF atten "; + case CSR_PSKEY_NO_CAL_ON_BOOT: + return "Do not calibrate radio on boot"; + case CSR_PSKEY_RSSI_HI_TARGET: + return "RSSI high target"; + case CSR_PSKEY_PREFERRED_MIN_ATTENUATION: + return "Preferred minimum attenuator setting"; + case CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_OVERRIDE: + return "Combo: Treat all packets as high priority"; + case CSR_PSKEY_LC_MULTISLOT_HOLDOFF: + return "Time till single slot packets are used for resync"; + case CSR_PSKEY_FREE_KEY_PIGEON_HOLE: + return "Link key store bitfield"; + case CSR_PSKEY_LINK_KEY_BD_ADDR0: + return "Bluetooth address + link key 0"; + case CSR_PSKEY_LINK_KEY_BD_ADDR1: + return "Bluetooth address + link key 1"; + case CSR_PSKEY_LINK_KEY_BD_ADDR2: + return "Bluetooth address + link key 2"; + case CSR_PSKEY_LINK_KEY_BD_ADDR3: + return "Bluetooth address + link key 3"; + case CSR_PSKEY_LINK_KEY_BD_ADDR4: + return "Bluetooth address + link key 4"; + case CSR_PSKEY_LINK_KEY_BD_ADDR5: + return "Bluetooth address + link key 5"; + case CSR_PSKEY_LINK_KEY_BD_ADDR6: + return "Bluetooth address + link key 6"; + case CSR_PSKEY_LINK_KEY_BD_ADDR7: + return "Bluetooth address + link key 7"; + case CSR_PSKEY_LINK_KEY_BD_ADDR8: + return "Bluetooth address + link key 8"; + case CSR_PSKEY_LINK_KEY_BD_ADDR9: + return "Bluetooth address + link key 9"; + case CSR_PSKEY_LINK_KEY_BD_ADDR10: + return "Bluetooth address + link key 10"; + case CSR_PSKEY_LINK_KEY_BD_ADDR11: + return "Bluetooth address + link key 11"; + case CSR_PSKEY_LINK_KEY_BD_ADDR12: + return "Bluetooth address + link key 12"; + case CSR_PSKEY_LINK_KEY_BD_ADDR13: + return "Bluetooth address + link key 13"; + case CSR_PSKEY_LINK_KEY_BD_ADDR14: + return "Bluetooth address + link key 14"; + case CSR_PSKEY_LINK_KEY_BD_ADDR15: + return "Bluetooth address + link key 15"; + case CSR_PSKEY_ENC_KEY_LMIN: + return "Minimum encryption key length"; + case CSR_PSKEY_ENC_KEY_LMAX: + return "Maximum encryption key length"; + case CSR_PSKEY_LOCAL_SUPPORTED_FEATURES: + return "Local supported features block"; + case CSR_PSKEY_LM_USE_UNIT_KEY: + return "Allow use of unit key for authentication?"; + case CSR_PSKEY_HCI_NOP_DISABLE: + return "Disable the HCI Command_Status event on boot"; + case CSR_PSKEY_LM_MAX_EVENT_FILTERS: + return "Maximum number of event filters"; + case CSR_PSKEY_LM_USE_ENC_MODE_BROADCAST: + return "Allow LM to use enc_mode=2"; + case CSR_PSKEY_LM_TEST_SEND_ACCEPTED_TWICE: + return "LM sends two LMP_accepted messages in test mode"; + case CSR_PSKEY_LM_MAX_PAGE_HOLD_TIME: + return "Maximum time we hold a device around page"; + case CSR_PSKEY_AFH_ADAPTATION_RESPONSE_TIME: + return "LM period for AFH adaption"; + case CSR_PSKEY_AFH_OPTIONS: + return "Options to configure AFH"; + case CSR_PSKEY_AFH_RSSI_RUN_PERIOD: + return "AFH RSSI reading period"; + case CSR_PSKEY_AFH_REENABLE_CHANNEL_TIME: + return "AFH good channel adding time"; + case CSR_PSKEY_NO_DROP_ON_ACR_MS_FAIL: + return "Complete link if acr barge-in role switch refused"; + case CSR_PSKEY_MAX_PRIVATE_KEYS: + return "Max private link keys stored"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR0: + return "Bluetooth address + link key 0"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR1: + return "Bluetooth address + link key 1"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR2: + return "Bluetooth address + link key 2"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR3: + return "Bluetooth address + link key 3"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR4: + return "Bluetooth address + link key 4"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR5: + return "Bluetooth address + link key 5"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR6: + return "Bluetooth address + link key 6"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR7: + return "Bluetooth address + link key 7"; + case CSR_PSKEY_LOCAL_SUPPORTED_COMMANDS: + return "Local supported commands"; + case CSR_PSKEY_LM_MAX_ABSENCE_INDEX: + return "Maximum absence index allowed"; + case CSR_PSKEY_DEVICE_NAME: + return "Local device's \"user friendly\" name"; + case CSR_PSKEY_AFH_RSSI_THRESHOLD: + return "AFH RSSI threshold"; + case CSR_PSKEY_LM_CASUAL_SCAN_INTERVAL: + return "Scan interval in slots for casual scanning"; + case CSR_PSKEY_AFH_MIN_MAP_CHANGE: + return "The minimum amount to change an AFH map by"; + case CSR_PSKEY_AFH_RSSI_LP_RUN_PERIOD: + return "AFH RSSI reading period when in low power mode"; + case CSR_PSKEY_HCI_LMP_LOCAL_VERSION: + return "The HCI and LMP version reported locally"; + case CSR_PSKEY_LMP_REMOTE_VERSION: + return "The LMP version reported remotely"; + case CSR_PSKEY_HOLD_ERROR_MESSAGE_NUMBER: + return "Maximum number of queued HCI Hardware Error Events"; + case CSR_PSKEY_DFU_ATTRIBUTES: + return "DFU attributes"; + case CSR_PSKEY_DFU_DETACH_TO: + return "DFU detach timeout"; + case CSR_PSKEY_DFU_TRANSFER_SIZE: + return "DFU transfer size"; + case CSR_PSKEY_DFU_ENABLE: + return "DFU enable"; + case CSR_PSKEY_DFU_LIN_REG_ENABLE: + return "Linear Regulator enabled at boot in DFU mode"; + case CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_MSB: + return "DFU encryption VM application public key MSB"; + case CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_LSB: + return "DFU encryption VM application public key LSB"; + case CSR_PSKEY_DFUENC_VMAPP_PK_M_DASH: + return "DFU encryption VM application M dash"; + case CSR_PSKEY_DFUENC_VMAPP_PK_R2N_MSB: + return "DFU encryption VM application public key R2N MSB"; + case CSR_PSKEY_DFUENC_VMAPP_PK_R2N_LSB: + return "DFU encryption VM application public key R2N LSB"; + case CSR_PSKEY_BCSP_LM_PS_BLOCK: + return "BCSP link establishment block"; + case CSR_PSKEY_HOSTIO_FC_PS_BLOCK: + return "HCI flow control block"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO0: + return "Host transport channel 0 settings (BCSP ACK)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO1: + return "Host transport channel 1 settings (BCSP-LE)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO2: + return "Host transport channel 2 settings (BCCMD)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO3: + return "Host transport channel 3 settings (HQ)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO4: + return "Host transport channel 4 settings (DM)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO5: + return "Host transport channel 5 settings (HCI CMD/EVT)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO6: + return "Host transport channel 6 settings (HCI ACL)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO7: + return "Host transport channel 7 settings (HCI SCO)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO8: + return "Host transport channel 8 settings (L2CAP)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO9: + return "Host transport channel 9 settings (RFCOMM)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO10: + return "Host transport channel 10 settings (SDP)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO11: + return "Host transport channel 11 settings (TEST)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO12: + return "Host transport channel 12 settings (DFU)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO13: + return "Host transport channel 13 settings (VM)"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO14: + return "Host transport channel 14 settings"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO15: + return "Host transport channel 15 settings"; + case CSR_PSKEY_HOSTIO_UART_RESET_TIMEOUT: + return "UART reset counter timeout"; + case CSR_PSKEY_HOSTIO_USE_HCI_EXTN: + return "Use hci_extn to route non-hci channels"; + case CSR_PSKEY_HOSTIO_USE_HCI_EXTN_CCFC: + return "Use command-complete flow control for hci_extn"; + case CSR_PSKEY_HOSTIO_HCI_EXTN_PAYLOAD_SIZE: + return "Maximum hci_extn payload size"; + case CSR_PSKEY_BCSP_LM_CNF_CNT_LIMIT: + return "BCSP link establishment conf message count"; + case CSR_PSKEY_HOSTIO_MAP_SCO_PCM: + return "Map SCO over PCM"; + case CSR_PSKEY_HOSTIO_AWKWARD_PCM_SYNC: + return "PCM interface synchronisation is difficult"; + case CSR_PSKEY_HOSTIO_BREAK_POLL_PERIOD: + return "Break poll period (microseconds)"; + case CSR_PSKEY_HOSTIO_MIN_UART_HCI_SCO_SIZE: + return "Minimum SCO packet size sent to host over UART HCI"; + case CSR_PSKEY_HOSTIO_MAP_SCO_CODEC: + return "Map SCO over the built-in codec"; + case CSR_PSKEY_PCM_CVSD_TX_HI_FREQ_BOOST: + return "High frequency boost for PCM when transmitting CVSD"; + case CSR_PSKEY_PCM_CVSD_RX_HI_FREQ_BOOST: + return "High frequency boost for PCM when receiving CVSD"; + case CSR_PSKEY_PCM_CONFIG32: + return "PCM interface settings bitfields"; + case CSR_PSKEY_USE_OLD_BCSP_LE: + return "Use the old version of BCSP link establishment"; + case CSR_PSKEY_PCM_CVSD_USE_NEW_FILTER: + return "CVSD uses the new filter if available"; + case CSR_PSKEY_PCM_FORMAT: + return "PCM data format"; + case CSR_PSKEY_CODEC_OUT_GAIN: + return "Audio output gain when using built-in codec"; + case CSR_PSKEY_CODEC_IN_GAIN: + return "Audio input gain when using built-in codec"; + case CSR_PSKEY_CODEC_PIO: + return "PIO to enable when built-in codec is enabled"; + case CSR_PSKEY_PCM_LOW_JITTER_CONFIG: + return "PCM interface settings for low jitter master mode"; + case CSR_PSKEY_HOSTIO_SCO_PCM_THRESHOLDS: + return "Thresholds for SCO PCM buffers"; + case CSR_PSKEY_HOSTIO_SCO_HCI_THRESHOLDS: + return "Thresholds for SCO HCI buffers"; + case CSR_PSKEY_HOSTIO_MAP_SCO_PCM_SLOT: + return "Route SCO data to specified slot in pcm frame"; + case CSR_PSKEY_UART_BAUDRATE: + return "UART Baud rate"; + case CSR_PSKEY_UART_CONFIG_BCSP: + return "UART configuration when using BCSP"; + case CSR_PSKEY_UART_CONFIG_H4: + return "UART configuration when using H4"; + case CSR_PSKEY_UART_CONFIG_H5: + return "UART configuration when using H5"; + case CSR_PSKEY_UART_CONFIG_USR: + return "UART configuration when under VM control"; + case CSR_PSKEY_UART_TX_CRCS: + return "Use CRCs for BCSP or H5"; + case CSR_PSKEY_UART_ACK_TIMEOUT: + return "Acknowledgement timeout for BCSP and H5"; + case CSR_PSKEY_UART_TX_MAX_ATTEMPTS: + return "Max times to send reliable BCSP or H5 message"; + case CSR_PSKEY_UART_TX_WINDOW_SIZE: + return "Transmit window size for BCSP and H5"; + case CSR_PSKEY_UART_HOST_WAKE: + return "UART host wakeup"; + case CSR_PSKEY_HOSTIO_THROTTLE_TIMEOUT: + return "Host interface performance control."; + case CSR_PSKEY_PCM_ALWAYS_ENABLE: + return "PCM port is always enable when chip is running"; + case CSR_PSKEY_UART_HOST_WAKE_SIGNAL: + return "Signal to use for uart host wakeup protocol"; + case CSR_PSKEY_UART_CONFIG_H4DS: + return "UART configuration when using H4DS"; + case CSR_PSKEY_H4DS_WAKE_DURATION: + return "How long to spend waking the host when using H4DS"; + case CSR_PSKEY_H4DS_MAXWU: + return "Maximum number of H4DS Wake-Up messages to send"; + case CSR_PSKEY_H4DS_LE_TIMER_PERIOD: + return "H4DS Link Establishment Tsync and Tconf period"; + case CSR_PSKEY_H4DS_TWU_TIMER_PERIOD: + return "H4DS Twu timer period"; + case CSR_PSKEY_H4DS_UART_IDLE_TIMER_PERIOD: + return "H4DS Tuart_idle timer period"; + case CSR_PSKEY_ANA_FTRIM: + return "Crystal frequency trim"; + case CSR_PSKEY_WD_TIMEOUT: + return "Watchdog timeout (microseconds)"; + case CSR_PSKEY_WD_PERIOD: + return "Watchdog period (microseconds)"; + case CSR_PSKEY_HOST_INTERFACE: + return "Host interface"; + case CSR_PSKEY_HQ_HOST_TIMEOUT: + return "HQ host command timeout"; + case CSR_PSKEY_HQ_ACTIVE: + return "Enable host query task?"; + case CSR_PSKEY_BCCMD_SECURITY_ACTIVE: + return "Enable configuration security"; + case CSR_PSKEY_ANA_FREQ: + return "Crystal frequency"; + case CSR_PSKEY_PIO_PROTECT_MASK: + return "Access to PIO pins"; + case CSR_PSKEY_PMALLOC_SIZES: + return "pmalloc sizes array"; + case CSR_PSKEY_UART_BAUD_RATE: + return "UART Baud rate (pre 18)"; + case CSR_PSKEY_UART_CONFIG: + return "UART configuration bitfield"; + case CSR_PSKEY_STUB: + return "Stub"; + case CSR_PSKEY_TXRX_PIO_CONTROL: + return "TX and RX PIO control"; + case CSR_PSKEY_ANA_RX_LEVEL: + return "ANA_RX_LVL register initial value"; + case CSR_PSKEY_ANA_RX_FTRIM: + return "ANA_RX_FTRIM register initial value"; + case CSR_PSKEY_PSBC_DATA_VERSION: + return "Persistent store version"; + case CSR_PSKEY_PCM0_ATTENUATION: + return "Volume control on PCM channel 0"; + case CSR_PSKEY_LO_LVL_MAX: + return "Maximum value of LO level control register"; + case CSR_PSKEY_LO_ADC_AMPL_MIN: + return "Minimum value of the LO amplitude measured on the ADC"; + case CSR_PSKEY_LO_ADC_AMPL_MAX: + return "Maximum value of the LO amplitude measured on the ADC"; + case CSR_PSKEY_IQ_TRIM_CHANNEL: + return "IQ calibration channel"; + case CSR_PSKEY_IQ_TRIM_GAIN: + return "IQ calibration gain"; + case CSR_PSKEY_IQ_TRIM_ENABLE: + return "IQ calibration enable"; + case CSR_PSKEY_TX_OFFSET_HALF_MHZ: + return "Transmit offset"; + case CSR_PSKEY_GBL_MISC_ENABLES: + return "Global miscellaneous hardware enables"; + case CSR_PSKEY_UART_SLEEP_TIMEOUT: + return "Time in ms to deep sleep if nothing received"; + case CSR_PSKEY_DEEP_SLEEP_STATE: + return "Deep sleep state usage"; + case CSR_PSKEY_IQ_ENABLE_PHASE_TRIM: + return "IQ phase enable"; + case CSR_PSKEY_HCI_HANDLE_FREEZE_PERIOD: + return "Time for which HCI handle is frozen after link removal"; + case CSR_PSKEY_MAX_FROZEN_HCI_HANDLES: + return "Maximum number of frozen HCI handles"; + case CSR_PSKEY_PAGETABLE_DESTRUCTION_DELAY: + return "Delay from freezing buf handle to deleting page table"; + case CSR_PSKEY_IQ_TRIM_PIO_SETTINGS: + return "IQ PIO settings"; + case CSR_PSKEY_USE_EXTERNAL_CLOCK: + return "Device uses an external clock"; + case CSR_PSKEY_DEEP_SLEEP_WAKE_CTS: + return "Exit deep sleep on CTS line activity"; + case CSR_PSKEY_FC_HC2H_FLUSH_DELAY: + return "Delay from disconnect to flushing HC->H FC tokens"; + case CSR_PSKEY_RX_HIGHSIDE: + return "Disable the HIGHSIDE bit in ANA_CONFIG"; + case CSR_PSKEY_TX_PRE_LVL: + return "TX pre-amplifier level"; + case CSR_PSKEY_RX_SINGLE_ENDED: + return "RX single ended"; + case CSR_PSKEY_TX_FILTER_CONFIG: + return "TX filter configuration"; + case CSR_PSKEY_CLOCK_REQUEST_ENABLE: + return "External clock request enable"; + case CSR_PSKEY_RX_MIN_ATTEN: + return "Minimum attenuation allowed for receiver"; + case CSR_PSKEY_XTAL_TARGET_AMPLITUDE: + return "Crystal target amplitude"; + case CSR_PSKEY_PCM_MIN_CPU_CLOCK: + return "Minimum CPU clock speed with PCM port running"; + case CSR_PSKEY_HOST_INTERFACE_PIO_USB: + return "USB host interface selection PIO line"; + case CSR_PSKEY_CPU_IDLE_MODE: + return "CPU idle mode when radio is active"; + case CSR_PSKEY_DEEP_SLEEP_CLEAR_RTS: + return "Deep sleep clears the UART RTS line"; + case CSR_PSKEY_RF_RESONANCE_TRIM: + return "Frequency trim for IQ and LNA resonant circuits"; + case CSR_PSKEY_DEEP_SLEEP_PIO_WAKE: + return "PIO line to wake the chip from deep sleep"; + case CSR_PSKEY_DRAIN_BORE_TIMERS: + return "Energy consumption measurement settings"; + case CSR_PSKEY_DRAIN_TX_POWER_BASE: + return "Energy consumption measurement settings"; + case CSR_PSKEY_MODULE_ID: + return "Module serial number"; + case CSR_PSKEY_MODULE_DESIGN: + return "Module design ID"; + case CSR_PSKEY_MODULE_SECURITY_CODE: + return "Module security code"; + case CSR_PSKEY_VM_DISABLE: + return "VM disable"; + case CSR_PSKEY_MOD_MANUF0: + return "Module manufactuer data 0"; + case CSR_PSKEY_MOD_MANUF1: + return "Module manufactuer data 1"; + case CSR_PSKEY_MOD_MANUF2: + return "Module manufactuer data 2"; + case CSR_PSKEY_MOD_MANUF3: + return "Module manufactuer data 3"; + case CSR_PSKEY_MOD_MANUF4: + return "Module manufactuer data 4"; + case CSR_PSKEY_MOD_MANUF5: + return "Module manufactuer data 5"; + case CSR_PSKEY_MOD_MANUF6: + return "Module manufactuer data 6"; + case CSR_PSKEY_MOD_MANUF7: + return "Module manufactuer data 7"; + case CSR_PSKEY_MOD_MANUF8: + return "Module manufactuer data 8"; + case CSR_PSKEY_MOD_MANUF9: + return "Module manufactuer data 9"; + case CSR_PSKEY_DUT_VM_DISABLE: + return "VM disable when entering radiotest modes"; + case CSR_PSKEY_USR0: + return "User configuration data 0"; + case CSR_PSKEY_USR1: + return "User configuration data 1"; + case CSR_PSKEY_USR2: + return "User configuration data 2"; + case CSR_PSKEY_USR3: + return "User configuration data 3"; + case CSR_PSKEY_USR4: + return "User configuration data 4"; + case CSR_PSKEY_USR5: + return "User configuration data 5"; + case CSR_PSKEY_USR6: + return "User configuration data 6"; + case CSR_PSKEY_USR7: + return "User configuration data 7"; + case CSR_PSKEY_USR8: + return "User configuration data 8"; + case CSR_PSKEY_USR9: + return "User configuration data 9"; + case CSR_PSKEY_USR10: + return "User configuration data 10"; + case CSR_PSKEY_USR11: + return "User configuration data 11"; + case CSR_PSKEY_USR12: + return "User configuration data 12"; + case CSR_PSKEY_USR13: + return "User configuration data 13"; + case CSR_PSKEY_USR14: + return "User configuration data 14"; + case CSR_PSKEY_USR15: + return "User configuration data 15"; + case CSR_PSKEY_USR16: + return "User configuration data 16"; + case CSR_PSKEY_USR17: + return "User configuration data 17"; + case CSR_PSKEY_USR18: + return "User configuration data 18"; + case CSR_PSKEY_USR19: + return "User configuration data 19"; + case CSR_PSKEY_USR20: + return "User configuration data 20"; + case CSR_PSKEY_USR21: + return "User configuration data 21"; + case CSR_PSKEY_USR22: + return "User configuration data 22"; + case CSR_PSKEY_USR23: + return "User configuration data 23"; + case CSR_PSKEY_USR24: + return "User configuration data 24"; + case CSR_PSKEY_USR25: + return "User configuration data 25"; + case CSR_PSKEY_USR26: + return "User configuration data 26"; + case CSR_PSKEY_USR27: + return "User configuration data 27"; + case CSR_PSKEY_USR28: + return "User configuration data 28"; + case CSR_PSKEY_USR29: + return "User configuration data 29"; + case CSR_PSKEY_USR30: + return "User configuration data 30"; + case CSR_PSKEY_USR31: + return "User configuration data 31"; + case CSR_PSKEY_USR32: + return "User configuration data 32"; + case CSR_PSKEY_USR33: + return "User configuration data 33"; + case CSR_PSKEY_USR34: + return "User configuration data 34"; + case CSR_PSKEY_USR35: + return "User configuration data 35"; + case CSR_PSKEY_USR36: + return "User configuration data 36"; + case CSR_PSKEY_USR37: + return "User configuration data 37"; + case CSR_PSKEY_USR38: + return "User configuration data 38"; + case CSR_PSKEY_USR39: + return "User configuration data 39"; + case CSR_PSKEY_USR40: + return "User configuration data 40"; + case CSR_PSKEY_USR41: + return "User configuration data 41"; + case CSR_PSKEY_USR42: + return "User configuration data 42"; + case CSR_PSKEY_USR43: + return "User configuration data 43"; + case CSR_PSKEY_USR44: + return "User configuration data 44"; + case CSR_PSKEY_USR45: + return "User configuration data 45"; + case CSR_PSKEY_USR46: + return "User configuration data 46"; + case CSR_PSKEY_USR47: + return "User configuration data 47"; + case CSR_PSKEY_USR48: + return "User configuration data 48"; + case CSR_PSKEY_USR49: + return "User configuration data 49"; + case CSR_PSKEY_USB_VERSION: + return "USB specification version number"; + case CSR_PSKEY_USB_DEVICE_CLASS_CODES: + return "USB device class codes"; + case CSR_PSKEY_USB_VENDOR_ID: + return "USB vendor identifier"; + case CSR_PSKEY_USB_PRODUCT_ID: + return "USB product identifier"; + case CSR_PSKEY_USB_MANUF_STRING: + return "USB manufacturer string"; + case CSR_PSKEY_USB_PRODUCT_STRING: + return "USB product string"; + case CSR_PSKEY_USB_SERIAL_NUMBER_STRING: + return "USB serial number string"; + case CSR_PSKEY_USB_CONFIG_STRING: + return "USB configuration string"; + case CSR_PSKEY_USB_ATTRIBUTES: + return "USB attributes bitmap"; + case CSR_PSKEY_USB_MAX_POWER: + return "USB device maximum power consumption"; + case CSR_PSKEY_USB_BT_IF_CLASS_CODES: + return "USB Bluetooth interface class codes"; + case CSR_PSKEY_USB_LANGID: + return "USB language strings supported"; + case CSR_PSKEY_USB_DFU_CLASS_CODES: + return "USB DFU class codes block"; + case CSR_PSKEY_USB_DFU_PRODUCT_ID: + return "USB DFU product ID"; + case CSR_PSKEY_USB_PIO_DETACH: + return "USB detach/attach PIO line"; + case CSR_PSKEY_USB_PIO_WAKEUP: + return "USB wakeup PIO line"; + case CSR_PSKEY_USB_PIO_PULLUP: + return "USB D+ pullup PIO line"; + case CSR_PSKEY_USB_PIO_VBUS: + return "USB VBus detection PIO Line"; + case CSR_PSKEY_USB_PIO_WAKE_TIMEOUT: + return "Timeout for assertion of USB PIO wake signal"; + case CSR_PSKEY_USB_PIO_RESUME: + return "PIO signal used in place of bus resume"; + case CSR_PSKEY_USB_BT_SCO_IF_CLASS_CODES: + return "USB Bluetooth SCO interface class codes"; + case CSR_PSKEY_USB_SUSPEND_PIO_LEVEL: + return "USB PIO levels to set when suspended"; + case CSR_PSKEY_USB_SUSPEND_PIO_DIR: + return "USB PIO I/O directions to set when suspended"; + case CSR_PSKEY_USB_SUSPEND_PIO_MASK: + return "USB PIO lines to be set forcibly in suspend"; + case CSR_PSKEY_USB_ENDPOINT_0_MAX_PACKET_SIZE: + return "The maxmimum packet size for USB endpoint 0"; + case CSR_PSKEY_USB_CONFIG: + return "USB config params for new chips (>bc2)"; + case CSR_PSKEY_RADIOTEST_ATTEN_INIT: + return "Radio test initial attenuator"; + case CSR_PSKEY_RADIOTEST_FIRST_TRIM_TIME: + return "IQ first calibration period in test"; + case CSR_PSKEY_RADIOTEST_SUBSEQUENT_TRIM_TIME: + return "IQ subsequent calibration period in test"; + case CSR_PSKEY_RADIOTEST_LO_LVL_TRIM_ENABLE: + return "LO_LVL calibration enable"; + case CSR_PSKEY_RADIOTEST_DISABLE_MODULATION: + return "Disable modulation during radiotest transmissions"; + case CSR_PSKEY_RFCOMM_FCON_THRESHOLD: + return "RFCOMM aggregate flow control on threshold"; + case CSR_PSKEY_RFCOMM_FCOFF_THRESHOLD: + return "RFCOMM aggregate flow control off threshold"; + case CSR_PSKEY_IPV6_STATIC_ADDR: + return "Static IPv6 address"; + case CSR_PSKEY_IPV4_STATIC_ADDR: + return "Static IPv4 address"; + case CSR_PSKEY_IPV6_STATIC_PREFIX_LEN: + return "Static IPv6 prefix length"; + case CSR_PSKEY_IPV6_STATIC_ROUTER_ADDR: + return "Static IPv6 router address"; + case CSR_PSKEY_IPV4_STATIC_SUBNET_MASK: + return "Static IPv4 subnet mask"; + case CSR_PSKEY_IPV4_STATIC_ROUTER_ADDR: + return "Static IPv4 router address"; + case CSR_PSKEY_MDNS_NAME: + return "Multicast DNS name"; + case CSR_PSKEY_FIXED_PIN: + return "Fixed PIN"; + case CSR_PSKEY_MDNS_PORT: + return "Multicast DNS port"; + case CSR_PSKEY_MDNS_TTL: + return "Multicast DNS TTL"; + case CSR_PSKEY_MDNS_IPV4_ADDR: + return "Multicast DNS IPv4 address"; + case CSR_PSKEY_ARP_CACHE_TIMEOUT: + return "ARP cache timeout"; + case CSR_PSKEY_HFP_POWER_TABLE: + return "HFP power table"; + case CSR_PSKEY_DRAIN_BORE_TIMER_COUNTERS: + return "Energy consumption estimation timer counters"; + case CSR_PSKEY_DRAIN_BORE_COUNTERS: + return "Energy consumption estimation counters"; + case CSR_PSKEY_LOOP_FILTER_TRIM: + return "Trim value to optimise loop filter"; + case CSR_PSKEY_DRAIN_BORE_CURRENT_PEAK: + return "Energy consumption estimation current peak"; + case CSR_PSKEY_VM_E2_CACHE_LIMIT: + return "Maximum RAM for caching EEPROM VM application"; + case CSR_PSKEY_FORCE_16MHZ_REF_PIO: + return "PIO line to force 16 MHz reference to be assumed"; + case CSR_PSKEY_CDMA_LO_REF_LIMITS: + return "Local oscillator frequency reference limits for CDMA"; + case CSR_PSKEY_CDMA_LO_ERROR_LIMITS: + return "Local oscillator frequency error limits for CDMA"; + case CSR_PSKEY_CLOCK_STARTUP_DELAY: + return "Clock startup delay in milliseconds"; + case CSR_PSKEY_DEEP_SLEEP_CORRECTION_FACTOR: + return "Deep sleep clock correction factor"; + case CSR_PSKEY_TEMPERATURE_CALIBRATION: + return "Temperature in deg C for a given internal setting"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA: + return "Temperature for given internal PA adjustment"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL: + return "Temperature for a given TX_PRE_LVL adjustment"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB: + return "Temperature for a given TX_BB adjustment"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_ANA_FTRIM: + return "Temperature for given crystal trim adjustment"; + case CSR_PSKEY_TEST_DELTA_OFFSET: + return "Frequency offset applied to synthesiser in test mode"; + case CSR_PSKEY_RX_DYNAMIC_LVL_OFFSET: + return "Receiver dynamic level offset depending on channel"; + case CSR_PSKEY_TEST_FORCE_OFFSET: + return "Force use of exact value in PSKEY_TEST_DELTA_OFFSET"; + case CSR_PSKEY_RF_TRAP_BAD_DIVISION_RATIOS: + return "Trap bad division ratios in radio frequency tables"; + case CSR_PSKEY_RADIOTEST_CDMA_LO_REF_LIMITS: + return "LO frequency reference limits for CDMA in radiotest"; + case CSR_PSKEY_INITIAL_BOOTMODE: + return "Initial device bootmode"; + case CSR_PSKEY_ONCHIP_HCI_CLIENT: + return "HCI traffic routed internally"; + case CSR_PSKEY_RX_ATTEN_BACKOFF: + return "Receiver attenuation back-off"; + case CSR_PSKEY_RX_ATTEN_UPDATE_RATE: + return "Receiver attenuation update rate"; + case CSR_PSKEY_SYNTH_TXRX_THRESHOLDS: + return "Local oscillator tuning voltage limits for tx and rx"; + case CSR_PSKEY_MIN_WAIT_STATES: + return "Flash wait state indicator"; + case CSR_PSKEY_RSSI_CORRECTION: + return "RSSI correction factor."; + case CSR_PSKEY_SCHED_THROTTLE_TIMEOUT: + return "Scheduler performance control."; + case CSR_PSKEY_DEEP_SLEEP_USE_EXTERNAL_CLOCK: + return "Deep sleep uses external 32 kHz clock source"; + case CSR_PSKEY_TRIM_RADIO_FILTERS: + return "Trim rx and tx radio filters if true."; + case CSR_PSKEY_TRANSMIT_OFFSET: + return "Transmit offset in units of 62.5 kHz"; + case CSR_PSKEY_USB_VM_CONTROL: + return "VM application will supply USB descriptors"; + case CSR_PSKEY_MR_ANA_RX_FTRIM: + return "Medium rate value for the ANA_RX_FTRIM register"; + case CSR_PSKEY_I2C_CONFIG: + return "I2C configuration"; + case CSR_PSKEY_IQ_LVL_RX: + return "IQ demand level for reception"; + case CSR_PSKEY_MR_TX_FILTER_CONFIG: + return "TX filter configuration used for enhanced data rate"; + case CSR_PSKEY_MR_TX_CONFIG2: + return "TX filter configuration used for enhanced data rate"; + case CSR_PSKEY_USB_DONT_RESET_BOOTMODE_ON_HOST_RESET: + return "Don't reset bootmode if USB host resets"; + case CSR_PSKEY_LC_USE_THROTTLING: + return "Adjust packet selection on packet error rate"; + case CSR_PSKEY_CHARGER_TRIM: + return "Trim value for the current charger"; + case CSR_PSKEY_CLOCK_REQUEST_FEATURES: + return "Clock request is tristated if enabled"; + case CSR_PSKEY_TRANSMIT_OFFSET_CLASS1: + return "Transmit offset / 62.5 kHz for class 1 radios"; + case CSR_PSKEY_TX_AVOID_PA_CLASS1_PIO: + return "PIO line asserted in class1 operation to avoid PA"; + case CSR_PSKEY_MR_PIO_CONFIG: + return "PIO line asserted in class1 operation to avoid PA"; + case CSR_PSKEY_UART_CONFIG2: + return "The UART Sampling point"; + case CSR_PSKEY_CLASS1_IQ_LVL: + return "IQ demand level for class 1 power level"; + case CSR_PSKEY_CLASS1_TX_CONFIG2: + return "TX filter configuration used for class 1 tx power"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA_CLASS1: + return "Temperature for given internal PA adjustment"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_EXTERNAL_PA_CLASS1: + return "Temperature for given internal PA adjustment"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL_MR: + return "Temperature adjustment for TX_PRE_LVL in EDR"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_HEADER: + return "Temperature for a given TX_BB in EDR header"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_PAYLOAD: + return "Temperature for a given TX_BB in EDR payload"; + case CSR_PSKEY_RX_MR_EQ_TAPS: + return "Adjust receiver configuration for EDR"; + case CSR_PSKEY_TX_PRE_LVL_CLASS1: + return "TX pre-amplifier level in class 1 operation"; + case CSR_PSKEY_ANALOGUE_ATTENUATOR: + return "TX analogue attenuator setting"; + case CSR_PSKEY_MR_RX_FILTER_TRIM: + return "Trim for receiver used in EDR."; + case CSR_PSKEY_MR_RX_FILTER_RESPONSE: + return "Filter response for receiver used in EDR."; + case CSR_PSKEY_PIO_WAKEUP_STATE: + return "PIO deep sleep wake up state "; + case CSR_PSKEY_MR_TX_IF_ATTEN_OFF_TEMP: + return "TX IF atten off temperature when using EDR."; + case CSR_PSKEY_LO_DIV_LATCH_BYPASS: + return "Bypass latch for LO dividers"; + case CSR_PSKEY_LO_VCO_STANDBY: + return "Use standby mode for the LO VCO"; + case CSR_PSKEY_SLOW_CLOCK_FILTER_SHIFT: + return "Slow clock sampling filter constant"; + case CSR_PSKEY_SLOW_CLOCK_FILTER_DIVIDER: + return "Slow clock filter fractional threshold"; + case CSR_PSKEY_USB_ATTRIBUTES_POWER: + return "USB self powered"; + case CSR_PSKEY_USB_ATTRIBUTES_WAKEUP: + return "USB responds to wake-up"; + case CSR_PSKEY_DFU_ATTRIBUTES_MANIFESTATION_TOLERANT: + return "DFU manifestation tolerant"; + case CSR_PSKEY_DFU_ATTRIBUTES_CAN_UPLOAD: + return "DFU can upload"; + case CSR_PSKEY_DFU_ATTRIBUTES_CAN_DOWNLOAD: + return "DFU can download"; + case CSR_PSKEY_UART_CONFIG_STOP_BITS: + return "UART: stop bits"; + case CSR_PSKEY_UART_CONFIG_PARITY_BIT: + return "UART: parity bit"; + case CSR_PSKEY_UART_CONFIG_FLOW_CTRL_EN: + return "UART: hardware flow control"; + case CSR_PSKEY_UART_CONFIG_RTS_AUTO_EN: + return "UART: RTS auto-enabled"; + case CSR_PSKEY_UART_CONFIG_RTS: + return "UART: RTS asserted"; + case CSR_PSKEY_UART_CONFIG_TX_ZERO_EN: + return "UART: TX zero enable"; + case CSR_PSKEY_UART_CONFIG_NON_BCSP_EN: + return "UART: enable BCSP-specific hardware"; + case CSR_PSKEY_UART_CONFIG_RX_RATE_DELAY: + return "UART: RX rate delay"; + case CSR_PSKEY_UART_SEQ_TIMEOUT: + return "UART: BCSP ack timeout"; + case CSR_PSKEY_UART_SEQ_RETRIES: + return "UART: retry limit in sequencing layer"; + case CSR_PSKEY_UART_SEQ_WINSIZE: + return "UART: BCSP transmit window size"; + case CSR_PSKEY_UART_USE_CRC_ON_TX: + return "UART: use BCSP CRCs"; + case CSR_PSKEY_UART_HOST_INITIAL_STATE: + return "UART: initial host state"; + case CSR_PSKEY_UART_HOST_ATTENTION_SPAN: + return "UART: host attention span"; + case CSR_PSKEY_UART_HOST_WAKEUP_TIME: + return "UART: host wakeup time"; + case CSR_PSKEY_UART_HOST_WAKEUP_WAIT: + return "UART: host wakeup wait"; + case CSR_PSKEY_BCSP_LM_MODE: + return "BCSP link establishment mode"; + case CSR_PSKEY_BCSP_LM_SYNC_RETRIES: + return "BCSP link establishment sync retries"; + case CSR_PSKEY_BCSP_LM_TSHY: + return "BCSP link establishment Tshy"; + case CSR_PSKEY_UART_DFU_CONFIG_STOP_BITS: + return "DFU mode UART: stop bits"; + case CSR_PSKEY_UART_DFU_CONFIG_PARITY_BIT: + return "DFU mode UART: parity bit"; + case CSR_PSKEY_UART_DFU_CONFIG_FLOW_CTRL_EN: + return "DFU mode UART: hardware flow control"; + case CSR_PSKEY_UART_DFU_CONFIG_RTS_AUTO_EN: + return "DFU mode UART: RTS auto-enabled"; + case CSR_PSKEY_UART_DFU_CONFIG_RTS: + return "DFU mode UART: RTS asserted"; + case CSR_PSKEY_UART_DFU_CONFIG_TX_ZERO_EN: + return "DFU mode UART: TX zero enable"; + case CSR_PSKEY_UART_DFU_CONFIG_NON_BCSP_EN: + return "DFU mode UART: enable BCSP-specific hardware"; + case CSR_PSKEY_UART_DFU_CONFIG_RX_RATE_DELAY: + return "DFU mode UART: RX rate delay"; + case CSR_PSKEY_AMUX_AIO0: + return "Multiplexer for AIO 0"; + case CSR_PSKEY_AMUX_AIO1: + return "Multiplexer for AIO 1"; + case CSR_PSKEY_AMUX_AIO2: + return "Multiplexer for AIO 2"; + case CSR_PSKEY_AMUX_AIO3: + return "Multiplexer for AIO 3"; + case CSR_PSKEY_LOCAL_NAME_SIMPLIFIED: + return "Local Name (simplified)"; + case CSR_PSKEY_EXTENDED_STUB: + return "Extended stub"; + default: + return "Unknown"; + } +} + +char *csr_pskeytoval(uint16_t pskey) +{ + switch (pskey) { + case CSR_PSKEY_BDADDR: + return "BDADDR"; + case CSR_PSKEY_COUNTRYCODE: + return "COUNTRYCODE"; + case CSR_PSKEY_CLASSOFDEVICE: + return "CLASSOFDEVICE"; + case CSR_PSKEY_DEVICE_DRIFT: + return "DEVICE_DRIFT"; + case CSR_PSKEY_DEVICE_JITTER: + return "DEVICE_JITTER"; + case CSR_PSKEY_MAX_ACLS: + return "MAX_ACLS"; + case CSR_PSKEY_MAX_SCOS: + return "MAX_SCOS"; + case CSR_PSKEY_MAX_REMOTE_MASTERS: + return "MAX_REMOTE_MASTERS"; + case CSR_PSKEY_ENABLE_MASTERY_WITH_SLAVERY: + return "ENABLE_MASTERY_WITH_SLAVERY"; + case CSR_PSKEY_H_HC_FC_MAX_ACL_PKT_LEN: + return "H_HC_FC_MAX_ACL_PKT_LEN"; + case CSR_PSKEY_H_HC_FC_MAX_SCO_PKT_LEN: + return "H_HC_FC_MAX_SCO_PKT_LEN"; + case CSR_PSKEY_H_HC_FC_MAX_ACL_PKTS: + return "H_HC_FC_MAX_ACL_PKTS"; + case CSR_PSKEY_H_HC_FC_MAX_SCO_PKTS: + return "H_HC_FC_MAX_SCO_PKTS"; + case CSR_PSKEY_LC_FC_BUFFER_LOW_WATER_MARK: + return "LC_FC_BUFFER_LOW_WATER_MARK"; + case CSR_PSKEY_LC_MAX_TX_POWER: + return "LC_MAX_TX_POWER"; + case CSR_PSKEY_TX_GAIN_RAMP: + return "TX_GAIN_RAMP"; + case CSR_PSKEY_LC_POWER_TABLE: + return "LC_POWER_TABLE"; + case CSR_PSKEY_LC_PEER_POWER_PERIOD: + return "LC_PEER_POWER_PERIOD"; + case CSR_PSKEY_LC_FC_POOLS_LOW_WATER_MARK: + return "LC_FC_POOLS_LOW_WATER_MARK"; + case CSR_PSKEY_LC_DEFAULT_TX_POWER: + return "LC_DEFAULT_TX_POWER"; + case CSR_PSKEY_LC_RSSI_GOLDEN_RANGE: + return "LC_RSSI_GOLDEN_RANGE"; + case CSR_PSKEY_LC_COMBO_DISABLE_PIO_MASK: + return "LC_COMBO_DISABLE_PIO_MASK"; + case CSR_PSKEY_LC_COMBO_PRIORITY_PIO_MASK: + return "LC_COMBO_PRIORITY_PIO_MASK"; + case CSR_PSKEY_LC_COMBO_DOT11_CHANNEL_PIO_BASE: + return "LC_COMBO_DOT11_CHANNEL_PIO_BASE"; + case CSR_PSKEY_LC_COMBO_DOT11_BLOCK_CHANNELS: + return "LC_COMBO_DOT11_BLOCK_CHANNELS"; + case CSR_PSKEY_LC_MAX_TX_POWER_NO_RSSI: + return "LC_MAX_TX_POWER_NO_RSSI"; + case CSR_PSKEY_LC_CONNECTION_RX_WINDOW: + return "LC_CONNECTION_RX_WINDOW"; + case CSR_PSKEY_LC_COMBO_DOT11_TX_PROTECTION_MODE: + return "LC_COMBO_DOT11_TX_PROTECTION_MODE"; + case CSR_PSKEY_LC_ENHANCED_POWER_TABLE: + return "LC_ENHANCED_POWER_TABLE"; + case CSR_PSKEY_LC_WIDEBAND_RSSI_CONFIG: + return "LC_WIDEBAND_RSSI_CONFIG"; + case CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_LEAD: + return "LC_COMBO_DOT11_PRIORITY_LEAD"; + case CSR_PSKEY_BT_CLOCK_INIT: + return "BT_CLOCK_INIT"; + case CSR_PSKEY_TX_MR_MOD_DELAY: + return "TX_MR_MOD_DELAY"; + case CSR_PSKEY_RX_MR_SYNC_TIMING: + return "RX_MR_SYNC_TIMING"; + case CSR_PSKEY_RX_MR_SYNC_CONFIG: + return "RX_MR_SYNC_CONFIG"; + case CSR_PSKEY_LC_LOST_SYNC_SLOTS: + return "LC_LOST_SYNC_SLOTS"; + case CSR_PSKEY_RX_MR_SAMP_CONFIG: + return "RX_MR_SAMP_CONFIG"; + case CSR_PSKEY_AGC_HYST_LEVELS: + return "AGC_HYST_LEVELS"; + case CSR_PSKEY_RX_LEVEL_LOW_SIGNAL: + return "RX_LEVEL_LOW_SIGNAL"; + case CSR_PSKEY_AGC_IQ_LVL_VALUES: + return "AGC_IQ_LVL_VALUES"; + case CSR_PSKEY_MR_FTRIM_OFFSET_12DB: + return "MR_FTRIM_OFFSET_12DB"; + case CSR_PSKEY_MR_FTRIM_OFFSET_6DB: + return "MR_FTRIM_OFFSET_6DB"; + case CSR_PSKEY_NO_CAL_ON_BOOT: + return "NO_CAL_ON_BOOT"; + case CSR_PSKEY_RSSI_HI_TARGET: + return "RSSI_HI_TARGET"; + case CSR_PSKEY_PREFERRED_MIN_ATTENUATION: + return "PREFERRED_MIN_ATTENUATION"; + case CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_OVERRIDE: + return "LC_COMBO_DOT11_PRIORITY_OVERRIDE"; + case CSR_PSKEY_LC_MULTISLOT_HOLDOFF: + return "LC_MULTISLOT_HOLDOFF"; + case CSR_PSKEY_FREE_KEY_PIGEON_HOLE: + return "FREE_KEY_PIGEON_HOLE"; + case CSR_PSKEY_LINK_KEY_BD_ADDR0: + return "LINK_KEY_BD_ADDR0"; + case CSR_PSKEY_LINK_KEY_BD_ADDR1: + return "LINK_KEY_BD_ADDR1"; + case CSR_PSKEY_LINK_KEY_BD_ADDR2: + return "LINK_KEY_BD_ADDR2"; + case CSR_PSKEY_LINK_KEY_BD_ADDR3: + return "LINK_KEY_BD_ADDR3"; + case CSR_PSKEY_LINK_KEY_BD_ADDR4: + return "LINK_KEY_BD_ADDR4"; + case CSR_PSKEY_LINK_KEY_BD_ADDR5: + return "LINK_KEY_BD_ADDR5"; + case CSR_PSKEY_LINK_KEY_BD_ADDR6: + return "LINK_KEY_BD_ADDR6"; + case CSR_PSKEY_LINK_KEY_BD_ADDR7: + return "LINK_KEY_BD_ADDR7"; + case CSR_PSKEY_LINK_KEY_BD_ADDR8: + return "LINK_KEY_BD_ADDR8"; + case CSR_PSKEY_LINK_KEY_BD_ADDR9: + return "LINK_KEY_BD_ADDR9"; + case CSR_PSKEY_LINK_KEY_BD_ADDR10: + return "LINK_KEY_BD_ADDR10"; + case CSR_PSKEY_LINK_KEY_BD_ADDR11: + return "LINK_KEY_BD_ADDR11"; + case CSR_PSKEY_LINK_KEY_BD_ADDR12: + return "LINK_KEY_BD_ADDR12"; + case CSR_PSKEY_LINK_KEY_BD_ADDR13: + return "LINK_KEY_BD_ADDR13"; + case CSR_PSKEY_LINK_KEY_BD_ADDR14: + return "LINK_KEY_BD_ADDR14"; + case CSR_PSKEY_LINK_KEY_BD_ADDR15: + return "LINK_KEY_BD_ADDR15"; + case CSR_PSKEY_ENC_KEY_LMIN: + return "ENC_KEY_LMIN"; + case CSR_PSKEY_ENC_KEY_LMAX: + return "ENC_KEY_LMAX"; + case CSR_PSKEY_LOCAL_SUPPORTED_FEATURES: + return "LOCAL_SUPPORTED_FEATURES"; + case CSR_PSKEY_LM_USE_UNIT_KEY: + return "LM_USE_UNIT_KEY"; + case CSR_PSKEY_HCI_NOP_DISABLE: + return "HCI_NOP_DISABLE"; + case CSR_PSKEY_LM_MAX_EVENT_FILTERS: + return "LM_MAX_EVENT_FILTERS"; + case CSR_PSKEY_LM_USE_ENC_MODE_BROADCAST: + return "LM_USE_ENC_MODE_BROADCAST"; + case CSR_PSKEY_LM_TEST_SEND_ACCEPTED_TWICE: + return "LM_TEST_SEND_ACCEPTED_TWICE"; + case CSR_PSKEY_LM_MAX_PAGE_HOLD_TIME: + return "LM_MAX_PAGE_HOLD_TIME"; + case CSR_PSKEY_AFH_ADAPTATION_RESPONSE_TIME: + return "AFH_ADAPTATION_RESPONSE_TIME"; + case CSR_PSKEY_AFH_OPTIONS: + return "AFH_OPTIONS"; + case CSR_PSKEY_AFH_RSSI_RUN_PERIOD: + return "AFH_RSSI_RUN_PERIOD"; + case CSR_PSKEY_AFH_REENABLE_CHANNEL_TIME: + return "AFH_REENABLE_CHANNEL_TIME"; + case CSR_PSKEY_NO_DROP_ON_ACR_MS_FAIL: + return "NO_DROP_ON_ACR_MS_FAIL"; + case CSR_PSKEY_MAX_PRIVATE_KEYS: + return "MAX_PRIVATE_KEYS"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR0: + return "PRIVATE_LINK_KEY_BD_ADDR0"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR1: + return "PRIVATE_LINK_KEY_BD_ADDR1"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR2: + return "PRIVATE_LINK_KEY_BD_ADDR2"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR3: + return "PRIVATE_LINK_KEY_BD_ADDR3"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR4: + return "PRIVATE_LINK_KEY_BD_ADDR4"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR5: + return "PRIVATE_LINK_KEY_BD_ADDR5"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR6: + return "PRIVATE_LINK_KEY_BD_ADDR6"; + case CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR7: + return "PRIVATE_LINK_KEY_BD_ADDR7"; + case CSR_PSKEY_LOCAL_SUPPORTED_COMMANDS: + return "LOCAL_SUPPORTED_COMMANDS"; + case CSR_PSKEY_LM_MAX_ABSENCE_INDEX: + return "LM_MAX_ABSENCE_INDEX"; + case CSR_PSKEY_DEVICE_NAME: + return "DEVICE_NAME"; + case CSR_PSKEY_AFH_RSSI_THRESHOLD: + return "AFH_RSSI_THRESHOLD"; + case CSR_PSKEY_LM_CASUAL_SCAN_INTERVAL: + return "LM_CASUAL_SCAN_INTERVAL"; + case CSR_PSKEY_AFH_MIN_MAP_CHANGE: + return "AFH_MIN_MAP_CHANGE"; + case CSR_PSKEY_AFH_RSSI_LP_RUN_PERIOD: + return "AFH_RSSI_LP_RUN_PERIOD"; + case CSR_PSKEY_HCI_LMP_LOCAL_VERSION: + return "HCI_LMP_LOCAL_VERSION"; + case CSR_PSKEY_LMP_REMOTE_VERSION: + return "LMP_REMOTE_VERSION"; + case CSR_PSKEY_HOLD_ERROR_MESSAGE_NUMBER: + return "HOLD_ERROR_MESSAGE_NUMBER"; + case CSR_PSKEY_DFU_ATTRIBUTES: + return "DFU_ATTRIBUTES"; + case CSR_PSKEY_DFU_DETACH_TO: + return "DFU_DETACH_TO"; + case CSR_PSKEY_DFU_TRANSFER_SIZE: + return "DFU_TRANSFER_SIZE"; + case CSR_PSKEY_DFU_ENABLE: + return "DFU_ENABLE"; + case CSR_PSKEY_DFU_LIN_REG_ENABLE: + return "DFU_LIN_REG_ENABLE"; + case CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_MSB: + return "DFUENC_VMAPP_PK_MODULUS_MSB"; + case CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_LSB: + return "DFUENC_VMAPP_PK_MODULUS_LSB"; + case CSR_PSKEY_DFUENC_VMAPP_PK_M_DASH: + return "DFUENC_VMAPP_PK_M_DASH"; + case CSR_PSKEY_DFUENC_VMAPP_PK_R2N_MSB: + return "DFUENC_VMAPP_PK_R2N_MSB"; + case CSR_PSKEY_DFUENC_VMAPP_PK_R2N_LSB: + return "DFUENC_VMAPP_PK_R2N_LSB"; + case CSR_PSKEY_BCSP_LM_PS_BLOCK: + return "BCSP_LM_PS_BLOCK"; + case CSR_PSKEY_HOSTIO_FC_PS_BLOCK: + return "HOSTIO_FC_PS_BLOCK"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO0: + return "HOSTIO_PROTOCOL_INFO0"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO1: + return "HOSTIO_PROTOCOL_INFO1"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO2: + return "HOSTIO_PROTOCOL_INFO2"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO3: + return "HOSTIO_PROTOCOL_INFO3"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO4: + return "HOSTIO_PROTOCOL_INFO4"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO5: + return "HOSTIO_PROTOCOL_INFO5"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO6: + return "HOSTIO_PROTOCOL_INFO6"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO7: + return "HOSTIO_PROTOCOL_INFO7"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO8: + return "HOSTIO_PROTOCOL_INFO8"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO9: + return "HOSTIO_PROTOCOL_INFO9"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO10: + return "HOSTIO_PROTOCOL_INFO10"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO11: + return "HOSTIO_PROTOCOL_INFO11"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO12: + return "HOSTIO_PROTOCOL_INFO12"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO13: + return "HOSTIO_PROTOCOL_INFO13"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO14: + return "HOSTIO_PROTOCOL_INFO14"; + case CSR_PSKEY_HOSTIO_PROTOCOL_INFO15: + return "HOSTIO_PROTOCOL_INFO15"; + case CSR_PSKEY_HOSTIO_UART_RESET_TIMEOUT: + return "HOSTIO_UART_RESET_TIMEOUT"; + case CSR_PSKEY_HOSTIO_USE_HCI_EXTN: + return "HOSTIO_USE_HCI_EXTN"; + case CSR_PSKEY_HOSTIO_USE_HCI_EXTN_CCFC: + return "HOSTIO_USE_HCI_EXTN_CCFC"; + case CSR_PSKEY_HOSTIO_HCI_EXTN_PAYLOAD_SIZE: + return "HOSTIO_HCI_EXTN_PAYLOAD_SIZE"; + case CSR_PSKEY_BCSP_LM_CNF_CNT_LIMIT: + return "BCSP_LM_CNF_CNT_LIMIT"; + case CSR_PSKEY_HOSTIO_MAP_SCO_PCM: + return "HOSTIO_MAP_SCO_PCM"; + case CSR_PSKEY_HOSTIO_AWKWARD_PCM_SYNC: + return "HOSTIO_AWKWARD_PCM_SYNC"; + case CSR_PSKEY_HOSTIO_BREAK_POLL_PERIOD: + return "HOSTIO_BREAK_POLL_PERIOD"; + case CSR_PSKEY_HOSTIO_MIN_UART_HCI_SCO_SIZE: + return "HOSTIO_MIN_UART_HCI_SCO_SIZE"; + case CSR_PSKEY_HOSTIO_MAP_SCO_CODEC: + return "HOSTIO_MAP_SCO_CODEC"; + case CSR_PSKEY_PCM_CVSD_TX_HI_FREQ_BOOST: + return "PCM_CVSD_TX_HI_FREQ_BOOST"; + case CSR_PSKEY_PCM_CVSD_RX_HI_FREQ_BOOST: + return "PCM_CVSD_RX_HI_FREQ_BOOST"; + case CSR_PSKEY_PCM_CONFIG32: + return "PCM_CONFIG32"; + case CSR_PSKEY_USE_OLD_BCSP_LE: + return "USE_OLD_BCSP_LE"; + case CSR_PSKEY_PCM_CVSD_USE_NEW_FILTER: + return "PCM_CVSD_USE_NEW_FILTER"; + case CSR_PSKEY_PCM_FORMAT: + return "PCM_FORMAT"; + case CSR_PSKEY_CODEC_OUT_GAIN: + return "CODEC_OUT_GAIN"; + case CSR_PSKEY_CODEC_IN_GAIN: + return "CODEC_IN_GAIN"; + case CSR_PSKEY_CODEC_PIO: + return "CODEC_PIO"; + case CSR_PSKEY_PCM_LOW_JITTER_CONFIG: + return "PCM_LOW_JITTER_CONFIG"; + case CSR_PSKEY_HOSTIO_SCO_PCM_THRESHOLDS: + return "HOSTIO_SCO_PCM_THRESHOLDS"; + case CSR_PSKEY_HOSTIO_SCO_HCI_THRESHOLDS: + return "HOSTIO_SCO_HCI_THRESHOLDS"; + case CSR_PSKEY_HOSTIO_MAP_SCO_PCM_SLOT: + return "HOSTIO_MAP_SCO_PCM_SLOT"; + case CSR_PSKEY_UART_BAUDRATE: + return "UART_BAUDRATE"; + case CSR_PSKEY_UART_CONFIG_BCSP: + return "UART_CONFIG_BCSP"; + case CSR_PSKEY_UART_CONFIG_H4: + return "UART_CONFIG_H4"; + case CSR_PSKEY_UART_CONFIG_H5: + return "UART_CONFIG_H5"; + case CSR_PSKEY_UART_CONFIG_USR: + return "UART_CONFIG_USR"; + case CSR_PSKEY_UART_TX_CRCS: + return "UART_TX_CRCS"; + case CSR_PSKEY_UART_ACK_TIMEOUT: + return "UART_ACK_TIMEOUT"; + case CSR_PSKEY_UART_TX_MAX_ATTEMPTS: + return "UART_TX_MAX_ATTEMPTS"; + case CSR_PSKEY_UART_TX_WINDOW_SIZE: + return "UART_TX_WINDOW_SIZE"; + case CSR_PSKEY_UART_HOST_WAKE: + return "UART_HOST_WAKE"; + case CSR_PSKEY_HOSTIO_THROTTLE_TIMEOUT: + return "HOSTIO_THROTTLE_TIMEOUT"; + case CSR_PSKEY_PCM_ALWAYS_ENABLE: + return "PCM_ALWAYS_ENABLE"; + case CSR_PSKEY_UART_HOST_WAKE_SIGNAL: + return "UART_HOST_WAKE_SIGNAL"; + case CSR_PSKEY_UART_CONFIG_H4DS: + return "UART_CONFIG_H4DS"; + case CSR_PSKEY_H4DS_WAKE_DURATION: + return "H4DS_WAKE_DURATION"; + case CSR_PSKEY_H4DS_MAXWU: + return "H4DS_MAXWU"; + case CSR_PSKEY_H4DS_LE_TIMER_PERIOD: + return "H4DS_LE_TIMER_PERIOD"; + case CSR_PSKEY_H4DS_TWU_TIMER_PERIOD: + return "H4DS_TWU_TIMER_PERIOD"; + case CSR_PSKEY_H4DS_UART_IDLE_TIMER_PERIOD: + return "H4DS_UART_IDLE_TIMER_PERIOD"; + case CSR_PSKEY_ANA_FTRIM: + return "ANA_FTRIM"; + case CSR_PSKEY_WD_TIMEOUT: + return "WD_TIMEOUT"; + case CSR_PSKEY_WD_PERIOD: + return "WD_PERIOD"; + case CSR_PSKEY_HOST_INTERFACE: + return "HOST_INTERFACE"; + case CSR_PSKEY_HQ_HOST_TIMEOUT: + return "HQ_HOST_TIMEOUT"; + case CSR_PSKEY_HQ_ACTIVE: + return "HQ_ACTIVE"; + case CSR_PSKEY_BCCMD_SECURITY_ACTIVE: + return "BCCMD_SECURITY_ACTIVE"; + case CSR_PSKEY_ANA_FREQ: + return "ANA_FREQ"; + case CSR_PSKEY_PIO_PROTECT_MASK: + return "PIO_PROTECT_MASK"; + case CSR_PSKEY_PMALLOC_SIZES: + return "PMALLOC_SIZES"; + case CSR_PSKEY_UART_BAUD_RATE: + return "UART_BAUD_RATE"; + case CSR_PSKEY_UART_CONFIG: + return "UART_CONFIG"; + case CSR_PSKEY_STUB: + return "STUB"; + case CSR_PSKEY_TXRX_PIO_CONTROL: + return "TXRX_PIO_CONTROL"; + case CSR_PSKEY_ANA_RX_LEVEL: + return "ANA_RX_LEVEL"; + case CSR_PSKEY_ANA_RX_FTRIM: + return "ANA_RX_FTRIM"; + case CSR_PSKEY_PSBC_DATA_VERSION: + return "PSBC_DATA_VERSION"; + case CSR_PSKEY_PCM0_ATTENUATION: + return "PCM0_ATTENUATION"; + case CSR_PSKEY_LO_LVL_MAX: + return "LO_LVL_MAX"; + case CSR_PSKEY_LO_ADC_AMPL_MIN: + return "LO_ADC_AMPL_MIN"; + case CSR_PSKEY_LO_ADC_AMPL_MAX: + return "LO_ADC_AMPL_MAX"; + case CSR_PSKEY_IQ_TRIM_CHANNEL: + return "IQ_TRIM_CHANNEL"; + case CSR_PSKEY_IQ_TRIM_GAIN: + return "IQ_TRIM_GAIN"; + case CSR_PSKEY_IQ_TRIM_ENABLE: + return "IQ_TRIM_ENABLE"; + case CSR_PSKEY_TX_OFFSET_HALF_MHZ: + return "TX_OFFSET_HALF_MHZ"; + case CSR_PSKEY_GBL_MISC_ENABLES: + return "GBL_MISC_ENABLES"; + case CSR_PSKEY_UART_SLEEP_TIMEOUT: + return "UART_SLEEP_TIMEOUT"; + case CSR_PSKEY_DEEP_SLEEP_STATE: + return "DEEP_SLEEP_STATE"; + case CSR_PSKEY_IQ_ENABLE_PHASE_TRIM: + return "IQ_ENABLE_PHASE_TRIM"; + case CSR_PSKEY_HCI_HANDLE_FREEZE_PERIOD: + return "HCI_HANDLE_FREEZE_PERIOD"; + case CSR_PSKEY_MAX_FROZEN_HCI_HANDLES: + return "MAX_FROZEN_HCI_HANDLES"; + case CSR_PSKEY_PAGETABLE_DESTRUCTION_DELAY: + return "PAGETABLE_DESTRUCTION_DELAY"; + case CSR_PSKEY_IQ_TRIM_PIO_SETTINGS: + return "IQ_TRIM_PIO_SETTINGS"; + case CSR_PSKEY_USE_EXTERNAL_CLOCK: + return "USE_EXTERNAL_CLOCK"; + case CSR_PSKEY_DEEP_SLEEP_WAKE_CTS: + return "DEEP_SLEEP_WAKE_CTS"; + case CSR_PSKEY_FC_HC2H_FLUSH_DELAY: + return "FC_HC2H_FLUSH_DELAY"; + case CSR_PSKEY_RX_HIGHSIDE: + return "RX_HIGHSIDE"; + case CSR_PSKEY_TX_PRE_LVL: + return "TX_PRE_LVL"; + case CSR_PSKEY_RX_SINGLE_ENDED: + return "RX_SINGLE_ENDED"; + case CSR_PSKEY_TX_FILTER_CONFIG: + return "TX_FILTER_CONFIG"; + case CSR_PSKEY_CLOCK_REQUEST_ENABLE: + return "CLOCK_REQUEST_ENABLE"; + case CSR_PSKEY_RX_MIN_ATTEN: + return "RX_MIN_ATTEN"; + case CSR_PSKEY_XTAL_TARGET_AMPLITUDE: + return "XTAL_TARGET_AMPLITUDE"; + case CSR_PSKEY_PCM_MIN_CPU_CLOCK: + return "PCM_MIN_CPU_CLOCK"; + case CSR_PSKEY_HOST_INTERFACE_PIO_USB: + return "HOST_INTERFACE_PIO_USB"; + case CSR_PSKEY_CPU_IDLE_MODE: + return "CPU_IDLE_MODE"; + case CSR_PSKEY_DEEP_SLEEP_CLEAR_RTS: + return "DEEP_SLEEP_CLEAR_RTS"; + case CSR_PSKEY_RF_RESONANCE_TRIM: + return "RF_RESONANCE_TRIM"; + case CSR_PSKEY_DEEP_SLEEP_PIO_WAKE: + return "DEEP_SLEEP_PIO_WAKE"; + case CSR_PSKEY_DRAIN_BORE_TIMERS: + return "DRAIN_BORE_TIMERS"; + case CSR_PSKEY_DRAIN_TX_POWER_BASE: + return "DRAIN_TX_POWER_BASE"; + case CSR_PSKEY_MODULE_ID: + return "MODULE_ID"; + case CSR_PSKEY_MODULE_DESIGN: + return "MODULE_DESIGN"; + case CSR_PSKEY_MODULE_SECURITY_CODE: + return "MODULE_SECURITY_CODE"; + case CSR_PSKEY_VM_DISABLE: + return "VM_DISABLE"; + case CSR_PSKEY_MOD_MANUF0: + return "MOD_MANUF0"; + case CSR_PSKEY_MOD_MANUF1: + return "MOD_MANUF1"; + case CSR_PSKEY_MOD_MANUF2: + return "MOD_MANUF2"; + case CSR_PSKEY_MOD_MANUF3: + return "MOD_MANUF3"; + case CSR_PSKEY_MOD_MANUF4: + return "MOD_MANUF4"; + case CSR_PSKEY_MOD_MANUF5: + return "MOD_MANUF5"; + case CSR_PSKEY_MOD_MANUF6: + return "MOD_MANUF6"; + case CSR_PSKEY_MOD_MANUF7: + return "MOD_MANUF7"; + case CSR_PSKEY_MOD_MANUF8: + return "MOD_MANUF8"; + case CSR_PSKEY_MOD_MANUF9: + return "MOD_MANUF9"; + case CSR_PSKEY_DUT_VM_DISABLE: + return "DUT_VM_DISABLE"; + case CSR_PSKEY_USR0: + return "USR0"; + case CSR_PSKEY_USR1: + return "USR1"; + case CSR_PSKEY_USR2: + return "USR2"; + case CSR_PSKEY_USR3: + return "USR3"; + case CSR_PSKEY_USR4: + return "USR4"; + case CSR_PSKEY_USR5: + return "USR5"; + case CSR_PSKEY_USR6: + return "USR6"; + case CSR_PSKEY_USR7: + return "USR7"; + case CSR_PSKEY_USR8: + return "USR8"; + case CSR_PSKEY_USR9: + return "USR9"; + case CSR_PSKEY_USR10: + return "USR10"; + case CSR_PSKEY_USR11: + return "USR11"; + case CSR_PSKEY_USR12: + return "USR12"; + case CSR_PSKEY_USR13: + return "USR13"; + case CSR_PSKEY_USR14: + return "USR14"; + case CSR_PSKEY_USR15: + return "USR15"; + case CSR_PSKEY_USR16: + return "USR16"; + case CSR_PSKEY_USR17: + return "USR17"; + case CSR_PSKEY_USR18: + return "USR18"; + case CSR_PSKEY_USR19: + return "USR19"; + case CSR_PSKEY_USR20: + return "USR20"; + case CSR_PSKEY_USR21: + return "USR21"; + case CSR_PSKEY_USR22: + return "USR22"; + case CSR_PSKEY_USR23: + return "USR23"; + case CSR_PSKEY_USR24: + return "USR24"; + case CSR_PSKEY_USR25: + return "USR25"; + case CSR_PSKEY_USR26: + return "USR26"; + case CSR_PSKEY_USR27: + return "USR27"; + case CSR_PSKEY_USR28: + return "USR28"; + case CSR_PSKEY_USR29: + return "USR29"; + case CSR_PSKEY_USR30: + return "USR30"; + case CSR_PSKEY_USR31: + return "USR31"; + case CSR_PSKEY_USR32: + return "USR32"; + case CSR_PSKEY_USR33: + return "USR33"; + case CSR_PSKEY_USR34: + return "USR34"; + case CSR_PSKEY_USR35: + return "USR35"; + case CSR_PSKEY_USR36: + return "USR36"; + case CSR_PSKEY_USR37: + return "USR37"; + case CSR_PSKEY_USR38: + return "USR38"; + case CSR_PSKEY_USR39: + return "USR39"; + case CSR_PSKEY_USR40: + return "USR40"; + case CSR_PSKEY_USR41: + return "USR41"; + case CSR_PSKEY_USR42: + return "USR42"; + case CSR_PSKEY_USR43: + return "USR43"; + case CSR_PSKEY_USR44: + return "USR44"; + case CSR_PSKEY_USR45: + return "USR45"; + case CSR_PSKEY_USR46: + return "USR46"; + case CSR_PSKEY_USR47: + return "USR47"; + case CSR_PSKEY_USR48: + return "USR48"; + case CSR_PSKEY_USR49: + return "USR49"; + case CSR_PSKEY_USB_VERSION: + return "USB_VERSION"; + case CSR_PSKEY_USB_DEVICE_CLASS_CODES: + return "USB_DEVICE_CLASS_CODES"; + case CSR_PSKEY_USB_VENDOR_ID: + return "USB_VENDOR_ID"; + case CSR_PSKEY_USB_PRODUCT_ID: + return "USB_PRODUCT_ID"; + case CSR_PSKEY_USB_MANUF_STRING: + return "USB_MANUF_STRING"; + case CSR_PSKEY_USB_PRODUCT_STRING: + return "USB_PRODUCT_STRING"; + case CSR_PSKEY_USB_SERIAL_NUMBER_STRING: + return "USB_SERIAL_NUMBER_STRING"; + case CSR_PSKEY_USB_CONFIG_STRING: + return "USB_CONFIG_STRING"; + case CSR_PSKEY_USB_ATTRIBUTES: + return "USB_ATTRIBUTES"; + case CSR_PSKEY_USB_MAX_POWER: + return "USB_MAX_POWER"; + case CSR_PSKEY_USB_BT_IF_CLASS_CODES: + return "USB_BT_IF_CLASS_CODES"; + case CSR_PSKEY_USB_LANGID: + return "USB_LANGID"; + case CSR_PSKEY_USB_DFU_CLASS_CODES: + return "USB_DFU_CLASS_CODES"; + case CSR_PSKEY_USB_DFU_PRODUCT_ID: + return "USB_DFU_PRODUCT_ID"; + case CSR_PSKEY_USB_PIO_DETACH: + return "USB_PIO_DETACH"; + case CSR_PSKEY_USB_PIO_WAKEUP: + return "USB_PIO_WAKEUP"; + case CSR_PSKEY_USB_PIO_PULLUP: + return "USB_PIO_PULLUP"; + case CSR_PSKEY_USB_PIO_VBUS: + return "USB_PIO_VBUS"; + case CSR_PSKEY_USB_PIO_WAKE_TIMEOUT: + return "USB_PIO_WAKE_TIMEOUT"; + case CSR_PSKEY_USB_PIO_RESUME: + return "USB_PIO_RESUME"; + case CSR_PSKEY_USB_BT_SCO_IF_CLASS_CODES: + return "USB_BT_SCO_IF_CLASS_CODES"; + case CSR_PSKEY_USB_SUSPEND_PIO_LEVEL: + return "USB_SUSPEND_PIO_LEVEL"; + case CSR_PSKEY_USB_SUSPEND_PIO_DIR: + return "USB_SUSPEND_PIO_DIR"; + case CSR_PSKEY_USB_SUSPEND_PIO_MASK: + return "USB_SUSPEND_PIO_MASK"; + case CSR_PSKEY_USB_ENDPOINT_0_MAX_PACKET_SIZE: + return "USB_ENDPOINT_0_MAX_PACKET_SIZE"; + case CSR_PSKEY_USB_CONFIG: + return "USB_CONFIG"; + case CSR_PSKEY_RADIOTEST_ATTEN_INIT: + return "RADIOTEST_ATTEN_INIT"; + case CSR_PSKEY_RADIOTEST_FIRST_TRIM_TIME: + return "RADIOTEST_FIRST_TRIM_TIME"; + case CSR_PSKEY_RADIOTEST_SUBSEQUENT_TRIM_TIME: + return "RADIOTEST_SUBSEQUENT_TRIM_TIME"; + case CSR_PSKEY_RADIOTEST_LO_LVL_TRIM_ENABLE: + return "RADIOTEST_LO_LVL_TRIM_ENABLE"; + case CSR_PSKEY_RADIOTEST_DISABLE_MODULATION: + return "RADIOTEST_DISABLE_MODULATION"; + case CSR_PSKEY_RFCOMM_FCON_THRESHOLD: + return "RFCOMM_FCON_THRESHOLD"; + case CSR_PSKEY_RFCOMM_FCOFF_THRESHOLD: + return "RFCOMM_FCOFF_THRESHOLD"; + case CSR_PSKEY_IPV6_STATIC_ADDR: + return "IPV6_STATIC_ADDR"; + case CSR_PSKEY_IPV4_STATIC_ADDR: + return "IPV4_STATIC_ADDR"; + case CSR_PSKEY_IPV6_STATIC_PREFIX_LEN: + return "IPV6_STATIC_PREFIX_LEN"; + case CSR_PSKEY_IPV6_STATIC_ROUTER_ADDR: + return "IPV6_STATIC_ROUTER_ADDR"; + case CSR_PSKEY_IPV4_STATIC_SUBNET_MASK: + return "IPV4_STATIC_SUBNET_MASK"; + case CSR_PSKEY_IPV4_STATIC_ROUTER_ADDR: + return "IPV4_STATIC_ROUTER_ADDR"; + case CSR_PSKEY_MDNS_NAME: + return "MDNS_NAME"; + case CSR_PSKEY_FIXED_PIN: + return "FIXED_PIN"; + case CSR_PSKEY_MDNS_PORT: + return "MDNS_PORT"; + case CSR_PSKEY_MDNS_TTL: + return "MDNS_TTL"; + case CSR_PSKEY_MDNS_IPV4_ADDR: + return "MDNS_IPV4_ADDR"; + case CSR_PSKEY_ARP_CACHE_TIMEOUT: + return "ARP_CACHE_TIMEOUT"; + case CSR_PSKEY_HFP_POWER_TABLE: + return "HFP_POWER_TABLE"; + case CSR_PSKEY_DRAIN_BORE_TIMER_COUNTERS: + return "DRAIN_BORE_TIMER_COUNTERS"; + case CSR_PSKEY_DRAIN_BORE_COUNTERS: + return "DRAIN_BORE_COUNTERS"; + case CSR_PSKEY_LOOP_FILTER_TRIM: + return "LOOP_FILTER_TRIM"; + case CSR_PSKEY_DRAIN_BORE_CURRENT_PEAK: + return "DRAIN_BORE_CURRENT_PEAK"; + case CSR_PSKEY_VM_E2_CACHE_LIMIT: + return "VM_E2_CACHE_LIMIT"; + case CSR_PSKEY_FORCE_16MHZ_REF_PIO: + return "FORCE_16MHZ_REF_PIO"; + case CSR_PSKEY_CDMA_LO_REF_LIMITS: + return "CDMA_LO_REF_LIMITS"; + case CSR_PSKEY_CDMA_LO_ERROR_LIMITS: + return "CDMA_LO_ERROR_LIMITS"; + case CSR_PSKEY_CLOCK_STARTUP_DELAY: + return "CLOCK_STARTUP_DELAY"; + case CSR_PSKEY_DEEP_SLEEP_CORRECTION_FACTOR: + return "DEEP_SLEEP_CORRECTION_FACTOR"; + case CSR_PSKEY_TEMPERATURE_CALIBRATION: + return "TEMPERATURE_CALIBRATION"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA: + return "TEMPERATURE_VS_DELTA_INTERNAL_PA"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL: + return "TEMPERATURE_VS_DELTA_TX_PRE_LVL"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB: + return "TEMPERATURE_VS_DELTA_TX_BB"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_ANA_FTRIM: + return "TEMPERATURE_VS_DELTA_ANA_FTRIM"; + case CSR_PSKEY_TEST_DELTA_OFFSET: + return "TEST_DELTA_OFFSET"; + case CSR_PSKEY_RX_DYNAMIC_LVL_OFFSET: + return "RX_DYNAMIC_LVL_OFFSET"; + case CSR_PSKEY_TEST_FORCE_OFFSET: + return "TEST_FORCE_OFFSET"; + case CSR_PSKEY_RF_TRAP_BAD_DIVISION_RATIOS: + return "RF_TRAP_BAD_DIVISION_RATIOS"; + case CSR_PSKEY_RADIOTEST_CDMA_LO_REF_LIMITS: + return "RADIOTEST_CDMA_LO_REF_LIMITS"; + case CSR_PSKEY_INITIAL_BOOTMODE: + return "INITIAL_BOOTMODE"; + case CSR_PSKEY_ONCHIP_HCI_CLIENT: + return "ONCHIP_HCI_CLIENT"; + case CSR_PSKEY_RX_ATTEN_BACKOFF: + return "RX_ATTEN_BACKOFF"; + case CSR_PSKEY_RX_ATTEN_UPDATE_RATE: + return "RX_ATTEN_UPDATE_RATE"; + case CSR_PSKEY_SYNTH_TXRX_THRESHOLDS: + return "SYNTH_TXRX_THRESHOLDS"; + case CSR_PSKEY_MIN_WAIT_STATES: + return "MIN_WAIT_STATES"; + case CSR_PSKEY_RSSI_CORRECTION: + return "RSSI_CORRECTION"; + case CSR_PSKEY_SCHED_THROTTLE_TIMEOUT: + return "SCHED_THROTTLE_TIMEOUT"; + case CSR_PSKEY_DEEP_SLEEP_USE_EXTERNAL_CLOCK: + return "DEEP_SLEEP_USE_EXTERNAL_CLOCK"; + case CSR_PSKEY_TRIM_RADIO_FILTERS: + return "TRIM_RADIO_FILTERS"; + case CSR_PSKEY_TRANSMIT_OFFSET: + return "TRANSMIT_OFFSET"; + case CSR_PSKEY_USB_VM_CONTROL: + return "USB_VM_CONTROL"; + case CSR_PSKEY_MR_ANA_RX_FTRIM: + return "MR_ANA_RX_FTRIM"; + case CSR_PSKEY_I2C_CONFIG: + return "I2C_CONFIG"; + case CSR_PSKEY_IQ_LVL_RX: + return "IQ_LVL_RX"; + case CSR_PSKEY_MR_TX_FILTER_CONFIG: + return "MR_TX_FILTER_CONFIG"; + case CSR_PSKEY_MR_TX_CONFIG2: + return "MR_TX_CONFIG2"; + case CSR_PSKEY_USB_DONT_RESET_BOOTMODE_ON_HOST_RESET: + return "USB_DONT_RESET_BOOTMODE_ON_HOST_RESET"; + case CSR_PSKEY_LC_USE_THROTTLING: + return "LC_USE_THROTTLING"; + case CSR_PSKEY_CHARGER_TRIM: + return "CHARGER_TRIM"; + case CSR_PSKEY_CLOCK_REQUEST_FEATURES: + return "CLOCK_REQUEST_FEATURES"; + case CSR_PSKEY_TRANSMIT_OFFSET_CLASS1: + return "TRANSMIT_OFFSET_CLASS1"; + case CSR_PSKEY_TX_AVOID_PA_CLASS1_PIO: + return "TX_AVOID_PA_CLASS1_PIO"; + case CSR_PSKEY_MR_PIO_CONFIG: + return "MR_PIO_CONFIG"; + case CSR_PSKEY_UART_CONFIG2: + return "UART_CONFIG2"; + case CSR_PSKEY_CLASS1_IQ_LVL: + return "CLASS1_IQ_LVL"; + case CSR_PSKEY_CLASS1_TX_CONFIG2: + return "CLASS1_TX_CONFIG2"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA_CLASS1: + return "TEMPERATURE_VS_DELTA_INTERNAL_PA_CLASS1"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_EXTERNAL_PA_CLASS1: + return "TEMPERATURE_VS_DELTA_EXTERNAL_PA_CLASS1"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL_MR: + return "TEMPERATURE_VS_DELTA_TX_PRE_LVL_MR"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_HEADER: + return "TEMPERATURE_VS_DELTA_TX_BB_MR_HEADER"; + case CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_PAYLOAD: + return "TEMPERATURE_VS_DELTA_TX_BB_MR_PAYLOAD"; + case CSR_PSKEY_RX_MR_EQ_TAPS: + return "RX_MR_EQ_TAPS"; + case CSR_PSKEY_TX_PRE_LVL_CLASS1: + return "TX_PRE_LVL_CLASS1"; + case CSR_PSKEY_ANALOGUE_ATTENUATOR: + return "ANALOGUE_ATTENUATOR"; + case CSR_PSKEY_MR_RX_FILTER_TRIM: + return "MR_RX_FILTER_TRIM"; + case CSR_PSKEY_MR_RX_FILTER_RESPONSE: + return "MR_RX_FILTER_RESPONSE"; + case CSR_PSKEY_PIO_WAKEUP_STATE: + return "PIO_WAKEUP_STATE"; + case CSR_PSKEY_MR_TX_IF_ATTEN_OFF_TEMP: + return "MR_TX_IF_ATTEN_OFF_TEMP"; + case CSR_PSKEY_LO_DIV_LATCH_BYPASS: + return "LO_DIV_LATCH_BYPASS"; + case CSR_PSKEY_LO_VCO_STANDBY: + return "LO_VCO_STANDBY"; + case CSR_PSKEY_SLOW_CLOCK_FILTER_SHIFT: + return "SLOW_CLOCK_FILTER_SHIFT"; + case CSR_PSKEY_SLOW_CLOCK_FILTER_DIVIDER: + return "SLOW_CLOCK_FILTER_DIVIDER"; + case CSR_PSKEY_USB_ATTRIBUTES_POWER: + return "USB_ATTRIBUTES_POWER"; + case CSR_PSKEY_USB_ATTRIBUTES_WAKEUP: + return "USB_ATTRIBUTES_WAKEUP"; + case CSR_PSKEY_DFU_ATTRIBUTES_MANIFESTATION_TOLERANT: + return "DFU_ATTRIBUTES_MANIFESTATION_TOLERANT"; + case CSR_PSKEY_DFU_ATTRIBUTES_CAN_UPLOAD: + return "DFU_ATTRIBUTES_CAN_UPLOAD"; + case CSR_PSKEY_DFU_ATTRIBUTES_CAN_DOWNLOAD: + return "DFU_ATTRIBUTES_CAN_DOWNLOAD"; + case CSR_PSKEY_UART_CONFIG_STOP_BITS: + return "UART_CONFIG_STOP_BITS"; + case CSR_PSKEY_UART_CONFIG_PARITY_BIT: + return "UART_CONFIG_PARITY_BIT"; + case CSR_PSKEY_UART_CONFIG_FLOW_CTRL_EN: + return "UART_CONFIG_FLOW_CTRL_EN"; + case CSR_PSKEY_UART_CONFIG_RTS_AUTO_EN: + return "UART_CONFIG_RTS_AUTO_EN"; + case CSR_PSKEY_UART_CONFIG_RTS: + return "UART_CONFIG_RTS"; + case CSR_PSKEY_UART_CONFIG_TX_ZERO_EN: + return "UART_CONFIG_TX_ZERO_EN"; + case CSR_PSKEY_UART_CONFIG_NON_BCSP_EN: + return "UART_CONFIG_NON_BCSP_EN"; + case CSR_PSKEY_UART_CONFIG_RX_RATE_DELAY: + return "UART_CONFIG_RX_RATE_DELAY"; + case CSR_PSKEY_UART_SEQ_TIMEOUT: + return "UART_SEQ_TIMEOUT"; + case CSR_PSKEY_UART_SEQ_RETRIES: + return "UART_SEQ_RETRIES"; + case CSR_PSKEY_UART_SEQ_WINSIZE: + return "UART_SEQ_WINSIZE"; + case CSR_PSKEY_UART_USE_CRC_ON_TX: + return "UART_USE_CRC_ON_TX"; + case CSR_PSKEY_UART_HOST_INITIAL_STATE: + return "UART_HOST_INITIAL_STATE"; + case CSR_PSKEY_UART_HOST_ATTENTION_SPAN: + return "UART_HOST_ATTENTION_SPAN"; + case CSR_PSKEY_UART_HOST_WAKEUP_TIME: + return "UART_HOST_WAKEUP_TIME"; + case CSR_PSKEY_UART_HOST_WAKEUP_WAIT: + return "UART_HOST_WAKEUP_WAIT"; + case CSR_PSKEY_BCSP_LM_MODE: + return "BCSP_LM_MODE"; + case CSR_PSKEY_BCSP_LM_SYNC_RETRIES: + return "BCSP_LM_SYNC_RETRIES"; + case CSR_PSKEY_BCSP_LM_TSHY: + return "BCSP_LM_TSHY"; + case CSR_PSKEY_UART_DFU_CONFIG_STOP_BITS: + return "UART_DFU_CONFIG_STOP_BITS"; + case CSR_PSKEY_UART_DFU_CONFIG_PARITY_BIT: + return "UART_DFU_CONFIG_PARITY_BIT"; + case CSR_PSKEY_UART_DFU_CONFIG_FLOW_CTRL_EN: + return "UART_DFU_CONFIG_FLOW_CTRL_EN"; + case CSR_PSKEY_UART_DFU_CONFIG_RTS_AUTO_EN: + return "UART_DFU_CONFIG_RTS_AUTO_EN"; + case CSR_PSKEY_UART_DFU_CONFIG_RTS: + return "UART_DFU_CONFIG_RTS"; + case CSR_PSKEY_UART_DFU_CONFIG_TX_ZERO_EN: + return "UART_DFU_CONFIG_TX_ZERO_EN"; + case CSR_PSKEY_UART_DFU_CONFIG_NON_BCSP_EN: + return "UART_DFU_CONFIG_NON_BCSP_EN"; + case CSR_PSKEY_UART_DFU_CONFIG_RX_RATE_DELAY: + return "UART_DFU_CONFIG_RX_RATE_DELAY"; + case CSR_PSKEY_AMUX_AIO0: + return "AMUX_AIO0"; + case CSR_PSKEY_AMUX_AIO1: + return "AMUX_AIO1"; + case CSR_PSKEY_AMUX_AIO2: + return "AMUX_AIO2"; + case CSR_PSKEY_AMUX_AIO3: + return "AMUX_AIO3"; + case CSR_PSKEY_LOCAL_NAME_SIMPLIFIED: + return "LOCAL_NAME_SIMPLIFIED"; + case CSR_PSKEY_EXTENDED_STUB: + return "EXTENDED_STUB"; + default: + return "UNKNOWN"; + } +} + +int csr_write_varid_valueless(int dd, uint16_t seqnum, uint16_t varid) +{ + unsigned char cmd[] = { 0x02, 0x00, 0x09, 0x00, + seqnum & 0xff, seqnum >> 8, varid & 0xff, varid >> 8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + + switch (varid) { + case CSR_VARID_COLD_RESET: + case CSR_VARID_WARM_RESET: + case CSR_VARID_COLD_HALT: + case CSR_VARID_WARM_HALT: + return hci_send_cmd(dd, OGF_VENDOR_CMD, 0x00, sizeof(cmd) + 1, cp); + } + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + if (rp[0] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[9] + (rp[10] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + return 0; +} + +int csr_write_varid_complex(int dd, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length) +{ + unsigned char cmd[] = { 0x02, 0x00, ((length / 2) + 5) & 0xff, ((length / 2) + 5) >> 8, + seqnum & 0xff, seqnum >> 8, varid & 0xff, varid >> 8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + memcpy(cp + 11, value, length); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + length + 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + if (rp[0] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[9] + (rp[10] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + return 0; +} + +int csr_read_varid_complex(int dd, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length) +{ + unsigned char cmd[] = { 0x00, 0x00, ((length / 2) + 5) & 0xff, ((length / 2) + 5) >> 8, + seqnum & 0xff, seqnum >> 8, varid & 0xff, varid >> 8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + memcpy(cp + 11, value, length); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + length + 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + if (rp[0] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[9] + (rp[10] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + memcpy(value, rp + 11, length); + + return 0; +} + +int csr_read_varid_uint16(int dd, uint16_t seqnum, uint16_t varid, uint16_t *value) +{ + unsigned char cmd[] = { 0x00, 0x00, 0x09, 0x00, + seqnum & 0xff, seqnum >> 8, varid & 0xff, varid >> 8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + if (rp[0] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[9] + (rp[10] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + *value = rp[11] + (rp[12] << 8); + + return 0; +} + +int csr_read_varid_uint32(int dd, uint16_t seqnum, uint16_t varid, uint32_t *value) +{ + unsigned char cmd[] = { 0x00, 0x00, 0x09, 0x00, + seqnum & 0xff, seqnum >> 8, varid & 0xff, varid >> 8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + if (rp[0] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[9] + (rp[10] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + *value = ((rp[11] + (rp[12] << 8)) << 16) + (rp[13] + (rp[14] << 8)); + + return 0; +} + +int csr_read_pskey_complex(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint8_t *value, uint16_t length) +{ + unsigned char cmd[] = { 0x00, 0x00, ((length / 2) + 8) & 0xff, ((length / 2) + 8) >> 8, + seqnum & 0xff, seqnum >> 8, 0x03, 0x70, 0x00, 0x00, + pskey & 0xff, pskey >> 8, + (length / 2) & 0xff, (length / 2) >> 8, + stores & 0xff, stores >> 8, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + length - 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + if (rp[0] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[9] + (rp[10] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + memcpy(value, rp + 17, length); + + return 0; +} + +int csr_write_pskey_complex(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint8_t *value, uint16_t length) +{ + unsigned char cmd[] = { 0x02, 0x00, ((length / 2) + 8) & 0xff, ((length / 2) + 8) >> 8, + seqnum & 0xff, seqnum >> 8, 0x03, 0x70, 0x00, 0x00, + pskey & 0xff, pskey >> 8, + (length / 2) & 0xff, (length / 2) >> 8, + stores & 0xff, stores >> 8, 0x00, 0x00 }; + + unsigned char cp[254], rp[254]; + struct hci_request rq; + + memset(&cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + + memcpy(cp + 17, value, length); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = sizeof(cmd) + length - 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + if (rp[0] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[9] + (rp[10] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + return 0; +} + +int csr_read_pskey_uint16(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint16_t *value) +{ + uint8_t array[2] = { 0x00, 0x00 }; + int err; + + err = csr_read_pskey_complex(dd, seqnum, pskey, stores, array, 2); + + *value = array[0] + (array[1] << 8); + + return err; +} + +int csr_write_pskey_uint16(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint16_t value) +{ + uint8_t array[2] = { value & 0xff, value >> 8 }; + + return csr_write_pskey_complex(dd, seqnum, pskey, stores, array, 2); +} + +int csr_read_pskey_uint32(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint32_t *value) +{ + uint8_t array[4] = { 0x00, 0x00, 0x00, 0x00 }; + int err; + + err = csr_read_pskey_complex(dd, seqnum, pskey, stores, array, 4); + + *value = ((array[0] + (array[1] << 8)) << 16) + + (array[2] + (array[3] << 8)); + + return err; +} + +int csr_write_pskey_uint32(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint32_t value) +{ + uint8_t array[4] = { (value & 0xff0000) >> 16, value >> 24, + value & 0xff, (value & 0xff00) >> 8 }; + + return csr_write_pskey_complex(dd, seqnum, pskey, stores, array, 4); +} + +int psr_put(uint16_t pskey, uint8_t *value, uint16_t size) +{ + struct psr_data *item; + + item = malloc(sizeof(*item)); + if (!item) + return -ENOMEM; + + item->pskey = pskey; + + if (size > 0) { + item->value = malloc(size); + if (!item->value) { + free(item); + return -ENOMEM; + } + + memcpy(item->value, value, size); + item->size = size; + } else { + item->value = NULL; + item->size = 0; + } + + item->next = NULL; + + if (!head) + head = item; + else + tail->next = item; + + tail = item; + + return 0; +} + +int psr_get(uint16_t *pskey, uint8_t *value, uint16_t *size) +{ + struct psr_data *item = head; + + if (!head) + return -ENOENT; + + *pskey = item->pskey; + + if (item->value) { + if (value && item->size > 0) + memcpy(value, item->value, item->size); + free(item->value); + *size = item->size; + } else + *size = 0; + + if (head == tail) + tail = NULL; + + head = head->next; + free(item); + + return 0; +} + +static int parse_line(char *str) +{ + uint8_t array[256]; + uint16_t value, pskey, length = 0; + char *off, *end; + + pskey = strtol(str + 1, NULL, 16); + off = strstr(str, "="); + if (!off) + return -EIO; + + off++; + + while (length <= sizeof(array) - 2) { + value = strtol(off, &end, 16); + if (value == 0 && off == end) + break; + + array[length++] = value & 0xff; + array[length++] = value >> 8; + + if (*end == '\0') + break; + + off = end + 1; + } + + return psr_put(pskey, array, length); +} + +int psr_read(const char *filename) +{ + struct stat st; + char *str, *map, *off, *end; + int fd, err = 0; + + fd = open(filename, O_RDONLY); + if (fd < 0) + return fd; + + if (fstat(fd, &st) < 0) { + err = -errno; + goto close; + } + + map = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (!map || map == MAP_FAILED) { + err = -errno; + goto close; + } + + off = map; + + while (1) { + if (*off == '\r' || *off == '\n') { + off++; + continue; + } + + end = strpbrk(off, "\r\n"); + if (!end) + break; + + str = malloc(end - off + 1); + if (!str) + break; + + memset(str, 0, end - off + 1); + strncpy(str, off, end - off); + if (*str == '&') + parse_line(str); + + free(str); + off = end + 1; + } + + munmap(map, st.st_size); + +close: + close(fd); + + return err; +} + +int psr_print(void) +{ + uint8_t array[256]; + uint16_t pskey, length; + char *str, val[7]; + int i; + + while (1) { + if (psr_get(&pskey, array, &length) < 0) + break; + + str = csr_pskeytoval(pskey); + if (!strcasecmp(str, "UNKNOWN")) { + sprintf(val, "0x%04x", pskey); + str = NULL; + } + + printf("// %s%s\n&%04x =", str ? "PSKEY_" : "", + str ? str : val, pskey); + for (i = 0; i < length / 2; i++) + printf(" %02x%02x", array[i * 2 + 1], array[i * 2]); + printf("\n"); + } + + return 0; +} diff --git a/tools/csr.h b/tools/csr.h new file mode 100644 index 0000000..cc245a5 --- /dev/null +++ b/tools/csr.h @@ -0,0 +1,555 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#define CSR_VARID_PS_CLR_ALL 0x000b /* valueless */ +#define CSR_VARID_PS_FACTORY_SET 0x000c /* valueless */ +#define CSR_VARID_PS_CLR_ALL_STORES 0x082d /* uint16 */ +#define CSR_VARID_BC01_STATUS 0x2801 /* uint16 */ +#define CSR_VARID_BUILDID 0x2819 /* uint16 */ +#define CSR_VARID_CHIPVER 0x281a /* uint16 */ +#define CSR_VARID_CHIPREV 0x281b /* uint16 */ +#define CSR_VARID_INTERFACE_VERSION 0x2825 /* uint16 */ +#define CSR_VARID_RAND 0x282a /* uint16 */ +#define CSR_VARID_MAX_CRYPT_KEY_LENGTH 0x282c /* uint16 */ +#define CSR_VARID_CHIPANAREV 0x2836 /* uint16 */ +#define CSR_VARID_BUILDID_LOADER 0x2838 /* uint16 */ +#define CSR_VARID_BT_CLOCK 0x2c00 /* uint32 */ +#define CSR_VARID_PS_NEXT 0x3005 /* complex */ +#define CSR_VARID_PS_SIZE 0x3006 /* complex */ +#define CSR_VARID_ADC_RES 0x3007 /* complex */ +#define CSR_VARID_CRYPT_KEY_LENGTH 0x3008 /* complex */ +#define CSR_VARID_PICONET_INSTANCE 0x3009 /* complex */ +#define CSR_VARID_GET_CLR_EVT 0x300a /* complex */ +#define CSR_VARID_GET_NEXT_BUILDDEF 0x300b /* complex */ +#define CSR_VARID_PS_MEMORY_TYPE 0x3012 /* complex */ +#define CSR_VARID_READ_BUILD_NAME 0x301c /* complex */ +#define CSR_VARID_COLD_RESET 0x4001 /* valueless */ +#define CSR_VARID_WARM_RESET 0x4002 /* valueless */ +#define CSR_VARID_COLD_HALT 0x4003 /* valueless */ +#define CSR_VARID_WARM_HALT 0x4004 /* valueless */ +#define CSR_VARID_INIT_BT_STACK 0x4005 /* valueless */ +#define CSR_VARID_ACTIVATE_BT_STACK 0x4006 /* valueless */ +#define CSR_VARID_ENABLE_TX 0x4007 /* valueless */ +#define CSR_VARID_DISABLE_TX 0x4008 /* valueless */ +#define CSR_VARID_RECAL 0x4009 /* valueless */ +#define CSR_VARID_PS_FACTORY_RESTORE 0x400d /* valueless */ +#define CSR_VARID_PS_FACTORY_RESTORE_ALL 0x400e /* valueless */ +#define CSR_VARID_PS_DEFRAG_RESET 0x400f /* valueless */ +#define CSR_VARID_KILL_VM_APPLICATION 0x4010 /* valueless */ +#define CSR_VARID_HOPPING_ON 0x4011 /* valueless */ +#define CSR_VARID_CANCEL_PAGE 0x4012 /* valueless */ +#define CSR_VARID_PS_CLR 0x4818 /* uint16 */ +#define CSR_VARID_MAP_SCO_PCM 0x481c /* uint16 */ +#define CSR_VARID_ADC 0x4829 /* uint16 */ +#define CSR_VARID_SINGLE_CHAN 0x482e /* uint16 */ +#define CSR_VARID_RADIOTEST 0x5004 /* complex */ +#define CSR_VARID_PS_CLR_STORES 0x500c /* complex */ +#define CSR_VARID_NO_VARIABLE 0x6000 /* valueless */ +#define CSR_VARID_CONFIG_UART 0x6802 /* uint16 */ +#define CSR_VARID_PANIC_ARG 0x6805 /* uint16 */ +#define CSR_VARID_FAULT_ARG 0x6806 /* uint16 */ +#define CSR_VARID_MAX_TX_POWER 0x6827 /* int8 */ +#define CSR_VARID_DEFAULT_TX_POWER 0x682b /* int8 */ +#define CSR_VARID_PS 0x7003 /* complex */ + +#define CSR_PSKEY_BDADDR 0x0001 /* bdaddr / uint16[] = { 0x00A5A5, 0x5b, 0x0002 } */ +#define CSR_PSKEY_COUNTRYCODE 0x0002 /* uint16 */ +#define CSR_PSKEY_CLASSOFDEVICE 0x0003 /* bdcod */ +#define CSR_PSKEY_DEVICE_DRIFT 0x0004 /* uint16 */ +#define CSR_PSKEY_DEVICE_JITTER 0x0005 /* uint16 */ +#define CSR_PSKEY_MAX_ACLS 0x000d /* uint16 */ +#define CSR_PSKEY_MAX_SCOS 0x000e /* uint16 */ +#define CSR_PSKEY_MAX_REMOTE_MASTERS 0x000f /* uint16 */ +#define CSR_PSKEY_ENABLE_MASTERY_WITH_SLAVERY 0x0010 /* bool */ +#define CSR_PSKEY_H_HC_FC_MAX_ACL_PKT_LEN 0x0011 /* uint16 */ +#define CSR_PSKEY_H_HC_FC_MAX_SCO_PKT_LEN 0x0012 /* uint8 */ +#define CSR_PSKEY_H_HC_FC_MAX_ACL_PKTS 0x0013 /* uint16 */ +#define CSR_PSKEY_H_HC_FC_MAX_SCO_PKTS 0x0014 /* uint16 */ +#define CSR_PSKEY_LC_FC_BUFFER_LOW_WATER_MARK 0x0015 /* lc_fc_lwm */ +#define CSR_PSKEY_LC_MAX_TX_POWER 0x0017 /* int16 */ +#define CSR_PSKEY_TX_GAIN_RAMP 0x001d /* uint16 */ +#define CSR_PSKEY_LC_POWER_TABLE 0x001e /* power_setting[] */ +#define CSR_PSKEY_LC_PEER_POWER_PERIOD 0x001f /* TIME */ +#define CSR_PSKEY_LC_FC_POOLS_LOW_WATER_MARK 0x0020 /* lc_fc_lwm */ +#define CSR_PSKEY_LC_DEFAULT_TX_POWER 0x0021 /* int16 */ +#define CSR_PSKEY_LC_RSSI_GOLDEN_RANGE 0x0022 /* uint8 */ +#define CSR_PSKEY_LC_COMBO_DISABLE_PIO_MASK 0x0028 /* uint16[] */ +#define CSR_PSKEY_LC_COMBO_PRIORITY_PIO_MASK 0x0029 /* uint16[] */ +#define CSR_PSKEY_LC_COMBO_DOT11_CHANNEL_PIO_BASE 0x002a /* uint16 */ +#define CSR_PSKEY_LC_COMBO_DOT11_BLOCK_CHANNELS 0x002b /* uint16 */ +#define CSR_PSKEY_LC_MAX_TX_POWER_NO_RSSI 0x002d /* int8 */ +#define CSR_PSKEY_LC_CONNECTION_RX_WINDOW 0x002e /* uint16 */ +#define CSR_PSKEY_LC_COMBO_DOT11_TX_PROTECTION_MODE 0x0030 /* uint16 */ +#define CSR_PSKEY_LC_ENHANCED_POWER_TABLE 0x0031 /* enhanced_power_setting[] */ +#define CSR_PSKEY_LC_WIDEBAND_RSSI_CONFIG 0x0032 /* wideband_rssi_config */ +#define CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_LEAD 0x0033 /* uint16 */ +#define CSR_PSKEY_BT_CLOCK_INIT 0x0034 /* uint32 */ +#define CSR_PSKEY_TX_MR_MOD_DELAY 0x0038 /* uint8 */ +#define CSR_PSKEY_RX_MR_SYNC_TIMING 0x0039 /* uint16 */ +#define CSR_PSKEY_RX_MR_SYNC_CONFIG 0x003a /* uint16 */ +#define CSR_PSKEY_LC_LOST_SYNC_SLOTS 0x003b /* uint16 */ +#define CSR_PSKEY_RX_MR_SAMP_CONFIG 0x003c /* uint16 */ +#define CSR_PSKEY_AGC_HYST_LEVELS 0x003d /* agc_hyst_config */ +#define CSR_PSKEY_RX_LEVEL_LOW_SIGNAL 0x003e /* uint16 */ +#define CSR_PSKEY_AGC_IQ_LVL_VALUES 0x003f /* IQ_LVL_VAL[] */ +#define CSR_PSKEY_MR_FTRIM_OFFSET_12DB 0x0040 /* uint16 */ +#define CSR_PSKEY_MR_FTRIM_OFFSET_6DB 0x0041 /* uint16 */ +#define CSR_PSKEY_NO_CAL_ON_BOOT 0x0042 /* bool */ +#define CSR_PSKEY_RSSI_HI_TARGET 0x0043 /* uint8 */ +#define CSR_PSKEY_PREFERRED_MIN_ATTENUATION 0x0044 /* uint8 */ +#define CSR_PSKEY_LC_COMBO_DOT11_PRIORITY_OVERRIDE 0x0045 /* bool */ +#define CSR_PSKEY_LC_MULTISLOT_HOLDOFF 0x0047 /* TIME */ +#define CSR_PSKEY_FREE_KEY_PIGEON_HOLE 0x00c9 /* uint16 */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR0 0x00ca /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR1 0x00cb /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR2 0x00cc /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR3 0x00cd /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR4 0x00ce /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR5 0x00cf /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR6 0x00d0 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR7 0x00d1 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR8 0x00d2 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR9 0x00d3 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR10 0x00d4 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR11 0x00d5 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR12 0x00d6 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR13 0x00d7 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR14 0x00d8 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LINK_KEY_BD_ADDR15 0x00d9 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_ENC_KEY_LMIN 0x00da /* uint16 */ +#define CSR_PSKEY_ENC_KEY_LMAX 0x00db /* uint16 */ +#define CSR_PSKEY_LOCAL_SUPPORTED_FEATURES 0x00ef /* uint16[] = { 0xffff, 0xfe8f, 0xf99b, 0x8000 }*/ +#define CSR_PSKEY_LM_USE_UNIT_KEY 0x00f0 /* bool */ +#define CSR_PSKEY_HCI_NOP_DISABLE 0x00f2 /* bool */ +#define CSR_PSKEY_LM_MAX_EVENT_FILTERS 0x00f4 /* uint8 */ +#define CSR_PSKEY_LM_USE_ENC_MODE_BROADCAST 0x00f5 /* bool */ +#define CSR_PSKEY_LM_TEST_SEND_ACCEPTED_TWICE 0x00f6 /* bool */ +#define CSR_PSKEY_LM_MAX_PAGE_HOLD_TIME 0x00f7 /* uint16 */ +#define CSR_PSKEY_AFH_ADAPTATION_RESPONSE_TIME 0x00f8 /* uint16 */ +#define CSR_PSKEY_AFH_OPTIONS 0x00f9 /* uint16 */ +#define CSR_PSKEY_AFH_RSSI_RUN_PERIOD 0x00fa /* uint16 */ +#define CSR_PSKEY_AFH_REENABLE_CHANNEL_TIME 0x00fb /* uint16 */ +#define CSR_PSKEY_NO_DROP_ON_ACR_MS_FAIL 0x00fc /* bool */ +#define CSR_PSKEY_MAX_PRIVATE_KEYS 0x00fd /* uint8 */ +#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR0 0x00fe /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR1 0x00ff /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR2 0x0100 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR3 0x0101 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR4 0x0102 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR5 0x0103 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR6 0x0104 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_PRIVATE_LINK_KEY_BD_ADDR7 0x0105 /* LM_LINK_KEY_BD_ADDR_T */ +#define CSR_PSKEY_LOCAL_SUPPORTED_COMMANDS 0x0106 /* uint16[] = { 0xffff, 0x03ff, 0xfffe, 0xffff, 0xffff, 0xffff, 0x0ff3, 0xfff8, 0x003f } */ +#define CSR_PSKEY_LM_MAX_ABSENCE_INDEX 0x0107 /* uint8 */ +#define CSR_PSKEY_DEVICE_NAME 0x0108 /* uint16[] */ +#define CSR_PSKEY_AFH_RSSI_THRESHOLD 0x0109 /* uint16 */ +#define CSR_PSKEY_LM_CASUAL_SCAN_INTERVAL 0x010a /* uint16 */ +#define CSR_PSKEY_AFH_MIN_MAP_CHANGE 0x010b /* uint16[] */ +#define CSR_PSKEY_AFH_RSSI_LP_RUN_PERIOD 0x010c /* uint16 */ +#define CSR_PSKEY_HCI_LMP_LOCAL_VERSION 0x010d /* uint16 */ +#define CSR_PSKEY_LMP_REMOTE_VERSION 0x010e /* uint8 */ +#define CSR_PSKEY_HOLD_ERROR_MESSAGE_NUMBER 0x0113 /* uint16 */ +#define CSR_PSKEY_DFU_ATTRIBUTES 0x0136 /* uint8 */ +#define CSR_PSKEY_DFU_DETACH_TO 0x0137 /* uint16 */ +#define CSR_PSKEY_DFU_TRANSFER_SIZE 0x0138 /* uint16 */ +#define CSR_PSKEY_DFU_ENABLE 0x0139 /* bool */ +#define CSR_PSKEY_DFU_LIN_REG_ENABLE 0x013a /* bool */ +#define CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_MSB 0x015e /* uint16[] */ +#define CSR_PSKEY_DFUENC_VMAPP_PK_MODULUS_LSB 0x015f /* uint16[] */ +#define CSR_PSKEY_DFUENC_VMAPP_PK_M_DASH 0x0160 /* uint16 */ +#define CSR_PSKEY_DFUENC_VMAPP_PK_R2N_MSB 0x0161 /* uint16[] */ +#define CSR_PSKEY_DFUENC_VMAPP_PK_R2N_LSB 0x0162 /* uint16[] */ +#define CSR_PSKEY_BCSP_LM_PS_BLOCK 0x0192 /* BCSP_LM_PS_BLOCK */ +#define CSR_PSKEY_HOSTIO_FC_PS_BLOCK 0x0193 /* HOSTIO_FC_PS_BLOCK */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO0 0x0194 /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO1 0x0195 /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO2 0x0196 /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO3 0x0197 /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO4 0x0198 /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO5 0x0199 /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO6 0x019a /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO7 0x019b /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO8 0x019c /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO9 0x019d /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO10 0x019e /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO11 0x019f /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO12 0x01a0 /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO13 0x01a1 /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO14 0x01a2 /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_PROTOCOL_INFO15 0x01a3 /* PROTOCOL_INFO */ +#define CSR_PSKEY_HOSTIO_UART_RESET_TIMEOUT 0x01a4 /* TIME */ +#define CSR_PSKEY_HOSTIO_USE_HCI_EXTN 0x01a5 /* bool */ +#define CSR_PSKEY_HOSTIO_USE_HCI_EXTN_CCFC 0x01a6 /* bool */ +#define CSR_PSKEY_HOSTIO_HCI_EXTN_PAYLOAD_SIZE 0x01a7 /* uint16 */ +#define CSR_PSKEY_BCSP_LM_CNF_CNT_LIMIT 0x01aa /* uint16 */ +#define CSR_PSKEY_HOSTIO_MAP_SCO_PCM 0x01ab /* bool */ +#define CSR_PSKEY_HOSTIO_AWKWARD_PCM_SYNC 0x01ac /* bool */ +#define CSR_PSKEY_HOSTIO_BREAK_POLL_PERIOD 0x01ad /* TIME */ +#define CSR_PSKEY_HOSTIO_MIN_UART_HCI_SCO_SIZE 0x01ae /* uint16 */ +#define CSR_PSKEY_HOSTIO_MAP_SCO_CODEC 0x01b0 /* bool */ +#define CSR_PSKEY_PCM_CVSD_TX_HI_FREQ_BOOST 0x01b1 /* uint16 */ +#define CSR_PSKEY_PCM_CVSD_RX_HI_FREQ_BOOST 0x01b2 /* uint16 */ +#define CSR_PSKEY_PCM_CONFIG32 0x01b3 /* uint32 */ +#define CSR_PSKEY_USE_OLD_BCSP_LE 0x01b4 /* uint16 */ +#define CSR_PSKEY_PCM_CVSD_USE_NEW_FILTER 0x01b5 /* bool */ +#define CSR_PSKEY_PCM_FORMAT 0x01b6 /* uint16 */ +#define CSR_PSKEY_CODEC_OUT_GAIN 0x01b7 /* uint16 */ +#define CSR_PSKEY_CODEC_IN_GAIN 0x01b8 /* uint16 */ +#define CSR_PSKEY_CODEC_PIO 0x01b9 /* uint16 */ +#define CSR_PSKEY_PCM_LOW_JITTER_CONFIG 0x01ba /* uint32 */ +#define CSR_PSKEY_HOSTIO_SCO_PCM_THRESHOLDS 0x01bb /* uint16[] */ +#define CSR_PSKEY_HOSTIO_SCO_HCI_THRESHOLDS 0x01bc /* uint16[] */ +#define CSR_PSKEY_HOSTIO_MAP_SCO_PCM_SLOT 0x01bd /* uint16 */ +#define CSR_PSKEY_UART_BAUDRATE 0x01be /* uint16 */ +#define CSR_PSKEY_UART_CONFIG_BCSP 0x01bf /* uint16 */ +#define CSR_PSKEY_UART_CONFIG_H4 0x01c0 /* uint16 */ +#define CSR_PSKEY_UART_CONFIG_H5 0x01c1 /* uint16 */ +#define CSR_PSKEY_UART_CONFIG_USR 0x01c2 /* uint16 */ +#define CSR_PSKEY_UART_TX_CRCS 0x01c3 /* bool */ +#define CSR_PSKEY_UART_ACK_TIMEOUT 0x01c4 /* uint16 */ +#define CSR_PSKEY_UART_TX_MAX_ATTEMPTS 0x01c5 /* uint16 */ +#define CSR_PSKEY_UART_TX_WINDOW_SIZE 0x01c6 /* uint16 */ +#define CSR_PSKEY_UART_HOST_WAKE 0x01c7 /* uint16[] */ +#define CSR_PSKEY_HOSTIO_THROTTLE_TIMEOUT 0x01c8 /* TIME */ +#define CSR_PSKEY_PCM_ALWAYS_ENABLE 0x01c9 /* bool */ +#define CSR_PSKEY_UART_HOST_WAKE_SIGNAL 0x01ca /* uint16 */ +#define CSR_PSKEY_UART_CONFIG_H4DS 0x01cb /* uint16 */ +#define CSR_PSKEY_H4DS_WAKE_DURATION 0x01cc /* uint16 */ +#define CSR_PSKEY_H4DS_MAXWU 0x01cd /* uint16 */ +#define CSR_PSKEY_H4DS_LE_TIMER_PERIOD 0x01cf /* uint16 */ +#define CSR_PSKEY_H4DS_TWU_TIMER_PERIOD 0x01d0 /* uint16 */ +#define CSR_PSKEY_H4DS_UART_IDLE_TIMER_PERIOD 0x01d1 /* uint16 */ +#define CSR_PSKEY_ANA_FTRIM 0x01f6 /* uint16 */ +#define CSR_PSKEY_WD_TIMEOUT 0x01f7 /* TIME */ +#define CSR_PSKEY_WD_PERIOD 0x01f8 /* TIME */ +#define CSR_PSKEY_HOST_INTERFACE 0x01f9 /* phys_bus */ +#define CSR_PSKEY_HQ_HOST_TIMEOUT 0x01fb /* TIME */ +#define CSR_PSKEY_HQ_ACTIVE 0x01fc /* bool */ +#define CSR_PSKEY_BCCMD_SECURITY_ACTIVE 0x01fd /* bool */ +#define CSR_PSKEY_ANA_FREQ 0x01fe /* uint16 */ +#define CSR_PSKEY_PIO_PROTECT_MASK 0x0202 /* uint16 */ +#define CSR_PSKEY_PMALLOC_SIZES 0x0203 /* uint16[] */ +#define CSR_PSKEY_UART_BAUD_RATE 0x0204 /* uint16 */ +#define CSR_PSKEY_UART_CONFIG 0x0205 /* uint16 */ +#define CSR_PSKEY_STUB 0x0207 /* uint16 */ +#define CSR_PSKEY_TXRX_PIO_CONTROL 0x0209 /* uint16 */ +#define CSR_PSKEY_ANA_RX_LEVEL 0x020b /* uint16 */ +#define CSR_PSKEY_ANA_RX_FTRIM 0x020c /* uint16 */ +#define CSR_PSKEY_PSBC_DATA_VERSION 0x020d /* uint16 */ +#define CSR_PSKEY_PCM0_ATTENUATION 0x020f /* uint16 */ +#define CSR_PSKEY_LO_LVL_MAX 0x0211 /* uint16 */ +#define CSR_PSKEY_LO_ADC_AMPL_MIN 0x0212 /* uint16 */ +#define CSR_PSKEY_LO_ADC_AMPL_MAX 0x0213 /* uint16 */ +#define CSR_PSKEY_IQ_TRIM_CHANNEL 0x0214 /* uint16 */ +#define CSR_PSKEY_IQ_TRIM_GAIN 0x0215 /* uint16 */ +#define CSR_PSKEY_IQ_TRIM_ENABLE 0x0216 /* iq_trim_enable_flag */ +#define CSR_PSKEY_TX_OFFSET_HALF_MHZ 0x0217 /* int16 */ +#define CSR_PSKEY_GBL_MISC_ENABLES 0x0221 /* uint16 */ +#define CSR_PSKEY_UART_SLEEP_TIMEOUT 0x0222 /* uint16 */ +#define CSR_PSKEY_DEEP_SLEEP_STATE 0x0229 /* deep_sleep_state */ +#define CSR_PSKEY_IQ_ENABLE_PHASE_TRIM 0x022d /* bool */ +#define CSR_PSKEY_HCI_HANDLE_FREEZE_PERIOD 0x0237 /* TIME */ +#define CSR_PSKEY_MAX_FROZEN_HCI_HANDLES 0x0238 /* uint16 */ +#define CSR_PSKEY_PAGETABLE_DESTRUCTION_DELAY 0x0239 /* TIME */ +#define CSR_PSKEY_IQ_TRIM_PIO_SETTINGS 0x023a /* uint8 */ +#define CSR_PSKEY_USE_EXTERNAL_CLOCK 0x023b /* bool */ +#define CSR_PSKEY_DEEP_SLEEP_WAKE_CTS 0x023c /* uint16 */ +#define CSR_PSKEY_FC_HC2H_FLUSH_DELAY 0x023d /* TIME */ +#define CSR_PSKEY_RX_HIGHSIDE 0x023e /* bool */ +#define CSR_PSKEY_TX_PRE_LVL 0x0240 /* uint8 */ +#define CSR_PSKEY_RX_SINGLE_ENDED 0x0242 /* bool */ +#define CSR_PSKEY_TX_FILTER_CONFIG 0x0243 /* uint32 */ +#define CSR_PSKEY_CLOCK_REQUEST_ENABLE 0x0246 /* uint16 */ +#define CSR_PSKEY_RX_MIN_ATTEN 0x0249 /* uint16 */ +#define CSR_PSKEY_XTAL_TARGET_AMPLITUDE 0x024b /* uint8 */ +#define CSR_PSKEY_PCM_MIN_CPU_CLOCK 0x024d /* uint16 */ +#define CSR_PSKEY_HOST_INTERFACE_PIO_USB 0x0250 /* uint16 */ +#define CSR_PSKEY_CPU_IDLE_MODE 0x0251 /* cpu_idle_mode */ +#define CSR_PSKEY_DEEP_SLEEP_CLEAR_RTS 0x0252 /* bool */ +#define CSR_PSKEY_RF_RESONANCE_TRIM 0x0254 /* uint16 */ +#define CSR_PSKEY_DEEP_SLEEP_PIO_WAKE 0x0255 /* uint16 */ +#define CSR_PSKEY_DRAIN_BORE_TIMERS 0x0256 /* uint32[] */ +#define CSR_PSKEY_DRAIN_TX_POWER_BASE 0x0257 /* uint16 */ +#define CSR_PSKEY_MODULE_ID 0x0259 /* uint32 */ +#define CSR_PSKEY_MODULE_DESIGN 0x025a /* uint16 */ +#define CSR_PSKEY_MODULE_SECURITY_CODE 0x025c /* uint16[] */ +#define CSR_PSKEY_VM_DISABLE 0x025d /* bool */ +#define CSR_PSKEY_MOD_MANUF0 0x025e /* uint16[] */ +#define CSR_PSKEY_MOD_MANUF1 0x025f /* uint16[] */ +#define CSR_PSKEY_MOD_MANUF2 0x0260 /* uint16[] */ +#define CSR_PSKEY_MOD_MANUF3 0x0261 /* uint16[] */ +#define CSR_PSKEY_MOD_MANUF4 0x0262 /* uint16[] */ +#define CSR_PSKEY_MOD_MANUF5 0x0263 /* uint16[] */ +#define CSR_PSKEY_MOD_MANUF6 0x0264 /* uint16[] */ +#define CSR_PSKEY_MOD_MANUF7 0x0265 /* uint16[] */ +#define CSR_PSKEY_MOD_MANUF8 0x0266 /* uint16[] */ +#define CSR_PSKEY_MOD_MANUF9 0x0267 /* uint16[] */ +#define CSR_PSKEY_DUT_VM_DISABLE 0x0268 /* bool */ +#define CSR_PSKEY_USR0 0x028a /* uint16[] */ +#define CSR_PSKEY_USR1 0x028b /* uint16[] */ +#define CSR_PSKEY_USR2 0x028c /* uint16[] */ +#define CSR_PSKEY_USR3 0x028d /* uint16[] */ +#define CSR_PSKEY_USR4 0x028e /* uint16[] */ +#define CSR_PSKEY_USR5 0x028f /* uint16[] */ +#define CSR_PSKEY_USR6 0x0290 /* uint16[] */ +#define CSR_PSKEY_USR7 0x0291 /* uint16[] */ +#define CSR_PSKEY_USR8 0x0292 /* uint16[] */ +#define CSR_PSKEY_USR9 0x0293 /* uint16[] */ +#define CSR_PSKEY_USR10 0x0294 /* uint16[] */ +#define CSR_PSKEY_USR11 0x0295 /* uint16[] */ +#define CSR_PSKEY_USR12 0x0296 /* uint16[] */ +#define CSR_PSKEY_USR13 0x0297 /* uint16[] */ +#define CSR_PSKEY_USR14 0x0298 /* uint16[] */ +#define CSR_PSKEY_USR15 0x0299 /* uint16[] */ +#define CSR_PSKEY_USR16 0x029a /* uint16[] */ +#define CSR_PSKEY_USR17 0x029b /* uint16[] */ +#define CSR_PSKEY_USR18 0x029c /* uint16[] */ +#define CSR_PSKEY_USR19 0x029d /* uint16[] */ +#define CSR_PSKEY_USR20 0x029e /* uint16[] */ +#define CSR_PSKEY_USR21 0x029f /* uint16[] */ +#define CSR_PSKEY_USR22 0x02a0 /* uint16[] */ +#define CSR_PSKEY_USR23 0x02a1 /* uint16[] */ +#define CSR_PSKEY_USR24 0x02a2 /* uint16[] */ +#define CSR_PSKEY_USR25 0x02a3 /* uint16[] */ +#define CSR_PSKEY_USR26 0x02a4 /* uint16[] */ +#define CSR_PSKEY_USR27 0x02a5 /* uint16[] */ +#define CSR_PSKEY_USR28 0x02a6 /* uint16[] */ +#define CSR_PSKEY_USR29 0x02a7 /* uint16[] */ +#define CSR_PSKEY_USR30 0x02a8 /* uint16[] */ +#define CSR_PSKEY_USR31 0x02a9 /* uint16[] */ +#define CSR_PSKEY_USR32 0x02aa /* uint16[] */ +#define CSR_PSKEY_USR33 0x02ab /* uint16[] */ +#define CSR_PSKEY_USR34 0x02ac /* uint16[] */ +#define CSR_PSKEY_USR35 0x02ad /* uint16[] */ +#define CSR_PSKEY_USR36 0x02ae /* uint16[] */ +#define CSR_PSKEY_USR37 0x02af /* uint16[] */ +#define CSR_PSKEY_USR38 0x02b0 /* uint16[] */ +#define CSR_PSKEY_USR39 0x02b1 /* uint16[] */ +#define CSR_PSKEY_USR40 0x02b2 /* uint16[] */ +#define CSR_PSKEY_USR41 0x02b3 /* uint16[] */ +#define CSR_PSKEY_USR42 0x02b4 /* uint16[] */ +#define CSR_PSKEY_USR43 0x02b5 /* uint16[] */ +#define CSR_PSKEY_USR44 0x02b6 /* uint16[] */ +#define CSR_PSKEY_USR45 0x02b7 /* uint16[] */ +#define CSR_PSKEY_USR46 0x02b8 /* uint16[] */ +#define CSR_PSKEY_USR47 0x02b9 /* uint16[] */ +#define CSR_PSKEY_USR48 0x02ba /* uint16[] */ +#define CSR_PSKEY_USR49 0x02bb /* uint16[] */ +#define CSR_PSKEY_USB_VERSION 0x02bc /* uint16 */ +#define CSR_PSKEY_USB_DEVICE_CLASS_CODES 0x02bd /* usbclass */ +#define CSR_PSKEY_USB_VENDOR_ID 0x02be /* uint16 */ +#define CSR_PSKEY_USB_PRODUCT_ID 0x02bf /* uint16 */ +#define CSR_PSKEY_USB_MANUF_STRING 0x02c1 /* unicodestring */ +#define CSR_PSKEY_USB_PRODUCT_STRING 0x02c2 /* unicodestring */ +#define CSR_PSKEY_USB_SERIAL_NUMBER_STRING 0x02c3 /* unicodestring */ +#define CSR_PSKEY_USB_CONFIG_STRING 0x02c4 /* unicodestring */ +#define CSR_PSKEY_USB_ATTRIBUTES 0x02c5 /* uint8 */ +#define CSR_PSKEY_USB_MAX_POWER 0x02c6 /* uint16 */ +#define CSR_PSKEY_USB_BT_IF_CLASS_CODES 0x02c7 /* usbclass */ +#define CSR_PSKEY_USB_LANGID 0x02c9 /* uint16 */ +#define CSR_PSKEY_USB_DFU_CLASS_CODES 0x02ca /* usbclass */ +#define CSR_PSKEY_USB_DFU_PRODUCT_ID 0x02cb /* uint16 */ +#define CSR_PSKEY_USB_PIO_DETACH 0x02ce /* uint16 */ +#define CSR_PSKEY_USB_PIO_WAKEUP 0x02cf /* uint16 */ +#define CSR_PSKEY_USB_PIO_PULLUP 0x02d0 /* uint16 */ +#define CSR_PSKEY_USB_PIO_VBUS 0x02d1 /* uint16 */ +#define CSR_PSKEY_USB_PIO_WAKE_TIMEOUT 0x02d2 /* uint16 */ +#define CSR_PSKEY_USB_PIO_RESUME 0x02d3 /* uint16 */ +#define CSR_PSKEY_USB_BT_SCO_IF_CLASS_CODES 0x02d4 /* usbclass */ +#define CSR_PSKEY_USB_SUSPEND_PIO_LEVEL 0x02d5 /* uint16 */ +#define CSR_PSKEY_USB_SUSPEND_PIO_DIR 0x02d6 /* uint16 */ +#define CSR_PSKEY_USB_SUSPEND_PIO_MASK 0x02d7 /* uint16 */ +#define CSR_PSKEY_USB_ENDPOINT_0_MAX_PACKET_SIZE 0x02d8 /* uint8 */ +#define CSR_PSKEY_USB_CONFIG 0x02d9 /* uint16 */ +#define CSR_PSKEY_RADIOTEST_ATTEN_INIT 0x0320 /* uint16 */ +#define CSR_PSKEY_RADIOTEST_FIRST_TRIM_TIME 0x0326 /* TIME */ +#define CSR_PSKEY_RADIOTEST_SUBSEQUENT_TRIM_TIME 0x0327 /* TIME */ +#define CSR_PSKEY_RADIOTEST_LO_LVL_TRIM_ENABLE 0x0328 /* bool */ +#define CSR_PSKEY_RADIOTEST_DISABLE_MODULATION 0x032c /* bool */ +#define CSR_PSKEY_RFCOMM_FCON_THRESHOLD 0x0352 /* uint16 */ +#define CSR_PSKEY_RFCOMM_FCOFF_THRESHOLD 0x0353 /* uint16 */ +#define CSR_PSKEY_IPV6_STATIC_ADDR 0x0354 /* uint16[] */ +#define CSR_PSKEY_IPV4_STATIC_ADDR 0x0355 /* uint32 */ +#define CSR_PSKEY_IPV6_STATIC_PREFIX_LEN 0x0356 /* uint8 */ +#define CSR_PSKEY_IPV6_STATIC_ROUTER_ADDR 0x0357 /* uint16[] */ +#define CSR_PSKEY_IPV4_STATIC_SUBNET_MASK 0x0358 /* uint32 */ +#define CSR_PSKEY_IPV4_STATIC_ROUTER_ADDR 0x0359 /* uint32 */ +#define CSR_PSKEY_MDNS_NAME 0x035a /* char[] */ +#define CSR_PSKEY_FIXED_PIN 0x035b /* uint8[] */ +#define CSR_PSKEY_MDNS_PORT 0x035c /* uint16 */ +#define CSR_PSKEY_MDNS_TTL 0x035d /* uint8 */ +#define CSR_PSKEY_MDNS_IPV4_ADDR 0x035e /* uint32 */ +#define CSR_PSKEY_ARP_CACHE_TIMEOUT 0x035f /* uint16 */ +#define CSR_PSKEY_HFP_POWER_TABLE 0x0360 /* uint16[] */ +#define CSR_PSKEY_DRAIN_BORE_TIMER_COUNTERS 0x03e7 /* uint32[] */ +#define CSR_PSKEY_DRAIN_BORE_COUNTERS 0x03e6 /* uint32[] */ +#define CSR_PSKEY_LOOP_FILTER_TRIM 0x03e4 /* uint16 */ +#define CSR_PSKEY_DRAIN_BORE_CURRENT_PEAK 0x03e3 /* uint32[] */ +#define CSR_PSKEY_VM_E2_CACHE_LIMIT 0x03e2 /* uint16 */ +#define CSR_PSKEY_FORCE_16MHZ_REF_PIO 0x03e1 /* uint16 */ +#define CSR_PSKEY_CDMA_LO_REF_LIMITS 0x03df /* uint16 */ +#define CSR_PSKEY_CDMA_LO_ERROR_LIMITS 0x03de /* uint16 */ +#define CSR_PSKEY_CLOCK_STARTUP_DELAY 0x03dd /* uint16 */ +#define CSR_PSKEY_DEEP_SLEEP_CORRECTION_FACTOR 0x03dc /* int16 */ +#define CSR_PSKEY_TEMPERATURE_CALIBRATION 0x03db /* temperature_calibration */ +#define CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA 0x03da /* temperature_calibration[] */ +#define CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL 0x03d9 /* temperature_calibration[] */ +#define CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB 0x03d8 /* temperature_calibration[] */ +#define CSR_PSKEY_TEMPERATURE_VS_DELTA_ANA_FTRIM 0x03d7 /* temperature_calibration[] */ +#define CSR_PSKEY_TEST_DELTA_OFFSET 0x03d6 /* uint16 */ +#define CSR_PSKEY_RX_DYNAMIC_LVL_OFFSET 0x03d4 /* uint16 */ +#define CSR_PSKEY_TEST_FORCE_OFFSET 0x03d3 /* bool */ +#define CSR_PSKEY_RF_TRAP_BAD_DIVISION_RATIOS 0x03cf /* uint16 */ +#define CSR_PSKEY_RADIOTEST_CDMA_LO_REF_LIMITS 0x03ce /* uint16 */ +#define CSR_PSKEY_INITIAL_BOOTMODE 0x03cd /* int16 */ +#define CSR_PSKEY_ONCHIP_HCI_CLIENT 0x03cc /* bool */ +#define CSR_PSKEY_RX_ATTEN_BACKOFF 0x03ca /* uint16 */ +#define CSR_PSKEY_RX_ATTEN_UPDATE_RATE 0x03c9 /* uint16 */ +#define CSR_PSKEY_SYNTH_TXRX_THRESHOLDS 0x03c7 /* uint16 */ +#define CSR_PSKEY_MIN_WAIT_STATES 0x03c6 /* uint16 */ +#define CSR_PSKEY_RSSI_CORRECTION 0x03c5 /* int8 */ +#define CSR_PSKEY_SCHED_THROTTLE_TIMEOUT 0x03c4 /* TIME */ +#define CSR_PSKEY_DEEP_SLEEP_USE_EXTERNAL_CLOCK 0x03c3 /* bool */ +#define CSR_PSKEY_TRIM_RADIO_FILTERS 0x03c2 /* uint16 */ +#define CSR_PSKEY_TRANSMIT_OFFSET 0x03c1 /* int16 */ +#define CSR_PSKEY_USB_VM_CONTROL 0x03c0 /* bool */ +#define CSR_PSKEY_MR_ANA_RX_FTRIM 0x03bf /* uint16 */ +#define CSR_PSKEY_I2C_CONFIG 0x03be /* uint16 */ +#define CSR_PSKEY_IQ_LVL_RX 0x03bd /* uint16 */ +#define CSR_PSKEY_MR_TX_FILTER_CONFIG 0x03bb /* uint32 */ +#define CSR_PSKEY_MR_TX_CONFIG2 0x03ba /* uint16 */ +#define CSR_PSKEY_USB_DONT_RESET_BOOTMODE_ON_HOST_RESET 0x03b9 /* bool */ +#define CSR_PSKEY_LC_USE_THROTTLING 0x03b8 /* bool */ +#define CSR_PSKEY_CHARGER_TRIM 0x03b7 /* uint16 */ +#define CSR_PSKEY_CLOCK_REQUEST_FEATURES 0x03b6 /* uint16 */ +#define CSR_PSKEY_TRANSMIT_OFFSET_CLASS1 0x03b4 /* int16 */ +#define CSR_PSKEY_TX_AVOID_PA_CLASS1_PIO 0x03b3 /* uint16 */ +#define CSR_PSKEY_MR_PIO_CONFIG 0x03b2 /* uint16 */ +#define CSR_PSKEY_UART_CONFIG2 0x03b1 /* uint8 */ +#define CSR_PSKEY_CLASS1_IQ_LVL 0x03b0 /* uint16 */ +#define CSR_PSKEY_CLASS1_TX_CONFIG2 0x03af /* uint16 */ +#define CSR_PSKEY_TEMPERATURE_VS_DELTA_INTERNAL_PA_CLASS1 0x03ae /* temperature_calibration[] */ +#define CSR_PSKEY_TEMPERATURE_VS_DELTA_EXTERNAL_PA_CLASS1 0x03ad /* temperature_calibration[] */ +#define CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_PRE_LVL_MR 0x03ac /* temperature_calibration[] */ +#define CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_HEADER 0x03ab /* temperature_calibration[] */ +#define CSR_PSKEY_TEMPERATURE_VS_DELTA_TX_BB_MR_PAYLOAD 0x03aa /* temperature_calibration[] */ +#define CSR_PSKEY_RX_MR_EQ_TAPS 0x03a9 /* uint16[] */ +#define CSR_PSKEY_TX_PRE_LVL_CLASS1 0x03a8 /* uint8 */ +#define CSR_PSKEY_ANALOGUE_ATTENUATOR 0x03a7 /* bool */ +#define CSR_PSKEY_MR_RX_FILTER_TRIM 0x03a6 /* uint16 */ +#define CSR_PSKEY_MR_RX_FILTER_RESPONSE 0x03a5 /* int16[] */ +#define CSR_PSKEY_PIO_WAKEUP_STATE 0x039f /* uint16 */ +#define CSR_PSKEY_MR_TX_IF_ATTEN_OFF_TEMP 0x0394 /* int16 */ +#define CSR_PSKEY_LO_DIV_LATCH_BYPASS 0x0393 /* bool */ +#define CSR_PSKEY_LO_VCO_STANDBY 0x0392 /* bool */ +#define CSR_PSKEY_SLOW_CLOCK_FILTER_SHIFT 0x0391 /* uint16 */ +#define CSR_PSKEY_SLOW_CLOCK_FILTER_DIVIDER 0x0390 /* uint16 */ +#define CSR_PSKEY_USB_ATTRIBUTES_POWER 0x03f2 /* bool */ +#define CSR_PSKEY_USB_ATTRIBUTES_WAKEUP 0x03f3 /* bool */ +#define CSR_PSKEY_DFU_ATTRIBUTES_MANIFESTATION_TOLERANT 0x03f4 /* bool */ +#define CSR_PSKEY_DFU_ATTRIBUTES_CAN_UPLOAD 0x03f5 /* bool */ +#define CSR_PSKEY_DFU_ATTRIBUTES_CAN_DOWNLOAD 0x03f6 /* bool */ +#define CSR_PSKEY_UART_CONFIG_STOP_BITS 0x03fc /* bool */ +#define CSR_PSKEY_UART_CONFIG_PARITY_BIT 0x03fd /* uint16 */ +#define CSR_PSKEY_UART_CONFIG_FLOW_CTRL_EN 0x03fe /* bool */ +#define CSR_PSKEY_UART_CONFIG_RTS_AUTO_EN 0x03ff /* bool */ +#define CSR_PSKEY_UART_CONFIG_RTS 0x0400 /* bool */ +#define CSR_PSKEY_UART_CONFIG_TX_ZERO_EN 0x0401 /* bool */ +#define CSR_PSKEY_UART_CONFIG_NON_BCSP_EN 0x0402 /* bool */ +#define CSR_PSKEY_UART_CONFIG_RX_RATE_DELAY 0x0403 /* uint16 */ +#define CSR_PSKEY_UART_SEQ_TIMEOUT 0x0405 /* uint16 */ +#define CSR_PSKEY_UART_SEQ_RETRIES 0x0406 /* uint16 */ +#define CSR_PSKEY_UART_SEQ_WINSIZE 0x0407 /* uint16 */ +#define CSR_PSKEY_UART_USE_CRC_ON_TX 0x0408 /* bool */ +#define CSR_PSKEY_UART_HOST_INITIAL_STATE 0x0409 /* hwakeup_state */ +#define CSR_PSKEY_UART_HOST_ATTENTION_SPAN 0x040a /* uint16 */ +#define CSR_PSKEY_UART_HOST_WAKEUP_TIME 0x040b /* uint16 */ +#define CSR_PSKEY_UART_HOST_WAKEUP_WAIT 0x040c /* uint16 */ +#define CSR_PSKEY_BCSP_LM_MODE 0x0410 /* uint16 */ +#define CSR_PSKEY_BCSP_LM_SYNC_RETRIES 0x0411 /* uint16 */ +#define CSR_PSKEY_BCSP_LM_TSHY 0x0412 /* uint16 */ +#define CSR_PSKEY_UART_DFU_CONFIG_STOP_BITS 0x0417 /* bool */ +#define CSR_PSKEY_UART_DFU_CONFIG_PARITY_BIT 0x0418 /* uint16 */ +#define CSR_PSKEY_UART_DFU_CONFIG_FLOW_CTRL_EN 0x0419 /* bool */ +#define CSR_PSKEY_UART_DFU_CONFIG_RTS_AUTO_EN 0x041a /* bool */ +#define CSR_PSKEY_UART_DFU_CONFIG_RTS 0x041b /* bool */ +#define CSR_PSKEY_UART_DFU_CONFIG_TX_ZERO_EN 0x041c /* bool */ +#define CSR_PSKEY_UART_DFU_CONFIG_NON_BCSP_EN 0x041d /* bool */ +#define CSR_PSKEY_UART_DFU_CONFIG_RX_RATE_DELAY 0x041e /* uint16 */ +#define CSR_PSKEY_AMUX_AIO0 0x041f /* ana_amux_sel */ +#define CSR_PSKEY_AMUX_AIO1 0x0420 /* ana_amux_sel */ +#define CSR_PSKEY_AMUX_AIO2 0x0421 /* ana_amux_sel */ +#define CSR_PSKEY_AMUX_AIO3 0x0422 /* ana_amux_sel */ +#define CSR_PSKEY_LOCAL_NAME_SIMPLIFIED 0x0423 /* local_name_complete */ +#define CSR_PSKEY_EXTENDED_STUB 0x2001 /* uint16 */ + +char *csr_builddeftostr(uint16_t def); +char *csr_buildidtostr(uint16_t id); +char *csr_chipvertostr(uint16_t ver, uint16_t rev); +char *csr_pskeytostr(uint16_t pskey); +char *csr_pskeytoval(uint16_t pskey); + +int csr_open_hci(char *device); +int csr_read_hci(uint16_t varid, uint8_t *value, uint16_t length); +int csr_write_hci(uint16_t varid, uint8_t *value, uint16_t length); +void csr_close_hci(void); + +int csr_open_usb(char *device); +int csr_read_usb(uint16_t varid, uint8_t *value, uint16_t length); +int csr_write_usb(uint16_t varid, uint8_t *value, uint16_t length); +void csr_close_usb(void); + +int csr_open_bcsp(char *device, speed_t bcsp_rate); +int csr_read_bcsp(uint16_t varid, uint8_t *value, uint16_t length); +int csr_write_bcsp(uint16_t varid, uint8_t *value, uint16_t length); +void csr_close_bcsp(void); + +int csr_open_h4(char *device); +int csr_read_h4(uint16_t varid, uint8_t *value, uint16_t length); +int csr_write_h4(uint16_t varid, uint8_t *value, uint16_t length); +void csr_close_h4(void); + +int csr_open_3wire(char *device); +int csr_read_3wire(uint16_t varid, uint8_t *value, uint16_t length); +int csr_write_3wire(uint16_t varid, uint8_t *value, uint16_t length); +void csr_close_3wire(void); + +int csr_write_varid_valueless(int dd, uint16_t seqnum, uint16_t varid); +int csr_write_varid_complex(int dd, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length); +int csr_read_varid_complex(int dd, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length); +int csr_read_varid_uint16(int dd, uint16_t seqnum, uint16_t varid, uint16_t *value); +int csr_read_varid_uint32(int dd, uint16_t seqnum, uint16_t varid, uint32_t *value); +int csr_read_pskey_complex(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint8_t *value, uint16_t length); +int csr_write_pskey_complex(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint8_t *value, uint16_t length); +int csr_read_pskey_uint16(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint16_t *value); +int csr_write_pskey_uint16(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint16_t value); +int csr_read_pskey_uint32(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint32_t *value); +int csr_write_pskey_uint32(int dd, uint16_t seqnum, uint16_t pskey, uint16_t stores, uint32_t value); + +int psr_put(uint16_t pskey, uint8_t *value, uint16_t size); +int psr_get(uint16_t *pskey, uint8_t *value, uint16_t *size); +int psr_read(const char *filename); +int psr_print(void); diff --git a/tools/csr_3wire.c b/tools/csr_3wire.c new file mode 100644 index 0000000..33fcf38 --- /dev/null +++ b/tools/csr_3wire.c @@ -0,0 +1,62 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include + +#include "csr.h" + +static uint16_t seqnum = 0x0000; + +int csr_open_3wire(char *device) +{ + fprintf(stderr, "Transport not implemented\n"); + + return -1; +} + +static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length) +{ + errno = EIO; + + return -1; +} + +int csr_read_3wire(uint16_t varid, uint8_t *value, uint16_t length) +{ + return do_command(0x0000, seqnum++, varid, value, length); +} + +int csr_write_3wire(uint16_t varid, uint8_t *value, uint16_t length) +{ + return do_command(0x0002, seqnum++, varid, value, length); +} + +void csr_close_3wire(void) +{ +} diff --git a/tools/csr_bcsp.c b/tools/csr_bcsp.c new file mode 100644 index 0000000..e3eea34 --- /dev/null +++ b/tools/csr_bcsp.c @@ -0,0 +1,257 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "csr.h" +#include "ubcsp.h" + +static uint16_t seqnum = 0x0000; + +static int fd = -1; + +static struct ubcsp_packet send_packet; +static uint8_t send_buffer[512]; + +static struct ubcsp_packet receive_packet; +static uint8_t receive_buffer[512]; + +int csr_open_bcsp(char *device, speed_t bcsp_rate) +{ + struct termios ti; + uint8_t delay, activity = 0x00; + int timeout = 0; + + if (!device) + device = "/dev/ttyS0"; + + fd = open(device, O_RDWR | O_NOCTTY); + if (fd < 0) { + fprintf(stderr, "Can't open serial port: %s (%d)\n", + strerror(errno), errno); + return -1; + } + + tcflush(fd, TCIOFLUSH); + + if (tcgetattr(fd, &ti) < 0) { + fprintf(stderr, "Can't get port settings: %s (%d)\n", + strerror(errno), errno); + close(fd); + return -1; + } + + cfmakeraw(&ti); + + ti.c_cflag |= CLOCAL; + ti.c_cflag &= ~CRTSCTS; + ti.c_cflag |= PARENB; + ti.c_cflag &= ~PARODD; + ti.c_cflag &= ~CSIZE; + ti.c_cflag |= CS8; + ti.c_cflag &= ~CSTOPB; + + ti.c_cc[VMIN] = 1; + ti.c_cc[VTIME] = 0; + + cfsetospeed(&ti, bcsp_rate); + + if (tcsetattr(fd, TCSANOW, &ti) < 0) { + fprintf(stderr, "Can't change port settings: %s (%d)\n", + strerror(errno), errno); + close(fd); + return -1; + } + + tcflush(fd, TCIOFLUSH); + + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) < 0) { + fprintf(stderr, "Can't set non blocking mode: %s (%d)\n", + strerror(errno), errno); + close(fd); + return -1; + } + + memset(&send_packet, 0, sizeof(send_packet)); + memset(&receive_packet, 0, sizeof(receive_packet)); + + ubcsp_initialize(); + + send_packet.length = 512; + send_packet.payload = send_buffer; + + receive_packet.length = 512; + receive_packet.payload = receive_buffer; + + ubcsp_receive_packet(&receive_packet); + + while (1) { + delay = ubcsp_poll(&activity); + + if (activity & UBCSP_PACKET_SENT) + break; + + if (delay) { + usleep(delay * 100); + + if (timeout++ > 5000) { + fprintf(stderr, "Initialization timed out\n"); + return -1; + } + } + } + + return 0; +} + +void put_uart(uint8_t ch) +{ + if (write(fd, &ch, 1) < 0) + fprintf(stderr, "UART write error\n"); +} + +uint8_t get_uart(uint8_t *ch) +{ + int res = read(fd, ch, 1); + return res > 0 ? res : 0; +} + +static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length) +{ + unsigned char cp[254], rp[254]; + uint8_t cmd[10]; + uint16_t size; + uint8_t delay, activity = 0x00; + int timeout = 0, sent = 0; + + size = (length < 8) ? 9 : ((length + 1) / 2) + 5; + + cmd[0] = command & 0xff; + cmd[1] = command >> 8; + cmd[2] = size & 0xff; + cmd[3] = size >> 8; + cmd[4] = seqnum & 0xff; + cmd[5] = seqnum >> 8; + cmd[6] = varid & 0xff; + cmd[7] = varid >> 8; + cmd[8] = 0x00; + cmd[9] = 0x00; + + memset(cp, 0, sizeof(cp)); + cp[0] = 0x00; + cp[1] = 0xfc; + cp[2] = (size * 2) + 1; + cp[3] = 0xc2; + memcpy(cp + 4, cmd, sizeof(cmd)); + memcpy(cp + 14, value, length); + + receive_packet.length = 512; + ubcsp_receive_packet(&receive_packet); + + send_packet.channel = 5; + send_packet.reliable = 1; + send_packet.length = (size * 2) + 4; + memcpy(send_packet.payload, cp, (size * 2) + 4); + + ubcsp_send_packet(&send_packet); + + while (1) { + delay = ubcsp_poll(&activity); + + if (activity & UBCSP_PACKET_SENT) { + switch (varid) { + case CSR_VARID_COLD_RESET: + case CSR_VARID_WARM_RESET: + case CSR_VARID_COLD_HALT: + case CSR_VARID_WARM_HALT: + return 0; + } + + sent = 1; + timeout = 0; + } + + if (activity & UBCSP_PACKET_RECEIVED) { + if (sent && receive_packet.channel == 5 && + receive_packet.payload[0] == 0xff) { + memcpy(rp, receive_packet.payload, + receive_packet.length); + break; + } + + receive_packet.length = 512; + ubcsp_receive_packet(&receive_packet); + timeout = 0; + } + + if (delay) { + usleep(delay * 100); + + if (timeout++ > 5000) { + fprintf(stderr, "Operation timed out\n"); + errno = ETIMEDOUT; + return -1; + } + } + } + + if (rp[0] != 0xff || rp[2] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[11] + (rp[12] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + memcpy(value, rp + 13, length); + + return 0; +} + +int csr_read_bcsp(uint16_t varid, uint8_t *value, uint16_t length) +{ + return do_command(0x0000, seqnum++, varid, value, length); +} + +int csr_write_bcsp(uint16_t varid, uint8_t *value, uint16_t length) +{ + return do_command(0x0002, seqnum++, varid, value, length); +} + +void csr_close_bcsp(void) +{ + close(fd); +} diff --git a/tools/csr_h4.c b/tools/csr_h4.c new file mode 100644 index 0000000..2dcaec1 --- /dev/null +++ b/tools/csr_h4.c @@ -0,0 +1,166 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "csr.h" + +static uint16_t seqnum = 0x0000; + +static int fd = -1; + +int csr_open_h4(char *device) +{ + struct termios ti; + + if (!device) + device = "/dev/ttyS0"; + + fd = open(device, O_RDWR | O_NOCTTY); + if (fd < 0) { + fprintf(stderr, "Can't open serial port: %s (%d)\n", + strerror(errno), errno); + return -1; + } + + tcflush(fd, TCIOFLUSH); + + if (tcgetattr(fd, &ti) < 0) { + fprintf(stderr, "Can't get port settings: %s (%d)\n", + strerror(errno), errno); + close(fd); + return -1; + } + + cfmakeraw(&ti); + + ti.c_cflag |= CLOCAL; + ti.c_cflag |= CRTSCTS; + + cfsetospeed(&ti, B38400); + + if (tcsetattr(fd, TCSANOW, &ti) < 0) { + fprintf(stderr, "Can't change port settings: %s (%d)\n", + strerror(errno), errno); + close(fd); + return -1; + } + + tcflush(fd, TCIOFLUSH); + + return 0; +} + +static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length) +{ + unsigned char cp[254], rp[254]; + uint8_t cmd[10]; + uint16_t size; + int len, offset = 3; + + size = (length < 8) ? 9 : ((length + 1) / 2) + 5; + + cmd[0] = command & 0xff; + cmd[1] = command >> 8; + cmd[2] = size & 0xff; + cmd[3] = size >> 8; + cmd[4] = seqnum & 0xff; + cmd[5] = seqnum >> 8; + cmd[6] = varid & 0xff; + cmd[7] = varid >> 8; + cmd[8] = 0x00; + cmd[9] = 0x00; + + memset(cp, 0, sizeof(cp)); + cp[0] = 0x01; + cp[1] = 0x00; + cp[2] = 0xfc; + cp[3] = (size * 2) + 1; + cp[4] = 0xc2; + memcpy(cp + 5, cmd, sizeof(cmd)); + memcpy(cp + 15, value, length); + + if (write(fd, cp, (size * 2) + 5) < 0) + return -1; + + switch (varid) { + case CSR_VARID_COLD_RESET: + case CSR_VARID_WARM_RESET: + case CSR_VARID_COLD_HALT: + case CSR_VARID_WARM_HALT: + return 0; + } + + do { + if (read(fd, rp, 1) < 1) + return -1; + } while (rp[0] != 0x04); + + if (read(fd, rp + 1, 2) < 2) + return -1; + + do { + len = read(fd, rp + offset, sizeof(rp) - offset); + offset += len; + } while (offset < rp[2] + 3); + + if (rp[0] != 0x04 || rp[1] != 0xff || rp[3] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[12] + (rp[13] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + memcpy(value, rp + 14, length); + + return 0; +} + +int csr_read_h4(uint16_t varid, uint8_t *value, uint16_t length) +{ + return do_command(0x0000, seqnum++, varid, value, length); +} + +int csr_write_h4(uint16_t varid, uint8_t *value, uint16_t length) +{ + return do_command(0x0002, seqnum++, varid, value, length); +} + +void csr_close_h4(void) +{ + close(fd); +} diff --git a/tools/csr_hci.c b/tools/csr_hci.c new file mode 100644 index 0000000..d2e4ab9 --- /dev/null +++ b/tools/csr_hci.c @@ -0,0 +1,160 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "csr.h" + +static uint16_t seqnum = 0x0000; + +static int dd = -1; + +int csr_open_hci(char *device) +{ + struct hci_dev_info di; + struct hci_version ver; + int dev = 0; + + if (device) { + dev = hci_devid(device); + if (dev < 0) { + fprintf(stderr, "Device not available\n"); + return -1; + } + } + + dd = hci_open_dev(dev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + dev, strerror(errno), errno); + return -1; + } + + if (hci_devinfo(dev, &di) < 0) { + fprintf(stderr, "Can't get device info for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + return -1; + } + + if (hci_read_local_version(dd, &ver, 1000) < 0) { + fprintf(stderr, "Can't read version info for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + return -1; + } + + if (ver.manufacturer != 10) { + fprintf(stderr, "Unsupported manufacturer\n"); + hci_close_dev(dd); + return -1; + } + + return 0; +} + +static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, uint8_t *value, uint16_t length) +{ + unsigned char cp[254], rp[254]; + struct hci_request rq; + uint8_t cmd[10]; + uint16_t size; + + size = (length < 8) ? 9 : ((length + 1) / 2) + 5; + + cmd[0] = command & 0xff; + cmd[1] = command >> 8; + cmd[2] = size & 0xff; + cmd[3] = size >> 8; + cmd[4] = seqnum & 0xff; + cmd[5] = seqnum >> 8; + cmd[6] = varid & 0xff; + cmd[7] = varid >> 8; + cmd[8] = 0x00; + cmd[9] = 0x00; + + memset(cp, 0, sizeof(cp)); + cp[0] = 0xc2; + memcpy(cp + 1, cmd, sizeof(cmd)); + memcpy(cp + 11, value, length); + + switch (varid) { + case CSR_VARID_COLD_RESET: + case CSR_VARID_WARM_RESET: + case CSR_VARID_COLD_HALT: + case CSR_VARID_WARM_HALT: + return hci_send_cmd(dd, OGF_VENDOR_CMD, 0x00, (size * 2) + 1, cp); + } + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x00; + rq.event = EVT_VENDOR; + rq.cparam = cp; + rq.clen = (size * 2) + 1; + rq.rparam = rp; + rq.rlen = sizeof(rp); + + if (hci_send_req(dd, &rq, 2000) < 0) + return -1; + + if (rp[0] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[9] + (rp[10] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + memcpy(value, rp + 11, length); + + return 0; +} + +int csr_read_hci(uint16_t varid, uint8_t *value, uint16_t length) +{ + return do_command(0x0000, seqnum++, varid, value, length); +} + +int csr_write_hci(uint16_t varid, uint8_t *value, uint16_t length) +{ + return do_command(0x0002, seqnum++, varid, value, length); +} + +void csr_close_hci(void) +{ + hci_close_dev(dd); +} diff --git a/tools/csr_usb.c b/tools/csr_usb.c new file mode 100644 index 0000000..32fdf1f --- /dev/null +++ b/tools/csr_usb.c @@ -0,0 +1,308 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "csr.h" + +#define USB_TYPE_CLASS (0x01 << 5) + +#define USB_RECIP_DEVICE 0x00 + +#define USB_ENDPOINT_IN 0x80 +#define USB_ENDPOINT_OUT 0x00 + +struct usbfs_ctrltransfer { + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + uint32_t timeout; /* in milliseconds */ + void *data; /* pointer to data */ +}; + +struct usbfs_bulktransfer { + unsigned int ep; + unsigned int len; + unsigned int timeout; /* in milliseconds */ + void *data; /* pointer to data */ +}; + +#define USBFS_IOCTL_CONTROL _IOWR('U', 0, struct usbfs_ctrltransfer) +#define USBFS_IOCTL_BULK _IOWR('U', 2, struct usbfs_bulktransfer) +#define USBFS_IOCTL_CLAIMINTF _IOR('U', 15, unsigned int) +#define USBFS_IOCTL_RELEASEINTF _IOR('U', 16, unsigned int) + +static int read_value(const char *name, const char *attr, bool hex_number) +{ + char path[PATH_MAX]; + FILE *file; + int n, value; + + snprintf(path, sizeof(path), "/sys/bus/usb/devices/%s/%s", name, attr); + + file = fopen(path, "r"); + if (!file) + return -1; + + n = fscanf(file, hex_number ? "%d" : "%04x", &value); + if (n != 1) { + fclose(file); + return -1; + } + + fclose(file); + return value; +} + +#define read_hex_value(name, file) read_value((name), (file), true) +#define read_num_value(name, file) read_value((name), (file), false) + +static char *check_device(const char *name) +{ + char path[PATH_MAX]; + int busnum, devnum, vendor, product; + + busnum = read_num_value(name, "busnum"); + if (busnum < 0) + return NULL; + + devnum = read_num_value(name, "devnum"); + if (devnum < 0) + return NULL; + + snprintf(path, sizeof(path), "/dev/bus/usb/%03u/%03u", busnum, devnum); + + vendor = read_hex_value(name, "idVendor"); + if (vendor < 0) + return NULL; + + product = read_hex_value(name, "idProduct"); + if (product < 0) + return NULL; + + if (vendor != 0x0a12 || product != 0x0001) + return NULL; + + return strdup(path); +} + +static char *find_device(void) +{ + char *path = NULL; + DIR *dir; + + dir = opendir("/sys/bus/usb/devices"); + if (!dir) + return NULL; + + while (1) { + struct dirent *d; + + d = readdir(dir); + if (!d) + break; + + if ((!isdigit(d->d_name[0]) && strncmp(d->d_name, "usb", 3)) + || strchr(d->d_name, ':')) + continue; + + path = check_device(d->d_name); + if (path) + break; + } + + closedir(dir); + + return path; +} + +static uint16_t seqnum = 0x0000; +static int handle = -1; + +int csr_open_usb(char *device) +{ + int interface = 0; + char *path; + + path = find_device(); + if (!path) { + fprintf(stderr, "Device not available\n"); + return -1; + } + + handle = open(path, O_RDWR, O_CLOEXEC | O_NONBLOCK); + + free(path); + + if (handle < 0) { + fprintf(stderr, "Can't open device: %s (%d)\n", + strerror(errno), errno); + return -1; + } + + if (ioctl(handle, USBFS_IOCTL_CLAIMINTF, &interface) < 0) { + fprintf(stderr, "Can't claim interface: %s (%d)\n", + strerror(errno), errno); + close(handle); + handle = -1; + return -1; + } + + return 0; +} + +static int control_write(int fd, void *data, unsigned short size) +{ + struct usbfs_ctrltransfer transfer; + + transfer.bmRequestType = USB_TYPE_CLASS | USB_ENDPOINT_OUT | + USB_RECIP_DEVICE; + transfer.bRequest = 0; + transfer.wValue = 0; + transfer.wIndex = 0; + transfer.wLength = size, + transfer.timeout = 2000; + transfer.data = data; + + if (ioctl(fd, USBFS_IOCTL_CONTROL, &transfer) < 0) { + fprintf(stderr, "Control transfer failed: %s (%d)\n", + strerror(errno), errno); + return -1; + } + + return 0; +} + +static int interrupt_read(int fd, unsigned char endpoint, + void *data, unsigned short size) +{ + struct usbfs_bulktransfer transfer; + + transfer.ep = endpoint; + transfer.len = size, + transfer.timeout = 20; + transfer.data = data; + + return ioctl(fd, USBFS_IOCTL_BULK, &transfer); +} + +static int do_command(uint16_t command, uint16_t seqnum, uint16_t varid, + uint8_t *value, uint16_t length) +{ + unsigned char cp[254], rp[254]; + uint8_t cmd[10]; + uint16_t size; + int len, offset = 0; + + size = (length < 8) ? 9 : ((length + 1) / 2) + 5; + + cmd[0] = command & 0xff; + cmd[1] = command >> 8; + cmd[2] = size & 0xff; + cmd[3] = size >> 8; + cmd[4] = seqnum & 0xff; + cmd[5] = seqnum >> 8; + cmd[6] = varid & 0xff; + cmd[7] = varid >> 8; + cmd[8] = 0x00; + cmd[9] = 0x00; + + memset(cp, 0, sizeof(cp)); + cp[0] = 0x00; + cp[1] = 0xfc; + cp[2] = (size * 2) + 1; + cp[3] = 0xc2; + memcpy(cp + 4, cmd, sizeof(cmd)); + memcpy(cp + 14, value, length); + + interrupt_read(handle, USB_ENDPOINT_IN | 0x01, rp, sizeof(rp)); + + control_write(handle, cp, (size * 2) + 4); + + switch (varid) { + case CSR_VARID_COLD_RESET: + case CSR_VARID_WARM_RESET: + case CSR_VARID_COLD_HALT: + case CSR_VARID_WARM_HALT: + return 0; + } + + do { + len = interrupt_read(handle, USB_ENDPOINT_IN | 0x01, + rp + offset, sizeof(rp) - offset); + if (len < 0) + break; + offset += len; + } while (len > 0); + + if (rp[0] != 0xff || rp[2] != 0xc2) { + errno = EIO; + return -1; + } + + if ((rp[11] + (rp[12] << 8)) != 0) { + errno = ENXIO; + return -1; + } + + memcpy(value, rp + 13, length); + + return 0; +} + +int csr_read_usb(uint16_t varid, uint8_t *value, uint16_t length) +{ + return do_command(0x0000, seqnum++, varid, value, length); +} + +int csr_write_usb(uint16_t varid, uint8_t *value, uint16_t length) +{ + return do_command(0x0002, seqnum++, varid, value, length); +} + +void csr_close_usb(void) +{ + int interface = 0; + + ioctl(handle, USBFS_IOCTL_RELEASEINTF, &interface); + + close(handle); + handle = -1; +} diff --git a/tools/eddystone.c b/tools/eddystone.c new file mode 100644 index 0000000..4764c67 --- /dev/null +++ b/tools/eddystone.c @@ -0,0 +1,311 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011-2012 Intel Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "monitor/bt.h" +#include "src/shared/mainloop.h" +#include "src/shared/timeout.h" +#include "src/shared/util.h" +#include "src/shared/hci.h" + +static int urandom_fd; +static struct bt_hci *hci_dev; + +static bool shutdown_timeout(void *user_data) +{ + mainloop_quit(); + + return false; +} + +static void shutdown_complete(const void *data, uint8_t size, void *user_data) +{ + unsigned int id = PTR_TO_UINT(user_data); + + timeout_remove(id); + mainloop_quit(); +} + +static void shutdown_device(void) +{ + uint8_t enable = 0x00; + unsigned int id; + + bt_hci_flush(hci_dev); + + id = timeout_add(5000, shutdown_timeout, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_LE_SET_ADV_ENABLE, + &enable, 1, NULL, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, + shutdown_complete, UINT_TO_PTR(id), NULL); +} + +static void set_random_address(void) +{ + struct bt_hci_cmd_le_set_random_address cmd; + ssize_t len; + + len = read(urandom_fd, cmd.addr, sizeof(cmd.addr)); + if (len < 0 || len != sizeof(cmd.addr)) { + fprintf(stderr, "Failed to read random data\n"); + return; + } + + /* Clear top most significant bits */ + cmd.addr[5] &= 0x3f; + + bt_hci_send(hci_dev, BT_HCI_CMD_LE_SET_RANDOM_ADDRESS, + &cmd, sizeof(cmd), NULL, NULL, NULL); +} + +static void set_adv_parameters(void) +{ + struct bt_hci_cmd_le_set_adv_parameters cmd; + + cmd.min_interval = cpu_to_le16(0x0800); + cmd.max_interval = cpu_to_le16(0x0800); + cmd.type = 0x03; /* Non-connectable advertising */ + cmd.own_addr_type = 0x01; /* Use random address */ + cmd.direct_addr_type = 0x00; + memset(cmd.direct_addr, 0, 6); + cmd.channel_map = 0x07; + cmd.filter_policy = 0x00; + + bt_hci_send(hci_dev, BT_HCI_CMD_LE_SET_ADV_PARAMETERS, + &cmd, sizeof(cmd), NULL, NULL, NULL); +} + +static void set_adv_enable(void) +{ + uint8_t enable = 0x01; + + bt_hci_send(hci_dev, BT_HCI_CMD_LE_SET_ADV_ENABLE, + &enable, 1, NULL, NULL, NULL); +} + +static void adv_data_callback(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + fprintf(stderr, "Failed to set advertising data\n"); + shutdown_device(); + return; + } + + set_random_address(); + set_adv_parameters(); + set_adv_enable(); +} + +static void adv_tx_power_callback(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_le_read_adv_tx_power *rsp = data; + struct bt_hci_cmd_le_set_adv_data cmd; + + if (rsp->status) { + fprintf(stderr, "Failed to read advertising TX power\n"); + shutdown_device(); + return; + } + + cmd.data[0] = 0x02; /* Field length */ + cmd.data[1] = 0x01; /* Flags */ + cmd.data[2] = 0x04; /* BR/EDR Not Supported */ + + cmd.data[3] = 0x03; /* Field length */ + cmd.data[4] = 0x03; /* 16-bit Service UUID list */ + cmd.data[5] = 0xaa; /* Eddystone UUID */ + cmd.data[6] = 0xfe; + + cmd.data[7] = 0x0c; /* Field length */ + cmd.data[8] = 0x16; /* 16-bit Service UUID data */ + cmd.data[9] = 0xaa; /* Eddystone UUID */ + cmd.data[10] = 0xfe; + cmd.data[11] = 0x10; /* Eddystone-URL frame type */ + cmd.data[12] = 0x00; /* Calibrated Tx power at 0m */ + cmd.data[13] = 0x00; /* URL Scheme Prefix http://www. */ + cmd.data[14] = 'b'; + cmd.data[15] = 'l'; + cmd.data[16] = 'u'; + cmd.data[17] = 'e'; + cmd.data[18] = 'z'; + cmd.data[19] = 0x01; /* .org/ */ + + cmd.data[20] = 0x00; /* Field terminator */ + memset(cmd.data + 21, 0, 9); + + cmd.len = 1 + cmd.data[0] + 1 + cmd.data[3] + 1 + cmd.data[7]; + + bt_hci_send(hci_dev, BT_HCI_CMD_LE_SET_ADV_DATA, &cmd, sizeof(cmd), + adv_data_callback, NULL, NULL); +} + +static void local_features_callback(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_read_local_features *rsp = data; + + if (rsp->status) { + fprintf(stderr, "Failed to read local features\n"); + shutdown_device(); + return; + } + + if (!(rsp->features[4] & 0x40)) { + fprintf(stderr, "Controller without Low Energy support\n"); + shutdown_device(); + return; + } + + bt_hci_send(hci_dev, BT_HCI_CMD_LE_READ_ADV_TX_POWER, NULL, 0, + adv_tx_power_callback, NULL, NULL); +} + +static void start_eddystone(void) +{ + bt_hci_send(hci_dev, BT_HCI_CMD_RESET, NULL, 0, NULL, NULL, NULL); + + bt_hci_send(hci_dev, BT_HCI_CMD_READ_LOCAL_FEATURES, NULL, 0, + local_features_callback, NULL, NULL); +} + +static void signal_callback(int signum, void *user_data) +{ + static bool terminated = false; + + switch (signum) { + case SIGINT: + case SIGTERM: + if (!terminated) { + shutdown_device(); + terminated = true; + } + break; + } +} + +static void usage(void) +{ + printf("eddystone - Low Energy Eddystone testing tool\n" + "Usage:\n"); + printf("\teddystone [options]\n"); + printf("Options:\n" + "\t-i, --index Use specified controller\n" + "\t-h, --help Show help options\n"); +} + +static const struct option main_options[] = { + { "index", required_argument, NULL, 'i' }, + { "version", no_argument, NULL, 'v' }, + { "help", no_argument, NULL, 'h' }, + { } +}; + +int main(int argc, char *argv[]) +{ + uint16_t index = 0; + const char *str; + int exit_status; + + for (;;) { + int opt; + + opt = getopt_long(argc, argv, "i:vh", main_options, NULL); + if (opt < 0) + break; + + switch (opt) { + case 'i': + if (strlen(optarg) > 3 && !strncmp(optarg, "hci", 3)) + str = optarg + 3; + else + str = optarg; + if (!isdigit(*str)) { + usage(); + return EXIT_FAILURE; + } + index = atoi(str); + break; + case 'v': + printf("%s\n", VERSION); + return EXIT_SUCCESS; + case 'h': + usage(); + return EXIT_SUCCESS; + default: + return EXIT_FAILURE; + } + } + + if (argc - optind > 0) { + fprintf(stderr, "Invalid command line parameters\n"); + return EXIT_FAILURE; + } + + urandom_fd = open("/dev/urandom", O_RDONLY); + if (urandom_fd < 0) { + fprintf(stderr, "Failed to open /dev/urandom device\n"); + return EXIT_FAILURE; + } + + mainloop_init(); + + printf("Low Energy Eddystone utility ver %s\n", VERSION); + + hci_dev = bt_hci_new_user_channel(index); + if (!hci_dev) { + fprintf(stderr, "Failed to open HCI user channel\n"); + exit_status = EXIT_FAILURE; + goto done; + } + + start_eddystone(); + + exit_status = mainloop_run_with_signal(signal_callback, NULL); + + bt_hci_unref(hci_dev); + +done: + close(urandom_fd); + + return exit_status; +} diff --git a/tools/gap-tester.c b/tools/gap-tester.c new file mode 100644 index 0000000..2aa4042 --- /dev/null +++ b/tools/gap-tester.c @@ -0,0 +1,139 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 +#endif + +#include "gdbus/gdbus.h" + +#include "src/shared/tester.h" +#include "emulator/hciemu.h" + +static DBusConnection *dbus_conn = NULL; +static GDBusClient *dbus_client = NULL; +static GDBusProxy *adapter_proxy = NULL; + +static struct hciemu *hciemu_stack = NULL; + +static void connect_handler(DBusConnection *connection, void *user_data) +{ + tester_print("Connected to daemon"); + + hciemu_stack = hciemu_new(HCIEMU_TYPE_BREDRLE); +} + +static void disconnect_handler(DBusConnection *connection, void *user_data) +{ + tester_print("Disconnected from daemon"); + + dbus_connection_unref(dbus_conn); + dbus_conn = NULL; + + tester_teardown_complete(); +} + +static gboolean compare_string_property(GDBusProxy *proxy, const char *name, + const char *value) +{ + DBusMessageIter iter; + const char *str; + + if (g_dbus_proxy_get_property(proxy, name, &iter) == FALSE) + return FALSE; + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(&iter, &str); + + return g_str_equal(str, value); +} + +static void proxy_added(GDBusProxy *proxy, void *user_data) +{ + const char *interface; + + interface = g_dbus_proxy_get_interface(proxy); + + if (g_str_equal(interface, "org.bluez.Adapter1") == TRUE) { + if (compare_string_property(proxy, "Address", + hciemu_get_address(hciemu_stack)) == TRUE) { + adapter_proxy = proxy; + tester_print("Found adapter"); + + tester_setup_complete(); + } + } +} + +static void proxy_removed(GDBusProxy *proxy, void *user_data) +{ + const char *interface; + + interface = g_dbus_proxy_get_interface(proxy); + + if (g_str_equal(interface, "org.bluez.Adapter1") == TRUE) { + if (adapter_proxy == proxy) { + adapter_proxy = NULL; + tester_print("Adapter removed"); + + g_dbus_client_unref(dbus_client); + dbus_client = NULL; + } + } +} + +static void test_setup(const void *test_data) +{ + dbus_conn = g_dbus_setup_private(DBUS_BUS_SYSTEM, NULL, NULL); + + dbus_client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez"); + + g_dbus_client_set_connect_watch(dbus_client, connect_handler, NULL); + g_dbus_client_set_disconnect_watch(dbus_client, + disconnect_handler, NULL); + + g_dbus_client_set_proxy_handlers(dbus_client, proxy_added, + proxy_removed, NULL, NULL); +} + +static void test_run(const void *test_data) +{ + tester_test_passed(); +} + +static void test_teardown(const void *test_data) +{ + hciemu_unref(hciemu_stack); + hciemu_stack = NULL; +} + +int main(int argc, char *argv[]) +{ + tester_init(&argc, &argv); + + tester_add("Adapter setup", NULL, test_setup, test_run, test_teardown); + + return tester_run(); +} diff --git a/tools/gatt-service.c b/tools/gatt-service.c new file mode 100644 index 0000000..374a4c9 --- /dev/null +++ b/tools/gatt-service.c @@ -0,0 +1,900 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2014 Instituto Nokia de Tecnologia - INdT + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gdbus/gdbus.h" + +#include "src/error.h" + +#define GATT_MGR_IFACE "org.bluez.GattManager1" +#define GATT_SERVICE_IFACE "org.bluez.GattService1" +#define GATT_CHR_IFACE "org.bluez.GattCharacteristic1" +#define GATT_DESCRIPTOR_IFACE "org.bluez.GattDescriptor1" + +/* Immediate Alert Service UUID */ +#define IAS_UUID "00001802-0000-1000-8000-00805f9b34fb" +#define ALERT_LEVEL_CHR_UUID "00002a06-0000-1000-8000-00805f9b34fb" +#define IAS_UUID1 "A00B" +#define IAS_UUID2 "A00C" +#define IAS_UUID3 "A00D" +#define ALERT_LEVEL_CHR_UUID1 "00002b06-0000-1000-8000-00805f9b34fb" +#define ALERT_LEVEL_CHR_UUID2 "00002c07-0000-1000-8000-00805f9b34fb" +/* Random UUID for testing purpose */ + +/* Random UUID for testing purpose */ +#define READ_WRITE_DESCRIPTOR_UUID "8260c653-1a54-426b-9e36-e84c238bc669" +#define READ_WRITE_DESCRIPTOR_UUID1 "0260c653-1a54-426b-9e36-e84c238bc669" +#define READ_WRITE_DESCRIPTOR_UUID2 "FFFF" + +static GMainLoop *main_loop; +static GSList *services; +static DBusConnection *connection; + +struct characteristic { + char *service; + char *uuid; + char *path; + uint8_t *value; + int vlen; + const char **props; +}; + +struct descriptor { + struct characteristic *chr; + char *uuid; + char *path; + uint8_t *value; + int vlen; + const char **props; +}; + +/* + * Alert Level support Write Without Response only. Supported + * properties are defined at doc/gatt-api.txt. See "Flags" + * property of the GattCharacteristic1. + */ +static const char *ias_alert_level_props[] = { "write-without-response", NULL }; +static const char *desc_props[] = { "read", "write", NULL }; + +static gboolean desc_get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct descriptor *desc = user_data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &desc->uuid); + + return TRUE; +} + +static gboolean desc_get_characteristic(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct descriptor *desc = user_data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &desc->chr->path); + + return TRUE; +} + +static bool desc_read(struct descriptor *desc, DBusMessageIter *iter) +{ + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array); + + if (desc->vlen && desc->value) + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &desc->value, desc->vlen); + + dbus_message_iter_close_container(iter, &array); + + return true; +} + +static gboolean desc_get_value(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct descriptor *desc = user_data; + + printf("Descriptor(%s): Get(\"Value\")\n", desc->uuid); + + return desc_read(desc, iter); +} + +static void desc_write(struct descriptor *desc, const uint8_t *value, int len) +{ + g_free(desc->value); + desc->value = g_memdup(value, len); + desc->vlen = len; + + g_dbus_emit_property_changed(connection, desc->path, + GATT_DESCRIPTOR_IFACE, "Value"); +} + +static int parse_value(DBusMessageIter *iter, const uint8_t **value, int *len) +{ + DBusMessageIter array; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(iter, &array); + dbus_message_iter_get_fixed_array(&array, value, len); + + return 0; +} + +static void desc_set_value(const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *user_data) +{ + struct descriptor *desc = user_data; + const uint8_t *value; + int len; + + printf("Descriptor(%s): Set(\"Value\", ...)\n", desc->uuid); + + if (parse_value(iter, &value, &len)) { + printf("Invalid value for Set('Value'...)\n"); + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + desc_write(desc, value, len); + + g_dbus_pending_property_success(id); +} + +static gboolean desc_get_props(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct descriptor *desc = data; + DBusMessageIter array; + int i; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array); + + for (i = 0; desc->props[i]; i++) + dbus_message_iter_append_basic(&array, + DBUS_TYPE_STRING, &desc->props[i]); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static const GDBusPropertyTable desc_properties[] = { + { "UUID", "s", desc_get_uuid }, + { "Characteristic", "o", desc_get_characteristic }, + { "Value", "ay", desc_get_value, desc_set_value, NULL }, + { "Flags", "as", desc_get_props, NULL, NULL }, + { } +}; + +static gboolean chr_get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct characteristic *chr = user_data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &chr->uuid); + + return TRUE; +} + +static gboolean chr_get_service(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct characteristic *chr = user_data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, + &chr->service); + + return TRUE; +} + +static bool chr_read(struct characteristic *chr, DBusMessageIter *iter) +{ + DBusMessageIter array; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_BYTE_AS_STRING, &array); + + dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, + &chr->value, chr->vlen); + + dbus_message_iter_close_container(iter, &array); + + return true; +} + +static gboolean chr_get_value(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + struct characteristic *chr = user_data; + + printf("Characteristic(%s): Get(\"Value\")\n", chr->uuid); + + return chr_read(chr, iter); +} + +static gboolean chr_get_props(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct characteristic *chr = data; + DBusMessageIter array; + int i; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array); + + for (i = 0; chr->props[i]; i++) + dbus_message_iter_append_basic(&array, + DBUS_TYPE_STRING, &chr->props[i]); + + dbus_message_iter_close_container(iter, &array); + + return TRUE; +} + +static void chr_write(struct characteristic *chr, const uint8_t *value, int len) +{ + g_free(chr->value); + chr->value = g_memdup(value, len); + chr->vlen = len; + + g_dbus_emit_property_changed(connection, chr->path, GATT_CHR_IFACE, + "Value"); +} + +static void chr_set_value(const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *user_data) +{ + struct characteristic *chr = user_data; + const uint8_t *value; + int len; + + printf("Characteristic(%s): Set('Value', ...)\n", chr->uuid); + + if (!parse_value(iter, &value, &len)) { + printf("Invalid value for Set('Value'...)\n"); + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + chr_write(chr, value, len); + + g_dbus_pending_property_success(id); +} + +static const GDBusPropertyTable chr_properties[] = { + { "UUID", "s", chr_get_uuid }, + { "Service", "o", chr_get_service }, + { "Value", "ay", chr_get_value, chr_set_value, NULL }, + { "Flags", "as", chr_get_props, NULL, NULL }, + { } +}; + +static gboolean service_get_primary(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + dbus_bool_t primary = TRUE; + + printf("Get Primary: %s\n", primary ? "True" : "False"); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &primary); + + return TRUE; +} + +static gboolean service_get_uuid(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + const char *uuid = user_data; + + printf("Get UUID: %s\n", uuid); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); + + return TRUE; +} + +static gboolean service_get_includes(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *user_data) +{ + const char *uuid = user_data; + char service_path[100] = {0,}; + DBusMessageIter array; + char *p = NULL; + + snprintf(service_path, 100, "/service3"); + printf("Get Includes: %s\n", uuid); + + p = service_path; + + printf("Includes path: %s\n", p); + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_OBJECT_PATH_AS_STRING, &array); + + dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH, + &p); + + snprintf(service_path, 100, "/service2"); + p = service_path; + printf("Get Includes: %s\n", p); + + dbus_message_iter_append_basic(&array, DBUS_TYPE_OBJECT_PATH, + &p); + dbus_message_iter_close_container(iter, &array); + + + return TRUE; + +} + + +static gboolean service_exist_includes(const GDBusPropertyTable *property, + void *user_data) +{ + const char *uuid = user_data; + + printf("Exist Includes: %s\n", uuid); + if (strncmp(uuid, "00001802", 8) == 0) + return TRUE; + + return FALSE; +} + +static const GDBusPropertyTable service_properties[] = { + { "Primary", "b", service_get_primary }, + { "UUID", "s", service_get_uuid }, + { "Includes", "ao", service_get_includes, NULL, + service_exist_includes }, + { } +}; + +static void chr_iface_destroy(gpointer user_data) +{ + struct characteristic *chr = user_data; + + g_free(chr->uuid); + g_free(chr->service); + g_free(chr->value); + g_free(chr->path); + g_free(chr); +} + +static void desc_iface_destroy(gpointer user_data) +{ + struct descriptor *desc = user_data; + + g_free(desc->uuid); + g_free(desc->value); + g_free(desc->path); + g_free(desc); +} + +static int parse_options(DBusMessageIter *iter, const char **device) +{ + DBusMessageIter dict; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) + return -EINVAL; + + dbus_message_iter_recurse(iter, &dict); + + while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(&dict, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "device") == 0) { + if (var != DBUS_TYPE_OBJECT_PATH) + return -EINVAL; + dbus_message_iter_get_basic(&value, device); + printf("Device: %s\n", *device); + } + + dbus_message_iter_next(&dict); + } + + return 0; +} + +static DBusMessage *chr_read_value(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct characteristic *chr = user_data; + DBusMessage *reply; + DBusMessageIter iter; + const char *device; + + if (!dbus_message_iter_init(msg, &iter)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + if (parse_options(&iter, &device)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return g_dbus_create_error(msg, DBUS_ERROR_NO_MEMORY, + "No Memory"); + + dbus_message_iter_init_append(reply, &iter); + + chr_read(chr, &iter); + + return reply; +} + +static DBusMessage *chr_write_value(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct characteristic *chr = user_data; + DBusMessageIter iter; + const uint8_t *value; + int len; + const char *device; + + dbus_message_iter_init(msg, &iter); + + if (parse_value(&iter, &value, &len)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + if (parse_options(&iter, &device)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + chr_write(chr, value, len); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *chr_start_notify(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + return g_dbus_create_error(msg, DBUS_ERROR_NOT_SUPPORTED, + "Not Supported"); +} + +static DBusMessage *chr_stop_notify(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + return g_dbus_create_error(msg, DBUS_ERROR_NOT_SUPPORTED, + "Not Supported"); +} + +static const GDBusMethodTable chr_methods[] = { + { GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({ "options", "a{sv}" }), + GDBUS_ARGS({ "value", "ay" }), + chr_read_value) }, + { GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({ "value", "ay" }, + { "options", "a{sv}" }), + NULL, chr_write_value) }, + { GDBUS_ASYNC_METHOD("StartNotify", NULL, NULL, chr_start_notify) }, + { GDBUS_METHOD("StopNotify", NULL, NULL, chr_stop_notify) }, + { } +}; + +static DBusMessage *desc_read_value(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct descriptor *desc = user_data; + DBusMessage *reply; + DBusMessageIter iter; + const char *device; + + if (!dbus_message_iter_init(msg, &iter)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + if (parse_options(&iter, &device)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return g_dbus_create_error(msg, DBUS_ERROR_NO_MEMORY, + "No Memory"); + + dbus_message_iter_init_append(reply, &iter); + + desc_read(desc, &iter); + + return reply; +} + +static DBusMessage *desc_write_value(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct descriptor *desc = user_data; + DBusMessageIter iter; + const char *device; + const uint8_t *value; + int len; + + if (!dbus_message_iter_init(msg, &iter)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + if (parse_value(&iter, &value, &len)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + if (parse_options(&iter, &device)) + return g_dbus_create_error(msg, DBUS_ERROR_INVALID_ARGS, + "Invalid arguments"); + + desc_write(desc, value, len); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable desc_methods[] = { + { GDBUS_ASYNC_METHOD("ReadValue", GDBUS_ARGS({ "options", "a{sv}" }), + GDBUS_ARGS({ "value", "ay" }), + desc_read_value) }, + { GDBUS_ASYNC_METHOD("WriteValue", GDBUS_ARGS({ "value", "ay" }, + { "options", "a{sv}" }), + NULL, desc_write_value) }, + { } +}; + +static gboolean register_characteristic(const char *chr_uuid, + const uint8_t *value, int vlen, + const char **props, + const char *desc_uuid, + const char **desc_props, + const char *service_path) +{ + struct characteristic *chr; + struct descriptor *desc; + static int id = 1; + + chr = g_new0(struct characteristic, 1); + chr->uuid = g_strdup(chr_uuid); + chr->value = g_memdup(value, vlen); + chr->vlen = vlen; + chr->props = props; + chr->service = g_strdup(service_path); + chr->path = g_strdup_printf("%s/characteristic%d", service_path, id++); + + if (!g_dbus_register_interface(connection, chr->path, GATT_CHR_IFACE, + chr_methods, NULL, chr_properties, + chr, chr_iface_destroy)) { + printf("Couldn't register characteristic interface\n"); + chr_iface_destroy(chr); + return FALSE; + } + + if (!desc_uuid) + return TRUE; + + desc = g_new0(struct descriptor, 1); + desc->uuid = g_strdup(desc_uuid); + desc->chr = chr; + desc->props = desc_props; + desc->path = g_strdup_printf("%s/descriptor%d", chr->path, id++); + + if (!g_dbus_register_interface(connection, desc->path, + GATT_DESCRIPTOR_IFACE, + desc_methods, NULL, desc_properties, + desc, desc_iface_destroy)) { + printf("Couldn't register descriptor interface\n"); + g_dbus_unregister_interface(connection, chr->path, + GATT_CHR_IFACE); + + desc_iface_destroy(desc); + return FALSE; + } + + return TRUE; +} + +static char *register_service(const char *uuid) +{ + static int id = 1; + char *path; + + path = g_strdup_printf("/service%d", id++); + if (!g_dbus_register_interface(connection, path, GATT_SERVICE_IFACE, + NULL, NULL, service_properties, + g_strdup(uuid), g_free)) { + printf("Couldn't register service interface\n"); + g_free(path); + return NULL; + } + + return path; +} + +static void create_services_one(void) +{ + char *service_path; + uint8_t level = 0; + + service_path = register_service(IAS_UUID); + if (!service_path) + return; + + /* Add Alert Level Characteristic to Immediate Alert Service */ + if (!register_characteristic(ALERT_LEVEL_CHR_UUID, + &level, sizeof(level), + ias_alert_level_props, + READ_WRITE_DESCRIPTOR_UUID, + desc_props, + service_path)) { + printf("Couldn't register Alert Level characteristic (IAS)\n"); + g_dbus_unregister_interface(connection, service_path, + GATT_SERVICE_IFACE); + g_free(service_path); + return; + } + + services = g_slist_prepend(services, service_path); + printf("Registered service: %s\n", service_path); +} + + +static void create_services_two(void) +{ + char *service_path; + uint8_t level = 0; + + service_path = register_service(IAS_UUID2); + if (!service_path) + return; + + if (!register_characteristic(ALERT_LEVEL_CHR_UUID2, + &level, sizeof(level), + ias_alert_level_props, + READ_WRITE_DESCRIPTOR_UUID2, + desc_props, + service_path)) { + printf("Couldn't register Alert Level characteristic (IAS)\n"); + g_dbus_unregister_interface(connection, service_path, + GATT_SERVICE_IFACE); + g_free(service_path); + return; + } + services = g_slist_prepend(services, service_path); + printf("Registered service: %s\n", service_path); +} + +static void create_services_three(void) +{ + char *service_path; + uint8_t level = 0; + + service_path = register_service(IAS_UUID3); + if (!service_path) + return; + + if (!register_characteristic(ALERT_LEVEL_CHR_UUID1, + &level, sizeof(level), + ias_alert_level_props, + READ_WRITE_DESCRIPTOR_UUID1, + desc_props, + service_path)) { + printf("Couldn't register Alert Level characteristic (IAS)\n"); + g_dbus_unregister_interface(connection, service_path, + GATT_SERVICE_IFACE); + g_free(service_path); + return; + } + + + services = g_slist_prepend(services, service_path); + printf("Registered service: %s\n", service_path); +} + +static void register_app_reply(DBusMessage *reply, void *user_data) +{ + DBusError derr; + + dbus_error_init(&derr); + dbus_set_error_from_message(&derr, reply); + + if (dbus_error_is_set(&derr)) + printf("RegisterApplication: %s\n", derr.message); + else + printf("RegisterApplication: OK\n"); + + dbus_error_free(&derr); +} + +static void register_app_setup(DBusMessageIter *iter, void *user_data) +{ + const char *path = "/"; + DBusMessageIter dict; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict); + + /* TODO: Add options dictionary */ + + dbus_message_iter_close_container(iter, &dict); +} + +static void register_app(GDBusProxy *proxy) +{ + if (!g_dbus_proxy_method_call(proxy, "RegisterApplication", + register_app_setup, register_app_reply, + NULL, NULL)) { + printf("Unable to call RegisterApplication\n"); + return; + } +} + +static void proxy_added_cb(GDBusProxy *proxy, void *user_data) +{ + const char *iface; + + iface = g_dbus_proxy_get_interface(proxy); + + if (g_strcmp0(iface, GATT_MGR_IFACE)) + return; + + register_app(proxy); +} + +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) { + printf("Terminating\n"); + g_main_loop_quit(main_loop); + } + + __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; +} + +int main(int argc, char *argv[]) +{ + GDBusClient *client; + guint signal; + + signal = setup_signalfd(); + if (signal == 0) + return -errno; + + connection = g_dbus_setup_bus(DBUS_BUS_SYSTEM, NULL, NULL); + + main_loop = g_main_loop_new(NULL, FALSE); + + g_dbus_attach_object_manager(connection); + + printf("gatt-service unique name: %s\n", + dbus_bus_get_unique_name(connection)); + + create_services_one(); + create_services_two(); + create_services_three(); + + client = g_dbus_client_new(connection, "org.bluez", "/"); + + g_dbus_client_set_proxy_handlers(client, proxy_added_cb, NULL, NULL, + NULL); + + g_main_loop_run(main_loop); + + g_dbus_client_unref(client); + + g_source_remove(signal); + + g_slist_free_full(services, g_free); + dbus_connection_unref(connection); + + return 0; +} diff --git a/tools/hci-tester.c b/tools/hci-tester.c new file mode 100644 index 0000000..7873e7a --- /dev/null +++ b/tools/hci-tester.c @@ -0,0 +1,986 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2013 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 +#endif + +#include +#include + +#include "monitor/bt.h" +#include "src/shared/hci.h" +#include "src/shared/util.h" +#include "src/shared/ecc.h" +#include "src/shared/tester.h" + +struct user_data { + const void *test_data; + uint16_t index_ut; + uint16_t index_lt; + struct bt_hci *hci_ut; /* Upper Tester / IUT */ + struct bt_hci *hci_lt; /* Lower Tester / Reference */ + + uint8_t bdaddr_ut[6]; + uint8_t bdaddr_lt[6]; + uint16_t handle_ut; +}; + +struct le_keys { + uint8_t remote_sk[32]; + uint8_t local_pk[64]; +} key_test_data; + +static void swap_buf(const uint8_t *src, uint8_t *dst, uint16_t len) +{ + int i; + + for (i = 0; i < len; i++) + dst[len - 1 - i] = src[i]; +} + +static void test_debug(const char *str, void *user_data) +{ + tester_debug("%s", str); +} + +static void test_pre_setup_lt_address(const void *data, uint8_t size, + void *user_data) +{ + struct user_data *user = tester_get_data(); + const struct bt_hci_rsp_read_bd_addr *rsp = data; + + if (rsp->status) { + tester_warn("Read lower tester address failed (0x%02x)", + rsp->status); + tester_pre_setup_failed(); + return; + } + + memcpy(user->bdaddr_lt, rsp->bdaddr, 6); + + tester_pre_setup_complete(); +} + +static void test_pre_setup_lt_complete(const void *data, uint8_t size, + void *user_data) +{ + struct user_data *user = tester_get_data(); + uint8_t status = *((uint8_t *) data); + + if (status) { + tester_warn("Reset lower tester failed (0x%02x)", status); + tester_pre_setup_failed(); + return; + } + + if (!bt_hci_send(user->hci_lt, BT_HCI_CMD_READ_BD_ADDR, NULL, 0, + test_pre_setup_lt_address, NULL, NULL)) { + tester_warn("Failed to read lower tester address"); + tester_pre_setup_failed(); + return; + } +} + +static void test_pre_setup_ut_address(const void *data, uint8_t size, + void *user_data) +{ + struct user_data *user = tester_get_data(); + const struct bt_hci_rsp_read_bd_addr *rsp = data; + + if (rsp->status) { + tester_warn("Read upper tester address failed (0x%02x)", + rsp->status); + tester_pre_setup_failed(); + return; + } + + memcpy(user->bdaddr_ut, rsp->bdaddr, 6); + + user->hci_lt = bt_hci_new_user_channel(user->index_lt); + if (!user->hci_lt) { + tester_warn("Failed to setup lower tester user channel"); + tester_pre_setup_failed(); + return; + } + + if (!bt_hci_send(user->hci_lt, BT_HCI_CMD_RESET, NULL, 0, + test_pre_setup_lt_complete, NULL, NULL)) { + tester_warn("Failed to reset lower tester"); + tester_pre_setup_failed(); + return; + } +} + +static void test_pre_setup_ut_complete(const void *data, uint8_t size, + void *user_data) +{ + struct user_data *user = tester_get_data(); + uint8_t status = *((uint8_t *) data); + + if (status) { + tester_warn("Reset upper tester failed (0x%02x)", status); + tester_pre_setup_failed(); + return; + } + + if (user->index_lt == 0xffff) { + tester_pre_setup_complete(); + return; + } + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_READ_BD_ADDR, NULL, 0, + test_pre_setup_ut_address, NULL, NULL)) { + tester_warn("Failed to read upper tester address"); + tester_pre_setup_failed(); + return; + } +} + +static void test_pre_setup(const void *test_data) +{ + struct user_data *user = tester_get_data(); + + user->hci_ut = bt_hci_new_user_channel(user->index_ut); + if (!user->hci_ut) { + tester_warn("Failed to setup upper tester user channel"); + tester_pre_setup_failed(); + return; + } + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_RESET, NULL, 0, + test_pre_setup_ut_complete, NULL, NULL)) { + tester_warn("Failed to reset upper tester"); + tester_pre_setup_failed(); + return; + } +} + +static void test_post_teardown(const void *test_data) +{ + struct user_data *user = tester_get_data(); + + bt_hci_unref(user->hci_lt); + user->hci_lt = NULL; + + bt_hci_unref(user->hci_ut); + user->hci_ut = NULL; + + tester_post_teardown_complete(); +} + +static void user_data_free(void *data) +{ + struct user_data *user = data; + + free(user); +} + +#define test_hci(name, data, setup, func, teardown) \ + do { \ + struct user_data *user; \ + user = calloc(1, sizeof(struct user_data)); \ + if (!user) \ + break; \ + user->test_data = data; \ + user->index_ut = 0; \ + user->index_lt = 1; \ + tester_add_full(name, data, \ + test_pre_setup, setup, func, teardown, \ + test_post_teardown, 30, user, user_data_free); \ + } while (0) + +#define test_hci_local(name, data, setup, func) \ + do { \ + struct user_data *user; \ + user = calloc(1, sizeof(struct user_data)); \ + if (!user) \ + break; \ + user->test_data = data; \ + user->index_ut = 0; \ + user->index_lt = 0xffff; \ + tester_add_full(name, data, \ + test_pre_setup, setup, func, NULL, \ + test_post_teardown, 30, user, user_data_free); \ + } while (0) + +static void setup_features_complete(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_read_local_features *rsp = data; + + if (rsp->status) { + tester_warn("Failed to get HCI features (0x%02x)", rsp->status); + tester_setup_failed(); + return; + } + + tester_setup_complete(); +} + +static void setup_features(const void *test_data) +{ + struct user_data *user = tester_get_data(); + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_READ_LOCAL_FEATURES, NULL, 0, + setup_features_complete, NULL, NULL)) { + tester_warn("Failed to send HCI features command"); + tester_setup_failed(); + return; + } +} + +static void test_reset(const void *test_data) +{ + tester_test_passed(); +} + +static void test_command_complete(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + tester_warn("HCI command failed (0x%02x)", status); + tester_test_failed(); + return; + } + + tester_test_passed(); +} + +static void test_command(uint16_t opcode) +{ + struct user_data *user = tester_get_data(); + + if (!bt_hci_send(user->hci_ut, opcode, NULL, 0, + test_command_complete, NULL, NULL)) { + tester_warn("Failed to send HCI command 0x%04x", opcode); + tester_test_failed(); + return; + } +} + +static void test_read_local_version_information(const void *test_data) +{ + test_command(BT_HCI_CMD_READ_LOCAL_VERSION); +} + +static void test_read_local_supported_commands(const void *test_data) +{ + test_command(BT_HCI_CMD_READ_LOCAL_COMMANDS); +} + +static void test_read_local_supported_features(const void *test_data) +{ + test_command(BT_HCI_CMD_READ_LOCAL_FEATURES); +} + +static void test_local_extended_features_complete(const void *data, + uint8_t size, void *user_data) +{ + const struct bt_hci_rsp_read_local_ext_features *rsp = data; + + if (rsp->status) { + tester_warn("Failed to get HCI extended features (0x%02x)", + rsp->status); + tester_test_failed(); + return; + } + + tester_test_passed(); +} + +static void test_read_local_extended_features(const void *test_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_read_local_ext_features cmd; + + cmd.page = 0x00; + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_READ_LOCAL_EXT_FEATURES, + &cmd, sizeof(cmd), + test_local_extended_features_complete, + NULL, NULL)) { + tester_warn("Failed to send HCI extended features command"); + tester_test_failed(); + return; + } +} + +static void test_read_buffer_size(const void *test_data) +{ + test_command(BT_HCI_CMD_READ_BUFFER_SIZE); +} + +static void test_read_country_code(const void *test_data) +{ + test_command(BT_HCI_CMD_READ_COUNTRY_CODE); +} + +static void test_read_bd_addr(const void *test_data) +{ + test_command(BT_HCI_CMD_READ_BD_ADDR); +} + +static void test_read_local_supported_codecs(const void *test_data) +{ + test_command(BT_HCI_CMD_READ_LOCAL_CODECS); +} + +static void test_le_read_white_list_size(const void *test_data) +{ + test_command(BT_HCI_CMD_LE_READ_WHITE_LIST_SIZE); +} + +static void test_le_clear_white_list(const void *test_data) +{ + test_command(BT_HCI_CMD_LE_CLEAR_WHITE_LIST); +} + +static void test_le_encrypt_complete(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_rsp_le_encrypt *rsp = data; + uint8_t sample[16] = { + 0x7d, 0xf7, 0x6b, 0x0c, 0x1a, 0xb8, 0x99, 0xb3, + 0x3e, 0x42, 0xf0, 0x47, 0xb9, 0x1b, 0x54, 0x6f + }; + uint8_t enc_data[16]; + + if (rsp->status) { + tester_warn("Failed HCI LE Encrypt (0x%02x)", rsp->status); + tester_test_failed(); + return; + } + + swap_buf(rsp->data, enc_data, 16); + util_hexdump('>', enc_data, 16, test_debug, NULL); + + if (!memcmp(sample, enc_data, 16)) + tester_test_passed(); + else + tester_test_failed(); +} + +/* Data are taken from RFC 4493 Test Vectors */ +static void test_le_encrypt(const void *test_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_le_encrypt cmd; + uint8_t key[16] = { + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c + }; + uint8_t plaintext[16] = { 0 }; + + /* Swap bytes since our interface has LE interface, opposed to + * common crypto interface + */ + swap_buf(key, cmd.key, 16); + swap_buf(plaintext, cmd.plaintext, 16); + + util_hexdump('<', cmd.key, 16, test_debug, NULL); + util_hexdump('<', cmd.plaintext, 16, test_debug, NULL); + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_LE_ENCRYPT, &cmd, sizeof(cmd), + test_le_encrypt_complete, NULL, NULL)) { + tester_warn("Failed to send HCI LE Encrypt command"); + tester_test_failed(); + return; + } + +} + +static void test_le_rand(const void *test_data) +{ + test_command(BT_HCI_CMD_LE_RAND); +} + +static void test_le_read_local_pk_complete(const void *data, uint8_t size, + void *user_data) +{ + const uint8_t *event = data; + const struct bt_hci_evt_le_read_local_pk256_complete *evt; + struct le_keys *keys = user_data; + + if (*event != BT_HCI_EVT_LE_READ_LOCAL_PK256_COMPLETE) { + tester_warn("Failed Read Local PK256 command"); + tester_test_failed(); + return; + } + + evt = (void *)(event + 1); + if (evt->status) { + tester_warn("HCI Read Local PK complete failed (0x%02x)", + evt->status); + tester_test_failed(); + return; + } + + memcpy(keys->local_pk, evt->local_pk256, 64); + + util_hexdump('>', evt->local_pk256, 64, test_debug, NULL); + + tester_test_passed(); +} + +static void test_le_read_local_pk_status(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + tester_warn("Failed to send Read Local PK256 cmd (0x%02x)", status); + tester_test_failed(); + return; + } +} + +static void test_le_read_local_pk(const void *test_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_set_event_mask sem; + struct bt_hci_cmd_le_set_event_mask lsem; + + bt_hci_register(user->hci_ut, BT_HCI_EVT_LE_META_EVENT, + test_le_read_local_pk_complete, + (void *)test_data, NULL); + + memset(sem.mask, 0, 8); + sem.mask[1] |= 0x20; /* Command Complete */ + sem.mask[1] |= 0x40; /* Command Status */ + sem.mask[7] |= 0x20; /* LE Meta */ + + bt_hci_send(user->hci_ut, BT_HCI_CMD_SET_EVENT_MASK, + &sem, sizeof(sem), NULL, NULL, NULL); + + memset(lsem.mask, 0, 8); + lsem.mask[0] |= 0x80; /* LE Read Local P-256 Public Key Complete */ + + bt_hci_send(user->hci_ut, BT_HCI_CMD_LE_SET_EVENT_MASK, + &lsem, sizeof(lsem), NULL, NULL, NULL); + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_LE_READ_LOCAL_PK256, NULL, + 0, test_le_read_local_pk_status, + NULL, NULL)) { + tester_warn("Failed to send HCI LE Read Local PK256 command"); + tester_test_failed(); + return; + } +} + +static void setup_le_read_local_pk_complete(const void *data, uint8_t size, + void *user_data) +{ + const uint8_t *event = data; + const struct bt_hci_evt_le_read_local_pk256_complete *evt; + struct le_keys *keys = user_data; + + if (*event != BT_HCI_EVT_LE_READ_LOCAL_PK256_COMPLETE) { + tester_warn("Failed Read Local PK256 command"); + tester_setup_failed(); + return; + } + + evt = (void *)(event + 1); + if (evt->status) { + tester_warn("HCI Read Local PK complete failed (0x%02x)", + evt->status); + tester_setup_failed(); + return; + } + + memcpy(keys->local_pk, evt->local_pk256, 64); + + util_hexdump('>', evt->local_pk256, 64, test_debug, NULL); + + tester_setup_complete(); +} + +static void setup_le_read_local_pk_status(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + tester_warn("Failed to send DHKey gen cmd (0x%02x)", status); + tester_setup_failed(); + return; + } +} + +static void setup_le_generate_dhkey(const void *test_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_set_event_mask sem; + struct bt_hci_cmd_le_set_event_mask lsem; + + bt_hci_register(user->hci_ut, BT_HCI_EVT_LE_META_EVENT, + setup_le_read_local_pk_complete, + (void *)test_data, NULL); + + memset(sem.mask, 0, 8); + sem.mask[1] |= 0x20; /* Command Complete */ + sem.mask[1] |= 0x40; /* Command Status */ + sem.mask[7] |= 0x20; /* LE Meta */ + + bt_hci_send(user->hci_ut, BT_HCI_CMD_SET_EVENT_MASK, + &sem, sizeof(sem), NULL, NULL, NULL); + + memset(lsem.mask, 0, 8); + lsem.mask[0] |= 0x80; /* LE Read Local P-256 Public Key Complete */ + lsem.mask[1] |= 0x01; /* LE Generate DHKey Complete */ + + bt_hci_send(user->hci_ut, BT_HCI_CMD_LE_SET_EVENT_MASK, + &lsem, sizeof(lsem), NULL, NULL, NULL); + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_LE_READ_LOCAL_PK256, NULL, + 0, setup_le_read_local_pk_status, + NULL, NULL)) { + tester_warn("Failed to send HCI LE Read Local PK256 command"); + tester_setup_failed(); + return; + } +} + +static void test_le_generate_dhkey_complete(const void *data, uint8_t size, + void *user_data) +{ + const uint8_t *event = data; + const struct bt_hci_evt_le_generate_dhkey_complete *evt; + struct le_keys *keys = user_data; + uint8_t dhkey[32]; + + if (*event != BT_HCI_EVT_LE_GENERATE_DHKEY_COMPLETE) { + tester_warn("Failed DHKey generation command"); + tester_test_failed(); + return; + } + + evt = (void *)(event + 1); + if (evt->status) { + tester_warn("HCI Generate DHKey complete failed (0x%02x)", + evt->status); + tester_test_failed(); + return; + } + + util_hexdump('>', evt->dhkey, 32, test_debug, NULL); + + + util_hexdump('S', keys->remote_sk, 32, test_debug, NULL); + util_hexdump('P', keys->local_pk, 64, test_debug, NULL); + + /* Generate DHKey ourself with local public key and remote + * private key we got when generated public / private key + * pair for BT_HCI_CMD_LE_GENERATE_DHKEY argument. + */ + ecdh_shared_secret(keys->local_pk, keys->remote_sk, dhkey); + + util_hexdump('D', dhkey, 32, test_debug, NULL); + + if (!memcmp(dhkey, evt->dhkey, 32)) + tester_test_passed(); + else + tester_test_failed(); +} + +static void test_le_generate_dhkey_status(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + tester_warn("Failed to send DHKey gen cmd (0x%02x)", status); + tester_test_failed(); + return; + } +} + +static void test_le_generate_dhkey(const void *test_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_le_generate_dhkey cmd; + struct le_keys *keys = (void *)test_data; + + ecc_make_key(cmd.remote_pk256, keys->remote_sk); + + /* Unregister handler for META event */ + bt_hci_unregister(user->hci_ut, 1); + + bt_hci_register(user->hci_ut, BT_HCI_EVT_LE_META_EVENT, + test_le_generate_dhkey_complete, keys, + NULL); + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_LE_GENERATE_DHKEY, &cmd, + sizeof(cmd), test_le_generate_dhkey_status, + NULL, NULL)) { + tester_warn("Failed to send HCI LE Encrypt command"); + tester_test_failed(); + return; + } + +} + +static void test_inquiry_complete(const void *data, uint8_t size, + void *user_data) +{ + const struct bt_hci_evt_inquiry_complete *evt = data; + + if (evt->status) { + tester_warn("HCI inquiry complete failed (0x%02x)", + evt->status); + tester_test_failed(); + return; + } + + tester_test_passed(); +} + +static void test_inquiry_status(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + tester_warn("HCI inquiry command failed (0x%02x)", status); + tester_test_failed(); + return; + } +} + +static void test_inquiry_liac(const void *test_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_inquiry cmd; + + bt_hci_register(user->hci_ut, BT_HCI_EVT_INQUIRY_COMPLETE, + test_inquiry_complete, NULL, NULL); + + cmd.lap[0] = 0x00; + cmd.lap[1] = 0x8b; + cmd.lap[2] = 0x9e; + cmd.length = 0x08; + cmd.num_resp = 0x00; + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_INQUIRY, &cmd, sizeof(cmd), + test_inquiry_status, NULL, NULL)) { + tester_warn("Failed to send HCI inquiry command"); + tester_test_failed(); + return; + } +} + +static void setup_lt_connectable_complete(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + tester_warn("Failed to set HCI scan enable (0x%02x)", status); + tester_setup_failed(); + return; + } + + tester_setup_complete(); +} + +static void setup_lt_connect_request_accept(const void *data, uint8_t size, + void *user_data) +{ + struct user_data *user = tester_get_data(); + const struct bt_hci_evt_conn_request *evt = data; + struct bt_hci_cmd_accept_conn_request cmd; + + memcpy(cmd.bdaddr, evt->bdaddr, 6); + cmd.role = 0x01; + + if (!bt_hci_send(user->hci_lt, BT_HCI_CMD_ACCEPT_CONN_REQUEST, + &cmd, sizeof(cmd), NULL, NULL, NULL)) { + tester_warn("Failed to send HCI accept connection command"); + return; + } +} + +static void setup_lt_connectable(const void *test_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_write_scan_enable cmd; + + bt_hci_register(user->hci_lt, BT_HCI_EVT_CONN_REQUEST, + setup_lt_connect_request_accept, NULL, NULL); + + cmd.enable = 0x02; + + if (!bt_hci_send(user->hci_lt, BT_HCI_CMD_WRITE_SCAN_ENABLE, + &cmd, sizeof(cmd), + setup_lt_connectable_complete, NULL, NULL)) { + tester_warn("Failed to send HCI scan enable command"); + tester_setup_failed(); + return; + } +} + +static void test_create_connection_disconnect(void *user_data) +{ + tester_test_passed(); +} + +static void test_create_connection_complete(const void *data, uint8_t size, + void *user_data) +{ + struct user_data *user = tester_get_data(); + const struct bt_hci_evt_conn_complete *evt = data; + + if (evt->status) { + tester_warn("HCI create connection complete failed (0x%02x)", + evt->status); + tester_test_failed(); + return; + } + + user->handle_ut = le16_to_cpu(evt->handle); + + tester_wait(2, test_create_connection_disconnect, NULL); +} + +static void test_create_connection_status(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + tester_warn("HCI create connection command failed (0x%02x)", + status); + tester_test_failed(); + return; + } +} + +static void test_create_connection(const void *test_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_create_conn cmd; + + bt_hci_register(user->hci_ut, BT_HCI_EVT_CONN_COMPLETE, + test_create_connection_complete, NULL, NULL); + + memcpy(cmd.bdaddr, user->bdaddr_lt, 6); + cmd.pkt_type = cpu_to_le16(0x0008); + cmd.pscan_rep_mode = 0x02; + cmd.pscan_mode = 0x00; + cmd.clock_offset = cpu_to_le16(0x0000); + cmd.role_switch = 0x01; + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_CREATE_CONN, + &cmd, sizeof(cmd), + test_create_connection_status, + NULL, NULL)) { + tester_warn("Failed to send HCI create connection command"); + tester_test_failed(); + return; + } +} + +static void teardown_timeout(void *user_data) +{ + tester_teardown_complete(); +} + +static void teardown_disconnect_status(const void *data, uint8_t size, + void *user_data) +{ + uint8_t status = *((uint8_t *) data); + + if (status) { + tester_warn("HCI disconnect failed (0x%02x)", status); + tester_teardown_failed(); + return; + } + + tester_wait(1, teardown_timeout, NULL); +} + +static void teardown_connection(const void *test_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_disconnect cmd; + + cmd.handle = cpu_to_le16(user->handle_ut); + cmd.reason = 0x13; + + if (!bt_hci_send(user->hci_ut, BT_HCI_CMD_DISCONNECT, + &cmd, sizeof(cmd), + teardown_disconnect_status, + NULL, NULL)) { + tester_warn("Failed to send HCI disconnect command"); + tester_test_failed(); + return; + } +} + +static void test_adv_report(const void *data, uint8_t size, void *user_data) +{ + struct user_data *user = tester_get_data(); + uint8_t subevent = *((uint8_t *) data); + const struct bt_hci_evt_le_adv_report *lar = data + 1; + + switch (subevent) { + case BT_HCI_EVT_LE_ADV_REPORT: + if (!memcmp(lar->addr, user->bdaddr_ut, 6)) + tester_setup_complete(); + break; + } +} + +static void setup_advertising_initiated(const void *test_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_set_event_mask sem; + struct bt_hci_cmd_le_set_event_mask lsem; + struct bt_hci_cmd_le_set_scan_enable lsse; + struct bt_hci_cmd_le_set_adv_parameters lsap; + struct bt_hci_cmd_le_set_adv_enable lsae; + + bt_hci_register(user->hci_lt, BT_HCI_EVT_LE_META_EVENT, + test_adv_report, NULL, NULL); + + memset(sem.mask, 0, 8); + sem.mask[1] |= 0x20; /* Command Complete */ + sem.mask[1] |= 0x40; /* Command Status */ + sem.mask[7] |= 0x20; /* LE Meta */ + + bt_hci_send(user->hci_lt, BT_HCI_CMD_SET_EVENT_MASK, + &sem, sizeof(sem), NULL, NULL, NULL); + + memset(lsem.mask, 0, 8); + lsem.mask[0] |= 0x02; /* LE Advertising Report */ + + bt_hci_send(user->hci_lt, BT_HCI_CMD_LE_SET_EVENT_MASK, + &lsem, sizeof(lsem), NULL, NULL, NULL); + + lsse.enable = 0x01; + lsse.filter_dup = 0x00; + + bt_hci_send(user->hci_lt, BT_HCI_CMD_LE_SET_SCAN_ENABLE, + &lsse, sizeof(lsse), NULL, NULL, NULL); + + lsap.min_interval = cpu_to_le16(0x0800); + lsap.max_interval = cpu_to_le16(0x0800); + lsap.type = 0x03; + lsap.own_addr_type = 0x00; + lsap.direct_addr_type = 0x00; + memset(lsap.direct_addr, 0, 6); + lsap.channel_map = 0x07; + lsap.filter_policy = 0x00; + + bt_hci_send(user->hci_ut, BT_HCI_CMD_LE_SET_ADV_PARAMETERS, + &lsap, sizeof(lsap), NULL, NULL, NULL); + + lsae.enable = 0x01; + + bt_hci_send(user->hci_ut, BT_HCI_CMD_LE_SET_ADV_ENABLE, + &lsae, sizeof(lsae), NULL, NULL, NULL); +} + +static void test_reset_in_advertising_state_timeout(void *user_data) +{ + struct user_data *user = tester_get_data(); + struct bt_hci_cmd_le_set_adv_enable lsae; + struct bt_hci_cmd_le_set_scan_enable lsse; + + lsae.enable = 0x00; + + bt_hci_send(user->hci_ut, BT_HCI_CMD_LE_SET_ADV_ENABLE, + &lsae, sizeof(lsae), NULL, NULL, NULL); + + lsse.enable = 0x00; + lsse.filter_dup = 0x00; + + bt_hci_send(user->hci_lt, BT_HCI_CMD_LE_SET_SCAN_ENABLE, + &lsse, sizeof(lsse), NULL, NULL, NULL); + + tester_test_passed(); +} + +static void test_reset_in_advertising_state(const void *test_data) +{ + struct user_data *user = tester_get_data(); + + bt_hci_send(user->hci_ut, BT_HCI_CMD_RESET, NULL, 0, NULL, NULL, NULL); + + tester_wait(5, test_reset_in_advertising_state_timeout, NULL); +} + +int main(int argc, char *argv[]) +{ + tester_init(&argc, &argv); + + test_hci_local("Reset", NULL, NULL, test_reset); + + test_hci_local("Read Local Version Information", NULL, NULL, + test_read_local_version_information); + test_hci_local("Read Local Supported Commands", NULL, NULL, + test_read_local_supported_commands); + test_hci_local("Read Local Supported Features", NULL, NULL, + test_read_local_supported_features); + test_hci_local("Read Local Extended Features", NULL, + setup_features, + test_read_local_extended_features); + test_hci_local("Read Buffer Size", NULL, NULL, + test_read_buffer_size); + test_hci_local("Read Country Code", NULL, NULL, + test_read_country_code); + test_hci_local("Read BD_ADDR", NULL, NULL, + test_read_bd_addr); + test_hci_local("Read Local Supported Codecs", NULL, NULL, + test_read_local_supported_codecs); + + test_hci_local("LE Read White List Size", NULL, NULL, + test_le_read_white_list_size); + test_hci_local("LE Clear White List", NULL, NULL, + test_le_clear_white_list); + test_hci_local("LE Encrypt", NULL, NULL, + test_le_encrypt); + test_hci_local("LE Rand", NULL, NULL, + test_le_rand); + test_hci_local("LE Read Local PK", &key_test_data, NULL, + test_le_read_local_pk); + test_hci_local("LE Generate DHKey", &key_test_data, + setup_le_generate_dhkey, + test_le_generate_dhkey); + + test_hci_local("Inquiry (LIAC)", NULL, NULL, test_inquiry_liac); + + test_hci("Create Connection", NULL, + setup_lt_connectable, + test_create_connection, + teardown_connection); + + test_hci("TP/DSU/BV-02-C Reset in Advertising State", NULL, + setup_advertising_initiated, + test_reset_in_advertising_state, NULL); + + return tester_run(); +} diff --git a/tools/hciattach.1 b/tools/hciattach.1 new file mode 100644 index 0000000..d506034 --- /dev/null +++ b/tools/hciattach.1 @@ -0,0 +1,158 @@ +.TH HCIATTACH 1 "Jan 22 2002" BlueZ "Linux System Administration" +.SH NAME +hciattach \- attach serial devices via UART HCI to BlueZ stack +.SH SYNOPSIS +.B hciattach +.RB [\| \-b \|] +.RB [\| \-n \|] +.RB [\| \-p \|] +.RB [\| \-t +.IR timeout \|] +.RB [\| \-s +.IR speed \|] +.RB [\| \-l \|] +.RB [\| \-r \|] +.I tty +.IR type \||\| id +.I speed +.I flow +.I bdaddr +.SH DESCRIPTION +.LP +Hciattach is used to attach a serial UART to the Bluetooth stack as HCI +transport interface. +.SH OPTIONS +.TP +.B \-b +Send break. +.TP +.B \-n +Don't detach from controlling terminal. +.TP +.B \-p +Print the PID when detaching. +.TP +.BI \-t " timeout" +Specify an initialization timeout. (Default is 5 seconds.) +.TP +.BI \-s " speed" +Specify an initial speed instead of the hardware default. +.TP +.B \-l +List all available configurations. +.TP +.B \-r +Set the HCI device into raw mode (the kernel and bluetoothd will ignore it). +.TP +.I tty +This specifies the serial device to attach. A leading +.B /dev +can be omitted. Examples: +.B /dev/ttyS1 +.B ttyS2 +.TP +.IR type \||\| id +The +.I type +or +.I id +of the Bluetooth device that is to be attached, i.e. vendor or other device +specific identifier. Currently supported types are +.RS +.TP +.B type +.B description +.TP +.B any +Unspecified HCI_UART interface, no vendor specific options +.TP +.B ericsson +Ericsson based modules +.TP +.B digi +Digianswer based cards +.TP +.B xircom +Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter +.TP +.B csr +CSR Casira serial adapter or BrainBoxes serial dongle (BL642) +.TP +.B bboxes +BrainBoxes PCMCIA card (BL620) +.TP +.B swave +Silicon Wave kits +.TP +.B bcsp +Serial adapters using CSR chips with BCSP serial protocol +.TP +.B ath3k +Atheros AR300x based serial Bluetooth device +.TP +.B intel +Intel Bluetooth device +.RE + +Supported IDs are (manufacturer id, product id) +.RS +.TP +.B 0x0105, 0x080a +Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter +.TP +.B 0x0160, 0x0002 +BrainBoxes PCMCIA card (BL620) +.RE + +.TP +.I speed +The +.I speed +specifies the UART speed to use. Baudrates higher than 115.200bps require +vendor specific initializations that are not implemented for all types of +devices. In general the following speeds are supported: + +.B 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 + +Supported vendor devices are automatically initialised to their respective +best settings. +.TP +.I flow +If the keyword +.I flow +is appended to the list of options then hardware flow control is forced on +the serial link ( +.B CRTSCTS +). All above mentioned device types have +.B flow +set by default. To force no flow control use +.B noflow +instead. +.TP +.I sleep +Enables hardware specific power management feature. If +.I sleep +is appended to the list of options then this feature is enabled. To disable +this feature use +.B nosleep +instead. +All above mentioned device types have +.B nosleep +set by default. + +Note: This option will only be valid for hardware which support +hardware specific power management enable option from host. +.TP +.I bdaddr +The +.I bdaddr +specifies the Bluetooth Address to use. Some devices (like the STLC2500) +do not store the Bluetooth address in hardware memory. Instead it must +be uploaded during the initialization process. If this argument +is specified, then the address will be used to initialize the device. +Otherwise, a default address will be used. + +.SH AUTHORS +Written by Maxim Krasnyansky +.PP +Manual page by Nils Faerber diff --git a/tools/hciattach.c b/tools/hciattach.c new file mode 100644 index 0000000..9a02173 --- /dev/null +++ b/tools/hciattach.c @@ -0,0 +1,1438 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "src/shared/tty.h" + +#include "hciattach.h" + +struct uart_t { + char *type; + int m_id; + int p_id; + int proto; + int init_speed; + int speed; + int flags; + int pm; + char *bdaddr; + int (*init) (int fd, struct uart_t *u, struct termios *ti); + int (*post) (int fd, struct uart_t *u, struct termios *ti); +}; + +#define FLOW_CTL 0x0001 +#define AMP_DEV 0x0002 +#define ENABLE_PM 1 +#define DISABLE_PM 0 + +static volatile sig_atomic_t __io_canceled = 0; + +static void sig_hup(int sig) +{ +} + +static void sig_term(int sig) +{ + __io_canceled = 1; +} + +static void sig_alarm(int sig) +{ + fprintf(stderr, "Initialization timed out.\n"); + exit(1); +} + +int set_speed(int fd, struct termios *ti, int speed) +{ + if (cfsetospeed(ti, tty_get_speed(speed)) < 0) + return -errno; + + if (cfsetispeed(ti, tty_get_speed(speed)) < 0) + return -errno; + + if (tcsetattr(fd, TCSANOW, ti) < 0) + return -errno; + + return 0; +} + +/* + * Read an HCI event from the given file descriptor. + */ +int read_hci_event(int fd, unsigned char* buf, int size) +{ + int remain, r; + int count = 0; + + if (size <= 0) + return -1; + + /* The first byte identifies the packet type. For HCI event packets, it + * should be 0x04, so we read until we get to the 0x04. */ + while (1) { + r = read(fd, buf, 1); + if (r <= 0) + return -1; + if (buf[0] == 0x04) + break; + } + count++; + + /* The next two bytes are the event code and parameter total length. */ + while (count < 3) { + r = read(fd, buf + count, 3 - count); + if (r <= 0) + return -1; + count += r; + } + + /* Now we read the parameters. */ + if (buf[2] < (size - 3)) + remain = buf[2]; + else + remain = size - 3; + + while ((count - 3) < remain) { + r = read(fd, buf + count, remain - (count - 3)); + if (r <= 0) + return -1; + count += r; + } + + return count; +} + +/* + * Ericsson specific initialization + */ +static int ericsson(int fd, struct uart_t *u, struct termios *ti) +{ + struct timespec tm = {0, 50000}; + char cmd[5]; + + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x09; + cmd[2] = 0xfc; + cmd[3] = 0x01; + + switch (u->speed) { + case 57600: + cmd[4] = 0x03; + break; + case 115200: + cmd[4] = 0x02; + break; + case 230400: + cmd[4] = 0x01; + break; + case 460800: + cmd[4] = 0x00; + break; + case 921600: + cmd[4] = 0x20; + break; + case 2000000: + cmd[4] = 0x25; + break; + case 3000000: + cmd[4] = 0x27; + break; + case 4000000: + cmd[4] = 0x2B; + break; + default: + cmd[4] = 0x03; + u->speed = 57600; + fprintf(stderr, "Invalid speed requested, using %d bps instead\n", u->speed); + break; + } + + /* Send initialization command */ + if (write(fd, cmd, 5) != 5) { + perror("Failed to write init command"); + return -1; + } + + nanosleep(&tm, NULL); + return 0; +} + +/* + * Digianswer specific initialization + */ +static int digi(int fd, struct uart_t *u, struct termios *ti) +{ + struct timespec tm = {0, 50000}; + char cmd[5]; + + /* DigiAnswer set baud rate command */ + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x07; + cmd[2] = 0xfc; + cmd[3] = 0x01; + + switch (u->speed) { + case 57600: + cmd[4] = 0x08; + break; + case 115200: + cmd[4] = 0x09; + break; + default: + cmd[4] = 0x09; + u->speed = 115200; + break; + } + + /* Send initialization command */ + if (write(fd, cmd, 5) != 5) { + perror("Failed to write init command"); + return -1; + } + + nanosleep(&tm, NULL); + return 0; +} + +static int texas(int fd, struct uart_t *u, struct termios *ti) +{ + return texas_init(fd, &u->speed, ti); +} + +static int texas2(int fd, struct uart_t *u, struct termios *ti) +{ + return texas_post(fd, ti); +} + +static int texasalt(int fd, struct uart_t *u, struct termios *ti) +{ + return texasalt_init(fd, u->speed, ti); +} + +static int ath3k_ps(int fd, struct uart_t *u, struct termios *ti) +{ + return ath3k_init(fd, u->speed, u->init_speed, u->bdaddr, ti); +} + +static int ath3k_pm(int fd, struct uart_t *u, struct termios *ti) +{ + return ath3k_post(fd, u->pm); +} + +static int qualcomm(int fd, struct uart_t *u, struct termios *ti) +{ + return qualcomm_init(fd, u->speed, ti, u->bdaddr); +} + +static int intel(int fd, struct uart_t *u, struct termios *ti) +{ + return intel_init(fd, u->init_speed, &u->speed, ti); +} + +static int bcm43xx(int fd, struct uart_t *u, struct termios *ti) +{ + return bcm43xx_init(fd, u->init_speed, u->speed, ti, u->bdaddr); +} + +static int read_check(int fd, void *buf, int count) +{ + int res; + + do { + res = read(fd, buf, count); + if (res != -1) { + buf += res; + count -= res; + } + } while (count && (errno == 0 || errno == EINTR)); + + if (count) + return -1; + + return 0; +} + +/* + * BCSP specific initialization + */ +static int serial_fd; +static int bcsp_max_retries = 10; + +static void bcsp_tshy_sig_alarm(int sig) +{ + unsigned char bcsp_sync_pkt[10] = {0xc0,0x00,0x41,0x00,0xbe,0xda,0xdc,0xed,0xed,0xc0}; + static int retries = 0; + + if (retries < bcsp_max_retries) { + retries++; + if (write(serial_fd, &bcsp_sync_pkt, 10) < 0) + return; + alarm(1); + return; + } + + tcflush(serial_fd, TCIOFLUSH); + fprintf(stderr, "BCSP initialization timed out\n"); + exit(1); +} + +static void bcsp_tconf_sig_alarm(int sig) +{ + unsigned char bcsp_conf_pkt[10] = {0xc0,0x00,0x41,0x00,0xbe,0xad,0xef,0xac,0xed,0xc0}; + static int retries = 0; + + if (retries < bcsp_max_retries){ + retries++; + if (write(serial_fd, &bcsp_conf_pkt, 10) < 0) + return; + alarm(1); + return; + } + + tcflush(serial_fd, TCIOFLUSH); + fprintf(stderr, "BCSP initialization timed out\n"); + exit(1); +} + +static int bcsp(int fd, struct uart_t *u, struct termios *ti) +{ + unsigned char byte, bcsph[4], bcspp[4], + bcsp_sync_resp_pkt[10] = {0xc0,0x00,0x41,0x00,0xbe,0xac,0xaf,0xef,0xee,0xc0}, + bcsp_conf_resp_pkt[10] = {0xc0,0x00,0x41,0x00,0xbe,0xde,0xad,0xd0,0xd0,0xc0}, + bcspsync[4] = {0xda, 0xdc, 0xed, 0xed}, + bcspsyncresp[4] = {0xac,0xaf,0xef,0xee}, + bcspconf[4] = {0xad,0xef,0xac,0xed}, + bcspconfresp[4] = {0xde,0xad,0xd0,0xd0}; + struct sigaction sa; + int len; + + if (set_speed(fd, ti, u->speed) < 0) { + perror("Can't set default baud rate"); + return -1; + } + + ti->c_cflag |= PARENB; + ti->c_cflag &= ~(PARODD); + + if (tcsetattr(fd, TCSANOW, ti) < 0) { + perror("Can't set port settings"); + return -1; + } + + alarm(0); + + serial_fd = fd; + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = bcsp_tshy_sig_alarm; + sigaction(SIGALRM, &sa, NULL); + + /* State = shy */ + + bcsp_tshy_sig_alarm(0); + while (1) { + do { + if (read_check(fd, &byte, 1) == -1){ + perror("Failed to read"); + return -1; + } + } while (byte != 0xC0); + + do { + if ( read_check(fd, &bcsph[0], 1) == -1){ + perror("Failed to read"); + return -1; + } + } while (bcsph[0] == 0xC0); + + if ( read_check(fd, &bcsph[1], 3) == -1){ + perror("Failed to read"); + return -1; + } + + if (((bcsph[0] + bcsph[1] + bcsph[2]) & 0xFF) != (unsigned char)~bcsph[3]) + continue; + if (bcsph[1] != 0x41 || bcsph[2] != 0x00) + continue; + + if (read_check(fd, &bcspp, 4) == -1){ + perror("Failed to read"); + return -1; + } + + if (!memcmp(bcspp, bcspsync, 4)) { + if (write(fd, &bcsp_sync_resp_pkt,10) < 0) + return -1; + } else if (!memcmp(bcspp, bcspsyncresp, 4)) + break; + } + + /* State = curious */ + + alarm(0); + sa.sa_handler = bcsp_tconf_sig_alarm; + sigaction(SIGALRM, &sa, NULL); + alarm(1); + + while (1) { + do { + if (read_check(fd, &byte, 1) == -1){ + perror("Failed to read"); + return -1; + } + } while (byte != 0xC0); + + do { + if (read_check(fd, &bcsph[0], 1) == -1){ + perror("Failed to read"); + return -1; + } + } while (bcsph[0] == 0xC0); + + if (read_check(fd, &bcsph[1], 3) == -1){ + perror("Failed to read"); + return -1; + } + + if (((bcsph[0] + bcsph[1] + bcsph[2]) & 0xFF) != (unsigned char)~bcsph[3]) + continue; + + if (bcsph[1] != 0x41 || bcsph[2] != 0x00) + continue; + + if (read_check(fd, &bcspp, 4) == -1){ + perror("Failed to read"); + return -1; + } + + if (!memcmp(bcspp, bcspsync, 4)) + len = write(fd, &bcsp_sync_resp_pkt, 10); + else if (!memcmp(bcspp, bcspconf, 4)) + len = write(fd, &bcsp_conf_resp_pkt, 10); + else if (!memcmp(bcspp, bcspconfresp, 4)) + break; + else + continue; + + if (len < 0) + return -errno; + } + + /* State = garrulous */ + + return 0; +} + +/* + * CSR specific initialization + * Inspired strongly by code in OpenBT and experimentations with Brainboxes + * Pcmcia card. + * Jean Tourrilhes - 14.11.01 + */ +static int csr(int fd, struct uart_t *u, struct termios *ti) +{ + struct timespec tm = {0, 10000000}; /* 10ms - be generous */ + unsigned char cmd[30]; /* Command */ + unsigned char resp[30]; /* Response */ + int clen = 0; /* Command len */ + static int csr_seq = 0; /* Sequence number of command */ + int divisor; + + /* It seems that if we set the CSR UART speed straight away, it + * won't work, the CSR UART gets into a state where we can't talk + * to it anymore. + * On the other hand, doing a read before setting the CSR speed + * seems to be ok. + * Therefore, the strategy is to read the build ID (useful for + * debugging) and only then set the CSR UART speed. Doing like + * this is more complex but at least it works ;-) + * The CSR UART control may be slow to wake up or something because + * every time I read its speed, its bogus... + * Jean II */ + + /* Try to read the build ID of the CSR chip */ + clen = 5 + (5 + 6) * 2; + /* HCI header */ + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x00; /* CSR command */ + cmd[2] = 0xfc; /* MANUFACTURER_SPEC */ + cmd[3] = 1 + (5 + 6) * 2; /* len */ + /* CSR MSG header */ + cmd[4] = 0xC2; /* first+last+channel=BCC */ + /* CSR BCC header */ + cmd[5] = 0x00; /* type = GET-REQ */ + cmd[6] = 0x00; /* - msB */ + cmd[7] = 5 + 4; /* len */ + cmd[8] = 0x00; /* - msB */ + cmd[9] = csr_seq & 0xFF;/* seq num */ + cmd[10] = (csr_seq >> 8) & 0xFF; /* - msB */ + csr_seq++; + cmd[11] = 0x19; /* var_id = CSR_CMD_BUILD_ID */ + cmd[12] = 0x28; /* - msB */ + cmd[13] = 0x00; /* status = STATUS_OK */ + cmd[14] = 0x00; /* - msB */ + /* CSR BCC payload */ + memset(cmd + 15, 0, 6 * 2); + + /* Send command */ + do { + if (write(fd, cmd, clen) != clen) { + perror("Failed to write init command (GET_BUILD_ID)"); + return -1; + } + + /* Read reply. */ + if (read_hci_event(fd, resp, 100) < 0) { + perror("Failed to read init response (GET_BUILD_ID)"); + return -1; + } + + /* Event code 0xFF is for vendor-specific events, which is + * what we're looking for. */ + } while (resp[1] != 0xFF); + +#ifdef CSR_DEBUG + { + char temp[512]; + int i; + for (i=0; i < rlen; i++) + sprintf(temp + (i*3), "-%02X", resp[i]); + fprintf(stderr, "Reading CSR build ID %d [%s]\n", rlen, temp + 1); + // In theory, it should look like : + // 04-FF-13-FF-01-00-09-00-00-00-19-28-00-00-73-00-00-00-00-00-00-00 + } +#endif + /* Display that to user */ + fprintf(stderr, "CSR build ID 0x%02X-0x%02X\n", + resp[15] & 0xFF, resp[14] & 0xFF); + + /* Try to read the current speed of the CSR chip */ + clen = 5 + (5 + 4)*2; + /* -- HCI header */ + cmd[3] = 1 + (5 + 4)*2; /* len */ + /* -- CSR BCC header -- */ + cmd[9] = csr_seq & 0xFF; /* seq num */ + cmd[10] = (csr_seq >> 8) & 0xFF; /* - msB */ + csr_seq++; + cmd[11] = 0x02; /* var_id = CONFIG_UART */ + cmd[12] = 0x68; /* - msB */ + +#ifdef CSR_DEBUG + /* Send command */ + do { + if (write(fd, cmd, clen) != clen) { + perror("Failed to write init command (GET_BUILD_ID)"); + return -1; + } + + /* Read reply. */ + if (read_hci_event(fd, resp, 100) < 0) { + perror("Failed to read init response (GET_BUILD_ID)"); + return -1; + } + + /* Event code 0xFF is for vendor-specific events, which is + * what we're looking for. */ + } while (resp[1] != 0xFF); + + { + char temp[512]; + int i; + for (i=0; i < rlen; i++) + sprintf(temp + (i*3), "-%02X", resp[i]); + fprintf(stderr, "Reading CSR UART speed %d [%s]\n", rlen, temp+1); + } +#endif + + if (u->speed > 1500000) { + fprintf(stderr, "Speed %d too high. Remaining at %d baud\n", + u->speed, u->init_speed); + u->speed = u->init_speed; + } else if (!tty_get_speed(u->speed)) { + /* Unknown speed. Why oh why can't we just pass an int to the kernel? */ + fprintf(stderr, "Speed %d unrecognised. Remaining at %d baud\n", + u->speed, u->init_speed); + u->speed = u->init_speed; + } + if (u->speed == u->init_speed) + return 0; + + /* Now, create the command that will set the UART speed */ + /* CSR BCC header */ + cmd[5] = 0x02; /* type = SET-REQ */ + cmd[6] = 0x00; /* - msB */ + cmd[9] = csr_seq & 0xFF; /* seq num */ + cmd[10] = (csr_seq >> 8) & 0xFF;/* - msB */ + csr_seq++; + + divisor = (u->speed*64+7812)/15625; + + /* No parity, one stop bit -> divisor |= 0x0000; */ + cmd[15] = (divisor) & 0xFF; /* divider */ + cmd[16] = (divisor >> 8) & 0xFF; /* - msB */ + /* The rest of the payload will be 0x00 */ + +#ifdef CSR_DEBUG + { + char temp[512]; + int i; + for(i = 0; i < clen; i++) + sprintf(temp + (i*3), "-%02X", cmd[i]); + fprintf(stderr, "Writing CSR UART speed %d [%s]\n", clen, temp + 1); + // In theory, it should look like : + // 01-00-FC-13-C2-02-00-09-00-03-00-02-68-00-00-BF-0E-00-00-00-00-00-00 + // 01-00-FC-13-C2-02-00-09-00-01-00-02-68-00-00-D8-01-00-00-00-00-00-00 + } +#endif + + /* Send the command to set the CSR UART speed */ + if (write(fd, cmd, clen) != clen) { + perror("Failed to write init command (SET_UART_SPEED)"); + return -1; + } + + nanosleep(&tm, NULL); + return 0; +} + +/* + * Silicon Wave specific initialization + * Thomas Moser + */ +static int swave(int fd, struct uart_t *u, struct termios *ti) +{ + struct timespec tm = { 0, 500000 }; + char cmd[10], rsp[100]; + int r; + + // Silicon Wave set baud rate command + // see HCI Vendor Specific Interface from Silicon Wave + // first send a "param access set" command to set the + // appropriate data fields in RAM. Then send a "HCI Reset + // Subcommand", e.g. "soft reset" to make the changes effective. + + cmd[0] = HCI_COMMAND_PKT; // it's a command packet + cmd[1] = 0x0B; // OCF 0x0B = param access set + cmd[2] = 0xfc; // OGF bx111111 = vendor specific + cmd[3] = 0x06; // 6 bytes of data following + cmd[4] = 0x01; // param sub command + cmd[5] = 0x11; // tag 17 = 0x11 = HCI Transport Params + cmd[6] = 0x03; // length of the parameter following + cmd[7] = 0x01; // HCI Transport flow control enable + cmd[8] = 0x01; // HCI Transport Type = UART + + switch (u->speed) { + case 19200: + cmd[9] = 0x03; + break; + case 38400: + cmd[9] = 0x02; + break; + case 57600: + cmd[9] = 0x01; + break; + case 115200: + cmd[9] = 0x00; + break; + default: + u->speed = 115200; + cmd[9] = 0x00; + break; + } + + /* Send initialization command */ + if (write(fd, cmd, 10) != 10) { + perror("Failed to write init command"); + return -1; + } + + // We should wait for a "GET Event" to confirm the success of + // the baud rate setting. Wait some time before reading. Better: + // read with timeout, parse data + // until correct answer, else error handling ... todo ... + + nanosleep(&tm, NULL); + + r = read(fd, rsp, sizeof(rsp)); + if (r > 0) { + // guess it's okay, but we should parse the reply. But since + // I don't react on an error anyway ... todo + // Response packet format: + // 04 Event + // FF Vendor specific + // 07 Parameter length + // 0B Subcommand + // 01 Setevent + // 11 Tag specifying HCI Transport Layer Parameter + // 03 length + // 01 flow on + // 01 Hci Transport type = Uart + // xx Baud rate set (see above) + } else { + // ups, got error. + return -1; + } + + // we probably got the reply. Now we must send the "soft reset" + // which is standard HCI RESET. + + cmd[0] = HCI_COMMAND_PKT; // it's a command packet + cmd[1] = 0x03; + cmd[2] = 0x0c; + cmd[3] = 0x00; + + /* Send reset command */ + if (write(fd, cmd, 4) != 4) { + perror("Can't write Silicon Wave reset cmd."); + return -1; + } + + nanosleep(&tm, NULL); + + // now the uart baud rate on the silicon wave module is set and effective. + // change our own baud rate as well. Then there is a reset event coming in + // on the *new* baud rate. This is *undocumented*! The packet looks like this: + // 04 FF 01 0B (which would make that a confirmation of 0x0B = "Param + // subcommand class". So: change to new baud rate, read with timeout, parse + // data, error handling. BTW: all param access in Silicon Wave is done this way. + // Maybe this code would belong in a separate file, or at least code reuse... + + return 0; +} + +/* + * ST Microelectronics specific initialization + * Marcel Holtmann + */ +static int st(int fd, struct uart_t *u, struct termios *ti) +{ + struct timespec tm = {0, 50000}; + char cmd[5]; + + /* ST Microelectronics set baud rate command */ + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x46; // OCF = Hci_Cmd_ST_Set_Uart_Baud_Rate + cmd[2] = 0xfc; // OGF = Vendor specific + cmd[3] = 0x01; + + switch (u->speed) { + case 9600: + cmd[4] = 0x09; + break; + case 19200: + cmd[4] = 0x0b; + break; + case 38400: + cmd[4] = 0x0d; + break; + case 57600: + cmd[4] = 0x0e; + break; + case 115200: + cmd[4] = 0x10; + break; + case 230400: + cmd[4] = 0x12; + break; + case 460800: + cmd[4] = 0x13; + break; + case 921600: + cmd[4] = 0x14; + break; + default: + cmd[4] = 0x10; + u->speed = 115200; + break; + } + + /* Send initialization command */ + if (write(fd, cmd, 5) != 5) { + perror("Failed to write init command"); + return -1; + } + + nanosleep(&tm, NULL); + return 0; +} + +static int stlc2500(int fd, struct uart_t *u, struct termios *ti) +{ + bdaddr_t bdaddr; + unsigned char resp[10]; + int n; + int rvalue; + + /* STLC2500 has an ericsson core */ + rvalue = ericsson(fd, u, ti); + if (rvalue != 0) + return rvalue; + +#ifdef STLC2500_DEBUG + fprintf(stderr, "Setting speed\n"); +#endif + if (set_speed(fd, ti, u->speed) < 0) { + perror("Can't set baud rate"); + return -1; + } + +#ifdef STLC2500_DEBUG + fprintf(stderr, "Speed set...\n"); +#endif + + /* Read reply */ + if ((n = read_hci_event(fd, resp, 10)) < 0) { + fprintf(stderr, "Failed to set baud rate on chip\n"); + return -1; + } + +#ifdef STLC2500_DEBUG + for (i = 0; i < n; i++) { + fprintf(stderr, "resp[%d] = %02x\n", i, resp[i]); + } +#endif + + str2ba(u->bdaddr, &bdaddr); + return stlc2500_init(fd, &bdaddr); +} + +static int bgb2xx(int fd, struct uart_t *u, struct termios *ti) +{ + bdaddr_t bdaddr; + + str2ba(u->bdaddr, &bdaddr); + + return bgb2xx_init(fd, &bdaddr); +} + +/* + * Broadcom specific initialization + * Extracted from Jungo openrg + */ +static int bcm2035(int fd, struct uart_t *u, struct termios *ti) +{ + int n; + unsigned char cmd[30], resp[30]; + + /* Reset the BT Chip */ + memset(cmd, 0, sizeof(cmd)); + memset(resp, 0, sizeof(resp)); + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x03; + cmd[2] = 0x0c; + cmd[3] = 0x00; + + /* Send command */ + if (write(fd, cmd, 4) != 4) { + fprintf(stderr, "Failed to write reset command\n"); + return -1; + } + + /* Read reply */ + if ((n = read_hci_event(fd, resp, 4)) < 0) { + fprintf(stderr, "Failed to reset chip\n"); + return -1; + } + + if (u->bdaddr != NULL) { + /* Set BD_ADDR */ + memset(cmd, 0, sizeof(cmd)); + memset(resp, 0, sizeof(resp)); + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x01; + cmd[2] = 0xfc; + cmd[3] = 0x06; + str2ba(u->bdaddr, (bdaddr_t *) (cmd + 4)); + + /* Send command */ + if (write(fd, cmd, 10) != 10) { + fprintf(stderr, "Failed to write BD_ADDR command\n"); + return -1; + } + + /* Read reply */ + if ((n = read_hci_event(fd, resp, 10)) < 0) { + fprintf(stderr, "Failed to set BD_ADDR\n"); + return -1; + } + } + + /* Read the local version info */ + memset(cmd, 0, sizeof(cmd)); + memset(resp, 0, sizeof(resp)); + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x01; + cmd[2] = 0x10; + cmd[3] = 0x00; + + /* Send command */ + if (write(fd, cmd, 4) != 4) { + fprintf(stderr, "Failed to write \"read local version\" " + "command\n"); + return -1; + } + + /* Read reply */ + if ((n = read_hci_event(fd, resp, 4)) < 0) { + fprintf(stderr, "Failed to read local version\n"); + return -1; + } + + /* Read the local supported commands info */ + memset(cmd, 0, sizeof(cmd)); + memset(resp, 0, sizeof(resp)); + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x02; + cmd[2] = 0x10; + cmd[3] = 0x00; + + /* Send command */ + if (write(fd, cmd, 4) != 4) { + fprintf(stderr, "Failed to write \"read local supported " + "commands\" command\n"); + return -1; + } + + /* Read reply */ + if ((n = read_hci_event(fd, resp, 4)) < 0) { + fprintf(stderr, "Failed to read local supported commands\n"); + return -1; + } + + /* Set the baud rate */ + memset(cmd, 0, sizeof(cmd)); + memset(resp, 0, sizeof(resp)); + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x18; + cmd[2] = 0xfc; + cmd[3] = 0x02; + switch (u->speed) { + case 57600: + cmd[4] = 0x00; + cmd[5] = 0xe6; + break; + case 230400: + cmd[4] = 0x22; + cmd[5] = 0xfa; + break; + case 460800: + cmd[4] = 0x22; + cmd[5] = 0xfd; + break; + case 921600: + cmd[4] = 0x55; + cmd[5] = 0xff; + break; + default: + /* Default is 115200 */ + cmd[4] = 0x00; + cmd[5] = 0xf3; + break; + } + fprintf(stderr, "Baud rate parameters: DHBR=0x%2x,DLBR=0x%2x\n", + cmd[4], cmd[5]); + + /* Send command */ + if (write(fd, cmd, 6) != 6) { + fprintf(stderr, "Failed to write \"set baud rate\" command\n"); + return -1; + } + + if ((n = read_hci_event(fd, resp, 6)) < 0) { + fprintf(stderr, "Failed to set baud rate\n"); + return -1; + } + + return 0; +} + +struct uart_t uart[] = { + { "any", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, NULL }, + + { "ericsson", 0x0000, 0x0000, HCI_UART_H4, 57600, 115200, + FLOW_CTL, DISABLE_PM, NULL, ericsson }, + + { "digi", 0x0000, 0x0000, HCI_UART_H4, 9600, 115200, + FLOW_CTL, DISABLE_PM, NULL, digi }, + + { "bcsp", 0x0000, 0x0000, HCI_UART_BCSP, 115200, 115200, + 0, DISABLE_PM, NULL, bcsp }, + + /* Xircom PCMCIA cards: Credit Card Adapter and Real Port Adapter */ + { "xircom", 0x0105, 0x080a, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, NULL }, + + /* CSR Casira serial adapter or BrainBoxes serial dongle (BL642) */ + { "csr", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, csr }, + + /* BrainBoxes PCMCIA card (BL620) */ + { "bboxes", 0x0160, 0x0002, HCI_UART_H4, 115200, 460800, + FLOW_CTL, DISABLE_PM, NULL, csr }, + + /* Silicon Wave kits */ + { "swave", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, swave }, + + /* Texas Instruments Bluelink (BRF) modules */ + { "texas", 0x0000, 0x0000, HCI_UART_LL, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, texas, texas2 }, + + { "texasalt", 0x0000, 0x0000, HCI_UART_LL, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, texasalt, NULL }, + + /* ST Microelectronics minikits based on STLC2410/STLC2415 */ + { "st", 0x0000, 0x0000, HCI_UART_H4, 57600, 115200, + FLOW_CTL, DISABLE_PM, NULL, st }, + + /* ST Microelectronics minikits based on STLC2500 */ + { "stlc2500", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, "00:80:E1:00:AB:BA", stlc2500 }, + + /* Philips generic Ericsson IP core based */ + { "philips", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, NULL }, + + /* Philips BGB2xx Module */ + { "bgb2xx", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, "BD:B2:10:00:AB:BA", bgb2xx }, + + /* Sphinx Electronics PICO Card */ + { "picocard", 0x025e, 0x1000, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, NULL }, + + /* Inventel BlueBird Module */ + { "inventel", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, NULL }, + + /* COM One Platinium Bluetooth PC Card */ + { "comone", 0xffff, 0x0101, HCI_UART_BCSP, 115200, 115200, + 0, DISABLE_PM, NULL, bcsp }, + + /* TDK Bluetooth PC Card and IBM Bluetooth PC Card II */ + { "tdk", 0x0105, 0x4254, HCI_UART_BCSP, 115200, 115200, + 0, DISABLE_PM, NULL, bcsp }, + + /* Socket Bluetooth CF Card (Rev G) */ + { "socket", 0x0104, 0x0096, HCI_UART_BCSP, 230400, 230400, + 0, DISABLE_PM, NULL, bcsp }, + + /* 3Com Bluetooth Card (Version 3.0) */ + { "3com", 0x0101, 0x0041, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, csr }, + + /* AmbiCom BT2000C Bluetooth PC/CF Card */ + { "bt2000c", 0x022d, 0x2000, HCI_UART_H4, 57600, 460800, + FLOW_CTL, DISABLE_PM, NULL, csr }, + + /* Zoom Bluetooth PCMCIA Card */ + { "zoom", 0x0279, 0x950b, HCI_UART_BCSP, 115200, 115200, + 0, DISABLE_PM, NULL, bcsp }, + + /* Sitecom CN-504 PCMCIA Card */ + { "sitecom", 0x0279, 0x950b, HCI_UART_BCSP, 115200, 115200, + 0, DISABLE_PM, NULL, bcsp }, + + /* Billionton PCBTC1 PCMCIA Card */ + { "billionton", 0x0279, 0x950b, HCI_UART_BCSP, 115200, 115200, + 0, DISABLE_PM, NULL, bcsp }, + + /* Broadcom BCM2035 */ + { "bcm2035", 0x0A5C, 0x2035, HCI_UART_H4, 115200, 460800, + FLOW_CTL, DISABLE_PM, NULL, bcm2035 }, + + /* Broadcom BCM43XX */ + { "bcm43xx", 0x0000, 0x0000, HCI_UART_H4, 115200, 3000000, + FLOW_CTL, DISABLE_PM, NULL, bcm43xx, NULL }, + + { "ath3k", 0x0000, 0x0000, HCI_UART_ATH3K, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, ath3k_ps, ath3k_pm }, + + /* QUALCOMM BTS */ + { "qualcomm", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, qualcomm, NULL }, + + /* Intel Bluetooth Module */ + { "intel", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200, + FLOW_CTL, DISABLE_PM, NULL, intel, NULL }, + + /* Three-wire UART */ + { "3wire", 0x0000, 0x0000, HCI_UART_3WIRE, 115200, 115200, + 0, DISABLE_PM, NULL, NULL, NULL }, + + /* AMP controller UART */ + { "amp", 0x0000, 0x0000, HCI_UART_H4, 115200, 115200, + AMP_DEV, DISABLE_PM, NULL, NULL, NULL }, + + { NULL, 0 } +}; + +static struct uart_t * get_by_id(int m_id, int p_id) +{ + int i; + for (i = 0; uart[i].type; i++) { + if (uart[i].m_id == m_id && uart[i].p_id == p_id) + return &uart[i]; + } + return NULL; +} + +static struct uart_t * get_by_type(char *type) +{ + int i; + for (i = 0; uart[i].type; i++) { + if (!strcmp(uart[i].type, type)) + return &uart[i]; + } + return NULL; +} + +/* Initialize UART driver */ +static int init_uart(char *dev, struct uart_t *u, int send_break, int raw) +{ + struct termios ti; + int fd, i; + unsigned long flags = 0; + + if (raw) + flags |= 1 << HCI_UART_RAW_DEVICE; + + if (u->flags & AMP_DEV) + flags |= 1 << HCI_UART_CREATE_AMP; + + fd = open(dev, O_RDWR | O_NOCTTY); + if (fd < 0) { + perror("Can't open serial port"); + return -1; + } + + tcflush(fd, TCIOFLUSH); + + if (tcgetattr(fd, &ti) < 0) { + perror("Can't get port settings"); + goto fail; + } + + cfmakeraw(&ti); + + ti.c_cflag |= CLOCAL; + if (u->flags & FLOW_CTL) + ti.c_cflag |= CRTSCTS; + else + ti.c_cflag &= ~CRTSCTS; + + if (tcsetattr(fd, TCSANOW, &ti) < 0) { + perror("Can't set port settings"); + goto fail; + } + + /* Set initial baudrate */ + if (set_speed(fd, &ti, u->init_speed) < 0) { + perror("Can't set initial baud rate"); + goto fail; + } + + tcflush(fd, TCIOFLUSH); + + if (send_break) { + tcsendbreak(fd, 0); + usleep(500000); + } + + if (u->init && u->init(fd, u, &ti) < 0) + goto fail; + + tcflush(fd, TCIOFLUSH); + + /* Set actual baudrate */ + if (set_speed(fd, &ti, u->speed) < 0) { + perror("Can't set baud rate"); + goto fail; + } + + /* Set TTY to N_HCI line discipline */ + i = N_HCI; + if (ioctl(fd, TIOCSETD, &i) < 0) { + perror("Can't set line discipline"); + goto fail; + } + + if (flags && ioctl(fd, HCIUARTSETFLAGS, flags) < 0) { + perror("Can't set UART flags"); + goto fail; + } + + if (ioctl(fd, HCIUARTSETPROTO, u->proto) < 0) { + perror("Can't set device"); + goto fail; + } + + if (u->post && u->post(fd, u, &ti) < 0) + goto fail; + + return fd; + +fail: + close(fd); + return -1; +} + +static void usage(void) +{ + printf("hciattach - HCI UART driver initialization utility\n"); + printf("Usage:\n"); + printf("\thciattach [-n] [-p] [-b] [-r] [-t timeout] [-s initial_speed]" + " [speed] [flow|noflow]" + " [sleep|nosleep] [bdaddr]\n"); + printf("\thciattach -l\n"); +} + +int main(int argc, char *argv[]) +{ + struct uart_t *u = NULL; + int detach, printpid, raw, opt, i, n, ld, err; + int to = 10; + int init_speed = 0; + int send_break = 0; + pid_t pid; + struct sigaction sa; + struct pollfd p; + sigset_t sigs; + char dev[PATH_MAX]; + + detach = 1; + printpid = 0; + raw = 0; + + while ((opt=getopt(argc, argv, "bnpt:s:lr")) != EOF) { + switch(opt) { + case 'b': + send_break = 1; + break; + + case 'n': + detach = 0; + break; + + case 'p': + printpid = 1; + break; + + case 't': + to = atoi(optarg); + break; + + case 's': + init_speed = atoi(optarg); + break; + + case 'l': + for (i = 0; uart[i].type; i++) { + printf("%-10s0x%04x,0x%04x\n", uart[i].type, + uart[i].m_id, uart[i].p_id); + } + exit(0); + + case 'r': + raw = 1; + break; + + default: + usage(); + exit(1); + } + } + + n = argc - optind; + if (n < 2) { + usage(); + exit(1); + } + + for (n = 0; optind < argc; n++, optind++) { + char *opt; + + opt = argv[optind]; + + switch(n) { + case 0: + dev[0] = 0; + if (!strchr(opt, '/')) + strcpy(dev, "/dev/"); + + if (strlen(opt) > PATH_MAX - (strlen(dev) + 1)) { + fprintf(stderr, "Invalid serial device\n"); + exit(1); + } + + strcat(dev, opt); + break; + + case 1: + if (strchr(argv[optind], ',')) { + int m_id, p_id; + sscanf(argv[optind], "%x,%x", &m_id, &p_id); + u = get_by_id(m_id, p_id); + } else { + u = get_by_type(opt); + } + + if (!u) { + fprintf(stderr, "Unknown device type or id\n"); + exit(1); + } + + break; + + case 2: + u->speed = atoi(argv[optind]); + break; + + case 3: + if (!strcmp("flow", argv[optind])) + u->flags |= FLOW_CTL; + else + u->flags &= ~FLOW_CTL; + break; + + case 4: + if (!strcmp("sleep", argv[optind])) + u->pm = ENABLE_PM; + else + u->pm = DISABLE_PM; + break; + + case 5: + u->bdaddr = argv[optind]; + break; + } + } + + if (!u) { + fprintf(stderr, "Unknown device type or id\n"); + exit(1); + } + + /* If user specified a initial speed, use that instead of + the hardware's default */ + if (init_speed) + u->init_speed = init_speed; + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = sig_alarm; + sigaction(SIGALRM, &sa, NULL); + + /* 10 seconds should be enough for initialization */ + alarm(to); + bcsp_max_retries = to; + + n = init_uart(dev, u, send_break, raw); + if (n < 0) { + perror("Can't initialize device"); + exit(1); + } + + printf("Device setup complete\n"); + + alarm(0); + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + + sa.sa_handler = sig_term; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = sig_hup; + sigaction(SIGHUP, &sa, NULL); + + if (detach) { + if ((pid = fork())) { + if (printpid) + printf("%d\n", pid); + return 0; + } + + for (i = 0; i < 20; i++) + if (i != n) + close(i); + } + + p.fd = n; + p.events = POLLERR | POLLHUP; + + sigfillset(&sigs); + sigdelset(&sigs, SIGCHLD); + sigdelset(&sigs, SIGPIPE); + sigdelset(&sigs, SIGTERM); + sigdelset(&sigs, SIGINT); + sigdelset(&sigs, SIGHUP); + + while (!__io_canceled) { + p.revents = 0; + err = ppoll(&p, 1, NULL, &sigs); + if (err < 0 && errno == EINTR) + continue; + if (err) + break; + } + + /* Restore TTY line discipline */ + ld = N_TTY; + if (ioctl(n, TIOCSETD, &ld) < 0) { + perror("Can't restore line discipline"); + exit(1); + } + + return 0; +} diff --git a/tools/hciattach.h b/tools/hciattach.h new file mode 100644 index 0000000..249aab4 --- /dev/null +++ b/tools/hciattach.h @@ -0,0 +1,71 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2003-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#ifndef N_HCI +#define N_HCI 15 +#endif + +#define HCIUARTSETPROTO _IOW('U', 200, int) +#define HCIUARTGETPROTO _IOR('U', 201, int) +#define HCIUARTGETDEVICE _IOR('U', 202, int) +#define HCIUARTSETFLAGS _IOW('U', 203, int) +#define HCIUARTGETFLAGS _IOR('U', 204, int) + +#define HCI_UART_H4 0 +#define HCI_UART_BCSP 1 +#define HCI_UART_3WIRE 2 +#define HCI_UART_H4DS 3 +#define HCI_UART_LL 4 +#define HCI_UART_ATH3K 5 +#define HCI_UART_INTEL 6 +#define HCI_UART_BCM 7 +#define HCI_UART_QCA 8 +#define HCI_UART_AG6XX 9 +#define HCI_UART_NOKIA 10 +#define HCI_UART_MRVL 11 + +#define HCI_UART_RAW_DEVICE 0 +#define HCI_UART_RESET_ON_INIT 1 +#define HCI_UART_CREATE_AMP 2 +#define HCI_UART_INIT_PENDING 3 +#define HCI_UART_EXT_CONFIG 4 +#define HCI_UART_VND_DETECT 5 + +int read_hci_event(int fd, unsigned char *buf, int size); +int set_speed(int fd, struct termios *ti, int speed); +int uart_speed(int speed); + +int texas_init(int fd, int *speed, struct termios *ti); +int texas_post(int fd, struct termios *ti); +int texasalt_init(int fd, int speed, struct termios *ti); +int stlc2500_init(int fd, bdaddr_t *bdaddr); +int bgb2xx_init(int dd, bdaddr_t *bdaddr); +int ath3k_init(int fd, int speed, int init_speed, char *bdaddr, + struct termios *ti); +int ath3k_post(int fd, int pm); +int qualcomm_init(int fd, int speed, struct termios *ti, const char *bdaddr); +int intel_init(int fd, int init_speed, int *speed, struct termios *ti); +int bcm43xx_init(int fd, int def_speed, int speed, struct termios *ti, + const char *bdaddr); diff --git a/tools/hciattach_ath3k.c b/tools/hciattach_ath3k.c new file mode 100644 index 0000000..d920050 --- /dev/null +++ b/tools/hciattach_ath3k.c @@ -0,0 +1,1036 @@ +/* + * Copyright (c) 2009-2010 Atheros Communications Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "hciattach.h" + +#define TRUE 1 +#define FALSE 0 + +#define FW_PATH "/lib/firmware/ar3k/" + +struct ps_cfg_entry { + uint32_t id; + uint32_t len; + uint8_t *data; +}; + +struct ps_entry_type { + unsigned char type; + unsigned char array; +}; + +#define MAX_TAGS 50 +#define PS_HDR_LEN 4 +#define HCI_VENDOR_CMD_OGF 0x3F +#define HCI_PS_CMD_OCF 0x0B + +struct ps_cfg_entry ps_list[MAX_TAGS]; + +static void load_hci_ps_hdr(uint8_t *cmd, uint8_t ps_op, int len, int index) +{ + hci_command_hdr *ch = (void *)cmd; + + ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF, + HCI_PS_CMD_OCF)); + ch->plen = len + PS_HDR_LEN; + cmd += HCI_COMMAND_HDR_SIZE; + + cmd[0] = ps_op; + cmd[1] = index; + cmd[2] = index >> 8; + cmd[3] = len; +} + +#define PS_EVENT_LEN 100 + +/* + * Send HCI command and wait for command complete event. + * The event buffer has to be freed by the caller. + */ +static int send_hci_cmd_sync(int dev, uint8_t *cmd, int len, uint8_t **event) +{ + int err; + uint8_t *hci_event; + uint8_t pkt_type = HCI_COMMAND_PKT; + + if (len == 0) + return len; + + if (write(dev, &pkt_type, 1) != 1) + return -EILSEQ; + if (write(dev, (unsigned char *)cmd, len) != len) + return -EILSEQ; + + hci_event = (uint8_t *)malloc(PS_EVENT_LEN); + if (!hci_event) + return -ENOMEM; + + err = read_hci_event(dev, (unsigned char *)hci_event, PS_EVENT_LEN); + if (err > 0) { + *event = hci_event; + } else { + free(hci_event); + return -EILSEQ; + } + + return len; +} + +#define HCI_EV_SUCCESS 0x00 + +static int read_ps_event(uint8_t *event, uint16_t ocf) +{ + hci_event_hdr *eh; + uint16_t opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF, ocf)); + + event++; + + eh = (void *)event; + event += HCI_EVENT_HDR_SIZE; + + if (eh->evt == EVT_CMD_COMPLETE) { + evt_cmd_complete *cc = (void *)event; + + event += EVT_CMD_COMPLETE_SIZE; + + if (cc->opcode == opcode && event[0] == HCI_EV_SUCCESS) + return 0; + else + return -EILSEQ; + } + + return -EILSEQ; +} + +static int write_cmd(int fd, uint8_t *buffer, int len) +{ + uint8_t *event; + int err; + + err = send_hci_cmd_sync(fd, buffer, len, &event); + if (err < 0) + return err; + + err = read_ps_event(event, HCI_PS_CMD_OCF); + + free(event); + + return err; +} + +#define PS_WRITE 1 +#define PS_RESET 2 +#define WRITE_PATCH 8 +#define ENABLE_PATCH 11 + +#define HCI_PS_CMD_HDR_LEN 7 + +#define PS_RESET_PARAM_LEN 6 +#define HCI_MAX_CMD_SIZE 260 +#define PS_RESET_CMD_LEN (HCI_PS_CMD_HDR_LEN + PS_RESET_PARAM_LEN) + +#define PS_ID_MASK 0xFF + +/* Sends PS commands using vendor specficic HCI commands */ +static int write_ps_cmd(int fd, uint8_t opcode, uint32_t ps_param) +{ + uint8_t cmd[HCI_MAX_CMD_SIZE]; + uint32_t i; + + switch (opcode) { + case ENABLE_PATCH: + load_hci_ps_hdr(cmd, opcode, 0, 0x00); + + if (write_cmd(fd, cmd, HCI_PS_CMD_HDR_LEN) < 0) + return -EILSEQ; + break; + + case PS_RESET: + load_hci_ps_hdr(cmd, opcode, PS_RESET_PARAM_LEN, 0x00); + + cmd[7] = 0x00; + cmd[PS_RESET_CMD_LEN - 2] = ps_param & PS_ID_MASK; + cmd[PS_RESET_CMD_LEN - 1] = (ps_param >> 8) & PS_ID_MASK; + + if (write_cmd(fd, cmd, PS_RESET_CMD_LEN) < 0) + return -EILSEQ; + break; + + case PS_WRITE: + for (i = 0; i < ps_param; i++) { + load_hci_ps_hdr(cmd, opcode, ps_list[i].len, + ps_list[i].id); + + memcpy(&cmd[HCI_PS_CMD_HDR_LEN], ps_list[i].data, + ps_list[i].len); + + if (write_cmd(fd, cmd, ps_list[i].len + + HCI_PS_CMD_HDR_LEN) < 0) + return -EILSEQ; + } + break; + } + + return 0; +} + +#define __is_delim(ch) ((ch) == ':') +#define MAX_PREAMBLE_LEN 4 + +/* Parse PS entry preamble of format [X:X] for main type and subtype */ +static int get_ps_type(char *ptr, int index, char *type, char *sub_type) +{ + int i; + int delim = FALSE; + + if (index > MAX_PREAMBLE_LEN) + return -EILSEQ; + + for (i = 1; i < index; i++) { + if (__is_delim(ptr[i])) { + delim = TRUE; + continue; + } + + if (isalpha(ptr[i])) { + if (delim == FALSE) + (*type) = toupper(ptr[i]); + else + (*sub_type) = toupper(ptr[i]); + } + } + + return 0; +} + +#define ARRAY 'A' +#define STRING 'S' +#define DECIMAL 'D' +#define BINARY 'B' + +#define PS_HEX 0 +#define PS_DEC 1 + +static int get_input_format(char *buf, struct ps_entry_type *format) +{ + char *ptr = NULL; + char type = '\0'; + char sub_type = '\0'; + + format->type = PS_HEX; + format->array = TRUE; + + if (strstr(buf, "[") != buf) + return 0; + + ptr = strstr(buf, "]"); + if (!ptr) + return -EILSEQ; + + if (get_ps_type(buf, ptr - buf, &type, &sub_type) < 0) + return -EILSEQ; + + /* Check is data type is of array */ + if (type == ARRAY || sub_type == ARRAY) + format->array = TRUE; + + if (type == STRING || sub_type == STRING) + format->array = FALSE; + + if (type == DECIMAL || type == BINARY) + format->type = PS_DEC; + else + format->type = PS_HEX; + + return 0; +} + +#define UNDEFINED 0xFFFF + +static unsigned int read_data_in_section(char *buf, struct ps_entry_type type) +{ + char *ptr = buf; + + if (!buf) + return UNDEFINED; + + if (buf == strstr(buf, "[")) { + ptr = strstr(buf, "]"); + if (!ptr) + return UNDEFINED; + + ptr++; + } + + if (type.type == PS_HEX && type.array != TRUE) + return strtol(ptr, NULL, 16); + + return UNDEFINED; +} + +struct tag_info { + unsigned section; + unsigned line_count; + unsigned char_cnt; + unsigned byte_count; +}; + +static inline int update_char_count(const char *buf) +{ + char *end_ptr; + + if (strstr(buf, "[") == buf) { + end_ptr = strstr(buf, "]"); + if (!end_ptr) + return 0; + else + return (end_ptr - buf) + 1; + } + + return 0; +} + +/* Read PS entries as string, convert and add to Hex array */ +static void update_tag_data(struct ps_cfg_entry *tag, + struct tag_info *info, const char *ptr) +{ + char buf[3]; + + buf[2] = '\0'; + + strncpy(buf, &ptr[info->char_cnt], 2); + tag->data[info->byte_count] = strtol(buf, NULL, 16); + info->char_cnt += 3; + info->byte_count++; + + strncpy(buf, &ptr[info->char_cnt], 2); + tag->data[info->byte_count] = strtol(buf, NULL, 16); + info->char_cnt += 3; + info->byte_count++; +} + +#define PS_UNDEF 0 +#define PS_ID 1 +#define PS_LEN 2 +#define PS_DATA 3 + +#define PS_MAX_LEN 500 +#define LINE_SIZE_MAX (PS_MAX_LEN * 2) +#define ENTRY_PER_LINE 16 + +#define __check_comment(buf) (((buf)[0] == '/') && ((buf)[1] == '/')) +#define __skip_space(str) while (*(str) == ' ') ((str)++) + +static int ath_parse_ps(FILE *stream) +{ + char buf[LINE_SIZE_MAX + 1]; + char *ptr; + uint8_t tag_cnt = 0; + int16_t byte_count = 0; + struct ps_entry_type format; + struct tag_info status = { 0, 0, 0, 0 }; + + do { + int read_count; + struct ps_cfg_entry *tag; + + ptr = fgets(buf, LINE_SIZE_MAX, stream); + if (!ptr) + break; + + __skip_space(ptr); + if (__check_comment(ptr)) + continue; + + /* Lines with a '#' will be followed by new PS entry */ + if (ptr == strstr(ptr, "#")) { + if (status.section != PS_UNDEF) { + return -EILSEQ; + } else { + status.section = PS_ID; + continue; + } + } + + tag = &ps_list[tag_cnt]; + + switch (status.section) { + case PS_ID: + if (get_input_format(ptr, &format) < 0) + return -EILSEQ; + + tag->id = read_data_in_section(ptr, format); + status.section = PS_LEN; + break; + + case PS_LEN: + if (get_input_format(ptr, &format) < 0) + return -EILSEQ; + + byte_count = read_data_in_section(ptr, format); + if (byte_count > PS_MAX_LEN) + return -EILSEQ; + + tag->len = byte_count; + tag->data = (uint8_t *)malloc(byte_count); + + status.section = PS_DATA; + status.line_count = 0; + break; + + case PS_DATA: + if (status.line_count == 0) + if (get_input_format(ptr, &format) < 0) + return -EILSEQ; + + __skip_space(ptr); + + status.char_cnt = update_char_count(ptr); + + read_count = (byte_count > ENTRY_PER_LINE) ? + ENTRY_PER_LINE : byte_count; + + if (format.type == PS_HEX && format.array == TRUE) { + while (read_count > 0) { + update_tag_data(tag, &status, ptr); + read_count -= 2; + } + + if (byte_count > ENTRY_PER_LINE) + byte_count -= ENTRY_PER_LINE; + else + byte_count = 0; + } + + status.line_count++; + + if (byte_count == 0) + memset(&status, 0x00, sizeof(struct tag_info)); + + if (status.section == PS_UNDEF) + tag_cnt++; + + if (tag_cnt == MAX_TAGS) + return -EILSEQ; + break; + } + } while (ptr); + + return tag_cnt; +} + +#define MAX_PATCH_CMD 244 +struct patch_entry { + int16_t len; + uint8_t data[MAX_PATCH_CMD]; +}; + +#define SET_PATCH_RAM_ID 0x0D +#define SET_PATCH_RAM_CMD_SIZE 11 +#define ADDRESS_LEN 4 +static int set_patch_ram(int dev, char *patch_loc, int len) +{ + int err; + uint8_t cmd[20]; + int i, j; + char loc_byte[3]; + uint8_t *event; + uint8_t *loc_ptr = &cmd[7]; + + if (!patch_loc) + return -1; + + loc_byte[2] = '\0'; + + load_hci_ps_hdr(cmd, SET_PATCH_RAM_ID, ADDRESS_LEN, 0); + + for (i = 0, j = 3; i < 4; i++, j--) { + loc_byte[0] = patch_loc[0]; + loc_byte[1] = patch_loc[1]; + loc_ptr[j] = strtol(loc_byte, NULL, 16); + patch_loc += 2; + } + + err = send_hci_cmd_sync(dev, cmd, SET_PATCH_RAM_CMD_SIZE, &event); + if (err < 0) + return err; + + err = read_ps_event(event, HCI_PS_CMD_OCF); + + free(event); + + return err; +} + +#define PATCH_LOC_KEY "DA:" +#define PATCH_LOC_STRING_LEN (8 + 237) +static int ps_patch_download(int fd, FILE *stream) +{ + char byte[3]; + char ptr[MAX_PATCH_CMD + 1]; + int byte_cnt; + int patch_count = 0; + char patch_loc[PATCH_LOC_STRING_LEN + 1]; + + byte[2] = '\0'; + + while (fgets(ptr, MAX_PATCH_CMD, stream)) { + if (strlen(ptr) <= 1) + continue; + else if (strstr(ptr, PATCH_LOC_KEY) == ptr) { + strncpy(patch_loc, &ptr[sizeof(PATCH_LOC_KEY) - 1], + PATCH_LOC_STRING_LEN); + if (set_patch_ram(fd, patch_loc, sizeof(patch_loc)) < 0) + return -1; + } else if (isxdigit(ptr[0])) + break; + else + return -1; + } + + byte_cnt = strtol(ptr, NULL, 16); + + while (byte_cnt > 0) { + int i; + uint8_t cmd[HCI_MAX_CMD_SIZE]; + struct patch_entry patch; + + if (byte_cnt > MAX_PATCH_CMD) + patch.len = MAX_PATCH_CMD; + else + patch.len = byte_cnt; + + for (i = 0; i < patch.len; i++) { + if (!fgets(byte, 3, stream)) + return -1; + + patch.data[i] = strtoul(byte, NULL, 16); + } + + load_hci_ps_hdr(cmd, WRITE_PATCH, patch.len, patch_count); + memcpy(&cmd[HCI_PS_CMD_HDR_LEN], patch.data, patch.len); + + if (write_cmd(fd, cmd, patch.len + HCI_PS_CMD_HDR_LEN) < 0) + return -1; + + patch_count++; + byte_cnt = byte_cnt - MAX_PATCH_CMD; + } + + if (write_ps_cmd(fd, ENABLE_PATCH, 0) < 0) + return -1; + + return patch_count; +} + +#define PS_RAM_SIZE 2048 + +static int ps_config_download(int fd, int tag_count) +{ + if (write_ps_cmd(fd, PS_RESET, PS_RAM_SIZE) < 0) + return -1; + + if (tag_count > 0) + if (write_ps_cmd(fd, PS_WRITE, tag_count) < 0) + return -1; + return 0; +} + +#define PS_ASIC_FILE "PS_ASIC.pst" +#define PS_FPGA_FILE "PS_FPGA.pst" + +static void get_ps_file_name(uint32_t devtype, uint32_t rom_version, + char *path) +{ + char *filename; + + if (devtype == 0xdeadc0de) + filename = PS_ASIC_FILE; + else + filename = PS_FPGA_FILE; + + snprintf(path, MAXPATHLEN, "%s%x/%s", FW_PATH, rom_version, filename); +} + +#define PATCH_FILE "RamPatch.txt" +#define FPGA_ROM_VERSION 0x99999999 +#define ROM_DEV_TYPE 0xdeadc0de + +static void get_patch_file_name(uint32_t dev_type, uint32_t rom_version, + uint32_t build_version, char *path) +{ + if (rom_version == FPGA_ROM_VERSION && dev_type != ROM_DEV_TYPE && + dev_type != 0 && build_version == 1) + path[0] = '\0'; + else + snprintf(path, MAXPATHLEN, "%s%x/%s", + FW_PATH, rom_version, PATCH_FILE); +} + +#define VERIFY_CRC 9 +#define PS_REGION 1 +#define PATCH_REGION 2 + +static int get_ath3k_crc(int dev) +{ + uint8_t cmd[7]; + uint8_t *event; + int err; + + load_hci_ps_hdr(cmd, VERIFY_CRC, 0, PS_REGION | PATCH_REGION); + + err = send_hci_cmd_sync(dev, cmd, sizeof(cmd), &event); + if (err < 0) + return err; + /* Send error code if CRC check patched */ + if (read_ps_event(event, HCI_PS_CMD_OCF) >= 0) + err = -EILSEQ; + + free(event); + + return err; +} + +#define DEV_REGISTER 0x4FFC +#define GET_DEV_TYPE_OCF 0x05 + +static int get_device_type(int dev, uint32_t *code) +{ + uint8_t cmd[8]; + uint8_t *event; + uint32_t reg; + int err; + uint8_t *ptr = cmd; + hci_command_hdr *ch = (void *)cmd; + + ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF, + GET_DEV_TYPE_OCF)); + ch->plen = 5; + ptr += HCI_COMMAND_HDR_SIZE; + + ptr[0] = (uint8_t)DEV_REGISTER; + ptr[1] = (uint8_t)DEV_REGISTER >> 8; + ptr[2] = (uint8_t)DEV_REGISTER >> 16; + ptr[3] = (uint8_t)DEV_REGISTER >> 24; + ptr[4] = 0x04; + + err = send_hci_cmd_sync(dev, cmd, sizeof(cmd), &event); + if (err < 0) + return err; + + err = read_ps_event(event, GET_DEV_TYPE_OCF); + if (err < 0) + goto cleanup; + + reg = event[10]; + reg = (reg << 8) | event[9]; + reg = (reg << 8) | event[8]; + reg = (reg << 8) | event[7]; + *code = reg; + +cleanup: + free(event); + + return err; +} + +#define GET_VERSION_OCF 0x1E + +static int read_ath3k_version(int pConfig, uint32_t *rom_version, + uint32_t *build_version) +{ + uint8_t cmd[3]; + uint8_t *event; + int err; + int status; + hci_command_hdr *ch = (void *)cmd; + + ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF, + GET_VERSION_OCF)); + ch->plen = 0; + + err = send_hci_cmd_sync(pConfig, cmd, sizeof(cmd), &event); + if (err < 0) + return err; + + err = read_ps_event(event, GET_VERSION_OCF); + if (err < 0) + goto cleanup; + + status = event[10]; + status = (status << 8) | event[9]; + status = (status << 8) | event[8]; + status = (status << 8) | event[7]; + *rom_version = status; + + status = event[14]; + status = (status << 8) | event[13]; + status = (status << 8) | event[12]; + status = (status << 8) | event[11]; + *build_version = status; + +cleanup: + free(event); + + return err; +} + +static void convert_bdaddr(char *str_bdaddr, char *bdaddr) +{ + char bdbyte[3]; + char *str_byte = str_bdaddr; + int i, j; + int colon_present = 0; + + if (strstr(str_bdaddr, ":")) + colon_present = 1; + + bdbyte[2] = '\0'; + + /* Reverse the BDADDR to LSB first */ + for (i = 0, j = 5; i < 6; i++, j--) { + bdbyte[0] = str_byte[0]; + bdbyte[1] = str_byte[1]; + bdaddr[j] = strtol(bdbyte, NULL, 16); + + if (colon_present == 1) + str_byte += 3; + else + str_byte += 2; + } +} + +static int write_bdaddr(int pConfig, char *bdaddr) +{ + uint8_t *event; + int err; + uint8_t cmd[13]; + uint8_t *ptr = cmd; + hci_command_hdr *ch = (void *)cmd; + + memset(cmd, 0, sizeof(cmd)); + + ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF, + HCI_PS_CMD_OCF)); + ch->plen = 10; + ptr += HCI_COMMAND_HDR_SIZE; + + ptr[0] = 0x01; + ptr[1] = 0x01; + ptr[2] = 0x00; + ptr[3] = 0x06; + + convert_bdaddr(bdaddr, (char *)&ptr[4]); + + err = send_hci_cmd_sync(pConfig, cmd, sizeof(cmd), &event); + if (err < 0) + return err; + + err = read_ps_event(event, HCI_PS_CMD_OCF); + + free(event); + + return err; +} + +#define BDADDR_FILE "ar3kbdaddr.pst" + +static void write_bdaddr_from_file(int rom_version, int fd) +{ + FILE *stream; + char bdaddr[PATH_MAX]; + char bdaddr_file[PATH_MAX]; + + snprintf(bdaddr_file, MAXPATHLEN, "%s%x/%s", + FW_PATH, rom_version, BDADDR_FILE); + + stream = fopen(bdaddr_file, "r"); + if (!stream) + return; + + if (fgets(bdaddr, PATH_MAX - 1, stream)) + write_bdaddr(fd, bdaddr); + + fclose(stream); +} + +static int ath_ps_download(int fd) +{ + int err = 0; + int tag_count; + int patch_count = 0; + uint32_t rom_version = 0; + uint32_t build_version = 0; + uint32_t dev_type = 0; + char patch_file[PATH_MAX]; + char ps_file[PATH_MAX]; + FILE *stream; + + /* + * Verfiy firmware version. depending on it select the PS + * config file to download. + */ + if (get_device_type(fd, &dev_type) < 0) { + err = -EILSEQ; + goto download_cmplete; + } + + if (read_ath3k_version(fd, &rom_version, &build_version) < 0) { + err = -EILSEQ; + goto download_cmplete; + } + + /* Do not download configuration if CRC passes */ + if (get_ath3k_crc(fd) < 0) { + err = 0; + goto download_cmplete; + } + + get_ps_file_name(dev_type, rom_version, ps_file); + get_patch_file_name(dev_type, rom_version, build_version, patch_file); + + stream = fopen(ps_file, "r"); + if (!stream) { + perror("firmware file open error\n"); + err = -EILSEQ; + goto download_cmplete; + } + tag_count = ath_parse_ps(stream); + + fclose(stream); + + if (tag_count < 0) { + err = -EILSEQ; + goto download_cmplete; + } + + stream = fopen(patch_file, "r"); + if(stream) { + patch_count = ps_patch_download(fd, stream); + fclose(stream); + + if (patch_count < 0) { + err = -EILSEQ; + goto download_cmplete; + } + } + + err = ps_config_download(fd, tag_count); + +download_cmplete: + if (!err) + write_bdaddr_from_file(rom_version, fd); + + return err; +} + +#define HCI_SLEEP_CMD_OCF 0x04 + +/* + * Atheros AR300x specific initialization post callback + */ +int ath3k_post(int fd, int pm) +{ + int dev_id, dd; + struct timespec tm = { 0, 50000 }; + + sleep(1); + + dev_id = ioctl(fd, HCIUARTGETDEVICE, 0); + if (dev_id < 0) { + perror("cannot get device id"); + return dev_id; + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + return dd; + } + + if (ioctl(dd, HCIDEVUP, dev_id) < 0 && errno != EALREADY) { + perror("hci down:Power management Disabled"); + hci_close_dev(dd); + return -1; + } + + /* send vendor specific command with Sleep feature Enabled */ + if (hci_send_cmd(dd, OGF_VENDOR_CMD, HCI_SLEEP_CMD_OCF, 1, &pm) < 0) + perror("PM command failed, power management Disabled"); + + nanosleep(&tm, NULL); + hci_close_dev(dd); + + return 0; +} + +#define HCI_VENDOR_CMD_OGF 0x3F +#define HCI_PS_CMD_OCF 0x0B +#define HCI_CHG_BAUD_CMD_OCF 0x0C + +#define WRITE_BDADDR_CMD_LEN 14 +#define WRITE_BAUD_CMD_LEN 6 +#define MAX_CMD_LEN WRITE_BDADDR_CMD_LEN + +static int set_cntrlr_baud(int fd, int speed) +{ + int baud; + struct timespec tm = { 0, 500000 }; + unsigned char cmd[MAX_CMD_LEN], rsp[HCI_MAX_EVENT_SIZE]; + unsigned char *ptr = cmd + 1; + hci_command_hdr *ch = (void *)ptr; + + cmd[0] = HCI_COMMAND_PKT; + + /* set controller baud rate to user specified value */ + ptr = cmd + 1; + ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF, + HCI_CHG_BAUD_CMD_OCF)); + ch->plen = 2; + ptr += HCI_COMMAND_HDR_SIZE; + + baud = speed/100; + ptr[0] = (char)baud; + ptr[1] = (char)(baud >> 8); + + if (write(fd, cmd, WRITE_BAUD_CMD_LEN) != WRITE_BAUD_CMD_LEN) { + perror("Failed to write change baud rate command"); + return -ETIMEDOUT; + } + + nanosleep(&tm, NULL); + + if (read_hci_event(fd, rsp, sizeof(rsp)) < 0) + return -ETIMEDOUT; + + return 0; +} + +/* + * Atheros AR300x specific initialization and configuration file + * download + */ +int ath3k_init(int fd, int speed, int init_speed, char *bdaddr, + struct termios *ti) +{ + int r; + int err = 0; + struct timespec tm = { 0, 500000 }; + unsigned char cmd[MAX_CMD_LEN], rsp[HCI_MAX_EVENT_SIZE]; + unsigned char *ptr = cmd + 1; + hci_command_hdr *ch = (void *)ptr; + + cmd[0] = HCI_COMMAND_PKT; + + /* set both controller and host baud rate to maximum possible value */ + err = set_cntrlr_baud(fd, speed); + if (err < 0) + return err; + + err = set_speed(fd, ti, speed); + if (err < 0) { + perror("Can't set required baud rate"); + return err; + } + + /* Download PS and patch */ + r = ath_ps_download(fd); + if (r < 0) { + perror("Failed to Download configuration"); + err = -ETIMEDOUT; + goto failed; + } + + /* Write BDADDR */ + if (bdaddr) { + ch->opcode = htobs(cmd_opcode_pack(HCI_VENDOR_CMD_OGF, + HCI_PS_CMD_OCF)); + ch->plen = 10; + ptr += HCI_COMMAND_HDR_SIZE; + + ptr[0] = 0x01; + ptr[1] = 0x01; + ptr[2] = 0x00; + ptr[3] = 0x06; + str2ba(bdaddr, (bdaddr_t *)(ptr + 4)); + + if (write(fd, cmd, WRITE_BDADDR_CMD_LEN) != + WRITE_BDADDR_CMD_LEN) { + perror("Failed to write BD_ADDR command\n"); + err = -ETIMEDOUT; + goto failed; + } + + if (read_hci_event(fd, rsp, sizeof(rsp)) < 0) { + perror("Failed to set BD_ADDR\n"); + err = -ETIMEDOUT; + goto failed; + } + } + + /* Send HCI Reset */ + cmd[1] = 0x03; + cmd[2] = 0x0C; + cmd[3] = 0x00; + + r = write(fd, cmd, 4); + if (r != 4) { + err = -ETIMEDOUT; + goto failed; + } + + nanosleep(&tm, NULL); + if (read_hci_event(fd, rsp, sizeof(rsp)) < 0) { + err = -ETIMEDOUT; + goto failed; + } + + err = set_cntrlr_baud(fd, speed); + if (err < 0) + return err; + +failed: + if (err < 0) { + set_cntrlr_baud(fd, init_speed); + set_speed(fd, ti, init_speed); + } + + return err; +} diff --git a/tools/hciattach_bcm43xx.c b/tools/hciattach_bcm43xx.c new file mode 100644 index 0000000..be82cd0 --- /dev/null +++ b/tools/hciattach_bcm43xx.c @@ -0,0 +1,395 @@ +/* + * + * 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "hciattach.h" + +#ifndef FIRMWARE_DIR +#define FIRMWARE_DIR "/etc/firmware" +#endif + +#define FW_EXT ".hcd" + +#define BCM43XX_CLOCK_48 1 +#define BCM43XX_CLOCK_24 2 + +#define CMD_SUCCESS 0x00 + +#define CC_MIN_SIZE 7 + +#define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) + +static int bcm43xx_read_local_name(int fd, char *name, size_t size) +{ + unsigned char cmd[] = { HCI_COMMAND_PKT, 0x14, 0x0C, 0x00 }; + unsigned char *resp; + unsigned int name_len; + + resp = malloc(size + CC_MIN_SIZE); + if (!resp) + return -1; + + tcflush(fd, TCIOFLUSH); + + if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) { + fprintf(stderr, "Failed to write read local name command\n"); + goto fail; + } + + if (read_hci_event(fd, resp, size) < CC_MIN_SIZE) { + fprintf(stderr, "Failed to read local name, invalid HCI event\n"); + goto fail; + } + + if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) { + fprintf(stderr, "Failed to read local name, command failure\n"); + goto fail; + } + + name_len = resp[2] - 1; + + strncpy(name, (char *) &resp[7], MIN(name_len, size)); + name[size - 1] = 0; + + free(resp); + return 0; + +fail: + free(resp); + return -1; +} + +static int bcm43xx_reset(int fd) +{ + unsigned char cmd[] = { HCI_COMMAND_PKT, 0x03, 0x0C, 0x00 }; + unsigned char resp[CC_MIN_SIZE]; + + if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) { + fprintf(stderr, "Failed to write reset command\n"); + return -1; + } + + if (read_hci_event(fd, resp, sizeof(resp)) < CC_MIN_SIZE) { + fprintf(stderr, "Failed to reset chip, invalid HCI event\n"); + return -1; + } + + if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) { + fprintf(stderr, "Failed to reset chip, command failure\n"); + return -1; + } + + return 0; +} + +static int bcm43xx_set_bdaddr(int fd, const char *bdaddr) +{ + unsigned char cmd[] = + { HCI_COMMAND_PKT, 0x01, 0xfc, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; + unsigned char resp[CC_MIN_SIZE]; + + printf("Set BDADDR UART: %s\n", bdaddr); + + if (str2ba(bdaddr, (bdaddr_t *) (&cmd[4])) < 0) { + fprintf(stderr, "Incorrect bdaddr\n"); + return -1; + } + + tcflush(fd, TCIOFLUSH); + + if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) { + fprintf(stderr, "Failed to write set bdaddr command\n"); + return -1; + } + + if (read_hci_event(fd, resp, sizeof(resp)) < CC_MIN_SIZE) { + fprintf(stderr, "Failed to set bdaddr, invalid HCI event\n"); + return -1; + } + + if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) { + fprintf(stderr, "Failed to set bdaddr, command failure\n"); + return -1; + } + + return 0; +} + +static int bcm43xx_set_clock(int fd, unsigned char clock) +{ + unsigned char cmd[] = { HCI_COMMAND_PKT, 0x45, 0xfc, 0x01, 0x00 }; + unsigned char resp[CC_MIN_SIZE]; + + printf("Set Controller clock (%d)\n", clock); + + cmd[4] = clock; + + tcflush(fd, TCIOFLUSH); + + if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) { + fprintf(stderr, "Failed to write update clock command\n"); + return -1; + } + + if (read_hci_event(fd, resp, sizeof(resp)) < CC_MIN_SIZE) { + fprintf(stderr, "Failed to update clock, invalid HCI event\n"); + return -1; + } + + if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) { + fprintf(stderr, "Failed to update clock, command failure\n"); + return -1; + } + + return 0; +} + +static int bcm43xx_set_speed(int fd, struct termios *ti, uint32_t speed) +{ + unsigned char cmd[] = + { HCI_COMMAND_PKT, 0x18, 0xfc, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; + unsigned char resp[CC_MIN_SIZE]; + + if (speed > 3000000 && bcm43xx_set_clock(fd, BCM43XX_CLOCK_48)) + return -1; + + printf("Set Controller UART speed to %d bit/s\n", speed); + + cmd[6] = (uint8_t) (speed); + cmd[7] = (uint8_t) (speed >> 8); + cmd[8] = (uint8_t) (speed >> 16); + cmd[9] = (uint8_t) (speed >> 24); + + tcflush(fd, TCIOFLUSH); + + if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) { + fprintf(stderr, "Failed to write update baudrate command\n"); + return -1; + } + + if (read_hci_event(fd, resp, sizeof(resp)) < CC_MIN_SIZE) { + fprintf(stderr, "Failed to update baudrate, invalid HCI event\n"); + return -1; + } + + if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) { + fprintf(stderr, "Failed to update baudrate, command failure\n"); + return -1; + } + + if (set_speed(fd, ti, speed) < 0) { + perror("Can't set host baud rate"); + return -1; + } + + return 0; +} + +static int bcm43xx_load_firmware(int fd, const char *fw) +{ + unsigned char cmd[] = { HCI_COMMAND_PKT, 0x2e, 0xfc, 0x00 }; + struct timespec tm_mode = { 0, 50000000 }; + struct timespec tm_ready = { 0, 200000000 }; + unsigned char resp[CC_MIN_SIZE]; + unsigned char tx_buf[1024]; + int len, fd_fw, n; + + printf("Flash firmware %s\n", fw); + + fd_fw = open(fw, O_RDONLY); + if (fd_fw < 0) { + fprintf(stderr, "Unable to open firmware (%s)\n", fw); + return -1; + } + + tcflush(fd, TCIOFLUSH); + + if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) { + fprintf(stderr, "Failed to write download mode command\n"); + goto fail; + } + + if (read_hci_event(fd, resp, sizeof(resp)) < CC_MIN_SIZE) { + fprintf(stderr, "Failed to load firmware, invalid HCI event\n"); + goto fail; + } + + if (resp[4] != cmd[1] || resp[5] != cmd[2] || resp[6] != CMD_SUCCESS) { + fprintf(stderr, "Failed to load firmware, command failure\n"); + goto fail; + } + + /* Wait 50ms to let the firmware placed in download mode */ + nanosleep(&tm_mode, NULL); + + tcflush(fd, TCIOFLUSH); + + while ((n = read(fd_fw, &tx_buf[1], 3))) { + if (n < 0) { + fprintf(stderr, "Failed to read firmware\n"); + goto fail; + } + + tx_buf[0] = HCI_COMMAND_PKT; + + len = tx_buf[3]; + + if (read(fd_fw, &tx_buf[4], len) < 0) { + fprintf(stderr, "Failed to read firmware\n"); + goto fail; + } + + if (write(fd, tx_buf, len + 4) != (len + 4)) { + fprintf(stderr, "Failed to write firmware\n"); + goto fail; + } + + read_hci_event(fd, resp, sizeof(resp)); + tcflush(fd, TCIOFLUSH); + } + + /* Wait for firmware ready */ + nanosleep(&tm_ready, NULL); + + close(fd_fw); + return 0; + +fail: + close(fd_fw); + return -1; +} + +static int bcm43xx_locate_patch(const char *dir_name, + const char *chip_name, char *location) +{ + DIR *dir; + int ret = -1; + + dir = opendir(dir_name); + if (!dir) { + fprintf(stderr, "Cannot open directory '%s': %s\n", + dir_name, strerror(errno)); + return -1; + } + + /* Recursively look for a BCM43XX*.hcd */ + while (1) { + struct dirent *entry = readdir(dir); + if (!entry) + break; + + if (entry->d_type & DT_DIR) { + char path[PATH_MAX]; + + if (!strcmp(entry->d_name, "..") || !strcmp(entry->d_name, ".")) + continue; + + snprintf(path, PATH_MAX, "%s/%s", dir_name, entry->d_name); + + ret = bcm43xx_locate_patch(path, chip_name, location); + if (!ret) + break; + } else if (!strncmp(chip_name, entry->d_name, strlen(chip_name))) { + unsigned int name_len = strlen(entry->d_name); + size_t curs_ext = name_len - sizeof(FW_EXT) + 1; + + if (curs_ext > name_len) + break; + + if (strncmp(FW_EXT, &entry->d_name[curs_ext], sizeof(FW_EXT))) + break; + + /* found */ + snprintf(location, PATH_MAX, "%s/%s", dir_name, entry->d_name); + ret = 0; + break; + } + } + + closedir(dir); + + return ret; +} + +int bcm43xx_init(int fd, int def_speed, int speed, struct termios *ti, + const char *bdaddr) +{ + char chip_name[20]; + char fw_path[PATH_MAX]; + + printf("bcm43xx_init\n"); + + if (bcm43xx_reset(fd)) + return -1; + + if (bcm43xx_read_local_name(fd, chip_name, sizeof(chip_name))) + return -1; + + if (bcm43xx_locate_patch(FIRMWARE_DIR, chip_name, fw_path)) { + fprintf(stderr, "Patch not found, continue anyway\n"); + } else { + if (bcm43xx_set_speed(fd, ti, speed)) + return -1; + + if (bcm43xx_load_firmware(fd, fw_path)) + return -1; + + /* Controller speed has been reset to def speed */ + if (set_speed(fd, ti, def_speed) < 0) { + perror("Can't set host baud rate"); + return -1; + } + + if (bcm43xx_reset(fd)) + return -1; + } + + if (bdaddr) + bcm43xx_set_bdaddr(fd, bdaddr); + + if (bcm43xx_set_speed(fd, ti, speed)) + return -1; + + return 0; +} diff --git a/tools/hciattach_intel.c b/tools/hciattach_intel.c new file mode 100644 index 0000000..0f0b60f --- /dev/null +++ b/tools/hciattach_intel.c @@ -0,0 +1,596 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 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 +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "hciattach.h" + +#ifdef INTEL_DEBUG +#define DBGPRINT(fmt, args...) printf("DBG: " fmt "\n", ## args) +#define PRINT_PACKET(buf, len, msg) { \ + int i; \ + printf("%s\n", msg); \ + for (i = 0; i < len; i++) \ + printf("%02X ", buf[i]); \ + printf("\n"); \ + } +#else +#define DBGPRINT(fmt, args...) +#define PRINT_PACKET(buf, len, msg) +#endif + +#define PATCH_SEQ_EXT ".bseq" +#define PATCH_FILE_PATH "/lib/firmware/intel/" +#define PATCH_MAX_LEN 260 +#define PATCH_TYPE_CMD 1 +#define PATCH_TYPE_EVT 2 + +#define INTEL_VER_PARAM_LEN 9 +#define INTEL_MFG_PARAM_LEN 2 + +/** + * A data structure for a patch entry. + */ +struct patch_entry { + int type; + int len; + unsigned char data[PATCH_MAX_LEN]; +}; + +/** + * A structure for patch context + */ +struct patch_ctx { + int dev; + int fd; + int patch_error; + int reset_enable_patch; +}; + +/** + * Send HCI command to the controller + */ +static int intel_write_cmd(int dev, unsigned char *buf, int len) +{ + int ret; + + PRINT_PACKET(buf, len, "<----- SEND CMD: "); + + ret = write(dev, buf, len); + if (ret < 0) + return -errno; + + if (ret != len) + return -1; + + return ret; +} + +/** + * Read the event from the controller + */ +static int intel_read_evt(int dev, unsigned char *buf, int len) +{ + int ret; + + ret = read_hci_event(dev, buf, len); + if (ret < 0) + return -1; + + PRINT_PACKET(buf, ret, "-----> READ EVT: "); + + return ret; +} + +/** + * Validate HCI events + */ +static int validate_events(struct patch_entry *event, + struct patch_entry *entry) +{ + if (event == NULL || entry == NULL) { + DBGPRINT("invalid patch entry parameters"); + return -1; + } + + if (event->len != entry->len) { + DBGPRINT("lengths are mismatched:[%d|%d]", + event->len, entry->len); + return -1; + } + + if (memcmp(event->data, entry->data, event->len)) { + DBGPRINT("data is mismatched"); + return -1; + } + + return 0; +} + +/** + * Read the next patch entry one line at a time + */ +static int get_next_patch_entry(int fd, struct patch_entry *entry) +{ + int size; + char rb; + + if (read(fd, &rb, 1) <= 0) + return 0; + + entry->type = rb; + + switch (entry->type) { + case PATCH_TYPE_CMD: + entry->data[0] = HCI_COMMAND_PKT; + + if (read(fd, &entry->data[1], 3) < 0) + return -1; + + size = (int)entry->data[3]; + + if (read(fd, &entry->data[4], size) < 0) + return -1; + + entry->len = HCI_TYPE_LEN + HCI_COMMAND_HDR_SIZE + size; + + break; + + case PATCH_TYPE_EVT: + entry->data[0] = HCI_EVENT_PKT; + + if (read(fd, &entry->data[1], 2) < 0) + return -1; + + size = (int)entry->data[2]; + + if (read(fd, &entry->data[3], size) < 0) + return -1; + + entry->len = HCI_TYPE_LEN + HCI_EVENT_HDR_SIZE + size; + + break; + + default: + fprintf(stderr, "invalid patch entry(%d)\n", entry->type); + return -1; + } + + return entry->len; +} + +/** + * Download the patch set to the controller and verify the event + */ +static int intel_download_patch(struct patch_ctx *ctx) +{ + int ret; + struct patch_entry entry; + struct patch_entry event; + + DBGPRINT("start patch downloading"); + + do { + ret = get_next_patch_entry(ctx->fd, &entry); + if (ret <= 0) { + ctx->patch_error = 1; + break; + } + + switch (entry.type) { + case PATCH_TYPE_CMD: + ret = intel_write_cmd(ctx->dev, + entry.data, + entry.len); + if (ret <= 0) { + fprintf(stderr, "failed to send cmd(%d)\n", + ret); + return ret; + } + break; + + case PATCH_TYPE_EVT: + ret = intel_read_evt(ctx->dev, event.data, + sizeof(event.data)); + if (ret <= 0) { + fprintf(stderr, "failed to read evt(%d)\n", + ret); + return ret; + } + event.len = ret; + + if (validate_events(&event, &entry) < 0) { + DBGPRINT("events are mismatched"); + ctx->patch_error = 1; + return -1; + } + break; + + default: + fprintf(stderr, "unknown patch type(%d)\n", + entry.type); + return -1; + } + } while (1); + + return ret; +} + +static int open_patch_file(struct patch_ctx *ctx, char *fw_ver) +{ + char patch_file[PATH_MAX]; + + snprintf(patch_file, PATH_MAX, "%s%s%s", PATCH_FILE_PATH, + fw_ver, PATCH_SEQ_EXT); + DBGPRINT("PATCH_FILE: %s", patch_file); + + ctx->fd = open(patch_file, O_RDONLY); + if (ctx->fd < 0) { + DBGPRINT("cannot open patch file. go to post patch"); + return -1; + } + + return 0; +} + +/** + * Prepare the controller for patching. + */ +static int pre_patch(struct patch_ctx *ctx) +{ + int ret, i; + struct patch_entry entry; + char fw_ver[INTEL_VER_PARAM_LEN * 2]; + + DBGPRINT("start pre_patch"); + + entry.data[0] = HCI_COMMAND_PKT; + entry.data[1] = 0x11; + entry.data[2] = 0xFC; + entry.data[3] = 0x02; + entry.data[4] = 0x01; + entry.data[5] = 0x00; + entry.len = HCI_TYPE_LEN + HCI_COMMAND_HDR_SIZE + INTEL_MFG_PARAM_LEN; + + ret = intel_write_cmd(ctx->dev, entry.data, entry.len); + if (ret < 0) { + fprintf(stderr, "failed to send cmd(%d)\n", ret); + return ret; + } + + ret = intel_read_evt(ctx->dev, entry.data, sizeof(entry.data)); + if (ret < 0) { + fprintf(stderr, "failed to read evt(%d)\n", ret); + return ret; + } + entry.len = ret; + + if (entry.data[6] != 0x00) { + DBGPRINT("command failed. status=%02x", entry.data[6]); + ctx->patch_error = 1; + return -1; + } + + entry.data[0] = HCI_COMMAND_PKT; + entry.data[1] = 0x05; + entry.data[2] = 0xFC; + entry.data[3] = 0x00; + entry.len = HCI_TYPE_LEN + HCI_COMMAND_HDR_SIZE; + + ret = intel_write_cmd(ctx->dev, entry.data, entry.len); + if (ret < 0) { + fprintf(stderr, "failed to send cmd(%d)\n", ret); + return ret; + } + + ret = intel_read_evt(ctx->dev, entry.data, sizeof(entry.data)); + if (ret < 0) { + fprintf(stderr, "failed to read evt(%d)\n", ret); + return ret; + } + entry.len = ret; + + if (entry.data[6] != 0x00) { + DBGPRINT("command failed. status=%02x", entry.data[6]); + ctx->patch_error = 1; + return -1; + } + + for (i = 0; i < INTEL_VER_PARAM_LEN; i++) + sprintf(&fw_ver[i*2], "%02x", entry.data[7+i]); + + if (open_patch_file(ctx, fw_ver) < 0) { + ctx->patch_error = 1; + return -1; + } + + return ret; +} + +/* + * check the event is startup event + */ +static int is_startup_evt(unsigned char *buf) +{ + if (buf[1] == 0xFF && buf[2] == 0x01 && buf[3] == 0x00) + return 1; + + return 0; +} + +/** + * Finalize the patch process and reset the controller + */ +static int post_patch(struct patch_ctx *ctx) +{ + int ret; + struct patch_entry entry; + + DBGPRINT("start post_patch"); + + entry.data[0] = HCI_COMMAND_PKT; + entry.data[1] = 0x11; + entry.data[2] = 0xFC; + entry.data[3] = 0x02; + entry.data[4] = 0x00; + if (ctx->reset_enable_patch) + entry.data[5] = 0x02; + else + entry.data[5] = 0x01; + + entry.len = HCI_TYPE_LEN + HCI_COMMAND_HDR_SIZE + INTEL_MFG_PARAM_LEN; + + ret = intel_write_cmd(ctx->dev, entry.data, entry.len); + if (ret < 0) { + fprintf(stderr, "failed to send cmd(%d)\n", ret); + return ret; + } + + ret = intel_read_evt(ctx->dev, entry.data, sizeof(entry.data)); + if (ret < 0) { + fprintf(stderr, "failed to read evt(%d)\n", ret); + return ret; + } + entry.len = ret; + + if (entry.data[6] != 0x00) { + fprintf(stderr, "cmd failed. st=%02x\n", entry.data[6]); + return -1; + } + + do { + ret = intel_read_evt(ctx->dev, entry.data, + sizeof(entry.data)); + if (ret < 0) { + fprintf(stderr, "failed to read cmd(%d)\n", ret); + return ret; + } + entry.len = ret; + } while (!is_startup_evt(entry.data)); + + return ret; +} + +/** + * Main routine that handles the device patching process. + */ +static int intel_patch_device(struct patch_ctx *ctx) +{ + int ret; + + ret = pre_patch(ctx); + if (ret < 0) { + if (!ctx->patch_error) { + fprintf(stderr, "I/O error: pre_patch failed\n"); + return ret; + } + + DBGPRINT("patch failed. proceed to post patch"); + goto post_patch; + } + + ret = intel_download_patch(ctx); + if (ret < 0) { + if (!ctx->patch_error) { + fprintf(stderr, "I/O error: download_patch failed\n"); + close(ctx->fd); + return ret; + } + } else { + DBGPRINT("patch done"); + ctx->reset_enable_patch = 1; + } + + close(ctx->fd); + +post_patch: + ret = post_patch(ctx); + if (ret < 0) { + fprintf(stderr, "post_patch failed(%d)\n", ret); + return ret; + } + + return 0; +} + +static int set_rts(int dev, int rtsval) +{ + int arg; + + if (ioctl(dev, TIOCMGET, &arg) < 0) { + perror("cannot get TIOCMGET"); + return -errno; + } + if (rtsval) + arg |= TIOCM_RTS; + else + arg &= ~TIOCM_RTS; + + if (ioctl(dev, TIOCMSET, &arg) == -1) { + perror("cannot set TIOCMGET"); + return -errno; + } + + return 0; +} + +static unsigned char get_intel_speed(int speed) +{ + switch (speed) { + case 9600: + return 0x00; + case 19200: + return 0x01; + case 38400: + return 0x02; + case 57600: + return 0x03; + case 115200: + return 0x04; + case 230400: + return 0x05; + case 460800: + return 0x06; + case 921600: + return 0x07; + case 1843200: + return 0x08; + case 3250000: + return 0x09; + case 2000000: + return 0x0A; + case 3000000: + return 0x0B; + default: + return 0xFF; + } +} + +/** + * if it failed to change to new baudrate, it will rollback + * to initial baudrate + */ +static int change_baudrate(int dev, int init_speed, int *speed, + struct termios *ti) +{ + int ret; + unsigned char br; + unsigned char cmd[5]; + unsigned char evt[7]; + + DBGPRINT("start baudrate change"); + + ret = set_rts(dev, 0); + if (ret < 0) { + fprintf(stderr, "failed to clear RTS\n"); + return ret; + } + + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x06; + cmd[2] = 0xFC; + cmd[3] = 0x01; + + br = get_intel_speed(*speed); + if (br == 0xFF) { + fprintf(stderr, "speed %d is not supported\n", *speed); + return -1; + } + cmd[4] = br; + + ret = intel_write_cmd(dev, cmd, sizeof(cmd)); + if (ret < 0) { + fprintf(stderr, "failed to send cmd(%d)\n", ret); + return ret; + } + + /* + * wait for buffer to be consumed by the controller + */ + usleep(300000); + + if (set_speed(dev, ti, *speed) < 0) { + fprintf(stderr, "can't set to new baud rate\n"); + return -1; + } + + ret = set_rts(dev, 1); + if (ret < 0) { + fprintf(stderr, "failed to set RTS\n"); + return ret; + } + + ret = intel_read_evt(dev, evt, sizeof(evt)); + if (ret < 0) { + fprintf(stderr, "failed to read evt(%d)\n", ret); + return ret; + } + + if (evt[4] != 0x00) { + fprintf(stderr, + "failed to change speed. use default speed %d\n", + init_speed); + *speed = init_speed; + } + + return 0; +} + +/** + * An entry point for Intel specific initialization + */ +int intel_init(int dev, int init_speed, int *speed, struct termios *ti) +{ + int ret = 0; + struct patch_ctx ctx; + + if (change_baudrate(dev, init_speed, speed, ti) < 0) + return -1; + + ctx.dev = dev; + ctx.patch_error = 0; + ctx.reset_enable_patch = 0; + + ret = intel_patch_device(&ctx); + if (ret < 0) + fprintf(stderr, "failed to initialize the device"); + + return ret; +} diff --git a/tools/hciattach_qualcomm.c b/tools/hciattach_qualcomm.c new file mode 100644 index 0000000..29d15a5 --- /dev/null +++ b/tools/hciattach_qualcomm.c @@ -0,0 +1,275 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2010 Marcel Holtmann + * Copyright (c) 2010, Code Aurora Forum. All rights reserved. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "hciattach.h" + +#define FAILIF(x, args...) do { \ + if (x) { \ + fprintf(stderr, ##args); \ + return -1; \ + } \ +} while (0) + +typedef struct { + uint8_t uart_prefix; + hci_event_hdr hci_hdr; + evt_cmd_complete cmd_complete; + uint8_t status; + uint8_t data[16]; +} __attribute__((packed)) command_complete_t; + +static int read_command_complete(int fd, + unsigned short opcode, + unsigned char len) +{ + command_complete_t resp; + unsigned char vsevent[512]; + int n; + + /* Read reply. */ + n = read_hci_event(fd, vsevent, sizeof(vsevent)); + FAILIF(n < 0, "Failed to read response"); + + FAILIF(vsevent[1] != 0xFF, "Failed to read response"); + + n = read_hci_event(fd, (unsigned char *)&resp, sizeof(resp)); + FAILIF(n < 0, "Failed to read response"); + + /* event must be event-complete */ + FAILIF(resp.hci_hdr.evt != EVT_CMD_COMPLETE, + "Error in response: not a cmd-complete event, " + "but 0x%02x!\n", resp.hci_hdr.evt); + + FAILIF(resp.hci_hdr.plen < 4, /* plen >= 4 for EVT_CMD_COMPLETE */ + "Error in response: plen is not >= 4, but 0x%02x!\n", + resp.hci_hdr.plen); + + /* cmd-complete event: opcode */ + FAILIF(resp.cmd_complete.opcode != 0, + "Error in response: opcode is 0x%04x, not 0!", + resp.cmd_complete.opcode); + + return resp.status == 0 ? 0 : -1; +} + +static int qualcomm_load_firmware(int fd, const char *firmware, const char *bdaddr_s) +{ + + int fw = open(firmware, O_RDONLY); + + fprintf(stdout, "Opening firmware file: %s\n", firmware); + + FAILIF(fw < 0, + "Could not open firmware file %s: %s (%d).\n", + firmware, strerror(errno), errno); + + fprintf(stdout, "Uploading firmware...\n"); + do { + /* Read each command and wait for a response. */ + unsigned char data[1024]; + unsigned char cmdp[1 + sizeof(hci_command_hdr)]; + hci_command_hdr *cmd = (hci_command_hdr *) (cmdp + 1); + int nr; + + nr = read(fw, cmdp, sizeof(cmdp)); + if (!nr) + break; + + FAILIF(nr != sizeof(cmdp), + "Could not read H4 + HCI header!\n"); + FAILIF(*cmdp != HCI_COMMAND_PKT, + "Command is not an H4 command packet!\n"); + + FAILIF(read(fw, data, cmd->plen) != cmd->plen, + "Could not read %d bytes of data \ + for command with opcode %04x!\n", + cmd->plen, cmd->opcode); + + if ((data[0] == 1) && (data[1] == 2) && (data[2] == 6)) { + bdaddr_t bdaddr; + if (bdaddr_s != NULL) { + str2ba(bdaddr_s, &bdaddr); + memcpy(&data[3], &bdaddr, sizeof(bdaddr_t)); + } + } + + { + int nw; + struct iovec iov_cmd[2]; + iov_cmd[0].iov_base = cmdp; + iov_cmd[0].iov_len = sizeof(cmdp); + iov_cmd[1].iov_base = data; + iov_cmd[1].iov_len = cmd->plen; + nw = writev(fd, iov_cmd, 2); + FAILIF(nw != (int) sizeof(cmdp) + cmd->plen, + "Could not send entire command \ + (sent only %d bytes)!\n", + nw); + } + + /* Wait for response */ + if (read_command_complete(fd, cmd->opcode, cmd->plen) < 0) + return -1; + } while (1); + fprintf(stdout, "Firmware upload successful.\n"); + + close(fw); + + return 0; +} + +int qualcomm_init(int fd, int speed, struct termios *ti, const char *bdaddr) +{ + struct timespec tm = {0, 50000}; + char cmd[5]; + unsigned char resp[100]; /* Response */ + char fw[100]; + int n; + + memset(resp, 0, 100); + + /* Get Manufacturer and LMP version */ + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x01; + cmd[2] = 0x10; + cmd[3] = 0x00; + + do { + n = write(fd, cmd, 4); + if (n < 4) { + perror("Failed to write init command"); + return -1; + } + + /* Read reply. */ + if (read_hci_event(fd, resp, 100) < 0) { + perror("Failed to read init response"); + return -1; + } + + /* Wait for command complete event for our Opcode */ + } while (resp[4] != cmd[1] && resp[5] != cmd[2]); + + /* Verify manufacturer */ + if ((resp[11] & 0xFF) != 0x1d) + fprintf(stderr, + "WARNING : module's manufacturer is not Qualcomm\n"); + + /* Print LMP version */ + fprintf(stderr, + "Qualcomm module LMP version : 0x%02x\n", resp[10] & 0xFF); + + /* Print LMP subversion */ + { + unsigned short lmp_subv = resp[13] | (resp[14] << 8); + + fprintf(stderr, "Qualcomm module LMP sub-version : 0x%04x\n", + lmp_subv); + } + + /* Get SoC type */ + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x00; + cmd[2] = 0xFC; + cmd[3] = 0x01; + cmd[4] = 0x06; + + do { + n = write(fd, cmd, 5); + if (n < 5) { + perror("Failed to write vendor init command"); + return -1; + } + + /* Read reply. */ + if ((n = read_hci_event(fd, resp, 100)) < 0) { + perror("Failed to read vendor init response"); + return -1; + } + + } while (resp[3] != 0 && resp[4] != 2); + + snprintf(fw, sizeof(fw), "/etc/firmware/%c%c%c%c%c%c_%c%c%c%c.bin", + resp[18], resp[19], resp[20], resp[21], + resp[22], resp[23], + resp[32], resp[33], resp[34], resp[35]); + + /* Wait for command complete event for our Opcode */ + if (read_hci_event(fd, resp, 100) < 0) { + perror("Failed to read init response"); + return -1; + } + + qualcomm_load_firmware(fd, fw, bdaddr); + + /* Reset */ + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x03; + cmd[2] = 0x0C; + cmd[3] = 0x00; + + do { + n = write(fd, cmd, 4); + if (n < 4) { + perror("Failed to write reset command"); + return -1; + } + + /* Read reply. */ + if ((n = read_hci_event(fd, resp, 100)) < 0) { + perror("Failed to read reset response"); + return -1; + } + + } while (resp[4] != cmd[1] && resp[5] != cmd[2]); + + nanosleep(&tm, NULL); + + return 0; +} diff --git a/tools/hciattach_st.c b/tools/hciattach_st.c new file mode 100644 index 0000000..b34964c --- /dev/null +++ b/tools/hciattach_st.c @@ -0,0 +1,278 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" + +#include "hciattach.h" + +static int debug = 0; + +static int do_command(int fd, uint8_t ogf, uint16_t ocf, + uint8_t *cparam, int clen, uint8_t *rparam, int rlen) +{ + //uint16_t opcode = (uint16_t) ((ocf & 0x03ff) | (ogf << 10)); + unsigned char cp[260], rp[260]; + int len, size, offset = 3; + + cp[0] = 0x01; + cp[1] = ocf & 0xff; + cp[2] = ogf << 2 | ocf >> 8; + cp[3] = clen; + + if (clen > 0) + memcpy(cp + 4, cparam, clen); + + if (debug) { + int i; + printf("[<"); + for (i = 0; i < clen + 4; i++) + printf(" %02x", cp[i]); + printf("]\n"); + } + + if (write(fd, cp, clen + 4) < 0) + return -1; + + do { + if (read(fd, rp, 1) < 1) + return -1; + } while (rp[0] != 0x04); + + if (read(fd, rp + 1, 2) < 2) + return -1; + + do { + len = read(fd, rp + offset, sizeof(rp) - offset); + offset += len; + } while (offset < rp[2] + 3); + + if (debug) { + int i; + printf("[>"); + for (i = 0; i < offset; i++) + printf(" %02x", rp[i]); + printf("]\n"); + } + + if (rp[0] != 0x04) { + errno = EIO; + return -1; + } + + switch (rp[1]) { + case 0x0e: /* command complete */ + if (rp[6] != 0x00) + return -ENXIO; + offset = 3 + 4; + size = rp[2] - 4; + break; + case 0x0f: /* command status */ + /* fall through */ + default: + offset = 3; + size = rp[2]; + break; + } + + if (!rparam || rlen < size) + return -ENXIO; + + memcpy(rparam, rp + offset, size); + + return size; +} + +static int load_file(int dd, uint16_t version, const char *suffix) +{ + DIR *dir; + struct dirent *d; + char pathname[PATH_MAX], filename[PATH_MAX + NAME_MAX + 1], prefix[20]; + unsigned char cmd[256]; + unsigned char buf[256]; + uint8_t seqnum = 0; + int fd, size, len, found_fw_file; + + memset(filename, 0, sizeof(filename)); + + snprintf(prefix, sizeof(prefix), "STLC2500_R%d_%02d_", + version >> 8, version & 0xff); + + strcpy(pathname, "/lib/firmware"); + dir = opendir(pathname); + if (!dir) { + strcpy(pathname, "."); + dir = opendir(pathname); + if (!dir) + return -errno; + } + + found_fw_file = 0; + while (1) { + d = readdir(dir); + if (!d) + break; + + if (strncmp(d->d_name + strlen(d->d_name) - strlen(suffix), + suffix, strlen(suffix))) + continue; + + if (strncmp(d->d_name, prefix, strlen(prefix))) + continue; + + snprintf(filename, sizeof(filename), "%s/%s", + pathname, d->d_name); + found_fw_file = 1; + } + + closedir(dir); + + if (!found_fw_file) + return -ENOENT; + + printf("Loading file %s\n", filename); + + fd = open(filename, O_RDONLY); + if (fd < 0) { + perror("Can't open firmware file"); + return -errno; + } + + while (1) { + size = read(fd, cmd + 1, 254); + if (size <= 0) + break; + + cmd[0] = seqnum; + + len = do_command(dd, 0xff, 0x002e, cmd, size + 1, buf, sizeof(buf)); + if (len < 1) + break; + + if (buf[0] != seqnum) { + fprintf(stderr, "Sequence number mismatch\n"); + break; + } + + seqnum++; + } + + close(fd); + + return 0; +} + +int stlc2500_init(int dd, bdaddr_t *bdaddr) +{ + unsigned char cmd[16]; + unsigned char buf[254]; + uint16_t version; + int len; + int err; + + /* Hci_Cmd_Ericsson_Read_Revision_Information */ + len = do_command(dd, 0xff, 0x000f, NULL, 0, buf, sizeof(buf)); + if (len < 0) + return -1; + + printf("%s\n", buf); + + /* HCI_Read_Local_Version_Information */ + len = do_command(dd, 0x04, 0x0001, NULL, 0, buf, sizeof(buf)); + if (len < 0) + return -1; + + version = buf[2] << 8 | buf[1]; + + err = load_file(dd, version, ".ptc"); + if (err < 0) { + if (err == -ENOENT) + fprintf(stderr, "No ROM patch file loaded.\n"); + else + return -1; + } + + err = load_file(dd, buf[2] << 8 | buf[1], ".ssf"); + if (err < 0) { + if (err == -ENOENT) + fprintf(stderr, "No static settings file loaded.\n"); + else + return -1; + } + + cmd[0] = 0xfe; + cmd[1] = 0x06; + bacpy((bdaddr_t *) (cmd + 2), bdaddr); + + /* Hci_Cmd_ST_Store_In_NVDS */ + len = do_command(dd, 0xff, 0x0022, cmd, 8, buf, sizeof(buf)); + if (len < 0) + return -1; + + /* HCI_Reset : applies parameters*/ + len = do_command(dd, 0x03, 0x0003, NULL, 0, buf, sizeof(buf)); + if (len < 0) + return -1; + + return 0; +} + +int bgb2xx_init(int dd, bdaddr_t *bdaddr) +{ + unsigned char cmd[16]; + unsigned char buf[254]; + int len; + + len = do_command(dd, 0xff, 0x000f, NULL, 0, buf, sizeof(buf)); + if (len < 0) + return -1; + + printf("%s\n", buf); + + cmd[0] = 0xfe; + cmd[1] = 0x06; + bacpy((bdaddr_t *) (cmd + 2), bdaddr); + + len = do_command(dd, 0xff, 0x0022, cmd, 8, buf, sizeof(buf)); + if (len < 0) + return -1; + + len = do_command(dd, 0x03, 0x0003, NULL, 0, buf, sizeof(buf)); + if (len < 0) + return -1; + + return 0; +} diff --git a/tools/hciattach_ti.c b/tools/hciattach_ti.c new file mode 100644 index 0000000..1838394 --- /dev/null +++ b/tools/hciattach_ti.c @@ -0,0 +1,534 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2007-2008 Texas Instruments, Inc. + * Copyright (C) 2005-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "hciattach.h" + +#ifdef HCIATTACH_DEBUG +#define DPRINTF(x...) printf(x) +#else +#define DPRINTF(x...) +#endif + +#define HCIUARTGETDEVICE _IOR('U', 202, int) + +#define MAKEWORD(a, b) ((uint16_t)(((uint8_t)(a)) | ((uint16_t)((uint8_t)(b))) << 8)) + +#define TI_MANUFACTURER_ID 13 + +#define FIRMWARE_DIRECTORY "/lib/firmware/ti-connectivity/" + +#define ACTION_SEND_COMMAND 1 +#define ACTION_WAIT_EVENT 2 +#define ACTION_SERIAL 3 +#define ACTION_DELAY 4 +#define ACTION_RUN_SCRIPT 5 +#define ACTION_REMARKS 6 + +#define BRF_DEEP_SLEEP_OPCODE_BYTE_1 0x0c +#define BRF_DEEP_SLEEP_OPCODE_BYTE_2 0xfd +#define BRF_DEEP_SLEEP_OPCODE \ + (BRF_DEEP_SLEEP_OPCODE_BYTE_1 | (BRF_DEEP_SLEEP_OPCODE_BYTE_2 << 8)) + +#define FILE_HEADER_MAGIC 0x42535442 + +/* + * BRF Firmware header + */ +struct bts_header { + uint32_t magic; + uint32_t version; + uint8_t future[24]; + uint8_t actions[0]; +}__attribute__ ((packed)); + +/* + * BRF Actions structure + */ +struct bts_action { + uint16_t type; + uint16_t size; + uint8_t data[0]; +} __attribute__ ((packed)); + +struct bts_action_send { + uint8_t data[0]; +} __attribute__ ((packed)); + +struct bts_action_wait { + uint32_t msec; + uint32_t size; + uint8_t data[0]; +}__attribute__ ((packed)); + +struct bts_action_delay { + uint32_t msec; +}__attribute__ ((packed)); + +struct bts_action_serial { + uint32_t baud; + uint32_t flow_control; +}__attribute__ ((packed)); + +static FILE *bts_load_script(const char *file_name, uint32_t *version) +{ + struct bts_header header; + FILE *fp; + + fp = fopen(file_name, "rb"); + if (!fp) { + perror("can't open firmware file"); + return NULL; + } + + if (1 != fread(&header, sizeof(struct bts_header), 1, fp)) { + perror("can't read firmware file"); + goto errclose; + } + + if (header.magic != FILE_HEADER_MAGIC) { + fprintf(stderr, "%s not a legal TI firmware file\n", file_name); + goto errclose; + } + + if (NULL != version) + *version = header.version; + + return fp; + +errclose: + fclose(fp); + + return NULL; +} + +static unsigned long bts_fetch_action(FILE *fp, unsigned char *action_buf, + unsigned long buf_size, uint16_t *action_type) +{ + struct bts_action action_hdr; + unsigned long nread; + + if (!fp) + return 0; + + if (1 != fread(&action_hdr, sizeof(struct bts_action), 1, fp)) + return 0; + + if (action_hdr.size > buf_size) { + fprintf(stderr, "bts_next_action: not enough space to read next action\n"); + return 0; + } + + nread = fread(action_buf, sizeof(uint8_t), action_hdr.size, fp); + if (nread != (action_hdr.size)) { + fprintf(stderr, "bts_next_action: fread failed to read next action\n"); + return 0; + } + + *action_type = action_hdr.type; + + return nread * sizeof(uint8_t); +} + +static void bts_unload_script(FILE *fp) +{ + if (fp) + fclose(fp); +} + +static int is_it_texas(const uint8_t *respond) +{ + uint16_t manufacturer_id; + + manufacturer_id = MAKEWORD(respond[11], respond[12]); + + return TI_MANUFACTURER_ID == manufacturer_id ? 1 : 0; +} + +static const char *get_firmware_name(const uint8_t *respond) +{ + static char firmware_file_name[PATH_MAX] = {0}; + uint16_t version = 0, chip = 0, min_ver = 0, maj_ver = 0; + + version = MAKEWORD(respond[13], respond[14]); + chip = (version & 0x7C00) >> 10; + min_ver = (version & 0x007F); + maj_ver = (version & 0x0380) >> 7; + + if (version & 0x8000) + maj_ver |= 0x0008; + + sprintf(firmware_file_name, FIRMWARE_DIRECTORY "TIInit_%d.%d.%d.bts", chip, maj_ver, min_ver); + + return firmware_file_name; +} + +static void brf_delay(struct bts_action_delay *delay) +{ + usleep(1000 * delay->msec); +} + +static int brf_set_serial_params(struct bts_action_serial *serial_action, + int fd, int *speed, struct termios *ti) +{ + fprintf(stderr, "texas: changing baud rate to %u, flow control to %u\n", + serial_action->baud, serial_action->flow_control ); + tcflush(fd, TCIOFLUSH); + + if (serial_action->flow_control) + ti->c_cflag |= CRTSCTS; + else + ti->c_cflag &= ~CRTSCTS; + + if (tcsetattr(fd, TCSANOW, ti) < 0) { + perror("Can't set port settings"); + return -1; + } + + tcflush(fd, TCIOFLUSH); + + if (set_speed(fd, ti, serial_action->baud) < 0) { + perror("Can't set baud rate"); + return -1; + } + + if (speed) + *speed = serial_action->baud; + + return 0; +} + +static int brf_send_command_socket(int fd, struct bts_action_send *send_action) +{ + char response[1024] = {0}; + hci_command_hdr *cmd = (hci_command_hdr *) send_action->data; + uint16_t opcode = cmd->opcode; + + struct hci_request rq; + memset(&rq, 0, sizeof(rq)); + rq.ogf = cmd_opcode_ogf(opcode); + rq.ocf = cmd_opcode_ocf(opcode); + rq.event = EVT_CMD_COMPLETE; + rq.cparam = &send_action->data[3]; + rq.clen = send_action->data[2]; + rq.rparam = response; + rq.rlen = sizeof(response); + + if (hci_send_req(fd, &rq, 15) < 0) { + perror("Cannot send hci command to socket"); + return -1; + } + + /* verify success */ + if (response[0]) { + errno = EIO; + return -1; + } + + return 0; +} + +static int brf_send_command_file(int fd, struct bts_action_send *send_action, + long size) +{ + unsigned char response[1024] = {0}; + long ret = 0; + + /* send command */ + if (size != write(fd, send_action, size)) { + perror("Texas: Failed to write action command"); + return -1; + } + + /* read response */ + ret = read_hci_event(fd, response, sizeof(response)); + if (ret < 0) { + perror("texas: failed to read command response"); + return -1; + } + + /* verify success */ + if (ret < 7 || 0 != response[6]) { + fprintf( stderr, "TI init command failed.\n" ); + errno = EIO; + return -1; + } + + return 0; +} + + +static int brf_send_command(int fd, struct bts_action_send *send_action, + long size, int hcill_installed) +{ + int ret = 0; + char *fixed_action; + + /* remove packet type when giving to socket API */ + if (hcill_installed) { + fixed_action = ((char *) send_action) + 1; + ret = brf_send_command_socket(fd, (struct bts_action_send *) fixed_action); + } else { + ret = brf_send_command_file(fd, send_action, size); + } + + return ret; +} + +static int brf_do_action(uint16_t brf_type, uint8_t *brf_action, long brf_size, + int fd, int *speed, struct termios *ti, int hcill_installed) +{ + int ret = 0; + + switch (brf_type) { + case ACTION_SEND_COMMAND: + DPRINTF("W"); + ret = brf_send_command(fd, + (struct bts_action_send *) brf_action, + brf_size, hcill_installed); + break; + case ACTION_WAIT_EVENT: + DPRINTF("R"); + break; + case ACTION_SERIAL: + DPRINTF("S"); + ret = brf_set_serial_params((struct bts_action_serial *) brf_action, fd, speed, ti); + break; + case ACTION_DELAY: + DPRINTF("D"); + brf_delay((struct bts_action_delay *) brf_action); + break; + case ACTION_REMARKS: + DPRINTF("C"); + break; + default: + fprintf(stderr, "brf_init: unknown firmware action type (%d)\n", brf_type); + break; + } + + return ret; +} + +/* + * tests whether a given brf action is a HCI_VS_Sleep_Mode_Configurations cmd + */ +static int brf_action_is_deep_sleep(uint8_t *brf_action, long brf_size, + uint16_t brf_type) +{ + uint16_t opcode; + + if (brf_type != ACTION_SEND_COMMAND) + return 0; + + if (brf_size < 3) + return 0; + + if (brf_action[0] != HCI_COMMAND_PKT) + return 0; + + /* HCI data is little endian */ + opcode = brf_action[1] | (brf_action[2] << 8); + + if (opcode != BRF_DEEP_SLEEP_OPCODE) + return 0; + + /* action is deep sleep configuration command ! */ + return 1; +} + +/* + * This function is called twice. + * The first time it is called, it loads the brf script, and executes its + * commands until it reaches a deep sleep command (or its end). + * The second time it is called, it assumes HCILL protocol is set up, + * and sends rest of brf script via the supplied socket. + */ +static int brf_do_script(int fd, int *speed, struct termios *ti, const char *bts_file) +{ + int ret = 0, hcill_installed = bts_file ? 0 : 1; + uint32_t vers; + static FILE *brf_script_file = NULL; + static uint8_t brf_action[512]; + static long brf_size; + static uint16_t brf_type; + + /* is it the first time we are called ? */ + if (0 == hcill_installed) { + DPRINTF("Sending script to serial device\n"); + brf_script_file = bts_load_script(bts_file, &vers ); + if (!brf_script_file) { + fprintf(stderr, "Warning: cannot find BTS file: %s\n", + bts_file); + return 0; + } + + fprintf( stderr, "Loaded BTS script version %u\n", vers ); + + brf_size = bts_fetch_action(brf_script_file, brf_action, + sizeof(brf_action), &brf_type); + if (brf_size == 0) { + fprintf(stderr, "Warning: BTS file is empty !"); + return 0; + } + } + else { + DPRINTF("Sending script to bluetooth socket\n"); + } + + /* execute current action and continue to parse brf script file */ + while (brf_size != 0) { + ret = brf_do_action(brf_type, brf_action, brf_size, + fd, speed, ti, hcill_installed); + if (ret == -1) + break; + + brf_size = bts_fetch_action(brf_script_file, brf_action, + sizeof(brf_action), &brf_type); + + /* if this is the first time we run (no HCILL yet) */ + /* and a deep sleep command is encountered */ + /* we exit */ + if (!hcill_installed && + brf_action_is_deep_sleep(brf_action, + brf_size, brf_type)) + return 0; + } + + bts_unload_script(brf_script_file); + brf_script_file = NULL; + DPRINTF("\n"); + + return ret; +} + +int texas_init(int fd, int *speed, struct termios *ti) +{ + struct timespec tm = {0, 50000}; + char cmd[4]; + unsigned char resp[100]; /* Response */ + const char *bts_file; + int n; + + memset(resp,'\0', 100); + + /* It is possible to get software version with manufacturer specific + HCI command HCI_VS_TI_Version_Number. But the only thing you get more + is if this is point-to-point or point-to-multipoint module */ + + /* Get Manufacturer and LMP version */ + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x01; + cmd[2] = 0x10; + cmd[3] = 0x00; + + do { + n = write(fd, cmd, 4); + if (n < 0) { + perror("Failed to write init command (READ_LOCAL_VERSION_INFORMATION)"); + return -1; + } + if (n < 4) { + fprintf(stderr, "Wanted to write 4 bytes, could only write %d. Stop\n", n); + return -1; + } + + /* Read reply. */ + if (read_hci_event(fd, resp, 100) < 0) { + perror("Failed to read init response (READ_LOCAL_VERSION_INFORMATION)"); + return -1; + } + + /* Wait for command complete event for our Opcode */ + } while (resp[4] != cmd[1] && resp[5] != cmd[2]); + + /* Verify manufacturer */ + if (! is_it_texas(resp)) { + fprintf(stderr,"ERROR: module's manufacturer is not Texas Instruments\n"); + return -1; + } + + fprintf(stderr, "Found a Texas Instruments' chip!\n"); + + bts_file = get_firmware_name(resp); + fprintf(stderr, "Firmware file : %s\n", bts_file); + + n = brf_do_script(fd, speed, ti, bts_file); + + nanosleep(&tm, NULL); + + return n; +} + +int texas_post(int fd, struct termios *ti) +{ + int dev_id, dd, ret = 0; + + sleep(1); + + dev_id = ioctl(fd, HCIUARTGETDEVICE, 0); + if (dev_id < 0) { + perror("cannot get device id"); + return -1; + } + + DPRINTF("\nAdded device hci%d\n", dev_id); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + return -1; + } + + if (ioctl(dd, HCIDEVUP, dev_id) < 0 && errno != EALREADY) { + fprintf(stderr, "Can't init device hci%d: %s (%d)", dev_id, + strerror(errno), errno); + hci_close_dev(dd); + return -1; + } + + ret = brf_do_script(dd, NULL, ti, NULL); + + hci_close_dev(dd); + + return ret; +} diff --git a/tools/hciattach_tialt.c b/tools/hciattach_tialt.c new file mode 100644 index 0000000..78498ed --- /dev/null +++ b/tools/hciattach_tialt.c @@ -0,0 +1,244 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2005-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "hciattach.h" + +#define FAILIF(x, args...) do { \ + if (x) { \ + fprintf(stderr, ##args); \ + return -1; \ + } \ +} while(0) + +typedef struct { + uint8_t uart_prefix; + hci_event_hdr hci_hdr; + evt_cmd_complete cmd_complete; + uint8_t status; + uint8_t data[16]; +} __attribute__((packed)) command_complete_t; + +static int read_command_complete(int fd, unsigned short opcode, unsigned char len) { + command_complete_t resp; + /* Read reply. */ + FAILIF(read_hci_event(fd, (unsigned char *)&resp, sizeof(resp)) < 0, + "Failed to read response"); + + /* Parse speed-change reply */ + FAILIF(resp.uart_prefix != HCI_EVENT_PKT, + "Error in response: not an event packet, but 0x%02x!\n", + resp.uart_prefix); + + FAILIF(resp.hci_hdr.evt != EVT_CMD_COMPLETE, /* event must be event-complete */ + "Error in response: not a cmd-complete event, " + "but 0x%02x!\n", resp.hci_hdr.evt); + + FAILIF(resp.hci_hdr.plen < 4, /* plen >= 4 for EVT_CMD_COMPLETE */ + "Error in response: plen is not >= 4, but 0x%02x!\n", + resp.hci_hdr.plen); + + /* cmd-complete event: opcode */ + FAILIF(resp.cmd_complete.opcode != (uint16_t)opcode, + "Error in response: opcode is 0x%04x, not 0x%04x!", + resp.cmd_complete.opcode, opcode); + + return resp.status == 0 ? 0 : -1; +} + +typedef struct { + uint8_t uart_prefix; + hci_command_hdr hci_hdr; + uint32_t speed; +} __attribute__((packed)) texas_speed_change_cmd_t; + +static int texas_change_speed(int fd, uint32_t speed) +{ + return 0; +} + +static int texas_load_firmware(int fd, const char *firmware) { + + int fw = open(firmware, O_RDONLY); + + fprintf(stdout, "Opening firmware file: %s\n", firmware); + + FAILIF(fw < 0, + "Could not open firmware file %s: %s (%d).\n", + firmware, strerror(errno), errno); + + fprintf(stdout, "Uploading firmware...\n"); + do { + /* Read each command and wait for a response. */ + unsigned char data[1024]; + unsigned char cmdp[1 + sizeof(hci_command_hdr)]; + hci_command_hdr *cmd = (hci_command_hdr *)(cmdp + 1); + int nr; + nr = read(fw, cmdp, sizeof(cmdp)); + if (!nr) + break; + FAILIF(nr != sizeof(cmdp), "Could not read H4 + HCI header!\n"); + FAILIF(*cmdp != HCI_COMMAND_PKT, "Command is not an H4 command packet!\n"); + + FAILIF(read(fw, data, cmd->plen) != cmd->plen, + "Could not read %d bytes of data for command with opcode %04x!\n", + cmd->plen, + cmd->opcode); + + { + int nw; +#if 0 + fprintf(stdout, "\topcode 0x%04x (%d bytes of data).\n", + cmd->opcode, + cmd->plen); +#endif + struct iovec iov_cmd[2]; + iov_cmd[0].iov_base = cmdp; + iov_cmd[0].iov_len = sizeof(cmdp); + iov_cmd[1].iov_base = data; + iov_cmd[1].iov_len = cmd->plen; + nw = writev(fd, iov_cmd, 2); + FAILIF(nw != (int) sizeof(cmd) + cmd->plen, + "Could not send entire command (sent only %d bytes)!\n", + nw); + } + + /* Wait for response */ + if (read_command_complete(fd, + cmd->opcode, + cmd->plen) < 0) { + return -1; + } + + } while(1); + fprintf(stdout, "Firmware upload successful.\n"); + + close(fw); + return 0; +} + +int texasalt_init(int fd, int speed, struct termios *ti) +{ + struct timespec tm = {0, 50000}; + char cmd[4]; + unsigned char resp[100]; /* Response */ + int n; + + memset(resp,'\0', 100); + + /* It is possible to get software version with manufacturer specific + HCI command HCI_VS_TI_Version_Number. But the only thing you get more + is if this is point-to-point or point-to-multipoint module */ + + /* Get Manufacturer and LMP version */ + cmd[0] = HCI_COMMAND_PKT; + cmd[1] = 0x01; + cmd[2] = 0x10; + cmd[3] = 0x00; + + do { + n = write(fd, cmd, 4); + if (n < 0) { + perror("Failed to write init command (READ_LOCAL_VERSION_INFORMATION)"); + return -1; + } + if (n < 4) { + fprintf(stderr, "Wanted to write 4 bytes, could only write %d. Stop\n", n); + return -1; + } + + /* Read reply. */ + if (read_hci_event(fd, resp, 100) < 0) { + perror("Failed to read init response (READ_LOCAL_VERSION_INFORMATION)"); + return -1; + } + + /* Wait for command complete event for our Opcode */ + } while (resp[4] != cmd[1] && resp[5] != cmd[2]); + + /* Verify manufacturer */ + if ((resp[11] & 0xFF) != 0x0d) + fprintf(stderr,"WARNING : module's manufacturer is not Texas Instrument\n"); + + /* Print LMP version */ + fprintf(stderr, "Texas module LMP version : 0x%02x\n", resp[10] & 0xFF); + + /* Print LMP subversion */ + { + unsigned short lmp_subv = resp[13] | (resp[14] << 8); + unsigned short brf_chip = (lmp_subv & 0x7c00) >> 10; + static const char *c_brf_chip[8] = { + "unknown", + "unknown", + "brf6100", + "brf6150", + "brf6300", + "brf6350", + "unknown", + "wl1271" + }; + char fw[100]; + + fprintf(stderr, "Texas module LMP sub-version : 0x%04x\n", lmp_subv); + + fprintf(stderr, + "\tinternal version freeze: %d\n" + "\tsoftware version: %d\n" + "\tchip: %s (%d)\n", + lmp_subv & 0x7f, + ((lmp_subv & 0x8000) >> (15-3)) | ((lmp_subv & 0x380) >> 7), + ((brf_chip > 7) ? "unknown" : c_brf_chip[brf_chip]), + brf_chip); + + sprintf(fw, "/etc/firmware/%s.bin", c_brf_chip[brf_chip]); + texas_load_firmware(fd, fw); + + texas_change_speed(fd, speed); + } + nanosleep(&tm, NULL); + return 0; +} diff --git a/tools/hciconfig.1 b/tools/hciconfig.1 new file mode 100644 index 0000000..633ffa3 --- /dev/null +++ b/tools/hciconfig.1 @@ -0,0 +1,272 @@ +.TH HCICONFIG 1 "Nov 11 2002" BlueZ "Linux System Administration" +.SH NAME +hciconfig \- configure Bluetooth devices +.SH SYNOPSIS +.B hciconfig +.B \-h +.br +.B hciconfig +.RB [\| \-a \|] +.br +.B hciconfig +.RB [\| \-a \|] +.B hciX +.RI [\| command +.RI [\| "command parameters" \|]\|] + +.SH DESCRIPTION +.LP +.B hciconfig +is used to configure Bluetooth devices. +.I hciX +is the name of a Bluetooth device installed in the system. If +.I hciX +is not given, +.B hciconfig +prints name and basic information about all the Bluetooth devices installed in +the system. If +.I hciX +is given but no command is given, it prints basic information on device +.I hciX +only. Basic information is +interface type, BD address, ACL MTU, SCO MTU, flags (up, init, running, raw, +page scan enabled, inquiry scan enabled, inquiry, authentication enabled, +encryption enabled). +.SH OPTIONS +.TP +.B \-h, \-\-help +Gives a list of possible commands. +.TP +.B \-a, \-\-all +Other than the basic info, print features, packet type, link policy, link mode, +name, class, version. +.SH COMMANDS +.TP +.B up +Open and initialize HCI device. +.TP +.B down +Close HCI device. +.TP +.B reset +Reset HCI device. +.TP +.B rstat +Reset statistic counters. +.TP +.B auth +Enable authentication (sets device to security mode 3). +.TP +.B noauth +Disable authentication. +.TP +.B encrypt +Enable encryption (sets device to security mode 3). +.TP +.B noencrypt +Disable encryption. +.TP +.B secmgr +Enable security manager (current kernel support is limited). +.TP +.B nosecmgr +Disable security manager. +.TP +.B piscan +Enable page and inquiry scan. +.TP +.B noscan +Disable page and inquiry scan. +.TP +.B iscan +Enable inquiry scan, disable page scan. +.TP +.B pscan +Enable page scan, disable inquiry scan. +.TP +\fBptype\fP [\fItype\fP] +With no +.I type +, displays the current packet types. Otherwise, all the packet types specified +by +.I type +are set. +.I type +is a comma-separated list of packet types, where the possible packet types are +.BR DM1 , +.BR DM3 , +.BR DM5 , +.BR DH1 , +.BR DH3 , +.BR DH5 , +.BR HV1 , +.BR HV2 , +.BR HV3 . +.TP +.BI name " [name]" +With no +.IR name , +prints local name. Otherwise, sets local name to +.IR name . +.TP +.BI class " [class]" +With no +.IR class , +prints class of device. Otherwise, sets class of device to +.IR class . +.I +class +is a 24-bit hex number describing the class of device, as specified in section +1.2 of the Bluetooth Assigned Numers document. +.TP +.BI voice " [voice]" +With no +.IR voice , +prints voice setting. Otherwise, sets voice setting to +.IR voice . +.I voice +is a 16-bit hex number describing the voice setting. +.TP +.BI iac " [iac]" +With no +.IR iac , +prints the current IAC setting. Otherwise, sets the IAC to +.IR iac . +.TP +.BI inqtpl " [level]" +With no +.IR level , +prints out the current inquiry transmit power level. Otherwise, sets +inquiry transmit power level to +.IR level . +.TP +.BI inqmode " [mode]" +With no +.IR mode , +prints out the current inquiry mode. Otherwise, sets inquiry mode to +.IR mode . +.TP +.BI inqdata " [data]" +With no +.IR name , +prints out the current inquiry data. Otherwise, sets inquiry data to +.IR data . +.TP +.BI inqtype " [type]" +With no +.IR type , +prints out the current inquiry scan type. Otherwise, sets inquiry scan type to +.IR type . +.TP +\fBinqparams\fP [\fIwin\fP:\fIint\fP] +With no +.IR win : int , +prints inquiry scan window and interval. Otherwise, sets inquiry scan window +to +.I win +slots and inquiry scan interval to +.I int +slots. +.TP +\fBpageparms\fP [\fIwin\fP:\fIint\fP] +With no +.IR win : int , +prints page scan window and interval. Otherwise, sets page scan window to +.I win +slots and page scan interval to +.I int +slots. +.TP +.BI pageto " [to]" +With no +.IR to , +prints page timeout. Otherwise, sets page timeout +to .I +to +slots. +.TP +.BI afhmode " [mode]" +With no +.IR mode , +prints out the current AFH mode. Otherwise, sets AFH mode to +.IR mode . +.TP +.BI sspmode " [mode]" +With no +.IR mode , +prints out the current Simple Pairing mode. Otherwise, sets Simple Pairing mode to +.IR mode . +.TP +\fBaclmtu\fP \fImtu\fP:\fIpkt\fP +Sets ACL MTU to +to +.I mtu +bytes and ACL buffer size to +.I pkt +packets. +.TP +\fBscomtu\fP \fImtu\fP:\fIpkt\fP +Sets SCO MTU to +.I mtu +bytes and SCO buffer size to +.I pkt +packets. +.TP +.BI delkey " " +This command deletes the stored link key for +.I bdaddr +from the device. +.TP +.BI oobdata +Get local OOB data (invalidates previously read data). +.TP +.BI commands +Display supported commands. +.TP +.BI features +Display device features. +.TP +.BI version +Display version information. +.TP +.BI revision +Display revision information. +.TP +.BI lm " [mode]" +With no +.I mode +, prints link mode. +.B MASTER +or +.B SLAVE +mean, respectively, to ask to become master or to remain slave when a +connection request comes in. The additional keyword +.B ACCEPT +means that baseband connections will be accepted even if there are no +listening +.I AF_BLUETOOTH +sockets. +.I mode +is +.B NONE +or a comma-separated list of keywords, where possible keywords are +.B MASTER +and +.B "ACCEPT" . +.B NONE +sets link policy to the default behaviour of remaining slave and not accepting +baseband connections when there are no listening +.I AF_BLUETOOTH +sockets. If +.B MASTER +is present, the device will ask to become master if a connection request comes +in. If +.B ACCEPT +is present, the device will accept baseband connections even when there are no +listening +.I AF_BLUETOOTH +sockets. +.SH AUTHORS +Written by Maxim Krasnyansky and Marcel Holtmann +.PP +man page by Fabrizio Gennari diff --git a/tools/hciconfig.c b/tools/hciconfig.c new file mode 100644 index 0000000..ddc17c4 --- /dev/null +++ b/tools/hciconfig.c @@ -0,0 +1,2065 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "src/textfile.h" +#include "src/shared/util.h" +#include "tools/csr.h" + +static struct hci_dev_info di; +static int all; + +static void print_dev_hdr(struct hci_dev_info *di); +static void print_dev_info(int ctl, struct hci_dev_info *di); + +static void print_dev_list(int ctl, int flags) +{ + struct hci_dev_list_req *dl; + struct hci_dev_req *dr; + int i; + + if (!(dl = malloc(HCI_MAX_DEV * sizeof(struct hci_dev_req) + + sizeof(uint16_t)))) { + perror("Can't allocate memory"); + exit(1); + } + dl->dev_num = HCI_MAX_DEV; + dr = dl->dev_req; + + if (ioctl(ctl, HCIGETDEVLIST, (void *) dl) < 0) { + perror("Can't get device list"); + free(dl); + exit(1); + } + + for (i = 0; i< dl->dev_num; i++) { + di.dev_id = (dr+i)->dev_id; + if (ioctl(ctl, HCIGETDEVINFO, (void *) &di) < 0) + continue; + print_dev_info(ctl, &di); + } + + free(dl); +} + +static void print_pkt_type(struct hci_dev_info *di) +{ + char *str; + str = hci_ptypetostr(di->pkt_type); + printf("\tPacket type: %s\n", str); + bt_free(str); +} + +static void print_link_policy(struct hci_dev_info *di) +{ + printf("\tLink policy: %s\n", hci_lptostr(di->link_policy)); +} + +static void print_link_mode(struct hci_dev_info *di) +{ + char *str; + str = hci_lmtostr(di->link_mode); + printf("\tLink mode: %s\n", str); + bt_free(str); +} + +static void print_dev_features(struct hci_dev_info *di, int format) +{ + printf("\tFeatures: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x " + "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", + di->features[0], di->features[1], di->features[2], + di->features[3], di->features[4], di->features[5], + di->features[6], di->features[7]); + + if (format) { + char *tmp = lmp_featurestostr(di->features, "\t\t", 63); + printf("%s\n", tmp); + bt_free(tmp); + } +} + +static void print_le_states(uint64_t states) +{ + int i; + const char *le_states[] = { + "Non-connectable Advertising State" , + "Scannable Advertising State", + "Connectable Advertising State", + "Directed Advertising State", + "Passive Scanning State", + "Active Scanning State", + "Initiating State/Connection State in Master Role", + "Connection State in the Slave Role", + "Non-connectable Advertising State and Passive Scanning State combination", + "Scannable Advertising State and Passive Scanning State combination", + "Connectable Advertising State and Passive Scanning State combination", + "Directed Advertising State and Passive Scanning State combination", + "Non-connectable Advertising State and Active Scanning State combination", + "Scannable Advertising State and Active Scanning State combination", + "Connectable Advertising State and Active Scanning State combination", + "Directed Advertising State and Active Scanning State combination", + "Non-connectable Advertising State and Initiating State combination", + "Scannable Advertising State and Initiating State combination", + "Non-connectable Advertising State and Master Role combination", + "Scannable Advertising State and Master Role combination", + "Non-connectable Advertising State and Slave Role combination", + "Scannable Advertising State and Slave Role combination", + "Passive Scanning State and Initiating State combination", + "Active Scanning State and Initiating State combination", + "Passive Scanning State and Master Role combination", + "Active Scanning State and Master Role combination", + "Passive Scanning State and Slave Role combination", + "Active Scanning State and Slave Role combination", + "Initiating State and Master Role combination/Master Role and Master Role combination", + NULL + }; + + printf("Supported link layer states:\n"); + for (i = 0; le_states[i]; i++) { + const char *status; + + status = states & (1 << i) ? "YES" : "NO "; + printf("\t%s %s\n", status, le_states[i]); + } +} + +static void cmd_rstat(int ctl, int hdev, char *opt) +{ + /* Reset HCI device stat counters */ + if (ioctl(ctl, HCIDEVRESTAT, hdev) < 0) { + fprintf(stderr, "Can't reset stats counters hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } +} + +static void cmd_scan(int ctl, int hdev, char *opt) +{ + struct hci_dev_req dr; + + dr.dev_id = hdev; + dr.dev_opt = SCAN_DISABLED; + if (!strcmp(opt, "iscan")) + dr.dev_opt = SCAN_INQUIRY; + else if (!strcmp(opt, "pscan")) + dr.dev_opt = SCAN_PAGE; + else if (!strcmp(opt, "piscan")) + dr.dev_opt = SCAN_PAGE | SCAN_INQUIRY; + + if (ioctl(ctl, HCISETSCAN, (unsigned long) &dr) < 0) { + fprintf(stderr, "Can't set scan mode on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } +} + +static void cmd_le_addr(int ctl, int hdev, char *opt) +{ + struct hci_request rq; + le_set_random_address_cp cp; + uint8_t status; + int dd, err, ret; + + if (!opt) + return; + + if (hdev < 0) + hdev = hci_get_route(NULL); + + dd = hci_open_dev(hdev); + if (dd < 0) { + err = -errno; + fprintf(stderr, "Could not open device: %s(%d)\n", + strerror(-err), -err); + exit(1); + } + + memset(&cp, 0, sizeof(cp)); + + str2ba(opt, &cp.bdaddr); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_RANDOM_ADDRESS; + rq.cparam = &cp; + rq.clen = LE_SET_RANDOM_ADDRESS_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + ret = hci_send_req(dd, &rq, 1000); + if (status || ret < 0) { + err = -errno; + fprintf(stderr, "Can't set random address for hci%d: " + "%s (%d)\n", hdev, strerror(-err), -err); + } + + hci_close_dev(dd); +} + +static void cmd_le_adv(int ctl, int hdev, char *opt) +{ + struct hci_request rq; + le_set_advertise_enable_cp advertise_cp; + le_set_advertising_parameters_cp adv_params_cp; + uint8_t status; + int dd, ret; + + if (hdev < 0) + hdev = hci_get_route(NULL); + + dd = hci_open_dev(hdev); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + memset(&adv_params_cp, 0, sizeof(adv_params_cp)); + adv_params_cp.min_interval = htobs(0x0800); + adv_params_cp.max_interval = htobs(0x0800); + if (opt) + adv_params_cp.advtype = atoi(opt); + adv_params_cp.chan_map = 7; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS; + rq.cparam = &adv_params_cp; + rq.clen = LE_SET_ADVERTISING_PARAMETERS_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + ret = hci_send_req(dd, &rq, 1000); + if (ret < 0) + goto done; + + memset(&advertise_cp, 0, sizeof(advertise_cp)); + advertise_cp.enable = 0x01; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_ADVERTISE_ENABLE; + rq.cparam = &advertise_cp; + rq.clen = LE_SET_ADVERTISE_ENABLE_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + ret = hci_send_req(dd, &rq, 1000); + +done: + hci_close_dev(dd); + + if (ret < 0) { + fprintf(stderr, "Can't set advertise mode on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (status) { + fprintf(stderr, + "LE set advertise enable on hci%d returned status %d\n", + hdev, status); + exit(1); + } +} + +static void cmd_no_le_adv(int ctl, int hdev, char *opt) +{ + struct hci_request rq; + le_set_advertise_enable_cp advertise_cp; + uint8_t status; + int dd, ret; + + if (hdev < 0) + hdev = hci_get_route(NULL); + + dd = hci_open_dev(hdev); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + memset(&advertise_cp, 0, sizeof(advertise_cp)); + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_ADVERTISE_ENABLE; + rq.cparam = &advertise_cp; + rq.clen = LE_SET_ADVERTISE_ENABLE_CP_SIZE; + rq.rparam = &status; + rq.rlen = 1; + + ret = hci_send_req(dd, &rq, 1000); + + hci_close_dev(dd); + + if (ret < 0) { + fprintf(stderr, "Can't set advertise mode on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (status) { + fprintf(stderr, "LE set advertise enable on hci%d returned status %d\n", + hdev, status); + exit(1); + } +} + +static void cmd_le_states(int ctl, int hdev, char *opt) +{ + le_read_supported_states_rp rp; + struct hci_request rq; + int err, dd; + + if (hdev < 0) + hdev = hci_get_route(NULL); + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + memset(&rp, 0, sizeof(rp)); + memset(&rq, 0, sizeof(rq)); + + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_READ_SUPPORTED_STATES; + rq.rparam = &rp; + rq.rlen = LE_READ_SUPPORTED_STATES_RP_SIZE; + + err = hci_send_req(dd, &rq, 1000); + + hci_close_dev(dd); + + if (err < 0) { + fprintf(stderr, "Can't read LE supported states on hci%d:" + " %s(%d)\n", hdev, strerror(errno), errno); + exit(1); + } + + if (rp.status) { + fprintf(stderr, "Read LE supported states on hci%d" + " returned status %d\n", hdev, rp.status); + exit(1); + } + + print_le_states(rp.states); +} + +static void cmd_iac(int ctl, int hdev, char *opt) +{ + int s = hci_open_dev(hdev); + + if (s < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + if (opt) { + int l = strtoul(opt, 0, 16); + uint8_t lap[3]; + if (!strcasecmp(opt, "giac")) { + l = 0x9e8b33; + } else if (!strcasecmp(opt, "liac")) { + l = 0x9e8b00; + } else if (l < 0x9e8b00 || l > 0x9e8b3f) { + printf("Invalid access code 0x%x\n", l); + exit(1); + } + lap[0] = (l & 0xff); + lap[1] = (l >> 8) & 0xff; + lap[2] = (l >> 16) & 0xff; + if (hci_write_current_iac_lap(s, 1, lap, 1000) < 0) { + printf("Failed to set IAC on hci%d: %s\n", hdev, strerror(errno)); + exit(1); + } + } else { + uint8_t lap[3 * MAX_IAC_LAP]; + int i, j; + uint8_t n; + if (hci_read_current_iac_lap(s, &n, lap, 1000) < 0) { + printf("Failed to read IAC from hci%d: %s\n", hdev, strerror(errno)); + exit(1); + } + print_dev_hdr(&di); + printf("\tIAC: "); + for (i = 0; i < n; i++) { + printf("0x"); + for (j = 3; j--; ) + printf("%02x", lap[j + 3 * i]); + if (i < n - 1) + printf(", "); + } + printf("\n"); + } + close(s); +} + +static void cmd_auth(int ctl, int hdev, char *opt) +{ + struct hci_dev_req dr; + + dr.dev_id = hdev; + if (!strcmp(opt, "auth")) + dr.dev_opt = AUTH_ENABLED; + else + dr.dev_opt = AUTH_DISABLED; + + if (ioctl(ctl, HCISETAUTH, (unsigned long) &dr) < 0) { + fprintf(stderr, "Can't set auth on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } +} + +static void cmd_encrypt(int ctl, int hdev, char *opt) +{ + struct hci_dev_req dr; + + dr.dev_id = hdev; + if (!strcmp(opt, "encrypt")) + dr.dev_opt = ENCRYPT_P2P; + else + dr.dev_opt = ENCRYPT_DISABLED; + + if (ioctl(ctl, HCISETENCRYPT, (unsigned long) &dr) < 0) { + fprintf(stderr, "Can't set encrypt on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } +} + +static void cmd_up(int ctl, int hdev, char *opt) +{ + /* Start HCI device */ + if (ioctl(ctl, HCIDEVUP, hdev) < 0) { + if (errno == EALREADY) + return; + fprintf(stderr, "Can't init device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } +} + +static void cmd_down(int ctl, int hdev, char *opt) +{ + /* Stop HCI device */ + if (ioctl(ctl, HCIDEVDOWN, hdev) < 0) { + fprintf(stderr, "Can't down device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } +} + +static void cmd_reset(int ctl, int hdev, char *opt) +{ + /* Reset HCI device */ +#if 0 + if (ioctl(ctl, HCIDEVRESET, hdev) < 0 ){ + fprintf(stderr, "Reset failed for device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } +#endif + cmd_down(ctl, hdev, "down"); + cmd_up(ctl, hdev, "up"); +} + +static void cmd_ptype(int ctl, int hdev, char *opt) +{ + struct hci_dev_req dr; + + dr.dev_id = hdev; + + if (hci_strtoptype(opt, &dr.dev_opt)) { + if (ioctl(ctl, HCISETPTYPE, (unsigned long) &dr) < 0) { + fprintf(stderr, "Can't set pkttype on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + print_dev_hdr(&di); + print_pkt_type(&di); + } +} + +static void cmd_lp(int ctl, int hdev, char *opt) +{ + struct hci_dev_req dr; + + dr.dev_id = hdev; + + if (hci_strtolp(opt, &dr.dev_opt)) { + if (ioctl(ctl, HCISETLINKPOL, (unsigned long) &dr) < 0) { + fprintf(stderr, "Can't set link policy on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + print_dev_hdr(&di); + print_link_policy(&di); + } +} + +static void cmd_lm(int ctl, int hdev, char *opt) +{ + struct hci_dev_req dr; + + dr.dev_id = hdev; + + if (hci_strtolm(opt, &dr.dev_opt)) { + if (ioctl(ctl, HCISETLINKMODE, (unsigned long) &dr) < 0) { + fprintf(stderr, "Can't set default link mode on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + print_dev_hdr(&di); + print_link_mode(&di); + } +} + +static void cmd_aclmtu(int ctl, int hdev, char *opt) +{ + struct hci_dev_req dr = { .dev_id = hdev }; + uint16_t mtu, mpkt; + + if (!opt) + return; + + if (sscanf(opt, "%4hu:%4hu", &mtu, &mpkt) != 2) + return; + + dr.dev_opt = htobl(htobs(mpkt) | (htobs(mtu) << 16)); + + if (ioctl(ctl, HCISETACLMTU, (unsigned long) &dr) < 0) { + fprintf(stderr, "Can't set ACL mtu on hci%d: %s(%d)\n", + hdev, strerror(errno), errno); + exit(1); + } +} + +static void cmd_scomtu(int ctl, int hdev, char *opt) +{ + struct hci_dev_req dr = { .dev_id = hdev }; + uint16_t mtu, mpkt; + + if (!opt) + return; + + if (sscanf(opt, "%4hu:%4hu", &mtu, &mpkt) != 2) + return; + + dr.dev_opt = htobl(htobs(mpkt) | (htobs(mtu) << 16)); + + if (ioctl(ctl, HCISETSCOMTU, (unsigned long) &dr) < 0) { + fprintf(stderr, "Can't set SCO mtu on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } +} + +static void cmd_features(int ctl, int hdev, char *opt) +{ + uint8_t features[8], max_page = 0; + char *tmp; + int i, dd; + + if (!(di.features[7] & LMP_EXT_FEAT)) { + print_dev_hdr(&di); + print_dev_features(&di, 1); + return; + } + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (hci_read_local_ext_features(dd, 0, &max_page, features, 1000) < 0) { + fprintf(stderr, "Can't read extended features hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (max_page < 1 && (features[6] & LMP_SIMPLE_PAIR)) + max_page = 1; + + print_dev_hdr(&di); + printf("\tFeatures%s: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x " + "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", + (max_page > 0) ? " page 0" : "", + features[0], features[1], features[2], features[3], + features[4], features[5], features[6], features[7]); + + tmp = lmp_featurestostr(di.features, "\t\t", 63); + printf("%s\n", tmp); + bt_free(tmp); + + for (i = 1; i <= max_page; i++) { + if (hci_read_local_ext_features(dd, i, NULL, + features, 1000) < 0) + continue; + + printf("\tFeatures page %d: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x " + "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", i, + features[0], features[1], features[2], features[3], + features[4], features[5], features[6], features[7]); + } + + hci_close_dev(dd); +} + +static void cmd_name(int ctl, int hdev, char *opt) +{ + int dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (opt) { + if (hci_write_local_name(dd, opt, 2000) < 0) { + fprintf(stderr, "Can't change local name on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + char name[249]; + int i; + + if (hci_read_local_name(dd, sizeof(name), name, 1000) < 0) { + fprintf(stderr, "Can't read local name on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + for (i = 0; i < 248 && name[i]; i++) { + if ((unsigned char) name[i] < 32 || name[i] == 127) + name[i] = '.'; + } + + name[248] = '\0'; + + print_dev_hdr(&di); + printf("\tName: '%s'\n", name); + } + + hci_close_dev(dd); +} + +/* + * see http://www.bluetooth.org/assigned-numbers/baseband.htm --- all + * strings are reproduced verbatim + */ +static char *get_minor_device_name(int major, int minor) +{ + switch (major) { + case 0: /* misc */ + return ""; + case 1: /* computer */ + switch (minor) { + case 0: + return "Uncategorized"; + case 1: + return "Desktop workstation"; + case 2: + return "Server"; + case 3: + return "Laptop"; + case 4: + return "Handheld"; + case 5: + return "Palm"; + case 6: + return "Wearable"; + } + break; + case 2: /* phone */ + switch (minor) { + case 0: + return "Uncategorized"; + case 1: + return "Cellular"; + case 2: + return "Cordless"; + case 3: + return "Smart phone"; + case 4: + return "Wired modem or voice gateway"; + case 5: + return "Common ISDN Access"; + case 6: + return "Sim Card Reader"; + } + break; + case 3: /* lan access */ + if (minor == 0) + return "Uncategorized"; + switch (minor / 8) { + case 0: + return "Fully available"; + case 1: + return "1-17% utilized"; + case 2: + return "17-33% utilized"; + case 3: + return "33-50% utilized"; + case 4: + return "50-67% utilized"; + case 5: + return "67-83% utilized"; + case 6: + return "83-99% utilized"; + case 7: + return "No service available"; + } + break; + case 4: /* audio/video */ + switch (minor) { + case 0: + return "Uncategorized"; + case 1: + return "Device conforms to the Headset profile"; + case 2: + return "Hands-free"; + /* 3 is reserved */ + case 4: + return "Microphone"; + case 5: + return "Loudspeaker"; + case 6: + return "Headphones"; + case 7: + return "Portable Audio"; + case 8: + return "Car Audio"; + case 9: + return "Set-top box"; + case 10: + return "HiFi Audio Device"; + case 11: + return "VCR"; + case 12: + return "Video Camera"; + case 13: + return "Camcorder"; + case 14: + return "Video Monitor"; + case 15: + return "Video Display and Loudspeaker"; + case 16: + return "Video Conferencing"; + /* 17 is reserved */ + case 18: + return "Gaming/Toy"; + } + break; + case 5: /* peripheral */ { + static char cls_str[48]; + + cls_str[0] = '\0'; + + switch (minor & 48) { + case 16: + strncpy(cls_str, "Keyboard", sizeof(cls_str)); + break; + case 32: + strncpy(cls_str, "Pointing device", sizeof(cls_str)); + break; + case 48: + strncpy(cls_str, "Combo keyboard/pointing device", sizeof(cls_str)); + break; + } + if ((minor & 15) && (strlen(cls_str) > 0)) + strcat(cls_str, "/"); + + switch (minor & 15) { + case 0: + break; + case 1: + strncat(cls_str, "Joystick", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + case 2: + strncat(cls_str, "Gamepad", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + case 3: + strncat(cls_str, "Remote control", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + case 4: + strncat(cls_str, "Sensing device", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + case 5: + strncat(cls_str, "Digitizer tablet", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + case 6: + strncat(cls_str, "Card reader", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + default: + strncat(cls_str, "(reserved)", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + } + if (strlen(cls_str) > 0) + return cls_str; + break; + } + case 6: /* imaging */ + if (minor & 4) + return "Display"; + if (minor & 8) + return "Camera"; + if (minor & 16) + return "Scanner"; + if (minor & 32) + return "Printer"; + break; + case 7: /* wearable */ + switch (minor) { + case 1: + return "Wrist Watch"; + case 2: + return "Pager"; + case 3: + return "Jacket"; + case 4: + return "Helmet"; + case 5: + return "Glasses"; + } + break; + case 8: /* toy */ + switch (minor) { + case 1: + return "Robot"; + case 2: + return "Vehicle"; + case 3: + return "Doll / Action Figure"; + case 4: + return "Controller"; + case 5: + return "Game"; + } + break; + case 63: /* uncategorised */ + return ""; + } + return "Unknown (reserved) minor device class"; +} + +static void cmd_class(int ctl, int hdev, char *opt) +{ + static const char *services[] = { "Positioning", + "Networking", + "Rendering", + "Capturing", + "Object Transfer", + "Audio", + "Telephony", + "Information" }; + static const char *major_devices[] = { "Miscellaneous", + "Computer", + "Phone", + "LAN Access", + "Audio/Video", + "Peripheral", + "Imaging", + "Uncategorized" }; + int s = hci_open_dev(hdev); + + if (s < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + if (opt) { + uint32_t cod = strtoul(opt, NULL, 16); + if (hci_write_class_of_dev(s, cod, 2000) < 0) { + fprintf(stderr, "Can't write local class of device on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + uint8_t cls[3]; + if (hci_read_class_of_dev(s, cls, 1000) < 0) { + fprintf(stderr, "Can't read class of device on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + print_dev_hdr(&di); + printf("\tClass: 0x%02x%02x%02x\n", cls[2], cls[1], cls[0]); + printf("\tService Classes: "); + if (cls[2]) { + unsigned int i; + int first = 1; + for (i = 0; i < (sizeof(services) / sizeof(*services)); i++) + if (cls[2] & (1 << i)) { + if (!first) + printf(", "); + printf("%s", services[i]); + first = 0; + } + } else + printf("Unspecified"); + printf("\n\tDevice Class: "); + if ((cls[1] & 0x1f) >= sizeof(major_devices) / sizeof(*major_devices)) + printf("Invalid Device Class!\n"); + else + printf("%s, %s\n", major_devices[cls[1] & 0x1f], + get_minor_device_name(cls[1] & 0x1f, cls[0] >> 2)); + } + + hci_close_dev(s); +} + +static void cmd_voice(int ctl, int hdev, char *opt) +{ + static char *icf[] = { "Linear", + "u-Law", + "A-Law", + "Reserved" }; + + static char *idf[] = { "1's complement", + "2's complement", + "Sign-Magnitude", + "Reserved" }; + + static char *iss[] = { "8 bit", + "16 bit" }; + + static char *acf[] = { "CVSD", + "u-Law", + "A-Law", + "Reserved" }; + + int s = hci_open_dev(hdev); + + if (s < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + if (opt) { + uint16_t vs = htobs(strtoul(opt, NULL, 16)); + if (hci_write_voice_setting(s, vs, 2000) < 0) { + fprintf(stderr, "Can't write voice setting on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + uint16_t vs; + uint8_t ic; + if (hci_read_voice_setting(s, &vs, 1000) < 0) { + fprintf(stderr, "Can't read voice setting on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + vs = htobs(vs); + ic = (vs & 0x0300) >> 8; + print_dev_hdr(&di); + printf("\tVoice setting: 0x%04x%s\n", vs, + ((vs & 0x03fc) == 0x0060) ? " (Default Condition)" : ""); + printf("\tInput Coding: %s\n", icf[ic]); + printf("\tInput Data Format: %s\n", idf[(vs & 0xc0) >> 6]); + + if (!ic) { + printf("\tInput Sample Size: %s\n", + iss[(vs & 0x20) >> 5]); + printf("\t# of bits padding at MSB: %d\n", + (vs & 0x1c) >> 2); + } + printf("\tAir Coding Format: %s\n", acf[vs & 0x03]); + } + + hci_close_dev(s); +} + +static void cmd_delkey(int ctl, int hdev, char *opt) +{ + bdaddr_t bdaddr; + uint8_t all; + int dd; + + if (!opt) + return; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (!strcasecmp(opt, "all")) { + bacpy(&bdaddr, BDADDR_ANY); + all = 1; + } else { + str2ba(opt, &bdaddr); + all = 0; + } + + if (hci_delete_stored_link_key(dd, &bdaddr, all, 1000) < 0) { + fprintf(stderr, "Can't delete stored link key on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + hci_close_dev(dd); +} + +static void cmd_oob_data(int ctl, int hdev, char *opt) +{ + uint8_t hash[16], randomizer[16]; + int i, dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (hci_read_local_oob_data(dd, hash, randomizer, 1000) < 0) { + fprintf(stderr, "Can't read local OOB data on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + print_dev_hdr(&di); + printf("\tOOB Hash: "); + for (i = 0; i < 16; i++) + printf(" %02x", hash[i]); + printf("\n\tRandomizer:"); + for (i = 0; i < 16; i++) + printf(" %02x", randomizer[i]); + printf("\n"); + + hci_close_dev(dd); +} + +static void cmd_commands(int ctl, int hdev, char *opt) +{ + uint8_t cmds[64]; + char *str; + int i, n, dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (hci_read_local_commands(dd, cmds, 1000) < 0) { + fprintf(stderr, "Can't read support commands on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + print_dev_hdr(&di); + for (i = 0; i < 64; i++) { + if (!cmds[i]) + continue; + + printf("%s Octet %-2d = 0x%02x (Bit", + i ? "\t\t ": "\tCommands:", i, cmds[i]); + for (n = 0; n < 8; n++) + if (cmds[i] & (1 << n)) + printf(" %d", n); + printf(")\n"); + } + + str = hci_commandstostr(cmds, "\t", 71); + printf("%s\n", str); + bt_free(str); + + hci_close_dev(dd); +} + +static void cmd_version(int ctl, int hdev, char *opt) +{ + struct hci_version ver; + char *hciver, *lmpver; + int dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (hci_read_local_version(dd, &ver, 1000) < 0) { + fprintf(stderr, "Can't read version info hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + hciver = hci_vertostr(ver.hci_ver); + if (((di.type & 0x30) >> 4) == HCI_PRIMARY) + lmpver = lmp_vertostr(ver.lmp_ver); + else + lmpver = pal_vertostr(ver.lmp_ver); + + print_dev_hdr(&di); + printf("\tHCI Version: %s (0x%x) Revision: 0x%x\n" + "\t%s Version: %s (0x%x) Subversion: 0x%x\n" + "\tManufacturer: %s (%d)\n", + hciver ? hciver : "n/a", ver.hci_ver, ver.hci_rev, + (((di.type & 0x30) >> 4) == HCI_PRIMARY) ? "LMP" : "PAL", + lmpver ? lmpver : "n/a", ver.lmp_ver, ver.lmp_subver, + bt_compidtostr(ver.manufacturer), ver.manufacturer); + + if (hciver) + bt_free(hciver); + if (lmpver) + bt_free(lmpver); + + hci_close_dev(dd); +} + +static void cmd_inq_tpl(int ctl, int hdev, char *opt) +{ + int dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (opt) { + int8_t level = atoi(opt); + + if (hci_write_inquiry_transmit_power_level(dd, level, 2000) < 0) { + fprintf(stderr, "Can't set inquiry transmit power level on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + int8_t level; + + if (hci_read_inq_response_tx_power_level(dd, &level, 1000) < 0) { + fprintf(stderr, "Can't read inquiry transmit power level on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + print_dev_hdr(&di); + printf("\tInquiry transmit power level: %d\n", level); + } + + hci_close_dev(dd); +} + +static void cmd_inq_mode(int ctl, int hdev, char *opt) +{ + int dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (opt) { + uint8_t mode = atoi(opt); + + if (hci_write_inquiry_mode(dd, mode, 2000) < 0) { + fprintf(stderr, "Can't set inquiry mode on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + uint8_t mode; + + if (hci_read_inquiry_mode(dd, &mode, 1000) < 0) { + fprintf(stderr, "Can't read inquiry mode on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + print_dev_hdr(&di); + printf("\tInquiry mode: "); + switch (mode) { + case 0: + printf("Standard Inquiry\n"); + break; + case 1: + printf("Inquiry with RSSI\n"); + break; + case 2: + printf("Inquiry with RSSI or Extended Inquiry\n"); + break; + default: + printf("Unknown (0x%02x)\n", mode); + break; + } + } + + hci_close_dev(dd); +} + +static void cmd_inq_data(int ctl, int hdev, char *opt) +{ + int i, dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (opt) { + uint8_t fec = 0, data[HCI_MAX_EIR_LENGTH]; + char tmp[3]; + int i, size; + + memset(data, 0, sizeof(data)); + + memset(tmp, 0, sizeof(tmp)); + size = (strlen(opt) + 1) / 2; + if (size > HCI_MAX_EIR_LENGTH) + size = HCI_MAX_EIR_LENGTH; + + for (i = 0; i < size; i++) { + memcpy(tmp, opt + (i * 2), 2); + data[i] = strtol(tmp, NULL, 16); + } + + if (hci_write_ext_inquiry_response(dd, fec, data, 2000) < 0) { + fprintf(stderr, "Can't set extended inquiry response on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + uint8_t fec, data[HCI_MAX_EIR_LENGTH], len, type, *ptr; + char *str; + + if (hci_read_ext_inquiry_response(dd, &fec, data, 1000) < 0) { + fprintf(stderr, "Can't read extended inquiry response on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + print_dev_hdr(&di); + printf("\tFEC %s\n\t\t", fec ? "enabled" : "disabled"); + for (i = 0; i < HCI_MAX_EIR_LENGTH; i++) + printf("%02x%s%s", data[i], (i + 1) % 8 ? "" : " ", + (i + 1) % 16 ? " " : (i < 239 ? "\n\t\t" : "\n")); + + ptr = data; + while (*ptr) { + len = *ptr++; + type = *ptr++; + switch (type) { + case 0x01: + printf("\tFlags:"); + for (i = 0; i < len - 1; i++) + printf(" 0x%2.2x", *((uint8_t *) (ptr + i))); + printf("\n"); + break; + case 0x02: + case 0x03: + printf("\t%s service classes:", + type == 0x02 ? "Shortened" : "Complete"); + for (i = 0; i < (len - 1) / 2; i++) { + uint16_t val = get_le16((ptr + (i * 2))); + printf(" 0x%4.4x", val); + } + printf("\n"); + break; + case 0x08: + case 0x09: + str = malloc(len); + if (str) { + snprintf(str, len, "%s", ptr); + for (i = 0; i < len - 1; i++) { + if ((unsigned char) str[i] < 32 || str[i] == 127) + str[i] = '.'; + } + printf("\t%s local name: \'%s\'\n", + type == 0x08 ? "Shortened" : "Complete", str); + free(str); + } + break; + case 0x0a: + printf("\tTX power level: %d\n", *((int8_t *) ptr)); + break; + case 0x10: + printf("\tDevice ID with %d bytes data\n", + len - 1); + break; + default: + printf("\tUnknown type 0x%02x with %d bytes data\n", + type, len - 1); + break; + } + + ptr += (len - 1); + } + + printf("\n"); + } + + hci_close_dev(dd); +} + +static void cmd_inq_type(int ctl, int hdev, char *opt) +{ + int dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (opt) { + uint8_t type = atoi(opt); + + if (hci_write_inquiry_scan_type(dd, type, 2000) < 0) { + fprintf(stderr, "Can't set inquiry scan type on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + uint8_t type; + + if (hci_read_inquiry_scan_type(dd, &type, 1000) < 0) { + fprintf(stderr, "Can't read inquiry scan type on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + print_dev_hdr(&di); + printf("\tInquiry scan type: %s\n", + type == 1 ? "Interlaced Inquiry Scan" : "Standard Inquiry Scan"); + } + + hci_close_dev(dd); +} + +static void cmd_inq_parms(int ctl, int hdev, char *opt) +{ + struct hci_request rq; + int s; + + if ((s = hci_open_dev(hdev)) < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + memset(&rq, 0, sizeof(rq)); + + if (opt) { + unsigned int window, interval; + write_inq_activity_cp cp; + + if (sscanf(opt,"%4u:%4u", &window, &interval) != 2) { + printf("Invalid argument format\n"); + exit(1); + } + + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_INQ_ACTIVITY; + rq.cparam = &cp; + rq.clen = WRITE_INQ_ACTIVITY_CP_SIZE; + + cp.window = htobs((uint16_t) window); + cp.interval = htobs((uint16_t) interval); + + if (window < 0x12 || window > 0x1000) + printf("Warning: inquiry window out of range!\n"); + + if (interval < 0x12 || interval > 0x1000) + printf("Warning: inquiry interval out of range!\n"); + + if (hci_send_req(s, &rq, 2000) < 0) { + fprintf(stderr, "Can't set inquiry parameters name on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + uint16_t window, interval; + read_inq_activity_rp rp; + + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_INQ_ACTIVITY; + rq.rparam = &rp; + rq.rlen = READ_INQ_ACTIVITY_RP_SIZE; + + if (hci_send_req(s, &rq, 1000) < 0) { + fprintf(stderr, "Can't read inquiry parameters on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + if (rp.status) { + printf("Read inquiry parameters on hci%d returned status %d\n", + hdev, rp.status); + exit(1); + } + print_dev_hdr(&di); + + window = btohs(rp.window); + interval = btohs(rp.interval); + printf("\tInquiry interval: %u slots (%.2f ms), window: %u slots (%.2f ms)\n", + interval, (float)interval * 0.625, window, (float)window * 0.625); + } + + hci_close_dev(s); +} + +static void cmd_page_parms(int ctl, int hdev, char *opt) +{ + struct hci_request rq; + int s; + + if ((s = hci_open_dev(hdev)) < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + memset(&rq, 0, sizeof(rq)); + + if (opt) { + unsigned int window, interval; + write_page_activity_cp cp; + + if (sscanf(opt,"%4u:%4u", &window, &interval) != 2) { + printf("Invalid argument format\n"); + exit(1); + } + + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_PAGE_ACTIVITY; + rq.cparam = &cp; + rq.clen = WRITE_PAGE_ACTIVITY_CP_SIZE; + + cp.window = htobs((uint16_t) window); + cp.interval = htobs((uint16_t) interval); + + if (window < 0x12 || window > 0x1000) + printf("Warning: page window out of range!\n"); + + if (interval < 0x12 || interval > 0x1000) + printf("Warning: page interval out of range!\n"); + + if (hci_send_req(s, &rq, 2000) < 0) { + fprintf(stderr, "Can't set page parameters name on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + uint16_t window, interval; + read_page_activity_rp rp; + + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_PAGE_ACTIVITY; + rq.rparam = &rp; + rq.rlen = READ_PAGE_ACTIVITY_RP_SIZE; + + if (hci_send_req(s, &rq, 1000) < 0) { + fprintf(stderr, "Can't read page parameters on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + if (rp.status) { + printf("Read page parameters on hci%d returned status %d\n", + hdev, rp.status); + exit(1); + } + print_dev_hdr(&di); + + window = btohs(rp.window); + interval = btohs(rp.interval); + printf("\tPage interval: %u slots (%.2f ms), " + "window: %u slots (%.2f ms)\n", + interval, (float)interval * 0.625, + window, (float)window * 0.625); + } + + hci_close_dev(s); +} + +static void cmd_page_to(int ctl, int hdev, char *opt) +{ + struct hci_request rq; + int s; + + if ((s = hci_open_dev(hdev)) < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + memset(&rq, 0, sizeof(rq)); + + if (opt) { + unsigned int timeout; + write_page_timeout_cp cp; + + if (sscanf(opt,"%5u", &timeout) != 1) { + printf("Invalid argument format\n"); + exit(1); + } + + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_WRITE_PAGE_TIMEOUT; + rq.cparam = &cp; + rq.clen = WRITE_PAGE_TIMEOUT_CP_SIZE; + + cp.timeout = htobs((uint16_t) timeout); + + if (timeout < 0x01 || timeout > 0xFFFF) + printf("Warning: page timeout out of range!\n"); + + if (hci_send_req(s, &rq, 2000) < 0) { + fprintf(stderr, "Can't set page timeout on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + uint16_t timeout; + read_page_timeout_rp rp; + + rq.ogf = OGF_HOST_CTL; + rq.ocf = OCF_READ_PAGE_TIMEOUT; + rq.rparam = &rp; + rq.rlen = READ_PAGE_TIMEOUT_RP_SIZE; + + if (hci_send_req(s, &rq, 1000) < 0) { + fprintf(stderr, "Can't read page timeout on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + if (rp.status) { + printf("Read page timeout on hci%d returned status %d\n", + hdev, rp.status); + exit(1); + } + print_dev_hdr(&di); + + timeout = btohs(rp.timeout); + printf("\tPage timeout: %u slots (%.2f ms)\n", + timeout, (float)timeout * 0.625); + } + + hci_close_dev(s); +} + +static void cmd_afh_mode(int ctl, int hdev, char *opt) +{ + int dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (opt) { + uint8_t mode = atoi(opt); + + if (hci_write_afh_mode(dd, mode, 2000) < 0) { + fprintf(stderr, "Can't set AFH mode on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + uint8_t mode; + + if (hci_read_afh_mode(dd, &mode, 1000) < 0) { + fprintf(stderr, "Can't read AFH mode on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + print_dev_hdr(&di); + printf("\tAFH mode: %s\n", mode == 1 ? "Enabled" : "Disabled"); + } + + hci_close_dev(dd); +} + +static void cmd_ssp_mode(int ctl, int hdev, char *opt) +{ + int dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (opt) { + uint8_t mode = atoi(opt); + + if (hci_write_simple_pairing_mode(dd, mode, 2000) < 0) { + fprintf(stderr, "Can't set Simple Pairing mode on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + } else { + uint8_t mode; + + if (hci_read_simple_pairing_mode(dd, &mode, 1000) < 0) { + fprintf(stderr, "Can't read Simple Pairing mode on hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + print_dev_hdr(&di); + printf("\tSimple Pairing mode: %s\n", + mode == 1 ? "Enabled" : "Disabled"); + } + + hci_close_dev(dd); +} + +static void print_rev_ericsson(int dd) +{ + struct hci_request rq; + unsigned char buf[102]; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x000f; + rq.cparam = NULL; + rq.clen = 0; + rq.rparam = &buf; + rq.rlen = sizeof(buf); + + if (hci_send_req(dd, &rq, 1000) < 0) { + printf("\nCan't read revision info: %s (%d)\n", + strerror(errno), errno); + return; + } + + printf("\t%s\n", buf + 1); +} + +static void print_rev_csr(int dd, uint16_t rev) +{ + uint16_t buildid, chipver, chiprev, maxkeylen, mapsco; + + if (csr_read_varid_uint16(dd, 0, CSR_VARID_BUILDID, &buildid) < 0) { + printf("\t%s\n", csr_buildidtostr(rev)); + return; + } + + printf("\t%s\n", csr_buildidtostr(buildid)); + + if (!csr_read_varid_uint16(dd, 1, CSR_VARID_CHIPVER, &chipver)) { + if (csr_read_varid_uint16(dd, 2, CSR_VARID_CHIPREV, &chiprev) < 0) + chiprev = 0; + printf("\tChip version: %s\n", csr_chipvertostr(chipver, chiprev)); + } + + if (!csr_read_varid_uint16(dd, 3, CSR_VARID_MAX_CRYPT_KEY_LENGTH, &maxkeylen)) + printf("\tMax key size: %d bit\n", maxkeylen * 8); + + if (!csr_read_pskey_uint16(dd, 4, CSR_PSKEY_HOSTIO_MAP_SCO_PCM, 0x0000, &mapsco)) + printf("\tSCO mapping: %s\n", mapsco ? "PCM" : "HCI"); +} + +static void print_rev_digianswer(int dd) +{ + struct hci_request rq; + unsigned char req[] = { 0x07 }; + unsigned char buf[102]; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_VENDOR_CMD; + rq.ocf = 0x000e; + rq.cparam = req; + rq.clen = sizeof(req); + rq.rparam = &buf; + rq.rlen = sizeof(buf); + + if (hci_send_req(dd, &rq, 1000) < 0) { + printf("\nCan't read revision info: %s (%d)\n", + strerror(errno), errno); + return; + } + + printf("\t%s\n", buf + 1); +} + +static void print_rev_broadcom(uint16_t hci_rev, uint16_t lmp_subver) +{ + printf("\tFirmware %d.%d / %d\n", + hci_rev & 0xff, lmp_subver >> 8, lmp_subver & 0xff); +} + +static void print_rev_avm(uint16_t hci_rev, uint16_t lmp_subver) +{ + if (lmp_subver == 0x01) + printf("\tFirmware 03.%d.%d\n", hci_rev >> 8, hci_rev & 0xff); + else + printf("\tUnknown type\n"); +} + +static void cmd_revision(int ctl, int hdev, char *opt) +{ + struct hci_version ver; + int dd; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + return; + } + + if (hci_read_local_version(dd, &ver, 1000) < 0) { + fprintf(stderr, "Can't read version info for hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + return; + } + + print_dev_hdr(&di); + switch (ver.manufacturer) { + case 0: + case 37: + case 48: + print_rev_ericsson(dd); + break; + case 10: + print_rev_csr(dd, ver.hci_rev); + break; + case 12: + print_rev_digianswer(dd); + break; + case 15: + print_rev_broadcom(ver.hci_rev, ver.lmp_subver); + break; + case 31: + print_rev_avm(ver.hci_rev, ver.lmp_subver); + break; + default: + printf("\tUnsupported manufacturer\n"); + break; + } + + hci_close_dev(dd); + + return; +} + +static void cmd_block(int ctl, int hdev, char *opt) +{ + bdaddr_t bdaddr; + int dd; + + if (!opt) + return; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + str2ba(opt, &bdaddr); + + if (ioctl(dd, HCIBLOCKADDR, &bdaddr) < 0) { + perror("ioctl(HCIBLOCKADDR)"); + exit(1); + } + + hci_close_dev(dd); +} + +static void cmd_unblock(int ctl, int hdev, char *opt) +{ + bdaddr_t bdaddr; + int dd; + + if (!opt) + return; + + dd = hci_open_dev(hdev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + hdev, strerror(errno), errno); + exit(1); + } + + if (!strcasecmp(opt, "all")) + bacpy(&bdaddr, BDADDR_ANY); + else + str2ba(opt, &bdaddr); + + if (ioctl(dd, HCIUNBLOCKADDR, &bdaddr) < 0) { + perror("ioctl(HCIUNBLOCKADDR)"); + exit(1); + } + + hci_close_dev(dd); +} + +static void print_dev_hdr(struct hci_dev_info *di) +{ + static int hdr = -1; + char addr[18]; + + if (hdr == di->dev_id) + return; + hdr = di->dev_id; + + ba2str(&di->bdaddr, addr); + + printf("%s:\tType: %s Bus: %s\n", di->name, + hci_typetostr((di->type & 0x30) >> 4), + hci_bustostr(di->type & 0x0f)); + printf("\tBD Address: %s ACL MTU: %d:%d SCO MTU: %d:%d\n", + addr, di->acl_mtu, di->acl_pkts, + di->sco_mtu, di->sco_pkts); +} + +static void print_dev_info(int ctl, struct hci_dev_info *di) +{ + struct hci_dev_stats *st = &di->stat; + char *str; + + print_dev_hdr(di); + + str = hci_dflagstostr(di->flags); + printf("\t%s\n", str); + bt_free(str); + + printf("\tRX bytes:%d acl:%d sco:%d events:%d errors:%d\n", + st->byte_rx, st->acl_rx, st->sco_rx, st->evt_rx, st->err_rx); + + printf("\tTX bytes:%d acl:%d sco:%d commands:%d errors:%d\n", + st->byte_tx, st->acl_tx, st->sco_tx, st->cmd_tx, st->err_tx); + + if (all && !hci_test_bit(HCI_RAW, &di->flags)) { + print_dev_features(di, 0); + + if (((di->type & 0x30) >> 4) == HCI_PRIMARY) { + print_pkt_type(di); + print_link_policy(di); + print_link_mode(di); + + if (hci_test_bit(HCI_UP, &di->flags)) { + cmd_name(ctl, di->dev_id, NULL); + cmd_class(ctl, di->dev_id, NULL); + } + } + + if (hci_test_bit(HCI_UP, &di->flags)) + cmd_version(ctl, di->dev_id, NULL); + } + + printf("\n"); +} + +static struct { + char *cmd; + void (*func)(int ctl, int hdev, char *opt); + char *opt; + char *doc; +} command[] = { + { "up", cmd_up, 0, "Open and initialize HCI device" }, + { "down", cmd_down, 0, "Close HCI device" }, + { "reset", cmd_reset, 0, "Reset HCI device" }, + { "rstat", cmd_rstat, 0, "Reset statistic counters" }, + { "auth", cmd_auth, 0, "Enable Authentication" }, + { "noauth", cmd_auth, 0, "Disable Authentication" }, + { "encrypt", cmd_encrypt, 0, "Enable Encryption" }, + { "noencrypt", cmd_encrypt, 0, "Disable Encryption" }, + { "piscan", cmd_scan, 0, "Enable Page and Inquiry scan" }, + { "noscan", cmd_scan, 0, "Disable scan" }, + { "iscan", cmd_scan, 0, "Enable Inquiry scan" }, + { "pscan", cmd_scan, 0, "Enable Page scan" }, + { "ptype", cmd_ptype, "[type]", "Get/Set default packet type" }, + { "lm", cmd_lm, "[mode]", "Get/Set default link mode" }, + { "lp", cmd_lp, "[policy]", "Get/Set default link policy" }, + { "name", cmd_name, "[name]", "Get/Set local name" }, + { "class", cmd_class, "[class]", "Get/Set class of device" }, + { "voice", cmd_voice, "[voice]", "Get/Set voice setting" }, + { "iac", cmd_iac, "[iac]", "Get/Set inquiry access code" }, + { "inqtpl", cmd_inq_tpl, "[level]", "Get/Set inquiry transmit power level" }, + { "inqmode", cmd_inq_mode, "[mode]", "Get/Set inquiry mode" }, + { "inqdata", cmd_inq_data, "[data]", "Get/Set inquiry data" }, + { "inqtype", cmd_inq_type, "[type]", "Get/Set inquiry scan type" }, + { "inqparms", cmd_inq_parms, "[win:int]", "Get/Set inquiry scan window and interval" }, + { "pageparms", cmd_page_parms, "[win:int]", "Get/Set page scan window and interval" }, + { "pageto", cmd_page_to, "[to]", "Get/Set page timeout" }, + { "afhmode", cmd_afh_mode, "[mode]", "Get/Set AFH mode" }, + { "sspmode", cmd_ssp_mode, "[mode]", "Get/Set Simple Pairing Mode" }, + { "aclmtu", cmd_aclmtu, "", "Set ACL MTU and number of packets" }, + { "scomtu", cmd_scomtu, "", "Set SCO MTU and number of packets" }, + { "delkey", cmd_delkey, "", "Delete link key from the device" }, + { "oobdata", cmd_oob_data, 0, "Get local OOB data" }, + { "commands", cmd_commands, 0, "Display supported commands" }, + { "features", cmd_features, 0, "Display device features" }, + { "version", cmd_version, 0, "Display version information" }, + { "revision", cmd_revision, 0, "Display revision information" }, + { "block", cmd_block, "", "Add a device to the blacklist" }, + { "unblock", cmd_unblock, "", "Remove a device from the blacklist" }, + { "lerandaddr", cmd_le_addr, "", "Set LE Random Address" }, + { "leadv", cmd_le_adv, "[type]", "Enable LE advertising" + "\n\t\t\t0 - Connectable undirected advertising (default)" + "\n\t\t\t3 - Non connectable undirected advertising"}, + { "noleadv", cmd_no_le_adv, 0, "Disable LE advertising" }, + { "lestates", cmd_le_states, 0, "Display the supported LE states" }, + { NULL, NULL, 0 } +}; + +static void usage(void) +{ + int i; + + printf("hciconfig - HCI device configuration utility\n"); + printf("Usage:\n" + "\thciconfig\n" + "\thciconfig [-a] hciX [command ...]\n"); + printf("Commands:\n"); + for (i = 0; command[i].cmd; i++) + printf("\t%-10s %-8s\t%s\n", command[i].cmd, + command[i].opt ? command[i].opt : " ", + command[i].doc); +} + +static struct option main_options[] = { + { "help", 0, 0, 'h' }, + { "all", 0, 0, 'a' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + int opt, ctl, i, cmd = 0; + + while ((opt = getopt_long(argc, argv, "ah", main_options, NULL)) != -1) { + switch (opt) { + case 'a': + all = 1; + break; + + case 'h': + default: + usage(); + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + /* Open HCI socket */ + if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)) < 0) { + perror("Can't open HCI socket."); + exit(1); + } + + if (argc < 1) { + print_dev_list(ctl, 0); + exit(0); + } + + di.dev_id = atoi(argv[0] + 3); + argc--; argv++; + + if (ioctl(ctl, HCIGETDEVINFO, (void *) &di)) { + perror("Can't get device info"); + exit(1); + } + + while (argc > 0) { + for (i = 0; command[i].cmd; i++) { + if (strncmp(command[i].cmd, + *argv, strlen(command[i].cmd))) + continue; + + if (command[i].opt) { + argc--; argv++; + } + + command[i].func(ctl, di.dev_id, *argv); + cmd = 1; + break; + } + + if (command[i].cmd == 0) + fprintf(stderr, "Warning: unknown command - \"%s\"\n", + *argv); + + argc--; argv++; + } + + if (!cmd) + print_dev_info(ctl, &di); + + close(ctl); + return 0; +} diff --git a/tools/hcidump.1 b/tools/hcidump.1 new file mode 100644 index 0000000..5c1441b --- /dev/null +++ b/tools/hcidump.1 @@ -0,0 +1,118 @@ +.TH HCIDUMP 1 "Nov 12 2002" BlueZ "Linux System Administration" +.SH NAME +hcidump \- Parse HCI data +.SH SYNOPSIS +.B hcidump [-h] +.br +.B hcidump [option [option...]] [filter] + +.SH DESCRIPTION +.LP +.B +hcidump +reads raw HCI data coming from and going to a Bluetooth device (which can be +specified with the option +.BR -i , +default is the first available one) and prints to screen commands, events and +data in a human-readable form. Optionally, the dump can be written to a file +rather than parsed, and the dump file can be parsed in a subsequent moment. +.SH OPTIONS +.TP +.BI -h +Prints usage info and exits +.TP +.BI -i " " +Data is read from +.IR hciX , +which must be the name of an installed Bluetooth device. If not specified, +and if +.B +-r +option is not set, data is read from the first available Bluetooth device. +.TP +.BI -l " " "\fR,\fP \-\^\-snap-len=" "" +Sets max length of processed packets to +.IR len . +.TP +.BI -p " " "\fR,\fP \-\^\-psm=" "" +Sets default Protocol Service Multiplexer to +.IR psm . +.TP +.BI -m " " "\fR,\fP \-\^\-manufacturer=" "" +Sets default company id for manufacturer to +.IR compid . +.TP +.BI -w " " "\fR,\fP \-\^\-save-dump=" "" +Parse output is not printed to screen, instead data read from device is saved in file +.IR file . +The saved dump file can be subsequently parsed with option +.BR -r . +.TP +.BI -r " " "\fR,\fP \-\^\-read-dump=" "" +Data is not read from a Bluetooth device, but from file +.IR file . +.I +file +is created with option +.BR -t ", " "\-\^\-timestamp" +Prepend a time stamp to every packet. +.TP +.BR -a ", " "\-\^\-ascii" +For every packet, not only is the packet type displayed, but also all data in ASCII. +.TP +.BR -x ", " "\-\^\-hex" +For every packet, not only is the packet type displayed, but also all data in hex. +.TP +.BR -X ", " "\-\^\-ext" +For every packet, not only is the packet type displayed, but also all data in hex and ASCII. +.TP +.BR -R ", " "\-\^\-raw" +For every packet, only the raw data is displayed. +.TP +.BR -C ", " "\-\^\-cmtp=" "" +Sets the PSM value for the CAPI Message Transport Protocol. +.TP +.BR -H ", " "\-\^\-hcrp=" "" +Sets the PSM value for the Hardcopy Control Channel. +.TP +.BR -O ", " "\-\^\-obex=" "" +Sets the RFCOMM channel value for the Object Exchange Protocol. +.TP +.BR -P ", " "\-\^\-ppp=" "" +Sets the RFCOMM channel value for the Point-to-Point Protocol. +.TP +.BR -D ", " "\-\^\-pppdump=" "" +Extract PPP traffic with pppdump format. +.TP +.BR -A ", " "\-\^\-audio=" "" +Extract SCO audio data. +.TP +.BR -Y ", " "\-\^\-novendor" +Don't display any vendor commands or events and don't show any pin code or link key in plain text. +.SH FILTERS +.B +filter +is a space-separated list of packet categories: available categories are +.IR lmp , +.IR hci , +.IR sco , +.IR l2cap , +.IR rfcomm , +.IR sdp , +.IR bnep , +.IR cmtp , +.IR hidp , +.IR hcrp , +.IR avdtp , +.IR avctp , +.IR obex , +.IR capi +and +.IR ppp . +If filters are used, only packets belonging to the specified categories are +dumped. By default, all packets are dumped. +.SH AUTHORS +Written by Maxim Krasnyansky +and Marcel Holtmann +.PP +man page by Fabrizio Gennari diff --git a/tools/hcidump.c b/tools/hcidump.c new file mode 100644 index 0000000..33d429b --- /dev/null +++ b/tools/hcidump.c @@ -0,0 +1,822 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2002 Maxim Krasnyansky + * Copyright (C) 2003-2011 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "parser/parser.h" +#include "parser/sdp.h" + +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#define SNAP_LEN HCI_MAX_FRAME_SIZE + +/* Modes */ +enum { + PARSE, + READ, + WRITE, + PPPDUMP, + AUDIO +}; + +/* Default options */ +static int snap_len = SNAP_LEN; +static int mode = PARSE; +static char *dump_file = NULL; +static char *pppdump_file = NULL; +static char *audio_file = NULL; + +struct hcidump_hdr { + uint16_t len; + uint8_t in; + uint8_t pad; + uint32_t ts_sec; + uint32_t ts_usec; +} __attribute__ ((packed)); +#define HCIDUMP_HDR_SIZE (sizeof(struct hcidump_hdr)) + +struct btsnoop_hdr { + uint8_t id[8]; /* Identification Pattern */ + uint32_t version; /* Version Number = 1 */ + uint32_t type; /* Datalink Type */ +} __attribute__ ((packed)); +#define BTSNOOP_HDR_SIZE (sizeof(struct btsnoop_hdr)) + +struct btsnoop_pkt { + uint32_t size; /* Original Length */ + uint32_t len; /* Included Length */ + uint32_t flags; /* Packet Flags */ + uint32_t drops; /* Cumulative Drops */ + uint64_t ts; /* Timestamp microseconds */ + uint8_t data[0]; /* Packet Data */ +} __attribute__ ((packed)); +#define BTSNOOP_PKT_SIZE (sizeof(struct btsnoop_pkt)) + +static uint8_t btsnoop_id[] = { 0x62, 0x74, 0x73, 0x6e, 0x6f, 0x6f, 0x70, 0x00 }; + +static uint32_t btsnoop_version = 0; +static uint32_t btsnoop_type = 0; + +struct pktlog_hdr { + uint32_t len; + uint64_t ts; + uint8_t type; +} __attribute__ ((packed)); +#define PKTLOG_HDR_SIZE (sizeof(struct pktlog_hdr)) + +static inline int read_n(int fd, char *buf, int len) +{ + int t = 0, w; + + while (len > 0) { + if ((w = read(fd, buf, len)) < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!w) + return 0; + len -= w; buf += w; t += w; + } + return t; +} + +static inline int write_n(int fd, char *buf, int len) +{ + int t = 0, w; + + while (len > 0) { + if ((w = write(fd, buf, len)) < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!w) + return 0; + len -= w; buf += w; t += w; + } + return t; +} + +static int process_frames(int dev, int sock, int fd, unsigned long flags) +{ + struct cmsghdr *cmsg; + struct msghdr msg; + struct iovec iv; + struct hcidump_hdr *dh; + struct btsnoop_pkt *dp; + struct frame frm; + struct pollfd fds[2]; + int nfds = 0; + char *buf; + char ctrl[100]; + int len, hdr_size = HCIDUMP_HDR_SIZE; + + if (sock < 0) + return -1; + + if (snap_len < SNAP_LEN) + snap_len = SNAP_LEN; + + if (flags & DUMP_BTSNOOP) + hdr_size = BTSNOOP_PKT_SIZE; + + buf = malloc(snap_len + hdr_size); + if (!buf) { + perror("Can't allocate data buffer"); + return -1; + } + + dh = (void *) buf; + dp = (void *) buf; + frm.data = buf + hdr_size; + + if (dev == HCI_DEV_NONE) + printf("system: "); + else + printf("device: hci%d ", dev); + + printf("snap_len: %d filter: 0x%lx\n", snap_len, parser.filter); + + memset(&msg, 0, sizeof(msg)); + + fds[nfds].fd = sock; + fds[nfds].events = POLLIN; + fds[nfds].revents = 0; + nfds++; + + while (1) { + int i, n = poll(fds, nfds, -1); + if (n <= 0) + continue; + + for (i = 0; i < nfds; i++) { + if (fds[i].revents & (POLLHUP | POLLERR | POLLNVAL)) { + if (fds[i].fd == sock) + printf("device: disconnected\n"); + else + printf("client: disconnect\n"); + return 0; + } + } + + iv.iov_base = frm.data; + iv.iov_len = snap_len; + + msg.msg_iov = &iv; + msg.msg_iovlen = 1; + msg.msg_control = ctrl; + msg.msg_controllen = 100; + + len = recvmsg(sock, &msg, MSG_DONTWAIT); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + perror("Receive failed"); + return -1; + } + + /* Process control message */ + frm.data_len = len; + frm.dev_id = dev; + frm.in = 0; + frm.pppdump_fd = parser.pppdump_fd; + frm.audio_fd = parser.audio_fd; + + cmsg = CMSG_FIRSTHDR(&msg); + while (cmsg) { + int dir; + switch (cmsg->cmsg_type) { + case HCI_CMSG_DIR: + memcpy(&dir, CMSG_DATA(cmsg), sizeof(int)); + frm.in = (uint8_t) dir; + break; + case HCI_CMSG_TSTAMP: + memcpy(&frm.ts, CMSG_DATA(cmsg), + sizeof(struct timeval)); + break; + } + cmsg = CMSG_NXTHDR(&msg, cmsg); + } + + frm.ptr = frm.data; + frm.len = frm.data_len; + + switch (mode) { + case WRITE: + /* Save or send dump */ + if (flags & DUMP_BTSNOOP) { + uint64_t ts; + uint8_t pkt_type = ((uint8_t *) frm.data)[0]; + dp->size = htobe32(frm.data_len); + dp->len = dp->size; + dp->flags = be32toh(frm.in & 0x01); + dp->drops = 0; + ts = (frm.ts.tv_sec - 946684800ll) * 1000000ll + frm.ts.tv_usec; + dp->ts = htobe64(ts + 0x00E03AB44A676000ll); + if (pkt_type == HCI_COMMAND_PKT || + pkt_type == HCI_EVENT_PKT) + dp->flags |= be32toh(0x02); + } else { + dh->len = htobs(frm.data_len); + dh->in = frm.in; + dh->ts_sec = htobl(frm.ts.tv_sec); + dh->ts_usec = htobl(frm.ts.tv_usec); + } + + if (write_n(fd, buf, frm.data_len + hdr_size) < 0) { + perror("Write error"); + return -1; + } + break; + + default: + /* Parse and print */ + parse(&frm); + break; + } + } + + return 0; +} + +static void read_dump(int fd) +{ + struct hcidump_hdr dh; + struct btsnoop_pkt dp; + struct pktlog_hdr ph; + struct frame frm; + int err; + + frm.data = malloc(HCI_MAX_FRAME_SIZE); + if (!frm.data) { + perror("Can't allocate data buffer"); + exit(1); + } + + while (1) { + if (parser.flags & DUMP_PKTLOG) + err = read_n(fd, (void *) &ph, PKTLOG_HDR_SIZE); + else if (parser.flags & DUMP_BTSNOOP) + err = read_n(fd, (void *) &dp, BTSNOOP_PKT_SIZE); + else + err = read_n(fd, (void *) &dh, HCIDUMP_HDR_SIZE); + + if (err < 0) + goto failed; + if (!err) + goto done; + + if (parser.flags & DUMP_PKTLOG) { + switch (ph.type) { + case 0x00: + ((uint8_t *) frm.data)[0] = HCI_COMMAND_PKT; + frm.in = 0; + break; + case 0x01: + ((uint8_t *) frm.data)[0] = HCI_EVENT_PKT; + frm.in = 1; + break; + case 0x02: + ((uint8_t *) frm.data)[0] = HCI_ACLDATA_PKT; + frm.in = 0; + break; + case 0x03: + ((uint8_t *) frm.data)[0] = HCI_ACLDATA_PKT; + frm.in = 1; + break; + default: + lseek(fd, be32toh(ph.len) - 9, SEEK_CUR); + continue; + } + + frm.data_len = be32toh(ph.len) - 8; + err = read_n(fd, frm.data + 1, frm.data_len - 1); + } else if (parser.flags & DUMP_BTSNOOP) { + uint32_t opcode; + uint8_t pkt_type; + + switch (btsnoop_type) { + case 1001: + if (be32toh(dp.flags) & 0x02) { + if (be32toh(dp.flags) & 0x01) + pkt_type = HCI_EVENT_PKT; + else + pkt_type = HCI_COMMAND_PKT; + } else + pkt_type = HCI_ACLDATA_PKT; + + ((uint8_t *) frm.data)[0] = pkt_type; + + frm.data_len = be32toh(dp.len) + 1; + err = read_n(fd, frm.data + 1, frm.data_len - 1); + break; + + case 1002: + frm.data_len = be32toh(dp.len); + err = read_n(fd, frm.data, frm.data_len); + break; + + case 2001: + opcode = be32toh(dp.flags) & 0xffff; + + switch (opcode) { + case 2: + pkt_type = HCI_COMMAND_PKT; + frm.in = 0; + break; + case 3: + pkt_type = HCI_EVENT_PKT; + frm.in = 1; + break; + case 4: + pkt_type = HCI_ACLDATA_PKT; + frm.in = 0; + break; + case 5: + pkt_type = HCI_ACLDATA_PKT; + frm.in = 1; + break; + case 6: + pkt_type = HCI_SCODATA_PKT; + frm.in = 0; + break; + case 7: + pkt_type = HCI_SCODATA_PKT; + frm.in = 1; + break; + default: + pkt_type = 0xff; + break; + } + + ((uint8_t *) frm.data)[0] = pkt_type; + + frm.data_len = be32toh(dp.len) + 1; + err = read_n(fd, frm.data + 1, frm.data_len - 1); + } + } else { + frm.data_len = btohs(dh.len); + err = read_n(fd, frm.data, frm.data_len); + } + + if (err < 0) + goto failed; + if (!err) + goto done; + + frm.ptr = frm.data; + frm.len = frm.data_len; + + if (parser.flags & DUMP_PKTLOG) { + uint64_t ts; + ts = be64toh(ph.ts); + frm.ts.tv_sec = ts >> 32; + frm.ts.tv_usec = ts & 0xffffffff; + } else if (parser.flags & DUMP_BTSNOOP) { + uint64_t ts; + frm.in = be32toh(dp.flags) & 0x01; + ts = be64toh(dp.ts) - 0x00E03AB44A676000ll; + frm.ts.tv_sec = (ts / 1000000ll) + 946684800ll; + frm.ts.tv_usec = ts % 1000000ll; + } else { + frm.in = dh.in; + frm.ts.tv_sec = btohl(dh.ts_sec); + frm.ts.tv_usec = btohl(dh.ts_usec); + } + + parse(&frm); + } + +done: + free(frm.data); + return; + +failed: + perror("Read failed"); + free(frm.data); + exit(1); +} + +static int open_file(char *file, int mode, unsigned long flags) +{ + unsigned char buf[BTSNOOP_HDR_SIZE]; + struct btsnoop_hdr *hdr = (struct btsnoop_hdr *) buf; + int fd, len, open_flags; + + if (mode == WRITE || mode == PPPDUMP || mode == AUDIO) + open_flags = O_WRONLY | O_CREAT | O_TRUNC; + else + open_flags = O_RDONLY; + + fd = open(file, open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd < 0) { + perror("Can't open dump file"); + exit(1); + } + + if (mode == READ) { + len = read(fd, buf, BTSNOOP_HDR_SIZE); + if (len != BTSNOOP_HDR_SIZE) { + lseek(fd, 0, SEEK_SET); + return fd; + } + + if (!memcmp(hdr->id, btsnoop_id, sizeof(btsnoop_id))) { + parser.flags |= DUMP_BTSNOOP; + + btsnoop_version = be32toh(hdr->version); + btsnoop_type = be32toh(hdr->type); + + printf("btsnoop version: %d datalink type: %d\n", + btsnoop_version, btsnoop_type); + + if (btsnoop_version != 1) { + fprintf(stderr, "Unsupported BTSnoop version\n"); + exit(1); + } + + if (btsnoop_type != 1001 && btsnoop_type != 1002 && + btsnoop_type != 2001) { + fprintf(stderr, "Unsupported BTSnoop datalink type\n"); + exit(1); + } + } else { + if (buf[0] == 0x00 && buf[1] == 0x00) { + parser.flags |= DUMP_PKTLOG; + printf("packet logger data format\n"); + } + + parser.flags &= ~DUMP_BTSNOOP; + lseek(fd, 0, SEEK_SET); + return fd; + } + } else { + if (flags & DUMP_BTSNOOP) { + btsnoop_version = 1; + btsnoop_type = 1002; + + memcpy(hdr->id, btsnoop_id, sizeof(btsnoop_id)); + hdr->version = htobe32(btsnoop_version); + hdr->type = htobe32(btsnoop_type); + + printf("btsnoop version: %d datalink type: %d\n", + btsnoop_version, btsnoop_type); + + len = write(fd, buf, BTSNOOP_HDR_SIZE); + if (len < 0) { + perror("Can't create dump header"); + exit(1); + } + + if (len != BTSNOOP_HDR_SIZE) { + fprintf(stderr, "Header size mismatch\n"); + exit(1); + } + } + } + + return fd; +} + +static int open_socket(int dev, unsigned long flags) +{ + struct sockaddr_hci addr; + struct hci_filter flt; + int sk, opt; + + /* Create HCI socket */ + sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (sk < 0) { + perror("Can't create raw socket"); + return -1; + } + + opt = 1; + if (setsockopt(sk, SOL_HCI, HCI_DATA_DIR, &opt, sizeof(opt)) < 0) { + perror("Can't enable data direction info"); + goto fail; + } + + opt = 1; + if (setsockopt(sk, SOL_HCI, HCI_TIME_STAMP, &opt, sizeof(opt)) < 0) { + perror("Can't enable time stamp"); + goto fail; + } + + /* Setup filter */ + hci_filter_clear(&flt); + hci_filter_all_ptypes(&flt); + hci_filter_all_events(&flt); + if (setsockopt(sk, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + perror("Can't set filter"); + goto fail; + } + + /* Bind socket to the HCI device */ + memset(&addr, 0, sizeof(addr)); + addr.hci_family = AF_BLUETOOTH; + addr.hci_dev = dev; + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + printf("Can't attach to device hci%d. %s(%d)\n", + dev, strerror(errno), errno); + goto fail; + } + + return sk; + +fail: + close(sk); + return -1; +} + +static struct { + char *name; + int flag; +} filters[] = { + { "lmp", FILT_LMP }, + { "hci", FILT_HCI }, + { "sco", FILT_SCO }, + { "l2cap", FILT_L2CAP }, + { "a2mp", FILT_A2MP }, + { "rfcomm", FILT_RFCOMM }, + { "sdp", FILT_SDP }, + { "bnep", FILT_BNEP }, + { "cmtp", FILT_CMTP }, + { "hidp", FILT_HIDP }, + { "hcrp", FILT_HCRP }, + { "att", FILT_ATT }, + { "smp", FILT_SMP }, + { "avdtp", FILT_AVDTP }, + { "avctp", FILT_AVCTP }, + { "obex", FILT_OBEX }, + { "capi", FILT_CAPI }, + { "ppp", FILT_PPP }, + { "sap", FILT_SAP }, + { "csr", FILT_CSR }, + { "dga", FILT_DGA }, + { 0 } +}; + +static unsigned long parse_filter(int argc, char **argv) +{ + unsigned long filter = 0; + int i,n; + + for (i = 0; i < argc; i++) { + for (n = 0; filters[n].name; n++) { + if (!strcasecmp(filters[n].name, argv[i])) { + filter |= filters[n].flag; + break; + } + } + } + + return filter; +} + +static void usage(void) +{ + printf( + "Usage: hcidump [OPTION...] [filter]\n" + " -i, --device=hci_dev HCI device\n" + " -l, --snap-len=len Snap len (in bytes)\n" + " -p, --psm=psm Default PSM\n" + " -m, --manufacturer=compid Default manufacturer\n" + " -w, --save-dump=file Save dump to a file\n" + " -r, --read-dump=file Read dump from a file\n" + " -t, --ts Display time stamps\n" + " -a, --ascii Dump data in ascii\n" + " -x, --hex Dump data in hex\n" + " -X, --ext Dump data in hex and ascii\n" + " -R, --raw Dump raw data\n" + " -C, --cmtp=psm PSM for CMTP\n" + " -H, --hcrp=psm PSM for HCRP\n" + " -O, --obex=port Channel/PSM for OBEX\n" + " -P, --ppp=channel Channel for PPP\n" + " -S, --sap=channel Channel for SAP\n" + " -D, --pppdump=file Extract PPP traffic\n" + " -A, --audio=file Extract SCO audio data\n" + " -Y, --novendor No vendor commands or events\n" + " -h, --help Give this help list\n" + " -v, --version Give version information\n" + " --usage Give a short usage message\n" + ); +} + +static struct option main_options[] = { + { "device", 1, 0, 'i' }, + { "snap-len", 1, 0, 'l' }, + { "psm", 1, 0, 'p' }, + { "manufacturer", 1, 0, 'm' }, + { "save-dump", 1, 0, 'w' }, + { "read-dump", 1, 0, 'r' }, + { "timestamp", 0, 0, 't' }, + { "ascii", 0, 0, 'a' }, + { "hex", 0, 0, 'x' }, + { "ext", 0, 0, 'X' }, + { "raw", 0, 0, 'R' }, + { "cmtp", 1, 0, 'C' }, + { "hcrp", 1, 0, 'H' }, + { "obex", 1, 0, 'O' }, + { "ppp", 1, 0, 'P' }, + { "sap", 1, 0, 'S' }, + { "pppdump", 1, 0, 'D' }, + { "audio", 1, 0, 'A' }, + { "novendor", 0, 0, 'Y' }, + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { 0 } +}; + +int main(int argc, char *argv[]) +{ + unsigned long flags = 0; + unsigned long filter = 0; + int device = 0; + int defpsm = 0; + int defcompid = DEFAULT_COMPID; + int opt, pppdump_fd = -1, audio_fd = -1; + uint16_t obex_port; + + while ((opt = getopt_long(argc, argv, + "i:l:p:m:w:r:taxXRC:H:O:P:S:D:A:Yhv", + main_options, NULL)) != -1) { + switch(opt) { + case 'i': + if (strcasecmp(optarg, "none") && strcasecmp(optarg, "system")) + device = atoi(optarg + 3); + else + device = HCI_DEV_NONE; + break; + + case 'l': + snap_len = atoi(optarg); + break; + + case 'p': + defpsm = atoi(optarg); + break; + + case 'm': + defcompid = atoi(optarg); + break; + + case 'w': + mode = WRITE; + dump_file = strdup(optarg); + break; + + case 'r': + mode = READ; + dump_file = strdup(optarg); + break; + + case 't': + flags |= DUMP_TSTAMP; + break; + + case 'a': + flags |= DUMP_ASCII; + break; + + case 'x': + flags |= DUMP_HEX; + break; + + case 'X': + flags |= DUMP_EXT; + break; + + case 'R': + flags |= DUMP_RAW; + break; + + case 'C': + set_proto(0, atoi(optarg), 0, SDP_UUID_CMTP); + break; + + case 'H': + set_proto(0, atoi(optarg), 0, SDP_UUID_HARDCOPY_CONTROL_CHANNEL); + break; + + case 'O': + obex_port = atoi(optarg); + if (obex_port > 31) + set_proto(0, obex_port, 0, SDP_UUID_OBEX); + else + set_proto(0, 0, obex_port, SDP_UUID_OBEX); + break; + + case 'P': + set_proto(0, 0, atoi(optarg), SDP_UUID_LAN_ACCESS_PPP); + break; + + case 'S': + set_proto(0, 0, atoi(optarg), SDP_UUID_SIM_ACCESS); + break; + + case 'D': + pppdump_file = strdup(optarg); + break; + + case 'A': + audio_file = strdup(optarg); + break; + + case 'Y': + flags |= DUMP_NOVENDOR; + break; + + case 'v': + printf("%s\n", VERSION); + exit(0); + + case 'h': + default: + usage(); + exit(0); + } + } + + argc -= optind; + argv += optind; + optind = 0; + + printf("HCI sniffer - Bluetooth packet analyzer ver %s\n", VERSION); + + if (argc > 0) + filter = parse_filter(argc, argv); + + /* Default settings */ + if (!filter) + filter = ~0L; + + if (pppdump_file) + pppdump_fd = open_file(pppdump_file, PPPDUMP, flags); + + if (audio_file) + audio_fd = open_file(audio_file, AUDIO, flags); + + switch (mode) { + case PARSE: + flags |= DUMP_VERBOSE; + init_parser(flags, filter, defpsm, defcompid, + pppdump_fd, audio_fd); + process_frames(device, open_socket(device, flags), -1, flags); + break; + + case READ: + flags |= DUMP_VERBOSE; + init_parser(flags, filter, defpsm, defcompid, + pppdump_fd, audio_fd); + read_dump(open_file(dump_file, mode, flags)); + break; + + case WRITE: + flags |= DUMP_BTSNOOP; + process_frames(device, open_socket(device, flags), + open_file(dump_file, mode, flags), flags); + break; + } + + return 0; +} diff --git a/tools/hcieventmask.c b/tools/hcieventmask.c new file mode 100644 index 0000000..b5f818d --- /dev/null +++ b/tools/hcieventmask.c @@ -0,0 +1,130 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +static struct option main_options[] = { + { "device", 1, 0, 'i' }, + { 0, 0, 0, 0 } +}; + +int main(int argc, char *argv[]) +{ + uint8_t events[8] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00 }; + struct hci_dev_info di; + struct hci_version ver; + int dd, opt, dev = 0; + + while ((opt=getopt_long(argc, argv, "+i:", main_options, NULL)) != -1) { + switch (opt) { + case 'i': + dev = hci_devid(optarg); + if (dev < 0) { + perror("Invalid device"); + exit(1); + } + break; + } + } + + dd = hci_open_dev(dev); + if (dd < 0) { + fprintf(stderr, "Can't open device hci%d: %s (%d)\n", + dev, strerror(errno), errno); + exit(1); + } + + if (hci_devinfo(dev, &di) < 0) { + fprintf(stderr, "Can't get device info for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + exit(1); + } + + if (hci_read_local_version(dd, &ver, 1000) < 0) { + fprintf(stderr, "Can't read version info for hci%d: %s (%d)\n", + dev, strerror(errno), errno); + hci_close_dev(dd); + exit(1); + } + + hci_close_dev(dd); + + if (ver.hci_ver > 1) { + if (di.features[5] & LMP_SNIFF_SUBR) + events[5] |= 0x20; + + if (di.features[5] & LMP_PAUSE_ENC) + events[5] |= 0x80; + + if (di.features[6] & LMP_EXT_INQ) + events[5] |= 0x40; + + if (di.features[6] & LMP_NFLUSH_PKTS) + events[7] |= 0x01; + + if (di.features[7] & LMP_LSTO) + events[6] |= 0x80; + + if (di.features[6] & LMP_SIMPLE_PAIR) { + events[6] |= 0x01; /* IO Capability Request */ + events[6] |= 0x02; /* IO Capability Response */ + events[6] |= 0x04; /* User Confirmation Request */ + events[6] |= 0x08; /* User Passkey Request */ + events[6] |= 0x10; /* Remote OOB Data Request */ + events[6] |= 0x20; /* Simple Pairing Complete */ + events[7] |= 0x04; /* User Passkey Notification */ + events[7] |= 0x08; /* Keypress Notification */ + events[7] |= 0x10; /* Remote Host Supported + * Features Notification */ + } + + if (di.features[4] & LMP_LE) + events[7] |= 0x20; + + if (di.features[6] & LMP_LE_BREDR) + events[7] |= 0x20; + } + + printf("Setting event mask:\n"); + printf("\thcitool cmd 0x%02x 0x%04x " + "0x%02x 0x%02x 0x%02x 0x%02x " + "0x%02x 0x%02x 0x%02x 0x%02x\n", + OGF_HOST_CTL, OCF_SET_EVENT_MASK, + events[0], events[1], events[2], events[3], + events[4], events[5], events[6], events[7]); + + return 0; +} diff --git a/tools/hcisecfilter.c b/tools/hcisecfilter.c new file mode 100644 index 0000000..18c9033 --- /dev/null +++ b/tools/hcisecfilter.c @@ -0,0 +1,155 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +int main(int argc, char *argv[]) +{ + uint32_t type_mask; + uint32_t event_mask[2]; + uint32_t ocf_mask[4]; + + /* Packet types */ + memset(&type_mask, 0, sizeof(type_mask)); + hci_set_bit(HCI_EVENT_PKT, &type_mask); + + printf("Type mask: { 0x%02x }\n", type_mask); + + /* Events */ + memset(event_mask, 0, sizeof(event_mask)); + hci_set_bit(EVT_INQUIRY_COMPLETE, event_mask); + hci_set_bit(EVT_INQUIRY_RESULT, event_mask); + hci_set_bit(EVT_CONN_COMPLETE, event_mask); + hci_set_bit(EVT_CONN_REQUEST, event_mask); + hci_set_bit(EVT_DISCONN_COMPLETE, event_mask); + hci_set_bit(EVT_AUTH_COMPLETE, event_mask); + hci_set_bit(EVT_REMOTE_NAME_REQ_COMPLETE, event_mask); + hci_set_bit(EVT_ENCRYPT_CHANGE, event_mask); + hci_set_bit(EVT_READ_REMOTE_FEATURES_COMPLETE, event_mask); + hci_set_bit(EVT_READ_REMOTE_VERSION_COMPLETE, event_mask); + hci_set_bit(EVT_CMD_COMPLETE, event_mask); + hci_set_bit(EVT_CMD_STATUS, event_mask); + hci_set_bit(EVT_READ_CLOCK_OFFSET_COMPLETE, event_mask); + hci_set_bit(EVT_INQUIRY_RESULT_WITH_RSSI, event_mask); + hci_set_bit(EVT_READ_REMOTE_EXT_FEATURES_COMPLETE, event_mask); + hci_set_bit(EVT_SYNC_CONN_COMPLETE, event_mask); + hci_set_bit(EVT_SYNC_CONN_CHANGED, event_mask); + hci_set_bit(EVT_EXTENDED_INQUIRY_RESULT, event_mask); + + printf("Event mask: { 0x%08x, 0x%08x }\n", + event_mask[0], event_mask[1]); + + /* OGF_LINK_CTL */ + memset(ocf_mask, 0, sizeof(ocf_mask)); + hci_set_bit(OCF_INQUIRY, ocf_mask); + hci_set_bit(OCF_INQUIRY_CANCEL, ocf_mask); + hci_set_bit(OCF_REMOTE_NAME_REQ, ocf_mask); + hci_set_bit(OCF_REMOTE_NAME_REQ_CANCEL, ocf_mask); + hci_set_bit(OCF_READ_REMOTE_FEATURES, ocf_mask); + hci_set_bit(OCF_READ_REMOTE_EXT_FEATURES, ocf_mask); + hci_set_bit(OCF_READ_REMOTE_VERSION, ocf_mask); + hci_set_bit(OCF_READ_CLOCK_OFFSET, ocf_mask); + hci_set_bit(OCF_READ_LMP_HANDLE, ocf_mask); + + printf("OGF_LINK_CTL: { 0x%08x, 0x%08x, 0x%08x, 0x%02x }\n", + ocf_mask[0], ocf_mask[1], ocf_mask[2], ocf_mask[3]); + + /* OGF_LINK_POLICY */ + memset(ocf_mask, 0, sizeof(ocf_mask)); + hci_set_bit(OCF_ROLE_DISCOVERY, ocf_mask); + hci_set_bit(OCF_READ_LINK_POLICY, ocf_mask); + hci_set_bit(OCF_READ_DEFAULT_LINK_POLICY, ocf_mask); + + printf("OGF_LINK_POLICY: { 0x%08x, 0x%08x, 0x%08x, 0x%02x }\n", + ocf_mask[0], ocf_mask[1], ocf_mask[2], ocf_mask[3]); + + /* OGF_HOST_CTL */ + memset(ocf_mask, 0, sizeof(ocf_mask)); + hci_set_bit(OCF_READ_PIN_TYPE, ocf_mask); + hci_set_bit(OCF_READ_LOCAL_NAME, ocf_mask); + hci_set_bit(OCF_READ_CONN_ACCEPT_TIMEOUT, ocf_mask); + hci_set_bit(OCF_READ_PAGE_TIMEOUT, ocf_mask); + hci_set_bit(OCF_READ_SCAN_ENABLE, ocf_mask); + hci_set_bit(OCF_READ_PAGE_ACTIVITY, ocf_mask); + hci_set_bit(OCF_READ_INQ_ACTIVITY, ocf_mask); + hci_set_bit(OCF_READ_AUTH_ENABLE, ocf_mask); + hci_set_bit(OCF_READ_ENCRYPT_MODE, ocf_mask); + hci_set_bit(OCF_READ_CLASS_OF_DEV, ocf_mask); + hci_set_bit(OCF_READ_VOICE_SETTING, ocf_mask); + hci_set_bit(OCF_READ_AUTOMATIC_FLUSH_TIMEOUT, ocf_mask); + hci_set_bit(OCF_READ_NUM_BROADCAST_RETRANS, ocf_mask); + hci_set_bit(OCF_READ_HOLD_MODE_ACTIVITY, ocf_mask); + hci_set_bit(OCF_READ_TRANSMIT_POWER_LEVEL, ocf_mask); + hci_set_bit(OCF_READ_LINK_SUPERVISION_TIMEOUT, ocf_mask); + hci_set_bit(OCF_READ_NUM_SUPPORTED_IAC, ocf_mask); + hci_set_bit(OCF_READ_CURRENT_IAC_LAP, ocf_mask); + hci_set_bit(OCF_READ_PAGE_SCAN_PERIOD_MODE, ocf_mask); + hci_set_bit(OCF_READ_PAGE_SCAN_MODE, ocf_mask); + hci_set_bit(OCF_READ_INQUIRY_SCAN_TYPE, ocf_mask); + hci_set_bit(OCF_READ_INQUIRY_MODE, ocf_mask); + hci_set_bit(OCF_READ_PAGE_SCAN_TYPE, ocf_mask); + hci_set_bit(OCF_READ_AFH_MODE, ocf_mask); + hci_set_bit(OCF_READ_EXT_INQUIRY_RESPONSE, ocf_mask); + hci_set_bit(OCF_READ_SIMPLE_PAIRING_MODE, ocf_mask); + hci_set_bit(OCF_READ_INQ_RESPONSE_TX_POWER_LEVEL, ocf_mask); + hci_set_bit(OCF_READ_DEFAULT_ERROR_DATA_REPORTING, ocf_mask); + + printf("OGF_HOST_CTL: { 0x%08x, 0x%08x, 0x%08x, 0x%02x }\n", + ocf_mask[0], ocf_mask[1], ocf_mask[2], ocf_mask[3]); + + /* OGF_INFO_PARAM */ + memset(ocf_mask, 0, sizeof(ocf_mask)); + hci_set_bit(OCF_READ_LOCAL_VERSION, ocf_mask); + hci_set_bit(OCF_READ_LOCAL_COMMANDS, ocf_mask); + hci_set_bit(OCF_READ_LOCAL_FEATURES, ocf_mask); + hci_set_bit(OCF_READ_LOCAL_EXT_FEATURES, ocf_mask); + hci_set_bit(OCF_READ_BUFFER_SIZE, ocf_mask); + hci_set_bit(OCF_READ_COUNTRY_CODE, ocf_mask); + hci_set_bit(OCF_READ_BD_ADDR, ocf_mask); + + printf("OGF_INFO_PARAM: { 0x%08x, 0x%08x, 0x%08x, 0x%02x }\n", + ocf_mask[0], ocf_mask[1], ocf_mask[2], ocf_mask[3]); + + /* OGF_STATUS_PARAM */ + memset(ocf_mask, 0, sizeof(ocf_mask)); + hci_set_bit(OCF_READ_FAILED_CONTACT_COUNTER, ocf_mask); + hci_set_bit(OCF_READ_LINK_QUALITY, ocf_mask); + hci_set_bit(OCF_READ_RSSI, ocf_mask); + hci_set_bit(OCF_READ_AFH_MAP, ocf_mask); + hci_set_bit(OCF_READ_CLOCK, ocf_mask); + + printf("OGF_STATUS_PARAM: { 0x%08x, 0x%08x, 0x%08x, 0x%02x }\n", + ocf_mask[0], ocf_mask[1], ocf_mask[2], ocf_mask[3]); + + return 0; +} diff --git a/tools/hcitool.1 b/tools/hcitool.1 new file mode 100644 index 0000000..7d06556 --- /dev/null +++ b/tools/hcitool.1 @@ -0,0 +1,255 @@ +.TH HCITOOL 1 "Nov 12 2002" BlueZ "Linux System Administration" +.SH NAME +hcitool \- configure Bluetooth connections +.SH SYNOPSIS +.B hcitool [-h] +.br +.B hcitool [-i ] [command [command parameters]] + +.SH DESCRIPTION +.LP +.B +hcitool +is used to configure Bluetooth connections and send some special command to +Bluetooth devices. If no +.B +command +is given, or if the option +.B +-h +is used, +.B +hcitool +prints some usage information and exits. +.SH OPTIONS +.TP +.BI -h +Gives a list of possible commands +.TP +.BI -i " " +The command is applied to device +.I +hciX +, which must be the name of an installed Bluetooth device. If not specified, +the command will be sent to the first available Bluetooth device. +.SH COMMANDS +.TP +.BI dev +Display local devices +.TP +.BI inq +Inquire remote devices. For each discovered device, Bluetooth device address, +clock offset and class are printed. +.TP +.BI scan +Inquire remote devices. For each discovered device, device name are printed. +.TP +.BI name " " +Print device name of remote device with Bluetooth address +.IR bdaddr . +.TP +.BI info " " +Print device name, version and supported features of remote device with +Bluetooth address +.IR bdaddr . +.TP +.BI spinq +Start periodic inquiry process. No inquiry results are printed. +.TP +.BI epinq +Exit periodic inquiry process. +.TP +.BI cmd " [parameters]" +Submit an arbitrary HCI command to local device. +.IR ogf , +.IR ocf +and +.IR parameters +are hexadecimal bytes. +.TP +.BI con +Display active baseband connections +.TP +.BI cc " [--role=m|s] [--pkt-type=] " +Create baseband connection to remote device with Bluetooth address +.IR bdaddr . +Option +.I +--pkt-type +specifies a list of allowed packet types. +.I + +is a comma-separated list of packet types, where the possible packet types are +.BR DM1 , +.BR DM3 , +.BR DM5 , +.BR DH1 , +.BR DH3 , +.BR DH5 , +.BR HV1 , +.BR HV2 , +.BR HV3 . +Default is to allow all packet types. Option +.I +--role +can have value +.I +m +(do not allow role switch, stay master) or +.I +s +(allow role switch, become slave if the peer asks to become master). Default is +.IR m . +.TP +.BI dc " [reason]" +Delete baseband connection from remote device with Bluetooth address +.IR bdaddr . +The reason can be one of the Bluetooth HCI error codes. Default is +.IR 19 +for user ended connections. The value must be given in decimal. +.TP +.BI sr " " +Switch role for the baseband connection from the remote device to +.BR master +or +.BR slave . +.TP +.BI cpt " " +Change packet types for baseband connection to device with Bluetooth address +.IR bdaddr . +.I +packet types +is a comma-separated list of packet types, where the possible packet types are +.BR DM1 , +.BR DM3 , +.BR DM5 , +.BR DH1 , +.BR DH3 , +.BR DH5 , +.BR HV1 , +.BR HV2 , +.BR HV3 . +.TP +.BI rssi " " +Display received signal strength information for the connection to the device +with Bluetooth address +.IR bdaddr . +.TP +.BI lq " " +Display link quality for the connection to the device with Bluetooth address +.IR bdaddr . +.TP +.BI tpl " [type]" +Display transmit power level for the connection to the device with Bluetooth address +.IR bdaddr . +The type can be +.BR 0 +for the current transmit power level (which is default) or +.BR 1 +for the maximum transmit power level. +.TP +.BI afh " " +Display AFH channel map for the connection to the device with Bluetooth address +.IR bdaddr . +.TP +.BI lp " [value]" +With no +.IR value , +displays link policy settings for the connection to the device with Bluetooth address +.IR bdaddr . +If +.IR value +is given, sets the link policy settings for that connection to +.IR value . +Possible values are RSWITCH, HOLD, SNIFF and PARK. +.TP +.BI lst " [value]" +With no +.IR value , +displays link supervision timeout for the connection to the device with Bluetooth address +.IR bdaddr . +If +.I +value +is given, sets the link supervision timeout for that connection to +.I +value +slots, or to infinite if +.I +value +is 0. +.TP +.BI auth " " +Request authentication for the device with Bluetooth address +.IR bdaddr . +.TP +.BI enc " [encrypt enable]" +Enable or disable the encryption for the device with Bluetooth address +.IR bdaddr . +.TP +.BI key " " +Change the connection link key for the device with Bluetooth address +.IR bdaddr . +.TP +.BI clkoff " " +Read the clock offset for the device with Bluetooth address +.IR bdaddr . +.TP +.BI clock " [bdaddr] [which clock]" +Read the clock for the device with Bluetooth address +.IR bdaddr . +The clock can be +.BR 0 +for the local clock or +.BR 1 +for the piconet clock (which is default). +.TP +.BI lescan " [--privacy] [--passive] [--whitelist] [--discovery=g|l] \ +[--duplicates]" +Start LE scan +.TP +.BI leinfo " [--static] [--random] " +Get LE remote information +.TP +.BI lewladd " [--random] " +Add device to LE White List +.TP +.BI lewlrm " " +Remove device from LE White List +.TP +.BI lewlsz +Read size of LE White List +.TP +.BI lewlclr +Clear LE White List +.TP +.BI lerladd " [--local irk] [--peer irk] [--random] " +Add device to LE Resolving List +.TP +.BI lerlrm " " +Remove device from LE Resolving List +.TP +.BI lerlclr +Clear LE Resolving List +.TP +.BI lerlsz +Read size of LE Resolving List +.TP +.BI lerlon +Enable LE Address Resolution +.TP +.BI lerloff +Disable LE Address Resolution +.TP +.BI lecc " [--static] [--random] | [--whitelist]" +Create a LE Connection +.TP +.BI ledc " [reason]" +Disconnect a LE Connection +.TP +.BI lecup " " +LE Connection Update +.SH AUTHORS +Written by Maxim Krasnyansky and Marcel Holtmann +.PP +man page by Fabrizio Gennari diff --git a/tools/hcitool.c b/tools/hcitool.c new file mode 100644 index 0000000..9250c41 --- /dev/null +++ b/tools/hcitool.c @@ -0,0 +1,3502 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" + +#include "src/oui.h" + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +/* Unofficial value, might still change */ +#define LE_LINK 0x80 + +#define FLAGS_AD_TYPE 0x01 +#define FLAGS_LIMITED_MODE_BIT 0x01 +#define FLAGS_GENERAL_MODE_BIT 0x02 + +#define EIR_FLAGS 0x01 /* flags */ +#define EIR_UUID16_SOME 0x02 /* 16-bit UUID, more available */ +#define EIR_UUID16_ALL 0x03 /* 16-bit UUID, all listed */ +#define EIR_UUID32_SOME 0x04 /* 32-bit UUID, more available */ +#define EIR_UUID32_ALL 0x05 /* 32-bit UUID, all listed */ +#define EIR_UUID128_SOME 0x06 /* 128-bit UUID, more available */ +#define EIR_UUID128_ALL 0x07 /* 128-bit UUID, all listed */ +#define EIR_NAME_SHORT 0x08 /* shortened local name */ +#define EIR_NAME_COMPLETE 0x09 /* complete local name */ +#define EIR_TX_POWER 0x0A /* transmit power level */ +#define EIR_DEVICE_ID 0x10 /* device ID */ + +#define for_each_opt(opt, long, short) while ((opt=getopt_long(argc, argv, short ? short:"+", long, NULL)) != -1) + +static volatile int signal_received = 0; + +static void usage(void); + +static int str2buf(const char *str, uint8_t *buf, size_t blen) +{ + int i, dlen; + + if (str == NULL) + return -EINVAL; + + memset(buf, 0, blen); + + dlen = MIN((strlen(str) / 2), blen); + + for (i = 0; i < dlen; i++) + sscanf(str + (i * 2), "%02hhX", &buf[i]); + + return 0; +} + +static int dev_info(int s, int dev_id, long arg) +{ + struct hci_dev_info di = { .dev_id = dev_id }; + char addr[18]; + + if (ioctl(s, HCIGETDEVINFO, (void *) &di)) + return 0; + + ba2str(&di.bdaddr, addr); + printf("\t%s\t%s\n", di.name, addr); + return 0; +} + +static void helper_arg(int min_num_arg, int max_num_arg, int *argc, + char ***argv, const char *usage) +{ + *argc -= optind; + /* too many arguments, but when "max_num_arg < min_num_arg" then no + limiting (prefer "max_num_arg=-1" to gen infinity) + */ + if ( (*argc > max_num_arg) && (max_num_arg >= min_num_arg ) ) { + fprintf(stderr, "%s: too many arguments (maximal: %i)\n", + *argv[0], max_num_arg); + printf("%s", usage); + exit(1); + } + + /* print usage */ + if (*argc < min_num_arg) { + fprintf(stderr, "%s: too few arguments (minimal: %i)\n", + *argv[0], min_num_arg); + printf("%s", usage); + exit(0); + } + + *argv += optind; +} + +static char *type2str(uint8_t type) +{ + switch (type) { + case SCO_LINK: + return "SCO"; + case ACL_LINK: + return "ACL"; + case ESCO_LINK: + return "eSCO"; + case LE_LINK: + return "LE"; + default: + return "Unknown"; + } +} + +static int conn_list(int s, int dev_id, long arg) +{ + struct hci_conn_list_req *cl; + struct hci_conn_info *ci; + int id = arg; + int i; + + if (id != -1 && dev_id != id) + return 0; + + if (!(cl = malloc(10 * sizeof(*ci) + sizeof(*cl)))) { + perror("Can't allocate memory"); + exit(1); + } + cl->dev_id = dev_id; + cl->conn_num = 10; + ci = cl->conn_info; + + if (ioctl(s, HCIGETCONNLIST, (void *) cl)) { + perror("Can't get connection list"); + exit(1); + } + + for (i = 0; i < cl->conn_num; i++, ci++) { + char addr[18]; + char *str; + ba2str(&ci->bdaddr, addr); + str = hci_lmtostr(ci->link_mode); + printf("\t%s %s %s handle %d state %d lm %s\n", + ci->out ? "<" : ">", type2str(ci->type), + addr, ci->handle, ci->state, str); + bt_free(str); + } + + free(cl); + return 0; +} + +static int find_conn(int s, int dev_id, long arg) +{ + struct hci_conn_list_req *cl; + struct hci_conn_info *ci; + int i; + + if (!(cl = malloc(10 * sizeof(*ci) + sizeof(*cl)))) { + perror("Can't allocate memory"); + exit(1); + } + cl->dev_id = dev_id; + cl->conn_num = 10; + ci = cl->conn_info; + + if (ioctl(s, HCIGETCONNLIST, (void *) cl)) { + perror("Can't get connection list"); + exit(1); + } + + for (i = 0; i < cl->conn_num; i++, ci++) + if (!bacmp((bdaddr_t *) arg, &ci->bdaddr)) { + free(cl); + return 1; + } + + free(cl); + return 0; +} + +static void hex_dump(char *pref, int width, unsigned char *buf, int len) +{ + register int i,n; + + for (i = 0, n = 1; i < len; i++, n++) { + if (n == 1) + printf("%s", pref); + printf("%2.2X ", buf[i]); + if (n == width) { + printf("\n"); + n = 0; + } + } + if (i && n!=1) + printf("\n"); +} + +static char *get_minor_device_name(int major, int minor) +{ + switch (major) { + case 0: /* misc */ + return ""; + case 1: /* computer */ + switch (minor) { + case 0: + return "Uncategorized"; + case 1: + return "Desktop workstation"; + case 2: + return "Server"; + case 3: + return "Laptop"; + case 4: + return "Handheld"; + case 5: + return "Palm"; + case 6: + return "Wearable"; + } + break; + case 2: /* phone */ + switch (minor) { + case 0: + return "Uncategorized"; + case 1: + return "Cellular"; + case 2: + return "Cordless"; + case 3: + return "Smart phone"; + case 4: + return "Wired modem or voice gateway"; + case 5: + return "Common ISDN Access"; + case 6: + return "Sim Card Reader"; + } + break; + case 3: /* lan access */ + if (minor == 0) + return "Uncategorized"; + switch (minor / 8) { + case 0: + return "Fully available"; + case 1: + return "1-17% utilized"; + case 2: + return "17-33% utilized"; + case 3: + return "33-50% utilized"; + case 4: + return "50-67% utilized"; + case 5: + return "67-83% utilized"; + case 6: + return "83-99% utilized"; + case 7: + return "No service available"; + } + break; + case 4: /* audio/video */ + switch (minor) { + case 0: + return "Uncategorized"; + case 1: + return "Device conforms to the Headset profile"; + case 2: + return "Hands-free"; + /* 3 is reserved */ + case 4: + return "Microphone"; + case 5: + return "Loudspeaker"; + case 6: + return "Headphones"; + case 7: + return "Portable Audio"; + case 8: + return "Car Audio"; + case 9: + return "Set-top box"; + case 10: + return "HiFi Audio Device"; + case 11: + return "VCR"; + case 12: + return "Video Camera"; + case 13: + return "Camcorder"; + case 14: + return "Video Monitor"; + case 15: + return "Video Display and Loudspeaker"; + case 16: + return "Video Conferencing"; + /* 17 is reserved */ + case 18: + return "Gaming/Toy"; + } + break; + case 5: /* peripheral */ { + static char cls_str[48]; cls_str[0] = 0; + + switch (minor & 48) { + case 16: + strncpy(cls_str, "Keyboard", sizeof(cls_str)); + break; + case 32: + strncpy(cls_str, "Pointing device", sizeof(cls_str)); + break; + case 48: + strncpy(cls_str, "Combo keyboard/pointing device", sizeof(cls_str)); + break; + } + if ((minor & 15) && (strlen(cls_str) > 0)) + strcat(cls_str, "/"); + + switch (minor & 15) { + case 0: + break; + case 1: + strncat(cls_str, "Joystick", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + case 2: + strncat(cls_str, "Gamepad", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + case 3: + strncat(cls_str, "Remote control", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + case 4: + strncat(cls_str, "Sensing device", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + case 5: + strncat(cls_str, "Digitizer tablet", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + case 6: + strncat(cls_str, "Card reader", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + default: + strncat(cls_str, "(reserved)", + sizeof(cls_str) - strlen(cls_str) - 1); + break; + } + if (strlen(cls_str) > 0) + return cls_str; + break; + } + case 6: /* imaging */ + if (minor & 4) + return "Display"; + if (minor & 8) + return "Camera"; + if (minor & 16) + return "Scanner"; + if (minor & 32) + return "Printer"; + break; + case 7: /* wearable */ + switch (minor) { + case 1: + return "Wrist Watch"; + case 2: + return "Pager"; + case 3: + return "Jacket"; + case 4: + return "Helmet"; + case 5: + return "Glasses"; + } + break; + case 8: /* toy */ + switch (minor) { + case 1: + return "Robot"; + case 2: + return "Vehicle"; + case 3: + return "Doll / Action Figure"; + case 4: + return "Controller"; + case 5: + return "Game"; + } + break; + case 63: /* uncategorised */ + return ""; + } + return "Unknown (reserved) minor device class"; +} + +static char *major_classes[] = { + "Miscellaneous", "Computer", "Phone", "LAN Access", + "Audio/Video", "Peripheral", "Imaging", "Uncategorized" +}; + +/* Display local devices */ + +static struct option dev_options[] = { + { "help", 0, 0, 'h' }, + {0, 0, 0, 0 } +}; + +static const char *dev_help = + "Usage:\n" + "\tdev\n"; + +static void cmd_dev(int dev_id, int argc, char **argv) +{ + int opt; + + for_each_opt(opt, dev_options, NULL) { + switch (opt) { + default: + printf("%s", dev_help); + return; + } + } + helper_arg(0, 0, &argc, &argv, dev_help); + + printf("Devices:\n"); + + hci_for_each_dev(HCI_UP, dev_info, 0); +} + +/* Inquiry */ + +static struct option inq_options[] = { + { "help", 0, 0, 'h' }, + { "length", 1, 0, 'l' }, + { "numrsp", 1, 0, 'n' }, + { "iac", 1, 0, 'i' }, + { "flush", 0, 0, 'f' }, + { 0, 0, 0, 0 } +}; + +static const char *inq_help = + "Usage:\n" + "\tinq [--length=N] maximum inquiry duration in 1.28 s units\n" + "\t [--numrsp=N] specify maximum number of inquiry responses\n" + "\t [--iac=lap] specify the inquiry access code\n" + "\t [--flush] flush the inquiry cache\n"; + +static void cmd_inq(int dev_id, int argc, char **argv) +{ + inquiry_info *info = NULL; + uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; + int num_rsp, length, flags; + char addr[18]; + int i, l, opt; + + length = 8; /* ~10 seconds */ + num_rsp = 0; + flags = 0; + + for_each_opt(opt, inq_options, NULL) { + switch (opt) { + case 'l': + length = atoi(optarg); + break; + + case 'n': + num_rsp = atoi(optarg); + break; + + case 'i': + l = strtoul(optarg, 0, 16); + if (!strcasecmp(optarg, "giac")) { + l = 0x9e8b33; + } else if (!strcasecmp(optarg, "liac")) { + l = 0x9e8b00; + } if (l < 0x9e8b00 || l > 0x9e8b3f) { + printf("Invalid access code 0x%x\n", l); + exit(1); + } + lap[0] = (l & 0xff); + lap[1] = (l >> 8) & 0xff; + lap[2] = (l >> 16) & 0xff; + break; + + case 'f': + flags |= IREQ_CACHE_FLUSH; + break; + + default: + printf("%s", inq_help); + return; + } + } + helper_arg(0, 0, &argc, &argv, inq_help); + + printf("Inquiring ...\n"); + + num_rsp = hci_inquiry(dev_id, length, num_rsp, lap, &info, flags); + if (num_rsp < 0) { + perror("Inquiry failed."); + exit(1); + } + + for (i = 0; i < num_rsp; i++) { + ba2str(&(info+i)->bdaddr, addr); + printf("\t%s\tclock offset: 0x%4.4x\tclass: 0x%2.2x%2.2x%2.2x\n", + addr, btohs((info+i)->clock_offset), + (info+i)->dev_class[2], + (info+i)->dev_class[1], + (info+i)->dev_class[0]); + } + + bt_free(info); +} + +/* Device scanning */ + +static struct option scan_options[] = { + { "help", 0, 0, 'h' }, + { "length", 1, 0, 'l' }, + { "numrsp", 1, 0, 'n' }, + { "iac", 1, 0, 'i' }, + { "flush", 0, 0, 'f' }, + { "class", 0, 0, 'C' }, + { "info", 0, 0, 'I' }, + { "oui", 0, 0, 'O' }, + { "all", 0, 0, 'A' }, + { "ext", 0, 0, 'A' }, + { 0, 0, 0, 0 } +}; + +static const char *scan_help = + "Usage:\n" + "\tscan [--length=N] [--numrsp=N] [--iac=lap] [--flush] [--class] [--info] [--oui] [--refresh]\n"; + +static void cmd_scan(int dev_id, int argc, char **argv) +{ + inquiry_info *info = NULL; + uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; + int num_rsp, length, flags; + uint8_t cls[3], features[8]; + char addr[18], name[249], *comp; + struct hci_version version; + struct hci_dev_info di; + struct hci_conn_info_req *cr; + int extcls = 0, extinf = 0, extoui = 0; + int i, n, l, opt, dd, cc; + + length = 8; /* ~10 seconds */ + num_rsp = 0; + flags = 0; + + for_each_opt(opt, scan_options, NULL) { + switch (opt) { + case 'l': + length = atoi(optarg); + break; + + case 'n': + num_rsp = atoi(optarg); + break; + + case 'i': + l = strtoul(optarg, 0, 16); + if (!strcasecmp(optarg, "giac")) { + l = 0x9e8b33; + } else if (!strcasecmp(optarg, "liac")) { + l = 0x9e8b00; + } else if (l < 0x9e8b00 || l > 0x9e8b3f) { + printf("Invalid access code 0x%x\n", l); + exit(1); + } + lap[0] = (l & 0xff); + lap[1] = (l >> 8) & 0xff; + lap[2] = (l >> 16) & 0xff; + break; + + case 'f': + flags |= IREQ_CACHE_FLUSH; + break; + + case 'C': + extcls = 1; + break; + + case 'I': + extinf = 1; + break; + + case 'O': + extoui = 1; + break; + + case 'A': + extcls = 1; + extinf = 1; + extoui = 1; + break; + + default: + printf("%s", scan_help); + return; + } + } + helper_arg(0, 0, &argc, &argv, scan_help); + + if (dev_id < 0) { + dev_id = hci_get_route(NULL); + if (dev_id < 0) { + perror("Device is not available"); + exit(1); + } + } + + if (hci_devinfo(dev_id, &di) < 0) { + perror("Can't get device info"); + exit(1); + } + + printf("Scanning ...\n"); + num_rsp = hci_inquiry(dev_id, length, num_rsp, lap, &info, flags); + if (num_rsp < 0) { + perror("Inquiry failed"); + exit(1); + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + free(info); + exit(1); + } + + if (extcls || extinf || extoui) + printf("\n"); + + for (i = 0; i < num_rsp; i++) { + uint16_t handle = 0; + + if (!extcls && !extinf && !extoui) { + ba2str(&(info+i)->bdaddr, addr); + + if (hci_read_remote_name_with_clock_offset(dd, + &(info+i)->bdaddr, + (info+i)->pscan_rep_mode, + (info+i)->clock_offset | 0x8000, + sizeof(name), name, 100000) < 0) + strcpy(name, "n/a"); + + for (n = 0; n < 248 && name[n]; n++) { + if ((unsigned char) name[i] < 32 || name[i] == 127) + name[i] = '.'; + } + + name[248] = '\0'; + + printf("\t%s\t%s\n", addr, name); + continue; + } + + ba2str(&(info+i)->bdaddr, addr); + printf("BD Address:\t%s [mode %d, clkoffset 0x%4.4x]\n", addr, + (info+i)->pscan_rep_mode, btohs((info+i)->clock_offset)); + + if (extoui) { + comp = batocomp(&(info+i)->bdaddr); + if (comp) { + char oui[9]; + ba2oui(&(info+i)->bdaddr, oui); + printf("OUI company:\t%s (%s)\n", comp, oui); + free(comp); + } + } + + cc = 0; + + if (extinf) { + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (cr) { + bacpy(&cr->bdaddr, &(info+i)->bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + handle = 0; + cc = 1; + } else { + handle = htobs(cr->conn_info->handle); + cc = 0; + } + free(cr); + } + + if (cc) { + if (hci_create_connection(dd, &(info+i)->bdaddr, + htobs(di.pkt_type & ACL_PTYPE_MASK), + (info+i)->clock_offset | 0x8000, + 0x01, &handle, 25000) < 0) { + handle = 0; + cc = 0; + } + } + } + + if (hci_read_remote_name_with_clock_offset(dd, + &(info+i)->bdaddr, + (info+i)->pscan_rep_mode, + (info+i)->clock_offset | 0x8000, + sizeof(name), name, 100000) < 0) { + } else { + for (n = 0; n < 248 && name[n]; n++) { + if ((unsigned char) name[i] < 32 || name[i] == 127) + name[i] = '.'; + } + + name[248] = '\0'; + } + + if (strlen(name) > 0) + printf("Device name:\t%s\n", name); + + if (extcls) { + memcpy(cls, (info+i)->dev_class, 3); + printf("Device class:\t"); + if ((cls[1] & 0x1f) > sizeof(major_classes) / sizeof(char *)) + printf("Invalid"); + else + printf("%s, %s", major_classes[cls[1] & 0x1f], + get_minor_device_name(cls[1] & 0x1f, cls[0] >> 2)); + printf(" (0x%2.2x%2.2x%2.2x)\n", cls[2], cls[1], cls[0]); + } + + if (extinf && handle > 0) { + if (hci_read_remote_version(dd, handle, &version, 20000) == 0) { + char *ver = lmp_vertostr(version.lmp_ver); + printf("Manufacturer:\t%s (%d)\n", + bt_compidtostr(version.manufacturer), + version.manufacturer); + printf("LMP version:\t%s (0x%x) [subver 0x%x]\n", + ver ? ver : "n/a", + version.lmp_ver, version.lmp_subver); + if (ver) + bt_free(ver); + } + + if (hci_read_remote_features(dd, handle, features, 20000) == 0) { + char *tmp = lmp_featurestostr(features, "\t\t", 63); + printf("LMP features:\t0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x" + " 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", + features[0], features[1], + features[2], features[3], + features[4], features[5], + features[6], features[7]); + printf("%s\n", tmp); + bt_free(tmp); + } + + if (cc) { + usleep(10000); + hci_disconnect(dd, handle, HCI_OE_USER_ENDED_CONNECTION, 10000); + } + } + + printf("\n"); + } + + bt_free(info); + + hci_close_dev(dd); +} + +/* Remote name */ + +static struct option name_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *name_help = + "Usage:\n" + "\tname \n"; + +static void cmd_name(int dev_id, int argc, char **argv) +{ + bdaddr_t bdaddr; + char name[248]; + int opt, dd; + + for_each_opt(opt, name_options, NULL) { + switch (opt) { + default: + printf("%s", name_help); + return; + } + } + helper_arg(1, 1, &argc, &argv, name_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_get_route(&bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Device is not available.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + if (hci_read_remote_name(dd, &bdaddr, sizeof(name), name, 25000) == 0) + printf("%s\n", name); + + hci_close_dev(dd); +} + +/* Info about remote device */ + +static struct option info_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *info_help = + "Usage:\n" + "\tinfo \n"; + +static void cmd_info(int dev_id, int argc, char **argv) +{ + bdaddr_t bdaddr; + uint16_t handle; + uint8_t features[8], max_page = 0; + char name[249], *comp, *tmp; + struct hci_version version; + struct hci_dev_info di; + struct hci_conn_info_req *cr; + int i, opt, dd, cc = 0; + + for_each_opt(opt, info_options, NULL) { + switch (opt) { + default: + printf("%s", info_help); + return; + } + } + helper_arg(1, 1, &argc, &argv, info_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + + if (dev_id < 0) + dev_id = hci_get_route(&bdaddr); + + if (dev_id < 0) { + fprintf(stderr, "Device is not available or not connected.\n"); + exit(1); + } + + if (hci_devinfo(dev_id, &di) < 0) { + perror("Can't get device info"); + exit(1); + } + + printf("Requesting information ...\n"); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't get connection info"); + close(dd); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + if (hci_create_connection(dd, &bdaddr, + htobs(di.pkt_type & ACL_PTYPE_MASK), + 0, 0x01, &handle, 25000) < 0) { + perror("Can't create connection"); + free(cr); + close(dd); + exit(1); + } + sleep(1); + cc = 1; + } else + handle = htobs(cr->conn_info->handle); + + free(cr); + + printf("\tBD Address: %s\n", argv[0]); + + comp = batocomp(&bdaddr); + if (comp) { + char oui[9]; + ba2oui(&bdaddr, oui); + printf("\tOUI Company: %s (%s)\n", comp, oui); + free(comp); + } + + if (hci_read_remote_name(dd, &bdaddr, sizeof(name), name, 25000) == 0) + printf("\tDevice Name: %s\n", name); + + if (hci_read_remote_version(dd, handle, &version, 20000) == 0) { + char *ver = lmp_vertostr(version.lmp_ver); + printf("\tLMP Version: %s (0x%x) LMP Subversion: 0x%x\n" + "\tManufacturer: %s (%d)\n", + ver ? ver : "n/a", + version.lmp_ver, + version.lmp_subver, + bt_compidtostr(version.manufacturer), + version.manufacturer); + if (ver) + bt_free(ver); + } + + memset(features, 0, sizeof(features)); + hci_read_remote_features(dd, handle, features, 20000); + + if ((di.features[7] & LMP_EXT_FEAT) && (features[7] & LMP_EXT_FEAT)) + hci_read_remote_ext_features(dd, handle, 0, &max_page, + features, 20000); + + if (max_page < 1 && (features[6] & LMP_SIMPLE_PAIR)) + max_page = 1; + + printf("\tFeatures%s: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x " + "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", + (max_page > 0) ? " page 0" : "", + features[0], features[1], features[2], features[3], + features[4], features[5], features[6], features[7]); + + tmp = lmp_featurestostr(features, "\t\t", 63); + printf("%s\n", tmp); + bt_free(tmp); + + for (i = 1; i <= max_page; i++) { + if (hci_read_remote_ext_features(dd, handle, i, NULL, + features, 20000) < 0) + continue; + + printf("\tFeatures page %d: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x " + "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", i, + features[0], features[1], features[2], features[3], + features[4], features[5], features[6], features[7]); + } + + if (cc) { + usleep(10000); + hci_disconnect(dd, handle, HCI_OE_USER_ENDED_CONNECTION, 10000); + } + + hci_close_dev(dd); +} + +/* Start periodic inquiry */ + +static struct option spinq_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *spinq_help = + "Usage:\n" + "\tspinq\n"; + +static void cmd_spinq(int dev_id, int argc, char **argv) +{ + uint8_t lap[3] = { 0x33, 0x8b, 0x9e }; + struct hci_request rq; + periodic_inquiry_cp cp; + int opt, dd; + + for_each_opt(opt, spinq_options, NULL) { + switch (opt) { + default: + printf("%s", spinq_help); + return; + } + } + helper_arg(0, 0, &argc, &argv, spinq_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Device open failed"); + exit(EXIT_FAILURE); + } + + memset(&cp, 0, sizeof(cp)); + memcpy(cp.lap, lap, 3); + cp.max_period = htobs(16); + cp.min_period = htobs(10); + cp.length = 8; + cp.num_rsp = 0; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_PERIODIC_INQUIRY; + rq.cparam = &cp; + rq.clen = PERIODIC_INQUIRY_CP_SIZE; + + if (hci_send_req(dd, &rq, 100) < 0) { + perror("Periodic inquiry failed"); + exit(EXIT_FAILURE); + } + + hci_close_dev(dd); +} + +/* Exit periodic inquiry */ + +static struct option epinq_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *epinq_help = + "Usage:\n" + "\tepinq\n"; + +static void cmd_epinq(int dev_id, int argc, char **argv) +{ + int opt, dd; + + for_each_opt(opt, epinq_options, NULL) { + switch (opt) { + default: + printf("%s", epinq_help); + return; + } + } + helper_arg(0, 0, &argc, &argv, epinq_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Device open failed"); + exit(EXIT_FAILURE); + } + + if (hci_send_cmd(dd, OGF_LINK_CTL, + OCF_EXIT_PERIODIC_INQUIRY, 0, NULL) < 0) { + perror("Exit periodic inquiry failed"); + exit(EXIT_FAILURE); + } + + hci_close_dev(dd); +} + +/* Send arbitrary HCI commands */ + +static struct option cmd_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *cmd_help = + "Usage:\n" + "\tcmd [parameters]\n" + "Example:\n" + "\tcmd 0x03 0x0013 0x41 0x42 0x43 0x44\n"; + +static void cmd_cmd(int dev_id, int argc, char **argv) +{ + unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr = buf; + struct hci_filter flt; + hci_event_hdr *hdr; + int i, opt, len, dd; + uint16_t ocf; + uint8_t ogf; + + for_each_opt(opt, cmd_options, NULL) { + switch (opt) { + default: + printf("%s", cmd_help); + return; + } + } + helper_arg(2, -1, &argc, &argv, cmd_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + errno = 0; + ogf = strtol(argv[0], NULL, 16); + ocf = strtol(argv[1], NULL, 16); + if (errno == ERANGE || (ogf > 0x3f) || (ocf > 0x3ff)) { + printf("%s", cmd_help); + return; + } + + for (i = 2, len = 0; i < argc && len < (int) sizeof(buf); i++, len++) + *ptr++ = (uint8_t) strtol(argv[i], NULL, 16); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Device open failed"); + exit(EXIT_FAILURE); + } + + /* Setup filter */ + hci_filter_clear(&flt); + hci_filter_set_ptype(HCI_EVENT_PKT, &flt); + hci_filter_all_events(&flt); + if (setsockopt(dd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + perror("HCI filter setup failed"); + exit(EXIT_FAILURE); + } + + printf("< HCI Command: ogf 0x%02x, ocf 0x%04x, plen %d\n", ogf, ocf, len); + hex_dump(" ", 20, buf, len); fflush(stdout); + + if (hci_send_cmd(dd, ogf, ocf, len, buf) < 0) { + perror("Send failed"); + exit(EXIT_FAILURE); + } + + len = read(dd, buf, sizeof(buf)); + if (len < 0) { + perror("Read failed"); + exit(EXIT_FAILURE); + } + + hdr = (void *)(buf + 1); + ptr = buf + (1 + HCI_EVENT_HDR_SIZE); + len -= (1 + HCI_EVENT_HDR_SIZE); + + printf("> HCI Event: 0x%02x plen %d\n", hdr->evt, hdr->plen); + hex_dump(" ", 20, ptr, len); fflush(stdout); + + hci_close_dev(dd); +} + +/* Display active connections */ + +static struct option con_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *con_help = + "Usage:\n" + "\tcon\n"; + +static void cmd_con(int dev_id, int argc, char **argv) +{ + int opt; + + for_each_opt(opt, con_options, NULL) { + switch (opt) { + default: + printf("%s", con_help); + return; + } + } + helper_arg(0, 0, &argc, &argv, con_help); + + printf("Connections:\n"); + + hci_for_each_dev(HCI_UP, conn_list, dev_id); +} + +/* Create connection */ + +static struct option cc_options[] = { + { "help", 0, 0, 'h' }, + { "role", 1, 0, 'r' }, + { "ptype", 1, 0, 'p' }, + { 0, 0, 0, 0 } +}; + +static const char *cc_help = + "Usage:\n" + "\tcc [--role=m|s] [--ptype=pkt_types] \n" + "Example:\n" + "\tcc --ptype=dm1,dh3,dh5 01:02:03:04:05:06\n" + "\tcc --role=m 01:02:03:04:05:06\n"; + +static void cmd_cc(int dev_id, int argc, char **argv) +{ + bdaddr_t bdaddr; + uint16_t handle; + uint8_t role; + unsigned int ptype; + int dd, opt; + + role = 0x01; + ptype = HCI_DM1 | HCI_DM3 | HCI_DM5 | HCI_DH1 | HCI_DH3 | HCI_DH5; + + for_each_opt(opt, cc_options, NULL) { + switch (opt) { + case 'p': + hci_strtoptype(optarg, &ptype); + break; + + case 'r': + role = optarg[0] == 'm' ? 0 : 1; + break; + + default: + printf("%s", cc_help); + return; + } + } + helper_arg(1, 1, &argc, &argv, cc_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_get_route(&bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Device is not available.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + if (hci_create_connection(dd, &bdaddr, htobs(ptype), + htobs(0x0000), role, &handle, 25000) < 0) + perror("Can't create connection"); + + hci_close_dev(dd); +} + +/* Close connection */ + +static struct option dc_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *dc_help = + "Usage:\n" + "\tdc [reason]\n"; + +static void cmd_dc(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + uint8_t reason; + int opt, dd; + + for_each_opt(opt, dc_options, NULL) { + switch (opt) { + default: + printf("%s", dc_help); + return; + } + } + helper_arg(1, 2, &argc, &argv, dc_help); + + str2ba(argv[0], &bdaddr); + reason = (argc > 1) ? atoi(argv[1]) : HCI_OE_USER_ENDED_CONNECTION; + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + if (hci_disconnect(dd, htobs(cr->conn_info->handle), + reason, 10000) < 0) + perror("Disconnect failed"); + + free(cr); + + hci_close_dev(dd); +} + +/* Role switch */ + +static struct option sr_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *sr_help = + "Usage:\n" + "\tsr \n"; + +static void cmd_sr(int dev_id, int argc, char **argv) +{ + bdaddr_t bdaddr; + uint8_t role; + int opt, dd; + + for_each_opt(opt, sr_options, NULL) { + switch (opt) { + default: + printf("%s", sr_help); + return; + } + } + helper_arg(2, 2, &argc, &argv, sr_help); + + str2ba(argv[0], &bdaddr); + switch (argv[1][0]) { + case 'm': + role = 0; + break; + case 's': + role = 1; + break; + default: + role = atoi(argv[1]); + break; + } + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + if (hci_switch_role(dd, &bdaddr, role, 10000) < 0) { + perror("Switch role request failed"); + exit(1); + } + + hci_close_dev(dd); +} + +/* Read RSSI */ + +static struct option rssi_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *rssi_help = + "Usage:\n" + "\trssi \n"; + +static void cmd_rssi(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + int8_t rssi; + int opt, dd; + + for_each_opt(opt, rssi_options, NULL) { + switch (opt) { + default: + printf("%s", rssi_help); + return; + } + } + helper_arg(1, 1, &argc, &argv, rssi_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + if (hci_read_rssi(dd, htobs(cr->conn_info->handle), &rssi, 1000) < 0) { + perror("Read RSSI failed"); + exit(1); + } + + printf("RSSI return value: %d\n", rssi); + + free(cr); + + hci_close_dev(dd); +} + +/* Get link quality */ + +static struct option lq_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lq_help = + "Usage:\n" + "\tlq \n"; + +static void cmd_lq(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + uint8_t lq; + int opt, dd; + + for_each_opt(opt, lq_options, NULL) { + switch (opt) { + default: + printf("%s", lq_help); + return; + } + } + helper_arg(1, 1, &argc, &argv, lq_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + if (hci_read_link_quality(dd, htobs(cr->conn_info->handle), &lq, 1000) < 0) { + perror("HCI read_link_quality request failed"); + exit(1); + } + + printf("Link quality: %d\n", lq); + + free(cr); + + hci_close_dev(dd); +} + +/* Get transmit power level */ + +static struct option tpl_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *tpl_help = + "Usage:\n" + "\ttpl [type]\n"; + +static void cmd_tpl(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + uint8_t type; + int8_t level; + int opt, dd; + + for_each_opt(opt, tpl_options, NULL) { + switch (opt) { + default: + printf("%s", tpl_help); + return; + } + } + helper_arg(1, 2, &argc, &argv, tpl_help); + + str2ba(argv[0], &bdaddr); + type = (argc > 1) ? atoi(argv[1]) : 0; + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + if (hci_read_transmit_power_level(dd, htobs(cr->conn_info->handle), type, &level, 1000) < 0) { + perror("HCI read transmit power level request failed"); + exit(1); + } + + printf("%s transmit power level: %d\n", + (type == 0) ? "Current" : "Maximum", level); + + free(cr); + + hci_close_dev(dd); +} + +/* Get AFH channel map */ + +static struct option afh_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *afh_help = + "Usage:\n" + "\tafh \n"; + +static void cmd_afh(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + uint16_t handle; + uint8_t mode, map[10]; + int opt, dd; + + for_each_opt(opt, afh_options, NULL) { + switch (opt) { + default: + printf("%s", afh_help); + return; + } + } + helper_arg(1, 1, &argc, &argv, afh_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + handle = htobs(cr->conn_info->handle); + + if (hci_read_afh_map(dd, handle, &mode, map, 1000) < 0) { + perror("HCI read AFH map request failed"); + exit(1); + } + + if (mode == 0x01) { + int i; + printf("AFH map: 0x"); + for (i = 0; i < 10; i++) + printf("%02x", map[i]); + printf("\n"); + } else + printf("AFH disabled\n"); + + free(cr); + + hci_close_dev(dd); +} + +/* Set connection packet type */ + +static struct option cpt_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *cpt_help = + "Usage:\n" + "\tcpt \n"; + +static void cmd_cpt(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + struct hci_request rq; + set_conn_ptype_cp cp; + evt_conn_ptype_changed rp; + bdaddr_t bdaddr; + unsigned int ptype; + int dd, opt; + + for_each_opt(opt, cpt_options, NULL) { + switch (opt) { + default: + printf("%s", cpt_help); + return; + } + } + helper_arg(2, 2, &argc, &argv, cpt_help); + + str2ba(argv[0], &bdaddr); + hci_strtoptype(argv[1], &ptype); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + cp.handle = htobs(cr->conn_info->handle); + cp.pkt_type = ptype; + + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LINK_CTL; + rq.ocf = OCF_SET_CONN_PTYPE; + rq.cparam = &cp; + rq.clen = SET_CONN_PTYPE_CP_SIZE; + rq.rparam = &rp; + rq.rlen = EVT_CONN_PTYPE_CHANGED_SIZE; + rq.event = EVT_CONN_PTYPE_CHANGED; + + if (hci_send_req(dd, &rq, 100) < 0) { + perror("Packet type change failed"); + exit(1); + } + + free(cr); + + hci_close_dev(dd); +} + +/* Get/Set link policy settings */ + +static struct option lp_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lp_help = + "Usage:\n" + "\tlp [link policy]\n"; + +static void cmd_lp(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + uint16_t policy; + int opt, dd; + + for_each_opt(opt, lp_options, NULL) { + switch (opt) { + default: + printf("%s", lp_help); + return; + } + } + helper_arg(1, 2, &argc, &argv, lp_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + if (argc == 1) { + char *str; + if (hci_read_link_policy(dd, htobs(cr->conn_info->handle), + &policy, 1000) < 0) { + perror("HCI read_link_policy_settings request failed"); + exit(1); + } + + policy = btohs(policy); + str = hci_lptostr(policy); + if (str) { + printf("Link policy settings: %s\n", str); + bt_free(str); + } else { + fprintf(stderr, "Invalig settings\n"); + exit(1); + } + } else { + unsigned int val; + if (hci_strtolp(argv[1], &val) < 0) { + fprintf(stderr, "Invalig arguments\n"); + exit(1); + } + policy = val; + + if (hci_write_link_policy(dd, htobs(cr->conn_info->handle), + htobs(policy), 1000) < 0) { + perror("HCI write_link_policy_settings request failed"); + exit(1); + } + } + + free(cr); + + hci_close_dev(dd); +} + +/* Get/Set link supervision timeout */ + +static struct option lst_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lst_help = + "Usage:\n" + "\tlst [new value in slots]\n"; + +static void cmd_lst(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + uint16_t timeout; + int opt, dd; + + for_each_opt(opt, lst_options, NULL) { + switch (opt) { + default: + printf("%s", lst_help); + return; + } + } + helper_arg(1, 2, &argc, &argv, lst_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + if (argc == 1) { + if (hci_read_link_supervision_timeout(dd, htobs(cr->conn_info->handle), + &timeout, 1000) < 0) { + perror("HCI read_link_supervision_timeout request failed"); + exit(1); + } + + timeout = btohs(timeout); + + if (timeout) + printf("Link supervision timeout: %u slots (%.2f msec)\n", + timeout, (float) timeout * 0.625); + else + printf("Link supervision timeout never expires\n"); + } else { + timeout = strtol(argv[1], NULL, 10); + + if (hci_write_link_supervision_timeout(dd, htobs(cr->conn_info->handle), + htobs(timeout), 1000) < 0) { + perror("HCI write_link_supervision_timeout request failed"); + exit(1); + } + } + + free(cr); + + hci_close_dev(dd); +} + +/* Request authentication */ + +static struct option auth_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *auth_help = + "Usage:\n" + "\tauth \n"; + +static void cmd_auth(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + int opt, dd; + + for_each_opt(opt, auth_options, NULL) { + switch (opt) { + default: + printf("%s", auth_help); + return; + } + } + helper_arg(1, 1, &argc, &argv, auth_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + if (hci_authenticate_link(dd, htobs(cr->conn_info->handle), 25000) < 0) { + perror("HCI authentication request failed"); + exit(1); + } + + free(cr); + + hci_close_dev(dd); +} + +/* Activate encryption */ + +static struct option enc_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *enc_help = + "Usage:\n" + "\tenc [encrypt enable]\n"; + +static void cmd_enc(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + uint8_t encrypt; + int opt, dd; + + for_each_opt(opt, enc_options, NULL) { + switch (opt) { + default: + printf("%s", enc_help); + return; + } + } + helper_arg(1, 2, &argc, &argv, enc_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + encrypt = (argc > 1) ? atoi(argv[1]) : 1; + + if (hci_encrypt_link(dd, htobs(cr->conn_info->handle), encrypt, 25000) < 0) { + perror("HCI set encryption request failed"); + exit(1); + } + + free(cr); + + hci_close_dev(dd); +} + +/* Change connection link key */ + +static struct option key_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *key_help = + "Usage:\n" + "\tkey \n"; + +static void cmd_key(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + int opt, dd; + + for_each_opt(opt, key_options, NULL) { + switch (opt) { + default: + printf("%s", key_help); + return; + } + } + helper_arg(1, 1, &argc, &argv, key_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + if (hci_change_link_key(dd, htobs(cr->conn_info->handle), 25000) < 0) { + perror("Changing link key failed"); + exit(1); + } + + free(cr); + + hci_close_dev(dd); +} + +/* Read clock offset */ + +static struct option clkoff_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *clkoff_help = + "Usage:\n" + "\tclkoff \n"; + +static void cmd_clkoff(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + uint16_t offset; + int opt, dd; + + for_each_opt(opt, clkoff_options, NULL) { + switch (opt) { + default: + printf("%s", clkoff_help); + return; + } + } + helper_arg(1, 1, &argc, &argv, clkoff_help); + + str2ba(argv[0], &bdaddr); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + exit(1); + } + + if (hci_read_clock_offset(dd, htobs(cr->conn_info->handle), &offset, 1000) < 0) { + perror("Reading clock offset failed"); + exit(1); + } + + printf("Clock offset: 0x%4.4x\n", btohs(offset)); + + free(cr); + + hci_close_dev(dd); +} + +/* Read clock */ + +static struct option clock_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *clock_help = + "Usage:\n" + "\tclock [bdaddr] [which clock]\n"; + +static void cmd_clock(int dev_id, int argc, char **argv) +{ + struct hci_conn_info_req *cr; + bdaddr_t bdaddr; + uint8_t which; + uint32_t handle, clock; + uint16_t accuracy; + int opt, dd; + + for_each_opt(opt, clock_options, NULL) { + switch (opt) { + default: + printf("%s", clock_help); + return; + } + } + helper_arg(0, 2, &argc, &argv, clock_help); + + if (argc > 0) + str2ba(argv[0], &bdaddr); + else + bacpy(&bdaddr, BDADDR_ANY); + + if (dev_id < 0 && !bacmp(&bdaddr, BDADDR_ANY)) + dev_id = hci_get_route(NULL); + + if (dev_id < 0) { + dev_id = hci_for_each_dev(HCI_UP, find_conn, (long) &bdaddr); + if (dev_id < 0) { + fprintf(stderr, "Not connected.\n"); + exit(1); + } + } + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("HCI device open failed"); + exit(1); + } + + if (bacmp(&bdaddr, BDADDR_ANY)) { + cr = malloc(sizeof(*cr) + sizeof(struct hci_conn_info)); + if (!cr) { + perror("Can't allocate memory"); + exit(1); + } + + bacpy(&cr->bdaddr, &bdaddr); + cr->type = ACL_LINK; + if (ioctl(dd, HCIGETCONNINFO, (unsigned long) cr) < 0) { + perror("Get connection info failed"); + free(cr); + exit(1); + } + + handle = htobs(cr->conn_info->handle); + which = (argc > 1) ? atoi(argv[1]) : 0x01; + + free(cr); + } else { + handle = 0x00; + which = 0x00; + } + + if (hci_read_clock(dd, handle, which, &clock, &accuracy, 1000) < 0) { + perror("Reading clock failed"); + exit(1); + } + + accuracy = btohs(accuracy); + + printf("Clock: 0x%4.4x\n", btohl(clock)); + printf("Accuracy: %.2f msec\n", (float) accuracy * 0.3125); + + hci_close_dev(dd); +} + +static int read_flags(uint8_t *flags, const uint8_t *data, size_t size) +{ + size_t offset; + + if (!flags || !data) + return -EINVAL; + + offset = 0; + while (offset < size) { + uint8_t len = data[offset]; + uint8_t type; + + /* Check if it is the end of the significant part */ + if (len == 0) + break; + + if (len + offset > size) + break; + + type = data[offset + 1]; + + if (type == FLAGS_AD_TYPE) { + *flags = data[offset + 2]; + return 0; + } + + offset += 1 + len; + } + + return -ENOENT; +} + +static int check_report_filter(uint8_t procedure, le_advertising_info *info) +{ + uint8_t flags; + + /* If no discovery procedure is set, all reports are treat as valid */ + if (procedure == 0) + return 1; + + /* Read flags AD type value from the advertising report if it exists */ + if (read_flags(&flags, info->data, info->length)) + return 0; + + switch (procedure) { + case 'l': /* Limited Discovery Procedure */ + if (flags & FLAGS_LIMITED_MODE_BIT) + return 1; + break; + case 'g': /* General Discovery Procedure */ + if (flags & (FLAGS_LIMITED_MODE_BIT | FLAGS_GENERAL_MODE_BIT)) + return 1; + break; + default: + fprintf(stderr, "Unknown discovery procedure\n"); + } + + return 0; +} + +static void sigint_handler(int sig) +{ + signal_received = sig; +} + +static void eir_parse_name(uint8_t *eir, size_t eir_len, + char *buf, size_t buf_len) +{ + size_t offset; + + offset = 0; + while (offset < eir_len) { + uint8_t field_len = eir[0]; + size_t name_len; + + /* Check for the end of EIR */ + if (field_len == 0) + break; + + if (offset + field_len > eir_len) + goto failed; + + switch (eir[1]) { + case EIR_NAME_SHORT: + case EIR_NAME_COMPLETE: + name_len = field_len - 1; + if (name_len > buf_len) + goto failed; + + memcpy(buf, &eir[2], name_len); + return; + } + + offset += field_len + 1; + eir += field_len + 1; + } + +failed: + snprintf(buf, buf_len, "(unknown)"); +} + +static int print_advertising_devices(int dd, uint8_t filter_type) +{ + unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr; + struct hci_filter nf, of; + struct sigaction sa; + socklen_t olen; + int len; + + olen = sizeof(of); + if (getsockopt(dd, SOL_HCI, HCI_FILTER, &of, &olen) < 0) { + printf("Could not get socket options\n"); + return -1; + } + + hci_filter_clear(&nf); + hci_filter_set_ptype(HCI_EVENT_PKT, &nf); + hci_filter_set_event(EVT_LE_META_EVENT, &nf); + + if (setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) { + printf("Could not set socket options\n"); + return -1; + } + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_NOCLDSTOP; + sa.sa_handler = sigint_handler; + sigaction(SIGINT, &sa, NULL); + + while (1) { + evt_le_meta_event *meta; + le_advertising_info *info; + char addr[18]; + + while ((len = read(dd, buf, sizeof(buf))) < 0) { + if (errno == EINTR && signal_received == SIGINT) { + len = 0; + goto done; + } + + if (errno == EAGAIN || errno == EINTR) + continue; + goto done; + } + + ptr = buf + (1 + HCI_EVENT_HDR_SIZE); + len -= (1 + HCI_EVENT_HDR_SIZE); + + meta = (void *) ptr; + + if (meta->subevent != 0x02) + goto done; + + /* Ignoring multiple reports */ + info = (le_advertising_info *) (meta->data + 1); + if (check_report_filter(filter_type, info)) { + char name[30]; + + memset(name, 0, sizeof(name)); + + ba2str(&info->bdaddr, addr); + eir_parse_name(info->data, info->length, + name, sizeof(name) - 1); + + printf("%s %s\n", addr, name); + } + } + +done: + setsockopt(dd, SOL_HCI, HCI_FILTER, &of, sizeof(of)); + + if (len < 0) + return -1; + + return 0; +} + +static struct option lescan_options[] = { + { "help", 0, 0, 'h' }, + { "static", 0, 0, 's' }, + { "privacy", 0, 0, 'p' }, + { "passive", 0, 0, 'P' }, + { "whitelist", 0, 0, 'w' }, + { "discovery", 1, 0, 'd' }, + { "duplicates", 0, 0, 'D' }, + { 0, 0, 0, 0 } +}; + +static const char *lescan_help = + "Usage:\n" + "\tlescan [--privacy] enable privacy\n" + "\tlescan [--passive] set scan type passive (default active)\n" + "\tlescan [--whitelist] scan for address in the whitelist only\n" + "\tlescan [--discovery=g|l] enable general or limited discovery" + "procedure\n" + "\tlescan [--duplicates] don't filter duplicates\n"; + +static void cmd_lescan(int dev_id, int argc, char **argv) +{ + int err, opt, dd; + uint8_t own_type = LE_PUBLIC_ADDRESS; + uint8_t scan_type = 0x01; + uint8_t filter_type = 0; + uint8_t filter_policy = 0x00; + uint16_t interval = htobs(0x0010); + uint16_t window = htobs(0x0010); + uint8_t filter_dup = 0x01; + + for_each_opt(opt, lescan_options, NULL) { + switch (opt) { + case 's': + own_type = LE_RANDOM_ADDRESS; + break; + case 'p': + own_type = LE_RANDOM_ADDRESS; + break; + case 'P': + scan_type = 0x00; /* Passive */ + break; + case 'w': + filter_policy = 0x01; /* Whitelist */ + break; + case 'd': + filter_type = optarg[0]; + if (filter_type != 'g' && filter_type != 'l') { + fprintf(stderr, "Unknown discovery procedure\n"); + exit(1); + } + + interval = htobs(0x0012); + window = htobs(0x0012); + break; + case 'D': + filter_dup = 0x00; + break; + default: + printf("%s", lescan_help); + return; + } + } + helper_arg(0, 1, &argc, &argv, lescan_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + err = hci_le_set_scan_parameters(dd, scan_type, interval, window, + own_type, filter_policy, 10000); + if (err < 0) { + perror("Set scan parameters failed"); + exit(1); + } + + err = hci_le_set_scan_enable(dd, 0x01, filter_dup, 10000); + if (err < 0) { + perror("Enable scan failed"); + exit(1); + } + + printf("LE Scan ...\n"); + + err = print_advertising_devices(dd, filter_type); + if (err < 0) { + perror("Could not receive advertising events"); + exit(1); + } + + err = hci_le_set_scan_enable(dd, 0x00, filter_dup, 10000); + if (err < 0) { + perror("Disable scan failed"); + exit(1); + } + + hci_close_dev(dd); +} + +static struct option leinfo_options[] = { + { "help", 0, 0, 'h' }, + { "static", 0, 0, 's' }, + { "random", 0, 0, 'r' }, + { 0, 0, 0, 0 } +}; + +static const char *leinfo_help = + "Usage:\n" + "\tleinfo [--static] [--random] \n"; + +static void cmd_leinfo(int dev_id, int argc, char **argv) +{ + bdaddr_t bdaddr; + uint16_t handle; + uint8_t features[8]; + struct hci_version version; + uint16_t interval, latency, max_ce_length, max_interval, min_ce_length; + uint16_t min_interval, supervision_timeout, window; + uint8_t initiator_filter, own_bdaddr_type, peer_bdaddr_type; + int opt, err, dd; + + own_bdaddr_type = LE_PUBLIC_ADDRESS; + peer_bdaddr_type = LE_PUBLIC_ADDRESS; + + for_each_opt(opt, leinfo_options, NULL) { + switch (opt) { + case 's': + own_bdaddr_type = LE_RANDOM_ADDRESS; + break; + case 'r': + peer_bdaddr_type = LE_RANDOM_ADDRESS; + break; + default: + printf("%s", leinfo_help); + return; + } + } + helper_arg(1, 1, &argc, &argv, leinfo_help); + + str2ba(argv[0], &bdaddr); + + printf("Requesting information ...\n"); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + interval = htobs(0x0004); + window = htobs(0x0004); + initiator_filter = 0; + min_interval = htobs(0x000F); + max_interval = htobs(0x000F); + latency = htobs(0x0000); + supervision_timeout = htobs(0x0C80); + min_ce_length = htobs(0x0000); + max_ce_length = htobs(0x0000); + + err = hci_le_create_conn(dd, interval, window, initiator_filter, + peer_bdaddr_type, bdaddr, own_bdaddr_type, min_interval, + max_interval, latency, supervision_timeout, + min_ce_length, max_ce_length, &handle, 25000); + if (err < 0) { + perror("Could not create connection"); + exit(1); + } + + printf("\tHandle: %d (0x%04x)\n", handle, handle); + + if (hci_read_remote_version(dd, handle, &version, 20000) == 0) { + char *ver = lmp_vertostr(version.lmp_ver); + printf("\tLMP Version: %s (0x%x) LMP Subversion: 0x%x\n" + "\tManufacturer: %s (%d)\n", + ver ? ver : "n/a", + version.lmp_ver, + version.lmp_subver, + bt_compidtostr(version.manufacturer), + version.manufacturer); + if (ver) + bt_free(ver); + } + + memset(features, 0, sizeof(features)); + hci_le_read_remote_features(dd, handle, features, 20000); + + printf("\tFeatures: 0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x " + "0x%2.2x 0x%2.2x 0x%2.2x 0x%2.2x\n", + features[0], features[1], features[2], features[3], + features[4], features[5], features[6], features[7]); + + usleep(10000); + hci_disconnect(dd, handle, HCI_OE_USER_ENDED_CONNECTION, 10000); + + hci_close_dev(dd); +} + +static struct option lecc_options[] = { + { "help", 0, 0, 'h' }, + { "static", 0, 0, 's' }, + { "random", 0, 0, 'r' }, + { "whitelist", 0, 0, 'w' }, + { 0, 0, 0, 0 } +}; + +static const char *lecc_help = + "Usage:\n" + "\tlecc [--static] [--random] \n" + "\tlecc --whitelist\n"; + +static void cmd_lecc(int dev_id, int argc, char **argv) +{ + int err, opt, dd; + bdaddr_t bdaddr; + uint16_t interval, latency, max_ce_length, max_interval, min_ce_length; + uint16_t min_interval, supervision_timeout, window, handle; + uint8_t initiator_filter, own_bdaddr_type, peer_bdaddr_type; + + own_bdaddr_type = LE_PUBLIC_ADDRESS; + peer_bdaddr_type = LE_PUBLIC_ADDRESS; + initiator_filter = 0; /* Use peer address */ + + for_each_opt(opt, lecc_options, NULL) { + switch (opt) { + case 's': + own_bdaddr_type = LE_RANDOM_ADDRESS; + break; + case 'r': + peer_bdaddr_type = LE_RANDOM_ADDRESS; + break; + case 'w': + initiator_filter = 0x01; /* Use white list */ + break; + default: + printf("%s", lecc_help); + return; + } + } + helper_arg(0, 1, &argc, &argv, lecc_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + memset(&bdaddr, 0, sizeof(bdaddr_t)); + if (argv[0]) + str2ba(argv[0], &bdaddr); + + interval = htobs(0x0004); + window = htobs(0x0004); + min_interval = htobs(0x000F); + max_interval = htobs(0x000F); + latency = htobs(0x0000); + supervision_timeout = htobs(0x0C80); + min_ce_length = htobs(0x0001); + max_ce_length = htobs(0x0001); + + err = hci_le_create_conn(dd, interval, window, initiator_filter, + peer_bdaddr_type, bdaddr, own_bdaddr_type, min_interval, + max_interval, latency, supervision_timeout, + min_ce_length, max_ce_length, &handle, 25000); + if (err < 0) { + perror("Could not create connection"); + exit(1); + } + + printf("Connection handle %d\n", handle); + + hci_close_dev(dd); +} + +static struct option lewladd_options[] = { + { "help", 0, 0, 'h' }, + { "random", 0, 0, 'r' }, + { 0, 0, 0, 0 } +}; + +static const char *lewladd_help = + "Usage:\n" + "\tlewladd [--random] \n"; + +static void cmd_lewladd(int dev_id, int argc, char **argv) +{ + int err, opt, dd; + bdaddr_t bdaddr; + uint8_t bdaddr_type = LE_PUBLIC_ADDRESS; + + for_each_opt(opt, lewladd_options, NULL) { + switch (opt) { + case 'r': + bdaddr_type = LE_RANDOM_ADDRESS; + break; + default: + printf("%s", lewladd_help); + return; + } + } + + helper_arg(1, 1, &argc, &argv, lewladd_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + str2ba(argv[0], &bdaddr); + + err = hci_le_add_white_list(dd, &bdaddr, bdaddr_type, 1000); + hci_close_dev(dd); + + if (err < 0) { + err = -errno; + fprintf(stderr, "Can't add to white list: %s(%d)\n", + strerror(-err), -err); + exit(1); + } +} + +static struct option lewlrm_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lewlrm_help = + "Usage:\n" + "\tlewlrm \n"; + +static void cmd_lewlrm(int dev_id, int argc, char **argv) +{ + int err, opt, dd; + bdaddr_t bdaddr; + + for_each_opt(opt, lewlrm_options, NULL) { + switch (opt) { + default: + printf("%s", lewlrm_help); + return; + } + } + + helper_arg(1, 1, &argc, &argv, lewlrm_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + str2ba(argv[0], &bdaddr); + + err = hci_le_rm_white_list(dd, &bdaddr, LE_PUBLIC_ADDRESS, 1000); + hci_close_dev(dd); + + if (err < 0) { + err = errno; + fprintf(stderr, "Can't remove from white list: %s(%d)\n", + strerror(err), err); + exit(1); + } +} + +static struct option lewlsz_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lewlsz_help = + "Usage:\n" + "\tlewlsz\n"; + +static void cmd_lewlsz(int dev_id, int argc, char **argv) +{ + int err, dd, opt; + uint8_t size; + + for_each_opt(opt, lewlsz_options, NULL) { + switch (opt) { + default: + printf("%s", lewlsz_help); + return; + } + } + + helper_arg(0, 0, &argc, &argv, lewlsz_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + err = hci_le_read_white_list_size(dd, &size, 1000); + hci_close_dev(dd); + + if (err < 0) { + err = -errno; + fprintf(stderr, "Can't read white list size: %s(%d)\n", + strerror(-err), -err); + exit(1); + } + + printf("White list size: %d\n", size); +} + +static struct option lewlclr_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lewlclr_help = + "Usage:\n" + "\tlewlclr\n"; + +static void cmd_lewlclr(int dev_id, int argc, char **argv) +{ + int err, dd, opt; + + for_each_opt(opt, lewlclr_options, NULL) { + switch (opt) { + default: + printf("%s", lewlclr_help); + return; + } + } + + helper_arg(0, 0, &argc, &argv, lewlclr_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + err = hci_le_clear_white_list(dd, 1000); + hci_close_dev(dd); + + if (err < 0) { + err = -errno; + fprintf(stderr, "Can't clear white list: %s(%d)\n", + strerror(-err), -err); + exit(1); + } +} + +static struct option lerladd_options[] = { + { "help", 0, 0, 'h' }, + { "random", 0, 0, 'r' }, + { "local", 1, 0, 'l' }, + { "peer", 1, 0, 'p' }, + { 0, 0, 0, 0 } +}; + +static const char *lerladd_help = + "Usage:\n" + "\tlerladd [--local irk] [--peer irk] [--random] \n"; + +static void cmd_lerladd(int dev_id, int argc, char **argv) +{ + int err, opt, dd; + bdaddr_t bdaddr; + uint8_t bdaddr_type = LE_PUBLIC_ADDRESS; + uint8_t local_irk[16], peer_irk[16]; + + memset(local_irk, 0, 16); + memset(peer_irk, 0, 16); + + for_each_opt(opt, lerladd_options, NULL) { + switch (opt) { + case 'r': + bdaddr_type = LE_RANDOM_ADDRESS; + break; + case 'l': + str2buf(optarg, local_irk, 16); + break; + case 'p': + str2buf(optarg, peer_irk, 16); + break; + default: + printf("%s", lerladd_help); + return; + } + } + + helper_arg(1, 1, &argc, &argv, lerladd_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + str2ba(argv[0], &bdaddr); + + err = hci_le_add_resolving_list(dd, &bdaddr, bdaddr_type, + peer_irk, local_irk, 1000); + hci_close_dev(dd); + + if (err < 0) { + err = -errno; + fprintf(stderr, "Can't add to resolving list: %s(%d)\n", + strerror(-err), -err); + exit(1); + } +} + +static struct option lerlrm_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lerlrm_help = + "Usage:\n" + "\tlerlrm \n"; + +static void cmd_lerlrm(int dev_id, int argc, char **argv) +{ + int err, opt, dd; + bdaddr_t bdaddr; + + for_each_opt(opt, lerlrm_options, NULL) { + switch (opt) { + default: + printf("%s", lerlrm_help); + return; + } + } + + helper_arg(1, 1, &argc, &argv, lerlrm_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + str2ba(argv[0], &bdaddr); + + err = hci_le_rm_resolving_list(dd, &bdaddr, LE_PUBLIC_ADDRESS, 1000); + hci_close_dev(dd); + + if (err < 0) { + err = errno; + fprintf(stderr, "Can't remove from resolving list: %s(%d)\n", + strerror(err), err); + exit(1); + } +} + +static struct option lerlclr_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lerlclr_help = + "Usage:\n" + "\tlerlclr\n"; + +static void cmd_lerlclr(int dev_id, int argc, char **argv) +{ + int err, dd, opt; + + for_each_opt(opt, lerlclr_options, NULL) { + switch (opt) { + default: + printf("%s", lerlclr_help); + return; + } + } + + helper_arg(0, 0, &argc, &argv, lerlclr_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + err = hci_le_clear_resolving_list(dd, 1000); + hci_close_dev(dd); + + if (err < 0) { + err = -errno; + fprintf(stderr, "Can't clear resolving list: %s(%d)\n", + strerror(-err), -err); + exit(1); + } +} + +static struct option lerlsz_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lerlsz_help = + "Usage:\n" + "\tlerlsz\n"; + +static void cmd_lerlsz(int dev_id, int argc, char **argv) +{ + int err, dd, opt; + uint8_t size; + + for_each_opt(opt, lerlsz_options, NULL) { + switch (opt) { + default: + printf("%s", lerlsz_help); + return; + } + } + + helper_arg(0, 0, &argc, &argv, lerlsz_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + err = hci_le_read_resolving_list_size(dd, &size, 1000); + hci_close_dev(dd); + + if (err < 0) { + err = -errno; + fprintf(stderr, "Can't read resolving list size: %s(%d)\n", + strerror(-err), -err); + exit(1); + } + + printf("Resolving list size: %d\n", size); +} + +static struct option lerlon_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lerlon_help = + "Usage:\n" + "\tlerlon\n"; + +static void cmd_lerlon(int dev_id, int argc, char **argv) +{ + int err, dd, opt; + + for_each_opt(opt, lerlon_options, NULL) { + switch (opt) { + default: + printf("%s", lerlon_help); + return; + } + } + + helper_arg(0, 0, &argc, &argv, lerlon_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + err = hci_le_set_address_resolution_enable(dd, 0x01, 1000); + hci_close_dev(dd); + + if (err < 0) { + err = -errno; + fprintf(stderr, "Can't set address resolution enable: %s(%d)\n", + strerror(-err), -err); + exit(1); + } +} + +static struct option lerloff_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *lerloff_help = + "Usage:\n" + "\tlerloff\n"; + +static void cmd_lerloff(int dev_id, int argc, char **argv) +{ + int err, dd, opt; + + for_each_opt(opt, lerloff_options, NULL) { + switch (opt) { + default: + printf("%s", lerloff_help); + return; + } + } + + helper_arg(0, 0, &argc, &argv, lerloff_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + err = hci_le_set_address_resolution_enable(dd, 0x00, 1000); + hci_close_dev(dd); + + if (err < 0) { + err = -errno; + fprintf(stderr, "Can't set address resolution enable: %s(%d)\n", + strerror(-err), -err); + exit(1); + } +} + +static struct option ledc_options[] = { + { "help", 0, 0, 'h' }, + { 0, 0, 0, 0 } +}; + +static const char *ledc_help = + "Usage:\n" + "\tledc [reason]\n"; + +static void cmd_ledc(int dev_id, int argc, char **argv) +{ + int err, opt, dd; + uint16_t handle; + uint8_t reason; + + for_each_opt(opt, ledc_options, NULL) { + switch (opt) { + default: + printf("%s", ledc_help); + return; + } + } + helper_arg(1, 2, &argc, &argv, ledc_help); + + if (dev_id < 0) + dev_id = hci_get_route(NULL); + + dd = hci_open_dev(dev_id); + if (dd < 0) { + perror("Could not open device"); + exit(1); + } + + handle = atoi(argv[0]); + + reason = (argc > 1) ? atoi(argv[1]) : HCI_OE_USER_ENDED_CONNECTION; + + err = hci_disconnect(dd, handle, reason, 10000); + if (err < 0) { + perror("Could not disconnect"); + exit(1); + } + + hci_close_dev(dd); +} + +static struct option lecup_options[] = { + { "help", 0, 0, 'h' }, + { "handle", 1, 0, 'H' }, + { "min", 1, 0, 'm' }, + { "max", 1, 0, 'M' }, + { "latency", 1, 0, 'l' }, + { "timeout", 1, 0, 't' }, + { 0, 0, 0, 0 } +}; + +static const char *lecup_help = + "Usage:\n" + "\tlecup \n" + "\tOptions:\n" + "\t --handle=<0xXXXX> LE connection handle\n" + "\t --min= Range: 0x0006 to 0x0C80\n" + "\t --max= Range: 0x0006 to 0x0C80\n" + "\t --latency= Slave latency. Range: 0x0000 to 0x03E8\n" + "\t --timeout=